aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/appmon/doc/src/notes.xml15
-rw-r--r--lib/appmon/src/appmon.erl15
-rw-r--r--lib/appmon/src/appmon_info.erl21
-rw-r--r--lib/appmon/src/appmon_place.erl14
-rw-r--r--lib/appmon/vsn.mk2
-rw-r--r--lib/asn1/c_src/Makefile2
-rw-r--r--lib/asn1/doc/src/notes.xml19
-rw-r--r--lib/asn1/src/asn1ct.erl1
-rw-r--r--lib/asn1/vsn.mk11
-rw-r--r--lib/common_test/Makefile16
-rw-r--r--lib/common_test/doc/src/Makefile3
-rw-r--r--lib/common_test/doc/src/common_test_app.xml10
-rw-r--r--lib/common_test/doc/src/config_file_chapter.xml252
-rw-r--r--lib/common_test/doc/src/ct_master_chapter.xml62
-rw-r--r--lib/common_test/doc/src/event_handler_chapter.xml22
-rw-r--r--lib/common_test/doc/src/install_chapter.xml139
-rw-r--r--lib/common_test/doc/src/notes.xml146
-rw-r--r--lib/common_test/doc/src/ref_man.xml7
-rw-r--r--lib/common_test/doc/src/run_test.xml108
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml78
-rw-r--r--lib/common_test/doc/src/test_structure_chapter.xml10
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml44
-rw-r--r--lib/common_test/src/Makefile16
-rw-r--r--lib/common_test/src/common_test.app.src17
-rw-r--r--lib/common_test/src/ct.erl141
-rw-r--r--lib/common_test/src/ct_config.erl786
-rw-r--r--lib/common_test/src/ct_config_plain.erl109
-rw-r--r--lib/common_test/src/ct_config_xml.erl118
-rw-r--r--lib/common_test/src/ct_framework.erl43
-rw-r--r--lib/common_test/src/ct_gen_conn.erl14
-rw-r--r--lib/common_test/src/ct_logs.erl16
-rw-r--r--lib/common_test/src/ct_master.erl142
-rw-r--r--lib/common_test/src/ct_master_logs.erl14
-rw-r--r--lib/common_test/src/ct_repeat.erl12
-rw-r--r--lib/common_test/src/ct_run.erl1682
-rw-r--r--lib/common_test/src/ct_slave.erl439
-rw-r--r--lib/common_test/src/ct_snmp.erl22
-rw-r--r--lib/common_test/src/ct_ssh.erl26
-rw-r--r--lib/common_test/src/ct_telnet_client.erl61
-rw-r--r--lib/common_test/src/ct_testspec.erl315
-rw-r--r--lib/common_test/src/ct_util.erl653
-rw-r--r--lib/common_test/src/ct_util.hrl5
-rw-r--r--lib/common_test/src/vts.erl24
-rw-r--r--lib/common_test/test/Makefile12
-rw-r--r--lib/common_test/test/ct_config_SUITE.erl255
-rw-r--r--lib/common_test/test/ct_config_SUITE_data/config/config.txt31
-rw-r--r--lib/common_test/test/ct_config_SUITE_data/config/config.xml27
-rw-r--r--lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl46
-rw-r--r--lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl145
-rw-r--r--lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl93
-rw-r--r--lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl123
-rw-r--r--lib/common_test/test/ct_error_SUITE.erl243
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl123
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl134
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl88
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl47
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl46
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl3
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl194
-rw-r--r--lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl138
-rw-r--r--lib/common_test/test/ct_event_handler_SUITE.erl9
-rw-r--r--lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl23
-rw-r--r--lib/common_test/test/ct_groups_test_1_SUITE.erl108
-rw-r--r--lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl10
-rw-r--r--lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl6
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE.erl170
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg1
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl105
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl281
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl314
-rw-r--r--lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec26
-rw-r--r--lib/common_test/test/ct_master_SUITE.erl136
-rw-r--r--lib/common_test/test/ct_master_SUITE_data/master/config.txt2
-rw-r--r--lib/common_test/test/ct_master_SUITE_data/master/config.xml4
-rw-r--r--lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl57
-rw-r--r--lib/common_test/test/ct_misc_1_SUITE.erl165
-rw-r--r--lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl134
-rw-r--r--lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl134
-rw-r--r--lib/common_test/test/ct_skip_SUITE.erl17
-rw-r--r--lib/common_test/test/ct_smoke_test_SUITE.erl52
-rw-r--r--lib/common_test/test/ct_test_server_if_1_SUITE.erl17
-rw-r--r--lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl8
-rw-r--r--lib/common_test/test/ct_test_support.erl120
-rw-r--r--lib/common_test/test/ct_test_support_eh.erl27
-rw-r--r--lib/common_test/test/ct_userconfig_callback.erl32
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/doc/src/compile.xml61
-rw-r--r--lib/compiler/doc/src/notes.xml100
-rw-r--r--lib/compiler/src/Makefile1
-rw-r--r--lib/compiler/src/beam_asm.erl21
-rw-r--r--lib/compiler/src/beam_block.erl1
-rw-r--r--lib/compiler/src/beam_bool.erl21
-rw-r--r--lib/compiler/src/beam_dead.erl58
-rw-r--r--lib/compiler/src/beam_dict.erl4
-rw-r--r--lib/compiler/src/beam_disasm.erl33
-rw-r--r--lib/compiler/src/beam_peep.erl58
-rw-r--r--lib/compiler/src/beam_receive.erl388
-rw-r--r--lib/compiler/src/beam_type.erl6
-rw-r--r--lib/compiler/src/beam_utils.erl16
-rw-r--r--lib/compiler/src/beam_validator.erl10
-rw-r--r--lib/compiler/src/cerl.erl13
-rw-r--r--lib/compiler/src/cerl_inline.erl7
-rw-r--r--lib/compiler/src/cerl_trees.erl16
-rw-r--r--lib/compiler/src/compile.erl63
-rw-r--r--lib/compiler/src/compiler.app.src11
-rw-r--r--lib/compiler/src/erl_bifs.erl12
-rw-r--r--lib/compiler/src/genop.tab8
-rw-r--r--lib/compiler/src/rec_env.erl14
-rw-r--r--lib/compiler/src/sys_core_fold.erl72
-rw-r--r--lib/compiler/src/sys_pre_expand.erl28
-rw-r--r--lib/compiler/src/v3_core.erl73
-rw-r--r--lib/compiler/src/v3_kernel.erl19
-rw-r--r--lib/compiler/src/v3_life.erl2
-rw-r--r--lib/compiler/test/andor_SUITE.erl24
-rw-r--r--lib/compiler/test/bs_match_SUITE.erl24
-rw-r--r--lib/compiler/test/compile_SUITE.erl46
-rw-r--r--lib/compiler/test/compiler.cover2
-rw-r--r--lib/compiler/test/core_SUITE_data/.gitignore1
-rw-r--r--lib/compiler/test/error_SUITE.erl166
-rw-r--r--lib/compiler/test/float_SUITE.erl36
-rw-r--r--lib/compiler/test/guard_SUITE.erl146
-rw-r--r--lib/compiler/test/inline_SUITE_data/decode1.erl48
-rw-r--r--lib/compiler/test/match_SUITE.erl41
-rw-r--r--lib/compiler/test/misc_SUITE.erl90
-rw-r--r--lib/compiler/test/num_bif_SUITE.erl14
-rw-r--r--lib/compiler/test/receive_SUITE.erl63
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/no_1.erl99
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/no_2.erl26
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/no_3.erl14
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_1.erl12
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_2.erl13
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_3.erl16
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_4.erl16
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_5.erl46
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_6.erl15
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_7.erl12
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_8.erl15
-rw-r--r--lib/compiler/test/receive_SUITE_data/ref_opt/yes_9.erl13
-rw-r--r--lib/compiler/test/record_SUITE.erl39
-rw-r--r--lib/compiler/test/test_lib.erl40
-rw-r--r--lib/compiler/test/warnings_SUITE.erl17
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/Makefile.in20
-rw-r--r--lib/crypto/c_src/crypto.c1547
-rw-r--r--lib/crypto/c_src/crypto_drv.c1817
-rw-r--r--lib/crypto/doc/src/crypto.xml25
-rw-r--r--lib/crypto/doc/src/notes.xml37
-rw-r--r--lib/crypto/priv/Makefile28
-rw-r--r--lib/crypto/src/Makefile12
-rw-r--r--lib/crypto/src/crypto.app.src12
-rw-r--r--lib/crypto/src/crypto.erl585
-rw-r--r--lib/crypto/src/crypto_server.erl88
-rw-r--r--lib/crypto/test/blowfish_SUITE.erl15
-rw-r--r--lib/crypto/test/crypto_SUITE.erl52
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/debugger/doc/src/notes.xml24
-rw-r--r--lib/debugger/src/dbg_icmd.erl29
-rw-r--r--lib/debugger/src/dbg_ieval.erl53
-rw-r--r--lib/debugger/src/dbg_iload.erl59
-rw-r--r--lib/debugger/src/dbg_iserver.erl50
-rw-r--r--lib/debugger/src/dbg_ui_break_win.erl7
-rw-r--r--lib/debugger/src/dbg_ui_filedialog_win.erl13
-rw-r--r--lib/debugger/src/dbg_ui_mon.erl21
-rw-r--r--lib/debugger/src/dbg_ui_mon_win.erl28
-rw-r--r--lib/debugger/src/dbg_ui_trace_win.erl237
-rw-r--r--lib/debugger/src/dbg_ui_view.erl6
-rw-r--r--lib/debugger/src/dbg_ui_win.erl23
-rw-r--r--lib/debugger/src/dbg_ui_winman.erl12
-rw-r--r--lib/debugger/src/dbg_wx_break_win.erl6
-rw-r--r--lib/debugger/src/dbg_wx_interpret.erl10
-rw-r--r--lib/debugger/src/dbg_wx_mon.erl19
-rw-r--r--lib/debugger/src/dbg_wx_mon_win.erl11
-rw-r--r--lib/debugger/src/dbg_wx_trace.erl50
-rwxr-xr-xlib/debugger/src/dbg_wx_trace_win.erl34
-rw-r--r--lib/debugger/src/dbg_wx_view.erl39
-rwxr-xr-xlib/debugger/src/dbg_wx_winman.erl12
-rw-r--r--lib/debugger/src/i.erl8
-rw-r--r--lib/debugger/src/int.erl10
-rw-r--r--lib/debugger/vsn.mk2
-rw-r--r--lib/dialyzer/RELEASE_NOTES13
-rwxr-xr-xlib/dialyzer/doc/src/notes.xml35
-rw-r--r--lib/dialyzer/src/dialyzer.erl36
-rw-r--r--lib/dialyzer/src/dialyzer_analysis_callgraph.erl79
-rw-r--r--lib/dialyzer/src/dialyzer_behaviours.erl76
-rw-r--r--lib/dialyzer/src/dialyzer_callgraph.erl2
-rw-r--r--lib/dialyzer/src/dialyzer_cl.erl41
-rw-r--r--lib/dialyzer/src/dialyzer_codeserver.erl78
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl104
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl465
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl2
-rw-r--r--lib/dialyzer/src/dialyzer_plt.erl117
-rw-r--r--lib/dialyzer/src/dialyzer_races.erl64
-rw-r--r--lib/dialyzer/src/dialyzer_succ_typings.erl2
-rw-r--r--lib/dialyzer/src/dialyzer_typesig.erl393
-rw-r--r--lib/dialyzer/src/dialyzer_utils.erl67
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/docbuilder/src/docb_edoc_xml_cb.erl64
-rw-r--r--lib/docbuilder/src/docb_html.erl27
-rw-r--r--lib/docbuilder/src/docb_html_util.erl17
-rw-r--r--lib/docbuilder/src/docb_main.erl27
-rw-r--r--lib/docbuilder/src/docb_pretty_format.erl4
-rw-r--r--lib/docbuilder/src/docb_tr_application2html.erl12
-rw-r--r--lib/docbuilder/src/docb_tr_cite2html.erl22
-rw-r--r--lib/docbuilder/src/docb_tr_index2html.erl4
-rw-r--r--lib/docbuilder/src/docb_tr_part2html.erl15
-rw-r--r--lib/docbuilder/src/docb_tr_term2html.erl22
-rw-r--r--lib/docbuilder/src/docb_transform.erl4
-rw-r--r--lib/docbuilder/src/docb_util.erl8
-rw-r--r--lib/docbuilder/src/docb_xmerl_tree_cb.erl4
-rw-r--r--lib/erl_docgen/doc/src/notes.xml20
-rw-r--r--lib/erl_docgen/priv/xsl/db_man.xsl10
-rw-r--r--lib/erl_docgen/vsn.mk2
-rw-r--r--lib/erl_interface/doc/src/ei.xml2
-rw-r--r--lib/erl_interface/doc/src/notes.xml39
-rw-r--r--lib/erl_interface/include/ei.h13
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c18
-rw-r--r--lib/erl_interface/src/connect/ei_connect_int.h11
-rw-r--r--lib/erl_interface/src/decode/decode_double.c30
-rw-r--r--lib/erl_interface/src/decode/decode_skip.c11
-rw-r--r--lib/erl_interface/src/encode/encode_double.c20
-rw-r--r--lib/erl_interface/src/legacy/decode_term.c11
-rw-r--r--lib/erl_interface/src/legacy/erl_marshal.c39
-rw-r--r--lib/erl_interface/src/misc/ei_decode_term.c23
-rw-r--r--lib/erl_interface/src/misc/ei_printterm.c11
-rw-r--r--lib/erl_interface/src/misc/get_type.c17
-rw-r--r--lib/erl_interface/src/misc/putget.h38
-rw-r--r--lib/erl_interface/src/misc/show_msg.c11
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE.erl29
-rw-r--r--lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c19
-rw-r--r--lib/erl_interface/test/ei_encode_SUITE.erl30
-rw-r--r--lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c14
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE.erl14
-rw-r--r--lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c12
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/gs/contribs/bonk/bonk.erl26
-rw-r--r--lib/gs/contribs/othello/othello_adt.erl36
-rw-r--r--lib/gs/doc/src/notes.xml23
-rw-r--r--lib/gs/src/tool_utils.erl23
-rw-r--r--lib/gs/vsn.mk2
-rw-r--r--lib/hipe/cerl/cerl_closurean.erl14
-rw-r--r--lib/hipe/cerl/cerl_messagean.erl16
-rw-r--r--lib/hipe/cerl/erl_bif_types.erl243
-rw-r--r--lib/hipe/cerl/erl_types.erl345
-rw-r--r--lib/hipe/doc/src/notes.xml62
-rw-r--r--lib/hipe/flow/hipe_dominators.erl12
-rw-r--r--lib/hipe/icode/hipe_beam_to_icode.erl29
-rw-r--r--lib/hipe/icode/hipe_icode.erl32
-rw-r--r--lib/hipe/util/hipe_digraph.erl12
-rw-r--r--lib/hipe/vsn.mk2
-rw-r--r--lib/ic/doc/src/Makefile16
-rw-r--r--lib/ic/doc/src/notes.xml22
-rw-r--r--lib/ic/vsn.mk6
-rw-r--r--lib/inets/doc/src/http_server.xml13
-rw-r--r--lib/inets/doc/src/httpc.xml19
-rw-r--r--lib/inets/doc/src/httpd.xml66
-rw-r--r--lib/inets/doc/src/mod_esi.xml3
-rw-r--r--lib/inets/doc/src/notes.xml169
-rw-r--r--lib/inets/examples/Makefile194
-rw-r--r--lib/inets/examples/httpd_load_test/Makefile123
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt.config.skel20
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt.erl74
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt.sh.skel44
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_client.erl370
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_ctrl.erl1530
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_logger.erl138
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_logger.hrl33
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_random_html.erl59
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_server.erl163
-rw-r--r--lib/inets/examples/httpd_load_test/hdlt_slave.erl291
l---------lib/inets/examples/httpd_load_test/hdlt_ssl_client_cert.pem1
l---------lib/inets/examples/httpd_load_test/hdlt_ssl_server_cert.pem1
-rw-r--r--lib/inets/examples/httpd_load_test/modules.mk44
-rw-r--r--lib/inets/examples/server_root/Makefile209
-rw-r--r--lib/inets/examples/subdirs.mk3
-rw-r--r--lib/inets/src/ftp/Makefile28
-rw-r--r--lib/inets/src/ftp/ftp.erl58
-rw-r--r--lib/inets/src/ftp/ftp_internal.hrl13
-rw-r--r--lib/inets/src/http_client/Makefile26
-rw-r--r--lib/inets/src/http_client/http.erl35
-rw-r--r--lib/inets/src/http_client/httpc.erl44
-rw-r--r--lib/inets/src/http_client/httpc_cookie.erl12
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl173
-rw-r--r--lib/inets/src/http_client/httpc_internal.hrl14
-rw-r--r--lib/inets/src/http_client/httpc_manager.erl34
-rw-r--r--lib/inets/src/http_client/httpc_request.erl15
-rw-r--r--lib/inets/src/http_client/httpc_response.erl2
-rw-r--r--lib/inets/src/http_lib/Makefile27
-rw-r--r--lib/inets/src/http_lib/http_internal.hrl27
-rw-r--r--lib/inets/src/http_lib/http_transport.erl219
-rw-r--r--lib/inets/src/http_lib/http_util.erl106
-rw-r--r--lib/inets/src/http_server/Makefile26
-rw-r--r--lib/inets/src/http_server/httpd.erl279
-rw-r--r--lib/inets/src/http_server/httpd_acceptor.erl16
-rw-r--r--lib/inets/src/http_server/httpd_cgi.erl13
-rw-r--r--lib/inets/src/http_server/httpd_conf.erl98
-rw-r--r--lib/inets/src/http_server/httpd_esi.erl13
-rw-r--r--lib/inets/src/http_server/httpd_instance_sup.erl6
-rw-r--r--lib/inets/src/http_server/httpd_internal.hrl13
-rw-r--r--lib/inets/src/http_server/httpd_manager.erl38
-rw-r--r--lib/inets/src/http_server/httpd_request.erl66
-rw-r--r--lib/inets/src/http_server/httpd_request_handler.erl104
-rw-r--r--lib/inets/src/http_server/httpd_sup.erl10
-rw-r--r--lib/inets/src/http_server/httpd_util.erl21
-rw-r--r--lib/inets/src/http_server/mod_alias.erl73
-rw-r--r--lib/inets/src/http_server/mod_esi.erl43
-rw-r--r--lib/inets/src/inets_app/Makefile18
-rw-r--r--lib/inets/src/inets_app/inets.app.src1
-rw-r--r--lib/inets/src/inets_app/inets.appup.src36
-rw-r--r--lib/inets/src/inets_app/inets.erl2
-rw-r--r--lib/inets/src/inets_app/inets.mk45
-rw-r--r--lib/inets/src/inets_app/inets_service.erl12
-rw-r--r--lib/inets/src/tftp/Makefile22
-rw-r--r--lib/inets/test/Makefile9
-rw-r--r--lib/inets/test/ftp_suite_lib.erl88
-rw-r--r--lib/inets/test/http_format_SUITE.erl16
-rw-r--r--lib/inets/test/httpc_SUITE.erl515
-rw-r--r--lib/inets/test/httpd_SUITE.erl1655
-rw-r--r--lib/inets/test/httpd_SUITE_data/server_root/Makefile209
-rw-r--r--lib/inets/test/httpd_block.erl101
-rw-r--r--lib/inets/test/httpd_mod.erl136
-rw-r--r--lib/inets/test/httpd_poll.erl66
-rw-r--r--lib/inets/test/httpd_test_data/server_root/Makefile209
-rw-r--r--lib/inets/test/httpd_test_lib.erl43
-rw-r--r--lib/inets/test/httpd_time_test.erl65
-rw-r--r--lib/inets/test/inets_sup_SUITE.erl4
-rw-r--r--lib/inets/test/inets_test_lib.erl218
-rw-r--r--lib/inets/test/inets_test_lib.hrl15
-rw-r--r--lib/inets/vsn.mk26
-rw-r--r--lib/jinterface/java_src/Makefile16
-rw-r--r--lib/jinterface/java_src/pom.xml.src106
-rw-r--r--lib/kernel/doc/src/file.xml59
-rw-r--r--lib/kernel/doc/src/gen_sctp.xml2
-rw-r--r--lib/kernel/doc/src/notes.xml115
-rw-r--r--lib/kernel/src/application_controller.erl191
-rw-r--r--lib/kernel/src/application_starter.erl22
-rw-r--r--lib/kernel/src/auth.erl20
-rw-r--r--lib/kernel/src/code.erl8
-rw-r--r--lib/kernel/src/code_server.erl147
-rw-r--r--lib/kernel/src/dist_util.erl15
-rw-r--r--lib/kernel/src/erl_boot_server.erl18
-rw-r--r--lib/kernel/src/erl_epmd.erl4
-rw-r--r--lib/kernel/src/error_handler.erl18
-rw-r--r--lib/kernel/src/file.erl45
-rw-r--r--lib/kernel/src/file_io_server.erl61
-rw-r--r--lib/kernel/src/group.erl48
-rw-r--r--lib/kernel/src/heart.erl19
-rw-r--r--lib/kernel/src/inet.erl2
-rw-r--r--lib/kernel/src/inet6_tcp_dist.erl35
-rw-r--r--lib/kernel/src/inet_db.erl50
-rw-r--r--lib/kernel/src/inet_dns.erl44
-rw-r--r--lib/kernel/src/inet_gethost_native.erl15
-rw-r--r--lib/kernel/src/inet_res.erl11
-rw-r--r--lib/kernel/src/kernel_config.erl4
-rw-r--r--lib/kernel/src/net_kernel.erl201
-rw-r--r--lib/kernel/src/os.erl31
-rw-r--r--lib/kernel/src/pg2.erl4
-rw-r--r--lib/kernel/src/ram_file.erl48
-rw-r--r--lib/kernel/src/rpc.erl25
-rw-r--r--lib/kernel/src/standard_error.erl24
-rw-r--r--lib/kernel/src/user.erl21
-rw-r--r--lib/kernel/test/code_SUITE.erl40
-rw-r--r--lib/kernel/test/file_SUITE.erl212
-rw-r--r--lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c2
-rw-r--r--lib/kernel/test/os_SUITE.erl7
-rw-r--r--lib/kernel/test/pg2_SUITE.erl135
-rw-r--r--lib/kernel/test/prim_file_SUITE.erl130
-rw-r--r--lib/kernel/vsn.mk6
-rw-r--r--lib/megaco/doc/src/Makefile18
-rw-r--r--lib/megaco/doc/src/files.mk14
-rw-r--r--lib/megaco/doc/src/megaco.xml16
-rw-r--r--lib/megaco/doc/src/megaco_performance.xml122
-rw-r--r--lib/megaco/doc/src/megaco_user.xml3
-rw-r--r--lib/megaco/doc/src/mstone1-s8flex.log234
-rw-r--r--lib/megaco/doc/src/mstone1.gifbin30004 -> 0 bytes
-rw-r--r--lib/megaco/doc/src/mstone1.jpgbin17845 -> 16746 bytes
-rw-r--r--lib/megaco/doc/src/mstone1.pngbin53099 -> 0 bytes
-rw-r--r--lib/megaco/doc/src/mstone1.ps1959
-rw-r--r--lib/megaco/doc/src/notes.xml551
-rw-r--r--lib/megaco/doc/src/notes_history.xml413
-rw-r--r--lib/megaco/src/app/megaco.appup.src34
-rw-r--r--lib/megaco/src/app/megaco_internal.hrl26
-rw-r--r--lib/megaco/src/engine/megaco_config.erl427
-rw-r--r--lib/megaco/src/engine/megaco_messenger.erl239
-rw-r--r--lib/megaco/src/engine/megaco_monitor.erl33
-rw-r--r--lib/megaco/src/flex/Makefile.in2
-rw-r--r--lib/megaco/test/megaco_config_test.erl362
-rw-r--r--lib/megaco/vsn.mk8
-rw-r--r--lib/mnesia/doc/src/notes.xml16
-rw-r--r--lib/mnesia/examples/mnesia_meter.erl12
-rw-r--r--lib/mnesia/src/mnesia.appup.src64
-rw-r--r--lib/mnesia/src/mnesia_controller.erl62
-rw-r--r--lib/mnesia/src/mnesia_lib.erl40
-rw-r--r--lib/mnesia/src/mnesia_recover.erl41
-rw-r--r--lib/mnesia/src/mnesia_schema.erl33
-rw-r--r--lib/mnesia/test/Makefile118
-rw-r--r--lib/mnesia/test/README107
-rw-r--r--lib/mnesia/test/mnesia.spec23
-rw-r--r--lib/mnesia/test/mnesia.spec.vxworks362
-rw-r--r--lib/mnesia/test/mnesia_SUITE.erl203
-rw-r--r--lib/mnesia/test/mnesia_atomicity_test.erl839
-rw-r--r--lib/mnesia/test/mnesia_config_backup.erl105
-rw-r--r--lib/mnesia/test/mnesia_config_event.erl74
-rw-r--r--lib/mnesia/test/mnesia_config_test.erl1466
-rw-r--r--lib/mnesia/test/mnesia_consistency_test.erl1612
-rw-r--r--lib/mnesia/test/mnesia_cost.erl222
-rw-r--r--lib/mnesia/test/mnesia_dbn_meters.erl242
-rw-r--r--lib/mnesia/test/mnesia_dirty_access_test.erl927
-rw-r--r--lib/mnesia/test/mnesia_durability_test.erl1470
-rw-r--r--lib/mnesia/test/mnesia_evil_backup.erl750
-rw-r--r--lib/mnesia/test/mnesia_evil_coverage_test.erl2401
-rw-r--r--lib/mnesia/test/mnesia_examples_test.erl160
-rw-r--r--lib/mnesia/test/mnesia_frag_test.erl875
-rw-r--r--lib/mnesia/test/mnesia_inconsistent_database_test.erl74
-rw-r--r--lib/mnesia/test/mnesia_install_test.erl342
-rw-r--r--lib/mnesia/test/mnesia_isolation_test.erl2419
-rw-r--r--lib/mnesia/test/mnesia_measure_test.erl203
-rw-r--r--lib/mnesia/test/mnesia_meter.erl465
-rw-r--r--lib/mnesia/test/mnesia_nice_coverage_test.erl227
-rw-r--r--lib/mnesia/test/mnesia_qlc_test.erl475
-rw-r--r--lib/mnesia/test/mnesia_recovery_test.erl1701
-rw-r--r--lib/mnesia/test/mnesia_registry_test.erl137
-rw-r--r--lib/mnesia/test/mnesia_schema_recovery_test.erl787
-rw-r--r--lib/mnesia/test/mnesia_test_lib.erl1058
-rw-r--r--lib/mnesia/test/mnesia_test_lib.hrl132
-rw-r--r--lib/mnesia/test/mnesia_tpcb.erl1268
-rw-r--r--lib/mnesia/test/mnesia_trans_access_test.erl1254
-rwxr-xr-xlib/mnesia/test/mt60
-rw-r--r--lib/mnesia/test/mt.erl262
-rw-r--r--lib/mnesia/vsn.mk5
-rw-r--r--lib/observer/doc/src/notes.xml15
-rw-r--r--lib/observer/vsn.mk2
-rw-r--r--lib/odbc/test/Makefile114
-rw-r--r--lib/odbc/test/README86
-rw-r--r--lib/odbc/test/odbc.dynspec31
-rw-r--r--lib/odbc/test/odbc.spec9
-rw-r--r--lib/odbc/test/odbc.spec.win5
-rw-r--r--lib/odbc/test/odbc_connect_SUITE.erl816
-rw-r--r--lib/odbc/test/odbc_data_type_SUITE.erl1498
-rw-r--r--lib/odbc/test/odbc_query_SUITE.erl1453
-rw-r--r--lib/odbc/test/odbc_start_SUITE.erl147
-rw-r--r--lib/odbc/test/odbc_test.hrl37
-rw-r--r--lib/odbc/test/odbc_test_lib.erl77
-rw-r--r--lib/odbc/test/oracle.erl246
-rw-r--r--lib/odbc/test/postgres.erl294
-rw-r--r--lib/odbc/test/sqlserver.erl298
-rw-r--r--lib/parsetools/doc/src/notes.xml22
-rw-r--r--lib/parsetools/src/leex.erl53
-rw-r--r--lib/parsetools/vsn.mk2
-rw-r--r--lib/public_key/asn1/OTP-PKIX.asn12
-rw-r--r--lib/public_key/doc/src/notes.xml50
-rw-r--r--lib/public_key/src/pubkey_cert.erl68
-rw-r--r--lib/public_key/src/pubkey_cert_records.erl398
-rw-r--r--lib/public_key/src/pubkey_crypto.erl11
-rw-r--r--lib/public_key/src/pubkey_pem.erl28
-rw-r--r--lib/public_key/src/public_key.app.src3
-rw-r--r--lib/public_key/src/public_key.appup.src32
-rw-r--r--lib/public_key/src/public_key.erl19
-rw-r--r--lib/public_key/test/Makefile6
-rw-r--r--lib/public_key/test/pkey_test.erl412
-rw-r--r--lib/public_key/test/public_key.cover2
-rw-r--r--lib/public_key/test/public_key_SUITE.erl236
-rw-r--r--lib/public_key/vsn.mk10
-rwxr-xr-xlib/reltool/bin/reltool.escript249
-rw-r--r--lib/reltool/doc/src/notes.xml67
-rw-r--r--lib/reltool/doc/src/reltool.xml185
-rw-r--r--lib/reltool/doc/src/reltool_examples.xml4
-rw-r--r--lib/reltool/src/Makefile42
-rw-r--r--lib/reltool/src/files.mk32
-rw-r--r--lib/reltool/src/reltool.app.src45
-rw-r--r--lib/reltool/src/reltool.appup.src22
-rw-r--r--lib/reltool/src/reltool.erl97
-rw-r--r--lib/reltool/src/reltool.hrl142
-rw-r--r--lib/reltool/src/reltool_mod_win.erl10
-rw-r--r--lib/reltool/src/reltool_server.erl561
-rw-r--r--lib/reltool/src/reltool_sys_win.erl2
-rw-r--r--lib/reltool/src/reltool_target.erl493
-rw-r--r--lib/reltool/src/reltool_utils.erl82
-rw-r--r--lib/reltool/test/Makefile11
-rw-r--r--lib/reltool/test/reltool_app_SUITE.erl291
-rw-r--r--lib/reltool/test/reltool_server_SUITE.erl38
-rw-r--r--lib/reltool/test/reltool_test_lib.erl14
-rw-r--r--lib/reltool/test/reltool_test_lib.hrl11
-rwxr-xr-xlib/reltool/test/rtt12
-rw-r--r--lib/reltool/vsn.mk5
-rw-r--r--lib/runtime_tools/doc/src/notes.xml15
-rw-r--r--lib/runtime_tools/vsn.mk2
-rw-r--r--lib/sasl/doc/src/notes.xml27
-rw-r--r--lib/sasl/src/release_handler_1.erl18
-rw-r--r--lib/sasl/vsn.mk2
-rw-r--r--lib/snmp/doc/src/notes.xml132
-rw-r--r--lib/snmp/doc/src/snmp_app.xml2
-rw-r--r--lib/snmp/doc/src/snmp_config.xml2
-rw-r--r--lib/snmp/doc/src/snmpa.xml36
-rw-r--r--lib/snmp/doc/src/snmpa_mpd.xml42
-rw-r--r--lib/snmp/doc/src/snmpc.xml152
-rw-r--r--lib/snmp/src/agent/snmpa.erl23
-rw-r--r--lib/snmp/src/agent/snmpa_agent.erl230
-rw-r--r--lib/snmp/src/agent/snmpa_internal.hrl12
-rw-r--r--lib/snmp/src/agent/snmpa_mib.erl12
-rw-r--r--lib/snmp/src/agent/snmpa_mpd.erl226
-rw-r--r--lib/snmp/src/agent/snmpa_trap.erl95
-rw-r--r--lib/snmp/src/agent/snmpa_usm.erl82
-rw-r--r--lib/snmp/src/app/snmp.appup.src174
-rw-r--r--lib/snmp/src/compile/snmpc.erl187
-rw-r--r--lib/snmp/src/compile/snmpc_lib.erl13
-rw-r--r--lib/snmp/src/manager/snmpm_mpd.erl24
-rw-r--r--lib/snmp/src/manager/snmpm_server.erl26
-rw-r--r--lib/snmp/src/misc/snmp_pdus.erl51
-rw-r--r--lib/snmp/src/misc/snmp_usm.erl10
-rw-r--r--lib/snmp/test/modules.mk14
-rw-r--r--lib/snmp/test/snmp_agent_test.erl49
-rw-r--r--lib/snmp/test/snmp_agent_test_lib.erl12
-rw-r--r--lib/snmp/test/snmp_compiler_test.erl70
-rw-r--r--lib/snmp/test/snmp_manager_config_test.erl90
-rw-r--r--lib/snmp/test/snmp_manager_test.erl29
-rw-r--r--lib/snmp/test/snmp_manager_user_test.erl43
-rw-r--r--lib/snmp/test/snmp_pdus_test.erl64
-rw-r--r--lib/snmp/test/snmp_test_data/OLD-SNMPEA-MIB.mib18
-rw-r--r--lib/snmp/test/snmp_test_data/OTP8574-MIB.mib77
-rw-r--r--lib/snmp/test/snmp_test_data/OTP8595-MIB.mib45
-rw-r--r--lib/snmp/test/snmp_test_lib.erl22
-rw-r--r--lib/snmp/test/snmp_test_mgr_misc.erl14
-rw-r--r--lib/snmp/vsn.mk171
-rw-r--r--lib/ssh/Makefile10
-rw-r--r--lib/ssh/doc/src/book.xml6
-rw-r--r--lib/ssh/doc/src/notes.xml23
-rw-r--r--lib/ssh/doc/src/ref_man.xml6
-rw-r--r--lib/ssh/doc/src/ssh_sftp.xml6
-rw-r--r--lib/ssh/doc/src/ssh_sftpd.xml6
-rw-r--r--lib/ssh/examples/Makefile10
-rw-r--r--lib/ssh/examples/ssh_sample_cli.erl66
-rw-r--r--lib/ssh/src/Makefile13
-rw-r--r--lib/ssh/src/ssh.app.src3
-rw-r--r--lib/ssh/src/ssh.appup.src24
-rw-r--r--lib/ssh/src/ssh.hrl10
-rw-r--r--lib/ssh/src/ssh_acceptor.erl14
-rw-r--r--lib/ssh/src/ssh_acceptor_sup.erl10
-rw-r--r--lib/ssh/src/ssh_app.erl10
-rw-r--r--lib/ssh/src/ssh_auth.erl10
-rw-r--r--lib/ssh/src/ssh_auth.hrl10
-rwxr-xr-xlib/ssh/src/ssh_bits.erl10
-rw-r--r--lib/ssh/src/ssh_channel.erl10
-rw-r--r--lib/ssh/src/ssh_channel_sup.erl10
-rw-r--r--lib/ssh/src/ssh_cli.erl18
-rwxr-xr-xlib/ssh/src/ssh_cm.erl237
-rwxr-xr-xlib/ssh/src/ssh_connect.hrl13
-rw-r--r--lib/ssh/src/ssh_connection.erl20
-rw-r--r--lib/ssh/src/ssh_connection_controler.erl14
-rw-r--r--lib/ssh/src/ssh_connection_handler.erl7
-rw-r--r--lib/ssh/src/ssh_connection_manager.erl7
-rwxr-xr-xlib/ssh/src/ssh_dsa.erl10
-rwxr-xr-xlib/ssh/src/ssh_file.erl10
-rwxr-xr-xlib/ssh/src/ssh_io.erl10
-rwxr-xr-xlib/ssh/src/ssh_math.erl10
-rw-r--r--lib/ssh/src/ssh_no_io.erl10
-rwxr-xr-xlib/ssh/src/ssh_rsa.erl10
-rwxr-xr-xlib/ssh/src/ssh_sftp.erl48
-rw-r--r--lib/ssh/src/ssh_sftpd.erl19
-rw-r--r--lib/ssh/src/ssh_sftpd_file.erl10
-rw-r--r--lib/ssh/src/ssh_sftpd_file_api.erl10
-rw-r--r--lib/ssh/src/ssh_shell.erl10
-rw-r--r--lib/ssh/src/ssh_ssh.erl65
-rw-r--r--lib/ssh/src/ssh_sshd.erl48
-rw-r--r--lib/ssh/src/ssh_subsystem_sup.erl10
-rw-r--r--lib/ssh/src/ssh_sup.erl10
-rw-r--r--lib/ssh/src/ssh_system_sup.erl10
-rw-r--r--lib/ssh/src/ssh_transport.erl10
-rw-r--r--lib/ssh/src/ssh_transport.hrl10
-rwxr-xr-xlib/ssh/src/ssh_userauth.hrl10
-rw-r--r--lib/ssh/src/ssh_userreg.erl10
-rw-r--r--lib/ssh/src/ssh_xfer.erl10
-rwxr-xr-xlib/ssh/src/ssh_xfer.hrl10
-rw-r--r--lib/ssh/src/sshc_sup.erl10
-rw-r--r--lib/ssh/src/sshd_sup.erl10
-rw-r--r--lib/ssh/vsn.mk7
-rw-r--r--lib/ssl/Makefile14
-rw-r--r--lib/ssl/doc/src/Makefile4
-rw-r--r--lib/ssl/doc/src/book.xml3
-rw-r--r--lib/ssl/doc/src/create_certs.xml148
-rw-r--r--lib/ssl/doc/src/insidecover.xml14
-rw-r--r--lib/ssl/doc/src/licenses.xml156
-rw-r--r--lib/ssl/doc/src/make.dep30
-rw-r--r--lib/ssl/doc/src/new_ssl.xml678
-rw-r--r--lib/ssl/doc/src/notes.xml147
-rw-r--r--lib/ssl/doc/src/old_ssl.xml709
-rw-r--r--lib/ssl/doc/src/refman.xml9
-rw-r--r--lib/ssl/doc/src/ssl.xml986
-rw-r--r--lib/ssl/doc/src/ssl_app.xml159
-rw-r--r--lib/ssl/doc/src/ssl_distribution.xml8
-rw-r--r--lib/ssl/doc/src/ssl_protocol.xml431
-rw-r--r--lib/ssl/doc/src/ssl_session_cache_api.xml158
-rw-r--r--lib/ssl/doc/src/usersguide.xml22
-rw-r--r--lib/ssl/doc/src/using_ssl.xml202
-rw-r--r--lib/ssl/pkix/Makefile121
-rw-r--r--lib/ssl/pkix/OTP-PKIX.asn1config2
-rw-r--r--lib/ssl/pkix/OTP-PKIX.set.asn6
-rwxr-xr-xlib/ssl/pkix/PKCS-1.asn154
-rw-r--r--lib/ssl/pkix/PKIX1Algorithms88.asn1274
-rw-r--r--lib/ssl/pkix/PKIX1Algorithms88.hrl94
-rw-r--r--lib/ssl/pkix/PKIX1Explicit88.asn1619
-rw-r--r--lib/ssl/pkix/PKIX1Explicit88.hrl163
-rw-r--r--lib/ssl/pkix/PKIX1Implicit88.asn1349
-rw-r--r--lib/ssl/pkix/PKIX1Implicit88.hrl93
-rw-r--r--lib/ssl/pkix/PKIXAttributeCertificate.asn1189
-rw-r--r--lib/ssl/pkix/PKIXAttributeCertificate.hrl64
-rw-r--r--lib/ssl/pkix/README49
-rw-r--r--lib/ssl/pkix/SSL-PKIX.asn1704
-rw-r--r--lib/ssl/pkix/mk_ssl_pkix_oid.erl94
-rw-r--r--lib/ssl/pkix/prebuild.skip5
-rw-r--r--lib/ssl/src/Makefile25
-rw-r--r--lib/ssl/src/ssl.app.src9
-rw-r--r--lib/ssl/src/ssl.appup.src10
-rw-r--r--lib/ssl/src/ssl.erl268
-rw-r--r--lib/ssl/src/ssl_alert.erl85
-rw-r--r--lib/ssl/src/ssl_app.erl12
-rw-r--r--lib/ssl/src/ssl_base64.erl129
-rw-r--r--lib/ssl/src/ssl_certificate.erl130
-rw-r--r--lib/ssl/src/ssl_certificate_db.erl30
-rw-r--r--lib/ssl/src/ssl_cipher.erl624
-rw-r--r--lib/ssl/src/ssl_cipher.hrl78
-rw-r--r--lib/ssl/src/ssl_connection.erl1576
-rw-r--r--lib/ssl/src/ssl_handshake.erl597
-rw-r--r--lib/ssl/src/ssl_handshake.hrl15
-rw-r--r--lib/ssl/src/ssl_internal.hrl27
-rw-r--r--lib/ssl/src/ssl_manager.erl129
-rw-r--r--lib/ssl/src/ssl_pem.erl147
-rw-r--r--lib/ssl/src/ssl_pkix.erl307
-rw-r--r--lib/ssl/src/ssl_pkix.hrl81
-rw-r--r--lib/ssl/src/ssl_record.erl267
-rw-r--r--lib/ssl/src/ssl_record.hrl6
-rw-r--r--lib/ssl/src/ssl_session.erl32
-rw-r--r--lib/ssl/src/ssl_session_cache.erl61
-rw-r--r--lib/ssl/src/ssl_session_cache_api.erl12
-rw-r--r--lib/ssl/src/ssl_ssl3.erl92
-rw-r--r--lib/ssl/src/ssl_sup.erl33
-rw-r--r--lib/ssl/src/ssl_tls1.erl92
-rw-r--r--lib/ssl/test/Makefile17
-rw-r--r--lib/ssl/test/erl_make_certs.erl412
-rw-r--r--lib/ssl/test/old_ssl_active_SUITE.erl2
-rw-r--r--lib/ssl/test/old_ssl_active_once_SUITE.erl12
-rw-r--r--lib/ssl/test/old_ssl_dist_SUITE.erl52
-rw-r--r--lib/ssl/test/old_ssl_misc_SUITE.erl12
-rw-r--r--lib/ssl/test/old_ssl_passive_SUITE.erl12
-rw-r--r--lib/ssl/test/old_ssl_peer_cert_SUITE.erl12
-rw-r--r--lib/ssl/test/old_ssl_protocol_SUITE.erl12
-rw-r--r--lib/ssl/test/old_ssl_verify_SUITE.erl12
-rw-r--r--lib/ssl/test/old_transport_accept_SUITE.erl19
-rw-r--r--lib/ssl/test/ssl.cover14
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl1475
-rw-r--r--lib/ssl/test/ssl_packet_SUITE.erl588
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl11
-rw-r--r--lib/ssl/test/ssl_test_MACHINE.erl27
-rw-r--r--lib/ssl/test/ssl_test_lib.erl188
-rw-r--r--lib/ssl/test/ssl_to_openssl_SUITE.erl508
-rw-r--r--lib/ssl/vsn.mk23
-rw-r--r--lib/stdlib/doc/src/Makefile11
-rw-r--r--lib/stdlib/doc/src/beam_lib.xml7
-rw-r--r--lib/stdlib/doc/src/binary.xml729
-rw-r--r--lib/stdlib/doc/src/erl_scan.xml19
-rw-r--r--lib/stdlib/doc/src/ets.xml8
-rw-r--r--lib/stdlib/doc/src/gen_event.xml64
-rw-r--r--lib/stdlib/doc/src/gen_fsm.xml64
-rw-r--r--lib/stdlib/doc/src/gen_server.xml61
-rw-r--r--lib/stdlib/doc/src/lists.xml6
-rw-r--r--lib/stdlib/doc/src/notes.xml277
-rw-r--r--lib/stdlib/doc/src/re.xml6
-rw-r--r--lib/stdlib/doc/src/ref_man.xml7
-rw-r--r--lib/stdlib/doc/src/timer.xml3
-rw-r--r--lib/stdlib/src/Makefile1
-rw-r--r--lib/stdlib/src/beam_lib.erl37
-rw-r--r--lib/stdlib/src/binary.erl177
-rw-r--r--lib/stdlib/src/c.erl117
-rw-r--r--lib/stdlib/src/dets.erl11
-rw-r--r--lib/stdlib/src/dets_sup.erl17
-rw-r--r--lib/stdlib/src/digraph.erl12
-rw-r--r--lib/stdlib/src/edlin.erl5
-rw-r--r--lib/stdlib/src/epp.erl55
-rw-r--r--lib/stdlib/src/erl_compile.erl12
-rw-r--r--lib/stdlib/src/erl_expand_records.erl137
-rw-r--r--lib/stdlib/src/erl_internal.erl151
-rw-r--r--lib/stdlib/src/erl_lint.erl456
-rw-r--r--lib/stdlib/src/erl_parse.yrl153
-rw-r--r--lib/stdlib/src/erl_pp.erl35
-rw-r--r--lib/stdlib/src/erl_scan.erl134
-rw-r--r--lib/stdlib/src/escript.erl88
-rw-r--r--lib/stdlib/src/ets.erl121
-rw-r--r--lib/stdlib/src/file_sorter.erl9
-rw-r--r--lib/stdlib/src/gen.erl45
-rw-r--r--lib/stdlib/src/gen_event.erl42
-rw-r--r--lib/stdlib/src/gen_fsm.erl58
-rw-r--r--lib/stdlib/src/gen_server.erl62
-rw-r--r--lib/stdlib/src/io.erl11
-rw-r--r--lib/stdlib/src/io_lib.erl2
-rw-r--r--lib/stdlib/src/io_lib_fread.erl12
-rw-r--r--lib/stdlib/src/lists.erl24
-rw-r--r--lib/stdlib/src/otp_internal.erl146
-rw-r--r--lib/stdlib/src/proc_lib.erl12
-rw-r--r--lib/stdlib/src/proplists.erl12
-rw-r--r--lib/stdlib/src/stdlib.app.src11
-rw-r--r--lib/stdlib/src/supervisor.erl172
-rw-r--r--lib/stdlib/src/timer.erl66
-rw-r--r--lib/stdlib/test/Makefile2
-rw-r--r--lib/stdlib/test/binary_module_SUITE.erl1323
-rw-r--r--lib/stdlib/test/binref.erl588
-rw-r--r--lib/stdlib/test/dummy1_h.erl15
-rw-r--r--lib/stdlib/test/epp_SUITE.erl62
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl235
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl162
-rw-r--r--lib/stdlib/test/erl_scan_SUITE.erl213
-rw-r--r--lib/stdlib/test/gen_event_SUITE.erl59
-rw-r--r--lib/stdlib/test/gen_fsm_SUITE.erl67
-rw-r--r--lib/stdlib/test/gen_server_SUITE.erl115
-rw-r--r--lib/stdlib/test/io_proto_SUITE.erl1
-rw-r--r--lib/stdlib/test/qlc_SUITE.erl15
-rw-r--r--lib/stdlib/test/re_SUITE.erl26
-rw-r--r--lib/stdlib/vsn.mk3
-rw-r--r--lib/syntax_tools/doc/src/notes.xml15
-rw-r--r--lib/syntax_tools/src/erl_comment_scan.erl5
-rw-r--r--lib/syntax_tools/src/erl_prettypr.erl6
-rw-r--r--lib/syntax_tools/src/erl_recomment.erl9
-rw-r--r--lib/syntax_tools/src/erl_syntax.erl1
-rw-r--r--lib/syntax_tools/src/erl_syntax_lib.erl7
-rw-r--r--lib/syntax_tools/src/igor.erl22
-rw-r--r--lib/syntax_tools/src/prettypr.erl2
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/test_server/doc/src/notes.xml64
-rw-r--r--lib/test_server/doc/src/test_server.xml16
-rw-r--r--lib/test_server/doc/src/test_server_ctrl.xml28
-rw-r--r--lib/test_server/doc/src/ts.xml4
-rw-r--r--lib/test_server/src/test_server.erl550
-rw-r--r--lib/test_server/src/test_server_ctrl.erl1049
-rw-r--r--lib/test_server/src/test_server_internal.hrl12
-rw-r--r--lib/test_server/src/test_server_node.erl1
-rw-r--r--lib/test_server/src/test_server_sup.erl14
-rw-r--r--lib/test_server/src/ts.erl12
-rw-r--r--lib/test_server/src/ts_erl_config.erl26
-rw-r--r--lib/test_server/src/ts_run.erl80
-rw-r--r--lib/test_server/test/test_server_SUITE.erl2
-rw-r--r--lib/test_server/vsn.mk2
-rw-r--r--lib/tools/doc/src/eprof.xml55
-rw-r--r--lib/tools/doc/src/notes.xml70
-rw-r--r--lib/tools/emacs/Makefile1
-rw-r--r--lib/tools/emacs/README9
-rw-r--r--lib/tools/emacs/erlang-eunit.el368
-rw-r--r--lib/tools/emacs/erlang-flymake.el102
-rw-r--r--lib/tools/emacs/erlang-start.el5
-rw-r--r--lib/tools/emacs/erlang.el261
-rw-r--r--lib/tools/emacs/test.erl.indented79
-rw-r--r--lib/tools/emacs/test.erl.orig83
-rw-r--r--lib/tools/src/eprof.erl756
-rw-r--r--lib/tools/src/xref_base.erl174
-rw-r--r--lib/tools/src/xref_compiler.erl133
-rw-r--r--lib/tools/src/xref_reader.erl52
-rw-r--r--lib/tools/test/eprof_SUITE.erl95
-rw-r--r--lib/tools/test/eprof_SUITE_data/eprof_test.erl9
-rw-r--r--lib/tools/test/fprof_SUITE.erl4
-rw-r--r--lib/tools/test/xref_SUITE.erl334
-rw-r--r--lib/tools/test/xref_SUITE_data/read/read.erl12
-rw-r--r--lib/tools/vsn.mk12
-rw-r--r--lib/tv/doc/src/notes.xml21
-rw-r--r--lib/tv/src/tv_io_lib_format.erl17
-rw-r--r--lib/tv/src/tv_pb.erl37
-rw-r--r--lib/tv/src/tv_pg_gridfcns.erl59
-rw-r--r--lib/tv/vsn.mk12
-rw-r--r--lib/webtool/doc/src/notes.xml17
-rw-r--r--lib/webtool/doc/src/start_webtool.xml10
-rw-r--r--lib/webtool/src/webtool.erl18
-rw-r--r--lib/webtool/vsn.mk2
-rw-r--r--lib/wx/c_src/Makefile.in2
-rw-r--r--lib/wx/doc/src/notes.xml17
-rw-r--r--lib/wx/src/wx_object.erl71
-rw-r--r--lib/wx/vsn.mk6
-rw-r--r--lib/xmerl/doc/src/notes.xml8
-rw-r--r--lib/xmerl/src/xmerl_xsd.erl19
-rw-r--r--lib/xmerl/vsn.mk3
775 files changed, 71090 insertions, 25536 deletions
diff --git a/lib/appmon/doc/src/notes.xml b/lib/appmon/doc/src/notes.xml
index 219b5671a4..29e66411a6 100644
--- a/lib/appmon/doc/src/notes.xml
+++ b/lib/appmon/doc/src/notes.xml
@@ -30,6 +30,21 @@
</header>
<p>This document describes the changes made to the Appmon application.</p>
+<section><title>Appmon 2.1.12</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Warnings due to new autoimported BIFs removed</p>
+ <p>
+ Own Id: OTP-8674 Aux Id: OTP-8579 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Appmon 2.1.11</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/appmon/src/appmon.erl b/lib/appmon/src/appmon.erl
index 6f5d2824d2..2b982cddf0 100644
--- a/lib/appmon/src/appmon.erl
+++ b/lib/appmon/src/appmon.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(appmon).
-behaviour(gen_server).
@@ -838,7 +838,7 @@ draw_apps(GUI, [App | Apps], X, Lx0, N, GSObjs) ->
%% Some necessary data
{_Pid, AppName, _Descr} = App,
Text = atom_to_list(AppName),
- Width = max(8*length(Text)+10, ?wBTN),
+ Width = erlang:max(8*length(Text)+10, ?wBTN),
%% Connect the application to the node label with a line
%% Lx0 = leftmost X coordinate (above previous application button)
@@ -1009,9 +1009,6 @@ bcast(MNodes, Msg) ->
end,
MNodes).
-max(X, Y) when X>Y -> X;
-max(_, Y) -> Y.
-
%% parse_nodes(MNodes) -> NodeApps
%% MNodes -> [#mnode{}]
%% NodeApps -> [{Node, Status, Apps}]
diff --git a/lib/appmon/src/appmon_info.erl b/lib/appmon/src/appmon_info.erl
index 4e36d3a13f..332140f69d 100644
--- a/lib/appmon/src/appmon_info.erl
+++ b/lib/appmon/src/appmon_info.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
%%----------------------------------------------------------------------
@@ -807,24 +807,21 @@ load(Opts) ->
case get_opt(load_scale, Opts) of
linear ->
- min(trunc(load_range()*(Td/Tot+Q/6)),
+ erlang:min(trunc(load_range()*(Td/Tot+Q/6)),
load_range());
prog ->
- min(trunc(load_range()*prog(Td/Tot+Q/6)),
+ erlang:min(trunc(load_range()*prog(Td/Tot+Q/6)),
load_range())
end;
queue ->
case get_opt(load_scale, Opts) of
linear ->
- min(trunc(load_range()*Q/6), load_range());
+ erlang:min(trunc(load_range()*Q/6), load_range());
prog ->
- min(trunc(load_range()*prog(Q/6)), load_range())
+ erlang:min(trunc(load_range()*prog(Q/6)), load_range())
end
end.
-min(X,Y) when X<Y -> X;
-min(_,Y)->Y.
-
%%
%% T shall be within 0 and 0.9 for this to work correctly
diff --git a/lib/appmon/src/appmon_place.erl b/lib/appmon/src/appmon_place.erl
index 5a6ae6aa48..fe1e909d7c 100644
--- a/lib/appmon/src/appmon_place.erl
+++ b/lib/appmon/src/appmon_place.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%------------------------------------------------------------
%%
@@ -155,10 +155,8 @@ move2(DG, V, LastX, DeltaX) ->
ChLX = foldl(fun(C, LX) -> move2(DG, C, LX, DeltaX) end,
tll(LastX),
appmon_dg:get(out, DG, V)),
- [max(NewX+appmon_dg:get(w, DG, V), hdd(LastX)) | ChLX].
+ [erlang:max(NewX+appmon_dg:get(w, DG, V), hdd(LastX)) | ChLX].
-max(A, B) when A>B -> A;
-max(_, B) -> B.
%%------------------------------------------------------------
%%
diff --git a/lib/appmon/vsn.mk b/lib/appmon/vsn.mk
index 78b95e5688..cfcb5d3eb6 100644
--- a/lib/appmon/vsn.mk
+++ b/lib/appmon/vsn.mk
@@ -16,4 +16,4 @@
#
# %CopyrightEnd%
-APPMON_VSN = 2.1.11
+APPMON_VSN = 2.1.12
diff --git a/lib/asn1/c_src/Makefile b/lib/asn1/c_src/Makefile
index 906c513fad..9e9cb18524 100644
--- a/lib/asn1/c_src/Makefile
+++ b/lib/asn1/c_src/Makefile
@@ -124,7 +124,7 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
$(INSTALL_DIR) $(RELSYSDIR)/priv/lib
- $(INSTALL_DATA) $(SHARED_OBJ_FILES) $(RELSYSDIR)/priv/lib
+ $(INSTALL_PROGRAM) $(SHARED_OBJ_FILES) $(RELSYSDIR)/priv/lib
$(INSTALL_DIR) $(RELSYSDIR)/c_src
$(INSTALL_DATA) $(C_FILES) $(RELSYSDIR)/c_src
diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml
index 5eadb0c90c..c5ec682e0f 100644
--- a/lib/asn1/doc/src/notes.xml
+++ b/lib/asn1/doc/src/notes.xml
@@ -31,6 +31,25 @@
<p>This document describes the changes made to the asn1 application.</p>
+<section><title>Asn1 1.6.14</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ By default, the ASN.1 compiler is now silent in the
+ absence of warnings or errors. The new '<c>verbose</c>'
+ option or the '<c>-v</c>' option for <c>erlc</c> can be
+ given to show extra information (for instance, about the
+ files that are generated). (Thanks to Tuncer Ayaz.)</p>
+ <p>
+ Own Id: OTP-8565</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Asn1 1.6.13</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/asn1/src/asn1ct.erl b/lib/asn1/src/asn1ct.erl
index 1859428c49..ae681a4a78 100644
--- a/lib/asn1/src/asn1ct.erl
+++ b/lib/asn1/src/asn1ct.erl
@@ -1215,7 +1215,6 @@ compile(File, _OutFile, Options) ->
%% io:format("~p~n~s~n",[_Reason,"error"]),
error;
ok ->
- io:format("ok~n"),
ok;
ParseRes when is_tuple(ParseRes) ->
io:format("~p~n",[ParseRes]),
diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk
index 32151a0cac..b7e91e42a0 100644
--- a/lib/asn1/vsn.mk
+++ b/lib/asn1/vsn.mk
@@ -1,7 +1,12 @@
-#next version number to use is 1.6.14 | 1.7 | 2.0
-ASN1_VSN = 1.6.13
+#next version number to use is 1.6.15 | 1.7 | 2.0
+ASN1_VSN = 1.6.14
-TICKETS = OTP-8463
+TICKETS = OTP-8565 \
+ OTP-8516
+
+TICKETS_1.6.14 = \
+ OTP-8565 \
+ OTP-8516
TICKETS_1.6.13 = \
OTP-8463
diff --git a/lib/common_test/Makefile b/lib/common_test/Makefile
index ebca4523ab..e16efd0c9d 100644
--- a/lib/common_test/Makefile
+++ b/lib/common_test/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2003-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2003-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%
#
@@ -25,12 +25,12 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
#
ifeq ($(findstring linux,$(TARGET)),linux)
-SUB_DIRECTORIES = doc/src src priv
+SUB_DIRECTORIES = doc/src src
else
ifeq ($(findstring solaris,$(TARGET)),solaris)
-SUB_DIRECTORIES = doc/src src priv
+SUB_DIRECTORIES = doc/src src
else
-SUB_DIRECTORIES = doc/src src priv
+SUB_DIRECTORIES = doc/src src
endif
endif
diff --git a/lib/common_test/doc/src/Makefile b/lib/common_test/doc/src/Makefile
index a2c014418d..6322860088 100644
--- a/lib/common_test/doc/src/Makefile
+++ b/lib/common_test/doc/src/Makefile
@@ -45,7 +45,8 @@ CT_MODULES = \
ct_ssh \
ct_rpc \
ct_snmp \
- unix_telnet
+ unix_telnet \
+ ct_slave
CT_XML_FILES = $(CT_MODULES:=.xml)
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index 7b52883f8a..e30eef2488 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2003</year><year>2009</year>
+ <year>2003</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Common Test</title>
@@ -337,7 +337,7 @@
</func>
<func>
- <name>Module:testcase() -> [Info] </name>
+ <name>Module:Testcase() -> [Info] </name>
<fsummary>Test case info function. </fsummary>
<type>
<v> Info = {timetrap,Time} | {require,Required} |
@@ -396,7 +396,7 @@
<func>
- <name>Module:testcase(Config) -> void() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
+ <name>Module:Testcase(Config) -> void() | {skip,Reason} | {comment,Comment} | {save_config,SaveConfig} | {skip_and_save,Reason,SaveConfig} | exit() </name>
<fsummary>A test case</fsummary>
<type>
<v> Config = SaveConfig = [{Key,Value}]</v>
diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml
index a22a5270c1..850bc74e19 100644
--- a/lib/common_test/doc/src/config_file_chapter.xml
+++ b/lib/common_test/doc/src/config_file_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Config Files</title>
@@ -180,7 +180,126 @@
</section>
<section>
- <title>Examples</title>
+ <title>User specific configuration data formats</title>
+
+ <p>It is possible for the user to specify configuration data on a
+ different format than key-value tuples in a text file, as described
+ so far. The data can e.g. be read from arbitrary files, fetched from
+ the web over http, or requested from a user specific process.
+ To support this, Common Test provides a callback module plugin
+ mechanism to handle configuration data.</p>
+
+ <section>
+ <title>Default callback modules for handling configuration data</title>
+ <p>The Common Test application includes default callback modules
+ for handling configuration data specified in standard config files
+ (see above) and in xml files:</p>
+ <list>
+ <item>
+ <c>ct_config_plain</c> - for reading configuration files with
+ key-value tuples (standard format). This handler will be used to
+ parse configuration files if no user callback is specified.
+ </item>
+ <item>
+ <c>ct_config_xml</c> - for reading configuration data from XML
+ files.
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Using XML configuration files</title>
+ <p>This is an example of an XML configuration file:</p>
+ <pre><![CDATA[
+<config>
+ <ftp_host>
+ <ftp>"targethost"</ftp>
+ <username>"tester"</username>
+ <password>"letmein"</password>
+ </ftp_host>
+ <lm_directory>"/test/loadmodules"</lm_directory>
+</config>]]></pre>
+
+ <p>This configuration file, once read, will produce the same configuration
+ variables as the following text file:</p>
+ <pre>
+{ftp_host, [{ftp,"targethost"},
+ {username,"tester"},
+ {password,"letmein"}]}.
+
+{lm_directory, "/test/loadmodules"}.</pre>
+ </section>
+
+ <section>
+ <title>How to implement a user specific handler</title>
+
+ <p>The user specific handler can be written to handle special
+ configuration file formats. The parameter can be either file
+ name(s) or configuration string(s) (the empty list is valid).</p>
+
+ <p>The callback module implementing the handler is responsible for
+ checking correctness of configuration strings.</p>
+
+ <p>To perform validation of the configuration strings, the callback module
+ should have the following function exported:</p>
+
+ <p><c>Callback:check_parameter/1</c></p>
+ <p>The input argument will be passed from Common Test, as defined in the test
+ specification or given as an option to <c>run_test</c>.</p>
+
+ <p>The return value should be any of the following values indicating if given
+ configuration parameter is valid:</p>
+ <list>
+ <item>
+ <c>{ok, {file, FileName}}</c> - parameter is a file name and
+ the file exists,
+ </item>
+ <item>
+ <c>{ok, {config, ConfigString}}</c> - parameter is a config string
+ and it is correct,
+ </item>
+ <item>
+ <c>{error, {nofile, FileName}}</c> - there is no file with the given
+ name in the current directory,
+ </item>
+ <item>
+ <c>{error, {wrong_config, ConfigString}}</c> - the configuration string
+ is wrong.
+ </item>
+ </list>
+
+ <p>To perform reading of configuration data - initially before the tests
+ start, or as a result of data being reloaded during test execution -
+ the following function should be exported from the callback module:</p>
+
+ <p><c>Callback:read_config/1</c></p>
+
+ <p>The input argument is the same as for the <c>check_parameter/1</c> function.</p>
+ <p>The return value should be either:</p>
+
+ <list>
+ <item>
+ <c>{ok, Config}</c> - if the configuration variables are read successfully,
+ </item>
+ <item>
+ <c>{error, Error, ErrorDetails}</c> - if the callback module fails to
+ proceed with the given configuration parameters.
+ </item>
+ </list>
+ <p><c>Config</c> is the proper Erlang key-value list, with possible
+ key-value sublists as values, like for the configuration file
+ example above:</p>
+
+ <pre>
+ [{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]},
+ {lm_directory, "/test/loadmodules"}]</pre>
+
+ </section>
+
+ </section>
+
+ <section>
+ <title>Examples of configuration data handling</title>
<p>A config file for using the FTP client to access files on a remote
host could look like this:</p>
@@ -188,28 +307,33 @@
<pre>
{ftp_host, [{ftp,"targethost"},
{username,"tester"},
- {password,"letmein"}]}.
+ {password,"letmein"}]}.
{lm_directory, "/test/loadmodules"}.</pre>
- <p>Example of how to assert that the configuration data is available and
+
+ <p>The XML version shown in the chapter above can also be used, but it should be
+ explicitly specified that the <c>ct_config_xml</c> callback module is to be
+ used by Common Test.</p>
+
+ <p>Example of how to assert that the configuration data is available and
use it for an FTP session:</p>
<pre>
init_per_testcase(ftptest, Config) ->
{ok,_} = ct_ftp:open(ftp),
- Config.
+ Config.
end_per_testcase(ftptest, _Config) ->
ct_ftp:close(ftp).
ftptest() ->
[{require,ftp,ftp_host},
- {require,lm_directory}].
+ {require,lm_directory}].
ftptest(Config) ->
- Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
+ Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
Local = filename:join(?config(priv_dir,Config), "loadmodule"),
ok = ct_ftp:recv(ftp, Remote, Local),
- ...</pre>
+ ...</pre>
<p>An example of how the above functions could be rewritten
if necessary to open multiple connections to the FTP server:</p>
@@ -217,7 +341,7 @@
init_per_testcase(ftptest, Config) ->
{ok,Handle1} = ct_ftp:open(ftp_host),
{ok,Handle2} = ct_ftp:open(ftp_host),
- [{ftp_handles,[Handle1,Handle2]} | Config].
+ [{ftp_handles,[Handle1,Handle2]} | Config].
end_per_testcase(ftptest, Config) ->
lists:foreach(fun(Handle) -> ct_ftp:close(Handle) end,
@@ -225,17 +349,117 @@
ftptest() ->
[{require,ftp_host},
- {require,lm_directory}].
+ {require,lm_directory}].
ftptest(Config) ->
- Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
+ Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
Local = filename:join(?config(priv_dir,Config), "loadmodule"),
[Handle | MoreHandles] = ?config(ftp_handles,Config),
ok = ct_ftp:recv(Handle, Remote, Local),
- ...</pre>
+ ...</pre>
</section>
+ <section>
+ <title>Example of user specific configuration handler</title>
+ <p>A simple configuration handling driver which will ask an external server for
+ configuration data can be implemented this way:</p>
+ <pre>
+-module(config_driver).
+-export([read_config/1, check_parameter/1]).
+
+read_config(ServerName)->
+ ServerModule = list_to_atom(ServerName),
+ ServerModule:start(),
+ ServerModule:get_config().
+
+check_parameter(ServerName)->
+ ServerModule = list_to_atom(ServerName),
+ case code:is_loaded(ServerModule) of
+ {file, _}->
+ {ok, {config, ServerName}};
+ false->
+ case code:load_file(ServerModule) of
+ {module, ServerModule}->
+ {ok, {config, ServerName}};
+ {error, nofile}->
+ {error, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}}
+ end
+ end.</pre>
+
+ <p>The configuration string for this driver may be "config_server", if the
+ config_server.erl module below is compiled and exists in the code path
+ during test execution:</p>
+ <pre>
+-module(config_server).
+-export([start/0, stop/0, init/1, get_config/0, loop/0]).
+
+-define(REGISTERED_NAME, ct_test_config_server).
+
+start()->
+ case whereis(?REGISTERED_NAME) of
+ undefined->
+ spawn(?MODULE, init, [?REGISTERED_NAME]),
+ wait();
+ _Pid->
+ ok
+ end,
+ ?REGISTERED_NAME.
+
+init(Name)->
+ register(Name, self()),
+ loop().
+
+get_config()->
+ call(self(), get_config).
+
+stop()->
+ call(self(), stop).
+
+call(Client, Request)->
+ case whereis(?REGISTERED_NAME) of
+ undefined->
+ {error, not_started, Request};
+ Pid->
+ Pid ! {Client, Request},
+ receive
+ Reply->
+ {ok, Reply}
+ after 4000->
+ {error, timeout, Request}
+ end
+ end.
+
+loop()->
+ receive
+ {Pid, stop}->
+ Pid ! ok;
+ {Pid, get_config}->
+ {D,T} = erlang:localtime(),
+ Pid !
+ [{localtime, [{date, D}, {time, T}]},
+ {node, erlang:node()},
+ {now, erlang:now()},
+ {config_server_pid, self()},
+ {config_server_vsn, ?vsn}],
+ ?MODULE:loop()
+ end.
+
+wait()->
+ case whereis(?REGISTERED_NAME) of
+ undefined->
+ wait();
+ _Pid->
+ ok
+ end.</pre>
+
+ <p>In this example, the handler also provides the ability to dynamically reload
+ configuration variables. If <c>ct:reload_config(localtime)</c> is called from
+ the test case function, all variables loaded with <c>config_driver:read_config/1</c>
+ will be updated with their latest values, and the new value for variable
+ <c>localtime</c> will be returned.</p>
+ </section>
+
</chapter>
diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml
index 79288cfe4c..01f8e61d36 100644
--- a/lib/common_test/doc/src/ct_master_chapter.xml
+++ b/lib/common_test/doc/src/ct_master_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2006</year><year>2009</year>
+ <year>2006</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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 Common Test for Large Scale Testing</title>
@@ -30,6 +30,7 @@
</header>
<section>
+ <marker id="general"></marker>
<title>General</title>
<p>Large scale automated testing requires running multiple independent
test sessions in parallel. This is accomplished by running
@@ -102,9 +103,10 @@
<p><c>ct_master:abort()</c> (stop all) or <c>ct_master:abort(Nodes)</c></p>
<p>For detailed information about the <c>ct_master</c> API, please see the
- manual page for this module.</p>
+ <seealso marker="ct_master">manual page</seealso> for this module.</p>
</section>
<section>
+ <marker id="test_specifications"></marker>
<title>Test Specifications</title>
<p>The test specifications used as input to CT Master are fully compatible with the
specifications used as input to the regular CT server. The syntax is described in the
@@ -186,7 +188,7 @@
<seealso marker="run_test_chapter#test_specifications">Running Test Suites</seealso>
chapter). The result is that any test specified to run on a node with the same
name as the Common Test node in question (typically <c>ct@somehost</c> if started
- with the <c>run_test</c> script), will be performed. Tests without explicit
+ with the <c>run_test</c> program), will be performed. Tests without explicit
node association will always be performed too of course!</p>
<note><p>It is recommended that absolute paths are used for log directories,
@@ -194,6 +196,56 @@
current working directory settings are not important.</p></note>
</section>
+ <section>
+ <title>Automatic startup of test target nodes</title>
+ <marker id="ct_slave"></marker>
+ <p>Is is possible to automatically start, and perform initial actions, on
+ test target nodes by using the test specification term <c>init</c>.</p>
+ <p>Currently, two sub-terms are supported, <c>node_start</c> and <c>eval</c>.</p>
+ <p>Example:</p>
+ <pre>
+ {node, node1, node1@host1}.
+ {node, node2, node1@host2}.
+ {node, node3, node2@host2}.
+ {node, node4, node1@host3}.
+ {init, node1, [{node_start, [{callback_module, my_slave_callback}]}]}.
+ {init, [node2, node3], {node_start, [{username, "ct_user"}, {password, "ct_password"}]}}.
+ {init, node4, {eval, {module, function, []}}}.</pre>
+
+ <p>This test specification declares that <c>node1@host1</c> is to be started using
+ the user callback function <c>callback_module:my_slave_callback/0</c>, and nodes
+ <c>node1@host2</c> and <c>node2@host2</c> will be started with the default callback
+ module <c>ct_slave</c>. The given user name and password is used to log into remote
+ host <c>host2</c>. Also, the function <c>module:function/0</c> will be evaluated on
+ <c>node1@host3</c>, and the result of this call will be printed to the log.</p>
+
+ <p>The default <seealso marker="ct_slave">ct_slave</seealso> callback module,
+ which is part of the Common Test application, has the following features:
+ <list>
+ <item>Starting Erlang target nodes on local or remote hosts
+ (ssh is used for communication).
+ </item>
+ <item>Ability to start an Erlang emulator with additional flags
+ (any flags supported by <c>erl</c> are supported).
+ </item>
+ <item>Supervision of a node being started by means of internal callback
+ functions. Used to prevent hanging nodes. (Configurable).
+ </item>
+ <item>Monitoring of the master node by the slaves. A slave node may be
+ stopped in case the master node terminates. (Configurable).
+ </item>
+ <item>Execution of user functions after a slave node is started.
+ Functions can be given as a list of {Module, Function, Arguments} tuples.
+ </item>
+ </list>
+ </p>
+ <p>Note that it is possible to specify an <c>eval</c> term for the node as well
+ as <c>startup_functions</c> in the <c>node_start</c> options list. In this
+ case first the node will be started, then the <c>startup_functions</c> are
+ executed, and finally functions specified with <c>eval</c> are called.
+ </p>
+ </section>
+
</chapter>
diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml
index a550810850..7f5144b760 100644
--- a/lib/common_test/doc/src/event_handler_chapter.xml
+++ b/lib/common_test/doc/src/event_handler_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2006</year><year>2009</year>
+ <year>2006</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Event Handling</title>
@@ -68,29 +68,27 @@
Example:</p>
<p><c>$ run_test -suite test/my_SUITE -event_handler handlers/my_evh1
handlers/my_evh2 -pa $PWD/handlers</c></p>
+ <p>Use the <c><![CDATA[run_test -event_handler_init]]></c> option instead of
+ <c><![CDATA[-event_handler]]></c> to pass start arguments to the event handler
+ init function.</p>
<p>All event handler modules must have gen_event behaviour. Note also that
these modules must be precompiled, and that their locations must be
added explicitly to the Erlang code server search path (like in the
example).</p>
- <p>It is not possible to specify start arguments to the event handlers when
- using the <c>run_test</c> script. You may however pass along start arguments
- if you use the <c>ct:run_test/1</c> function. An event_handler tuple in the argument
- <c>Opts</c> has the following definition (see also <c>ct:run_test/1</c> in the
- reference manual):</p>
+ <p>An event_handler tuple in the argument <c>Opts</c> has the following
+ definition (see also <c>ct:run_test/1</c> in the reference manual):</p>
<pre>
{event_handler,EventHandlers}
EventHandlers = EH | [EH]
EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
- InitArgs = [term()]
- </pre>
+ InitArgs = [term()]</pre>
<p>Example:</p>
<pre>
- 1> ct:run_test([{suite,"test/my_SUITE"},{event_handler,[my_evh1,{my_evh2,[node()]}]}]).
- </pre>
+ 1> ct:run_test([{suite,"test/my_SUITE"},{event_handler,[my_evh1,{my_evh2,[node()]}]}]).</pre>
<p>This will install two event handlers for the <c>my_SUITE</c> test. Event handler
<c>my_evh1</c> is started with <c>[]</c> as argument to the init function. Event handler
<c>my_evh2</c> is started with the name of the current node in the init argument list.</p>
diff --git a/lib/common_test/doc/src/install_chapter.xml b/lib/common_test/doc/src/install_chapter.xml
index e1ff5abf6a..828588a673 100644
--- a/lib/common_test/doc/src/install_chapter.xml
+++ b/lib/common_test/doc/src/install_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2007</year><year>2009</year>
+ <year>2007</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Installation</title>
@@ -33,82 +33,77 @@
<marker id="general"></marker>
<title>General information</title>
- <p>The two main interfaces for running tests with Common Test
- are an executable Bourne shell script named <c>run_test</c> and an
- erlang module named <c>ct</c>. The shell script will work on Unix/Linux
- (and Linux-like environments such as Cygwin on Windows) and the
- <c>ct</c> interface functions can be called from the Erlang shell
- (or from any Erlang function) on any supported platform.</p>
-
- <p>The Common Test application is installed with the Erlang/OTP
- system and no explicit installation is required to start using
- Common Test by means of the interface functions in the <c>ct</c>
- module. If you wish to use <c>run_test</c>, however, this script
- needs to be generated first, according to the instructions below.</p>
- </section>
+ <p>The two main interfaces for running tests with Common Test
+ are an executable program named run_test and an
+ erlang module named <c>ct</c>. The run_test program
+ is compiled for the underlying operating system (e.g. Unix/Linux
+ or Windows) during the build of the Erlang/OTP system, and is
+ installed automatically with other executable programs in
+ the top level <c>bin</c> directory of Erlang/OTP.
+ The <c>ct</c> interface functions can be called from the Erlang shell,
+ or from any Erlang function, on any supported platform.</p>
+
+ <p>A legacy Bourne shell script - also named run_test - exists,
+ which may be manually generated and installed. This script may be used
+ instead of the run_test program mentioned above, e.g. if the user
+ wishes to modify or customize the Common Test start flags in a simpler
+ way than making changes to the run_test C program.</p>
- <section>
- <title>Unix/Linux</title>
-
- <p>Go to the <c><![CDATA[common_test-<vsn>]]></c> directory, located
- among the other OTP applications (under the OTP lib directory). Here you
- execute the <c>install.sh</c> script with argument <c>local</c>:</p>
-
- <p><c>
- $ ./install.sh local
- </c></p>
+ <p>The Common Test application is installed with the Erlang/OTP
+ system and no additional installation step is required to start using
+ Common Test by means of the run_test executable program, and/or the interface
+ functions in the <c>ct</c> module. If you wish to use the legacy Bourne
+ shell script version of run_test, however, this script needs to be
+ generated first, according to the instructions below.</p>
+
+ <p><note>Before reading on, please note that since Common Test version
+ 1.5, the run_test shell script is no longer required for starting
+ tests with Common Test from the OS command line. The run_test
+ program (descibed above) is the new recommended command line interface
+ for Common Test. The shell script exists mainly for legacy reasons and
+ may not be updated in future releases of Common Test. It may even be removed.
+ </note></p>
+
+ <p>Optional step to generate a shell script for starting Common Test:</p>
+ <p>To generate the run_test shell script, navigate to the
+ <c><![CDATA[common_test-<vsn>]]></c> directory, located among the other
+ OTP applications (under the OTP lib directory). Here execute the
+ <c>install.sh</c> script with argument <c>local</c>:</p>
+
+ <p><c>
+ $ ./install.sh local
+ </c></p>
- <p>This generates the executable <c>run_test</c> script in the
- <c><![CDATA[common_test-<vsn>/priv/bin]]></c> directory. The script
- will include absolute paths to the Common Test and Test Server
- application directories, so it's possible to copy or move the script to
- a different location on the file system, if desired, without having to
- update it. It's of course possible to leave the script under the
- <c>priv/bin</c> directory and update the PATH variable accordingly (or
- create a link or alias to it).</p>
-
- <p>If you, for any reason, have copied Common Test and Test Server
- to a different location than the default OTP lib directory, you can
- generate a <c>run_test</c> script with a different top level directory,
- simply by specifying the directory, instead of <c>local</c>, when running
- <c>install.sh</c>. Example:</p>
-
- <p><c>
- $ install.sh /usr/local/test_tools
+ <p>This generates the executable run_test script in the
+ <c><![CDATA[common_test-<vsn>/priv/bin]]></c> directory. The script
+ will include absolute paths to the Common Test and Test Server
+ application directories, so it's possible to copy or move the script to
+ a different location on the file system, if desired, without having to
+ update it. It's of course possible to leave the script under the
+ <c>priv/bin</c> directory and update the PATH variable accordingly (or
+ create a link or alias to it).</p>
+
+ <p>If you, for any reason, have copied Common Test and Test Server
+ to a different location than the default OTP lib directory, you can
+ generate a run_test script with a different top level directory,
+ simply by specifying the directory, instead of <c>local</c>, when running
+ <c>install.sh</c>. Example:</p>
+
+ <p><c>
+ $ install.sh /usr/local/test_tools
</c></p>
<p>Note that the <c><![CDATA[common_test-<vsn>]]></c> and
- <c><![CDATA[test_server-<vsn>]]></c> directories must be located under the
- same top directory. Note also that the install script does not copy files
- or update environment variables. It only generates the <c>run_test</c>
- script.</p>
+ <c><![CDATA[test_server-<vsn>]]></c> directories must be located under the
+ same top directory. Note also that the install script does not copy files
+ or update environment variables. It only generates the run_test
+ script.</p>
- <p>Whenever you install a new version of Erlang/OTP, the <c>run_test</c>
- script needs to be regenerated, or updated manually with new directory names
- (new version numbers), for it to "see" the latest Common Test and Test Server
- versions.</p>
+ <p>Whenever you install a new version of Erlang/OTP, the run_test
+ script needs to be regenerated, or updated manually with new directory names
+ (new version numbers), for it to "see" the latest Common Test and Test Server
+ versions.</p>
- <p>For more information on the <c>run_test</c> script and the <c>ct</c>
- module, please see the reference manual.</p>
- </section>
-
- <section>
- <title>Windows</title>
-
- <p>On Windows it is very convenient to use Cygwin (<c>www.cygwin.com</c>)
- for running Common Test and Erlang, since it enables you to use the
- <c>run_test</c> script for starting Common Test. If you are a Cygwin
- user, simply follow the instructions above for generating the <c>run_test</c>
- script.</p>
-
- <p>If you do not use Cygwin, you have to rely on the API functions
- in the <c>ct</c> module (instead of <c>run_test</c>) for running
- Common Test as described initially in this chapter.</p>
-
- <p>If you, for any reason, have chosen to store Common Test and Test Server
- in a different location than the default OTP lib directory, make
- sure the <c>ebin</c> directories of these applications are included
- in the Erlang code server path (so the application modules can be loaded).</p>
</section>
</chapter>
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index 4f5f6caa8c..eadfc83c07 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -32,6 +32,152 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Process calls using monitors in Common Test would not
+ clear the inbox of remaining DOWN messages. This has been
+ fixed.</p>
+ <p>
+ Own Id: OTP-8621 Aux Id: seq11560 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ It is now possible for the user to provide specific
+ callback modules that handle test configuration data, so
+ that data on arbitray form can be accessed (e.g. by
+ reading files or by communicating with a configuration
+ server process). Two default callback modules have been
+ introduced in Common Test: ct_config_plain and
+ ct_config_xml. The former is used to handle the
+ traditional Common Test configuration files (with terms
+ on key-value tuple form) and the latter to handle
+ configuration data on XML representation.</p>
+ <p>
+ Own Id: OTP-8485</p>
+ </item>
+ <item>
+ <p>
+ It is now possible to execute test suites that are not
+ necessarily available on the local file system, but have
+ been loaded on the test node in advance (e.g. sent as
+ binaries from a remote node and loaded by RPC). A
+ requirement is that the no_auto_compile (or
+ {auto_compile,false}) parameter has been set.</p>
+ <p>
+ Own Id: OTP-8490 Aux Id: seq11500 </p>
+ </item>
+ <item>
+ <p>
+ Test Server will now call the end_per_testcase/2 function
+ even if the test case has been terminated explicitly
+ (with abort_current_testcase/1), or after a timetrap
+ timeout. Under these circumstances the return value of
+ end_per_testcase is completely ignored. Therefore the
+ function will not be able to change the reason for test
+ case termination by returning {fail,Reason}, nor will it
+ be able to save data with {save_config,Data}.</p>
+ <p>
+ Own Id: OTP-8500 Aux Id: seq11521 </p>
+ </item>
+ <item>
+ <p>
+ It is now possible to use the test specification term
+ 'init' to start Common Test nodes automatically, as well
+ as have initial function calls evaluated on the nodes. A
+ default callback module for the 'init' term, ct_slave,
+ has been introduced to enable Common Test Master to
+ perform host login and node startup operations over ssh.</p>
+ <p>
+ Own Id: OTP-8570</p>
+ </item>
+ <item>
+ <p>
+ The run_test script has been replaced by a program (with
+ the same name) which can be executed without explicit
+ installation. The start flags are the same as for the
+ legacy start script.</p>
+ <p>
+ Own Id: OTP-8650</p>
+ </item>
+ <item>
+ <p>
+ Previously, a repeat property of a test case group
+ specified the number of times the group should be
+ repeated after the main test run. I.e. {repeat,N} would
+ case the group to execute 1+N times. To be consistent
+ with the behaviour of the run_test repeat option, this
+ has been changed. N now specifies the absolute number of
+ executions instead.</p>
+ <p>
+ Own Id: OTP-8689 Aux Id: seq11502 </p>
+ </item>
+ <item>
+ <p>
+ With the run_test -erl_args option, it's possible to
+ divide the options on the run_test command line into ones
+ that Common Test should process (those preceding
+ -erl_args, and ones it should ignore (those succeeding
+ -erl_args). Options preceding -erl_args that Common Test
+ doesn't recognize are also ignored (i.e. the same
+ behaviour as earlier versions of Common Test).</p>
+ <p>
+ Own Id: OTP-8690 Aux Id: OTP-8650 </p>
+ </item>
+ <item>
+ <p>
+ Directories added with -pa or -pz in the pre-erl_args
+ part of the run_test command line will be converted from
+ relative to absolute, this to avoid problems loading user
+ modules when Common Test switches working directory
+ during the test run.</p>
+ <p>
+ Own Id: OTP-8691 Aux Id: OTP-8650 </p>
+ </item>
+ <item>
+ <p>
+ The timetrap handling has been made more user
+ controllable by means of new start options and new ct
+ interface functions. With the 'multiply_timetraps' start
+ option, it's possible to specify a value which all
+ timetrap timeout values get multiplied by. This is useful
+ e.g. to extend the timetraps temporarily while running
+ cover or trace. The 'scale_timetraps' start option
+ switches on or off the Test Server timetrap scaling
+ feature (which tries to detect if the tests may benefit
+ from extended timetraps, e.g. due to running certain test
+ tools, and performs the scaling automatically).
+ Furthermore, the ct:timetrap/1 function has been
+ introduced, which makes it possible to set/reset
+ timetraps during test execution. Also, a ct:sleep/1
+ function is now available, which takes the timetrap
+ parameters into account when calculating the time to
+ suspend the process.</p>
+ <p>
+ Own Id: OTP-8693</p>
+ </item>
+ <item>
+ <p>
+ A new run_test start option, event_handler_init, has been
+ added that takes a start argument which gets passed to
+ the init function of the event handler.</p>
+ <p>
+ Own Id: OTP-8694</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.4.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/common_test/doc/src/ref_man.xml b/lib/common_test/doc/src/ref_man.xml
index beb3ed3247..8be234d979 100644
--- a/lib/common_test/doc/src/ref_man.xml
+++ b/lib/common_test/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2003</year><year>2009</year>
+ <year>2003</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Common Test Reference Manual</title>
@@ -75,6 +75,7 @@
<xi:include href="ct_snmp.xml"/>
<xi:include href="ct_telnet.xml"/>
<xi:include href="unix_telnet.xml"/>
+ <xi:include href="ct_slave.xml"/>
</application>
diff --git a/lib/common_test/doc/src/run_test.xml b/lib/common_test/doc/src/run_test.xml
index d9dd22d411..d609c4287f 100644
--- a/lib/common_test/doc/src/run_test.xml
+++ b/lib/common_test/doc/src/run_test.xml
@@ -4,7 +4,7 @@
<comref>
<header>
<copyright>
- <year>2007</year><year>2009</year>
+ <year>2007</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,62 +13,87 @@
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>The run_test shell script</title>
+ <title>The run_test program</title>
<prepared>Peter Andersson</prepared>
<responsible>Peter Andersson</responsible>
<docno></docno>
<approved></approved>
<checked></checked>
- <date>2007-07-04</date>
- <rev>PA1</rev>
+ <date>2010-04-01</date>
+ <rev>PA2</rev>
<file>run_test.xml</file>
</header>
- <com>run_test</com>
- <comsummary>Shell script used for starting
- Common Test from the Unix command line.
+ <com>run_test</com>
+ <comsummary>Program used for starting Common Test from the
+ OS command line.
</comsummary>
<description>
- <p>The <c>run_test</c> script is automatically generated as Common
- Test is installed (please see the Installation chapter in the Common
- Test User's Guide for more information). The script accepts a number
- of different start flags. Some flags trigger <c>run_test</c>
- to start the Common Test application and pass on data to it. Some
- flags start an Erlang node prepared for running Common Test in a
- particular mode.</p>
- <p><c>run_test</c> also accepts Erlang emulator
- flags. These are used when <c>run_test</c> calls <c>erl</c> to
- start the Erlang node (making it possible to e.g. add directories to
- the code server path, change the cookie on the node, start
- additional applications, etc).</p>
- <p>If <c>run_test</c> is called without parameters, it prints all valid
- start flags to stdout.</p>
+ <p>The <c>run_test</c> program is automatically installed with Erlang/OTP
+ and Common Test (please see the Installation chapter in the Common
+ Test User's Guide for more information). The program accepts a number
+ of different start flags. Some flags trigger <c>run_test</c>
+ to start the Common Test application and pass on data to it. Some
+ flags start an Erlang node prepared for running Common Test in a
+ particular mode.</p>
+
+ <p><c>run_test</c> also accepts Erlang emulator flags. These are used
+ when <c>run_test</c> calls <c>erl</c> to start the Erlang node
+ (making it possible to e.g. add directories to the code server path,
+ change the cookie on the node, start additional applications, etc).</p>
+
+ <p>With the optional flag:</p>
+ <pre>-erl_args</pre>
+ <p>it's possible to divide the options on the <c>run_test</c> command line into
+ two groups, one that Common Test should process (those preceding <c>-erl_args</c>),
+ and one it should completely ignore and pass on directly to the emulator
+ (those following <c>-erl_args</c>). Options preceding <c>-erl_args</c> that Common Test
+ doesn't recognize, also get passed on to the emulator untouched.
+ By means of <c>-erl_args</c> the user may specify flags with the same name, but
+ with different destinations, on the <c>run_test</c> command line.</p>
+ <p>If <c>-pa</c> or <c>-pz</c> flags are specified in the Common Test group of options
+ (preceding <c>-erl_args</c>), relative directories will be converted to
+ absolute and re-inserted into the code path by Common Test (to avoid
+ problems loading user modules when Common Test changes working directory
+ during test runs). Common Test will however ignore <c>-pa</c> and <c>-pz</c> flags
+ following <c>-erl_args</c> on the command line. These directories are added
+ to the code path normally (i.e. on specified form)</p>
+
+ <p>If <c>run_test</c> is called with option:</p>
+ <pre>-help</pre>
+ <p>it prints all valid start flags to stdout.</p>
</description>
<section>
<title>Run tests from command line</title>
<pre>
- run_test [-dir TestDir1 TestDir2 .. TestDirN] |
- [-suite Suite1 Suite2 .. SuiteN
+ run_test [-dir TestDir1 TestDir2 .. TestDirN] |
+ [-suite Suite1 Suite2 .. SuiteN
[[-group Group1 Group2 .. GroupN] [-case Case1 Case2 .. CaseN]]]
[-step [config | keep_inactive]]
[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
+ [-userconfig CallbackModule1 ConfigString1 and CallbackModule2
+ ConfigString2 and .. and CallbackModuleN ConfigStringN]
[-decrypt_key Key] | [-decrypt_file KeyFile]
[-logdir LogDir]
[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]
[-stylesheet CSSFile]
[-cover CoverCfgFile]
- [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]
+ [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] |
+ [-event_handler_init EvHandler1 InitArg1 and
+ EvHandler2 InitArg2 and .. EvHandlerN InitArgN]
[-include InclDir1 InclDir2 .. InclDirN]
[-no_auto_compile]
+ [-muliply_timetraps Multiplier]
+ [-scale_timetraps]
[-repeat N [-force_stop]] |
[-duration HHMMSS [-force_stop]] |
[-until [YYMoMoDD]HHMMSS [-force_stop]]
@@ -79,15 +104,21 @@
<pre>
run_test -spec TestSpec1 TestSpec2 .. TestSpecN
[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
+ [-userconfig CallbackModule1 ConfigString1 and CallbackModule2
+ ConfigString2 and .. and CallbackModuleN ConfigStringN]
[-decrypt_key Key] | [-decrypt_file KeyFile]
[-logdir LogDir]
[-allow_user_terms]
[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]
[-stylesheet CSSFile]
[-cover CoverCfgFile]
- [-event_handler EvHandler1 EvHandler2 .. EvHandlerN]
+ [-event_handler EvHandler1 EvHandler2 .. EvHandlerN] |
+ [-event_handler_init EvHandler1 InitArg1 and
+ EvHandler2 InitArg2 and .. EvHandlerN InitArgN]
[-include InclDir1 InclDir2 .. InclDirN]
[-no_auto_compile]
+ [-muliply_timetraps Multiplier]
+ [-scale_timetraps]
[-repeat N [-force_stop]] |
[-duration HHMMSS [-force_stop]] |
[-until [YYMoMoDD]HHMMSS [-force_stop]]
@@ -98,11 +129,15 @@
<pre>
run_test -vts [-browser Browser]
[-config ConfigFile1 ConfigFile2 .. ConfigFileN]
+ [-userconfig CallbackModule1 ConfigString1 and CallbackModule2
+ ConfigString2 and .. and CallbackModuleN ConfigStringN]
[-decrypt_key Key] | [-decrypt_file KeyFile]
[-dir TestDir1 TestDir2 .. TestDirN] |
[-suite Suite [[-group Group] [-case Case]]]
[-include InclDir1 InclDir2 .. InclDirN]
[-no_auto_compile]
+ [-muliply_timetraps Multiplier]
+ [-scale_timetraps]
[-basic_html]</pre>
</section>
<section>
@@ -113,28 +148,23 @@
<section>
<title>Run CT in interactive mode</title>
<pre>
- run_test -shell
+ run_test -shell
[-config ConfigFile1 ConfigFile2 ... ConfigFileN]
+ [-userconfig CallbackModule1 ConfigString1 and CallbackModule2
+ ConfigString2 and .. and CallbackModuleN ConfigStringN]
[-decrypt_key Key] | [-decrypt_file KeyFile]</pre>
</section>
<section>
- <title>Start an Erlang node with a given name</title>
- <pre>
- run_test -ctname NodeName</pre>
- </section>
- <section>
<title>Start a Common Test Master node</title>
<pre>
run_test -ctmaster</pre>
</section>
<section>
- <title>See also</title>
- <p>Please read the <seealso marker="run_test_chapter">Running Test Suites</seealso>
- chapter in the Common Test User's Guide for information about the meaning of the
- different start flags.</p>
+ <title>See also</title>
+ <p>Please read the <seealso marker="run_test_chapter">Running Test Suites</seealso>
+ chapter in the Common Test User's Guide for information about the meaning of the
+ different start flags.</p>
</section>
</comref>
-
-
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index d731d18783..207df7f5b5 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2009</year>
+ <year>2003</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Running Test Suites</title>
@@ -97,25 +97,32 @@
the <c><![CDATA[{auto_compile,false}]]></c> option with
<c><![CDATA[ct:run_test/1]]></c>. With automatic compilation
disabled, the user is responsible for compiling the test suite modules
- (and any help modules) before the test run. Common Test will only verify
- that the specified test suites exist before starting the tests.</p>
+ (and any help modules) before the test run. If the modules can not be loaded
+ from the local file system during startup of Common Test, the user needs to
+ pre-load the modules before starting the test. Common Test will only verify
+ that the specified test suites exist (i.e. that they are, or can be, loaded).
+ This is useful e.g. if the test suites are transferred and loaded as binaries via
+ RPC from a remote node.</p>
</section>
<section>
- <title>Running tests from the UNIX command line</title>
+ <title>Running tests from the OS command line</title>
- <p>The script <c>run_test</c> can be used for running tests from
- the Unix/Linux command line, e.g.
+ <p>The <c>run_test</c> program can be used for running tests from
+ the OS command line, e.g.
</p>
<list>
<item><c><![CDATA[run_test -config <configfilenames> -dir <dirs>]]></c></item>
<item><c><![CDATA[run_test -config <configfilenames> -suite <suiteswithfullpath>]]></c>
</item>
+ <item><c><![CDATA[run_test -userconfig <callbackmodulename> <configfilenames> -suite <suiteswithfullpath>]]></c>
+ </item>
<item><c><![CDATA[run_test -config <configfilenames> -suite <suitewithfullpath>
-group <groupnames> -case <casenames>]]></c></item>
</list>
<p>Examples:</p>
<p><c>$ run_test -config $CFGS/sys1.cfg $CFGS/sys2.cfg -dir $SYS1_TEST $SYS2_TEST</c></p>
+ <p><c>$ run_test -userconfig ct_config_xml $CFGS/sys1.xml $CFGS/sys2.xml -dir $SYS1_TEST $SYS2_TEST</c></p>
<p><c>$ run_test -suite $SYS1_TEST/setup_SUITE $SYS2_TEST/config_SUITE</c></p>
<p><c>$ run_test -suite $SYS1_TEST/setup_SUITE -case start stop</c></p>
<p><c>$ run_test -suite $SYS1_TEST/setup_SUITE -group installation -case start stop</c></p>
@@ -136,8 +143,14 @@
<seealso marker="cover_chapter#cover">Code Coverage Analysis</seealso>).</item>
<item><c><![CDATA[-event_handler <event_handlers>]]></c>, to install
<seealso marker="event_handler_chapter#event_handling">event handlers</seealso>.</item>
+ <item><c><![CDATA[-event_handler_init <event_handlers>]]></c>, to install
+ <seealso marker="event_handler_chapter#event_handling">event handlers</seealso> including start arguments.</item>
<item><c><![CDATA[-include]]></c>, specifies include directories (see above).</item>
<item><c><![CDATA[-no_auto_compile]]></c>, disables the automatic test suite compilation feature (see above).</item>
+ <item><c><![CDATA[-multiply_timetraps <n>]]></c>, extends <seealso marker="write_test_chapter#timetraps">timetrap
+ timeout</seealso> values.</item>
+ <item><c><![CDATA[-scale_timetraps <bool>]]></c>, enables automatic <seealso marker="write_test_chapter#timetraps">timetrap
+ timeout</seealso> scaling.</item>
<item><c><![CDATA[-repeat <n>]]></c>, tells Common Test to repeat the tests n times (see below).</item>
<item><c><![CDATA[-duration <time>]]></c>, tells Common Test to repeat the tests for duration of time (see below).</item>
<item><c><![CDATA[-until <stop_time>]]></c>, tells Common Test to repeat the tests until stop_time (see below).</item>
@@ -165,7 +178,7 @@
the current working directory of the Erlang Runtime System during the test run!</p>
</note>
- <p>For details on how to generate the <c>run_test</c> script, see the
+ <p>For more information about the <c>run_test</c> program, see the
<seealso marker="install_chapter#general">Installation</seealso> chapter.
</p>
</section>
@@ -174,7 +187,7 @@
<title>Running tests from the Web based GUI</title>
<p>The web based GUI, VTS, is started with the <c>run_test</c>
- script. From the GUI you can load config files, and select
+ program. From the GUI you can load config files, and select
directories, suites and cases to run. You can also state the
config files, directories, suites and cases on the command line
when starting the web based GUI.
@@ -198,10 +211,11 @@
<p>Example:</p>
<p><c><![CDATA[$ run_test -vts -browser 'firefox&']]></c></p>
<p>Note that the browser must run as a separate OS process or VTS will hang!</p>
- <p>If no specific browser start command is specified, netscape will
+ <p>If no specific browser start command is specified, Firefox will
be the default browser on Unix platforms and Internet Explorer on Windows.
- If Common Test fails to start a browser automatically, start your
- favourite browser manually instead and type in the URL that Common Test
+ If Common Test fails to start a browser automatically, or <c>'none'</c> is
+ specified as the value for -browser (i.e. <c>-browser none</c>), start your
+ favourite browser manually and type in the URL that Common Test
displays in the shell.</p>
</section>
@@ -211,7 +225,7 @@
<p>Common Test provides an Erlang API for running tests. The main (and most
flexible) function for specifying and executing tests is called
<c>ct:run_test/1</c>. This function takes the same start parameters as
- the <c>run_test</c> script described above, only the flags are instead
+ the <c>run_test</c> program described above, only the flags are instead
given as options in a list of key-value tuples. E.g. a test specified
with <c>run_test</c> like:</p>
<p><c>$ run_test -suite ./my_SUITE -logdir ./results</c></p>
@@ -237,13 +251,14 @@
manually and call <c>ct:install/1</c> to install any configuration
data you might need (use <c>[]</c> as argument otherwise), then
call <c>ct:start_interactive/0</c> to start Common Test. If you use
- the <c>run_test</c> script, you may start the Erlang shell and Common Test
+ the <c>run_test</c> program, you may start the Erlang shell and Common Test
in the same go by using the <c>-shell</c> and, optionally, the <c>-config</c>
- flag:
+ and/or <c>-userconfig</c> flag. Examples:
</p>
<list>
<item><c>run_test -shell</c></item>
- <item><c><![CDATA[run_test -shell -config <configfilename>]]></c></item>
+ <item><c><![CDATA[run_test -shell -config cfg/db.cfg]]></c></item>
+ <item><c><![CDATA[run_test -shell -userconfig db_login testuser x523qZ]]></c></item>
</list>
<p>If no config file is given with the <c>run_test</c> command,
@@ -268,7 +283,8 @@
2> ct_telnet:open(unix_telnet).
{ok,&lt;0.105.0&gt;}
4> ct_telnet:cmd(unix_telnet, "ls .").
- {ok,["ls .","file1 ...",...]}</pre>
+ {ok,["ls .","file1 ...",...]}
+ </pre>
<p>Everything that Common Test normally prints in the test case logs,
will in the interactive mode be written to a log named
@@ -350,14 +366,21 @@
<p>Below is the test specification syntax. Test specifications can
be used to run tests both in a single test host environment and in
- a distributed Common Test environment. Node parameters are only relevant in the
- latter (see the chapter about running Common Test in distributed mode for information).
- For details on the event_handler term, see the
+ a distributed Common Test environment (Large Scale Testing). The init term,
+ as well as node parameters, are only relevant in the latter (see the
+ <seealso marker="ct_master_chapter#test_specifications">Large Scale Testing</seealso>
+ chapter for information). For details on the event_handler term, see the
<seealso marker="event_handler_chapter#event_handling">Event Handling</seealso>
chapter.</p>
<p>Config terms:</p>
<pre>
{node, NodeAlias, Node}.
+
+ {init, InitOptions}.
+ {init, [NodeAlias], InitOptions}.
+
+ {multiply_timetraps, N}.
+ {scale_timetraps, Bool}.
{cover, CoverSpecFile}.
{cover, NodeRef, CoverSpecFile}.
@@ -367,6 +390,9 @@
{config, ConfigFiles}.
{config, NodeRefs, ConfigFiles}.
+
+ {userconfig, {CallbackModule, ConfigStrings}}.
+ {userconfig, NodeRefs, {CallbackModule, ConfigStrings}}.
{alias, DirAlias, Dir}.
@@ -395,9 +421,12 @@
<p>Types:</p>
<pre>
NodeAlias = atom()
+ InitOptions = term()
Node = node()
NodeRef = NodeAlias | Node | master
NodeRefs = all_nodes | [NodeRef] | NodeRef
+ N = integer()
+ Bool = true | false
CoverSpecFile = string()
IncludeDirs = string() | [string()]
ConfigFiles = string() | [string()]
@@ -449,9 +478,14 @@
<item>Secondly, the test for system t2 should run. The included suites are
t2B and t2C. Included are also test cases test4, test1 and test7 in suite
t2A. Note that the test cases will be executed in the specified order.</item>
- <item>Lastly, all suites for systems t3 are to be completely skipped and this
+ <item>Lastly, all suites for systems t3 are to be completely skipped and this
should be explicitly noted in the log files.</item>
</list>
+ <p>It is possible to specify initialization options for nodes defined in the
+ test specification. Currently, there are options to start the node and/or to
+ evaluate any function on the node.
+ See the <seealso marker="ct_master_chapter#ct_slave">Automatic startup of
+ the test target nodes</seealso> chapter for details.</p>
<p>It is possible for the user to provide a test specification that
includes (for Common Test) unrecognizable terms. If this is desired,
the <c>-allow_user_terms</c> flag should be used when starting tests with
diff --git a/lib/common_test/doc/src/test_structure_chapter.xml b/lib/common_test/doc/src/test_structure_chapter.xml
index c8628b3a7a..cd38ae0c7c 100644
--- a/lib/common_test/doc/src/test_structure_chapter.xml
+++ b/lib/common_test/doc/src/test_structure_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2006</year><year>2009</year>
+ <year>2006</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Test Structure</title>
@@ -146,8 +146,8 @@
<tag><em>run_test</em></tag>
<item>
- The name of an executable Bourne shell script that may be
- used on Linux/Unix as an interface for specifying and running
+ The name of an executable program that may be
+ used as an interface for specifying and running
tests with Common Test.
</item>
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index 212e3d85be..5afec6de6a 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2003</year><year>2009</year>
+ <year>2003</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>Writing Test Suites</title>
@@ -157,6 +157,15 @@
<c>{skipped,Reason}</c> (where Reason is a user specific term).
</p>
+ <p>The <c>end_per_testcase/2</c> function is called even after a
+ test case terminates due to a call to <c>ct:abort_current_testcase/1</c>,
+ or after a timetrap timeout. However, <c>end_per_testcase</c>
+ will then execute on a different process than the test case
+ function, and in this situation, <c>end_per_testcase</c> will
+ not be able to change the reason for test case termination by
+ returning <c>{fail,Reason}</c>, nor will it be able to save data with
+ <c>{save_config,Data}</c>.</p>
+
<p>If <c>init_per_testcase</c> crashes, the test case itself is skipped
automatically (so called <em>auto skipped</em>). If <c>init_per_testcase</c>
returns a <c>skip</c> tuple, also then will the test case be skipped (so
@@ -682,12 +691,33 @@
<c>end_per_suite</c> execute, like test cases, on dedicated Erlang
processes.
</p>
+ </section>
+ <section>
+ <title>Timetrap timeouts</title>
+ <marker id="timetraps"></marker>
<p>The default time limit for a test case is 30 minutes, unless a
- <c>timetrap</c> is specified either by the test case info function
- or the <c>suite/0</c> function.
- </p>
-
+ <c>timetrap</c> is specified either by the suite info function
+ or a test case info function. The timetrap timeout value defined
+ in <c>suite/0</c> is the value that will be used for each test case
+ in the suite (as well as for the configuration functions
+ <c>init_per_suite/1</c> and <c>end_per_suite</c>). A timetrap timeout
+ value set with the test case info function will override the value set
+ by <c>suite/0</c>, but only for that particular test case.</p>
+ <p>It is also possible to set/reset a timetrap during test case (or
+ configuration function) execution. This is done by calling
+ <c>ct:timetrap/1</c>. This function will cancel the current timetrap
+ and start a new one.</p>
+ <p>Timetrap values can be extended with a multiplier value specified at
+ startup with the <c>multiply_timetraps</c> option. It is also possible
+ to let Test Server decide to scale up timetrap timeout values
+ automatically, e.g. if tools such as cover or trace are running during
+ the test. This feature is disabled by default and can be enabled with
+ the <c>scale_timetraps</c> start option.</p>
+ <p>If a test case needs to suspend itself for a time that also gets
+ multipled by <c>multiply_timetraps</c>, and possibly scaled up if
+ <c>scale_timetraps</c> is enabled, the function <c>ct:sleep/1</c>
+ may be called.</p>
</section>
<section>
diff --git a/lib/common_test/src/Makefile b/lib/common_test/src/Makefile
index e7e2d1275d..027667e6b0 100644
--- a/lib/common_test/src/Makefile
+++ b/lib/common_test/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2003-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2003-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%
#
@@ -63,7 +63,11 @@ MODULES= \
ct_telnet_client \
ct_make \
vts \
- unix_telnet
+ unix_telnet \
+ ct_config \
+ ct_config_plain \
+ ct_config_xml \
+ ct_slave
TARGET_MODULES= $(MODULES:%=$(EBIN)/%)
diff --git a/lib/common_test/src/common_test.app.src b/lib/common_test/src/common_test.app.src
index 7b72932ad4..b42173f412 100644
--- a/lib/common_test/src/common_test.app.src
+++ b/lib/common_test/src/common_test.app.src
@@ -1,19 +1,19 @@
% This is an -*- erlang -*- file.
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
{application, common_test,
@@ -42,10 +42,15 @@
ct_testspec,
ct_util,
unix_telnet,
- vts
+ vts,
+ ct_config,
+ ct_config_plain,
+ ct_config_xml,
+ ct_slave
]},
{registered, [ct_logs,
ct_util_server,
+ ct_config_server,
ct_make_ref,
vts,
ct_master,
diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl
index 8ae041e5b4..0d82a86e7d 100644
--- a/lib/common_test/src/ct.erl
+++ b/lib/common_test/src/ct.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -59,18 +59,22 @@
%% Test suite API
-export([require/1, require/2,
get_config/1, get_config/2, get_config/3,
+ reload_config/1,
log/1, log/2, log/3,
print/1, print/2, print/3,
pal/1, pal/2, pal/3,
fail/1, comment/1,
- testcases/2, userdata/2, userdata/3]).
+ testcases/2, userdata/2, userdata/3,
+ timetrap/1, sleep/1]).
+
+%% New API for manipulating with config handlers
+-export([add_config/2, remove_config/2]).
%% Other interface functions
-export([get_status/0, abort_current_testcase/1,
encrypt_config_file/2, encrypt_config_file/3,
decrypt_config_file/2, decrypt_config_file/3]).
-
-export([get_target_name/1]).
-export([parse_table/1, listenv/1]).
@@ -93,7 +97,7 @@
%%% <code>install([{config,["config_node.ctc","config_user.ctc"]}])</code>.</p>
%%%
%%% <p>Note that this function is automatically run by the
-%%% <code>run_test</code> script.</p>
+%%% <code>run_test</code> program.</p>
install(Opts) ->
ct_run:install(Opts).
@@ -135,14 +139,20 @@ run(TestDirs) ->
%%% @spec run_test(Opts) -> Result
%%% Opts = [OptTuples]
%%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} |
-%%% {testcase,Cases} | {group,Groups} | {spec,TestSpecs} |
+%%% {userconfig, UserConfig} |
+%%% {testcase,Cases} | {group,Groups} | {spec,TestSpecs} |
%%% {allow_user_terms,Bool} | {logdir,LogDir} |
-%%% {silent_connections,Conns} | {cover,CoverSpecFile} |
-%%% {step,StepOpts} | {event_handler,EventHandlers} | {include,InclDirs} |
-%%% {auto_compile,Bool} | {repeat,N} | {duration,DurTime} |
-%%% {until,StopTime} | {force_stop,Bool} | {decrypt,DecryptKeyOrFile} |
+%%% {silent_connections,Conns} | {stylesheet,CSSFile} |
+%%% {cover,CoverSpecFile} | {step,StepOpts} |
+%%% {event_handler,EventHandlers} | {include,InclDirs} |
+%%% {auto_compile,Bool} | {multiply_timetraps,M} | {scale_timetraps,Bool} |
+%%% {repeat,N} | {duration,DurTime} | {until,StopTime} |
+%%% {force_stop,Bool} | {decrypt,DecryptKeyOrFile} |
%%% {refresh_logs,LogDir} | {basic_html,Bool}
%%% CfgFiles = [string()] | string()
+%%% UserConfig = [{CallbackMod,CfgStrings}] | {CallbackMod,CfgStrings}
+%%% CallbackMod = atom()
+%%% CfgStrings = [string()] | string()
%%% TestDirs = [string()] | string()
%%% Suites = [string()] | string()
%%% Cases = [atom()] | atom()
@@ -150,6 +160,7 @@ run(TestDirs) ->
%%% TestSpecs = [string()] | string()
%%% LogDir = string()
%%% Conns = all | [atom()]
+%%% CSSFile = string()
%%% CoverSpecFile = string()
%%% StepOpts = [StepOpt] | []
%%% StepOpt = config | keep_inactive
@@ -157,6 +168,7 @@ run(TestDirs) ->
%%% EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
%%% InitArgs = [term()]
%%% InclDirs = [string()] | string()
+%%% M = integer()
%%% N = integer()
%%% DurTime = string(HHMMSS)
%%% StopTime = string(YYMoMoDDHHMMSS) | string(HHMMSS)
@@ -165,11 +177,11 @@ run(TestDirs) ->
%%% DecryptFile = string()
%%% Result = [TestResult] | {error,Reason}
%%% @doc Run tests as specified by the combination of options in <code>Opts</code>.
-%%% The options are the same as those used with the <code>run_test</code> script.
+%%% The options are the same as those used with the <code>run_test</code> program.
%%% Note that here a <code>TestDir</code> can be used to point out the path to
%%% a <code>Suite</code>. Note also that the option <code>testcase</code>
%%% corresponds to the <code>-case</code> option in the <code>run_test</code>
-%%% script. Configuration files specified in <code>Opts</code> will be
+%%% program. Configuration files specified in <code>Opts</code> will be
%%% installed automatically at startup.
run_test(Opts) ->
ct_run:run_test(Opts).
@@ -211,7 +223,7 @@ step(TestDir,Suite,Case,Opts) ->
%%%
%%% <p>From this mode all test case support functions can be executed
%%% directly from the erlang shell. The interactive mode can also be
-%%% started from the unix command line with <code>run_test -shell
+%%% started from the OS command line with <code>run_test -shell
%%% [-config File...]</code>.</p>
%%%
%%% <p>If any functions using "required config data" (e.g. telnet or
@@ -269,7 +281,7 @@ stop_interactive() ->
%%% @see get_config/2
%%% @see get_config/3
require(Required) ->
- ct_util:require(Required).
+ ct_config:require(Required).
%%%-----------------------------------------------------------------
%%% @spec require(Name,Required) -> ok | {error,Reason}
@@ -304,19 +316,19 @@ require(Required) ->
%%% @see get_config/2
%%% @see get_config/3
require(Name,Required) ->
- ct_util:require(Name,Required).
+ ct_config:require(Name,Required).
%%%-----------------------------------------------------------------
%%% @spec get_config(Required) -> Value
%%% @equiv get_config(Required,undefined,[])
get_config(Required) ->
- ct_util:get_config(Required,undefined,[]).
+ ct_config:get_config(Required,undefined,[]).
%%%-----------------------------------------------------------------
%%% @spec get_config(Required,Default) -> Value
%%% @equiv get_config(Required,Default,[])
get_config(Required,Default) ->
- ct_util:get_config(Required,Default,[]).
+ ct_config:get_config(Required,Default,[]).
%%%-----------------------------------------------------------------
%%% @spec get_config(Required,Default,Opts) -> ValueOrElement
@@ -375,7 +387,26 @@ get_config(Required,Default) ->
%%% @see require/1
%%% @see require/2
get_config(Required,Default,Opts) ->
- ct_util:get_config(Required,Default,Opts).
+ ct_config:get_config(Required,Default,Opts).
+
+%%%-----------------------------------------------------------------
+%%% @spec reload_config(Required) -> ValueOrElement
+%%% Required = KeyOrName | {KeyOrName,SubKey}
+%%% KeyOrName = atom()
+%%% SubKey = atom()
+%%% ValueOrElement = term()
+%%%
+%%% @doc Reload config file which contains specified configuration key.
+%%%
+%%% <p>This function performs updating of the configuration data from which the
+%%% given configuration variable was read, and returns the (possibly) new
+%%% value of this variable.</p>
+%%% <p>Note that if some variables were present in the configuration but are not loaded
+%%% using this function, they will be removed from the configuration table together
+%%% with their aliases.</p>
+%%%
+reload_config(Required)->
+ ct_config:reload_config(Required).
%%%-----------------------------------------------------------------
%%% @spec log(Format) -> ok
@@ -734,7 +765,7 @@ abort_current_testcase(Reason) ->
%%% <p>See the <code>crypto</code> application for details on DES3
%%% encryption/decryption.</p>
encrypt_config_file(SrcFileName, EncryptFileName) ->
- ct_util:encrypt_config_file(SrcFileName, EncryptFileName).
+ ct_config:encrypt_config_file(SrcFileName, EncryptFileName).
%%%-----------------------------------------------------------------
%%% @spec encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) ->
@@ -754,7 +785,7 @@ encrypt_config_file(SrcFileName, EncryptFileName) ->
%%% <p>See the <code>crypto</code> application for details on DES3
%%% encryption/decryption.</p>
encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) ->
- ct_util:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile).
+ ct_config:encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile).
%%%-----------------------------------------------------------------
%%% @spec decrypt_config_file(EncryptFileName, TargetFileName) ->
@@ -770,7 +801,7 @@ encrypt_config_file(SrcFileName, EncryptFileName, KeyOrFile) ->
%%% <code>.ct_config.crypt</code> in the current directory, or the
%%% home directory of the user (it is searched for in that order).</p>
decrypt_config_file(EncryptFileName, TargetFileName) ->
- ct_util:decrypt_config_file(EncryptFileName, TargetFileName).
+ ct_config:decrypt_config_file(EncryptFileName, TargetFileName).
%%%-----------------------------------------------------------------
%%% @spec decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) ->
@@ -785,5 +816,65 @@ decrypt_config_file(EncryptFileName, TargetFileName) ->
%%% file contents is saved in the target file. The key must have the
%%% the same value as that used for encryption.</p>
decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile) ->
- ct_util:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile).
+ ct_config:decrypt_config_file(EncryptFileName, TargetFileName, KeyOrFile).
+
+%%%-----------------------------------------------------------------
+%%% @spec add_config(Callback, Config) -> ok | {error, Reason}
+%%% Callback = atom()
+%%% Config = string()
+%%% Reason = term()
+%%%
+%%% @doc <p>This function loads configuration variables using the
+%%% given callback module and configuration string. Callback module
+%%% should be either loaded or present in the code part. Loaded
+%%% configuration variables can later be removed using
+%%% <code>remove_config/2</code> function.</p>
+add_config(Callback, Config)->
+ ct_config:add_config(Callback, Config).
+
+%%%-----------------------------------------------------------------
+%%% @spec remove_config(Callback, Config) -> ok
+%%% Callback = atom()
+%%% Config = string()
+%%% Reason = term()
+%%%
+%%% @doc <p>This function removes configuration variables (together with
+%%% their aliases) which were loaded with specified callback module and
+%%% configuration string.</p>
+remove_config(Callback, Config) ->
+ ct_config:remove_config(Callback, Config).
+
+%%%-----------------------------------------------------------------
+%%% @spec timetrap(Time) -> ok
+%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity
+%%% Hours = integer()
+%%% Mins = integer()
+%%% Secs = integer()
+%%% Millisecs = integer() | float()
+%%%
+%%% @doc <p>Use this function to set a new timetrap for the running test case.</p>
+timetrap(Time) ->
+ test_server:timetrap(Time).
+
+%%%-----------------------------------------------------------------
+%%% @spec sleep(Time) -> ok
+%%% Time = {hours,Hours} | {minutes,Mins} | {seconds,Secs} | Millisecs | infinity
+%%% Hours = integer()
+%%% Mins = integer()
+%%% Secs = integer()
+%%% Millisecs = integer() | float()
+%%%
+%%% @doc <p>This function, similar to <c>timer:sleep/1</c>, suspends the test
+%%% case for specified time. However, this function also multiplies
+%%% <c>Time</c> with the 'multiply_timetraps' value (if set) and under
+%%% certain circumstances also scales up the time automatically
+%%% if 'scale_timetraps' is set to true (default is false).</p>
+sleep({hours,Hs}) ->
+ sleep(trunc(Hs * 1000 * 60 * 60));
+sleep({minutes,Ms}) ->
+ sleep(trunc(Ms * 1000 * 60));
+sleep({seconds,Ss}) ->
+ sleep(trunc(Ss * 1000));
+sleep(Time) ->
+ test_server:adjusted_sleep(Time).
diff --git a/lib/common_test/src/ct_config.erl b/lib/common_test/src/ct_config.erl
new file mode 100644
index 0000000000..dc6fcc66e5
--- /dev/null
+++ b/lib/common_test/src/ct_config.erl
@@ -0,0 +1,786 @@
+%%--------------------------------------------------------------------
+%% %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%
+%%----------------------------------------------------------------------
+%% File : ct_config.erl
+%% Description : CT module for reading and manipulating of configuration
+%% data
+%%
+%% Created : 15 February 2010
+%%----------------------------------------------------------------------
+-module(ct_config).
+
+-export([start/1, stop/0]).
+
+-export([read_config_files/1,
+ get_config_file_list/1]).
+
+-export([require/1, require/2]).
+
+-export([get_config/1, get_config/2, get_config/3,
+ get_all_config/0]).
+
+-export([set_default_config/2, set_default_config/3]).
+
+-export([delete_default_config/1]).
+
+-export([reload_config/1, update_config/2]).
+
+-export([release_allocated/0]).
+
+-export([encrypt_config_file/2, encrypt_config_file/3,
+ decrypt_config_file/2, decrypt_config_file/3,
+ get_crypt_key_from_file/0, get_crypt_key_from_file/1]).
+
+-export([get_ref_from_name/1, get_name_from_ref/1, get_key_from_name/1]).
+
+-export([check_config_files/1, prepare_config_list/1]).
+
+-export([add_config/2, remove_config/2]).
+
+-include("ct_util.hrl").
+
+-define(cryptfile, ".ct_config.crypt").
+
+-record(ct_conf,{key,value,handler,config,ref,name='_UNDEF',default=false}).
+
+start(Mode) ->
+ case whereis(ct_config_server) of
+ undefined ->
+ Me = self(),
+ Pid = spawn_link(fun() -> do_start(Me) end),
+ receive
+ {Pid,started} -> Pid;
+ {Pid,Error} -> exit(Error)
+ end;
+ Pid ->
+ case ct_util:get_mode() of
+ interactive when Mode==interactive ->
+ Pid;
+ interactive ->
+ {error,interactive_mode};
+ _OtherMode ->
+ Pid
+ end
+ end.
+
+do_start(Parent) ->
+ process_flag(trap_exit,true),
+ register(ct_config_server,self()),
+ ct_util:create_table(?attr_table,bag,#ct_conf.key),
+ {ok,StartDir} = file:get_cwd(),
+ Opts = case ct_util:read_opts() of
+ {ok,Opts1} ->
+ Opts1;
+ Error ->
+ Parent ! {self(),Error},
+ exit(Error)
+ end,
+ case read_config_files(Opts) of
+ ok ->
+ Parent ! {self(),started},
+ loop(StartDir);
+ ReadError ->
+ Parent ! {self(),ReadError},
+ exit(ReadError)
+ end.
+
+stop() ->
+ case whereis(ct_config_server) of
+ undefined -> ok;
+ _ -> call({stop})
+ end.
+
+call(Msg) ->
+ MRef = erlang:monitor(process, whereis(ct_config_server)),
+ Ref = make_ref(),
+ ct_config_server ! {Msg,{self(),Ref}},
+ receive
+ {Ref, Result} ->
+ erlang:demonitor(MRef, [flush]),
+ Result;
+ {'DOWN',MRef,process,_,Reason} ->
+ {error,{ct_util_server_down,Reason}}
+ end.
+
+return({To,Ref},Result) ->
+ To ! {Ref, Result}.
+
+loop(StartDir) ->
+ receive
+ {{require,Name,Tag,SubTags},From} ->
+ Result = do_require(Name,Tag,SubTags),
+ return(From,Result),
+ loop(StartDir);
+ {{set_default_config,{Config,Scope}},From} ->
+ set_config(Config,{true,Scope}),
+ return(From,ok),
+ loop(StartDir);
+ {{set_default_config,{Name,Config,Scope}},From} ->
+ set_config(Name,Config,{true,Scope}),
+ return(From,ok),
+ loop(StartDir);
+ {{delete_default_config,Scope},From} ->
+ delete_config({true,Scope}),
+ return(From,ok),
+ loop(StartDir);
+ {{update_config,{Name,NewConfig}},From} ->
+ update_conf(Name,NewConfig),
+ return(From,ok),
+ loop(StartDir);
+ {{reload_config, KeyOrName},From}->
+ NewValue = reload_conf(KeyOrName),
+ return(From, NewValue),
+ loop(StartDir);
+ {{stop},From} ->
+ ets:delete(?attr_table),
+ file:set_cwd(StartDir),
+ return(From,ok)
+ end.
+
+set_default_config(NewConfig, Scope) ->
+ call({set_default_config, {NewConfig, Scope}}).
+
+set_default_config(Name, NewConfig, Scope) ->
+ call({set_default_config, {Name, NewConfig, Scope}}).
+
+delete_default_config(Scope) ->
+ call({delete_default_config, Scope}).
+
+update_config(Name, Config) ->
+ call({update_config, {Name, Config}}).
+
+reload_config(KeyOrName) ->
+ call({reload_config, KeyOrName}).
+
+process_default_configs(Opts) ->
+ case lists:keysearch(config, 1, Opts) of
+ {value,{_,Files=[File|_]}} when is_list(File) ->
+ Files;
+ {value,{_,File=[C|_]}} when is_integer(C) ->
+ [File];
+ {value,{_,[]}} ->
+ [];
+ false ->
+ []
+ end.
+
+process_user_configs(Opts, Acc) ->
+ case lists:keytake(userconfig, 1, Opts) of
+ false ->
+ lists:reverse(Acc);
+ {value, {userconfig, Config=[{_,_}|_]}, NewOpts} ->
+ Acc1 = lists:map(fun({_Callback, []}=Cfg) ->
+ Cfg;
+ ({Callback, Files=[File|_]}) when is_list(File) ->
+ {Callback, Files};
+ ({Callback, File=[C|_]}) when is_integer(C) ->
+ {Callback, [File]}
+ end, Config),
+ process_user_configs(NewOpts, lists:reverse(Acc1)++Acc);
+ {value, {userconfig, {Callback, []}}, NewOpts} ->
+ process_user_configs(NewOpts, [{Callback, []} | Acc]);
+ {value, {userconfig, {Callback, Files=[File|_]}}, NewOpts} when is_list(File) ->
+ process_user_configs(NewOpts, [{Callback, Files} | Acc]);
+ {value, {userconfig, {Callback, File=[C|_]}}, NewOpts} when is_integer(C) ->
+ process_user_configs(NewOpts, [{Callback, [File]} | Acc])
+ end.
+
+get_config_file_list(Opts) ->
+ DefaultConfigs = process_default_configs(Opts),
+ CfgFiles =
+ if
+ DefaultConfigs == []->
+ [];
+ true->
+ [{?ct_config_txt, DefaultConfigs}]
+ end ++
+ process_user_configs(Opts, []),
+ CfgFiles.
+
+read_config_files(Opts) ->
+ AddCallback = fun(CallBack, []) ->
+ [{CallBack, []}];
+ (CallBack, [F|_]=Files) when is_integer(F) ->
+ [{CallBack, Files}];
+ (CallBack, [F|_]=Files) when is_list(F) ->
+ lists:map(fun(X) -> {CallBack, X} end, Files)
+ end,
+ ConfigFiles = case lists:keyfind(config, 1, Opts) of
+ {config, ConfigLists}->
+ lists:foldr(fun({Callback,Files}, Acc) ->
+ AddCallback(Callback,Files) ++ Acc
+ end,
+ [],
+ ConfigLists);
+ false->
+ []
+ end,
+ read_config_files_int(ConfigFiles, fun store_config/3).
+
+read_config_files_int([{Callback, File}|Files], FunToSave) ->
+ case Callback:read_config(File) of
+ {ok, Config} ->
+ FunToSave(Config, Callback, File),
+ read_config_files_int(Files, FunToSave);
+ {error, ErrorName, ErrorDetail}->
+ {user_error, {ErrorName, File, ErrorDetail}}
+ end;
+read_config_files_int([], _FunToSave) ->
+ ok.
+
+store_config(Config, Callback, File) ->
+ [ets:insert(?attr_table,
+ #ct_conf{key=Key,
+ value=Val,
+ handler=Callback,
+ config=File,
+ ref=ct_util:ct_make_ref(),
+ default=false}) ||
+ {Key,Val} <- Config].
+
+keyfindall(Key, Pos, List) ->
+ [E || E <- List, element(Pos, E) =:= Key].
+
+rewrite_config(Config, Callback, File) ->
+ OldRows = ets:match_object(?attr_table,
+ #ct_conf{handler=Callback,
+ config=File,_='_'}),
+ ets:match_delete(?attr_table,
+ #ct_conf{handler=Callback,
+ config=File,_='_'}),
+ Updater = fun({Key, Value}) ->
+ case keyfindall(Key, #ct_conf.key, OldRows) of
+ []->
+ ets:insert(?attr_table,
+ #ct_conf{key=Key,
+ value=Value,
+ handler=Callback,
+ config=File,
+ ref=ct_util:ct_make_ref()});
+ RowsToUpdate ->
+ Inserter = fun(Row) ->
+ ets:insert(?attr_table,
+ Row#ct_conf{value=Value,
+ ref=ct_util:ct_make_ref()})
+ end,
+ lists:foreach(Inserter, RowsToUpdate)
+ end
+ end,
+ [Updater({Key, Value})||{Key, Value}<-Config].
+
+set_config(Config,Default) ->
+ set_config('_UNDEF',Config,Default).
+
+set_config(Name,Config,Default) ->
+ [ets:insert(?attr_table,
+ #ct_conf{key=Key,value=Val,ref=ct_util:ct_make_ref(),
+ name=Name,default=Default}) ||
+ {Key,Val} <- Config].
+
+get_config(KeyOrName) ->
+ get_config(KeyOrName,undefined,[]).
+
+get_config(KeyOrName,Default) ->
+ get_config(KeyOrName,Default,[]).
+
+get_config(KeyOrName,Default,Opts) when is_atom(KeyOrName) ->
+ case lookup_config(KeyOrName) of
+ [] ->
+ Default;
+ [{_Ref,Val}|_] = Vals ->
+ case {lists:member(all,Opts),lists:member(element,Opts)} of
+ {true,true} ->
+ [{KeyOrName,V} || {_R,V} <- lists:sort(Vals)];
+ {true,false} ->
+ [V || {_R,V} <- lists:sort(Vals)];
+ {false,true} ->
+ {KeyOrName,Val};
+ {false,false} ->
+ Val
+ end
+ end;
+
+get_config({KeyOrName,SubKey},Default,Opts) ->
+ case lookup_config(KeyOrName) of
+ [] ->
+ Default;
+ Vals ->
+ Vals1 = case [Val || {_Ref,Val} <- lists:sort(Vals)] of
+ Result=[L|_] when is_list(L) ->
+ case L of
+ [{_,_}|_] ->
+ Result;
+ _ ->
+ []
+ end;
+ _ ->
+ []
+ end,
+ case get_subconfig([SubKey],Vals1,[],Opts) of
+ {ok,[{_,SubVal}|_]=SubVals} ->
+ case {lists:member(all,Opts),lists:member(element,Opts)} of
+ {true,true} ->
+ [{{KeyOrName,SubKey},Val} || {_,Val} <- SubVals];
+ {true,false} ->
+ [Val || {_SubKey,Val} <- SubVals];
+ {false,true} ->
+ {{KeyOrName,SubKey},SubVal};
+ {false,false} ->
+ SubVal
+ end;
+ _ ->
+ Default
+ end
+ end.
+
+get_subconfig(SubKeys,Values) ->
+ get_subconfig(SubKeys,Values,[],[]).
+
+get_subconfig(SubKeys,[Value|Rest],Mapped,Opts) ->
+ case do_get_config(SubKeys,Value,[]) of
+ {ok,SubMapped} ->
+ case lists:member(all,Opts) of
+ true ->
+ get_subconfig(SubKeys,Rest,Mapped++SubMapped,Opts);
+ false ->
+ {ok,SubMapped}
+ end;
+ _Error ->
+ get_subconfig(SubKeys,Rest,Mapped,Opts)
+ end;
+get_subconfig(SubKeys,[],[],_) ->
+ {error,{not_available,SubKeys}};
+get_subconfig(_SubKeys,[],Mapped,_) ->
+ {ok,Mapped}.
+
+do_get_config([Key|Required],Available,Mapped) ->
+ case lists:keysearch(Key,1,Available) of
+ {value,{Key,Value}} ->
+ NewAvailable = lists:keydelete(Key,1,Available),
+ NewMapped = [{Key,Value}|Mapped],
+ do_get_config(Required,NewAvailable,NewMapped);
+ false ->
+ {error,{not_available,Key}}
+ end;
+do_get_config([],_Available,Mapped) ->
+ {ok,lists:reverse(Mapped)}.
+
+get_all_config() ->
+ ets:select(?attr_table,[{#ct_conf{name='$1',key='$2',value='$3',
+ default='$4',_='_'},
+ [],
+ [{{'$1','$2','$3','$4'}}]}]).
+
+lookup_config(KeyOrName) ->
+ case lookup_name(KeyOrName) of
+ [] ->
+ lookup_key(KeyOrName);
+ Values ->
+ Values
+ end.
+
+lookup_name(Name) ->
+ ets:select(?attr_table,[{#ct_conf{ref='$1',value='$2',name=Name,_='_'},
+ [],
+ [{{'$1','$2'}}]}]).
+lookup_key(Key) ->
+ ets:select(?attr_table,[{#ct_conf{key=Key,ref='$1',value='$2',name='_UNDEF',_='_'},
+ [],
+ [{{'$1','$2'}}]}]).
+
+lookup_handler_for_config({Key, _Subkey}) ->
+ lookup_handler_for_config(Key);
+lookup_handler_for_config(KeyOrName) ->
+ case lookup_handler_for_name(KeyOrName) of
+ [] ->
+ lookup_handler_for_key(KeyOrName);
+ Values ->
+ Values
+ end.
+
+lookup_handler_for_name(Name) ->
+ ets:select(?attr_table,[{#ct_conf{handler='$1',config='$2',name=Name,_='_'},
+ [],
+ [{{'$1','$2'}}]}]).
+
+lookup_handler_for_key(Key) ->
+ ets:select(?attr_table,[{#ct_conf{handler='$1',config='$2',key=Key,_='_'},
+ [],
+ [{{'$1','$2'}}]}]).
+
+
+update_conf(Name, NewConfig) ->
+ Old = ets:select(?attr_table,[{#ct_conf{name=Name,_='_'},[],['$_']}]),
+ lists:foreach(fun(OldElem) ->
+ NewElem = OldElem#ct_conf{value=NewConfig},
+ ets:delete_object(?attr_table, OldElem),
+ ets:insert(?attr_table, NewElem)
+ end, Old),
+ ok.
+
+reload_conf(KeyOrName) ->
+ case lookup_handler_for_config(KeyOrName) of
+ []->
+ undefined;
+ HandlerList->
+ HandlerList2 = lists:usort(HandlerList),
+ read_config_files_int(HandlerList2, fun rewrite_config/3),
+ get_config(KeyOrName)
+ end.
+
+release_allocated() ->
+ Allocated = ets:select(?attr_table,[{#ct_conf{name='$1',_='_'},
+ [{'=/=','$1','_UNDEF'}],
+ ['$_']}]),
+ release_allocated(Allocated).
+release_allocated([H|T]) ->
+ ets:delete_object(?attr_table,H),
+ ets:insert(?attr_table,H#ct_conf{name='_UNDEF'}),
+ release_allocated(T);
+release_allocated([]) ->
+ ok.
+
+allocate(Name,Key,SubKeys) ->
+ case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of
+ [] ->
+ {error,{not_available,Key}};
+ Available ->
+ case allocate_subconfig(Name,SubKeys,Available,false) of
+ ok ->
+ ok;
+ Error ->
+ Error
+ end
+ end.
+
+allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) ->
+ case do_get_config(SubKeys,Value,[]) of
+ {ok,_SubMapped} ->
+ ets:insert(?attr_table,C#ct_conf{name=Name}),
+ allocate_subconfig(Name,SubKeys,Rest,true);
+ _Error ->
+ allocate_subconfig(Name,SubKeys,Rest,Found)
+ end;
+allocate_subconfig(_Name,_SubKeys,[],true) ->
+ ok;
+allocate_subconfig(_Name,SubKeys,[],false) ->
+ {error,{not_available,SubKeys}}.
+
+delete_config(Default) ->
+ ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}),
+ ok.
+
+require(Key) when is_atom(Key) ->
+ require({Key,[]});
+require({Key,SubKeys}) when is_atom(Key) ->
+ allocate('_UNDEF',Key,to_list(SubKeys));
+require(Key) ->
+ {error,{invalid,Key}}.
+
+require(Name,Key) when is_atom(Key) ->
+ require(Name,{Key,[]});
+require(Name,{Key,SubKeys}) when is_atom(Name), is_atom(Key) ->
+ call({require,Name,Key,to_list(SubKeys)});
+require(Name,Keys) ->
+ {error,{invalid,{Name,Keys}}}.
+
+to_list(X) when is_list(X) -> X;
+to_list(X) -> [X].
+
+do_require(Name,Key,SubKeys) when is_list(SubKeys) ->
+ case get_key_from_name(Name) of
+ {error,_} ->
+ allocate(Name,Key,SubKeys);
+ {ok,Key} ->
+ %% already allocated - check that it has all required subkeys
+ Vals = [Val || {_Ref,Val} <- lookup_name(Name)],
+ case get_subconfig(SubKeys,Vals) of
+ {ok,_SubMapped} ->
+ ok;
+ Error ->
+ Error
+ end;
+ {ok,OtherKey} ->
+ {error,{name_in_use,Name,OtherKey}}
+ end.
+
+encrypt_config_file(SrcFileName, EncryptFileName) ->
+ case get_crypt_key_from_file() of
+ {error,_} = E ->
+ E;
+ Key ->
+ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key})
+ end.
+
+get_ref_from_name(Name) ->
+ case ets:select(?attr_table,[{#ct_conf{name=Name,ref='$1',_='_'},
+ [],
+ ['$1']}]) of
+ [Ref] ->
+ {ok,Ref};
+ _ ->
+ {error,{no_such_name,Name}}
+ end.
+
+get_name_from_ref(Ref) ->
+ case ets:select(?attr_table,[{#ct_conf{name='$1',ref=Ref,_='_'},
+ [],
+ ['$1']}]) of
+ [Name] ->
+ {ok,Name};
+ _ ->
+ {error,{no_such_ref,Ref}}
+ end.
+
+get_key_from_name(Name) ->
+ case ets:select(?attr_table,[{#ct_conf{name=Name,key='$1',_='_'},
+ [],
+ ['$1']}]) of
+ [Key|_] ->
+ {ok,Key};
+ _ ->
+ {error,{no_such_name,Name}}
+ end.
+
+encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) ->
+ case get_crypt_key_from_file(KeyFile) of
+ {error,_} = E ->
+ E;
+ Key ->
+ encrypt_config_file(SrcFileName, EncryptFileName, {key,Key})
+ end;
+
+encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
+ crypto:start(),
+ {K1,K2,K3,IVec} = make_crypto_key(Key),
+ case file:read_file(SrcFileName) of
+ {ok,Bin0} ->
+ Bin1 = term_to_binary({SrcFileName,Bin0}),
+ Bin2 = case byte_size(Bin1) rem 8 of
+ 0 -> Bin1;
+ N -> list_to_binary([Bin1,random_bytes(8-N)])
+ end,
+ EncBin = crypto:des3_cbc_encrypt(K1, K2, K3, IVec, Bin2),
+ case file:write_file(EncryptFileName, EncBin) of
+ ok ->
+ io:format("~s --(encrypt)--> ~s~n",
+ [SrcFileName,EncryptFileName]),
+ ok;
+ {error,Reason} ->
+ {error,{Reason,EncryptFileName}}
+ end;
+ {error,Reason} ->
+ {error,{Reason,SrcFileName}}
+ end.
+
+decrypt_config_file(EncryptFileName, TargetFileName) ->
+ case get_crypt_key_from_file() of
+ {error,_} = E ->
+ E;
+ Key ->
+ decrypt_config_file(EncryptFileName, TargetFileName, {key,Key})
+ end.
+
+decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) ->
+ case get_crypt_key_from_file(KeyFile) of
+ {error,_} = E ->
+ E;
+ Key ->
+ decrypt_config_file(EncryptFileName, TargetFileName, {key,Key})
+ end;
+
+decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
+ crypto:start(),
+ {K1,K2,K3,IVec} = make_crypto_key(Key),
+ case file:read_file(EncryptFileName) of
+ {ok,Bin} ->
+ DecBin = crypto:des3_cbc_decrypt(K1, K2, K3, IVec, Bin),
+ case catch binary_to_term(DecBin) of
+ {'EXIT',_} ->
+ {error,bad_file};
+ {_SrcFile,SrcBin} ->
+ case TargetFileName of
+ undefined ->
+ {ok,SrcBin};
+ _ ->
+ case file:write_file(TargetFileName, SrcBin) of
+ ok ->
+ io:format("~s --(decrypt)--> ~s~n",
+ [EncryptFileName,TargetFileName]),
+ ok;
+ {error,Reason} ->
+ {error,{Reason,TargetFileName}}
+ end
+ end
+ end;
+ {error,Reason} ->
+ {error,{Reason,EncryptFileName}}
+ end.
+
+get_crypt_key_from_file(File) ->
+ case file:read_file(File) of
+ {ok,Bin} ->
+ case catch string:tokens(binary_to_list(Bin), [$\n,$\r]) of
+ [Key] ->
+ Key;
+ _ ->
+ {error,{bad_crypt_file,File}}
+ end;
+ {error,Reason} ->
+ {error,{Reason,File}}
+ end.
+
+get_crypt_key_from_file() ->
+ CwdFile = filename:join(".",?cryptfile),
+ {Result,FullName} =
+ case file:read_file(CwdFile) of
+ {ok,Bin} ->
+ {Bin,CwdFile};
+ _ ->
+ case init:get_argument(home) of
+ {ok,[[Home]]} ->
+ HomeFile = filename:join(Home,?cryptfile),
+ case file:read_file(HomeFile) of
+ {ok,Bin} ->
+ {Bin,HomeFile};
+ _ ->
+ {{error,no_crypt_file},noent}
+ end;
+ _ ->
+ {{error,no_crypt_file},noent}
+ end
+ end,
+ case FullName of
+ noent ->
+ Result;
+ _ ->
+ case catch string:tokens(binary_to_list(Result), [$\n,$\r]) of
+ [Key] ->
+ io:format("~nCrypt key file: ~s~n", [FullName]),
+ Key;
+ _ ->
+ {error,{bad_crypt_file,FullName}}
+ end
+ end.
+
+make_crypto_key(String) ->
+ <<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
+ <<K3:8/binary,IVec:8/binary>> = erlang:md5([First|lists:reverse(String)]),
+ {K1,K2,K3,IVec}.
+
+random_bytes(N) ->
+ {A,B,C} = now(),
+ random:seed(A, B, C),
+ random_bytes_1(N, []).
+
+random_bytes_1(0, Acc) -> Acc;
+random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]).
+
+check_callback_load(Callback) ->
+ case code:is_loaded(Callback) of
+ {file, _Filename}->
+ check_exports(Callback);
+ false->
+ case code:load_file(Callback) of
+ {module, Callback}->
+ check_exports(Callback);
+ {error, Error}->
+ {error, Error}
+ end
+ end.
+
+check_exports(Callback) ->
+ Fs = Callback:module_info(exports),
+ case {lists:member({check_parameter,1},Fs),
+ lists:member({read_config,1},Fs)} of
+ {true, true} ->
+ {ok, Callback};
+ _ ->
+ {error, missing_callback_functions}
+ end.
+
+check_config_files(Configs) ->
+ ConfigChecker = fun
+ ({Callback, [F|_R]=Files}) ->
+ case check_callback_load(Callback) of
+ {ok, Callback} ->
+ if is_integer(F) ->
+ Callback:check_parameter(Files);
+ is_list(F) ->
+ lists:map(fun(File) ->
+ Callback:check_parameter(File)
+ end,
+ Files)
+ end;
+ {error, Why}->
+ {error, {callback, {Callback,Why}}}
+ end;
+ ({Callback, []}) ->
+ case check_callback_load(Callback) of
+ {ok, Callback}->
+ Callback:check_parameter([]);
+ {error, Why}->
+ {error, {callback, {Callback,Why}}}
+ end
+ end,
+ lists:keysearch(error, 1, lists:flatten(lists:map(ConfigChecker, Configs))).
+
+prepare_user_configs([ConfigString|UserConfigs], Acc, new) ->
+ prepare_user_configs(UserConfigs,
+ [{list_to_atom(ConfigString), []}|Acc],
+ cur);
+prepare_user_configs(["and"|UserConfigs], Acc, _) ->
+ prepare_user_configs(UserConfigs, Acc, new);
+prepare_user_configs([ConfigString|UserConfigs], [{LastMod, LastList}|Acc], cur) ->
+ prepare_user_configs(UserConfigs,
+ [{LastMod, [ConfigString|LastList]}|Acc],
+ cur);
+prepare_user_configs([], Acc, _) ->
+ Acc.
+
+prepare_config_list(Args) ->
+ ConfigFiles = case lists:keysearch(ct_config, 1, Args) of
+ {value,{ct_config,Files}}->
+ [{?ct_config_txt,[filename:absname(F) || F <- Files]}];
+ false->
+ []
+ end,
+ UserConfigs = case lists:keysearch(userconfig, 1, Args) of
+ {value,{userconfig,UserConfigFiles}}->
+ prepare_user_configs(UserConfigFiles, [], new);
+ false->
+ []
+ end,
+ ConfigFiles ++ UserConfigs.
+
+% TODO: add logging of the loaded configuration file to the CT FW log!!!
+add_config(Callback, []) ->
+ read_config_files_int([{Callback, []}], fun store_config/3);
+add_config(Callback, [File|_Files]=Config) when is_list(File) ->
+ lists:foreach(fun(CfgStr) ->
+ read_config_files_int([{Callback, CfgStr}], fun store_config/3) end,
+ Config);
+add_config(Callback, [C|_]=Config) when is_integer(C) ->
+ read_config_files_int([{Callback, Config}], fun store_config/3),
+ ok.
+
+remove_config(Callback, Config) ->
+ ets:match_delete(?attr_table,
+ #ct_conf{handler=Callback,
+ config=Config,_='_'}),
+ ok.
diff --git a/lib/common_test/src/ct_config_plain.erl b/lib/common_test/src/ct_config_plain.erl
new file mode 100644
index 0000000000..3fbc8af9fb
--- /dev/null
+++ b/lib/common_test/src/ct_config_plain.erl
@@ -0,0 +1,109 @@
+%%--------------------------------------------------------------------
+%% %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%
+%%----------------------------------------------------------------------
+%% File : ct_config_plain.erl
+%% Description : CT callback module for reading configs from text files
+%%
+%% Created : 15 February 2010
+%%----------------------------------------------------------------------
+-module(ct_config_plain).
+-export([read_config/1, check_parameter/1]).
+
+read_config(ConfigFile) ->
+ case file:consult(ConfigFile) of
+ {ok,Config} ->
+ {ok, Config};
+ {error,enoent} ->
+ {error, config_file_error, enoent};
+ {error,Reason} ->
+ Key =
+ case application:get_env(common_test, decrypt) of
+ {ok,KeyOrFile} ->
+ case KeyOrFile of
+ {key,K} ->
+ K;
+ {file,F} ->
+ ct_config:get_crypt_key_from_file(F)
+ end;
+ _ ->
+ ct_config:get_crypt_key_from_file()
+ end,
+ case Key of
+ {error,no_crypt_file} ->
+ {error, config_file_error, Reason};
+ {error,CryptError} ->
+ {error, decrypt_file_error, CryptError};
+ _ when is_list(Key) ->
+ case ct_config:decrypt_config_file(ConfigFile, undefined, {key,Key}) of
+ {ok,CfgBin} ->
+ case read_config_terms(CfgBin) of
+ {error,ReadFail} ->
+ {error, config_file_error, ReadFail};
+ Config ->
+ {ok, Config}
+ end;
+ {error,DecryptFail} ->
+ {error, decrypt_config_error, DecryptFail}
+ end;
+ _ ->
+ {error, bad_decrypt_key, Key}
+ end
+ end.
+
+% check if config file exists
+check_parameter(File)->
+ case filelib:is_file(File) of
+ true->
+ {ok, {file, File}};
+ false->
+ {error, {nofile, File}}
+ end.
+
+read_config_terms(Bin) when is_binary(Bin) ->
+ case catch binary_to_list(Bin) of
+ {'EXIT',_} ->
+ {error,invalid_textfile};
+ Lines ->
+ read_config_terms(Lines)
+ end;
+read_config_terms(Lines) when is_list(Lines) ->
+ read_config_terms1(erl_scan:tokens([], Lines, 0), 1, [], []).
+
+read_config_terms1({done,{ok,Ts,EL},Rest}, L, Terms, _) ->
+ case erl_parse:parse_term(Ts) of
+ {ok,Term} when Rest == [] ->
+ lists:reverse([Term|Terms]);
+ {ok,Term} ->
+ read_config_terms1(erl_scan:tokens([], Rest, 0),
+ EL+1, [Term|Terms], Rest);
+ _ ->
+ {error,{bad_term,{L,EL}}}
+ end;
+read_config_terms1({done,{eof,_},_}, _, Terms, Rest) when Rest == [] ->
+ lists:reverse(Terms);
+read_config_terms1({done,{eof,EL},_}, L, _, _) ->
+ {error,{bad_term,{L,EL}}};
+read_config_terms1({done,{error,Info,EL},_}, L, _, _) ->
+ {error,{Info,{L,EL}}};
+read_config_terms1({more,_}, L, Terms, Rest) ->
+ case string:tokens(Rest, [$\n,$\r,$\t]) of
+ [] ->
+ lists:reverse(Terms);
+ _ ->
+ {error,{bad_term,L}}
+ end.
diff --git a/lib/common_test/src/ct_config_xml.erl b/lib/common_test/src/ct_config_xml.erl
new file mode 100644
index 0000000000..8a6e75e635
--- /dev/null
+++ b/lib/common_test/src/ct_config_xml.erl
@@ -0,0 +1,118 @@
+%%--------------------------------------------------------------------
+%% %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%
+%%----------------------------------------------------------------------
+%% File : ct_config_xml.erl
+%% Description : CT callback module for reading configs from XML files
+%%
+%% Created : 16 February 2010
+%%----------------------------------------------------------------------
+-module(ct_config_xml).
+-export([read_config/1, check_parameter/1]).
+
+% read config file
+read_config(ConfigFile) ->
+ case catch do_read_xml_config(ConfigFile) of
+ {ok, Config}->
+ {ok, Config};
+ {error, Error, ErroneousString}->
+ {error, Error, ErroneousString}
+ end.
+
+% check file exists
+check_parameter(File)->
+ case filelib:is_file(File) of
+ true->
+ {ok, {file, File}};
+ false->
+ {error, {nofile, File}}
+ end.
+
+% actual reading of the config
+do_read_xml_config(ConfigFile)->
+ case catch xmerl_sax_parser:file(ConfigFile,
+ [{event_fun, fun event/3},
+ {event_state, []}]) of
+ {ok, EntityList, _}->
+ {ok, lists:reverse(transform_entity_list(EntityList))};
+ Oops->
+ {error, parsing_failed, Oops}
+ end.
+
+% event callback for xmerl_sax_parser
+event(Event, _LineNo, State) ->
+ tag(Event, State).
+
+% document start
+tag(startDocument, State) ->
+ State;
+
+% start of the config
+tag({startElement, _Uri, "config", _QName, _Attributes}, []) ->
+ [{"config", []}];
+
+% start tag
+tag({startElement, _Uri, Name, _QName, _Attributes}, Tags) ->
+ [{Name, []}|Tags];
+
+% value
+tag({characters, String}, [{Tag, _Value}|Tags]) ->
+ [{Tag, String}|Tags];
+
+% end tag
+tag({endElement, _Uri, _Name, _QName},
+ [Entity, {PrevEntityTag, PrevEntityValue}|Tags]) ->
+ NewHead = {PrevEntityTag, [Entity|PrevEntityValue]},
+ [NewHead|Tags];
+
+% end of the config
+tag({endElement, _Uri, "config", _QName}, [{"config", Config}]) ->
+ Config;
+
+% end of document, return result
+tag(endDocument, {_Tags, Result}) ->
+ Result;
+
+% default
+tag(_El, State) ->
+ State.
+
+% transform of the ugly deeply nested entity list to the key-value "tree"
+transform_entity_list(EntityList)->
+ lists:map(fun transform_entity/1, EntityList).
+
+% transform entity from {list(), list()} to {atom(), term()}
+transform_entity({Tag, [Value|Rest]}) when
+ is_tuple(Value)->
+ {list_to_atom(Tag), transform_entity_list(lists:reverse([Value|Rest]))};
+transform_entity({Tag, String})->
+ case list_to_term(String) of
+ {ok, Value}->
+ {list_to_atom(Tag), Value};
+ Error->
+ throw(Error)
+ end.
+
+% transform a string with Erlang terms
+list_to_term(String) ->
+ {ok, T, _} = erl_scan:string(String++"."),
+ case catch erl_parse:parse_term(T) of
+ {ok, Term} ->
+ {ok, Term};
+ Error ->
+ {error, Error, String}
+ end.
diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl
index ed8b564921..3dd1026f13 100644
--- a/lib/common_test/src/ct_framework.erl
+++ b/lib/common_test/src/ct_framework.erl
@@ -113,9 +113,9 @@ init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) ->
ok;
true ->
%% delete all default values used in previous suite
- ct_util:delete_default_config(suite),
+ ct_config:delete_default_config(suite),
%% release all name -> key bindings (once per suite)
- ct_util:release_allocated()
+ ct_config:release_allocated()
end,
TestCaseInfo =
case catch apply(Mod,Func,[]) of
@@ -125,7 +125,7 @@ init_tc1(Mod,Func,[Config0],DoInit) when is_list(Config0) ->
%% clear all config data default values set by previous
%% testcase info function (these should only survive the
%% testcase, not the whole suite)
- ct_util:delete_default_config(testcase),
+ ct_config:delete_default_config(testcase),
case add_defaults(Mod,Func,TestCaseInfo,DoInit) of
Error = {suite0_failed,_} ->
ct_logs:init_tc(),
@@ -161,6 +161,7 @@ init_tc2(Mod,Func,SuiteInfo,MergeResult,Config,DoInit) ->
_ ->
MergeResult
end,
+
%% timetrap must be handled before require
MergedInfo1 = timetrap_first(MergedInfo, [], []),
%% tell logger to use specified style sheet
@@ -244,8 +245,8 @@ add_defaults(Mod,Func,FuncInfo,DoInit) ->
_ ->
{suite0_failed,bad_return_value}
end.
-
-add_defaults1(_Mod,init_per_suite,[],SuiteInfo,_) ->
+
+add_defaults1(_Mod,init_per_suite,[],SuiteInfo,_DoInit) ->
SuiteInfo;
add_defaults1(Mod,Func,FuncInfo,SuiteInfo,DoInit) ->
@@ -253,15 +254,27 @@ add_defaults1(Mod,Func,FuncInfo,SuiteInfo,DoInit) ->
%% can result in weird behaviour (suite values get overwritten)
SuiteReqs =
[SDDef || SDDef <- SuiteInfo,
- require == element(1,SDDef)],
- case [element(2,Clash) || Clash <- SuiteReqs,
- true == lists:keymember(element(2,Clash),2,FuncInfo)] of
+ ((require == element(1,SDDef)) or
+ (default_config == element(1,SDDef)))],
+ FuncReqs =
+ [FIDef || FIDef <- FuncInfo,
+ require == element(1,FIDef)],
+ case [element(2,Clash) || Clash <- SuiteReqs,
+ require == element(1, Clash),
+ true == lists:keymember(element(2,Clash),2,
+ FuncReqs)] of
[] ->
add_defaults2(Mod,Func,FuncInfo,SuiteInfo,SuiteReqs,DoInit);
Clashes ->
{error,{config_name_already_in_use,Clashes}}
end.
+add_defaults2(Mod,init_per_suite,IPSInfo,SuiteInfo,SuiteReqs,false) ->
+ %% not common practise to use a test case info function for
+ %% init_per_suite (usually handled by suite/0), but let's support
+ %% it just in case...
+ add_defaults2(Mod,init_per_suite,IPSInfo,SuiteInfo,SuiteReqs,true);
+
add_defaults2(_Mod,_Func,FuncInfo,SuiteInfo,_,false) ->
%% include require elements from test case info, but not from suite/0
%% (since we've already required those vars)
@@ -381,10 +394,10 @@ try_set_default(Name,Key,Info,Where) ->
{_,[]} ->
no_default;
{'_UNDEF',_} ->
- [ct_util:set_default_config([CfgVal],Where) || CfgVal <- CfgElems],
+ [ct_config:set_default_config([CfgVal],Where) || CfgVal <- CfgElems],
ok;
_ ->
- [ct_util:set_default_config(Name,[CfgVal],Where) || CfgVal <- CfgElems],
+ [ct_config:set_default_config(Name,[CfgVal],Where) || CfgVal <- CfgElems],
ok
end.
@@ -631,7 +644,7 @@ group_or_func(Func, _Config) ->
%%% and every test case. If the former, all test cases in the suite
%%% should be returned.
-get_suite(Mod, all) ->
+get_suite(Mod, all) ->
case catch apply(Mod, groups, []) of
{'EXIT',_} ->
get_all(Mod, []);
@@ -667,12 +680,18 @@ get_suite(Mod, Name) ->
%% (and only) test case so we can report Error properly
[{?MODULE,error_in_suite,[[Error]]}];
ConfTests ->
+
+ %%! --- Thu Jun 3 19:13:22 2010 --- peppe was here!
+ %%! HEERE!
+ %%! Must be able to search recursively for group Name,
+ %%! this only handles top level groups!
+
FindConf = fun({conf,Props,_,_,_}) ->
case proplists:get_value(name, Props) of
Name -> true;
_ -> false
end
- end,
+ end,
case lists:filter(FindConf, ConfTests) of
[] -> % must be a test case
get_seq(Mod, Name);
diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl
index a31e57c7ea..5aab4dd2dd 100644
--- a/lib/common_test/src/ct_gen_conn.erl
+++ b/lib/common_test/src/ct_gen_conn.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -76,7 +76,7 @@ start(Name,Address,InitData,CallbackMod) ->
MRef = erlang:monitor(process,Pid),
receive
{connected,Pid} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
ct_util:register_connection(Name,Address,CallbackMod,Pid),
{ok,Pid};
{Error,Pid} ->
@@ -182,7 +182,7 @@ call(Pid,Msg) ->
Pid ! {Msg,{self(),Ref}},
receive
{Ref, Result} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
case Result of
{retry,_Data} ->
call(Pid,Result);
diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl
index bd1a89ae1f..5683d06aa7 100644
--- a/lib/common_test/src/ct_logs.erl
+++ b/lib/common_test/src/ct_logs.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -80,7 +80,7 @@ init(Mode) ->
MRef = erlang:monitor(process,Pid),
receive
{started,Pid,Result} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
Result;
{'DOWN',MRef,process,_,Reason} ->
exit({could_not_start_process,?MODULE,Reason})
@@ -163,7 +163,7 @@ call(Msg) ->
?MODULE ! {Msg,{self(),Ref}},
receive
{Ref, Result} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
Result;
{'DOWN',MRef,process,_,Reason} ->
{error,{process_down,?MODULE,Reason}}
@@ -505,7 +505,7 @@ logger_loop(State) ->
logger_loop(State);
{set_stylesheet,TC,SSFile} ->
Fd = State#logger_state.ct_log_fd,
- io:format(Fd, "~p uses external style sheet: ~s~n", [TC,SSFile]),
+ io:format(Fd, "~p loading external style sheet: ~s~n", [TC,SSFile]),
logger_loop(State#logger_state{stylesheet=SSFile});
{clear_stylesheet,_} when State#logger_state.stylesheet == undefined ->
logger_loop(State);
diff --git a/lib/common_test/src/ct_master.erl b/lib/common_test/src/ct_master.erl
index 7eb2c3cfef..42e4cf08f4 100644
--- a/lib/common_test/src/ct_master.erl
+++ b/lib/common_test/src/ct_master.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-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%
%%
@@ -28,7 +28,7 @@
-export([abort/0,abort/1,progress/0]).
--export([init_master/6, init_node_ctrl/3]).
+-export([init_master/7, init_node_ctrl/3]).
-export([status/2]).
@@ -49,7 +49,8 @@
%%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} |
%%% {testcase,Cases} | {spec,TestSpecs} | {allow_user_terms,Bool} |
%%% {logdir,LogDir} | {event_handler,EventHandlers} |
-%%% {silent_connections,Conns} | {cover,CoverSpecFile}
+%%% {silent_connections,Conns} | {cover,CoverSpecFile} |
+%%% {userconfig, UserCfgFiles}
%%% CfgFiles = string() | [string()]
%%% TestDirs = string() | [string()]
%%% Suites = atom() | [atom()]
@@ -98,11 +99,14 @@ run([TS|TestSpecs],AllowUserTerms,InclNodes,ExclNodes) when is_list(TS),
{error,Reason} ->
{error,Reason};
TSRec=#testspec{logdir=AllLogDirs,
- config=AllCfgFiles,
+ config=StdCfgFiles,
+ userconfig=UserCfgFiles,
+ init=AllInitOpts,
event_handler=AllEvHs} ->
+ AllCfgFiles = {StdCfgFiles, UserCfgFiles},
RunSkipPerNode = ct_testspec:prepare_tests(TSRec),
RunSkipPerNode2 = exclude_nodes(ExclNodes,RunSkipPerNode),
- run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],TS1)
+ run_all(RunSkipPerNode2,AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllInitOpts,TS1)
end,
[{TS,Result} | run(TestSpecs,AllowUserTerms,InclNodes,ExclNodes)];
run([],_,_,_) ->
@@ -157,10 +161,13 @@ run_on_node([TS|TestSpecs],AllowUserTerms,Node) when is_list(TS),is_atom(Node) -
{error,Reason} ->
{error,Reason};
TSRec=#testspec{logdir=AllLogDirs,
- config=AllCfgFiles,
+ config=StdCfgFiles,
+ init=AllInitOpts,
+ userconfig=UserCfgFiles,
event_handler=AllEvHs} ->
+ AllCfgFiles = {StdCfgFiles, UserCfgFiles},
{Run,Skip} = ct_testspec:prepare_tests(TSRec,Node),
- run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],TS1)
+ run_all([{Node,Run,Skip}],AllLogDirs,AllCfgFiles,AllEvHs,[],[],AllInitOpts,TS1)
end,
[{TS,Result} | run_on_node(TestSpecs,AllowUserTerms,Node)];
run_on_node([],_,_) ->
@@ -180,7 +187,9 @@ run_on_node(TestSpecs,Node) ->
-run_all([{Node,Run,Skip}|Rest],AllLogDirs,AllCfgFiles,AllEvHs,NodeOpts,LogDirs,Specs) ->
+run_all([{Node,Run,Skip}|Rest],AllLogDirs,
+ {AllStdCfgFiles, AllUserCfgFiles}=AllCfgFiles,
+ AllEvHs,NodeOpts,LogDirs,InitOptions,Specs) ->
LogDir =
lists:foldl(fun({N,Dir},_Found) when N == Node ->
Dir;
@@ -191,29 +200,36 @@ run_all([{Node,Run,Skip}|Rest],AllLogDirs,AllCfgFiles,AllEvHs,NodeOpts,LogDirs,S
(_Dir,Found) ->
Found
end,".",AllLogDirs),
- CfgFiles =
+
+ StdCfgFiles =
lists:foldr(fun({N,F},Fs) when N == Node -> [F|Fs];
({_N,_F},Fs) -> Fs;
(F,Fs) -> [F|Fs]
- end,[],AllCfgFiles),
+ end,[],AllStdCfgFiles),
+ UserCfgFiles =
+ lists:foldr(fun({N,F},Fs) when N == Node -> [{userconfig, F}|Fs];
+ ({_N,_F},Fs) -> Fs;
+ (F,Fs) -> [{userconfig, F}|Fs]
+ end,[],AllUserCfgFiles),
EvHs =
lists:foldr(fun({N,H,A},Hs) when N == Node -> [{H,A}|Hs];
({_N,_H,_A},Hs) -> Hs;
({H,A},Hs) -> [{H,A}|Hs]
end,[],AllEvHs),
+
NO = {Node,[{prepared_tests,{Run,Skip},Specs},
{logdir,LogDir},
- {config,CfgFiles},
- {event_handler,EvHs}]},
- run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],Specs);
-run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,Specs) ->
+ {config,StdCfgFiles},
+ {event_handler,EvHs}] ++ UserCfgFiles},
+ run_all(Rest,AllLogDirs,AllCfgFiles,AllEvHs,[NO|NodeOpts],[LogDir|LogDirs],InitOptions,Specs);
+run_all([],AllLogDirs,_,AllEvHs,NodeOpts,LogDirs,InitOptions,Specs) ->
Handlers = [{H,A} || {Master,H,A} <- AllEvHs, Master == master],
MasterLogDir = case lists:keysearch(master,1,AllLogDirs) of
{value,{_,Dir}} -> Dir;
false -> "."
end,
log(tty,"Master Logdir","~s",[MasterLogDir]),
- start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,Specs),
+ start_master(lists:reverse(NodeOpts),Handlers,MasterLogDir,LogDirs,InitOptions,Specs),
ok.
@@ -251,17 +267,17 @@ progress() ->
%%% MASTER, runs on central controlling node.
%%%-----------------------------------------------------------------
start_master(NodeOptsList) ->
- start_master(NodeOptsList,[],".",[],[]).
+ start_master(NodeOptsList,[],".",[],[],[]).
-start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,Specs) ->
+start_master(NodeOptsList,EvHandlers,MasterLogDir,LogDirs,InitOptions,Specs) ->
Master = spawn_link(?MODULE,init_master,[self(),NodeOptsList,EvHandlers,
- MasterLogDir,LogDirs,Specs]),
+ MasterLogDir,LogDirs,InitOptions,Specs]),
receive
{Master,Result} -> Result
end.
%%% @hidden
-init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,Specs) ->
+init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,InitOptions,Specs) ->
case whereis(ct_master) of
undefined ->
register(ct_master,self()),
@@ -314,10 +330,10 @@ init_master(Parent,NodeOptsList,EvHandlers,MasterLogDir,LogDirs,Specs) ->
Pid when is_pid(Pid) ->
ok
end,
- init_master1(Parent,NodeOptsList,LogDirs).
+ init_master1(Parent,NodeOptsList,InitOptions,LogDirs).
-init_master1(Parent,NodeOptsList,LogDirs) ->
- {Inaccessible,NodeOptsList1} = ping_nodes(NodeOptsList,[],[]),
+init_master1(Parent,NodeOptsList,InitOptions,LogDirs) ->
+ {Inaccessible,NodeOptsList1,InitOptions1} = init_nodes(NodeOptsList,InitOptions),
case Inaccessible of
[] ->
init_master2(Parent,NodeOptsList,LogDirs);
@@ -331,7 +347,7 @@ init_master1(Parent,NodeOptsList,LogDirs) ->
"Proceeding without: ~p",[Inaccessible]),
init_master2(Parent,NodeOptsList1,LogDirs);
"r\n" ->
- init_master1(Parent,NodeOptsList,LogDirs);
+ init_master1(Parent,NodeOptsList,InitOptions1,LogDirs);
_ ->
log(html,"Aborting Tests","",[]),
ct_master_event:stop(),
@@ -542,6 +558,9 @@ get_pid(Node,NodeCtrlPids) ->
undefined
end.
+ping_nodes(NodeOptions)->
+ ping_nodes(NodeOptions, [], []).
+
ping_nodes([NO={Node,_Opts}|NOs],Inaccessible,NodeOpts) ->
case net_adm:ping(Node) of
pong ->
@@ -678,13 +697,80 @@ call(Pid,Msg) ->
{'DOWN', Ref, _, _, _} ->
{error,master_died}
end,
- erlang:demonitor(Ref),
+ erlang:demonitor(Ref, [flush]),
Return.
reply(Result,To) ->
To ! {self(),Result},
ok.
+init_nodes(NodeOptions, InitOptions)->
+ ping_nodes(NodeOptions),
+ start_nodes(InitOptions),
+ eval_on_nodes(InitOptions),
+ {Inaccessible, NodeOptions1}=ping_nodes(NodeOptions),
+ InitOptions1 = filter_accessible(InitOptions, Inaccessible),
+ {Inaccessible, NodeOptions1, InitOptions1}.
+
+% only nodes which are inaccessible now, should be initiated later
+filter_accessible(InitOptions, Inaccessible)->
+ [{Node,Option}||{Node,Option}<-InitOptions, lists:member(Node, Inaccessible)].
+
+start_nodes(InitOptions)->
+ lists:foreach(fun({NodeName, Options})->
+ [NodeS,HostS]=string:tokens(atom_to_list(NodeName), "@"),
+ Node=list_to_atom(NodeS),
+ Host=list_to_atom(HostS),
+ HasNodeStart = lists:keymember(node_start, 1, Options),
+ IsAlive = lists:member(NodeName, nodes()),
+ case {HasNodeStart, IsAlive} of
+ {false, false}->
+ io:format("WARNING: Node ~p is not alive but has no node_start option~n", [NodeName]);
+ {false, true}->
+ io:format("Node ~p is alive~n", [NodeName]);
+ {true, false}->
+ {node_start, NodeStart} = lists:keyfind(node_start, 1, Options),
+ {value, {callback_module, Callback}, NodeStart2}=
+ lists:keytake(callback_module, 1, NodeStart),
+ case Callback:start(Host, Node, NodeStart2) of
+ {ok, NodeName} ->
+ io:format("Node ~p started successfully with callback ~p~n", [NodeName,Callback]);
+ {error, Reason, _NodeName} ->
+ io:format("Failed to start node ~p with callback ~p! Reason: ~p~n", [NodeName, Callback, Reason])
+ end;
+ {true, true}->
+ io:format("WARNING: Node ~p is alive but has node_start option~n", [NodeName])
+ end
+ end,
+ InitOptions).
+
+eval_on_nodes(InitOptions)->
+ lists:foreach(fun({NodeName, Options})->
+ HasEval = lists:keymember(eval, 1, Options),
+ IsAlive = lists:member(NodeName, nodes()),
+ case {HasEval, IsAlive} of
+ {false,_}->
+ ok;
+ {true,false}->
+ io:format("WARNING: Node ~p is not alive but has eval option ~n", [NodeName]);
+ {true,true}->
+ {eval, MFAs} = lists:keyfind(eval, 1, Options),
+ evaluate(NodeName, MFAs)
+ end
+ end,
+ InitOptions).
+
+evaluate(Node, [{M,F,A}|MFAs])->
+ case rpc:call(Node, M, F, A) of
+ {badrpc,Reason}->
+ io:format("WARNING: Failed to call ~p:~p/~p on node ~p due to ~p~n", [M,F,length(A),Node,Reason]);
+ Result->
+ io:format("Called ~p:~p/~p on node ~p, result: ~p~n", [M,F,length(A),Node,Result])
+ end,
+ evaluate(Node, MFAs);
+evaluate(_Node, [])->
+ ok.
+
%cast(Msg) ->
% cast(whereis(ct_master),Msg).
diff --git a/lib/common_test/src/ct_master_logs.erl b/lib/common_test/src/ct_master_logs.erl
index 63f60b1182..244faace06 100644
--- a/lib/common_test/src/ct_master_logs.erl
+++ b/lib/common_test/src/ct_master_logs.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-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%
%%
@@ -44,7 +44,7 @@ start(LogDir,Nodes) ->
MRef = erlang:monitor(process,Pid),
receive
{started,Pid,Result} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
{Pid,Result};
{'DOWN',MRef,process,_,Reason} ->
exit({could_not_start_process,?MODULE,Reason})
@@ -435,7 +435,7 @@ call(Msg) ->
?MODULE ! {Msg,{self(),Ref}},
receive
{Ref, Result} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
Result;
{'DOWN',MRef,process,_,Reason} ->
{error,{process_down,?MODULE,Reason}}
diff --git a/lib/common_test/src/ct_repeat.erl b/lib/common_test/src/ct_repeat.erl
index 7ac6e045d7..be3c485b75 100644
--- a/lib/common_test/src/ct_repeat.erl
+++ b/lib/common_test/src/ct_repeat.erl
@@ -1,26 +1,26 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
%%% @doc Common Test Framework module that handles repeated test runs
%%%
%%% <p>This module exports functions for repeating tests. The following
-%%% script flags (or equivalent ct:run_test/1 options) are supported:
+%%% start flags (or equivalent ct:run_test/1 options) are supported:
%%% -until <StopTime>, StopTime = YYMoMoDDHHMMSS | HHMMSS
%%% -duration <DurTime>, DurTime = HHMMSS
%%% -force_stop
diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl
index 6b1063f74c..6a9c42d1b9 100644
--- a/lib/common_test/src/ct_run.erl
+++ b/lib/common_test/src/ct_run.erl
@@ -24,7 +24,6 @@
-module(ct_run).
-
%% Script interface
-export([script_start/0,script_usage/0]).
@@ -46,11 +45,27 @@
-define(abs(Name), filename:absname(Name)).
-define(testdir(Name, Suite), ct_util:get_testdir(Name, Suite)).
+-record(opts, {vts,
+ shell,
+ cover,
+ coverspec,
+ step,
+ logdir,
+ config = [],
+ event_handlers = [],
+ include = [],
+ silent_connections,
+ stylesheet,
+ multiply_timetraps = 1,
+ scale_timetraps = false,
+ testspecs = [],
+ tests}).
+
%%%-----------------------------------------------------------------
%%% @spec script_start() -> void()
%%%
-%%% @doc Start tests via the run_test script.
-%%%
+%%% @doc Start tests via the run_test program or script.
+%%%
%%% <p>Example:<br/><code>./run_test -config config.ctc -dir
%%% $TEST_DIR</code></p>
%%%
@@ -59,13 +74,53 @@
%%%
script_start() ->
process_flag(trap_exit, true),
- Args = merge_arguments(init:get_arguments()),
+ Init = init:get_arguments(),
+ CtArgs = lists:takewhile(fun({ct_erl_args,_}) -> false;
+ (_) -> true end, Init),
+
+ %% convert relative dirs added with pa or pz (pre erl_args on
+ %% the run_test command line) to absolute so that app modules
+ %% can be found even after CT changes CWD to logdir
+ rel_to_abs(CtArgs),
+
+ Args =
+ case application:get_env(common_test, run_test_start_opts) of
+ {ok,EnvStartOpts} ->
+ FlagFilter = fun(Flags) ->
+ lists:filter(fun({root,_}) -> false;
+ ({progname,_}) -> false;
+ ({home,_}) -> false;
+ ({noshell,_}) -> false;
+ ({noinput,_}) -> false;
+ (_) -> true
+ end, Flags)
+ end,
+ %% used for purpose of testing the run_test interface
+ io:format(user, "~n-------------------- START ARGS --------------------~n", []),
+ io:format(user, "--- Init args:~n~p~n", [FlagFilter(Init)]),
+ io:format(user, "--- CT args:~n~p~n", [FlagFilter(CtArgs)]),
+ EnvArgs = opts2args(EnvStartOpts),
+ io:format(user, "--- Env opts -> args:~n~p~n =>~n~p~n",
+ [EnvStartOpts,EnvArgs]),
+ Merged = merge_arguments(CtArgs ++ EnvArgs),
+ io:format(user, "--- Merged args:~n~p~n", [FlagFilter(Merged)]),
+ io:format(user, "----------------------------------------------------~n~n", []),
+ Merged;
+ _ ->
+ merge_arguments(CtArgs)
+ end,
+ case proplists:get_value(help, Args) of
+ undefined -> script_start(Args);
+ _ -> script_usage()
+ end.
+
+script_start(Args) ->
Tracing = start_trace(Args),
- Res =
+ Res =
case ct_repeat:loop_test(script, Args) of
- false ->
+ false ->
{ok,Cwd} = file:get_cwd(),
- CTVsn =
+ CTVsn =
case filename:basename(code:lib_dir(common_test)) of
CTBase when is_list(CTBase) ->
case string:tokens(CTBase, "-") of
@@ -76,7 +131,7 @@ script_start() ->
io:format("~nCommon Test~s starting (cwd is ~s)~n~n", [CTVsn,Cwd]),
Self = self(),
Pid = spawn_link(fun() -> script_start1(Self, Args) end),
- receive
+ receive
{'EXIT',Pid,Reason} ->
case Reason of
{user_error,What} ->
@@ -101,293 +156,320 @@ script_start() ->
Res.
script_start1(Parent, Args) ->
- case lists:keymember(preload, 1, Args) of
- true -> preload();
- false -> ok
- end,
-
- VtsOrShell =
- case lists:keymember(vts, 1, Args) of
- true ->
- vts;
- false ->
- case lists:keymember(shell, 1, Args) of
- true -> shell;
- false -> false
- end
- end,
- LogDir =
- case lists:keysearch(logdir, 1, Args) of
- {value,{logdir,[LogD]}} -> LogD;
- false -> "."
- end,
- EvHandlers =
- case lists:keysearch(event_handler, 1, Args) of
- {value,{event_handler,Handlers}} ->
- lists:map(fun(H) -> {list_to_atom(H),[]} end, Handlers);
- false ->
- []
- end,
- Cover =
- case lists:keysearch(cover, 1, Args) of
- {value,{cover,CoverFile}} ->
- {cover,?abs(CoverFile)};
- false ->
- false
- end,
-
- case lists:keysearch(ct_decrypt_key, 1, Args) of
- {value,{_,[DecryptKey]}} ->
+ %% read general start flags
+ Vts = get_start_opt(vts, true, Args),
+ Shell = get_start_opt(shell, true, Args),
+ Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args),
+ LogDir = get_start_opt(logdir, fun([LogD]) -> LogD end, ".", Args),
+ MultTT = get_start_opt(multiply_timetraps,
+ fun([MT]) -> list_to_integer(MT) end, 1, Args),
+ ScaleTT = get_start_opt(scale_timetraps,
+ fun([CT]) -> list_to_atom(CT);
+ ([]) -> true
+ end, false, Args),
+ EvHandlers = event_handler_args2opts(Args),
+
+ %% check flags and set corresponding application env variables
+
+ %% ct_decrypt_key | ct_decrypt_file
+ case proplists:get_value(ct_decrypt_key, Args) of
+ [DecryptKey] ->
application:set_env(common_test, decrypt, {key,DecryptKey});
- false ->
- case lists:keysearch(ct_decrypt_file, 1, Args) of
- {value,{_,[DecryptFile]}} ->
- application:set_env(common_test, decrypt,
- {file,filename:absname(DecryptFile)});
- false ->
+ undefined ->
+ case proplists:get_value(ct_decrypt_file, Args) of
+ [DecryptFile] ->
+ application:set_env(common_test, decrypt,
+ {file,?abs(DecryptFile)});
+ undefined ->
application:unset_env(common_test, decrypt)
end
end,
-
- case lists:keysearch(no_auto_compile, 1, Args) of
- {value,_} ->
- application:set_env(common_test, auto_compile, false);
- false ->
- application:set_env(common_test, auto_compile, true),
-
- InclDirs =
- case lists:keysearch(include,1,Args) of
- {value,{include,Incl}} when is_list(hd(Incl)) ->
- Incl;
- {value,{include,Incl}} when is_list(Incl) ->
- [Incl];
+ %% no_auto_compile + include
+ IncludeDirs =
+ case proplists:get_value(no_auto_compile, Args) of
+ undefined ->
+ application:set_env(common_test, auto_compile, true),
+ InclDirs =
+ case proplists:get_value(include, Args) of
+ Incl when is_list(hd(Incl)) ->
+ Incl;
+ Incl when is_list(Incl) ->
+ [Incl];
+ undefined ->
+ []
+ end,
+ case os:getenv("CT_INCLUDE_PATH") of
false ->
- []
- end,
- case os:getenv("CT_INCLUDE_PATH") of
- false ->
- application:set_env(common_test, include, InclDirs);
- CtInclPath ->
- InclDirs1 = string:tokens(CtInclPath,[$:,$ ,$,]),
- application:set_env(common_test, include, InclDirs1++InclDirs)
- end
- end,
-
- case lists:keysearch(basic_html, 1, Args) of
- {value,_} ->
- application:set_env(common_test, basic_html, true);
- false ->
- application:set_env(common_test, basic_html, false)
- end,
-
- Result =
- case lists:keysearch(refresh_logs, 1, Args) of
- {value,{refresh_logs,Refresh}} ->
- LogDir1 = case Refresh of
- [] -> LogDir;
- [RefreshDir] -> ?abs(RefreshDir)
- end,
- {ok,Cwd} = file:get_cwd(),
- file:set_cwd(LogDir1),
- timer:sleep(500), % give the shell time to print version etc
- io:nl(),
- case catch ct_logs:make_all_suites_index(refresh) of
- {'EXIT',ASReason} ->
- file:set_cwd(Cwd),
- {error,{all_suites_index,ASReason}};
- _ ->
- case catch ct_logs:make_all_runs_index(refresh) of
- {'EXIT',ARReason} ->
- file:set_cwd(Cwd),
- {error,{all_runs_index,ARReason}};
- _ ->
- file:set_cwd(Cwd),
- io:format("Logs in ~s refreshed!~n~n", [LogDir1]),
- timer:sleep(500), % time to flush io before quitting
- ok
- end
+ application:set_env(common_test, include, InclDirs),
+ InclDirs;
+ CtInclPath ->
+ AllInclDirs =
+ string:tokens(CtInclPath,[$:,$ ,$,]) ++ InclDirs,
+ application:set_env(common_test, include, AllInclDirs),
+ AllInclDirs
end;
- false ->
- case lists:keysearch(ct_config, 1, Args) of
- {value,{ct_config,ConfigFiles}} ->
- case lists:keysearch(spec, 1, Args) of
- false ->
- case get_configfiles(ConfigFiles, [], LogDir,
- EvHandlers) of
- ok ->
- script_start2(VtsOrShell, ConfigFiles,
- EvHandlers, Args, LogDir,
- Cover);
- Error ->
- Error
- end;
- _ ->
- script_start2(VtsOrShell, ConfigFiles,
- EvHandlers, Args, LogDir, Cover)
- end;
- false ->
- case install([{config,[]},
- {event_handler,EvHandlers}],
- LogDir) of
- ok ->
- script_start2(VtsOrShell, [], EvHandlers,
- Args, LogDir, Cover);
- Error ->
- Error
- end
- end
+ _ ->
+ application:set_env(common_test, auto_compile, false),
+ []
end,
+ %% silent connections
+ SilentConns =
+ get_start_opt(silent_connections,
+ fun(["all"]) -> [];
+ (Conns) -> [list_to_atom(Conn) || Conn <- Conns]
+ end, Args),
+ %% stylesheet
+ Stylesheet = get_start_opt(stylesheet,
+ fun([SS]) -> ?abs(SS) end, Args),
+ %% basic_html - used by ct_logs
+ case proplists:get_value(basic_html, Args) of
+ undefined ->
+ application:set_env(common_test, basic_html, false);
+ _ ->
+ application:set_env(common_test, basic_html, true)
+ end,
+
+ StartOpts = #opts{vts = Vts, shell = Shell, cover = Cover,
+ logdir = LogDir, event_handlers = EvHandlers,
+ include = IncludeDirs,
+ silent_connections = SilentConns,
+ stylesheet = Stylesheet,
+ multiply_timetraps = MultTT,
+ scale_timetraps = ScaleTT},
+
+ %% check if log files should be refreshed or go on to run tests...
+ Result = run_or_refresh(StartOpts, Args),
+ %% send final results to starting process waiting in script_start/0
Parent ! {self(), Result}.
-get_configfiles([File|Files], Acc, LogDir, EvHandlers) ->
- case filelib:is_file(File) of
- true ->
- get_configfiles(Files, [?abs(File)|Acc],
- LogDir, EvHandlers);
- false ->
- {error,{cant_read_config_file,File}}
- end;
-get_configfiles([], Acc, LogDir, EvHandlers) ->
- install([{config,lists:reverse(Acc)}, {event_handler,EvHandlers}], LogDir).
+run_or_refresh(StartOpts = #opts{logdir = LogDir}, Args) ->
+ case proplists:get_value(refresh_logs, Args) of
+ undefined ->
+ script_start2(StartOpts, Args);
+ Refresh ->
+ LogDir1 = case Refresh of
+ [] -> which(logdir,LogDir);
+ [RefreshDir] -> ?abs(RefreshDir)
+ end,
+ {ok,Cwd} = file:get_cwd(),
+ file:set_cwd(LogDir1),
+ %% give the shell time to print version etc
+ timer:sleep(500),
+ io:nl(),
+ case catch ct_logs:make_all_suites_index(refresh) of
+ {'EXIT',ASReason} ->
+ file:set_cwd(Cwd),
+ {error,{all_suites_index,ASReason}};
+ _ ->
+ case catch ct_logs:make_all_runs_index(refresh) of
+ {'EXIT',ARReason} ->
+ file:set_cwd(Cwd),
+ {error,{all_runs_index,ARReason}};
+ _ ->
+ file:set_cwd(Cwd),
+ io:format("Logs in ~s refreshed!~n~n", [LogDir1]),
+ timer:sleep(500), % time to flush io before quitting
+ ok
+ end
+ end
+ end.
-script_start2(false, ConfigFiles, EvHandlers, Args, LogDir, Cover) ->
- case lists:keysearch(spec, 1, Args) of
- {value,{spec,[]}} ->
+script_start2(StartOpts = #opts{vts = undefined,
+ shell = undefined}, Args) ->
+ TestSpec = proplists:get_value(spec, Args),
+ {Terms,Opts} =
+ case TestSpec of
+ Specs when Specs =/= [], Specs =/= undefined ->
+ %% using testspec as input for test
+ Relaxed = get_start_opt(allow_user_terms, true, false, Args),
+ case catch ct_testspec:collect_tests_from_file(Specs, Relaxed) of
+ {E,Reason} when E == error ; E == 'EXIT' ->
+ {{error,Reason},StartOpts};
+ TS ->
+ SpecStartOpts = get_data_for_node(TS, node()),
+
+ LogDir = choose_val(StartOpts#opts.logdir,
+ SpecStartOpts#opts.logdir),
+
+ Cover = choose_val(StartOpts#opts.cover,
+ SpecStartOpts#opts.cover),
+ MultTT = choose_val(StartOpts#opts.multiply_timetraps,
+ SpecStartOpts#opts.multiply_timetraps),
+ ScaleTT = choose_val(StartOpts#opts.scale_timetraps,
+ SpecStartOpts#opts.scale_timetraps),
+ AllEvHs = merge_vals([StartOpts#opts.event_handlers,
+ SpecStartOpts#opts.event_handlers]),
+ AllInclude = merge_vals([StartOpts#opts.include,
+ SpecStartOpts#opts.include]),
+ application:set_env(common_test, include, AllInclude),
+
+ {TS,StartOpts#opts{testspecs = Specs,
+ cover = Cover,
+ logdir = LogDir,
+ config = SpecStartOpts#opts.config,
+ event_handlers = AllEvHs,
+ include = AllInclude,
+ multiply_timetraps = MultTT,
+ scale_timetraps = ScaleTT}}
+ end;
+ _ ->
+ {undefined,StartOpts}
+ end,
+ %% read config/userconfig from start flags
+ InitConfig = ct_config:prepare_config_list(Args),
+ case {TestSpec,Terms} of
+ {_,{error,_}=Error} ->
+ Error;
+ {[],_} ->
{error,no_testspec_specified};
- {value,{spec,Specs}} ->
- Relaxed = lists:keymember(allow_user_terms, 1, Args),
- %% using testspec as input for test
- case catch ct_testspec:collect_tests_from_file(Specs, Relaxed) of
- {error,Reason} ->
- {error,Reason};
- TS ->
- {LogDir1,TSCoverFile,ConfigFiles1,EvHandlers1,Include1} =
- get_data_for_node(TS,node()),
- UserInclude =
- case application:get_env(common_test, include) of
- {ok,Include} -> Include++Include1;
- _ -> Include1
- end,
- application:set_env(common_test, include, UserInclude),
- LogDir2 = which_logdir(LogDir,LogDir1),
- CoverOpt = case {Cover,TSCoverFile} of
- {false,undef} -> [];
- {_,undef} -> [Cover];
- {false,_} -> [{cover,TSCoverFile}]
- end,
- case get_configfiles(ConfigFiles++ConfigFiles1,
- [], LogDir2,
- EvHandlers++EvHandlers1) of
- ok ->
- {Run,Skip} = ct_testspec:prepare_tests(TS, node()),
- do_run(Run, Skip, CoverOpt, Args, LogDir2);
- Error ->
- Error
- end
+ {undefined,_} -> % no testspec used
+ case check_and_install_configfiles(InitConfig,
+ which(logdir,Opts#opts.logdir),
+ Opts#opts.event_handlers) of
+ ok -> % go on read tests from start flags
+ script_start3(Opts#opts{config=InitConfig}, Args);
+ Error ->
+ Error
end;
- false ->
- script_start3(false, ConfigFiles, EvHandlers, Args, LogDir, Cover)
+ {_,_} -> % testspec used
+ %% merge config from start flags with config from testspec
+ AllConfig = merge_vals([InitConfig, Opts#opts.config]),
+ case check_and_install_configfiles(AllConfig,
+ which(logdir,Opts#opts.logdir),
+ Opts#opts.event_handlers) of
+ ok -> % read tests from spec
+ {Run,Skip} = ct_testspec:prepare_tests(Terms, node()),
+ do_run(Run, Skip, Opts#opts{config=AllConfig}, Args);
+ Error ->
+ Error
+ end
end;
-script_start2(VtsOrShell, ConfigFiles, EvHandlers, Args, LogDir, Cover) ->
- script_start3(VtsOrShell, ConfigFiles, EvHandlers, Args, LogDir, Cover).
-script_start3(VtsOrShell, ConfigFiles, EvHandlers, Args, LogDir, Cover) ->
- case lists:keysearch(dir, 1, Args) of
- {value,{dir,[]}} ->
- {error,no_dir_specified};
- {value,{dir,Dirs}} ->
- script_start4(VtsOrShell, ConfigFiles, EvHandlers, tests(Dirs),
- Cover, Args, LogDir);
+script_start2(StartOpts, Args) ->
+ %% read config/userconfig from start flags
+ InitConfig = ct_config:prepare_config_list(Args),
+ case check_and_install_configfiles(InitConfig,
+ which(logdir,StartOpts#opts.logdir),
+ StartOpts#opts.event_handlers) of
+ ok -> % go on read tests from start flags
+ script_start3(StartOpts#opts{config=InitConfig}, Args);
+ Error ->
+ Error
+ end.
+
+check_and_install_configfiles(Configs, LogDir, EvHandlers) ->
+ case ct_config:check_config_files(Configs) of
false ->
- case lists:keysearch(suite, 1, Args) of
- {value,{suite,[]}} ->
+ install([{config,Configs},
+ {event_handler,EvHandlers}], LogDir);
+ {value,{error,{nofile,File}}} ->
+ {error,{cant_read_config_file,File}};
+ {value,{error,{wrong_config,Message}}}->
+ {error,{wrong_config,Message}};
+ {value,{error,{callback,Info}}} ->
+ {error,{cant_load_callback_module,Info}}
+ end.
+
+script_start3(StartOpts, Args) ->
+ case proplists:get_value(dir, Args) of
+ [] ->
+ {error,no_dir_specified};
+ Dirs when is_list(Dirs) ->
+ script_start4(StartOpts#opts{tests = tests(Dirs)}, Args);
+ undefined ->
+ case proplists:get_value(suite, Args) of
+ [] ->
{error,no_suite_specified};
- {value,{suite,Suites}} ->
- StepOrCover =
- case lists:keysearch(step, 1, Args) of
- {value,Step} -> Step;
- false -> Cover
- end,
- S2M = fun(S) ->
- {filename:dirname(S),
- list_to_atom(
- filename:rootname(filename:basename(S)))}
- end,
- DirMods = lists:map(S2M, Suites),
- {Specified,GroupsAndCases} =
- case {lists:keysearch(group, 1, Args),
- lists:keysearch('case', 1, Args)} of
- {{value,{_,Gs}},{value,{_,Cs}}} -> {true,Gs++Cs};
- {{value,{_,Gs}},_} -> {true,Gs};
- {_,{value,{_,Cs}}} -> {true,Cs};
- _ -> {false,[]}
- end,
- if Specified, length(GroupsAndCases) == 0 ->
- {error,no_case_or_group_specified};
- Specified, length(DirMods) > 1 ->
+ Suites when is_list(Suites) ->
+ StartOpts1 =
+ get_start_opt(step,
+ fun(Step) ->
+ StartOpts#opts{step = Step,
+ cover = undefined}
+ end, StartOpts, Args),
+ DirMods = [suite_to_test(S) || S <- Suites],
+ case groups_and_cases(proplists:get_value(group, Args),
+ proplists:get_value(testcase, Args)) of
+ Error = {error,_} ->
+ Error;
+ [] when DirMods =/= [] ->
+ Ts = tests(DirMods),
+ script_start4(StartOpts1#opts{tests = Ts}, Args);
+ GroupsAndCases when length(DirMods) == 1 ->
+ Ts = tests(DirMods, GroupsAndCases),
+ script_start4(StartOpts1#opts{tests = Ts}, Args);
+ [_,_|_] when length(DirMods) > 1 ->
{error,multiple_suites_and_cases};
- length(GroupsAndCases) > 0, length(DirMods) == 1 ->
- GsAndCs = lists:map(fun(C) -> list_to_atom(C) end,
- GroupsAndCases),
- script_start4(VtsOrShell, ConfigFiles, EvHandlers,
- tests(DirMods, GsAndCs),
- StepOrCover, Args, LogDir);
- not Specified, length(DirMods) > 0 ->
- script_start4(VtsOrShell, ConfigFiles, EvHandlers,
- tests(DirMods),
- StepOrCover, Args, LogDir);
- true ->
- {error,incorrect_suite_and_case_options}
+ _ ->
+ {error,incorrect_suite_option}
end;
- false when VtsOrShell=/=false ->
- script_start4(VtsOrShell, ConfigFiles, EvHandlers,
- [], Cover, Args, LogDir);
- false ->
- script_usage(),
- {error,incorrect_usage}
+ undefined ->
+ if StartOpts#opts.vts ; StartOpts#opts.shell ->
+ script_start4(StartOpts#opts{tests = []}, Args);
+ true ->
+ script_usage(),
+ {error,incorrect_usage}
+ end
end
end.
-script_start4(vts, ConfigFiles, EvHandlers, Tests, false, _Args, LogDir) ->
+script_start4(#opts{vts = true, config = Config, event_handlers = EvHandlers,
+ tests = Tests, logdir = LogDir}, _Args) ->
+ ConfigFiles =
+ lists:foldl(fun({ct_config_plain,CfgFiles}, AllFiles) when
+ is_list(hd(CfgFiles)) ->
+ AllFiles ++ CfgFiles;
+ ({ct_config_plain,CfgFile}, AllFiles) when
+ is_integer(hd(CfgFile)) ->
+ AllFiles ++ [CfgFile];
+ (_, AllFiles) ->
+ AllFiles
+ end, [], Config),
vts:init_data(ConfigFiles, EvHandlers, ?abs(LogDir), Tests);
-script_start4(shell, ConfigFiles, EvHandlers, _Tests, false, Args, LogDir) ->
- Opts = [{config,ConfigFiles},{event_handler,EvHandlers}],
- if ConfigFiles == [] ->
+
+script_start4(#opts{shell = true, config = Config, event_handlers = EvHandlers,
+ logdir = LogDir, testspecs = Specs}, _Args) ->
+ InstallOpts = [{config,Config},{event_handler,EvHandlers}],
+ if Config == [] ->
ok;
true ->
- io:format("\nInstalling: ~p\n\n", [ConfigFiles])
+ io:format("\nInstalling: ~p\n\n", [Config])
end,
- case install(Opts) of
+ case install(InstallOpts) of
ok ->
ct_util:start(interactive, LogDir),
- log_ts_names(Args),
+ log_ts_names(Specs),
io:nl(),
ok;
Error ->
Error
end;
-script_start4(vts, _CfgFs, _EvHs, _Tests, _Cover={cover,_}, _Args, _LogDir) ->
- %% Add support later (maybe).
- script_usage(),
- erlang:halt();
-script_start4(shell, _CfgFs, _EvHs, _Tests, _Cover={cover,_}, _Args, _LogDir) ->
- %% Add support later (maybe).
- script_usage();
-script_start4(false, _CfgFs, _EvHs, Tests, Cover={cover,_}, Args, LogDir) ->
- do_run(Tests, [], [Cover], Args, LogDir);
-script_start4(false, _ConfigFiles, _EvHandlers, Tests, false, Args, LogDir) ->
- do_run(Tests, [], [], Args, LogDir);
-script_start4(false, _ConfigFiles, _EvHandlers, Test, Step, Args, LogDir) ->
- do_run(Test, [], [Step], Args, LogDir);
-script_start4(vts, _ConfigFiles, _EvHandlers, _Test, _Step, _Args, _LogDir) ->
- script_usage(),
+
+script_start4(#opts{vts = true, cover = Cover}, _) ->
+ case Cover of
+ undefined ->
+ script_usage();
+ _ ->
+ %% Add support later (maybe).
+ io:format("\nCan't run cover in vts mode.\n\n", [])
+ end,
erlang:halt();
-script_start4(shell, _ConfigFiles, _EvHandlers, _Test, _Step, _Args, _LogDir) ->
- script_usage().
+
+script_start4(#opts{shell = true, cover = Cover}, _) ->
+ case Cover of
+ undefined ->
+ script_usage();
+ _ ->
+ %% Add support later (maybe).
+ io:format("\nCan't run cover in interactive mode.\n\n", [])
+ end;
+
+script_start4(Opts = #opts{tests = Tests}, Args) ->
+ do_run(Tests, [], Opts, Args).
%%%-----------------------------------------------------------------
%%% @spec script_usage() -> ok
-%%% @doc Print script usage information for <code>run_test</code>.
+%%% @doc Print usage information for <code>run_test</code>.
script_usage() ->
io:format("\n\nUsage:\n\n"),
io:format("Run tests in web based GUI:\n\n"
@@ -396,24 +478,29 @@ script_usage() ->
"\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
"\n\t[-dir TestDir1 TestDir2 .. TestDirN] |"
"\n\t[-suite Suite [-case Case]]"
- "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
+ "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
"\n\t[-no_auto_compile]"
+ "\n\t[-multiply_timetraps N]"
+ "\n\t[-scale_timetraps]"
"\n\t[-basic_html]\n\n"),
io:format("Run tests from command line:\n\n"
"\trun_test [-dir TestDir1 TestDir2 .. TestDirN] |"
"\n\t[-suite Suite1 Suite2 .. SuiteN [-case Case1 Case2 .. CaseN]]"
"\n\t[-step [config | keep_inactive]]"
"\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
+ "\n\t[-userconfig CallbackModule ConfigFile1 .. ConfigFileN]"
"\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]"
"\n\t[-logdir LogDir]"
"\n\t[-silent_connections [ConnType1 ConnType2 .. ConnTypeN]]"
"\n\t[-stylesheet CSSFile]"
"\n\t[-cover CoverCfgFile]"
"\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
- "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
+ "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
"\n\t[-no_auto_compile]"
- "\n\t[-basic_html]"
- "\n\t[-repeat N [-force_stop]] |"
+ "\n\t[-multiply_timetraps N]"
+ "\n\t[-scale_timetraps]"
+ "\n\t[-basic_html]"
+ "\n\t[-repeat N [-force_stop]] |"
"\n\t[-duration HHMMSS [-force_stop]] |"
"\n\t[-until [YYMoMoDD]HHMMSS [-force_stop]]\n\n"),
io:format("Run tests using test specification:\n\n"
@@ -426,10 +513,12 @@ script_usage() ->
"\n\t[-stylesheet CSSFile]"
"\n\t[-cover CoverCfgFile]"
"\n\t[-event_handler EvHandler1 EvHandler2 .. EvHandlerN]"
- "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
+ "\n\t[-include InclDir1 InclDir2 .. InclDirN]"
"\n\t[-no_auto_compile]"
- "\n\t[-basic_html]"
- "\n\t[-repeat N [-force_stop]] |"
+ "\n\t[-multiply_timetraps N]"
+ "\n\t[-scale_timetraps]"
+ "\n\t[-basic_html]"
+ "\n\t[-repeat N [-force_stop]] |"
"\n\t[-duration HHMMSS [-force_stop]] |"
"\n\t[-until [YYMoMoDD]HHMMSS [-force_stop]]\n\n"),
io:format("Refresh the HTML index files:\n\n"
@@ -440,7 +529,6 @@ script_usage() ->
"\trun_test -shell"
"\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]"
"\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n").
-
%%%-----------------------------------------------------------------
%%% @hidden
@@ -468,7 +556,7 @@ install(Opts, LogDir) ->
[io:format(Fd, "~p.\n", [Opt]) || Opt <- Opts],
file:close(Fd),
ok;
- {error,Reason} ->
+ {error,Reason} ->
io:format("CT failed to install configuration data. Please "
"verify that the log directory exists and that "
"write permission is set.\n\n", []),
@@ -487,69 +575,58 @@ variables_file_name(Dir) ->
filename:join(Dir, "variables-"++atom_to_list(node())).
%%%-----------------------------------------------------------------
-%%% @hidden
+%%% @spec run_test(Opts) -> Result
+%%% Opts = [tuple()]
+%%% Result = [TestResult] | {error,Reason}
+%%%
+%%% @doc Start tests from the erlang shell or from an erlang program.
%%% @equiv ct:run_test/1
+%%%-----------------------------------------------------------------
-%% Opts = [OptTuples]
-%% OptTuples = {config,CfgFiles} | {dir,TestDirs} | {suite,Suites} |
-%% {testcase,Cases} | {spec,TestSpecs} | {allow_user_terms,Bool} |
-%% {logdir,LogDir} | {cover,CoverSpecFile} | {step,StepOpts} |
-%% {silent_connections,Conns} | {event_handler,EventHandlers} |
-%% {include,InclDirs} | {auto_compile,Bool} |
-%% {repeat,N} | {duration,DurTime} | {until,StopTime} | {force_stop,Bool} |
-%% {decrypt,KeyOrFile}
-
-run_test(Opt) when is_tuple(Opt) ->
- run_test([Opt]);
-
-run_test(Opts) when is_list(Opts) ->
- case lists:keysearch(refresh_logs, 1, Opts) of
- {value,{_,RefreshDir}} ->
- refresh_logs(?abs(RefreshDir)),
- ok;
- false ->
- Tracing = start_trace(Opts),
+run_test(StartOpt) when is_tuple(StartOpt) ->
+ run_test([StartOpt]);
+
+run_test(StartOpts) when is_list(StartOpts) ->
+ case proplists:get_value(refresh_logs, StartOpts) of
+ undefined ->
+ Tracing = start_trace(StartOpts),
{ok,Cwd} = file:get_cwd(),
- io:format("~nCommon Test starting (cwd is ~s)~n~n", [Cwd]),
+ io:format("~nCommon Test starting (cwd is ~s)~n~n", [Cwd]),
Res =
- case ct_repeat:loop_test(func, Opts) of
+ case ct_repeat:loop_test(func, StartOpts) of
false ->
- case catch run_test1(Opts) of
- {'EXIT',Reason} ->
+ case catch run_test1(StartOpts) of
+ {'EXIT',Reason} ->
file:set_cwd(Cwd),
{error,Reason};
- Result ->
+ Result ->
Result
end;
Result ->
Result
end,
stop_trace(Tracing),
- Res
+ Res;
+ RefreshDir ->
+ refresh_logs(?abs(RefreshDir)),
+ ok
end.
-run_test1(Opts) ->
- LogDir =
- case lists:keysearch(logdir, 1, Opts) of
- {value,{_,LD}} when is_list(LD) -> LD;
- false -> "."
- end,
- CfgFiles =
- case lists:keysearch(config, 1, Opts) of
- {value,{_,Files=[File|_]}} when is_list(File) ->
- Files;
- {value,{_,File=[C|_]}} when is_integer(C) ->
- [File];
- {value,{_,[]}} ->
- [];
- false ->
- []
- end,
+run_test1(StartOpts) ->
+ %% logdir
+ LogDir = get_start_opt(logdir, fun(LD) when is_list(LD) -> LD end,
+ ".", StartOpts),
+ %% config & userconfig
+ CfgFiles = ct_config:get_config_file_list(StartOpts),
+
+ %% event handlers
EvHandlers =
- case lists:keysearch(event_handler, 1, Opts) of
- {value,{_,H}} when is_atom(H) ->
+ case proplists:get_value(event_handler, StartOpts) of
+ undefined ->
+ [];
+ H when is_atom(H) ->
[{H,[]}];
- {value,{_,H}} ->
+ H ->
Hs =
if is_tuple(H) -> [H];
is_list(H) -> H;
@@ -564,41 +641,39 @@ run_test1(Opts) ->
{EH,Args};
(_) ->
[]
- end, Hs));
- _ ->
- []
- end,
- SilentConns =
- case lists:keysearch(silent_connections, 1, Opts) of
- {value,{_,all}} ->
- [];
- {value,{_,Conns}} ->
- Conns;
- _ ->
- undefined
- end,
- Cover =
- case lists:keysearch(cover, 1, Opts) of
- {value,{_,CoverFile}} ->
- [{cover,?abs(CoverFile)}];
- _ ->
- []
+ end, Hs))
end,
+
+ %% silent connections
+ SilentConns = get_start_opt(silent_connections,
+ fun(all) -> [];
+ (Conns) -> Conns
+ end, StartOpts),
+ %% stylesheet
+ Stylesheet = get_start_opt(stylesheet,
+ fun(SS) -> ?abs(SS) end,
+ StartOpts),
+ %% code coverage
+ Cover = get_start_opt(cover,
+ fun(CoverFile) -> ?abs(CoverFile) end, StartOpts),
+
+ %% timetrap manipulation
+ MultiplyTT = get_start_opt(multiply_timetraps, value, 1, StartOpts),
+ ScaleTT = get_start_opt(scale_timetraps, value, false, StartOpts),
+
+ %% auto compile & include files
Include =
- case lists:keysearch(auto_compile, 1, Opts) of
- {value,{auto_compile,ACBool}} ->
- application:set_env(common_test, auto_compile, ACBool),
- [];
- _ ->
+ case proplists:get_value(auto_compile, StartOpts) of
+ undefined ->
application:set_env(common_test, auto_compile, true),
InclDirs =
- case lists:keysearch(include, 1, Opts) of
- {value,{include,Incl}} when is_list(hd(Incl)) ->
- Incl;
- {value,{include,Incl}} when is_list(Incl) ->
- [Incl];
- false ->
- []
+ case proplists:get_value(include, StartOpts) of
+ undefined ->
+ [];
+ Incl when is_list(hd(Incl)) ->
+ Incl;
+ Incl when is_list(Incl) ->
+ [Incl]
end,
case os:getenv("CT_INCLUDE_PATH") of
false ->
@@ -609,117 +684,165 @@ run_test1(Opts) ->
AllInclDirs = InclDirs1++InclDirs,
application:set_env(common_test, include, AllInclDirs),
AllInclDirs
- end
+ end;
+ ACBool ->
+ application:set_env(common_test, auto_compile, ACBool),
+ []
end,
- case lists:keysearch(decrypt, 1, Opts) of
- {value,{_,Key={key,_}}} ->
+ %% decrypt config file
+ case proplists:get_value(decrypt, StartOpts) of
+ undefined ->
+ application:unset_env(common_test, decrypt);
+ Key={key,_} ->
application:set_env(common_test, decrypt, Key);
- {value,{_,{file,KeyFile}}} ->
- application:set_env(common_test, decrypt, {file,filename:absname(KeyFile)});
- false ->
- application:unset_env(common_test, decrypt)
+ {file,KeyFile} ->
+ application:set_env(common_test, decrypt, {file,?abs(KeyFile)})
end,
- case lists:keysearch(basic_html, 1, Opts) of
- {value,{basic_html,BasicHtmlBool}} ->
- application:set_env(common_test, basic_html, BasicHtmlBool);
- _ ->
- application:set_env(common_test, basic_html, false)
+ %% basic html - used by ct_logs
+ case proplists:get_value(basic_html, StartOpts) of
+ undefined ->
+ application:set_env(common_test, basic_html, false);
+ BasicHtmlBool ->
+ application:set_env(common_test, basic_html, BasicHtmlBool)
end,
- case lists:keysearch(spec, 1, Opts) of
- {value,{_,Specs}} ->
- Relaxed =
- case lists:keysearch(allow_user_terms, 1, Opts) of
- {value,{_,true}} -> true;
- _ -> false
- end,
+ %% stepped execution
+ Step = get_start_opt(step, value, StartOpts),
+
+ Opts = #opts{cover = Cover, step = Step, logdir = LogDir, config = CfgFiles,
+ event_handlers = EvHandlers, include = Include,
+ silent_connections = SilentConns,
+ stylesheet = Stylesheet,
+ multiply_timetraps = MultiplyTT,
+ scale_timetraps = ScaleTT},
+
+ %% test specification
+ case proplists:get_value(spec, StartOpts) of
+ undefined ->
+ case proplists:get_value(prepared_tests, StartOpts) of
+ undefined -> % use dir|suite|case
+ run_dir(Opts, StartOpts);
+ {{Run,Skip},Specs} -> % use prepared tests
+ run_prepared(Run, Skip, Opts#opts{testspecs = Specs}, StartOpts)
+ end;
+ Specs ->
+ Relaxed = get_start_opt(allow_user_terms, value, false, StartOpts),
%% using testspec(s) as input for test
- run_spec_file(LogDir, CfgFiles, EvHandlers, Include, Specs, Relaxed, Cover,
- replace_opt([{silent_connections,SilentConns}], Opts));
- false ->
- case lists:keysearch(prepared_tests, 1, Opts) of
- {value,{_,{Run,Skip},Specs}} -> % use prepared tests
- run_prepared(LogDir, CfgFiles, EvHandlers,
- Run, Skip, Cover,
- replace_opt([{silent_connections,SilentConns},
- {spec,Specs}],Opts));
- false -> % use dir|suite|case
- StepOrCover =
- case lists:keysearch(step, 1, Opts) of
- {value,Step} -> [Step];
- false -> Cover
- end,
- run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover,
- replace_opt([{silent_connections,SilentConns}], Opts))
- end
+ run_spec_file(Relaxed, Opts#opts{testspecs = Specs}, StartOpts)
end.
-replace_opt([O={Key,_Val}|Os], Opts) ->
- [O | replace_opt(Os, lists:keydelete(Key, 1, Opts))];
-replace_opt([], Opts) ->
- Opts.
-
-run_spec_file(LogDir, CfgFiles, EvHandlers, Include, Specs, Relaxed, Cover, Opts) ->
+run_spec_file(Relaxed,
+ Opts = #opts{testspecs = Specs, config = CfgFiles},
+ StartOpts) ->
Specs1 = case Specs of
[X|_] when is_integer(X) -> [Specs];
_ -> Specs
end,
- AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1),
+ AbsSpecs = lists:map(fun(SF) -> ?abs(SF) end, Specs1),
log_ts_names(AbsSpecs),
case catch ct_testspec:collect_tests_from_file(AbsSpecs, Relaxed) of
- {error,CTReason} ->
+ {Error,CTReason} when Error == error ; Error == 'EXIT' ->
exit(CTReason);
TS ->
- {LogDir1,TSCoverFile,CfgFiles1,EvHandlers1,Include1} =
- get_data_for_node(TS, node()),
- application:set_env(common_test, include, Include++Include1),
- LogDir2 = which_logdir(LogDir, LogDir1),
- CoverOpt = case {Cover,TSCoverFile} of
- {[],undef} -> [];
- {_,undef} -> Cover;
- {[],_} -> [{cover,TSCoverFile}]
- end,
- case get_configfiles(CfgFiles++CfgFiles1, [], LogDir2,
- EvHandlers++EvHandlers1) of
+ SpecOpts = get_data_for_node(TS, node()),
+ LogDir = choose_val(Opts#opts.logdir,
+ SpecOpts#opts.logdir),
+ AllConfig = merge_vals([CfgFiles, SpecOpts#opts.config]),
+ Cover = choose_val(Opts#opts.cover,
+ SpecOpts#opts.cover),
+ MultTT = choose_val(Opts#opts.multiply_timetraps,
+ SpecOpts#opts.multiply_timetraps),
+ ScaleTT = choose_val(Opts#opts.scale_timetraps,
+ SpecOpts#opts.scale_timetraps),
+ AllEvHs = merge_vals([Opts#opts.event_handlers,
+ SpecOpts#opts.event_handlers]),
+ AllInclude = merge_vals([Opts#opts.include,
+ SpecOpts#opts.include]),
+ application:set_env(common_test, include, AllInclude),
+
+ case check_and_install_configfiles(AllConfig,
+ which(logdir,LogDir),
+ AllEvHs) of
ok ->
+ Opts1 = Opts#opts{cover = Cover,
+ logdir = LogDir,
+ config = AllConfig,
+ event_handlers = AllEvHs,
+ include = AllInclude,
+ testspecs = AbsSpecs,
+ multiply_timetraps = MultTT,
+ scale_timetraps = ScaleTT},
{Run,Skip} = ct_testspec:prepare_tests(TS, node()),
- do_run(Run, Skip, CoverOpt,
- replace_opt([{spec,AbsSpecs}], Opts),
- LogDir2);
+ do_run(Run, Skip, Opts1, StartOpts);
{error,GCFReason} ->
exit(GCFReason)
end
end.
-run_prepared(LogDir, CfgFiles, EvHandlers, Run, Skip, Cover, Opts) ->
- case get_configfiles(CfgFiles, [], LogDir, EvHandlers) of
+run_prepared(Run, Skip, Opts = #opts{logdir = LogDir,
+ config = CfgFiles,
+ event_handlers = EvHandlers},
+ StartOpts) ->
+ case check_and_install_configfiles(CfgFiles, LogDir, EvHandlers) of
ok ->
- do_run(Run, Skip, Cover, Opts, LogDir);
+ do_run(Run, Skip, Opts, StartOpts);
{error,Reason} ->
exit(Reason)
- end.
-
-run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) ->
- AbsCfgFiles =
- lists:map(fun(F) ->
- AbsName = ?abs(F),
- case filelib:is_file(AbsName) of
- true -> AbsName;
- false -> exit({no_such_file,AbsName})
- end
- end, CfgFiles),
+ end.
+check_config_file(Callback, File)->
+ case code:is_loaded(Callback) of
+ false ->
+ case code:load_file(Callback) of
+ {module,_} -> ok;
+ {error,Why} -> exit({cant_load_callback_module,Why})
+ end;
+ _ ->
+ ok
+ end,
+ case Callback:check_parameter(File) of
+ {ok,{file,File}}->
+ ?abs(File);
+ {ok,{config,_}}->
+ File;
+ {error,{wrong_config,Message}}->
+ exit({wrong_config,{Callback,Message}});
+ {error,{nofile,File}}->
+ exit({no_such_file,?abs(File)})
+ end.
+
+run_dir(Opts = #opts{logdir = LogDir,
+ config = CfgFiles,
+ event_handlers = EvHandlers}, StartOpts) ->
+ AbsCfgFiles =
+ lists:map(fun({Callback,FileList})->
+ case code:is_loaded(Callback) of
+ {file,_Path}->
+ ok;
+ false ->
+ case code:load_file(Callback) of
+ {module,Callback}->
+ ok;
+ {error,_}->
+ exit({no_such_module,Callback})
+ end
+ end,
+ {Callback,
+ lists:map(fun(File)->
+ check_config_file(Callback, File)
+ end, FileList)}
+ end, CfgFiles),
case install([{config,AbsCfgFiles},{event_handler,EvHandlers}], LogDir) of
ok -> ok;
{error,IReason} -> exit(IReason)
end,
- case lists:keysearch(dir,1,Opts) of
+ case lists:keysearch(dir, 1, StartOpts) of
{value,{_,Dirs=[Dir|_]}} when not is_integer(Dir),
length(Dirs)>1 ->
%% multiple dirs (no suite)
- do_run(tests(Dirs), [], StepOrCover, Opts, LogDir);
+ do_run(tests(Dirs), [], Opts, StartOpts);
false -> % no dir
%% fun for converting suite name to {Dir,Mod} tuple
S2M = fun(S) when is_list(S) ->
@@ -728,105 +851,120 @@ run_dir(LogDir, CfgFiles, EvHandlers, StepOrCover, Opts) ->
(A) ->
{".",A}
end,
- case lists:keysearch(suite, 1, Opts) of
+ case lists:keysearch(suite, 1, StartOpts) of
{value,{_,Suite}} when is_integer(hd(Suite)) ; is_atom(Suite) ->
{Dir,Mod} = S2M(Suite),
- case listify(proplists:get_value(group, Opts, [])) ++
- listify(proplists:get_value(testcase, Opts, [])) of
+ case listify(proplists:get_value(group, StartOpts, [])) ++
+ listify(proplists:get_value(testcase, StartOpts, [])) of
[] ->
- do_run(tests(Dir, listify(Mod)), [], StepOrCover, Opts, LogDir);
+ do_run(tests(Dir, listify(Mod)), [], Opts, StartOpts);
GsAndCs ->
- do_run(tests(Dir, Mod, GsAndCs), [], StepOrCover, Opts, LogDir)
+ do_run(tests(Dir, Mod, GsAndCs), [], Opts, StartOpts)
end;
{value,{_,Suites}} ->
- do_run(tests(lists:map(S2M, Suites)), [], StepOrCover, Opts, LogDir);
+ do_run(tests(lists:map(S2M, Suites)), [], Opts, StartOpts);
_ ->
exit(no_tests_specified)
- end;
+ end;
{value,{_,Dir}} ->
- case lists:keysearch(suite, 1, Opts) of
+ case lists:keysearch(suite, 1, StartOpts) of
{value,{_,Suite}} when is_integer(hd(Suite)) ; is_atom(Suite) ->
- Mod = if is_atom(Suite) -> Suite;
- true -> list_to_atom(Suite)
+ Mod = if is_atom(Suite) -> Suite;
+ true -> list_to_atom(Suite)
end,
- case listify(proplists:get_value(group, Opts, [])) ++
- listify(proplists:get_value(testcase, Opts, [])) of
+ case listify(proplists:get_value(group, StartOpts, [])) ++
+ listify(proplists:get_value(testcase, StartOpts, [])) of
[] ->
- do_run(tests(Dir, listify(Mod)), [], StepOrCover, Opts, LogDir);
+ do_run(tests(Dir, listify(Mod)), [], Opts, StartOpts);
GsAndCs ->
- do_run(tests(Dir, Mod, GsAndCs), [], StepOrCover, Opts, LogDir)
+ do_run(tests(Dir, Mod, GsAndCs), [], Opts, StartOpts)
end;
{value,{_,Suites=[Suite|_]}} when is_list(Suite) ->
Mods = lists:map(fun(Str) -> list_to_atom(Str) end, Suites),
- do_run(tests(delistify(Dir), Mods), [], StepOrCover, Opts, LogDir);
+ do_run(tests(delistify(Dir), Mods), [], Opts, StartOpts);
{value,{_,Suites}} ->
- do_run(tests(delistify(Dir), Suites), [], StepOrCover, Opts, LogDir);
+ do_run(tests(delistify(Dir), Suites), [], Opts, StartOpts);
false -> % no suite, only dir
- do_run(tests(listify(Dir)), [], StepOrCover, Opts, LogDir)
- end
+ do_run(tests(listify(Dir)), [], Opts, StartOpts)
+ end
end.
%%%-----------------------------------------------------------------
-%%% @hidden
+%%% @spec run_testspec(TestSpec) -> Result
+%%% TestSpec = [term()]
%%%
+%%% @doc Run test specified by <code>TestSpec</code>. The terms are
+%%% the same as those used in test specification files.
+%%% @equiv ct:run_testspec/1
+%%%-----------------------------------------------------------------
-%% using testspec(s) as input for test
run_testspec(TestSpec) ->
{ok,Cwd} = file:get_cwd(),
io:format("~nCommon Test starting (cwd is ~s)~n~n", [Cwd]),
case catch run_testspec1(TestSpec) of
- {'EXIT',Reason} ->
+ {'EXIT',Reason} ->
file:set_cwd(Cwd),
{error,Reason};
- Result ->
+ Result ->
Result
end.
run_testspec1(TestSpec) ->
- case ct_testspec:collect_tests_from_list(TestSpec,false) of
- {error,CTReason} ->
+ case catch ct_testspec:collect_tests_from_list(TestSpec, false) of
+ {E,CTReason} when E == error ; E == 'EXIT' ->
exit(CTReason);
TS ->
- {LogDir,TSCoverFile,CfgFiles,EvHandlers,Include} =
- get_data_for_node(TS,node()),
- case os:getenv("CT_INCLUDE_PATH") of
- false ->
- application:set_env(common_test, include, Include);
- CtInclPath ->
- EnvInclude = string:tokens(CtInclPath, [$:,$ ,$,]),
- application:set_env(common_test, include, EnvInclude++Include)
- end,
- CoverOpt = if TSCoverFile == undef -> [];
- true -> [{cover,TSCoverFile}]
- end,
- case get_configfiles(CfgFiles,[],LogDir,EvHandlers) of
+ Opts = get_data_for_node(TS, node()),
+
+ AllInclude =
+ case os:getenv("CT_INCLUDE_PATH") of
+ false ->
+ Opts#opts.include;
+ CtInclPath ->
+ EnvInclude = string:tokens(CtInclPath, [$:,$ ,$,]),
+ EnvInclude++Opts#opts.include
+ end,
+ application:set_env(common_test, include, AllInclude),
+
+ case check_and_install_configfiles(Opts#opts.config,
+ which(logdir,Opts#opts.logdir),
+ Opts#opts.event_handlers) of
ok ->
- {Run,Skip} = ct_testspec:prepare_tests(TS,node()),
- do_run(Run,Skip,CoverOpt,[],LogDir);
+ Opts1 = Opts#opts{testspecs = [TestSpec],
+ include = AllInclude},
+ {Run,Skip} = ct_testspec:prepare_tests(TS, node()),
+ do_run(Run, Skip, Opts1, []);
{error,GCFReason} ->
exit(GCFReason)
end
end.
-
get_data_for_node(#testspec{logdir=LogDirs,
cover=CoverFs,
config=Cfgs,
+ userconfig=UsrCfgs,
event_handler=EvHs,
- include=Incl}, Node) ->
- LogDir = case lists:keysearch(Node,1,LogDirs) of
- {value,{Node,Dir}} -> Dir;
- false -> "."
+ include=Incl,
+ multiply_timetraps=MTs,
+ scale_timetraps=STs}, Node) ->
+ LogDir = case proplists:get_value(Node, LogDirs) of
+ undefined -> ".";
+ Dir -> Dir
end,
- Cover = case lists:keysearch(Node,1,CoverFs) of
- {value,{Node,CovFile}} -> CovFile;
- false -> undef
- end,
- ConfigFiles = [F || {N,F} <- Cfgs, N==Node],
+ Cover = proplists:get_value(Node, CoverFs),
+ MT = proplists:get_value(Node, MTs),
+ ST = proplists:get_value(Node, STs),
+ ConfigFiles = [{?ct_config_txt,F} || {N,F} <- Cfgs, N==Node] ++
+ [CBF || {N,CBF} <- UsrCfgs, N==Node],
EvHandlers = [{H,A} || {N,H,A} <- EvHs, N==Node],
Include = [I || {N,I} <- Incl, N==Node],
- {LogDir,Cover,ConfigFiles,EvHandlers,Include}.
-
+ #opts{logdir = LogDir,
+ cover = Cover,
+ config = ConfigFiles,
+ event_handlers = EvHandlers,
+ include = Include,
+ multiply_timetraps = MT,
+ scale_timetraps = ST}.
refresh_logs(LogDir) ->
{ok,Cwd} = file:get_cwd(),
@@ -851,11 +989,27 @@ refresh_logs(LogDir) ->
end
end.
-which_logdir(".",Dir) ->
+which(logdir, undefined) ->
+ ".";
+which(logdir, Dir) ->
Dir;
-which_logdir(Dir,_) ->
- Dir.
-
+which(multiply_timetraps, undefined) ->
+ 1;
+which(multiply_timetraps, MT) ->
+ MT;
+which(scale_timetraps, undefined) ->
+ false;
+which(scale_timetraps, ST) ->
+ ST.
+
+choose_val(undefined, V1) ->
+ V1;
+choose_val(V0, _V1) ->
+ V0.
+
+merge_vals(Vs) ->
+ lists:append(Vs).
+
listify([C|_]=Str) when is_integer(C) -> [Str];
listify(L) when is_list(L) -> L;
listify(E) -> [E].
@@ -885,6 +1039,22 @@ run(TestDirs) ->
install([]),
do_run(tests(TestDirs), []).
+suite_to_test(Suite) ->
+ {filename:dirname(Suite),list_to_atom(filename:rootname(filename:basename(Suite)))}.
+
+groups_and_cases(Gs, Cs) when ((Gs == undefined) or (Gs == [])) and
+ ((Cs == undefined) or (Cs == [])) ->
+ [];
+groups_and_cases(Gs, Cs) when Gs == undefined ; Gs == [] ->
+ [list_to_atom(C) || C <- Cs];
+groups_and_cases(Gs, Cs) when Cs == undefined ; Cs == [] ->
+ [{list_to_atom(G),all} || G <- Gs];
+groups_and_cases([G], Cs) ->
+ [{list_to_atom(G),[list_to_atom(C) || C <- Cs]}];
+groups_and_cases([_,_|_] , Cs) when Cs =/= [] ->
+ {error,multiple_groups_and_cases};
+groups_and_cases(_Gs, _Cs) ->
+ {error,incorrect_group_or_case_option}.
tests(TestDir, Suites, []) when is_list(TestDir), is_integer(hd(TestDir)) ->
[{?testdir(TestDir,Suites),ensure_atom(Suites),all}];
@@ -901,30 +1071,42 @@ tests(TestDir) when is_list(TestDir), is_integer(hd(TestDir)) ->
tests(TestDirs) when is_list(TestDirs), is_list(hd(TestDirs)) ->
[{?testdir(TestDir,all),all,all} || TestDir <- TestDirs].
-do_run(Tests, Opt) ->
- do_run(Tests, [], Opt, [], ".").
+do_run(Tests, Misc) when is_list(Misc) ->
+ do_run(Tests, Misc, ".").
-do_run(Tests, Opt, LogDir) ->
- do_run(Tests, [], Opt, [], LogDir).
+do_run(Tests, Misc, LogDir) when is_list(Misc) ->
+ Opts =
+ case proplists:get_value(step, Misc) of
+ undefined ->
+ #opts{};
+ StepOpts ->
+ #opts{step = StepOpts}
+ end,
+ Opts1 =
+ case proplists:get_value(cover, Misc) of
+ undefined ->
+ Opts;
+ CoverFile ->
+ Opts#opts{cover = CoverFile}
+ end,
+ do_run(Tests, [], Opts1#opts{logdir = LogDir}, []).
-do_run(Tests, Skip, Opt, Args, LogDir) ->
+do_run(Tests, Skip, Opts, Args) ->
+ #opts{cover = Cover} = Opts,
case code:which(test_server) of
non_existing ->
exit({error,no_path_to_test_server});
_ ->
- Opt1 =
- case lists:keysearch(cover, 1, Opt) of
- {value,{_,CoverFile}} ->
- case ct_cover:get_spec(CoverFile) of
- {error,Reason} ->
- exit({error,Reason});
- Spec ->
- [{cover_spec,Spec} |
- lists:keydelete(cover, 1, Opt)]
- end;
- _ ->
- Opt
- end,
+ Opts1 = if Cover == undefined ->
+ Opts;
+ true ->
+ case ct_cover:get_spec(Cover) of
+ {error,Reason} ->
+ exit({error,Reason});
+ CoverSpec ->
+ Opts#opts{coverspec = CoverSpec}
+ end
+ end,
%% This env variable is used by test_server to determine
%% which framework it runs under.
case os:getenv("TEST_SERVER_FRAMEWORK") of
@@ -935,50 +1117,39 @@ do_run(Tests, Skip, Opt, Args, LogDir) ->
Other ->
erlang:display(list_to_atom("Note: TEST_SERVER_FRAMEWORK = " ++ Other))
end,
- case ct_util:start(LogDir) of
+ case ct_util:start(Opts#opts.logdir) of
{error,interactive_mode} ->
io:format("CT is started in interactive mode. "
"To exit this mode, run ct:stop_interactive().\n"
"To enter the interactive mode again, "
"run ct:start_interactive()\n\n",[]),
{error,interactive_mode};
-
_Pid ->
- %% save style sheet info
- case lists:keysearch(stylesheet, 1, Args) of
- {value,{_,SSFile}} ->
- ct_util:set_testdata({stylesheet,SSFile});
- _ ->
- ct_util:set_testdata({stylesheet,undefined})
- end,
-
- case lists:keysearch(silent_connections, 1, Args) of
- {value,{silent_connections,undefined}} ->
- ok;
- {value,{silent_connections,[]}} ->
+ %% save stylesheet info
+ ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}),
+ %% enable silent connections
+ case Opts#opts.silent_connections of
+ [] ->
Conns = ct_util:override_silence_all_connections(),
ct_logs:log("Silent connections", "~p", [Conns]);
- {value,{silent_connections,Cs}} ->
- Conns = lists:map(fun(S) when is_list(S) ->
- list_to_atom(S);
- (A) -> A
- end, Cs),
+ Conns when is_list(Conns) ->
ct_util:override_silence_connections(Conns),
ct_logs:log("Silent connections", "~p", [Conns]);
_ ->
ok
end,
- log_ts_names(Args),
+ log_ts_names(Opts1#opts.testspecs),
TestSuites = suite_tuples(Tests),
- {SuiteMakeErrors,AllMakeErrors} =
+ {_TestSuites1,SuiteMakeErrors,AllMakeErrors} =
case application:get_env(common_test, auto_compile) of
{ok,false} ->
- SuitesNotFound = verify_suites(TestSuites),
- {SuitesNotFound,SuitesNotFound};
+ {TestSuites1,SuitesNotFound} =
+ verify_suites(TestSuites),
+ {TestSuites1,SuitesNotFound,SuitesNotFound};
_ ->
{SuiteErrs,HelpErrs} = auto_compile(TestSuites),
- {SuiteErrs,SuiteErrs++HelpErrs}
+ {TestSuites,SuiteErrs,SuiteErrs++HelpErrs}
end,
case continue(AllMakeErrors) of
@@ -986,7 +1157,7 @@ do_run(Tests, Skip, Opt, Args, LogDir) ->
SavedErrors = save_make_errors(SuiteMakeErrors),
ct_repeat:log_loop_info(Args),
{Tests1,Skip1} = final_tests(Tests,[],Skip,SavedErrors),
- R = do_run_test(Tests1, Skip1, Opt1),
+ R = do_run_test(Tests1, Skip1, Opts1),
ct_util:stop(normal),
R;
false ->
@@ -1012,7 +1183,7 @@ auto_compile(TestSuites) ->
case application:get_env(common_test, include) of
{ok,UserInclDirs} when length(UserInclDirs) > 0 ->
io:format("Including the following directories:~n"),
- [begin io:format("~p~n",[UserInclDir]), {i,UserInclDir} end ||
+ [begin io:format("~p~n",[UserInclDir]), {i,UserInclDir} end ||
UserInclDir <- UserInclDirs];
_ ->
[]
@@ -1020,11 +1191,11 @@ auto_compile(TestSuites) ->
SuiteMakeErrors =
lists:flatmap(fun({TestDir,Suite} = TS) ->
case run_make(suites, TestDir, Suite, UserInclude) of
- {error,{make_failed,Bad}} ->
+ {error,{make_failed,Bad}} ->
[{TS,Bad}];
- {error,_} ->
+ {error,_} ->
[{TS,[filename:join(TestDir,"*_SUITE")]}];
- _ ->
+ _ ->
[]
end
end, TestSuites),
@@ -1048,39 +1219,63 @@ auto_compile(TestSuites) ->
true -> % already visited
{Done,Failed}
end
- end, {[],[]}, TestSuites),
+ end, {[],[]}, TestSuites),
{SuiteMakeErrors,lists:reverse(HelpMakeErrors)}.
%% verify that specified test suites exist (if auto compile is disabled)
verify_suites(TestSuites) ->
io:nl(),
- Verify =
- fun({Dir,Suite},NotFound) ->
+ Verify =
+ fun({Dir,Suite}=DS,{Found,NotFound}) ->
case locate_test_dir(Dir, Suite) of
{ok,TestDir} ->
if Suite == all ->
- NotFound;
+ {[DS|Found],NotFound};
true ->
- Beam = filename:join(TestDir, atom_to_list(Suite)++".beam"),
+ Beam = filename:join(TestDir,
+ atom_to_list(Suite)++".beam"),
case filelib:is_regular(Beam) of
- true ->
- NotFound;
- false ->
- Name = filename:join(TestDir, atom_to_list(Suite)),
- io:format("Suite ~w not found in directory ~s~n",
- [Suite,TestDir]),
- [{{Dir,Suite},[Name]} | NotFound]
+ true ->
+ {[DS|Found],NotFound};
+ false ->
+ case code:is_loaded(Suite) of
+ {file,SuiteFile} ->
+ %% test suite is already loaded and
+ %% since auto_compile == false,
+ %% let's assume the user has
+ %% loaded the beam file explicitly
+ ActualDir = filename:dirname(SuiteFile),
+ {[{ActualDir,Suite}|Found],NotFound};
+ false ->
+ Name =
+ filename:join(TestDir,
+ atom_to_list(Suite)),
+ io:format(user,
+ "Suite ~w not found"
+ "in directory ~s~n",
+ [Suite,TestDir]),
+ {Found,[{DS,[Name]}|NotFound]}
+ end
end
end;
{error,_Reason} ->
- io:format("Directory ~s is invalid~n", [Dir]),
- Name = filename:join(Dir, atom_to_list(Suite)),
- [{{Dir,Suite},[Name]} | NotFound]
+ case code:is_loaded(Suite) of
+ {file,SuiteFile} ->
+ %% test suite is already loaded and since
+ %% auto_compile == false, let's assume the
+ %% user has loaded the beam file explicitly
+ ActualDir = filename:dirname(SuiteFile),
+ {[{ActualDir,Suite}|Found],NotFound};
+ false ->
+ io:format(user, "Directory ~s is invalid~n", [Dir]),
+ Name = filename:join(Dir, atom_to_list(Suite)),
+ {Found,[{DS,[Name]}|NotFound]}
+ end
end
end,
- lists:reverse(lists:foldl(Verify, [], TestSuites)).
-
-
+ {ActualFound,Missing} = lists:foldl(Verify, {[],[]}, TestSuites),
+ {lists:reverse(ActualFound),lists:reverse(Missing)}.
+
save_make_errors([]) ->
[];
save_make_errors(Errors) ->
@@ -1096,7 +1291,7 @@ get_bad_suites([{{_TestDir,_Suite},Failed}|Errors], BadSuites) ->
get_bad_suites([], BadSuites) ->
BadSuites.
-
+
%%%-----------------------------------------------------------------
%%% @hidden
@@ -1107,7 +1302,7 @@ step(TestDir, Suite, Case) ->
%%%-----------------------------------------------------------------
%%% @hidden
%%% @equiv ct:step/4
-step(TestDir, Suite, Case, Opts) when is_list(TestDir), is_atom(Suite), is_atom(Case),
+step(TestDir, Suite, Case, Opts) when is_list(TestDir), is_atom(Suite), is_atom(Case),
Suite =/= all, Case =/= all ->
do_run([{TestDir,Suite,Case}], [{step,Opts}]).
@@ -1140,7 +1335,7 @@ final_tests([{TestDir,Suites,_}|Tests],
Skip1 = [{TD,S,"Make failed"} || {{TD,S},_} <- Bad, S1 <- Suites,
S == S1, TD == TestDir],
- Final1 = [{TestDir,S,all} || S <- Suites],
+ Final1 = [{TestDir,S,all} || S <- Suites],
final_tests(Tests, lists:reverse(Final1)++Final, Skip++Skip1, Bad);
final_tests([{TestDir,all,all}|Tests], Final, Skip, Bad) ->
@@ -1173,7 +1368,7 @@ final_tests([{TestDir,Suite,Cases}|Tests], Final, Skip, Bad) ->
final_tests([], Final, Skip, _Bad) ->
{lists:reverse(Final),Skip}.
-continue([]) ->
+continue([]) ->
true;
continue(_MakeErrors) ->
io:nl(),
@@ -1214,7 +1409,7 @@ set_group_leader_same_as_shell() ->
false
end
end,
- case [P || P <- processes(), GS2or3(P),
+ case [P || P <- processes(), GS2or3(P),
true == lists:keymember(shell,1,element(2,process_info(P,dictionary)))] of
[GL|_] ->
group_leader(GL, self());
@@ -1238,29 +1433,29 @@ check_and_add([{TestDir0,M,_} | Tests], Added) ->
check_and_add([], _) ->
ok.
-do_run_test(Tests, Skip, Opt) ->
+do_run_test(Tests, Skip, Opts) ->
case check_and_add(Tests, []) of
ok ->
ct_util:set_testdata({stats,{0,0,{0,0}}}),
ct_util:set_testdata({cover,undefined}),
test_server_ctrl:start_link(local),
- case lists:keysearch(cover_spec, 1, Opt) of
- {value,{_,CovData={CovFile,
- CovNodes,
- _CovImport,
- CovExport,
- #cover{app = CovApp,
- level = CovLevel,
- excl_mods = CovExcl,
- incl_mods = CovIncl,
- cross = CovCross,
- src = _CovSrc}}}} ->
+ case Opts#opts.coverspec of
+ CovData={CovFile,
+ CovNodes,
+ _CovImport,
+ CovExport,
+ #cover{app = CovApp,
+ level = CovLevel,
+ excl_mods = CovExcl,
+ incl_mods = CovIncl,
+ cross = CovCross,
+ src = _CovSrc}} ->
ct_logs:log("COVER INFO","Using cover specification file: ~s~n"
"App: ~w~n"
"Cross cover: ~w~n"
"Including ~w modules~n"
"Excluding ~w modules",
- [CovFile,CovApp,CovCross,length(CovIncl),length(CovExcl)]),
+ [CovFile,CovApp,CovCross,length(CovIncl),length(CovExcl)]),
%% cover export file will be used for export and import
%% between tests so make sure it doesn't exist initially
@@ -1293,33 +1488,37 @@ do_run_test(Tests, Skip, Opt) ->
true;
_ ->
false
- end,
+ end,
%% let test_server expand the test tuples and count no of cases
{Suites,NoOfCases} = count_test_cases(Tests, Skip),
Suites1 = delete_dups(Suites),
NoOfTests = length(Tests),
NoOfSuites = length(Suites1),
- ct_util:warn_duplicates(Suites1),
+ ct_util:warn_duplicates(Suites1),
{ok,Cwd} = file:get_cwd(),
io:format("~nCWD set to: ~p~n", [Cwd]),
if NoOfCases == unknown ->
- io:format("~nTEST INFO: ~w test(s), ~w suite(s)~n~n",
+ io:format("~nTEST INFO: ~w test(s), ~w suite(s)~n~n",
[NoOfTests,NoOfSuites]),
- ct_logs:log("TEST INFO","~w test(s), ~w suite(s)",
+ ct_logs:log("TEST INFO","~w test(s), ~w suite(s)",
[NoOfTests,NoOfSuites]);
true ->
- io:format("~nTEST INFO: ~w test(s), ~w case(s) in ~w suite(s)~n~n",
+ io:format("~nTEST INFO: ~w test(s), ~w case(s) in ~w suite(s)~n~n",
[NoOfTests,NoOfCases,NoOfSuites]),
- ct_logs:log("TEST INFO","~w test(s), ~w case(s) in ~w suite(s)",
+ ct_logs:log("TEST INFO","~w test(s), ~w case(s) in ~w suite(s)",
[NoOfTests,NoOfCases,NoOfSuites])
end,
+
+ test_server_ctrl:multiply_timetraps(Opts#opts.multiply_timetraps),
+ test_server_ctrl:scale_timetraps(Opts#opts.scale_timetraps),
+
ct_event:notify(#event{name=start_info,
node=node(),
data={NoOfTests,NoOfSuites,NoOfCases}}),
- CleanUp = add_jobs(Tests, Skip, Opt, []),
+ CleanUp = add_jobs(Tests, Skip, Opts, []),
unlink(whereis(test_server_ctrl)),
- catch test_server_ctrl:wait_finish(),
- %% check if last testcase has left a "dead" trace window
+ catch test_server_ctrl:wait_finish(),
+ %% check if last testcase has left a "dead" trace window
%% behind, and if so, kill it
case ct_util:get_testdata(interpret) of
{_What,kill,{TCPid,AttPid}} ->
@@ -1327,8 +1526,8 @@ do_run_test(Tests, Skip, Opt) ->
_ ->
ok
end,
- lists:foreach(fun(Suite) ->
- maybe_cleanup_interpret(Suite, Opt)
+ lists:foreach(fun(Suite) ->
+ maybe_cleanup_interpret(Suite, Opts#opts.step)
end, CleanUp);
Error ->
Error
@@ -1344,9 +1543,9 @@ count_test_cases(Tests, Skip) ->
SendResult = fun(Me, Result) -> Me ! {no_of_cases,Result} end,
TSPid = test_server_ctrl:start_get_totals(SendResult),
Ref = erlang:monitor(process, TSPid),
- add_jobs(Tests, Skip, [], []),
+ add_jobs(Tests, Skip, #opts{}, []),
{Suites,NoOfCases} = count_test_cases1(length(Tests), 0, [], Ref),
- erlang:demonitor(Ref),
+ erlang:demonitor(Ref, [flush]),
test_server_ctrl:stop_get_totals(),
{Suites,NoOfCases}.
@@ -1354,11 +1553,11 @@ count_test_cases1(0, N, Suites, _) ->
{lists:flatten(Suites), N};
count_test_cases1(Jobs, N, Suites, Ref) ->
receive
- {no_of_cases,{Ss,N1}} ->
+ {no_of_cases,{Ss,N1}} ->
count_test_cases1(Jobs-1, add_known(N,N1), [Ss|Suites], Ref);
- {'DOWN', Ref, _, _, _} ->
+ {'DOWN', Ref, _, _, _} ->
{[],0}
- end.
+ end.
add_known(unknown, _) ->
unknown;
@@ -1367,72 +1566,86 @@ add_known(_, unknown) ->
add_known(N, N1) ->
N+N1.
-add_jobs([{TestDir,all,_}|Tests], Skip, Opt, CleanUp) ->
+add_jobs([{TestDir,all,_}|Tests], Skip, Opts, CleanUp) ->
Name = get_name(TestDir),
case catch test_server_ctrl:add_dir_with_skip(Name, TestDir,
skiplist(TestDir,Skip)) of
- {'EXIT',_} ->
+ {'EXIT',_} ->
CleanUp;
_ ->
wait_for_idle(),
- add_jobs(Tests, Skip, Opt, CleanUp)
+ add_jobs(Tests, Skip, Opts, CleanUp)
end;
-add_jobs([{TestDir,[Suite],all}|Tests], Skip, Opt, CleanUp) when is_atom(Suite) ->
- add_jobs([{TestDir,Suite,all}|Tests], Skip, Opt, CleanUp);
-add_jobs([{TestDir,Suites,all}|Tests], Skip, Opt, CleanUp) when is_list(Suites) ->
+add_jobs([{TestDir,[Suite],all}|Tests], Skip, Opts, CleanUp) when is_atom(Suite) ->
+ add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp);
+add_jobs([{TestDir,Suites,all}|Tests], Skip, Opts, CleanUp) when is_list(Suites) ->
Name = get_name(TestDir) ++ ".suites",
case catch test_server_ctrl:add_module_with_skip(Name, Suites,
skiplist(TestDir,Skip)) of
- {'EXIT',_} ->
+ {'EXIT',_} ->
CleanUp;
_ ->
wait_for_idle(),
- add_jobs(Tests, Skip, Opt, CleanUp)
+ add_jobs(Tests, Skip, Opts, CleanUp)
end;
-add_jobs([{TestDir,Suite,all}|Tests], Skip, Opt, CleanUp) ->
- case maybe_interpret(Suite, all, Opt) of
+add_jobs([{TestDir,Suite,all}|Tests], Skip, Opts, CleanUp) ->
+ case maybe_interpret(Suite, all, Opts) of
ok ->
Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite),
case catch test_server_ctrl:add_module_with_skip(Name, [Suite],
skiplist(TestDir,Skip)) of
- {'EXIT',_} ->
+ {'EXIT',_} ->
CleanUp;
_ ->
wait_for_idle(),
- add_jobs(Tests, Skip, Opt, [Suite|CleanUp])
+ add_jobs(Tests, Skip, Opts, [Suite|CleanUp])
end;
Error ->
Error
end;
-add_jobs([{TestDir,Suite,[Case]}|Tests], Skip, Opt, CleanUp) when is_atom(Case) ->
- add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opt, CleanUp);
-add_jobs([{TestDir,Suite,Cases}|Tests], Skip, Opt, CleanUp) when is_list(Cases) ->
- case maybe_interpret(Suite, Cases, Opt) of
+
+%% group
+add_jobs([{TestDir,Suite,[{GroupName,_Cases}]}|Tests], Skip, Opts, CleanUp) when
+ is_atom(GroupName) ->
+ add_jobs([{TestDir,Suite,GroupName}|Tests], Skip, Opts, CleanUp);
+add_jobs([{TestDir,Suite,{GroupName,_Cases}}|Tests], Skip, Opts, CleanUp) when
+ is_atom(GroupName) ->
+ add_jobs([{TestDir,Suite,GroupName}|Tests], Skip, Opts, CleanUp);
+
+%% test case
+add_jobs([{TestDir,Suite,[Case]}|Tests], Skip, Opts, CleanUp) when is_atom(Case) ->
+ add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp);
+
+add_jobs([{TestDir,Suite,Cases}|Tests], Skip, Opts, CleanUp) when is_list(Cases) ->
+ Cases1 = lists:map(fun({GroupName,_}) when is_atom(GroupName) -> GroupName;
+ (Case) -> Case
+ end, Cases),
+ case maybe_interpret(Suite, Cases1, Opts) of
ok ->
Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ ".cases",
- case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases,
+ case catch test_server_ctrl:add_cases_with_skip(Name, Suite, Cases1,
skiplist(TestDir,Skip)) of
- {'EXIT',_} ->
+ {'EXIT',_} ->
CleanUp;
_ ->
wait_for_idle(),
- add_jobs(Tests, Skip, Opt, [Suite|CleanUp])
+ add_jobs(Tests, Skip, Opts, [Suite|CleanUp])
end;
Error ->
Error
end;
-add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opt, CleanUp) when is_atom(Case) ->
- case maybe_interpret(Suite, Case, Opt) of
+add_jobs([{TestDir,Suite,Case}|Tests], Skip, Opts, CleanUp) when is_atom(Case) ->
+ case maybe_interpret(Suite, Case, Opts) of
ok ->
- Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ "." ++
+ Name = get_name(TestDir) ++ "." ++ atom_to_list(Suite) ++ "." ++
atom_to_list(Case),
case catch test_server_ctrl:add_case_with_skip(Name, Suite, Case,
skiplist(TestDir,Skip)) of
- {'EXIT',_} ->
+ {'EXIT',_} ->
CleanUp;
_ ->
wait_for_idle(),
- add_jobs(Tests, Skip, Opt, [Suite|CleanUp])
+ add_jobs(Tests, Skip, Opts, [Suite|CleanUp])
end;
Error ->
Error
@@ -1453,7 +1666,7 @@ wait_for_idle() ->
idle -> ok;
{'DOWN', Ref, _, _, _} -> error
end,
- erlang:demonitor(Ref),
+ erlang:demonitor(Ref, [flush]),
ct_util:update_last_run_index(),
Result
end.
@@ -1482,7 +1695,7 @@ get_name(Dir) ->
end,
Base = filename:basename(TestDir),
case filename:basename(filename:dirname(TestDir)) of
- "" ->
+ "" ->
Base;
TopDir ->
TopDir ++ "." ++ Base
@@ -1513,15 +1726,15 @@ run_make(Targets, TestDir0, Mod, UserInclude) ->
{i,CtInclude},
{i,XmerlInclude},
debug_info],
- Result =
+ Result =
if Mod == all ; Targets == helpmods ->
case (catch ct_make:all([noexec|ErlFlags])) of
- {'EXIT',_} = Failure ->
+ {'EXIT',_} = Failure ->
Failure;
MakeInfo ->
FileTest = fun(F, suites) -> is_suite(F);
- (F, helpmods) -> not is_suite(F);
- (_, _) -> true end,
+ (F, helpmods) -> not is_suite(F)
+ end,
Files = lists:flatmap(fun({F,out_of_date}) ->
case FileTest(F, Targets) of
true -> [F];
@@ -1535,7 +1748,7 @@ run_make(Targets, TestDir0, Mod, UserInclude) ->
true ->
(catch ct_make:files([Mod], [load|ErlFlags]))
end,
-
+
ok = file:set_cwd(Cwd),
%% send finished_make notification
ct_event:notify(#event{name=finished_make,
@@ -1549,7 +1762,7 @@ run_make(Targets, TestDir0, Mod, UserInclude) ->
{error,{make_crashed,TestDir,Reason}};
{error,ModInfo} ->
io:format("{error,make_failed}\n", []),
- Bad = [filename:join(TestDir, M) || {M,R} <- ModInfo,
+ Bad = [filename:join(TestDir, M) || {M,R} <- ModInfo,
R == error],
{error,{make_failed,Bad}}
end;
@@ -1561,8 +1774,8 @@ run_make(Targets, TestDir0, Mod, UserInclude) ->
get_dir(App, Dir) ->
filename:join(code:lib_dir(App), Dir).
-maybe_interpret(Suite, Cases, [{step,StepOpts}]) ->
- %% if other suite has run before this one, check if last testcase
+maybe_interpret(Suite, Cases, #opts{step = StepOpts}) when StepOpts =/= undefined ->
+ %% if other suite has run before this one, check if last testcase
%% has left a "dead" trace window behind, and if so, kill it
case ct_util:get_testdata(interpret) of
{_What,kill,{TCPid,AttPid}} ->
@@ -1605,7 +1818,7 @@ maybe_interpret2(Suite, Cases, StepOpts) ->
WinOp = case lists:member(keep_inactive, ensure_atom(StepOpts)) of
true -> no_kill;
false -> kill
- end,
+ end,
ct_util:set_testdata({interpret,{{Suite,Cases},WinOp,
{undefined,undefined}}}),
ok.
@@ -1621,37 +1834,44 @@ set_break_on_config(Suite, StepOpts) ->
ok
end.
-maybe_cleanup_interpret(Suite, [{step,_}]) ->
- i:iq(Suite);
-maybe_cleanup_interpret(_, _) ->
- ok.
+maybe_cleanup_interpret(_, undefined) ->
+ ok;
+maybe_cleanup_interpret(Suite, _) ->
+ i:iq(Suite).
+
+log_ts_names([]) ->
+ ok;
+log_ts_names(Specs) ->
+ List = lists:map(fun(Name) ->
+ Name ++ " "
+ end, Specs),
+ ct_logs:log("Test Specification file(s)", "~s",
+ [lists:flatten(List)]).
-log_ts_names(Args) ->
- case lists:keysearch(spec, 1, Args) of
- {value,{_,Specs}} ->
- List = lists:map(fun(Name) ->
- Name ++ " "
- end, Specs),
- ct_logs:log("Test Specification file(s)", "~s",
- [lists:flatten(List)]);
- _ ->
- ok
- end.
-
merge_arguments(Args) ->
merge_arguments(Args, []).
merge_arguments([LogDir={logdir,_}|Args], Merged) ->
merge_arguments(Args, handle_arg(replace, LogDir, Merged));
+
merge_arguments([CoverFile={cover,_}|Args], Merged) ->
merge_arguments(Args, handle_arg(replace, CoverFile, Merged));
-merge_arguments([Arg={_,_}|Args], Merged) ->
+
+merge_arguments([{'case',TC}|Args], Merged) ->
+ merge_arguments(Args, handle_arg(merge, {testcase,TC}, Merged));
+
+merge_arguments([Arg|Args], Merged) ->
merge_arguments(Args, handle_arg(merge, Arg, Merged));
+
merge_arguments([], Merged) ->
Merged.
handle_arg(replace, {Key,Elems}, [{Key,_}|Merged]) ->
[{Key,Elems}|Merged];
+handle_arg(merge, {event_handler_init,Elems}, [{event_handler_init,PrevElems}|Merged]) ->
+ [{event_handler_init,PrevElems++["add"|Elems]}|Merged];
+handle_arg(merge, {userconfig,Elems}, [{userconfig,PrevElems}|Merged]) ->
+ [{userconfig,PrevElems++["add"|Elems]}|Merged];
handle_arg(merge, {Key,Elems}, [{Key,PrevElems}|Merged]) ->
[{Key,PrevElems++Elems}|Merged];
handle_arg(Op, Arg, [Other|Merged]) ->
@@ -1659,6 +1879,164 @@ handle_arg(Op, Arg, [Other|Merged]) ->
handle_arg(_,Arg,[]) ->
[Arg].
+get_start_opt(Key, IfExists, Args) ->
+ get_start_opt(Key, IfExists, undefined, Args).
+
+get_start_opt(Key, IfExists, IfNotExists, Args) ->
+ case lists:keysearch(Key, 1, Args) of
+ {value,{Key,Val}} when is_function(IfExists) ->
+ IfExists(Val);
+ {value,{Key,Val}} when IfExists == value ->
+ Val;
+ {value,{Key,_Val}} ->
+ IfExists;
+ _ when is_function(IfNotExists) ->
+ IfNotExists();
+ _ ->
+ IfNotExists
+ end.
+
+event_handler_args2opts(Args) ->
+ case proplists:get_value(event_handler, Args) of
+ undefined ->
+ event_handler_args2opts([], Args);
+ EHs ->
+ event_handler_args2opts([{list_to_atom(EH),[]} || EH <- EHs], Args)
+ end.
+event_handler_args2opts(Default, Args) ->
+ case proplists:get_value(event_handler_init, Args) of
+ undefined ->
+ Default;
+ EHs ->
+ event_handler_init_args2opts(EHs)
+ end.
+event_handler_init_args2opts([EH, Arg, "and" | EHs]) ->
+ [{list_to_atom(EH),lists:flatten(io_lib:format("~s",[Arg]))} |
+ event_handler_init_args2opts(EHs)];
+event_handler_init_args2opts([EH, Arg]) ->
+ [{list_to_atom(EH),lists:flatten(io_lib:format("~s",[Arg]))}];
+event_handler_init_args2opts([]) ->
+ [].
+
+%% This function reads pa and pz arguments, converts dirs from relative
+%% to absolute, and re-inserts them in the code path. The order of the
+%% dirs in the code path remain the same. Note however that since this
+%% function is only used for arguments "pre run_test erl_args", the order
+%% relative dirs "post run_test erl_args" is not kept!
+rel_to_abs(CtArgs) ->
+ {PA,PZ} = get_pa_pz(CtArgs, [], []),
+ io:format(user, "~n", []),
+ [begin
+ code:del_path(filename:basename(D)),
+ Abs = filename:absname(D),
+ code:add_pathz(Abs),
+ if D /= Abs ->
+ io:format(user, "Converting ~p to ~p and re-inserting "
+ "with add_pathz/1~n",
+ [D, Abs]);
+ true ->
+ ok
+ end
+ end || D <- PZ],
+ [begin
+ code:del_path(filename:basename(D)),
+ Abs = filename:absname(D),
+ code:add_patha(Abs),
+ if D /= Abs ->
+ io:format(user, "Converting ~p to ~p and re-inserting "
+ "with add_patha/1~n",
+ [D, Abs]);
+ true ->ok
+ end
+ end || D <- PA],
+ io:format(user, "~n", []).
+
+get_pa_pz([{pa,Dirs} | Args], PA, PZ) ->
+ get_pa_pz(Args, PA ++ Dirs, PZ);
+get_pa_pz([{pz,Dirs} | Args], PA, PZ) ->
+ get_pa_pz(Args, PA, PZ ++ Dirs);
+get_pa_pz([_ | Args], PA, PZ) ->
+ get_pa_pz(Args, PA, PZ);
+get_pa_pz([], PA, PZ) ->
+ {PA,PZ}.
+
+%% This function translates ct:run_test/1 start options
+%% to run_test start arguments (on the init arguments format) -
+%% this is useful mainly for testing the ct_run start functions.
+opts2args(EnvStartOpts) ->
+ lists:flatmap(fun({config,CfgFiles}) ->
+ [{ct_config,[CfgFiles]}];
+ ({userconfig,{CBM,CfgStr=[X|_]}}) when is_integer(X) ->
+ [{userconfig,[atom_to_list(CBM),CfgStr]}];
+ ({userconfig,{CBM,CfgStrs}}) when is_list(CfgStrs) ->
+ [{userconfig,[atom_to_list(CBM) | CfgStrs]}];
+ ({userconfig,UserCfg}) when is_list(UserCfg) ->
+ Strs =
+ lists:map(fun({CBM,CfgStr=[X|_]}) when is_integer(X) ->
+ [atom_to_list(CBM),CfgStr,"and"];
+ ({CBM,CfgStrs}) when is_list(CfgStrs) ->
+ [atom_to_list(CBM) | CfgStrs] ++ ["and"]
+ end, UserCfg),
+ [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)),
+ [{userconfig,lists:reverse(StrsR)}];
+ ({testcase,Case}) when is_atom(Case) ->
+ [{'case',[atom_to_list(Case)]}];
+ ({testcase,Cases}) ->
+ [{'case',[atom_to_list(C) || C <- Cases]}];
+ ({'case',Cases}) ->
+ [{'case',[atom_to_list(C) || C <- Cases]}];
+ ({allow_user_terms,true}) ->
+ [{allow_user_terms,[]}];
+ ({allow_user_terms,false}) ->
+ [];
+ ({auto_compile,false}) ->
+ [{no_auto_compile,[]}];
+ ({auto_compile,true}) ->
+ [];
+ ({scale_timetraps,true}) ->
+ [{scale_timetraps,[]}];
+ ({scale_timetraps,false}) ->
+ [];
+ ({force_stop,true}) ->
+ [{force_stop,[]}];
+ ({force_stop,false}) ->
+ [];
+ ({decrypt,{key,Key}}) ->
+ [{ct_decrypt_key,[Key]}];
+ ({decrypt,{file,File}}) ->
+ [{ct_decrypt_file,[File]}];
+ ({basic_html,true}) ->
+ ({basic_html,[]});
+ ({basic_html,false}) ->
+ [];
+ ({event_handler,EH}) when is_atom(EH) ->
+ [{event_handler,[atom_to_list(EH)]}];
+ ({event_handler,EHs}) when is_list(EHs) ->
+ [{event_handler,[atom_to_list(EH) || EH <- EHs]}];
+ ({event_handler,{EH,Arg}}) when is_atom(EH) ->
+ ArgStr = lists:flatten(io_lib:format("~p", [Arg])),
+ [{event_handler_init,[atom_to_list(EH),ArgStr]}];
+ ({event_handler,{EHs,Arg}}) when is_list(EHs) ->
+ ArgStr = lists:flatten(io_lib:format("~p", [Arg])),
+ Strs = lists:map(fun(EH) ->
+ [atom_to_list(EH),ArgStr,"and"]
+ end, EHs),
+ [_LastAnd|StrsR] = lists:reverse(lists:flatten(Strs)),
+ [{event_handler_init,lists:reverse(StrsR)}];
+ ({Opt,As=[A|_]}) when is_atom(A) ->
+ [{Opt,[atom_to_list(Atom) || Atom <- As]}];
+ ({Opt,Strs=[S|_]}) when is_list(S) ->
+ [{Opt,Strs}];
+ ({Opt,A}) when is_atom(A) ->
+ [{Opt,[atom_to_list(A)]}];
+ ({Opt,I}) when is_integer(I) ->
+ [{Opt,[integer_to_list(I)]}];
+ ({Opt,S}) when is_list(S) ->
+ [{Opt,[S]}];
+ (Opt) ->
+ Opt
+ end, EnvStartOpts).
+
locate_test_dir(Dir, Suite) ->
TestDir = case ct_util:is_test_dir(Dir) of
true -> Dir;
@@ -1723,18 +2101,18 @@ start_trace(Args) ->
case file:consult(TraceSpec) of
{ok,Terms} ->
case catch do_trace(Terms) of
- ok ->
+ ok ->
true;
{_,Error} ->
io:format("Warning! Tracing not started. Reason: ~p~n~n",
[Error]),
false
- end;
+ end;
{_,Error} ->
io:format("Warning! Tracing not started. Reason: ~p~n~n",
[Error]),
false
- end;
+ end;
false ->
false
end.
@@ -1746,61 +2124,22 @@ do_trace(Terms) ->
case dbg:tpl(M,[{'_',[],[{return_trace}]}]) of
{error,What} -> exit({error,{tracing_failed,What}});
_ -> ok
- end;
+ end;
({f,M,F}) ->
case dbg:tpl(M,F,[{'_',[],[{return_trace}]}]) of
{error,What} -> exit({error,{tracing_failed,What}});
_ -> ok
- end;
+ end;
(Huh) ->
exit({error,{unrecognized_trace_term,Huh}})
end, Terms),
ok.
-
+
stop_trace(true) ->
dbg:stop_clear();
stop_trace(false) ->
ok.
-preload() ->
- io:format("~nLoading Common Test and Test Server modules...~n~n"),
- preload_mod([ct_logs,
- ct_make,
- ct_telnet,
- ct,
- ct_master,
- ct_testspec,
- ct_cover,
- ct_master_event,
- ct_util,
- ct_event,
- ct_master_logs,
- ct_framework,
- teln,
- ct_ftp,
- ct_rpc,
- unix_telnet,
- ct_gen_conn,
- ct_line,
- ct_snmp,
- test_server_sup,
- test_server,
- test_server_ctrl,
- test_server_h,
- test_server_line,
- test_server_node]).
-
-preload_mod([M|Ms]) ->
- case code:is_loaded(M) of
- false ->
- {module,M} = code:load_file(M),
- preload_mod(Ms);
- _ ->
- ok
- end;
-preload_mod([]) ->
- ok.
-
ensure_atom(Atom) when is_atom(Atom) ->
Atom;
ensure_atom(String) when is_list(String), is_integer(hd(String)) ->
@@ -1809,4 +2148,3 @@ ensure_atom(List) when is_list(List) ->
[ensure_atom(Item) || Item <- List];
ensure_atom(Other) ->
Other.
-
diff --git a/lib/common_test/src/ct_slave.erl b/lib/common_test/src/ct_slave.erl
new file mode 100644
index 0000000000..d2a491e079
--- /dev/null
+++ b/lib/common_test/src/ct_slave.erl
@@ -0,0 +1,439 @@
+%%--------------------------------------------------------------------
+%% %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%
+
+%%% @doc Common Test Framework functions for starting and stopping nodes for
+%%% Large Scale Testing.
+%%%
+%%% <p>This module exports functions which are used by the Common Test Master
+%%% to start and stop "slave" nodes. It is the default callback module for the
+%%% <code>{init, node_start}</code> term of the Test Specification.</p>
+
+%%----------------------------------------------------------------------
+%% File : ct_slave.erl
+%% Description : CT module for starting nodes for large-scale testing.
+%%
+%% Created : 7 April 2010
+%%----------------------------------------------------------------------
+-module(ct_slave).
+
+-export([start/1, start/2, start/3, stop/1, stop/2]).
+
+-export([slave_started/2, slave_ready/2, monitor_master/1]).
+
+-record(options, {username, password, boot_timeout, init_timeout,
+ startup_timeout, startup_functions, monitor_master,
+ kill_if_fail, erl_flags}).
+
+%%%-----------------------------------------------------------------
+%%% @spec start(Node) -> Result
+%%% Node = atom()
+%%% Result = {ok, NodeName} |
+%%% {error, already_started, NodeName} |
+%%% {error, started_not_connected, NodeName} |
+%%% {error, boot_timeout, NodeName} |
+%%% {error, init_timeout, NodeName} |
+%%% {error, startup_timeout, NodeName} |
+%%% {error, not_alive, NodeName}
+%%% NodeName = atom()
+%%% @doc Starts an Erlang node with name <code>Node</code> on the local host.
+%%% @see start/3
+start(Node)->
+ start(gethostname(), Node).
+
+%%%-----------------------------------------------------------------
+%%% @spec start(Host, Node) -> Result
+%%% Node = atom()
+%%% Host = atom()
+%%% Result = {ok, NodeName} |
+%%% {error, already_started, NodeName} |
+%%% {error, started_not_connected, NodeName} |
+%%% {error, boot_timeout, NodeName} |
+%%% {error, init_timeout, NodeName} |
+%%% {error, startup_timeout, NodeName} |
+%%% {error, not_alive, NodeName}
+%%% NodeName = atom()
+%%% @doc Starts an Erlang node with name <code>Node</code> on host
+%%% <code>Host</code> with the default options.
+%%% @see start/3
+start(Host, Node)->
+ start(Host, Node, []).
+
+%%%-----------------------------------------------------------------
+%%% @spec start(Host, Node, Opts) -> Result
+%%% Node = atom()
+%%% Host = atom()
+%%% Opts = [OptTuples]
+%%% OptTuples = {username, Username} |
+%%% {password, Password} |
+%%% {boot_timeout, BootTimeout} | {init_timeout, InitTimeout} |
+%%% {startup_timeout, StartupTimeout} |
+%%% {startup_functions, StartupFunctions} |
+%%% {monitor_master, Monitor} |
+%%% {kill_if_fail, KillIfFail} |
+%%% {erl_flags, ErlangFlags}
+%%% Username = string()
+%%% Password = string()
+%%% BootTimeout = integer()
+%%% InitTimeout = integer()
+%%% StartupTimeout = integer()
+%%% StartupFunctions = [StartupFunctionSpec]
+%%% StartupFunctionSpec = {Module, Function, Arguments}
+%%% Module = atom()
+%%% Function = atom()
+%%% Arguments = [term]
+%%% Monitor = bool()
+%%% KillIfFail = bool()
+%%% ErlangFlags = string()
+%%% Result = {ok, NodeName} | {error, already_started, NodeName} |
+%%% {error, started_not_connected, NodeName} |
+%%% {error, boot_timeout, NodeName} |
+%%% {error, init_timeout, NodeName} |
+%%% {error, startup_timeout, NodeName} |
+%%% {error, not_alive, NodeName}
+%%% NodeName = atom()
+%%% @doc Starts an Erlang node with name <code>Node</code> on host
+%%% <code>Host</code> as specified by the combination of options in
+%%% <code>Opts</code>.
+%%%
+%%% <p>Options <code>Username</code> and <code>Password</code> will be used
+%%% to log in onto the remote host <code>Host</code>.
+%%% Username, if omitted, defaults to the current user name,
+%%% and password is empty by default.</p>
+%%%
+%%% <p>A list of functions specified in the <code>Startup</code> option will be
+%%% executed after startup of the node. Note that all used modules should be
+%%% present in the code path on the <code>Host</code>.</p>
+%%%
+%%% <p>The timeouts are applied as follows:
+%%% <list>
+%%% <item>
+%%% <code>BootTimeout</code> - time to start the Erlang node, in seconds.
+%%% Defaults to 3 seconds. If node does not become pingable within this time,
+%%% the result <code>{error, boot_timeout, NodeName}</code> is returned;
+%%% </item>
+%%% <item>
+%%% <code>InitTimeout</code> - time to wait for the node until it calls the
+%%% internal callback function informing master about successfull startup.
+%%% Defaults to one second.
+%%% In case of timed out message the result
+%%% <code>{error, init_timeout, NodeName}</code> is returned;
+%%% </item>
+%%% <item>
+%%% <code>StartupTimeout</code> - time to wait intil the node finishes to run
+%%% the <code>StartupFunctions</code>. Defaults to one second.
+%%% If this timeout occurs, the result
+%%% <code>{error, startup_timeout, NodeName}</code> is returned.
+%%% </item>
+%%% </list></p>
+%%%
+%%% <p>Option <code>monitor_master</code> specifies, if the slave node should be
+%%% stopped in case of master node stop. Defaults to false.</p>
+%%%
+%%% <p>Option <code>kill_if_fail</code> specifies, if the slave node should be
+%%% killed in case of a timeout during initialization or startup.
+%%% Defaults to true. Note that node also may be still alive it the boot
+%%% timeout occurred, but it will not be killed in this case.</p>
+%%%
+%%% <p>Option <code>erlang_flags</code> specifies, which flags will be added
+%%% to the parameters of the <code>erl</code> executable.</p>
+%%%
+%%% <p>Special return values are:
+%%% <list>
+%%% <item><code>{error, already_started, NodeName}</code> - if the node with
+%%% the given name is already started on a given host;</item>
+%%% <item><code>{error, started_not_connected, NodeName}</code> - if node is
+%%% started, but not connected to the master node.</item>
+%%% <item><code>{error, not_alive, NodeName}</code> - if node on which the
+%%% <code>ct_slave:start/3</code> is called, is not alive. Note that
+%%% <code>NodeName</code> is the name of current node in this case.</item>
+%%% </list></p>
+%%%
+start(Host, Node, Options)->
+ ENode = enodename(Host, Node),
+ case erlang:is_alive() of
+ false->
+ {error, not_alive, node()};
+ true->
+ case is_started(ENode) of
+ false->
+ OptionsRec = fetch_options(Options),
+ do_start(Host, Node, OptionsRec);
+ {true, not_connected}->
+ {error, started_not_connected, ENode};
+ {true, connected}->
+ {error, already_started, ENode}
+ end
+ end.
+
+%%% @spec stop(Node) -> Result
+%%% Node = atom()
+%%% Result = {ok, NodeName} |
+%%% {error, not_started, NodeName} |
+%%% {error, not_connected, NodeName} |
+%%% {error, stop_timeout, NodeName}
+%%% NodeName = atom()
+%%% @doc Stops the running Erlang node with name <code>Node</code> on
+%%% the localhost.
+stop(Node)->
+ stop(gethostname(), Node).
+
+%%% @spec stop(Host, Node) -> Result
+%%% Host = atom()
+%%% Node = atom()
+%%% Result = {ok, NodeName} |
+%%% {error, not_started, NodeName} |
+%%% {error, not_connected, NodeName} |
+%%% {error, stop_timeout, NodeName}
+%%% NodeName = atom()
+%%% @doc Stops the running Erlang node with name <code>Node</code> on
+%%% host <code>Host</code>.
+stop(Host, Node)->
+ ENode = enodename(Host, Node),
+ case is_started(ENode) of
+ {true, connected}->
+ do_stop(ENode);
+ {true, not_connected}->
+ {error, not_connected, ENode};
+ false->
+ {error, not_started, ENode}
+ end.
+
+%%% fetch an option value from the tagged tuple list with default
+get_option_value(Key, OptionList, Default)->
+ case lists:keyfind(Key, 1, OptionList) of
+ false->
+ Default;
+ {Key, Value}->
+ Value
+ end.
+
+%%% convert option list to the option record, fill all defaults
+fetch_options(Options)->
+ UserName = get_option_value(username, Options, []),
+ Password = get_option_value(password, Options, []),
+ BootTimeout = get_option_value(boot_timeout, Options, 3),
+ InitTimeout = get_option_value(init_timeout, Options, 1),
+ StartupTimeout = get_option_value(startup_timeout, Options, 1),
+ StartupFunctions = get_option_value(startup_functions, Options, []),
+ Monitor = get_option_value(monitor_master, Options, false),
+ KillIfFail = get_option_value(kill_if_fail, Options, true),
+ ErlFlags = get_option_value(erl_flags, Options, []),
+ #options{username=UserName, password=Password,
+ boot_timeout=BootTimeout, init_timeout=InitTimeout,
+ startup_timeout=StartupTimeout, startup_functions=StartupFunctions,
+ monitor_master=Monitor, kill_if_fail=KillIfFail, erl_flags=ErlFlags}.
+
+% send a message when slave node is started
+% @hidden
+slave_started(ENode, MasterPid)->
+ MasterPid ! {node_started, ENode},
+ ok.
+
+% send a message when slave node has finished startup
+% @hidden
+slave_ready(ENode, MasterPid)->
+ MasterPid ! {node_ready, ENode},
+ ok.
+
+% start monitoring of the master node
+% @hidden
+monitor_master(MasterNode)->
+ spawn(fun()->monitor_master_int(MasterNode) end).
+
+% code of the masterdeath-waiter process
+monitor_master_int(MasterNode)->
+ erlang:monitor_node(MasterNode, true),
+ receive
+ {nodedown, MasterNode}->
+ init:stop()
+ end.
+
+% check if node is listed in the nodes()
+is_connected(ENode)->
+ [N||N<-nodes(), N==ENode] == [ENode].
+
+% check if node is alive (ping and disconnect if pingable)
+is_started(ENode)->
+ case is_connected(ENode) of
+ true->
+ {true, connected};
+ false->
+ case net_adm:ping(ENode) of
+ pang->
+ false;
+ pong->
+ erlang:disconnect_node(ENode),
+ {true, not_connected}
+ end
+ end.
+
+% make a Erlang node name from name and hostname
+enodename(Host, Node)->
+ list_to_atom(atom_to_list(Node)++"@"++atom_to_list(Host)).
+
+% performs actual start of the "slave" node
+do_start(Host, Node, Options)->
+ ENode = enodename(Host, Node),
+ Functions =
+ lists:concat([[{ct_slave, slave_started, [ENode, self()]}],
+ Options#options.startup_functions,
+ [{ct_slave, slave_ready, [ENode, self()]}]]),
+ Functions2 = if
+ Options#options.monitor_master->
+ [{ct_slave, monitor_master, [node()]}|Functions];
+ true->
+ Functions
+ end,
+ MasterHost = gethostname(),
+ if
+ MasterHost == Host ->
+ spawn_local_node(Node, Options);
+ true->
+ spawn_remote_node(Host, Node, Options)
+ end,
+ BootTimeout = Options#options.boot_timeout,
+ InitTimeout = Options#options.init_timeout,
+ StartupTimeout = Options#options.startup_timeout,
+ Result = case wait_for_node_alive(ENode, BootTimeout) of
+ pong->
+ call_functions(ENode, Functions2),
+ receive
+ {node_started, ENode}->
+ receive
+ {node_ready, ENode}->
+ {ok, ENode}
+ after StartupTimeout*1000->
+ {error, startup_timeout, ENode}
+ end
+ after InitTimeout*1000 ->
+ {error, init_timeout, ENode}
+ end;
+ pang->
+ {error, boot_timeout, ENode}
+ end,
+ case Result of
+ {ok, ENode}->
+ ok;
+ {error, Timeout, ENode}
+ when ((Timeout==init_timeout) or (Timeout==startup_timeout)) and
+ Options#options.kill_if_fail->
+ do_stop(ENode);
+ _-> ok
+ end,
+ Result.
+
+% are we using fully qualified hostnames
+long_or_short()->
+ case net_kernel:longnames() of
+ true->
+ " -name ";
+ false->
+ " -sname "
+ end.
+
+% get the localhost's name, depending on the using name policy
+gethostname()->
+ Hostname = case net_kernel:longnames() of
+ true->
+ net_adm:localhost();
+ _->
+ {ok, Name}=inet:gethostname(),
+ Name
+ end,
+ list_to_atom(Hostname).
+
+% get cmd for starting Erlang
+get_cmd(Node, Flags)->
+ Cookie = erlang:get_cookie(),
+ "erl -detached -noinput -setcookie "++ atom_to_list(Cookie) ++
+ long_or_short() ++ atom_to_list(Node) ++ " " ++ Flags.
+
+% spawn node locally
+spawn_local_node(Node, Options)->
+ ErlFlags = Options#options.erl_flags,
+ Cmd = get_cmd(Node, ErlFlags),
+ open_port({spawn, Cmd}, [stream]).
+
+% start crypto and ssh if not yet started
+check_for_ssh_running()->
+ case application:get_application(crypto) of
+ undefined->
+ application:start(crypto),
+ case application:get_application(ssh) of
+ undefined->
+ application:start(ssh);
+ {ok, ssh}->
+ ok
+ end;
+ {ok, crypto}->
+ ok
+ end.
+
+% spawn node remotely
+spawn_remote_node(Host, Node, Options)->
+ Username = Options#options.username,
+ Password = Options#options.password,
+ ErlFlags = Options#options.erl_flags,
+ SSHOptions = case {Username, Password} of
+ {[], []}->
+ [];
+ {_, []}->
+ [{user, Username}];
+ {_, _}->
+ [{user, Username}, {password, Password}]
+ end ++ [{silently_accept_hosts, true}],
+ check_for_ssh_running(),
+ {ok, SSHConnRef} = ssh:connect(atom_to_list(Host), 22, SSHOptions),
+ {ok, SSHChannelId} = ssh_connection:session_channel(SSHConnRef, infinity),
+ ssh_connection:exec(SSHConnRef, SSHChannelId, get_cmd(Node, ErlFlags), infinity).
+
+% call functions on a remote Erlang node
+call_functions(_Node, [])->
+ ok;
+call_functions(Node, [{M, F, A}|Functions])->
+ rpc:call(Node, M, F, A),
+ call_functions(Node, Functions).
+
+% wait N seconds until node is pingable
+wait_for_node_alive(_Node, 0)->
+ pang;
+wait_for_node_alive(Node, N)->
+ timer:sleep(1000),
+ case net_adm:ping(Node) of
+ pong->
+ pong;
+ pang->
+ wait_for_node_alive(Node, N-1)
+ end.
+
+% call init:stop on a remote node
+do_stop(ENode)->
+ spawn(ENode, init, stop, []),
+ wait_for_node_dead(ENode, 5).
+
+% wait N seconds until node is disconnected
+wait_for_node_dead(Node, 0)->
+ {error, stop_timeout, Node};
+wait_for_node_dead(Node, N)->
+ timer:sleep(1000),
+ case lists:member(Node, nodes()) of
+ true->
+ wait_for_node_dead(Node, N-1);
+ false->
+ {ok, Node}
+ end.
diff --git a/lib/common_test/src/ct_snmp.erl b/lib/common_test/src/ct_snmp.erl
index 7ff88ad7d3..8fe63e8ed1 100644
--- a/lib/common_test/src/ct_snmp.erl
+++ b/lib/common_test/src/ct_snmp.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
@@ -332,7 +332,7 @@ set_info(Config) ->
register_users(MgrAgentConfName, Users) ->
{snmp, SnmpVals} = ct:get_config(MgrAgentConfName),
NewSnmpVals = lists:keyreplace(users, 1, SnmpVals, {users, Users}),
- ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
+ ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
setup_users(Users).
%%% @spec register_agents(MgrAgentConfName, ManagedAgents) -> ok | {error, Reason}
@@ -347,7 +347,7 @@ register_agents(MgrAgentConfName, ManagedAgents) ->
{snmp, SnmpVals} = ct:get_config(MgrAgentConfName),
NewSnmpVals = lists:keyreplace(managed_agents, 1, SnmpVals,
{managed_agents, ManagedAgents}),
- ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
+ ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
setup_managed_agents(ManagedAgents).
%%% @spec register_usm_users(MgrAgentConfName, UsmUsers) -> ok | {error, Reason}
@@ -361,7 +361,7 @@ register_agents(MgrAgentConfName, ManagedAgents) ->
register_usm_users(MgrAgentConfName, UsmUsers) ->
{snmp, SnmpVals} = ct:get_config(MgrAgentConfName),
NewSnmpVals = lists:keyreplace(users, 1, SnmpVals, {usm_users, UsmUsers}),
- ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
+ ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
EngineID = ct:get_config({MgrAgentConfName, engine_id}, ?ENGINE_ID),
setup_usm_users(UsmUsers, EngineID).
@@ -376,7 +376,7 @@ unregister_users(MgrAgentConfName) ->
ct:get_config({MgrAgentConfName, users})),
{snmp, SnmpVals} = ct:get_config(MgrAgentConfName),
NewSnmpVals = lists:keyreplace(users, 1, SnmpVals, {users, []}),
- ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
+ ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
takedown_users(Users).
%%% @spec unregister_agents(MgrAgentConfName) -> ok | {error, Reason}
@@ -393,7 +393,7 @@ unregister_agents(MgrAgentConfName) ->
{snmp, SnmpVals} = ct:get_config(MgrAgentConfName),
NewSnmpVals = lists:keyreplace(managed_agents, 1, SnmpVals,
{managed_agents, []}),
- ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
+ ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
takedown_managed_agents(ManagedAgents).
@@ -409,7 +409,7 @@ update_usm_users(MgrAgentConfName, UsmUsers) ->
{snmp, SnmpVals} = ct:get_config(MgrAgentConfName),
NewSnmpVals = lists:keyreplace(usm_users, 1, SnmpVals,
{usm_users, UsmUsers}),
- ct_util:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
+ ct_config:update_config(MgrAgentConfName, {snmp, NewSnmpVals}),
EngineID = ct:get_config({MgrAgentConfName, engine_id}, ?ENGINE_ID),
do_update_usm_users(UsmUsers, EngineID).
diff --git a/lib/common_test/src/ct_ssh.erl b/lib/common_test/src/ct_ssh.erl
index f2b25b1fcd..aebb28bc42 100644
--- a/lib/common_test/src/ct_ssh.erl
+++ b/lib/common_test/src/ct_ssh.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -961,24 +961,25 @@ init(KeyOrName, {ConnType,Addr,Port}, AllOpts) ->
ssh ->
ssh:connect(Addr, Port, FinalOptions);
sftp ->
- ssh_sftp:connect(Addr, Port, FinalOptions)
+ ssh_sftp:start_channel(Addr, Port, FinalOptions)
end,
case Result of
- {ok,SSHRef} ->
+ Error = {error,_} ->
+ Error;
+ Ok ->
+ SSHRef = element(2, Ok),
log(heading(init,KeyOrName),
"Opened ~w connection:\nHost: ~p (~p)\nUser: ~p\nPassword: ~p\n",
[ConnType,Addr,Port,User,lists:duplicate(length(Password),$*)]),
{ok,SSHRef,#state{ssh_ref=SSHRef, conn_type=ConnType,
- target=KeyOrName}};
- Error ->
- Error
+ target=KeyOrName}}
end.
%% @hidden
handle_msg(sftp_connect, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
log(heading(sftp_connect,Target), "SSH Ref: ~p", [SSHRef]),
- {ssh_sftp:connect(SSHRef),State};
+ {ssh_sftp:start_channel(SSHRef),State};
handle_msg({session_open,TO}, State) ->
#state{ssh_ref=SSHRef, target=Target} = State,
@@ -1202,7 +1203,7 @@ terminate(SSHRef, State) ->
sftp ->
log(heading(disconnect_sftp,State#state.target),
"SFTP Ref: ~p",[SSHRef]),
- ssh_sftp:stop(SSHRef)
+ ssh_sftp:stop_channel(SSHRef)
end.
@@ -1213,7 +1214,6 @@ terminate(SSHRef, State) ->
%%%
do_recv_response(SSH, Chn, Data, End, Timeout) ->
receive
-
{ssh_cm, SSH, {open,Chn,RemoteChn,{session}}} ->
debug("RECVD open"),
{ok,{open,Chn,RemoteChn,{session}}};
diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl
index 1a12c5e343..d703b39ac5 100644
--- a/lib/common_test/src/ct_telnet_client.erl
+++ b/lib/common_test/src/ct_telnet_client.erl
@@ -35,8 +35,6 @@
-export([open/1, open/2, open/3, open/4, close/1]).
-export([send_data/2, get_data/1]).
--define(DBG, false).
-
-define(TELNET_PORT, 23).
-define(OPEN_TIMEOUT,10000).
-define(IDLE_TIMEOUT,10000).
@@ -287,35 +285,38 @@ get_subcmd([?SE | Rest], Acc) ->
get_subcmd([Opt | Rest], Acc) ->
get_subcmd(Rest, [Opt | Acc]).
-
+-ifdef(debug).
dbg(_Str,_Args) ->
- if ?DBG -> io:format(_Str,_Args);
- true -> ok
+ io:format(_Str,_Args).
+
+cmd_dbg(_Cmd) ->
+ case _Cmd of
+ [?IAC|Cmd1] ->
+ cmd_dbg(Cmd1);
+ [Ctrl|Opts] ->
+ CtrlStr =
+ case Ctrl of
+ ?DO -> "DO";
+ ?DONT -> "DONT";
+ ?WILL -> "WILL";
+ ?WONT -> "WONT";
+ ?NOP -> "NOP";
+ _ -> "CMD"
+ end,
+ Opts1 =
+ case Opts of
+ [Opt] -> Opt;
+ _ -> Opts
+ end,
+ io:format("~s(~w): ~w\n", [CtrlStr,Ctrl,Opts1]);
+ Any ->
+ io:format("Unexpected in cmd_dbg:~n~w~n",[Any])
end.
+-else.
+dbg(_Str,_Args) ->
+ ok.
+
cmd_dbg(_Cmd) ->
- if ?DBG ->
- case _Cmd of
- [?IAC|Cmd1] ->
- cmd_dbg(Cmd1);
- [Ctrl|Opts] ->
- CtrlStr =
- case Ctrl of
- ?DO -> "DO";
- ?DONT -> "DONT";
- ?WILL -> "WILL";
- ?WONT -> "WONT";
- ?NOP -> "NOP";
- _ -> "CMD"
- end,
- Opts1 =
- case Opts of
- [Opt] -> Opt;
- _ -> Opts
- end,
- io:format("~s(~w): ~w\n", [CtrlStr,Ctrl,Opts1]);
- Any ->
- io:format("Unexpected in cmd_dbg:~n~w~n",[Any])
- end;
- true -> ok
- end.
+ ok.
+-endif.
diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl
index 4378ec5a52..0f68b062f6 100644
--- a/lib/common_test/src/ct_testspec.erl
+++ b/lib/common_test/src/ct_testspec.erl
@@ -17,7 +17,7 @@
%% %CopyrightEnd%
%%
-%%% @doc Common Test Framework functions handlig test specifikations.
+%%% @doc Common Test Framework functions handling test specifications.
%%%
%%% <p>This module exports functions that are used within CT to
%%% scan and parse test specifikations.</p>
@@ -270,33 +270,8 @@ collect_tests(Terms,TestSpec,Relaxed) ->
put(relaxed,Relaxed),
TestSpec1 = get_global(Terms,TestSpec),
TestSpec2 = get_all_nodes(Terms,TestSpec1),
- case catch evaluate(Terms,TestSpec2) of
- {error,{Node,{M,F,A},Reason}} ->
- io:format("Error! Common Test failed to evaluate ~w:~w/~w on ~w. "
- "Reason: ~p~n~n", [M,F,A,Node,Reason]);
- _ -> ok
- end,
- add_tests(Terms,TestSpec2).
-
-evaluate([{eval,NodeRef,{M,F,Args}}|Ts],Spec) ->
- Node = ref2node(NodeRef,Spec#testspec.nodes),
- case rpc:call(Node,M,F,Args) of
- {badrpc,Reason} ->
- throw({error,{Node,{M,F,length(Args)},Reason}});
- _ ->
- ok
- end,
- evaluate(Ts,Spec);
-evaluate([{eval,{M,F,Args}}|Ts],Spec) ->
- case catch apply(M,F,Args) of
- {'EXIT',Reason} ->
- throw({error,{node(),{M,F,length(Args)},Reason}});
- _ ->
- ok
- end,
- evaluate(Ts,Spec);
-evaluate([],_Spec) ->
- ok.
+ {Terms2, TestSpec3} = filter_init_terms(Terms, [], TestSpec2),
+ add_tests(Terms2,TestSpec3).
get_global([{alias,Ref,Dir}|Ts],Spec=#testspec{alias=Refs}) ->
get_global(Ts,Spec#testspec{alias=[{Ref,get_absdir(Dir,Spec)}|Refs]});
@@ -305,6 +280,26 @@ get_global([{node,Ref,Node}|Ts],Spec=#testspec{nodes=Refs}) ->
get_global([_|Ts],Spec) -> get_global(Ts,Spec);
get_global([],Spec) -> Spec.
+get_absfile(Callback, FullName,#testspec{spec_dir=SpecDir}) ->
+ % we need to temporary switch to new cwd here, because
+ % otherwise config files cannot be found
+ {ok, OldWd} = file:get_cwd(),
+ ok = file:set_cwd(SpecDir),
+ R = Callback:check_parameter(FullName),
+ ok = file:set_cwd(OldWd),
+ case R of
+ {ok, {file, FullName}}->
+ File = filename:basename(FullName),
+ Dir = get_absname(filename:dirname(FullName),SpecDir),
+ filename:join(Dir,File);
+ {ok, {config, FullName}}->
+ FullName;
+ {error, {nofile, FullName}}->
+ FullName;
+ {error, {wrong_config, FullName}}->
+ FullName
+ end.
+
get_absfile(FullName,#testspec{spec_dir=SpecDir}) ->
File = filename:basename(FullName),
Dir = get_absname(filename:dirname(FullName),SpecDir),
@@ -353,6 +348,68 @@ get_all_nodes([_|Ts],Spec) ->
get_all_nodes([],Spec) ->
Spec.
+filter_init_terms([{init, InitOptions}|Ts], NewTerms, Spec)->
+ filter_init_terms([{init, list_nodes(Spec), InitOptions}|Ts], NewTerms, Spec);
+filter_init_terms([{init, NodeRef, InitOptions}|Ts], NewTerms, Spec)
+ when is_atom(NodeRef)->
+ filter_init_terms([{init, [NodeRef], InitOptions}|Ts], NewTerms, Spec);
+filter_init_terms([{init, NodeRefs, InitOption}|Ts], NewTerms, Spec) when is_tuple(InitOption) ->
+ filter_init_terms([{init, NodeRefs, [InitOption]}|Ts], NewTerms, Spec);
+filter_init_terms([{init, [NodeRef|NodeRefs], InitOptions}|Ts], NewTerms, Spec=#testspec{init=InitData})->
+ NodeStartOptions = case lists:keyfind(node_start, 1, InitOptions) of
+ {node_start, NSOptions}->
+ case lists:keyfind(callback_module, 1, NSOptions) of
+ {callback_module, _Callback}->
+ NSOptions;
+ false->
+ [{callback_module, ct_slave}|NSOptions]
+ end;
+ false->
+ []
+ end,
+ EvalTerms = case lists:keyfind(eval, 1, InitOptions) of
+ {eval, MFA} when is_tuple(MFA)->
+ [MFA];
+ {eval, MFAs} when is_list(MFAs)->
+ MFAs;
+ false->
+ []
+ end,
+ Node = ref2node(NodeRef,Spec#testspec.nodes),
+ InitData2 = add_option({node_start, NodeStartOptions}, Node, InitData, true),
+ InitData3 = add_option({eval, EvalTerms}, Node, InitData2, false),
+ filter_init_terms([{init, NodeRefs, InitOptions}|Ts], NewTerms, Spec#testspec{init=InitData3});
+filter_init_terms([{init, [], _}|Ts], NewTerms, Spec)->
+ filter_init_terms(Ts, NewTerms, Spec);
+filter_init_terms([Term|Ts], NewTerms, Spec)->
+ filter_init_terms(Ts, [Term|NewTerms], Spec);
+filter_init_terms([], NewTerms, Spec)->
+ {lists:reverse(NewTerms), Spec}.
+
+add_option([], _, List, _)->
+ List;
+add_option({Key, Value}, Node, List, WarnIfExists) when is_list(Value)->
+ OldOptions = case lists:keyfind(Node, 1, List) of
+ {Node, Options}->
+ Options;
+ false->
+ []
+ end,
+ NewOption = case lists:keyfind(Key, 1, OldOptions) of
+ {Key, OldOption} when WarnIfExists, OldOption/=[]->
+ io:format("There is an option ~w=~w already defined for node ~p, skipping new ~w~n",
+ [Key, OldOption, Node, Value]),
+ OldOption;
+ {Key, OldOption}->
+ OldOption ++ Value;
+ false->
+ Value
+ end,
+ lists:keystore(Node, 1, List,
+ {Node, lists:keystore(Key, 1, OldOptions, {Key, NewOption})});
+add_option({Key, Value}, Node, List, WarnIfExists)->
+ add_option({Key, [Value]}, Node, List, WarnIfExists).
+
save_nodes(Nodes,Spec=#testspec{nodes=NodeRefs}) ->
NodeRefs1 =
lists:foldr(fun(all_nodes,NR) ->
@@ -415,6 +472,36 @@ add_tests([{cover,Node,File}|Ts],Spec) ->
add_tests([{cover,File}|Ts],Spec) ->
add_tests([{cover,all_nodes,File}|Ts],Spec);
+%% --- multiply_timetraps ---
+add_tests([{multiply_timetraps,all_nodes,MT}|Ts],Spec) ->
+ Tests = lists:map(fun(N) -> {multiply_timetraps,N,MT} end, list_nodes(Spec)),
+ add_tests(Tests++Ts,Spec);
+add_tests([{multiply_timetraps,Nodes,MT}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,multiply_timetraps,[MT],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{multiply_timetraps,Node,MT}|Ts],Spec) ->
+ MTs = Spec#testspec.multiply_timetraps,
+ MTs1 = [{ref2node(Node,Spec#testspec.nodes),MT} |
+ lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,MTs)],
+ add_tests(Ts,Spec#testspec{multiply_timetraps=MTs1});
+add_tests([{multiply_timetraps,MT}|Ts],Spec) ->
+ add_tests([{multiply_timetraps,all_nodes,MT}|Ts],Spec);
+
+%% --- scale_timetraps ---
+add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec) ->
+ Tests = lists:map(fun(N) -> {scale_timetraps,N,ST} end, list_nodes(Spec)),
+ add_tests(Tests++Ts,Spec);
+add_tests([{scale_timetraps,Nodes,ST}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,scale_timetraps,[ST],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{scale_timetraps,Node,ST}|Ts],Spec) ->
+ STs = Spec#testspec.scale_timetraps,
+ STs1 = [{ref2node(Node,Spec#testspec.nodes),ST} |
+ lists:keydelete(ref2node(Node,Spec#testspec.nodes),1,STs)],
+ add_tests(Ts,Spec#testspec{scale_timetraps=STs1});
+add_tests([{scale_timetraps,ST}|Ts],Spec) ->
+ add_tests([{scale_timetraps,all_nodes,ST}|Ts],Spec);
+
%% --- config ---
add_tests([{config,all_nodes,Files}|Ts],Spec) ->
Tests = lists:map(fun(N) -> {config,N,Files} end, list_nodes(Spec)),
@@ -434,6 +521,27 @@ add_tests([{config,Node,F}|Ts],Spec) ->
add_tests([{config,Files}|Ts],Spec) ->
add_tests([{config,all_nodes,Files}|Ts],Spec);
+
+%% --- userconfig ---
+add_tests([{userconfig,all_nodes,CBF}|Ts],Spec) ->
+ Tests = lists:map(fun(N) -> {userconfig,N,CBF} end, list_nodes(Spec)),
+ add_tests(Tests++Ts,Spec);
+add_tests([{userconfig,Nodes,CBF}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,userconfig,[CBF],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{userconfig,Node,[{Callback, Config}|CBF]}|Ts],Spec) ->
+ Cfgs = Spec#testspec.userconfig,
+ Node1 = ref2node(Node,Spec#testspec.nodes),
+ add_tests([{userconfig,Node,CBF}|Ts],
+ Spec#testspec{userconfig=[{Node1,{Callback,
+ get_absfile(Callback, Config ,Spec)}}|Cfgs]});
+add_tests([{userconfig,_Node,[]}|Ts],Spec) ->
+ add_tests(Ts,Spec);
+add_tests([{userconfig,Node,CBF}|Ts],Spec) ->
+ add_tests([{userconfig,Node,[CBF]}|Ts],Spec);
+add_tests([{userconfig,CBF}|Ts],Spec) ->
+ add_tests([{userconfig,all_nodes,CBF}|Ts],Spec);
+
%% --- event_handler ---
add_tests([{event_handler,all_nodes,Hs}|Ts],Spec) ->
Tests = lists:map(fun(N) -> {event_handler,N,Hs,[]} end, list_nodes(Spec)),
@@ -516,6 +624,38 @@ add_tests([{suites,Node,Dir,Ss}|Ts],Spec) ->
Ss,Tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
+%% --- groups ---
+%% Later make it possible to specify group execution properties
+%% that will override thse in the suite. Also make it possible
+%% create dynamic groups in specification, i.e. to group test cases
+%% by means of groups defined only in the test specification.
+add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec) ->
+ add_tests([{groups,list_nodes(Spec),Dir,Suite,Gs}|Ts],Spec);
+add_tests([{groups,all_nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
+ add_tests([{groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs}}|Ts],Spec);
+add_tests([{groups,Dir,Suite,Gs}|Ts],Spec) ->
+ add_tests([{groups,all_nodes,Dir,Suite,Gs}|Ts],Spec);
+add_tests([{groups,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
+ add_tests([{groups,all_nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec);
+add_tests([{groups,Nodes,Dir,Suite,Gs}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,groups,[Dir,Suite,Gs],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{groups,Nodes,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,groups,[Dir,Suite,Gs,{cases,TCs}],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{groups,Node,Dir,Suite,Gs}|Ts],Spec) ->
+ Tests = Spec#testspec.tests,
+ Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes),
+ ref2dir(Dir,Spec#testspec.alias),
+ Suite,Gs,all,Tests),
+ add_tests(Ts,Spec#testspec{tests=Tests1});
+add_tests([{groups,Node,Dir,Suite,Gs,{cases,TCs}}|Ts],Spec) ->
+ Tests = Spec#testspec.tests,
+ Tests1 = insert_groups(ref2node(Node,Spec#testspec.nodes),
+ ref2dir(Dir,Spec#testspec.alias),
+ Suite,Gs,TCs,Tests),
+ add_tests(Ts,Spec#testspec{tests=Tests1});
+
%% --- cases ---
add_tests([{cases,all_nodes,Dir,Suite,Cs}|Ts],Spec) ->
add_tests([{cases,list_nodes(Spec),Dir,Suite,Cs}|Ts],Spec);
@@ -546,6 +686,34 @@ add_tests([{skip_suites,Node,Dir,Ss,Cmt}|Ts],Spec) ->
Ss,Cmt,Tests),
add_tests(Ts,Spec#testspec{tests=Tests1});
+%% --- skip_groups ---
+add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
+ add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,Cmt}|Ts],Spec);
+add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
+ add_tests([{skip_groups,list_nodes(Spec),Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec);
+add_tests([{skip_groups,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
+ add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,Cmt}|Ts],Spec);
+add_tests([{skip_groups,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
+ add_tests([{skip_groups,all_nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec);
+add_tests([{skip_groups,Nodes,Dir,Suite,Gs,Cmt}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,Cmt],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{skip_groups,Nodes,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) when is_list(Nodes) ->
+ Ts1 = separate(Nodes,skip_groups,[Dir,Suite,Gs,{cases,TCs},Cmt],Ts,Spec#testspec.nodes),
+ add_tests(Ts1,Spec);
+add_tests([{skip_groups,Node,Dir,Suite,Gs,Cmt}|Ts],Spec) ->
+ Tests = Spec#testspec.tests,
+ Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes),
+ ref2dir(Dir,Spec#testspec.alias),
+ Suite,Gs,all,Cmt,Tests),
+ add_tests(Ts,Spec#testspec{tests=Tests1});
+add_tests([{skip_groups,Node,Dir,Suite,Gs,{cases,TCs},Cmt}|Ts],Spec) ->
+ Tests = Spec#testspec.tests,
+ Tests1 = skip_groups(ref2node(Node,Spec#testspec.nodes),
+ ref2dir(Dir,Spec#testspec.alias),
+ Suite,Gs,TCs,Cmt,Tests),
+ add_tests(Ts,Spec#testspec{tests=Tests1});
+
%% --- skip_cases ---
add_tests([{skip_cases,all_nodes,Dir,Suite,Cs,Cmt}|Ts],Spec) ->
add_tests([{skip_cases,list_nodes(Spec),Dir,Suite,Cs,Cmt}|Ts],Spec);
@@ -614,8 +782,11 @@ separate([],_,_,_) ->
%% Representation:
-%% {{Node,Dir},[{Suite1,[case11,case12,...]},{Suite2,[case21,case22,...]},...]}
-%% {{Node,Dir},[{Suite1,{skip,Cmt}},{Suite2,[{case21,{skip,Cmt}},case22,...]},...]}
+%% {{Node,Dir},[{Suite1,[GrOrCase11,GrOrCase12,...]},
+%% {Suite2,[GrOrCase21,GrOrCase22,...]},...]}
+%% {{Node,Dir},[{Suite1,{skip,Cmt}},
+%% {Suite2,[{GrOrCase21,{skip,Cmt}},GrOrCase22,...]},...]}
+%% GrOrCase = {GroupName,[Case1,Case2,...]} | Case
insert_suites(Node,Dir,[S|Ss],Tests) ->
Tests1 = insert_cases(Node,Dir,S,all,Tests),
@@ -625,6 +796,54 @@ insert_suites(_Node,_Dir,[],Tests) ->
insert_suites(Node,Dir,S,Tests) ->
insert_suites(Node,Dir,[S],Tests).
+insert_groups(Node,Dir,Suite,Group,Cases,Tests) when is_atom(Group) ->
+ insert_groups(Node,Dir,Suite,[Group],Cases,Tests);
+insert_groups(Node,Dir,Suite,Groups,Cases,Tests) when
+ ((Cases == all) or is_list(Cases)) and is_list(Groups) ->
+ case lists:keysearch({Node,Dir},1,Tests) of
+ {value,{{Node,Dir},[{all,_}]}} ->
+ Tests;
+ {value,{{Node,Dir},Suites0}} ->
+ Suites1 = insert_groups1(Suite,
+ [{Gr,Cases} || Gr <- Groups],
+ Suites0),
+ insert_in_order({{Node,Dir},Suites1},Tests);
+ false ->
+ Groups1 = [{Gr,Cases} || Gr <- Groups],
+ insert_in_order({{Node,Dir},[{Suite,Groups1}]},Tests)
+ end;
+insert_groups(Node,Dir,Suite,Groups,Case,Tests) when is_atom(Case) ->
+ Cases = if Case == all -> all; true -> [Case] end,
+ insert_groups(Node,Dir,Suite,Groups,Cases,Tests).
+
+insert_groups1(_Suite,_Groups,all) ->
+ all;
+insert_groups1(Suite,Groups,Suites0) ->
+ case lists:keysearch(Suite,1,Suites0) of
+ {value,{Suite,all}} ->
+ Suites0;
+ {value,{Suite,GrAndCases0}} ->
+ GrAndCases = insert_groups2(Groups,GrAndCases0),
+ insert_in_order({Suite,GrAndCases},Suites0);
+ false ->
+ insert_in_order({Suite,Groups},Suites0)
+ end.
+
+insert_groups2(_Groups,all) ->
+ all;
+insert_groups2([Group={GrName,Cases}|Groups],GrAndCases) ->
+ case lists:keysearch(GrName,1,GrAndCases) of
+ {value,{GrName,all}} ->
+ GrAndCases;
+ {value,{GrName,Cases0}} ->
+ Cases1 = insert_in_order(Cases,Cases0),
+ insert_groups2(Groups,insert_in_order({GrName,Cases1},GrAndCases));
+ false ->
+ insert_groups2(Groups,insert_in_order(Group,GrAndCases))
+ end;
+insert_groups2([],GrAndCases) ->
+ GrAndCases.
+
insert_cases(Node,Dir,Suite,Cases,Tests) when is_list(Cases) ->
case lists:keysearch({Node,Dir},1,Tests) of
{value,{{Node,Dir},[{all,_}]}} ->
@@ -659,6 +878,35 @@ skip_suites(_Node,_Dir,[],_Cmt,Tests) ->
skip_suites(Node,Dir,S,Cmt,Tests) ->
skip_suites(Node,Dir,[S],Cmt,Tests).
+skip_groups(Node,Dir,Suite,Group,Case,Cmt,Tests) when is_atom(Group) ->
+ skip_groups(Node,Dir,Suite,[Group],[Case],Cmt,Tests);
+skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests) when
+ ((Cases == all) or is_list(Cases)) and is_list(Groups) ->
+ Suites =
+ case lists:keysearch({Node,Dir},1,Tests) of
+ {value,{{Node,Dir},Suites0}} ->
+ Suites0;
+ false ->
+ []
+ end,
+ Suites1 = skip_groups1(Suite,[{Gr,Cases} || Gr <- Groups],Cmt,Suites),
+ insert_in_order({{Node,Dir},Suites1},Tests);
+skip_groups(Node,Dir,Suite,Groups,Case,Cmt,Tests) when is_atom(Case) ->
+ Cases = if Case == all -> all; true -> [Case] end,
+ skip_groups(Node,Dir,Suite,Groups,Cases,Cmt,Tests).
+
+skip_groups1(Suite,Groups,Cmt,Suites0) ->
+ SkipGroups = lists:map(fun(Group) ->
+ {Group,{skip,Cmt}}
+ end,Groups),
+ case lists:keysearch(Suite,1,Suites0) of
+ {value,{Suite,GrAndCases0}} ->
+ GrAndCases1 = GrAndCases0 ++ SkipGroups,
+ insert_in_order({Suite,GrAndCases1},Suites0);
+ false ->
+ insert_in_order({Suite,SkipGroups},Suites0)
+ end.
+
skip_cases(Node,Dir,Suite,Cases,Cmt,Tests) when is_list(Cases) ->
Suites =
case lists:keysearch({Node,Dir},1,Tests) of
@@ -753,6 +1001,8 @@ valid_terms() ->
{cover,3},
{config,2},
{config,3},
+ {userconfig, 2},
+ {userconfig, 3},
{alias,3},
{logdir,2},
{logdir,3},
@@ -761,7 +1011,6 @@ valid_terms() ->
{event_handler,4},
{include,2},
{include,3},
-
{suites,3},
{suites,4},
{cases,4},
@@ -816,7 +1065,3 @@ common_letters([L|Ls],Term,Count) ->
end;
common_letters([],_,Count) ->
Count.
-
-
-
-
diff --git a/lib/common_test/src/ct_util.erl b/lib/common_test/src/ct_util.erl
index ba3d789f8d..eddaf4c8b9 100644
--- a/lib/common_test/src/ct_util.erl
+++ b/lib/common_test/src/ct_util.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -30,10 +30,7 @@
-export([register_connection/4,unregister_connection/1,
does_connection_exist/3,get_key_from_name/1]).
--export([require/1, require/2, get_config/1, get_config/2, get_config/3,
- set_default_config/2, set_default_config/3, delete_default_config/1,
- get_all_config/0, update_config/2,
- release_allocated/0, close_connections/0]).
+-export([close_connections/0]).
-export([save_suite_data/3, save_suite_data/2, read_suite_data/1,
delete_suite_data/0, delete_suite_data/1, match_delete_suite_data/1,
@@ -46,6 +43,8 @@
silence_all_connections/0, silence_connections/1, is_silenced/1,
reset_silent_connections/0]).
+-export([get_mode/0, create_table/3, read_opts/0]).
+
-export([set_cwd/1, reset_cwd/0]).
-export([parse_table/1]).
@@ -56,23 +55,15 @@
-export([is_test_dir/1, get_testdir/2]).
--export([encrypt_config_file/2, encrypt_config_file/3,
- decrypt_config_file/2, decrypt_config_file/3]).
-
--export([kill_attached/2, get_attached/1]).
+-export([kill_attached/2, get_attached/1, ct_make_ref/0]).
-export([warn_duplicates/1]).
-include("ct_event.hrl").
-include("ct_util.hrl").
--record(ct_conf,{key,value,ref,name='_UNDEF',default=false}).
-%% default = {true,suite} | {true,testcase} | false
-
-record(suite_data, {key,name,value}).
--define(cryptfile, ".ct_config.crypt").
-
%%%-----------------------------------------------------------------
%%% @spec start(Mode) -> Pid | exit(Error)
%%% Mode = normal | interactive
@@ -119,7 +110,6 @@ start(Mode,LogDir) ->
do_start(Parent,Mode,LogDir) ->
process_flag(trap_exit,true),
register(ct_util_server,self()),
- create_table(?attr_table,bag,#ct_conf.key),
create_table(?conn_table,#conn.handle),
create_table(?board_table,2),
create_table(?suite_table,#suite_data.key),
@@ -135,7 +125,6 @@ do_start(Parent,Mode,LogDir) ->
Parent ! {self(),Error},
exit(Error)
end,
-
%% start an event manager (if not already started by master)
case ct_event:start_link() of
{error,{already_started,_}} ->
@@ -148,38 +137,34 @@ do_start(Parent,Mode,LogDir) ->
ct_event:add_handler([{vts,VtsPid}])
end
end,
- case read_config_files(Opts) of
- ok ->
- %% add user handlers
- case lists:keysearch(event_handler,1,Opts) of
- {value,{_,Handlers}} ->
- Add = fun({H,Args}) ->
- case catch gen_event:add_handler(?CT_EVMGR_REF,H,Args) of
- ok -> ok;
- {'EXIT',Why} -> exit(Why);
- Other -> exit({event_handler,Other})
- end
- end,
- case catch lists:foreach(Add,Handlers) of
- {'EXIT',Reason} ->
- Parent ! {self(),Reason};
- _ ->
- ok
- end;
- false ->
+ %% start ct_config server
+ ct_config:start(Mode),
+ %% add user event handlers
+ case lists:keysearch(event_handler,1,Opts) of
+ {value,{_,Handlers}} ->
+ Add = fun({H,Args}) ->
+ case catch gen_event:add_handler(?CT_EVMGR_REF,H,Args) of
+ ok -> ok;
+ {'EXIT',Why} -> exit(Why);
+ Other -> exit({event_handler,Other})
+ end
+ end,
+ case catch lists:foreach(Add,Handlers) of
+ {'EXIT',Reason} ->
+ Parent ! {self(),Reason};
+ _ ->
ok
- end,
- {StartTime,TestLogDir} = ct_logs:init(Mode),
- ct_event:notify(#event{name=test_start,
- node=node(),
- data={StartTime,
- lists:flatten(TestLogDir)}}),
- Parent ! {self(),started},
- loop(Mode,[],StartDir);
- ReadError ->
- Parent ! {self(),ReadError},
- exit(ReadError)
- end.
+ end;
+ false ->
+ ok
+ end,
+ {StartTime,TestLogDir} = ct_logs:init(Mode),
+ ct_event:notify(#event{name=test_start,
+ node=node(),
+ data={StartTime,
+ lists:flatten(TestLogDir)}}),
+ Parent ! {self(),started},
+ loop(Mode,[],StartDir).
create_table(TableName,KeyPos) ->
create_table(TableName,set,KeyPos).
@@ -197,106 +182,6 @@ read_opts() ->
{error,{bad_installation,Error}}
end.
-read_config_files(Opts) ->
- ConfigFiles =
- lists:foldl(fun({config,Files},Acc) ->
- Acc ++ Files;
- (_,Acc) ->
- Acc
- end,[],Opts),
- read_config_files1(ConfigFiles).
-
-read_config_files1([ConfigFile|Files]) ->
- case file:consult(ConfigFile) of
- {ok,Config} ->
- set_config(Config),
- read_config_files1(Files);
- {error,enoent} ->
- {user_error,{config_file_error,ConfigFile,enoent}};
- {error,Reason} ->
- Key =
- case application:get_env(common_test, decrypt) of
- {ok,KeyOrFile} ->
- case KeyOrFile of
- {key,K} ->
- K;
- {file,F} ->
- get_crypt_key_from_file(F)
- end;
- _ ->
- get_crypt_key_from_file()
- end,
- case Key of
- {error,no_crypt_file} ->
- {user_error,{config_file_error,ConfigFile,Reason}};
- {error,CryptError} ->
- {user_error,{decrypt_file_error,ConfigFile,CryptError}};
- _ when is_list(Key) ->
- case decrypt_config_file(ConfigFile, undefined, {key,Key}) of
- {ok,CfgBin} ->
- case read_config_terms(CfgBin) of
- {error,ReadFail} ->
- {user_error,{config_file_error,ConfigFile,ReadFail}};
- Config ->
- set_config(Config),
- read_config_files1(Files)
- end;
- {error,DecryptFail} ->
- {user_error,{decrypt_config_error,ConfigFile,DecryptFail}}
- end;
- _ ->
- {user_error,{bad_decrypt_key,ConfigFile,Key}}
- end
- end;
-read_config_files1([]) ->
- ok.
-
-read_config_terms(Bin) when is_binary(Bin) ->
- case catch binary_to_list(Bin) of
- {'EXIT',_} ->
- {error,invalid_textfile};
- Lines ->
- read_config_terms(Lines)
- end;
-read_config_terms(Lines) when is_list(Lines) ->
- read_config_terms1(erl_scan:tokens([], Lines, 0), 1, [], []).
-
-read_config_terms1({done,{ok,Ts,EL},Rest}, L, Terms, _) ->
- case erl_parse:parse_term(Ts) of
- {ok,Term} when Rest == [] ->
- lists:reverse([Term|Terms]);
- {ok,Term} ->
- read_config_terms1(erl_scan:tokens([], Rest, 0),
- EL+1, [Term|Terms], Rest);
- _ ->
- {error,{bad_term,{L,EL}}}
- end;
-read_config_terms1({done,{eof,_},_}, _, Terms, Rest) when Rest == [] ->
- lists:reverse(Terms);
-read_config_terms1({done,{eof,EL},_}, L, _, _) ->
- {error,{bad_term,{L,EL}}};
-read_config_terms1({done,{error,Info,EL},_}, L, _, _) ->
- {error,{Info,{L,EL}}};
-read_config_terms1({more,_}, L, Terms, Rest) ->
- case string:tokens(Rest, [$\n,$\r,$\t]) of
- [] ->
- lists:reverse(Terms);
- _ ->
- {error,{bad_term,L}}
- end.
-
-set_default_config(NewConfig, Scope) ->
- call({set_default_config, {NewConfig, Scope}}).
-
-set_default_config(Name, NewConfig, Scope) ->
- call({set_default_config, {Name, NewConfig, Scope}}).
-
-delete_default_config(Scope) ->
- call({delete_default_config, Scope}).
-
-update_config(Name, Config) ->
- call({update_config, {Name, Config}}).
-
save_suite_data(Key, Value) ->
call({save_suite_data, {Key, undefined, Value}}).
@@ -342,26 +227,6 @@ loop(Mode,TestData,StartDir) ->
ct_logs:make_last_run_index(),
return(From,ok),
loop(Mode,TestData,StartDir);
- {{require,Name,Tag,SubTags},From} ->
- Result = do_require(Name,Tag,SubTags),
- return(From,Result),
- loop(Mode,TestData,StartDir);
- {{set_default_config,{Config,Scope}},From} ->
- set_config(Config,{true,Scope}),
- return(From,ok),
- loop(Mode,TestData,StartDir);
- {{set_default_config,{Name,Config,Scope}},From} ->
- set_config(Name,Config,{true,Scope}),
- return(From,ok),
- loop(Mode,TestData,StartDir);
- {{delete_default_config,Scope},From} ->
- delete_config({true,Scope}),
- return(From,ok),
- loop(Mode,TestData,StartDir);
- {{update_config,{Name,NewConfig}},From} ->
- update_conf(Name,NewConfig),
- return(From,ok),
- loop(Mode,TestData,StartDir);
{{save_suite_data,{Key,Name,Value}},From} ->
ets:insert(?suite_table, #suite_data{key=Key,
name=Name,
@@ -434,14 +299,14 @@ loop(Mode,TestData,StartDir) ->
ct_event:sync_notify(#event{name=test_done,
node=node(),
data=Time}),
- ets:delete(?attr_table),
close_connections(ets:tab2list(?conn_table)),
ets:delete(?conn_table),
ets:delete(?board_table),
ets:delete(?suite_table),
ct_logs:close(How),
- file:set_cwd(StartDir),
ct_event:stop(),
+ ct_config:stop(),
+ file:set_cwd(StartDir),
return(From,ok);
{get_mode,From} ->
return(From,Mode),
@@ -463,6 +328,8 @@ close_connections([#conn{handle=Handle,callback=CB}|Conns]) ->
close_connections([]) ->
ok.
+get_key_from_name(Name)->
+ ct_config:get_key_from_name(Name).
%%%-----------------------------------------------------------------
%%% @spec register_connection(TargetName,Address,Callback,Handle) ->
@@ -480,7 +347,7 @@ close_connections([]) ->
%%% test is finished by calling <code>Callback:close/1</code>.</p>
register_connection(TargetName,Address,Callback,Handle) ->
TargetRef =
- case get_ref_from_name(TargetName) of
+ case ct_config:get_ref_from_name(TargetName) of
{ok,Ref} ->
Ref;
_ ->
@@ -518,7 +385,7 @@ unregister_connection(Handle) ->
%%%
%%% @doc Check if a connection already exists.
does_connection_exist(TargetName,Address,Callback) ->
- case get_ref_from_name(TargetName) of
+ case ct_config:get_ref_from_name(TargetName) of
{ok,TargetRef} ->
case ets:select(?conn_table,[{#conn{handle='$1',
targetref=TargetRef,
@@ -548,7 +415,7 @@ does_connection_exist(TargetName,Address,Callback) ->
%%% @doc Return all connections for the <code>Callback</code> on the
%%% given target (<code>TargetName</code>).
get_connections(TargetName,Callback) ->
- case get_ref_from_name(TargetName) of
+ case ct_config:get_ref_from_name(TargetName) of
{ok,Ref} ->
{ok,ets:select(?conn_table,[{#conn{handle='$1',
address='$2',
@@ -568,250 +435,11 @@ get_target_name(ConnPid) ->
[],
['$1']}]) of
[TargetRef] ->
- get_name_from_ref(TargetRef);
+ ct_config:get_name_from_ref(TargetRef);
[] ->
{error,{unknown_connection,ConnPid}}
end.
-
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:require/1
-require(Key) when is_atom(Key) ->
- require({Key,[]});
-require({Key,SubKeys}) when is_atom(Key) ->
- allocate('_UNDEF',Key,to_list(SubKeys));
-require(Key) ->
- {error,{invalid,Key}}.
-
-
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:require/2
-require(Name,Key) when is_atom(Key) ->
- require(Name,{Key,[]});
-require(Name,{Key,SubKeys}) when is_atom(Name), is_atom(Key) ->
- call({require,Name,Key,to_list(SubKeys)});
-require(Name,Keys) ->
- {error,{invalid,{Name,Keys}}}.
-
-to_list(X) when is_list(X) -> X;
-to_list(X) -> [X].
-
-do_require(Name,Key,SubKeys) when is_list(SubKeys) ->
- case get_key_from_name(Name) of
- {error,_} ->
- allocate(Name,Key,SubKeys);
- {ok,Key} ->
- %% already allocated - check that it has all required subkeys
- Vals = [Val || {_Ref,Val} <- lookup_name(Name)],
- case get_subconfig(SubKeys,Vals) of
- {ok,_SubMapped} ->
- ok;
- Error ->
- Error
- end;
- {ok,OtherKey} ->
- {error,{name_in_use,Name,OtherKey}}
- end.
-
-allocate(Name,Key,SubKeys) ->
- case ets:match_object(?attr_table,#ct_conf{key=Key,name='_UNDEF',_='_'}) of
- [] ->
- {error,{not_available,Key}};
- Available ->
- case allocate_subconfig(Name,SubKeys,Available,false) of
- ok ->
- ok;
- Error ->
- Error
- end
- end.
-
-allocate_subconfig(Name,SubKeys,[C=#ct_conf{value=Value}|Rest],Found) ->
- case do_get_config(SubKeys,Value,[]) of
- {ok,_SubMapped} ->
- ets:insert(?attr_table,C#ct_conf{name=Name}),
- allocate_subconfig(Name,SubKeys,Rest,true);
- _Error ->
- allocate_subconfig(Name,SubKeys,Rest,Found)
- end;
-allocate_subconfig(_Name,_SubKeys,[],true) ->
- ok;
-allocate_subconfig(_Name,SubKeys,[],false) ->
- {error,{not_available,SubKeys}}.
-
-
-
-
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:get_config/1
-get_config(KeyOrName) ->
- get_config(KeyOrName,undefined,[]).
-
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:get_config/2
-get_config(KeyOrName,Default) ->
- get_config(KeyOrName,Default,[]).
-
-%%%-----------------------------------------------------------------
-%%% @hidden
-%%% @equiv ct:get_config/3
-get_config(KeyOrName,Default,Opts) when is_atom(KeyOrName) ->
- case lookup_config(KeyOrName) of
- [] ->
- Default;
- [{_Ref,Val}|_] = Vals ->
- case {lists:member(all,Opts),lists:member(element,Opts)} of
- {true,true} ->
- [{KeyOrName,V} || {_R,V} <- lists:sort(Vals)];
- {true,false} ->
- [V || {_R,V} <- lists:sort(Vals)];
- {false,true} ->
- {KeyOrName,Val};
- {false,false} ->
- Val
- end
- end;
-
-get_config({KeyOrName,SubKey},Default,Opts) ->
- case lookup_config(KeyOrName) of
- [] ->
- Default;
- Vals ->
- Vals1 = case [Val || {_Ref,Val} <- lists:sort(Vals)] of
- Result=[L|_] when is_list(L) ->
- case L of
- [{_,_}|_] ->
- Result;
- _ ->
- []
- end;
- _ ->
- []
- end,
- case get_subconfig([SubKey],Vals1,[],Opts) of
- {ok,[{_,SubVal}|_]=SubVals} ->
- case {lists:member(all,Opts),lists:member(element,Opts)} of
- {true,true} ->
- [{{KeyOrName,SubKey},Val} || {_,Val} <- SubVals];
- {true,false} ->
- [Val || {_SubKey,Val} <- SubVals];
- {false,true} ->
- {{KeyOrName,SubKey},SubVal};
- {false,false} ->
- SubVal
- end;
- _ ->
- Default
- end
- end.
-
-
-get_subconfig(SubKeys,Values) ->
- get_subconfig(SubKeys,Values,[],[]).
-
-get_subconfig(SubKeys,[Value|Rest],Mapped,Opts) ->
- case do_get_config(SubKeys,Value,[]) of
- {ok,SubMapped} ->
- case lists:member(all,Opts) of
- true ->
- get_subconfig(SubKeys,Rest,Mapped++SubMapped,Opts);
- false ->
- {ok,SubMapped}
- end;
- _Error ->
- get_subconfig(SubKeys,Rest,Mapped,Opts)
- end;
-get_subconfig(SubKeys,[],[],_) ->
- {error,{not_available,SubKeys}};
-get_subconfig(_SubKeys,[],Mapped,_) ->
- {ok,Mapped}.
-
-do_get_config([Key|Required],Available,Mapped) ->
- case lists:keysearch(Key,1,Available) of
- {value,{Key,Value}} ->
- NewAvailable = lists:keydelete(Key,1,Available),
- NewMapped = [{Key,Value}|Mapped],
- do_get_config(Required,NewAvailable,NewMapped);
- false ->
- {error,{not_available,Key}}
- end;
-do_get_config([],_Available,Mapped) ->
- {ok,lists:reverse(Mapped)}.
-
-get_all_config() ->
- ets:select(?attr_table,[{#ct_conf{name='$1',key='$2',value='$3',
- default='$4',_='_'},
- [],
- [{{'$1','$2','$3','$4'}}]}]).
-
-lookup_config(KeyOrName) ->
- case lookup_name(KeyOrName) of
- [] ->
- lookup_key(KeyOrName);
- Values ->
- Values
- end.
-
-lookup_name(Name) ->
- ets:select(?attr_table,[{#ct_conf{ref='$1',value='$2',name=Name,_='_'},
- [],
- [{{'$1','$2'}}]}]).
-lookup_key(Key) ->
- ets:select(?attr_table,[{#ct_conf{key=Key,ref='$1',value='$2',name='_UNDEF',_='_'},
- [],
- [{{'$1','$2'}}]}]).
-
-set_config(Config) ->
- set_config('_UNDEF',Config,false).
-
-set_config(Config,Default) ->
- set_config('_UNDEF',Config,Default).
-
-set_config(Name,Config,Default) ->
- [ets:insert(?attr_table,
- #ct_conf{key=Key,value=Val,ref=ct_make_ref(),
- name=Name,default=Default}) ||
- {Key,Val} <- Config].
-
-delete_config(Default) ->
- ets:match_delete(?attr_table,#ct_conf{default=Default,_='_'}),
- ok.
-
-
-%%%-----------------------------------------------------------------
-%%% @spec release_allocated() -> ok
-%%%
-%%% @doc Release all allocated resources, but don't take down any
-%%% connections.
-release_allocated() ->
- Allocated = ets:select(?attr_table,[{#ct_conf{name='$1',_='_'},
- [{'=/=','$1','_UNDEF'}],
- ['$_']}]),
- release_allocated(Allocated).
-release_allocated([H|T]) ->
- ets:delete_object(?attr_table,H),
- ets:insert(?attr_table,H#ct_conf{name='_UNDEF'}),
- release_allocated(T);
-release_allocated([]) ->
- ok.
-
-%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
-update_conf(Name, NewConfig) ->
- Old = ets:select(?attr_table,[{#ct_conf{name=Name,_='_'},[],['$_']}]),
- lists:foreach(fun(OldElem) ->
- NewElem = OldElem#ct_conf{value=NewConfig},
- ets:delete_object(?attr_table, OldElem),
- ets:insert(?attr_table, NewElem)
- end, Old),
- ok.
-
%%%-----------------------------------------------------------------
%%% @spec close_connections() -> ok
%%%
@@ -991,166 +619,6 @@ get_testdir(Dir, Suite) when is_list(Suite) ->
get_testdir(Dir, _) ->
get_testdir(Dir, all).
-%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
-encrypt_config_file(SrcFileName, EncryptFileName) ->
- case get_crypt_key_from_file() of
- {error,_} = E ->
- E;
- Key ->
- encrypt_config_file(SrcFileName, EncryptFileName, {key,Key})
- end.
-
-%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
-encrypt_config_file(SrcFileName, EncryptFileName, {file,KeyFile}) ->
- case get_crypt_key_from_file(KeyFile) of
- {error,_} = E ->
- E;
- Key ->
- encrypt_config_file(SrcFileName, EncryptFileName, {key,Key})
- end;
-
-encrypt_config_file(SrcFileName, EncryptFileName, {key,Key}) ->
- crypto:start(),
- {K1,K2,K3,IVec} = make_crypto_key(Key),
- case file:read_file(SrcFileName) of
- {ok,Bin0} ->
- Bin1 = term_to_binary({SrcFileName,Bin0}),
- Bin2 = case byte_size(Bin1) rem 8 of
- 0 -> Bin1;
- N -> list_to_binary([Bin1,random_bytes(8-N)])
- end,
- EncBin = crypto:des3_cbc_encrypt(K1, K2, K3, IVec, Bin2),
- case file:write_file(EncryptFileName, EncBin) of
- ok ->
- io:format("~s --(encrypt)--> ~s~n",
- [SrcFileName,EncryptFileName]),
- ok;
- {error,Reason} ->
- {error,{Reason,EncryptFileName}}
- end;
- {error,Reason} ->
- {error,{Reason,SrcFileName}}
- end.
-
-%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
-decrypt_config_file(EncryptFileName, TargetFileName) ->
- case get_crypt_key_from_file() of
- {error,_} = E ->
- E;
- Key ->
- decrypt_config_file(EncryptFileName, TargetFileName, {key,Key})
- end.
-
-
-%%%-----------------------------------------------------------------
-%%% @spec
-%%%
-%%% @doc
-decrypt_config_file(EncryptFileName, TargetFileName, {file,KeyFile}) ->
- case get_crypt_key_from_file(KeyFile) of
- {error,_} = E ->
- E;
- Key ->
- decrypt_config_file(EncryptFileName, TargetFileName, {key,Key})
- end;
-
-decrypt_config_file(EncryptFileName, TargetFileName, {key,Key}) ->
- crypto:start(),
- {K1,K2,K3,IVec} = make_crypto_key(Key),
- case file:read_file(EncryptFileName) of
- {ok,Bin} ->
- DecBin = crypto:des3_cbc_decrypt(K1, K2, K3, IVec, Bin),
- case catch binary_to_term(DecBin) of
- {'EXIT',_} ->
- {error,bad_file};
- {_SrcFile,SrcBin} ->
- case TargetFileName of
- undefined ->
- {ok,SrcBin};
- _ ->
- case file:write_file(TargetFileName, SrcBin) of
- ok ->
- io:format("~s --(decrypt)--> ~s~n",
- [EncryptFileName,TargetFileName]),
- ok;
- {error,Reason} ->
- {error,{Reason,TargetFileName}}
- end
- end
- end;
- {error,Reason} ->
- {error,{Reason,EncryptFileName}}
- end.
-
-
-get_crypt_key_from_file(File) ->
- case file:read_file(File) of
- {ok,Bin} ->
- case catch string:tokens(binary_to_list(Bin), [$\n,$\r]) of
- [Key] ->
- Key;
- _ ->
- {error,{bad_crypt_file,File}}
- end;
- {error,Reason} ->
- {error,{Reason,File}}
- end.
-
-get_crypt_key_from_file() ->
- CwdFile = filename:join(".",?cryptfile),
- {Result,FullName} =
- case file:read_file(CwdFile) of
- {ok,Bin} ->
- {Bin,CwdFile};
- _ ->
- case init:get_argument(home) of
- {ok,[[Home]]} ->
- HomeFile = filename:join(Home,?cryptfile),
- case file:read_file(HomeFile) of
- {ok,Bin} ->
- {Bin,HomeFile};
- _ ->
- {{error,no_crypt_file},noent}
- end;
- _ ->
- {{error,no_crypt_file},noent}
- end
- end,
- case FullName of
- noent ->
- Result;
- _ ->
- case catch string:tokens(binary_to_list(Result), [$\n,$\r]) of
- [Key] ->
- io:format("~nCrypt key file: ~s~n", [FullName]),
- Key;
- _ ->
- {error,{bad_crypt_file,FullName}}
- end
- end.
-
-make_crypto_key(String) ->
- <<K1:8/binary,K2:8/binary>> = First = erlang:md5(String),
- <<K3:8/binary,IVec:8/binary>> = erlang:md5([First|lists:reverse(String)]),
- {K1,K2,K3,IVec}.
-
-random_bytes(N) ->
- {A,B,C} = now(),
- random:seed(A, B, C),
- random_bytes_1(N, []).
-
-random_bytes_1(0, Acc) -> Acc;
-random_bytes_1(N, Acc) -> random_bytes_1(N-1, [random:uniform(255)|Acc]).
-
%%%-----------------------------------------------------------------
%%% @spec
@@ -1210,7 +678,7 @@ call(Msg) ->
ct_util_server ! {Msg,{self(),Ref}},
receive
{Ref, Result} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
Result;
{'DOWN',MRef,process,_,Reason} ->
{error,{ct_util_server_down,Reason}}
@@ -1244,37 +712,6 @@ ct_make_ref_loop(N) ->
From ! {self(),N},
ct_make_ref_loop(N+1)
end.
-
-get_ref_from_name(Name) ->
- case ets:select(?attr_table,[{#ct_conf{name=Name,ref='$1',_='_'},
- [],
- ['$1']}]) of
- [Ref] ->
- {ok,Ref};
- _ ->
- {error,{no_such_name,Name}}
- end.
-
-get_name_from_ref(Ref) ->
- case ets:select(?attr_table,[{#ct_conf{name='$1',ref=Ref,_='_'},
- [],
- ['$1']}]) of
- [Name] ->
- {ok,Name};
- _ ->
- {error,{no_such_ref,Ref}}
- end.
-
-get_key_from_name(Name) ->
- case ets:select(?attr_table,[{#ct_conf{name=Name,key='$1',_='_'},
- [],
- ['$1']}]) of
- [Key|_] ->
- {ok,Key};
- _ ->
- {error,{no_such_name,Name}}
- end.
-
abs_name(Dir0) ->
Abs = filename:absname(Dir0),
diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl
index c1dc14f943..54eed29415 100644
--- a/lib/common_test/src/ct_util.hrl
+++ b/lib/common_test/src/ct_util.hrl
@@ -29,11 +29,15 @@
-record(testspec, {spec_dir,
nodes=[],
+ init=[],
logdir=["."],
cover=[],
config=[],
+ userconfig=[],
event_handler=[],
include=[],
+ multiply_timetraps=[],
+ scale_timetraps=[],
alias=[],
tests=[]}).
@@ -50,3 +54,4 @@
-define(CT_MEVMGR_REF, ct_master_event).
-define(missing_suites_info, "missing_suites.info").
+-define(ct_config_txt, ct_config_plain).
diff --git a/lib/common_test/src/vts.erl b/lib/common_test/src/vts.erl
index ad4845a7c3..2ee982d726 100644
--- a/lib/common_test/src/vts.erl
+++ b/lib/common_test/src/vts.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -100,7 +100,7 @@ start_link() ->
MRef = erlang:monitor(process,Pid),
receive
{Pid,started} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
{ok,Pid};
{'DOWN',MRef,process,_,Reason} ->
{error,{vts,died,Reason}}
@@ -160,11 +160,13 @@ init(Parent) ->
loop(State) ->
receive
- {{init_data,ConfigFiles,EvHandlers,LogDir,Tests},From} ->
- ct_install(State),
+ {{init_data,Config,EvHandlers,LogDir,Tests},From} ->
+ %% ct:pal("State#state.current_log_dir=~p", [State#state.current_log_dir]),
+ NewState = State#state{config=Config,event_handler=EvHandlers,
+ current_log_dir=LogDir,tests=Tests},
+ ct_install(NewState),
return(From,ok),
- loop(#state{config=ConfigFiles,event_handler=EvHandlers,
- current_log_dir=LogDir,tests=Tests});
+ loop(NewState);
{start_page,From} ->
return(From,start_page1()),
loop(State);
@@ -257,7 +259,7 @@ call(Msg) ->
Pid ! {Msg,{self(),Ref}},
receive
{Ref, Result} ->
- erlang:demonitor(MRef),
+ erlang:demonitor(MRef, [flush]),
Result;
{'DOWN',MRef,process,_,Reason} ->
{error,{process_down,Pid,Reason}}
diff --git a/lib/common_test/test/Makefile b/lib/common_test/test/Makefile
index 35ba22aa59..97ded5eb9a 100644
--- a/lib/common_test/test/Makefile
+++ b/lib/common_test/test/Makefile
@@ -27,13 +27,17 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
MODULES= \
ct_test_support \
ct_test_support_eh \
+ ct_userconfig_callback \
ct_smoke_test_SUITE \
ct_event_handler_SUITE \
ct_groups_test_1_SUITE \
ct_groups_test_2_SUITE \
ct_skip_SUITE \
ct_error_SUITE \
- ct_test_server_if_1_SUITE
+ ct_test_server_if_1_SUITE \
+ ct_config_SUITE \
+ ct_master_SUITE \
+ ct_misc_1_SUITE
ERL_FILES= $(MODULES:%=%.erl)
@@ -64,15 +68,15 @@ EBIN = .
#.PHONY: make_emakefile
#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)
+# > $(EMAKEFILE)
tests debug opt:
+ erl $(ERL_MAKE_FLAGS) -make
clean:
rm -f $(TARGET_FILES)
+# rm -f $(EMAKEFILE)
rm -f core
docs:
diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl
new file mode 100644
index 0000000000..72ff781f82
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE.erl
@@ -0,0 +1,255 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_config_SUITE
+%%%
+%%% Description:
+%%% Test configuration handling in Common Test suites.
+%%%
+%%% The suites used for the test are located in the data directory.
+%%%-------------------------------------------------------------------
+-module(ct_config_SUITE).
+
+-compile(export_all).
+
+-include_lib("test_server/include/test_server.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ DataDir = ?config(data_dir, Config),
+ PathDir = filename:join(DataDir, "config/test"),
+ Config1 = ct_test_support:init_per_suite([{path_dirs,[PathDir]} | Config]),
+ PrivDir = ?config(priv_dir, Config1),
+ ConfigDir = filename:join(PrivDir, "config"),
+ ok = file:make_dir(ConfigDir),
+ [{config_dir,ConfigDir} | Config1].
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+all(doc) ->
+ [""];
+
+all(suite) ->
+ [
+ require,
+ userconfig_static,
+ userconfig_dynamic,
+ testspec_legacy,
+ testspec_static,
+ testspec_dynamic
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+require(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ run_test(config_static_SUITE,
+ Config,
+ {config, filename:join(DataDir, "config/config.txt")},
+ ["config_static_SUITE"]).
+
+userconfig_static(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ run_test(config_static_SUITE,
+ Config,
+ {userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}},
+ ["config_static_SUITE"]).
+
+userconfig_dynamic(Config) when is_list(Config) ->
+ run_test(config_dynamic_SUITE,
+ Config,
+ {userconfig, {config_driver, "config_server"}},
+ ["config_dynamic_SUITE"]).
+
+testspec_legacy(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ ConfigDir = ?config(config_dir, Config),
+ make_spec(DataDir, ConfigDir,
+ "spec_legacy.spec",
+ [config_static_SUITE],
+ [{config, filename:join(DataDir, "config/config.txt")}]),
+ run_test(config_static_SUITE,
+ Config,
+ {spec, filename:join(ConfigDir, "spec_legacy.spec")},
+ []),
+ file:delete(filename:join(ConfigDir, "spec_legacy.spec")).
+
+testspec_static(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ ConfigDir = ?config(config_dir, Config),
+ make_spec(DataDir, ConfigDir,
+ "spec_static.spec",
+ [config_static_SUITE],
+ [{userconfig, {ct_config_xml, filename:join(DataDir, "config/config.xml")}}]),
+ run_test(config_static_SUITE,
+ Config,
+ {spec, filename:join(ConfigDir, "spec_static.spec")},
+ []),
+ file:delete(filename:join(ConfigDir, "spec_static.spec")).
+
+testspec_dynamic(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ ConfigDir = ?config(config_dir, Config),
+ make_spec(DataDir, ConfigDir, "spec_dynamic.spec",
+ [config_dynamic_SUITE],
+ [{userconfig, {config_driver, "config_server"}}]),
+ run_test(config_dynamic_SUITE,
+ Config,
+ {spec, filename:join(ConfigDir, "spec_dynamic.spec")},
+ []),
+ file:delete(filename:join(ConfigDir, "spec_dynamic.spec")).
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+make_spec(DataDir, ConfigDir, Filename, Suites, Config)->
+ {ok, Fd} = file:open(filename:join(ConfigDir, Filename), [write]),
+ ok = file:write(Fd,
+ io_lib:format("{suites, \"~sconfig/test/\", ~p}.~n", [DataDir, Suites])),
+ lists:foreach(fun(C)-> ok=file:write(Fd, io_lib:format("~p.~n", [C])) end, Config),
+ ok = file:close(Fd).
+
+run_test(Name, Config, CTConfig, SuiteNames)->
+ DataDir = ?config(data_dir, Config),
+ Joiner = fun(Suite) -> filename:join(DataDir, "config/test/"++Suite) end,
+ Suites = lists:map(Joiner, SuiteNames),
+ {Opts,ERPid} = setup_env({suite,Suites}, Config, CTConfig),
+ ok = ct_test_support:run(Opts, Config),
+ TestEvents = ct_test_support:get_events(ERPid, Config),
+ ct_test_support:log_events(Name,
+ reformat_events(TestEvents, ?eh),
+ ?config(config_dir, Config)),
+ ExpEvents = events_to_check(Name),
+ ok = ct_test_support:verify_events(ExpEvents, TestEvents, Config).
+
+setup_env(Test, Config, CTConfig) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [Test,{event_handler,{?eh,EvHArgs}}, CTConfig],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+reformat_events(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ expected_events(Test) ++ events_to_check(Test, N-1).
+
+expected_events(config_static_SUITE)->
+[
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,8}},
+ {?eh,tc_start,{config_static_SUITE,init_per_suite}},
+ {?eh,tc_done,{config_static_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{config_static_SUITE,test_get_config_simple}},
+ {?eh,tc_done,{config_static_SUITE,test_get_config_simple,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{config_static_SUITE,test_get_config_nested}},
+ {?eh,tc_done,{config_static_SUITE,test_get_config_nested,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ {?eh,tc_start,{config_static_SUITE,test_default_suitewide}},
+ {?eh,tc_done,{config_static_SUITE,test_default_suitewide,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,{config_static_SUITE,test_config_name_already_in_use1}},
+ {?eh,tc_done,
+ {config_static_SUITE,test_config_name_already_in_use1,{skipped,{config_name_already_in_use,[x1]}}}},
+ {?eh,test_stats,{3,0,{1,0}}},
+ {?eh,tc_start,{config_static_SUITE,test_default_tclocal}},
+ {?eh,tc_done,{config_static_SUITE,test_default_tclocal,ok}},
+ {?eh,test_stats,{4,0,{1,0}}},
+ {?eh,tc_start,{config_static_SUITE,test_config_name_already_in_use2}},
+ {?eh,tc_done,
+ {config_static_SUITE,test_config_name_already_in_use2,
+ {skipped,{config_name_already_in_use,[x1,alias]}}}},
+ {?eh,test_stats,{4,0,{2,0}}},
+ {?eh,tc_start,{config_static_SUITE,test_alias_tclocal}},
+ {?eh,tc_done,{config_static_SUITE,test_alias_tclocal,ok}},
+ {?eh,test_stats,{5,0,{2,0}}},
+ {?eh,tc_start,{config_static_SUITE,test_get_config_undefined}},
+ {?eh,tc_done,{config_static_SUITE,test_get_config_undefined,ok}},
+ {?eh,test_stats,{6,0,{2,0}}},
+ {?eh,tc_start,{config_static_SUITE,end_per_suite}},
+ {?eh,tc_done,{config_static_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+];
+
+expected_events(config_dynamic_SUITE)->
+[
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,5}},
+ {?eh,tc_start,{config_dynamic_SUITE,init_per_suite}},
+ {?eh,tc_done,{config_dynamic_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{config_dynamic_SUITE,test_get_known_variable}},
+ {?eh,tc_done,
+ {config_dynamic_SUITE,test_get_known_variable,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{config_dynamic_SUITE,test_localtime_update}},
+ {?eh,tc_done,{config_dynamic_SUITE,test_localtime_update,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ {?eh,tc_start,{config_dynamic_SUITE,test_server_pid}},
+ {?eh,tc_done,{config_dynamic_SUITE,test_server_pid,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,
+ {config_dynamic_SUITE,test_disappearable_variable}},
+ {?eh,tc_done,
+ {config_dynamic_SUITE,test_disappearable_variable,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,tc_start,
+ {config_dynamic_SUITE,test_disappearable_variable_alias}},
+ {?eh,tc_done,
+ {config_dynamic_SUITE,test_disappearable_variable_alias,ok}},
+ {?eh,test_stats,{5,0,{0,0}}},
+ {?eh,tc_start,{config_dynamic_SUITE,end_per_suite}},
+ {?eh,tc_done,{config_dynamic_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+].
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/config.txt b/lib/common_test/test/ct_config_SUITE_data/config/config.txt
new file mode 100644
index 0000000000..fcbffcd7f3
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/config.txt
@@ -0,0 +1,31 @@
+{x, suite}.
+{gen_cfg,
+ [
+ {a,a_value},
+ {b,b_value}
+ ]}.
+{gen_cfg2,
+ [
+ {c, "Hello, world!"},
+ {d, atom_value},
+ {e, {tuple,1,"third value",[]}},
+ {f, []},
+ {g, [1,atom,"string",13.6,{1,2,3}]}
+ ]}.
+{gen_cfg3,
+ [
+ {h,
+ [
+ {i, third1},
+ {j, "Third2"},
+ {k, 'THIRD3'}
+ ]},
+ {l,
+ [
+ {m,
+ [
+ {n, "N"},
+ {o, 'O'}
+ ]}
+ ]}
+ ]}.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/config.xml b/lib/common_test/test/ct_config_SUITE_data/config/config.xml
new file mode 100644
index 0000000000..0a3e5f2e31
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/config.xml
@@ -0,0 +1,27 @@
+<config>
+ <x>suite</x>
+ <gen_cfg>
+ <a>a_value</a>
+ <b>b_value</b>
+ </gen_cfg>
+ <gen_cfg2>
+ <c>"Hello, world!"</c>
+ <d>atom_value</d>
+ <e>{tuple,1,"third value",[]}</e>
+ <f>[]</f>
+ <g>[1,atom,"string",13.6,{1,2,3}]</g>
+ </gen_cfg2>
+ <gen_cfg3>
+ <h>
+ <i>third1</i>
+ <j>"Third2"</j>
+ <k>'THIRD3'</k>
+ </h>
+ <l>
+ <m>
+ <n>"N"</n>
+ <o>'O'</o>
+ </m>
+ </l>
+ </gen_cfg3>
+</config>
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl
new file mode 100644
index 0000000000..d93faf6ec6
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_driver.erl
@@ -0,0 +1,46 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_config_SUITE
+%%%
+%%% Description:
+%%% Config driver used in the CT's tests (config_2_SUITE)
+%%%-------------------------------------------------------------------
+-module(config_driver).
+-export([read_config/1, check_parameter/1]).
+
+read_config(ServerName)->
+ ServerModule = list_to_atom(ServerName),
+ ServerModule:start(),
+ ServerModule:get_config().
+
+check_parameter(ServerName)->
+ ServerModule = list_to_atom(ServerName),
+ case code:is_loaded(ServerModule) of
+ {file, _}->
+ {ok, {config, ServerName}};
+ false->
+ case code:load_file(ServerModule) of
+ {module, ServerModule}->
+ {ok, {config, ServerName}};
+ {error, nofile}->
+ {error, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}}
+ end
+ end.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
new file mode 100644
index 0000000000..8ee12a2e4d
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_dynamic_SUITE.erl
@@ -0,0 +1,145 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: config_dynamic_SUITE
+%%%
+%%% Description:
+%%% Test suite for common_test which tests the userconfig functionality
+%%%-------------------------------------------------------------------
+-module(config_dynamic_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%% This suite will be run with dynamic userconfig
+%% test_driver.erl is compliant to ct_config_* callback
+%% and test_server is simple server for getting runtime-changing data
+%% which will return the list with the following variables:
+%% localtime = the erlang:localtime() result in list [{date, Date}, {time, Time}]
+%% node = erlang:node() - can be compared in the testcase
+%% now = erlang:now() - easier to compare than localtime()
+%% config_server_pid - pid of the config server, should NOT change!
+%% config_server_vsn - .19
+%% config_server_iteration - a number of iteration config_server's loop done
+%% disappearable_variable - hereAmI - will be absent on even iterations
+
+suite() ->
+ [
+ {timetrap, {seconds,10}}
+ ].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_) ->
+ ok.
+
+all() -> [test_get_known_variable, test_localtime_update,
+ test_server_pid, test_disappearable_variable,
+ test_disappearable_variable_alias].
+
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(_, _) ->
+ ok.
+
+% test that usual config works
+test_get_known_variable(_)->
+ Node = erlang:node(),
+ 0.19 = ct:get_config(config_server_vsn),
+ Node = ct:get_config(node),
+ ok.
+
+% localtime will be updated in 5 seconds, check that
+test_localtime_update(_)->
+ Seconds = 5,
+ LT1 = ct:get_config(localtime),
+ timer:sleep(Seconds*1000),
+ LT2 = ct:reload_config(localtime),
+ case is_diff_ok(LT1, LT2, Seconds) of
+ {false, Actual, Exp}->
+ ct:fail(io_lib:format("Time difference ~p is not ok, expected ~p", [Actual, Exp]));
+ true->
+ ok
+ end.
+
+% server pid should not change
+test_server_pid()->
+ [{require, cfvsn, config_server_vsn}].
+test_server_pid(_)->
+ Pid = ct:get_config(config_server_pid),
+ Pid = ct:reload_config(config_server_pid),
+ Vsn = ct:get_config(config_server_vsn),
+ % aliases remain after config reloading
+ Vsn = ct:get_config(cfvsn),
+ ok.
+
+% test that variables may disappear from the config_2_SUITE
+test_disappearable_variable(_)->
+ % ask CT for config_server_iteration variable
+ Iter = ct:reload_config(config_server_iteration),
+ % here we should reload this variable in case it's odd
+ if Iter rem 2 == 1->
+ Iter2 = ct:reload_config(config_server_iteration),
+ Iter2 = Iter+1;
+ true->ok
+ end,
+ % now disappearable_variable should be in place
+ hereAmI = ct:get_config(disappearable_variable),
+ % and now it should disappear
+ undefined = ct:reload_config(disappearable_variable).
+
+% alias of disappearable_variable should disappear too
+test_disappearable_variable_alias(_)->
+ % the same rules apply for this testcase as for previous one
+ Iter = ct:reload_config(config_server_iteration),
+ Iter2 = if
+ Iter rem 2 == 1 ->
+ NewIter = ct:reload_config(config_server_iteration),
+ NewIter = Iter+1;
+ true->
+ Iter
+ end,
+ ct:require(diav, disappearable_variable),
+ hereAmI = ct:get_config(disappearable_variable),
+ hereAmI = ct:get_config(diav),
+ ct:reload_config(disappearable_variable),
+ undefined = ct:get_config(disappearable_variable),
+ % after reloading, it's even again
+ Iter3=ct:get_config(config_server_iteration),
+ Iter3 = Iter2+1,
+ % and alias does not exist
+ undefined = ct:get_config(diav).
+
+my_dt_to_datetime([{date, D},{time, T}])->
+ {D, T}.
+
+is_diff_ok(DT1, DT2, Seconds)->
+ GS1 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT1)),
+ GS2 = calendar:datetime_to_gregorian_seconds(my_dt_to_datetime(DT2)),
+ if
+ GS2-GS1 > Seconds+Seconds/2;
+ GS2-GS1 < Seconds-Seconds/2->
+ {false, GS2-GS1, Seconds};
+ true->
+ true
+ end.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl
new file mode 100644
index 0000000000..8463fea645
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_server.erl
@@ -0,0 +1,93 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_config_SUITE
+%%%
+%%% Description:
+%%% Config server used in the CT's tests (config_2_SUITE)
+%%%-------------------------------------------------------------------
+-module(config_server).
+-export([start/0, stop/0, loop/1, init/1, get_config/0]).
+
+-define(REGISTERED_NAME, ct_test_config_server).
+-define(vsn, 0.19).
+
+start()->
+ case whereis(?REGISTERED_NAME) of
+ undefined->
+ spawn(?MODULE, init, [?REGISTERED_NAME]),
+ wait();
+ _Pid->
+ ok
+ end,
+ ?REGISTERED_NAME.
+
+init(Name)->
+ register(Name, self()),
+ loop(0).
+
+get_config()->
+ call(self(), get_config).
+
+stop()->
+ call(self(), stop).
+
+call(Client, Request)->
+ case whereis(?REGISTERED_NAME) of
+ undefined->
+ {error, not_started, Request};
+ Pid->
+ Pid ! {Client, Request},
+ receive
+ Reply->
+ {ok, Reply}
+ after 4000->
+ {error, timeout, Request}
+ end
+ end.
+
+loop(Iteration)->
+ receive
+ {Pid, stop}->
+ Pid ! ok;
+ {Pid, get_config}->
+ {D,T} = erlang:localtime(),
+ Config =
+ [{localtime, [{date, D}, {time, T}]},
+ {node, erlang:node()},
+ {config_server_iteration, Iteration},
+ {now, erlang:now()},
+ {config_server_pid, self()},
+ {config_server_vsn, ?vsn}],
+ Config2 = if Iteration rem 2 == 0->
+ Config ++ [{disappearable_variable, hereAmI}];
+ true-> Config
+ end,
+ Pid ! Config2,
+ ?MODULE:loop(Iteration+1)
+ end.
+
+wait()->
+ case whereis(?REGISTERED_NAME) of
+ undefined->
+ wait();
+ _Pid->
+ ok
+ end.
diff --git a/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
new file mode 100644
index 0000000000..8751a2e8f3
--- /dev/null
+++ b/lib/common_test/test/ct_config_SUITE_data/config/test/config_static_SUITE.erl
@@ -0,0 +1,123 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: config_static_SUITE
+%%%
+%%% Description:
+%%% Test suite for common_test which tests the get_config and require
+%%% functionality
+%%%-------------------------------------------------------------------
+-module(config_static_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+% The config contains variables:
+% x - atom
+% gen_cfg - list with two key-values tagged with a and b
+% gen_cfg2 - list of five key-values tagged with c, d, e, f and g
+% gen_cfg3 - list of two complex key-values taggen with:
+% h: three elements inside - i, j and k
+% l: m inside, contains n and o
+
+suite() ->
+ [
+ {timetrap, {seconds,10}},
+ %% x1 doesn't exist in cfg-file!
+ {require, x1, x},
+ {require, gen_cfg3},
+ {require, alias, gen_cfg},
+ %% x1 default value
+ {x1, {x,suite}}
+ ].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_) ->
+ ok.
+
+all() -> [test_get_config_simple, test_get_config_nested, test_default_suitewide,
+ test_config_name_already_in_use1, test_default_tclocal,
+ test_config_name_already_in_use2, test_alias_tclocal,
+ test_get_config_undefined].
+
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(_, _) ->
+ ok.
+
+%% test getting a simple value
+test_get_config_simple(_)->
+ suite = ct:get_config(x),
+ ok.
+
+%% test getting a nested value
+test_get_config_nested(_)->
+ a_value = ct:get_config({gen_cfg, a}),
+ ok.
+
+%% test suite-wide default value
+test_default_suitewide(_)->
+ suite = ct:get_config(x1),
+ ok.
+
+%% should get skipped
+test_config_name_already_in_use1() ->
+ [{timetrap, {seconds,2}},
+ {require, x1, x},
+ {x1, {x,test2}}].
+test_config_name_already_in_use1(_) ->
+ ct:fail("Test should've been skipped, you shouldn't see this!"),
+ ok.
+
+%% test defaults in a testcase
+test_default_tclocal() ->
+ [{timetrap, {seconds,3}},
+ {require, y1, y},
+ {y1, {y,test3}}].
+test_default_tclocal(_) ->
+ test3 = ct:get_config(y1),
+ ok.
+
+%% should get skipped
+test_config_name_already_in_use2() ->
+ [{require,alias,something},
+ {alias,{something,else}},
+ {require, x1, x},
+ {x1, {x,test4}}].
+test_config_name_already_in_use2(_) ->
+ ct:fail("Test should've been skipped, you shouldn't see this!"),
+ ok.
+
+%% test aliases
+test_alias_tclocal() ->
+ [{require,newalias,gen_cfg}].
+test_alias_tclocal(_) ->
+ A = [{a,a_value},{b,b_value}] = ct:get_config(newalias),
+ A = ct:get_config(gen_cfg),
+ ok.
+
+%% test for getting undefined variables
+test_get_config_undefined(_) ->
+ undefined = ct:get_config(y1),
+ ok.
diff --git a/lib/common_test/test/ct_error_SUITE.erl b/lib/common_test/test/ct_error_SUITE.erl
index be75d768fc..2fa031b884 100644
--- a/lib/common_test/test/ct_error_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE.erl
@@ -63,7 +63,10 @@ all(suite) ->
[
cfg_error,
lib_error,
- no_compile
+ no_compile,
+ timetrap_end_conf,
+ timetrap_normal,
+ timetrap_extended
].
@@ -84,17 +87,22 @@ cfg_error(Config) when is_list(Config) ->
Join(DataDir, "cfg_error_6_SUITE"),
Join(DataDir, "cfg_error_7_SUITE"),
Join(DataDir, "cfg_error_8_SUITE"),
- Join(DataDir, "cfg_error_9_SUITE")
+ Join(DataDir, "cfg_error_9_SUITE"),
+ Join(DataDir, "cfg_error_10_SUITE"),
+ Join(DataDir, "cfg_error_11_SUITE"),
+ Join(DataDir, "cfg_error_12_SUITE"),
+ Join(DataDir, "cfg_error_13_SUITE"),
+ Join(DataDir, "cfg_error_14_SUITE")
],
- {Opts,ERPid} = setup({suite,Suites}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ {Opts,ERPid} = setup([{suite,Suites}], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(cfg_error,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(cfg_error),
+ TestEvents = events_to_check(cfg_error),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -104,15 +112,15 @@ lib_error(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
Join = fun(D, S) -> filename:join(D, "error/test/"++S) end,
Suites = [Join(DataDir, "lib_error_1_SUITE")],
- {Opts,ERPid} = setup({suite,Suites}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ {Opts,ERPid} = setup([{suite,Suites}], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(lib_error,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(lib_error),
+ TestEvents = events_to_check(lib_error),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -122,17 +130,75 @@ no_compile(Config) when is_list(Config) ->
DataDir = ?config(data_dir, Config),
Join = fun(D, S) -> filename:join(D, "error/test/"++S) end,
Suites = [Join(DataDir, "no_compile_SUITE")],
- {Opts,ERPid} = setup({suite,Suites}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ {Opts,ERPid} = setup([{suite,Suites}], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(no_compile,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(no_compile),
+ TestEvents = events_to_check(no_compile),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
+%%%-----------------------------------------------------------------
+%%%
+timetrap_end_conf(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ Join = fun(D, S) -> filename:join(D, "error/test/"++S) end,
+ Suites = [Join(DataDir, "timetrap_1_SUITE")],
+ {Opts,ERPid} = setup([{suite,Suites}], Config),
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(timetrap_end_conf,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config)),
+
+ TestEvents = events_to_check(timetrap_end_conf),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+timetrap_normal(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ Join = fun(D, S) -> filename:join(D, "error/test/"++S) end,
+ Suite = Join(DataDir, "timetrap_2_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},
+ {userconfig,{ct_userconfig_callback,
+ "multiply 1 scale false"}}],
+ Config),
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(timetrap_normal,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config)),
+
+ TestEvents = events_to_check(timetrap_normal),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+timetrap_extended(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ Join = fun(D, S) -> filename:join(D, "error/test/"++S) end,
+ Suite = Join(DataDir, "timetrap_2_SUITE"),
+ {Opts,ERPid} = setup([{suite,Suite},
+ {multiply_timetraps,2},
+ {scale_timetraps,false},
+ {userconfig,{ct_userconfig_callback,
+ "multiply 2 scale false"}}],
+ Config),
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(timetrap_extended,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config)),
+
+ TestEvents = events_to_check(timetrap_extended),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
@@ -142,7 +208,7 @@ setup(Test, Config) ->
Opts0 = ct_test_support:get_opts(Config),
Level = ?config(trace_level, Config),
EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
- Opts = Opts0 ++ [Test,{event_handler,{?eh,EvHArgs}}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
ERPid = ct_test_support:start_event_receiver(Config),
{Opts,ERPid}.
@@ -154,11 +220,20 @@ reformat(Events, EH) ->
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
test_events(cfg_error) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
- {?eh,start_info,{9,9,33}},
+ {?eh,start_info,{14,14,42}},
{?eh,tc_start,{cfg_error_1_SUITE,init_per_suite}},
{?eh,tc_done,
@@ -470,6 +545,59 @@ test_events(cfg_error) ->
{?eh,tc_start,{cfg_error_9_SUITE,end_per_suite}},
{?eh,tc_done,{cfg_error_9_SUITE,end_per_suite,ok}},
+ {?eh,tc_start,{cfg_error_10_SUITE,init_per_suite}},
+ {?eh,tc_done,{cfg_error_10_SUITE,init_per_suite,
+ {failed,{error,fail_init_per_suite}}}},
+ {?eh,tc_auto_skip,{cfg_error_10_SUITE,tc1,
+ {failed,{cfg_error_10_SUITE,init_per_suite,
+ {failed,fail_init_per_suite}}}}},
+ {?eh,test_stats,{12,3,{0,19}}},
+ {?eh,tc_auto_skip,{cfg_error_10_SUITE,end_per_suite,
+ {failed,{cfg_error_10_SUITE,init_per_suite,
+ {failed,fail_init_per_suite}}}}},
+ {?eh,tc_start,{cfg_error_11_SUITE,init_per_suite}},
+ {?eh,tc_done,{cfg_error_11_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{cfg_error_11_SUITE,tc1}},
+ {?eh,tc_done,{cfg_error_11_SUITE,tc1,
+ {skipped,{config_name_already_in_use,[dummy0]}}}},
+ {?eh,test_stats,{12,3,{1,19}}},
+ {?eh,tc_start,{cfg_error_11_SUITE,tc2}},
+ {?eh,tc_done,{cfg_error_11_SUITE,tc2,ok}},
+ {?eh,test_stats,{13,3,{1,19}}},
+ {?eh,tc_start,{cfg_error_11_SUITE,end_per_suite}},
+ {?eh,tc_done,{cfg_error_11_SUITE,end_per_suite,ok}},
+ {?eh,tc_start,{cfg_error_12_SUITE,tc1}},
+ {?eh,tc_done,{cfg_error_12_SUITE,tc1,{failed,{timetrap_timeout,500}}}},
+ {?eh,test_stats,{13,4,{1,19}}},
+ {?eh,tc_start,{cfg_error_12_SUITE,tc2}},
+ {?eh,tc_done,{cfg_error_12_SUITE,tc2,{failed,
+ {cfg_error_12_SUITE,end_per_testcase,
+ {timetrap_timeout,500}}}}},
+ {?eh,test_stats,{14,4,{1,19}}},
+ {?eh,tc_start,{cfg_error_12_SUITE,tc3}},
+ {?eh,tc_done,{cfg_error_12_SUITE,tc3,ok}},
+ {?eh,test_stats,{15,4,{1,19}}},
+ {?eh,tc_start,{cfg_error_12_SUITE,tc4}},
+ {?eh,tc_done,{cfg_error_12_SUITE,tc4,{failed,
+ {cfg_error_12_SUITE,end_per_testcase,
+ {timetrap_timeout,500}}}}},
+ {?eh,test_stats,{16,4,{1,19}}},
+ {?eh,tc_start,{cfg_error_13_SUITE,init_per_suite}},
+ {?eh,tc_done,{cfg_error_13_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{cfg_error_13_SUITE,tc1}},
+ {?eh,tc_done,{cfg_error_13_SUITE,tc1,ok}},
+ {?eh,test_stats,{17,4,{1,19}}},
+ {?eh,tc_start,{cfg_error_13_SUITE,end_per_suite}},
+ {?eh,tc_done,{cfg_error_13_SUITE,end_per_suite,ok}},
+ {?eh,tc_start,{cfg_error_14_SUITE,init_per_suite}},
+ {?eh,tc_done,{cfg_error_14_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{cfg_error_14_SUITE,tc1}},
+ {?eh,tc_done,{cfg_error_14_SUITE,tc1,ok}},
+ {?eh,test_stats,{18,4,{1,19}}},
+ {?eh,tc_start,{cfg_error_14_SUITE,end_per_suite}},
+ {?eh,tc_done,{cfg_error_14_SUITE,end_per_suite,
+ {comment,
+ "should succeed since ct_fw cancels timetrap in end_tc"}}},
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
@@ -555,4 +683,91 @@ test_events(lib_error) ->
];
test_events(no_compile) ->
- [].
+ [];
+
+test_events(timetrap_end_conf) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,6}},
+ {?eh,tc_start,{timetrap_1_SUITE,init_per_suite}},
+ {?eh,tc_done,{timetrap_1_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc1}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc1,{failed,{timetrap_timeout,1000}}}},
+ {?eh,test_stats,{0,1,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc2}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc2,{failed,{timetrap_timeout,1000}}}},
+ {?eh,test_stats,{0,2,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc3}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc3,{failed,{testcase_aborted,testing_end_conf}}}},
+ {?eh,test_stats,{0,3,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc4}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc4,{failed,{testcase_aborted,testing_end_conf}}}},
+ {?eh,test_stats,{0,4,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc5}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc5,{failed,{timetrap_timeout,1000}}}},
+ {?eh,test_stats,{0,5,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,tc6}},
+ {?eh,tc_done,
+ {timetrap_1_SUITE,tc6,{failed,{testcase_aborted,testing_end_conf}}}},
+ {?eh,test_stats,{0,6,{0,0}}},
+ {?eh,tc_start,{timetrap_1_SUITE,end_per_suite}},
+ {?eh,tc_done,{timetrap_1_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(timetrap_normal) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,3}},
+ {?eh,tc_start,{timetrap_2_SUITE,init_per_suite}},
+ {?eh,tc_done,{timetrap_2_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{timetrap_2_SUITE,tc0}},
+ {?eh,tc_done,
+ {timetrap_2_SUITE,tc0,{failed,{timetrap_timeout,3000}}}},
+ {?eh,test_stats,{0,1,{0,0}}},
+ {?eh,tc_start,{timetrap_2_SUITE,tc1}},
+ {?eh,tc_done,
+ {timetrap_2_SUITE,tc1,{failed,{timetrap_timeout,1000}}}},
+ {?eh,test_stats,{0,2,{0,0}}},
+ {?eh,tc_start,{timetrap_2_SUITE,tc2}},
+ {?eh,tc_done,
+ {timetrap_2_SUITE,tc2,{failed,{timetrap_timeout,500}}}},
+ {?eh,test_stats,{0,3,{0,0}}},
+ {?eh,tc_start,{timetrap_2_SUITE,end_per_suite}},
+ {?eh,tc_done,{timetrap_2_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(timetrap_extended) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,3}},
+ {?eh,tc_start,{timetrap_2_SUITE,init_per_suite}},
+ {?eh,tc_done,{timetrap_2_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{timetrap_2_SUITE,tc0}},
+ {?eh,tc_done,
+ {timetrap_2_SUITE,tc0,{failed,{timetrap_timeout,6000}}}},
+ {?eh,test_stats,{0,1,{0,0}}},
+ {?eh,tc_start,{timetrap_2_SUITE,tc1}},
+ {?eh,tc_done,
+ {timetrap_2_SUITE,tc1,{failed,{timetrap_timeout,2000}}}},
+ {?eh,test_stats,{0,2,{0,0}}},
+ {?eh,tc_start,{timetrap_2_SUITE,tc2}},
+ {?eh,tc_done,
+ {timetrap_2_SUITE,tc2,{failed,{timetrap_timeout,1000}}}},
+ {?eh,test_stats,{0,3,{0,0}}},
+ {?eh,tc_start,{timetrap_2_SUITE,end_per_suite}},
+ {?eh,tc_done,{timetrap_2_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ].
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl
new file mode 100644
index 0000000000..9f9a90372b
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_10_SUITE.erl
@@ -0,0 +1,123 @@
+%%
+%% %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(cfg_error_10_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% Function: suite() -> Info
+%% Info = [tuple()]
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,2}},
+ {require, dummy0}, {default_config, dummy0, "suite/0"},
+ {require, dummy1}, {default_config, dummy1, "suite/0"},
+ {require, dummy2}, {default_config, dummy2, "suite/0"}].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_suite() ->
+ put('$test_server_framework_test',
+ fun(init_tc, _Default) -> {fail,fail_init_per_suite};
+ (_, Default) -> Default
+ end),
+ [{require, dummy3}, {default_config, dummy3, "init_per_suite/0"},
+ {timetrap,3000}].
+
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_testcase(_, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ done.
+
+%%--------------------------------------------------------------------
+%% Function: groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%%--------------------------------------------------------------------
+groups() ->
+ [].
+
+%%--------------------------------------------------------------------
+%% Function: all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%%--------------------------------------------------------------------
+all() ->
+ [tc1].
+
+tc1(_) ->
+ exit(should_never_run).
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl
new file mode 100644
index 0000000000..ce94533110
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_11_SUITE.erl
@@ -0,0 +1,134 @@
+%%
+%% %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(cfg_error_11_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% Function: suite() -> Info
+%% Info = [tuple()]
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,2}},
+ {require, dummy0}, {default_config, dummy0, "suite/0"},
+ {require, dummy1}, {default_config, dummy1, "suite/0"},
+ {require, dummy2}, {default_config, dummy2, "suite/0"}].
+
+
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_suite() ->
+ put('$test_server_framework_test',
+ fun(end_tc, _Default) -> {fail,fail_end_per_suite};
+ (_, Default) -> Default
+ end),
+ [{require, dummy3}, {default_config, dummy3, "end_per_suite/0"},
+ {timetrap,3000}].
+
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_testcase(_, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ done.
+
+%%--------------------------------------------------------------------
+%% Function: groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%%--------------------------------------------------------------------
+groups() ->
+ [].
+
+%%--------------------------------------------------------------------
+%% Function: all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%%--------------------------------------------------------------------
+all() ->
+ [tc1, tc2].
+
+tc1() ->
+ [{require, dummy0}, {default_config, dummy0, "tc1"}].
+
+tc1(_) ->
+ dummy.
+
+tc2() ->
+ [{timetrap,1}].
+
+tc2(_) ->
+ dummy.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl
new file mode 100644
index 0000000000..806d3caf72
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_12_SUITE.erl
@@ -0,0 +1,88 @@
+%%
+%% %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(cfg_error_12_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(tc2, _Config) ->
+ timer:sleep(2000),
+ exit(this_should_not_be_printed);
+end_per_testcase(tc4, _Config) ->
+ timer:sleep(2000),
+ exit(this_should_not_be_printed);
+end_per_testcase(_, _) ->
+ ok.
+
+all() ->
+ [tc1, tc2, tc3, tc4].
+
+%%%-----------------------------------------------------------------
+tc1() ->
+ put('$test_server_framework_test',
+ fun(init_tc, _Default) ->
+ ct:pal("init_tc(~p): Night time...",[self()]),
+ timer:sleep(2000),
+ ct:pal("init_tc(~p): Day time!",[self()]),
+ exit(this_should_not_be_printed);
+ (_, Default) -> Default
+ end),
+ [{timetrap,500}].
+
+tc1(_) ->
+ exit(this_should_not_be_printed).
+
+%%%-----------------------------------------------------------------
+tc2() ->
+ [{timetrap,500}].
+
+tc2(_) ->
+ ok.
+
+%%%-----------------------------------------------------------------
+tc3() ->
+ [{timetrap,500}].
+
+tc3(_) ->
+ put('$test_server_framework_test',
+ fun(end_tc, _Default) ->
+ ct:pal("end_tc(~p): Night time...",[self()]),
+ timer:sleep(1000),
+ ct:pal("end_tc(~p): Day time!",[self()]);
+ (_, Default) -> Default
+ end),
+ {comment,"should succeed since ct_fw cancels timetrap in end_tc"}.
+
+%%%-----------------------------------------------------------------
+tc4() ->
+ put('$test_server_framework_test',
+ fun(end_tc, _Default) ->
+ ct:pal("end_tc(~p): Night time...",[self()]),
+ timer:sleep(1000),
+ ct:pal("end_tc(~p): Day time!",[self()]);
+ (_, Default) -> Default
+ end),
+ [{timetrap,500}].
+
+tc4(_) ->
+ ok.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl
new file mode 100644
index 0000000000..c8a3c1d15e
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_13_SUITE.erl
@@ -0,0 +1,47 @@
+%%
+%% %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(cfg_error_13_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+init_per_suite() ->
+ put('$test_server_framework_test',
+ fun(end_tc, _Default) ->
+ ct:pal("end_tc(~p): Night time...",[self()]),
+ timer:sleep(1000),
+ ct:pal("end_tc(~p): Day time!",[self()]);
+ (_, Default) -> Default
+ end),
+ [{timetrap,500}].
+
+init_per_suite(Config) ->
+ ct:comment("should succeed since ct_fw cancels timetrap in end_tc"),
+ Config.
+
+end_per_suite(_) ->
+ ok.
+
+all() ->
+ [tc1].
+
+%%%-----------------------------------------------------------------
+tc1(_) ->
+ dummy.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl
new file mode 100644
index 0000000000..960d0f61b0
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_14_SUITE.erl
@@ -0,0 +1,46 @@
+%%
+%% %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(cfg_error_14_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite() ->
+ put('$test_server_framework_test',
+ fun(end_tc, _Default) ->
+ ct:pal("end_tc(~p): Night time...",[self()]),
+ timer:sleep(1000),
+ ct:pal("end_tc(~p): Day time!",[self()]);
+ (_, Default) -> Default
+ end),
+ [{timetrap,500}].
+
+end_per_suite(_Config) ->
+ {comment,"should succeed since ct_fw cancels timetrap in end_tc"}.
+
+all() ->
+ [tc1].
+
+%%%-----------------------------------------------------------------
+tc1(_) ->
+ dummy.
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl
index bf01bb52d9..08c57887ef 100644
--- a/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/cfg_error_3_SUITE.erl
@@ -37,7 +37,8 @@ suite() ->
%%--------------------------------------------------------------------
init_per_suite(Config) ->
timer:sleep(5000),
- Config.
+ exit(shouldnt_happen).
+% Config.
%%--------------------------------------------------------------------
%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
new file mode 100644
index 0000000000..cb3109349b
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_1_SUITE.erl
@@ -0,0 +1,194 @@
+%%
+%% %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(timetrap_1_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% Function: suite() -> Info
+%% Info = [tuple()]
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,1}}].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ TabPid = spawn(fun() ->
+ ets:new(?MODULE, [named_table, set, public]),
+ ets:insert(?MODULE, {last_case,ok}),
+ receive _ -> ok end
+ end),
+ [{tab,TabPid} | Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_suite(Config) ->
+ exit(?config(tab, Config), kill),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_testcase(TC, Config) ->
+ {_,_} = process_info(?config(tab, Config), priority),
+ [{_,ok}] = ets:lookup(?MODULE, last_case),
+ ets:insert(?MODULE, {last_case,fail}),
+ init_per_testcase1(TC, Config).
+
+init_per_testcase1(tc1, Config) ->
+ [{tc,tc1}|Config];
+
+init_per_testcase1(tc2, Config) ->
+ [{tc,tc2}|Config];
+
+init_per_testcase1(tc3, Config) ->
+ [{tc,tc3}|Config];
+
+init_per_testcase1(tc4, Config) ->
+ [{tc,tc4},{default_timeout,5000}|Config];
+
+init_per_testcase1(tc5, Config) ->
+ [{tc,tc5}|Config];
+
+init_per_testcase1(tc6, Config) ->
+ [{tc,tc6}|Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_testcase(TC, Config) ->
+ {_,_} = process_info(?config(tab, Config), priority),
+ [{_,fail}] = ets:lookup(?MODULE, last_case),
+ ets:insert(?MODULE, {last_case,ok}),
+ end_per_testcase1(TC, Config).
+
+end_per_testcase1(tc1, Config) ->
+ ct:pal("end_per_testcase(tc1): ~p", [Config]),
+ tc1 = ?config(tc, Config),
+ {failed,timetrap_timeout} = ?config(tc_status, Config),
+ ok;
+
+end_per_testcase1(tc2, Config) ->
+ ct:pal("end_per_testcase(tc2): ~p", [Config]),
+ tc2 = ?config(tc, Config),
+ {failed,timetrap_timeout} = ?config(tc_status, Config),
+ timer:sleep(2000);
+
+end_per_testcase1(tc3, Config) ->
+ ct:pal("end_per_testcase(tc3): ~p", [Config]),
+ tc3 = ?config(tc, Config),
+ {failed,{testcase_aborted,testing_end_conf}} = ?config(tc_status, Config),
+ ok;
+
+end_per_testcase1(tc4, Config) ->
+ ct:pal("end_per_testcase(tc4): ~p", [Config]),
+ tc4 = ?config(tc, Config),
+ {failed,{testcase_aborted,testing_end_conf}} = ?config(tc_status, Config),
+ timer:sleep(2000);
+
+end_per_testcase1(tc5, Config) ->
+ ct:pal("end_per_testcase(tc5): ~p", [Config]),
+ tc5 = ?config(tc, Config),
+ exit(end_per_tc_fail_after_timeout);
+
+end_per_testcase1(tc6, Config) ->
+ ct:pal("end_per_testcase(tc6): ~p", [Config]),
+ tc6 = ?config(tc, Config),
+ exit(end_per_tc_fail_after_abort).
+
+%%--------------------------------------------------------------------
+%% Function: groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%%--------------------------------------------------------------------
+groups() ->
+ [].
+
+%%--------------------------------------------------------------------
+%% Function: all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%%--------------------------------------------------------------------
+all() ->
+ [tc1, tc2, tc3, tc4, tc5, tc6].
+
+tc1(_) ->
+ timer:sleep(2000).
+
+tc2(_) ->
+ timer:sleep(2000).
+
+tc3(_) ->
+ spawn(ct, abort_current_testcase, [testing_end_conf]),
+ timer:sleep(2000).
+
+tc4(_) ->
+ spawn(ct, abort_current_testcase, [testing_end_conf]),
+ timer:sleep(2000).
+
+tc5(_) ->
+ timer:sleep(2000).
+
+tc6(_) ->
+ spawn(ct, abort_current_testcase, [testing_end_conf]),
+ timer:sleep(2000).
diff --git a/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl
new file mode 100644
index 0000000000..99bb400137
--- /dev/null
+++ b/lib/common_test/test/ct_error_SUITE_data/error/test/timetrap_2_SUITE.erl
@@ -0,0 +1,138 @@
+%%
+%% %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(timetrap_2_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% Function: suite() -> Info
+%% Info = [tuple()]
+%%--------------------------------------------------------------------
+suite() ->
+ [{timetrap,{seconds,3}},
+ {require,multiply},
+ {require,scale}].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_group(GroupName, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_group(_GroupName, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_group(GroupName, Config0) ->
+%% void() | {save_config,Config1}
+%% GroupName = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_group(_GroupName, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%% Reason = term()
+%%--------------------------------------------------------------------
+init_per_testcase(tc1, Config) ->
+ ct:timetrap({seconds,1}),
+ Config;
+
+init_per_testcase(tc3, Config) ->
+ ct:timetrap({seconds,1}),
+ Config;
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1}
+%% TestCase = atom()
+%% Config0 = Config1 = [tuple()]
+%%--------------------------------------------------------------------
+end_per_testcase(_, Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: groups() -> [Group]
+%% Group = {GroupName,Properties,GroupsAndTestCases}
+%% GroupName = atom()
+%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
+%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
+%% TestCase = atom()
+%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
+%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
+%% repeat_until_any_ok | repeat_until_any_fail
+%% N = integer() | forever
+%%--------------------------------------------------------------------
+groups() ->
+ [].
+
+%%--------------------------------------------------------------------
+%% Function: all() -> GroupsAndTestCases | {skip,Reason}
+%% GroupsAndTestCases = [{group,GroupName} | TestCase]
+%% GroupName = atom()
+%% TestCase = atom()
+%% Reason = term()
+%%--------------------------------------------------------------------
+all() ->
+ [tc0,tc1,tc2].
+
+tc0(_) ->
+ N = list_to_integer(ct:get_config(multiply)),
+ ct:comment(io_lib:format("TO after ~w sec", [3*N])),
+ ct:sleep({seconds,5}),
+ ok.
+
+tc1(_) ->
+ N =list_to_integer( ct:get_config(multiply)),
+ ct:comment(io_lib:format("TO after ~w sec", [1*N])),
+ ct:sleep({seconds,5}),
+ ok.
+
+tc2(_) ->
+ N = list_to_integer(ct:get_config(multiply)),
+ ct:comment(io_lib:format("TO after ~w sec", [0.5*N])),
+ ct:timetrap(500),
+ ct:sleep(2000),
+ ok.
diff --git a/lib/common_test/test/ct_event_handler_SUITE.erl b/lib/common_test/test/ct_event_handler_SUITE.erl
index bafd32f937..00a4c4ded3 100644
--- a/lib/common_test/test/ct_event_handler_SUITE.erl
+++ b/lib/common_test/test/ct_event_handler_SUITE.erl
@@ -88,7 +88,7 @@ start_stop(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -110,8 +110,7 @@ start_stop(Config) when is_list(Config) ->
{eh_A,test_done,{'DEF','STOP_TIME'}},
{eh_A,stop_logging,[]}],
- ok = ct_test_support:verify_events(TestEvents, Events, Config),
- {comment,"NOTE! Known problem with test_start event!"}.
+ ok = ct_test_support:verify_events(TestEvents++TestEvents, Events, Config).
results(doc) ->
@@ -135,7 +134,7 @@ results(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -163,7 +162,7 @@ results(Config) when is_list(Config) ->
{eh_A,test_done,{'DEF','STOP_TIME'}},
{eh_A,stop_logging,[]}],
- ok = ct_test_support:verify_events(TestEvents, Events, Config).
+ ok = ct_test_support:verify_events(TestEvents++TestEvents, Events, Config).
%%%-----------------------------------------------------------------
diff --git a/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl b/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl
index 6e526f15a2..54cf3a22e7 100644
--- a/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl
+++ b/lib/common_test/test/ct_event_handler_SUITE_data/eh_A.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -44,6 +44,19 @@
%% Description: Whenever a new event handler is added to an event manager,
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
+init(String = [X|_]) when is_integer(X) ->
+ case erl_scan:string(String++".") of
+ {ok,Ts,_} ->
+ case erl_parse:parse_term(Ts) of
+ {ok,Args} ->
+ init(Args);
+ _ ->
+ init(String)
+ end;
+ _ ->
+ init(String)
+ end;
+
init(Args) ->
S1 = case lists:keysearch(cbm, 1, Args) of
diff --git a/lib/common_test/test/ct_groups_test_1_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE.erl
index 1761b773f5..64d61fc104 100644
--- a/lib/common_test/test/ct_groups_test_1_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_1_SUITE.erl
@@ -76,14 +76,14 @@ groups_suite_1(Config) when is_list(Config) ->
Suite = filename:join(DataDir, "groups_1/test/groups_11_SUITE"),
{Opts,ERPid} = setup({suite,Suite}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(groups_suite_1,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(groups_suite_1),
+ TestEvents = events_to_check(groups_suite_1),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -96,14 +96,14 @@ groups_suite_2(Config) when is_list(Config) ->
Suite = filename:join(DataDir, "groups_1/test/groups_12_SUITE"),
{Opts,ERPid} = setup({suite,Suite}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(groups_suite_2,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(groups_suite_2),
+ TestEvents = events_to_check(groups_suite_2),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -117,14 +117,14 @@ groups_suites_1(Config) when is_list(Config) ->
filename:join(DataDir, "groups_1/test/groups_12_SUITE")],
{Opts,ERPid} = setup({suite,Suites}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(groups_suites_1,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(groups_suites_1),
+ TestEvents = events_to_check(groups_suites_1),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -137,14 +137,14 @@ groups_dir_1(Config) when is_list(Config) ->
Dir = filename:join(DataDir, "groups_1"),
{Opts,ERPid} = setup({dir,Dir}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(groups_dir_1,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(groups_dir_1),
+ TestEvents = events_to_check(groups_dir_1),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -157,14 +157,14 @@ groups_dirs_1(Config) when is_list(Config) ->
filename:join(DataDir, "groups_2")],
{Opts,ERPid} = setup({dir,Dirs}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(groups_dirs_1,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(groups_dirs_1),
+ TestEvents = events_to_check(groups_dirs_1),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -188,6 +188,14 @@ reformat(Events, EH) ->
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
test_events(groups_suite_1) ->
[{?eh,start_logging,{'DEF','RUNDIR'}},
@@ -327,14 +335,14 @@ test_events(groups_suite_2) ->
{?eh,tc_start,{groups_12_SUITE,testcase_2a}},
{?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}},
- [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}},
+ [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3a}},
{?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3b}},
{?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}},
- {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}],
+ {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}],
[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}},
@@ -361,12 +369,8 @@ test_events(groups_suite_2) ->
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}},
-
- %% the done event could come in during the parallel subgroup
- %% and we can't test that, yet...
- %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
- %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
-
+ {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
+ {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}},
@@ -525,14 +529,14 @@ test_events(groups_suites_1) ->
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_2,[parallel]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_2a}},
{?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}},
- [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}},
+ [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3a}},
{?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3b}},
{?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}},
- {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}],
+ {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}],
[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3a}},
@@ -555,12 +559,8 @@ test_events(groups_suites_1) ->
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_4,[]},ok}},
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}},
-
- %% the done event could come in during the parallel subgroup
- %% and we can't test that, yet...
- %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
- %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
-
+ {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
+ {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}},
[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_7,[sequence]}}},
@@ -715,14 +715,14 @@ test_events(groups_dir_1) ->
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_2,[parallel]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_2a}},
{?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}},
- [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}},
+ [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3a}},
{?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3b}},
{?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}},
- {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}],
+ {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}],
[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3a}},
@@ -745,12 +745,8 @@ test_events(groups_dir_1) ->
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_4,[]},ok}},
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}},
-
- %% the done event could come in during the parallel subgroup
- %% and we can't test that, yet...
- %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
- %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
-
+ {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
+ {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}},
[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_7,[sequence]}}},
@@ -906,14 +902,14 @@ test_events(groups_dirs_1) ->
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_2,[parallel]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_2a}},
{?eh,tc_done,{groups_12_SUITE,testcase_2a,ok}},
- [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}},
+ [{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3a}},
{?eh,tc_done,{groups_12_SUITE,testcase_3a,ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3b}},
{?eh,tc_done,{groups_12_SUITE,testcase_3b,ok}},
- {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}},
- {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}],
+ {?eh,tc_start,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}},
+ {?eh,tc_done,{groups_12_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}],
[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_3,[]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_3,[]},ok}},
{?eh,tc_start,{groups_12_SUITE,testcase_3a}},
@@ -936,12 +932,8 @@ test_events(groups_dirs_1) ->
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_4,[]},ok}},
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_5,[parallel]},ok}},
-
- %% the done event could come in during the parallel subgroup
- %% and we can't test that, yet...
- %% {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
- %% {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
-
+ {?eh,tc_start,{groups_12_SUITE,testcase_5a}},
+ {?eh,tc_done,{groups_12_SUITE,testcase_5a,ok}},
{parallel,[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]}}},
{?eh,tc_done,{groups_12_SUITE,{init_per_group,test_group_6,[parallel]},ok}},
[{?eh,tc_start,{groups_12_SUITE,{init_per_group,test_group_7,[sequence]}}},
@@ -1138,17 +1130,17 @@ test_events(groups_dirs_1) ->
{?eh,tc_start,{groups_22_SUITE,testcase_2a}},
{?eh,tc_done,{groups_22_SUITE,testcase_2a,ok}},
[{?eh,tc_start,
- {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,1}]}}},
+ {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,2}]}}},
{?eh,tc_done,
- {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,1}]},ok}},
+ {groups_22_SUITE,{init_per_group,test_group_3,[{repeat,2}]},ok}},
{?eh,tc_start,{groups_22_SUITE,testcase_3a}},
{?eh,tc_done,{groups_22_SUITE,testcase_3a,ok}},
{?eh,tc_start,{groups_22_SUITE,testcase_3b}},
{?eh,tc_done,{groups_22_SUITE,testcase_3b,ok}},
{?eh,tc_start,
- {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,1}]}}},
+ {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,2}]}}},
{?eh,tc_done,
- {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,1}]},ok}}],
+ {groups_22_SUITE,{end_per_group,test_group_3,[{repeat,2}]},ok}}],
[{?eh,tc_start,
{groups_22_SUITE,{init_per_group,test_group_3,[]}}},
{?eh,tc_done,
@@ -1181,12 +1173,8 @@ test_events(groups_dirs_1) ->
{groups_22_SUITE,{init_per_group,test_group_5,[parallel]}}},
{?eh,tc_done,
{groups_22_SUITE,{init_per_group,test_group_5,[parallel]},ok}},
-
- %% the done event could come in during the parallel subgroup
- %% and we can't test that, yet...
- %% {?eh,tc_start,{groups_22_SUITE,testcase_5a}},
- %% {?eh,tc_done,{groups_22_SUITE,testcase_5a,ok}},
-
+ {?eh,tc_start,{groups_22_SUITE,testcase_5a}},
+ {?eh,tc_done,{groups_22_SUITE,testcase_5a,ok}},
{parallel,
[{?eh,tc_start,
{groups_22_SUITE,{init_per_group,test_group_6,[parallel]}}},
diff --git a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl
index b261ef581f..ec90ef95d1 100644
--- a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_1/test/groups_12_SUITE.erl
@@ -37,7 +37,7 @@ groups() ->
{test_group_2, [parallel], [testcase_2a,
- {test_group_3, [{repeat,1}],
+ {test_group_3, [{repeat,2}],
[testcase_3a, testcase_3b]},
testcase_2b]},
@@ -102,8 +102,8 @@ init_per_group(Group, Config) ->
io_lib:format("shuffled, ~w", [S]);
{test_group_1b,[{name,test_group_1b},parallel]} -> "parallel";
{test_group_2,[{name,test_group_2},parallel]} -> "parallel";
- {test_group_3,[{name,test_group_3},{repeat,1}]} -> "repeat 1";
- {test_group_3,[{name,test_group_3}]} -> "repeat 0";
+ {test_group_3,[{name,test_group_3},{repeat,2}]} -> "repeat 2";
+ {test_group_3,[{name,test_group_3}]} -> "repeat 1";
{test_group_4,[{name,test_group_4}]} -> ok;
{test_group_5,[{name,test_group_5},parallel]} -> "parallel";
{test_group_6,[{name,test_group_6},parallel]} -> "parallel";
@@ -275,6 +275,10 @@ testcase_5a(Config) ->
test_group_5 = ?config(test_group_5,Config),
undefined = ?config(testcase_3,Config),
testcase_5a = ?config(testcase_5a,Config),
+ %% increase chance the done event will come
+ %% during execution of subgroup (could be
+ %% tricky to handle)
+ timer:sleep(3),
ok.
testcase_5b() ->
[].
diff --git a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl
index 2e19cf6310..ec0adc5df0 100644
--- a/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_1_SUITE_data/groups_2/test/groups_22_SUITE.erl
@@ -37,7 +37,7 @@ groups() ->
{test_group_2, [parallel], [testcase_2a,
- {test_group_3, [{repeat,1}],
+ {test_group_3, [{repeat,2}],
[testcase_3a, testcase_3b]},
testcase_2b]},
@@ -102,8 +102,8 @@ init_per_group(Group, Config) ->
io_lib:format("shuffled, ~w", [S]);
{test_group_1b,[{name,test_group_1b},parallel]} -> "parallel";
{test_group_2,[{name,test_group_2},parallel]} -> "parallel";
- {test_group_3,[{name,test_group_3},{repeat,1}]} -> "repeat 1";
- {test_group_3,[{name,test_group_3}]} -> "repeat 0";
+ {test_group_3,[{name,test_group_3},{repeat,2}]} -> "repeat 2";
+ {test_group_3,[{name,test_group_3}]} -> "repeat 1";
{test_group_4,[{name,test_group_4}]} -> ok;
{test_group_5,[{name,test_group_5},parallel]} -> "parallel";
{test_group_6,[{name,test_group_6},parallel]} -> "parallel";
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE.erl
index 5a60d855b7..56e0ac30c7 100644
--- a/lib/common_test/test/ct_groups_test_2_SUITE.erl
+++ b/lib/common_test/test/ct_groups_test_2_SUITE.erl
@@ -60,7 +60,7 @@ all(doc) ->
["Run smoke tests of Common Test."];
all(suite) ->
- [missing_conf].
+ [missing_conf, testspec_1, repeat_1].
%%--------------------------------------------------------------------
%% TEST CASES
@@ -75,17 +75,55 @@ missing_conf(Config) when is_list(Config) ->
Suite = filename:join(DataDir, "groups_1/missing_conf_SUITE"),
{Opts,ERPid} = setup({suite,Suite}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(missing_conf_SUITE,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(missing_conf),
+ TestEvents = events_to_check(missing_conf),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+%%%-----------------------------------------------------------------
+%%%
+
+testspec_1(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+
+ TestSpec = filename:join(DataDir, "specs/groups_2.1.spec"),
+
+ {Opts,ERPid} = setup({spec,TestSpec}, Config),
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(testspec_1,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config)),
+
+ TestEvents = events_to_check(testspec_1),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
+%%%
+
+repeat_1(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+
+ Suite = filename:join(DataDir, "groups_1/repeat_1_SUITE"),
+
+ {Opts,ERPid} = setup({suite,Suite}, Config),
+ ok = ct_test_support:run(Opts, Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(repeat_1,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config)),
+
+ TestEvents = events_to_check(repeat_1),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+%%%-----------------------------------------------------------------
%%% HELP FUNCTIONS
%%%-----------------------------------------------------------------
@@ -105,6 +143,130 @@ reformat(Events, EH) ->
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
test_events(missing_conf) ->
- exit(must_handle_this).
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,2}},
+ {?eh,tc_start,{ct_framework,ct_init_per_group}},
+ {?eh,tc_done,{ct_framework,ct_init_per_group,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{missing_conf_SUITE,tc1}},
+ {?eh,tc_done,{missing_conf_SUITE,tc1,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ {?eh,tc_start,{missing_conf_SUITE,tc2}},
+ {?eh,tc_done,{missing_conf_SUITE,tc2,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,{ct_framework,ct_end_per_group}},
+ {?eh,tc_done,{ct_framework,ct_end_per_group,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ];
+
+test_events(testspec_1) ->
+ [];
+
+test_events(repeat_1) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{1,1,unknown}},
+ {?eh,tc_start,{repeat_1_SUITE,init_per_suite}},
+ {?eh,tc_done,{repeat_1_SUITE,init_per_suite,ok}},
+ [{?eh,tc_start,
+ {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,2}]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,{init_per_group,test_group_1,[{repeat,2}]},ok}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_1a}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_1a,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_1b}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_1b,ok}},
+ {?eh,test_stats,{2,0,{0,0}}},
+ {?eh,tc_start,
+ {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,2}]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,{end_per_group,test_group_1,[{repeat,2}]},ok}}],
+ [{?eh,tc_start,
+ {repeat_1_SUITE,{init_per_group,test_group_1,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,{init_per_group,test_group_1,[]},ok}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_1a}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_1a,ok}},
+ {?eh,test_stats,{3,0,{0,0}}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_1b}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_1b,ok}},
+ {?eh,test_stats,{4,0,{0,0}}},
+ {?eh,tc_start,
+ {repeat_1_SUITE,{end_per_group,test_group_1,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,{end_per_group,test_group_1,[]},ok}}],
+ [{?eh,tc_start,
+ {repeat_1_SUITE,{init_per_group,test_group_2,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,{init_per_group,test_group_2,[]},ok}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_2a}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_2a,ok}},
+ {?eh,test_stats,{5,0,{0,0}}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_2b}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_2b,ok}},
+ {?eh,test_stats,{6,0,{0,0}}},
+ {?eh,tc_start,
+ {repeat_1_SUITE,{end_per_group,test_group_2,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,{end_per_group,test_group_2,[]},ok}}],
+ [{?eh,tc_start,
+ {repeat_1_SUITE,
+ {init_per_group,test_group_3,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,
+ {init_per_group,test_group_3,[]},
+ ok}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_3a}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_3a,ok}},
+ {?eh,test_stats,{7,0,{0,0}}},
+ [{?eh,tc_start,
+ {repeat_1_SUITE,
+ {init_per_group,test_group_4,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,
+ {init_per_group,test_group_4,[]},
+ ok}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_4a}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_4a,ok}},
+ {?eh,test_stats,{8,0,{0,0}}},
+ {?eh,tc_start,{repeat_1_SUITE,testcase_4b}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_4b,ok}},
+ {?eh,test_stats,{9,0,{0,0}}},
+ {?eh,tc_start,
+ {repeat_1_SUITE,
+ {end_per_group,test_group_4,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,
+ {end_per_group,test_group_4,[]},
+ ok}}],
+ {?eh,tc_start,{repeat_1_SUITE,testcase_3b}},
+ {?eh,tc_done,{repeat_1_SUITE,testcase_3b,ok}},
+ {?eh,test_stats,{10,0,{0,0}}},
+ {?eh,tc_start,
+ {repeat_1_SUITE,
+ {end_per_group,test_group_3,[]}}},
+ {?eh,tc_done,
+ {repeat_1_SUITE,
+ {end_per_group,test_group_3,[]},
+ ok}}],
+ {?eh,tc_start,{repeat_1_SUITE,end_per_suite}},
+ {?eh,tc_done,{repeat_1_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ].
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg b/lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg
new file mode 100644
index 0000000000..4928505157
--- /dev/null
+++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/cfgs/groups_2.1.cfg
@@ -0,0 +1 @@
+{dummy_key, "dummy_data"}.
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl
new file mode 100644
index 0000000000..b4b9b03ca5
--- /dev/null
+++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_1/repeat_1_SUITE.erl
@@ -0,0 +1,105 @@
+%%
+%% %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(repeat_1_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%====================================================================
+%% COMMON TEST CALLBACK FUNCTIONS
+%%====================================================================
+
+suite() ->
+ [{timetrap,{minutes,1}}].
+
+groups() ->
+ [
+ {test_group_1, [{repeat,2}], [testcase_1a,testcase_1b]},
+ {test_group_2, [{repeat,1}], [testcase_2a,testcase_2b]},
+
+ {test_group_3, [{repeat_until_all_fail,1}],
+ [testcase_3a,
+ {test_group_4, [{repeat_until_any_fail,1}],
+ [testcase_4a, testcase_4b]},
+ testcase_3b]}
+ ].
+
+all() ->
+ [
+ {group, test_group_1},
+ {group, test_group_2},
+ {group, test_group_3}
+ ].
+
+%%--------------------------------------------------------------------
+%% Suite Configuration
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Group Configuration
+%%--------------------------------------------------------------------
+
+init_per_group(Group, Config) ->
+ Group = proplists:get_value(name,?config(tc_group_properties,Config)),
+ ct:comment(Group),
+ Config.
+
+end_per_group(_Group, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Testcase Configuration
+%%--------------------------------------------------------------------
+
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Testcases
+%%--------------------------------------------------------------------
+
+testcase_1a(_) ->
+ ok.
+testcase_1b(_) ->
+ ok.
+
+testcase_2a(_) ->
+ ok.
+testcase_2b(_) ->
+ ok.
+
+testcase_3a(_) ->
+ ok.
+testcase_3b(_) ->
+ ok.
+
+testcase_4a(_) ->
+ ok.
+testcase_4b(_) ->
+ ok.
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl
new file mode 100644
index 0000000000..2533ac8e84
--- /dev/null
+++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_21_SUITE.erl
@@ -0,0 +1,281 @@
+%%
+%% %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(groups_21_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%====================================================================
+%% COMMON TEST CALLBACK FUNCTIONS
+%%====================================================================
+
+suite() ->
+ [{timetrap,{minutes,1}}].
+
+groups() ->
+ [
+ {test_group_1a, [testcase_1a,testcase_1b]},
+
+ {test_group_1b, [], [testcase_1a,testcase_1b]},
+
+ {test_group_2, [], [testcase_2a,
+
+ {test_group_3, [], [testcase_3a,
+ testcase_3b]},
+ testcase_2b]},
+
+ {test_group_4, [{test_group_5, [], [testcase_5a,
+
+ {group, test_group_6},
+
+ testcase_5b]}]},
+
+ {test_group_6, [{group, test_group_7}]},
+
+ {test_group_7, [testcase_7a,testcase_7b]}
+ ].
+
+all() ->
+ [testcase_1,
+ {group, test_group_1a},
+ {group, test_group_1b},
+ testcase_2,
+ {group, test_group_2},
+ testcase_3,
+ {group, test_group_4}].
+
+%% this func only for internal test purposes
+grs_and_tcs() ->
+ {[
+ test_group_1a, test_group_1b,
+ test_group_2, test_group_3,
+ test_group_4, test_group_5,
+ test_group_6, test_group_7
+ ],
+ [
+ testcase_1,
+ testcase_1a, testcase_1b,
+ testcase_2,
+ testcase_2a, testcase_2b,
+ testcase_3a, testcase_3b,
+ testcase_3,
+ testcase_5a, testcase_5b,
+ testcase_7a, testcase_7b
+ ]}.
+
+%%--------------------------------------------------------------------
+%% Suite Configuration
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ [{suite,init}|Config].
+
+end_per_suite(Config) ->
+ init = ?config(suite,Config).
+
+%%--------------------------------------------------------------------
+%% Group Configuration
+%%--------------------------------------------------------------------
+
+init_per_group(Group, Config) ->
+ [{name,Group}] = ?config(tc_group_properties,Config),
+ {Grs,_} = grs_and_tcs(),
+ case lists:member(Group, Grs) of
+ true ->
+ ct:comment(io_lib:format("~w", [Group])),
+ init = ?config(suite,Config),
+ [{Group,Group} | Config];
+ false ->
+ ct:fail({bad_group,Group})
+ end.
+
+end_per_group(Group, Config) ->
+ {Grs,_} = grs_and_tcs(),
+ case lists:member(Group, Grs) of
+ true ->
+ init = ?config(suite,Config),
+ Group = ?config(Group,Config),
+ ok;
+ false ->
+ ct:fail({bad_group,Group})
+ end.
+
+%%--------------------------------------------------------------------
+%% Testcase Configuration
+%%--------------------------------------------------------------------
+
+init_per_testcase(TestCase, Config) ->
+ {_,TCs} = grs_and_tcs(),
+ case lists:member(TestCase, TCs) of
+ true ->
+ init = ?config(suite,Config),
+ [{TestCase,TestCase} | Config];
+ false ->
+ ct:fail({unknown_testcase,TestCase})
+ end.
+
+end_per_testcase(TestCase, Config) ->
+ {_,TCs} = grs_and_tcs(),
+ case lists:member(TestCase, TCs) of
+ true ->
+ init = ?config(suite,Config),
+ TestCase = ?config(TestCase,Config),
+ ok;
+ false ->
+ ct:fail({unknown_testcase,TestCase})
+ end.
+
+
+%%--------------------------------------------------------------------
+%% Testcases
+%%--------------------------------------------------------------------
+
+testcase_1() ->
+ [].
+testcase_1(Config) ->
+ init = ?config(suite,Config),
+ testcase_1 = ?config(testcase_1,Config),
+ ok.
+
+testcase_1a() ->
+ [].
+testcase_1a(Config) ->
+ init = ?config(suite,Config),
+ case ?config(test_group_1a,Config) of
+ test_group_1a -> ok;
+ _ ->
+ case ?config(test_group_1b,Config) of
+ test_group_1b -> ok;
+ _ -> ct:fail(no_group_data)
+ end
+ end,
+ testcase_1a = ?config(testcase_1a,Config),
+ ok.
+testcase_1b() ->
+ [].
+testcase_1b(Config) ->
+ init = ?config(suite,Config),
+ case ?config(test_group_1a,Config) of
+ test_group_1a -> ok;
+ _ ->
+ case ?config(test_group_1b,Config) of
+ test_group_1b -> ok;
+ _ -> ct:fail(no_group_data)
+ end
+ end,
+ undefined = ?config(testcase_1a,Config),
+ testcase_1b = ?config(testcase_1b,Config),
+ ok.
+
+testcase_2() ->
+ [].
+testcase_2(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_1a,Config),
+ undefined = ?config(test_group_1b,Config),
+ testcase_2 = ?config(testcase_2,Config),
+ ok.
+
+testcase_2a() ->
+ [].
+testcase_2a(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ testcase_2a = ?config(testcase_2a,Config),
+ ok.
+testcase_2b() ->
+ [].
+testcase_2b(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ undefined = ?config(testcase_2a,Config),
+ testcase_2b = ?config(testcase_2b,Config),
+ ok.
+
+testcase_3a() ->
+ [].
+testcase_3a(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ test_group_3 = ?config(test_group_3,Config),
+ undefined = ?config(testcase_2b,Config),
+ testcase_3a = ?config(testcase_3a,Config),
+ ok.
+testcase_3b() ->
+ [].
+testcase_3b(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ test_group_3 = ?config(test_group_3,Config),
+ undefined = ?config(testcase_3a,Config),
+ testcase_3b = ?config(testcase_3b,Config),
+ ok.
+
+testcase_3() ->
+ [].
+testcase_3(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_2,Config),
+ undefined = ?config(test_group_3,Config),
+ testcase_3 = ?config(testcase_3,Config),
+ ok.
+
+testcase_5a() ->
+ [].
+testcase_5a(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_3,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ undefined = ?config(testcase_3,Config),
+ testcase_5a = ?config(testcase_5a,Config),
+ ok.
+testcase_5b() ->
+ [].
+testcase_5b(Config) ->
+ init = ?config(suite,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ undefined = ?config(testcase_5a,Config),
+ testcase_5b = ?config(testcase_5b,Config),
+ ok.
+
+testcase_7a() ->
+ [].
+testcase_7a(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_3,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ test_group_6 = ?config(test_group_6,Config),
+ test_group_7 = ?config(test_group_7,Config),
+ testcase_7a = ?config(testcase_7a,Config),
+ ok.
+testcase_7b() ->
+ [].
+testcase_7b(Config) ->
+ init = ?config(suite,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ test_group_6 = ?config(test_group_6,Config),
+ test_group_7 = ?config(test_group_7,Config),
+ undefined = ?config(testcase_7a,Config),
+ testcase_7b = ?config(testcase_7b,Config),
+ ok.
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl
new file mode 100644
index 0000000000..cd517876df
--- /dev/null
+++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/groups_2/groups_22_SUITE.erl
@@ -0,0 +1,314 @@
+%%
+%% %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(groups_22_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%====================================================================
+%% COMMON TEST CALLBACK FUNCTIONS
+%%====================================================================
+
+suite() ->
+ [{timetrap,{minutes,1}}].
+
+groups() ->
+ [
+ {test_group_1a, [shuffle], [testcase_1a,testcase_1b,testcase_1c]},
+
+ {test_group_1b, [parallel], [testcase_1a,testcase_1b]},
+
+ {test_group_2, [parallel], [testcase_2a,
+
+ {test_group_3, [{repeat,1}],
+ [testcase_3a, testcase_3b]},
+
+ testcase_2b]},
+
+ {test_group_4, [{test_group_5, [parallel], [testcase_5a,
+
+ {group, test_group_6},
+
+ testcase_5b]}]},
+
+ {test_group_6, [parallel], [{group, test_group_7}]},
+
+ {test_group_7, [sequence], [testcase_7a,testcase_7b]}
+ ].
+
+all() ->
+ [{group, test_group_1a},
+ {group, test_group_1b},
+ testcase_1,
+ testcase_2,
+ {group, test_group_2},
+ testcase_3,
+ {group, test_group_4}].
+
+%% this func only for internal test purposes
+grs_and_tcs() ->
+ {[
+ test_group_1a, test_group_1b,
+ test_group_2, test_group_3,
+ test_group_4, test_group_5,
+ test_group_6, test_group_7
+ ],
+ [
+ testcase_1a, testcase_1b, testcase_1c,
+ testcase_1,
+ testcase_2,
+ testcase_2a, testcase_2b,
+ testcase_3a, testcase_3b,
+ testcase_3,
+ testcase_5a, testcase_5b,
+ testcase_7a, testcase_7b
+ ]}.
+
+%%--------------------------------------------------------------------
+%% Suite Configuration
+%%--------------------------------------------------------------------
+
+init_per_suite(Config) ->
+ [{suite,init}|Config].
+
+end_per_suite(Config) ->
+ init = ?config(suite,Config).
+
+%%--------------------------------------------------------------------
+%% Group Configuration
+%%--------------------------------------------------------------------
+
+init_per_group(Group, Config) ->
+ Cmt =
+ case {Group,?config(tc_group_properties,Config)} of
+ {test_group_1a,[{shuffle,S},{name,test_group_1a}]} ->
+ io_lib:format("shuffled, ~w", [S]);
+ {test_group_1b,[{name,test_group_1b},parallel]} -> "parallel";
+ {test_group_2,[{name,test_group_2},parallel]} -> "parallel";
+ {test_group_3,[{name,test_group_3},{repeat,1}]} -> "repeat 1";
+ {test_group_3,[{name,test_group_3}]} -> "repeat 0";
+ {test_group_4,[{name,test_group_4}]} -> ok;
+ {test_group_5,[{name,test_group_5},parallel]} -> "parallel";
+ {test_group_6,[{name,test_group_6},parallel]} -> "parallel";
+ {test_group_7,[{name,test_group_7},sequence]} -> "sequence"
+ end,
+ {Grs,_} = grs_and_tcs(),
+ case lists:member(Group, Grs) of
+ true ->
+ init = ?config(suite,Config),
+ ct:comment(io_lib:format("~w, ~s", [Group,Cmt])),
+ [{Group,Group} | Config];
+ false ->
+ ct:fail({bad_group,Group})
+ end.
+
+end_per_group(Group, Config) ->
+ {Grs,_} = grs_and_tcs(),
+ case lists:member(Group, Grs) of
+ true ->
+ init = ?config(suite,Config),
+ Group = ?config(Group,Config),
+ ok;
+ false ->
+ ct:fail({bad_group,Group})
+ end.
+
+%%--------------------------------------------------------------------
+%% Testcase Configuration
+%%--------------------------------------------------------------------
+
+init_per_testcase(TestCase, Config) ->
+ {_,TCs} = grs_and_tcs(),
+ case lists:member(TestCase, TCs) of
+ true ->
+ init = ?config(suite,Config),
+ [{TestCase,TestCase} | Config];
+ false ->
+ ct:fail({unknown_testcase,TestCase})
+ end.
+
+end_per_testcase(TestCase, Config) ->
+ {_,TCs} = grs_and_tcs(),
+ case lists:member(TestCase, TCs) of
+ true ->
+ init = ?config(suite,Config),
+ TestCase = ?config(TestCase,Config),
+ ok;
+ false ->
+ ct:fail({unknown_testcase,TestCase})
+ end.
+
+
+%%--------------------------------------------------------------------
+%% Testcases
+%%--------------------------------------------------------------------
+
+testcase_1a() ->
+ [].
+testcase_1a(Config) ->
+ init = ?config(suite,Config),
+ case ?config(test_group_1a,Config) of
+ test_group_1a -> ok;
+ _ ->
+ case ?config(test_group_1b,Config) of
+ test_group_1b -> ok;
+ _ -> ct:fail(no_group_data)
+ end
+ end,
+ testcase_1a = ?config(testcase_1a,Config),
+ ok.
+testcase_1b() ->
+ [].
+testcase_1b(Config) ->
+ init = ?config(suite,Config),
+ case ?config(test_group_1a,Config) of
+ test_group_1a -> ok;
+ _ ->
+ case ?config(test_group_1b,Config) of
+ test_group_1b -> ok;
+ _ -> ct:fail(no_group_data)
+ end
+ end,
+ undefined = ?config(testcase_1a,Config),
+ testcase_1b = ?config(testcase_1b,Config),
+ ok.
+
+testcase_1c() ->
+ [].
+testcase_1c(Config) ->
+ init = ?config(suite,Config),
+ case ?config(test_group_1a,Config) of
+ test_group_1a -> ok;
+ _ ->
+ case ?config(test_group_1b,Config) of
+ test_group_1b -> ok;
+ _ -> ct:fail(no_group_data)
+ end
+ end,
+ undefined = ?config(testcase_1b,Config),
+ testcase_1c = ?config(testcase_1c,Config),
+ ok.
+
+testcase_1() ->
+ [].
+testcase_1(Config) ->
+ init = ?config(suite,Config),
+ testcase_1 = ?config(testcase_1,Config),
+ ok.
+
+testcase_2() ->
+ [].
+testcase_2(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_1a,Config),
+ undefined = ?config(test_group_1b,Config),
+ testcase_2 = ?config(testcase_2,Config),
+ ok.
+
+testcase_2a() ->
+ [].
+testcase_2a(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ testcase_2a = ?config(testcase_2a,Config),
+ ok.
+testcase_2b() ->
+ [].
+testcase_2b(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ undefined = ?config(testcase_2a,Config),
+ testcase_2b = ?config(testcase_2b,Config),
+ ok.
+
+testcase_3a() ->
+ [].
+testcase_3a(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ test_group_3 = ?config(test_group_3,Config),
+ undefined = ?config(testcase_2b,Config),
+ testcase_3a = ?config(testcase_3a,Config),
+ ok.
+testcase_3b() ->
+ [].
+testcase_3b(Config) ->
+ init = ?config(suite,Config),
+ test_group_2 = ?config(test_group_2,Config),
+ test_group_3 = ?config(test_group_3,Config),
+ undefined = ?config(testcase_3a,Config),
+ testcase_3b = ?config(testcase_3b,Config),
+ ok.
+
+testcase_3() ->
+ [].
+testcase_3(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_2,Config),
+ undefined = ?config(test_group_3,Config),
+ testcase_3 = ?config(testcase_3,Config),
+ ok.
+
+testcase_5a() ->
+ [].
+testcase_5a(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_3,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ undefined = ?config(testcase_3,Config),
+ testcase_5a = ?config(testcase_5a,Config),
+ %% increase chance the done event will come
+ %% during execution of subgroup (could be
+ %% tricky to handle)
+ timer:sleep(3),
+ ok.
+testcase_5b() ->
+ [].
+testcase_5b(Config) ->
+ init = ?config(suite,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ undefined = ?config(testcase_5a,Config),
+ testcase_5b = ?config(testcase_5b,Config),
+ ok.
+
+testcase_7a() ->
+ [].
+testcase_7a(Config) ->
+ init = ?config(suite,Config),
+ undefined = ?config(test_group_3,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ test_group_6 = ?config(test_group_6,Config),
+ test_group_7 = ?config(test_group_7,Config),
+ testcase_7a = ?config(testcase_7a,Config),
+ ok.
+testcase_7b() ->
+ [].
+testcase_7b(Config) ->
+ init = ?config(suite,Config),
+ test_group_4 = ?config(test_group_4,Config),
+ test_group_5 = ?config(test_group_5,Config),
+ test_group_6 = ?config(test_group_6,Config),
+ test_group_7 = ?config(test_group_7,Config),
+ undefined = ?config(testcase_7a,Config),
+ testcase_7b = ?config(testcase_7b,Config),
+ ok.
diff --git a/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec b/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec
new file mode 100644
index 0000000000..24d778ac44
--- /dev/null
+++ b/lib/common_test/test/ct_groups_test_2_SUITE_data/specs/groups_2.1.spec
@@ -0,0 +1,26 @@
+
+{config, "../cfgs/groups_2.1.cfg"}.
+{alias, groups_2, "../groups_2"}.
+
+{suites, groups_2, groups_21_SUITE}.
+%{skip_groups, groups_2, groups_21_SUITE,
+% [test_group_1b, test_group_7], "Skip tg_1b & tg_7"}.
+{skip_cases, groups_2, groups_21_SUITE,
+ [testcase_1b, testcase_3a], "Skip tc_1b & tc_3a"}.
+
+{groups, groups_2, groups_22_SUITE,
+ test_group_1a}.
+{skip_cases, groups_2, groups_22_SUITE,
+ testcase_1a, "Skip tc_1a"}.
+
+{groups, groups_2, groups_22_SUITE,
+ test_group_1b}.
+{skip_cases, groups_2, groups_22_SUITE,
+ testcase_1b, "Skip tc_1b"}.
+%{skip_groups, groups_2, groups_21_SUITE,
+% [test_group_3], "Skip tg_3"}.
+
+{groups, groups_2, groups_22_SUITE,
+ test_group_5}.
+{skip_cases, groups_2, groups_22_SUITE,
+ [testcase_7a, testcase_7b], "Skip tc_7a & tc_7b"}.
diff --git a/lib/common_test/test/ct_master_SUITE.erl b/lib/common_test/test/ct_master_SUITE.erl
new file mode 100644
index 0000000000..87e2c3049a
--- /dev/null
+++ b/lib/common_test/test/ct_master_SUITE.erl
@@ -0,0 +1,136 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_master_SUITE
+%%%
+%%% Description:
+%%% Test ct_master.
+%%%
+%%% The suites used for the test are located in the data directory.
+%%%-------------------------------------------------------------------
+-module(ct_master_SUITE).
+-compile(export_all).
+
+-include_lib("test_server/include/test_server.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config1 = ct_test_support:init_per_suite(Config),
+ Config1.
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, [{master, true}|Config]).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+all(doc) ->
+ [""];
+
+all(suite) ->
+ [
+ ct_master_test
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+ct_master_test(Config) when is_list(Config)->
+ NodeCount = 5,
+ DataDir = ?config(data_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ NodeNames = [list_to_atom("testnode_"++integer_to_list(N)) ||
+ N <- lists:seq(1, NodeCount)],
+ FileName = filename:join(PrivDir, "ct_master_spec.spec"),
+ Suites = [master_SUITE],
+ TSFile = make_spec(DataDir, FileName, NodeNames, Suites, Config),
+ [{TSFile, ok}] = run_test(ct_master_test, FileName, Config),
+ ok.
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+make_spec(DataDir, FileName, NodeNames, Suites, Config)->
+ {ok, HostName} = inet:gethostname(),
+
+ N = lists:map(fun(NodeName)->
+ {node, NodeName, list_to_atom(atom_to_list(NodeName)++"@"++HostName)}
+ end,
+ NodeNames),
+
+ C = lists:map(fun(NodeName)->
+ Rnd = random:uniform(2),
+ if Rnd == 1->
+ {config, NodeName, filename:join(DataDir, "master/config.txt")};
+ true->
+ {userconfig, NodeName, {ct_config_xml, filename:join(DataDir, "master/config.xml")}}
+ end
+ end,
+ NodeNames),
+
+ NS = lists:map(fun(NodeName)->
+ {init, NodeName, [
+ {node_start, [{startup_functions, []}, {monitor_master, false}]},
+ {eval, {erlang, nodes, []}}
+ ]
+ }
+ end,
+ NodeNames),
+
+ S = [{suites, NodeNames, filename:join(DataDir, "master"), Suites}],
+
+ PrivDir = ?config(priv_dir, Config),
+ LD = lists:map(fun(NodeName)->
+ {logdir, NodeName, get_log_dir(PrivDir, NodeName)}
+ end,
+ NodeNames) ++ [{logdir, master, PrivDir}],
+
+ ct_test_support:write_testspec(N++C++S++LD++NS, FileName).
+
+get_log_dir(PrivDir, NodeName)->
+ LogDir = filename:join(PrivDir, io_lib:format("slave.~p", [NodeName])),
+ file:make_dir(LogDir),
+ LogDir.
+
+run_test(_Name, FileName, Config)->
+ [{FileName, ok}] = ct_test_support:run(ct_master, run, [FileName], Config).
+
+reformat_events(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+expected_events(_)->
+[].
diff --git a/lib/common_test/test/ct_master_SUITE_data/master/config.txt b/lib/common_test/test/ct_master_SUITE_data/master/config.txt
new file mode 100644
index 0000000000..3baf9e392c
--- /dev/null
+++ b/lib/common_test/test/ct_master_SUITE_data/master/config.txt
@@ -0,0 +1,2 @@
+{a, b}.
+{c, d}.
diff --git a/lib/common_test/test/ct_master_SUITE_data/master/config.xml b/lib/common_test/test/ct_master_SUITE_data/master/config.xml
new file mode 100644
index 0000000000..c031f45f35
--- /dev/null
+++ b/lib/common_test/test/ct_master_SUITE_data/master/config.xml
@@ -0,0 +1,4 @@
+<config>
+ <a>b</a>
+ <c>d</c>
+</config>
diff --git a/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl b/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl
new file mode 100644
index 0000000000..e37ec3659c
--- /dev/null
+++ b/lib/common_test/test/ct_master_SUITE_data/master/master_SUITE.erl
@@ -0,0 +1,57 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: master_SUITE
+%%%
+%%% Description:
+%%% Test suite for common_test which tests the ct_master functionality
+%%%-------------------------------------------------------------------
+-module(master_SUITE).
+
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+suite() ->
+ [].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_) ->
+ ok.
+
+all() -> [first_testcase, second_testcase, third_testcase].
+
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(_, _) ->
+ ok.
+
+first_testcase(_)->
+ b = ct:get_config(a).
+
+second_testcase(_)->
+ d = ct:get_config(c).
+
+third_testcase(_)->
+ A = 4,
+ A = 2*2.
diff --git a/lib/common_test/test/ct_misc_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE.erl
new file mode 100644
index 0000000000..eb6c6aa101
--- /dev/null
+++ b/lib/common_test/test/ct_misc_1_SUITE.erl
@@ -0,0 +1,165 @@
+%%
+%% %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%
+%%
+
+%%%-------------------------------------------------------------------
+%%% File: ct_misc_1_SUITE
+%%%
+%%% Description:
+%%% Test misc things in Common Test suites.
+%%%
+%%% The suites used for the test are located in the data directory.
+%%%-------------------------------------------------------------------
+-module(ct_misc_1_SUITE).
+
+-compile(export_all).
+
+-include_lib("test_server/include/test_server.hrl").
+-include_lib("test_server/include/test_server_line.hrl").
+-include_lib("common_test/include/ct_event.hrl").
+
+-define(eh, ct_test_support_eh).
+
+%%--------------------------------------------------------------------
+%% TEST SERVER CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Description: Since Common Test starts another Test Server
+%% instance, the tests need to be performed on a separate node (or
+%% there will be clashes with logging processes etc).
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config1 = ct_test_support:init_per_suite(Config),
+ Config1.
+
+end_per_suite(Config) ->
+ ct_test_support:end_per_suite(Config).
+
+init_per_testcase(TestCase, Config) ->
+ ct_test_support:init_per_testcase(TestCase, Config).
+
+end_per_testcase(TestCase, Config) ->
+ ct_test_support:end_per_testcase(TestCase, Config).
+
+all(doc) ->
+ [""];
+
+all(suite) ->
+ [
+ beam_me_up
+ ].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+%%%-----------------------------------------------------------------
+%%%
+beam_me_up(Config) when is_list(Config) ->
+ DataDir = ?config(data_dir, Config),
+ CTNode = ?config(ct_node, Config),
+
+ %% Path = rpc:call(CTNode, code, get_path, []),
+ %% [_ | Parts] = lists:reverse(filename:split(DataDir)),
+ %% TSDir = filename:join(lists:reverse(Parts)),
+ %% true = rpc:call(CTNode, code, del_path, [TSDir]),
+
+ Mods = [beam_1_SUITE, beam_2_SUITE],
+ Suites = [atom_to_list(M) || M <- Mods],
+ [{error,_} = rpc:call(CTNode, code, load_file, [M]) || M <- Mods],
+
+ code:add_path(DataDir),
+ CRes =
+ [compile:file(filename:join(DataDir,F),
+ [verbose,report_errors,
+ report_warnings,binary]) || F <- Suites],
+
+ [{module,_} = rpc:call(CTNode, code, load_binary,
+ [Mod, atom_to_list(Mod), Bin]) ||
+ {ok,Mod,Bin} <- CRes],
+
+ {Opts,ERPid} = setup([{suite,Suites},{auto_compile,false}], Config),
+
+ ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ Events = ct_test_support:get_events(ERPid, Config),
+
+ ct_test_support:log_events(beam_me_up,
+ reformat(Events, ?eh),
+ ?config(priv_dir, Config)),
+
+ TestEvents = events_to_check(beam_me_up, 1),
+ ok = ct_test_support:verify_events(TestEvents, Events, Config).
+
+%%%-----------------------------------------------------------------
+%%% HELP FUNCTIONS
+%%%-----------------------------------------------------------------
+
+setup(Test, Config) ->
+ Opts0 = ct_test_support:get_opts(Config),
+ Level = ?config(trace_level, Config),
+ EvHArgs = [{cbm,ct_test_support},{trace_level,Level}],
+ Opts = Opts0 ++ [{event_handler,{?eh,EvHArgs}}|Test],
+ ERPid = ct_test_support:start_event_receiver(Config),
+ {Opts,ERPid}.
+
+reformat(Events, EH) ->
+ ct_test_support:reformat(Events, EH).
+%reformat(Events, _EH) ->
+% Events.
+
+%%%-----------------------------------------------------------------
+%%% TEST EVENTS
+%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
+test_events(beam_me_up) ->
+ [
+ {?eh,start_logging,{'DEF','RUNDIR'}},
+ {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
+ {?eh,start_info,{2,2,4}},
+ {?eh,tc_start,{beam_1_SUITE,init_per_suite}},
+ {?eh,tc_done,{beam_1_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{beam_1_SUITE,tc1}},
+ {?eh,tc_done,{beam_1_SUITE,tc1,ok}},
+ {?eh,test_stats,{1,0,{0,0}}},
+ {?eh,tc_start,{beam_1_SUITE,tc2}},
+ {?eh,tc_done,{beam_1_SUITE,tc2,{failed,{error,'tc2 failed'}}}},
+ {?eh,test_stats,{1,1,{0,0}}},
+ {?eh,tc_start,{beam_1_SUITE,end_per_suite}},
+ {?eh,tc_done,{beam_1_SUITE,end_per_suite,ok}},
+ {?eh,tc_start,{beam_2_SUITE,init_per_suite}},
+ {?eh,tc_done,{beam_2_SUITE,init_per_suite,ok}},
+ {?eh,tc_start,{beam_2_SUITE,tc1}},
+ {?eh,tc_done,{beam_2_SUITE,tc1,ok}},
+ {?eh,test_stats,{2,1,{0,0}}},
+ {?eh,tc_start,{beam_2_SUITE,tc2}},
+ {?eh,tc_done,{beam_2_SUITE,tc2,{failed,{error,'tc2 failed'}}}},
+ {?eh,test_stats,{2,2,{0,0}}},
+ {?eh,tc_start,{beam_2_SUITE,end_per_suite}},
+ {?eh,tc_done,{beam_2_SUITE,end_per_suite,ok}},
+ {?eh,test_done,{'DEF','STOP_TIME'}},
+ {?eh,stop_logging,[]}
+ ].
diff --git a/lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl
new file mode 100644
index 0000000000..382bdefded
--- /dev/null
+++ b/lib/common_test/test/ct_misc_1_SUITE_data/beam_1_SUITE.erl
@@ -0,0 +1,134 @@
+%%
+%% %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(beam_1_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% COMMON TEST CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Function: suite() -> Info
+%%
+%% Info = [tuple()]
+%% List of key/value pairs.
+%%
+%% Description: Returns list of tuples to set default properties
+%% for the suite.
+%%
+%% Note: The suite/0 function is only meant to be used to return
+%% default data values, not perform any other operations.
+%%--------------------------------------------------------------------
+suite() ->
+ [
+ {timetrap,{seconds,10}}
+ ].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%%
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Reason = term()
+%% The reason for skipping the suite.
+%%
+%% Description: Initialization before the suite.
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
+%%
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Cleanup after the suite.
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%%
+%% TestCase = atom()
+%% Name of the test case that is about to run.
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Reason = term()
+%% The reason for skipping the test case.
+%%
+%% Description: Initialization before each test case.
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1}
+%%
+%% TestCase = atom()
+%% Name of the test case that is finished.
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Cleanup after each test case.
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all() -> TestCases | {skip,Reason}
+%%
+%% TestCases = [TestCase | {sequence,SeqName}]
+%% TestCase = atom()
+%% Name of a test case.
+%% SeqName = atom()
+%% Name of a test case sequence.
+%% Reason = term()
+%% The reason for skipping all test cases.
+%%
+%% Description: Returns the list of test cases that are to be executed.
+%%--------------------------------------------------------------------
+all() ->
+ [tc1, tc2].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+tc1(_Config) ->
+ ct:comment("tc1 executed"),
+ ok.
+
+tc2(_Config) ->
+ exit('tc2 failed').
diff --git a/lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl b/lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl
new file mode 100644
index 0000000000..70c1f2b471
--- /dev/null
+++ b/lib/common_test/test/ct_misc_1_SUITE_data/beam_2_SUITE.erl
@@ -0,0 +1,134 @@
+%%
+%% %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(beam_2_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include_lib("common_test/include/ct.hrl").
+
+%%--------------------------------------------------------------------
+%% COMMON TEST CALLBACK FUNCTIONS
+%%--------------------------------------------------------------------
+
+%%--------------------------------------------------------------------
+%% Function: suite() -> Info
+%%
+%% Info = [tuple()]
+%% List of key/value pairs.
+%%
+%% Description: Returns list of tuples to set default properties
+%% for the suite.
+%%
+%% Note: The suite/0 function is only meant to be used to return
+%% default data values, not perform any other operations.
+%%--------------------------------------------------------------------
+suite() ->
+ [
+ {timetrap,{seconds,10}}
+ ].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%%
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Reason = term()
+%% The reason for skipping the suite.
+%%
+%% Description: Initialization before the suite.
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config0) -> void() | {save_config,Config1}
+%%
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Cleanup after the suite.
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config0) ->
+%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
+%%
+%% TestCase = atom()
+%% Name of the test case that is about to run.
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Reason = term()
+%% The reason for skipping the test case.
+%%
+%% Description: Initialization before each test case.
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ Config.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config0) ->
+%% void() | {save_config,Config1}
+%%
+%% TestCase = atom()
+%% Name of the test case that is finished.
+%% Config0 = Config1 = [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Cleanup after each test case.
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, _Config) ->
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: all() -> TestCases | {skip,Reason}
+%%
+%% TestCases = [TestCase | {sequence,SeqName}]
+%% TestCase = atom()
+%% Name of a test case.
+%% SeqName = atom()
+%% Name of a test case sequence.
+%% Reason = term()
+%% The reason for skipping all test cases.
+%%
+%% Description: Returns the list of test cases that are to be executed.
+%%--------------------------------------------------------------------
+all() ->
+ [tc1, tc2].
+
+%%--------------------------------------------------------------------
+%% TEST CASES
+%%--------------------------------------------------------------------
+
+tc1(_Config) ->
+ ct:comment("tc1 executed"),
+ ok.
+
+tc2(_Config) ->
+ exit('tc2 failed').
diff --git a/lib/common_test/test/ct_skip_SUITE.erl b/lib/common_test/test/ct_skip_SUITE.erl
index 9f428723f5..2e02061dec 100644
--- a/lib/common_test/test/ct_skip_SUITE.erl
+++ b/lib/common_test/test/ct_skip_SUITE.erl
@@ -89,14 +89,14 @@ auto_skip(Config) when is_list(Config) ->
],
{Opts,ERPid} = setup({suite,Suites}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(auto_skip,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(auto_skip),
+ TestEvents = events_to_check(auto_skip),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -112,14 +112,14 @@ user_skip(Config) when is_list(Config) ->
Join(DataDir, "user_skip_5_SUITE")],
{Opts,ERPid} = setup({suite,Suites}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(user_skip,
reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(user_skip),
+ TestEvents = events_to_check(user_skip),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -142,6 +142,15 @@ reformat(Events, EH) ->
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
test_events(auto_skip) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
diff --git a/lib/common_test/test/ct_smoke_test_SUITE.erl b/lib/common_test/test/ct_smoke_test_SUITE.erl
index f1c695f614..05a2c20695 100644
--- a/lib/common_test/test/ct_smoke_test_SUITE.erl
+++ b/lib/common_test/test/ct_smoke_test_SUITE.erl
@@ -162,7 +162,7 @@ dir1(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -170,7 +170,7 @@ dir1(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(dir1),
+ TestEvents = events_to_check(dir1),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -191,7 +191,7 @@ dir2(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -199,7 +199,7 @@ dir2(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(dir2),
+ TestEvents = events_to_check(dir2),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -221,7 +221,7 @@ dir1_2(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -229,7 +229,7 @@ dir1_2(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(dir1_2),
+ TestEvents = events_to_check(dir1_2),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -251,7 +251,7 @@ suite11(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -259,7 +259,7 @@ suite11(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(suite11),
+ TestEvents = events_to_check(suite11),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -280,7 +280,7 @@ suite21(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -288,7 +288,7 @@ suite21(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(suite21),
+ TestEvents = events_to_check(suite21),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -311,7 +311,7 @@ suite11_21(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -319,7 +319,7 @@ suite11_21(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(suite11_21),
+ TestEvents = events_to_check(suite11_21),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -342,7 +342,7 @@ tc111(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -350,7 +350,7 @@ tc111(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(tc111),
+ TestEvents = events_to_check(tc111),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -372,7 +372,7 @@ tc211(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -380,7 +380,7 @@ tc211(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(tc211),
+ TestEvents = events_to_check(tc211),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
%%%-----------------------------------------------------------------
@@ -403,7 +403,7 @@ tc111_112(Config) when is_list(Config) ->
ERPid = ct_test_support:start_event_receiver(Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
@@ -411,7 +411,7 @@ tc111_112(Config) when is_list(Config) ->
ct_test_support:reformat(Events, ?eh),
?config(priv_dir, Config)),
- TestEvents = test_events(tc111_112),
+ TestEvents = events_to_check(tc111_112),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -423,8 +423,16 @@ eh_opts(Config) ->
Level = ?config(trace_level, Config),
[{event_handler,{?eh,[{cbm,ct_test_support},{trace_level,Level}]}}].
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
-test_events(Test) when Test == dir1 ; Test == dir2 ;
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ events(Test) ++ events_to_check(Test, N-1).
+
+events(Test) when Test == dir1 ; Test == dir2 ;
Test == suite11 ; Test == suite21 ->
Suite = if Test == dir1 ; Test == suite11 -> happy_11_SUITE;
true -> happy_21_SUITE
@@ -465,7 +473,7 @@ test_events(Test) when Test == dir1 ; Test == dir2 ;
{?eh,test_done,{'DEF','STOP_TIME'}},
{?eh,stop_logging,[]}
];
-test_events(Test) when Test == dir1_2 ; Test == suite11_21 ->
+events(Test) when Test == dir1_2 ; Test == suite11_21 ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
@@ -532,7 +540,7 @@ test_events(Test) when Test == dir1_2 ; Test == suite11_21 ->
{?eh,stop_logging,[]}
];
-test_events(Test) when Test == tc111 ; Test == tc211 ->
+events(Test) when Test == tc111 ; Test == tc211 ->
Suite = if Test == tc111 -> happy_11_SUITE; true -> happy_21_SUITE end,
[
{?eh,start_logging,{'DEF','RUNDIR'}},
@@ -549,7 +557,7 @@ test_events(Test) when Test == tc111 ; Test == tc211 ->
{?eh,stop_logging,[]}
];
-test_events(tc111_112) ->
+events(tc111_112) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
{?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}},
diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE.erl
index 069f8c75fc..eb85409073 100644
--- a/lib/common_test/test/ct_test_server_if_1_SUITE.erl
+++ b/lib/common_test/test/ct_test_server_if_1_SUITE.erl
@@ -87,14 +87,14 @@ ts_if_1(Config) when is_list(Config) ->
TestSpecName = ct_test_support:write_testspec(TestSpec, PrivDir, "ts_if_1_spec"),
{Opts,ERPid} = setup({spec,TestSpecName}, Config),
- ok = ct_test_support:run(ct, run_test, [Opts], Config),
+ ok = ct_test_support:run(Opts, Config),
Events = ct_test_support:get_events(ERPid, Config),
ct_test_support:log_events(ts_if_1,
reformat(Events, ?eh),
PrivDir),
- TestEvents = test_events(ts_if_1),
+ TestEvents = events_to_check(ts_if_1),
ok = ct_test_support:verify_events(TestEvents, Events, Config).
@@ -119,6 +119,15 @@ reformat(Events, EH) ->
%%%-----------------------------------------------------------------
%%% TEST EVENTS
%%%-----------------------------------------------------------------
+events_to_check(Test) ->
+ %% 2 tests (ct:run_test + script_start) is default
+ events_to_check(Test, 2).
+
+events_to_check(_, 0) ->
+ [];
+events_to_check(Test, N) ->
+ test_events(Test) ++ events_to_check(Test, N-1).
+
test_events(ts_if_1) ->
[
{?eh,start_logging,{'DEF','RUNDIR'}},
@@ -193,14 +202,14 @@ test_events(ts_if_1) ->
{?eh,tc_done,{ts_if_1_SUITE,{end_per_group,g2,[parallel]},ok}}]},
{?eh,tc_start,{ts_if_1_SUITE,tc12}},
- {?eh,tc_done,{undefined,undefined,{testcase_aborted,{abort_current_testcase,tc12},'_'}}},
+ {?eh,tc_done,{ts_if_1_SUITE,tc12,{failed,{testcase_aborted,'stopping tc12'}}}},
{?eh,test_stats,{2,5,{3,6}}},
{?eh,tc_start,{ts_if_1_SUITE,tc13}},
{?eh,tc_done,{ts_if_1_SUITE,tc13,ok}},
{?eh,test_stats,{3,5,{3,6}}},
{?eh,tc_start,{ts_if_1_SUITE,end_per_suite}},
{?eh,tc_done,{ts_if_1_SUITE,end_per_suite,ok}},
-%%!
+
{?eh,tc_start,{ts_if_2_SUITE,init_per_suite}},
{?eh,tc_done,{ts_if_2_SUITE,init_per_suite,
{failed,{error,{suite0_failed,{exited,suite0_goes_boom}}}}}},
diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl
index 8e90df21ce..47cea190dd 100644
--- a/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl
+++ b/lib/common_test/test/ct_test_server_if_1_SUITE_data/test_server_if/test/ts_if_1_SUITE.erl
@@ -93,6 +93,10 @@ init_per_testcase(_TestCase, Config) ->
%%--------------------------------------------------------------------
end_per_testcase(tc2, Config) ->
timer:sleep(5000);
+end_per_testcase(tc12, Config) ->
+ ct:comment("end_per_testcase(tc12) called!"),
+ ct:pal("end_per_testcase(tc12) called!", []),
+ ok;
end_per_testcase(_TestCase, _Config) ->
ok.
@@ -180,9 +184,9 @@ gtc2(_) ->
exit(should_have_been_skipped).
tc12(_) ->
- F = fun() -> ct:abort_current_testcase({abort_current_testcase,tc12}) end,
+ F = fun() -> ct:abort_current_testcase('stopping tc12') end,
spawn(F),
- timer:sleep(500),
+ timer:sleep(1000),
exit(should_have_been_aborted).
tc13(_) ->
diff --git a/lib/common_test/test/ct_test_support.erl b/lib/common_test/test/ct_test_support.erl
index 6148e3280e..6a75c8f789 100644
--- a/lib/common_test/test/ct_test_support.erl
+++ b/lib/common_test/test/ct_test_support.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -27,8 +27,9 @@
-include_lib("common_test/include/ct_event.hrl").
-export([init_per_suite/1, init_per_suite/2, end_per_suite/1,
- init_per_testcase/2, end_per_testcase/2, write_testspec/3,
- run/4, get_opts/1, wait_for_ct_stop/1]).
+ init_per_testcase/2, end_per_testcase/2,
+ write_testspec/2, write_testspec/3,
+ run/2, run/4, get_opts/1, wait_for_ct_stop/1]).
-export([handle_event/2, start_event_receiver/1, get_events/2,
verify_events/3, reformat/2, log_events/3]).
@@ -55,17 +56,28 @@ init_per_suite(Config, Level) ->
test_server:fail(Reason);
{ok,CTNode} ->
test_server:format(0, "Node ~p started~n", [CTNode]),
+ IsCover = test_server:is_cover(),
+ if IsCover ->
+ cover:start(CTNode);
+ true->
+ ok
+ end,
DataDir = ?config(data_dir, Config),
PrivDir = ?config(priv_dir, Config),
%% PrivDir as well as directory of Test Server suites
%% have to be in code path on Common Test node.
- true = rpc:call(CTNode, code, add_patha, [PrivDir]),
[_ | Parts] = lists:reverse(filename:split(DataDir)),
TSDir = filename:join(lists:reverse(Parts)),
- true = rpc:call(CTNode, code, add_patha, [TSDir]),
- test_server:format(Level, "Dirs added to code path (on ~w):~n"
- "~s~n~s~n", [CTNode,TSDir,PrivDir]),
+ AddPathDirs = case ?config(path_dirs, Config) of
+ undefined -> [];
+ Ds -> Ds
+ end,
+ PathDirs = [PrivDir,TSDir | AddPathDirs],
+ [true = rpc:call(CTNode, code, add_patha, [D]) || D <- PathDirs],
+ test_server:format(Level, "Dirs added to code path (on ~w):~n",
+ [CTNode]),
+ [io:format("~s~n", [D]) || D <- PathDirs],
TraceFile = filename:join(DataDir, "ct.trace"),
case file:read_file_info(TraceFile) of
@@ -87,6 +99,7 @@ end_per_suite(Config) ->
CTNode = ?config(ct_node, Config),
PrivDir = ?config(priv_dir, Config),
true = rpc:call(CTNode, code, del_path, [filename:join(PrivDir,"")]),
+ cover:stop(CTNode),
slave:stop(CTNode),
ok.
@@ -95,9 +108,16 @@ end_per_suite(Config) ->
init_per_testcase(_TestCase, Config) ->
{_,{_,LogDir}} = lists:keysearch(logdir, 1, get_opts(Config)),
- test_server:format("See Common Test logs here:\n"
+ case lists:keysearch(master, 1, Config) of
+ false->
+ test_server:format("See Common Test logs here:\n\n"
"<a href=\"file://~s/all_runs.html\">~s/all_runs.html</a>",
- [LogDir,LogDir]),
+ [LogDir,LogDir]);
+ {value, _}->
+ test_server:format("See CT Master Test logs here:\n\n"
+ "<a href=\"file://~s/master_runs.html\">~s/master_runs.html</a>",
+ [LogDir,LogDir])
+ end,
Config.
%%%-----------------------------------------------------------------
@@ -111,9 +131,10 @@ end_per_testcase(_TestCase, Config) ->
%%%-----------------------------------------------------------------
%%%
-
write_testspec(TestSpec, Dir, Name) ->
- TSFile = filename:join(Dir, Name),
+ write_testspec(TestSpec, filename:join(Dir, Name)).
+
+write_testspec(TestSpec, TSFile) ->
{ok,Dev} = file:open(TSFile, [write]),
[io:format(Dev, "~p.~n", [Entry]) || Entry <- TestSpec],
file:close(Dev),
@@ -158,10 +179,32 @@ get_opts(Config) ->
%%%-----------------------------------------------------------------
%%%
+run(Opts, Config) ->
+ CTNode = ?config(ct_node, Config),
+ Level = ?config(trace_level, Config),
+ %% use ct interface
+ test_server:format(Level, "~n[RUN #1] Calling ct:run_test(~p) on ~p~n",
+ [Opts, CTNode]),
+ Result1 = rpc:call(CTNode, ct, run_test, [Opts]),
+
+ %% use run_test interface (simulated)
+ test_server:format(Level, "Saving start opts on ~p: ~p~n", [CTNode,Opts]),
+ rpc:call(CTNode, application, set_env, [common_test, run_test_start_opts, Opts]),
+ test_server:format(Level, "[RUN #2] Calling ct_run:script_start() on ~p~n", [CTNode]),
+ Result2 = rpc:call(CTNode, ct_run, script_start, []),
+ case {Result1,Result2} of
+ {ok,ok} ->
+ ok;
+ {E,_} when E =/= ok ->
+ E;
+ {_,E} when E =/= ok ->
+ E
+ end.
+
run(M, F, A, Config) ->
CTNode = ?config(ct_node, Config),
Level = ?config(trace_level, Config),
- test_server:format(Level, "Calling ~w:~w(~p) on ~p~n",
+ test_server:format(Level, "~nCalling ~w:~w(~p) on ~p~n",
[M, F, A, CTNode]),
rpc:call(CTNode, M, F, A).
@@ -231,8 +274,12 @@ verify_events(TEvs, Evs, Config) ->
ok
end.
+verify_events1([TestEv|_], [{TEH,#event{name=stop_logging,node=Node,data=_}}|_], Node, _)
+ when element(1,TestEv) == TEH, element(2,TestEv) =/= stop_logging ->
+ test_server:format("Failed to find ~p in the list of events!~n", [TestEv]),
+ exit({event_not_found,TestEv});
+
verify_events1(TEvs = [TestEv | TestEvs], Evs = [_|Events], Node, Config) ->
-%% test_server:format("Next expected event: ~p~n", [TestEv]),
case catch locate(TestEv, Node, Evs, Config) of
nomatch ->
verify_events1(TEvs, Events, Node, Config);
@@ -332,6 +379,10 @@ locate({parallel,TEvs}, Node, Evs, Config) ->
EH == TEH, EvNode == Node,
Mod == M, Func == F ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_start_not_found,TEv});
(_) ->
true
end, Evs1),
@@ -345,6 +396,10 @@ locate({parallel,TEvs}, Node, Evs, Config) ->
EH == TEH, EvNode == Node,
Mod == M, Func == F ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_done_not_found,TEv});
(_) ->
true
end, Evs2),
@@ -388,6 +443,10 @@ locate({parallel,TEvs}, Node, Evs, Config) ->
EH == TEH, EvNode == Node, Mod == M,
EvGName == GroupName, EvProps == Props ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_start_not_found,TEv});
(_) ->
true
end, RemEvs),
@@ -410,6 +469,10 @@ locate({parallel,TEvs}, Node, Evs, Config) ->
EH == TEH, EvNode == Node, Mod == M,
EvGName == GroupName, EvProps == Props, Res == R ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_done_not_found,TEv});
(_) ->
true
end, RemEvs),
@@ -429,6 +492,10 @@ locate({parallel,TEvs}, Node, Evs, Config) ->
data={Mod,end_per_group,Reason}}}) when
EH == TEH, EvNode == Node, Mod == M, Reason == R ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_auto_skip_not_found,TEv});
(_) ->
true
end, RemEvs),
@@ -533,6 +600,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) ->
EH == TEH, EvNode == Node,
Mod == M, Func == F ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_start_not_found,TEv});
(_) ->
true
end, Evs1),
@@ -576,6 +647,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) ->
EH == TEH, EvNode == Node, Mod == M,
EvGName == GroupName ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_start_not_found,TEv});
(_) ->
true
end, RemEvs),
@@ -611,6 +686,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) ->
EH == TEH, EvNode == Node, Mod == M,
EvGName == GroupName, Res == R ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_done_not_found,TEv});
(_) ->
true
end, RemEvs),
@@ -643,6 +722,10 @@ locate({shuffle,TEvs}, Node, Evs, Config) ->
data={Mod,end_per_group,Reason}}}) when
EH == TEH, EvNode == Node, Mod == M, Reason == R ->
false;
+ ({EH,#event{name=stop_logging,
+ node=EvNode,data=_}}) when
+ EH == TEH, EvNode == Node ->
+ exit({tc_auto_skip_not_found,TEv});
(_) ->
true
end, RemEvs),
@@ -785,7 +868,8 @@ log_events(TC, Events, PrivDir) ->
io:format(Dev, "[~n", []),
log_events1(Events, Dev, " "),
file:close(Dev),
- io:format("Events written to logfile: ~p~n", [LogFile]),
+ io:format("Events written to logfile: <a href=\"file://~s\">~s</a>~n",
+ [LogFile,LogFile]),
io:format(user, "Events written to logfile: ~p~n", [LogFile]).
log_events1(Evs, Dev, "") ->
diff --git a/lib/common_test/test/ct_test_support_eh.erl b/lib/common_test/test/ct_test_support_eh.erl
index fd3ae18746..70f73b9b81 100644
--- a/lib/common_test/test/ct_test_support_eh.erl
+++ b/lib/common_test/test/ct_test_support_eh.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -44,8 +44,20 @@
%% Description: Whenever a new event handler is added to an event manager,
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
+init(String = [X|_]) when is_integer(X) ->
+ case erl_scan:string(String++".") of
+ {ok,Ts,_} ->
+ case erl_parse:parse_term(Ts) of
+ {ok,Args} ->
+ init(Args);
+ _ ->
+ init(String)
+ end;
+ _ ->
+ init(String)
+ end;
+
init(Args) ->
-
S1 = case lists:keysearch(cbm, 1, Args) of
{_,{cbm,CBM}} ->
#state{cbm=CBM};
@@ -58,7 +70,8 @@ init(Args) ->
_ ->
S1
end,
- print(S2#state.trace_level, "Event Handler ~w started!~n", [?MODULE]),
+ print(S2#state.trace_level, "Event Handler ~w started with ~p~n",
+ [?MODULE,Args]),
{ok,S2}.
%%--------------------------------------------------------------------
diff --git a/lib/common_test/test/ct_userconfig_callback.erl b/lib/common_test/test/ct_userconfig_callback.erl
new file mode 100644
index 0000000000..ca51bf240b
--- /dev/null
+++ b/lib/common_test/test/ct_userconfig_callback.erl
@@ -0,0 +1,32 @@
+%%--------------------------------------------------------------------
+%% %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(ct_userconfig_callback).
+
+-export([check_parameter/1, read_config/1]).
+
+read_config(Str) ->
+ KeyVals = string:tokens(Str, " "),
+ {ok,read_config1(KeyVals)}.
+
+read_config1([Key,Val | KeyVals]) ->
+ [{list_to_atom(Key),Val} | read_config1(KeyVals)];
+read_config1([]) ->
+ [].
+
+check_parameter(Str) ->
+ {ok,{config,Str}}.
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index ee07350c55..2947c6a152 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1,3 +1,3 @@
-COMMON_TEST_VSN = 1.4.7
+COMMON_TEST_VSN = 1.5
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml
index bbd3f1043d..e1f24b602d 100644
--- a/lib/compiler/doc/src/compile.xml
+++ b/lib/compiler/doc/src/compile.xml
@@ -310,6 +310,23 @@
(there will not even be a warning if there is a mismatch).</p>
</item>
+ <tag><c>{no_auto_import,[F/A, ...]}</c></tag>
+ <item>
+ <p>Makes the function <c>F/A</c> no longer beeing
+ auto-imported from the module <c>erlang</c>, which resolves
+ BIF name clashes. This option has to be used to resolve name
+ clashes with BIFs auto-imported before R14A, if one wants to
+ call the local function with the same name as an
+ auto-imported BIF without module prefix.</p>
+ <note>
+ <p>From R14A and forward, the compiler resolves calls
+ without module prefix to local or imported functions before
+ trying auto-imported BIFs. If the BIF is to be
+ called, use the <c>erlang</c> module prefix in the call, not
+ <c>{ no_auto_import,[F/A, ...]}</c></p>
+ </note>
+ </item>
+
</taglist>
<p>If warnings are turned on (the <c>report_warnings</c> option
@@ -338,31 +355,35 @@
<tag><c>nowarn_bif_clash</c></tag>
<item>
- <p>By default, there will be a compilation error if a
- module contains an exported function with the same name
- as an auto-imported BIF (such as <c>size/1</c>) AND
- there is a call to it without a qualifying module name.
- The reason is that the BIF will be called, not
- the function in the same module. The recommended way to
- eliminate that warning is to use a call with a module
- name - either <c>erlang</c> to call the BIF or
- <c>?MODULE</c> to call the function in the same module.
- The warning can also be turned off using this option,
- but that is not recommended.</p>
+ <p>This option is removed, it will generate a fatal error if used.</p>
+
+ <warning>
+ <p>Beginning with R14A, the compiler no longer calls the
+ auto-imported BIF if the name clashes with a local or
+ explicitly imported function and a call without explicit
+ module name is issued. Instead the local or imported
+ function is called. Still accepting <c>nowarn_bif_clash</c> would makes a
+ module calling functions clashing with autoimported BIFs
+ compile with both the old and new compilers, but with
+ completely different semantics, why the option was removed.</p>
- <p><em>The use of this option is strongly discouraged,
- as code that uses it will probably break in a future
- major release (R14 or R15).</em></p>
+ <p>The use of this option has always been strongly discouraged.
+ From OTP R14A and forward it's an error to use it.</p>
+ <p>To resolve BIF clashes, use explicit module names or the
+ <c>{no_auto_import,[F/A]}</c> compiler directive.</p>
+ </warning>
</item>
<tag><c>{nowarn_bif_clash, FAs}</c></tag>
<item>
- <p>Turns off warnings as <c>nowarn_bif_clash</c> but only
- for the mentioned local functions. <c>FAs</c> is a tuple
- <c>{Name,Arity}</c> or a list of such tuples.</p>
- <p><em>The use of this option is strongly discouraged,
- as code that uses it will probably break in a future
- major release (R14 or R15).</em></p>
+ <p>This option is removed, it will generate a fatal error if used.</p>
+
+ <warning>
+ <p>The use of this option has always been strongly discouraged.
+ From OTP R14A and forward it's an error to use it.</p>
+ <p>To resolve BIF clashes, use explicit module names or the
+ <c>{no_auto_import,[F/A]}</c> compiler directive.</p>
+ </warning>
</item>
<tag><c>warn_export_all</c></tag>
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 7ea000a895..c08839bc7b 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -31,6 +31,106 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 4.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Several problems in the inliner have been fixed.</p>
+ <p>
+ Own Id: OTP-8552</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The module binary from EEP31 (and EEP9) is implemented.</p>
+ <p>
+ Own Id: OTP-8217</p>
+ </item>
+ <item>
+ <p>Local and imported functions now override the
+ auto-imported BIFs when the names clash. The pre R14
+ behaviour was that auto-imported BIFs would override
+ local functions. To avoid that old programs change
+ behaviour, the following will generate an error:</p>
+ <list><item><p>Doing a call without explicit module name
+ to a local function having a name clashing with the name
+ of an auto-imported BIF that was present (and
+ auto-imported) before OTP R14A</p></item>
+ <item><p>Explicitly importing a function having a name
+ clashing with the name of an autoimported BIF that was
+ present (and autoimported) before OTP R14A</p></item>
+ <item><p>Using any form of the old compiler directive
+ <c>nowarn_bif_clash</c></p></item> </list> <p>If the BIF
+ was added or auto-imported in OTP R14A or later,
+ overriding it with an import or a local function will
+ only result in a warning,</p> <p>To resolve clashes, you
+ can either use the explicit module name <c>erlang</c> to
+ call the BIF, or you can remove the auto-import of that
+ specific BIF by using the new compiler directive
+ <c>-compile({no_auto_import,[F/A]}).</c>, which makes all
+ calls to the local or imported function without explicit
+ module name pass without warnings or errors.</p> <p>The
+ change makes it possible to add auto-imported BIFs
+ without breaking or silently changing old code in the
+ future. However some current code ingeniously utilizing
+ the old behaviour or the <c>nowarn_bif_clash</c> compiler
+ directive, might need changing to be accepted by the
+ compiler.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8579</p>
+ </item>
+ <item>
+ <p>The undocumented, unsupport, and deprecated function
+ <c>lists:flat_length/1</c> has been removed.</p>
+ <p>
+ Own Id: OTP-8584</p>
+ </item>
+ <item>
+ <p>Nested records can now be accessed without
+ parenthesis. See the Reference Manual for examples.
+ (Thanks to YAMASHINA Hio and Tuncer Ayaz.)</p>
+ <p>
+ Own Id: OTP-8597</p>
+ </item>
+ <item>
+ <p>It is now possible to suppress the warning in code
+ such as "<c>list_to_integer(S), ok</c>" by assigning the
+ ignored value "_" like this: "<c>_ = list_to_integer(S),
+ ok</c>".</p>
+ <p>
+ Own Id: OTP-8602</p>
+ </item>
+ <item>
+ <p><c>receive</c> statements that can only read out a
+ newly created reference are now specially optimized so
+ that it will execute in constant time regardless of the
+ number of messages in the receive queue for the process.
+ That optimization will benefit calls to
+ <c>gen_server:call()</c>. (See <c>gen:do_call/4</c> for
+ an example of a receive statement that will be
+ optimized.)</p>
+ <p>
+ Own Id: OTP-8623</p>
+ </item>
+ <item>
+ <p>The compiler optimizes record operations better.</p>
+ <p>
+ Own Id: OTP-8668</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 4.6.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/Makefile b/lib/compiler/src/Makefile
index 70ddd54145..0f6d2f6193 100644
--- a/lib/compiler/src/Makefile
+++ b/lib/compiler/src/Makefile
@@ -58,6 +58,7 @@ MODULES = \
beam_listing \
beam_opcodes \
beam_peep \
+ beam_receive \
beam_trim \
beam_type \
beam_utils \
diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl
index 497c4fa07b..89d64834cf 100644
--- a/lib/compiler/src/beam_asm.erl
+++ b/lib/compiler/src/beam_asm.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
%% Purpose : Assembler for threaded Beam.
@@ -23,7 +23,7 @@
-export([module/4]).
-export([encode/2]).
--import(lists, [map/2,member/2,keymember/3,duplicate/2,filter/2]).
+-import(lists, [map/2,member/2,keymember/3,duplicate/2]).
-include("beam_opcodes.hrl").
module(Code, Abst, SourceFile, Opts) ->
@@ -191,11 +191,7 @@ flatten_exports(Exps) ->
flatten_imports(Imps) ->
list_to_binary(map(fun({M,F,A}) -> <<M:32,F:32,A:32>> end, Imps)).
-build_attributes(Opts, SourceFile, Attr0, Essentials) ->
- Attr = filter(fun({type,_}) -> false;
- ({spec,_}) -> false;
- (_) -> true
- end, Attr0),
+build_attributes(Opts, SourceFile, Attr, Essentials) ->
Misc = case member(slim, Opts) of
false ->
{{Y,Mo,D},{H,Mi,S}} = erlang:universaltime(),
@@ -265,7 +261,8 @@ make_op({gc_bif,Bif,Fail,Live,Args,Dest}, Dict) ->
Arity = length(Args),
BifOp = case Arity of
1 -> gc_bif1;
- 2 -> gc_bif2
+ 2 -> gc_bif2;
+ 3 -> gc_bif3
end,
encode_op(BifOp, [Fail,Live,{extfunc,erlang,Bif,Arity}|Args++[Dest]],Dict);
make_op({bs_add=Op,Fail,[Src1,Src2,Unit],Dest}, Dict) ->
diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl
index 32703b4dd1..9c6f835ab0 100644
--- a/lib/compiler/src/beam_block.erl
+++ b/lib/compiler/src/beam_block.erl
@@ -201,7 +201,6 @@ move_allocates_2(Alloc, [], Acc) ->
alloc_may_pass({set,_,_,{alloc,_,_}}) -> false;
alloc_may_pass({set,_,_,{set_tuple_element,_}}) -> false;
alloc_may_pass({set,_,_,put_list}) -> false;
-alloc_may_pass({set,_,_,{put_tuple,_}}) -> false;
alloc_may_pass({set,_,_,put}) -> false;
alloc_may_pass({set,_,_,_}) -> true.
diff --git a/lib/compiler/src/beam_bool.erl b/lib/compiler/src/beam_bool.erl
index dcc6ad4c7c..d9ea6f5a70 100644
--- a/lib/compiler/src/beam_bool.erl
+++ b/lib/compiler/src/beam_bool.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
%% Purpose: Optimizes booleans in guards.
@@ -631,10 +631,10 @@ fetch_reg(V, [{I,V}|_]) -> {x,I};
fetch_reg(V, [_|SRs]) -> fetch_reg(V, SRs).
live_regs(Regs) ->
- foldl(fun ({I,_}, _) -> I;
- ([], Max) -> Max end,
- -1, Regs)+1.
-
+ foldl(fun ({I,_}, _) ->
+ I
+ end, -1, Regs)+1.
+
%%%
%%% Convert a block to Static Single Assignment (SSA) form.
@@ -748,8 +748,7 @@ initialized_regs([{bs_context_to_binary,Src}|Is], Regs) ->
initialized_regs([{label,_},{func_info,_,_,Arity}|_], Regs) ->
InitRegs = free_vars_regs(Arity),
add_init_regs(InitRegs, Regs);
-initialized_regs([_|_], Regs) -> Regs;
-initialized_regs([], Regs) -> Regs.
+initialized_regs([_|_], Regs) -> Regs.
add_init_regs([{x,_}=X|T], Regs) ->
add_init_regs(T, ordsets:add_element(X, Regs));
diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl
index 7b4cd814a2..bb93110176 100644
--- a/lib/compiler/src/beam_dead.erl
+++ b/lib/compiler/src/beam_dead.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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%
%%
@@ -281,12 +281,12 @@ forward([{test,is_eq_exact,_,[Dst,Src]}=I,{move,Src,Dst}|Is], D, Lc, Acc) ->
forward([I|Is], D, Lc, Acc);
forward([{test,is_nil,_,[Dst]}=I,{move,nil,Dst}|Is], D, Lc, Acc) ->
forward([I|Is], D, Lc, Acc);
-forward([{test,is_eq_exact,_,[_,{atom,_}]}=I|Is], D, Lc, [{label,_}|_]=Acc) ->
+forward([{test,is_eq_exact,_,_}=I|Is], D, Lc, Acc) ->
case Is of
[{label,_}|_] -> forward(Is, D, Lc, [I|Acc]);
_ -> forward(Is, D, Lc+1, [{label,Lc},I|Acc])
end;
-forward([{test,is_ne_exact,_,[_,{atom,_}]}=I|Is], D, Lc, [{label,_}|_]=Acc) ->
+forward([{test,is_ne_exact,_,_}=I|Is], D, Lc, Acc) ->
case Is of
[{label,_}|_] -> forward(Is, D, Lc, [I|Acc]);
_ -> forward(Is, D, Lc+1, [{label,Lc},I|Acc])
@@ -371,10 +371,10 @@ backward([{test,bs_start_match2,{f,To0},Live,[Src|_]=Info,Dst}|Is], D, Acc) ->
To = shortcut_bs_start_match(To0, Src, D),
I = {test,bs_start_match2,{f,To},Live,Info,Dst},
backward(Is, D, [I|Acc]);
-backward([{test,is_eq_exact=Op,{f,To0},[Reg,{atom,Val}]=Ops}|Is], D, Acc) ->
+backward([{test,is_eq_exact,{f,To0},[Reg,{atom,Val}]=Ops}|Is], D, Acc) ->
To1 = shortcut_bs_test(To0, Is, D),
To = shortcut_fail_label(To1, Reg, Val, D),
- I = {test,Op,{f,To},Ops},
+ I = combine_eqs(To, Ops, D, Acc),
backward(Is, D, [I|Acc]);
backward([{test,Op,{f,To0},Ops0}|Is], D, Acc) ->
To1 = shortcut_bs_test(To0, Is, D),
@@ -394,7 +394,10 @@ backward([{test,Op,{f,To0},Ops0}|Is], D, Acc) ->
_Code ->
To2
end,
- I = {test,Op,{f,To},Ops0},
+ I = case Op of
+ is_eq_exact -> combine_eqs(To, Ops0, D, Acc);
+ _ -> {test,Op,{f,To},Ops0}
+ end,
backward(Is, D, [I|Acc]);
backward([{test,Op,{f,To0},Live,Ops0,Dst}|Is], D, Acc) ->
To1 = shortcut_bs_test(To0, Is, D),
@@ -519,6 +522,41 @@ bif_to_test(Name, Args, Fail) ->
not_possible() -> throw(not_possible).
+%% combine_eqs(To, Operands, Acc) -> Instruction.
+%% Combine two is_eq_exact instructions or (an is_eq_exact
+%% instruction and a select_val instruction) to a select_val
+%% instruction if possible.
+%%
+%% Example:
+%%
+%% is_eq_exact F1 Reg Lit1 select_val Reg F2 [ Lit1 L1
+%% L1: . Lit2 L2 ]
+%% .
+%% . ==>
+%% .
+%% F1: is_eq_exact F2 Reg Lit2 F1: is_eq_exact F2 Reg Lit2
+%% L2: .... L2:
+%%
+combine_eqs(To, [Reg,{Type,_}=Lit1]=Ops, D, [{label,L1}|_])
+ when Type =:= atom; Type =:= integer ->
+ case beam_utils:code_at(To, D) of
+ [{test,is_eq_exact,{f,F2},[Reg,{Type,_}=Lit2]},
+ {label,L2}|_] when Lit1 =/= Lit2 ->
+ {select_val,Reg,{f,F2},{list,[Lit1,{f,L1},Lit2,{f,L2}]}};
+ [{select_val,Reg,{f,F2},{list,[{Type,_}|_]=List0}}|_] ->
+ List = remove_from_list(Lit1, List0),
+ {select_val,Reg,{f,F2},{list,[Lit1,{f,L1}|List]}};
+ _Is ->
+ {test,is_eq_exact,{f,To},Ops}
+ end;
+combine_eqs(To, Ops, _D, _Acc) ->
+ {test,is_eq_exact,{f,To},Ops}.
+
+remove_from_list(Lit, [Lit,{f,_}|T]) ->
+ T;
+remove_from_list(Lit, [Val,{f,_}=Fail|T]) ->
+ [Val,Fail|remove_from_list(Lit, T)];
+remove_from_list(_, []) -> [].
%% shortcut_bs_test(TargetLabel, [Instruction], D) -> TargetLabel'
%% Try to shortcut the failure label for a bit syntax matching.
diff --git a/lib/compiler/src/beam_dict.erl b/lib/compiler/src/beam_dict.erl
index 4ffe8bc606..9164259a94 100644
--- a/lib/compiler/src/beam_dict.erl
+++ b/lib/compiler/src/beam_dict.erl
@@ -33,7 +33,7 @@
exports = [] :: [{label(), arity(), label()}],
locals = [] :: [{label(), arity(), label()}],
imports = gb_trees:empty() :: gb_tree(), %{{M,F,A},Index}
- strings = [] :: [string()], %String pool
+ strings = [] :: string(), %String pool
lambdas = [], %[{...}]
literals = dict:new() :: dict(), %Format: {Literal,Number}
next_atom = 1 :: pos_integer(),
@@ -219,7 +219,7 @@ my_term_to_binary(Term) ->
%% Search for string Str in the string pool Pool.
%% old_string(Str, Pool) -> none | Index
--spec old_string(string(), [string()]) -> 'none' | pos_integer().
+-spec old_string(string(), string()) -> 'none' | pos_integer().
old_string([C|Str]=Str0, [C|Pool]) ->
case lists:prefix(Str, Pool) of
diff --git a/lib/compiler/src/beam_disasm.erl b/lib/compiler/src/beam_disasm.erl
index c956f2f000..017ca129b0 100644
--- a/lib/compiler/src/beam_disasm.erl
+++ b/lib/compiler/src/beam_disasm.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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%
%%=======================================================================
%% Notes:
@@ -621,8 +621,7 @@ resolve_names(Fun, Imports, Str, Lbls, Lambdas, Literals, M) ->
%%
%% New make_fun2/4 instruction added in August 2001 (R8).
-%% New put_literal/2 instruction added in Feb 2006 R11B-4.
-%% We handle them specially here to avoid adding an argument to
+%% We handle it specially here to avoid adding an argument to
%% the clause for every instruction.
%%
@@ -631,8 +630,6 @@ resolve_inst({make_fun2,Args}, _, _, _, Lambdas, _, M) ->
{OldIndex,{F,A,_Lbl,_Index,NumFree,OldUniq}} =
lists:keyfind(OldIndex, 1, Lambdas),
{make_fun2,{M,F,A},OldIndex,OldUniq,NumFree};
-resolve_inst({put_literal,[{u,Index},Dst]},_,_,_,_,Literals,_) ->
- {put_literal,{literal,gb_trees:get(Index, Literals)},Dst};
resolve_inst(Instr, Imports, Str, Lbls, _Lambdas, _Literals, _M) ->
%% io:format(?MODULE_STRING":resolve_inst ~p.~n", [Instr]),
resolve_inst(Instr, Imports, Str, Lbls).
@@ -1004,13 +1001,17 @@ resolve_inst({gc_bif2,Args},Imports,_,_) ->
[F,Live,Bif,A1,A2,Reg] = resolve_args(Args),
{extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
{gc_bif,BifName,F,Live,[A1,A2],Reg};
+%%
+%% New instruction in R14, gc_bif with 3 arguments
+%%
+resolve_inst({gc_bif3,Args},Imports,_,_) ->
+ [F,Live,Bif,A1,A2,A3,Reg] = resolve_args(Args),
+ {extfunc,_Mod,BifName,_Arity} = lookup(Bif+1,Imports),
+ {gc_bif,BifName,F,Live,[A1,A2,A3],Reg};
%%
%% New instructions for creating non-byte aligned binaries.
%%
-resolve_inst({bs_bits_to_bytes2,[_Arg2,_Arg3]=Args},_,_,_) ->
- [A2,A3] = resolve_args(Args),
- {bs_bits_to_bytes2,A2,A3};
resolve_inst({bs_final2,[X,Y]},_,_,_) ->
{bs_final2,X,Y};
@@ -1096,6 +1097,14 @@ resolve_inst({on_load,[]},_,_,_) ->
on_load;
%%
+%% R14A.
+%%
+resolve_inst({recv_mark,[Lbl]},_,_,_) ->
+ {recv_mark,Lbl};
+resolve_inst({recv_set,[Lbl]},_,_,_) ->
+ {recv_set,Lbl};
+
+%%
%% Catches instructions that are not yet handled.
%%
resolve_inst(X,_,_,_) -> ?exit({resolve_inst,X}).
diff --git a/lib/compiler/src/beam_peep.erl b/lib/compiler/src/beam_peep.erl
index d03ac4b1f4..f39fc50b95 100644
--- a/lib/compiler/src/beam_peep.erl
+++ b/lib/compiler/src/beam_peep.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -64,22 +64,7 @@ function({function,Name,Arity,CLabel,Is0}) ->
%% InEncoding =:= latin1, OutEncoding =:= unicode;
%% InEncoding =:= latin1, OutEncoding =:= utf8 ->
%%
-%% (2) Code like
-%%
-%% is_ne_exact Fail Reg Literal1
-%% is_ne_exact Fail Reg Literal2
-%% is_ne_exact Fail Reg Literal3
-%% is_eq_exact UltimateFail Reg Literal4
-%% Fail: ....
-%%
-%% can be rewritten to
-%%
-%% select_val Reg UltimateFail [ Literal1 Fail
-%% Literal2 Fail
-%% Literal3 Fail
-%% Literal4 Fail ]
-%%
-%% (3) A select_val/4 instruction that only verifies that
+%% (2) A select_val/4 instruction that only verifies that
%% its argument is either 'true' or 'false' can be
%% be replaced with an is_boolean/2 instruction. That is:
%%
@@ -132,7 +117,7 @@ peep([{test,Op,_,Ops}=I|Is], SeenTests0, Acc) ->
false ->
%% Remember that we have seen this test.
SeenTests = gb_sets:insert(Test, SeenTests0),
- make_select_val(I, Is, SeenTests, Acc)
+ peep(Is, SeenTests, [I|Acc])
end
end;
peep([{select_val,Src,Fail,
@@ -151,33 +136,6 @@ peep([I|Is], _, Acc) ->
peep(Is, gb_sets:empty(), [I|Acc]);
peep([], _, Acc) -> reverse(Acc).
-make_select_val({test,is_ne_exact,{f,Fail},[Val,Lit]}=I0,
- Is0, SeenTests, Acc) ->
- try
- Type = case Lit of
- {atom,_} -> atom;
- {integer,_} -> integer;
- _ -> throw(impossible)
- end,
- {I,Is} = make_select_val_1(Is0, Fail, Val, Type, [Lit,{f,Fail}]),
- peep([I|Is], SeenTests, Acc)
- catch
- impossible ->
- peep(Is0, SeenTests, [I0|Acc])
- end;
-make_select_val(I, Is, SeenTests, Acc) ->
- peep(Is, SeenTests, [I|Acc]).
-
-make_select_val_1([{test,is_ne_exact,{f,Fail},[Val,{Type,_}=Lit]}|Is],
- Fail, Val, Type, Acc) ->
- make_select_val_1(Is, Fail, Val, Type, [Lit,{f,Fail}|Acc]);
-make_select_val_1([{test,is_eq_exact,{f,UltimateFail},[Val,{Type,_}=Lit]} |
- [{label,Fail}|_]=Is], Fail, Val, Type, Acc) ->
- Choices = [Lit,{f,Fail}|Acc],
- I = {select_val,Val,{f,UltimateFail},{list,Choices}},
- {I,Is};
-make_select_val_1(_Is, _Fail, _Val, _Type, _Acc) -> throw(impossible).
-
kill_seen(Dst, Seen0) ->
gb_sets:from_ordset(kill_seen_1(gb_sets:to_list(Seen0), Dst)).
@@ -187,5 +145,3 @@ kill_seen_1([{_,Ops}=Test|T], Dst) ->
false -> [Test|kill_seen_1(T, Dst)]
end;
kill_seen_1([], _) -> [].
-
-
diff --git a/lib/compiler/src/beam_receive.erl b/lib/compiler/src/beam_receive.erl
new file mode 100644
index 0000000000..9ed44ad5d7
--- /dev/null
+++ b/lib/compiler/src/beam_receive.erl
@@ -0,0 +1,388 @@
+%%
+%% %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(beam_receive).
+-export([module/2]).
+-import(lists, [foldl/3,reverse/1,reverse/2]).
+
+%%%
+%%% In code such as:
+%%%
+%%% Ref = make_ref(), %Or erlang:monitor(process, Pid)
+%%% .
+%%% .
+%%% .
+%%% receive
+%%% {Ref,Reply} -> Reply
+%%% end.
+%%%
+%%% we know that none of the messages that exist in the message queue
+%%% before the call to make_ref/0 can be matched out in the receive
+%%% statement. Therefore we can avoid going through the entire message
+%%% queue if we introduce two new instructions (here written as
+%%% BIFs in pseudo-Erlang):
+%%%
+%%% recv_mark(SomeUniqInteger),
+%%% Ref = make_ref(),
+%%% .
+%%% .
+%%% .
+%%% recv_set(SomeUniqInteger),
+%%% receive
+%%% {Ref,Reply} -> Reply
+%%% end.
+%%%
+%%% The recv_mark/1 instruction will save the current position and
+%%% SomeUniqInteger in the process context. The recv_set
+%%% instruction will verify that SomeUniqInteger is still stored
+%%% in the process context. If it is, it will set the current pointer
+%%% for the message queue (the next message to be read out) to the
+%%% position that was saved by recv_mark/1.
+%%%
+%%% The remove_message instruction must be modified to invalidate
+%%% the information stored by the previous recv_mark/1, in case there
+%%% is another receive executed between the calls to recv_mark/1 and
+%%% recv_set/1.
+%%%
+%%% We use a reference to a label (i.e. a position in the loaded code)
+%%% as the SomeUniqInteger.
+%%%
+
+module({Mod,Exp,Attr,Fs0,Lc}, _Opts) ->
+ Fs = [function(F) || F <- Fs0],
+ Code = {Mod,Exp,Attr,Fs,Lc},
+ {ok,Code}.
+
+%%%
+%%% Local functions.
+%%%
+
+function({function,Name,Arity,Entry,Is}) ->
+ try
+ D = beam_utils:index_labels(Is),
+ {function,Name,Arity,Entry,opt(Is, D, [])}
+ catch
+ Class:Error ->
+ Stack = erlang:get_stacktrace(),
+ io:fwrite("Function: ~w/~w\n", [Name,Arity]),
+ erlang:raise(Class, Error, Stack)
+ end.
+
+opt([{call_ext,Arity,{extfunc,erlang,Name,Arity}}=I|Is0], D, Acc) ->
+ case creates_new_ref(Name, Arity) of
+ true ->
+ %% The call creates a brand new reference. Now
+ %% search for a receive statement in the same
+ %% function that will match against the reference.
+ case opt_recv(Is0, D) of
+ no ->
+ opt(Is0, D, [I|Acc]);
+ {yes,Is,Lbl} ->
+ opt(Is, D, [I,{recv_mark,{f,Lbl}}|Acc])
+ end;
+ false ->
+ opt(Is0, D, [I|Acc])
+ end;
+opt([I|Is], D, Acc) ->
+ opt(Is, D, [I|Acc]);
+opt([], _, Acc) ->
+ reverse(Acc).
+
+%% creates_new_ref(Name, Arity) -> true|false.
+%% Return 'true' if the BIF Name/Arity will create a new reference.
+creates_new_ref(monitor, 2) -> true;
+creates_new_ref(make_ref, 0) -> true;
+creates_new_ref(_, _) -> false.
+
+%% opt_recv([Instruction], LabelIndex) -> no|{yes,[Instruction]}
+%% Search for a receive statement that will only retrieve messages
+%% that contain the newly created reference (which is currently in {x,0}).
+opt_recv(Is, D) ->
+ R = regs_init_x0(),
+ L = gb_sets:empty(),
+ opt_recv(Is, D, R, L, []).
+
+opt_recv([{label,L}=Lbl,{loop_rec,{f,Fail},_}=Loop|Is], D, R0, _, Acc) ->
+ R = regs_kill_not_live(0, R0),
+ case regs_to_list(R) of
+ [{y,_}=RefReg] ->
+ %% We now have the new reference in the Y register RefReg
+ %% and the current instruction is the beginning of a
+ %% receive statement. We must now verify that only messages
+ %% that contain the reference will be matched.
+ case opt_ref_used(Is, RefReg, Fail, D) of
+ false ->
+ no;
+ true ->
+ RecvSet = {recv_set,{f,L}},
+ {yes,reverse(Acc, [RecvSet,Lbl,Loop|Is]),L}
+ end;
+ [] ->
+ no
+ end;
+opt_recv([I|Is], D, R0, L0, Acc) ->
+ {R,L} = opt_update_regs(I, R0, L0),
+ case regs_empty(R) of
+ true ->
+ %% The reference is no longer alive. There is no
+ %% point in continuing the search.
+ no;
+ false ->
+ opt_recv(Is, D, R, L, [I|Acc])
+ end.
+
+opt_update_regs({block,Bl}, R, L) ->
+ {opt_update_regs_bl(Bl, R),L};
+opt_update_regs({call,_,_}, R, L) ->
+ {regs_kill_not_live(0, R),L};
+opt_update_regs({call_ext,_,_}, R, L) ->
+ {regs_kill_not_live(0, R),L};
+opt_update_regs({call_fun,_}, R, L) ->
+ {regs_kill_not_live(0, R),L};
+opt_update_regs({kill,Y}, R, L) ->
+ {regs_kill([Y], R),L};
+opt_update_regs(send, R, L) ->
+ {regs_kill_not_live(0, R),L};
+opt_update_regs({'catch',_,{f,Lbl}}, R, L) ->
+ {R,gb_sets:add(Lbl, L)};
+opt_update_regs({catch_end,_}, R, L) ->
+ {R,L};
+opt_update_regs({label,Lbl}, R, L) ->
+ case gb_sets:is_member(Lbl, L) of
+ false ->
+ %% We can't allow arbitrary labels (since the receive
+ %% could be entered without first creating the reference).
+ {regs_init(),L};
+ true ->
+ %% A catch label for a previously seen catch instruction is OK.
+ {R,L}
+ end;
+opt_update_regs({try_end,_}, R, L) ->
+ {R,L};
+opt_update_regs(_I, _R, L) ->
+ %% Unrecognized instruction. Abort the search.
+ {regs_init(),L}.
+
+opt_update_regs_bl([{set,Ds,_,{alloc,Live,_}}|Is], Regs0) ->
+ Regs1 = regs_kill_not_live(Live, Regs0),
+ Regs = regs_kill(Ds, Regs1),
+ opt_update_regs_bl(Is, Regs);
+opt_update_regs_bl([{set,[Dst]=Ds,[Src],move}|Is], Regs0) ->
+ Regs1 = regs_kill(Ds, Regs0),
+ Regs = case regs_is_member(Src, Regs1) of
+ false -> Regs1;
+ true -> regs_add(Dst, Regs1)
+ end,
+ opt_update_regs_bl(Is, Regs);
+opt_update_regs_bl([{set,Ds,_,_}|Is], Regs0) ->
+ Regs = regs_kill(Ds, Regs0),
+ opt_update_regs_bl(Is, Regs);
+opt_update_regs_bl([], Regs) -> Regs.
+
+%% opt_ref_used([Instruction], RefRegister, FailLabel, LabelIndex) -> true|false
+%% Return 'true' if it is certain that only messages that contain the same
+%% reference as in RefRegister can be matched out. Otherwise return 'false'.
+%%
+%% Basically, we follow all possible paths through the receive statement.
+%% If all paths are safe, we return 'true'.
+%%
+%% A branch to FailLabel is safe, because it exits the receive statement
+%% and no further message may be matched out.
+%%
+%% If a path hits an comparision between RefRegister and part of the message,
+%% that path is safe (any messages that may be matched further down the
+%% path is guaranteed to contain the reference).
+%%
+%% Otherwise, if we hit a 'remove_message' instruction, we give up
+%% and return 'false' (the optimization is definitely unsafe). If
+%% we hit an unrecognized instruction, we also give up and return
+%% 'false' (the optimization may be unsafe).
+
+opt_ref_used(Is, RefReg, Fail, D) ->
+ Done = gb_sets:singleton(Fail),
+ Regs = regs_init_x0(),
+ try
+ opt_ref_used_1(Is, RefReg, D, Done, Regs),
+ true
+ catch
+ throw:not_used ->
+ false
+ end.
+
+%% This functions only returns if all paths through the receive
+%% statement are safe, and throws an 'not_used' term otherwise.
+opt_ref_used_1([{block,Bl}|Is], RefReg, D, Done, Regs0) ->
+ Regs = opt_ref_used_bl(Bl, Regs0),
+ opt_ref_used_1(Is, RefReg, D, Done, Regs);
+opt_ref_used_1([{test,is_eq_exact,{f,Fail},Args}|Is], RefReg, D, Done0, Regs) ->
+ Done = opt_ref_used_at(Fail, RefReg, D, Done0, Regs),
+ case is_ref_msg_comparison(Args, RefReg, Regs) of
+ false ->
+ opt_ref_used_1(Is, RefReg, D, Done, Regs);
+ true ->
+ %% The instructions that follow (Is) can only be executed
+ %% if the message contains the same reference as in RefReg.
+ Done
+ end;
+opt_ref_used_1([{test,is_ne_exact,{f,Fail},Args}|Is], RefReg, D, Done0, Regs) ->
+ Done = opt_ref_used_1(Is, RefReg, D, Done0, Regs),
+ case is_ref_msg_comparison(Args, RefReg, Regs) of
+ false ->
+ opt_ref_used_at(Fail, RefReg, D, Done, Regs);
+ true ->
+ Done
+ end;
+opt_ref_used_1([{test,_,{f,Fail},_}|Is], RefReg, D, Done0, Regs) ->
+ Done = opt_ref_used_at(Fail, RefReg, D, Done0, Regs),
+ opt_ref_used_1(Is, RefReg, D, Done, Regs);
+opt_ref_used_1([{select_tuple_arity,_,{f,Fail},{list,List}}|_], RefReg, D, Done, Regs) ->
+ Lbls = [F || {f,F} <- List] ++ [Fail],
+ opt_ref_used_in_all(Lbls, RefReg, D, Done, Regs);
+opt_ref_used_1([{select_val,_,{f,Fail},{list,List}}|_], RefReg, D, Done, Regs) ->
+ Lbls = [F || {f,F} <- List] ++ [Fail],
+ opt_ref_used_in_all(Lbls, RefReg, D, Done, Regs);
+opt_ref_used_1([{label,Lbl}|Is], RefReg, D, Done, Regs) ->
+ case gb_sets:is_member(Lbl, Done) of
+ true -> Done;
+ false -> opt_ref_used_1(Is, RefReg, D, Done, Regs)
+ end;
+opt_ref_used_1([{loop_rec_end,_}|_], _, _, Done, _) ->
+ Done;
+opt_ref_used_1([_I|_], _RefReg, _D, _Done, _Regs) ->
+ %% The optimization may be unsafe.
+ throw(not_used).
+
+%% is_ref_msg_comparison(Args, RefReg, RegisterSet) -> true|false.
+%% Return 'true' if Args denotes a comparison between the
+%% reference and message or part of the message.
+is_ref_msg_comparison([R,RefReg], RefReg, Regs) ->
+ regs_is_member(R, Regs);
+is_ref_msg_comparison([RefReg,R], RefReg, Regs) ->
+ regs_is_member(R, Regs);
+is_ref_msg_comparison([_,_], _, _) -> false.
+
+opt_ref_used_in_all([L|Ls], RefReg, D, Done0, Regs) ->
+ Done = opt_ref_used_at(L, RefReg, D, Done0, Regs),
+ opt_ref_used_in_all(Ls, RefReg, D, Done, Regs);
+opt_ref_used_in_all([], _, _, Done, _) -> Done.
+
+opt_ref_used_at(Fail, RefReg, D, Done0, Regs) ->
+ case gb_sets:is_member(Fail, Done0) of
+ true ->
+ Done0;
+ false ->
+ Is = beam_utils:code_at(Fail, D),
+ Done = opt_ref_used_1(Is, RefReg, D, Done0, Regs),
+ gb_sets:add(Fail, Done)
+ end.
+
+opt_ref_used_bl([{set,[],[],remove_message}|_], _) ->
+ %% We have proved that a message that does not depend on the
+ %% reference can be matched out.
+ throw(not_used);
+opt_ref_used_bl([{set,Ds,Ss,_}|Is], Regs0) ->
+ case regs_all_members(Ss, Regs0) of
+ false ->
+ %% The destination registers may be assigned values that
+ %% are not dependent on the message being matched.
+ Regs = regs_kill(Ds, Regs0),
+ opt_ref_used_bl(Is, Regs);
+ true ->
+ %% All the sources depend on the message directly or
+ %% indirectly.
+ Regs = regs_add_list(Ds, Regs0),
+ opt_ref_used_bl(Is, Regs)
+ end;
+opt_ref_used_bl([], Regs) -> Regs.
+
+%%%
+%%% Functions for keeping track of a set of registers.
+%%%
+
+%% regs_init() -> RegisterSet
+%% Return an empty set of registers.
+
+regs_init() ->
+ {0,0}.
+
+%% regs_init_x0() -> RegisterSet
+%% Return a set that only contains the {x,0} register.
+
+regs_init_x0() ->
+ {1 bsl 0,0}.
+
+%% regs_empty(Register) -> true|false
+%% Test whether the register set is empty.
+
+regs_empty(R) ->
+ R =:= {0,0}.
+
+%% regs_kill_not_live(Live, RegisterSet) -> RegisterSet'
+%% Kill all registers indicated not live by Live.
+
+regs_kill_not_live(Live, {Xregs,Yregs}) ->
+ {Xregs band ((1 bsl Live)-1),Yregs}.
+
+%% regs_kill([Register], RegisterSet) -> RegisterSet'
+%% Kill all registers mentioned in the list of registers.
+
+regs_kill([{x,N}|Rs], {Xregs,Yregs}) ->
+ regs_kill(Rs, {Xregs band (bnot (1 bsl N)),Yregs});
+regs_kill([{y,N}|Rs], {Xregs,Yregs}) ->
+ regs_kill(Rs, {Xregs,Yregs band (bnot (1 bsl N))});
+regs_kill([{fr,_}|Rs], Regs) ->
+ regs_kill(Rs, Regs);
+regs_kill([], Regs) -> Regs.
+
+regs_add_list(List, Regs) ->
+ foldl(fun(R, A) -> regs_add(R, A) end, Regs, List).
+
+%% regs_add(Register, RegisterSet) -> RegisterSet'
+%% Add a new register to the set of registers.
+
+regs_add({x,N}, {Xregs,Yregs}) ->
+ {Xregs bor (1 bsl N),Yregs};
+regs_add({y,N}, {Xregs,Yregs}) ->
+ {Xregs,Yregs bor (1 bsl N)}.
+
+%% regs_all_members([Register], RegisterSet) -> true|false
+%% Test whether all of the registers are part of the register set.
+
+regs_all_members([R|Rs], Regs) ->
+ regs_is_member(R, Regs) andalso regs_all_members(Rs, Regs);
+regs_all_members([], _) -> true.
+
+%% regs_is_member(Register, RegisterSet) -> true|false
+%% Test whether Register is part of the register set.
+
+regs_is_member({x,N}, {Regs,_}) -> Regs band (1 bsl N) =/= 0;
+regs_is_member({y,N}, {_,Regs}) -> Regs band (1 bsl N) =/= 0;
+regs_is_member(_, _) -> false.
+
+%% regs_to_list(RegisterSet) -> [Register]
+%% Convert the register set to an explicit list of registers.
+regs_to_list({Xregs,Yregs}) ->
+ regs_to_list_1(Xregs, 0, x, regs_to_list_1(Yregs, 0, y, [])).
+
+regs_to_list_1(0, _, _, Acc) ->
+ Acc;
+regs_to_list_1(Regs, N, Tag, Acc) when (Regs band 1) =:= 1 ->
+ regs_to_list_1(Regs bsr 1, N+1, Tag, [{Tag,N}|Acc]);
+regs_to_list_1(Regs, N, Tag, Acc) ->
+ regs_to_list_1(Regs bsr 1, N+1, Tag, Acc).
diff --git a/lib/compiler/src/beam_type.erl b/lib/compiler/src/beam_type.erl
index 66c8816409..f83f73b224 100644
--- a/lib/compiler/src/beam_type.erl
+++ b/lib/compiler/src/beam_type.erl
@@ -76,9 +76,6 @@ simplify_basic_1([{set,[D],[{integer,Index},Reg],{bif,element,_}}=I0|Is], Ts0, A
end,
Ts = update(I, Ts0),
simplify_basic_1(Is, Ts, [I|Acc]);
-simplify_basic_1([{set,[_],[_],{bif,_,{f,0}}}=I|Is], Ts0, Acc) ->
- Ts = update(I, Ts0),
- simplify_basic_1(Is, Ts, [I|Acc]);
simplify_basic_1([{set,[D],[TupleReg],{get_tuple_element,0}}=I|Is0], Ts0, Acc) ->
case tdb_find(TupleReg, Ts0) of
{tuple,_,[Contents]} ->
@@ -118,7 +115,6 @@ simplify_basic_1([{test,is_record,_,[R,{atom,_}=Tag,{integer,Arity}]}=I|Is], Ts0
Ts = update(I, Ts0),
simplify_basic_1(Is, Ts, [I|Acc])
end;
-
simplify_basic_1([I|Is], Ts0, Acc) ->
Ts = update(I, Ts0),
simplify_basic_1(Is, Ts, [I|Acc]);
@@ -183,7 +179,7 @@ simplify_float_1([], Ts, Rs, Acc0) ->
{Is,Ts}.
opt_fmoves([{set,[{x,_}=R],[{fr,_}]=Src,fmove}=I1,
- {set,[{y,_}]=Dst,[{x,_}=R],move}=I2|Is], Acc) ->
+ {set,[_]=Dst,[{x,_}=R],move}=I2|Is], Acc) ->
case beam_utils:is_killed_block(R, Is) of
false -> opt_fmoves(Is, [I2,I1|Acc]);
true -> opt_fmoves(Is, [{set,Dst,Src,fmove}|Acc])
diff --git a/lib/compiler/src/beam_utils.erl b/lib/compiler/src/beam_utils.erl
index ac249e6672..761d4ffec0 100644
--- a/lib/compiler/src/beam_utils.erl
+++ b/lib/compiler/src/beam_utils.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
%% Purpose : Common utilities used by several optimization passes.
@@ -424,12 +424,6 @@ check_liveness(R, [{bs_add,{f,0},Ss,D}|Is], St) ->
false when R =:= D -> {killed,St};
false -> check_liveness(R, Is, St)
end;
-check_liveness(R, [{bs_bits_to_bytes2,Src,Dst}|Is], St) ->
- case R of
- Src -> {used,St};
- Dst -> {killed,St};
- _ -> check_liveness(R, Is, St)
- end;
check_liveness(R, [{bs_put_binary,{f,0},Sz,_,_,Src}|Is], St) ->
case member(R, [Sz,Src]) of
true -> {used,St};
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 1fd61831e0..f3a2b01e04 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -18,6 +18,8 @@
-module(beam_validator).
+-compile({no_auto_import,[min/2]}).
+
-export([file/1, files/1]).
%% Interface for compiler.
@@ -416,6 +418,11 @@ valfun_1({put,Src}, Vst) ->
valfun_1({put_string,Sz,_,Dst}, Vst0) when is_integer(Sz) ->
Vst = eat_heap(2*Sz, Vst0),
set_type_reg(cons, Dst, Vst);
+%% Instructions for optimization of selective receives.
+valfun_1({recv_mark,{f,Fail}}, Vst) when is_integer(Fail) ->
+ Vst;
+valfun_1({recv_set,{f,Fail}}, Vst) when is_integer(Fail) ->
+ Vst;
%% Misc.
valfun_1({'%live',Live}, Vst) ->
verify_live(Live, Vst),
@@ -752,9 +759,6 @@ valfun_4({bs_utf8_size,{f,Fail},A,Dst}, Vst) ->
valfun_4({bs_utf16_size,{f,Fail},A,Dst}, Vst) ->
assert_term(A, Vst),
set_type_reg({integer,[]}, Dst, branch_state(Fail, Vst));
-valfun_4({bs_bits_to_bytes2,Src,Dst}, Vst) ->
- assert_term(Src, Vst),
- set_type_reg({integer,[]}, Dst, Vst);
valfun_4({bs_bits_to_bytes,{f,Fail},Src,Dst}, Vst) ->
assert_term(Src, Vst),
set_type_reg({integer,[]}, Dst, branch_state(Fail, Vst));
diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl
index 74fc0878cf..d1fd9d40e2 100644
--- a/lib/compiler/src/cerl.erl
+++ b/lib/compiler/src/cerl.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%% =====================================================================
@@ -122,6 +122,9 @@
bitstr_bitsize/1, bitstr_unit/1, bitstr_type/1,
bitstr_flags/1]).
+-export_type([c_binary/0, c_call/0, c_clause/0, c_cons/0, c_fun/0, c_literal/0,
+ c_module/0, c_tuple/0, c_values/0, c_var/0, cerl/0, var_name/0]).
+
%%
%% needed by the include file below -- do not move
%%
diff --git a/lib/compiler/src/cerl_inline.erl b/lib/compiler/src/cerl_inline.erl
index 6d7eca0113..c15103999f 100644
--- a/lib/compiler/src/cerl_inline.erl
+++ b/lib/compiler/src/cerl_inline.erl
@@ -65,7 +65,6 @@
try_evars/1, try_handler/1, tuple_es/1, tuple_arity/1,
type/1, values_es/1, var_name/1]).
--import(erlang, [max/2]).
-import(lists, [foldl/3, foldr/3, mapfoldl/3, reverse/1]).
%%
@@ -201,9 +200,9 @@ start(Reply, Tree, Ctxt, Opts) ->
false ->
ok
end,
- Size = max(1, proplists:get_value(inline_size, Opts)),
- Effort = max(1, proplists:get_value(inline_effort, Opts)),
- Unroll = max(1, proplists:get_value(inline_unroll, Opts)),
+ Size = erlang:max(1, proplists:get_value(inline_size, Opts)),
+ Effort = erlang:max(1, proplists:get_value(inline_effort, Opts)),
+ Unroll = erlang:max(1, proplists:get_value(inline_unroll, Opts)),
case proplists:get_bool(verbose, Opts) of
true ->
io:fwrite("Inlining: inline_size=~w inline_effort=~w\n",
diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl
index 7a2057713e..1e3755025f 100644
--- a/lib/compiler/src/cerl_trees.erl
+++ b/lib/compiler/src/cerl_trees.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%% @doc Basic functions on Core Erlang abstract syntax trees.
@@ -73,14 +73,12 @@ depth(T) ->
[] ->
0;
Gs ->
- 1 + lists:foldl(fun (G, A) -> max(depth_1(G), A) end, 0, Gs)
+ 1 + lists:foldl(fun (G, A) -> erlang:max(depth_1(G), A) end, 0, Gs)
end.
depth_1(Ts) ->
- lists:foldl(fun (T, A) -> max(depth(T), A) end, 0, Ts).
+ lists:foldl(fun (T, A) -> erlang:max(depth(T), A) end, 0, Ts).
-max(X, Y) when X > Y -> X;
-max(_, Y) -> Y.
%% @spec size(Tree::cerl()) -> integer()
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 5017fd2c23..ed7a9144a8 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -29,6 +29,8 @@
%% Erlc interface.
-export([compile/3,compile_beam/3,compile_asm/3,compile_core/3]).
+-export_type([option/0]).
+
-include("erl_compile.hrl").
-include("core_parse.hrl").
@@ -39,8 +41,7 @@
-type option() :: atom() | {atom(), term()} | {'d', atom(), term()}.
--type line() :: integer().
--type err_info() :: {line(), module(), term()}. %% ErrorDescriptor
+-type err_info() :: {erl_scan:line(), module(), term()}. %% ErrorDescriptor
-type errors() :: [{file:filename(), [err_info()]}].
-type warnings() :: [{file:filename(), [err_info()]}].
-type mod_ret() :: {'ok', module()}
@@ -68,7 +69,7 @@
file(File) -> file(File, ?DEFAULT_OPTIONS).
--spec file(module() | file:filename(), [option()]) -> comp_ret().
+-spec file(module() | file:filename(), [option()] | option()) -> comp_ret().
file(File, Opts) when is_list(Opts) ->
do_compile({file,File}, Opts++env_default_opts());
@@ -86,6 +87,8 @@ forms(Forms, Opt) when is_atom(Opt) ->
%% would have generated a Beam file, false otherwise (if only a binary or a
%% listing file would have been generated).
+-spec output_generated([option()]) -> boolean().
+
output_generated(Opts) ->
noenv_output_generated(Opts++env_default_opts()).
@@ -94,6 +97,8 @@ output_generated(Opts) ->
%% for default options.
%%
+-spec noenv_file(module() | file:filename(), [option()] | option()) -> comp_ret().
+
noenv_file(File, Opts) when is_list(Opts) ->
do_compile({file,File}, Opts);
noenv_file(File, Opt) ->
@@ -104,6 +109,8 @@ noenv_forms(Forms, Opts) when is_list(Opts) ->
noenv_forms(Forms, Opt) when is_atom(Opt) ->
noenv_forms(Forms, [Opt|?DEFAULT_OPTIONS]).
+-spec noenv_output_generated([option()]) -> boolean().
+
noenv_output_generated(Opts) ->
any(fun ({save_binary,_F}) -> true;
(_Other) -> false
@@ -162,6 +169,10 @@ expand_opt(report, Os) ->
[report_errors,report_warnings|Os];
expand_opt(return, Os) ->
[return_errors,return_warnings|Os];
+expand_opt(r12, Os) ->
+ [no_recv_opt|Os];
+expand_opt(r13, Os) ->
+ [no_recv_opt|Os];
expand_opt({debug_info_key,_}=O, Os) ->
[encrypt_debug_info,O|Os];
expand_opt(no_float_opt, Os) ->
@@ -210,16 +221,16 @@ format_error({module_name,Mod,Filename}) ->
[Mod,Filename]).
%% The compile state record.
--record(compile, {filename="",
- dir="",
- base="",
- ifile="",
- ofile="",
+-record(compile, {filename="" :: file:filename(),
+ dir="" :: file:filename(),
+ base="" :: file:filename(),
+ ifile="" :: file:filename(),
+ ofile="" :: file:filename(),
module=[],
code=[],
core_code=[],
abstract_code=[], %Abstract code for debugger.
- options=[],
+ options=[] :: [option()],
errors=[],
warnings=[]}).
@@ -290,15 +301,6 @@ fold_comp([{Name,Pass}|Ps], Run, St0) ->
end;
fold_comp([], _Run, St) -> {ok,St}.
-os_process_size() ->
- case os:type() of
- {unix, sunos} ->
- Size = os:cmd("ps -o vsz -p " ++ os:getpid() ++ " | tail -1"),
- list_to_integer(lib:nonl(Size));
- _ ->
- 0
- end.
-
run_tc({Name,Fun}, St) ->
Before0 = statistics(runtime),
Val = (catch Fun(St)),
@@ -307,9 +309,8 @@ run_tc({Name,Fun}, St) ->
{After_c, _} = After0,
Mem0 = erts_debug:flat_size(Val)*erlang:system_info(wordsize),
Mem = lists:flatten(io_lib:format("~.1f kB", [Mem0/1024])),
- Sz = lists:flatten(io_lib:format("~.1f MB", [os_process_size()/1024])),
- io:format(" ~-30s: ~10.2f s ~12s ~10s\n",
- [Name,(After_c-Before_c) / 1000,Mem,Sz]),
+ io:format(" ~-30s: ~10.2f s ~12s\n",
+ [Name,(After_c-Before_c) / 1000,Mem]),
Val.
comp_ret_ok(#compile{code=Code,warnings=Warn0,module=Mod,options=Opts}=St) ->
@@ -366,7 +367,7 @@ mpf(Ms) ->
[{File,[M || {F,M} <- Ms, F =:= File]} ||
File <- lists:usort([F || {F,_} <- Ms])].
-%% passes(form|file, [Option]) -> [{Name,PassFun}]
+%% passes(forms|file, [Option]) -> [{Name,PassFun}]
%% Figure out which passes that need to be run.
passes(forms, Opts) ->
@@ -621,6 +622,8 @@ asm_passes() ->
{iff,dclean,{listing,"clean"}},
{unless,no_bsm_opt,{pass,beam_bsm}},
{iff,dbsm,{listing,"bsm"}},
+ {unless,no_recv_opt,{pass,beam_receive}},
+ {iff,drecv,{listing,"recv"}},
{unless,no_stack_trimming,{pass,beam_trim}},
{iff,dtrim,{listing,"trim"}},
{pass,beam_flatten}]},
@@ -830,7 +833,6 @@ get_core_transforms(Opts) -> [M || {core_transform,M} <- Opts].
core_transforms(St) ->
%% The options field holds the complete list of options at this
-
Ts = get_core_transforms(St#compile.options),
foldl_core_transforms(St, Ts).
@@ -904,13 +906,8 @@ expand_module(#compile{code=Code,options=Opts0}=St0) ->
{ok,St0#compile{module=Mod,options=Opts,code={Mod,Exp,Forms}}}.
core_module(#compile{code=Code0,options=Opts}=St) ->
- case v3_core:module(Code0, Opts) of
- {ok,Code,Ws} ->
- {ok,St#compile{code=Code,warnings=St#compile.warnings ++ Ws}};
- {error,Es,Ws} ->
- {error,St#compile{warnings=St#compile.warnings ++ Ws,
- errors=St#compile.errors ++ Es}}
- end.
+ {ok,Code,Ws} = v3_core:module(Code0, Opts),
+ {ok,St#compile{code=Code,warnings=St#compile.warnings ++ Ws}}.
core_fold_module(#compile{code=Code0,options=Opts,warnings=Warns}=St) ->
{ok,Code,Ws} = sys_core_fold:module(Code0, Opts),
@@ -1179,12 +1176,12 @@ write_binary(Name, Bin, St) ->
%% report_errors(State) -> ok
%% report_warnings(State) -> ok
-report_errors(St) ->
- case member(report_errors, St#compile.options) of
+report_errors(#compile{options=Opts,errors=Errors}) ->
+ case member(report_errors, Opts) of
true ->
foreach(fun ({{F,_L},Eds}) -> list_errors(F, Eds);
({F,Eds}) -> list_errors(F, Eds) end,
- St#compile.errors);
+ Errors);
false -> ok
end.
diff --git a/lib/compiler/src/compiler.app.src b/lib/compiler/src/compiler.app.src
index b0311365c4..4ac879c9a4 100644
--- a/lib/compiler/src/compiler.app.src
+++ b/lib/compiler/src/compiler.app.src
@@ -1,19 +1,19 @@
% This is an -*- erlang -*- file.
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
{application, compiler,
@@ -33,6 +33,7 @@
beam_listing,
beam_opcodes,
beam_peep,
+ beam_receive,
beam_trim,
beam_type,
beam_utils,
diff --git a/lib/compiler/src/erl_bifs.erl b/lib/compiler/src/erl_bifs.erl
index e87bb276de..f8128702dd 100644
--- a/lib/compiler/src/erl_bifs.erl
+++ b/lib/compiler/src/erl_bifs.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%% Purpose: Information about the Erlang built-in functions.
@@ -65,6 +65,8 @@ is_pure(erlang, 'xor', 2) -> true;
is_pure(erlang, abs, 1) -> true;
is_pure(erlang, atom_to_binary, 2) -> true;
is_pure(erlang, atom_to_list, 1) -> true;
+is_pure(erlang, binary_part, 2) -> true;
+is_pure(erlang, binary_part, 3) -> true;
is_pure(erlang, binary_to_atom, 2) -> true;
is_pure(erlang, binary_to_list, 1) -> true;
is_pure(erlang, binary_to_list, 3) -> true;
diff --git a/lib/compiler/src/genop.tab b/lib/compiler/src/genop.tab
index 47e6e72402..63527bda8f 100644
--- a/lib/compiler/src/genop.tab
+++ b/lib/compiler/src/genop.tab
@@ -208,7 +208,7 @@ BEAM_FORMAT_NUMBER=0
# New instructions in R10B.
109: bs_init2/6
-110: bs_bits_to_bytes/3
+110: -bs_bits_to_bytes/3
111: bs_add/5
112: apply/1
113: apply_last/2
@@ -274,3 +274,9 @@ BEAM_FORMAT_NUMBER=0
# R13B03
149: on_load/0
+
+# R14A
+
+150: recv_mark/1
+151: recv_set/1
+152: gc_bif3/7
diff --git a/lib/compiler/src/rec_env.erl b/lib/compiler/src/rec_env.erl
index 9b73e08ad8..31a1f8b0b7 100644
--- a/lib/compiler/src/rec_env.erl
+++ b/lib/compiler/src/rec_env.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%% @author Richard Carlsson <[email protected]>
@@ -32,7 +32,7 @@
get/2, is_defined/2, is_empty/1, keys/1, lookup/2, new_key/1,
new_key/2, new_keys/2, new_keys/3, size/1, to_list/1]).
--import(erlang, [max/2]).
+-export_type([environment/0]).
-ifdef(DEBUG).
-export([test/1, test_custom/1, test_custom/2]).
@@ -586,7 +586,7 @@ new_key(N, R, _T, F, Env) ->
new_key(generate(N, R1), R1, 0, F, Env).
start_range(Env) ->
- max(env_size(Env) * ?START_RANGE_FACTOR, ?MINIMUM_RANGE).
+ erlang:max(env_size(Env) * ?START_RANGE_FACTOR, ?MINIMUM_RANGE).
%% The previous key might or might not be used to compute the next key
%% to be tried. It is currently not used.
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index 068478496b..96015fbe58 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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%
%%
%% Purpose : Constant folding optimisation for Core
@@ -602,15 +602,23 @@ count_bits_1(Int, Bits) -> count_bits_1(Int bsr 64, Bits+64).
%% a rewritten expression consisting of a sequence of
%% the arguments only is returned.
-useless_call(effect, #c_call{module=#c_literal{val=Mod},
+useless_call(effect, #c_call{anno=Anno,
+ module=#c_literal{val=Mod},
name=#c_literal{val=Name},
args=Args}=Call) ->
A = length(Args),
case erl_bifs:is_safe(Mod, Name, A) of
false ->
case erl_bifs:is_pure(Mod, Name, A) of
- true -> add_warning(Call, result_ignored);
- false -> ok
+ true ->
+ case member(result_not_wanted, Anno) of
+ false ->
+ add_warning(Call, result_ignored);
+ true ->
+ ok
+ end;
+ false ->
+ ok
end,
no;
true ->
@@ -1030,6 +1038,8 @@ fold_non_lit_args(Call, lists, append, [Arg1,Arg2], _) ->
eval_append(Call, Arg1, Arg2);
fold_non_lit_args(Call, erlang, setelement, [Arg1,Arg2,Arg3], _) ->
eval_setelement(Call, Arg1, Arg2, Arg3);
+fold_non_lit_args(Call, erlang, is_record, [Arg1,Arg2,Arg3], Sub) ->
+ eval_is_record(Call, Arg1, Arg2, Arg3, Sub);
fold_non_lit_args(Call, erlang, N, Args, Sub) ->
NumArgs = length(Args),
case erl_internal:comp_op(N, NumArgs) of
@@ -1186,19 +1196,22 @@ eval_element(Call, #c_literal{val=Pos}, #c_tuple{es=Es}, _Types) when is_integer
true ->
eval_failure(Call, badarg)
end;
-%% eval_element(Call, #c_literal{val=Pos}, #c_var{name=V}, Types)
-%% when is_integer(Pos) ->
-%% case orddict:find(V, Types#sub.t) of
-%% {ok,#c_tuple{es=Elements}} ->
-%% if
-%% 1 =< Pos, Pos =< length(Elements) ->
-%% lists:nth(Pos, Elements);
-%% true ->
-%% eval_failure(Call, badarg)
-%% end;
-%% error ->
-%% Call
-%% end;
+eval_element(Call, #c_literal{val=Pos}, #c_var{name=V}, Types)
+ when is_integer(Pos) ->
+ case orddict:find(V, Types#sub.t) of
+ {ok,#c_tuple{es=Elements}} ->
+ if
+ 1 =< Pos, Pos =< length(Elements) ->
+ case lists:nth(Pos, Elements) of
+ #c_alias{var=Alias} -> Alias;
+ Res -> Res
+ end;
+ true ->
+ eval_failure(Call, badarg)
+ end;
+ error ->
+ Call
+ end;
eval_element(Call, Pos, Tuple, _Types) ->
case is_not_integer(Pos) orelse is_not_tuple(Tuple) of
true ->
@@ -1207,6 +1220,20 @@ eval_element(Call, Pos, Tuple, _Types) ->
Call
end.
+%% eval_is_record(Call, Var, Tag, Size, Types) -> Val.
+%% Evaluates is_record/3 using type information.
+%%
+eval_is_record(Call, #c_var{name=V}, #c_literal{val=NeededTag}=Lit,
+ #c_literal{val=Size}, Types) ->
+ case orddict:find(V, Types#sub.t) of
+ {ok,#c_tuple{es=[#c_literal{val=Tag}|_]=Es}} ->
+ Lit#c_literal{val=Tag =:= NeededTag andalso
+ length(Es) =:= Size};
+ _ ->
+ Call
+ end;
+eval_is_record(Call, _, _, _, _) -> Call.
+
%% is_not_integer(Core) -> true | false.
%% Returns true if Core is definitely not an integer.
@@ -2806,7 +2833,8 @@ format_error({no_effect,{erlang,F,A}}) ->
end,
flatten(io_lib:format(Fmt, Args));
format_error(result_ignored) ->
- "the result of the expression is ignored";
+ "the result of the expression is ignored "
+ "(suppress the warning by assigning the expression to the _ variable)";
format_error(useless_building) ->
"a term is constructed, but never used";
format_error(bin_opt_alias) ->
diff --git a/lib/compiler/src/sys_pre_expand.erl b/lib/compiler/src/sys_pre_expand.erl
index f80d03dfac..480954adac 100644
--- a/lib/compiler/src/sys_pre_expand.erl
+++ b/lib/compiler/src/sys_pre_expand.erl
@@ -403,16 +403,21 @@ expr({'fun',Line,Body}, St) ->
expr({call,Line,{atom,La,N}=Atom,As0}, St0) ->
{As,St1} = expr_list(As0, St0),
Ar = length(As),
- case erl_internal:bif(N, Ar) of
- true ->
- {{call,Line,{remote,La,{atom,La,erlang},Atom},As},St1};
- false ->
- case imported(N, Ar, St1) of
- {yes,Mod} ->
- {{call,Line,{remote,La,{atom,La,Mod},Atom},As},St1};
- no ->
- {{call,Line,Atom,As},St1}
- end
+ case defined(N,Ar,St1) of
+ true ->
+ {{call,Line,Atom,As},St1};
+ _ ->
+ case imported(N, Ar, St1) of
+ {yes,Mod} ->
+ {{call,Line,{remote,La,{atom,La,Mod},Atom},As},St1};
+ no ->
+ case erl_internal:bif(N, Ar) of
+ true ->
+ {{call,Line,{remote,La,{atom,La,erlang},Atom},As},St1};
+ false -> %% This should have been handled by erl_lint
+ {{call,Line,Atom,As},St1}
+ end
+ end
end;
expr({call,Line,{record_field,_,_,_}=M,As0}, St0) ->
expr({call,Line,expand_package(M, St0),As0}, St0);
@@ -685,3 +690,6 @@ imported(F, A, St) ->
{ok,Mod} -> {yes,Mod};
error -> no
end.
+
+defined(F, A, St) ->
+ ordsets:is_element({F,A}, St#expand.defined).
diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl
index 4ac2196d79..f6bb45787c 100644
--- a/lib/compiler/src/v3_core.erl
+++ b/lib/compiler/src/v3_core.erl
@@ -122,14 +122,13 @@
| iclause() | ifun() | iletrec() | imatch() | iprimop()
| iprotect() | ireceive1() | ireceive2() | iset() | itry().
--type error() :: {file:filename(), [{integer(), module(), term()}]}.
-type warning() :: {file:filename(), [{integer(), module(), term()}]}.
-record(core, {vcount=0 :: non_neg_integer(), %Variable counter
fcount=0 :: non_neg_integer(), %Function counter
in_guard=false :: boolean(), %In guard or not.
+ wanted=true :: boolean(), %Result wanted or not.
opts :: [compile:option()], %Options.
- es=[] :: [error()], %Errors.
ws=[] :: [warning()], %Warnings.
file=[{file,""}]}). %File
@@ -140,46 +139,41 @@
| {attribute, integer(), attribute(), _}.
-spec module({module(), [fa()], [form()]}, [compile:option()]) ->
- {'ok',cerl:c_module(),[warning()]} | {'error',[error()],[warning()]}.
+ {'ok',cerl:c_module(),[warning()]}.
module({Mod,Exp,Forms}, Opts) ->
Cexp = map(fun ({_N,_A} = NA) -> #c_var{name=NA} end, Exp),
- {Kfs0,As0,Es,Ws,_File} = foldl(fun (F, Acc) ->
- form(F, Acc, Opts)
- end, {[],[],[],[],[]}, Forms),
+ {Kfs0,As0,Ws,_File} = foldl(fun (F, Acc) ->
+ form(F, Acc, Opts)
+ end, {[],[],[],[]}, Forms),
Kfs = reverse(Kfs0),
As = reverse(As0),
- case Es of
- [] ->
- {ok,#c_module{name=#c_literal{val=Mod},exports=Cexp,attrs=As,defs=Kfs},Ws};
- _ ->
- {error,Es,Ws}
- end.
+ {ok,#c_module{name=#c_literal{val=Mod},exports=Cexp,attrs=As,defs=Kfs},Ws}.
-form({function,_,_,_,_}=F0, {Fs,As,Es0,Ws0,File}, Opts) ->
- {F,Es,Ws} = function(F0, Es0, Ws0, File, Opts),
- {[F|Fs],As,Es,Ws,File};
-form({attribute,_,file,{File,_Line}}, {Fs,As,Es,Ws,_}, _Opts) ->
- {Fs,As,Es,Ws,File};
-form({attribute,_,_,_}=F, {Fs,As,Es,Ws,File}, _Opts) ->
- {Fs,[attribute(F)|As],Es,Ws,File}.
+form({function,_,_,_,_}=F0, {Fs,As,Ws0,File}, Opts) ->
+ {F,Ws} = function(F0, Ws0, File, Opts),
+ {[F|Fs],As,Ws,File};
+form({attribute,_,file,{File,_Line}}, {Fs,As,Ws,_}, _Opts) ->
+ {Fs,As,Ws,File};
+form({attribute,_,_,_}=F, {Fs,As,Ws,File}, _Opts) ->
+ {Fs,[attribute(F)|As],Ws,File}.
attribute({attribute,Line,Name,Val}) ->
{#c_literal{val=Name, anno=[Line]}, #c_literal{val=Val, anno=[Line]}}.
-function({function,_,Name,Arity,Cs0}, Es0, Ws0, File, Opts) ->
+function({function,_,Name,Arity,Cs0}, Ws0, File, Opts) ->
%%ok = io:fwrite("~p - ", [{Name,Arity}]),
- St0 = #core{vcount=0,opts=Opts,es=Es0,ws=Ws0,file=[{file,File}]},
+ St0 = #core{vcount=0,opts=Opts,ws=Ws0,file=[{file,File}]},
{B0,St1} = body(Cs0, Name, Arity, St0),
%%ok = io:fwrite("1", []),
%%ok = io:fwrite("~w:~p~n", [?LINE,B0]),
{B1,St2} = ubody(B0, St1),
%%ok = io:fwrite("2", []),
%%ok = io:fwrite("~w:~p~n", [?LINE,B1]),
- {B2,#core{es=Es,ws=Ws}} = cbody(B1, St2),
+ {B2,#core{ws=Ws}} = cbody(B1, St2),
%%ok = io:fwrite("3~n", []),
%%ok = io:fwrite("~w:~p~n", [?LINE,B2]),
- {{#c_var{name={Name,Arity}},B2},Es,Ws}.
+ {{#c_var{name={Name,Arity}},B2},Ws}.
body(Cs0, Name, Arity, St0) ->
Anno = lineno_anno(element(2, hd(Cs0)), St0),
@@ -580,10 +574,14 @@ expr({'fun',L,{function,F,A},{_,_,_}=Id}, St) ->
{#c_var{anno=Lanno++[{id,Id}],name={F,A}},[],St};
expr({'fun',L,{clauses,Cs},Id}, St) ->
fun_tq(Id, Cs, L, St);
-expr({call,L,{remote,_,M,F},As0}, St0) ->
+expr({call,L,{remote,_,M,F},As0}, #core{wanted=Wanted}=St0) ->
{[M1,F1|As1],Aps,St1} = safe_list([M,F|As0], St0),
Lanno = lineno_anno(L, St1),
- {#icall{anno=#a{anno=Lanno},module=M1,name=F1,args=As1},Aps,St1};
+ Anno = case Wanted of
+ false -> [result_not_wanted|Lanno];
+ true -> Lanno
+ end,
+ {#icall{anno=#a{anno=Anno},module=M1,name=F1,args=As1},Aps,St1};
expr({call,Lc,{atom,Lf,F},As0}, St0) ->
{As1,Aps,St1} = safe_list(As0, St0),
Op = #c_var{anno=lineno_anno(Lf, St1),name={F,length(As1)}},
@@ -596,23 +594,28 @@ expr({call,L,FunExp,As0}, St0) ->
expr({match,L,P0,E0}, St0) ->
%% First fold matches together to create aliases.
{P1,E1} = fold_match(E0, P0),
- {E2,Eps,St1} = novars(E1, St0),
+ St1 = case P1 of
+ {var,_,'_'} -> St0#core{wanted=false};
+ _ -> St0
+ end,
+ {E2,Eps,St2} = novars(E1, St1),
+ St3 = St2#core{wanted=St0#core.wanted},
P2 = try
- pattern(P1, St1)
+ pattern(P1, St3)
catch
throw:Thrown ->
Thrown
end,
- {Fpat,St2} = new_var(St1),
+ {Fpat,St4} = new_var(St3),
Fc = fail_clause([Fpat], c_tuple([#c_literal{val=badmatch},Fpat])),
- Lanno = lineno_anno(L, St2),
+ Lanno = lineno_anno(L, St4),
case P2 of
nomatch ->
- St = add_warning(L, nomatch, St2),
+ St = add_warning(L, nomatch, St4),
{#icase{anno=#a{anno=Lanno},
args=[E2],clauses=[],fc=Fc},Eps,St};
Other when not is_atom(Other) ->
- {#imatch{anno=#a{anno=Lanno},pat=P2,arg=E2,fc=Fc},Eps,St2}
+ {#imatch{anno=#a{anno=Lanno},pat=P2,arg=E2,fc=Fc},Eps,St4}
end;
expr({op,_,'++',{lc,Llc,E,Qs},More}, St0) ->
%% Optimise '++' here because of the list comprehension algorithm.
@@ -2086,20 +2089,12 @@ is_simple(#c_literal{}) -> true;
is_simple(#c_cons{hd=H,tl=T}) ->
is_simple(H) andalso is_simple(T);
is_simple(#c_tuple{es=Es}) -> is_simple_list(Es);
-is_simple(#c_binary{segments=Es}) -> is_simp_bin(Es);
is_simple(_) -> false.
-spec is_simple_list([cerl:cerl()]) -> boolean().
is_simple_list(Es) -> lists:all(fun is_simple/1, Es).
--spec is_simp_bin([cerl:cerl()]) -> boolean().
-
-is_simp_bin(Es) ->
- lists:all(fun (#c_bitstr{val=E,size=S}) ->
- is_simple(E) andalso is_simple(S)
- end, Es).
-
%%%
%%% Handling of warnings.
%%%
diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl
index 974c64a8bc..fbe4d8617e 100644
--- a/lib/compiler/src/v3_kernel.erl
+++ b/lib/compiler/src/v3_kernel.erl
@@ -128,14 +128,27 @@ copy_anno(Kdst, Ksrc) ->
{'ok', #k_mdef{}, [warning()]}.
module(#c_module{anno=A,name=M,exports=Es,attrs=As,defs=Fs}, _Options) ->
+ Kas = attributes(As),
+ Kes = map(fun (#c_var{name={_,_}=Fname}) -> Fname end, Es),
St0 = #kern{lit=dict:new()},
{Kfs,St} = mapfoldl(fun function/2, St0, Fs),
- Kes = map(fun (#c_var{name={_,_}=Fname}) -> Fname end, Es),
- Kas = map(fun ({#c_literal{val=N},V}) ->
- {N,core_lib:literal_value(V)} end, As),
{ok,#k_mdef{anno=A,name=M#c_literal.val,exports=Kes,attributes=Kas,
body=Kfs ++ St#kern.funs},lists:sort(St#kern.ws)}.
+attributes([{#c_literal{val=Name},Val}|As]) ->
+ case include_attribute(Name) of
+ false ->
+ attributes(As);
+ true ->
+ [{Name,core_lib:literal_value(Val)}|attributes(As)]
+ end;
+attributes([]) -> [].
+
+include_attribute(type) -> false;
+include_attribute(spec) -> false;
+include_attribute(opaque) -> false;
+include_attribute(_) -> true.
+
function({#c_var{name={F,Arity}=FA},Body}, St0) ->
try
St1 = St0#kern{func=FA,ff=undefined,vcount=0,fcount=0,ds=sets:new()},
diff --git a/lib/compiler/src/v3_life.erl b/lib/compiler/src/v3_life.erl
index 9fda37530b..a7a4d4dc91 100644
--- a/lib/compiler/src/v3_life.erl
+++ b/lib/compiler/src/v3_life.erl
@@ -361,8 +361,6 @@ match_fail(#k_literal{anno=Anno,val={Atom,Val}}, I, A) when is_atom(Atom) ->
match_fail(#k_tuple{anno=Anno,es=[#k_atom{val=Atom},#k_literal{val=Val}]}, I, A);
match_fail(#k_literal{anno=Anno,val={Atom}}, I, A) when is_atom(Atom) ->
match_fail(#k_tuple{anno=Anno,es=[#k_atom{val=Atom}]}, I, A);
-match_fail(#k_literal{anno=Anno,val=Atom}, I, A) when is_atom(Atom) ->
- match_fail(#k_atom{anno=Anno,val=Atom}, I, A);
match_fail(#k_tuple{es=[#k_atom{val=function_clause}|As]}, I, A) ->
#l{ke={match_fail,{function_clause,literal_list(As, [])}},i=I,a=A};
match_fail(#k_tuple{es=[#k_atom{val=badmatch},Val]}, I, A) ->
diff --git a/lib/compiler/test/andor_SUITE.erl b/lib/compiler/test/andor_SUITE.erl
index a460d54239..84cfd16e60 100644
--- a/lib/compiler/test/andor_SUITE.erl
+++ b/lib/compiler/test/andor_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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(andor_SUITE).
@@ -141,6 +141,10 @@ t_and_or(Config) when is_list(Config) ->
ok.
+-define(GUARD(E), if E -> true;
+ true -> false
+ end).
+
t_andalso(Config) when is_list(Config) ->
Bs = [true,false],
Ps = [{X,Y} || X <- Bs, Y <- Bs],
@@ -151,6 +155,11 @@ t_andalso(Config) when is_list(Config) ->
?line false = false andalso true,
?line false = false andalso false,
+ ?line true = ?GUARD(true andalso true),
+ ?line false = ?GUARD(true andalso false),
+ ?line false = ?GUARD(false andalso true),
+ ?line false = ?GUARD(false andalso false),
+
?line false = false andalso glurf,
?line false = false andalso exit(exit_now),
@@ -176,6 +185,11 @@ t_orelse(Config) when is_list(Config) ->
?line true = false orelse true,
?line false = false orelse false,
+ ?line true = ?GUARD(true orelse true),
+ ?line true = ?GUARD(true orelse false),
+ ?line true = ?GUARD(false orelse true),
+ ?line false = ?GUARD(false orelse false),
+
?line true = true orelse glurf,
?line true = true orelse exit(exit_now),
diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl
index 75b6f801e7..caaa587006 100644
--- a/lib/compiler/test/bs_match_SUITE.erl
+++ b/lib/compiler/test/bs_match_SUITE.erl
@@ -30,7 +30,8 @@
multiple_uses/1,zero_label/1,followed_by_catch/1,
matching_meets_construction/1,simon/1,matching_and_andalso/1,
otp_7188/1,otp_7233/1,otp_7240/1,otp_7498/1,
- match_string/1,zero_width/1,bad_size/1,haystack/1]).
+ match_string/1,zero_width/1,bad_size/1,haystack/1,
+ cover_beam_bool/1]).
-export([coverage_id/1]).
@@ -45,7 +46,7 @@ all(suite) ->
wfbm,degenerated_match,bs_sum,coverage,multiple_uses,zero_label,
followed_by_catch,matching_meets_construction,simon,matching_and_andalso,
otp_7188,otp_7233,otp_7240,otp_7498,match_string,zero_width,bad_size,
- haystack].
+ haystack,cover_beam_bool].
init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
Dog = test_server:timetrap(?t:minutes(1)),
@@ -985,6 +986,25 @@ fc(_, Args, {'EXIT',{{case_clause,ActualArgs},_}})
when ?MODULE =:= bs_match_inline_SUITE ->
Args = tuple_to_list(ActualArgs).
+%% Cover the clause handling bs_context to binary in
+%% beam_block:initialized_regs/2.
+cover_beam_bool(Config) when is_list(Config) ->
+ ?line ok = do_cover_beam_bool(<<>>, 3),
+ ?line <<19>> = do_cover_beam_bool(<<19>>, 2),
+ ?line <<42>> = do_cover_beam_bool(<<42>>, 1),
+ ?line <<17>> = do_cover_beam_bool(<<13,17>>, 0),
+ ok.
+
+do_cover_beam_bool(Bin, X) when X > 0 ->
+ if
+ X =:= 1; X =:= 2 ->
+ Bin;
+ true ->
+ ok
+ end;
+do_cover_beam_bool(<<_,Bin/binary>>, X) ->
+ do_cover_beam_bool(Bin, X+1).
+
check(F, R) ->
R = F().
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 7c3990a855..e1cc5dafb5 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(compile_SUITE).
@@ -625,7 +625,7 @@ core(Config) when is_list(Config) ->
{raw_abstract_v1,Abstr}}]}} =
beam_lib:chunks(Beam, [abstract_code]),
{Mod,Abstr} end || Beam <- TestBeams],
- ?line Res = p_run(fun(F) -> do_core(F, Outdir) end, Abstr),
+ ?line Res = test_lib:p_run(fun(F) -> do_core(F, Outdir) end, Abstr),
?line test_server:timetrap_cancel(Dog),
Res.
@@ -661,7 +661,7 @@ asm(Config) when is_list(Config) ->
?line Wc = filename:join(filename:dirname(code:which(?MODULE)), "*.beam"),
?line TestBeams = filelib:wildcard(Wc),
- ?line Res = p_run(fun(F) -> do_asm(F, Outdir) end, TestBeams),
+ ?line Res = test_lib:p_run(fun(F) -> do_asm(F, Outdir) end, TestBeams),
?line test_server:timetrap_cancel(Dog),
Res.
@@ -688,35 +688,3 @@ do_asm(Beam, Outdir) ->
[M,Class,Error,erlang:get_stacktrace()]),
error
end.
-
-%% p_run(fun() -> ok|error, List) -> ok
-%% Will fail the test case if there were any errors.
-
-p_run(Test, List) ->
- N = erlang:system_info(schedulers) + 1,
- p_run_loop(Test, List, N, [], 0, 0).
-
-p_run_loop(_, [], _, [], Errors, Ws) ->
- case Errors of
- 0 ->
- case Ws of
- 0 -> ok;
- 1 -> {comment,"1 core_lint failure"};
- N -> {comment,integer_to_list(N)++" core_lint failures"}
- end;
- N -> ?t:fail({N,errors})
- end;
-p_run_loop(Test, [H|T], N, Refs, Errors, Ws) when length(Refs) < N ->
- {_,Ref} = erlang:spawn_monitor(fun() -> exit(Test(H)) end),
- p_run_loop(Test, T, N, [Ref|Refs], Errors, Ws);
-p_run_loop(Test, List, N, Refs0, Errors0, Ws0) ->
- receive
- {'DOWN',Ref,process,_,Res} ->
- {Errors,Ws} = case Res of
- ok -> {Errors0,Ws0};
- error -> {Errors0+1,Ws0};
- warning -> {Errors0,Ws0+1}
- end,
- Refs = Refs0 -- [Ref],
- p_run_loop(Test, List, N, Refs, Errors, Ws)
- end.
diff --git a/lib/compiler/test/compiler.cover b/lib/compiler/test/compiler.cover
index 5ec2408a35..69d284ea6c 100644
--- a/lib/compiler/test/compiler.cover
+++ b/lib/compiler/test/compiler.cover
@@ -1,3 +1,3 @@
%% -*- erlang -*-
-{exclude,[sys_pre_attributes,core_parse]}.
+{exclude,[sys_pre_attributes,core_scan,core_parse]}.
diff --git a/lib/compiler/test/core_SUITE_data/.gitignore b/lib/compiler/test/core_SUITE_data/.gitignore
new file mode 100644
index 0000000000..d11d93d37f
--- /dev/null
+++ b/lib/compiler/test/core_SUITE_data/.gitignore
@@ -0,0 +1 @@
+!*.core
diff --git a/lib/compiler/test/error_SUITE.erl b/lib/compiler/test/error_SUITE.erl
index 4530313bb0..ec58a0761e 100644
--- a/lib/compiler/test/error_SUITE.erl
+++ b/lib/compiler/test/error_SUITE.erl
@@ -21,11 +21,133 @@
-include("test_server.hrl").
-export([all/1,
- head_mismatch_line/1,warnings_as_errors/1]).
+ head_mismatch_line/1,warnings_as_errors/1, bif_clashes/1]).
all(suite) ->
test_lib:recompile(?MODULE),
- [head_mismatch_line,warnings_as_errors].
+ [head_mismatch_line,warnings_as_errors,bif_clashes].
+
+
+bif_clashes(Config) when is_list(Config) ->
+ Ts = [{bif_clashes1,
+ <<"
+ -export([t/0]).
+ t() ->
+ length([a,b,c]).
+
+ length(X) ->
+ erlang:length(X).
+ ">>,
+ [return_warnings],
+ {error,
+ [{4, erl_lint,{call_to_redefined_old_bif,{length,1}}}], []} }],
+ ?line [] = run(Config, Ts),
+ Ts1 = [{bif_clashes2,
+ <<"
+ -export([t/0]).
+ -import(x,[length/1]).
+ t() ->
+ length([a,b,c]).
+ ">>,
+ [return_warnings],
+ {error,
+ [{3, erl_lint,{redefine_old_bif_import,{length,1}}}], []} }],
+ ?line [] = run(Config, Ts1),
+ Ts00 = [{bif_clashes3,
+ <<"
+ -export([t/0]).
+ -compile({no_auto_import,[length/1]}).
+ t() ->
+ length([a,b,c]).
+
+ length(X) ->
+ erlang:length(X).
+ ">>,
+ [return_warnings],
+ []}],
+ ?line [] = run(Config, Ts00),
+ Ts11 = [{bif_clashes4,
+ <<"
+ -export([t/0]).
+ -compile({no_auto_import,[length/1]}).
+ -import(x,[length/1]).
+ t() ->
+ length([a,b,c]).
+ ">>,
+ [return_warnings],
+ []}],
+ ?line [] = run(Config, Ts11),
+ Ts000 = [{bif_clashes5,
+ <<"
+ -export([t/0]).
+ t() ->
+ binary_part(<<1,2,3,4>>,1,2).
+
+ binary_part(X,Y,Z) ->
+ erlang:binary_part(X,Y,Z).
+ ">>,
+ [return_warnings],
+ {warning,
+ [{4, erl_lint,{call_to_redefined_bif,{binary_part,3}}}]} }],
+ ?line [] = run(Config, Ts000),
+ Ts111 = [{bif_clashes6,
+ <<"
+ -export([t/0]).
+ -import(x,[binary_part/3]).
+ t() ->
+ binary_part(<<1,2,3,4>>,1,2).
+ ">>,
+ [return_warnings],
+ {warning,
+ [{3, erl_lint,{redefine_bif_import,{binary_part,3}}}]} }],
+ ?line [] = run(Config, Ts111),
+ Ts2 = [{bif_clashes7,
+ <<"
+ -export([t/0]).
+ -compile({no_auto_import,[length/1]}).
+ -import(x,[length/1]).
+ t() ->
+ length([a,b,c]).
+ length(X) ->
+ erlang:length(X).
+ ">>,
+ [],
+ {error,
+ [{7,erl_lint,{define_import,{length,1}}}],
+ []} }],
+ ?line [] = run2(Config, Ts2),
+ Ts3 = [{bif_clashes8,
+ <<"
+ -export([t/1]).
+ -compile({no_auto_import,[length/1]}).
+ t(X) when length(X) > 3 ->
+ length([a,b,c]).
+ length(X) ->
+ erlang:length(X).
+ ">>,
+ [],
+ {error,
+ [{4,erl_lint,{illegal_guard_local_call,{length,1}}}],
+ []} }],
+ ?line [] = run2(Config, Ts3),
+ Ts4 = [{bif_clashes9,
+ <<"
+ -export([t/1]).
+ -compile({no_auto_import,[length/1]}).
+ -import(x,[length/1]).
+ t(X) when length(X) > 3 ->
+ length([a,b,c]).
+ ">>,
+ [],
+ {error,
+ [{5,erl_lint,{illegal_guard_local_call,{length,1}}}],
+ []} }],
+ ?line [] = run2(Config, Ts4),
+
+ ok.
+
+
+
%% Tests that a head mismatch is reported on the correct line (OTP-2125).
head_mismatch_line(Config) when is_list(Config) ->
@@ -49,7 +171,7 @@ warnings_as_errors(Config) when is_list(Config) ->
A = unused,
ok.
">>,
- [warnings_as_errors],
+ [export_all,warnings_as_errors],
{error,
[],
[{3,erl_lint,{unused_var,'A'}}]} }],
@@ -70,6 +192,24 @@ run(Config, Tests) ->
end,
lists:foldl(F, [], Tests).
+run2(Config, Tests) ->
+ F = fun({N,P,Ws,E}, BadL) ->
+ case catch filter(run_test(Config, P, Ws)) of
+ E ->
+ BadL;
+ Bad ->
+ ?t:format("~nTest ~p failed. Expected~n ~p~n"
+ "but got~n ~p~n", [N, E, Bad]),
+ fail()
+ end
+ end,
+ lists:foldl(F, [], Tests).
+
+filter({error,Es,_Ws}) ->
+ {error,Es,[]};
+filter(X) ->
+ X.
+
%% Compiles a test module and returns the list of errors and warnings.
@@ -78,17 +218,29 @@ run_test(Conf, Test0, Warnings) ->
?line DataDir = ?config(priv_dir, Conf),
?line Test = ["-module(errors_test). ", Test0],
?line File = filename:join(DataDir, Filename),
- ?line Opts = [binary,export_all,return|Warnings],
+ ?line Opts = [binary,return_errors|Warnings],
?line ok = file:write_file(File, Test),
%% Compile once just to print all errors and warnings.
- ?line compile:file(File, [binary,export_all,report|Warnings]),
+ ?line compile:file(File, [binary,report|Warnings]),
%% Test result of compilation.
?line Res = case compile:file(File, Opts) of
- {error,[{_File,Es}],Ws} ->
+ {ok,errors_test,_,[{_File,Ws}]} ->
+ %io:format("compile:file(~s,~p) ->~n~p~n",
+ % [File,Opts,Ws]),
+ {warning,Ws};
+ {ok,errors_test,_,[]} ->
+ %io:format("compile:file(~s,~p) ->~n~p~n",
+ % [File,Opts,Ws]),
+ [];
+ {error,[{XFile,Es}],Ws} = _ZZ when is_list(XFile) ->
+ %io:format("compile:file(~s,~p) ->~n~p~n",
+ % [File,Opts,_ZZ]),
{error,Es,Ws};
- {error,Es,[{_File,Ws}]} ->
+ {error,Es,[{_File,Ws}]} = _ZZ->
+ %io:format("compile:file(~s,~p) ->~n~p~n",
+ % [File,Opts,_ZZ]),
{error,Es,Ws}
end,
file:delete(File),
diff --git a/lib/compiler/test/float_SUITE.erl b/lib/compiler/test/float_SUITE.erl
index 3d2dbf47e9..b48b1daa32 100644
--- a/lib/compiler/test/float_SUITE.erl
+++ b/lib/compiler/test/float_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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(float_SUITE).
@@ -41,7 +41,7 @@ float_sub(A)->
float_mul(0, _, _)->
ok;
float_mul(Iter, A, B) when is_float(A), is_float(B) ->
- A*B,
+ _ = A*B,
float_mul(Iter-1, A, B).
%% Thanks to Mikael Pettersson and Tobias Lindahl (HiPE).
@@ -82,6 +82,14 @@ bad_negate(X, Y) when is_float(X) ->
Y1 = -Y, %BIF call.
{X2, Y1}.
+%% Some math functions are not implemented on all platforms.
+-define(OPTIONAL(Expected, Expr),
+ try
+ Expected = Expr
+ catch
+ error:undef -> ok
+ end).
+
math_functions(Config) when is_list(Config) ->
%% Mostly silly coverage.
?line 0.0 = math:tan(0),
@@ -93,6 +101,14 @@ math_functions(Config) when is_list(Config) ->
?line -1.0 = math:cos(math:pi()),
?line 1.0 = math:exp(0),
?line 1.0 = math:pow(math:pi(), 0),
+ ?line 0.0 = math:log(1),
+ ?line 0.0 = math:asin(0),
+ ?line 0.0 = math:acos(1),
+ ?line ?OPTIONAL(0.0, math:asinh(0)),
+ ?line ?OPTIONAL(0.0, math:acosh(1)),
+ ?line ?OPTIONAL(0.0, math:atanh(0)),
+ ?line ?OPTIONAL(0.0, math:erf(0)),
+ ?line ?OPTIONAL(1.0, math:erfc(0)),
?line 0.0 = math:tan(id(0)),
?line 0.0 = math:atan2(id(0), 1),
@@ -101,6 +117,14 @@ math_functions(Config) when is_list(Config) ->
?line 0.0 = math:tanh(id(0)),
?line 1.0 = math:log10(id(10)),
?line 1.0 = math:exp(id(0)),
+ ?line 0.0 = math:log(id(1)),
+ ?line 0.0 = math:asin(id(0)),
+ ?line 0.0 = math:acos(id(1)),
+ ?line ?OPTIONAL(0.0, math:asinh(id(0))),
+ ?line ?OPTIONAL(0.0, math:acosh(id(1))),
+ ?line ?OPTIONAL(0.0, math:atanh(id(0))),
+ ?line ?OPTIONAL(0.0, math:erf(id(0))),
+ ?line ?OPTIONAL(1.0, math:erfc(id(0))),
%% Only for coverage (of beam_type.erl).
?line {'EXIT',{undef,_}} = (catch math:fnurfla(0)),
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index f3960b28c3..8f23bd2e5a 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -31,7 +31,7 @@
t_is_boolean/1,is_function_2/1,
tricky/1,rel_ops/1,literal_type_tests/1,
basic_andalso_orelse/1,traverse_dcd/1,
- check_qlc_hrl/1,andalso_semi/1,tuple_size/1]).
+ check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1]).
all(suite) ->
test_lib:recompile(?MODULE),
@@ -43,7 +43,7 @@ all(suite) ->
build_in_guard,old_guard_tests,gbif,
t_is_boolean,is_function_2,tricky,rel_ops,literal_type_tests,
basic_andalso_orelse,traverse_dcd,check_qlc_hrl,andalso_semi,
- tuple_size].
+ t_tuple_size,binary_part].
misc(Config) when is_list(Config) ->
?line 42 = case id(42) of
@@ -1330,7 +1330,7 @@ andalso_semi_bar(Bar) when is_list(Bar) andalso length(Bar) =:= 3; Bar =:= 1 ->
ok.
-tuple_size(Config) when is_list(Config) ->
+t_tuple_size(Config) when is_list(Config) ->
?line 10 = do_tuple_size({1,2,3,4}),
?line fc(catch do_tuple_size({1,2,3})),
?line fc(catch do_tuple_size(42)),
@@ -1362,6 +1362,146 @@ ludicrous_tuple_size(T)
when tuple_size(T) =:= 16#FFFFFFFFFFFFFFFF -> ok;
ludicrous_tuple_size(_) -> error.
+%%
+%% The binary_part/2,3 guard BIFs
+%%
+-define(MASK_ERROR(EXPR),mask_error((catch (EXPR)))).
+mask_error({'EXIT',{Err,_}}) ->
+ Err;
+mask_error(Else) ->
+ Else.
+
+binary_part(doc) ->
+ ["Tests the binary_part/2,3 guard (GC) bif's"];
+binary_part(Config) when is_list(Config) ->
+ %% This is more or less a copy of what the guard_SUITE in emulator
+ %% does to cover the guard bif's
+ ?line 1 = bptest(<<1,2,3>>),
+ ?line 2 = bptest(<<2,1,3>>),
+ ?line error = bptest(<<1>>),
+ ?line error = bptest(<<>>),
+ ?line error = bptest(apa),
+ ?line 3 = bptest(<<2,3,3>>),
+ % With one variable (pos)
+ ?line 1 = bptest(<<1,2,3>>,1),
+ ?line 2 = bptest(<<2,1,3>>,1),
+ ?line error = bptest(<<1>>,1),
+ ?line error = bptest(<<>>,1),
+ ?line error = bptest(apa,1),
+ ?line 3 = bptest(<<2,3,3>>,1),
+ % With one variable (length)
+ ?line 1 = bptesty(<<1,2,3>>,1),
+ ?line 2 = bptesty(<<2,1,3>>,1),
+ ?line error = bptesty(<<1>>,1),
+ ?line error = bptesty(<<>>,1),
+ ?line error = bptesty(apa,1),
+ ?line 3 = bptesty(<<2,3,3>>,2),
+ % With one variable (whole tuple)
+ ?line 1 = bptestx(<<1,2,3>>,{1,1}),
+ ?line 2 = bptestx(<<2,1,3>>,{1,1}),
+ ?line error = bptestx(<<1>>,{1,1}),
+ ?line error = bptestx(<<>>,{1,1}),
+ ?line error = bptestx(apa,{1,1}),
+ ?line 3 = bptestx(<<2,3,3>>,{1,2}),
+ % With two variables
+ ?line 1 = bptest(<<1,2,3>>,1,1),
+ ?line 2 = bptest(<<2,1,3>>,1,1),
+ ?line error = bptest(<<1>>,1,1),
+ ?line error = bptest(<<>>,1,1),
+ ?line error = bptest(apa,1,1),
+ ?line 3 = bptest(<<2,3,3>>,1,2),
+ % Direct (autoimported) call, these will be evaluated by the compiler...
+ ?line <<2>> = binary_part(<<1,2,3>>,1,1),
+ ?line <<1>> = binary_part(<<2,1,3>>,1,1),
+ % Compiler warnings due to constant evaluation expected (3)
+ ?line badarg = ?MASK_ERROR(binary_part(<<1>>,1,1)),
+ ?line badarg = ?MASK_ERROR(binary_part(<<>>,1,1)),
+ ?line badarg = ?MASK_ERROR(binary_part(apa,1,1)),
+ ?line <<3,3>> = binary_part(<<2,3,3>>,1,2),
+ % Direct call through apply
+ ?line <<2>> = apply(erlang,binary_part,[<<1,2,3>>,1,1]),
+ ?line <<1>> = apply(erlang,binary_part,[<<2,1,3>>,1,1]),
+ % Compiler warnings due to constant evaluation expected (3)
+ ?line badarg = ?MASK_ERROR(apply(erlang,binary_part,[<<1>>,1,1])),
+ ?line badarg = ?MASK_ERROR(apply(erlang,binary_part,[<<>>,1,1])),
+ ?line badarg = ?MASK_ERROR(apply(erlang,binary_part,[apa,1,1])),
+ ?line <<3,3>> = apply(erlang,binary_part,[<<2,3,3>>,1,2]),
+ % Constant propagation
+ ?line Bin = <<1,2,3>>,
+ ?line ok = if
+ binary_part(Bin,1,1) =:= <<2>> ->
+ ok;
+ %% Compiler warning, clause cannot match (expected)
+ true ->
+ error
+ end,
+ ?line ok = if
+ binary_part(Bin,{1,1}) =:= <<2>> ->
+ ok;
+ %% Compiler warning, clause cannot match (expected)
+ true ->
+ error
+ end,
+ ok.
+
+
+bptest(B) when length(B) =:= 1337 ->
+ 1;
+bptest(B) when binary_part(B,{1,1}) =:= <<2>> ->
+ 1;
+bptest(B) when erlang:binary_part(B,1,1) =:= <<1>> ->
+ 2;
+bptest(B) when erlang:binary_part(B,{1,2}) =:= <<3,3>> ->
+ 3;
+bptest(_) ->
+ error.
+
+bptest(B,A) when length(B) =:= A ->
+ 1;
+bptest(B,A) when binary_part(B,{A,1}) =:= <<2>> ->
+ 1;
+bptest(B,A) when erlang:binary_part(B,A,1) =:= <<1>> ->
+ 2;
+bptest(B,A) when erlang:binary_part(B,{A,2}) =:= <<3,3>> ->
+ 3;
+bptest(_,_) ->
+ error.
+
+bptestx(B,A) when length(B) =:= A ->
+ 1;
+bptestx(B,A) when binary_part(B,A) =:= <<2>> ->
+ 1;
+bptestx(B,A) when erlang:binary_part(B,A) =:= <<1>> ->
+ 2;
+bptestx(B,A) when erlang:binary_part(B,A) =:= <<3,3>> ->
+ 3;
+bptestx(_,_) ->
+ error.
+
+bptesty(B,A) when length(B) =:= A ->
+ 1;
+bptesty(B,A) when binary_part(B,{1,A}) =:= <<2>> ->
+ 1;
+bptesty(B,A) when erlang:binary_part(B,1,A) =:= <<1>> ->
+ 2;
+bptesty(B,A) when erlang:binary_part(B,{1,A}) =:= <<3,3>> ->
+ 3;
+bptesty(_,_) ->
+ error.
+
+bptest(B,A,_C) when length(B) =:= A ->
+ 1;
+bptest(B,A,C) when binary_part(B,{A,C}) =:= <<2>> ->
+ 1;
+bptest(B,A,C) when erlang:binary_part(B,A,C) =:= <<1>> ->
+ 2;
+bptest(B,A,C) when erlang:binary_part(B,{A,C}) =:= <<3,3>> ->
+ 3;
+bptest(_,_,_) ->
+ error.
+
+
+
%% Call this function to turn off constant propagation.
id(I) -> I.
diff --git a/lib/compiler/test/inline_SUITE_data/decode1.erl b/lib/compiler/test/inline_SUITE_data/decode1.erl
index d51bedcb2e..9b4fc071a3 100644
--- a/lib/compiler/test/inline_SUITE_data/decode1.erl
+++ b/lib/compiler/test/inline_SUITE_data/decode1.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-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%
%%
%----------------------------------------------------------------------
@@ -37,25 +37,25 @@
FrameList = [89,128,0,8,132,0,26,133,133,0,38,148,94,
128,0,2,129,128,92,128,0,2,0,0,112,128,0,
10,194,69,0,0,0,0,0,18,52,95],
- Frame = concat_binary([list_to_binary([89]),list_to_binary([128]),
- list_to_binary([0]),list_to_binary([8]),
- list_to_binary([132]),list_to_binary([0]),
- list_to_binary([26]),list_to_binary([133]),
- list_to_binary([133]),list_to_binary([0]),
- list_to_binary([38]),list_to_binary([148]),
- list_to_binary([94]),list_to_binary([128]),
- list_to_binary([0]),list_to_binary([2]),
- list_to_binary([129]),list_to_binary([128]),
- list_to_binary([92]),list_to_binary([128]),
- list_to_binary([0]),list_to_binary([2]),
- list_to_binary([0]),list_to_binary([0]),
- list_to_binary([112]),list_to_binary([128]),
- list_to_binary([0]),list_to_binary([10]),
- list_to_binary([194]),list_to_binary([69]),
- list_to_binary([0]),list_to_binary([0]),
- list_to_binary([0]),list_to_binary([0]),
- list_to_binary([0]),list_to_binary([18]),
- list_to_binary([52]),list_to_binary([95])]),
+ Frame = list_to_binary([list_to_binary([89]),list_to_binary([128]),
+ list_to_binary([0]),list_to_binary([8]),
+ list_to_binary([132]),list_to_binary([0]),
+ list_to_binary([26]),list_to_binary([133]),
+ list_to_binary([133]),list_to_binary([0]),
+ list_to_binary([38]),list_to_binary([148]),
+ list_to_binary([94]),list_to_binary([128]),
+ list_to_binary([0]),list_to_binary([2]),
+ list_to_binary([129]),list_to_binary([128]),
+ list_to_binary([92]),list_to_binary([128]),
+ list_to_binary([0]),list_to_binary([2]),
+ list_to_binary([0]),list_to_binary([0]),
+ list_to_binary([112]),list_to_binary([128]),
+ list_to_binary([0]),list_to_binary([10]),
+ list_to_binary([194]),list_to_binary([69]),
+ list_to_binary([0]),list_to_binary([0]),
+ list_to_binary([0]),list_to_binary([0]),
+ list_to_binary([0]),list_to_binary([18]),
+ list_to_binary([52]),list_to_binary([95])]),
R = loop(2,0,Frame),
{R,R =:= {0,[{ie,112,itu_t_standard,ignore,10,<<194,69,0,0,0,0,0,18,52,95>>},
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index 20969c0b26..fd51b777ac 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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(match_SUITE).
@@ -21,14 +21,14 @@
-export([all/1,
pmatch/1,mixed/1,aliases/1,match_in_call/1,
untuplify/1,shortcut_boolean/1,letify_guard/1,
- selectify/1]).
+ selectify/1,underscore/1]).
-include("test_server.hrl").
all(suite) ->
test_lib:recompile(?MODULE),
[pmatch,mixed,aliases,match_in_call,untuplify,shortcut_boolean,
- letify_guard,selectify].
+ letify_guard,selectify,underscore].
pmatch(Config) when is_list(Config) ->
?line ok = doit(1),
@@ -112,6 +112,12 @@ aliases(Config) when is_list(Config) ->
?line {42,42,42,42} = multiple_aliases_1(42),
?line {7,7,7} = multiple_aliases_2(7),
?line {{a,b},{a,b},{a,b}} = multiple_aliases_3({a,b}),
+
+ %% Lists/literals.
+ ?line {a,b} = list_alias1([a,b]),
+ ?line {a,b} = list_alias2([a,b]),
+ ?line {a,b} = list_alias3([a,b]),
+
ok.
str_alias(V) ->
@@ -206,6 +212,15 @@ multiple_aliases_2((A=B)=(A=C)) ->
multiple_aliases_3((A={_,_}=B)={_,_}=C) ->
{A,B,C}.
+list_alias1([a,b]=[X,Y]) ->
+ {X,Y}.
+
+list_alias2([X,Y]=[a,b]) ->
+ {X,Y}.
+
+list_alias3([X,b]=[a,Y]) ->
+ {X,Y}.
+
%% OTP-7018.
match_in_call(Config) when is_list(Config) ->
@@ -352,4 +367,16 @@ sel_same_value2(V) when V =:= 42; V =:= 43 ->
sel_same_value2(_) ->
error.
+underscore(Config) when is_list(Config) ->
+ case Config of
+ [] ->
+ %% Assignment to _ at the end of a construct.
+ _ = length(Config);
+ [_|_] ->
+ %% Assignment to _ at the end of a construct.
+ _ = list_to_tuple(Config)
+ end,
+ _ = is_list(Config),
+ ok.
+
id(I) -> I.
diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl
index e096571d50..450a4e279d 100644
--- a/lib/compiler/test/misc_SUITE.erl
+++ b/lib/compiler/test/misc_SUITE.erl
@@ -1,29 +1,46 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-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(misc_SUITE).
-export([all/1,init_per_testcase/2,fin_per_testcase/2,
tobias/1,empty_string/1,md5/1,silly_coverage/1,
- confused_literals/1,integer_encoding/1]).
+ confused_literals/1,integer_encoding/1,override_bif/1]).
-include("test_server.hrl").
+%% For the override_bif testcase.
+%% NB, no other testcases in this testsuite can use these without erlang:prefix!
+-compile({no_auto_import,[abs/1]}).
+-compile({no_auto_import,[binary_part/3]}).
+-compile({no_auto_import,[binary_part/2]}).
+-import(test_lib,[binary_part/2]).
+
+%% This should do no harm (except for fun byte_size/1 which does not, by design, work with import
+-compile({no_auto_import,[byte_size/1]}).
+-import(erlang,[byte_size/1]).
+
+
+
+%% Include an opaque declaration to cover the stripping of
+%% opaque types from attributes in v3_kernel.
+-opaque misc_SUITE_test_cases() :: [atom()].
+
init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
Dog = test_server:timetrap(?t:minutes(10)),
[{watchdog,Dog}|Config].
@@ -33,10 +50,44 @@ fin_per_testcase(Case, Config) when is_atom(Case), is_list(Config) ->
?t:timetrap_cancel(Dog),
ok.
+-spec all(any()) -> misc_SUITE_test_cases().
+
all(suite) ->
test_lib:recompile(?MODULE),
[tobias,empty_string,md5,silly_coverage,confused_literals,
- integer_encoding].
+ integer_encoding, override_bif].
+
+
+%%
+%% Functions that override new and old bif's
+%%
+abs(_N) ->
+ dummy_abs.
+
+binary_part(_,_,_) ->
+ dummy_bp.
+
+% Make sure that auto-imported BIF's are overridden correctly
+
+override_bif(suite) ->
+ [];
+override_bif(doc) ->
+ ["Test dat local functions and imports override auto-imported BIFs."];
+override_bif(Config) when is_list(Config) ->
+ ?line dummy_abs = abs(1),
+ ?line dummy_bp = binary_part(<<"hello">>,1,1),
+ ?line dummy = binary_part(<<"hello">>,{1,1}),
+ ?line 1 = erlang:abs(1),
+ ?line <<"e">> = erlang:binary_part(<<"hello">>,1,1),
+ ?line <<"e">> = erlang:binary_part(<<"hello">>,{1,1}),
+ F = fun(X) when byte_size(X) =:= 4 ->
+ four;
+ (X) ->
+ byte_size(X)
+ end,
+ ?line four = F(<<1,2,3,4>>),
+ ?line 5 = F(<<1,2,3,4,5>>),
+ ok.
%% A bug reported by Tobias Lindahl for a development version of R11B.
@@ -92,13 +143,23 @@ md5_1(Beam) ->
silly_coverage(Config) when is_list(Config) ->
%% sys_core_fold, sys_core_setel, v3_kernel
BadCoreErlang = {c_module,[],
- name,exports,attrs,
+ name,[],[],
[{{c_var,[],{foo,2}},seriously_bad_body}]},
?line expect_error(fun() -> sys_core_fold:module(BadCoreErlang, []) end),
?line expect_error(fun() -> sys_core_dsetel:module(BadCoreErlang, []) end),
?line expect_error(fun() -> v3_kernel:module(BadCoreErlang, []) end),
- %% v3_codgen
+ %% v3_life
+ BadKernel = {k_mdef,[],?MODULE,
+ [{foo,0}],
+ [],
+ [{k_fdef,
+ {k,[],[],[]},
+ f,0,[],
+ seriously_bad_body}]},
+ ?line expect_error(fun() -> v3_life:module(BadKernel, []) end),
+
+ %% v3_codegen
CodegenInput = {?MODULE,[{foo,0}],[],[{function,foo,0,[a|b],a,b}]},
?line expect_error(fun() -> v3_codegen:module(CodegenInput, []) end),
@@ -154,6 +215,17 @@ silly_coverage(Config) when is_list(Config) ->
{test,bs_get_binary2,{f,99},0,[{x,0},{atom,all},1,[]],{x,0}},
{block,[a|b]}]}],0},
?line expect_error(fun() -> beam_bsm:module(BsmInput, []) end),
+
+ %% beam_receive.
+ ReceiveInput = {?MODULE,[{foo,0}],[],
+ [{function,foo,0,2,
+ [{label,1},
+ {func_info,{atom,?MODULE},{atom,foo},0},
+ {label,2},
+ {call_ext,0,{extfunc,erlang,make_ref,0}},
+ {block,[a|b]}]}],0},
+ ?line expect_error(fun() -> beam_receive:module(ReceiveInput, []) end),
+
ok.
expect_error(Fun) ->
diff --git a/lib/compiler/test/num_bif_SUITE.erl b/lib/compiler/test/num_bif_SUITE.erl
index c246f56611..912f7366dd 100644
--- a/lib/compiler/test/num_bif_SUITE.erl
+++ b/lib/compiler/test/num_bif_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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(num_bif_SUITE).
@@ -166,7 +166,7 @@ t_list_to_float_safe(Config) when is_list(Config) ->
t_list_to_float_risky(Config) when is_list(Config) ->
?line Many_Ones = lists:duplicate(25000, $1),
- ?line list_to_float("2."++Many_Ones),
+ ?line _ = list_to_float("2."++Many_Ones),
?line {'EXIT', {badarg, _}} = (catch list_to_float("2"++Many_Ones)),
ok.
@@ -186,7 +186,7 @@ t_list_to_integer(Config) when is_list(Config) ->
%% Bignums.
?line 123456932798748738738 = list_to_integer("123456932798748738738"),
- ?line list_to_integer(lists:duplicate(2000, $1)),
+ ?line _ = list_to_integer(lists:duplicate(2000, $1)),
ok.
%% Tests round/1.
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index cb8833759a..fca3f0387b 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
%%% Purpose : Compiles various modules with tough code
@@ -21,7 +21,7 @@
-module(receive_SUITE).
-export([all/1,init_per_testcase/2,fin_per_testcase/2,
- recv/1,coverage/1,otp_7980/1]).
+ recv/1,coverage/1,otp_7980/1,ref_opt/1]).
-include("test_server.hrl").
@@ -36,7 +36,7 @@ fin_per_testcase(_Case, Config) ->
all(suite) ->
test_lib:recompile(?MODULE),
- [recv,coverage,otp_7980].
+ [recv,coverage,otp_7980,ref_opt].
-record(state, {ena = true}).
@@ -157,5 +157,52 @@ otp_7980_add_clients(Count) ->
end,
N - 1
end, Count, [1,2,3]).
-
+
+ref_opt(Config) when is_list(Config) ->
+ case ?MODULE of
+ receive_SUITE -> ref_opt_1(Config);
+ _ -> {skip,"Enough to run this case once."}
+ end.
+
+ref_opt_1(Config) ->
+ ?line DataDir = ?config(data_dir, Config),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Sources = filelib:wildcard(filename:join([DataDir,"ref_opt","*.erl"])),
+ ?line test_lib:p_run(fun(Src) ->
+ do_ref_opt(Src, PrivDir)
+ end, Sources),
+ ok.
+
+do_ref_opt(Source, PrivDir) ->
+ try
+ {ok,Mod} = c:c(Source, [{outdir,PrivDir}]),
+ ok = Mod:Mod(),
+ Base = filename:rootname(filename:basename(Source), ".erl"),
+ BeamFile = filename:join(PrivDir, Base),
+ {beam_file,Mod,_,_,_,Code} = beam_disasm:file(BeamFile),
+ case Base of
+ "no_"++_ ->
+ [] = collect_recv_opt_instrs(Code);
+ "yes_"++_ ->
+ [{recv_mark,{f,L}},{recv_set,{f,L}}] =
+ collect_recv_opt_instrs(Code)
+ end,
+ ok
+ catch Class:Error ->
+ io:format("~s: ~p ~p\n~p\n",
+ [Source,Class,Error,erlang:get_stacktrace()]),
+ error
+ end.
+
+collect_recv_opt_instrs(Code) ->
+ L = [ [I || I <- Is,
+ begin
+ case I of
+ {recv_mark,{f,_}} -> true;
+ {recv_set,{f,_}} -> true;
+ _ -> false
+ end
+ end] || {function,_,_,_,Is} <- Code],
+ lists:append(L).
+
id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/no_1.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/no_1.erl
new file mode 100644
index 0000000000..bc63dac437
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/no_1.erl
@@ -0,0 +1,99 @@
+-module(no_1).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f1(X) ->
+ Ref = make_ref(),
+ receive
+ _ when [X] =:= Ref ->
+ ok
+ end.
+
+f2(X, Y) ->
+ _Ref = make_ref(),
+ receive
+ _ when X =:= Y ->
+ ok
+ end.
+
+f3(X) ->
+ Ref = make_ref(),
+ receive
+ _ when X =:= Ref ->
+ ok
+ end.
+
+f4(X) ->
+ Ref = make_ref(),
+ receive
+ {X,_} when not X =:= Ref ->
+ ok
+ end.
+
+f5(X) ->
+ Ref = make_ref(),
+ receive
+ {Y,_} when X =:= Y; Y =:= Ref ->
+ ok
+ end.
+
+f6(X) ->
+ Ref = make_ref(),
+ receive
+ {Y,_} when Y =:= Ref; Ref =:= X ->
+ ok
+ end.
+
+f7(X) ->
+ Ref = make_ref(),
+ receive
+ {Y,_} when Y =:= Ref; not (X =:= Ref) ->
+ ok
+ end.
+
+f8(X) ->
+ Ref = make_ref(),
+ receive
+ {Y,_} when not (X =:= Ref); Y =:= Ref ->
+ ok
+ end.
+
+f9(X) ->
+ Ref = make_ref(),
+ receive
+ {Y,_} when (not (X =:= Ref)) or (Y =:= Ref) ->
+ ok
+ end.
+
+f10(X, Y) ->
+ Ref = make_ref(),
+ receive
+ {Z,_} when not (X =:= Y andalso Z =:= Ref) ->
+ ok
+ end.
+
+f11(X, Y) ->
+ Ref = make_ref(),
+ receive
+ {Z,_} when not ((X =:= Y) and (Z =:= Ref)) ->
+ ok
+ end.
+
+f12(X, Y) ->
+ Ref = make_ref(),
+ receive
+ {Z,_} when not ((Z =:= Ref) and (X =:= Y)) ->
+ ok
+ end.
+
+f13() ->
+ Ref = make_ref(),
+ RefCopy = id(Ref),
+ receive
+ _ when hd([RefCopy]) =:= Ref ->
+ ok
+ end.
+
+id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/no_2.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/no_2.erl
new file mode 100644
index 0000000000..bc8d30c2ac
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/no_2.erl
@@ -0,0 +1,26 @@
+-module(no_2).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f1() ->
+ Ref = make_ref(),
+ receive
+ {'DOWN',Ref} ->
+ ok;
+ {'DOWN',_} ->
+ ok
+ end.
+
+f2(Pid, Msg) ->
+ Ref = erlang:monitor(process, Pid),
+ Pid ! Msg,
+ receive
+ {ok,Ref,Reply} ->
+ {ok,Reply};
+ {error,Ref,Reply} ->
+ {error,Reply};
+ {error,A,B} ->
+ {error,A,B}
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/no_3.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/no_3.erl
new file mode 100644
index 0000000000..44cf8d7f71
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/no_3.erl
@@ -0,0 +1,14 @@
+-module(no_3).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f(X) ->
+ Ref = case X of
+ false -> ref;
+ true -> make_ref()
+ end,
+ receive
+ Ref -> ok
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_1.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_1.erl
new file mode 100644
index 0000000000..e2ebe234c1
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_1.erl
@@ -0,0 +1,12 @@
+-module(yes_1).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f() ->
+ Ref = make_ref(),
+ receive
+ {Ref,Reply} ->
+ Reply
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_2.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_2.erl
new file mode 100644
index 0000000000..6077cdcab9
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_2.erl
@@ -0,0 +1,13 @@
+-module(yes_2).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f(Pid, Msg) ->
+ Ref = make_ref(),
+ Pid ! Msg,
+ receive
+ {Ref,Reply} ->
+ Reply
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_3.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_3.erl
new file mode 100644
index 0000000000..28efc542ae
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_3.erl
@@ -0,0 +1,16 @@
+-module(yes_3).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f(Pid, Msg) ->
+ Ref = make_ref(),
+ do_send(Pid, Msg),
+ receive
+ {Ref,Reply} ->
+ Reply
+ end.
+
+do_send(Pid, Msg) ->
+ Pid ! Msg.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_4.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_4.erl
new file mode 100644
index 0000000000..d1ba4832c7
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_4.erl
@@ -0,0 +1,16 @@
+-module(yes_4).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f(Pid, Msg) ->
+ Ref = make_ref(),
+ catch do_send(Pid, Msg),
+ receive
+ {Ref,Reply} ->
+ Reply
+ end.
+
+do_send(Pid, Msg) ->
+ Pid ! Msg.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_5.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_5.erl
new file mode 100644
index 0000000000..3f02fba6a6
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_5.erl
@@ -0,0 +1,46 @@
+-module(yes_5).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+do_call(Process, Label, Request, Timeout) ->
+ Node = case Process of
+ {_S, N} when is_atom(N) ->
+ N;
+ _ when is_pid(Process) ->
+ node(Process)
+ end,
+ try erlang:monitor(process, Process) of
+ Mref ->
+ catch erlang:send(Process, {Label, {self(), Mref}, Request},
+ [noconnect]),
+ receive
+ {Mref, Reply} ->
+ erlang:demonitor(Mref, [flush]),
+ {ok, Reply};
+ {'DOWN', Mref, _, _, noconnection} ->
+ exit({nodedown, Node});
+ {'DOWN', Mref, _, _, Reason} ->
+ exit(Reason)
+ after Timeout ->
+ erlang:demonitor(Mref),
+ receive
+ {'DOWN', Mref, _, _, _} -> true
+ after 0 -> true
+ end,
+ exit(timeout)
+ end
+ catch
+ error:_ ->
+ monitor_node(Node, true),
+ receive
+ {nodedown, Node} ->
+ monitor_node(Node, false),
+ exit({nodedown, Node})
+ after 0 ->
+ Tag = make_ref(),
+ Process ! {Label, {self(), Tag}, Request},
+ ?MODULE:wait_resp(Node, Tag, Timeout)
+ end
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_6.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_6.erl
new file mode 100644
index 0000000000..c54b636aa6
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_6.erl
@@ -0,0 +1,15 @@
+-module(yes_6).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f(Pid, Msg) ->
+ Ref = erlang:monitor(process, Pid),
+ Pid ! Msg,
+ receive
+ {ok,Ref,Reply} ->
+ {ok,Reply};
+ {error,Ref,Reply} ->
+ {error,Reply}
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_7.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_7.erl
new file mode 100644
index 0000000000..849eab1746
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_7.erl
@@ -0,0 +1,12 @@
+-module(yes_7).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f(X, Y) ->
+ Ref = make_ref(),
+ receive
+ {Z,_} when X =:= Y, Ref =:= Z ->
+ ok
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_8.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_8.erl
new file mode 100644
index 0000000000..a47fe8cfbf
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_8.erl
@@ -0,0 +1,15 @@
+-module(yes_8).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+%% Cover use of floating point registers.
+
+f(Pid, X) when is_float(X) ->
+ Ref = make_ref(),
+ Pid ! {X+3},
+ receive
+ Ref ->
+ ok
+ end.
diff --git a/lib/compiler/test/receive_SUITE_data/ref_opt/yes_9.erl b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_9.erl
new file mode 100644
index 0000000000..97fce5e734
--- /dev/null
+++ b/lib/compiler/test/receive_SUITE_data/ref_opt/yes_9.erl
@@ -0,0 +1,13 @@
+-module(yes_9).
+-compile(export_all).
+
+?MODULE() ->
+ ok.
+
+f(Fun) ->
+ Ref = make_ref(),
+ Fun(),
+ receive
+ {Ref,Reply} ->
+ Reply
+ end.
diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl
index bd2ffd7f65..f26ff769c7 100644
--- a/lib/compiler/test/record_SUITE.erl
+++ b/lib/compiler/test/record_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
%%% Purpose : Test records.
@@ -24,7 +24,7 @@
-export([all/1,init_per_testcase/2,fin_per_testcase/2,
errors/1,record_test_2/1,record_test_3/1,record_access_in_guards/1,
- guard_opt/1,eval_once/1,foobar/1,missing_test_heap/1]).
+ guard_opt/1,eval_once/1,foobar/1,missing_test_heap/1, nested_access/1]).
init_per_testcase(_Case, Config) ->
?line Dog = test_server:timetrap(test_server:minutes(2)),
@@ -38,7 +38,7 @@ fin_per_testcase(_Case, Config) ->
all(suite) ->
test_lib:recompile(?MODULE),
[errors,record_test_2,record_test_3,record_access_in_guards,
- guard_opt,eval_once,foobar,missing_test_heap].
+ guard_opt,eval_once,foobar,missing_test_heap,nested_access].
-record(foo, {a,b,c,d}).
-record(bar, {a,b,c,d}).
@@ -522,4 +522,29 @@ missing_test_heap_1(A = #foo_rec {foo_1 = _B,
foo_3 = C + 1,
foo_2 = D + 1}.
+-record(nrec0, {name = <<"nested0">>}).
+-record(nrec1, {name = <<"nested1">>, nrec0=#nrec0{}}).
+-record(nrec2, {name = <<"nested2">>, nrec1=#nrec1{}}).
+
+nested_access(Config) when is_list(Config) ->
+ N0 = #nrec0{},
+ N1 = #nrec1{},
+ N2 = #nrec2{},
+ ?line <<"nested0">> = N0#nrec0.name,
+ ?line <<"nested1">> = N1#nrec1.name,
+ ?line <<"nested2">> = N2#nrec2.name,
+ ?line <<"nested0">> = N1#nrec1.nrec0#nrec0.name,
+ ?line <<"nested0">> = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
+ ?line <<"nested1">> = N2#nrec2.nrec1#nrec1.name,
+ ?line <<"nested0">> = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
+
+ N1a = N2#nrec2.nrec1#nrec1{name = <<"nested1a">>},
+ ?line <<"nested1a">> = N1a#nrec1.name,
+
+ N2a = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = <<"nested0a">>},
+ N2b = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = <<"nested0a">>},
+ ?line <<"nested0a">> = N2a#nrec0.name,
+ ?line N2a = N2b,
+ ok.
+
id(I) -> I.
diff --git a/lib/compiler/test/test_lib.erl b/lib/compiler/test/test_lib.erl
index 844bbfc4b9..d8799952a9 100644
--- a/lib/compiler/test/test_lib.erl
+++ b/lib/compiler/test/test_lib.erl
@@ -19,8 +19,8 @@
-module(test_lib).
-include("test_server.hrl").
-
--export([recompile/1,opt_opts/1,get_data_dir/1,smoke_disasm/1]).
+-compile({no_auto_import,[binary_part/2]}).
+-export([recompile/1,opt_opts/1,get_data_dir/1,smoke_disasm/1,p_run/2,binary_part/2]).
recompile(Mod) when is_atom(Mod) ->
case whereis(cover_server) of
@@ -72,3 +72,39 @@ get_data_dir(Config) ->
{ok,Data2,_} = regexp:sub(Data1, "_post_opt_SUITE", "_SUITE"),
{ok,Data,_} = regexp:sub(Data2, "_inline_SUITE", "_SUITE"),
Data.
+
+%% p_run(fun(Data) -> ok|error, List) -> ok
+%% Will fail the test case if there were any errors.
+
+p_run(Test, List) ->
+ N = erlang:system_info(schedulers) + 1,
+ p_run_loop(Test, List, N, [], 0, 0).
+
+p_run_loop(_, [], _, [], Errors, Ws) ->
+ case Errors of
+ 0 ->
+ case Ws of
+ 0 -> ok;
+ 1 -> {comment,"1 warning"};
+ N -> {comment,integer_to_list(N)++" warnings"}
+ end;
+ N -> ?t:fail({N,errors})
+ end;
+p_run_loop(Test, [H|T], N, Refs, Errors, Ws) when length(Refs) < N ->
+ {_,Ref} = erlang:spawn_monitor(fun() -> exit(Test(H)) end),
+ p_run_loop(Test, T, N, [Ref|Refs], Errors, Ws);
+p_run_loop(Test, List, N, Refs0, Errors0, Ws0) ->
+ receive
+ {'DOWN',Ref,process,_,Res} ->
+ {Errors,Ws} = case Res of
+ ok -> {Errors0,Ws0};
+ error -> {Errors0+1,Ws0};
+ warning -> {Errors0,Ws0+1}
+ end,
+ Refs = Refs0 -- [Ref],
+ p_run_loop(Test, List, N, Refs, Errors, Ws)
+ end.
+
+%% This is for the misc_SUITE:override_bif testcase
+binary_part(_A,_B) ->
+ dummy.
diff --git a/lib/compiler/test/warnings_SUITE.erl b/lib/compiler/test/warnings_SUITE.erl
index 6e60ab88cb..5ed8836c70 100644
--- a/lib/compiler/test/warnings_SUITE.erl
+++ b/lib/compiler/test/warnings_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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(warnings_SUITE).
@@ -375,7 +375,12 @@ effect(Config) when is_list(Config) ->
comp_op ->
X =:= 2;
cookie ->
- erlang:get_cookie()
+ erlang:get_cookie();
+ result_ignore ->
+ _ = list_to_integer(X);
+ warn_lc_4 ->
+ %% No warning because of assignment to _.
+ [_ = abs(Z) || Z <- [1,2,3]]
end,
ok.
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index a5e6de7b5f..47feab5fe1 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 4.6.5
+COMPILER_VSN = 4.7
diff --git a/lib/crypto/c_src/Makefile.in b/lib/crypto/c_src/Makefile.in
index 18040a3b26..e728db18eb 100644
--- a/lib/crypto/c_src/Makefile.in
+++ b/lib/crypto/c_src/Makefile.in
@@ -68,13 +68,13 @@ RELSYSDIR = $(RELEASE_PATH)/lib/crypto-$(VSN)
# ----------------------------------------------------
# Misc Macros
# ----------------------------------------------------
-OBJS = $(OBJDIR)/crypto_drv.o
-DRV_MAKEFILE = $(PRIVDIR)/Makefile
+OBJS = $(OBJDIR)/crypto.o
+NIF_MAKEFILE = $(PRIVDIR)/Makefile
ifeq ($(findstring win32,$(TARGET)), win32)
-DYN_DRIVER = $(LIBDIR)/crypto_drv.dll
+NIF_LIB = $(LIBDIR)/crypto.dll
else
-DYN_DRIVER = $(LIBDIR)/crypto_drv.so
+NIF_LIB = $(LIBDIR)/crypto.so
endif
ifeq ($(HOST_OS),)
@@ -94,7 +94,7 @@ endif
# Targets
# ----------------------------------------------------
-debug opt valgrind: $(OBJDIR) $(LIBDIR) $(DYN_DRIVER)
+debug opt valgrind: $(OBJDIR) $(LIBDIR) $(NIF_LIB)
$(OBJDIR):
-@mkdir -p $(OBJDIR)
@@ -106,16 +106,16 @@ $(OBJDIR)/%.o: %.c
$(INSTALL_DIR) $(OBJDIR)
$(CC) -c -o $@ $(ALL_CFLAGS) $<
-$(LIBDIR)/crypto_drv.so: $(OBJS)
+$(LIBDIR)/crypto.so: $(OBJS)
$(INSTALL_DIR) $(LIBDIR)
$(LD) $(LDFLAGS) -o $@ $^ $(LDLIBS) $(CRYPTO_LINK_LIB)
-$(LIBDIR)/crypto_drv.dll: $(OBJS)
+$(LIBDIR)/crypto.dll: $(OBJS)
$(INSTALL_DIR) $(LIBDIR)
$(LD) $(LDFLAGS) -o $@ $(SSL_DED_LD_RUNTIME_LIBRARY_PATH) -L$(SSL_LIBDIR) $(OBJS) -llibeay32
clean:
- rm -f $(DYN_DRIVER) $(OBJS)
+ rm -f $(NIF_LIB) $(OBJS)
rm -f core *~
docs:
@@ -128,9 +128,9 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
$(INSTALL_DIR) $(RELSYSDIR)/priv/obj
$(INSTALL_DIR) $(RELSYSDIR)/priv/lib
- $(INSTALL_DATA) $(DRV_MAKEFILE) $(RELSYSDIR)/priv/obj
+ $(INSTALL_DATA) $(NIF_MAKEFILE) $(RELSYSDIR)/priv/obj
$(INSTALL_PROGRAM) $(OBJS) $(RELSYSDIR)/priv/obj
- $(INSTALL_PROGRAM) $(DYN_DRIVER) $(RELSYSDIR)/priv/lib
+ $(INSTALL_PROGRAM) $(NIF_LIB) $(RELSYSDIR)/priv/lib
release_docs_spec:
diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c
new file mode 100644
index 0000000000..68079f06c7
--- /dev/null
+++ b/lib/crypto/c_src/crypto.c
@@ -0,0 +1,1547 @@
+/*
+ * %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%
+ */
+
+/*
+ * Purpose: Dynamically loadable NIF library for cryptography.
+ * Based on OpenSSL.
+ */
+
+#ifdef __WIN32__
+ #include <windows.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "erl_nif.h"
+
+#define OPENSSL_THREAD_DEFINES
+#include <openssl/opensslconf.h>
+
+#include <openssl/crypto.h>
+#include <openssl/des.h>
+/* #include <openssl/idea.h> This is not supported on the openssl OTP requires */
+#include <openssl/dsa.h>
+#include <openssl/rsa.h>
+#include <openssl/aes.h>
+#include <openssl/md5.h>
+#include <openssl/md4.h>
+#include <openssl/sha.h>
+#include <openssl/bn.h>
+#include <openssl/objects.h>
+#include <openssl/rc4.h>
+#include <openssl/rc2.h>
+#include <openssl/blowfish.h>
+#include <openssl/rand.h>
+
+#ifdef VALGRIND
+ # include <valgrind/memcheck.h>
+
+/* libcrypto mixes supplied buffer contents into its entropy pool,
+ which makes valgrind complain about the use of uninitialized data.
+ We use this valgrind "request" to make sure that no such seemingly
+ undefined data is returned.
+*/
+ # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \
+ VALGRIND_MAKE_MEM_DEFINED(ptr,size)
+
+ # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size) \
+ ((void) ((VALGRIND_CHECK_MEM_IS_DEFINED(ptr,size) == 0) ? 1 : \
+ (fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%d) failed at %s:%d\r\n",\
+ (ptr),(size), __FILE__, __LINE__), abort(), 0)))
+#else
+ # define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size)
+ # define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size)
+#endif
+
+#ifdef DEBUG
+ # define ASSERT(e) \
+ ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\
+ #e, __FILE__, __LINE__), abort(), 0)))
+#else
+ # define ASSERT(e) ((void) 1)
+#endif
+
+#ifdef __GNUC__
+ # define INLINE __inline__
+#elif defined(__WIN32__)
+ # define INLINE __forceinline
+#else
+ # define INLINE
+#endif
+
+
+#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \
+ (((unsigned char*) (s))[1] << 16) | \
+ (((unsigned char*) (s))[2] << 8) | \
+ (((unsigned char*) (s))[3]))
+
+#define put_int32(s,i) \
+{ (s)[0] = (char)(((i) >> 24) & 0xff);\
+ (s)[1] = (char)(((i) >> 16) & 0xff);\
+ (s)[2] = (char)(((i) >> 8) & 0xff);\
+ (s)[3] = (char)((i) & 0xff);\
+}
+
+/* NIF interface declarations */
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
+static int reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info);
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info);
+static void unload(ErlNifEnv* env, void* priv_data);
+
+/* The NIFs: */
+static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md5(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md5_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md5_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md5_final(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM sha(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM sha_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM sha_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM sha_final(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md4(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md4_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md4_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md4_final(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM md5_mac_n(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM sha_mac_n(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM des_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM des_ecb_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM des_ede3_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM aes_cfb_128_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rand_bytes_1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rand_bytes_3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM dss_verify(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rsa_verify(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM aes_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rc4_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rc2_40_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rsa_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM dss_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rsa_public_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM rsa_private_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM dh_generate_parameters_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM dh_check(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM bf_cfb64_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM bf_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM bf_ecb_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+static ERL_NIF_TERM blowfish_ofb64_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]);
+
+
+/* openssl callbacks */
+#ifdef OPENSSL_THREADS
+static void locking_function(int mode, int n, const char *file, int line);
+static unsigned long id_function(void);
+static struct CRYPTO_dynlock_value* dyn_create_function(const char *file,
+ int line);
+static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value* ptr,
+ const char *file, int line);
+static void dyn_destroy_function(struct CRYPTO_dynlock_value *ptr,
+ const char *file, int line);
+#endif /* OPENSSL_THREADS */
+
+/* helpers */
+static void hmac_md5(unsigned char *key, int klen,
+ unsigned char *dbuf, int dlen,
+ unsigned char *hmacbuf);
+static void hmac_sha1(unsigned char *key, int klen,
+ unsigned char *dbuf, int dlen,
+ unsigned char *hmacbuf);
+
+static int library_refc = 0; /* number of users of this dynamic library */
+
+static ErlNifFunc nif_funcs[] = {
+ {"info_lib", 0, info_lib},
+ {"md5", 1, md5},
+ {"md5_init", 0, md5_init},
+ {"md5_update", 2, md5_update},
+ {"md5_final", 1, md5_final},
+ {"sha", 1, sha},
+ {"sha_init", 0, sha_init},
+ {"sha_update", 2, sha_update},
+ {"sha_final", 1, sha_final},
+ {"md4", 1, md4},
+ {"md4_init", 0, md4_init},
+ {"md4_update", 2, md4_update},
+ {"md4_final", 1, md4_final},
+ {"md5_mac_n", 3, md5_mac_n},
+ {"sha_mac_n", 3, sha_mac_n},
+ {"des_cbc_crypt", 4, des_cbc_crypt},
+ {"des_ecb_crypt", 3, des_ecb_crypt},
+ {"des_ede3_cbc_crypt", 6, des_ede3_cbc_crypt},
+ {"aes_cfb_128_crypt", 4, aes_cfb_128_crypt},
+ {"rand_bytes", 1, rand_bytes_1},
+ {"rand_bytes", 3, rand_bytes_3},
+ {"rand_uniform_nif", 2, rand_uniform_nif},
+ {"mod_exp_nif", 3, mod_exp_nif},
+ {"dss_verify", 4, dss_verify},
+ {"rsa_verify", 4, rsa_verify},
+ {"aes_cbc_crypt", 4, aes_cbc_crypt},
+ {"exor", 2, exor},
+ {"rc4_encrypt", 2, rc4_encrypt},
+ {"rc4_set_key", 1, rc4_set_key},
+ {"rc4_encrypt_with_state", 2, rc4_encrypt_with_state},
+ {"rc2_40_cbc_crypt", 4, rc2_40_cbc_crypt},
+ {"rsa_sign_nif", 3, rsa_sign_nif},
+ {"dss_sign_nif", 3, dss_sign_nif},
+ {"rsa_public_crypt", 4, rsa_public_crypt},
+ {"rsa_private_crypt", 4, rsa_private_crypt},
+ {"dh_generate_parameters_nif", 2, dh_generate_parameters_nif},
+ {"dh_check", 1, dh_check},
+ {"dh_generate_key_nif", 2, dh_generate_key_nif},
+ {"dh_compute_key_nif", 3, dh_compute_key_nif},
+ {"bf_cfb64_crypt", 4, bf_cfb64_crypt},
+ {"bf_cbc_crypt", 4, bf_cbc_crypt},
+ {"bf_ecb_crypt", 3, bf_ecb_crypt},
+ {"blowfish_ofb64_encrypt", 3, blowfish_ofb64_encrypt}
+};
+
+ERL_NIF_INIT(crypto,nif_funcs,load,reload,upgrade,unload)
+
+
+#define MD5_CTX_LEN (sizeof(MD5_CTX))
+#define MD5_LEN 16
+#define MD5_LEN_96 12
+#define MD4_CTX_LEN (sizeof(MD4_CTX))
+#define MD4_LEN 16
+#define SHA_CTX_LEN (sizeof(SHA_CTX))
+#define SHA_LEN 20
+#define SHA_LEN_96 12
+#define HMAC_INT_LEN 64
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5c
+
+
+static ErlNifRWLock** lock_vec = NULL; /* Static locks used by openssl */
+static ERL_NIF_TERM atom_true;
+static ERL_NIF_TERM atom_false;
+static ERL_NIF_TERM atom_sha;
+static ERL_NIF_TERM atom_md5;
+static ERL_NIF_TERM atom_error;
+static ERL_NIF_TERM atom_rsa_pkcs1_padding;
+static ERL_NIF_TERM atom_rsa_pkcs1_oaep_padding;
+static ERL_NIF_TERM atom_rsa_no_padding;
+static ERL_NIF_TERM atom_undefined;
+
+static ERL_NIF_TERM atom_ok;
+static ERL_NIF_TERM atom_not_prime;
+static ERL_NIF_TERM atom_not_strong_prime;
+static ERL_NIF_TERM atom_unable_to_check_generator;
+static ERL_NIF_TERM atom_not_suitable_generator;
+static ERL_NIF_TERM atom_check_failed;
+static ERL_NIF_TERM atom_unknown;
+static ERL_NIF_TERM atom_none;
+
+
+static int is_ok_load_info(ErlNifEnv* env, ERL_NIF_TERM load_info)
+{
+ int i;
+ return enif_get_int(env,load_info,&i) && i == 101;
+}
+static void* crypto_alloc(size_t size)
+{
+ return enif_alloc(size);
+}
+static void* crypto_realloc(void* ptr, size_t size)
+{
+ return enif_realloc(ptr, size);
+}
+static void crypto_free(void* ptr)
+{
+ enif_free(ptr);
+}
+
+static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ ErlNifSysInfo sys_info;
+ CRYPTO_set_mem_functions(crypto_alloc, crypto_realloc, crypto_free);
+
+ if (!is_ok_load_info(env, load_info)) {
+ return -1;
+ }
+
+#ifdef OPENSSL_THREADS
+ enif_system_info(&sys_info, sizeof(sys_info));
+
+ if (sys_info.scheduler_threads > 1) {
+ int i;
+ lock_vec = enif_alloc(CRYPTO_num_locks()*sizeof(*lock_vec));
+ if (lock_vec==NULL) return -1;
+ memset(lock_vec,0,CRYPTO_num_locks()*sizeof(*lock_vec));
+
+ for (i=CRYPTO_num_locks()-1; i>=0; --i) {
+ lock_vec[i] = enif_rwlock_create("crypto_stat");
+ if (lock_vec[i]==NULL) return -1;
+ }
+ CRYPTO_set_locking_callback(locking_function);
+ CRYPTO_set_id_callback(id_function);
+ CRYPTO_set_dynlock_create_callback(dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+ }
+ /* else no need for locks */
+#endif /* OPENSSL_THREADS */
+
+ atom_true = enif_make_atom(env,"true");
+ atom_false = enif_make_atom(env,"false");
+ atom_sha = enif_make_atom(env,"sha");
+ atom_md5 = enif_make_atom(env,"md5");
+ atom_error = enif_make_atom(env,"error");
+ atom_rsa_pkcs1_padding = enif_make_atom(env,"rsa_pkcs1_padding");
+ atom_rsa_pkcs1_oaep_padding = enif_make_atom(env,"rsa_pkcs1_oaep_padding");
+ atom_rsa_no_padding = enif_make_atom(env,"rsa_no_padding");
+ atom_undefined = enif_make_atom(env,"undefined");
+ atom_ok = enif_make_atom(env,"ok");
+ atom_not_prime = enif_make_atom(env,"not_prime");
+ atom_not_strong_prime = enif_make_atom(env,"not_strong_prime");
+ atom_unable_to_check_generator = enif_make_atom(env,"unable_to_check_generator");
+ atom_not_suitable_generator = enif_make_atom(env,"not_suitable_generator");
+ atom_check_failed = enif_make_atom(env,"check_failed");
+ atom_unknown = enif_make_atom(env,"unknown");
+ atom_none = enif_make_atom(env,"none");
+
+ *priv_data = NULL;
+ library_refc++;
+ return 0;
+}
+
+static int reload(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ if (*priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ if (library_refc == 0) {
+ /* No support for real library upgrade. The tricky thing is to know
+ when to (re)set the callbacks for allocation and locking. */
+ return -2;
+ }
+ if (!is_ok_load_info(env, load_info)) {
+ return -1;
+ }
+ return 0;
+}
+
+static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data,
+ ERL_NIF_TERM load_info)
+{
+ int i;
+ if (*old_priv_data != NULL) {
+ return -1; /* Don't know how to do that */
+ }
+ i = reload(env,priv_data,load_info);
+ if (i != 0) {
+ return i;
+ }
+ library_refc++;
+ return 0;
+}
+
+static void unload(ErlNifEnv* env, void* priv_data)
+{
+ if (--library_refc <= 0) {
+ CRYPTO_cleanup_all_ex_data();
+
+ if (lock_vec != NULL) {
+ int i;
+ for (i=CRYPTO_num_locks()-1; i>=0; --i) {
+ if (lock_vec[i] != NULL) {
+ enif_rwlock_destroy(lock_vec[i]);
+ }
+ }
+ enif_free(lock_vec);
+ }
+ }
+ /*else NIF library still used by other (new) module code */
+}
+
+static ERL_NIF_TERM info_lib(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ /* [{<<"OpenSSL">>,9470143,<<"OpenSSL 0.9.8k 25 Mar 2009">>}] */
+
+ static const char libname[] = "OpenSSL";
+ unsigned name_sz = strlen(libname);
+ const char* ver = SSLeay_version(SSLEAY_VERSION);
+ unsigned ver_sz = strlen(ver);
+ ERL_NIF_TERM name_term, ver_term;
+
+ memcpy(enif_make_new_binary(env, name_sz, &name_term), libname, name_sz);
+ memcpy(enif_make_new_binary(env, ver_sz, &ver_term), ver, ver_sz);
+
+ return enif_make_list1(env, enif_make_tuple3(env, name_term,
+ enif_make_int(env, SSLeay()),
+ ver_term));
+}
+
+static ERL_NIF_TERM md5(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data) */
+ ErlNifBinary ibin;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &ibin)) {
+ return enif_make_badarg(env);
+ }
+ MD5((unsigned char *) ibin.data, ibin.size,
+ enif_make_new_binary(env,MD5_LEN, &ret));
+ return ret;
+}
+static ERL_NIF_TERM md5_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+ ERL_NIF_TERM ret;
+ MD5_Init((MD5_CTX *) enif_make_new_binary(env, MD5_CTX_LEN, &ret));
+ return ret;
+}
+static ERL_NIF_TERM md5_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ MD5_CTX* new_ctx;
+ ErlNifBinary ctx_bin, data_bin;
+ ERL_NIF_TERM ret;
+ if (!enif_inspect_binary(env, argv[0], &ctx_bin)
+ || ctx_bin.size != MD5_CTX_LEN
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
+ return enif_make_badarg(env);
+ }
+ new_ctx = (MD5_CTX*) enif_make_new_binary(env,MD5_CTX_LEN, &ret);
+ memcpy(new_ctx, ctx_bin.data, MD5_CTX_LEN);
+ MD5_Update(new_ctx, data_bin.data, data_bin.size);
+ return ret;
+}
+static ERL_NIF_TERM md5_final(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) */
+ ErlNifBinary ctx_bin;
+ MD5_CTX ctx_clone;
+ ERL_NIF_TERM ret;
+ if (!enif_inspect_binary(env, argv[0], &ctx_bin) || ctx_bin.size != MD5_CTX_LEN) {
+ return enif_make_badarg(env);
+ }
+ memcpy(&ctx_clone, ctx_bin.data, MD5_CTX_LEN); /* writable */
+ MD5_Final(enif_make_new_binary(env, MD5_LEN, &ret), &ctx_clone);
+ return ret;
+}
+
+static ERL_NIF_TERM sha(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data) */
+ ErlNifBinary ibin;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &ibin)) {
+ return enif_make_badarg(env);
+ }
+ SHA1((unsigned char *) ibin.data, ibin.size,
+ enif_make_new_binary(env,SHA_LEN, &ret));
+ return ret;
+}
+static ERL_NIF_TERM sha_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+ ERL_NIF_TERM ret;
+ SHA1_Init((SHA_CTX *) enif_make_new_binary(env, SHA_CTX_LEN, &ret));
+ return ret;
+}
+static ERL_NIF_TERM sha_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ SHA_CTX* new_ctx;
+ ErlNifBinary ctx_bin, data_bin;
+ ERL_NIF_TERM ret;
+ if (!enif_inspect_binary(env, argv[0], &ctx_bin) || ctx_bin.size != SHA_CTX_LEN
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
+ return enif_make_badarg(env);
+ }
+ new_ctx = (SHA_CTX*) enif_make_new_binary(env,SHA_CTX_LEN, &ret);
+ memcpy(new_ctx, ctx_bin.data, SHA_CTX_LEN);
+ SHA1_Update(new_ctx, data_bin.data, data_bin.size);
+ return ret;
+}
+static ERL_NIF_TERM sha_final(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) */
+ ErlNifBinary ctx_bin;
+ SHA_CTX ctx_clone;
+ ERL_NIF_TERM ret;
+ if (!enif_inspect_binary(env, argv[0], &ctx_bin) || ctx_bin.size != SHA_CTX_LEN) {
+ return enif_make_badarg(env);
+ }
+ memcpy(&ctx_clone, ctx_bin.data, SHA_CTX_LEN); /* writable */
+ SHA1_Final(enif_make_new_binary(env, SHA_LEN, &ret), &ctx_clone);
+ return ret;
+}
+
+static ERL_NIF_TERM md4(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data) */
+ ErlNifBinary ibin;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &ibin)) {
+ return enif_make_badarg(env);
+ }
+ MD4((unsigned char *) ibin.data, ibin.size,
+ enif_make_new_binary(env,MD4_LEN, &ret));
+ return ret;
+}
+static ERL_NIF_TERM md4_init(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* () */
+ ERL_NIF_TERM ret;
+ MD4_Init((MD4_CTX *) enif_make_new_binary(env, MD4_CTX_LEN, &ret));
+ return ret;
+}
+static ERL_NIF_TERM md4_update(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context, Data) */
+ MD4_CTX* new_ctx;
+ ErlNifBinary ctx_bin, data_bin;
+ ERL_NIF_TERM ret;
+ if (!enif_inspect_binary(env, argv[0], &ctx_bin) || ctx_bin.size != MD4_CTX_LEN
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)) {
+ return enif_make_badarg(env);
+ }
+ new_ctx = (MD4_CTX*) enif_make_new_binary(env,MD4_CTX_LEN, &ret);
+ memcpy(new_ctx, ctx_bin.data, MD4_CTX_LEN);
+ MD4_Update(new_ctx, data_bin.data, data_bin.size);
+ return ret;
+}
+static ERL_NIF_TERM md4_final(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Context) */
+ ErlNifBinary ctx_bin;
+ MD4_CTX ctx_clone;
+ ERL_NIF_TERM ret;
+ if (!enif_inspect_binary(env, argv[0], &ctx_bin) || ctx_bin.size != MD4_CTX_LEN) {
+ return enif_make_badarg(env);
+ }
+ memcpy(&ctx_clone, ctx_bin.data, MD4_CTX_LEN); /* writable */
+ MD4_Final(enif_make_new_binary(env, MD4_LEN, &ret), &ctx_clone);
+ return ret;
+}
+
+static ERL_NIF_TERM md5_mac_n(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Data, MacSize) */
+ unsigned char hmacbuf[SHA_DIGEST_LENGTH];
+ ErlNifBinary key, data;
+ unsigned mac_sz;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data)
+ || !enif_get_uint(env,argv[2],&mac_sz) || mac_sz > MD5_LEN) {
+ return enif_make_badarg(env);
+ }
+ hmac_md5(key.data, key.size, data.data, data.size, hmacbuf);
+ memcpy(enif_make_new_binary(env, mac_sz, &ret), hmacbuf, mac_sz);
+ return ret;
+}
+
+static ERL_NIF_TERM sha_mac_n(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Data, MacSize) */
+ unsigned char hmacbuf[SHA_DIGEST_LENGTH];
+ ErlNifBinary key, data;
+ unsigned mac_sz;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data)
+ || !enif_get_uint(env,argv[2],&mac_sz) || mac_sz > SHA_LEN) {
+ return enif_make_badarg(env);
+ }
+ hmac_sha1(key.data, key.size, data.data, data.size, hmacbuf);
+ memcpy(enif_make_new_binary(env, mac_sz, &ret),
+ hmacbuf, mac_sz);
+ return ret;
+}
+
+static ERL_NIF_TERM des_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Ivec, Text, IsEncrypt) */
+ ErlNifBinary key, ivec, text;
+ DES_key_schedule schedule;
+ DES_cblock ivec_clone; /* writable copy */
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key) || key.size != 8
+ || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[2], &text)
+ || text.size % 8 != 0) {
+ return enif_make_badarg(env);
+ }
+ memcpy(&ivec_clone, ivec.data, 8);
+ DES_set_key((const_DES_cblock*)key.data, &schedule);
+ DES_ncbc_encrypt(text.data, enif_make_new_binary(env, text.size, &ret),
+ text.size, &schedule, &ivec_clone, (argv[3] == atom_true));
+ return ret;
+}
+
+static ERL_NIF_TERM des_ecb_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Text/Cipher, IsEncrypt) */
+ ErlNifBinary key, text;
+ DES_key_schedule schedule;
+ ERL_NIF_TERM ret;
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key) || key.size != 8 ||
+ !enif_inspect_iolist_as_binary(env, argv[1], &text) || text.size != 8) {
+ return enif_make_badarg(env);
+ }
+ DES_set_key((const_DES_cblock*)key.data, &schedule);
+ DES_ecb_encrypt((const_DES_cblock*)text.data,
+ (DES_cblock*)enif_make_new_binary(env, 8, &ret),
+ &schedule, (argv[2] == atom_true));
+ return ret;
+}
+
+static ERL_NIF_TERM des_ede3_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key1, Key2, Key3, IVec, Text/Cipher, IsEncrypt) */
+ ErlNifBinary key1, key2, key3, ivec, text;
+ DES_key_schedule schedule1, schedule2, schedule3;
+ DES_cblock ivec_clone; /* writable copy */
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key1) || key1.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[1], &key2) || key2.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[2], &key3) || key3.size != 8
+ || !enif_inspect_binary(env, argv[3], &ivec) || ivec.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[4], &text)
+ || text.size % 8 != 0) {
+ return enif_make_badarg(env);
+ }
+
+ memcpy(&ivec_clone, ivec.data, 8);
+ DES_set_key((const_DES_cblock*)key1.data, &schedule1);
+ DES_set_key((const_DES_cblock*)key2.data, &schedule2);
+ DES_set_key((const_DES_cblock*)key3.data, &schedule3);
+ DES_ede3_cbc_encrypt(text.data, enif_make_new_binary(env,text.size,&ret),
+ text.size, &schedule1, &schedule2, &schedule3,
+ &ivec_clone, (argv[5] == atom_true));
+ return ret;
+}
+
+static ERL_NIF_TERM aes_cfb_128_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+ ErlNifBinary key, ivec, text;
+ AES_KEY aes_key;
+ unsigned char ivec_clone[16]; /* writable copy */
+ int new_ivlen = 0;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key) || key.size != 16
+ || !enif_inspect_binary(env, argv[1], &ivec) || ivec.size != 16
+ || !enif_inspect_iolist_as_binary(env, argv[2], &text)
+ || text.size % 16 != 0) {
+ return enif_make_badarg(env);
+ }
+
+ memcpy(ivec_clone, ivec.data, 16);
+ AES_set_encrypt_key(key.data, 128, &aes_key);
+ AES_cfb128_encrypt((unsigned char *) text.data,
+ enif_make_new_binary(env, text.size, &ret),
+ text.size, &aes_key, ivec_clone, &new_ivlen,
+ (argv[3] == atom_true));
+ return ret;
+}
+
+static ERL_NIF_TERM rand_bytes_1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Bytes) */
+ unsigned bytes;
+ unsigned char* data;
+ ERL_NIF_TERM ret;
+ if (!enif_get_uint(env, argv[0], &bytes)) {
+ return enif_make_badarg(env);
+ }
+ data = enif_make_new_binary(env, bytes, &ret);
+ RAND_pseudo_bytes(data, bytes);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes);
+ return ret;
+}
+static ERL_NIF_TERM rand_bytes_3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Bytes, TopMask, BottomMask) */
+ unsigned bytes;
+ unsigned char* data;
+ unsigned top_mask, bot_mask;
+ ERL_NIF_TERM ret;
+ if (!enif_get_uint(env, argv[0], &bytes)
+ || !enif_get_uint(env, argv[1], &top_mask)
+ || !enif_get_uint(env, argv[2], &bot_mask)) {
+ return enif_make_badarg(env);
+ }
+ data = enif_make_new_binary(env, bytes, &ret);
+ RAND_pseudo_bytes(data, bytes);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes);
+ if (bytes > 0) {
+ data[bytes-1] |= top_mask;
+ data[0] |= bot_mask;
+ }
+ return ret;
+}
+
+static int get_bn_from_mpint(ErlNifEnv* env, ERL_NIF_TERM term, BIGNUM** bnp)
+{
+ ErlNifBinary bin;
+ int sz;
+ if (!enif_inspect_binary(env,term,&bin)) {
+ return 0;
+ }
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(bin.data, bin.size);
+ sz = bin.size - 4;
+ if (sz < 0 || get_int32(bin.data) != sz) {
+ return 0;
+ }
+ *bnp = BN_bin2bn(bin.data+4, sz, NULL);
+ return 1;
+}
+
+static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Lo,Hi) */
+ BIGNUM *bn_from, *bn_to, *bn_rand;
+ unsigned char* data;
+ unsigned dlen;
+ ERL_NIF_TERM ret;
+ if (!get_bn_from_mpint(env, argv[0], &bn_from)
+ || !get_bn_from_mpint(env, argv[1], &bn_rand)) {
+ return enif_make_badarg(env);
+ }
+
+ bn_to = BN_new();
+ BN_sub(bn_to, bn_rand, bn_from);
+ BN_pseudo_rand_range(bn_rand, bn_to);
+ BN_add(bn_rand, bn_rand, bn_from);
+ dlen = BN_num_bytes(bn_rand);
+ data = enif_make_new_binary(env, dlen+4, &ret);
+ put_int32(data, dlen);
+ BN_bn2bin(bn_rand, data+4);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(data+4, dlen);
+ BN_free(bn_rand);
+ BN_free(bn_from);
+ BN_free(bn_to);
+ return ret;
+}
+
+static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Base,Exponent,Modulo) */
+ BIGNUM *bn_base=NULL, *bn_exponent=NULL, *bn_modulo, *bn_result;
+ BN_CTX *bn_ctx;
+ unsigned char* ptr;
+ unsigned dlen;
+ ERL_NIF_TERM ret;
+
+ if (!get_bn_from_mpint(env, argv[0], &bn_base)
+ || !get_bn_from_mpint(env, argv[1], &bn_exponent)
+ || !get_bn_from_mpint(env, argv[2], &bn_modulo)) {
+
+ if (bn_base) BN_free(bn_base);
+ if (bn_exponent) BN_free(bn_exponent);
+ return enif_make_badarg(env);
+ }
+ bn_result = BN_new();
+ bn_ctx = BN_CTX_new();
+ BN_mod_exp(bn_result, bn_base, bn_exponent, bn_modulo, bn_ctx);
+ dlen = BN_num_bytes(bn_result);
+ ptr = enif_make_new_binary(env, dlen+4, &ret);
+ put_int32(ptr, dlen);
+ BN_bn2bin(bn_result, ptr+4);
+ BN_free(bn_result);
+ BN_CTX_free(bn_ctx);
+ BN_free(bn_modulo);
+ BN_free(bn_exponent);
+ BN_free(bn_base);
+ return ret;
+}
+
+static int inspect_mpint(ErlNifEnv* env, ERL_NIF_TERM term, ErlNifBinary* bin)
+{
+ return enif_inspect_binary(env, term, bin) &&
+ bin->size >= 4 && get_int32(bin->data) == bin->size-4;
+}
+
+static ERL_NIF_TERM dss_verify(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (DigestType,Data,Signature,Key=[P, Q, G, Y]) */
+ ErlNifBinary data_bin, sign_bin;
+ BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_y;
+ unsigned char hmacbuf[SHA_DIGEST_LENGTH];
+ ERL_NIF_TERM head, tail;
+ DSA *dsa;
+ int i;
+
+ if (!inspect_mpint(env, argv[2], &sign_bin)
+ || !enif_get_list_cell(env, argv[3], &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa_p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa_q)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa_g)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa_y)
+ || !enif_is_empty_list(env,tail)) {
+ return enif_make_badarg(env);
+ }
+ if (argv[0] == atom_sha && inspect_mpint(env, argv[1], &data_bin)) {
+ SHA1(data_bin.data+4, data_bin.size-4, hmacbuf);
+ }
+ else if (argv[0] == atom_none && enif_inspect_binary(env, argv[1], &data_bin)
+ && data_bin.size == SHA_DIGEST_LENGTH) {
+ memcpy(hmacbuf, data_bin.data, SHA_DIGEST_LENGTH);
+ }
+ else {
+ return enif_make_badarg(env);
+ }
+
+ dsa = DSA_new();
+ dsa->p = dsa_p;
+ dsa->q = dsa_q;
+ dsa->g = dsa_g;
+ dsa->priv_key = NULL;
+ dsa->pub_key = dsa_y;
+ i = DSA_verify(0, hmacbuf, SHA_DIGEST_LENGTH,
+ sign_bin.data+4, sign_bin.size-4, dsa);
+ DSA_free(dsa);
+ return(i > 0) ? atom_true : atom_false;
+}
+
+static ERL_NIF_TERM rsa_verify(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type, Data, Signature, Key=[E,N]) */
+ ErlNifBinary data_bin, sign_bin;
+ unsigned char hmacbuf[SHA_DIGEST_LENGTH];
+ ERL_NIF_TERM head, tail, ret;
+ int i, is_sha;
+ RSA* rsa = RSA_new();
+
+ if (argv[0] == atom_sha) is_sha = 1;
+ else if (argv[0] == atom_md5) is_sha = 0;
+ else goto badarg;
+
+ if (!inspect_mpint(env, argv[1], &data_bin)
+ || !inspect_mpint(env, argv[2], &sign_bin)
+ || !enif_get_list_cell(env, argv[3], &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->e)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->n)
+ || !enif_is_empty_list(env, tail)) {
+ badarg:
+ ret = enif_make_badarg(env);
+ }
+ else {
+ if (is_sha) {
+ SHA1(data_bin.data+4, data_bin.size-4, hmacbuf);
+ i = RSA_verify(NID_sha1, hmacbuf, SHA_DIGEST_LENGTH,
+ sign_bin.data+4, sign_bin.size-4, rsa);
+ }
+ else {
+ MD5(data_bin.data+4, data_bin.size-4, hmacbuf);
+ i = RSA_verify(NID_md5, hmacbuf, MD5_DIGEST_LENGTH,
+ sign_bin.data+4, sign_bin.size-4, rsa);
+ }
+ ret = (i==1 ? atom_true : atom_false);
+ }
+ RSA_free(rsa);
+ return ret;
+}
+
+static ERL_NIF_TERM aes_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data, IsEncrypt) */
+ ErlNifBinary key_bin, ivec_bin, data_bin;
+ AES_KEY aes_key;
+ unsigned char ivec[16];
+ int i;
+ unsigned char* ret_ptr;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || (key_bin.size != 16 && key_bin.size != 32)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || ivec_bin.size != 16
+ || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)
+ || data_bin.size % 16 != 0) {
+
+ return enif_make_badarg(env);
+ }
+
+ if (argv[3] == atom_true) {
+ i = AES_ENCRYPT;
+ AES_set_encrypt_key(key_bin.data, key_bin.size*8, &aes_key);
+ }
+ else {
+ i = AES_DECRYPT;
+ AES_set_decrypt_key(key_bin.data, key_bin.size*8, &aes_key);
+ }
+
+ ret_ptr = enif_make_new_binary(env, data_bin.size, &ret);
+ memcpy(ivec, ivec_bin.data, 16); /* writable copy */
+ AES_cbc_encrypt(data_bin.data, ret_ptr, data_bin.size, &aes_key, ivec, i);
+ return ret;
+}
+
+static ERL_NIF_TERM exor(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data1, Data2) */
+ ErlNifBinary d1, d2;
+ unsigned char* ret_ptr;
+ int i;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env,argv[0], &d1)
+ || !enif_inspect_iolist_as_binary(env,argv[1], &d2)
+ || d1.size != d2.size) {
+ return enif_make_badarg(env);
+ }
+ ret_ptr = enif_make_new_binary(env, d1.size, &ret);
+
+ for (i=0; i<d1.size; i++) {
+ ret_ptr[i] = d1.data[i] ^ d2.data[i];
+ }
+ return ret;
+}
+
+static ERL_NIF_TERM rc4_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Data) */
+ ErlNifBinary key, data;
+ RC4_KEY rc4_key;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env,argv[0], &key)
+ || !enif_inspect_iolist_as_binary(env,argv[1], &data)) {
+ return enif_make_badarg(env);
+ }
+ RC4_set_key(&rc4_key, key.size, key.data);
+ RC4(&rc4_key, data.size, data.data,
+ enif_make_new_binary(env, data.size, &ret));
+ return ret;
+}
+
+static ERL_NIF_TERM rc4_set_key(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key) */
+ ErlNifBinary key;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env,argv[0], &key)) {
+ return enif_make_badarg(env);
+ }
+ RC4_set_key((RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &ret),
+ key.size, key.data);
+ return ret;
+}
+
+static ERL_NIF_TERM rc4_encrypt_with_state(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (State, Data) */
+
+ ErlNifBinary state, data;
+ RC4_KEY* rc4_key;
+ ERL_NIF_TERM new_state, new_data;
+
+ if (!enif_inspect_iolist_as_binary(env,argv[0], &state)
+ || state.size != sizeof(RC4_KEY)
+ || !enif_inspect_iolist_as_binary(env,argv[1], &data)) {
+ return enif_make_badarg(env);
+ }
+ rc4_key = (RC4_KEY*)enif_make_new_binary(env, sizeof(RC4_KEY), &new_state);
+ memcpy(rc4_key, state.data, sizeof(RC4_KEY));
+ RC4(rc4_key, data.size, data.data,
+ enif_make_new_binary(env, data.size, &new_data));
+
+ return enif_make_tuple2(env,argv[0],new_data);
+}
+
+static ERL_NIF_TERM rc2_40_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key,IVec,Data,IsEncrypt) */
+ ErlNifBinary key_bin, ivec_bin, data_bin;
+ RC2_KEY rc2_key;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || key_bin.size != 5
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || ivec_bin.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)) {
+
+ return enif_make_badarg(env);
+ }
+
+ RC2_set_key(&rc2_key, 5, key_bin.data, 40);
+ RC2_cbc_encrypt(data_bin.data,
+ enif_make_new_binary(env, data_bin.size, &ret),
+ data_bin.size, &rc2_key,
+ ivec_bin.data,
+ (argv[3] == atom_true));
+
+ return ret;
+}
+
+static ERL_NIF_TERM rsa_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Type,Data,Key=[E,N,D]) */
+ ErlNifBinary data_bin, ret_bin;
+ ERL_NIF_TERM head, tail;
+ unsigned char hmacbuf[SHA_DIGEST_LENGTH];
+ unsigned rsa_s_len;
+ RSA *rsa = RSA_new();
+ int i, is_sha;
+
+ if (argv[0] == atom_sha) is_sha = 1;
+ else if (argv[0] == atom_md5) is_sha = 0;
+ else goto badarg;
+
+ if (!inspect_mpint(env,argv[1],&data_bin)
+ || !enif_get_list_cell(env, argv[2], &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->e)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->n)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->d)
+ || !enif_is_empty_list(env,tail)) {
+ badarg:
+ RSA_free(rsa);
+ return enif_make_badarg(env);
+ }
+ enif_alloc_binary(RSA_size(rsa), &ret_bin);
+ if (is_sha) {
+ SHA1(data_bin.data+4, data_bin.size-4, hmacbuf);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(hmacbuf, SHA_DIGEST_LENGTH);
+ i = RSA_sign(NID_sha1, hmacbuf, SHA_DIGEST_LENGTH,
+ ret_bin.data, &rsa_s_len, rsa);
+ }
+ else {
+ MD5(data_bin.data+4, data_bin.size-4, hmacbuf);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(hmacbuf, MD5_DIGEST_LENGTH);
+ i = RSA_sign(NID_md5, hmacbuf,MD5_DIGEST_LENGTH,
+ ret_bin.data, &rsa_s_len, rsa);
+ }
+ RSA_free(rsa);
+ if (i) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, rsa_s_len);
+ if (rsa_s_len != data_bin.size) {
+ enif_realloc_binary(&ret_bin, rsa_s_len);
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(ret_bin.data, rsa_s_len);
+ }
+ return enif_make_binary(env,&ret_bin);
+ }
+ else {
+ enif_release_binary(&ret_bin);
+ return atom_error;
+ }
+}
+
+static ERL_NIF_TERM dss_sign_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (DigesType, Data, Key=[P,Q,G,PrivKey]) */
+ ErlNifBinary data_bin, ret_bin;
+ ERL_NIF_TERM head, tail;
+ unsigned char hmacbuf[SHA_DIGEST_LENGTH];
+ unsigned int dsa_s_len;
+ DSA* dsa = DSA_new();
+ int i;
+
+ dsa->pub_key = NULL;
+ if (!enif_get_list_cell(env, argv[2], &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa->p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa->q)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa->g)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dsa->priv_key)
+ || !enif_is_empty_list(env,tail)) {
+ goto badarg;
+ }
+ if (argv[0] == atom_sha && inspect_mpint(env, argv[1], &data_bin)) {
+ SHA1(data_bin.data+4, data_bin.size-4, hmacbuf);
+ }
+ else if (argv[0] == atom_none && enif_inspect_binary(env,argv[1],&data_bin)
+ && data_bin.size == SHA_DIGEST_LENGTH) {
+ memcpy(hmacbuf, data_bin.data, SHA_DIGEST_LENGTH);
+ }
+ else {
+ badarg:
+ DSA_free(dsa);
+ return enif_make_badarg(env);
+ }
+
+ enif_alloc_binary(DSA_size(dsa), &ret_bin);
+ i = DSA_sign(NID_sha1, hmacbuf, SHA_DIGEST_LENGTH,
+ ret_bin.data, &dsa_s_len, dsa);
+ DSA_free(dsa);
+ if (i) {
+ if (dsa_s_len != ret_bin.size) {
+ enif_realloc_binary(&ret_bin, dsa_s_len);
+ }
+ return enif_make_binary(env, &ret_bin);
+ }
+ else {
+ return atom_error;
+ }
+}
+
+static int rsa_pad(ERL_NIF_TERM term, int* padding)
+{
+ if (term == atom_rsa_pkcs1_padding) {
+ *padding = RSA_PKCS1_PADDING;
+ }
+ else if (term == atom_rsa_pkcs1_oaep_padding) {
+ *padding = RSA_PKCS1_OAEP_PADDING;
+ }
+ else if (term == atom_rsa_no_padding) {
+ *padding = RSA_NO_PADDING;
+ }
+ else {
+ return 0;
+ }
+ return 1;
+}
+
+static ERL_NIF_TERM rsa_public_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data, PublKey=[E,N], Padding, IsEncrypt) */
+ ErlNifBinary data_bin, ret_bin;
+ ERL_NIF_TERM head, tail;
+ int padding, i;
+ RSA* rsa = RSA_new();
+
+ if (!enif_inspect_binary(env, argv[0], &data_bin)
+ || !enif_get_list_cell(env, argv[1], &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->e)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->n)
+ || !enif_is_empty_list(env,tail)
+ || !rsa_pad(argv[2], &padding)) {
+
+ RSA_free(rsa);
+ return enif_make_badarg(env);
+ }
+
+ enif_alloc_binary(RSA_size(rsa), &ret_bin);
+
+ if (argv[3] == atom_true) {
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,data_len);
+ i = RSA_public_encrypt(data_bin.size, data_bin.data,
+ ret_bin.data, rsa, padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, i);
+ }
+ }
+ else {
+ i = RSA_public_decrypt(data_bin.size, data_bin.data,
+ ret_bin.data, rsa, padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, i);
+ enif_realloc_binary(&ret_bin, i);
+ }
+ }
+ RSA_free(rsa);
+ if (i > 0) {
+ return enif_make_binary(env,&ret_bin);
+ }
+ else {
+ return atom_error;
+ }
+}
+
+static ERL_NIF_TERM rsa_private_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Data, PublKey=[E,N,D], Padding, IsEncrypt) */
+ ErlNifBinary data_bin, ret_bin;
+ ERL_NIF_TERM head, tail;
+ int padding, i;
+ RSA* rsa = RSA_new();
+
+ if (!enif_inspect_binary(env, argv[0], &data_bin)
+ || !enif_get_list_cell(env, argv[1], &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->e)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->n)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &rsa->d)
+ || !enif_is_empty_list(env,tail)
+ || !rsa_pad(argv[2], &padding)) {
+
+ RSA_free(rsa);
+ return enif_make_badarg(env);
+ }
+
+ enif_alloc_binary(RSA_size(rsa), &ret_bin);
+
+ if (argv[3] == atom_true) {
+ ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,data_len);
+ i = RSA_private_encrypt(data_bin.size, data_bin.data,
+ ret_bin.data, rsa, padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, i);
+ }
+ }
+ else {
+ i = RSA_private_decrypt(data_bin.size, data_bin.data,
+ ret_bin.data, rsa, padding);
+ if (i > 0) {
+ ERL_VALGRIND_MAKE_MEM_DEFINED(ret_bin.data, i);
+ enif_realloc_binary(&ret_bin, i);
+ }
+ }
+ RSA_free(rsa);
+ if (i > 0) {
+ return enif_make_binary(env,&ret_bin);
+ }
+ else {
+ return atom_error;
+ }
+}
+
+static ERL_NIF_TERM dh_generate_parameters_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (PrimeLen, Generator) */
+ int prime_len, generator;
+ DH* dh_params;
+ int p_len, g_len;
+ unsigned char *p_ptr, *g_ptr;
+ ERL_NIF_TERM ret_p, ret_g;
+
+ if (!enif_get_int(env, argv[0], &prime_len)
+ || !enif_get_int(env, argv[1], &generator)) {
+
+ return enif_make_badarg(env);
+ }
+ dh_params = DH_generate_parameters(prime_len, generator, NULL, NULL);
+ if (dh_params == NULL) {
+ return atom_error;
+ }
+ p_len = BN_num_bytes(dh_params->p);
+ g_len = BN_num_bytes(dh_params->g);
+ p_ptr = enif_make_new_binary(env, p_len+4, &ret_p);
+ g_ptr = enif_make_new_binary(env, g_len+4, &ret_g);
+ put_int32(p_ptr, p_len);
+ put_int32(g_ptr, g_len);
+ BN_bn2bin(dh_params->p, p_ptr+4);
+ BN_bn2bin(dh_params->g, g_ptr+4);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(p_ptr+4, p_len);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(g_ptr+4, g_len);
+ DH_free(dh_params);
+ return enif_make_list2(env, ret_p, ret_g);
+}
+
+static ERL_NIF_TERM dh_check(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* ([PrimeLen, Generator]) */
+ DH* dh_params = DH_new();
+ int i;
+ ERL_NIF_TERM ret, head, tail;
+
+ if (!enif_get_list_cell(env, argv[0], &head, &tail)
+ || !get_bn_from_mpint(env, head, &dh_params->p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dh_params->g)
+ || !enif_is_empty_list(env,tail)) {
+
+ DH_free(dh_params);
+ return enif_make_badarg(env);
+ }
+ if (DH_check(dh_params, &i)) {
+ if (i == 0) ret = atom_ok;
+ else if (i & DH_CHECK_P_NOT_PRIME) ret = atom_not_prime;
+ else if (i & DH_CHECK_P_NOT_SAFE_PRIME) ret = atom_not_strong_prime;
+ else if (i & DH_UNABLE_TO_CHECK_GENERATOR) ret = atom_unable_to_check_generator;
+ else if (i & DH_NOT_SUITABLE_GENERATOR) ret = atom_not_suitable_generator;
+ else ret = enif_make_tuple2(env, atom_unknown, enif_make_uint(env, i));
+ }
+ else { /* Check Failed */
+ ret = enif_make_tuple2(env, atom_error, atom_check_failed);
+ }
+ DH_free(dh_params);
+ return ret;
+}
+
+static ERL_NIF_TERM dh_generate_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (PrivKey, DHParams=[P,G]) */
+ DH* dh_params = DH_new();
+ int pub_len, prv_len;
+ unsigned char *pub_ptr, *prv_ptr;
+ ERL_NIF_TERM ret, ret_pub, ret_prv, head, tail;
+
+ if (!(get_bn_from_mpint(env, argv[0], &dh_params->priv_key)
+ || argv[0] == atom_undefined)
+ || !enif_get_list_cell(env, argv[1], &head, &tail)
+ || !get_bn_from_mpint(env, head, &dh_params->p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dh_params->g)
+ || !enif_is_empty_list(env, tail)) {
+
+ return enif_make_badarg(env);
+ }
+
+ if (DH_generate_key(dh_params)) {
+ pub_len = BN_num_bytes(dh_params->pub_key);
+ prv_len = BN_num_bytes(dh_params->priv_key);
+ pub_ptr = enif_make_new_binary(env, pub_len+4, &ret_pub);
+ prv_ptr = enif_make_new_binary(env, prv_len+4, &ret_prv);
+ put_int32(pub_ptr, pub_len);
+ put_int32(prv_ptr, prv_len);
+ BN_bn2bin(dh_params->pub_key, pub_ptr+4);
+ BN_bn2bin(dh_params->priv_key, prv_ptr+4);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(pub_ptr+4, pub_len);
+ ERL_VALGRIND_MAKE_MEM_DEFINED(prv_ptr+4, prv_len);
+ ret = enif_make_tuple2(env, ret_pub, ret_prv);
+ }
+ else {
+ ret = atom_error;
+ }
+ DH_free(dh_params);
+ return ret;
+}
+
+static ERL_NIF_TERM dh_compute_key_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (OthersPublicKey, MyPrivateKey, DHParams=[P,G]) */
+ DH* dh_params = DH_new();
+ BIGNUM* pubkey;
+ int i;
+ ErlNifBinary ret_bin;
+ ERL_NIF_TERM ret, head, tail;
+
+ if (!get_bn_from_mpint(env, argv[0], &pubkey)
+ || !get_bn_from_mpint(env, argv[1], &dh_params->priv_key)
+ || !enif_get_list_cell(env, argv[2], &head, &tail)
+ || !get_bn_from_mpint(env, head, &dh_params->p)
+ || !enif_get_list_cell(env, tail, &head, &tail)
+ || !get_bn_from_mpint(env, head, &dh_params->g)
+ || !enif_is_empty_list(env, tail)) {
+
+ ret = enif_make_badarg(env);
+ }
+ else {
+ enif_alloc_binary(DH_size(dh_params), &ret_bin);
+ i = DH_compute_key(ret_bin.data, pubkey, dh_params);
+ if (i > 0) {
+ if (i != ret_bin.size) {
+ enif_realloc_binary(&ret_bin, i);
+ }
+ ret = enif_make_binary(env, &ret_bin);
+ }
+ else {
+ ret = atom_error;
+ }
+ }
+ DH_free(dh_params);
+ return ret;
+}
+
+static ERL_NIF_TERM bf_cfb64_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Ivec, Data, IsEncrypt) */
+ ErlNifBinary key_bin, ivec_bin, data_bin;
+ BF_KEY bf_key; /* blowfish key 8 */
+ unsigned char bf_tkey[8]; /* blowfish ivec */
+ int bf_n = 0; /* blowfish ivec pos */
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || ivec_bin.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)) {
+ return enif_make_badarg(env);
+ }
+
+ BF_set_key(&bf_key, key_bin.size, key_bin.data);
+ memcpy(bf_tkey, ivec_bin.data, 8);
+ BF_cfb64_encrypt(data_bin.data, enif_make_new_binary(env,data_bin.size,&ret),
+ data_bin.size, &bf_key, bf_tkey, &bf_n,
+ (argv[3] == atom_true ? BF_ENCRYPT : BF_DECRYPT));
+ return ret;
+}
+
+static ERL_NIF_TERM bf_cbc_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Ivec, Data, IsEncrypt) */
+ ErlNifBinary key_bin, ivec_bin, data_bin;
+ BF_KEY bf_key; /* blowfish key 8 */
+ unsigned char bf_tkey[8]; /* blowfish ivec */
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || ivec_bin.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)
+ || data_bin.size % 8 != 0) {
+ return enif_make_badarg(env);
+ }
+
+ BF_set_key(&bf_key, key_bin.size, key_bin.data);
+ memcpy(bf_tkey, ivec_bin.data, 8);
+ BF_cbc_encrypt(data_bin.data, enif_make_new_binary(env,data_bin.size,&ret),
+ data_bin.size, &bf_key, bf_tkey,
+ (argv[3] == atom_true ? BF_ENCRYPT : BF_DECRYPT));
+ return ret;
+}
+
+static ERL_NIF_TERM bf_ecb_crypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, Data, IsEncrypt) */
+ ErlNifBinary key_bin, data_bin;
+ BF_KEY bf_key; /* blowfish key 8 */
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || !enif_inspect_iolist_as_binary(env, argv[1], &data_bin)
+ || data_bin.size < 8) {
+ return enif_make_badarg(env);
+ }
+ BF_set_key(&bf_key, key_bin.size, key_bin.data);
+ BF_ecb_encrypt(data_bin.data, enif_make_new_binary(env,data_bin.size,&ret),
+ &bf_key, (argv[2] == atom_true ? BF_ENCRYPT : BF_DECRYPT));
+ return ret;
+}
+
+static ERL_NIF_TERM blowfish_ofb64_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{/* (Key, IVec, Data) */
+ ErlNifBinary key_bin, ivec_bin, data_bin;
+ BF_KEY bf_key; /* blowfish key 8 */
+ unsigned char bf_tkey[8]; /* blowfish ivec */
+ int bf_n = 0; /* blowfish ivec pos */
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_iolist_as_binary(env, argv[0], &key_bin)
+ || !enif_inspect_binary(env, argv[1], &ivec_bin)
+ || ivec_bin.size != 8
+ || !enif_inspect_iolist_as_binary(env, argv[2], &data_bin)) {
+ return enif_make_badarg(env);
+ }
+
+ BF_set_key(&bf_key, key_bin.size, key_bin.data);
+ memcpy(bf_tkey, ivec_bin.data, 8);
+ BF_ofb64_encrypt(data_bin.data, enif_make_new_binary(env,data_bin.size,&ret),
+ data_bin.size, &bf_key, bf_tkey, &bf_n);
+ return ret;
+}
+
+
+
+#ifdef OPENSSL_THREADS /* vvvvvvvvvvvvvvv OPENSSL_THREADS vvvvvvvvvvvvvvvv */
+
+static INLINE void locking(int mode, ErlNifRWLock* lock)
+{
+ switch (mode) {
+ case CRYPTO_LOCK|CRYPTO_READ:
+ enif_rwlock_rlock(lock);
+ break;
+ case CRYPTO_LOCK|CRYPTO_WRITE:
+ enif_rwlock_rwlock(lock);
+ break;
+ case CRYPTO_UNLOCK|CRYPTO_READ:
+ enif_rwlock_runlock(lock);
+ break;
+ case CRYPTO_UNLOCK|CRYPTO_WRITE:
+ enif_rwlock_rwunlock(lock);
+ break;
+ default:
+ ASSERT(!"Invalid lock mode");
+ }
+}
+
+/* Callback from openssl for static locking
+ */
+static void locking_function(int mode, int n, const char *file, int line)
+{
+ ASSERT(n>=0 && n<CRYPTO_num_locks());
+
+ locking(mode, lock_vec[n]);
+}
+
+/* Callback from openssl for thread id
+ */
+static unsigned long id_function(void)
+{
+ return(unsigned long) enif_thread_self();
+}
+
+/* Callbacks for dynamic locking, not used by current openssl version (0.9.8)
+ */
+static struct CRYPTO_dynlock_value* dyn_create_function(const char *file, int line) {
+ return(struct CRYPTO_dynlock_value*) enif_rwlock_create("crypto_dyn");
+}
+static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value* ptr,const char *file, int line)
+{
+ locking(mode, (ErlNifRWLock*)ptr);
+}
+static void dyn_destroy_function(struct CRYPTO_dynlock_value *ptr, const char *file, int line)
+{
+ enif_rwlock_destroy((ErlNifRWLock*)ptr);
+}
+
+#endif /* ^^^^^^^^^^^^^^^^^^^^^^ OPENSSL_THREADS ^^^^^^^^^^^^^^^^^^^^^^ */
+
+/* HMAC */
+
+static void hmac_md5(unsigned char *key, int klen, unsigned char *dbuf, int dlen,
+ unsigned char *hmacbuf)
+{
+ MD5_CTX ctx;
+ char ipad[HMAC_INT_LEN];
+ char opad[HMAC_INT_LEN];
+ unsigned char nkey[MD5_LEN];
+ int i;
+
+ /* Change key if longer than 64 bytes */
+ if (klen > HMAC_INT_LEN) {
+ MD5(key, klen, nkey);
+ key = nkey;
+ klen = MD5_LEN;
+ }
+
+ memset(ipad, '\0', sizeof(ipad));
+ memset(opad, '\0', sizeof(opad));
+ memcpy(ipad, key, klen);
+ memcpy(opad, key, klen);
+
+ for (i = 0; i < HMAC_INT_LEN; i++) {
+ ipad[i] ^= HMAC_IPAD;
+ opad[i] ^= HMAC_OPAD;
+ }
+
+ /* inner MD5 */
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, ipad, HMAC_INT_LEN);
+ MD5_Update(&ctx, dbuf, dlen);
+ MD5_Final((unsigned char *) hmacbuf, &ctx);
+ /* outer MD5 */
+ MD5_Init(&ctx);
+ MD5_Update(&ctx, opad, HMAC_INT_LEN);
+ MD5_Update(&ctx, hmacbuf, MD5_LEN);
+ MD5_Final((unsigned char *) hmacbuf, &ctx);
+}
+
+static void hmac_sha1(unsigned char *key, int klen,
+ unsigned char *dbuf, int dlen,
+ unsigned char *hmacbuf)
+{
+ SHA_CTX ctx;
+ char ipad[HMAC_INT_LEN];
+ char opad[HMAC_INT_LEN];
+ unsigned char nkey[SHA_LEN];
+ int i;
+
+ /* Change key if longer than 64 bytes */
+ if (klen > HMAC_INT_LEN) {
+ SHA1(key, klen, nkey);
+ key = nkey;
+ klen = SHA_LEN;
+ }
+
+ memset(ipad, '\0', sizeof(ipad));
+ memset(opad, '\0', sizeof(opad));
+ memcpy(ipad, key, klen);
+ memcpy(opad, key, klen);
+
+ for (i = 0; i < HMAC_INT_LEN; i++) {
+ ipad[i] ^= HMAC_IPAD;
+ opad[i] ^= HMAC_OPAD;
+ }
+
+ /* inner SHA */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, ipad, HMAC_INT_LEN);
+ SHA1_Update(&ctx, dbuf, dlen);
+ SHA1_Final((unsigned char *) hmacbuf, &ctx);
+ /* outer SHA */
+ SHA1_Init(&ctx);
+ SHA1_Update(&ctx, opad, HMAC_INT_LEN);
+ SHA1_Update(&ctx, hmacbuf, SHA_LEN);
+ SHA1_Final((unsigned char *) hmacbuf, &ctx);
+}
+
diff --git a/lib/crypto/c_src/crypto_drv.c b/lib/crypto/c_src/crypto_drv.c
deleted file mode 100644
index 8c1356c4cf..0000000000
--- a/lib/crypto/c_src/crypto_drv.c
+++ /dev/null
@@ -1,1817 +0,0 @@
-/*
- * %CopyrightBegin%
- *
- * Copyright Ericsson AB 1999-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%
- */
-
-/*
- * Purpose: Dynamically loadable driver for cryptography libraries.
- * Based on OpenSSL.
- */
-
-#ifdef __WIN32__
-#include <windows.h>
-#endif
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include "erl_driver.h"
-
-#define OPENSSL_THREAD_DEFINES
-#include <openssl/opensslconf.h>
-#ifndef OPENSSL_THREADS
-# ifdef __GNUC__
-# warning No thread support by openssl. Driver will use coarse grain locking.
-# endif
-#endif
-
-#include <openssl/crypto.h>
-#include <openssl/des.h>
-/* #include <openssl/idea.h> This is not supported on the openssl OTP requires */
-#include <openssl/dsa.h>
-#include <openssl/rsa.h>
-#include <openssl/aes.h>
-#include <openssl/md5.h>
-#include <openssl/md4.h>
-#include <openssl/sha.h>
-#include <openssl/bn.h>
-#include <openssl/objects.h>
-#include <openssl/rc4.h>
-#include <openssl/rc2.h>
-#include <openssl/blowfish.h>
-#include <openssl/rand.h>
-
-#ifdef VALGRIND
-# include <valgrind/memcheck.h>
-
-/* libcrypto mixes supplied buffer contents into its entropy pool,
- which makes valgrind complain about the use of uninitialized data.
- We use this valgrind "request" to make sure that no such seemingly
- undefined data escapes the driver.
-*/
-# define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size) \
- VALGRIND_MAKE_MEM_DEFINED(ptr,size)
-
-# define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size) \
- ((void) ((VALGRIND_CHECK_MEM_IS_DEFINED(ptr,size) == 0) ? 1 : \
- (fprintf(stderr,"\r\n####### VALGRIND_ASSSERT(%p,%d) failed at %s:%d\r\n",\
- (ptr),(size), __FILE__, __LINE__), abort(), 0)))
-#else
-# define ERL_VALGRIND_MAKE_MEM_DEFINED(ptr,size)
-# define ERL_VALGRIND_ASSERT_MEM_DEFINED(ptr,size)
-#endif
-
-#ifdef DEBUG
-# define ASSERT(e) \
- ((void) ((e) ? 1 : (fprintf(stderr,"Assert '%s' failed at %s:%d\n",\
- #e, __FILE__, __LINE__), abort(), 0)))
-#else
-# define ASSERT(e) ((void) 1)
-#endif
-
-#ifdef __GNUC__
-# define INLINE __inline__
-#elif defined(__WIN32__)
-# define INLINE __forceinline
-#else
-# define INLINE
-#endif
-
-
-#define get_int32(s) ((((unsigned char*) (s))[0] << 24) | \
- (((unsigned char*) (s))[1] << 16) | \
- (((unsigned char*) (s))[2] << 8) | \
- (((unsigned char*) (s))[3]))
-
-#define put_int32(s,i) \
-{ (s)[0] = (char)(((i) >> 24) & 0xff);\
- (s)[1] = (char)(((i) >> 16) & 0xff);\
- (s)[2] = (char)(((i) >> 8) & 0xff);\
- (s)[3] = (char)((i) & 0xff);\
-}
-
-/* Driver interface declarations */
-static int init(void);
-static void finish(void);
-static ErlDrvData start(ErlDrvPort port, char *command);
-static void stop(ErlDrvData drv_data);
-static int crypto_control(ErlDrvData drv_data, unsigned int command, char *buf,
- int len, char **rbuf, int rlen);
-
-/* openssl callbacks */
-#ifdef OPENSSL_THREADS
-static void locking_function(int mode, int n, const char *file, int line);
-static unsigned long id_function(void);
-static struct CRYPTO_dynlock_value* dyn_create_function(const char *file,
- int line);
-static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value* ptr,
- const char *file, int line);
-static void dyn_destroy_function(struct CRYPTO_dynlock_value *ptr,
- const char *file, int line);
-#endif /* OPENSSL_THREADS */
-
-/* helpers */
-static void hmac_md5(char *key, int klen, char *dbuf, int dlen,
- char *hmacbuf);
-static void hmac_sha1(char *key, int klen, char *dbuf, int dlen,
- char *hmacbuf);
-
-static ErlDrvEntry crypto_driver_entry = {
- init,
- start,
- stop,
- NULL, /* output */
- NULL, /* ready_input */
- NULL, /* ready_output */
- "crypto_drv",
- finish,
- NULL, /* handle */
- crypto_control,
- NULL, /* timeout */
- NULL, /* outputv */
-
- NULL, /* ready_async */
- NULL, /* flush */
- NULL, /* call */
- NULL, /* event */
- ERL_DRV_EXTENDED_MARKER,
- ERL_DRV_EXTENDED_MAJOR_VERSION,
- ERL_DRV_EXTENDED_MINOR_VERSION,
-#ifdef OPENSSL_THREADS
- ERL_DRV_FLAG_USE_PORT_LOCKING,
-#else
- 0,
-#endif
- NULL, /* handle2 */
- NULL /* process_exit */
-};
-
-
-/* Keep the following definitions in alignment with the FUNC_LIST
- * in crypto.erl.
- */
-
-#define DRV_INFO 0
-#define DRV_MD5 1
-#define DRV_MD5_INIT 2
-#define DRV_MD5_UPDATE 3
-#define DRV_MD5_FINAL 4
-#define DRV_SHA 5
-#define DRV_SHA_INIT 6
-#define DRV_SHA_UPDATE 7
-#define DRV_SHA_FINAL 8
-#define DRV_MD5_MAC 9
-#define DRV_MD5_MAC_96 10
-#define DRV_SHA_MAC 11
-#define DRV_SHA_MAC_96 12
-#define DRV_CBC_DES_ENCRYPT 13
-#define DRV_CBC_DES_DECRYPT 14
-#define DRV_EDE3_CBC_DES_ENCRYPT 15
-#define DRV_EDE3_CBC_DES_DECRYPT 16
-#define DRV_AES_CFB_128_ENCRYPT 17
-#define DRV_AES_CFB_128_DECRYPT 18
-#define DRV_RAND_BYTES 19
-#define DRV_RAND_UNIFORM 20
-#define DRV_MOD_EXP 21
-#define DRV_DSS_VERIFY 22
-#define DRV_RSA_VERIFY_SHA 23
-/* #define DRV_RSA_VERIFY_MD5 35 */
-#define DRV_CBC_AES128_ENCRYPT 24
-#define DRV_CBC_AES128_DECRYPT 25
-#define DRV_XOR 26
-#define DRV_RC4_ENCRYPT 27 /* no decrypt needed; symmetric */
-#define DRV_RC4_SETKEY 28
-#define DRV_RC4_ENCRYPT_WITH_STATE 29
-#define DRV_CBC_RC2_40_ENCRYPT 30
-#define DRV_CBC_RC2_40_DECRYPT 31
-#define DRV_CBC_AES256_ENCRYPT 32
-#define DRV_CBC_AES256_DECRYPT 33
-#define DRV_INFO_LIB 34
-/* #define DRV_RSA_VERIFY_SHA 23 */
-#define DRV_RSA_VERIFY_MD5 35
-#define DRV_RSA_SIGN_SHA 36
-#define DRV_RSA_SIGN_MD5 37
-#define DRV_DSS_SIGN 38
-#define DRV_RSA_PUBLIC_ENCRYPT 39
-#define DRV_RSA_PRIVATE_DECRYPT 40
-#define DRV_RSA_PRIVATE_ENCRYPT 41
-#define DRV_RSA_PUBLIC_DECRYPT 42
-#define DRV_DH_GENERATE_PARAMS 43
-#define DRV_DH_CHECK 44
-#define DRV_DH_GENERATE_KEY 45
-#define DRV_DH_COMPUTE_KEY 46
-#define DRV_MD4 47
-#define DRV_MD4_INIT 48
-#define DRV_MD4_UPDATE 49
-#define DRV_MD4_FINAL 50
-
-#define SSL_VERSION_0_9_8 0
-#if SSL_VERSION_0_9_8
-#define DRV_SHA256 51
-#define DRV_SHA256_INIT 52
-#define DRV_SHA256_UPDATE 53
-#define DRV_SHA256_FINAL 54
-#define DRV_SHA512 55
-#define DRV_SHA512_INIT 56
-#define DRV_SHA512_UPDATE 57
-#define DRV_SHA512_FINAL 58
-#endif
-
-#define DRV_BF_CFB64_ENCRYPT 59
-#define DRV_BF_CFB64_DECRYPT 60
-#define DRV_BF_ECB_ENCRYPT 61
-#define DRV_BF_ECB_DECRYPT 62
-#define DRV_BF_OFB64_ENCRYPT 63
-#define DRV_BF_CBC_ENCRYPT 64
-#define DRV_BF_CBC_DECRYPT 65
-
-#define DRV_ECB_DES_ENCRYPT 66
-#define DRV_ECB_DES_DECRYPT 67
-
-/* #define DRV_CBC_IDEA_ENCRYPT 34 */
-/* #define DRV_CBC_IDEA_DECRYPT 35 */
-
-/* Not DRV_DH_GENERATE_PARAMS DRV_DH_CHECK
- * Calc RSA_VERIFY_* and RSA_SIGN once */
-#define NUM_CRYPTO_FUNCS 48
-
-#define MD5_CTX_LEN (sizeof(MD5_CTX))
-#define MD5_LEN 16
-#define MD5_LEN_96 12
-#define MD4_CTX_LEN (sizeof(MD4_CTX))
-#define MD4_LEN 16
-#define SHA_CTX_LEN (sizeof(SHA_CTX))
-#define SHA_LEN 20
-#define SHA_LEN_96 12
-#define HMAC_INT_LEN 64
-
-#define HMAC_IPAD 0x36
-#define HMAC_OPAD 0x5c
-
-#if SSL_VERSION_0_9_8
-#define SHA256_CTX_LEN (sizeof(SHA256_CTX))
-#define SHA256_LEN 32
-
-#define SHA512_CTX_LEN (sizeof(SHA512_CTX))
-#define SHA512_LEN 64
-#endif
-
-/* INITIALIZATION AFTER LOADING */
-
-/*
- * This is the init function called after this driver has been loaded.
- * It must *not* be declared static. Must return the address to
- * the driver entry.
- */
-
-#if !defined(__WIN32__)
-DRIVER_INIT(crypto_drv);
-#endif
-
-DRIVER_INIT(crypto_drv)
-{
- return &crypto_driver_entry;
-}
-
-static ErlDrvRWLock** lock_vec = NULL; /* Static locks used by openssl */
-
-/* DRIVER INTERFACE */
-
-static int init(void)
-{
- ErlDrvSysInfo sys_info;
- int i;
-
- CRYPTO_set_mem_functions(driver_alloc, driver_realloc, driver_free);
-
-#ifdef OPENSSL_THREADS
- driver_system_info(&sys_info, sizeof(sys_info));
-
- if(sys_info.scheduler_threads > 1) {
- lock_vec = driver_alloc(CRYPTO_num_locks()*sizeof(*lock_vec));
- if (lock_vec==NULL) return -1;
- memset(lock_vec,0,CRYPTO_num_locks()*sizeof(*lock_vec));
-
- for(i=CRYPTO_num_locks()-1; i>=0; --i) {
- lock_vec[i] = erl_drv_rwlock_create("crypto_drv_stat");
- if (lock_vec[i]==NULL) return -1;
- }
- CRYPTO_set_locking_callback(locking_function);
- CRYPTO_set_id_callback(id_function);
- CRYPTO_set_dynlock_create_callback(dyn_create_function);
- CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
- CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
- }
- /* else no need for locks */
-#endif /* OPENSSL_THREADS */
-
- return 0;
-}
-
-static void finish(void)
-{
- /* Moved here from crypto_control() as it's not thread safe */
- CRYPTO_cleanup_all_ex_data();
-
- if(lock_vec != NULL) {
- int i;
- for(i=CRYPTO_num_locks()-1; i>=0; --i) {
- if (lock_vec[i] != NULL) {
- erl_drv_rwlock_destroy(lock_vec[i]);
- }
- }
- driver_free(lock_vec);
- }
-}
-
-static ErlDrvData start(ErlDrvPort port, char *command)
-{
- set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
- return 0; /* not used */
-}
-
-static void stop(ErlDrvData drv_data)
-{
- return;
-}
-
-/* Helper functions for 'crypto_control'
-*/
-static INLINE unsigned char* return_binary(char **rbuf, int rlen, int len)
-{
- if (len <= rlen) {
- return (unsigned char *) *rbuf;
- }
- else {
- ErlDrvBinary* bin;
- *rbuf = (char*) (bin = driver_alloc_binary(len));
- return (bin==NULL) ? NULL : (unsigned char *) bin->orig_bytes;
- }
-}
-
-static INLINE unsigned char* return_binary_shrink(char **rbuf, int rlen, unsigned char* data, int len)
-{
- if ((char *) data == *rbuf) { /* default buffer */
- ASSERT(len <= rlen);
- return (unsigned char *) data;
- }
- else {
- ErlDrvBinary* bin = (ErlDrvBinary*) *rbuf;
- *rbuf = (char*) (bin=driver_realloc_binary(bin, len));
- return (bin==NULL) ? NULL : (unsigned char *) bin->orig_bytes;
- }
-}
-
-/* Nowadays (R13) it does matter what value control returns
- * as it may return data in default buffer.
- */
-static int crypto_control(ErlDrvData drv_data, unsigned int command, char *buf,
- int len, char **rbuf, int rlen)
-{
- int klen, dlen, macsize, from_len, to_len, i;
- int base_len, exponent_len, modulo_len;
- int data_len, dsa_p_len, dsa_q_len;
- int dsa_s_len, dsa_g_len, dsa_y_len;
- int rsa_e_len, rsa_n_len, rsa_d_len, padding;
- int or_mask;
- int prime_len, generator;
- int privkey_len, pubkey_len, dh_p_len, dh_g_len;
- unsigned int rsa_s_len, j;
- char *key, *key2, *dbuf;
- unsigned char *p;
- const_DES_cblock *des_key, *des_key2, *des_key3;
- const unsigned char *des_dbuf;
- BIGNUM *bn_from, *bn_to, *bn_rand, *bn_result;
- BIGNUM *bn_base, *bn_exponent, *bn_modulo;
- BIGNUM *dsa_p, *dsa_q, *dsa_g, *dsa_y;
- BIGNUM *rsa_n, *rsa_e, *rsa_d;
- BIGNUM *dh_p, *dh_g, *privkey, *pubkey;
- DES_cblock *des_ivec;
- unsigned char* bin;
- DES_key_schedule schedule, schedule2, schedule3;
- DH *dh_params;
-/* IDEA_KEY_SCHEDULE idea, idea2; */
- char hmacbuf[SHA_DIGEST_LENGTH];
- unsigned char *rsa_s, *dsa_s;
- /* char hmacbuf[SHA_LEN]; */
-#if SSL_VERSION_0_9_8
- SHA256_CTX sha256_ctx;
- SHA512_CTX sha512_ctx;
-#endif
- MD5_CTX md5_ctx;
- MD4_CTX md4_ctx;
- SHA_CTX sha_ctx;
- int new_ivlen = 0;
- BN_CTX *bn_ctx;
- DSA *dsa;
- RSA *rsa;
- AES_KEY aes_key;
- RC4_KEY rc4_key;
- RC2_KEY rc2_key;
-
- switch(command) {
-
- case DRV_INFO:
- bin = return_binary(rbuf,rlen,NUM_CRYPTO_FUNCS);
- if (bin==NULL) return -1;
-
- for (i = 0; i < NUM_CRYPTO_FUNCS; i++) {
- bin[i] = i + 1;
- }
- return NUM_CRYPTO_FUNCS;
-
- case DRV_MD5:
- bin = return_binary(rbuf,rlen,MD5_LEN);
- if (bin==NULL) return -1;
- MD5((unsigned char *) buf, len, bin);
- return MD5_LEN;
-
- case DRV_MD5_INIT:
- bin = return_binary(rbuf,rlen,MD5_CTX_LEN);
- if (bin==NULL) return -1;
- MD5_Init((MD5_CTX *)bin);
- return MD5_CTX_LEN;
-
- case DRV_MD5_UPDATE:
- if (len < MD5_CTX_LEN)
- return -1;
- bin = return_binary(rbuf,rlen,MD5_CTX_LEN);
- if (bin==NULL) return -1;
- memcpy(bin, buf, MD5_CTX_LEN);
- MD5_Update((MD5_CTX *)bin, buf + MD5_CTX_LEN,
- len - MD5_CTX_LEN);
- return MD5_CTX_LEN;
-
- case DRV_MD5_FINAL:
- if (len != MD5_CTX_LEN)
- return -1;
- memcpy(&md5_ctx, buf, MD5_CTX_LEN); /* XXX Use buf only? */
- bin = return_binary(rbuf,rlen,MD5_LEN);
- if (bin==NULL) return -1;
- MD5_Final(bin, &md5_ctx);
- return MD5_LEN;
-
- case DRV_SHA:
- bin = return_binary(rbuf,rlen,SHA_LEN);
- if (bin==NULL) return -1;
- SHA1((unsigned char *) buf, len, bin);
- return SHA_LEN;
-
- case DRV_SHA_INIT:
- bin = return_binary(rbuf,rlen,SHA_CTX_LEN);
- if (bin==NULL) return -1;
- SHA1_Init((SHA_CTX*)bin);
- return SHA_CTX_LEN;
-
- case DRV_SHA_UPDATE:
- if (len < SHA_CTX_LEN)
- return -1;
- bin = return_binary(rbuf,rlen,SHA_CTX_LEN);
- if (bin==NULL) return -1;
- memcpy(bin, buf, SHA_CTX_LEN);
- SHA1_Update((SHA_CTX*)bin, buf + SHA_CTX_LEN, len - SHA_CTX_LEN);
- return SHA_CTX_LEN;
-
- case DRV_SHA_FINAL:
- if (len != SHA_CTX_LEN)
- return -1;
- memcpy(&sha_ctx, buf, SHA_CTX_LEN); /* XXX Use buf only? */
- bin = return_binary(rbuf,rlen,SHA_LEN);
- if (bin==NULL) return -1;
- SHA1_Final(bin, &sha_ctx);
- return SHA_LEN;
-
- case DRV_MD5_MAC:
- case DRV_MD5_MAC_96:
- /* buf = klen[4] key data */
- klen = get_int32(buf);
- key = buf + 4;
- dlen = len - klen - 4;
- dbuf = key + klen;
- hmac_md5(key, klen, dbuf, dlen, hmacbuf);
- macsize = (command == DRV_MD5_MAC) ? MD5_LEN : MD5_LEN_96;
- bin = return_binary(rbuf,rlen,macsize);
- if (bin==NULL) return -1;
- memcpy(bin, hmacbuf, macsize);
- return macsize;
-
- case DRV_SHA_MAC:
- case DRV_SHA_MAC_96:
- /* buf = klen[4] key data */
- klen = get_int32(buf);
- key = buf + 4;
- dlen = len - klen - 4;
- dbuf = key + klen;
- hmac_sha1(key, klen, dbuf, dlen, hmacbuf);
- macsize = (command == DRV_SHA_MAC) ? SHA_LEN : SHA_LEN_96;
- bin = return_binary(rbuf,rlen,macsize);
- if (bin==NULL) return -1;
- memcpy(bin, (unsigned char *) hmacbuf, macsize);
- return macsize;
-
- case DRV_CBC_DES_ENCRYPT:
- case DRV_CBC_DES_DECRYPT:
- /* buf = key[8] ivec[8] data */
- dlen = len - 16;
- if (dlen < 0)
- return -1;
- if (dlen % 8 != 0)
- return -1;
- des_key = (const_DES_cblock*) buf;
- des_ivec = (DES_cblock*)(buf + 8);
- des_dbuf = (unsigned char *) (buf + 16);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- DES_set_key(des_key, &schedule);
- DES_ncbc_encrypt(des_dbuf, bin, dlen, &schedule, des_ivec,
- (command == DRV_CBC_DES_ENCRYPT));
- return dlen;
-
- case DRV_ECB_DES_ENCRYPT:
- case DRV_ECB_DES_DECRYPT:
- /* buf = key[8] data */
- dlen = len - 8;
- if (dlen != 8)
- return -1;
- des_key = (const_DES_cblock*) buf;
- des_dbuf = (unsigned char *) (buf + 8);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- DES_set_key(des_key, &schedule);
- DES_ecb_encrypt((const_DES_cblock*) des_dbuf, (DES_cblock*) bin, &schedule,
- (command == DRV_ECB_DES_ENCRYPT));
- return dlen;
-
- case DRV_BF_ECB_ENCRYPT:
- case DRV_BF_ECB_DECRYPT:
- {
- /* buf = klen[4] key data */
- int bf_direction;
- const unsigned char *ukey;
- const unsigned char *bf_dbuf; /* blowfish input data */
- BF_KEY bf_key; /* blowfish key 8 */
-
- klen = get_int32(buf);
- ukey = (unsigned char *) buf + 4;
- bf_dbuf = ukey + klen;
- dlen = len - 4 - klen;
- if (dlen < 0) return -1;
- BF_set_key(&bf_key, klen, ukey);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- bf_direction = command == DRV_BF_ECB_ENCRYPT ? BF_ENCRYPT : BF_DECRYPT;
- BF_ecb_encrypt(bf_dbuf, bin, &bf_key, bf_direction);
- return dlen;
- }
-
- case DRV_BF_CBC_ENCRYPT:
- case DRV_BF_CBC_DECRYPT:
- {
- /* buf = klen[4] key ivec[8] data */
- unsigned char *ukey;
- unsigned char* ivec;
- unsigned char bf_tkey[8]; /* blowfish ivec */
- int bf_direction;
- const unsigned char *bf_dbuf; /* blowfish input data */
- BF_KEY bf_key; /* blowfish key 8 */
-
- klen = get_int32(buf);
- ukey = (unsigned char *)buf + 4;
- ivec = ukey + klen;
- bf_dbuf = ivec + 8;
- dlen = len - 4 - klen - 8;
- if (dlen < 0) return -1;
- BF_set_key(&bf_key, klen, ukey);
- memcpy(bf_tkey, ivec, 8);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- bf_direction = command == DRV_BF_CBC_ENCRYPT ? BF_ENCRYPT : BF_DECRYPT;
- BF_cbc_encrypt(bf_dbuf, bin, dlen, &bf_key, bf_tkey, bf_direction);
- return dlen;
- }
-
- case DRV_BF_OFB64_ENCRYPT:
- {
- /* buf = klen[4] key ivec[8] data */
- unsigned char *ukey;
- unsigned char* ivec;
- unsigned char bf_tkey[8]; /* blowfish ivec */
- int bf_n; /* blowfish ivec pos */
- const unsigned char *bf_dbuf; /* blowfish input data */
- BF_KEY bf_key; /* blowfish key 8 */
-
- klen = get_int32(buf);
- ukey = (unsigned char *)buf + 4;
- ivec = ukey + klen;
- bf_dbuf = ivec + 8;
- dlen = len - 4 - klen - 8;
- if (dlen < 0) return -1;
- BF_set_key(&bf_key, klen, ukey);
- memcpy(bf_tkey, ivec, 8);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- bf_n = 0;
- BF_ofb64_encrypt(bf_dbuf, bin, dlen, &bf_key, bf_tkey, &bf_n);
- return dlen;
- }
-
- case DRV_BF_CFB64_ENCRYPT:
- case DRV_BF_CFB64_DECRYPT:
- {
- /* buf = klen[4] key ivec[8] data */
- unsigned char* ivec;
- unsigned char bf_tkey[8]; /* blowfish ivec */
- int bf_n; /* blowfish ivec pos */
- int bf_direction;
- const unsigned char *bf_dbuf; /* blowfish input data */
- BF_KEY bf_key; /* blowfish key 8 */
-
- klen = get_int32(buf);
- key = buf + 4;
- ivec = (unsigned char *) (key + klen);
- bf_dbuf = ivec + 8;
- dlen = len - 4 - klen - 8;
- if (dlen < 0) return -1;
- BF_set_key(&bf_key, klen, (unsigned char *) key);
- memcpy(bf_tkey, ivec, 8);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- bf_direction = command == DRV_BF_CFB64_ENCRYPT ? BF_ENCRYPT : BF_DECRYPT;
- bf_n = 0;
- BF_cfb64_encrypt(bf_dbuf, bin, dlen, &bf_key, bf_tkey, &bf_n, bf_direction);
- return dlen;
- }
-
-/* case DRV_CBC_IDEA_ENCRYPT: */
-/* case DRV_CBC_IDEA_DECRYPT: */
- /* buf = key[16] ivec[8] data */
-/* dlen = len - 24; */
-/* if (dlen < 0) */
-/* return -1; */
-/* if (dlen % 8 != 0) */
-/* return -1; */
-/* bin = return_binary(rbuf,rlen,dlen); */
-/* idea_set_encrypt_key(buf, &idea); */
-/* if (command == DRV_CBC_IDEA_DECRYPT) { */
-/* idea_set_decrypt_key(&idea, &idea2); */
-/* memcpy(&idea, &idea2, sizeof(idea)); */
-/* } */
-/* idea_cbc_encrypt(buf + 24, bin, dlen, &idea, buf + 8, */
-/* (command == DRV_CBC_IDEA_ENCRYPT)); */
-/* return dlen; */
-
- case DRV_CBC_RC2_40_ENCRYPT:
- case DRV_CBC_RC2_40_DECRYPT:
- /* buf = key[5] ivec[8] data */
- dlen = len - 13;
- if (dlen < 0)
- return -1;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- RC2_set_key(&rc2_key, 5, (unsigned char *) buf, 40);
- RC2_cbc_encrypt((unsigned char *) (buf + 13), bin, dlen, &rc2_key,
- (unsigned char *) (buf + 5),
- (command == DRV_CBC_RC2_40_ENCRYPT));
- return dlen;
-
- case DRV_EDE3_CBC_DES_ENCRYPT:
- case DRV_EDE3_CBC_DES_DECRYPT:
- dlen = len - 32;
- if (dlen < 0)
- return -1;
- des_key = (const_DES_cblock*) buf;
- des_key2 = (const_DES_cblock*) (buf + 8);
- des_key3 = (const_DES_cblock*) (buf + 16);
- des_ivec = (DES_cblock*) (buf + 24);
- des_dbuf = (unsigned char *) (buf + 32);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- DES_set_key(des_key, &schedule);
- DES_set_key(des_key2, &schedule2);
- DES_set_key(des_key3, &schedule3);
- DES_ede3_cbc_encrypt(des_dbuf, bin, dlen, &schedule,
- &schedule2, &schedule3, des_ivec,
- (command == DRV_EDE3_CBC_DES_ENCRYPT));
- return dlen;
-
- case DRV_AES_CFB_128_ENCRYPT:
- case DRV_AES_CFB_128_DECRYPT:
- /* buf = key[16] ivec[16] data */
- dlen = len - 32;
- if (dlen < 0)
- return -1;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- AES_set_encrypt_key((unsigned char *) buf, 128, &aes_key);
- AES_cfb128_encrypt((unsigned char *) (buf+32), bin, dlen, &aes_key,
- (unsigned char *) (buf+16), &new_ivlen,
- (command == DRV_AES_CFB_128_ENCRYPT));
- return dlen;
-
- case DRV_RC4_ENCRYPT:
- /* buf = klen[4] key data */
- klen = get_int32(buf);
- key = buf + 4;
- dlen = len - klen - 4;
- dbuf = key + klen;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- RC4_set_key(&rc4_key, klen, (unsigned char *) key);
- RC4(&rc4_key, dlen, (unsigned char *) dbuf, bin);
- return dlen;
-
- case DRV_RC4_SETKEY:
- /* buf = key */
- dlen = sizeof(rc4_key);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- RC4_set_key(&rc4_key, len, (unsigned char *) buf);
- memcpy(bin, &rc4_key, dlen);
- return dlen;
-
- case DRV_RC4_ENCRYPT_WITH_STATE:
- /* buf = statelength[4] state data, return statelength[4] state data */
- klen = get_int32(buf);
- key = buf + 4;
- dlen = len - klen - 4;
- dbuf = key + klen;
- bin = return_binary(rbuf,rlen,len);
- if (bin==NULL) return -1;
- memcpy(&rc4_key, key, klen);
- RC4(&rc4_key, dlen, (unsigned char *) dbuf, bin + klen + 4);
- memcpy(bin, buf, 4);
- memcpy(bin + 4, &rc4_key, klen);
- return len;
-
- case DRV_RAND_BYTES:
- /* buf = <<rlen:32/integer,topmask:8/integer,bottommask:8/integer>> */
-
- if (len != 6)
- return -1;
- dlen = get_int32(buf);
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- RAND_pseudo_bytes(bin,dlen);
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin, dlen);
- or_mask = ((unsigned char*)buf)[4];
- bin[dlen-1] |= or_mask; /* topmask */
- or_mask = ((unsigned char*)buf)[5];
- bin[0] |= or_mask; /* bottommask */
- return dlen;
-
- case DRV_RAND_UNIFORM:
- /* buf = <<from_len:32/integer,bn_from:from_len/binary, *
- * to_len:32/integer,bn_to:to_len/binary>> */
- if (len < 8)
- return -1;
- from_len = get_int32(buf);
- if (len < (8 + from_len))
- return -1;
- to_len = get_int32(buf + 4 + from_len);
- if (len != (8 + from_len + to_len))
- return -1;
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf, 4 + from_len + 4 + to_len);
- bn_from = BN_new();
- BN_bin2bn((unsigned char *)(buf + 4), from_len, bn_from);
- bn_rand = BN_new();
- BN_bin2bn((unsigned char *)(buf + 8 + from_len), to_len, bn_rand);
- bn_to = BN_new();
- BN_sub(bn_to, bn_rand, bn_from);
- BN_pseudo_rand_range(bn_rand, bn_to);
- BN_add(bn_rand, bn_rand, bn_from);
- dlen = BN_num_bytes(bn_rand);
- bin = return_binary(rbuf,rlen,dlen + 4);
- if (bin==NULL) return -1;
- put_int32(bin, dlen);
- BN_bn2bin(bn_rand,(unsigned char*)(bin + 4));
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+4, dlen);
- BN_free(bn_rand);
- BN_free(bn_from);
- BN_free(bn_to);
- return dlen + 4;
-
- case DRV_MOD_EXP:
- /* buf = <<base_len:32/integer,base/binary, *
- * exponent_len:32/integer,exponent/binary, *
- * modulo_len:32/integer, modulo/binary>> */
- if (len < 12)
- return -1;
- base_len = get_int32(buf);
- if (len < (12 + base_len))
- return -1;
- exponent_len = get_int32(buf + 4 + base_len);
- if (len < (12 + base_len + exponent_len))
- return -1;
- modulo_len = get_int32(buf + 8 + base_len + exponent_len);
- if (len != (12 + base_len + exponent_len + modulo_len))
- return -1;
- bn_base = BN_new();
- BN_bin2bn((unsigned char *)(buf + 4),
- base_len, bn_base);
- bn_exponent = BN_new();
- BN_bin2bn((unsigned char *)(buf + 8 + base_len),
- exponent_len, bn_exponent);
- bn_modulo = BN_new();
- BN_bin2bn((unsigned char *)(buf + 12 + base_len + exponent_len),
- modulo_len, bn_modulo);
- bn_result = BN_new();
- bn_ctx = BN_CTX_new();
- BN_mod_exp(bn_result, bn_base, bn_exponent,
- bn_modulo, bn_ctx);
- dlen = BN_num_bytes(bn_result);
- bin = return_binary(rbuf,rlen,dlen + 4);
- if (bin==NULL) return -1;
- put_int32(bin, dlen);
- BN_bn2bin(bn_result,(unsigned char*)(bin + 4));
- BN_free(bn_result);
- BN_free(bn_modulo);
- BN_free(bn_exponent);
- BN_free(bn_base);
- BN_CTX_free(bn_ctx);
- return dlen + 4;
-
- case DRV_DSS_VERIFY:
- /* buf = <<data_len:32/integer, data:data_len/binary,
- * dsa_s_len:32/integer, dsa_s:dsa_s_len/binary,
- * dsa_p_len:32/integer, dsa_p:dsa_p_len/binary,
- * dsa_q_len:32/integer, dsa_q:dsa_q_len/binary,
- * dsa_g_len:32/integer, dsa_g:dsa_g_len/binary,
- * dsa_y_len:32/integer, dsa_y:dsa_y_len/binary>> */
- i = 0;
- j = 0;
- if (len < 24)
- return -1;
- data_len = get_int32(buf + i + j);
- j += data_len; i += 4;
- if (len < (24 + j))
- return -1;
- dsa_s_len = get_int32(buf + i + j);
- j += dsa_s_len; i += 4;
- if (len < (24 + j))
- return -1;
- dsa_p_len = get_int32(buf + i + j);
- j += dsa_p_len; i += 4;
- if (len < (24 + j))
- return -1;
- dsa_q_len = get_int32(buf + i + j);
- j += dsa_q_len; i += 4;
- if (len < (24 + j))
- return -1;
- dsa_g_len = get_int32(buf + i + j);
- j += dsa_g_len; i += 4;
- if (len < (24 + j))
- return -1;
- dsa_y_len = get_int32(buf + i + j);
- j += dsa_y_len;
- if (len != (24 + j))
- return -1;
- i = 4;
- SHA1((unsigned char *) (buf + i), data_len, (unsigned char *) hmacbuf);
- i += data_len + 4;
- dsa_s = (unsigned char *)(buf + i);
- i += (dsa_s_len + 4);
- dsa_p = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_p_len, dsa_p);
- i += (dsa_p_len + 4);
- dsa_q = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_q_len, dsa_q);
- i += (dsa_q_len + 4);
- dsa_g = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_g_len, dsa_g);
- i += (dsa_g_len + 4);
- dsa_y = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_y_len, dsa_y);
- dsa = DSA_new();
- dsa->p = dsa_p;
- dsa->q = dsa_q;
- dsa->g = dsa_g;
- dsa->priv_key = NULL;
- dsa->pub_key = dsa_y;
- i = DSA_verify(0, (unsigned char *) hmacbuf, SHA_DIGEST_LENGTH,
- dsa_s, dsa_s_len, dsa);
- bin = return_binary(rbuf,rlen,1);
- if (bin==NULL) return -1;
-
- DSA_free(dsa);
- bin[0] = (i > 0) ? 1 : 0;
- return 1;
-
- case DRV_DSS_SIGN:
- /* buf = <<data_len:32/integer, data:data_len/binary,
- * dsa_p_len:32/integer, dsa_p:dsa_p_len/binary,
- * dsa_q_len:32/integer, dsa_q:dsa_q_len/binary,
- * dsa_g_len:32/integer, dsa_g:dsa_g_len/binary,
- * dsa_y_len:32/integer, dsa_y:dsa_y_len/binary,
- * dsa_x_len:32/integer, dsa_s:dsa_x_len/binary>> */
- i = 0;
- j = 0;
- if (len < 20)
- return -1;
- data_len = get_int32(buf + i + j);
- j += data_len; i += 4;
- if (len < (20 + j))
- return -1;
- dsa_p_len = get_int32(buf + i + j);
- j += dsa_p_len; i += 4;
- if (len < (20 + j))
- return -1;
- dsa_q_len = get_int32(buf + i + j);
- j += dsa_q_len; i += 4;
- if (len < (20 + j))
- return -1;
- dsa_g_len = get_int32(buf + i + j);
- j += dsa_g_len; i += 4;
- if (len < (20 + j))
- return -1;
- dsa_y_len = get_int32(buf + i + j);
- j += dsa_y_len;
- if (len < (20 + j))
- return -1;
- if (len != (20 + j))
- return -1;
-
- i = 4;
- SHA1((unsigned char *) (buf + i), data_len, (unsigned char *) hmacbuf);
- i += data_len + 4;
- dsa_p = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_p_len, dsa_p);
- i += (dsa_p_len + 4);
- dsa_q = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_q_len, dsa_q);
- i += (dsa_q_len + 4);
- dsa_g = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_g_len, dsa_g);
- i += (dsa_g_len + 4);
- dsa_y = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dsa_y_len, dsa_y);
- /* i += (dsa_y_len + 4); */
-
- dsa = DSA_new();
- dsa->p = dsa_p;
- dsa->q = dsa_q;
- dsa->g = dsa_g;
- dsa->priv_key = dsa_y;
- dsa->pub_key = NULL;
- dlen = DSA_size(dsa);
- bin = return_binary(rbuf,rlen, dlen+1);
- if (bin==NULL) return -1;
- i = DSA_sign(NID_sha1,
- (unsigned char *) hmacbuf,SHA_DIGEST_LENGTH,
- (unsigned char *) &bin[1],
- (unsigned int *) &dsa_s_len, dsa);
- DSA_free(dsa);
- if (i) {
- if (dsa_s_len != dlen) {
- bin = return_binary_shrink(rbuf,rlen,bin,dsa_s_len+1);
- }
- bin[0] = 1;
- return dsa_s_len + 1;
- }
- else {
- bin[0] = 0;
- return 1;
- }
-
- case DRV_RSA_VERIFY_MD5:
- case DRV_RSA_VERIFY_SHA:
- /* buf = <<data_len:32/integer, data:data_len/binary,
- * rsa_s_len:32/integer, rsa_s:rsa_s_len/binary,
- * rsa_e_len:32/integer, rsa_e:rsa_e_len/binary,
- * rsa_n_len:32/integer, rsa_n:rsa_n_len/binary>> */
- i = 0;
- j = 0;
- if (len < 16)
- return -1;
- data_len = get_int32(buf + i + j);
- j += data_len; i += 4;
- if (len < (16 + j))
- return -1;
- rsa_s_len = get_int32(buf + i + j);
- j += rsa_s_len; i += 4;
- if (len < (16 + j))
- return -1;
- rsa_e_len = get_int32(buf + i + j);
- j += rsa_e_len; i += 4;
- if (len < (16 + j))
- return -1;
- rsa_n_len = get_int32(buf + i + j);
- j += rsa_n_len; i += 4;
- if (len != (16 + j))
- return -1;
- i = 4;
- i += (data_len + 4);
- rsa_s = (unsigned char *)(buf + i);
- i += (rsa_s_len + 4);
- rsa_e = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), rsa_e_len, rsa_e);
- i += (rsa_e_len + 4);
- rsa_n = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), rsa_n_len, rsa_n);
- rsa = RSA_new();
- rsa->n = rsa_n;
- rsa->e = rsa_e;
- i = 4;
- if(command == DRV_RSA_VERIFY_SHA) {
- SHA1((unsigned char *) (buf + i), data_len,
- (unsigned char *) hmacbuf);
- i = RSA_verify(NID_sha1, (unsigned char *) hmacbuf, SHA_DIGEST_LENGTH,
- rsa_s, rsa_s_len, rsa);
- } else {
- MD5((unsigned char *) (buf + i), data_len, (unsigned char *) hmacbuf);
- i = RSA_verify(NID_md5, (unsigned char *) hmacbuf, MD5_DIGEST_LENGTH,
- rsa_s, rsa_s_len, rsa);
- }
-
- bin = return_binary(rbuf,rlen,1);
- if (bin==NULL) return -1;
- bin[0] = (char)(i & 0xff);
- RSA_free(rsa);
- return 1;
-
- case DRV_RSA_SIGN_MD5:
- case DRV_RSA_SIGN_SHA:
- /* buf = <<data_len:32/integer, data:data_len/binary,
- * rsa_e_len:32/integer, rsa_e:rsa_e_len/binary,
- * rsa_n_len:32/integer, rsa_n:rsa_n_len/binary,
- * rsa_d_len:32/integer, rsa_d:rsa_d_len/binary>> */
-
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf,len);
-
- i = 0;
- j = 0;
-
- if (len < 16)
- return -1;
- data_len = get_int32(buf + i + j);
- j += data_len; i += 4;
- if (len < (16 + j))
- return -1;
- rsa_e_len = get_int32(buf + i + j);
- j += rsa_e_len; i += 4;
- if (len < (16 + j))
- return -1;
- rsa_n_len = get_int32(buf + i + j);
- j += rsa_n_len; i += 4;
- if (len < (16 + j))
- return -1;
- rsa_d_len = get_int32(buf + i + j);
- j += rsa_d_len; i += 4;
- if (len != (16 + j))
- return -1;
-
- i = 4;
- i += (data_len + 4);
- rsa_e = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), rsa_e_len, rsa_e);
- i += (rsa_e_len + 4);
- rsa_n = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), rsa_n_len, rsa_n);
- i += (rsa_n_len + 4);
- rsa_d = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), rsa_d_len, rsa_d);
- i += (rsa_d_len + 4);
-
- rsa = RSA_new();
- rsa->e = rsa_e;
- rsa->n = rsa_n;
- rsa->d = rsa_d;
-
- dlen = RSA_size(rsa);
- bin = return_binary(rbuf,rlen,dlen+1);
- if (bin==NULL) return -1;
- i = 4;
- if (command == DRV_RSA_SIGN_MD5) {
- MD5((unsigned char *) (buf + i), data_len, (unsigned char *) hmacbuf);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(hmacbuf, MD5_DIGEST_LENGTH);
- i = RSA_sign(NID_md5,
- (unsigned char *) hmacbuf,MD5_DIGEST_LENGTH,
- (unsigned char *) &bin[1],
- &rsa_s_len, rsa);
- } else {
- SHA1((unsigned char *) (buf + i), data_len,
- (unsigned char *) hmacbuf);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(hmacbuf, SHA_DIGEST_LENGTH);
- i = RSA_sign(NID_sha1,
- (unsigned char *) hmacbuf,SHA_DIGEST_LENGTH,
- (unsigned char *) &bin[1],
- &rsa_s_len, rsa);
- }
- RSA_free(rsa);
- if (i) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+1, rsa_s_len);
- if (rsa_s_len != dlen) {
- bin = return_binary_shrink(rbuf,rlen,bin,rsa_s_len+1);
- ERL_VALGRIND_ASSERT_MEM_DEFINED(bin+1, rsa_s_len);
- }
- bin[0] = 1;
- return rsa_s_len + 1;
- }
- else {
- bin[0] = 0;
- return 1;
- }
-
- case DRV_RSA_PRIVATE_DECRYPT:
- case DRV_RSA_PRIVATE_ENCRYPT:
- /* buf = <<data_len:32/integer, data:data_len/binary,
- * rsa_e_len:32/integer, rsa_e:rsa_e_len/binary,
- * rsa_n_len:32/integer, rsa_n:rsa_n_len/binary,
- * rsa_d_len:32/integer, rsa_d:rsa_d_len/binary,
- * pad:8/integer >> */
-
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf,len);
- i = 0;
- j = 0;
-
- if (len < 17)
- return -1;
- data_len = get_int32(buf + i + j);
- j += data_len; i += 4;
- if (len < (17 + j))
- return -1;
- rsa_e_len = get_int32(buf + i + j);
- j += rsa_e_len; i += 4;
- if (len < (17 + j))
- return -1;
- rsa_n_len = get_int32(buf + i + j);
- j += rsa_n_len; i += 4;
- if (len < (17 + j))
- return -1;
- rsa_d_len = get_int32(buf + i + j);
- j += rsa_d_len; i += 4;
- padding = *(unsigned char *) (buf+i+j);
- if (len != (17 + j))
- return -1;
-
- i = 4;
- i += (data_len + 4);
- rsa_e = BN_new();
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,rsa_e_len);
- BN_bin2bn((unsigned char *)(buf + i), rsa_e_len, rsa_e);
- i += (rsa_e_len + 4);
- rsa_n = BN_new();
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,rsa_n_len);
- BN_bin2bn((unsigned char *)(buf + i), rsa_n_len, rsa_n);
- i += (rsa_n_len + 4);
- rsa_d = BN_new();
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,rsa_d_len);
- BN_bin2bn((unsigned char *)(buf + i), rsa_d_len, rsa_d);
- i += (rsa_d_len + 4);
-
- switch(padding) {
- case 0:
- padding = RSA_NO_PADDING;
- break;
- case 1:
- padding = RSA_PKCS1_PADDING;
- break;
- case 2:
- padding = RSA_PKCS1_OAEP_PADDING;
- break;
- case 3:
- padding = RSA_SSLV23_PADDING;
- break;
- default:
- return -1;
- }
-
- rsa = RSA_new();
- rsa->e = rsa_e;
- rsa->n = rsa_n;
- rsa->d = rsa_d;
-
- dlen = RSA_size(rsa) + 1;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- i = 4;
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,data_len);
- if(command == DRV_RSA_PRIVATE_DECRYPT) {
- i = RSA_private_decrypt(data_len, (unsigned char *) (buf+i),
- (unsigned char *) &bin[1],
- rsa, padding);
- if(i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(&bin[1],i);
- bin = return_binary_shrink(rbuf,rlen, bin, i+1);
- if (bin==NULL) return -1;
- }
- } else {
- i = RSA_private_encrypt(data_len, (unsigned char *) (buf+i),
- (unsigned char *) &bin[1],
- rsa, padding);
- if(i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(&bin[1],i);
- }
- }
- RSA_free(rsa);
- if(i > 0) {
- bin[0] = 1;
- return i + 1;
- } else {
- bin[0] = 0;
- return 1;
- }
- break;
-
- case DRV_RSA_PUBLIC_ENCRYPT:
- case DRV_RSA_PUBLIC_DECRYPT:
- /* buf = <<data_len:32/integer, data:data_len/binary,
- * rsa_e_len:32/integer, rsa_e:rsa_e_len/binary,
- * rsa_n_len:32/integer, rsa_n:rsa_n_len/binary,
- * pad:8/integer >> */
-
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf,len);
- i = 0;
- j = 0;
-
- if (len < 13)
- return -1;
- data_len = get_int32(buf + i + j);
- j += data_len; i += 4;
- if (len < (13 + j))
- return -1;
- rsa_e_len = get_int32(buf + i + j);
- j += rsa_e_len; i += 4;
- if (len < (13 + j))
- return -1;
- rsa_n_len = get_int32(buf + i + j);
- j += rsa_n_len; i += 4;
- if (len < (13 + j))
- return -1;
- padding = *(unsigned char *) (buf + i + j);
- if (len != (13 + j))
- return -1;
-
- i = 4;
- i += (data_len + 4);
- rsa_e = BN_new();
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,rsa_e_len);
- BN_bin2bn((unsigned char *)(buf + i), rsa_e_len, rsa_e);
- i += (rsa_e_len + 4);
- rsa_n = BN_new();
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,rsa_n_len);
- BN_bin2bn((unsigned char *)(buf + i), rsa_n_len, rsa_n);
- i += (rsa_n_len + 4);
-
- switch(padding) {
- case 0:
- padding = RSA_NO_PADDING;
- break;
- case 1:
- padding = RSA_PKCS1_PADDING;
- break;
- case 2:
- padding = RSA_PKCS1_OAEP_PADDING;
- break;
- case 3:
- padding = RSA_SSLV23_PADDING;
- break;
- default:
- return -1;
- }
-
- rsa = RSA_new();
- rsa->e = rsa_e;
- rsa->n = rsa_n;
-
- dlen = RSA_size(rsa) + 1;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- i = 4;
- if(command == DRV_RSA_PUBLIC_ENCRYPT) {
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf+i,data_len);
- i = RSA_public_encrypt(data_len, (unsigned char *) (buf+i),
- (unsigned char *) &bin[1],
- rsa, padding);
- if (i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+1, i);
- }
- } else {
- i = RSA_public_decrypt(data_len, (unsigned char *) (buf+i),
- (unsigned char *) &bin[1],
- rsa, padding);
- if(i > 0) {
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+1, i);
- bin = return_binary_shrink(rbuf,rlen,bin, i+1);
- if (bin==NULL) return -1;
- }
- }
-
- RSA_free(rsa);
- if(i > 0) {
- bin[0] = 1;
- return i + 1;
- } else {
-/* ERR_load_crypto_strings(); */
-/* fprintf(stderr, "%d: %s \r\n", __LINE__, ERR_reason_error_string(ERR_get_error())); */
- bin[0] = 0;
- return 1;
- }
- break;
-
- case DRV_CBC_AES128_ENCRYPT:
- case DRV_CBC_AES256_ENCRYPT:
- case DRV_CBC_AES128_DECRYPT:
- case DRV_CBC_AES256_DECRYPT:
- /* buf = key[klen] ivec[klen] data */
- if (command == DRV_CBC_AES256_ENCRYPT || command == DRV_CBC_AES256_DECRYPT)
- klen = 32;
- else
- klen = 16;
- dlen = len - klen - 16;
- if (dlen < 0)
- return -1;
- if (dlen % 16 != 0)
- return -1;
- if (command == DRV_CBC_AES128_ENCRYPT || command == DRV_CBC_AES256_ENCRYPT) {
- i = AES_ENCRYPT;
- AES_set_encrypt_key((unsigned char *) buf, klen*8, &aes_key);
- } else {
- i = AES_DECRYPT;
- AES_set_decrypt_key((unsigned char *) buf, klen*8, &aes_key);
- }
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- AES_cbc_encrypt((unsigned char *) (buf + klen+16),
- (unsigned char *) bin,
- dlen,
- &aes_key,
- (unsigned char *) (buf + klen),
- i);
- return dlen;
-
-/* case DRV_CBC_AES128_DECRYPT: */
-/* case DRV_CBC_AES256_DECRYPT: */
-/* /\* buf = key[klen] ivec[16] data *\/ */
-/* if (command == DRV_CBC_AES256_DECRYPT) */
-/* klen = 32; */
-/* else */
-/* klen = 16; */
-/* dlen = len - klen - 16; */
-/* if (dlen < 0) */
-/* return -1; */
-/* *rbuf = (char *)(bin = driver_alloc_binary(dlen)); */
-/* AES_set_decrypt_key((unsigned char *) buf, klen*8, &aes_key); */
-/* AES_cbc_encrypt((unsigned char *) (buf + klen+16), */
-/* (unsigned char *) bin->orig_bytes, */
-/* dlen, */
-/* &aes_key, */
-/* (unsigned char *) (buf + klen), */
-/* AES_DECRYPT); */
-/* return dlen; */
-/* break; */
-
- case DRV_XOR:
- /* buf = data1, data2 with same size */
- dlen = len / 2;
- if (len != dlen * 2)
- return -1;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- p = bin,
- dbuf = buf + dlen;
- for (key = buf, key2 = dbuf; key != dbuf; ++key, ++key2, ++p)
- *p = *key ^ *key2;
- return dlen;
-
- case DRV_DH_GENERATE_PARAMS:
- /* buf = <<PrimeLen:32 Generator:32>> */
- if (len != 8)
- return -1;
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf,len);
- prime_len = get_int32(buf);
- generator = get_int32(buf+4);
- dh_params = DH_generate_parameters(prime_len, generator, NULL, NULL);
-
- if(dh_params) {
- dh_p_len = BN_num_bytes(dh_params->p);
- dh_g_len = BN_num_bytes(dh_params->g);
- dlen = 1 + 4 + 4 + dh_g_len + dh_p_len;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- bin[0] = 1;
- put_int32(bin+1, dh_p_len);
- BN_bn2bin(dh_params->p, bin+5);
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+5,dh_p_len);
- put_int32(bin+5+dh_p_len, dh_g_len);
- BN_bn2bin(dh_params->g, bin+5+dh_p_len+4);
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+5+dh_p_len+4,dh_g_len);
- } else {
- dlen = 1;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- bin[0] = 0;
- }
- DH_free(dh_params);
- return dlen;
-
- case DRV_DH_CHECK:
- /* buf = <<dh_p_len:32/integer, dh_p:dh_p_len/binary,
- * dh_g_len:32/integer, dh_g:dh_g_len/binary>> */
- i = 0;
- j = 0;
- if(len < 8) return -1;
- dh_p_len = get_int32(buf + i + j);
- j += dh_p_len; i += 4;
- if (len < (8 + j)) return -1;
- dh_g_len = get_int32(buf + i + j);
- j += dh_g_len; i += 4;
- if(len != (8+j)) return -1;
- i=4;
- dh_p = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dh_p_len, dh_p);
- i += (dh_p_len + 4);
- dh_g = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dh_g_len, dh_g);
- /* i += (dsa_g_len + 4); */
-
- dh_params = DH_new();
- dh_params->p = dh_p;
- dh_params->g = dh_g;
-
- i=0;
- bin = return_binary(rbuf,rlen,4);
- if (bin==NULL) return -1;
- if(DH_check(dh_params, &i)) {
- put_int32(bin, i);
- } else {
- /* Check Failed */
- put_int32(bin, -1);
- }
- DH_free(dh_params);
- return 4;
-
- case DRV_DH_GENERATE_KEY:
- /* buf = <<key_len:32, key:key_len/binary, *
- * dh_p_len:32/integer, dh_p:dh_p_len/binary, *
- * dh_g_len:32/integer, dh_g:dh_g_len/binary>> */
- ERL_VALGRIND_ASSERT_MEM_DEFINED(buf,len);
- i = 0;
- j = 0;
- if(len < 12) return -1;
- base_len = get_int32(buf + i + j);
- j += base_len; i += 4;
- if (len < (12 + j)) return -1;
- dh_p_len = get_int32(buf + i + j);
- j += dh_p_len; i += 4;
- if (len < (12 + j)) return -1;
- dh_g_len = get_int32(buf + i + j);
- j += dh_g_len; i += 4;
- if(len != (12 + j)) return -1;
- i=4;
- i += (base_len + 4);
- dh_p = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dh_p_len, dh_p);
- i += (dh_p_len + 4);
- dh_g = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dh_g_len, dh_g);
- /* i += (dsa_g_len + 4); */
-
- dh_params = DH_new();
- dh_params->p = dh_p;
- dh_params->g = dh_g;
- if(base_len > 0) {
- dh_params->priv_key = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), base_len,
- dh_params->priv_key);
- }
- i=0;
- if(DH_generate_key(dh_params)) {
- privkey_len = BN_num_bytes(dh_params->priv_key);
- pubkey_len = BN_num_bytes(dh_params->pub_key);
- dlen = 1 + 4 + 4 + pubkey_len + privkey_len;
- bin = return_binary(rbuf,rlen, dlen);
- if (bin==NULL) return -1;
- bin[0] = 1;
- put_int32(bin+1, pubkey_len);
- BN_bn2bin(dh_params->pub_key, bin+5);
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+5, pubkey_len);
- put_int32(bin+5+pubkey_len, privkey_len);
- BN_bn2bin(dh_params->priv_key, bin+5+pubkey_len+4);
- ERL_VALGRIND_MAKE_MEM_DEFINED(bin+5+pubkey_len+4, privkey_len);
- } else {
- dlen = 1;
- bin = return_binary(rbuf,rlen,dlen);
- if (bin==NULL) return -1;
- bin[0] = 0;
- }
- DH_free(dh_params);
- return dlen;
-
- case DRV_DH_COMPUTE_KEY:
- /* buf = <<pubkey_len:32, pubkey:pubkey_len/binary, *
- * privkey_len:32, privkey:privkey_len/binary, *
- * dh_p_len:32/integer, dh_p:dh_p_len/binary, *
- * dh_g_len:32/integer, dh_g:dh_g_len/binary>> */
- i = 0;
- j = 0;
- if(len < 16) return -1;
- pubkey_len = get_int32(buf + i + j);
- j += pubkey_len; i += 4;
- if (len < (16 + j)) return -1;
- privkey_len = get_int32(buf + i + j);
- j += privkey_len; i += 4;
- if (len < (16 + j)) return -1;
- dh_p_len = get_int32(buf + i + j);
- j += dh_p_len; i += 4;
- if (len < (16 + j)) return -1;
- dh_g_len = get_int32(buf + i + j);
- j += dh_g_len; i += 4;
- if(len != (16 + j)) return -1;
- i=4;
- pubkey = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), pubkey_len, pubkey);
- i += (pubkey_len + 4);
- privkey = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), privkey_len, privkey);
- i += (privkey_len + 4);
- dh_p = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dh_p_len, dh_p);
- i += (dh_p_len + 4);
- dh_g = BN_new();
- BN_bin2bn((unsigned char *)(buf + i), dh_g_len, dh_g);
- /* i += (dsa_g_len + 4); */
-
- dh_params = DH_new();
- dh_params->p = dh_p;
- dh_params->g = dh_g;
- dh_params->priv_key = privkey;
-
- klen = DH_size(dh_params);
- bin = return_binary(rbuf,rlen,1+klen);
- if (bin==NULL) return -1;
- i = DH_compute_key(&bin[1], pubkey, dh_params);
- DH_free(dh_params);
- if (i > 0) {
- if (i != klen) {
- bin = return_binary_shrink(rbuf,rlen,bin,1+i);
- }
- bin[0] = 1;
- return i + 1;
- }
- else {
- bin[0] = 0;
- return 1;
- }
-
- case DRV_MD4:
- bin = return_binary(rbuf,rlen,MD4_LEN);
- MD4((unsigned char *)buf, len, (unsigned char *)bin);
- return MD4_LEN;
-
- case DRV_MD4_INIT:
- bin = return_binary(rbuf,rlen,MD4_CTX_LEN);
- MD4_Init((MD4_CTX *) bin);
- return MD4_CTX_LEN;
-
- case DRV_MD4_UPDATE:
- if (len < MD4_CTX_LEN)
- return -1;
- bin = return_binary(rbuf,rlen,MD4_CTX_LEN);
- memcpy(bin, buf, MD4_CTX_LEN);
- MD4_Update((MD4_CTX *) bin, buf + MD4_CTX_LEN, len - MD4_CTX_LEN);
- return MD4_CTX_LEN;
-
- case DRV_MD4_FINAL:
- if (len != MD4_CTX_LEN)
- return -1;
- memcpy(&md4_ctx, buf, MD4_CTX_LEN); /* XXX Use buf only? */
- bin = return_binary(rbuf,rlen,MD4_LEN);
- MD4_Final((unsigned char *)bin, &md4_ctx);
- return MD4_LEN;
-
-#if SSL_VERSION_0_9_8
- case DRV_SHA256:
- bin = return_binary(rbuf,rlen,SHA256_LEN);
- SHA256(buf, len, bin);
- return SHA256_LEN;
-
- case DRV_SHA256_INIT:
- bin = return_binary(rbuf,rlen,SHA256_CTX_LEN);
- SHA256_Init((SHA256_CTX *)bin);
- return SHA256_CTX_LEN;
-
- case DRV_SHA256_UPDATE:
- if (len < SHA256_CTX_LEN)
- return -1;
- bin = return_binary(rbuf,rlen,SHA256_CTX_LEN);
- memcpy(bin, buf, SHA256_CTX_LEN);
- SHA256_Update((SHA256_CTX *)bin, buf + SHA256_CTX_LEN,
- len - SHA256_CTX_LEN);
- return SHA256_CTX_LEN;
-
- case DRV_SHA256_FINAL:
- if (len != SHA256_CTX_LEN)
- return -1;
- memcpy(&sha256_ctx, buf, SHA256_CTX_LEN); /* XXX Use buf only? */
- bin = return_binary(rbuf,rlen,SHA256_LEN);
- SHA256_Final(bin, &sha256_ctx);
- return SHA256_LEN;
-
- case DRV_SHA512:
- bin = return_binary(rbuf,rlen,SHA512_LEN);
- SHA512(buf, len, bin);
- return SHA512_LEN;
-
- case DRV_SHA512_INIT:
- bin = return_binary(rbuf,rlen,SHA512_CTX_LEN);
- SHA512_Init((SHA512_CTX *)bin);
- return SHA512_CTX_LEN;
-
- case DRV_SHA512_UPDATE:
- if (len < SHA512_CTX_LEN)
- return -1;
- bin = return_binary(rbuf,rlen,SHA512_CTX_LEN);
- memcpy(bin, buf, SHA512_CTX_LEN);
- SHA512_Update((SHA512_CTX *)bin, buf + SHA512_CTX_LEN,
- len - SHA512_CTX_LEN);
- return SHA512_CTX_LEN;
-
- case DRV_SHA512_FINAL:
- if (len != SHA512_CTX_LEN)
- return -1;
- memcpy(&sha512_ctx, buf, SHA512_CTX_LEN); /* XXX Use buf only? */
- bin = return_binary(rbuf,rlen,SHA512_LEN));
- SHA512_Final(bin, &sha512_ctx);
- return SHA512_LEN;
-#endif
-
- case DRV_INFO_LIB:
- {/* <<DrvVer:8, NameSize:8, Name:NameSize/binary, VerNum:32, VerStr/binary>> */
- static const char libname[] = "OpenSSL";
- unsigned name_sz = strlen(libname);
- const char* ver = SSLeay_version(SSLEAY_VERSION);
- unsigned ver_sz = strlen(ver);
- dlen = 1+1+name_sz+4+ver_sz;
- bin = return_binary(rbuf, rlen, dlen);
- if (bin==NULL) return -1;
- p = bin;
- *p++ = 0; /* "driver version" for future use */
- *p++ = name_sz;
- memcpy(p, libname, name_sz);
- p += name_sz;
- put_int32(p,SSLeay()); /* OPENSSL_VERSION_NUMBER */
- p += 4;
- memcpy(p, ver, ver_sz);
- }
- return dlen;
-
- default:
- break;
- }
- return -1;
-}
-
-
-#ifdef OPENSSL_THREADS /* vvvvvvvvvvvvvvv OPENSSL_THREADS vvvvvvvvvvvvvvvv */
-
-static INLINE void locking(int mode, ErlDrvRWLock* lock)
-{
- switch(mode) {
- case CRYPTO_LOCK|CRYPTO_READ:
- erl_drv_rwlock_rlock(lock);
- break;
- case CRYPTO_LOCK|CRYPTO_WRITE:
- erl_drv_rwlock_rwlock(lock);
- break;
- case CRYPTO_UNLOCK|CRYPTO_READ:
- erl_drv_rwlock_runlock(lock);
- break;
- case CRYPTO_UNLOCK|CRYPTO_WRITE:
- erl_drv_rwlock_rwunlock(lock);
- break;
- default:
- ASSERT(!"Invalid lock mode");
- }
-}
-
-/* Callback from openssl for static locking
- */
-static void locking_function(int mode, int n, const char *file, int line)
-{
- ASSERT(n>=0 && n<CRYPTO_num_locks());
-
- locking(mode, lock_vec[n]);
-}
-
-/* Callback from openssl for thread id
- */
-static unsigned long id_function(void)
-{
- return (unsigned long) erl_drv_thread_self();
-}
-
-/* Callbacks for dynamic locking, not used by current openssl version (0.9.8)
- */
-static struct CRYPTO_dynlock_value* dyn_create_function(const char *file, int line)
-{
- return (struct CRYPTO_dynlock_value*) erl_drv_rwlock_create("crypto_drv_dyn");
-}
-static void dyn_lock_function(int mode, struct CRYPTO_dynlock_value* ptr,const char *file, int line)
-{
- locking(mode, (ErlDrvRWLock*)ptr);
-}
-static void dyn_destroy_function(struct CRYPTO_dynlock_value *ptr, const char *file, int line)
-{
- erl_drv_rwlock_destroy((ErlDrvRWLock*)ptr);
-}
-
-#endif /* ^^^^^^^^^^^^^^^^^^^^^^ OPENSSL_THREADS ^^^^^^^^^^^^^^^^^^^^^^ */
-
-/* HMAC */
-
-static void hmac_md5(char *key, int klen, char *dbuf, int dlen, char *hmacbuf)
-{
- MD5_CTX ctx;
- char ipad[HMAC_INT_LEN];
- char opad[HMAC_INT_LEN];
- unsigned char nkey[MD5_LEN];
- int i;
-
- /* Change key if longer than 64 bytes */
- if (klen > HMAC_INT_LEN) {
- MD5_CTX kctx;
-
- MD5_Init(&kctx);
- MD5_Update(&kctx, key, klen);
- MD5_Final(nkey, &kctx);
- key = (char *) nkey;
- klen = MD5_LEN;
- }
-
- memset(ipad, '\0', sizeof(ipad));
- memset(opad, '\0', sizeof(opad));
- memcpy(ipad, key, klen);
- memcpy(opad, key, klen);
-
- for (i = 0; i < HMAC_INT_LEN; i++) {
- ipad[i] ^= HMAC_IPAD;
- opad[i] ^= HMAC_OPAD;
- }
-
- /* inner MD5 */
- MD5_Init(&ctx);
- MD5_Update(&ctx, ipad, HMAC_INT_LEN);
- MD5_Update(&ctx, dbuf, dlen);
- MD5_Final((unsigned char *) hmacbuf, &ctx);
- /* outer MD5 */
- MD5_Init(&ctx);
- MD5_Update(&ctx, opad, HMAC_INT_LEN);
- MD5_Update(&ctx, hmacbuf, MD5_LEN);
- MD5_Final((unsigned char *) hmacbuf, &ctx);
-}
-
-static void hmac_sha1(char *key, int klen, char *dbuf, int dlen,
- char *hmacbuf)
-{
- SHA_CTX ctx;
- char ipad[HMAC_INT_LEN];
- char opad[HMAC_INT_LEN];
- unsigned char nkey[SHA_LEN];
- int i;
-
- /* Change key if longer than 64 bytes */
- if (klen > HMAC_INT_LEN) {
- SHA_CTX kctx;
-
- SHA1_Init(&kctx);
- SHA1_Update(&kctx, key, klen);
- SHA1_Final(nkey, &kctx);
- key = (char *) nkey;
- klen = SHA_LEN;
- }
-
- memset(ipad, '\0', sizeof(ipad));
- memset(opad, '\0', sizeof(opad));
- memcpy(ipad, key, klen);
- memcpy(opad, key, klen);
-
- for (i = 0; i < HMAC_INT_LEN; i++) {
- ipad[i] ^= HMAC_IPAD;
- opad[i] ^= HMAC_OPAD;
- }
-
- /* inner SHA */
- SHA1_Init(&ctx);
- SHA1_Update(&ctx, ipad, HMAC_INT_LEN);
- SHA1_Update(&ctx, dbuf, dlen);
- SHA1_Final((unsigned char *) hmacbuf, &ctx);
- /* outer SHA */
- SHA1_Init(&ctx);
- SHA1_Update(&ctx, opad, HMAC_INT_LEN);
- SHA1_Update(&ctx, hmacbuf, SHA_LEN);
- SHA1_Final((unsigned char *) hmacbuf, &ctx);
-}
diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml
index 256eab3e3c..e1431cfd81 100644
--- a/lib/crypto/doc/src/crypto.xml
+++ b/lib/crypto/doc/src/crypto.xml
@@ -755,39 +755,44 @@ Mpint() = <![CDATA[<<ByteLen:32/integer-big, Bytes:ByteLen/binary>>]]>
<func>
<name>dss_sign(Data, Key) -> Signature</name>
+ <name>dss_sign(DigestType, Data, Key) -> Signature</name>
<fsummary>Sign the data using dsa with given private key.</fsummary>
<type>
- <v>Digest = Mpint</v>
+ <v>DigestType = sha | none (default is sha)</v>
+ <v>Data = Mpint | ShaDigest</v>
<v>Key = [P, Q, G, X]</v>
<v>P, Q, G, X = Mpint</v>
<d> Where <c>P</c>, <c>Q</c> and <c>G</c> are the dss
parameters and <c>X</c> is the private key.</d>
- <v>Mpint = binary()</v>
+ <v>ShaDigest = binary() with length 20 bytes</v>
<v>Signature = binary()</v>
</type>
<desc>
- <p>Calculates the sha digest of the <c>Data</c>
- and creates a DSS signature with the private key <c>Key</c>
- of the digest.</p>
+ <p>Creates a DSS signature with the private key <c>Key</c> of a digest.
+ If <c>DigestType</c> is 'sha', the digest is calculated as SHA1 of <c>Data</c>.
+ If <c>DigestType</c> is 'none', <c>Data</c> is the precalculated SHA1 digest.</p>
</desc>
</func>
<func>
<name>dss_verify(Data, Signature, Key) -> Verified</name>
+ <name>dss_verify(DigestType, Data, Signature, Key) -> Verified</name>
<fsummary>Verify the data and signature using dsa with given public key.</fsummary>
<type>
<v>Verified = boolean()</v>
- <v>Digest, Signature = Mpint</v>
+ <v>DigestType = sha | none</v>
+ <v>Data = Mpint | ShaDigest</v>
+ <v>Signature = Mpint</v>
<v>Key = [P, Q, G, Y]</v>
<v>P, Q, G, Y = Mpint</v>
<d> Where <c>P</c>, <c>Q</c> and <c>G</c> are the dss
parameters and <c>Y</c> is the public key.</d>
- <v>Mpint = binary()</v>
+ <v>ShaDigest = binary() with length 20 bytes</v>
</type>
<desc>
- <p>Calculates the sha digest of the <c>Data</c> and verifies that the
- digest matches the DSS signature using the public key <c>Key</c>.
- </p>
+ <p>Verifies that a digest matches the DSS signature using the public key <c>Key</c>.
+ If <c>DigestType</c> is 'sha', the digest is calculated as SHA1 of <c>Data</c>.
+ If <c>DigestType</c> is 'none', <c>Data</c> is the precalculated SHA1 digest.</p>
</desc>
</func>
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index 6b9d1f56f1..79798331ca 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -30,6 +30,43 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 2.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ crypto application changed to use NIFs instead of driver.</p>
+ <p>
+ Own Id: OTP-8333</p>
+ </item>
+ <item>
+ <p>
+ des_ecb_encrypt/2 and des_ecb_decrypt/2 has been added to
+ the crypto module. The crypto:md4/1 function has been
+ documented.</p>
+ <p>
+ Own Id: OTP-8551</p>
+ </item>
+ <item>
+ <p>The undocumented, unsupport, and deprecated function
+ <c>lists:flat_length/1</c> has been removed.</p>
+ <p>
+ Own Id: OTP-8584</p>
+ </item>
+ <item>
+ <p>
+ New variants of <c>crypto:dss_sign</c> and
+ <c>crypto:dss_verify</c> with an extra argument to
+ control how the digest is calculated.</p>
+ <p>
+ Own Id: OTP-8700</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 1.6.4</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/crypto/priv/Makefile b/lib/crypto/priv/Makefile
index b8acdacc00..0989f14c94 100644
--- a/lib/crypto/priv/Makefile
+++ b/lib/crypto/priv/Makefile
@@ -18,21 +18,21 @@
# ----------------------------------------------------
# THIS MAKEFILE SERVES AS AN EXAMPLE OF
-# HOW TO RELINK THE CRYPTO DRIVER
+# HOW TO RELINK THE CRYPTO NIF LIBRARY
# ----------------------------------------------------
# ----------------------------------------------------
-# Variables for linking a .so driver on unix.
+# Variables for linking a .so library on unix.
# Note: These may differ between systems.
# ----------------------------------------------------
SO_LD = gcc
SO_LDFLAGS = -G
SO_SSL_LIBDIR = /usr/local/lib
-SO_DRIVER = $(LIBDIR)/$(DRIVER_NAME).so
+SO_NIFLIB = $(LIBDIR)/$(LIB_NAME).so
# ----------------------------------------------------
-# Variables for linking a win32 .dll driver.
+# Variables for linking a win32 .dll library.
# Note: These may differ between systems.
# ----------------------------------------------------
@@ -42,9 +42,9 @@ DLL_LIBDIR = "c:\\OpenSSL\\lib\\VC"
DLL_LIBS = libeay32.lib MSVCRT.LIB kernel32.lib \
advapi32.lib gdi32.lib user32.lib \
comctl32.lib comdlg32.lib shell32.lib
-DLL_DRIVER = $(LIBDIR)/$(DRIVER_NAME).dll
-DLL_EXP = $(LIBDIR)/$(DRIVER_NAME).exp
-DLL_LIB = $(LIBDIR)/$(DRIVER_NAME).lib
+DLL_NIFLIB = $(LIBDIR)/$(LIB_NAME).dll
+DLL_EXP = $(LIBDIR)/$(LIB_NAME).exp
+DLL_LIB = $(LIBDIR)/$(LIB_NAME).lib
# ----------------------------------------------------
# Common variables
@@ -52,27 +52,27 @@ DLL_LIB = $(LIBDIR)/$(DRIVER_NAME).lib
OBJDIR = ./
LIBDIR = ../lib
-DRIVER_NAME = crypto_drv
-OBJS = $(OBJDIR)/crypto_drv.o
+LIB_NAME = crypto
+OBJS = $(OBJDIR)/crypto.o
# ----------------------------------------------------
# Targets
# ----------------------------------------------------
-$(SO_DRIVER): $(OBJS)
+$(SO_NIFLIB): $(OBJS)
$(SO_LD) $(SO_LDFLAGS) -L$(SO_SSL_LIBDIR) -Wl,-R$(SO_SSL_LIBDIR) \
-o $@ $^ -lcrypto
-$(DLL_DRIVER): $(OBJS)
+$(DLL_NIFLIB): $(OBJS)
$(DLL_LD) $(DLL_LDFLAGS) -out:$@ -libpath:$(DLL_LIBDIR) $(OBJS) \
$(DLL_LIBS)
-so: $(SO_DRIVER)
+so: $(SO_NIFLIB)
-dll: $(DLL_DRIVER)
+dll: $(DLL_NIFLIB)
clean:
- rm -f $(SO_DRIVER) $(DLL_DRIVER)
+ rm -f $(SO_NIFLIB) $(DLL_NIFLIB)
rm -f $(DLL_EXP) $(DLL_LIB)
rm -f core *~
diff --git a/lib/crypto/src/Makefile b/lib/crypto/src/Makefile
index 51092d16a5..0e886ce8bf 100644
--- a/lib/crypto/src/Makefile
+++ b/lib/crypto/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1999-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1999-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
@@ -57,7 +57,7 @@ APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-ERL_COMPILE_FLAGS += +warn_obsolete_guard
+ERL_COMPILE_FLAGS += +warn_obsolete_guard -DCRYPTO_VSN=\"$(VSN)\"
# ----------------------------------------------------
# Targets
diff --git a/lib/crypto/src/crypto.app.src b/lib/crypto/src/crypto.app.src
index a24760a781..5548b6a1b5 100644
--- a/lib/crypto/src/crypto.app.src
+++ b/lib/crypto/src/crypto.app.src
@@ -1,23 +1,23 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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%
%%
{application, crypto,
- [{description, "CRYPTO version 1"},
+ [{description, "CRYPTO version 2"},
{vsn, "%VSN%"},
{modules, [crypto,
crypto_app,
diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl
index 8cb750759f..39512d27e1 100644
--- a/lib/crypto/src/crypto.erl
+++ b/lib/crypto/src/crypto.erl
@@ -21,7 +21,7 @@
-module(crypto).
--export([start/0, stop/0, info/0, info_lib/0]).
+-export([start/0, stop/0, info/0, info_lib/0, version/0]).
-export([md4/1, md4_init/0, md4_update/2, md4_final/1]).
-export([md5/1, md5_init/0, md5_update/2, md5_final/1]).
-export([sha/1, sha_init/0, sha_update/2, sha_final/1]).
@@ -40,8 +40,8 @@
-export([exor/2]).
-export([rc4_encrypt/2, rc4_set_key/1, rc4_encrypt_with_state/2]).
-export([rc2_40_cbc_encrypt/3, rc2_40_cbc_decrypt/3]).
--export([dss_verify/3, rsa_verify/3, rsa_verify/4]).
--export([dss_sign/2, rsa_sign/2, rsa_sign/3]).
+-export([dss_verify/3, dss_verify/4, rsa_verify/3, rsa_verify/4]).
+-export([dss_sign/2, dss_sign/3, rsa_sign/2, rsa_sign/3]).
-export([rsa_public_encrypt/3, rsa_private_decrypt/3]).
-export([rsa_private_encrypt/3, rsa_public_decrypt/3]).
-export([dh_generate_key/1, dh_generate_key/2, dh_compute_key/3]).
@@ -54,82 +54,6 @@
-export([dh_generate_parameters/2, dh_check/1]). %% Testing see below
--define(INFO, 0).
--define(MD5, 1).
--define(MD5_INIT, 2).
--define(MD5_UPDATE, 3).
--define(MD5_FINAL, 4).
--define(SHA, 5).
--define(SHA_INIT, 6).
--define(SHA_UPDATE, 7).
--define(SHA_FINAL, 8).
--define(MD5_MAC, 9).
--define(MD5_MAC_96, 10).
--define(SHA_MAC, 11).
--define(SHA_MAC_96, 12).
--define(DES_CBC_ENCRYPT, 13).
--define(DES_CBC_DECRYPT, 14).
--define(DES_EDE3_CBC_ENCRYPT, 15).
--define(DES_EDE3_CBC_DECRYPT, 16).
--define(AES_CFB_128_ENCRYPT, 17).
--define(AES_CFB_128_DECRYPT, 18).
--define(RAND_BYTES, 19).
--define(RAND_UNIFORM, 20).
--define(MOD_EXP, 21).
--define(DSS_VERIFY, 22).
--define(RSA_VERIFY_SHA, 23).
-%-define(RSA_VERIFY_MD5, 35).
--define(AES_CBC_128_ENCRYPT, 24).
--define(AES_CBC_128_DECRYPT, 25).
--define(XOR, 26).
--define(RC4_ENCRYPT, 27).
--define(RC4_SET_KEY, 28).
--define(RC4_ENCRYPT_WITH_STATE, 29).
--define(RC2_40_CBC_ENCRYPT, 30).
--define(RC2_40_CBC_DECRYPT, 31).
--define(AES_CBC_256_ENCRYPT, 32).
--define(AES_CBC_256_DECRYPT, 33).
--define(INFO_LIB,34).
-%-define(RSA_VERIFY_SHA, 23).
--define(RSA_VERIFY_MD5, 35).
--define(RSA_SIGN_SHA, 36).
--define(RSA_SIGN_MD5, 37).
--define(DSS_SIGN, 38).
--define(RSA_PUBLIC_ENCRYPT, 39).
--define(RSA_PRIVATE_DECRYPT, 40).
--define(RSA_PRIVATE_ENCRYPT, 41).
--define(RSA_PUBLIC_DECRYPT, 42).
--define(DH_GENERATE_PARAMS, 43).
--define(DH_CHECK, 44).
--define(DH_GENERATE_KEY, 45).
--define(DH_COMPUTE_KEY, 46).
--define(MD4, 47).
--define(MD4_INIT, 48).
--define(MD4_UPDATE, 49).
--define(MD4_FINAL, 50).
-
-%% -define(SHA256, 51).
-%% -define(SHA256_INIT, 52).
-%% -define(SHA256_UPDATE, 53).
-%% -define(SHA256_FINAL, 54).
-%% -define(SHA512, 55).
-%% -define(SHA512_INIT, 56).
-%% -define(SHA512_UPDATE, 57).
-%% -define(SHA512_FINAL, 58).
-
--define(BF_CFB64_ENCRYPT, 59).
--define(BF_CFB64_DECRYPT, 60).
--define(BF_ECB_ENCRYPT, 61).
--define(BF_ECB_DECRYPT, 62).
--define(BF_OFB64_ENCRYPT, 63).
--define(BF_CBC_ENCRYPT, 64).
--define(BF_CBC_DECRYPT, 65).
-
--define(DES_ECB_ENCRYPT, 66).
--define(DES_ECB_DECRYPT, 67).
-
-%% -define(IDEA_CBC_ENCRYPT, 34).
-%% -define(IDEA_CBC_DECRYPT, 35).
-define(FUNC_LIST, [md4, md4_init, md4_update, md4_final,
md5, md5_init, md5_update, md5_final,
@@ -158,6 +82,48 @@
aes_cbc_256_encrypt, aes_cbc_256_decrypt,
info_lib]).
+-type rsa_digest_type() :: 'md5' | 'sha'.
+-type dss_digest_type() :: 'none' | 'sha'.
+-type crypto_integer() :: binary() | integer().
+
+-define(nif_stub,nif_stub_error(?LINE)).
+
+-on_load(on_load/0).
+
+-define(CRYPTO_NIF_VSN,101).
+
+on_load() ->
+ LibName = "crypto",
+ PrivDir = code:priv_dir(crypto),
+ Lib1 = filename:join([PrivDir, "lib", LibName]),
+ Status = case erlang:load_nif(Lib1, ?CRYPTO_NIF_VSN) of
+ ok -> ok;
+ {error, {load_failed, _}}=Error1 ->
+ LibDir2 =
+ filename:join([PrivDir, "lib",
+ erlang:system_info(system_architecture)]),
+ Candidate =
+ filelib:wildcard(filename:join([LibDir2,LibName ++ "*" ])),
+ case Candidate of
+ [] -> Error1;
+ _ ->
+ Lib2 = filename:join([LibDir2, LibName]),
+ erlang:load_nif(Lib2, ?CRYPTO_NIF_VSN)
+ end;
+ Error1 -> Error1
+ end,
+ case Status of
+ ok -> ok;
+ {error, {E, Str}} ->
+ error_logger:error_msg("Unable to load crypto library. Failed with error:~n\"~p, ~s\"~n"
+ "OpenSSL might not be installed on this system.~n",[E,Str]),
+ Status
+ end.
+
+
+nif_stub_error(Line) ->
+ erlang:nif_error({nif_not_loaded,module,?MODULE,line,Line}).
+
start() ->
application:start(crypto).
@@ -165,15 +131,15 @@ stop() ->
application:stop(crypto).
info() ->
- lists:map(fun(I) ->
- lists:nth(I, ?FUNC_LIST)
- end, binary_to_list(control(?INFO, []))).
+ ?FUNC_LIST.
-info_lib() ->
- <<_DrvVer:8, NameSize:8, Name:NameSize/binary,
- VerNum:32, VerStr/binary>> = control(?INFO_LIB,[]),
- [{Name,VerNum,VerStr}].
+info_lib() -> ?nif_stub.
+%% Crypto app version history:
+%% (no version): Driver implementation
+%% 2.0 : NIF implementation, requires OTP R14
+version() -> ?CRYPTO_VSN.
+
%% Below Key and Data are binaries or IO-lists. IVec is a binary.
%% Output is always a binary. Context is a binary.
@@ -184,73 +150,43 @@ info_lib() ->
%%
%% MD5
%%
-md5(Data) ->
- control(?MD5, Data).
-md5_init() ->
- control(?MD5_INIT, []).
+-spec md5(iodata()) -> binary().
+-spec md5_init() -> binary().
+-spec md5_update(binary(), iodata()) -> binary().
+-spec md5_final(binary()) -> binary().
-md5_update(Context, Data) ->
- control(?MD5_UPDATE, [Context, Data]).
-
-md5_final(Context) ->
- control(?MD5_FINAL, Context).
+md5(_Data) -> ?nif_stub.
+md5_init() -> ?nif_stub.
+md5_update(_Context, _Data) -> ?nif_stub.
+md5_final(_Context) -> ?nif_stub.
%%
%% MD4
%%
-md4(Data) ->
- control(?MD4, Data).
-
-md4_init() ->
- control(?MD4_INIT, []).
+-spec md4(iodata()) -> binary().
+-spec md4_init() -> binary().
+-spec md4_update(binary(), iodata()) -> binary().
+-spec md4_final(binary()) -> binary().
-md4_update(Context, Data) ->
- control(?MD4_UPDATE, [Context, Data]).
-
-md4_final(Context) ->
- control(?MD4_FINAL, Context).
+md4(_Data) -> ?nif_stub.
+md4_init() -> ?nif_stub.
+md4_update(_Context, _Data) -> ?nif_stub.
+md4_final(_Context) -> ?nif_stub.
%%
%% SHA
%%
-sha(Data) ->
- control(?SHA, Data).
-
-sha_init() ->
- control(?SHA_INIT, []).
-
-sha_update(Context, Data) ->
- control(?SHA_UPDATE, [Context, Data]).
-
-sha_final(Context) ->
- control(?SHA_FINAL, Context).
-
-%% sha256 and sha512 requires openssl-0.9.8 removed for now
-
-%% sha256(Data) ->
-%% control(?SHA256, Data).
-
-%% sha256_init() ->
-%% control(?SHA256_INIT, []).
-
-%% sha256_update(Context, Data) ->
-%% control(?SHA256_UPDATE, [Context, Data]).
-
-%% sha256_final(Context) ->
-%% control(?SHA256_FINAL, Context).
+-spec sha(iodata()) -> binary().
+-spec sha_init() -> binary().
+-spec sha_update(binary(), iodata()) -> binary().
+-spec sha_final(binary()) -> binary().
-%% sha512(Data) ->
-%% control(?SHA512, Data).
+sha(_Data) -> ?nif_stub.
+sha_init() -> ?nif_stub.
+sha_update(_Context, _Data) -> ?nif_stub.
+sha_final(_Context) -> ?nif_stub.
-%% sha512_init() ->
-%% control(?SHA512_INIT, []).
-
-%% sha512_update(Context, Data) ->
-%% control(?SHA512_UPDATE, [Context, Data]).
-
-%% sha512_final(Context) ->
-%% control(?SHA512_FINAL, Context).
%%
%% MESSAGE AUTHENTICATION CODES
@@ -259,21 +195,31 @@ sha_final(Context) ->
%%
%% MD5_MAC
%%
+-spec md5_mac(iodata(), iodata()) -> binary.
+-spec md5_mac_96(iodata(), iodata()) -> binary.
+
md5_mac(Key, Data) ->
- control_bin(?MD5_MAC, Key, Data).
+ md5_mac_n(Key,Data,16).
md5_mac_96(Key, Data) ->
- control_bin(?MD5_MAC_96, Key, Data).
+ md5_mac_n(Key,Data,12).
+md5_mac_n(_Key,_Data,_MacSz) -> ?nif_stub.
+
%%
%% SHA_MAC
%%
+-spec sha_mac(iodata(), iodata()) -> binary.
+-spec sha_mac_96(iodata(), iodata()) -> binary.
+
sha_mac(Key, Data) ->
- control_bin(?SHA_MAC, Key, Data).
+ sha_mac_n(Key,Data,20).
sha_mac_96(Key, Data) ->
- control_bin(?SHA_MAC_96, Key, Data).
+ sha_mac_n(Key,Data,12).
+sha_mac_n(_Key,_Data,_MacSz) -> ?nif_stub.
+
%%
%% CRYPTO FUNCTIONS
%%
@@ -281,11 +227,16 @@ sha_mac_96(Key, Data) ->
%%
%% DES - in cipher block chaining mode (CBC)
%%
+-spec des_cbc_encrypt(iodata(), binary(), iodata()) -> binary().
+-spec des_cbc_decrypt(iodata(), binary(), iodata()) -> binary().
+
des_cbc_encrypt(Key, IVec, Data) ->
- control(?DES_CBC_ENCRYPT, [Key, IVec, Data]).
+ des_cbc_crypt(Key, IVec, Data, true).
des_cbc_decrypt(Key, IVec, Data) ->
- control(?DES_CBC_DECRYPT, [Key, IVec, Data]).
+ des_cbc_crypt(Key, IVec, Data, false).
+
+des_cbc_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
%%
%% dec_cbc_ivec(Data) -> binary()
@@ -293,6 +244,8 @@ des_cbc_decrypt(Key, IVec, Data) ->
%% Returns the IVec to be used in the next iteration of
%% des_cbc_[encrypt|decrypt].
%%
+-spec des_cbc_ivec(iodata()) -> binary().
+
des_cbc_ivec(Data) when is_binary(Data) ->
{_, IVec} = split_binary(Data, size(Data) - 8),
IVec;
@@ -302,83 +255,99 @@ des_cbc_ivec(Data) when is_list(Data) ->
%%
%% DES - in electronic codebook mode (ECB)
%%
-des_ecb_encrypt(Key, Data) ->
- control(?DES_ECB_ENCRYPT, [Key, Data]).
+-spec des_ecb_encrypt(iodata(), iodata()) -> binary().
+-spec des_ecb_decrypt(iodata(), iodata()) -> binary().
+des_ecb_encrypt(Key, Data) ->
+ des_ecb_crypt(Key, Data, true).
des_ecb_decrypt(Key, Data) ->
- control(?DES_ECB_DECRYPT, [Key, Data]).
+ des_ecb_crypt(Key, Data, false).
+des_ecb_crypt(_Key, _Data, _IsEncrypt) -> ?nif_stub.
%%
%% DES3 - in cipher block chaining mode (CBC)
%%
+-spec des3_cbc_encrypt(iodata(), iodata(), iodata(), binary(), iodata()) ->
+ binary().
+-spec des3_cbc_decrypt(iodata(), iodata(), iodata(), binary(), iodata()) ->
+ binary().
+
des3_cbc_encrypt(Key1, Key2, Key3, IVec, Data) ->
des_ede3_cbc_encrypt(Key1, Key2, Key3, IVec, Data).
des_ede3_cbc_encrypt(Key1, Key2, Key3, IVec, Data) ->
- %%io:format("des_ede3_cbc_encrypt: size(Data)=~p\n", [size(list_to_binary([Data]))]),
- control(?DES_EDE3_CBC_ENCRYPT, [Key1, Key2, Key3, IVec, Data]).
+ des_ede3_cbc_crypt(Key1, Key2, Key3, IVec, Data, true).
des3_cbc_decrypt(Key1, Key2, Key3, IVec, Data) ->
des_ede3_cbc_decrypt(Key1, Key2, Key3, IVec, Data).
des_ede3_cbc_decrypt(Key1, Key2, Key3, IVec, Data) ->
- control(?DES_EDE3_CBC_DECRYPT, [Key1, Key2, Key3, IVec, Data]).
+ des_ede3_cbc_crypt(Key1, Key2, Key3, IVec, Data, false).
+
+des_ede3_cbc_crypt(_Key1, _Key2, _Key3, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
%%
%% Blowfish
%%
-blowfish_ecb_encrypt(Key, Data) when byte_size(Data) >= 8 ->
- control_bin(?BF_ECB_ENCRYPT, Key, list_to_binary([Data])).
+-spec blowfish_ecb_encrypt(iodata(), iodata()) -> binary().
+-spec blowfish_ecb_decrypt(iodata(), iodata()) -> binary().
+-spec blowfish_cbc_encrypt(iodata(), binary(), iodata()) -> binary().
+-spec blowfish_cbc_decrypt(iodata(), binary(), iodata()) -> binary().
+-spec blowfish_cfb64_encrypt(iodata(), binary(), iodata()) -> binary().
+-spec blowfish_cfb64_decrypt(iodata(), binary(), iodata()) -> binary().
+-spec blowfish_ofb64_encrypt(iodata(), binary(), iodata()) -> binary().
+
+blowfish_ecb_encrypt(Key, Data) ->
+ bf_ecb_crypt(Key,Data, true).
-blowfish_ecb_decrypt(Key, Data) when byte_size(Data) >= 8 ->
- control_bin(?BF_ECB_DECRYPT, Key, list_to_binary([Data])).
+blowfish_ecb_decrypt(Key, Data) ->
+ bf_ecb_crypt(Key,Data, false).
-blowfish_cbc_encrypt(Key, IVec, Data) when byte_size(Data) rem 8 =:= 0 ->
- control_bin(?BF_CBC_ENCRYPT, Key, list_to_binary([IVec, Data])).
+bf_ecb_crypt(_Key,_Data,_IsEncrypt) -> ?nif_stub.
-blowfish_cbc_decrypt(Key, IVec, Data) when byte_size(Data) rem 8 =:= 0 ->
- control_bin(?BF_CBC_DECRYPT, Key, list_to_binary([IVec, Data])).
+blowfish_cbc_encrypt(Key, IVec, Data) ->
+ bf_cbc_crypt(Key,IVec,Data,true).
-blowfish_cfb64_encrypt(Key, IVec, Data) when byte_size(IVec) =:= 8 ->
- control_bin(?BF_CFB64_ENCRYPT, Key, list_to_binary([IVec, Data])).
+blowfish_cbc_decrypt(Key, IVec, Data) ->
+ bf_cbc_crypt(Key,IVec,Data,false).
-blowfish_cfb64_decrypt(Key, IVec, Data) when byte_size(IVec) =:= 8 ->
- control_bin(?BF_CFB64_DECRYPT, Key, list_to_binary([IVec, Data])).
+bf_cbc_crypt(_Key,_IVec,_Data,_IsEncrypt) -> ?nif_stub.
-blowfish_ofb64_encrypt(Key, IVec, Data) when byte_size(IVec) =:= 8 ->
- control_bin(?BF_OFB64_ENCRYPT, Key, list_to_binary([IVec, Data])).
+blowfish_cfb64_encrypt(Key, IVec, Data) ->
+ bf_cfb64_crypt(Key, IVec, Data, true).
+
+blowfish_cfb64_decrypt(Key, IVec, Data) ->
+ bf_cfb64_crypt(Key, IVec, Data, false).
+
+bf_cfb64_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
+
+blowfish_ofb64_encrypt(_Key, _IVec, _Data) -> ?nif_stub.
%%
%% AES in cipher feedback mode (CFB)
%%
+-spec aes_cfb_128_encrypt(iodata(), binary(), iodata()) -> binary().
+-spec aes_cfb_128_decrypt(iodata(), binary(), iodata()) -> binary().
+
aes_cfb_128_encrypt(Key, IVec, Data) ->
- control(?AES_CFB_128_ENCRYPT, [Key, IVec, Data]).
+ aes_cfb_128_crypt(Key, IVec, Data, true).
aes_cfb_128_decrypt(Key, IVec, Data) ->
- control(?AES_CFB_128_DECRYPT, [Key, IVec, Data]).
+ aes_cfb_128_crypt(Key, IVec, Data, false).
-
-%% %%
-%% %% IDEA - in cipher block chaining mode (CBC)
-%% %%
-%% idea_cbc_encrypt(Key, IVec, Data) ->
-%% control(?IDEA_CBC_ENCRYPT, [Key, IVec, Data]).
-
-%% idea_cbc_decrypt(Key, IVec, Data) ->
-%% control(?IDEA_CBC_DECRYPT, [Key, IVec, Data]).
+aes_cfb_128_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
%%
%% RAND - pseudo random numbers using RN_ functions in crypto lib
%%
+-spec rand_bytes(non_neg_integer()) -> binary().
+-spec rand_uniform(crypto_integer(), crypto_integer()) ->
+ crypto_integer().
-rand_bytes(Bytes) ->
- rand_bytes(Bytes, 0, 0).
-rand_bytes(Bytes, Topmask, Bottommask) ->
- control(?RAND_BYTES,[<<Bytes:32/integer,
- Topmask:8/integer,
- Bottommask:8/integer>>]).
+rand_bytes(_Bytes) -> ?nif_stub.
+rand_bytes(_Bytes, _Topmask, _Bottommask) -> ?nif_stub.
rand_uniform(From,To) when is_binary(From), is_binary(To) ->
- case control(?RAND_UNIFORM,[From,To]) of
+ case rand_uniform_nif(From,To) of
<<Len:32/integer, MSB, Rest/binary>> when MSB > 127 ->
<<(Len + 1):32/integer, 0, MSB, Rest/binary>>;
Whatever ->
@@ -394,6 +363,8 @@ rand_uniform(From,To) when is_integer(From),is_integer(To) ->
Other
end.
+rand_uniform_nif(_From,_To) -> ?nif_stub.
+
%%
%% mod_exp - utility for rsa generation
%%
@@ -402,121 +373,142 @@ mod_exp(Base, Exponent, Modulo)
erlint(mod_exp(mpint(Base), mpint(Exponent), mpint(Modulo)));
mod_exp(Base, Exponent, Modulo) ->
- case control(?MOD_EXP,[Base,Exponent,Modulo]) of
+ case mod_exp_nif(Base,Exponent,Modulo) of
<<Len:32/integer, MSB, Rest/binary>> when MSB > 127 ->
<<(Len + 1):32/integer, 0, MSB, Rest/binary>>;
Whatever ->
Whatever
end.
+mod_exp_nif(_Base,_Exp,_Mod) -> ?nif_stub.
+
%%
%% DSS, RSA - verify
%%
+-spec dss_verify(binary(), binary(), [binary()]) -> boolean().
+-spec dss_verify(dss_digest_type(), binary(), binary(), [binary()]) -> boolean().
+-spec rsa_verify(binary(), binary(), [binary()]) -> boolean().
+-spec rsa_verify(rsa_digest_type(), binary(), binary(), [binary()]) ->
+ boolean().
%% Key = [P,Q,G,Y] P,Q,G=DSSParams Y=PublicKey
dss_verify(Data,Signature,Key) ->
- control(?DSS_VERIFY, [Data,Signature,Key]) == <<1>>.
+ dss_verify(sha, Data, Signature, Key).
+dss_verify(_Type,_Data,_Signature,_Key) -> ?nif_stub.
% Key = [E,N] E=PublicExponent N=PublicModulus
rsa_verify(Data,Signature,Key) ->
rsa_verify(sha, Data,Signature,Key).
-rsa_verify(Type,Data,Signature,Key) ->
- control(rsa_verify_digest_type(Type), [Data,Signature,Key]) == <<1>>.
+rsa_verify(_Type,_Data,_Signature,_Key) -> ?nif_stub.
-rsa_verify_digest_type(md5) -> ?RSA_VERIFY_MD5;
-rsa_verify_digest_type(sha) -> ?RSA_VERIFY_SHA;
-rsa_verify_digest_type(Bad) -> erlang:error(badarg, [Bad]).
%%
%% DSS, RSA - sign
%%
%% Key = [P,Q,G,X] P,Q,G=DSSParams X=PrivateKey
-dss_sign(Data, Key) ->
- <<Ret:8, Signature/binary>> = control(?DSS_SIGN, [Data,Key]),
- case Ret of
- 1 -> Signature;
- 0 -> erlang:error(badkey, [Data, Key])
+-spec dss_sign(binary(), [binary()]) -> binary().
+-spec dss_sign(dss_digest_type(), binary(), [binary()]) -> binary().
+-spec rsa_sign(binary(), [binary()]) -> binary().
+-spec rsa_sign(rsa_digest_type(), binary(), [binary()]) -> binary().
+
+dss_sign(Data,Key) ->
+ dss_sign(sha,Data,Key).
+dss_sign(Type, Data, Key) ->
+ case dss_sign_nif(Type,Data,Key) of
+ error -> erlang:error(badkey, [Data, Key]);
+ Sign -> Sign
end.
+dss_sign_nif(_Type,_Data,_Key) -> ?nif_stub.
+
%% Key = [E,N,D] E=PublicExponent N=PublicModulus D=PrivateExponent
rsa_sign(Data,Key) ->
rsa_sign(sha, Data, Key).
rsa_sign(Type, Data, Key) ->
- <<Ret:8, Signature/binary>> = control(rsa_sign_digest_type(Type), [Data,Key]),
- case Ret of
- 1 -> Signature;
- 0 -> erlang:error(badkey, [Type,Data,Key])
+ case rsa_sign_nif(Type,Data,Key) of
+ error -> erlang:error(badkey, [Type,Data,Key]);
+ Sign -> Sign
end.
-rsa_sign_digest_type(md5) -> ?RSA_SIGN_MD5;
-rsa_sign_digest_type(sha) -> ?RSA_SIGN_SHA;
-rsa_sign_digest_type(Bad) -> erlang:error(badarg, [Bad]).
+rsa_sign_nif(_Type,_Data,_Key) -> ?nif_stub.
+
%%
%% rsa_public_encrypt
%% rsa_private_decrypt
+-type rsa_padding() :: 'rsa_pkcs1_padding' | 'rsa_pkcs1_oaep_padding' | 'rsa_no_padding'.
+
+-spec rsa_public_encrypt(binary(), [binary()], rsa_padding()) ->
+ binary().
+-spec rsa_public_decrypt(binary(), [binary()], rsa_padding()) ->
+ binary().
+-spec rsa_private_encrypt(binary(), [binary()], rsa_padding()) ->
+ binary().
+-spec rsa_private_decrypt(binary(), [binary()], rsa_padding()) ->
+ binary().
%% Binary, Key = [E,N]
rsa_public_encrypt(BinMesg, Key, Padding) ->
- Size = iolist_size(BinMesg),
- <<Ret:8, Signature/binary>> =
- control(?RSA_PUBLIC_ENCRYPT, [<<Size:32>>,BinMesg,Key,rsa_pad(Padding)]),
- case Ret of
- 1 -> Signature;
- 0 -> erlang:error(encrypt_failed, [BinMesg,Key, Padding])
- end.
+ case rsa_public_crypt(BinMesg, Key, Padding, true) of
+ error ->
+ erlang:error(encrypt_failed, [BinMesg,Key, Padding]);
+ Sign -> Sign
+ end.
+
+rsa_public_crypt(_BinMsg, _Key, _Padding, _IsEncrypt) -> ?nif_stub.
%% Binary, Key = [E,N,D]
rsa_private_decrypt(BinMesg, Key, Padding) ->
- Size = iolist_size(BinMesg),
- <<Ret:8, Signature/binary>> =
- control(?RSA_PRIVATE_DECRYPT, [<<Size:32>>,BinMesg,Key,rsa_pad(Padding)]),
- case Ret of
- 1 -> Signature;
- 0 -> erlang:error(decrypt_failed, [BinMesg,Key, Padding])
- end.
-
-rsa_pad(rsa_pkcs1_padding) -> 1;
-rsa_pad(rsa_pkcs1_oaep_padding) -> 2;
-%% rsa_pad(rsa_sslv23_padding) -> 3;
-rsa_pad(rsa_no_padding) -> 0;
-rsa_pad(Bad) -> erlang:error(badarg, [Bad]).
+ case rsa_private_crypt(BinMesg, Key, Padding, false) of
+ error ->
+ erlang:error(decrypt_failed, [BinMesg,Key, Padding]);
+ Sign -> Sign
+ end.
+
+rsa_private_crypt(_BinMsg, _Key, _Padding, _IsEncrypt) -> ?nif_stub.
+
%% Binary, Key = [E,N,D]
rsa_private_encrypt(BinMesg, Key, Padding) ->
- Size = iolist_size(BinMesg),
- <<Ret:8, Signature/binary>> =
- control(?RSA_PRIVATE_ENCRYPT, [<<Size:32>>,BinMesg,Key,rsa_pad(Padding)]),
- case Ret of
- 1 -> Signature;
- 0 -> erlang:error(encrypt_failed, [BinMesg,Key, Padding])
- end.
+ case rsa_private_crypt(BinMesg, Key, Padding, true) of
+ error ->
+ erlang:error(encrypt_failed, [BinMesg,Key, Padding]);
+ Sign -> Sign
+ end.
%% Binary, Key = [E,N]
rsa_public_decrypt(BinMesg, Key, Padding) ->
- Size = iolist_size(BinMesg),
- <<Ret:8, Signature/binary>> =
- control(?RSA_PUBLIC_DECRYPT, [<<Size:32>>,BinMesg,Key,rsa_pad(Padding)]),
- case Ret of
- 1 -> Signature;
- 0 -> erlang:error(decrypt_failed, [BinMesg,Key, Padding])
+ case rsa_public_crypt(BinMesg, Key, Padding, false) of
+ error ->
+ erlang:error(decrypt_failed, [BinMesg,Key, Padding]);
+ Sign -> Sign
end.
%%
%% AES - with 128 or 256 bit key in cipher block chaining mode (CBC)
%%
+-spec aes_cbc_128_encrypt(iodata(), binary(), iodata()) ->
+ binary().
+-spec aes_cbc_128_decrypt(iodata(), binary(), iodata()) ->
+ binary().
+-spec aes_cbc_256_encrypt(iodata(), binary(), iodata()) ->
+ binary().
+-spec aes_cbc_256_decrypt(iodata(), binary(), iodata()) ->
+ binary().
aes_cbc_128_encrypt(Key, IVec, Data) ->
- control(?AES_CBC_128_ENCRYPT, [Key, IVec, Data]).
+ aes_cbc_crypt(Key, IVec, Data, true).
aes_cbc_128_decrypt(Key, IVec, Data) ->
- control(?AES_CBC_128_DECRYPT, [Key, IVec, Data]).
+ aes_cbc_crypt(Key, IVec, Data, false).
aes_cbc_256_encrypt(Key, IVec, Data) ->
- control(?AES_CBC_256_ENCRYPT, [Key, IVec, Data]).
+ aes_cbc_crypt(Key, IVec, Data, true).
aes_cbc_256_decrypt(Key, IVec, Data) ->
- control(?AES_CBC_256_DECRYPT, [Key, IVec, Data]).
+ aes_cbc_crypt(Key, IVec, Data, false).
+
+aes_cbc_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
%%
%% aes_cbc_ivec(Data) -> binary()
@@ -537,31 +529,29 @@ aes_cbc_ivec(Data) when is_list(Data) ->
%% NB doesn't check that they are the same size, just concatenates
%% them and sends them to the driver
%%
-exor(A, B) ->
- control(?XOR, [A, B]).
+-spec exor(iodata(), iodata()) -> binary().
+
+exor(_A, _B) -> ?nif_stub.
%%
%% RC4 - symmetric stream cipher
%%
-rc4_encrypt(Key, Data) ->
- control_bin(?RC4_ENCRYPT, Key, Data).
+-spec rc4_encrypt(iodata(), iodata()) -> binary().
-rc4_set_key(Key) ->
- control(?RC4_SET_KEY, Key).
-
-rc4_encrypt_with_state(State, Data) ->
- <<Sz:32/integer-big-unsigned, S:Sz/binary, D/binary>> =
- control_bin(?RC4_ENCRYPT_WITH_STATE, State, Data),
- {S, D}.
+rc4_encrypt(_Key, _Data) -> ?nif_stub.
+rc4_set_key(_Key) -> ?nif_stub.
+rc4_encrypt_with_state(_State, _Data) -> ?nif_stub.
%%
%% RC2 - 40 bits block cipher
%%
rc2_40_cbc_encrypt(Key, IVec, Data) ->
- control(?RC2_40_CBC_ENCRYPT, [Key, IVec, Data]).
+ rc2_40_cbc_crypt(Key,IVec,Data,true).
rc2_40_cbc_decrypt(Key, IVec, Data) ->
- control(?RC2_40_CBC_DECRYPT, [Key, IVec, Data]).
+ rc2_40_cbc_crypt(Key,IVec,Data,false).
+
+rc2_40_cbc_crypt(_Key, _IVec, _Data, _IsEncrypt) -> ?nif_stub.
%%
%% DH Diffie-Hellman functions
@@ -576,87 +566,50 @@ rc2_40_cbc_decrypt(Key, IVec, Data) ->
%%
%% usage: dh_generate_parameters(1024, 2 or 5) ->
%% [Prime=mpint(), SharedGenerator=mpint()]
-dh_generate_parameters(PrimeLen, Generator)
- when is_integer(PrimeLen), is_integer(Generator) ->
- case control(?DH_GENERATE_PARAMS, <<PrimeLen:32, Generator:32>>) of
- <<0:8, _/binary>> ->
- erlang:error(generation_failed, [PrimeLen,Generator]);
- <<1:8, PLen0:32, _:PLen0/binary, GLen0:32,_:GLen0/binary>> = Bin ->
- PLen = PLen0+4,
- GLen = GLen0+4,
- <<_:8, PBin:PLen/binary,GBin:GLen/binary>> = Bin,
- [PBin, GBin]
- end.
+dh_generate_parameters(PrimeLen, Generator) ->
+ case dh_generate_parameters_nif(PrimeLen, Generator) of
+ error -> erlang:error(generation_failed, [PrimeLen,Generator]);
+ Ret -> Ret
+ end.
+
+dh_generate_parameters_nif(_PrimeLen, _Generator) -> ?nif_stub.
%% Checks that the DHParameters are ok.
%% DHParameters = [P (Prime)= mpint(), G(Generator) = mpint()]
-dh_check(DHParameters) ->
- case control(?DH_CHECK, DHParameters) of
- <<0:32>> -> ok;
- <<_:24,_:1,_:1,_:1,1:1>> -> not_prime;
- <<_:24,_:1,_:1,1:1,0:1>> -> not_strong_prime;
- <<_:24,_:1,1:1,0:1,0:1>> -> unable_to_check_generator;
- <<_:24,1:1,0:1,0:1,0:1>> -> not_suitable_generator;
- <<16#FFFF:32>> -> {error, check_failed};
- <<X:32>> -> {unknown, X}
- end.
+dh_check([_Prime,_Gen]) -> ?nif_stub.
%% DHParameters = [P (Prime)= mpint(), G(Generator) = mpint()]
%% PrivKey = mpint()
+-spec dh_generate_key([binary()]) -> {binary(),binary()}.
+-spec dh_generate_key(binary()|undefined, [binary()]) ->
+ {binary(),binary()}.
+
dh_generate_key(DHParameters) ->
- dh_generate_key(<<0:32>>, DHParameters).
+ dh_generate_key(undefined, DHParameters).
dh_generate_key(PrivateKey, DHParameters) ->
- case control(?DH_GENERATE_KEY, [PrivateKey, DHParameters]) of
- <<0:8, _/binary>> ->
- erlang:error(generation_failed, [PrivateKey,DHParameters]);
- Bin = <<1:8, PubLen0:32, _:PubLen0/binary, PrivLen0:32, _:PrivLen0/binary>> ->
- PubLen = PubLen0+4,
- PrivLen = PrivLen0+4,
- <<_:8, PubBin:PubLen/binary,PrivBin:PrivLen/binary>> = Bin,
- {PubBin, PrivBin}
+ case dh_generate_key_nif(PrivateKey, DHParameters) of
+ error -> erlang:error(generation_failed, [PrivateKey,DHParameters]);
+ Res -> Res
end.
+dh_generate_key_nif(_PrivateKey, _DHParameters) -> ?nif_stub.
+
%% DHParameters = [P (Prime)= mpint(), G(Generator) = mpint()]
%% MyPrivKey, OthersPublicKey = mpint()
+-spec dh_compute_key(binary(), binary(), [binary()]) -> binary().
+
dh_compute_key(OthersPublicKey, MyPrivateKey, DHParameters) ->
- case control(?DH_COMPUTE_KEY, [OthersPublicKey, MyPrivateKey, DHParameters]) of
- <<0:8, _/binary>> ->
- erlang:error(computation_failed, [OthersPublicKey,MyPrivateKey,DHParameters]);
- <<1:8, Binary/binary>> -> Binary
+ case dh_compute_key_nif(OthersPublicKey,MyPrivateKey,DHParameters) of
+ error -> erlang:error(computation_failed, [OthersPublicKey,MyPrivateKey,DHParameters]);
+ Ret -> Ret
end.
+dh_compute_key_nif(_OthersPublicKey, _MyPrivateKey, _DHParameters) -> ?nif_stub.
+
%%
%% LOCAL FUNCTIONS
%%
-control_bin(Cmd, Key, Data) ->
- Sz = iolist_size(Key),
- control(Cmd, [<<Sz:32/integer-unsigned>>, Key, Data]).
-
-control(Cmd, Data) ->
- Port = crypto_server:client_port(),
- erlang:port_control(Port, Cmd, Data).
-
-
-%% sizehdr(N) ->
-%% [(N bsr 24) band 255,
-%% (N bsr 16) band 255,
-%% (N bsr 8) band 255,
-%% N band 255].
-
-%% Flat length of IOlist (or binary)
-%% flen(L) when binary(L) ->
-%% size(L);
-%% flen(L) ->
-%% flen(L, 0).
-
-%% flen([H| T], N) when list(H) ->
-%% flen(H, flen(T, N));
-%% flen([H| T], N) when binary(H) ->
-%% flen(T, N + size(H));
-%% flen([H| T], N) when integer(H), 0 =< H, H =< 255 ->
-%% flen(T, N + 1);
-%% flen([], N) ->
-%% N.
+
%% large integer in a binary with 32bit length
%% MP representaion (SSH2)
diff --git a/lib/crypto/src/crypto_server.erl b/lib/crypto/src/crypto_server.erl
index 0b1e5c9b02..89650a9f06 100644
--- a/lib/crypto/src/crypto_server.erl
+++ b/lib/crypto/src/crypto_server.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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%
%%
@@ -23,14 +23,12 @@
-behaviour(gen_server).
--export([start_link/0,client_port/0]).
+-export([start_link/0]).
%% Internal exports, call-back functions.
-export([init/1,handle_call/3,handle_cast/2,handle_info/2,code_change/3,
terminate/2]).
-%% Measurements shows that inlining port_names/0 is worth doing.
--compile({inline,[{port_names,0}]}).
%%% --------------------------------------------------------
%%% Interface Functions.
@@ -40,66 +38,8 @@ start_link() ->
gen_server:start_link({local, crypto_server}, crypto_server, [], []).
init([]) ->
- process_flag(trap_exit, true),
- erl_ddll:start(),
- PrivDir = code:priv_dir(crypto),
- LibDir1 = filename:join([PrivDir, "lib"]),
- {Status, LibDir} =
- case erl_ddll:load_driver(LibDir1, crypto_drv) of
- ok -> {ok,LibDir1};
- {error,Error1} ->
- LibDir2 =
- filename:join(LibDir1,
- erlang:system_info(system_architecture)),
- Candidate =
- filelib:wildcard(filename:join([LibDir2,"crypto_drv*"])),
- case Candidate of
- [] ->
- {{error,Error1},LibDir1};
- _ ->
- case erl_ddll:load_driver(LibDir2, crypto_drv) of
- ok ->
- {ok,LibDir2};
- {error, Error2} ->
- {{error,Error2},LibDir2}
- end
- end
- end,
- case Status of
- ok ->
- Cmd = "crypto_drv elibcrypto " ++
- filename:join([LibDir, "elibcrypto"]),
- open_ports(Cmd,size(port_names()));
- {error, E} ->
- Str = erl_ddll:format_error(E),
- error_logger:error_msg("Unable to load crypto_drv. Failed with error:~n\"~s\"~nOpenSSL might not be installed on this system.~n",[Str]),
- {stop,nodriver}
- end.
-
-open_ports(_,0) ->
- {ok, []};
-open_ports(Cmd,N) ->
- Port = open_port({spawn, Cmd}, []),
- %% check that driver is loaded, linked and working
- %% since crypto_drv links towards libcrypto, this is a good thing
- %% since libcrypto is known to be bad with backwards compatibility
- case catch port_control(Port, 0, []) of
- {'EXIT', _} ->
- {stop, nodriver};
- _ ->
- register(element(N,port_names()), Port),
- open_ports(Cmd,N-1)
- end.
-
-port_names() ->
- { crypto_drv01, crypto_drv02, crypto_drv03, crypto_drv04,
- crypto_drv05, crypto_drv06, crypto_drv07, crypto_drv08,
- crypto_drv09, crypto_drv10, crypto_drv11, crypto_drv12,
- crypto_drv13, crypto_drv14, crypto_drv15, crypto_drv16 }.
-
-client_port() ->
- element(erlang:system_info(scheduler_id) rem size(port_names()) + 1,
- port_names()).
+ {ok,[]}.
+
%%% --------------------------------------------------------
@@ -112,11 +52,6 @@ handle_call(_, _, State) ->
handle_cast(_, State) ->
{noreply, State}.
-handle_info({'EXIT', Pid, _Reason}, State) when is_pid(Pid) ->
- {noreply, State};
-
-handle_info({'EXIT', Port, Reason}, State) when is_port(Port) ->
- {stop, {port_died, Reason}, State};
handle_info(_, State) ->
{noreply, State}.
@@ -124,13 +59,8 @@ code_change(_OldVsn, State, _Extra) ->
{ok, State}.
terminate(_Reason, _State) ->
- close_ports(size(port_names())).
+ [].
-close_ports(0) ->
- ok;
-close_ports(N) ->
- element(N,port_names()) ! {self(), close},
- close_ports(N-1).
diff --git a/lib/crypto/test/blowfish_SUITE.erl b/lib/crypto/test/blowfish_SUITE.erl
index d4cc167ea9..d117e7cc3d 100644
--- a/lib/crypto/test/blowfish_SUITE.erl
+++ b/lib/crypto/test/blowfish_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
@@ -116,7 +116,8 @@ all(suite) ->
ecb_test(KeyBytes, ClearBytes, CipherBytes) ->
{Key, Clear, Cipher} =
{to_bin(KeyBytes), to_bin(ClearBytes), to_bin(CipherBytes)},
- crypto:blowfish_ecb_encrypt(Key, Clear) =:= Cipher.
+ ?line m(crypto:blowfish_ecb_encrypt(Key, Clear), Cipher),
+ true.
ecb(doc) ->
"Test that ECB mode is OK";
@@ -208,3 +209,5 @@ to_bin([], Acc) ->
iolist_to_binary(lists:reverse(Acc));
to_bin([C1, C2 | Rest], Acc) ->
to_bin(Rest, [(dehex(C1) bsl 4) bor dehex(C2) | Acc]).
+
+m(X,X) -> ok.
diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl
index 8467ff2274..576949d38d 100644
--- a/lib/crypto/test/crypto_SUITE.erl
+++ b/lib/crypto/test/crypto_SUITE.erl
@@ -119,7 +119,7 @@ link_test(Config) when is_list(Config) ->
link_test_1() ->
?line CryptoPriv = code:priv_dir(crypto),
- ?line Wc = filename:join([CryptoPriv,"lib","crypto_drv.*"]),
+ ?line Wc = filename:join([CryptoPriv,"lib","crypto.*"]),
?line case filelib:wildcard(Wc) of
[] -> {skip,"Didn't find the crypto driver"};
[Drv] -> link_test_2(Drv)
@@ -441,7 +441,7 @@ des_cbc_iter(Config) when is_list(Config) ->
?line Cipher1 = crypto:des_cbc_encrypt(Key, IVec, Plain1),
?line IVec2 = crypto:des_cbc_ivec(Cipher1),
?line Cipher2 = crypto:des_cbc_encrypt(Key, IVec2, Plain2),
- ?line Cipher = concat_binary([Cipher1, Cipher2]),
+ ?line Cipher = list_to_binary([Cipher1, Cipher2]),
?line m(Cipher, hexstr2bin("e5c7cdde872bf27c43e934008c389c"
"0f683788499a7c05f6")).
@@ -770,18 +770,18 @@ dsa_verify_test(Config) when is_list(Config) ->
crypto:mpint(Key)
],
- ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(SigBlob),
+ ?line m(my_dss_verify(sized_binary(Msg), sized_binary(SigBlob),
ValidKey), true),
BadMsg = one_bit_wrong(Msg),
- ?line m(crypto:dss_verify(sized_binary(BadMsg), sized_binary(SigBlob),
+ ?line m(my_dss_verify(sized_binary(BadMsg), sized_binary(SigBlob),
ValidKey), false),
BadSig = one_bit_wrong(SigBlob),
- ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(BadSig),
+ ?line m(my_dss_verify(sized_binary(Msg), sized_binary(BadSig),
ValidKey), false),
SizeErr = size(SigBlob) - 13,
- BadArg = (catch crypto:dss_verify(sized_binary(Msg), <<SizeErr:32, SigBlob/binary>>,
+ BadArg = (catch my_dss_verify(sized_binary(Msg), <<SizeErr:32, SigBlob/binary>>,
ValidKey)),
?line m(element(1,element(2,BadArg)), badarg),
@@ -791,9 +791,12 @@ dsa_verify_test(Config) when is_list(Config) ->
crypto:mpint(Key+17)
],
- ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(SigBlob),
+ ?line m(my_dss_verify(sized_binary(Msg), sized_binary(SigBlob),
InValidKey), false).
+
+one_bit_wrong(List) when is_list(List) ->
+ lists:map(fun(Bin) -> one_bit_wrong(Bin) end, List);
one_bit_wrong(Bin) ->
Half = size(Bin) div 2,
<<First:Half/binary, Byte:8, Last/binary>> = Bin,
@@ -843,16 +846,16 @@ dsa_sign_test(Config) when is_list(Config) ->
ParamG = 18320614775012672475365915366944922415598782131828709277168615511695849821411624805195787607930033958243224786899641459701930253094446221381818858674389863050420226114787005820357372837321561754462061849169568607689530279303056075793886577588606958623645901271866346406773590024901668622321064384483571751669,
Params = [crypto:mpint(ParamP), crypto:mpint(ParamQ), crypto:mpint(ParamG)],
- ?line Sig1 = crypto:dss_sign(sized_binary(Msg), [Params, crypto:mpint(PrivKey)]),
+ ?line Sig1 = my_dss_sign(sized_binary(Msg), Params ++ [crypto:mpint(PrivKey)]),
- ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(Sig1),
- [Params, crypto:mpint(PubKey)]), true),
+ ?line m(my_dss_verify(sized_binary(Msg), Sig1,
+ Params ++ [crypto:mpint(PubKey)]), true),
- ?line m(crypto:dss_verify(sized_binary(one_bit_wrong(Msg)), sized_binary(Sig1),
- [Params, crypto:mpint(PubKey)]), false),
+ ?line m(my_dss_verify(sized_binary(one_bit_wrong(Msg)), Sig1,
+ Params ++ [crypto:mpint(PubKey)]), false),
- ?line m(crypto:dss_verify(sized_binary(Msg), sized_binary(one_bit_wrong(Sig1)),
- [Params, crypto:mpint(PubKey)]), false),
+ ?line m(my_dss_verify(sized_binary(Msg), one_bit_wrong(Sig1),
+ Params ++ [crypto:mpint(PubKey)]), false),
%%?line Bad = crypto:dss_sign(sized_binary(Msg), [Params, crypto:mpint(PubKey)]),
@@ -1132,3 +1135,24 @@ zero_bin(N) when is_integer(N) ->
<<0:N8/integer>>;
zero_bin(B) when is_binary(B) ->
zero_bin(size(B)).
+
+my_dss_verify(Data,[Sign|Tail],Key) ->
+ Res = my_dss_verify(Data,sized_binary(Sign),Key),
+ case Tail of
+ [] -> Res;
+ _ -> ?line Res = my_dss_verify(Data,Tail,Key)
+ end;
+my_dss_verify(Data,Sign,Key) ->
+ ?line Res = crypto:dss_verify(Data, Sign, Key),
+ ?line Res = crypto:dss_verify(sha, Data, Sign, Key),
+ ?line <<_:32,Raw/binary>> = Data,
+ ?line Res = crypto:dss_verify(none, crypto:sha(Raw), Sign, Key),
+ Res.
+
+my_dss_sign(Data,Key) ->
+ ?line S1 = crypto:dss_sign(Data, Key),
+ ?line S2 = crypto:dss_sign(sha, Data, Key),
+ ?line <<_:32,Raw/binary>> = Data,
+ ?line S3 = crypto:dss_sign(none, crypto:sha(Raw), Key),
+ [S1,S2,S3].
+
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 68eecfe759..d04736fc2b 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 1.6.4
+CRYPTO_VSN = 2.0
diff --git a/lib/debugger/doc/src/notes.xml b/lib/debugger/doc/src/notes.xml
index afd49e8593..c5f611b8f3 100644
--- a/lib/debugger/doc/src/notes.xml
+++ b/lib/debugger/doc/src/notes.xml
@@ -32,6 +32,30 @@
<p>This document describes the changes made to the Debugger
application.</p>
+<section><title>Debugger 3.2.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Warnings due to new autoimported BIFs removed</p>
+ <p>
+ Own Id: OTP-8674 Aux Id: OTP-8579 </p>
+ </item>
+ <item>
+ <p>
+ The predefined builtin type tid() has been removed.
+ Instead, ets:tid() should be used.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8687</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Debugger 3.2.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/debugger/src/dbg_icmd.erl b/lib/debugger/src/dbg_icmd.erl
index 7ccb9793a3..a26b16c82d 100644
--- a/lib/debugger/src/dbg_icmd.erl
+++ b/lib/debugger/src/dbg_icmd.erl
@@ -94,7 +94,7 @@ break_p(Mod, Line, Le, Bs) ->
Bool = case Cond of
null -> true;
{CM, CN} ->
- try apply(CM, CN, [Bs]) of
+ try CM:CN(Bs) of
true -> true;
false -> false;
_Term -> false
@@ -245,7 +245,7 @@ handle_int_msg({attached, AttPid}, Status, _Bs,
%% Tell attached process in which module evalution is located
if
- Le==1 ->
+ Le =:= 1 ->
tell_attached({attached, undefined, -1, get(trace)});
true ->
tell_attached({attached, M, Line, get(trace)}),
@@ -269,7 +269,7 @@ handle_int_msg(detached, _Status, _Bs, _Ieval) ->
handle_int_msg({old_code,Mod}, Status, Bs,
#ieval{level=Le,module=M}=Ieval) ->
if
- Status==idle, Le==1 ->
+ Status =:= idle, Le =:= 1 ->
erase([Mod|db]),
put(cache, []);
true ->
@@ -352,9 +352,9 @@ set_stack_trace(true) ->
set_stack_trace(all);
set_stack_trace(Flag) ->
if
- Flag==false ->
+ Flag =:= false ->
put(stack, []);
- Flag==no_tail; Flag==all ->
+ Flag =:= no_tail; Flag =:= all ->
ignore
end,
put(trace_stack, Flag),
@@ -367,7 +367,7 @@ bindings(Bs, nostack) ->
Bs;
bindings(Bs, SP) ->
case dbg_ieval:stack_level() of
- Le when SP>Le ->
+ Le when SP > Le ->
Bs;
_ ->
dbg_ieval:bindings(SP)
@@ -377,7 +377,6 @@ messages() ->
{messages, Msgs} = erlang:process_info(get(self), messages),
Msgs.
-
%%====================================================================
%% Evaluating expressions within process context
%%====================================================================
@@ -398,7 +397,7 @@ eval_restricted({From,_Mod,Cmd,SP}, Bs) ->
From ! {self(), {eval_rsp, Rsp}}
end.
-eval_nonrestricted({From,Mod,Cmd,SP}, Bs, #ieval{level=Le}) when SP<Le->
+eval_nonrestricted({From,Mod,Cmd,SP}, Bs, #ieval{level=Le}) when SP < Le->
%% Evaluate in stack
eval_restricted({From, Mod, Cmd, SP}, Bs),
Bs;
@@ -424,15 +423,15 @@ eval_nonrestricted({From, _Mod, Cmd, _SP}, Bs,
eval_nonrestricted_1({match,_,{var,_,Var},Expr}, Bs, Ieval) ->
{value,Res,Bs2} =
dbg_ieval:eval_expr(Expr, Bs, Ieval#ieval{last_call=false}),
- Bs3 = case lists:keysearch(Var, 1, Bs) of
- {value, {Var,_Value}} ->
+ Bs3 = case lists:keyfind(Var, 1, Bs) of
+ {Var,_Value} ->
lists:keyreplace(Var, 1, Bs2, {Var,Res});
false -> [{Var,Res} | Bs2]
end,
{Res,Bs3};
eval_nonrestricted_1({var,_,Var}, Bs, _Ieval) ->
- Res = case lists:keysearch(Var, 1, Bs) of
- {value, {Var, Value}} -> Value;
+ Res = case lists:keyfind(Var, 1, Bs) of
+ {Var, Value} -> Value;
false -> unbound
end,
{Res,Bs};
@@ -458,7 +457,6 @@ parse_cmd(Cmd, LineNo) ->
{ok,Forms} = erl_parse:parse_exprs(Tokens),
Forms.
-
%%====================================================================
%% Library functions for attached process handling
%%====================================================================
@@ -470,13 +468,12 @@ tell_attached(Msg) ->
AttPid ! {self(), Msg}
end.
-
%%====================================================================
%% get_binding/2
%%====================================================================
get_binding(Var, Bs) ->
- case lists:keysearch(Var, 1, Bs) of
- {value, {Var, Value}} -> {value, Value};
+ case lists:keyfind(Var, 1, Bs) of
+ {Var, Value} -> {value, Value};
false -> unbound
end.
diff --git a/lib/debugger/src/dbg_ieval.erl b/lib/debugger/src/dbg_ieval.erl
index c13fda7ac1..476dfd8796 100644
--- a/lib/debugger/src/dbg_ieval.erl
+++ b/lib/debugger/src/dbg_ieval.erl
@@ -120,9 +120,9 @@ check_exit_msg({'EXIT', Int, Reason}, _Bs, #ieval{level=Le}) ->
%% This *must* be interpreter which has terminated,
%% we are not linked to anyone else
if
- Le==1 ->
+ Le =:= 1 ->
exit(Reason);
- Le>1 ->
+ Le > 1 ->
exit({Int, Reason})
end;
check_exit_msg({'DOWN',_,_,_,Reason}, Bs,
@@ -139,9 +139,9 @@ check_exit_msg({'DOWN',_,_,_,Reason}, Bs,
%% importance in this case
%% If we don't save them, however, post-mortem analysis
%% of the process isn't possible
- undefined when Le==1 -> % died outside interpreted code
+ undefined when Le =:= 1 -> % died outside interpreted code
{};
- undefined when Le>1 ->
+ undefined when Le > 1 ->
StackBin = term_to_binary(get(stack)),
{{Mod, Li}, Bs, StackBin};
@@ -152,9 +152,9 @@ check_exit_msg({'DOWN',_,_,_,Reason}, Bs,
dbg_iserver:cast(get(int), {set_exit_info,self(),ExitInfo}),
if
- Le==1 ->
+ Le =:= 1 ->
exit(Reason);
- Le>1 ->
+ Le > 1 ->
exit({get(self), Reason})
end;
check_exit_msg(_Msg, _Bs, _Ieval) ->
@@ -271,7 +271,7 @@ meta_loop(Debugged, Bs, #ieval{level=Le} = Ieval) ->
end;
%% Re-entry to Meta from non-interpreted code
- {re_entry, Debugged, {eval,{M,F,As}}} when Le==1 ->
+ {re_entry, Debugged, {eval,{M,F,As}}} when Le =:= 1 ->
%% Reset process dictionary
%% This is really only necessary if the process left
%% interpreted code at a call level > 1
@@ -346,7 +346,7 @@ push(MFA, Bs, #ieval{level=Le,module=Cm,line=Li,last_call=Lc}) ->
[] -> put(stack, [Entry]);
[_Entry|Entries] -> put(stack, [Entry|Entries])
end;
- _ -> % all | no_tail when Lc==false
+ _ -> % all | no_tail when Lc =:= false
put(stack, [Entry|get(stack)])
end.
@@ -413,10 +413,10 @@ sublist(L, Start, Length) ->
lists:sublist(L, Start, Length).
fix_stacktrace2([{_,{{M,F,As1},_,_}}, {_,{{M,F,As2},_,_}}|_])
- when length(As1)==length(As2) ->
+ when length(As1) =:= length(As2) ->
[{M,F,As1}];
fix_stacktrace2([{_,{{Fun,As1},_,_}}, {_,{{Fun,As2},_,_}}|_])
- when length(As1)==length(As2) ->
+ when length(As1) =:= length(As2) ->
[{Fun,As1}];
fix_stacktrace2([{_,{MFA,_,_}}|Entries]) ->
[MFA|fix_stacktrace2(Entries)];
@@ -465,9 +465,9 @@ stack_frame(SP, Dir, [{SP, _}|Stack]) ->
case Stack of
[{Le, {_MFA,Where,Bs}}|_] ->
{Le, Where, Bs};
- [] when Dir==up ->
+ [] when Dir =:= up ->
top;
- [] when Dir==down ->
+ [] when Dir =:= down ->
bottom
end;
stack_frame(SP, Dir, [_Entry|Stack]) ->
@@ -509,7 +509,7 @@ trace(What, Args, true) ->
end,
io_lib:format(" (~w) receive " ++ Tail, [Le]);
- received when Args==null ->
+ received when Args =:= null ->
io_lib:format("~n", []);
received -> % Args=Msg
io_lib:format("~n<== ~p~n", [Args]);
@@ -586,8 +586,8 @@ eval_mfa(Debugged, M, F, As, Ieval) ->
end.
eval_function(Mod, Fun, As0, Bs0, _Called, Ieval) when is_function(Fun);
- Mod==?MODULE,
- Fun==eval_fun ->
+ Mod =:= ?MODULE,
+ Fun =:= eval_fun ->
#ieval{level=Le, line=Li, last_call=Lc} = Ieval,
case lambda(Fun, As0) of
{Cs,Module,Name,As,Bs} ->
@@ -657,7 +657,7 @@ eval_function(Mod, Name, As0, Bs0, Called, Ieval) ->
lambda(eval_fun, [Cs,As,Bs,{Mod,Name}=F]) ->
%% Fun defined in interpreted code, called from outside
if
- length(element(3,hd(Cs))) == length(As) ->
+ length(element(3,hd(Cs))) =:= length(As) ->
db_ref(Mod), %% Adds ref between module and process
{Cs,Mod,Name,As,Bs};
true ->
@@ -672,7 +672,7 @@ lambda(Fun, As) when is_function(Fun) ->
{env, [{Mod,Name},Bs,Cs]} = erlang:fun_info(Fun, env),
{arity, Arity} = erlang:fun_info(Fun, arity),
if
- length(As) == Arity ->
+ length(As) =:= Arity ->
db_ref(Mod), %% Adds ref between module and process
{Cs,Mod,Name,As,Bs};
true ->
@@ -731,7 +731,7 @@ db_ref(Mod) ->
ModDb ->
Node = node(get(int)),
DbRef = if
- Node/=node() -> {Node,ModDb};
+ Node =/= node() -> {Node,ModDb};
true -> ModDb
end,
put([Mod|db], DbRef),
@@ -741,13 +741,12 @@ db_ref(Mod) ->
DbRef
end.
-
cache(Key, Data) ->
put(cache, lists:sublist([{Key,Data}|get(cache)], 5)).
cached(Key) ->
- case lists:keysearch(Key, 1, get(cache)) of
- {value,{Key,Data}} -> Data;
+ case lists:keyfind(Key, 1, get(cache)) of
+ {Key,Data} -> Data;
false -> false
end.
@@ -844,7 +843,7 @@ expr({'try',Line,Es,CaseCs,CatchCs,[]}, Bs0, Ieval0) ->
case_clauses(Val, CaseCs, Bs, try_clause, Ieval)
end
catch
- Class:Reason when CatchCs=/=[] ->
+ Class:Reason when CatchCs =/= [] ->
catch_clauses({Class,Reason,[]}, CatchCs, Bs0, Ieval)
end;
expr({'try',Line,Es,CaseCs,CatchCs,As}, Bs0, Ieval0) ->
@@ -1498,10 +1497,7 @@ guard(Gs, Bs) -> or_guard(Gs, Bs).
or_guard([G|Gs], Bs) ->
%% Short-circuit OR.
- case and_guard(G, Bs) of
- true -> true;
- false -> or_guard(Gs, Bs)
- end;
+ and_guard(G, Bs) orelse or_guard(Gs, Bs);
or_guard([], _) -> false.
and_guard([G|Gs], Bs) ->
@@ -1598,8 +1594,7 @@ match1({bin,_,Fs}, B, Bs0, BBs0) when is_bitstring(B) ->
try eval_bits:match_bits(Fs, B, Bs1, BBs,
fun(L, R, Bs) -> match1(L, R, Bs, BBs) end,
fun(E, Bs) -> expr(E, Bs, #ieval{}) end,
- false) of
- Match -> Match
+ false)
catch
_:_ -> throw(nomatch)
end;
@@ -1687,7 +1682,7 @@ merge_bindings([{Name,V}|B1s], B2s, Ieval) ->
case binding(Name, B2s) of
{value,V} -> % Already there, and the same
merge_bindings(B1s, B2s, Ieval);
- {value,_} when Name=='_' -> % Already there, but anonymous
+ {value,_} when Name =:= '_' -> % Already there, but anonymous
B2s1 = lists:keydelete('_', 1, B2s),
[{Name,V}|merge_bindings(B1s, B2s1, Ieval)];
{value,_} -> % Already there, but different => badmatch
diff --git a/lib/debugger/src/dbg_iload.erl b/lib/debugger/src/dbg_iload.erl
index 1216338006..2ae0c333da 100644
--- a/lib/debugger/src/dbg_iload.erl
+++ b/lib/debugger/src/dbg_iload.erl
@@ -1,64 +1,60 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-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(dbg_iload).
-%% External exports
-export([load_mod/4]).
-%% Internal exports
--export([load_mod1/4]).
-
%%====================================================================
%% External exports
%%====================================================================
%%--------------------------------------------------------------------
%% load_mod(Mod, File, Binary, Db) -> {ok, Mod}
-%% Mod = atom()
+%% Mod = module()
%% File = string() Source file (including path)
%% Binary = binary()
%% Db = ETS identifier
%% Load a new module into the database.
%%
-%% We want the loading of a module to be syncronous so no other
+%% We want the loading of a module to be synchronous so that no other
%% process tries to interpret code in a module not being completely
%% loaded. This is achieved as this function is called from
%% dbg_iserver. We are suspended until the module has been loaded.
%%--------------------------------------------------------------------
+-spec load_mod(Mod, file:filename(), binary(), ets:tid()) ->
+ {'ok', Mod} when is_subtype(Mod, atom()).
+
load_mod(Mod, File, Binary, Db) ->
Flag = process_flag(trap_exit, true),
- Pid = spawn_link(?MODULE, load_mod1, [Mod, File, Binary, Db]),
+ Pid = spawn_link(fun () -> load_mod1(Mod, File, Binary, Db) end),
receive
{'EXIT', Pid, What} ->
process_flag(trap_exit, Flag),
What
end.
-%%====================================================================
-%% Internal exports
-%%====================================================================
+-spec load_mod1(atom(), file:filename(), binary(), ets:tid()) -> no_return().
load_mod1(Mod, File, Binary, Db) ->
store_module(Mod, File, Binary, Db),
exit({ok, Mod}).
-
%%====================================================================
%% Internal functions
%%====================================================================
@@ -84,7 +80,7 @@ store_module(Mod, File, Binary, Db) ->
Attr = store_forms(Forms, Mod, Db, Exp, []),
erase(mod_md5),
erase(current_function),
-% store_funs(Db, Mod),
+ %% store_funs(Db, Mod),
erase(vcount),
erase(funs),
erase(fun_count),
@@ -412,8 +408,7 @@ expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,fault}},[_]=As}) ->
{dbg,Line,fault,expr_list(As)};
expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,exit}},[_]=As}) ->
{dbg,Line,exit,expr_list(As)};
-expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,apply}},As0})
- when length(As0) == 3 ->
+expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,apply}},[_,_,_]=As0}) ->
As = expr_list(As0),
{apply,Line,As};
expr({call,Line,{remote,_,{atom,_,Mod},{atom,_,Func}},As0}) ->
@@ -521,15 +516,9 @@ expr(Other) ->
%% here as sys_pre_expand has transformed source.
is_guard_test({op,_,Op,L,R}) ->
- case erl_internal:comp_op(Op, 2) of
- true -> is_gexpr_list([L,R]);
- false -> false
- end;
+ erl_internal:comp_op(Op, 2) andalso is_gexpr_list([L,R]);
is_guard_test({call,_,{remote,_,{atom,_,erlang},{atom,_,Test}},As}) ->
- case erl_internal:type_test(Test, length(As)) of
- true -> is_gexpr_list(As);
- false -> false
- end;
+ erl_internal:type_test(Test, length(As)) andalso is_gexpr_list(As);
is_guard_test({atom,_,true}) -> true;
is_guard_test(_) -> false.
@@ -546,22 +535,12 @@ is_gexpr({call,_,{remote,_,{atom,_,erlang},{atom,_,F}},As}) ->
Ar = length(As),
case erl_internal:guard_bif(F, Ar) of
true -> is_gexpr_list(As);
- false ->
- case erl_internal:arith_op(F, Ar) of
- true -> is_gexpr_list(As);
- false -> false
- end
+ false -> erl_internal:arith_op(F, Ar) andalso is_gexpr_list(As)
end;
is_gexpr({op,_,Op,A}) ->
- case erl_internal:arith_op(Op, 1) of
- true -> is_gexpr(A);
- false -> false
- end;
+ erl_internal:arith_op(Op, 1) andalso is_gexpr(A);
is_gexpr({op,_,Op,A1,A2}) ->
- case erl_internal:arith_op(Op, 2) of
- true -> is_gexpr_list([A1,A2]);
- false -> false
- end;
+ erl_internal:arith_op(Op, 2) andalso is_gexpr_list([A1,A2]);
is_gexpr(_) -> false.
is_gexpr_list(Es) -> lists:all(fun (E) -> is_gexpr(E) end, Es).
diff --git a/lib/debugger/src/dbg_iserver.erl b/lib/debugger/src/dbg_iserver.erl
index 4c1e9ccb7b..59188d83a2 100644
--- a/lib/debugger/src/dbg_iserver.erl
+++ b/lib/debugger/src/dbg_iserver.erl
@@ -155,11 +155,8 @@ handle_call(get_stack_trace, _From, State) ->
%% Retrieving information
handle_call(snapshot, _From, State) ->
- Reply = lists:map(fun(Proc) ->
- {Proc#proc.pid, Proc#proc.function,
- Proc#proc.status, Proc#proc.info}
- end,
- State#state.procs),
+ Reply = [{Proc#proc.pid, Proc#proc.function,
+ Proc#proc.status, Proc#proc.info} || Proc <- State#state.procs],
{reply, Reply, State};
handle_call({get_meta, Pid}, _From, State) ->
Reply = case get_proc({pid, Pid}, State#state.procs) of
@@ -181,21 +178,21 @@ handle_call({get_attpid, Pid}, _From, State) ->
%% Breakpoint handling
handle_call({new_break, Point, Options}, _From, State) ->
- case lists:keysearch(Point, 1, State#state.breaks) of
+ case lists:keymember(Point, 1, State#state.breaks) of
false ->
Break = {Point, Options},
send_all([subscriber, meta, attached],
{new_break, Break}, State),
Breaks = keyinsert(Break, 1, State#state.breaks),
{reply, ok, State#state{breaks=Breaks}};
- {value, _Break} ->
+ true ->
{reply, {error, break_exists}, State}
end;
handle_call(all_breaks, _From, State) ->
{reply, State#state.breaks, State};
handle_call({all_breaks, Mod}, _From, State) ->
Reply = lists:filter(fun({{M,_L}, _Options}) ->
- if M==Mod -> true; true -> false end
+ M =/= Mod
end,
State#state.breaks),
{reply, Reply, State};
@@ -276,7 +273,7 @@ handle_call({contents, Mod, Pid}, _From, State) ->
Db = State#state.db,
[{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}),
ModDb = if
- Pid==any -> hd(ModDbs);
+ Pid =:= any -> hd(ModDbs);
true ->
lists:foldl(fun(T, not_found) ->
[{T, Pids}] = ets:lookup(Db, T),
@@ -295,7 +292,7 @@ handle_call({raw_contents, Mod, Pid}, _From, State) ->
Db = State#state.db,
[{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}),
ModDb = if
- Pid==any -> hd(ModDbs);
+ Pid =:= any -> hd(ModDbs);
true ->
lists:foldl(fun(T, not_found) ->
[{T, Pids}] = ets:lookup(Db, T),
@@ -360,15 +357,15 @@ handle_cast({set_stack_trace, Flag}, State) ->
%% Retrieving information
handle_cast(clear, State) ->
Procs = lists:filter(fun(#proc{status=Status}) ->
- if Status==exit -> false; true -> true end
+ Status =/= exit
end,
State#state.procs),
{noreply, State#state{procs=Procs}};
%% Breakpoint handling
handle_cast({delete_break, Point}, State) ->
- case lists:keysearch(Point, 1, State#state.breaks) of
- {value, _Break} ->
+ case lists:keymember(Point, 1, State#state.breaks) of
+ true ->
send_all([subscriber, meta, attached],
{delete_break, Point}, State),
Breaks = lists:keydelete(Point, 1, State#state.breaks),
@@ -377,8 +374,8 @@ handle_cast({delete_break, Point}, State) ->
{noreply, State}
end;
handle_cast({break_option, Point, Option, Value}, State) ->
- case lists:keysearch(Point, 1, State#state.breaks) of
- {value, {Point, Options}} ->
+ case lists:keyfind(Point, 1, State#state.breaks) of
+ {Point, Options} ->
N = case Option of
status -> 1;
action -> 2;
@@ -399,7 +396,7 @@ handle_cast(no_break, State) ->
handle_cast({no_break, Mod}, State) ->
send_all([subscriber, meta, attached], {no_break, Mod}, State),
Breaks = lists:filter(fun({{M, _L}, _O}) ->
- if M==Mod -> false; true -> true end
+ M =/= Mod
end,
State#state.breaks),
{noreply, State#state{breaks=Breaks}};
@@ -409,7 +406,7 @@ handle_cast({set_status, Meta, Status, Info}, State) ->
{true, Proc} = get_proc({meta, Meta}, State#state.procs),
send_all(subscriber, {new_status, Proc#proc.pid, Status, Info}, State),
if
- Status==break ->
+ Status =:= break ->
auto_attach(break, State#state.auto, Proc);
true -> ignore
end,
@@ -526,11 +523,10 @@ code_change(_OldVsn, State, _Extra) ->
%% Internal functions
%%====================================================================
-auto_attach(Why, Auto, Proc) when is_record(Proc, proc) ->
- case Proc#proc.attpid of
- AttPid when is_pid(AttPid) -> ignore;
- undefined ->
- auto_attach(Why, Auto, Proc#proc.pid)
+auto_attach(Why, Auto, #proc{attpid = Attpid, pid = Pid}) ->
+ case Attpid of
+ undefined -> auto_attach(Why, Auto, Pid);
+ _ when is_pid(Attpid) -> ignore
end;
auto_attach(Why, Auto, Pid) when is_pid(Pid) ->
case Auto of
@@ -545,7 +541,7 @@ auto_attach(Why, Auto, Pid) when is_pid(Pid) ->
keyinsert(Tuple1, N, [Tuple2|Tuples]) ->
if
- element(N, Tuple1)<element(N, Tuple2) ->
+ element(N, Tuple1) < element(N, Tuple2) ->
[Tuple1, Tuple2|Tuples];
true ->
[Tuple2 | keyinsert(Tuple1, N, Tuples)]
@@ -576,7 +572,7 @@ send_all([], _Msg, _State) -> ok;
send_all(subscriber, Msg, State) ->
send_all(State#state.subs, Msg);
send_all(meta, Msg, State) ->
- Metas = lists:map(fun(Proc) -> Proc#proc.meta end, State#state.procs),
+ Metas = [Proc#proc.meta || Proc <- State#state.procs],
send_all(Metas, Msg);
send_all(attached, Msg, State) ->
AttPids= mapfilter(fun(Proc) ->
@@ -600,7 +596,7 @@ get_proc({Type, Pid}, Procs) ->
meta -> #proc.meta;
attpid -> #proc.attpid
end,
- case lists:keysearch(Pid, Index, Procs) of
- {value, Proc} -> {true, Proc};
- false -> false
+ case lists:keyfind(Pid, Index, Procs) of
+ false -> false;
+ Proc -> {true, Proc}
end.
diff --git a/lib/debugger/src/dbg_ui_break_win.erl b/lib/debugger/src/dbg_ui_break_win.erl
index a56fe36828..0c1e25e703 100644
--- a/lib/debugger/src/dbg_ui_break_win.erl
+++ b/lib/debugger/src/dbg_ui_break_win.erl
@@ -268,12 +268,7 @@ handle_event({gs, _Id, click, _Data, ["Ok"|_]}, WinInfo) ->
IndexL ->
Funcs = WinInfo#winInfo.funcs,
Breaks =
- lists:map(fun(Index) ->
- Func = lists:nth(Index+1,
- Funcs),
- [Mod | Func]
- end,
- IndexL),
+ [[Mod|lists:nth(Index+1, Funcs)] || Index <- IndexL],
{break, Breaks, enable}
end
end;
diff --git a/lib/debugger/src/dbg_ui_filedialog_win.erl b/lib/debugger/src/dbg_ui_filedialog_win.erl
index f7d76076a5..79ccf20946 100644
--- a/lib/debugger/src/dbg_ui_filedialog_win.erl
+++ b/lib/debugger/src/dbg_ui_filedialog_win.erl
@@ -202,8 +202,7 @@ handle_event({gs, 'Files', doubleclick, _Data, _Arg}, WinInfo) ->
handle_event({gs, _Id, click, select, _Arg}, _WinInfo) ->
{select, gs:read('Selection', text)};
handle_event({gs, _Id, click, multiselect, _Arg}, WinInfo) ->
- Files = lists:map(fun(File) -> untag(File) end,
- gs:read('Files', items)),
+ Files = [untag(File) || File <- gs:read('Files', items)],
{multiselect, WinInfo#winInfo.cwd, Files};
handle_event({gs, _Id, click, filter, _Arg}, WinInfo) ->
{Cwd, Pattern} = update_win(gs:read('Filter', text),
@@ -286,7 +285,7 @@ max_existing([Name | Names]) ->
max_existing(Dir, [Name | Names]) ->
Dir2 = filename:join(Dir, Name),
case filelib:is_file(Dir2, erl_prim_loader) of
- true when Names==[] -> {Dir2, []};
+ true when Names =:= [] -> {Dir2, []};
true -> max_existing(Dir2, Names);
false -> {Dir, [Name | Names]}
end.
@@ -309,11 +308,8 @@ extra_filter([], _Dir, _Fun) -> [].
get_subdirs(Dir) ->
case erl_prim_loader:list_dir(Dir) of
{ok, FileNames} ->
- X = lists:filter(fun(FileName) ->
- File = filename:join(Dir, FileName),
- filelib:is_dir(File, erl_prim_loader)
- end,
- FileNames),
+ X = [FN || FN <- FileNames,
+ filelib:is_dir(filename:join(Dir, FN), erl_prim_loader)],
lists:sort(X);
_Error ->
[]
@@ -335,4 +331,3 @@ compare([], [$/|File]) ->
File;
compare(_, _) ->
error.
-
diff --git a/lib/debugger/src/dbg_ui_mon.erl b/lib/debugger/src/dbg_ui_mon.erl
index 8888075124..82fe210968 100644
--- a/lib/debugger/src/dbg_ui_mon.erl
+++ b/lib/debugger/src/dbg_ui_mon.erl
@@ -162,7 +162,7 @@ init2(CallingPid, Mode, SFile, GS) ->
CallingPid ! {initialization_complete, self()},
if
- SFile==default ->
+ SFile =:= default ->
loop(State3);
true ->
loop(load_settings(SFile, State3))
@@ -226,7 +226,7 @@ loop(State) ->
gui_cmd(stopped, State);
%% From the GUI
- GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent)==gs ->
+ GuiEvent when is_tuple(GuiEvent), element(1, GuiEvent) =:= gs ->
Cmd = dbg_ui_mon_win:handle_event(GuiEvent,State#state.win),
State2 = gui_cmd(Cmd, State),
loop(State2);
@@ -269,7 +269,7 @@ gui_cmd(ignore, State) ->
State;
gui_cmd(stopped, State) ->
if
- State#state.starter==true -> int:stop();
+ State#state.starter =:= true -> int:stop();
true -> int:auto_attach(false)
end,
exit(stop);
@@ -413,9 +413,9 @@ gui_cmd({'Trace Window', TraceWin}, State) ->
State2;
gui_cmd({'Auto Attach', When}, State) ->
if
- When==[] -> int:auto_attach(false);
+ When =:= [] -> int:auto_attach(false);
true ->
- Flags = lists:map(fun(Name) -> map(Name) end, When),
+ Flags = [map(Name) || Name <- When],
int:auto_attach(Flags, trace_function(State))
end,
State;
@@ -676,7 +676,7 @@ load_settings2(Settings, State) ->
Break,
int:break(Mod, Line),
if
- Status==inactive ->
+ Status =:= inactive ->
int:disable_break(Mod, Line);
true -> ignore
end,
@@ -700,12 +700,8 @@ save_settings(SFile, State) ->
int:auto_attach(),
int:stack_trace(),
State#state.backtrace,
- lists:map(fun(Mod) ->
- int:file(Mod)
- end,
- int:interpreted()),
+ [int:file(Mod) || Mod <- int:interpreted()],
int:all_breaks()},
-
Binary = term_to_binary({debugger_settings, Settings}),
case file:write_file(SFile, Binary) of
ok ->
@@ -720,13 +716,12 @@ save_settings(SFile, State) ->
%%====================================================================
registered_name(Pid) ->
-
%% Yield in order to give Pid more time to register its name
timer:sleep(200),
Node = node(Pid),
if
- Node==node() ->
+ Node =:= node() ->
case erlang:process_info(Pid, registered_name) of
{registered_name, Name} -> Name;
_ -> undefined
diff --git a/lib/debugger/src/dbg_ui_mon_win.erl b/lib/debugger/src/dbg_ui_mon_win.erl
index ba2f94c550..66e59a822a 100644
--- a/lib/debugger/src/dbg_ui_mon_win.erl
+++ b/lib/debugger/src/dbg_ui_mon_win.erl
@@ -224,8 +224,7 @@ select(MenuItem, Bool) ->
%%--------------------------------------------------------------------
add_module(WinInfo, Menu, Mod) ->
Modules = WinInfo#winInfo.modules,
- case lists:keysearch(Mod, #moduleInfo.module, Modules) of
- {value, _ModInfo} -> WinInfo;
+ case lists:keymember(Mod, #moduleInfo.module, Modules) of
false ->
%% Create a menu for the module
Font = dbg_ui_win:font(normal),
@@ -244,7 +243,8 @@ add_module(WinInfo, Menu, Mod) ->
gs:config(WinInfo#winInfo.listbox, {add, Mod}),
ModInfo = #moduleInfo{module=Mod, menubtn=MenuBtn},
- WinInfo#winInfo{modules=[ModInfo | Modules]}
+ WinInfo#winInfo{modules=[ModInfo | Modules]};
+ true -> WinInfo
end.
%%--------------------------------------------------------------------
@@ -491,14 +491,13 @@ handle_event({gs, _Id, click, autoattach, _Arg}, WinInfo) ->
%% Process grid
handle_event({gs, _Id, keypress, _Data, [Key|_]}, WinInfo) when
- Key=='Up'; Key=='Down' ->
- Dir = if Key=='Up' -> up; Key=='Down' -> down end,
+ Key =:= 'Up'; Key =:= 'Down' ->
+ Dir = if Key =:= 'Up' -> up; Key =:= 'Down' -> down end,
Row = move(WinInfo, Dir),
-
if Row>1 ->
WinInfo2 = highlight(WinInfo, Row),
- {value, #procInfo{pid=Pid}} =
- lists:keysearch(Row, #procInfo.row, WinInfo#winInfo.processes),
+ #procInfo{pid=Pid} =
+ lists:keyfind(Row, #procInfo.row, WinInfo#winInfo.processes),
{focus, Pid, WinInfo2};
true ->
ignore
@@ -515,10 +514,9 @@ handle_event(_GSEvent, _WinInfo) ->
move(WinInfo, Dir) ->
Row = WinInfo#winInfo.focus,
Last = WinInfo#winInfo.row,
-
if
- Dir==up, Row>1 -> Row-1;
- Dir==down, Row<Last -> Row+1;
+ Dir =:= up, Row > 1 -> Row-1;
+ Dir =:= down, Row < Last -> Row+1;
true -> Row
end.
@@ -533,7 +531,6 @@ highlight(WinInfo, Row) ->
GridLine2 = gs:read(Grid, {obj_at_row, Row}),
gs:config(GridLine2, {fg, white}),
WinInfo#winInfo{focus=Row}.
-
%%====================================================================
%% Internal functions
@@ -545,7 +542,7 @@ configure(WinInfo, {W, H}) ->
Dx = NewW - gs:read(Grid, width),
Dy = H-42 - gs:read(Grid, height),
if
- (Dx+Dy)=/=0 ->
+ (Dx+Dy) =/= 0 ->
gs:config(Grid, [{width, NewW}, {height, H-30}]),
Cols = calc_columnwidths(NewW),
gs:config(Grid, Cols);
@@ -555,10 +552,9 @@ configure(WinInfo, {W, H}) ->
calc_columnwidths(Width) ->
W = if
- Width=<?Wg -> ?Wg;
+ Width =< ?Wg -> ?Wg;
true -> Width
end,
- First = lists:map(fun (X) -> round(X) end,
- [0.13*W, 0.27*W, 0.18*W, 0.18*W]),
+ First = [round(X) || X <- [0.13*W, 0.27*W, 0.18*W, 0.18*W]],
Last = W - lists:sum(First) - 30,
{columnwidths, First++[Last]}.
diff --git a/lib/debugger/src/dbg_ui_trace_win.erl b/lib/debugger/src/dbg_ui_trace_win.erl
index dbf93c7f45..82d4199630 100644
--- a/lib/debugger/src/dbg_ui_trace_win.erl
+++ b/lib/debugger/src/dbg_ui_trace_win.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(dbg_ui_trace_win).
@@ -106,7 +106,7 @@ create_win(GS, Title, TraceWin, Menus) ->
gs:read('CodeArea', height) +
gs:read('RB1', height) +
gs:read('ButtonArea', height) +
- max(gs:read('EvalArea', height),
+ erlang:max(gs:read('EvalArea', height),
gs:read('BindArea', height)) +
gs:read('RB2', height) +
gs:read('TraceArea', height)}),
@@ -147,17 +147,17 @@ configure(WinInfo, TraceWin) ->
H = gs:read(Win, height),
H2 = if
- Bu1==close, Bu2==open ->
+ Bu1 =:= close, Bu2 =:= open ->
resize_button_area(open, width, W-4),
gs:config('ButtonArea', {height, 30}),
H+30;
- Bu1==open, Bu2==close ->
+ Bu1 =:= open, Bu2 =:= close ->
gs:config('ButtonArea', [{width, 0}, {height, 0}]),
H-30;
true -> H
end,
H3 = if
- Ev1==close, Ev2==open, Bi1==open ->
+ Ev1 =:= close, Ev2 =:= open, Bi1 =:= open ->
Wnew1 = round((W-10-4)/2), % W = window/2 - rb - pads
Hbi1 = gs:read('BindArea', height), % H = bind area h
resize_eval_area(open, width, Wnew1),
@@ -167,25 +167,25 @@ configure(WinInfo, TraceWin) ->
resize_bind_area(open, width,
Wnew1-gs:read('BindArea', width)),
H2;
- Ev1==close, Ev2==open, Bi1==close ->
+ Ev1 =:= close, Ev2 =:= open, Bi1 =:= close ->
resize_eval_area(open, width, W-4),
resize_eval_area(open, height, 200),
H2+200;
- Ev1==open, Ev2==close, Bi1==open ->
+ Ev1 =:= open, Ev2 =:= close, Bi1 =:= open ->
gs:config('EvalArea', [{width,0}, {height,0}]),
gs:config('RB3', [{width, 0}, {height, 0}]),
Wnew2 = W-4,
resize_bind_area(open, width,
Wnew2-gs:read('BindArea', width)),
H2;
- Ev1==open, Ev2==close, Bi1==close ->
+ Ev1 =:= open, Ev2 =:= close, Bi1 =:= close ->
Hs1 = gs:read('EvalArea', height),
gs:config('EvalArea', [{width, 0}, {height, 0}]),
H2-Hs1;
true -> H2
end,
H4 = if
- Bi1==close, Bi2==open, Ev2==open ->
+ Bi1 =:= close, Bi2 =:= open, Ev2 =:= open ->
Wnew3 = round((W-10-4)/2), % W = window/2 - rb - pads
Hs2 = gs:read('EvalArea', height), % H = eval area h
resize_bind_area(open, width, Wnew3),
@@ -194,29 +194,29 @@ configure(WinInfo, TraceWin) ->
resize_eval_area(open, width,
Wnew3-gs:read('EvalArea', width)),
H3;
- Bi1==close, Bi2==open, Ev2==close ->
+ Bi1 =:= close, Bi2 =:= open, Ev2 =:= close ->
resize_bind_area(open, width, W-4),
resize_bind_area(open, height, 200),
H3+200;
- Bi1==open, Bi2==close, Ev2==open ->
+ Bi1 =:= open, Bi2 =:= close, Ev2 =:= open ->
gs:config('BindArea', [{width, 0}, {height, 0}]),
gs:config('RB3', [{width, 0}, {height, 0}]),
Wnew4 = W-4,
resize_eval_area(open, width,
Wnew4-gs:read('EvalArea', width)),
H3;
- Bi1==open, Bi2==close, Ev2==close ->
+ Bi1 =:= open, Bi2 =:= close, Ev2 =:= close ->
Hbi2 = gs:read('BindArea', height),
gs:config('BindArea', [{width, 0}, {height, 0}]),
H3-Hbi2;
true -> H3
end,
H5 = if
- Tr1==close, Tr2==open ->
+ Tr1 =:= close, Tr2 =:= open ->
resize_trace_area(open, width, W-4),
resize_trace_area(open, height, 200),
H4+200;
- Tr1==open, Tr2==close ->
+ Tr1 =:= open, Tr2 =:= close ->
Hf = gs:read('TraceArea', height),
gs:config('TraceArea', [{width, 0}, {height, 0}]),
H4-Hf;
@@ -226,10 +226,10 @@ configure(WinInfo, TraceWin) ->
RB1old = rb1(OldFlags), RB1new = rb1(NewFlags),
if
- RB1old==close, RB1new==open ->
+ RB1old =:= close, RB1new =:= open ->
gs:config('RB1', [{width, W-4}, {height, 10}]),
gs:config(Win, {height, gs:read(Win, height)+10});
- RB1old==open, RB1new==close ->
+ RB1old =:= open, RB1new =:= close ->
gs:config('RB1', [{width, 0}, {height, 0}, lower]),
gs:config(Win, {height, gs:read(Win, height)-10});
true -> ignore
@@ -237,10 +237,10 @@ configure(WinInfo, TraceWin) ->
RB2old = rb2(OldFlags), RB2new = rb2(NewFlags),
if
- RB2old==close, RB2new==open ->
+ RB2old =:= close, RB2new =:= open ->
gs:config('RB2', [{width, W-4}, {height, 10}]),
gs:config(Win, {height,gs:read(Win, height)+10});
- RB2old==open, RB2new==close ->
+ RB2old =:= open, RB2new =:= close ->
gs:config('RB2', [{width, 0}, {height, 0}, lower]),
gs:config(Win, {height, gs:read(Win, height)-10});
true -> ignore
@@ -301,15 +301,15 @@ select(MenuItem, Bool) ->
%% Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
add_break(WinInfo, Menu, {{Mod,Line},[Status|_Options]}=Break) ->
- case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
- {value, {Mod, Editor}} ->
+ case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
+ {Mod, Editor} ->
add_break_to_code(Editor, Line, Status);
false -> ignore
end,
add_break_to_menu(WinInfo, Menu, Break).
add_break_to_code(Editor, Line, Status) ->
- Color = if Status==active -> red; Status==inactive -> blue end,
+ Color = if Status =:= active -> red; Status =:= inactive -> blue end,
config_editor(Editor, [{overwrite,{{Line,0},"-@- "}},
{fg,{{{Line,0},{Line,lineend}}, Color}}]).
@@ -330,8 +330,8 @@ add_break_to_menu(WinInfo, Menu, {Point, [Status|_Options]=Options}) ->
%% Cond = null | {Mod, Func}
%%--------------------------------------------------------------------
update_break(WinInfo, {{Mod,Line},[Status|_Options]}=Break) ->
- case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
- {value, {Mod, Editor}} ->
+ case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
+ {Mod, Editor} ->
add_break_to_code(Editor, Line, Status);
false -> ignore
end,
@@ -352,8 +352,8 @@ update_break_in_menu(WinInfo, {Point, [Status|_Options]=Options}) ->
%% Point = {Mod, Line}
%%--------------------------------------------------------------------
delete_break(WinInfo, {Mod,Line}=Point) ->
- case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
- {value, {Mod, Editor}} -> delete_break_from_code(Editor, Line);
+ case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
+ {Mod, Editor} -> delete_break_from_code(Editor, Line);
false -> ignore
end,
delete_break_from_menu(WinInfo, Point).
@@ -379,11 +379,11 @@ clear_breaks(WinInfo) ->
clear_breaks(WinInfo, all).
clear_breaks(WinInfo, Mod) ->
Remove = if
- Mod==all -> WinInfo#winInfo.breaks;
+ Mod =:= all -> WinInfo#winInfo.breaks;
true ->
lists:filter(fun(#breakInfo{point={Mod2,_L}}) ->
if
- Mod2==Mod -> true;
+ Mod2 =:= Mod -> true;
true -> false
end
end,
@@ -450,8 +450,8 @@ display(Arg) ->
%% Note: remove_code/2 should not be used for currently shown module.
%%--------------------------------------------------------------------
is_shown(WinInfo, Mod) ->
- case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
- {value, {Mod, Editor}} ->
+ case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
+ {Mod, Editor} ->
gs:config(Editor, raise),
{true, WinInfo#winInfo{editor={Mod, Editor}}};
false -> false
@@ -459,24 +459,22 @@ is_shown(WinInfo, Mod) ->
show_code(WinInfo, Mod, Contents) ->
Editors = WinInfo#winInfo.editors,
- {Flag, Editor} = case lists:keysearch(Mod, 1, Editors) of
- {value, {Mod, Ed}} -> {existing, Ed};
+ {Flag, Editor} = case lists:keyfind(Mod, 1, Editors) of
+ {Mod, Ed} -> {existing, Ed};
false -> {new, code_editor()}
end,
-
%% Insert code and update breakpoints, if any
config_editor(Editor, [raise, clear]),
show_code(Editor, Contents),
lists:foreach(fun(BreakInfo) ->
case BreakInfo#breakInfo.point of
- {Mod2, Line} when Mod2==Mod ->
+ {Mod2, Line} when Mod2 =:= Mod ->
Status = BreakInfo#breakInfo.status,
add_break_to_code(Editor, Line,Status);
_Point -> ignore
end
end,
WinInfo#winInfo.breaks),
-
case Flag of
existing ->
WinInfo#winInfo{editor={Mod, Editor}};
@@ -485,7 +483,7 @@ show_code(WinInfo, Mod, Contents) ->
editors=[{Mod, Editor} | Editors]}
end.
-show_code(Editor, Text) when length(Text)>1500 ->
+show_code(Editor, Text) when length(Text) > 1500 ->
%% Add some text at a time so that other processes may get scheduled
Str = string:sub_string(Text, 1, 1500),
config_editor(Editor, {insert,{'end', Str}}),
@@ -494,21 +492,19 @@ show_code(Editor, Text) ->
config_editor(Editor, {insert,{'end',Text}}).
show_no_code(WinInfo) ->
- {value, {'$top', Editor}} =
- lists:keysearch('$top', 1, WinInfo#winInfo.editors),
+ {'$top', Editor} = lists:keyfind('$top', 1, WinInfo#winInfo.editors),
gs:config(Editor, raise),
WinInfo#winInfo{editor={'$top', Editor}}.
remove_code(WinInfo, Mod) ->
Editors = WinInfo#winInfo.editors,
- case lists:keysearch(Mod, 1, Editors) of
- {value, {Mod, Editor}} ->
+ case lists:keyfind(Mod, 1, Editors) of
+ {Mod, Editor} ->
gs:destroy(Editor),
WinInfo#winInfo{editors=lists:keydelete(Mod, 1, Editors)};
false ->
WinInfo
end.
-
%%--------------------------------------------------------------------
%% mark_line(WinInfo, Line, How) -> WinInfo
@@ -522,7 +518,7 @@ mark_line(WinInfo, Line, How) ->
mark_line2(Editor, WinInfo#winInfo.marked_line, false),
mark_line2(Editor, Line, How),
if
- Line/=0 -> config_editor(Editor, {vscrollpos, Line-5});
+ Line =/= 0 -> config_editor(Editor, {vscrollpos, Line-5});
true -> ignore
end,
WinInfo#winInfo{marked_line=Line}.
@@ -537,7 +533,7 @@ mark_line2(Editor, Line, How) ->
false -> " "
end,
Font = if
- How==false -> dbg_ui_win:font(normal);
+ How =:= false -> dbg_ui_win:font(normal);
true -> dbg_ui_win:font(bold)
end,
config_editor(Editor, [{overwrite, {{Line,5}, Prefix}},
@@ -558,10 +554,10 @@ select_line(WinInfo, Line) ->
%% help window, it must be checked that it is correct
Size = gs:read(Editor, size),
if
- Line==0 ->
+ Line =:= 0 ->
select_line(Editor, WinInfo#winInfo.selected_line, false),
WinInfo#winInfo{selected_line=0};
- Line<Size ->
+ Line < Size ->
select_line(Editor, Line, true),
config_editor(Editor, {vscrollpos, Line-5}),
WinInfo#winInfo{selected_line=Line};
@@ -712,10 +708,10 @@ handle_event({gs, Editor, buttonpress, code_editor, _Arg}, WinInfo) ->
{Row, _} ->
{Mod, _Editor} = WinInfo#winInfo.editor,
Point = {Mod, Row},
- case lists:keysearch(Point, #breakInfo.point,
+ case lists:keymember(Point, #breakInfo.point,
WinInfo#winInfo.breaks) of
- {value, _BreakInfo} -> {break, Point, delete};
- false -> {break, Point, add}
+ false -> {break, Point, add};
+ true -> {break, Point, delete}
end;
{Row2, _} ->
select_line(Editor, Row2, true),
@@ -776,7 +772,7 @@ code_editor() ->
code_editor(Name, W, H) ->
Editor = if
- Name==null -> gs:editor('CodeArea', []);
+ Name =:= null -> gs:editor('CodeArea', []);
true -> gs:editor(Name, 'CodeArea', [])
end,
gs:config(Editor, [{x,5}, {y,30}, {width,W}, {height,H},
@@ -814,8 +810,8 @@ buttons() ->
{'Where','WhereButton'}, {'Up','UpButton'}, {'Down','DownButton'}].
is_button(Name) ->
- case lists:keysearch(Name, 1, buttons()) of
- {value, {Name, Button}} -> {true, Button};
+ case lists:keyfind(Name, 1, buttons()) of
+ {Name, Button} -> {true, Button};
false -> false
end.
@@ -847,7 +843,7 @@ resize_button_area(open, width, Diff) ->
eval_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
{W,H} = if
- Ev==open -> {289,200};
+ Ev =:= open -> {289,200};
true -> {0,0}
end,
Font = dbg_ui_win:font(normal),
@@ -870,7 +866,7 @@ eval_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
{font_style,{{{1,0},'end'},Font}}]),
gs:config('EvalEditor', {enable, false}),
if
- Ev==open, Bi==close -> resize_eval_area(Ev, width, 257);
+ Ev =:= open, Bi =:= close -> resize_eval_area(Ev, width, 257);
true -> ignore
end.
@@ -891,7 +887,7 @@ resize_eval_area(open, Key, Diff) ->
bind_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
{W,H} = if
- Bi==open -> {249,200};
+ Bi =:= open -> {249,200};
true -> {0,0}
end,
gs:frame('BindArea', Win,
@@ -908,7 +904,7 @@ bind_area({Ev,Bi}, X, Y, FrameOpts, Win) ->
{text,{1,"Name"}}, {text,{2,"Value"}}, {font,Font}]),
gs:config('BindGrid', {rows,{1,1}}),
if
- Bi==open, Ev==close -> resize_bind_area(Bi, width, 297);
+ Bi =:= open, Ev =:= close -> resize_bind_area(Bi, width, 297);
true -> ignore
end.
@@ -993,15 +989,15 @@ resizebar(Flag, Name, X, Y, W, H, Obj) ->
rb1({_Bu,Ev,Bi,Tr}) ->
if
- Ev==close, Bi==close, Tr==close -> close;
+ Ev =:= close, Bi =:= close, Tr =:= close -> close;
true -> open
end.
rb2({_Bu,Ev,Bi,Tr}) ->
if
- Tr==open ->
+ Tr =:= open ->
if
- Ev==close, Bi==close -> close;
+ Ev =:= close, Bi =:= close -> close;
true -> open
end;
true -> close
@@ -1009,7 +1005,7 @@ rb2({_Bu,Ev,Bi,Tr}) ->
rb3({_Bu,Ev,Bi,_Tr}) ->
if
- Ev==open, Bi==open -> open;
+ Ev =:= open, Bi =:= open -> open;
true -> close
end.
@@ -1032,7 +1028,7 @@ config_v() ->
gs:config('RB3', {y,Y3}),
gs:config('BindArea', {y,Y3}),
- Y4 = Y3 + max(gs:read('EvalArea', height),
+ Y4 = Y3 + erlang:max(gs:read('EvalArea', height),
gs:read('BindArea', height)),
gs:config('RB2', {y,Y4}),
@@ -1061,13 +1057,13 @@ configure(WinInfo, NewW, NewH) ->
OldH = 25+gs:read('CodeArea', height)+
gs:read('RB1', height)+
gs:read('ButtonArea', height)+
- max(gs:read('EvalArea', height), gs:read('BindArea', height))+
+ erlang:max(gs:read('EvalArea', height), gs:read('BindArea', height))+
gs:read('RB2', height)+
gs:read('TraceArea', height),
%% Adjust width unless it is unchanged or less than minimum width
if
- OldW/=NewW ->
+ OldW =/= NewW ->
{Dcode,Deval,Dbind} = configure_widths(OldW,NewW,Flags),
resize_code_area(WinInfo, width, Dcode),
case rb1(Flags) of
@@ -1090,7 +1086,7 @@ configure(WinInfo, NewW, NewH) ->
%% Adjust height unless it is unchanged or less than minimum height
if
- OldH/=NewH ->
+ OldH =/= NewH ->
{Dcode2,Deval2,Dtrace} = configure_heights(OldH,NewH,Flags),
resize_code_area(WinInfo, height, Dcode2),
resize_eval_area(Ev, height, Deval2),
@@ -1112,16 +1108,16 @@ configure_widths(OldW, NewW, Flags) ->
{_Bu,Ev,Bi,_Tr} = Flags,
%% Difference between old and new width, considering min window width
- Diff = abs(max(OldW,330)-max(NewW,330)),
+ Diff = abs(erlang:max(OldW,330)-erlang:max(NewW,330)),
%% Check how much the frames can be resized in reality
Limits = if
%% Window larger
- NewW>OldW ->
+ NewW > OldW ->
if
- Ev==open,Bi==open -> {0,Diff,Diff};
- Ev==open -> {0,Diff,0};
- Bi==open -> {0,0,Diff};
+ Ev =:= open, Bi =:= open -> {0,Diff,Diff};
+ Ev =:= open -> {0,Diff,0};
+ Bi =:= open -> {0,0,Diff};
true -> {Diff,0,0}
end;
@@ -1129,12 +1125,12 @@ configure_widths(OldW, NewW, Flags) ->
%% and current size
OldW>NewW ->
if
- Ev==open,Bi==open ->
+ Ev =:= open, Bi =:= open ->
{0,
gs:read('EvalArea',width)-204,
gs:read('BindArea',width)-112};
- Ev==open -> {0,Diff,0};
- Bi==open -> {0,0,Diff};
+ Ev =:= open -> {0,Diff,0};
+ Bi =:= open -> {0,0,Diff};
true -> {Diff,0,0}
end
end,
@@ -1142,13 +1138,13 @@ configure_widths(OldW, NewW, Flags) ->
case Limits of
%% No Shell or Bind frame, larger window
- {T,0,0} when NewW>OldW -> {T,0,0};
+ {T,0,0} when NewW > OldW -> {T,0,0};
%% No Shell or Bind frame, smaller window
- {T,0,0} when OldW>NewW -> {-T,0,0};
+ {T,0,0} when OldW > NewW -> {-T,0,0};
%% Window larger; divide Diff among the frames and return result
- {_,Sf,B} when NewW>OldW ->
+ {_,Sf,B} when NewW > OldW ->
{_,Sf2,B2} = divide([{0,0},{0,Sf},{0,B}],Diff),
{Sf2+B2,Sf2,B2};
@@ -1166,38 +1162,38 @@ configure_heights(OldH, NewH, Flags) ->
%% Difference between old and new height, considering min win height
MinH = min_height(Flags),
- Diff = abs(max(OldH,MinH)-max(NewH,MinH)),
+ Diff = abs(erlang:max(OldH,MinH)-erlang:max(NewH,MinH)),
%% Check how much the frames can be resized in reality
{T,Sf,Ff} = if
%% Window larger
- NewH>OldH ->
+ NewH > OldH ->
{Diff,
if
- Ev==close, Bi==close -> 0;
+ Ev =:= close, Bi =:= close -> 0;
true -> Diff
end,
if
- Tr==open -> Diff;
+ Tr =:= open -> Diff;
true -> 0
end};
%% Window smaller; get difference between min size
%% and current size
- OldH>NewH ->
+ OldH > NewH ->
{gs:read('CodeArea',height)-100,
if
- Ev==close, Bi==close -> 0;
+ Ev =:= close, Bi =:= close -> 0;
true ->
if
- Ev==open ->
+ Ev =:= open ->
gs:read('EvalArea',height)-100;
- Bi==open ->
+ Bi =:= open ->
gs:read('BindArea',height)-100
end
end,
if
- Tr==open -> gs:read('TraceArea',height)-100;
+ Tr =:= open -> gs:read('TraceArea',height)-100;
true -> 0
end}
end,
@@ -1251,10 +1247,8 @@ divide(L, Diff) ->
if
%% All of Diff has been distributed
- D==0 -> {T,S,F};
-
+ D =:= 0 -> {T,S,F};
true ->
-
%% For each element, try to add as much as possible of D
{NewT,Dt} = divide2(D,T,Tmax),
{NewS,Ds} = divide2(D,S,Smax),
@@ -1296,25 +1290,25 @@ resize(WinInfo, ResizeBar) ->
rblimits('RB2',W,H),
rblimits('RB3',W,H)).
-resizeloop(WI, RB, Prev, {Min1,Max1},{Min2,Max2},{Min3,Max3}) ->
+resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3}) ->
receive
- {gs,_,motion,_,[_,Y]} when RB=='RB1', Y>Min1,Y<Max1 ->
+ {gs,_,motion,_,[_,Y]} when RB =:= 'RB1', Y > Min1, Y < Max1 ->
gs:config('RB1', {y,Y}),
- resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3});
- {gs,_,motion,_,_} when RB=='RB1' ->
- resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3});
+ resizeloop(WI, RB, Y, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
+ {gs,_,motion,_,_} when RB =:= 'RB1' ->
+ resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
- {gs,_,motion,_,[_,Y]} when RB=='RB2', Y>Min2,Y<Max2 ->
+ {gs,_,motion,_,[_,Y]} when RB =:= 'RB2', Y > Min2, Y < Max2 ->
gs:config('RB2', {y,Y}),
- resizeloop(WI, RB, Y, {Min1,Max1},{Min2,Max2},{Min3,Max3});
- {gs,_,motion,_,_} when RB=='RB2' ->
- resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3});
+ resizeloop(WI, RB, Y, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
+ {gs,_,motion,_,_} when RB =:= 'RB2' ->
+ resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
- {gs,_,motion,_,[X,_]} when RB=='RB3', X>Min3,X<Max3 ->
+ {gs,_,motion,_,[X,_]} when RB =:= 'RB3', X > Min3, X < Max3 ->
gs:config('RB3', {x,X}),
- resizeloop(WI, RB, X, {Min1,Max1},{Min2,Max2},{Min3,Max3});
- {gs,_,motion,_,_} when RB=='RB3' ->
- resizeloop(WI, RB, Prev,{Min1,Max1},{Min2,Max2},{Min3,Max3});
+ resizeloop(WI, RB, X, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
+ {gs,_,motion,_,_} when RB =:= 'RB3' ->
+ resizeloop(WI, RB, Prev, {Min1,Max1}, {Min2,Max2}, {Min3,Max3});
{gs,_,buttonrelease,_,_} ->
resize_win(WI, RB, Prev)
@@ -1329,7 +1323,7 @@ resize_win(WinInfo, 'RB1', Y) ->
%% Resize Code, Evaluator and Binding areas
resize_code_area(WinInfo, height, -Diff),
if
- S==close, Bi==close, F==open ->
+ S =:= close, Bi =:= close, F =:= open ->
resize_trace_area(open, height, Diff);
true ->
resize_eval_area(S, height, Diff),
@@ -1388,37 +1382,27 @@ rblimits('RB1',_W,H) ->
RB2 = gs:read('RB2',height),
FF = gs:read('TraceArea',height),
Max = case RB2 of
- 0 when FF/=0 ->
+ 0 when FF =/= 0 ->
H-112;
_ ->
Y = gs:read('RB2',y),
- max(Min,Y-140)
+ erlang:max(Min,Y-140)
end,
{Min,Max};
rblimits('RB2',_W,H) ->
-
- %% TraceFrame should not have height <100
+ %% TraceFrame should not have height < 100
Max = H-112,
-
%% Min is decided by a minimum distance to 'RB1'
Y = gs:read('RB1',y),
- Min = min(Max,Y+140),
-
+ Min = erlang:min(Max,Y+140),
{Min,Max};
rblimits('RB3',W,_H) ->
-
%% Neither CodeArea nor BindArea should occupy
%% less than 1/3 of the total window width and EvalFrame should
%% be at least 289 pixels wide
- {max(round(W/3),289),round(2*W/3)}.
-
-max(A, B) when A>B -> A;
-max(_A, B) -> B.
-
-min(A, B) when A<B -> A;
-min(_A, B) -> B.
+ {erlang:max(round(W/3),289),round(2*W/3)}.
%%====================================================================
@@ -1490,7 +1474,7 @@ helpwin_action(gotoline, default, AttPid, _Editor, Data, Win) ->
end,
Data;
helpwin_action(search, case_sensitive, _AttPid, _Ed, {Pos, CS}, _Win) ->
- Bool = if CS==true -> false; CS==false -> true end,
+ Bool = if CS =:= true -> false; CS =:= false -> true end,
{Pos, Bool};
helpwin_action(search, default, _AttPid, Editor, {Pos, CS}, Win) ->
gs:config(lbl(Win), {label, {text, ""}}),
@@ -1523,13 +1507,9 @@ search(Str, Editor, Max, {Row, Col}, CS) ->
lowercase(true, Str) -> Str;
lowercase(false, Str) ->
- lists:map(fun(Char) ->
- if
- Char>=$A, Char=<$Z -> Char+32;
- true -> Char
- end
- end,
- Str).
+ [if Char >= $A, Char =< $Z -> Char+32;
+ true -> Char
+ end || Char <- Str].
mark_string(Editor, {Row, Col}, Str) ->
Between = {{Row,Col}, {Row,Col+length(Str)}},
@@ -1546,10 +1526,9 @@ unmark_string(Editor, {Row, Col}) ->
{fg, {Between, black}}]).
helpwin(Type, GS, {X, Y}) ->
- W = 200, Pad=10, Wbtn = 50,
+ W = 200, Pad = 10, Wbtn = 50,
- Title =
- case Type of search -> "Search"; gotoline -> "Go To Line" end,
+ Title = case Type of search -> "Search"; gotoline -> "Go To Line" end,
Win = gs:window(GS, [{title, Title}, {x, X}, {y, Y}, {width, W},
{destroy, true}]),
diff --git a/lib/debugger/src/dbg_ui_view.erl b/lib/debugger/src/dbg_ui_view.erl
index 075275f196..7350a830a8 100644
--- a/lib/debugger/src/dbg_ui_view.erl
+++ b/lib/debugger/src/dbg_ui_view.erl
@@ -21,9 +21,6 @@
%% External exports
-export([start/2]).
-%% Internal exports
--export([init/3]).
-
-record(state, {gs, % term() Graphics system id
win, % term() Attach process window data
coords, % {X,Y} Mouse point position
@@ -42,7 +39,7 @@ start(GS, Mod) ->
Title = "View Module " ++ atom_to_list(Mod),
case dbg_ui_winman:is_started(Title) of
true -> ignore;
- false -> spawn(?MODULE, init, [GS, Mod, Title])
+ false -> spawn(fun () -> init(GS, Mod, Title) end)
end.
@@ -51,7 +48,6 @@ start(GS, Mod) ->
%%====================================================================
init(GS, Mod, Title) ->
-
%% Subscribe to messages from the interpreter
int:subscribe(),
diff --git a/lib/debugger/src/dbg_ui_win.erl b/lib/debugger/src/dbg_ui_win.erl
index 9840aa54da..9bed6a1ec5 100644
--- a/lib/debugger/src/dbg_ui_win.erl
+++ b/lib/debugger/src/dbg_ui_win.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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(dbg_ui_win).
@@ -24,7 +24,6 @@
create_menus/2, select/2, selected/1,
add_break/2, update_break/2, delete_break/1,
motion/2
-
]).
-record(break, {mb, smi, emi, dimi, demi}).
@@ -49,11 +48,11 @@ init() ->
font(Style) ->
GS = init(),
Style2 = if
- Style==normal -> [];
+ Style =:= normal -> [];
true -> [Style]
end,
case gs:read(GS, {choose_font, {screen,Style2,12}}) of
- Font when element(1, Font)==screen ->
+ Font when element(1, Font) =:= screen ->
Font;
_ ->
gs:read(GS, {choose_font, {courier,Style2,12}})
@@ -76,13 +75,10 @@ min_size(Font, Strings, MinW, MinH) ->
min_size(GS, Font, [String|Strings], MinW, MinH) ->
{W, H} = gs:read(GS, {font_wh, {Font, String}}),
- min_size(GS, Font, Strings, max(MinW, W), max(MinH, H));
+ min_size(GS, Font, Strings, erlang:max(MinW, W), erlang:max(MinH, H));
min_size(_GS, _Font, [], W, H) ->
{W, H}.
-max(X, Y) when X>Y -> X;
-max(_X, Y) -> Y.
-
%%--------------------------------------------------------------------
%% create_menus(MenuBar, [Menu])
%% MenuBar = gsobj()
@@ -171,8 +167,7 @@ select(MenuItem, Bool) ->
%%--------------------------------------------------------------------
selected(Menu) ->
Children = gs:read(Menu, children),
- Selected = lists:filter(fun(Child) -> gs:read(Child, select) end,
- Children),
+ Selected = [gs:read(Child, select) || Child <- Children],
lists:map(fun(Child) ->
{text, Name} = gs:read(Child, label),
list_to_atom(Name)
diff --git a/lib/debugger/src/dbg_ui_winman.erl b/lib/debugger/src/dbg_ui_winman.erl
index 71023cd0d6..398735a7ca 100644
--- a/lib/debugger/src/dbg_ui_winman.erl
+++ b/lib/debugger/src/dbg_ui_winman.erl
@@ -120,9 +120,9 @@ init(_Arg) ->
{ok, #state{}}.
handle_call({is_started, Title}, _From, State) ->
- Reply = case lists:keysearch(Title, #win.title, State#state.wins) of
- {value, Win} -> {true, Win#win.win};
- false -> false
+ Reply = case lists:keyfind(Title, #win.title, State#state.wins) of
+ false -> false;
+ Win -> {true, Win#win.win}
end,
{reply, Reply, State}.
@@ -134,8 +134,8 @@ handle_cast({insert, Pid, Title, Win}, State) ->
handle_cast({clear_process, Title}, State) ->
OldWins = State#state.wins,
- Wins = case lists:keysearch(Title, #win.title, OldWins) of
- {value, #win{owner=Pid}} ->
+ Wins = case lists:keyfind(Title, #win.title, OldWins) of
+ #win{owner=Pid} ->
Msg = {dbg_ui_winman, destroy},
Pid ! Msg,
lists:keydelete(Title, #win.title, OldWins);
@@ -147,7 +147,7 @@ handle_cast({clear_process, Title}, State) ->
handle_info({'EXIT', Pid, _Reason}, State) ->
[Mon | _Wins] = State#state.wins,
if
- Pid==Mon#win.owner -> {stop, normal, State};
+ Pid =:= Mon#win.owner -> {stop, normal, State};
true ->
Wins2 = lists:keydelete(Pid, #win.owner, State#state.wins),
inform_all(Wins2),
diff --git a/lib/debugger/src/dbg_wx_break_win.erl b/lib/debugger/src/dbg_wx_break_win.erl
index 5dafb0fbe6..78733c98c8 100644
--- a/lib/debugger/src/dbg_wx_break_win.erl
+++ b/lib/debugger/src/dbg_wx_break_win.erl
@@ -206,11 +206,7 @@ handle_event(#wx{id=OKorListBox, event=#wxCommand{type=OkorDoubleClick}},
OkorDoubleClick =:= command_listbox_doubleclicked ->
Mod = wxComboBox:getValue(Text),
{_, IndexL} = wxListBox:getSelections(LB),
- Breaks = lists:map(fun(Index) ->
- Func = lists:nth(Index+1, Funcs),
- [list_to_atom(Mod) | Func]
- end,
- IndexL),
+ Breaks = [[list_to_atom(Mod)|lists:nth(Index+1, Funcs)] || Index <- IndexL],
wxDialog:destroy(Win),
{break, Breaks, enable};
handle_event(#wx{id=?wxID_OK},#winInfo{win=Win,text=Text, entries=Es, trigger=Trigger}) ->
diff --git a/lib/debugger/src/dbg_wx_interpret.erl b/lib/debugger/src/dbg_wx_interpret.erl
index f711ba679d..ffcfbcf36b 100644
--- a/lib/debugger/src/dbg_wx_interpret.erl
+++ b/lib/debugger/src/dbg_wx_interpret.erl
@@ -115,11 +115,11 @@ interpret_all(Dir, [File0|Files], Mode, Window, Errors) ->
interpret_all(_Dir, [], _Mode, _Window, []) ->
true;
interpret_all(Dir, [], _Mode, Window, Errors) ->
- Msg = lists:map(fun(Name) ->
- File = filename:join(Dir, Name),
- Error = format_error(int:interpretable(File)),
- ["\n ",Name,": ",Error]
- end, Errors),
+ Msg = [begin
+ File = filename:join(Dir, Name),
+ Error = format_error(int:interpretable(File)),
+ ["\n ",Name,": ",Error]
+ end || Name <- Errors],
All = ["Error when interpreting: ", Msg],
dbg_wx_win:confirm(Window, lists:flatten(All)),
true.
diff --git a/lib/debugger/src/dbg_wx_mon.erl b/lib/debugger/src/dbg_wx_mon.erl
index 3f55c38d35..6bdec994b1 100644
--- a/lib/debugger/src/dbg_wx_mon.erl
+++ b/lib/debugger/src/dbg_wx_mon.erl
@@ -170,7 +170,7 @@ init2(CallingPid, Mode, SFile, GS) ->
CallingPid ! {initialization_complete, self()},
if
- SFile==default ->
+ SFile =:= default ->
loop(State3);
true ->
loop(load_settings(SFile, State3))
@@ -268,7 +268,7 @@ gui_cmd(ignore, State) ->
State;
gui_cmd(stopped, State) ->
if
- State#state.starter==true -> int:stop();
+ State#state.starter =:= true -> int:stop();
true -> int:auto_attach(false)
end,
exit(stop);
@@ -420,9 +420,9 @@ gui_cmd({'Trace Window', TraceWin}, State) ->
State2;
gui_cmd({'Auto Attach', When}, State) ->
if
- When==[] -> int:auto_attach(false);
+ When =:= [] -> int:auto_attach(false);
true ->
- Flags = lists:map(fun(Name) -> map(Name) end, When),
+ Flags = [map(Name) || Name <- When],
int:auto_attach(Flags, trace_function(State))
end,
State;
@@ -691,12 +691,12 @@ load_settings2(Settings, State) ->
Break,
int:break(Mod, Line),
if
- Status==inactive ->
+ Status =:= inactive ->
int:disable_break(Mod, Line);
true -> ignore
end,
if
- Action/=enable ->
+ Action =/= enable ->
int:action_at_break(Mod,Line,Action);
true -> ignore
end,
@@ -715,10 +715,7 @@ save_settings(SFile, State) ->
int:auto_attach(),
int:stack_trace(),
State#state.backtrace,
- lists:map(fun(Mod) ->
- int:file(Mod)
- end,
- int:interpreted()),
+ [int:file(Mod) || Mod <- int:interpreted()],
int:all_breaks()},
Binary = term_to_binary({debugger_settings, Settings}),
@@ -741,7 +738,7 @@ registered_name(Pid) ->
Node = node(Pid),
if
- Node==node() ->
+ Node =:= node() ->
case erlang:process_info(Pid, registered_name) of
{registered_name, Name} -> Name;
_ -> undefined
diff --git a/lib/debugger/src/dbg_wx_mon_win.erl b/lib/debugger/src/dbg_wx_mon_win.erl
index 8ad4f4213f..04c3501b8c 100644
--- a/lib/debugger/src/dbg_wx_mon_win.erl
+++ b/lib/debugger/src/dbg_wx_mon_win.erl
@@ -266,8 +266,7 @@ select(MenuItem, Bool) ->
add_module(WinInfo, MenuName, Mod) ->
Win = WinInfo#winInfo.window,
Modules = WinInfo#winInfo.modules,
- case lists:keysearch(Mod, #moduleInfo.module, Modules) of
- {value, _ModInfo} -> WinInfo;
+ case lists:keymember(Mod, #moduleInfo.module, Modules) of
false ->
%% Create a menu for the module
Menu = get(MenuName),
@@ -284,8 +283,9 @@ add_module(WinInfo, MenuName, Mod) ->
wxListBox:append(WinInfo#winInfo.listbox, atom_to_list(Mod)),
ModInfo = #moduleInfo{module=Mod, menubtn={Menu,MenuBtn}},
- WinInfo#winInfo{modules=[ModInfo | Modules]}
- end.
+ WinInfo#winInfo{modules=[ModInfo | Modules]};
+ true -> WinInfo
+ end.
%%--------------------------------------------------------------------
%% delete_module(WinInfo, Mod) -> WinInfo
@@ -559,8 +559,7 @@ handle_event(#wx{event=#wxCommand{type=command_checkbox_clicked}},
handle_event(#wx{event=#wxList{type=command_list_item_selected,
itemIndex=Row}}, WinInfo) ->
#winInfo{processes=Pids} = WinInfo,
- {value, #procInfo{pid=Pid}} =
- lists:keysearch(Row, #procInfo.row, Pids),
+ #procInfo{pid=Pid} = lists:keyfind(Row, #procInfo.row, Pids),
{focus, Pid, WinInfo#winInfo{focus=Row}};
handle_event(#wx{event=#wxList{type=command_list_item_activated}},
_WinInfo) ->
diff --git a/lib/debugger/src/dbg_wx_trace.erl b/lib/debugger/src/dbg_wx_trace.erl
index f9fdf593c4..6675ea33e7 100644
--- a/lib/debugger/src/dbg_wx_trace.erl
+++ b/lib/debugger/src/dbg_wx_trace.erl
@@ -180,12 +180,12 @@ init_contents(Breaks, State) ->
State#state{win=Win}.
-loop(#state{meta=Meta} = State) ->
+loop(#state{meta=Meta, win=Win} = State) ->
receive
%% From the GUI main window
- GuiEvent when element(1, GuiEvent)==wx ->
+ GuiEvent when element(1, GuiEvent) =:= wx ->
Cmd = wx:batch(fun() ->
- dbg_wx_trace_win:handle_event(GuiEvent,State#state.win)
+ dbg_wx_trace_win:handle_event(GuiEvent,Win)
end),
State2 = gui_cmd(Cmd, State),
loop(State2);
@@ -211,11 +211,11 @@ loop(#state{meta=Meta} = State) ->
%% From the dbg_wx_winman process (Debugger window manager)
{dbg_ui_winman, update_windows_menu, Data} ->
- Window = dbg_wx_trace_win:get_window(State#state.win),
+ Window = dbg_wx_trace_win:get_window(Win),
dbg_wx_winman:update_windows_menu(Window,Data),
loop(State);
{dbg_ui_winman, destroy} ->
- dbg_wx_trace_win:stop(State#state.win),
+ dbg_wx_trace_win:stop(Win),
exit(stop)
end.
@@ -269,7 +269,7 @@ gui_cmd('Continue', State) ->
int:meta(State#state.meta, continue),
{Status, Mod, Line} = State#state.status,
if
- Status==wait_break ->
+ Status =:= wait_break ->
Win = dbg_wx_trace_win:unmark_line(State#state.win),
gui_enable_functions(wait_running),
State#state{win=Win, status={wait_running,Mod,Line}};
@@ -291,7 +291,7 @@ gui_cmd('Stop', State) ->
int:meta(State#state.meta, stop),
{Status, Mod, Line} = State#state.status,
if
- Status==wait_running ->
+ Status =:= wait_running ->
Win = dbg_wx_trace_win:mark_line(State#state.win, Line,
break),
gui_enable_functions(wait_break),
@@ -421,7 +421,7 @@ gui_cmd('Function Break...', State) ->
gui_cmd('Enable All', State) ->
Breaks = int:all_breaks(),
ThisMod = State#state.cm,
- lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod =:= ThisMod ->
int:enable_break(Mod, Line);
(_Break) ->
ignore
@@ -431,7 +431,7 @@ gui_cmd('Enable All', State) ->
gui_cmd('Disable All', State) ->
Breaks = int:all_breaks(),
ThisMod = State#state.cm,
- lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod =:= ThisMod ->
int:disable_break(Mod, Line);
(_Break) ->
ignore
@@ -458,7 +458,7 @@ gui_cmd({'Trace Window', TraceWin}, State) ->
Win = dbg_wx_trace_win:configure(State#state.win, TraceWin),
{Status,_,_} = State#state.status,
if
- Status==break; Status==wait_break ->
+ Status =:= break; Status =:= wait_break ->
gui_enable_btrace(Trace, State#state.stack_trace);
true -> ignore
end,
@@ -467,7 +467,7 @@ gui_cmd({'Stack Trace', [Name]}, State) ->
int:meta(State#state.meta, stack_trace, map(Name)),
{Status,_,_} = State#state.status,
if
- Status==break; Status==wait_break ->
+ Status =:= break; Status =:= wait_break ->
gui_enable_btrace(State#state.trace, map(Name));
true -> ignore
end,
@@ -490,9 +490,9 @@ gui_cmd('Debugger', State) ->
gui_cmd({user_command, Cmd}, State) ->
{Status, _Mod, _Line} = State#state.status,
if
- Status==break;
- Status==wait_break;
- Status==wait_running ->
+ Status =:= break;
+ Status =:= wait_break;
+ Status =:= wait_running ->
Cm = State#state.cm,
Arg = case State#state.stack of
{Cur, Max} when Cur<Max -> {Cm, Cmd, Cur};
@@ -531,14 +531,14 @@ add_break(WI, Coords, Type, Mod, Line) ->
int_cmd({interpret, Mod}, State) ->
if
- Mod==State#state.cm ->
+ Mod =:= State#state.cm ->
State#state{cm_obsolete=true};
true ->
State
end;
int_cmd({no_interpret, Mod}, State) ->
if
- Mod==State#state.cm ->
+ Mod =:= State#state.cm ->
State#state{cm_obsolete=true};
true ->
Win = dbg_wx_trace_win:remove_code(State#state.win, Mod),
@@ -584,7 +584,7 @@ meta_cmd({re_entry, dbg_ieval, eval_fun}, State) ->
meta_cmd({re_entry, Mod, _Func}, State) ->
Obs = State#state.cm_obsolete,
case State#state.cm of
- Mod when Obs==true ->
+ Mod when Obs =:= true ->
Win = gui_load_module(State#state.win, Mod,State#state.pid),
State#state{win=Win, cm_obsolete=false};
Mod -> State;
@@ -630,11 +630,11 @@ meta_cmd({func_at, Mod, Line, Cur}, State) ->
gui_enable_functions(idle),
dbg_wx_trace_win:display(State#state.win, idle),
State#state{win=Win, cm=Mod, status={idle,Mod,Line}, stack=Stack};
-meta_cmd({wait_at, Mod, Line, Cur}, #state{status={Status,_,_}}=State)
- when Status/=init, Status/=break ->
+meta_cmd({wait_at, Mod, Line, Cur}, #state{status={Status,_,_}, win=Win}=State)
+ when Status =/= init, Status =/= break ->
Stack = {Cur,Cur},
gui_enable_functions(wait_running),
- dbg_wx_trace_win:display(State#state.win, {wait,Mod,Line}),
+ dbg_wx_trace_win:display(Win, {wait,Mod,Line}),
State#state{status={wait_running,Mod,Line}, stack=Stack};
meta_cmd({wait_at, Mod, Line, Cur}, State) ->
Stack = {Cur,Cur},
@@ -675,7 +675,7 @@ meta_cmd({stack_trace, Flag}, State) ->
gui_enable_updown(Flag, State#state.stack),
{Status,_,_} = State#state.status,
if
- Status==break; Status==wait_break ->
+ Status =:= break; Status =:= wait_break ->
gui_enable_btrace(State#state.trace, Flag);
true -> ignore
end,
@@ -813,7 +813,7 @@ gui_enable_functions(Status) ->
gui_enable_updown(Flag, Stack) ->
{Enable, Disable} =
if
- Flag==false -> {[], ['Up', 'Down']};
+ Flag =:= false -> {[], ['Up', 'Down']};
true ->
case Stack of
{1,1} -> {[], ['Up', 'Down']};
@@ -826,14 +826,14 @@ gui_enable_updown(Flag, Stack) ->
dbg_wx_trace_win:enable(Enable, true),
dbg_wx_trace_win:enable(Disable, false),
if
- Enable==[] -> dbg_wx_trace_win:enable(['Where'], false);
+ Enable =:= [] -> dbg_wx_trace_win:enable(['Where'], false);
true -> dbg_wx_trace_win:enable(['Where'], true)
end.
gui_enable_btrace(Trace, StackTrace) ->
Bool = if
- Trace==false -> false;
- StackTrace==false -> false;
+ Trace =:= false -> false;
+ StackTrace =:= false -> false;
true -> true
end,
dbg_wx_trace_win:enable(['Back Trace'], Bool).
diff --git a/lib/debugger/src/dbg_wx_trace_win.erl b/lib/debugger/src/dbg_wx_trace_win.erl
index 3799acdc1b..720b913024 100755
--- a/lib/debugger/src/dbg_wx_trace_win.erl
+++ b/lib/debugger/src/dbg_wx_trace_win.erl
@@ -410,11 +410,11 @@ clear_breaks(WinInfo) ->
clear_breaks(WinInfo, all).
clear_breaks(WinInfo, Mod) ->
Remove = if
- Mod==all -> WinInfo#winInfo.breaks;
+ Mod =:= all -> WinInfo#winInfo.breaks;
true ->
lists:filter(fun(#breakInfo{point={Mod2,_L}}) ->
if
- Mod2==Mod -> true;
+ Mod2 =:= Mod -> true;
true -> false
end
end,
@@ -481,8 +481,8 @@ display(#winInfo{window=Win, sb=Sb},Arg) ->
%% Note: remove_code/2 should not be used for currently shown module.
%%--------------------------------------------------------------------
is_shown(WinInfo, Mod) ->
- case lists:keysearch(Mod, 1, WinInfo#winInfo.editors) of
- {value, {Mod, Editor}} ->
+ case lists:keyfind(Mod, 1, WinInfo#winInfo.editors) of
+ {Mod, Editor} ->
gs:config(Editor, raise), %% BUGBUG
{true, WinInfo#winInfo{editor={Mod, Editor}}};
false -> false
@@ -494,7 +494,7 @@ show_code(WinInfo = #winInfo{editor={_, Ed}}, Mod, Contents) ->
lists:foreach(fun(BreakInfo) ->
case BreakInfo#breakInfo.point of
- {Mod2, Line} when Mod2==Mod ->
+ {Mod2, Line} when Mod2 =:= Mod ->
Status = BreakInfo#breakInfo.status,
dbg_wx_code:add_break_to_code(Ed, Line,Status);
_Point -> ignore
@@ -540,10 +540,10 @@ select_line(WinInfo, Line) ->
%% help window, it must be checked that it is correct
Size = dbg_wx_code:get_no_lines(Ed),
if
- Line==0 ->
+ Line =:= 0 ->
dbg_wx_code:goto_line(Ed,1),
WinInfo#winInfo{selected_line=0};
- Line<Size ->
+ Line < Size ->
dbg_wx_code:goto_line(Ed,Line),
WinInfo#winInfo{selected_line=Line};
true ->
@@ -632,7 +632,7 @@ handle_event(#wx{id=?SASH_CODE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) ->
Change = CH - H,
ChangeH = fun(Item) ->
{ItemW, ItemH} = wxSizerItem:getMinSize(Item),
- wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1))
+ wxSizerItem:setInitSize(Item, ItemW, erlang:max(ItemH+Change,-1))
end,
if Enable ->
{IW, IH} = wxSizer:getMinSize(InfoSzr),
@@ -694,7 +694,7 @@ handle_event(#wx{id=?SASH_TRACE, event=#wxSash{dragRect={_X,_Y,_W,H}}}, Wi) ->
true -> %% Change the Eval and Bindings area
ChangeH = fun(Item) ->
{ItemW, ItemH} = wxSizerItem:getMinSize(Item),
- wxSizerItem:setInitSize(Item, ItemW, max(ItemH+Change,-1))
+ wxSizerItem:setInitSize(Item, ItemW, erlang:max(ItemH+Change,-1))
end,
{IW, IH} = wxSizer:getMinSize(InfoSzr),
[ChangeH(Child) || Child <- wxSizer:getChildren(InfoSzr)],
@@ -764,8 +764,8 @@ handle_event(#wx{event=#wxStyledText{type=stc_doubleclick}},
WinInfo = #winInfo{editor={Mod,Ed}}) ->
Line = wxStyledTextCtrl:getCurrentLine(Ed),
Point = {Mod, Line+1},
- case lists:keysearch(Point, #breakInfo.point,WinInfo#winInfo.breaks) of
- {value, _BreakInfo} -> {break, Point, delete};
+ case lists:keymember(Point, #breakInfo.point, WinInfo#winInfo.breaks) of
+ true -> {break, Point, delete};
false -> {break, Point, add}
end;
@@ -837,7 +837,7 @@ handle_event(#wx{id=?SEARCH_ENTRY, event=#wxCommand{cmdString=Str}},
%% Button area
handle_event(#wx{id=ID, event=#wxCommand{type=command_button_clicked}},_Wi) ->
- {value, {Button, _}} = lists:keysearch(ID, 2, buttons()),
+ {Button, _} = lists:keyfind(ID, 2, buttons()),
Button;
%% Evaluator area
@@ -908,8 +908,8 @@ buttons() ->
{'Where',?WhereButton}, {'Up',?UpButton}, {'Down',?DownButton}].
is_button(Name) ->
- case lists:keysearch(Name, 1, buttons()) of
- {value, {Name, Button}} -> {true, Button};
+ case lists:keyfind(Name, 1, buttons()) of
+ {Name, Button} -> {true, Button};
false -> false
end.
@@ -1021,9 +1021,3 @@ helpwin(Type, WinInfo = #winInfo{sg=Sg =#sub{in=Sa}}) ->
search -> wxWindow:setFocus(Sa#sa.search)
end,
Wi.
-
-max(X,Y) when X > Y -> X;
-max(_,Y) -> Y.
-
-
-
diff --git a/lib/debugger/src/dbg_wx_view.erl b/lib/debugger/src/dbg_wx_view.erl
index 6d34e5650c..8ff89a4847 100644
--- a/lib/debugger/src/dbg_wx_view.erl
+++ b/lib/debugger/src/dbg_wx_view.erl
@@ -23,9 +23,6 @@
%% External exports
-export([start/2]).
-%% Internal exports
--export([init/4]).
-
-record(state, {gs, % term() Graphics system id
win, % term() Attach process window data
coords, % {X,Y} Mouse point position
@@ -46,7 +43,7 @@ start(GS, Mod) ->
true -> ignore;
false ->
Env = wx:get_env(),
- spawn_link(?MODULE, init, [GS, Env, Mod, Title])
+ spawn_link(fun () -> init(GS, Env, Mod, Title) end)
end.
@@ -84,7 +81,7 @@ loop(State) ->
receive
%% From the GUI main window
- GuiEvent when element(1, GuiEvent)==wx ->
+ GuiEvent when element(1, GuiEvent) =:= wx ->
Cmd = wx:batch(fun() ->
dbg_wx_trace_win:handle_event(GuiEvent, State#state.win)
end),
@@ -167,7 +164,7 @@ gui_cmd('Function Break...', State) ->
gui_cmd('Enable All', State) ->
Breaks = int:all_breaks(),
ThisMod = State#state.mod,
- lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod =:= ThisMod ->
int:enable_break(Mod, Line);
(_Break) ->
ignore
@@ -177,7 +174,7 @@ gui_cmd('Enable All', State) ->
gui_cmd('Disable All', State) ->
Breaks = int:all_breaks(),
ThisMod = State#state.mod,
- lists:foreach(fun ({{Mod, Line}, _Options}) when Mod==ThisMod ->
+ lists:foreach(fun ({{Mod, Line}, _Options}) when Mod =:= ThisMod ->
int:disable_break(Mod, Line);
(_Break) ->
ignore
@@ -214,21 +211,19 @@ add_break(GS, Coords, Type, Mod, Line) ->
%%--Commands from the interpreter-------------------------------------
-int_cmd({new_break, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) ->
- Win = dbg_wx_trace_win:add_break(State#state.win, 'Break', Break),
- State#state{win=Win};
-int_cmd({delete_break, {Mod,_Line}=Point}, #state{mod=Mod}=State) ->
- Win = dbg_wx_trace_win:delete_break(State#state.win, Point),
- State#state{win=Win};
-int_cmd({break_options, {{Mod,_Line},_Options}=Break}, #state{mod=Mod}=State) ->
- Win = dbg_wx_trace_win:update_break(State#state.win, Break),
- State#state{win=Win};
-int_cmd(no_break, State) ->
- Win = dbg_wx_trace_win:clear_breaks(State#state.win),
- State#state{win=Win};
-int_cmd({no_break, _Mod}, State) ->
- Win = dbg_wx_trace_win:clear_breaks(State#state.win),
- State#state{win=Win};
+int_cmd({new_break, {{Mod,_Line},_Options}=Break},
+ #state{mod = Mod, win = Win}=State) ->
+ State#state{win = dbg_wx_trace_win:add_break(Win, 'Break', Break)};
+int_cmd({delete_break, {Mod,_Line}=Point},
+ #state{mod = Mod, win = Win}=State) ->
+ State#state{win = dbg_wx_trace_win:delete_break(Win, Point)};
+int_cmd({break_options, {{Mod,_Line},_Options}=Break},
+ #state{mod = Mod, win = Win}=State) ->
+ State#state{win = dbg_wx_trace_win:update_break(Win, Break)};
+int_cmd(no_break, #state{win = Win}=State) ->
+ State#state{win = dbg_wx_trace_win:clear_breaks(Win)};
+int_cmd({no_break, _Mod}, #state{win = Win}=State) ->
+ State#state{win = dbg_wx_trace_win:clear_breaks(Win)};
int_cmd(_, State) ->
State.
diff --git a/lib/debugger/src/dbg_wx_winman.erl b/lib/debugger/src/dbg_wx_winman.erl
index 1daabe3435..d0ddfeb51a 100755
--- a/lib/debugger/src/dbg_wx_winman.erl
+++ b/lib/debugger/src/dbg_wx_winman.erl
@@ -118,9 +118,9 @@ init([]) ->
{ok, #state{}}.
handle_call({is_started, Title}, _From, State) ->
- Reply = case lists:keysearch(Title, #win.title, State#state.wins) of
- {value, Win} -> {true, Win#win.win};
- false -> false
+ Reply = case lists:keyfind(Title, #win.title, State#state.wins) of
+ false -> false;
+ Win -> {true, Win#win.win}
end,
{reply, Reply, State}.
@@ -132,8 +132,8 @@ handle_cast({insert, Pid, Title, Win}, State) ->
handle_cast({clear_process, Title}, State) ->
OldWins = State#state.wins,
- Wins = case lists:keysearch(Title, #win.title, OldWins) of
- {value, #win{owner=Pid}} ->
+ Wins = case lists:keyfind(Title, #win.title, OldWins) of
+ #win{owner=Pid} ->
Msg = {dbg_ui_winman, destroy},
Pid ! Msg,
lists:keydelete(Title, #win.title, OldWins);
@@ -145,7 +145,7 @@ handle_cast({clear_process, Title}, State) ->
handle_info({'EXIT', Pid, _Reason}, State) ->
[Mon | _Wins] = State#state.wins,
if
- Pid==Mon#win.owner -> {stop, normal, State};
+ Pid =:= Mon#win.owner -> {stop, normal, State};
true ->
Wins2 = lists:keydelete(Pid, #win.owner, State#state.wins),
inform_all(Wins2),
diff --git a/lib/debugger/src/i.erl b/lib/debugger/src/i.erl
index 7c2fb22946..476a53482e 100644
--- a/lib/debugger/src/i.erl
+++ b/lib/debugger/src/i.erl
@@ -31,7 +31,6 @@
iv() ->
Vsn = string:substr(filename:basename(code:lib_dir(debugger)), 10),
list_to_atom(Vsn).
-
%% -------------------------------------------
%% Start a new graphical monitor.
@@ -288,10 +287,9 @@ ia(X,Y,Z) ->
%% -------------------------------------------
ia(Pid,Fnk) ->
- case lists:keysearch(Pid, 1, int:snapshot()) of
- {value, _PidTuple} ->
- int:attach(Pid,Fnk);
- false -> no_proc
+ case lists:keymember(Pid, 1, int:snapshot()) of
+ false -> no_proc;
+ true -> int:attach(Pid,Fnk)
end.
ia(X,Y,Z,Fnk) ->
diff --git a/lib/debugger/src/int.erl b/lib/debugger/src/int.erl
index eeb4df4a8e..9ee2102a19 100644
--- a/lib/debugger/src/int.erl
+++ b/lib/debugger/src/int.erl
@@ -261,7 +261,7 @@ del_break_in(Mod, Func, Arity) when is_atom(Mod), is_atom(Func), is_integer(Arit
end.
first_lines(Clauses) ->
- lists:map(fun(Clause) -> first_line(Clause) end, Clauses).
+ [first_line(Clause) || Clause <- Clauses].
first_line({clause,_L,_Vars,_,Exprs}) ->
first_line(Exprs);
@@ -469,10 +469,10 @@ contents(Mod, Pid) ->
%% Arity = integer()
%%--------------------------------------------------------------------
functions(Mod) ->
- lists:filter(fun([module_info, _Arity]) -> false;
- (_Func) -> true
- end,
- dbg_iserver:call({functions, Mod})).
+ [F || F <- dbg_iserver:call({functions, Mod}), functions_1(F)].
+
+functions_1([module_info, _Arity]) -> false;
+functions_1(_Func) -> true.
%%====================================================================
diff --git a/lib/debugger/vsn.mk b/lib/debugger/vsn.mk
index 5ce37a6bde..f33d66b5cf 100644
--- a/lib/debugger/vsn.mk
+++ b/lib/debugger/vsn.mk
@@ -1 +1 @@
-DEBUGGER_VSN = 3.2.2
+DEBUGGER_VSN = 3.2.3
diff --git a/lib/dialyzer/RELEASE_NOTES b/lib/dialyzer/RELEASE_NOTES
index b668142327..62b0c92f97 100644
--- a/lib/dialyzer/RELEASE_NOTES
+++ b/lib/dialyzer/RELEASE_NOTES
@@ -3,6 +3,19 @@
(in reversed chronological order)
==============================================================================
+Version 2.3.0 (in Erlang/OTP R14)
+---------------------------------
+ - Dialyzer properly supports the new attribute -export_type and checks
+ that remote types only refer to exported types. A warning is produced
+ if some files/applications refer to types defined in modules which are
+ neither in the PLT nor in the analyzed applications.
+ - Support for detecting data races involving whereis/1 and unregister/1.
+ - More precise identification of the reason(s) why a record construction
+ violates the types declared for its fields.
+ - Fixed bug in the handling of the 'or' guard.
+ - Better handling of the erlang:element/2 BIF.
+ - Complete handling of Erlang BIFs.
+
Version 2.2.0 (in Erlang/OTP R13B04)
------------------------------------
- Much better support for opaque types (thanks to Manouk Manoukian).
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index b6106b928a..856af01525 100755
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -31,6 +31,41 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<section><title>Dialyzer 2.3.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Various changes to dialyzer-related files for R14.</p>
+ <p>
+ - Dialyzer properly supports the new attribute
+ -export_type and checks that remote types only refer to
+ exported types. A warning is produced if some
+ files/applications refer to types defined in modules
+ which are neither in the PLT nor in the analyzed
+ applications.</p>
+ <p>
+ - Support for detecting data races involving whereis/1
+ and unregister/1.</p>
+ <p>
+ - More precise identification of the reason(s) why a
+ record construction violates the types declared for its
+ fields.</p>
+ <p>
+ - Fixed bug in the handling of the 'or' guard.</p>
+ <p>
+ - Better handling of the erlang:element/2 BIF.</p>
+ <p>
+ - Complete handling of Erlang BIFs.</p>
+ <p>
+ Own Id: OTP-8699</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 2.2.0</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index 3b7b68e8c4..d8fd073ca6 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -33,8 +33,8 @@
%% NOTE: Only functions exported by this module are available to
%% other applications.
%%--------------------------------------------------------------------
--export([plain_cl/0,
- run/1,
+-export([plain_cl/0,
+ run/1,
gui/0,
gui/1,
plt_info/1,
@@ -55,7 +55,7 @@
plain_cl() ->
case dialyzer_cl_parse:start() of
- {check_init, Opts} ->
+ {check_init, Opts} ->
cl_halt(cl_check_init(Opts), Opts);
{plt_info, Opts} ->
cl_halt(cl_print_plt_info(Opts), Opts);
@@ -72,7 +72,7 @@ plain_cl() ->
false ->
gui_halt(internal_gui(Type, Opts), Opts)
end;
- {cl, Opts} ->
+ {cl, Opts} ->
case Opts#options.check_plt of
true ->
case cl_check_init(Opts#options{get_warnings = false}) of
@@ -82,7 +82,7 @@ plain_cl() ->
false ->
cl_halt(cl(Opts), Opts)
end;
- {error, Msg} ->
+ {error, Msg} ->
cl_error(Msg)
end.
@@ -146,7 +146,7 @@ cl(Opts) ->
-spec run(dial_options()) -> [dial_warning()].
run(Opts) ->
- try dialyzer_options:build([{report_mode, quiet},
+ try dialyzer_options:build([{report_mode, quiet},
{erlang_mode, true}|Opts]) of
{error, Msg} ->
throw({dialyzer_error, Msg});
@@ -161,7 +161,7 @@ run(Opts) ->
throw({dialyzer_error, ErrorMsg1})
end
catch
- throw:{dialyzer_error, ErrorMsg} ->
+ throw:{dialyzer_error, ErrorMsg} ->
erlang:error({dialyzer_error, lists:flatten(ErrorMsg)})
end.
@@ -226,7 +226,7 @@ plt_info(Plt) ->
%%-----------
doit(F) ->
- try
+ try
{ok, F()}
catch
throw:{dialyzer_error, Msg} ->
@@ -241,9 +241,9 @@ gui_halt(R, Opts) ->
-spec cl_halt({'ok',dial_ret()} | {'error',string()}, #options{}) -> no_return().
-cl_halt({ok, R = ?RET_NOTHING_SUSPICIOUS}, #options{report_mode = quiet}) ->
+cl_halt({ok, R = ?RET_NOTHING_SUSPICIOUS}, #options{report_mode = quiet}) ->
halt(R);
-cl_halt({ok, R = ?RET_DISCREPANCIES}, #options{report_mode = quiet}) ->
+cl_halt({ok, R = ?RET_DISCREPANCIES}, #options{report_mode = quiet}) ->
halt(R);
cl_halt({ok, R = ?RET_NOTHING_SUSPICIOUS}, #options{}) ->
io:put_chars("done (passed successfully)\n"),
@@ -267,7 +267,7 @@ cl_check_log(Output) ->
-spec format_warning(dial_warning()) -> string().
-format_warning({_Tag, {File, Line}, Msg}) when is_list(File),
+format_warning({_Tag, {File, Line}, Msg}) when is_list(File),
is_integer(Line) ->
BaseName = filename:basename(File),
String = lists:flatten(message_to_string(Msg)),
@@ -290,7 +290,7 @@ message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}) ->
message_to_string({bin_construction, [Culprit, Size, Seg, Type]}) ->
io_lib:format("Binary construction will fail since the ~s field ~s in"
" segment ~s has type ~s\n", [Culprit, Size, Seg, Type]);
-message_to_string({call, [M, F, Args, ArgNs, FailReason,
+message_to_string({call, [M, F, Args, ArgNs, FailReason,
SigArgs, SigRet, Contract]}) ->
io_lib:format("The call ~w:~w~s ", [M, F, Args]) ++
call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract);
@@ -329,9 +329,9 @@ message_to_string({no_return, [Type|Name]}) ->
only_normal -> NameString ++ "has no local return\n";
both -> NameString ++ "has no local return\n"
end;
-message_to_string({record_constr, [Types, Name]}) ->
+message_to_string({record_constr, [RecConstr, FieldDiffs]}) ->
io_lib:format("Record construction ~s violates the"
- " declared type for #~w{}\n", [Types, Name]);
+ " declared type of field ~s\n", [RecConstr, FieldDiffs]);
message_to_string({record_constr, [Name, Field, Type]}) ->
io_lib:format("Record construction violates the declared type for #~w{}"
" since ~s cannot be of type ~s\n", [Name, Field, Type]);
@@ -358,7 +358,7 @@ message_to_string({contract_diff, [M, F, _A, Contract, Sig]}) ->
[M, F, Contract, M, F, Sig]);
message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}) ->
io_lib:format("Type specification ~w:~w~s"
- " is a subtype of the success typing: ~w:~w~s\n",
+ " is a subtype of the success typing: ~w:~w~s\n",
[M, F, Contract, M, F, Sig]);
message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}) ->
io_lib:format("Type specification ~w:~w~s"
@@ -427,7 +427,7 @@ message_to_string({spec_missing, [B, F, A]}) ->
%% Auxiliary functions below
%%-----------------------------------------------------------------------------
-call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
+call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
{IsOverloaded, Contract}) ->
PositionString = form_position_string(ArgNs),
case FailReason of
@@ -442,7 +442,7 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
" from the success typing arguments: ~s\n",
[PositionString, SigArgs])
end;
- only_contract ->
+ only_contract ->
case (ArgNs =:= []) orelse IsOverloaded of
true ->
%% We do not know which arguments caused the failure
@@ -494,7 +494,7 @@ form_position_string(ArgNs) ->
case ArgNs of
[] -> "";
[N1] -> ordinal(N1);
- [_,_|_] ->
+ [_,_|_] ->
[Last|Prevs] = lists:reverse(ArgNs),
", " ++ Head = lists:flatten([io_lib:format(", ~s",[ordinal(N)]) ||
N <- lists:reverse(Prevs)]),
diff --git a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
index ab1bbe5ade..3438cc8c7e 100644
--- a/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_analysis_callgraph.erl
@@ -96,6 +96,9 @@ loop(#server_state{parent = Parent, legal_warnings = LegalWarnings} = State,
end;
{AnalPid, ext_calls, NewExtCalls} ->
loop(State, Analysis, NewExtCalls);
+ {AnalPid, ext_types, ExtTypes} ->
+ send_ext_types(Parent, ExtTypes),
+ loop(State, Analysis, ExtCalls);
{AnalPid, unknown_behaviours, UnknownBehaviour} ->
send_unknown_behaviours(Parent, UnknownBehaviour),
loop(State, Analysis, ExtCalls);
@@ -123,8 +126,7 @@ analysis_start(Parent, Analysis) ->
parent = Parent,
start_from = Analysis#analysis.start_from,
use_contracts = Analysis#analysis.use_contracts,
- behaviours = {Analysis#analysis.behaviours_chk,
- []}
+ behaviours = {Analysis#analysis.behaviours_chk, []}
},
Files = ordsets:from_list(Analysis#analysis.files),
{Callgraph, NoWarn, TmpCServer0} = compile_and_store(Files, State),
@@ -132,22 +134,36 @@ analysis_start(Parent, Analysis) ->
NewCServer =
try
NewRecords = dialyzer_codeserver:get_temp_records(TmpCServer0),
- OldRecords = dialyzer_plt:get_types(State#analysis_state.plt),
+ NewExpTypes = dialyzer_codeserver:get_temp_exported_types(TmpCServer0),
+ OldRecords = dialyzer_plt:get_types(Plt),
+ OldExpTypes0 = dialyzer_plt:get_exported_types(Plt),
MergedRecords = dialyzer_utils:merge_records(NewRecords, OldRecords),
+ RemMods =
+ [case Analysis#analysis.start_from of
+ byte_code -> list_to_atom(filename:basename(F, ".beam"));
+ src_code -> list_to_atom(filename:basename(F, ".erl"))
+ end || F <- Files],
+ OldExpTypes1 = dialyzer_utils:sets_filter(RemMods, OldExpTypes0),
+ MergedExpTypes = sets:union(NewExpTypes, OldExpTypes1),
TmpCServer1 = dialyzer_codeserver:set_temp_records(MergedRecords, TmpCServer0),
- TmpCServer2 = dialyzer_utils:process_record_remote_types(TmpCServer1),
- dialyzer_contracts:process_contract_remote_types(TmpCServer2)
+ TmpCServer2 =
+ dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes,
+ TmpCServer1),
+ TmpCServer3 = dialyzer_utils:process_record_remote_types(TmpCServer2),
+ dialyzer_contracts:process_contract_remote_types(TmpCServer3)
catch
throw:{error, _ErrorMsg} = Error -> exit(Error)
end,
- NewPlt = dialyzer_plt:insert_types(Plt, dialyzer_codeserver:get_records(NewCServer)),
- State0 = State#analysis_state{plt = NewPlt},
+ NewPlt0 = dialyzer_plt:insert_types(Plt, dialyzer_codeserver:get_records(NewCServer)),
+ ExpTypes = dialyzer_codeserver:get_exported_types(NewCServer),
+ NewPlt1 = dialyzer_plt:insert_exported_types(NewPlt0, ExpTypes),
+ State0 = State#analysis_state{plt = NewPlt1},
dump_callgraph(Callgraph, State0, Analysis),
State1 = State0#analysis_state{codeserver = NewCServer},
State2 = State1#analysis_state{no_warn_unused = NoWarn},
%% Remove all old versions of the files being analyzed
AllNodes = dialyzer_callgraph:all_nodes(Callgraph),
- Plt1 = dialyzer_plt:delete_list(NewPlt, AllNodes),
+ Plt1 = dialyzer_plt:delete_list(NewPlt1, AllNodes),
Exports = dialyzer_codeserver:get_exports(NewCServer),
NewCallgraph =
case Analysis#analysis.race_detection of
@@ -155,6 +171,7 @@ analysis_start(Parent, Analysis) ->
false -> Callgraph
end,
State3 = analyze_callgraph(NewCallgraph, State2#analysis_state{plt = Plt1}),
+ rcv_and_send_ext_types(Parent),
NonExports = sets:subtract(sets:from_list(AllNodes), Exports),
NonExportsList = sets:to_list(NonExports),
Plt3 = dialyzer_plt:delete_list(State3#analysis_state.plt, NonExportsList),
@@ -371,14 +388,28 @@ compile_byte(File, Callgraph, CServer, UseContracts) ->
store_core(Mod, Core, NoWarn, Callgraph, CServer) ->
Exp = get_exports_from_core(Core),
+ OldExpTypes = dialyzer_codeserver:get_temp_exported_types(CServer),
+ NewExpTypes = get_exported_types_from_core(Core),
+ MergedExpTypes = sets:union(NewExpTypes, OldExpTypes),
CServer1 = dialyzer_codeserver:insert_exports(Exp, CServer),
- {LabeledCore, CServer2} = label_core(Core, CServer1),
- store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer2, NoWarn).
+ CServer2 = dialyzer_codeserver:insert_temp_exported_types(MergedExpTypes,
+ CServer1),
+ {LabeledCore, CServer3} = label_core(Core, CServer2),
+ store_code_and_build_callgraph(Mod, LabeledCore, Callgraph, CServer3, NoWarn).
abs_get_nowarn(Abs, M) ->
- [{M, F, A}
+ [{M, F, A}
|| {attribute, _, compile, {nowarn_unused_function, {F, A}}} <- Abs].
+get_exported_types_from_core(Core) ->
+ Attrs = cerl:module_attrs(Core),
+ ExpTypes1 = [cerl:concrete(L2) || {L1, L2} <- Attrs, cerl:is_literal(L1),
+ cerl:is_literal(L2),
+ cerl:concrete(L1) =:= 'export_type'],
+ ExpTypes2 = lists:flatten(ExpTypes1),
+ M = cerl:atom_val(cerl:module_name(Core)),
+ sets:from_list([{M, F, A} || {F, A} <- ExpTypes2]).
+
get_exports_from_core(Core) ->
Tree = cerl:from_records(Core),
Exports1 = cerl:module_exports(Tree),
@@ -390,7 +421,7 @@ label_core(Core, CServer) ->
NextLabel = dialyzer_codeserver:get_next_core_label(CServer),
CoreTree = cerl:from_records(Core),
{LabeledTree, NewNextLabel} = cerl_trees:label(CoreTree, NextLabel),
- {cerl:to_records(LabeledTree),
+ {cerl:to_records(LabeledTree),
dialyzer_codeserver:set_next_core_label(NewNextLabel, CServer)}.
store_code_and_build_callgraph(Mod, Core, Callgraph, CServer, NoWarn) ->
@@ -454,6 +485,20 @@ default_includes(Dir) ->
%% Handle Messages
%%-------------------------------------------------------------------
+rcv_and_send_ext_types(Parent) ->
+ Self = self(),
+ Self ! {Self, done},
+ ExtTypes = rcv_ext_types(Self, []),
+ Parent ! {Self, ext_types, ExtTypes},
+ ok.
+
+rcv_ext_types(Self, ExtTypes) ->
+ receive
+ {Self, ext_types, ExtType} ->
+ rcv_ext_types(Self, [ExtType|ExtTypes]);
+ {Self, done} -> lists:usort(ExtTypes)
+ end.
+
send_log(Parent, Msg) ->
Parent ! {self(), log, Msg},
ok.
@@ -471,11 +516,15 @@ filter_warnings(LegalWarnings, Warnings) ->
send_analysis_done(Parent, Plt, DocPlt) ->
Parent ! {self(), done, Plt, DocPlt},
ok.
-
+
send_ext_calls(Parent, ExtCalls) ->
Parent ! {self(), ext_calls, ExtCalls},
ok.
+send_ext_types(Parent, ExtTypes) ->
+ Parent ! {self(), ext_types, ExtTypes},
+ ok.
+
send_unknown_behaviours(Parent, UnknownBehaviours) ->
Parent ! {self(), unknown_behaviours, UnknownBehaviours},
ok.
@@ -491,7 +540,7 @@ send_mod_deps(Parent, ModuleDeps) ->
Parent ! {self(), mod_deps, ModuleDeps},
ok.
-format_bad_calls([{{_, _, _}, {_, module_info, A}}|Left], CodeServer, Acc)
+format_bad_calls([{{_, _, _}, {_, module_info, A}}|Left], CodeServer, Acc)
when A =:= 0; A =:= 1 ->
format_bad_calls(Left, CodeServer, Acc);
format_bad_calls([{FromMFA, {M, F, A} = To}|Left], CodeServer, Acc) ->
@@ -504,7 +553,7 @@ format_bad_calls([], _CodeServer, Acc) ->
Acc.
find_call_file_and_line(Tree, MFA) ->
- Fun =
+ Fun =
fun(SubTree, Acc) ->
case cerl:is_c_call(SubTree) of
true ->
diff --git a/lib/dialyzer/src/dialyzer_behaviours.erl b/lib/dialyzer/src/dialyzer_behaviours.erl
index 4e8dceaa8e..47ce9ba6eb 100644
--- a/lib/dialyzer/src/dialyzer_behaviours.erl
+++ b/lib/dialyzer/src/dialyzer_behaviours.erl
@@ -34,19 +34,25 @@
translate_behaviour_api_call/5, translatable_behaviours/1,
translate_callgraph/3]).
+-export_type([behaviour/0, behaviour_api_dict/0]).
+
%%--------------------------------------------------------------------
-include("dialyzer.hrl").
%%--------------------------------------------------------------------
+-type behaviour() :: atom().
+
-record(state, {plt :: dialyzer_plt:plt(),
codeserver :: dialyzer_codeserver:codeserver(),
- filename :: string(),
- behlines :: [{atom(), number()}]}).
+ filename :: file:filename(),
+ behlines :: [{behaviour(), non_neg_integer()}]}).
+
+%%--------------------------------------------------------------------
-spec get_behaviours([module()], dialyzer_codeserver:codeserver()) ->
- {[atom()], [atom()]}.
+ {[behaviour()], [behaviour()]}.
get_behaviours(Modules, Codeserver) ->
get_behaviours(Modules, Codeserver, [], []).
@@ -59,29 +65,37 @@ check_callbacks(Module, Attrs, Plt, Codeserver) ->
{Behaviours, BehLines} = get_behaviours(Attrs),
case Behaviours of
[] -> [];
- _ -> {_Var,Code} =
- dialyzer_codeserver:lookup_mfa_code({Module,module_info,0},
- Codeserver),
- File = get_file(cerl:get_ann(Code)),
- State = #state{plt = Plt, codeserver = Codeserver, filename = File,
- behlines = BehLines},
- Warnings = get_warnings(Module, Behaviours, State),
- [add_tag_file_line(Module, W, State) || W <- Warnings]
+ _ ->
+ MFA = {Module,module_info,0},
+ {_Var,Code} = dialyzer_codeserver:lookup_mfa_code(MFA, Codeserver),
+ File = get_file(cerl:get_ann(Code)),
+ State = #state{plt = Plt, codeserver = Codeserver, filename = File,
+ behlines = BehLines},
+ Warnings = get_warnings(Module, Behaviours, State),
+ [add_tag_file_line(Module, W, State) || W <- Warnings]
end.
--spec translatable_behaviours(cerl:c_module()) -> [{atom(),[_]}].
+-spec translatable_behaviours(cerl:c_module()) -> behaviour_api_dict().
translatable_behaviours(Tree) ->
Attrs = cerl:module_attrs(Tree),
{Behaviours, _BehLines} = get_behaviours(Attrs),
[{B, Calls} || B <- Behaviours, (Calls = behaviour_api_calls(B)) =/= []].
--spec get_behaviour_apis([atom()]) -> [mfa()].
+-spec get_behaviour_apis([behaviour()]) -> [mfa()].
get_behaviour_apis(Behaviours) ->
get_behaviour_apis(Behaviours, []).
--spec translate_behaviour_api_call(_, _, _, _, _) -> _.
+-spec translate_behaviour_api_call(dialyzer_races:mfa_or_funlbl(),
+ [erl_types:erl_type()],
+ [dialyzer_races:core_vars()],
+ module(),
+ behaviour_api_dict()) ->
+ {dialyzer_races:mfa_or_funlbl(),
+ [erl_types:erl_type()],
+ [dialyzer_races:core_vars()]}
+ | 'plain_call'.
translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, []) ->
plain_call;
@@ -101,8 +115,9 @@ translate_behaviour_api_call({Module, Fun, Arity}, ArgTypes, Args,
translate_behaviour_api_call(_Fun, _ArgTypes, _Args, _Module, _BehApiInfo) ->
plain_call.
--spec translate_callgraph([{atom(), _}], atom(), dialyzer_callgraph:callgraph())
- -> dialyzer_callgraph:callgraph().
+-spec translate_callgraph(behaviour_api_dict(), atom(),
+ dialyzer_callgraph:callgraph()) ->
+ dialyzer_callgraph:callgraph().
translate_callgraph([{Behaviour,_}|Behaviours], Module, Callgraph) ->
UsedCalls = [Call || {_From, {M, _F, _A}} = Call <-
@@ -156,9 +171,11 @@ check_all_callbacks(Module, Behaviour, Callbacks, State) ->
check_all_callbacks(_Module, _Behaviour, [], _State, Acc) ->
Acc;
-check_all_callbacks(Module, Behaviour, [{Fun, Arity, Spec}|Rest], State, Acc) ->
- Records = dialyzer_codeserver:get_records(State#state.codeserver),
- case parse_spec(Spec, Records) of
+check_all_callbacks(Module, Behaviour, [{Fun, Arity, Spec}|Rest],
+ #state{codeserver = CServer} = State, Acc) ->
+ Records = dialyzer_codeserver:get_records(CServer),
+ ExpTypes = dialyzer_codeserver:get_exported_types(CServer),
+ case parse_spec(Spec, ExpTypes, Records) of
{ok, Fun, Type} ->
RetType = erl_types:t_fun_range(Type),
ArgTypes = erl_types:t_fun_args(Type),
@@ -172,7 +189,7 @@ check_all_callbacks(Module, Behaviour, [{Fun, Arity}|Rest], State, Acc) ->
Warns = {spec_missing, [Behaviour, Fun, Arity]},
check_all_callbacks(Module, Behaviour, Rest, State, [Warns|Acc]).
-parse_spec(String, Records) ->
+parse_spec(String, ExpTypes, Records) ->
case erl_scan:string(String) of
{ok, Tokens, _} ->
case erl_parse:parse(Tokens) of
@@ -181,7 +198,8 @@ parse_spec(String, Records) ->
{attribute, _, 'spec', {{Fun, _}, [TypeForm|_Constraint]}} ->
MaybeRemoteType = erl_types:t_from_form(TypeForm),
try
- Type = erl_types:t_solve_remote(MaybeRemoteType, Records),
+ Type = erl_types:t_solve_remote(MaybeRemoteType, ExpTypes,
+ Records),
{ok, Fun, Type}
catch
throw:{error,Msg} -> {spec_remote_error, Msg}
@@ -260,7 +278,7 @@ get_line([]) -> -1.
get_file([{file, File}|_]) -> File;
get_file([_|Tail]) -> get_file(Tail).
-%%------------------------------------------------------------------------------
+%%-----------------------------------------------------------------------------
get_behaviours([], _Codeserver, KnownAcc, UnknownAcc) ->
{KnownAcc, UnknownAcc};
@@ -289,7 +307,7 @@ call_behaviours([Behaviour|Rest], KnownAcc, UnknownAcc) ->
_:_ -> call_behaviours(Rest, KnownAcc, [Behaviour | UnknownAcc])
end.
-%-------------------------------------------------------------------------------
+%------------------------------------------------------------------------------
get_behaviour_apis([], Acc) ->
Acc;
@@ -298,14 +316,22 @@ get_behaviour_apis([Behaviour | Rest], Acc) ->
{{Fun, Arity}, _} <- behaviour_api_calls(Behaviour)],
get_behaviour_apis(Rest, MFAs ++ Acc).
-%-------------------------------------------------------------------------------
+%------------------------------------------------------------------------------
nth_or_0(0, _List, Zero) ->
Zero;
nth_or_0(N, List, _Zero) ->
lists:nth(N, List).
-%-------------------------------------------------------------------------------
+%------------------------------------------------------------------------------
+
+-type behaviour_api_dict()::[{behaviour(), behaviour_api_info()}].
+-type behaviour_api_info()::[{original_fun(), replacement_fun()}].
+-type original_fun()::{atom(), arity()}.
+-type replacement_fun()::{atom(), arity(), arg_list()}.
+-type arg_list()::[byte()].
+
+-spec behaviour_api_calls(behaviour()) -> behaviour_api_info().
behaviour_api_calls(gen_server) ->
[{{start_link, 3}, {init, 1, [2]}},
diff --git a/lib/dialyzer/src/dialyzer_callgraph.erl b/lib/dialyzer/src/dialyzer_callgraph.erl
index f932f43548..d3de5aaf45 100644
--- a/lib/dialyzer/src/dialyzer_callgraph.erl
+++ b/lib/dialyzer/src/dialyzer_callgraph.erl
@@ -59,6 +59,8 @@
put_named_tables/2, put_public_tables/2, put_behaviour_api_calls/2,
get_behaviour_api_calls/1]).
+-export_type([callgraph/0]).
+
-include("dialyzer.hrl").
%%----------------------------------------------------------------------
diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl
index d533e734db..57f0d6e736 100644
--- a/lib/dialyzer/src/dialyzer_cl.erl
+++ b/lib/dialyzer/src/dialyzer_cl.erl
@@ -38,6 +38,7 @@
{backend_pid :: pid(),
erlang_mode = false :: boolean(),
external_calls = [] :: [mfa()],
+ external_types = [] :: [mfa()],
legal_warnings = ordsets:new() :: [dial_warn_tag()],
mod_deps = dict:new() :: dict(),
output = standard_io :: io:device(),
@@ -47,7 +48,7 @@
report_mode = normal :: rep_mode(),
return_status= ?RET_NOTHING_SUSPICIOUS :: dial_ret(),
stored_warnings = [] :: [dial_warning()],
- unknown_behaviours = [] :: [atom()]
+ unknown_behaviours = [] :: [dialyzer_behaviours:behaviour()]
}).
%%--------------------------------------------------------------------
@@ -538,6 +539,8 @@ cl_loop(State, LogCache) ->
return_value(State, NewPlt);
{BackendPid, ext_calls, ExtCalls} ->
cl_loop(State#cl_state{external_calls = ExtCalls}, LogCache);
+ {BackendPid, ext_types, ExtTypes} ->
+ cl_loop(State#cl_state{external_types = ExtTypes}, LogCache);
{BackendPid, mod_deps, ModDeps} ->
NewState = State#cl_state{mod_deps = ModDeps},
cl_loop(NewState, LogCache);
@@ -574,7 +577,7 @@ format_log_cache(LogCache) ->
store_warnings(#cl_state{stored_warnings = StoredWarnings} = St, Warnings) ->
St#cl_state{stored_warnings = StoredWarnings ++ Warnings}.
--spec store_unknown_behaviours(#cl_state{}, [_]) -> #cl_state{}.
+-spec store_unknown_behaviours(#cl_state{}, [dialyzer_behaviours:behaviour()]) -> #cl_state{}.
store_unknown_behaviours(#cl_state{unknown_behaviours = Behs} = St, Beh) ->
St#cl_state{unknown_behaviours = Beh ++ Behs}.
@@ -613,6 +616,7 @@ return_value(State = #cl_state{erlang_mode = ErlangMode,
false ->
print_warnings(State),
print_ext_calls(State),
+ print_ext_types(State),
print_unknown_behaviours(State),
maybe_close_output_file(State),
{RetValue, []};
@@ -649,10 +653,41 @@ do_print_ext_calls(Output, [{M,F,A}|T], Before) ->
do_print_ext_calls(_, [], _) ->
ok.
+print_ext_types(#cl_state{report_mode = quiet}) ->
+ ok;
+print_ext_types(#cl_state{output = Output,
+ external_calls = Calls,
+ external_types = Types,
+ stored_warnings = Warnings,
+ output_format = Format}) ->
+ case Types =:= [] of
+ true -> ok;
+ false ->
+ case Warnings =:= [] andalso Calls =:= [] of
+ true -> io:nl(Output); %% Need to do a newline first
+ false -> ok
+ end,
+ case Format of
+ formatted ->
+ io:put_chars(Output, "Unknown types:\n"),
+ do_print_ext_types(Output, Types, " ");
+ raw ->
+ io:put_chars(Output, "%% Unknown types:\n"),
+ do_print_ext_types(Output, Types, "%% ")
+ end
+ end.
+
+do_print_ext_types(Output, [{M,F,A}|T], Before) ->
+ io:format(Output, "~s~p:~p/~p\n", [Before,M,F,A]),
+ do_print_ext_types(Output, T, Before);
+do_print_ext_types(_, [], _) ->
+ ok.
+
%%print_unknown_behaviours(#cl_state{report_mode = quiet}) ->
%% ok;
print_unknown_behaviours(#cl_state{output = Output,
external_calls = Calls,
+ external_types = Types,
stored_warnings = Warnings,
unknown_behaviours = DupBehaviours,
legal_warnings = LegalWarnings,
@@ -662,7 +697,7 @@ print_unknown_behaviours(#cl_state{output = Output,
false -> ok;
true ->
Behaviours = lists:usort(DupBehaviours),
- case Warnings =:= [] andalso Calls =:= [] of
+ case Warnings =:= [] andalso Calls =:= [] andalso Types =:= [] of
true -> io:nl(Output); %% Need to do a newline first
false -> ok
end,
diff --git a/lib/dialyzer/src/dialyzer_codeserver.erl b/lib/dialyzer/src/dialyzer_codeserver.erl
index 3bc5fadc21..b2097f7e53 100644
--- a/lib/dialyzer/src/dialyzer_codeserver.erl
+++ b/lib/dialyzer/src/dialyzer_codeserver.erl
@@ -21,7 +21,7 @@
%%%-------------------------------------------------------------------
%%% File : dialyzer_codeserver.erl
%%% Author : Tobias Lindahl <[email protected]>
-%%% Description :
+%%% Description :
%%%
%%% Created : 4 Apr 2005 by Tobias Lindahl <[email protected]>
%%%-------------------------------------------------------------------
@@ -29,15 +29,19 @@
-export([delete/1,
finalize_contracts/2,
+ finalize_exported_types/2,
finalize_records/2,
get_contracts/1,
- get_exports/1,
+ get_exported_types/1,
+ get_exports/1,
get_records/1,
get_next_core_label/1,
get_temp_contracts/1,
+ get_temp_exported_types/1,
get_temp_records/1,
- insert/3,
- insert_exports/2,
+ insert/3,
+ insert_exports/2,
+ insert_temp_exported_types/2,
is_exported/2,
lookup_mod_code/2,
lookup_mfa_code/2,
@@ -52,17 +56,21 @@
store_contracts/3,
store_temp_contracts/3]).
+-export_type([codeserver/0]).
+
-include("dialyzer.hrl").
%%--------------------------------------------------------------------
--record(codeserver, {table_pid :: pid(),
- exports = sets:new() :: set(), % set(mfa())
- next_core_label = 0 :: label(),
- records = dict:new() :: dict(),
- temp_records = dict:new() :: dict(),
- contracts = dict:new() :: dict(),
- temp_contracts = dict:new() :: dict()}).
+-record(codeserver, {table_pid :: pid(),
+ exported_types = sets:new() :: set(), % set(mfa())
+ temp_exported_types = sets:new() :: set(), % set(mfa())
+ exports = sets:new() :: set(), % set(mfa())
+ next_core_label = 0 :: label(),
+ records = dict:new() :: dict(),
+ temp_records = dict:new() :: dict(),
+ contracts = dict:new() :: dict(),
+ temp_contracts = dict:new() :: dict()}).
-opaque codeserver() :: #codeserver{}.
@@ -78,12 +86,17 @@ new() ->
delete(#codeserver{table_pid = TablePid}) ->
table__delete(TablePid).
--spec insert(module(), cerl:c_module(), codeserver()) -> codeserver().
+-spec insert(atom(), cerl:c_module(), codeserver()) -> codeserver().
insert(Mod, ModCode, CS) ->
NewTablePid = table__insert(CS#codeserver.table_pid, Mod, ModCode),
CS#codeserver{table_pid = NewTablePid}.
+-spec insert_temp_exported_types(set(), codeserver()) -> codeserver().
+
+insert_temp_exported_types(Set, CS) ->
+ CS#codeserver{temp_exported_types = Set}.
+
-spec insert_exports([mfa()], codeserver()) -> codeserver().
insert_exports(List, #codeserver{exports = Exports} = CS) ->
@@ -96,12 +109,27 @@ insert_exports(List, #codeserver{exports = Exports} = CS) ->
is_exported(MFA, #codeserver{exports = Exports}) ->
sets:is_element(MFA, Exports).
+-spec get_exported_types(codeserver()) -> set(). % set(mfa())
+
+get_exported_types(#codeserver{exported_types = ExpTypes}) ->
+ ExpTypes.
+
+-spec get_temp_exported_types(codeserver()) -> set().
+
+get_temp_exported_types(#codeserver{temp_exported_types = TempExpTypes}) ->
+ TempExpTypes.
+
-spec get_exports(codeserver()) -> set(). % set(mfa())
get_exports(#codeserver{exports = Exports}) ->
Exports.
--spec lookup_mod_code(module(), codeserver()) -> cerl:c_module().
+-spec finalize_exported_types(set(), codeserver()) -> codeserver().
+
+finalize_exported_types(Set, CS) ->
+ CS#codeserver{exported_types = Set, temp_exported_types = sets:new()}.
+
+-spec lookup_mod_code(atom(), codeserver()) -> cerl:c_module().
lookup_mod_code(Mod, CS) when is_atom(Mod) ->
table__lookup(CS#codeserver.table_pid, Mod).
@@ -121,7 +149,7 @@ get_next_core_label(#codeserver{next_core_label = NCL}) ->
set_next_core_label(NCL, CS) ->
CS#codeserver{next_core_label = NCL}.
--spec store_records(module(), dict(), codeserver()) -> codeserver().
+-spec store_records(atom(), dict(), codeserver()) -> codeserver().
store_records(Mod, Dict, #codeserver{records = RecDict} = CS)
when is_atom(Mod) ->
@@ -130,7 +158,7 @@ store_records(Mod, Dict, #codeserver{records = RecDict} = CS)
false -> CS#codeserver{records = dict:store(Mod, Dict, RecDict)}
end.
--spec lookup_mod_records(module(), codeserver()) -> dict().
+-spec lookup_mod_records(atom(), codeserver()) -> dict().
lookup_mod_records(Mod, #codeserver{records = RecDict})
when is_atom(Mod) ->
@@ -139,12 +167,12 @@ lookup_mod_records(Mod, #codeserver{records = RecDict})
{ok, Dict} -> Dict
end.
--spec get_records(codeserver()) -> dict().
+-spec get_records(codeserver()) -> dict().
get_records(#codeserver{records = RecDict}) ->
RecDict.
--spec store_temp_records(module(), dict(), codeserver()) -> codeserver().
+-spec store_temp_records(atom(), dict(), codeserver()) -> codeserver().
store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS)
when is_atom(Mod) ->
@@ -153,7 +181,7 @@ store_temp_records(Mod, Dict, #codeserver{temp_records = TempRecDict} = CS)
false -> CS#codeserver{temp_records = dict:store(Mod, Dict, TempRecDict)}
end.
--spec get_temp_records(codeserver()) -> dict().
+-spec get_temp_records(codeserver()) -> dict().
get_temp_records(#codeserver{temp_records = TempRecDict}) ->
TempRecDict.
@@ -163,12 +191,12 @@ get_temp_records(#codeserver{temp_records = TempRecDict}) ->
set_temp_records(Dict, CS) ->
CS#codeserver{temp_records = Dict}.
--spec finalize_records(dict(), codeserver()) -> codeserver().
+-spec finalize_records(dict(), codeserver()) -> codeserver().
finalize_records(Dict, CS) ->
CS#codeserver{records = Dict, temp_records = dict:new()}.
--spec store_contracts(module(), dict(), codeserver()) -> codeserver().
+-spec store_contracts(atom(), dict(), codeserver()) -> codeserver().
store_contracts(Mod, Dict, #codeserver{contracts = C} = CS) when is_atom(Mod) ->
case dict:size(Dict) =:= 0 of
@@ -176,7 +204,7 @@ store_contracts(Mod, Dict, #codeserver{contracts = C} = CS) when is_atom(Mod) ->
false -> CS#codeserver{contracts = dict:store(Mod, Dict, C)}
end.
--spec lookup_mod_contracts(module(), codeserver()) -> dict().
+-spec lookup_mod_contracts(atom(), codeserver()) -> dict().
lookup_mod_contracts(Mod, #codeserver{contracts = ContDict})
when is_atom(Mod) ->
@@ -185,7 +213,7 @@ lookup_mod_contracts(Mod, #codeserver{contracts = ContDict})
{ok, Dict} -> Dict
end.
--spec lookup_mfa_contract(mfa(), codeserver()) ->
+-spec lookup_mfa_contract(mfa(), codeserver()) ->
'error' | {'ok', dialyzer_contracts:file_contract()}.
lookup_mfa_contract({M,_F,_A} = MFA, #codeserver{contracts = ContDict}) ->
@@ -194,12 +222,12 @@ lookup_mfa_contract({M,_F,_A} = MFA, #codeserver{contracts = ContDict}) ->
{ok, Dict} -> dict:find(MFA, Dict)
end.
--spec get_contracts(codeserver()) -> dict().
+-spec get_contracts(codeserver()) -> dict().
get_contracts(#codeserver{contracts = ContDict}) ->
ContDict.
--spec store_temp_contracts(module(), dict(), codeserver()) -> codeserver().
+-spec store_temp_contracts(atom(), dict(), codeserver()) -> codeserver().
store_temp_contracts(Mod, Dict, #codeserver{temp_contracts = C} = CS)
when is_atom(Mod) ->
@@ -263,7 +291,7 @@ table__loop(Cached, Map) ->
Pid ! {self(), Mod, Ans},
table__loop({Mod, Ans}, Map);
{insert, List} ->
- NewMap = lists:foldl(fun({Key, Val}, AccMap) ->
+ NewMap = lists:foldl(fun({Key, Val}, AccMap) ->
dict:store(Key, Val, AccMap)
end, Map, List),
table__loop(Cached, NewMap)
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 3486c72748..bf80c6f470 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -21,7 +21,7 @@
-module(dialyzer_contracts).
-export([check_contract/2,
- check_contracts/3,
+ check_contracts/3,
contracts_without_fun/3,
contract_to_string/1,
get_invalid_contract_warnings/3,
@@ -33,6 +33,8 @@
process_contract_remote_types/1,
store_tmp_contract/5]).
+-export_type([file_contract/0, plt_contracts/0]).
+
%%-----------------------------------------------------------------------
-include("dialyzer.hrl").
@@ -50,7 +52,7 @@
%% to expand records and/or remote types that they might contain.
%%-----------------------------------------------------------------------
--type tmp_contract_fun() :: fun((dict()) -> contract_pair()).
+-type tmp_contract_fun() :: fun((set(), dict()) -> contract_pair()).
-record(tmp_contract, {contract_funs = [] :: [tmp_contract_fun()],
forms = [] :: [{_, _}]}).
@@ -104,13 +106,13 @@ contract_to_string(#contract{forms = Forms}) ->
contract_to_string_1([{Contract, []}]) ->
strip_fun(erl_types:t_form_to_string(Contract));
contract_to_string_1([{Contract, []}|Rest]) ->
- strip_fun(erl_types:t_form_to_string(Contract)) ++ "\n ; "
+ strip_fun(erl_types:t_form_to_string(Contract)) ++ "\n ; "
++ contract_to_string_1(Rest);
contract_to_string_1([{Contract, Constraints}]) ->
- strip_fun(erl_types:t_form_to_string(Contract)) ++ " when "
+ strip_fun(erl_types:t_form_to_string(Contract)) ++ " when "
++ constraints_to_string(Constraints);
contract_to_string_1([{Contract, Constraints}|Rest]) ->
- strip_fun(erl_types:t_form_to_string(Contract)) ++ " when "
+ strip_fun(erl_types:t_form_to_string(Contract)) ++ " when "
++ constraints_to_string(Constraints) ++ ";" ++
contract_to_string_1(Rest).
@@ -128,7 +130,7 @@ constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}]) ->
sequence([erl_types:t_form_to_string(T) || T <- Types], ",") ++ ")";
constraints_to_string([{type, _, constraint, [{atom, _, What}, Types]}|Rest]) ->
atom_to_list(What) ++ "(" ++
- sequence([erl_types:t_form_to_string(T) || T <- Types], ",")
+ sequence([erl_types:t_form_to_string(T) || T <- Types], ",")
++ "), " ++ constraints_to_string(Rest).
sequence([], _Delimiter) -> "";
@@ -140,10 +142,11 @@ sequence([H|T], Delimiter) -> H ++ Delimiter ++ sequence(T, Delimiter).
process_contract_remote_types(CodeServer) ->
TmpContractDict = dialyzer_codeserver:get_temp_contracts(CodeServer),
+ ExpTypes = dialyzer_codeserver:get_exported_types(CodeServer),
RecordDict = dialyzer_codeserver:get_records(CodeServer),
ContractFun =
fun({_M, _F, _A}, {File, #tmp_contract{contract_funs = CFuns, forms = Forms}}) ->
- NewCs = [CFun(RecordDict) || CFun <- CFuns],
+ NewCs = [CFun(ExpTypes, RecordDict) || CFun <- CFuns],
Args = general_domain(NewCs),
{File, #contract{contracts = NewCs, args = Args, forms = Forms}}
end,
@@ -153,21 +156,21 @@ process_contract_remote_types(CodeServer) ->
end,
NewContractDict = dict:map(ModuleFun, TmpContractDict),
dialyzer_codeserver:finalize_contracts(NewContractDict, CodeServer).
-
+
-spec check_contracts([{mfa(), file_contract()}],
dialyzer_callgraph:callgraph(), dict()) -> plt_contracts().
check_contracts(Contracts, Callgraph, FunTypes) ->
FoldFun =
- fun(Label, Type, NewContracts) ->
+ fun(Label, Type, NewContracts) ->
{ok, {M,F,A} = MFA} = dialyzer_callgraph:lookup_name(Label, Callgraph),
case orddict:find(MFA, Contracts) of
- {ok, {_FileLine, Contract}} ->
+ {ok, {_FileLine, Contract}} ->
case check_contract(Contract, Type) of
ok ->
case erl_bif_types:is_known(M, F, A) of
true ->
- %% Disregard the contracts since
+ %% Disregard the contracts since
%% this is a known function.
NewContracts;
false ->
@@ -184,8 +187,8 @@ check_contracts(Contracts, Callgraph, FunTypes) ->
-spec check_contract(#contract{}, erl_types:erl_type()) -> 'ok' | {'error', term()}.
check_contract(#contract{contracts = Contracts}, SuccType) ->
- try
- Contracts1 = [{Contract, insert_constraints(Constraints, dict:new())}
+ try
+ Contracts1 = [{Contract, insert_constraints(Constraints, dict:new())}
|| {Contract, Constraints} <- Contracts],
Contracts2 = [erl_types:t_subst(Contract, Dict)
|| {Contract, Dict} <- Contracts1],
@@ -194,7 +197,7 @@ check_contract(#contract{contracts = Contracts}, SuccType) ->
error ->
{error, {overlapping_contract, []}};
ok ->
- InfList = [erl_types:t_inf(Contract, SuccType, opaque)
+ InfList = [erl_types:t_inf(Contract, SuccType, opaque)
|| Contract <- Contracts2],
case check_contract_inf_list(InfList, SuccType) of
{error, _} = Invalid -> Invalid;
@@ -226,7 +229,7 @@ check_contract_inf_list([FunType|Left], SuccType) ->
STRange = erl_types:t_fun_range(SuccType),
case erl_types:t_is_none_or_unit(STRange) of
true -> ok;
- false ->
+ false ->
Range = erl_types:t_fun_range(FunType),
case erl_types:t_is_none(erl_types:t_inf(STRange, Range, opaque)) of
true -> check_contract_inf_list(Left, SuccType);
@@ -258,9 +261,9 @@ check_extraneous_1(Contract, SuccType) ->
process_contracts(OverContracts, Args) ->
process_contracts(OverContracts, Args, erl_types:t_none()).
-
+
process_contracts([OverContract|Left], Args, AccRange) ->
- NewAccRange =
+ NewAccRange =
case process_contract(OverContract, Args) of
error -> AccRange;
{ok, Range} -> erl_types:t_sup(AccRange, Range)
@@ -273,12 +276,12 @@ process_contracts([], _Args, AccRange) ->
process_contract({Contract, Constraints}, CallTypes0) ->
CallTypesFun = erl_types:t_fun(CallTypes0, erl_types:t_any()),
- ContArgsFun = erl_types:t_fun(erl_types:t_fun_args(Contract),
+ ContArgsFun = erl_types:t_fun(erl_types:t_fun_args(Contract),
erl_types:t_any()),
?debug("Instance: Contract: ~s\n Arguments: ~s\n",
- [erl_types:t_to_string(ContArgsFun),
+ [erl_types:t_to_string(ContArgsFun),
erl_types:t_to_string(CallTypesFun)]),
- case solve_constraints(ContArgsFun, CallTypesFun, Constraints) of
+ case solve_constraints(ContArgsFun, CallTypesFun, Constraints) of
{ok, VarDict} ->
{ok, erl_types:t_subst(erl_types:t_fun_range(Contract), VarDict)};
error -> error
@@ -288,7 +291,7 @@ solve_constraints(Contract, Call, Constraints) ->
%% First make sure the call follows the constraints
CDict = insert_constraints(Constraints, dict:new()),
Contract1 = erl_types:t_subst(Contract, CDict),
- %% Just a safe over-approximation.
+ %% Just a safe over-approximation.
%% TODO: Find the types for type variables properly
ContrArgs = erl_types:t_fun_args(Contract1),
CallArgs = erl_types:t_fun_args(Call),
@@ -309,7 +312,7 @@ solve_constraints(Contract, Call, Constraints) ->
-spec contracts_without_fun(dict(), [_], dialyzer_callgraph:callgraph()) -> [dial_warning()].
contracts_without_fun(Contracts, AllFuns0, Callgraph) ->
- AllFuns1 = [{dialyzer_callgraph:lookup_name(Label, Callgraph), Arity}
+ AllFuns1 = [{dialyzer_callgraph:lookup_name(Label, Callgraph), Arity}
|| {Label, Arity} <- AllFuns0],
AllFuns2 = [{M, F, A} || {{ok, {M, F, _}}, A} <- AllFuns1],
AllContractMFAs = dict:fetch_keys(Contracts),
@@ -351,46 +354,49 @@ contract_from_form(Forms, RecDict) ->
{CFuns, Forms1} = contract_from_form(Forms, RecDict, [], []),
#tmp_contract{contract_funs = CFuns, forms = Forms1}.
-contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict,
+contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], RecDict,
TypeAcc, FormAcc) ->
- TypeFun =
- fun(AllRecords) ->
+ TypeFun =
+ fun(ExpTypes, AllRecords) ->
Type = erl_types:t_from_form(Form, RecDict),
- NewType = erl_types:t_solve_remote(Type, AllRecords),
+ NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords),
{NewType, []}
end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, []} | FormAcc],
contract_from_form(Left, RecDict, NewTypeAcc, NewFormAcc);
-contract_from_form([{type, _L1, bounded_fun,
+contract_from_form([{type, _L1, bounded_fun,
[{type, _L2, 'fun', [_, _]} = Form, Constr]}| Left],
RecDict, TypeAcc, FormAcc) ->
- TypeFun =
- fun(AllRecords) ->
- Constr1 = [constraint_from_form(C, RecDict, AllRecords) || C <- Constr],
+ TypeFun =
+ fun(ExpTypes, AllRecords) ->
+ Constr1 = [constraint_from_form(C, RecDict, ExpTypes, AllRecords)
+ || C <- Constr],
VarDict = insert_constraints(Constr1, dict:new()),
Type = erl_types:t_from_form(Form, RecDict, VarDict),
- NewType = erl_types:t_solve_remote(Type, AllRecords),
+ NewType = erl_types:t_solve_remote(Type, ExpTypes, AllRecords),
{NewType, Constr1}
- end,
+ end,
NewTypeAcc = [TypeFun | TypeAcc],
NewFormAcc = [{Form, Constr} | FormAcc],
contract_from_form(Left, RecDict, NewTypeAcc, NewFormAcc);
-contract_from_form([], _RecDict, TypeAcc, FormAcc) ->
+contract_from_form([], _RecDict, TypeAcc, FormAcc) ->
{lists:reverse(TypeAcc), lists:reverse(FormAcc)}.
-constraint_from_form({type, _, constraint, [{atom, _, is_subtype},
- [Type1, Type2]]}, RecDict, AllRecords) ->
+constraint_from_form({type, _, constraint, [{atom, _, is_subtype},
+ [Type1, Type2]]}, RecDict,
+ ExpTypes, AllRecords) ->
T1 = erl_types:t_from_form(Type1, RecDict),
T2 = erl_types:t_from_form(Type2, RecDict),
- T3 = erl_types:t_solve_remote(T1, AllRecords),
- T4 = erl_types:t_solve_remote(T2, AllRecords),
+ T3 = erl_types:t_solve_remote(T1, ExpTypes, AllRecords),
+ T4 = erl_types:t_solve_remote(T2, ExpTypes, AllRecords),
{subtype, T3, T4};
-constraint_from_form({type, _, constraint, [{atom,_,Name}, List]}, _RecDict, _) ->
+constraint_from_form({type, _, constraint, [{atom,_,Name}, List]}, _RecDict,
+ _ExpTypes, _AllRecords) ->
N = length(List),
throw({error, io_lib:format("Unsupported type guard ~w/~w\n", [Name, N])}).
-%% Gets the most general domain of a list of domains of all
+%% Gets the most general domain of a list of domains of all
%% the overloaded contracts
general_domain(List) ->
@@ -419,7 +425,7 @@ get_invalid_contract_warnings_modules([Mod|Mods], CodeServer, Plt, Acc) ->
get_invalid_contract_warnings_modules([], _CodeServer, _Plt, Acc) ->
Acc.
-get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
+get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
Plt, RecDict, Acc) ->
case dialyzer_plt:lookup(Plt, MFA) of
none ->
@@ -447,15 +453,15 @@ get_invalid_contract_warnings_funs([{MFA, {FileLine, Contract}}|Left],
BifRet = erl_bif_types:type(M, F, A),
BifSig = erl_types:t_fun(BifArgs, BifRet),
case check_contract(Contract, BifSig) of
- {error, _} ->
+ {error, _} ->
[invalid_contract_warning(MFA, FileLine, BifSig, RecDict)
|Acc];
ok ->
- picky_contract_check(CSig, BifSig, MFA, FileLine,
+ picky_contract_check(CSig, BifSig, MFA, FileLine,
Contract, RecDict, Acc)
end;
false ->
- picky_contract_check(CSig, Sig, MFA, FileLine, Contract,
+ picky_contract_check(CSig, Sig, MFA, FileLine, Contract,
RecDict, Acc)
end
end,
@@ -479,12 +485,12 @@ picky_contract_check(CSig0, Sig0, MFA, FileLine, Contract, RecDict, Acc) ->
Sig = erl_types:t_abstract_records(Sig0, RecDict),
case erl_types:t_is_equal(CSig, Sig) of
true -> Acc;
- false ->
+ false ->
case (erl_types:t_is_none(erl_types:t_fun_range(Sig)) andalso
erl_types:t_is_unit(erl_types:t_fun_range(CSig))) of
true -> Acc;
false ->
- case extra_contract_warning(MFA, FileLine, Contract,
+ case extra_contract_warning(MFA, FileLine, Contract,
CSig, Sig, RecDict) of
no_warning -> Acc;
{warning, Warning} -> [Warning|Acc]
@@ -503,16 +509,16 @@ extra_contract_warning({M, F, A}, FileLine, Contract, CSig, Sig, RecDict) ->
ContractString = contract_to_string(Contract),
{Tag, Msg} =
case erl_types:t_is_subtype(CSig, Sig) of
- true ->
- {?WARN_CONTRACT_SUBTYPE,
+ true ->
+ {?WARN_CONTRACT_SUBTYPE,
{contract_subtype, [M, F, A, ContractString, SigString]}};
false ->
case erl_types:t_is_subtype(Sig, CSig) of
true ->
- {?WARN_CONTRACT_SUPERTYPE,
+ {?WARN_CONTRACT_SUPERTYPE,
{contract_supertype, [M, F, A, ContractString, SigString]}};
false ->
- {?WARN_CONTRACT_NOT_EQUAL,
+ {?WARN_CONTRACT_NOT_EQUAL,
{contract_diff, [M, F, A, ContractString, SigString]}}
end
end,
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index 1ccfaaa52f..b80c7efc1a 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -21,7 +21,7 @@
%%%-------------------------------------------------------------------
%%% File : dialyzer_dataflow.erl
%%% Author : Tobias Lindahl <[email protected]>
-%%% Description :
+%%% Description :
%%%
%%% Created : 19 Apr 2005 by Tobias Lindahl <[email protected]>
%%%-------------------------------------------------------------------
@@ -30,6 +30,7 @@
-export([get_fun_types/4, get_warnings/5, format_args/3]).
+%% Data structure interfaces.
-export([state__add_warning/2, state__cleanup/1,
state__get_callgraph/1, state__get_races/1,
state__get_records/1, state__put_callgraph/2,
@@ -38,9 +39,11 @@
%% Debug and test interfaces.
-export([get_top_level_signatures/2, pp/1]).
+-export_type([state/0]).
+
-include("dialyzer.hrl").
--import(erl_types,
+-import(erl_types,
[any_none/1, t_any/0, t_atom/0, t_atom/1, t_atom_vals/1,
t_binary/0, t_boolean/0,
t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_bitstr_match/2,
@@ -88,14 +91,15 @@
fun_tab :: dict(),
plt :: dialyzer_plt:plt(),
opaques :: [erl_types:erl_type()],
- races :: dialyzer_races:races(),
- records :: dict(),
+ races = dialyzer_races:new() :: dialyzer_races:races(),
+ records = dict:new() :: dict(),
tree_map :: dict(),
warning_mode = false :: boolean(),
warnings = [] :: [dial_warning()],
work :: {[_], [_], set()},
module :: module(),
- behaviour_api_info = [] :: [{atom(),[_]}]}).
+ behaviour_api_dict = [] ::
+ dialyzer_behaviours:behaviour_api_dict()}).
%% Exported Types
@@ -163,20 +167,20 @@ get_top_level_signatures(Code, Records) ->
error ->
Arity = cerl:fname_arity(V),
Type = t_fun(lists:duplicate(Arity,
- t_none()),
+ t_none()),
t_none()),
dict:store(Label, Type, Acc);
{ok, _} -> Acc
end
end, FunTypes, cerl:module_defs(Tree)),
dialyzer_callgraph:delete(Callgraph),
- Sigs = [{{cerl:fname_id(V), cerl:fname_arity(V)},
- dict:fetch(get_label(F), FunTypes1)}
+ Sigs = [{{cerl:fname_id(V), cerl:fname_arity(V)},
+ dict:fetch(get_label(F), FunTypes1)}
|| {V, F} <- cerl:module_defs(Tree)],
ordsets:from_list(Sigs).
get_def_plt() ->
- try
+ try
dialyzer_plt:from_file(dialyzer_plt:get_default_plt())
catch
throw:{dialyzer_error, _} -> dialyzer_plt:new()
@@ -202,7 +206,7 @@ annotate_module(Code, Plt) ->
annotate(Tree, State) ->
case cerl:subtrees(Tree) of
[] -> set_type(Tree, State);
- List ->
+ List ->
NewSubTrees = [[annotate(Subtree, State) || Subtree <- Group]
|| Group <- List],
NewTree = cerl:update_tree(Tree, NewSubTrees),
@@ -214,9 +218,9 @@ set_type(Tree, State) ->
'fun' ->
Type = state__fun_type(Tree, State),
case t_is_any(Type) of
- true ->
+ true ->
cerl:set_ann(Tree, delete_ann(typesig, cerl:get_ann(Tree)));
- false ->
+ false ->
cerl:set_ann(Tree, append_ann(typesig, Type, cerl:get_ann(Tree)))
end;
apply ->
@@ -224,10 +228,10 @@ set_type(Tree, State) ->
unknown -> Tree;
ReturnType ->
case t_is_any(ReturnType) of
- true ->
+ true ->
cerl:set_ann(Tree, delete_ann(type, cerl:get_ann(Tree)));
- false ->
- cerl:set_ann(Tree, append_ann(type, ReturnType,
+ false ->
+ cerl:set_ann(Tree, append_ann(type, ReturnType,
cerl:get_ann(Tree)))
end
end;
@@ -236,7 +240,7 @@ set_type(Tree, State) ->
end.
append_ann(Tag, Val, [X | Xs]) ->
- if tuple_size(X) >= 1, element(1, X) =:= Tag ->
+ if tuple_size(X) >= 1, element(1, X) =:= Tag ->
append_ann(Tag, Val, Xs);
true ->
[X | append_ann(Tag, Val, Xs)]
@@ -245,7 +249,7 @@ append_ann(Tag, Val, []) ->
[{Tag, Val}].
delete_ann(Tag, [X | Xs]) ->
- if tuple_size(X) >= 1, element(1, X) =:= Tag ->
+ if tuple_size(X) >= 1, element(1, X) =:= Tag ->
delete_ann(Tag, Xs);
true ->
[X | delete_ann(Tag, Xs)]
@@ -314,21 +318,21 @@ analyze_loop(#state{callgraph = Callgraph, races = Races} = State) ->
{Fun, NewState} ->
ArgTypes = state__get_args(Fun, NewState),
case any_none(ArgTypes) of
- true ->
- ?debug("Not handling1 ~w: ~s\n",
- [state__lookup_name(get_label(Fun), State),
+ true ->
+ ?debug("Not handling1 ~w: ~s\n",
+ [state__lookup_name(get_label(Fun), State),
t_to_string(t_product(ArgTypes))]),
analyze_loop(NewState);
- false ->
+ false ->
case state__fun_env(Fun, NewState) of
- none ->
- ?debug("Not handling2 ~w: ~s\n",
- [state__lookup_name(get_label(Fun), State),
+ none ->
+ ?debug("Not handling2 ~w: ~s\n",
+ [state__lookup_name(get_label(Fun), State),
t_to_string(t_product(ArgTypes))]),
analyze_loop(NewState);
Map ->
- ?debug("Handling fun ~p: ~s\n",
- [state__lookup_name(get_label(Fun), State),
+ ?debug("Handling fun ~p: ~s\n",
+ [state__lookup_name(get_label(Fun), State),
t_to_string(state__fun_type(Fun, NewState))]),
NewState1 = state__mark_fun_as_handled(NewState, Fun),
Vars = cerl:fun_vars(Fun),
@@ -339,19 +343,19 @@ analyze_loop(#state{callgraph = Callgraph, races = Races} = State) ->
RaceAnalysis = dialyzer_races:get_race_analysis(Races),
NewState3 =
case RaceDetection andalso RaceAnalysis of
- true ->
+ true ->
NewState2 = state__renew_curr_fun(
state__lookup_name(FunLabel, NewState1), FunLabel,
NewState1),
state__renew_race_list([], 0, NewState2);
false -> NewState1
end,
- {NewState4, _Map2, BodyType} =
+ {NewState4, _Map2, BodyType} =
traverse(Body, Map1, NewState3),
- ?debug("Done analyzing: ~w:~s\n",
+ ?debug("Done analyzing: ~w:~s\n",
[state__lookup_name(get_label(Fun), State),
t_to_string(t_fun(ArgTypes, BodyType))]),
- NewState5 =
+ NewState5 =
case RaceDetection andalso RaceAnalysis of
true ->
Races1 = NewState4#state.races,
@@ -382,7 +386,7 @@ traverse(Tree, Map, State) ->
%% This only happens when checking for illegal record patterns
%% so the handling is a bit rudimentary.
traverse(cerl:alias_pat(Tree), Map, State);
- apply ->
+ apply ->
handle_apply(Tree, Map, State);
binary ->
Segs = cerl:binary_segments(Tree),
@@ -416,7 +420,7 @@ traverse(Tree, Map, State) ->
%% By not including the variables in scope we can assure that we
%% will get the current function type when using the variables.
FoldFun = fun({Var, Fun}, {AccState, AccMap}) ->
- {NewAccState, NewAccMap0, FunType} =
+ {NewAccState, NewAccMap0, FunType} =
traverse(Fun, AccMap, AccState),
NewAccMap = enter_type(Var, FunType, NewAccMap0),
{NewAccState, NewAccMap}
@@ -428,7 +432,7 @@ traverse(Tree, Map, State) ->
case cerl:unfold_literal(Tree) of
Tree ->
Type = literal_type(Tree),
- NewType =
+ NewType =
case erl_types:t_opaque_match_atom(Type, State#state.opaques) of
[Opaque] -> Opaque;
_ -> Type
@@ -446,8 +450,8 @@ traverse(Tree, Map, State) ->
bs_init_writable -> t_from_term(<<>>);
Other -> erlang:error({'Unsupported primop', Other})
end,
- {State, Map, Type};
- 'receive' ->
+ {State, Map, Type};
+ 'receive' ->
handle_receive(Tree, Map, State);
seq ->
Arg = cerl:seq_arg(Tree),
@@ -457,13 +461,13 @@ traverse(Tree, Map, State) ->
true ->
SMA;
false ->
- State2 =
+ State2 =
case (t_is_any(ArgType) orelse t_is_simple(ArgType)
orelse is_call_to_send(Arg)) of
true -> % do not warn in these cases
State1;
false ->
- state__add_warning(State1, ?WARN_UNMATCHED_RETURN, Arg,
+ state__add_warning(State1, ?WARN_UNMATCHED_RETURN, Arg,
{unmatched_return,
[format_type(ArgType, State1)]})
end,
@@ -481,12 +485,12 @@ traverse(Tree, Map, State) ->
var ->
?debug("Looking up unknown variable: ~p\n", [Tree]),
case state__lookup_type_for_rec_var(Tree, State) of
- error ->
+ error ->
LType = lookup_type(Tree, Map),
Opaques = State#state.opaques,
case t_opaque_match_record(LType, Opaques) of
[Opaque] -> {State, Map, Opaque};
- _ ->
+ _ ->
case t_opaque_match_atom(LType, Opaques) of
[Opaque] -> {State, Map, Opaque};
_ -> {State, Map, LType}
@@ -506,7 +510,7 @@ traverse_list([Tree|Tail], Map, State, Acc) ->
traverse_list(Tail, Map1, State1, [Type|Acc]);
traverse_list([], Map, State, Acc) ->
{State, Map, lists:reverse(Acc)}.
-
+
%%________________________________________
%%
%% Special instructions
@@ -518,7 +522,7 @@ handle_apply(Tree, Map, State) ->
{State1, Map1, ArgTypes} = traverse_list(Args, Map, State),
{State2, Map2, OpType} = traverse(Op, Map1, State1),
case any_none(ArgTypes) of
- true ->
+ true ->
{State2, Map2, t_none()};
false ->
{CallSitesKnown, FunList} =
@@ -533,7 +537,7 @@ handle_apply(Tree, Map, State) ->
OpType1 = t_inf(OpType, t_fun(Arity, t_any())),
case t_is_none(OpType1) of
true ->
- Msg = {fun_app_no_fun,
+ Msg = {fun_app_no_fun,
[format_cerl(Op), format_type(OpType, State2), Arity]},
State3 = state__add_warning(State2, ?WARN_FAILING_CALL,
Tree, Msg),
@@ -541,7 +545,7 @@ handle_apply(Tree, Map, State) ->
false ->
NewArgs = t_inf_lists(ArgTypes, t_fun_args(OpType1)),
case any_none(NewArgs) of
- true ->
+ true ->
Msg = {fun_app_args,
[format_args(Args, ArgTypes, State),
format_type(OpType, State)]},
@@ -554,7 +558,7 @@ handle_apply(Tree, Map, State) ->
end
end;
true ->
- FunInfoList = [{local, state__fun_info(Fun, State)}
+ FunInfoList = [{local, state__fun_info(Fun, State)}
|| Fun <- FunList],
handle_apply_or_call(FunInfoList, Args, ArgTypes, Map2, Tree, State1)
end
@@ -562,7 +566,7 @@ handle_apply(Tree, Map, State) ->
handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State) ->
None = t_none(),
- handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State,
+ handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State,
[None || _ <- ArgTypes], None).
handle_apply_or_call([{local, external}|Left], Args, ArgTypes, Map, Tree, State,
@@ -577,7 +581,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
Any = t_any(),
AnyArgs = [Any || _ <- Args],
GenSig = {AnyArgs, fun(_) -> t_any() end},
- {CArgs, CRange} =
+ {CArgs, CRange} =
case Contr of
{value, #contract{args = As} = C} ->
{As, fun(FunArgs) ->
@@ -627,9 +631,9 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
end
end,
ArgModeMask = [case lists:member(Arg, Opaques) of
- true -> opaque;
- false -> structured
- end || Arg <- ArgTypes],
+ true -> opaque;
+ false -> structured
+ end || Arg <- ArgTypes],
NewArgsSig = t_inf_lists_masked(SigArgs, ArgTypes, ArgModeMask),
NewArgsContract = t_inf_lists_masked(CArgs, ArgTypes, ArgModeMask),
NewArgsBif = t_inf_lists_masked(BifArgs, ArgTypes, ArgModeMask),
@@ -637,7 +641,7 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
NewArgTypes = t_inf_lists_masked(NewArgTypes0, NewArgsBif, ArgModeMask),
BifRet = BifRange(NewArgTypes),
{TmpArgTypes, TmpArgsContract} =
- case (TypeOfApply == remote) andalso (not IsBIF) of
+ case (TypeOfApply =:= remote) andalso (not IsBIF) of
true ->
List1 = lists:zip(CArgs, NewArgTypes),
List2 = lists:zip(CArgs, NewArgsContract),
@@ -648,16 +652,17 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
false -> {NewArgTypes, NewArgsContract}
end,
ContrRet = CRange(TmpArgTypes),
- RetMode = case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of
- true -> opaque;
- false -> structured
- end,
+ RetMode =
+ case t_contains_opaque(ContrRet) orelse t_contains_opaque(BifRet) of
+ true -> opaque;
+ false -> structured
+ end,
RetWithoutLocal = t_inf(t_inf(ContrRet, BifRet, RetMode), SigRange, RetMode),
?debug("--------------------------------------------------------\n", []),
?debug("Fun: ~p\n", [Fun]),
?debug("Args: ~s\n", [erl_types:t_to_string(t_product(ArgTypes))]),
?debug("NewArgsSig: ~s\n", [erl_types:t_to_string(t_product(NewArgsSig))]),
- ?debug("NewArgsContract: ~s\n",
+ ?debug("NewArgsContract: ~s\n",
[erl_types:t_to_string(t_product(NewArgsContract))]),
?debug("NewArgsBif: ~s\n", [erl_types:t_to_string(t_product(NewArgsBif))]),
?debug("NewArgTypes: ~s\n", [erl_types:t_to_string(t_product(NewArgTypes))]),
@@ -677,11 +682,11 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
%% respective callback module's function.
Module = State#state.module,
- BehApiInfo = State#state.behaviour_api_info,
+ BehApiDict = State#state.behaviour_api_dict,
{RealFun, RealArgTypes, RealArgs} =
case dialyzer_behaviours:translate_behaviour_api_call(Fun, ArgTypes,
Args, Module,
- BehApiInfo) of
+ BehApiDict) of
plain_call -> {Fun, ArgTypes, Args};
BehaviourAPI -> BehaviourAPI
end,
@@ -698,10 +703,10 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
FailedSig = any_none(NewArgsSig),
FailedContract = any_none([CRange(TmpArgsContract)|NewArgsContract]),
FailedBif = any_none([BifRange(NewArgsBif)|NewArgsBif]),
- InfSig = t_inf(t_fun(SigArgs, SigRange),
+ InfSig = t_inf(t_fun(SigArgs, SigRange),
t_fun(BifArgs, BifRange(BifArgs))),
FailReason = apply_fail_reason(FailedSig, FailedBif, FailedContract),
- Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig,
+ Msg = get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes, InfSig,
Contr, CArgs, State1, FailReason),
WarnType = case Msg of
{call, _} -> ?WARN_FAILING_CALL;
@@ -725,15 +730,15 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left],
remote ->
add_bif_warnings(Fun, NewArgTypes, Tree, State2)
end,
- NewAccArgTypes =
+ NewAccArgTypes =
case FailedConj of
true -> AccArgTypes;
false -> [t_sup(X, Y) || {X, Y} <- lists:zip(NewArgTypes, AccArgTypes)]
end,
NewAccRet = t_sup(AccRet, t_inf(RetWithoutLocal, LocalRet, opaque)),
- handle_apply_or_call(Left, Args, ArgTypes, Map, Tree,
+ handle_apply_or_call(Left, Args, ArgTypes, Map, Tree,
State3, NewAccArgTypes, NewAccRet);
-handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State,
+handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State,
AccArgTypes, AccRet) ->
NewMap = enter_type_lists(Args, AccArgTypes, Map),
{State, NewMap, AccRet}.
@@ -745,13 +750,13 @@ apply_fail_reason(FailedSig, FailedBif, FailedContract) ->
true -> both
end.
-get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
+get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
Sig, Contract, ContrArgs, State, FailReason) ->
ArgStrings = format_args(Args, ArgTypes, State),
ContractInfo =
case Contract of
{value, #contract{} = C} ->
- {dialyzer_contracts:is_overloaded(C),
+ {dialyzer_contracts:is_overloaded(C),
dialyzer_contracts:contract_to_string(C)};
none -> {false, none}
end,
@@ -765,7 +770,7 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
{M, F, _A} ->
case is_opaque_type_test_problem(Fun, NewArgTypes, State) of
true ->
- [Opaque] = NewArgTypes,
+ [Opaque] = NewArgTypes,
{opaque_type_test, [atom_to_list(F), erl_types:t_to_string(Opaque)]};
false ->
SigArgs = t_fun_args(Sig),
@@ -789,7 +794,7 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
{call_without_opaque, [M, F, ArgStrings, ExpectedTriples]};
false -> %% there is a structured term clash in some argument
{call, [M, F, ArgStrings,
- ArgNs, FailReason,
+ ArgNs, FailReason,
format_sig_args(Sig, State),
format_type(t_fun_range(Sig), State),
ContractInfo]}
@@ -797,8 +802,8 @@ get_apply_fail_msg(Fun, Args, ArgTypes, NewArgTypes,
end
end;
Label when is_integer(Label) ->
- {apply, [ArgStrings,
- ArgNs, FailReason,
+ {apply, [ArgStrings,
+ ArgNs, FailReason,
format_sig_args(Sig, State),
format_type(t_fun_range(Sig), State),
ContractInfo]}
@@ -826,7 +831,7 @@ is_opaque_type_test_problem(Fun, ArgTypes, State) ->
FN =:= is_number; FN =:= is_pid; FN =:= is_port;
FN =:= is_reference; FN =:= is_tuple ->
[Type] = ArgTypes,
- erl_types:t_is_opaque(Type) andalso
+ erl_types:t_is_opaque(Type) andalso
not lists:member(Type, State#state.opaques);
_ -> false
end.
@@ -1043,7 +1048,7 @@ handle_cons(Tree, Map, State) ->
Tl = cerl:cons_tl(Tree),
{State1, Map1, HdType} = traverse(Hd, Map, State),
{State2, Map2, TlType} = traverse(Tl, Map1, State1),
- State3 =
+ State3 =
case t_is_none(t_inf(TlType, t_list())) of
true ->
Msg = {improper_list_constr, [format_type(TlType, State2)]},
@@ -1088,7 +1093,7 @@ handle_let(Tree, Map, #state{callgraph = Callgraph, races = Races} = State) ->
case cerl:is_literal(Mod) andalso
cerl:concrete(Mod) =:= ets andalso
cerl:is_literal(Name) andalso
- cerl:concrete(Name) =:= new of
+ cerl:concrete(Name) =:= new of
true ->
NewTable = dialyzer_races:get_new_table(State1#state.races),
renew_public_tables(Vars, NewTable,
@@ -1112,7 +1117,7 @@ handle_module(Tree, Map, State) ->
%% By not including the variables in scope we can assure that we
%% will get the current function type when using the variables.
Defs = cerl:module_defs(Tree),
- PartFun = fun({_Var, Fun}) ->
+ PartFun = fun({_Var, Fun}) ->
state__is_escaping(get_label(Fun), State)
end,
{Defs1, Defs2} = lists:partition(PartFun, Defs),
@@ -1143,12 +1148,12 @@ handle_receive(Tree, Map,
RaceListSize + 1, State);
false -> State
end,
- {MapList, State2, ReceiveType} =
+ {MapList, State2, ReceiveType} =
handle_clauses(Clauses, ?no_arg, t_any(), t_any(), State1, [], Map,
[], []),
Map1 = join_maps(MapList, Map),
{State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2),
- case (t_is_atom(TimeoutType) andalso
+ case (t_is_atom(TimeoutType) andalso
(t_atom_vals(TimeoutType) =:= ['infinity'])) of
true ->
{State3, Map2, ReceiveType};
@@ -1168,17 +1173,17 @@ handle_try(Tree, Map, State) ->
Vars = cerl:try_vars(Tree),
Body = cerl:try_body(Tree),
Handler = cerl:try_handler(Tree),
- {State1, Map1, ArgType} = traverse(Arg, Map, State),
+ {State1, Map1, ArgType} = traverse(Arg, Map, State),
Map2 = mark_as_fresh(Vars, Map1),
{SuccState, SuccMap, SuccType} =
case bind_pat_vars(Vars, t_to_tlist(ArgType), [], Map2, State1) of
{error, _, _, _, _} ->
{State1, map__new(), t_none()};
{SuccMap1, VarTypes} ->
- %% Try to bind the argument. Will only succeed if
+ %% Try to bind the argument. Will only succeed if
%% it is a simple structured term.
SuccMap2 =
- case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], [],
+ case bind_pat_vars_reverse([Arg], [t_product(VarTypes)], [],
SuccMap1, State1) of
{error, _, _, _, _} -> SuccMap1;
{SM, _} -> SM
@@ -1212,10 +1217,10 @@ handle_tuple(Tree, Map, State) ->
RecFields = t_tuple_args(RecStruct),
case bind_pat_vars(Elements, RecFields, [], Map1, State1) of
{error, _, ErrorPat, ErrorType, _} ->
- Msg = {record_constr,
+ Msg = {record_constr,
[TagVal, format_patterns(ErrorPat),
format_type(ErrorType, State1)]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
+ State2 = state__add_warning(State1, ?WARN_MATCHING,
Tree, Msg),
{State2, Map1, t_none()};
{Map2, _ETypes} ->
@@ -1224,26 +1229,24 @@ handle_tuple(Tree, Map, State) ->
_ ->
case state__lookup_record(TagVal, length(Left), State1) of
error -> {State1, Map1, TupleType};
- {ok, Prototype} ->
- %% io:format("In handle_tuple:\n Prototype = ~p\n", [Prototype]),
- InfTupleType = t_inf(Prototype, TupleType),
- %% io:format(" TupleType = ~p,\n Inf = ~p\n", [TupleType, InfTupleType]),
+ {ok, RecType} ->
+ InfTupleType = t_inf(RecType, TupleType),
case t_is_none(InfTupleType) of
true ->
- Msg = {record_constr,
- [format_type(TupleType, State1), TagVal]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
+ RecC = format_type(TupleType, State1),
+ FieldDiffs = format_field_diffs(TupleType, State1),
+ Msg = {record_constr, [RecC, FieldDiffs]},
+ State2 = state__add_warning(State1, ?WARN_MATCHING,
Tree, Msg),
{State2, Map1, t_none()};
false ->
- case bind_pat_vars(Elements, t_tuple_args(Prototype),
+ case bind_pat_vars(Elements, t_tuple_args(RecType),
[], Map1, State1) of
{error, bind, ErrorPat, ErrorType, _} ->
- %% io:format("error\n", []),
- Msg = {record_constr,
+ Msg = {record_constr,
[TagVal, format_patterns(ErrorPat),
format_type(ErrorType, State1)]},
- State2 = state__add_warning(State1, ?WARN_MATCHING,
+ State2 = state__add_warning(State1, ?WARN_MATCHING,
Tree, Msg),
{State2, Map1, t_none()};
{Map2, ETypes} ->
@@ -1305,7 +1308,7 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType,
handle_clauses([], _Arg, _ArgType, _OrigArgType,
#state{callgraph = Callgraph, races = Races} = State,
CaseTypes, _MapIn, Acc, ClauseAcc) ->
- State1 =
+ State1 =
case dialyzer_callgraph:get_race_detection(Callgraph) andalso
dialyzer_races:get_race_analysis(Races) of
true ->
@@ -1313,7 +1316,7 @@ handle_clauses([], _Arg, _ArgType, _OrigArgType,
[dialyzer_races:end_case_new(ClauseAcc)|
dialyzer_races:get_race_list(Races)],
dialyzer_races:get_race_list_size(Races) + 1, State);
- false -> State
+ false -> State
end,
{lists:reverse(Acc), State1, t_sup(CaseTypes)}.
@@ -1324,7 +1327,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
Body = cerl:clause_body(C),
RaceDetection = dialyzer_callgraph:get_race_detection(Callgraph),
RaceAnalysis = dialyzer_races:get_race_analysis(Races),
- State1 =
+ State1 =
case RaceDetection andalso RaceAnalysis of
true ->
state__renew_fun_args(Pats, State);
@@ -1339,7 +1342,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
true ->
{error, bind, Pats, ArgType0, ArgType0};
false ->
- ArgTypes =
+ ArgTypes =
case t_is_any(ArgType0) of
true -> [ArgType0 || _ <- Pats];
false -> t_to_tlist(ArgType0)
@@ -1348,7 +1351,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
end,
case BindRes of
{error, BindOrOpaque, NewPats, Type, OpaqueTerm} ->
- ?debug("Failed binding pattern: ~s\nto ~s\n",
+ ?debug("Failed binding pattern: ~s\nto ~s\n",
[cerl_prettypr:format(C), format_type(ArgType0, State1)]),
case state__warning_mode(State1) of
false ->
@@ -1359,7 +1362,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
bind -> format_patterns(Pats);
opaque -> format_patterns(NewPats)
end,
- {Msg, Force} =
+ {Msg, Force} =
case t_is_none(ArgType0) of
true ->
PatTypes = [PatString, format_type(OrigArgType, State1)],
@@ -1375,13 +1378,13 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
{_, _} -> {{pattern_match_cov, PatTypes}, false}
end;
false ->
- %% Try to find out if this is a default clause in a list
+ %% Try to find out if this is a default clause in a list
%% comprehension and supress this. A real Hack(tm)
Force0 =
case is_compiler_generated(cerl:get_ann(C)) of
true ->
case Pats of
- [Pat] ->
+ [Pat] ->
case cerl:is_c_cons(Pat) of
true ->
not (cerl:is_c_var(cerl:cons_hd(Pat)) andalso
@@ -1398,9 +1401,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
end,
PatTypes = case BindOrOpaque of
bind -> [PatString, format_type(ArgType0, State1)];
- opaque -> [PatString, format_type(Type, State1),
+ opaque -> [PatString, format_type(Type, State1),
format_type(OpaqueTerm, State1)]
- end,
+ end,
FailedMsg = case BindOrOpaque of
bind -> {pattern_match, PatTypes};
opaque -> {opaque_match, PatTypes}
@@ -1420,9 +1423,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
case Arg =:= ?no_arg of
true -> Map2;
false ->
- %% Try to bind the argument. Will only succeed if
+ %% Try to bind the argument. Will only succeed if
%% it is a simple structured term.
- case bind_pat_vars_reverse([Arg], [t_product(PatTypes)],
+ case bind_pat_vars_reverse([Arg], [t_product(PatTypes)],
[], Map2, State1) of
{error, _, _, _, _} -> Map2;
{NewMap, _} -> NewMap
@@ -1436,11 +1439,11 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
t_subtract(t_product(t_to_tlist(ArgType0)), GenType)
end,
case bind_guard(Guard, Map3, State1) of
- {error, Reason} ->
- ?debug("Failed guard: ~s\n",
+ {error, Reason} ->
+ ?debug("Failed guard: ~s\n",
[cerl_prettypr:format(C, [{hook, cerl_typean:pp_hook()}])]),
PatString = format_patterns(Pats),
- DefaultMsg =
+ DefaultMsg =
case Pats =:= [] of
true -> {guard_fail, []};
false ->
@@ -1470,7 +1473,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map,
bind_subst(Arg, Pats, Map) ->
case cerl:type(Arg) of
- values ->
+ values ->
bind_subst_list(cerl:values_es(Arg), Pats, Map);
var ->
[Pat] = Pats,
@@ -1499,16 +1502,16 @@ bind_subst_list([], [], Map) ->
%%
bind_pat_vars(Pats, Types, Acc, Map, State) ->
- try
+ try
bind_pat_vars(Pats, Types, Acc, Map, State, false)
- catch
+ catch
throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType}
end.
bind_pat_vars_reverse(Pats, Types, Acc, Map, State) ->
- try
+ try
bind_pat_vars(Pats, Types, Acc, Map, State, true)
- catch
+ catch
throw:Error -> Error % Error = {error, bind | opaque, ErrorPats, ErrorType}
end.
@@ -1520,7 +1523,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
AliasPat = cerl:alias_pat(Pat),
Var = cerl:alias_var(Pat),
Map1 = enter_subst(Var, AliasPat, Map),
- {Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [],
+ {Map2, [PatType]} = bind_pat_vars([AliasPat], [Type], [],
Map1, State, Rev),
{enter_type(Var, PatType, Map2), PatType};
binary ->
@@ -1541,18 +1544,18 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
cons ->
Cons = t_inf(Type, t_cons()),
case t_is_none(Cons) of
- true ->
+ true ->
bind_opaque_pats(t_cons(), Type, Pat, Map, State, Rev);
false ->
- {Map1, [HdType, TlType]} =
+ {Map1, [HdType, TlType]} =
bind_pat_vars([cerl:cons_hd(Pat), cerl:cons_tl(Pat)],
- [t_cons_hd(Cons), t_cons_tl(Cons)],
+ [t_cons_hd(Cons), t_cons_tl(Cons)],
[], Map, State, Rev),
{Map1, t_cons(HdType, TlType)}
end;
literal ->
Literal = literal_type(Pat),
- LiteralOrOpaque =
+ LiteralOrOpaque =
case t_opaque_match_atom(Literal, State#state.opaques) of
[Opaque] -> Opaque;
_ -> Literal
@@ -1564,7 +1567,7 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
end;
tuple ->
Es = cerl:tuple_es(Pat),
- Prototype =
+ Prototype =
case Es of
[] -> t_tuple([]);
[Tag|Left] ->
@@ -1585,10 +1588,10 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
false ->
SubTuples = t_tuple_subtypes(Tuple),
%% Need to call the top function to get the try-catch wrapper
- Results =
+ Results =
case Rev of
true ->
- [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [],
+ [bind_pat_vars_reverse(Es, t_tuple_args(SubTuple), [],
Map, State)
|| SubTuple <- SubTuples];
false ->
@@ -1632,12 +1635,12 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
end,
%% Must do inf when binding args to pats. Vars in pats are fresh.
VarType2 = t_inf(VarType1, Type),
- VarType3 =
+ VarType3 =
case Opaques =/= [] of
true ->
case t_opaque_match_record(VarType2, Opaques) of
[OpaqueRec] -> OpaqueRec;
- _ ->
+ _ ->
case t_opaque_match_atom(VarType2, Opaques) of
[OpaqueAtom] -> OpaqueAtom;
_ -> VarType2
@@ -1648,9 +1651,9 @@ bind_pat_vars([Pat|PatLeft], [Type|TypeLeft], Acc, Map, State, Rev) ->
case t_is_none(VarType3) of
true ->
case t_find_opaque_mismatch(VarType1, Type) of
- {ok, T1, T2} ->
+ {ok, T1, T2} ->
bind_error([Pat], T1, T2, opaque);
- error ->
+ error ->
bind_error([Pat], Type, t_none(), bind)
end;
false ->
@@ -1752,10 +1755,10 @@ bind_guard(Guard, Map, State) ->
end.
bind_guard(Guard, Map, Env, Eval, State) ->
- ?debug("Handling ~w guard: ~s\n",
+ ?debug("Handling ~w guard: ~s\n",
[Eval, cerl_prettypr:format(Guard, [{noann, true}])]),
case cerl:type(Guard) of
- binary ->
+ binary ->
{Map, t_binary()};
'case' ->
Arg = cerl:case_arg(Guard),
@@ -1793,10 +1796,10 @@ bind_guard(Guard, Map, Env, Eval, State) ->
var ->
?debug("Looking for var(~w)...", [cerl_trees:get_label(Guard)]),
case dict:find(get_label(Guard), Env) of
- error ->
+ error ->
?debug("Did not find it\n", []),
Type = lookup_type(Guard, Map),
- Constr =
+ Constr =
case Eval of
pos -> t_atom(true);
neg -> t_atom(false);
@@ -1804,7 +1807,7 @@ bind_guard(Guard, Map, Env, Eval, State) ->
end,
Inf = t_inf(Constr, Type),
{enter_type(Guard, Inf, Map), Inf};
- {ok, Tree} ->
+ {ok, Tree} ->
?debug("Found it\n", []),
{Map1, Type} = bind_guard(Tree, Map, Env, Eval, State),
{enter_type(Guard, Type, Map1), Type}
@@ -1827,7 +1830,7 @@ handle_guard_call(Guard, Map, Env, Eval, State) ->
handle_guard_type_test(Guard, F, Map, Env, Eval, State);
{erlang, is_function, 2} ->
handle_guard_is_function(Guard, Map, Env, Eval, State);
- MFA when (MFA =:= {erlang, internal_is_record, 3}) or
+ MFA when (MFA =:= {erlang, internal_is_record, 3}) or
(MFA =:= {erlang, is_record, 3}) ->
handle_guard_is_record(Guard, Map, Env, Eval, State);
{erlang, '=:=', 2} ->
@@ -1840,7 +1843,7 @@ handle_guard_call(Guard, Map, Env, Eval, State) ->
handle_guard_or(Guard, Map, Env, Eval, State);
{erlang, 'not', 1} ->
handle_guard_not(Guard, Map, Env, Eval, State);
- {erlang, Comp, 2} when Comp =:= '<'; Comp =:= '=<';
+ {erlang, Comp, 2} when Comp =:= '<'; Comp =:= '=<';
Comp =:= '>'; Comp =:= '>=' ->
handle_guard_comp(Guard, Comp, Map, Env, Eval, State);
_ ->
@@ -1875,7 +1878,7 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) ->
List -> List
end,
Map2 = enter_type_lists(Args, t_inf_lists(BifArgs, As0, Mode), Map1),
- Ret =
+ Ret =
case Eval of
pos -> t_inf(t_atom(true), BifRet);
neg -> t_inf(t_atom(false), BifRet);
@@ -1892,14 +1895,14 @@ handle_guard_gen_fun({M, F, A}, Guard, Map, Env, Eval, State) ->
end.
handle_guard_type_test(Guard, F, Map, Env, Eval, State) ->
- [Arg] = cerl:call_args(Guard),
+ [Arg] = cerl:call_args(Guard),
{Map1, ArgType} = bind_guard(Arg, Map, Env, dont_know, State),
case bind_type_test(Eval, F, ArgType, State) of
- error ->
+ error ->
?debug("Type test: ~w failed\n", [F]),
signal_guard_fail(Guard, [ArgType], State);
- {ok, NewArgType, Ret} ->
- ?debug("Type test: ~w succeeded, NewType: ~s, Ret: ~s\n",
+ {ok, NewArgType, Ret} ->
+ ?debug("Type test: ~w succeeded, NewType: ~s, Ret: ~s\n",
[F, t_to_string(NewArgType), t_to_string(Ret)]),
{enter_type(Arg, NewArgType, Map1), Ret}
end.
@@ -1930,13 +1933,13 @@ bind_type_test(Eval, TypeTest, ArgType, State) ->
end;
neg ->
case Mode of
- opaque ->
+ opaque ->
Struct = erl_types:t_opaque_structure(ArgType),
case t_is_none(t_subtract(Struct, Type)) of
true -> error;
false -> {ok, ArgType, t_atom(false)}
end;
- structured ->
+ structured ->
Sub = t_subtract(ArgType, Type),
case t_is_none(Sub) of
true -> error;
@@ -1974,7 +1977,7 @@ handle_guard_comp(Guard, Comp, Map, Env, Eval, State) ->
error -> signal_guard_fail(Guard, ArgTypes, State);
{ok, NewMap} -> {NewMap, t_atom(true)}
end;
- {_, _} ->
+ {_, _} ->
handle_guard_gen_fun({erlang, Comp, 2}, Guard, Map, Env, Eval, State)
end.
@@ -2021,7 +2024,7 @@ handle_guard_is_function(Guard, Map, Env, Eval, State) ->
end,
FunType = t_inf(FunType0, FunTypeConstr),
case t_is_none(FunType) of
- true ->
+ true ->
case Eval of
pos -> signal_guard_fail(Guard, ArgTypes0, State);
neg -> {Map1, t_atom(false)};
@@ -2057,16 +2060,16 @@ handle_guard_is_record(Guard, Map, Env, Eval, State) ->
end,
Type = t_inf(NewTupleType, RecType, Mode),
case t_is_none(Type) of
- true ->
+ true ->
case Eval of
- pos -> signal_guard_fail(Guard,
- [RecType, t_from_term(Tag),
+ pos -> signal_guard_fail(Guard,
+ [RecType, t_from_term(Tag),
t_from_term(Arity)],
State);
neg -> {Map1, t_atom(false)};
dont_know -> {Map1, t_atom(false)}
end;
- false ->
+ false ->
case Eval of
pos -> {enter_type(Rec, Type, Map1), t_atom(true)};
neg -> {Map1, t_atom(false)};
@@ -2079,17 +2082,17 @@ handle_guard_eq(Guard, Map, Env, Eval, State) ->
case {cerl:type(Arg1), cerl:type(Arg2)} of
{literal, literal} ->
case cerl:concrete(Arg1) =:= cerl:concrete(Arg2) of
- true ->
- if
+ true ->
+ if
Eval =:= pos -> {Map, t_atom(true)};
Eval =:= neg -> throw({fail, none});
Eval =:= dont_know -> {Map, t_atom(true)}
end;
false ->
- if
+ if
Eval =:= neg -> {Map, t_atom(false)};
Eval =:= dont_know -> {Map, t_atom(false)};
- Eval =:= pos ->
+ Eval =:= pos ->
ArgTypes = [t_from_term(cerl:concrete(Arg1)),
t_from_term(cerl:concrete(Arg2))],
signal_guard_fail(Guard, ArgTypes, State)
@@ -2144,7 +2147,7 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) ->
false ->
if Eval =:= neg -> {Map, t_atom(false)};
Eval =:= dont_know -> {Map, t_atom(false)};
- Eval =:= pos ->
+ Eval =:= pos ->
ArgTypes = [t_from_term(cerl:concrete(Arg1)),
t_from_term(cerl:concrete(Arg2))],
signal_guard_fail(Guard, ArgTypes, State)
@@ -2161,7 +2164,7 @@ handle_guard_eqeq(Guard, Map, Env, Eval, State) ->
bind_eqeq_guard(Guard, Arg1, Arg2, Map, Env, Eval, State) ->
{Map1, Type1} = bind_guard(Arg1, Map, Env, dont_know, State),
{Map2, Type2} = bind_guard(Arg2, Map1, Env, dont_know, State),
- ?debug("Types are:~s =:= ~s\n", [t_to_string(Type1),
+ ?debug("Types are:~s =:= ~s\n", [t_to_string(Type1),
t_to_string(Type2)]),
Inf = t_inf(Type1, Type2),
case t_is_none(Inf) of
@@ -2202,15 +2205,15 @@ bind_eqeq_guard_lit_other(Guard, Arg1, Arg2, Map, Env, State) ->
{_, Type} = MT = bind_guard(Arg2, Map, Env, pos, State),
case t_is_atom(true, Type) of
true -> MT;
- false ->
+ false ->
{_, Type0} = bind_guard(Arg2, Map, Env, dont_know, State),
signal_guard_fail(Guard, [Type0, t_atom(true)], State)
end;
- false ->
+ false ->
{Map1, Type} = bind_guard(Arg2, Map, Env, neg, State),
case t_is_atom(false, Type) of
true -> {Map1, t_atom(true)};
- false ->
+ false ->
{_, Type0} = bind_guard(Arg2, Map, Env, dont_know, State),
signal_guard_fail(Guard, [Type0, t_atom(true)], State)
end;
@@ -2242,11 +2245,11 @@ handle_guard_and(Guard, Map, Env, Eval, State) ->
end
end;
neg ->
- {Map1, Type1} =
+ {Map1, Type1} =
try bind_guard(Arg1, Map, Env, neg, State)
catch throw:{fail, _} -> bind_guard(Arg2, Map, Env, pos, State)
end,
- {Map2, Type2} =
+ {Map2, Type2} =
try bind_guard(Arg1, Map, Env, neg, State)
catch throw:{fail, _} -> bind_guard(Arg2, Map, Env, pos, State)
end,
@@ -2272,18 +2275,18 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
[Arg1, Arg2] = cerl:call_args(Guard),
case Eval of
pos ->
- {Map1, Bool1} =
+ {Map1, Bool1} =
try bind_guard(Arg1, Map, Env, pos, State)
- catch
+ catch
throw:{fail,_} -> bind_guard(Arg1, Map, Env, dont_know, State)
end,
- {Map2, Bool2} =
+ {Map2, Bool2} =
try bind_guard(Arg2, Map, Env, pos, State)
- catch
+ catch
throw:{fail,_} -> bind_guard(Arg2, Map, Env, dont_know, State)
end,
case ((t_is_atom(true, Bool1) andalso t_is_boolean(Bool2))
- orelse
+ orelse
(t_is_atom(true, Bool2) andalso t_is_boolean(Bool1))) of
true -> {join_maps([Map1, Map2], Map), t_atom(true)};
false -> throw({fail, none})
@@ -2311,19 +2314,19 @@ handle_guard_or(Guard, Map, Env, Eval, State) ->
handle_guard_not(Guard, Map, Env, Eval, State) ->
[Arg] = cerl:call_args(Guard),
case Eval of
- neg ->
+ neg ->
{Map1, Type} = bind_guard(Arg, Map, Env, pos, State),
case t_is_atom(true, Type) of
true -> {Map1, t_atom(false)};
false -> throw({fail, none})
end;
- pos ->
+ pos ->
{Map1, Type} = bind_guard(Arg, Map, Env, neg, State),
case t_is_atom(false, Type) of
true -> {Map1, t_atom(true)};
false -> throw({fail, none})
end;
- dont_know ->
+ dont_know ->
{Map1, Type} = bind_guard(Arg, Map, Env, dont_know, State),
Bool = t_inf(Type, t_boolean()),
case t_is_none(Bool) of
@@ -2355,10 +2358,10 @@ signal_guard_fail(Guard, ArgTypes, State) ->
MFA = {cerl:atom_val(cerl:call_module(Guard)), F, length(Args)},
Msg =
case is_infix_op(MFA) of
- true ->
+ true ->
[ArgType1, ArgType2] = ArgTypes,
[Arg1, Arg2] = Args,
- {guard_fail, [format_args_1([Arg1], [ArgType1], State),
+ {guard_fail, [format_args_1([Arg1], [ArgType1], State),
atom_to_list(F),
format_args_1([Arg2], [ArgType2], State)]};
false ->
@@ -2381,7 +2384,7 @@ is_infix_op({M, F, A}) when is_atom(M), is_atom(F),
no_return().
signal_guard_fatal_fail(Guard, ArgTypes, State) ->
- Args = cerl:call_args(Guard),
+ Args = cerl:call_args(Guard),
F = cerl:atom_val(cerl:call_name(Guard)),
Msg = mk_guard_msg(F, Args, ArgTypes, State),
throw({fatal_fail, {Guard, Msg}}).
@@ -2392,11 +2395,11 @@ mk_guard_msg(F, Args, ArgTypes, State) ->
true -> {opaque_guard, FArgs};
false -> {guard_fail, FArgs}
end.
-
+
bind_guard_case_clauses(Arg, Clauses, Map, Env, Eval, State) ->
Clauses1 = filter_fail_clauses(Clauses),
{GenMap, GenArgType} = bind_guard(Arg, Map, Env, dont_know, State),
- bind_guard_case_clauses(GenArgType, GenMap, Arg, Clauses1, Map, Env, Eval,
+ bind_guard_case_clauses(GenArgType, GenMap, Arg, Clauses1, Map, Env, Eval,
t_none(), [], State).
filter_fail_clauses([Clause|Left]) ->
@@ -2413,7 +2416,7 @@ filter_fail_clauses([Clause|Left]) ->
filter_fail_clauses([]) ->
[].
-bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
+bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
Map, Env, Eval, AccType, AccMaps, State) ->
Pats = cerl:clause_pats(Clause),
{NewMap0, ArgType} =
@@ -2427,7 +2430,7 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
false -> bind_guard(ArgExpr, Map, Env, neg, State);
_ -> {GenMap, GenArgType}
end
- catch
+ catch
throw:{fail, _} -> {none, GenArgType}
end;
false ->
@@ -2457,7 +2460,7 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
NewGenArgType = t_subtract(GenArgType, GenPatType),
case (NewMap1 =:= none) orelse t_is_none(GenArgType) of
true ->
- bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env,
+ bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env,
Eval, AccType, AccMaps, State);
false ->
{NewAccType, NewAccMaps} =
@@ -2467,15 +2470,15 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
true -> throw({fail, none});
false -> ok
end,
- {NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2,
+ {NewMap3, CType} = bind_guard(cerl:clause_body(Clause), NewMap2,
Env, Eval, State),
case Eval of
- pos ->
+ pos ->
case t_is_atom(true, CType) of
true -> ok;
false -> throw({fail, none})
end;
- neg ->
+ neg ->
case t_is_atom(false, CType) of
true -> ok;
false -> throw({fail, none})
@@ -2487,10 +2490,10 @@ bind_guard_case_clauses(GenArgType, GenMap, ArgExpr, [Clause|Left],
catch
throw:{fail, _What} -> {AccType, AccMaps}
end,
- bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env,
+ bind_guard_case_clauses(NewGenArgType, GenMap, ArgExpr, Left, Map, Env,
Eval, NewAccType, NewAccMaps, State)
end;
-bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval,
+bind_guard_case_clauses(_GenArgType, _GenMap, _ArgExpr, [], Map, _Env, _Eval,
AccType, AccMaps, _State) ->
case t_is_none(AccType) of
true -> throw({fail, none});
@@ -2576,7 +2579,7 @@ enter_type(Key, Val, {Map, Subst} = MS) ->
enter_subst(Key, Val, {Map, Subst} = MS) ->
KeyLabel = get_label(Key),
case cerl:is_literal(Val) of
- true ->
+ true ->
NewMap = dict:store(KeyLabel, literal_type(Val), Map),
{NewMap, Subst};
false ->
@@ -2598,13 +2601,13 @@ enter_subst(Key, Val, {Map, Subst} = MS) ->
end
end.
-lookup_type(Key, {Map, Subst}) ->
+lookup_type(Key, {Map, Subst}) ->
lookup(Key, Map, Subst, t_none()).
lookup(Key, Map, Subst, AnyNone) ->
case cerl:is_literal(Key) of
true -> literal_type(Key);
- false ->
+ false ->
Label = get_label(Key),
case dict:find(Label, Subst) of
{ok, NewKey} -> lookup(NewKey, Map, Subst, AnyNone);
@@ -2669,7 +2672,7 @@ get_label(T) ->
t_is_simple(ArgType) ->
t_is_atom(ArgType) orelse t_is_number(ArgType) orelse t_is_port(ArgType)
- orelse t_is_pid(ArgType) orelse t_is_reference(ArgType)
+ orelse t_is_pid(ArgType) orelse t_is_reference(ArgType)
orelse t_is_nil(ArgType).
%% t_is_structured(ArgType) ->
@@ -2687,8 +2690,8 @@ is_call_to_send(Tree) ->
Mod = cerl:call_module(Tree),
Name = cerl:call_name(Tree),
Arity = cerl:call_arity(Tree),
- cerl:is_c_atom(Mod)
- andalso cerl:is_c_atom(Name)
+ cerl:is_c_atom(Mod)
+ andalso cerl:is_c_atom(Name)
andalso (cerl:atom_val(Name) =:= '!')
andalso (cerl:atom_val(Mod) =:= erlang)
andalso (Arity =:= 2)
@@ -2714,7 +2717,7 @@ filter_match_fail([Clause] = Cls) ->
filter_match_fail([H|T]) ->
[H|filter_match_fail(T)];
filter_match_fail([]) ->
- %% This can actually happen, for example in
+ %% This can actually happen, for example in
%% receive after 1 -> ok end
[].
@@ -2731,9 +2734,11 @@ determine_mode(Type, Opaques) ->
%%% ===========================================================================
state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) ->
+ Opaques = erl_types:module_builtin_opaques(Module) ++
+ erl_types:t_opaque_from_records(Records),
TreeMap = build_tree_map(Tree),
Funs = dict:fetch_keys(TreeMap),
- FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt),
+ FunTab = init_fun_tab(Funs, dict:new(), TreeMap, Callgraph, Plt, Opaques),
Work = init_work([get_label(Tree)]),
Env = dict:store(top, map__new(), dict:new()),
Opaques = erl_types:module_builtin_opaques(Module) ++
@@ -2741,12 +2746,12 @@ state__new(Callgraph, Tree, Plt, Module, Records, BehaviourTranslations) ->
#state{callgraph = Callgraph, envs = Env, fun_tab = FunTab, opaques = Opaques,
plt = Plt, races = dialyzer_races:new(), records = Records,
warning_mode = false, warnings = [], work = Work, tree_map = TreeMap,
- module = Module, behaviour_api_info = BehaviourTranslations}.
+ module = Module, behaviour_api_dict = BehaviourTranslations}.
state__mark_fun_as_handled(#state{fun_tab = FunTab} = State, Fun0) ->
Fun = get_label(Fun0),
case dict:find(Fun, FunTab) of
- {ok, {not_handled, Entry}} ->
+ {ok, {not_handled, Entry}} ->
State#state{fun_tab = dict:store(Fun, Entry, FunTab)};
{ok, {_, _}} ->
State
@@ -2800,7 +2805,7 @@ state__add_warning(State, Tag, Tree, Msg) ->
state__add_warning(#state{warning_mode = false} = State, _, _, _, _) ->
State;
-state__add_warning(#state{warnings = Warnings, warning_mode = true} = State,
+state__add_warning(#state{warnings = Warnings, warning_mode = true} = State,
Tag, Tree, Msg, Force) ->
Ann = cerl:get_ann(Tree),
case Force of
@@ -2848,7 +2853,7 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
{Name, Contract} =
case dialyzer_callgraph:lookup_name(FunLbl, Callgraph) of
error -> {[], none};
- {ok, {_M, F, A} = MFA} ->
+ {ok, {_M, F, A} = MFA} ->
{[F, A], dialyzer_plt:lookup_contract(Plt, MFA)}
end,
case t_is_none(Ret) of
@@ -2866,19 +2871,19 @@ state__get_warnings(#state{tree_map = TreeMap, fun_tab = FunTab,
case classify_returns(Fun) of
no_match ->
Msg = {no_return, [no_match|Name]},
- state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
+ state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
Fun, Msg);
only_explicit ->
Msg = {no_return, [only_explicit|Name]},
- state__add_warning(AccState, ?WARN_RETURN_ONLY_EXIT,
+ state__add_warning(AccState, ?WARN_RETURN_ONLY_EXIT,
Fun, Msg);
only_normal ->
Msg = {no_return, [only_normal|Name]},
- state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
+ state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
Fun, Msg);
both ->
Msg = {no_return, [both|Name]},
- state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
+ state__add_warning(AccState, ?WARN_RETURN_NO_RETURN,
Fun, Msg)
end;
false ->
@@ -2916,10 +2921,10 @@ state__lookup_name(Fun, #state{callgraph = Callgraph}) ->
state__lookup_record(Tag, Arity, #state{records = Records}) ->
case erl_types:lookup_record(Tag, Arity, Records) of
- {ok, Fields} ->
+ {ok, Fields} ->
{ok, t_tuple([t_atom(Tag)|
[FieldType || {_FieldName, FieldType} <- Fields]])};
- error ->
+ error ->
error
end.
@@ -2942,15 +2947,15 @@ build_tree_map(Tree) ->
end,
cerl_trees:fold(Fun, dict:new(), Tree).
-init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt) ->
+init_fun_tab([top|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
NewDict = dict:store(top, {not_handled, {[], t_none()}}, Dict),
- init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt);
-init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) ->
+ init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques);
+init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt, Opaques) ->
Arity = cerl:fun_arity(dict:fetch(Fun, TreeMap)),
FunEntry =
case dialyzer_callgraph:is_escaping(Fun, Callgraph) of
true ->
- Args = lists:duplicate(Arity, t_any()),
+ Args = lists:duplicate(Arity, t_any()),
case lookup_fun_sig(Fun, Callgraph, Plt) of
none -> {Args, t_unit()};
{value, {RetType, _}} ->
@@ -2962,8 +2967,8 @@ init_fun_tab([Fun|Left], Dict, TreeMap, Callgraph, Plt) ->
false -> {lists:duplicate(Arity, t_none()), t_unit()}
end,
NewDict = dict:store(Fun, {not_handled, FunEntry}, Dict),
- init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt);
-init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt) ->
+ init_fun_tab(Left, NewDict, TreeMap, Callgraph, Plt, Opaques);
+init_fun_tab([], Dict, _TreeMap, _Callgraph, _Plt, _Opaques) ->
Dict.
state__update_fun_env(Tree, Map, #state{envs = Envs} = State) ->
@@ -2990,7 +2995,7 @@ state__all_fun_types(#state{fun_tab = FunTab}) ->
dict:map(fun(_Fun, {Args, Ret}) -> t_fun(Args, Ret)end, Tab1).
state__fun_type(Fun, #state{fun_tab = FunTab}) ->
- Label =
+ Label =
if is_integer(Fun) -> Fun;
true -> get_label(Fun)
end,
@@ -3001,10 +3006,10 @@ state__fun_type(Fun, #state{fun_tab = FunTab}) ->
t_fun(A, R)
end.
-state__update_fun_entry(Tree, ArgTypes, Out0,
+state__update_fun_entry(Tree, ArgTypes, Out0,
#state{fun_tab=FunTab, callgraph=CG, plt=Plt} = State)->
Fun = get_label(Tree),
- Out1 =
+ Out1 =
if Fun =:= top -> Out0;
true ->
case lookup_fun_sig(Fun, CG, Plt) of
@@ -3016,15 +3021,15 @@ state__update_fun_entry(Tree, ArgTypes, Out0,
case dict:find(Fun, FunTab) of
{ok, {ArgTypes, OldOut}} ->
case t_is_equal(OldOut, Out) of
- true ->
- ?debug("Fixpoint for ~w: ~s\n",
- [state__lookup_name(Fun, State),
+ true ->
+ ?debug("Fixpoint for ~w: ~s\n",
+ [state__lookup_name(Fun, State),
t_to_string(t_fun(ArgTypes, Out))]),
State;
false ->
NewEntry = {ArgTypes, Out},
- ?debug("New Entry for ~w: ~s\n",
- [state__lookup_name(Fun, State),
+ ?debug("New Entry for ~w: ~s\n",
+ [state__lookup_name(Fun, State),
t_to_string(t_fun(ArgTypes, Out))]),
NewFunTab = dict:store(Fun, NewEntry, FunTab),
State1 = State#state{fun_tab = NewFunTab},
@@ -3033,8 +3038,8 @@ state__update_fun_entry(Tree, ArgTypes, Out0,
{ok, {NewArgTypes, _OldOut}} ->
%% Can only happen in self-recursive functions. Only update the out type.
NewEntry = {NewArgTypes, Out},
- ?debug("New Entry for ~w: ~s\n",
- [state__lookup_name(Fun, State),
+ ?debug("New Entry for ~w: ~s\n",
+ [state__lookup_name(Fun, State),
t_to_string(t_fun(NewArgTypes, Out))]),
NewFunTab = dict:store(Fun, NewEntry, FunTab),
State1 = State#state{fun_tab = NewFunTab},
@@ -3053,9 +3058,9 @@ state__add_work_from_fun(Tree, #state{callgraph = Callgraph,
MFAList ->
LabelList = [dialyzer_callgraph:lookup_label(MFA, Callgraph)
|| MFA <- MFAList],
- %% Must filter the result for results in this module.
+ %% Must filter the result for results in this module.
FilteredList = [L || {ok, L} <- LabelList, dict:is_key(L, TreeMap)],
- ?debug("~w: Will try to add:~w\n",
+ ?debug("~w: Will try to add:~w\n",
[state__lookup_name(get_label(Tree), State), MFAList]),
lists:foldl(fun(L, AccState) ->
state__add_work(L, AccState)
@@ -3086,15 +3091,15 @@ state__fun_info(external, #state{}) ->
external;
state__fun_info({_, _, _} = MFA, #state{plt = PLT}) ->
{MFA,
- dialyzer_plt:lookup(PLT, MFA),
+ dialyzer_plt:lookup(PLT, MFA),
dialyzer_plt:lookup_contract(PLT, MFA),
t_any()};
state__fun_info(Fun, #state{callgraph = CG, fun_tab = FunTab, plt = PLT}) ->
{Sig, Contract} =
case dialyzer_callgraph:lookup_name(Fun, CG) of
- error ->
+ error ->
{dialyzer_plt:lookup(PLT, Fun), none};
- {ok, MFA} ->
+ {ok, MFA} ->
{dialyzer_plt:lookup(PLT, MFA), dialyzer_plt:lookup_contract(PLT, MFA)}
end,
LocalRet =
@@ -3122,18 +3127,18 @@ state__find_apply_return(Tree, #state{callgraph = Callgraph} = State) ->
forward_args(Fun, ArgTypes, #state{work = Work, fun_tab = FunTab} = State) ->
{OldArgTypes, OldOut, Fixpoint} =
case dict:find(Fun, FunTab) of
- {ok, {not_handled, {OldArgTypes0, OldOut0}}} ->
+ {ok, {not_handled, {OldArgTypes0, OldOut0}}} ->
{OldArgTypes0, OldOut0, false};
{ok, {OldArgTypes0, OldOut0}} ->
- {OldArgTypes0, OldOut0,
+ {OldArgTypes0, OldOut0,
t_is_subtype(t_product(ArgTypes), t_product(OldArgTypes0))}
end,
case Fixpoint of
true -> State;
- false ->
+ false ->
NewArgTypes = [t_sup(X, Y) || {X, Y} <- lists:zip(ArgTypes, OldArgTypes)],
NewWork = add_work(Fun, Work),
- ?debug("~w: forwarding args ~s\n",
+ ?debug("~w: forwarding args ~s\n",
[state__lookup_name(Fun, State),
t_to_string(t_product(NewArgTypes))]),
NewFunTab = dict:store(Fun, {NewArgTypes, OldOut}, FunTab),
@@ -3248,7 +3253,7 @@ get_file([_|Tail]) -> get_file(Tail).
is_compiler_generated(Ann) ->
lists:member(compiler_generated, Ann) orelse (get_line(Ann) < 1).
--spec format_args([term()], [erl_types:erl_type()], state()) ->
+-spec format_args([cerl:cerl()], [erl_types:erl_type()], state()) ->
nonempty_string().
format_args([], [], _State) ->
@@ -3256,9 +3261,6 @@ format_args([], [], _State) ->
format_args(ArgList, TypeList, State) ->
"(" ++ format_args_1(ArgList, TypeList, State) ++ ")".
--spec format_args_1([term(),...], [erl_types:erl_type(),...], state()) ->
- string().
-
format_args_1([Arg], [Type], State) ->
format_arg(Arg) ++ format_type(Type, State);
format_args_1([Arg|Args], [Type|Types], State) ->
@@ -3291,6 +3293,11 @@ format_arg(Arg) ->
format_type(Type, #state{records = R}) ->
t_to_string(Type, R).
+-spec format_field_diffs(erl_types:erl_type(), state()) -> string().
+
+format_field_diffs(RecConstruction, #state{records = R}) ->
+ erl_types:record_field_diffs_to_string(RecConstruction, R).
+
-spec format_sig_args(erl_types:erl_type(), state()) -> string().
format_sig_args(Type, #state{records = R}) ->
@@ -3298,12 +3305,12 @@ format_sig_args(Type, #state{records = R}) ->
case SigArgs of
[] -> "()";
[SArg|SArgs] ->
- lists:flatten("(" ++ t_to_string(SArg, R)
+ lists:flatten("(" ++ t_to_string(SArg, R)
++ ["," ++ t_to_string(T, R) || T <- SArgs] ++ ")")
end.
format_cerl(Tree) ->
- cerl_prettypr:format(cerl:set_ann(Tree, []),
+ cerl_prettypr:format(cerl:set_ann(Tree, []),
[{hook, dialyzer_utils:pp_hook()},
{noann, true},
{paper, 100000}, %% These guys strip
@@ -3366,7 +3373,7 @@ find_terminals(Tree) ->
true ->
M = cerl:concrete(M0),
F = cerl:concrete(F0),
- case (erl_bif_types:is_known(M, F, A)
+ case (erl_bif_types:is_known(M, F, A)
andalso t_is_none(erl_bif_types:type(M, F, A))) of
true -> {true, false};
false -> {false, true}
@@ -3381,12 +3388,12 @@ find_terminals(Tree) ->
letrec -> find_terminals(cerl:letrec_body(Tree));
literal -> {false, true};
primop -> {false, false}; %% match_fail, etc. are not explicit exits.
- 'receive' ->
+ 'receive' ->
Timeout = cerl:receive_timeout(Tree),
Clauses = cerl:receive_clauses(Tree),
case (cerl:is_literal(Timeout) andalso
(cerl:concrete(Timeout) =:= infinity)) of
- true ->
+ true ->
if Clauses =:= [] -> {false, true}; %% A never ending receive.
true -> find_terminals_list(Clauses)
end;
@@ -3454,11 +3461,11 @@ find_rec_warnings_tuple(Tree, State) ->
TagVal = cerl:atom_val(Tag),
case state__lookup_record(TagVal, length(Left), State) of
error -> State;
- {ok, Prototype} ->
+ {ok, Prototype} ->
InfTupleType = t_inf(Prototype, TupleType),
case t_is_none(InfTupleType) of
true ->
- Msg = {record_matching,
+ Msg = {record_matching,
[format_patterns([Tree]), TagVal]},
state__add_warning(State, ?WARN_MATCHING, Tree, Msg);
false ->
@@ -3476,7 +3483,7 @@ find_rec_warnings_tuple(Tree, State) ->
%%----------------------------------------------------------------------------
-ifdef(DEBUG_PP).
-debug_pp(Tree, true) ->
+debug_pp(Tree, true) ->
io:put_chars(cerl_prettypr:format(Tree, [{hook, cerl_typean:pp_hook()}])),
io:nl(),
ok;
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index da0e1f9aaf..010625b7bd 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -184,7 +184,7 @@ build_options([], Options) ->
assert_filenames(Term, [FileName|Left]) when length(FileName) >= 0 ->
case filelib:is_file(FileName) orelse filelib:is_dir(FileName) of
true -> ok;
- false -> bad_option("No such file or directory", FileName)
+ false -> bad_option("No such file, directory or application", FileName)
end,
assert_filenames(Term, Left);
assert_filenames(_Term, []) ->
diff --git a/lib/dialyzer/src/dialyzer_plt.erl b/lib/dialyzer/src/dialyzer_plt.erl
index e387077a46..268ec4a5f0 100644
--- a/lib/dialyzer/src/dialyzer_plt.erl
+++ b/lib/dialyzer/src/dialyzer_plt.erl
@@ -21,7 +21,7 @@
%%%-------------------------------------------------------------------
%%% File : dialyzer_plt.erl
%%% Author : Tobias Lindahl <[email protected]>
-%%% Description : Interface to display information in the persistent
+%%% Description : Interface to display information in the persistent
%%% lookup tables.
%%%
%%% Created : 23 Jul 2004 by Tobias Lindahl <[email protected]>
@@ -39,10 +39,12 @@
from_file/1,
get_default_plt/0,
get_types/1,
+ get_exported_types/1,
%% insert/3,
insert_list/2,
insert_contract_list/2,
insert_types/2,
+ insert_exported_types/2,
lookup/2,
lookup_contract/2,
lookup_module/2,
@@ -57,6 +59,8 @@
%% Debug utilities
-export([pp_non_returning/0, pp_mod/1]).
+-export_type([plt/0, plt_info/0]).
+
%%----------------------------------------------------------------------
-type mod_deps() :: dict().
@@ -70,9 +74,10 @@
%%----------------------------------------------------------------------
--record(plt, {info = table_new() :: dict(),
- types = table_new() :: dict(),
- contracts = table_new() :: dict()}).
+-record(plt, {info = table_new() :: dict(),
+ types = table_new() :: dict(),
+ contracts = table_new() :: dict(),
+ exported_types = sets:new() :: set()}).
-opaque plt() :: #plt{}.
-include("dialyzer.hrl").
@@ -80,13 +85,14 @@
-type file_md5() :: {file:filename(), binary()}.
-type plt_info() :: {[file_md5()], dict()}.
--record(file_plt, {version = "" :: string(),
- file_md5_list = [] :: [file_md5()],
- info = dict:new() :: dict(),
- contracts = dict:new() :: dict(),
- types = dict:new() :: dict(),
- mod_deps :: mod_deps(),
- implementation_md5 = [] :: [file_md5()]}).
+-record(file_plt, {version = "" :: string(),
+ file_md5_list = [] :: [file_md5()],
+ info = dict:new() :: dict(),
+ contracts = dict:new() :: dict(),
+ types = dict:new() :: dict(),
+ exported_types = sets:new() :: set(),
+ mod_deps :: mod_deps(),
+ implementation_md5 = [] :: [file_md5()]}).
%%----------------------------------------------------------------------
@@ -95,19 +101,23 @@
new() ->
#plt{}.
--spec delete_module(plt(), module()) -> plt().
+-spec delete_module(plt(), atom()) -> plt().
-delete_module(#plt{info = Info, types = Types, contracts = Contracts}, Mod) ->
+delete_module(#plt{info = Info, types = Types, contracts = Contracts,
+ exported_types = ExpTypes}, Mod) ->
#plt{info = table_delete_module(Info, Mod),
types = table_delete_module2(Types, Mod),
- contracts = table_delete_module(Contracts, Mod)}.
+ contracts = table_delete_module(Contracts, Mod),
+ exported_types = table_delete_module1(ExpTypes, Mod)}.
-spec delete_list(plt(), [mfa() | integer()]) -> plt().
-delete_list(#plt{info = Info, types = Types, contracts = Contracts}, List) ->
+delete_list(#plt{info = Info, types = Types, contracts = Contracts,
+ exported_types = ExpTypes}, List) ->
#plt{info = table_delete_list(Info, List),
types = Types,
- contracts = table_delete_list(Contracts, List)}.
+ contracts = table_delete_list(Contracts, List),
+ exported_types = ExpTypes}.
-spec insert_contract_list(plt(), dialyzer_contracts:plt_contracts()) -> plt().
@@ -126,7 +136,7 @@ delete_contract_list(#plt{contracts = Contracts} = PLT, List) ->
PLT#plt{contracts = table_delete_list(Contracts, List)}.
%% -spec insert(plt(), mfa() | integer(), {_, _}) -> plt().
-%%
+%%
%% insert(#plt{info = Info} = PLT, Id, Types) ->
%% PLT#plt{info = table_insert(Info, Id, Types)}.
@@ -150,19 +160,29 @@ lookup(#plt{info = Info}, Label) when is_integer(Label) ->
insert_types(PLT, Rec) ->
PLT#plt{types = Rec}.
+-spec insert_exported_types(plt(), set()) -> plt().
+
+insert_exported_types(PLT, Set) ->
+ PLT#plt{exported_types = Set}.
+
-spec get_types(plt()) -> dict().
get_types(#plt{types = Types}) ->
Types.
+-spec get_exported_types(plt()) -> set().
+
+get_exported_types(#plt{exported_types = ExpTypes}) ->
+ ExpTypes.
+
-type mfa_types() :: {mfa(), erl_types:erl_type(), [erl_types:erl_type()]}.
--spec lookup_module(plt(), module()) -> 'none' | {'value', [mfa_types()]}.
+-spec lookup_module(plt(), atom()) -> 'none' | {'value', [mfa_types()]}.
lookup_module(#plt{info = Info}, M) when is_atom(M) ->
table_lookup_module(Info, M).
--spec contains_module(plt(), module()) -> boolean().
+-spec contains_module(plt(), atom()) -> boolean().
contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) ->
table_contains_module(Info, M) orelse table_contains_module(Cs, M).
@@ -170,7 +190,7 @@ contains_module(#plt{info = Info, contracts = Cs}, M) when is_atom(M) ->
-spec contains_mfa(plt(), mfa()) -> boolean().
contains_mfa(#plt{info = Info, contracts = Contracts}, MFA) ->
- (table_lookup(Info, MFA) =/= none)
+ (table_lookup(Info, MFA) =/= none)
orelse (table_lookup(Contracts, MFA) =/= none).
-spec get_default_plt() -> file:filename().
@@ -201,13 +221,14 @@ from_file(FileName, ReturnInfo) ->
case get_record_from_file(FileName) of
{ok, Rec} ->
case check_version(Rec) of
- error ->
+ error ->
Msg = io_lib:format("Old PLT file ~s\n", [FileName]),
error(Msg);
- ok ->
+ ok ->
Plt = #plt{info = Rec#file_plt.info,
types = Rec#file_plt.types,
- contracts = Rec#file_plt.contracts},
+ contracts = Rec#file_plt.contracts,
+ exported_types = Rec#file_plt.exported_types},
case ReturnInfo of
false -> Plt;
true ->
@@ -217,12 +238,12 @@ from_file(FileName, ReturnInfo) ->
end
end;
{error, Reason} ->
- error(io_lib:format("Could not read PLT file ~s: ~p\n",
+ error(io_lib:format("Could not read PLT file ~s: ~p\n",
[FileName, Reason]))
end.
-type inc_file_err_rsn() :: 'no_such_file' | 'read_error'.
--spec included_files(file:filename()) -> {'ok', [file:filename()]}
+-spec included_files(file:filename()) -> {'ok', [file:filename()]}
| {'error', inc_file_err_rsn()}.
included_files(FileName) ->
@@ -247,12 +268,12 @@ get_record_from_file(FileName) ->
try binary_to_term(Bin) of
#file_plt{} = FilePLT -> {ok, FilePLT};
_ -> {error, not_valid}
- catch
+ catch
_:_ -> {error, not_valid}
end;
{error, enoent} ->
{error, no_such_file};
- {error, _} ->
+ {error, _} ->
{error, read_error}
end.
@@ -261,19 +282,22 @@ get_record_from_file(FileName) ->
merge_plts(List) ->
InfoList = [Info || #plt{info = Info} <- List],
TypesList = [Types || #plt{types = Types} <- List],
+ ExpTypesList = [ExpTypes || #plt{exported_types = ExpTypes} <- List],
ContractsList = [Contracts || #plt{contracts = Contracts} <- List],
#plt{info = table_merge(InfoList),
types = table_merge(TypesList),
+ exported_types = sets_merge(ExpTypesList),
contracts = table_merge(ContractsList)}.
-spec to_file(file:filename(), plt(), mod_deps(), {[file_md5()], mod_deps()}) -> 'ok'.
to_file(FileName,
- #plt{info = Info, types = Types, contracts = Contracts},
+ #plt{info = Info, types = Types, contracts = Contracts,
+ exported_types = ExpTypes},
ModDeps, {MD5, OldModDeps}) ->
- NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) ->
+ NewModDeps = dict:merge(fun(_Key, OldVal, NewVal) ->
ordsets:union(OldVal, NewVal)
- end,
+ end,
OldModDeps, ModDeps),
ImplMd5 = compute_implementation_md5(),
Record = #file_plt{version = ?VSN,
@@ -281,13 +305,14 @@ to_file(FileName,
info = Info,
contracts = Contracts,
types = Types,
+ exported_types = ExpTypes,
mod_deps = NewModDeps,
implementation_md5 = ImplMd5},
Bin = term_to_binary(Record, [compressed]),
case file:write_file(FileName, Bin) of
ok -> ok;
{error, Reason} ->
- Msg = io_lib:format("Could not write PLT file ~s: ~w\n",
+ Msg = io_lib:format("Could not write PLT file ~s: ~w\n",
[FileName, Reason]),
throw({dialyzer_error, Msg})
end.
@@ -295,8 +320,8 @@ to_file(FileName,
-type md5_diff() :: [{'differ', atom()} | {'removed', atom()}].
-type check_error() :: 'not_valid' | 'no_such_file' | 'read_error'
| {'no_file_to_remove', file:filename()}.
-
--spec check_plt(file:filename(), [file:filename()], [file:filename()]) ->
+
+-spec check_plt(file:filename(), [file:filename()], [file:filename()]) ->
'ok'
| {'error', check_error()}
| {'differ', [file_md5()], md5_diff(), mod_deps()}
@@ -306,7 +331,7 @@ check_plt(FileName, RemoveFiles, AddFiles) ->
case get_record_from_file(FileName) of
{ok, #file_plt{file_md5_list = Md5, mod_deps = ModDeps} = Rec} ->
case check_version(Rec) of
- ok ->
+ ok ->
case compute_new_md5(Md5, RemoveFiles, AddFiles) of
ok -> ok;
{differ, NewMd5, DiffMd5} -> {differ, NewMd5, DiffMd5, ModDeps};
@@ -363,7 +388,7 @@ compute_md5_from_files(Files) ->
compute_md5_from_file(File) ->
case filelib:is_regular(File) of
- false ->
+ false ->
Msg = io_lib:format("Not a regular file: ~s\n", [File]),
throw({dialyzer_error, Msg});
true ->
@@ -394,7 +419,7 @@ init_md5_list_1([{File, _Md5}|Md5Left], [{remove, File}|DiffLeft], Acc) ->
init_md5_list_1(Md5Left, DiffLeft, Acc);
init_md5_list_1([{File, _Md5} = Entry|Md5Left], [{add, File}|DiffLeft], Acc) ->
init_md5_list_1(Md5Left, DiffLeft, [Entry|Acc]);
-init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List,
+init_md5_list_1([{File1, _Md5} = Entry|Md5Left] = Md5List,
[{Tag, File2}|DiffLeft] = DiffList, Acc) ->
case File1 < File2 of
true -> init_md5_list_1(Md5Left, DiffList, [Entry|Acc]);
@@ -425,7 +450,7 @@ get_specs(#plt{info = Info}) ->
beam_file_to_module(Filename) ->
list_to_atom(filename:basename(Filename, ".beam")).
--spec get_specs(plt(), module(), atom(), arity_patt()) -> 'none' | string().
+-spec get_specs(plt(), atom(), atom(), arity_patt()) -> 'none' | string().
get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) ->
MFA = {M, F, A},
@@ -435,7 +460,7 @@ get_specs(#plt{info = Info}, M, F, A) when is_atom(M), is_atom(F) ->
end.
create_specs([{{M, F, _A}, {Ret, Args}}|Left], M) ->
- [io_lib:format("-spec ~w(~s) -> ~s\n",
+ [io_lib:format("-spec ~w(~s) -> ~s\n",
[F, expand_args(Args), erl_types:t_to_string(Ret)])
| create_specs(Left, M)];
create_specs(List = [{{M, _F, _A}, {_Ret, _Args}}| _], _M) ->
@@ -475,6 +500,9 @@ table_delete_module(Plt, Mod) ->
(_, _) -> true
end, Plt).
+table_delete_module1(Plt, Mod) ->
+ sets:filter(fun({M, _F, _A}) -> M =/= Mod end, Plt).
+
table_delete_module2(Plt, Mod) ->
dict:filter(fun(M, _Val) -> M =/= Mod end, Plt).
@@ -488,7 +516,7 @@ table_insert_list(Plt, [{Key, Val}|Left]) ->
table_insert_list(Plt, []) ->
Plt.
-table_insert(Plt, Key, {_Ret, _Arg} = Obj) ->
+table_insert(Plt, Key, {_Ret, _Arg} = Obj) ->
dict:store(Key, Obj, Plt);
table_insert(Plt, Key, #contract{} = C) ->
dict:store(Key, C, Plt).
@@ -526,6 +554,15 @@ table_merge([Plt|Plts], Acc) ->
NewAcc = dict:merge(fun(_Key, Val, Val) -> Val end, Plt, Acc),
table_merge(Plts, NewAcc).
+sets_merge([H|T]) ->
+ sets_merge(T, H).
+
+sets_merge([], Acc) ->
+ Acc;
+sets_merge([Plt|Plts], Acc) ->
+ NewAcc = sets:union(Plt, Acc),
+ sets_merge(Plts, NewAcc).
+
%%---------------------------------------------------------------------------
%% Debug utilities.
@@ -555,7 +592,7 @@ pp_non_returning() ->
[M, F, dialyzer_utils:format_sig(Type)])
end, lists:sort(None)).
--spec pp_mod(module()) -> 'ok'.
+-spec pp_mod(atom()) -> 'ok'.
pp_mod(Mod) when is_atom(Mod) ->
PltFile = get_default_plt(),
diff --git a/lib/dialyzer/src/dialyzer_races.erl b/lib/dialyzer/src/dialyzer_races.erl
index 4972967960..ec8d613b96 100644
--- a/lib/dialyzer/src/dialyzer_races.erl
+++ b/lib/dialyzer/src/dialyzer_races.erl
@@ -21,7 +21,7 @@
%%%----------------------------------------------------------------------
%%% File : dialyzer_races.erl
%%% Author : Maria Christakis <[email protected]>
-%%% Description : Utility functions for race condition detection
+%%% Description : Utility functions for race condition detection
%%%
%%% Created : 21 Nov 2008 by Maria Christakis <[email protected]>
%%%----------------------------------------------------------------------
@@ -39,6 +39,8 @@
let_tag_new/2, new/0, put_curr_fun/3, put_fun_args/2,
put_race_analysis/2, put_race_list/3]).
+-export_type([races/0, mfa_or_funlbl/0, core_vars/0]).
+
-include("dialyzer.hrl").
%%% ===========================================================================
@@ -80,7 +82,7 @@
-type call() :: 'whereis' | 'register' | 'unregister' | 'ets_new'
| 'ets_lookup' | 'ets_insert' | 'mnesia_dirty_read1'
| 'mnesia_dirty_read2' | 'mnesia_dirty_write1'
- | 'mnesia_dirty_write2' | 'function_call'.
+ | 'mnesia_dirty_write2' | 'function_call'.
-type race_tag() :: 'whereis_register' | 'whereis_unregister'
| 'ets_lookup_insert' | 'mnesia_dirty_read_write'.
@@ -157,7 +159,7 @@
%%% ===========================================================================
-spec store_race_call(mfa_or_funlbl(), [erl_types:erl_type()], [core_vars()],
- file_line(), dialyzer_dataflow:state()) ->
+ file_line(), dialyzer_dataflow:state()) ->
dialyzer_dataflow:state().
store_race_call(Fun, ArgTypes, Args, FileLine, State) ->
@@ -166,7 +168,7 @@ store_race_call(Fun, ArgTypes, Args, FileLine, State) ->
CurrFunLabel = Races#races.curr_fun_label,
RaceTags = Races#races.race_tags,
CleanState = dialyzer_dataflow:state__records_only(State),
- {NewRaceList, NewRaceListSize, NewRaceTags, NewTable} =
+ {NewRaceList, NewRaceListSize, NewRaceTags, NewTable} =
case CurrFun of
{_Module, module_info, A} when A =:= 0 orelse A =:= 1 ->
{[], 0, RaceTags, no_t};
@@ -422,7 +424,7 @@ fixup_race_forward_pullout(CurrFun, CurrFunLabel, Calls, Code, RaceList,
Races = dialyzer_dataflow:state__get_races(State),
{RetCurrFun, RetCurrFunLabel, RetCalls, RetCode,
RetRaceList, RetRaceVarMap, RetFunDefVars, RetFunCallVars,
- RetFunArgTypes, RetNestingLevel} =
+ RetFunArgTypes, RetNestingLevel} =
fixup_race_forward_helper(NewCurrFun,
NewCurrFunLabel, Fun, Int, NewCalls, NewCalls,
[#curr_fun{status = out, mfa = NewCurrFun,
@@ -576,7 +578,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList,
RaceTag ->
PublicTables = dialyzer_callgraph:get_public_tables(Callgraph),
NamedTables = dialyzer_callgraph:get_named_tables(Callgraph),
- WarnVarArgs1 =
+ WarnVarArgs1 =
var_type_analysis(FunDefVars, FunArgTypes, WarnVarArgs,
RaceWarnTag, RaceVarMap,
dialyzer_dataflow:state__records_only(State)),
@@ -598,7 +600,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList,
[#warn_call{call_name = ets_insert, args = WarnVarArgs,
var_map = RaceVarMap}],
[Tab, Names, _, _] = WarnVarArgs,
- case IsPublic orelse
+ case IsPublic orelse
compare_var_list(Tab, PublicTables, RaceVarMap)
orelse
length(Names -- NamedTables) < length(Names) of
@@ -636,7 +638,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList,
#curr_fun{mfa = CurrFun2, label = CurrFunLabel2,
var_map = RaceVarMap2, def_vars = FunDefVars2,
call_vars = FunCallVars2, arg_types = FunArgTypes2},
- Code2, NestingLevel2} =
+ Code2, NestingLevel2} =
remove_clause(NewRL,
#curr_fun{mfa = CurrFun, label = CurrFunLabel,
var_map = RaceVarMap1,
@@ -648,7 +650,7 @@ fixup_race_forward(CurrFun, CurrFunLabel, Calls, Code, RaceList,
RaceVarMap2, FunDefVars2, FunCallVars2, FunArgTypes2,
NestingLevel2, false};
false ->
- {CurrFun, CurrFunLabel, Tail, NewRL, RaceVarMap1,
+ {CurrFun, CurrFunLabel, Tail, NewRL, RaceVarMap1,
FunDefVars, FunCallVars, FunArgTypes, NewNL, false}
end;
#end_clause{arg = Arg, pats = Pats, guard = Guard} ->
@@ -893,7 +895,7 @@ do_clause(RaceList, WarnVarArgs, RaceWarnTag, RaceVarMap, CurrLevel,
PublicTables, NamedTables) ->
{DepList, IsPublic, Continue} =
get_deplist_paths(fixup_case_path(RaceList, 0), WarnVarArgs,
- RaceWarnTag, RaceVarMap, CurrLevel,
+ RaceWarnTag, RaceVarMap, CurrLevel,
PublicTables, NamedTables),
{fixup_case_rest_paths(RaceList, 0), DepList, IsPublic, Continue}.
@@ -963,7 +965,7 @@ fixup_race_forward_helper(CurrFun, CurrFunLabel, Fun, FunLabel,
#curr_fun{mfa = NewCurrFun, label = NewCurrFunLabel,
var_map = NewRaceVarMap, def_vars = NewFunDefVars,
call_vars = NewFunCallVars, arg_types = NewFunArgTypes},
- NewCode, NewNestingLevel} =
+ NewCode, NewNestingLevel} =
remove_clause(RaceList,
#curr_fun{mfa = CurrFun, label = CurrFunLabel, var_map = RaceVarMap,
def_vars = FunDefVars, call_vars = FunCallVars,
@@ -995,7 +997,7 @@ fixup_race_forward_helper(CurrFun, CurrFunLabel, Fun, FunLabel,
arg_types = NewFunTypes}],
[#curr_fun{status = in, mfa = Fun,
label = FunLabel, var_map = NewRaceVarMap,
- def_vars = Args, call_vars = NewFunArgs,
+ def_vars = Args, call_vars = NewFunArgs,
arg_types = NewFunTypes}|
lists:reverse(StateRaceList)] ++
RetC, NewRaceVarMap),
@@ -1060,7 +1062,7 @@ fixup_race_backward(CurrFun, Calls, CallsToAnalyze, Parents, Height) ->
case Height =:= 0 of
true -> Parents;
false ->
- case Calls of
+ case Calls of
[] ->
case is_integer(CurrFun) orelse lists:member(CurrFun, Parents) of
true -> Parents;
@@ -1219,7 +1221,7 @@ are_bound_vars(Vars1, Vars2, RaceVarMap) ->
callgraph__renew_tables(Table, Callgraph) ->
case Table of
{named, NameLabel, Names} ->
- PTablesToAdd =
+ PTablesToAdd =
case NameLabel of
?no_label -> [];
_Other -> [NameLabel]
@@ -1438,7 +1440,7 @@ lists_key_members_lists_helper(Elem, List, N) when is_integer(Elem) ->
end;
lists_key_members_lists_helper(_Elem, _List, _N) ->
[0].
-
+
lists_key_replace(N, List, NewMember) ->
{Before, [_|After]} = lists:split(N - 1, List),
Before ++ [NewMember|After].
@@ -1488,7 +1490,7 @@ refine_race_helper(RaceCall, VarArgs, WarnVarArgs, RaceWarnTag, DependencyList,
false -> DependencyList
end.
-remove_clause(RaceList, CurrTuple, Code, NestingLevel) ->
+remove_clause(RaceList, CurrTuple, Code, NestingLevel) ->
NewRaceList = fixup_case_rest_paths(RaceList, 0),
{NewCurrTuple, NewCode} =
cleanup_clause_code(CurrTuple, Code, 0, NestingLevel),
@@ -1621,7 +1623,7 @@ compare_ets_insert(OldWarnVarArgs, NewWarnVarArgs, RaceVarMap) ->
end
end,
case Bool of
- true ->
+ true ->
case any_args(Old4) of
true ->
case compare_list_vars(Old3, ets_list_args(New3), [], RaceVarMap) of
@@ -1690,7 +1692,6 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) ->
false ->
compare_var_list(VA1, WVA1, RaceVarMap) orelse
compare_argtypes(VA2, WVA2)
-
end
end;
?WARN_WHEREIS_UNREGISTER ->
@@ -1704,7 +1705,6 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) ->
false ->
compare_var_list(VA1, WVA1, RaceVarMap) orelse
compare_argtypes(VA2, WVA2)
-
end
end;
?WARN_ETS_LOOKUP_INSERT ->
@@ -1716,12 +1716,12 @@ compare_types(VarArgs, WarnVarArgs, RaceWarnTag, RaceVarMap) ->
false ->
case any_args(WVA2) of
true -> compare_var_list(VA1, WVA1, RaceVarMap);
- false ->
+ false ->
compare_var_list(VA1, WVA1, RaceVarMap) orelse
compare_argtypes(VA2, WVA2)
end
end,
- Bool andalso
+ Bool andalso
(case any_args(VA4) of
true ->
compare_var_list(VA3, WVA3, RaceVarMap);
@@ -2158,7 +2158,7 @@ race_var_map_guard_helper1(Arg, Pats, RaceVarMap, Op) ->
_Else -> {RaceVarMap, false}
end;
false -> {RaceVarMap, false}
- end;
+ end;
_Other -> {RaceVarMap, false}
end;
_Other -> {RaceVarMap, false}
@@ -2241,7 +2241,7 @@ var_analysis(FunDefArgs, FunCallArgs, WarnVarArgs, RaceWarnTag) ->
[WVA1, WVA2|T] = WarnVarArgs,
ArgNos = lists_key_members_lists(WVA1, FunDefArgs),
[[lists_get(N, FunCallArgs) || N <- ArgNos], WVA2|T]
- end.
+ end.
var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag,
RaceVarMap, CleanState) ->
@@ -2286,7 +2286,7 @@ var_type_analysis(FunDefArgs, FunCallTypes, WarnVarArgs, RaceWarnTag,
ets_tuple_argtypes1(lists:nth(N2 + 1, FunVarArgs), [], [], 0),
[]),
FirstVarArg ++ [Vars2, NewWVA4]
-
+
end;
?WARN_MNESIA_DIRTY_READ_WRITE ->
[WVA1, WVA2|T] = WarnVarArgs,
@@ -2330,7 +2330,7 @@ get_race_warn(Fun, Args, ArgTypes, DepList, State) ->
-spec get_race_warnings(races(), dialyzer_dataflow:state()) ->
{races(), dialyzer_dataflow:state()}.
-
+
get_race_warnings(#races{race_warnings = RaceWarnings}, State) ->
get_race_warnings_helper(RaceWarnings, State).
@@ -2430,12 +2430,12 @@ end_clause_new(Arg, Pats, Guard) ->
#end_clause{arg = Arg, pats = Pats, guard = Guard}.
-spec get_curr_fun(races()) -> mfa_or_funlbl().
-
+
get_curr_fun(#races{curr_fun = CurrFun}) ->
CurrFun.
-spec get_curr_fun_args(races()) -> core_args().
-
+
get_curr_fun_args(#races{curr_fun_args = CurrFunArgs}) ->
CurrFunArgs.
@@ -2445,17 +2445,17 @@ get_new_table(#races{new_table = Table}) ->
Table.
-spec get_race_analysis(races()) -> boolean().
-
+
get_race_analysis(#races{race_analysis = RaceAnalysis}) ->
RaceAnalysis.
-spec get_race_list(races()) -> code().
-
+
get_race_list(#races{race_list = RaceList}) ->
RaceList.
-spec get_race_list_size(races()) -> non_neg_integer().
-
+
get_race_list_size(#races{race_list_size = RaceListSize}) ->
RaceListSize.
@@ -2483,10 +2483,10 @@ put_fun_args(Args, #races{curr_fun_args = CurrFunArgs} = Races) ->
empty -> Races#races{curr_fun_args = Args};
_Other -> Races
end.
-
+
-spec put_race_analysis(boolean(), races()) ->
races().
-
+
put_race_analysis(Analysis, Races) ->
Races#races{race_analysis = Analysis}.
diff --git a/lib/dialyzer/src/dialyzer_succ_typings.erl b/lib/dialyzer/src/dialyzer_succ_typings.erl
index 1ff4783852..8bfc66fc39 100644
--- a/lib/dialyzer/src/dialyzer_succ_typings.erl
+++ b/lib/dialyzer/src/dialyzer_succ_typings.erl
@@ -435,7 +435,7 @@ format_scc(SCC) ->
%%
%% ============================================================================
--spec doit(module() | string()) -> 'ok'.
+-spec doit(atom() | file:filename()) -> 'ok'.
doit(Module) ->
{ok, AbstrCode} = dialyzer_utils:get_abstract_code_from_src(Module),
diff --git a/lib/dialyzer/src/dialyzer_typesig.erl b/lib/dialyzer/src/dialyzer_typesig.erl
index 35b283a00a..3effb1c2e6 100644
--- a/lib/dialyzer/src/dialyzer_typesig.erl
+++ b/lib/dialyzer/src/dialyzer_typesig.erl
@@ -21,7 +21,7 @@
%%%-------------------------------------------------------------------
%%% File : dialyzer_typesig.erl
%%% Author : Tobias Lindahl <[email protected]>
-%%% Description :
+%%% Description :
%%%
%%% Created : 25 Apr 2005 by Tobias Lindahl <[email protected]>
%%%-------------------------------------------------------------------
@@ -31,12 +31,12 @@
-export([analyze_scc/5]).
-export([get_safe_underapprox/2]).
--import(erl_types,
+-import(erl_types,
[t_any/0, t_atom/0, t_atom_vals/1,
t_binary/0, t_bitstr/0, t_bitstr/2, t_bitstr_concat/1, t_boolean/0,
t_collect_vars/1, t_cons/2, t_cons_hd/1, t_cons_tl/1,
t_float/0, t_from_range/2, t_from_term/1,
- t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1,
+ t_fun/0, t_fun/2, t_fun_args/1, t_fun_range/1,
t_has_var/1,
t_inf/2, t_inf/3, t_integer/0,
t_is_any/1, t_is_atom/1, t_is_atom/2, t_is_cons/1, t_is_equal/2,
@@ -44,7 +44,7 @@
t_is_integer/1, t_non_neg_integer/0,
t_is_list/1, t_is_nil/1, t_is_none/1, t_is_number/1,
- t_is_subtype/2, t_limit/2, t_list/0, t_list/1,
+ t_is_subtype/2, t_limit/2, t_list/0, t_list/1,
t_list_elements/1, t_nonempty_list/1, t_maybe_improper_list/0,
t_module/0, t_number/0, t_number_vals/1,
t_opaque_match_record/2, t_opaque_matching_structure/2,
@@ -101,7 +101,7 @@
name_map = dict:new() :: dict(),
next_label :: label(),
non_self_recs = [] :: [label()],
- plt :: dialyzer_plt:plt(),
+ plt :: dialyzer_plt:plt(),
prop_types = dict:new() :: dict(),
records = dict:new() :: dict(),
opaques = [] :: [erl_types:erl_type()],
@@ -140,8 +140,8 @@
%% where Def = {Var, Fun} as in the Core Erlang module definitions.
%% Records = dict(RecName, {Arity, [{FieldName, FieldType}]})
%% NextLabel - An integer that is higher than any label in the code.
-%% CallGraph - A callgraph as produced by dialyzer_callgraph.erl
-%% Note: The callgraph must have been built with all the
+%% CallGraph - A callgraph as produced by dialyzer_callgraph.erl
+%% Note: The callgraph must have been built with all the
%% code that the SCC is a part of.
%% PLT - A dialyzer PLT. This PLT should contain available information
%% about functions that can be called by this SCC.
@@ -150,7 +150,7 @@
%%-----------------------------------------------------------------------------
-spec analyze_scc(typesig_scc(), label(),
- dialyzer_callgraph:callgraph(),
+ dialyzer_callgraph:callgraph(),
dialyzer_plt:plt(), dict()) -> dict().
analyze_scc(SCC, NextLabel, CallGraph, Plt, PropTypes) ->
@@ -202,7 +202,7 @@ traverse(Tree, DefinedVars, State) ->
{State1, OpType} = traverse(Op, DefinedVars, State0),
{State2, FunType} = state__get_fun_prototype(OpType, Arity, State1),
State3 = state__store_conj(FunType, eq, OpType, State2),
- State4 = state__store_conj(mk_var(Tree), sub, t_fun_range(FunType),
+ State4 = state__store_conj(mk_var(Tree), sub, t_fun_range(FunType),
State3),
State5 = state__store_conj_lists(ArgTypes, sub, t_fun_args(FunType),
State4),
@@ -216,7 +216,7 @@ traverse(Tree, DefinedVars, State) ->
end
end;
binary ->
- {State1, SegTypes} = traverse_list(cerl:binary_segments(Tree),
+ {State1, SegTypes} = traverse_list(cerl:binary_segments(Tree),
DefinedVars, State),
Type = mk_fun_var(fun(Map) ->
TmpSegTypes = lookup_type_list(SegTypes, Map),
@@ -227,7 +227,7 @@ traverse(Tree, DefinedVars, State) ->
Size = cerl:bitstr_size(Tree),
UnitVal = cerl:int_val(cerl:bitstr_unit(Tree)),
Val = cerl:bitstr_val(Tree),
- {State1, [SizeType, ValType]} =
+ {State1, [SizeType, ValType]} =
traverse_list([Size, Val], DefinedVars, State),
{State2, TypeConstr} =
case cerl:bitstr_bitsize(Tree) of
@@ -250,7 +250,7 @@ traverse(Tree, DefinedVars, State) ->
case state__is_in_match(State1) of
true ->
Flags = cerl:concrete(cerl:bitstr_flags(Tree)),
- mk_fun_var(bitstr_val_constr(SizeType, UnitVal, Flags),
+ mk_fun_var(bitstr_val_constr(SizeType, UnitVal, Flags),
[SizeType]);
false -> t_integer()
end;
@@ -282,7 +282,7 @@ traverse(Tree, DefinedVars, State) ->
false ->
ConsVar = mk_var(Tree),
ConsType = mk_fun_var(fun(Map) ->
- t_cons(lookup_type(HdVar, Map),
+ t_cons(lookup_type(HdVar, Map),
lookup_type(TlVar, Map))
end, [HdVar, TlVar]),
HdType = mk_fun_var(fun(Map) ->
@@ -299,8 +299,8 @@ traverse(Tree, DefinedVars, State) ->
true -> t_cons_tl(Cons)
end
end, [ConsVar]),
- State2 = state__store_conj_lists([HdVar, TlVar, ConsVar], sub,
- [HdType, TlType, ConsType],
+ State2 = state__store_conj_lists([HdVar, TlVar, ConsVar], sub,
+ [HdType, TlType, ConsType],
State1),
{State2, ConsVar}
end;
@@ -314,14 +314,14 @@ traverse(Tree, DefinedVars, State) ->
error -> t_fun(length(Vars), t_none());
{ok, Dom} -> t_fun(Dom, t_none())
end,
- State2 =
+ State2 =
try
State1 = case state__add_prop_constrs(Tree, State0) of
not_called -> State0;
PropState -> PropState
end,
{BodyState, BodyVar} = traverse(Body, DefinedVars1, State1),
- state__store_conj(mk_var(Tree), eq,
+ state__store_conj(mk_var(Tree), eq,
t_fun(mk_var_list(Vars), BodyVar), BodyState)
catch
throw:error ->
@@ -340,7 +340,7 @@ traverse(Tree, DefinedVars, State) ->
Arg = cerl:let_arg(Tree),
Body = cerl:let_body(Tree),
{State1, ArgVars} = traverse(Arg, DefinedVars, State),
- State2 = state__store_conj(t_product(mk_var_list(Vars)), eq,
+ State2 = state__store_conj(t_product(mk_var_list(Vars)), eq,
ArgVars, State1),
DefinedVars1 = add_def_list(Vars, DefinedVars),
traverse(Body, DefinedVars1, State2);
@@ -353,12 +353,12 @@ traverse(Tree, DefinedVars, State) ->
DefinedVars1 = add_def_list(Vars, DefinedVars),
{State2, _} = traverse_list(Funs, DefinedVars1, State1),
traverse(Body, DefinedVars1, State2);
- literal ->
+ literal ->
%% This is needed for finding records
case cerl:unfold_literal(Tree) of
- Tree ->
+ Tree ->
Type = t_from_term(cerl:concrete(Tree)),
- NewType =
+ NewType =
case erl_types:t_opaque_match_atom(Type, State#state.opaques) of
[Opaque] -> Opaque;
_ -> Type
@@ -370,7 +370,7 @@ traverse(Tree, DefinedVars, State) ->
Defs = cerl:module_defs(Tree),
Funs = [Fun || {_Var, Fun} <- Defs],
Vars = [Var || {Var, _Fun} <- Defs],
- DefinedVars1 = add_def_list(Vars, DefinedVars),
+ DefinedVars1 = add_def_list(Vars, DefinedVars),
State1 = state__store_funs(Vars, Funs, State),
FoldFun = fun(Fun, AccState) ->
{S, _} = traverse(Fun, DefinedVars1,
@@ -388,7 +388,7 @@ traverse(Tree, DefinedVars, State) ->
'receive' ->
Clauses = filter_match_fail(cerl:receive_clauses(Tree)),
Timeout = cerl:receive_timeout(Tree),
- case (cerl:is_c_atom(Timeout) andalso
+ case (cerl:is_c_atom(Timeout) andalso
(cerl:atom_val(Timeout) =:= infinity)) of
true ->
handle_clauses(Clauses, mk_var(Tree), [], DefinedVars, State);
@@ -421,7 +421,7 @@ traverse(Tree, DefinedVars, State) ->
case t_has_var(Var) of
true ->
{AccState1, NewVar} = state__mk_var(AccState),
- {NewVar,
+ {NewVar,
state__store_conj(Var, eq, NewVar, AccState1)};
false ->
{Var, AccState}
@@ -431,7 +431,7 @@ traverse(Tree, DefinedVars, State) ->
{TmpState, t_tuple(NewEvars)}
end,
case Elements of
- [Tag|Fields] ->
+ [Tag|Fields] ->
case cerl:is_c_atom(Tag) of
true ->
%% Check if an opaque term is constructed.
@@ -534,7 +534,7 @@ handle_try(Tree, DefinedVars, State) ->
Handler = cerl:try_handler(Tree),
State1 = state__new_constraint_context(State),
{ArgBodyState, BodyVar} =
- try
+ try
{State2, ArgVar} = traverse(Arg, DefinedVars, State1),
DefinedVars1 = add_def_list(Vars, DefinedVars),
{State3, BodyVar1} = traverse(Body, DefinedVars1, State2),
@@ -542,17 +542,17 @@ handle_try(Tree, DefinedVars, State) ->
State3),
{State4, BodyVar1}
catch
- throw:error ->
+ throw:error ->
{State1, t_none()}
end,
State6 = state__new_constraint_context(ArgBodyState),
{HandlerState, HandlerVar} =
try
- DefinedVars2 = add_def_list([X || X <- EVars, cerl:is_c_var(X)],
+ DefinedVars2 = add_def_list([X || X <- EVars, cerl:is_c_var(X)],
DefinedVars),
traverse(Handler, DefinedVars2, State6)
catch
- throw:error ->
+ throw:error ->
{State6, t_none()}
end,
ArgBodyCs = state__cs(ArgBodyState),
@@ -561,7 +561,7 @@ handle_try(Tree, DefinedVars, State) ->
OldCs = state__cs(State),
case state__is_in_guard(State) of
true ->
- Conj1 = mk_conj_constraint_list([ArgBodyCs,
+ Conj1 = mk_conj_constraint_list([ArgBodyCs,
mk_constraint(BodyVar, eq, TreeVar)]),
Disj = mk_disj_constraint_list([Conj1,
mk_constraint(HandlerVar, eq, TreeVar)]),
@@ -573,10 +573,10 @@ handle_try(Tree, DefinedVars, State) ->
{NewCs, ReturnVar} =
case {t_is_none(BodyVar), t_is_none(HandlerVar)} of
{false, false} ->
- Conj1 =
+ Conj1 =
mk_conj_constraint_list([ArgBodyCs,
mk_constraint(TreeVar, eq, BodyVar)]),
- Conj2 =
+ Conj2 =
mk_conj_constraint_list([HandlerCs,
mk_constraint(TreeVar, eq, HandlerVar)]),
Disj = mk_disj_constraint_list([Conj1, Conj2]),
@@ -603,7 +603,7 @@ handle_try(Tree, DefinedVars, State) ->
%% Call
%%
-handle_call(Call, DefinedVars, State) ->
+handle_call(Call, DefinedVars, State) ->
Args = cerl:call_args(Call),
Mod = cerl:call_module(Call),
Fun = cerl:call_name(Call),
@@ -618,7 +618,7 @@ handle_call(Call, DefinedVars, State) ->
case state__lookup_rec_var_in_scope(MFA, State) of
error ->
case get_bif_constr(MFA, Dst, ArgVars, State1) of
- none ->
+ none ->
{get_plt_constr(MFA, Dst, ArgVars, State1), Dst};
C ->
{state__store_conj(C, State1), Dst}
@@ -647,7 +647,7 @@ get_plt_constr(MFA, Dst, ArgVars, State) ->
case PltRes of
none -> State;
{value, {PltRetType, PltArgTypes}} ->
- state__store_conj_lists([Dst|ArgVars], sub,
+ state__store_conj_lists([Dst|ArgVars], sub,
[PltRetType|PltArgTypes], State)
end;
{value, #contract{args = GenArgs} = C} ->
@@ -655,7 +655,7 @@ get_plt_constr(MFA, Dst, ArgVars, State) ->
case PltRes of
none ->
{mk_fun_var(fun(Map) ->
- ArgTypes = lookup_type_list(ArgVars, Map),
+ ArgTypes = lookup_type_list(ArgVars, Map),
dialyzer_contracts:get_contract_return(C, ArgTypes)
end, ArgVars), GenArgs};
{value, {PltRetType, PltArgTypes}} ->
@@ -692,7 +692,7 @@ filter_match_fail([Clause] = Cls) ->
filter_match_fail([H|T]) ->
[H|filter_match_fail(T)];
filter_match_fail([]) ->
- %% This can actually happen, for example in
+ %% This can actually happen, for example in
%% receive after 1 -> ok end
[].
@@ -714,16 +714,16 @@ handle_clauses(Clauses, TopVar, Arg, Action, DefinedVars, State) ->
if length(Clauses) > ?MAX_NOF_CLAUSES -> overflow;
true -> []
end,
- {State1, CList} = handle_clauses_1(Clauses, TopVar, Arg, DefinedVars,
+ {State1, CList} = handle_clauses_1(Clauses, TopVar, Arg, DefinedVars,
State, SubtrTypeList, []),
{NewCs, NewState} =
case Action of
- none ->
+ none ->
if CList =:= [] -> throw(error);
true -> {CList, State1}
end;
- _ ->
- try
+ _ ->
+ try
{State2, ActionVar} = traverse(Action, DefinedVars, State1),
TmpC = mk_constraint(TopVar, eq, ActionVar),
ActionCs = mk_conj_constraint_list([state__cs(State2),TmpC]),
@@ -740,7 +740,7 @@ handle_clauses(Clauses, TopVar, Arg, Action, DefinedVars, State) ->
FinalState = state__new_constraint_context(NewState),
{state__store_conj_list([OldCs, NewCList], FinalState), TopVar}.
-handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars,
+handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars,
State, SubtrTypes, Acc) ->
State0 = state__new_constraint_context(State),
Pats = cerl:clause_pats(Clause),
@@ -749,22 +749,22 @@ handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars,
NewSubtrTypes =
case SubtrTypes =:= overflow of
true -> overflow;
- false ->
+ false ->
ordsets:add_element(get_safe_underapprox(Pats, Guard), SubtrTypes)
end,
- try
+ try
DefinedVars1 = add_def_from_tree_list(Pats, DefinedVars),
State1 = state__set_in_match(State0, true),
{State2, PatVars} = traverse_list(Pats, DefinedVars1, State1),
State3 =
case Arg =:= [] of
true -> State2;
- false ->
+ false ->
S = state__store_conj(Arg, eq, t_product(PatVars), State2),
case SubtrTypes =:= overflow of
true -> S;
false ->
- SubtrPatVar = mk_fun_var(fun(Map) ->
+ SubtrPatVar = mk_fun_var(fun(Map) ->
TmpType = lookup_type(Arg, Map),
t_subtract_list(TmpType, SubtrTypes)
end, [Arg]),
@@ -772,15 +772,15 @@ handle_clauses_1([Clause|Tail], TopVar, Arg, DefinedVars,
end
end,
State4 = handle_guard(Guard, DefinedVars1, State3),
- {State5, BodyVar} = traverse(Body, DefinedVars1,
+ {State5, BodyVar} = traverse(Body, DefinedVars1,
state__set_in_match(State4, false)),
State6 = state__store_conj(TopVar, eq, BodyVar, State5),
Cs = state__cs(State6),
- handle_clauses_1(Tail, TopVar, Arg, DefinedVars, State6,
+ handle_clauses_1(Tail, TopVar, Arg, DefinedVars, State6,
NewSubtrTypes, [Cs|Acc])
catch
- throw:error ->
- handle_clauses_1(Tail, TopVar, Arg, DefinedVars,
+ throw:error ->
+ handle_clauses_1(Tail, TopVar, Arg, DefinedVars,
State, NewSubtrTypes, Acc)
end;
handle_clauses_1([], _TopVar, _Arg, _DefinedVars, State, _SubtrType, Acc) ->
@@ -792,7 +792,7 @@ get_safe_underapprox(Pats, Guard) ->
try
Map1 = cerl_trees:fold(fun(X, Acc) ->
case cerl:is_c_var(X) of
- true ->
+ true ->
dict:store(cerl_trees:get_label(X), t_any(),
Acc);
false -> Acc
@@ -804,8 +804,8 @@ get_safe_underapprox(Pats, Guard) ->
false ->
case cerl:is_c_var(Guard) of
false -> Map2;
- true ->
- dict:store(cerl_trees:get_label(Guard),
+ true ->
+ dict:store(cerl_trees:get_label(Guard),
t_from_term(true), Map2)
end
end,
@@ -819,8 +819,8 @@ get_underapprox_from_guard(Tree, Map) ->
True = t_from_term(true),
case cerl:type(Tree) of
call ->
- case {cerl:concrete(cerl:call_module(Tree)),
- cerl:concrete(cerl:call_name(Tree)),
+ case {cerl:concrete(cerl:call_module(Tree)),
+ cerl:concrete(cerl:call_name(Tree)),
length(cerl:call_args(Tree))} of
{erlang, is_function, 2} ->
[Fun, Arity] = cerl:call_args(Tree),
@@ -856,15 +856,15 @@ get_underapprox_from_guard(Tree, Map) ->
{erlang, '==', 2} -> throw(dont_know);
{erlang, 'and', 2} ->
[Arg1, Arg2] = cerl:call_args(Tree),
- case ((cerl:is_c_var(Arg1) orelse cerl:is_literal(Arg1))
+ case ((cerl:is_c_var(Arg1) orelse cerl:is_literal(Arg1))
andalso
(cerl:is_c_var(Arg2) orelse cerl:is_literal(Arg2))) of
true ->
{Arg1Type, _} = get_underapprox_from_guard(Arg1, Map),
{Arg2Type, _} = get_underapprox_from_guard(Arg2, Map),
- case (t_is_equal(True, Arg1Type) andalso
+ case (t_is_equal(True, Arg1Type) andalso
t_is_equal(True, Arg2Type)) of
- true -> {True, Map};
+ true -> {True, Map};
false -> throw(dont_know)
end;
false ->
@@ -876,7 +876,7 @@ get_underapprox_from_guard(Tree, Map) ->
end
end;
var ->
- Type =
+ Type =
case dict:find(cerl_trees:get_label(Tree), Map) of
error -> throw(dont_know);
{ok, T} -> T
@@ -931,7 +931,7 @@ bitstr_constr(SizeType, UnitVal) ->
MinSize = erl_types:number_min(TmpSizeType),
t_bitstr(UnitVal, MinSize * UnitVal)
end;
- false ->
+ false ->
t_bitstr(UnitVal, 0)
end
end.
@@ -975,9 +975,9 @@ get_safe_underapprox_1([Pat|Left], Acc, Map) ->
end;
binary ->
%% TODO: Can maybe do something here
- throw(dont_know);
+ throw(dont_know);
cons ->
- {[Hd, Tl], Map1} =
+ {[Hd, Tl], Map1} =
get_safe_underapprox_1([cerl:cons_hd(Pat), cerl:cons_tl(Pat)], [], Map),
case t_is_any(Tl) of
true -> get_safe_underapprox_1(Left, [t_nonempty_list(Hd)|Acc], Map1);
@@ -1020,7 +1020,7 @@ get_safe_underapprox_1([], Acc, Map) ->
%% Guards
%%
-handle_guard(Guard, DefinedVars, State) ->
+handle_guard(Guard, DefinedVars, State) ->
True = t_from_term(true),
State1 = state__set_in_guard(State, true),
State2 = state__new_constraint_context(State1),
@@ -1039,7 +1039,7 @@ handle_guard(Guard, DefinedVars, State) ->
%%
%%=============================================================================
-get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State)
+get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State)
when Op =:= '+'; Op =:= '-'; Op =:= '*' ->
ReturnType = mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
@@ -1047,14 +1047,14 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State)
end, Args),
ArgFun =
fun(A, Pos) ->
- F =
+ F =
fun(Map) ->
DstType = lookup_type(Dst, Map),
AType = lookup_type(A, Map),
case t_is_integer(DstType) of
true ->
case t_is_integer(AType) of
- true ->
+ true ->
eval_inv_arith(Op, Pos, DstType, AType);
false ->
%% This must be temporary.
@@ -1062,7 +1062,7 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State)
end;
false ->
case t_is_float(DstType) of
- true ->
+ true ->
case t_is_integer(AType) of
true -> t_float();
false -> t_number()
@@ -1079,9 +1079,9 @@ get_bif_constr({erlang, Op, 2}, Dst, Args = [Arg1, Arg2], _State)
mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType),
mk_constraint(Arg1, sub, Arg1FunVar),
mk_constraint(Arg2, sub, Arg2FunVar)]);
-get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
+get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
when Op =:= '<'; Op =:= '=<'; Op =:= '>'; Op =:= '>=' ->
- ArgFun =
+ ArgFun =
fun(LocalArg1, LocalArg2, LocalOp) ->
fun(Map) ->
DstType = lookup_type(Dst, Map),
@@ -1098,19 +1098,19 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
Max2 = erl_types:number_max(Arg2Type),
Min2 = erl_types:number_min(Arg2Type),
case LocalOp of
- '=<' ->
+ '=<' ->
if IsTrue -> t_from_range(Min1, Max2);
IsFalse -> t_from_range(range_inc(Min2), Max1)
end;
- '<' ->
+ '<' ->
if IsTrue -> t_from_range(Min1, range_dec(Max2));
IsFalse -> t_from_range(Min2, Max1)
end;
- '>=' ->
+ '>=' ->
if IsTrue -> t_from_range(Min2, Max1);
IsFalse -> t_from_range(Min1, range_dec(Max2))
end;
- '>' ->
+ '>' ->
if IsTrue -> t_from_range(range_inc(Min2), Max1);
IsFalse -> t_from_range(Min1, Max2)
end
@@ -1131,7 +1131,7 @@ get_bif_constr({erlang, Op, 2}, Dst, [Arg1, Arg2] = Args, _State)
DstArgs = [Dst, Arg1, Arg2],
Arg1Var = mk_fun_var(Arg1Fun, DstArgs),
Arg2Var = mk_fun_var(Arg2Fun, DstArgs),
- DstVar = mk_fun_var(fun(Map) ->
+ DstVar = mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
erl_bif_types:type(erlang, Op, 2, TmpArgTypes)
end, Args),
@@ -1143,9 +1143,9 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) ->
DstType = lookup_type(Dst, Map),
case t_is_cons(DstType) of
true -> t_list(t_cons_hd(DstType));
- false ->
+ false ->
case t_is_list(DstType) of
- true ->
+ true ->
case t_is_nil(DstType) of
true -> DstType;
false -> t_list(t_list_elements(DstType))
@@ -1160,7 +1160,7 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) ->
true -> t_sup(t_cons_tl(DstType), DstType);
false ->
case t_is_list(DstType) of
- true ->
+ true ->
case t_is_nil(DstType) of
true -> DstType;
false -> t_list(t_list_elements(DstType))
@@ -1170,10 +1170,10 @@ get_bif_constr({erlang, '++', 2}, Dst, [Hd, Tl] = Args, _State) ->
end
end,
DstL = [Dst],
- HdVar = mk_fun_var(HdFun, DstL),
+ HdVar = mk_fun_var(HdFun, DstL),
TlVar = mk_fun_var(TlFun, DstL),
ArgTypes = erl_bif_types:arg_types(erlang, '++', 2),
- ReturnType = mk_fun_var(fun(Map) ->
+ ReturnType = mk_fun_var(fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
erl_bif_types:type(erlang, '++', 2, TmpArgTypes)
end, Args),
@@ -1198,7 +1198,7 @@ get_bif_constr({erlang, is_function, 2}, Dst, [Fun, Arity], _State) ->
ArgFun = fun(Map) ->
DstType = lookup_type(Dst, Map),
case t_is_atom(true, DstType) of
- true ->
+ true ->
ArityType = lookup_type(Arity, Map),
case t_number_vals(ArityType) of
unknown -> t_fun();
@@ -1231,7 +1231,7 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
end
end,
ArgV = mk_fun_var(ArgFun, [Dst]),
- DstFun = fun(Map) ->
+ DstFun = fun(Map) ->
TmpArgTypes = lookup_type_list(Args, Map),
erl_bif_types:type(erlang, is_record, 2, TmpArgTypes)
end,
@@ -1241,7 +1241,7 @@ get_bif_constr({erlang, is_record, 2}, Dst, [Var, Tag] = Args, _State) ->
mk_constraint(Var, sub, ArgV)]);
get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
%% TODO: Revise this to make it precise for Tag and Arity.
- ArgFun =
+ ArgFun =
fun(Map) ->
case t_is_atom(true, lookup_type(Dst, Map)) of
true ->
@@ -1257,14 +1257,14 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
GenRecord = t_tuple([TagType|AnyElems]),
case t_atom_vals(TagType) of
[TagVal] ->
- case state__lookup_record(State, TagVal,
+ case state__lookup_record(State, TagVal,
ArityVal - 1) of
{ok, Type} ->
AllOpaques = State#state.opaques,
case t_opaque_match_record(Type, AllOpaques) of
[Opaque] -> Opaque;
_ -> Type
- end;
+ end;
error -> GenRecord
end;
_ -> GenRecord
@@ -1279,9 +1279,9 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
end
end,
ArgV = mk_fun_var(ArgFun, [Tag, Arity, Dst]),
- DstFun = fun(Map) ->
+ DstFun = fun(Map) ->
[TmpVar, TmpTag, TmpArity] = TmpArgTypes = lookup_type_list(Args, Map),
- TmpArgTypes2 =
+ TmpArgTypes2 =
case lists:member(TmpVar, State#state.opaques) of
true ->
case t_is_integer(TmpArity) of
@@ -1293,7 +1293,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
case t_atom_vals(TmpTag) of
[TmpTagVal] ->
case state__lookup_record(State, TmpTagVal, TmpArityVal - 1) of
- {ok, TmpType} ->
+ {ok, TmpType} ->
case t_is_none(t_inf(TmpType, TmpVar, opaque)) of
true -> TmpArgTypes;
false -> [TmpType, TmpTag, TmpArity]
@@ -1312,7 +1312,7 @@ get_bif_constr({erlang, is_record, 3}, Dst, [Var, Tag, Arity] = Args, State) ->
end,
erl_bif_types:type(erlang, is_record, 3, TmpArgTypes2)
end,
- DstV = mk_fun_var(DstFun, Args),
+ DstV = mk_fun_var(DstFun, Args),
mk_conj_constraint_list([mk_constraint(Dst, sub, DstV),
mk_constraint(Arity, sub, t_integer()),
mk_constraint(Tag, sub, t_atom()),
@@ -1334,7 +1334,7 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
true -> False;
false -> t_boolean()
end;
- false ->
+ false ->
t_boolean()
end
end
@@ -1349,7 +1349,7 @@ get_bif_constr({erlang, 'and', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
case t_is_atom(false, Arg2Type) of
true -> False;
false ->
- case (t_is_atom(true, Arg1Type)
+ case (t_is_atom(true, Arg1Type)
andalso t_is_atom(true, Arg2Type)) of
true -> True;
false -> t_boolean()
@@ -1378,7 +1378,7 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
true -> True;
false -> t_boolean()
end;
- false ->
+ false ->
t_boolean()
end
end
@@ -1393,7 +1393,7 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
case t_is_atom(true, Arg2Type) of
true -> True;
false ->
- case (t_is_atom(false, Arg1Type)
+ case (t_is_atom(false, Arg1Type)
andalso t_is_atom(false, Arg2Type)) of
true -> False;
false -> t_boolean()
@@ -1414,7 +1414,7 @@ get_bif_constr({erlang, 'or', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
get_bif_constr({erlang, 'not', 1}, Dst, [Arg] = Args, _State) ->
True = t_from_term(true),
False = t_from_term(false),
- Fun = fun(Var) ->
+ Fun = fun(Var) ->
fun(Map) ->
Type = lookup_type(Var, Map),
case t_is_atom(true, Type) of
@@ -1439,7 +1439,7 @@ get_bif_constr({erlang, '=:=', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
OtherVarType = lookup_type(OtherVar, Map),
case t_is_atom(true, DstType) of
true -> OtherVarType;
- false ->
+ false ->
case t_is_atom(false, DstType) of
true ->
case is_singleton_type(OtherVarType) of
@@ -1492,7 +1492,7 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
true ->
case t_is_number(VarType) of
true -> t_number();
- false ->
+ false ->
case t_is_atom(VarType) of
true -> VarType;
false -> t_any()
@@ -1511,7 +1511,8 @@ get_bif_constr({erlang, '==', 2}, Dst, [Arg1, Arg2] = Args, _State) ->
mk_conj_constraint_list([mk_constraint(Dst, sub, DstV),
mk_constraint(Arg1, sub, ArgV1),
mk_constraint(Arg2, sub, ArgV2)]);
-get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, State) ->
+get_bif_constr({erlang, element, 2} = _BIF, Dst, Args,
+ #state{cs = Constrs} = State) ->
GenType = erl_bif_types:type(erlang, element, 2),
case t_is_none(GenType) of
true -> ?debug("Bif: ~w failed\n", [_BIF]), throw(error);
@@ -1525,9 +1526,14 @@ get_bif_constr({erlang, element, 2} = _BIF, Dst, Args, State) ->
erl_bif_types:type(erlang, element, 2, ATs2)
end,
ReturnType = mk_fun_var(Fun, Args),
- ArgTypes = erl_bif_types:arg_types(erlang, element, 2),
+ ArgTypes = erl_bif_types:arg_types(erlang, element, 2),
Cs = mk_constraints(Args, sub, ArgTypes),
- mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|Cs])
+ NewCs =
+ case find_element(Args, Constrs) of
+ 'unknown' -> Cs;
+ Elem -> [mk_constraint(Dst, eq, Elem)|Cs]
+ end,
+ mk_conj_constraint_list([mk_constraint(Dst, sub, ReturnType)|NewCs])
end;
get_bif_constr({M, F, A} = _BIF, Dst, Args, State) ->
GenType = erl_bif_types:type(M, F, A),
@@ -1541,7 +1547,7 @@ get_bif_constr({M, F, A} = _BIF, Dst, Args, State) ->
false -> T
end
end,
- ReturnType = mk_fun_var(fun(Map) ->
+ ReturnType = mk_fun_var(fun(Map) ->
TmpArgTypes0 = lookup_type_list(Args, Map),
TmpArgTypes = [UnopaqueFun(T) || T<- TmpArgTypes0],
erl_bif_types:type(M, F, A, TmpArgTypes)
@@ -1561,12 +1567,12 @@ get_bif_constr({M, F, A} = _BIF, Dst, Args, State) ->
end
end.
-eval_inv_arith('+', _Pos, Dst, Arg) ->
+eval_inv_arith('+', _Pos, Dst, Arg) ->
erl_bif_types:type(erlang, '-', 2, [Dst, Arg]);
-eval_inv_arith('*', _Pos, Dst, Arg) ->
+eval_inv_arith('*', _Pos, Dst, Arg) ->
case t_number_vals(Arg) of
[0] -> t_integer();
- _ ->
+ _ ->
TmpRet = erl_bif_types:type(erlang, 'div', 2, [Dst, Arg]),
Zero = t_from_term(0),
%% If 0 is not part of the result, it cannot be part of the argument.
@@ -1575,9 +1581,9 @@ eval_inv_arith('*', _Pos, Dst, Arg) ->
true -> TmpRet
end
end;
-eval_inv_arith('-', 1, Dst, Arg) ->
+eval_inv_arith('-', 1, Dst, Arg) ->
erl_bif_types:type(erlang, '-', 2, [Arg, Dst]);
-eval_inv_arith('-', 2, Dst, Arg) ->
+eval_inv_arith('-', 2, Dst, Arg) ->
erl_bif_types:type(erlang, '+', 2, [Arg, Dst]).
range_inc(neg_inf) -> neg_inf;
@@ -1614,7 +1620,7 @@ get_bif_test_constr(Dst, Arg, Type, State) ->
end;
false -> t_from_term(false)
end;
- false ->
+ false ->
case t_is_subtype(ArgType, Type) of
true -> t_from_term(true);
false -> t_boolean()
@@ -1632,11 +1638,11 @@ get_bif_test_constr(Dst, Arg, Type, State) ->
%%=============================================================================
solve([Fun], State) ->
- ?debug("============ Analyzing Fun: ~w ===========\n",
+ ?debug("============ Analyzing Fun: ~w ===========\n",
[debug_lookup_name(Fun)]),
solve_fun(Fun, dict:new(), State);
solve([_|_] = SCC, State) ->
- ?debug("============ Analyzing SCC: ~w ===========\n",
+ ?debug("============ Analyzing SCC: ~w ===========\n",
[[debug_lookup_name(F) || F <- SCC]]),
solve_scc(SCC, dict:new(), State, false).
@@ -1655,7 +1661,7 @@ solve_fun(Fun, FunMap, State) ->
solve_scc(SCC, Map, State, TryingUnit) ->
State1 = state__mark_as_non_self_rec(SCC, State),
- Vars0 = [{Fun, state__get_rec_var(Fun, State)} || Fun <- SCC],
+ Vars0 = [{Fun, state__get_rec_var(Fun, State)} || Fun <- SCC],
Vars = [Var || {_, {ok, Var}} <- Vars0],
Funs = [Fun || {Fun, {ok, _}} <- Vars0],
Types = unsafe_lookup_type_list(Funs, Map),
@@ -1682,7 +1688,7 @@ solve_scc(SCC, Map, State, TryingUnit) ->
false ->
Map2
end;
- false ->
+ false ->
?debug("SCC ~w did not reach fixpoint\n", [SCC]),
solve_scc(SCC, Map2, State, TryingUnit)
end.
@@ -1704,29 +1710,29 @@ scc_fold_fun(F, FunMap, State) ->
format_type(NewType)]),
NewFunMap.
-solve_ref_or_list(#constraint_ref{id = Id, deps = Deps},
+solve_ref_or_list(#constraint_ref{id = Id, deps = Deps},
Map, MapDict, State) ->
- {OldLocalMap, Check} =
+ {OldLocalMap, Check} =
case dict:find(Id, MapDict) of
error -> {dict:new(), false};
{ok, M} -> {M, true}
- end,
+ end,
?debug("Checking ref to fun: ~w\n", [debug_lookup_name(Id)]),
CheckDeps = ordsets:del_element(t_var_name(Id), Deps),
case Check andalso maps_are_equal(OldLocalMap, Map, CheckDeps) of
- true ->
+ true ->
?debug("Equal\n", []),
{ok, MapDict, Map};
false ->
?debug("Not equal. Solving\n", []),
Cs = state__get_cs(Id, State),
- Res =
+ Res =
case state__is_self_rec(Id, State) of
true -> solve_self_recursive(Cs, Map, MapDict, Id, t_none(), State);
false -> solve_ref_or_list(Cs, Map, MapDict, State)
end,
case Res of
- {error, NewMapDict} ->
+ {error, NewMapDict} ->
?debug("Error solving for function ~p\n", [debug_lookup_name(Id)]),
Arity = state__fun_arity(Id, State),
FunType =
@@ -1755,17 +1761,17 @@ solve_ref_or_list(#constraint_ref{id = Id, deps = Deps},
end;
solve_ref_or_list(#constraint_list{type=Type, list = Cs, deps = Deps, id = Id},
Map, MapDict, State) ->
- {OldLocalMap, Check} =
+ {OldLocalMap, Check} =
case dict:find(Id, MapDict) of
error -> {dict:new(), false};
{ok, M} -> {M, true}
end,
?debug("Checking ref to list: ~w\n", [Id]),
case Check andalso maps_are_equal(OldLocalMap, Map, Deps) of
- true ->
+ true ->
?debug("~w equal ~w\n", [Type, Id]),
{ok, MapDict, Map};
- false ->
+ false ->
?debug("~w not equal: ~w. Solving\n", [Type, Id]),
solve_clist(Cs, Type, Id, Deps, MapDict, Map, State)
end.
@@ -1793,7 +1799,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) ->
[[{X, format_type(Y)} || {X, Y} <- dict:to_list(NewMap)]]),
NewRecType = unsafe_lookup_type(Id, NewMap),
case t_is_equal(NewRecType, RecType0) of
- true ->
+ true ->
{ok, NewMapDict, enter_type(RecVar, NewRecType, NewMap)};
false ->
solve_self_recursive(Cs, Map, MapDict, Id, NewRecType, State)
@@ -1801,7 +1807,7 @@ solve_self_recursive(Cs, Map, MapDict, Id, RecType0, State) ->
end.
solve_clist(Cs, conj, Id, Deps, MapDict, Map, State) ->
- case solve_cs(Cs, Map, MapDict, State) of
+ case solve_cs(Cs, Map, MapDict, State) of
{error, _} = Error -> Error;
{ok, NewMapDict, NewMap} = Ret ->
case Cs of
@@ -1821,12 +1827,12 @@ solve_clist(Cs, disj, Id, _Deps, MapDict, Map, State) ->
{ok, NewDict, NewMap} -> {{ok, NewMap}, NewDict};
{error, _NewDict} = Error -> Error
end
- end,
+ end,
{Maps, NewMapDict} = lists:mapfoldl(Fun, MapDict, Cs),
case [X || {ok, X} <- Maps] of
[] -> {error, NewMapDict};
- MapList ->
- NewMap = join_maps(MapList),
+ MapList ->
+ NewMap = join_maps(MapList),
{ok, dict:store(Id, NewMap, NewMapDict), NewMap}
end.
@@ -1844,13 +1850,13 @@ solve_cs([#constraint{} = C|Tail], Map, MapDict, State) ->
case solve_one_c(C, Map, State#state.opaques) of
error ->
?debug("+++++++++++\nFailed: ~s :: ~s ~w ~s :: ~s\n+++++++++++\n",
- [format_type(C#constraint.lhs),
+ [format_type(C#constraint.lhs),
format_type(lookup_type(C#constraint.lhs, Map)),
C#constraint.op,
- format_type(C#constraint.rhs),
+ format_type(C#constraint.rhs),
format_type(lookup_type(C#constraint.rhs, Map))]),
{error, MapDict};
- {ok, NewMap} ->
+ {ok, NewMap} ->
solve_cs(Tail, NewMap, MapDict, State)
end;
solve_cs([], Map, MapDict, _State) ->
@@ -1863,7 +1869,7 @@ solve_one_c(#constraint{lhs = Lhs, rhs = Rhs, op = Op}, Map, Opaques) ->
?debug("Solving: ~s :: ~s ~w ~s :: ~s\n\tInf: ~s\n",
[format_type(Lhs), format_type(LhsType), Op,
format_type(Rhs), format_type(RhsType), format_type(Inf)]),
- case t_is_none(Inf) of
+ case t_is_none(Inf) of
true -> error;
false ->
case Op of
@@ -1887,8 +1893,8 @@ solve_subtype(Type, Inf, Map, Opaques) ->
try t_unify(Type, Inf, Opaques) of
{_, List} -> {ok, enter_type_list(List, Map)}
catch
- throw:{mismatch, _T1, _T2} ->
- ?debug("Mismatch between ~s and ~s\n",
+ throw:{mismatch, _T1, _T2} ->
+ ?debug("Mismatch between ~s and ~s\n",
[format_type(_T1), format_type(_T2)]),
error
end.
@@ -1936,9 +1942,9 @@ maps_are_equal_1(Map1, Map2, [H|Tail]) ->
T2 = lookup_type(H, Map2),
case t_is_equal(T1, T2) of
true -> maps_are_equal_1(Map1, Map2, Tail);
- false ->
+ false ->
?debug("~w: ~s =/= ~s\n", [H, format_type(T1), format_type(T2)]),
- false
+ false
end;
maps_are_equal_1(_Map1, _Map2, []) ->
true.
@@ -1953,7 +1959,7 @@ prune_keys(Map1, Map2, Deps) ->
true ->
Keys1 = dict:fetch_keys(Map1),
case length(Keys1) > NofDeps of
- true ->
+ true ->
Set1 = lists:sort(Keys1),
Set2 = lists:sort(dict:fetch_keys(Map2)),
ordsets:intersection(ordsets:union(Set1, Set2), Deps);
@@ -2035,7 +2041,7 @@ lookup_type(Key, Map) ->
mk_var(Var) ->
case cerl:is_literal(Var) of
true -> Var;
- false ->
+ false ->
case cerl:is_c_values(Var) of
true -> t_product(mk_var_no_lit_list(cerl:values_es(Var)));
false -> t_var(cerl_trees:get_label(Var))
@@ -2076,10 +2082,10 @@ state__set_opaques(#state{records = RecDict} = State, {M, _F, _A}) ->
state__lookup_record(#state{records = Records}, Tag, Arity) ->
case erl_types:lookup_record(Tag, Arity, Records) of
- {ok, Fields} ->
+ {ok, Fields} ->
{ok, t_tuple([t_from_term(Tag)|
[FieldType || {_FieldName, FieldType} <- Fields]])};
- error ->
+ error ->
error
end.
@@ -2098,12 +2104,12 @@ state__is_in_guard(#state{in_guard = Bool}) ->
state__get_fun_prototype(Op, Arity, State) ->
case t_is_fun(Op) of
true -> {State, Op};
- false ->
+ false ->
{State1, [Ret|Args]} = state__mk_vars(Arity+1, State),
Fun = t_fun(Args, Ret),
{State1, Fun}
end.
-
+
state__lookup_rec_var_in_scope(MFA, #state{name_map = NameMap}) ->
dict:find(MFA, NameMap).
@@ -2115,11 +2121,11 @@ state__store_fun_arity(Tree, #state{fun_arities = Map} = State) ->
state__fun_arity(Id, #state{fun_arities = Map}) ->
dict:fetch(Id, Map).
-state__lookup_undef_var(Tree, #state{callgraph = CG, plt = Plt}) ->
+state__lookup_undef_var(Tree, #state{callgraph = CG, plt = Plt}) ->
Label = cerl_trees:get_label(Tree),
case dialyzer_callgraph:lookup_rec_var(Label, CG) of
error -> error;
- {ok, MFA} ->
+ {ok, MFA} ->
case dialyzer_plt:lookup(Plt, MFA) of
none -> error;
{value, {RetType, ArgTypes}} -> {ok, t_fun(ArgTypes, RetType)}
@@ -2179,7 +2185,7 @@ state__add_prop_constrs(Tree, #state{prop_types = PropTypes} = State) ->
case erl_types:any_none(ArgTypes) of
true -> not_called;
false ->
- ?debug("Adding propagated constr: ~s for function ~w\n",
+ ?debug("Adding propagated constr: ~s for function ~w\n",
[format_type(FunType), debug_lookup_name(mk_var(Tree))]),
FunVar = mk_var(Tree),
state__store_conj(FunVar, sub, FunType, State)
@@ -2225,7 +2231,7 @@ state__store_conj_lists_1([], _Op, [], State) ->
state__mk_var(#state{next_label = NL} = State) ->
{State#state{next_label = NL+1}, t_var(NL)}.
-
+
state__mk_vars(N, #state{next_label = NL} = State) ->
NewLabel = NL + N,
Vars = [t_var(X) || X <- lists:seq(NL, NewLabel-1)],
@@ -2235,7 +2241,7 @@ state__store_constrs(Id, Cs, #state{cmap = Dict} = State) ->
NewDict = dict:store(Id, Cs, Dict),
State#state{cmap = NewDict}.
-state__get_cs(Var, #state{cmap = Dict}) ->
+state__get_cs(Var, #state{cmap = Dict}) ->
dict:fetch(Var, Dict).
%% The functions here will not be treated as self recursive.
@@ -2286,7 +2292,7 @@ mk_constraint(Lhs, Op, Rhs) ->
%% This constraint is constant. Solve it immediately.
case solve_one_c(C, dict:new(), []) of
error -> throw(error);
- _ ->
+ _ ->
%% This is always true, keep it anyway for logistic reasons
C
end;
@@ -2335,7 +2341,7 @@ mk_constraint_1(Lhs, eq, Rhs) when Lhs < Rhs ->
mk_constraint_1(Lhs, eq, Rhs) ->
#constraint{lhs = Rhs, op = eq, rhs = Lhs};
mk_constraint_1(Lhs, Op, Rhs) ->
- #constraint{lhs = Lhs, op = Op, rhs = Rhs}.
+ #constraint{lhs = Lhs, op = Op, rhs = Rhs}.
mk_constraints([Lhs|LhsTail], Op, [Rhs|RhsTail]) ->
[mk_constraint(Lhs, Op, Rhs)|mk_constraints(LhsTail, Op, RhsTail)];
@@ -2350,7 +2356,7 @@ mk_constraint_list(Type, List) ->
List2 = ordsets:filter(fun(X) -> get_deps(X) =/= [] end, List1),
Deps = calculate_deps(List2),
case Deps =:= [] of
- true -> #constraint_list{type = conj,
+ true -> #constraint_list{type = conj,
list = [mk_constraint(t_any(), eq, t_any())],
deps = []};
false -> #constraint_list{type = Type, list = List2, deps = Deps}
@@ -2372,11 +2378,11 @@ update_constraint_list(CL, List) ->
%% We expand guard constraints into dijunctive normal form to gain
%% precision in simple guards. However, because of the exponential
%% growth of this expansion in the presens of disjunctions we can even
-%% get into trouble while expanding.
+%% get into trouble while expanding.
%%
%% To limit this we only expand when the number of disjunctions are
%% below a certain limit. This limit is currently set based on the
-%% behaviour of boolean 'or'.
+%% behaviour of boolean 'or'.
%%
%% V1 = V2 or V3
%%
@@ -2395,7 +2401,7 @@ update_constraint_list(CL, List) ->
-define(DISJ_NORM_FORM_LIMIT, 28).
mk_disj_norm_form(#constraint_list{} = CL) ->
- try
+ try
List1 = expand_to_conjunctions(CL),
mk_disj_constraint_list(List1)
catch
@@ -2409,7 +2415,7 @@ expand_to_conjunctions(#constraint_list{type = conj, list = List}) ->
true -> [mk_conj_constraint_list(List1)];
false ->
case List2 of
- [JustOneList] ->
+ [JustOneList] ->
[mk_conj_constraint_list([L|List1]) || L <- JustOneList];
_ ->
combine_conj_lists(List2, List1)
@@ -2422,7 +2428,7 @@ expand_to_conjunctions(#constraint_list{type = disj, list = List}) ->
List1 = [C || C <- List, is_simple_constraint(C)],
%% Just an assert.
[] = [C || #constraint{} = C <- List1],
- Expanded = lists:flatten([expand_to_conjunctions(C)
+ Expanded = lists:flatten([expand_to_conjunctions(C)
|| #constraint_list{} = C <- List]),
ReturnList = Expanded ++ List1,
if length(ReturnList) > ?DISJ_NORM_FORM_LIMIT -> throw(too_many_disj);
@@ -2467,7 +2473,7 @@ wrap_simple_constr(#constraint_list{} = C) -> C;
wrap_simple_constr(#constraint_ref{} = C) -> C.
enumerate_constraints(State) ->
- Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State)))
+ Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State)))
|| Id <- state__scc(State)],
{_, _, NewState} = enumerate_constraints(Cs, 0, [], State),
NewState.
@@ -2475,9 +2481,9 @@ enumerate_constraints(State) ->
enumerate_constraints([#constraint_ref{id = Id} = C|Tail], N, Acc, State) ->
Cs = state__get_cs(Id, State),
{[NewCs], NewN, NewState1} = enumerate_constraints([Cs], N, [], State),
- NewState2 = state__store_constrs(Id, NewCs, NewState1),
+ NewState2 = state__store_constrs(Id, NewCs, NewState1),
enumerate_constraints(Tail, NewN+1, [C|Acc], NewState2);
-enumerate_constraints([#constraint_list{type = conj, list = List} = C|Tail],
+enumerate_constraints([#constraint_list{type = conj, list = List} = C|Tail],
N, Acc, State) ->
%% Separate the flat constraints from the deep ones to make a
%% separate fixpoint interation over the flat ones for speed.
@@ -2496,7 +2502,7 @@ enumerate_constraints([#constraint_list{type = conj, list = List} = C|Tail],
end,
NewAcc = [C#constraint_list{list = NewList, id = {list, N3}}|Acc],
enumerate_constraints(Tail, N3+1, NewAcc, State2);
-enumerate_constraints([#constraint_list{list = List, type = disj} = C|Tail],
+enumerate_constraints([#constraint_list{list = List, type = disj} = C|Tail],
N, Acc, State) ->
{NewList, NewN, NewState} = enumerate_constraints(List, N, [], State),
NewAcc = [C#constraint_list{list = NewList, id = {list, NewN}}|Acc],
@@ -2515,7 +2521,7 @@ group_constraints_in_components(Cs, N) ->
case find_dep_components(DepList, []) of
[_] -> {Cs, N};
[_|_] = Components ->
- ConstrComp = [[C || #constraint{deps = D} = C <- Cs,
+ ConstrComp = [[C || #constraint{deps = D} = C <- Cs,
ordsets:is_subset(D, Comp)]
|| Comp <- Components],
lists:mapfoldl(fun(CComp, TmpN) ->
@@ -2545,7 +2551,7 @@ find_dep_components([], AccSet, Ungrouped) ->
%% Put the fun ref constraints last in any conjunction since we need
%% to separate the environment from the interior of the function.
order_fun_constraints(State) ->
- Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State)))
+ Cs = [mk_constraint_ref(Id, get_deps(state__get_cs(Id, State)))
|| Id <- state__scc(State)],
order_fun_constraints(Cs, State).
@@ -2565,8 +2571,8 @@ order_fun_constraints([#constraint_list{list = List, type = Type} = C|Tail],
case Type of
conj -> order_fun_constraints(List, [], [], State);
disj ->
- FoldFun = fun(X, AccState) ->
- {[NewX], NewAccState} =
+ FoldFun = fun(X, AccState) ->
+ {[NewX], NewAccState} =
order_fun_constraints([X], [], [], AccState),
{NewX, NewAccState}
end,
@@ -2588,7 +2594,7 @@ order_fun_constraints([], Funs, Acc, State) ->
is_singleton_non_number_type(Type) ->
case t_is_number(Type) of
- true -> false;
+ true -> false;
false -> is_singleton_type(Type)
end.
@@ -2613,6 +2619,41 @@ is_singleton_type(Type) ->
end
end.
+find_element(Args, Cs) ->
+ [Pos, Tuple] = Args,
+ case erl_types:t_is_number(Pos) of
+ true ->
+ case erl_types:t_number_vals(Pos) of
+ 'unknown' -> 'unknown';
+ [I] ->
+ case find_constraint(Tuple, Cs) of
+ 'unknown' -> 'unknown';
+ #constraint{lhs = ExTuple} ->
+ case erl_types:t_is_tuple(ExTuple) of
+ true ->
+ Elems = erl_types:t_tuple_args(ExTuple),
+ Elem = lists:nth(I, Elems),
+ case erl_types:t_is_var(Elem) of
+ true -> Elem;
+ false -> 'unknown'
+ end;
+ false -> 'unknown'
+ end
+ end;
+ _ -> 'unknown'
+ end;
+ false -> 'unknown'
+ end.
+
+find_constraint(_Tuple, []) ->
+ 'unknown';
+find_constraint(Tuple, [#constraint{op = 'eq', rhs = Tuple} = C|_]) ->
+ C;
+find_constraint(Tuple, [#constraint_list{list = List}|Cs]) ->
+ find_constraint(Tuple, List ++ Cs);
+find_constraint(Tuple, [_|Cs]) ->
+ find_constraint(Tuple, Cs).
+
%% ============================================================================
%%
%% Pretty printer and debug facilities.
@@ -2638,7 +2679,7 @@ format_type(Type) ->
-ifdef(DEBUG_NAME_MAP).
debug_make_name_map(Vars, Funs) ->
Map = get(dialyzer_typesig_map),
- NewMap =
+ NewMap =
if Map =:= undefined -> debug_make_name_map(Vars, Funs, dict:new());
true -> debug_make_name_map(Vars, Funs, Map)
end,
@@ -2676,15 +2717,15 @@ pp_constraints(Cs, State) ->
io:nl(),
Res.
-pp_constraints([List|Tail], Separator, Level, MaxDepth,
+pp_constraints([List|Tail], Separator, Level, MaxDepth,
State) when is_list(List) ->
pp_constraints(List++Tail, Separator, Level, MaxDepth, State);
-pp_constraints([#constraint_ref{id = Id}|Left], Separator,
+pp_constraints([#constraint_ref{id = Id}|Left], Separator,
Level, MaxDepth, State) ->
Cs = state__get_cs(Id, State),
io:format("%Ref ~w%", [t_var_name(Id)]),
pp_constraints([Cs|Left], Separator, Level, MaxDepth, State);
-pp_constraints([#constraint{lhs = Lhs, op = Op, rhs = Rhs}], _Separator,
+pp_constraints([#constraint{lhs = Lhs, op = Op, rhs = Rhs}], _Separator,
Level, MaxDepth, _State) ->
io:format("~s ~w ~s", [format_type(Lhs), Op, format_type(Rhs)]),
erlang:max(Level, MaxDepth);
@@ -2721,7 +2762,7 @@ pp_constrs_scc(_SCC, _State) ->
constraints_to_dot_scc(SCC, State) ->
io:format("SCC: ~p\n", [SCC]),
- Name = lists:flatten([io_lib:format("'~w'", [debug_lookup_name(Fun)])
+ Name = lists:flatten([io_lib:format("'~w'", [debug_lookup_name(Fun)])
|| Fun <- SCC]),
Cs = [state__get_cs(Fun, State) || Fun <- SCC],
constraints_to_dot(Cs, Name, State).
@@ -2737,22 +2778,22 @@ constraints_to_dot(Cs0, Name, State) ->
constraints_to_nodes([{Name, #constraint_list{type = Type, list = List, id=Id}}
|Left], N, Level, Graph, Opts, State) ->
- N1 = N + length(List),
+ N1 = N + length(List),
NewList = lists:zip(lists:seq(N, N1 - 1), List),
Names = [SubName || {SubName, _C} <- NewList],
Edges = [{Name, SubName} || SubName <- Names],
- ThisNode = [{Name, Opt} || Opt <- [{label,
+ ThisNode = [{Name, Opt} || Opt <- [{label,
lists:flatten(io_lib:format("~w", [Id]))},
{shape, get_shape(Type)},
{level, Level}]],
- {NewGraph, NewOpts, N2} = constraints_to_nodes(NewList, N1, Level+1,
- [Edges|Graph],
+ {NewGraph, NewOpts, N2} = constraints_to_nodes(NewList, N1, Level+1,
+ [Edges|Graph],
[ThisNode|Opts], State),
constraints_to_nodes(Left, N2, Level, NewGraph, NewOpts, State);
constraints_to_nodes([{Name, #constraint{lhs = Lhs, op = Op, rhs = Rhs}}|Left],
N, Level, Graph, Opts, State) ->
- Label = lists:flatten(io_lib:format("~s ~w ~s",
- [format_type(Lhs), Op,
+ Label = lists:flatten(io_lib:format("~s ~w ~s",
+ [format_type(Lhs), Op,
format_type(Rhs)])),
ThisNode = [{Name, Opt} || Opt <- [{label, Label}, {level, Level}]],
NewOpts = [ThisNode|Opts],
@@ -2761,20 +2802,20 @@ constraints_to_nodes([{Name, #constraint_ref{id = Id0}}|Left],
N, Level, Graph, Opts, State) ->
Id = debug_lookup_name(Id0),
CList = state__get_cs(Id0, State),
- ThisNode = [{Name, Opt} || Opt <- [{label,
+ ThisNode = [{Name, Opt} || Opt <- [{label,
lists:flatten(io_lib:format("~w", [Id]))},
{shape, ellipse},
- {level, Level}]],
- NewList = [{N, CList}],
- {NewGraph, NewOpts, N1} = constraints_to_nodes(NewList, N + 1, Level + 1,
+ {level, Level}]],
+ NewList = [{N, CList}],
+ {NewGraph, NewOpts, N1} = constraints_to_nodes(NewList, N + 1, Level + 1,
[{Name, N}|Graph],
[ThisNode|Opts], State),
constraints_to_nodes(Left, N1, Level, NewGraph, NewOpts, State);
constraints_to_nodes([], N, _Level, Graph, Opts, _State) ->
{lists:flatten(Graph), lists:flatten(Opts), N}.
-
+
get_shape(conj) -> box;
-get_shape(disj) -> diamond.
+get_shape(disj) -> diamond.
-else.
constraints_to_dot_scc(_SCC, _State) ->
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl
index 6ea243c26f..a9da229061 100644
--- a/lib/dialyzer/src/dialyzer_utils.erl
+++ b/lib/dialyzer/src/dialyzer_utils.erl
@@ -21,7 +21,7 @@
%%%-------------------------------------------------------------------
%%% File : dialyzer_utils.erl
%%% Author : Tobias Lindahl <[email protected]>
-%%% Description :
+%%% Description :
%%%
%%% Created : 5 Dec 2006 by Tobias Lindahl <[email protected]>
%%%-------------------------------------------------------------------
@@ -42,6 +42,7 @@
merge_records/2,
pp_hook/0,
process_record_remote_types/1,
+ sets_filter/2,
src_compiler_opts/0
]).
@@ -73,12 +74,11 @@ print_types1([{record, _Name} = Key|T], RecDict) ->
-define(debug(D_), ok).
-endif.
-%%
-%% Types that need to be imported from somewhere else
-%%
+%% ----------------------------------------------------------------------------
--type abstract_code() :: [tuple()]. %% XXX: refine
--type comp_options() :: [atom()]. %% XXX: only a resticted set of options used
+-type abstract_code() :: [tuple()]. %% XXX: import from somewhere
+-type comp_options() :: [compile:option()].
+-type mod_or_fname() :: atom() | file:filename().
%% ============================================================================
%%
@@ -86,13 +86,13 @@ print_types1([{record, _Name} = Key|T], RecDict) ->
%%
%% ============================================================================
--spec get_abstract_code_from_src(atom() | file:filename()) ->
+-spec get_abstract_code_from_src(mod_or_fname()) ->
{'ok', abstract_code()} | {'error', [string()]}.
get_abstract_code_from_src(File) ->
get_abstract_code_from_src(File, src_compiler_opts()).
--spec get_abstract_code_from_src(atom() | file:filename(), comp_options()) ->
+-spec get_abstract_code_from_src(mod_or_fname(), comp_options()) ->
{'ok', abstract_code()} | {'error', [string()]}.
get_abstract_code_from_src(File, Opts) ->
@@ -169,13 +169,13 @@ get_record_and_type_info(AbstractCode) ->
Module = get_module(AbstractCode),
get_record_and_type_info(AbstractCode, Module, dict:new()).
--spec get_record_and_type_info(abstract_code(), atom(), dict()) ->
+-spec get_record_and_type_info(abstract_code(), module(), dict()) ->
{'ok', dict()} | {'error', string()}.
get_record_and_type_info(AbstractCode, Module, RecDict) ->
get_record_and_type_info(AbstractCode, Module, [], RecDict).
-get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left],
+get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left],
Module, Records, RecDict) ->
{ok, Fields} = get_record_fields(Fields0, RecDict),
Arity = length(Fields),
@@ -188,7 +188,7 @@ get_record_and_type_info([{attribute, _, type, {{record, Name}, Fields0, []}}
Arity = length(Fields),
NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict),
get_record_and_type_info(Left, Module, Records, NewRecDict);
-get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left],
+get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left],
Module, Records, RecDict) when Attr =:= 'type';
Attr =:= 'opaque' ->
try
@@ -197,7 +197,7 @@ get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left],
catch
throw:{error, _} = Error -> Error
end;
-get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left],
+get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left],
Module, Records, RecDict) when Attr =:= 'type';
Attr =:= 'opaque' ->
try
@@ -219,7 +219,7 @@ get_record_and_type_info([], _Module, Records, RecDict) ->
end.
add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) ->
- case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of
+ case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of
true ->
throw({error, io_lib:format("Type already defined: ~w\n", [Name])});
false ->
@@ -237,7 +237,7 @@ add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) ->
get_record_fields(Fields, RecDict) ->
get_record_fields(Fields, RecDict, []).
-get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left],
+get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left],
RecDict, Acc) ->
Name =
case OrdRecField of
@@ -278,13 +278,16 @@ type_record_fields([RecKey|Recs], RecDict) ->
process_record_remote_types(CServer) ->
TempRecords = dialyzer_codeserver:get_temp_records(CServer),
+ TempExpTypes = dialyzer_codeserver:get_temp_exported_types(CServer),
RecordFun =
fun(Key, Value) ->
case Key of
{record, _Name} ->
FieldFun =
fun(_Arity, Fields) ->
- [{Name, erl_types:t_solve_remote(Field, TempRecords)} || {Name, Field} <- Fields]
+ [{Name, erl_types:t_solve_remote(Field, TempExpTypes,
+ TempRecords)}
+ || {Name, Field} <- Fields]
end,
orddict:map(FieldFun, Value);
_Other -> Value
@@ -295,7 +298,8 @@ process_record_remote_types(CServer) ->
dict:map(RecordFun, Record)
end,
NewRecords = dict:map(ModuleFun, TempRecords),
- dialyzer_codeserver:finalize_records(NewRecords, CServer).
+ CServer1 = dialyzer_codeserver:finalize_records(NewRecords, CServer),
+ dialyzer_codeserver:finalize_exported_types(TempExpTypes, CServer1).
-spec merge_records(dict(), dict()) -> dict().
@@ -308,19 +312,19 @@ merge_records(NewRecords, OldRecords) ->
%%
%% ============================================================================
--spec get_spec_info(module(), abstract_code(), dict()) ->
+-spec get_spec_info(atom(), abstract_code(), dict()) ->
{'ok', dict()} | {'error', string()}.
get_spec_info(ModName, AbstractCode, RecordsDict) ->
get_spec_info(AbstractCode, dict:new(), RecordsDict, ModName, "nofile").
-%% TypeSpec is a list of conditional contracts for a function.
+%% TypeSpec is a list of conditional contracts for a function.
%% Each contract is of the form {[Argument], Range, [Constraint]} where
%% - Argument and Range are in erl_types:erl_type() format and
%% - Constraint is of the form {subtype, T1, T2} where T1 and T2
%% are erl_types:erl_type()
-get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left],
+get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left],
SpecDict, RecordsDict, ModName, File) when is_list(TypeSpec) ->
MFA = case Id of
{_, _, _} = T -> T;
@@ -335,7 +339,7 @@ get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left],
{ok, {{OtherFile, L},_C}} ->
{Mod, Fun, Arity} = MFA,
Msg = io_lib:format(" Contract for function ~w:~w/~w "
- "already defined in ~s:~w\n",
+ "already defined in ~s:~w\n",
[Mod, Fun, Arity, OtherFile, L]),
throw({error, Msg})
catch
@@ -353,15 +357,30 @@ get_spec_info([], SpecDict, _RecordsDict, _ModName, _File) ->
%% ============================================================================
%%
+%% Exported types
+%%
+%% ============================================================================
+
+-spec sets_filter([module()], set()) -> set().
+
+sets_filter([], ExpTypes) ->
+ ExpTypes;
+sets_filter([Mod|Mods], ExpTypes) ->
+ NewExpTypes = sets:filter(fun({M, _F, _A}) -> M =/= Mod end, ExpTypes),
+ sets_filter(Mods, NewExpTypes).
+
+%% ============================================================================
+%%
%% Util utils
%%
%% ============================================================================
--spec src_compiler_opts() -> comp_options().
+-spec src_compiler_opts() -> [compile:option(),...].
src_compiler_opts() ->
[no_copt, to_core, binary, return_errors,
- no_inline, strict_record_tests, strict_record_updates].
+ no_inline, strict_record_tests, strict_record_updates,
+ no_is_record_optimization].
-spec get_module(abstract_code()) -> module().
@@ -381,7 +400,7 @@ cleanup_parse_transforms([]) ->
-spec format_errors([{module(), string()}]) -> [string()].
format_errors([{Mod, Errors}|Left]) ->
- FormatedError =
+ FormatedError =
[io_lib:format("~s:~w: ~s\n", [Mod, Line, M:format_error(Desc)])
|| {Line, M, Desc} <- Errors],
[lists:flatten(FormatedError) | format_errors(Left)];
@@ -456,7 +475,7 @@ pp_size(Size, Ctxt, Cont) ->
end.
pp_opts(Type, Flags) ->
- FinalFlags =
+ FinalFlags =
case cerl:atom_val(Type) of
binary -> [];
float -> keep_endian(cerl:concrete(Flags));
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index e3e3f6d668..f2daf86def 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 2.2.0
+DIALYZER_VSN = 2.3.0
diff --git a/lib/docbuilder/src/docb_edoc_xml_cb.erl b/lib/docbuilder/src/docb_edoc_xml_cb.erl
index 4dba843341..f5cfc0fe18 100644
--- a/lib/docbuilder/src/docb_edoc_xml_cb.erl
+++ b/lib/docbuilder/src/docb_edoc_xml_cb.erl
@@ -478,31 +478,29 @@ otp_xmlify_a_href("#"++_ = Marker, Es0) -> % <seealso marker="#what">
otp_xmlify_a_href("http:"++_ = URL, Es0) -> % external URL
{URL, Es0};
otp_xmlify_a_href("OTPROOT"++AppRef, Es0) -> % <.. marker="App:FileRef
- case split(AppRef, "/") of
- [AppS, "doc", FileRef1] ->
- FileRef = AppS++":"++otp_xmlify_a_fileref(FileRef1, AppS),
- [#xmlText{value=Str0} = T] = Es0,
- Str = case split(Str0, "/") of
- %% //Application
- [AppS2] ->
- %% AppS2 can differ from AppS
- %% Example: xmerl/XMerL
- AppS2;
- [_AppS,ModRef] ->
- case split(ModRef, ":") of
- %% //Application/Module
- [Module] ->
- Module++"(3)";
- %% //Application/Module:Type()
- [_Module,_Type] ->
- ModRef
- end;
- %% //Application/Module:Function/Arity
- [_AppS,ModFunc,Arity] ->
- ModFunc++"/"++Arity
- end,
- {FileRef, [T#xmlText{value=Str}]}
- end;
+ [AppS, "doc", FileRef1] = split(AppRef, "/"),
+ FileRef = AppS++":"++otp_xmlify_a_fileref(FileRef1, AppS),
+ [#xmlText{value=Str0} = T] = Es0,
+ Str = case split(Str0, "/") of
+ %% //Application
+ [AppS2] ->
+ %% AppS2 can differ from AppS
+ %% Example: xmerl/XMerL
+ AppS2;
+ [_AppS,ModRef] ->
+ case split(ModRef, ":") of
+ %% //Application/Module
+ [Module] ->
+ Module++"(3)";
+ %% //Application/Module:Type()
+ [_Module,_Type] ->
+ ModRef
+ end;
+ %% //Application/Module:Function/Arity
+ [_AppS,ModFunc,Arity] ->
+ ModFunc++"/"++Arity
+ end,
+ {FileRef, [T#xmlText{value=Str}]};
otp_xmlify_a_href("../"++File, Es0) ->
%% Special case: This kind of relative path is used on some
%% places within i.e. EDoc and refers to a file within the same
@@ -639,13 +637,13 @@ otp_xmlify_table([#xmlText{} = E|Es]) ->
[E | otp_xmlify_table(Es)];
otp_xmlify_table([#xmlElement{name=tbody} = E|Es]) ->
otp_xmlify_table(E#xmlElement.content)++otp_xmlify_table(Es);
-otp_xmlify_table([#xmlElement{name=tr} = E|Es]) ->
+otp_xmlify_table([#xmlElement{name=tr, content=Content}|Es]) ->
%% Insert newlines between table rows
- otp_xmlify_table(E#xmlElement.content)++[{br,[]}]++otp_xmlify_table(Es);
-otp_xmlify_table([#xmlElement{name=th} = E|Es]) ->
- [{em, E#xmlElement.content} | otp_xmlify_table(Es)];
-otp_xmlify_table([#xmlElement{name=td} = E|Es]) ->
- otp_xmlify_e(E#xmlElement.content) ++ otp_xmlify_table(Es);
+ otp_xmlify_table(Content)++[{br,[]}]++otp_xmlify_table(Es);
+otp_xmlify_table([#xmlElement{name=th, content=Content}|Es]) ->
+ [{em, Content} | otp_xmlify_table(Es)];
+otp_xmlify_table([#xmlElement{name=td, content=Content}|Es]) ->
+ otp_xmlify_e(Content) ++ otp_xmlify_table(Es);
otp_xmlify_table([]) ->
[].
@@ -1155,8 +1153,8 @@ get_text(#xmlElement{content=[E]}) ->
%% text_only(Es) -> Ts
%% Takes a list of xmlElement and xmlText and return a lists of xmlText.
-text_only([#xmlElement{} = E |Es]) ->
- text_only(E#xmlElement.content) ++ text_only(Es);
+text_only([#xmlElement{content = Content}|Es]) ->
+ text_only(Content) ++ text_only(Es);
text_only([#xmlText{} = E |Es]) ->
[E | text_only(Es)];
text_only([]) ->
diff --git a/lib/docbuilder/src/docb_html.erl b/lib/docbuilder/src/docb_html.erl
index 9aea4c8a66..bdfc5ea876 100644
--- a/lib/docbuilder/src/docb_html.erl
+++ b/lib/docbuilder/src/docb_html.erl
@@ -283,16 +283,16 @@ rule([term|_], {_, [ID], _}, Opts) ->
ID,
"</strong></em> "]}, Opts};
TermList ->
- case lists:keysearch(ID, 1, TermList) of
+ case lists:keyfind(ID, 1, TermList) of
false ->
{{drop, ["<em><strong>", ID,
"</strong></em> "]},
Opts};
- {value, {ID, Name, _Description, _Resp}} ->
+ {ID, Name, _Description, _Resp} ->
{{drop, ["<em><strong>", Name,
"</strong></em> "]},
Opts};
- {value, {ID, Name, _Description}} ->
+ {ID, Name, _Description} ->
{{drop, [ "<em><strong>", Name,
"</strong></em> "]},
Opts}
@@ -306,16 +306,16 @@ rule([term|_], {_, [ID], _}, Opts) ->
TermList ->
PartApplication =
docb_util:lookup_option(part_application, Opts),
- case lists:keysearch(ID, 1, TermList) of
+ case lists:keyfind(ID, 1, TermList) of
false ->
{{drop, ["<a href=\"", PartApplication,
"_term.html#", ID, "\">", ID,
"</a> "]}, Opts};
- {value, {ID, Name, _Description, _Resp}} ->
+ {ID, Name, _Description, _Resp} ->
{{drop, ["<a href=\"", PartApplication,
"_term.html#", ID, "\">", Name,
"</a> "]}, Opts};
- {value, {ID, Name, _Description}} ->
+ {ID, Name, _Description} ->
{{drop, ["<a href=\"", PartApplication,
"_term.html#", ID, "\">", Name,
"</a> "]}, Opts}
@@ -331,17 +331,16 @@ rule([cite|_], {_, [ID], _}, Opts) ->
{{drop, ["<em><strong>", ID, "</strong></em> "]},
Opts};
CiteList ->
- case lists:keysearch(ID, 1, CiteList) of
+ case lists:keyfind(ID, 1, CiteList) of
false ->
{{drop,
["<em><strong>", ID, "</strong></em> "]},
Opts};
-
- {value, {ID, Name, _Description, _Resp}} ->
+ {ID, Name, _Description, _Resp} ->
{{drop, ["<em><strong>", Name,
"</strong></em> "]},
Opts};
- {value, {ID, Name, _Description}} ->
+ {ID, Name, _Description} ->
{{drop, ["<em><strong>", Name,
"</strong></em> "]},
Opts}
@@ -355,18 +354,18 @@ rule([cite|_], {_, [ID], _}, Opts) ->
CiteList ->
PartApp =
docb_util:lookup_option(part_application, Opts),
- case lists:keysearch(ID, 1, CiteList) of
+ case lists:keyfind(ID, 1, CiteList) of
false ->
{{drop, ["<a href=\"", PartApp,
"_cite.html#", ID, "\">", ID,
"</a> "]},
Opts};
- {value, {ID, Name, _Description, _Resp}} ->
+ {ID, Name, _Description, _Resp} ->
{{drop, ["<a href=\"", PartApp,
"_cite.html#", ID, "\">", Name,
"</a> "]},
Opts};
- {value, {ID, Name, _Description}} ->
+ {ID, Name, _Description} ->
{{drop, ["<a href=\"", PartApp,
"_cite.html#", ID, "\">", Name,
"</a> "]},
@@ -376,7 +375,7 @@ rule([cite|_], {_, [ID], _}, Opts) ->
end;
rule([code|_], {_, [Type], [{pcdata, _, Code}]}, Opts) ->
- case lists:member(Type,["ERL","C","NONE"]) of
+ case lists:member(Type, ["ERL","C","NONE"]) of
true ->
{{drop, ["\n<div class=\"example\"><pre>\n", docb_html_util:element_cdata_to_html(Code),
"\n</pre></div>\n"]}, Opts};
diff --git a/lib/docbuilder/src/docb_html_util.erl b/lib/docbuilder/src/docb_html_util.erl
index b2951706ea..02ce8b52a7 100644
--- a/lib/docbuilder/src/docb_html_util.erl
+++ b/lib/docbuilder/src/docb_html_util.erl
@@ -136,7 +136,6 @@ copy_pics(Src, Dest, Opts) ->
Dir = code:lib_dir(docbuilder),
InFile = filename:join([Dir, "etc", Src]),
OutFile = docb_util:outfile(Dest, "", Opts),
-
case filelib:last_modified(OutFile) of
0 -> % File doesn't exist
file:copy(InFile, OutFile);
@@ -156,10 +155,10 @@ copy_pics(Src, Dest, Opts) ->
%%--Resolve header data-------------------------------------------------
extract_header_data(Key, {header, [], List}) ->
- case lists:keysearch(Key, 1, List) of
- {value, {Key, [], []}} ->
+ case lists:keyfind(Key, 1, List) of
+ {Key, [], []} ->
"";
- {value, {Key, [], [{pcdata, [], Value}]}} ->
+ {Key, [], [{pcdata, [], Value}]} ->
pcdata_to_html(Value);
false ->
""
@@ -253,7 +252,7 @@ make_anchor_href(HRef) ->
{ok, [HRef]} ->
%% No `#' in HRef, i.e. only path
make_anchor_href(HRef, "");
- {ok, [Path, Fragment]}->
+ {ok, [Path, Fragment]} ->
make_anchor_href(Path, Fragment)
end.
@@ -398,10 +397,10 @@ count_sections([]) ->
%%--Make a ToC----------------------------------------------------------
format_toc(Toc) ->
- lists:map(fun({Number, Title}) ->
- [Number, " <a href = \"#", Number,
- "\">", Title, "</a><br/>\n"]
- end, Toc).
+ [format_toc1(T) || T <- Toc].
+
+format_toc1({Number, Title}) ->
+ [Number, " <a href = \"#", Number, "\">", Title, "</a><br/>\n"].
%%--Convert HTML ISO Latin 1 characters to ordinary characters----------
diff --git a/lib/docbuilder/src/docb_main.erl b/lib/docbuilder/src/docb_main.erl
index ef21f65557..87a1401a02 100644
--- a/lib/docbuilder/src/docb_main.erl
+++ b/lib/docbuilder/src/docb_main.erl
@@ -55,7 +55,7 @@ process(File, Opts) ->
%% If no target format is specified, assume HTML:
Tos = if
- Tos0==[] -> [html];
+ Tos0 =:= [] -> [html];
true -> Tos0
end,
@@ -327,12 +327,8 @@ verify(Tree) -> verify(Tree, [], 1).
verify({pcdata, Optional, _}, Path, Level) ->
verify_optional(Optional, Path, Level);
verify({Tag, Optional, Args}, Path, Level) when is_list(Args) ->
- case verify_optional(Optional, Path, Level) of
- true ->
- verify_list(Args, [Tag|Path], Level);
- false ->
- false
- end;
+ verify_optional(Optional, Path, Level)
+ andalso verify_list(Args, [Tag|Path], Level);
verify(Other, Path, Level) ->
verify_error(Other, Path, Level).
@@ -342,12 +338,7 @@ verify_error(X, Path, Level) ->
false.
verify_list([H|T], Path, Level) ->
- case verify(H, Path, Level) of
- true ->
- verify_list(T, Path, Level +1);
- false ->
- false
- end;
+ verify(H, Path, Level) andalso verify_list(T, Path, Level + 1);
verify_list([], _, _) ->
true.
@@ -419,7 +410,7 @@ edit1_list(_Tag, [], _Op) ->
%% Actual transformation of tree structure to desired format.
transform(From, To, Opts, File, Tree) ->
Filter = if
- To==html; To==kwic ->
+ To =:= html; To =:= kwic ->
list_to_atom("docb_tr_" ++ atom_to_list(From) ++
[$2|atom_to_list(To)]);
true ->
@@ -427,7 +418,7 @@ transform(From, To, Opts, File, Tree) ->
[$2|atom_to_list(To)])
end,
- case catch apply(Filter, transform, [File, Tree, Opts]) of
+ case catch Filter:transform(File, Tree, Opts) of
%% R5C
{'EXIT', {undef, [{Filter, transform, [File, Tree, Opts]}|_]}}->
@@ -459,9 +450,9 @@ transform(From, To, Opts, File, Tree) ->
finish_transform(Tree, File, Opts, Filter) ->
{Str, NewOpts} = pp(Tree, [], 1, Filter, Opts),
Extension =
- case catch apply(Filter, extension, [NewOpts]) of
+ case catch Filter:extension(NewOpts) of
{'EXIT', _} ->
- apply(Filter, extension, []);
+ Filter:extension();
Others ->
Others
end,
@@ -606,7 +597,7 @@ include_all(Fd) ->
eof ->
[];
ListOfChars ->
- lists:append(ListOfChars, include_all(Fd))
+ ListOfChars ++ include_all(Fd)
end.
extract(File, Fd, StartTag, StopTag, State) ->
diff --git a/lib/docbuilder/src/docb_pretty_format.erl b/lib/docbuilder/src/docb_pretty_format.erl
index 0c4fb0507b..25dcd8987b 100644
--- a/lib/docbuilder/src/docb_pretty_format.erl
+++ b/lib/docbuilder/src/docb_pretty_format.erl
@@ -47,7 +47,7 @@ term(Term) ->
%% the next line to need an "extra" indent!).
term([], Indent) ->
{Indent, [$[,$]]};
-term(L, Indent) when list(L) ->
+term(L, Indent) when is_list(L) ->
case is_string(L) of
true ->
{Indent, io_lib:write_string(L)};
@@ -59,7 +59,7 @@ term(L, Indent) when list(L) ->
write_simple_list(L, Indent)
end
end;
-term(T, Indent) when tuple(T) ->
+term(T, Indent) when is_tuple(T) ->
case complex_tuple(T) of
true ->
write_complex_tuple(T, Indent);
diff --git a/lib/docbuilder/src/docb_tr_application2html.erl b/lib/docbuilder/src/docb_tr_application2html.erl
index 4084cfe6ba..d8cb214d0a 100644
--- a/lib/docbuilder/src/docb_tr_application2html.erl
+++ b/lib/docbuilder/src/docb_tr_application2html.erl
@@ -119,8 +119,8 @@ transform(File, {application, _Attrs, [Header|Rest]}, Opts0) ->
case docb_main:parse1("fascicules", Opts0) of
{ok, Parse} ->
FascData = get_fasc_data(Parse),
- case lists:keysearch(File, 1, FascData) of
- {value, {_, _, "YES", _}} ->
+ case lists:keyfind(File, 1, FascData) of
+ {_, _, "YES", _} ->
OrigFile =
docb_util:outfile(File++"_frame",
".html", Opts0),
@@ -167,7 +167,7 @@ concat_files([File|Rest], Body, Opts) ->
%% Remove the reference manual header
[{Ref, [], [_Hdr| NewBody]}] = NewParse,
RefParse = [{Ref, [], NewBody}],
- lists:append(Body, concat_files(Rest, RefParse, Opts));
+ Body ++ concat_files(Rest, RefParse, Opts);
errors ->
errors
end;
@@ -216,7 +216,7 @@ make_toc([{lib, Attrs, More}|Rest]) ->
make_toc([{com, Attrs, More}|Rest]) ->
[{com, Attrs, More}|make_toc(Rest)];
make_toc([{_Tag, _Attrs, More}|Rest]) ->
- lists:append(make_toc(More), make_toc(Rest)).
+ make_toc(More) ++ make_toc(Rest).
rule([module|_], {_, [File], _}) ->
{"<small><a target=\"document\" href=\"" ++
@@ -280,9 +280,7 @@ get_fasc_data({fascicules, _, Fascs}) ->
Fascs).
get_avals(Atts) ->
- lists:map(fun(Tuple) ->
- element(3, Tuple) end,
- Atts).
+ [element(3, Tuple) || Tuple <- Atts].
get_pc_text([{pcdata, _, Text}]) ->
Text.
diff --git a/lib/docbuilder/src/docb_tr_cite2html.erl b/lib/docbuilder/src/docb_tr_cite2html.erl
index 4ecbfa4e91..77f1c4e636 100644
--- a/lib/docbuilder/src/docb_tr_cite2html.erl
+++ b/lib/docbuilder/src/docb_tr_cite2html.erl
@@ -39,24 +39,22 @@ purge_body([], _) ->
purge_body([{pcdata,_Attrs,_More}|Rest], CiteList) ->
purge_body(Rest, CiteList);
purge_body([{cite,[{"ID","CDATA",ID}],More}|Rest], CiteList) ->
- case lists:keysearch(ID, 1, CiteList) of
+ case lists:keyfind(ID, 1, CiteList) of
false ->
[{cite, [{"NAME","CDATA",ID}, {"ID","CDATA",ID}], More}|
purge_body(Rest, CiteList)];
- {value, {ID, Name, _Description, _Responsible}} ->
+ {ID, Name, _Description, _Responsible} ->
[{cite, [{"NAME","CDATA",Name}, {"ID","CDATA",ID}], More}|
purge_body(Rest, CiteList)];
- {value, {ID, Name, _Description}} ->
+ {ID, Name, _Description} ->
[{cite, [{"NAME","CDATA",Name}, {"ID","CDATA",ID}], More}|
purge_body(Rest, CiteList)]
end;
purge_body([{_Tag,_Attrs,More}|Rest], CiteList) ->
- lists:append(purge_body(More, CiteList),
- purge_body(Rest, CiteList)).
+ purge_body(More, CiteList) ++ purge_body(Rest, CiteList).
rule([header|_], _) ->
{drop, ""};
-
rule(_, _) ->
{drop, ""}.
@@ -91,19 +89,19 @@ rule([cite|_], {_,[Name,ID], [{citedef,[],[{pcdata,[],Def}]}]}, Opts) ->
false -> [];
Value -> Value
end,
- case lists:keysearch(ID, 1, CiteList) of
+ case lists:keyfind(ID, 1, CiteList) of
false ->
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ ID ++ "</strong></a></dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Def) ++ "\n</dd>\n"}, Opts};
- {value, {ID, Name, Description, _Responsible}} ->
+ {ID, Name, Description, _Responsible} ->
docb_util:message(warning,
"Global cite ~s overriding local", [ID]),
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ Name ++ "</strong></a></dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Description) ++ "\n</dd>\n"},
Opts};
- {value, {ID, Name, Description}} ->
+ {ID, Name, Description} ->
docb_util:message(warning,
"Global cite ~s overriding local", [ID]),
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
@@ -117,19 +115,19 @@ rule([cite|_], {_,[Name,ID],_}, Opts) ->
false -> [];
Value -> Value
end,
- case lists:keysearch(ID, 1, CiteList) of
+ case lists:keyfind(ID, 1, CiteList) of
false ->
docb_util:message(error,
"The cite ~s has no definition", [ID]),
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ ID ++ "</strong></a></dt>\n<dd>" ++
"??" ++ "\n</dd>\n"}, Opts};
- {value, {ID, Name, Description, _Responsible}} ->
+ {ID, Name, Description, _Responsible} ->
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ Name ++ "</strong></a></dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Description) ++ "\n</dd>\n"},
Opts};
- {value, {ID, Name, Description}} ->
+ {ID, Name, Description} ->
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ Name ++ "</strong></a></dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Description) ++ "\n</dd>\n"}, Opts}
diff --git a/lib/docbuilder/src/docb_tr_index2html.erl b/lib/docbuilder/src/docb_tr_index2html.erl
index bbf419f3ef..312342add2 100644
--- a/lib/docbuilder/src/docb_tr_index2html.erl
+++ b/lib/docbuilder/src/docb_tr_index2html.erl
@@ -73,9 +73,7 @@ prune_flat([], _) ->
[].
keep_pcdata(Trees) ->
- lists:filter(fun({pcdata, _, _}) -> true;
- (_) -> false
- end, Trees).
+ [T || T = {pcdata, _, _} <- Trees].
new_trees(FileFuncs) ->
Files0 = [{File, RefType} || {File, RefType, _} <- FileFuncs],
diff --git a/lib/docbuilder/src/docb_tr_part2html.erl b/lib/docbuilder/src/docb_tr_part2html.erl
index dd44c4a8df..30befe8432 100644
--- a/lib/docbuilder/src/docb_tr_part2html.erl
+++ b/lib/docbuilder/src/docb_tr_part2html.erl
@@ -93,8 +93,8 @@ transform(File, {part, _Attrs, [Header| Rest]}, Opts0) ->
case docb_main:parse1("fascicules", Opts0) of
{ok, Parse} ->
FascData = get_fasc_data(Parse),
- case lists:keysearch(File, 1, FascData) of
- {value, {_, _, "YES", _}} ->
+ case lists:keyfind(File, 1, FascData) of
+ {_, _, "YES", _} ->
OrigFile =
docb_util:outfile(File++"_frame",
".html", Opts0),
@@ -137,7 +137,7 @@ concat_files([File | Rest], Body, ChLevel, Opts, TP, TOpts, Ext) ->
case docb_main:parse1(File, Opts) of
{ok, Parse} ->
{TopTag, Attrs, [Header = {header, _, HeaderContents} | More]} = Parse,
- {value,{title,_,Title}} = lists:keysearch(title,1,HeaderContents),
+ {title,_,Title} = lists:keyfind(title,1,HeaderContents),
NewMore = [{section, [], [{title, [], Title}| More]}],
NewParse = {TopTag, Attrs, [Header| NewMore]},
if
@@ -156,9 +156,8 @@ concat_files([File | Rest], Body, ChLevel, Opts, TP, TOpts, Ext) ->
docb_html_util:number(NewParse,
integer_to_list(ChLevel), File),
{_, [], [_| NewBody]} = NumberTree,
- lists:append(Body,
- concat_files(Rest, NewBody, ChLevel+1, Opts,
- TP, TOpts, Ext));
+ Body ++ concat_files(Rest, NewBody, ChLevel+1, Opts,
+ TP, TOpts, Ext);
errors ->
throw({error,"Parse error when building chapter "++File})
end;
@@ -232,9 +231,7 @@ get_fasc_data({fascicules, _, Fascs}) ->
Fascs).
get_avals(Atts) ->
- lists:map(fun(Tuple) ->
- element(3, Tuple) end,
- Atts).
+ [element(3, Tuple) || Tuple <- Atts].
get_pc_text([{pcdata, _, Text}]) ->
Text.
diff --git a/lib/docbuilder/src/docb_tr_term2html.erl b/lib/docbuilder/src/docb_tr_term2html.erl
index 0a993cebb1..a3c4a5312a 100644
--- a/lib/docbuilder/src/docb_tr_term2html.erl
+++ b/lib/docbuilder/src/docb_tr_term2html.erl
@@ -39,24 +39,22 @@ purge_body([], _) ->
purge_body([{pcdata,_Attrs,_More}|Rest], TermList) ->
purge_body(Rest, TermList);
purge_body([{term,[{"ID","CDATA",ID}],More}|Rest], TermList) ->
- case lists:keysearch(ID, 1, TermList) of
+ case lists:keyfind(ID, 1, TermList) of
false ->
[{term,[{"NAME","CDATA",ID},{"ID","CDATA",ID}],More}|
purge_body(Rest, TermList)];
- {value, {ID, Name, _Description, _Responsible}} ->
+ {ID, Name, _Description, _Responsible} ->
[{term,[{"NAME","CDATA",Name},{"ID","CDATA",ID}],More}|
purge_body(Rest, TermList)];
- {value, {ID, Name, _Description}} ->
+ {ID, Name, _Description} ->
[{term,[{"NAME","CDATA",Name},{"ID","CDATA",ID}],More}|
purge_body(Rest, TermList)]
end;
purge_body([{_Tag,_Attrs,More}|Rest], TermList) ->
- lists:append(purge_body(More, TermList),
- purge_body(Rest, TermList)).
+ purge_body(More, TermList) ++ purge_body(Rest, TermList).
rule([header|_], _) ->
{drop, ""};
-
rule(_, _) ->
{drop, ""}.
@@ -82,19 +80,19 @@ rule([term|_], {_,[Name,ID],[{termdef,[],[{pcdata,[],Def}]}]}, Opts) ->
false -> [];
Value -> Value
end,
- case lists:keysearch(ID, 1, TermList) of
+ case lists:keyfind(ID, 1, TermList) of
false ->
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ ID ++ "</strong></a>\n</dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Def) ++ "\n</dd>\n"}, Opts};
- {value, {ID, Name, Description, _Responsible}} ->
+ {ID, Name, Description, _Responsible} ->
docb_util:message(warning,
"Global term ~s overriding local", [ID]),
{{drop,"\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ Name ++ "</strong></a></dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Description) ++ "\n</dd>\n"},
Opts};
- {value, {ID, Name, Description}} ->
+ {ID, Name, Description} ->
docb_util:message(warning,
"Global term ~s overriding local", [ID]),
{{drop, "\n<dt><a name=\"" ++ ID ++ "\">" ++
@@ -107,19 +105,19 @@ rule([term|_], {_,[Name,ID],_}, Opts) ->
false -> [];
Value -> Value
end,
- case lists:keysearch(ID, 1, TermList) of
+ case lists:keyfind(ID, 1, TermList) of
false ->
docb_util:message(error,
"The term ~s has no definition", [ID]),
{{drop, "\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ ID ++ "</strong></a></dt>\n<dd>" ++
"??" ++ "\n</dd>\n"}, Opts};
- {value, {ID, Name, Description, _Responsible}} ->
+ {ID, Name, Description, _Responsible} ->
{{drop, "\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ Name ++ "</strong></a></dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Description) ++ "\n</dd>\n"},
Opts};
- {value, {ID, Name, Description}} ->
+ {ID, Name, Description} ->
{{drop, "\n<dt><a name=\"" ++ ID ++ "\">" ++
"<strong>" ++ Name ++ "</strong></a></dt>\n<dd>" ++
docb_html_util:pcdata_to_html(Description) ++ "\n</dd>\n"}, Opts}
diff --git a/lib/docbuilder/src/docb_transform.erl b/lib/docbuilder/src/docb_transform.erl
index a432038adf..9c7561b07b 100644
--- a/lib/docbuilder/src/docb_transform.erl
+++ b/lib/docbuilder/src/docb_transform.erl
@@ -135,8 +135,8 @@ parse([Opt | _RawOpts], _Opts) ->
get_defs(Type, File, Opts) ->
Key = {defs,Type},
{PrevDefs, Opts2} =
- case lists:keysearch(Key, 1, Opts) of
- {value, {_, Defs0}} ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Defs0} ->
{Defs0, lists:keydelete(Key, 1, Opts)};
false ->
{[], Opts}
diff --git a/lib/docbuilder/src/docb_util.erl b/lib/docbuilder/src/docb_util.erl
index 59673ef3a4..9b2eec7733 100644
--- a/lib/docbuilder/src/docb_util.erl
+++ b/lib/docbuilder/src/docb_util.erl
@@ -61,7 +61,7 @@ html_snippet(What, Opts) ->
case lookup_option(html_mod, Opts) of
false -> "";
Module ->
- case catch apply(Module, What, []) of
+ case catch Module:What() of
HTML when is_list(HTML) ->
HTML;
{'EXIT', {undef, _}} ->
@@ -82,7 +82,7 @@ html_snippet(What, Arg, Opts) ->
case lookup_option(html_mod, Opts) of
false -> "";
Module ->
- case catch apply(Module, What, [Arg]) of
+ case catch Module:What(Arg) of
HTML when is_list(HTML) ->
HTML;
{'EXIT', {undef, _}} ->
@@ -106,8 +106,8 @@ html_snippet(What, Arg, Opts) ->
%% lookup_option(Opt, Opts) -> Value | false
lookup_option(Opt, Opts) ->
- case lists:keysearch(Opt, 1, Opts) of
- {value, {Opt,Value}} -> Value;
+ case lists:keyfind(Opt, 1, Opts) of
+ {Opt,Value} -> Value;
false -> false
end.
diff --git a/lib/docbuilder/src/docb_xmerl_tree_cb.erl b/lib/docbuilder/src/docb_xmerl_tree_cb.erl
index d57f55bff8..bc62069230 100644
--- a/lib/docbuilder/src/docb_xmerl_tree_cb.erl
+++ b/lib/docbuilder/src/docb_xmerl_tree_cb.erl
@@ -188,8 +188,8 @@ attrs(DTD, Tag, GivenAttrs) ->
merge_attrs(Tag, default_attrs(DTD, Tag), GivenAttrs).
merge_attrs(Tag, [{NameA, Type, DefVal}|Default], GivenAttrs) ->
- Val = case lists:keysearch(NameA, #xmlAttribute.name, GivenAttrs) of
- {value, #xmlAttribute{value=Val0}} -> Val0;
+ Val = case lists:keyfind(NameA, #xmlAttribute.name, GivenAttrs) of
+ #xmlAttribute{value=Val0} -> Val0;
false -> DefVal
end,
Attr = {attr_name(NameA), Type, attr_val(Type, Val)},
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index ee74e598d9..ce6ba3d923 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -29,7 +29,25 @@
<file>notes.xml</file>
</header>
<p>This document describes the changes made to the erl_docgen application.</p>
- <section><title>Erl_Docgen 0.2</title>
+ <section><title>erl_docgen 0.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The text within code examples (CODE and PRE tags) will not be
+ space normalized for man pages.
+ </p>
+ <p>
+ Own Id: OTP-8476
+ </p>
+ </item>
+ </list>
+ </section>
+
+ </section>
+
+ <section><title>erl_docgen 0.2</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/erl_docgen/priv/xsl/db_man.xsl b/lib/erl_docgen/priv/xsl/db_man.xsl
index a2b1e755e2..71c4a66707 100644
--- a/lib/erl_docgen/priv/xsl/db_man.xsl
+++ b/lib/erl_docgen/priv/xsl/db_man.xsl
@@ -336,6 +336,16 @@
<xsl:text>&gt;</xsl:text>
</xsl:template>
+ <!-- Do not noramlize any text within pre and code tags. -->
+ <xsl:template match="pre/text()">
+ <xsl:value-of select="."/>
+ </xsl:template>
+
+ <xsl:template match="code/text()">
+ <xsl:value-of select="."/>
+ </xsl:template>
+
+
<!-- Replace ' by \&' ans . by \&. -->
<xsl:template match="text()">
<xsl:variable name="startstring">
diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk
index dd74ae9ddd..5b14051034 100644
--- a/lib/erl_docgen/vsn.mk
+++ b/lib/erl_docgen/vsn.mk
@@ -17,6 +17,6 @@
# %CopyrightEnd%
#
-ERL_DOCGEN_VSN = 0.2
+ERL_DOCGEN_VSN = 0.2.1
TICKETS =
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 9083ae02b0..d7af7a1b67 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -581,7 +581,7 @@ ei_x_encode_empty_list(&amp;x);
<c><![CDATA[term]]></c> union, it is decoded, and the appropriate field
in <c><![CDATA[term->value]]></c> is set, and <c><![CDATA[*index]]></c> is
incremented by the term size.</p>
- <p>The function returns 0 on successful encoding, -1 on error,
+ <p>The function returns 0 on successful decoding, -1 on error,
and 1 if the term seems alright, but does not fit in the
<c><![CDATA[term]]></c> structure. If it returns 0, the <c><![CDATA[index]]></c>
will be incremented, and the <c><![CDATA[term]]></c> contains the
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index 14aec4a4d9..848da9cb23 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -30,6 +30,45 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 3.7</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>compact IEEE 754 double encoding in external binary
+ format for ei</p> <list><item><p>Implement the compact
+ IEEE 754 double encoding in external binary format for
+ ei. Encoding for ei now always produces the NEW_FLOAT_EXT
+ format. Decoding and term printing handle both the old
+ ERL_FLOAT_EXT encoding and the new NEW_FLOAT_EXT
+ encoding. </p></item> <item><p>Legacy erl_interface code
+ also handles the new encoding, but still produces the
+ ERL_FLOAT_EXT encoding by default.</p></item>
+ <item><p>Also enable the DFLAG_NEW_FLOATS distribution
+ flag.</p></item> <item><p>ei_get_type() will return
+ ERL_FLOAT_EXT regardless if the external format is
+ encoded with ERL_FLOAT_EXT or NEW_FLOAT_EXT for
+ doubles.</p></item> <item><p>Reduce the number of copies
+ of the code for encoding and decoding doubles throughout
+ ei and erl_interface by instead calling the ei encoding
+ and decoding functions wherever possible.</p></item>
+ <item><p>Restore commented-out float tests in
+ ei_decode_SUITE and ei_encode_SUITE in
+ lib/erl_interface/test. Modify them to make them match
+ the style of other tests in the same suites.</p></item>
+ </list> <p>These changes are based on an ei float patch
+ from Serge Aleynikov originally submitted against R12B-2
+ in July 2008 and reworked by Steve Vinoski May 2010.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8684</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 3.6.5</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index d1a697615a..466d84bb99 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1998-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%
*/
#ifndef EI_H
@@ -110,6 +110,7 @@
#define ERL_SMALL_INTEGER_EXT 'a'
#define ERL_INTEGER_EXT 'b'
#define ERL_FLOAT_EXT 'c'
+#define NEW_FLOAT_EXT 'F'
#define ERL_ATOM_EXT 'd'
#define ERL_REFERENCE_EXT 'e'
#define ERL_NEW_REFERENCE_EXT 'r'
@@ -718,11 +719,9 @@ int ei_x_encode_bignum(ei_x_buff *x, mpz_t obj);
#define EI_LONGLONG __int64
#define EI_ULONGLONG unsigned __int64
#else
-#ifndef VXWORKS
#define EI_LONGLONG long long
#define EI_ULONGLONG unsigned long long
#endif
-#endif
#ifndef VXWORKS
int ei_decode_longlong(const char *buf, int *index, EI_LONGLONG *p);
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index d2d0a7e7c1..b1b79aa0e5 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2000-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2000-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%
*/
/*
@@ -1323,7 +1323,8 @@ static int send_name_or_challenge(int fd, char *nodename,
put32be(s, (DFLAG_EXTENDED_REFERENCES
| DFLAG_EXTENDED_PIDS_PORTS
| DFLAG_FUN_TAGS
- | DFLAG_NEW_FUN_TAGS));
+ | DFLAG_NEW_FUN_TAGS
+ | DFLAG_NEW_FLOATS));
if (f_chall)
put32be(s, challenge);
memcpy(s, nodename, strlen(nodename));
@@ -1393,6 +1394,11 @@ static int recv_challenge(int fd, unsigned *challenge,
goto error;
}
+ if (!(*flags & DFLAG_NEW_FLOATS)) {
+ EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE peer cannot "
+ "handle binary float encoding");
+ goto error;
+ }
if (getpeername(fd, (struct sockaddr *) &sin, &sin_len) < 0) {
EI_TRACE_ERR0("recv_challenge","<- RECV_CHALLENGE can't get peername");
diff --git a/lib/erl_interface/src/connect/ei_connect_int.h b/lib/erl_interface/src/connect/ei_connect_int.h
index 9926f799df..3c42b49b82 100644
--- a/lib/erl_interface/src/connect/ei_connect_int.h
+++ b/lib/erl_interface/src/connect/ei_connect_int.h
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2001-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2001-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%
*/
/*
@@ -101,6 +101,7 @@ extern int h_errno;
#define DFLAG_FUN_TAGS 16
#define DFLAG_NEW_FUN_TAGS 0x80
#define DFLAG_EXTENDED_PIDS_PORTS 0x100
+#define DFLAG_NEW_FLOATS 0x800
ei_cnode *ei_fd_to_cnode(int fd);
int ei_distversion(int fd);
diff --git a/lib/erl_interface/src/decode/decode_double.c b/lib/erl_interface/src/decode/decode_double.c
index 66dbe474ec..ed6e39655e 100644
--- a/lib/erl_interface/src/decode/decode_double.c
+++ b/lib/erl_interface/src/decode/decode_double.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1998-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 <stdio.h>
@@ -26,14 +26,22 @@ int ei_decode_double(const char *buf, int *index, double *p)
{
const char *s = buf + *index;
const char *s0 = s;
- double f;
+ FloatExt f;
- if (get8(s) != ERL_FLOAT_EXT) return -1;
-
- if (sscanf(s, "%lf", &f) != 1) return -1;
+ switch (get8(s)) {
+ case ERL_FLOAT_EXT:
+ if (sscanf(s, "%lf", &f.d) != 1) return -1;
+ s += 31;
+ break;
+ case NEW_FLOAT_EXT:
+ /* IEEE 754 format */
+ f.val = get64be(s);
+ break;
+ default:
+ return -1;
+ }
- s += 31;
- if (p) *p = f;
+ if (p) *p = f.d;
*index += s-s0;
return 0;
}
diff --git a/lib/erl_interface/src/decode/decode_skip.c b/lib/erl_interface/src/decode/decode_skip.c
index 316b5bee98..f6c5d861ab 100644
--- a/lib/erl_interface/src/decode/decode_skip.c
+++ b/lib/erl_interface/src/decode/decode_skip.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2002-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2002-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 "eidef.h"
@@ -77,6 +77,7 @@ int ei_skip_term(const char* buf, int* index)
if (ei_decode_big(buf, index, NULL) < 0) return -1;
break;
case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
if (ei_decode_double(buf, index, NULL) < 0) return -1;
break;
case ERL_FUN_EXT:
diff --git a/lib/erl_interface/src/encode/encode_double.c b/lib/erl_interface/src/encode/encode_double.c
index 53f3d52ba6..148a49f73a 100644
--- a/lib/erl_interface/src/encode/encode_double.c
+++ b/lib/erl_interface/src/encode/encode_double.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1998-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 <stdio.h>
@@ -27,13 +27,13 @@ int ei_encode_double(char *buf, int *index, double p)
char *s = buf + *index;
char *s0 = s;
- if (!buf) s ++;
+ if (!buf)
+ s += 9;
else {
- put8(s,ERL_FLOAT_EXT);
- memset(s, 0, 31);
- sprintf(s, "%.20e", p);
+ /* IEEE 754 format */
+ put8(s, NEW_FLOAT_EXT);
+ put64be(s, ((FloatExt*)&p)->val);
}
- s += 31;
*index += s-s0;
diff --git a/lib/erl_interface/src/legacy/decode_term.c b/lib/erl_interface/src/legacy/decode_term.c
index ef29d6f57d..796cebdfef 100644
--- a/lib/erl_interface/src/legacy/decode_term.c
+++ b/lib/erl_interface/src/legacy/decode_term.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1998-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 "eidef.h"
@@ -59,6 +59,7 @@ int ei_decode_term(const char *buf, int *index, void *t)
return ei_decode_long(buf,index,NULL);
case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
return ei_decode_double(buf,index,NULL);
case ERL_ATOM_EXT:
diff --git a/lib/erl_interface/src/legacy/erl_marshal.c b/lib/erl_interface/src/legacy/erl_marshal.c
index 4b5f28178f..c57c552b90 100644
--- a/lib/erl_interface/src/legacy/erl_marshal.c
+++ b/lib/erl_interface/src/legacy/erl_marshal.c
@@ -1,19 +1,19 @@
/*
* %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
* 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%
*/
/*
@@ -102,6 +102,7 @@ void erl_init_marshal(void)
cmp_array[ERL_SMALL_INTEGER_EXT] = 1;
cmp_array[ERL_INTEGER_EXT] = 1;
cmp_array[ERL_FLOAT_EXT] = 1;
+ cmp_array[NEW_FLOAT_EXT] = 1;
cmp_array[ERL_SMALL_BIG_EXT] = 1;
cmp_array[ERL_LARGE_BIG_EXT] = 1;
cmp_array[ERL_ATOM_EXT] = 2;
@@ -124,6 +125,7 @@ void erl_init_marshal(void)
cmp_num_class[ERL_SMALL_INTEGER_EXT] = SMALL;
cmp_num_class[ERL_INTEGER_EXT] = SMALL;
cmp_num_class[ERL_FLOAT_EXT] = FLOAT;
+ cmp_num_class[NEW_FLOAT_EXT] = FLOAT;
cmp_num_class[ERL_SMALL_BIG_EXT] = BIG;
cmp_num_class[ERL_LARGE_BIG_EXT] = BIG;
init_cmp_num_class_p = 0;
@@ -1008,10 +1010,13 @@ static ETERM *erl_decode_it(unsigned char **ext)
return ep;
case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
ERL_TYPE(ep) = ERL_FLOAT;
- if (sscanf((char *) *ext, "%lf", &ff) != 1)
+ cp = (char *) *ext;
+ i = -1;
+ if (ei_decode_double(cp, &i, &ff) == -1)
goto failure;
- *ext += 31;
+ *ext += i;
ep->uval.fval.f = ff;
return ep;
@@ -1176,6 +1181,7 @@ unsigned char erl_ext_type(unsigned char *ext)
case ERL_LARGE_TUPLE_EXT:
return ERL_TUPLE;
case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
return ERL_FLOAT;
case ERL_BINARY_EXT:
return ERL_BINARY;
@@ -1218,6 +1224,7 @@ int erl_ext_size(unsigned char *t)
case ERL_BINARY_EXT:
case ERL_STRING_EXT:
case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
case ERL_SMALL_BIG_EXT:
case ERL_LARGE_BIG_EXT:
return 0;
@@ -1332,6 +1339,9 @@ static int jump(unsigned char **ext)
case ERL_FLOAT_EXT:
*ext += 31;
break;
+ case NEW_FLOAT_EXT:
+ *ext += 8;
+ break;
case ERL_BINARY_EXT:
i = (**ext << 24) | ((*ext)[1] << 16) |((*ext)[2] << 8) | (*ext)[3];
*ext += 4+i;
@@ -1696,12 +1706,15 @@ static int cmp_exe2(unsigned char **e1, unsigned char **e2)
}
return 0;
case ERL_FLOAT_EXT:
- if (sscanf((char *) *e1, "%lf", &ff1) != 1)
- return -1;
- *e1 += 31;
- if (sscanf((char *) *e2, "%lf", &ff2) != 1)
- return -1;
- *e2 += 31;
+ case NEW_FLOAT_EXT:
+ i = -1;
+ if (ei_decode_double((char *) *e1, &i, &ff1) != 0)
+ return -1;
+ *e1 += i;
+ j = -1;
+ if (ei_decode_double((char *) *e2, &j, &ff2) != 0)
+ return -1;
+ *e2 += j;
return cmp_floats(ff1,ff2);
case ERL_BINARY_EXT:
diff --git a/lib/erl_interface/src/misc/ei_decode_term.c b/lib/erl_interface/src/misc/ei_decode_term.c
index 7b95ff232f..ddcbfa5a9a 100644
--- a/lib/erl_interface/src/misc/ei_decode_term.c
+++ b/lib/erl_interface/src/misc/ei_decode_term.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2001-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2001-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%
*
@@ -25,9 +25,9 @@
#include "ei_decode_term.h"
#include "putget.h"
-/* Returns 1 if term is decoded, 0 if term is OK, but not decoded here
- and -1 if something is wrong.
- ONLY changes index if term is decoded (return value 1)! */
+/* Returns 0 on successful encoding, -1 on error, and 1 if the term seems
+ alright, but does not fit in the term structure. If it returns 0, the
+ index will be incremented, and the term contains the decoded term. */
int ei_decode_ei_term(const char* buf, int* index, ei_term* term)
{
@@ -46,11 +46,8 @@ int ei_decode_ei_term(const char* buf, int* index, ei_term* term)
term->value.i_val = get32be(s);
break;
case ERL_FLOAT_EXT:
- if (s[30]) return -1;
- if (sscanf(s, "%lf", &f) != 1) return -1;
- s += 31;
- term->value.d_val = f;
- break;
+ case NEW_FLOAT_EXT:
+ return ei_decode_double(buf, index, &term->value.d_val);
case ERL_ATOM_EXT:
len = get16be(s);
memcpy(term->value.atom_name, s, len);
diff --git a/lib/erl_interface/src/misc/ei_printterm.c b/lib/erl_interface/src/misc/ei_printterm.c
index 8d0eef5e79..98473f780e 100644
--- a/lib/erl_interface/src/misc/ei_printterm.c
+++ b/lib/erl_interface/src/misc/ei_printterm.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2001-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2001-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%
*
@@ -272,6 +272,7 @@ static int print_term(FILE* fp, ei_x_buff* x,
break;
case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
if (ei_decode_double(buf, index, &d) < 0) goto err;
ch_written += xprintf(fp, x, "%f", d);
break;
diff --git a/lib/erl_interface/src/misc/get_type.c b/lib/erl_interface/src/misc/get_type.c
index d67a6a80d3..2a680d0f94 100644
--- a/lib/erl_interface/src/misc/get_type.c
+++ b/lib/erl_interface/src/misc/get_type.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1998-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%
*
@@ -122,7 +122,12 @@ int ei_get_type_internal(const char *buf, const int *index,
case ERL_STRING_EXT:
*len = get16be(s);
break;
-
+
+ case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
+ *type = ERL_FLOAT_EXT;
+ break;
+
case ERL_LARGE_TUPLE_EXT:
case ERL_LIST_EXT:
case ERL_BINARY_EXT:
diff --git a/lib/erl_interface/src/misc/putget.h b/lib/erl_interface/src/misc/putget.h
index 98d9ebb64c..7a43de324b 100644
--- a/lib/erl_interface/src/misc/putget.h
+++ b/lib/erl_interface/src/misc/putget.h
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1998-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%
*
@@ -54,6 +54,18 @@
(s) += 4; \
} while (0)
+#define put64be(s,n) do { \
+ (s)[0] = ((n) >> 56) & 0xff; \
+ (s)[1] = ((n) >> 48) & 0xff; \
+ (s)[2] = ((n) >> 40) & 0xff; \
+ (s)[3] = ((n) >> 32) & 0xff; \
+ (s)[4] = ((n) >> 24) & 0xff; \
+ (s)[5] = ((n) >> 16) & 0xff; \
+ (s)[6] = ((n) >> 8) & 0xff; \
+ (s)[7] = (n) & 0xff; \
+ (s) += 8; \
+} while (0)
+
#define get8(s) \
((s) += 1, \
((unsigned char *)(s))[-1] & 0xff)
@@ -82,4 +94,20 @@
(((unsigned char *)(s))[-2] << 8) | \
((unsigned char *)(s))[-1]))
+#define get64be(s) \
+ ((s) += 8, \
+ (((EI_ULONGLONG)((unsigned char *)(s))[-8] << 56) | \
+ ((EI_ULONGLONG)((unsigned char *)(s))[-7] << 48) | \
+ ((EI_ULONGLONG)((unsigned char *)(s))[-6] << 40) | \
+ ((EI_ULONGLONG)((unsigned char *)(s))[-5] << 32) | \
+ ((EI_ULONGLONG)((unsigned char *)(s))[-4] << 24) | \
+ ((EI_ULONGLONG)((unsigned char *)(s))[-3] << 16) | \
+ ((EI_ULONGLONG)((unsigned char *)(s))[-2] << 8) | \
+ (EI_ULONGLONG)((unsigned char *)(s))[-1]))
+
+typedef union float_ext {
+ double d;
+ EI_ULONGLONG val;
+} FloatExt;
+
#endif /* _PUTGET_H */
diff --git a/lib/erl_interface/src/misc/show_msg.c b/lib/erl_interface/src/misc/show_msg.c
index 25865d6f8e..14bea5e01f 100644
--- a/lib/erl_interface/src/misc/show_msg.c
+++ b/lib/erl_interface/src/misc/show_msg.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 1998-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 1998-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%
*
@@ -400,6 +400,7 @@ static void show_term(const char *termbuf, int *index, FILE *stream)
break;
case ERL_FLOAT_EXT:
+ case NEW_FLOAT_EXT:
ei_decode_double(termbuf,index,&fnum);
fprintf(stream,"%f",fnum);
break;
diff --git a/lib/erl_interface/test/ei_decode_SUITE.erl b/lib/erl_interface/test/ei_decode_SUITE.erl
index ea528728ab..c6858b45ad 100644
--- a/lib/erl_interface/test/ei_decode_SUITE.erl
+++ b/lib/erl_interface/test/ei_decode_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
@@ -181,22 +181,9 @@ test_ei_decode_misc(suite) -> [];
test_ei_decode_misc(Config) when is_list(Config) ->
?line P = runner:start(?test_ei_decode_misc),
-% ?line <<131>> = get_binaries(P),
-
-% ?line {term,F} = get_term(P),
-% ?line match_float(F, 0.0),
-% ?line {term,F} = get_term(P),
-% ?line match_float(F, 0.0),
-
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, -1.0),
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, -1.0),
-
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, 1.0),
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, 1.0),
+ ?line send_term_as_binary(P,0.0),
+ ?line send_term_as_binary(P,-1.0),
+ ?line send_term_as_binary(P,1.0),
?line send_term_as_binary(P,false),
?line send_term_as_binary(P,true),
diff --git a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
index d81ea88437..5447e2deb3 100644
--- a/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
+++ b/lib/erl_interface/test/ei_decode_SUITE_data/ei_decode_test.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2004-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2004-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%
*/
@@ -515,11 +515,10 @@ TESTCASE(test_ei_decode_misc)
/*
EI_DECODE_0(decode_version);
*/
-/*
- EI_DECODE_2(decode_double, 0.0);
- EI_DECODE_2(decode_double, -1.0);
- EI_DECODE_2(decode_double, 1.0);
-*/
+ EI_DECODE_2(decode_double, 32, double, 0.0);
+ EI_DECODE_2(decode_double, 32, double, -1.0);
+ EI_DECODE_2(decode_double, 32, double, 1.0);
+
EI_DECODE_2(decode_boolean, 8, int, 0);
EI_DECODE_2(decode_boolean, 7, int, 1);
diff --git a/lib/erl_interface/test/ei_encode_SUITE.erl b/lib/erl_interface/test/ei_encode_SUITE.erl
index fb790eb7c3..6b9de4f093 100644
--- a/lib/erl_interface/test/ei_encode_SUITE.erl
+++ b/lib/erl_interface/test/ei_encode_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
@@ -181,20 +181,14 @@ test_ei_encode_misc(Config) when is_list(Config) ->
?line <<131>> = get_binaries(P),
-% ?line {term,F} = get_term(P),
-% ?line match_float(F, 0.0),
-% ?line {term,F} = get_term(P),
-% ?line match_float(F, 0.0),
+ ?line {<<70,_:8/binary>>,F0} = get_buf_and_term(P),
+ ?line true = match_float(F0, 0.0),
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, -1.0),
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, -1.0),
+ ?line {<<70,_:8/binary>>,Fn1} = get_buf_and_term(P),
+ ?line true = match_float(Fn1, -1.0),
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, 1.0),
-% ?line {term,F} = get_term(P),
-% ?line true = match_float(F, 1.0),
+ ?line {<<70,_:8/binary>>,Fp1} = get_buf_and_term(P),
+ ?line true = match_float(Fp1, 1.0),
?line {<<100,0,5,"false">>,false} = get_buf_and_term(P),
?line {<<100,0,4,"true">> ,true} = get_buf_and_term(P),
@@ -310,6 +304,8 @@ get_term(P) ->
%%
+match_float(F, Match) when is_float(F), is_float(Match), F == Match ->
+ true;
match_float(F, Match) when is_float(F), F > Match*0.99, F < Match*1.01 ->
true.
diff --git a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
index f8de0b7878..c373658152 100644
--- a/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
+++ b/lib/erl_interface/test/ei_encode_SUITE_data/ei_encode_test.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2004-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2004-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%
*/
@@ -350,13 +350,13 @@ TESTCASE(test_ei_encode_char)
TESTCASE(test_ei_encode_misc)
{
EI_ENCODE_0(encode_version);
-/*
+
EI_ENCODE_1(encode_double, 0.0);
EI_ENCODE_1(encode_double, -1.0);
EI_ENCODE_1(encode_double, 1.0);
-*/
+
EI_ENCODE_1(encode_boolean, 0) /* Only case it should be false */;
EI_ENCODE_1(encode_boolean, 1);
diff --git a/lib/erl_interface/test/ei_tmo_SUITE.erl b/lib/erl_interface/test/ei_tmo_SUITE.erl
index 0c211aa148..e7a2465421 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE.erl
+++ b/lib/erl_interface/test/ei_tmo_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -349,10 +349,12 @@ make_and_check_dummy() ->
-define(DFLAG_ATOM_CACHE,2).
-define(DFLAG_EXTENDED_REFERENCES,4).
-define(DFLAG_EXTENDED_PIDS_PORTS,16#100).
+-define(DFLAG_NEW_FLOATS,16#800).
-define(DFLAG_DIST_MONITOR,8).
%% From R9 and forward extended references is compulsory
--define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor ?DFLAG_EXTENDED_PIDS_PORTS)).
+%% From 14 and forward new float is compulsory
+-define(COMPULSORY_DFLAGS, (?DFLAG_EXTENDED_REFERENCES bor ?DFLAG_EXTENDED_PIDS_PORTS bor ?DFLAG_NEW_FLOATS)).
-define(shutdown(X), exit(X)).
-define(int16(X), [((X) bsr 8) band 16#ff, (X) band 16#ff]).
diff --git a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
index 2cc9af975d..db90b1810e 100644
--- a/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
+++ b/lib/erl_interface/test/ei_tmo_SUITE_data/ei_tmo_test.c
@@ -1,19 +1,19 @@
/*
* %CopyrightBegin%
- *
- * Copyright Ericsson AB 2003-2009. All Rights Reserved.
- *
+ *
+ * Copyright Ericsson AB 2003-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%
*/
@@ -65,7 +65,7 @@ static void debugf_open(int number)
{
char filename[1024];
sprintf(filename,"ei_tmo_test%d.debug",number);
-#if !defined(VXWORKS) && !defined(__WIN32__) && !defined(_OSE_)
+#if !defined(VXWORKS) && !defined(__WIN32__)
close(2);
#endif
debugfile = fopen(filename,"a");
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index 589b9e2f9c..672b1be55f 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1 +1 @@
-EI_VSN = 3.6.5
+EI_VSN = 3.7
diff --git a/lib/gs/contribs/bonk/bonk.erl b/lib/gs/contribs/bonk/bonk.erl
index 12d94f6c5e..79f01bf659 100644
--- a/lib/gs/contribs/bonk/bonk.erl
+++ b/lib/gs/contribs/bonk/bonk.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -33,10 +33,10 @@ run() ->
run([ColorMode]) -> % This is for the start script...
run(ColorMode);
-run(ColorMode) when atom(ColorMode) ->
+run(ColorMode) when is_atom(ColorMode) ->
GS = gs:start(),
- SoundPid=spawn_link(bonk_sound,start,[]),
- {H,M,S}=time(),
+ SoundPid = spawn_link(bonk_sound,start,[]),
+ {H,M,S} = time(),
random:seed(H*13,M*7,S*3),
{SqrPids, Bmps, Colors} = create_board(GS, ColorMode),
{ScoreL,_File} = get_highscore(),
@@ -96,7 +96,7 @@ init(SoundPid, SqrPids, Bmps, Colors) ->
game(SoundPid, SqrPids, Bmps, Colors, Scores) ->
receive
- {gs, _Square, buttonpress, SqrPid, [1 | _Rest]} when pid(SqrPid) ->
+ {gs, _Square, buttonpress, SqrPid, [1 | _Rest]} when is_pid(SqrPid) ->
SqrPid ! bonk,
game(SoundPid, SqrPids, Bmps, Colors, Scores);
{gs, _Id, buttonpress, _Data, [Butt | _Rest]} when Butt =/= 1 ->
@@ -224,11 +224,9 @@ update_score(SoundPid, SqrPids, Scores) ->
send_to_all([], _Msg) ->
true;
-
-send_to_all([Pid|Rest],Msg) when pid(Pid) ->
+send_to_all([Pid|Rest],Msg) when is_pid(Pid) ->
Pid ! Msg,
send_to_all(Rest,Msg);
-
send_to_all([_Else|Rest],Msg) ->
send_to_all(Rest,Msg).
@@ -460,7 +458,7 @@ update_scorelist(SoundPid, Scores) ->
{ScoreL,FileName} = get_highscore(),
New_scorelist=update_scorelist_2(ScoreL, Score, 0, SoundPid),
display_highscore(New_scorelist),
- case file:open(FileName, write) of
+ case file:open(FileName, [write]) of
{error,_} ->
true;
{ok,FD} ->
@@ -559,7 +557,7 @@ display_about() ->
{activebg, BGColor}]),
gs:create(text, aboutText, aboutCan, [{width, Wid-30}, {coords, [{15, 0}]},
{fg, TextColor}, {justify, center}]),
- case file:open(lists:append(bonk_dir(),"bonk.txt"), read) of
+ case file:open(lists:append(bonk_dir(),"bonk.txt"), [read]) of
{ok, Fd} ->
write_text(Fd, "", io:get_line(Fd, "")),
file:close(Fd);
diff --git a/lib/gs/contribs/othello/othello_adt.erl b/lib/gs/contribs/othello/othello_adt.erl
index d1d3ec950b..fb60c30b89 100644
--- a/lib/gs/contribs/othello/othello_adt.erl
+++ b/lib/gs/contribs/othello/othello_adt.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -375,29 +375,29 @@ is_good(Colour,H,Board) ->
false.
is_good_0(_,_,false,_) -> false;
-is_good_0(_,H,D,_) when integer(H), integer(D), H+D<0 -> false;
-is_good_0(_,H,D,_) when integer(H), integer(D), H+D>63 -> false;
-is_good_0(black,H,D,Board) when integer(H), integer(D) ->
+is_good_0(_,H,D,_) when is_integer(H), is_integer(D), H+D<0 -> false;
+is_good_0(_,H,D,_) when is_integer(H), is_integer(D), H+D>63 -> false;
+is_good_0(black,H,D,Board) when is_integer(H), is_integer(D) ->
case element((H+D)+1,Board) of
white -> is_good_1(black,H+D,dir(H+D,D),Board);
_ -> false
end;
-is_good_0(white,H,D,Board) when integer(H), integer(D) ->
+is_good_0(white,H,D,Board) when is_integer(H), is_integer(D) ->
case element((H+D)+1,Board) of
black -> is_good_1(white,H+D,dir(H+D,D),Board);
_ -> false
end.
is_good_1(_,_,false,_) -> false;
-is_good_1(_,H,D,_) when integer(H), integer(D), H+D<0 -> false;
-is_good_1(_,H,D,_) when integer(H), integer(D), H+D>63 -> false;
-is_good_1(black,H,D,Board) when integer(H), integer(D) ->
+is_good_1(_,H,D,_) when is_integer(H), is_integer(D), H+D<0 -> false;
+is_good_1(_,H,D,_) when is_integer(H), is_integer(D), H+D>63 -> false;
+is_good_1(black,H,D,Board) when is_integer(H), is_integer(D) ->
case element((H+D)+1,Board) of
white -> is_good_1(black,H+D,dir(H+D,D),Board);
black -> throw(true);
_ -> false
end;
-is_good_1(white,H,D,Board) when integer(H), integer(D) ->
+is_good_1(white,H,D,Board) when is_integer(H), is_integer(D) ->
case element((H+D)+1,Board) of
black -> is_good_1(white,H+D,dir(H+D,D),Board);
white -> throw(true);
@@ -429,15 +429,15 @@ turn(Colour,H,D,Board) ->
Board
end.
-turn_0(_,H,D,B) when integer(H), integer(D), H+D<0 -> B;
-turn_0(_,H,D,B) when integer(H), integer(D), H+D>63 -> B;
-turn_0(black,H,D,Board) when integer(H), integer(D) ->
+turn_0(_,H,D,B) when is_integer(H), is_integer(D), H+D<0 -> B;
+turn_0(_,H,D,B) when is_integer(H), is_integer(D), H+D>63 -> B;
+turn_0(black,H,D,Board) when is_integer(H), is_integer(D) ->
E = H+D,
case element(E+1,Board) of
white -> turn_0(black,H+D,D,swap(black,E,Board));
_ -> Board
end;
-turn_0(white,H,D,Board) when integer(H), integer(D) ->
+turn_0(white,H,D,Board) when is_integer(H), is_integer(D) ->
E = H+D,
case element(E+1,Board) of
black -> turn_0(white,H+D,D,swap(white,E,Board));
@@ -450,7 +450,7 @@ turn_0(white,H,D,Board) when integer(H), integer(D) ->
%% Neighbours are not changed !!
%%-------------------------------------------------------
-swap(Colour,Pos,Board) when integer(Pos) ->
+swap(Colour,Pos,Board) when is_integer(Pos) ->
setelement(Pos+1,Board,Colour).
score(Pos) -> score1({col(Pos),row(Pos)}).
diff --git a/lib/gs/doc/src/notes.xml b/lib/gs/doc/src/notes.xml
index 8e4032ccc1..352c335e23 100644
--- a/lib/gs/doc/src/notes.xml
+++ b/lib/gs/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>GS Release Notes</title>
@@ -47,7 +47,22 @@
</section>
</section>
- <section><title>GS 1.5.11</title>
+ <section><title>GS 1.5.12</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Warnings due to new autoimported BIFs removed</p>
+ <p>
+ Own Id: OTP-8674 Aux Id: OTP-8579 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>GS 1.5.11</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/gs/src/tool_utils.erl b/lib/gs/src/tool_utils.erl
index 697dd07151..b07e92c4f0 100644
--- a/lib/gs/src/tool_utils.erl
+++ b/lib/gs/src/tool_utils.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
@@ -224,11 +224,11 @@ help_win(Type, Parent, Strings) ->
{Wbtn0,Hbtn0} = gs:read(Lbl, {font_wh,{Font,"Cancel"}}),
%% Compute size of the objects and adjust the graphics accordingly
- Wbtn = max(Wbtn0+10, ?Wbtn),
- Hbtn = max(Hbtn0+10, ?Hbtn),
- Hent = max(Hent0+10, ?Hent),
- Wlbl = max(Wlbl0, max(Nbtn*Wbtn+(Nbtn-1)*?PAD, ?Wlbl)),
- Hlbl = max(Hlbl0, ?Hlbl),
+ Wbtn = erlang:max(Wbtn0+10, ?Wbtn),
+ Hbtn = erlang:max(Hbtn0+10, ?Hbtn),
+ Hent = erlang:max(Hent0+10, ?Hent),
+ Wlbl = erlang:max(Wlbl0, erlang:max(Nbtn*Wbtn+(Nbtn-1)*?PAD, ?Wlbl)),
+ Hlbl = erlang:max(Hlbl0, ?Hlbl),
Wwin = ?PAD+Wlbl+?PAD,
@@ -297,9 +297,6 @@ data("Yes") -> {helpwin,yes};
data("No") -> {helpwin,no};
data("Cancel") -> {helpwin,cancel}.
-max(X, Y) when X>Y -> X;
-max(_X, Y) -> Y.
-
get_coords(Parent, W, H) ->
case gs:read(Parent, x) of
X when is_integer(X) ->
diff --git a/lib/gs/vsn.mk b/lib/gs/vsn.mk
index 701d0e178d..35976d556a 100644
--- a/lib/gs/vsn.mk
+++ b/lib/gs/vsn.mk
@@ -1,2 +1,2 @@
-GS_VSN = 1.5.11
+GS_VSN = 1.5.12
diff --git a/lib/hipe/cerl/cerl_closurean.erl b/lib/hipe/cerl/cerl_closurean.erl
index 12771668ac..021acd5b35 100644
--- a/lib/hipe/cerl/cerl_closurean.erl
+++ b/lib/hipe/cerl/cerl_closurean.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
%% =====================================================================
@@ -808,7 +808,7 @@ take_work({Queue0, Set0}) ->
is_escape_op(match_fail, 1) -> false;
is_escape_op(F, A) when is_atom(F), is_integer(A) -> true.
--spec is_escape_op(module(), atom(), arity()) -> boolean().
+-spec is_escape_op(atom(), atom(), arity()) -> boolean().
is_escape_op(erlang, error, 1) -> false;
is_escape_op(erlang, error, 2) -> false;
@@ -825,7 +825,7 @@ is_escape_op(M, F, A) when is_atom(M), is_atom(F), is_integer(A) -> true.
is_literal_op(match_fail, 1) -> true;
is_literal_op(F, A) when is_atom(F), is_integer(A) -> false.
--spec is_literal_op(module(), atom(), arity()) -> boolean().
+-spec is_literal_op(atom(), atom(), arity()) -> boolean().
is_literal_op(erlang, '+', 2) -> true;
is_literal_op(erlang, '-', 2) -> true;
diff --git a/lib/hipe/cerl/cerl_messagean.erl b/lib/hipe/cerl/cerl_messagean.erl
index 0753376e7d..6dd93adaa3 100644
--- a/lib/hipe/cerl/cerl_messagean.erl
+++ b/lib/hipe/cerl/cerl_messagean.erl
@@ -1,19 +1,19 @@
%% =====================================================================
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
%% Message analysis of Core Erlang programs.
@@ -1043,7 +1043,7 @@ get_deps(L, Dep) ->
%% is_escape_op(_F, _A) -> [].
--spec is_escape_op(module(), atom(), arity()) -> [arity()].
+-spec is_escape_op(atom(), atom(), arity()) -> [arity()].
is_escape_op(erlang, '!', 2) -> [2];
is_escape_op(erlang, send, 2) -> [2];
@@ -1064,7 +1064,7 @@ is_escape_op(_M, _F, _A) -> [].
is_imm_op(match_fail, 1) -> true;
is_imm_op(_, _) -> false.
--spec is_imm_op(module(), atom(), arity()) -> boolean().
+-spec is_imm_op(atom(), atom(), arity()) -> boolean().
is_imm_op(erlang, self, 0) -> true;
is_imm_op(erlang, '=:=', 2) -> true;
@@ -1102,4 +1102,4 @@ is_imm_op(erlang, throw, 1) -> true;
is_imm_op(erlang, exit, 1) -> true;
is_imm_op(erlang, error, 1) -> true;
is_imm_op(erlang, error, 2) -> true;
-is_imm_op(_, _, _) -> false.
+is_imm_op(_M, _F, _A) -> false.
diff --git a/lib/hipe/cerl/erl_bif_types.erl b/lib/hipe/cerl/erl_bif_types.erl
index 213f98dcc0..9df3dedb45 100644
--- a/lib/hipe/cerl/erl_bif_types.erl
+++ b/lib/hipe/cerl/erl_bif_types.erl
@@ -143,6 +143,51 @@ type(M, F, A) ->
-spec type(atom(), atom(), arity(), [erl_types:erl_type()]) -> erl_types:erl_type().
+%%-- binary -------------------------------------------------------------------
+type(binary, at, 2, Xs) ->
+ strict(arg_types(binary, at, 2), Xs, fun(_) -> t_integer() end);
+type(binary, bin_to_list, Arity, Xs) when 1 =< Arity, Arity =< 3 ->
+ strict(arg_types(binary, bin_to_list, Arity), Xs,
+ fun(_) -> t_list(t_integer()) end);
+type(binary, compile_pattern, 1, Xs) ->
+ strict(arg_types(binary, compile_pattern, 1), Xs,
+ fun(_) -> t_tuple([t_atom(bm),t_binary()]) end);
+type(binary, copy, Arity, Xs) when Arity =:= 1; Arity =:= 2 ->
+ strict(arg_types(binary, copy, Arity), Xs,
+ fun(_) -> t_binary() end);
+type(binary, decode_unsigned, Arity, Xs) when Arity =:= 1; Arity =:= 2 ->
+ strict(arg_types(binary, decode_unsigned, Arity), Xs,
+ fun(_) -> t_non_neg_integer() end);
+type(binary, encode_unsigned, Arity, Xs) when Arity =:= 1; Arity =:= 2 ->
+ strict(arg_types(binary, encode_unsigned, Arity), Xs,
+ fun(_) -> t_binary() end);
+type(binary, first, 1, Xs) ->
+ strict(arg_types(binary, first, 1), Xs, fun(_) -> t_non_neg_integer() end);
+type(binary, last, 1, Xs) ->
+ strict(arg_types(binary, last, 1), Xs, fun(_) -> t_non_neg_integer() end);
+type(binary, list_to_bin, 1, Xs) ->
+ type(erlang, list_to_binary, 1, Xs);
+type(binary, longest_common_prefix, 1, Xs) ->
+ strict(arg_types(binary, longest_common_prefix, 1), Xs,
+ fun(_) -> t_integer() end);
+type(binary, longest_common_suffix, 1, Xs) ->
+ strict(arg_types(binary, longest_common_suffix, 1), Xs,
+ fun(_) -> t_integer() end);
+type(binary, match, Arity, Xs) when Arity =:= 2; Arity =:= 3 ->
+ strict(arg_types(binary, match, Arity), Xs,
+ fun(_) ->
+ t_sup(t_atom('nomatch'), t_binary_canonical_part())
+ end);
+type(binary, matches, Arity, Xs) when Arity =:= 2; Arity =:= 3 ->
+ strict(arg_types(binary, matches, Arity), Xs,
+ fun(_) -> t_list(t_binary_canonical_part()) end);
+type(binary, part, 2, Xs) ->
+ type(erlang, binary_part, 2, Xs);
+type(binary, part, 3, Xs) ->
+ type(erlang, binary_part, 3, Xs);
+type(binary, referenced_byte_size, 1, Xs) ->
+ strict(arg_types(binary, referenced_byte_size, 1), Xs,
+ fun(_) -> t_non_neg_integer() end);
%%-- code ---------------------------------------------------------------------
type(code, add_path, 1, Xs) ->
strict(arg_types(code, add_path, 1), Xs,
@@ -665,6 +710,14 @@ type(erlang, 'bnot', 1, Xs) ->
%% strict(arg_types(erlang, 'bnot', 1), Xs, fun (_) -> t_integer() end);
type(erlang, abs, 1, Xs) ->
strict(arg_types(erlang, abs, 1), Xs, fun ([X]) -> X end);
+type(erlang, adler32, 1, Xs) ->
+ strict(arg_types(erlang, adler32, 1), Xs, fun (_) -> t_adler32() end);
+type(erlang, adler32, 2, Xs) ->
+ strict(arg_types(erlang, adler32, 2), Xs, fun (_) -> t_adler32() end);
+type(erlang, adler32_combine, 3, Xs) ->
+ strict(arg_types(erlang, adler32_combine, 3), Xs,
+ fun (_) -> t_adler32() end);
+type(erlang, append, 2, Xs) -> type(erlang, '++', 2, Xs); % alias
type(erlang, append_element, 2, Xs) ->
strict(arg_types(erlang, append_element, 2), Xs, fun (_) -> t_tuple() end);
type(erlang, apply, 2, Xs) ->
@@ -683,6 +736,10 @@ type(erlang, atom_to_binary, 2, Xs) ->
strict(arg_types(erlang, atom_to_binary, 2), Xs, fun (_) -> t_binary() end);
type(erlang, atom_to_list, 1, Xs) ->
strict(arg_types(erlang, atom_to_list, 1), Xs, fun (_) -> t_string() end);
+type(erlang, binary_part, 2, Xs) ->
+ strict(arg_types(erlang, binary_part, 2), Xs, fun (_) -> t_binary() end);
+type(erlang, binary_part, 3, Xs) ->
+ strict(arg_types(erlang, binary_part, 3), Xs, fun (_) -> t_binary() end);
type(erlang, binary_to_atom, 2, Xs) ->
strict(arg_types(erlang, binary_to_atom, 2), Xs, fun (_) -> t_atom() end);
type(erlang, binary_to_existing_atom, 2, Xs) ->
@@ -726,11 +783,11 @@ type(erlang, check_process_code, 2, Xs) ->
type(erlang, concat_binary, 1, Xs) ->
strict(arg_types(erlang, concat_binary, 1), Xs, fun (_) -> t_binary() end);
type(erlang, crc32, 1, Xs) ->
- strict(arg_types(erlang, crc32, 1), Xs, fun (_) -> t_integer() end);
+ strict(arg_types(erlang, crc32, 1), Xs, fun (_) -> t_crc32() end);
type(erlang, crc32, 2, Xs) ->
- strict(arg_types(erlang, crc32, 2), Xs, fun (_) -> t_integer() end);
+ strict(arg_types(erlang, crc32, 2), Xs, fun (_) -> t_crc32() end);
type(erlang, crc32_combine, 3, Xs) ->
- strict(arg_types(erlang, crc32_combine, 3), Xs, fun (_) -> t_integer() end);
+ strict(arg_types(erlang, crc32_combine, 3), Xs, fun (_) -> t_crc32() end);
type(erlang, date, 0, _) ->
t_date();
type(erlang, decode_packet, 3, Xs) ->
@@ -752,6 +809,10 @@ type(erlang, demonitor, 2, Xs) ->
type(erlang, disconnect_node, 1, Xs) ->
strict(arg_types(erlang, disconnect_node, 1), Xs, fun (_) -> t_boolean() end);
type(erlang, display, 1, _) -> t_atom('true');
+type(erlang, display_string, 1, Xs) ->
+ strict(arg_types(erlang, display_string, 1), Xs, fun(_) -> t_atom('true') end);
+type(erlang, display_nl, 0, _) ->
+ t_atom('true');
type(erlang, dist_exit, 3, Xs) ->
strict(arg_types(erlang, dist_exit, 3), Xs, fun (_) -> t_atom('true') end);
type(erlang, element, 2, Xs) ->
@@ -802,6 +863,8 @@ type(erlang, fun_to_list, 1, Xs) ->
type(erlang, garbage_collect, 0, _) -> t_atom('true');
type(erlang, garbage_collect, 1, Xs) ->
strict(arg_types(erlang, garbage_collect, 1), Xs, fun (_) -> t_boolean() end);
+type(erlang, garbage_collect_message_area, 0, _) ->
+ t_boolean();
type(erlang, get, 0, _) -> t_list(t_tuple(2));
type(erlang, get, 1, _) -> t_any(); % | t_atom('undefined')
type(erlang, get_cookie, 0, _) -> t_atom(); % | t_atom('nocookie')
@@ -1066,11 +1129,19 @@ type(erlang, list_to_pid, 1, Xs) ->
strict(arg_types(erlang, list_to_pid, 1), Xs, fun (_) -> t_pid() end);
type(erlang, list_to_tuple, 1, Xs) ->
strict(arg_types(erlang, list_to_tuple, 1), Xs, fun (_) -> t_tuple() end);
-type(erlang, loaded, 0, _) ->
- t_list(t_atom());
type(erlang, load_module, 2, Xs) ->
strict(arg_types(erlang, load_module, 2), Xs,
fun ([Mod,_Bin]) -> t_code_load_return(Mod) end);
+type(erlang, load_nif, 2, Xs) ->
+ strict(arg_types(erlang, load_nif, 2), Xs,
+ fun (_) ->
+ Reason = t_atoms(['load_failed', 'bad_lib', 'load',
+ 'reload', 'upgrade', 'old_code']),
+ RsnPair = t_tuple([Reason, t_string()]),
+ t_sup(t_atom('ok'), t_tuple([t_atom('error'), RsnPair]))
+ end);
+type(erlang, loaded, 0, _) ->
+ t_list(t_atom());
type(erlang, localtime, 0, Xs) ->
type(erlang, universaltime, 0, Xs); % same
type(erlang, localtime_to_universaltime, 1, Xs) ->
@@ -1147,6 +1218,10 @@ type(erlang, monitor_node, 2, Xs) ->
type(erlang, monitor_node, 3, Xs) ->
strict(arg_types(erlang, monitor_node, 3), Xs,
fun (_) -> t_atom('true') end);
+type(erlang, nif_error, 1, _) ->
+ t_any();
+type(erlang, nif_error, 2, Xs) ->
+ strict(arg_types(erlang, nif_error, 2), Xs, fun (_) -> t_any() end);
type(erlang, node, 0, _) -> t_node();
type(erlang, node, 1, Xs) ->
strict(arg_types(erlang, node, 1), Xs, fun (_) -> t_node() end);
@@ -1165,8 +1240,8 @@ type(erlang, phash2, 2, Xs) ->
strict(arg_types(erlang, phash2, 2), Xs, fun (_) -> t_non_neg_integer() end);
type(erlang, pid_to_list, 1, Xs) ->
strict(arg_types(erlang, pid_to_list, 1), Xs, fun (_) -> t_string() end);
-type(erlang, port_call, 3, Xs) ->
- strict(arg_types(erlang, port_call, 3), Xs, fun (_) -> t_any() end);
+type(erlang, port_call, Arity, Xs) when Arity =:= 2; Arity =:= 3 ->
+ strict(arg_types(erlang, port_call, Arity), Xs, fun (_) -> t_any() end);
type(erlang, port_close, 1, Xs) ->
strict(arg_types(erlang, port_close, 1), Xs,
fun (_) -> t_atom('true') end);
@@ -1495,6 +1570,7 @@ type(erlang, statistics, 1, Xs) ->
T_statistics_1
end
end);
+type(erlang, subtract, 2, Xs) -> type(erlang, '--', 2, Xs); % alias
type(erlang, suspend_process, 1, Xs) ->
strict(arg_types(erlang, suspend_process, 1), Xs,
fun (_) -> t_atom('true') end);
@@ -1587,7 +1663,7 @@ type(erlang, system_info, 1, Xs) ->
t_sup([t_atom('false'),
t_list(t_tuple([t_atom(), t_any()]))]);
['endian'] ->
- t_sup([t_atom('big'), t_atom('little')]);
+ t_endian();
['fullsweep_after'] ->
t_tuple([t_atom('fullsweep_after'), t_non_neg_integer()]);
['garbage_collection'] ->
@@ -1599,9 +1675,8 @@ type(erlang, system_info, 1, Xs) ->
['heap_type'] ->
t_sup([t_atom('private'), t_atom('hybrid')]);
['hipe_architecture'] ->
- t_sup([t_atom('amd64'), t_atom('arm'),
- t_atom('powerpc'), t_atom('undefined'),
- t_atom('ultrasparc'), t_atom('x86')]);
+ t_atoms(['amd64', 'arm', 'powerpc', 'ppc64',
+ 'undefined', 'ultrasparc', 'x86']);
['info'] ->
t_binary();
['internal_cpu_topology'] -> %% Undocumented internal feature
@@ -1777,11 +1852,21 @@ type(erts_debug, disassemble, 1, Xs) ->
fun (_) -> t_sup([t_atom('false'),
t_atom('undef'),
t_tuple([t_integer(), t_binary(), t_mfa()])]) end);
+type(erts_debug, display, 1, _) ->
+ t_string();
type(erts_debug, dist_ext_to_term, 2, Xs) ->
strict(arg_types(erts_debug, dist_ext_to_term, 2), Xs,
fun (_) -> t_any() end);
+type(erts_debug, dump_monitors, 1, Xs) ->
+ strict(arg_types(erts_debug, dump_monitors, 1), Xs,
+ fun(_) -> t_atom('true') end);
+type(erts_debug, dump_links, 1, Xs) ->
+ strict(arg_types(erts_debug, dump_links, 1), Xs,
+ fun(_) -> t_atom('true') end);
type(erts_debug, flat_size, 1, Xs) ->
strict(arg_types(erts_debug, flat_size, 1), Xs, fun (_) -> t_integer() end);
+type(erts_debug, get_internal_state, 1, _) ->
+ t_any();
type(erts_debug, lock_counters, 1, Xs) ->
strict(arg_types(erts_debug, lock_counters, 1), Xs,
fun ([Arg]) ->
@@ -1802,6 +1887,8 @@ type(erts_debug, lock_counters, 1, Xs) ->
end);
type(erts_debug, same, 2, Xs) ->
strict(arg_types(erts_debug, same, 2), Xs, fun (_) -> t_boolean() end);
+type(erts_debug, set_internal_state, 2, _) ->
+ t_any();
%%-- ets ----------------------------------------------------------------------
type(ets, all, 0, _) ->
t_list(t_tab());
@@ -2091,7 +2178,7 @@ type(hipe_bifs, set_native_address, 3, Xs) ->
strict(arg_types(hipe_bifs, set_native_address, 3), Xs,
fun (_) -> t_nil() end);
type(hipe_bifs, system_crc, 1, Xs) ->
- strict(arg_types(hipe_bifs, system_crc, 1), Xs, fun (_) -> t_integer() end);
+ strict(arg_types(hipe_bifs, system_crc, 1), Xs, fun (_) -> t_crc32() end);
type(hipe_bifs, term_to_word, 1, Xs) ->
strict(arg_types(hipe_bifs, term_to_word, 1), Xs,
fun (_) -> t_integer() end);
@@ -3186,6 +3273,53 @@ arith(Op, X1, X2) ->
-spec arg_types(atom(), atom(), arity()) -> [erl_types:erl_type()] | 'unknown'.
+%%------- binary --------------------------------------------------------------
+arg_types(binary, at, 2) ->
+ [t_binary(), t_non_neg_integer()];
+arg_types(binary, bin_to_list, 1) ->
+ [t_binary()];
+arg_types(binary, bin_to_list, 2) ->
+ [t_binary(), t_binary_part()];
+arg_types(binary, bin_to_list, 3) ->
+ [t_binary(), t_integer(), t_non_neg_integer()];
+arg_types(binary, compile_pattern, 1) ->
+ [t_sup(t_binary(), t_list(t_binary()))];
+arg_types(binary, copy, 1) ->
+ [t_binary()];
+arg_types(binary, copy, 2) ->
+ [t_binary(), t_non_neg_integer()];
+arg_types(binary, decode_unsigned, 1) ->
+ [t_binary()];
+arg_types(binary, decode_unsigned, 2) ->
+ [t_binary(), t_endian()];
+arg_types(binary, encode_unsigned, 1) ->
+ [t_non_neg_integer()];
+arg_types(binary, encode_unsigned, 2) ->
+ [t_non_neg_integer(), t_endian()];
+arg_types(binary, first, 1) ->
+ [t_binary()];
+arg_types(binary, last, 1) ->
+ [t_binary()];
+arg_types(binary, list_to_bin, 1) ->
+ arg_types(erlang, list_to_binary, 1);
+arg_types(binary, longest_common_prefix, 1) ->
+ [t_list(t_binary())];
+arg_types(binary, longest_common_suffix, 1) ->
+ [t_list(t_binary())];
+arg_types(binary, match, 2) ->
+ [t_binary(), t_binary_pattern()];
+arg_types(binary, match, 3) ->
+ [t_binary(), t_binary_pattern(), t_binary_options()];
+arg_types(binary, matches, 2) ->
+ [t_binary(), t_binary_pattern()];
+arg_types(binary, matches, 3) ->
+ [t_binary(), t_binary_pattern(), t_binary_options()];
+arg_types(binary, part, 2) ->
+ arg_types(erlang, binary_part, 2);
+arg_types(binary, part, 3) ->
+ arg_types(erlang, binary_part, 3);
+arg_types(binary, referenced_byte_size, 1) ->
+ [t_binary()];
%%------- code ----------------------------------------------------------------
arg_types(code, add_path, 1) ->
[t_string()];
@@ -3367,6 +3501,14 @@ arg_types(erlang, 'bnot', 1) ->
[t_integer()];
arg_types(erlang, abs, 1) ->
[t_number()];
+arg_types(erlang, adler32, 1) ->
+ [t_iodata()];
+arg_types(erlang, adler32, 2) ->
+ [t_adler32(), t_iodata()];
+arg_types(erlang, adler32_combine, 3) ->
+ [t_adler32(), t_adler32(), t_non_neg_integer()];
+arg_types(erlang, append, 2) ->
+ arg_types(erlang, '++', 2);
arg_types(erlang, append_element, 2) ->
[t_tuple(), t_any()];
arg_types(erlang, apply, 2) ->
@@ -3380,6 +3522,10 @@ arg_types(erlang, atom_to_binary, 2) ->
[t_atom(), t_encoding_a2b()];
arg_types(erlang, atom_to_list, 1) ->
[t_atom()];
+arg_types(erlang, binary_part, 2) ->
+ [t_binary(), t_tuple([t_integer(),t_integer()])];
+arg_types(erlang, binary_part, 3) ->
+ [t_binary(), t_integer(), t_integer()];
arg_types(erlang, binary_to_atom, 2) ->
[t_binary(), t_encoding_a2b()];
arg_types(erlang, binary_to_existing_atom, 2) ->
@@ -3415,9 +3561,9 @@ arg_types(erlang, concat_binary, 1) ->
arg_types(erlang, crc32, 1) ->
[t_iodata()];
arg_types(erlang, crc32, 2) ->
- [t_integer(), t_iodata()];
+ [t_crc32(), t_iodata()];
arg_types(erlang, crc32_combine, 3) ->
- [t_integer(), t_integer(), t_integer()];
+ [t_crc32(), t_crc32(), t_non_neg_integer()];
arg_types(erlang, date, 0) ->
[];
arg_types(erlang, decode_packet, 3) ->
@@ -3432,6 +3578,10 @@ arg_types(erlang, disconnect_node, 1) ->
[t_node()];
arg_types(erlang, display, 1) ->
[t_any()];
+arg_types(erlang, display_nl, 0) ->
+ [];
+arg_types(erlang, display_string, 1) ->
+ [t_string()];
arg_types(erlang, dist_exit, 3) ->
[t_pid(), t_dist_exit(), t_sup(t_pid(), t_port())];
arg_types(erlang, element, 2) ->
@@ -3468,6 +3618,8 @@ arg_types(erlang, garbage_collect, 0) ->
[];
arg_types(erlang, garbage_collect, 1) ->
[t_pid()];
+arg_types(erlang, garbage_collect_message_area, 0) ->
+ [];
arg_types(erlang, get, 0) ->
[];
arg_types(erlang, get, 1) ->
@@ -3572,10 +3724,12 @@ arg_types(erlang, list_to_pid, 1) ->
[t_string()];
arg_types(erlang, list_to_tuple, 1) ->
[t_list()];
-arg_types(erlang, loaded, 0) ->
- [];
arg_types(erlang, load_module, 2) ->
[t_atom(), t_binary()];
+arg_types(erlang, load_nif, 2) ->
+ [t_string(), t_any()];
+arg_types(erlang, loaded, 0) ->
+ [];
arg_types(erlang, localtime, 0) ->
[];
arg_types(erlang, localtime_to_universaltime, 1) ->
@@ -3618,6 +3772,10 @@ arg_types(erlang, monitor_node, 2) ->
[t_node(), t_boolean()];
arg_types(erlang, monitor_node, 3) ->
[t_node(), t_boolean(), t_list(t_atom('allow_passive_connect'))];
+arg_types(erlang, nif_error, 1) ->
+ [t_any()];
+arg_types(erlang, nif_error, 2) ->
+ [t_any(), t_list()];
arg_types(erlang, node, 0) ->
[];
arg_types(erlang, node, 1) ->
@@ -3658,6 +3816,8 @@ arg_types(erlang, phash2, 2) ->
[t_any(), t_pos_integer()];
arg_types(erlang, pid_to_list, 1) ->
[t_pid()];
+arg_types(erlang, port_call, 2) ->
+ [t_sup(t_port(), t_atom()), t_any()];
arg_types(erlang, port_call, 3) ->
[t_sup(t_port(), t_atom()), t_integer(), t_any()];
arg_types(erlang, port_close, 1) ->
@@ -3785,6 +3945,8 @@ arg_types(erlang, statistics, 1) ->
t_atom('run_queue'),
t_atom('runtime'),
t_atom('wall_clock')])];
+arg_types(erlang, subtract, 2) ->
+ arg_types(erlang, '--', 2);
arg_types(erlang, suspend_process, 1) ->
[t_pid()];
arg_types(erlang, suspend_process, 2) ->
@@ -3864,7 +4026,8 @@ arg_types(erlang, trace_info, 2) ->
t_atom('flags'), t_atom('tracer'),
%% while the following are items about a func
t_atom('traced'), t_atom('match_spec'), t_atom('meta'),
- t_atom('meta_match_spec'), t_atom('call_count'), t_atom('all')])];
+ t_atom('meta_match_spec'), t_atom('call_count'),
+ t_atom('call_time'), t_atom('all')])];
arg_types(erlang, trace_pattern, 2) ->
[t_sup(t_tuple([t_atom(), t_atom(), t_sup(t_arity(), t_atom('_'))]),
t_atom('on_load')),
@@ -3873,7 +4036,7 @@ arg_types(erlang, trace_pattern, 3) ->
arg_types(erlang, trace_pattern, 2) ++
[t_list(t_sup([t_atom('global'), t_atom('local'),
t_atom('meta'), t_tuple([t_atom('meta'), t_pid()]),
- t_atom('call_count')]))];
+ t_atom('call_count'), t_atom('call_time')]))];
arg_types(erlang, trunc, 1) ->
[t_number()];
arg_types(erlang, tuple_size, 1) ->
@@ -3907,10 +4070,18 @@ arg_types(erts_debug, breakpoint, 2) ->
[t_tuple([t_atom(), t_atom(), t_sup(t_integer(), t_atom('_'))]), t_boolean()];
arg_types(erts_debug, disassemble, 1) ->
[t_sup(t_mfa(), t_integer())];
+arg_types(erts_debug, display, 1) ->
+ [t_any()];
arg_types(erts_debug, dist_ext_to_term, 2) ->
[t_tuple(), t_binary()];
+arg_types(erts_debug, dump_monitors, 1) ->
+ [t_sup([t_pid(),t_atom()])];
+arg_types(erts_debug, dump_links, 1) ->
+ [t_sup([t_pid(),t_atom(),t_port()])];
arg_types(erts_debug, flat_size, 1) ->
[t_any()];
+arg_types(erts_debug, get_internal_state, 1) ->
+ [t_any()];
arg_types(erts_debug, lock_counters, 1) ->
[t_sup([t_atom(enabled),
t_atom(info),
@@ -3919,6 +4090,8 @@ arg_types(erts_debug, lock_counters, 1) ->
t_tuple([t_atom(process_locks), t_boolean()])])];
arg_types(erts_debug, same, 2) ->
[t_any(), t_any()];
+arg_types(erts_debug, set_internal_state, 2) ->
+ [t_any(), t_any()];
%%------- ets -----------------------------------------------------------------
arg_types(ets, all, 0) ->
[];
@@ -4099,7 +4272,7 @@ arg_types(hipe_bifs, call_count_off, 1) ->
arg_types(hipe_bifs, call_count_on, 1) ->
[t_mfa()];
arg_types(hipe_bifs, check_crc, 1) ->
- [t_integer()];
+ [t_crc32()];
arg_types(hipe_bifs, enter_code, 2) ->
[t_binary(), t_sup(t_nil(), t_tuple())];
arg_types(hipe_bifs, enter_sdesc, 1) ->
@@ -4143,7 +4316,7 @@ arg_types(hipe_bifs, set_funinfo_native_address, 3) ->
arg_types(hipe_bifs, set_native_address, 3) ->
[t_mfa(), t_integer(), t_boolean()];
arg_types(hipe_bifs, system_crc, 1) ->
- [t_integer()];
+ [t_crc32()];
arg_types(hipe_bifs, term_to_word, 1) ->
[t_any()];
arg_types(hipe_bifs, update_code_size, 3) ->
@@ -4457,6 +4630,30 @@ t_httppacket() ->
t_sup([t_HttpRequest(), t_HttpResponse(),
t_HttpHeader(), t_atom('http_eoh'), t_HttpError()]).
+t_endian() ->
+ t_sup([t_atom('big'), t_atom('little')]).
+
+%% =====================================================================
+%% Types for the binary module
+%% =====================================================================
+
+t_binary_part() ->
+ t_tuple([t_non_neg_integer(),t_integer()]).
+
+t_binary_canonical_part() ->
+ t_tuple([t_non_neg_integer(),t_non_neg_integer()]).
+
+t_binary_pattern() ->
+ t_sup([t_binary(),
+ t_list(t_binary()),
+ t_binary_compiled_pattern()]).
+
+t_binary_compiled_pattern() ->
+ t_tuple([t_atom('cp'),t_binary()]).
+
+t_binary_options() ->
+ t_list(t_tuple([t_atom('scope'),t_binary_part()])).
+
%% =====================================================================
%% HTTP types documented in R12B-4
%% =====================================================================
@@ -4539,6 +4736,12 @@ t_code_loaded_fname_or_status() ->
%% These are used for the built-in functions of 'erlang'
%% =====================================================================
+t_adler32() ->
+ t_non_neg_integer().
+
+t_crc32() ->
+ t_non_neg_integer().
+
t_decode_packet_option() ->
t_sup([t_tuple([t_atom('packet_size'), t_non_neg_integer()]),
t_tuple([t_atom('line_length'), t_non_neg_integer()])]).
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index f3b91b3953..9a40be6d14 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -178,7 +178,7 @@
t_remote/3,
t_string/0,
t_struct_from_opaque/2,
- t_solve_remote/2,
+ t_solve_remote/3,
t_subst/2,
t_subtract/2,
t_subtract_list/2,
@@ -205,11 +205,14 @@
t_var_name/1,
%% t_assign_variables_to_subtype/2,
type_is_defined/3,
+ record_field_diffs_to_string/2,
subst_all_vars_to_any/1,
- lift_list_to_pos_empty/1
+ lift_list_to_pos_empty/1,
+ is_erl_type/1
]).
%%-define(DO_ERL_TYPES_TEST, true).
+-compile({no_auto_import,[min/2,max/2]}).
-ifdef(DO_ERL_TYPES_TEST).
-export([test/0]).
@@ -221,6 +224,8 @@
-export([t_is_identifier/1]).
-endif.
+-export_type([erl_type/0]).
+
%%=============================================================================
%%
%% Definition of the type structure
@@ -299,7 +304,7 @@
%% Auxiliary types and convenient macros
%%
--type parse_form() :: {atom(), _, _} | {atom(), _, _, _}. %% XXX: Temporarily
+-type parse_form() :: {atom(), _, _} | {atom(), _, _, _} | {'op', _, _, _, _}. %% XXX: Temporarily
-type rng_elem() :: 'pos_inf' | 'neg_inf' | integer().
-record(int_set, {set :: [integer()]}).
@@ -398,7 +403,8 @@ t_is_none(_) -> false.
-spec t_opaque(module(), atom(), [_], erl_type()) -> erl_type().
t_opaque(Mod, Name, Args, Struct) ->
- ?opaque(set_singleton(#opaque{mod=Mod, name=Name, args=Args, struct=Struct})).
+ O = #opaque{mod = Mod, name = Name, args = Args, struct = Struct},
+ ?opaque(set_singleton(O)).
-spec t_is_opaque(erl_type()) -> boolean().
@@ -427,7 +433,7 @@ t_opaque_structure(?opaque(Elements)) ->
t_opaque_module(?opaque(Elements)) ->
case ordsets:size(Elements) of
1 ->
- [#opaque{mod=Module}] = ordsets:to_list(Elements),
+ [#opaque{mod = Module}] = ordsets:to_list(Elements),
Module;
_ -> throw({error, "Unexpected multiple opaque types"})
end.
@@ -631,7 +637,7 @@ t_unopaque_on_mismatch(GenType, Type, Opaques) ->
case t_inf(GenType, Type) of
?none ->
Unopaqued = t_unopaque(Type, Opaques),
- %% Unions might be a problem, must investigate.
+ %% XXX: Unions might be a problem, must investigate.
case t_inf(GenType, Unopaqued) of
?none -> Type;
_ -> Unopaqued
@@ -643,12 +649,12 @@ t_unopaque_on_mismatch(GenType, Type, Opaques) ->
module_builtin_opaques(Module) ->
[O || O <- all_opaque_builtins(), t_opaque_module(O) =:= Module].
-
+
%%-----------------------------------------------------------------------------
-%% Remote types
-%% These types are used for preprocessing they should never reach the analysis stage
+%% Remote types: these types are used for preprocessing;
+%% they should never reach the analysis stage.
--spec t_remote(module(), atom(), [_]) -> erl_type().
+-spec t_remote(atom(), atom(), [erl_type()]) -> erl_type().
t_remote(Mod, Name, Args) ->
?remote(set_singleton(#remote{mod = Mod, name = Name, args = Args})).
@@ -658,126 +664,132 @@ t_remote(Mod, Name, Args) ->
t_is_remote(?remote(_)) -> true;
t_is_remote(_) -> false.
--spec t_solve_remote(erl_type(), dict()) -> erl_type().
+-spec t_solve_remote(erl_type(), set(), dict()) -> erl_type().
-t_solve_remote(Type , Records) ->
- {RT, _RR} = t_solve_remote(Type, Records, []),
+t_solve_remote(Type, ExpTypes, Records) ->
+ {RT, _RR} = t_solve_remote(Type, ExpTypes, Records, []),
RT.
-t_solve_remote(?function(Domain, Range), R, C) ->
- {RT1, RR1} = t_solve_remote(Domain, R, C),
- {RT2, RR2} = t_solve_remote(Range, R, C),
+t_solve_remote(?function(Domain, Range), ET, R, C) ->
+ {RT1, RR1} = t_solve_remote(Domain, ET, R, C),
+ {RT2, RR2} = t_solve_remote(Range, ET, R, C),
{?function(RT1, RT2), RR1 ++ RR2};
-t_solve_remote(?list(Types, Term, Size), R, C) ->
- {RT, RR} = t_solve_remote(Types, R, C),
+t_solve_remote(?list(Types, Term, Size), ET, R, C) ->
+ {RT, RR} = t_solve_remote(Types, ET, R, C),
{?list(RT, Term, Size), RR};
-t_solve_remote(?product(Types), R, C) ->
- {RL, RR} = list_solve_remote(Types, R, C),
+t_solve_remote(?product(Types), ET, R, C) ->
+ {RL, RR} = list_solve_remote(Types, ET, R, C),
{?product(RL), RR};
-t_solve_remote(?opaque(Set), R, C) ->
+t_solve_remote(?opaque(Set), ET, R, C) ->
List = ordsets:to_list(Set),
- {NewList, RR} = opaques_solve_remote(List, R, C),
+ {NewList, RR} = opaques_solve_remote(List, ET, R, C),
{?opaque(ordsets:from_list(NewList)), RR};
-t_solve_remote(?tuple(?any, _, _) = T, _R, _C) -> {T, []};
-t_solve_remote(?tuple(Types, Arity, Tag), R, C) ->
- {RL, RR} = list_solve_remote(Types, R, C),
+t_solve_remote(?tuple(?any, _, _) = T, _ET, _R, _C) -> {T, []};
+t_solve_remote(?tuple(Types, Arity, Tag), ET, R, C) ->
+ {RL, RR} = list_solve_remote(Types, ET, R, C),
{?tuple(RL, Arity, Tag), RR};
-t_solve_remote(?tuple_set(Set), R, C) ->
- {NewSet, RR} = tuples_solve_remote(Set, R, C),
+t_solve_remote(?tuple_set(Set), ET, R, C) ->
+ {NewSet, RR} = tuples_solve_remote(Set, ET, R, C),
{?tuple_set(NewSet), RR};
-t_solve_remote(?remote(Set), R, C) ->
+t_solve_remote(?remote(Set), ET, R, C) ->
RemoteList = ordsets:to_list(Set),
- {RL, RR} = list_solve_remote_type(RemoteList, R, C),
+ {RL, RR} = list_solve_remote_type(RemoteList, ET, R, C),
{t_sup(RL), RR};
-t_solve_remote(?union(List), R, C) ->
- {RL, RR} = list_solve_remote(List, R, C),
+t_solve_remote(?union(List), ET, R, C) ->
+ {RL, RR} = list_solve_remote(List, ET, R, C),
{t_sup(RL), RR};
-t_solve_remote(T, _R, _C) -> {T, []}.
+t_solve_remote(T, _ET, _R, _C) -> {T, []}.
t_solve_remote_type(#remote{mod = RemMod, name = Name, args = Args} = RemType,
- R, C) ->
+ ET, R, C) ->
+ ArgsLen = length(Args),
case dict:find(RemMod, R) of
error ->
- Msg = io_lib:format("Cannot locate module ~w to "
- "resolve the remote type: ~w:~w()~n",
- [RemMod, RemMod, Name]),
- throw({error, Msg});
+ self() ! {self(), ext_types, {RemMod, Name, ArgsLen}},
+ {t_any(), []};
{ok, RemDict} ->
- case lookup_type(Name, RemDict) of
- {type, {_Mod, Type, ArgNames}} when length(Args) =:= length(ArgNames) ->
- {NewType, NewCycle, NewRR} =
- case unfold(RemType, C) of
- true ->
- List = lists:zip(ArgNames, Args),
- TmpVarDict = dict:from_list(List),
- {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []};
- false -> {t_any(), C, [RemType]}
- end,
- {RT, RR} = t_solve_remote(NewType, R, NewCycle),
- RetRR = NewRR ++ RR,
- RT1 =
- case lists:member(RemType, RetRR) of
- true -> t_limit(RT, ?REC_TYPE_LIMIT);
- false -> RT
- end,
- {RT1, RetRR};
- {opaque, {Mod, Type, ArgNames}} when length(Args) =:= length(ArgNames) ->
- List = lists:zip(ArgNames, Args),
- TmpVarDict = dict:from_list(List),
- {Rep, NewCycle, NewRR} =
- case unfold(RemType, C) of
- true -> {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []};
- false -> {t_any(), C, [RemType]}
- end,
- {NewRep, RR} = t_solve_remote(Rep, R, NewCycle),
- RetRR = NewRR ++ RR,
- RT1 =
- case lists:member(RemType, RetRR) of
- true -> t_limit(NewRep, ?REC_TYPE_LIMIT);
- false -> NewRep
- end,
- {t_from_form({opaque, -1, Name, {Mod, Args, RT1}},
- RemDict, TmpVarDict),
- RetRR};
- {type, _} ->
- Msg = io_lib:format("Unknown remote type ~w\n", [Name]),
- throw({error, Msg});
- {opaque, _} ->
- Msg = io_lib:format("Unknown remote opaque type ~w\n", [Name]),
- throw({error, Msg});
- error ->
- Msg = io_lib:format("Unable to find remote type ~w:~w()\n",
- [RemMod, Name]),
- throw({error, Msg})
+ MFA = {RemMod, Name, ArgsLen},
+ case sets:is_element(MFA, ET) of
+ true ->
+ case lookup_type(Name, RemDict) of
+ {type, {_Mod, Type, ArgNames}} when ArgsLen =:= length(ArgNames) ->
+ {NewType, NewCycle, NewRR} =
+ case unfold(RemType, C) of
+ true ->
+ List = lists:zip(ArgNames, Args),
+ TmpVarDict = dict:from_list(List),
+ {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []};
+ false -> {t_any(), C, [RemType]}
+ end,
+ {RT, RR} = t_solve_remote(NewType, ET, R, NewCycle),
+ RetRR = NewRR ++ RR,
+ RT1 =
+ case lists:member(RemType, RetRR) of
+ true -> t_limit(RT, ?REC_TYPE_LIMIT);
+ false -> RT
+ end,
+ {RT1, RetRR};
+ {opaque, {Mod, Type, ArgNames}} when ArgsLen =:= length(ArgNames) ->
+ List = lists:zip(ArgNames, Args),
+ TmpVarDict = dict:from_list(List),
+ {Rep, NewCycle, NewRR} =
+ case unfold(RemType, C) of
+ true -> {t_from_form(Type, RemDict, TmpVarDict), [RemType|C], []};
+ false -> {t_any(), C, [RemType]}
+ end,
+ {NewRep, RR} = t_solve_remote(Rep, ET, R, NewCycle),
+ RetRR = NewRR ++ RR,
+ RT1 =
+ case lists:member(RemType, RetRR) of
+ true -> t_limit(NewRep, ?REC_TYPE_LIMIT);
+ false -> NewRep
+ end,
+ {t_from_form({opaque, -1, Name, {Mod, Args, RT1}},
+ RemDict, TmpVarDict),
+ RetRR};
+ {type, _} ->
+ Msg = io_lib:format("Unknown remote type ~w\n", [Name]),
+ throw({error, Msg});
+ {opaque, _} ->
+ Msg = io_lib:format("Unknown remote opaque type ~w\n", [Name]),
+ throw({error, Msg});
+ error ->
+ Msg = io_lib:format("Unable to find remote type ~w:~w()\n",
+ [RemMod, Name]),
+ throw({error, Msg})
+ end;
+ false ->
+ self() ! {self(), ext_types, {RemMod, Name, ArgsLen}},
+ {t_any(), []}
end
end.
-list_solve_remote([], _R, _C) ->
+list_solve_remote([], _ET, _R, _C) ->
{[], []};
-list_solve_remote([Type|Types], R, C) ->
- {RT, RR1} = t_solve_remote(Type, R, C),
- {RL, RR2} = list_solve_remote(Types, R, C),
+list_solve_remote([Type|Types], ET, R, C) ->
+ {RT, RR1} = t_solve_remote(Type, ET, R, C),
+ {RL, RR2} = list_solve_remote(Types, ET, R, C),
{[RT|RL], RR1 ++ RR2}.
-list_solve_remote_type([], _R, _C) ->
+list_solve_remote_type([], _ET, _R, _C) ->
{[], []};
-list_solve_remote_type([Type|Types], R, C) ->
- {RT, RR1} = t_solve_remote_type(Type, R, C),
- {RL, RR2} = list_solve_remote_type(Types, R, C),
+list_solve_remote_type([Type|Types], ET, R, C) ->
+ {RT, RR1} = t_solve_remote_type(Type, ET, R, C),
+ {RL, RR2} = list_solve_remote_type(Types, ET, R, C),
{[RT|RL], RR1 ++ RR2}.
-opaques_solve_remote([], _R, _C) ->
+opaques_solve_remote([], _ET, _R, _C) ->
{[], []};
-opaques_solve_remote([#opaque{struct = Struct} = Remote|Tail], R, C) ->
- {RT, RR1} = t_solve_remote(Struct, R, C),
- {LOp, RR2} = opaques_solve_remote(Tail, R, C),
+opaques_solve_remote([#opaque{struct = Struct} = Remote|Tail], ET, R, C) ->
+ {RT, RR1} = t_solve_remote(Struct, ET, R, C),
+ {LOp, RR2} = opaques_solve_remote(Tail, ET, R, C),
{[Remote#opaque{struct = RT}|LOp], RR1 ++ RR2}.
-tuples_solve_remote([], _R, _C) ->
+tuples_solve_remote([], _ET, _R, _C) ->
{[], []};
-tuples_solve_remote([{Sz, Tuples}|Tail], R, C) ->
- {RL, RR1} = list_solve_remote(Tuples, R, C),
- {LSzTpls, RR2} = tuples_solve_remote(Tail, R, C),
+tuples_solve_remote([{Sz, Tuples}|Tail], ET, R, C) ->
+ {RL, RR1} = list_solve_remote(Tuples, ET, R, C),
+ {LSzTpls, RR2} = tuples_solve_remote(Tail, ET, R, C),
{[{Sz, RL}|LSzTpls], RR1 ++ RR2}.
%%-----------------------------------------------------------------------------
@@ -801,7 +813,7 @@ t_is_none_or_unit(?unit) -> true;
t_is_none_or_unit(_) -> false.
%%-----------------------------------------------------------------------------
-%% Atoms and the derived type bool
+%% Atoms and the derived type boolean
%%
-spec t_atom() -> erl_type().
@@ -1596,7 +1608,7 @@ t_set() ->
t_tid() ->
t_opaque(ets, tid, [], t_integer()).
--spec all_opaque_builtins() -> [erl_type()].
+-spec all_opaque_builtins() -> [erl_type(),...].
all_opaque_builtins() ->
[t_array(), t_dict(), t_digraph(), t_gb_set(),
@@ -2523,12 +2535,14 @@ t_subst(T, _Dict, _Fun) ->
%% Unification
%%
--spec t_unify(erl_type(), erl_type()) -> {erl_type(), [{_, erl_type()}]}.
+-type t_unify_ret() :: {erl_type(), [{_, erl_type()}]}.
+
+-spec t_unify(erl_type(), erl_type()) -> t_unify_ret().
t_unify(T1, T2) ->
t_unify(T1, T2, []).
--spec t_unify(erl_type(), erl_type(), [erl_type()]) -> {erl_type(), [{_, erl_type()}]}.
+-spec t_unify(erl_type(), erl_type(), [erl_type()]) -> t_unify_ret().
t_unify(T1, T2, Opaques) ->
{T, Dict} = t_unify(T1, T2, dict:new(), Opaques),
@@ -2541,7 +2555,7 @@ t_unify(?var(Id1) = T, ?var(Id2), Dict, Opaques) ->
error ->
case dict:find(Id2, Dict) of
error -> {T, dict:store(Id2, T, Dict)};
- {ok, Type} -> {Type, t_unify(T, Type, Dict, Opaques)}
+ {ok, Type} -> t_unify(T, Type, Dict, Opaques)
end;
{ok, Type1} ->
case dict:find(Id2, Dict) of
@@ -3298,28 +3312,44 @@ record_to_string(Tag, [_|Fields], FieldNames, RecDict) ->
FieldStrings = record_fields_to_string(Fields, FieldNames, RecDict, []),
"#" ++ atom_to_list(Tag) ++ "{" ++ sequence(FieldStrings, [], ",") ++ "}".
-record_fields_to_string([Field|Left1], [{FieldName, DeclaredType}|Left2],
- RecDict, Acc) ->
- PrintType =
- case t_is_equal(Field, DeclaredType) of
- true -> false;
+record_fields_to_string([F|Fs], [{FName, _DefType}|FDefs], RecDict, Acc) ->
+ NewAcc =
+ case t_is_any(F) orelse t_is_atom('undefined', F) of
+ true -> Acc;
false ->
- case t_is_any(DeclaredType) andalso t_is_atom(undefined, Field) of
- true -> false;
- false ->
- TmpType = t_subtract(DeclaredType, t_atom(undefined)),
- not t_is_equal(Field, TmpType)
- end
+ StrFV = atom_to_list(FName) ++ "::" ++ t_to_string(F, RecDict),
+ %% ActualDefType = t_subtract(DefType, t_atom('undefined')),
+ %% Str = case t_is_any(ActualDefType) of
+ %% true -> StrFV;
+ %% false -> StrFV ++ "::" ++ t_to_string(ActualDefType, RecDict)
+ %% end,
+ [StrFV|Acc]
end,
- case PrintType of
- false -> record_fields_to_string(Left1, Left2, RecDict, Acc);
- true ->
- String = atom_to_list(FieldName) ++ "::" ++ t_to_string(Field, RecDict),
- record_fields_to_string(Left1, Left2, RecDict, [String|Acc])
- end;
+ record_fields_to_string(Fs, FDefs, RecDict, NewAcc);
record_fields_to_string([], [], _RecDict, Acc) ->
lists:reverse(Acc).
+-spec record_field_diffs_to_string(erl_type(), dict()) -> string().
+
+record_field_diffs_to_string(?tuple([_|Fs], Arity, Tag), RecDict) ->
+ [TagAtom] = t_atom_vals(Tag),
+ {ok, FieldNames} = lookup_record(TagAtom, Arity-1, RecDict),
+ %% io:format("RecCElems = ~p\nRecTypes = ~p\n", [Fs, FieldNames]),
+ FieldDiffs = field_diffs(Fs, FieldNames, RecDict, []),
+ sequence(FieldDiffs, [], " and ").
+
+field_diffs([F|Fs], [{FName, DefType}|FDefs], RecDict, Acc) ->
+ NewAcc =
+ case t_is_subtype(F, DefType) of
+ true -> Acc;
+ false ->
+ Str = atom_to_list(FName) ++ "::" ++ t_to_string(DefType, RecDict),
+ [Str|Acc]
+ end,
+ field_diffs(Fs, FDefs, RecDict, NewAcc);
+field_diffs([], [], _, Acc) ->
+ lists:reverse(Acc).
+
comma_sequence(Types, RecDict) ->
List = [case T =:= ?any of
true -> "_";
@@ -3338,8 +3368,8 @@ sequence([], [], _Delimiter) ->
[];
sequence([T], Acc, _Delimiter) ->
lists:flatten(lists:reverse([T|Acc]));
-sequence([T|Left], Acc, Delimiter) ->
- sequence(Left, [T ++ Delimiter|Acc], Delimiter).
+sequence([T|Ts], Acc, Delimiter) ->
+ sequence(Ts, [T ++ Delimiter|Acc], Delimiter).
%%=============================================================================
%%
@@ -3386,6 +3416,18 @@ t_from_form({atom, _L, Atom}, _TypeNames, _RecDict, _VarDict) ->
{t_atom(Atom), []};
t_from_form({integer, _L, Int}, _TypeNames, _RecDict, _VarDict) ->
{t_integer(Int), []};
+t_from_form({op, _L, _Op, _Arg} = Op, _TypeNames, _RecDict, _VarDict) ->
+ case erl_eval:partial_eval(Op) of
+ {integer, _, Val} ->
+ {t_integer(Val), []};
+ _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Op])})
+ end;
+t_from_form({op, _L, _Op, _Arg1, _Arg2} = Op, _TypeNames, _RecDict, _VarDict) ->
+ case erl_eval:partial_eval(Op) of
+ {integer, _, Val} ->
+ {t_integer(Val), []};
+ _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Op])})
+ end;
t_from_form({type, _L, any, []}, _TypeNames, _RecDict, _VarDict) ->
{t_any(), []};
t_from_form({type, _L, arity, []}, _TypeNames, _RecDict, _VarDict) ->
@@ -3396,9 +3438,15 @@ t_from_form({type, _L, atom, []}, _TypeNames, _RecDict, _VarDict) ->
{t_atom(), []};
t_from_form({type, _L, binary, []}, _TypeNames, _RecDict, _VarDict) ->
{t_binary(), []};
-t_from_form({type, _L, binary, [{integer, _, Base}, {integer, _, Unit}]},
+t_from_form({type, _L, binary, [Base, Unit]} = Type,
_TypeNames, _RecDict, _VarDict) ->
- {t_bitstr(Unit, Base), []};
+ case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of
+ {{integer, _, BaseVal},
+ {integer, _, UnitVal}}
+ when BaseVal >= 0, UnitVal >= 0 ->
+ {t_bitstr(UnitVal, BaseVal), []};
+ _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Type])})
+ end;
t_from_form({type, _L, bitstring, []}, _TypeNames, _RecDict, _VarDict) ->
{t_bitstr(), []};
t_from_form({type, _L, bool, []}, _TypeNames, _RecDict, _VarDict) ->
@@ -3502,9 +3550,14 @@ t_from_form({type, _L, product, Elements}, TypeNames, RecDict, VarDict) ->
{t_product(L), R};
t_from_form({type, _L, queue, []}, _TypeNames, _RecDict, _VarDict) ->
{t_queue(), []};
-t_from_form({type, _L, range, [{integer, _, From}, {integer, _, To}]},
+t_from_form({type, _L, range, [From, To]} = Type,
_TypeNames, _RecDict, _VarDict) ->
- {t_from_range(From, To), []};
+ case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
+ {{integer, _, FromVal},
+ {integer, _, ToVal}} ->
+ {t_from_range(FromVal, ToVal), []};
+ _ -> throw({error, io_lib:format("Unable evaluate type ~w\n", [Type])})
+ end;
t_from_form({type, _L, record, [Name|Fields]}, TypeNames, RecDict, VarDict) ->
record_from_form(Name, Fields, TypeNames, RecDict, VarDict);
t_from_form({type, _L, reference, []}, _TypeNames, _RecDict, _VarDict) ->
@@ -3679,6 +3732,16 @@ t_form_to_string({var, _L, Name}) -> atom_to_list(Name);
t_form_to_string({atom, _L, Atom}) ->
io_lib:write_string(atom_to_list(Atom), $'); % To quote or not to quote... '
t_form_to_string({integer, _L, Int}) -> integer_to_list(Int);
+t_form_to_string({op, _L, _Op, _Arg} = Op) ->
+ case erl_eval:partial_eval(Op) of
+ {integer, _, _} = Int -> t_form_to_string(Int);
+ _ -> io_lib:format("Bad formed type ~w",[Op])
+ end;
+t_form_to_string({op, _L, _Op, _Arg1, _Arg2} = Op) ->
+ case erl_eval:partial_eval(Op) of
+ {integer, _, _} = Int -> t_form_to_string(Int);
+ _ -> io_lib:format("Bad formed type ~w",[Op])
+ end;
t_form_to_string({ann_type, _L, [Var, Type]}) ->
t_form_to_string(Var) ++ "::" ++ t_form_to_string(Type);
t_form_to_string({paren_type, _L, [Type]}) ->
@@ -3705,8 +3768,12 @@ t_form_to_string({type, _L, nonempty_list, [Type]}) ->
t_form_to_string({type, _L, nonempty_string, []}) -> "nonempty_string()";
t_form_to_string({type, _L, product, Elements}) ->
"<" ++ sequence(t_form_to_string_list(Elements), ",") ++ ">";
-t_form_to_string({type, _L, range, [{integer, _, From}, {integer, _, To}]}) ->
- io_lib:format("~w..~w", [From, To]);
+t_form_to_string({type, _L, range, [From, To]} = Type) ->
+ case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
+ {{integer, _, FromVal}, {integer, _, ToVal}} ->
+ io_lib:format("~w..~w", [FromVal, ToVal]);
+ _ -> io_lib:format("Bad formed type ~w",[Type])
+ end;
t_form_to_string({type, _L, record, [{atom, _, Name}]}) ->
io_lib:format("#~w{}", [Name]);
t_form_to_string({type, _L, record, [{atom, _, Name}|Fields]}) ->
@@ -3725,13 +3792,17 @@ t_form_to_string({type, _L, Name, []} = T) ->
try t_to_string(t_from_form(T))
catch throw:{error, _} -> atom_to_list(Name) ++ "()"
end;
-t_form_to_string({type, _L, binary, [{integer, _, X}, {integer, _, Y}]}) ->
- case Y of
- 0 ->
- case X of
- 0 -> "<<>>";
- _ -> io_lib:format("<<_:~w>>", [X])
- end
+t_form_to_string({type, _L, binary, [X,Y]} = Type) ->
+ case {erl_eval:partial_eval(X), erl_eval:partial_eval(Y)} of
+ {{integer, _, XVal}, {integer, _, YVal}} ->
+ case YVal of
+ 0 ->
+ case XVal of
+ 0 -> "<<>>";
+ _ -> io_lib:format("<<_:~w>>", [XVal])
+ end
+ end;
+ _ -> io_lib:format("Bad formed type ~w",[Type])
end;
t_form_to_string({type, _L, Name, List}) ->
io_lib:format("~w(~s)", [Name, sequence(t_form_to_string_list(List), ",")]).
@@ -3763,6 +3834,8 @@ any_none_or_unit([?unit|_]) -> true;
any_none_or_unit([_|Left]) -> any_none_or_unit(Left);
any_none_or_unit([]) -> false.
+-spec is_erl_type(any()) -> boolean().
+
is_erl_type(?any) -> true;
is_erl_type(?none) -> true;
is_erl_type(?unit) -> true;
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index 2c78558190..7b34a4f427 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -30,6 +30,68 @@
</header>
<p>This document describes the changes made to HiPE.</p>
+<section><title>Hipe 3.7.6</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p><c>receive</c> statements that can only read out a
+ newly created reference are now specially optimized so
+ that it will execute in constant time regardless of the
+ number of messages in the receive queue for the process.
+ That optimization will benefit calls to
+ <c>gen_server:call()</c>. (See <c>gen:do_call/4</c> for
+ an example of a receive statement that will be
+ optimized.)</p>
+ <p>
+ Own Id: OTP-8623</p>
+ </item>
+ <item>
+ <p>
+ Various changes to dialyzer-related files for R14.</p>
+ <p>
+ - Dialyzer properly supports the new attribute
+ -export_type and checks that remote types only refer to
+ exported types. A warning is produced if some
+ files/applications refer to types defined in modules
+ which are neither in the PLT nor in the analyzed
+ applications.</p>
+ <p>
+ - Support for detecting data races involving whereis/1
+ and unregister/1.</p>
+ <p>
+ - More precise identification of the reason(s) why a
+ record construction violates the types declared for its
+ fields.</p>
+ <p>
+ - Fixed bug in the handling of the 'or' guard.</p>
+ <p>
+ - Better handling of the erlang:element/2 BIF.</p>
+ <p>
+ - Complete handling of Erlang BIFs.</p>
+ <p>
+ Own Id: OTP-8699</p>
+ </item>
+ <item>
+ <p><c>eprof</c> has been reimplemented with support in
+ the Erlang virtual machine and is now both faster (i.e.
+ slows down the code being measured less) and scales much
+ better. In measurements we saw speed-ups compared to the
+ old eprof ranging from 6 times (for sequential code that
+ only uses one scheduler/core) up to 84 times (for
+ parallel code that uses 8 cores).</p>
+ <p>Note: The API for the <c>eprof</c> has been cleaned up
+ and extended. See the documentation.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8706</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Hipe 3.7.5</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/hipe/flow/hipe_dominators.erl b/lib/hipe/flow/hipe_dominators.erl
index 3bfa6d43c4..17357461a5 100644
--- a/lib/hipe/flow/hipe_dominators.erl
+++ b/lib/hipe/flow/hipe_dominators.erl
@@ -1,20 +1,20 @@
%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
%%------------------------------------------------------------------------
@@ -37,6 +37,8 @@
domFrontier_create/2,
domFrontier_get/2]).
+-export_type([domTree/0]).
+
-include("cfg.hrl").
%%========================================================================
diff --git a/lib/hipe/icode/hipe_beam_to_icode.erl b/lib/hipe/icode/hipe_beam_to_icode.erl
index 3923e98673..1f8be4040e 100644
--- a/lib/hipe/icode/hipe_beam_to_icode.erl
+++ b/lib/hipe/icode/hipe_beam_to_icode.erl
@@ -1,20 +1,20 @@
%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%%=======================================================================
@@ -22,8 +22,6 @@
%% Author : Kostis Sagonas
%% Description : Translates symbolic BEAM code to Icode
%%=======================================================================
-%% $Id$
-%%=======================================================================
%% @doc
%% This file translates symbolic BEAM code to Icode which is HiPE's
%% intermediate code representation. Either the code of an entire
@@ -431,6 +429,11 @@ trans_fun([{wait_timeout,{_,Lbl},Reg}|Instructions], Env) ->
SuspTmout = hipe_icode:mk_if(suspend_msg_timeout,[],
map_label(Lbl),hipe_icode:label_name(DoneLbl)),
Movs ++ [SetTmout, SuspTmout, DoneLbl | trans_fun(Instructions,Env1)];
+%%--- recv_mark/1 & recv_set/1 --- XXX: Handle better??
+trans_fun([{recv_mark,{f,_}}|Instructions], Env) ->
+ trans_fun(Instructions,Env);
+trans_fun([{recv_set,{f,_}}|Instructions], Env) ->
+ trans_fun(Instructions,Env);
%%--------------------------------------------------------------------
%%--- Translation of arithmetics {bif,ArithOp, ...} ---
%%--------------------------------------------------------------------
@@ -892,11 +895,6 @@ trans_fun([{bs_init_bits,{f,Lbl},Size,_Words,_LiveRegs,{field_flags,Flags0},X}|
end,
trans_bin_call({hipe_bs_primop,Name}, Lbl, Args, [Dst, Base, Offset],
Base, Offset, Env, Instructions);
-trans_fun([{bs_bits_to_bytes2, Bits, Bytes}|Instructions], Env) ->
- Src = trans_arg(Bits),
- Dst = mk_var(Bytes),
- [hipe_icode:mk_primop([Dst], 'bsl', [Src, hipe_icode:mk_const(3)])|
- trans_fun(Instructions,Env)];
trans_fun([{bs_add, {f,Lbl}, [Old,New,Unit], Res}|Instructions], Env) ->
Dst = mk_var(Res),
Temp = mk_var(new),
@@ -1126,13 +1124,6 @@ trans_fun([{gc_bif,Name,Fail,_Live,SrcRs,DstR}|Instructions], Env) ->
trans_fun([{bif,Name,Fail,SrcRs,DstR}|Instructions], Env)
end;
%%--------------------------------------------------------------------
-%% Instruction for constant pool added in February 2007 for R11B-4.
-%%--------------------------------------------------------------------
-trans_fun([{put_literal,{literal,Literal},DstR}|Instructions], Env) ->
- DstV = mk_var(DstR),
- Move = hipe_icode:mk_move(DstV, hipe_icode:mk_const(Literal)),
- [Move | trans_fun(Instructions, Env)];
-%%--------------------------------------------------------------------
%% New test instruction added in July 2007 for R12.
%%--------------------------------------------------------------------
%%--- is_bitstr ---
diff --git a/lib/hipe/icode/hipe_icode.erl b/lib/hipe/icode/hipe_icode.erl
index a4614d7237..3e211bf385 100644
--- a/lib/hipe/icode/hipe_icode.erl
+++ b/lib/hipe/icode/hipe_icode.erl
@@ -1,20 +1,20 @@
%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1578,27 +1578,19 @@ redirect_jmp(Jmp, ToOld, ToNew) ->
_ -> Jmp
end
end,
- simplify_branch(NewI).
-
-%%
-%% @doc Turns a branch into a goto if it has only one successor and it
-%% is safe to do so.
-%%
-
--spec simplify_branch(icode_instr()) -> icode_instr().
-
-simplify_branch(I) ->
- case ordsets:from_list(successors(I)) of
+ %% Turn a branch into a goto if it has only one successor and it is
+ %% safe to do so.
+ case ordsets:from_list(successors(NewI)) of
[Label] ->
Goto = mk_goto(Label),
- case I of
- #icode_type{} -> Goto;
+ case NewI of
#icode_if{} -> Goto;
#icode_switch_tuple_arity{} -> Goto;
#icode_switch_val{} -> Goto;
- _ -> I
+ #icode_type{} -> Goto;
+ _ -> NewI
end;
- _ -> I
+ _ -> NewI
end.
%%
diff --git a/lib/hipe/util/hipe_digraph.erl b/lib/hipe/util/hipe_digraph.erl
index a62e913fe5..fcfaa64684 100644
--- a/lib/hipe/util/hipe_digraph.erl
+++ b/lib/hipe/util/hipe_digraph.erl
@@ -1,20 +1,20 @@
%% -*- erlang-indent-level: 2 -*-
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
%%-----------------------------------------------------------------------
@@ -30,6 +30,8 @@
from_list/1, to_list/1, get_parents/2, get_children/2]).
-export([reverse_preorder_sccs/1]).
+-export_type([hdg/0]).
+
%%------------------------------------------------------------------------
-type ordset(T) :: [T]. % XXX: temporarily
diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk
index 129718a305..31c860ddec 100644
--- a/lib/hipe/vsn.mk
+++ b/lib/hipe/vsn.mk
@@ -1 +1 @@
-HIPE_VSN = 3.7.5
+HIPE_VSN = 3.7.6
diff --git a/lib/ic/doc/src/Makefile b/lib/ic/doc/src/Makefile
index 26d0932a95..8eda436a24 100644
--- a/lib/ic/doc/src/Makefile
+++ b/lib/ic/doc/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1998-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1998-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%
#
#
@@ -211,7 +211,11 @@ $(HTMLDIR)/%.gif: %.gif
ifdef DOCSUPPORT
+ifneq (,$(JAVA))
docs: pdf html man $(JAVADOC_GENERATED_FILES)
+else
+docs: pdf html man
+endif
$(TOP_PDF_FILE): $(XML_FILES)
@@ -301,6 +305,7 @@ release_docs_spec: docs
$(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTML_FILES) \
$(RELSYSDIR)/doc/html
$(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR)
+ifneq (,$(JAVA))
$(INSTALL_DIR) $(RELSYSDIR)/doc/html/java
$(INSTALL_DIR) $(RELSYSDIR)/doc/html/java/resources
$(INSTALL_DIR) $(RELSYSDIR)/doc/html/java/com
@@ -313,6 +318,7 @@ release_docs_spec: docs
$(RELSYSDIR)/doc/html/java/resources
$(INSTALL_DATA) $(JAVADOC_PACK_HTML_FILES) \
$(RELSYSDIR)/doc/html/java/com/ericsson/otp/ic
+endif
$(INSTALL_DIR) $(RELEASE_PATH)/man/man3
$(INSTALL_DATA) $(MAN3_FILES) $(RELEASE_PATH)/man/man3
diff --git a/lib/ic/doc/src/notes.xml b/lib/ic/doc/src/notes.xml
index dbafde7b4b..6684547572 100644
--- a/lib/ic/doc/src/notes.xml
+++ b/lib/ic/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>1998</year><year>2009</year>
+ <year>1998</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>IDL Compiler Release Notes</title>
@@ -31,6 +31,22 @@
</header>
<section>
+ <title>IC 4.2.25</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+ <list type="bulleted">
+ <item>
+ <p>
+ The documentation can now be built and installed without Java.</p>
+ <p>
+ Own Id: OTP-8639 Aux Id:</p>
+ </item>
+ </list>
+ </section>
+ </section>
+
+ <section>
<title>IC 4.2.24</title>
<section>
diff --git a/lib/ic/vsn.mk b/lib/ic/vsn.mk
index e0fccf4889..4aa2a04b60 100644
--- a/lib/ic/vsn.mk
+++ b/lib/ic/vsn.mk
@@ -1,6 +1,8 @@
-IC_VSN = 4.2.24
+IC_VSN = 4.2.25
-TICKETS = OTP-8307 \
+TICKETS = OTP-8639
+
+TICKETS_4.2.24 = OTP-8307 \
OTP-8353 \
OTP-8354 \
OTP-8355
diff --git a/lib/inets/doc/src/http_server.xml b/lib/inets/doc/src/http_server.xml
index 01e0b47d37..68dfd1add0 100644
--- a/lib/inets/doc/src/http_server.xml
+++ b/lib/inets/doc/src/http_server.xml
@@ -30,6 +30,8 @@
<date></date>
<rev></rev>
<file>http_server.xml</file>
+
+ <marker id="intro"></marker>
</header>
<section>
@@ -65,6 +67,8 @@
Server API. This API can be used to advantage by all who wants
to enhance the server core functionality, for example custom
logging and authentication.</p>
+
+ <marker id="config"></marker>
</section>
<section>
@@ -109,6 +113,8 @@
functions or only exported functions on chosen modules.</p>
<p>{accept_timeout, integer()} sets the wanted timeout value for
the server to set up a request connection.</p>
+
+ <marker id="using_http_server_api"></marker>
</section>
<section>
@@ -173,6 +179,7 @@
the ip address reported by the info function and can
not be the hostname that is allowed when inputting bind_address.</p>
+ <marker id="htaccess"></marker>
</section>
<section>
@@ -337,6 +344,8 @@ UserName:Password
</item>
</list>
</section>
+
+ <marker id="dynamic_we_pages"></marker>
</section>
<section>
@@ -434,6 +443,8 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
</note>
</section>
</section>
+
+ <marker id="logging"></marker>
</section>
<section>
@@ -467,6 +478,8 @@ http://your.server.org/eval?httpd_example:print(atom_to_list(apply(erlang,halt,[
</p>
<p><em>[date]</em> access to <em>path</em> failed for
<em>remotehost</em>, reason: <em>reason</em></p>
+
+ <marker id="ssi"></marker>
</section>
<section>
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml
index 7430a62b1b..9c8df28fec 100644
--- a/lib/inets/doc/src/httpc.xml
+++ b/lib/inets/doc/src/httpc.xml
@@ -167,6 +167,8 @@ ssl_options() = {verify, code()} |
<v>http_option() = {timeout, timeout()} |
{connect_timeout, timeout()} |
{ssl, ssl_options()} |
+ {ossl, ssl_options()} |
+ {essl, ssl_options()} |
{autoredirect, boolean()} |
{proxy_auth, {userstring(), passwordstring()}} |
{version, http_version()} |
@@ -222,7 +224,22 @@ ssl_options() = {verify, code()} |
<tag><c><![CDATA[ssl]]></c></tag>
<item>
- <p>If using SSL, these SSL-specific options are used. </p>
+ <p>This is the default ssl config option, currently defaults to
+ <c>ossl</c>, see below. </p>
+ <p>Defaults to <c>[]</c>. </p>
+ </item>
+
+ <tag><c><![CDATA[ossl]]></c></tag>
+ <item>
+ <p>If using the OpenSSL based (old) implementation of SSL,
+ these SSL-specific options are used. </p>
+ <p>Defaults to <c>[]</c>. </p>
+ </item>
+
+ <tag><c><![CDATA[essl]]></c></tag>
+ <item>
+ <p>If using the Erlang based (new) implementation of SSL,
+ these SSL-specific options are used. </p>
<p>Defaults to <c>[]</c>. </p>
</item>
diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml
index 7dabeb33e9..847605fe93 100644
--- a/lib/inets/doc/src/httpd.xml
+++ b/lib/inets/doc/src/httpd.xml
@@ -148,8 +148,13 @@
in the apache like configuration file.
</item>
- <tag>{socket_type, ip_comm | ssl}</tag>
+ <tag>{socket_type, ip_comm | ssl | ossl | essl}</tag>
<item>
+ <p>When using ssl, there are several alternatives.
+ <c>ossl</c> specifically uses the OpenSSL based (old) SSL.
+ <c>essl</c> specifically uses the Erlang based (new) SSL.
+ When using <c>ssl</c> it <em>currently</em> defaults to
+ <c>ossl</c>. </p>
<p>Defaults to <c>ip_comm</c>. </p>
</item>
@@ -267,18 +272,22 @@ text/plain asc txt
The <c>common</c> format is one line that looks like this:
<c>remotehost rfc931 authuser [date] "request" status bytes</c></p>
- <pre>remotehost
+ <pre>
+remotehost
Remote
rfc931
The client's remote username (RFC 931).
authuser
- The username with which the user authenticated himself.
+ The username with which the user authenticated
+ himself.
[date]
Date and time of the request (RFC 1123).
"request"
- The request line exactly as it came from the client(RFC 1945).
+ The request line exactly as it came from the client
+ (RFC 1945).
status
- The HTTP status code returned to the client (RFC 1945).
+ The HTTP status code returned to the client
+ (RFC 1945).
bytes
The content-length of the document transferred.
</pre>
@@ -286,10 +295,11 @@ bytes
<p>The <c>combined</c> format is on line that look like this:
<c>remotehost rfc931 authuser [date] "request" status bytes "referer" "user_agent" </c></p>
- <pre>"referer"
+ <pre>
+"referer"
The url the client was on before
- requesting your url. (If it could not be determined a minus
- sign will be placed in this field)
+ requesting your url. (If it could not be determined
+ a minus sign will be placed in this field)
"user_agent"
The software the client claims to be using. (If it
could not be determined a minus sign will be placed in
@@ -389,6 +399,31 @@ bytes
and an access to http://your.server.org/image/foo.gif would refer to
the file /ftp/pub/image/foo.gif.</item>
+ <tag>{re_write, {Re, Replacement}}</tag>
+
+ <item> Where Re = string() and Replacement = string().
+ The ReWrite property allows documents to be stored in the local file
+ system instead of the document_root location. URLs are rewritten
+ by re:replace/3 to produce a path in the local filesystem.
+ For example:
+
+ <code>{re_write, {"^/[~]([^/]+)(.*)$", "/home/\\1/public\\2"}</code>
+
+ and an access to http://your.server.org/~bob/foo.gif would refer to
+ the file /home/bob/public/foo.gif.
+
+ In an Apache like configuration file the Re is separated
+ from Replacement with one single space, and as expected
+ backslashes do not need to be backslash escaped so the
+ same example would become:
+
+ <code>ReWrite ^/[~]([^/]+)(.*)$ /home/\1/public\2</code>
+
+ Beware of trailing space in Replacement that will be used.
+ If you must have a space in Re use e.g the character encoding
+ <code>\040</code> see <seealso marker="re">re(3)</seealso>.
+ </item>
+
<tag>{directory_index, [string()]}</tag>
<item>
@@ -408,7 +443,7 @@ bytes
</taglist>
<marker id="cgi_prop"></marker>
- <p><em>CGI properties - requires mod_cgi</em></p>
+ <p><em>CGI properties - requires mod_cgi</em></p>
<taglist>
<tag>{script_alias, {Alias, RealName}}</tag>
<item> Where Alias = string() and RealName = string().
@@ -423,6 +458,19 @@ bytes
the server to run the script /web/cgi-bin/foo.
</item>
+ <tag>{script_re_write, {Re, Replacement}}</tag>
+ <item> Where Re = string() and Replacement = string().
+ Has the same behavior as the ReWrite property, except that
+ it also marks the target directory as containing CGI
+ scripts. URLs with a path beginning with url-path are mapped to
+ scripts beginning with directory-filename, for example:
+
+ <code> {script_re_write, {"^/cgi-bin/(\\d+)/", "/web/\\1/cgi-bin/"}</code>
+
+ and an access to http://your.server.org/cgi-bin/17/foo would cause
+ the server to run the script /web/17/cgi-bin/foo.
+ </item>
+
<tag>{script_nocache, boolean()}</tag>
<item>
diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml
index 6bad77dc0a..3c473d3f94 100644
--- a/lib/inets/doc/src/mod_esi.xml
+++ b/lib/inets/doc/src/mod_esi.xml
@@ -73,7 +73,8 @@
<v>SessionID = term()</v>
<v>Env = [EnvironmentDirectives] ++ ParsedHeader</v>
<v>EnvironmentDirectives = {Key,Value}</v>
- <v>Key = query_string | content_length | server_software | gateway_interface | server_protocol | server_port | request_method | remote_addr | script_name. &lt;v>Input = string()</v>
+ <v>Key = query_string | content_length | server_software | gateway_interface | server_protocol | server_port | request_method | remote_addr | script_name</v>
+ <v>Input = string()</v>
</type>
<desc>
<p>The <c>Module</c> must be found in the code path and export
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 762c2c84c5..23ad5c0df0 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -32,6 +32,174 @@
<file>notes.xml</file>
</header>
+ <section><title>Inets 5.4</title>
+
+ <section><title>Improvements and New Features</title>
+<!--
+ <p>-</p>
+-->
+
+ <list>
+ <item>
+ <p>[httpc|httpd] - Now allow the use of the "new" ssl, by using
+ the <c>essl</c> tag instead. </p>
+ <p>See the <c>http_option</c> option in the
+ <seealso marker="httpc#request2">request/4,5</seealso> or
+ the <seealso marker="httpd#comm_prop">socket-type</seealso>
+ section of the Communication properties chapter for more info, </p>
+ <p>Own Id: OTP-7907</p>
+ </item>
+
+ <item>
+ <p>Deprecated functions designated to be removed in R14 has been
+ removed. Also, some new functions has been marked as deprecated
+ (the old http client api module). </p>
+ <p>Own Id: OTP-8564</p>
+ <p>*** POTENTIAL INCOMPATIBILITY ***</p>
+ </item>
+
+ <item>
+ <p>[httpd] - Improved mod_alias.
+ Now able to do better URL rewrites. </p>
+ <p>See
+ <seealso marker="httpd#alias_prop">URL aliasing properties</seealso>
+ and the
+ <seealso marker="httpd#cgi_prop">CGI properties</seealso>
+ section(s) for more info, </p>
+ <p>Own Id: OTP-8573</p>
+ </item>
+
+ </list>
+ </section>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+
+ <p>-</p>
+
+<!--
+ <list>
+ <item>
+ <p>[httpd] The server did not fully support the documented module
+ callback api. Specifically, the load function should be able to
+ return the atom <c>ok</c>, but this was not accepted. </p>
+ <p>Own Id: OTP-8359</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+
+ </section> <!-- 5.4 -->
+
+
+ <section><title>Inets 5.3.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <p>-</p>
+
+<!--
+ <list>
+ <item>
+ <p>[httpc] - Allow users to pass socket options to the transport
+ module when making requests. </p>
+ <p>See the <c>socket_opts</c> option in the
+ <seealso marker="httpc#request2">request/4</seealso> or
+ <seealso marker="httpc#set_options">set_options/1,2</seealso>
+ for more info, </p>
+ <p>Own Id: OTP-8352</p>
+ </item>
+
+ </list>
+-->
+ </section>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+
+<!--
+ <p>-</p>
+-->
+
+ <list>
+ <item>
+ <p>[httpc] - Made cookie handling more case insensitive.</p>
+ <p>Own Id: OTP-8609</p>
+ <p>Nicolas Thauvin</p>
+ </item>
+
+ <item>
+ <p>[httpc|httpd] - Netscape cookie dates can also be given with a
+ 2-digit year (e.g. 06 = 2006). </p>
+ <p>Own Id: OTP-8610</p>
+ <p>Nicolas Thauvin</p>
+ </item>
+
+ <item>
+ <p>[httpd] - Added support (again) for the documented debugging
+ features. See the User's Guide
+ <seealso marker="http_server#config">Configuration</seealso>
+ chapter for more info. </p>
+ <p>Own Id: OTP-8624</p>
+ </item>
+
+ </list>
+ </section>
+
+ </section> <!-- 5.3.3 -->
+
+
+ <section><title>Inets 5.3.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <p>-</p>
+
+<!--
+ <list>
+ <item>
+ <p>[httpc] - Allow users to pass socket options to the transport
+ module when making requests. </p>
+ <p>See the <c>socket_opts</c> option in the
+ <seealso marker="httpc#request2">request/4</seealso> or
+ <seealso marker="httpc#set_options">set_options/1,2</seealso>
+ for more info, </p>
+ <p>Own Id: OTP-8352</p>
+ </item>
+
+ </list>
+-->
+ </section>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+
+<!--
+ <p>-</p>
+-->
+
+ <list>
+ <item>
+ <p>[httpc] - Memory leak plugged.
+ The profile manager never cleaned up in its handler database.
+ This meant that with each new request handler, another entry
+ was created that was never deleted. Eventually the request
+ id counter (used as a key) would wrap, but the machine would
+ most likely run out of memory before that happened.</p>
+ <p>Own Id: OTP-8542</p>
+ <p>Lev Walkin</p>
+ </item>
+
+ <item>
+ <p>[httpc] - https requests with default port (443) not handled
+ properly. </p>
+ <p>Own Id: OTP-8607</p>
+ <p>jebu ittiachen</p>
+ </item>
+
+ </list>
+ </section>
+
+ </section> <!-- 5.3.2 -->
+
+
<section><title>Inets 5.3.1</title>
<section><title>Improvements and New Features</title>
@@ -197,6 +365,7 @@
<p>Own Id: OTP-8016</p>
<p>*** POTENTIAL INCOMPATIBILITY ***</p>
</item>
+
</list>
</section>
diff --git a/lib/inets/examples/Makefile b/lib/inets/examples/Makefile
index a42b0e38b6..775c449062 100644
--- a/lib/inets/examples/Makefile
+++ b/lib/inets/examples/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2009. All Rights Reserved.
-#
+#
+# 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%
#
#
@@ -21,189 +21,15 @@ include $(ERL_TOP)/make/target.mk
include $(ERL_TOP)/make/$(TARGET)/otp.mk
# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../vsn.mk
-VSN=$(INETS_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
-
-# ----------------------------------------------------
-# Target Specs
+# Common Macros
# ----------------------------------------------------
-MODULE=
-AUTH_FILES = server_root/auth/group \
- server_root/auth/passwd
-CGI_FILES = server_root/cgi-bin/printenv.sh
-CONF_FILES = server_root/conf/8080.conf \
- server_root/conf/8888.conf \
- server_root/conf/httpd.conf \
- server_root/conf/ssl.conf \
- server_root/conf/mime.types
-OPEN_FILES = server_root/htdocs/open/dummy.html
-MNESIA_OPEN_FILES = server_root/htdocs/mnesia_open/dummy.html
-MISC_FILES = server_root/htdocs/misc/friedrich.html \
- server_root/htdocs/misc/oech.html
-SECRET_FILES = server_root/htdocs/secret/dummy.html
-MNESIA_SECRET_FILES = server_root/htdocs/mnesia_secret/dummy.html
-HTDOCS_FILES = server_root/htdocs/index.html \
- server_root/htdocs/config.shtml \
- server_root/htdocs/echo.shtml \
- server_root/htdocs/exec.shtml \
- server_root/htdocs/flastmod.shtml \
- server_root/htdocs/fsize.shtml \
- server_root/htdocs/include.shtml
-ICON_FILES = server_root/icons/README \
- server_root/icons/a.gif \
- server_root/icons/alert.black.gif \
- server_root/icons/alert.red.gif \
- server_root/icons/apache_pb.gif \
- server_root/icons/back.gif \
- server_root/icons/ball.gray.gif \
- server_root/icons/ball.red.gif \
- server_root/icons/binary.gif \
- server_root/icons/binhex.gif \
- server_root/icons/blank.gif \
- server_root/icons/bomb.gif \
- server_root/icons/box1.gif \
- server_root/icons/box2.gif \
- server_root/icons/broken.gif \
- server_root/icons/burst.gif \
- server_root/icons/button1.gif \
- server_root/icons/button10.gif \
- server_root/icons/button2.gif \
- server_root/icons/button3.gif \
- server_root/icons/button4.gif \
- server_root/icons/button5.gif \
- server_root/icons/button6.gif \
- server_root/icons/button7.gif \
- server_root/icons/button8.gif \
- server_root/icons/button9.gif \
- server_root/icons/buttonl.gif \
- server_root/icons/buttonr.gif \
- server_root/icons/c.gif \
- server_root/icons/comp.blue.gif \
- server_root/icons/comp.gray.gif \
- server_root/icons/compressed.gif \
- server_root/icons/continued.gif \
- server_root/icons/dir.gif \
- server_root/icons/down.gif \
- server_root/icons/dvi.gif \
- server_root/icons/f.gif \
- server_root/icons/folder.gif \
- server_root/icons/folder.open.gif \
- server_root/icons/folder.sec.gif \
- server_root/icons/forward.gif \
- server_root/icons/generic.gif \
- server_root/icons/generic.red.gif \
- server_root/icons/generic.sec.gif \
- server_root/icons/hand.right.gif \
- server_root/icons/hand.up.gif \
- server_root/icons/htdig.gif \
- server_root/icons/icon.sheet.gif \
- server_root/icons/image1.gif \
- server_root/icons/image2.gif \
- server_root/icons/image3.gif \
- server_root/icons/index.gif \
- server_root/icons/layout.gif \
- server_root/icons/left.gif \
- server_root/icons/link.gif \
- server_root/icons/movie.gif \
- server_root/icons/p.gif \
- server_root/icons/patch.gif \
- server_root/icons/pdf.gif \
- server_root/icons/pie0.gif \
- server_root/icons/pie1.gif \
- server_root/icons/pie2.gif \
- server_root/icons/pie3.gif \
- server_root/icons/pie4.gif \
- server_root/icons/pie5.gif \
- server_root/icons/pie6.gif \
- server_root/icons/pie7.gif \
- server_root/icons/pie8.gif \
- server_root/icons/portal.gif \
- server_root/icons/poweredby.gif \
- server_root/icons/ps.gif \
- server_root/icons/quill.gif \
- server_root/icons/right.gif \
- server_root/icons/screw1.gif \
- server_root/icons/screw2.gif \
- server_root/icons/script.gif \
- server_root/icons/sound1.gif \
- server_root/icons/sound2.gif \
- server_root/icons/sphere1.gif \
- server_root/icons/sphere2.gif \
- server_root/icons/star.gif \
- server_root/icons/star_blank.gif \
- server_root/icons/tar.gif \
- server_root/icons/tex.gif \
- server_root/icons/text.gif \
- server_root/icons/transfer.gif \
- server_root/icons/unknown.gif \
- server_root/icons/up.gif \
- server_root/icons/uu.gif \
- server_root/icons/uuencoded.gif \
- server_root/icons/world1.gif \
- server_root/icons/world2.gif
+include subdirs.mk
-SSL_FILES = server_root/ssl/ssl_client.pem \
- server_root/ssl/ssl_server.pem
+SPECIAL_TARGETS =
# ----------------------------------------------------
-# FLAGS
+# Default Subdir Targets
# ----------------------------------------------------
-ERL_COMPILE_FLAGS +=
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-debug opt:
-
-clean:
-
-docs:
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth
- $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin
- $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf
- $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open
- $(INSTALL_DATA) $(OPEN_FILES) \
- $(RELSYSDIR)/examples/server_root/htdocs/open
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
- $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \
- $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc
- $(INSTALL_DATA) $(MISC_FILES) \
- $(RELSYSDIR)/examples/server_root/htdocs/misc
- $(INSTALL_DIR) \
- $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret
- $(INSTALL_DIR) \
- $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret
- $(INSTALL_DATA) $(SECRET_FILES) \
- $(RELSYSDIR)/examples/server_root/htdocs/secret
- $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \
- $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs
- $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons
- $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl
- $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl
- $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs
-
-release_docs_spec:
+include $(ERL_TOP)/make/otp_subdir.mk
diff --git a/lib/inets/examples/httpd_load_test/Makefile b/lib/inets/examples/httpd_load_test/Makefile
new file mode 100644
index 0000000000..1cc61ad8ae
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/Makefile
@@ -0,0 +1,123 @@
+#
+# %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
+
+EBIN = .
+
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../../vsn.mk
+
+VSN=$(INETS_VSN)
+
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+EXAMPLE_RELSYSDIR = $(RELSYSDIR)/examples
+HDLT_RELSYSDIR = $(EXAMPLE_RELSYSDIR)/httpd_load_test
+
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+include modules.mk
+
+ERL_FILES = $(MODULES:%=%.erl)
+
+SOURCE = $(ERL_FILES) $(INTERNAL_HRL_FILES)
+
+TARGET_FILES = \
+ $(ERL_FILES:%.erl=$(EBIN)/%.$(EMULATOR))
+
+ifeq ($(TYPE),debug)
+ERL_COMPILE_FLAGS += -Ddebug -W
+endif
+
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+include ../../src/inets_app/inets.mk
+
+ERL_COMPILE_FLAGS += \
+ $(INETS_FLAGS) \
+ $(INETS_ERL_COMPILE_FLAGS) \
+ -I../../include
+
+
+# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+debug:
+ @${MAKE} TYPE=debug opt
+
+opt: $(TARGET_FILES)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f errs core *~
+
+docs:
+
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+
+release_spec: opt
+ $(INSTALL_DIR) $(EXAMPLE_RELSYSDIR)
+ $(INSTALL_DIR) $(HDLT_RELSYSDIR)
+ $(INSTALL_DATA) $(SCRIPT_SKELETONS) $(HDLT_RELSYSDIR)
+ $(INSTALL_DATA) $(CONF_SKELETONS) $(HDLT_RELSYSDIR)
+ $(INSTALL_DATA) $(CERT_FILES) $(HDLT_RELSYSDIR)
+ $(INSTALL_DATA) $(TARGET_FILES) $(HDLT_RELSYSDIR)
+ $(INSTALL_DATA) $(ERL_FILES) $(HDLT_RELSYSDIR)
+
+
+release_docs_spec:
+
+
+# ----------------------------------------------------
+# Include dependencies
+# ----------------------------------------------------
+
+megaco_codec_transform.$(EMULATOR): megaco_codec_transform.erl
+
+megaco_codec_meas.$(EMULATOR): megaco_codec_meas.erl
+
+megaco_codec_mstone1.$(EMULATOR): megaco_codec_mstone1.erl
+
+megaco_codec_mstone2.$(EMULATOR): megaco_codec_mstone2.erl
+
+megaco_codec_mstone_lib.$(EMULATOR): megaco_codec_mstone_lib.erl
+
diff --git a/lib/inets/examples/httpd_load_test/hdlt.config.skel b/lib/inets/examples/httpd_load_test/hdlt.config.skel
new file mode 100644
index 0000000000..640867ebac
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt.config.skel
@@ -0,0 +1,20 @@
+%% Debug: silence | info | log | debug
+{debug, [{ctrl, info}, {proxy, silence}, {slave, silence}, {client, silence}]}.
+{server, {"/usr/local/bin", "fooserver"}}.
+%% {port, 8888}. % integer() > 0
+{server_dir, "/tmp/hdlt"}. % Absolute path
+{work_dir, "/tmp/hdlt"}. % Absolute path
+{clients,
+ [
+ {"/opt/local/bin", "foo"},
+ {"/usr/local/bin", "bar"}
+ ]
+}.
+%% {send_rate, 80}. % Max number of outstanding requests, integer() > 0
+%% {test_time, 120}. % Number of seconds,
+%% {max_nof_schedulers, 8}. % integer() >= 0
+%% {work_simulator, 10000}. % integer() > 0
+%% {data_size, {100, 500, 2}}. % {integer() > 0, integer() > 0, integer() > 0}
+%% {socket_type, ip_comm}. % ip_comm | ssl | essl | ossl
+%% {server_cert_file, "hdlt_ssl_server_cert.pem"}.
+%% {client_cert_file, "hdlt_ssl_client_cert.pem"}. \ No newline at end of file
diff --git a/lib/inets/examples/httpd_load_test/hdlt.erl b/lib/inets/examples/httpd_load_test/hdlt.erl
new file mode 100644
index 0000000000..18d8c34ccf
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt.erl
@@ -0,0 +1,74 @@
+%%
+%% %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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Main API module for the httpd load test utility
+%%----------------------------------------------------------------------
+
+-module(hdlt).
+
+
+%%-----------------------------------------------------------------
+%% Public interface
+%%-----------------------------------------------------------------
+
+-export([start/0, start/1, stop/0, help/0]).
+
+
+%%-----------------------------------------------------------------
+%% Start the HDLT utility
+%%-----------------------------------------------------------------
+
+start() ->
+ ConfigFile = "hdlt.config",
+ case file:consult(ConfigFile) of
+ {ok, Config} when is_list(Config) ->
+ start(Config);
+ Error ->
+ Error
+ end.
+
+start(Config) ->
+ Flag = process_flag(trap_exit, true),
+ Result =
+ case hdlt_ctrl:start(Config) of
+ {ok, Pid} ->
+ receive
+ {'EXIT', Pid, normal} ->
+ ok;
+ {'EXIT', Pid, Reason} ->
+ io:format("HDLT failed: "
+ "~n ~p"
+ "~n", [Reason]),
+ {error, Reason}
+ end;
+ Error ->
+ Error
+ end,
+ process_flag(trap_exit, Flag),
+ Result.
+
+
+
+stop() ->
+ hdlt_ctrl:stop().
+
+
+help() ->
+ hdlt_ctrl:help().
diff --git a/lib/inets/examples/httpd_load_test/hdlt.sh.skel b/lib/inets/examples/httpd_load_test/hdlt.sh.skel
new file mode 100644
index 0000000000..a250bad9c5
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt.sh.skel
@@ -0,0 +1,44 @@
+#!/bin/sh
+
+#
+# %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%
+
+# Skeleton for a script intended to run the hdlt(N)
+# performance test.
+#
+# This test can be used for several things depending on the
+# configuration: SMP or SocketType performance tests
+#
+
+ERL_HOME=<path to otp top dir>
+INETS_HOME=$ERL_HOME/lib/erlang/lib/<inets dir>
+HDLT_HOME=$INETS_HOME/examples/httpd_load_test
+PATH=$ERL_HOME/bin:$PATH
+
+HDLT="-s hdlt start"
+STOP="-s init stop"
+
+ERL="erl \
+ -noshell \
+ -pa $HDLT_HOME \
+ $HDLT \
+ $STOP"
+
+echo $ERL
+$ERL | tee hdlt.log
+
diff --git a/lib/inets/examples/httpd_load_test/hdlt_client.erl b/lib/inets/examples/httpd_load_test/hdlt_client.erl
new file mode 100644
index 0000000000..d65ac5a885
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_client.erl
@@ -0,0 +1,370 @@
+%%
+%% %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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The HDLT client module.
+%% This is the traffic generator
+%%----------------------------------------------------------------------
+
+-module(hdlt_client).
+
+-export([
+ start/1,
+ stop/0,
+ start_inets/0,
+ start_service/1,
+ release/0,
+ node_info/0
+ ]).
+
+-export([
+ proxy/1
+ ]).
+
+-include("hdlt_logger.hrl").
+
+-define(CTRL, hdlt_ctrl).
+-define(PROXY, hdlt_proxy).
+
+-record(state,
+ {
+ mode = initial,
+ send_rate,
+ time,
+ stop_time,
+ url,
+ nof_reqs = 0,
+ nof_reps = 0,
+ last_req,
+ sizes,
+ socket_type,
+ cert_file
+ }).
+
+
+
+start(Debug) ->
+ proc_lib:start_link(?MODULE, proxy, [Debug]).
+
+stop() ->
+ (catch erlang:send(?PROXY, stop)),
+ ok.
+
+start_inets() ->
+ ?PROXY ! start_inets.
+
+start_service(Args) ->
+ ?PROXY ! {start_client, Args, self()},
+ receive
+ client_started ->
+ %% ?LOG("client service started"),
+ ok
+ end.
+
+release() ->
+ ?PROXY ! release.
+
+node_info() ->
+ ?PROXY ! {node_info, self()},
+ receive
+ {node_info, NodeInfo} ->
+ NodeInfo
+ end.
+
+
+%% ---------------------------------------------------------------------
+%%
+%% The proxy process
+%%
+
+proxy(Debug) ->
+ process_flag(trap_exit, true),
+ erlang:register(?PROXY, self()),
+ SName = lists:flatten(
+ io_lib:format("HDLT PROXY[~p,~p]", [self(), node()])),
+ ?SET_NAME(SName),
+ ?SET_LEVEL(Debug),
+ ?LOG("starting", []),
+ Ref = await_for_controller(10),
+ CtrlNode = node(Ref),
+ erlang:monitor_node(CtrlNode, true),
+ proc_lib:init_ack({ok, self()}),
+ ?DEBUG("started", []),
+ proxy_loop(Ref, CtrlNode, undefined).
+
+await_for_controller(N) when N > 0 ->
+ case global:whereis_name(hdlt_ctrl) of
+ Pid when is_pid(Pid) ->
+ erlang:monitor(process, Pid);
+ _ ->
+ timer:sleep(1000),
+ await_for_controller(N-1)
+ end;
+await_for_controller(_) ->
+ proc_lib:init_ack({error, controller_not_found, nodes()}),
+ timer:sleep(500),
+ init:stop().
+
+
+proxy_loop(Ref, CtrlNode, Client) ->
+ ?DEBUG("await command", []),
+ receive
+ stop ->
+ ?LOG("stop", []),
+ timer:sleep(1000),
+ halt();
+
+ start_inets ->
+ ?LOG("start the inets service framework", []),
+ %% inets:enable_trace(max, "/tmp/inets-httpc-trace.log", all),
+ case (catch inets:start()) of
+ ok ->
+ ?LOG("framework started", []),
+ proxy_loop(Ref, CtrlNode, Client);
+ Error ->
+ ?LOG("failed starting inets service framework: "
+ "~n Error: ~p", [Error]),
+ timer:sleep(1000),
+ halt()
+ end;
+
+ {start_client, Args, From} ->
+ ?LOG("start client with"
+ "~n Args: ~p", [Args]),
+ Client2 = spawn_link(fun() -> client(Args) end),
+ From ! client_started,
+ proxy_loop(Ref, CtrlNode, Client2);
+
+ release ->
+ ?LOG("release", []),
+ Client ! go,
+ proxy_loop(Ref, CtrlNode, Client);
+
+ {node_info, Pid} ->
+ ?LOG("received requets for node info", []),
+ NodeInfo = get_node_info(),
+ Pid ! {node_info, NodeInfo},
+ proxy_loop(Ref, CtrlNode, Client);
+
+ {'EXIT', Client, normal} ->
+ ?LOG("received normal exit message from client (~p)",
+ [Client]),
+ exit(normal);
+
+ {'EXIT', Client, Reason} ->
+ ?INFO("received exit message from client (~p)"
+ "~n Reason: ~p", [Client, Reason]),
+ %% Unexpected client termination, inform the controller and die
+ global:send(hdlt_ctrl, {client_exit, Client, node(), Reason}),
+ exit({client_exit, Reason});
+
+ {nodedown, CtrlNode} ->
+ ?LOG("received nodedown for controller node - terminate", []),
+ halt();
+
+ {'DOWN', Ref, process, _, _} ->
+ ?INFO("received DOWN message for controller - terminate", []),
+ %% The controller has terminated, dont care why, time to die
+ halt()
+
+ end.
+
+
+
+%% ---------------------------------------------------------------------
+%%
+%% The client process
+%%
+
+client([SocketType, CertFile, URLBase, Sizes, Time, SendRate, Debug]) ->
+ SName = lists:flatten(
+ io_lib:format("HDLT CLIENT[~p,~p]", [self(), node()])),
+ ?SET_NAME(SName),
+ ?SET_LEVEL(Debug),
+ ?LOG("starting with"
+ "~n SocketType: ~p"
+ "~n Time: ~p"
+ "~n SendRate: ~p", [SocketType, Time, SendRate]),
+ httpc:set_options([{max_pipeline_length, 0}]),
+ if
+ (SocketType =:= ssl) orelse
+ (SocketType =:= ossl) orelse
+ (SocketType =:= essl) ->
+ %% Ensure crypto and ssl started:
+ crypto:start(),
+ ssl:start();
+ true ->
+ ok
+ end,
+ State = #state{mode = idle,
+ url = URLBase,
+ time = Time,
+ send_rate = SendRate,
+ sizes = Sizes,
+ socket_type = SocketType,
+ cert_file = CertFile},
+ ?DEBUG("started", []),
+ client_loop(State).
+
+%% The point is to first start all client nodes and then this
+%% process. Then, when they are all started, the go-ahead, go,
+%% message is sent to let them lose at the same time.
+client_loop(#state{mode = idle,
+ time = Time,
+ send_rate = SendRate} = State) ->
+ ?DEBUG("[idle] awaiting the go command", []),
+ receive
+ go ->
+ ?LOG("[idle] received go", []),
+ erlang:send_after(Time, self(), stop),
+ NewState = send_requests(State, SendRate),
+ client_loop(NewState#state{mode = generating,
+ nof_reqs = SendRate})
+ end;
+
+%% In this mode the client is generating traffic.
+%% It will continue to do so until the stop message
+%% is received.
+client_loop(#state{mode = generating} = State) ->
+ receive
+ stop ->
+ ?LOG("[generating] received stop", []),
+ StopTime = timestamp(),
+ req_reply(State),
+ client_loop(State#state{mode = stopping, stop_time = StopTime});
+
+ {http, {_, {{_, 200, _}, _, _}}} ->
+ %% ?DEBUG("[generating] received reply - send another request", []),
+ NewState = send_requests(State, 1),
+ client_loop(NewState#state{nof_reps = NewState#state.nof_reps + 1,
+ nof_reqs = NewState#state.nof_reqs + 1});
+
+ {http, {ReqId, {error, Reason}}} ->
+ ?INFO("[generating] request ~p failed: "
+ "~n Reason: ~p"
+ "~n NofReqs: ~p"
+ "~n NofReps: ~p",
+ [ReqId, Reason, State#state.nof_reqs, State#state.nof_reps]),
+ exit({Reason, generating, State#state.nof_reqs, State#state.nof_reps});
+
+ Else ->
+ ?LOG("[generating] received unexpected message: "
+ "~n~p", [Else]),
+ unexpected_data(Else),
+ client_loop(State)
+ end;
+
+%% The client no longer issues any new requests, instead it
+%% waits for replies for all the oustanding requests to
+%% arrive.
+client_loop(#state{mode = stopping,
+ time = Time,
+ last_req = LastReqId} = State) ->
+ receive
+ {http, {LastReqId, {{_, 200, _}, _, _}}} ->
+ ?DEBUG("[stopping] received reply for last request (~p)", [LastReqId]),
+ time_to_complete(State),
+ ok;
+
+ {http, {ReqId, {{_, 200, _}, _, _}}} ->
+ ?DEBUG("[stopping] received reply ~p", [ReqId]),
+ client_loop(State);
+
+ {http, {ReqId, {error, Reason}}} ->
+ ?INFO("[stopping] request ~p failed: "
+ "~n Reason: ~p"
+ "~n NofReqs: ~p"
+ "~n NofReps: ~p",
+ [ReqId, Reason, State#state.nof_reqs, State#state.nof_reps]),
+ exit({Reason, stopping, State#state.nof_reqs, State#state.nof_reps});
+
+ Else ->
+ ?LOG("[stopping] received unexpected message: "
+ "~n~p", [Else]),
+ unexpected_data(Else),
+ client_loop(State)
+
+ after Time ->
+ ?INFO("timeout when"
+ "~n Number of requests: ~p"
+ "~n Number of replies: ~p",
+ [State#state.nof_reqs, State#state.nof_reps]),
+ exit({timeout, State#state.nof_reqs, State#state.nof_reps})
+ end.
+
+req_reply(#state{nof_reqs = NofReqs, nof_reps = NofReps}) ->
+ load_data({req_reply, node(), NofReqs, NofReps}).
+
+time_to_complete(#state{stop_time = StopTime}) ->
+ StoppedTime = os:timestamp(),
+ load_data({time_to_complete, node(), StopTime, StoppedTime}).
+
+load_data(Data) ->
+ global:send(?CTRL, {load_data, Data}).
+
+unexpected_data(Else) ->
+ global:send(?CTRL, {unexpected_data, Else}).
+
+
+send_requests(#state{sizes = Sizes} = State, N) ->
+ send_requests(State, N, Sizes).
+
+send_requests(State, 0, Sizes) ->
+ State#state{sizes = Sizes};
+send_requests(#state{socket_type = SocketType,
+ cert_file = CertFile} = State, N, [Sz | Sizes]) ->
+ URL = lists:flatten(io_lib:format("~s~w", [State#state.url, Sz])),
+ Method = get,
+ Request = {URL, []},
+ HTTPOptions =
+ case SocketType of
+ ip_comm ->
+ [];
+ _ ->
+ SslOpts = [{verify, 0},
+ {certfile, CertFile},
+ {keyfile, CertFile}],
+ case SocketType of
+ ssl ->
+ [{ssl, SslOpts}];
+ ossl ->
+ [{ssl, {ossl, SslOpts}}];
+ essl ->
+ [{ssl, {essl, SslOpts}}]
+ end
+ end,
+ Options = [{sync, false}],
+ {ok, Ref} = httpc:request(Method, Request, HTTPOptions, Options),
+ send_requests(State#state{last_req = Ref}, N-1, lists:append(Sizes, [Sz])).
+
+
+timestamp() ->
+ os:timestamp().
+
+
+get_node_info() ->
+ [{cpu_topology, erlang:system_info(cpu_topology)},
+ {heap_type, erlang:system_info(heap_type)},
+ {nof_schedulers, erlang:system_info(schedulers)},
+ {otp_release, erlang:system_info(otp_release)},
+ {version, erlang:system_info(version)},
+ {system_version, erlang:system_info(system_version)},
+ {system_architecture, erlang:system_info(system_architecture)}].
+
+
diff --git a/lib/inets/examples/httpd_load_test/hdlt_ctrl.erl b/lib/inets/examples/httpd_load_test/hdlt_ctrl.erl
new file mode 100644
index 0000000000..950d2632f7
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_ctrl.erl
@@ -0,0 +1,1530 @@
+%%
+%% %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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The httpd load test (hdlt) controller/collector module,
+%% This module contains all the code of the httpd load test
+%% controller/collector. It sets up the test, starts all
+%% server and client nodes and applications and finally
+%% collects test data.
+%%----------------------------------------------------------------------
+
+-module(hdlt_ctrl).
+
+-export([start/1, stop/0, help/0]).
+
+-export([init/1, proxy/7]).
+
+-include_lib("kernel/include/file.hrl").
+-include("hdlt_logger.hrl").
+
+-define(DEFAULT_SENDRATE, 89).
+-define(DEFAULT_TEST_TIME, 120). % 2 minutes
+-define(DEFAULT_PORT, 8889).
+-define(TIMEOUT, 60000).
+-define(DEFAULT_MAX_NOF_SCHEDULERS, 8).
+-define(DEFAULT_SERVER_DIR, "/tmp/hdlt").
+-define(DEFAULT_WORK_DIR, "/tmp/hdlt").
+-define(SSH_PORT, 22).
+-define(DEFAULT_SOCKET_TYPE, ip_comm).
+-define(DEFAULT_SERVER_CERT, "hdlt_ssl_server_cert.pem").
+-define(DEFAULT_CLIENT_CERT, "hdlt_ssl_client_cert.pem").
+-define(SSH_CONNECT_TIMEOUT, 5000).
+-define(NODE_START_TIMEOUT, 5000).
+-define(LOCAL_PROXY_START_TIMEOUT, ?NODE_START_TIMEOUT * 4).
+-define(DEFAULT_DEBUGS,
+ [{ctrl, info}, {slave, silence}, {proxy, silence}, {client, silence}]).
+-define(DEFAULT_WORK_SIM, 10000).
+-define(DEFAULT_DATA_SIZE_START, 500).
+-define(DEFAULT_DATA_SIZE_END, 1500).
+-define(DEFAULT_DATA_SIZE_INCR, 1).
+-define(DEFAULT_DATA_SIZE, {?DEFAULT_DATA_SIZE_START,
+ ?DEFAULT_DATA_SIZE_END,
+ ?DEFAULT_DATA_SIZE_INCR}).
+
+
+%% hdlt = httpd load test
+
+-define(COLLECTOR, hdlt_ctrl).
+-define(RESULTS_TAB, hdlt_results).
+
+-define(CLIENT_MOD, hdlt_client).
+-define(CLIENT_NODE_NAME, ?CLIENT_MOD).
+
+-define(SERVER_MOD, hdlt_server).
+-define(SERVER_NODE_NAME, ?SERVER_MOD).
+
+-define(LOGGER, hdlt_logger).
+
+
+-record(state,
+ {
+ url,
+ test_time,
+ send_rate,
+ http_server,
+ http_port,
+ results = ?RESULTS_TAB,
+ nodes,
+ server_root,
+ doc_root,
+ server_dir,
+ work_dir,
+ server_conn,
+ client_conns = [],
+ client_mod = ?CLIENT_MOD,
+ clients,
+ nof_schedulers = 0,
+ max_nof_schedulers,
+ socket_type,
+ server_cert_file,
+ client_cert_file,
+ debugs,
+ client_sz_from,
+ client_sz_to,
+ client_sz_incr
+ }
+ ).
+
+-record(proxy,
+ {
+ mode,
+ mod,
+ connection,
+ channel,
+ host,
+ cmd,
+ node_name,
+ node,
+ ref,
+ erl_path,
+ paths,
+ args
+ }).
+
+-record(connection,
+ {
+ proxy,
+ node,
+ node_name,
+ host
+ }).
+
+
+-record(client, {host, path, version}).
+-record(server, {host, path, version}).
+
+
+start(Config) when is_list(Config) ->
+ proc_lib:start_link(?MODULE, init, [Config]).
+
+stop() ->
+ global:send(?COLLECTOR, stop).
+
+init(Config) ->
+ %% io:format("Config: ~n~p~n", [Config]),
+ case (catch do_init(Config)) of
+ {ok, State} ->
+ proc_lib:init_ack({ok, self()}),
+ loop(State);
+ {error, _Reason} = Error ->
+ proc_lib:init_ack(Error),
+ ok;
+ {'EXIT', Reason} ->
+ proc_lib:init_ack({error, Reason}),
+ ok
+ end.
+
+do_init(Config) ->
+ %% Do not trap exit, but register ourself
+ global:register_name(?COLLECTOR, self()),
+
+ State = #state{},
+ ets:new(State#state.results, [bag, named_table]),
+
+ hdlt_logger:start(),
+ global:sync(),
+
+ %% Maybe enable debug
+ Debugs = get_debugs(Config),
+ ?SET_NAME("HDLT CTRL"),
+ set_debug_level(Debugs),
+
+ ?DEBUG("network info: "
+ "~n Global names: ~p"
+ "~n Nodes: ~p", [global:registered_names(), nodes()]),
+
+ %% Read config
+ ?LOG("read config", []),
+ SendRate = get_send_rate(Config),
+ Clients = get_clients(Config),
+ TestTime = get_test_time(Config),
+ Server = get_server(Config),
+ Port = get_port(Config),
+ ServerDir = get_server_dir(Config),
+ WorkingDir = get_work_dir(Config),
+ MaxNofSchedulers = get_max_nof_schedulers(Config),
+ SocketType = get_socket_type(Config),
+ ServerCertFile = get_server_cert_file(Config),
+ ClientCertFile = get_client_cert_file(Config),
+ WorkSim = get_work_sim(Config),
+ {From, To, Incr} = get_data_size(Config),
+
+ URL = url(Server, Port, SocketType, WorkSim),
+ ServerRoot = filename:join(ServerDir, "server_root"),
+ DocRoot = ServerRoot, %% Not really used in this test
+
+ ?DEBUG("randomize setup", []),
+ randomized_sizes_init(),
+
+ %% Start used applications
+ ?DEBUG("ensure crypto started", []),
+ crypto:start(),
+ ?DEBUG("ensure ssh started", []),
+ ssh:start(),
+
+ State2 = State#state{server_root = ServerRoot,
+ doc_root = DocRoot,
+ server_dir = ServerDir,
+ work_dir = WorkingDir,
+ max_nof_schedulers = MaxNofSchedulers,
+ socket_type = SocketType,
+ server_cert_file = ServerCertFile,
+ client_cert_file = ClientCertFile,
+ http_server = Server,
+ http_port = Port,
+ url = URL,
+ test_time = TestTime,
+ send_rate = SendRate,
+ clients = Clients,
+ debugs = Debugs,
+ client_sz_from = From,
+ client_sz_to = To,
+ client_sz_incr = Incr},
+
+ ?LOG("prepare server host", []),
+ prepare_server_host(State2),
+
+ ?LOG("prepare client hosts", []),
+ State3 = prepare_client_hosts(State2),
+
+ ?LOG("basic init done", []),
+ {ok, State3}.
+
+
+loop(#state{nof_schedulers = N, max_nof_schedulers = M} = State) when N > M ->
+
+ ?INFO("Starting to analyse data", []),
+
+ AnalysedTab = analyse_data(State),
+
+ Files = save_results_to_file(AnalysedTab, State),
+ io:format("~n******************************************************"
+ "~n~nResult(s) saved to: ~n~p~n", [Files]),
+ clean_up(State);
+
+loop(#state{url = URL,
+ test_time = TestTime,
+ send_rate = SendRate,
+ nof_schedulers = NofSchedulers} = State) ->
+
+ {StartH, StartM, StartS} = erlang:time(),
+
+ ?INFO("Performing test with ~p smp-scheduler(s): ~n"
+ " It will take a minimum of: ~p seconds. ~n"
+ " Start time: ~.2.0w:~.2.0w:~.2.0w",
+ [NofSchedulers, round(TestTime/1000), StartH, StartM, StartS]),
+
+ %% Start the server node
+ %% (The local proxy, the node, the remote proxy, and the inets framework)
+ State1 = start_server_node(State),
+ ?DEBUG("nodes after server start: ~p", [nodes() -- [node()]]),
+
+ %% Start the client node(s)
+ %% (The local proxy, the node, the remote proxy, and the inets framework)
+ ?LOG("start client node(s)", []),
+ State2 = start_client_nodes(State1),
+ ?DEBUG("nodes after client(s) start: ~p", [nodes() -- [node()]]),
+
+ ?LOG("start server", []),
+ start_server(State2),
+
+ ?LOG("start clients", []),
+ start_clients(State2, URL, TestTime, SendRate),
+
+ ?LOG("release clients", []),
+ release_clients(State2),
+
+ ?LOG("collect data", []),
+ collect_data(State2),
+
+ ?LOG("stop all nodes", []),
+ State3 = stop_nodes(State2),
+
+ ?INFO("Test with ~p smp-scheduler(s) complete"
+ "~n~n"
+ "****************************************************************"
+ "~n",
+ [NofSchedulers]),
+ loop(State3#state{nof_schedulers = NofSchedulers + 1}).
+
+
+prepare_server_host(#state{server_root = ServerRoot,
+ http_server = #server{host = Host},
+ socket_type = SocketType,
+ server_cert_file = CertFile}) ->
+ ?INFO("prepare server host ~s", [Host]),
+ Opts = [{user_interaction, false},
+ {silently_accept_hosts, true},
+ {timeout, 2*?SSH_CONNECT_TIMEOUT},
+ {connect_timeout, ?SSH_CONNECT_TIMEOUT}],
+ case ssh_sftp:start_channel(Host, Opts) of
+ {ok, Sftp, ConnectionRef} ->
+ ?DEBUG("sftp connection established - now transer server content",
+ []),
+ create_server_content(Sftp, ServerRoot, SocketType, CertFile),
+ ?DEBUG("server content transfered - now close ssh connection ",
+ []),
+ ssh:close(ConnectionRef),
+ ?DEBUG("server preparation complete ", []),
+ ok;
+ Error ->
+ ?INFO("FAILED creating sftp channel to server host ~s: "
+ "~n ~p", [Host, Error]),
+ exit({failed_establishing_sftp_connection, Error})
+ end.
+
+create_server_content(Sftp, ServerRoot, SocketType, CertFile) ->
+ %% Create server root
+ ?DEBUG("ensure existence of ~p", [ServerRoot]),
+ ensure_remote_dir_exist(Sftp, ServerRoot),
+
+ %% Create the server ebin dir (for the starter module)
+ EBIN = filename:join(ServerRoot, "ebin"),
+ ?DEBUG("make ebin dir: ~p", [EBIN]),
+ maybe_create_remote_dir(Sftp, EBIN),
+
+ %% Create the server ebin dir (for the starter module)
+ LOG = filename:join(ServerRoot, "log"),
+ ?DEBUG("make log dir: ~p", [LOG]),
+ maybe_create_remote_dir(Sftp, LOG),
+
+ LocalServerMod = local_server_module(),
+ ?DEBUG("copy server stub/proxy module ~s", [LocalServerMod]),
+ RemoteServerMod = remote_server_module(EBIN),
+ {ok, ServerModBin} = file:read_file(LocalServerMod),
+ ok = ssh_sftp:write_file(Sftp, RemoteServerMod, ServerModBin),
+
+ LocalSlaveMod = local_slave_module(),
+ ?DEBUG("copy slave module ~s", [LocalSlaveMod]),
+ RemoteSlaveMod = remote_slave_module(EBIN),
+ {ok, SlaveModBin} = file:read_file(LocalSlaveMod),
+ ok = ssh_sftp:write_file(Sftp, RemoteSlaveMod, SlaveModBin),
+
+ LocalLoggerMod = local_logger_module(),
+ ?DEBUG("copy logger module ~s", [LocalLoggerMod]),
+ RemoteLoggerMod = remote_logger_module(EBIN),
+ {ok, LoggerModBin} = file:read_file(LocalLoggerMod),
+ ok = ssh_sftp:write_file(Sftp, RemoteLoggerMod, LoggerModBin),
+
+ %% Create the inets server data dir
+ CGI = filename:join(ServerRoot, "cgi-bin"),
+ ?DEBUG("make cgi dir: ~p", [CGI]),
+ maybe_create_remote_dir(Sftp, CGI),
+
+ LocalRandomMod = local_random_html_module(),
+ ?DEBUG("copy random-html module ~s", [LocalRandomMod]),
+ RemoteRandomMod = remote_random_html_module(EBIN),
+ {ok, RandomModBin} = file:read_file(LocalRandomMod),
+ ok = ssh_sftp:write_file(Sftp, RemoteRandomMod, RandomModBin),
+
+ case SocketType of
+ ip_comm ->
+ ok;
+ _ ->
+ SSLDir = filename:join(ServerRoot, "ssl"),
+ ?DEBUG("make conf dir: ~p", [SSLDir]),
+ maybe_create_remote_dir(Sftp, SSLDir),
+ ?DEBUG("copy ssl cert file ~s", [CertFile]),
+ {ok, CertBin} = file:read_file(CertFile),
+ RemoteCertFile = filename:join(SSLDir,
+ filename:basename(CertFile)),
+ ok = ssh_sftp:write_file(Sftp, RemoteCertFile, CertBin),
+ ok
+ end,
+
+ ?DEBUG("done", []),
+ ok.
+
+remote_server_module(Path) ->
+ Mod = server_module(),
+ filename:join(Path, Mod).
+
+local_server_module() ->
+ Mod = server_module(),
+ case code:where_is_file(Mod) of
+ Path when is_list(Path) ->
+ Path;
+ _ ->
+ exit({server_module_not_found, Mod})
+ end.
+
+server_module() ->
+ module(?SERVER_MOD).
+
+
+prepare_client_hosts(#state{work_dir = WorkDir,
+ clients = Clients,
+ socket_type = SocketType,
+ client_cert_file = CertFile} = State) ->
+ Clients2 =
+ prepare_client_hosts(WorkDir, SocketType, CertFile, Clients, []),
+ State#state{clients = Clients2}.
+
+prepare_client_hosts(_WorkDir, _SocketType, _CertFile, [], Acc) ->
+ lists:reverse(Acc);
+prepare_client_hosts(WorkDir, SocketType, CertFile, [Client|Clients], Acc) ->
+ case prepare_client_host(WorkDir, SocketType, CertFile, Client) of
+ ok ->
+ prepare_client_hosts(WorkDir, SocketType, CertFile, Clients,
+ [Client|Acc]);
+ _ ->
+ prepare_client_hosts(WorkDir, SocketType, CertFile, Clients, Acc)
+ end.
+
+prepare_client_host(WorkDir, SocketType, CertFile, #client{host = Host}) ->
+ ?INFO("prepare client host ~s", [Host]),
+ Opts = [{user_interaction, false},
+ {silently_accept_hosts, true},
+ {timeout, 2*?SSH_CONNECT_TIMEOUT},
+ {connect_timeout, ?SSH_CONNECT_TIMEOUT}],
+ case ssh_sftp:start_channel(Host, Opts) of
+ {ok, Sftp, ConnectionRef} ->
+ ?DEBUG("sftp connection established - now transer client content",
+ []),
+ create_client_content(Sftp, WorkDir, SocketType, CertFile),
+ ?DEBUG("client content transered - now close ssh connection ", []),
+ ssh:close(ConnectionRef),
+ ?DEBUG("client preparation complete ", []),
+ ok;
+ Error ->
+ ?INFO("FAILED creating sftp channel to client host ~s: skipping"
+ "~n ~p", [Host, Error]),
+ Error
+ end.
+
+create_client_content(Sftp, WorkDir, SocketType, CertFile) ->
+ %% Create work dir
+ ?DEBUG("ensure existence of ~p", [WorkDir]),
+ ensure_remote_dir_exist(Sftp, WorkDir),
+
+ %% Create the client ebin dir
+ EBIN = filename:join(WorkDir, "ebin"),
+ RemoteClientMod = remote_client_module(EBIN),
+ ?DEBUG("make ebin dir: ~p", [EBIN]),
+ maybe_create_remote_dir(Sftp, EBIN),
+
+ LocalClientMod = local_client_module(),
+ ?DEBUG("copy client stub/proxy module ~s", [LocalClientMod]),
+ {ok, ClientModBin} = file:read_file(LocalClientMod),
+ ok = ssh_sftp:write_file(Sftp, RemoteClientMod, ClientModBin),
+
+ LocalSlaveMod = local_slave_module(),
+ ?DEBUG("copy slave module ~s", [LocalSlaveMod]),
+ RemoteSlaveMod = remote_slave_module(EBIN),
+ {ok, SlaveModBin} = file:read_file(LocalSlaveMod),
+ ok = ssh_sftp:write_file(Sftp, RemoteSlaveMod, SlaveModBin),
+
+ LocalLoggerMod = local_logger_module(),
+ ?DEBUG("copy logger module ~s", [LocalLoggerMod]),
+ RemoteLoggerMod = remote_logger_module(EBIN),
+ {ok, LoggerModBin} = file:read_file(LocalLoggerMod),
+ ok = ssh_sftp:write_file(Sftp, RemoteLoggerMod, LoggerModBin),
+
+ case SocketType of
+ ip_comm ->
+ ok;
+ _ ->
+ %% We should really store the remote path somewhere as
+ %% we use it when starting the client service...
+ SSLDir = filename:join(WorkDir, "ssl"),
+ ?DEBUG("make ssl dir: ~p", [SSLDir]),
+ maybe_create_remote_dir(Sftp, SSLDir),
+ ?DEBUG("copy ssl cert file ~s", [CertFile]),
+ {ok, CertBin} = file:read_file(CertFile),
+ RemoteCertFile = filename:join(SSLDir,
+ filename:basename(CertFile)),
+ ok = ssh_sftp:write_file(Sftp, RemoteCertFile, CertBin),
+ ok
+ end,
+
+ ?DEBUG("done", []),
+ ok.
+
+remote_client_module(Path) ->
+ Mod = client_module(),
+ filename:join(Path, Mod).
+
+local_client_module() ->
+ Mod = client_module(),
+ case code:where_is_file(Mod) of
+ Path when is_list(Path) ->
+ Path;
+ _ ->
+ exit({client_module_not_found, Mod})
+ end.
+
+client_module() ->
+ module(?CLIENT_MOD).
+
+
+remote_slave_module(Path) ->
+ Mod = slave_module(),
+ filename:join(Path, Mod).
+
+local_slave_module() ->
+ Mod = slave_module(),
+ case code:where_is_file(Mod) of
+ Path when is_list(Path) ->
+ Path;
+ _ ->
+ exit({slave_module_not_found, Mod})
+ end.
+
+slave_module() ->
+ module(hdlt_slave).
+
+
+remote_logger_module(Path) ->
+ Mod = logger_module(),
+ filename:join(Path, Mod).
+
+local_logger_module() ->
+ Mod = logger_module(),
+ case code:where_is_file(Mod) of
+ Path when is_list(Path) ->
+ Path;
+ _ ->
+ exit({logger_module_not_found, Mod})
+ end.
+
+logger_module() ->
+ module(hdlt_logger).
+
+
+remote_random_html_module(Path) ->
+ Mod = random_html_module(),
+ filename:join(Path, Mod).
+
+local_random_html_module() ->
+ Mod = random_html_module(),
+ case code:where_is_file(Mod) of
+ Path when is_list(Path) ->
+ Path;
+ _ ->
+ exit({random_module_not_found, Mod})
+ end.
+
+random_html_module() ->
+ module(hdlt_random_html).
+
+
+module(Mod) ->
+ Ext = string:to_lower(erlang:system_info(machine)),
+ lists:flatten(io_lib:format("~w.~s", [Mod, Ext])).
+
+
+%% -----------------------------------------------------------------------
+%% - For every node created (server and client both) there is both
+%% a local and remote proxy.
+%% - The local proxy is running on the local (controller/collector) node.
+%% - The remote proxy is running on the client or server node(s).
+%% - The local (ctrl) proxy monitor the remote (server/client) proxy.
+%% - The remote (server/client) proxy monitor the local (ctrl) proxy.
+%%
+
+start_client_nodes(#state{clients = Clients,
+ work_dir = WorkDir,
+ debugs = Debugs} = State) ->
+ Connections =
+ [start_client_node(Client, WorkDir, Debugs) || Client <- Clients],
+ State#state{client_conns = Connections}.
+
+start_client_node(#client{path = ErlPath, host = Host}, WorkDir, Debugs) ->
+ ?INFO("start client on host ~p", [Host]),
+ EbinDir = filename:join(WorkDir, "ebin"),
+ start_client_node(Host, ErlPath, [EbinDir], Debugs).
+
+start_client_node(Host, ErlPath, Paths, Debugs) ->
+ start_node(Host, ?CLIENT_NODE_NAME,
+ ErlPath, Paths, [], ?CLIENT_MOD, Debugs).
+
+
+start_server_node(#state{http_server = #server{path = ErlPath, host = Host},
+ server_root = ServerRoot,
+ nof_schedulers = NofScheds,
+ debugs = Debugs} = State) ->
+ ?INFO("start server on host ~p", [Host]),
+ CgiBinDir = filename:join(ServerRoot, "cgi-bin"),
+ EbinDir = filename:join(ServerRoot, "ebin"),
+ Connection =
+ start_server_node(Host, ErlPath, [CgiBinDir, EbinDir],
+ Debugs, NofScheds),
+ State#state{server_conn = Connection}.
+
+start_server_node(Host, ErlPath, Paths, Debugs, NofScheds) ->
+ Args =
+ if
+ NofScheds =:= 0 ->
+ "-smp disable";
+ true ->
+ lists:flatten(io_lib:format("-smp +S ~w", [NofScheds]))
+ end,
+ start_node(Host, ?SERVER_NODE_NAME,
+ ErlPath, Paths, Args, ?SERVER_MOD, Debugs).
+
+
+%% -----------------------------------------------------------------------
+%% - For every node created (server and client both) there is both
+%% a local and remote proxy.
+%% - The local proxy is running on the local (controller/collector) node.
+%% - The remote proxy is running on the client or server node(s).
+%% - The local (ctrl) proxy monitor the remote (server/client) proxy.
+%% - The remote (server/client) proxy monitor the local (ctrl) proxy.
+%%
+
+start_node(Host, NodeName, ErlPath, Paths, Args, Module, Debugs) ->
+ %% Start the (local) proxy
+ ?DEBUG("start_node -> start local proxy and remote node", []),
+ ProxyDebug = proplists:get_value(proxy, Debugs, silence),
+ Proxy = proxy_start(Host, NodeName, ErlPath, Paths, Args, Module,
+ ProxyDebug),
+
+ ?DEBUG("start_node -> local proxy started - now start node", []),
+ SlaveDebug = proplists:get_value(slave, Debugs, silence),
+ Node = proxy_start_node(Proxy, SlaveDebug),
+
+ ?DEBUG("start_node -> sync global", []),
+ global:sync(),
+
+ ?DEBUG("start_node -> start remote proxy", []),
+ proxy_start_remote(Proxy),
+
+ ?DEBUG("start_node -> start (remote) inets framework", []),
+ proxy_start_inets(Proxy),
+
+ ?DEBUG("start_node -> done", []),
+ #connection{proxy = Proxy, node = Node, node_name = NodeName, host = Host}.
+
+
+proxy_start(Host, NodeName, ErlPath, Paths, Args, Module, Debug) ->
+ ?LOG("try starting local proxy for ~p@~s", [NodeName, Host]),
+ ProxyArgs = [Host, NodeName, ErlPath, Paths, Args, Module, Debug],
+ case proc_lib:start_link(?MODULE, proxy,
+ ProxyArgs, ?LOCAL_PROXY_START_TIMEOUT) of
+ {ok, Proxy} ->
+ Proxy;
+ Error ->
+ exit({failed_starting_proxy, Error})
+ end.
+
+proxy_start_node(Proxy, Debug) ->
+ {ok, Node} = proxy_request(Proxy, {start_node, Debug}),
+ Node.
+
+proxy_start_remote(Proxy) ->
+ proxy_request(Proxy, start_remote_proxy).
+
+proxy_start_inets(Proxy) ->
+ proxy_request(Proxy, start_inets).
+
+proxy_start_service(Proxy, Args) ->
+ proxy_request(Proxy, {start_service, Args}).
+
+proxy_release(Proxy) ->
+ proxy_request(Proxy, release).
+
+proxy_stop(Proxy) ->
+ StopResult = proxy_request(Proxy, stop),
+ ?DEBUG("proxy stop result: ~p", [StopResult]),
+ StopResult.
+
+proxy_request(Proxy, Req) ->
+ Ref = make_ref(),
+ Proxy ! {proxy_request, Ref, self(), Req},
+ receive
+ {proxy_reply, Ref, Proxy, Rep} ->
+ Rep
+ end.
+
+proxy_reply(From, Ref, Rep) ->
+ From ! {proxy_reply, Ref, self(), Rep}.
+
+proxy(Host, NodeName, ErlPath, Paths, Args, Module, Debug) ->
+ process_flag(trap_exit, true),
+ SName = lists:flatten(
+ io_lib:format("HDLT CTRL PROXY[~p,~s,~w]",
+ [self(), Host, NodeName])),
+ ?SET_NAME(SName),
+ ?SET_LEVEL(Debug),
+ ?LOG("starting with"
+ "~n Host: ~p"
+ "~n NodeName: ~p"
+ "~n ErlPath: ~p"
+ "~n Paths: ~p"
+ "~n Args: ~p"
+ "~n Module: ~p", [Host, NodeName, ErlPath, Paths, Args, Module]),
+ State = #proxy{mode = started,
+ mod = Module,
+ host = Host,
+ node_name = NodeName,
+ erl_path = ErlPath,
+ paths = Paths,
+ args = Args},
+ proc_lib:init_ack({ok, self()}),
+ ?DEBUG("started", []),
+ proxy_loop(State).
+
+
+proxy_loop(#proxy{mode = stopping}) ->
+ receive
+ {proxy_request, Ref, From, stop} ->
+ ?LOG("[stopping] received stop order", []),
+ proxy_reply(From, Ref, ok),
+ exit(normal);
+
+ {'EXIT', Pid, Reason} ->
+ ?INFO("[stopping] received exit message from ~p: "
+ "~n Reason: ~p", [Pid, Reason]),
+ exit(Reason)
+
+ end;
+
+proxy_loop(#proxy{mode = started,
+ host = Host,
+ node_name = NodeName,
+ erl_path = ErlPath,
+ paths = Paths,
+ args = Args} = State) ->
+ receive
+ {proxy_request, Ref, From, {start_node, Debug}} ->
+ ?LOG("[starting] received start_node order", []),
+ case hdlt_slave:start_link(Host, NodeName,
+ ErlPath, Paths, Args,
+ Debug) of
+ {ok, Node} ->
+ ?DEBUG("[starting] node ~p started - now monitor", [Node]),
+ erlang:monitor_node(Node, true),
+ State2 = State#proxy{mode = operational,
+ node = Node},
+ proxy_reply(From, Ref, {ok, Node}),
+ proxy_loop(State2);
+ {error, Reason} ->
+ ?INFO("[starting] failed starting node: "
+ "~n Reason: ~p", [Reason]),
+ exit({failed_starting_node, {Host, NodeName, Reason}})
+ end;
+
+ {'EXIT', Pid, Reason} ->
+ ?INFO("[stopping] received exit message from ~p: "
+ "~n Reason: ~p", [Pid, Reason]),
+ exit(Reason)
+
+ end;
+
+proxy_loop(#proxy{mode = operational,
+ mod = Mod,
+ node = Node} = State) ->
+ ?DEBUG("[operational] await command", []),
+ receive
+ {proxy_request, Ref, From, start_remote_proxy} ->
+ ?LOG("[operational] start remote proxy", []),
+ case rpc:call(Node, Mod, start, [?GET_LEVEL()]) of
+ {ok, Pid} ->
+ ?DEBUG("[operational] remote proxy started (~p) - "
+ "create monitor", [Pid]),
+ ProxyRef = erlang:monitor(process, Pid),
+ ?DEBUG("[operational] monitor: ~p", [Ref]),
+ proxy_reply(From, Ref, ok),
+ proxy_loop(State#proxy{ref = ProxyRef});
+ Error ->
+ ?INFO("[operational] failed starting remote proxy"
+ "~n Error: ~p", [Error]),
+ ReplyReason = {failed_starting_remote_proxy,
+ {Node, Error}},
+ Reply = {error, ReplyReason},
+ proxy_reply(From, Ref, Reply),
+ exit({failed_starting_remote_proxy, {Node, Error}})
+ end;
+
+ {proxy_request, Ref, From, start_inets} ->
+ ?INFO("[operational] start inets framework", []),
+ rpc:cast(Node, Mod, start_inets, []),
+ proxy_reply(From, Ref, ok),
+ proxy_loop(State);
+
+ {proxy_request, Ref, From, {start_service, Args}} ->
+ ?INFO("[operational] start service with"
+ "~n ~p", [Args]),
+ case rpc:call(Node, Mod, start_service, Args) of
+ ok ->
+ ?DEBUG("[operational] service started", []),
+ proxy_reply(From, Ref, ok),
+ proxy_loop(State);
+ Error ->
+ ?INFO("[operational] failed starting service: "
+ "~n Args. ~p"
+ "~n Error: ~p", [Args, Error]),
+ erlang:demonitor(State#proxy.ref, [flush]),
+ Reply = {error, {failed_starting_service, Node, Error}},
+ proxy_reply(From, Ref, Reply),
+ exit({failed_starting_service, Node, Error})
+ end;
+
+ {proxy_request, Ref, From, release} ->
+ ?INFO("[operational] release", []),
+ rpc:call(Node, Mod, release, []),
+ proxy_reply(From, Ref, ok),
+ proxy_loop(State);
+
+ {proxy_request, Ref, From, stop} ->
+ ?INFO("[operational] received stop order", []),
+ erlang:demonitor(State#proxy.ref, [flush]),
+ ?DEBUG("[operational] rpc cast stop order", []),
+ rpc:cast(Node, Mod, stop, []),
+ %% And wait for the node death to be reported
+ Reason =
+ receive
+ {nodedown, Node} when State#proxy.node =:= Node ->
+ ok
+ after 10000 ->
+ ?INFO("Node did not die within expected time frame",
+ []),
+ {node_death_timeout, Node}
+ end,
+ ?DEBUG("[operational] ack stop", []),
+ proxy_reply(From, Ref, Reason),
+ exit(normal);
+
+ {nodedown, Node} when State#proxy.node =:= Node ->
+ ?INFO("[operational] received unexpected nodedoen message", []),
+ exit({node_died, Node});
+
+ {'DOWN', Ref, process, _, normal} when State#proxy.ref =:= Ref ->
+ ?INFO("[operational] remote proxy terminated normally", []),
+ proxy_loop(State#proxy{ref = undefined,
+ connection = undefined,
+ mode = stopping});
+
+ {'DOWN', Ref, process, _, noconnection} when State#proxy.ref =:= Ref ->
+ ?INFO("[operational] remote proxy terminated - no node", []),
+ proxy_loop(State#proxy{ref = undefined,
+ connection = undefined,
+ mode = stopping});
+
+ {'DOWN', Ref, process, _, Reason} when State#proxy.ref =:= Ref ->
+ ?INFO("[operational] remote proxy terminated: "
+ "~n Reason: ~p", [Reason]),
+ exit({remote_proxy_crash, Reason});
+
+ {'EXIT', Pid, Reason} ->
+ ?INFO("[operational] received unexpected exit message from ~p: "
+ "~n Reason: ~p", [Pid, Reason]),
+ proxy_loop(State)
+
+ end.
+
+
+stop_nodes(#state{server_conn = ServerConn,
+ client_conns = ClientConns} = State) ->
+ lists:foreach(
+ fun(#connection{proxy = Proxy, node_name = NodeName, host = Host}) ->
+ ?DEBUG("stop_erlang_nodes -> send stop order to local proxy ~p"
+ "~n for node ~p on ~s", [Proxy, NodeName, Host]),
+ proxy_stop(Proxy)
+ end,
+ ClientConns ++ [ServerConn]),
+ ?DEBUG("stop_erlang_nodes -> sleep some to give the nodes time to die",
+ []),
+ timer:sleep(1000),
+ ?DEBUG("stop_erlang_nodes -> and a final cleanup round", []),
+ lists:foreach(fun(Node) ->
+ ?INFO("try brutal stop node ~p", [Node]),
+ rpc:cast(Node, erlang, halt, [])
+ end,
+ nodes() -- [node()]),
+ ?DEBUG("stop_erlang_nodes -> done", []),
+ State#state{server_conn = undefined, client_conns = []}.
+
+
+%% The nodes on which the HDLT clients run have been started previously
+start_clients(#state{client_conns = Connections,
+ debugs = Debugs,
+ work_dir = WorkDir,
+ socket_type = SocketType,
+ client_cert_file = CertFile,
+ client_sz_from = From,
+ client_sz_to = To,
+ client_sz_incr = Incr},
+ URL, TestTime, SendRate) ->
+ Debug = proplists:get_value(client, Debugs, silence),
+ StartClient =
+ fun(#connection{host = Host} = Connection) ->
+ ?DEBUG("start client on ~p", [Host]),
+ start_client(Connection,
+ WorkDir, SocketType, CertFile,
+ URL, From, To, Incr,
+ TestTime, SendRate, Debug);
+ (_) ->
+ ok
+ end,
+ lists:foreach(StartClient, Connections).
+
+start_client(#connection{proxy = Proxy},
+ WorkDir, SocketType, LocalCertFile,
+ URL, From, To, Incr,
+ TestTime, SendRate, Debug) ->
+ SSLDir = filename:join(WorkDir, "ssl"),
+ CertFile = filename:join(SSLDir, filename:basename(LocalCertFile)),
+ Sizes = randomized_sizes(From, To, Incr),
+ Args = [SocketType, CertFile, URL, Sizes, TestTime, SendRate, Debug],
+ proxy_start_service(Proxy, [Args]).
+
+release_clients(#state{client_conns = Connections}) ->
+ ReleaseClient =
+ fun(#connection{proxy = Proxy,
+ host = Host}) ->
+ ?DEBUG("release client on ~p", [Host]),
+ proxy_release(Proxy);
+ (_) ->
+ ok
+ end,
+ lists:foreach(ReleaseClient, Connections).
+
+
+start_server(#state{server_conn = #connection{proxy = Proxy},
+ http_port = Port,
+ server_root = ServerRoot,
+ doc_root = DocRoot,
+ socket_type = SocketType,
+ server_cert_file = CertFile}) ->
+
+ HttpdConfig =
+ httpd_config(Port, "hdlt", ServerRoot, DocRoot, SocketType, CertFile),
+ ?LOG("start the httpd inets service with config: "
+ "~n ~p", [HttpdConfig]),
+ proxy_start_service(Proxy, [HttpdConfig]),
+ ?DEBUG("start_server -> done", []),
+ ok.
+
+
+httpd_config(Port, ServerName, ServerRoot, DocRoot,
+ SocketType, LocalCertFile) ->
+ LogDir = filename:join(ServerRoot, "log"),
+ ErrorLog = filename:join(LogDir, "error_log"),
+ TransferLog = filename:join(LogDir, "access_log"),
+
+ SSL =
+ case SocketType of
+ ip_comm ->
+ [];
+ _ -> % ssl
+ SSLDir = filename:join(ServerRoot, "ssl"),
+ CertFile =
+ filename:join(SSLDir, filename:basename(LocalCertFile)),
+ [
+ {ssl_certificate_file, CertFile},
+ {ssl_certificate_key_file, CertFile},
+ {ssl_verify_client, 0}
+ ]
+ end,
+ [{port, Port},
+ {server_name, ServerName},
+ {server_root, ServerRoot},
+ {document_root, DocRoot},
+ {error_log, ErrorLog},
+ {error_log_format, pretty},
+ {transfer_log, TransferLog},
+ {socket_type, SocketType},
+ {max_clients, 10000},
+ {modules, [mod_alias, mod_auth, mod_esi, mod_actions, mod_cgi,
+ mod_dir, mod_get, mod_head, mod_log, mod_disk_log]},
+ {script_alias, {"/cgi-bin", filename:join(ServerRoot, "cgi-bin")}},
+ {erl_script_alias, {"/cgi-bin", [hdlt_random_html]}},
+ {erl_script_timeout, 120000} | SSL].
+
+
+clean_up(#state{server_root = ServerRoot,
+ work_dir = WorkDir,
+ http_server = #server{host = Host},
+ clients = Clients}) ->
+ ?DEBUG("begin server cleanup", []),
+ server_clean_up(ServerRoot, WorkDir, Host),
+ ?DEBUG("begin lient cleanup", []),
+ clients_clean_up(WorkDir, Clients),
+ ?DEBUG("cleanup done", []),
+ ok.
+
+server_clean_up(ServerRoot, WorkDir, Host) ->
+ ?DEBUG("server cleanup - create sftp channel", []),
+ {ok, Sftp, ConnectionRef} =
+ ssh_sftp:start_channel(Host, [{user_interaction, false},
+ {silently_accept_hosts, true}]),
+ ?DEBUG("server cleanup - delete ~p dirs", [ServerRoot]),
+ del_dirs(Sftp, ServerRoot),
+ ?DEBUG("server cleanup - delete ~p dirs", [WorkDir]),
+ del_dirs(Sftp, WorkDir),
+ ?DEBUG("server cleanup - close sftp channel", []),
+ ssh:close(ConnectionRef).
+
+clients_clean_up(_WorkDir, []) ->
+ ok;
+clients_clean_up(WorkDir, [Client|Clients]) ->
+ client_clean_up(WorkDir, Client),
+ clients_clean_up(WorkDir, Clients).
+
+client_clean_up(WorkDir, #client{host = Host}) ->
+ ?DEBUG("client cleanup - create sftp channel to ~p", [Host]),
+ {ok, Sftp, ConnectionRef} =
+ ssh_sftp:start_channel(Host, [{user_interaction, false},
+ {silently_accept_hosts, true}]),
+ ?DEBUG("client cleanup - delete ~p dirs", [WorkDir]),
+ del_dirs(Sftp, WorkDir),
+ ?DEBUG("client cleanup - close sftp channel", []),
+ ssh:close(ConnectionRef).
+
+
+del_dirs(Sftp, Dir) ->
+ case ssh_sftp:list_dir(Sftp, Dir) of
+ {ok, []} ->
+ ssh_sftp:del_dir(Sftp, Dir);
+ {ok, Files} ->
+ Files2 = [F || F <- Files, (F =/= "..") andalso (F =/= ".")],
+ lists:foreach(fun(File) when ((File =/= "..") andalso
+ (File =/= ".")) ->
+ FullPath = filename:join(Dir, File),
+ case ssh_sftp:read_file_info(Sftp,
+ FullPath) of
+ {ok, #file_info{type = directory}} ->
+ del_dirs(Sftp, FullPath),
+ ssh_sftp:del_dir(Sftp, FullPath);
+ {ok, _} ->
+ ssh_sftp:delete(Sftp, FullPath)
+ end
+ end, Files2);
+ _ ->
+ ok
+ end.
+
+collect_data(#state{clients = Clients} = State) ->
+ N = length(Clients),
+ collect_req_reply(N, State),
+ collect_time(N, State).
+
+collect_req_reply(0, _State) ->
+ ?DEBUG("all reply data collected", []),
+ ok;
+collect_req_reply(N, #state{nof_schedulers = NofScheduler,
+ results = Db,
+ client_conns = Conns} = State) ->
+ ?DEBUG("await reply data from ~p client(s)", [N]),
+ receive
+ {load_data,
+ {req_reply, Client, NoRequests, NoReplys}} ->
+ ?DEBUG("received req_reply load-data from client ~p: "
+ "~n Number of requests: ~p"
+ "~n Number of replies: ~p",
+ [Client, NoRequests, NoReplys]),
+ ets:insert(Db, {{NofScheduler, Client},
+ {req_reply, NoRequests, NoReplys}});
+ stop ->
+ ?INFO("received stop", []),
+ exit(self(), stop);
+
+ {client_exit, Client, Node, Reason} ->
+ ?INFO("Received unexpected client exit from ~p on node ~p "
+ "while collecting replies: "
+ "~n ~p", [Client, Node, Reason]),
+ case lists:keysearch(Node, #connection.node, Conns) of
+ {value, Conn} ->
+ ?LOG("Found problem connection: "
+ "~n ~p", [Conn]),
+ exit({unexpected_client_exit, Reason});
+ false ->
+ collect_req_reply(N, State)
+ end
+ end,
+ collect_req_reply(N-1, State).
+
+collect_time(0, _State) ->
+ ?DEBUG("all time data collected", []),
+ ok;
+collect_time(N, #state{nof_schedulers = NofScheduler,
+ results = Db,
+ client_conns = Conns} = State) ->
+ ?DEBUG("await time data from ~p clients", [N]),
+ receive
+ {load_data,
+ {time_to_complete, Client, StopTime, LastResponseTime}} ->
+ ?LOG("received time load-data from client ~p: "
+ "~n Time of stop: ~p"
+ "~n Time of last response: ~p",
+ [Client, StopTime, LastResponseTime]),
+ ets:insert(Db, {{NofScheduler, Client},
+ {time, StopTime, LastResponseTime}});
+ stop ->
+ ?INFO("received stop while collecting data, when N = ~p", [N]),
+ exit(self(), stop);
+
+ {client_exit, Client, Node, Reason} ->
+ ?INFO("Received unexpected exit from client ~p on node ~p "
+ "while collecting time data: "
+ "~n ~p", [Client, Node, Reason]),
+ case lists:keysearch(Node, #connection.node, Conns) of
+ {value, Conn} ->
+ ?LOG("Found problem connection: "
+ "~n ~p", [Conn]),
+ exit({unexpected_client_exit, Reason});
+ false ->
+ collect_req_reply(N, State)
+ end;
+
+ Else -> %%% Something is wrong!
+ ?INFO("RECEIVED UNEXPECTED MESSAGE WHILE COLLECTING TIME DATA: "
+ "~n ~p", [Else]),
+ collect_time(N, State)
+ end,
+ collect_time(N-1, State).
+
+analyse_data(#state{results = Db,
+ max_nof_schedulers = MaxNofSchedulers,
+ test_time = MicroSec}) ->
+ Tab = ets:new(analysed_results, [set]),
+ lists:foreach(fun(NofSchedulers) ->
+ Result = analyse(NofSchedulers, Db, MicroSec),
+ ets:insert(Tab, Result)
+ end, [N || N <- lists:seq(0, MaxNofSchedulers)]),
+ Tab.
+
+
+no_requests_replys(NoSchedulers, Tab) ->
+ NoRequests =
+ ets:select(Tab, [{{{NoSchedulers,'_'},{req_reply, '$1', '_'}},
+ [],['$$']}]),
+ NoReplys =
+ ets:select(Tab, [{{{NoSchedulers, '_'}, {req_reply, '_', '$1'}},
+ [], ['$$']}]),
+
+ {lists:sum(lists:append(NoRequests)),
+ lists:sum(lists:append(NoReplys))}.
+
+max_time_to_final_response(NofSchedulers, Tab) ->
+ Candidates =
+ ets:select(Tab, [{{{NofSchedulers, '_'}, {time, '$1', '$2'}},
+ [], ['$$']}]),
+
+ NewCandidates = lists:map(
+ fun([StopTime, LastTime]) ->
+ round(
+ timer:now_diff(LastTime, StopTime) / 100000)/10
+ end, Candidates),
+
+ lists:max(NewCandidates).
+
+
+analyse(NofSchedulers, Db, TestTime) ->
+ Sec = TestTime / 1000,
+ {NoRequests, NoReplys} = no_requests_replys(NofSchedulers, Db),
+ {NofSchedulers, round(NoReplys / Sec), NoRequests,
+ max_time_to_final_response(NofSchedulers, Db)}.
+
+
+save_results_to_file(AnalysedTab,
+ #state{socket_type = SocketType,
+ http_server = #server{host = Server},
+ max_nof_schedulers = MaxNofSchedulers}) ->
+ FileName = fun(Post) ->
+ File =
+ lists:flatten(
+ io_lib:format("~s_~w_~s",
+ [Server, SocketType, Post])),
+ filename:join("./", File)
+ end,
+ Reps = FileName("replys_per_sec.txt"),
+ Reqs = FileName("total_requests.txt"),
+ Decay = FileName("decay_time.txt"),
+
+ [FdReps, FdReqs, FdDecay] =
+ lists:map(fun(File) ->
+ {ok, Fd} = file:open(File, [write]),
+ Fd
+ end, [Reps, Reqs, Decay]),
+ lists:foreach(fun(NofSchedulers) ->
+ save_result_to_file(NofSchedulers,
+ FdReps, FdReqs,
+ FdDecay, AnalysedTab)
+ end, [N || N <- lists:seq(0, MaxNofSchedulers)]),
+ [Reps, Reqs, Decay].
+
+save_result_to_file(NofSchedulers,
+ FdReps, FdReqs, FdDecay, AnalysedTab) ->
+
+ [{NofSchedulers, NofRepsPerSec, NofReqs, MaxFinalResponseTime}] =
+ ets:lookup(AnalysedTab, NofSchedulers),
+
+ file:write(FdReps, io_lib:format("~p,~p~n",
+ [NofRepsPerSec, NofSchedulers])),
+ file:write(FdReqs, io_lib:format("~p,~p~n",
+ [NofReqs, NofSchedulers])),
+ file:write(FdDecay, io_lib:format("~p,~p~n", [MaxFinalResponseTime,
+ NofSchedulers])).
+
+
+help() ->
+ io:format("hdlt:start(Options). Where options:~n "
+ " ~n~p~n~n hdlt:start([]). -> hdlt:start(~p)~n~n",
+ [[{send_rate, "integer()",
+ "Numer of outstanding requests that a client "
+ "should have during the test to create a load situation."},
+ {clients, "[{path(), host()}]", "Paths to erlang and names of hosts to run clients on."},
+ {test_time, "{hours(), mins(), sec()}",
+ "How long the test should be run."},
+ {server, "{path(), host()}", "Path to erl and name of host to run the HTTP-server on."},
+ {port, "port()", "The port that the HTTP-server should use."},
+ {server_dir, "dir()", "The directory where the HTTP server "
+ " stores its contents and configuration."},
+ {work_dir, "dir()", "Path on the computer, where the test "
+ "is run, to a directory where the results can be saved."},
+ {max_no_schedulers, "integer()",
+ "Max number of schedulers to run."},
+ {socket_type, "Httpd configuration option socket_type"}],
+ defaults()]).
+
+
+defaults() ->
+ [{send_rate, ?DEFAULT_SENDRATE},
+ %% {clients, []},
+ {test_time, ?DEFAULT_TEST_TIME},
+ %% {server, ?DEFAULT_SERVER},
+ {port, ?DEFAULT_PORT},
+ {server_dir, ?DEFAULT_SERVER_DIR},
+ {work_dir, ?DEFAULT_WORK_DIR},
+ {max_nof_schedulers, ?DEFAULT_MAX_NOF_SCHEDULERS},
+ {socket_type, ?DEFAULT_SOCKET_TYPE}].
+
+
+get_debugs(Config) ->
+ ?DEBUG("get debugs", []),
+ Debugs = proplists:get_value(debug, Config, ?DEFAULT_DEBUGS),
+ verify_debugs(Debugs),
+ Debugs.
+
+verify_debugs([]) ->
+ ok;
+verify_debugs([{Tag, Debug}|Debugs]) ->
+ verify_debug(Tag, Debug),
+ verify_debugs(Debugs).
+
+verify_debug(Tag, Debug) ->
+ case lists:member(Tag, [ctrl, proxy, slave, client]) of
+ true ->
+ ok;
+ false ->
+ exit({bad_debug_tag, Tag})
+ end,
+ case lists:member(Debug, [silence, info, log, debug]) of
+ true ->
+ ok;
+ false ->
+ exit({bad_debug_level, Debug})
+ end.
+
+get_send_rate(Config) ->
+ ?DEBUG("get send_rate", []),
+ case proplists:get_value(send_rate, Config, ?DEFAULT_SENDRATE) of
+ SendRate when is_integer(SendRate) andalso (SendRate > 0) ->
+ SendRate;
+ BadSendRate ->
+ exit({bad_sendrate, BadSendRate})
+ end.
+
+
+get_clients(Config) ->
+ ?DEBUG("get clients", []),
+ case proplists:get_value(clients, Config, undefined) of
+ undefined ->
+ missing_mandatory_config(clients);
+ Clients when is_list(Clients) andalso (length(Clients) > 0) ->
+ case [#client{path = Path, host = Host} ||
+ {Path, Host} <- Clients] of
+ Clients2 when (length(Clients2) > 0) ->
+ Clients2;
+ _ ->
+ exit({bad_clients, Clients})
+ end;
+
+ BadClients ->
+ exit({bad_clients, BadClients})
+
+ end.
+
+get_server(Config) ->
+ ?DEBUG("get server", []),
+ case proplists:get_value(server, Config) of
+ {Path, Host} when is_list(Path) andalso is_list(Host) ->
+ #server{path = Path, host = Host};
+ undefined ->
+ missing_mandatory_config(server)
+ end.
+
+get_server_dir(Config) ->
+ ?DEBUG("get server_dir", []),
+ get_dir(server_dir, Config, ?DEFAULT_SERVER_DIR).
+
+get_work_dir(Config) ->
+ ?DEBUG("get work_dir", []),
+ get_dir(work_dir, Config, ?DEFAULT_WORK_DIR).
+
+get_dir(Key, Config, Default) ->
+ Dir = proplists:get_value(Key, Config, Default),
+ ensure_absolute(Dir),
+ Dir.
+
+ensure_absolute(Path) ->
+ case filename:pathtype(Path) of
+ absolute ->
+ ok;
+ PathType ->
+ exit({bad_pathtype, Path, PathType})
+ end.
+
+get_port(Config) ->
+ ?DEBUG("get port", []),
+ case proplists:get_value(port, Config, ?DEFAULT_PORT) of
+ Port when is_integer(Port) andalso (Port > 0) ->
+ Port;
+ BadPort ->
+ exit({bad_port, BadPort})
+ end.
+
+get_socket_type(Config) ->
+ ?DEBUG("get socket_type", []),
+ case proplists:get_value(socket_type, Config, ?DEFAULT_SOCKET_TYPE) of
+ SocketType when ((SocketType =:= ip_comm) orelse
+ (SocketType =:= ssl) orelse
+ (SocketType =:= essl) orelse
+ (SocketType =:= ossl)) ->
+ SocketType;
+ BadSocketType ->
+ exit({bad_socket_type, BadSocketType})
+ end.
+
+get_test_time(Config) ->
+ ?DEBUG("get test_time", []),
+ case proplists:get_value(test_time, Config, ?DEFAULT_TEST_TIME) of
+ Seconds when is_integer(Seconds) andalso (Seconds > 0) ->
+ timer:seconds(Seconds);
+ BadTestTime ->
+ exit({bad_test_time, BadTestTime})
+ end.
+
+get_max_nof_schedulers(Config) ->
+ ?DEBUG("get max_nof_schedulers", []),
+ case proplists:get_value(max_nof_schedulers,
+ Config,
+ ?DEFAULT_MAX_NOF_SCHEDULERS) of
+ MaxNofScheds when (is_integer(MaxNofScheds) andalso
+ (MaxNofScheds >= 0)) ->
+ MaxNofScheds;
+ BadMaxNofScheds ->
+ exit({bad_max_nof_schedulers, BadMaxNofScheds})
+ end.
+
+
+get_server_cert_file(Config) ->
+ ?DEBUG("get server cert file", []),
+ get_cert_file(server_cert_file, ?DEFAULT_SERVER_CERT, Config).
+
+get_client_cert_file(Config) ->
+ ?DEBUG("get client cert file", []),
+ get_cert_file(client_cert_file, ?DEFAULT_CLIENT_CERT, Config).
+
+get_cert_file(Tag, DefaultCertFileName, Config) ->
+ LibDir = code:lib_dir(inets),
+ HdltDir = filename:join(LibDir, "examples/httpd_load_test"),
+ DefaultCertFile = filename:join(HdltDir, DefaultCertFileName),
+ case proplists:get_value(Tag, Config, DefaultCertFile) of
+ F when is_list(F) ->
+ case file:read_file_info(F) of
+ {ok, #file_info{type = regular}} ->
+ F;
+ {ok, #file_info{type = Type}} ->
+ exit({wrong_file_type, Tag, F, Type});
+ {error, Reason} ->
+ exit({failed_readin_file_info, Tag, F, Reason})
+ end;
+ BadFile ->
+ exit({bad_cert_file, Tag, BadFile})
+ end.
+
+
+get_work_sim(Config) ->
+ ?DEBUG("get work_sim", []),
+ case proplists:get_value(work_simulator, Config, ?DEFAULT_WORK_SIM) of
+ WS when is_integer(WS) andalso (WS > 0) ->
+ WS;
+ BadWS ->
+ exit({bad_work_simulator, BadWS})
+ end.
+
+
+get_data_size(Config) ->
+ ?DEBUG("get data_size", []),
+ case proplists:get_value(data_size, Config, ?DEFAULT_DATA_SIZE) of
+ {From, To, Incr} = DS when (is_integer(From) andalso
+ is_integer(To) andalso
+ is_integer(Incr) andalso
+ (To > From) andalso
+ (From > 0) andalso
+ (Incr > 0)) ->
+ DS;
+ {From, To} when (is_integer(From) andalso
+ is_integer(To) andalso
+ (To > From) andalso
+ (From > 0)) ->
+ {From, To, ?DEFAULT_DATA_SIZE_INCR};
+ BadDS ->
+ exit({bad_data_size, BadDS})
+ end.
+
+
+url(#server{host = Host}, Port, SocketType, WorkSim) ->
+ Scheme =
+ case SocketType of
+ ip_comm ->
+ "http";
+ _ -> %% SSL
+ "https"
+ end,
+ lists:flatten(
+ io_lib:format("~s://~s:~w/cgi-bin/hdlt_random_html:page?~w:",
+ [Scheme, Host, Port, WorkSim])).
+
+
+missing_mandatory_config(Missing) ->
+ exit({missing_mandatory_config, Missing}).
+
+
+ensure_remote_dir_exist(Sftp, Path0) ->
+ case filename:split(Path0) of
+ [Root, Dir | Rest] ->
+ %% We never accept creating the root directory,
+ %% or the next level, so these *must* exist:
+ Path = filename:join(Root, Dir),
+ case ssh_sftp:read_file_info(Sftp, Path) of
+ {ok, #file_info{type = directory}} ->
+ ensure_remote_dir_exist(Sftp, Path, Rest);
+ {ok, #file_info{type = Type}} ->
+ ?INFO("Not a dir: ~p (~p)", [Path, Type]),
+ exit({not_a_dir, Path, Type});
+ {error, Reason} ->
+ ?INFO("Failed reading file info for ~p: ~p",
+ [Path, Reason]),
+ exit({failed_reading_file_info, Path, Reason})
+ end;
+ BadSplit ->
+ ?INFO("Bad remote dir path: ~p -> ~p", [Path0, BadSplit]),
+ exit({bad_dir, Path0})
+ end.
+
+ensure_remote_dir_exist(_Sftp, _Dir, []) ->
+ ok;
+ensure_remote_dir_exist(Sftp, Path, [Dir|Rest]) ->
+ NewPath = filename:join(Path, Dir),
+ case ssh_sftp:read_file_info(Sftp, NewPath) of
+ {ok, #file_info{type = directory}} ->
+ ensure_remote_dir_exist(Sftp, NewPath, Rest);
+ {ok, #file_info{type = Type}} ->
+ %% Exist, but is not a dir
+ ?INFO("Not a dir: ~p (~p)", [NewPath, Type]),
+ exit({not_a_dir, NewPath, Type});
+ {error, Reason} ->
+ %% This *could* be because the dir does not exist,
+ %% but it could also be some other error.
+ %% As usual, the error reason of the sftp is
+ %% a pease of crap, so we cannot use the
+ %% error reason.
+ %% The simplest way to test this is to simply
+ %% try to create the directory, since we should
+ %% ensure its existence anyway..
+ case ssh_sftp:make_dir(Sftp, NewPath) of
+ ok ->
+ ensure_remote_dir_exist(Sftp, NewPath, Rest);
+ _ ->
+ ?INFO("Failed reading file info for ~p: ~p",
+ [Dir, Reason]),
+ exit({failed_reading_file_info, NewPath, Reason})
+ end
+ end.
+
+maybe_create_remote_dir(Sftp, Dir) ->
+ case ssh_sftp:read_file_info(Sftp, Dir) of
+ {ok, #file_info{type = directory}} ->
+ ok;
+ {ok, #file_info{type = Type}} ->
+ %% Exist, but is not a dir
+ ?INFO("Not a dir: ~p (~p)", [Dir, Type]),
+ exit({not_a_dir, Dir, Type});
+ {error, Reason} ->
+ %% Assume dir noes not exist...
+ case ssh_sftp:make_dir(Sftp, Dir) of
+ ok ->
+ ok;
+ _ ->
+ ?INFO("Failed reading file info for ~p: ~p",
+ [Dir, Reason]),
+ exit({failed_reading_file_info, Dir, Reason})
+ end
+ end.
+
+
+set_debug_level(Debugs) ->
+ Debug = proplists:get_value(ctrl, Debugs, silence),
+ ?SET_LEVEL(Debug).
+
+
+%% Generates a list of numbers between A and B, such that
+%% there is exact one number between A and B and then
+%% randomizes that list.
+
+randomized_sizes_init() ->
+ {A, B, C} = os:timestamp(),
+ random:seed(A, B, C).
+
+randomized_sizes(From, To, Incr) ->
+ L = lists:seq(From, To, Incr),
+ Len = length(L),
+ randomized_sizes2(L, 0, Len-1).
+
+randomized_sizes2(L, N, Len) when N >= Len ->
+ L;
+randomized_sizes2(L, N, Len) ->
+ SplitWhere = random:uniform(Len),
+ {A, B} = lists:split(SplitWhere, L),
+ randomized_sizes2(B ++ A, N+1, Len).
diff --git a/lib/inets/examples/httpd_load_test/hdlt_logger.erl b/lib/inets/examples/httpd_load_test/hdlt_logger.erl
new file mode 100644
index 0000000000..b0c7eab2d1
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_logger.erl
@@ -0,0 +1,138 @@
+%%
+%% %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%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: This is a simple logger utility for the HDLT toolkit.
+%% It assumesd that the debug level and the "name" of the
+%% logging entity has been put in process environment
+%% (using the set_level and set_name functions respectively).
+%%----------------------------------------------------------------------
+
+%%
+
+-module(hdlt_logger).
+
+-export([
+ start/0,
+ set_level/1, get_level/0, set_name/1,
+ info/2, log/2, debug/2
+ ]).
+
+-export([logger/1]).
+
+-define(LOGGER, ?MODULE).
+-define(MSG, hdlt_logger_msg).
+-define(LEVEL, hdlt_logger_level).
+-define(NAME, hdlt_logger_name).
+-define(INFO_STR, "INFO").
+-define(LOG_STR, "LOG ").
+-define(DEBUG_STR, "DBG ").
+
+
+start() ->
+ Self = self(),
+ proc_lib:start(?MODULE, logger, [Self]).
+
+set_name(Name) when is_list(Name) ->
+ put(?NAME, Name),
+ ok.
+
+get_level() ->
+ get(?LEVEL).
+
+set_level(Level) ->
+ case lists:member(Level, [silence, info, log, debug]) of
+ true ->
+ put(?LEVEL, Level),
+ ok;
+ false ->
+ erlang:error({bad_debug_level, Level})
+ end.
+
+
+info(F, A) ->
+%% io:format("info -> " ++ F ++ "~n", A),
+ do_log(info, get(?LEVEL), F, A).
+
+log(F, A) ->
+%% io:format("log -> " ++ F ++ "~n", A),
+ do_log(log, get(?LEVEL), F, A).
+
+debug(F, A) ->
+%% io:format("debug -> " ++ F ++ "~n", A),
+ do_log(debug, get(?LEVEL), F, A).
+
+
+logger(Parent) ->
+ global:register_name(?LOGGER, self()),
+ Ref = erlang:monitor(process, Parent),
+ proc_lib:init_ack(self()),
+ logger_loop(Ref).
+
+logger_loop(Ref) ->
+ receive
+ {?MSG, F, A} ->
+ io:format(F, A),
+ logger_loop(Ref);
+ {'DOWN', Ref, process, _Object, _Info} ->
+ %% start the stop timer
+ erlang:send_after(timer:seconds(5), self(), stop),
+ logger_loop(undefined);
+ stop ->
+ global:unregister_name(?LOGGER),
+ ok
+ end.
+
+
+formated_timestamp() ->
+ {Date, Time} = erlang:localtime(),
+ {YYYY,MM,DD} = Date,
+ {Hour,Min,Sec} = Time,
+ FormatDate =
+ io_lib:format("~.4w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
+ [YYYY,MM,DD,Hour,Min,Sec]),
+ lists:flatten(FormatDate).
+
+do_log(_, silence, _, _) ->
+ ok;
+do_log(info, info, F, A) ->
+ do_log(?INFO_STR, F, A);
+do_log(info, log, F, A) ->
+ do_log(?INFO_STR, F, A);
+do_log(log, log, F, A) ->
+ do_log(?LOG_STR, F, A);
+do_log(info, debug, F, A) ->
+ do_log(?INFO_STR, F, A);
+do_log(log, debug, F, A) ->
+ do_log(?LOG_STR, F, A);
+do_log(debug, debug, F, A) ->
+ do_log(?DEBUG_STR, F, A);
+do_log(_, _, _F, _A) ->
+ ok.
+
+do_log(SEV, F, A) ->
+ Name =
+ case get(?NAME) of
+ L when is_list(L) ->
+ L;
+ _ ->
+ "UNDEFINED"
+ end,
+ Msg = {?MSG, "~s ~s [~s] " ++ F ++ "~n",
+ [SEV, Name, formated_timestamp() | A]},
+ (catch global:send(?LOGGER, Msg)).
diff --git a/lib/inets/examples/httpd_load_test/hdlt_logger.hrl b/lib/inets/examples/httpd_load_test/hdlt_logger.hrl
new file mode 100644
index 0000000000..aa94babc48
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_logger.hrl
@@ -0,0 +1,33 @@
+%%
+%% %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%
+%%
+%%
+
+-ifndef(hdlt_logger_hrl).
+-define(hdlt_logger_hrl, true).
+
+%% Various log macros
+-define(SET_LEVEL(N), hdlt_logger:set_level(N)).
+-define(GET_LEVEL(), hdlt_logger:get_level()).
+-define(SET_NAME(N), hdlt_logger:set_name(N)).
+
+-define(INFO(F, A), hdlt_logger:info(F, A)).
+-define(LOG(F, A), hdlt_logger:log(F, A)).
+-define(DEBUG(F, A), hdlt_logger:debug(F, A)).
+
+-endif. % -ifdef(hdlt_logger_hrl).
diff --git a/lib/inets/examples/httpd_load_test/hdlt_random_html.erl b/lib/inets/examples/httpd_load_test/hdlt_random_html.erl
new file mode 100644
index 0000000000..e3a572c61f
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_random_html.erl
@@ -0,0 +1,59 @@
+%%
+%% %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(hdlt_random_html).
+-export([page/3]).
+
+page(SessionID, _Env, Input) ->
+%% log("page(~p) -> deliver content-type when"
+%% "~n SessionID: ~p"
+%% "~n Env: ~p"
+%% "~n Input: ~p", [self(), SessionID, Env, Input]),
+ [WorkSimStr, SzSimStr] = string:tokens(Input, [$:]),
+ WorkSim = list_to_integer(WorkSimStr),
+ SzSim = list_to_integer(SzSimStr),
+ mod_esi:deliver(SessionID, "Content-Type:text/html\r\n\r\n"),
+ mod_esi:deliver(SessionID, start("Random test page")),
+ mod_esi:deliver(SessionID, content(WorkSim, SzSim)),
+ mod_esi:deliver(SessionID, stop()),
+ ok.
+
+start(Title) ->
+ "<HTML>
+<HEAD>
+<TITLE>" ++ Title ++ "</TITLE>
+ </HEAD>
+<BODY>\n".
+
+stop() ->
+ "</BODY>
+</HTML>
+".
+
+content(WorkSim, SzSim) ->
+ {A, B, C} = now(),
+ random:seed(A, B, C),
+ lists:sort([random:uniform(X) || X <- lists:seq(1, WorkSim)]),
+ lists:flatten(lists:duplicate(SzSim, "Dummy data ")).
+
+%% log(F, A) ->
+%% hdlt_logger:set_name("HDLT RANDOM-HTML"),
+%% hdlt_logger:set_level(debug),
+%% hdlt_logger:log(F, A).
diff --git a/lib/inets/examples/httpd_load_test/hdlt_server.erl b/lib/inets/examples/httpd_load_test/hdlt_server.erl
new file mode 100644
index 0000000000..3e5a849d5b
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_server.erl
@@ -0,0 +1,163 @@
+%%
+%% %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%
+%%
+%%
+%%----------------------------------------------------------------------
+%% Purpose: The HDLT server module.
+%% This is just a stub, making future expansion easy.
+%% All code in this module is executed in the local node!
+%%----------------------------------------------------------------------
+
+-module(hdlt_server).
+
+-export([start/1, stop/0, start_inets/0, start_service/1]).
+
+-export([proxy/1]).
+
+-include_lib("kernel/include/file.hrl").
+-include("hdlt_logger.hrl").
+
+
+-define(PROXY, hdlt_proxy).
+
+
+%% This function is used to start the proxy process
+%% This function is called *after* the nodes has been
+%% "connected" with the controller/collector node.
+
+start(Debug) ->
+ proc_lib:start(?MODULE, proxy, [Debug]).
+
+stop() ->
+ ?PROXY ! stop.
+
+start_inets() ->
+ ?PROXY ! start_inets.
+
+start_service(Config) ->
+ ?PROXY ! {server_start, Config, self()},
+ receive
+ {server_start_result, Result} ->
+ Result
+ after 15000 ->
+ {error, timeout}
+ end.
+
+
+proxy(Debug) ->
+ process_flag(trap_exit, true),
+ erlang:register(?PROXY, self()),
+ ?SET_NAME("HDLT PROXY"),
+ ?SET_LEVEL(Debug),
+ ?LOG("starting", []),
+ Ref = await_for_controller(10),
+ CtrlNode = node(Ref),
+ erlang:monitor_node(CtrlNode, true),
+ proc_lib:init_ack({ok, self()}),
+ ?DEBUG("started", []),
+ proxy_loop(Ref, CtrlNode).
+
+await_for_controller(N) when N > 0 ->
+ case global:whereis_name(hdlt_ctrl) of
+ Pid when is_pid(Pid) ->
+ erlang:monitor(process, Pid);
+ _ ->
+ timer:sleep(1000),
+ await_for_controller(N-1)
+ end;
+await_for_controller(_) ->
+ proc_lib:init_ack({error, controller_not_found, nodes()}),
+ timer:sleep(500),
+ halt().
+
+
+proxy_loop(Ref, CtrlNode) ->
+ ?DEBUG("await command", []),
+ receive
+ stop ->
+ ?LOG("received stop", []),
+ halt();
+
+ start_inets ->
+ ?LOG("start the inets service framework", []),
+ case (catch inets:start()) of
+ ok ->
+ ?LOG("framework started", []),
+ proxy_loop(Ref, CtrlNode);
+ Error ->
+ ?LOG("failed starting inets service framework: "
+ "~n Error: ~p", [Error]),
+ halt()
+ end;
+
+ {server_start, Config, From} ->
+ ?LOG("start-server", []),
+ maybe_start_crypto_and_ssl(Config),
+ %% inets:enable_trace(max, "/tmp/inets-httpd-trace.log", httpd),
+ %% inets:enable_trace(max, "/tmp/inets-httpd-trace.log", all),
+ case (catch inets:start(httpd, Config)) of
+ {ok, _} ->
+ ?LOG("server started when"
+ "~n which(inets): ~p"
+ "~n RootDir: ~p"
+ "~n System info: ~p", [code:which(inets),
+ code:root_dir(),
+ get_node_info()]),
+ From ! {server_start_result, ok},
+ proxy_loop(Ref, CtrlNode);
+ Error ->
+ ?INFO("server start failed"
+ "~n Error: ~p", [Error]),
+ From ! {server_start_result, Error},
+ halt()
+ end;
+
+ {nodedown, CtrlNode} ->
+ ?LOG("received nodedown for controller node - terminate", []),
+ halt();
+
+ {'DOWN', Ref, process, _, _} ->
+ ?LOG("received DOWN message for controller - terminate", []),
+ %% The controller has terminated, time to die
+ halt()
+
+ end.
+
+
+maybe_start_crypto_and_ssl(Config) ->
+ case lists:keysearch(socket_type, 1, Config) of
+ {value, {socket_type, SocketType}} when ((SocketType =:= ssl) orelse
+ (SocketType =:= ossl) orelse
+ (SocketType =:= essl)) ->
+ ?LOG("maybe start crypto and ssl", []),
+ (catch crypto:start()),
+ ssl:start();
+ _ ->
+ ok
+ end.
+
+
+get_node_info() ->
+ [{cpu_topology, erlang:system_info(cpu_topology)},
+ {heap_type, erlang:system_info(heap_type)},
+ {nof_schedulers, erlang:system_info(schedulers)},
+ {otp_release, erlang:system_info(otp_release)},
+ {version, erlang:system_info(version)},
+ {system_version, erlang:system_info(system_version)},
+ {system_architecture, erlang:system_info(system_architecture)}].
+
diff --git a/lib/inets/examples/httpd_load_test/hdlt_slave.erl b/lib/inets/examples/httpd_load_test/hdlt_slave.erl
new file mode 100644
index 0000000000..52af9b5b90
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_slave.erl
@@ -0,0 +1,291 @@
+%%
+%% %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(hdlt_slave).
+
+
+-export([start_link/4, start_link/5, start_link/6, stop/1]).
+
+%% Internal exports
+-export([wait_for_slave/9, slave_start/1, wait_for_master_to_die/3]).
+
+-include("hdlt_logger.hrl").
+
+-define(SSH_PORT, 22).
+-define(TIMEOUT, 60000).
+-define(LOGGER, hdlt_logger).
+
+
+%% ***********************************************************************
+%% start_link/4,5 --
+%%
+%% The start/4,5 functions are used to start a slave Erlang node.
+%% The node on which the start/N functions are used is called the
+%% master in the description below.
+%%
+%% If hostname is the same for the master and the slave,
+%% the Erlang node will simply be spawned. The only requirment for
+%% this to work is that the 'erl' program can be found in PATH.
+%%
+%% If the master and slave are on different hosts, start/N uses
+%% the 'rsh' program to spawn an Erlang node on the other host.
+%% Alternative, if the master was started as
+%% 'erl -sname xxx -rsh my_rsh...', then 'my_rsh' will be used instead
+%% of 'rsh' (this is useful for systems where the rsh program is named
+%% 'remsh').
+%%
+%% For this to work, the following conditions must be fulfilled:
+%%
+%% 1. There must be an Rsh program on computer; if not an error
+%% is returned.
+%%
+%% 2. The hosts must be configured to allowed 'rsh' access without
+%% prompts for password.
+%%
+%% The slave node will have its filer and user server redirected
+%% to the master. When the master node dies, the slave node will
+%% terminate. For the start_link functions, the slave node will
+%% terminate also if the process which called start_link terminates.
+%%
+%% Returns: {ok, Name@Host} |
+%% {error, timeout} |
+%% {error, no_rsh} |
+%% {error, {already_running, Name@Host}}
+
+start_link(Host, Name, ErlPath, Paths) ->
+ start_link(Host, Name, ErlPath, Paths, [], silence).
+
+start_link(Host, Name, ErlPath, Paths, DebugLevel) when is_atom(DebugLevel) ->
+ start_link(Host, Name, ErlPath, Paths, [], DebugLevel);
+start_link(Host, Name, ErlPath, Paths, Args) when is_list(Args) ->
+ start_link(Host, Name, ErlPath, Paths, Args, silence).
+
+start_link(Host, Name, ErlPath, Paths, Args, DebugLevel) ->
+ Node = list_to_atom(lists:concat([Name, "@", Host])),
+ case net_adm:ping(Node) of
+ pang ->
+ start_it(Host, Name, Node, ErlPath, Paths, Args, DebugLevel);
+ pong ->
+ {error, {already_running, Node}}
+ end.
+
+%% Stops a running node.
+
+stop(Node) ->
+ rpc:call(Node, erlang, halt, []),
+ ok.
+
+
+%% Starts a new slave node.
+
+start_it(Host, Name, Node, ErlPath, Paths, Args, DebugLevel) ->
+ Prog = filename:join([ErlPath, "erl"]),
+ spawn(?MODULE, wait_for_slave, [self(), Host, Name, Node, Paths, Args, self(), Prog, DebugLevel]),
+ receive
+ {result, Result} -> Result
+ end.
+
+%% Waits for the slave to start.
+
+wait_for_slave(Parent, Host, Name, Node, Paths, Args,
+ LinkTo, Prog, DebugLevel) ->
+ ?SET_NAME("HDLT SLAVE STARTER"),
+ ?SET_LEVEL(DebugLevel),
+ ?DEBUG("begin", []),
+ Waiter = register_unique_name(0),
+ case mk_cmd(Host, Name, Paths, Args, Waiter, Prog) of
+ {ok, Cmd} ->
+ ?DEBUG("command generated: ~n~s", [Cmd]),
+ case (catch ssh_slave_start(Host, Cmd)) of
+ {ok, Conn, _Chan} ->
+ ?DEBUG("ssh channel created", []),
+ receive
+ {SlavePid, slave_started} ->
+ ?DEBUG("slave started: ~p", [SlavePid]),
+ unregister(Waiter),
+ slave_started(Parent, LinkTo, SlavePid, Conn,
+ DebugLevel)
+ after 32000 ->
+ ?INFO("slave node failed to report in on time",
+ []),
+ %% If it seems that the node was partially started,
+ %% try to kill it.
+ case net_adm:ping(Node) of
+ pong ->
+ spawn(Node, erlang, halt, []),
+ ok;
+ _ ->
+ ok
+ end,
+ Parent ! {result, {error, timeout}}
+ end;
+ {error, Reason} = Error ->
+ ?INFO("FAILED starting node: "
+ "~n ~p"
+ "~n ~p", [Reason, Cmd]),
+ Parent ! {result, Error}
+ end;
+ Other ->
+ ?INFO("FAILED creating node command string: "
+ "~n ~p", [Other]),
+ Parent ! {result, Other}
+ end.
+
+
+ssh_slave_start(Host, ErlCmd) ->
+ ?DEBUG("ssh_slave_start -> try connect to ~p", [Host]),
+ Connection =
+ case (catch ssh:connect(Host, ?SSH_PORT,
+ [{silently_accept_hosts, true}])) of
+ {ok, Conn} ->
+ ?DEBUG("ssh_exec_erl -> connected: ~p", [Conn]),
+ Conn;
+ Error1 ->
+ ?LOG("failed connecting to ~p: ~p", [Host, Error1]),
+ throw({error, {ssh_connect_failed, Error1}})
+ end,
+
+ ?DEBUG("ssh_exec_erl -> connected - now create channel", []),
+ Channel =
+ case (catch ssh_connection:session_channel(Connection, ?TIMEOUT)) of
+ {ok, Chan} ->
+ ?DEBUG("ssh_exec_erl -> channel ~p created", [Chan]),
+ Chan;
+ Error2 ->
+ ?LOG("failed creating channel: ~p", [Error2]),
+ throw({error, {ssh_channel_create_failed, Error2}})
+ end,
+
+ ?DEBUG("ssh_exec_erl -> channel created - now exec command: "
+ "~n ~p", [ErlCmd]),
+ case (catch ssh_connection:exec(Connection, Channel, ErlCmd, infinity)) of
+ success ->
+ ?DEBUG("ssh_exec_erl -> command exec'ed - clean ssh msg", []),
+ clean_ssh_msg(),
+ ?DEBUG("ssh_exec_erl -> done", []),
+ {ok, Connection, Channel};
+ Error3 ->
+ ?LOG("failed exec comand: ~p", [Error3]),
+ throw({error, {ssh_exec_failed, Error3}})
+ end.
+
+clean_ssh_msg() ->
+ receive
+ {ssh_cm, _X, _Y} ->
+ clean_ssh_msg()
+ after 1000 ->
+ ok
+ end.
+
+
+slave_started(ReplyTo, Master, Slave, Conn, Level)
+ when is_pid(Master) andalso is_pid(Slave) ->
+ process_flag(trap_exit, true),
+ SName = lists:flatten(
+ io_lib:format("HDLT SLAVE CTRL[~p,~p]",
+ [self(), node(Slave)])),
+ ?SET_NAME(SName),
+ ?SET_LEVEL(Level),
+ ?LOG("initiating", []),
+ MasterRef = erlang:monitor(process, Master),
+ SlaveRef = erlang:monitor(process, Slave),
+ ReplyTo ! {result, {ok, node(Slave)}},
+ slave_running(Master, MasterRef, Slave, SlaveRef, Conn).
+
+
+%% The slave node will be killed if the master process terminates,
+%% The master process will not be killed if the slave node terminates.
+
+slave_running(Master, MasterRef, Slave, SlaveRef, Conn) ->
+ ?DEBUG("await message", []),
+ receive
+ {'DOWN', MasterRef, process, _Object, _Info} ->
+ ?LOG("received DOWN from master", []),
+ erlang:demonitor(SlaveRef, [flush]),
+ Slave ! {nodedown, node()},
+ ssh:close(Conn);
+
+ {'DOWN', SlaveRef, process, Object, _Info} ->
+ ?LOG("received DOWN from slave (~p)", [Object]),
+ erlang:demonitor(MasterRef, [flush]),
+ ssh:close(Conn);
+
+ Other ->
+ ?DEBUG("received unknown: ~n~p", [Other]),
+ slave_running(Master, MasterRef, Slave, SlaveRef, Conn)
+
+ end.
+
+register_unique_name(Number) ->
+ Name = list_to_atom(lists:concat([?MODULE, "_waiter_", Number])),
+ case catch register(Name, self()) of
+ true ->
+ Name;
+ {'EXIT', {badarg, _}} ->
+ register_unique_name(Number+1)
+ end.
+
+
+%% Makes up the command to start the nodes.
+%% If the node should run on the local host, there is
+%% no need to use rsh.
+
+mk_cmd(Host, Name, Paths, Args, Waiter, Prog) ->
+ PaPaths = [[" -pa ", Path] || Path <- Paths],
+ {ok, lists:flatten(
+ lists:concat([Prog,
+ " -detached -nopinput ",
+ Args, " ",
+ " -sname ", Name, "@", Host,
+ " -s ", ?MODULE, " slave_start ", node(),
+ " ", Waiter,
+ " ", PaPaths]))}.
+
+
+%% This function will be invoked on the slave, using the -s option of erl.
+%% It will wait for the master node to terminate.
+
+slave_start([Master, Waiter]) ->
+ spawn(?MODULE, wait_for_master_to_die, [Master, Waiter, silence]);
+slave_start([Master, Waiter, Level]) ->
+ spawn(?MODULE, wait_for_master_to_die, [Master, Waiter, Level]).
+
+
+wait_for_master_to_die(Master, Waiter, Level) ->
+ process_flag(trap_exit, true),
+ SName = lists:flatten(
+ io_lib:format("HDLT-SLAVE MASTER MONITOR[~p,~p,~p]",
+ [self(), node(), Master])),
+ ?SET_NAME(SName),
+ ?SET_LEVEL(Level),
+ erlang:monitor_node(Master, true),
+ {Waiter, Master} ! {self(), slave_started},
+ wloop(Master).
+
+wloop(Master) ->
+ ?DEBUG("await message", []),
+ receive
+ {nodedown, Master} ->
+ ?INFO("received master nodedown", []),
+ halt();
+ _Other ->
+ wloop(Master)
+ end.
+
+
+
diff --git a/lib/inets/examples/httpd_load_test/hdlt_ssl_client_cert.pem b/lib/inets/examples/httpd_load_test/hdlt_ssl_client_cert.pem
new file mode 120000
index 0000000000..41644a1098
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_ssl_client_cert.pem
@@ -0,0 +1 @@
+../../test/httpc_SUITE_data/ssl_client_cert.pem \ No newline at end of file
diff --git a/lib/inets/examples/httpd_load_test/hdlt_ssl_server_cert.pem b/lib/inets/examples/httpd_load_test/hdlt_ssl_server_cert.pem
new file mode 120000
index 0000000000..41644a1098
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/hdlt_ssl_server_cert.pem
@@ -0,0 +1 @@
+../../test/httpc_SUITE_data/ssl_client_cert.pem \ No newline at end of file
diff --git a/lib/inets/examples/httpd_load_test/modules.mk b/lib/inets/examples/httpd_load_test/modules.mk
new file mode 100644
index 0000000000..9d0d7103d5
--- /dev/null
+++ b/lib/inets/examples/httpd_load_test/modules.mk
@@ -0,0 +1,44 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+
+# %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%
+
+SCRIPT_SKELETONS = \
+ hdlt.sh.skel
+
+CONF_SKELETONS = \
+ hdlt.config.skel
+
+CERT_FILES = \
+ hdlt_ssl_client_cert.pem \
+ hdlt_ssl_server_cert.pem
+
+README = HDLT_README
+
+MODULES = \
+ hdlt \
+ hdlt_ctrl \
+ hdlt_client \
+ hdlt_logger \
+ hdlt_random_html \
+ hdlt_server \
+ hdlt_slave
+
+INTERNAL_HRL_FILES = \
+ hdlt_logger.hrl
+
+
diff --git a/lib/inets/examples/server_root/Makefile b/lib/inets/examples/server_root/Makefile
new file mode 100644
index 0000000000..d7a3231068
--- /dev/null
+++ b/lib/inets/examples/server_root/Makefile
@@ -0,0 +1,209 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-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=$(INETS_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+MODULE=
+
+AUTH_FILES = auth/group \
+ auth/passwd
+CGI_FILES = cgi-bin/printenv.sh
+CONF_FILES = conf/8080.conf \
+ conf/8888.conf \
+ conf/httpd.conf \
+ conf/ssl.conf \
+ conf/mime.types
+OPEN_FILES = htdocs/open/dummy.html
+MNESIA_OPEN_FILES = htdocs/mnesia_open/dummy.html
+MISC_FILES = htdocs/misc/friedrich.html \
+ htdocs/misc/oech.html
+SECRET_FILES = htdocs/secret/dummy.html
+MNESIA_SECRET_FILES = htdocs/mnesia_secret/dummy.html
+HTDOCS_FILES = htdocs/index.html \
+ htdocs/config.shtml \
+ htdocs/echo.shtml \
+ htdocs/exec.shtml \
+ htdocs/flastmod.shtml \
+ htdocs/fsize.shtml \
+ htdocs/include.shtml
+ICON_FILES = icons/README \
+ icons/a.gif \
+ icons/alert.black.gif \
+ icons/alert.red.gif \
+ icons/apache_pb.gif \
+ icons/back.gif \
+ icons/ball.gray.gif \
+ icons/ball.red.gif \
+ icons/binary.gif \
+ icons/binhex.gif \
+ icons/blank.gif \
+ icons/bomb.gif \
+ icons/box1.gif \
+ icons/box2.gif \
+ icons/broken.gif \
+ icons/burst.gif \
+ icons/button1.gif \
+ icons/button10.gif \
+ icons/button2.gif \
+ icons/button3.gif \
+ icons/button4.gif \
+ icons/button5.gif \
+ icons/button6.gif \
+ icons/button7.gif \
+ icons/button8.gif \
+ icons/button9.gif \
+ icons/buttonl.gif \
+ icons/buttonr.gif \
+ icons/c.gif \
+ icons/comp.blue.gif \
+ icons/comp.gray.gif \
+ icons/compressed.gif \
+ icons/continued.gif \
+ icons/dir.gif \
+ icons/down.gif \
+ icons/dvi.gif \
+ icons/f.gif \
+ icons/folder.gif \
+ icons/folder.open.gif \
+ icons/folder.sec.gif \
+ icons/forward.gif \
+ icons/generic.gif \
+ icons/generic.red.gif \
+ icons/generic.sec.gif \
+ icons/hand.right.gif \
+ icons/hand.up.gif \
+ icons/htdig.gif \
+ icons/icon.sheet.gif \
+ icons/image1.gif \
+ icons/image2.gif \
+ icons/image3.gif \
+ icons/index.gif \
+ icons/layout.gif \
+ icons/left.gif \
+ icons/link.gif \
+ icons/movie.gif \
+ icons/p.gif \
+ icons/patch.gif \
+ icons/pdf.gif \
+ icons/pie0.gif \
+ icons/pie1.gif \
+ icons/pie2.gif \
+ icons/pie3.gif \
+ icons/pie4.gif \
+ icons/pie5.gif \
+ icons/pie6.gif \
+ icons/pie7.gif \
+ icons/pie8.gif \
+ icons/portal.gif \
+ icons/poweredby.gif \
+ icons/ps.gif \
+ icons/quill.gif \
+ icons/right.gif \
+ icons/screw1.gif \
+ icons/screw2.gif \
+ icons/script.gif \
+ icons/sound1.gif \
+ icons/sound2.gif \
+ icons/sphere1.gif \
+ icons/sphere2.gif \
+ icons/star.gif \
+ icons/star_blank.gif \
+ icons/tar.gif \
+ icons/tex.gif \
+ icons/text.gif \
+ icons/transfer.gif \
+ icons/unknown.gif \
+ icons/up.gif \
+ icons/uu.gif \
+ icons/uuencoded.gif \
+ icons/world1.gif \
+ icons/world2.gif
+
+SSL_FILES = ssl/ssl_client.pem \
+ ssl/ssl_server.pem
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS +=
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt:
+
+clean:
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth
+ $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin
+ $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf
+ $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open
+ $(INSTALL_DATA) $(OPEN_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/open
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
+ $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc
+ $(INSTALL_DATA) $(MISC_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/misc
+ $(INSTALL_DIR) \
+ $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret
+ $(INSTALL_DIR) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret
+ $(INSTALL_DATA) $(SECRET_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/secret
+ $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs
+ $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons
+ $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl
+ $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs
+
+release_docs_spec:
+
diff --git a/lib/inets/examples/subdirs.mk b/lib/inets/examples/subdirs.mk
new file mode 100644
index 0000000000..10a331fc26
--- /dev/null
+++ b/lib/inets/examples/subdirs.mk
@@ -0,0 +1,3 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+
+SUB_DIRECTORIES = server_root httpd_load_test \ No newline at end of file
diff --git a/lib/inets/src/ftp/Makefile b/lib/inets/src/ftp/Makefile
index 0c15277a18..19b93870df 100644
--- a/lib/inets/src/ftp/Makefile
+++ b/lib/inets/src/ftp/Makefile
@@ -22,6 +22,7 @@ include $(ERL_TOP)/make/target.mk
EBIN = ../../ebin
include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
# ----------------------------------------------------
# Application version
# ----------------------------------------------------
@@ -29,6 +30,7 @@ include ../../vsn.mk
VSN = $(INETS_VSN)
+
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
@@ -52,24 +54,21 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
-# INETS FLAGS
+# FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
+
+include ../inets_app/inets.mk
ifeq ($(FTP_DEBUG),true)
INETS_FLAGS += -Dftp_debug
endif
+ERL_COMPILE_FLAGS += \
+ $(INETS_FLAGS) \
+ $(INETS_ERL_COMPILE_FLAGS) \
+ -I../../include \
+ -I../inets_app
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-INETS_ERL_FLAGS += -I ../inets_app -pa ../../ebin
-
-ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \
- $(INETS_FLAGS) \
- +'{parse_transform,sys_pre_attributes}' \
- +'{attribute,insert,app_vsn,$(APP_VSN)}'
# ----------------------------------------------------
# Targets
@@ -89,9 +88,10 @@ docs:
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/src
- $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/src/ftp
+ $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/ftp
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
release_docs_spec:
diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl
index 534fcae675..5ad74851c8 100644
--- a/lib/inets/src/ftp/ftp.erl
+++ b/lib/inets/src/ftp/ftp.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
%%
@@ -25,14 +25,12 @@
-behaviour(gen_server).
-behaviour(inets_service).
--deprecated({open, 3, next_major_release}).
--deprecated({force_active, 1, next_major_release}).
%% API - Client interface
-export([cd/2, close/1, delete/2, formaterror/1,
lcd/2, lpwd/1, ls/1, ls/2,
mkdir/2, nlist/1, nlist/2,
- open/1, open/2, open/3, force_active/1,
+ open/1, open/2,
pwd/1, quote/2,
recv/2, recv/3, recv_bin/2,
recv_chunk_start/2, recv_chunk/1,
@@ -133,11 +131,6 @@ open(Host, Port) when is_integer(Port) ->
open(Host, [{port, Port}]);
%% </BACKWARD-COMPATIBILLITY>
-%% <BACKWARD-COMPATIBILLITY>
-open(Host, [H|_] = Flags) when is_atom(H) ->
- open(Host, ?FTP_PORT, Flags);
-%% </BACKWARD-COMPATIBILLITY>
-
open(Host, Opts) when is_list(Opts) ->
?fcrt("open", [{host, Host}, {opts, Opts}]),
try
@@ -160,32 +153,6 @@ open(Host, Opts) when is_list(Opts) ->
end.
-%% <BACKWARD-COMPATIBILLITY>
-open(Host, Port, Flags) when is_integer(Port) andalso is_list(Flags) ->
- ?fcrt("open", [{host, Host}, {port, Port}, {flags, Flags}]),
- try
- {ok, StartOptions} = start_options([{flags, Flags}]),
- ?fcrt("open", [{start_options, StartOptions}]),
- {ok, OpenOptions} = open_options([{host, Host}, {port, Port}|Flags]),
- ?fcrt("open", [{open_options, OpenOptions}]),
- case ftp_sup:start_child([[{client, self()} | StartOptions], []]) of
- {ok, Pid} ->
- ?fcrt("open - ok", [{pid, Pid}]),
- call(Pid, {open, ip_comm, OpenOptions}, plain);
- Error1 ->
- ?fcrt("open - error", [{error1, Error1}]),
- Error1
- end
- catch
- throw:Error2 ->
- Error2
- end.
-%% </BACKWARD-COMPATIBILLITY>
-
-
-
-
-
%%--------------------------------------------------------------------------
%% user(Pid, User, Pass, <Acc>) -> ok | {error, euser} | {error, econn}
%% | {error, eacct}
@@ -528,16 +495,6 @@ close(Pid) ->
cast(Pid, close),
ok.
-%%--------------------------------------------------------------------------
-%% force_active(Pid) -> ok
-%% Pid = pid()
-%%
-%% Description: Force connection to use active mode.
-%%--------------------------------------------------------------------------
-force_active(Pid) ->
- error_logger:info_report("This function is deprecated use the mode flag "
- "instead"),
- call(Pid, force_active, atom).
%%--------------------------------------------------------------------------
%% formaterror(Tag) -> string()
@@ -886,9 +843,6 @@ handle_call({_, {open, ip_comm, Host, Opts}}, From, State) ->
{stop, normal, State2#state{client = undefined}}
end;
-handle_call({_, force_active}, _, State) ->
- {reply, ok, State#state{mode = active}};
-
handle_call({_, {user, User, Password}}, From,
#state{csock = CSock} = State) when (CSock =/= undefined) ->
handle_user(User, Password, "", State#state{client = From});
diff --git a/lib/inets/src/ftp/ftp_internal.hrl b/lib/inets/src/ftp/ftp_internal.hrl
index c3fa1e611d..148f8217ba 100644
--- a/lib/inets/src/ftp/ftp_internal.hrl
+++ b/lib/inets/src/ftp/ftp_internal.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
%%
@@ -21,7 +21,8 @@
-ifndef(ftp_internal_hrl).
-define(ftp_internal_hrl, true).
--include("inets_internal.hrl").
+-include_lib("inets/src/inets_app/inets_internal.hrl").
+
-define(SERVICE, ftpc).
-define(fcri(Label, Content), ?report_important(Label, ?SERVICE, Content)).
-define(fcrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)).
diff --git a/lib/inets/src/http_client/Makefile b/lib/inets/src/http_client/Makefile
index 628c91421f..575c6efaec 100644
--- a/lib/inets/src/http_client/Makefile
+++ b/lib/inets/src/http_client/Makefile
@@ -61,20 +61,17 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
-# INETS FLAGS
-# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
-
-
-# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-INETS_ERL_FLAGS += -I ../http_lib -I ../inets_app -pa ../../ebin
-ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \
- $(INETS_FLAGS) \
- +'{parse_transform,sys_pre_attributes}' \
- +'{attribute,insert,app_vsn,$(APP_VSN)}'
+include ../inets_app/inets.mk
+
+ERL_COMPILE_FLAGS += \
+ $(INETS_FLAGS) \
+ $(INETS_ERL_COMPILE_FLAGS) \
+ -I../../include \
+ -I../inets_app \
+ -I../http_lib
# ----------------------------------------------------
@@ -94,9 +91,10 @@ docs:
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/src
- $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/src/http_client
+ $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/http_client
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
release_docs_spec:
diff --git a/lib/inets/src/http_client/http.erl b/lib/inets/src/http_client/http.erl
index 7e1e90b50e..bbe2fec267 100644
--- a/lib/inets/src/http_client/http.erl
+++ b/lib/inets/src/http_client/http.erl
@@ -18,21 +18,38 @@
%%
%%
-%% Description:
-%%% This version of the HTTP/1.1 client supports:
-%%% - RFC 2616 HTTP 1.1 client part
-%%% - RFC 2818 HTTP Over TLS
+%%% Description: OLD API MODULE - USE httpc INSTEAD
-module(http).
-%% API
--export([request/1, request/2, request/4, request/5,
+-deprecated({request, 1, next_major_release}).
+-deprecated({request, 2, next_major_release}).
+-deprecated({request, 4, next_major_release}).
+-deprecated({request, 5, next_major_release}).
+-deprecated({cancel_request, 1, next_major_release}).
+-deprecated({cancel_request, 2, next_major_release}).
+-deprecated({set_option, 2, next_major_release}).
+-deprecated({set_option, 3, next_major_release}).
+-deprecated({set_options, 1, next_major_release}).
+-deprecated({set_options, 2, next_major_release}).
+-deprecated({verify_cookies, 2, next_major_release}).
+-deprecated({verify_cookies, 3, next_major_release}).
+-deprecated({cookie_header, 1, next_major_release}).
+-deprecated({cookie_header, 2, next_major_release}).
+-deprecated({stream_next, 1, next_major_release}).
+-deprecated({default_profile, 0, next_major_release}).
+
+%% Deprecated
+-export([
+ request/1, request/2, request/4, request/5,
cancel_request/1, cancel_request/2,
set_option/2, set_option/3,
set_options/1, set_options/2,
- verify_cookies/2, verify_cookies/3, cookie_header/1,
- cookie_header/2, stream_next/1,
- default_profile/0]).
+ verify_cookies/2, verify_cookies/3,
+ cookie_header/1, cookie_header/2,
+ stream_next/1,
+ default_profile/0
+ ]).
%%%=========================================================================
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
index 5205605e0a..851364001c 100644
--- a/lib/inets/src/http_client/httpc.erl
+++ b/lib/inets/src/http_client/httpc.erl
@@ -48,7 +48,7 @@
stop_service/1,
services/0, service_info/1]).
--include("http_internal.hrl").
+-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpc_internal.hrl").
-define(DEFAULT_PROFILE, default).
@@ -104,8 +104,14 @@ request(Url, Profile) ->
%% HTTPOptions - [HttpOption]
%% HTTPOption - {timeout, Time} | {connect_timeout, Time} |
%% {ssl, SSLOptions} | {proxy_auth, {User, Password}}
-%% Ssloptions = [SSLOption]
-%% SSLOption = {verify, code()} | {depth, depth()} | {certfile, path()} |
+%% Ssloptions = ssl_options() |
+%% {ssl, ssl_options()} |
+%% {ossl, ssl_options()} |
+%% {essl, ssl_options()}
+%% ssl_options() = [ssl_option()]
+%% ssl_option() = {verify, code()} |
+%% {depth, depth()} |
+%% {certfile, path()} |
%% {keyfile, path()} | {password, string()} | {cacertfile, path()} |
%% {ciphers, string()}
%% Options - [Option]
@@ -432,7 +438,7 @@ handle_request(Method, Url,
Options = request_options(Options0),
Sync = proplists:get_value(sync, Options),
Stream = proplists:get_value(stream, Options),
- Host2 = header_host(Host, Port),
+ Host2 = header_host(Scheme, Host, Port),
HeadersRecord = header_record(NewHeaders, Host2, HTTPOptions),
Receiver = proplists:get_value(receiver, Options),
SocketOpts = proplists:get_value(socket_opts, Options),
@@ -579,7 +585,13 @@ http_options_default() ->
error
end,
SslPost = fun(Value) when is_list(Value) ->
- {ok, Value};
+ {ok, {?HTTP_DEFAULT_SSL_KIND, Value}};
+ ({ssl, SslOptions}) when is_list(SslOptions) ->
+ {ok, {?HTTP_DEFAULT_SSL_KIND, SslOptions}};
+ ({ossl, SslOptions}) when is_list(SslOptions) ->
+ {ok, {ossl, SslOptions}};
+ ({essl, SslOptions}) when is_list(SslOptions) ->
+ {ok, {essl, SslOptions}};
(_) ->
error
end,
@@ -604,14 +616,14 @@ http_options_default() ->
error
end,
[
- {version, {value, "HTTP/1.1"}, #http_options.version, VersionPost},
- {timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost},
- {autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost},
- {ssl, {value, []}, #http_options.ssl, SslPost},
- {proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost},
- {relaxed, {value, false}, #http_options.relaxed, RelaxedPost},
- %% this field has to be *after* the timeout field (as that field is used for the default value)
- {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost}
+ {version, {value, "HTTP/1.1"}, #http_options.version, VersionPost},
+ {timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost},
+ {autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost},
+ {ssl, {value, {?HTTP_DEFAULT_SSL_KIND, []}}, #http_options.ssl, SslPost},
+ {proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost},
+ {relaxed, {value, false}, #http_options.relaxed, RelaxedPost},
+ %% this field has to be *after* the timeout option (as that field is used for the default value)
+ {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost}
].
@@ -895,9 +907,11 @@ bad_option(Option, BadValue) ->
throw({error, {bad_option, Option, BadValue}}).
-header_host(Host, 80 = _Port) ->
+header_host(https, Host, 443 = _Port) ->
Host;
-header_host(Host, Port) ->
+header_host(http, Host, 80 = _Port) ->
+ Host;
+header_host(_Scheme, Host, Port) ->
Host ++ ":" ++ integer_to_list(Port).
diff --git a/lib/inets/src/http_client/httpc_cookie.erl b/lib/inets/src/http_client/httpc_cookie.erl
index 586701b4a1..4d61f82b5a 100644
--- a/lib/inets/src/http_client/httpc_cookie.erl
+++ b/lib/inets/src/http_client/httpc_cookie.erl
@@ -476,13 +476,13 @@ path_sort(Cookies)->
lists:reverse(lists:keysort(#http_cookie.path, Cookies)).
-%% Informally, the Set-Cookie response header comprises the token
-%% Set-Cookie:, followed by a comma-separated list of one or more
-%% cookies. Netscape cookies expires attribute may also have a
-%% , in this case the header list will have been incorrectly split
-%% in parse_set_cookies/2 this functions fixs that problem.
+%% Informally, the Set-Cookie response header comprises the token
+%% Set-Cookie:, followed by a comma-separated list of one or more
+%% cookies. Netscape cookies expires attribute may also have a,
+%% in this case the header list will have been incorrectly split
+%% in parse_set_cookies/2 this functions fix that problem.
fix_netscape_cookie([Cookie1, Cookie2 | Rest], Acc) ->
- case inets_regexp:match(Cookie1, "expires=") of
+ case inets_regexp:match(string:to_lower(Cookie1), "expires=") of
{_, _, _} ->
fix_netscape_cookie(Rest, [Cookie1 ++ Cookie2 | Acc]);
nomatch ->
diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl
index 695ff9cf82..c34b641b7b 100644
--- a/lib/inets/src/http_client/httpc_handler.erl
+++ b/lib/inets/src/http_client/httpc_handler.erl
@@ -22,8 +22,8 @@
-behaviour(gen_server).
+-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpc_internal.hrl").
--include("http_internal.hrl").
%%--------------------------------------------------------------------
@@ -177,8 +177,8 @@ stream(BodyPart, Request = #request{stream = Self}, Code)
stream(BodyPart, Request = #request{stream = Self}, 404)
when (Self =:= self) orelse (Self =:= {self, once}) ->
?hcrt("stream - self with 404", [{stream, Self}]),
- httpc_response:send(Request#request.from,
- {Request#request.id, stream, BodyPart}),
+ httpc_response:send(Request#request.from,
+ {Request#request.id, stream, BodyPart}),
{<<>>, Request};
%% Stream to file
@@ -286,8 +286,7 @@ handle_call({connect_and_send, #request{address = Address0,
handle_call(#request{address = Addr} = Request, _,
#state{status = Status,
- session = #tcp_session{socket = Socket,
- type = pipeline} = Session,
+ session = #session{type = pipeline} = Session,
timers = Timers,
options = #options{proxy = Proxy} = _Options,
profile_name = ProfileName} = State)
@@ -301,7 +300,7 @@ handle_call(#request{address = Addr} = Request, _,
Address = handle_proxy(Addr, Proxy),
- case httpc_request:send(Address, Request, Socket) of
+ case httpc_request:send(Address, Session, Request) of
ok ->
?hcrd("request sent", []),
@@ -320,10 +319,10 @@ handle_call(#request{address = Addr} = Request, _,
NewTimers = NewState#state.timers,
NewPipeline = queue:in(Request, State#state.pipeline),
NewSession =
- Session#tcp_session{queue_length =
- %% Queue + current
- queue:len(NewPipeline) + 1,
- client_close = ClientClose},
+ Session#session{queue_length =
+ %% Queue + current
+ queue:len(NewPipeline) + 1,
+ client_close = ClientClose},
httpc_manager:insert_session(NewSession, ProfileName),
?hcrd("session updated", []),
{reply, ok, State#state{pipeline = NewPipeline,
@@ -336,8 +335,8 @@ handle_call(#request{address = Addr} = Request, _,
cancel_timer(Timers#timers.queue_timer,
timeout_queue),
NewSession =
- Session#tcp_session{queue_length = 1,
- client_close = ClientClose},
+ Session#session{queue_length = 1,
+ client_close = ClientClose},
httpc_manager:insert_session(NewSession, ProfileName),
Relaxed =
(Request#request.settings)#http_options.relaxed,
@@ -357,8 +356,7 @@ handle_call(#request{address = Addr} = Request, _,
handle_call(#request{address = Addr} = Request, _,
#state{status = Status,
- session = #tcp_session{socket = Socket,
- type = keep_alive} = Session,
+ session = #session{type = keep_alive} = Session,
timers = Timers,
options = #options{proxy = Proxy} = _Options,
profile_name = ProfileName} = State)
@@ -370,7 +368,7 @@ handle_call(#request{address = Addr} = Request, _,
{status, Status}]),
Address = handle_proxy(Addr, Proxy),
- case httpc_request:send(Address, Request, Socket) of
+ case httpc_request:send(Address, Session, Request) of
ok ->
?hcrd("request sent", []),
@@ -389,10 +387,10 @@ handle_call(#request{address = Addr} = Request, _,
NewTimers = NewState#state.timers,
NewKeepAlive = queue:in(Request, State#state.keep_alive),
NewSession =
- Session#tcp_session{queue_length =
- %% Queue + current
- queue:len(NewKeepAlive) + 1,
- client_close = ClientClose},
+ Session#session{queue_length =
+ %% Queue + current
+ queue:len(NewKeepAlive) + 1,
+ client_close = ClientClose},
httpc_manager:insert_session(NewSession, ProfileName),
?hcrd("session updated", []),
{reply, ok, State#state{keep_alive = NewKeepAlive,
@@ -405,8 +403,8 @@ handle_call(#request{address = Addr} = Request, _,
cancel_timer(Timers#timers.queue_timer,
timeout_queue),
NewSession =
- Session#tcp_session{queue_length = 1,
- client_close = ClientClose},
+ Session#session{queue_length = 1,
+ client_close = ClientClose},
httpc_manager:insert_session(NewSession, ProfileName),
Relaxed =
(Request#request.settings)#http_options.relaxed,
@@ -589,13 +587,13 @@ handle_info({ssl_closed, _}, State = #state{request = undefined}) ->
%%% Error cases
handle_info({tcp_closed, _}, #state{session = Session0} = State) ->
- Socket = Session0#tcp_session.socket,
- Session = Session0#tcp_session{socket = {remote_close, Socket}},
+ Socket = Session0#session.socket,
+ Session = Session0#session{socket = {remote_close, Socket}},
%% {stop, session_remotly_closed, State};
{stop, normal, State#state{session = Session}};
handle_info({ssl_closed, _}, #state{session = Session0} = State) ->
- Socket = Session0#tcp_session.socket,
- Session = Session0#tcp_session{socket = {remote_close, Socket}},
+ Socket = Session0#session.socket,
+ Session = Session0#session{socket = {remote_close, Socket}},
%% {stop, session_remotly_closed, State};
{stop, normal, State#state{session = Session}};
handle_info({tcp_error, _, _} = Reason, State) ->
@@ -699,19 +697,18 @@ terminate(normal, #state{session = undefined}) ->
%% Init error sending, no session information has been setup but
%% there is a socket that needs closing.
terminate(normal,
- #state{request = Request,
- session = #tcp_session{id = undefined,
- socket = Socket}}) ->
- http_transport:close(socket_type(Request), Socket);
+ #state{session = #session{id = undefined} = Session}) ->
+ close_socket(Session);
%% Socket closed remotely
terminate(normal,
- #state{session = #tcp_session{socket = {remote_close, Socket},
- id = Id},
+ #state{session = #session{socket = {remote_close, Socket},
+ socket_type = SocketType,
+ id = Id},
profile_name = ProfileName,
- request = Request,
- timers = Timers,
- pipeline = Pipeline}) ->
+ request = Request,
+ timers = Timers,
+ pipeline = Pipeline}) ->
?hcrt("terminate(normal) - remote close",
[{id, Id}, {profile, ProfileName}]),
@@ -728,11 +725,11 @@ terminate(normal,
deliver_answers([Request | queue:to_list(Pipeline)]),
%% And, just in case, close our side (**really** overkill)
- http_transport:close(socket_type(Request), Socket);
+ http_transport:close(SocketType, Socket);
-terminate(_, #state{session = #tcp_session{id = Id,
- socket = Socket,
- scheme = Scheme},
+terminate(_, #state{session = #session{id = Id,
+ socket = Socket,
+ socket_type = SocketType},
request = undefined,
profile_name = ProfileName,
timers = Timers,
@@ -744,7 +741,7 @@ terminate(_, #state{session = #tcp_session{id = Id,
maybe_retry_queue(KeepAlive, State),
cancel_timer(Timers#timers.queue_timer, timeout_queue),
- http_transport:close(socket_type(Scheme), Socket);
+ http_transport:close(SocketType, Socket);
terminate(Reason, #state{request = undefined}) ->
?hcrt("terminate", [{reason, Reason}]),
@@ -878,22 +875,23 @@ connect_and_send_first_request(Address,
ConnTimeout = Settings#http_options.connect_timeout,
case connect(SocketType, Address, Options, ConnTimeout) of
{ok, Socket} ->
+ Session = #session{id = {OrigAddress, self()},
+ scheme = Scheme,
+ socket = Socket,
+ socket_type = SocketType},
?hcrd("connected - now send first request", [{socket, Socket}]),
- case httpc_request:send(Address, Request, Socket) of
+ case httpc_request:send(Address, Session, Request) of
ok ->
?hcrd("first request sent", []),
ClientClose =
httpc_request:is_client_closing(Headers),
SessionType = httpc_manager:session_type(Options),
- Session =
- #tcp_session{id = {OrigAddress, self()},
- scheme = Scheme,
- socket = Socket,
- client_close = ClientClose,
- type = SessionType},
+ Session2 =
+ Session#session{client_close = ClientClose,
+ type = SessionType},
TmpState =
State#state{request = Request,
- session = Session,
+ session = Session2,
mfa = init_mfa(Request, State),
status_line = init_status_line(Request),
headers = undefined,
@@ -947,21 +945,20 @@ handler_info(#state{request = Request,
?hcrt("handler info", [{request_info, RequestInfo}]),
%% Info about the current session/socket
- SessionType = Session#tcp_session.type,
- QueueLen = case Session#tcp_session.type of
+ SessionType = Session#session.type,
+ QueueLen = case SessionType of
pipeline ->
queue:len(Pipeline);
keep_alive ->
queue:len(KeepAlive)
end,
- Socket = Session#tcp_session.socket,
- Scheme = Session#tcp_session.scheme,
- SocketType = socket_type(Scheme),
+ Scheme = Session#session.scheme,
+ Socket = Session#session.socket,
+ SocketType = Session#session.socket_type,
?hcrt("handler info", [{session_type, SessionType},
{queue_length, QueueLen},
{scheme, Scheme},
- {socket_type, SocketType},
{socket, Socket}]),
SocketOpts = http_transport:getopts(SocketType, Socket),
@@ -1118,9 +1115,7 @@ handle_response(#state{request = Request,
?hcrd("handle response - continue", []),
%% Send request body
{_, RequestBody} = Request#request.content,
- http_transport:send(socket_type(Session#tcp_session.scheme),
- Session#tcp_session.socket,
- RequestBody),
+ send_raw(Session, RequestBody),
%% Wait for next response
activate_once(Session),
Relaxed = (Request#request.settings)#http_options.relaxed,
@@ -1217,7 +1212,7 @@ handle_pipeline(#state{status = pipeline,
%% If a pipeline that has been idle for some time is not
%% closed by the server, the client may want to close it.
NewState = activate_queue_timeout(TimeOut, State),
- NewSession = Session#tcp_session{queue_length = 0},
+ NewSession = Session#session{queue_length = 0},
httpc_manager:insert_session(NewSession, ProfileName),
%% Note mfa will be initilized when a new request
%% arrives.
@@ -1239,9 +1234,9 @@ handle_pipeline(#state{status = pipeline,
false ->
?hcrv("next request", [{request, NextRequest}]),
NewSession =
- Session#tcp_session{queue_length =
- %% Queue + current
- queue:len(Pipeline) + 1},
+ Session#session{queue_length =
+ %% Queue + current
+ queue:len(Pipeline) + 1},
httpc_manager:insert_session(NewSession, ProfileName),
Relaxed =
(NextRequest#request.settings)#http_options.relaxed,
@@ -1290,16 +1285,16 @@ handle_keep_alive_queue(
%% If a keep_alive session has been idle for some time is not
%% closed by the server, the client may want to close it.
NewState = activate_queue_timeout(TimeOut, State),
- NewSession = Session#tcp_session{queue_length = 0},
+ NewSession = Session#session{queue_length = 0},
httpc_manager:insert_session(NewSession, ProfileName),
%% Note mfa will be initilized when a new request
%% arrives.
{noreply,
- NewState#state{request = undefined,
- mfa = undefined,
+ NewState#state{request = undefined,
+ mfa = undefined,
status_line = undefined,
- headers = undefined,
- body = undefined
+ headers = undefined,
+ body = undefined
}
};
{{value, NextRequest}, KeepAlive} ->
@@ -1342,10 +1337,12 @@ case_insensitive_header(Str) when is_list(Str) ->
case_insensitive_header(Str) ->
Str.
-activate_once(#tcp_session{scheme = Scheme, socket = Socket}) ->
- SocketType = socket_type(Scheme),
+activate_once(#session{socket = Socket, socket_type = SocketType}) ->
http_transport:setopts(SocketType, Socket, [{active, once}]).
+close_socket(#session{socket = Socket, socket_type = SocketType}) ->
+ http_transport:close(SocketType, Socket).
+
activate_request_timeout(
#state{request = #request{timer = undefined} = Request} = State) ->
Timeout = (Request#request.settings)#http_options.timeout,
@@ -1378,7 +1375,7 @@ activate_queue_timeout(Time, State) ->
State#state{timers = #timers{queue_timer = Ref}}.
-is_pipeline_enabled_client(#tcp_session{type = pipeline}) ->
+is_pipeline_enabled_client(#session{type = pipeline}) ->
true;
is_pipeline_enabled_client(_) ->
false.
@@ -1391,7 +1388,7 @@ is_keep_alive_enabled_server("HTTP/1.0",
is_keep_alive_enabled_server(_,_) ->
false.
-is_keep_alive_connection(Headers, #tcp_session{client_close = ClientClose}) ->
+is_keep_alive_connection(Headers, #session{client_close = ClientClose}) ->
(not ((ClientClose) orelse httpc_response:is_server_closing(Headers))).
try_to_enable_pipeline_or_keep_alive(
@@ -1416,7 +1413,7 @@ try_to_enable_pipeline_or_keep_alive(
httpc_manager:insert_session(Session, ProfileName),
%% Make sure type is keep_alive in session
%% as it in this case might be pipeline
- NewSession = Session#tcp_session{type = keep_alive},
+ NewSession = Session#session{type = keep_alive},
State#state{status = keep_alive,
session = NewSession}
end;
@@ -1424,14 +1421,16 @@ try_to_enable_pipeline_or_keep_alive(
State#state{status = close}
end.
-answer_request(Request, Msg, #state{timers = Timers} = State) ->
+answer_request(#request{id = RequestId, from = From} = Request, Msg,
+ #state{timers = Timers, profile_name = ProfileName} = State) ->
?hcrt("answer request", [{request, Request}]),
- httpc_response:send(Request#request.from, Msg),
+ httpc_response:send(From, Msg),
RequestTimers = Timers#timers.request_timers,
TimerRef =
- proplists:get_value(Request#request.id, RequestTimers, undefined),
- Timer = {Request#request.id, TimerRef},
+ proplists:get_value(RequestId, RequestTimers, undefined),
+ Timer = {RequestId, TimerRef},
cancel_timer(TimerRef, {timeout, Request#request.id}),
+ httpc_manager:request_done(RequestId, ProfileName),
State#state{request = Request#request{from = answer_sent},
timers =
Timers#timers{request_timers =
@@ -1549,11 +1548,11 @@ init_status_line(#request{settings = Settings}) ->
socket_type(#request{scheme = http}) ->
ip_comm;
socket_type(#request{scheme = https, settings = Settings}) ->
- {ssl, Settings#http_options.ssl};
-socket_type(http) ->
- ip_comm;
-socket_type(https) ->
- {ssl, []}. %% Dummy value ok for ex setopts that does not use this value
+ Settings#http_options.ssl.
+%% socket_type(http) ->
+%% ip_comm;
+%% socket_type(https) ->
+%% {ssl1, []}. %% Dummy value ok for ex setopts that does not use this value
start_stream({_Version, _Code, _ReasonPhrase}, _Headers,
#request{stream = none} = Request) ->
@@ -1622,18 +1621,15 @@ end_stream(SL, R) ->
next_body_chunk(#state{request = #request{stream = {self, once}},
- once = once, session = Session} = State) ->
- http_transport:setopts(socket_type(Session#tcp_session.scheme),
- Session#tcp_session.socket,
- [{active, once}]),
+ once = once,
+ session = Session} = State) ->
+ activate_once(Session),
State#state{once = inactive};
next_body_chunk(#state{request = #request{stream = {self, once}},
once = inactive} = State) ->
State; %% Wait for user to call stream_next
next_body_chunk(#state{session = Session} = State) ->
- http_transport:setopts(socket_type(Session#tcp_session.scheme),
- Session#tcp_session.socket,
- [{active, once}]),
+ activate_once(Session),
State.
handle_verbose(verbose) ->
@@ -1710,6 +1706,11 @@ handle_verbose(_) ->
%% ok.
+send_raw(#session{socket = Socket, socket_type = SocketType}, Body) ->
+ http_transport:send(SocketType, Socket, Body).
+
+
+
call(Msg, Pid) ->
Timeout = infinity,
call(Msg, Pid, Timeout).
diff --git a/lib/inets/src/http_client/httpc_internal.hrl b/lib/inets/src/http_client/httpc_internal.hrl
index 4d76c4beb3..3cdd95a02b 100644
--- a/lib/inets/src/http_client/httpc_internal.hrl
+++ b/lib/inets/src/http_client/httpc_internal.hrl
@@ -18,7 +18,11 @@
%%
%%
--include("inets_internal.hrl").
+-ifndef(httpc_internal_hrl).
+-define(httpc_internal_hrl, true).
+
+-include_lib("inets/src/inets_app/inets_internal.hrl").
+
-define(SERVICE, httpc).
-define(hcri(Label, Data), ?report_important(Label, ?SERVICE, Data)).
-define(hcrv(Label, Data), ?report_verbose(Label, ?SERVICE, Data)).
@@ -104,13 +108,14 @@
}
).
--record(tcp_session,
+-record(session,
{
id, % {{Host, Port}, HandlerPid}
client_close, % true | false
scheme, % http (HTTP/TCP) | https (HTTP/SSL/TCP)
socket, % Open socket, used by connection
- queue_length = 1, % Current length of pipeline or keep alive queue
+ socket_type, % socket-type, used by connection
+ queue_length = 1, % Current length of pipeline or keep-alive queue
type % pipeline | keep_alive (wait for response before sending new request)
}).
@@ -138,3 +143,6 @@
%% path, % string()
%% q % query: string()
%% }).
+
+
+-endif. % -ifdef(httpc_internal_hrl).
diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl
index f3cd81f4a7..d5d6376369 100644
--- a/lib/inets/src/http_client/httpc_manager.erl
+++ b/lib/inets/src/http_client/httpc_manager.erl
@@ -21,8 +21,8 @@
-behaviour(gen_server).
+-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpc_internal.hrl").
--include("http_internal.hrl").
%% Internal Application API
-export([
@@ -30,6 +30,7 @@
request/2,
cancel_request/2,
request_canceled/2,
+ request_done/2,
retry_request/2,
redirect_request/2,
insert_session/2,
@@ -171,6 +172,18 @@ request_canceled(RequestId, ProfileName) ->
%%--------------------------------------------------------------------
+%% Function: request_done(RequestId, ProfileName) -> ok
+%% RequestId - ref()
+%% ProfileName = atom()
+%%
+%% Description: Inform tha manager that a request has been completed.
+%%--------------------------------------------------------------------
+
+request_done(RequestId, ProfileName) ->
+ cast(ProfileName, {request_done, RequestId}).
+
+
+%%--------------------------------------------------------------------
%% Function: insert_session(Session, ProfileName) -> _
%% Session - #tcp_session{}
%% ProfileName - atom()
@@ -320,7 +333,7 @@ do_init(ProfileName, CookiesDir) ->
?hcrt("create session db", []),
SessionDbName = session_db_name(ProfileName),
ets:new(SessionDbName,
- [public, set, named_table, {keypos, #tcp_session.id}]),
+ [public, set, named_table, {keypos, #session.id}]),
%% Create handler db
?hcrt("create handler/request db", []),
@@ -486,6 +499,11 @@ handle_cast({request_canceled, RequestId}, State) ->
{noreply, State}
end;
+handle_cast({request_done, RequestId}, State) ->
+ ?hcrv("request done", [{request_id, RequestId}]),
+ ets:delete(State#state.handler_db, RequestId),
+ {noreply, State};
+
handle_cast({set_options, Options}, State = #state{options = OldOptions}) ->
?hcrv("set options", [{options, Options}, {old_options, OldOptions}]),
NewOptions =
@@ -858,12 +876,12 @@ select_session(Method, HostPort, Scheme, SessionType,
%% client_close, scheme and type specified.
%% The fields id (part of: HandlerPid) and queue_length
%% specified.
- Pattern = #tcp_session{id = {HostPort, '$1'},
- client_close = false,
- scheme = Scheme,
- socket = '_',
- queue_length = '$2',
- type = SessionType},
+ Pattern = #session{id = {HostPort, '$1'},
+ client_close = false,
+ scheme = Scheme,
+ queue_length = '$2',
+ type = SessionType,
+ _ = '_'},
%% {'_', {HostPort, '$1'}, false, Scheme, '_', '$2', SessionTyp},
Candidates = ets:match(SessionDb, Pattern),
?hcrd("select session", [{host_port, HostPort},
diff --git a/lib/inets/src/http_client/httpc_request.erl b/lib/inets/src/http_client/httpc_request.erl
index 55e0af4b42..d4df97ad40 100644
--- a/lib/inets/src/http_client/httpc_request.erl
+++ b/lib/inets/src/http_client/httpc_request.erl
@@ -19,12 +19,13 @@
-module(httpc_request).
--include("http_internal.hrl").
+-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpc_internal.hrl").
%%% Internal API
-export([send/3, is_idempotent/1, is_client_closing/1]).
+
%%%=========================================================================
%%% Internal application API
%%%=========================================================================
@@ -39,10 +40,9 @@
%%
%% Description: Composes and sends a HTTP-request.
%%-------------------------------------------------------------------------
-send(SendAddr, #request{scheme = Scheme, socket_opts = SocketOpts} = Request,
- Socket)
+send(SendAddr, #session{socket = Socket, socket_type = SocketType},
+ #request{socket_opts = SocketOpts} = Request)
when is_list(SocketOpts) ->
- SocketType = socket_type(Scheme),
case http_transport:setopts(SocketType, Socket, SocketOpts) of
ok ->
send(SendAddr, Socket, SocketType,
@@ -50,8 +50,7 @@ send(SendAddr, #request{scheme = Scheme, socket_opts = SocketOpts} = Request,
{error, Reason} ->
{error, {setopts_failed, Reason}}
end;
-send(SendAddr, #request{scheme = Scheme} = Request, Socket) ->
- SocketType = socket_type(Scheme),
+send(SendAddr, #session{socket = Socket, socket_type = SocketType}, Request) ->
send(SendAddr, Socket, SocketType, Request).
send(SendAddr, Socket, SocketType,
@@ -209,10 +208,6 @@ headers(_, "HTTP/0.9") ->
headers(Headers, _) ->
Headers.
-socket_type(http) ->
- ip_comm;
-socket_type(https) ->
- {ssl, []}.
http_headers([], Headers) ->
lists:flatten(Headers);
diff --git a/lib/inets/src/http_client/httpc_response.erl b/lib/inets/src/http_client/httpc_response.erl
index df7d40a33e..bb9c516259 100644
--- a/lib/inets/src/http_client/httpc_response.erl
+++ b/lib/inets/src/http_client/httpc_response.erl
@@ -19,7 +19,7 @@
-module(httpc_response).
--include("http_internal.hrl").
+-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpc_internal.hrl").
%% API
diff --git a/lib/inets/src/http_lib/Makefile b/lib/inets/src/http_lib/Makefile
index 7f4c92861c..5dac3b0c00 100644
--- a/lib/inets/src/http_lib/Makefile
+++ b/lib/inets/src/http_lib/Makefile
@@ -55,24 +55,16 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
-# INETS FLAGS
-# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
-
-
-# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-INETS_ERL_FLAGS += -I ../inets_app
-ifeq ($(WARN_UNUSED_WARS),true)
-ERL_COMPILE_FLAGS += +warn_unused_vars
-endif
+include ../inets_app/inets.mk
-ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \
- $(INETS_FLAGS) \
- +'{parse_transform,sys_pre_attributes}' \
- +'{attribute,insert,app_vsn,$(APP_VSN)}'
+ERL_COMPILE_FLAGS += \
+ $(INETS_FLAGS) \
+ $(INETS_ERL_COMPILE_FLAGS) \
+ -I../../include \
+ -I../inets_app
# ----------------------------------------------------
@@ -94,9 +86,10 @@ docs:
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/src
- $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/src/http_lib
+ $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/http_lib
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
release_docs_spec:
diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl
index bb2e831727..5440f214b5 100644
--- a/lib/inets/src/http_lib/http_internal.hrl
+++ b/lib/inets/src/http_lib/http_internal.hrl
@@ -1,28 +1,37 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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("inets_internal.hrl").
+-ifndef(http_internal_hrl).
+-define(http_internal_hrl, true).
--define(HTTP_MAX_BODY_SIZE, nolimit).
+-include_lib("inets/src/inets_app/inets_internal.hrl").
+
+-define(HTTP_MAX_BODY_SIZE, nolimit).
-define(HTTP_MAX_HEADER_SIZE, 10240).
--define(HTTP_MAX_URI_SIZE, nolimit).
+-define(HTTP_MAX_URI_SIZE, nolimit).
+
+-ifndef(HTTP_DEFAULT_SSL_KIND).
+-define(HTTP_DEFAULT_SSL_KIND, ossl).
+%% -define(HTTP_DEFAULT_SSL_KIND, essl).
+-endif. % -ifdef(HTTP_DEFAULT_SSL_KIND).
+
%%% Response headers
-record(http_response_h,{
@@ -106,3 +115,5 @@
'last-modified',
other=[] % list() - Key/Value list with other headers
}).
+
+-endif. % -ifdef(http_internal_hrl).
diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl
index 7c2ac626e6..b8121852b8 100644
--- a/lib/inets/src/http_lib/http_transport.erl
+++ b/lib/inets/src/http_lib/http_transport.erl
@@ -36,7 +36,9 @@
-export([negotiate/3]).
--include("inets_internal.hrl").
+-include_lib("inets/src/inets_app/inets_internal.hrl").
+-include("http_internal.hrl").
+
-define(SERVICE, httpl).
-define(hlri(Label, Content), ?report_important(Label, ?SERVICE, Content)).
-define(hlrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)).
@@ -55,6 +57,18 @@
%% Description: Makes sure inet_db or ssl is started.
%%-------------------------------------------------------------------------
start(ip_comm) ->
+ do_start_ip_comm();
+
+%% This is just for backward compatibillity
+start({ssl, _}) ->
+ do_start_ssl();
+start({ossl, _}) ->
+ do_start_ssl();
+start({essl, _}) ->
+ do_start_ssl().
+
+
+do_start_ip_comm() ->
case inet_db:start() of
{ok, _} ->
ok;
@@ -62,8 +76,9 @@ start(ip_comm) ->
ok;
Error ->
Error
- end;
-start({ssl, _}) ->
+ end.
+
+do_start_ssl() ->
case ssl:start() of
ok ->
ok;
@@ -97,18 +112,26 @@ connect(ip_comm = _SocketType, {Host, Port}, Opts0, Timeout)
[{host, Host}, {port, Port}, {opts, Opts}, {timeout, Timeout}]),
gen_tcp:connect(Host, Port, Opts, Timeout);
-connect({ssl, SslConfig}, {Host, Port}, _, Timeout) ->
- Opts = [binary, {active, false}] ++ SslConfig,
- ?hlrt("connect using ssl",
- [{host, Host}, {port, Port}, {ssl_config, SslConfig},
- {timeout, Timeout}]),
+%% Wrapper for backaward compatibillity
+connect({ssl, SslConfig}, Address, Opts, Timeout) ->
+ connect({?HTTP_DEFAULT_SSL_KIND, SslConfig}, Address, Opts, Timeout);
+
+connect({ossl, SslConfig}, {Host, Port}, _, Timeout) ->
+ Opts = [binary, {active, false}, {ssl_imp, old}] ++ SslConfig,
+ ?hlrt("connect using ossl",
+ [{host, Host},
+ {port, Port},
+ {ssl_config, SslConfig},
+ {timeout, Timeout}]),
ssl:connect(Host, Port, Opts, Timeout);
-connect({erl_ssl, SslConfig}, {Host, Port}, _, Timeout) ->
+connect({essl, SslConfig}, {Host, Port}, _, Timeout) ->
Opts = [binary, {active, false}, {ssl_imp, new}] ++ SslConfig,
- ?hlrt("connect using erl_ssl",
- [{host, Host}, {port, Port}, {ssl_config, SslConfig},
- {timeout, Timeout}]),
+ ?hlrt("connect using essl",
+ [{host, Host},
+ {port, Port},
+ {ssl_config, SslConfig},
+ {timeout, Timeout}]),
ssl:connect(Host, Port, Opts, Timeout).
@@ -136,13 +159,32 @@ listen(ip_comm, Addr, Port) ->
Else
end;
-listen({ssl, SSLConfig} = Ssl, Addr, Port) ->
+%% Wrapper for backaward compatibillity
+listen({ssl, SSLConfig}, Addr, Port) ->
+ ?hlrt("listen (wrapper)",
+ [{addr, Addr},
+ {port, Port},
+ {ssl_config, SSLConfig}]),
+ listen({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Addr, Port);
+
+listen({ossl, SSLConfig} = Ssl, Addr, Port) ->
+ ?hlrt("listen (ossl)",
+ [{addr, Addr},
+ {port, Port},
+ {ssl_config, SSLConfig}]),
Opt = sock_opt(Ssl, Addr, SSLConfig),
- ssl:listen(Port, Opt);
-
-listen({erl_ssl, SSLConfig} = Ssl, Addr, Port) ->
+ ?hlrt("listen options", [{opt, Opt}]),
+ ssl:listen(Port, [{ssl_imp, old} | Opt]);
+
+listen({essl, SSLConfig} = Ssl, Addr, Port) ->
+ ?hlrt("listen (essl)",
+ [{addr, Addr},
+ {port, Port},
+ {ssl_config, SSLConfig}]),
Opt = sock_opt(Ssl, Addr, SSLConfig),
- ssl:listen(Port, [{ssl_imp, new} | Opt]).
+ ?hlrt("listen options", [{opt, Opt}]),
+ Opt2 = [{ssl_imp, new}, {reuseaddr, true} | Opt],
+ ssl:listen(Port, Opt2).
listen_ip_comm(Addr, Port) ->
@@ -228,9 +270,17 @@ ip_family_of(IpFamilyStr) ->
%%-------------------------------------------------------------------------
accept(SocketType, ListenSocket) ->
accept(SocketType, ListenSocket, infinity).
+
accept(ip_comm, ListenSocket, Timeout) ->
gen_tcp:accept(ListenSocket, Timeout);
-accept({ssl,_SSLConfig}, ListenSocket, Timeout) ->
+
+%% Wrapper for backaward compatibillity
+accept({ssl, SSLConfig}, ListenSocket, Timeout) ->
+ accept({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, ListenSocket, Timeout);
+
+accept({ossl, _SSLConfig}, ListenSocket, Timeout) ->
+ ssl:transport_accept(ListenSocket, Timeout);
+accept({essl, _SSLConfig}, ListenSocket, Timeout) ->
ssl:transport_accept(ListenSocket, Timeout).
@@ -244,7 +294,15 @@ accept({ssl,_SSLConfig}, ListenSocket, Timeout) ->
%%-------------------------------------------------------------------------
controlling_process(ip_comm, Socket, NewOwner) ->
gen_tcp:controlling_process(Socket, NewOwner);
-controlling_process({ssl, _}, Socket, NewOwner) ->
+
+%% Wrapper for backaward compatibillity
+controlling_process({ssl, SSLConfig}, Socket, NewOwner) ->
+ controlling_process({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, NewOwner);
+
+controlling_process({ossl, _}, Socket, NewOwner) ->
+ ssl:controlling_process(Socket, NewOwner);
+
+controlling_process({essl, _}, Socket, NewOwner) ->
ssl:controlling_process(Socket, NewOwner).
@@ -259,9 +317,23 @@ controlling_process({ssl, _}, Socket, NewOwner) ->
setopts(ip_comm, Socket, Options) ->
?hlrt("ip_comm setopts", [{socket, Socket}, {options, Options}]),
inet:setopts(Socket, Options);
-setopts({ssl, _}, Socket, Options) ->
- ?hlrt("ssl setopts", [{socket, Socket}, {options, Options}]),
- ssl:setopts(Socket, Options).
+
+%% Wrapper for backaward compatibillity
+setopts({ssl, SSLConfig}, Socket, Options) ->
+ setopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options);
+
+setopts({ossl, _}, Socket, Options) ->
+ ?hlrt("[o]ssl setopts", [{socket, Socket}, {options, Options}]),
+ Reason = (catch ssl:setopts(Socket, Options)),
+ ?hlrt("[o]ssl setopts result", [{reason, Reason}]),
+ Reason;
+
+
+setopts({essl, _}, Socket, Options) ->
+ ?hlrt("[e]ssl setopts", [{socket, Socket}, {options, Options}]),
+ Reason = (catch ssl:setopts(Socket, Options)),
+ ?hlrt("[e]ssl setopts result", [{reason, Reason}]),
+ Reason.
%%-------------------------------------------------------------------------
@@ -283,15 +355,27 @@ getopts(ip_comm, Socket, Options) ->
{error, _} ->
[]
end;
-getopts({ssl, _}, Socket, Options) ->
+
+%% Wrapper for backaward compatibillity
+getopts({ssl, SSLConfig}, Socket, Options) ->
+ getopts({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Options);
+
+getopts({ossl, _}, Socket, Options) ->
?hlrt("ssl getopts", [{socket, Socket}, {options, Options}]),
+ getopts_ssl(Socket, Options);
+
+getopts({essl, _}, Socket, Options) ->
+ ?hlrt("essl getopts", [{socket, Socket}, {options, Options}]),
+ getopts_ssl(Socket, Options).
+
+getopts_ssl(Socket, Options) ->
case ssl:getopts(Socket, Options) of
{ok, SocketOpts} ->
SocketOpts;
{error, _} ->
[]
end.
-
+
%%-------------------------------------------------------------------------
%% getstat(SocketType, Socket) -> socket_stats()
@@ -308,8 +392,15 @@ getstat(ip_comm = _SocketType, Socket) ->
{error, _} ->
[]
end;
-getstat({ssl, _} = _SocketType, _Socket) ->
- %% ?hlrt("ssl getstat", [{socket, Socket}]),
+
+%% Wrapper for backaward compatibillity
+getstat({ssl, SSLConfig}, Socket) ->
+ getstat({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
+
+getstat({ossl, _} = _SocketType, _Socket) ->
+ [];
+
+getstat({essl, _} = _SocketType, _Socket) ->
[].
@@ -322,7 +413,15 @@ getstat({ssl, _} = _SocketType, _Socket) ->
%%-------------------------------------------------------------------------
send(ip_comm, Socket, Message) ->
gen_tcp:send(Socket, Message);
-send({ssl, _}, Socket, Message) ->
+
+%% Wrapper for backaward compatibillity
+send({ssl, SSLConfig}, Socket, Message) ->
+ send({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Message);
+
+send({ossl, _}, Socket, Message) ->
+ ssl:send(Socket, Message);
+
+send({essl, _}, Socket, Message) ->
ssl:send(Socket, Message).
@@ -335,9 +434,18 @@ send({ssl, _}, Socket, Message) ->
%%-------------------------------------------------------------------------
close(ip_comm, Socket) ->
gen_tcp:close(Socket);
-close({ssl, _}, Socket) ->
+
+%% Wrapper for backaward compatibillity
+close({ssl, SSLConfig}, Socket) ->
+ close({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
+
+close({ossl, _}, Socket) ->
+ ssl:close(Socket);
+
+close({essl, _}, Socket) ->
ssl:close(Socket).
+
%%-------------------------------------------------------------------------
%% peername(SocketType, Socket) -> {Port, SockName}
%% SocketType = ip_comm | {ssl, _}
@@ -368,7 +476,17 @@ peername(ip_comm, Socket) ->
{-1, "unknown"}
end;
-peername({ssl, _}, Socket) ->
+%% Wrapper for backaward compatibillity
+peername({ssl, SSLConfig}, Socket) ->
+ peername({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
+
+peername({ossl, _}, Socket) ->
+ peername_ssl(Socket);
+
+peername({essl, _}, Socket) ->
+ peername_ssl(Socket).
+
+peername_ssl(Socket) ->
case ssl:peername(Socket) of
{ok,{{A, B, C, D}, Port}} ->
PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++
@@ -409,7 +527,17 @@ sockname(ip_comm, Socket) ->
{-1, "unknown"}
end;
-sockname({ssl, _}, Socket) ->
+%% Wrapper for backaward compatibillity
+sockname({ssl, SSLConfig}, Socket) ->
+ sockname({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket);
+
+sockname({ossl, _}, Socket) ->
+ sockname_ssl(Socket);
+
+sockname({essl, _}, Socket) ->
+ sockname_ssl(Socket).
+
+sockname_ssl(Socket) ->
case ssl:sockname(Socket) of
{ok,{{A, B, C, D}, Port}} ->
SockName = integer_to_list(A)++"."++integer_to_list(B)++"."++
@@ -455,22 +583,31 @@ sock_opt2(Opts) ->
[{packet, 0}, {active, false} | Opts].
negotiate(ip_comm,_,_) ->
+ ?hlrt("negotiate(ip_comm)", []),
ok;
-negotiate({ssl,_},Socket,Timeout) ->
- negotiate(Socket, Timeout);
-negotiate({erl_ssl, _}, Socket, Timeout) ->
- negotiate(Socket, Timeout).
-
-negotiate(Socket, Timeout) ->
+negotiate({ssl, SSLConfig}, Socket, Timeout) ->
+ ?hlrt("negotiate(ssl)", []),
+ negotiate({?HTTP_DEFAULT_SSL_KIND, SSLConfig}, Socket, Timeout);
+negotiate({ossl, _}, Socket, Timeout) ->
+ ?hlrt("negotiate(ossl)", []),
+ negotiate_ssl(Socket, Timeout);
+negotiate({essl, _}, Socket, Timeout) ->
+ ?hlrt("negotiate(essl)", []),
+ negotiate_ssl(Socket, Timeout).
+
+negotiate_ssl(Socket, Timeout) ->
+ ?hlrt("negotiate_ssl", [{socket, Socket}, {timeout, Timeout}]),
case ssl:ssl_accept(Socket, Timeout) of
ok ->
ok;
- {error, Error} ->
- case lists:member(Error,
- [timeout,econnreset,esslaccept,esslerrssl]) of
+ {error, Reason} ->
+ ?hlrd("negotiate_ssl - accept failed", [{reason, Reason}]),
+ %% Look for "valid" error reasons
+ ValidReasons = [timeout, econnreset, esslaccept, esslerrssl],
+ case lists:member(Reason, ValidReasons) of
true ->
- {error,normal};
+ {error, normal};
false ->
- {error, Error}
+ {error, Reason}
end
end.
diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl
index ddb58c7116..4f1147176c 100644
--- a/lib/inets/src/http_lib/http_util.erl
+++ b/lib/inets/src/http_lib/http_util.erl
@@ -38,13 +38,79 @@ to_upper(Str) ->
to_lower(Str) ->
string:to_lower(Str).
-convert_netscapecookie_date([_D,_A,_Y, $,, _SP,
- D1,D2,_DA,
- M,O,N,_DA,
- Y1,Y2,Y3,Y4,_SP,
- H1,H2,_Col,
- M1,M2,_Col,
+%% Example: Mon, 09-Dec-2002 13:46:00 GMT
+convert_netscapecookie_date([_D,_A,_Y, $,, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y1,Y2,Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
+ S1,S2|_Rest]) ->
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+convert_netscapecookie_date([_D,_A,_Y, $,, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
+ S1,S2|_Rest]) ->
+ {CurrentYear, _, _} = date(),
+ [Y1,Y2|_] = integer_to_list(CurrentYear),
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+convert_netscapecookie_date([_D,_A,_Y, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y1,Y2,Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
S1,S2|_Rest]) ->
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+convert_netscapecookie_date([_D,_A,_Y, $ ,
+ D1,D2, $-,
+ M,O,N, $-,
+ Y3,Y4, $ ,
+ H1,H2, $:,
+ M1,M2, $:,
+ S1,S2|_Rest]) ->
+ {CurrentYear, _, _} = date(),
+ [Y1,Y2|_] = integer_to_list(CurrentYear),
+ Year = list_to_integer([Y1,Y2,Y3,Y4]),
+ Day = list_to_integer([D1,D2]),
+ Month = convert_month([M,O,N]),
+ Hour = list_to_integer([H1,H2]),
+ Min = list_to_integer([M1,M2]),
+ Sec = list_to_integer([S1,S2]),
+ {{Year,Month,Day},{Hour,Min,Sec}};
+
+%% Sloppy...
+convert_netscapecookie_date([_D,_A,_Y, $,, _SP,
+ D1,D2,_DA,
+ M,O,N,_DA,
+ Y1,Y2,Y3,Y4,_SP,
+ H1,H2,_Col,
+ M1,M2,_Col,
+ S1,S2|_Rest]) ->
Year=list_to_integer([Y1,Y2,Y3,Y4]),
Day=list_to_integer([D1,D2]),
Month=convert_month([M,O,N]),
@@ -54,12 +120,12 @@ convert_netscapecookie_date([_D,_A,_Y, $,, _SP,
{{Year,Month,Day},{Hour,Min,Sec}};
convert_netscapecookie_date([_D,_A,_Y, _SP,
- D1,D2,_DA,
- M,O,N,_DA,
- Y1,Y2,Y3,Y4,_SP,
- H1,H2,_Col,
- M1,M2,_Col,
- S1,S2|_Rest]) ->
+ D1,D2,_DA,
+ M,O,N,_DA,
+ Y1,Y2,Y3,Y4,_SP,
+ H1,H2,_Col,
+ M1,M2,_Col,
+ S1,S2|_Rest]) ->
Year=list_to_integer([Y1,Y2,Y3,Y4]),
Day=list_to_integer([D1,D2]),
Month=convert_month([M,O,N]),
@@ -68,17 +134,17 @@ convert_netscapecookie_date([_D,_A,_Y, _SP,
Sec=list_to_integer([S1,S2]),
{{Year,Month,Day},{Hour,Min,Sec}}.
-hexlist_to_integer([])->
+hexlist_to_integer([]) ->
empty;
%%When the string only contains one value its eaasy done.
%% 0-9
-hexlist_to_integer([Size]) when Size >= 48 , Size =< 57 ->
+hexlist_to_integer([Size]) when (Size >= 48) andalso (Size =< 57) ->
Size - 48;
%% A-F
-hexlist_to_integer([Size]) when Size >= 65 , Size =< 70 ->
+hexlist_to_integer([Size]) when (Size >= 65) andalso (Size =< 70) ->
Size - 55;
%% a-f
-hexlist_to_integer([Size]) when Size >= 97 , Size =< 102 ->
+hexlist_to_integer([Size]) when (Size >= 97) andalso (Size =< 102) ->
Size - 87;
hexlist_to_integer([_Size]) ->
not_a_num;
@@ -141,7 +207,7 @@ hexlist_to_integer2([HexVal | HexString], Pos, Sum)
hexlist_to_integer2(_AfterHexString, _Pos, Sum)->
Sum.
-integer_to_hexlist(Num, Pot, Res) when Pot<0 ->
+integer_to_hexlist(Num, Pot, Res) when Pot < 0 ->
convert_to_ascii([Num | Res]);
integer_to_hexlist(Num,Pot,Res) ->
@@ -163,7 +229,9 @@ convert_to_ascii(RevesedNum) ->
convert_to_ascii([], Num)->
Num;
-convert_to_ascii([Num | Reversed], Number) when Num > -1, Num < 10 ->
+convert_to_ascii([Num | Reversed], Number)
+ when (Num > -1) andalso (Num < 10) ->
convert_to_ascii(Reversed, [Num + 48 | Number]);
-convert_to_ascii([Num | Reversed], Number) when Num > 9, Num < 16 ->
+convert_to_ascii([Num | Reversed], Number)
+ when (Num > 9) andalso (Num < 16) ->
convert_to_ascii(Reversed, [Num + 55 | Number]).
diff --git a/lib/inets/src/http_server/Makefile b/lib/inets/src/http_server/Makefile
index ce1405011e..879e605217 100644
--- a/lib/inets/src/http_server/Makefile
+++ b/lib/inets/src/http_server/Makefile
@@ -90,20 +90,17 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
-# INETS FLAGS
-# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
-
-
-# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-INETS_ERL_FLAGS += -I ../http_lib -I ../inets_app -pa ../../ebin
-ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \
- $(INETS_FLAGS) \
- +'{parse_transform,sys_pre_attributes}' \
- +'{attribute,insert,app_vsn,$(APP_VSN)}'
+include ../inets_app/inets.mk
+
+ERL_COMPILE_FLAGS += \
+ $(INETS_FLAGS) \
+ $(INETS_ERL_COMPILE_FLAGS) \
+ -I../../include \
+ -I../inets_app \
+ -I../http_lib
# ----------------------------------------------------
@@ -125,9 +122,10 @@ docs:
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/src
- $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/src/http_server
+ $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/http_server
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
release_docs_spec:
diff --git a/lib/inets/src/http_server/httpd.erl b/lib/inets/src/http_server/httpd.erl
index 8fe54ccef6..fb5fa1c758 100644
--- a/lib/inets/src/http_server/httpd.erl
+++ b/lib/inets/src/http_server/httpd.erl
@@ -24,54 +24,25 @@
-include("httpd.hrl").
--deprecated({start, 0, next_major_release}).
--deprecated({start, 1, next_major_release}).
--deprecated({start_link, 1, next_major_release}).
--deprecated({start_child, 0, next_major_release}).
--deprecated({start_child, 1, next_major_release}).
--deprecated({stop, 0, next_major_release}).
--deprecated({stop, 1, next_major_release}).
--deprecated({stop, 2, next_major_release}).
--deprecated({stop_child, 0, next_major_release}).
--deprecated({stop_child, 1, next_major_release}).
--deprecated({stop_child, 2, next_major_release}).
--deprecated({restart, 0, next_major_release}).
--deprecated({restart, 1, next_major_release}).
--deprecated({restart, 2, next_major_release}).
--deprecated({block, 0, next_major_release}).
--deprecated({block, 1, next_major_release}).
--deprecated({block, 2, next_major_release}).
--deprecated({block, 3, next_major_release}).
--deprecated({block, 4, next_major_release}).
--deprecated({unblock, 0, next_major_release}).
--deprecated({unblock, 1, next_major_release}).
--deprecated({unblock, 2, next_major_release}).
%% Behavior callbacks
--export([start_standalone/1, start_service/1, stop_service/1, services/0,
- service_info/1]).
+-export([
+ start_standalone/1,
+ start_service/1,
+ stop_service/1,
+ services/0,
+ service_info/1
+ ]).
%% API
-export([parse_query/1, reload_config/2, info/1, info/2, info/3]).
-%% Deprecated
--export([start/0, start/1,
- start_link/0, start_link/1,
- start_child/0,start_child/1,
- stop/0,stop/1,stop/2,
- stop_child/0,stop_child/1,stop_child/2,
- restart/0,restart/1,restart/2]).
-
-%% Management stuff should be internal functions
-%% Will be from r13
--export([block/0,block/1,block/2,block/3,block/4,
- unblock/0,unblock/1,unblock/2]).
-
-%% Internal Debugging and status info stuff...
-%% Keep for now should probably be moved to test catalog
--export([get_status/1,get_status/2,get_status/3,
- get_admin_state/0,get_admin_state/1,get_admin_state/2,
- get_usage_state/0,get_usage_state/1,get_usage_state/2]).
+%% Internal debugging and status info stuff...
+-export([
+ get_status/1, get_status/2, get_status/3,
+ get_admin_state/0, get_admin_state/1, get_admin_state/2,
+ get_usage_state/0, get_usage_state/1, get_usage_state/2
+ ]).
%%%========================================================================
%%% API
@@ -111,6 +82,7 @@ info(Address, Port, Properties) when is_integer(Port) andalso
is_list(Properties) ->
httpd_conf:get_config(Address, Port, Properties).
+
%%%========================================================================
%%% Behavior callbacks
%%%========================================================================
@@ -149,6 +121,8 @@ service_info(Pid) ->
exit:{noproc, _} ->
{error, service_not_available}
end.
+
+
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
@@ -176,6 +150,7 @@ child_name2info({httpd_instance_sup, Address, Port}) ->
{ok, [{bind_address, Address}, {port, Port} | Info]}
end.
+
reload(Config, Address, Port) ->
Name = make_name(Address,Port),
case whereis(Name) of
@@ -185,26 +160,12 @@ reload(Config, Address, Port) ->
{error,not_started}
end.
-reload(Addr, Port) when is_integer(Port) ->
- Name = make_name(Addr,Port),
- case whereis(Name) of
- Pid when is_pid(Pid) ->
- httpd_manager:reload(Pid, undefined);
- _ ->
- {error,not_started}
- end.
%%% =========================================================
-%%% Function: block/0, block/1, block/2, block/3, block/4
-%%% block()
-%%% block(Port)
-%%% block(ConfigFile)
-%%% block(Addr,Port)
-%%% block(Port,Mode)
-%%% block(ConfigFile,Mode)
-%%% block(Addr,Port,Mode)
-%%% block(ConfigFile,Mode,Timeout)
-%%% block(Addr,Port,Mode,Timeout)
+%%% Function: block/3, block/4
+%%% block(Addr, Port, Mode)
+%%% block(ConfigFile, Mode, Timeout)
+%%% block(Addr, Port, Mode, Timeout)
%%%
%%% Returns: ok | {error,Reason}
%%%
@@ -237,58 +198,32 @@ reload(Addr, Port) when is_integer(Port) ->
%%% Mode -> disturbing | non_disturbing
%%% Timeout -> integer()
%%%
-block() -> block(undefined,8888,disturbing).
-
-block(Port) when is_integer(Port) ->
- block(undefined,Port,disturbing);
-
-block(ConfigFile) when is_list(ConfigFile) ->
- case get_addr_and_port(ConfigFile) of
- {ok,Addr,Port} ->
- block(Addr,Port,disturbing);
- Error ->
- Error
- end.
-
-block(Addr,Port) when is_integer(Port) ->
- block(Addr,Port,disturbing);
-
-block(Port,Mode) when is_integer(Port) andalso is_atom(Mode) ->
- block(undefined,Port,Mode);
-
-block(ConfigFile,Mode) when is_list(ConfigFile) andalso is_atom(Mode) ->
- case get_addr_and_port(ConfigFile) of
- {ok,Addr,Port} ->
- block(Addr,Port,Mode);
- Error ->
- Error
- end.
-
-block(Addr,Port,disturbing) when is_integer(Port) ->
- do_block(Addr,Port,disturbing);
-block(Addr,Port,non_disturbing) when is_integer(Port) ->
- do_block(Addr,Port,non_disturbing);
+block(Addr, Port, disturbing) when is_integer(Port) ->
+ do_block(Addr, Port, disturbing);
+block(Addr, Port, non_disturbing) when is_integer(Port) ->
+ do_block(Addr, Port, non_disturbing);
-block(ConfigFile,Mode,Timeout) when is_list(ConfigFile) andalso
- is_atom(Mode) andalso
- is_integer(Timeout) ->
+block(ConfigFile, Mode, Timeout)
+ when is_list(ConfigFile) andalso
+ is_atom(Mode) andalso
+ is_integer(Timeout) ->
case get_addr_and_port(ConfigFile) of
- {ok,Addr,Port} ->
- block(Addr,Port,Mode,Timeout);
+ {ok, Addr, Port} ->
+ block(Addr, Port, Mode, Timeout);
Error ->
Error
end.
-block(Addr,Port,non_disturbing,Timeout)
+block(Addr, Port, non_disturbing, Timeout)
+ when is_integer(Port) andalso is_integer(Timeout) ->
+ do_block(Addr, Port, non_disturbing, Timeout);
+block(Addr,Port,disturbing,Timeout)
when is_integer(Port) andalso is_integer(Timeout) ->
- do_block(Addr,Port,non_disturbing,Timeout);
-block(Addr,Port,disturbing,Timeout) when is_integer(Port) andalso
- is_integer(Timeout) ->
- do_block(Addr,Port,disturbing,Timeout).
+ do_block(Addr, Port, disturbing, Timeout).
-do_block(Addr,Port,Mode) when is_integer(Port) andalso is_atom(Mode) ->
+do_block(Addr, Port, Mode) when is_integer(Port) andalso is_atom(Mode) ->
Name = make_name(Addr,Port),
case whereis(Name) of
Pid when is_pid(Pid) ->
@@ -298,7 +233,7 @@ do_block(Addr,Port,Mode) when is_integer(Port) andalso is_atom(Mode) ->
end.
-do_block(Addr,Port,Mode,Timeout)
+do_block(Addr, Port, Mode, Timeout)
when is_integer(Port) andalso is_atom(Mode) ->
Name = make_name(Addr,Port),
case whereis(Name) of
@@ -310,11 +245,8 @@ do_block(Addr,Port,Mode,Timeout)
%%% =========================================================
-%%% Function: unblock/0, unblock/1, unblock/2
-%%% unblock()
-%%% unblock(Port)
-%%% unblock(ConfigFile)
-%%% unblock(Addr,Port)
+%%% Function: unblock/2
+%%% unblock(Addr, Port)
%%%
%%% Description: This function is used to reverse a previous block
%%% operation on the HTTP server.
@@ -323,16 +255,6 @@ do_block(Addr,Port,Mode,Timeout)
%%% Addr -> {A,B,C,D} | string() | undefined
%%% ConfigFile -> string()
%%%
-unblock() -> unblock(undefined,8888).
-unblock(Port) when is_integer(Port) -> unblock(undefined,Port);
-
-unblock(ConfigFile) when is_list(ConfigFile) ->
- case get_addr_and_port(ConfigFile) of
- {ok,Addr,Port} ->
- unblock(Addr,Port);
- Error ->
- Error
- end.
unblock(Addr, Port) when is_integer(Port) ->
Name = make_name(Addr,Port),
@@ -521,80 +443,81 @@ do_reload_config(ConfigList, Mode) ->
%%%--------------------------------------------------------------
%%% Deprecated
%%%--------------------------------------------------------------
-start() ->
- start("/var/tmp/server_root/conf/8888.conf").
-start(ConfigFile) ->
- {ok, Pid} = inets:start(httpd, ConfigFile, stand_alone),
- unlink(Pid),
- {ok, Pid}.
+%% start() ->
+%% start("/var/tmp/server_root/conf/8888.conf").
-start_link() ->
- start("/var/tmp/server_root/conf/8888.conf").
+%% start(ConfigFile) ->
+%% {ok, Pid} = inets:start(httpd, ConfigFile, stand_alone),
+%% unlink(Pid),
+%% {ok, Pid}.
-start_link(ConfigFile) when is_list(ConfigFile) ->
- inets:start(httpd, ConfigFile, stand_alone).
+%% start_link() ->
+%% start("/var/tmp/server_root/conf/8888.conf").
-stop() ->
- stop(8888).
+%% start_link(ConfigFile) when is_list(ConfigFile) ->
+%% inets:start(httpd, ConfigFile, stand_alone).
-stop(Port) when is_integer(Port) ->
- stop(undefined, Port);
-stop(Pid) when is_pid(Pid) ->
- old_stop(Pid);
-stop(ConfigFile) when is_list(ConfigFile) ->
- old_stop(ConfigFile).
+%% stop() ->
+%% stop(8888).
-stop(Addr, Port) when is_integer(Port) ->
- old_stop(Addr, Port).
+%% stop(Port) when is_integer(Port) ->
+%% stop(undefined, Port);
+%% stop(Pid) when is_pid(Pid) ->
+%% old_stop(Pid);
+%% stop(ConfigFile) when is_list(ConfigFile) ->
+%% old_stop(ConfigFile).
-start_child() ->
- start_child("/var/tmp/server_root/conf/8888.conf").
+%% stop(Addr, Port) when is_integer(Port) ->
+%% old_stop(Addr, Port).
-start_child(ConfigFile) ->
- httpd_sup:start_child(ConfigFile).
+%% start_child() ->
+%% start_child("/var/tmp/server_root/conf/8888.conf").
-stop_child() ->
- stop_child(8888).
+%% start_child(ConfigFile) ->
+%% httpd_sup:start_child(ConfigFile).
-stop_child(Port) ->
- stop_child(undefined, Port).
+%% stop_child() ->
+%% stop_child(8888).
-stop_child(Addr, Port) when is_integer(Port) ->
- httpd_sup:stop_child(Addr, Port).
+%% stop_child(Port) ->
+%% stop_child(undefined, Port).
-restart() -> reload(undefined, 8888).
+%% stop_child(Addr, Port) when is_integer(Port) ->
+%% httpd_sup:stop_child(Addr, Port).
-restart(Port) when is_integer(Port) ->
- reload(undefined, Port).
-restart(Addr, Port) ->
- reload(Addr, Port).
+%% restart() -> reload(undefined, 8888).
-old_stop(Pid) when is_pid(Pid) ->
- do_stop(Pid);
-old_stop(ConfigFile) when is_list(ConfigFile) ->
- case get_addr_and_port(ConfigFile) of
- {ok, Addr, Port} ->
- old_stop(Addr, Port);
-
- Error ->
- Error
- end;
-old_stop(_StartArgs) ->
- ok.
+%% restart(Port) when is_integer(Port) ->
+%% reload(undefined, Port).
+%% restart(Addr, Port) ->
+%% reload(Addr, Port).
-old_stop(Addr, Port) when is_integer(Port) ->
- Name = old_make_name(Addr, Port),
- case whereis(Name) of
- Pid when is_pid(Pid) ->
- do_stop(Pid),
- ok;
- _ ->
- not_started
- end.
+%% old_stop(Pid) when is_pid(Pid) ->
+%% do_stop(Pid);
+%% old_stop(ConfigFile) when is_list(ConfigFile) ->
+%% case get_addr_and_port(ConfigFile) of
+%% {ok, Addr, Port} ->
+%% old_stop(Addr, Port);
+
+%% Error ->
+%% Error
+%% end;
+%% old_stop(_StartArgs) ->
+%% ok.
+
+%% old_stop(Addr, Port) when is_integer(Port) ->
+%% Name = old_make_name(Addr, Port),
+%% case whereis(Name) of
+%% Pid when is_pid(Pid) ->
+%% do_stop(Pid),
+%% ok;
+%% _ ->
+%% not_started
+%% end.
-do_stop(Pid) ->
- exit(Pid, shutdown).
+%% do_stop(Pid) ->
+%% exit(Pid, shutdown).
-old_make_name(Addr,Port) ->
- httpd_util:make_name("httpd_instance_sup",Addr,Port).
+%% old_make_name(Addr,Port) ->
+%% httpd_util:make_name("httpd_instance_sup",Addr,Port).
diff --git a/lib/inets/src/http_server/httpd_acceptor.erl b/lib/inets/src/http_server/httpd_acceptor.erl
index 568fd3c610..c261eff6b2 100644
--- a/lib/inets/src/http_server/httpd_acceptor.erl
+++ b/lib/inets/src/http_server/httpd_acceptor.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%%
@@ -138,9 +138,9 @@ acceptor_loop(Manager, SocketType, ListenSocket, ConfigDb, AcceptTimeout) ->
handle_error(Reason, ConfigDb),
?MODULE:acceptor_loop(Manager, SocketType, ListenSocket,
ConfigDb, AcceptTimeout);
- {'EXIT', Reason} ->
- ?hdri("accept exited", [{reason, Reason}]),
- handle_error({'EXIT', Reason}, ConfigDb),
+ {'EXIT', _Reason} = EXIT ->
+ ?hdri("accept exited", [{reason, _Reason}]),
+ handle_error(EXIT, ConfigDb),
?MODULE:acceptor_loop(Manager, SocketType, ListenSocket,
ConfigDb, AcceptTimeout)
end.
diff --git a/lib/inets/src/http_server/httpd_cgi.erl b/lib/inets/src/http_server/httpd_cgi.erl
index 0532d7d100..c06a06aad3 100644
--- a/lib/inets/src/http_server/httpd_cgi.erl
+++ b/lib/inets/src/http_server/httpd_cgi.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
%%
@@ -21,7 +21,8 @@
-export([parse_headers/1, handle_headers/1]).
--include("inets_internal.hrl").
+-include_lib("inets/src/inets_app/inets_internal.hrl").
+
%%%=========================================================================
%%% Internal application API
diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl
index 5ca2e47eb5..8438c4037e 100644
--- a/lib/inets/src/http_server/httpd_conf.erl
+++ b/lib/inets/src/http_server/httpd_conf.erl
@@ -25,13 +25,15 @@
%% Application internal API
-export([load/1, load/2, load_mime_types/1, store/1, store/2,
- remove/1, remove_all/1, config/1, get_config/2, get_config/3,
- lookup/2, lookup/3, lookup/4,
- validate_properties/1]).
+ remove/1, remove_all/1, get_config/2, get_config/3,
+ lookup_socket_type/1,
+ lookup/2, lookup/3, lookup/4,
+ validate_properties/1]).
-define(VMODULE,"CONF").
-include("httpd.hrl").
-include("httpd_internal.hrl").
+-include_lib("inets/src/http_lib/http_internal.hrl").
%%%=========================================================================
@@ -216,9 +218,12 @@ load("ServerName " ++ ServerName, []) ->
{ok,[],{server_name,clean(ServerName)}};
load("SocketType " ++ SocketType, []) ->
- case check_enum(clean(SocketType),["ssl","ip_comm"]) of
+ %% ssl is the same as HTTP_DEFAULT_SSL_KIND
+ %% ossl is ssl based on OpenSSL (the "old" ssl)
+ %% essl is the pure Erlang-based ssl (the "new" ssl)
+ case check_enum(clean(SocketType), ["ssl", "ossl", "essl", "ip_comm"]) of
{ok, ValidSocketType} ->
- {ok, [], {socket_type,ValidSocketType}};
+ {ok, [], {socket_type, ValidSocketType}};
{error,_} ->
{error, ?NICE(clean(SocketType) ++ " is an invalid SocketType")}
end;
@@ -226,7 +231,7 @@ load("SocketType " ++ SocketType, []) ->
load("Port " ++ Port, []) ->
case make_integer(Port) of
{ok, Integer} ->
- {ok, [], {port,Integer}};
+ {ok, [], {port, Integer}};
{error, _} ->
{error, ?NICE(clean(Port)++" is an invalid Port")}
end;
@@ -534,7 +539,10 @@ validate_config_params([{server_name, Value} | _]) ->
throw({server_name, Value});
validate_config_params([{socket_type, Value} | Rest])
- when (Value =:= ip_comm) orelse (Value =:= ssl) ->
+ when (Value =:= ip_comm) orelse
+ (Value =:= ssl) orelse
+ (Value =:= ossl) orelse
+ (Value =:= essl) ->
validate_config_params(Rest);
validate_config_params([{socket_type, Value} | _]) ->
throw({socket_type, Value});
@@ -695,6 +703,8 @@ store(ConfigList0) ->
ConfigList)
catch
throw:Error ->
+ ?hdri("store - config parameter validation failed",
+ [{error, Error}]),
{error, {invalid_option, Error}}
end.
@@ -741,27 +751,27 @@ remove(ConfigDB) ->
ets:delete(ConfigDB),
ok.
-config(ConfigDB) ->
- case httpd_util:lookup(ConfigDB, socket_type,ip_comm) of
- ssl ->
- case ssl_certificate_file(ConfigDB) of
- undefined ->
- {error,
- "Directive SSLCertificateFile "
- "not found in the config file"};
- SSLCertificateFile ->
- {ssl,
- SSLCertificateFile++
- ssl_certificate_key_file(ConfigDB)++
- ssl_verify_client(ConfigDB)++
- ssl_ciphers(ConfigDB)++
- ssl_password(ConfigDB)++
- ssl_verify_depth(ConfigDB)++
- ssl_ca_certificate_file(ConfigDB)}
- end;
- ip_comm ->
- ip_comm
- end.
+%% config(ConfigDB) ->
+%% case httpd_util:lookup(ConfigDB, socket_type, ip_comm) of
+%% ssl ->
+%% case ssl_certificate_file(ConfigDB) of
+%% undefined ->
+%% {error,
+%% "Directive SSLCertificateFile "
+%% "not found in the config file"};
+%% SSLCertificateFile ->
+%% {ssl,
+%% SSLCertificateFile++
+%% ssl_certificate_key_file(ConfigDB)++
+%% ssl_verify_client(ConfigDB)++
+%% ssl_ciphers(ConfigDB)++
+%% ssl_password(ConfigDB)++
+%% ssl_verify_depth(ConfigDB)++
+%% ssl_ca_certificate_file(ConfigDB)}
+%% end;
+%% ip_comm ->
+%% ip_comm
+%% end.
get_config(Address, Port) ->
@@ -797,6 +807,38 @@ table(Address, Port) ->
httpd_util:make_name("httpd_conf", Address, Port).
+lookup_socket_type(ConfigDB) ->
+ case httpd_util:lookup(ConfigDB, socket_type, ip_comm) of
+ ip_comm ->
+ ip_comm;
+ SSL when (SSL =:= ssl) orelse (SSL =:= ossl) orelse (SSL =:= essl) ->
+ SSLTag =
+ if
+ (SSL =:= ssl) ->
+ ?HTTP_DEFAULT_SSL_KIND;
+ true ->
+ SSL
+ end,
+ case ssl_certificate_file(ConfigDB) of
+ undefined ->
+ Reason = "Directive SSLCertificateFile "
+ "not found in the config file",
+ throw({error, Reason});
+ SSLCertificateFile ->
+ {SSLTag, SSLCertificateFile ++ ssl_config(ConfigDB)}
+ end
+ end.
+
+ssl_config(ConfigDB) ->
+ ssl_certificate_key_file(ConfigDB) ++
+ ssl_verify_client(ConfigDB) ++
+ ssl_ciphers(ConfigDB) ++
+ ssl_password(ConfigDB) ++
+ ssl_verify_depth(ConfigDB) ++
+ ssl_ca_certificate_file(ConfigDB).
+
+
+
%%%========================================================================
%%% Internal functions
%%%========================================================================
diff --git a/lib/inets/src/http_server/httpd_esi.erl b/lib/inets/src/http_server/httpd_esi.erl
index b1a75fda52..026ec9a5fe 100644
--- a/lib/inets/src/http_server/httpd_esi.erl
+++ b/lib/inets/src/http_server/httpd_esi.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
%%
@@ -21,7 +21,8 @@
-export([parse_headers/1, handle_headers/1]).
--include("inets_internal.hrl").
+-include_lib("inets/src/inets_app/inets_internal.hrl").
+
%%%=========================================================================
%%% Internal application API
diff --git a/lib/inets/src/http_server/httpd_instance_sup.erl b/lib/inets/src/http_server/httpd_instance_sup.erl
index 0aaeb838c2..baa60d318c 100644
--- a/lib/inets/src/http_server/httpd_instance_sup.erl
+++ b/lib/inets/src/http_server/httpd_instance_sup.erl
@@ -97,14 +97,16 @@ start_link(ConfigFile, AcceptTimeout, ListenInfo, Debug) ->
%%%=========================================================================
%%% Supervisor callback
%%%=========================================================================
-init([ConfigFile, ConfigList, AcceptTimeout, _Debug, Address, Port]) ->
+init([ConfigFile, ConfigList, AcceptTimeout, Debug, Address, Port]) ->
+ httpd_util:enable_debug(Debug),
Flags = {one_for_one, 0, 1},
Children = [sup_spec(httpd_acceptor_sup, Address, Port),
sup_spec(httpd_misc_sup, Address, Port),
worker_spec(httpd_manager, Address, Port,
ConfigFile, ConfigList,AcceptTimeout)],
{ok, {Flags, Children}};
-init([ConfigFile, ConfigList, AcceptTimeout, _Debug, Address, Port, ListenInfo]) ->
+init([ConfigFile, ConfigList, AcceptTimeout, Debug, Address, Port, ListenInfo]) ->
+ httpd_util:enable_debug(Debug),
Flags = {one_for_one, 0, 1},
Children = [sup_spec(httpd_acceptor_sup, Address, Port),
sup_spec(httpd_misc_sup, Address, Port),
diff --git a/lib/inets/src/http_server/httpd_internal.hrl b/lib/inets/src/http_server/httpd_internal.hrl
index 7795ab6c18..38b0ddefd3 100644
--- a/lib/inets/src/http_server/httpd_internal.hrl
+++ b/lib/inets/src/http_server/httpd_internal.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%
@@ -21,7 +21,8 @@
-ifndef(httpd_internal_hrl).
-define(httpd_internal_hrl, true).
--include("inets_internal.hrl").
+-include_lib("inets/src/inets_app/inets_internal.hrl").
+
-define(SERVICE, httpd).
-define(hdri(Label, Content), ?report_important(Label, ?SERVICE, Content)).
-define(hdrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)).
diff --git a/lib/inets/src/http_server/httpd_manager.erl b/lib/inets/src/http_server/httpd_manager.erl
index f2e8763907..b44bc77c41 100644
--- a/lib/inets/src/http_server/httpd_manager.erl
+++ b/lib/inets/src/http_server/httpd_manager.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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%
%%
%%
@@ -238,24 +238,25 @@ init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port]) ->
case (catch do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port)) of
{error, Reason} ->
String = lists:flatten(
- io_lib:format("Failed initiating "
- "web server: ~n~p~n~p~n",
- [ConfigFile,Reason])),
+ io_lib:format("Failed initiating web server: "
+ "~n~p"
+ "~n~p"
+ "~n", [ConfigFile, Reason])),
error_logger:error_report(String),
{stop, {error, Reason}};
{ok, State} ->
{ok, State}
end;
-init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port,
- ListenInfo]) ->
+init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo]) ->
process_flag(trap_exit, true),
case (catch do_init(ConfigFile, ConfigList, AcceptTimeout,
Addr, Port, ListenInfo)) of
{error, Reason} ->
String = lists:flatten(
- io_lib:format("Failed initiating "
- "web server: ~n~p~n~p~n",
- [ConfigFile,Reason])),
+ io_lib:format("Failed initiating web server: "
+ "~n~p"
+ "~n~p"
+ "~n", [ConfigFile, Reason])),
error_logger:error_report(String),
{stop, {error, Reason}};
{ok, State} ->
@@ -264,13 +265,14 @@ init([ConfigFile, ConfigList, AcceptTimeout, Addr, Port,
do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port) ->
NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile),
- ConfigDB = do_initial_store(ConfigList),
- SocketType = httpd_conf:config(ConfigDB),
+ ConfigDB = do_initial_store(ConfigList),
+ SocketType = httpd_conf:lookup_socket_type(ConfigDB),
case httpd_acceptor_sup:start_acceptor(SocketType, Addr,
Port, ConfigDB, AcceptTimeout) of
{ok, _Pid} ->
- Status = [{max_conn,0}, {last_heavy_load,never},
- {last_connection,never}],
+ Status = [{max_conn, 0},
+ {last_heavy_load, never},
+ {last_connection, never}],
State = #state{socket_type = SocketType,
config_file = NewConfigFile,
config_db = ConfigDB,
@@ -284,7 +286,7 @@ do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port) ->
do_init(ConfigFile, ConfigList, AcceptTimeout, Addr, Port, ListenInfo) ->
NewConfigFile = proplists:get_value(file, ConfigList, ConfigFile),
ConfigDB = do_initial_store(ConfigList),
- SocketType = httpd_conf:config(ConfigDB),
+ SocketType = httpd_conf:lookup_socket_type(ConfigDB),
case httpd_acceptor_sup:start_acceptor(SocketType, Addr,
Port, ConfigDB,
AcceptTimeout, ListenInfo) of
diff --git a/lib/inets/src/http_server/httpd_request.erl b/lib/inets/src/http_server/httpd_request.erl
index 8eee08e766..883acbf585 100644
--- a/lib/inets/src/http_server/httpd_request.erl
+++ b/lib/inets/src/http_server/httpd_request.erl
@@ -19,22 +19,35 @@
-module(httpd_request).
--include("http_internal.hrl").
+-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpd.hrl").
+-include("httpd_internal.hrl").
--export([parse/1, whole_body/2, validate/3, update_mod_data/5,
- body_data/2]).
+-export([
+ parse/1,
+ whole_body/2,
+ validate/3,
+ update_mod_data/5,
+ body_data/2
+ ]).
%% Callback API - used for example if the header/body is received a
%% little at a time on a socket.
--export([parse_method/1, parse_uri/1, parse_version/1, parse_headers/1,
- whole_body/1]).
+-export([
+ parse_method/1, parse_uri/1, parse_version/1, parse_headers/1,
+ whole_body/1
+ ]).
+
%%%=========================================================================
%%% Internal application API
%%%=========================================================================
parse([Bin, MaxSizes]) ->
- parse_method(Bin, [], MaxSizes, []).
+ ?hdrt("parse", [{bin, Bin}, {max_sizes, MaxSizes}]),
+ parse_method(Bin, [], MaxSizes, []);
+parse(Unknown) ->
+ ?hdrt("parse", [{unknown, Unknown}]),
+ exit({bad_args, Unknown}).
%% Functions that may be returned during the decoding process
%% if the input data is incompleate.
@@ -119,30 +132,65 @@ update_mod_data(ModData, Method, RequestURI, HTTPVersion, Headers)->
%%% Internal functions
%%%========================================================================
parse_method(<<>>, Method, MaxSizes, Result) ->
+ ?hdrt("parse_method - empty bin",
+ [{method, Method}, {max_sizes, MaxSizes}, {result, Result}]),
{?MODULE, parse_method, [Method, MaxSizes, Result]};
parse_method(<<?SP, Rest/binary>>, Method, MaxSizes, Result) ->
+ ?hdrt("parse_method - SP begin",
+ [{rest, Rest},
+ {method, Method},
+ {max_sizes, MaxSizes},
+ {result, Result}]),
parse_uri(Rest, [], 0, MaxSizes,
[string:strip(lists:reverse(Method)) | Result]);
parse_method(<<Octet, Rest/binary>>, Method, MaxSizes, Result) ->
+ ?hdrt("parse_method",
+ [{octet, Octet},
+ {rest, Rest},
+ {method, Method},
+ {max_sizes, MaxSizes},
+ {result, Result}]),
parse_method(Rest, [Octet | Method], MaxSizes, Result).
-parse_uri(_, _, CurrSize, {MaxURI, _}, _) when CurrSize > MaxURI,
- MaxURI =/= nolimit ->
+parse_uri(_, _, CurrSize, {MaxURI, _}, _)
+ when (CurrSize > MaxURI) andalso (MaxURI =/= nolimit) ->
+ ?hdrt("parse_uri",
+ [{current_size, CurrSize},
+ {max_uri, MaxURI}]),
%% We do not know the version of the client as it comes after the
%% uri send the lowest version in the response so that the client
%% will be able to handle it.
HttpVersion = "HTTP/0.9",
{error, {uri_too_long, MaxURI}, HttpVersion};
parse_uri(<<>>, URI, CurrSize, MaxSizes, Result) ->
+ ?hdrt("parse_uri - empty bin",
+ [{uri, URI},
+ {current_size, CurrSize},
+ {max_sz, MaxSizes},
+ {result, Result}]),
{?MODULE, parse_uri, [URI, CurrSize, MaxSizes, Result]};
parse_uri(<<?SP, Rest/binary>>, URI, _, MaxSizes, Result) ->
+ ?hdrt("parse_uri - SP begin",
+ [{uri, URI},
+ {max_sz, MaxSizes},
+ {result, Result}]),
parse_version(Rest, [], MaxSizes,
[string:strip(lists:reverse(URI)) | Result]);
%% Can happen if it is a simple HTTP/0.9 request e.i "GET /\r\n\r\n"
-parse_uri(<<?CR, _Rest/binary>> = Data, URI, _,MaxSizes, Result) ->
+parse_uri(<<?CR, _Rest/binary>> = Data, URI, _, MaxSizes, Result) ->
+ ?hdrt("parse_uri - CR begin",
+ [{uri, URI},
+ {max_sz, MaxSizes},
+ {result, Result}]),
parse_version(Data, [], MaxSizes,
[string:strip(lists:reverse(URI)) | Result]);
parse_uri(<<Octet, Rest/binary>>, URI, CurrSize, MaxSizes, Result) ->
+ ?hdrt("parse_uri",
+ [{octet, Octet},
+ {uri, URI},
+ {curr_sz, CurrSize},
+ {max_sz, MaxSizes},
+ {result, Result}]),
parse_uri(Rest, [Octet | URI], CurrSize + 1, MaxSizes, Result).
parse_version(<<>>, Version, MaxSizes, Result) ->
diff --git a/lib/inets/src/http_server/httpd_request_handler.erl b/lib/inets/src/http_server/httpd_request_handler.erl
index fa832cba3f..a9db6e2058 100644
--- a/lib/inets/src/http_server/httpd_request_handler.erl
+++ b/lib/inets/src/http_server/httpd_request_handler.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
%%
@@ -101,11 +101,13 @@ init([Manager, ConfigDB, AcceptTimeout]) ->
Then = erlang:now(),
+ ?hdrd("negotiate", []),
case http_transport:negotiate(SocketType, Socket, TimeOut) of
{error, Error} ->
+ ?hdrd("negotiation failed", [{error, Error}]),
exit(Error); %% Can be 'normal'.
ok ->
- ?hdrt("negotiated", []),
+ ?hdrt("negotiation successfull", []),
NewTimeout = TimeOut - timer:now_diff(now(),Then) div 1000,
continue_init(Manager, ConfigDB, SocketType, Socket, NewTimeout)
end.
@@ -121,12 +123,9 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) ->
socket = Socket,
init_data = InitData},
- MaxHeaderSize = httpd_util:lookup(ConfigDB, max_header_size,
- ?HTTP_MAX_HEADER_SIZE),
- MaxURISize = httpd_util:lookup(ConfigDB, max_uri_size,
- ?HTTP_MAX_URI_SIZE),
- NrOfRequest = httpd_util:lookup(ConfigDB,
- max_keep_alive_request, infinity),
+ MaxHeaderSize = max_header_size(ConfigDB),
+ MaxURISize = max_uri_size(ConfigDB),
+ NrOfRequest = max_keep_alive_request(ConfigDB),
{_, Status} = httpd_manager:new_connection(Manager),
@@ -142,9 +141,10 @@ continue_init(Manager, ConfigDB, SocketType, Socket, TimeOut) ->
?hdrt("activate request timeout", []),
NewState = activate_request_timeout(State),
- ?hdrt("update socket options", []),
- http_transport:setopts(SocketType, Socket, [binary,{packet, 0},
- {active, once}]),
+ ?hdrt("set socket options (binary, packet & active)", []),
+ http_transport:setopts(SocketType, Socket,
+ [binary, {packet, 0}, {active, once}]),
+
?hdrt("init done", []),
gen_server:enter_loop(?MODULE, [], NewState).
@@ -180,21 +180,29 @@ handle_cast(Msg, State) ->
%% {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
-handle_info({Proto, Socket, Data}, State =
+handle_info({Proto, Socket, Data},
#state{mfa = {Module, Function, Args} = MFA,
mod = #mod{socket_type = SockType,
socket = Socket} = ModData} = State)
when (((Proto =:= tcp) orelse
(Proto =:= ssl) orelse
(Proto =:= dummy)) andalso is_binary(Data)) ->
+
?hdrd("received data",
[{data, Data}, {proto, Proto},
{socket, Socket}, {socket_type, SockType}, {mfa, MFA}]),
- case Module:Function([Data | Args]) of
+
+%% case (catch Module:Function([Data | Args])) of
+ PROCESSED = (catch Module:Function([Data | Args])),
+
+ ?hdrt("data processed", [{processing_result, PROCESSED}]),
+
+ case PROCESSED of
{ok, Result} ->
?hdrd("data processed", [{result, Result}]),
NewState = cancel_request_timeout(State),
handle_http_msg(Result, NewState);
+
{error, {uri_too_long, MaxSize}, Version} ->
?hdrv("uri too long", [{max_size, MaxSize}, {version, Version}]),
NewModData = ModData#mod{http_version = Version},
@@ -205,7 +213,8 @@ handle_info({Proto, Socket, Data}, State =
{stop, normal, State#state{response_sent = true,
mod = NewModData}};
{error, {header_too_long, MaxSize}, Version} ->
- ?hdrv("header too long", [{max_size, MaxSize}, {version, Version}]),
+ ?hdrv("header too long",
+ [{max_size, MaxSize}, {version, Version}]),
NewModData = ModData#mod{http_version = Version},
httpd_response:send_status(NewModData, 413, "Header too long"),
Reason = io_lib:format("Header too long, max size is ~p~n",
@@ -263,14 +272,16 @@ terminate(Reason, #state{response_sent = false, mod = ModData} = State) ->
httpd_response:send_status(ModData, 500, none),
error_log(httpd_util:reason_phrase(500), ModData),
terminate(Reason, State#state{response_sent = true, mod = ModData});
-terminate(_, State) ->
+terminate(_Reason, State) ->
do_terminate(State).
do_terminate(#state{mod = ModData, manager = Manager} = State) ->
catch httpd_manager:done_connection(Manager),
cancel_request_timeout(State),
+ %% receive after 5000 -> ok end,
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket).
+
%%--------------------------------------------------------------------
%% code_change(OldVsn, State, Extra) -> {ok, NewState}
%%
@@ -279,6 +290,7 @@ do_terminate(#state{mod = ModData, manager = Manager} = State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
+
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -383,9 +395,8 @@ is_host_specified_if_required(_, _, _) ->
handle_body(#state{mod = #mod{config_db = ConfigDB}} = State) ->
?hdrt("handle body", []),
- MaxHeaderSize =
- httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE),
- MaxBodySize = httpd_util:lookup(ConfigDB, max_body_size, nolimit),
+ MaxHeaderSize = max_header_size(ConfigDB),
+ MaxBodySize = max_body_size(ConfigDB),
case handle_expect(State, MaxBodySize) of
ok ->
@@ -538,24 +549,23 @@ handle_response(#state{body = Body,
{stop, normal, State#state{response_sent = true}}.
handle_next_request(#state{mod = #mod{connection = true} = ModData,
- max_keep_alive_request = Max} = State, Data) ->
+ max_keep_alive_request = Max} = State, Data) ->
?hdrt("handle next request", [{max, Max}]),
+
NewModData = #mod{socket_type = ModData#mod.socket_type,
- socket = ModData#mod.socket,
- config_db = ModData#mod.config_db,
- init_data = ModData#mod.init_data},
- MaxHeaderSize =
- httpd_util:lookup(ModData#mod.config_db,
- max_header_size, ?HTTP_MAX_HEADER_SIZE),
- MaxURISize = httpd_util:lookup(ModData#mod.config_db, max_uri_size,
- ?HTTP_MAX_URI_SIZE),
- TmpState = State#state{mod = NewModData,
- mfa = {httpd_request, parse, [{MaxURISize,
- MaxHeaderSize}]},
+ socket = ModData#mod.socket,
+ config_db = ModData#mod.config_db,
+ init_data = ModData#mod.init_data},
+ MaxHeaderSize = max_header_size(ModData#mod.config_db),
+ MaxURISize = max_uri_size(ModData#mod.config_db),
+
+ MFA = {httpd_request, parse, [{MaxURISize, MaxHeaderSize}]},
+ TmpState = State#state{mod = NewModData,
+ mfa = MFA,
max_keep_alive_request = decrease(Max),
- headers = undefined,
- body = undefined,
- response_sent = false},
+ headers = undefined,
+ body = undefined,
+ response_sent = false},
NewState = activate_request_timeout(TmpState),
@@ -596,7 +606,7 @@ decrease(N) ->
error_log(ReasonString, Info) ->
Error = lists:flatten(
- io_lib:format("Error reading request:~s",[ReasonString])),
+ io_lib:format("Error reading request: ~s", [ReasonString])),
error_log(mod_log, Info, Error),
error_log(mod_disk_log, Info, Error).
@@ -609,3 +619,21 @@ error_log(Mod, #mod{config_db = ConfigDB} = Info, String) ->
_ ->
ok
end.
+
+
+%%--------------------------------------------------------------------
+%% Config access wrapper functions
+%%--------------------------------------------------------------------
+
+max_header_size(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, max_header_size, ?HTTP_MAX_HEADER_SIZE).
+
+max_uri_size(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, max_uri_size, ?HTTP_MAX_URI_SIZE).
+
+max_body_size(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, max_body_size, nolimit).
+
+max_keep_alive_request(ConfigDB) ->
+ httpd_util:lookup(ConfigDB, max_keep_alive_request, infinity).
+
diff --git a/lib/inets/src/http_server/httpd_sup.erl b/lib/inets/src/http_server/httpd_sup.erl
index 3399f78b53..1507c6852a 100644
--- a/lib/inets/src/http_server/httpd_sup.erl
+++ b/lib/inets/src/http_server/httpd_sup.erl
@@ -185,14 +185,14 @@ httpd_child_spec(ConfigFile, AcceptTimeout, Debug) ->
httpd_child_spec(Config, AcceptTimeout, Debug, Addr, 0) ->
case start_listen(Addr, 0, Config) of
{Pid, {NewPort, NewConfig, ListenSocket}} ->
- Name = {httpd_instance_sup, Addr, NewPort},
+ Name = {httpd_instance_sup, Addr, NewPort},
StartFunc = {httpd_instance_sup, start_link,
[NewConfig, AcceptTimeout,
{Pid, ListenSocket}, Debug]},
- Restart = permanent,
- Shutdown = infinity,
- Modules = [httpd_instance_sup],
- Type = supervisor,
+ Restart = permanent,
+ Shutdown = infinity,
+ Modules = [httpd_instance_sup],
+ Type = supervisor,
{Name, StartFunc, Restart, Shutdown, Type, Modules};
{Pid, {error, Reason}} ->
exit(Pid, normal),
diff --git a/lib/inets/src/http_server/httpd_util.erl b/lib/inets/src/http_server/httpd_util.erl
index b59fd861dc..cfad79638f 100644
--- a/lib/inets/src/http_server/httpd_util.erl
+++ b/lib/inets/src/http_server/httpd_util.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
%%
@@ -755,23 +755,18 @@ do_enable_debug([{Level,Modules}|Rest])
when is_atom(Level) andalso is_list(Modules) ->
case Level of
all_functions ->
- io:format("Tracing on all functions set on modules: ~p~n",
- [Modules]),
lists:foreach(
- fun(X)->
+ fun(X) ->
dbg:tpl(X, [{'_', [], [{return_trace}]}])
end, Modules);
exported_functions ->
- io:format("Tracing on exported functions set on "
- "modules: ~p~n",[Modules]),
lists:foreach(
- fun(X)->
+ fun(X) ->
dbg:tp(X, [{'_', [], [{return_trace}]}])
end, Modules);
disable ->
- io:format("Tracing disabled on modules: ~p~n", [Modules]),
lists:foreach(
- fun(X)->
+ fun(X) ->
dbg:ctp(X)
end, Modules);
_ ->
diff --git a/lib/inets/src/http_server/mod_alias.erl b/lib/inets/src/http_server/mod_alias.erl
index ec0a12242f..9c5a8cc1c6 100644
--- a/lib/inets/src/http_server/mod_alias.erl
+++ b/lib/inets/src/http_server/mod_alias.erl
@@ -103,6 +103,19 @@ real_name(ConfigDB, RequestURI, []) ->
httpd_util:split_path(default_index(ConfigDB, RealName)),
{ShortPath, Path, AfterPath};
+real_name(ConfigDB, RequestURI, [{MP,Replacement}|Rest])
+ when element(1, MP) =:= re_pattern ->
+ case re:run(RequestURI, MP, [{capture,[]}]) of
+ match ->
+ NewURI = re:replace(RequestURI, MP, Replacement, [{return,list}]),
+ {ShortPath,_} = httpd_util:split_path(NewURI),
+ {Path,AfterPath} =
+ httpd_util:split_path(default_index(ConfigDB, NewURI)),
+ {ShortPath, Path, AfterPath};
+ nomatch ->
+ real_name(ConfigDB, RequestURI, Rest)
+ end;
+
real_name(ConfigDB, RequestURI, [{FakeName,RealName}|Rest]) ->
case inets_regexp:match(RequestURI, "^" ++ FakeName) of
{match, _, _} ->
@@ -120,6 +133,18 @@ real_name(ConfigDB, RequestURI, [{FakeName,RealName}|Rest]) ->
real_script_name(_ConfigDB, _RequestURI, []) ->
not_a_script;
+
+real_script_name(ConfigDB, RequestURI, [{MP,Replacement} | Rest])
+ when element(1, MP) =:= re_pattern ->
+ case re:run(RequestURI, MP, [{capture,[]}]) of
+ match ->
+ ActualName =
+ re:replace(RequestURI, MP, Replacement, [{return,list}]),
+ httpd_util:split_script_path(default_index(ConfigDB, ActualName));
+ nomatch ->
+ real_script_name(ConfigDB, RequestURI, Rest)
+ end;
+
real_script_name(ConfigDB, RequestURI, [{FakeName,RealName} | Rest]) ->
case inets_regexp:match(RequestURI, "^" ++ FakeName) of
{match,_,_} ->
@@ -180,6 +205,8 @@ load("Alias " ++ Alias, []) ->
{ok, _} ->
{error,?NICE(httpd_conf:clean(Alias)++" is an invalid Alias")}
end;
+load("ReWrite " ++ Rule, Acc) ->
+ load_re_write(Rule, Acc, "ReWrite", re_write);
load("ScriptAlias " ++ ScriptAlias, []) ->
case inets_regexp:split(ScriptAlias, " ") of
{ok, [FakeName, RealName]} ->
@@ -189,6 +216,24 @@ load("ScriptAlias " ++ ScriptAlias, []) ->
{ok, _} ->
{error, ?NICE(httpd_conf:clean(ScriptAlias)++
" is an invalid ScriptAlias")}
+ end;
+load("ScriptReWrite " ++ Rule, Acc) ->
+ load_re_write(Rule, Acc, "ScriptReWrite", script_re_write).
+
+load_re_write(Rule0, Acc, Type, Tag) ->
+ case lists:dropwhile(
+ fun ($\s) -> true; ($\t) -> true; (_) -> false end,
+ Rule0) of
+ "" ->
+ {error, ?NICE(httpd_conf:clean(Rule0)++" is an invalid "++Type)};
+ Rule ->
+ case string:chr(Rule, $\s) of
+ 0 ->
+ {ok, Acc, {Tag, {Rule, ""}}};
+ N ->
+ {Re, [_|Replacement]} = lists:split(N-1, Rule),
+ {ok, Acc, {Tag, {Re, Replacement}}}
+ end
end.
store({directory_index, Value} = Conf, _) when is_list(Value) ->
@@ -200,16 +245,36 @@ store({directory_index, Value} = Conf, _) when is_list(Value) ->
end;
store({directory_index, Value}, _) ->
{error, {wrong_type, {directory_index, Value}}};
-store({alias, {Fake, Real}} = Conf, _)
- when is_list(Fake) andalso is_list(Real) ->
+store({alias, {Fake, Real}} = Conf, _)
+ when is_list(Fake), is_list(Real) ->
{ok, Conf};
store({alias, Value}, _) ->
{error, {wrong_type, {alias, Value}}};
+store({re_write, {Re, Replacement}} = Conf, _)
+ when is_list(Re), is_list(Replacement) ->
+ case re:compile(Re) of
+ {ok, MP} ->
+ {ok, {alias, {MP, Replacement}}};
+ {error,_} ->
+ {error, {re_compile, Conf}}
+ end;
+store({re_write, _} = Conf, _) ->
+ {error, {wrong_type, Conf}};
store({script_alias, {Fake, Real}} = Conf, _)
- when is_list(Fake) andalso is_list(Real) ->
+ when is_list(Fake), is_list(Real) ->
{ok, Conf};
store({script_alias, Value}, _) ->
- {error, {wrong_type, {script_alias, Value}}}.
+ {error, {wrong_type, {script_alias, Value}}};
+store({script_re_write, {Re, Replacement}} = Conf, _)
+ when is_list(Re), is_list(Replacement) ->
+ case re:compile(Re) of
+ {ok, MP} ->
+ {ok, {script_alias, {MP, Replacement}}};
+ {error,_} ->
+ {error, {re_compile, Conf}}
+ end;
+store({script_re_write, _} = Conf, _) ->
+ {error, {wrong_type, Conf}}.
is_directory_index_list([]) ->
true;
diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl
index cb33544540..f7877aa9e2 100644
--- a/lib/inets/src/http_server/mod_esi.erl
+++ b/lib/inets/src/http_server/mod_esi.erl
@@ -29,6 +29,7 @@
-export([do/1, load/2, store/2]).
-include("httpd.hrl").
+-include("httpd_internal.hrl").
-define(VMODULE,"ESI").
-define(DEFAULT_ERL_TIMEOUT,15000).
@@ -37,6 +38,7 @@
%%%=========================================================================
%%% API
%%%=========================================================================
+
%%--------------------------------------------------------------------------
%% deliver(SessionID, Data) -> ok | {error, bad_sessionID}
%% SessionID = pid()
@@ -48,7 +50,7 @@
%% request handling process so it can forward it to the client.
%%-------------------------------------------------------------------------
deliver(SessionID, Data) when is_pid(SessionID) ->
- SessionID ! {ok, Data},
+ SessionID ! {esi_data, Data},
ok;
deliver(_SessionID, _Data) ->
{error, bad_sessionID}.
@@ -65,6 +67,7 @@ deliver(_SessionID, _Data) ->
%% Description: See httpd(3) ESWAPI CALLBACK FUNCTIONS
%%-------------------------------------------------------------------------
do(ModData) ->
+ ?hdrt("do", []),
case proplists:get_value(status, ModData#mod.data) of
{_StatusCode, _PhraseArgs, _Reason} ->
{proceed, ModData#mod.data};
@@ -184,6 +187,7 @@ store({erl_script_nocache, Value}, _) ->
%%% Internal functions
%%%========================================================================
generate_response(ModData) ->
+ ?hdrt("generate response", []),
case scheme(ModData#mod.request_uri, ModData#mod.config_db) of
{eval, ESIBody, Modules} ->
eval(ModData, ESIBody, Modules);
@@ -235,6 +239,7 @@ alias_match_str(Alias, eval_script_alias) ->
erl(#mod{method = Method} = ModData, ESIBody, Modules)
when (Method =:= "GET") orelse (Method =:= "HEAD") ->
+ ?hdrt("erl", [{method, Method}]),
case httpd_util:split(ESIBody,":|%3A|/",2) of
{ok, [ModuleName, FuncAndInput]} ->
case httpd_util:split(FuncAndInput,"[\?/]",2) of
@@ -260,6 +265,7 @@ erl(#mod{request_uri = ReqUri,
method = "PUT",
http_version = Version,
data = Data}, _ESIBody, _Modules) ->
+ ?hdrt("erl", [{method, put}]),
{proceed, [{status,{501,{"PUT", ReqUri, Version},
?NICE("Erl mechanism doesn't support method PUT")}}|
Data]};
@@ -268,12 +274,14 @@ erl(#mod{request_uri = ReqUri,
method = "DELETE",
http_version = Version,
data = Data}, _ESIBody, _Modules) ->
+ ?hdrt("erl", [{method, delete}]),
{proceed,[{status,{501,{"DELETE", ReqUri, Version},
?NICE("Erl mechanism doesn't support method DELETE")}}|
Data]};
erl(#mod{method = "POST",
entity_body = Body} = ModData, ESIBody, Modules) ->
+ ?hdrt("erl", [{method, post}]),
case httpd_util:split(ESIBody,":|%3A|/",2) of
{ok,[ModuleName, Function]} ->
generate_webpage(ModData, ESIBody, Modules,
@@ -289,6 +297,7 @@ generate_webpage(ModData, ESIBody, [all], Module, FunctionName,
FunctionName, Input, ScriptElements);
generate_webpage(ModData, ESIBody, Modules, Module, FunctionName,
Input, ScriptElements) ->
+ ?hdrt("generate webpage", []),
Function = list_to_atom(FunctionName),
case lists:member(Module, Modules) of
true ->
@@ -309,8 +318,9 @@ generate_webpage(ModData, ESIBody, Modules, Module, FunctionName,
%% Old API that waits for the dymnamic webpage to be totally generated
%% before anythig is sent back to the client.
-erl_scheme_webpage_whole(Module, Function, Env, Input, ModData) ->
- case (catch Module:Function(Env, Input)) of
+erl_scheme_webpage_whole(Mod, Func, Env, Input, ModData) ->
+ ?hdrt("erl_scheme_webpage_whole", [{module, Mod}, {function, Func}]),
+ case (catch Mod:Func(Env, Input)) of
{'EXIT',{undef, _}} ->
{proceed, [{status, {404, ModData#mod.request_uri, "Not found"}}
| ModData#mod.data]};
@@ -347,6 +357,7 @@ erl_scheme_webpage_whole(Module, Function, Env, Input, ModData) ->
%% in small chunks at the time during generation.
erl_scheme_webpage_chunk(Mod, Func, Env, Input, ModData) ->
process_flag(trap_exit, true),
+ ?hdrt("erl_scheme_webpage_chunk", [{module, Mod}, {function, Func}]),
Self = self(),
%% Spawn worker that generates the webpage.
%% It would be nicer to use erlang:function_exported/3 but if the
@@ -372,9 +383,12 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid) ->
deliver_webpage_chunk(ModData, Pid, Timeout).
deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
+ ?hdrt("deliver_webpage_chunk", [{timeout, Timeout}]),
case receive_headers(Timeout) of
{error, Reason} ->
%% Happens when webpage generator callback/3 is undefined
+ ?hdrv("deliver_webpage_chunk - failed receiving headers",
+ [{reason, Reason}]),
{error, Reason};
{Headers, Body} ->
case httpd_esi:handle_headers(Headers) of
@@ -399,6 +413,7 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
IsDisableChunkedSend)
end;
timeout ->
+ ?hdrv("deliver_webpage_chunk - timeout", []),
send_headers(ModData, {504, "Timeout"},[{"connection", "close"}]),
httpd_socket:close(ModData#mod.socket_type, ModData#mod.socket),
process_flag(trap_exit,false),
@@ -407,11 +422,17 @@ deliver_webpage_chunk(#mod{config_db = Db} = ModData, Pid, Timeout) ->
receive_headers(Timeout) ->
receive
+ {esi_data, Chunk} ->
+ ?hdrt("receive_headers - received esi data (esi)", []),
+ httpd_esi:parse_headers(lists:flatten(Chunk));
{ok, Chunk} ->
+ ?hdrt("receive_headers - received esi data (ok)", []),
httpd_esi:parse_headers(lists:flatten(Chunk));
{'EXIT', Pid, erl_scheme_webpage_chunk_undefined} when is_pid(Pid) ->
+ ?hdrd("receive_headers - exit:chunk-undef", []),
{error, erl_scheme_webpage_chunk_undefined};
{'EXIT', Pid, Reason} when is_pid(Pid) ->
+ ?hdrv("receive_headers - exit", [{reason, Reason}]),
exit({mod_esi_linked_process_died, Pid, Reason})
after Timeout ->
timeout
@@ -427,19 +448,29 @@ handle_body(_, #mod{method = "HEAD"} = ModData, _, _, Size, _) ->
{proceed, [{response, {already_sent, 200, Size}} | ModData#mod.data]};
handle_body(Pid, ModData, Body, Timeout, Size, IsDisableChunkedSend) ->
+ ?hdrt("handle_body - send chunk", [{timeout, Timeout}, {size, Size}]),
httpd_response:send_chunk(ModData, Body, IsDisableChunkedSend),
receive
+ {esi_data, Data} ->
+ ?hdrt("handle_body - received data (esi)", []),
+ handle_body(Pid, ModData, Data, Timeout, Size + length(Data),
+ IsDisableChunkedSend);
{ok, Data} ->
+ ?hdrt("handle_body - received data (ok)", []),
handle_body(Pid, ModData, Data, Timeout, Size + length(Data),
IsDisableChunkedSend);
{'EXIT', Pid, normal} when is_pid(Pid) ->
+ ?hdrt("handle_body - exit:normal", []),
httpd_response:send_final_chunk(ModData, IsDisableChunkedSend),
{proceed, [{response, {already_sent, 200, Size}} |
ModData#mod.data]};
{'EXIT', Pid, Reason} when is_pid(Pid) ->
+ ?hdrv("handle_body - exit", [{reason, Reason}]),
httpd_response:send_final_chunk(ModData, IsDisableChunkedSend),
exit({mod_esi_linked_process_died, Pid, Reason})
+
after Timeout ->
+ ?hdrv("handle_body - timeout", []),
process_flag(trap_exit,false),
httpd_response:send_final_chunk(ModData, IsDisableChunkedSend),
exit({mod_esi_linked_process_timeout, Pid})
@@ -473,6 +504,7 @@ eval(#mod{request_uri = ReqUri,
method = "PUT",
http_version = Version,
data = Data}, _ESIBody, _Modules) ->
+ ?hdrt("eval", [{method, put}]),
{proceed,[{status,{501,{"PUT", ReqUri, Version},
?NICE("Eval mechanism doesn't support method PUT")}}|
Data]};
@@ -481,6 +513,7 @@ eval(#mod{request_uri = ReqUri,
method = "DELETE",
http_version = Version,
data = Data}, _ESIBody, _Modules) ->
+ ?hdrt("eval", [{method, delete}]),
{proceed,[{status,{501,{"DELETE", ReqUri, Version},
?NICE("Eval mechanism doesn't support method DELETE")}}|
Data]};
@@ -489,12 +522,14 @@ eval(#mod{request_uri = ReqUri,
method = "POST",
http_version = Version,
data = Data}, _ESIBody, _Modules) ->
+ ?hdrt("eval", [{method, post}]),
{proceed,[{status,{501,{"POST", ReqUri, Version},
?NICE("Eval mechanism doesn't support method POST")}}|
Data]};
eval(#mod{method = Method} = ModData, ESIBody, Modules)
- when Method == "GET"; Method == "HEAD" ->
+ when (Method =:= "GET") orelse (Method =:= "HEAD") ->
+ ?hdrt("eval", [{method, Method}]),
case is_authorized(ESIBody, Modules) of
true ->
case generate_webpage(ESIBody) of
diff --git a/lib/inets/src/inets_app/Makefile b/lib/inets/src/inets_app/Makefile
index 33c9e34a3a..4632ff3b68 100644
--- a/lib/inets/src/inets_app/Makefile
+++ b/lib/inets/src/inets_app/Makefile
@@ -67,18 +67,15 @@ APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
# ----------------------------------------------------
-# INETS FLAGS
-# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
-
-
-# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
-ERL_COMPILE_FLAGS += $(INETS_FLAGS) \
- +'{parse_transform,sys_pre_attributes}' \
- +'{attribute,insert,app_vsn,$(APP_VSN)}'
+include inets.mk
+
+ERL_COMPILE_FLAGS += \
+ $(INETS_FLAGS) \
+ $(INETS_ERL_COMPILE_FLAGS) \
+ -I../../include
# ----------------------------------------------------
@@ -112,7 +109,8 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
$(INSTALL_DIR) $(RELSYSDIR)/src
- $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/src/inets_app
+ $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/inets_app
$(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
diff --git a/lib/inets/src/inets_app/inets.app.src b/lib/inets/src/inets_app/inets.app.src
index 04f6365b98..cb036157a5 100644
--- a/lib/inets/src/inets_app/inets.app.src
+++ b/lib/inets/src/inets_app/inets.app.src
@@ -107,5 +107,6 @@
tftp_sup
]},
{registered,[inets_sup, httpc_manager]},
+ %% If the "new" ssl is used then 'crypto' must be started before inets.
{applications,[kernel,stdlib]},
{mod,{inets_app,[]}}]}.
diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src
index ba1dc8ccdb..64fe664006 100644
--- a/lib/inets/src/inets_app/inets.appup.src
+++ b/lib/inets/src/inets_app/inets.appup.src
@@ -18,10 +18,24 @@
{"%VSN%",
[
+ {"5.3.3",
+ [
+ {restart_application, inets}
+ ]
+ },
+ {"5.3.2",
+ [
+ {restart_application, inets}
+ ]
+ },
+ {"5.3.1",
+ [
+ {restart_application, inets}
+ ]
+ },
{"5.3",
[
- {update, httpc_handler, soft, soft_purge, soft_purge, [mod_esi]},
- {load_module, mod_esi, soft_purge, soft_purge, []}
+ {restart_application, inets}
]
},
{"5.2",
@@ -41,10 +55,24 @@
}
],
[
+ {"5.3.3",
+ [
+ {restart_application, inets}
+ ]
+ },
+ {"5.3.2",
+ [
+ {restart_application, inets}
+ ]
+ },
+ {"5.3.1",
+ [
+ {restart_application, inets}
+ ]
+ },
{"5.3",
[
- {update, httpc_handler, soft, soft_purge, soft_purge, [mod_esi]},
- {load_module, mod_esi, soft_purge, soft_purge, []}
+ {restart_application, inets}
]
},
{"5.2",
diff --git a/lib/inets/src/inets_app/inets.erl b/lib/inets/src/inets_app/inets.erl
index 7e3f862ee7..f1fa5fd997 100644
--- a/lib/inets/src/inets_app/inets.erl
+++ b/lib/inets/src/inets_app/inets.erl
@@ -533,7 +533,7 @@ error_to_exit(Where, {error, Reason}) ->
%%-----------------------------------------------------------------
-%% report_event(Serverity, Label, Service, Content)
+%% report_event(Severity, Label, Service, Content)
%%
%% Parameters:
%% Severity -> 0 =< integer() =< 100
diff --git a/lib/inets/src/inets_app/inets.mk b/lib/inets/src/inets_app/inets.mk
new file mode 100644
index 0000000000..b6e9fe1d96
--- /dev/null
+++ b/lib/inets/src/inets_app/inets.mk
@@ -0,0 +1,45 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+
+# %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%
+
+ifeq ($(INETS_TRACE), io)
+ERL_COMPILE_FLAGS += -Dinets_trace_io
+endif
+
+ifeq ($(INETS_DEBUG), true)
+ERL_COMPILE_FLAGS += -Dinets_debug
+endif
+
+ifeq ($(USE_INETS_HIPE), true)
+ERL_COMPILE_FLAGS += +native
+endif
+
+ifeq ($(WARN_UNUSED_WARS), true)
+ERL_COMPILE_FLAGS += +warn_unused_vars
+endif
+
+INETS_APP_VSN_COMPILE_FLAGS = \
+ +'{parse_transform,sys_pre_attributes}' \
+ +'{attribute,insert,app_vsn,$(APP_VSN)}'
+
+INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
+
+INETS_ERL_COMPILE_FLAGS += \
+ -pa $(ERL_TOP)/lib/inets/ebin \
+ $(INETS_APP_VSN_COMPILE_FLAGS)
+
diff --git a/lib/inets/src/inets_app/inets_service.erl b/lib/inets/src/inets_app/inets_service.erl
index 3499314d54..e9eb9892f2 100644
--- a/lib/inets/src/inets_app/inets_service.erl
+++ b/lib/inets/src/inets_app/inets_service.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
%%
@@ -61,5 +61,5 @@ behaviour_info(_) ->
%% service_info() -> [{Property, Value}] | {error, Reason}
-%% ex: http:service_info() -> [{profile, ProfileName}]
+%% ex: httpc:service_info() -> [{profile, ProfileName}]
%% httpd:service_info() -> [{host, Host}, {port, Port}]
diff --git a/lib/inets/src/tftp/Makefile b/lib/inets/src/tftp/Makefile
index b4339da1e2..759b70c8e4 100644
--- a/lib/inets/src/tftp/Makefile
+++ b/lib/inets/src/tftp/Makefile
@@ -56,17 +56,16 @@ TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
# ----------------------------------------------------
-# INETS FLAGS
+# FLAGS
# ----------------------------------------------------
-INETS_FLAGS = -D'SERVER_SOFTWARE="$(APPLICATION)/$(VSN)"'
+include ../inets_app/inets.mk
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-ERL_COMPILE_FLAGS += $(INETS_FLAGS) \
- +'{parse_transform,sys_pre_attributes}' \
- +'{attribute,insert,app_vsn,$(APP_VSN)}'
+ERL_COMPILE_FLAGS += \
+ $(INETS_FLAGS) \
+ $(INETS_ERL_COMPILE_FLAGS) \
+ -I../../include \
+ -I../inets_app
# ----------------------------------------------------
@@ -87,9 +86,10 @@ docs:
include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/src
- $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/src/tftp
+ $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src/tftp
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
release_docs_spec:
diff --git a/lib/inets/test/Makefile b/lib/inets/test/Makefile
index 668752da9e..bb7f2186af 100644
--- a/lib/inets/test/Makefile
+++ b/lib/inets/test/Makefile
@@ -143,6 +143,8 @@ else
INETS_FLAGS += -Dhttpd_security_verbosity=log
endif
+INETS_FLAGS += -pa ../../inets/ebin
+
INETS_ROOT = ../../inets
MODULES = \
@@ -241,8 +243,11 @@ RELTESTSYSBINDIR = $(RELTESTSYSALLDATADIR)/bin
# The path to the test_server ebin dir is needed when
# running the target "targets".
# ----------------------------------------------------
-ERL_COMPILE_FLAGS += -pa ../../../internal_tools/test_server/ebin \
- $(INCLUDES) $(FTP_FLAGS) $(INETS_FLAGS)
+ERL_COMPILE_FLAGS += \
+ -pa ../../../internal_tools/test_server/ebin \
+ $(INCLUDES) \
+ $(FTP_FLAGS) \
+ $(INETS_FLAGS)
# ----------------------------------------------------
# Targets
diff --git a/lib/inets/test/ftp_suite_lib.erl b/lib/inets/test/ftp_suite_lib.erl
index 75e1a5a7f9..5e27bc3a86 100644
--- a/lib/inets/test/ftp_suite_lib.erl
+++ b/lib/inets/test/ftp_suite_lib.erl
@@ -48,14 +48,17 @@
-ifdef(ftp_debug_client).
-define(ftp_open(Host, Flags),
- do_ftp_open(Host, [debug, {timeout, timer:seconds(15)}] ++ Flags)).
+ do_ftp_open(Host, [{debug, debug},
+ {timeout, timer:seconds(15)} | Flags])).
-else.
-ifdef(ftp_trace_client).
-define(ftp_open(Host, Flags),
- do_ftp_open(Host, [trace, {timeout, timer:seconds(15)}] ++ Flags)).
+ do_ftp_open(Host, [{debug, trace},
+ {timeout, timer:seconds(15)} | Flags])).
-else.
-define(ftp_open(Host, Flags),
- do_ftp_open(Host, [verbose, {timeout, timer:seconds(15)}] ++ Flags)).
+ do_ftp_open(Host, [{verbose, true},
+ {timeout, timer:seconds(15)} | Flags])).
-endif.
-endif.
@@ -113,9 +116,7 @@ get_ftpd_host([Host|Hosts]) ->
p("get_ftpd_host -> entry with"
"~n Host: ~p"
"~n", [Host]),
- case (catch ftp:open({option_list,
- [{host, Host}, {port, ?FTP_PORT},
- {timeout, 20000}]})) of
+ case (catch ftp:open(Host, [{port, ?FTP_PORT}, {timeout, 20000}])) of
{ok, Pid} ->
(catch ftp:close(Pid)),
{ok, Host};
@@ -212,7 +213,7 @@ do_init_per_testcase(Case, Config)
inets:start(),
NewConfig = close_connection(watch_dog(Config)),
Host = ftp_host(Config),
- case (catch ?ftp_open(Host, [])) of
+ case (catch ?ftp_open(Host, [{mode, passive}])) of
{ok, Pid} ->
[{ftp, Pid} | data_dir(NewConfig)];
{skip, _} = SKIP ->
@@ -225,9 +226,8 @@ do_init_per_testcase(Case, Config)
inets:start(),
NewConfig = close_connection(watch_dog(Config)),
Host = ftp_host(Config),
- case (catch ?ftp_open(Host, [])) of
+ case (catch ?ftp_open(Host, [{mode, active}])) of
{ok, Pid} ->
- ok = ftp:force_active(Pid),
[{ftp, Pid} | data_dir(NewConfig)];
{skip, _} = SKIP ->
SKIP
@@ -240,11 +240,10 @@ do_init_per_testcase(Case, Config)
io:format(user, "~n~n*** INIT ~w:~w ***~n~n", [?MODULE, Case]),
NewConfig = close_connection(watch_dog(Config)),
Host = ftp_host(Config),
- Opts = [{host, Host},
- {port, ?FTP_PORT},
- {flags, [verbose]},
+ Opts = [{port, ?FTP_PORT},
+ {verbose, true},
{progress, {?MODULE, progress, #progress{}}}],
- case ftp:open({option_list, Opts}) of
+ case ftp:open(Host, Opts) of
{ok, Pid} ->
ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS),
[{ftp, Pid} | data_dir(NewConfig)];
@@ -257,22 +256,23 @@ do_init_per_testcase(Case, Config) ->
inets:start(),
NewConfig = close_connection(watch_dog(Config)),
Host = ftp_host(Config),
- Flags =
+ Opts1 =
if
((Case =:= passive_ip_v6_disabled) orelse
(Case =:= active_ip_v6_disabled)) ->
- [ip_v6_disabled];
+ [{ipfamily, inet}];
true ->
[]
end,
- case (catch ?ftp_open(Host, Flags)) of
+ Opts2 =
+ case string:tokens(atom_to_list(Case), [$_]) of
+ [_, "active" | _] ->
+ [{mode, active} | Opts1];
+ _ ->
+ [{mode, passive} | Opts1]
+ end,
+ case (catch ?ftp_open(Host, Opts2)) of
{ok, Pid} ->
- case string:tokens(atom_to_list(Case), [$_]) of
- [_, "active"|_] ->
- ok = ftp:force_active(Pid);
- _ ->
- ok
- end,
ok = ftp:user(Pid, ?FTP_USER, ?FTP_PASS),
[{ftp, Pid} | data_dir(NewConfig)];
{skip, _} = SKIP ->
@@ -365,6 +365,7 @@ open(Config) when is_list(Config) ->
Host = ftp_host(Config),
(catch tc_open(Host)).
+
tc_open(Host) ->
{ok, Pid} = ?ftp_open(Host, []),
ok = ftp:close(Pid),
@@ -374,8 +375,9 @@ tc_open(Host) ->
{flags, [verbose]},
{timeout, 30000}]}),
ok = ftp:close(Pid1),
- {error, ehost} = ftp:open({option_list, [{port, ?FTP_PORT},
- {flags, [verbose]}]}),
+
+ {error, ehost} =
+ ftp:open({option_list, [{port, ?FTP_PORT}, {flags, [verbose]}]}),
{ok, Pid2} = ftp:open(Host),
ok = ftp:close(Pid2),
@@ -408,6 +410,15 @@ tc_open(Host) ->
{mode, cool}]}),
test_server:sleep(100),
ok = ftp:close(Pid6),
+
+ {ok, Pid7} =
+ ftp:open(Host, [{port, ?FTP_PORT}, {verbose, true}, {timeout, 30000}]),
+ ok = ftp:close(Pid7),
+
+ {ok, Pid8} =
+ ftp:open(Host, ?FTP_PORT),
+ ok = ftp:close(Pid8),
+
ok.
@@ -420,7 +431,7 @@ open_port(suite) ->
[];
open_port(Config) when is_list(Config) ->
Host = ftp_host(Config),
- {ok, Pid} = ftp:open(Host, ?FTP_PORT),
+ {ok, Pid} = ftp:open(Host, [{port, ?FTP_PORT}]),
ok = ftp:close(Pid),
{error, ehost} = ftp:open(?BAD_HOST, []),
ok.
@@ -954,26 +965,39 @@ api_missuse(doc)->
["Test that behaviour of the ftp process if the api is abused"];
api_missuse(suite) -> [];
api_missuse(Config) when is_list(Config) ->
+ io:format("api_missuse -> entry~n", []),
+ Flag = process_flag(trap_exit, true),
Pid = ?config(ftp, Config),
Host = ftp_host(Config),
-
+
%% Serious programming fault, connetion will be shut down
- {error, {connection_terminated, 'API_violation'}} =
- gen_server:call(Pid, {self(), foobar, 10}, infinity),
+ io:format("api_missuse -> verify bad call termination (~p)~n", [Pid]),
+ case (catch gen_server:call(Pid, {self(), foobar, 10}, infinity)) of
+ {error, {connection_terminated, 'API_violation'}} ->
+ ok;
+ Unexpected1 ->
+ exit({unexpected_result, Unexpected1})
+ end,
test_server:sleep(500),
undefined = process_info(Pid, status),
+ io:format("api_missuse -> start new client~n", []),
{ok, Pid2} = ?ftp_open(Host, []),
%% Serious programming fault, connetion will be shut down
+ io:format("api_missuse -> verify bad cast termination~n", []),
gen_server:cast(Pid2, {self(), foobar, 10}),
test_server:sleep(500),
undefined = process_info(Pid2, status),
+ io:format("api_missuse -> start new client~n", []),
{ok, Pid3} = ?ftp_open(Host, []),
%% Could be an innocent misstake the connection lives.
+ io:format("api_missuse -> verify bad bang~n", []),
Pid3 ! foobar,
test_server:sleep(500),
{status, _} = process_info(Pid3, status),
+ process_flag(trap_exit, Flag),
+ io:format("api_missuse -> done~n", []),
ok.
@@ -1525,11 +1549,11 @@ split([C| Cs], I, Is) ->
split([], I, Is) ->
lists:reverse([lists:reverse(I)| Is]).
-do_ftp_open(Host, Flags) ->
+do_ftp_open(Host, Opts) ->
io:format("do_ftp_open -> entry with"
- "~n Host: ~p"
- "~n Flags: ~p", [Host, Flags]),
- case ftp:open(Host, Flags) of
+ "~n Host: ~p"
+ "~n Opts: ~p", [Host, Opts]),
+ case ftp:open(Host, Opts) of
{ok, _} = OK ->
OK;
{error, Reason} ->
diff --git a/lib/inets/test/http_format_SUITE.erl b/lib/inets/test/http_format_SUITE.erl
index 9559317640..79945f0f4d 100644
--- a/lib/inets/test/http_format_SUITE.erl
+++ b/lib/inets/test/http_format_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
%%
@@ -567,6 +567,12 @@ convert_netscapecookie_date(Config) when is_list(Config) ->
http_util:convert_netscapecookie_date("Sun, 12-Nov-2006 08:59:38 GMT"),
{{2006,12,12},{8,59,38}} =
http_util:convert_netscapecookie_date("Sun, 12-Dec-2006 08:59:38 GMT"),
+ {{2006,12,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun 12-Dec-2006 08:59:38 GMT"),
+ {{2006,12,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun, 12-Dec-06 08:59:38 GMT"),
+ {{2006,12,12},{8,59,38}} =
+ http_util:convert_netscapecookie_date("Sun 12-Dec-06 08:59:38 GMT"),
ok.
%%--------------------------------------------------------------------
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index aa65fb1197..b5fd896001 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -87,8 +87,14 @@ all(suite) ->
http_headers_dummy,
http_bad_response,
ssl_head,
+ ossl_head,
+ essl_head,
ssl_get,
+ ossl_get,
+ essl_get,
ssl_trace,
+ ossl_trace,
+ essl_trace,
http_redirect,
http_redirect_loop,
http_internal_server_error,
@@ -179,49 +185,66 @@ init_per_testcase(otp_8154_1 = Case, Config) ->
init_per_testcase(Case, Config) ->
init_per_testcase(Case, 2, Config).
+init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) ->
+ tsp("init_per_testcase_ssl -> stop ssl"),
+ application:stop(ssl),
+ Config2 = lists:keydelete(local_ssl_server, 1, Config),
+ %% Will start inets
+ tsp("init_per_testcase_ssl -> try start http server (including inets)"),
+ Server = inets_test_lib:start_http_server(
+ filename:join(PrivDir, SslConfFile), Tag),
+ tsp("init_per_testcase -> Server: ~p", [Server]),
+ [{local_ssl_server, Server} | Config2].
+
init_per_testcase(Case, Timeout, Config) ->
- io:format(user, "~n~n*** INIT ~w:~w[~w] ***~n~n",
+ io:format(user, "~n~n*** INIT ~w:[~w][~w] ***~n~n",
[?MODULE, Timeout, Case]),
- PrivDir = ?config(priv_dir, Config),
+ PrivDir = ?config(priv_dir, Config),
+ tsp("init_per_testcase -> stop inets"),
application:stop(inets),
- Dog = test_server:timetrap(inets_test_lib:minutes(Timeout)),
- TmpConfig = lists:keydelete(watchdog, 1, Config),
- IpConfFile = integer_to_list(?IP_PORT) ++ ".conf",
+ Dog = test_server:timetrap(inets_test_lib:minutes(Timeout)),
+ TmpConfig = lists:keydelete(watchdog, 1, Config),
+ IpConfFile = integer_to_list(?IP_PORT) ++ ".conf",
SslConfFile = integer_to_list(?SSL_PORT) ++ ".conf",
+ %% inets:enable_trace(max, io, httpd),
+ %% inets:enable_trace(max, io, httpc),
+ inets:enable_trace(max, io, all),
+
NewConfig =
case atom_to_list(Case) of
- "ssl" ++ _ ->
- application:stop(ssl),
- TmpConfig2 =
- lists:keydelete(local_ssl_server, 1, TmpConfig),
- %% Will start inets
- Server =
- inets_test_lib:start_http_server(
- filename:join(PrivDir, SslConfFile)),
- [{watchdog, Dog}, {local_ssl_server, Server} | TmpConfig2];
+ [$s, $s, $l | _] ->
+ init_per_testcase_ssl(ssl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]);
+
+ [$o, $s, $s, $l | _] ->
+ init_per_testcase_ssl(ossl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]);
+
+ [$e, $s, $s, $l | _] ->
+ init_per_testcase_ssl(essl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]);
+
"proxy" ++ Rest ->
- case Rest of
- "_https_not_supported" ->
- inets:start(),
- case (catch application:start(ssl)) of
- ok ->
- [{watchdog, Dog} | TmpConfig];
- _ ->
- [{skip,
- "SSL does not seem to be supported"}
- | TmpConfig]
- end;
- _ ->
- case is_proxy_available(?PROXY, ?PROXY_PORT) of
- true ->
- inets:start(),
- [{watchdog, Dog} | TmpConfig];
- false ->
- [{skip, "Failed to contact proxy"} |
- TmpConfig]
- end
- end;
+ case Rest of
+ "_https_not_supported" ->
+ tsp("init_per_testcase -> [proxy case] start inets"),
+ inets:start(),
+ tsp("init_per_testcase -> [proxy case] start ssl"),
+ case (catch application:start(ssl)) of
+ ok ->
+ [{watchdog, Dog} | TmpConfig];
+ _ ->
+ [{skip, "SSL does not seem to be supported"}
+ | TmpConfig]
+ end;
+ _ ->
+ case is_proxy_available(?PROXY, ?PROXY_PORT) of
+ true ->
+ inets:start(),
+ [{watchdog, Dog} | TmpConfig];
+ false ->
+ [{skip, "Failed to contact proxy"} |
+ TmpConfig]
+ end
+ end;
_ ->
TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig),
Server =
@@ -231,13 +254,12 @@ init_per_testcase(Case, Timeout, Config) ->
[{watchdog, Dog}, {local_server, Server} | TmpConfig2]
end,
- http:set_options([{proxy, {{?PROXY, ?PROXY_PORT},
- ["localhost", ?IPV6_LOCAL_HOST]}}]),
- inets:enable_trace(max, io, httpc),
- %% inets:enable_trace(max, io, all),
+ httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT},
+ ["localhost", ?IPV6_LOCAL_HOST]}}]),
%% snmp:set_trace([gen_tcp, inet_tcp, prim_inet]),
NewConfig.
+
%%--------------------------------------------------------------------
%% Function: end_per_testcase(Case, Config) -> _
%% Case - atom()
@@ -306,7 +328,7 @@ http_head(Config) when is_list(Config) ->
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
- case http:request(head, {URL, []}, [], []) of
+ case httpc:request(head, {URL, []}, [], []) of
{ok, {{_,200,_}, [_ | _], []}} ->
ok;
{ok, WrongReply} ->
@@ -337,7 +359,7 @@ http_get(Config) when is_list(Config) ->
HttpOptions1 = [{timeout, Timeout}, {connect_timeout, ConnTimeout}],
Options1 = [],
Body =
- case http:request(Method, Request, HttpOptions1, Options1) of
+ case httpc:request(Method, Request, HttpOptions1, Options1) of
{ok, {{_,200,_}, [_ | _], ReplyBody = [_ | _]}} ->
ReplyBody;
{ok, UnexpectedReply1} ->
@@ -346,12 +368,12 @@ http_get(Config) when is_list(Config) ->
tsf({bad_reply, Error1})
end,
- %% eqvivivalent to http:request(get, {URL, []}, [], []),
+ %% eqvivivalent to httpc:request(get, {URL, []}, [], []),
inets_test_lib:check_body(Body),
HttpOptions2 = [],
Options2 = [{body_format, binary}],
- case http:request(Method, Request, HttpOptions2, Options2) of
+ case httpc:request(Method, Request, HttpOptions2, Options2) of
{ok, {{_,200,_}, [_ | _], Bin}} when is_binary(Bin) ->
ok;
{ok, {{_,200,_}, [_ | _], BadBin}} ->
@@ -390,11 +412,11 @@ http_post(Config) when is_list(Config) ->
Body = lists:duplicate(100, "1"),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
- http:request(post, {URL, [{"expect","100-continue"}],
+ httpc:request(post, {URL, [{"expect","100-continue"}],
"text/plain", Body}, [], []),
{ok, {{_,504,_}, [_ | _], []}} =
- http:request(post, {URL, [{"expect","100-continue"}],
+ httpc:request(post, {URL, [{"expect","100-continue"}],
"text/plain", "foobar"}, [], []);
_ ->
{skip, "Failed to start local http-server"}
@@ -411,13 +433,13 @@ http_emulate_lower_versions(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, Body0} =
- http:request(get, {URL, []}, [{version, "HTTP/0.9"}], []),
+ httpc:request(get, {URL, []}, [{version, "HTTP/0.9"}], []),
inets_test_lib:check_body(Body0),
{ok, {{"HTTP/1.0", 200, _}, [_ | _], Body1 = [_ | _]}} =
- http:request(get, {URL, []}, [{version, "HTTP/1.0"}], []),
+ httpc:request(get, {URL, []}, [{version, "HTTP/1.0"}], []),
inets_test_lib:check_body(Body1),
{ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} =
- http:request(get, {URL, []}, [{version, "HTTP/1.1"}], []),
+ httpc:request(get, {URL, []}, [{version, "HTTP/1.1"}], []),
inets_test_lib:check_body(Body2);
_->
{skip, "Failed to start local http-server"}
@@ -431,24 +453,24 @@ http_relaxed(doc) ->
http_relaxed(suite) ->
[];
http_relaxed(Config) when is_list(Config) ->
- ok = http:set_options([{ipv6, disabled}]), % also test the old option
- %% ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipv6, disabled}]), % also test the old option
+ %% ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++
"/missing_reason_phrase.html",
{error, Reason} =
- http:request(get, {URL, []}, [{relaxed, false}], []),
+ httpc:request(get, {URL, []}, [{relaxed, false}], []),
test_server:format("Not relaxed: ~p~n", [Reason]),
{ok, {{_, 200, _}, [_ | _], [_ | _]}} =
- http:request(get, {URL, []}, [{relaxed, true}], []),
+ httpc:request(get, {URL, []}, [{relaxed, true}], []),
DummyServerPid ! stop,
- ok = http:set_options([{ipv6, enabled}]),
- %% ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipv6, enabled}]),
+ %% ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -458,7 +480,7 @@ http_dummy_pipe(doc) ->
http_dummy_pipe(suite) ->
[];
http_dummy_pipe(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/foobar.html",
@@ -466,7 +488,7 @@ http_dummy_pipe(Config) when is_list(Config) ->
test_pipeline(URL),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
http_inets_pipe(doc) ->
@@ -488,11 +510,11 @@ test_pipeline(URL) ->
p("test_pipeline -> entry with"
"~n URL: ~p", [URL]),
- http:set_options([{pipeline_timeout, 50000}]),
+ httpc:set_options([{pipeline_timeout, 50000}]),
p("test_pipeline -> issue (async) request 1"),
{ok, RequestId1} =
- http:request(get, {URL, []}, [], [{sync, false}]),
+ httpc:request(get, {URL, []}, [], [{sync, false}]),
test_server:format("RequestId1: ~p~n", [RequestId1]),
p("test_pipeline -> RequestId1: ~p", [RequestId1]),
@@ -502,13 +524,13 @@ test_pipeline(URL) ->
p("test_pipeline -> issue (async) request 2"),
{ok, RequestId2} =
- http:request(get, {URL, []}, [], [{sync, false}]),
+ httpc:request(get, {URL, []}, [], [{sync, false}]),
tsp("RequestId2: ~p", [RequestId2]),
p("test_pipeline -> RequestId2: ~p", [RequestId2]),
p("test_pipeline -> issue (sync) request 3"),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
- http:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [], []),
p("test_pipeline -> expect reply for (async) request 1 or 2"),
receive
@@ -544,18 +566,18 @@ test_pipeline(URL) ->
p("test_pipeline -> issue (async) request 4"),
{ok, RequestId3} =
- http:request(get, {URL, []}, [], [{sync, false}]),
+ httpc:request(get, {URL, []}, [], [{sync, false}]),
tsp("RequestId3: ~p", [RequestId3]),
p("test_pipeline -> RequestId3: ~p", [RequestId3]),
p("test_pipeline -> issue (async) request 5"),
{ok, RequestId4} =
- http:request(get, {URL, []}, [], [{sync, false}]),
+ httpc:request(get, {URL, []}, [], [{sync, false}]),
tsp("RequestId4: ~p~n", [RequestId4]),
p("test_pipeline -> RequestId4: ~p", [RequestId4]),
p("test_pipeline -> cancel (async) request 4"),
- ok = http:cancel_request(RequestId3),
+ ok = httpc:cancel_request(RequestId3),
p("test_pipeline -> expect *no* reply for cancelled (async) request 4 (for 3 secs)"),
receive
@@ -607,7 +629,7 @@ http_trace(Config) when is_list(Config) ->
ok ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
- case http:request(trace, {URL, []}, [], []) of
+ case httpc:request(trace, {URL, []}, [], []) of
{ok, {{_,200,_}, [_ | _], "TRACE /dummy.html" ++ _}} ->
ok;
{ok, {{_,200,_}, [_ | _], WrongBody}} ->
@@ -631,7 +653,7 @@ http_async(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, RequestId} =
- http:request(get, {URL, []}, [], [{sync, false}]),
+ httpc:request(get, {URL, []}, [], [{sync, false}]),
Body =
receive
@@ -644,8 +666,8 @@ http_async(Config) when is_list(Config) ->
inets_test_lib:check_body(binary_to_list(Body)),
{ok, NewRequestId} =
- http:request(get, {URL, []}, [], [{sync, false}]),
- ok = http:cancel_request(NewRequestId),
+ httpc:request(get, {URL, []}, [], [{sync, false}]),
+ ok = httpc:cancel_request(NewRequestId),
receive
{http, {NewRequestId, _NewResult}} ->
test_server:fail(http_cancel_request_failed)
@@ -669,9 +691,9 @@ http_save_to_file(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, saved_to_file}
- = http:request(get, {URL, []}, [], [{stream, FilePath}]),
+ = httpc:request(get, {URL, []}, [], [{stream, FilePath}]),
{ok, Bin} = file:read_file(FilePath),
- {ok, {{_,200,_}, [_ | _], Body}} = http:request(URL),
+ {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL),
Bin == Body;
_ ->
{skip, "Failed to start local http-server"}
@@ -690,7 +712,7 @@ http_save_to_file_async(Config) when is_list(Config) ->
FilePath = filename:join(PrivDir, "dummy.html"),
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
- {ok, RequestId} = http:request(get, {URL, []}, [],
+ {ok, RequestId} = httpc:request(get, {URL, []}, [],
[{stream, FilePath},
{sync, false}]),
receive
@@ -701,7 +723,7 @@ http_save_to_file_async(Config) when is_list(Config) ->
end,
{ok, Bin} = file:read_file(FilePath),
- {ok, {{_,200,_}, [_ | _], Body}} = http:request(URL),
+ {ok, {{_,200,_}, [_ | _], Body}} = httpc:request(URL),
Bin == Body;
_ ->
{skip, "Failed to start local http-server"}
@@ -731,7 +753,7 @@ http_headers(Config) when is_list(Config) ->
Date = httpd_util:rfc1123_date({date(), time()}),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
- http:request(get, {URL, [{"If-Modified-Since",
+ httpc:request(get, {URL, [{"If-Modified-Since",
Mod},
{"From","[email protected]"},
{"Date", Date}
@@ -742,7 +764,7 @@ http_headers(Config) when is_list(Config) ->
CreatedSec+1)),
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
- http:request(get, {URL, [{"If-UnModified-Since",
+ httpc:request(get, {URL, [{"If-UnModified-Since",
Mod1}
]}, [], []),
@@ -750,12 +772,12 @@ http_headers(Config) when is_list(Config) ->
{ok, {{_,200,_}, [_ | _], [_ | _]}} =
- http:request(get, {URL, [{"If-Match",
+ httpc:request(get, {URL, [{"If-Match",
Tag}
]}, [], []),
{ok, {{_,200,_}, [_ | _], _}} =
- http:request(get, {URL, [{"If-None-Match",
+ httpc:request(get, {URL, [{"If-None-Match",
"NotEtag,NeihterEtag"},
{"Connection", "Close"}
]}, [], []),
@@ -773,7 +795,7 @@ http_headers_dummy(doc) ->
http_headers_dummy(suite) ->
[];
http_headers_dummy(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy_headers.html",
@@ -789,7 +811,7 @@ http_headers_dummy(Config) when is_list(Config) ->
%% that the client header-handling code. This would not
%% be a vaild http-request!
{ok, {{_,200,_}, [_ | _], [_|_]}} =
- http:request(post,
+ httpc:request(post,
{URL,
[{"Via",
"1.0 fred, 1.1 nowhere.com (Apache/1.1)"},
@@ -828,7 +850,7 @@ http_headers_dummy(Config) when is_list(Config) ->
], "text/plain", FooBar},
[], []),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -838,21 +860,21 @@ http_bad_response(doc) ->
http_bad_response(suite) ->
[];
http_bad_response(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_crlf.html",
URL1 = ?URL_START ++ integer_to_list(Port) ++ "/wrong_statusline.html",
- {error, timeout} = http:request(get, {URL, []}, [{timeout, 400}], []),
+ {error, timeout} = httpc:request(get, {URL, []}, [{timeout, 400}], []),
- {error, Reason} = http:request(URL1),
+ {error, Reason} = httpc:request(URL1),
test_server:format("Wrong Statusline: ~p~n", [Reason]),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -862,69 +884,168 @@ ssl_head(doc) ->
ssl_head(suite) ->
[];
ssl_head(Config) when is_list(Config) ->
+ ssl_head(ssl, Config).
+
+ossl_head(doc) ->
+ ["Same as http_head/1 but over ssl sockets."];
+ossl_head(suite) ->
+ [];
+ossl_head(Config) when is_list(Config) ->
+ ssl_head(ossl, Config).
+
+essl_head(doc) ->
+ ["Same as http_head/1 but over ssl sockets."];
+essl_head(suite) ->
+ [];
+essl_head(Config) when is_list(Config) ->
+ ssl_head(essl, Config).
+
+ssl_head(SslTag, Config) ->
+ tsp("ssl_head -> entry with"
+ "~n SslTag: ~p"
+ "~n Config: ~p", [SslTag, Config]),
case ?config(local_ssl_server, Config) of
ok ->
- DataDir = ?config(data_dir, Config),
- Port = ?config(local_ssl_port, Config),
- URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
- CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(local_ssl_port, Config),
+ URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
+ SSLConfig =
+ case SslTag of
+ ssl ->
+ SSLOptions;
+ ossl ->
+ {ossl, SSLOptions};
+ essl ->
+ {essl, SSLOptions}
+ end,
+ tsp("ssl_head -> make request using: "
+ "~n URL: ~p"
+ "~n SslTag: ~p"
+ "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]),
{ok, {{_,200, _}, [_ | _], []}} =
- http:request(head, {URL, []}, [{ssl, SSLOptions}], []);
+ httpc:request(head, {URL, []}, [{ssl, SSLConfig}], []);
{ok, _} ->
- {skip, "Failed to start local http-server"};
+ {skip, "local http-server not started"};
_ ->
- {skip, "Failed to start SSL"}
+ {skip, "SSL not started"}
end.
+
+
%%-------------------------------------------------------------------------
ssl_get(doc) ->
["Same as http_get/1 but over ssl sockets."];
ssl_get(suite) ->
[];
ssl_get(Config) when is_list(Config) ->
+ ssl_get(ssl, Config).
+
+ossl_get(doc) ->
+ ["Same as http_get/1 but over ssl sockets."];
+ossl_get(suite) ->
+ [];
+ossl_get(Config) when is_list(Config) ->
+ ssl_get(ossl, Config).
+
+essl_get(doc) ->
+ ["Same as http_get/1 but over ssl sockets."];
+essl_get(suite) ->
+ [];
+essl_get(Config) when is_list(Config) ->
+ ssl_get(essl, Config).
+
+ssl_get(SslTag, Config) when is_list(Config) ->
case ?config(local_ssl_server, Config) of
ok ->
- DataDir = ?config(data_dir, Config),
- Port = ?config(local_ssl_port, Config),
- URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
- CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(local_ssl_port, Config),
+ URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
- {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} =
- http:request(get, {URL, []}, [{ssl, SSLOptions}], []),
- inets_test_lib:check_body(Body);
+ SSLConfig =
+ case SslTag of
+ ssl ->
+ SSLOptions;
+ ossl ->
+ {ossl, SSLOptions};
+ essl ->
+ {essl, SSLOptions}
+ end,
+ tsp("ssl_get -> make request using: "
+ "~n URL: ~p"
+ "~n SslTag: ~p"
+ "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]),
+ {ok, {{_,200, _}, [_ | _], Body = [_ | _]}} =
+ httpc:request(get, {URL, []}, [{ssl, SSLConfig}], []),
+ inets_test_lib:check_body(Body);
{ok, _} ->
{skip, "Failed to start local http-server"};
_ ->
{skip, "Failed to start SSL"}
end.
+
+
%%-------------------------------------------------------------------------
ssl_trace(doc) ->
["Same as http_trace/1 but over ssl sockets."];
ssl_trace(suite) ->
[];
ssl_trace(Config) when is_list(Config) ->
+ ssl_trace(ssl, Config).
+
+ossl_trace(doc) ->
+ ["Same as http_trace/1 but over ssl sockets."];
+ossl_trace(suite) ->
+ [];
+ossl_trace(Config) when is_list(Config) ->
+ ssl_trace(ossl, Config).
+
+essl_trace(doc) ->
+ ["Same as http_trace/1 but over ssl sockets."];
+essl_trace(suite) ->
+ [];
+essl_trace(Config) when is_list(Config) ->
+ ssl_trace(essl, Config).
+
+ssl_trace(SslTag, Config) when is_list(Config) ->
case ?config(local_ssl_server, Config) of
ok ->
- DataDir = ?config(data_dir, Config),
- Port = ?config(local_ssl_port, Config),
- URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
- CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
+ DataDir = ?config(data_dir, Config),
+ Port = ?config(local_ssl_port, Config),
+ URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/dummy.html",
+ CertFile = filename:join(DataDir, "ssl_client_cert.pem"),
SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}],
- case http:request(trace, {URL, []}, [{ssl, SSLOptions}], []) of
+ SSLConfig =
+ case SslTag of
+ ssl ->
+ SSLOptions;
+ ossl ->
+ {ossl, SSLOptions};
+ essl ->
+ {essl, SSLOptions}
+ end,
+ tsp("ssl_trace -> make request using: "
+ "~n URL: ~p"
+ "~n SslTag: ~p"
+ "~n SSLOptions: ~p", [URL, SslTag, SSLOptions]),
+ case httpc:request(trace, {URL, []}, [{ssl, SSLConfig}], []) of
{ok, {{_,200, _}, [_ | _], "TRACE /dummy.html" ++ _}} ->
ok;
{ok, {{_,200,_}, [_ | _], WrongBody}} ->
- test_server:fail({wrong_body, WrongBody});
+ tsf({wrong_body, WrongBody});
{ok, WrongReply} ->
- test_server:fail({wrong_reply, WrongReply});
+ tsf({wrong_reply, WrongReply});
Error ->
- test_server:fail({failed, Error})
+ tsf({failed, Error})
end;
{ok, _} ->
{skip, "Failed to start local http-server"};
_ ->
{skip, "Failed to start SSL"}
end.
+
+
%%-------------------------------------------------------------------------
http_redirect(doc) ->
["Test redirect with dummy server as httpd does not implement"
@@ -937,7 +1058,7 @@ http_redirect(Config) when is_list(Config) ->
case ?config(local_server, Config) of
ok ->
tsp("http_redirect -> set ipfamily option to inet"),
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
tsp("http_redirect -> start dummy server inet"),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
@@ -948,29 +1069,29 @@ http_redirect(Config) when is_list(Config) ->
tsp("http_redirect -> issue request 1: "
"~n ~p", [URL300]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = http:request(get, {URL300, []}, [], []),
+ = httpc:request(get, {URL300, []}, [], []),
tsp("http_redirect -> issue request 2: "
"~n ~p", [URL300]),
{ok, {{_,300,_}, [_ | _], _}} =
- http:request(get, {URL300, []}, [{autoredirect, false}], []),
+ httpc:request(get, {URL300, []}, [{autoredirect, false}], []),
URL301 = ?URL_START ++ integer_to_list(Port) ++ "/301.html",
tsp("http_redirect -> issue request 3: "
"~n ~p", [URL301]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = http:request(get, {URL301, []}, [], []),
+ = httpc:request(get, {URL301, []}, [], []),
tsp("http_redirect -> issue request 4: "
"~n ~p", [URL301]),
{ok, {{_,200,_}, [_ | _], []}}
- = http:request(head, {URL301, []}, [], []),
+ = httpc:request(head, {URL301, []}, [], []),
tsp("http_redirect -> issue request 5: "
"~n ~p", [URL301]),
{ok, {{_,301,_}, [_ | _], [_|_]}}
- = http:request(post, {URL301, [],"text/plain", "foobar"},
+ = httpc:request(post, {URL301, [],"text/plain", "foobar"},
[], []),
URL302 = ?URL_START ++ integer_to_list(Port) ++ "/302.html",
@@ -978,8 +1099,8 @@ http_redirect(Config) when is_list(Config) ->
tsp("http_redirect -> issue request 6: "
"~n ~p", [URL302]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = http:request(get, {URL302, []}, [], []),
- case http:request(get, {URL302, []}, [], []) of
+ = httpc:request(get, {URL302, []}, [], []),
+ case httpc:request(get, {URL302, []}, [], []) of
{ok, Reply7} ->
case Reply7 of
{{_,200,_}, [_ | _], [_|_]} ->
@@ -1006,12 +1127,12 @@ http_redirect(Config) when is_list(Config) ->
tsp("http_redirect -> issue request 7: "
"~n ~p", [URL302]),
{ok, {{_,200,_}, [_ | _], []}}
- = http:request(head, {URL302, []}, [], []),
+ = httpc:request(head, {URL302, []}, [], []),
tsp("http_redirect -> issue request 8: "
"~n ~p", [URL302]),
{ok, {{_,302,_}, [_ | _], [_|_]}}
- = http:request(post, {URL302, [],"text/plain", "foobar"},
+ = httpc:request(post, {URL302, [],"text/plain", "foobar"},
[], []),
URL307 = ?URL_START ++ integer_to_list(Port) ++ "/307.html",
@@ -1019,23 +1140,23 @@ http_redirect(Config) when is_list(Config) ->
tsp("http_redirect -> issue request 9: "
"~n ~p", [URL307]),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = http:request(get, {URL307, []}, [], []),
+ = httpc:request(get, {URL307, []}, [], []),
tsp("http_redirect -> issue request 10: "
"~n ~p", [URL307]),
{ok, {{_,200,_}, [_ | _], []}}
- = http:request(head, {URL307, []}, [], []),
+ = httpc:request(head, {URL307, []}, [], []),
tsp("http_redirect -> issue request 11: "
"~n ~p", [URL307]),
{ok, {{_,307,_}, [_ | _], [_|_]}}
- = http:request(post, {URL307, [],"text/plain", "foobar"},
+ = httpc:request(post, {URL307, [],"text/plain", "foobar"},
[], []),
tsp("http_redirect -> stop dummy server"),
DummyServerPid ! stop,
tsp("http_redirect -> reset ipfamily option (to inet6fb4)"),
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
tsp("http_redirect -> done"),
ok;
@@ -1051,15 +1172,15 @@ http_redirect_loop(doc) ->
http_redirect_loop(suite) ->
[];
http_redirect_loop(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/redirectloop.html",
{ok, {{_,300,_}, [_ | _], _}}
- = http:request(get, {URL, []}, [], []),
+ = httpc:request(get, {URL, []}, [], []),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
%%-------------------------------------------------------------------------
@@ -1068,13 +1189,13 @@ http_internal_server_error(doc) ->
http_internal_server_error(suite) ->
[];
http_internal_server_error(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL500 = ?URL_START ++ integer_to_list(Port) ++ "/500.html",
{ok, {{_,500,_}, [_ | _], _}}
- = http:request(get, {URL500, []}, [], []),
+ = httpc:request(get, {URL500, []}, [], []),
URL503 = ?URL_START ++ integer_to_list(Port) ++ "/503.html",
@@ -1084,16 +1205,16 @@ http_internal_server_error(Config) when is_list(Config) ->
ets:insert(unavailable, {503, unavailable}),
{ok, {{_,200, _}, [_ | _], [_|_]}} =
- http:request(get, {URL503, []}, [], []),
+ httpc:request(get, {URL503, []}, [], []),
ets:insert(unavailable, {503, long_unavailable}),
{ok, {{_,503, _}, [_ | _], [_|_]}} =
- http:request(get, {URL503, []}, [], []),
+ httpc:request(get, {URL503, []}, [], []),
ets:delete(unavailable),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1103,7 +1224,7 @@ http_userinfo(doc) ->
http_userinfo(suite) ->
[];
http_userinfo(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
@@ -1111,16 +1232,16 @@ http_userinfo(Config) when is_list(Config) ->
++ integer_to_list(Port) ++ "/userinfo.html",
{ok, {{_,200,_}, [_ | _], _}}
- = http:request(get, {URLAuth, []}, [], []),
+ = httpc:request(get, {URLAuth, []}, [], []),
URLUnAuth = "http://alladin:foobar@localhost:"
++ integer_to_list(Port) ++ "/userinfo.html",
{ok, {{_,401, _}, [_ | _], _}} =
- http:request(get, {URLUnAuth, []}, [], []),
+ httpc:request(get, {URLUnAuth, []}, [], []),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1130,7 +1251,7 @@ http_cookie(doc) ->
http_cookie(suite) ->
[];
http_cookie(Config) when is_list(Config) ->
- ok = http:set_options([{cookies, enabled}, {ipfamily, inet}]),
+ ok = httpc:set_options([{cookies, enabled}, {ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URLStart = ?URL_START
@@ -1139,19 +1260,19 @@ http_cookie(Config) when is_list(Config) ->
URLCookie = URLStart ++ "/cookie.html",
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = http:request(get, {URLCookie, []}, [], []),
+ = httpc:request(get, {URLCookie, []}, [], []),
ets:new(cookie, [named_table, public, set]),
ets:insert(cookie, {cookies, true}),
{ok, {{_,200,_}, [_ | _], [_|_]}}
- = http:request(get, {URLStart ++ "/", []}, [], []),
+ = httpc:request(get, {URLStart ++ "/", []}, [], []),
ets:delete(cookie),
- ok = http:set_options([{cookies, disabled}, {ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{cookies, disabled}, {ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6************
ok.
%%-------------------------------------------------------------------------
@@ -1162,7 +1283,7 @@ proxy_options(suite) ->
proxy_options(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
- case http:request(options, {?PROXY_URL, []}, [], []) of
+ case httpc:request(options, {?PROXY_URL, []}, [], []) of
{ok, {{_,200,_}, Headers, _}} ->
case lists:keysearch("allow", 1, Headers) of
{value, {"allow", _}} ->
@@ -1186,7 +1307,7 @@ proxy_head(suite) ->
proxy_head(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
- case http:request(head, {?PROXY_URL, []}, [], []) of
+ case httpc:request(head, {?PROXY_URL, []}, [], []) of
{ok, {{_,200, _}, [_ | _], []}} ->
ok;
Unexpected ->
@@ -1205,7 +1326,7 @@ proxy_get(suite) ->
proxy_get(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
- case http:request(get, {?PROXY_URL, []}, [], []) of
+ case httpc:request(get, {?PROXY_URL, []}, [], []) of
{ok, {{_,200,_}, [_ | _], Body = [_ | _]}} ->
inets_test_lib:check_body(Body);
Unexpected ->
@@ -1257,7 +1378,7 @@ proxy_emulate_lower_versions(Config) when is_list(Config) ->
end.
pelv_get(Version) ->
- http:request(get, {?PROXY_URL, []}, [{version, Version}], []).
+ httpc:request(get, {?PROXY_URL, []}, [{version, Version}], []).
%%-------------------------------------------------------------------------
proxy_trace(doc) ->
@@ -1266,7 +1387,7 @@ proxy_trace(suite) ->
[];
proxy_trace(Config) when is_list(Config) ->
%%{ok, {{_,200,_}, [_ | _], "TRACE " ++ _}} =
- %% http:request(trace, {?PROXY_URL, []}, [], []),
+ %% httpc:request(trace, {?PROXY_URL, []}, [], []),
{skip, "HTTP TRACE is no longer allowed on the ?PROXY_URL server due "
"to security reasons"}.
@@ -1281,7 +1402,7 @@ proxy_post(suite) ->
proxy_post(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
- case http:request(post, {?PROXY_URL, [],
+ case httpc:request(post, {?PROXY_URL, [],
"text/plain", "foobar"}, [],[]) of
{ok, {{_,405,_}, [_ | _], [_ | _]}} ->
ok;
@@ -1303,7 +1424,7 @@ proxy_put(suite) ->
proxy_put(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
- case http:request(put, {"http://www.erlang.org/foobar.html", [],
+ case httpc:request(put, {"http://www.erlang.org/foobar.html", [],
"html", "<html> <body><h1> foo </h1>"
"<p>bar</p> </body></html>"}, [], []) of
{ok, {{_,405,_}, [_ | _], [_ | _]}} ->
@@ -1328,7 +1449,7 @@ proxy_delete(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
URL = ?PROXY_URL ++ "/foobar.html",
- case http:request(delete, {URL, []}, [], []) of
+ case httpc:request(delete, {URL, []}, [], []) of
{ok, {{_,404,_}, [_ | _], [_ | _]}} ->
ok;
Unexpected ->
@@ -1348,7 +1469,7 @@ proxy_headers(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
{ok, {{_,200,_}, [_ | _], [_ | _]}}
- = http:request(get, {?PROXY_URL,
+ = httpc:request(get, {?PROXY_URL,
[
{"Accept",
"text/*, text/html,"
@@ -1383,7 +1504,7 @@ proxy_auth(Config) when is_list(Config) ->
%% atleast the code for sending the header does not crash!
case ?config(skip, Config) of
undefined ->
- case http:request(get, {?PROXY_URL, []},
+ case httpc:request(get, {?PROXY_URL, []},
[{proxy_auth, {"foo", "bar"}}], []) of
{ok, {{_,200, _}, [_ | _], [_|_]}} ->
ok;
@@ -1403,7 +1524,7 @@ http_server_does_not_exist(suite) ->
[];
http_server_does_not_exist(Config) when is_list(Config) ->
{error, _} =
- http:request(get, {"http://localhost:" ++
+ httpc:request(get, {"http://localhost:" ++
integer_to_list(?NOT_IN_USE_PORT)
++ "/", []},[], []),
ok.
@@ -1418,7 +1539,7 @@ page_does_not_exist(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/doesnotexist.html",
{ok, {{_,404,_}, [_ | _], [_ | _]}}
- = http:request(get, {URL, []}, [], []),
+ = httpc:request(get, {URL, []}, [], []),
ok.
@@ -1432,7 +1553,7 @@ proxy_page_does_not_exist(Config) when is_list(Config) ->
undefined ->
URL = ?PROXY_URL ++ "/doesnotexist.html",
{ok, {{_,404,_}, [_ | _], [_ | _]}} =
- http:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [], []),
ok;
Reason ->
{skip, Reason}
@@ -1446,7 +1567,7 @@ proxy_https_not_supported(doc) ->
proxy_https_not_supported(suite) ->
[];
proxy_https_not_supported(Config) when is_list(Config) ->
- Result = http:request(get, {"https://login.yahoo.com", []}, [], []),
+ Result = httpc:request(get, {"https://login.yahoo.com", []}, [], []),
case Result of
{error, Reason} ->
%% ok so far
@@ -1478,10 +1599,10 @@ http_stream(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, {{_,200,_}, [_ | _], Body}} =
- http:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [], []),
{ok, RequestId} =
- http:request(get, {URL, []}, [], [{sync, false},
+ httpc:request(get, {URL, []}, [], [{sync, false},
{stream, self}]),
receive
@@ -1506,7 +1627,7 @@ http_stream_once(Config) when is_list(Config) ->
"~n Config: ~p", [Config]),
p("http_stream_once -> set ipfamily to inet", []),
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
p("http_stream_once -> start dummy server", []),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
@@ -1521,18 +1642,18 @@ http_stream_once(Config) when is_list(Config) ->
p("http_stream_once -> stop dummy server", []),
DummyServerPid ! stop,
p("http_stream_once -> set ipfamily to inet6fb4", []),
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
p("http_stream_once -> done", []),
ok.
once(URL) ->
p("once -> issue sync request for ~p", [URL]),
{ok, {{_,200,_}, [_ | _], Body}} =
- http:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [], []),
p("once -> issue async (self stream) request for ~p", [URL]),
{ok, RequestId} =
- http:request(get, {URL, []}, [], [{sync, false},
+ httpc:request(get, {URL, []}, [], [{sync, false},
{stream, {self, once}}]),
p("once -> await stream_start reply for (async) request ~p", [RequestId]),
@@ -1576,10 +1697,10 @@ proxy_stream(Config) when is_list(Config) ->
case ?config(skip, Config) of
undefined ->
{ok, {{_,200,_}, [_ | _], Body}} =
- http:request(get, {?PROXY_URL, []}, [], []),
+ httpc:request(get, {?PROXY_URL, []}, [], []),
{ok, RequestId} =
- http:request(get, {?PROXY_URL, []}, [],
+ httpc:request(get, {?PROXY_URL, []}, [],
[{sync, false}, {stream, self}]),
receive
@@ -1659,7 +1780,7 @@ ipv6(Config) when is_list(Config) ->
URL = "http://[" ++ ?IPV6_LOCAL_HOST ++ "]:" ++
integer_to_list(Port) ++ "/foobar.html",
{ok, {{_,200,_}, [_ | _], [_|_]}} =
- http:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [], []),
DummyServerPid ! stop,
ok;
@@ -1677,11 +1798,11 @@ headers_as_is(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, {{_,200,_}, [_|_], [_|_]}} =
- http:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]},
+ httpc:request(get, {URL, [{"Host", "localhost"},{"Te", ""}]},
[], [{headers_as_is, true}]),
{ok, {{_,400,_}, [_|_], [_|_]}} =
- http:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]),
+ httpc:request(get, {URL, [{"Te", ""}]},[], [{headers_as_is, true}]),
ok.
@@ -1696,13 +1817,13 @@ options(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html",
{ok, {{_,200,_}, [_ | _], Bin}}
- = http:request(get, {URL, []}, [{foo, bar}],
+ = httpc:request(get, {URL, []}, [{foo, bar}],
%% Ignore unknown options
[{body_format, binary}, {foo, bar}]),
true = is_binary(Bin),
{ok, {200, [_|_]}}
- = http:request(get, {URL, []}, [{timeout, infinity}],
+ = httpc:request(get, {URL, []}, [{timeout, infinity}],
[{full_result, false}]);
_ ->
{skip, "Failed to start local http-server"}
@@ -1715,17 +1836,17 @@ http_invalid_http(doc) ->
http_invalid_http(suite) ->
[];
http_invalid_http(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/invalid_http.html",
{error, {could_not_parse_as_http, _} = Reason} =
- http:request(get, {URL, []}, [], []),
+ httpc:request(get, {URL, []}, [], []),
test_server:format("Parse error: ~p ~n", [Reason]),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1762,7 +1883,7 @@ empty_body_otp_6243(Config) when is_list(Config) ->
Port = ?config(local_port, Config),
URL = ?URL_START ++ integer_to_list(Port) ++ "/empty.html",
{ok, {{_,200,_}, [_ | _], []}} =
- http:request(get, {URL, []}, [{timeout, 500}], []).
+ httpc:request(get, {URL, []}, [{timeout, 500}], []).
%%-------------------------------------------------------------------------
@@ -1772,14 +1893,14 @@ transfer_encoding_otp_6807(doc) ->
transfer_encoding_otp_6807(suite) ->
[];
transfer_encoding_otp_6807(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++
"/capital_transfer_encoding.html",
- {ok, {{_,200,_}, [_|_], [_ | _]}} = http:request(URL),
+ {ok, {{_,200,_}, [_|_], [_ | _]}} = httpc:request(URL),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1805,13 +1926,13 @@ empty_response_header_otp_6830(doc) ->
empty_response_header_otp_6830(suite) ->
[];
empty_response_header_otp_6830(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/no_headers.html",
- {ok, {{_,200,_}, [], [_ | _]}} = http:request(URL),
+ {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(URL),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1822,13 +1943,13 @@ no_content_204_otp_6982(doc) ->
no_content_204_otp_6982(suite) ->
[];
no_content_204_otp_6982(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/no_content.html",
- {ok, {{_,204,_}, [], []}} = http:request(URL),
+ {ok, {{_,204,_}, [], []}} = httpc:request(URL),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1840,13 +1961,13 @@ missing_CR_otp_7304(doc) ->
missing_CR_otp_7304(suite) ->
[];
missing_CR_otp_7304(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_CR.html",
- {ok, {{_,200,_}, _, [_ | _]}} = http:request(URL),
+ {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(URL),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1860,15 +1981,15 @@ otp_7883_1(doc) ->
otp_7883_1(suite) ->
[];
otp_7883_1(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html",
- {error, socket_closed_remotely} = http:request(URL),
+ {error, socket_closed_remotely} = httpc:request(URL),
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
otp_7883_2(doc) ->
@@ -1876,7 +1997,7 @@ otp_7883_2(doc) ->
otp_7883_2(suite) ->
[];
otp_7883_2(Config) when is_list(Config) ->
- ok = http:set_options([{ipfamily, inet}]),
+ ok = httpc:set_options([{ipfamily, inet}]),
{DummyServerPid, Port} = dummy_server(self(), ipv4),
@@ -1885,9 +2006,9 @@ otp_7883_2(Config) when is_list(Config) ->
Request = {URL, []},
HttpOptions = [],
Options = [{sync, false}],
- Profile = http:default_profile(),
+ Profile = httpc:default_profile(),
{ok, RequestId} =
- http:request(Method, Request, HttpOptions, Options, Profile),
+ httpc:request(Method, Request, HttpOptions, Options, Profile),
ok =
receive
{http, {RequestId, {error, socket_closed_remotely}}} ->
@@ -1895,7 +2016,7 @@ otp_7883_2(Config) when is_list(Config) ->
end,
DummyServerPid ! stop,
- ok = http:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
+ ok = httpc:set_options([{ipfamily, inet6fb4}]), % ********** ipfamily = inet6 *************
ok.
@@ -1966,7 +2087,7 @@ run_clients(NumClients, ServerPort, SeqNumServer) ->
fun() ->
io:format("[~w] client started - "
"issue request~n", [Id]),
- case http:request(Url) of
+ case httpc:request(Url) of
{ok, {{_,200,_}, _, Resp}} ->
io:format("[~w] 200 response: "
"~p~n", [Id, Resp]),
@@ -2354,7 +2475,7 @@ otp_8352(Config) when is_list(Config) ->
ConnOptions = [{max_sessions, MaxSessions},
{max_keep_alive_length, MaxKeepAlive},
{keep_alive_timeout, KeepAliveTimeout}],
- http:set_options(ConnOptions),
+ httpc:set_options(ConnOptions),
Method = get,
Port = ?config(local_port, Config),
@@ -2366,9 +2487,9 @@ otp_8352(Config) when is_list(Config) ->
Options1 = [{socket_opts, [{tos, 87},
{recbuf, 16#FFFF},
{sndbuf, 16#FFFF}]}],
- case http:request(Method, Request, HttpOptions1, Options1) of
+ case httpc:request(Method, Request, HttpOptions1, Options1) of
{ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} ->
- %% equivaliant to http:request(get, {URL, []}, [], []),
+ %% equivaliant to httpc:request(get, {URL, []}, [], []),
inets_test_lib:check_body(ReplyBody1);
{ok, UnexpectedReply1} ->
tsf({unexpected_reply, UnexpectedReply1});
@@ -2382,9 +2503,9 @@ otp_8352(Config) when is_list(Config) ->
Options2 = [{socket_opts, [{tos, 84},
{recbuf, 32#1FFFF},
{sndbuf, 32#1FFFF}]}],
- case http:request(Method, Request, HttpOptions2, Options2) of
+ case httpc:request(Method, Request, HttpOptions2, Options2) of
{ok, {{_,200,_}, [_ | _], ReplyBody2 = [_ | _]}} ->
- %% equivaliant to http:request(get, {URL, []}, [], []),
+ %% equivaliant to httpc:request(get, {URL, []}, [], []),
inets_test_lib:check_body(ReplyBody2);
{ok, UnexpectedReply2} ->
tsf({unexpected_reply, UnexpectedReply2});
@@ -2406,13 +2527,13 @@ otp_8371(doc) ->
otp_8371(suite) ->
[];
otp_8371(Config) when is_list(Config) ->
- ok = http:set_options([{ipv6, disabled}]), % also test the old option
+ ok = httpc:set_options([{ipv6, disabled}]), % also test the old option
{DummyServerPid, Port} = dummy_server(self(), ipv4),
URL = ?URL_START ++ integer_to_list(Port) ++
"/ensure_host_header_with_port.html",
- case http:request(get, {URL, []}, [], []) of
+ case httpc:request(get, {URL, []}, [], []) of
{ok, Result} ->
case Result of
{{_, 200, _}, _Headers, Body} ->
@@ -2436,7 +2557,7 @@ otp_8371(Config) when is_list(Config) ->
end,
DummyServerPid ! stop,
- ok = http:set_options([{ipv6, enabled}]),
+ ok = httpc:set_options([{ipv6, enabled}]),
ok.
@@ -2537,7 +2658,7 @@ receive_streamed_body(RequestId, Body) ->
end.
receive_streamed_body(RequestId, Body, Pid) ->
- http:stream_next(Pid),
+ httpc:stream_next(Pid),
test_server:format("~p:receive_streamed_body -> requested next stream ~n", [?MODULE]),
receive
{http, {RequestId, stream, BinBodyPart}} ->
@@ -2921,11 +3042,11 @@ provocate_not_modified_bug(Url) ->
Timeout = 15000, %% 15s should be plenty
{ok, {{_, 200, _}, ReplyHeaders, _Body}} =
- http:request(get, {Url, []}, [{timeout, Timeout}], []),
+ httpc:request(get, {Url, []}, [{timeout, Timeout}], []),
Etag = pick_header(ReplyHeaders, "ETag"),
Last = pick_header(ReplyHeaders, "last-modified"),
- case http:request(get, {Url, [{"If-None-Match", Etag},
+ case httpc:request(get, {Url, [{"If-None-Match", Etag},
{"If-Modified-Since", Last}]},
[{timeout, 15000}],
[]) of
diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl
index 7403d4a643..3255cbec06 100644
--- a/lib/inets/test/httpd_SUITE.erl
+++ b/lib/inets/test/httpd_SUITE.erl
@@ -32,44 +32,176 @@
init_per_suite/1, end_per_suite/1]).
%% Test cases must be exported.
--export([ip/1, ssl/1, http_1_1_ip/1, http_1_0_ip/1, http_0_9_ip/1,
- ipv6/1, tickets/1]).
+-export([
+ ip/1,
+ ssl/1, pssl/1, ossl/1, essl/1,
+ http_1_1_ip/1,
+ http_1_0_ip/1,
+ http_0_9_ip/1,
+ ipv6/1,
+ tickets/1
+ ]).
%% Core Server tests
--export([ip_mod_alias/1, ip_mod_actions/1, ip_mod_security/1, ip_mod_auth/1,
- ip_mod_auth_api/1, ip_mod_auth_mnesia_api/1,
- ip_mod_htaccess/1, ip_mod_cgi/1, ip_mod_esi/1,
- ip_mod_get/1, ip_mod_head/1, ip_mod_all/1, ip_load_light/1,
- ip_load_medium/1, ip_load_heavy/1, ip_dos_hostname/1,
- ip_time_test/1, ip_block_disturbing_idle/1,
- ip_block_non_disturbing_idle/1, ip_block_503/1,
- ip_block_disturbing_active/1, ip_block_non_disturbing_active/1,
+-export([
+ ip_mod_alias/1,
+ ip_mod_actions/1,
+ ip_mod_security/1,
+ ip_mod_auth/1,
+ ip_mod_auth_api/1,
+ ip_mod_auth_mnesia_api/1,
+ ip_mod_htaccess/1,
+ ip_mod_cgi/1,
+ ip_mod_esi/1,
+ ip_mod_get/1,
+ ip_mod_head/1,
+ ip_mod_all/1,
+ ip_load_light/1,
+ ip_load_medium/1,
+ ip_load_heavy/1,
+ ip_dos_hostname/1,
+ ip_time_test/1,
+ ip_block_disturbing_idle/1,
+ ip_block_non_disturbing_idle/1,
+ ip_block_503/1,
+ ip_block_disturbing_active/1,
+ ip_block_non_disturbing_active/1,
ip_block_disturbing_active_timeout_not_released/1,
ip_block_disturbing_active_timeout_released/1,
ip_block_non_disturbing_active_timeout_not_released/1,
ip_block_non_disturbing_active_timeout_released/1,
ip_block_disturbing_blocker_dies/1,
ip_block_non_disturbing_blocker_dies/1,
- ip_restart_no_block/1, ip_restart_disturbing_block/1,
+ ip_restart_no_block/1,
+ ip_restart_disturbing_block/1,
ip_restart_non_disturbing_block/1
]).
--export([ssl_mod_alias/1, ssl_mod_actions/1, ssl_mod_security/1,
- ssl_mod_auth/1, ssl_mod_auth_api/1,
- ssl_mod_auth_mnesia_api/1, ssl_mod_htaccess/1,
- ssl_mod_cgi/1, ssl_mod_esi/1, ssl_mod_get/1, ssl_mod_head/1,
- ssl_mod_all/1, ssl_load_light/1, ssl_load_medium/1,
- ssl_load_heavy/1, ssl_dos_hostname/1, ssl_time_test/1,
- ssl_restart_no_block/1, ssl_restart_disturbing_block/1,
- ssl_restart_non_disturbing_block/1, ssl_block_disturbing_idle/1,
- ssl_block_non_disturbing_idle/1, ssl_block_503/1,
- ssl_block_disturbing_active/1, ssl_block_non_disturbing_active/1,
- ssl_block_disturbing_active_timeout_not_released/1,
- ssl_block_disturbing_active_timeout_released/1,
- ssl_block_non_disturbing_active_timeout_not_released/1,
- ssl_block_non_disturbing_active_timeout_released/1,
- ssl_block_disturbing_blocker_dies/1,
- ssl_block_non_disturbing_blocker_dies/1]).
+-export([
+ pssl_mod_alias/1,
+ ossl_mod_alias/1,
+ essl_mod_alias/1,
+
+ pssl_mod_actions/1,
+ ossl_mod_actions/1,
+ essl_mod_actions/1,
+
+ pssl_mod_security/1,
+ ossl_mod_security/1,
+ essl_mod_security/1,
+
+ pssl_mod_auth/1,
+ ossl_mod_auth/1,
+ essl_mod_auth/1,
+
+ pssl_mod_auth_api/1,
+ ossl_mod_auth_api/1,
+ essl_mod_auth_api/1,
+
+ pssl_mod_auth_mnesia_api/1,
+ ossl_mod_auth_mnesia_api/1,
+ essl_mod_auth_mnesia_api/1,
+
+ pssl_mod_htaccess/1,
+ ossl_mod_htaccess/1,
+ essl_mod_htaccess/1,
+
+ pssl_mod_cgi/1,
+ ossl_mod_cgi/1,
+ essl_mod_cgi/1,
+
+ pssl_mod_esi/1,
+ ossl_mod_esi/1,
+ essl_mod_esi/1,
+
+ pssl_mod_get/1,
+ ossl_mod_get/1,
+ essl_mod_get/1,
+
+ pssl_mod_head/1,
+ ossl_mod_head/1,
+ essl_mod_head/1,
+
+ pssl_mod_all/1,
+ ossl_mod_all/1,
+ essl_mod_all/1,
+
+ pssl_load_light/1,
+ ossl_load_light/1,
+ essl_load_light/1,
+
+ pssl_load_medium/1,
+ ossl_load_medium/1,
+ essl_load_medium/1,
+
+ pssl_load_heavy/1,
+ ossl_load_heavy/1,
+ essl_load_heavy/1,
+
+ pssl_dos_hostname/1,
+ ossl_dos_hostname/1,
+ essl_dos_hostname/1,
+
+ pssl_time_test/1,
+ ossl_time_test/1,
+ essl_time_test/1,
+
+ pssl_restart_no_block/1,
+ ossl_restart_no_block/1,
+ essl_restart_no_block/1,
+
+ pssl_restart_disturbing_block/1,
+ ossl_restart_disturbing_block/1,
+ essl_restart_disturbing_block/1,
+
+ pssl_restart_non_disturbing_block/1,
+ ossl_restart_non_disturbing_block/1,
+ essl_restart_non_disturbing_block/1,
+
+ pssl_block_disturbing_idle/1,
+ ossl_block_disturbing_idle/1,
+ essl_block_disturbing_idle/1,
+
+ pssl_block_non_disturbing_idle/1,
+ ossl_block_non_disturbing_idle/1,
+ essl_block_non_disturbing_idle/1,
+
+ pssl_block_503/1,
+ ossl_block_503/1,
+ essl_block_503/1,
+
+ pssl_block_disturbing_active/1,
+ ossl_block_disturbing_active/1,
+ essl_block_disturbing_active/1,
+
+ pssl_block_non_disturbing_active/1,
+ ossl_block_non_disturbing_active/1,
+ essl_block_non_disturbing_active/1,
+
+ pssl_block_disturbing_active_timeout_not_released/1,
+ ossl_block_disturbing_active_timeout_not_released/1,
+ essl_block_disturbing_active_timeout_not_released/1,
+
+ pssl_block_disturbing_active_timeout_released/1,
+ ossl_block_disturbing_active_timeout_released/1,
+ essl_block_disturbing_active_timeout_released/1,
+
+ pssl_block_non_disturbing_active_timeout_not_released/1,
+ ossl_block_non_disturbing_active_timeout_not_released/1,
+ essl_block_non_disturbing_active_timeout_not_released/1,
+
+ pssl_block_non_disturbing_active_timeout_released/1,
+ ossl_block_non_disturbing_active_timeout_released/1,
+ essl_block_non_disturbing_active_timeout_released/1,
+
+ pssl_block_disturbing_blocker_dies/1,
+ ossl_block_disturbing_blocker_dies/1,
+ essl_block_disturbing_blocker_dies/1,
+
+ pssl_block_non_disturbing_blocker_dies/1,
+ ossl_block_non_disturbing_blocker_dies/1,
+ essl_block_non_disturbing_blocker_dies/1
+ ]).
%%% HTTP 1.1 tests
-export([ip_host/1, ip_chunked/1, ip_expect/1, ip_range/1,
@@ -103,8 +235,8 @@
%% Seconds before successful auths timeout.
-define(AUTH_TIMEOUT,5).
--record(httpd_user, {user_name, password, user_data}).
--record(httpd_group,{group_name, userlist}).
+-record(httpd_user, {user_name, password, user_data}).
+-record(httpd_group, {group_name, userlist}).
%%--------------------------------------------------------------------
@@ -197,9 +329,9 @@ init_per_testcase2(Case, Config) ->
"~n Config: ~p"
"~n", [?MODULE, Case, Config]),
- IpNormal = integer_to_list(?IP_PORT) ++ ".conf",
- IpHtacess = integer_to_list(?IP_PORT) ++ "htacess.conf",
- SslNormal = integer_to_list(?SSL_PORT) ++ ".conf",
+ IpNormal = integer_to_list(?IP_PORT) ++ ".conf",
+ IpHtacess = integer_to_list(?IP_PORT) ++ "htacess.conf",
+ SslNormal = integer_to_list(?SSL_PORT) ++ ".conf",
SslHtacess = integer_to_list(?SSL_PORT) ++ "htacess.conf",
DataDir = ?config(data_dir, Config),
@@ -210,8 +342,8 @@ init_per_testcase2(Case, Config) ->
"~n DataDir: ~p"
"~n", [?MODULE, Case, SuiteTopDir, DataDir]),
- TcTopDir = filename:join(SuiteTopDir, Case),
- ?line ok = file:make_dir(TcTopDir),
+ TcTopDir = filename:join(SuiteTopDir, Case),
+ ?line ok = file:make_dir(TcTopDir),
io:format(user, "~w:init_per_testcase2(~w) -> "
"~n TcTopDir: ~p"
@@ -267,9 +399,21 @@ init_per_testcase2(Case, Config) ->
%% To be used by SSL test cases
io:format(user, "~w:init_per_testcase2(~w) -> ssl testcase setups~n",
[?MODULE, Case]),
- create_config([{port, ?SSL_PORT}, {sock_type, ssl} | NewConfig],
+ SocketType =
+ case atom_to_list(Case) of
+ [X, $s, $s, $l | _] ->
+ case X of
+ $p -> ssl;
+ $o -> ossl;
+ $e -> essl
+ end;
+ _ ->
+ ssl
+ end,
+
+ create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig],
normal_acess, SslNormal),
- create_config([{port, ?SSL_PORT}, {sock_type, ssl} | NewConfig],
+ create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig],
mod_htaccess, SslHtacess),
%% To be used by IPv6 test cases. Case-clause is so that
@@ -300,8 +444,14 @@ init_per_testcase3(Case, Config) ->
io:format(user, "~w:init_per_testcase3(~w) -> entry with"
"~n Config: ~p", [?MODULE, Case, Config]),
+
+%% %% Create a new fresh node to be used by the server in this test-case
+
+%% NodeName = list_to_atom(atom_to_list(Case) ++ "_httpd"),
+%% Node = inets_test_lib:start_node(NodeName),
+
%% Clean up (we do not want this clean up in end_per_testcase
- %% if init_per_testcase crases for some testcase it will
+ %% if init_per_testcase crashes for some testcase it will
%% have contaminated the environment and there will be no clean up.)
%% This init can take a few different paths so that one crashes
%% does not mean that all invocations will.
@@ -310,15 +460,26 @@ init_per_testcase3(Case, Config) ->
application:stop(inets),
application:stop(ssl),
cleanup_mnesia(),
-
- %% TraceLevel = max,
- TraceLevel = 70,
- TraceDest = io,
- inets:enable_trace(TraceLevel, TraceDest),
+ %% Set trace
+ case lists:reverse(atom_to_list(Case)) of
+ "tset_emit" ++ _Rest -> % test-cases ending with time_test
+ io:format(user, "~w:init_per_testcase3(~w) -> disabling trace",
+ [?MODULE, Case]),
+ inets:disable_trace();
+ _ ->
+ %% TraceLevel = max,
+ io:format(user, "~w:init_per_testcase3(~w) -> enabling trace",
+ [?MODULE, Case]),
+ TraceLevel = 70,
+ TraceDest = io,
+ inets:enable_trace(TraceLevel, TraceDest, httpd)
+ end,
+
%% Start initialization
io:format(user, "~w:init_per_testcase3(~w) -> start init",
[?MODULE, Case]),
+
Dog = test_server:timetrap(inets_test_lib:minutes(10)),
NewConfig = lists:keydelete(watchdog, 1, Config),
@@ -351,22 +512,35 @@ init_per_testcase3(Case, Config) ->
filename:join(TcTopDir,
integer_to_list(?IP_PORT) ++ ".conf")}]),
Rest;
- "ssl_mod_htaccess" ->
+
+ [X, $s, $s, $l, $_, $m, $o, $d, $_, $h, $t, $a, $c, $c, $e, $s, $s] ->
+ SslTag =
+ case X of
+ $p -> ssl; % plain
+ $o -> ossl; % OpenSSL based ssl
+ $e -> essl % Erlang based ssl
+ end,
case inets_test_lib:start_http_server_ssl(
filename:join(TcTopDir,
integer_to_list(?SSL_PORT) ++
- "htacess.conf")) of
+ "htacess.conf"), SslTag) of
ok ->
"mod_htaccess";
Other ->
error_logger:info_report("Other: ~p~n", [Other]),
{skip, "SSL does not seem to be supported"}
end;
- "ssl_" ++ Rest ->
+ [X, $s, $s, $l, $_ | Rest] ->
+ SslTag =
+ case X of
+ $p -> ssl;
+ $o -> ossl;
+ $e -> essl
+ end,
case inets_test_lib:start_http_server_ssl(
filename:join(TcTopDir,
integer_to_list(?SSL_PORT) ++
- ".conf")) of
+ ".conf"), SslTag) of
ok ->
Rest;
Other ->
@@ -431,6 +605,7 @@ end_per_testcase2(Case, Config) ->
application:unset_env(inets, services),
application:stop(inets),
application:stop(ssl),
+ application:stop(crypto), % used by the new ssl (essl test cases)
cleanup_mnesia(),
io:format(user, "~w:end_per_testcase2(~w) -> done~n",
[?MODULE, Case]),
@@ -461,6 +636,9 @@ ip(suite) ->
ip_load_heavy,
ip_dos_hostname,
ip_time_test,
+ ip_restart_no_block,
+ ip_restart_disturbing_block,
+ ip_restart_non_disturbing_block,
ip_block_disturbing_idle,
ip_block_non_disturbing_idle,
ip_block_503,
@@ -471,10 +649,7 @@ ip(suite) ->
ip_block_non_disturbing_active_timeout_not_released,
ip_block_non_disturbing_active_timeout_released,
ip_block_disturbing_blocker_dies,
- ip_block_non_disturbing_blocker_dies,
- ip_restart_no_block,
- ip_restart_disturbing_block,
- ip_restart_non_disturbing_block
+ ip_block_non_disturbing_blocker_dies
].
%%-------------------------------------------------------------------------
@@ -482,39 +657,124 @@ ssl(doc) ->
["HTTP test using SSL"];
ssl(suite) ->
[
- ssl_mod_alias,
- ssl_mod_actions,
- ssl_mod_security,
- ssl_mod_auth,
- ssl_mod_auth_api,
- ssl_mod_auth_mnesia_api,
- ssl_mod_htaccess,
- ssl_mod_cgi,
- ssl_mod_esi,
- ssl_mod_get,
- ssl_mod_head,
- ssl_mod_all,
- ssl_load_light,
- ssl_load_medium,
- ssl_load_heavy,
- ssl_dos_hostname,
- ssl_time_test,
- ssl_restart_no_block,
- ssl_restart_disturbing_block,
- ssl_restart_non_disturbing_block,
- ssl_block_disturbing_idle,
- ssl_block_non_disturbing_idle,
- ssl_block_503,
- ssl_block_disturbing_active,
- ssl_block_non_disturbing_active,
- ssl_block_disturbing_active_timeout_not_released,
- ssl_block_disturbing_active_timeout_released,
- ssl_block_non_disturbing_active_timeout_not_released,
- ssl_block_non_disturbing_active_timeout_released,
- ssl_block_disturbing_blocker_dies,
- ssl_block_non_disturbing_blocker_dies
+ pssl,
+ ossl,
+ essl
].
+
+pssl(doc) ->
+ ["HTTP test using SSL - using old way of configuring SSL"];
+pssl(suite) ->
+ [
+ pssl_mod_alias,
+ pssl_mod_actions,
+ pssl_mod_security,
+ pssl_mod_auth,
+ pssl_mod_auth_api,
+ pssl_mod_auth_mnesia_api,
+ pssl_mod_htaccess,
+ pssl_mod_cgi,
+ pssl_mod_esi,
+ pssl_mod_get,
+ pssl_mod_head,
+ pssl_mod_all,
+ pssl_load_light,
+ pssl_load_medium,
+ pssl_load_heavy,
+ pssl_dos_hostname,
+ pssl_time_test,
+ pssl_restart_no_block,
+ pssl_restart_disturbing_block,
+ pssl_restart_non_disturbing_block,
+ pssl_block_disturbing_idle,
+ pssl_block_non_disturbing_idle,
+ pssl_block_503,
+ pssl_block_disturbing_active,
+ pssl_block_non_disturbing_active,
+ pssl_block_disturbing_active_timeout_not_released,
+ pssl_block_disturbing_active_timeout_released,
+ pssl_block_non_disturbing_active_timeout_not_released,
+ pssl_block_non_disturbing_active_timeout_released,
+ pssl_block_disturbing_blocker_dies,
+ pssl_block_non_disturbing_blocker_dies
+ ].
+
+ossl(doc) ->
+ ["HTTP test using SSL - using new way of configuring usage of old SSL"];
+ossl(suite) ->
+ [
+ ossl_mod_alias,
+ ossl_mod_actions,
+ ossl_mod_security,
+ ossl_mod_auth,
+ ossl_mod_auth_api,
+ ossl_mod_auth_mnesia_api,
+ ossl_mod_htaccess,
+ ossl_mod_cgi,
+ ossl_mod_esi,
+ ossl_mod_get,
+ ossl_mod_head,
+ ossl_mod_all,
+ ossl_load_light,
+ ossl_load_medium,
+ ossl_load_heavy,
+ ossl_dos_hostname,
+ ossl_time_test,
+ ossl_restart_no_block,
+ ossl_restart_disturbing_block,
+ ossl_restart_non_disturbing_block,
+ ossl_block_disturbing_idle,
+ ossl_block_non_disturbing_idle,
+ ossl_block_503,
+ ossl_block_disturbing_active,
+ ossl_block_non_disturbing_active,
+ ossl_block_disturbing_active_timeout_not_released,
+ ossl_block_disturbing_active_timeout_released,
+ ossl_block_non_disturbing_active_timeout_not_released,
+ ossl_block_non_disturbing_active_timeout_released,
+ ossl_block_disturbing_blocker_dies,
+ ossl_block_non_disturbing_blocker_dies
+ ].
+
+essl(doc) ->
+ ["HTTP test using SSL - using new way of configuring usage of new SSL"];
+essl(suite) ->
+ [
+ essl_mod_alias,
+ essl_mod_actions,
+ essl_mod_security,
+ essl_mod_auth,
+ essl_mod_auth_api,
+ essl_mod_auth_mnesia_api,
+ essl_mod_htaccess,
+ essl_mod_cgi,
+ essl_mod_esi,
+ essl_mod_get,
+ essl_mod_head,
+ essl_mod_all,
+ essl_load_light,
+ essl_load_medium,
+ essl_load_heavy,
+ essl_dos_hostname,
+ essl_time_test,
+ essl_restart_no_block,
+ essl_restart_disturbing_block,
+ essl_restart_non_disturbing_block,
+ essl_block_disturbing_idle,
+ essl_block_non_disturbing_idle,
+ essl_block_503,
+ essl_block_disturbing_active,
+ essl_block_non_disturbing_active,
+ essl_block_disturbing_active_timeout_not_released,
+ essl_block_disturbing_active_timeout_released,
+ essl_block_non_disturbing_active_timeout_not_released,
+ essl_block_non_disturbing_active_timeout_released,
+ essl_block_disturbing_blocker_dies,
+ essl_block_non_disturbing_blocker_dies
+ ].
+
+
%%-------------------------------------------------------------------------
http_1_1_ip(doc) ->
["HTTP/1.1"];
@@ -721,6 +981,8 @@ ip_load_heavy(Config) when is_list(Config) ->
?config(node, Config),
get_nof_clients(ip_comm, heavy)),
ok.
+
+
%%-------------------------------------------------------------------------
ip_dos_hostname(doc) ->
["Denial Of Service (DOS) attack test case"];
@@ -730,6 +992,8 @@ ip_dos_hostname(Config) when is_list(Config) ->
dos_hostname(ip_comm, ?IP_PORT, ?config(host, Config),
?config(node, Config), ?MAX_HEADER_SIZE),
ok.
+
+
%%-------------------------------------------------------------------------
ip_time_test(doc) ->
[""];
@@ -966,363 +1230,1072 @@ ip_restart_non_disturbing_block(Config) when is_list(Config) ->
ok.
%%-------------------------------------------------------------------------
-ssl_mod_alias(doc) ->
- ["Module test: mod_alias"];
-ssl_mod_alias(suite) ->
+
+pssl_mod_alias(doc) ->
+ ["Module test: mod_alias - old SSL config"];
+pssl_mod_alias(suite) ->
+ [];
+pssl_mod_alias(Config) when is_list(Config) ->
+ ssl_mod_alias(ssl, Config).
+
+ossl_mod_alias(doc) ->
+ ["Module test: mod_alias - using new of configure old SSL"];
+ossl_mod_alias(suite) ->
+ [];
+ossl_mod_alias(Config) when is_list(Config) ->
+ ssl_mod_alias(ossl, Config).
+
+essl_mod_alias(doc) ->
+ ["Module test: mod_alias - using new of configure new SSL"];
+essl_mod_alias(suite) ->
[];
-ssl_mod_alias(Config) when is_list(Config) ->
- httpd_mod:alias(ssl, ?SSL_PORT,
+essl_mod_alias(Config) when is_list(Config) ->
+ ssl_mod_alias(essl, Config).
+
+
+ssl_mod_alias(Tag, Config) ->
+ httpd_mod:alias(Tag, ?SSL_PORT,
?config(host, Config), ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_actions(doc) ->
- ["Module test: mod_actions"];
-ssl_mod_actions(suite) ->
+
+pssl_mod_actions(doc) ->
+ ["Module test: mod_actions - old SSL config"];
+pssl_mod_actions(suite) ->
[];
-ssl_mod_actions(Config) when is_list(Config) ->
- httpd_mod:actions(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_actions(Config) when is_list(Config) ->
+ ssl_mod_actions(ssl, Config).
+
+ossl_mod_actions(doc) ->
+ ["Module test: mod_actions - using new of configure old SSL"];
+ossl_mod_actions(suite) ->
+ [];
+ossl_mod_actions(Config) when is_list(Config) ->
+ ssl_mod_actions(ossl, Config).
+
+essl_mod_actions(doc) ->
+ ["Module test: mod_actions - using new of configure new SSL"];
+essl_mod_actions(suite) ->
+ [];
+essl_mod_actions(Config) when is_list(Config) ->
+ ssl_mod_actions(essl, Config).
+
+
+ssl_mod_actions(Tag, Config) ->
+ httpd_mod:actions(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_security(doc) ->
- ["Module test: mod_security"];
-ssl_mod_security(suite) ->
+
+pssl_mod_security(doc) ->
+ ["Module test: mod_security - old SSL config"];
+pssl_mod_security(suite) ->
[];
-ssl_mod_security(Config) when is_list(Config) ->
+pssl_mod_security(Config) when is_list(Config) ->
+ ssl_mod_security(ssl, Config).
+
+ossl_mod_security(doc) ->
+ ["Module test: mod_security - using new of configure old SSL"];
+ossl_mod_security(suite) ->
+ [];
+ossl_mod_security(Config) when is_list(Config) ->
+ ssl_mod_security(ossl, Config).
+
+essl_mod_security(doc) ->
+ ["Module test: mod_security - using new of configure new SSL"];
+essl_mod_security(suite) ->
+ [];
+essl_mod_security(Config) when is_list(Config) ->
+ ssl_mod_security(essl, Config).
+
+ssl_mod_security(Tag, Config) ->
ServerRoot = ?config(server_root, Config),
- httpd_mod:security(ServerRoot, ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+ httpd_mod:security(ServerRoot,
+ Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_auth(doc) ->
- ["Module test: mod_auth"];
-ssl_mod_auth(suite) ->
+
+pssl_mod_auth(doc) ->
+ ["Module test: mod_auth - old SSL config"];
+pssl_mod_auth(suite) ->
[];
-ssl_mod_auth(Config) when is_list(Config) ->
- httpd_mod:auth(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_auth(Config) when is_list(Config) ->
+ ssl_mod_auth(ssl, Config).
+
+ossl_mod_auth(doc) ->
+ ["Module test: mod_auth - using new of configure old SSL"];
+ossl_mod_auth(suite) ->
+ [];
+ossl_mod_auth(Config) when is_list(Config) ->
+ ssl_mod_auth(ossl, Config).
+
+essl_mod_auth(doc) ->
+ ["Module test: mod_auth - using new of configure new SSL"];
+essl_mod_auth(suite) ->
+ [];
+essl_mod_auth(Config) when is_list(Config) ->
+ ssl_mod_auth(essl, Config).
+
+ssl_mod_auth(Tag, Config) ->
+ httpd_mod:auth(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_auth_api(doc) ->
- ["Module test: mod_auth"];
-ssl_mod_auth_api(suite) ->
+
+pssl_mod_auth_api(doc) ->
+ ["Module test: mod_auth - old SSL config"];
+pssl_mod_auth_api(suite) ->
+ [];
+pssl_mod_auth_api(Config) when is_list(Config) ->
+ ssl_mod_auth_api(ssl, Config).
+
+ossl_mod_auth_api(doc) ->
+ ["Module test: mod_auth - using new of configure old SSL"];
+ossl_mod_auth_api(suite) ->
+ [];
+ossl_mod_auth_api(Config) when is_list(Config) ->
+ ssl_mod_auth_api(ossl, Config).
+
+essl_mod_auth_api(doc) ->
+ ["Module test: mod_auth - using new of configure new SSL"];
+essl_mod_auth_api(suite) ->
[];
-ssl_mod_auth_api(Config) when is_list(Config) ->
+essl_mod_auth_api(Config) when is_list(Config) ->
+ ssl_mod_auth_api(essl, Config).
+
+ssl_mod_auth_api(Tag, Config) ->
ServerRoot = ?config(server_root, Config),
- Host = ?config(host, Config),
- Node = ?config(node, Config),
- httpd_mod:auth_api(ServerRoot, "", ssl, ?SSL_PORT, Host, Node),
- httpd_mod:auth_api(ServerRoot, "dets_", ssl, ?SSL_PORT, Host, Node),
- httpd_mod:auth_api(ServerRoot, "mnesia_", ssl, ?SSL_PORT, Host, Node),
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ httpd_mod:auth_api(ServerRoot, "", Tag, ?SSL_PORT, Host, Node),
+ httpd_mod:auth_api(ServerRoot, "dets_", Tag, ?SSL_PORT, Host, Node),
+ httpd_mod:auth_api(ServerRoot, "mnesia_", Tag, ?SSL_PORT, Host, Node),
ok.
+
%%-------------------------------------------------------------------------
-ssl_mod_auth_mnesia_api(doc) ->
- ["Module test: mod_auth_mnesia_api"];
-ssl_mod_auth_mnesia_api(suite) ->
+
+pssl_mod_auth_mnesia_api(doc) ->
+ ["Module test: mod_auth_mnesia_api - old SSL config"];
+pssl_mod_auth_mnesia_api(suite) ->
[];
-ssl_mod_auth_mnesia_api(Config) when is_list(Config) ->
- httpd_mod:auth_mnesia_api(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_auth_mnesia_api(Config) when is_list(Config) ->
+ ssl_mod_auth_mnesia_api(ssl, Config).
+
+ossl_mod_auth_mnesia_api(doc) ->
+ ["Module test: mod_auth_mnesia_api - using new of configure old SSL"];
+ossl_mod_auth_mnesia_api(suite) ->
+ [];
+ossl_mod_auth_mnesia_api(Config) when is_list(Config) ->
+ ssl_mod_auth_mnesia_api(ossl, Config).
+
+essl_mod_auth_mnesia_api(doc) ->
+ ["Module test: mod_auth_mnesia_api - using new of configure new SSL"];
+essl_mod_auth_mnesia_api(suite) ->
+ [];
+essl_mod_auth_mnesia_api(Config) when is_list(Config) ->
+ ssl_mod_auth_mnesia_api(essl, Config).
+
+ssl_mod_auth_mnesia_api(Tag, Config) ->
+ httpd_mod:auth_mnesia_api(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_htaccess(doc) ->
- ["Module test: mod_htaccess"];
-ssl_mod_htaccess(suite) ->
+
+pssl_mod_htaccess(doc) ->
+ ["Module test: mod_htaccess - old SSL config"];
+pssl_mod_htaccess(suite) ->
[];
-ssl_mod_htaccess(Config) when is_list(Config) ->
- httpd_mod:htaccess(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_htaccess(Config) when is_list(Config) ->
+ ssl_mod_htaccess(ssl, Config).
+
+ossl_mod_htaccess(doc) ->
+ ["Module test: mod_htaccess - using new of configure old SSL"];
+ossl_mod_htaccess(suite) ->
+ [];
+ossl_mod_htaccess(Config) when is_list(Config) ->
+ ssl_mod_htaccess(ossl, Config).
+
+essl_mod_htaccess(doc) ->
+ ["Module test: mod_htaccess - using new of configure new SSL"];
+essl_mod_htaccess(suite) ->
+ [];
+essl_mod_htaccess(Config) when is_list(Config) ->
+ ssl_mod_htaccess(essl, Config).
+
+ssl_mod_htaccess(Tag, Config) ->
+ httpd_mod:htaccess(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_cgi(doc) ->
- ["Module test: mod_cgi"];
-ssl_mod_cgi(suite) ->
+
+pssl_mod_cgi(doc) ->
+ ["Module test: mod_cgi - old SSL config"];
+pssl_mod_cgi(suite) ->
+ [];
+pssl_mod_cgi(Config) when is_list(Config) ->
+ ssl_mod_cgi(ssl, Config).
+
+ossl_mod_cgi(doc) ->
+ ["Module test: mod_cgi - using new of configure old SSL"];
+ossl_mod_cgi(suite) ->
+ [];
+ossl_mod_cgi(Config) when is_list(Config) ->
+ ssl_mod_cgi(ossl, Config).
+
+essl_mod_cgi(doc) ->
+ ["Module test: mod_cgi - using new of configure new SSL"];
+essl_mod_cgi(suite) ->
[];
-ssl_mod_cgi(Config) when is_list(Config) ->
+essl_mod_cgi(Config) when is_list(Config) ->
+ ssl_mod_cgi(essl, Config).
+
+ssl_mod_cgi(Tag, Config) ->
case test_server:os_type() of
vxworks ->
{skip, cgi_not_supported_on_vxwoks};
_ ->
- httpd_mod:cgi(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+ httpd_mod:cgi(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok
end.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_esi(doc) ->
- ["Module test: mod_esi"];
-ssl_mod_esi(suite) ->
+
+pssl_mod_esi(doc) ->
+ ["Module test: mod_esi - old SSL config"];
+pssl_mod_esi(suite) ->
[];
-ssl_mod_esi(Config) when is_list(Config) ->
- httpd_mod:esi(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_esi(Config) when is_list(Config) ->
+ ssl_mod_esi(ssl, Config).
+
+ossl_mod_esi(doc) ->
+ ["Module test: mod_esi - using new of configure old SSL"];
+ossl_mod_esi(suite) ->
+ [];
+ossl_mod_esi(Config) when is_list(Config) ->
+ ssl_mod_esi(ossl, Config).
+
+essl_mod_esi(doc) ->
+ ["Module test: mod_esi - using new of configure new SSL"];
+essl_mod_esi(suite) ->
+ [];
+essl_mod_esi(Config) when is_list(Config) ->
+ ssl_mod_esi(essl, Config).
+
+ssl_mod_esi(Tag, Config) ->
+ httpd_mod:esi(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
%%-------------------------------------------------------------------------
-ssl_mod_get(doc) ->
- ["Module test: mod_get"];
-ssl_mod_get(suite) ->
+
+pssl_mod_get(doc) ->
+ ["Module test: mod_get - old SSL config"];
+pssl_mod_get(suite) ->
[];
-ssl_mod_get(Config) when is_list(Config) ->
- httpd_mod:get(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_get(Config) when is_list(Config) ->
+ ssl_mod_get(ssl, Config).
+
+ossl_mod_get(doc) ->
+ ["Module test: mod_get - using new of configure old SSL"];
+ossl_mod_get(suite) ->
+ [];
+ossl_mod_get(Config) when is_list(Config) ->
+ ssl_mod_get(ossl, Config).
+
+essl_mod_get(doc) ->
+ ["Module test: mod_get - using new of configure new SSL"];
+essl_mod_get(suite) ->
+ [];
+essl_mod_get(Config) when is_list(Config) ->
+ ssl_mod_get(essl, Config).
+
+ssl_mod_get(Tag, Config) ->
+ httpd_mod:get(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_head(doc) ->
- ["Module test: mod_head"];
-ssl_mod_head(suite) ->
+
+pssl_mod_head(doc) ->
+ ["Module test: mod_head - old SSL config"];
+pssl_mod_head(suite) ->
[];
-ssl_mod_head(Config) when is_list(Config) ->
- httpd_mod:head(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_head(Config) when is_list(Config) ->
+ ssl_mod_head(ssl, Config).
+
+ossl_mod_head(doc) ->
+ ["Module test: mod_head - using new of configure old SSL"];
+ossl_mod_head(suite) ->
+ [];
+ossl_mod_head(Config) when is_list(Config) ->
+ ssl_mod_head(ossl, Config).
+
+essl_mod_head(doc) ->
+ ["Module test: mod_head - using new of configure new SSL"];
+essl_mod_head(suite) ->
+ [];
+essl_mod_head(Config) when is_list(Config) ->
+ ssl_mod_head(essl, Config).
+
+ssl_mod_head(Tag, Config) ->
+ httpd_mod:head(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_mod_all(doc) ->
- ["All modules test"];
-ssl_mod_all(suite) ->
+
+pssl_mod_all(doc) ->
+ ["All modules test - old SSL config"];
+pssl_mod_all(suite) ->
[];
-ssl_mod_all(Config) when is_list(Config) ->
- httpd_mod:all(ssl, ?SSL_PORT,
- ?config(host, Config), ?config(node, Config)),
+pssl_mod_all(Config) when is_list(Config) ->
+ ssl_mod_all(ssl, Config).
+
+ossl_mod_all(doc) ->
+ ["All modules test - using new of configure old SSL"];
+ossl_mod_all(suite) ->
+ [];
+ossl_mod_all(Config) when is_list(Config) ->
+ ssl_mod_all(ossl, Config).
+
+essl_mod_all(doc) ->
+ ["All modules test - using new of configure new SSL"];
+essl_mod_all(suite) ->
+ [];
+essl_mod_all(Config) when is_list(Config) ->
+ ssl_mod_all(essl, Config).
+
+ssl_mod_all(Tag, Config) ->
+ httpd_mod:all(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
%%-------------------------------------------------------------------------
-ssl_load_light(doc) ->
- ["Test light load"];
-ssl_load_light(suite) ->
+
+pssl_load_light(doc) ->
+ ["Test light load - old SSL config"];
+pssl_load_light(suite) ->
+ [];
+pssl_load_light(Config) when is_list(Config) ->
+ ssl_load_light(ssl, Config).
+
+ossl_load_light(doc) ->
+ ["Test light load - using new of configure old SSL"];
+ossl_load_light(suite) ->
+ [];
+ossl_load_light(Config) when is_list(Config) ->
+ ssl_load_light(ossl, Config).
+
+essl_load_light(doc) ->
+ ["Test light load - using new of configure new SSL"];
+essl_load_light(suite) ->
[];
-ssl_load_light(Config) when is_list(Config) ->
- httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config),
+essl_load_light(Config) when is_list(Config) ->
+ ssl_load_light(essl, Config).
+
+ssl_load_light(Tag, Config) ->
+ httpd_load:load_test(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
?config(node, Config),
get_nof_clients(ssl, light)),
ok.
+
%%-------------------------------------------------------------------------
-ssl_load_medium(doc) ->
- ["Test medium load"];
-ssl_load_medium(suite) ->
+
+pssl_load_medium(doc) ->
+ ["Test medium load - old SSL config"];
+pssl_load_medium(suite) ->
+ [];
+pssl_load_medium(Config) when is_list(Config) ->
+ ssl_load_medium(ssl, Config).
+
+ossl_load_medium(doc) ->
+ ["Test medium load - using new of configure old SSL"];
+ossl_load_medium(suite) ->
[];
-ssl_load_medium(Config) when is_list(Config) ->
+ossl_load_medium(Config) when is_list(Config) ->
+ ssl_load_medium(ossl, Config).
+
+essl_load_medium(doc) ->
+ ["Test medium load - using new of configure new SSL"];
+essl_load_medium(suite) ->
+ [];
+essl_load_medium(Config) when is_list(Config) ->
+ ssl_load_medium(essl, Config).
+
+ssl_load_medium(Tag, Config) ->
%% <CONDITIONAL-SKIP>
Skippable = [win32],
Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
?NON_PC_TC_MAYBE_SKIP(Config, Condition),
%% </CONDITIONAL-SKIP>
- httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config),
+ httpd_load:load_test(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
?config(node, Config),
get_nof_clients(ssl, medium)),
ok.
+
%%-------------------------------------------------------------------------
-ssl_load_heavy(doc) ->
- ["Test heavy load"];
-ssl_load_heavy(suite) ->
+
+pssl_load_heavy(doc) ->
+ ["Test heavy load - old SSL config"];
+pssl_load_heavy(suite) ->
[];
-ssl_load_heavy(Config) when is_list(Config) ->
+pssl_load_heavy(Config) when is_list(Config) ->
+ ssl_load_heavy(ssl, Config).
+
+ossl_load_heavy(doc) ->
+ ["Test heavy load - using new of configure old SSL"];
+ossl_load_heavy(suite) ->
+ [];
+ossl_load_heavy(Config) when is_list(Config) ->
+ ssl_load_heavy(ossl, Config).
+
+essl_load_heavy(doc) ->
+ ["Test heavy load - using new of configure new SSL"];
+essl_load_heavy(suite) ->
+ [];
+essl_load_heavy(Config) when is_list(Config) ->
+ ssl_load_heavy(essl, Config).
+
+ssl_load_heavy(Tag, Config) ->
%% <CONDITIONAL-SKIP>
Skippable = [win32],
Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
?NON_PC_TC_MAYBE_SKIP(Config, Condition),
%% </CONDITIONAL-SKIP>
- httpd_load:load_test(ssl, ?SSL_PORT, ?config(host, Config),
+ httpd_load:load_test(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
?config(node, Config),
get_nof_clients(ssl, heavy)),
ok.
+
%%-------------------------------------------------------------------------
-ssl_dos_hostname(doc) ->
- ["Denial Of Service (DOS) attack test case"];
-ssl_dos_hostname(suite) ->
+
+pssl_dos_hostname(doc) ->
+ ["Denial Of Service (DOS) attack test case - old SSL config"];
+pssl_dos_hostname(suite) ->
[];
-ssl_dos_hostname(Config) when is_list(Config) ->
- dos_hostname(ssl, ?SSL_PORT, ?config(host, Config),
- ?config(node, Config), ?MAX_HEADER_SIZE),
+pssl_dos_hostname(Config) when is_list(Config) ->
+ ssl_dos_hostname(ssl, Config).
+
+ossl_dos_hostname(doc) ->
+ ["Denial Of Service (DOS) attack test case - using new of configure old SSL"];
+ossl_dos_hostname(suite) ->
+ [];
+ossl_dos_hostname(Config) when is_list(Config) ->
+ ssl_dos_hostname(ossl, Config).
+
+essl_dos_hostname(doc) ->
+ ["Denial Of Service (DOS) attack test case - using new of configure new SSL"];
+essl_dos_hostname(suite) ->
+ [];
+essl_dos_hostname(Config) when is_list(Config) ->
+ ssl_dos_hostname(essl, Config).
+
+ssl_dos_hostname(Tag, Config) ->
+ dos_hostname(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config),
+ ?MAX_HEADER_SIZE),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_time_test(doc) ->
- [""];
-ssl_time_test(suite) ->
+
+pssl_time_test(doc) ->
+ ["old SSL config"];
+pssl_time_test(suite) ->
[];
-ssl_time_test(Config) when is_list(Config) ->
+pssl_time_test(Config) when is_list(Config) ->
+ ssl_time_test(ssl, Config).
+
+ossl_time_test(doc) ->
+ ["using new of configure old SSL"];
+ossl_time_test(suite) ->
+ [];
+ossl_time_test(Config) when is_list(Config) ->
+ ssl_time_test(ossl, Config).
+
+essl_time_test(doc) ->
+ ["using new of configure new SSL"];
+essl_time_test(suite) ->
+ [];
+essl_time_test(Config) when is_list(Config) ->
+ ssl_time_test(essl, Config).
+
+ssl_time_test(Tag, Config) when is_list(Config) ->
%% <CONDITIONAL-SKIP>
- Condition = fun() -> true end,
+ FreeBSDVersionVerify =
+ fun() ->
+ case os:version() of
+ {7, 1, _} -> % We only have one such machine, so...
+ true;
+ _ ->
+ false
+ end
+ end,
+ Skippable = [win32, {unix, [{freebsd, FreeBSDVersionVerify}]}],
+ Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
?NON_PC_TC_MAYBE_SKIP(Config, Condition),
%% </CONDITIONAL-SKIP>
- httpd_time_test:t(ssl, ?config(host, Config), ?SSL_PORT),
+ httpd_time_test:t(Tag,
+ ?config(host, Config),
+ ?SSL_PORT),
ok.
+
%%-------------------------------------------------------------------------
-ssl_block_503(doc) ->
+
+pssl_block_503(doc) ->
["Check that you will receive status code 503 when the server"
- " is blocked and 200 when its not blocked."];
-ssl_block_503(suite) ->
+ " is blocked and 200 when its not blocked - old SSL config."];
+pssl_block_503(suite) ->
[];
-ssl_block_503(Config) when is_list(Config) ->
- httpd_block:block_503(ssl, ?SSL_PORT, ?config(host, Config),
+pssl_block_503(Config) when is_list(Config) ->
+ ssl_block_503(ssl, Config).
+
+ossl_block_503(doc) ->
+ ["Check that you will receive status code 503 when the server"
+ " is blocked and 200 when its not blocked - using new of configure old SSL."];
+ossl_block_503(suite) ->
+ [];
+ossl_block_503(Config) when is_list(Config) ->
+ ssl_block_503(ossl, Config).
+
+essl_block_503(doc) ->
+ ["Check that you will receive status code 503 when the server"
+ " is blocked and 200 when its not blocked - using new of configure new SSL."];
+essl_block_503(suite) ->
+ [];
+essl_block_503(Config) when is_list(Config) ->
+ ssl_block_503(essl, Config).
+
+ssl_block_503(Tag, Config) ->
+ httpd_block:block_503(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_block_disturbing_idle(doc) ->
+
+pssl_block_disturbing_idle(doc) ->
["Check that you can block/unblock an idle server. The strategy "
- "distribing does not really make a difference in this case."];
-ssl_block_disturbing_idle(suite) ->
+ "distribing does not really make a difference in this case."
+ "Old SSL config"];
+pssl_block_disturbing_idle(suite) ->
[];
-ssl_block_disturbing_idle(Config) when is_list(Config) ->
- httpd_block:block_disturbing_idle(ssl, ?SSL_PORT,
+pssl_block_disturbing_idle(Config) when is_list(Config) ->
+ ssl_block_disturbing_idle(ssl, Config).
+
+ossl_block_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "distribing does not really make a difference in this case."
+ "Using new of configure old SSL"];
+ossl_block_disturbing_idle(suite) ->
+ [];
+ossl_block_disturbing_idle(Config) when is_list(Config) ->
+ ssl_block_disturbing_idle(ossl, Config).
+
+essl_block_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "distribing does not really make a difference in this case."
+ "Using new of configure new SSL"];
+essl_block_disturbing_idle(suite) ->
+ [];
+essl_block_disturbing_idle(Config) when is_list(Config) ->
+ ssl_block_disturbing_idle(essl, Config).
+
+ssl_block_disturbing_idle(Tag, Config) ->
+ httpd_block:block_disturbing_idle(Tag,
+ ?SSL_PORT,
?config(host, Config),
?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_block_non_disturbing_idle(doc) ->
+
+pssl_block_non_disturbing_idle(doc) ->
["Check that you can block/unblock an idle server. The strategy "
- "non distribing does not really make a difference in this case."];
-ssl_block_non_disturbing_idle(suite) ->
+ "non distribing does not really make a difference in this case."
+ "Old SSL config"];
+pssl_block_non_disturbing_idle(suite) ->
+ [];
+pssl_block_non_disturbing_idle(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_idle(ssl, Config).
+
+ossl_block_non_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing does not really make a difference in this case."
+ "Using new of configure old SSL"];
+ossl_block_non_disturbing_idle(suite) ->
+ [];
+ossl_block_non_disturbing_idle(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_idle(ossl, Config).
+
+essl_block_non_disturbing_idle(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing does not really make a difference in this case."
+ "Using new of configure new SSL"];
+essl_block_non_disturbing_idle(suite) ->
[];
-ssl_block_non_disturbing_idle(Config) when is_list(Config) ->
- httpd_block:block_non_disturbing_idle(ssl, ?SSL_PORT,
+essl_block_non_disturbing_idle(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_idle(essl, Config).
+
+ssl_block_non_disturbing_idle(Tag, Config) ->
+ httpd_block:block_non_disturbing_idle(Tag,
+ ?SSL_PORT,
?config(host, Config),
?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_block_disturbing_active(doc) ->
+
+pssl_block_disturbing_active(doc) ->
["Check that you can block/unblock an active server. The strategy "
- "distribing means ongoing requests should be terminated."];
-ssl_block_disturbing_active(suite) ->
+ "distribing means ongoing requests should be terminated."
+ "Old SSL config"];
+pssl_block_disturbing_active(suite) ->
+ [];
+pssl_block_disturbing_active(Config) when is_list(Config) ->
+ ssl_block_disturbing_active(ssl, Config).
+
+ossl_block_disturbing_active(doc) ->
+ ["Check that you can block/unblock an active server. The strategy "
+ "distribing means ongoing requests should be terminated."
+ "Using new of configure old SSL"];
+ossl_block_disturbing_active(suite) ->
[];
-ssl_block_disturbing_active(Config) when is_list(Config) ->
- httpd_block:block_disturbing_active(ssl, ?SSL_PORT,
+ossl_block_disturbing_active(Config) when is_list(Config) ->
+ ssl_block_disturbing_active(ossl, Config).
+
+essl_block_disturbing_active(doc) ->
+ ["Check that you can block/unblock an active server. The strategy "
+ "distribing means ongoing requests should be terminated."
+ "Using new of configure new SSL"];
+essl_block_disturbing_active(suite) ->
+ [];
+essl_block_disturbing_active(Config) when is_list(Config) ->
+ ssl_block_disturbing_active(essl, Config).
+
+ssl_block_disturbing_active(Tag, Config) ->
+ httpd_block:block_disturbing_active(Tag,
+ ?SSL_PORT,
?config(host, Config),
?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_block_non_disturbing_active(doc) ->
+
+pssl_block_non_disturbing_active(doc) ->
["Check that you can block/unblock an idle server. The strategy "
- "non distribing means the ongoing requests should be compleated."];
-ssl_block_non_disturbing_active(suite) ->
+ "non distribing means the ongoing requests should be compleated."
+ "Old SSL config"];
+pssl_block_non_disturbing_active(suite) ->
+ [];
+pssl_block_non_disturbing_active(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_active(ssl, Config).
+
+ossl_block_non_disturbing_active(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing means the ongoing requests should be compleated."
+ "Using new of configure old SSL"];
+ossl_block_non_disturbing_active(suite) ->
[];
-ssl_block_non_disturbing_active(Config) when is_list(Config) ->
- httpd_block:block_non_disturbing_idle(ssl, ?SSL_PORT,
+ossl_block_non_disturbing_active(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_active(ossl, Config).
+
+essl_block_non_disturbing_active(doc) ->
+ ["Check that you can block/unblock an idle server. The strategy "
+ "non distribing means the ongoing requests should be compleated."
+ "Using new of configure new SSL"];
+essl_block_non_disturbing_active(suite) ->
+ [];
+essl_block_non_disturbing_active(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_active(essl, Config).
+
+ssl_block_non_disturbing_active(Tag, Config) ->
+ httpd_block:block_non_disturbing_idle(Tag,
+ ?SSL_PORT,
?config(host, Config),
?config(node, Config)),
ok.
+
%%-------------------------------------------------------------------------
-ssl_block_disturbing_active_timeout_not_released(doc) ->
+
+pssl_block_disturbing_active_timeout_not_released(doc) ->
["Check that you can block an active server. The strategy "
"distribing means ongoing requests should be compleated"
- "if the timeout does not occur."];
-ssl_block_disturbing_active_timeout_not_released(suite) ->
+ "if the timeout does not occur."
+ "Old SSL config"];
+pssl_block_disturbing_active_timeout_not_released(suite) ->
[];
-ssl_block_disturbing_active_timeout_not_released(Config)
+pssl_block_disturbing_active_timeout_not_released(Config)
when is_list(Config) ->
- httpd_block:
- block_disturbing_active_timeout_not_released(ssl,
- ?SSL_PORT,
- ?config(host,
- Config),
- ?config(node,
- Config)),
+ ssl_block_disturbing_active_timeout_not_released(ssl, Config).
+
+ossl_block_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be compleated"
+ "if the timeout does not occur."
+ "Using new of configure old SSL"];
+ossl_block_disturbing_active_timeout_not_released(suite) ->
+ [];
+ossl_block_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ ssl_block_disturbing_active_timeout_not_released(ossl, Config).
+
+essl_block_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be compleated"
+ "if the timeout does not occur."
+ "Using new of configure new SSL"];
+essl_block_disturbing_active_timeout_not_released(suite) ->
+ [];
+essl_block_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ ssl_block_disturbing_active_timeout_not_released(essl, Config).
+
+ssl_block_disturbing_active_timeout_not_released(Tag, Config) ->
+ Port = ?SSL_PORT,
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ httpd_block:block_disturbing_active_timeout_not_released(Tag,
+ Port, Host, Node),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_block_disturbing_active_timeout_released(doc) ->
+
+pssl_block_disturbing_active_timeout_released(doc) ->
["Check that you can block an active server. The strategy "
"distribing means ongoing requests should be terminated when"
- "the timeout occurs."];
-ssl_block_disturbing_active_timeout_released(suite) ->
+ "the timeout occurs."
+ "Old SSL config"];
+pssl_block_disturbing_active_timeout_released(suite) ->
[];
-ssl_block_disturbing_active_timeout_released(Config)
+pssl_block_disturbing_active_timeout_released(Config)
when is_list(Config) ->
- httpd_block:block_disturbing_active_timeout_released(ssl,
- ?SSL_PORT,
- ?config(host,
- Config),
- ?config(node,
- Config)),
+ ssl_block_disturbing_active_timeout_released(ssl, Config).
+
+ossl_block_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be terminated when"
+ "the timeout occurs."
+ "Using new of configure old SSL"];
+ossl_block_disturbing_active_timeout_released(suite) ->
+ [];
+ossl_block_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ ssl_block_disturbing_active_timeout_released(ossl, Config).
+
+essl_block_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "distribing means ongoing requests should be terminated when"
+ "the timeout occurs."
+ "Using new of configure new SSL"];
+essl_block_disturbing_active_timeout_released(suite) ->
+ [];
+essl_block_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ ssl_block_disturbing_active_timeout_released(essl, Config).
+
+ssl_block_disturbing_active_timeout_released(Tag, Config) ->
+ Port = ?SSL_PORT,
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ httpd_block:block_disturbing_active_timeout_released(Tag,
+ Port,
+ Host,
+ Node),
ok.
+
%%-------------------------------------------------------------------------
-ssl_block_non_disturbing_active_timeout_not_released(doc) ->
+
+pssl_block_non_disturbing_active_timeout_not_released(doc) ->
["Check that you can block an active server. The strategy "
- "non non distribing means ongoing requests should be completed."];
-ssl_block_non_disturbing_active_timeout_not_released(suite) ->
+ "non non distribing means ongoing requests should be completed."
+ "Old SSL config"];
+pssl_block_non_disturbing_active_timeout_not_released(suite) ->
[];
-ssl_block_non_disturbing_active_timeout_not_released(Config)
+pssl_block_non_disturbing_active_timeout_not_released(Config)
when is_list(Config) ->
- httpd_block:
- block_non_disturbing_active_timeout_not_released(ssl,
- ?SSL_PORT,
- ?config(host,
- Config),
- ?config(node,
- Config)),
+ ssl_block_non_disturbing_active_timeout_not_released(ssl, Config).
+
+ossl_block_non_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non non distribing means ongoing requests should be completed."
+ "Using new of configure old SSL"];
+ossl_block_non_disturbing_active_timeout_not_released(suite) ->
+ [];
+ossl_block_non_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ ssl_block_non_disturbing_active_timeout_not_released(ossl, Config).
+
+essl_block_non_disturbing_active_timeout_not_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non non distribing means ongoing requests should be completed."
+ "Using new of configure new SSL"];
+essl_block_non_disturbing_active_timeout_not_released(suite) ->
+ [];
+essl_block_non_disturbing_active_timeout_not_released(Config)
+ when is_list(Config) ->
+ ssl_block_non_disturbing_active_timeout_not_released(essl, Config).
+
+ssl_block_non_disturbing_active_timeout_not_released(Tag, Config) ->
+ Port = ?SSL_PORT,
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ httpd_block:block_non_disturbing_active_timeout_not_released(Tag,
+ Port,
+ Host,
+ Node),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_block_non_disturbing_active_timeout_released(doc) ->
+
+pssl_block_non_disturbing_active_timeout_released(doc) ->
["Check that you can block an active server. The strategy "
- "non non distribing means ongoing requests should be completed. "
- "When the timeout occurs the block operation sohould be canceled." ];
-ssl_block_non_disturbing_active_timeout_released(suite) ->
+ "non distribing means ongoing requests should be completed. "
+ "When the timeout occurs the block operation sohould be canceled."
+ "Old SSL config"];
+pssl_block_non_disturbing_active_timeout_released(suite) ->
[];
-ssl_block_non_disturbing_active_timeout_released(Config)
+pssl_block_non_disturbing_active_timeout_released(Config)
when is_list(Config) ->
- httpd_block:
- block_non_disturbing_active_timeout_released(ssl,
- ?SSL_PORT,
- ?config(host,
- Config),
- ?config(node,
- Config)),
+ ssl_block_non_disturbing_active_timeout_released(ssl, Config).
+
+ossl_block_non_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non distribing means ongoing requests should be completed. "
+ "When the timeout occurs the block operation sohould be canceled."
+ "Using new of configure old SSL"];
+ossl_block_non_disturbing_active_timeout_released(suite) ->
+ [];
+ossl_block_non_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ ssl_block_non_disturbing_active_timeout_released(ossl, Config).
+
+essl_block_non_disturbing_active_timeout_released(doc) ->
+ ["Check that you can block an active server. The strategy "
+ "non distribing means ongoing requests should be completed. "
+ "When the timeout occurs the block operation sohould be canceled."
+ "Using new of configure new SSL"];
+essl_block_non_disturbing_active_timeout_released(suite) ->
+ [];
+essl_block_non_disturbing_active_timeout_released(Config)
+ when is_list(Config) ->
+ ssl_block_non_disturbing_active_timeout_released(essl, Config).
+
+ssl_block_non_disturbing_active_timeout_released(Tag, Config)
+ when is_list(Config) ->
+ Port = ?SSL_PORT,
+ Host = ?config(host, Config),
+ Node = ?config(node, Config),
+ httpd_block:block_non_disturbing_active_timeout_released(Tag,
+ Port,
+ Host,
+ Node),
+
ok.
+
%%-------------------------------------------------------------------------
-ssl_block_disturbing_blocker_dies(doc) ->
+
+pssl_block_disturbing_blocker_dies(doc) ->
+ ["old SSL config"];
+pssl_block_disturbing_blocker_dies(suite) ->
[];
-ssl_block_disturbing_blocker_dies(suite) ->
+pssl_block_disturbing_blocker_dies(Config) when is_list(Config) ->
+ ssl_block_disturbing_blocker_dies(ssl, Config).
+
+ossl_block_disturbing_blocker_dies(doc) ->
+ ["using new of configure old SSL"];
+ossl_block_disturbing_blocker_dies(suite) ->
+ [];
+ossl_block_disturbing_blocker_dies(Config) when is_list(Config) ->
+ ssl_block_disturbing_blocker_dies(ossl, Config).
+
+essl_block_disturbing_blocker_dies(doc) ->
+ ["using new of configure new SSL"];
+essl_block_disturbing_blocker_dies(suite) ->
[];
-ssl_block_disturbing_blocker_dies(Config) when is_list(Config) ->
- httpd_block:disturbing_blocker_dies(ssl, ?SSL_PORT,
+essl_block_disturbing_blocker_dies(Config) when is_list(Config) ->
+ ssl_block_disturbing_blocker_dies(essl, Config).
+
+ssl_block_disturbing_blocker_dies(Tag, Config) ->
+ httpd_block:disturbing_blocker_dies(Tag,
+ ?SSL_PORT,
?config(host, Config),
?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_block_non_disturbing_blocker_dies(doc) ->
+
+pssl_block_non_disturbing_blocker_dies(doc) ->
+ ["old SSL config"];
+pssl_block_non_disturbing_blocker_dies(suite) ->
+ [];
+pssl_block_non_disturbing_blocker_dies(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_blocker_dies(ssl, Config).
+
+ossl_block_non_disturbing_blocker_dies(doc) ->
+ ["using new of configure old SSL"];
+ossl_block_non_disturbing_blocker_dies(suite) ->
[];
-ssl_block_non_disturbing_blocker_dies(suite) ->
+ossl_block_non_disturbing_blocker_dies(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_blocker_dies(ossl, Config).
+
+essl_block_non_disturbing_blocker_dies(doc) ->
+ ["using new of configure new SSL"];
+essl_block_non_disturbing_blocker_dies(suite) ->
[];
-ssl_block_non_disturbing_blocker_dies(Config) when is_list(Config) ->
- httpd_block:non_disturbing_blocker_dies(ssl, ?SSL_PORT,
+essl_block_non_disturbing_blocker_dies(Config) when is_list(Config) ->
+ ssl_block_non_disturbing_blocker_dies(essl, Config).
+
+ssl_block_non_disturbing_blocker_dies(Tag, Config) ->
+ httpd_block:non_disturbing_blocker_dies(Tag,
+ ?SSL_PORT,
?config(host, Config),
?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_restart_no_block(doc) ->
- [""];
-ssl_restart_no_block(suite) ->
+
+pssl_restart_no_block(doc) ->
+ ["old SSL config"];
+pssl_restart_no_block(suite) ->
+ [];
+pssl_restart_no_block(Config) when is_list(Config) ->
+ ssl_restart_no_block(ssl, Config).
+
+ossl_restart_no_block(doc) ->
+ ["using new of configure old SSL"];
+ossl_restart_no_block(suite) ->
+ [];
+ossl_restart_no_block(Config) when is_list(Config) ->
+ ssl_restart_no_block(ossl, Config).
+
+essl_restart_no_block(doc) ->
+ ["using new of configure new SSL"];
+essl_restart_no_block(suite) ->
[];
-ssl_restart_no_block(Config) when is_list(Config) ->
- httpd_block:restart_no_block(ssl, ?SSL_PORT, ?config(host, Config),
+essl_restart_no_block(Config) when is_list(Config) ->
+ ssl_restart_no_block(essl, Config).
+
+ssl_restart_no_block(Tag, Config) ->
+ httpd_block:restart_no_block(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
?config(node, Config)),
ok.
+
+
%%-------------------------------------------------------------------------
-ssl_restart_disturbing_block(doc) ->
- [""];
-ssl_restart_disturbing_block(suite) ->
+
+pssl_restart_disturbing_block(doc) ->
+ ["old SSL config"];
+pssl_restart_disturbing_block(suite) ->
+ [];
+pssl_restart_disturbing_block(Config) when is_list(Config) ->
+ ssl_restart_disturbing_block(ssl, Config).
+
+ossl_restart_disturbing_block(doc) ->
+ ["using new of configure old SSL"];
+ossl_restart_disturbing_block(suite) ->
+ [];
+ossl_restart_disturbing_block(Config) when is_list(Config) ->
+ ssl_restart_disturbing_block(ossl, Config).
+
+essl_restart_disturbing_block(doc) ->
+ ["using new of configure new SSL"];
+essl_restart_disturbing_block(suite) ->
[];
-ssl_restart_disturbing_block(Config) when is_list(Config) ->
+essl_restart_disturbing_block(Config) when is_list(Config) ->
+ ssl_restart_disturbing_block(essl, Config).
+
+ssl_restart_disturbing_block(Tag, Config) ->
%% <CONDITIONAL-SKIP>
Condition =
fun() ->
case os:type() of
{unix, linux} ->
- HW = string:strip(os:cmd("uname -m"), right, $\n),
- case HW of
+ case ?OSCMD("uname -m") of
"ppc" ->
- case inet:gethostname() of
- {ok, "peach"} ->
- true;
+ case file:read_file_info("/etc/fedora-release") of
+ {ok, _} ->
+ case ?OSCMD("awk '{print $2}' /etc/fedora-release") of
+ "release" ->
+ %% Fedora 7 and later
+ case ?OSCMD("awk '{print $3}' /etc/fedora-release") of
+ "7" ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ false
+ end;
_ ->
false
end;
@@ -1336,17 +2309,36 @@ ssl_restart_disturbing_block(Config) when is_list(Config) ->
?NON_PC_TC_MAYBE_SKIP(Config, Condition),
%% </CONDITIONAL-SKIP>
- httpd_block:restart_disturbing_block(ssl, ?SSL_PORT,
+ httpd_block:restart_disturbing_block(Tag, ?SSL_PORT,
?config(host, Config),
?config(node, Config)),
ok.
+
%%-------------------------------------------------------------------------
-ssl_restart_non_disturbing_block(doc) ->
- [""];
-ssl_restart_non_disturbing_block(suite) ->
+
+pssl_restart_non_disturbing_block(doc) ->
+ ["old SSL config"];
+pssl_restart_non_disturbing_block(suite) ->
[];
-ssl_restart_non_disturbing_block(Config) when is_list(Config) ->
+pssl_restart_non_disturbing_block(Config) when is_list(Config) ->
+ ssl_restart_non_disturbing_block(ssl, Config).
+
+ossl_restart_non_disturbing_block(doc) ->
+ ["using new of configure old SSL"];
+ossl_restart_non_disturbing_block(suite) ->
+ [];
+ossl_restart_non_disturbing_block(Config) when is_list(Config) ->
+ ssl_restart_non_disturbing_block(ossl, Config).
+
+essl_restart_non_disturbing_block(doc) ->
+ ["using new of configure new SSL"];
+essl_restart_non_disturbing_block(suite) ->
+ [];
+essl_restart_non_disturbing_block(Config) when is_list(Config) ->
+ ssl_restart_non_disturbing_block(essl, Config).
+
+ssl_restart_non_disturbing_block(Tag, Config) ->
%% <CONDITIONAL-SKIP>
Condition =
fun() ->
@@ -1371,11 +2363,13 @@ ssl_restart_non_disturbing_block(Config) when is_list(Config) ->
?NON_PC_TC_MAYBE_SKIP(Config, Condition),
%% </CONDITIONAL-SKIP>
- httpd_block:restart_non_disturbing_block(ssl, ?SSL_PORT,
- ?config(host, Config),
- ?config(node, Config)),
+ httpd_block:restart_non_disturbing_block(Tag,
+ ?SSL_PORT,
+ ?config(host, Config),
+ ?config(node, Config)),
ok.
+
%%-------------------------------------------------------------------------
ip_host(doc) ->
["Control that the server accepts/rejects requests with/ without host"];
@@ -1665,17 +2659,29 @@ dos_hostname(Type, Port, Host, Node, Max) ->
%% Other help functions
create_config(Config, Access, FileName) ->
ServerRoot = ?config(server_root, Config),
- TcTopDir = ?config(tc_top_dir, Config),
- Port = ?config(port, Config),
- Type = ?config(sock_type, Config),
- Host = ?config(host, Config),
- Mods = io_lib:format("~p", [httpd_mod]),
- Funcs = io_lib:format("~p", [ssl_password_cb]),
- MaxHdrSz = io_lib:format("~p", [256]),
- MaxHdrAct = io_lib:format("~p", [close]),
+ TcTopDir = ?config(tc_top_dir, Config),
+ Port = ?config(port, Config),
+ Type = ?config(sock_type, Config),
+ Host = ?config(host, Config),
+ Mods = io_lib:format("~p", [httpd_mod]),
+ Funcs = io_lib:format("~p", [ssl_password_cb]),
+ MaxHdrSz = io_lib:format("~p", [256]),
+ MaxHdrAct = io_lib:format("~p", [close]),
+
+ io:format(user,
+ "create_config -> "
+ "~n ServerRoot: ~p"
+ "~n TcTopDir: ~p"
+ "~n Type: ~p"
+ "~n Port: ~p"
+ "~n Host: ~p"
+ "~n", [ServerRoot, TcTopDir, Port, Type, Host]),
+
SSL =
- case Type of
- ssl ->
+ if
+ (Type =:= ssl) orelse
+ (Type =:= ossl) orelse
+ (Type =:= essl) ->
[cline(["SSLCertificateFile ",
filename:join(ServerRoot, "ssl/ssl_server.pem")]),
cline(["SSLCertificateKeyFile ",
@@ -1686,25 +2692,25 @@ create_config(Config, Access, FileName) ->
cline(["SSLPasswordCallbackFunction ", Funcs]),
cline(["SSLVerifyClient 0"]),
cline(["SSLVerifyDepth 1"])];
- _ ->
+ true ->
[]
end,
- Mod_order = case Access of
- mod_htaccess ->
- "Modules mod_alias mod_htaccess mod_auth "
- "mod_security "
- "mod_responsecontrol mod_trace mod_esi "
- "mod_actions mod_cgi mod_include mod_dir "
- "mod_range mod_get "
- "mod_head mod_log mod_disk_log";
- _ ->
- "Modules mod_alias mod_auth mod_security "
- "mod_responsecontrol mod_trace mod_esi "
- "mod_actions mod_cgi mod_include mod_dir "
- "mod_range mod_get "
- "mod_head mod_log mod_disk_log"
- end,
-
+ ModOrder = case Access of
+ mod_htaccess ->
+ "Modules mod_alias mod_htaccess mod_auth "
+ "mod_security "
+ "mod_responsecontrol mod_trace mod_esi "
+ "mod_actions mod_cgi mod_include mod_dir "
+ "mod_range mod_get "
+ "mod_head mod_log mod_disk_log";
+ _ ->
+ "Modules mod_alias mod_auth mod_security "
+ "mod_responsecontrol mod_trace mod_esi "
+ "mod_actions mod_cgi mod_include mod_dir "
+ "mod_range mod_get "
+ "mod_head mod_log mod_disk_log"
+ end,
+
%% The test suite currently does not handle an explicit BindAddress.
%% They assume any has been used, that is Addr is always set to undefined!
@@ -1720,7 +2726,7 @@ create_config(Config, Access, FileName) ->
cline(["Port ", integer_to_list(Port)]),
cline(["ServerName ", Host]),
cline(["SocketType ", atom_to_list(Type)]),
- cline([Mod_order]),
+ cline([ModOrder]),
%% cline(["LogFormat ", "erlang"]),
cline(["ServerAdmin [email protected]"]),
cline(["BindAddress ", BindAddress]),
@@ -1882,18 +2888,18 @@ start_mnesia(Node) ->
ok ->
ok;
Other ->
- test_server:fail({failed_to_cleanup_mnesia, Other})
+ tsf({failed_to_cleanup_mnesia, Other})
end,
- case rpc:call(Node, ?MODULE, setup_mnesia, []) of
+ case rpc:call(Node, ?MODULE, setup_mnesia, []) of
{atomic, ok} ->
ok;
Other2 ->
- test_server:fail({failed_to_setup_mnesia, Other2})
+ tsf({failed_to_setup_mnesia, Other2})
end,
ok.
setup_mnesia() ->
- setup_mnesia([node()]).
+ setup_mnesia([node()]).
setup_mnesia(Nodes) ->
ok = mnesia:create_schema(Nodes),
@@ -2029,20 +3035,20 @@ dos_hostname_request(Host) ->
get_nof_clients(Mode, Load) ->
get_nof_clients(test_server:os_type(), Mode, Load).
-get_nof_clients(vxworks, _, light) -> 1;
+get_nof_clients(vxworks, _, light) -> 1;
get_nof_clients(vxworks, ip_comm, medium) -> 3;
-get_nof_clients(vxworks, ssl, medium) -> 3;
+get_nof_clients(vxworks, ssl, medium) -> 3;
get_nof_clients(vxworks, ip_comm, heavy) -> 5;
-get_nof_clients(vxworks, ssl, heavy) -> 5;
-get_nof_clients(_, ip_comm, light) -> 5;
-get_nof_clients(_, ssl, light) -> 2;
-get_nof_clients(_, ip_comm, medium) -> 10;
-get_nof_clients(_, ssl, medium) -> 4;
-get_nof_clients(_, ip_comm, heavy) -> 20;
-get_nof_clients(_, ssl, heavy) -> 6.
+get_nof_clients(vxworks, ssl, heavy) -> 5;
+get_nof_clients(_, ip_comm, light) -> 5;
+get_nof_clients(_, ssl, light) -> 2;
+get_nof_clients(_, ip_comm, medium) -> 10;
+get_nof_clients(_, ssl, medium) -> 4;
+get_nof_clients(_, ip_comm, heavy) -> 20;
+get_nof_clients(_, ssl, heavy) -> 6.
%% Make a file 100 bytes long containing 012...9*10
-create_range_data(Path)->
+create_range_data(Path) ->
PathAndFileName=filename:join([Path,"range.txt"]),
file:write_file(PathAndFileName,list_to_binary(["12345678901234567890",
"12345678901234567890",
@@ -2079,3 +3085,6 @@ create_range_data(Path)->
%% {ok, Fd} = file:open(ConfigFile, [write]),
%% ok = file:write(Fd, lists:flatten(HttpConfig)),
%% ok = file:close(Fd).
+
+tsf(Reason) ->
+ test_server:fail(Reason).
diff --git a/lib/inets/test/httpd_SUITE_data/server_root/Makefile b/lib/inets/test/httpd_SUITE_data/server_root/Makefile
new file mode 100644
index 0000000000..d7a3231068
--- /dev/null
+++ b/lib/inets/test/httpd_SUITE_data/server_root/Makefile
@@ -0,0 +1,209 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-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=$(INETS_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+MODULE=
+
+AUTH_FILES = auth/group \
+ auth/passwd
+CGI_FILES = cgi-bin/printenv.sh
+CONF_FILES = conf/8080.conf \
+ conf/8888.conf \
+ conf/httpd.conf \
+ conf/ssl.conf \
+ conf/mime.types
+OPEN_FILES = htdocs/open/dummy.html
+MNESIA_OPEN_FILES = htdocs/mnesia_open/dummy.html
+MISC_FILES = htdocs/misc/friedrich.html \
+ htdocs/misc/oech.html
+SECRET_FILES = htdocs/secret/dummy.html
+MNESIA_SECRET_FILES = htdocs/mnesia_secret/dummy.html
+HTDOCS_FILES = htdocs/index.html \
+ htdocs/config.shtml \
+ htdocs/echo.shtml \
+ htdocs/exec.shtml \
+ htdocs/flastmod.shtml \
+ htdocs/fsize.shtml \
+ htdocs/include.shtml
+ICON_FILES = icons/README \
+ icons/a.gif \
+ icons/alert.black.gif \
+ icons/alert.red.gif \
+ icons/apache_pb.gif \
+ icons/back.gif \
+ icons/ball.gray.gif \
+ icons/ball.red.gif \
+ icons/binary.gif \
+ icons/binhex.gif \
+ icons/blank.gif \
+ icons/bomb.gif \
+ icons/box1.gif \
+ icons/box2.gif \
+ icons/broken.gif \
+ icons/burst.gif \
+ icons/button1.gif \
+ icons/button10.gif \
+ icons/button2.gif \
+ icons/button3.gif \
+ icons/button4.gif \
+ icons/button5.gif \
+ icons/button6.gif \
+ icons/button7.gif \
+ icons/button8.gif \
+ icons/button9.gif \
+ icons/buttonl.gif \
+ icons/buttonr.gif \
+ icons/c.gif \
+ icons/comp.blue.gif \
+ icons/comp.gray.gif \
+ icons/compressed.gif \
+ icons/continued.gif \
+ icons/dir.gif \
+ icons/down.gif \
+ icons/dvi.gif \
+ icons/f.gif \
+ icons/folder.gif \
+ icons/folder.open.gif \
+ icons/folder.sec.gif \
+ icons/forward.gif \
+ icons/generic.gif \
+ icons/generic.red.gif \
+ icons/generic.sec.gif \
+ icons/hand.right.gif \
+ icons/hand.up.gif \
+ icons/htdig.gif \
+ icons/icon.sheet.gif \
+ icons/image1.gif \
+ icons/image2.gif \
+ icons/image3.gif \
+ icons/index.gif \
+ icons/layout.gif \
+ icons/left.gif \
+ icons/link.gif \
+ icons/movie.gif \
+ icons/p.gif \
+ icons/patch.gif \
+ icons/pdf.gif \
+ icons/pie0.gif \
+ icons/pie1.gif \
+ icons/pie2.gif \
+ icons/pie3.gif \
+ icons/pie4.gif \
+ icons/pie5.gif \
+ icons/pie6.gif \
+ icons/pie7.gif \
+ icons/pie8.gif \
+ icons/portal.gif \
+ icons/poweredby.gif \
+ icons/ps.gif \
+ icons/quill.gif \
+ icons/right.gif \
+ icons/screw1.gif \
+ icons/screw2.gif \
+ icons/script.gif \
+ icons/sound1.gif \
+ icons/sound2.gif \
+ icons/sphere1.gif \
+ icons/sphere2.gif \
+ icons/star.gif \
+ icons/star_blank.gif \
+ icons/tar.gif \
+ icons/tex.gif \
+ icons/text.gif \
+ icons/transfer.gif \
+ icons/unknown.gif \
+ icons/up.gif \
+ icons/uu.gif \
+ icons/uuencoded.gif \
+ icons/world1.gif \
+ icons/world2.gif
+
+SSL_FILES = ssl/ssl_client.pem \
+ ssl/ssl_server.pem
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS +=
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt:
+
+clean:
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth
+ $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin
+ $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf
+ $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open
+ $(INSTALL_DATA) $(OPEN_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/open
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
+ $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc
+ $(INSTALL_DATA) $(MISC_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/misc
+ $(INSTALL_DIR) \
+ $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret
+ $(INSTALL_DIR) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret
+ $(INSTALL_DATA) $(SECRET_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/secret
+ $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs
+ $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons
+ $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl
+ $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs
+
+release_docs_spec:
+
diff --git a/lib/inets/test/httpd_block.erl b/lib/inets/test/httpd_block.erl
index f967d8172a..ac1bf43ff5 100644
--- a/lib/inets/test/httpd_block.erl
+++ b/lib/inets/test/httpd_block.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
%%
@@ -36,6 +36,7 @@
]).
%% Help functions
+-export([httpd_block/3, httpd_block/4, httpd_unblock/2, httpd_restart/2]).
-export([do_block_server/4, do_block_nd_server/5, do_long_poll/6]).
-define(report(Label, Content),
@@ -47,18 +48,24 @@
%% Test cases starts here.
%%-------------------------------------------------------------------------
block_disturbing_idle(_Type, Port, Host, Node) ->
- unblocked = get_admin_state(Node, Host, Port),
+ io:format("block_disturbing_idle -> entry~n", []),
+ validate_admin_state(Node, Host, Port, unblocked),
block_server(Node, Host, Port),
- blocked = get_admin_state(Node, Host, Port),
+ validate_admin_state(Node, Host, Port, blocked),
unblock_server(Node, Host, Port),
- unblocked = get_admin_state(Node, Host, Port).
+ validate_admin_state(Node, Host, Port, unblocked),
+ io:format("block_disturbing_idle -> done~n", []),
+ ok.
+
%%--------------------------------------------------------------------
block_non_disturbing_idle(_Type, Port, Host, Node) ->
unblocked = get_admin_state(Node, Host, Port),
block_nd_server(Node, Host, Port),
blocked = get_admin_state(Node, Host, Port),
unblock_server(Node, Host, Port),
- unblocked = get_admin_state(Node, Host, Port).
+ unblocked = get_admin_state(Node, Host, Port),
+ ok.
+
%%--------------------------------------------------------------------
block_503(Type, Port, Host, Node) ->
Req = "GET / HTTP/1.0\r\ndummy-host.ericsson.se:\r\n\r\n",
@@ -76,6 +83,7 @@ block_503(Type, Port, Host, Node) ->
ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Req,
[{statuscode, 200},
{version, "HTTP/1.0"}]).
+
%%--------------------------------------------------------------------
block_disturbing_active(Type, Port, Host, Node) ->
process_flag(trap_exit, true),
@@ -87,6 +95,7 @@ block_disturbing_active(Type, Port, Host, Node) ->
blocked = get_admin_state(Node, Host, Port),
process_flag(trap_exit, false),
ok.
+
%%--------------------------------------------------------------------
block_non_disturbing_active(Type, Port, Host, Node) ->
process_flag(trap_exit, true),
@@ -219,32 +228,91 @@ do_block_nd_server(Node, Host, Port, Timeout, Reply) ->
restart_server(Node, _Host, Port) ->
Addr = undefined,
- rpc:call(Node, httpd, restart, [Addr, Port]).
+ rpc:call(Node, ?MODULE, httpd_restart, [Addr, Port]).
+
block_server(Node, _Host, Port) ->
+ io:format("block_server -> entry~n", []),
Addr = undefined,
- rpc:call(Node, httpd, block, [Addr, Port]).
+ rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, disturbing]).
+
block_server(Node, _Host, Port, Timeout) ->
Addr = undefined,
- rpc:call(Node, httpd, block, [Addr, Port, disturbing, Timeout]).
+ rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, disturbing, Timeout]).
+
block_nd_server(Node, _Host, Port) ->
Addr = undefined,
- rpc:call(Node, httpd, block, [Addr, Port, non_disturbing]).
+ rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, non_disturbing]).
block_nd_server(Node, _Host, Port, Timeout) ->
Addr = undefined,
- rpc:call(Node, httpd, block, [Addr, Port, non_disturbing, Timeout]).
+ rpc:call(Node, ?MODULE, httpd_block, [Addr, Port, non_disturbing, Timeout]).
unblock_server(Node, _Host, Port) ->
+ io:format("~p:~p:block_server -> entry~n", [node(),self()]),
Addr = undefined,
- rpc:call(Node, httpd, unblock, [Addr, Port]).
+ rpc:call(Node, ?MODULE, httpd_unblock, [Addr, Port]).
+
+
+httpd_block(Addr, Port, Mode) ->
+ io:format("~p:~p:httpd_block -> entry~n", [node(),self()]),
+ Name = make_name(Addr, Port),
+ case whereis(Name) of
+ Pid when is_pid(Pid) ->
+ httpd_manager:block(Pid, Mode);
+ _ ->
+ {error, not_started}
+ end.
+
+httpd_block(Addr, Port, Mode, Timeout) ->
+ Name = make_name(Addr, Port),
+ case whereis(Name) of
+ Pid when is_pid(Pid) ->
+ httpd_manager:block(Pid, Mode, Timeout);
+ _ ->
+ {error, not_started}
+ end.
+
+httpd_unblock(Addr, Port) ->
+ io:format("~p:~p:httpd_unblock -> entry~n", [node(),self()]),
+ Name = make_name(Addr, Port),
+ case whereis(Name) of
+ Pid when is_pid(Pid) ->
+ httpd_manager:unblock(Pid);
+ _ ->
+ {error, not_started}
+ end.
+
+httpd_restart(Addr, Port) ->
+ Name = make_name(Addr, Port),
+ case whereis(Name) of
+ Pid when is_pid(Pid) ->
+ httpd_manager:reload(Pid, undefined);
+ _ ->
+ {error, not_started}
+ end.
+
+make_name(Addr, Port) ->
+ httpd_util:make_name("httpd", Addr, Port).
-get_admin_state(Node,_Host,Port) ->
+get_admin_state(Node, _Host, Port) ->
Addr = undefined,
rpc:call(Node, httpd, get_admin_state, [Addr, Port]).
+validate_admin_state(Node, Host, Port, Expect) ->
+ io:format("try validating server admin state: ~p~n", [Expect]),
+ case get_admin_state(Node, Host, Port) of
+ Expect ->
+ ok;
+ Unexpected ->
+ io:format("failed validating server admin state: ~p~n",
+ [Unexpected]),
+ exit({unexpected_admin_state, Unexpected, Expect})
+ end.
+
+
await_normal_process_exit(Pid, Name, Timeout) ->
receive
{'EXIT', Pid, normal} ->
@@ -260,6 +328,7 @@ await_normal_process_exit(Pid, Name, Timeout) ->
test_server:fail("timeout while waiting for " ++ Name)
end.
+
await_suite_failed_process_exit(Pid, Name, Timeout, Why) ->
receive
{'EXIT', Pid, {suite_failed, Why}} ->
diff --git a/lib/inets/test/httpd_mod.erl b/lib/inets/test/httpd_mod.erl
index b03f842e7c..f2c1fd6a65 100644
--- a/lib/inets/test/httpd_mod.erl
+++ b/lib/inets/test/httpd_mod.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
%%
@@ -40,6 +40,13 @@
%% Test cases starts here.
%%-------------------------------------------------------------------------
alias(Type, Port, Host, Node) ->
+%% io:format(user, "~w:alias -> entry with"
+%% "~n Type: ~p"
+%% "~n Port: ~p"
+%% "~n Host: ~p"
+%% "~n Node: ~p"
+%% "~n", [?MODULE, Type, Port, Host, Node]),
+
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"GET /pics/icon.sheet.gif "
"HTTP/1.0\r\n\r\n",
@@ -82,14 +89,15 @@ actions(Type, Port, Host, Node) ->
%%-------------------------------------------------------------------------
security(ServerRoot, Type, Port, Host, Node) ->
- io:format(user, "~w:security -> entry with"
- "~n ServerRoot: ~p"
- "~n Type: ~p"
- "~n Port: ~p"
- "~n Host: ~p"
- "~n Node: ~p"
- "~n", [?MODULE, ServerRoot, Type, Port, Host, Node]),
+%% io:format(user, "~w:security -> entry with"
+%% "~n ServerRoot: ~p"
+%% "~n Type: ~p"
+%% "~n Port: ~p"
+%% "~n Host: ~p"
+%% "~n Node: ~p"
+%% "~n", [?MODULE, ServerRoot, Type, Port, Host, Node]),
+%% io:format(user, "~w:security -> register~n", [?MODULE]),
global:register_name(mod_security_test, self()), % Receive events
test_server:sleep(5000),
@@ -99,54 +107,71 @@ security(ServerRoot, Type, Port, Host, Node) ->
%% Test blocking / unblocking of users.
%% /open, require user one Aladdin
+%% io:format(user, "~w:security -> remove user~n", [?MODULE]),
remove_users(Node, ServerRoot, Host, Port, "open"),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node, "/open/", "one", "onePassword",
[{statuscode, 401}]),
+%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "one"}, {password, "onePassword"}]},
Node, Port),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type,Host,Port,Node,"/open/", "two", "twoPassword",
[{statuscode, 401}]),
+%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "two"}, {password, "twoPassword"}]},
Node, Port),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "Aladdin",
"AladdinPassword", [{statuscode, 401}]),
+%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "Aladdin"},
{password, "AladdinPassword"}]},
Node, Port),
+%% io:format(user, "~w:security -> add users~n", [?MODULE]),
add_user(Node, ServerRoot, Port, "open", "one", "onePassword", []),
add_user(Node, ServerRoot, Port, "open", "two", "twoPassword", []),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword",
[{statuscode, 401}]),
+%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "one"}, {password, "WrongPassword"}]},
Node, Port),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword",
[{statuscode, 401}]),
+%% io:format(user, "~w:security -> await fail security event~n", [?MODULE]),
receive_security_event({event, auth_fail, Port, OpenDir,
[{user, "one"}, {password, "WrongPassword"}]},
Node, Port),
+%% io:format(user, "~w:security -> await block security event~n", [?MODULE]),
receive_security_event({event, user_block, Port, OpenDir,
[{user, "one"}]}, Node, Port),
+%% io:format(user, "~w:security -> unregister~n", [?MODULE]),
global:unregister_name(mod_security_test), % No more events.
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "one", "WrongPassword",
[{statuscode, 401}]),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword",
[{statuscode, 403}]),
%% User "one" should be blocked now..
%% [{"one",_, Port, OpenDir,_}] = list_blocked_users(Node,Port),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
case list_blocked_users(Node, Port) of
[{"one",_, Port, OpenDir,_}] ->
ok;
@@ -156,35 +181,54 @@ security(ServerRoot, Type, Port, Host, Node) ->
exit({unexpected_blocked, Blocked})
end,
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
[{"one",_, Port, OpenDir,_}] = list_blocked_users(Node,Port,OpenDir),
+%% io:format(user, "~w:security -> unblock user~n", [?MODULE]),
true = unblock_user(Node, "one", Port, OpenDir),
%% User "one" should not be blocked any more..
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
[] = list_blocked_users(Node, Port),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
[] = list_blocked_users(Node, Port, OpenDir),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "one", "onePassword",
[{statuscode, 200}]),
%% Test list_auth_users & auth_timeout
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
["one"] = list_auth_users(Node, Port),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
["one"] = list_auth_users(Node, Port, OpenDir),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "two", "onePassword",
[{statuscode, 401}]),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
["one"] = list_auth_users(Node, Port),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
["one"] = list_auth_users(Node, Port, OpenDir),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword",
[{statuscode, 401}]),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
["one"] = list_auth_users(Node, Port),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
["one"] = list_auth_users(Node, Port, OpenDir),
%% Wait for successful auth to timeout.
test_server:sleep(?AUTH_TIMEOUT*1001),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
[] = list_auth_users(Node, Port),
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
[] = list_auth_users(Node, Port, OpenDir),
%% "two" is blocked.
+%% io:format(user, "~w:security -> unblock user~n", [?MODULE]),
true = unblock_user(Node, "two", Port, OpenDir),
%% Test explicit blocking. Block user 'two'.
+%% io:format(user, "~w:security -> list blocked users~n", [?MODULE]),
[] = list_blocked_users(Node,Port,OpenDir),
+%% io:format(user, "~w:security -> block user~n", [?MODULE]),
true = block_user(Node, "two", Port, OpenDir, 10),
+%% io:format(user, "~w:security -> auth request~n", [?MODULE]),
auth_request(Type, Host, Port, Node,"/open/", "two", "twoPassword",
[{statuscode, 401}]).
@@ -600,6 +644,11 @@ htaccess(Type, Port, Host, Node) ->
{header, "WWW-Authenticate"}]).
%%--------------------------------------------------------------------
cgi(Type, Port, Host, Node) ->
+%% tsp("cgi -> entry with"
+%% "~n Type: ~p"
+%% "~n Port: ~p"
+%% "~n Host: ~p"
+%% "~n Node: ~p", []),
{Script, Script2, Script3} =
case test_server:os_type() of
{win32, _} ->
@@ -609,6 +658,7 @@ cgi(Type, Port, Host, Node) ->
end,
%% The length (> 100) is intentional
+%% tsp("cgi -> request 01 with length > 100"),
ok = httpd_test_lib:
verify_request(Type, Host, Port, Node,
"POST /cgi-bin/" ++ Script3 ++
@@ -636,46 +686,55 @@ cgi(Type, Port, Host, Node) ->
{version, "HTTP/1.0"},
{header, "content-type", "text/plain"}]),
+%% tsp("cgi -> request 02"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"GET /cgi-bin/"++ Script ++
" HTTP/1.0\r\n\r\n",
[{statuscode, 200},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 03"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"GET /cgi-bin/not_there "
"HTTP/1.0\r\n\r\n",
[{statuscode, 404},{statuscode, 500},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 04"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"GET /cgi-bin/"++ Script ++
"?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n",
[{statuscode, 200},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 04"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"POST /cgi-bin/"++ Script ++
" HTTP/1.0\r\n\r\n",
[{statuscode, 200},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 05"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"GET /htbin/"++ Script ++
" HTTP/1.0\r\n\r\n",
[{statuscode, 200},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 06"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"GET /htbin/not_there "
"HTTP/1.0\r\n\r\n",
[{statuscode, 404},{statuscode, 500},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 07"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"GET /htbin/"++ Script ++
"?Nisse:kkk?sss/lll HTTP/1.0\r\n\r\n",
[{statuscode, 200},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 08"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"POST /htbin/"++ Script ++
" HTTP/1.0\r\n\r\n",
[{statuscode, 200},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 09"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"POST /htbin/"++ Script ++
" HTTP/1.0\r\n\r\n",
@@ -683,19 +742,24 @@ cgi(Type, Port, Host, Node) ->
{version, "HTTP/1.0"}]),
%% Execute an existing, but bad CGI script..
+%% tsp("cgi -> request 10 - bad script"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"POST /htbin/"++ Script2 ++
" HTTP/1.0\r\n\r\n",
[{statuscode, 404},
{version, "HTTP/1.0"}]),
+%% tsp("cgi -> request 11 - bad script"),
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
"POST /cgi-bin/"++ Script2 ++
" HTTP/1.0\r\n\r\n",
[{statuscode, 404},
{version, "HTTP/1.0"}]),
+
+%% tsp("cgi -> done"),
ok.
+
%%--------------------------------------------------------------------
esi(Type, Port, Host, Node) ->
%% Check "ErlScriptAlias" and "EvalScriptAlias" directives
@@ -850,25 +914,44 @@ list_users(Node, Root, _Host, Port, Dir) ->
Directory = filename:join([Root, "htdocs", Dir]),
rpc:call(Node, mod_auth, list_users, [Addr, Port, Directory]).
+
receive_security_event(Event, Node, Port) ->
- io:format(user, "~w:receive_security_event -> entry with"
- "~n Event: ~p"
- "~n Node: ~p"
- "~n Port: ~p"
- "~n", [?MODULE, Event, Node, Port]),
+%% io:format(user, "~w:receive_security_event -> entry with"
+%% "~n Event: ~p"
+%% "~n Node: ~p"
+%% "~n Port: ~p"
+%% "~n", [?MODULE, Event, Node, Port]),
receive
Event ->
ok;
{'EXIT', _, _} ->
- receive_security_event(Event, Node, Port);
- Other ->
- test_server:fail({unexpected_event,
- {expected, Event}, {received, Other}})
+ receive_security_event(Event, Node, Port)
after 5000 ->
- test_server:fail(no_event_recived)
+ %% Flush the message queue, to see if we got something...
+ Msgs = inets_test_lib:flush(),
+ tsf({expected_event_not_received, Msgs})
end.
+%% receive_security_event(Event, Node, Port) ->
+%% io:format(user, "~w:receive_security_event -> entry with"
+%% "~n Event: ~p"
+%% "~n Node: ~p"
+%% "~n Port: ~p"
+%% "~n", [?MODULE, Event, Node, Port]),
+%% receive
+%% Event ->
+%% ok;
+%% {'EXIT', _, _} ->
+%% receive_security_event(Event, Node, Port);
+%% Other ->
+%% test_server:fail({unexpected_event,
+%% {expected, Event}, {received, Other}})
+%% after 5000 ->
+%% test_server:fail(no_event_recived)
+
+%% end.
+
list_blocked_users(Node,Port) ->
Addr = undefined, % Assumed to be on the same host
rpc:call(Node, mod_security, list_blocked_users, [Addr,Port]).
@@ -945,3 +1028,12 @@ check_lists_members1(L,L) ->
ok;
check_lists_members1(L1,L2) ->
{error,{lists_not_equal,L1,L2}}.
+
+
+%% tsp(F) ->
+%% tsp(F, []).
+%% tsp(F, A) ->
+%% test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).
+
+tsf(Reason) ->
+ test_server:fail(Reason).
diff --git a/lib/inets/test/httpd_poll.erl b/lib/inets/test/httpd_poll.erl
index 1cc10365a7..32335cabcf 100644
--- a/lib/inets/test/httpd_poll.erl
+++ b/lib/inets/test/httpd_poll.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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%
%%
%%
@@ -27,7 +27,8 @@
%% gen_server exports
-export([init/1,
- handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
+ handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
-define(default_verbosity,error).
@@ -86,8 +87,8 @@ options(Options) ->
options([], Defaults, Options) ->
Options ++ Defaults;
-options([{Key,Val} = Opt|Opts], Defaults, Options) ->
- options(Opts, lists:keydelete(Key, 1, Defaults), [Opt|Options]).
+options([{Key, _Val} = Opt|Opts], Defaults, Options) ->
+ options(Opts, lists:keydelete(Key, 1, Defaults), [Opt | Options]).
verbosity(silence) ->
@@ -134,10 +135,9 @@ uris(otp) ->
uri_top_index(),
uri_internal_product1(),
uri_internal_product2(),
- uri_p7a_test_results(),
+ uri_r13b03_test_results(),
uri_bjorn1(),
- uri_bjorn2(),
- uri_top_ronja()
+ uri_bjorn2()
].
uri_top_index() ->
@@ -149,9 +149,9 @@ uri_internal_product1() ->
uri_internal_product2() ->
{"product internal page (2)","/product/internal"}.
-uri_p7a_test_results() ->
- {"test summery index page",
- "/product/internal/test/test_results/progress_P7A/index.html"}.
+uri_r13b03_test_results() ->
+ {"daily build index page",
+ "/product/internal/test/daily/logs.html"}.
uri_bjorn1() ->
{"bjorns home page (1)","/~bjorn/"}.
@@ -159,9 +159,6 @@ uri_bjorn1() ->
uri_bjorn2() ->
{"bjorns home page (2)","/~bjorn"}.
-uri_top_ronja() ->
- {"ronja top page","/ronja/"}.
-
handle_call(stop, _From, State) ->
vlog("stop request"),
@@ -199,7 +196,11 @@ handle_info(Info, State) ->
{noreply, State}.
-terminate(Reason,State) ->
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+
+terminate(_Reason, State) ->
tcancel(State#state.tref),
log_close(get(log_file)),
ok.
@@ -287,16 +288,16 @@ trash_the_rest(Socket,N) ->
end.
-add(N1,N2) when integer(N1),integer(N2) ->
+add(N1, N2) when is_integer(N1) andalso is_integer(N2) ->
N1 + N2;
-add(N1,N2) when integer(N1) ->
+add(N1, _N2) when is_integer(N1) ->
N1;
-add(N1,N2) when integer(N2) ->
+add(_N1, N2) when is_integer(N2) ->
N2.
-sz(L) when list(L) ->
+sz(L) when is_list(L) ->
length(lists:flatten(L));
-sz(B) when binary(B) ->
+sz(B) when is_binary(B) ->
size(B);
sz(O) ->
{unknown_size,O}.
@@ -307,9 +308,9 @@ sz(O) ->
%% Status code to printable string
%%
-status_to_message(L) when list(L) ->
+status_to_message(L) when is_list(L) ->
case (catch list_to_integer(L)) of
- I when integer(I) ->
+ I when is_integer(I) ->
status_to_message(I);
_ ->
io_lib:format("UNKNOWN STATUS CODE: '~p'",[L])
@@ -470,12 +471,12 @@ vlog(F,A) -> vprint(get(verbosity),log,F,A).
verror(F) -> vprint(get(verbosity),error,F,[]).
verror(F,A) -> vprint(get(verbosity),error,F,A).
-vprint(trace,Severity,F,A) -> vprint(Severity,F,A);
-vprint(debug,trace,F,A) -> ok;
-vprint(debug,Severity,F,A) -> vprint(Severity,F,A);
-vprint(log,log,F,A) -> vprint(log,F,A);
-vprint(log,error,F,A) -> vprint(log,F,A);
-vprint(error,error,F,A) -> vprint(error,F,A);
+vprint(trace, Severity, F, A) -> vprint(Severity,F,A);
+vprint(debug, trace, _F, _A) -> ok;
+vprint(debug, Severity, F, A) -> vprint(Severity,F,A);
+vprint(log, log, F, A) -> vprint(log,F,A);
+vprint(log, error, F, A) -> vprint(log,F,A);
+vprint(error, error, F, A) -> vprint(error,F,A);
vprint(_Verbosity,_Severity,_F,_A) -> ok.
vprint(Severity,F,A) ->
@@ -491,6 +492,3 @@ image_of(trace) -> "TRC: ".
local_time() -> calendar:local_time().
-
-
-
diff --git a/lib/inets/test/httpd_test_data/server_root/Makefile b/lib/inets/test/httpd_test_data/server_root/Makefile
new file mode 100644
index 0000000000..d7a3231068
--- /dev/null
+++ b/lib/inets/test/httpd_test_data/server_root/Makefile
@@ -0,0 +1,209 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1997-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=$(INETS_VSN)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+MODULE=
+
+AUTH_FILES = auth/group \
+ auth/passwd
+CGI_FILES = cgi-bin/printenv.sh
+CONF_FILES = conf/8080.conf \
+ conf/8888.conf \
+ conf/httpd.conf \
+ conf/ssl.conf \
+ conf/mime.types
+OPEN_FILES = htdocs/open/dummy.html
+MNESIA_OPEN_FILES = htdocs/mnesia_open/dummy.html
+MISC_FILES = htdocs/misc/friedrich.html \
+ htdocs/misc/oech.html
+SECRET_FILES = htdocs/secret/dummy.html
+MNESIA_SECRET_FILES = htdocs/mnesia_secret/dummy.html
+HTDOCS_FILES = htdocs/index.html \
+ htdocs/config.shtml \
+ htdocs/echo.shtml \
+ htdocs/exec.shtml \
+ htdocs/flastmod.shtml \
+ htdocs/fsize.shtml \
+ htdocs/include.shtml
+ICON_FILES = icons/README \
+ icons/a.gif \
+ icons/alert.black.gif \
+ icons/alert.red.gif \
+ icons/apache_pb.gif \
+ icons/back.gif \
+ icons/ball.gray.gif \
+ icons/ball.red.gif \
+ icons/binary.gif \
+ icons/binhex.gif \
+ icons/blank.gif \
+ icons/bomb.gif \
+ icons/box1.gif \
+ icons/box2.gif \
+ icons/broken.gif \
+ icons/burst.gif \
+ icons/button1.gif \
+ icons/button10.gif \
+ icons/button2.gif \
+ icons/button3.gif \
+ icons/button4.gif \
+ icons/button5.gif \
+ icons/button6.gif \
+ icons/button7.gif \
+ icons/button8.gif \
+ icons/button9.gif \
+ icons/buttonl.gif \
+ icons/buttonr.gif \
+ icons/c.gif \
+ icons/comp.blue.gif \
+ icons/comp.gray.gif \
+ icons/compressed.gif \
+ icons/continued.gif \
+ icons/dir.gif \
+ icons/down.gif \
+ icons/dvi.gif \
+ icons/f.gif \
+ icons/folder.gif \
+ icons/folder.open.gif \
+ icons/folder.sec.gif \
+ icons/forward.gif \
+ icons/generic.gif \
+ icons/generic.red.gif \
+ icons/generic.sec.gif \
+ icons/hand.right.gif \
+ icons/hand.up.gif \
+ icons/htdig.gif \
+ icons/icon.sheet.gif \
+ icons/image1.gif \
+ icons/image2.gif \
+ icons/image3.gif \
+ icons/index.gif \
+ icons/layout.gif \
+ icons/left.gif \
+ icons/link.gif \
+ icons/movie.gif \
+ icons/p.gif \
+ icons/patch.gif \
+ icons/pdf.gif \
+ icons/pie0.gif \
+ icons/pie1.gif \
+ icons/pie2.gif \
+ icons/pie3.gif \
+ icons/pie4.gif \
+ icons/pie5.gif \
+ icons/pie6.gif \
+ icons/pie7.gif \
+ icons/pie8.gif \
+ icons/portal.gif \
+ icons/poweredby.gif \
+ icons/ps.gif \
+ icons/quill.gif \
+ icons/right.gif \
+ icons/screw1.gif \
+ icons/screw2.gif \
+ icons/script.gif \
+ icons/sound1.gif \
+ icons/sound2.gif \
+ icons/sphere1.gif \
+ icons/sphere2.gif \
+ icons/star.gif \
+ icons/star_blank.gif \
+ icons/tar.gif \
+ icons/tex.gif \
+ icons/text.gif \
+ icons/transfer.gif \
+ icons/unknown.gif \
+ icons/up.gif \
+ icons/uu.gif \
+ icons/uuencoded.gif \
+ icons/world1.gif \
+ icons/world2.gif
+
+SSL_FILES = ssl/ssl_client.pem \
+ ssl/ssl_server.pem
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS +=
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt:
+
+clean:
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/auth
+ $(INSTALL_DATA) $(AUTH_FILES) $(RELSYSDIR)/examples/server_root/auth
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/cgi-bin
+ $(INSTALL_SCRIPT) $(CGI_FILES) $(RELSYSDIR)/examples/server_root/cgi-bin
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/conf
+ $(INSTALL_DATA) $(CONF_FILES) $(RELSYSDIR)/examples/server_root/conf
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/open
+ $(INSTALL_DATA) $(OPEN_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/open
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
+ $(INSTALL_DATA) $(MNESIA_OPEN_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_open
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs/misc
+ $(INSTALL_DATA) $(MISC_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/misc
+ $(INSTALL_DIR) \
+ $(RELSYSDIR)/examples/server_root/htdocs/secret/top_secret
+ $(INSTALL_DIR) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret/top_secret
+ $(INSTALL_DATA) $(SECRET_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/secret
+ $(INSTALL_DATA) $(MNESIA_SECRET_FILES) \
+ $(RELSYSDIR)/examples/server_root/htdocs/mnesia_secret
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/htdocs
+ $(INSTALL_DATA) $(HTDOCS_FILES) $(RELSYSDIR)/examples/server_root/htdocs
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/icons
+ $(INSTALL_DATA) $(ICON_FILES) $(RELSYSDIR)/examples/server_root/icons
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/ssl
+ $(INSTALL_DATA) $(SSL_FILES) $(RELSYSDIR)/examples/server_root/ssl
+ $(INSTALL_DIR) $(RELSYSDIR)/examples/server_root/logs
+
+release_docs_spec:
+
diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl
index 6abee5be2c..3189a758a5 100644
--- a/lib/inets/test/httpd_test_lib.erl
+++ b/lib/inets/test/httpd_test_lib.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%%
@@ -72,6 +72,8 @@
'last-modified',
other=[] % list() - Key/Value list with other headers
}).
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%--------------------------------------------------------------------
@@ -81,7 +83,8 @@ verify_request(SocketType, Host, Port, Node, RequestStr, Options) ->
verify_request(SocketType, Host, Port, Node, RequestStr, Options, 30000).
verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) ->
{ok, Socket} = inets_test_lib:connect_bin(SocketType, Host, Port),
- inets_test_lib:send(SocketType, Socket, RequestStr),
+
+ _SendRes = inets_test_lib:send(SocketType, Socket, RequestStr),
State = case inets_regexp:match(RequestStr, "printenv") of
nomatch ->
@@ -90,18 +93,26 @@ verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) ->
#state{print = true}
end,
- case request(State#state{request = RequestStr, socket = Socket}, TimeOut) of
- {error, Reson} ->
- {error, Reson};
+ case request(State#state{request = RequestStr,
+ socket = Socket}, TimeOut) of
+ {error, Reason} ->
+ tsp("request failed: "
+ "~n Reason: ~p", [Reason]),
+ {error, Reason};
NewState ->
+ tsp("validate reply: "
+ "~n NewState: ~p", [NewState]),
ValidateResult = validate(RequestStr, NewState, Options,
Node, Port),
+ tsp("validation result: "
+ "~n ~p", [ValidateResult]),
inets_test_lib:close(SocketType, Socket),
ValidateResult
end.
request(#state{mfa = {Module, Function, Args},
request = RequestStr, socket = Socket} = State, TimeOut) ->
+
HeadRequest = lists:sublist(RequestStr, 1, 4),
receive
{tcp, Socket, Data} ->
@@ -109,12 +120,12 @@ request(#state{mfa = {Module, Function, Args},
case Module:Function([Data | Args]) of
{ok, Parsed} ->
handle_http_msg(Parsed, State);
- {_, whole_body, _} when HeadRequest == "HEAD" ->
+ {_, whole_body, _} when HeadRequest =:= "HEAD" ->
State#state{body = <<>>};
NewMFA ->
request(State#state{mfa = NewMFA}, TimeOut)
end;
- {tcp_closed, Socket} when Function == whole_body ->
+ {tcp_closed, Socket} when Function =:= whole_body ->
print(tcp, "closed", State),
State#state{body = hd(Args)};
{tcp_closed, Socket} ->
@@ -126,12 +137,12 @@ request(#state{mfa = {Module, Function, Args},
case Module:Function([Data | Args]) of
{ok, Parsed} ->
handle_http_msg(Parsed, State);
- {_, whole_body, _} when HeadRequest == "HEAD" ->
+ {_, whole_body, _} when HeadRequest =:= "HEAD" ->
State#state{body = <<>>};
NewMFA ->
request(State#state{mfa = NewMFA}, TimeOut)
end;
- {ssl_closed, Socket} when Function == whole_body ->
+ {ssl_closed, Socket} when Function =:= whole_body ->
print(ssl, "closed", State),
State#state{body = hd(Args)};
{ssl_closed, Socket} ->
@@ -330,3 +341,9 @@ print(Proto, Data, #state{print = true}) ->
print(_, _, #state{print = false}) ->
ok.
+
+%% tsp(F) ->
+%% tsp(F, []).
+tsp(F, A) ->
+ test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).
+
diff --git a/lib/inets/test/httpd_time_test.erl b/lib/inets/test/httpd_time_test.erl
index 7d6aa08542..f39f9faff0 100644
--- a/lib/inets/test/httpd_time_test.erl
+++ b/lib/inets/test/httpd_time_test.erl
@@ -1,25 +1,25 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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(httpd_time_test).
--export([t/3, t1/2, t2/2]).
+-export([t/3, t1/2, t2/2, t3/2, t4/2]).
-export([do/1, do/2, do/3, do/4, do/5]).
@@ -29,6 +29,9 @@
-record(stat, {pid, time = undefined, count = undefined, res}).
+%% -define(NUM_POLLERS, 10).
+-define(NUM_POLLERS, 1).
+
%%% -----------------------------------------------------------------
%%% Test suite interface
@@ -42,9 +45,17 @@ t2(Host, Port) ->
t(ssl, Host, Port).
+t3(Host, Port) ->
+ t(ossl, Host, Port).
+
+
+t4(Host, Port) ->
+ t(essl, Host, Port).
+
+
t(SocketType, Host, Port) ->
%% put(dbg,true),
- main(1, SocketType, Host, Port, 60000).
+ main(?NUM_POLLERS, SocketType, Host, Port, 60000).
@@ -111,28 +122,40 @@ loop(Pollers, Timeout) ->
"~n Timeout: ~p", [Timeout]),
Start = t(),
receive
- {'EXIT', Pid, {poller_stat_failure, Time, Reason}} ->
+ {'EXIT', Pid, {poller_stat_failure, SocketType, Host, Port, Time, Reason}} ->
case is_poller(Pid, Pollers) of
true ->
error_msg("received unexpected exit from poller ~p~n"
"befor completion of test "
- "(after ~p micro sec):~n"
- "~p~n", [Pid,Time,Reason]),
- exit({fail, {poller_exit, Pid, Reason}});
+ "after ~p micro sec"
+ "~n SocketType: ~p"
+ "~n Host: ~p"
+ "~n Port: ~p"
+ "~n~p~n",
+ [Pid, SocketType, Host, Port, Time, Reason]),
+ exit({fail, {poller_exit, Pid, Time, Reason}});
false ->
error_msg("received unexpected ~p from ~p"
"befor completion of test", [Reason, Pid]),
loop(Pollers, to(Timeout, Start))
end;
- {poller_stat_failure, Pid, {Time, Reason}} ->
+ {poller_stat_failure, Pid, {SocketType, Host, Port, Time, Reason}} ->
error_msg("received stat failure ~p from poller ~p after ~p "
- "befor completion of test", [Reason, Pid, Time]),
- exit({fail, {poller_failure, Pid, Reason}});
-
- {poller_stat_failure, Pid, Reason} ->
+ "befor completion of test"
+ "~n SocketType: ~p"
+ "~n Host: ~p"
+ "~n Port: ~p",
+ [Reason, Pid, Time, SocketType, Host, Port]),
+ exit({fail, {poller_failure, Pid, Time, Reason}});
+
+ {poller_stat_failure, Pid, SocketType, Host, Port, Reason} ->
error_msg("received stat failure ~p from poller ~p "
- "befor completion of test", [Reason, Pid]),
+ "befor completion of test"
+ "~n SocketType: ~p"
+ "~n Host: ~p"
+ "~n Port: ~p",
+ [Reason, Pid, SocketType, Host, Port]),
exit({fail, {poller_failure, Pid, Reason}});
Any ->
@@ -250,16 +273,16 @@ is_poller(Pid, [_|Rest]) ->
poller_main(Parent, SocketType, Host, Port) ->
process_flag(trap_exit,true),
- put(sname,poller),
+ put(sname, poller),
case timer:tc(?MODULE, poller_loop, [SocketType, Host, Port, uris()]) of
{Time, Count} when is_integer(Time) andalso is_integer(Count) ->
Parent ! {poller_statistics, self(), {Time, Count}};
{Time, {'EXIT', Reason}} when is_integer(Time) ->
- exit({poller_stat_failure, Time, Reason});
+ exit({poller_stat_failure, SocketType, Host, Port, Time, Reason});
{Time, Other} when is_integer(Time) ->
- Parent ! {poller_stat_failure, self(), {Time, Other}};
+ Parent ! {poller_stat_failure, self(), {SocketType, Host, Port, Time, Other}};
Else ->
- Parent ! {poller_stat_failure, self(), Else}
+ Parent ! {poller_stat_failure, self(), SocketType, Host, Port, Else}
end.
diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl
index ba41e0960c..1e701bc074 100644
--- a/lib/inets/test/inets_sup_SUITE.erl
+++ b/lib/inets/test/inets_sup_SUITE.erl
@@ -372,11 +372,11 @@ httpc_subtree(Config) when is_list(Config) ->
"~n Config: ~p", [Config]),
tsp("httpc_subtree -> start inets service httpc with profile foo"),
- {ok, Foo} = inets:start(httpc, [{profile, foo}]),
+ {ok, _Foo} = inets:start(httpc, [{profile, foo}]),
tsp("httpc_subtree -> "
"start stand-alone inets service httpc with profile bar"),
- {ok, Bar} = inets:start(httpc, [{profile, bar}], stand_alone),
+ {ok, _Bar} = inets:start(httpc, [{profile, bar}], stand_alone),
tsp("httpc_subtree -> retreive list of httpc instances"),
HttpcChildren = supervisor:which_children(httpc_profile_sup),
diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl
index 6af2ad32f7..86fc2d1a32 100644
--- a/lib/inets/test/inets_test_lib.erl
+++ b/lib/inets/test/inets_test_lib.erl
@@ -1,44 +1,136 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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(inets_test_lib).
-include("inets_test_lib.hrl").
+-include_lib("inets/src/http_lib/http_internal.hrl").
%% Various small utility functions
--export([start_http_server/1, start_http_server_ssl/1]).
+-export([start_http_server/1, start_http_server/2]).
+-export([start_http_server_ssl/1, start_http_server_ssl/2]).
-export([hostname/0]).
-export([connect_bin/3, connect_byte/3, send/3, close/2]).
-export([copy_file/3, copy_files/2, copy_dirs/2, del_dirs/1]).
-export([info/4, log/4, debug/4, print/4]).
-export([check_body/1]).
-export([millis/0, millis_diff/2, hours/1, minutes/1, seconds/1, sleep/1]).
+-export([oscmd/1]).
-export([non_pc_tc_maybe_skip/4, os_based_skip/1]).
+-export([flush/0]).
+-export([start_node/1, stop_node/1]).
+
+%% -- Misc os command and stuff
+
+oscmd(Cmd) ->
+ string:strip(os:cmd(Cmd), right, $\n).
+
+%% -- Misc node operation wrapper functions --
+
+start_node(Name) ->
+ Pa = filename:dirname(code:which(?MODULE)),
+ Args = case init:get_argument('CC_TEST') of
+ {ok, [[]]} ->
+ " -pa /clearcase/otp/libraries/snmp/ebin ";
+ {ok, [[Path]]} ->
+ " -pa " ++ Path;
+ error ->
+ ""
+ end,
+ A = Args ++ " -pa " ++ Pa,
+ Opts = [{cleanup,false}, {args, A}],
+ case (catch test_server:start_node(Name, slave, Opts)) of
+ {ok, Node} ->
+ Node;
+ Else ->
+ exit({failed_starting_node, Name, Else})
+ end.
+
+stop_node(Node) ->
+ rpc:cast(Node, erlang, halt, []),
+ await_stopped(Node, 5).
+
+await_stopped(_, 0) ->
+ ok;
+await_stopped(Node, N) ->
+ Nodes = erlang:nodes(),
+ case lists:member(Node, Nodes) of
+ true ->
+ sleep(1000),
+ await_stopped(Node, N-1);
+ false ->
+ ok
+ end.
+
+
+%% ----------------------------------------------------------------
+%% HTTPD starter functions
+%%
start_http_server(Conf) ->
+ start_http_server(Conf, ?HTTP_DEFAULT_SSL_KIND).
+
+start_http_server(Conf, essl = _SslTag) ->
+ application:start(crypto),
+ do_start_http_server(Conf);
+start_http_server(Conf, _SslTag) ->
+ do_start_http_server(Conf).
+
+do_start_http_server(Conf) ->
+ tsp("start http server with "
+ "~n Conf: ~p"
+ "~n", [Conf]),
application:load(inets),
- ok = application:set_env(inets, services, [{httpd, Conf}]),
- ok = application:start(inets).
-
+ case application:set_env(inets, services, [{httpd, Conf}]) of
+ ok ->
+ case application:start(inets) of
+ ok ->
+ ok;
+ Error1 ->
+ test_server:format("<ERROR> Failed starting application: "
+ "~n Error: ~p"
+ "~n", [Error1]),
+ Error1
+ end;
+ Error2 ->
+ test_server:format("<ERROR> Failed set application env: "
+ "~n Error: ~p"
+ "~n", [Error2]),
+ Error2
+ end.
+
start_http_server_ssl(FileName) ->
+ start_http_server_ssl(FileName, ?HTTP_DEFAULT_SSL_KIND).
+
+start_http_server_ssl(FileName, essl = _SslTag) ->
+ application:start(crypto),
+ do_start_http_server_ssl(FileName);
+start_http_server_ssl(FileName, _SslTag) ->
+ do_start_http_server_ssl(FileName).
+
+do_start_http_server_ssl(FileName) ->
+ tsp("start (ssl) http server with "
+ "~n FileName: ~p"
+ "~n", [FileName]),
application:start(ssl),
- catch start_http_server(FileName).
+ catch do_start_http_server(FileName).
+
%% ----------------------------------------------------------------------
%% print functions
@@ -84,27 +176,17 @@ copy_files(FromDir, ToDir) ->
copy_dirs(FromDirRoot, ToDirRoot) ->
-%% io:format("~w:copy_dirs -> entry with"
-%% "~n FromDirRoot: ~p"
-%% "~n ToDirRoot: ~p"
-%% "~n", [?MODULE, FromDirRoot, ToDirRoot]),
{ok, Files} = file:list_dir(FromDirRoot),
lists:foreach(
fun(FileOrDir) ->
%% Check if it's a directory or a file
-%% io:format("~w:copy_dirs -> check ~p"
-%% "~n", [?MODULE, FileOrDir]),
case filelib:is_dir(filename:join(FromDirRoot, FileOrDir)) of
true ->
-%% io:format("~w:copy_dirs -> ~p is a directory"
-%% "~n", [?MODULE, FileOrDir]),
FromDir = filename:join([FromDirRoot, FileOrDir]),
ToDir = filename:join([ToDirRoot, FileOrDir]),
ok = file:make_dir(ToDir),
copy_dirs(FromDir, ToDir);
false ->
-%% io:format("~w:copy_dirs -> ~p is a file"
-%% "~n", [?MODULE, FileOrDir]),
copy_file(FileOrDir, FromDirRoot, ToDirRoot)
end
end, Files).
@@ -133,8 +215,8 @@ check_body(Body) ->
0 ->
case string:rstr(Body, "</HTML>") of
0 ->
- test_server:format("Body ~p~n", [Body]),
- test_server:fail(did_not_receive_whole_body);
+ tsp("Body ~p", [Body]),
+ tsf(did_not_receive_whole_body);
_ ->
ok
end;
@@ -204,9 +286,31 @@ os_based_skip(_) ->
%% Port -> integer()
connect_bin(ssl, Host, Port) ->
+ connect(ssl, Host, Port, [binary, {packet,0}]);
+connect_bin(ossl, Host, Port) ->
+ connect(ssl, Host, Port, [{ssl_imp, old}, binary, {packet,0}]);
+connect_bin(essl, Host, Port) ->
+ connect(ssl, Host, Port, [{ssl_imp, new}, binary, {packet,0}, {reuseaddr, true}]);
+connect_bin(ip_comm, Host, Port) ->
+ Opts = [inet6, binary, {packet,0}],
+ connect(ip_comm, Host, Port, Opts).
+
+
+connect_byte(ssl, Host, Port) ->
+ connect(ssl, Host, Port, [{packet,0}]);
+connect_byte(ossl, Host, Port) ->
+ connect(ssl, Host, Port, [{ssl_imp, old}, {packet,0}]);
+connect_byte(essl, Host, Port) ->
+ connect(ssl, Host, Port, [{ssl_imp, new}, {packet,0}]);
+connect_byte(ip_comm, Host, Port) ->
+ Opts = [inet6, {packet,0}],
+ connect(ip_comm, Host, Port, Opts).
+
+
+connect(ssl, Host, Port, Opts) ->
ssl:start(),
%% Does not support ipv6 in old ssl
- case ssl:connect(Host, Port, [binary, {packet,0}]) of
+ case ssl:connect(Host, Port, Opts) of
{ok, Socket} ->
{ok, Socket};
{error, Reason} ->
@@ -214,61 +318,48 @@ connect_bin(ssl, Host, Port) ->
Error ->
Error
end;
-connect_bin(ip_comm, Host, Port) ->
- Opts = [inet6, binary, {packet,0}],
- connect(ip_comm, Host, Port, Opts).
-
-
connect(ip_comm, Host, Port, Opts) ->
- test_server:format("gen_tcp:connect(~p, ~p, ~p) ~n", [Host, Port, Opts]),
case gen_tcp:connect(Host,Port, Opts) of
{ok, Socket} ->
- test_server:format("connect success~n", []),
+ %% tsp("connect success"),
{ok, Socket};
{error, nxdomain} ->
- test_server:format("nxdomain opts: ~p~n", [Opts]),
+ tsp("nxdomain opts: ~p", [Opts]),
connect(ip_comm, Host, Port, lists:delete(inet6, Opts));
{error, eafnosupport} ->
- test_server:format("eafnosupport opts: ~p~n", [Opts]),
+ tsp("eafnosupport opts: ~p", [Opts]),
connect(ip_comm, Host, Port, lists:delete(inet6, Opts));
{error, {enfile,_}} ->
- test_server:format("Error enfile~n", []),
+ tsp("Error enfile"),
{error, enfile};
Error ->
- test_server:format("Unexpected error: "
- "~n Error: ~p"
- "~nwhen"
- "~n Host: ~p"
- "~n Port: ~p"
- "~n Opts: ~p"
- "~n", [Error, Host, Port, Opts]),
+ tsp("Unexpected error: "
+ "~n Error: ~p"
+ "~nwhen"
+ "~n Host: ~p"
+ "~n Port: ~p"
+ "~n Opts: ~p"
+ "~n", [Error, Host, Port, Opts]),
Error
end.
-connect_byte(ip_comm, Host, Port) ->
- Opts = [inet6, {packet,0}],
- connect(ip_comm, Host, Port, Opts);
-
-connect_byte(ssl, Host, Port) ->
- ssl:start(),
- %% Does not support ipv6 in old ssl
- case ssl:connect(Host,Port,[{packet,0}]) of
- {ok,Socket} ->
- {ok,Socket};
- {error,{enfile,_}} ->
- {error, enfile};
- Error ->
- Error
- end.
send(ssl, Socket, Data) ->
ssl:send(Socket, Data);
+send(ossl, Socket, Data) ->
+ ssl:send(Socket, Data);
+send(essl, Socket, Data) ->
+ ssl:send(Socket, Data);
send(ip_comm,Socket,Data) ->
gen_tcp:send(Socket,Data).
close(ssl,Socket) ->
catch ssl:close(Socket);
+close(ossl,Socket) ->
+ catch ssl:close(Socket);
+close(essl,Socket) ->
+ catch ssl:close(Socket);
close(ip_comm,Socket) ->
catch gen_tcp:close(Socket).
@@ -300,3 +391,20 @@ sleep(MSecs) ->
skip(Reason, File, Line) ->
exit({skipped, {Reason, File, Line}}).
+
+flush() ->
+ receive
+ Msg ->
+ [Msg | flush()]
+ after 1000 ->
+ []
+ end.
+
+
+tsp(F) ->
+ tsp(F, []).
+tsp(F, A) ->
+ test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]).
+
+tsf(Reason) ->
+ test_server:fail(Reason).
diff --git a/lib/inets/test/inets_test_lib.hrl b/lib/inets/test/inets_test_lib.hrl
index 12a43fa136..0cdb04139c 100644
--- a/lib/inets/test/inets_test_lib.hrl
+++ b/lib/inets/test/inets_test_lib.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%%
@@ -46,6 +46,11 @@
-endif.
+%% - OS Command and stuff
+
+-define(OSCMD(Cmd), inets_test_lib:oscmd(Cmd)).
+
+
%% - Test case macros -
-define(EXPANDABLE(I, C, F), inets_test_lib:expandable(I, C, F)).
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 401bf4d37d..57c87e7036 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -18,11 +18,24 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 5.3.1
+INETS_VSN = 5.4
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
-TICKETS = OTP-8508 OTP-8509
+TICKETS = OTP-7907 OTP-8564 OTP-8573
+
+TICKETS_5_3_3 = \
+ OTP-8609 \
+ OTP-8610 \
+ OTP-8624
+
+TICKETS_5_3_2 = \
+ OTP-8542 \
+ OTP-8607
+
+TICKETS_5_3_1 = \
+ OTP-8508 \
+ OTP-8509
TICKETS_5_3 = \
OTP-8016 \
@@ -38,7 +51,14 @@ TICKETS_5_3 = \
OTP-8359 \
OTP-8371
-TICKETS_5_2 = OTP-8204 OTP-8206 OTP-8247 OTP-8248 OTP-8249 OTP-8258 OTP-8280
+TICKETS_5_2 = \
+ OTP-8204 \
+ OTP-8206 \
+ OTP-8247 \
+ OTP-8248 \
+ OTP-8249 \
+ OTP-8258 \
+ OTP-8280
TICKETS_5_1_3 = OTP-8154
diff --git a/lib/jinterface/java_src/Makefile b/lib/jinterface/java_src/Makefile
index 37a57352ad..22c55328b8 100644
--- a/lib/jinterface/java_src/Makefile
+++ b/lib/jinterface/java_src/Makefile
@@ -35,7 +35,21 @@ VSN=$(JINTERFACE_VSN)
SPECIAL_TARGETS =
+TARGET_FILES= $(POM_TARGET)
+SPECIAL_TARGETS =
+
+POM_FILE= pom.xml
+
+POM_TARGET= ../$(POM_FILE)
+POM_SRC= $(POM_FILE).src
+
+# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+$(POM_TARGET): $(POM_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
# ----------------------------------------------------
# Default Subdir Targets
@@ -43,7 +57,7 @@ SPECIAL_TARGETS =
.PHONY: debug opt instr release docs release_docs tests release_tests clean depend
-debug opt instr release docs release_docs tests release_tests clean depend:
+debug opt instr release docs release_docs tests release_tests clean depend: $(TARGET_FILES)
set -e; set -x; \
case "$(MAKE)" in *clearmake*) tflag="-T";; *) tflag="";; esac; \
if test -f com/ericsson/otp/erlang/ignore_config_record.inf; then xflag=$$tflag; fi; \
diff --git a/lib/jinterface/java_src/pom.xml.src b/lib/jinterface/java_src/pom.xml.src
new file mode 100644
index 0000000000..cef49b735a
--- /dev/null
+++ b/lib/jinterface/java_src/pom.xml.src
@@ -0,0 +1,106 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>org.erlang.otp</groupId>
+ <artifactId>jinterface</artifactId>
+ <packaging>jar</packaging>
+ <version>%VSN%</version>
+ <name>jinterface</name>
+ <description>
+ Jinterface Java package contains java classes, which help you integrate programs written in Java with Erlang.
+ Erlang is a programming language designed at the Ericsson Computer Science Laboratory.
+ </description>
+ <url>http://erlang.org/</url>
+ <licenses>
+ <license>
+ <name>ERLANG PUBLIC LICENSE 1.1</name>
+ <url>http://www.erlang.org/EPLICENSE</url>
+ <distribution>repo</distribution>
+ </license>
+ </licenses>
+ <scm>
+ <connection>git://github.com/erlang/otp.git</connection>
+ <developerConnection>git://github.com/erlang/otp.git</developerConnection>
+ <url>http://github.com/erlang/otp</url>
+ </scm>
+ <developers>
+ <developer>
+ <email>[email protected]</email>
+ </developer>
+ </developers>
+ <organization>
+ <name>Open Source Erlang</name>
+ <url>http://www.erlang.org/</url>
+ </organization>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <source>1.5</source>
+ <target>1.5</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <phase>generate-sources</phase>
+ <goals>
+ <goal>add-source</goal>
+ </goals>
+ <configuration>
+ <sources>
+ <source>java_src</source>
+ </sources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <distributionManagement>
+ <repository>
+ <id>ossrh</id>
+ <url>http://oss.sonatype.org/service/local/staging/deploy/maven2</url>
+ </repository>
+ <snapshotRepository>
+ <id>ossrh</id>
+ <url>http://oss.sonatype.org/content/repositories/snapshots</url>
+ </snapshotRepository>
+ </distributionManagement>
+ <profiles>
+ <profile>
+ <id>release-sign-artifacts</id>
+ <activation>
+ <property>
+ <name>performRelease</name>
+ <value>true</value>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-gpg-plugin</artifactId>
+ <version>1.0-alpha-4</version>
+ <executions>
+ <execution>
+ <id>sign-artifacts</id>
+ <phase>verify</phase>
+ <goals>
+ <goal>sign</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+</project>
diff --git a/lib/kernel/doc/src/file.xml b/lib/kernel/doc/src/file.xml
index 50f9722a1c..a9ceac0bcf 100644
--- a/lib/kernel/doc/src/file.xml
+++ b/lib/kernel/doc/src/file.xml
@@ -62,6 +62,25 @@ time() = {{Year, Month, Day}, {Hour, Minute, Second}}
</section>
<funcs>
<func>
+ <name>advise(IoDevice, Offset, Length, Advise) -> ok | {error, Reason}</name>
+ <fsummary>Predeclare an access pattern for file data</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Offset = int()</v>
+ <v>Length = int()</v>
+ <v>Advise = posix_file_advise()</v>
+ <v>posix_file_advise() = normal | sequential | random | no_reuse
+ | will_need | dont_need</v>
+ <v>Reason = ext_posix()</v>
+ </type>
+ <desc>
+ <p><c>advise/4</c> can be used to announce an intention to access file
+ data in a specific pattern in the future, thus allowing the
+ operating system to perform appropriate optimizations.</p>
+ <p>On some platforms, this function might have no effect.</p>
+ </desc>
+ </func>
+ <func>
<name>change_group(Filename, Gid) -> ok | {error, Reason}</name>
<fsummary>Change group of a file</fsummary>
<type>
@@ -584,7 +603,7 @@ f.txt: {person, "kalle", 25}.
<type>
<v>Filename = name()</v>
<v>Modes = [Mode]</v>
- <v>&nbsp;Mode = read | write | append | raw | binary | {delayed_write, Size, Delay} | delayed_write | {read_ahead, Size} | read_ahead | compressed</v>
+ <v>&nbsp;Mode = read | write | append | exclusive | raw | binary | {delayed_write, Size, Delay} | delayed_write | {read_ahead, Size} | read_ahead | compressed</v>
<v>&nbsp;&nbsp;Size = Delay = int()</v>
<v>IoDevice = io_device()</v>
<v>Reason = ext_posix() | system_limit</v>
@@ -611,6 +630,17 @@ f.txt: {person, "kalle", 25}.
file opened with <c>append</c> will take place at
the end of the file.</p>
</item>
+ <tag><c>exclusive</c></tag>
+ <item>
+ <p>The file, when opened for writing, is created if it
+ does not exist. If the file exists, open will return
+ <c>{error, eexist}</c>.</p>
+ <warning><p>This option does not guarantee exclusiveness on
+ file systems that do not support O_EXCL properly,
+ such as NFS. Do not depend on this option unless you
+ know that the file system supports it (in general, local
+ file systems should be safe).</p></warning>
+ </item>
<tag><c>raw</c></tag>
<item>
<p>The <c>raw</c> option allows faster access to a file,
@@ -1641,6 +1671,33 @@ f.txt: {person, "kalle", 25}.
</desc>
</func>
<func>
+ <name>datasync(IoDevice) -> ok | {error, Reason}</name>
+ <fsummary>Synchronizes the in-memory data of a file, ignoring most of its metadata, with that on the physical medium</fsummary>
+ <type>
+ <v>IoDevice = io_device()</v>
+ <v>Reason = ext_posix() | terminated</v>
+ </type>
+ <desc>
+ <p>Makes sure that any buffers kept by the operating system
+ (not by the Erlang runtime system) are written to disk. In
+ many ways it's resembles fsync but it not requires to update
+ some of file's metadata such as the access time. On
+ some platforms, this function might have no effect.</p>
+ <p>Applications that access databases or log files often write
+ a tiny data fragment (e.g., one line in a log file) and then
+ call fsync() immediately in order to ensure that the written
+ data is physically stored on the harddisk. Unfortunately, fsync()
+ will always initiate two write operations: one for the newly
+ written data and another one in order to update the modification
+ time stored in the inode. If the modification time is not a part
+ of the transaction concept fdatasync() can be used to avoid
+ unnecessary inode disk write operations.</p>
+ <p>Available only in some POSIX systems. This call results in a
+ call to fsync(), or has no effect, in systems not implementing
+ the fdatasync syscall.</p>
+ </desc>
+ </func>
+ <func>
<name>truncate(IoDevice) -> ok | {error, Reason}</name>
<fsummary>Truncate a file</fsummary>
<type>
diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml
index 3a8011e28b..fb09092f1c 100644
--- a/lib/kernel/doc/src/gen_sctp.xml
+++ b/lib/kernel/doc/src/gen_sctp.xml
@@ -1173,7 +1173,7 @@ client_loop(S, Peer1, Port1, AssocId1, Peer2, Port2, AssocId2) -&gt;
<title>SEE ALSO</title>
<p><seealso marker="inet">inet(3)</seealso>,
<seealso marker="gen_tcp">gen_tcp(3)</seealso>,
- <seealso marker="gen_udp">gen_upd(3)</seealso>,
+ <seealso marker="gen_udp">gen_udp(3)</seealso>,
<url href="http://www.rfc-archive.org/getrfc.php?rfc=2960">RFC2960</url> (Stream Control Transmission Protocol),
<url href="http://tools.ietf.org/html/draft-ietf-tsvwg-sctpsocket-13">Sockets API Extensions for SCTP.</url></p>
<marker id="authors"></marker>
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 9c3f6524ae..b503716037 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -30,6 +30,121 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 2.14</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ os:find_executable can now be fed with the complete name
+ of the executable on Windows and still find it. I.e
+ os:find_executable("werl.exe") will work as
+ os:find_executable("werl").</p>
+ <p>
+ Own Id: OTP-3626</p>
+ </item>
+ <item>
+ <p>
+ The shell's line editing has been improved to more
+ resemble the behaviour of readline and other shells.
+ (Thanks to Dave Peticolas)</p>
+ <p>
+ Own Id: OTP-8635</p>
+ </item>
+ <item>
+ <p>Under certain circumstances the net kernel could hang.
+ (Thanks to Scott Lystig Fritchie.)</p>
+ <p>
+ Own Id: OTP-8643 Aux Id: seq11584 </p>
+ </item>
+ <item>
+ <p>
+ The kernel DNS resolver was leaking one or two ports if
+ the DNS reply could not be parsed or if the resolver(s)
+ caused noconnection type errors. Bug now fixed. A DNS
+ specification borderline truncated reply triggering the
+ port leakage bug has also been fixed.</p>
+ <p>
+ Own Id: OTP-8652</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>As of this version, the global name server no longer
+ supports nodes running Erlang/OTP R11B.</p>
+ <p>
+ Own Id: OTP-8527</p>
+ </item>
+ <item>
+ <p>
+ The file module's functions write,read and read_line now
+ handles named io_servers like 'standard_io' and
+ 'standard_error' correctly.</p>
+ <p>
+ Own Id: OTP-8611</p>
+ </item>
+ <item>
+ <p>
+ The functions file:advise/4 and file:datasync/1 have been
+ added. (Thanks to Filipe David Manana.)</p>
+ <p>
+ Own Id: OTP-8637</p>
+ </item>
+ <item>
+ <p>When exchanging groups between nodes <c>pg2</c> did
+ not remove duplicated members. This bug was introduced in
+ R13B03 (kernel-2.13.4).</p>
+ <p>
+ Own Id: OTP-8653</p>
+ </item>
+ <item>
+ <p>
+ There is a new option 'exclusive' to file:open/2 that
+ uses the OS O_EXCL flag where supported to open the file
+ in exclusive mode.</p>
+ <p>
+ Own Id: OTP-8670</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 2.13.5.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A bug introduced in Kernel 2.13.5.2 has been fixed.</p>
+ <p>
+ Own Id: OTP-8686 Aux Id: OTP-8643</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Kernel 2.13.5.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Under certain circumstances the net kernel could hang.
+ (Thanks to Scott Lystig Fritchie.)</p>
+ <p>
+ Own Id: OTP-8643 Aux Id: seq11584</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 2.13.5.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl
index 5277472ea6..42f527f400 100644
--- a/lib/kernel/src/application_controller.erl
+++ b/lib/kernel/src/application_controller.erl
@@ -40,7 +40,7 @@
-export([test_change_apps/2]).
-import(lists, [zf/2, map/2, foreach/2, foldl/3,
- keysearch/3, keydelete/3, keyreplace/4]).
+ keyfind/3, keydelete/3, keyreplace/4]).
-include("application_master.hrl").
@@ -524,7 +524,9 @@ init(Init, Kernel) ->
end.
-%% Check the syntax of the .config file [{ApplicationName, [{Parameter, Value}]}].
+%% Check the syntax of the .config file
+%% [{ApplicationName, [{Parameter, Value}]}].
+
check_conf_data([]) ->
ok;
check_conf_data(ConfData) when is_list(ConfData) ->
@@ -568,8 +570,8 @@ check_para_kernel([]) ->
ok;
check_para_kernel([{distributed, Apps} | ParaList]) when is_list(Apps) ->
case check_distributed(Apps) of
- {error, ErrorMsg} ->
- {error, ErrorMsg};
+ {error, _ErrorMsg} = Error ->
+ Error;
_ ->
check_para_kernel(ParaList)
end;
@@ -633,9 +635,9 @@ handle_call({load_application, Application}, From, S) ->
false ->
{reply, ok, NewS}
end;
- {error, Error} ->
- {reply, {error, Error}, S};
- {'EXIT',R} ->
+ {error, _} = Error ->
+ {reply, Error, S};
+ {'EXIT', R} ->
{reply, {error, R}, S}
end;
@@ -660,7 +662,7 @@ handle_call({start_application, AppName, RestartType}, From, S) ->
%% Incase of erroneous variables do not start the application,
%% if the application is permanent crash the node.
%% Check if the application is already starting.
- case lists:keysearch(AppName, 1, Start_req) of
+ case lists:keyfind(AppName, 1, Start_req) of
false ->
case catch check_start_cond(AppName, RestartType, Started, Running) of
{ok, Appl} ->
@@ -689,13 +691,12 @@ handle_call({start_application, AppName, RestartType}, From, S) ->
{reply, ok, SS}
end
end;
- {error, R} ->
- {reply, {error, R}, S}
+ {error, _R} = Error ->
+ {reply, Error, S}
end;
- {value, {AppName, _FromX}} ->
+ {AppName, _FromX} ->
SS = S#state{start_req = [{AppName, From} | Start_req]},
{noreply, SS}
-
end;
handle_call({permit_application, AppName, Bool}, From, S) ->
@@ -769,11 +770,11 @@ handle_call({permit_application, AppName, Bool}, From, S) ->
{noreply, SS};
%%==========================
- %% unpermit the applicaition
+ %% unpermit the application
%%==========================
%% running
{false, _, _, _, _, {value, {_AppName, Id}}} ->
- {value, {_AppName2, Type}} = keysearch(AppName, 1, Started),
+ {_AppName2, Type} = lists:keyfind(AppName, 1, Started),
stop_appl(AppName, Id, Type),
NRunning = keydelete(AppName, 1, Running),
{reply, ok, S#state{running = NRunning}};
@@ -803,9 +804,9 @@ handle_call({permit_application, AppName, Bool}, From, S) ->
handle_call({stop_application, AppName}, _From, S) ->
#state{running = Running, started = Started} = S,
- case keysearch(AppName, 1, Running) of
- {value, {_AppName, Id}} ->
- {value, {_AppName2, Type}} = keysearch(AppName, 1, Started),
+ case lists:keyfind(AppName, 1, Running) of
+ {_AppName, Id} ->
+ {_AppName2, Type} = lists:keyfind(AppName, 1, Started),
stop_appl(AppName, Id, Type),
NRunning = keydelete(AppName, 1, Running),
NStarted = keydelete(AppName, 1, Started),
@@ -831,8 +832,8 @@ handle_call({change_application_data, Applications, Config}, _From, S) ->
end,
[]),
case catch do_change_apps(Applications, Config, OldAppls) of
- {error, R} ->
- {reply, {error, R}, S};
+ {error, _} = Error ->
+ {reply, Error, S};
{'EXIT', R} ->
{reply, {error, R}, S};
NewAppls ->
@@ -886,10 +887,10 @@ handle_call({control_application, AppName}, {Pid, _Tag}, S) ->
handle_call({start_type, AppName}, _From, S) ->
Starting = S#state.starting,
- StartType = case keysearch(AppName, 1, Starting) of
+ StartType = case lists:keyfind(AppName, 1, Starting) of
false ->
local;
- {value, {_AppName, _RestartType, Type, _F}} ->
+ {_AppName, _RestartType, Type, _F} ->
Type
end,
{reply, StartType, S};
@@ -913,7 +914,7 @@ handle_application_started(AppName, Res, S) ->
#state{starting = Starting, running = Running, started = Started,
start_req = Start_req} = S,
Start_reqN = reply_to_requester(AppName, Start_req, Res),
- {value, {_AppName, RestartType, _Type, _From}} = keysearch(AppName, 1, Starting),
+ {_AppName, RestartType, _Type, _From} = lists:keyfind(AppName, 1, Starting),
case Res of
{ok, Id} ->
case AppName of
@@ -928,7 +929,6 @@ handle_application_started(AppName, Res, S) ->
running = NRunning,
started = NStarted,
start_req = Start_reqN},
-
%% The permission may have been changed during start
Perm = application:get_env(kernel, permissions),
case {Perm, Id} of
@@ -939,10 +939,10 @@ handle_application_started(AppName, Res, S) ->
case lists:member({AppName, false}, Perms) of
true ->
#state{running = StopRunning, started = StopStarted} = NewS,
- case keysearch(AppName, 1, StopRunning) of
- {value, {_AppName, Id}} ->
- {value, {_AppName2, Type}} =
- keysearch(AppName, 1, StopStarted),
+ case lists:keyfind(AppName, 1, StopRunning) of
+ {_AppName, Id} ->
+ {_AppName2, Type} =
+ lists:keyfind(AppName, 1, StopStarted),
stop_appl(AppName, Id, Type),
NStopRunning = keydelete(AppName, 1, StopRunning),
cntrl(AppName, NewS, {ac_application_stopped, AppName}),
@@ -957,12 +957,8 @@ handle_application_started(AppName, Res, S) ->
_ ->
{noreply, NewS}
end;
-
-
-
-
- {error, R} when RestartType =:= temporary ->
- notify_cntrl_started(AppName, undefined, S, {error, R}),
+ {error, R} = Error when RestartType =:= temporary ->
+ notify_cntrl_started(AppName, undefined, S, Error),
info_exited(AppName, R, RestartType),
{noreply, S#state{starting = keydelete(AppName, 1, Starting),
start_req = Start_reqN}};
@@ -987,8 +983,8 @@ handle_application_started(AppName, Res, S) ->
Reason = {application_start_failure, AppName, R},
{stop, to_string(Reason), S}
end;
- {error, R} -> %% permanent
- notify_cntrl_started(AppName, undefined, S, {error, R}),
+ {error, R} = Error -> %% permanent
+ notify_cntrl_started(AppName, undefined, S, Error),
info_exited(AppName, R, RestartType),
Reason = {application_start_failure, AppName, R},
{stop, to_string(Reason), S};
@@ -1018,12 +1014,12 @@ handle_info({ac_load_application_reply, AppName, Res}, S) ->
handle_info({ac_start_application_reply, AppName, Res}, S) ->
Start_req = S#state.start_req,
- case keysearch(AppName, 1, Starting = S#state.starting) of
- {value, {_AppName, RestartType, Type, From}} ->
+ case lists:keyfind(AppName, 1, Starting = S#state.starting) of
+ {_AppName, RestartType, Type, From} ->
case Res of
start_it ->
{true, Appl} = get_loaded(AppName),
- spawn_starter(From, Appl, S, Type),
+ spawn_starter(From, Appl, S, Type),
{noreply, S};
{started, Node} ->
handle_application_started(AppName,
@@ -1037,23 +1033,19 @@ handle_info({ac_start_application_reply, AppName, Res}, S) ->
S#state{starting = keydelete(AppName, 1, Starting),
started = [{AppName, RestartType} | Started],
start_req = Start_reqN}};
- {takeover, Node} ->
+ {takeover, _Node} = Takeover ->
{true, Appl} = get_loaded(AppName),
- spawn_starter(From, Appl, S, {takeover, Node}),
+ spawn_starter(From, Appl, S, Takeover),
NewStarting1 = keydelete(AppName, 1, Starting),
- NewStarting = [{AppName, RestartType, {takeover, Node}, From} | NewStarting1],
+ NewStarting = [{AppName, RestartType, Takeover, From} | NewStarting1],
{noreply, S#state{starting = NewStarting}};
- {error, Reason} when RestartType =:= permanent ->
- Start_reqN =
- reply_to_requester(AppName, Start_req,
- {error, Reason}),
+ {error, Reason} = Error when RestartType =:= permanent ->
+ Start_reqN = reply_to_requester(AppName, Start_req, Error),
{stop, to_string(Reason), S#state{start_req = Start_reqN}};
- {error, Reason} ->
- Start_reqN =
- reply_to_requester(AppName, Start_req,
- {error, Reason}),
+ {error, _Reason} = Error ->
+ Start_reqN = reply_to_requester(AppName, Start_req, Error),
{noreply, S#state{starting =
- keydelete(AppName, 1, Starting),
+ keydelete(AppName, 1, Starting),
start_req = Start_reqN}}
end;
false ->
@@ -1064,8 +1056,8 @@ handle_info({ac_change_application_req, AppName, Msg}, S) ->
Running = S#state.running,
Started = S#state.started,
Starting = S#state.starting,
- case {keysearch(AppName, 1, Running), keysearch(AppName, 1, Started)} of
- {{value, {AppName, Id}}, {value, {_AppName2, Type}}} ->
+ case {keyfind(AppName, 1, Running), keyfind(AppName, 1, Started)} of
+ {{AppName, Id}, {_AppName2, Type}} ->
case Msg of
{started, Node} ->
stop_appl(AppName, Id, Type),
@@ -1158,17 +1150,17 @@ handle_info({'EXIT', Pid, Reason}, S) ->
ets:match_delete(ac_tab, {{application_master, '_'}, Pid}),
NRunning = keydelete(Pid, 2, S#state.running),
NewS = S#state{running = NRunning},
- case keysearch(Pid, 2, S#state.running) of
- {value, {AppName, _AmPid}} ->
+ case lists:keyfind(Pid, 2, S#state.running) of
+ {AppName, _AmPid} ->
cntrl(AppName, S, {ac_application_stopped, AppName}),
- case keysearch(AppName, 1, S#state.started) of
- {value, {_AppName, temporary}} ->
+ case lists:keyfind(AppName, 1, S#state.started) of
+ {_AppName, temporary} ->
info_exited(AppName, Reason, temporary),
{noreply, NewS};
- {value, {_AppName, transient}} when Reason =:= normal ->
+ {_AppName, transient} when Reason =:= normal ->
info_exited(AppName, Reason, transient),
{noreply, NewS};
- {value, {_AppName, Type}} ->
+ {_AppName, Type} ->
info_exited(AppName, Reason, Type),
{stop, to_string({application_terminated, AppName, Reason}), NewS}
end;
@@ -1209,8 +1201,8 @@ code_change(_OldVsn, State, _Extra) ->
%%% Internal functions
%%%-----------------------------------------------------------------
cntrl(AppName, #state{control = Control}, Msg) ->
- case keysearch(AppName, 1, Control) of
- {value, {_AppName, Pid}} ->
+ case lists:keyfind(AppName, 1, Control) of
+ {_AppName, Pid} ->
Pid ! Msg,
true;
false ->
@@ -1310,8 +1302,8 @@ check_start_cond(AppName, RestartType, Started, Running) ->
end.
do_start(AppName, RT, Type, From, S) ->
- RestartType = case keysearch(AppName, 1, S#state.started) of
- {value, {_AppName2, OldRT}} ->
+ RestartType = case lists:keyfind(AppName, 1, S#state.started) of
+ {_AppName2, OldRT} ->
get_restart_type(RT, OldRT);
false ->
RT
@@ -1323,12 +1315,12 @@ do_start(AppName, RT, Type, From, S) ->
{true, Appl} = get_loaded(AppName),
Start_req = S#state.start_req,
spawn_starter(undefined, Appl, S, Type),
- Starting = case keysearch(AppName, 1, S#state.starting) of
+ Starting = case lists:keymember(AppName, 1, S#state.starting) of
false ->
%% UW: don't know if this is necessary
[{AppName, RestartType, Type, From} |
S#state.starting];
- _ ->
+ true ->
S#state.starting
end,
S#state{starting = Starting,
@@ -1368,10 +1360,10 @@ start_appl(Appl, S, Type) ->
end
end, Appl#appl.apps),
case application_master:start_link(ApplData, Type) of
- {ok, Pid} ->
- {ok, Pid};
- {error, Reason} ->
- throw({error, Reason})
+ {ok, _Pid} = Ok ->
+ Ok;
+ {error, _Reason} = Error ->
+ throw(Error)
end
end.
@@ -1463,15 +1455,15 @@ prim_parse(Tokens, Acc) ->
case erl_parse:parse_term(Tokens2 ++ [Dot]) of
{ok, Term} ->
prim_parse(Rest, [Term | Acc]);
- {error, Reason} ->
- {error, Reason}
+ {error, _R} = Error ->
+ Error
end;
{Tokens2, []} ->
case erl_parse:parse_term(Tokens2) of
{ok, Term} ->
{ok, lists:reverse([Term | Acc])};
- {error, Reason} ->
- {error, Reason}
+ {error, _R} = Error ->
+ Error
end
end.
@@ -1484,7 +1476,7 @@ make_appl_i({application, Name, Opts}) when is_atom(Name), is_list(Opts) ->
Apps = get_opt(applications, Opts, []),
Mod =
case get_opt(mod, Opts, []) of
- {M,A} when is_atom(M) -> {M,A};
+ {M,_A}=MA when is_atom(M) -> MA;
[] -> [];
Other -> throw({error, {badstartspec, Other}})
end,
@@ -1493,8 +1485,8 @@ make_appl_i({application, Name, Opts}) when is_atom(Name), is_list(Opts) ->
MaxP = get_opt(maxP, Opts, infinity),
MaxT = get_opt(maxT, Opts, infinity),
IncApps = get_opt(included_applications, Opts, []),
- {#appl_data{name = Name, regs = Regs, mod = Mod, phases = Phases, mods = Mods,
- inc_apps = IncApps, maxP = MaxP, maxT = MaxT},
+ {#appl_data{name = Name, regs = Regs, mod = Mod, phases = Phases,
+ mods = Mods, inc_apps = IncApps, maxP = MaxP, maxT = MaxT},
Env, IncApps, Descr, Id, Vsn, Apps};
make_appl_i({application, Name, Opts}) when is_list(Opts) ->
throw({error,{invalid_name,Name}});
@@ -1573,12 +1565,12 @@ do_change_appl({ok, {ApplData, Env, IncApps, Descr, Id, Vsn, Apps}},
vsn=Vsn,
inc_apps=IncApps,
apps=Apps};
-do_change_appl({error, R}, _Appl, _ConfData) ->
- throw({error, R}).
+do_change_appl({error, _R} = Error, _Appl, _ConfData) ->
+ throw(Error).
get_opt(Key, List, Default) ->
- case keysearch(Key, 1, List) of
- {value, {_Key, Val}} -> Val;
+ case lists:keyfind(Key, 1, List) of
+ {_Key, Val} -> Val;
_ -> Default
end.
@@ -1612,8 +1604,8 @@ make_term(Str) ->
end.
get_env_i(Name, #state{conf_data = ConfData}) when is_list(ConfData) ->
- case keysearch(Name, 1, ConfData) of
- {value, {_Name, Env}} -> Env;
+ case lists:keyfind(Name, 1, ConfData) of
+ {_Name, Env} -> Env;
_ -> []
end;
get_env_i(_Name, _) -> [].
@@ -1633,9 +1625,6 @@ merge_env([{App, AppEnv1} | T], Env2, Res) ->
merge_env([], Env2, Res) ->
Env2 ++ Res.
-
-
-
%% Merges envs for an application. Env2 overrides Env1
merge_app_env(Env1, Env2) ->
merge_app_env(Env1, Env2, []).
@@ -1699,13 +1688,12 @@ do_config_change([], _EnvBefore, Errors) ->
{error, Errors};
do_config_change([{App, _Id} | Apps], EnvBefore, Errors) ->
AppEnvNow = lists:sort(application:get_all_env(App)),
- AppEnvBefore = case lists:keysearch(App, 1, EnvBefore) of
+ AppEnvBefore = case lists:keyfind(App, 1, EnvBefore) of
false ->
[];
- {value, {App, AppEnvBeforeT}} ->
+ {App, AppEnvBeforeT} ->
lists:sort(AppEnvBeforeT)
end,
-
Res =
case AppEnvNow of
AppEnvBefore ->
@@ -1725,12 +1713,12 @@ do_config_change([{App, _Id} | Apps], EnvBefore, Errors) ->
%% if the cb-function is not defined
{'EXIT', {undef, _}} ->
ok;
- {error, Error} ->
- {error, Error};
+ {error, _} = Error ->
+ Error;
Else ->
{error, Else}
end;
- {ok,[]} ->
+ {ok, []} ->
{error, {module_not_defined, App}};
undefined ->
{error, {application_not_found, App}}
@@ -1744,9 +1732,7 @@ do_config_change([{App, _Id} | Apps], EnvBefore, Errors) ->
{error, NewError} ->
do_config_change(Apps, EnvBefore,[NewError | Errors])
end.
-
-
-
+
%%-----------------------------------------------------------------
%% Check if the configuration is changed in anyway.
@@ -1760,21 +1746,17 @@ do_config_diff([], AppEnvBefore, {Changed, New}) ->
do_config_diff(AppEnvNow, [], {Changed, New}) ->
{Changed, AppEnvNow++New, []};
do_config_diff([{Env, Value} | AppEnvNow], AppEnvBefore, {Changed, New}) ->
- case lists:keysearch(Env, 1, AppEnvBefore) of
- {value, {Env, Value}} ->
+ case lists:keyfind(Env, 1, AppEnvBefore) of
+ {Env, Value} ->
do_config_diff(AppEnvNow, lists:keydelete(Env,1,AppEnvBefore), {Changed, New});
- {value, {Env, _OtherValue}} ->
+ {Env, _OtherValue} ->
do_config_diff(AppEnvNow, lists:keydelete(Env,1,AppEnvBefore),
{[{Env, Value} | Changed], New});
false ->
do_config_diff(AppEnvNow, AppEnvBefore, {Changed, [{Env, Value}|New]})
end.
-
-
-
-
%%-----------------------------------------------------------------
%% Read the .config files.
%%-----------------------------------------------------------------
@@ -1929,14 +1911,13 @@ reply_to_requester(AppName, Start_req, Res) ->
%% Update the environment variable permission for an application.
%%-----------------------------------------------------------------
update_permissions(AppName, Bool) ->
- case ets:lookup(ac_tab, {env, kernel, permissions}) of
+ T = {env, kernel, permissions},
+ case ets:lookup(ac_tab, T) of
[] ->
- ets:insert(ac_tab, {{env, kernel, permissions},
- [{AppName, Bool}]});
+ ets:insert(ac_tab, {T, [{AppName, Bool}]});
[{_, Perm}] ->
Perm2 = lists:keydelete(AppName, 1, Perm),
- ets:insert(ac_tab, {{env, kernel, permissions},
- [{AppName, Bool}| Perm2]})
+ ets:insert(ac_tab, {T, [{AppName, Bool}|Perm2]})
end.
%%-----------------------------------------------------------------
diff --git a/lib/kernel/src/application_starter.erl b/lib/kernel/src/application_starter.erl
index 8d839e4662..564366f304 100644
--- a/lib/kernel/src/application_starter.erl
+++ b/lib/kernel/src/application_starter.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-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%
%%
%% ----------------------------------------------------------------------
@@ -42,8 +42,8 @@ start([], _Type, _Apps) ->
ok;
start([{Phase,_PhaseArgs}|Phases], Type, Apps) ->
case start_apps(Phase, Type, Apps) of
- {error, Error} ->
- {error, Error};
+ {error, _} = Error ->
+ Error;
_ ->
start(Phases, Type, Apps)
end.
@@ -56,8 +56,8 @@ start_apps(_Phase, _Type, []) ->
ok;
start_apps(Phase, Type, [App | Apps]) ->
case catch run_start_phase(Phase, Type, App) of
- {error, Error} ->
- {error, Error};
+ {error, _} = Error ->
+ Error;
_ ->
start_apps(Phase, Type, Apps)
end.
@@ -91,10 +91,10 @@ run_the_phase(Phase, Type, App, Mod) ->
{ok, Sp} ->
Sp
end,
- case lists:keysearch(Phase, 1, Start_phases) of
+ case lists:keyfind(Phase, 1, Start_phases) of
false ->
ok;
- {value, {Phase, PhaseArgs}} ->
+ {Phase, PhaseArgs} ->
case catch Mod:start_phase(Phase, Type, PhaseArgs) of
ok ->
ok;
diff --git a/lib/kernel/src/auth.erl b/lib/kernel/src/auth.erl
index 31ee1d1ef4..7fe30ae828 100644
--- a/lib/kernel/src/auth.erl
+++ b/lib/kernel/src/auth.erl
@@ -119,8 +119,8 @@ sync_cookie() ->
-spec print(Node :: node(), Format :: string(), Args :: [_]) -> 'ok'.
-print(Node,Format,Args) ->
- (catch gen_server:cast({auth,Node},{print,Format,Args})).
+print(Node, Format, Args) ->
+ (catch gen_server:cast({auth, Node}, {print, Format, Args})).
%%--gen_server callbacks------------------------------------------------
@@ -154,7 +154,7 @@ handle_call({set_cookie, Node, Cookie}, {_From,_Tag}, State)
%%
%% Happens when the distribution is brought up and
-%% Someone wight have set up the cookie for our new nodename.
+%% someone might have set up the cookie for our new node name.
%%
handle_call({set_cookie, Node, Cookie}, {_From,_Tag}, State) ->
@@ -162,9 +162,9 @@ handle_call({set_cookie, Node, Cookie}, {_From,_Tag}, State) ->
{reply, true, State};
handle_call(sync_cookie, _From, State) ->
- case ets:lookup(State#state.other_cookies,node()) of
+ case ets:lookup(State#state.other_cookies, node()) of
[{_N,C}] ->
- ets:delete(State#state.other_cookies,node()),
+ ets:delete(State#state.other_cookies, node()),
{reply, true, State#state{our_cookie = C}};
[] ->
{reply, true, State}
@@ -182,7 +182,7 @@ handle_call(echo, _From, O) ->
handle_cast({print,What,Args}, O) ->
%% always allow print outs
- error_logger:error_msg(What,Args),
+ error_logger:error_msg(What, Args),
{noreply, O}.
%% A series of bad messages that may come (from older distribution versions).
@@ -206,10 +206,10 @@ handle_info({_From,badcookie,ddd_server,_Mess}, O) ->
{noreply, O};
handle_info({From,badcookie,rex,_Msg}, O) ->
auth:print(getnode(From),
- "~n** Unauthorized rpc attempt to ~w **~n",[node()]),
+ "~n** Unauthorized rpc attempt to ~w **~n", [node()]),
disconnect_node(node(From)),
{noreply, O};
-%% These two messages has to do with the old auth:is_auth() call (net_adm:ping)
+%% These two messages have to do with the old auth:is_auth() call (net_adm:ping)
handle_info({From,badcookie,net_kernel,{'$gen_call',{From,Tag},{is_auth,_Node}}}, O) -> %% ho ho
From ! {Tag, no},
{noreply, O};
@@ -233,7 +233,7 @@ handle_info({From,badcookie,Name,Mess}, Opened) ->
end
end,
{noreply, Opened};
-handle_info(_, O)-> % Ignore anything else especially EXIT signals
+handle_info(_, O) -> % Ignore anything else especially EXIT signals
{noreply, O}.
-spec code_change(term(), state(), term()) -> {'ok', state()}.
@@ -282,7 +282,7 @@ init_cookie() ->
end;
_Other ->
#state{our_cookie = nocookie,
- other_cookies = ets:new(cookies,[?COOKIE_ETS_PROTECTION])}
+ other_cookies = ets:new(cookies, [?COOKIE_ETS_PROTECTION])}
end.
read_cookie() ->
diff --git a/lib/kernel/src/code.erl b/lib/kernel/src/code.erl
index ffe58ae7a9..42d4818f08 100644
--- a/lib/kernel/src/code.erl
+++ b/lib/kernel/src/code.erl
@@ -66,6 +66,8 @@
set_primary_archive/3,
clash/0]).
+-export_type([load_error_rsn/0, load_ret/0]).
+
-include_lib("kernel/include/file.hrl").
%% User interface.
@@ -425,8 +427,8 @@ where_is_file(Path, File) when is_list(Path), is_list(File) ->
FileInfo :: #file_info{})
-> 'ok' | {'error', atom()}.
-set_primary_archive(ArchiveFile0, ArchiveBin, FileInfo)
- when is_list(ArchiveFile0), is_binary(ArchiveBin), is_record(FileInfo, file_info) ->
+set_primary_archive(ArchiveFile0, ArchiveBin, #file_info{} = FileInfo)
+ when is_list(ArchiveFile0), is_binary(ArchiveBin) ->
ArchiveFile = filename:absname(ArchiveFile0),
case call({set_primary_archive, ArchiveFile, ArchiveBin, FileInfo}) of
{ok, []} ->
@@ -473,7 +475,7 @@ decorate([], _) -> [];
decorate([File|Tail], Dir) ->
[{Dir, File} | decorate(Tail, Dir)].
-filter(_Ext, Dir, {error,_}) ->
+filter(_Ext, Dir, error) ->
io:format("** Bad path can't read ~s~n", [Dir]), [];
filter(Ext, _, {ok,Files}) ->
filter2(Ext, length(Ext), Files).
diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl
index 2bc93b8238..4a1fc7df34 100644
--- a/lib/kernel/src/code_server.erl
+++ b/lib/kernel/src/code_server.erl
@@ -66,8 +66,8 @@ init(Ref, Parent, [Root,Mode0]) ->
Mode =
case Mode0 of
- minimal -> interactive;
- _ -> Mode0
+ minimal -> interactive;
+ _ -> Mode0
end,
IPath =
@@ -75,7 +75,7 @@ init(Ref, Parent, [Root,Mode0]) ->
interactive ->
LibDir = filename:append(Root, "lib"),
{ok,Dirs} = erl_prim_loader:list_dir(LibDir),
- {Paths,_Libs} = make_path(LibDir,Dirs),
+ {Paths,_Libs} = make_path(LibDir, Dirs),
UserLibPaths = get_user_lib_dirs(),
["."] ++ UserLibPaths ++ Paths;
_ ->
@@ -98,7 +98,7 @@ init(Ref, Parent, [Root,Mode0]) ->
end,
Parent ! {Ref,{ok,self()}},
- loop(State#state{supervisor=Parent}).
+ loop(State#state{supervisor = Parent}).
get_user_lib_dirs() ->
case os:getenv("ERL_LIBS") of
@@ -170,8 +170,8 @@ loop(#state{supervisor=Supervisor}=State0) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% System upgrade
-handle_system_msg(SysState,Msg,From,Parent,Misc) ->
- case do_sys_cmd(SysState,Msg,Parent, Misc) of
+handle_system_msg(SysState, Msg, From, Parent, Misc) ->
+ case do_sys_cmd(SysState, Msg, Parent, Misc) of
{suspended, Reply, NMisc} ->
gen_reply(From, Reply),
suspend_loop(suspended, Parent, NMisc);
@@ -208,7 +208,7 @@ do_sys_cmd(SysState, {debug, _What}, _Parent, Misc) ->
do_sys_cmd(suspended, {change_code, Module, Vsn, Extra}, _Parent, Misc0) ->
{Res, Misc} =
case catch ?MODULE:system_code_change(Misc0, Module, Vsn, Extra) of
- {ok, Misc1} -> {ok, Misc1};
+ {ok, _} = Ok -> Ok;
Else -> {{error, Else}, Misc0}
end,
{suspended, Res, Misc};
@@ -219,7 +219,7 @@ system_continue(_Parent, _Debug, State) ->
loop(State).
system_terminate(_Reason, _Parent, _Debug, _State) ->
-% error_msg("~p terminating: ~p~n ",[?MODULE,Reason]),
+ %% error_msg("~p terminating: ~p~n ", [?MODULE, Reason]),
exit(shutdown).
-spec system_code_change(state(), module(), term(), term()) -> {'ok', state()}.
@@ -242,7 +242,7 @@ handle_call({stick_mod,Mod}, {_From,_Tag}, S) ->
handle_call({unstick_mod,Mod}, {_From,_Tag}, S) ->
{reply,stick_mod(Mod, false, S),S};
-handle_call({dir,Dir},{_From,_Tag}, S) ->
+handle_call({dir,Dir}, {_From,_Tag}, S) ->
Root = S#state.root,
Resp = do_dir(Root,Dir,S#state.namedb),
{reply,Resp,S};
@@ -255,43 +255,47 @@ handle_call({load_file,Mod}, Caller, St) ->
load_file(Mod, Caller, St)
end;
-handle_call({add_path,Where,Dir0}, {_From,_Tag}, S=#state{cache=Cache0}) ->
+handle_call({add_path,Where,Dir0}, {_From,_Tag},
+ #state{cache=Cache0,namedb=Namedb,path=Path0}=S) ->
case Cache0 of
no_cache ->
- {Resp,Path} = add_path(Where, Dir0, S#state.path, S#state.namedb),
+ {Resp,Path} = add_path(Where, Dir0, Path0, Namedb),
{reply,Resp,S#state{path=Path}};
_ ->
Dir = absname(Dir0), %% Cache always expands the path
- {Resp,Path} = add_path(Where, Dir, S#state.path, S#state.namedb),
- Cache=update_cache([Dir],Where,Cache0),
+ {Resp,Path} = add_path(Where, Dir, Path0, Namedb),
+ Cache = update_cache([Dir], Where, Cache0),
{reply,Resp,S#state{path=Path,cache=Cache}}
end;
-handle_call({add_paths,Where,Dirs0}, {_From,_Tag}, S=#state{cache=Cache0}) ->
+handle_call({add_paths,Where,Dirs0}, {_From,_Tag},
+ #state{cache=Cache0,namedb=Namedb,path=Path0}=S) ->
case Cache0 of
no_cache ->
- {Resp,Path} = add_paths(Where,Dirs0,S#state.path,S#state.namedb),
- {reply,Resp, S#state{path=Path}};
+ {Resp,Path} = add_paths(Where, Dirs0, Path0, Namedb),
+ {reply,Resp,S#state{path=Path}};
_ ->
%% Cache always expands the path
Dirs = [absname(Dir) || Dir <- Dirs0],
- {Resp,Path} = add_paths(Where, Dirs, S#state.path, S#state.namedb),
+ {Resp,Path} = add_paths(Where, Dirs, Path0, Namedb),
Cache=update_cache(Dirs,Where,Cache0),
{reply,Resp,S#state{cache=Cache,path=Path}}
end;
-handle_call({set_path,PathList}, {_From,_Tag}, S) ->
- Path = S#state.path,
- {Resp, NewPath,NewDb} = set_path(PathList, Path, S#state.namedb),
- {reply,Resp,rehash_cache(S#state{path = NewPath, namedb=NewDb})};
+handle_call({set_path,PathList}, {_From,_Tag},
+ #state{path=Path0,namedb=Namedb}=S) ->
+ {Resp,Path,NewDb} = set_path(PathList, Path0, Namedb),
+ {reply,Resp,rehash_cache(S#state{path=Path,namedb=NewDb})};
-handle_call({del_path,Name}, {_From,_Tag}, S) ->
- {Resp,Path} = del_path(Name,S#state.path,S#state.namedb),
- {reply,Resp,rehash_cache(S#state{path = Path})};
+handle_call({del_path,Name}, {_From,_Tag},
+ #state{path=Path0,namedb=Namedb}=S) ->
+ {Resp,Path} = del_path(Name, Path0, Namedb),
+ {reply,Resp,rehash_cache(S#state{path=Path})};
-handle_call({replace_path,Name,Dir}, {_From,_Tag}, S) ->
- {Resp,Path} = replace_path(Name,Dir,S#state.path,S#state.namedb),
- {reply,Resp,rehash_cache(S#state{path = Path})};
+handle_call({replace_path,Name,Dir}, {_From,_Tag},
+ #state{path=Path0,namedb=Namedb}=S) ->
+ {Resp,Path} = replace_path(Name, Dir, Path0, Namedb),
+ {reply,Resp,rehash_cache(S#state{path=Path})};
handle_call(rehash, {_From,_Tag}, S0) ->
S = create_cache(S0),
@@ -313,12 +317,12 @@ handle_call({load_binary,Mod,File,Bin}, Caller, S) ->
do_load_binary(Mod, File, Bin, Caller, S);
handle_call({load_native_partial,Mod,Bin}, {_From,_Tag}, S) ->
- Result = (catch hipe_unified_loader:load(Mod,Bin)),
+ Result = (catch hipe_unified_loader:load(Mod, Bin)),
Status = hipe_result_to_status(Result),
{reply,Status,S};
handle_call({load_native_sticky,Mod,Bin,WholeModule}, {_From,_Tag}, S) ->
- Result = (catch hipe_unified_loader:load_module(Mod,Bin,WholeModule)),
+ Result = (catch hipe_unified_loader:load_module(Mod, Bin, WholeModule)),
Status = hipe_result_to_status(Result),
{reply,Status,S};
@@ -390,8 +394,8 @@ handle_call({set_primary_archive, File, ArchiveBin, FileInfo}, {_From,_Tag}, S=#
case erl_prim_loader:set_primary_archive(File, ArchiveBin, FileInfo) of
{ok, Files} ->
{reply, {ok, Mode, Files}, S};
- {error, Reason} ->
- {reply, {error, Reason}, S}
+ {error, _Reason} = Error ->
+ {reply, Error, S}
end;
handle_call({is_cached,File}, {_From,_Tag}, S=#state{cache=Cache}) ->
@@ -471,8 +475,8 @@ locate_mods([], _, _, Cache, Path) ->
filter_mods([File|Rest], Where, Exts, Dir, Cache) ->
Ext = filename:extension(File),
Root = list_to_atom(filename:rootname(File, Ext)),
- case lists:keysearch(Ext, 2, Exts) of
- {value,{Type,_}} ->
+ case lists:keyfind(Ext, 2, Exts) of
+ {Type, _} ->
Key = {Type,Root},
case Where of
first ->
@@ -489,7 +493,6 @@ filter_mods([File|Rest], Where, Exts, Dir, Cache) ->
ok
end,
filter_mods(Rest, Where, Exts, Dir, Cache);
-
filter_mods([], _, _, _, Cache) ->
Cache.
@@ -500,27 +503,27 @@ filter_mods([], _, _, _, Cache) ->
%%
%% Create the initial path.
%%
-make_path(BundleDir,Bundles0) ->
+make_path(BundleDir, Bundles0) ->
Bundles = choose_bundles(Bundles0),
- make_path(BundleDir,Bundles,[],[]).
+ make_path(BundleDir, Bundles, [], []).
choose_bundles(Bundles) ->
ArchiveExt = archive_extension(),
- Bs = lists:sort([create_bundle(B,ArchiveExt) || B <- Bundles]),
+ Bs = lists:sort([create_bundle(B, ArchiveExt) || B <- Bundles]),
[FullName || {_Name,_NumVsn,FullName} <-
choose(lists:reverse(Bs), [], ArchiveExt)].
-create_bundle(FullName,ArchiveExt) ->
- BaseName = filename:basename(FullName,ArchiveExt),
+create_bundle(FullName, ArchiveExt) ->
+ BaseName = filename:basename(FullName, ArchiveExt),
case split(BaseName, "-") of
- Toks when length(Toks) > 1 ->
+ [_, _|_] = Toks ->
VsnStr = lists:last(Toks),
case vsn_to_num(VsnStr) of
{ok, VsnNum} ->
- Name = join(lists:sublist(Toks,length(Toks)-1),"-"),
+ Name = join(lists:sublist(Toks, length(Toks)-1),"-"),
{Name,VsnNum,FullName};
false ->
- {FullName, [0], FullName}
+ {FullName,[0],FullName}
end;
_ ->
{FullName,[0],FullName}
@@ -571,8 +574,8 @@ join([], _) ->
[].
choose([{Name,NumVsn,NewFullName}=New|Bs], Acc, ArchiveExt) ->
- case lists:keysearch(Name,1,Acc) of
- {value, {_, NV, OldFullName}} when NV =:= NumVsn ->
+ case lists:keyfind(Name, 1, Acc) of
+ {_, NV, OldFullName} when NV =:= NumVsn ->
case filename:extension(OldFullName) =:= ArchiveExt of
false ->
choose(Bs,Acc, ArchiveExt);
@@ -580,7 +583,7 @@ choose([{Name,NumVsn,NewFullName}=New|Bs], Acc, ArchiveExt) ->
Acc2 = lists:keystore(Name, 1, Acc, New),
choose(Bs,Acc2, ArchiveExt)
end;
- {value, {_, _, _}} ->
+ {_, _, _} ->
choose(Bs,Acc, ArchiveExt);
false ->
choose(Bs,[{Name,NumVsn,NewFullName}|Acc], ArchiveExt)
@@ -604,8 +607,8 @@ make_path(BundleDir,[Bundle|Tail],Res,Bs) ->
Ebin2 = filename:join([filename:dirname(Dir), Base ++ Ext, Base, "ebin"]),
Ebins =
case split(Base, "-") of
- Toks when length(Toks) > 1 ->
- AppName = join(lists:sublist(Toks,length(Toks)-1),"-"),
+ [_, _|_] = Toks ->
+ AppName = join(lists:sublist(Toks, length(Toks)-1),"-"),
Ebin3 = filename:join([filename:dirname(Dir), Base ++ Ext, AppName, "ebin"]),
[Ebin3, Ebin2, Dir];
_ ->
@@ -837,30 +840,25 @@ add_path(_,_,Path,_) ->
%% then the table is created :-)
%%
do_add(first,Dir,Path,NameDb) ->
- update(Dir,NameDb),
+ update(Dir, NameDb),
[Dir|lists:delete(Dir,Path)];
do_add(last,Dir,Path,NameDb) ->
case lists:member(Dir,Path) of
true ->
Path;
false ->
- maybe_update(Dir,NameDb),
+ maybe_update(Dir, NameDb),
Path ++ [Dir]
end.
%% Do not update if the same name already exists !
-maybe_update(Dir,NameDb) ->
- case lookup_name(get_name(Dir),NameDb) of
- false -> update(Dir,NameDb);
- _ -> false
- end.
+maybe_update(Dir, NameDb) ->
+ (lookup_name(get_name(Dir), NameDb) =:= false) andalso update(Dir, NameDb).
update(_Dir, false) ->
- ok;
-update(Dir,NameDb) ->
- replace_name(Dir,NameDb).
-
-
+ true;
+update(Dir, NameDb) ->
+ replace_name(Dir, NameDb).
%%
%% Set a completely new path.
@@ -948,8 +946,8 @@ all_archive_subdirs(AppDir) ->
Base = filename:basename(AppDir),
Dirs =
case split(Base, "-") of
- Toks when length(Toks) > 1 ->
- Base2 = join(lists:sublist(Toks,length(Toks)-1),"-"),
+ [_, _|_] = Toks ->
+ Base2 = join(lists:sublist(Toks, length(Toks)-1), "-"),
[Base2, Base];
_ ->
[Base]
@@ -1062,7 +1060,6 @@ check_pars(Name,Dir) ->
{error,bad_name}
end.
-
del_ebin(Dir) ->
case filename:basename(Dir) of
"ebin" ->
@@ -1081,8 +1078,6 @@ del_ebin(Dir) ->
Dir
end.
-
-
replace_name(Dir, Db) ->
case get_name(Dir) of
Dir ->
@@ -1189,15 +1184,7 @@ get_mods([File|Tail], Extension) ->
get_mods([], _) -> [].
is_sticky(Mod, Db) ->
- case erlang:module_loaded(Mod) of
- true ->
- case ets:lookup(Db, {sticky,Mod}) of
- [] -> false;
- _ -> true
- end;
- false ->
- false
- end.
+ erlang:module_loaded(Mod) andalso (ets:lookup(Db, {sticky, Mod}) =/= []).
add_paths(Where,[Dir|Tail],Path,NameDb) ->
{_,NPath} = add_path(Where,Dir,Path,NameDb),
@@ -1205,7 +1192,6 @@ add_paths(Where,[Dir|Tail],Path,NameDb) ->
add_paths(_,_,Path,_) ->
{ok,Path}.
-
do_load_binary(Module, File, Binary, Caller, St) ->
case modp(Module) andalso modp(File) andalso is_binary(Binary) of
true ->
@@ -1222,7 +1208,6 @@ modp(Atom) when is_atom(Atom) -> true;
modp(List) when is_list(List) -> int_list(List);
modp(_) -> false.
-
load_abs(File, Mod0, Caller, St) ->
Ext = objfile_extension(),
FileName0 = lists:concat([File, Ext]),
@@ -1265,20 +1250,20 @@ try_load_module_1(File, Mod, Bin, Caller, #state{moddb=Db}=St) ->
{reply,{error,sticky_directory},St};
false ->
case catch load_native_code(Mod, Bin) of
- {module,Mod} ->
+ {module,Mod} = Module ->
ets:insert(Db, {Mod,File}),
- {reply,{module,Mod},St};
+ {reply,Module,St};
no_native ->
case erlang:load_module(Mod, Bin) of
- {module,Mod} ->
+ {module,Mod} = Module ->
ets:insert(Db, {Mod,File}),
post_beam_load(Mod),
- {reply,{module,Mod},St};
+ {reply,Module,St};
{error,on_load} ->
handle_on_load(Mod, File, Caller, St);
- {error,What} ->
+ {error,What} = Error ->
error_msg("Loading of ~s failed: ~p\n", [File, What]),
- {reply,{error,What},St}
+ {reply,Error,St}
end;
Error ->
error_msg("Native loading of ~s failed: ~p\n",
diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl
index a2937d60b8..f0d54a2f3e 100644
--- a/lib/kernel/src/dist_util.erl
+++ b/lib/kernel/src/dist_util.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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%
%%
%%%----------------------------------------------------------------------
@@ -564,7 +564,7 @@ recv_challenge(#hs_data{socket=Socket,other_node=Node,
case Recv(Socket, 0, infinity) of
{ok,[$n,V1,V0,Fl1,Fl2,Fl3,Fl4,CA3,CA2,CA1,CA0 | Ns]} ->
Flags = ?u32(Fl1,Fl2,Fl3,Fl4),
- case {list_to_existing_atom(Ns),?u16(V1,V0)} of
+ try {list_to_existing_atom(Ns),?u16(V1,V0)} of
{Node,Version} ->
Challenge = ?u32(CA3,CA2,CA1,CA0),
?trace("recv: node=~w, challenge=~w version=~w\n",
@@ -572,6 +572,9 @@ recv_challenge(#hs_data{socket=Socket,other_node=Node,
{Flags,Challenge};
_ ->
?shutdown(no_node)
+ catch
+ error:badarg ->
+ ?shutdown(no_node)
end;
_ ->
?shutdown(no_node)
diff --git a/lib/kernel/src/erl_boot_server.erl b/lib/kernel/src/erl_boot_server.erl
index c5a4305731..b4c5f5e27c 100644
--- a/lib/kernel/src/erl_boot_server.erl
+++ b/lib/kernel/src/erl_boot_server.erl
@@ -179,15 +179,15 @@ init(Slaves) ->
Pid ! {Ref, L},
%% We trap exit inorder to restart boot_init and udp_port
process_flag(trap_exit, true),
- {ok, #state {priority = 0,
- version = erlang:system_info(version),
- udp_sock = U,
- udp_port = UPort,
- listen_sock = L,
- listen_port = Port,
- slaves = ordsets:from_list(Slaves),
- bootp = Pid
- }}.
+ {ok, #state{priority = 0,
+ version = erlang:system_info(version),
+ udp_sock = U,
+ udp_port = UPort,
+ listen_sock = L,
+ listen_port = Port,
+ slaves = ordsets:from_list(Slaves),
+ bootp = Pid
+ }}.
-spec handle_call('which' | {'add',atom()} | {'delete',atom()}, _, state()) ->
{'reply', 'ok' | [atom()], state()}.
diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl
index c4ff4c7281..4a22637304 100644
--- a/lib/kernel/src/erl_epmd.erl
+++ b/lib/kernel/src/erl_epmd.erl
@@ -141,8 +141,8 @@ handle_call({register, Name, PortNo}, _From, State) ->
end;
handle_call(client_info_req, _From, State) ->
- Reply = {ok,{r4,State#state.name,State#state.port_no}},
- {reply,Reply,State};
+ Reply = {ok,{r4,State#state.name,State#state.port_no}},
+ {reply, Reply, State};
handle_call(stop, _From, State) ->
{stop, shutdown, ok, State}.
diff --git a/lib/kernel/src/error_handler.erl b/lib/kernel/src/error_handler.erl
index 5f2507fc08..17dd02acd4 100644
--- a/lib/kernel/src/error_handler.erl
+++ b/lib/kernel/src/error_handler.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(error_handler).
@@ -117,13 +117,13 @@ stub_function(Mod, Func, Args) ->
check_inheritance(Module, Args) ->
Attrs = erlang:get_module_info(Module, attributes),
- case lists:keysearch(extends, 1, Attrs) of
- {value,{extends,[Base]}} when is_atom(Base), Base =/= Module ->
+ case lists:keyfind(extends, 1, Attrs) of
+ {extends, [Base]} when is_atom(Base), Base =/= Module ->
%% This is just a heuristic for detecting abstract modules
%% with inheritance so they can be handled; it would be
%% much better to do it in the emulator runtime
- case lists:keysearch(abstract, 1, Attrs) of
- {value,{abstract,[true]}} ->
+ case lists:keyfind(abstract, 1, Attrs) of
+ {abstract, [true]} ->
case lists:reverse(Args) of
[M|Rs] when tuple_size(M) > 1,
element(1,M) =:= Module,
diff --git a/lib/kernel/src/file.erl b/lib/kernel/src/file.erl
index 46ffa9d708..cffe4e3db5 100644
--- a/lib/kernel/src/file.erl
+++ b/lib/kernel/src/file.erl
@@ -36,11 +36,11 @@
%% Specialized
-export([ipread_s32bu_p32bu/3]).
%% Generic file contents.
--export([open/2, close/1,
+-export([open/2, close/1, advise/4,
read/2, write/2,
pread/2, pread/3, pwrite/2, pwrite/3,
read_line/1,
- position/2, truncate/1, sync/1,
+ position/2, truncate/1, datasync/1, sync/1,
copy/2, copy/3]).
%% High level operations
-export([consult/1, path_consult/2]).
@@ -61,6 +61,9 @@
-export([ipread_s32bu_p32bu_int/3]).
+%% Types that can be used from other modules -- alphabetically ordered.
+-export_type([date_time/0, fd/0, file_info/0, filename/0, io_device/0,
+ name/0, posix/0]).
%%% Includes and defines
-include("file.hrl").
@@ -81,7 +84,7 @@
-type mode() :: 'read' | 'write' | 'append' | 'raw' | 'binary' |
{'delayed_write', non_neg_integer(), non_neg_integer()} |
'delayed_write' | {'read_ahead', pos_integer()} |
- 'read_ahead' | 'compressed'.
+ 'read_ahead' | 'compressed' | 'exclusive'.
-type name() :: string() | atom() | [name()].
-type posix() :: atom().
-type bindings() :: any().
@@ -89,6 +92,8 @@
-type date() :: {pos_integer(), pos_integer(), pos_integer()}.
-type time() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
-type date_time() :: {date(), time()}.
+-type posix_file_advise() :: 'normal' | 'sequential' | 'random' | 'no_reuse' |
+ 'will_need' | 'dont_need'.
%%%-----------------------------------------------------------------
%%% General functions
@@ -352,10 +357,22 @@ close(#file_descriptor{module = Module} = Handle) ->
close(_) ->
{error, badarg}.
--spec read(File :: io_device(), Size :: non_neg_integer()) ->
+-spec advise(File :: io_device(), Offset :: integer(),
+ Length :: integer(), Advise :: posix_file_advise()) ->
+ 'ok' | {'error', posix()}.
+
+advise(File, Offset, Length, Advise) when is_pid(File) ->
+ R = file_request(File, {advise, Offset, Length, Advise}),
+ wait_file_reply(File, R);
+advise(#file_descriptor{module = Module} = Handle, Offset, Length, Advise) ->
+ Module:advise(Handle, Offset, Length, Advise);
+advise(_, _, _, _) ->
+ {error, badarg}.
+
+-spec read(File :: io_device() | atom(), Size :: non_neg_integer()) ->
'eof' | {'ok', [char()] | binary()} | {'error', posix()}.
-read(File, Sz) when is_pid(File), is_integer(Sz), Sz >= 0 ->
+read(File, Sz) when (is_pid(File) orelse is_atom(File)), is_integer(Sz), Sz >= 0 ->
case io:request(File, {get_chars, '', Sz}) of
Data when is_list(Data); is_binary(Data) ->
{ok, Data};
@@ -368,10 +385,10 @@ read(#file_descriptor{module = Module} = Handle, Sz)
read(_, _) ->
{error, badarg}.
--spec read_line(File :: io_device()) ->
+-spec read_line(File :: io_device() | atom()) ->
'eof' | {'ok', [char()] | binary()} | {'error', posix()}.
-read_line(File) when is_pid(File) ->
+read_line(File) when (is_pid(File) orelse is_atom(File)) ->
case io:request(File, {get_line, ''}) of
Data when is_list(Data); is_binary(Data) ->
{ok, Data};
@@ -422,10 +439,10 @@ pread(#file_descriptor{module = Module} = Handle, Offs, Sz)
pread(_, _, _) ->
{error, badarg}.
--spec write(File :: io_device(), Byte :: iodata()) ->
+-spec write(File :: io_device() | atom(), Byte :: iodata()) ->
'ok' | {'error', posix()}.
-write(File, Bytes) when is_pid(File) ->
+write(File, Bytes) when (is_pid(File) orelse is_atom(File)) ->
case make_binary(Bytes) of
Bin when is_binary(Bin) ->
io:request(File, {put_chars,Bin});
@@ -472,6 +489,16 @@ pwrite(#file_descriptor{module = Module} = Handle, Offs, Bytes) ->
pwrite(_, _, _) ->
{error, badarg}.
+-spec datasync(File :: io_device()) -> 'ok' | {'error', posix()}.
+
+datasync(File) when is_pid(File) ->
+ R = file_request(File, datasync),
+ wait_file_reply(File, R);
+datasync(#file_descriptor{module = Module} = Handle) ->
+ Module:datasync(Handle);
+datasync(_) ->
+ {error, badarg}.
+
-spec sync(File :: io_device()) -> 'ok' | {'error', posix()}.
sync(File) when is_pid(File) ->
diff --git a/lib/kernel/src/file_io_server.erl b/lib/kernel/src/file_io_server.erl
index 37e803c493..39dc32bb79 100644
--- a/lib/kernel/src/file_io_server.erl
+++ b/lib/kernel/src/file_io_server.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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(file_io_server).
@@ -106,21 +106,21 @@ do_start(Spawn, Owner, FileName, ModeList) ->
parse_options(List) ->
parse_options(expand_encoding(List), list, latin1, []).
-parse_options([],list,Uni,Acc) ->
+parse_options([], list, Uni, Acc) ->
{list,Uni,[binary|lists:reverse(Acc)]};
-parse_options([],binary,Uni,Acc) ->
+parse_options([], binary, Uni, Acc) ->
{binary,Uni,lists:reverse(Acc)};
-parse_options([{encoding, Encoding}|T],RMode,_,Acc) ->
+parse_options([{encoding, Encoding}|T], RMode, _, Acc) ->
case valid_enc(Encoding) of
{ok, ExpandedEnc} ->
- parse_options(T,RMode,ExpandedEnc,Acc);
- {error,Reason} ->
- {error,Reason}
+ parse_options(T, RMode, ExpandedEnc, Acc);
+ {error,_Reason} = Error ->
+ Error
end;
-parse_options([binary|T],_,Uni,Acc) ->
- parse_options(T,binary,Uni,[binary|Acc]);
-parse_options([H|T],R,U,Acc) ->
- parse_options(T,R,U,[H|Acc]).
+parse_options([binary|T], _, Uni, Acc) ->
+ parse_options(T, binary, Uni, [binary|Acc]);
+parse_options([H|T], R, U, Acc) ->
+ parse_options(T, R, U, [H|Acc]).
expand_encoding([]) ->
[];
@@ -153,7 +153,6 @@ valid_enc(_Other) ->
{error,badarg}.
-
server_loop(#state{mref = Mref} = State) ->
receive
{file_request, From, ReplyAs, Request} when is_pid(From) ->
@@ -199,6 +198,14 @@ io_reply(From, ReplyAs, Reply) ->
%%%-----------------------------------------------------------------
%%% file requests
+file_request({advise,Offset,Length,Advise},
+ #state{handle=Handle}=State) ->
+ case ?PRIM_FILE:advise(Handle, Offset, Length, Advise) of
+ {error,_}=Reply ->
+ {stop,normal,Reply,State};
+ Reply ->
+ {reply,Reply,State}
+ end;
file_request({pread,At,Sz},
#state{handle=Handle,buf=Buf,read_mode=ReadMode}=State) ->
case position(Handle, At, Buf) of
@@ -220,6 +227,14 @@ file_request({pwrite,At,Data},
Reply ->
std_reply(Reply, State)
end;
+file_request(datasync,
+ #state{handle=Handle}=State) ->
+ case ?PRIM_FILE:datasync(Handle) of
+ {error,_}=Reply ->
+ {stop,normal,Reply,State};
+ Reply ->
+ {reply,Reply,State}
+ end;
file_request(sync,
#state{handle=Handle}=State) ->
case ?PRIM_FILE:sync(Handle) of
@@ -326,7 +341,6 @@ io_request(Unknown,
{error,{error,Reason},State}.
-
%% Process a list of requests as long as the results are ok.
io_request_loop([], Result) ->
@@ -342,7 +356,6 @@ io_request_loop([Request|Tail],
io_request_loop(Tail, io_request(Request, State)).
-
%% I/O request put_chars
%%
put_chars(Chars, latin1, #state{handle=Handle, unic=latin1}=State) ->
@@ -653,20 +666,14 @@ do_setopts(Opts, State) ->
end.
getopts(#state{read_mode=RM, unic=Unic} = State) ->
- Bin = {binary, case RM of
- binary ->
- true;
- _ ->
- false
- end},
+ Bin = {binary, RM =:= binary},
Uni = {encoding, Unic},
{reply,[Bin,Uni],State}.
-
%% Concatenate two binaries and convert the result to list or binary
-cat(B1, B2, binary,latin1,latin1) ->
+cat(B1, B2, binary, latin1, latin1) ->
list_to_binary([B1,B2]);
-cat(B1, B2, binary,InEncoding,OutEncoding) ->
+cat(B1, B2, binary, InEncoding, OutEncoding) ->
case unicode:characters_to_binary([B1,B2],InEncoding,OutEncoding) of
Good when is_binary(Good) ->
Good;
diff --git a/lib/kernel/src/group.erl b/lib/kernel/src/group.erl
index a45ba34eae..f92c6f7208 100644
--- a/lib/kernel/src/group.erl
+++ b/lib/kernel/src/group.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(group).
@@ -477,15 +477,15 @@ get_line(Chars, Pbs, Drv, Encoding) ->
get_line1(edlin:edit_line(Chars, Cont), Drv, new_stack(get(line_buffer)),
Encoding).
-get_line1({done,Line,Rest,Rs}, Drv, _Ls, _Encoding) ->
+get_line1({done,Line,Rest,Rs}, Drv, Ls, _Encoding) ->
send_drv_reqs(Drv, Rs),
- put(line_buffer, [Line|lists:delete(Line, get(line_buffer))]),
+ save_line_buffer(Line, get_lines(Ls)),
{done,Line,Rest};
get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Ls0, Encoding)
when ((Mode =:= none) and (Char =:= $\^P))
or ((Mode =:= meta_left_sq_bracket) and (Char =:= $A)) ->
send_drv_reqs(Drv, Rs),
- case up_stack(Ls0) of
+ case up_stack(save_line(Ls0, edlin:current_line(Cont))) of
{none,_Ls} ->
send_drv(Drv, beep),
get_line1(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
@@ -498,14 +498,14 @@ get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Ls0, Encoding)
Drv,
Ls, Encoding)
end;
-get_line1({undefined,{_A,Mode,Char},_Cs,Cont,Rs}, Drv, Ls0, Encoding)
+get_line1({undefined,{_A,Mode,Char},Cs,Cont,Rs}, Drv, Ls0, Encoding)
when ((Mode =:= none) and (Char =:= $\^N))
or ((Mode =:= meta_left_sq_bracket) and (Char =:= $B)) ->
send_drv_reqs(Drv, Rs),
- case down_stack(Ls0) of
- {none,Ls} ->
- send_drv_reqs(Drv, edlin:erase_line(Cont)),
- get_line1(edlin:start(edlin:prompt(Cont)), Drv, Ls, Encoding);
+ case down_stack(save_line(Ls0, edlin:current_line(Cont))) of
+ {none,_Ls} ->
+ send_drv(Drv, beep),
+ get_line1(edlin:edit_line(Cs, Cont), Drv, Ls0, Encoding);
{Lcs,Ls} ->
send_drv_reqs(Drv, edlin:erase_line(Cont)),
{more_chars,Ncont,Nrs} = edlin:start(edlin:prompt(Cont)),
@@ -627,6 +627,28 @@ down_stack({stack,U,{},[]}) ->
down_stack({stack,U,C,D}) ->
down_stack({stack,[C|U],{},D}).
+save_line({stack, U, {}, []}, Line) ->
+ {stack, U, {}, [Line]};
+save_line({stack, U, _L, D}, Line) ->
+ {stack, U, Line, D}.
+
+get_lines({stack, U, {}, []}) ->
+ U;
+get_lines({stack, U, {}, D}) ->
+ tl(lists:reverse(D, U));
+get_lines({stack, U, L, D}) ->
+ get_lines({stack, U, {}, [L|D]}).
+
+save_line_buffer("\n", Lines) ->
+ save_line_buffer(Lines);
+save_line_buffer(Line, [Line|_Lines]=Lines) ->
+ save_line_buffer(Lines);
+save_line_buffer(Line, Lines) ->
+ save_line_buffer([Line|Lines]).
+
+save_line_buffer(Lines) ->
+ put(line_buffer, Lines).
+
%% This is get_line without line editing (except for backspace) and
%% without echo.
get_password_line(Chars, Drv) ->
diff --git a/lib/kernel/src/heart.erl b/lib/kernel/src/heart.erl
index bad0950fca..e78acfc7a6 100644
--- a/lib/kernel/src/heart.erl
+++ b/lib/kernel/src/heart.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(heart).
@@ -61,8 +61,8 @@ start() ->
wait_for_init_ack(From) ->
receive
- {ok, From} ->
- {ok, From};
+ {ok, From} = Ok ->
+ Ok;
{no_heart, From} ->
ignore;
{Error, From} ->
@@ -119,8 +119,7 @@ wait() ->
start_portprogram() ->
check_start_heart(),
- HeartCmd = "heart -pid " ++ os:getpid() ++ " " ++
- get_heart_timeouts(),
+ HeartCmd = "heart -pid " ++ os:getpid() ++ " " ++ get_heart_timeouts(),
try open_port({spawn, HeartCmd}, [{packet, 2}]) of
Port when is_port(Port) ->
case wait_ack(Port) of
@@ -175,7 +174,7 @@ wait_ack(Port) ->
loop(Parent, Port, Cmd) ->
send_heart_beat(Port),
receive
- {From, set_cmd, NewCmd} when is_list(NewCmd), length(NewCmd) < 2047 ->
+ {From, set_cmd, NewCmd} when length(NewCmd) < 2047 ->
send_heart_cmd(Port, NewCmd),
wait_ack(Port),
From ! {heart, ok},
diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl
index eb503235d8..93d75321ba 100644
--- a/lib/kernel/src/inet.erl
+++ b/lib/kernel/src/inet.erl
@@ -62,6 +62,8 @@
%% timer interface
-export([start_timer/1, timeout/1, timeout/2, stop_timer/1]).
+-export_type([ip_address/0, socket/0]).
+
%% imports
-import(lists, [append/1, duplicate/2, filter/2, foldl/3]).
diff --git a/lib/kernel/src/inet6_tcp_dist.erl b/lib/kernel/src/inet6_tcp_dist.erl
index 34cf582af7..fab00bbb9f 100644
--- a/lib/kernel/src/inet6_tcp_dist.erl
+++ b/lib/kernel/src/inet6_tcp_dist.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(inet6_tcp_dist).
@@ -87,7 +87,7 @@ accept(Listen) ->
accept_loop(Kernel, Listen) ->
case inet6_tcp:accept(Listen) of
{ok, Socket} ->
- Kernel ! {accept,self(),Socket,inet,tcp},
+ Kernel ! {accept,self(),Socket,inet6,tcp},
controller(Kernel, Socket),
accept_loop(Kernel, Listen);
Error ->
@@ -236,8 +236,8 @@ do_setup(Kernel, Node, Type, MyNode, LongOrShortNames,SetupTime) ->
timer = Timer,
this_flags = 0,
other_version = Version,
- f_send = fun inet_tcp:send/2,
- f_recv = fun inet_tcp:recv/3,
+ f_send = fun inet6_tcp:send/2,
+ f_recv = fun inet6_tcp:recv/3,
f_setopts_pre_nodeup =
fun(S) ->
inet:setopts
@@ -262,7 +262,7 @@ do_setup(Kernel, Node, Type, MyNode, LongOrShortNames,SetupTime) ->
address = {Ip,TcpPort},
host = Address,
protocol = tcp,
- family = inet}
+ family = inet6}
end,
mf_tick = fun ?MODULE:tick/1,
mf_getstat = fun ?MODULE:getstat/1,
@@ -302,12 +302,17 @@ splitnode(Node, LongOrShortNames) ->
Host = lists:append(Tail),
case split_node(Host, $., []) of
[_] when LongOrShortNames =:= longnames ->
- error_msg("** System running to use "
- "fully qualified "
- "hostnames **~n"
- "** Hostname ~s is illegal **~n",
- [Host]),
- ?shutdown(Node);
+ case inet_parse:ipv6strict_address(Host) of
+ {ok, _} ->
+ [Name, Host];
+ _ ->
+ error_msg("** System running to use "
+ "fully qualified "
+ "hostnames **~n"
+ "** Hostname ~s is illegal **~n",
+ [Host]),
+ ?shutdown(Node)
+ end;
L when length(L) > 1, LongOrShortNames =:= shortnames ->
error_msg("** System NOT running to use fully qualified "
"hostnames **~n"
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index ec95620cc0..d4749b9756 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -102,14 +102,14 @@
start() ->
case gen_server:start({local, inet_db}, inet_db, [], []) of
- {ok,Pid} -> inet_config:init(), {ok,Pid};
+ {ok, _Pid}=Ok -> inet_config:init(), Ok;
Error -> Error
end.
start_link() ->
case gen_server:start_link({local, inet_db}, inet_db, [], []) of
- {ok,Pid} -> inet_config:init(), {ok,Pid};
+ {ok, _Pid}=Ok -> inet_config:init(), Ok;
Error -> Error
end.
@@ -140,7 +140,6 @@ add_hosts(File) ->
Error -> Error
end.
-
add_host(IP, Names) -> call({add_host, IP, Names}).
del_host(IP) -> call({del_host, IP}).
@@ -482,10 +481,7 @@ res_check_option_absfile(F) ->
res_check_list([], _Fun) -> true;
res_check_list([H|T], Fun) ->
- case Fun(H) of
- true -> res_check_list(T, Fun);
- false -> false
- end;
+ Fun(H) andalso res_check_list(T, Fun);
res_check_list(_, _Fun) -> false.
res_check_ns({{A,B,C,D,E,F,G,H}, Port})
@@ -497,12 +493,12 @@ res_check_ns(_) -> false.
res_check_search("") -> true;
res_check_search(Dom) -> inet_parse:visible_string(Dom).
-socks_option(server) -> db_get(socks5_server);
-socks_option(port) -> db_get(socks5_port);
-socks_option(methods) -> db_get(socks5_methods);
-socks_option(noproxy) -> db_get(socks5_noproxy).
+socks_option(server) -> db_get(socks5_server);
+socks_option(port) -> db_get(socks5_port);
+socks_option(methods) -> db_get(socks5_methods);
+socks_option(noproxy) -> db_get(socks5_noproxy).
-gethostname() -> db_get(hostname).
+gethostname() -> db_get(hostname).
res_update_conf() ->
res_update(res_resolv_conf, res_resolv_conf_tm, res_resolv_conf_info,
@@ -591,15 +587,13 @@ getbyname(Name, Type) ->
getbysearch(Name, Dot, [Dom | Ds], Type, _) ->
case hostent_by_domain(Name ++ Dot ++ Dom, Type) of
- {ok, HEnt} -> {ok, HEnt};
- Error ->
- getbysearch(Name, Dot, Ds, Type, Error)
+ {ok, _HEnt}=Ok -> Ok;
+ Error -> getbysearch(Name, Dot, Ds, Type, Error)
end;
getbysearch(_Name, _Dot, [], _Type, Error) ->
Error.
-
%%
%% get_searchlist
%%
@@ -610,7 +604,6 @@ get_searchlist() ->
end.
-
make_hostent(Name, Addrs, Aliases, ?S_A) ->
#hostent {
h_name = Name,
@@ -1146,7 +1139,6 @@ handle_call(Request, From, #state{db=Db}=State) ->
{reply, error, State}
end.
-
%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State} |
@@ -1359,16 +1351,15 @@ do_add_rr(RR, Db, State) ->
TM = times(),
case alloc_entry(Db, CacheDb, TM) of
true ->
- cache_rr(Db, CacheDb, RR#dns_rr { tm = TM,
- cnt = TM });
+ cache_rr(Db, CacheDb, RR#dns_rr{tm = TM, cnt = TM});
_ ->
false
end.
cache_rr(_Db, Cache, RR) ->
%% delete possible old entry
- ets:match_delete(Cache, RR#dns_rr { cnt = '_', tm = '_', ttl = '_',
- bm = '_', func = '_'}),
+ ets:match_delete(Cache, RR#dns_rr{cnt = '_', tm = '_', ttl = '_',
+ bm = '_', func = '_'}),
ets:insert(Cache, RR).
times() ->
@@ -1378,9 +1369,9 @@ times() ->
%% lookup and remove old entries
do_lookup_rr(Domain, Class, Type) ->
- match_rr(#dns_rr { domain = tolower(Domain), class = Class,type = Type,
- cnt = '_', tm = '_', ttl = '_',
- bm = '_', func = '_', data = '_'}).
+ match_rr(#dns_rr{domain = tolower(Domain), class = Class,type = Type,
+ cnt = '_', tm = '_', ttl = '_',
+ bm = '_', func = '_', data = '_'}).
match_rr(RR) ->
filter_rr(ets:match_object(inet_cache, RR), times()).
@@ -1431,7 +1422,7 @@ dn_in_addr_arpa(A,B,C,D) ->
integer_to_list(A) ++ ".in-addr.arpa".
dnib(X) ->
- [ hex(X), $., hex(X bsr 4), $., hex(X bsr 8), $., hex(X bsr 12), $.].
+ [hex(X), $., hex(X bsr 4), $., hex(X bsr 8), $., hex(X bsr 12), $.].
hex(X) ->
X4 = (X band 16#f),
@@ -1526,12 +1517,7 @@ alloc_entry(CacheDb, OldSize, TM, N) ->
delete_n_oldest(CacheDb, TM, OldestTM, N) ->
DelTM = trunc((TM - OldestTM) * 0.3) + OldestTM,
- case delete_older(CacheDb, DelTM, N) of
- 0 ->
- false;
- _ ->
- true
- end.
+ delete_older(CacheDb, DelTM, N) =/= 0.
%% Delete entries with latest access time older than TM.
%% Delete max N number of entries.
diff --git a/lib/kernel/src/inet_dns.erl b/lib/kernel/src/inet_dns.erl
index 669a361c9d..1289e176c7 100644
--- a/lib/kernel/src/inet_dns.erl
+++ b/lib/kernel/src/inet_dns.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(inet_dns).
@@ -129,27 +129,33 @@ do_decode(<<Id:16,
RA:1,PR:1,_:2,Rcode:4,
QdCount:16,AnCount:16,NsCount:16,ArCount:16,
QdBuf/binary>>=Buffer) ->
- {AnBuf,QdList} = decode_query_section(QdBuf,QdCount,Buffer),
- {NsBuf,AnList} = decode_rr_section(AnBuf,AnCount,Buffer),
- {ArBuf,NsList} = decode_rr_section(NsBuf,NsCount,Buffer),
- {Rest,ArList} = decode_rr_section(ArBuf,ArCount,Buffer),
+ {AnBuf,QdList,QdTC} = decode_query_section(QdBuf,QdCount,Buffer),
+ {NsBuf,AnList,AnTC} = decode_rr_section(AnBuf,AnCount,Buffer),
+ {ArBuf,NsList,NsTC} = decode_rr_section(NsBuf,NsCount,Buffer),
+ {Rest,ArList,ArTC} = decode_rr_section(ArBuf,ArCount,Buffer),
case Rest of
<<>> ->
+ HdrTC = decode_boolean(TC),
DnsHdr =
#dns_header{id=Id,
qr=decode_boolean(QR),
opcode=decode_opcode(Opcode),
aa=decode_boolean(AA),
- tc=decode_boolean(TC),
+ tc=HdrTC,
rd=decode_boolean(RD),
ra=decode_boolean(RA),
pr=decode_boolean(PR),
rcode=Rcode},
- #dns_rec{header=DnsHdr,
- qdlist=QdList,
- anlist=AnList,
- nslist=NsList,
- arlist=ArList};
+ case QdTC or AnTC or NsTC or ArTC of
+ true when not HdrTC ->
+ throw(?DECODE_ERROR);
+ _ ->
+ #dns_rec{header=DnsHdr,
+ qdlist=QdList,
+ anlist=AnList,
+ nslist=NsList,
+ arlist=ArList}
+ end;
_ ->
%% Garbage data after DNS message
throw(?DECODE_ERROR)
@@ -161,8 +167,10 @@ do_decode(_) ->
decode_query_section(Bin, N, Buffer) ->
decode_query_section(Bin, N, Buffer, []).
+decode_query_section(<<>>=Rest, N, _Buffer, Qs) ->
+ {Rest,reverse(Qs),N =/= 0};
decode_query_section(Rest, 0, _Buffer, Qs) ->
- {Rest,reverse(Qs)};
+ {Rest,reverse(Qs),false};
decode_query_section(Bin, N, Buffer, Qs) ->
case decode_name(Bin, Buffer) of
{<<Type:16,Class:16,Rest/binary>>,Name} ->
@@ -179,8 +187,10 @@ decode_query_section(Bin, N, Buffer, Qs) ->
decode_rr_section(Bin, N, Buffer) ->
decode_rr_section(Bin, N, Buffer, []).
+decode_rr_section(<<>>=Rest, N, _Buffer, RRs) ->
+ {Rest,reverse(RRs),N =/= 0};
decode_rr_section(Rest, 0, _Buffer, RRs) ->
- {Rest,reverse(RRs)};
+ {Rest,reverse(RRs),false};
decode_rr_section(Bin, N, Buffer, RRs) ->
case decode_name(Bin, Buffer) of
{<<T:16/unsigned,C:16/unsigned,TTL:4/binary,
diff --git a/lib/kernel/src/inet_gethost_native.erl b/lib/kernel/src/inet_gethost_native.erl
index b7b3007b99..db3e44ce6f 100644
--- a/lib/kernel/src/inet_gethost_native.erl
+++ b/lib/kernel/src/inet_gethost_native.erl
@@ -154,7 +154,7 @@ run_once() ->
{Port, {data, <<1:32, BinReply/binary>>}} ->
Pid ! {R, {ok, BinReply}}
after Timeout ->
- Pid ! {R,{error,timeout}}
+ Pid ! {R, {error, timeout}}
end.
-spec terminate(term(), pid()) -> 'ok'.
@@ -342,14 +342,14 @@ pick_client(State,RID,Clid) ->
{last, SoleClient}; % Note, not removed, the caller
% should cleanup request data
CList ->
- case lists:keysearch(Clid,1,CList) of
- {value, Client} ->
+ case lists:keyfind(Clid,1,CList) of
+ false ->
+ false;
+ Client ->
NCList = lists:keydelete(Clid,1,CList),
ets:insert(State#state.requests,
R#request{clients = NCList}),
- {more, Client};
- false ->
- false
+ {more, Client}
end
end
end.
@@ -387,8 +387,7 @@ restart_port(#state{port = Port, requests = Requests}) ->
end,
Requests),
NewPort.
-
-
+
do_open_port(Poolsize, ExtraArgs) ->
try
diff --git a/lib/kernel/src/inet_res.erl b/lib/kernel/src/inet_res.erl
index 9b9e078898..de0f23bf24 100644
--- a/lib/kernel/src/inet_res.erl
+++ b/lib/kernel/src/inet_res.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
%% RFC 1035, 2671, 2782, 2915.
@@ -592,6 +592,7 @@ query_retries(_Q, _NSs, _Timer, Retry, Retry, S) ->
query_retries(Q, NSs, Timer, Retry, I, S0) ->
Num = length(NSs),
if Num =:= 0 ->
+ udp_close(S0),
{error,timeout};
true ->
case query_nss(Q, NSs, Timer, Retry, I, S0, []) of
diff --git a/lib/kernel/src/kernel_config.erl b/lib/kernel/src/kernel_config.erl
index cdad354876..b1daf655c9 100644
--- a/lib/kernel/src/kernel_config.erl
+++ b/lib/kernel/src/kernel_config.erl
@@ -92,9 +92,9 @@ code_change(_OldVsn, State, _Extra) ->
%%-----------------------------------------------------------------
sync_nodes() ->
case catch get_sync_data() of
- {error, Reason} ->
+ {error, Reason} = Error ->
error_logger:format("~p", [Reason]),
- {error, Reason};
+ Error;
{infinity, MandatoryNodes, OptionalNodes} ->
case wait_nodes(MandatoryNodes, OptionalNodes) of
ok ->
diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl
index 3afaedf274..0e5cc8c2c6 100644
--- a/lib/kernel/src/net_kernel.erl
+++ b/lib/kernel/src/net_kernel.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(net_kernel).
@@ -72,7 +72,7 @@
-export([publish_on_node/1, update_publish_nodes/1]).
-%% Internal Exports
+%% Internal Exports
-export([do_spawn/3,
spawn_func/6,
ticker/2,
@@ -94,7 +94,7 @@
connecttime, %% the connection setuptime.
connections, %% table of connections
conn_owners = [], %% List of connection owner pids,
- pend_owners = [], %% List of potential owners
+ pend_owners = [], %% List of potential owners
listen, %% list of #listen
allowed, %% list of allowed nodes in a restricted system
verbose = 0, %% level of verboseness
@@ -232,7 +232,7 @@ do_connect(Node, Type, WaitForBarred) -> %% Type = normal | hidden
%% "connected from other end.~n",[Node]),
true;
{Pid, false} ->
- ?connect_failure(Node,{barred_connection,
+ ?connect_failure(Node,{barred_connection,
ets:lookup(sys_dist, Node)}),
%%io:format("Net Kernel: barred connection (~p) "
%% "- failure.~n",[Node]),
@@ -244,12 +244,12 @@ do_connect(Node, Type, WaitForBarred) -> %% Type = normal | hidden
{ok, never} ->
?connect_failure(Node,{dist_auto_connect,never}),
false;
- % This might happen due to connection close
+ % This might happen due to connection close
% not beeing propagated to user space yet.
- % Save the day by just not connecting...
+ % Save the day by just not connecting...
{ok, once} when Else =/= [],
(hd(Else))#connection.state =:= up ->
- ?connect_failure(Node,{barred_connection,
+ ?connect_failure(Node,{barred_connection,
ets:lookup(sys_dist, Node)}),
false;
_ ->
@@ -276,8 +276,8 @@ passive_connect_monitor(Parent, Node) ->
Parent ! {self(),true}
end
end.
-
-%% If the net_kernel isn't running we ignore all requests to the
+
+%% If the net_kernel isn't running we ignore all requests to the
%% kernel, thus basically accepting them :-)
request(Req) ->
case whereis(net_kernel) of
@@ -302,7 +302,7 @@ start_link([Name, LongOrShortNames]) ->
start_link([Name, LongOrShortNames, 15000]);
start_link([Name, LongOrShortNames, Ticktime]) ->
- case gen_server:start_link({local, net_kernel}, net_kernel,
+ case gen_server:start_link({local, net_kernel}, net_kernel,
{Name, LongOrShortNames, Ticktime}, []) of
{ok, Pid} ->
{ok, Pid};
@@ -313,7 +313,7 @@ start_link([Name, LongOrShortNames, Ticktime]) ->
end.
%% auth:get_cookie should only be able to return an atom
-%% tuple cookies are unknowns
+%% tuple cookies are unknowns
init({Name, LongOrShortNames, TickT}) ->
process_flag(trap_exit,true),
@@ -354,13 +354,13 @@ init({Name, LongOrShortNames, TickT}) ->
%% The response is delayed until the connection is up and
%% running.
%%
-handle_call({connect, _, Node}, _From, State) when Node =:= node() ->
- {reply, true, State};
+handle_call({connect, _, Node}, From, State) when Node =:= node() ->
+ async_reply({reply, true, State}, From);
handle_call({connect, Type, Node}, From, State) ->
verbose({connect, Type, Node}, 1, State),
case ets:lookup(sys_dist, Node) of
[Conn] when Conn#connection.state =:= up ->
- {reply, true, State};
+ async_reply({reply, true, State}, From);
[Conn] when Conn#connection.state =:= pending ->
Waiting = Conn#connection.waiting,
ets:insert(sys_dist, Conn#connection{waiting = [From|Waiting]}),
@@ -376,74 +376,75 @@ handle_call({connect, Type, Node}, From, State) ->
{noreply,State#state{conn_owners=Owners}};
_ ->
?connect_failure(Node, {setup_call, failed}),
- {reply, false, State}
+ async_reply({reply, false, State}, From)
end
end;
%%
%% Close the connection to Node.
%%
-handle_call({disconnect, Node}, _From, State) when Node =:= node() ->
- {reply, false, State};
-handle_call({disconnect, Node}, _From, State) ->
+handle_call({disconnect, Node}, From, State) when Node =:= node() ->
+ async_reply({reply, false, State}, From);
+handle_call({disconnect, Node}, From, State) ->
verbose({disconnect, Node}, 1, State),
{Reply, State1} = do_disconnect(Node, State),
- {reply, Reply, State1};
+ async_reply({reply, Reply, State1}, From);
-%%
+%%
%% The spawn/4 BIF ends up here.
-%%
+%%
handle_call({spawn,M,F,A,Gleader},{From,Tag},State) when is_pid(From) ->
do_spawn([no_link,{From,Tag},M,F,A,Gleader],[],State);
-%%
+%%
%% The spawn_link/4 BIF ends up here.
-%%
+%%
handle_call({spawn_link,M,F,A,Gleader},{From,Tag},State) when is_pid(From) ->
do_spawn([link,{From,Tag},M,F,A,Gleader],[],State);
-%%
+%%
%% The spawn_opt/5 BIF ends up here.
-%%
+%%
handle_call({spawn_opt,M,F,A,O,L,Gleader},{From,Tag},State) when is_pid(From) ->
do_spawn([L,{From,Tag},M,F,A,Gleader],O,State);
-%%
+%%
%% Only allow certain nodes.
-%%
-handle_call({allow, Nodes}, _From, State) ->
+%%
+handle_call({allow, Nodes}, From, State) ->
case all_atoms(Nodes) of
true ->
Allowed = State#state.allowed,
- {reply,ok,State#state{allowed = Allowed ++ Nodes}};
+ async_reply({reply,ok,State#state{allowed = Allowed ++ Nodes}},
+ From);
false ->
- {reply,error,State}
+ async_reply({reply,error,State}, From)
end;
-%%
+%%
%% authentication, used by auth. Simply works as this:
%% if the message comes through, the other node IS authorized.
-%%
-handle_call({is_auth, _Node}, _From, State) ->
- {reply,yes,State};
+%%
+handle_call({is_auth, _Node}, From, State) ->
+ async_reply({reply,yes,State}, From);
-%%
+%%
%% Not applicable any longer !?
-%%
-handle_call({apply,_Mod,_Fun,_Args}, {From,Tag}, State)
+%%
+handle_call({apply,_Mod,_Fun,_Args}, {From,Tag}, State)
when is_pid(From), node(From) =:= node() ->
- gen_server:reply({From,Tag}, not_implemented),
+ async_gen_server_reply({From,Tag}, not_implemented),
% Port = State#state.port,
% catch apply(Mod,Fun,[Port|Args]),
{noreply,State};
-handle_call(longnames, _From, State) ->
- {reply, get(longnames), State};
+handle_call(longnames, From, State) ->
+ async_reply({reply, get(longnames), State}, From);
-handle_call({update_publish_nodes, Ns}, _From, State) ->
- {reply, ok, State#state{publish_on_nodes = Ns}};
+handle_call({update_publish_nodes, Ns}, From, State) ->
+ async_reply({reply, ok, State#state{publish_on_nodes = Ns}}, From);
-handle_call({publish_on_node, Node}, _From, State) ->
+handle_call({publish_on_node, Node}, From, State) ->
NewState = case State#state.publish_on_nodes of
undefined ->
State#state{publish_on_nodes =
@@ -457,11 +458,12 @@ handle_call({publish_on_node, Node}, _From, State) ->
Nodes ->
lists:member(Node, Nodes)
end,
- {reply, Publish, NewState};
+ async_reply({reply, Publish, NewState}, From);
-handle_call({verbose, Level}, _From, State) ->
- {reply, State#state.verbose, State#state{verbose = Level}};
+handle_call({verbose, Level}, From, State) ->
+ async_reply({reply, State#state.verbose, State#state{verbose = Level}},
+ From);
%%
%% Set new ticktime
@@ -471,16 +473,16 @@ handle_call({verbose, Level}, _From, State) ->
%% #tick_change{} record if the ticker process has been upgraded;
%% otherwise, an integer or an atom.
-handle_call(ticktime, _, #state{tick = #tick{time = T}} = State) ->
- {reply, T, State};
-handle_call(ticktime, _, #state{tick = #tick_change{time = T}} = State) ->
- {reply, {ongoing_change_to, T}, State};
+handle_call(ticktime, From, #state{tick = #tick{time = T}} = State) ->
+ async_reply({reply, T, State}, From);
+handle_call(ticktime, From, #state{tick = #tick_change{time = T}} = State) ->
+ async_reply({reply, {ongoing_change_to, T}, State}, From);
-handle_call({new_ticktime,T,_TP}, _, #state{tick = #tick{time = T}} = State) ->
+handle_call({new_ticktime,T,_TP}, From, #state{tick = #tick{time = T}} = State) ->
?tckr_dbg(no_tick_change),
- {reply, unchanged, State};
+ async_reply({reply, unchanged, State}, From);
-handle_call({new_ticktime,T,TP}, _, #state{tick = #tick{ticker = Tckr,
+handle_call({new_ticktime,T,TP}, From, #state{tick = #tick{ticker = Tckr,
time = OT}} = State) ->
?tckr_dbg(initiating_tick_change),
start_aux_ticker(T, OT, TP),
@@ -493,14 +495,18 @@ handle_call({new_ticktime,T,TP}, _, #state{tick = #tick{ticker = Tckr,
?tckr_dbg(shorter_ticktime),
shorter
end,
- {reply, change_initiated, State#state{tick = #tick_change{ticker = Tckr,
- time = T,
- how = How}}};
+ async_reply({reply, change_initiated,
+ State#state{tick = #tick_change{ticker = Tckr,
+ time = T,
+ how = How}}}, From);
-handle_call({new_ticktime,_,_},
+handle_call({new_ticktime,From,_},
_,
#state{tick = #tick_change{time = T}} = State) ->
- {reply, {ongoing_change_to, T}, State}.
+ async_reply({reply, {ongoing_change_to, T}, State}, From);
+
+handle_call(_Msg, _From, State) ->
+ {noreply, State}.
%% ------------------------------------------------------------
%% handle_cast.
@@ -568,7 +574,7 @@ handle_info({accept,AcceptPid,Socket,Family,Proto}, State) ->
%%
%% A node has successfully been connected.
%%
-handle_info({SetupPid, {nodeup,Node,Address,Type,Immediate}},
+handle_info({SetupPid, {nodeup,Node,Address,Type,Immediate}},
State) ->
case {Immediate, ets:lookup(sys_dist, Node)} of
{true, [Conn]} when Conn#connection.state =:= pending,
@@ -656,7 +662,7 @@ handle_info({From,registered_send,To,Mess},State) ->
send(From,To,Mess),
{noreply,State};
-%% badcookies SHOULD not be sent
+%% badcookies SHOULD not be sent
%% (if someone does erlang:set_cookie(node(),foo) this may be)
handle_info({From,badcookie,_To,_Mess}, State) ->
error_logger:error_msg("~n** Got OLD cookie from ~w~n",
@@ -704,7 +710,7 @@ handle_info(X, State) ->
%% 4. The ticker process.
%% (5. Garbage pid.)
%%
-%% The process type function that handled the process throws
+%% The process type function that handled the process throws
%% the handle_info return value !
%% -----------------------------------------------------------
@@ -994,9 +1000,9 @@ ticker(Kernel, Tick) when is_integer(Tick) ->
ticker_loop(Kernel, Tick).
to_integer(T) when is_integer(T) -> T;
-to_integer(T) when is_atom(T) ->
+to_integer(T) when is_atom(T) ->
list_to_integer(atom_to_list(T));
-to_integer(T) when is_list(T) ->
+to_integer(T) when is_list(T) ->
list_to_integer(T).
ticker_loop(Kernel, Tick) ->
@@ -1004,7 +1010,7 @@ ticker_loop(Kernel, Tick) ->
{new_ticktime, NewTick} ->
?tckr_dbg({ticker_changed_time, Tick, NewTick}),
?MODULE:ticker_loop(Kernel, NewTick)
- after Tick ->
+ after Tick ->
Kernel ! tick,
?MODULE:ticker_loop(Kernel, Tick)
end.
@@ -1052,7 +1058,7 @@ send(_From,To,Mess) ->
-ifdef(UNUSED).
safesend(Name,Mess) when is_atom(Name) ->
- case whereis(Name) of
+ case whereis(Name) of
undefined ->
Mess;
P when is_pid(P) ->
@@ -1063,11 +1069,12 @@ safesend(Pid, Mess) -> Pid ! Mess.
-endif.
do_spawn(SpawnFuncArgs, SpawnOpts, State) ->
+ [_,From|_] = SpawnFuncArgs,
case catch spawn_opt(?MODULE, spawn_func, SpawnFuncArgs, SpawnOpts) of
- {'EXIT', {Reason,_}} ->
- {reply, {'EXIT', {Reason,[]}}, State};
- {'EXIT', Reason} ->
- {reply, {'EXIT', {Reason,[]}}, State};
+ {'EXIT', {Reason,_}} ->
+ async_reply({reply, {'EXIT', {Reason,[]}}, State}, From);
+ {'EXIT', Reason} ->
+ async_reply({reply, {'EXIT', {Reason,[]}}, State}, From);
_ ->
{noreply,State}
end.
@@ -1145,7 +1152,7 @@ get_proto_mod(Family,Protocol,[L|Ls]) ->
true ->
get_proto_mod(Family,Protocol,Ls)
end;
-get_proto_mod(_Family, _Protocol, []) ->
+get_proto_mod(_Family, _Protocol, []) ->
error.
%% -------- Initialisation functions ------------------------
@@ -1156,9 +1163,9 @@ init_node(Name, LongOrShortNames) ->
case create_name(Name, LongOrShortNames, 1) of
{ok,Node} ->
case start_protos(list_to_atom(NameWithoutHost),Node) of
- {ok, Ls} ->
+ {ok, Ls} ->
{ok, Node, Ls};
- Error ->
+ Error ->
Error
end;
Error ->
@@ -1167,9 +1174,9 @@ init_node(Name, LongOrShortNames) ->
%% Create the node name
create_name(Name, LongOrShortNames, Try) ->
- put(longnames, case LongOrShortNames of
- shortnames -> false;
- longnames -> true
+ put(longnames, case LongOrShortNames of
+ shortnames -> false;
+ longnames -> true
end),
{Head,Host1} = create_hostpart(Name, LongOrShortNames),
case Host1 of
@@ -1218,7 +1225,7 @@ create_hostpart(Name, LongOrShortNames) ->
{Head,Host1}.
%%
-%%
+%%
%%
protocol_childspecs() ->
case init:get_argument(proto_dist) of
@@ -1228,7 +1235,7 @@ protocol_childspecs() ->
protocol_childspecs(["inet_tcp"])
end.
-protocol_childspecs([]) ->
+protocol_childspecs([]) ->
[];
protocol_childspecs([H|T]) ->
Mod = list_to_atom(H ++ "_dist"),
@@ -1238,15 +1245,15 @@ protocol_childspecs([H|T]) ->
_ ->
protocol_childspecs(T)
end.
-
-
+
+
%%
%% epmd_module() -> module_name of erl_epmd or similar gen_server_module.
%%
epmd_module() ->
case init:get_argument(epmd_module) of
- {ok,[[Module]]} ->
+ {ok,[[Module]]} ->
Module;
_ ->
erl_epmd
@@ -1293,7 +1300,7 @@ start_protos(Name, [Proto | Ps], Node, Ls) ->
error_logger:info_msg("Protocol: ~p: not supported~n", [Proto]),
start_protos(Name,Ps, Node, Ls);
{'EXIT', Reason} ->
- error_logger:info_msg("Protocol: ~p: register error: ~p~n",
+ error_logger:info_msg("Protocol: ~p: register error: ~p~n",
[Proto, Reason]),
start_protos(Name,Ps, Node, Ls);
{error, duplicate_name} ->
@@ -1303,7 +1310,7 @@ start_protos(Name, [Proto | Ps], Node, Ls) ->
[Proto]),
start_protos(Name,Ps, Node, Ls);
{error, Reason} ->
- error_logger:info_msg("Protocol: ~p: register/listen error: ~p~n",
+ error_logger:info_msg("Protocol: ~p: register/listen error: ~p~n",
[Proto, Reason]),
start_protos(Name,Ps, Node, Ls)
end;
@@ -1409,7 +1416,7 @@ reply_waiting(_Node, Waiting, Rep) ->
reply_waiting1(lists:reverse(Waiting), Rep).
reply_waiting1([From|W], Rep) ->
- gen_server:reply(From, Rep),
+ async_gen_server_reply(From, Rep),
reply_waiting1(W, Rep);
reply_waiting1([], _) ->
ok.
@@ -1455,7 +1462,7 @@ display_info({Node, Info}, {I,O}) ->
integer_to_list(In), integer_to_list(Out), Address),
{I+In,O+Out}.
-fmt_address(undefined) ->
+fmt_address(undefined) ->
"-";
fmt_address(A) ->
case A#net_address.family of
@@ -1511,3 +1518,21 @@ verbose(_, _, _) ->
getnode(P) when is_pid(P) -> node(P);
getnode(P) -> P.
+
+async_reply({reply, Msg, State}, From) ->
+ async_gen_server_reply(From, Msg),
+ {noreply, State}.
+
+async_gen_server_reply(From, Msg) ->
+ {Pid, Tag} = From,
+ M = {Tag, Msg},
+ case catch erlang:send(Pid, M, [nosuspend, noconnect]) of
+ ok ->
+ ok;
+ nosuspend ->
+ spawn(fun() -> catch erlang:send(Pid, M, [noconnect]) end);
+ noconnect ->
+ ok; % The gen module takes care of this case.
+ {'EXIT', _}=EXIT ->
+ EXIT
+ end.
diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl
index d0b498edc9..75a11a8afd 100644
--- a/lib/kernel/src/os.erl
+++ b/lib/kernel/src/os.erl
@@ -50,7 +50,7 @@ find_executable(Name, Path) ->
relative ->
find_executable1(Name, split_path(Path), Extensions);
_ ->
- case verify_executable(Name, Extensions) of
+ case verify_executable(Name, Extensions, Extensions) of
{ok, Complete} ->
Complete;
error ->
@@ -60,7 +60,7 @@ find_executable(Name, Path) ->
find_executable1(Name, [Base|Rest], Extensions) ->
Complete0 = filename:join(Base, Name),
- case verify_executable(Complete0, Extensions) of
+ case verify_executable(Complete0, Extensions, Extensions) of
{ok, Complete} ->
Complete;
error ->
@@ -69,7 +69,7 @@ find_executable1(Name, [Base|Rest], Extensions) ->
find_executable1(_Name, [], _Extensions) ->
false.
-verify_executable(Name0, [Ext|Rest]) ->
+verify_executable(Name0, [Ext|Rest], OrigExtensions) ->
Name1 = Name0 ++ Ext,
case os:type() of
vxworks ->
@@ -78,7 +78,7 @@ verify_executable(Name0, [Ext|Rest]) ->
{ok, _} ->
{ok, Name1};
_ ->
- verify_executable(Name0, Rest)
+ verify_executable(Name0, Rest, OrigExtensions)
end;
_ ->
case file:read_file_info(Name1) of
@@ -87,12 +87,30 @@ verify_executable(Name0, [Ext|Rest]) ->
%% on Unix, since we test if any execution bit is set.
{ok, Name1};
_ ->
- verify_executable(Name0, Rest)
+ verify_executable(Name0, Rest, OrigExtensions)
end
end;
-verify_executable(_, []) ->
+verify_executable(Name, [], OrigExtensions) when OrigExtensions =/= [""] -> %% Windows
+ %% Will only happen on windows, hence case insensitivity
+ case can_be_full_name(string:to_lower(Name),OrigExtensions) of
+ true ->
+ verify_executable(Name,[""],[""]);
+ _ ->
+ error
+ end;
+verify_executable(_, [], _) ->
error.
+can_be_full_name(_Name,[]) ->
+ false;
+can_be_full_name(Name,[H|T]) ->
+ case lists:suffix(H,Name) of %% Name is in lowercase, cause this is a windows thing
+ true ->
+ true;
+ _ ->
+ can_be_full_name(Name,T)
+ end.
+
split_path(Path) ->
case type() of
{win32, _} ->
@@ -119,6 +137,7 @@ reverse_element(List) ->
lists:reverse(List).
-spec extensions() -> [string()].
+%% Extensions in lower case
extensions() ->
case type() of
{win32, _} -> [".exe",".com",".cmd",".bat"];
diff --git a/lib/kernel/src/pg2.erl b/lib/kernel/src/pg2.erl
index cb9fec2ffe..956a900adc 100644
--- a/lib/kernel/src/pg2.erl
+++ b/lib/kernel/src/pg2.erl
@@ -251,7 +251,9 @@ terminate(_Reason, _S) ->
%%% Pid is a member of group Name.
store(List) ->
- _ = [assure_group(Name) andalso [join_group(Name, P) || P <- Members] ||
+ _ = [(assure_group(Name)
+ andalso
+ [join_group(Name, P) || P <- Members -- group_members(Name)]) ||
[Name, Members] <- List],
ok.
diff --git a/lib/kernel/src/ram_file.erl b/lib/kernel/src/ram_file.erl
index d996650948..48ea871433 100644
--- a/lib/kernel/src/ram_file.erl
+++ b/lib/kernel/src/ram_file.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(ram_file).
@@ -24,11 +24,11 @@
-export([open/2, close/1]).
-export([write/2, read/2, copy/3,
pread/2, pread/3, pwrite/2, pwrite/3,
- position/2, truncate/1, sync/1]).
+ position/2, truncate/1, datasync/1, sync/1]).
%% Specialized file operations
-export([get_size/1, get_file/1, set_file/2, get_file_close/1]).
--export([compress/1, uncompress/1, uuencode/1, uudecode/1]).
+-export([compress/1, uncompress/1, uuencode/1, uudecode/1, advise/4]).
-export([open_mode/1]). %% used by ftp-file
@@ -60,6 +60,7 @@
-define(RAM_FILE_TRUNCATE, 14).
-define(RAM_FILE_PREAD, 17).
-define(RAM_FILE_PWRITE, 18).
+-define(RAM_FILE_FDATASYNC, 19).
%% Other operations
-define(RAM_FILE_GET, 30).
@@ -70,6 +71,7 @@
-define(RAM_FILE_UUENCODE, 35).
-define(RAM_FILE_UUDECODE, 36).
-define(RAM_FILE_SIZE, 37).
+-define(RAM_FILE_ADVISE, 38).
%% Open modes for RAM_FILE_OPEN
-define(RAM_FILE_MODE_READ, 1).
@@ -90,6 +92,14 @@
-define(RAM_FILE_RESP_NUMBER, 3).
-define(RAM_FILE_RESP_INFO, 4).
+%% POSIX file advises
+-define(POSIX_FADV_NORMAL, 0).
+-define(POSIX_FADV_RANDOM, 1).
+-define(POSIX_FADV_SEQUENTIAL, 2).
+-define(POSIX_FADV_WILLNEED, 3).
+-define(POSIX_FADV_DONTNEED, 4).
+-define(POSIX_FADV_NOREUSE, 5).
+
%% --------------------------------------------------------------------------
%% Generic file contents operations.
%%
@@ -167,6 +177,8 @@ copy(#file_descriptor{module = ?MODULE} = Source,
%% XXX Should be moved down to the driver for optimization.
file:copy_opened(Source, Dest, Length).
+datasync(#file_descriptor{module = ?MODULE, data = Port}) ->
+ call_port(Port, <<?RAM_FILE_FDATASYNC>>).
sync(#file_descriptor{module = ?MODULE, data = Port}) ->
call_port(Port, <<?RAM_FILE_FSYNC>>).
@@ -349,6 +361,28 @@ uudecode(#file_descriptor{module = ?MODULE, data = Port}) ->
uudecode(#file_descriptor{}) ->
{error, enotsup}.
+advise(#file_descriptor{module = ?MODULE, data = Port}, Offset,
+ Length, Advise) ->
+ Cmd0 = <<?RAM_FILE_ADVISE, Offset:64/signed, Length:64/signed>>,
+ case Advise of
+ normal ->
+ call_port(Port, <<Cmd0/binary, ?POSIX_FADV_NORMAL:32/signed>>);
+ random ->
+ call_port(Port, <<Cmd0/binary, ?POSIX_FADV_RANDOM:32/signed>>);
+ sequential ->
+ call_port(Port, <<Cmd0/binary, ?POSIX_FADV_SEQUENTIAL:32/signed>>);
+ will_need ->
+ call_port(Port, <<Cmd0/binary, ?POSIX_FADV_WILLNEED:32/signed>>);
+ dont_need ->
+ call_port(Port, <<Cmd0/binary, ?POSIX_FADV_DONTNEED:32/signed>>);
+ no_reuse ->
+ call_port(Port, <<Cmd0/binary, ?POSIX_FADV_NOREUSE:32/signed>>);
+ _ ->
+ {error, einval}
+ end;
+advise(#file_descriptor{}, _Offset, _Length, _Advise) ->
+ {error, enotsup}.
+
%%%-----------------------------------------------------------------
diff --git a/lib/kernel/src/rpc.erl b/lib/kernel/src/rpc.erl
index 43a987f313..e09acb5024 100644
--- a/lib/kernel/src/rpc.erl
+++ b/lib/kernel/src/rpc.erl
@@ -56,7 +56,7 @@
-export([safe_multi_server_call/2,safe_multi_server_call/3]).
%% gen_server exports
--export([init/1,handle_call/3,handle_cast/2,handle_info/2,
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
%% Internals
@@ -73,12 +73,12 @@
-spec start() -> {'ok', pid()} | 'ignore' | {'error', term()}.
start() ->
- gen_server:start({local,?NAME},?MODULE,[],[]).
+ gen_server:start({local,?NAME}, ?MODULE, [], []).
-spec start_link() -> {'ok', pid()} | 'ignore' | {'error', term()}.
start_link() ->
- gen_server:start_link({local,?NAME},?MODULE,[],[]).
+ gen_server:start_link({local,?NAME}, ?MODULE, [], []).
-spec stop() -> term().
@@ -121,12 +121,11 @@ handle_call(_, _To, S) ->
-spec handle_cast(term(), state()) -> {'noreply', state()}.
handle_cast({cast, Mod, Fun, Args, Gleader}, S) ->
- spawn(
- fun() ->
- set_group_leader(Gleader),
- apply(Mod, Fun, Args)
- end),
- {noreply, S};
+ spawn(fun() ->
+ set_group_leader(Gleader),
+ apply(Mod, Fun, Args)
+ end),
+ {noreply, S};
handle_cast(_, S) ->
{noreply, S}. % Ignore !
@@ -163,7 +162,7 @@ handle_info({From, {sbcast, Name, Msg}}, S) ->
_ ->
From ! {?NAME, node(), node()}
end,
- {noreply,S};
+ {noreply, S};
handle_info({From, {send, Name, Msg}}, S) ->
case catch Name ! {From, Msg} of %% use catch to get the printout
{'EXIT', _} ->
@@ -171,12 +170,12 @@ handle_info({From, {send, Name, Msg}}, S) ->
_ ->
ok %% It's up to Name to respond !!!!!
end,
- {noreply,S};
+ {noreply, S};
handle_info({From, {call,Mod,Fun,Args,Gleader}}, S) ->
%% Special for hidden C node's, uugh ...
handle_call_call(Mod, Fun, Args, Gleader, {From,?NAME}, S);
handle_info(_, S) ->
- {noreply,S}.
+ {noreply, S}.
-spec terminate(term(), state()) -> 'ok'.
@@ -231,7 +230,7 @@ proxy_user() ->
case whereis(rex_proxy_user) of
Pid when is_pid(Pid) -> Pid;
undefined ->
- Pid = spawn(fun()-> proxy_user_loop() end),
+ Pid = spawn(fun() -> proxy_user_loop() end),
try register(rex_proxy_user,Pid) of
true -> Pid
catch error:_ -> % spawn race, kill and try again
diff --git a/lib/kernel/src/standard_error.erl b/lib/kernel/src/standard_error.erl
index 98a44d1ee9..e41dcd01fc 100644
--- a/lib/kernel/src/standard_error.erl
+++ b/lib/kernel/src/standard_error.erl
@@ -24,8 +24,6 @@
-define(NAME, standard_error).
-define(PROCNAME_SUP, standard_error_sup).
-%% Internal exports
--export([server/1, server/2]).
%% Defines for control ops
-define(CTRL_OP_GET_WINSIZE,100).
@@ -54,18 +52,11 @@ init([]) ->
{error,no_stderror}
end.
-
start_port(PortSettings) ->
- Id = spawn(?MODULE,server,[{fd,2,2},PortSettings]),
- register(?NAME,Id),
+ Id = spawn(fun () -> server({fd,2,2}, PortSettings) end),
+ register(?NAME, Id),
Id.
-
-server(Pid) when is_pid(Pid) ->
- process_flag(trap_exit, true),
- link(Pid),
- run(Pid).
-
server(PortName,PortSettings) ->
process_flag(trap_exit, true),
Port = open_port(PortName,PortSettings),
@@ -88,17 +79,15 @@ server_loop(Port) ->
server_loop(Port)
end.
-
get_fd_geometry(Port) ->
case (catch port_control(Port,?CTRL_OP_GET_WINSIZE,[])) of
- List when is_list(List), length(List) =:= 8 ->
+ List when length(List) =:= 8 ->
<<W:32/native,H:32/native>> = list_to_binary(List),
{W,H};
_ ->
error
end.
-
%% NewSaveBuffer = io_request(Request, FromPid, ReplyAs, Port, SaveBuffer)
do_io_request(Req, From, ReplyAs, Port) ->
@@ -227,12 +216,7 @@ do_setopts(Opts, _Port) ->
{ok,ok}.
getopts(_Port) ->
- Uni = {unicode, case get(unicode) of
- true ->
- true;
- _ ->
- false
- end},
+ Uni = {unicode, get(unicode) =:= true},
{ok,[Uni]}.
wrap_characters_to_binary(Chars,From,To) ->
diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl
index 17dc5a56a2..88f32df20b 100644
--- a/lib/kernel/src/user.erl
+++ b/lib/kernel/src/user.erl
@@ -26,9 +26,6 @@
-define(NAME, user).
-%% Internal exports
--export([server/1, server/2]).
-
%% Defines for control ops
-define(CTRL_OP_GET_WINSIZE,100).
@@ -43,7 +40,7 @@ start([Mod,Fun|Args]) ->
%% Mod,Fun,Args should return a pid. That process is supposed to act
%% as the io port.
Pid = apply(Mod, Fun, Args), % This better work!
- Id = spawn(?MODULE, server, [Pid]),
+ Id = spawn(fun() -> server(Pid) end),
register(?NAME, Id),
Id.
@@ -52,8 +49,8 @@ start_out() ->
start_port([out,binary]).
start_port(PortSettings) ->
- Id = spawn(?MODULE,server,[{fd,0,1},PortSettings]),
- register(?NAME,Id),
+ Id = spawn(fun() -> server({fd,0,1}, PortSettings) end),
+ register(?NAME, Id),
Id.
%% Return the pid of the shell process.
@@ -72,7 +69,6 @@ interfaces(User) ->
[]
end.
-
server(Pid) when is_pid(Pid) ->
process_flag(trap_exit, true),
link(Pid),
@@ -104,7 +100,7 @@ catch_loop(Port, Shell, Q) ->
new_shell ->
exit(Shell, kill),
catch_loop(Port, start_new_shell());
- {unknown_exit,{Shell,Reason},_} -> % shell has exited
+ {unknown_exit,{Shell,Reason},_} -> % shell has exited
case Reason of
normal ->
put_chars("*** ", Port, []);
@@ -172,7 +168,7 @@ server_loop(Port, Q) ->
get_fd_geometry(Port) ->
case (catch port_control(Port,?CTRL_OP_GET_WINSIZE,[])) of
- List when is_list(List), length(List) =:= 8 ->
+ List when length(List) =:= 8 ->
<<W:32/native,H:32/native>> = list_to_binary(List),
{W,H};
_ ->
@@ -373,12 +369,7 @@ do_setopts(Opts, _Port, Q) ->
end.
getopts(_Port,Q) ->
- Bin = {binary, case get(read_mode) of
- binary ->
- true;
- _ ->
- false
- end},
+ Bin = {binary, get(read_mode) =:= binary},
Uni = {encoding, case get(unicode) of
true ->
unicode;
diff --git a/lib/kernel/test/code_SUITE.erl b/lib/kernel/test/code_SUITE.erl
index 37b9200942..08b6477c9d 100644
--- a/lib/kernel/test/code_SUITE.erl
+++ b/lib/kernel/test/code_SUITE.erl
@@ -543,8 +543,8 @@ add_del_path(Config) when is_list(Config) ->
?line code:del_path(Dir2),
?line PrivDir1 = code:priv_dir(dummy_app),
ok.
-
-
+
+
clash(Config) when is_list(Config) ->
DDir = ?config(data_dir,Config)++"clash/",
P = code:get_path(),
@@ -555,11 +555,11 @@ clash(Config) when is_list(Config) ->
?line true = code:del_path("."),
?line true = code:add_path(DDir++"foobar-0.1/ebin"),
?line true = code:add_path(DDir++"zork-0.8/ebin"),
- ?line test_server:capture_start(),
- ?line code:clash(),
- ?line test_server:capture_stop(),
- ?line OKMsg = test_server:capture_get(),
- ?line lists:prefix("** Found 0 name clashes in code paths", OKMsg),
+ test_server:capture_start(),
+ ?line ok = code:clash(),
+ test_server:capture_stop(),
+ ?line [OKMsg|_] = test_server:capture_get(),
+ ?line true = lists:prefix("** Found 0 name clashes", OKMsg),
?line true = code:set_path(P),
%% test clashing entries
@@ -568,13 +568,29 @@ clash(Config) when is_list(Config) ->
?line true = code:del_path("."),
?line true = code:add_path(DDir++"foobar-0.1/ebin"),
?line true = code:add_path(DDir++"foobar-0.1.ez/foobar-0.1/ebin"),
- ?line test_server:capture_start(),
- ?line code:clash(),
- ?line test_server:capture_stop(),
- ?line [ErrMsg1|_] = test_server:capture_get(),
- ?line {match, [" hides "]} = re:run(ErrMsg1, "\\*\\* .*( hides ).*",
+ test_server:capture_start(),
+ ?line ok = code:clash(),
+ test_server:capture_stop(),
+ ?line [ClashMsg|_] = test_server:capture_get(),
+ ?line {match, [" hides "]} = re:run(ClashMsg, "\\*\\* .*( hides ).*",
[{capture,all_but_first,list}]),
?line true = code:set_path(P),
+
+ %% test "Bad path can't read"
+
+ %% remove "." to prevent clash with test-server path
+ Priv = ?config(priv_dir, Config),
+ ?line true = code:del_path("."),
+ TmpEzFile = Priv++"foobar-0.tmp.ez",
+ ?line {ok, _} = file:copy(DDir++"foobar-0.1.ez", TmpEzFile),
+ ?line true = code:add_path(TmpEzFile++"/foobar-0.1/ebin"),
+ ?line ok = file:delete(TmpEzFile),
+ test_server:capture_start(),
+ ?line ok = code:clash(),
+ test_server:capture_stop(),
+ ?line [BadPathMsg|_] = test_server:capture_get(),
+ ?line true = lists:prefix("** Bad path can't read", BadPathMsg),
+ ?line true = code:set_path(P),
ok.
ext_mod_dep(suite) ->
diff --git a/lib/kernel/test/file_SUITE.erl b/lib/kernel/test/file_SUITE.erl
index d01e1f1fcf..17c47f871d 100644
--- a/lib/kernel/test/file_SUITE.erl
+++ b/lib/kernel/test/file_SUITE.erl
@@ -52,8 +52,8 @@
old_modes/1, new_modes/1, path_open/1, open_errors/1]).
-export([file_info/1, file_info_basic_file/1, file_info_basic_directory/1,
file_info_bad/1, file_info_times/1, file_write_file_info/1]).
--export([rename/1, access/1, truncate/1, sync/1,
- read_write/1, pread_write/1, append/1]).
+-export([rename/1, access/1, truncate/1, datasync/1, sync/1,
+ read_write/1, pread_write/1, append/1, exclusive/1]).
-export([errors/1, e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
-export([otp_5814/1]).
@@ -82,6 +82,10 @@
-export([read_line_1/1, read_line_2/1, read_line_3/1,read_line_4/1]).
+-export([advise/1]).
+
+-export([standard_io/1,mini_server/1]).
+
%% Debug exports
-export([create_file_slow/2, create_file/2, create_bin/2]).
-export([verify_file/2, verify_bin/3]).
@@ -101,7 +105,8 @@ all(suite) ->
compression, links, copy,
delayed_write, read_ahead, segment_read, segment_write,
ipread, pid2name, interleaved_read_write,
- otp_5814, large_file, read_line_1, read_line_2, read_line_3, read_line_4],
+ otp_5814, large_file, read_line_1, read_line_2, read_line_3, read_line_4,
+ standard_io],
fini}.
init(Config) when is_list(Config) ->
@@ -170,6 +175,85 @@ time_dist({_D1, _T1} = DT1, {_D2, _T2} = DT2) ->
- calendar:datetime_to_gregorian_seconds(DT1).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mini_server(Parent) ->
+ receive
+ die ->
+ ok;
+ {io_request,From,To,{put_chars,Data}} ->
+ Parent ! {io_request,From,To,{put_chars,Data}},
+ From ! {io_reply, To, ok},
+ mini_server(Parent);
+ {io_request,From,To,{get_chars,'',N}} ->
+ Parent ! {io_request,From,To,{get_chars,'',N}},
+ From ! {io_reply, To, {ok, lists:duplicate(N,$a)}},
+ mini_server(Parent);
+ {io_request,From,To,{get_line,''}} ->
+ Parent ! {io_request,From,To,{get_line,''}},
+ From ! {io_reply, To, {ok, "hej\n"}},
+ mini_server(Parent)
+ end.
+
+standard_io(suite) ->
+ [];
+standard_io(doc) ->
+ ["Test that standard i/o-servers work with file module"];
+standard_io(Config) when is_list(Config) ->
+ %% Really just a smoke test
+ ?line Pid = spawn(?MODULE,mini_server,[self()]),
+ ?line register(mini_server,Pid),
+ ?line ok = file:write(mini_server,<<"hej\n">>),
+ ?line receive
+ {io_request,_,_,{put_chars,<<"hej\n">>}} ->
+ ok
+ after 1000 ->
+ exit(noreply)
+ end,
+ ?line {ok,"aaaaa"} = file:read(mini_server,5),
+ ?line receive
+ {io_request,_,_,{get_chars,'',5}} ->
+ ok
+ after 1000 ->
+ exit(noreply)
+ end,
+ ?line {ok,"hej\n"} = file:read_line(mini_server),
+ ?line receive
+ {io_request,_,_,{get_line,''}} ->
+ ok
+ after 1000 ->
+ exit(noreply)
+ end,
+ ?line OldGL = group_leader(),
+ ?line group_leader(Pid,self()),
+ ?line ok = file:write(standard_io,<<"hej\n">>),
+ ?line group_leader(OldGL,self()),
+ ?line receive
+ {io_request,_,_,{put_chars,<<"hej\n">>}} ->
+ ok
+ after 1000 ->
+ exit(noreply)
+ end,
+ ?line group_leader(Pid,self()),
+ ?line {ok,"aaaaa"} = file:read(standard_io,5),
+ ?line group_leader(OldGL,self()),
+ ?line receive
+ {io_request,_,_,{get_chars,'',5}} ->
+ ok
+ after 1000 ->
+ exit(noreply)
+ end,
+ ?line group_leader(Pid,self()),
+ ?line {ok,"hej\n"} = file:read_line(standard_io),
+ ?line group_leader(OldGL,self()),
+ ?line receive
+ {io_request,_,_,{get_line,''}} ->
+ ok
+ after 1000 ->
+ exit(noreply)
+ end,
+ Pid ! die,
+ receive after 1000 -> ok end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
read_write_file(suite) -> [];
read_write_file(doc) -> [];
@@ -270,7 +354,10 @@ make_del_dir(Config) when is_list(Config) ->
%% Try deleting some bad directories
%% Deleting the parent directory to the current, sounds dangerous, huh?
%% Don't worry ;-) the parent directory should never be empty, right?
- ?line {error, eexist} = ?FILE_MODULE:del_dir('..'),
+ case ?FILE_MODULE:del_dir('..') of
+ {error, eexist} -> ok;
+ {error, einval} -> ok %FreeBSD
+ end,
?line {error, enoent} = ?FILE_MODULE:del_dir(""),
?line {error, badarg} = ?FILE_MODULE:del_dir([3,2,1,{}]),
@@ -374,10 +461,12 @@ win_cur_dir_1(_Config) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-files(suite) -> [open,pos,file_info,consult,eval,script,truncate,sync].
+files(suite) ->
+ [open,pos,file_info,consult,eval,script,truncate,
+ sync,datasync,advise].
open(suite) -> [open1,old_modes,new_modes,path_open,close,access,read_write,
- pread_write,append,open_errors].
+ pread_write,append,open_errors,exclusive].
open1(suite) -> [];
open1(doc) -> [];
@@ -751,6 +840,22 @@ open_errors(Config) when is_list(Config) ->
?line test_server:timetrap_cancel(Dog),
ok.
+exclusive(suite) -> [];
+exclusive(doc) -> "Test exclusive access to a file.";
+exclusive(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(5)),
+ ?line RootDir = ?config(priv_dir,Config),
+ ?line NewDir = filename:join(RootDir,
+ atom_to_list(?MODULE)
+ ++"_exclusive"),
+ ?line ok = ?FILE_MODULE:make_dir(NewDir),
+ ?line Name = filename:join(NewDir, "ex_file.txt"),
+ ?line {ok, Fd} = ?FILE_MODULE:open(Name, [write, exclusive]),
+ ?line {error, eexist} = ?FILE_MODULE:open(Name, [write, exclusive]),
+ ?line ok = ?FILE_MODULE:close(Fd),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
pos(suite) -> [pos1,pos2].
@@ -1352,6 +1457,30 @@ truncate(Config) when is_list(Config) ->
ok.
+datasync(suite) -> [];
+datasync(doc) -> "Tests that ?FILE_MODULE:datasync/1 at least doesn't crash.";
+datasync(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(5)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Sync = filename:join(PrivDir,
+ atom_to_list(?MODULE)
+ ++"_sync.fil"),
+
+ %% Raw open.
+ ?line {ok, Fd} = ?FILE_MODULE:open(Sync, [write, raw]),
+ ?line ok = ?FILE_MODULE:datasync(Fd),
+ ?line ok = ?FILE_MODULE:close(Fd),
+
+ %% Ordinary open.
+ ?line {ok, Fd2} = ?FILE_MODULE:open(Sync, [write]),
+ ?line ok = ?FILE_MODULE:datasync(Fd2),
+ ?line ok = ?FILE_MODULE:close(Fd2),
+
+ ?line [] = flush(),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+
sync(suite) -> [];
sync(doc) -> "Tests that ?FILE_MODULE:sync/1 at least doesn't crash.";
sync(Config) when is_list(Config) ->
@@ -1375,6 +1504,77 @@ sync(Config) when is_list(Config) ->
?line test_server:timetrap_cancel(Dog),
ok.
+advise(suite) -> [];
+advise(doc) -> "Tests that ?FILE_MODULE:advise/4 at least doesn't crash.";
+advise(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(5)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Advise = filename:join(PrivDir,
+ atom_to_list(?MODULE)
+ ++"_advise.fil"),
+
+ Line1 = "Hello\n",
+ Line2 = "World!\n",
+
+ ?line {ok, Fd} = ?FILE_MODULE:open(Advise, [write]),
+ ?line ok = ?FILE_MODULE:advise(Fd, 0, 0, normal),
+ ?line ok = io:format(Fd, "~s", [Line1]),
+ ?line ok = io:format(Fd, "~s", [Line2]),
+ ?line ok = ?FILE_MODULE:close(Fd),
+
+ ?line {ok, Fd2} = ?FILE_MODULE:open(Advise, [write]),
+ ?line ok = ?FILE_MODULE:advise(Fd2, 0, 0, random),
+ ?line ok = io:format(Fd2, "~s", [Line1]),
+ ?line ok = io:format(Fd2, "~s", [Line2]),
+ ?line ok = ?FILE_MODULE:close(Fd2),
+
+ ?line {ok, Fd3} = ?FILE_MODULE:open(Advise, [write]),
+ ?line ok = ?FILE_MODULE:advise(Fd3, 0, 0, sequential),
+ ?line ok = io:format(Fd3, "~s", [Line1]),
+ ?line ok = io:format(Fd3, "~s", [Line2]),
+ ?line ok = ?FILE_MODULE:close(Fd3),
+
+ ?line {ok, Fd4} = ?FILE_MODULE:open(Advise, [write]),
+ ?line ok = ?FILE_MODULE:advise(Fd4, 0, 0, will_need),
+ ?line ok = io:format(Fd4, "~s", [Line1]),
+ ?line ok = io:format(Fd4, "~s", [Line2]),
+ ?line ok = ?FILE_MODULE:close(Fd4),
+
+ ?line {ok, Fd5} = ?FILE_MODULE:open(Advise, [write]),
+ ?line ok = ?FILE_MODULE:advise(Fd5, 0, 0, dont_need),
+ ?line ok = io:format(Fd5, "~s", [Line1]),
+ ?line ok = io:format(Fd5, "~s", [Line2]),
+ ?line ok = ?FILE_MODULE:close(Fd5),
+
+ ?line {ok, Fd6} = ?FILE_MODULE:open(Advise, [write]),
+ ?line ok = ?FILE_MODULE:advise(Fd6, 0, 0, no_reuse),
+ ?line ok = io:format(Fd6, "~s", [Line1]),
+ ?line ok = io:format(Fd6, "~s", [Line2]),
+ ?line ok = ?FILE_MODULE:close(Fd6),
+
+ ?line {ok, Fd7} = ?FILE_MODULE:open(Advise, [write]),
+ ?line {error, einval} = ?FILE_MODULE:advise(Fd7, 0, 0, bad_advise),
+ ?line ok = ?FILE_MODULE:close(Fd7),
+
+ %% test write without advise, then a read after an advise
+ ?line {ok, Fd8} = ?FILE_MODULE:open(Advise, [write]),
+ ?line ok = io:format(Fd8, "~s", [Line1]),
+ ?line ok = io:format(Fd8, "~s", [Line2]),
+ ?line ok = ?FILE_MODULE:close(Fd8),
+ ?line {ok, Fd9} = ?FILE_MODULE:open(Advise, [read]),
+ Offset = 0,
+ %% same as a 0 length in some implementations
+ Length = length(Line1) + length(Line2),
+ ?line ok = ?FILE_MODULE:advise(Fd9, Offset, Length, sequential),
+ ?line {ok, Line1} = ?FILE_MODULE:read_line(Fd9),
+ ?line {ok, Line2} = ?FILE_MODULE:read_line(Fd9),
+ ?line eof = ?FILE_MODULE:read_line(Fd9),
+ ?line ok = ?FILE_MODULE:close(Fd9),
+
+ ?line [] = flush(),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c b/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c
index fb3c622909..f24c93edf5 100644
--- a/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c
+++ b/lib/kernel/test/inet_sockopt_SUITE_data/sockopt_helper.c
@@ -1,4 +1,4 @@
-#if defined(VXWORKS) || defined(__OSE__)
+#if defined(VXWORKS)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl
index 6a3534b094..ace9501d18 100644
--- a/lib/kernel/test/os_SUITE.erl
+++ b/lib/kernel/test/os_SUITE.erl
@@ -137,6 +137,13 @@ find_executable(Config) when is_list(Config) ->
?line find_exe(Abin, "my_ar", ".exe", Path),
?line find_exe(Abin, "my_ascii", ".com", Path),
?line find_exe(Abin, "my_adb", ".bat", Path),
+ %% OTP-3626 find names of executables given with extension
+ ?line find_exe(Abin, "my_ar.exe", "", Path),
+ ?line find_exe(Abin, "my_ascii.com", "", Path),
+ ?line find_exe(Abin, "my_adb.bat", "", Path),
+ ?line find_exe(Abin, "my_ar.EXE", "", Path),
+ ?line find_exe(Abin, "my_ascii.COM", "", Path),
+ ?line find_exe(Abin, "MY_ADB.BAT", "", Path),
%% Search for programs in Abin (second element in PATH).
?line find_exe(Abin, "my_ar", ".exe", Path),
diff --git a/lib/kernel/test/pg2_SUITE.erl b/lib/kernel/test/pg2_SUITE.erl
index 8eb1a7ca19..df28dcf447 100644
--- a/lib/kernel/test/pg2_SUITE.erl
+++ b/lib/kernel/test/pg2_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%----------------------------------------------------------------
%% Purpose:Test Suite for the 'pg2' module.
@@ -26,8 +26,8 @@
-export([all/1, init_per_testcase/2, fin_per_testcase/2]).
--export([tickets/1,
- otp_7277/1, otp_8259/1,
+-export([tickets/1,
+ otp_7277/1, otp_8259/1, otp_8653/1,
compat/1, basic/1]).
% Default timetrap timeout (set in init_per_testcase).
@@ -37,7 +37,8 @@
-define(testcase, ?config(?TESTCASE, Config)).
%% Internal export.
--export([mk_part_node/3, part1/5, p_init/3, start_proc/1, sane/0]).
+-export([mk_part_node_and_group/3, part2/4,
+ mk_part_node/3, part1/5, p_init/3, start_proc/1, sane/0]).
init_per_testcase(Case, Config) ->
?line Dog = ?t:timetrap(?default_timeout),
@@ -48,11 +49,11 @@ fin_per_testcase(_Case, _Config) ->
test_server:timetrap_cancel(Dog),
ok.
-all(suite) ->
+all(suite) ->
[tickets].
tickets(suite) ->
- [otp_7277, otp_8259, compat, basic].
+ [otp_7277, otp_8259, otp_8653, compat, basic].
otp_7277(doc) ->
"OTP-7277. Bugfix leave().";
@@ -65,9 +66,9 @@ otp_7277(Config) when is_list(Config) ->
?line ok = pg2:leave(b, P),
?line true = exit(P, kill),
case {pg2:get_members(a), pg2:get_local_members(a)} of
- {[], []} ->
+ {[], []} ->
ok;
- _ ->
+ _ ->
timer:sleep(100),
?line [] = pg2:get_members(a),
?line [] = pg2:get_local_members(a)
@@ -79,6 +80,63 @@ otp_7277(Config) when is_list(Config) ->
-define(UNTIL(Seq), loop_until_true(fun() -> Seq end, Config)).
-define(UNTIL_LOOP, 300).
+otp_8653(suite) -> [];
+otp_8653(doc) ->
+ ["OTP-8259. Member was not removed after being killed."];
+otp_8653(Config) when is_list(Config) ->
+ Timeout = 15,
+ ?line Dog = test_server:timetrap({seconds,Timeout}),
+
+ ?line [A, B, C] = start_nodes([a, b, c], peer, Config),
+
+ ?line wait_for_ready_net(Config),
+
+ % make b and c connected, partitioned from node() and a
+ ?line rpc_cast(B, ?MODULE, part2, [Config, node(), A, C]),
+ ?line ?UNTIL(is_ready_partition(Config)),
+
+ % Connect to the other partition.
+ ?line pong = net_adm:ping(B),
+ timer:sleep(100),
+ ?line pong = net_adm:ping(C),
+ ?line _ = global:sync(),
+ ?line [A, B, C] = lists:sort(nodes()),
+
+ G = pg2_otp_8653,
+ ?line ?UNTIL(begin
+ GA = lists:sort(rpc:call(A, pg2, get_members, [G])),
+ GB = lists:sort(rpc:call(B, pg2, get_members, [G])),
+ GC = lists:sort(rpc:call(C, pg2, get_members, [G])),
+ GT = lists:sort(pg2:get_members(G)),
+ GA =:= GB andalso
+ GB =:= GC andalso
+ GC =:= GT andalso
+ 8 =:= length(GA)
+ end),
+ ?line ok = pg2:delete(G),
+ ?line stop_nodes([A,B,C]),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+part2(Config, Main, A, C) ->
+ Function = mk_part_node_and_group,
+ case catch begin
+ make_partition(Config, [Main, A], [node(), C], Function)
+ end
+ of
+ ok -> ok
+ end.
+
+mk_part_node_and_group(File, MyPart0, Config) ->
+ touch(File, "start"), % debug
+ MyPart = lists:sort(MyPart0),
+ ?UNTIL(is_node_in_part(File, MyPart)),
+ G = pg2_otp_8653,
+ Pid = spawn(forever()),
+ ok = pg2:create(G),
+ _ = [ok = pg2:join(G, Pid) || _ <- [1,1]],
+ touch(File, "done").
+
otp_8259(suite) -> [];
otp_8259(doc) ->
["OTP-8259. Member was not removed after being killed."];
@@ -102,7 +160,7 @@ otp_8259(Config) when is_list(Config) ->
% make b and c connected, partitioned from node() and a
?line rpc_cast(B, ?MODULE, part1, [Config, node(), A, C, Name]),
?line ?UNTIL(is_ready_partition(Config)),
-
+
% Connect to the other partition.
% The resolver on node b will be called.
?line pong = net_adm:ping(B),
@@ -140,9 +198,9 @@ start_proc(Name) ->
p_init(Parent, Name, TestServer) ->
Resolve = fun(_Name, Pid1, Pid2) ->
%% The pid on node a will be chosen.
- [{_,Min}, {_,Max}] =
+ [{_,Min}, {_,Max}] =
lists:sort([{node(Pid1),Pid1}, {node(Pid2),Pid2}]),
- %% b is connected to test_server.
+ %% b is connected to test_server.
%% exit(Min, kill), % would ping a
rpc:cast(TestServer, erlang, exit, [Min, kill]),
Max
@@ -165,7 +223,7 @@ compat(Config) when is_list(Config) ->
true ->
Timeout = 15,
?line Dog = test_server:timetrap({seconds,Timeout}),
- Pid = spawn(forever()),
+ Pid = spawn(forever()),
G = a,
?line ok = pg2:create(G),
?line ok = pg2:join(G, Pid),
@@ -365,7 +423,7 @@ killit(N, P, Ps, Ns) ->
timer:sleep(100),
sane(Ns),
lists:keydelete(P, 1, Ps).
-
+
pr(Node, C) ->
_ = [?t:format("~p: ", [Node]) || Node =/= node()],
?t:format("do ~p~n", [C]).
@@ -412,27 +470,27 @@ sane(Ns) ->
wsane(Ns) ->
%% Same members on all nodes:
- {[_],gs} =
+ {[_],gs} =
{lists:usort([rpc:call(N, pg2, which_groups, []) || N <- Ns]),gs},
- _ = [{[_],ms,G} = {lists:usort([rpc:call(N, pg2, get_members, [G]) ||
+ _ = [{[_],ms,G} = {lists:usort([rpc:call(N, pg2, get_members, [G]) ||
N <- Ns]),ms,G} ||
G <- pg2:which_groups()],
%% The local members are a partitioning of the members:
- [begin
- LocalMembers =
+ [begin
+ LocalMembers =
lists:sort(lists:append(
- [rpc:call(N, pg2, get_local_members, [G]) ||
+ [rpc:call(N, pg2, get_local_members, [G]) ||
N <- Ns])),
{part, LocalMembers} = {part, lists:sort(pg2:get_members(G))}
end || G <- pg2:which_groups()],
%% The closest pid should run on the local node, if possible.
[[case rpc:call(N, pg2, get_closest_pid, [G]) of
Pid when is_pid(Pid), node(Pid) =:= N ->
- true =
+ true =
lists:member(Pid, rpc:call(N, pg2, get_local_members, [G]));
%% FIXME. Om annan nod: member, local = [].
_ -> [] = rpc:call(N, pg2, get_local_members, [G])
- end || N <- Ns]
+ end || N <- Ns]
|| G <- pg2:which_groups()].
%% Look inside the pg2_table.
@@ -482,9 +540,9 @@ start_node_rel(Name, Rel, How) ->
{RelList, ""}
end,
?line Pa = filename:dirname(code:which(?MODULE)),
- ?line Res = test_server:start_node(Name, How,
+ ?line Res = test_server:start_node(Name, How,
[{args,
- Compat ++
+ Compat ++
" -kernel net_setuptime 100 "
" -pa " ++ Pa},
{erl, Release}]),
@@ -575,29 +633,30 @@ get_known(Node) ->
case catch gen_server:call({global_name_server,Node},get_known,infinity) of
{'EXIT', _} ->
[list, without, nodenames];
- Known when is_list(Known) ->
+ Known when is_list(Known) ->
lists:sort([Node | Known])
end.
node_name(Name, Config) ->
U = "_",
{{Y,M,D}, {H,Min,S}} = calendar:now_to_local_time(now()),
- Date = io_lib:format("~4w_~2..0w_~2..0w__~2..0w_~2..0w_~2..0w",
+ Date = io_lib:format("~4w_~2..0w_~2..0w__~2..0w_~2..0w_~2..0w",
[Y,M,D, H,Min,S]),
L = lists:flatten(Date),
lists:concat([Name,U,?testcase,U,U,L]).
-%% this one runs on one node in Part2
-%% The partition is ready when is_ready_partition(Config) returns (true).
-%% this one runs on one node in Part2
+%% This one runs on one node in Part2.
%% The partition is ready when is_ready_partition(Config) returns (true).
make_partition(Config, Part1, Part2) ->
+ make_partition(Config, Part1, Part2, mk_part_node).
+
+make_partition(Config, Part1, Part2, Function) ->
Dir = ?config(priv_dir, Config),
- Ns = [begin
+ Ns = [begin
Name = lists:concat([atom_to_list(N),"_",msec(),".part"]),
File = filename:join([Dir, Name]),
file:delete(File),
- rpc_cast(N, ?MODULE, mk_part_node, [File, Part, Config], File),
+ rpc_cast(N, ?MODULE, Function, [File, Part, Config], File),
{N, File}
end || Part <- [Part1, Part2], N <- Part],
all_nodes_files(Ns, "done", Config),
@@ -614,10 +673,10 @@ mk_part_node(File, MyPart0, Config) ->
%% The calls to append_to_file are for debugging.
is_node_in_part(File, MyPart) ->
- lists:foreach(fun(N) ->
+ lists:foreach(fun(N) ->
_ = erlang:disconnect_node(N)
end, nodes() -- MyPart),
- case {(Known = get_known(node())) =:= MyPart,
+ case {(Known = get_known(node())) =:= MyPart,
(Nodes = lists:sort([node() | nodes()])) =:= MyPart} of
{true, true} ->
%% Make sure the resolvers have been terminated,
@@ -649,7 +708,7 @@ wait_for_ready_net(Nodes0, Config) ->
?t:format("wait_for_ready_net ~p~n", [Nodes]),
?UNTIL(begin
lists:all(fun(N) -> Nodes =:= get_known(N) end, Nodes) and
- lists:all(fun(N) ->
+ lists:all(fun(N) ->
LNs = rpc:call(N, erlang, nodes, []),
Nodes =:= lists:sort([N | LNs])
end, Nodes)
@@ -688,11 +747,11 @@ file_contents(File, ContentsList, Config) ->
file_contents(File, ContentsList, Config, no_log_file).
file_contents(File, ContentsList, Config, LogFile) ->
- Contents = list_to_binary(ContentsList),
+ Contents = list_to_binary(ContentsList),
Sz = size(Contents),
?UNTIL(begin
case file:read_file(File) of
- {ok, FileContents}=Reply ->
+ {ok, FileContents}=Reply ->
case catch split_binary(FileContents, Sz) of
{Contents,_} ->
true;
diff --git a/lib/kernel/test/prim_file_SUITE.erl b/lib/kernel/test/prim_file_SUITE.erl
index 860aeecbf4..1688ec45ca 100644
--- a/lib/kernel/test/prim_file_SUITE.erl
+++ b/lib/kernel/test/prim_file_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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(prim_file_SUITE).
@@ -34,8 +34,8 @@
file_info_times_a/1, file_info_times_b/1,
file_write_file_info_a/1, file_write_file_info_b/1]).
-export([rename_a/1, rename_b/1,
- access/1, truncate/1, sync/1,
- read_write/1, pread_write/1, append/1]).
+ access/1, truncate/1, datasync/1, sync/1,
+ read_write/1, pread_write/1, append/1, exclusive/1]).
-export([errors/1, e_delete/1, e_rename/1, e_make_dir/1, e_del_dir/1]).
-export([compression/1, read_not_really_compressed/1,
@@ -48,6 +48,8 @@
symlinks_a/1, symlinks_b/1,
list_dir_limit/1]).
+-export([advise/1]).
+
-include("test_server.hrl").
-include_lib("kernel/include/file.hrl").
@@ -243,7 +245,10 @@ make_del_dir(Config, Handle, Suffix) ->
%% Try deleting some bad directories
%% Deleting the parent directory to the current, sounds dangerous, huh?
%% Don't worry ;-) the parent directory should never be empty, right?
- ?line {error, eexist} = ?PRIM_FILE_call(del_dir, Handle, [".."]),
+ case ?PRIM_FILE_call(del_dir, Handle, [".."]) of
+ {error, eexist} -> ok;
+ {error, einval} -> ok %FreeBSD
+ end,
?line {error, enoent} = ?PRIM_FILE_call(del_dir, Handle, [""]),
?line {error, badarg} = ?PRIM_FILE_call(del_dir, Handle, [[3,2,1,{}]]),
@@ -377,10 +382,10 @@ win_cur_dir_1(_Config, Handle) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-files(suite) -> [open,pos,file_info,truncate,sync].
+files(suite) -> [open,pos,file_info,truncate,sync,datasync,advise].
open(suite) -> [open1,modes,close,access,read_write,
- pread_write,append].
+ pread_write,append,exclusive].
open1(suite) -> [];
open1(doc) -> [];
@@ -605,6 +610,22 @@ append(Config) when is_list(Config) ->
?line test_server:timetrap_cancel(Dog),
ok.
+exclusive(suite) -> [];
+exclusive(doc) -> "Test exclusive access to a file.";
+exclusive(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(5)),
+ ?line RootDir = ?config(priv_dir,Config),
+ ?line NewDir = filename:join(RootDir,
+ atom_to_list(?MODULE)
+ ++"_exclusive"),
+ ?line ok = ?PRIM_FILE:make_dir(NewDir),
+ ?line Name = filename:join(NewDir, "ex_file.txt"),
+ ?line {ok,Fd} = ?PRIM_FILE:open(Name, [write, exclusive]),
+ ?line {error, eexist} = ?PRIM_FILE:open(Name, [write, exclusive]),
+ ?line ok = ?PRIM_FILE:close(Fd),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
pos(suite) -> [pos1,pos2].
@@ -1061,6 +1082,24 @@ truncate(Config) when is_list(Config) ->
ok.
+datasync(suite) -> [];
+datasync(doc) -> "Tests that ?PRIM_FILE:datasync/1 at least doesn't crash.";
+datasync(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(5)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Sync = filename:join(PrivDir,
+ atom_to_list(?MODULE)
+ ++"_sync.fil"),
+
+ %% Raw open.
+ ?line {ok, Fd} = ?PRIM_FILE:open(Sync, [write]),
+ ?line ok = ?PRIM_FILE:datasync(Fd),
+ ?line ok = ?PRIM_FILE:close(Fd),
+
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+
sync(suite) -> [];
sync(doc) -> "Tests that ?PRIM_FILE:sync/1 at least doesn't crash.";
sync(Config) when is_list(Config) ->
@@ -1079,6 +1118,77 @@ sync(Config) when is_list(Config) ->
ok.
+advise(suite) -> [];
+advise(doc) -> "Tests that ?PRIM_FILE:advise/4 at least doesn't crash.";
+advise(Config) when is_list(Config) ->
+ ?line Dog = test_server:timetrap(test_server:seconds(5)),
+ ?line PrivDir = ?config(priv_dir, Config),
+ ?line Advise = filename:join(PrivDir,
+ atom_to_list(?MODULE)
+ ++"_advise.fil"),
+
+ Line1 = "Hello\n",
+ Line2 = "World!\n",
+
+ ?line {ok, Fd} = ?PRIM_FILE:open(Advise, [write]),
+ ?line ok = ?PRIM_FILE:advise(Fd, 0, 0, normal),
+ ?line ok = ?PRIM_FILE:write(Fd, Line1),
+ ?line ok = ?PRIM_FILE:write(Fd, Line2),
+ ?line ok = ?PRIM_FILE:close(Fd),
+
+ ?line {ok, Fd2} = ?PRIM_FILE:open(Advise, [write]),
+ ?line ok = ?PRIM_FILE:advise(Fd2, 0, 0, random),
+ ?line ok = ?PRIM_FILE:write(Fd2, Line1),
+ ?line ok = ?PRIM_FILE:write(Fd2, Line2),
+ ?line ok = ?PRIM_FILE:close(Fd2),
+
+ ?line {ok, Fd3} = ?PRIM_FILE:open(Advise, [write]),
+ ?line ok = ?PRIM_FILE:advise(Fd3, 0, 0, sequential),
+ ?line ok = ?PRIM_FILE:write(Fd3, Line1),
+ ?line ok = ?PRIM_FILE:write(Fd3, Line2),
+ ?line ok = ?PRIM_FILE:close(Fd3),
+
+ ?line {ok, Fd4} = ?PRIM_FILE:open(Advise, [write]),
+ ?line ok = ?PRIM_FILE:advise(Fd4, 0, 0, will_need),
+ ?line ok = ?PRIM_FILE:write(Fd4, Line1),
+ ?line ok = ?PRIM_FILE:write(Fd4, Line2),
+ ?line ok = ?PRIM_FILE:close(Fd4),
+
+ ?line {ok, Fd5} = ?PRIM_FILE:open(Advise, [write]),
+ ?line ok = ?PRIM_FILE:advise(Fd5, 0, 0, dont_need),
+ ?line ok = ?PRIM_FILE:write(Fd5, Line1),
+ ?line ok = ?PRIM_FILE:write(Fd5, Line2),
+ ?line ok = ?PRIM_FILE:close(Fd5),
+
+ ?line {ok, Fd6} = ?PRIM_FILE:open(Advise, [write]),
+ ?line ok = ?PRIM_FILE:advise(Fd6, 0, 0, no_reuse),
+ ?line ok = ?PRIM_FILE:write(Fd6, Line1),
+ ?line ok = ?PRIM_FILE:write(Fd6, Line2),
+ ?line ok = ?PRIM_FILE:close(Fd6),
+
+ ?line {ok, Fd7} = ?PRIM_FILE:open(Advise, [write]),
+ ?line {error, einval} = ?PRIM_FILE:advise(Fd7, 0, 0, bad_advise),
+ ?line ok = ?PRIM_FILE:close(Fd7),
+
+ %% test write without advise, then a read after an advise
+ ?line {ok, Fd8} = ?PRIM_FILE:open(Advise, [write]),
+ ?line ok = ?PRIM_FILE:write(Fd8, Line1),
+ ?line ok = ?PRIM_FILE:write(Fd8, Line2),
+ ?line ok = ?PRIM_FILE:close(Fd8),
+ ?line {ok, Fd9} = ?PRIM_FILE:open(Advise, [read]),
+ Offset = 0,
+ %% same as a 0 length in some implementations
+ Length = length(Line1) + length(Line2),
+ ?line ok = ?PRIM_FILE:advise(Fd9, Offset, Length, sequential),
+ ?line {ok, Line1} = ?PRIM_FILE:read_line(Fd9),
+ ?line {ok, Line2} = ?PRIM_FILE:read_line(Fd9),
+ ?line eof = ?PRIM_FILE:read_line(Fd9),
+ ?line ok = ?PRIM_FILE:close(Fd9),
+
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
delete_a(suite) -> [];
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index 64a358f114..9a191f9aeb 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1,4 +1,4 @@
-#
+#
# %CopyrightBegin%
#
# Copyright Ericsson AB 1997-2010. All Rights Reserved.
@@ -15,6 +15,6 @@
# under the License.
#
# %CopyrightEnd%
-#
+#
-KERNEL_VSN = 2.14
+KERNEL_VSN = 2.14.1
diff --git a/lib/megaco/doc/src/Makefile b/lib/megaco/doc/src/Makefile
index 2355a1b8b9..4b3c117b20 100644
--- a/lib/megaco/doc/src/Makefile
+++ b/lib/megaco/doc/src/Makefile
@@ -125,6 +125,12 @@ DVIPS_FLAGS +=
$(HTMLDIR)/%.gif: %.gif
$(INSTALL_DATA) $< $@
+$(HTMLDIR)/%.jpg: %.jpg
+ $(INSTALL_DATA) $< $@
+
+$(HTMLDIR)/%.png: %.png
+ $(INSTALL_DATA) $< $@
+
ifdef DOCSUPPORT
docs: pdf html man
@@ -135,7 +141,7 @@ $(TOP_PDF_FILE): $(XML_FILES)
pdf: $(TOP_PDF_FILE)
-html: gifs $(HTML_REF_MAN_FILE)
+html: imgs $(HTML_REF_MAN_FILE)
clean clean_docs: clean_html clean_man
rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
@@ -149,7 +155,7 @@ else
ifeq ($(DOCTYPE),ps)
docs: ps
else
-docs: html gifs man
+docs: html imgs man
endif
endif
@@ -157,7 +163,7 @@ pdf: $(TOP_PDF_FILE)
ps: $(TOP_PS_FILE)
-html: gifs $(HTML_FILES) $(TOP_HTML_FILES)
+html: imgs $(HTML_FILES) $(TOP_HTML_FILES)
mhtml: html $(HTML_REF3_FILES) $(HTML_CHAPTER_FILES)
@@ -182,7 +188,7 @@ clean_man:
clean_html:
rm -rf $(HTMLDIR)/*
-gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
+imgs: $(IMG_FILES:%=$(HTMLDIR)/%)
man: $(MAN3_FILES)
@@ -208,7 +214,7 @@ info:
@echo "XML_REF3_FILES = $(XML_REF3_FILES)"
@echo "XML_CHAPTER_FILES = $(XML_CHAPTER_FILES)"
@echo ""
- @echo "GIF_FILES = $(GIF_FILES)"
+ @echo "IMG_FILES = $(IMG_FILES)"
@echo ""
@echo "TEX_FILES_USERS_GUIDE = $(TEX_FILES_USERS_GUIDE)"
@echo "TEX_FILES_REF_MAN = $(TEX_FILES_REF_MAN)"
@@ -258,7 +264,7 @@ release_docs_spec: ps
else
release_docs_spec: docs
$(INSTALL_DIR) $(RELSYSDIR)/doc/html
- $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTML_FILES) \
+ $(INSTALL_DATA) $(IMG_FILES) $(EXTRA_FILES) $(HTML_FILES) \
$(RELSYSDIR)/doc/html
$(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR)
$(INSTALL_DIR) $(RELEASE_PATH)/man/man3
diff --git a/lib/megaco/doc/src/files.mk b/lib/megaco/doc/src/files.mk
index debc5d278d..efacb7e422 100644
--- a/lib/megaco/doc/src/files.mk
+++ b/lib/megaco/doc/src/files.mk
@@ -1,20 +1,20 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2001-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2001-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%
XML_APPLICATION_FILES = \
@@ -56,7 +56,7 @@ XML_CHAPTER_FILES = \
BOOK_FILES = book.xml
-GIF_FILES = \
+IMG_FILES = \
single_node_config.gif \
distr_node_config.gif \
megaco_sys_arch.gif \
@@ -70,4 +70,4 @@ GIF_FILES = \
MG_startup_call_flow.gif \
call_flow.gif \
call_flow_cont.gif \
- mstone1.gif
+ mstone1.jpg
diff --git a/lib/megaco/doc/src/megaco.xml b/lib/megaco/doc/src/megaco.xml
index 0fb9d5aac6..ae9e250965 100644
--- a/lib/megaco/doc/src/megaco.xml
+++ b/lib/megaco/doc/src/megaco.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2000</year><year>2009</year>
+ <year>2000</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>megaco</title>
@@ -40,6 +40,16 @@
<section>
<title>DATA TYPES</title>
<code type="none"><![CDATA[
+megaco_mid() = ip4Address() | ip6Address() |
+ domainName() | deviceName() |
+ mtpAddress()
+ip4Address() = #'IP4Address'{}
+ip6Address() = #'IP6Address'{}
+domainName() = #'DomainName'{}
+deviceName() = pathName()
+pathName() = ia5String(1..64)
+mtpAddress() = octetString(2..4)
+
action_request() = #'ActionRequest'{}
action_reply() = #'ActionReply'{}
error_desc() = #'ErrorDescriptor'{}
diff --git a/lib/megaco/doc/src/megaco_performance.xml b/lib/megaco/doc/src/megaco_performance.xml
index 72b5c156ba..eb3d852a19 100644
--- a/lib/megaco/doc/src/megaco_performance.xml
+++ b/lib/megaco/doc/src/megaco_performance.xml
@@ -50,9 +50,15 @@
of these configurations for each codec. The figures presented are
the average of all used messages.</p>
- <p>For comparison, also included are performance figures
- where the flex driver was built as <c>non-reentrant</c> flex
- (figures within parenthesis). </p>
+ <p>For comparison, also included are first, performance figures with
+ megaco (including the measurement software) and asn1 applications
+ hipe-compiled (second figure in the time columns, note that per bin
+ decode had some issues so those figures are not included), and second,
+ performance figures where the flex driver was built as
+ <c>non-reentrant</c> flex
+ (third figure in the time columns,
+ only valid for text codecs using the flex-scanner,
+ figures within parenthesis). </p>
<table>
<row>
@@ -67,122 +73,122 @@
<row>
<cell align="left" valign="middle">pretty</cell>
<cell align="right" valign="middle">336</cell>
- <cell align="right" valign="middle">22</cell>
- <cell align="right" valign="middle">76</cell>
- <cell align="right" valign="middle">98</cell>
+ <cell align="right" valign="middle">20 / 13</cell>
+ <cell align="right" valign="middle">75 / 40</cell>
+ <cell align="right" valign="middle">95 / 53</cell>
</row>
<row>
<cell align="left" valign="middle">pretty [flex]</cell>
<cell align="right" valign="middle">336</cell>
- <cell align="right" valign="middle">22 (22)</cell>
- <cell align="right" valign="middle">41 (40)</cell>
- <cell align="right" valign="middle">63 (62)</cell>
+ <cell align="right" valign="middle">20 / 13 / 20</cell>
+ <cell align="right" valign="middle">39 / 33 / 38</cell>
+ <cell align="right" valign="middle">59 / 46 / 58</cell>
</row>
<!-- COMPACT -->
<row>
<cell align="left" valign="middle">compact</cell>
<cell align="right" valign="middle">181</cell>
- <cell align="right" valign="middle">19</cell>
- <cell align="right" valign="middle">63</cell>
- <cell align="right" valign="middle">82</cell>
+ <cell align="right" valign="middle">17 / 10</cell>
+ <cell align="right" valign="middle">62 / 35</cell>
+ <cell align="right" valign="middle">79 / 45</cell>
</row>
<row>
<cell align="left" valign="middle">compact [flex]</cell>
<cell align="right" valign="middle">181</cell>
- <cell align="right" valign="middle">19 (19)</cell>
- <cell align="right" valign="middle">38 (36)</cell>
- <cell align="right" valign="middle">57 (55)</cell>
+ <cell align="right" valign="middle">17 / 10 / 17</cell>
+ <cell align="right" valign="middle">37 / 31 / 36</cell>
+ <cell align="right" valign="middle">54 / 41 / 53</cell>
</row>
<!-- PER -->
<row>
<cell align="left" valign="middle">per bin</cell>
<cell align="right" valign="middle">91</cell>
- <cell align="right" valign="middle">63</cell>
- <cell align="right" valign="middle">69</cell>
- <cell align="right" valign="middle">132</cell>
+ <cell align="right" valign="middle">60 / 29</cell>
+ <cell align="right" valign="middle">64 / -</cell>
+ <cell align="right" valign="middle">124 / -</cell>
</row>
<row>
<cell align="left" valign="middle">per bin [driver]</cell>
<cell align="right" valign="middle">91</cell>
- <cell align="right" valign="middle">43</cell>
- <cell align="right" valign="middle">45</cell>
- <cell align="right" valign="middle">88</cell>
+ <cell align="right" valign="middle">39 / 24</cell>
+ <cell align="right" valign="middle">42 / 26</cell>
+ <cell align="right" valign="middle">81 / 50</cell>
</row>
<row>
<cell align="left" valign="middle">per bin [native]</cell>
<cell align="right" valign="middle">91</cell>
- <cell align="right" valign="middle">47</cell>
- <cell align="right" valign="middle">51</cell>
- <cell align="right" valign="middle">99</cell>
+ <cell align="right" valign="middle">45 / 21</cell>
+ <cell align="right" valign="middle">48 / -</cell>
+ <cell align="right" valign="middle">93 / -</cell>
</row>
<row>
<cell align="left" valign="middle">per bin [driver,native]</cell>
<cell align="right" valign="middle">91</cell>
- <cell align="right" valign="middle">26</cell>
- <cell align="right" valign="middle">29</cell>
- <cell align="right" valign="middle">55</cell>
+ <cell align="right" valign="middle">25 / 15</cell>
+ <cell align="right" valign="middle">27 / 18</cell>
+ <cell align="right" valign="middle">52 / 33</cell>
</row>
<!-- BER -->
<row>
<cell align="left" valign="middle">ber bin</cell>
<cell align="right" valign="middle">165</cell>
- <cell align="right" valign="middle">35</cell>
- <cell align="right" valign="middle">42</cell>
- <cell align="right" valign="middle">77</cell>
+ <cell align="right" valign="middle">32 / 19</cell>
+ <cell align="right" valign="middle">38 / 21</cell>
+ <cell align="right" valign="middle">70 / 40</cell>
</row>
<row>
<cell align="left" valign="middle">ber bin [driver]</cell>
<cell align="right" valign="middle">165</cell>
- <cell align="right" valign="middle">35</cell>
- <cell align="right" valign="middle">37</cell>
- <cell align="right" valign="middle">72</cell>
+ <cell align="right" valign="middle">32 / 19</cell>
+ <cell align="right" valign="middle">33 / 20</cell>
+ <cell align="right" valign="middle">65 / 39</cell>
</row>
<row>
<cell align="left" valign="middle">ber bin [native]</cell>
<cell align="right" valign="middle">165</cell>
- <cell align="right" valign="middle">19</cell>
- <cell align="right" valign="middle">26</cell>
- <cell align="right" valign="middle">45</cell>
+ <cell align="right" valign="middle">17 / 11</cell>
+ <cell align="right" valign="middle">25 / 13</cell>
+ <cell align="right" valign="middle">42 / 24</cell>
</row>
<row>
<cell align="left" valign="middle">ber bin [driver,native]</cell>
<cell align="right" valign="middle">165</cell>
- <cell align="right" valign="middle">19</cell>
- <cell align="right" valign="middle">20</cell>
- <cell align="right" valign="middle">39</cell>
+ <cell align="right" valign="middle">17 / 11</cell>
+ <cell align="right" valign="middle">17 / 12</cell>
+ <cell align="right" valign="middle">34 / 23</cell>
</row>
<!-- ERLANG -->
<row>
<cell align="left" valign="middle">erl_dist</cell>
<cell align="right" valign="middle">875</cell>
- <cell align="right" valign="middle">5</cell>
- <cell align="right" valign="middle">10</cell>
- <cell align="right" valign="middle">15</cell>
+ <cell align="right" valign="middle">5 / 5</cell>
+ <cell align="right" valign="middle">10 / 10</cell>
+ <cell align="right" valign="middle">15 / 15</cell>
</row>
<row>
<cell align="left" valign="middle">erl_dist [megaco_compressed]</cell>
<cell align="right" valign="middle">405</cell>
- <cell align="right" valign="middle">6</cell>
- <cell align="right" valign="middle">7</cell>
- <cell align="right" valign="middle">13</cell>
+ <cell align="right" valign="middle">6 / 4</cell>
+ <cell align="right" valign="middle">7 / 4</cell>
+ <cell align="right" valign="middle">13 / 8</cell>
</row>
<row>
<cell align="left" valign="middle">erl_dist [compressed]</cell>
<cell align="right" valign="middle">345</cell>
- <cell align="right" valign="middle">86</cell>
- <cell align="right" valign="middle">21</cell>
- <cell align="right" valign="middle">107</cell>
+ <cell align="right" valign="middle">47 / 47</cell>
+ <cell align="right" valign="middle">20 / 20</cell>
+ <cell align="right" valign="middle">67 / 67</cell>
</row>
<row>
<cell align="left" valign="middle">erl_dist [megaco_compressed,compressed]</cell>
<cell align="right" valign="middle">200</cell>
- <cell align="right" valign="middle">71</cell>
- <cell align="right" valign="middle">12</cell>
- <cell align="right" valign="middle">83</cell>
+ <cell align="right" valign="middle">34 / 33</cell>
+ <cell align="right" valign="middle">11 / 9</cell>
+ <cell align="right" valign="middle">45 / 42</cell>
</row>
<tcaption>Codec performance</tcaption>
@@ -201,8 +207,8 @@
<p>When running SMP erlang on a multi-core machine the "throughput"
is significantly higher. The mstone1 test is an extreme test,
but it shows what is gained by using the reentrant flex-scanner. </p>
- <image file="mstone1.gif">
- <icaption>MStone1 with mstone1.sh -d flex -s 8</icaption>
+ <image file="mstone1.jpg">
+ <icaption>MStone1 with mstone1.sh -d flex -s 4</icaption>
</image>
</section>
@@ -276,10 +282,10 @@ MEGACO/1 [124.124.124.222]
<section>
<title>Setup</title>
<p>The measurements has been performed on a
- Dell PowerEdge 1950iii with
- 2* Intel Xeon L5430 @ 2.66 GHz, with 8 GB memory and
- running SLES 10 SP2 x86_64, kernel 2.6.16.60-0.34-smp.
- Software versions was open source OTP R13B and megaco-3.11.</p>
+ HP xw4600 Workstation with
+ a Intel(R) Core(TM)2 Quad CPU Q9550 @ 2.83GHz, with 4 GB memory and
+ running Ubuntu 10.04 x86_64, kernel 2.6.32-22-generic.
+ Software versions was open source OTP R13B04 (megaco-3.14).</p>
</section>
<section>
@@ -302,7 +308,7 @@ MEGACO/1 [124.124.124.222]
to our fastest text encoder (compact). </p>
</item>
<item>
- <p>our fastest binary decoder (ber) is about 47% (44%) faster than our
+ <p>our fastest binary decoder (ber) is about 54% (61%) faster than our
fastest text decoder (compact). </p>
</item>
</list>
diff --git a/lib/megaco/doc/src/megaco_user.xml b/lib/megaco/doc/src/megaco_user.xml
index 6a0de29385..7332fa684d 100644
--- a/lib/megaco/doc/src/megaco_user.xml
+++ b/lib/megaco/doc/src/megaco_user.xml
@@ -471,7 +471,7 @@ protocol_version() = integer() ]]></code>
<v>transaction_result() = action_reps()</v>
<v>segment_result() = {segment_no(), last_segment(), action_reps()}</v>
<v>action_reps() = [action_reply()]</v>
- <v>failure() = {error, reason()}</v>
+ <v>failure() = {error, reason()} | {error, ReplyNo, reason()}</v>
<v>reason() = transaction_reason() | segment_reason() | user_cancel_reason() | send_reason() | other_reason()</v>
<v>transaction_reason() = error_desc()</v>
<v>segment_reason() = {segment_no(), last_segment(), error_desc()}</v>
@@ -486,6 +486,7 @@ protocol_version() = integer() ]]></code>
<v>send_failed_reason() = {send_message_failed, reason_for_send_failure()}</v>
<v>reason_for_send_failure() = term()</v>
<v>ReplyData = reply_data()</v>
+ <v>ReplyNo = integer() > 0</v>
<v>reply_data() = term()</v>
<v>Extra = term()</v>
</type>
diff --git a/lib/megaco/doc/src/mstone1-s8flex.log b/lib/megaco/doc/src/mstone1-s8flex.log
deleted file mode 100644
index d9d28399f3..0000000000
--- a/lib/megaco/doc/src/mstone1-s8flex.log
+++ /dev/null
@@ -1,234 +0,0 @@
-
----------------------------------------------
-Factor 01
-
-erl -noshell -smp +S 8 -pa /ldisk/bmk/pgm/otp-r13b-m311p08-re/lib/erlang/lib/megaco-3.11/examples/meas -s megaco_codec_mstone1 start_flex time_test 01 -s init stop
-
-OS: unix-linux: 2.6.16
-System architecture: x86_64-unknown-linux-gnu
-OTP release: R13B
-System version: Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
-Heap type: private
-Global heap size: 0
-Thread support: true
-Thread pool size: 0
-Process limit: 32768
-SMP support: true
-Num schedulers: 8
-Scheduler bindings: {0,1,4,5,2,3,6,7}
-Scheduler bind type: thread_no_node_processor_spread
-Cpu topology: [{processor,[{core,{logical,0}},{core,{logical,4}},{core,{logical,2}},{core,{logical,6}}]},{processor,[{core,{logical,1}},{core,{logical,5}},{core,{logical,3}},{core,{logical,7}}]}]
-Megaco version: megaco-3.11-p08
-ASN.1 version: 1.6.10
-
- * starting runners [16] ................ done
- * await runners ready ................ done
- * now snooze
- * release them
-
-16 runners
-Runner heap size data:
- Min: 75025
- Max: 1682835
- Avg: 582577
-Runner reductions data:
- Min: 927126711
- Max: 5292487523
- Avg: 1929935038
-
-MStone: 63283727
-
----------------------------------------------
-Factor 02
-
-erl -noshell -smp +S 8 -pa /ldisk/bmk/pgm/otp-r13b-m311p08-re/lib/erlang/lib/megaco-3.11/examples/meas -s megaco_codec_mstone1 start_flex time_test 02 -s init stop
-
-OS: unix-linux: 2.6.16
-System architecture: x86_64-unknown-linux-gnu
-OTP release: R13B
-System version: Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
-Heap type: private
-Global heap size: 0
-Thread support: true
-Thread pool size: 0
-Process limit: 32768
-SMP support: true
-Num schedulers: 8
-Scheduler bindings: {0,1,4,5,2,3,6,7}
-Scheduler bind type: thread_no_node_processor_spread
-Cpu topology: [{processor,[{core,{logical,0}},{core,{logical,4}},{core,{logical,2}},{core,{logical,6}}]},{processor,[{core,{logical,1}},{core,{logical,5}},{core,{logical,3}},{core,{logical,7}}]}]
-Megaco version: megaco-3.11-p08
-ASN.1 version: 1.6.10
-
- * starting runners [32] ................................ done
- * await runners ready ................................ done
- * now snooze
- * release them
-
-32 runners
-Runner heap size data:
- Min: 75025
- Max: 1346269
- Avg: 388569
-Runner reductions data:
- Min: 645498054
- Max: 2774469009
- Avg: 943407719
-
-MStone: 61441342
-
----------------------------------------------
-Factor 04
-
-erl -noshell -smp +S 8 -pa /ldisk/bmk/pgm/otp-r13b-m311p08-re/lib/erlang/lib/megaco-3.11/examples/meas -s megaco_codec_mstone1 start_flex time_test 04 -s init stop
-
-OS: unix-linux: 2.6.16
-System architecture: x86_64-unknown-linux-gnu
-OTP release: R13B
-System version: Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
-Heap type: private
-Global heap size: 0
-Thread support: true
-Thread pool size: 0
-Process limit: 32768
-SMP support: true
-Num schedulers: 8
-Scheduler bindings: {0,1,4,5,2,3,6,7}
-Scheduler bind type: thread_no_node_processor_spread
-Cpu topology: [{processor,[{core,{logical,0}},{core,{logical,4}},{core,{logical,2}},{core,{logical,6}}]},{processor,[{core,{logical,1}},{core,{logical,5}},{core,{logical,3}},{core,{logical,7}}]}]
-Megaco version: megaco-3.11-p08
-ASN.1 version: 1.6.10
-
- * starting runners [64] ................................................................ done
- * await runners ready ................................................................ done
- * now snooze
- * release them
-
-64 runners
-Runner heap size data:
- Min: 75025
- Max: 1682835
- Avg: 462690
-Runner reductions data:
- Min: 395464832
- Max: 916378232
- Avg: 507760636
-
-MStone: 66958216
-
----------------------------------------------
-Factor 08
-
-erl -noshell -smp +S 8 -pa /ldisk/bmk/pgm/otp-r13b-m311p08-re/lib/erlang/lib/megaco-3.11/examples/meas -s megaco_codec_mstone1 start_flex time_test 08 -s init stop
-
-OS: unix-linux: 2.6.16
-System architecture: x86_64-unknown-linux-gnu
-OTP release: R13B
-System version: Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
-Heap type: private
-Global heap size: 0
-Thread support: true
-Thread pool size: 0
-Process limit: 32768
-SMP support: true
-Num schedulers: 8
-Scheduler bindings: {0,1,4,5,2,3,6,7}
-Scheduler bind type: thread_no_node_processor_spread
-Cpu topology: [{processor,[{core,{logical,0}},{core,{logical,4}},{core,{logical,2}},{core,{logical,6}}]},{processor,[{core,{logical,1}},{core,{logical,5}},{core,{logical,3}},{core,{logical,7}}]}]
-Megaco version: megaco-3.11-p08
-ASN.1 version: 1.6.10
-
- * starting runners [128] ................................................................................................................................ done
- * await runners ready ................................................................................................................................ done
- * now snooze
- * release them
-
-128 runners
-Runner heap size data:
- Min: 75025
- Max: 832040
- Avg: 173900
-Runner reductions data:
- Min: 236710819
- Max: 457961244
- Avg: 269562568
-
-MStone: 72098418
-
----------------------------------------------
-Factor 16
-
-erl -noshell -smp +S 8 -pa /ldisk/bmk/pgm/otp-r13b-m311p08-re/lib/erlang/lib/megaco-3.11/examples/meas -s megaco_codec_mstone1 start_flex time_test 16 -s init stop
-
-OS: unix-linux: 2.6.16
-System architecture: x86_64-unknown-linux-gnu
-OTP release: R13B
-System version: Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
-Heap type: private
-Global heap size: 0
-Thread support: true
-Thread pool size: 0
-Process limit: 32768
-SMP support: true
-Num schedulers: 8
-Scheduler bindings: {0,1,4,5,2,3,6,7}
-Scheduler bind type: thread_no_node_processor_spread
-Cpu topology: [{processor,[{core,{logical,0}},{core,{logical,4}},{core,{logical,2}},{core,{logical,6}}]},{processor,[{core,{logical,1}},{core,{logical,5}},{core,{logical,3}},{core,{logical,7}}]}]
-Megaco version: megaco-3.11-p08
-ASN.1 version: 1.6.10
-
- * starting runners [256] ................................................................................................................................................................................................................................................................ done
- * await runners ready ................................................................................................................................................................................................................................................................ done
- * now snooze
- * release them
-
-256 runners
-Runner heap size data:
- Min: 75025
- Max: 317811
- Avg: 131652
-Runner reductions data:
- Min: 134104991
- Max: 163429204
- Avg: 142654707
-
-MStone: 77139535
-
----------------------------------------------
-Factor 32
-
-erl -noshell -smp +S 8 -pa /ldisk/bmk/pgm/otp-r13b-m311p08-re/lib/erlang/lib/megaco-3.11/examples/meas -s megaco_codec_mstone1 start_flex time_test 32 -s init stop
-
-OS: unix-linux: 2.6.16
-System architecture: x86_64-unknown-linux-gnu
-OTP release: R13B
-System version: Erlang R13B (erts-5.7.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll:false]
-Heap type: private
-Global heap size: 0
-Thread support: true
-Thread pool size: 0
-Process limit: 32768
-SMP support: true
-Num schedulers: 8
-Scheduler bindings: {0,1,4,5,2,3,6,7}
-Scheduler bind type: thread_no_node_processor_spread
-Cpu topology: [{processor,[{core,{logical,0}},{core,{logical,4}},{core,{logical,2}},{core,{logical,6}}]},{processor,[{core,{logical,1}},{core,{logical,5}},{core,{logical,3}},{core,{logical,7}}]}]
-Megaco version: megaco-3.11-p08
-ASN.1 version: 1.6.10
-
- * starting runners [512] ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ done
- * await runners ready ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................ done
- * now snooze
- * release them
-
-512 runners
-Runner heap size data:
- Min: 75025
- Max: 196418
- Avg: 107328
-Runner reductions data:
- Min: 71186547
- Max: 74170110
- Avg: 72380653
-
-MStone: 78820851
diff --git a/lib/megaco/doc/src/mstone1.gif b/lib/megaco/doc/src/mstone1.gif
deleted file mode 100644
index 54c9c5514c..0000000000
--- a/lib/megaco/doc/src/mstone1.gif
+++ /dev/null
Binary files differ
diff --git a/lib/megaco/doc/src/mstone1.jpg b/lib/megaco/doc/src/mstone1.jpg
index b417429a08..3edc65faf1 100644
--- a/lib/megaco/doc/src/mstone1.jpg
+++ b/lib/megaco/doc/src/mstone1.jpg
Binary files differ
diff --git a/lib/megaco/doc/src/mstone1.png b/lib/megaco/doc/src/mstone1.png
deleted file mode 100644
index 19af210abc..0000000000
--- a/lib/megaco/doc/src/mstone1.png
+++ /dev/null
Binary files differ
diff --git a/lib/megaco/doc/src/mstone1.ps b/lib/megaco/doc/src/mstone1.ps
deleted file mode 100644
index 6436a4eb43..0000000000
--- a/lib/megaco/doc/src/mstone1.ps
+++ /dev/null
@@ -1,1959 +0,0 @@
-%!PS-Adobe-3.0
-%%Creator: GIMP PostScript file plugin V 1.17 by Peter Kirchgessner
-%%Title: mstone1_html_m5496b992.ps
-%%CreationDate: Fri May 29 19:07:25 2009
-%%DocumentData: Clean7Bit
-%%LanguageLevel: 2
-%%Pages: 1
-%%BoundingBox: 14 14 476 247
-%%EndComments
-%%BeginProlog
-% Use own dictionary to avoid conflicts
-10 dict begin
-%%EndProlog
-%%Page: 1 1
-% Translate for offset
-14.173228346456694 14.173228346456694 translate
-% Translate to begin of first scanline
-0 231.99920747433686 translate
-460.99842519685041 -231.99920747433686 scale
-% Image geometry
-461 232 8
-% Transformation matrix
-[ 461 0 0 232 0 0 ]
-% Strings to hold RGB-samples per scanline
-/rstr 461 string def
-/gstr 461 string def
-/bstr 461 string def
-{currentfile /ASCII85Decode filter /RunLengthDecode filter rstr readstring pop}
-{currentfile /ASCII85Decode filter /RunLengthDecode filter gstr readstring pop}
-{currentfile /ASCII85Decode filter /RunLengthDecode filter bstr readstring pop}
-true 3
-%%BeginData: 120502 ASCII Bytes
-colorimage
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-i;[-^"<[I>jo>>[s!S-Pn#nhl!"q57bl%G=s7lKks8;orq>UEis7ZKkpAapfqu?Nmr:^0\rj;e)
-s8Dutrr<#nq!DD]s0Gl?!Ao]1s8;oe!<;Hd"Sr)ls7>sarVlfr/b/l@q#CBds82ck([qD(r;Z+-
-8G3#_q>X&-q#CBlrVZ]fs7lWkrV6<jq"ssfrrVrprVcaFr<)rsrr)lpo`#!n$31)/s8VZin5BJi
-oDZ*k&-j;?o`+scYV-/(\,61)<Vugjs8VP@2$sI+9X"<ms7ZKmo_\Zn>9<i*IeWm8s8W#ss6a>7
-!'%(Ss5mc1$9F^WqXX[`s7QE7?l9"V-$I''!:^$grVccn!WW2js8VWhoDeR_rsA5qr;V'f!%3Qh
-rVnG1s8W&:BG^ja+`,[)rrEc4s7u]mq>Wh_s8Vrqr;ZWnp](3as8)ckq>^KXrr35rs8Vlns8W)t
-ruq.6s7?$[s8VurqZ$9hs7--bmf3=ap\k-lp1<7`r;Zfpir=N~>
-i;[-^"<[I>jo>>[s!S-Pn#nhl!"q57bl%G=s7lKks8;orq>UEis7ZKkpAapfqu?Nmr:^0\rj;e)
-s8Dutrr<#nq!DD]s0Gl?!Ao]1s8;oe!<;Hd"Sr)ls7>sarVlfr/b/l@q#CBds82ck([qD(r;Z+-
-8G3#_q>X&-q#CBlrVZ]fs7lWkrV6<jq"ssfrrVrprVcaFr<)rsrr)lpo`#!n$31)/s8VZin5BJi
-oDZ*k&-j;?o`+scYV-/(\,61)<Vugjs8VP@2$sI+9X"<ms7ZKmo_\Zn>9<i*IeWm8s8W#ss6a>7
-!'%(Ss5mc1$9F^WqXX[`s7QE7?l9"V-$I''!:^$grVccn!WW2js8VWhoDeR_rsA5qr;V'f!%3Qh
-rVnG1s8W&:BG^ja+`,[)rrEc4s7u]mq>Wh_s8Vrqr;ZWnp](3as8)ckq>^KXrr35rs8Vlns8W)t
-ruq.6s7?$[s8VurqZ$9hs7--bmf3=ap\k-lp1<7`r;Zfpir=N~>
-i;[-^"<[I>jo>>[s!S-Pn#nhl!"q57bl%G=s7lKks8;orq>UEis7ZKkpAapfqu?Nmr:^0\rj;e)
-s8Dutrr<#nq!DD]s0Gl?!Ao]1s8;oe!<;Hd"Sr)ls7>sarVlfr/b/l@q#CBds82ck([qD(r;Z+-
-8G3#_q>X&-q#CBlrVZ]fs7lWkrV6<jq"ssfrrVrprVcaFr<)rsrr)lpo`#!n$31)/s8VZin5BJi
-oDZ*k&-j;?o`+scYV-/(\,61)<Vugjs8VP@2$sI+9X"<ms7ZKmo_\Zn>9<i*IeWm8s8W#ss6a>7
-!'%(Ss5mc1$9F^WqXX[`s7QE7?l9"V-$I''!:^$grVccn!WW2js8VWhoDeR_rsA5qr;V'f!%3Qh
-rVnG1s8W&:BG^ja+`,[)rrEc4s7u]mq>Wh_s8Vrqr;ZWnp](3as8)ckq>^KXrr35rs8Vlns8W)t
-ruq.6s7?$[s8VurqZ$9hs7--bmf3=ap\k-lp1<7`r;Zfpir=N~>
-iVtA&rr<bBqu6Wds4%SZs+<S^q"W^NJ3s8@meQb[r9s[crVuors69R`q#::FqZ$Qjrr<#ks/l_1
-r;Z`qs8N&ns8Pa:s7KNEj7gnuGk1l7O91hXnc8^Ws6ose')2G)!<<)is8VZas8VrqrrE)krVmPV
-&W6Sfs*kU(s7G^Ys8;ors8V]hrsAN$rqZTir;ZforVm9("8i,tnGE7bp'gKjs%rUhq>]s9#70bt
-s'(TAs2e'#me,NGYlD]_]Dqa-s8)]oRic.Ls6/p>1H44gs7ZKls7'<Ae,7KeMuWhJs7>OU'qaXY
-Q!44a2h^2\T3:=UruCM-s-G.0iqhZ[?qLA7s8VoprVuI#kl(P[k5Y)Prr`3!o'ZMW0P!u3s56>%
-pAY*cs7lVu-^WlnhTC"&s7lX)XnMbos8C%?!<<)ts8N&js8VQes8)]o"nMKhs7,aYrs/&nq"+Oc
-qY^?m%efr!rr;lqo()MSr;ZHQrr3?"s8C2KpAP$krq5=OJ,~>
-iVtA&rr<bBqu6Wds4%SZs+<S^q"W^NJ3s8@meQb[r9s[crVuors69R`q#::FqZ$Qjrr<#ks/l_1
-r;Z`qs8N&ns8Pa:s7KNEj7gnuGk1l7O91hXnc8^Ws6ose')2G)!<<)is8VZas8VrqrrE)krVmPV
-&W6Sfs*kU(s7G^Ys8;ors8V]hrsAN$rqZTir;ZforVm9("8i,tnGE7bp'gKjs%rUhq>]s9#70bt
-s'(TAs2e'#me,NGYlD]_]Dqa-s8)]oRic.Ls6/p>1H44gs7ZKls7'<Ae,7KeMuWhJs7>OU'qaXY
-Q!44a2h^2\T3:=UruCM-s-G.0iqhZ[?qLA7s8VoprVuI#kl(P[k5Y)Prr`3!o'ZMW0P!u3s56>%
-pAY*cs7lVu-^WlnhTC"&s7lX)XnMbos8C%?!<<)ts8N&js8VQes8)]o"nMKhs7,aYrs/&nq"+Oc
-qY^?m%efr!rr;lqo()MSr;ZHQrr3?"s8C2KpAP$krq5=OJ,~>
-iVtA&rr<bBqu6Wds4%SZs+<S^q"W^NJ3s8@meQb[r9s[crVuors69R`q#::FqZ$Qjrr<#ks/l_1
-r;Z`qs8N&ns8Pa:s7KNEj7gnuGk1l7O91hXnc8^Ws6ose')2G)!<<)is8VZas8VrqrrE)krVmPV
-&W6Sfs*kU(s7G^Ys8;ors8V]hrsAN$rqZTir;ZforVm9("8i,tnGE7bp'gKjs%rUhq>]s9#70bt
-s'(TAs2e'#me,NGYlD]_]Dqa-s8)]oRic.Ls6/p>1H44gs7ZKls7'<Ae,7KeMuWhJs7>OU'qaXY
-Q!44a2h^2\T3:=UruCM-s-G.0iqhZ[?qLA7s8VoprVuI#kl(P[k5Y)Prr`3!o'ZMW0P!u3s56>%
-pAY*cs7lVu-^WlnhTC"&s7lX)XnMbos8C%?!<<)ts8N&js8VQes8)]o"nMKhs7,aYrs/&nq"+Oc
-qY^?m%efr!rr;lqo()MSr;ZHQrr3?"s8C2KpAP$krq5=OJ,~>
-iVsMdrr>bfrr;ors%6/hq@)lhn,E=ko(;q^s8)9bru_14rVuWlqZ$Tcs7cE_s7lWorV?Kn!&)Lr
-r;Z`qs8N#t.'ZYLmeleYs82cG/-#GF7K>jVr"Sl)p@SC]pAb'j)>O7*s8;osp%\Od(]aO7s8DZk
-s)AFjq8OP?nGiIerU]p`s5<nV%JTntp\jaaoDejOs7Q9grs&E(qu?Zqqu6UQ!<;cmo`+C[rV/$r
-$Ma5lo)J[grSmnXnR7@Ts8Rmls8W)unGfm(c27P*s8;QiK1>k?q"Odf!:]se$J-*hkPtP]s-Irp
-rr4S:s+4_[rVuTks7u]ap]&\`dIcf*s7?9jli@"`l2UY\rrE)lrVuoos8Dull2:Q%q#:`hrr;HX
-!WVlnnGi%.&^(.JoDeF^rVua+<^-N_)#aJ;:`9!,nG`I\s8V`hs8VZimf2kXs7QBk(ARe,qu?Hk
-s7lWks8V9^lhUDWs8)ZfqYq*#s8Duis8V]j>.4G(r;ZHMs*t~>
-iVsMdrr>bfrr;ors%6/hq@)lhn,E=ko(;q^s8)9bru_14rVuWlqZ$Tcs7cE_s7lWorV?Kn!&)Lr
-r;Z`qs8N#t.'ZYLmeleYs82cG/-#GF7K>jVr"Sl)p@SC]pAb'j)>O7*s8;osp%\Od(]aO7s8DZk
-s)AFjq8OP?nGiIerU]p`s5<nV%JTntp\jaaoDejOs7Q9grs&E(qu?Zqqu6UQ!<;cmo`+C[rV/$r
-$Ma5lo)J[grSmnXnR7@Ts8Rmls8W)unGfm(c27P*s8;QiK1>k?q"Odf!:]se$J-*hkPtP]s-Irp
-rr4S:s+4_[rVuTks7u]ap]&\`dIcf*s7?9jli@"`l2UY\rrE)lrVuoos8Dull2:Q%q#:`hrr;HX
-!WVlnnGi%.&^(.JoDeF^rVua+<^-N_)#aJ;:`9!,nG`I\s8V`hs8VZimf2kXs7QBk(ARe,qu?Hk
-s7lWks8V9^lhUDWs8)ZfqYq*#s8Duis8V]j>.4G(r;ZHMs*t~>
-iVsMdrr>bfrr;ors%6/hq@)lhn,E=ko(;q^s8)9bru_14rVuWlqZ$Tcs7cE_s7lWorV?Kn!&)Lr
-r;Z`qs8N#t.'ZYLmeleYs82cG/-#GF7K>jVr"Sl)p@SC]pAb'j)>O7*s8;osp%\Od(]aO7s8DZk
-s)AFjq8OP?nGiIerU]p`s5<nV%JTntp\jaaoDejOs7Q9grs&E(qu?Zqqu6UQ!<;cmo`+C[rV/$r
-$Ma5lo)J[grSmnXnR7@Ts8Rmls8W)unGfm(c27P*s8;QiK1>k?q"Odf!:]se$J-*hkPtP]s-Irp
-rr4S:s+4_[rVuTks7u]ap]&\`dIcf*s7?9jli@"`l2UY\rrE)lrVuoos8Dull2:Q%q#:`hrr;HX
-!WVlnnGi%.&^(.JoDeF^rVua+<^-N_)#aJ;:`9!,nG`I\s8V`hs8VZimf2kXs7QBk(ARe,qu?Hk
-s7lWks8V9^lhUDWs8)ZfqYq*#s8Duis8V]j>.4G(r;ZHMs*t~>
-hu>A1^]NWns8UpUgAq7!D=RZ+n,!(^!XA]1qu=Z&&kLmDs7cu%,760Ys8Mqh.K^9HrVmB&!W;ur
-s8W)uqu8sss8)`p./i`:k5bP^F=I8:JGoQKrrBGN!B&+$!!!$$s7QDa0-1[tp&>$ls6ose+nu"/
-/bh7Ss8VWhSfo'pRf<?bb<$_4_Z0Z5!j*[M9Z?]'s#p;_qu?Zqs6T4S!;$6im/Qq^potP)!<<&j
-s7H?hs61U)ru9Php&!_is76-gs6(<As6+D9(1oI>r(2A7rr4,.p&FgUs7bF]s8W#ss6)5Xs7uTm
-rr5gAlMpMVrr;]_rr3@+L]@DRs8Mrjmf*:brsIui$2sc%!!WE/kl(Miq=4LTs8NAjs8VcZ)?'S!
-k5SDNs8)cqpAb*knc&U.$0_Efp?`1<!;ZQmndM'D"XDHk*"4[RqY'shXY^#%PlLafMEq4nc^?0]
-1u/'/ruAa=*'MXCrs[:"2Xh6@s6fle']f;88cSDXs8DoWs*t~>
-hu>A1^]NWns8UpUgAq7!D=RZ+n,!(^!XA]1qu=Z&&kLmDs7cu%,760Ys8Mqh.K^9HrVmB&!W;ur
-s8W)uqu8sss8)`p./i`:k5bP^F=I8:JGoQKrrBGN!B&+$!!!$$s7QDa0-1[tp&>$ls6ose+nu"/
-/bh7Ss8VWhSfo'pRf<?bb<$_4_Z0Z5!j*[M9Z?]'s#p;_qu?Zqs6T4S!;$6im/Qq^potP)!<<&j
-s7H?hs61U)ru9Php&!_is76-gs6(<As6+D9(1oI>r(2A7rr4,.p&FgUs7bF]s8W#ss6)5Xs7uTm
-rr5gAlMpMVrr;]_rr3@+L]@DRs8Mrjmf*:brsIui$2sc%!!WE/kl(Miq=4LTs8NAjs8VcZ)?'S!
-k5SDNs8)cqpAb*knc&U.$0_Efp?`1<!;ZQmndM'D"XDHk*"4[RqY'shXY^#%PlLafMEq4nc^?0]
-1u/'/ruAa=*'MXCrs[:"2Xh6@s6fle']f;88cSDXs8DoWs*t~>
-hu>A1^]NWns8UpUgAq7!D=RZ+n,!(^!XA]1qu=Z&&kLmDs7cu%,760Ys8Mqh.K^9HrVmB&!W;ur
-s8W)uqu8sss8)`p./i`:k5bP^F=I8:JGoQKrrBGN!B&+$!!!$$s7QDa0-1[tp&>$ls6ose+nu"/
-/bh7Ss8VWhSfo'pRf<?bb<$_4_Z0Z5!j*[M9Z?]'s#p;_qu?Zqs6T4S!;$6im/Qq^potP)!<<&j
-s7H?hs61U)ru9Php&!_is76-gs6(<As6+D9(1oI>r(2A7rr4,.p&FgUs7bF]s8W#ss6)5Xs7uTm
-rr5gAlMpMVrr;]_rr3@+L]@DRs8Mrjmf*:brsIui$2sc%!!WE/kl(Miq=4LTs8NAjs8VcZ)?'S!
-k5SDNs8)cqpAb*knc&U.$0_Efp?`1<!;ZQmndM'D"XDHk*"4[RqY'shXY^#%PlLafMEq4nc^?0]
-1u/'/ruAa=*'MXCrs[:"2Xh6@s6fle']f;88cSDXs8DoWs*t~>
-hu=Gls!)ggr:-(+q$-Q.*rnZciW&rUr<<3#HEe%,rPo\lq=jqll18LAq"DES`;\%HNq`SHrrN&t
-rVuoss8MF/q#C?nh=psLqi"MGs(0mabZ+TBs764eh"6%KrVmE&q>^,f0WtH)25fU?s82Wls7ZKm
-g(jo5kPtDYXuF;bc8V'gY9D$HaYCQorr?WSr*M;YrVm'""8i,to`"k0r"U.P+0tq?s5tW#rrM9X
-s8V3\r6eJYq>V#urr3)e$M45qs%7iJs6<$gp=)Y:U&N7op@eO`s8V9^s7?8R6N@)]s8)a6Shg?n
-N;qlVV)86&S*L%Rr;Zff)WLPbs8V3\s8)Quq#C<is8W#tmJd+jqGDJ:s8Duarr3c/s8R::cMu?[
-J,''*n,ND(g&M*Ms7#sd!s/<QFT2;3KeMlps8V`f"BX+<M?$H#s)'jms8&H8^])G=M>mQ_f_'Cq
-8)j2O62:KF^+I@_as>1!!,:ots'SRus8Q(cqZ$@$pAb!hrr(pXJ,~>
-hu=Gls!)ggr:-(+q$-Q.*rnZciW&rUr<<3#HEe%,rPo\lq=jqll18LAq"DES`;\%HNq`SHrrN&t
-rVuoss8MF/q#C?nh=psLqi"MGs(0mabZ+TBs764eh"6%KrVmE&q>^,f0WtH)25fU?s82Wls7ZKm
-g(jo5kPtDYXuF;bc8V'gY9D$HaYCQorr?WSr*M;YrVm'""8i,to`"k0r"U.P+0tq?s5tW#rrM9X
-s8V3\r6eJYq>V#urr3)e$M45qs%7iJs6<$gp=)Y:U&N7op@eO`s8V9^s7?8R6N@)]s8)a6Shg?n
-N;qlVV)86&S*L%Rr;Zff)WLPbs8V3\s8)Quq#C<is8W#tmJd+jqGDJ:s8Duarr3c/s8R::cMu?[
-J,''*n,ND(g&M*Ms7#sd!s/<QFT2;3KeMlps8V`f"BX+<M?$H#s)'jms8&H8^])G=M>mQ_f_'Cq
-8)j2O62:KF^+I@_as>1!!,:ots'SRus8Q(cqZ$@$pAb!hrr(pXJ,~>
-hu=Gls!)ggr:-(+q$-Q.*rnZciW&rUr<<3#HEe%,rPo\lq=jqll18LAq"DES`;\%HNq`SHrrN&t
-rVuoss8MF/q#C?nh=psLqi"MGs(0mabZ+TBs764eh"6%KrVmE&q>^,f0WtH)25fU?s82Wls7ZKm
-g(jo5kPtDYXuF;bc8V'gY9D$HaYCQorr?WSr*M;YrVm'""8i,to`"k0r"U.P+0tq?s5tW#rrM9X
-s8V3\r6eJYq>V#urr3)e$M45qs%7iJs6<$gp=)Y:U&N7op@eO`s8V9^s7?8R6N@)]s8)a6Shg?n
-N;qlVV)86&S*L%Rr;Zff)WLPbs8V3\s8)Quq#C<is8W#tmJd+jqGDJ:s8Duarr3c/s8R::cMu?[
-J,''*n,ND(g&M*Ms7#sd!s/<QFT2;3KeMlps8V`f"BX+<M?$H#s)'jms8&H8^])G=M>mQ_f_'Cq
-8)j2O62:KF^+I@_as>1!!,:ots'SRus8Q(cqZ$@$pAb!hrr(pXJ,~>
-i;ZpV!<8Drq>9\2SG36as8TND%g5M.s8N)mrr5.-s8)bi70!8hq"t*c#l4KZ`;fi:gFN=$s8N,t
-s8Dutrr<#=.KBDCq#CBdq8EQds8Vid#64So&b,f+rr3>[%fcM.s7l3c-Ii%p"P4Ii$2+8s*:Npm
-s*k`&s7u]p6.>E)s3!1t6+Hspp8p1ErrE&u"8*3)o)AZ'r<)rsrr;lkqu?]pr7;p=s8R?rs6g^&
-s7Q0err<$rs8VNoqZ$![rt=f#s7H=ZWrK@ps8Du7!8?u:$NL#%oD\CPr9aO!$.f%Js7H0f!%,\^
-dO::Y"X1b_`$(]@q>]j^rrE)hs7cNm'CGer!<<*%!WWu9!<;9WrM:1ui;N[.m.^P[rr<!\!!!Er
-qZ$?js7Gmgs6]jUp](9^nc/@cs-aGes7RD(rrE)os8N)us#ol^gA(^=!<;Wi38aE/p?a[3!WVfl
-mg&CQs8E3"s7&%Vs8W&%2ZEi^lMpGI!8mbC!hKAas"VUpr;?TnjSs`~>
-i;ZpV!<8Drq>9\2SG36as8TND%g5M.s8N)mrr5.-s8)bi70!8hq"t*c#l4KZ`;fi:gFN=$s8N,t
-s8Dutrr<#=.KBDCq#CBdq8EQds8Vid#64So&b,f+rr3>[%fcM.s7l3c-Ii%p"P4Ii$2+8s*:Npm
-s*k`&s7u]p6.>E)s3!1t6+Hspp8p1ErrE&u"8*3)o)AZ'r<)rsrr;lkqu?]pr7;p=s8R?rs6g^&
-s7Q0err<$rs8VNoqZ$![rt=f#s7H=ZWrK@ps8Du7!8?u:$NL#%oD\CPr9aO!$.f%Js7H0f!%,\^
-dO::Y"X1b_`$(]@q>]j^rrE)hs7cNm'CGer!<<*%!WWu9!<;9WrM:1ui;N[.m.^P[rr<!\!!!Er
-qZ$?js7Gmgs6]jUp](9^nc/@cs-aGes7RD(rrE)os8N)us#ol^gA(^=!<;Wi38aE/p?a[3!WVfl
-mg&CQs8E3"s7&%Vs8W&%2ZEi^lMpGI!8mbC!hKAas"VUpr;?TnjSs`~>
-i;ZpV!<8Drq>9\2SG36as8TND%g5M.s8N)mrr5.-s8)bi70!8hq"t*c#l4KZ`;fi:gFN=$s8N,t
-s8Dutrr<#=.KBDCq#CBdq8EQds8Vid#64So&b,f+rr3>[%fcM.s7l3c-Ii%p"P4Ii$2+8s*:Npm
-s*k`&s7u]p6.>E)s3!1t6+Hspp8p1ErrE&u"8*3)o)AZ'r<)rsrr;lkqu?]pr7;p=s8R?rs6g^&
-s7Q0err<$rs8VNoqZ$![rt=f#s7H=ZWrK@ps8Du7!8?u:$NL#%oD\CPr9aO!$.f%Js7H0f!%,\^
-dO::Y"X1b_`$(]@q>]j^rrE)hs7cNm'CGer!<<*%!WWu9!<;9WrM:1ui;N[.m.^P[rr<!\!!!Er
-qZ$?js7Gmgs6]jUp](9^nc/@cs-aGes7RD(rrE)os8N)us#ol^gA(^=!<;Wi38aE/p?a[3!WVfl
-mg&CQs8E3"s7&%Vs8W&%2ZEi^lMpGI!8mbC!hKAas"VUpr;?TnjSs`~>
-i;ZRO!rha,VZ*Y'q#:?oq>1$WrNT!/K`2#Prq?EUpAb0Z'`[_(s8DQf"98B(!!!*$!#,/'s7??i
-s8Dutrr;l;1]$nHnFuq^^&YY=s7?3h')2D+s6]meqtC$iqYgNkr;Z@0r;['9"oeT&s8MlnruTZ)
-'bKO-s76?n&-r7F!:^KhqXjgf&H;A3jo>5T!;uk'r<)rsrp]U\s6Tdbs6]:rrn7A3s8N*!s6K^b
-q>]DNE:j/>mJ$YXmKijjpAaabs6q>Op%n]W!<<#k$iBl"s8VZinGgT1](Q*qs8VWc&`EKbqU6\Z
-!:]RUs4J1drtY2*(ubMpo`"mk$3:)/s8V?`rVultrr38t,anQ0r:p<jrVo=_l&%7PpVo^Equ?<^
-s8N`"qXFOb!!!N=oDK$rm38#!TH`n#$2"8lknEpgq>UHps8;j)nFHSZ!"&]3!!*$!s8VQa#lai)
-nc8[h)%HEAnbr=frW)uur;YkTDZ>J/s8;fp9=k!!rrDuXs*t~>
-i;ZRO!rha,VZ*Y'q#:?oq>1$WrNT!/K`2#Prq?EUpAb0Z'`[_(s8DQf"98B(!!!*$!#,/'s7??i
-s8Dutrr;l;1]$nHnFuq^^&YY=s7?3h')2D+s6]meqtC$iqYgNkr;Z@0r;['9"oeT&s8MlnruTZ)
-'bKO-s76?n&-r7F!:^KhqXjgf&H;A3jo>5T!;uk'r<)rsrp]U\s6Tdbs6]:rrn7A3s8N*!s6K^b
-q>]DNE:j/>mJ$YXmKijjpAaabs6q>Op%n]W!<<#k$iBl"s8VZinGgT1](Q*qs8VWc&`EKbqU6\Z
-!:]RUs4J1drtY2*(ubMpo`"mk$3:)/s8V?`rVultrr38t,anQ0r:p<jrVo=_l&%7PpVo^Equ?<^
-s8N`"qXFOb!!!N=oDK$rm38#!TH`n#$2"8lknEpgq>UHps8;j)nFHSZ!"&]3!!*$!s8VQa#lai)
-nc8[h)%HEAnbr=frW)uur;YkTDZ>J/s8;fp9=k!!rrDuXs*t~>
-i;ZRO!rha,VZ*Y'q#:?oq>1$WrNT!/K`2#Prq?EUpAb0Z'`[_(s8DQf"98B(!!!*$!#,/'s7??i
-s8Dutrr;l;1]$nHnFuq^^&YY=s7?3h')2D+s6]meqtC$iqYgNkr;Z@0r;['9"oeT&s8MlnruTZ)
-'bKO-s76?n&-r7F!:^KhqXjgf&H;A3jo>5T!;uk'r<)rsrp]U\s6Tdbs6]:rrn7A3s8N*!s6K^b
-q>]DNE:j/>mJ$YXmKijjpAaabs6q>Op%n]W!<<#k$iBl"s8VZinGgT1](Q*qs8VWc&`EKbqU6\Z
-!:]RUs4J1drtY2*(ubMpo`"mk$3:)/s8V?`rVultrr38t,anQ0r:p<jrVo=_l&%7PpVo^Equ?<^
-s8N`"qXFOb!!!N=oDK$rm38#!TH`n#$2"8lknEpgq>UHps8;j)nFHSZ!"&]3!!*$!s8VQa#lai)
-nc8[h)%HEAnbr=frW)uur;YkTDZ>J/s8;fp9=k!!rrDuXs*t~>
-hu@6fqYsn^r],f2pC$Qlr;ZZoo`)`;r;ccirt=o&s8)a"r;Qcsrr;fps8)frnG`Idp%JF^rW2rs
-rVuoss8C)%s8;QirV=&7VZ-Vis82WjqZ$<or;Qcls76$qs82lspAb-mpAY'qq>L*lq"k!i,P;$$
-<AiSg48AjU#Q"/es8Vrq"nqurr9=[irrE)os8N*!rr2pHr<)rsrr<#oqZ$Tfs8V<cs$L;rp\4si
-rVuors7Q9[#QO?Iec5@Bk8aL$)uKX8o`#-hs8W"65kb,l2ZN[Sp](!_s0)d1q#::5qt:!h+m]1*
-s8N*[email protected]"BoEY0ls7u]gs8;j*q"4NE-3*K7rVccqs7cQbs8NYkq>^KR
-(]471s8VmuR/[+$p&F[a!;lcrs8?dlr]Y<$rrDrqs8N)urtG)5oD&@c!VcWo#4MQgs8;Wi!;ZWo
-$3B\ss8!'"s8NPrrr3c)&afl"s7,ma`ZXe%I/X*FqC:.ps8:mVJ,~>
-hu@6fqYsn^r],f2pC$Qlr;ZZoo`)`;r;ccirt=o&s8)a"r;Qcsrr;fps8)frnG`Idp%JF^rW2rs
-rVuoss8C)%s8;QirV=&7VZ-Vis82WjqZ$<or;Qcls76$qs82lspAb-mpAY'qq>L*lq"k!i,P;$$
-<AiSg48AjU#Q"/es8Vrq"nqurr9=[irrE)os8N*!rr2pHr<)rsrr<#oqZ$Tfs8V<cs$L;rp\4si
-rVuors7Q9[#QO?Iec5@Bk8aL$)uKX8o`#-hs8W"65kb,l2ZN[Sp](!_s0)d1q#::5qt:!h+m]1*
-s8N*[email protected]"BoEY0ls7u]gs8;j*q"4NE-3*K7rVccqs7cQbs8NYkq>^KR
-(]471s8VmuR/[+$p&F[a!;lcrs8?dlr]Y<$rrDrqs8N)urtG)5oD&@c!VcWo#4MQgs8;Wi!;ZWo
-$3B\ss8!'"s8NPrrr3c)&afl"s7,ma`ZXe%I/X*FqC:.ps8:mVJ,~>
-hu@6fqYsn^r],f2pC$Qlr;ZZoo`)`;r;ccirt=o&s8)a"r;Qcsrr;fps8)frnG`Idp%JF^rW2rs
-rVuoss8C)%s8;QirV=&7VZ-Vis82WjqZ$<or;Qcls76$qs82lspAb-mpAY'qq>L*lq"k!i,P;$$
-<AiSg48AjU#Q"/es8Vrq"nqurr9=[irrE)os8N*!rr2pHr<)rsrr<#oqZ$Tfs8V<cs$L;rp\4si
-rVuors7Q9[#QO?Iec5@Bk8aL$)uKX8o`#-hs8W"65kb,l2ZN[Sp](!_s0)d1q#::5qt:!h+m]1*
-s8N*[email protected]"BoEY0ls7u]gs8;j*q"4NE-3*K7rVccqs7cQbs8NYkq>^KR
-(]471s8VmuR/[+$p&F[a!;lcrs8?dlr]Y<$rrDrqs8N)urtG)5oD&@c!VcWo#4MQgs8;Wi!;ZWo
-$3B\ss8!'"s8NPrrr3c)&afl"s7,ma`ZXe%I/X*FqC:.ps8:mVJ,~>
-iVuRLrrE*!c4-6IhZ*3Js8Dutp](9kiY;Csmf3;>]Dqa,Z7GtT!;lfrrW)uu/@,<Uq>^*ep&Fgf
-s7cQno_SUfjVRgop\Fif$G?05s"aTPs8VZhquH`r!rr2rrrW5s!<<)op/@des7u]frrMros8W#f
-s+gd-o%O@us8P$^rr2pPo`$SRn,NBj5PP0Xs763i!WW,mrr;uupAb$es8;fpqu?]nat*Jo!"o84
-!"')5s7cNm?J6(ms'UW]rVrNjs82irrX.T`#6"2okSn.3s"pbPs8W)unc,-\YlF_&s8N&urr6/k
-q>('@1\+qDq=Xd80DYPGpAap8%I<]as8;co!<3>us7H?kr;Qrjs7cT(rr30!s8Vfirr)it']T,l
-$gIusp]'a_oY_[/q#::<qYpQqo`tNsaoJaJhZ!NVs8;fp!rr;trW)ZlnG`Lgqu8@Pr;S>F$MON!
-rrW5ur;Qius8+jcq#BZi49#EVs8;iqs"]65cg^u/r=Ar$s8DunjSs`~>
-iVuRLrrE*!c4-6IhZ*3Js8Dutp](9kiY;Csmf3;>]Dqa,Z7GtT!;lfrrW)uu/@,<Uq>^*ep&Fgf
-s7cQno_SUfjVRgop\Fif$G?05s"aTPs8VZhquH`r!rr2rrrW5s!<<)op/@des7u]frrMros8W#f
-s+gd-o%O@us8P$^rr2pPo`$SRn,NBj5PP0Xs763i!WW,mrr;uupAb$es8;fpqu?]nat*Jo!"o84
-!"')5s7cNm?J6(ms'UW]rVrNjs82irrX.T`#6"2okSn.3s"pbPs8W)unc,-\YlF_&s8N&urr6/k
-q>('@1\+qDq=Xd80DYPGpAap8%I<]as8;co!<3>us7H?kr;Qrjs7cT(rr30!s8Vfirr)it']T,l
-$gIusp]'a_oY_[/q#::<qYpQqo`tNsaoJaJhZ!NVs8;fp!rr;trW)ZlnG`Lgqu8@Pr;S>F$MON!
-rrW5ur;Qius8+jcq#BZi49#EVs8;iqs"]65cg^u/r=Ar$s8DunjSs`~>
-iVuRLrrE*!c4-6IhZ*3Js8Dutp](9kiY;Csmf3;>]Dqa,Z7GtT!;lfrrW)uu/@,<Uq>^*ep&Fgf
-s7cQno_SUfjVRgop\Fif$G?05s"aTPs8VZhquH`r!rr2rrrW5s!<<)op/@des7u]frrMros8W#f
-s+gd-o%O@us8P$^rr2pPo`$SRn,NBj5PP0Xs763i!WW,mrr;uupAb$es8;fpqu?]nat*Jo!"o84
-!"')5s7cNm?J6(ms'UW]rVrNjs82irrX.T`#6"2okSn.3s"pbPs8W)unc,-\YlF_&s8N&urr6/k
-q>('@1\+qDq=Xd80DYPGpAap8%I<]as8;co!<3>us7H?kr;Qrjs7cT(rr30!s8Vfirr)it']T,l
-$gIusp]'a_oY_[/q#::<qYpQqo`tNsaoJaJhZ!NVs8;fp!rr;trW)ZlnG`Lgqu8@Pr;S>F$MON!
-rrW5ur;Qius8+jcq#BZi49#EVs8;iqs"]65cg^u/r=Ar$s8DunjSs`~>
-i;Yn5#Q4Q#*tUm@jSo\OD2u@1qr,"6UAl%`o)Hi4ci(g)b5_A?s7lKd!rr;B*jb_$L2$\fmJd[r
-s7>s^rrGR1rr3@j)795>s7ZKks7QBjs$-SancAUes7uceruJoTrVrlC\Gt3F;Y'ngs6BUZs3h7G
-s7cAY!87.V(qo_+N`l+s'AEK&-+j3W!:0[\quufnrrhE_)#4(/s*XYB?@_><^a#H>s8VBarrV]a
-s$`OBm`S"Lr:SMI^A#;IcN!_?oD`*\s'Jk-`^Kl^dUl2Hs7bp[qYuBfaT)59s8P"Us7--F(V'.f
-1kYhf'tO@m4,rq2q>1-kn[KQqf_G0uR0!*`s6Td`o`+pks6sYsrr;imqZ$Bis(h*'qK><Wq8,F=
-s8VZip\k-##*Rp8qUgcKlMgq[rVoLj:]L@`!W)irq>p0Zs8NH+s8MWjrVuo4(rGb2L/IsNs763\
-!W)irq>p0es1BSnrn8$orrr2ps8Dip[fA)]s7--5:[e8^rqtgVJ,~>
-i;Yn5#Q4Q#*tUm@jSo\OD2u@1qr,"6UAl%`o)Hi4ci(g)b5_A?s7lKd!rr;B*jb_$L2$\fmJd[r
-s7>s^rrGR1rr3@j)795>s7ZKks7QBjs$-SancAUes7uceruJoTrVrlC\Gt3F;Y'ngs6BUZs3h7G
-s7cAY!87.V(qo_+N`l+s'AEK&-+j3W!:0[\quufnrrhE_)#4(/s*XYB?@_><^a#H>s8VBarrV]a
-s$`OBm`S"Lr:SMI^A#;IcN!_?oD`*\s'Jk-`^Kl^dUl2Hs7bp[qYuBfaT)59s8P"Us7--F(V'.f
-1kYhf'tO@m4,rq2q>1-kn[KQqf_G0uR0!*`s6Td`o`+pks6sYsrr;imqZ$Bis(h*'qK><Wq8,F=
-s8VZip\k-##*Rp8qUgcKlMgq[rVoLj:]L@`!W)irq>p0Zs8NH+s8MWjrVuo4(rGb2L/IsNs763\
-!W)irq>p0es1BSnrn8$orrr2ps8Dip[fA)]s7--5:[e8^rqtgVJ,~>
-i;Yn5#Q4Q#*tUm@jSo\OD2u@1qr,"6UAl%`o)Hi4ci(g)b5_A?s7lKd!rr;B*jb_$L2$\fmJd[r
-s7>s^rrGR1rr3@j)795>s7ZKks7QBjs$-SancAUes7uceruJoTrVrlC\Gt3F;Y'ngs6BUZs3h7G
-s7cAY!87.V(qo_+N`l+s'AEK&-+j3W!:0[\quufnrrhE_)#4(/s*XYB?@_><^a#H>s8VBarrV]a
-s$`OBm`S"Lr:SMI^A#;IcN!_?oD`*\s'Jk-`^Kl^dUl2Hs7bp[qYuBfaT)59s8P"Us7--F(V'.f
-1kYhf'tO@m4,rq2q>1-kn[KQqf_G0uR0!*`s6Td`o`+pks6sYsrr;imqZ$Bis(h*'qK><Wq8,F=
-s8VZip\k-##*Rp8qUgcKlMgq[rVoLj:]L@`!W)irq>p0Zs8NH+s8MWjrVuo4(rGb2L/IsNs763\
-!W)irq>p0es1BSnrn8$orrr2ps8Dip[fA)]s7--5:[e8^rqtgVJ,~>
-iW!firrVHbrNuX@s8VEcs8SNf$kESHV#UI_"[rC]b=3"4d-L0##PA&rr;urpm_([r$!!kqs8VZj
-gAh3Os8;Wgs0P_rs7-Nt!!j#6!<<&up&F:OrrDTh!<<#rrrE)mQkqs[s6d<?)&#W\rt#&-CAS]8
-s6fp`s&1*3s/n^'!(aa!p!bT#3kt^jp^@,js6p$gqY:*jo`,F$!<E06n[2F:"Y$2Gs8;oslMh%f
-p]%j$%gXtTs7?9j_C>O3`;]f5q>^2]./_t&$nf,N!#pR`s7H$br;Zd,qu@E4p+?=BrVuo=/-7/T
-s7c!"0*E>KrVlosr;Q^iY&59h!&@X2rW)Wkq#(*iq#L9k!!a#7!<<)n,l.B<r;HZqqM61+15c,(
-p&G'drUo^;9cO3J/7-Etnc8^gs.0D#rVults8Dor!<3!&ncSOFs8N3#s"O2E;ZmM4Yl=t$qssae
-s8Dor!<;ogs0t>r1Xc'u!<<)pm/QeZ"JYYbs6sBqs8VuTs*t~>
-iW!firrVHbrNuX@s8VEcs8SNf$kESHV#UI_"[rC]b=3"4d-L0##PA&rr;urpm_([r$!!kqs8VZj
-gAh3Os8;Wgs0P_rs7-Nt!!j#6!<<&up&F:OrrDTh!<<#rrrE)mQkqs[s6d<?)&#W\rt#&-CAS]8
-s6fp`s&1*3s/n^'!(aa!p!bT#3kt^jp^@,js6p$gqY:*jo`,F$!<E06n[2F:"Y$2Gs8;oslMh%f
-p]%j$%gXtTs7?9j_C>O3`;]f5q>^2]./_t&$nf,N!#pR`s7H$br;Zd,qu@E4p+?=BrVuo=/-7/T
-s7c!"0*E>KrVlosr;Q^iY&59h!&@X2rW)Wkq#(*iq#L9k!!a#7!<<)n,l.B<r;HZqqM61+15c,(
-p&G'drUo^;9cO3J/7-Etnc8^gs.0D#rVults8Dor!<3!&ncSOFs8N3#s"O2E;ZmM4Yl=t$qssae
-s8Dor!<;ogs0t>r1Xc'u!<<)pm/QeZ"JYYbs6sBqs8VuTs*t~>
-iW!firrVHbrNuX@s8VEcs8SNf$kESHV#UI_"[rC]b=3"4d-L0##PA&rr;urpm_([r$!!kqs8VZj
-gAh3Os8;Wgs0P_rs7-Nt!!j#6!<<&up&F:OrrDTh!<<#rrrE)mQkqs[s6d<?)&#W\rt#&-CAS]8
-s6fp`s&1*3s/n^'!(aa!p!bT#3kt^jp^@,js6p$gqY:*jo`,F$!<E06n[2F:"Y$2Gs8;oslMh%f
-p]%j$%gXtTs7?9j_C>O3`;]f5q>^2]./_t&$nf,N!#pR`s7H$br;Zd,qu@E4p+?=BrVuo=/-7/T
-s7c!"0*E>KrVlosr;Q^iY&59h!&@X2rW)Wkq#(*iq#L9k!!a#7!<<)n,l.B<r;HZqqM61+15c,(
-p&G'drUo^;9cO3J/7-Etnc8^gs.0D#rVults8Dor!<3!&ncSOFs8N3#s"O2E;ZmM4Yl=t$qssae
-s8Dor!<;ogs0t>r1Xc'u!<<)pm/QeZ"JYYbs6sBqs8VuTs*t~>
-i;X>_s8V`gp]('^s8Vrqp&Fdcrs\o,s82-^s7cQjs7Z9frs.uks6ojbrq69j!qH9]rr3T*s8Vur
-s7lNlqu?<gqGZ/Kp&=tMrVuogs8DriqZ$Tos8VQes8N#trpTmdn,<1Rs8N&ls6fpeo`"^erqufk
-s8;oqs7H'bq>^<err3f1s8Voms7u]ks8)3\s8Duos8)chpAb$hru1D,oDARbqt^9lo_/=Qq#C9j
-s7H?kp\+UUr;Q^#q=OUbmJlhXrsnkos("jhs7,^\m.LDYrr)j"q>9d`qtg=(med%ali6\Yl1Y/W
-rVuWlqYpL$o)Jafrr;Wjqu$Hn!r;Wjqu7Ass7H?es75sOs8VN&)?9U6rVu`jrr3;rm/QPJs7cQn
-r;Q^&mf3:dnFu_Js8;iq+Su-8q!\4^rr2rsn,N:bs8)cqnc/X^pAb0Ps8VKds6Tab-h%'7rr2rs
-n,*.as8N&uq#C*ds7?9jqu;`uiW&oWiEbsQs7QEjr8dm.~>
-i;X>_s8V`gp]('^s8Vrqp&Fdcrs\o,s82-^s7cQjs7Z9frs.uks6ojbrq69j!qH9]rr3T*s8Vur
-s7lNlqu?<gqGZ/Kp&=tMrVuogs8DriqZ$Tos8VQes8N#trpTmdn,<1Rs8N&ls6fpeo`"^erqufk
-s8;oqs7H'bq>^<err3f1s8Voms7u]ks8)3\s8Duos8)chpAb$hru1D,oDARbqt^9lo_/=Qq#C9j
-s7H?kp\+UUr;Q^#q=OUbmJlhXrsnkos("jhs7,^\m.LDYrr)j"q>9d`qtg=(med%ali6\Yl1Y/W
-rVuWlqYpL$o)Jafrr;Wjqu$Hn!r;Wjqu7Ass7H?es75sOs8VN&)?9U6rVu`jrr3;rm/QPJs7cQn
-r;Q^&mf3:dnFu_Js8;iq+Su-8q!\4^rr2rsn,N:bs8)cqnc/X^pAb0Ps8VKds6Tab-h%'7rr2rs
-n,*.as8N&uq#C*ds7?9jqu;`uiW&oWiEbsQs7QEjr8dm.~>
-i;X>_s8V`gp]('^s8Vrqp&Fdcrs\o,s82-^s7cQjs7Z9frs.uks6ojbrq69j!qH9]rr3T*s8Vur
-s7lNlqu?<gqGZ/Kp&=tMrVuogs8DriqZ$Tos8VQes8N#trpTmdn,<1Rs8N&ls6fpeo`"^erqufk
-s8;oqs7H'bq>^<err3f1s8Voms7u]ks8)3\s8Duos8)chpAb$hru1D,oDARbqt^9lo_/=Qq#C9j
-s7H?kp\+UUr;Q^#q=OUbmJlhXrsnkos("jhs7,^\m.LDYrr)j"q>9d`qtg=(med%ali6\Yl1Y/W
-rVuWlqYpL$o)Jafrr;Wjqu$Hn!r;Wjqu7Ass7H?es75sOs8VN&)?9U6rVu`jrr3;rm/QPJs7cQn
-r;Q^&mf3:dnFu_Js8;iq+Su-8q!\4^rr2rsn,N:bs8)cqnc/X^pAb0Ps8VKds6Tab-h%'7rr2rs
-n,*.as8N&uq#C*ds7?9jqu;`uiW&oWiEbsQs7QEjr8dm.~>
-i;WrSs7H?^rr3]+s8Vlns8DutqssdTr:p<dmJlnWrr4#2s8V`hpAb'jr9F=^mJm.bs6BXaoDeLZ
-qu?WkoDS[shbO4Hs8VlomJm4WrVm5is7cNfs8VQfs6ose!;QNm$iL&&s69R`p](-jmJ[&$kPX`I
-o)JRds7lWon+QeMs8)9co)JF\r;R-'q#:6is8W#ms6Tab"RuHjs82fq%I=&js8Voprr<#ks8W&s
-rsSi+s6p!bs7lWfs7cKl)u9)A+Fhr3s68@=90rUUq>]XXrU0^cqXjgfnGW@eo)AY)q#CBdr;ZQl
-nGiOUs6fdas8)Hho)8Lcq$[6%rr<#mr:Bs`qtL$g&H2Y(s$X6kq#C9jo`+Uas8;lr$i9o'nc/Ld
-s6BXYoD\b(r;ZTms7lNYs7$'`s8VQfs6p!frVc`umI^GSrr3?)nc/@Qs8VQfs6]gc"7Q9in,<8+
-p\Og]s763hs7Z0dnFkWSI/j$Bnkn9Do`"mjp>c1'~>
-i;WrSs7H?^rr3]+s8Vlns8DutqssdTr:p<dmJlnWrr4#2s8V`hpAb'jr9F=^mJm.bs6BXaoDeLZ
-qu?WkoDS[shbO4Hs8VlomJm4WrVm5is7cNfs8VQfs6ose!;QNm$iL&&s69R`p](-jmJ[&$kPX`I
-o)JRds7lWon+QeMs8)9co)JF\r;R-'q#:6is8W#ms6Tab"RuHjs82fq%I=&js8Voprr<#ks8W&s
-rsSi+s6p!bs7lWfs7cKl)u9)A+Fhr3s68@=90rUUq>]XXrU0^cqXjgfnGW@eo)AY)q#CBdr;ZQl
-nGiOUs6fdas8)Hho)8Lcq$[6%rr<#mr:Bs`qtL$g&H2Y(s$X6kq#C9jo`+Uas8;lr$i9o'nc/Ld
-s6BXYoD\b(r;ZTms7lNYs7$'`s8VQfs6p!frVc`umI^GSrr3?)nc/@Qs8VQfs6]gc"7Q9in,<8+
-p\Og]s763hs7Z0dnFkWSI/j$Bnkn9Do`"mjp>c1'~>
-i;WrSs7H?^rr3]+s8Vlns8DutqssdTr:p<dmJlnWrr4#2s8V`hpAb'jr9F=^mJm.bs6BXaoDeLZ
-qu?WkoDS[shbO4Hs8VlomJm4WrVm5is7cNfs8VQfs6ose!;QNm$iL&&s69R`p](-jmJ[&$kPX`I
-o)JRds7lWon+QeMs8)9co)JF\r;R-'q#:6is8W#ms6Tab"RuHjs82fq%I=&js8Voprr<#ks8W&s
-rsSi+s6p!bs7lWfs7cKl)u9)A+Fhr3s68@=90rUUq>]XXrU0^cqXjgfnGW@eo)AY)q#CBdr;ZQl
-nGiOUs6fdas8)Hho)8Lcq$[6%rr<#mr:Bs`qtL$g&H2Y(s$X6kq#C9jo`+Uas8;lr$i9o'nc/Ld
-s6BXYoD\b(r;ZTms7lNYs7$'`s8VQfs6p!frVc`umI^GSrr3?)nc/@Qs8VQfs6]gc"7Q9in,<8+
-p\Og]s763hs7Z0dnFkWSI/j$Bnkn9Do`"mjp>c1'~>
-hu=5brVuosp%J+TrVu`ds8V?\s8Vt's8Muss7QB^s7cQiqu?]ds8VQfqtg3^s8VWas7cQjoD/Fd
-rqZT]s82ior:^0\s8N&^mJlt]mJm4[s7u]ds8N#toDeXdq>'^`s7u]nqu>mQs8)ZnqXOUUs7u]f
-rr3,hq>^EjrVmT+rr;ilqu?]ls8Vfmq>^3hrqcZkrr5afo`+:Xs6Ta_nc/XYs7$'gn,*.Ts8V]j
-irArVq"+O\s7uTipAb0[rVHKmmf1RE0,FTt+@_mPq#B[Vs7Q-dn+m"`o`+dfoDS%Us82fn#lj]"
-o(rCbm/?n_&,Gu"q>L?ir;ZEhs82fgrVlg,li6taq"OgHs8Vlgs7--grW)Kert4r#s8Vcls7lEi
-meZtRs7QElrquuas7cQer;RN-s8V]jrr2rhs8)ccs7QEfs8;o`rr2p8rVuoos7lWds8V]jrr2rh
-s8W&ps7H?bs7lQm&b5]]%TrQ*pAb0ks8Vrks8W)Ys*t~>
-hu=5brVuosp%J+TrVu`ds8V?\s8Vt's8Muss7QB^s7cQiqu?]ds8VQfqtg3^s8VWas7cQjoD/Fd
-rqZT]s82ior:^0\s8N&^mJlt]mJm4[s7u]ds8N#toDeXdq>'^`s7u]nqu>mQs8)ZnqXOUUs7u]f
-rr3,hq>^EjrVmT+rr;ilqu?]ls8Vfmq>^3hrqcZkrr5afo`+:Xs6Ta_nc/XYs7$'gn,*.Ts8V]j
-irArVq"+O\s7uTipAb0[rVHKmmf1RE0,FTt+@_mPq#B[Vs7Q-dn+m"`o`+dfoDS%Us82fn#lj]"
-o(rCbm/?n_&,Gu"q>L?ir;ZEhs82fgrVlg,li6taq"OgHs8Vlgs7--grW)Kert4r#s8Vcls7lEi
-meZtRs7QElrquuas7cQer;RN-s8V]jrr2rhs8)ccs7QEfs8;o`rr2p8rVuoos7lWds8V]jrr2rh
-s8W&ps7H?bs7lQm&b5]]%TrQ*pAb0ks8Vrks8W)Ys*t~>
-hu=5brVuosp%J+TrVu`ds8V?\s8Vt's8Muss7QB^s7cQiqu?]ds8VQfqtg3^s8VWas7cQjoD/Fd
-rqZT]s82ior:^0\s8N&^mJlt]mJm4[s7u]ds8N#toDeXdq>'^`s7u]nqu>mQs8)ZnqXOUUs7u]f
-rr3,hq>^EjrVmT+rr;ilqu?]ls8Vfmq>^3hrqcZkrr5afo`+:Xs6Ta_nc/XYs7$'gn,*.Ts8V]j
-irArVq"+O\s7uTipAb0[rVHKmmf1RE0,FTt+@_mPq#B[Vs7Q-dn+m"`o`+dfoDS%Us82fn#lj]"
-o(rCbm/?n_&,Gu"q>L?ir;ZEhs82fgrVlg,li6taq"OgHs8Vlgs7--grW)Kert4r#s8Vcls7lEi
-meZtRs7QElrquuas7cQer;RN-s8V]jrr2rhs8)ccs7QEfs8;o`rr2p8rVuoos7lWds8V]jrr2rh
-s8W&ps7H?bs7lQm&b5]]%TrQ*pAb0ks8Vrks8W)Ys*t~>
-iVs,Srr<#ms8Mus..mQ;qu?Wpp]'m]s8Dutqt^9lkl:JYq"FUbs82Efs7uWfs82inp&4mi!Vl?e
-rt+u$s8VZir;ZNks8)<dp&FFYs$cb`s7?9jhZ*KJs82]nq"t'js7u]ns8;Qfs7QElkl:VRq#Bsc
-p&F[\s6TIZq>^-fnc/OSs8Dusr;ZEhq>^Kbs7cQmrVlrnqtU*h!qPsWrr4&<o`+OSs8W)up](9_
-s7$'Zs7Q0es7cBiq>^6ip%\Od!9jF^#Pe?!q#CBnq#::&p&G'cs5j:\kPkMAs5s=\(]+1&s7u]b
-s8)cms8)cps6BXap%SLdq#::@o)J"Lo`+shs7u3bo`"Xcs60LLs8VEbs8;`nq==Rcl2UJWqu$K^
-rr3)us8W)trr`#qr:g'f+o1]qs6'FZq#C6gs7lKcs8UsUs7cQfr;Z0as7Pp^s82cort"Ppq#C6g
-s7lKkq>^0grVc`q(%M>!s7QElq>^9jrqufcs8W)rs8VuWs*t~>
-iVs,Srr<#ms8Mus..mQ;qu?Wpp]'m]s8Dutqt^9lkl:JYq"FUbs82Efs7uWfs82inp&4mi!Vl?e
-rt+u$s8VZir;ZNks8)<dp&FFYs$cb`s7?9jhZ*KJs82]nq"t'js7u]ns8;Qfs7QElkl:VRq#Bsc
-p&F[\s6TIZq>^-fnc/OSs8Dusr;ZEhq>^Kbs7cQmrVlrnqtU*h!qPsWrr4&<o`+OSs8W)up](9_
-s7$'Zs7Q0es7cBiq>^6ip%\Od!9jF^#Pe?!q#CBnq#::&p&G'cs5j:\kPkMAs5s=\(]+1&s7u]b
-s8)cms8)cps6BXap%SLdq#::@o)J"Lo`+shs7u3bo`"Xcs60LLs8VEbs8;`nq==Rcl2UJWqu$K^
-rr3)us8W)trr`#qr:g'f+o1]qs6'FZq#C6gs7lKcs8UsUs7cQfr;Z0as7Pp^s82cort"Ppq#C6g
-s7lKkq>^0grVc`q(%M>!s7QElq>^9jrqufcs8W)rs8VuWs*t~>
-iVs,Srr<#ms8Mus..mQ;qu?Wpp]'m]s8Dutqt^9lkl:JYq"FUbs82Efs7uWfs82inp&4mi!Vl?e
-rt+u$s8VZir;ZNks8)<dp&FFYs$cb`s7?9jhZ*KJs82]nq"t'js7u]ns8;Qfs7QElkl:VRq#Bsc
-p&F[\s6TIZq>^-fnc/OSs8Dusr;ZEhq>^Kbs7cQmrVlrnqtU*h!qPsWrr4&<o`+OSs8W)up](9_
-s7$'Zs7Q0es7cBiq>^6ip%\Od!9jF^#Pe?!q#CBnq#::&p&G'cs5j:\kPkMAs5s=\(]+1&s7u]b
-s8)cms8)cps6BXap%SLdq#::@o)J"Lo`+shs7u3bo`"Xcs60LLs8VEbs8;`nq==Rcl2UJWqu$K^
-rr3)us8W)trr`#qr:g'f+o1]qs6'FZq#C6gs7lKcs8UsUs7cQfr;Z0as7Pp^s82cort"Ppq#C6g
-s7lKkq>^0grVc`q(%M>!s7QElq>^9jrqufcs8W)rs8VuWs*t~>
-i;X\as8Duqs8DrsoDe1Ws7uTms7H?kq#C-hq#14/pAagcs7cQkrr;orpAb0is8Vrps8Vijrr3#q
-rVlg%q>^Hhr;ZHgrVmN%s8W)ss7?9is7H?bs8Vrqs8)`p$hsYss8)ZnrVuonp&4mmq#C!arVllp
-rr3>ss8)c`s8M`lqYU3j!q?6frVpj2s8;osoDeOar;Z`hs8N&uoDJLcq>U<lqu-Ejrr;cks82Tk
-p&Fjdrr<#os8Duas8V]gp]'d\p&Fdas7?9eq#C-es8)cqq#CBhs8Vfmp](3js8)<dq"asirr;fe
-s8N&umJm4_s8Vinqu?Whqu9"Ns7ZKmrr<#ss8Vigs82]nr;Zfrqu?]mqu?9fqu?]hs82imrVuHg
-s8)Egnc/7]p[\@`s763ir;$Bcs8Vrqs8)`p&,Q>+p](9ks763irqHHmqYgEon,<7oqZ$Tls8W)u
-p&>!fqYpm$s8)Whs7QEjr;Q^"p](9kr;PdWJ,~>
-i;X\as8Duqs8DrsoDe1Ws7uTms7H?kq#C-hq#14/pAagcs7cQkrr;orpAb0is8Vrps8Vijrr3#q
-rVlg%q>^Hhr;ZHgrVmN%s8W)ss7?9is7H?bs8Vrqs8)`p$hsYss8)ZnrVuonp&4mmq#C!arVllp
-rr3>ss8)c`s8M`lqYU3j!q?6frVpj2s8;osoDeOar;Z`hs8N&uoDJLcq>U<lqu-Ejrr;cks82Tk
-p&Fjdrr<#os8Duas8V]gp]'d\p&Fdas7?9eq#C-es8)cqq#CBhs8Vfmp](3js8)<dq"asirr;fe
-s8N&umJm4_s8Vinqu?Whqu9"Ns7ZKmrr<#ss8Vigs82]nr;Zfrqu?]mqu?9fqu?]hs82imrVuHg
-s8)Egnc/7]p[\@`s763ir;$Bcs8Vrqs8)`p&,Q>+p](9ks763irqHHmqYgEon,<7oqZ$Tls8W)u
-p&>!fqYpm$s8)Whs7QEjr;Q^"p](9kr;PdWJ,~>
-i;X\as8Duqs8DrsoDe1Ws7uTms7H?kq#C-hq#14/pAagcs7cQkrr;orpAb0is8Vrps8Vijrr3#q
-rVlg%q>^Hhr;ZHgrVmN%s8W)ss7?9is7H?bs8Vrqs8)`p$hsYss8)ZnrVuonp&4mmq#C!arVllp
-rr3>ss8)c`s8M`lqYU3j!q?6frVpj2s8;osoDeOar;Z`hs8N&uoDJLcq>U<lqu-Ejrr;cks82Tk
-p&Fjdrr<#os8Duas8V]gp]'d\p&Fdas7?9eq#C-es8)cqq#CBhs8Vfmp](3js8)<dq"asirr;fe
-s8N&umJm4_s8Vinqu?Whqu9"Ns7ZKmrr<#ss8Vigs82]nr;Zfrqu?]mqu?9fqu?]hs82imrVuHg
-s8)Egnc/7]p[\@`s763ir;$Bcs8Vrqs8)`p&,Q>+p](9ks763irqHHmqYgEon,<7oqZ$Tls8W)u
-p&>!fqYpm$s8)Whs7QEjr;Q^"p](9kr;PdWJ,~>
-JcF^/#QFZ$rVuoqp\b$qp\Y!grVuWkrs&?"s82Wlq#:9pqY:$grs\c%q#:<cs8W#ss8)]nrrMrl
-qu6lrrr;Qhs8DZk'DVUss8VZis7cQnirB&Ls8;corVca!pAb!hp@nReqYU:(q>C9mkl:\Os8VWf
-s7$'grdk+1s*t~>
-JcF^/#QFZ$rVuoqp\b$qp\Y!grVuWkrs&?"s82Wlq#:9pqY:$grs\c%q#:<cs8W#ss8)]nrrMrl
-qu6lrrr;Qhs8DZk'DVUss8VZis7cQnirB&Ls8;corVca!pAb!hp@nReqYU:(q>C9mkl:\Os8VWf
-s7$'grdk+1s*t~>
-JcF^/#QFZ$rVuoqp\b$qp\Y!grVuWkrs&?"s82Wlq#:9pqY:$grs\c%q#:<cs8W#ss8)]nrrMrl
-qu6lrrr;Qhs8DZk'DVUss8VZis7cQnirB&Ls8;corVca!pAb!hp@nReqYU:(q>C9mkl:\Os8VWf
-s7$'grdk+1s*t~>
-JcF[.$1Iomo(MnZqYU6jruM"/s8Vrqs7uEhrVH6fr;HWoqtg?mr9X%Tqu?9drr3#up&=slq"ajf
-!r)9Zrr32as8VEbs7Z3e%eB&fo`+FUrq$0in+-MQrr3B"s8N#ns8Vccs8MW`rrVrcpAY'po`+s`
-m/6kcq>:3bJcFd1J,~>
-JcF[.$1Iomo(MnZqYU6jruM"/s8Vrqs7uEhrVH6fr;HWoqtg?mr9X%Tqu?9drr3#up&=slq"ajf
-!r)9Zrr32as8VEbs7Z3e%eB&fo`+FUrq$0in+-MQrr3B"s8N#ns8Vccs8MW`rrVrcpAY'po`+s`
-m/6kcq>:3bJcFd1J,~>
-JcF[.$1Iomo(MnZqYU6jruM"/s8Vrqs7uEhrVH6fr;HWoqtg?mr9X%Tqu?9drr3#up&=slq"ajf
-!r)9Zrr32as8VEbs7Z3e%eB&fo`+FUrq$0in+-MQrr3B"s8N#ns8Vccs8MW`rrVrcpAY'po`+s`
-m/6kcq>:3bJcFd1J,~>
-JcF^/%/p4rnc/Oequ?]pq=ssh'Dhb*s8;oqq>^?ls8)Wms7u]pq>UC,rVlisqu?]fs8Vufq>^Ko
-q>:'es8Vs"s7?6irUfmb!WDckrrMchr;RE/s7QElo)JUep](9bs8Vris8;]ms82`o&,Z2&pA=mi
-p&+L_s7QEjoR[&&s*t~>
-JcF^/%/p4rnc/Oequ?]pq=ssh'Dhb*s8;oqq>^?ls8)Wms7u]pq>UC,rVlisqu?]fs8Vufq>^Ko
-q>:'es8Vs"s7?6irUfmb!WDckrrMchr;RE/s7QElo)JUep](9bs8Vris8;]ms82`o&,Z2&pA=mi
-p&+L_s7QEjoR[&&s*t~>
-JcF^/%/p4rnc/Oequ?]pq=ssh'Dhb*s8;oqq>^?ls8)Wms7u]pq>UC,rVlisqu?]fs8Vufq>^Ko
-q>:'es8Vs"s7?6irUfmb!WDckrrMchr;RE/s7QElo)JUep](9bs8Vris8;]ms82`o&,Z2&pA=mi
-p&+L_s7QEjoR[&&s*t~>
-JcF^/(%VD)qZ$Qpp\Xges8Dfmp[eFbli6e[rrV]hn,E>7o_8Ccqu-Hgs8VQfs7uKes7Q3bs8Dfc
-s8W)oqu$<gs7H9cs7cQmq#:Eps7-*g%.sT!oDe4Wrr<#ps7-*g"7H0fr;Q]trr;rlrseu'qu$<g
-s8W)us6K^^rr3/us8W#srdk+1s*t~>
-JcF^/(%VD)qZ$Qpp\Xges8Dfmp[eFbli6e[rrV]hn,E>7o_8Ccqu-Hgs8VQfs7uKes7Q3bs8Dfc
-s8W)oqu$<gs7H9cs7cQmq#:Eps7-*g%.sT!oDe4Wrr<#ps7-*g"7H0fr;Q]trr;rlrseu'qu$<g
-s8W)us6K^^rr3/us8W#srdk+1s*t~>
-JcF^/(%VD)qZ$Qpp\Xges8Dfmp[eFbli6e[rrV]hn,E>7o_8Ccqu-Hgs8VQfs7uKes7Q3bs8Dfc
-s8W)oqu$<gs7H9cs7cQmq#:Eps7-*g%.sT!oDe4Wrr<#ps7-*g"7H0fr;Q]trr;rlrseu'qu$<g
-s8W)us6K^^rr3/us8W#srdk+1s*t~>
-JcF[.#k@rpk5Y>Qs8N#t(&7\+s7--hp](9boDej^s8;`ns7c-art>>2s7$'_q>^Enqu?]kr;ZZo
-rr)itp\Fgg"nqTfmJleQrrq`gpAaa]rr2uirVlokrVlfsp&+gnmeHh^l2LMY!<2rs%eof!s82Hg
-s7lKgl2USXrr2ukJcFg2J,~>
-JcF[.#k@rpk5Y>Qs8N#t(&7\+s7--hp](9boDej^s8;`ns7c-art>>2s7$'_q>^Enqu?]kr;ZZo
-rr)itp\Fgg"nqTfmJleQrrq`gpAaa]rr2uirVlokrVlfsp&+gnmeHh^l2LMY!<2rs%eof!s82Hg
-s7lKgl2USXrr2ukJcFg2J,~>
-JcF[.#k@rpk5Y>Qs8N#t(&7\+s7--hp](9boDej^s8;`ns7c-art>>2s7$'_q>^Enqu?]kr;ZZo
-rr)itp\Fgg"nqTfmJleQrrq`gpAaa]rr2uirVlokrVlfsp&+gnmeHh^l2LMY!<2rs%eof!s82Hg
-s7lKgl2USXrr2ukJcFg2J,~>
-JcF^/48/^Js8Vuls82]er;Zfrs8V?RrrDBbs8;ojs8;ons8W)urU]j`o)&ISs8McXFo^7rs8Duq
-p\k'fqu-Ntp]1?or:U(%r;HZ\&c_Fts8Vhn*]"3&mIpMY!r)`crr2ujp](9ls82cp%eTens4'Rf
-"`;Naq#C?dJcFd1J,~>
-JcF^/48/^Js8Vuls82]er;Zfrs8V?RrrDBbs8;ojs8;ons8W)urU]j`o)&ISs8McXFo^7rs8Duq
-p\k'fqu-Ntp]1?or:U(%r;HZ\&c_Fts8Vhn*]"3&mIpMY!r)`crr2ujp](9ls82cp%eTens4'Rf
-"`;Naq#C?dJcFd1J,~>
-JcF^/48/^Js8Vuls82]er;Zfrs8V?RrrDBbs8;ojs8;ons8W)urU]j`o)&ISs8McXFo^7rs8Duq
-p\k'fqu-Ntp]1?or:U(%r;HZ\&c_Fts8Vhn*]"3&mIpMY!r)`crr2ujp](9ls82cp%eTens4'Rf
-"`;Naq#C?dJcFd1J,~>
-JcF^/-2dN;li-GSr:g6kn,E@Zs82`opCRAopAFsds8VTgmelPRs7uZnrsSi+irB#\7h5S!oCi1`
-!;ZWo"oSE!o`"pjrrW&mq=t!irVum9!;HEbqu?[9l2C\_p](9clMpn^s8)?Zs8VihrrDlorseo+
-rUg-hs8P0/lg\Larr3#moR[&&s*t~>
-JcF^/-2dN;li-GSr:g6kn,E@Zs82`opCRAopAFsds8VTgmelPRs7uZnrsSi+irB#\7h5S!oCi1`
-!;ZWo"oSE!o`"pjrrW&mq=t!irVum9!;HEbqu?[9l2C\_p](9clMpn^s8)?Zs8VihrrDlorseo+
-rUg-hs8P0/lg\Larr3#moR[&&s*t~>
-JcF^/-2dN;li-GSr:g6kn,E@Zs82`opCRAopAFsds8VTgmelPRs7uZnrsSi+irB#\7h5S!oCi1`
-!;ZWo"oSE!o`"pjrrW&mq=t!irVum9!;HEbqu?[9l2C\_p](9clMpn^s8)?Zs8VihrrDlorseo+
-rUg-hs8P0/lg\Larr3#moR[&&s*t~>
-JcF[.70Shd+M3OG(8Um.riJ-[/>)nA!WWc1nO!I@pA+RkQkUF6p](9Z5mogQpAb*e!<;fns7H0f
-s7`BG!%k)JrrAE$%^k^!rrrAh1CKlXrr<#s'EJ16!<N)tm7IOWlhUPL0]i8m.eNN9"o"lL!%k)G
-rsJr/p](.$nGiOds7_*EjSs`~>
-JcF[.70Shd+M3OG(8Um.riJ-[/>)nA!WWc1nO!I@pA+RkQkUF6p](9Z5mogQpAb*e!<;fns7H0f
-s7`BG!%k)JrrAE$%^k^!rrrAh1CKlXrr<#s'EJ16!<N)tm7IOWlhUPL0]i8m.eNN9"o"lL!%k)G
-rsJr/p](.$nGiOds7_*EjSs`~>
-JcF[.70Shd+M3OG(8Um.riJ-[/>)nA!WWc1nO!I@pA+RkQkUF6p](9Z5mogQpAb*e!<;fns7H0f
-s7`BG!%k)JrrAE$%^k^!rrrAh1CKlXrr<#s'EJ16!<N)tm7IOWlhUPL0]i8m.eNN9"o"lL!%k)G
-rsJr/p](.$nGiOds7_*EjSs`~>
-JcF[..fjAaQ4IQjSJV>+r#*$@ZY&_,!VucoBlsB/Du]M9EU];5rVu\0POZ/#rVlg4!<;rro_ngb
-oD]r@p:4Q/r;_W1Te#U2rrD]js$?PYqdcSnI/s6Gr;ZNk!<;s#qYsojrO?nJs80N!rD:6%r;Z]p
-s8N&poD]r@p:4Q/s8Vur;N:_9CgR/<s8W)kJcFg2J,~>
-JcF[..fjAaQ4IQjSJV>+r#*$@ZY&_,!VucoBlsB/Du]M9EU];5rVu\0POZ/#rVlg4!<;rro_ngb
-oD]r@p:4Q/r;_W1Te#U2rrD]js$?PYqdcSnI/s6Gr;ZNk!<;s#qYsojrO?nJs80N!rD:6%r;Z]p
-s8N&poD]r@p:4Q/s8Vur;N:_9CgR/<s8W)kJcFg2J,~>
-JcF[..fjAaQ4IQjSJV>+r#*$@ZY&_,!VucoBlsB/Du]M9EU];5rVu\0POZ/#rVlg4!<;rro_ngb
-oD]r@p:4Q/r;_W1Te#U2rrD]js$?PYqdcSnI/s6Gr;ZNk!<;s#qYsojrO?nJs80N!rD:6%r;Z]p
-s8N&poD]r@p:4Q/s8Vur;N:_9CgR/<s8W)kJcFg2J,~>
-JcF[.0`_7Pm/["_rrDuqq\P"Ks7lWf!<;cm)#4'e"98B$s7?6js7cNrp]'8"r:Kpe"T.lks&]-q
-s8*=Xl14lQrrMurncSplqYpNop%/.^q\o#)lkB<ks8)cj!qH9js6ps$o(!^or;-Fi=C^=is7cQa
-pAb-ls8*=Xl14lQoDe^fi#Mdt+m](%rqV-Fir=N~>
-JcF[.0`_7Pm/["_rrDuqq\P"Ks7lWf!<;cm)#4'e"98B$s7?6js7cNrp]'8"r:Kpe"T.lks&]-q
-s8*=Xl14lQrrMurncSplqYpNop%/.^q\o#)lkB<ks8)cj!qH9js6ps$o(!^or;-Fi=C^=is7cQa
-pAb-ls8*=Xl14lQoDe^fi#Mdt+m](%rqV-Fir=N~>
-JcF[.0`_7Pm/["_rrDuqq\P"Ks7lWf!<;cm)#4'e"98B$s7?6js7cNrp]'8"r:Kpe"T.lks&]-q
-s8*=Xl14lQrrMurncSplqYpNop%/.^q\o#)lkB<ks8)cj!qH9js6ps$o(!^or;-Fi=C^=is7cQa
-pAb-ls8*=Xl14lQoDe^fi#Mdt+m](%rqV-Fir=N~>
-JcF^/$2+o.s8Nhls82lrrt=44'K<T$rrDfnoF9p^rsJf&%0$89!<<)b"onW-!<<)nqZQou1AUY?
-s8VfTC*kdZr:L'ipAY-mr:Bsg!"9S/p\t9hmJd1ds8)WdrrE*!#QOf-!"&f.rr4;CciM>is8;ob
-rsf#/rVuTRC*kdZr;ZfdmV%I@pP)lEs8Vopqgne.s*t~>
-JcF^/$2+o.s8Nhls82lrrt=44'K<T$rrDfnoF9p^rsJf&%0$89!<<)b"onW-!<<)nqZQou1AUY?
-s8VfTC*kdZr:L'ipAY-mr:Bsg!"9S/p\t9hmJd1ds8)WdrrE*!#QOf-!"&f.rr4;CciM>is8;ob
-rsf#/rVuTRC*kdZr;ZfdmV%I@pP)lEs8Vopqgne.s*t~>
-JcF^/$2+o.s8Nhls82lrrt=44'K<T$rrDfnoF9p^rsJf&%0$89!<<)b"onW-!<<)nqZQou1AUY?
-s8VfTC*kdZr:L'ipAY-mr:Bsg!"9S/p\t9hmJd1ds8)WdrrE*!#QOf-!"&f.rr4;CciM>is8;ob
-rsf#/rVuTRC*kdZr;ZfdmV%I@pP)lEs8Vopqgne.s*t~>
-JcF[.9*#"cp':Wir!EB$me$MYYS6d2!<;lp)<h+^+Sl$;s7l?jq<e2+q#C$ds6KRX!rW)or;Zfr
-qZ$<ipUCP0rrDofrrDrqs8;Hbqu6U#pFPJ,li@(arr4eM!;uisq=G*qq"k$_m/-fe<Fc'rrq?B`
-rVuirqZ$<ipUCP0qtpEk$1e#orrE)js8VP=s5X-0~>
-JcF[.9*#"cp':Wir!EB$me$MYYS6d2!<;lp)<h+^+Sl$;s7l?jq<e2+q#C$ds6KRX!rW)or;Zfr
-qZ$<ipUCP0rrDofrrDrqs8;Hbqu6U#pFPJ,li@(arr4eM!;uisq=G*qq"k$_m/-fe<Fc'rrq?B`
-rVuirqZ$<ipUCP0qtpEk$1e#orrE)js8VP=s5X-0~>
-JcF[.9*#"cp':Wir!EB$me$MYYS6d2!<;lp)<h+^+Sl$;s7l?jq<e2+q#C$ds6KRX!rW)or;Zfr
-qZ$<ipUCP0rrDofrrDrqs8;Hbqu6U#pFPJ,li@(arr4eM!;uisq=G*qq"k$_m/-fe<Fc'rrq?B`
-rVuirqZ$<ipUCP0qtpEk$1e#orrE)js8VP=s5X-0~>
-JcF[.!!*#u0`_4QrX/5rs'j%=]aXr@1=c$n?B+`3AGu65m/QSYqZ$S"N:4]/o)8Ug"RQ0goDe7X
-rsiM>m'7u<nIts&q?m#trr)j?rr;ZT@C>fB&+9Jtqtp:#rV['&s)En[c=ZqQs0b8o8YQ.`"Si#l
-r:p9k"CeJ!Z6oSN%J'N_C&.Oa70!;Nqu?PEs5a31~>
-JcF[.!!*#u0`_4QrX/5rs'j%=]aXr@1=c$n?B+`3AGu65m/QSYqZ$S"N:4]/o)8Ug"RQ0goDe7X
-rsiM>m'7u<nIts&q?m#trr)j?rr;ZT@C>fB&+9Jtqtp:#rV['&s)En[c=ZqQs0b8o8YQ.`"Si#l
-r:p9k"CeJ!Z6oSN%J'N_C&.Oa70!;Nqu?PEs5a31~>
-JcF[.!!*#u0`_4QrX/5rs'j%=]aXr@1=c$n?B+`3AGu65m/QSYqZ$S"N:4]/o)8Ug"RQ0goDe7X
-rsiM>m'7u<nIts&q?m#trr)j?rr;ZT@C>fB&+9Jtqtp:#rV['&s)En[c=ZqQs0b8o8YQ.`"Si#l
-r:p9k"CeJ!Z6oSN%J'N_C&.Oa70!;Nqu?PEs5a31~>
-JcF^/9`,:rq>(Ttn*UP_j8Z/$%48ggrQHBWs62]U-2[]A!;ulq!<;ZgqHa.WK)blHoF:j#p\uiF
-p\jib0-DUPs8N)ls8N*!qt^-cs8Dor#Nn&HW<rV!r;Q^3!<2uus81be"W!X.naRmpjPMT[q#C$c
-rtkJ/pS]_f-Fs0LnGhn@D#a]1jn\QKpA':>j8XW~>
-JcF^/9`,:rq>(Ttn*UP_j8Z/$%48ggrQHBWs62]U-2[]A!;ulq!<;ZgqHa.WK)blHoF:j#p\uiF
-p\jib0-DUPs8N)ls8N*!qt^-cs8Dor#Nn&HW<rV!r;Q^3!<2uus81be"W!X.naRmpjPMT[q#C$c
-rtkJ/pS]_f-Fs0LnGhn@D#a]1jn\QKpA':>j8XW~>
-JcF^/9`,:rq>(Ttn*UP_j8Z/$%48ggrQHBWs62]U-2[]A!;ulq!<;ZgqHa.WK)blHoF:j#p\uiF
-p\jib0-DUPs8N)ls8N*!qt^-cs8Dor#Nn&HW<rV!r;Q^3!<2uus81be"W!X.naRmpjPMT[q#C$c
-rtkJ/pS]_f-Fs0LnGhn@D#a]1jn\QKpA':>j8XW~>
-JcF^/!:p-h!W)corrD9^rt"f&s8DuknGiO]q>^<gs8)`p%/BJpnc/XZs8VQfs7QBk)"djoq>^Kg
-s82iroCN"\p\4^fq#:0^s8Drs)>sL2s8VKds82Bes8Ducqu?]bs7c3dp](*hrtbA/nc/Rds7lTn
-s8;`ms82iroCN"\p\t1#o(N"]s8;osrVuBbJcFd1J,~>
-JcF^/!:p-h!W)corrD9^rt"f&s8DuknGiO]q>^<gs8)`p%/BJpnc/XZs8VQfs7QBk)"djoq>^Kg
-s82iroCN"\p\4^fq#:0^s8Drs)>sL2s8VKds82Bes8Ducqu?]bs7c3dp](*hrtbA/nc/Rds7lTn
-s8;`ms82iroCN"\p\t1#o(N"]s8;osrVuBbJcFd1J,~>
-JcF^/!:p-h!W)corrD9^rt"f&s8DuknGiO]q>^<gs8)`p%/BJpnc/XZs8VQfs7QBk)"djoq>^Kg
-s82iroCN"\p\4^fq#:0^s8Drs)>sL2s8VKds82Bes8Ducqu?]bs7c3dp](*hrtbA/nc/Rds7lTn
-s8;`ms82iroCN"\p\t1#o(N"]s8;osrVuBbJcFd1J,~>
-JcFX-!V?<hrtbJ2s82`os8N#ms8VHbs8V`ks8Dipo`"k/oDejbs82irq>^Kas7ZKhs7ZEkp&G'[
-s8VZgrrMWbrVlllrr<#rs8NE(s8W)ps7uZorVlg>p&>!jrVuTkq#CBjp&G'hrr)los6]jYs8Vck
-s6fpeo)AY!p]'a_s7ZKgs7uWnnGi66s5X-0~>
-JcFX-!V?<hrtbJ2s82`os8N#ms8VHbs8V`ks8Dipo`"k/oDejbs82irq>^Kas7ZKhs7ZEkp&G'[
-s8VZgrrMWbrVlllrr<#rs8NE(s8W)ps7uZorVlg>p&>!jrVuTkq#CBjp&G'hrr)los6]jYs8Vck
-s6fpeo)AY!p]'a_s7ZKgs7uWnnGi66s5X-0~>
-JcFX-!V?<hrtbJ2s82`os8N#ms8VHbs8V`ks8Dipo`"k/oDejbs82irq>^Kas7ZKhs7ZEkp&G'[
-s8VZgrrMWbrVlllrr<#rs8NE(s8W)ps7uZorVlg>p&>!jrVuTkq#CBjp&G'hrr)los6]jYs8Vck
-s6fpeo)AY!p]'a_s7ZKgs7uWnnGi66s5X-0~>
-l2LbZrr33!r;Zfks8Dor!rDrkrr33%s7lQmq>1'i%fZM$s8W)uoDeF^q"FadJcC<$JcGWIJ,~>
-l2LbZrr33!r;Zfks8Dor!rDrkrr33%s7lQmq>1'i%fZM$s8W)uoDeF^q"FadJcC<$JcGWIJ,~>
-l2LbZrr33!r;Zfks8Dor!rDrkrr33%s7lQmq>1'i%fZM$s8W)uoDeF^q"FadJcC<$JcGWIJ,~>
-kl1_]oD8@a+8l07nb`@^s8Vccr;Zfqs8Dutp\=dcs8Micp&G'jq#13noCdb8JcC<$r;V9~>
-kl1_]oD8@a+8l07nb`@^s8Vccr;Zfqs8Dutp\=dcs8Micp&G'jq#13noCdb8JcC<$r;V9~>
-kl1_]oD8@a+8l07nb`@^s8Vccr;Zfqs8Dutp\=dcs8Micp&G'jq#13noCdb8JcC<$r;V9~>
-l2Le]rVc`uq"+ObrVlosr;-EnpAY'srr<#trVuWXrr3#tqu6Ttr;-BfJcC<$JcGWIJ,~>
-l2Le]rVc`uq"+ObrVlosr;-EnpAY'srr<#trVuWXrr3#tqu6Ttr;-BfJcC<$JcGWIJ,~>
-l2Le]rVc`uq"+ObrVlosr;-EnpAY'srr<#trVuWXrr3#tqu6Ttr;-BfJcC<$JcGWIJ,~>
-l2Lnbs8Vokrr3]'rr;ohs8DlqmJ[(Sn,NF_rr;c^rr3N*s8Vuks8W#ss7Z'as7u>=s+13$s8;nI~>
-l2Lnbs8Vokrr3]'rr;ohs8DlqmJ[(Sn,NF_rr;c^rr3N*s8Vuks8W#ss7Z'as7u>=s+13$s8;nI~>
-l2Lnbs8Vokrr3]'rr;ohs8DlqmJ[(Sn,NF_rr;c^rr3N*s8Vuks8W#ss7Z'as7u>=s+13$s8;nI~>
-kPl+kq#(0kp&G'goDS^hqu??^rVm#ss7uWlrr3&lrVlcq!rW)trr<#sJcC<$JcGTHJ,~>
-kPl+kq#(0kp&G'goDS^hqu??^rVm#ss7uWlrr3&lrVlcq!rW)trr<#sJcC<$JcGTHJ,~>
-kPl+kq#(0kp&G'goDS^hqu??^rVm#ss7uWlrr3&lrVlcq!rW)trr<#sJcC<$JcGTHJ,~>
-l2Le^qu6Trp\b$j&,lP.o)8U\s8Vfms7?6hq#:9rnc/OepAY(!r:g6`qYL6er;ZVEs+13$s8;nI~>
-l2Le^qu6Trp\b$j&,lP.o)8U\s8Vfms7?6hq#:9rnc/OepAY(!r:g6`qYL6er;ZVEs+13$s8;nI~>
-l2Le^qu6Trp\b$j&,lP.o)8U\s8Vfms7?6hq#:9rnc/OepAY(!r:g6`qYL6er;ZVEs+13$s8;nI~>
-k5Q_)rr<#lp&FX`s7cQhs7Z<hs8DutqZ$0ep%&.[s8Dunr;ZNbrVlumqZ#u7s+13$s8;nI~>
-k5Q_)rr<#lp&FX`s7cQhs7Z<hs8DutqZ$0ep%&.[s8Dunr;ZNbrVlumqZ#u7s+13$s8;nI~>
-k5Q_)rr<#lp&FX`s7cQhs7Z<hs8DutqZ$0ep%&.[s8Dunr;ZNbrVlumqZ#u7s+13$s8;nI~>
-l2NC0s8VZis-XfMeGlm-(Uj@a(F-Njs1fWp`W*e''u0d[+<@ojZmug4oDc6F+LZk$JcD,;qYog\
-J,~>
-l2NC0s8VZis-XfMeGlm-(Uj@a(F-Njs1fWp`W*e''u0d[+<@ojZmug4oDc6F+LZk$JcC<$qu;0~>
-l2NC0s8VZis-XfMeGlm-(Uj@a(F-Njs1fWp`W*e''u0d[+<@ojZmug4oDc6F+LZk$JcC<$qu;0~>
-kl1\ZoD\bA%ahRUp-P1O7/o??WB1(V1pE]9s!snQ1B0J7[5.VFUS]gHpH,d[2uipUq>L6k"o82u
-rr2kIs+13LrrE&rquQEerr2?cJ,~>
-kl1\ZoD\bA%ahRUp-P1O7/o??WB1(V1pE]9s!snQ1B0J7[5.VFUS]gHpH,d[2uipUq>L6k"o82u
-rr2kIs+13FrrDfYs*t~>
-kl1\ZoD\bA%ahRUp-P1O7/o??WB1(V1pE]9s!snQ1B0J7[5.VFUS]gHpH,d[2uipUq>L6k"o82u
-rr2kIs+13FrrDo\s*t~>
-l2NL9s8;]mlP[^`$iD+2k8!J#q">Ens!$P(%e(55mhPj5n(nl\"97^,li.:Trs/Jtrr)j'q=O[Z
-qu6Nms8RZJJc)PG"T.iaq"t'g!r2comf.e~>
-l2NL9s8;]mlP[^`$iD+2k8!J#q">Ens!$P(%e(55mhPj5n(nl\"97^,li.:Trs/Jtrr)j'q=O[Z
-qu6Nms8RZJJc)MF!r2WhrVllqm/MS~>
-l2NL9s8;]mlP[^`$iD+2k8!J#q">Ens!$P(%e(55mhPj5n(nl\"97^,li.:Trs/Jtrr)j'q=O[Z
-qu6Nms8RZJJc)PG!ri)qk5Tr~>
-l2NU)s8Drps.0Bb$LA6%rrE'!p[8CfrW)ue"9/B$m0<7es8NGs%J0T$s8N-"q?luns8Vrkp\t6l
-JcC<$WW*;(r;QTerr2KfrpKf:~>
-l2NU)s8Drps.0Bb$LA6%rrE'!p[8CfrW)ue"9/B$m0<7es8NGs%J0T$s8N-"q?luns8Vrkp\t6l
-JcC<$V>gYls8V]Ws*t~>
-l2NU)s8Drps.0Bb$LA6%rrE'!p[8CfrW)ue"9/B$m0<7es8NGs%J0T$s8N-"q?luns8Vrkp\t6l
-JcC<$V>gYps8VcYs*t~>
-kl37.q>^?ln,Mndrr_WI%f[:DknNde&aoK(r"Abl)u:6+lk9<uqtL?frt"Df$i^/8lc--3ZEBq2
-\$`TIZ`\kdJ[DD`$)t/6\[:u.r;Q]`s*t~>
-kl37.q>^?ln,Mndrr_WI%f[:DknNde&aoK(r"Abl)u:6+lk9<uqtL?frt"Df$i^/8lc--3ZEBq2
-\$`TIZ`\kdJ[DD`#H=r4\[;&0rTsQ7~>
-kl37.q>^?ln,Mndrr_WI%f[:DknNde&aoK(r"Abl)u:6+lk9<uqtL?frt"Df$i^/8lc--3ZEBq2
-\$`TIZ`\kdJ[DD`"f\`2\[;"os*t~>
-l2LhPs7ZEk/aB<BqF%]f9E.#MO];AW6Ck#<s%/0S8,[email protected]]nkcH]1AgtKs8D`lrrpF:
-rr<#rJcC<$V>gVpnA4o"s8MZjJ,~>
-l2LhPs7ZEk/aB<BqF%]f9E.#MO];AW6Ck#<s%/0S8,[email protected]]nkcH]1AgtKs8D`lrrpF:
-rr<#rJcC<$V>g\qn%eu&li2J~>
-l2LhPs7ZEk/aB<BqF%]f9E.#MO];AW6Ck#<s%/0S8,[email protected]]nkcH]1AgtKs8D`lrrpF:
-rr<#rJcC<$VuQeq"8V>urTaE5~>
-kl3XAp[S:Q0Esl%s3(fr[f=G^/[k3L_^-&Grjt9(\c8o_*P_Wt"<tV[rPK-YbQ%,1s8W&ts891u
-rr2uoJcC<$V#LSq[f?(#qu?]qo`'F~>
-kl3XAp[S:Q0Esl%s3(fr[f=G^/[k3L_^-&Grjt9(\c8o_*P_Wt"<tV[rPK-YbQ%,1s8W&ts891u
-rr2uoJcC<$VuI&"rr2\urq66hmJh\~>
-kl3XAp[S:Q0Esl%s3(fr[f=G^/[k3L_^-&Grjt9(\c8o_*P_Wt"<tV[rPK-YbQ%,1s8W&ts891u
-rr2uoJcC<$W;d/%q"ss]Z2F4jm/MS~>
-kl2"Xs8V?\s7Z?is8;]m#l"B!nGi:Ws7?0g"76'dnc&Omp](0ks7#XZrs\i&r;Q`%s8W)up&Fi=
-s+13LrrDurrrTY/qYL6lrq-5@~>
-kl2"Xs8V?\s7Z?is8;]m#l"B!nGi:Ws7?0g"76'dnc&Omp](0ks7#XZrs\i&r;Q`%s8W)up&Fi=
-s+13Lrs&8rrr9;'p?Va/~>
-kl2"Xs8V?\s7Z?is8;]m#l"B!nGi:Ws7?0g"76'dnc&Omp](0ks7#XZrs\i&r;Q`%s8W)up&Fi=
-s+13MrsJ_tq"jijqt'^`rU0]9~>
-kl344s7ZKmoDedgs7lWor;ZTmrr;ims8W)urVuoprqlWnrVucps7QEhs8;omrr<#srr3<(s0;V(
-qu?TorIP!"s/#_sYQ+:ls8W)js*t~>
-kl344s7ZKmoDedgs7lWor;ZTmrr;ims8W)urVuoprqlWnrVucps7QEhs8;omrr<#srr3<(s0;V(
-qu?TorIP!"s/H#&rr)]mX8_YTs*t~>
-kl344s7ZKmoDedgs7lWor;ZTmrr;ims8W)urVuoprqlWnrVucps7QEhs8;omrr<#srr3<(s0;V(
-qu?TorIP!"s/Q)+rVQBaql'D[qu-K]s*t~>
-kl1bPs8VcjrttV4rr;TipAb0jp]('hrVuWhs6]jbp&G$irsAZ$s7lWoq=t!eq>UN&s8.BIJcDPG
-$N9u(pU1%rs8N&lrVllro`'F~>
-kl1bPs8VcjrttV4rr;TipAb0jp]('hrVuWhs6]jbp&G$irsAZ$s7lWoq=t!eq>UN&s8.BIJcDPG
-$N0l%p9akos8;ojrVllro`'F~>
-kl1bPs8VcjrttV4rr;TipAb0jp]('hrVuWhs6]jbp&G$irsAZ$s7lWoq=t!eq>UN&s8.BIJcDPG
-$MsSsoWnGgrVccirVllro`'F~>
-kPkYPs8VZhs8Va"rVuohs760hnbiFYrr3,mq#C3frVm;ss8V<_q#9jas7cQeq>UN&s8.BIJcDPG
-$N9u(s0Mb$s6fpbrr2uroDa=~>
-kPkYPs8VZhs8Va"rVuohs760hnbiFYrr3,mq#C3frVm;ss8V<_q#9jas7cQeq>UN&s8.BIJcDPG
-$N9u(s0Mb$s6fpbrr2uroDa=~>
-kPkYPs8VZhs8Va"rVuohs760hnbiFYrr3,mq#C3frVm;ss8V<_q#9jas7cQeq>UN&s8.BIJcDPG
-$N9u(s0Mb$s6fpbrr2uroDa=~>
-l2LkRs8;?brsSPns8Vlorr;ZkoDJUf%/Khks82iro`+Uas5j7[$2FDtrr<#os8VulrrTP,qgncu
-s/#bqrWW2sr361urr*&tmf3=_oDa=~>
-l2LkRs8;?brsSPns8Vlorr;ZkoDJUf%/Khks82iro`+Uas5j7[$2FDtrr<#os8VulrrTP,qgncu
-s.KAnZ2ae%rri5es8Vlcs*t~>
-l2LkRs8;?brsSPns8Vlorr;ZkoDJUf%/Khks82iro`+Uas5j7[$2FDtrr<#os8VulrrTP,qgncu
-s.KAlZi'h,qsOLapAOX`J,~>
-kl1YUrr2ugrr3Dqs8Vfbs8VQfpAap[rr<#d'*%D"s7u]cs8Vc`s7Pj\q"jm_p\t<$s8.BIJcDSH
-$NBqsnG0$[qu#sPrr3#pp%/36~>
-kl1YUrr2ugrr3Dqs8Vfbs8VQfpAap[rr<#d'*%D"s7u]cs8Vc`s7Pj\q"jm_p\t<$s8.BIJcDMF
-"9/&pXoA>$o^Mk[!VuBZs*t~>
-kl1YUrr2ugrr3Dqs8Vfbs8VQfpAap[rr<#d'*%D"s7u]cs8Vc`s7Pj\q"jm_p\t<$s8.BIJcDJE
-!r`/(rr3#no)AXjp[\:Ts*t~>
-kl:\]/H5JFRL9Y(s1KZs_#D:j*5V[TV]62ks/d[j_Yq7g'Y"+^*$`N(s1g$'`q]B0!jhq(JcC<$
-V>h#&q"<qHWq,o\r;6HmrUKo<~>
-kl:\]/H5JFRL9Y(s1KZs_#D:j*5V[TV]62ks/d[j_Yq7g'Y"+^*$`N(s1g$'`q]B0!jhq(JcC<$
-UAk\ss0MV%s8W#qs8;orrq-5@~>
-kl:\]/H5JFRL9Y(s1KZs_#D:j*5V[TV]62ks/d[j_Yq7g'Y"+^*$`N(s1g$'`q]B0!jhq(JcC<$
-T`5#'rVm*$rVu`mrVZ<fJ,~>
-kl1Y\rr4G=+j7n>n5#UR6N9H@VFKqP:SXONpcYFE4o\<LX@E4JT;"sIqbWcO9_eVhZiBoRs+13H
-rt,,$o'Z.anFcP9r:U*bs8Vugs*t~>
-kl1Y\rr4G=+j7n>n5#UR6N9H@VFKqP:SXONpcYFE4o\<LX@E4JT;"sIqbWcO9_eVhZiBoRs+13E
-rso&.bPM5;kl:AVp](6hrq6;A~>
-kl1Y\rr4G=+j7n>n5#UR6N9H@VFKqP:SXONpcYFE4o\<LX@E4JT;"sIqbWcO9_eVhZiBoRs+13C
-rrCRJrs.ojq#C$cqtB[^J,~>
-l2NC/s8V?`ruf+h0)l"HpBpU.l1,/TrsJ;b%0$P)q#L3lrT=@a!<;@!nG`d[r;cWm!jhq(JcC<$
-V>gPmq#CU2k5PDWq=F4XJ,~>
-l2NC/s8V?`ruf+h0)l"HpBpU.l1,/TrsJ;b%0$P)q#L3lrT=@a!<;@!nG`d[r;cWm!jhq(JcC<$
-V#LN!$3^_7!so#ElMpn[q!\7^p&BO~>
-l2NC/s8V?`ruf+h0)l"HpBpU.l1,/TrsJ;b%0$P)q#L3lrT=@a!<;@!nG`d[r;cWm!jhq(JcC<$
-V#Lr8)&!bs%L`X^mJm4\p?_\Ks*t~>
-kPm..s8VSc$4`*r&+KT#rrE)s!<;iqs8E&u$Le!#rsJ,m!:pisrW)un!<;[+q>($lZiBoRs7?9d
-rpg$cro*k[rqu]nrr)lsrr)cprlP0KrquZhq"OIUq>C0irl>$Lq#D6N+;uIN-33i9p&G'^oDa=~>
-kPm..s8VSc$4`*r&+KT#rrE)s!<;iqs8E&u$Le!#rsJ,m!:pisrW)un!<;[+q>($lZiBoRs8;om
-rnm_arr;utrr;utrr;utrm(NJrr;utrr;u`s8DrbrrE&srrE&CrsfPt3^5_o5<AuIr:U*io(2m3~>
-kPm..s8VSc$4`*r&+KT#rrE)s!<;iqs8E&u$Le!#rsJ,m!:pisrW)un!<;[+q>($lZiBoRs7u]m
-rS[\arVuirrVuirrVuirrR1`FrV6EYrTX@\rQP9P,#D<E=Bnm#%fcM!qtodZo`'F~>
-l2O!Hp&G'Zs!,1n/,fqFo*t^4k4T;br"A8_$iV(,pC$m0o&gD\$M3d#s8NE#r<WB"s7+16Za[38
-b-JC`\?<;tZMh'.ZN%6:[CEcY]"G\dZh^g(Z2_-0Zhq'-Z2:^:Z*UdEZaI-IZaI-IZaI-IZa02-
-&[/4DXKAt8Zb<`DVmNG5WMd#lZN%6S[Bm9I[Bm?O]WecLYe@BY]!f/WYIq<TYJIQU^9tAU[C-"B
-!O\s,!!!&t!"G@3Yd1jK]!o/W]<eEFiNick\ZiNGTs_B+Z4X&9'a"sH!"feCo(r:apA=adp&BO~>
-l2O!Hp&G'Zs!,1n/,fqFo*t^4k4T;br"A8_$iV(,pC$m0o&gD\$M3d#s8NE#r<WB"s7+16Za[38
-b-JC`\?<;pZ2q59riuL,rN61)Z2(`rZMV!-Z4+"DZ*LX?Z*LX?Z*LY)Z4XFN_R-SWY-PaNY->UF
-_Qg2JrNZ1(rj2I+(p^KV\?E0CXh([I[B[*CWjf7@Wk>LA\Zu.?rj;^5&?Z-C"WRaQ$3UF*a0W([
-Z*jS:"1bb:\`'n#Y.CmIYbJS9qQg[?#!k4A5Wq\'#64`#s7c*aJ,~>
-l2O!Hp&G'Zs!,1n/,fqFo*t^4k4T;br"A8_$iV(,pC$m0o&gD\$M3d#s8NE#r<WB"s7+16Za[38
-b-JC`\?<;tZN%95[^<LB[/[E3Yl:d,Xfeu*[f3Z6ZN%0+ZMq6.[LomNY->(5Y->(5Y->(5Y->.9
-o!Aq:`3unXXK]CM[(!u_`3ZZF[f3Z6ZMq*-XoYi8rNcI-s0=MiX/rG%[Ap^@Xg"n(Z`UL0\>ld@
-ZG+/j]XbGVZ`OBC&1nkG+VbKhca^?iY-P77Z+@?E^#?C)Ye7<QZD>"AqR$jK-#RIKH?4=@*<6'4
-q!\+Os*t~>
-kPm[Dli6JUfY7df;3naMs$EN[3;WGJWNSJT9V&.Ps$E$a5kI[>X#U.J8?.bGs7QElq"4Obs1eC%
-r;ZfmJcGKEs82rqr;6Hjs8Mcms8N#q"T/,or;?'a*rc3=s8N&ts8N&ts8N&rpYkT=s8VuorqZ9_
-q!7bRl2Lh_rVQQn"nquqqZ$KlrsA,nrr;cnmJZq\rVd?)p])<Y#7Vab/-,8$lMgMUrrr#nrq?-[
-iq!6@p$)JK!VGjWoaC0f'ESXB!!EZ0i:6gH!r)<cp&BO~>
-kPm[Dli6JUfY7df;3naMs$EN[3;WGJWNSJT9V&.Ps$E$a5kI[>X#U.J8?.bGs7QElq"4Obs1eC%
-r;ZfmL&_)Ms8Duq"T/&mr;OP4!Uog_rr`#ms8N#t!qlTnr;Hfurr;ugrr`/rqYpKo&G?&!pAXjd
-rquc\rqcWdrp0L\s8N&u'a-H^0KiB!7g&e[nc/7\q>1!YrVucQrrW0!pAP!kr;$@"#=^mR91DK=
-#P.WfrrDuhs*t~>
-kPm[Dli6JUfY7df;3naMs$EN[3;WGJWNSJT9V&.Ps$E$a5kI[>X#U.J8?.bGs7QElq"4Obs1eC%
-r;ZfmJcG]KrqulprU^'hrUBjXrUBgkn+ZeXqXjU]rrDcds8W&sqZ6Qjq>CEkqY:!fr=JMrp[e:T
-qtp3dkPP#Nnb_\Ns8W)urt5`0=\r[[AmuMTqt^9drV?3ao$.1F/9uQ(O,/C),Q@B2pA"O\o`'F~>
-l2NI:qu?]fs0MnKWW0RV(!?*f&Mq#us0`ONf)NBG!6"oF3!IM*`sX*Co`)`G+3"$Pqu6s"Y5/+t
-rr;ioM>R;MqYU9is8E6#qtg0aqY9sap\Xmb!r2Werqc-]')_Y)qu$?hqu$?hqu$<fq#:6_rr3f.
-s8MrppAa^]r;QWnr;QWnr;QWnr;QWiqu6U/qZ$'brq$-gmJcVMrq$-[rqZQmqYUs$pZVZ0)?L9H
-![%L4o(VtXnc&OlrqZBbo'52r#2J>b%LFF&qB$:h!#PeC!<<-2!:p'eo)JU]rq6;A~>
-l2NI:qu?]fs0MnKWW0RV(!?*f&Mq#us0`ONf)NBG!6"oF3!IM*`sX*Co`)`G+3"$Pqu6s"Y5/+t
-rr;ioM>R;KrqlQgs7lZkrqccpqu#gX!rVrnmJ@[qqtg3dqtg3dqtg3dr;Zcrr9aL_qt9pf!r2fc
-qYCBmqYU-dqYpBhr;ZZort>>,rpTjdo`+sZs7,pbo`+O_qY'pqqZT_a2F]qm63R5d"n;Qlrr;uR
-rs&E$67s]U55@DR#=LXE845^.'`\41n,NFdo`'F~>
-l2NI:qu?]fs0MnKWW0RV(!?*f&Mq#us0`ONf)NBG!6"oF3!IM*`sX*Co`)`G+3"$Pqu6s"Y5/+t
-rr;ioLAUuKp](3j!rr6!r;HBequ$ZtrVuipnbX-uqt^-bqt^-bqt^-bqtpEiqtK:Iq"XI[rrrDp
-qsF:Zr;6NirV-<brq-6a!r)Ndr;R<%s8Vlos7$'[qu?Bip&Fs_rs0KZD.nHOF(H6errD`CrrcOr
-6=*ai=:e[fEGgA_J9>QXs8V`Rq#10`s*t~>
-l2Lq`s8W&trVlg*k5,,Ks7u]prTjI_rVlg=p\k*[s8W)up&G$krVuK`s7Q9ds8N&uqtg9krVcc*
-rr3&qs8ITLs8N#qrVQWo#64]&rr;utrr2ZlrVl`pnc&%X!VQBdrrVoooBuVYrqcO0o^)MQr:^'^
-q=O@ToD&.Op\=IFq=O+Mq>U7Vq=sXQlMq8)$31&2'`d[kh=pR=q"s(Hq!mu#,Tn9R+s8'P+s8'P
-+s8'P+s8'P+s8'P+s8'P+s8'P*%i/fl2KiYkiV*kkiV*k!$2IS#71YT!:p-grrW,krq6;A~>
-l2Lq`s8W&trVlg*k5,,Ks7u]prTjI_rVlg=p\k*[s8W)up&G$krVuK`s7Q9ds8N&uqtg9krVcc*
-rr3&qs8IlTqu-WrrlG-1rXJo$rVuosrr)`nrq6<\rr;Qgq#;-)nGiLfq>^9jrVZ]ls8Vrps8DHe
-rUopb3<qB17mo^73s>T`lMpb]qu>jZs8N$S4[2+p5!M4q5!M4q5!M4q5!M4q5!M4q5!M4q5!M4q
-3))='rs/it4$lD-5Xu%Xs8W'!s82HgJ,~>
-l2Lq`s8W&trVlg*k5,,Ks7u]prTjI_rVlg=p\k*[s8W)up&G$krVuK`s7Q9ds8N&uqtg9krVcc*
-rr3&qs8IiSrr'e8!;l?`&FfGhq"FFVq"t$]qs4%NpA=^bq>:0ir;QfpoD\airq-3mp&G'jp\tUF
->'5@LMM5FXs8VrnrrDHbs!'m0<)ut!<)ut!<)ut!<)ut!<)ut!<)ut!<)ut!<)usp@SZRq%jl"&
-F*rCSC)m9Rq"OUaqt0o=~>
-kl21ms8)cqoD7tVs7-'fli6\Xrt4\hs8;oso)Jacs7c9ap&F[[qu6lms82irrqlWn#Hn%(p%&.\
-r/1LNs8W'1rVH<ap\+7NpA=jhrr;utrr;usqu-?ir;QfsrU]mdrpTjln,NF_qu?]irVllmnc&Xh
-qtg/6qt9j\qtp*So^hbEp$;;Cp##H7n+?;Eq"aa\p\"+E,60.m"Tn`.+93]A-n7J,1FPd6/1i@D
-p\Ogar;-?fr;-?fr;-?fr;-?fr;-?fr;-?fr;-?frquchq#($crqucnrqucj!#lO^$P<dc!;Gp]
-s7H6erq6;A~>
-kl21ms8)cqoD7tVs7-'fli6\Xrt4\hs8;oso)Jacs7c9ap&F[[qu6lms82irrqlWn#Hn%(p%&.\
-r/1ISrVZWm!ri/tq#:TurVlcprVlcbrW)oorq$-jrr)Ecs8NMorr2N\rr2Hdrqucbnc&Ibrr2rt
-#5eH!s8W&krr3&us7uZo"7?-gqu-QprqudT64I-P92\AW9*RRK3B0\a4>BYZ3'B>&rr2lqrVlcp
-rVlcprVlcprVlcprVlcprVlcprVlcprr3,tq>^Blq>Up-4#oPk7QE[>s6fmcoD\@]J,~>
-kl21ms8)cqoD7tVs7-'fli6\Xrt4\hs8;oso)Jacs7c9ap&F[[qu6lms82irrqlWn#Hn%(p%&.\
-r/(CUr;-9frq69qr;QWnr;QWmrql`lrW2rrr;?-c!WMiao+:WjjnSW>nGE+Nq=sUSjn/BHqY:!_
-rs&B#q>:'^oD&=cp@J;"="8Z'IZ'#7DB;bZ8OZ`?764X,6qU)-rrN#qpAYU;<*EmMEaW,is6fa[
-n,;kXJ,~>
-kPkYOs8VQdrs8B!s7cBis7l-`rrr2tp&G'brr2umrr3<!s8W#jp&Fddq>C6srr<#qp\)"Gs82g&
-rquTfp\"1Lp\smdqu63e%fZD*r;QWnr;QWnr;QWnnG`mls8W&tqu6Whnc/UVruqC=q"a^\q"a^Q
-p%e1Nq!RnKm.'Z3n+,]5l13p+nes;<r\4X3/Li++!!O#6!"TG;lf[g/hY$X7nFQ;Clh0'5!;lNj%
-g4(&,SM7;*WZ$9r;ZNkrq-5@~>
-kPkYOs8VQdrs8B!s7cBis7l-`rrr2tp&G'brr2umrr3<!s8W#jp&Fddq>C6srr<#qp\)"Is8W)u
-rVlfdrrE&trrE&fs8N#es8W';rVlcprVlcprVlcprVlTls8Duqs8Vf]rqZTjnc&Ugq#;3(s8W#q
-s7cQnr;Zfns8Vloq>^*es!g;pr\tZR4$,V*&iWKN1/U+k%KHA+s6BXarVHNn!rW)uir'&VpAOaa
-o(`.nrt.7Y8Noa13X#K^p\sdTs*t~>
-kPkYOs8VQdrs8B!s7cBis7l-`rrr2tp&G'brr2umrr3<!s8W#jp&Fddq>C6srr<#qp\)"Hs8;ig
-s8Dlrqu-Kcs8DopqZ-Tbruh46qY9p^qY9p^qY9p^qY9[YqtpEms8Vl`qt9[PoCVnWrU9ajqu?]n
-r;ZE[rrY2T8kT%Q$VO:q0jK$KCm&jA,lR`CpA"XfrSmMSo'c;Ap?VMI%fI8:>@_,X>>Pn2r:BdU
-o)F4~>
-kl2:\s8)?ep](3lmJm4Js8D9`s7QBk!r;lgrr3,pnGhqIrr3#uqu6U#o_\Udrr;]hqu6o,rVuon
-rUfl9rs/Q$r;$-^p%\4[s8N&rs8MTh0E2"Ns8N&ts8N&ts8N&rq=j[Yq"ORXq"ORXq"ORYqr7GG
-m.Bo;q"a=WrV6Ebrq$%2qtTp\p\=LXp[Iq@p\=CPp[mq>ng$+K.4[1k1+"I@n+ZhV)u'$rnE9iY%
-hf-P&etE6mdfi;p\=LWp%7G<pYc&On,<7dn+cqY!!`Sfs8Vurs7c0cJ,~>
-kl2:\s8)?ep](3lmJm4Js8D9`s7QBk!r;lgrr3,pnGhqIrr3#uqu6U#o_\Udrr;]hqu6o,rVuon
-rUflAs8;iss8McmrVcfsrr2Kgq>T=P%d*fkq#CBjs8VNer:p<al2Lt_s6p!fr;$?l%Jp)]7RB*s
-6RY8Z3WK*Rrs0/k5"\.59K<IZrrN)tqu6Zjrr:sV!9jC\!9j7X&H;e7%1<CH#m:e(rqlEgrq?!a
-J,~>
-kl2:\s8)?ep](3lmJm4Js8D9`s7QBk!r;lgrr3,pnGhqIrr3#uqu6U#o_\Udrr;]hqu6o,rVuon
-rUfl>s8Mujs8DfpqYg9krV$9jrVcWnq>gENrrD6Xrs&&oq!e"DrUKmfrU9am5uUQK85M0>6Uq(T
-s8)fpr;R$W??D'^Q],2lo)I\D!93tP!93hL&GlV>)BKk?*YfOZrV?'bs82HgJ,~>
-l2NC7s8W#sqZ$<hs8Vrds8Vfms8Dorq#C<mrqcZns8W#ss6BX]qZ$?jl2Ue[s7ZEk$iB_ss8B>'
-s8Vrqrdk+JrrN,srql?f"TJ>srr2!Y!WMlco`FmR"Sr*6!s%ifqtg3cq"a^\q"a^\q"a^\q"a^[
-pDEEU0/G+;.O[5/./!6!o^_YIo^VSEp%S4[p`&u$oBQB%&el>n&M4"Jo'lDKoCr(Uq!n%Mo).MI
-#Pe>ir:g6kp%n\!rqlK_jLa@>o]lGUs82ieoDa=~>
-l2NC7s8W#sqZ$<hs8Vrds8Vfms8Dorq#C<mrqcZns8W#ss6BX]qZ$?jl2Ue[s7ZEk$iB_ss8B>'
-s8VrqreUULrqHHhrq$0frW<-!ro*kbp(7B6"Vq:Q";:k2jSobf4#f;X5;G&a4#[-=q#C?mrrW0!
-rqQL)r!#GE4A%Xq8-Jkjr;Zfprr2p"qu?]piVs/Xs76*^s8Vccrs\VKpAb0_s82cfrp]pZs*t~>
-l2NC7s8W#sqZ$<hs8Vrds8Vfms8Dorq#C<mrqcZns8W#ss6BX]qZ$?jl2Ue[s7ZEk$iB_ss8B>'
-s8Vrqre1=OrVQKlr;clrrVucoqZ$HmrV-?krVcTpqYU0\rUBgsr#$%d*&&K`)']Rgs8VuYrs;:h
-:.A2O6;U0<p&>0oq>'mdrVm-H@pa/,G]%"(e,K[Js76*^s8VcbrrCpTrs&5tqYTscp%/36~>
-kPkVVs82cp#Oh]mq>^*eq>UBpr;HKl"8)Wgr;Q^&rVuors8)clrqQKm$iU#%rr2lqZ2FOqq>UDO
-rr;ouqu-NkrrDrqrrW3"r;Q]rrql`qrWE,rqu-Bj$NL,*r;Zfjs8W)tr;QcqrVllorVlues8VK]
-ruh42!%8]k*$#V',QRB+o^)5Co(DSHo_%hKo_%kLo_%nQ0a96gp%S+QnG`.iq"ORXq"ORXq"ORX
-rV-BhrqcfrqsOgd!!Vuhq=XXYrql`ls7cWhp=&X?p\XXTm]cBYpAF=XJ,~>
-kPkVVs82cp#Oh]mq>^*eq>UBpr;HKl"8)Wgr;Q^&rVuors8)clrqQKm$iU#%rr2lqZ2FOqq>UDJ
-rr)fqrW`#prr;utr;Q]rqYpKorVc`qs7uX*s8Dups8VZhrqcKjrqlZnq>L9upAP!js6]jdmIpPd
-"@[email protected]'$57.>h!;Z-arAjm=s#U6@62gf`r:'acrY5>1rr)if&H`@D('Y<Q#lal(qZ$E5rr`,q
-q7Z_+!ri/srr2QiJ,~>
-kPkVVs82cp#Oh]mq>^*eq>UBpr;HKl"8)Wgr;Q^&rVuors8)clrqQKm$iU#%rr2lqZ2FOqq>UDO
-rqud4rqcHbp@RYBo(;SLqZ$Top\Xg`q>1$er;ZZn&cDV)rV?Ehnb`4Xp\Xj_qYU!bqZufiqu?]`
-s8VK[rs9EF>$5lf@oQVOm/R)\s%Ebm8Ol388,rVgs7kgX$2b\M()%u3*tAk]rrW3"rQ,!@pA=a)
-r;ZfqnGe"~>
-l2Me&r;Q`rli6bEs76!`rr;Tirr<#ns8VQfp&"X]rVc`ujT#8Trr3<#s8Dutnb)bUrqucr[f$.+
-q=ojI"9/2pr;?Qpqu$KnrWE)qs7lEirqlcqq>UZsrV$'es8;iq#6+Z&q#CB[r;Quus8Vros7Z6f
-(@hGG+pJ&S%2ffZmG7-jnEoB/kOS*`q(*+1p@e7Sp\+@Tpt#63oCr%Nqss[bqZ6Wor;R2es8DNa
[email protected]'^^qu?]q1&UqDp\=LXp\=LXp\=LXp\=LXp\=LXp\=LXp\=LXp\=LXp\=LXp\=LXp\=LX
-p\=R`rWN/$nF?GCs*t~>
-l2Me&r;Q`rli6bEs76!`rr;Tirr<#ns8VQfp&"X]rVc`ujT#8Trr3<#s8Dutnb)bUrqucr[f$.+
-q=o^ErVdK/nb2t]s8Vrps8W#sp\t-irr;uhruM%9pA+agqu6Wos7u]mp&=sQs8Dusrq??joCi1P
-q>UEo('['#4$?#$7QLbRoDeXdrqHHfs8O`8qE+a>rr)rurr)j$iW&rVs8W&ds8N#trVuj*jT#8R
-s8VrqhuEHKr58O=Zh=%ls8N#rr;cihs*t~>
-l2Me&r;Q`rli6bEs76!`rr;Tirr<#ns8VQfp&"X]rVc`ujT#8Trr3<#s8Dutnb)bUrqucr[f$.+
-q=oXC"T.ufkj89>%Ia#js8N&kqtg-bqYgBbrs/K#pA+agq>:0f#57ohnGE%ArqZotqXXFVmI9o8
-q=t!i#p*N"D/joGE*FC\rrE&srrb>P8P)SR9E7i_rs%TbrVZ]oq<7hiirB#Ns8VurirAfQrr<#s
-rQG3FrVHKmZhX@^s*t~>
-l2NC8s7c-brs8Z1!<:7_'Z'gh%kG'Xs2$#sci:I>%D;_D.NcD)aors)s8BhG'$U=S$2aPoqYRSs
-rqlNjs,-dYrVH<drV$6prqZ?br;?Hls8N#p!WN,trr;rqrXAMqr9s[WqYC0\s8VimrrW&pr;Q^!
-r:p<lrV6C/h>eDn":>,2"!7Nt+[Ik+.lSD0-n"TSqXP6jp\=RZq=sd\q=sUWq=XROrU^!krVQKj
-rr)isrqQKnrVlisrQ5'Br:p6d\EX$D~>
-l2NC8s7c-brs8Z1!<:7_'Z'gh%kG'Xs2$#sci:I>%D;_D.NcD)aors)s8BhG'$U=S$2aPoqYRSs
-rqlNjs,$aTs8W)tr<W?"r:g!cs8W)srsSf*qu6QnrVlfrrr;fnrVmi5r;Z9do(MkWn,E@Ys8N&l
-qtU*crU]LVrUfs_r>,G$*()8G:d.9-9,.(Z=%YA681[Iq3:6_LqZ$Tns75a[r;R9+rVcWbqu$3c
-pAFmhs8Drss8;]ls8=_NrVZWlrVZWlrVZWlrVZWlrVZWlrVZWlrVZWlrVZWlrVZWlrVZWlrVZWl
-rVQEiqRHP)rr2lp!<2WjJ,~>
-l2NC8s7c-brs8Z1!<:7_'Z'gh%kG'Xs2$#sci:I>%D;_D.NcD)aors)s8BhG'$U=S$2aPoqYRSs
-rqlNjs,-gRrsSi&p\"1MmdKlCq#($h"o.ujq>C0hrrN,srVl]o)u]g:o)8"Lo_e%RqssX_qt9^X
-q=OCIkk=`;oD8.rs8Ful>^qc]IV<[Ss$gHs5t=a0=$o=<rrN-!oDSdgq>9mjq=4@\qu?Zprs&H!
-rVcWgq"snHr;-6`q=j^Zq=j^Zq=j^Zq=j^Zq=j^Zq=j^Zq=j^Zq=j^Zq=j^Zq=j^Zq=j^Zq>'aU
-q=V2plMlA~>
-l2NR5s8Vons6fp#8Gt65WD*%EV2nRTqa6pO4T@p2YtG$cVR8kS:TU0Xs6rdaSh^-<r;QZnrs-"4
-s8;Qdp&'^I"9/2pr;?Qpqu6U&o(;ePo'GW2qu6Tls8MrnrsJc'pA"7Vq=4:Xr;HU#rVl3`pAXaa
-p\t.FnG`@VpAY!gqu-Ejqu-<W!"]qQ!!!60-3,pkg#__eo]P`:n`oc=oCVYHoCVbNqY:B^qsjRU
-oDAIUs8W)orsST$s8DrskPtAXs8Kt:"T5t5rqc!]J,~>
-l2NR5s8Vons6fp#8Gt65WD*%EV2nRTqa6pO4T@p2YtG$cVR8kS:TU0Xs6rdaSh^-<r;QZnrs-"4
-s8;Qdp&'ODs8ET.rq-0gs75j]rVu`cq#CBmrr2`n!<2or"oA2ms8Vljrs8W(mf3%]pAapes!@=;
-s7?*es8N&ts8N&ts8E3`:JF5J90cD\:]Kh[s7u]err<#mnc&monGi1]p\+Ucli.Opr;6BUqYKaQ
-r8[hMrr;lpqZ$TpdJa(E"T,e0qYC'g!<)orp&BO~>
-l2NR5s8Vons6fp#8Gt65WD*%EV2nRTqa6pO4T@p2YtG$cVR8kS:TU0Xs6rdaSh^-<r;QZnrs-"4
-s8;Qdp&'LC#lFJnmeZeWo(W+_"8r/us8DiprVZZqrVQTurVufqs8Mfn#4VZgs7lWjrr3,os8Vij
-pAYOF@Y0JmMhlG#,%1HErVllro`#3qq"aa_rVuokq>^'b!rM]`rV$9d%f6(erVc?]rS[SDqtg$^
-qYC9jqY&D1#kn,lqY%>op\4%SJ,~>
-kl3=(rVuo[s8P-ts!R+($N1\=kna!j'CY](q%NJk*;gN0kR.=krpBs^rse2a$NKu#rr`&dYPeA!
-!W2kRrVl]qrVlcq%fH@lq"=4JmdKZ9kkX];rr3'!rVc`lrVliq%/fu!q=!SBp%%kPpZ;>J2tHb6
-q!e(Qo]t\pkMk[h'dY(N,:+Q\-:Rt\!!WE'!tu:ImI^)>lhBcAq!7_Nq"a^\q"a^\q=jdkp&+CY
-nc&OZrrE&tr;ciqrrE&mrs&>qqu?]qrVZZArs&5os8TM(rp0T7~>
-kl3=(rVuo[s8P-ts!R+($N1\=kna!j'CY](q%NJk*;gN0kR.=krpBs^rse2a$NKu#rr`&dYPeA!
-!W2kNrVl]tqu$<imJ6bdp&FXRs8;for;uusrqcQrrr<#no)&Femem(fqu?Nmq>C70p](6ms"m#.
-5sdk(5s\TU8hrq,3CQM##6+W,rVHQes8VZVrs&<!pAajdrU]perVcZoqu?Kqo_\Ucrql^"p\=Ub
-s8Drpr;lipdej@Er;Z`"pA+^errN-!rVlKiJ,~>
-kl3=(rVuo[s8P-ts!R+($N1\=kna!j'CY](q%NJk*;gN0kR.=krpBs^rse2a$NKu#rr`&dYPeA!
-!W2kRrVc`rrVZNns7H-e#laktnbr:Zqtp?l"oJ,mqu-KkrrDllrrD`Zrt@t,='8U-='8R?*,f;?
-MLC)$?6&kB"8r3!q!A"aq#C$co)8.T!;lZn&cMUuq=s[UoC;GIqYKdTpA=a_q9o$Eq=XR\poO&Z
-qu-Hms8DTiJ,~>
-l2NsBr;Zcrs7*<[s7QHjquZluq!e^krrE)e"TJK%nHARhs8N>o&G5i&s8N-"q@!,rs8Vicr;Zf*
-q"=@SrV;$E"98;R"o/-##i>CVqu6`sr;?Km&H;V)lM1,@oCD#0nE9B/o&0N<%ajk9m5$+8-6FQB
-,ShQcqW&LWiW'Z($j7%B)\E)EkPP)Nr9XFUrql]^q>:!f!VcKirrDTPrs8Q&qWn1\q>]dYrrDuq
-rltKCr;Zcs]D_d.rpg#=~>
-l2NsBr;Zcrs7*<[s7QHjquZluq!e^krrE)e"TJK%nHARhs8N>o&G5i&s8N-"q@!,rs8Vicr;Zf*
-q"=@SrV;3JrVd<*rr:pg!sAf9"pkG9)!Lqu!<2ip!<2lq%IsJuqZ$Tgr;ZNkr;Z9crsdres8>;D
-2aT_u.mu9]q#:ck#VnV:92e,L1^j?PrVm)hs7ZKjs6fmarVZ`oqu6Wq!VcKirrDWhrseu-rr;ut
-rr;utrr;usq#(KnrqYs]s82i]r;Qcpp\ufDs8N&ts8N&ts8N&ts8N&ts8N&ts8N&ts8N&ts8N&t
-s8N&ts8N&ts8N&trVm#t[f61'r;Zcqo`'F~>
-l2NsBr;Zcrs7*<[s7QHjquZluq!e^krrE)e"TJK%nHARhs8N>o&G5i&s8N-"q@!,rs8Vicr;Zf*
-q"=@SrV;0Irr)ir$gf,U+=8]e+r:n=nc&OjrVQQlrVluuqu-KlrrDfdrrE#srrDKdrs;Li7T`c"
-6sWSlp\tU?<,I>KQ@*sW*<5R-rrVckqYpKpnG`FcquH`lrtbA)qtg0alM18RrVQQjrVQQjrVQQj
-rV-=%q>9gDo^hMGjnelOrVcTer@@pHs8Durs8Durs8Durs8Durs8Durs8Durs8Durs8Durs8Dur
-s8Durs8Dor#6">&q"O[ar;Qisqu-3fJ,~>
-kl3jEs7lW`s"_XprtYCr(\&79p_EE#+lWG6lPolq&cW7.jW4@$s7@?!rsIui#lal(m)ulG\@8'X
-^U:8PYe#[ss0_j76FF/'U:(%C*Yf>&(F^R/Z_FJ(ZaI-FZE^U<ZaI-GZE^U9_iLt&TWt8tWMu=:
-,oJcc'gEZd.&*]9Wgp9(Xfeh#qQ1U=V?SRa$ig86$R>_U_6:/LXi[QQYeR<]q7$F5qQq!H[^`]C
-Z`h'KZEpjCZEpjCZEpjCZMLp-Z4O:MU8tc7[\p72WkPa@Xg5G7YPYR[ZE^^?ZE^^?ZE^^?ZE^^?
-ZE^^?ZE^^?ZE^^?ZE^^?ZE^^?ZE^^?ZE^^?ZE^[5Zc8m@rr`8ur;Q6dJ,~>
-kl3jEs7lW`s"_XprtYCr(\&79p_EE#+lWG6lPolq&cW7.jW4@$s7@?!rsIui#lal(m)ulG\@8'X
-^U:8PYe#[srNR*AZ*CU7_ZMtd5X@Uo6okRhX0/\4Z2Ls1Za-mArNcI0+3^.c]s>Pa`P0'i5WUMo
-1Fc?Y4[$9g[^`<La1AmpY55^I\Y]d'4@M:r68^t4Z,!HJZDkg>[B.!>`3fBK!4)I-'t1NZZ_t+3
-]X"iJZE^^?ZE^^?ZE^_4YlM$-Yn+FBYJ.oj[C<EA]s4fF[_):?1:"6lZa-pCZa-pCZa-pCZa-pC
-Za-pCZa-pCZa-pCZa-pCZa-pCZa-pCZa-mFXgQ-Arr2fps8MZjJ,~>
-kl3jEs7lW`s"_XprtYCr(\&79p_EE#+lWG6lPolq&cW7.jW4@$s7@?!rsIui#lal(m)ulG\@8'X
-^U:8PYe#[s!3lI*6a3l)]=6?4?"n.sC0+P3(<j(hYct:7YdCdG['6^;Yd1XE\'q(lb.uEDeBcIV
-;+j,^6p"sH:/8mV`P]"#d)3f?\,*u[_QFebApefOH!OSeYe-[0XJj.9[]R->_6O!Ds/lF)Y-7],
-'st<TZ)+\+]!/EEYd1L=Yd1L=Yd1M4ZN%9G[^WfX]>Le]`5oj)\?;^>YH=t8](`Kf['I'E['I'E
-['I'E['I'E['I'E['I'E['I'E['I'E['I'E['I'E['I'E['[EK[)8I0qYU0is8)fpp&BO~>
-l2O!HqYU<mrO#K%s7nsOX>p5BT;jsPpb8bD5lWU1\h3qMUouWT6_UD6s7JCXUc/8Gs8W#lrVZN'
-p\"I`s7lNErtGD3rr;utrr;utrr;utrqlWns8Moq"oeQ%r;6KPs7?6crtYG#n,OIE#R1_N*u=qH
-nG)nYrquZhrqQKirqZTh+7_9=2^1"".4?K%i:6<enF?#3nE]N-o_&4Up\t!frqcNnrVuTlrVllr
-rr3)ss8W#cs8W&rrrDo]rt58.p\XXSp[.MFqu?QmrquNfqu"e>"T8,qqn2n-"9/5rrpg#=~>
-l2O!HqYU<mrO#K%s7nsOX>p5BT;jsPpb8bD5lWU1\h3qMUouWT6_UD4rq/:XVE"VLs8W&ns8W)4
-qtL*is7lNUrXJo,s8N&ts8N&ts8N&tlMph^nc/Lco)A^hnc/Ufs8Dus%fcA)"[GC;5t4(.3=#T`
-n,Emq69%Lk68CV`3(``As5s=\%Jg&%s82ims7Q0eqZ$Tjr=o;6"Uk\K#R:\;s82irqYpBjo_na_
-qu6Hl#6+Z%s8Mrlrr)rsq!\7\rWiK#s8;coqYpKo"T/2us80k:"oePu\GlI's8W)js*t~>
-l2O!HqYU<mrO#K%s7nsOX>p5BT;jsPpb8bD5lWU1\h3qMUouWT6_UD5s7\U_V)SACqtp0\r;Z`/
-qY'mfs7lHSrser,rVuirrVuirrVuirnbrFd$2so(qtU$bs8W&rrs8T'rVuirrVuins76-Ws8Doo
-rs9o^EH->RBkLEfm/IAb6W--E5Y4L9=oSF$pA"Xkrr;`hs8;Tj(^Cd"2)dWM,9-scp%7tLqtp?a
-rVH0`qYL3drq6?erqcWks8DuorVllpnc/XfoD]$orV?Bks89k9"SfD$qYU0i!r`#pp&BO~>
-l2Le`p&=tNqDQUus7=eL+3OT)%OAj`s25osa8`h!'Z0je+;D`n^Dn<>oDciH&&J8Cs8VHbqXF6W
-q"+CSrr2otr9F:arVuoqpAYj+r;QWnr;QWnr;QWnr;QN`o^i+Tqu6lms7>mSqu6-c!rW)oqu6Zo
-rVm)sr:p*br8dYNs8)fqrr3l8qtKR[&J,6N!!*B@!9`J7o(D84nE00+.K(Y7.P!&$0^SJrp[7hN
-q>'p`qt^-bqt^-bqt^-drpg!prqlK_o()GIqu4S7rVl`m!<2rsq#8V>#6+T$qmc\*rr<#tnGe"~>
-l2Le`p&=tMqDQUus7=eL+3OT)%OAj`s25osa8`h!'Z0je+;D`n^Dn66mJb0F'?:(Ms8VKds82i%
-s8;olrr3*"pA=deqZ-E^r!*-!r;Q`ks8N!$s8N&ts8MHd$haPps7uTiq>^6ip%JFbrr)cuqu6?h
-o_najr;QHirWiDts82`ns6'1Ws8Mrp%fZM.#!#(@845a37gT.lrr3/qq>^<ks8>(f5!;%k4?iT@
-p&FgErrE&gs8Drbrs&K&rr;utrh'2orr;l)s8Dfo!<2TiJ,~>
-l2Le`p&=tTqDQUus7=eL+3OT)%OAj`s25osa8`h!'Z0je+;D`n^Dn9;nc-fR'Z0_;o^gu6q=Xcj
-s7u]erVcWlpAFmgrrDuerso#&rVQ?drVHKirVQQjrUKdarsSJrq>KdKmI1#Lqt0mf#lFStrVQQj
-rVQKj"ShibqXFI[!r2W`rqZZnrVZZqme$PZrr)j',$f8;K7\Q"GVJjk#$"Jp:/">Sr^m.i!<(@G
-s8Dups8Dorq>L<iqYc]Z"T,V*qu-Bk!<)QiJ,~>
-kPkh^q"=[[s8)ckrr2uirr3?&s8W#gs82irrpTjd#6"Q$s7QE[rr3r1q=j[^pAapfs6B@Ll`K^G
-p$hkPs8N&urr;l`rs&H!p&G'kqWe(br:Tgas7Q'`s8VclrrVrfr;-HhrVZZspAa^[s8W'2k5P8W
-nG`%Yr;QWnr;QTgp\F^cr$_C.m/S(2#64`+#m11Z0IJ7t/MI5M+<g(9lKdd'md]l.p\=L[o_e<3
-s3UcLrr;l)s8D9`J,~>
-kPkh^q"=[[s8)ckrr2uirr3?&s8W#gs82irrpTjd)#aI6s7QE[rr)feqYL-iq>^0gs7--hrk\U6%
-/U#!s8)]loDA%Kqtfm\#QO_uo`+siq<%\`r;-6gp\4[_qY^'es8;ckr;I,qrpKdbrVl]nr;HZ\r
-r3&ls7c'`(Ae%<84#p984lN9#k^VF2)RK^(HOK7q#:Tgs8W)uq>^2?s3UcLrr;l)s8D9`J,~>
-kPkh^q"=[[s8)ckrr2uirr3?&s8W#gs82irrpTjd#6"Q$s7QE[rr3?#rr;rqo(M>>p?MYW!5ea8
-$iBtus82irq>^6fs76'nqYg9co)8Oap\FdYqu6U'qXsg`oB>E0qYg*`rr;oms82ims8;or!rr8u
-rVI#lqs4.VqYU'bq>:3Zrr3&os8)3a'I[m?JV&f=Li>6O@STHe;d2"X<`f(r!;ZTn!r`/oJcF*s
-"oeQ!\,ZEms*t~>
-l2Lk_rVucnrr`5ks8Dor$3'\ts7lWop]'j`rt+_us8M`fr;Zfrr;Zfqr;??grrDT`!"/Mcrr)Qj
-o`+sdqt'Rfq>C9mpAb$gkl21hp](9hn,E@erpT[_s7c?arr`8uqtpBg(%hM)nc&OSrq69ip&=ab
-rqHHes8MNertGA2rVuirq=<tDo(;VJo'PQ>!"Su.*<cQh!%[=.p%.qMo'u50n*]W4n*oo>p%\.F
-r;?5=s3CWJrr;l)s8D9`J,~>
-l2Lk_rVucnrr`5ks8Dor$3'\ts7lWop]'j`rt+_us8M`fr;ZfppAP!is8Vrorrhur#6bP8#Rh4P
-"97fhqXaXQr;?9Yrs&K&s7ZKirV?Horr)Hd#6"AkrVccmrVm0"s8DoiqZ$Qoo`$$-s8VZis6]j[
-s8Vfmq>UEfs7H?gnGiOdrVZWlrVZWgrsf;j9fbj?7m0]Y6hp]YqY^?noCr7fqXOUaon!.grrrE%
-qmZV(li2J~>
-l2Lk_rVucnrr`5ks8Dor$3'\ts7lWop]'j`rsJ;os8M`fr;Zfqq#(.-p\Xg`qt:C5)Bfn1',_Gk
-s763iqZ$Ejs7-'frr_upqu,aY"8VKWo`"mjqYpL&o^VJEp@S"NpA"O`q>V9/s8V`ks6p!_s8Vlo
-qu?]dp?VGCk4\NFo)S^_rsC8_Jp!!4KQM]!?gRdpp&G'bJcF$q"oeQ!\,ZEms*t~>
-kl1h]s7H?dpAP"+nGiO_s7lWorr;rms8;okqu?Wps82cp3;NURs7u]oqYpHnp[e@Z!$!+*(D%K$
-(]a-qnGN:Xr:KRUo^_YFo^_YFo^_YFo^_YIpA4^_r;-3d!<2He$i^2"o(2bUoCDVTrqZTorr2`m
-q#C$sq=s[Uq=j[Oq=s^Vq=ss]!:Kd_!;63d0_d">,pt)i.46AG!$MCH!!*HA(BEmrr;Q*\jnJK)
-p\":Rp\4IXq>9aOs8Vurs8ITLd/O:Ks80;*rTjK6~>
-kl1h]s7H?dpAP"+nGiO_s7lWorr;rms8;okqu?Wps82cp+o209rq6<kqu?WpoC;h[!\ll-5rq1i
-1)']^o`+sas7c6Vrrr?"s8Dusp\t6loDB-ts8VZhs8W&ts8Dutrpp'`rsSf'r;HQkrVccrrVc`q
-$MON"rr)lsqu$<]r;Qcmq>UOX4$#G%3>OY=-5'9A8PDZF6pCnUrVulds6K^bl1b2_p@/+^qYU9j
-JcF*s"oeQ!\,ZEms*t~>
-kl1h]s7H?dpAP"+nGiO_s7lWorr;rms8;okqu?Wps82cp,PhB<rUg-irVucnnFHVZ(JnXZBjb=K
-9.']=rVuoos8N&urpfpGrr`5nk4\WN!qt^MrVHQk!<)Ee!;lTl!W)?arrDiirtA%&:J48M8P;uK
-21KRsMM?P"@j:mO"o&&ks8Vifrs/Ajs8VlhqtksEd/O:Ks80;*rTjK6~>
-kPlUqs8DWjlKnQGs8W&mq=t!irr;rms8;okqu?Wps82cp$2OW"s7ZKlqYpKns!.11!$`6`#nA'm
-'EHeXq"aLVo_%kKq"ag_qt^-bqt^-bqt^3f"o.ueqtoaEq\eSrs7bpNp%J+Pna6)NroWkDo(;VL
-q"agdqu7K(b4YDkkOnK:me-5;nFcAAmd93!lQdtU-3*?5nF?MM)Y3=Z!"L%M"9K>_*WZ!7rqcZp
-p@nL_n,3"WrVQWmJcEgk"oeQ!\,ZEms*t~>
-kPlUqs8DWjlKnQGs8W&mq=t!irr;rms8;okqu?Wps82cp$2OW"s7ZKmr;Z`prXAN#"[>((4$QD%
-1_]6TrrDr]rrrB$q>^Kbl2M:_s8MZfs8Mlkrr;lfrr;H\s8N!.rVufJs8Vcbs8W#ps8Vflr;Qos
-q>WE<q)S3L(D/Z)3CQ2'83@1Xs8)Bes7cBis7$'erVQNmrrrE"rr2clJcF*s"oeQ!\,ZEms*t~>
-kPlUqs8DWjlKnQGs8W&mq=t!irr;rms8;okqu?Wps82cp$Mj`#rUg-iqu?QlrVm0QDK9fOMj&I"
-48o0_q#0s`qu#j]"9&9"p$)JYo)JFPrr<#srr3)hpAadXs8MusrrD$WrrE&grrH&$qG[Gmp\tdX
->^U74F)l)!)uos8o`+shrr2ukrVulrs8;oq#5e5mqY'a`JcF-t"oeQ!\,ZEms*t~>
-l2Le_rr2rtrVZ[)oD/FcrqHBkq#CBgs7?3h.e3H:rq?0cs8W&mrr2lqs8;ons82?e)&<tl!"',N
-!:B@Eq"=+KnEKK7oCWUena6,HoD&.Vq=sd\q=ssbo(rOes6g*d!%A'>s82K[o(;SIp\=LYqY^9i
-rVH3XiUZI.kOS3/l1,`K1+!e_)CR'Yq!0'imdK`=pA"FVoBcMu'-.Z'(CDntr;Z*_s7?9jlMpn^
-q#:Nrq"OggrIP!prrrE%qmZV(li2J~>
-l2Le_rr2rtrVZ[)oD/FcrqHBkq#CBgs7?3h$M"&orq?0cs8W&mr;RN,rq?<ir<GAM8jG6u3BIBD
-s7cQnqtpC+rr;utrr;utrr;utrr2cfs8VrVrsS)r"U>YK$k`dK#3ts_s8Moos8N)hrr3N's8Vrk
-s!gB&2_@-I5XIL(s8W)qr=oGs2b65)4t9,+s8M'WqX=F`lMgh\rVuiq#Q+>go)AX]rdk*rrrrE%
-qmZV(li2J~>
-l2Le_rr2rtrVZ[)oD/FcrqHBkq#CBgs7?3h'(Po"rq?0cs8W&krVulsrV-9brr39ZDh4+:MiE.-
-0`:qQp](3gn,ECbir9Jdj968>-6+'R*Yek>rU0[crqQL"2c*:?6rm&d?;p7k'cC"QCM@Zn6X('#
-rS[JFm/R+Ps8Dcmr;lforqcurp@%GGq"":[JcF-t"oeQ!\,ZEms*t~>
-kl2"gs763ilMpeOs7Q9h$N'l'r:Bscs8W)err4>Drr<#ks6fpdr:0UYqu?Qns7l!^)A)rP$j@7b
-!$NjE3'/S]l0@g+&FfAepuD/=o(2YNq"ORXq"aI[&+oo%!Ytq@!!!'(('=O9o`"mjrY#/)q"=:M
-naZ,:nF?&d.P2i",Q9:r+nGX##5%?Vp$256q=Og`qu6EkpAbEqo`+mis7ZHl!<)Nh!W2hHs3L]K
-rr;l)s8D9`J,~>
-kl2"gs763ilMpeOs7Q9h$N'l'r:Bscs8W)err33$rr<#ks6fmd!;6?g(]X4-rqlTu6:3b':dIN@
-$9("(5!q.+qX"4bmf31]rosFolhgVe5<_:l3Bo\l$2sbdrp]pk-osLH68SU)$Sh\\rr<#rs8Vlo
-p@S@brr2j4rr3!(&-N:D"rIRLr9sUUrr2Kgs8;Zirqc]nrVHTmrVlil!<.QLd/O:Ks80;*rTjK6~>
-kl2"gs763ilMpeOs7Q9h$N'l'r:Bscs8W)err33$rr<#ks6fmd#57oiq>0[\rr3K]Fb"muOaVOr
-.o/l.8kqV9rrD]arrDc`mh4FEoFG5ACh@?sBjD8An)4'@rUTsl2+L8':I+nP!'^>\"oSE#q>^3^
-rrN)qrq[W5rs',X'd"D8//&Kjlh9W;q!\1Yq=OIVq"XUYrq?EgrVH]gp&"]=s3^iMrr;l)s8D9`
-J,~>
-kPnc_s8Vo\4:*)*bo7P9s2b`r_"RfH*?3'!]d4B0rkfim_uIIk*Q[UF`>B6.s8W&nnGjCA!<<-)
-&/P?6p%e+Ppa@UI+=JWf-n6Vp-n6Vp-n6Vp.jlf&2"C/3pC?urrr;utrr;utrr;utrr)j)q#Cj6
-!"'&5%LiF6rVuos/,];>pFe-M.k3"s.Om"Bme-5@o^MJFo^hYHp[n@Rq#($[qYU-dqYU-drUp0s
-p@J%GmG7F)s8Moq!;6'c#QFc&s8)]oo7?qgrrrE%qmZV(li2J~>
-kPmX?s8Vo\4:*)*bo7P9s2b`r_"RfH*?3'!]d4B0rkfim_uIIk*QdgNaVki2rr)`orrtb\4[;J'
-6Tt\QruC\1s"cZ'1d!l^4?GYe4?GYe4?GYe4>SfW55Y9@s8W'-p]Nl_4'#BD<C$c_rr2`nr]L*A
-s#LAar;Zfos8;`n!r`/nrr2ump](9ms8Doo)#+".qYL6hs7$'as7cKhr;Q`ro_e^drr;lp#Q=]$
-s7uTmnq$hfrrrE%qmZV(li2J~>
-kPmX?s8Vo\4:*)*bo7P9s2b`r_"RfH*?3'!]d4B0rkfim_uIIk+3jEWar(]&q"ajfs!;N4H$t6g
-Eb-Kmrte+!>YS3t<E)st<E)st<E)st<DYnC7S!.O%fQ2!q"jd^q"jd^q"jd^rqHus&8@&EOH=RB
-BG1%6q#1'h#t7?U92&)U9LhPCrs&>snbE"YnbDq`qtg*]o`"Fbo^M\VrVQU)r;6$Vo(;bTqsa@T
-pA4X^qZd#rrV-0en:CVdrrrE%qmZV(li2J~>
-l2NC4s8)9cs&Qi5s8G<VXu?;ASuFdNq_YOO49$h,[5.bEVmS;W6)1;3rq&1WVE4_V/bnf8&f(uh
-!"T_e!:om^rquZgp%7qSrr;utrr;utrr;utrr;okp[R`01+FaL00q0=-7C2h-7C2e1Fja?mca00
-j7**P!WrK4!!!K/mk5b0-7C8k.P*1Gq"t!grr;upq"a^\q"a^\q"a^\q"agdjSoD_q==(KqtpEn
-rq$0irr2fqrdk*srs&H$s8B;#qWn03~>
-l2NC4s8)9cs&Qi5s8G<VXu?;ASuFdNq_YOO49$h,[5.bEVmS;W6)1;4rqAO_V`4PR#n&ge8PCm*
-4@954rrr;r5!1YXq)SI9*B?/@3]T5Z7m&d2s7u]ppAYml8Ou9G6TRdDs$AL=48q;*s8W',rVlcp
-rVlcprVlcprVc`qrVZQprVlfls8N#ps8Muus8ITL`;^&?rr;r'qYKOXJ,~>
-l2NpCs8)9cs&Qi5s8G<VXu?;ASuFdNq_YOO49$h,[5.bEVmS;W6)1A;s8G6lU+l62q#::6=)iSF
-GCFRM,PV3KrV66aqY9p^qY9p^qY9pcrVlsi76Nd064?:X;H$Il;H$Ii?WL#"rtI&$It)ctGB.gK
-s%u-Y9MA&M7n#eurrr;pq"F@Orp^-_pA"O`qu6ZprqHfrrVuorqtTs`qu-JEs2Y-DrVliqZhjOa
-s*t~>
-kl40Ms7c3b'(,_lrs%rW*W?cJp([,u,38b6n.kul(&ng5hA?1os6L]irW_Qb#ljr$o(2o'*<Hf_
-+rha+n+QYVr;69_o_/4Srt52&o()P;p\=LXp\=LXp\=FNq<]Qmj6[j>.N]]f,6.iH"onW9$3gJK
-lg*s+nF?&>o_%nNq>:'erVZQaq#L<^r;Z`hrsSf'q!mhFq#(0lrVZZj"T/,ss8DoorVQWjrV-Eh
-qgn[nqum#qs80@ks*t~>
-kl3jDs7c3b'(,_lrs%rW*W?cJp([,u,38b6n.kul(&ng5hA?1os6LZhrX%fg#lXc#q>Ugi9fGI(
-90G<=q"jses8;rtn,<Ffqu?-PrtPD*s"Ql)2E"5h#=:sU6oA+I6jGF?rri?"rVZW^quHWcqZ$Eo
-r;6Ekqu?Nkqu?QorVlfsrVZTlrUp*brIOmoqum#qs80@ks*t~>
-kl3dBs7c3b'(,_lrs%rW*W?cJp([,u,38b6n.kul(&ng5hA?1os6_*'rsn>m!:fOGnc'2"A6Etl
-H"L2!rr<#r!<2rs!;#gK!;$0h!;PjZ&Q3(F<D[.",BJHjHu>(-FX'?Irri?!qYL-Krrr>qq"OIS
-rUU0bp\=U_r;R3%p\4IZr;ZcoqY0a\rVZWnqYpQpJc*so"TJ8tqmktkJ,~>
-kl3F6nc/Ld###Msrs\r)!<3&nmg/sm!<;KirrE)d#5/3"rsJ<+oD\pmrW)ll$MaZ$r878L!X.N^
-rVlotr:'U`q%*8pn+lVAme6>Ip%J(Pp%A1R%/9>ep[RqNn+ZAE!"o21(]aU:#QXMSo_JF_qtp3a
-pA"CTo^q_Fp&+F]!WN#gqZQ`iq"aabqu$BlqZZfjo_&"WrVllqrqQTmrr2rrrr)cnqu?Hmqu20H
-dJjFLqt@Q"qY0@VJ,~>
-kl3sEnc/Ld###Msrs\r)!<3&nmg/sm!<;KirrE)d#5/3"rsJ<+oD\ghrW3&r$i'`$s6L+"%hB$V
-$3peKlMpk^rVcWorU9b<rqQNlo(W+^rVlcprVlcprVlcprVlips8Vrqs7u]nrt/'u7R^3I6UqI_
-li$_Ys8@NKJcGNF#6"Gm\GuF"m/MS~>
-kl3gAnc/Ld###Msrs\r)!<3&nmg/sm!<;KirrE)d#5/3"rsJ<+oD]'qrs&N""mk^Jo\p,b(`O2)
-'+PHdm,e6MrVuoor9""i.t`V5LQmaLH6`Ias8W)t^]4?2!<)fps8.BI_#FW;qt@Q"qY0@VJ,~>
-l2O!Ds82`opBT3n!<3&\r"B#Es616iq[r8m%/^b4mMl!9i9V*V!;lQtq#:iend>Eis80jCR@9nB
-Xe)/qV8\u&[0*eBZ*El(#G86-Vmre2_"b5hZ*U^AZ*U^<Y.UdEVR3_0Yakb'!!E?'!<E0#Y,oON
-]=PS`\$iWGXfSP%Vl-W&o!S%p!jJc*rMojuqlTk!!O/p0[0a1DY-"h1ZaI3Ir367.r3?7*!O8t^
-[*c5`Z+\>RrTaE5~>
-l2O<Ms82`opBT3n!<3&\r"B#Es616iq[r8m%/^b4mMl!9i9V*V!;lKmoDT9cp'guos8C9ZXgZ'U
-`3Z\fY0*9@Za-j?Yd(L?o!J^u[CNBPYdDCE\0/>l\[]2[\[/`\ZG!EO]WelB"@GOK83oa:2$UOr
-aL8MU[f<f>\@&cS\,Nl;\+-g*[IUd'[fX"I\,3T9\$i`QrNuR3s0hs8rj;U2!4;O/J[Ee2"L5Y`
-T`+0UJ,~>
-l2P,ds82`opBT3n!<3&\r"B#Es616iq[r8m%/^b4mMl!9i9V*V!;lU!q>V/tpBgWZoC'Q,X1?-U
-^9"?LWQ(C8\@o\ra2uB:\\#Da\\#Da\\#Da\\#DN\\55`[(+6R]_T/V[^a8_^p(Jd[_K!`EI3Cl
-I"HlW*Pf;4\,NcD\%0,`]=knm^VIXu[e$g)Yk,%"[(F-Q^B;0a]tOEWrOr6G!P,Z<Z3dnJ]=khe
-['6dCrO;j9qmcX9!P#Rh[*c5`Z+\>RrTaE5~>
-l2NF7s8Mfnn5ZX7*</=(Y!DeCViasYqEgaN56"35Yt+ahU9$iO<3)Wcs79$cS1aU9rs[TYp%7pW
-o_\O`o)AXQrs/Q$q>C$cq=a[\,5Cj%o^_YFo^_bDn+H)@pZhDDo*HKT*#^+/*$t[]mc4!6rqQKg
-s8LdQ!VuEeo`"O_p\ssfq#UBlr;Qosq=sd`rVufp!<;inJcF*s#5S2k[f?:)m/MS~>
-l2NF7s8Mfnn5ZX7*</=(Y!DeCViasYqEgaN56"35Yt+ahU9$iO<3)QXr:*UdTeu`Irs%3Wrr<#*
-r;Q]rpAFpnrr<#trposmrVuors8;oqqYL-jrqQL7r;6Nis8Vlos8!Kt6UO%/01\Y@s8)9arVZTl
-r;?Nhs7-'grU]perpKgcrqZQordk*ars&;spU:,"rp9Z8~>
-l2NdAs8Mfnn5ZX7*</=(Y!DeCViasYqEgaN56"35Yt+ahU9$iO<3)TZrUa'pUbqi@p@Zl2rVuo+
-rr2otp\Y!jrTO7]rVHNorqZHss7c9fp&G'err3BR@VB:YBi&Y\)?9a9p\t<nqtpBhnb_nP!;l0`
-s8)fpqYpTnqLS[^rs&;spU:,"rp9Z8~>
-kPm1,s8)cD*u:1Bb8))/s2bm*[djC8,o+l-]0$Y8s03sm^]2I[--#ub_@$dkqu-Nos82iq$G$36
-r;Zcjlhp\[li.1cq#C!ds8)Tl!WMokp^6Teq!@eEp\=LJs7cR%!U':Mq"aa_qu$Hmg&D-Qq>'p_
-s7llrr;?Qnr;QTn"T8)kqu20H])MiAs8;3_J,~>
-kPm^;s8)cD*u:1Bb8))/s2bm*[djC8,o+l-]0$Y8s03sm^]2I[+hdgO_[mO,s8W)us7c?hs0qq#
-qZ$Tjn+m"Sr!<9#qY:*_s8VrcrrrB$s7cQirr3JurW!'*!s\o7$NpUos8DTirr2rt*WH*<s8N&t
-s8N&ts8N#qr;6Ehr;6Ehr;6Ehr;6H]s8W'"rVlfms8W&rrrE%Ls2+d;\GuKms*t~>
-kPn$Ds8)cD*u:1Bb8))/s2bm*[djC8,o+l-]0$Y8s03sm^]2I[+Lq1C`=j'7s8;]hqY'jes181(
-rVuoonGiIaqY9^TpAOO]#5\,po`+sfq#C3h(&.\*nbr+Xs8Vur#8nNq'G)-+)%5[&rtkY2qY'XT
-o_/.YrVQQjrVQQjrVQQjrVQ0^!;l3a"8hrkrVQTsrVHBhrVuTiJcF!p!kA:.li2J~>
-l2Ltarr<#ns8)`p"nqQfqZ$*ars8B!s7$'`pAaa^rr_WfrUKgcs8)upl0\KBq#13no=Ou$#6"Js
-q>'pcl2Lb[mJdFfpAasgs8DcmrsJPnq>0j]nG*%`q;;2\rr;utrr;utrr;utroa:`rqlTjJcEF`
-#5e5qppC"sli2J~>
-l2Ltarr<#ns8)`p"nqQfqZ$*ars8B!s7$'`pAaa^ru:>)rUKperUp3hs8VZ^s75aYo_SFKYl4P"
-rr)$[!<2ut!;Z?gq#1Woo)A=]rqZ6bnGN:c$2s`#rr;fkn,E:`rq6:"rr;utrr;utrr;utroO1Z
-rW)oqrWN2trVlforr`8ur;Q]qp&9OBdJjFJq>U/rrVPp\J,~>
-l2Ltarr<#ns8)`p"nqQfqZ$*ars8B!s7$'`pAaa^ru:>)rUKpbo]u;Ks8Voks7Q$aqZ$TcXRu5]
-qu-Hm"oJ/ko_/.PqZ-Qnr;cTcrV6C"p[eFYs8VrfqWR_MrrVlbmJ?k_rVl]lqZ$L%s8Durs8Dur
-s8DurnGa='qY9p`[email protected]"Xa`rrr;pq"t$gr;6Njr:g<hrIP!srs&ArrqNl!
-qs494~>
-l2M1fs6fpequ?Ths8N&srr3f1s8N&pr;Zfrs8Dutqu6Kjs8Dups8Vflrrr>toD/:\qYpQ,rVm,r
-r;6<equ$HmrtbV3rqucnrqucnrqucnrr2lrs7lWorTaC_rVZ[#q!n1[s7c-an,NFeo)Aair:Bdc
-rosFbrqucpJcE=]"8o_0rp0T7~>
-l2M1fs6fpequ?Ths8N&srr4SGs8N&pr;Zfrs8Dutqu6Kjs8Dups8Vcjq>^Bmqu?]orr)Whp%nQh
-rV?Hlp?2Ger;Z]hrr;forr;utrr;usrVHElq>L4)qt0g^r9jLYq"k!hq"k$gn,2qXrr)iqf`1mK
-rVulrrVc`prquirr;Zfrs8;uts7?5@s3CWHr42k,li2J~>
-l2M1fs6fpequ?Ths8N&srr4#7s8N&pr;Zfrs8Dutqu6Kjs8Dups8VW]nG)k[rqucrrVca"X7PiU
-qt0gd"o\>pq"X^Vq[`K!qt'd`q"t$erVQQjrVcQl!;ufq')qY(p\*\?rVlfrs8)cqo]>f>r;@!"
-rVQQjrVQQjrVb^T!r2K_rqQNirqHoqo^qbIqu-Egq"jmdr;Qrtq"Xa`rVZQmqY^*hqYc!Fci4+F
-\c;Zps*t~>
-q>Uj"r;ZEhq#1-jqu?$^rsAT$s8Drqs7lBbrr3&os8Drs&,c2%q>^Enp\Y!Xs8DWjrr)j'rVQWl
-s8VlgrqcHj!k/..rr;lpr;R3)s8N&ts8N&ts8N&tgA_0PrVllsh#HsEkPkP]JcE=]"oeQ!\,ZEm
-s*t~>
-q>Uj"r;ZEhq#1-jqu?$^rsAT$s8Drqs7lBbrr3&os8Drs&,c2%q>^Enp\Y!Xs8DWjrr)j+rVQTg
-s8Vois82irrr2oq"9,V*qVqM_rr;utrr;utrr;utli-_[q#1<orlkE@rWN9#s8W)ns8W)urrE%L
-s24j?rr;l)s8D9`J,~>
-q>Uj"r;ZEhq#1-jqu?$^rsAT$s8Drqs7lBbrr3&os8Drs&,c2%q>^Enp\Y!Xs8DWjrr)j'rV??_
-rVucks8Dcn"L7jurVcTmlhq4krVuirrVuirrVuibrr`5sqYU*g"8hrlrVZ[$rVuirrVuirrSRVV
-rVQKfrVZNnrVufoqu?Tm!<;]iJcF*s"oeQ!\,ZEms*t~>
-p\tj!s7H-es6p!Xs7#a^r:^-iqY^?or:g-h#4hcnnbiFYrr3,mq#C3frVm;ss8V<_q#9jas7cQe
-q>UN&s8.BIJcDMF"oeQ!\,ZEms*t~>
-p\tj!s7H-es6p!Xs7#a^r:^-iqY^?or:g-h#4hcnnbiFYrr3,mq#C3frVm;ss8V<_q#9jas7cQe
-q>UN&s8.BIJcDMF"oeQ!\,ZEms*t~>
-p\tj!s7H-es6p!Xs7#a^r:^-iqY^?or:g-h#4hcnnbiFYrr3,mq#C3frVm;ss8V<_q#9jas7cQe
-q>UN&s8.BIJcDMF"oeQ!\,ZEms*t~>
-q>Ud!oDeRbs8MEcp&+h'o)JO]s8DQhp%eC\s82<cq>^'arr3E&r:0gas8V`kp&G'Rrr3<"qu?Zq
-s7u]pqt^6nZiBoRs+13FrrrE%qmZV(li2J~>
-q>Ud!oDeRbs8MEcp&+h'o)JO]s8DQhp%eC\s82<cq>^'arr3E&r:0gas8V`kp&G'Rrr3<"qu?Zq
-s7u]pqt^6nZiBoRs+13FrrrE%qmZV(li2J~>
-q>Ud!oDeRbs8MEcp&+h'o)JO]s8DQhp%eC\s82<cq>^'arr3E&r:0gas8V`kp&G'Rrr3<"qu?Zq
-s7u]pqt^6nZiBoRs+13FrrrE%qmZV(li2J~>
-pAZ!*s80]#%KAEas8MBbqZ$Hmo`+seq#CBgrr32pn,N+]q"4Rcs6^O"mf3=_s763ip%/4Vn,N1Z
-qtTpc!jhq(JcC<$U]1Mss80;*rTjK6~>
-pAZ!*s80]#%KAEas8MBbqZ$Hmo`+seq#CBgrr32pn,N+]q"4Rcs6^O"mf3=_s763ip%/4Vn,N1Z
-qtTpc!jhq(JcC<$U]1Mss80;*rTjK6~>
-pAZ!*s80]#%KAEas8MBbqZ$Hmo`+seq#CBgrr32pn,N+]q"4Rcs6^O"mf3=_s763ip%/4Vn,N1Z
-qtTpc!jhq(JcC<$U]1Mss80;*rTjK6~>
-q#;'+s8Vrq-)ptB!<;lks8Dutnb2eTrW"8Jm^`ZEbl>X"*5V[TV]62ks/d[j_Yq7g'Y"+^*$`N(
-s1g$'`q]B0!jhq(JcC<$U]1Mss80;*rTjK6~>
-q#;'+s8Vrq-)ptB!<;lks8Dutnb2eTrW"8Jm^`ZEbl>X"*5V[TV]62ks/d[j_Yq7g'Y"+^*$`N(
-s1g$'`q]B0!jhq(JcC<$U]1Mss80;*rTjK6~>
-q#;'+s8Vrq-)ptB!<;lks8Dutnb2eTrW"8Jm^`ZEbl>X"*5V[TV]62ks/d[j_Yq7g'Y"+^*$`N(
-s1g$'`q]B0!jhq(JcC<$U]1Mss80;*rTjK6~>
-q>UftoDe4XrtEc[h\Q4k#lO8cr;ZZorrE&u,[email protected](0R&3@>s%fD^7JK$ASj!*LSubE]2P6^9
-nG?%ORnWVW!jhq(JcC<$U]1Mss80;*rTjK6~>
-q>UftoDe4XrtEc[h\Q4k#lO8cr;ZZorrE&u,[email protected](0R&3@>s%fD^7JK$ASj!*LSubE]2P6^9
-nG?%ORnWVW!jhq(JcC<$U]1Mss80;*rTjK6~>
-q>UftoDe4XrtEc[h\Q4k#lO8cr;ZZorrE&u,[email protected](0R&3@>s%fD^7JK$ASj!*LSubE]2P6^9
-nG?%ORnWVW!jhq(JcC<$U]1Mss80;*rTjK6~>
-p\tX"s8)E0*WRLunGN+]s"4!Fs8Morqu?0_*o?E;n.b-X$2=H,na?nd#5.clq#^NX"oeT&lP/jg
-#j_Ehq#:E%s8.BIJcDMF"oeQ!\,ZEms*t~>
-p\tX"s8)E0*WRLunGN+]s"4!Fs8Morqu?0_*o?E;n.b-X$2=H,na?nd#5.clq#^NX"oeT&lP/jg
-#j_Ehq#:E%s8.BIJcDMF"oeQ!\,ZEms*t~>
-p\tX"s8)E0*WRLunGN+]s"4!Fs8Morqu?0_*o?E;n.b-X$2=H,na?nd#5.clq#^NX"oeT&lP/jg
-#j_Ehq#:E%s8.BIJcDMF"oeQ!\,ZEms*t~>
-q>W\Yr:U*es8VinoDejgrr;cns7--_rtYnZWrE(nrrE'!s82lsq#UNp!WEGprs8W3m/I(W'));)
-s7cTooG.2trrTP,qgncus.fStrr;l)s8D9`J,~>
-q>W\Yr:U*es8VinoDejgrr;cns7--_rtYnZWrE(nrrE'!s82lsq#UNp!WEGprs8W3m/I(W'));)
-s7cTooG.2trrTP,qgncus.fStrr;l)s8D9`J,~>
-q>W\Yr:U*es8VinoDejgrr;cns7--_rtYnZWrE(nrrE'!s82lsq#UNp!WEGprs8W3m/I(W'));)
-s7cTooG.2trrTP,qgncus.fStrr;l)s8D9`J,~>
-q#:Kqs7--Zrr5I^s82irq#C<hs7lWor;X5>rZ1Cl&-!:)p(%-#''\imrYjqn$iCP+k6q:soC3In
-rsAK!#lX]$n]7o?\$;aOZF%*NY.&tfJ[4gO#Ip\<X1#I?[DB-QUrTd=]Dqiqs*t~>
-q#:Kqs7--Zrr5I^s82irq#C<hs7lWor;X5>rZ1Cl&-!:)p(%-#''\imrYjqn$iCP+k6q:soC3In
-rsAK!#lX]$n]7o?\$;aOZF%*NY.&tfJ[4gO#Ip\<X1#I?[DB-QUrTd=]Dqiqs*t~>
-q#:Kqs7--Zrr5I^s82irq#C<hs7lWor;X5>rZ1Cl&-!:)p(%-#''\imrYjqn$iCP+k6q:soC3In
-rsAK!#lX]$n]7o?\$;aOZF%*NY.&tfJ[4gO#Ip\<X1#I?[DB-QUrTd=]Dqiqs*t~>
-q#:Baq#::aloYFO!<<&ns7u]hs7lEili47%q+Qs]3r_OBW&XYL;5p`^nP#LQ7fPf@]Kl*]SZ=aM
-mmsI?:&jnds7l6bs8Tk0o_eahq18Qss7$'grVum!p&FQrrrqE^]CYpnm/MS~>
-q#:Baq#::aloYFO!<<&ns7u]hs7lEili47%q+Qs]3r_OBW&XYL;5p`^nP#LQ7fPf@]Kl*]SZ=aM
-mmsI?:&jnds7l6bs8Tk0o_eahq18Qss7$'grVum!p&FQrrrqE^]CYpnm/MS~>
-q#:Baq#::aloYFO!<<&ns7u]hs7lEili47%q+Qs]3r_OBW&XYL;5p`^nP#LQ7fPf@]Kl*]SZ=aM
-mmsI?:&jnds7l6bs8Tk0o_eahq18Qss7$'grVum!p&FQrrrqE^]CYpnm/MS~>
-q#:d#rqufr!6bE:s8W#rrr4Y<qZ$T[3WKf0s0+!fbOige1V3Vd[Ls,%s1CJp`;d5)"j?qd$R43p
-s1oTq`VB?-rs8P*q>^Kos8)_GqgnY7qZlQhs6TdWs6%5q#6+Z&qR?J$li2J~>
-q#:d#rqufr!6bE:s8W#rrr4Y<qZ$T[3WKf0s0+!fbOige1V3Vd[Ls,%s1CJp`;d5)"j?qd$R43p
-s1oTq`VB?-rs8P*q>^Kos8)_GqgnY7qZlQhs6TdWs6%5q#6+Z&qR?J$li2J~>
-q#:d#rqufr!6bE:s8W#rrr4Y<qZ$T[3WKf0s0+!fbOige1V3Vd[Ls,%s1CJp`;d5)"j?qd$R43p
-s1oTq`VB?-rs8P*q>^Kos8)_GqgnY7qZlQhs6TdWs6%5q#6+Z&qR?J$li2J~>
-q>Vc1s8VZirJ.0Hs6]jdrV$3_q#CBms6K^bo'QJWq=jphrTjI_rVlg=p\k*[s8W)up&G$krVuK`
-s7Q9ds8N&uqtg9krVcc*rr3&qs8ITLJcG3=!;lcq!Ufp$rs&5tqOd`dq<\-3~>
-q>Vc1s8VZirJ.0Hs6]jdrV$3_q#CBms6K^bo'QJWq=jphrTjI_rVlg=p\k*[s8W)up&G$krVuK`
-s7Q9ds8N&uqtg9krVcc*rr3&qs8ITLJcG3=!;lcq!Ufp$rs&5tqOd`dq<\-3~>
-q>Vc1s8VZirJ.0Hs6]jdrV$3_q#CBms6K^bo'QJWq=jphrTjI_rVlg=p\k*[s8W)up&G$krVuK`
-s7Q9ds8N&uqtg9krVcc*rr3&qs8ITLJcG3=!;lcq!Ufp$rs&5tqOd`dq<\-3~>
-q#;H6o)JLT#QOi-#Pn5ks82irq!\7Ys8Vins8N&uo`+Xart4\hs8;oso)Jacs7c9ap&F[[qu6lm
-s82irrqlWn#Hn%(p%&.\r.4iurpfsms7$'Ys8Vlf_#=Q<mf10"p%dtSJ,~>
-q#;H6o)JLT#QOi-#Pn5ks82irq!\7Ys8Vins8N&uo`+Xart4\hs8;oso)Jacs7c9ap&F[[qu6lm
-s82irrqlWn#Hn%(p%&.\r.4iurpfsms7$'Ys8Vlf_#=Q<mf10"p%dtSJ,~>
-q#;H6o)JLT#QOi-#Pn5ks82irq!\7Ys8Vins8N&uo`+Xart4\hs8;oso)Jacs7c9ap&F[[qu6lm
-s82irrqlWn#Hn%(p%&.\r.4iurpfsms7$'Ys8Vlf_#=Q<mf10"p%dtSJ,~>
-q#:Ees7cHk!:'L^s7?9j&,lOus8)coq>^Hks8N&nnc&Olq#C$es7QBk!;HKm$2=K"r:]g`p]('e
-rs&K&s82Qa[=S@/s6'C`r;ZW.rs&>is6m#gq<S'2~>
-q#:Ees7cHk!:'L^s7?9j&,lOus8)coq>^Hks8N&nnc&Olq#C$es7QBk!;HKm$2=K"r:]g`p]('e
-rs&K&s82Qa[=S@/s6'C`r;ZW.rs&>is6m#gq<S'2~>
-q#:Ees7cHk!:'L^s7?9j&,lOus8)coq>^Hks8N&nnc&Olq#C$es7QBk!;HKm$2=K"r:]g`p]('e
-rs&K&s82Qa[=S@/s6'C`r;ZW.rs&>is6m#gq<S'2~>
-q>V3(s7--bo`+FYs8VTgs8)cos8;coqYpL%r:p<bo)JaUs8VckrrW#ro`"jnp@/+Mo)AXirql]p
-#P@olrr2rkqtpBuZMjh'q>Ks\JcC<$nc&g^s7ZKm!;+#*"SD]os7bjZJ,~>
-q>V3(s7--bo`+FYs8VTgs8)cos8;coqYpL%r:p<bo)JaUs8VckrrW#ro`"jnp@/+Mo)AXirql]p
-#P@olrr2rkqtpBuZMjh'q>Ks\JcC<$nc&g^s7ZKm!;+#*"SD]os7bjZJ,~>
-q>V3(s7--bo`+FYs8VTgs8)cos8;coqYpL%r:p<bo)JaUs8VckrrW#ro`"jnp@/+Mo)AXirql]p
-#P@olrr2rkqtpBuZMjh'q>Ks\JcC<$nc&g^s7ZKm!;+#*"SD]os7bjZJ,~>
-q>Up&s8Vlis2-8h-b]Q[qYgF&qYg9jo`+sbs7ZEkj8T)Yr>P_2s8DusqZ$Nos8;oslMpbXs7lW\
-s8Vrqp&=q#s82Wjs8TS-s8MciqY^?2rr`8urr14C!<2lq"8r&nr9XFcrqu]ndJj:Iqu$Bls8;lr
-!WN#crW3&uj8T2[qu6Tp#6+8pru%7F_#FT9s8O10+TDBCqt^-gnc++~>
-q>Up&s8Vlis2-8h-b]Q[qYgF&qYg9jo`+sbs7ZEkj8T)Yr>P_2s8DusqZ$Nos8;oslMpbXs7lTW
-rr2cop\k*uqtU*hr3Q>$s82cprVl0`!<2rsrVlZn%K?D,s8N&ts8N&ts8N#rr;ciorrE&srSdbG
-rrW,qrVQTprr)ffrmh&Crr2p"rquZkrr3'!rVb+C!r`#orVm6'rr;Zimf7>-oDHN+#laVspAeq.
-p@S7^s8MZjJ,~>
-q>Up&s8Vlis2-8h-b]Q[qYgF&qYg9jo`+sbs7ZEkj8T)Yr>P_2s8DusqZ$Nos8;oslMpbXs7lWY
-s8VrqpAOprq=a[`rNuP's8)Qk!<)ln$iTu$qu-Ejqu-EjqtU-Lrr`/pqYT%I!<)Nd!<)0^r;HTo
-ir/9ErVaS4#lX8fl2YZ$m/+["%JBA[!*fNoo)8UcqYL0]s*t~>
-q>UNos5EtW0,_.lW#bp<nGi:`rV-?lr;ZQhs8Vopq>C6lqt^6krp]sfpAa^\s8VfmrVuKho_ndq
-rr<#smelkXrqud%_>jQ4o^qPFr;?Tprlb<DqtU!WdJs4F!W;rqrrrDro(;\UrVlrss8Dor!;?9h
-"oe>jp&"aarrrAss8Vurp\k?drr)imr;HWert4\knc/V4s8V?Yrr<#onb2eTrVQZkp\P!iqu-Hr
-s8)cqqs==sq<.JOr;ZT_s"hs<eNEm$rqZQbpA"X5rsV6Hf%!:js75XAq>TsVs*t~>
-q>UNos5EtW3#T*uW#bp<nGi:`rV-?lr;ZQhs8Vopq>C6lqt^6krp]sfpAa^\s8VfmrVuKho`+mc
-qt9pfs7?0g"oSAuqmHA#rs&H%s8DimrU0^cr;ccpr<3,urr2lqrYPV6rVcZmr;HQlr;HQlr;HQj
-qY9g[qu6Tp#PnArp@n@Qr8[bUrq$-lrq?![qu7<+oBlACr:p3dqtoj[q>:'erVlEg"TJ8ts8;Tj
-!;-9j!WE&srr2irr;HR0r;6EirUU!cq"=^Yrq-6ho(i=brr)iprrE#js8Dp"s7uZnqX=D"qt0IZ
-qYpH`r;ZB`!+JB,!;H$`qZ$3^q#Ab@&H2>'>t7Wim.gV\rVuoerVlKiJ,~>
-q>UNos5EtW3Z5="W#bp<nGi:`rV-?lr;ZQhs8Vopq>C6lqt^6krp]sfpAa^\s8VfmrVuKho`+pi
-s7lTlr9sOXrqcioqtp<#rquors8;co')VCpp@e:Tq"FLVq"FLVq"FLXrVHNj!<(sX"8)'NlMUY^
-r8R_WrV6!X!;l]os8Dor"T/5qs8;iq!;HKm#lFJnq"jshs8Droqt^Kko_SFXqY0ahlhL5Lo_AFU
-rs8W(qY'[ap]($brrE&rrrE&os8DrprrE&`rtYG2q#CBis8(jB!*D6S!;,sarr;cjao;nMo\fd,
-+#!]Yp&4LEnbW.Ss*t~>
-q>V$$oDeCUr>)[4aTVY>rr2`lrr5+Sp[/"-!rp(X*Q.ok+Y:A)s3(lm])Tl"$GHJN)AU^#_B0rJ
-r;X>?)U@sOq>'scqu?]&s8Vf\o_&"Wqu6ZqbPqeDq"=Oar;5"Ds8N#qrVm'!o^MDCkl1SgpAajd
-rVulss82]n"TJ2eo_eRc(]474qu?]qs8MurrVlcprVHQor;Z6`s8Drqrri2ts8W)trt+o'o`"kC
-R5"[8q"t*kqt0dbrW2omq#19krqQTlr8[eWqtpBt.C\('hVKWsrt"]Og>N#2aYj+gnauhWs7Z*b
-J,~>
-q>V$$oDeCUr>)[4aTVY>rr2`lrr4tOp[/"-!rp(X*Q.ok+Y:A)s3(lm])Tl"$GHJN)AU^#_B0f;
-n,0O()UnQ_s8MunpA"N\pAOmcrVm#uqYL-hmf3(]rVccqnbiXgqY9m`rr)j#rq,XJqYT:NrVcit
-rqHEprqH*^r;RGtr;$-Oqt0m^q=jgcp&"O]r;HWfrtbJ2s8)`prVuiqrVlcprVlWms8Dueqtg?m
-rY>5(qtg*`rVZ0as8Vcm9*"nis7u?^qu6]pqsj[nrr)clqXO@TlhC2Gq$$HPmJm4crq6<k"oqOc
-5s&TWrqZWcrqcHcde=(ChZ.4M8Q8=_qYgErp\XCWp&BO~>
-q>V$$oDeCUr>)[4aTVY>rr2`lrr5"Pp[/"-!rp(X*Q.ok+Y:A)s3(lm])Tl"$GHJN)AU^#_B0iB
-pA_Q2(sV[Lq"XUWoCi$Yq"j[SrVHNrqXjFTmeZq[!<(sX"7tmCo@s9Tqtg3dqtg3dqtg3dqtg3f
-rri;tqu?0brrr)qp](-iqu6ftqY9j_rVulqrVHinqu-6drVQQhq$6TiqtU'Sp\jpf&c;P,rqu`p
-s8N&soB60E!;-3`rrN,tpAb'hs82fl"S2?_mIp,C&bG5RoCVbIo^D&%!&c5Q&jHBqoDn7WoZH_7
-n^IP"#o5*T!;cQ`m-X<5s*t~>
-p\tNsq>^9"0)up+rr4kSs8Vfjs8;osoKrWQ9T-)NoKT4>7K<3NWiA>Z4.<9KqFmTh4mPS;RlCBI
-6F!.Dqu$6frr)is^&J$6p&+^^q>Us&q>0p`q>0p`q>0p`q>1*ds8)`ms8;rsqu7<.rVlcprVlcp
-rVlcprVlcnr;HWo$N0bjrq6<kr;?Qmir8rWs8Muqr;7c8r;[email protected]:Rq#:9jpuD2;
-rr2rtrqc?[p&=Xa!WMohrrDWerrDroq#C-kr;QQkrtG,+s6K\MfuV5Rs7H9is7Q0es8;Qi"oe/a
-o_8=FrtU]@S!:=ORO>\\s8VlX_Cq.>WKX3Nq4uH9%%f5Ian,2gd]F5^rnHrBJ,~>
-p\tNsq>^9"0)up+rr4eQs8Vfjs8;osoKrWQ9T-)NoKT4>7K<3NWiA>Z4.<9KqFmTh4mPS;R4eF7
-4KtG?s8W&rrt58/Z2=M!q#C<fqtg*_qYL-go)ARerr2rr!;ufm,5V38rr)iprr)iprr)iprr)ir
-s8Dilr;Zfjs7?3fr;Zfrir&iQ!r;]hrVH]pqu$?jquZ`mrql^!p&4@Zs82`o"7-!Wrr2frrVlfr
-!;lWg#Pe5or;??]p\=UflLk&Irqc]nr;Q`ps8N)rrVmT%o_n@Y!+nW+!<;ujoBH&Ms7c-Zrqlcj
-qYL9lr;R0(qtTmKmdB,tl/h=(mhkERh.g/4>]F%h!+\8o8jZQl!*<<><bZ"<B@:B(BF8?L<'EEF
-@0$9+h>R3Eq#0mcJ,~>
-p\tNsq>^9"0)up+rr4kSs8Vfjs8;osoKrWQ9T-)NoKT4>7K<3NWiA>Z4.<9KqFmTh4mPS;RPFjA
-5-CG:r;HBfqu-HuZi0dsnb`1ZrVm#tq"4:Yo)A^erqZQjs8Dip!WDoeq#^9\o`"jurUT@8o^r1`
-rq,jYj8JEG%K6+rpA"Obr;ZWns7Q?hrs/8tr;Zfqqtoj^!<)Zl!;63gqYpNp!<)lr'_V7pdEqqa
-/P6$.mI9c+gZ\G/pA4dg!;cQks83E(q=jXTnEfMsiT&SFh::*Je/6Z^`()^`6X2oG!&4p5)$:gF
-!'<>?3_`&c9+Fl#=>23=*_^&DrU\+tp@A66~>
-p\tBks8V]brr3;sl2U2<s8V`ks8MpNJT;(p#5Rfortaep$1.[!lg>#X!<;3^rrr&S&,uq#q]GY2
-$hO9'rqcKhrquK]pTX5g!<2Tf"T/#iq>'scs8;orn,ERjq=sjbrqufrq?6]fp%eU@s8Dp"s8N#q
-qu-?gq[W/lm.gAHrUf%Cp%A(Pr;HWsrqlWfrrDucrs&>so`+pjrr)j#o)&Ieq"O^d"ZX]]s4]Tm
-rs#*`Y`QMjV=UJaWk,k=rru-=p%mV!6G`[.-EH#!iP.#Fs7Yp]J,~>
-p\tBks8V]brr3;sl2U2<s8V`ks8MpOJT;(p#5Rfortaep$1.[!lg>#X!<;3^rrr&S&,uq#q&Aqu
-#OhNss8;fos8MfdpT45jrr<!%rVlcorr)iqc2[hCrVluqqZ$QQrs/Q%rVZWlrVZTl!rW#qpAYEl
-s7QElr;ZHOrVlrss8W&s"98B!r;6?nqY^'arVQZjqY1!c"nqTWnEg/Nrtk8's8Vferr2cjnGuKK
-<_*5gr:0=Ms8W&l!V#RQpC[6!rr)clq=jUSnn)'B8TRsBo4.f.*,P<Dn*f*"p%8;Z9L1X7!:]IH
-lgXB1n*of8n*n]m&b52f6W@8a;ulXio`+mXq=jj\s*t~>
-p\tBks8V]brr3;sl2U2<s8V`ks8MpGJT;(p#5Rfortaep$1.[!lg>#X!<;3^rrr&S&,uq#qAf2&
-#k%KorVQEir<i5ior\)fq>'mar;HWtrVQHgiVsPfq"jd^q"jd^q"jd^q"jXLn,*+a$MsDeqYpNp
-p?V,BdJj7BpAX[mq#16mr;ZEfq!n4Tl2(D[rSRW"rVuWlrVuirrVuieq>^Efp&FpalGrrH%5K:2
-h!!e\hp^$3rp9aKjnJ0Amg\[NlKIBjiDiHS2e#0Go1'B]85[gbdFcOhc,BZ)+VFbj!<1FLcdU@i
-b0pjUjo?e_2DIl*s8V<MmHX9BJ,~>
-q#:?\r;QfmqYpLgrr<#bq#CB^q#@ZVp&t'rs6'mX!<<'+s8N*!rW)s'qu6]s!rr9#o*,*m!WW06
-s69X_qu?ZhpA=a_\ao1`rVuWjq"OFQp%\C\r:Bmhr;-3fpC$ZdoC;>>mdBK0nbVkV%K#nlo_%tT
-qu$HhrqksYpu)#CqZm&rqtTdSp\Xph&c;M#q>C9ks8N&uiq2s3q>1*grrN,qqY:*i"nM<_o`"jc
-rs&E$s5a4[r9jRi38<]ts7OMnq>UW=VQ-r"QMREbVlQkuVkg#WR>Qk"rrDo_rrDlkrrta&i8$d#
-oYLP5;7<p\2#mLNrr2p!q>1*`s*t~>
-q#:?\r;QfmqYpLSrr<#bq#CB^q#@ZVp&t'rs6'mX!<<'+s8N*!rW)s'qu6]s!rr9#o*,*m!<<'1
-r8n"Uq>^KkqYpL+_u96(r;ZKirVlisrr)clq=F@^q>:-gs8Mfn!;uHb!;uir$2sbtqtp?frr;Nf
-rrE&Vr:g'jqYU0grr3#uqu6BmqZ$Qos8W!0pAY*Urr;cnr;-6ap\4CVqu$?hr;Zd$oC`%Ss8N#o
-r=&H!rSRSPp\jp`q"a^aq>C./rVZQckQ#6V7lNS.!:]aMmHF65b_9S@E+N#F@hE0W?=%#J@Us(X
-AG5fdqrmqQoCsI$h=:"'q"!eAm.p,N6!.[uo\TE?p$);?p\=LXp\=LWde4"Aqt17n6>-AjoD\di
-rW2imo`'F~>
-q#:?\r;QfmqYpLSrr<#bq#CB^q#@ZVp&t'rs6'mX!<<'+s8N*!rW)s'qu6]s!rr9#o*,*m!<)j,
-rT=1Xr;Zfmqu6U%_YN]op\OFTp@nL\!;l-_!<)lrqu%0)q>:!bq>:!bq>:!bq>9gJkk>#U$N9et
-qu?]gqX!P?i;4#_qu-Ejqu-Ejqu-Ejp[J1J"7>UMq>L=)qt01?lhLA>s8Vrqq=jUWqt0pgpAt9f
-p\uoGs8V3\s8N&np%S.Rp%S.Uq"ja]q"j[OfDmf5'+5U3!9*.qcagp8Wae@b:.$f55n?=R9MS>Z
-;,^Ij<9NQ)m+Usr(Y\6:j7Dg0n_W?]!&$T(!9)>mmHNEniQCHrnb_eU/fdOaoD&@Zn*]l>s*t~>
-q#<MSs6fpY!<<)_6hgQZq>^Kbp](9`7*kK;rrD3O+9)fCgB7?Q$1I-rjUgtB)Y4F-jWX=@g[bdP
-o+LHg-3!o[m)6-6Yb7/nZ+%?VYe%-CX/W2(ZF.15\c9/=[f<`CZa-g>YHG%0XK/M3o=$B][Bd$@
-ZF.6S^8J3?Wj&S'[C*HN[^EQO[^EQO[^EQO[^EQO[^EQO[^EQO[^ERB[K!]5\,<cj[B-I9]Yh_+
-^7r0>SAW1]*R)$qb,r.HZ*h-T^;.S$]sY)MZF.-M\uWibbKRQ=`VI@V^Vmq?Xi8,tbl#ccbT=mM
-dF5dtI#0Pac27P@qp;o)D47&oX-U*?j5]+[lh][ekje97mc`]de]cL[ZbX#E[^ETS\%&oW\%&oW
-\%&oW][OR+\?*<b\\5_n0#+Hh.DEC"Z+mZR^p^YZ[C3KO[^`iX\@K,[\@K,[\@K,[\@K,[\@K,[
-\@K,[\@K,[\@K,[\@K,[\@K,[\@K,[[^r`S5-R0Lrs8Pcqu?<fp\"IWs*t~>
-q#=Fms6fpY!<<)_6hgQZq>^Kbp](9`7*kK;rrD3O+9)fCgB7?Q$1I-rjUgtB)Y4F-jWX=@g[bjR
-o+C6^+T;6;lc?NI]!%sX]=YP[Xh20W]tM%h\$i^7[/[Q6[f<i:\,s4P])K<+]!o,U[^EQO[^EQO
-[^EQO\%92^\$i]O[()j7[B[9LXgkjK\$icS\$icS\$icS\$icS\$icS\$icS\$icSqmZL3r3[]X
-]tM"bZ*1@9TY7\)^9aa<[\oqCZb`cT\$`TKZ*:F:[JmZ7[L'@9]WeuYVm<M+qPaatX/E^sXJi8$
-r2K[q*iZ6IZ_)VQFUMhW68qJ%:i$,+F`M\L>&ob"U7e<^qP/J3S!f_8StVpVVRNh0]XXrOYHG"1
-Xfee/Xfee/Xfee,[ALUPV5C;b['ZP-B1uV3WOAq2\Zi9MYd(F;Yd(F:YHP+4Y-5"3Y-5"3Y-5"3
-Y-5"3Y-5"3Y-5"3Y-5"3Y-5"3Y-5"3Y-5"3Y-5"4\u(MgA,u8ms7?9jp$r'4~>
-q#<MSs6fpY!<<)_6hgQZq>^Kbp](9`7*kK;rrD3O+9)fCgB7?Q$1I-rjUgtB)Y4F-jWX=@g[baM
-nIOp\,l[fWnB&,N\ZDRN]"5>UW3`\2Z*LaFrONBK]">Pc]">Pc]">QM]D]>>\0JGl[^NQO['m?M
-['m?M['m?MYGA&&]u%Us[^rEM_7-eDQa,PY!4;@'*jMcGWiN8-\@\lb];)s9RK0=ZS].kLX1>UC
-Z*LgLrkJKH%D0-Y['?1.Vm*7iUnOIXU'RBeTX]uXTqnCXTH9\uW2Pr#<Ghe.,ngG&)`(Lp6=X.o
-8iBXiKS4r3N;A6WLPh+ROcu&tS=?UXT<kYlZE^[@Z*U^AZ*U^AZ*U^AZ+[<S_QC5ZXgbU.!+n\p
-!2d61Y._*H]sP)PZF$pEZF$pEZF$pEZF$pEZF$pEZF$pEZF$pEZF$pEZF$pEZF$pEZF$pEZF$pE
-ZF$pE[)/Va!($\LnGi+Tn`BK8s*t~>
-q>Uirr;Zff%1iL>$E3aus"F3Js7lWj!W`H1!(a0)0DJ#0XBG,o8"m"Gs$``h2ZH:AY<;hDX0"\[
-r_Su\9)/Dc#kRHXp%5NWn+ck]#lFAenauVSrquBb!<2Ng!<2Tc!;uirs8;lr"o\K#r;?*2s8Vch
-rrW2cq>UC$2PM;ns8Duhm.gMSrrE&]rrf7%WMc]oWW/psV?NluV3I:[rVm3I]]e\]gpnO,s8:jU
-!<)Bd&GH._nb`+]s82Bes!FE^s8Drss89q;&GGo!s![pIrq5UPrqH'Tkkk&QJ,~>
-q>Uirr;Zff%1iL>$E3aus"sQOs7lWj!W`H1!(a0)0DJ#0XBG,o8"m"Gs$``h2ZH:AY<;hDX05"g
-s%o#V6h1*QrqH*brs$IBp\4CXp\Fghrpg!jrVQKiqtgBirqc`mr:'abrWrH!qsXFYrr2KfrZ2%<
-s8N&ts8N&ts8N&ts8N&ts8N&ts8N&ts8N&trVufps8Duqrqc]prr3Z-p%.kOqsj[apZ;Hc!;H'S
-s7l<ertYM0q=s^Zqu-KkqYBmZoCLu/nEB<-p[/7QmUU'E@:B.Cs'bq:(hIDl<`O[ooCMJPAlL]Y
-6"0imroNqJp\Fg`"7u'Xq"j^cp$r%N!qGmSrq6Kir:]g_rq^L-qYC!`qYC!`qYC!`q;qPCs8MN^
-q>L!_l2Y>pp[7qOqsj=Tq>1!bqtg3dqYC!`qYC!`qYC!`qYC!`qYC!`qYC!`qYC!`qYC!`qYC!`
-qYC!`qYC!`qYC!`r;Q]nl2gGLs8Vurs82fqr:L#>~>
-q>Uirr;Zff%1iL>$E3aus"F3Js7lWj!W`H1!(a0)0DJ#0XBG,o8"m"Gs$``h2ZH:AY<;hDX0+e\
-rD&]W9)/Dc!W)QirsQdFqY^6ipA4RZqYU3j%f6)!qtg3dqtg3dqtg3go`#X(s8DfhqY9p^qY9p^
-qY9p^qY9^Xr;Qfpn,<7gp\OU^`r?DEk5\`enaH#JqtL*i!<)Zl$iBYdjPS,+c,7Q@chPrndaLc`
-91MYO9c-Z):-UsW[GgK9!(/FQ-RD4^n+PZ+h;Ii&p]L-Xq"X^[!;6<^rUgEhp%\CUo(r4QrrVln
-q"t'rr;Z9eB`Rbt_>b#Em*5U]l2CYZp[mhDs8DTiJ,~>
-pAY?ks7u`qs6ose4n8RGs8;ojs8VZirrE)B((eIb_BBZ4s8'eS+M%Nl&KJsn_]Kc5p<=Nk]DMN;
-,TOl)qYpNoqtTmVU@nN_rtbG'p%\Fas8Monqu$?hqu$?hqu$?hrp]sZqZ-WprrE&srs'kMs82fb
-s8C(>!<)os"Y)ae^p5QVrs$$1XerV*WVNIoUopcbrt>>2.88OIc7Aq]q>^Kos8W&nr;Q]rrn.5d
-nabu6s8;omq=t!i)?9U6p\Opiq>^H9rt+htq>\50s7tpRoDS^\o()\Ns*t~>
-pAY?ks7u`qs6ose2=^_?s8;ojs8VZirrE)B((eIb_BBZ4s8'eS+M%Nl&KJsn_]Kc5p<=Nm_#OGI
-+rA&loD/C`rs,k0rqlHbo(W%]s8;Ee!;uck!;ZWj!;ZTi!;l<d!WDrqr@7^>pB9dYq#0XYoDS[e
-rr)iprr)iprr)iprr)iprr)iprr)iprr)iprr)lsrWW9"r;HQkrqdi9s8D]_mdBQ8n,)hN!!$G%
-?NBW]q"spdrVZNep@\(Mrq?Bb$M<o[n^r1uBO>[`pgOJ7BP$GkmHj0;m/?;OmelMsnbrLd!!#tg
-?3'oqs82Wbrr)]dq>C3hqu$EjrVZWlqtg<es7uZj#Q+5lr;6Egr:0Y"o`+p`r:'R_s7Q3[!;Z-^
-o_8=_pAOmbr;ccDq[WT(s76)irr<#ms7H0fq==Q9~>
-pAY?ks7u`qs6ose1\(M=s8;ojs8VZirrE)B((eIb_BBZ4s8'eS+M%Nl&KJsn_]Kc5p<=Nj])2H:
-,oao)qtg<mZMa_'q"F^^!;l`p&,l4spA"@VpA"@VpA"@VqtC'irU0O_qY^?qoBcP>rr3&qs75+J%
-f?5%rVQQjrVQQjrVQQjrqHNjrVZ[/qsj^e;EIYSjP]Lunal;>n+$&DrVI*$p@Ib>k2F6h8P;9C4
-\#6<#=M<[ccsqih"0>5jno#C!!#2=8c\#8rVuicqrdt\r;Q`qrp]pfrV?Hsr;ZWoo^'?ms6'cSk
-k>&Qs6oLMn+-L/~>
-q>UH_rr3#rr;?R,p\Y!_s8VurrVuKhs82Tfs7-'f)Z'C3p%n^_s7c6do_SU`s7cQnqZ$EkoDA@\
-rr2ulrr33#o(`%N]D;:&s8;*\!;l]o!<2uq!<2ut!rN#op&>'hr;Q]t/"IsbrrV`jqY:'orVuTj
-s7baW!;ufq!<2Wj#<Blgs7b3p9)S\kSY)UNrh^%$X0&G&Vkg#WOfmI=$hsGks8P'h0)th@p&=sk
-r:'acrr<#nrr;ofqZ[!!s7l?frVllsqu6Zoo(gZ0"TJH$`;fi9r;Qitqu69gJ,~>
-q>UH_rr3#rr;?R,p\Y!_s8VurrVuKhs82Tfs7-'f0DbVHp%n^_s7c6do_SU`s7cQnqZ$ElqZ$Qn
-rr)c`qY^?ipAY*g^\7Nto^VSLrVlg:rqlKcqu?WprVlfrrr;utrr;usq>'maqssX^rqcX"r;$6]
-qssXYqYMuErVlcmr:oj`@K?6%rq-3_rVcZmr;HQlr;-?_qYg$ar;HQlr;HQlr;HQlr;HQmq>Vf:
-s8)copAOm]na,K#jlu4)mdC-W:eX/M@K>ETjn&%YCM@HoAnCpOs(24B#\[Y!i:Z^8m.^&D"SMEZ
-p%A:Ws7cQgs7luur;Z`mjT&fkr;Q]tr;-?jrX8](rVlcprVlcprVlfprr2rrr;Q'_!<2rs$2jYs
-s8W)qq"am's8W!(ppC"us8MunqYU3]s*t~>
-q>UH_rr3#rr;?R,p\Y!_s8VurrVuKhs82Tfs7-'f)Z'C3p%n^_s7c6do_SU`s7cQnqZ$EjoDAC\
-rr2umrVm&qr;ZN.rVQTsqtTs`rVmE-q"a^\p\O[]q"jd^q"jmcrsJN#rVuipqtp6drU'UmqWlo:
-6icTMs7u]jpAY-lmJeU2oCVYHoCVYAoC2ADlh'T&g"G-8i8WeWf$FCK,97CC493.@`m`=+r^ISo
-92SDQ6UXI<:K1FrHJA&`qXOFUoCN(WqsaUkp@6u>>la*WptYlLrUTjcrV?HtrVH6ZnalOnqu6Tu
-qsUHSq>UBrrV6*_o)F4~>
-pAYumlZDFF(]_bZs8;ols8)]gs82ijp](!Urr3&ls7Q?j"oJ>orVucort>>*s7uZns7c']qu$?i
-r:p<lpAY*lrWDu,s7H0fs8M`l!;?0e''0)js8N&rqYBs]p\+I`s7H?gp&>$frr3-U=No1#0`M(P
-psT0[VLb87Xg5@>Vk93F/&Td%r;Ys!b=8,/M5K\?"8r2lroO1[rr;ltq<S%ZrrjDBs8;]imf*Ii
-qtTs^q>L3jr;HTqr;6*]"oSE"q"XjerrDuprrN&no(^Z/!W2forr^"8rVl0`J,~>
-pAYumlZDFF(]_bZs8;ols8)]gs82ijp](!Urr3&ls7Q?j"oJ>orVucors8Vus7uZns7c-as8W$%
-qXa[anbrIdrsAP0qrdbKn+lk[rr3B*qt^'br:g-eqYKRTr!E8uqVM2Gs8N&tqYq]9qW@YAqY'dZ
-p\+@Tp\+@Xl1t/>!!$A:<rgkJqrmnPqY1*Zo)ACco(_nJp]:6hrVQWk2Yle9q=s94>\[k[?X7#L
-BkqbiE)]:Z9MeGj!)@?*Dt<JhnF,i6md9H1n*ol<o_.VHqt0jZnGW:^s8;fns8N#t#4VZfp[eI[
-rVmN.q>'mcs8N&ts8N&ts8N&ts8M]krVl-_!ri)prr3'!r;FD1%fZA'ppL,"s8Dikq>'pdoDa=~>
-pAYumlZDFF(]_bZs8;ols8)]gs82ijp](!Urr3&ls7Q?j"oJ>orVucort>>*s7uZns7c$[qYg9j
-rVHQop\t0rrVQ8ps7-$e"9&#gqY^?trV6?iqtodWrVum)rSmkQrVZQgq=saap'(9ls7cQmpAY[%
-lML;4!!"o=2#tnprTX1Sp]9gRrqHQcq"=4Q47qq(lKINslKINhlKINfHV.429i"P^;c-7c!'VG7
-!#R"[email protected]\air2dhW=4kqqM,WoDeX_p&ORMnbCo>%K6(uq"jd^q"jd^q"jdb
-qZ$HmrVlisrC$PZq"41Mq"X^\qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^
-qY9p^qY9p^qY9p^qY9p^qY9pcrr_u!qYgEn"9&)kqXXZ:~>
-q>V3+s5j8W_#IB#]cR4Mrr<#nrVuosp&4mpmf3:\s8Voort"`!s5X.Equ?9[pAb0Ns8;iq)tj-s
-rr2corqH-ds8N&ds/c8#pA"1Qn+c\Rq>U6prr;uts8DWjs8E0!rpg$`q#:?nn,EVZf]i/&o?p>(
-rro6_Xe)tpn>-,\rMBOk"d-*aX/NQ&rsJZ's!k\Hs5_D)3pZeMq#C!YqtU!Xp]13bs8Dp$s8Dut
-rr;rjrtbV6rr;utrr;utrr;utrqlNeqtp<jrosFor:osXq>C6kq=sa\r;69ar5/I>r;6Nk[f#su
-r9aN7~>
-q>V3+s5j8W_#IB#]cR4Mrr<#nrVuosp&4mpmf3:\s8Voort"`!s5X.Equ?9[pAb0Ns8;iq(\dt!
-s8VrprUo^[rqZHVrhKJlp%nF_q>1*srV?$\qXOFRqYL!gqYpHn!qcNgq>VT8p%S4XqYL!ar;6Eh
-r;6<]naGiF>!b>39N2#Ylga!'qX+UWj^_8*?$0QGA--7MA,Tm:@3J<]=^tiYcL1;to'u8Aq#'jc
-quBu1;E@`S!:94Ho(MkSrV-Hgq"jsd#Q4Q!rqHHfp[/"Zrq??sq=sdNq==L_r;-TkoCVqErtYP2
-qtg-aqu-Qos8N&ts8N&ts8N&ti;Y_7s8N&ts8N&ts8N&ts8N&ts8N&ts8N&ts8N&ts8N&ts8N&t
-s8N&ts8N&ts8N&urr2f)s8N#t"o\AsrVlfgs*t~>
-q>V3+s5j8W_#IB#]cR4Mrr<#nrVuosp&4mpmf3:\s8Voort"`!s5X.Equ?9[pAb0Ns8;lr'`IV!
-mJ[(_s8Vris8DZaiUiT6s8Drs"oJ8loCVnXrr`9#s8DZk#lOPrq"j=OnbE"T#l+AnrVHQms82Zm
-&c;4jn)sa=*YT)54TO[2n_)Ojcj.t=7Pmk)=$lII&P5bq;Gg=h;GfS_92net^u=SUm3V,Wj4XJg
-2('/'6icB:naH&=n*TT6oChhDm-O-0oCVhSp&Fjcmf*:co(rggp]'jbq"j^Sn+6>Brs8Mnn+$#A
-pA4a_rs\l+rVuirrVuirrVuifs8W#tqu6OUs8Durs8Durs8Durs8Durs8Durs8Durs8Durs8Dur
-s8Durs8Durs8Durs8Durs8Durs8Durs8Durr;Qfp[J^%,rVQHgo)F4~>
-q#;Q2s8N)hl3R1K$N'l$s7u]os7lWop\4^Us8VBas8)cqnbN%]!VuBarrqWdq>0sfrr*,pr:9mf
-rq66i&(LXZq=O1<mI^2OrquZgq>UBorUp0lqt]gCrstKh/\C!,s8V`G[k3`/T;DC`!N33PrsJ\s
-p\+VB_T(HF/+`f:rWr8dr;?Hiq"X[Vp]^Kkrr;utqu.$%rqucqqZ$0erqQ*_q#Bm`ir98]q=sa\
-qu$Biq>^3iq8*(=q=j^XYkRhbp\=X`nGe"~>
-q#;Q2s8N)hl3R1K$N'l$s7u]os7lWop\4^Us8VBas8)cqnbN%]!VuBarrhQcq>1!ert5),oD\aa
-mJZt\qn`4+rVuorq#;!*qY9d[r;-<fr;6Ehr;6HjrV$6js83/rp%.bEnF,l;rqHTcq"OR[pC[)[
-BlF&W><#5J6WI4g@VBOjqI^%EDsZrVn)*O'mfDkCqWn.G$g-a>kjS9Cq=j^^rr2p-o_&8f4'5ql
-n+,T.qYU3fr;cimrrN#frVloqrSdbYnb_DErVm0%qtg0dqY'[^lMpn`s8Don!<)Kfn,Ejrs8N&t
-s8N&ts8N&ts3goFrrN)0q#:?noDa=~>
-q#;Q2s8N)hl3R1K$N'l$s7u]os7lWop\4^Us8VBas8)cqnbN%]!VuBartjo!q=s[[rVccms7cQn
-q!.YHna3RJnbi@c"o@iXmIU,NrrE&krr_u`kk+iFqX4IPlMg/Qk3_O&q<S[L@ql!94q\nY)EL=f
->[h#5qF(WV:!1`#c+V<kblQ;KpYcD6iSi\XpA=a`rV-iqnCuXs3>t+b!;$'WqX=Fbrq5XX!WDfa
-p&aOSkPkM]qZQWaoCi.OrsJYpn+#r>o_/(WrVHO&rVuirrVuirrVuirrU0\JrVuirrVuirrVuir
-rVuirrVuirrVuirrVuirrVuirrVuirrVuirrVuirrVuirrVuirrVuirrVuirrVQTp]DV[2rVQQl
-o)F4~>
-q>V6-s8DF;\oV`gdnTlBqu?]prr;lqs82fq-Mld&p\44Qp&Fmfs6BFRs7Q!`mJleFs7ZKmq=ssh
-jo>>>qt^-drVm9)s8O2@s82BKlL+EErsAW#m/HGPrp0RKqYom^"KA;i^6JJm%%s6\s8OCMaQVg!
-^*Lc!$iB\ghYdER1runm+TDE@rWr2rrr;uqqt^-^rrE#crr`2pqt^'b%K-,$s8VienaYo:p\4L^
-rrW)mr.G"[r:fgTn>u9Qo'u5=nb)_UoDa=~>
-q>V6-s8DF;\oV`gdnTlBqu?]prr;lqs82fq,5U@"p\44Qp&Fmfs6BFRs7Q!`mJleFs7ZKmq=ssg
-jT#8Br;R*%r;,^Pm/ZSPrUp0qnc/+Ys6]jQp\u!-rU9OSna5W(j5AhNiT'@cBP(Lt;u1,=C4kCB
-qZ'nm;GfMj!;QQanb;nR"7#XNmeunMo)[email protected]&Dp?q_Ss8Dlorr2p.n,N@Y!,,AB!:]d[
-qYg0err2lr!<)cl#Q+Apqt9g]r:9gUrrN)hq#:Epr;Q3c!WMuqq?-WmrVi)^!5%LqJ,~>
-q>V6-s8DF;\oV`gdnTlBqu?]prr;lqs82fq,5U@"p\44Qp&Fmfs6BFRs7Q!`mJleFs7ZKmq=smb
-hu3N:r;R*"q"!>+gAogko)8S/p?h#'kjA$;lhg)HrTO4CrVQQjrVQQioC),9jR)j5rp^9Xe'c60
-8jHfDqF_ArDU.Y3!'(u@*tCa?j6bjcq<S4AmH<R/mIKKBlgXcC#P%9Xq"FLPqYpL*rVQKjrVkpN
-m,7q@6<aH_eGfOGnb<OZq#16frr)`jq"X^_lMh_"qtg$YoC;;:mI0cDqY9p^qY9p^qY9p^qtg<e
-rq??drVAnVqY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^qY9p^
-qY9p^qY9p^qYL*gs8Th3s8Muds*t~>
-q#:]ls7s;3-g1M6g@kOG$1RupqXOL`li-e\rrW&rrr2p)o`+p\s7lW[s8V!TrrqWdqsXR]rr35j
-rql0ZjSAZMrs1-T9E5%iht6@#rrDlorsbNXVkKrdXg5:AZEL+3o)B*fs8O<I]tDH9s7#s[rs&H%
-s7lQmpuhYunc/Xas8DfjrVuosqY0XTiqi]T4dI&es763fpA=jgrr`8uqtp0gs8;ormf*=cq>L3h
-r;?Qnrsnedg[ah-p\Fggs8MT[qr[qYrW2rri;X5bs8N&ts8N&ts8N&td/OUTq=aRTZ2++fo'l):
-o_J(XJ,~>
-q#:]ls7s;3-g1M6g@kOG$1RupqXOL`li-e\rrW&rrr2p)o`+p\s7lW[s8V!Ts%_eXqsXR]s8N#a
-s8Vopmf34]oD87V!+Q0#qu6'ap\t'apA"L^rVQ?cnau_Tqtg3dqtg3dqtf4Fp[*!*D.-dW>$+j,
-=_D;flKdoin+uG_o(`7Zs7--k;GhE6rU^'gp\Y6fr;?$Ur9s.QrVQTl%JfthrVcHhr;ZfrrVlfp
-rs\5dp[J;i!;cQVrVlWjrr)j*rVZTlqu$?hqss=Mo_n[YquH]crs&2rs8VWhrr)lsrr)lgrWrK!
-q"aa^qu$EWs8W)urV-?lrkAC5rj_b'!ri/tp&BO~>
-q#:]ls7s;3-g1M6g@kOG$1RupqXOL`li-e\rrW&rrr2p)o`+p\s7lW[s8V!Ts"!=5qsXR]rVQKW
-rVuZmmJZkQlgO3(!(R%EmJ62Mo'tl"gu%/Um-X--jluO.#jq!*lK[AC<,)>?raH1E?!'^%cd:1R
-eG@W+gZS+kn_!mB-mqXfjPoh%oDA@`#5A/to(W1TkPkYTs8VuorrW2trr)j'kje0(!)N[cqX!SK
-s8)iqr9aOTr<E#nqsrkIq>C6qqtTgWqs47grVH6ZnaGl3n+cAKs8)cjrq?Bes8'P."oA#is8Th4
-s8Mucs*t~>
-q>UNhs7H9i48o3Zr;ZT_o)JX^qu?]R49,Ynp;8<i]CWlL(XN-Taql/>rP9I!`9tAU'ZC$g(E1<n
-s1&sW^%_@%q>UBt,F$?a74n!.%_8(0WiN/#WiN.g^S$gejSoVeq=t!i3OL=9rV6?js8)Ttm/HAN
-rq69inbi@arqm-"r:Bg[nbMJFq>:'h!qcEirVm-=s8VWhrp9X`lMgk[nG`Lfnc&dgs8MK_rr)j!
-q=XCSl2USYm/I@hq>($is8Mrq]DhlFrr3-#q"47Wnc++~>
-q>UNhs7H9i2uWdVr;ZT_o)JX^qu?]R49,Ynp;8<i]CWlL(XN-Taql/>rP9I!`9tAU'ZC$g(E'sb
-rjs3hbPqM^p&+[K!)Zic!!)]goCqb?kN;!on*fYjlfm]oE)ZRA'39Kg<GU^emIp>Oqu$ElrUK^Y
-nc&=anG;qmp[RnTs7uBV!,V`3rr;rkm.pMU#OVQYs8Vfms6BOfrr;fos8)cfrVllsrqmT2o`"jg
-qY'R^r;#UWs7$'crVZQjr;Q]crrDiarr<#rrr;osr;6@!pA=@Ys7u]pq=jX\r;Z<cqYp<jnbiRh
-rVZWgs8Ms*rr)iprr)iprr)iprm1NJs8MukZMF=qr;Qlsq"Xg\s*t~>
-q>UNhs7H9i=T/:"r;ZT_o)JX^qu?]R49,Ynp;8<i]CWlL(XN-Taql/>rP9I!`9tAU'ZC$g(Dja\
-rO<mcbQ%V>m.'Go!'WY!!!)`kq>0I9f@/4#gtpl'gtLN3@79rj#ZON?=%m)Wki)O+rp0gUn*9Q:
-m/HVWlh^5cp@S+Zs7>I.!*90dq#CBkn,3%]"n2K[s8VlVrrr5us7u]drVuorq>U`ro)S"7h"Ld>
-rr3-"qtg0dr;?`prVQQkqu6Nop@A1Mrr_liqXF@]"8;-LqTAj<rO`(4q"=Uc_#FB6qZ-T`s*t~>
-q#:]ps8W)hs7cNks7Q?j4T5<[p\b'Os1o-C7%0oCs$3QY55P:,Y*i&]33B#?s";*_2?,h:UJ1Rb
-6'S`3nb2t^rVn4@_;F#"XtBYQ%A0T(['-F$TUNQdX/`_srs&Apo^VSNq#:9onFuVU!U0F[rrjGC
-s82Whrr;fl"nMTco)/+IquH`qr;ZX#p%e1Rq!n+Oq>Bja"oJ?"r;Q]arrDrqrrW&krnIGRrVHHl
-"8MQaqsj[drqcNnrqu]nm/ICkqt^'crr2imr4Dt/_YsK9rqZH\s*t~>
-q#:]ps8W)hs7cNks7Q?j:]:=np\b'Os1o-C7%0oCs$3QY55P:,Y*i&]33B#?s";*_2?,h:U.G"V
-6'\uCqu?]ql1P*iA5P^";?61Nnmu6:?!h&QAl`tQ@:4$'lM9ZMlK@O!nFH;Hr;$?mp\t-mn+QSV
-&cD:qrU9dcmIUDQqu?]kp\+R]rrr,rqtC'akl1hcs82irqu$I*rqu]kr;?HjrVH0_r:L$hr;Q]r
-rqu]orV6Bmr;Q]sqtTX[!<2los8;lnrq['#q>9[Ys8W)sp\":Xr;ZBe!<2`mqu6Wo&cDV*rVZWl
-rVZWlrVZWmrr)firrE&drAOTPr;HQlr;HQlr;HQlr;HQlr;HQlr;HQlr;HQlr;HQlr;HQlr;HQl
-r;HQlrVZQcYk\"krr)j!qtKmap&BO~>
-q#:]ps8W)hs7cNks7Q?j:]:=np\b'Os1o-C7%0oCs$3QY55P:,Y*i&]33B#?s";*_2?,h:TLJGL
-6'o5Jr;QTehWOrA;E67#70)cAo4)!(;cHe%>=iEt<)[8Hh>5n8i8s(ck3VF#lL"!-n,DhYo`+jg
-rrMchq#:^"s8Vclqrd;GnbD_U"o&&pq>^<Trs/N&qZ$Tls8N#ts8Dcn#Q"Dmip?4+qu6U!rVH<_
-q"XU[!;?Eg!VZ-SqZ-T`rri)nqYL0frr`&bn,%_:$MsMqs1\O5rVQKjn,In~>
-q>V--rVud&!!*-$!s&8srr;rso`+jgs#BGS(&nd3oaUj8jn0>^rtOtp#lPb*miDB;l1Y5Y&Fnro
-rq[N%lPTNrlMpl8Y,g1Gj4)>O315TZrrN,srqc]pn,NFequ?]qrVtgT!<;lor;?ToXoAD#r;Zco
-!<2KfqYgNqroX7Zrr<#trk&11]);U.rp]r<~>
-q>V--rVud&!!*-$!s&8srr;rso`+jgs#BGS(&nd3oaUj8jn0>^rtOtp#lPb*miDB;l1Y5Y&Fo$"
-s7mDojV\!slMU2Q?qFO%6V1T^!,cEJp&t'^p\FXSq#:'lqYU3hrp0Rcrqu]mrr`2rqu$Ems82lr
-ci<Y:qZ$EkgAh$Krr)corVluur;HTls7?6\rtPJ4rr;utrr;utrr;utrr;utrr;ugs#^5[rr;ut
-rr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;utrr;us#5nJs
-[/Kt&r;Qlur;HWfs*t~>
-q>V--rVud&!!*-$!s&8srr;rso`+jgs$?(\(&nd3oaUj8jn0>^rtOtp#lPb*miDB;l1Y5Y&Fnoj
-q"5Emmiqf3kk=0>;`mT3,:l2F!+&jrn+$#Aq"jdZo(2qUqtU!`!;l-_"o\;mq"js&rrr;rq"jmd
-rVuor!;kXOmJm1aqYpZrqYL/BrsAW#s8KP.rr;rqmf.e~>
-q#="Xs7Xl<A]=TIrr2rtq>^?lrTsRVrs8\-nc8^i!<3'!rs&2s!;QQoqZ-Zr!;d!#q@*B(s8E)e
-rri<#!WMckoDehRf[A[;gr)U3s+13Hrs&E#q7-J'rp9Z8~>
-q#=.\s7Xl<A]=TIrr2rtq>^?lrTsRVrs8\-nc8^i!<3'!rs&2s!;QQoqZ-Zr!;d!#q@*B(s8NH!
-rrDTc!<2ipm.o`CE)TD)=]YUmnG2t\r;uuurdk*#s0)G,r;QN%s8Dr`s*t~>
-q#=4^s7Xl<A]=TIrr2rtq>^?lrTsRVrs8\-nc8^i!<3'!rs&2s!;QQoqZ-Zr!;d!#q@*B(s8;r_
-qZ?Zp$ig5)jQtIu@mVn(4$!Amh!4G'qgncus/Z/(r;QN%s8Dr`s*t~>
-q>XOqs82iqs0jm1LuA='oDeC]s7lWjp&FEX+9*5Riu72%lf&rbrtb.e*rcN:o+:p0n)"oM&,PT%
-r:qN2q%rbnoDYo?k;g)qa@SVE`RD-*\[f2Y[^EMo[Xkll[KX%B]?$WBlMlA~>
-q>XOqs82iqs0jm1LuA='oDeC]s7lWjp&FEX+9*5Riu72%lf&rbrtb.e*rcN:o+:p0n)"oM&,PZ.
-s7dGtmhGTnrr8^pXoMI%;ZHfbS@PH'[^EQO[^W_s[Xkll[KX%B]?$WBlMlA~>
-q>XOqs82iqs0jm1LuA='oDeC]s7lWjp&FEX+9*5Riu72%lf&rbrtb.e*rcN:o+:p0n)"oM&,PQ#
-qXts'qAoV4q"BlASH),H56(\:MQ3)HZ*LaF[^<Dm[Xkll[KX%B]?$WBlMlA~>
-q>Uius8VZip](%s"r->$s"jZSr;6Nor;Zf5*q2@^\OZZP\Z%aarD9K&0CrG3XZHJSOhXKc8"ueF
-qtig\Ud+bLnFHPX!]-rAqu6ZorVH]nq"X]:qgnXMqZm/rp[u)srVGm\J,~>
-q>Uius8VZip](%s"r->$s"FBOr;6Nor;Zf5*q2@^\OZZP\Z%aarD9K&0CrG3XZHJSOhXKc8"ukM
-rV&FDRQUNJ%K,qi!+#Z\nb2hRs82`nqZHcprVV6DJbubM#QOSnost,$qX"64~>
-q>Uius8VZip](%s"r->$s$H_br;6Nor;Zf5*q2@^\OZZP\Z%aarD9K&0CrG3XZHJSOhXKc8"uhH
-qY3@PUI5(]q=4"Ak2QG=!7'Win*]uFp\ssjp\+@WJbt#qZMOn,q"OHls8Df\s*t~>
-p&@;Is0XU6MYHl9s7lWks8Vi^rriQ?ZM;im#K$Mc&Ke[cs.gD=ci:F%)8kjO/c`H^cl3nFr;X\`
-(s_O;naZAErr39Fs8W)ts7l-]r;HWpquH_Is+13Trr`6"r4;.mJ,~>
-p&@,Ds0XU6MYHl9s7lWks8Vi^rriQ?ZM;im#K$Mc&Ke[cs.gD=ci:F%)8kjO/c`H^cl3tLrVa>I%
-F"GBrsSMuq!8"QpZqSRr;)!EJcD\K"9&8t]'96F~>
-p&@\Ts0XU6MYHl9s7lWks8Vi^rriQ?ZM;im#K$Mc&Ke[cs.gD=ci:F%)8kjO/c`H^cl3qLs8Tk[
-'@64DqtTgEmHrp6n+#N0qtp3grrrAuq"FL]JcC<$Z2Xq)s89Ims*t~>
-q#;Vss8Tl*ABFlOoDe^fp%A@Grr<#ls7QEis6fperVlinp]'pTrVm!!p](-fs8Vd:s7ZBPs8W#s
-s82KZl.bt3qZ"Y:rU9ORp%S4Vr;HQkq>1#?rIOpQrVlg"rNuFrrp9Z8~>
-q#;Vss8Tl*ABFlOoDe^fp%A@Grr<#ls7QEis6fperVlinp]'pTrVm!!p](-fs8Vcus7cQUrVQ'\
-rr)j&o)Ja]r4;RsoDJUirr)forrE%LrIOpQrVlg"rNuFrrp9Z8~>
-q#;Vss8Tl*ABFlOoDe^fp%A@Grr<#ls7QEis6fperVlinp]'pTrVm!!p](-fs8Vd-s7u]\s8Vfm
-s8DikoA]K;lh7m]q"":]"oJ)go_/05rIOpQrVlg"rNuFrrp9Z8~>
-pAY0b"TAB,!WW<#qtg?mpAY(:mJZtZo`+g_s8VfmpAb-ls7u]ds7ZK_s8VNerp]a`s7ZKkrql`q
-s8!B+p@RnDnG*"KYke%co%E[$qu6Tp"9/2prdk*#s0D\)qZ?fr\*<pC~>
-pAY0b"TAB,!WW<#qtg?mpAY(BmJZtZo`+g_s8VfmpAb-ls7u]ds7ZK_s8VNerp]a`s7ZKls8Vrl
-qsjCZqYpT`[/L".mJln[s8ITLJcDhOs8)ltrO;%kJ,~>
-pAY0b"TAB,!WW<#qtg?mpAY(8mJZtZo`+g_s8VfmpAb-ls7u]ds7ZK_s8VNerp]a`s7Z<h$hEob
-q"jj_q>L9Y[Jg+,mf3._rrrAuq"FL]JcC<$ZN't%!rr5.l2Q8~>
-q#:s*s82ios8V`kp&G!es7cQnnG`G)qZ$Tns8Vrqs8D`ms8;osrVuons8W&sq#16mpBLZlrpKUV
-rr)j.r:0FOp$hkUVtJs7nac5Grr)j!rqlTlJcC<$YQ"b&[J]gtm/MS~>
-q#:s*s82ios8V`kp&G!es7cQnnG`G)qZ$Tns8Vrqs8D`ms8;osrVuons8W&sq#16mpC.)rs7QEd
-r;HTks8W&srrr9!s0;Upqu?ZoJcC<$WrE5![J]gtm/MS~>
-q#:s*s82ios8V`kp&G!es7cQnnG`G)qZ$Tns8Vrqs8D`ms8;osrVuons8W&sq#16mpB1His82cp%
-K#qsqXaR^s82irZN'FhrrDrqq>gJFs+13Rrri5,r:p3Vs*t~>
-kl3.%s7--hp]'gaq#Bpbp&Fdds82fqr;Zfls8W)up](6ms7Q*cpAFIUpAY!i%K?8!o_JLaYkRqe
-o(`+Ys8Vusrdk*#s0D\)rWN9!ZhjOas*t~>
-kl3:)s7--hp]'gaq#Bpbp&Fdds82fqr;Zfls8W)up](6ms7Q*cpAb!hqu?ZprVQTo"o\8prqs5(
-rrE&trri;tqYU5Bs+13Qs8W'$s895"qWn03~>
-kl2sus7--hp]'gaq#Bpbp&Fdds82fqr;Zfls8W)up](6ms7Q*cpA=miqZZ`jqu-Eirr3,.q>L9g
-q>^HmJcC<$Z2ak'"TSD+qYKOXJ,~>
-l2LbQrVllnrr2uYrr482s8DWjrVlfks763`s763imf3:Uq>^Kgs82TcpAFsarr;ioqu6o-rVl`g
-r:U!brrW2trdk*#s/uA'\c;Wos*t~>
-l2LbQrVllnrr2uYrr3i&s8DWjrVlfks763`s763imf3:Uq>^KgrVmB,s8MTbqt9gbrr)fnrNcG&
-$2a_opA=[]qY^>Ds+13NrrTb2rTjK6~>
-l2LbQrVllnrr2uYrr3i&s8DWjrVlfks763`s763imf3:Uq>^KgqYp]gpA"4Qqu6o(o_A4Sq==<3
-s+13HrrTb2rTjK6~>
-kl1hcs6B.SlMgekq>9jbs7$'apAadQrVluupAb$ers8Aos7c?Jq>9m\s8W#t^%D=+rqubHs+13U
-rri2lqtIP`s*t~>
-kl1hcs6B.SlMgekq>9jbs7$'apAadQrVluupAb$ertbA(s7lW[s8Vlorr)cmqu$?co('*crr2p&
-rquWgq"jmdJcC<$YQ"b%pA=Tml2Q8~>
-kl1hcs6B.SlMgekq>9jbs7$'apAadQrVluupAb$ers8Aos8)c_s8Vilr;lrtrVm3#Y4M;Xq"X^_
-rIP!"s/Q)%qY'g\[d!gB~>
-kl1YErr4JG1]S,[qpuYr\Gsba*kVIPUD46bs0O*i`9t/h!4i-W*#lopqm@X\aS5N1"M+R4qt9aa
-!WN%Krdk'Rrr2p#rjVn(rp9Z8~>
-kl1YErr4YL1]S,[qpuYr\Gsba*kVIPUD46bs0O*i`9t/h!4i-W*$*3%s19Wl`q00+p\Xjeq@9P$
-qt0IZqtg-aq"adarIOs!rilD$rri>1rql]]s*t~>
-kl1YErr4SJ1]S,[qpuYr\Gsba*kVIPUD46bs0O*i`9t/h!4i-W*$!6's19Tja7TE3rVZ[&Vt0EF
-l1k#Jr;?Qks+10#rj)P&rri>1rql]]s*t~>
-kPm.-s8;`Zs0F$O2P@W<s"1aZ4S/JHW4=VQ>EGpIs%/<e8c(uNV*Y.T2kc]urqZQo_>aH9q>^GF
-s+13Ks8Vs!s805'rp0T7~>
-kPmU:s8;`Zs0F$O2P@W<s"1aZ4S/JHW4=VQ>EGpIs%/<e8c(uNV*Y.V4/\c4s8MfeqY^3er3Q:u
-s8)`ps8;oo"8r,srdk*#s0;V(qZQrr[Jp0ks*t~>
-kPm+,s8;`Zs0F$O2P@W<s"1aZ4S/JHW4=VQ>EGpIs%/<e8c(uNV*Y.U3iAZ6rr2utrVlosZi9e&
-o_\LarIP!"s/Z2"qZQrr[Jp0ks*t~>
-kl1\\qY^@AlllBAm-keX)X?9$s8Ni'k9'^0o(!@l$LZabmg]'Y'`[tGn*UYZqYL3l[Jp10qYBj^
-qu6Tp!WN"JqgnXKqZd*!s80;*rTjK6~>
-kl1\\qY^@KlllBAm-keX)X?9$s8Ni'k9'^0o(!@l$LZabmg]'W%fQ/@p%B-uqYpHlrr<#qWqHAj
-rs8Q&qtg*_q>'l<qgnXKqZd*!s80;*rTjK6~>
-kl1\\qY^@?lllBAm-keX)X?9$s8Ni'k9'^0o(!@l$LZabmg]'X%fQ)=p%TC$rsJ`%poaGms8N&p
-rquZmrIOisqm$#&s8Dup\,ZEms*t~>
-kl3p<s8VZis7^YtrrDTh!<3'!rrr)q!;HKnqZ-Zr!;cs"q@!<'s8NT&rrMWa!;Q$[rqH$\qu">/
-oChkJme?PVJcC<$WrE;&r:d]#q"="RJ,~>
-kl3I/s8VZis7^YtrrDTh!<3'!rrr)q!;HKnqZ-Zr!;cs"q@!<'s8E/er;lTk"98)ps7l9drs$11
-oDS^hrr2rprdk*#s02M-rquN"s7l9Rs*t~>
-kl3[5s8VZis7^YtrrDTh!<3'!rrr)q!;HKnqZ-Zr!;cs"q@!<'s8<&ar;l]n#QOf's8)<]q"r#0
-qu6TqrIP!"s/5l$rquN"s7l9Rs*t~>
-kPmO8s8VclQWsIb&,G`'r>"Dc'DDG>n+-e`*pE&4r"&)f!ril'm2Z0+)rolpo_/1M`N68LY,'4D
-riH<uYH4q5r3Ls[J[2Pd"LP8;`hVeuJ,~>
-kPmjAs8VclQWsIb&,G`'r>"Dc'DDG>n+-e`*pE&4r"&)f!ril'kn<^c)s?H1rVcceag&:dZ_PUI
-[_'5Z]="uNZ%933ZE:D8[&gXSUZqf/~>
-kPm+,s8VclQWsIb&,G`'r>"Dc'DDG>n+-e`*pE&4r"&)f!ril'kRmL`)sZc9rr3T'b,qhNXf9j_
-]=bh_\[8`LZMh"YZ@T<dZ37P9[)Sm*s*t~>
-l2NF7s7ZKfs+Y.cqYrsWSk&`HX/%oTs!b4D:\clIUHec9[APt`5b"cIqYrjG[l3pTrX#q2qY^9g
-qu$HmJcC<$WrE8%s80;*rTjK6~>
-l2NF7s7ZKfs+Y.cqYrsWSk&`HX/%oTs!b4D:\clIUHec9[APt`5b"]CpAI:B\2sH]rrTP+qgncu
-s.fStrr;l)s8D9`J,~>
-l2NF7s7ZKfs+Y.cqYrsWSk&`HX/%oTs!b4D:\clIUHec9[APt`5b"`Cp&77D\i]cds8Mrs[=S@/
-s.TGrrr;l)s8D9`J,~>
-kl37(s8W)n+92cLpWF-s_XtbW(s`-TbS_;9s2Q<1_<8KG*khTt'c4[bs100ca8#Z9ZiBoRs+13F
-rrrE%qmZV(li2J~>
-kl37(s8W)n+92cLpWF-s_XtbW(s`-TbS_;9s2Q<1_<8KG*khTt'c4[bs100ca8#Z9ZiBoRs+13F
-rrrE%qmZV(li2J~>
-kl37(s8W)n+92cLpWF-s_XtbW(s`-TbS_;9s2Q<1_<8KG*khTt'c4[bs100ca8#Z9ZiBoRs+13F
-rrrE%qmZV(li2J~>
-l2M4js8Dffs7cQfp](6fmJ[%lo`+p\s8)c^s8UpRrrqZep$)MQrr3/gs8VfmlhUP^ZiBoRs+13F
-rrrE%qmZV(li2J~>
-l2M4js8Dffs7cQfp](6fmJ[%lo`+p\s8)c^s8UpRrrqZep$)MQrr3/gs8VfmlhUP^ZiBoRs+13F
-rrrE%qmZV(li2J~>
-l2M4js8Dffs7cQfp](6fmJ[%lo`+p\s8)c^s8UpRrrqZep$)MQrr3/gs8VfmlhUP^ZiBoRs+13F
-rrrE%qmZV(li2J~>
-l2N=)s7lWomf3%Xs7?'dqZ$TkpAasfs6BIYs7>XXli6bNs7?9jp@J=ajT#8ApAY3#s8.BIJcDMF
-"oeQ!\,ZEms*t~>
-l2N=)s7lWomf3%Xs7?'dqZ$TkpAasfs6BIYs7>XXli6bNs7?9jp@J=ajT#8ApAY3#s8.BIJcDMF
-"oeQ!\,ZEms*t~>
-l2N=)s7lWomf3%Xs7?'dqZ$TkpAasfs6BIYs7>XXli6bNs7?9jp@J=ajT#8ApAY3#s8.BIJcDMF
-"oeQ!\,ZEms*t~>
-kl2:hrqHHmoD/.\p&G'jq=jphnbN+_&,lP-q>^Kls8DipmJlnZqYgEqq#Bs]rrTP,qgncus.fSt
-rr;l)s8D9`J,~>
-kl2:hrqHHmoD/.\p&G'jq=jphnbN+_&,lP-q>^Kls8DipmJlnZqYgEqq#Bs]rrTP,qgncus.fSt
-rr;l)s8D9`J,~>
-kl2:hrqHHmoD/.\p&G'jq=jphnbN+_&,lP-q>^Kls8DipmJlnZqYgEqq#Bs]rrTP,qgncus.fSt
-rr;l)s8D9`J,~>
-l2Lq_rr<#sq#:9qp&G']rr2umrr3N#qu>XTlMU\Sp%JFckl:JWrs/8tn,NFas8Mio!jhq(JcC<$
-U]1Mss80;*rTjK6~>
-l2Lq_rr<#sq#:9qp&G']rr2umrr3N#qu>XTlMU\Sp%JFckl:JWrs/8tn,NFas8Mio!jhq(JcC<$
-U]1Mss80;*rTjK6~>
-l2Lq_rr<#sq#:9qp&G']rr2umrr3N#qu>XTlMU\Sp%JFckl:JWrs/8tn,NFas8Mio!jhq(JcC<$
-U]1Mss80;*rTjK6~>
-kPl(^s8Vfmme?SXrVuiaq#C!brrr5urr<#jrVm,ms7lNis8VEarr`2tqt9aa!jhq(JcC<$U]1Ms
-s80;*rTjK6~>
-kPl(^s8Vfmme?SXrVuiaq#C!brrr5urr<#jrVm,ms7lNis8VEarr`2tqt9aa!jhq(JcC<$U]1Ms
-s80;*rTjK6~>
-kPl(^s8Vfmme?SXrVuiaq#C!brrr5urr<#jrVm,ms7lNis8VEarr`2tqt9aa!jhq(JcC<$U]1Ms
-s80;*rTjK6~>
-kl1hYs8MQgq>L=9nc/Xfs8Vlls7Q3fo`+UYs7lNlp&Fm^s8Vros7cQkr;Q]ro(i:eZiBoRs+13F
-rrrE%qmZV(li2J~>
-kl1hYs8MQgq>L=9nc/Xfs8Vlls7Q3fo`+UYs7lNlp&Fm^s8Vros7cQkr;Q]ro(i:eZiBoRs+13F
-rrrE%qmZV(li2J~>
-kl1hYs8MQgq>L=9nc/Xfs8Vlls7Q3fo`+UYs7lNlp&Fm^s8Vros7cQkr;Q]ro(i:eZiBoRs+13F
-rrrE%qmZV(li2J~>
-kPm78qu?WfI/s<Daqbl3rPB]m`;f\T)'H<d^)%O/s25fr])9Ph'>OSI]HeE6s8W&rrr3<(s0D\)
-qtg?mrIP!"s/,eur42k,li2J~>
-kPm78qu?WfI/s<Daqbl3rPB]m`;f\T)'H<d^)%O/s25fr])9Ph'>OSI]HeE6s8W&rrr3<(s0D\)
-qtg?mrIP!"s/,eur42k,li2J~>
-kPm78qu?WfI/s<Daqbl3rPB]m`;f\T)'H<d^)%O/s25fr])9Ph'>OSI]HeE6s8W&rrr3<(s0D\)
-qtg?mrIP!"s/,eur42k,li2J~>
-l2NF.s7lWkrr@TSs6rU[Z9&"TTWD;es$rca2uc+BXZ6;NT!L?T0s7TGpARmTV+(%UrsSc%r;Q`-
-s8W&ns8@NKJcD\K#5e5qppC"sli2J~>
-l2NF.s7lWkrr@TSs6rU[Z9&"TTWD;es$rca2uc+BXZ6;NT!L?T0s7TGpARmTV+(%UrsSc%r;Q`-
-s8W&ns8@NKJcD\K#5e5qppC"sli2J~>
-l2NF.s7lWkrr@TSs6rU[Z9&"TTWD;es$rca2uc+BXZ6;NT!L?T0s7TGpARmTV+(%UrsSc%r;Q`-
-s8W&ns8@NKJcD\K#5e5qppC"sli2J~>
-kl3j6oDedds8N)mrsmcK(B4j:gB7?Q$1I0qkmd=D*V0d/koT^Ch=D!Rndk![+oh*1s8W&ts88qi
-qZ$Kmp&0IAJcDSH!kA:.li2J~>
-kl3j6oDedds8N)mrsmcK(B4j:gB7?Q$1I0qkmd=D*V0d/koT^Ch=D!Rndk![+oh*1s8W&ts88qi
-qZ$Kmp&0IAJcDSH!kA:.li2J~>
-kl3j6oDedds8N)mrsmcK(B4j:gB7?Q$1I0qkmd=D*V0d/koT^Ch=D!Rndk![+oh*1s8W&ts88qi
-qZ$Kmp&0IAJcDSH!kA:.li2J~>
-l2LbQrVnPCs6TgdrrE*!"8i0!rsJf+!<<'!rs/N&"9/N(rrD]qs8N*!rt,.l!;cTms8D`lrrBt7
-rrDtJs+13Jrs&;spU:,"rp9Z8~>
-l2LbQrVnPCs6TgdrrE*!"8i0!rsJf+!<<'!rs/N&"9/N(rrD]qs8N*!rt,.l!;cTms8D`lrrBt7
-rrDtJs+13Jrs&;spU:,"rp9Z8~>
-l2LbQrVnPCs6TgdrrE*!"8i0!rsJf+!<<'!rs/N&"9/N(rrD]qs8N*!rt,.l!;cTms8D`lrrBt7
-rrDtJs+13Jrs&;spU:,"rp9Z8~>
-kl37/rVu*]p&>B_s!$Xp$2bS%q[)Wh$L@-drrW5b!<38siY)8!lMDUppBgBf$i^/8lc--3ZEBJ4
-ZEC=9Ye>UpJ[DA_"L5Y`T`+0UJ,~>
-kl37/rVu*]p&>B_s!$Xp$2bS%q[)Wh$L@-drrW5b!<38siY)8!lMDUppBgBf$i^/8lc--3ZEBJ4
-ZEC=9Ye>UpJ[DA_"L5Y`T`+0UJ,~>
-kl37/rVu*]p&>B_s!$Xp$2bS%q[)Wh$L@-drrW5b!<38siY)8!lMDUppBgBf$i^/8lc--3ZEBJ4
-ZEC=9Ye>UpJ[DA_"L5Y`T`+0UJ,~>
-l2NU9s82cprUp0js78RJSk8rHVO'aOqEh3^6hN^1Yu(<kUTd)J8uS79s8P<hTfiAOs8Vrkrr35C
-s8)Wks7lMCs+13Krs&H!p:1/!p[%p1~>
-l2NU9s82cprUp0js78RJSk8rHVO'aOqEh3^6hN^1Yu(<kUTd)J8uS79s8P<hTfiAOs8Vrkrr35C
-s8)Wks7lMCs+13Krs&H!p:1/!p[%p1~>
-l2NU9s82cprUp0js78RJSk8rHVO'aOqEh3^6hN^1Yu(<kUTd)J8uS79s8P<hTfiAOs8Vrkrr35C
-s8)Wks7lMCs+13Krs&H!p:1/!p[%p1~>
-l2NL;s8Vuds7lQno`)<K-aWie+Y:A)s3(lk]`$+u%_D\N*=pg"`u?,Co`)?2)U\<Trr)j!q=LZ^
-rVlosqLSQqr2KSsrqcZl\`s-E~>
-l2NL;s8Vuds7lQno`)<K-aWie+Y:A)s3(lk]`$+u%_D\N*=pg"`u?,Co`)?2)U\<Trr)j!q=LZ^
-rVlosqLSQqr2KSsrqcZl\`s-E~>
-l2NL;s8Vuds7lQno`)<K-aWie+Y:A)s3(lk]`$+u%_D\N*=pg"`u?,Co`)?2)U\<Trr)j!q=LZ^
-rVlosqLSQqr2KSsrqcZl\`s-E~>
-kl2_#qu?]as7H?hqu?$_s7H?gq>UEnnGiI_s7c-bs7ZHl&GcA%rr2fpp\k-jnGiOdq>L9l#.+@0
-qss^_JcC<$W;d)#rr;r'qYKOXJ,~>
-kl2_#qu?]as7H?hqu?$_s7H?gq>UEnnGiI_s7c-bs7ZHl&GcA%rr2fpp\k-jnGiOdq>L9l#.+@0
-qss^_JcC<$W;d)#rr;r'qYKOXJ,~>
-kl2_#qu?]as7H?hqu?$_s7H?gq>UEnnGiI_s7c-bs7ZHl&GcA%rr2fpp\k-jnGiOdq>L9l#.+@0
-qss^_JcC<$W;d)#rr;r'qYKOXJ,~>
-^Ae<5s8VukrrTP,qgncus.fStrr;l)s8D9`J,~>
-^Ae<5s8VukrrTP,qgncus.fStrr;l)s8D9`J,~>
-^Ae<5s8VukrrTP,qgncus.fStrr;l)s8D9`J,~>
-_>ac5s8MrrnGiL`rrTP,qgncus.fStrr;l)s8D9`J,~>
-_>ac5s8MrrnGiL`rrTP,qgncus.fStrr;l)s8D9`J,~>
-_>ac5s8MrrnGiL`rrTP,qgncus.fStrr;l)s8D9`J,~>
-_#FW2s8N&os7Z9g!jhq(JcC<$U]1Mss80;*rTjK6~>
-_#FW2s8N&os7Z9g!jhq(JcC<$U]1Mss80;*rTjK6~>
-_#FW2s8N&os7Z9g!jhq(JcC<$U]1Mss80;*rTjK6~>
-_#FW7s8Vurs7Z9g!jhq(JcC<$U]1Mss80;*rTjK6~>
-_#FW7s8Vurs7Z9g!jhq(JcC<$U]1Mss80;*rTjK6~>
-_#FW7s8Vurs7Z9g!jhq(JcC<$U]1Mss80;*rTjK6~>
-_>a`(s7?9jr;HEj!jhq(JcC<$U]1Mss80;*rTjK6~>
-_>a`(s7?9jr;HEj!jhq(JcC<$U]1Mss80;*rTjK6~>
-_>a`(s7?9jr;HEj!jhq(JcC<$U]1Mss80;*rTjK6~>
-^Ae>R*$)ilq>UN&s8.BIJcDMF"oeQ!\,ZEms*t~>
-^Ae>R*$)ilq>UN&s8.BIJcDMF"oeQ!\,ZEms*t~>
-^Ae>R*$)ilq>UN&s8.BIJcDMF"oeQ!\,ZEms*t~>
-_>a`1s5uVCRRd/Q!jhq(JcC<$U]1Mss80;*rTjK6~>
-_>a`1s5uVCRRd/Q!jhq(JcC<$U]1Mss80;*rTjK6~>
-_>a`1s5uVCRRd/Q!jhq(JcC<$U]1Mss80;*rTjK6~>
-_#FT-rsA2p!;QQqZiBoRs+13FrrrE%qmZV(li2J~>
-_#FT-rsA2p!;QQqZiBoRs+13FrrrE%qmZV(li2J~>
-_#FT-rsA2p!;QQqZiBoRs+13FrrrE%qmZV(li2J~>
-_>b>Hs7uitp]^]js8W#lrr;r+rVuiis8Vr1rr`&rs7jS5"8MorpqHb4rr;rns8W&6rs/N%s8W)q
-s80Y4#58)rrPAC*li2J~>
-_>b>Hs7uitp]^]js8W#lrr;r+rVuiis8Vr1rr`&rs7jS5"8MorpqHb4rr;rns8W&6rs/N%s8W)q
-s80Y4#58)rrPAC*li2J~>
-_>b>Hs7uitp]^]js8W#lrr;r+rVuiis8Vr1rr`&rs7jS5"8MorpqHb4rr;rns8W&6rs/N%s8W)q
-s80Y4#58)rrPAC*li2J~>
-_#FT8rsJ#j!<3!-m)ulG\@8KOW4BOJXgl3Q#H5;AZF-jC\&ko\\$`BHZEq3B_6O<QYd_'G\>QgP
-Z3S"FYcb[<^oY>Ws02X=Vu#]YJ,~>
-_#FT8rsJ#j!<3!-m)ulG\@8KOW4BOJXgl3Q#H5;AZF-jC\&ko\\$`BHZEq3B_6O<QYd_'G\>QgP
-Z3S"FYcb[<^oY>Ws02X=Vu#]YJ,~>
-_#FT8rsJ#j!<3!-m)ulG\@8KOW4BOJXgl3Q#H5;AZF-jC\&ko\\$`BHZEq3B_6O<QYd_'G\>QgP
-Z3S"FYcb[<^oY>Ws02X=Vu#]YJ,~>
-_>b>LnbYhCY<i3cs8Vicr;Zf;r;$BfrV?H/rs-11rqZTlqYA85#Pn5os7uZg]>4FNq>C9cp;H^A
-rs/Q"rVHH,q=hW'"SK>'s7bm[J,~>
-_>b>LnbYhCY<i3cs8Vicr;Zf;r;$BfrV?H/rs-11rqZTlqYA85#Pn5os7uZg]>4FNq>C9cp;H^A
-rs/Q"rVHH,q=hW'"SK>'s7bm[J,~>
-_>b>LnbYhCY<i3cs8Vicr;Zf;r;$BfrV?H/rs-11rqZTlqYA85#Pn5os7uZg]>4FNq>C9cp;H^A
-rs/Q"rVHH,q=hW'"SK>'s7bm[J,~>
-^]+N1`[qeMoD8Cb#5I_aqu?]jrr9h5"0DP&rr)l=rVc`urr<#"_>X<3!rfS,_>X<3!j)D$_>OW2
-r;Zeur;ZTZs*t~>
-^]+N1`[qeMoD8Cb#5I_aqu?]jrr9h5"0DP&rr)l=rVc`urr<#"_>X<3!rfS,_>X<3!j)D$_>OW2
-r;Zeur;ZTZs*t~>
-^]+N1`[qeMoD8Cb#5I_aqu?]jrr9h5"0DP&rr)l=rVc`urr<#"_>X<3!rfS,_>X<3!j)D$_>OW2
-r;Zeur;ZTZs*t~>
-_>ao5rVu]fs763cr;QZnrs-74q>U-dp@c?&#.+@0n+Z\Wa8ZABqY0IYs0KQA#5\,js8Vu"^]4?+
-!rDr'^&J91s/Q,!o'HC,~>
-_>ao5rVu]fs763cr;QZnrs-74q>U-dp@c?&#.+@0n+Z\Wa8ZABqY0IYs0KQA#5\,js8Vu"^]4?+
-!rDr'^&J91s/Q,!o'HC,~>
-_>ao5rVu]fs763cr;QZnrs-74q>U-dp@c?&#.+@0n+Z\Wa8ZABqY0IYs0KQA#5\,js8Vu"^]4?+
-!rDr'^&J91s/Q,!o'HC,~>
-^]+K0s8Vrqp\t0rqt^'aqmHG'"8r3!rPAI8\,?:*rVt"=rVm!!s89@Brquctqn<$GrW3&urr3&7
-r;XV4"S_l_s/G8_J,~>
-^]+K0s8Vrqp\t0rqt^'aqmHG'"8r3!rPAI8\,?:*rVt"=rVm!!s89@Brquctqn<$GrW3&urr3&7
-r;XV4"S_l_s/G8_J,~>
-^]+K0s8Vrqp\t0rqt^'aqmHG'"8r3!rPAI8\,?:*rVt"=rVm!!s89@Brquctqn<$GrW3&urr3&7
-r;XV4"S_l_s/G8_J,~>
-_>al@s76-gs8;cjqu6Nn#H@S"rU^'hqSE15_!V!srrD`6rrD`jrrW&a^qp$Ur;ZZfs7+>(_>ac:
-qu?]b`pio>rrU%/q!7s1~>
-_>al@s76-gs8;cjqu6Nn#H@S"rU^'hqSE15_!V!srrD`6rrD`jrrW&a^qp$Ur;ZZfs7+>(_>ac:
-qu?]b`pio>rrU%/q!7s1~>
-_>al@s76-gs8;cjqu6Nn#H@S"rU^'hqSE15_!V!srrD`6rrD`jrrW&a^qp$Ur;ZZfs7+>(_>ac:
-qu?]b`pio>rrU%/q!7s1~>
-_#FQ8s8Vchrr3<'r;HWorVlfrn,E=fp]&)/!;ZWo"8qups2k9?rrMrnrr2uo_>X]8s8Mios763+
-rW3&prr3&ms8Tq7#QOPurp]sfqX"64~>
-_#FQ8s8Vchrr3<'r;HWorVlfrn,E=fp]&)/!;ZWo"8qups2k9?rrMrnrr2uo_>X]8s8Mios763+
-rW3&prr3&ms8Tq7#QOPurp]sfqX"64~>
-_#FQ8s8Vchrr3<'r;HWorVlfrn,E=fp]&)/!;ZWo"8qups2k9?rrMrnrr2uo_>X]8s8Mios763+
-rW3&prr3&ms8Tq7#QOPurp]sfqX"64~>
-\,QO+q>:-j#5/#joDejfaSuM;s82`fs8Voorr`/us8L%<"8ViooY1>/r;Zfgs7X;/"TJ2rq"t$i
-"oA9!q#Bj.rsSi#rqucgs8W#sq>0FWJ,~>
-\,QO+q>:-j#5/#joDejfaSuM;s82`fs8Voorr`/us8L%<"8ViooY1>/r;Zfgs7X;/"TJ2rq"t$i
-"oA9!q#Bj.rsSi#rqucgs8W#sq>0FWJ,~>
-\,QO+q>:-j#5/#joDejfaSuM;s82`fs8Voorr`/us8L%<"8ViooY1>/r;Zfgs7X;/"TJ2rq"t$i
-"oA9!q#Bj.rsSi#rqucgs8W#sq>0FWJ,~>
-\c2^!r;HWtp&FXRrVlolrQ5'@nGiCbrs%rls8;fpqo8X@q>^Hos8)cl_>a]7qu?*am_8]-qZ$Tk
-m/Q_Rr:0dd!V?-7rs&E$qYgHjq#:9sirB&Us7bm[J,~>
-\c2^!r;HWtp&FXRrVlolrQ5'@nGiCbrs%rls8;fpqo8X@q>^Hos8)cl_>a]7qu?*am_8]-qZ$Tk
-m/Q_Rr:0dd!V?-7rs&E$qYgHjq#:9sirB&Us7bm[J,~>
-\c2^!r;HWtp&FXRrVlolrQ5'@nGiCbrs%rls8;fpqo8X@q>^Hos8)cl_>a]7qu?*am_8]-qZ$Tk
-m/Q_Rr:0dd!V?-7rs&E$qYgHjq#:9sirB&Us7bm[J,~>
-\,QU(qt'jWrr3)so`+j0rrr<"p[/"Qr;Qous8W&qaSuJ9s8VlmrqjJ1!;QQn!W23!rt"i!s6p!f
-nG`Ifo`+RYs8:4C%.X5prr;rfs8Vrgs7kp[J,~>
-\,QU(qt'jWrr3)so`+j0rrr<"p[/"Qr;Qous8W&qaSuJ9s8VlmrqjJ1!;QQn!W23!rt"i!s6p!f
-nG`Ifo`+RYs8:4C%.X5prr;rfs8Vrgs7kp[J,~>
-\,QU(qt'jWrr3)so`+j0rrr<"p[/"Qr;Qous8W&qaSuJ9s8VlmrqjJ1!;QQn!W23!rt"i!s6p!f
-nG`Ifo`+RYs8:4C%.X5prr;rfs8Vrgs7kp[J,~>
-\c2[%rVm#fs)SCsrr3&ls8L.?!<2ut"Ss&;&XrXt!W2f9rs&2so_ngJ#JpEErUg,o!#)NNrsngQ
-$NKkS4UE5-s69R`o[*U<q>UC%mJles"p2(#.Kgcpm/MS~>
-\c2[%rVm#fs)SCsrr3&ls8L.?!<2ut"Ss&;&XrXt!W2f9rs&2so_ngJ#JpEErUg,o!#)NNrsngQ
-$NKkS4UE5-s69R`o[*U<q>UC%mJles"p2(#.Kgcpm/MS~>
-\c2[%rVm#fs)SCsrr3&ls8L.?!<2ut"Ss&;&XrXt!W2f9rs&2so_ngJ#JpEErUg,o!#)NNrsngQ
-$NKkS4UE5-s69R`o[*U<q>UC%mJles"p2(#.Kgcpm/MS~>
-\,Qd&s7u[#K+%_ZpAY'lm`>D;p$r(Zs8TDDs7H?gs8L+>#O29Ws8Upu#J^9BrX@)l%J@R;$3Yt]
-s8-'Ho)8FRrr2uid/OXPs6fmdm.UJX_\<(Gq614ms*t~>
-\,Qd&s7u[#K+%_ZpAY'lm`>D;p$r(Zs8TDDs7H?gs8L+>#O29Ws8Upu#J^9BrX@)l%J@R;$3Yt]
-s8-'Ho)8FRrr2uid/OXPs6fmdm.UJX_\<(Gq614ms*t~>
-\,Qd&s7u[#K+%_ZpAY'lm`>D;p$r(Zs8TDDs7H?gs8L+>#O29Ws8Upu#J^9BrX@)l%J@R;$3Yt]
-s8-'Ho)8FRrr2uid/OXPs6fmdm.UJX_\<(Gq614ms*t~>
-\c2s5s8Mlos5a(Xrr3#uo>gk2rr)j&p&F(fs7ZKkrPnjAr;$'XRt1^Yrs&?"q@VQ0*l%^aq#gQp%
-K#qqs8W#es8:4C%f5htoD\dcr3@F?s7uR9m/MS~>
-\c2s5s8Mlos5a(Xrr3#uo>gk2rr)j&p&F(fs7ZKkrPnjAr;$'XRt1^Yrs&?"q@VQ0*l%^aq#gQp%
-K#qqs8W#es8:4C%f5htoD\dcr3@F?s7uR9m/MS~>
-\c2s5s8Mlos5a(Xrr3#uo>gk2rr)j&p&F(fs7ZKkrPnjAr;$'XRt1^Yrs&?"q@VQ0*l%^aq#gQp%
-K#qqs8W#es8:4C%f5htoD\dcr3@F?s7uR9m/MS~>
-\,Qp:s8MlprsSYpq>^KnrlG*En,N+]q=0?)rVlrqs80q<#Pn5rp#m+d!5\[?r:Bra!W]+i_>b&/
-rrE)k&j6i#l2:SUs8VT9rt#)#s8W)dp%8:uU\XrhFFifYJ,~>
-\,Qp:s8MlprsSYpq>^KnrlG*En,N+]q=0?)rVlrqs80q<#Pn5rp#m+d!5\[?r:Bra!W]+i_>b&/
-rrE)k&j6i#l2:SUs8VT9rt#)#s8W)dp%8:uU\XrhFFifYJ,~>
-\,Qp:s8MlprsSYpq>^KnrlG*En,N+]q=0?)rVlrqs80q<#Pn5rp#m+d!5\[?r:Bra!W]+i_>b&/
-rrE)k&j6i#l2:SUs8VT9rt#)#s8W)dp%8:uU\XrhFFifYJ,~>
-\GuU+s8NE!s7-0ds8W#rqT/[Ir;ZHis8JBds7?9cs8)cmao;VCs8MmObl7d[rri0<^r[M/rsT#(
-rrU?i'*&"*s7l?8rso#-rq-6jp](8b*<5i(>jME?~>
-\GuU+s8NE!s7-0ds8W#rqT/[Ir;ZHis8JBds7?9cs8)cmao;VCs8MmObl7d[rri0<^r[M/rsT#(
-rrU?i'*&"*s7l?8rso#-rq-6jp](8b*<5i(>jME?~>
-\GuU+s8NE!s7-0ds8W#rqT/[Ir;ZHis8JBds7?9cs8)cmao;VCs8MmObl7d[rri0<^r[M/rsT#(
-rrU?i'*&"*s7l?8rso#-rq-6jp](8b*<5i(>jME?~>
-Z2Y".s82Khs7+21&,cD's88Eds82ils8Vrqp<!=Fp](3l!!!]5%IsJps7$'^a8Z>A&^\K3r5&C@
-q>UHps#dX8*;9F/c2S=Os7?9jp](!b_%cg6@/g)js*t~>
-Z2Y".s82Khs7+21&,cD's88Eds82ils8Vrqp<!=Fp](3l!!!]5%IsJps7$'^a8Z>A&^\K3r5&C@
-q>UHps#dX8*;9F/c2S=Os7?9jp](!b_%cg6@/g)js*t~>
-Z2Y".s82Khs7+21&,cD's88Eds82ils8Vrqp<!=Fp](3l!!!]5%IsJps7$'^a8Z>A&^\K3r5&C@
-q>UHps#dX8*;9F/c2S=Os7?9jp](!b_%cg6@/g)js*t~>
-Z2Y"Cl2UeZq!cB)%fQG-k5PGb!!MTel2UVRa8ZSErTjL`lNQhYqu?]ba8ZA=r;Vm#$(\j2%fHJ/
-jP)9eUAt)is82ipdJjaSs8W&hs5"Xp$H2`?!!E;gs*t~>
-Z2Y"Cl2UeZq!cB)%fQG-k5PGb!!MTel2UVRa8ZSErTjL`lNQhYqu?]ba8ZA=r;Vm#$(\j2%fHJ/
-jP)9eUAt)is82ipdJjaSs8W&hs5"Xp$H2`?!!E;gs*t~>
-Z2Y"Cl2UeZq!cB)%fQG-k5PGb!!MTel2UVRa8ZSErTjL`lNQhYqu?]ba8ZA=r;Vm#$(\j2%fHJ/
-jP)9eUAt)is82ipdJjaSs8W&hs5"Xp$H2`?!!E;gs*t~>
-Z2Y'ls7ZKes8Vr;s8W!!p&G']rVm&ts8Dipn]Ce;rVuops6Td`s7?3h!WMl9rs/N&q#:<noCB`t
-&,cJ-qu?]cs8N&js8;`nrmC`Qqu?Tos7cQnrr;inrrD]Xs*t~>
-Z2Y'ls7ZKes8Vr;s8W!!p&G']rVm&ts8Dipn]Ce;rVuops6Td`s7?3h!WMl9rs/N&q#:<noCB`t
-&,cJ-qu?]cs8N&js8;`nrmC`Qqu?Tos7cQnrr;inrrD]Xs*t~>
-Z2Y'ls7ZKes8Vr;s8W!!p&G']rVm&ts8Dipn]Ce;rVuops6Td`s7?3h!WMl9rs/N&q#:<noCB`t
-&,cJ-qu?]cs8N&js8;`nrmC`Qqu?Tos7cQnrr;inrrD]Xs*t~>
-Yl=q*rVufqs8L+>%.F5pnc.qRp%n@]s7FA3&,?1is8VuerqZ6eqs4:^qoJd>jo>AF^Ae`?rVQNk
-s7uQlqu?]gs8W)Ers\/is7c*Us8Vrjs7?!Ns*t~>
-Yl=q*rVufqs8L+>%.F5pnc.qRp%n@]s7FA3&,?1is8VuerqZ6eqs4:^qoJd>jo>AF^Ae`?rVQNk
-s7uQlqu?]gs8W)Ers\/is7c*Us8Vrjs7?!Ns*t~>
-Yl=q*rVufqs8L+>%.F5pnc.qRp%n@]s7FA3&,?1is8VuerqZ6eqs4:^qoJd>jo>AF^Ae`?rVQNk
-s7uQlqu?]gs8W)Ers\/is7c*Us8Vrjs7?!Ns*t~>
-Z2XjrnG`Fgq=MZ+$gRcds8Vlor9"%Zou6q=nc&Ofr;ZforVufnaSuMCs8DornGN*rrsSW%li7"\
-s8Vrqs7Xh>s8;os#jM!]s8D'Zs6o4PJ,~>
-Z2XjrnG`Fgq=MZ+$gRcds8Vlor9"%Zou6q=nc&Ofr;ZforVufnaSuMCs8DornGN*rrsSW%li7"\
-s8Vrqs7Xh>s8;os#jM!]s8D'Zs6o4PJ,~>
-Z2XjrnG`Fgq=MZ+$gRcds8Vlor9"%Zou6q=nc&Ofr;ZforVufnaSuMCs8DornGN*rrsSW%li7"\
-s8Vrqs7Xh>s8;os#jM!]s8D'Zs6o4PJ,~>
-YQ"b&mf3=^aSue?s7cQnnbi4^m/R+^pAas0rrDHcrsA>tp[J4Rs8VrmaSuMAs8;iqqu?Z3rs/)n
-s8W&pq=O[d"8;cpnB_+Ep\k-lqt^*\s8Mcms5s:Hs*t~>
-YQ"b&mf3=^aSue?s7cQnnbi4^m/R+^pAas0rrDHcrsA>tp[J4Rs8VrmaSuMAs8;iqqu?Z3rs/)n
-s8W&pq=O[d"8;cpnB_+Ep\k-lqt^*\s8Mcms5s:Hs*t~>
-YQ"b&mf3=^aSue?s7cQnnbi4^m/R+^pAas0rrDHcrsA>tp[J4Rs8VrmaSuMAs8;iqqu?Z3rs/)n
-s8W&pq=O[d"8;cpnB_+Ep\k-lqt^*\s8Mcms5s:Hs*t~>
-Z2Xmns8;lr!;sn;"T&/ks82cp"o[ras8Vo7rsJ\is8Vlms8Vrhq8WF<pAa^^s7O,+!W)HdrrrAr
-p&Fjcd/OUMs8V?`s763im/6\Zs7kp[J,~>
-Z2Xmns8;lr!;sn;"T&/ks82cp"o[ras8Vo7rsJ\is8Vlms8Vrhq8WF<pAa^^s7O,+!W)HdrrrAr
-p&Fjcd/OUMs8V?`s763im/6\Zs7kp[J,~>
-Z2Xmns8;lr!;sn;"T&/ks82cp"o[ras8Vo7rsJ\is8Vlms8Vrhq8WF<pAa^^s7O,+!W)HdrrrAr
-p&Fjcd/OUMs8V?`s763im/6\Zs7kp[J,~>
-Z2Xn(s7ZEk!<("=%JTc"o`+s`s8;osrV$!+rsnZ#rr;lps8V]js82iroZ7%9r;Zfls8Kh6&,?2!
-s8VZ_s8N&urVuHgq9]-Bo)8Rf!<)lr"T&/hs7>UWJ,~>
-Z2Xn(s7ZEk!<("=%JTc"o`+s`s8;osrV$!+rsnZ#rr;lps8V]js82iroZ7%9r;Zfls8Kh6&,?2!
-s8VZ_s8N&urVuHgq9]-Bo)8Rf!<)lr"T&/hs7>UWJ,~>
-Z2Xn(s7ZEk!<("=%JTc"o`+s`s8;osrV$!+rsnZ#rr;lps8V]js82iroZ7%9r;Zfls8Kh6&,?2!
-s8VZ_s8N&urVuHgq9]-Bo)8Rf!<)lr"T&/hs7>UWJ,~>
-JcD\K$i^,(rqQ?is7cQfqYpL&rqufbs8Vclq>^9frVm*#s8;Qis6kO=Zi>O~>
-JcD\K$i^,(rqQ?is7cQfqYpL&rqufbs8Vclq>^9frVm*#s8;Qis6kO=Zi>O~>
-JcD\K$i^,(rqQ?is7cQfqYpL&rqufbs8Vclq>^9frVm*#s8;Qis6kO=Zi>O~>
-JcD_L"8)Nkqu6U'nG*"ZqZ$<io)JadrVmB'qX=IMs7bs]s763irUtgBZN#F~>
-JcD_L"8)Nkqu6U'nG*"ZqZ$<io)JadrVmB'qX=IMs7bs]s763irUtgBZN#F~>
-JcD_L"8)Nkqu6U'nG*"ZqZ$<io)JadrVmB'qX=IMs7bs]s763irUtgBZN#F~>
-JcD\K#PJ,skP5&Vq#::#o_.tXp[nL^q>]j]rr`,to'u_Z#6"T$s7$'eJcE+WJ,~>
-JcD\K#PJ,skP5&Vq#::#o_.tXp[nL^q>]j]rr`,to'u_Z#6"T$s7$'eJcE+WJ,~>
-JcD\K#PJ,skP5&Vq#::#o_.tXp[nL^q>]j]rr`,to'u_Z#6"T$s7$'eJcE+WJ,~>
-JcD_L%IF,nrq731!!W#pnc/:\rt4`"s7lWbq=FXPs8VfhpAb$UJcDtSJ,~>
-JcD_L%IF,nrq731!!W#pnc/:\rt4`"s7lWbq=FXPs8VfhpAb$UJcDtSJ,~>
-JcD_L%IF,nrq731!!W#pnc/:\rt4`"s7lWbq=FXPs8VfhpAb$UJcDtSJ,~>
-JcDYJ(B"(0!<)ros7`Y[qZ$!UqYL6Xs8VZnrr3B#s6BX_o`+pbs8DYBs0VfV~>
-JcDYJ(B"(0!<)ros7`Y[qZ$!UqYL6Xs8VZnrr3B#s6BX_o`+pbs8DYBs0VfV~>
-JcDYJ(B"(0!<)ros7`Y[qZ$!UqYL6Xs8VZnrr3B#s6BX_o`+pbs8DYBs0VfV~>
-JcD\K%IX/op'^Tks8W#X+WmHXrtN=!%3G$N!W_f!)%smerr?X.s8VM<s0M`U~>
-JcD\K%IX/op'^Tks8W#X+WmHXrtN=!%3G$N!W_f!)%smerr?X.s8VM<s0M`U~>
-JcD\K%IX/op'^Tks8W#X+WmHXrtN=!%3G$N!W_f!)%smerr?X.s8VM<s0M`U~>
-JcDYJ,Q.-4$NL2-q>^3hj8f&KruR9cs7cNns8OOQW&XD@!TX4FpOW@Ms*t~>
-JcDYJ,Q.-4$NL2-q>^3hj8f&KruR9cs7cNns8OOQW&XD@!TX4FpOW@Ms*t~>
-JcDYJ,Q.-4$NL2-q>^3hj8f&KruR9cs7cNns8OOQW&XD@!TX4FpOW@Ms*t~>
-JcD_Ls8)ouqYL9kruD$,-OBeQs7H]rs7lWg"8_usqu-TqrrE)qs8VkFs0M`U~>
-JcD_Ls8)ouqYL9kruD$,-OBeQs7H]rs7lWg"8_usqu-TqrrE)qs8VkFs0M`U~>
-JcD_Ls8)ouqYL9kruD$,-OBeQs7H]rs7lWg"8_usqu-TqrrE)qs8VkFs0M`U~>
-JcDYJ"8)We!<3!=p&>01rrDikrud@%s7-*ro`$&DYVGtR!<;cmqtksEZi>O~>
-JcDYJ"8)We!<3!=p&>01rrDikrud@%s7-*ro`$&DYVGtR!<;cmqtksEZi>O~>
-JcDYJ"8)We!<3!=p&>01rrDikrud@%s7-*ro`$&DYVGtR!<;cmqtksEZi>O~>
-JcD_L!:^!f,P)ZLnGi7\Sdtl&q!J+-,6\YYs.2mR[1!_Rs8EQ/s4mYSq18RQs*t~>
-JcD_L!:^!f,P)ZLnGi7\Sdtl&q!J+-,6\YYs.2mR[1!_Rs8EQ/s4mYSq18RQs*t~>
-JcD_L!:^!f,P)ZLnGi7\Sdtl&q!J+-,6\YYs.2mR[1!_Rs8EQ/s4mYSq18RQs*t~>
-JcD\K-1g[,p\"LbrVulsp%n^]s8Dorq#16Wq>^Kis8VinqXaR_s7Z&8s0M`U~>
-JcD\K-1g[,p\"LbrVulsp%n^]s8Dorq#16Wq>^Kis8VinqXaR_s7Z&8s0M`U~>
-JcD\K-1g[,p\"LbrVulsp%n^]s8Dorq#16Wq>^Kis8VinqXaR_s7Z&8s0M`U~>
-JcDYJ!W)WkrsSc"s8V-Zp&FjVs7H<j&F9Arq>('cq#C-gs8Vops6tU>Zi>O~>
-JcDYJ!W)WkrsSc"s8V-Zp&FjVs7H<j&F9Arq>('cq#C-gs8Vops6tU>Zi>O~>
-JcDYJ!W)WkrsSc"s8V-Zp&FjVs7H<j&F9Arq>('cq#C-gs8Vops6tU>Zi>O~>
-JcD_L$M+5sqYfmXqZ$6crr2ulrVlrmqYC-j!V?9hrsAT#s6]gbmf3=^JcE+WJ,~>
-JcD_L$M+5sqYfmXqZ$6crr2ulrVlrmqYC-j!V?9hrsAT#s6]gbmf3=^JcE+WJ,~>
-JcD_L$M+5sqYfmXqZ$6crr2ulrVlrmqYC-j!V?9hrsAT#s6]gbmf3=^JcE+WJ,~>
-JcD\K"o/#qq>^!aruLn7m/R(bq=s1Rs8Dorp&G'Ym/R%arr;`fs763eJcE+WJ,~>
-JcD\K"o/#qq>^!aruLn7m/R(bq=s1Rs8Dorp&G'Ym/R%arr;`fs763eJcE+WJ,~>
-JcD\K"o/#qq>^!aruLn7m/R(bq=s1Rs8Dorp&G'Ym/R%arr;`fs763eJcE+WJ,~>
-JcD_L%fZM.qu>XTqY1$gs8V`irVm,js8;oos6fmbrs/,plMpVYs87HJZi>O~>
-JcD_L%fZM.qu>XTqY1$gs8V`irVm,js8;oos6fmbrs/,plMpVYs87HJZi>O~>
-JcD_L%fZM.qu>XTqY1$gs8V`irVm,js8;oos6fmbrs/,plMpVYs87HJZi>O~>
-JcF^/s8Miorr1RMrr*T%rr;Kfp&G'is8MThs8)ces8;iprt"o)s7H?`s8VrqqZ$T_s8%<H[/YX~>
-JcE"Ts8Mlp'D2>)nGi1\rV-<hmJ["Yrq$-cqu6U+q#CBds7H?kqZ$Els6]j_JcE+WJ,~>
-JcFR+r;HTorr(LL'D)8(o)JIas82irnc/Xds7?9fr;Q^,q#CBds7H?kqZ$Els6]j_JcE+WJ,~>
-JcFa0"9.ujrql^8qY]s_oD\des8Vros82ioq>^?bs8Dipp%\Odp\t0mpAP!kpAY'pqXjgemJZt^
-rqQKtqt:!es8V]irt4l&qtL-crVuops8Vloqu?Wnrr3N*rVQWiq>^Kfs7ZK_s8VflrsJc*q#:<n
-rVufqq>UBonq$hrs*t~>
-JcF^/-2RZArquTks8W#sq>^-frql`qqYgHks8;]mqt:!fqu??arr;`lrrDckrrDclrWN#gs8VWc
-s!7UBpA+I[p%eISnG`.[rpg$fs7?9fp]($es8Vurs7lWks8Doqrt"u)qu?Hes8VclpAa[_s7ZHl
-$NC)#rr<#ss8;omrr2uhJcFO*J,~>
-JcFR++o([(qYU*gp\jUUq>('jqYgHks8;]mqt:!fqu??arr;`lrrDckrs8>urVcQas8VZ[ru:e-
-s8)cqo)8LdoDe^^s7lQms82irq#C6krVc`q&,Q8%s7lEis7QEcs7--hpAY(!rr;cms8W&tr;ZTl
-rrDV@s4mX)~>
-JcFa0"TJ;qq"t'j#5J&mo(2YUrr4)-s8Vops8;osp&G'equ?]ns7ZHlpAap[q>L!ds7,j_rs8Q%
-l21AUr;Q]prs\Z%s5X.Zqu?]fqu?TlrrN&mr;S>2s7lHis8Vcks75d]rVuZ`s8VNdqYL6dqt]g_
-mf3%Us8W#sr;Z]pr;ZQcJcFI(J,~>
-JcFX-$MjGqrVlKes82`nrVn22s8Vops8;osp&G'equ?]ns7ZHlpAap[q>L!ds6oRYrr;ormJ6br
-rp]jai;EECrVl?\rV6?krr<#rq#(.CkPt>Rrr<#krr;Q\s8Dumo)JaXrqcKkp\XdWs6fp]p](9k
-s8;ops8;olp4<7ts*t~>
-JcFX-2#I"ApA".Ns8)QfqY'^_mJm4^s8W#ss7QElq"t*kqu?Bhs7ZKfo_JIYs8VTZrVuoqs6f[^
-'DVV-kPtSZs8V`fs7uZmr;Zfpq#(.CkPt>Rrr<#krr;Q\s8Dumo)JaXrqcKkp\XdWs6fp]p](9k
-s8;ops8;olp4<7ts*t~>
-JcF^/!VlWms!%:=s8;]^q"t'br;ZZos8;ihs8D-\s7?9gmf2eVs7?-frr2p.n,MqVs8W)hq=ikG
-o_eXdrVm<*o]Yc=kj\96r:U!brr3u7s60L_p](-Ys8Vopp&Fpfs8VWgs8Drns8W&krVluqs8Vfl
-rsSDtnc/4Us6p!fmJd+b!W)M@s4mX)~>
-JcF^/#5S8urqQ'\rr2uprr3l1q>^?ls8;ihs8D-\s7?9gmf2eVs7?-frr2p+n,MqVrr2c`q=iqL
-q#(0lrr<!(rVQWjs8Vrqp\t3mrZ(_5kl:\Ws826as7u]fs82cps7-*grVlZns8DZirr`)ss7ZHl
-$hF>fs7?$cn,NFTrr2ouqY#L?h#Dm~>
-JcF^/-i<rBqXX"Hqu?]os8Vubo)8Ics8;ihs8D-\s7?9gmf2eVs7?-frr2p+n,MqVs8Vudq=inK
-p\=aqp@nUYrVuTkpAb-kruCk7kl:\Ws826as7u]fs82cps7-*grVlZns8DZirr`)ss7ZHl$hF>f
-s7?$cn,NFTrr2ouqY#L?h#Dm~>
-JcF[.!VZNlrs/Dkq""+Oq=FRb$iL&)qu?]ks7Gj]rr2p(qt'ddq>^KaqtpBm"7cEgr;Q`rrW<#s
-rqud,r9`56qsNb7p$i"Np\+Xdrr4#,p]($fs8DEdmJm4Js7#^]p%n^Qs7c9\s7H3foDS\#nFZ,J
-li6eQs7u]bs7QEjrIP"%s*t~>
-JcF[.#kIfhrqcHas8;lr!;6<j$iL&)qu?]ks7Gj]rr2p(qt'ddq>^KaqtpBm"S)NfqYp9is8E-!
-s8Vols!RC;qu?Nmr;Q`rme?bVrr;rcs6]jdjo=iCs7Q6gl2UMPp&F[]rq$*g&Ff>Zs6K^\o`+ae
-nc/:^rV_<Ig&HR~>
-JcF^/#Q=,apA"@Up\b%&o_SF_s8;osqu?]ks7Gj]rr2p(qt'ddq>^KaqtpBm"7cEfqtg?mrVZNn
-qW\"U$2aJon,<"\q>^EmruLP%s7lTnrU9dRs8V3\nFchSqZ#g[p\4@\o_\XZrVmGuo^2\Es7u<e
-q>^!bp&G!hJcFF'J,~>
-JcFR+/,]GFo^;/;mHsiOnc/OTrqcZpnbi1[s8V`[s7--hrVuoppAa^`q#16moD\amlMpkMq"aq%
-jRW?Jn*095s8V]kMYmAQpAP!j!Vu?drrW3"nGE4do_SIb"7-!equ6U1r:p<lqu6Wgs8W)dq#BOW
-nc/O^s71a@h#Dm~>
-JcF^/0E1hIqtL-jrVZ]qqtC'hm/R"OrqcZpnbi1[s8V`[s7--hrVuoppAa^`q#16moD\^llMpnP
-qYpKsrr)fnrr3#smf!.lr;HX+Q2gjapAP!j!Vu?drrW3"nGE4do_SIb"7-!equ6U1r:p<lqu6Wg
-s8W)dq#BOWnc/O^s71a@h#Dm~>
-JcFa0#6"Dhq"=7Wq>VW:l2CPJrqcZpnbi1[s8V`[s7--hrVuoppAa^`q#16moD\aolMpnMp\k!f
-q@iYtl1+B,o(MkOqYU*qOoPF_pAP!j!Vu?drrW3"nGE4do_SIb"7-!equ6U1r:p<lqu6Wgs8W)d
-q#BOWnc/O^s71a@h#Dm~>
-JcF^/!<)lr!WLRF!!)3]rrDurrrM]`rr4#<s8Dutmf34ZlNHGOs6]j\q#BUYs7lWis7u`pp\Fh:
-q#C@Fro2i8s8;Ef,i\_%s8W&rqu?]`q>^Ejr;Zfhp&":Zs8)chrVmf)s7QElr<Duqo(2nZs7Yj[
-s8;osq!nFbq>,[Bg])d~>
-JcF^/%JKYtr;ZfP!t,\D!se/krW)lqrrM]`rr48Cs8Dutmf34ZlNHGOs6]j\q#BUYs7ZHfs7u`q
-q#CBnrr)cmrr4;0!<;uds7b[S$o%#I!WW2urVQWpmJ6e\qu$Koo_&+Os8VrqpAP"0n,N(\s8</q
-s75d]r;ZKXs8W#ss7l-bs7uMBs4dR(~>
-JcFa0%f>bcp@eIbjUr[b*#TRdrr2urrr3#ip&=t6rr;rss6fpbpZhtGs8VKdp\=dQs8Vopq>^9k
-rU]p_q#0n7oBbi)!:St(pA!kE!\EX:"98E"rVQWpmJ6e\qu$Koo_&+Os8VrqpAP"0n,N(\s8</q
-s75d]r;ZKXs8W#ss7l-bs7uMBs4dR(~>
-JcFU,0_b/5nc0@A)Bf+Tr9a@cDZ@!m+M@Hb&2:6coEBRJi"l@nquD0F4octcq#GsYf`1pNpAY1]
-W>PR54o>:eK_YWI\-+Rmp[8T=!9F1Zr:g8On\HRa[/[?N$cW/?!/D$K!!!K.#^?;2!@ln'!.G1*
-rr<Q0s8VqHs4mX)~>
-JcF^/rr+VFoDeh(1,q3S$ig.jqZ^s<Z7@'1pV@CpXo@qrHO8UH!!)osIK)J2-f"LtIh:94rW)rt
-:&b+hr;6Bho)MJbs8Mi`li.3-NW0%Z\-+Rmp[8T=!9F1Zr:g8On\HRa[/[?N$cW/?!/D$K!!!K.
-#^?;2!@ln'!.G1*rr<Q0s8VqHs4mX)~>
-JcFa02#I(Aq""+Xs!^]E=&pLEs7#skDZ@!m+M@Hb&2:6coEBRJi"l@nquD0F4octcq#H![gAh-P
-;#C+ap@S"KoC(o*!)`gamdp/BqZ(>hrrW51">[:Wmga[EjT#5Wp]-<D_']f$s0*LO`W,Z4LCNMK
-!"Jr6GQ0c+.bst&IL"O*!"T)0s8%<Hh#Dm~>
-JcF[.2ZECKq<[JK.L[aH!:0I[r<;BU0s\bNs!GUg3rf-ZT>a@o"98$!iq39N_(,HbT#O%lrrrH"
-q#:FNb/tt+rt#2#*9R>",e^!,s8N(qcoh4)s!S!#ruo+n3:8Z;djG+p!2mk'rs\kr!oEtUs4Jao
-$(K:1p%o!ns8;bFs4mX)~>
-JcF^/>Q4Hjs8Vrq##$dG3s>E[rqlr_o.dPi0)m98Z9&$a!M@>%o`P6e#NGCUs1p2b!1^tmqYpa!
-r;ZfrrVZTjq=Xe^;?6Xln`TB@#Q,n7!WF@XUbDcJ!2\%)qu6UD"4mJq^;;kt28.Hcs8N(sa$K_6
-rUBsGs8DuN-MRn:cpdX)#QFc$qgne&s*t~>
-JcFa0"9&)fnGW@j,&^P'>p0+G8d4DL0s\bNs!GUg3rf-ZT>a@o"98$!iq39N_(,HbT>s:prr`/l
-qY'XToCMM>joALi!:9+@nG)h[pa#;0r?T(P0E;%PV9h@%rr4AKf)Ho-_Dps@^!e>.rrAt;62qAl
-nH.SIrVtOtp^*G:7J6N_rr;onJcFO*J,~>
-JcF[.s8FeI2Bi\4*tSl'!$3pLqZHll"98E-q[NT+#l+6"s7-Bf!rW''s8S?*'F=C6s7cTorrE&u
-:@eGbqi?2_*N[!.S.UX>YQ+V'mu;r"oaLK^)#rk,s7??l!!*$!s8N'!$j-G6"oo5(rsA_u!<3W!
-rr_upRj&+Ao+(g#!<;rsrVuZiJcFO*J,~>
-JcF[.2#dOP6T$>*7QNIq$SO\"qucum"98E-q[NT+#l+6"s7-Bf!rW''s8S?*'F=C6rq6?lrrE&u
-:@A,[mX]1l!+@fjAH6XYC#eq!ooF_*oaLK^)#rk,s7??l!!*$!s8N'!$j-G6"oo5(rsA_u!<3W!
-rr_upRj&+Ao+(g#!<;rsrVuZiJcFO*J,~>
-JcF^/J,T<Fs%GgJ0l:B,B.6DM5Q:icp&k?q#l>)3!!rAr"on,tp&b0l#QOgh*Y\nR!rr&ts8N)r
-qYK=Io]:=,@fT_&BjLdM@WUo.$1j12!qcuon/22j#ljMtrr<'!!<<'!!"8r/#6k/>nG`gpo`5"'
-n,EL`s-k2<"nN6(rs&Q(quH]qq"oXBh#Dm~>
-JcFa0s8G"LqtTIEjo>c2%3PZ6o_ACcs8Oghs6ot:](5n$quHQl!<3E%rrE)h(<PnCrr`<$&G#K)
-p@S:Zrr2urrr3'UdD@%'s#L/ZlQ-60*P8Bip\t6nrrE#os6g9on1)iYp\u8Ns8W#s!:g'jo`4@Y
-!<<!&eF3bD!<;cooCidipjrJ!s*t~>
-JcF[.%/9f%q#C@"1.sPq"o\H#F9)@@0u3hXs!bPMs8Vusq>LBo$Mj]%s7$lHli@%frr*K"oagch
-qtL*br9jFWp]+T!!;cB\jR2aKs61C$rZ/VP)#+%1s8N)tqZ$!js6qMcp%SJ,_Z0Z6rrDTh!qcQ[
-rrE)t#Lr5KrrE)n!V?$rp\9=>gAc[~>
-JcF[.!;QKl"XmMo?Y^nbs)\8@s"V=hn,FiJo)Jaf!;ZTorsJT%!<;R)am9$-"98B6o(<IanFlAF
-n+>`4mcXXa>la0Tna6&B!WD:("TKLSX;L^3!<<'!rVHQ_$30KEdIm86*Q%jVr;QcerrVinm/I(c
-r<LjA#lao)pAsm[&,5jMs4[L'~>
-JcFa0%K?5!q=jC<lMr4O$n;8VmO%o5s5q6=);P#=*Z!,hrrE)s%KE(ZnIYa#9)o8-e-,gO!;-;\
-7fE>erql]s15d%Is#^;]s.Bkp_uq1.s7u`frrDoqmf*:en%fhOjnlt9'A`We!<<'!s/\TW%dO'i
-!"/ej&HDb1s8ScZs7,o9s4mX)~>
-JcFR+2uW@J#"_6=9a1RopAY-mk/82Vh"]JB(=;FJ!<;s+s.D:?&HDc'!!s+a"TJB#o`(COs8N&t
-p\k"[q"Xn[#QOhpmHXZRs8A8fo>CbRci<hAo`"pfs6fmes6mc@&*<],*#%0,rrE*!!<9,fn.+a`
-:B1b&kRddo!<<(m6N?TOJcFO*J,~>
-JcFI("X7_q<c'&Zs)nDBk/82Vh"]JB(=;FJ!<;s+s.D:?&HDc'!!s+a"TSK%o`(=Jqtg0an+HDJ
-oB#6;7KDoIlgX<<!<)kb#kZ%<+4'u`!;-9kqZ$!`!<;N((_>a*`#KHHrVlltrrE)#6gtTNs%`V&
-!9b!orrE*!TgJeLq18S$s*t~>
-JcFa0#QFAjo^q\GmeZtdnb)_Drr4#6s5CEdk5Y1pr;Zfrs8W&ns8Vfiqu?]qp]'a_pAY'qrqu<d
-s8Drs#lXSrq>WSFp[.t[!qZ<arVlurs5WhOrt5),s763ir;Q`rqt-f`s8Vtprr35ls8ViZs8W)s
-rsJ>sqY'ses7H'cnq$hos*t~>
-JcF^/%/g/&rVHQo%0d"L$jHY1!:9^b*r,co[f>LipVm(1s8N&urV?KnpA=aes8McmnG`(Zrr4&=
-oDejerr)fdp&Fg[)=RUsrqH3Ys7c?cpAXpgj7`HO&c)J,o)Jafrr<#qoV_Tds8/bors/#ms7bjZ
-s8Mus$M+5npAb'jo_8CVJcFF'J,~>
-JcFI("X+p2/g_P:rrDKdruLn7iO8dKs7aM1s8W)us8Dcns7Z?es8W)ms7$'_rVm#tnGW7Wrq[Du
-m.'6,'ArECo_/(IqXaO[q>^<kj7`HO&c)J,o)Jafrr<#qoV_Tds8/bors/#ms7bjZs8Mus$M+5n
-pAb'jo_8CVJcFF'J,~>
-JcFa0*<,j2kPY2Iq![b4mdBW<k4elEo`+m`s8Vins6K[a"8DiqpAY(!fDkgArVu]cr;ZKhrs/E"
-r;QZcs8N#t$hO2lp%e7Tqu?]qo)/Lpp%\Ods7Q<ep\*bKrr`8ss8)`p)#F%)o`+XRs8VQ^s8W&r
-s8V?Vs8V?`qtKse!;;!Dg])d~>
-JcFX-#OMKip](0kq#14%med%Ro`+m`s8Vins6K[a"8DiqpAY(&fDkgArVu]cr;ZEgrr<#rrVm#k
-s8)`mrr3#jm/I"hrr)Wlq>^KorX/>nrr<#kr;66^k5PA_rqcZkrr3i3q"s^`p@&%]n+Zk^rVccr
-l1P)Vl2UYTqYpQhJcFL)J,~>
-JcFX-"7Z?jqYC.#nbiFVo`+m`s8Vins6K[a"8DiqpAY(!fDkgArVu]cr;ZHgrrDrqrt>8!qtL!a
-qXX:DjnAKEp%\7Wnc&CorVuQcrr<#kr;66^k5PA_rqcZkrr3i3q"s^`p@&%]n+Zk^rVccrl1P)V
-l2UYTqYpQhJcFL)J,~>
-JcFa0"TIKZrqHEl+S5*sp##99rr;lqnc/X]s7ZHls8Mues8W&os7--ds82cp!rN#mrr4>9lMpkT
-q>C0is7--dr;69hs82H]r;-Ejnbhn?s8W&rs7cQls8;lr#lF>qp@81_pZ_YV!;$3i!VH9grs88s
-r:p<lrr;Tgrs8MqrV?<is7uJAs4mX)~>
-JcF^/%-dflp\t3mp\=dgm/$_](]47&s8V`kpAY*lrr)Bes8Dfonc/Ldqu-O&qu?Hirqu9Ns8Vil
-rVluirqH?frt>80qu?]ks75ITs8Dorp](3lr;Q^%qtC'`nc/X`l2CV^oD\ajo_\Xf#k\/pq#CBn
-s7?3h#lF>oq>1-kq>#UAh#Dm~>
-JcF^/-ggs6p&"XbpA=mio)/Obqt^9^s8V`kpAY*lrr)Bes8Dfonc/Ldqu-O&qu?Hjs8DKQs8Vfj
-rVmQ$s7lHfp\Oa`p\"FVq>0UXmH+6Er<<5qs8Duqrr39$pAajVs8ViXrVllhrr3#kqu6U$o`+ja
-s8W)uoDS[pqtC!aqZ$Tkq18S$s*t~>
-JcFa0!ri,srr3r9s8W#qp\b$cq#CBXs8VEbs7uB_s8VZis7H6grsA5ls7c6emJm4Zrr2ugrr4PK
-n,)_Sr;Q]qs8)Qjs8V?`rr;umo_\IZq"FRas8)0`q#C0iq"jgdqu?]jrr3Don,NCenF6>Tnc/UW
-rVmK!s7cQgmJm(^s8W&ts6p!frIP"(s*t~>
-JcFU,*W5m-pAXmer;Zflo`"mSs8VEbs7uB_s8VZis7H6grsnSqs7c6emJm4Zs8N#brr2p!oDeX`
-rsSMfoDJRFr;-HnrV?Bk&cD\/qX4CYs7u]iqYC0gs8Vimrs\;`s8N&fnGE7Us8MKcrt4c#p]($U
-s82cps8Dutn,NFdJcFO*J,~>
-JcFX-*rYm+l0\38q=spbmca9>kl:\Ks8Vogp](9as8V`hrr3GtqZ$<`s6]jdp&G'jm/?qco`+aa
-rsSYpp](9Rs7uWjp[\:Z')25%s8)0`q#C0iq"jgdqu?]jrr3Don,NCenF6>Tnc/UWrVmK!s7cQg
-mJm(^s8W&ts6p!frIP"(s*t~>
-JcF^/"Sr&ns8Dor"8`&mr;Q^;qZ$Tjs8Vurs8;lgs8V`ks82cks8VNes7u]pqu?<frsAW%q#:3_
-r;-6frr2os!;HKm%/U#'oDJLMq!n+XrVZWo'Dqgus8W#ps7ZKis7cNmrqlQlqu6U4qu?WprqZEj
-s8;ogs8VTgpAb0ks7?9]pOWA!s*t~>
-JcF^/;YpFhrq-0fp](!fq>C9mrV?Knq#CBks8W#ro`+s`s8VupqZ$T`s8Vops82igrql]krV6Em
-p](6krr<#srV?<XrVHB\rr;ips7?9is8W&qrVmQ.s6p!fr;?Tgs82ijrr;upqZ$HlrttY5rVulm
-qZ$Tns7?9jnGi4^s8Duhs75o8s4dR(~>
-JcF^/3r]0RqWmbEme6/HoCVbJo(E%_q#CBks8W#ro`+s`s8VupqZ$T`s8Vops82igs8)]krV6Em
-q#CBnqYpQerr3E!s8DQdqWn%NqYgBjrVmQ.s6p!fr;?Tgs82ijrr;upqZ$HlrttY5rVulmqZ$Tn
-s7?9jnGi4^s8Duhs75o8s4dR(~>
-JcDeNr;Q<frVlfoJcDPGJ,~>
-JcFR+rVkLMs8MZj!WN&prdk*=s*t~>
-JcFU,!<)imrVc`m!<(%>qYc!FV#Pr~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-JcC<$JcE+WJ,~>
-%%EndData
-showpage
-%%Trailer
-end
-%%EOF
diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml
index 65bf0345f5..af6e87b56b 100644
--- a/lib/megaco/doc/src/notes.xml
+++ b/lib/megaco/doc/src/notes.xml
@@ -36,6 +36,132 @@
section is the version number of Megaco.</p>
<section>
+ <title>Megaco 3.14.1.1</title>
+
+ <p>Version 3.14.1.1 supports code replacement in runtime from/to
+ version 3.14.1, 3.14, 3.13, 3.12 and 3.11.3.</p>
+
+ <section>
+ <title>Improvements and new features</title>
+
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>Updated the
+ <seealso marker="megaco_performance">performance</seealso>
+ chapter. </p>
+ <p>Own Id: OTP-8696</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>A race condition when, during high load, processing
+ both the original and a resent message and delivering
+ this as two separate messages to the user. </p>
+ <p>Note that this solution only protects against multiple
+ reply deliveries! </p>
+ <p>Own Id: OTP-8529</p>
+ <p>Aux Id: Seq 10915</p>
+ </item>
+
+ <item>
+ <p>Fix shared libraries installation. </p>
+ <p>The flex shared lib(s) were incorrectly installed as data
+ files. </p>
+ <p>Peter Lemenkov</p>
+ <p>Own Id: OTP-8627</p>
+ </item>
+
+ <item>
+ <p>Eliminated a possible race condition while creating
+ pending counters. </p>
+ <p>Own Id: OTP-8634</p>
+ <p>Aux Id: Seq 11579</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+
+ </section> <!-- 3.14.1.1 -->
+
+
+ <section>
+ <title>Megaco 3.14.1</title>
+
+ <p>Version 3.14.1 supports code replacement in runtime from/to
+ version 3.14, 3.13, 3.12 and 3.11.3.</p>
+
+ <section>
+ <title>Improvements and new features</title>
+
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>A minor compiler related performance improvement. </p>
+ <p>Own Id: OTP-8561</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>A race condition when, during high load, processing
+ both the original and a resent message and delivering
+ this as two separate messages to the user. </p>
+ <p>Note that this solution only protects against multiple
+ reply deliveries! </p>
+ <p>Own Id: OTP-8529</p>
+ <p>Aux Id: Seq 10915</p>
+ </item>
+
+ <item>
+ <p>Fix shared libraries installation. </p>
+ <p>The flex shared lib(s) were incorrectly installed as data
+ files. </p>
+ <p>Peter Lemenkov</p>
+ <p>Own Id: OTP-8627</p>
+ </item>
+
+ <item>
+ <p>Eliminated a possible race condition while creating
+ pending counters. </p>
+ <p>Own Id: OTP-8634</p>
+ <p>Aux Id: Seq 11579</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ </section> <!-- 3.14.1 -->
+
+
+ <section>
<title>Megaco 3.14</title>
<p>Version 3.14 supports code replacement in runtime from/to
@@ -80,7 +206,7 @@
<item>
<p>Callbacks, when the callback module is unknown (undefined),
results in warning messages. </p>
- <p>A raise condition scenario. As part of a cancelation operation,
+ <p>A race condition scenario. As part of a cancelation operation,
replies with waiting acknowledgements is cancelled. This includes
informing the user (via a call to the handle_trans_ack callback
function). It is possible that at this point the connection data
@@ -612,13 +738,16 @@
<list type="bulleted">
<item>
- <p>Unexpected <seealso marker="megaco_user#unexpected_trans">handle_unexpected_reply</seealso> callbacks. </p>
- <p>The <seealso marker="megaco_user">megaco_user</seealso> callback function
+ <p>Unexpected
+ <seealso marker="megaco_user#unexpected_trans">handle_unexpected_reply</seealso>
+ callbacks. </p>
+ <p>The <seealso marker="megaco_user">megaco_user</seealso> callback
+ function
<seealso marker="megaco_user#unexpected_trans">handle_unexpected_reply</seealso>
could during high load be called with unexpected values for the Trans
- argument, such as an <c>TransactionReply</c> where <c>transactionResult</c>
- had the value <c>{error, timeout}</c>. This was a result of a raise condition
- and has now been fixed. </p>
+ argument, such as an <c>TransactionReply</c> where
+ <c>transactionResult</c> had the value <c>{error, timeout}</c>.
+ This was a result of a race condition and has now been fixed. </p>
<p>Own Id: OTP-7926</p>
<p>Aux Id: Seq 11255</p>
</item>
@@ -813,416 +942,6 @@
</section>
</section> <!-- 3.10 -->
-
- <section>
- <title>Megaco 3.9.4</title>
-
- <p>Version 3.9.4 supports code replacement in runtime from/to
- version 3.9.3, 3.9.2, 3.9.1.1, 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
- when using any of the drivers (flex for text or asn1 for binary).</p>
-
- <section>
- <title>Improvements and new features</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>Miscellaneous dialyzer related and test case cleanup. </p>
- <p>Own Id: OTP-7614</p>
- </item>
-
- </list>
--->
- </section>
-
- <section>
- <title>Fixed bugs and malfunctions</title>
-<!--
- <p>-</p>
--->
-
- <list type="bulleted">
- <item>
- <p>Segmenting a reply failed (with a badmatch) if the message
- did not actually need to be segmented (e.g. was within the
- size limit,
- <seealso marker="megaco#ui_max_pdu_size">max_pdu_size</seealso>). </p>
- <p>Own Id: OTP-7733</p>
- <p>Aux Id: Seq 11168</p>
- </item>
-
- <item>
- <p>Improve the error handling of megaco_tcp for received
- messages. </p>
- <p>Own Id: OTP-7728</p>
- </item>
-
- </list>
-
- </section>
-
- <section>
- <title>Incompatibilities</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>For those implementing their own codec's, the new megaco_encoder
- behaviour will require three more functions. See above for more
- info. </p>
- <p>Own Id: OTP-7168</p>
- <p>Aux Id: Seq 10867</p>
- </item>
-
- </list>
--->
-
- </section>
- </section> <!-- 3.9.3.1 -->
-
-
- <section>
- <title>Megaco 3.9.3</title>
-
- <p>Version 3.9.3 supports code replacement in runtime from/to
- version 3.9.2, 3.9.1.1, 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
- when using any of the drivers (flex for text or asn1 for binary).</p>
-
- <section>
- <title>Improvements and new features</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>Miscellaneous dialyzer related and test case cleanup. </p>
- <p>Own Id: OTP-7614</p>
- </item>
-
- </list>
--->
- </section>
-
- <section>
- <title>Fixed bugs and malfunctions</title>
-<!--
- <p>-</p>
--->
-
- <list type="bulleted">
- <item>
- <p>Memory leak in the flex scanner. There was a memory
- leak in the flex scanner function handling
- Property Parameters. </p>
- <p>Own Id: OTP-7700</p>
- <p>Aux Id: Seq 11126</p>
- </item>
-
- </list>
-
- </section>
-
- <section>
- <title>Incompatibilities</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>For those implementing their own codec's, the new megaco_encoder
- behaviour will require three more functions. See above for more
- info. </p>
- <p>Own Id: OTP-7168</p>
- <p>Aux Id: Seq 10867</p>
- </item>
-
- </list>
--->
-
- </section>
- </section> <!-- 3.9.3 -->
-
-
- <section>
- <title>Megaco 3.9.2</title>
-
- <p>Version 3.9.2 supports code replacement in runtime from/to
- version 3.9.1.1, 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
- when using any of the drivers (flex for text or asn1 for binary).</p>
-
- <section>
- <title>Improvements and new features</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>Miscellaneous dialyzer related and test case cleanup. </p>
- <p>Own Id: OTP-7614</p>
- </item>
-
- </list>
--->
- </section>
-
- <section>
- <title>Fixed bugs and malfunctions</title>
-<!--
- <p>-</p>
--->
-
- <list type="bulleted">
- <item>
- <p>The text encoders (v1, v2, v3, ...) all failed to
- properly encode the DigitMapDescriptor. </p>
- <p>Own Id: OTP-7671</p>
- <p>Aux Id: Seq 11113</p>
- </item>
-
- <item>
- <p>The mini decoder some time incorrectly identifies
- plain text as tokens. </p>
- <p>Own Id: OTP-7672</p>
- <p>Aux Id: Seq 11103</p>
- </item>
-
- </list>
-
- </section>
-
- <section>
- <title>Incompatibilities</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>For those implementing their own codec's, the new megaco_encoder
- behaviour will require three more functions. See above for more
- info. </p>
- <p>Own Id: OTP-7168</p>
- <p>Aux Id: Seq 10867</p>
- </item>
-
- </list>
--->
-
- </section>
- </section> <!-- 3.9.2 -->
-
-
- <section>
- <title>Megaco 3.9.1.1</title>
-
- <p>Version 3.9.1.1 supports code replacement in runtime from/to
- version 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
- when using any of the drivers (flex for text or asn1 for binary).</p>
-
- <section>
- <title>Improvements and new features</title>
-<!--
- <p>-</p>
--->
-
- <list type="bulleted">
- <item>
- <p>Miscellaneous dialyzer related and test case cleanup. </p>
- <p>Own Id: OTP-7614</p>
- </item>
-
- </list>
- </section>
-
- <section>
- <title>Fixed bugs and malfunctions</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>[text] The flex scanner did not allow an empty quotedString
- in propertyParm. </p>
- <p>Own Id: OTP-7573</p>
- <p>Aux Id: Seq 11062</p>
- </item>
-
- <item>
- <p>[text] Unable to decode a version 2 message with a
- topologyTriple containing an (optional) eventStream. </p>
- <p>Own Id: OTP-7576</p>
- <p>Aux Id: Seq 11066</p>
- </item>
-
- </list>
--->
-
- </section>
-
- <section>
- <title>Incompatibilities</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>For those implementing their own codec's, the new megaco_encoder
- behaviour will require three more functions. See above for more
- info. </p>
- <p>Own Id: OTP-7168</p>
- <p>Aux Id: Seq 10867</p>
- </item>
-
- </list>
--->
-
- </section>
- </section> <!-- 3.9.1.1 -->
-
-
- <section>
- <title>Megaco 3.9.1</title>
-
- <p>Version 3.9.1 supports code replacement in runtime from/to
- version 3.9, 3.8.2, 3.8.1 and 3.8 except
- when using any of the drivers (flex for text or asn1 for binary).</p>
-
- <section>
- <title>Improvements and new features</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>[text] The text codec(s) has been optimized. The parsing of
- "property parameters" has been moved to the scanner(s). Which means
- that when decoding messages containing property parameters, using
- the flex scanner, decode time(s) will be reduced. The reduction
- depends on the message, but can be as large as 25%. </p>
- <p>Own Id: OTP-7431</p>
- </item>
-
- </list>
--->
- </section>
-
- <section>
- <title>Fixed bugs and malfunctions</title>
-<!--
- <p>-</p>
--->
-
- <list type="bulleted">
- <item>
- <p>[text] The flex scanner did not allow an empty quotedString
- in propertyParm. </p>
- <p>Own Id: OTP-7573</p>
- <p>Aux Id: Seq 11062</p>
- </item>
-
- <item>
- <p>[text] Unable to decode a version 2 message with a
- topologyTriple containing an (optional) eventStream. </p>
- <p>Own Id: OTP-7576</p>
- <p>Aux Id: Seq 11066</p>
- </item>
-
- </list>
-
- </section>
-
- <section>
- <title>Incompatibilities</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>For those implementing their own codec's, the new megaco_encoder
- behaviour will require three more functions. See above for more
- info. </p>
- <p>Own Id: OTP-7168</p>
- <p>Aux Id: Seq 10867</p>
- </item>
-
- </list>
--->
-
- </section>
- </section> <!-- 3.9.1 -->
-
-
- <section>
- <title>Megaco 3.9</title>
-
- <p>Version 3.9 supports code replacement in runtime from/to
- version 3.8.2, 3.8.1 and 3.8 except
- when using any of the drivers (flex for text or asn1 for binary).</p>
-
- <section>
- <title>Improvements and new features</title>
-<!--
- <p>-</p>
--->
-
- <list type="bulleted">
- <item>
- <p>[text] The text codec(s) has been optimized. The parsing of
- "property parameters" has been moved to the scanner(s). Which means
- that when decoding messages containing property parameters, using
- the flex scanner, decode time(s) will be reduced. The reduction
- depends on the message, but can be as large as 25%. </p>
- <p>Own Id: OTP-7431</p>
- </item>
-
- </list>
- </section>
-
- <section>
- <title>Fixed bugs and malfunctions</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>If a TransactionRequest arrives while a user is
- connecting (is in the callback function
- handle_connect as a result of a megaco:connect call),
- megaco responds with a pending message and then drops
- the request.</p>
- <p>These messages will now be silently dropped, forcing the
- other side to resend. </p>
- <p>Own Id: OTP-7192</p>
- <p>Aux Id: Seq 10884</p>
- </item>
-
- </list>
--->
-
- </section>
-
- <section>
- <title>Incompatibilities</title>
- <p>-</p>
-
-<!--
- <list type="bulleted">
- <item>
- <p>For those implementing their own codec's, the new megaco_encoder
- behaviour will require three more functions. See above for more
- info. </p>
- <p>Own Id: OTP-7168</p>
- <p>Aux Id: Seq 10867</p>
- </item>
-
- </list>
--->
-
- </section>
- </section> <!-- 3.9 -->
-
-
<!-- section>
<title>Release notes history</title>
<p>For information about older versions see
diff --git a/lib/megaco/doc/src/notes_history.xml b/lib/megaco/doc/src/notes_history.xml
index 640b62230f..220ed4bbb1 100644
--- a/lib/megaco/doc/src/notes_history.xml
+++ b/lib/megaco/doc/src/notes_history.xml
@@ -33,6 +33,415 @@
</header>
<section>
+ <title>Megaco 3.9.4</title>
+
+ <p>Version 3.9.4 supports code replacement in runtime from/to
+ version 3.9.3, 3.9.2, 3.9.1.1, 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
+ when using any of the drivers (flex for text or asn1 for binary).</p>
+
+ <section>
+ <title>Improvements and new features</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>Miscellaneous dialyzer related and test case cleanup. </p>
+ <p>Own Id: OTP-7614</p>
+ </item>
+
+ </list>
+-->
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>Segmenting a reply failed (with a badmatch) if the message
+ did not actually need to be segmented (e.g. was within the
+ size limit,
+ <seealso marker="megaco#ui_max_pdu_size">max_pdu_size</seealso>). </p>
+ <p>Own Id: OTP-7733</p>
+ <p>Aux Id: Seq 11168</p>
+ </item>
+
+ <item>
+ <p>Improve the error handling of megaco_tcp for received
+ messages. </p>
+ <p>Own Id: OTP-7728</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>For those implementing their own codec's, the new megaco_encoder
+ behaviour will require three more functions. See above for more
+ info. </p>
+ <p>Own Id: OTP-7168</p>
+ <p>Aux Id: Seq 10867</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+ </section> <!-- 3.9.3.1 -->
+
+
+ <section>
+ <title>Megaco 3.9.3</title>
+
+ <p>Version 3.9.3 supports code replacement in runtime from/to
+ version 3.9.2, 3.9.1.1, 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
+ when using any of the drivers (flex for text or asn1 for binary).</p>
+
+ <section>
+ <title>Improvements and new features</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>Miscellaneous dialyzer related and test case cleanup. </p>
+ <p>Own Id: OTP-7614</p>
+ </item>
+
+ </list>
+-->
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>Memory leak in the flex scanner. There was a memory
+ leak in the flex scanner function handling
+ Property Parameters. </p>
+ <p>Own Id: OTP-7700</p>
+ <p>Aux Id: Seq 11126</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>For those implementing their own codec's, the new megaco_encoder
+ behaviour will require three more functions. See above for more
+ info. </p>
+ <p>Own Id: OTP-7168</p>
+ <p>Aux Id: Seq 10867</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+ </section> <!-- 3.9.3 -->
+
+
+ <section>
+ <title>Megaco 3.9.2</title>
+
+ <p>Version 3.9.2 supports code replacement in runtime from/to
+ version 3.9.1.1, 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
+ when using any of the drivers (flex for text or asn1 for binary).</p>
+
+ <section>
+ <title>Improvements and new features</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>Miscellaneous dialyzer related and test case cleanup. </p>
+ <p>Own Id: OTP-7614</p>
+ </item>
+
+ </list>
+-->
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>The text encoders (v1, v2, v3, ...) all failed to
+ properly encode the DigitMapDescriptor. </p>
+ <p>Own Id: OTP-7671</p>
+ <p>Aux Id: Seq 11113</p>
+ </item>
+
+ <item>
+ <p>The mini decoder some time incorrectly identifies
+ plain text as tokens. </p>
+ <p>Own Id: OTP-7672</p>
+ <p>Aux Id: Seq 11103</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>For those implementing their own codec's, the new megaco_encoder
+ behaviour will require three more functions. See above for more
+ info. </p>
+ <p>Own Id: OTP-7168</p>
+ <p>Aux Id: Seq 10867</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+ </section> <!-- 3.9.2 -->
+
+
+ <section>
+ <title>Megaco 3.9.1.1</title>
+
+ <p>Version 3.9.1.1 supports code replacement in runtime from/to
+ version 3.9.1, 3.9, 3.8.2, 3.8.1 and 3.8 except
+ when using any of the drivers (flex for text or asn1 for binary).</p>
+
+ <section>
+ <title>Improvements and new features</title>
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>Miscellaneous dialyzer related and test case cleanup. </p>
+ <p>Own Id: OTP-7614</p>
+ </item>
+
+ </list>
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>[text] The flex scanner did not allow an empty quotedString
+ in propertyParm. </p>
+ <p>Own Id: OTP-7573</p>
+ <p>Aux Id: Seq 11062</p>
+ </item>
+
+ <item>
+ <p>[text] Unable to decode a version 2 message with a
+ topologyTriple containing an (optional) eventStream. </p>
+ <p>Own Id: OTP-7576</p>
+ <p>Aux Id: Seq 11066</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>For those implementing their own codec's, the new megaco_encoder
+ behaviour will require three more functions. See above for more
+ info. </p>
+ <p>Own Id: OTP-7168</p>
+ <p>Aux Id: Seq 10867</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+ </section> <!-- 3.9.1.1 -->
+
+
+ <section>
+ <title>Megaco 3.9.1</title>
+
+ <p>Version 3.9.1 supports code replacement in runtime from/to
+ version 3.9, 3.8.2, 3.8.1 and 3.8 except
+ when using any of the drivers (flex for text or asn1 for binary).</p>
+
+ <section>
+ <title>Improvements and new features</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>[text] The text codec(s) has been optimized. The parsing of
+ "property parameters" has been moved to the scanner(s). Which means
+ that when decoding messages containing property parameters, using
+ the flex scanner, decode time(s) will be reduced. The reduction
+ depends on the message, but can be as large as 25%. </p>
+ <p>Own Id: OTP-7431</p>
+ </item>
+
+ </list>
+-->
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>[text] The flex scanner did not allow an empty quotedString
+ in propertyParm. </p>
+ <p>Own Id: OTP-7573</p>
+ <p>Aux Id: Seq 11062</p>
+ </item>
+
+ <item>
+ <p>[text] Unable to decode a version 2 message with a
+ topologyTriple containing an (optional) eventStream. </p>
+ <p>Own Id: OTP-7576</p>
+ <p>Aux Id: Seq 11066</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>For those implementing their own codec's, the new megaco_encoder
+ behaviour will require three more functions. See above for more
+ info. </p>
+ <p>Own Id: OTP-7168</p>
+ <p>Aux Id: Seq 10867</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+ </section> <!-- 3.9.1 -->
+
+
+ <section>
+ <title>Megaco 3.9</title>
+
+ <p>Version 3.9 supports code replacement in runtime from/to
+ version 3.8.2, 3.8.1 and 3.8 except
+ when using any of the drivers (flex for text or asn1 for binary).</p>
+
+ <section>
+ <title>Improvements and new features</title>
+<!--
+ <p>-</p>
+-->
+
+ <list type="bulleted">
+ <item>
+ <p>[text] The text codec(s) has been optimized. The parsing of
+ "property parameters" has been moved to the scanner(s). Which means
+ that when decoding messages containing property parameters, using
+ the flex scanner, decode time(s) will be reduced. The reduction
+ depends on the message, but can be as large as 25%. </p>
+ <p>Own Id: OTP-7431</p>
+ </item>
+
+ </list>
+ </section>
+
+ <section>
+ <title>Fixed bugs and malfunctions</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>If a TransactionRequest arrives while a user is
+ connecting (is in the callback function
+ handle_connect as a result of a megaco:connect call),
+ megaco responds with a pending message and then drops
+ the request.</p>
+ <p>These messages will now be silently dropped, forcing the
+ other side to resend. </p>
+ <p>Own Id: OTP-7192</p>
+ <p>Aux Id: Seq 10884</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+
+<!--
+ <list type="bulleted">
+ <item>
+ <p>For those implementing their own codec's, the new megaco_encoder
+ behaviour will require three more functions. See above for more
+ info. </p>
+ <p>Own Id: OTP-7168</p>
+ <p>Aux Id: Seq 10867</p>
+ </item>
+
+ </list>
+-->
+
+ </section>
+ </section> <!-- 3.9 -->
+
+
+ <section>
<title>Megaco 3.8.2</title>
<p>Version 3.8.2 supports code replacement in runtime from/to
@@ -901,7 +1310,7 @@
<list>
<item>
<p>When timers expire while a connection cancel
- (megaco:cancel) is in progress, there is a raise
+ (megaco:cancel) is in progress, there is a race
condition possibility. This has been eliminated. </p>
<p>Own Id: OTP-6921</p>
<p>Aux Id: Seq 10450</p>
@@ -1166,7 +1575,7 @@
<list type="bulleted">
<item>
<p>When replies arrive during a call to megaco:cancel
- there is a raise condition possibility. This has been
+ there is a race condition possibility. This has been
eliminated. </p>
<p>Own Id: OTP-6276</p>
<p>Aux Id: Seq 10450</p>
diff --git a/lib/megaco/src/app/megaco.appup.src b/lib/megaco/src/app/megaco.appup.src
index 4f781478ef..d904e8ab33 100644
--- a/lib/megaco/src/app/megaco.appup.src
+++ b/lib/megaco/src/app/megaco.appup.src
@@ -124,14 +124,32 @@
%% |
%% v
%% 3.14
+%% |
+%% v
+%% 3.14.1
+%% |
+%% v
+%% 3.14.1.1
%%
%%
{"%VSN%",
[
+ {"3.14.1",
+ [
+ ]
+ },
+ {"3.14",
+ [
+ {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]},
+ {update, megaco_monitor, soft, soft_purge, soft_purge, []},
+ {update, megaco_config, soft, soft_purge, soft_purge, []}
+ ]
+ },
{"3.13",
[
- {load_module, megaco_messenger, soft_purge, soft_purge, []},
+ {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]},
{load_module, megaco_filter, soft_purge, soft_purge, []},
+ {update, megaco_monitor, soft, soft_purge, soft_purge, []},
{update, megaco_config, soft, soft_purge, soft_purge, []},
{update, megaco_flex_scanner_handler, {advanced, downgrade_to_pre_3_13_1},
soft_purge, soft_purge, []}
@@ -163,10 +181,22 @@
}
],
[
+ {"3.14.1",
+ [
+ ]
+ },
+ {"3.14",
+ [
+ {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]},
+ {update, megaco_monitor, soft, soft_purge, soft_purge, []},
+ {update, megaco_config, soft, soft_purge, soft_purge, []}
+ ]
+ },
{"3.13",
[
- {load_module, megaco_messenger, soft_purge, soft_purge, []},
+ {load_module, megaco_messenger, soft_purge, soft_purge, [megaco_monitor]},
{load_module, megaco_filter, soft_purge, soft_purge, []},
+ {update, megaco_monitor, soft, soft_purge, soft_purge, []},
{update, megaco_config, soft, soft_purge, soft_purge, []},
{update, megaco_flex_scanner_handler, {advanced, upgrade_from_pre_3_13_1},
soft_purge, soft_purge, []}
diff --git a/lib/megaco/src/app/megaco_internal.hrl b/lib/megaco/src/app/megaco_internal.hrl
index adbaacacef..2c124e9060 100644
--- a/lib/megaco/src/app/megaco_internal.hrl
+++ b/lib/megaco/src/app/megaco_internal.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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%
%%
@@ -139,6 +139,22 @@
[?APPLICATION, ?MODULE, self()|A]))).
+-define(megaco_ereport(Label, Report),
+ ?megaco_report(error_report, Label, Report)).
+
+-define(megaco_wreport(Label, Report),
+ ?megaco_report(warning_report, Label, Report)).
+
+-define(megaco_ireport(Label, Report),
+ ?megaco_report(info_report, Label, Report)).
+
+-define(megaco_report(Func, Label, Report),
+ (catch error_logger:Func([{label, Label},
+ {application, ?APPLICATION},
+ {module, ?MODULE},
+ {process, self()} | Report]))).
+
+
%%%----------------------------------------------------------------------
%%% Default (ignore) value of the Extra argument to the
%%% megaco:receive_message/5 and process_received_message functions/5.
diff --git a/lib/megaco/src/engine/megaco_config.erl b/lib/megaco/src/engine/megaco_config.erl
index 1c7a141be7..6805db790d 100644
--- a/lib/megaco/src/engine/megaco_config.erl
+++ b/lib/megaco/src/engine/megaco_config.erl
@@ -224,149 +224,278 @@ update_user_info(UserMid, orig_pending_limit, Val) ->
update_user_info(UserMid, Item, Val) ->
call({update_user_info, UserMid, Item, Val}).
-conn_info(CH, Item)
- when is_record(CH, megaco_conn_handle) andalso (Item /= cancel) ->
- case Item of
- conn_handle ->
- CH;
- mid ->
- CH#megaco_conn_handle.local_mid;
- local_mid ->
- CH#megaco_conn_handle.local_mid;
- remote_mid ->
- CH#megaco_conn_handle.remote_mid;
- conn_data ->
- case lookup_local_conn(CH) of
- [] ->
- exit({no_such_connection, CH});
- [ConnData] ->
- ConnData
- end;
- _ ->
- case lookup_local_conn(CH) of
- [] ->
- exit({no_such_connection, CH});
- [ConnData] ->
- conn_info(ConnData, Item)
- end
+
+conn_info(Data, Item) ->
+ %% The purpose of this is a compiler optimization...
+ %% Args are processed from left to right.
+ do_conn_info(Item, Data).
+
+do_conn_info(mid = _Item, #megaco_conn_handle{local_mid = Mid}) ->
+ Mid;
+do_conn_info(local_mid = _Item, #megaco_conn_handle{local_mid = LMid}) ->
+ LMid;
+do_conn_info(remote_mid = _Item, #megaco_conn_handle{remote_mid = RMid}) ->
+ RMid;
+do_conn_info(conn_handle = _Item, CH) when is_record(CH, megaco_conn_handle) ->
+ CH;
+do_conn_info(conn_data = _Item, CH) when is_record(CH, megaco_conn_handle) ->
+ case lookup_local_conn(CH) of
+ [] ->
+ exit({no_such_connection, CH});
+ [ConnData] ->
+ ConnData
+ end;
+do_conn_info(Item, CH) when is_record(CH, megaco_conn_handle) ->
+ case lookup_local_conn(CH) of
+ [] ->
+ exit({no_such_connection, CH});
+ [ConnData] ->
+ do_conn_info(Item, ConnData)
end;
-conn_info(#conn_data{conn_handle = CH}, cancel) ->
+
+do_conn_info(cancel = _Item, #conn_data{conn_handle = CH}) ->
+ %% To minimise raise-condition propabillity,
+ %% we always look in the table instead of
+ %% in the record for this one
+ ets:lookup_element(megaco_local_conn, CH, #conn_data.cancel);
+do_conn_info(cancel = _Item, CH) when is_record(CH, megaco_conn_handle) ->
%% To minimise raise-condition propabillity,
%% we always look in the table instead of
%% in the record for this one
ets:lookup_element(megaco_local_conn, CH, #conn_data.cancel);
-conn_info(CD, Item) when is_record(CD, conn_data) ->
- case Item of
- all ->
- Tags0 = record_info(fields, conn_data),
- Tags1 = replace(serial, trans_id, Tags0),
- Tags = [mid, local_mid, remote_mid] ++
- replace(max_serial, max_trans_id, Tags1),
- [{Tag, conn_info(CD,Tag)} || Tag <- Tags,
- Tag /= conn_data,
- Tag /= trans_sender,
- Tag /= cancel];
- conn_data -> CD;
- conn_handle -> CD#conn_data.conn_handle;
- mid -> (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid;
- local_mid -> (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid;
- remote_mid -> (CD#conn_data.conn_handle)#megaco_conn_handle.remote_mid;
- trans_id -> CH = CD#conn_data.conn_handle,
- LocalMid = CH#megaco_conn_handle.local_mid,
- Item2 = {LocalMid, trans_id_counter},
- case (catch ets:lookup(megaco_config, Item2)) of
- {'EXIT', _} ->
- undefined_serial;
- [] ->
- user_info(LocalMid, min_trans_id);
- [{_, Serial}] ->
- Max = CD#conn_data.max_serial,
- if
- ((Max =:= infinity) andalso
- is_integer(Serial) andalso
- (Serial < 4294967295)) ->
- Serial + 1;
- (Max =:= infinity) andalso
- is_integer(Serial) andalso
- (Serial =:= 4294967295) ->
- user_info(LocalMid,
- min_trans_id);
- Serial < Max ->
- Serial + 1;
- Serial =:= Max ->
- user_info(LocalMid,
- min_trans_id);
- Serial =:= 4294967295 ->
- user_info(LocalMid,
- min_trans_id);
- true ->
- undefined_serial
- end
- end;
- max_trans_id -> CD#conn_data.max_serial;
- request_timer -> CD#conn_data.request_timer;
- long_request_timer -> CD#conn_data.long_request_timer;
-
- auto_ack -> CD#conn_data.auto_ack;
-
- trans_ack -> CD#conn_data.trans_ack;
- trans_ack_maxcount -> CD#conn_data.trans_ack_maxcount;
-
- trans_req -> CD#conn_data.trans_req;
- trans_req_maxcount -> CD#conn_data.trans_req_maxcount;
- trans_req_maxsize -> CD#conn_data.trans_req_maxsize;
-
- trans_timer -> CD#conn_data.trans_timer;
-
- pending_timer -> CD#conn_data.pending_timer;
- orig_pending_limit -> CD#conn_data.sent_pending_limit;
- sent_pending_limit -> CD#conn_data.sent_pending_limit;
- recv_pending_limit -> CD#conn_data.recv_pending_limit;
- reply_timer -> CD#conn_data.reply_timer;
- control_pid -> CD#conn_data.control_pid;
- monitor_ref -> CD#conn_data.monitor_ref;
- send_mod -> CD#conn_data.send_mod;
- send_handle -> CD#conn_data.send_handle;
- encoding_mod -> CD#conn_data.encoding_mod;
- encoding_config -> CD#conn_data.encoding_config;
- protocol_version -> CD#conn_data.protocol_version;
- auth_data -> CD#conn_data.auth_data;
- user_mod -> CD#conn_data.user_mod;
- user_args -> CD#conn_data.user_args;
- reply_action -> CD#conn_data.reply_action;
- reply_data -> CD#conn_data.reply_data;
- threaded -> CD#conn_data.threaded;
- strict_version -> CD#conn_data.strict_version;
- long_request_resend -> CD#conn_data.long_request_resend;
- call_proxy_gc_timeout -> CD#conn_data.call_proxy_gc_timeout;
- cancel -> CD#conn_data.cancel;
- resend_indication -> CD#conn_data.resend_indication;
- segment_reply_ind -> CD#conn_data.segment_reply_ind;
- segment_recv_acc -> CD#conn_data.segment_recv_acc;
- segment_recv_timer -> CD#conn_data.segment_recv_timer;
- segment_send -> CD#conn_data.segment_send;
- segment_send_timer -> CD#conn_data.segment_send_timer;
- max_pdu_size -> CD#conn_data.max_pdu_size;
- request_keep_alive_timeout -> CD#conn_data.request_keep_alive_timeout;
- receive_handle ->
- LocalMid = (CD#conn_data.conn_handle)#megaco_conn_handle.local_mid,
- #megaco_receive_handle{local_mid = LocalMid,
- encoding_mod = CD#conn_data.encoding_mod,
- encoding_config = CD#conn_data.encoding_config,
- send_mod = CD#conn_data.send_mod};
- _ ->
- exit({no_such_item, Item})
+do_conn_info(all = _Item,
+ #conn_data{conn_handle = CH,
+ serial = TransId,
+ max_serial = MaxTransId,
+ request_timer = ReqTmr,
+ long_request_timer = LongReqTmr,
+ auto_ack = AutoAck,
+ trans_ack = TransAck,
+ trans_ack_maxcount = TransAckMaxCount,
+ trans_req = TransReq,
+ trans_req_maxcount = TransReqMaxCount,
+ trans_req_maxsize = TransReqMaxSz,
+ trans_timer = TransTmr,
+ %% trans_sender,
+ pending_timer = PendingTmr,
+ sent_pending_limit = SentPendingLimit,
+ recv_pending_limit = RecvPendingLimit,
+ reply_timer = ReplyTmr,
+ control_pid = CtrlPid,
+ monitor_ref = MonRef,
+ send_mod = SendMod,
+ send_handle = SendHandle,
+ encoding_mod = EncodingMod,
+ encoding_config = EncodingConf,
+ protocol_version = ProtoVersion,
+ auth_data = AuthData,
+ user_mod = UserMod,
+ user_args = UserArgs,
+ reply_action = ReplyAction,
+ reply_data = ReplyData,
+ threaded = Threaded,
+ strict_version = StrictVersion,
+ long_request_resend = LongReqResend,
+ call_proxy_gc_timeout = CallProxyGCTimeout,
+ %% cancel,
+ resend_indication = ResendInd,
+ segment_reply_ind = SegReplyInd,
+ segment_recv_acc = SegRecvAcc,
+ segment_recv_timer = SegRecvTmr,
+ segment_send = SegSend,
+ segment_send_timer = SegSendTmr,
+ max_pdu_size = MaxPduSz,
+ request_keep_alive_timeout = RequestKeepAliveTmr}) ->
+ [{conn_handle, CH},
+ {trans_id, TransId},
+ {max_trans_id, MaxTransId},
+ {request_timer, ReqTmr},
+ {long_request_timer, LongReqTmr},
+ {mid, CH#megaco_conn_handle.local_mid},
+ {local_mid, CH#megaco_conn_handle.local_mid},
+ {remote_mid, CH#megaco_conn_handle.remote_mid},
+ {auto_ack, AutoAck},
+ {trans_ack, TransAck},
+ {trans_ack_maxcount, TransAckMaxCount},
+ {trans_req, TransReq},
+ {trans_req_maxcount, TransReqMaxCount},
+ {trans_req_maxsize, TransReqMaxSz},
+ {trans_timer, TransTmr},
+ {pending_timer, PendingTmr},
+ {sent_pending_limit, SentPendingLimit},
+ {recv_pending_limit, RecvPendingLimit},
+ {reply_timer, ReplyTmr},
+ {control_pid, CtrlPid},
+ {monitor_ref, MonRef},
+ {send_mod, SendMod},
+ {send_handle, SendHandle},
+ {encoding_mod, EncodingMod},
+ {encoding_config, EncodingConf},
+ {protocol_version, ProtoVersion},
+ {auth_data, AuthData},
+ {user_mod, UserMod},
+ {user_args, UserArgs},
+ {reply_action, ReplyAction},
+ {reply_data, ReplyData},
+ {threaded, Threaded},
+ {strict_version, StrictVersion},
+ {long_request_resend, LongReqResend},
+ {call_proxy_gc_timeout, CallProxyGCTimeout},
+ {resend_indication, ResendInd},
+ {segment_reply_ind, SegReplyInd},
+ {segment_recv_acc, SegRecvAcc},
+ {segment_recv_timer, SegRecvTmr},
+ {segment_send, SegSend},
+ {segment_send_timer, SegSendTmr},
+ {max_pdu_size, MaxPduSz},
+ {request_keep_alive_timeout, RequestKeepAliveTmr}];
+
+do_conn_info(conn_data = _Item, CD) ->
+ CD;
+do_conn_info(conn_handle = _Item, #conn_data{conn_handle = Val}) ->
+ Val;
+do_conn_info(mid = _Item,
+ #conn_data{conn_handle = #megaco_conn_handle{local_mid = Val}}) ->
+ Val;
+do_conn_info(local_mid = _Item,
+ #conn_data{conn_handle = #megaco_conn_handle{local_mid = Val}}) ->
+ Val;
+do_conn_info(remote_mid = _Item,
+ #conn_data{conn_handle = #megaco_conn_handle{remote_mid = Val}}) ->
+ Val;
+do_conn_info(trans_id = _Item,
+ #conn_data{conn_handle = #megaco_conn_handle{local_mid = LMid},
+ max_serial = Max}) ->
+ Item2 = {LMid, trans_id_counter},
+ case (catch ets:lookup(megaco_config, Item2)) of
+ {'EXIT', _} ->
+ undefined_serial;
+ [] ->
+ user_info(LMid, min_trans_id);
+ [{_, Serial}] ->
+ if
+ ((Max =:= infinity) andalso
+ is_integer(Serial) andalso
+ (Serial < 4294967295)) ->
+ Serial + 1;
+ ((Max =:= infinity) andalso
+ is_integer(Serial) andalso
+ (Serial =:= 4294967295)) ->
+ user_info(LMid, min_trans_id);
+ Serial < Max ->
+ Serial + 1;
+ Serial =:= Max ->
+ user_info(LMid, min_trans_id);
+ Serial =:= 4294967295 ->
+ user_info(LMid, min_trans_id);
+ true ->
+ undefined_serial
+ end
end;
-conn_info(BadHandle, _Item) ->
- {error, {no_such_connection, BadHandle}}.
-
-replace(_, _, []) ->
- [];
-replace(Item, WithItem, [Item|List]) ->
- [WithItem|List];
-replace(Item, WithItem, [OtherItem|List]) ->
- [OtherItem | replace(Item, WithItem, List)].
+do_conn_info(max_trans_id = _Item, #conn_data{max_serial = Val}) ->
+ Val;
+do_conn_info(request_timer = _Item, #conn_data{request_timer = Val}) ->
+ Val;
+do_conn_info(long_request_timer = _Item, #conn_data{long_request_timer = Val}) ->
+ Val;
+do_conn_info(auto_ack = _Item, #conn_data{auto_ack = Val}) ->
+ Val;
+do_conn_info(trans_ack = _Item, #conn_data{trans_ack = Val}) ->
+ Val;
+do_conn_info(trans_ack_maxcount = _Item, #conn_data{trans_ack_maxcount = Val}) ->
+ Val;
+do_conn_info(trans_req = _Item, #conn_data{trans_req = Val}) ->
+ Val;
+do_conn_info(trans_req_maxcount = _Item, #conn_data{trans_req_maxcount = Val}) ->
+ Val;
+do_conn_info(trans_req_maxsize = _Item, #conn_data{trans_req_maxsize = Val}) ->
+ Val;
+do_conn_info(trans_timer = _Item, #conn_data{trans_timer = Val}) ->
+ Val;
+do_conn_info(pending_timer = _Item, #conn_data{pending_timer = Val}) ->
+ Val;
+do_conn_info(orig_pending_limit = _Item, #conn_data{sent_pending_limit = Val}) ->
+ Val;
+do_conn_info(sent_pending_limit = _Item, #conn_data{sent_pending_limit = Val}) ->
+ Val;
+do_conn_info(recv_pending_limit = _Item, #conn_data{recv_pending_limit = Val}) ->
+ Val;
+do_conn_info(reply_timer = _Item, #conn_data{reply_timer = Val}) ->
+ Val;
+do_conn_info(control_pid = _Item, #conn_data{control_pid = Val}) ->
+ Val;
+do_conn_info(send_mod = _Item, #conn_data{send_mod = Val}) ->
+ Val;
+do_conn_info(send_handle = _Item, #conn_data{send_handle = Val}) ->
+ Val;
+do_conn_info(encoding_mod = _Item, #conn_data{encoding_mod = Val}) ->
+ Val;
+do_conn_info(encoding_config = _Item, #conn_data{encoding_config = Val}) ->
+ Val;
+do_conn_info(protocol_version = _Item, #conn_data{protocol_version = Val}) ->
+ Val;
+do_conn_info(auth_data = _Item, #conn_data{auth_data = Val}) ->
+ Val;
+do_conn_info(user_mod = _Item, #conn_data{user_mod = Val}) ->
+ Val;
+do_conn_info(user_args = _Item, #conn_data{user_args = Val}) ->
+ Val;
+do_conn_info(reply_action = _Item, #conn_data{reply_action = Val}) ->
+ Val;
+do_conn_info(reply_data = _Item, #conn_data{reply_data = Val}) ->
+ Val;
+do_conn_info(threaded = _Item, #conn_data{threaded = Val}) ->
+ Val;
+do_conn_info(strict_version = _Item, #conn_data{strict_version = Val}) ->
+ Val;
+do_conn_info(long_request_resend = _Item,
+ #conn_data{long_request_resend = Val}) ->
+ Val;
+do_conn_info(call_proxy_gc_timeout = _Item,
+ #conn_data{call_proxy_gc_timeout = Val}) ->
+ Val;
+do_conn_info(resend_indication = _Item, #conn_data{resend_indication = Val}) ->
+ Val;
+do_conn_info(segment_reply_ind = _Item, #conn_data{segment_reply_ind = Val}) ->
+ Val;
+do_conn_info(segment_recv_acc = _Item, #conn_data{segment_recv_acc = Val}) ->
+ Val;
+do_conn_info(segment_recv_timer = _Item,
+ #conn_data{segment_recv_timer = Val}) ->
+ Val;
+do_conn_info(segment_send = _Item, #conn_data{segment_send = Val}) ->
+ Val;
+do_conn_info(segment_send_timer = _Item,
+ #conn_data{segment_send_timer = Val}) ->
+ Val;
+do_conn_info(max_pdu_size = _Item, #conn_data{max_pdu_size = Val}) ->
+ Val;
+do_conn_info(request_keep_alive_timeout = _Item,
+ #conn_data{request_keep_alive_timeout = Val}) ->
+ Val;
+do_conn_info(receive_handle = _Item,
+ #conn_data{conn_handle = #megaco_conn_handle{local_mid = LMid},
+ encoding_mod = EM,
+ encoding_config = EC,
+ send_mod = SM}) ->
+ #megaco_receive_handle{local_mid = LMid,
+ encoding_mod = EM,
+ encoding_config = EC,
+ send_mod = SM};
+do_conn_info(Item, Data)
+ when is_record(Data, conn_data) orelse is_record(Data, megaco_conn_handle) ->
+ exit({no_such_item, Item});
+do_conn_info(_Item, BadData) ->
+ {error, {no_such_connection, BadData}}.
+
+
+%% replace(_, _, []) ->
+%% [];
+%% replace(Item, WithItem, [Item|List]) ->
+%% [WithItem|List];
+%% replace(Item, WithItem, [OtherItem|List]) ->
+%% [OtherItem | replace(Item, WithItem, List)].
update_conn_info(#conn_data{conn_handle = CH}, Item, Val) ->
@@ -499,31 +628,19 @@ incr_counter(Item, Incr) ->
end
catch
error:_ ->
+ %% Counter does not exist, so try creat it
try
begin
cre_counter(Item, Incr)
end
catch
exit:_ ->
- %% Ok, some other process got there before us,
- %% so try again
+ %% This is a raise condition.
+ %% When we tried to update the counter above, it
+ %% did not exist, but now it does...
ets:update_counter(megaco_config, Item, Incr)
end
end.
-%% incr_counter(Item, Incr) ->
-%% case (catch ets:update_counter(megaco_config, Item, Incr)) of
-%% {'EXIT', _} ->
-%% case (catch cre_counter(Item, Incr)) of
-%% {'EXIT', _} ->
-%% %% Ok, some other process got there before us,
-%% %% so try again
-%% ets:update_counter(megaco_config, Item, Incr);
-%% NewVal ->
-%% NewVal
-%% end;
-%% NewVal ->
-%% NewVal
-%% end.
cre_counter(Item, Initial) ->
case whereis(?SERVER) =:= self() of
@@ -531,8 +648,8 @@ cre_counter(Item, Initial) ->
case call({cre_counter, Item, Initial}) of
{ok, Value} ->
Value;
- Error ->
- exit(Error)
+ {error, Reason} ->
+ exit({failed_creating_counter, Item, Initial, Reason})
end;
true ->
%% Check that the counter does not already exists
@@ -542,7 +659,7 @@ cre_counter(Item, Initial) ->
ets:insert(megaco_config, {Item, Initial}),
{ok, Initial};
[_] ->
- %% Ouch, now what?
+ %% Possibly a raise condition
{error, already_exists}
end
diff --git a/lib/megaco/src/engine/megaco_messenger.erl b/lib/megaco/src/engine/megaco_messenger.erl
index 5756e8e896..5fad29931b 100644
--- a/lib/megaco/src/engine/megaco_messenger.erl
+++ b/lib/megaco/src/engine/megaco_messenger.erl
@@ -1541,30 +1541,6 @@ check_pending_limit(Limit, Direction, TransId) ->
aborted
end.
-%% check_pending_limit(infinity, _, _) ->
-%% {ok, 0};
-%% check_pending_limit(Limit, Direction, TransId) ->
-%% ?rt2("check pending limit", [Direction, Limit, TransId]),
-%% case (catch megaco_config:get_pending_counter(Direction, TransId)) of
-%% {'EXIT', _} ->
-%% %% This function is only called when we "know" the
-%% %% counter to exist. So, the only reason that this
-%% %% would happen is of the counter has been removed.
-%% %% This only happen if the pending limit has been
-%% %% reached. In any case, this is basically the same
-%% %% as aborted!
-%% ?rt2("check pending limit - exit", []),
-%% aborted;
-%% Val when Val =< Limit ->
-%% %% Since we have no intention to increment here, it
-%% %% is ok to be _at_ the limit
-%% ?rt2("check pending limit - ok", [Val]),
-%% {ok, Val};
-%% _Val ->
-%% ?rt2("check pending limit - aborted", [_Val]),
-%% aborted
-%% end.
-
check_and_maybe_incr_pending_limit(infinity, _, _) ->
ok;
@@ -1572,59 +1548,42 @@ check_and_maybe_incr_pending_limit(Limit, Direction, TransId) ->
%%
%% We need this kind of test to detect when we _pass_ the limit
%%
- ?rt2("check and maybe incr pending limit", [Direction, Limit, TransId]),
+ ?rt2("check and maybe incr pending limit", [{direction, Direction},
+ {transaction_id, TransId},
+ {counter_limit, Limit}]),
try megaco_config:get_pending_counter(Direction, TransId) of
Val when Val > Limit ->
- ?rt2("check and maybe incr - aborted", [Direction, Val, Limit]),
+ ?rt2("check and maybe incr - aborted", [{counter_value, Val}]),
aborted; % Already passed the limit
Val ->
- ?rt2("check and maybe incr - incr", [Direction, Val, Limit]),
+ ?rt2("check and maybe incr - incr", [{counter_value, Val}]),
megaco_config:incr_pending_counter(Direction, TransId),
if
Val < Limit ->
ok; % Still within the limit
true ->
?rt2("check and maybe incr - error",
- [Direction, Val, Limit]),
+ [{counter_value, Val}]),
error % Passed the limit
end
catch
_:_ ->
%% Has not been created yet (connect).
- megaco_config:cre_pending_counter(Direction, TransId, 1),
- ok
+ %% Try create it, but bevare of possible raise condition
+ try
+ begin
+ megaco_config:cre_pending_counter(Direction, TransId, 1),
+ ok
+ end
+ catch
+ _:_ ->
+ %% Ouch, raise condition, increment instead...
+ megaco_config:incr_pending_counter(Direction, TransId),
+ ok
+ end
end.
-%% check_and_maybe_incr_pending_limit(infinity, _, _) ->
-%% ok;
-%% check_and_maybe_incr_pending_limit(Limit, Direction, TransId) ->
-%% %%
-%% %% We need this kind of test to detect when we _pass_ the limit
-%% %%
-%% ?rt2("check and maybe incr pending limit", [Direction, Limit, TransId]),
-%% case (catch megaco_config:get_pending_counter(Direction, TransId)) of
-%% {'EXIT', _} ->
-%% %% Has not been created yet (connect).
-%% megaco_config:cre_pending_counter(Direction, TransId, 1),
-%% ok;
-%% Val when Val > Limit ->
-%% ?rt2("check and maybe incr - aborted", [Direction, Val, Limit]),
-%% aborted; % Already passed the limit
-%% Val ->
-%% ?rt2("check and maybe incr - incr", [Direction, Val, Limit]),
-%% megaco_config:incr_pending_counter(Direction, TransId),
-%% if
-%% Val < Limit ->
-%% ok; % Still within the limit
-%% true ->
-%% ?rt2("check and maybe incr - error",
-%% [Direction, Val, Limit]),
-%% error % Passed the limit
-%% end
-%% end.
-
-
%% BUGBUG BUGBUG BUGBUG
%%
%% Do we know that the Rep is still valid? A previous transaction
@@ -2648,33 +2607,84 @@ handle_reply(
handle_reply(#conn_data{conn_handle = CH} = CD, T, Extra) ->
TransId = to_local_trans_id(CD),
?rt2("handle reply", [T, TransId]),
- case megaco_monitor:lookup_request(TransId) of
- [Req] when (is_record(Req, request) andalso
- (CD#conn_data.cancel =:= true)) ->
+ case {megaco_monitor:request_lockcnt_inc(TransId),
+ megaco_monitor:lookup_request(TransId)} of
+ {_Cnt, [Req]} when (is_record(Req, request) andalso
+ (CD#conn_data.cancel =:= true)) ->
?TC_AWAIT_REPLY_EVENT(true),
+ ?report_trace(CD, "trans reply - cancel(1)", [T]),
do_handle_reply_cancel(CD, Req, T);
- [#request{remote_mid = RMid} = Req] when ((RMid =:= preliminary_mid) orelse
- (RMid =:= CH#megaco_conn_handle.remote_mid)) ->
+ {Cnt, [#request{remote_mid = RMid} = Req]} when
+ ((Cnt =:= 1) andalso
+ ((RMid =:= preliminary_mid) orelse
+ (RMid =:= CH#megaco_conn_handle.remote_mid))) ->
+ ?TC_AWAIT_REPLY_EVENT(false),
+ %% Just in case conn_data got update after our lookup
+ %% but before we looked up the request record, we
+ %% check the cancel field again.
+ case megaco_config:conn_info(CD, cancel) of
+ true ->
+ ?report_trace(CD, "trans reply - cancel(2)", [T]),
+ megaco_monitor:request_lockcnt_del(TransId),
+ do_handle_reply_cancel(CD, Req, T);
+ false ->
+ ?report_trace(CD, "trans reply", [T]),
+ do_handle_reply(CD, Req, TransId, T, Extra)
+ end;
+
+ {Cnt, [#request{remote_mid = RMid} = _Req]} when
+ (is_integer(Cnt) andalso
+ ((RMid =:= preliminary_mid) orelse
+ (RMid =:= CH#megaco_conn_handle.remote_mid))) ->
+ ?TC_AWAIT_REPLY_EVENT(false),
+ %% Ok, someone got there before me, now what?
+ %% This is a plain old raise condition
+ ?report_important(CD, "trans reply - raise condition",
+ [T, {request_lockcnt, Cnt}]),
+ megaco_monitor:request_lockcnt_dec(TransId);
+
+ %% no counter
+ {_Cnt, [#request{remote_mid = RMid} = Req]} when
+ ((RMid =:= preliminary_mid) orelse
+ (RMid =:= CH#megaco_conn_handle.remote_mid)) ->
?TC_AWAIT_REPLY_EVENT(false),
+ %% The counter does not exist.
+ %% This can only mean a code upgrade raise condition.
+ %% That is, this request record was created before
+ %% this feature (the counters) was instroduced.
+ %% The simples solution is this is to behave exactly as
+ %% before, that is pass it along, and leave it to the
+ %% user to figure out.
+
%% Just in case conn_data got update after our lookup
%% but before we looked up the request record, we
%% check the cancel field again.
+ ?report_verbose(CD, "trans reply - old style", [T]),
case megaco_config:conn_info(CD, cancel) of
true ->
+ megaco_monitor:request_lockcnt_del(TransId),
do_handle_reply_cancel(CD, Req, T);
false ->
do_handle_reply(CD, Req, TransId, T, Extra)
end;
- [#request{user_mod = UserMod,
- user_args = UserArgs,
- reply_action = Action,
- reply_data = UserData,
- remote_mid = RMid}] ->
+ {Cnt, [#request{user_mod = UserMod,
+ user_args = UserArgs,
+ reply_action = Action,
+ reply_data = UserData,
+ remote_mid = RMid}]} ->
?report_trace(CD,
"received trans reply with invalid remote mid",
- [T, RMid]),
+ [{transaction, T},
+ {remote_mid, RMid},
+ {request_lockcnt, Cnt}]),
+ if
+ is_integer(Cnt) ->
+ megaco_monitor:request_lockcnt_dec(TransId);
+ true ->
+ ok
+ end,
WrongMid = CH#megaco_conn_handle.remote_mid,
T2 = transform_transaction_reply_enc(CD#conn_data.protocol_version,
T),
@@ -2685,7 +2695,15 @@ handle_reply(#conn_data{conn_handle = CH} = CD, T, Extra) ->
reply_data = UserData},
return_reply(CD2, TransId, UserReply, Extra);
- [] ->
+ {Cnt, []} when is_integer(Cnt) ->
+ ?TC_AWAIT_REPLY_EVENT(undefined),
+ ?report_trace(CD, "trans reply (no receiver)",
+ [T, {request_lockcnt, Cnt}]),
+ megaco_monitor:request_lockcnt_dec(TransId),
+ return_unexpected_trans(CD, T, Extra);
+
+ %% No counter
+ {_Cnt, []} ->
?TC_AWAIT_REPLY_EVENT(undefined),
?report_trace(CD, "trans reply (no receiver)", [T]),
return_unexpected_trans(CD, T, Extra)
@@ -2716,6 +2734,7 @@ do_handle_reply(CD,
%% This is the first reply (maybe of many)
megaco_monitor:delete_request(TransId),
+ megaco_monitor:request_lockcnt_del(TransId),
megaco_monitor:cancel_apply_after(Ref), % OTP-4843
megaco_config:del_pending_counter(recv, TransId), % OTP-7189
@@ -3739,6 +3758,11 @@ insert_requests(ConnData, ConnHandle,
insert_request(ConnData, ConnHandle, TransId,
Action, Data, InitTimer, LongTimer) ->
+ %% We dont check the result of the lock-counter creation because
+ %% the only way it could already exist is if the transaction-id
+ %% range has wrapped and an old counter was not deleted.
+ megaco_monitor:request_lockcnt_cre(TransId),
+
#megaco_conn_handle{remote_mid = RemoteMid} = ConnHandle,
#conn_data{protocol_version = Version,
user_mod = UserMod,
@@ -4323,6 +4347,7 @@ cancel_request(ConnData, Req, Reason) ->
cancel_request2(ConnData, TransId, UserReply) ->
megaco_monitor:delete_request(TransId),
+ megaco_monitor:request_lockcnt_del(TransId),
megaco_config:del_pending_counter(recv, TransId), % OTP-7189
Serial = TransId#trans_id.serial,
ConnData2 = ConnData#conn_data{serial = Serial},
@@ -4380,29 +4405,67 @@ receive_reply_remote(ConnData, UserReply) ->
receive_reply_remote(ConnData, UserReply, Extra) ->
TransId = to_local_trans_id(ConnData),
- case (catch megaco_monitor:lookup_request(TransId)) of
- [#request{timer_ref = {_Type, Ref}} = Req] -> %% OTP-4843
+ case {megaco_monitor:request_lockcnt_inc(TransId),
+ (catch megaco_monitor:lookup_request(TransId))} of
+ {Cnt, [Req]} when (Cnt =:= 1) andalso is_record(Req, request) ->
%% Don't care about Req and Rep version diff
- megaco_monitor:delete_request(TransId),
- megaco_monitor:cancel_apply_after(Ref), % OTP-4843
- megaco_config:del_pending_counter(recv, TransId), % OTP-7189
-
- UserMod = Req#request.user_mod,
- UserArgs = Req#request.user_args,
- Action = Req#request.reply_action,
- UserData = Req#request.reply_data,
- ConnData2 = ConnData#conn_data{user_mod = UserMod,
- user_args = UserArgs,
- reply_action = Action,
- reply_data = UserData},
- return_reply(ConnData2, TransId, UserReply, Extra);
-
+ do_receive_reply_remote(ConnData, TransId, Req, UserReply, Extra);
+
+ {Cnt, [Req]} when is_integer(Cnt) andalso is_record(Req, request) ->
+ %% Another process is accessing, handle as unexpected
+ %% (so it has a possibillity to get logged).
+ ?report_important(ConnData, "trans reply (no receiver)",
+ [{user_reply, UserReply},
+ {request_lockcnt, Cnt}]),
+ megaco_monitor:request_lockcnt_dec(TransId),
+ return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra);
+
+ %% no counter
+ {_Cnt, [Req]} when is_record(Req, request) ->
+ %% The counter does not exist.
+ %% This can only mean a code upgrade raise condition.
+ %% That is, this request record was created before
+ %% this feature (the counters) was instroduced.
+ %% The simples solution to this is to behave exactly as
+ %% before, that is, pass it along, and leave it to the
+ %% user to figure out.
+ ?report_trace(ConnData,
+ "remote reply - "
+ "code upgrade raise condition",
+ [{user_reply, UserReply}]),
+ do_receive_reply_remote(ConnData, TransId, Req, UserReply, Extra);
+
+ {Cnt, _} when is_integer(Cnt) ->
+ ?report_trace(ConnData, "trans reply (no receiver)",
+ [{user_reply, UserReply}, {request_lockcnt, Cnt}]),
+ megaco_monitor:request_lockcnt_dec(TransId),
+ return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra);
+
_ ->
?report_trace(ConnData, "remote reply (no receiver)",
- [UserReply]),
+ [{user_reply, UserReply}]),
return_unexpected_trans_reply(ConnData, TransId, UserReply, Extra)
end.
+do_receive_reply_remote(ConnData, TransId,
+ #request{timer_ref = {_Type, Ref},
+ user_mod = UserMod,
+ user_args = UserArgs,
+ reply_action = Action,
+ reply_data = UserData} = _Req,
+ UserReply, Extra) ->
+ megaco_monitor:delete_request(TransId),
+ megaco_monitor:request_lockcnt_del(TransId),
+ megaco_monitor:cancel_apply_after(Ref), % OTP-4843
+ megaco_config:del_pending_counter(recv, TransId), % OTP-7189
+
+ ConnData2 = ConnData#conn_data{user_mod = UserMod,
+ user_args = UserArgs,
+ reply_action = Action,
+ reply_data = UserData},
+ return_reply(ConnData2, TransId, UserReply, Extra).
+
+
cancel_reply(ConnData, #reply{state = waiting_for_ack,
user_mod = UserMod,
user_args = UserArgs} = Rep, Reason) ->
diff --git a/lib/megaco/src/engine/megaco_monitor.erl b/lib/megaco/src/engine/megaco_monitor.erl
index f95a20cf58..29275371be 100644
--- a/lib/megaco/src/engine/megaco_monitor.erl
+++ b/lib/megaco/src/engine/megaco_monitor.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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%
%%
@@ -51,6 +51,11 @@
update_request_field/3, update_request_fields/2,
delete_request/1,
+ request_lockcnt_cre/1,
+ request_lockcnt_del/1,
+ request_lockcnt_inc/1,
+ request_lockcnt_dec/1,
+
lookup_reply/1,
lookup_reply_field/2,
match_replies/1,
@@ -115,6 +120,24 @@ update_request_fields(Key, NewFields) when is_list(NewFields) ->
delete_request(Key) ->
ets:delete(megaco_requests, Key).
+
+request_lockcnt_cre(TransId) ->
+ Key = {TransId, lockcnt},
+ ets:insert_new(megaco_requests, {Key, 1}).
+
+request_lockcnt_del(TransId) ->
+ Key = {TransId, lockcnt},
+ ets:delete(megaco_requests, Key).
+
+request_lockcnt_inc(TransId) ->
+ Key = {TransId, lockcnt},
+ (catch ets:update_counter(megaco_requests, Key, 1)).
+
+request_lockcnt_dec(TransId) ->
+ Key = {TransId, lockcnt},
+ (catch ets:update_counter(megaco_requests, Key, -1)).
+
+
lookup_reply(Key) ->
ets:lookup(megaco_replies, Key).
diff --git a/lib/megaco/src/flex/Makefile.in b/lib/megaco/src/flex/Makefile.in
index 6ce9b34617..5af651d89b 100644
--- a/lib/megaco/src/flex/Makefile.in
+++ b/lib/megaco/src/flex/Makefile.in
@@ -280,7 +280,7 @@ release_spec: opt
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
ifeq ($(ENABLE_MEGACO_FLEX_SCANNER),true)
$(INSTALL_DATA) $(FLEX_FILES) $(C_TARGETS) $(RELSYSDIR)/src/flex
- $(INSTALL_DATA) $(SOLIBS) $(RELSYSDIR)/priv/lib
+ $(INSTALL_PROGRAM) $(SOLIBS) $(RELSYSDIR)/priv/lib
endif
diff --git a/lib/megaco/test/megaco_config_test.erl b/lib/megaco/test/megaco_config_test.erl
index 453c1b8964..9ab1a7d90d 100644
--- a/lib/megaco/test/megaco_config_test.erl
+++ b/lib/megaco/test/megaco_config_test.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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%
%%
@@ -106,166 +106,197 @@ config(Config) when is_list(Config) ->
Evil = 400,
End = 500,
+ InitialCmd =
+ fun(No, Desc, Cmd, VerifyVal) ->
+ initial_command(Initial + No, Desc, Cmd, VerifyVal)
+ end,
+
+ VerifyCmd =
+ fun(M, No, Key, V) ->
+ verify_user_default_command(M, Verify + No, Key, V)
+ end,
+
+ NiceCmd =
+ fun(M, No, Key, Val) ->
+ nice_user_update_command(M, Nice + No, Key, Val)
+ end,
+
+ EvilCmd =
+ fun(M, No, Key, Val) ->
+ evil_user_update_command(M, Evil + No, Key, Val)
+ end,
+
+ %% End commands
+ ExitCmd =
+ fun(No, Desc, Cmd) ->
+ exit_command(End + No, Desc, Cmd)
+ end,
+ ErrorCmd =
+ fun(No, Desc, Cmd, MainReason, TS) ->
+ error_command(End + No, Desc, Cmd, MainReason, TS)
+ end,
+ PlainCmd =
+ fun(No, Desc, Cmd, V) ->
+ command(End + No, Desc, Cmd, V)
+ end,
+
Commands =
[
%% Initial commands
- initial_command( Initial + 0,
- "enable trace",
- fun() -> megaco:enable_trace(100, io) end, ok),
- initial_command( Initial + 1,
- "start",
- fun() -> megaco:start() end, ok),
- initial_command( Initial + 2,
- "Verify no active requests",
- fun() -> megaco:system_info(n_active_requests) end,
- 0),
- initial_command( Initial + 3,
- "Verify no active replies",
- fun() -> megaco:system_info(n_active_replies) end,
- 0),
- initial_command( Initial + 4,
- "Verify no active connections",
- fun() ->
- megaco:system_info(n_active_connections)
- end,
- 0),
- initial_command( Initial + 5,
- "Verify no connections",
- fun() -> megaco:system_info(connections) end, []),
- initial_command( Initial + 6,
- "Verify no users",
- fun() -> megaco:system_info(users) end, []),
- initial_command( Initial + 7,
- "Start user",
- fun() -> megaco:start_user(Mid, []) end, ok),
-
+ InitialCmd(0,
+ "enable trace",
+ fun() -> megaco:enable_trace(100, io) end,
+ ok),
+ InitialCmd(1,
+ "start",
+ fun() -> megaco:start() end,
+ ok),
+ InitialCmd(2,
+ "Verify no active requests",
+ fun() -> megaco:system_info(n_active_requests) end,
+ 0),
+ InitialCmd(3,
+ "Verify no active replies",
+ fun() -> megaco:system_info(n_active_replies) end,
+ 0),
+ InitialCmd(4,
+ "Verify no active connections",
+ fun() -> megaco:system_info(n_active_connections) end,
+ 0),
+ InitialCmd(5,
+ "Verify no connections",
+ fun() -> megaco:system_info(connections) end,
+ []),
+ InitialCmd(6,
+ "Verify no users",
+ fun() -> megaco:system_info(users) end,
+ []),
+ InitialCmd(7,
+ "Start user",
+ fun() -> megaco:start_user(Mid, []) end,
+ ok),
+
%% Verify user defaults
- verify_user_default_command(Mid, Verify + 1, connections, []),
- verify_user_default_command(Mid, Verify + 2, min_trans_id, 1),
- verify_user_default_command(Mid, Verify + 3, max_trans_id, infinity),
- verify_user_default_command(Mid, Verify + 4, request_timer,
- #megaco_incr_timer{}),
- verify_user_default_command(Mid, Verify + 5, long_request_timer, timer:seconds(60)),
- verify_user_default_command(Mid, Verify + 6, auto_ack, false),
- verify_user_default_command(Mid, Verify + 7, pending_timer, 30000),
- verify_user_default_command(Mid, Verify + 8, reply_timer, 30000),
- verify_user_default_command(Mid, Verify + 9, send_mod, megaco_tcp),
- verify_user_default_command(Mid, Verify + 10, encoding_mod,
- megaco_pretty_text_encoder),
- verify_user_default_command(Mid, Verify + 11, encoding_config, []),
- verify_user_default_command(Mid, Verify + 12, protocol_version, 1),
- verify_user_default_command(Mid, Verify + 13, reply_data, undefined),
- verify_user_default_command(Mid, Verify + 14, receive_handle,
- fun(H) when is_record(H, megaco_receive_handle) -> {ok, H};
- (R) -> {error, R}
- end),
+ VerifyCmd(Mid, 1, connections, []),
+ VerifyCmd(Mid, 2, min_trans_id, 1),
+ VerifyCmd(Mid, 3, max_trans_id, infinity),
+ VerifyCmd(Mid, 4, request_timer, #megaco_incr_timer{}),
+ VerifyCmd(Mid, 5, long_request_timer, timer:seconds(60)),
+ VerifyCmd(Mid, 6, auto_ack, false),
+ VerifyCmd(Mid, 7, pending_timer, 30000),
+ VerifyCmd(Mid, 8, reply_timer, 30000),
+ VerifyCmd(Mid, 9, send_mod, megaco_tcp),
+ VerifyCmd(Mid, 10, encoding_mod, megaco_pretty_text_encoder),
+ VerifyCmd(Mid, 11, encoding_config, []),
+ VerifyCmd(Mid, 12, protocol_version, 1),
+ VerifyCmd(Mid, 13, reply_data, undefined),
+ VerifyCmd(Mid, 14, receive_handle,
+ fun(H) when is_record(H, megaco_receive_handle) ->
+ {ok, H};
+ (R) ->
+ {error, R}
+ end),
%% Nice update
- nice_user_update_command(Mid, Nice + 1, min_trans_id, Int),
- nice_user_update_command(Mid, Nice + 2, max_trans_id, Int),
- nice_user_update_command(Mid, Nice + 3, max_trans_id, infinity),
- nice_user_update_command(Mid, Nice + 4, request_timer, Int),
- nice_user_update_command(Mid, Nice + 5, request_timer, infinity),
- nice_user_update_command(Mid, Nice + 6, request_timer, IT),
- nice_user_update_command(Mid, Nice + 7, long_request_timer, Int),
- nice_user_update_command(Mid, Nice + 8, long_request_timer, infinity),
- nice_user_update_command(Mid, Nice + 9, long_request_timer, IT),
- nice_user_update_command(Mid, Nice + 10, auto_ack, true),
- nice_user_update_command(Mid, Nice + 11, auto_ack, false),
- nice_user_update_command(Mid, Nice + 12, pending_timer, Int),
- nice_user_update_command(Mid, Nice + 13, pending_timer, infinity),
- nice_user_update_command(Mid, Nice + 14, pending_timer, IT),
- nice_user_update_command(Mid, Nice + 15, reply_timer, Int),
- nice_user_update_command(Mid, Nice + 16, reply_timer, infinity),
- nice_user_update_command(Mid, Nice + 17, reply_timer, IT),
- nice_user_update_command(Mid, Nice + 18, send_mod, an_atom),
- nice_user_update_command(Mid, Nice + 19, encoding_mod, an_atom),
- nice_user_update_command(Mid, Nice + 20, encoding_config, []),
- nice_user_update_command(Mid, Nice + 21, protocol_version, Int),
- nice_user_update_command(Mid, Nice + 23, reply_data, IT),
- nice_user_update_command(Mid, Nice + 23, resend_indication, true),
- nice_user_update_command(Mid, Nice + 24, resend_indication, false),
- nice_user_update_command(Mid, Nice + 25, resend_indication, flag),
+ NiceCmd(Mid, 1, min_trans_id, Int),
+ NiceCmd(Mid, 2, max_trans_id, Int),
+ NiceCmd(Mid, 3, max_trans_id, infinity),
+ NiceCmd(Mid, 4, request_timer, Int),
+ NiceCmd(Mid, 5, request_timer, infinity),
+ NiceCmd(Mid, 6, request_timer, IT),
+ NiceCmd(Mid, 7, long_request_timer, Int),
+ NiceCmd(Mid, 8, long_request_timer, infinity),
+ NiceCmd(Mid, 9, long_request_timer, IT),
+ NiceCmd(Mid, 10, auto_ack, true),
+ NiceCmd(Mid, 11, auto_ack, false),
+ NiceCmd(Mid, 12, pending_timer, Int),
+ NiceCmd(Mid, 13, pending_timer, infinity),
+ NiceCmd(Mid, 14, pending_timer, IT),
+ NiceCmd(Mid, 15, reply_timer, Int),
+ NiceCmd(Mid, 16, reply_timer, infinity),
+ NiceCmd(Mid, 17, reply_timer, IT),
+ NiceCmd(Mid, 18, send_mod, an_atom),
+ NiceCmd(Mid, 19, encoding_mod, an_atom),
+ NiceCmd(Mid, 20, encoding_config, []),
+ NiceCmd(Mid, 21, protocol_version, Int),
+ NiceCmd(Mid, 23, reply_data, IT),
+ NiceCmd(Mid, 23, resend_indication, true),
+ NiceCmd(Mid, 24, resend_indication, false),
+ NiceCmd(Mid, 25, resend_indication, flag),
%% Evil update
- evil_user_update_command(Mid, Evil + 1, min_trans_id, NonInt),
- evil_user_update_command(Mid, Evil + 2, max_trans_id, NonInt),
- evil_user_update_command(Mid, Evil + 3, max_trans_id, non_infinity),
- evil_user_update_command(Mid, Evil + 4, request_timer, NonInt),
- evil_user_update_command(Mid, Evil + 5, request_timer, non_infinity),
- evil_user_update_command(Mid, Evil + 6, request_timer, IT2),
- evil_user_update_command(Mid, Evil + 7, request_timer, IT3),
- evil_user_update_command(Mid, Evil + 8, request_timer, IT4),
- evil_user_update_command(Mid, Evil + 9, request_timer, IT5),
- evil_user_update_command(Mid, Evil + 10, long_request_timer, NonInt),
- evil_user_update_command(Mid, Evil + 11, long_request_timer, non_infinity),
- evil_user_update_command(Mid, Evil + 12, long_request_timer, IT2),
- evil_user_update_command(Mid, Evil + 13, long_request_timer, IT3),
- evil_user_update_command(Mid, Evil + 14, long_request_timer, IT4),
- evil_user_update_command(Mid, Evil + 15, long_request_timer, IT5),
- evil_user_update_command(Mid, Evil + 16, auto_ack, non_bool),
- evil_user_update_command(Mid, Evil + 17, pending_timer, NonInt),
- evil_user_update_command(Mid, Evil + 18, pending_timer, non_infinity),
- evil_user_update_command(Mid, Evil + 19, pending_timer, IT2),
- evil_user_update_command(Mid, Evil + 20, pending_timer, IT3),
- evil_user_update_command(Mid, Evil + 21, pending_timer, IT4),
- evil_user_update_command(Mid, Evil + 22, pending_timer, IT5),
- evil_user_update_command(Mid, Evil + 23, reply_timer, NonInt),
- evil_user_update_command(Mid, Evil + 24, reply_timer, non_infinity),
- evil_user_update_command(Mid, Evil + 25, reply_timer, IT2),
- evil_user_update_command(Mid, Evil + 26, reply_timer, IT3),
- evil_user_update_command(Mid, Evil + 27, reply_timer, IT4),
- evil_user_update_command(Mid, Evil + 28, reply_timer, IT5),
- evil_user_update_command(Mid, Evil + 29, send_mod, {non_atom}),
- evil_user_update_command(Mid, Evil + 30, encoding_mod, {non_atom}),
- evil_user_update_command(Mid, Evil + 31, encoding_config, non_list),
- evil_user_update_command(Mid, Evil + 32, protocol_version, NonInt),
- evil_user_update_command(Mid, Evil + 33, resend_indication, flagg),
-
-
- exit_command(End + 1,
- "Verify non-existing system info",
- fun() -> megaco:system_info(non_exist) end),
- exit_command(End + 2,
- "Verify non-existing user user info",
- fun() -> megaco:user_info(non_exist, trans_id) end),
- exit_command(End + 3, "Verify non-existing user info",
- fun() -> megaco:user_info(Mid, non_exist) end),
-
- error_command(End + 4,
- "Try updating user info for non-existing user",
- fun() ->
- megaco:update_user_info(non_exist, trans_id, 1)
- end,
- no_such_user, 2),
- error_command(End + 11,
- "Try updating non-existing user info",
- fun() ->
- megaco:update_user_info(Mid, trans_id, 4711)
- end,
- bad_user_val, 4),
- error_command(End + 12,
- "Try start already started user",
- fun() ->
- megaco:start_user(Mid, [])
- end,
- user_already_exists, 2),
-
- command(End + 13, "Verify started users",
- fun() -> megaco:system_info(users) end, [Mid]),
- command(End + 14, "Stop user", fun() -> megaco:stop_user(Mid) end, ok),
- command(End + 15, "Verify started users",
- fun() -> megaco:system_info(users) end, []),
- error_command(End + 16, "Try stop not started user",
- fun() -> megaco:stop_user(Mid) end, no_such_user, 2),
- error_command(End + 17, "Try start megaco (it's already started)",
- fun() -> megaco:start() end, already_started, 2),
- command(End + 18, "Stop megaco", fun() -> megaco:stop() end, ok),
- error_command(End + 19, "Try stop megaco (it's not running)",
- fun() -> megaco:stop() end, not_started, 2)
+ EvilCmd(Mid, 1, min_trans_id, NonInt),
+ EvilCmd(Mid, 2, max_trans_id, NonInt),
+ EvilCmd(Mid, 3, max_trans_id, non_infinity),
+ EvilCmd(Mid, 4, request_timer, NonInt),
+ EvilCmd(Mid, 5, request_timer, non_infinity),
+ EvilCmd(Mid, 6, request_timer, IT2),
+ EvilCmd(Mid, 7, request_timer, IT3),
+ EvilCmd(Mid, 8, request_timer, IT4),
+ EvilCmd(Mid, 9, request_timer, IT5),
+ EvilCmd(Mid, 10, long_request_timer, NonInt),
+ EvilCmd(Mid, 11, long_request_timer, non_infinity),
+ EvilCmd(Mid, 12, long_request_timer, IT2),
+ EvilCmd(Mid, 13, long_request_timer, IT3),
+ EvilCmd(Mid, 14, long_request_timer, IT4),
+ EvilCmd(Mid, 15, long_request_timer, IT5),
+ EvilCmd(Mid, 16, auto_ack, non_bool),
+ EvilCmd(Mid, 17, pending_timer, NonInt),
+ EvilCmd(Mid, 18, pending_timer, non_infinity),
+ EvilCmd(Mid, 19, pending_timer, IT2),
+ EvilCmd(Mid, 20, pending_timer, IT3),
+ EvilCmd(Mid, 21, pending_timer, IT4),
+ EvilCmd(Mid, 22, pending_timer, IT5),
+ EvilCmd(Mid, 23, reply_timer, NonInt),
+ EvilCmd(Mid, 24, reply_timer, non_infinity),
+ EvilCmd(Mid, 25, reply_timer, IT2),
+ EvilCmd(Mid, 26, reply_timer, IT3),
+ EvilCmd(Mid, 27, reply_timer, IT4),
+ EvilCmd(Mid, 28, reply_timer, IT5),
+ EvilCmd(Mid, 29, send_mod, {non_atom}),
+ EvilCmd(Mid, 30, encoding_mod, {non_atom}),
+ EvilCmd(Mid, 31, encoding_config, non_list),
+ EvilCmd(Mid, 32, protocol_version, NonInt),
+ EvilCmd(Mid, 33, resend_indication, flagg),
+
+
+ %% End
+ ExitCmd(1, "Verify non-existing system info",
+ fun() -> megaco:system_info(non_exist) end),
+ ExitCmd(2, "Verify non-existing user user info",
+ fun() -> megaco:user_info(non_exist, trans_id) end),
+ ExitCmd(3, "Verify non-existing user info",
+ fun() -> megaco:user_info(Mid, non_exist) end),
+
+ ErrorCmd(4, "Try updating user info for non-existing user",
+ fun() ->
+ megaco:update_user_info(non_exist, trans_id, 1)
+ end,
+ no_such_user, 2),
+ ErrorCmd(11, "Try updating non-existing user info",
+ fun() ->
+ megaco:update_user_info(Mid, trans_id, 4711)
+ end,
+ bad_user_val, 4),
+ ErrorCmd(12, "Try start already started user",
+ fun() -> megaco:start_user(Mid, []) end,
+ user_already_exists, 2),
+
+ PlainCmd(13, "Verify started users",
+ fun() -> megaco:system_info(users) end, [Mid]),
+ PlainCmd(14, "Stop user", fun() -> megaco:stop_user(Mid) end, ok),
+ PlainCmd(15, "Verify started users",
+ fun() -> megaco:system_info(users) end, []),
+ ErrorCmd(16, "Try stop not started user",
+ fun() -> megaco:stop_user(Mid) end, no_such_user, 2),
+ ErrorCmd(17, "Try start megaco (it's already started)",
+ fun() -> megaco:start() end, already_started, 2),
+ PlainCmd(18, "Stop megaco", fun() -> megaco:stop() end, ok),
+ ErrorCmd(19, "Try stop megaco (it's not running)",
+ fun() -> megaco:stop() end, not_started, 2)
],
@@ -279,7 +310,7 @@ exec([#command{id = No,
desc = Desc,
cmd = Cmd,
verify = Verify}|Commands]) ->
- io:format("Executing command ~2w: ~s: ", [No, Desc]),
+ io:format("Executing command ~3w: ~s: ", [No, Desc]),
case (catch Verify((catch Cmd()))) of
{ok, OK} ->
io:format("ok => ~p~n", [OK]),
@@ -320,7 +351,7 @@ nice_user_update_command(Mid, No, Key, Val) ->
evil_user_update_command(Mid, No, Key, Val) ->
- Desc = lists:flatten(io_lib:format("Evil: Update ~w", [Key])),
+ Desc = lists:flatten(io_lib:format("Evil - Update ~w", [Key])),
Cmd = fun() ->
case (catch megaco:user_info(Mid, Key)) of
{'EXIT', R} ->
@@ -371,14 +402,19 @@ error_command(No, Desc, Cmd, MainReason, TS) when is_function(Cmd) ->
end,
command(No, Desc, Cmd, Verify).
-command(No, Desc, Cmd, Verify) when is_integer(No) and is_list(Desc) and
- is_function(Cmd) and is_function(Verify) ->
+command(No, Desc, Cmd, Verify)
+ when (is_integer(No) andalso
+ is_list(Desc) andalso
+ is_function(Cmd) andalso
+ is_function(Verify)) ->
#command{id = No,
desc = Desc,
cmd = Cmd,
verify = Verify};
-command(No, Desc, Cmd, VerifyVal) when is_integer(No) and is_list(Desc) and
- is_function(Cmd) ->
+command(No, Desc, Cmd, VerifyVal)
+ when (is_integer(No) andalso
+ is_list(Desc) andalso
+ is_function(Cmd)) ->
Verify = fun(Val) ->
case Val of
VerifyVal ->
@@ -881,6 +917,12 @@ otp_8167(Config) when is_list(Config) ->
p("connect ok: CD = ~n~p", [CD]),
CH = CD#conn_data.conn_handle,
+ p("get value for item cancel from connection: ~p", [CH]),
+ false = megaco_config:conn_info(CH, cancel),
+
+ p("get value for item cancel from connection data", []),
+ false = megaco_config:conn_info(CD, cancel),
+
p("get value for item call_proxy_gc_timeout for connection: ~p", [CH]),
10101 = megaco_config:conn_info(CH, call_proxy_gc_timeout),
diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk
index 19eca6d309..efb46253aa 100644
--- a/lib/megaco/vsn.mk
+++ b/lib/megaco/vsn.mk
@@ -18,11 +18,15 @@
# %CopyrightEnd%
APPLICATION = megaco
-MEGACO_VSN = 3.14
+MEGACO_VSN = 3.14.1.1
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)"
-TICKETS = OTP-8317 OTP-8323 OTP-8328 OTP-8362 OTP-8403
+TICKETS = OTP-8696
+
+TICKETS_3_14_1 = OTP-8529 OTP-8561 OTP-8627 OTP-8634
+
+TICKETS_3_14 = OTP-8317 OTP-8323 OTP-8328 OTP-8362 OTP-8403
TICKETS_3_13 = OTP-8205 OTP-8239 OTP-8249
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 66242398d9..b0bead0ba0 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -37,6 +37,22 @@
bugfixes for every release of Mnesia. Each release of Mnesia
thus constitutes one section in this document. The title of each
section is the version number of Mnesia.</p>
+
+ <section><title>Mnesia 4.4.14</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added mnesia:subscribe(activity) contributed by Bernard
+ Duggan.</p>
+ <p>
+ Own Id: OTP-8519</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
<section><title>Mnesia 4.4.13</title>
diff --git a/lib/mnesia/examples/mnesia_meter.erl b/lib/mnesia/examples/mnesia_meter.erl
index ea74d8691b..68094c4431 100644
--- a/lib/mnesia/examples/mnesia_meter.erl
+++ b/lib/mnesia/examples/mnesia_meter.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
@@ -407,7 +407,7 @@ run(Nodes, Config, FunOverhead) ->
stop(Nodes),
Res.
-run_meter(M, Nodes, FunOverhead) when record(M, meter) ->
+run_meter(M, Nodes, FunOverhead) when is_record(M, meter) ->
io:format(".", []),
case catch init_records(M#meter.init, ?TIMES) of
{atomic, ok} ->
diff --git a/lib/mnesia/src/mnesia.appup.src b/lib/mnesia/src/mnesia.appup.src
index b3b9297db2..47c9bf9979 100644
--- a/lib/mnesia/src/mnesia.appup.src
+++ b/lib/mnesia/src/mnesia.appup.src
@@ -1,69 +1,7 @@
%% -*- erlang -*-
{"%VSN%",
- [
- {"4.4.12",
- [
- {update, mnesia, soft, soft_purge, soft_purge, []},
- {update, mnesia_loader, soft, soft_purge, soft_purge, []},
- {update, mnesia_monitor, soft, soft_purge, soft_purge, []},
- {update, mnesia_tm, soft, soft_purge, soft_purge, []}
- ]
- },
- {"4.4.11",
- [
- {update, mnesia, soft, soft_purge, soft_purge, []},
- {update, mnesia_loader, soft, soft_purge, soft_purge, []},
- {update, mnesia_monitor, soft, soft_purge, soft_purge, []},
- {update, mnesia_tm, soft, soft_purge, soft_purge, []},
- {update, mnesia_locker, soft, soft_purge, soft_purge, []},
- {update, mnesia_controller, soft, soft_purge, soft_purge, []}
- ]
- },
- {"4.4.10",
- [
- {update, mnesia, soft, soft_purge, soft_purge, []},
- {update, mnesia_loader, soft, soft_purge, soft_purge, []},
- {update, mnesia_monitor, soft, soft_purge, soft_purge, []},
- {update, mnesia_tm, soft, soft_purge, soft_purge, []},
- {update, mnesia_locker, soft, soft_purge, soft_purge, []},
- {update, mnesia_controller, soft, soft_purge, soft_purge, []}
- ]
- },
- {"4.4.9", [{restart_application, mnesia}]},
- {"4.4.8", [{restart_application, mnesia}]},
- {"4.4.7", [{restart_application, mnesia}]}
+ [
],
[
- {"4.4.12",
- [
- {update, mnesia, soft, soft_purge, soft_purge, []},
- {update, mnesia_loader, soft, soft_purge, soft_purge, []},
- {update, mnesia_monitor, soft, soft_purge, soft_purge, []},
- {update, mnesia_tm, soft, soft_purge, soft_purge, []}
- ]
- },
- {"4.4.11",
- [
- {update, mnesia, soft, soft_purge, soft_purge, []},
- {update, mnesia_loader, soft, soft_purge, soft_purge, []},
- {update, mnesia_monitor, soft, soft_purge, soft_purge, []},
- {update, mnesia_tm, soft, soft_purge, soft_purge, []},
- {update, mnesia_locker, soft, soft_purge, soft_purge, []},
- {update, mnesia_controller, soft, soft_purge, soft_purge, []}
- ]
- },
- {"4.4.10",
- [
- {update, mnesia, soft, soft_purge, soft_purge, []},
- {update, mnesia_loader, soft, soft_purge, soft_purge, []},
- {update, mnesia_monitor, soft, soft_purge, soft_purge, []},
- {update, mnesia_tm, soft, soft_purge, soft_purge, []},
- {update, mnesia_locker, soft, soft_purge, soft_purge, []},
- {update, mnesia_controller, soft, soft_purge, soft_purge, []}
- ]
- },
- {"4.4.9", [{restart_application, mnesia}]},
- {"4.4.8", [{restart_application, mnesia}]},
- {"4.4.7", [{restart_application, mnesia}]}
]
}.
diff --git a/lib/mnesia/src/mnesia_controller.erl b/lib/mnesia/src/mnesia_controller.erl
index 9bc480e619..0298b382a6 100644
--- a/lib/mnesia/src/mnesia_controller.erl
+++ b/lib/mnesia/src/mnesia_controller.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -52,6 +52,7 @@
async_dump_log/1,
sync_dump_log/1,
connect_nodes/1,
+ connect_nodes/2,
wait_for_schema_commit_lock/0,
release_schema_commit_lock/0,
create_table/1,
@@ -94,7 +95,7 @@
load_and_reply/2,
send_and_reply/2,
wait_for_tables_init/2,
- connect_nodes2/2
+ connect_nodes2/3
]).
-import(mnesia_lib, [set/2, add/2]).
@@ -420,12 +421,15 @@ try_schedule_late_disc_load(Tabs, Reason, MsgTag) ->
[[Tabs, Reason, MsgTag], AbortReason])
end.
-connect_nodes(Ns) ->
+connect_nodes(Ns) ->
+ connect_nodes(Ns, fun default_merge/1).
+
+connect_nodes(Ns, UserFun) ->
case mnesia:system_info(is_running) of
no ->
{error, {node_not_running, node()}};
yes ->
- Pid = spawn_link(?MODULE,connect_nodes2,[self(),Ns]),
+ Pid = spawn_link(?MODULE,connect_nodes2,[self(),Ns, UserFun]),
receive
{?MODULE, Pid, Res, New} ->
case Res of
@@ -443,7 +447,7 @@ connect_nodes(Ns) ->
end
end.
-connect_nodes2(Father, Ns) ->
+connect_nodes2(Father, Ns, UserFun) ->
Current = val({current, db_nodes}),
abcast([node()|Ns], {merging_schema, node()}),
{NewC, OldC} = mnesia_recover:connect_nodes(Ns),
@@ -451,7 +455,7 @@ connect_nodes2(Father, Ns) ->
New1 = mnesia_lib:intersect(Ns, Connected),
New = New1 -- Current,
process_flag(trap_exit, true),
- Res = try_merge_schema(New),
+ Res = try_merge_schema(New, UserFun),
Msg = {schema_is_merged, [], late_merge, []},
multicall([node()|Ns], Msg),
After = val({current, db_nodes}),
@@ -465,7 +469,7 @@ connect_nodes2(Father, Ns) ->
merge_schema() ->
AllNodes = mnesia_lib:all_nodes(),
- case try_merge_schema(AllNodes) of
+ case try_merge_schema(AllNodes, fun default_merge/1) of
ok ->
schema_is_merged();
{aborted, {throw, Str}} when is_list(Str) ->
@@ -474,8 +478,11 @@ merge_schema() ->
fatal("Failed to merge schema: ~p~n", [Else])
end.
-try_merge_schema(Nodes) ->
- case mnesia_schema:merge_schema() of
+default_merge(F) ->
+ F([]).
+
+try_merge_schema(Nodes, UserFun) ->
+ case mnesia_schema:merge_schema(UserFun) of
{atomic, not_merged} ->
%% No more nodes that we need to merge the schema with
ok;
@@ -488,11 +495,11 @@ try_merge_schema(Nodes) ->
im_running(OldFriends, NewFriends),
im_running(NewFriends, OldFriends),
- try_merge_schema(Nodes);
+ try_merge_schema(Nodes, UserFun);
{atomic, {"Cannot get cstructs", Node, Reason}} ->
dbg_out("Cannot get cstructs, Node ~p ~p~n", [Node, Reason]),
timer:sleep(1000), % Avoid a endless loop look alike
- try_merge_schema(Nodes);
+ try_merge_schema(Nodes, UserFun);
Other ->
Other
end.
@@ -1842,17 +1849,20 @@ reply(ReplyTo, Reply) ->
add_worker(Worker = #dump_log{}, State) ->
InitBy = Worker#dump_log.initiated_by,
Queue = State#state.dumper_queue,
- case lists:keymember(InitBy, #dump_log.initiated_by, Queue) of
- true when Worker#dump_log.opt_reply_to == undefined ->
- %% The same threshold has been exceeded again,
- %% before we have had the possibility to
- %% process the older one.
- DetectedBy = {dump_log, InitBy},
- Event = {mnesia_overload, DetectedBy},
- mnesia_lib:report_system_event(Event);
- _ ->
- ignore
- end,
+ Status =
+ case lists:keymember(InitBy, #dump_log.initiated_by, Queue) of
+ true when Worker#dump_log.opt_reply_to == undefined ->
+ %% The same threshold has been exceeded again,
+ %% before we have had the possibility to
+ %% process the older one.
+ DetectedBy = {dump_log, InitBy},
+ Event = {mnesia_overload, DetectedBy},
+ mnesia_lib:report_system_event(Event),
+ true;
+ _ ->
+ false
+ end,
+ mnesia_recover:log_dump_overload(Status),
Queue2 = Queue ++ [Worker],
State2 = State#state{dumper_queue = Queue2},
opt_start_worker(State2);
diff --git a/lib/mnesia/src/mnesia_lib.erl b/lib/mnesia/src/mnesia_lib.erl
index dba808e66e..3da3dd2f5c 100644
--- a/lib/mnesia/src/mnesia_lib.erl
+++ b/lib/mnesia/src/mnesia_lib.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -113,6 +113,9 @@
mkcore/1,
not_active_here/1,
other_val/2,
+ overload_read/0,
+ overload_read/1,
+ overload_set/2,
pad_name/3,
random_time/2,
read_counter/1,
@@ -551,6 +554,33 @@ cs_to_nodes(Cs) ->
Cs#cstruct.disc_only_copies ++
Cs#cstruct.disc_copies ++
Cs#cstruct.ram_copies.
+
+overload_types() ->
+ [mnesia_tm, mnesia_dump_log].
+
+valid_overload_type(T) ->
+ case lists:member(T, overload_types()) of
+ false ->
+ erlang:error(bad_type);
+ true ->
+ true
+ end.
+
+overload_set(Type, Bool) when is_boolean(Bool) ->
+ valid_overload_type(Type),
+ set({overload, Type}, Bool).
+
+overload_read() ->
+ [{T, overload_read(T)} || T <- overload_types()].
+
+overload_read(T) ->
+ case ?catch_val({overload, T}) of
+ {'EXIT',_} ->
+ valid_overload_type(T),
+ false;
+ Flag when is_boolean(Flag) ->
+ Flag
+ end.
dist_coredump() ->
dist_coredump(all_nodes()).
diff --git a/lib/mnesia/src/mnesia_recover.erl b/lib/mnesia/src/mnesia_recover.erl
index 6c53c2e752..0ca7bf3f7f 100644
--- a/lib/mnesia/src/mnesia_recover.erl
+++ b/lib/mnesia/src/mnesia_recover.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
@@ -36,6 +36,7 @@
incr_trans_tid_serial/0,
init/0,
log_decision/1,
+ log_dump_overload/1,
log_master_nodes/3,
log_mnesia_down/1,
log_mnesia_up/1,
@@ -70,6 +71,7 @@
unclear_decision,
unclear_waitfor,
tm_queue_len = 0,
+ log_dump_overload = false,
initiated = false,
early_msgs = []
}).
@@ -277,6 +279,9 @@ mnesia_down(Node) ->
cast({mnesia_down, Node})
end.
+log_dump_overload(Flag) when is_boolean(Flag) ->
+ cast({log_dump_overload, Flag}).
+
log_master_nodes(Args, UseDir, IsRunning) ->
if
IsRunning == yes ->
@@ -818,6 +823,12 @@ handle_cast({announce_all, Nodes}, State) ->
announce_all(Nodes),
{noreply, State};
+handle_cast({log_dump_overload, Flag}, State) when is_boolean(Flag) ->
+ Prev = State#state.log_dump_overload,
+ Overload = Prev orelse Flag,
+ mnesia_lib:overload_set(mnesia_dump_log, Overload),
+ {noreply, State#state{log_dump_overload = Flag}};
+
handle_cast(Msg, State) ->
error("~p got unexpected cast: ~p~n", [?MODULE, Msg]),
{noreply, State}.
@@ -851,12 +862,14 @@ handle_info(check_overload, S) ->
Len > Threshold, Prev > Threshold ->
What = {mnesia_tm, message_queue_len, [Prev, Len]},
mnesia_lib:report_system_event({mnesia_overload, What}),
+ mnesia_lib:overload_set(mnesia_tm, true),
{noreply, S#state{tm_queue_len = 0}};
Len > Threshold ->
{noreply, S#state{tm_queue_len = Len}};
true ->
+ mnesia_lib:overload_set(mnesia_tm, false),
{noreply, S#state{tm_queue_len = 0}}
end;
undefined ->
@@ -905,7 +918,23 @@ terminate(Reason, State) ->
%% Purpose: Upgrade process when its code is to be changed
%% Returns: {ok, NewState}
%%----------------------------------------------------------------------
-code_change(_OldVsn, State, _Extra) ->
+code_change(_OldVsn, {state,
+ Supervisor,
+ Unclear_pid,
+ Unclear_decision,
+ Unclear_waitfor,
+ Tm_queue_len,
+ Initiated,
+ Early_msgs
+ }, _Extra) ->
+ {ok, #state{supervisor = Supervisor,
+ unclear_pid = Unclear_pid,
+ unclear_decision = Unclear_decision,
+ unclear_waitfor = Unclear_waitfor,
+ tm_queue_len = Tm_queue_len,
+ initiated = Initiated,
+ early_msgs = Early_msgs}};
+code_change(_OldVsn, #state{} = State, _Extra) ->
{ok, State}.
%%%----------------------------------------------------------------------
diff --git a/lib/mnesia/src/mnesia_schema.erl b/lib/mnesia/src/mnesia_schema.erl
index 354431a296..17e570b881 100644
--- a/lib/mnesia/src/mnesia_schema.erl
+++ b/lib/mnesia/src/mnesia_schema.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -62,6 +62,7 @@
list2cs/1,
lock_schema/0,
merge_schema/0,
+ merge_schema/1,
move_table/3,
opt_create_dir/2,
prepare_commit/3,
@@ -2650,10 +2651,16 @@ make_dump_tables([]) ->
%% Merge the local schema with the schema on other nodes
merge_schema() ->
- schema_transaction(fun() -> do_merge_schema() end).
+ schema_transaction(fun() -> do_merge_schema([]) end).
+
+merge_schema(UserFun) ->
+ schema_transaction(fun() -> UserFun(fun(Arg) -> do_merge_schema(Arg) end) end).
-do_merge_schema() ->
+
+do_merge_schema(LockTabs0) ->
{_Mod, Tid, Ts} = get_tid_ts_and_lock(schema, write),
+ LockTabs = [{T, tab_to_nodes(T)} || T <- LockTabs0],
+ [get_tid_ts_and_lock(T,write) || {T,_} <- LockTabs],
Connected = val(recover_nodes),
Running = val({current, db_nodes}),
Store = Ts#tidstore.store,
@@ -2665,9 +2672,12 @@ do_merge_schema() ->
mnesia:abort({bad_commit, {missing_lock, Miss}})
end,
case Connected -- Running of
- [Node | _] ->
+ [Node | _] = OtherNodes ->
%% Time for a schema merging party!
mnesia_locker:wlock_no_exist(Tid, Store, schema, [Node]),
+ [mnesia_locker:wlock_no_exist(
+ Tid, Store, T, mnesia_lib:intersect(Ns, OtherNodes))
+ || {T,Ns} <- LockTabs],
case rpc:call(Node, mnesia_controller, get_cstructs, []) of
{cstructs, Cstructs, RemoteRunning1} ->
LockedAlready = Running ++ [Node],
@@ -2681,6 +2691,9 @@ do_merge_schema() ->
end,
NeedsLock = RemoteRunning -- LockedAlready,
mnesia_locker:wlock_no_exist(Tid, Store, schema, NeedsLock),
+ [mnesia_locker:wlock_no_exist(Tid, Store, T,
+ mnesia_lib:intersect(Ns,NeedsLock))
+ || {T,Ns} <- LockTabs],
{value, SchemaCs} =
lists:keysearch(schema, #cstruct.name, Cstructs),
@@ -2714,6 +2727,10 @@ do_merge_schema() ->
not_merged
end.
+tab_to_nodes(Tab) when is_atom(Tab) ->
+ Cs = val({Tab, cstruct}),
+ mnesia_lib:cs_to_nodes(Cs).
+
make_merge_schema(Node, [Cs | Cstructs]) ->
Ops = do_make_merge_schema(Node, Cs),
Ops ++ make_merge_schema(Node, Cstructs);
diff --git a/lib/mnesia/test/Makefile b/lib/mnesia/test/Makefile
new file mode 100644
index 0000000000..a4f32e3f78
--- /dev/null
+++ b/lib/mnesia/test/Makefile
@@ -0,0 +1,118 @@
+#
+# %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%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES= \
+ mt \
+ mnesia_SUITE \
+ mnesia_test_lib \
+ mnesia_install_test \
+ mnesia_registry_test \
+ mnesia_config_test \
+ mnesia_frag_test \
+ mnesia_inconsistent_database_test \
+ mnesia_config_backup \
+ mnesia_config_event \
+ mnesia_examples_test \
+ mnesia_nice_coverage_test \
+ mnesia_evil_coverage_test \
+ mnesia_evil_backup \
+ mnesia_trans_access_test \
+ mnesia_dirty_access_test \
+ mnesia_atomicity_test \
+ mnesia_consistency_test \
+ mnesia_isolation_test \
+ mnesia_durability_test \
+ mnesia_recovery_test \
+ mnesia_qlc_test \
+ mnesia_schema_recovery_test \
+ mnesia_measure_test \
+ mnesia_cost \
+ mnesia_dbn_meters
+
+MnesiaExamplesDir := ../examples
+
+ExampleModules = \
+ company \
+ company_o \
+ bup \
+ mnesia_meter \
+ mnesia_tpcb
+ExamplesHrl = \
+ company.hrl \
+ company_o.hrl
+
+ERL_FILES= $(MODULES:%=%.erl) $(ExampleModules:%=$(MnesiaExamplesDir)/%.erl)
+
+HRL_FILES= mnesia_test_lib.hrl $(ExamplesHrl:%=$(MnesiaExamplesDir)/%)
+
+TARGET_FILES= \
+ $(MODULES:%=$(EBIN)/%.$(EMULATOR)) $(ExampleModules:%=$(EBIN)/%.$(EMULATOR))
+
+INSTALL_PROGS= $(TARGET_FILES)
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/mnesia_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+#ERL_COMPILE_FLAGS +=
+
+EBIN = .
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+tests debug opt: $(TARGET_FILES)
+
+$(EBIN)/%.beam: $(MnesiaExamplesDir)/%.erl
+ $(ERLC) -bbeam $(ERL_COMPILE_FLAGS) -o$(EBIN) $<
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) mnesia.spec mnesia.spec.vxworks $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)
+ $(INSTALL_PROGRAM) mt $(INSTALL_PROGS) $(RELSYSDIR)
+# chmod -f -R u+w $(RELSYSDIR)
+# @tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
+
+release_docs_spec:
+
+
diff --git a/lib/mnesia/test/README b/lib/mnesia/test/README
new file mode 100644
index 0000000000..e0ced7399d
--- /dev/null
+++ b/lib/mnesia/test/README
@@ -0,0 +1,107 @@
+This directory contains the test suite of Mnesia.
+Compile it with "erl -make".
+
+Test cases are identified with a {Mod, Fun} tuple that maps
+to a function Mod:Fun(Config), where the test case hopefully
+is implemented. The test suite is organized in a hierarchy
+with {mnesia_SUITE, all} as the top.
+
+The module called mt, implements various convenience functions
+to ease up the execution of test cases. It does also provide
+aliases for some test cases. For example the atom Mod is an
+alias for {Mod, all}, the atom all for {mnesia_SUITE, all},
+evil for mnesia_evil_coverage_test etc.
+
+ mt:struct(TestCase)
+
+ Displays the test case structure from TestCase
+ and downwards the hierarchy. E.g. mt:struct(all)
+ will display the entire test suite.
+
+ mt:t(TestCase), mt:t(TestCase, Config)
+
+ Runs a single test case or a hierarchy of test cases.
+ mt:t(silly) is be a good starter, but you may also
+ try mt:t(all) directly if you feel lucky.
+
+ The identity of the last run test case and the outcome of
+ it is stored on file. mt:t() will re-run the last test case.
+
+ The Config argument contains various configuration
+ parameters for the test cases, such as which nodes that
+ are available for running the test suite. The default
+ settings should be enough for the most. Use mt:read_config()
+ to get the current default setting and change it with
+ mt:write_config(Config).
+
+ mt:doc(TestCase)
+
+ Generates html documentation for the test suite.
+
+In order to be able to run the test suite, the Erlang node must
+be started with the distribution enabled and the code path must
+be set to the mnesia/ebin, mnesia/examples, and mnesia/test
+directories. E.g. the following would do:
+
+ erl -sname a -pa $top/examples -pa $top/src -pa $top/ebin
+
+where $top is the path to the Mnesia installation. Many test
+cases needs 2 or 3 nodes. The node names may explicitly be
+stated as test suite configuration parameters, but by default
+the extra node names are generated. In this example the names
+will be: a, a1 and a2. It is enough to start the first node
+manually, the extra nodes will automatically be started if
+neccessary.
+
+The attached UNIX shell script mt, does not work on all
+platforms, but it may be used as a source for inspiration. It
+starts three Erlang nodes in one xterm's each. The main xterm
+(a@localhost) logs all output in the Erlang shell to a
+file. The file is piped thru grep to easily find successful
+test cases (i.e. test cases that encountered an error).
+
+During development we want to be able to run the test cases
+in the debugger. This demands a little bit of preparations:
+
+ - Start the neccessary number of nodes (normally 3).
+ This may either be done by running the mt script or
+ by starting the main node and then invoke mt:start_nodes()
+ to start the extra nodes with slave.
+
+ - Ensure that the nodes are connected. The easiest way to do
+ this is by invoking mt:ping().
+
+ - Load all files that needs to be interpreted. This is typically
+ all Mnesia files plus the test case. By invoking mnesia:ni()
+ and mnesia:ni([TestModule]) the neccessary modules will be
+ loaded on all CONNECTED nodes.
+
+The test case execution is supervised in order to ensure that no test
+case exceeds its maximum time limit, which by default is 5 minutes.
+When the limit is reached, the running test case gets aborted and the
+test server runs the next test case in line. This behaviour is useful
+when running the entire test suite during the night, but it is really
+annoying during debugging.
+
+ Use the "erl -mnesia_test_timeout" flag to disable the test case
+ time limit mechanism.
+
+Some mechanisms in Mnesia are almost impossible to test with a
+white box technique. In order to be able to write predictable
+test cases which tests the same thing every time it is run,
+Mnesia has been instrumented with debug functions. These may be
+controlled from a test program. For example to verify that the
+commit protocols work it is essential that it is possible to
+ensure that we are able to kill Mnesia in the most critical
+situations. Normally Mnesia is compiled with the debug
+functions disabled and this means that test cases which
+requires this functionality will be skipped. The mnesia:ni(),
+mentioned above, functions ensures that the interpreted code is
+instrumented with Mnesia's debug functionality. The mnesia:nc()
+functions compiles Mnesia with the debug setting enabled.
+
+Happy bug hunting!
+
+ Hakan Mattsson <[email protected]>
+
+
diff --git a/lib/mnesia/test/mnesia.spec b/lib/mnesia/test/mnesia.spec
new file mode 100644
index 0000000000..596f8b917d
--- /dev/null
+++ b/lib/mnesia/test/mnesia.spec
@@ -0,0 +1,23 @@
+{topcase, {dir, "../mnesia_test"}}.
+{require_nodenames, 2}.
+{skip, {mnesia_measure_test, ram_meter, "Takes to long time"}}.
+{skip, {mnesia_measure_test, disc_meter, "Takes to long time"}}.
+{skip, {mnesia_measure_test, disc_only_meter, "Takes to long time"}}.
+{skip, {mnesia_measure_test, cost, "Takes to long time"}}.
+{skip, {mnesia_measure_test, dbn_meters, "Takes to long time"}}.
+{skip, {mnesia_measure_test, tpcb, "Takes to long time"}}.
+{skip, {mnesia_measure_test, prediction, "Not yet implemented"}}.
+{skip, {mnesia_measure_test, consumption, "Not yet implemented"}}.
+{skip, {mnesia_measure_test, scalability, "Not yet implemented"}}.
+{skip, {mnesia_measure_test, tpcb, "Takes too much time and memory"}}.
+{skip, {mnesia_measure_test, measure_all_api_functions, "Not yet implemented"}}.
+{skip, {mnesia_measure_test, mnemosyne_vs_mnesia_kernel, "Not yet implemented"}}.
+{skip, {mnesia_examples_test, company, "Not yet implemented"}}.
+{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not yet implemented"}}.
+{skip, {mnesia_evil_backup, local_backup_checkpoint, "Not yet implemented"}}.
+{skip, {mnesia_config_test, max_wait_for_decision, "Not yet implemented"}}.
+{skip, {mnesia_recovery_test, after_full_disc_partition, "Not yet implemented"}}.
+{skip, {mnesia_recovery_test, system_upgrade, "Not yet implemented"}}.
+{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}.
+{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}.
+{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}.
diff --git a/lib/mnesia/test/mnesia.spec.vxworks b/lib/mnesia/test/mnesia.spec.vxworks
new file mode 100644
index 0000000000..11c01ea3fe
--- /dev/null
+++ b/lib/mnesia/test/mnesia.spec.vxworks
@@ -0,0 +1,362 @@
+{topcase, {dir, "../mnesia_test"}}.
+{require_nodenames, 3}.
+{diskless, true}.
+{skip, {mnesia_measure_test, all, "Too heavy"}}.
+%{mnesia_install_test, silly_durability} 'IMPL'
+%{mnesia_install_test, silly_move} 'IMPL'
+{skip, {mnesia_install_test, silly_upgrade, "Uses disk"}}.
+%{mnesia_install_test, conflict} 'IMPL'
+%{mnesia_install_test, dist} 'IMPL'
+{skip, {mnesia_examples_test, all, "Uses disk"}}.
+{skip, {mnesia_nice_coverage_test, all, "Uses disk"}}.
+
+%{mnesia_evil_coverage_test, system_info} 'IMPL'
+%{mnesia_evil_coverage_test, table_info} 'IMPL'
+%{mnesia_evil_coverage_test, error_description} 'IMPL'
+{skip, {mnesia_evil_coverage_test, db_node_lifecycle, "Uses disk"}}.
+{skip, {mnesia_evil_coverage_test, local_content, "Uses disk"}}.
+%{mnesia_evil_coverage_test, start_and_stop} 'IMPL'
+%{mnesia_evil_coverage_test, transaction} 'IMPL'
+{skip, {mnesia_evil_coverage_test, checkpoint, "Uses disk"}}.
+{skip, {mnesia_evil_backup, backup, "Uses disk"}}.
+{skip, {mnesia_evil_backup, global_backup_checkpoint, "Uses disk"}}.
+{skip, {mnesia_evil_backup, incremental_backup_checkpoint, "Uses disk"}}.
+{skip, {mnesia_evil_backup, local_backup_checkpoint, "Uses disk"}}.
+{skip, {mnesia_evil_backup, selective_backup_checkpoint, "Uses disk"}}.
+{skip, {mnesia_evil_backup, restore_errors, "Uses disk"}}.
+{skip, {mnesia_evil_backup, restore_clear, "Uses disk"}}.
+{skip, {mnesia_evil_backup, restore_keep, "Uses disk"}}.
+{skip, {mnesia_evil_backup, restore_recreate, "Uses disk"}}.
+{skip, {mnesia_evil_backup, traverse_backup, "Uses disk"}}.
+{skip, {mnesia_evil_backup, install_fallback, "Uses disk"}}.
+{skip, {mnesia_evil_backup, uninstall_fallback, "Uses disk"}}.
+{skip, {mnesia_evil_backup, local_fallback, "Uses disk"}}.
+%{mnesia_evil_coverage_test, table_lifecycle} 'IMPL'
+{skip, {mnesia_evil_coverage_test, replica_management, "Uses disk"}}.
+%{mnesia_evil_coverage_test, change_table_access_mode} 'IMPL'
+%{mnesia_evil_coverage_test, change_table_load_order} 'IMPL'
+{skip, {mnesia_evil_coverage_test, set_master_nodes, "Uses disk"}}.
+{skip, {mnesia_evil_coverage_test, offline_set_master_nodes, "Uses disk"}}.
+{skip, {mnesia_evil_coverage_test, replica_location, "Uses disk"}}.
+%{mnesia_evil_coverage_test, add_table_index_ram} 'IMPL'
+{skip, {mnesia_trans_access_test, add_table_index_disc, "Uses disc"}}.
+{skip, {mnesia_trans_access_test, add_table_index_disc_only, "Uses disc"}}.
+%{mnesia_evil_coverage_test, create_live_table_index_ram} 'IMPL'
+{skip, {mnesia_trans_access_test, create_live_table_index_disc, "Uses disc"}}.
+{skip, {mnesia_trans_access_test, create_live_table_index_disc_only, "Uses disc"}}.
+%{mnesia_evil_coverage_test, del_table_index_ram} 'IMPL'
+{skip, {mnesia_trans_access_test, del_table_index_disc, "Uses disc"}}.
+{skip, {mnesia_trans_access_test, del_table_index_disc_only, "Uses disc"}}.
+{skip, {mnesia_trans_access_test, idx_schema_changes_ram, "Uses disk"}}.
+{skip, {mnesia_trans_access_test, idx_schema_changes_disc, "Uses disc"}}.
+{skip, {mnesia_trans_access_test, idx_schema_changes_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_write_ram} 'IMPL'
+
+{skip, {mnesia_dirty_access_test, dirty_write_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_write_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_read_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_read_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_read_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_update_counter_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_update_counter_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_update_counter_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_delete_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_delete_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_delete_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_delete_object_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_delete_object_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_delete_object_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_match_object_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_match_object_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_match_object_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_index_match_object_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_index_match_object_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_index_read_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_index_read_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_index_read_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_index_update_set_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_index_update_set_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_index_update_bag_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_index_update_bag_disc_only, "Uses disc"}}.
+%{mnesia_dirty_access_test, dirty_iter_ram} 'IMPL'
+{skip, {mnesia_dirty_access_test, dirty_iter_disc, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, dirty_iter_disc_only, "Uses disc"}}.
+{skip, {mnesia_dirty_access_test, admin_tests, "Uses disk"}}.
+
+%{mnesia_trans_access_test, write} 'IMPL'
+%{mnesia_trans_access_test, read} 'IMPL'
+%{mnesia_trans_access_test, wread} 'IMPL'
+%{mnesia_trans_access_test, delete} 'IMPL'
+%{mnesia_trans_access_test, delete_object} 'IMPL'
+%{mnesia_trans_access_test, match_object} 'IMPL'
+%{mnesia_trans_access_test, all_keys} 'IMPL'
+%{mnesia_trans_access_test, index_match_object} 'IMPL'
+%{mnesia_trans_access_test, index_read} 'IMPL'
+%{mnesia_trans_access_test, index_update_set} 'IMPL'
+%{mnesia_trans_access_test, index_update_bag} 'IMPL'
+{skip, {mnesia_evil_coverage_test, dump_tables, "Uses disk"}}.
+{skip, {mnesia_evil_coverage_test, dump_log, "Uses disk"}}.
+%{mnesia_evil_coverage_test, wait_for_tables} 'IMPL'
+{skip, {mnesia_evil_coverage_test, force_load_table, "Uses disk"}}.
+%{mnesia_evil_coverage_test, user_properties} 'IMPL'
+%{mnesia_evil_coverage_test, record_name_dirty_access_ram} 'IMPL'
+{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc, "Uses disc"}}.
+{skip, {mnesia_evil_coverage_test, record_name_dirty_access_disc_only, "Uses disc"}}.
+%{mnesia_evil_coverage_test, snmp_open_table} 'IMPL'
+%{mnesia_evil_coverage_test, snmp_close_table} 'IMPL'
+%{mnesia_evil_coverage_test, snmp_get_next_index} 'IMPL'
+%{mnesia_evil_coverage_test, snmp_get_row} 'IMPL'
+%{mnesia_evil_coverage_test, snmp_get_mnesia_key} 'IMPL'
+%{mnesia_evil_coverage_test, snmp_update_counter} 'IMPL'
+%{mnesia_evil_coverage_test, info} 'IMPL'
+%{mnesia_evil_coverage_test, schema_0} 'IMPL'
+%{mnesia_evil_coverage_test, schema_1} 'IMPL'
+%{mnesia_evil_coverage_test, view_0} 'IMPL'
+{skip, {mnesia_evil_coverage_test, view_1, "Uses disk"}}.
+{skip, {mnesia_evil_coverage_test, view_2, "Uses disk"}}.
+%{mnesia_evil_coverage_test, lkill} 'IMPL'
+%{mnesia_evil_coverage_test, kill} 'IMPL'
+
+%{mnesia_config_test, access_module} 'IMPL'
+%{mnesia_config_test, auto_repair} 'IMPL'
+{skip, {mnesia_config_test, backup_module, "Uses disk"}}.
+{skip, {mnesia_config_test, dynamic_connect, "Uses disk"}}.
+%{mnesia_config_test, debug} 'IMPL'
+%{mnesia_config_test, dir} 'IMPL'
+{skip, {mnesia_config_test, dump_log_load_regulation, "Uses disk"}}.
+{skip, {mnesia_config_test, dump_log_time_threshold, "Uses disk"}}.
+{skip, {mnesia_config_test, dump_log_write_threshold, "Uses disk"}}.
+{skip, {mnesia_config_test, dump_log_update_in_place, "Uses disk"}}.
+{skip, {mnesia_config_test, embedded_mnemosyne, "Uses Mnemosyne"}}.
+%{mnesia_config_test, event_module} 'IMPL'
+{skip, {mnesia_config_test, ignore_fallback_at_startup, "Not Yet impl"}}.
+%{mnesia_config_test, inconsistent_database} 'IMPL'
+{skip, {mnesia_config_test, max_wait_for_decision, "Not Yet impl"}}.
+{skip, {mnesia_config_test, start_one_disc_full_then_one_disc_less, "Uses disc"}}.
+{skip, {mnesia_config_test, start_first_one_disc_less_then_one_disc_full, "Uses disc"}}.
+%%{skip, {mnesia_config_test, start_first_one_disc_less_then_two_more_disc_less, "Uses disc"}}.
+{skip, {mnesia_config_test, schema_location_and_extra_db_nodes_combinations, "Uses disk"}}.
+{skip, {mnesia_config_test, table_load_to_disc_less_nodes, "Uses disc"}}.
+{skip, {mnesia_config_test, schema_merge, "Uses Disc"}}.
+%{mnesia_config_test, unknown_config} 'IMPL'
+%{mnesia_registry_test, good_dump} 'IMPL'
+%{mnesia_registry_test, bad_dump} 'IMPL'
+
+%{mnesia_atomicity_test, explicit_abort_in_middle_of_trans} 'IMPL'
+%{mnesia_atomicity_test, runtime_error_in_middle_of_trans} 'IMPL'
+%{mnesia_atomicity_test, kill_self_in_middle_of_trans} 'IMPL'
+%{mnesia_atomicity_test, throw_in_middle_of_trans} 'IMPL'
+%{mnesia_atomicity_test, mnesia_down_during_infinite_trans} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_sw_rt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_sw_wt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wr_r} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_sw_sw} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_sw_w} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_sw_wr} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wr_wt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wr_sw} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wr_w} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_r_sw} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_r_w} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_r_wt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_rt_sw} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_rt_w} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_rt_wt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wt_r} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wt_w} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wt_rt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wt_wt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wt_wr} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_wt_sw} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_w_wr} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_w_sw} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_w_r} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_w_w} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_w_rt} 'IMPL'
+%{mnesia_atomicity_test, lock_waiter_w_wt} 'IMPL'
+%{mnesia_atomicity_test, restart_r_one} 'IMPL'
+%{mnesia_atomicity_test, restart_w_one} 'IMPL'
+%{mnesia_atomicity_test, restart_rt_one} 'IMPL'
+%{mnesia_atomicity_test, restart_wt_one} 'IMPL'
+%{mnesia_atomicity_test, restart_wr_one} 'IMPL'
+%{mnesia_atomicity_test, restart_sw_one} 'IMPL'
+%{mnesia_atomicity_test, restart_r_two} 'IMPL'
+%{mnesia_atomicity_test, restart_w_two} 'IMPL'
+%{mnesia_atomicity_test, restart_rt_two} 'IMPL'
+%{mnesia_atomicity_test, restart_wt_two} 'IMPL'
+%{mnesia_atomicity_test, restart_wr_two} 'IMPL'
+%{mnesia_atomicity_test, restart_sw_two} 'IMPL'
+
+%{mnesia_isolation_test, no_conflict} 'IMPL'
+%{mnesia_isolation_test, simple_queue_conflict} 'IMPL'
+%{mnesia_isolation_test, advanced_queue_conflict} 'IMPL'
+%{mnesia_isolation_test, simple_deadlock_conflict} 'IMPL'
+%{mnesia_isolation_test, advanced_deadlock_conflict} 'IMPL'
+%{mnesia_isolation_test, lock_burst} 'IMPL'
+%{mnesia_isolation_test, basic_sticky_functionality} 'IMPL'
+%{mnesia_isolation_test, create_table} 'IMPL'
+%{mnesia_isolation_test, delete_table} 'IMPL'
+%{mnesia_isolation_test, move_table_copy} 'IMPL'
+%{mnesia_isolation_test, add_table_index} 'IMPL'
+%{mnesia_isolation_test, del_table_index} 'IMPL'
+%{mnesia_isolation_test, transform_table} 'IMPL'
+%{mnesia_isolation_test, snmp_open_table} 'IMPL'
+%{mnesia_isolation_test, snmp_close_table} 'IMPL'
+{skip, {mnesia_isolation_test, change_table_copy_type, "Uses disk"}}.
+%{mnesia_isolation_test, change_table_access} 'IMPL'
+%{mnesia_isolation_test, add_table_copy} 'IMPL'
+%{mnesia_isolation_test, del_table_copy} 'IMPL'
+{skip, {mnesia_isolation_test, dump_tables, "Uses disk"}}.
+{skip, {mnesia_isolation_test, extra_admin_tests, "Uses disk"}}.
+%{mnesia_isolation_test, del_table_copy_1} 'IMPL'
+%{mnesia_isolation_test, del_table_copy_2} 'IMPL'
+%{mnesia_isolation_test, del_table_copy_3} 'IMPL'
+%{mnesia_isolation_test, add_table_copy_1} 'IMPL'
+%{mnesia_isolation_test, add_table_copy_2} 'IMPL'
+%{mnesia_isolation_test, add_table_copy_3} 'IMPL'
+%{mnesia_isolation_test, add_table_copy_4} 'IMPL'
+%{mnesia_isolation_test, move_table_copy_1} 'IMPL'
+%{mnesia_isolation_test, move_table_copy_2} 'IMPL'
+%{mnesia_isolation_test, move_table_copy_3} 'IMPL'
+%{mnesia_isolation_test, move_table_copy_4} 'IMPL'
+%{mnesia_isolation_test, dirty_updates_visible_direct} 'IMPL'
+%{mnesia_isolation_test, dirty_reads_regardless_of_trans} 'IMPL'
+%{mnesia_isolation_test, trans_update_invisibible_outside_trans} 'IMPL'
+%{mnesia_isolation_test, trans_update_visible_inside_trans} 'IMPL'
+%{mnesia_isolation_test, write_shadows} 'IMPL'
+%{mnesia_isolation_test, delete_shadows} 'IMPL'
+%{mnesia_isolation_test, write_delete_shadows_bag} 'IMPL'
+
+{skip, {mnesia_durability_test, all, "Uses disk "}}.
+%{mnesia_durability_test, load_local_contents_directly} 'IMPL'
+%{mnesia_durability_test, load_directly_when_all_are_ram_copiesA} 'IMPL'
+%{mnesia_durability_test, load_directly_when_all_are_ram_copiesB} 'IMPL'
+%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes1, "Uses disk schema"}}.
+%{skip, {mnesia_durability_test, late_load_when_all_are_ram_copies_on_ram_nodes2, "Uses disk schema"}}.
+%{skip, {mnesia_durability_test, load_when_last_replica_becomes_available, "Uses disk"}}.
+%{skip, {mnesia_durability_test, load_when_we_have_down_from_all_other_replica_nodes, "Uses disk"}}.
+%{skip, {mnesia_durability_test, late_load_transforms_into_disc_load, "Uses disc"}}.
+%{mnesia_durability_test, late_load_leads_to_hanging} 'IMPL'
+%{mnesia_durability_test, force_load_when_nobody_intents_to_load} 'IMPL'
+%{mnesia_durability_test, force_load_when_someone_has_decided_to_load} 'IMPL'
+%{mnesia_durability_test, force_load_when_someone_else_already_has_loaded} 'IMPL'
+%{mnesia_durability_test, force_load_when_we_has_loaded} 'IMPL'
+%{mnesia_durability_test, force_load_on_a_non_local_table} 'IMPL'
+%{mnesia_durability_test, force_load_when_the_table_does_not_exist} 'IMPL'
+%{mnesia_durability_test, master_nodes} 'IMPL'
+%{mnesia_durability_test, master_on_non_local_tables} 'IMPL'
+%{mnesia_durability_test, remote_force_load_with_local_master_node} 'IMPL'
+%{mnesia_durability_test, dump_ram_copies} 'IMPL'
+%{skip, {mnesia_durability_test, dump_disc_copies, "Uses disc"}}.
+%{skip, {mnesia_durability_test, dump_disc_only, "Uses disc"}}.
+%{skip, {mnesia_durability_test, durability_of_disc_copies, "Uses disc"}}.
+%{skip, {mnesia_durability_test, durability_of_disc_only_copies, "Uses disc"}}.
+
+{skip, {mnesia_recovery_test, mnesia_down, "Uses Disk"}}.
+%{mnesia_recovery_test, no_master_2} 'IMPL'
+%{mnesia_recovery_test, no_master_3} 'IMPL'
+%{mnesia_recovery_test, one_master_2} 'IMPL'
+%{mnesia_recovery_test, one_master_3} 'IMPL'
+%{mnesia_recovery_test, two_master_2} 'IMPL'
+%{mnesia_recovery_test, two_master_3} 'IMPL'
+%{mnesia_recovery_test, all_master_2} 'IMPL'
+%{mnesia_recovery_test, all_master_3} 'IMPL'
+{skip, {mnesia_recovery_test, mnesia_down_during_startup_disk_ram, "Uses disk"}}.
+%{mnesia_recovery_test, mnesia_down_during_startup_init_ram} 'IMPL'
+{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc, "Uses disc"}}.
+{skip, {mnesia_recovery_test, mnesia_down_during_startup_init_disc_only, "Uses disc"}}.
+%{mnesia_recovery_test, mnesia_down_during_startup_tm_ram} 'IMPL'
+{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc, "Uses disc"}}.
+{skip, {mnesia_recovery_test, mnesia_down_during_startup_tm_disc_only, "Uses disc"}}.
+%{mnesia_recovery_test, explicit_stop_during_snmp} 'IMPL'
+
+{skip, {mnesia_recovery_test, schema_trans, "Uses Disk, needs disk log"}}.
+{skip, {mnesia_recovery_test, async_dirty, "Uses disc"}}.
+{skip, {mnesia_recovery_test, sync_dirty, "Uses disc"}}.
+{skip, {mnesia_recovery_test, sym_trans, "Uses disc"}}.
+{skip, {mnesia_recovery_test, asym_trans, "Uses disc"}}.
+
+{skip, {mnesia_recovery_test, after_full_disc_partition, "Not Yet impl"}}.
+{skip, {mnesia_recovery_test, after_corrupt_files, "Uses disk"}}.
+
+%{mnesia_evil_coverage_test, subscriptions} 'IMPL'
+%{mnesia_evil_coverage_test, nested_trans_both_ok} 'IMPL'
+%{mnesia_evil_coverage_test, nested_trans_child_dies} 'IMPL'
+%{mnesia_evil_coverage_test, nested_trans_parent_dies} 'IMPL'
+%{mnesia_evil_coverage_test, nested_trans_both_dies} 'IMPL'
+%{mnesia_evil_coverage_test, mix_of_trans_sync_dirty} 'IMPL'
+%{mnesia_evil_coverage_test, mix_of_trans_async_dirty} 'IMPL'
+%{mnesia_evil_coverage_test, mix_of_trans_ets} 'IMPL'
+
+{skip, {mnesia_recovery_test, disc_less, "Uses disc (on the other nodes)"}}.
+{skip, {mnesia_recovery_test, system_upgrade, "Not Yet impl"}}.
+%{mnesia_consistency_test, consistency_after_restart_1_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_restart_1_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_restart_1_disc_only, "Uses disc"}}.
+%{mnesia_consistency_test, consistency_after_restart_2_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_restart_2_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_restart_2_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_dump_tables_1_ram, "Uses disk"}}.
+{skip, {mnesia_consistency_test, consistency_after_dump_tables_2_ram, "Uses disk"}}.
+%{mnesia_consistency_test, consistency_after_add_replica_2_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_add_replica_2_disc_only, "Uses disc"}}.
+%{mnesia_consistency_test, consistency_after_add_replica_3_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_add_replica_3_disc_only, "Uses disc"}}.
+%{mnesia_consistency_test, consistency_after_del_replica_2_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_del_replica_2_disc_only, "Uses disc"}}.
+%{mnesia_consistency_test, consistency_after_del_replica_3_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_del_replica_3_disc_only, "Uses disc"}}.
+%{mnesia_consistency_test, consistency_after_move_replica_2_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_move_replica_2_disc_only, "Uses disc"}}.
+%{mnesia_consistency_test, consistency_after_move_replica_3_ram} 'IMPL'
+{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_move_replica_3_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_transform_table, "Not yet implemented"}}.
+{skip, {mnesia_consistency_test, consistency_after_change_table_copy_type, "Not yet implemented"}}.
+{skip, {mnesia_consistency_test, consistency_after_fallback_2_ram, "Uses disk"}}.
+{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_fallback_2_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_fallback_3_ram, "Uses disk"}}.
+{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_fallback_3_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_restore_clear_ram, "Uses disk"}}.
+{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_restore_clear_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_restore_recreate_ram, "Uses disk"}}.
+{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_restore_recreate_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, consistency_after_rename_of_node, "Not yet implemented"}}.
+{skip, {mnesia_consistency_test, updates_during_checkpoint_activation, "Uses disk"}}.
+%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc, "Uses disc"}}.
+%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_2_disc_only, "Uses disc"}}.
+%%{mnesia_consistency_test, updates_during_checkpoint_activation_3_ram} 'IMPL'
+%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc, "Uses disc"}}.
+%{skip, {mnesia_consistency_test, updates_during_checkpoint_activation_3_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration, "Uses disk"}}.
+%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc, "Uses disc"}}.
+%{skip, {mnesia_consistency_test, updates_during_checkpoint_iteration_2_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_ram, "Uses disk"}}.
+{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, load_table_with_activated_checkpoint_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_ram, "Uses disk"}}.
+{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc, "Uses disc"}}.
+{skip, {mnesia_consistency_test, add_table_copy_to_table_with_activated_checkpoint_disc_only, "Uses disc"}}.
+{skip, {mnesia_consistency_test, inst_fallback_process_dies, "Uses disk"}}.
+{skip, {mnesia_consistency_test, fatal_when_inconsistency, "Uses disk"}}.
+{skip, {mnesia_consistency_test, after_delete, "Uses disk"}}.
+{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_switch, "Uses disk"}}.
+{skip, {mnesia_consistency_test, mnesia_down_during_backup_causes_abort, "Uses disk"}}.
+%{mnesia_consistency_test, cause_switch_after} 'IMPL'
+%{mnesia_consistency_test, cause_abort_before} 'IMPL'
+%{mnesia_consistency_test, cause_abort_after} 'IMPL'
+%{mnesia_consistency_test, change_schema_before} 'IMPL'
+%{mnesia_consistency_test, change_schema_after} 'IMPL'
+
diff --git a/lib/mnesia/test/mnesia_SUITE.erl b/lib/mnesia/test/mnesia_SUITE.erl
new file mode 100644
index 0000000000..b28deaf330
--- /dev/null
+++ b/lib/mnesia/test/mnesia_SUITE.erl
@@ -0,0 +1,203 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(mnesia_SUITE).
+-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 Mnesia really is a distributed real-time DBMS",
+ "This is the test suite of the Mnesia DBMS. The test suite",
+ "covers many aspects of usage and is indended to be developed",
+ "incrementally. The test suite is divided into a hierarchy of test",
+ "suites where the leafs actually implements the test cases.",
+ "The intention of each test case and sub test suite can be",
+ "read in comments where they are implemented or in worst cases",
+ "from their long mnemonic names. ",
+ "",
+ "The most simple test case of them all is called 'silly'",
+ "and is useful to run now and then, e.g. when some new fatal",
+ "bug has been introduced. It may be run even if Mnesia is in",
+ "such a bad shape that the test machinery cannot be used.",
+ "NB! Invoke the function directly with mnesia_SUITE:silly()",
+ "and do not involve the normal test machinery."];
+all(suite) ->
+ [
+ light,
+ medium,
+ heavy,
+ clean_up_suite
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+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,
+ {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}].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+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"];
+clean_up_suite(suite) ->
+ [];
+clean_up_suite(Config) when is_list(Config)->
+ mnesia:kill(),
+ Slaves = mnesia_test_lib:lookup_config(nodenames, Config),
+ Nodes = lists:delete(node(), Slaves),
+ rpc:multicall(Nodes, erlang, halt, []),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+otp_r4b(doc) ->
+ ["This test suite is an extract of the grand Mnesia suite",
+ "it contains OTP R4B specific test cases"];
+otp_r4b(suite) ->
+ [
+ {mnesia_config_test, access_module},
+ {mnesia_config_test, dump_log_load_regulation},
+ {mnesia_config_test, embedded_mnemosyne},
+ {mnesia_config_test, ignore_fallback_at_startup},
+ {mnesia_config_test, max_wait_for_decision},
+ {mnesia_consistency_test, consistency_after_restore},
+ {mnesia_evil_backup, restore},
+ {mnesia_evil_coverage_test, offline_set_master_nodes},
+ {mnesia_evil_coverage_test, record_name},
+ {mnesia_evil_coverage_test, user_properties},
+ {mnesia_registry_test, all},
+ otp_2363
+ ].
+
+otp_2363(doc) ->
+ ["Index on disc only tables"];
+otp_2363(suite) ->
+ [
+ {mnesia_dirty_access_test, dirty_index_match_object_disc_only},
+ {mnesia_dirty_access_test,dirty_index_read_disc_only},
+ {mnesia_dirty_access_test,dirty_index_update_bag_disc_only},
+ {mnesia_dirty_access_test,dirty_index_update_set_disc_only},
+ {mnesia_evil_coverage_test, create_live_table_index_disc_only}
+ ].
+
+
+
diff --git a/lib/mnesia/test/mnesia_atomicity_test.erl b/lib/mnesia/test/mnesia_atomicity_test.erl
new file mode 100644
index 0000000000..645c203a91
--- /dev/null
+++ b/lib/mnesia/test/mnesia_atomicity_test.erl
@@ -0,0 +1,839 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(mnesia_atomicity_test).
+-author('[email protected]').
+-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 atomicity of transactions",
+ "Verify that transactions are atomic, i.e. either all operations",
+ "in a transaction will be performed or none of them. It must be",
+ "assured that no partitially completed operations leaves any",
+ "effects in the database."];
+all(suite) ->
+ [
+ explicit_abort_in_middle_of_trans,
+ runtime_error_in_middle_of_trans,
+ kill_self_in_middle_of_trans,
+ throw_in_middle_of_trans,
+ mnesia_down_in_middle_of_trans
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+explicit_abort_in_middle_of_trans(suite) -> [];
+explicit_abort_in_middle_of_trans(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = explicit_abort_in_middle_of_trans,
+
+ Rec1A = {Tab, 1, a},
+ Rec1B = {Tab, 1, b},
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]}])),
+ %% Start a transaction on one node
+ {success, [A]} = ?start_activities([Node1]),
+
+ %% store an object in the Tab - first tranaction
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1A) % returns ok when successful
+ end,
+ ?match_receive({A, ok}),
+ A ! end_trans,
+ ?match_receive({A, {atomic, end_trans}}),
+
+ %% second transaction: store some new objects and abort before the
+ %% transaction is finished -> the new changes should be invisable
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1B),
+ exit(abort_by_purpose) %does that stop the process A ???
+ end,
+ ?match_receive({A, {aborted, abort_by_purpose}}),
+
+
+ %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT
+
+
+ %% Start a second transactionprocess, after the first failed
+ {success, [B]} = ?start_activities([Node1]),
+
+ %% check, whether the interupted transaction had no influence on the db
+ ?start_transactions([B]),
+ B ! fun() ->
+ ?match([Rec1A], mnesia:read({Tab, 1})),
+ ok
+ end,
+ ?match_receive({B, ok}),
+ B ! end_trans,
+ ?match_receive({B, {atomic, end_trans}}),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+runtime_error_in_middle_of_trans(suite) -> [];
+runtime_error_in_middle_of_trans(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = runtime_error_in_middle_of_trans,
+
+ Rec1A = {Tab, 1, a},
+ Rec1B = {Tab, 1, b},
+ Rec1C = {Tab, 1, c},
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]}])),
+ %% Start a transaction on one node
+ {success, [A]} = ?start_activities([Node1]),
+
+ %% store an object in the Tab - first tranaction
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1A) % returns ok when successful
+ end,
+ ?match_receive({A, ok}),
+ A ! end_trans,
+ ?match_receive({A, {atomic, end_trans}}),
+
+ %% second transaction: store some new objects and abort before the
+ %% transaction is finished -> the new changes should be invisable
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1B),
+ erlang:error(foo), % that should provoke a runtime error
+ mnesia:write(Rec1C)
+ end,
+ ?match_receive({A, {aborted, _Reason}}),
+
+ %?match_receive({A, {'EXIT', Msg1}), % A died and sends EXIT
+
+
+ %% Start a second transactionprocess, after the first failed
+ {success, [B]} = ?start_activities([Node1]),
+
+ %% check, whether the interupted transaction had no influence on the db
+ ?start_transactions([B]),
+ B ! fun() ->
+ ?match([Rec1A], mnesia:read({Tab, 1})),
+ ok
+ end,
+ ?match_receive({B, ok}),
+ B ! end_trans,
+ ?match_receive({B, {atomic, end_trans}}),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+kill_self_in_middle_of_trans(suite) -> [];
+kill_self_in_middle_of_trans(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = kill_self_in_middle_of_trans,
+
+ Rec1A = {Tab, 1, a},
+ Rec1B = {Tab, 1, b},
+ Rec1C = {Tab, 1, c},
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]}])),
+ %% Start a transaction on one node
+ {success, [A]} = ?start_activities([Node1]),
+
+ %% store an object in the Tab - first tranaction
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1A) % returns ok when successful
+ end,
+ ?match_receive({A, ok}),
+ A ! end_trans,
+ ?match_receive({A, {atomic, end_trans}}),
+
+ %% second transaction: store some new objects and abort before the
+ %% transaction is finished -> the new changes should be invisable
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1B),
+ exit(self(), kill), % that should kill the process himself
+ % - poor guy !
+ mnesia:write(Rec1C)
+ end,
+ %%
+ %% exit(.., kill) : the transaction can't trap this error - thus no
+ %% proper result can be send by the test server
+
+ % ?match_receive({A, {aborted, Reason}}),
+
+ ?match_receive({'EXIT', _Pid, killed}), % A is killed and sends EXIT
+
+ %% Start a second transactionprocess, after the first failed
+ {success, [B]} = ?start_activities([Node1]),
+
+ %% check, whether the interupted transaction had no influence on the db
+ ?start_transactions([B]),
+ B ! fun() ->
+ ?match([Rec1A], mnesia:read({Tab, 1})),
+ ok
+ end,
+ ?match_receive({B, ok}),
+ B ! end_trans,
+ ?match_receive({B, {atomic, end_trans}}),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+throw_in_middle_of_trans(suite) -> [];
+throw_in_middle_of_trans(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = throw_in_middle_of_trans,
+
+ Rec1A = {Tab, 1, a},
+ Rec1B = {Tab, 1, b},
+ Rec1C = {Tab, 1, c},
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]}])),
+ %% Start a transaction on one node
+ {success, [A]} = ?start_activities([Node1]),
+
+ %% store an object in the Tab - first tranaction
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1A) % returns ok when successful
+ end,
+ ?match_receive({A, ok}),
+ A ! end_trans,
+ ?match_receive({A, {atomic, end_trans}}),
+
+ %% second transaction: store some new objects and abort before the
+ %% transaction is finished -> the new changes should be invisable
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write(Rec1B),
+ throw(exit_transactian_by_a_throw),
+ mnesia:write(Rec1C)
+ end,
+ ?match_receive({A, {aborted, {throw, exit_transactian_by_a_throw}}}),
+ % A ! end_trans, % is A still alive ?
+ % ?match_receive({A, {atomic, end_trans}}), % {'EXIT', Pid, normal}
+
+ %?match_receive({A, {'EXIT', Pid, normal}}), % A died and sends EXIT
+
+ %% Start a second transactionprocess, after the first failed
+ {success, [B]} = ?start_activities([Node1]),
+
+ %% check, whether the interupted transaction had no influence on the db
+ ?start_transactions([B]),
+ B ! fun() ->
+ ?match([Rec1A], mnesia:read({Tab, 1})),
+ ok
+ end,
+ ?match_receive({B, ok}),
+ B ! end_trans,
+ ?match_receive({B, {atomic, end_trans}}),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mnesia_down_in_middle_of_trans(suite) ->
+ [
+ mnesia_down_during_infinite_trans,
+ lock_waiter,
+ restart_check
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mnesia_down_during_infinite_trans(suite) -> [];
+mnesia_down_during_infinite_trans(Config) when is_list(Config) ->
+ [Node1, Node2] = ?acquire_nodes(2, Config),
+ Tab = mnesia_down_during_infinite_trans,
+
+ ?match({atomic, ok},
+ mnesia:create_table([{name, Tab}, {ram_copies, [Node1, Node2]}])),
+ %% Start a transaction on one node
+ {success, [A2, A1]} = ?start_activities([Node2, Node1]),
+ %% Start order of the transactions are important
+ %% We also needs to sync the tid counter
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 1, test_ok}) end)),
+ mnesia_test_lib:start_sync_transactions([A2, A1]),
+
+ %% Obtain a write lock and wait forever
+ RecA = {Tab, 1, test_not_ok},
+ A1 ! fun() -> mnesia:write(RecA) end,
+ ?match_receive({A1, ok}),
+
+ A1 ! fun() -> process_flag(trap_exit, true), timer:sleep(infinity) end,
+ ?match_receive(timeout),
+
+ %% Try to get read lock, but gets queued
+ A2 ! fun() -> mnesia:read({Tab, 1}) end,
+ ?match_receive(timeout),
+
+ %% Kill Mnesia on other node
+ mnesia_test_lib:kill_mnesia([Node1]),
+
+ %% Second transaction gets the read lock
+ ?match_receive({A2, [{Tab, 1, test_ok}]}),
+ exit(A1, kill), % Needed since we trap exit
+
+ ?verify_mnesia([Node2], [Node1]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+lock_waiter(doc) ->
+ ["The purpose of this test case is to test the following situation:",
+ "process B locks an object, process A accesses that object as",
+ "well, but A has to wait for the lock to be released. Then",
+ "mnesia of B goes down. Question: will A get the lock ?",
+ "important: the transaction of A is the oldest one !!! (= a little tricky)",
+ "",
+ "several different access operations shall be tested",
+ "rt = read_lock_table, wt = write_lock_table, r = read,",
+ "sw = s_write, w = write, wr = wread"];
+lock_waiter(suite) ->
+ [
+ lock_waiter_sw_r,
+ lock_waiter_sw_rt,
+ lock_waiter_sw_wt,
+ lock_waiter_wr_r,
+ lock_waiter_srw_r,
+ lock_waiter_sw_sw,
+ lock_waiter_sw_w,
+ lock_waiter_sw_wr,
+ lock_waiter_sw_srw,
+ lock_waiter_wr_wt,
+ lock_waiter_srw_wt,
+ lock_waiter_wr_sw,
+ lock_waiter_srw_sw,
+ lock_waiter_wr_w,
+ lock_waiter_srw_w,
+ lock_waiter_r_sw,
+ lock_waiter_r_w,
+ lock_waiter_r_wt,
+ lock_waiter_rt_sw,
+ lock_waiter_rt_w,
+ lock_waiter_rt_wt,
+ lock_waiter_wr_wr,
+ lock_waiter_srw_srw,
+ lock_waiter_wt_r,
+ lock_waiter_wt_w,
+ lock_waiter_wt_rt,
+ lock_waiter_wt_wt,
+ lock_waiter_wt_wr,
+ lock_waiter_wt_srw,
+ lock_waiter_wt_sw,
+ lock_waiter_w_wr,
+ lock_waiter_w_srw,
+ lock_waiter_w_sw,
+ lock_waiter_w_r,
+ lock_waiter_w_w,
+ lock_waiter_w_rt,
+ lock_waiter_w_wt
+ ].
+
+lock_waiter_sw_r(suite) -> [];
+lock_waiter_sw_r(Config) when is_list(Config) ->
+ start_lock_waiter(sw, r, Config).
+
+lock_waiter_sw_rt(suite) -> [];
+lock_waiter_sw_rt(Config) when is_list(Config) ->
+ start_lock_waiter(sw, rt, Config).
+
+lock_waiter_sw_wt(suite) -> [];
+lock_waiter_sw_wt(Config) when is_list(Config) ->
+ start_lock_waiter(sw, wt,Config).
+
+lock_waiter_wr_r(suite) -> [];
+lock_waiter_wr_r(Config) when is_list(Config) ->
+ start_lock_waiter(wr, r, Config).
+
+lock_waiter_srw_r(suite) -> [];
+lock_waiter_srw_r(Config) when is_list(Config) ->
+ start_lock_waiter(srw, r, Config).
+
+lock_waiter_sw_sw(suite) -> [];
+lock_waiter_sw_sw(Config) when is_list(Config) ->
+ start_lock_waiter(sw, sw,Config).
+
+lock_waiter_srw_srw(suite) -> [];
+lock_waiter_srw_srw(Config) when is_list(Config) ->
+ start_lock_waiter(srw, srw,Config).
+
+lock_waiter_wr_wr(suite) -> [];
+lock_waiter_wr_wr(Config) when is_list(Config) ->
+ start_lock_waiter(wr, wr,Config).
+
+lock_waiter_sw_w(suite) -> [];
+lock_waiter_sw_w(Config) when is_list(Config) ->
+ start_lock_waiter(sw, w,Config).
+
+lock_waiter_sw_wr(suite) -> [];
+lock_waiter_sw_wr(Config) when is_list(Config) ->
+ start_lock_waiter(sw, wr,Config).
+
+lock_waiter_sw_srw(suite) -> [];
+lock_waiter_sw_srw(Config) when is_list(Config) ->
+ start_lock_waiter(sw, srw,Config).
+
+lock_waiter_wr_wt(suite) -> [];
+lock_waiter_wr_wt(Config) when is_list(Config) ->
+ start_lock_waiter(wr, wt,Config).
+
+lock_waiter_srw_wt(suite) -> [];
+lock_waiter_srw_wt(Config) when is_list(Config) ->
+ start_lock_waiter(srw, wt,Config).
+
+lock_waiter_wr_sw(suite) -> [];
+lock_waiter_wr_sw(Config) when is_list(Config) ->
+ start_lock_waiter(wr, sw,Config).
+
+lock_waiter_srw_sw(suite) -> [];
+lock_waiter_srw_sw(Config) when is_list(Config) ->
+ start_lock_waiter(srw, sw,Config).
+
+lock_waiter_wr_w(suite) -> [];
+lock_waiter_wr_w(Config) when is_list(Config) ->
+ start_lock_waiter(wr, w,Config).
+
+lock_waiter_srw_w(suite) -> [];
+lock_waiter_srw_w(Config) when is_list(Config) ->
+ start_lock_waiter(srw, w,Config).
+
+lock_waiter_r_sw(suite) -> [];
+lock_waiter_r_sw(Config) when is_list(Config) ->
+ start_lock_waiter(r, sw,Config).
+
+lock_waiter_r_w(suite) -> [];
+lock_waiter_r_w(Config) when is_list(Config) ->
+ start_lock_waiter(r, w,Config).
+
+lock_waiter_r_wt(suite) -> [];
+lock_waiter_r_wt(Config) when is_list(Config) ->
+ start_lock_waiter(r, wt,Config).
+
+lock_waiter_rt_sw(suite) -> [];
+lock_waiter_rt_sw(Config) when is_list(Config) ->
+ start_lock_waiter(rt, sw,Config).
+
+lock_waiter_rt_w(suite) -> [];
+lock_waiter_rt_w(Config) when is_list(Config) ->
+ start_lock_waiter(rt, w,Config).
+
+lock_waiter_rt_wt(suite) -> [];
+lock_waiter_rt_wt(Config) when is_list(Config) ->
+ start_lock_waiter(rt, wt,Config).
+
+lock_waiter_wt_r(suite) -> [];
+lock_waiter_wt_r(Config) when is_list(Config) ->
+ start_lock_waiter(wt, r,Config).
+
+lock_waiter_wt_w(suite) -> [];
+lock_waiter_wt_w(Config) when is_list(Config) ->
+ start_lock_waiter(wt, w,Config).
+
+lock_waiter_wt_rt(suite) -> [];
+lock_waiter_wt_rt(Config) when is_list(Config) ->
+ start_lock_waiter(wt, rt,Config).
+
+lock_waiter_wt_wt(suite) -> [];
+lock_waiter_wt_wt(Config) when is_list(Config) ->
+ start_lock_waiter(wt, wt,Config).
+
+lock_waiter_wt_wr(suite) -> [];
+lock_waiter_wt_wr(Config) when is_list(Config) ->
+ start_lock_waiter(wt, wr,Config).
+
+lock_waiter_wt_srw(suite) -> [];
+lock_waiter_wt_srw(Config) when is_list(Config) ->
+ start_lock_waiter(wt, srw,Config).
+
+lock_waiter_wt_sw(suite) -> [];
+lock_waiter_wt_sw(Config) when is_list(Config) ->
+ start_lock_waiter(wt, sw,Config).
+
+lock_waiter_w_wr(suite) -> [];
+lock_waiter_w_wr(Config) when is_list(Config) ->
+ start_lock_waiter(w, wr, Config).
+
+lock_waiter_w_srw(suite) -> [];
+lock_waiter_w_srw(Config) when is_list(Config) ->
+ start_lock_waiter(w, srw, Config).
+
+lock_waiter_w_sw(suite) -> [];
+lock_waiter_w_sw(Config) when is_list(Config) ->
+ start_lock_waiter(w, sw, Config).
+
+lock_waiter_w_r(suite) -> [];
+lock_waiter_w_r(Config) when is_list(Config) ->
+ start_lock_waiter(w, r, Config).
+
+lock_waiter_w_w(suite) -> [];
+lock_waiter_w_w(Config) when is_list(Config) ->
+ start_lock_waiter(w, w, Config).
+
+lock_waiter_w_rt(suite) -> [];
+lock_waiter_w_rt(Config) when is_list(Config) ->
+ start_lock_waiter(w, rt, Config).
+
+lock_waiter_w_wt(suite) -> [];
+lock_waiter_w_wt(Config) when is_list(Config) ->
+ start_lock_waiter(w, wt, Config).
+
+start_lock_waiter(BlockOpA, BlockOpB, Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+
+ TabName = mk_tab_name(lock_waiter_),
+ ?match({atomic, ok}, mnesia:create_table(TabName,
+ [{ram_copies, [N1, N2]}])),
+
+ %% initialize the table with object {1, c} - when there
+ %% is a read transaction, the read will find that value
+ ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)),
+ rpc:call(N2, ?MODULE, sync_tid_release, []),
+
+ Tester = self(),
+ Fun_A =fun() ->
+ NewCounter = incr_restart_counter(),
+ if
+ NewCounter == 1 ->
+ Tester ! go_ahead_test,
+ receive go_ahead -> ok end;
+ true -> ok
+ end,
+ lock_waiter_fun(BlockOpA, TabName, a),
+ NewCounter
+ end,
+
+ %% it's not possible to just spawn the transaction, because
+ %% the result shall be evaluated
+ A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]),
+
+ ?match(ok, receive go_ahead_test -> ok after 10000 -> timeout end),
+
+ mnesia_test_lib:sync_trans_tid_serial([N1, N2]),
+
+ Fun_B = fun() ->
+ lock_waiter_fun(BlockOpB, TabName, b),
+ A ! go_ahead,
+ wait(infinity)
+ end,
+
+ B = spawn_link(N2, mnesia, transaction, [Fun_B, 100]),
+
+ io:format("waiting for A (~p on ~p) to be in the queue ~n", [A, [N1, N2]]),
+ wait_for_a(A, [N1, N2]),
+
+ io:format("Queus ~p~n",
+ [[{N,rpc:call(N, mnesia, system_info, [lock_queue])} || N <- Nodes]]),
+
+ KillNode = node(B),
+ io:format("A was in the queue, time to kill Mnesia on B's node (~p on ~p)~n",
+ [B, KillNode]),
+
+ mnesia_test_lib:kill_mnesia([KillNode]), % kill mnesia of fun B
+
+ %% Read Ops does not need to be restarted
+ ExpectedCounter =
+ if
+ BlockOpA == sw, BlockOpB == w -> 1;
+ BlockOpA == sw, BlockOpB == wt -> 1;
+ BlockOpA == sw, BlockOpB == wr -> 1;
+ BlockOpA == srw, BlockOpB == w -> 1;
+ BlockOpA == srw, BlockOpB == wt -> 1;
+ BlockOpA == srw, BlockOpB == wr -> 1;
+ BlockOpA == r, BlockOpB /= sw -> 1;
+ BlockOpA == rt, BlockOpB /= sw -> 1;
+ true -> 2
+ end,
+ ?match_multi_receive([{'EXIT', A, {atomic, ExpectedCounter}},
+ {'EXIT', B, killed}]),
+
+ %% the expected result depends on the transaction of
+ %% fun A - when that doesn't change the object in the
+ %% table (e.g. it is a read) then the predefined
+ %% value {Tabname, 1, c} is expected to be the result here
+ ExpectedResult =
+ case BlockOpA of
+ w -> {TabName, 1, a};
+ sw ->{TabName, 1, a};
+ _all_other -> {TabName, 1, c}
+ end,
+
+ ?match({atomic, [ExpectedResult]},
+ mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end, 100)),
+ ?verify_mnesia([N1], [N2]).
+
+mk_tab_name(Prefix) ->
+ {Mega, Sec, Micro} = erlang:now(),
+ list_to_atom(lists:concat([Prefix , Mega, '_', Sec, '_', Micro])).
+
+lock_waiter_fun(Op, TabName, Val) ->
+ case Op of
+ rt -> mnesia:read_lock_table(TabName);
+ wt -> mnesia:write_lock_table(TabName);
+ r -> mnesia:read({TabName, 1});
+ w -> mnesia:write({TabName, 1, Val});
+ wr -> mnesia:wread({TabName, 1});
+ srw -> mnesia:read(TabName, 1, sticky_write);
+ sw -> mnesia:s_write({TabName, 1, Val})
+ end.
+
+wait_for_a(Pid, Nodes) ->
+ wait_for_a(Pid, Nodes, 5).
+
+wait_for_a(_P, _N, 0) ->
+ ?error("Timeout while waiting for lock on a~n", []);
+
+wait_for_a(Pid, Nodes, Count) ->
+ %% io:format("WAIT_FOR_A ~p ON ~w ~n", [Pid, Nodes]),
+ List = [rpc:call(N, mnesia, system_info, [lock_queue]) || N <- Nodes],
+ Q = lists:append(List),
+ check_q(Pid, Q, Nodes, Count).
+
+check_q(Pid, [{{_Oid,_Tid}, _Op, Pid, _WFT} | _Tail], _N, _Count) ->
+ ok;
+check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) ->
+ ok;
+check_q(Pid, [_ | Tail], N, Count) ->
+ check_q(Pid, Tail, N, Count);
+check_q(Pid, [], N, Count) ->
+ timer:sleep(500),
+ wait_for_a(Pid, N, Count - 1).
+
+perform_restarted_transaction (Fun_Trans) ->
+ %% the result of the transaction shall be:
+ %% - undefined (if the transaction was never executed)
+ %% - Times ( number of times that the transaction has been executed)
+
+ Result = mnesia:transaction(Fun_Trans, 100),
+ exit(Result).
+
+%% Returns new val
+incr_restart_counter() ->
+ NewCount =
+ case get(count_restart_of_transaction) of
+ undefined -> 1;
+ OldCount -> OldCount + 1
+ end,
+ put(count_restart_of_transaction, NewCount),
+ NewCount.
+
+wait(Mseconds) ->
+ receive
+ after Mseconds -> ok
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+restart_check (doc) ->
+ [
+ "test case:'A' performs a transaction on a table which",
+ "is only replicated on node B. During that transaction",
+ "mnesia on node B is killed. The transaction of A should",
+ "be stopped, since there is no further replica",
+ "rt = read_lock_table, wt = write_lock_table, r = read,",
+ "sw = s_write, w = write, wr = wread,"];
+restart_check(suite) ->
+ [
+ restart_r_one,
+ restart_w_one,
+ restart_rt_one,
+ restart_wt_one,
+ restart_wr_one,
+ restart_sw_one,
+ restart_r_two,
+ restart_w_two,
+ restart_rt_two,
+ restart_wt_two,
+ restart_wr_two,
+ restart_sw_two
+ ].
+
+restart_r_one(suite) -> [];
+restart_r_one(Config) when is_list(Config) ->
+ start_restart_check(r, one, Config).
+
+restart_w_one(suite) -> [];
+restart_w_one(Config) when is_list(Config) ->
+ start_restart_check(w, one, Config).
+
+restart_rt_one(suite) -> [];
+restart_rt_one(Config) when is_list(Config) ->
+ start_restart_check(rt, one, Config).
+
+restart_wt_one(suite) -> [];
+restart_wt_one(Config) when is_list(Config) ->
+ start_restart_check(wt, one, Config).
+
+restart_wr_one(suite) -> [];
+restart_wr_one(Config) when is_list(Config) ->
+ start_restart_check(wr, one, Config).
+
+restart_sw_one(suite) -> [];
+restart_sw_one(Config) when is_list(Config) ->
+ start_restart_check(sw, one, Config).
+
+restart_r_two(suite) -> [];
+restart_r_two(Config) when is_list(Config) ->
+ start_restart_check(r, two, Config).
+
+restart_w_two(suite) -> [];
+restart_w_two(Config) when is_list(Config) ->
+ start_restart_check(w, two, Config).
+
+restart_rt_two(suite) -> [];
+restart_rt_two(Config) when is_list(Config) ->
+ start_restart_check(rt, two, Config).
+
+restart_wt_two(suite) -> [];
+restart_wt_two(Config) when is_list(Config) ->
+ start_restart_check(wt, two, Config).
+
+restart_wr_two(suite) -> [];
+restart_wr_two(Config) when is_list(Config) ->
+ start_restart_check(wr, two, Config).
+
+restart_sw_two(suite) -> [];
+restart_sw_two(Config) when is_list(Config) ->
+ start_restart_check(sw, two, Config).
+
+start_restart_check(RestartOp, ReplicaNeed, Config) ->
+ [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config),
+
+ {TabName, _TabNodes} = create_restart_table(ReplicaNeed, Nodes),
+
+ %% initialize the table with object {1, c} - when there
+ %% is a read transaction, the read will find that value
+ ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({TabName, 1, c}) end)),
+
+ %% Really sync tid_release
+ rpc:multicall([N2,N3], ?MODULE, sync_tid_release, []),
+ Coord = self(),
+
+ Fun_A = fun() ->
+ NewCounter = incr_restart_counter(),
+ case NewCounter of
+ 1 ->
+ mnesia:write({TabName, 1, d}),
+ %% send a message to the test proc
+ Coord ! {self(),fun_a_is_blocked},
+ receive go_ahead -> ok end;
+ _ ->
+ %% the fun will NOT be blocked here
+ restart_fun_A(RestartOp, TabName)
+ end,
+ NewCounter
+ end,
+
+ A = spawn_link(N1, ?MODULE, perform_restarted_transaction, [Fun_A]),
+ ?match_receive({A,fun_a_is_blocked}),
+
+ %% mnesia shall be killed at that node, where A is reading
+ %% the information from
+ kill_where_to_read(TabName, N1, [N2, N3]),
+
+ %% wait some time to let mnesia go down and spread those news around
+ %% fun A shall be able to finish its job before being restarted
+ wait(500),
+ A ! go_ahead,
+
+ %% the sticky write doesnt work on remote nodes !!!
+ ExpectedMsg =
+ case RestartOp of
+ sw when ReplicaNeed == two ->
+ {'EXIT',A,{aborted, {not_local, TabName}}};
+ _all_other ->
+ case ReplicaNeed of
+ one ->
+ {'EXIT',A,{aborted, {no_exists, TabName}}};
+ two ->
+ {'EXIT',A,{atomic, 2}}
+ end
+ end,
+
+ ?match_receive(ExpectedMsg),
+
+ %% now mnesia has to be started again on the node KillNode
+ %% because the next test suite will need it
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [TabName])),
+
+
+ %% the expected result depends on the transaction of
+ %% fun A - when that doesnt change the object in the
+ %% table (e.g. it is a read) then the predefined
+ %% value {Tabname, 1, c} is expected to be the result here
+
+ ExpectedResult =
+ case ReplicaNeed of
+ one ->
+ [];
+ two ->
+ case RestartOp of
+ w -> [{TabName, 1, a}];
+ _ ->[ {TabName, 1, c}]
+ end
+ end,
+
+ ?match({atomic, ExpectedResult},
+ mnesia:transaction(fun() -> mnesia:read({TabName, 1}) end,100)),
+ ?verify_mnesia(Nodes, []).
+
+create_restart_table(ReplicaNeed, [_N1, N2, N3]) ->
+ TabNodes =
+ case ReplicaNeed of
+ one -> [N2];
+ two -> [N2, N3]
+ end,
+ TabName = mk_tab_name(restart_check_),
+ ?match({atomic, ok}, mnesia:create_table(TabName, [{ram_copies, TabNodes}])),
+ {TabName, TabNodes}.
+
+restart_fun_A(Op, TabName) ->
+ case Op of
+ rt -> mnesia:read_lock_table(TabName);
+ wt -> mnesia:write_lock_table(TabName);
+ r -> mnesia:read( {TabName, 1});
+ w -> mnesia:write({TabName, 1, a});
+ wr -> mnesia:wread({TabName, 1});
+ sw -> mnesia:s_write({TabName, 1, a})
+ end.
+
+kill_where_to_read(TabName, N1, Nodes) ->
+ Read = rpc:call(N1,mnesia,table_info, [TabName, where_to_read]),
+ case lists:member(Read, Nodes) of
+ true ->
+ mnesia_test_lib:kill_mnesia([Read]);
+ false ->
+ ?error("Fault while killing Mnesia: ~p~n", [Read]),
+ mnesia_test_lib:kill_mnesia(Nodes)
+ end.
+
+sync_tid_release() ->
+ sys:get_status(whereis(mnesia_tm)),
+ sys:get_status(whereis(mnesia_locker)),
+ ok.
+
diff --git a/lib/mnesia/test/mnesia_config_backup.erl b/lib/mnesia/test/mnesia_config_backup.erl
new file mode 100644
index 0000000000..a33ec6ac5c
--- /dev/null
+++ b/lib/mnesia/test/mnesia_config_backup.erl
@@ -0,0 +1,105 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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_config_backup).
+-author('[email protected]').
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%
+%% This module is used for testing the backup module config parameter.
+%%
+%% This module is an impostor for the mnesia_backup module.
+%%
+%%
+%% Original doc below:
+%%
+%% This module contains one implementation of callback functions
+%% used by Mnesia at backup and restore. The user may however
+%% write an own module the same interface as mnesia_backup and
+%% configure Mnesia so the alternate module performs the actual
+%% accesses to the backup media. This means that the user may put
+%% the backup on medias that Mnesia does not know about, possibly
+%% on hosts where Erlang is not running.
+%%
+%% The OpaqueData argument is never interpreted by other parts of
+%% Mnesia. It is the property of this module. Alternate implementations
+%% of this module may have different interpretations of OpaqueData.
+%% The OpaqueData argument given to open_write/1 and open_read/1
+%% are forwarded directly from the user.
+%%
+%% All functions must return {ok, NewOpaqueData} or {error, Reason}.
+%%
+%% The NewOpaqueData arguments returned by backup callback functions will
+%% be given as input when the next backup callback function is invoked.
+%% If any return value does not match {ok, _} the backup will be aborted.
+%%
+%% The NewOpaqueData arguments returned by restore callback functions will
+%% be given as input when the next restore callback function is invoked
+%% If any return value does not match {ok, _} the restore will be aborted.
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-export([
+ open_write/1, write/2, commit_write/1, abort_write/1,
+ open_read/1, read/1, close_read/1
+ ]).
+
+-record(backup, {name, mode, items}).
+
+open_write(Name) ->
+ file:delete(Name),
+ {ok, #backup{name = Name, mode = write, items = []}}.
+
+write(Opaque, Item) when Opaque#backup.mode == write ->
+ %% Build the list in reverse order
+ {ok, Opaque#backup{items = [Item | Opaque#backup.items]}}.
+
+commit_write(Opaque) when Opaque#backup.mode == write ->
+ Bin = term_to_binary(Opaque#backup.items),
+ case file:write_file(Opaque#backup.name, Bin) of
+ ok ->
+ {ok, Opaque#backup{mode = closed, items = []}};
+ {error, Reason} ->
+ {error, {commit_write, Reason}}
+ end.
+
+abort_write(Opaque) ->
+ {ok, Opaque#backup{mode = closed, items = []}}.
+
+open_read(Name) ->
+ case file:read_file(Name) of
+ {ok, Bin} ->
+ ReverseList = binary_to_term(Bin),
+ List = lists:reverse(ReverseList),
+ {ok, #backup{name = Name, mode = read, items = List}};
+ {error, Reason} ->
+ {error, {open_read, Reason}}
+ end.
+
+read(Opaque) when Opaque#backup.mode == read ->
+ case Opaque#backup.items of
+ [Head | Tail] ->
+ {ok, Opaque#backup{items = Tail}, Head};
+ [] ->
+ {ok, Opaque#backup{mode = closed}, []}
+ end.
+
+close_read(Opaque) ->
+ {ok, Opaque#backup{mode = closed, items = []}}.
diff --git a/lib/mnesia/test/mnesia_config_event.erl b/lib/mnesia/test/mnesia_config_event.erl
new file mode 100644
index 0000000000..6c1dea7ed5
--- /dev/null
+++ b/lib/mnesia/test/mnesia_config_event.erl
@@ -0,0 +1,74 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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_config_event).
+-author('[email protected]').
+
+-behaviour(gen_event).
+
+%%
+%% This module was stolen from Mnesia
+%%
+
+
+%% gen_event callback interface
+-export([init/1, handle_event/2, handle_call/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+
+init(_Args) ->
+ {ok, []}.
+
+handle_event(Msg, State) ->
+ handle_any_event(Msg, State).
+
+handle_info(Msg, State) ->
+ handle_any_event(Msg, State).
+
+
+handle_call(Msg, State) ->
+ handle_any_event(Msg, State).
+
+
+%% The main...
+
+handle_any_event({get_log, Pid}, State) ->
+ Pid ! {log, State},
+ {ok, State};
+handle_any_event(Msg, State) ->
+ io:format("Got event: ~p~n", [Msg]),
+ {ok, [Msg | State]}.
+
+%%-----------------------------------------------------------------
+%% terminate(Reason, State) ->
+%% AnyVal
+%%-----------------------------------------------------------------
+
+terminate(_Reason, _State) ->
+ ok.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Upgrade process when its code is to be changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+code_change(_OldVsn, _State, _Extra) ->
+ exit(not_supported).
+
diff --git a/lib/mnesia/test/mnesia_config_test.erl b/lib/mnesia/test/mnesia_config_test.erl
new file mode 100644
index 0000000000..7b62c63a62
--- /dev/null
+++ b/lib/mnesia/test/mnesia_config_test.erl
@@ -0,0 +1,1466 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(mnesia_config_test).
+-author('[email protected]').
+
+-include("mnesia_test_lib.hrl").
+
+-record(test_table,{i,a1,a2,a3}).
+-record(test_table2,{i, b}).
+
+-export([
+ all/1,
+ access_module/1,
+ auto_repair/1,
+ backup_module/1,
+ debug/1,
+ dir/1,
+ dump_log_load_regulation/1,
+ dump_log_thresholds/1,
+ dump_log_update_in_place/1,
+ embedded_mnemosyne/1,
+ event_module/1,
+ ignore_fallback_at_startup/1,
+ inconsistent_database/1,
+ max_wait_for_decision/1,
+ send_compressed/1,
+
+ app_test/1,
+ schema_config/1,
+ schema_merge/1,
+ unknown_config/1,
+
+ dump_log_time_threshold/1,
+ dump_log_write_threshold/1,
+
+ start_one_disc_full_then_one_disc_less/1,
+ start_first_one_disc_less_then_one_disc_full/1,
+ start_first_one_disc_less_then_two_more_disc_less/1,
+ schema_location_and_extra_db_nodes_combinations/1,
+ table_load_to_disc_less_nodes/1,
+ dynamic_connect/1,
+ dynamic_basic/1,
+ dynamic_ext/1,
+ dynamic_bad/1,
+
+ init_per_testcase/2,
+ fin_per_testcase/2,
+ c_nodes/0
+ ]).
+
+-export([check_logs/1]).
+
+-define(init(N, Config),
+ mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema,
+ {reload_appls, [mnesia]}],
+ N, Config, ?FILE, ?LINE)).
+-define(acquire(N, Config),
+ mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema,
+ {reload_appls, [mnesia]},
+ create_schema,
+ {start_appls, [mnesia]}],
+ N, Config, ?FILE, ?LINE)).
+-define(acquire_schema(N, Config),
+ mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema,
+ {reload_appls, [mnesia]},
+ create_schema],
+ N, Config, ?FILE, ?LINE)).
+-define(cleanup(N, Config),
+ mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}],
+ N, Config, ?FILE, ?LINE)).
+-define(trans(Fun),
+ ?match({atomic, ok}, mnesia:transaction(Fun))).
+
+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) ->
+ [
+ "Test all configuration parameters",
+ "Perform an exhaustive test of all the various parameters that",
+ "may be used to configure the Mnesia application.",
+ "",
+ "Hint: Check out the unofficial function mnesia:start/1.",
+ " But be careful to cleanup all configuration parameters",
+ " afterwards since the rest of the test suite may rely on",
+ " these default configurations. Perhaps it is best to run",
+ " these tests in a separate node which is dropped afterwards.",
+ "Are really all configuration parameters covered?"];
+
+all(suite) ->
+ [
+ access_module,
+ auto_repair,
+ backup_module,
+ debug,
+ dir,
+ dump_log_load_regulation,
+ dump_log_thresholds,
+ dump_log_update_in_place,
+ embedded_mnemosyne,
+ event_module,
+ ignore_fallback_at_startup,
+ inconsistent_database,
+ max_wait_for_decision,
+ send_compressed,
+
+ app_test,
+ schema_config,
+ unknown_config
+ ].
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+access_module(doc) ->
+ ["Replace the activity access module with another module and ",
+ "use it to read and write to some alternate table storage"];
+access_module(suite) -> [];
+access_module(Config) when is_list(Config) ->
+ Nodes = ?acquire_schema(1, Config),
+ ?match(ok, mnesia:start([{access_module, mnesia_frag}])),
+
+ ?match(mnesia_frag, mnesia:system_info(access_module)),
+
+ access_tab(ram_copies, Nodes),
+ case mnesia_test_lib:diskless(Config) of
+ true -> skip;
+ false ->
+ access_tab(disc_copies, Nodes)
+ , access_tab(disc_only_copies, Nodes)
+ end,
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config).
+
+access_tab(Storage, Nodes) ->
+ Tab = list_to_atom(lists:concat([access_tab_, Storage])),
+ RecName = some_access,
+ Attr = val,
+ TabDef = [{Storage, Nodes},
+ {type, bag},
+ {index, [Attr]},
+ {record_name, RecName}],
+ ?match({atomic,ok}, mnesia:create_table(Tab, TabDef)),
+
+ Activity = fun(Kind) ->
+ A = [Kind, Tab, RecName, Attr, Nodes],
+ io:format("kind: ~w, storage: ~w~n", [Kind, Storage]),
+ mnesia:activity(Kind, fun do_access/5, A)
+ end,
+ ModActivity = fun(Kind, M) ->
+ io:format("kind: ~w, storage: ~w. module: ~w~n",
+ [Kind, Storage, M]),
+ A = [Kind, Tab, RecName, Attr, Nodes],
+ mnesia:activity(Kind, fun do_access/5, A, M)
+ end,
+ ?match(ok, Activity(transaction)),
+ ?match(ok, Activity({transaction, 47})),
+ ?match(ok, ModActivity(transaction, mnesia)),
+ ?match(ok, ModActivity(transaction, mnesia_frag)),
+
+ ?match(ok, Activity(async_dirty)),
+ ?match(ok, Activity(sync_dirty)),
+ case Storage of
+ ram_copies ->
+ ?match(ok, Activity(ets));
+ _ ->
+ ignore
+ end.
+
+do_access(Kind, Tab, RecName, Attr, Nodes) ->
+ Tens = lists:sort([{RecName, 1, 10}, {RecName, 3, 10}]),
+ {OptNodes, OptTens} =
+ case Kind of
+ transaction -> {Nodes, Tens};
+ {transaction, _} -> {Nodes, Tens};
+ async_dirty -> {[], Tens};
+ sync_dirty -> {[], Tens};
+ ets -> {[], []}
+ end,
+ ?match(RecName, mnesia:table_info(Tab, record_name)),
+
+ ?match(ok, mnesia:write(Tab, {RecName, 1, 10}, write)),
+ ?match(ok, mnesia:write(Tab, {RecName, 2, 20}, sticky_write)),
+ ?match(ok, mnesia:write(Tab, {RecName, 2, 21}, sticky_write)),
+ ?match(ok, mnesia:write(Tab, {RecName, 2, 22}, write)),
+ ?match(ok, mnesia:write(Tab, {RecName, 3, 10}, write)),
+
+ Twos = [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}],
+ ?match(Twos, lists:sort(mnesia:read(Tab, 2, read))),
+
+ ?match(ok, mnesia:delete_object(Tab, {RecName, 2, 21}, sticky_write)),
+
+ TenPat = {RecName, '_', 10},
+ ?match(Tens, lists:sort(mnesia:match_object(Tab, TenPat, read))),
+ ?match(OptTens, lists:sort(mnesia:index_match_object(Tab, TenPat, Attr, read) )),
+ ?match(OptTens, lists:sort(mnesia:index_read(Tab, 10, Attr))),
+ Keys = [1, 2, 3],
+ ?match(Keys, lists:sort(mnesia:all_keys(Tab))),
+
+ First = mnesia:first(Tab),
+ Mid = mnesia:next(Tab, First),
+ Last = mnesia:next(Tab, Mid),
+ ?match('$end_of_table', mnesia:next(Tab, Last)),
+ ?match(Keys, lists:sort([First,Mid,Last])),
+
+ %% For set and bag these last, prev works as first and next
+ First2 = mnesia:last(Tab),
+ Mid2 = mnesia:prev(Tab, First2),
+ Last2 = mnesia:prev(Tab, Mid2),
+ ?match('$end_of_table', mnesia:prev(Tab, Last2)),
+ ?match(Keys, lists:sort([First2,Mid2,Last2])),
+
+ ?match([ok, ok, ok], [mnesia:delete(Tab, K, write) || K <- Keys]),
+ W = wild_pattern,
+ ?match([], mnesia:match_object(Tab, mnesia:table_info(Tab, W), read)),
+ ?log("Safe fixed ~p~n", [catch ets:info(Tab, safe_fixed)]),
+ ?log("Fixed ~p ~n", [catch ets:info(Tab, fixed)]),
+
+ ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, write)),
+ ?match(OptNodes, mnesia:lock({global, some_lock_item, Nodes}, read)),
+ ?match(OptNodes, mnesia:lock({table, Tab}, read)),
+ ?match(OptNodes, mnesia:lock({table, Tab}, write)),
+
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+auto_repair(doc) ->
+ ["Try the auto_repair mechanism on the various disk_logs and dets files.",
+ "",
+ "The case tests both normal values of the parameter, and also",
+ "one crazy value.",
+ "The test of the real auto_repair functionality is made in the",
+ "dets suite"
+ ];
+auto_repair(suite) -> [];
+auto_repair(Config) when is_list(Config) ->
+ ?init(1, Config),
+ ?match(ok, mnesia:start()), % Check default true
+ ?match(true, mnesia:system_info(auto_repair)),
+ ?match(stopped, mnesia:stop()),
+ ?match(ok, mnesia:start([{auto_repair, true}])),
+ ?match(true, mnesia:system_info(auto_repair)),
+ ?match(stopped, mnesia:stop()),
+ ?match(ok, mnesia:start([{auto_repair, false}])),
+ ?match(false, mnesia:system_info(auto_repair)),
+ ?match(stopped, mnesia:stop()),
+ ?match({error, {bad_type, auto_repair, your_mama}},
+ mnesia:start([{auto_repair, your_mama}])),
+ ?match(stopped, mnesia:stop()),
+ ?cleanup(1, Config),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+backup_module(doc) ->
+ ["Replace the backup module with another module and use it to",
+ "read and write to an alternate backup media, e.g stored in",
+ "the internal state of a simple process."];
+backup_module(suite) -> [];
+backup_module(Config) when is_list(Config) ->
+ Nodes = ?acquire_schema(1, Config),
+ ?match(ok, mnesia:start([{backup_module, mnesia_config_backup}])),
+ ?match({atomic,ok},
+ mnesia:create_table(test_table,
+ [{disc_copies, Nodes},
+ {attributes,
+ record_info(fields,test_table)}])),
+
+ ?match({atomic,ok},
+ mnesia:create_table(test_table2,
+ [{disc_copies, Nodes},
+ {attributes,
+ record_info(fields,test_table2)}])),
+ %% Write in test table
+ ?trans(fun() -> mnesia:write(#test_table{i=1}) end),
+ ?trans(fun() -> mnesia:write(#test_table{i=2}) end),
+
+ %% Write in test table 2
+ ?trans(fun() -> mnesia:write(#test_table2{i=3}) end),
+ ?trans(fun() -> mnesia:write(#test_table2{i=4}) end),
+ mnesia_test_lib:sync_tables(Nodes, [test_table, test_table2]),
+
+ File = whow,
+ %% Now make a backup
+ ?match(ok, mnesia:backup(File)),
+
+ ?match(ok, mnesia:install_fallback(File)),
+
+ %% Now add things
+ ?trans(fun() -> mnesia:write(#test_table{i=2.5}) end),
+ ?trans(fun() -> mnesia:write(#test_table2{i=3.5}) end),
+
+ mnesia_test_lib:kill_mnesia(Nodes),
+ receive after 2000 -> ok end,
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [test_table, test_table2])),
+
+ %% Now check newly started tables
+ ?match({atomic, [1,2]},
+ mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table)) end)),
+ ?match({atomic, [3,4]},
+ mnesia:transaction(fun() -> lists:sort(mnesia:all_keys(test_table2)) end)),
+
+ file:delete(File),
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+debug(doc) ->
+ ["Try out the four debug levels and ensure that the",
+ "expected events are generated."];
+debug(suite) -> [];
+debug(Config) when is_list(Config) ->
+ Nodes = ?init(1, Config),
+ case application:get_env(mnesia,debug) of
+ undefined ->
+ ?match(none, mnesia:system_info(debug));
+ {ok, false} ->
+ ?match(none, mnesia:system_info(debug));
+ {ok, true} ->
+ ?match(debug, mnesia:system_info(debug));
+ {ok, Env} ->
+ ?match(Env, mnesia:system_info(debug))
+ end,
+
+ ?match(ok, mnesia:start([{debug, verbose}])),
+ ?match(verbose, mnesia:system_info(debug)),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ receive after 2000 -> ok end,
+
+ ?match(ok, mnesia:start([{debug, debug}])),
+ ?match(debug, mnesia:system_info(debug)),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ receive after 2000 -> ok end,
+
+ ?match(ok, mnesia:start([{debug, trace}])),
+ ?match(trace, mnesia:system_info(debug)),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ receive after 2000 -> ok end,
+
+ ?match(ok, mnesia:start([{debug, true}])),
+ ?match(debug, mnesia:system_info(debug)),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ receive after 2000 -> ok end,
+
+ ?match(ok, mnesia:start([{debug, false}])),
+ ?match(none, mnesia:system_info(debug)),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dir(doc) ->
+ ["Try to use alternate Mnesia directories"];
+dir(suite) -> [];
+dir(Config) when is_list(Config) ->
+ Nodes = ?init(1, Config),
+
+ ?match(ok, mnesia:start([{dir, tuff}])),
+ Dir = filename:join([element(2, file:get_cwd()), "tuff"]),
+ ?match(Dir, mnesia:system_info(directory)),
+ mnesia_test_lib:kill_mnesia(Nodes),
+
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dump_log_update_in_place(doc) ->
+ ["Change the update in place policy for the transaction log dumper."];
+dump_log_update_in_place(suite) -> [];
+dump_log_update_in_place(Config) when is_list(Config) ->
+ Nodes = ?acquire(1, Config),
+ ?match(true, mnesia:system_info(dump_log_update_in_place)),
+ ?match({atomic,ok},
+ mnesia:create_table(test_table,
+ [{disc_copies, Nodes},
+ {attributes,
+ record_info(fields,test_table)}])),
+
+ mnesia_test_lib:kill_mnesia(Nodes),
+ receive after 2000 -> ok end,
+
+ ?match(ok, mnesia:start([{dump_log_update_in_place, false}])),
+ ?match(false, mnesia:system_info(dump_log_update_in_place)),
+
+ mnesia_test_lib:sync_tables(Nodes, [schema, test_table]),
+
+ %% Now provoke some log dumps
+
+ L = lists:map(
+ fun(Num) ->
+ %% Write something on one end ...
+ mnesia:transaction(
+ fun() ->
+ mnesia:write(#test_table{i=Num}) end
+ ) end,
+ lists:seq(1, 110)),
+
+ L2 = lists:duplicate(110, {atomic, ok}),
+
+ %% If this fails then some of the 110 writes above failed
+ ?match(true, L==L2),
+ if L==L2 -> ok;
+ true ->
+ ?verbose("***** List1 len: ~p, List2 len: ~p~n",
+ [length(L), length(L2)]),
+ ?verbose("L: ~p~nL2:~p~n", [L, L2])
+ end,
+
+ %% If we still can write, then Mnesia is probably alive
+ ?trans(fun() -> mnesia:write(#test_table{i=115}) end),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dump_log_thresholds(doc) ->
+ ["Elaborate with various values of the dump log thresholds and how",
+ "they affects each others. Both the dump_log_time_threshold and the",
+ "dump_log_write_threshold must be covered. Do also check that both",
+ "kinds of overload events are generated as expected.",
+ "",
+ "Logs are checked by first doing whatever has to be done to trigger ",
+ "a dump, and then stopping Mnesia and then look in the ",
+ "data files and see that the correct amount of transactions ",
+ "have been done."];
+dump_log_thresholds(suite) ->
+ [
+ dump_log_time_threshold,
+ dump_log_write_threshold
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dump_log_write_threshold(doc)->
+ ["This test case must be rewritten.",
+ "Dump logs are tested by doing transactions, then killing Mnesia and ",
+ "then examining the table data files and see if they are correct.",
+ "The test_table is used as a counter, test_table. is stepped once ",
+ "for each transaction."];
+dump_log_write_threshold(suite)->[];
+dump_log_write_threshold(Config) when is_list(Config) ->
+ [N1] = ?acquire_schema(1, Config),
+
+ Threshold = 3,
+ ?match(ok,mnesia:start([{dump_log_write_threshold, Threshold}])),
+
+ ?match({atomic,ok},
+ mnesia:create_table(test_table,
+ [{disc_copies, [N1]},
+ {attributes,
+ record_info(fields,test_table)}])),
+ ?match(dumped, mnesia:dump_log()),
+
+ ?match(ok, do_trans(2)), % Shall not have dumped
+ check_logs(0),
+
+ ?match(ok, do_trans(Threshold - 2)), % Trigger a dump
+ receive after 1000 -> ok end,
+ check_logs(Threshold),
+
+
+ ?match(ok, do_trans(Threshold - 1)),
+ ?match(dumped, mnesia:dump_log()), %% This should trigger ets2dcd dump
+ check_logs(0), %% and leave no dcl file
+
+ ?match(stopped, mnesia:stop()),
+
+ %% Check bad threshold value
+ ?match({error,{bad_type,dump_log_write_threshold,0}},
+ mnesia:start([{dump_log_write_threshold,0}])),
+
+ ?verify_mnesia([], [N1]),
+ ?cleanup(1, Config),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dump_log_time_threshold(doc)->
+ ["See doc on above."];
+dump_log_time_threshold(suite)->[];
+dump_log_time_threshold(Config) when is_list(Config) ->
+ Nodes = ?acquire_schema(1, Config),
+ Time = 4000,
+
+ %% Check bad threshold value
+ ?match({error,{bad_type,dump_log_time_threshold,0}},
+ mnesia:start([{dump_log_time_threshold,0}])),
+
+
+ ?match(ok,mnesia:start([{dump_log_write_threshold,100},
+ {dump_log_time_threshold, Time}])),
+
+ ?match({atomic,ok},mnesia:create_table(test_table,
+ [{disc_copies, Nodes},
+ {attributes,
+ record_info(fields,
+ test_table)}])),
+
+ %% Check that nothing is dumped when within time threshold
+ ?match(ok, do_trans(1)),
+ check_logs(0),
+
+ ?match(Time, mnesia:system_info(dump_log_time_threshold)),
+
+ %% Check that things get dumped when time threshold exceeded
+ ?match(ok, do_trans(5)),
+ receive after Time+2000 -> ok end,
+ check_logs(6),
+
+ ?verify_mnesia([node()], []),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%
+%%
+%% Help functions for dump log
+
+%% Do a transaction N times
+do_trans(0) -> ok;
+do_trans(N) ->
+ Fun = fun() ->
+ XX=incr(),
+ mnesia:write(#test_table{i=XX})
+ end,
+ {atomic, ok} = mnesia:transaction(Fun),
+ do_trans(N-1).
+
+%% An increasing number
+incr() ->
+ case get(bloody_counter) of
+ undefined -> put(bloody_counter, 2), 1;
+ Num -> put(bloody_counter, Num+1)
+ end.
+
+%%
+%% Check that the correct number of transactions have been recorded.
+%%-record(test_table,{i,a1,a2,a3}).
+check_logs(N) ->
+ File = mnesia_lib:tab2dcl(test_table),
+ Args = [{file, File}, {name, testing}, {repair, true}, {mode, read_only}],
+
+ if N == 0 ->
+ ?match(false, mnesia_lib:exists(File));
+ true ->
+ ?match(true, mnesia_lib:exists(File)),
+ ?match({ok, _Log}, disk_log:open(Args)),
+
+ {Cont, Terms} = disk_log:chunk(testing, start),
+ ?match(eof, disk_log:chunk(testing, Cont)),
+ %%?verbose("N: ~p, L: ~p~n", [N, L]),
+ disk_log:close(testing),
+
+ %% Correct number of records in file
+ ?match({N, N}, {N, length(Terms) -1 }) %% Ignore Header
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dump_log_load_regulation(doc) ->
+ ["Test the load regulation of the dumper"];
+dump_log_load_regulation(suite) ->
+ [];
+dump_log_load_regulation(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ Param = dump_log_load_regulation,
+
+ %% Normal
+ NoReg = false,
+ ?match(NoReg, mnesia:system_info(Param)),
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+
+ %% Bad
+ Bad = arne_anka,
+ ?match({error, {bad_type, Param, Bad}},
+ mnesia:start([{Param, Bad}])),
+
+ %% Regulation activated
+ Reg = true,
+ ?match(ok,mnesia:start([{Param, Reg}])),
+ ?match(Reg, mnesia:system_info(Param)),
+
+ Args =
+ [{db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {replica_nodes, Nodes},
+ {n_drivers_per_node, 5},
+ {n_branches, length(Nodes) * 10},
+ {n_accounts_per_branch, 5},
+ {replica_type, disc_copies},
+ {stop_after, timer:seconds(30)},
+ {report_interval, timer:seconds(10)},
+ {use_running_mnesia, true},
+ {reuse_history_id, true}],
+
+ ?match({ok, _}, mnesia_tpcb:start(Args)),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+embedded_mnemosyne(doc) ->
+ ["Start Mnemosyne as an embedded part of Mnesia",
+ "on some of the nodes"];
+embedded_mnemosyne(suite) ->
+ [];
+embedded_mnemosyne(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ Param = embedded_mnemosyne,
+
+ %% Normal
+ NoMnem = false,
+ ?match(NoMnem, mnesia:system_info(Param)),
+ ?match(undefined, whereis(mnemosyne_catalog)),
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+
+ %% Bad
+ Bad = arne_anka,
+ ?match({error, {bad_type, Param, Bad}},
+ mnesia:start([{Param, Bad}])),
+
+ case code:priv_dir(mnemosyne) of
+ {error, _} -> %% No mnemosyne on later systems
+ ok;
+ _ ->
+ %% Mnemosyne as embedded application
+ Mnem = true,
+ ?match(undefined, whereis(mnemosyne_catalog)),
+ ?match(ok,mnesia:start([{Param, Mnem}])),
+ ?match(Mnem, mnesia:system_info(Param)),
+ ?match(Pid when is_pid(Pid), whereis(mnemosyne_catalog)),
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+ ?match(undefined, whereis(mnemosyne_catalog))
+ end,
+ ?verify_mnesia([], Nodes),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+ignore_fallback_at_startup(doc) ->
+ ["Start Mnesia without rollback of the database to the fallback. ",
+ "Once Mnesia has been (re)started the installed fallback should",
+ "be handled as a normal active fallback.",
+ "Install a customized event module which disables the termination",
+ "of Mnesia when mnesia_down occurrs with an active fallback."].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+max_wait_for_decision(doc) ->
+ ["Provoke Mnesia to make a forced decision of the outome",
+ "of a heavy weight transaction."].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+send_compressed(doc) -> [];
+send_compressed(suite) -> [];
+send_compressed(Config) ->
+ [N1,N2] = Nodes = ?acquire_nodes(2, Config),
+ ?match({atomic,ok}, mnesia:create_table(t0, [{ram_copies,[N1,N2]}])),
+ ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])),
+ ?match({atomic,ok}, mnesia:create_table(t2, [{disc_only_copies,[N1,N2]}])),
+
+ Max = 1000,
+ Create = fun(Tab) -> [mnesia:write({Tab, N, {N, "FILLER-123490878345asdasd"}})
+ || N <- lists:seq(1, Max)],
+ ok
+ end,
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+ ?match(ok, mnesia:start([{send_compressed, 9}])),
+ ?match(ok, mnesia:wait_for_tables([t0,t1,t2], 5000)),
+
+ ?match({atomic, ok}, mnesia:transaction(Create, [t0])),
+ ?match({atomic, ok}, mnesia:transaction(Create, [t1])),
+ ?match({atomic, ok}, mnesia:transaction(Create, [t2])),
+
+ ?match([], mnesia_test_lib:start_mnesia([N2], [t0,t1,t2])),
+
+ Verify = fun(Tab) ->
+ [ [{Tab,N,{N,_}}] = mnesia:read(Tab, N) || N <- lists:seq(1, Max)],
+ ok
+ end,
+ ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t0]])),
+ ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t1]])),
+ ?match({atomic, ok}, rpc:call(N1, mnesia, transaction, [Verify, [t2]])),
+
+ ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t0]])),
+ ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t1]])),
+ ?match({atomic, ok}, rpc:call(N2, mnesia, transaction, [Verify, [t2]])),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config),
+ ok.
+
+app_test(doc) -> [];
+app_test(suite) -> [];
+app_test(_Config) ->
+ ?match(ok,test_server:app_test(mnesia)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+event_module(doc) ->
+ ["Replace the event module with another module and use it as",
+ "receiver of the various system and table events. Provoke",
+ "coverage of all kinds of events."];
+event_module(suite) -> [];
+event_module(Config) when is_list(Config) ->
+ Filter = fun({mnesia_system_event,{mnesia_info, _, _}}) -> false;
+ (_) -> true
+ end,
+
+ [_N1, N2]=Nodes=?acquire_schema(2, Config),
+
+ Def = case mnesia_test_lib:diskless(Config) of
+ true -> [{event_module, mnesia_config_event},
+ {extra_db_nodes, Nodes}];
+ false ->
+ [{event_module, mnesia_config_event}]
+ end,
+
+ ?match({[ok, ok], []}, rpc:multicall(Nodes, mnesia, start, [Def])),
+ receive after 1000 -> ok end,
+ mnesia_event ! {get_log, self()},
+ DebugLog1 = receive
+ {log, L1} -> L1
+ after 10000 -> [timeout]
+ end,
+ ?match([{mnesia_system_event,{mnesia_up,N2}}],
+ lists:filter(Filter, DebugLog1)),
+ mnesia_test_lib:kill_mnesia([N2]),
+ receive after 2000 -> ok end,
+
+ ?match({[ok], []}, rpc:multicall([N2], mnesia, start, [])),
+
+ receive after 1000 -> ok end,
+ mnesia_event ! {get_log, self()},
+ DebugLog = receive
+ {log, L} -> L
+ after 10000 -> [timeout]
+ end,
+ ?match([{mnesia_system_event,{mnesia_up,N2}},
+ {mnesia_system_event,{mnesia_down,N2}},
+ {mnesia_system_event,{mnesia_up, N2}}],
+ lists:filter(Filter, DebugLog)),
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+schema_config(doc) ->
+ ["Try many configurations with various schema_location's with and",
+ "without explicit extra_db_nodes. Do also provoke various schema merge",
+ "situations. Most of the other test suites focusses on tests where the",
+ "schema is residing on disc. Now it is time to perform an exhaustive",
+ "elaboration with various disc less configurations."];
+schema_config(suite) ->
+ [
+ start_one_disc_full_then_one_disc_less,
+ start_first_one_disc_less_then_one_disc_full,
+ start_first_one_disc_less_then_two_more_disc_less,
+ schema_location_and_extra_db_nodes_combinations,
+ table_load_to_disc_less_nodes,
+ schema_merge,
+ dynamic_connect
+ ].
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+start_one_disc_full_then_one_disc_less(doc)->
+ ["Start a disk node and then a disk less one. Distribute some",
+ "tables between them."];
+start_one_disc_full_then_one_disc_less(suite) -> [];
+start_one_disc_full_then_one_disc_less(Config) when is_list(Config) ->
+ [N1, N2] = ?init(2, Config),
+ ?match(ok, mnesia:create_schema([N1])),
+ ?match([], mnesia_test_lib:start_mnesia([N1])),
+
+ ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram},
+ {extra_db_nodes, [N1]}]])),
+ mnesia_test_lib:sync_tables([N1, N2], [schema]),
+
+ %% Now create some tables
+ ?match({atomic,ok},
+ mnesia:create_table(test_table,
+ [{ram_copies, [N1, N2]},
+ {attributes,
+ record_info(fields,test_table)}])),
+
+ ?match({atomic,ok},
+ rpc:call(
+ N2, mnesia,create_table, [test_table2,
+ [{ram_copies, [N1, N2]},
+ {attributes,
+ record_info(fields,test_table2)}]])),
+
+ %% Write something on one end ...
+ Rec = #test_table{i=55},
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec) end)),
+
+ %% ... and read it in the other
+ ?match({atomic, [Rec]},
+ rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:read({test_table, 55}) end])),
+
+
+ %% Then do the same but start at the other end
+ Rec2 = #test_table2{i=155},
+ ?match({atomic, ok},
+ rpc:call(N2, mnesia, transaction,
+ [fun() ->
+ mnesia:write(Rec2) end
+ ])),
+
+ ?match({atomic, [Rec2]},
+ mnesia:transaction(fun() -> mnesia:read({test_table2, 155}) end)),
+
+ ?verify_mnesia([N1, N2], []),
+ ?cleanup(2, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+start_first_one_disc_less_then_one_disc_full(doc)->
+ ["no_doc"];
+start_first_one_disc_less_then_one_disc_full(suite) -> [];
+start_first_one_disc_less_then_one_disc_full(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?init(2, Config),
+ ?match(ok, mnesia:create_schema([N1])),
+ ?match([], mnesia_test_lib:start_mnesia([N1])),
+
+ ?match({atomic, ok}, mnesia:add_table_copy(schema, N2, ram_copies)),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram},
+ {extra_db_nodes, Nodes}]])),
+
+ mnesia_test_lib:sync_tables([N1, N2], [schema]),
+
+ mnesia_test_lib:kill_mnesia(Nodes),
+ receive after 2000 -> ok end,
+ ?match([], mnesia_test_lib:start_mnesia(Nodes)),
+
+ mnesia_test_lib:sync_tables([N1, N2], [schema]),
+
+ %% Now create some tables
+ ?match({atomic,ok},
+ rpc:call(
+ N1, mnesia,create_table, [test_table,
+ [%%{disc_copies, [node()]},
+ {ram_copies, [N1, N2]},
+ {attributes,
+ record_info(fields,test_table)}]])),
+ mnesia_test_lib:sync_tables([N1, N2], [test_table]),
+
+ ?match({atomic,ok},
+ rpc:call(
+ N2, mnesia,create_table, [test_table2,
+ [%%{disc_copies, [node()]},
+ {ram_copies, [N1, N2]},
+ {attributes,
+ record_info(fields,test_table2)}]])),
+
+ mnesia_test_lib:sync_tables([N1, N2], [test_table, test_table2]),
+
+ %% Assure tables loaded
+ ?match({[ok, ok], []},
+ rpc:multicall([N1, N2], mnesia, wait_for_tables,
+ [[schema, test_table, test_table2], 10000])),
+
+ %% Write something on one end ...
+ Rec = #test_table{i=55},
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:write(Rec) end])),
+
+ %% ... and read it in the other
+ ?match({atomic, [Rec]},
+ rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:read({test_table, 55}) end])),
+
+ %% Then do the same but start at the other end
+ Rec2 = #test_table2{i=155},
+ ?match({atomic, ok},
+ rpc:call(N2, mnesia, transaction,
+ [fun() ->
+ mnesia:write(Rec2) end
+ ])),
+
+ ?match({atomic, [Rec2]},
+ rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:read({test_table2, 155}) end])),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+start_first_one_disc_less_then_two_more_disc_less(doc)->
+ ["no doc"];
+start_first_one_disc_less_then_two_more_disc_less(suite) -> [];
+start_first_one_disc_less_then_two_more_disc_less(Config) when is_list(Config) ->
+ Nodes = [N1, N2, N3] = ?init(3, Config),
+
+ ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])),
+
+ %% Really should use test_lib:mnesia_start for these ones but ...
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])),
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia,add_table_copy, [schema, N3, ram_copies])),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram},
+ {extra_db_nodes, [N1]}]])),
+ ?match(ok, rpc:call(N3, mnesia, start, [[{schema_location, ram},
+ {extra_db_nodes, [N1, N2]}]])),
+
+ %% Now create some tables
+ ?match({atomic,ok},
+ rpc:call(
+ N1, mnesia,create_table, [test_table,
+ [%%{disc_copies, [node()]},
+ {ram_copies, [N1, N2, N3]},
+ {attributes,
+ record_info(fields,test_table)}]])),
+
+ %% Assure tables loaded
+ ?match({[ok, ok, ok], []},
+ rpc:multicall([N1, N2, N3], mnesia, wait_for_tables,
+ [[test_table], 1000])),
+
+ %% Write something on one end ...
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:write(#test_table{i=44}) end])),
+
+ %% Force synchronicity
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:write_lock_table(test_table) end])),
+
+ %% ... and read it in the others
+ ?match({[{atomic, [{test_table, 44, _, _, _}]},
+ {atomic, [{test_table, 44, _, _, _}]}], []},
+ rpc:multicall([N2, N3], mnesia, transaction,
+ [fun() -> mnesia:read({test_table, 44}) end])),
+
+ %% Then do the other way around
+ ?match({atomic, ok},
+ rpc:call(N3, mnesia, transaction,
+ [fun() -> mnesia:write(#test_table{i=33}) end])),
+ %% Force synchronicity
+ ?match({atomic, ok},
+ rpc:call(N3, mnesia, transaction,
+ [fun() -> mnesia:write_lock_table(test_table) end])),
+
+ ?match({[{atomic, [{test_table, 44, _, _, _}]},
+ {atomic, [{test_table, 44, _, _, _}]}], []},
+ rpc:multicall([N1, N2], mnesia, transaction,
+ [fun() -> mnesia:read({test_table, 44}) end])),
+
+ mnesia_test_lib:reload_appls([mnesia], Nodes),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+schema_location_and_extra_db_nodes_combinations(doc)->
+ ["Test schema loaction and extra_db_nodes combinations."];
+schema_location_and_extra_db_nodes_combinations(suite) -> [];
+schema_location_and_extra_db_nodes_combinations(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?init(2, Config),
+ ?match(ok, mnesia:create_schema([N1])),
+ ?match([], mnesia_test_lib:start_mnesia([N1])),
+
+ %% Really should use test_lib:mnesia_start for these ones but ...
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram},
+ {extra_db_nodes, [N1]}]])),
+
+ %% Assure tables loaded
+ ?match({[ok, ok], []},
+ rpc:multicall([N1, N2], mnesia, wait_for_tables,
+ [[schema], 10000])),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(2, Config),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+table_load_to_disc_less_nodes(doc)->
+ ["Load tables to disc less nodes"];
+table_load_to_disc_less_nodes(suite) -> [];
+table_load_to_disc_less_nodes(Config) when is_list(Config) ->
+ [N1, N2] = ?init(2, Config),
+
+ ?match(ok, rpc:call(N1, mnesia, start, [[{schema_location, ram}]])),
+
+ %% Really should use test_lib:mnesia_start for these ones but ...
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia,add_table_copy, [schema, N2, ram_copies])),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{schema_location, ram},
+ {extra_db_nodes, [N1]}]])),
+
+ %% Now create some tables
+ ?match({atomic,ok},
+ rpc:call(
+ N1, mnesia,create_table, [test_table,
+ [%%{disc_copies, [node()]},
+ {ram_copies, [N1, N2]},
+ {attributes,
+ record_info(fields,test_table)}]])),
+
+ %% Assure tables loaded
+ ?match({[ok, ok], []},
+ rpc:multicall([N1, N2], mnesia, wait_for_tables,
+ [[test_table], 1000])),
+
+ %% Write something on one end ...
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:write(#test_table{i=44}) end])),
+
+ %% Force synchronicity
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:write_lock_table(test_table) end])),
+
+ %% ... and read it in the others
+ ?match({atomic, [{test_table, 44, _, _, _}]},
+ rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:read({test_table, 44}) end])),
+
+ ?cleanup(2, Config),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+schema_merge(doc) ->
+ ["Provoke various schema merge situations.",
+ "Perform various schema updates while some nodes are down,",
+ "stop the started nodes, start the stopped nodes and perform",
+ "schema updates. Now we have a situation were some of the table",
+ "definitions have been changed on two or more nodes independently",
+ "of each other and when Mnesia on the nodes tries to connect",
+ "to each other at restart the schema will be merged.",
+ "Do also try to provoke schema merge situations were the",
+ "schema cannot be merged."];
+
+schema_merge(suite) -> [];
+
+schema_merge(Config) when is_list(Config) ->
+ [N1, N2]=Nodes=?acquire(2,Config),
+
+ mnesia_test_lib:kill_mnesia([N2]),
+ receive after 1000 -> ok end,
+
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ ?match({atomic,ok},
+ rpc:call(
+ N1, mnesia,create_table,
+ [test_table,
+ [{Storage, [N1]},
+ {attributes,
+ record_info(fields,test_table)}]])),
+
+ ?match({atomic, ok},
+ rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:write(#test_table{i=44}) end])),
+
+ mnesia_test_lib:kill_mnesia([N1]),
+ receive after 2000 -> ok end,
+ %% Can't use std start because it waits for schema
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+
+ ?match({atomic,ok},
+ rpc:call(
+ N2, mnesia,create_table,
+ [test_table2,
+ [{Storage, [N2]},
+ {attributes,
+ record_info(fields,test_table2)}]])),
+
+ receive after 5000 -> ok end,
+
+ ?match({atomic, ok},
+ rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:write(#test_table2{i=33}) end])),
+
+ %% Can't use std start because it waits for schema
+ ?match(ok, rpc:call(N1, mnesia, start, [])),
+
+ %% Assure tables loaded
+ ?match({[ok, ok], []},
+ rpc:multicall([N1, N2], mnesia, wait_for_tables,
+ [[schema, test_table, test_table2], 10000])),
+
+ %% ... and read it in the others
+ ?match({[{atomic, [{test_table, 44, _, _, _}]},
+ {atomic, [{test_table, 44, _, _, _}]}], []},
+ rpc:multicall([N1, N2], mnesia, transaction,
+ [fun() -> mnesia:read({test_table, 44}) end])),
+
+ ?match({[{atomic, [{test_table2, 33, _}]},
+ {atomic, [{test_table2, 33, _}]}], []},
+ rpc:multicall([N1, N2], mnesia, transaction,
+ [fun() -> mnesia:read({test_table2, 33}) end])),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(2, Config),
+ ok.
+
+
+-define(connect(Nodes), mnesia:change_config(extra_db_nodes, Nodes)).
+-define(rpc_connect(From, Nodes),
+ rpc:call(From, mnesia, change_config, [extra_db_nodes, Nodes])).
+
+
+sort({ok, NS}) ->
+ {ok, lists:sort(NS)};
+sort(Ns) when is_tuple(Ns) ->
+ Ns;
+sort(NS) when is_list(NS) ->
+ lists:sort(NS).
+
+
+dynamic_connect(doc) ->
+ ["Test the new functionality where we start mnesia first and then "
+ "connect to the other mnesia nodes"];
+dynamic_connect(suite) ->
+ [
+ dynamic_basic,
+ dynamic_ext,
+ dynamic_bad
+ ].
+
+
+dynamic_basic(suite) -> [];
+dynamic_basic(Config) when is_list(Config) ->
+ Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config),
+ SNs = lists:sort(Nodes),
+
+ ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes--[N1]}, {disc_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])),
+
+ ?match({ok, SNs}, sort(?rpc_connect(N1, Nodes))), %% What shall happen?
+ ?match({ok, []}, sort(?rpc_connect(N1, [nonode@nothosted]))), %% What shall happen?
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ ?match(ok, mnesia:delete_schema([N2])),
+
+ ?match(ok, mnesia:dirty_write({tab1, 1, 1})),
+ ?match(ok, mnesia:dirty_write({tab2, 1, 1})),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2],5000])),
+ io:format("Here ~p ~n",[?LINE]),
+ check_storage(N2, N1, [N3]),
+ ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+ ?match(ok, mnesia:delete_schema([N3])),
+
+ io:format("T1 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]),
+ ?match(ok, rpc:call(N3, mnesia, start, [])),
+ io:format("T2 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]),
+ timer:sleep(2000),
+ io:format("T3 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]),
+ ?match({ok, [N1]}, sort(?rpc_connect(N3, [N1]))),
+ io:format("T4 ~p ~n",[rpc:call(N3,?MODULE,c_nodes,[])]),
+ ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])),
+ io:format("Here ~p ~n",[?LINE]),
+ check_storage(N3, N1, [N2]),
+ ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+ ?match(ok, mnesia:delete_schema([N3])),
+
+ ?match(ok, rpc:call(N3, mnesia, start, [])),
+ ?match({ok, [N3]}, sort(?rpc_connect(N1, [N3]))),
+ ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[tab1,tab2],5000])),
+ io:format("Here ~p ~n",[?LINE]),
+ check_storage(N3, N1, [N2]),
+ ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match(ok, mnesia:delete_schema([N2])),
+ ?match({atomic, ok}, mnesia:del_table_copy(schema, N2)),
+
+ % Ok, we have now removed references to node N2 from the other nodes
+ % mnesia should come up now.
+ ?match({atomic, ok}, mnesia:add_table_copy(tab1, N2, ram_copies)),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match({ok, _}, sort(?rpc_connect(N2, [N3]))),
+
+ ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))),
+
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])),
+ ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])),
+
+ mnesia_test_lib:kill_mnesia([N2]),
+
+ %%% SYNC!!!
+ timer:sleep(1000),
+
+ ?match([N3,N1], sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))),
+ ?match([N3,N1], sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match({ok, _}, sort(?rpc_connect(N3, [N2]))),
+
+ ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))),
+
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])),
+ ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])),
+
+ ?verify_mnesia(Nodes, []),
+%% ?cleanup(3, Config).
+ ok.
+
+c_nodes() ->
+ {mnesia_lib:val({current, db_nodes}),mnesia_lib:val(recover_nodes)}.
+
+
+dynamic_ext(suite) -> [];
+dynamic_ext(Config) when is_list(Config) ->
+ Ns = [N1,N2] = ?acquire_nodes(2, Config),
+ SNs = lists:sort([N1,N2]),
+
+ ?match({atomic, ok}, mnesia:create_table(tab0, [{disc_copies, [N1,N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, [N2]}])),
+
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match(ok, mnesia:delete_schema([N2])),
+ ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes, [N1]}]])),
+
+ ?match(SNs, sort(rpc:call(N1, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0,tab1,tab2,tab3], 2000])),
+
+ Check = fun({Tab,Storage}) ->
+ ?match(Storage, rpc:call(N2, mnesia, table_info, [Tab, storage_type])),
+ ?match([{N2,Storage}],
+ lists:sort(rpc:call(N2, mnesia, table_info, [Tab, where_to_commit])))
+ end,
+ [Check(Test) || Test <- [{tab1, ram_copies},{tab2, disc_copies},{tab3, disc_only_copies}]],
+
+ T = now(),
+ ?match(ok, mnesia:dirty_write({tab0, 42, T})),
+ ?match(ok, mnesia:dirty_write({tab1, 42, T})),
+ ?match(ok, mnesia:dirty_write({tab2, 42, T})),
+ ?match(ok, mnesia:dirty_write({tab3, 42, T})),
+
+ ?match(stopped, rpc:call(N2, mnesia, stop, [])),
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+ ?match(ok, mnesia:wait_for_tables([tab0,tab1,tab2,tab3], 10000)),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1,tab2,tab3], 100])),
+ ?match([], mnesia:dirty_read({tab1, 41})),
+ ?match([{tab2,42,T}], mnesia:dirty_read({tab2, 42})),
+ ?match([{tab3,42,T}], mnesia:dirty_read({tab3, 42})),
+
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match(ok, mnesia:delete_schema([N2])),
+
+ ?match(stopped, rpc:call(N1, mnesia, stop, [])),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])),
+ ?match({timeout,[tab0]}, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 500])),
+
+ ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])),
+ ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])),
+ ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})),
+ ?match([{tab0,42,T}], rpc:call(N2, mnesia,dirty_read,[{tab0,42}])),
+
+ ?match(stopped, rpc:call(N1, mnesia, stop, [])),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match(ok, mnesia:delete_schema([N2])),
+ ?match(ok, rpc:call(N1, mnesia, start, [[{extra_db_nodes, [N1,N2]}]])),
+ ?match({timeout,[tab0]}, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 500])),
+
+ ?match(ok, rpc:call(N2, mnesia, start, [[{extra_db_nodes,[N1,N2]}]])),
+ ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[tab0], 1500])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab0], 1500])),
+ ?match([{tab0,42,T}], mnesia:dirty_read({tab0, 42})),
+ ?match([{tab0,42,T}], rpc:call(N2,mnesia,dirty_read,[{tab0,42}])),
+
+ ?verify_mnesia(Ns, []),
+ ok.
+
+check_storage(Me, Orig, Other) ->
+ io:format("Nodes ~p ~p ~p~n",[Me,Orig,Other]),
+ rpc:multicall(Other, sys, status, [mnesia_locker]),
+ rpc:call(Me, sys, status, [mnesia_locker]),
+ rpc:call(Orig, sys, status, [mnesia_locker]),
+ rpc:multicall(Other, sys, status, [mnesia_controller]),
+ rpc:call(Me, sys, status, [mnesia_controller]),
+ rpc:call(Orig, sys, status, [mnesia_controller]),
+ %% Verify disc_copies
+ W2C = lists:sort([{Node,disc_copies} || Node <- [Me,Orig|Other]]),
+ W2W = lists:sort([Me,Orig|Other]),
+ ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [schema, storage_type])),
+ ?match(disc_copies, rpc:call(Me, mnesia, table_info, [schema, storage_type])),
+ ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [schema, where_to_commit]))),
+ ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [schema, where_to_commit]))),
+
+ ?match(disc_copies, rpc:call(Orig, mnesia, table_info, [tab2, storage_type])),
+ ?match(disc_copies, rpc:call(Me, mnesia, table_info, [tab2, storage_type])),
+ ?match(W2W, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_write]))),
+ ?match(Me, rpc:call(Me, mnesia, table_info, [tab2, where_to_read])),
+
+ ?match(W2C, lists:sort(rpc:call(Orig, mnesia, table_info, [tab2, where_to_commit]))),
+ ?match(W2C, lists:sort(rpc:call(Me, mnesia, table_info, [tab2, where_to_commit]))),
+
+ ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)),
+ ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)),
+ ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])),
+ ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])),
+
+ ?match(true, rpc:call(Me, mnesia_monitor, use_dir, [])),
+ ?match(disc_copies, rpc:call(Me, mnesia_lib, val, [{schema, storage_type}])),
+
+ mnesia_test_lib:kill_mnesia([Orig]),
+ mnesia_test_lib:kill_mnesia(Other),
+ T = now(),
+ ?match(ok, rpc:call(Me, mnesia, dirty_write, [{tab2, 42, T}])),
+ ?match(stopped, rpc:call(Me, mnesia, stop, [])),
+ ?match(ok, rpc:call(Me, mnesia, start, [])),
+ ?match([], mnesia_test_lib:start_mnesia([Orig|Other], [tab1,tab2])),
+ ?match([{tab2,42,T}], rpc:call(Me, mnesia, dirty_read, [{tab2, 42}])),
+ ?match([{tab2,42,T}], rpc:call(Orig, mnesia, dirty_read, [{tab2, 42}])),
+
+ ?match([{tab1,1,1}], mnesia:dirty_read(tab1,1)),
+ ?match([{tab2,1,1}], mnesia:dirty_read(tab2,1)),
+ ?match([{tab1,1,1}], rpc:call(Me, mnesia, dirty_read, [tab1,1])),
+ ?match([{tab2,1,1}], rpc:call(Me, mnesia, dirty_read, [tab2,1])),
+ ok.
+
+
+dynamic_bad(suite) -> [];
+dynamic_bad(Config) when is_list(Config) ->
+ Ns = [N1, N2, N3] = ?acquire_nodes(3, Config),
+ SNs = lists:sort([N2,N3]),
+
+ ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)),
+ ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)),
+ ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Ns -- [N1]},
+ {disc_copies, [N1]}])),
+ ?match(ok, mnesia:dirty_write({tab1, 1, 1})),
+
+ mnesia_test_lib:kill_mnesia(Ns),
+ ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])),
+ ?match({ok, [N2]}, ?rpc_connect(N3, [N2])),
+ ?match(SNs, sort(rpc:call(N2, mnesia, system_info, [running_db_nodes]))),
+ ?match(SNs, sort(rpc:call(N3, mnesia, system_info, [running_db_nodes]))),
+ ?match({badrpc, {'EXIT', {aborted, {no_exists, _, _}}}},
+ rpc:call(N2, mnesia, table_info, [tab1, where_to_read])),
+
+ ?match(ok, mnesia:start()),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[tab1], 1000])),
+ ?match(N2, rpc:call(N2, mnesia, table_info, [tab1, where_to_read])),
+ ?match([{tab1, 1, 1}], rpc:call(N2, mnesia, dirty_read, [tab1, 1])),
+
+ mnesia_test_lib:kill_mnesia(Ns),
+ ?match({[ok, ok], []}, rpc:multicall(Ns -- [N1], mnesia, start, [])),
+ ?match({ok, [N2]}, ?rpc_connect(N3, [N2])),
+ % Make a merge conflict
+ ?match({atomic, ok}, rpc:call(N3, mnesia, create_table, [tab1, []])),
+
+ io:format("We expect a mnesia crash here~n", []),
+ ?match({error,{_, _}}, mnesia:start()),
+
+ ?verify_mnesia(Ns -- [N1], []),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+unknown_config(doc) ->
+ ["Try some unknown configuration parameters and see that expected",
+ "things happens."];
+unknown_config(suite)-> [];
+unknown_config(Config) when is_list(Config) ->
+ ?init(1, Config),
+ %% NOTE: case 1 & 2 below do not respond the same
+ ?match({error, Res} when element(1, Res) == bad_type,
+ mnesia:start([{undefined_config,[]}])),
+ %% Below does not work, but the "correct" behaviour would be to have
+ %% case 1 above to behave as the one below.
+
+ %% in mnesia-1.3 {error,{bad_type,{[],undefined_config}}}
+ ?match({error, Res} when element(1, Res) == bad_type,
+ mnesia:start([{[],undefined_config}])),
+ ?cleanup(1, Config),
+ ok.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+inconsistent_database(doc) ->
+ ["Replace the event module with another module and use it as",
+ "receiver of the various system and table events. Provoke",
+ "coverage of all kinds of events."];
+inconsistent_database(suite) -> [];
+inconsistent_database(Config) when is_list(Config) ->
+ Nodes = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]}],
+ 2, Config, ?FILE, ?LINE),
+ KillAfter = length(Nodes) * timer:minutes(5),
+ ?acquire_schema(2, Config ++ [{tc_timeout, KillAfter}]),
+
+ Ok = [ok || _N <- Nodes],
+ StartArgs = [{event_module, mnesia_inconsistent_database_test}],
+ ?match({Ok, []}, rpc:multicall(Nodes, mnesia, start, [StartArgs])),
+ ?match([], mnesia_test_lib:kill_mnesia(Nodes)),
+
+ ?match(ok, mnesia_meter:go(ram_copies, Nodes)),
+
+ mnesia_test_lib:reload_appls([mnesia], Nodes),
+ ok.
+
diff --git a/lib/mnesia/test/mnesia_consistency_test.erl b/lib/mnesia/test/mnesia_consistency_test.erl
new file mode 100644
index 0000000000..ffe8ab7ac3
--- /dev/null
+++ b/lib/mnesia/test/mnesia_consistency_test.erl
@@ -0,0 +1,1612 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(mnesia_consistency_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 transaction consistency",
+ "Consistency is the property of the application that requires any",
+ "execution of the transaction to take the database from one",
+ "consistent state to another. Verify that the database is",
+ "consistent at any point in time.",
+ "Verify for various configurations.",
+ " Verify for both set and bag"];
+all(suite) ->
+ [
+ consistency_after_restart,
+ consistency_after_dump_tables,
+ consistency_after_add_replica,
+ consistency_after_del_replica,
+ consistency_after_move_replica,
+ consistency_after_transform_table,
+ consistency_after_change_table_copy_type,
+ consistency_after_fallback,
+ consistency_after_restore,
+ consistency_after_rename_of_node,
+ checkpoint_retainer_consistency,
+ backup_consistency
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% stolen from mnesia_tpcb.erl:
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Account record, total size must be at least 100 bytes
+
+-define(ACCOUNT_FILLER,
+ {123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234}).
+
+-record(account,
+ {
+ id = 0, %% Unique account id
+ branch_id = 0, %% Branch where the account is held
+ balance = 0, %% Account balance
+ filler = ?ACCOUNT_FILLER %% Gap filler to ensure size >= 100 bytes
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Branch record, total size must be at least 100 bytes
+
+-define(BRANCH_FILLER,
+ {123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Teller record, total size must be at least 100 bytes
+
+-define(TELLER_FILLER,
+ {123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890,
+ 1234567890123456789012345678901234567890123456789012345678}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% History record, total size must be at least 50 bytes
+
+-define(HISTORY_FILLER, 1234567890).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record(tab_config,
+ {
+ db_nodes = [node()],
+ replica_nodes = [node()],
+ replica_type = ram_copies,
+ use_running_mnesia = false,
+ n_branches = 1,
+ n_tellers_per_branch = 10, %% Must be 10
+ n_accounts_per_branch = 100000, %% Must be 100000
+ branch_filler = ?BRANCH_FILLER,
+ account_filler = ?ACCOUNT_FILLER,
+ teller_filler = ?TELLER_FILLER
+ }).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% stolen from mnesia_tpcb.erl:
+
+list2rec(List, Fields, DefaultTuple) ->
+ [Name|Defaults] = tuple_to_list(DefaultTuple),
+ List2 = list2rec(List, Fields, Defaults, []),
+ list_to_tuple([Name] ++ List2).
+
+list2rec(_List, [], [], Acc) ->
+ Acc;
+list2rec(List, [F|Fields], [D|Defaults], Acc) ->
+ {Val, List2} =
+ case lists:keysearch(F, 1, List) of
+ false ->
+ {D, List};
+ {value, {F, NewVal}} ->
+ {NewVal, lists:keydelete(F, 1, List)}
+ end,
+ list2rec(List2, Fields, Defaults, Acc ++ [Val]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+tpcb_config(ReplicaType, _NodeConfig, Nodes, NoDriverNodes) ->
+ [{n_branches, 10},
+ {n_drivers_per_node, 10},
+ {replica_nodes, Nodes},
+ {driver_nodes, Nodes -- NoDriverNodes},
+ {use_running_mnesia, true},
+ {report_interval, infinity},
+ {n_accounts_per_branch, 100},
+ {replica_type, ReplicaType},
+ {reuse_history_id, true}].
+
+%% Stolen from mnesia_tpcb:dist
+tpcb_config_dist(ReplicaType, _NodeConfig, Nodes, _Config) ->
+ [{db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {replica_nodes, Nodes},
+ {n_drivers_per_node, 10},
+ {n_branches, 1},
+ {use_running_mnesia, true},
+ {n_accounts_per_branch, 10},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(15)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% stolen from mnesia_recovery_test.erl:
+
+receive_messages([]) -> [];
+receive_messages(ListOfMsgs) ->
+ receive
+ {Pid, Msg} ->
+ case lists:member(Msg, ListOfMsgs) of
+ false ->
+ ?warning("I (~p) have received unexpected msg~n ~p ~n",
+ [self(),{Pid, Msg}]),
+ receive_messages(ListOfMsgs);
+ true ->
+ ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]),
+ [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])]
+ end;
+ Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else])
+ after timer:minutes(3) ->
+ ?error("Timeout in receive msgs while waiting for ~p~n",
+ [ListOfMsgs])
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_restart(suite) ->
+ [
+ consistency_after_restart_1_ram,
+ consistency_after_restart_1_disc,
+ consistency_after_restart_1_disc_only,
+ consistency_after_restart_2_ram,
+ consistency_after_restart_2_disc,
+ consistency_after_restart_2_disc_only
+ ].
+
+consistency_after_restart_1_ram(suite) -> [];
+consistency_after_restart_1_ram(Config) when is_list(Config) ->
+ consistency_after_restart(ram_copies, 2, Config).
+
+consistency_after_restart_1_disc(suite) -> [];
+consistency_after_restart_1_disc(Config) when is_list(Config) ->
+ consistency_after_restart(disc_copies, 2, Config).
+
+consistency_after_restart_1_disc_only(suite) -> [];
+consistency_after_restart_1_disc_only(Config) when is_list(Config) ->
+ consistency_after_restart(disc_only_copies, 2, Config).
+
+consistency_after_restart_2_ram(suite) -> [];
+consistency_after_restart_2_ram(Config) when is_list(Config) ->
+ consistency_after_restart(ram_copies, 3, Config).
+
+consistency_after_restart_2_disc(suite) -> [];
+consistency_after_restart_2_disc(Config) when is_list(Config) ->
+ consistency_after_restart(disc_copies, 3, Config).
+
+consistency_after_restart_2_disc_only(suite) -> [];
+consistency_after_restart_2_disc_only(Config) when is_list(Config) ->
+ consistency_after_restart(disc_only_copies, 3, Config).
+
+consistency_after_restart(ReplicaType, NodeConfig, Config) ->
+ [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config),
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("consistency_after_restart with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, [Node1]),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun () -> mnesia_tpcb:run(TpcbConfig) end,
+ timer:sleep(timer:seconds(10)),
+ mnesia_test_lib:kill_mnesia([Node1]),
+ %% Start and wait for tables to be loaded on all nodes
+ timer:sleep(timer:seconds(3)),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history])),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_dump_tables(suite) ->
+ [
+ consistency_after_dump_tables_1_ram,
+ consistency_after_dump_tables_2_ram
+ ].
+
+consistency_after_dump_tables_1_ram(suite) -> [];
+consistency_after_dump_tables_1_ram(Config) when is_list(Config) ->
+ consistency_after_dump_tables(ram_copies, 1, Config).
+
+consistency_after_dump_tables_2_ram(suite) -> [];
+consistency_after_dump_tables_2_ram(Config) when is_list(Config) ->
+ consistency_after_dump_tables(ram_copies, 2, Config).
+
+consistency_after_dump_tables(ReplicaType, NodeConfig, Config) ->
+ [Node1 | _] = Nodes = ?acquire_nodes(NodeConfig, Config),
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("consistency_after_dump_tables with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun() -> mnesia_tpcb:run(TpcbConfig) end,
+ timer:sleep(timer:seconds(10)),
+ ?match({atomic, ok}, rpc:call(Node1, mnesia, dump_tables,
+ [[branch, teller, account, history]])),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+
+ mnesia_test_lib:kill_mnesia(Nodes),
+ timer:sleep(timer:seconds(1)),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes,[account, branch,
+ teller, history])),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_add_replica(suite) ->
+ [
+ consistency_after_add_replica_2_ram,
+ consistency_after_add_replica_2_disc,
+ consistency_after_add_replica_2_disc_only,
+ consistency_after_add_replica_3_ram,
+ consistency_after_add_replica_3_disc,
+ consistency_after_add_replica_3_disc_only
+ ].
+
+consistency_after_add_replica_2_ram(suite) -> [];
+consistency_after_add_replica_2_ram(Config) when is_list(Config) ->
+ consistency_after_add_replica(ram_copies, 2, Config).
+
+consistency_after_add_replica_2_disc(suite) -> [];
+consistency_after_add_replica_2_disc(Config) when is_list(Config) ->
+ consistency_after_add_replica(disc_copies, 2, Config).
+
+consistency_after_add_replica_2_disc_only(suite) -> [];
+consistency_after_add_replica_2_disc_only(Config) when is_list(Config) ->
+ consistency_after_add_replica(disc_only_copies, 2, Config).
+
+consistency_after_add_replica_3_ram(suite) -> [];
+consistency_after_add_replica_3_ram(Config) when is_list(Config) ->
+ consistency_after_add_replica(ram_copies, 3, Config).
+
+consistency_after_add_replica_3_disc(suite) -> [];
+consistency_after_add_replica_3_disc(Config) when is_list(Config) ->
+ consistency_after_add_replica(disc_copies, 3, Config).
+
+consistency_after_add_replica_3_disc_only(suite) -> [];
+consistency_after_add_replica_3_disc_only(Config) when is_list(Config) ->
+ consistency_after_add_replica(disc_only_copies, 3, Config).
+
+consistency_after_add_replica(ReplicaType, NodeConfig, Config) ->
+ Nodes0 = ?acquire_nodes(NodeConfig, Config),
+ AddNode = lists:last(Nodes0),
+ Nodes = Nodes0 -- [AddNode],
+ Node1 = hd(Nodes),
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("consistency_after_add_replica with ~p on ~p~n",
+ [ReplicaType, Nodes0]),
+ TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun () -> mnesia_tpcb:run(TpcbConfig) end,
+ timer:sleep(timer:seconds(10)),
+ ?match({atomic, ok}, mnesia:add_table_copy(account, AddNode, ReplicaType)),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ ?verify_mnesia(Nodes0, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_del_replica(suite) ->
+ [
+ consistency_after_del_replica_2_ram,
+ consistency_after_del_replica_2_disc,
+ consistency_after_del_replica_2_disc_only,
+ consistency_after_del_replica_3_ram,
+ consistency_after_del_replica_3_disc,
+ consistency_after_del_replica_3_disc_only
+ ].
+
+consistency_after_del_replica_2_ram(suite) -> [];
+consistency_after_del_replica_2_ram(Config) when is_list(Config) ->
+ consistency_after_del_replica(ram_copies, 2, Config).
+
+consistency_after_del_replica_2_disc(suite) -> [];
+consistency_after_del_replica_2_disc(Config) when is_list(Config) ->
+ consistency_after_del_replica(disc_copies, 2, Config).
+
+consistency_after_del_replica_2_disc_only(suite) -> [];
+consistency_after_del_replica_2_disc_only(Config) when is_list(Config) ->
+ consistency_after_del_replica(disc_only_copies, 2, Config).
+
+consistency_after_del_replica_3_ram(suite) -> [];
+consistency_after_del_replica_3_ram(Config) when is_list(Config) ->
+ consistency_after_del_replica(ram_copies, 3, Config).
+
+consistency_after_del_replica_3_disc(suite) -> [];
+consistency_after_del_replica_3_disc(Config) when is_list(Config) ->
+ consistency_after_del_replica(disc_copies, 3, Config).
+
+consistency_after_del_replica_3_disc_only(suite) -> [];
+consistency_after_del_replica_3_disc_only(Config) when is_list(Config) ->
+ consistency_after_del_replica(disc_only_copies, 3, Config).
+
+consistency_after_del_replica(ReplicaType, NodeConfig, Config) ->
+ Nodes = ?acquire_nodes(NodeConfig, Config),
+ Node1 = hd(Nodes),
+ Node2 = lists:last(Nodes),
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("consistency_after_del_replica with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun () -> mnesia_tpcb:run(TpcbConfig) end,
+ timer:sleep(timer:seconds(10)),
+ ?match({atomic, ok}, mnesia:del_table_copy(account, Node2)),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_move_replica(suite) ->
+ [
+ consistency_after_move_replica_2_ram,
+ consistency_after_move_replica_2_disc,
+ consistency_after_move_replica_2_disc_only,
+ consistency_after_move_replica_3_ram,
+ consistency_after_move_replica_3_disc,
+ consistency_after_move_replica_3_disc_only
+ ].
+
+consistency_after_move_replica_2_ram(suite) -> [];
+consistency_after_move_replica_2_ram(Config) when is_list(Config) ->
+ consistency_after_move_replica(ram_copies, 2, Config).
+
+consistency_after_move_replica_2_disc(suite) -> [];
+consistency_after_move_replica_2_disc(Config) when is_list(Config) ->
+ consistency_after_move_replica(disc_copies, 2, Config).
+
+consistency_after_move_replica_2_disc_only(suite) -> [];
+consistency_after_move_replica_2_disc_only(Config) when is_list(Config) ->
+ consistency_after_move_replica(disc_only_copies, 2, Config).
+
+consistency_after_move_replica_3_ram(suite) -> [];
+consistency_after_move_replica_3_ram(Config) when is_list(Config) ->
+ consistency_after_move_replica(ram_copies, 3, Config).
+
+consistency_after_move_replica_3_disc(suite) -> [];
+consistency_after_move_replica_3_disc(Config) when is_list(Config) ->
+ consistency_after_move_replica(disc_copies, 3, Config).
+
+consistency_after_move_replica_3_disc_only(suite) -> [];
+consistency_after_move_replica_3_disc_only(Config) when is_list(Config) ->
+ consistency_after_move_replica(disc_only_copies, 3, Config).
+
+consistency_after_move_replica(ReplicaType, NodeConfig, Config) ->
+ Nodes = ?acquire_nodes(NodeConfig, Config ++ [{tc_timeout, timer:minutes(10)}]),
+ Node1 = hd(Nodes),
+ Node2 = lists:last(Nodes),
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("consistency_after_move_replica with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes -- [Node2], []),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun () -> mnesia_tpcb:run(TpcbConfig) end,
+ timer:sleep(timer:seconds(10)),
+ ?match({atomic, ok}, mnesia:move_table_copy(account, Node1, Node2)),
+ ?log("First move completed from node ~p to ~p ~n", [Node1, Node2]),
+ ?match({atomic, ok}, mnesia:move_table_copy(account, Node2, Node1)),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_transform_table(doc) ->
+ ["Check that the database is consistent after transform_table.",
+ " While applications are updating the involved tables. "];
+
+consistency_after_transform_table(suite) ->
+ [
+ consistency_after_transform_table_ram,
+ consistency_after_transform_table_disc,
+ consistency_after_transform_table_disc_only
+ ].
+
+
+consistency_after_transform_table_ram(suite) -> [];
+consistency_after_transform_table_ram(Config) when is_list(Config) ->
+ consistency_after_transform_table(ram_copies, Config).
+
+consistency_after_transform_table_disc(suite) -> [];
+consistency_after_transform_table_disc(Config) when is_list(Config) ->
+ consistency_after_transform_table(disc_copies, Config).
+
+consistency_after_transform_table_disc_only(suite) -> [];
+consistency_after_transform_table_disc_only(Config) when is_list(Config) ->
+ consistency_after_transform_table(disc_only_copies, Config).
+
+consistency_after_transform_table(Type, Config) ->
+ Nodes = [N1, N2,_N3] = ?acquire_nodes(3, Config),
+
+ ?match({atomic, ok}, mnesia:create_table(tab1, [{index, [3]}, {Type, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab2, [{index, [3]}, {Type, [N1,N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab3, [{index, [3]}, {Type, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(empty, [{index, [3]},{Type, Nodes}])),
+
+ Tabs = lists:sort([tab1, tab2, tab3, empty]),
+
+ [[mnesia:dirty_write({Tab, N, N}) || N <- lists:seq(1,10)] ||
+ Tab <- Tabs -- [empty, tab4]],
+ mnesia:dump_log(),
+
+ Ok = lists:duplicate(4, {atomic, ok}),
+ ?match(Ok, [mnesia:transform_table(Tab, fun({T, N, N}) -> {T, N, N, ok} end,
+ [k,a,n]) || Tab <- Tabs]),
+ [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs],
+
+ Filter = fun(Tab) -> mnesia:foldl(fun(A, Acc) when size(A) == 3 -> [A|Acc];
+ (A, Acc) when size(A) == 4 -> Acc
+ end, [], Tab)
+ end,
+
+ ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]),
+
+ mnesia_test_lib:kill_mnesia(Nodes),
+ mnesia_test_lib:start_mnesia(Nodes, Tabs),
+
+ ?match([Tabs, Tabs, Tabs],
+ [lists:sort(rpc:call(Node, mnesia,system_info, [tables]) -- [schema]) || Node <- Nodes]),
+
+ ?match([[],[],[],[]], [element(2,mnesia:transaction(Filter, [Tab])) || Tab <- Tabs]),
+ [?match([k,a,n], mnesia:table_info(Tab, attributes)) || Tab <- Tabs],
+
+ ?verify_mnesia(Nodes, []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_change_table_copy_type(doc) ->
+ ["Check that the database is consistent after change of copy type.",
+ " While applications are updating the involved tables. "].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_fallback(doc) ->
+ ["Check that installed fallbacks are consistent. Check this by starting ",
+ "some nodes, run tpcb on them, take a backup at any time, install it ",
+ "as a fallback, kill all nodes, start mnesia again and check for ",
+ "any inconsistencies"];
+consistency_after_fallback(suite) ->
+ [
+ consistency_after_fallback_2_ram,
+ consistency_after_fallback_2_disc,
+ consistency_after_fallback_2_disc_only,
+ consistency_after_fallback_3_ram,
+ consistency_after_fallback_3_disc
+ , consistency_after_fallback_3_disc_only
+ ].
+
+consistency_after_fallback_2_ram(suite) -> [];
+consistency_after_fallback_2_ram(Config) when is_list(Config) ->
+ consistency_after_fallback(ram_copies, 2, Config).
+
+consistency_after_fallback_2_disc(suite) -> [];
+consistency_after_fallback_2_disc(Config) when is_list(Config) ->
+ consistency_after_fallback(disc_copies, 2, Config).
+
+consistency_after_fallback_2_disc_only(suite) -> [];
+consistency_after_fallback_2_disc_only(Config) when is_list(Config) ->
+ consistency_after_fallback(disc_only_copies, 2, Config).
+
+consistency_after_fallback_3_ram(suite) -> [];
+consistency_after_fallback_3_ram(Config) when is_list(Config) ->
+ consistency_after_fallback(ram_copies, 3, Config).
+
+consistency_after_fallback_3_disc(suite) -> [];
+consistency_after_fallback_3_disc(Config) when is_list(Config) ->
+ consistency_after_fallback(disc_copies, 3, Config).
+
+consistency_after_fallback_3_disc_only(suite) -> [];
+consistency_after_fallback_3_disc_only(Config) when is_list(Config) ->
+ consistency_after_fallback(disc_only_copies, 3, Config).
+
+consistency_after_fallback(ReplicaType, NodeConfig, Config) ->
+ %%?verbose("Starting consistency_after_fallback2 at ~p~n", [self()]),
+ Delay = 5,
+ Nodes = ?acquire_nodes(NodeConfig, [{tc_timeout, timer:minutes(10)} | Config]),
+ Node1 = hd(Nodes),
+ %%?verbose("Mnesia info: ~p~n", [mnesia:info()]),
+
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("consistency_after_fallback with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes, []),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun () -> mnesia_tpcb:run(TpcbConfig) end,
+ timer:sleep(timer:seconds(Delay)),
+
+ %% Make a backup
+ ?verbose("Doing backup~n", []),
+ ?match(ok, mnesia:backup(consistency_after_fallback2)),
+
+ %% Install the backup as a fallback
+ ?verbose("Doing fallback~n", []),
+ ?match(ok, mnesia:install_fallback(consistency_after_fallback2)),
+ timer:sleep(timer:seconds(Delay)),
+
+ %% Stop tpcb
+ ?verbose("Stopping TPC-B~n", []),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+
+ %% Stop and then start mnesia and check table consistency
+ %%?verbose("Restarting Mnesia~n", []),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller,history]),
+
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ if
+ ReplicaType == ram_copies ->
+ %% Test that change_table_copy work i.e. no account.dcd file exists.
+ ?match({atomic, ok}, mnesia:change_table_copy_type(account, node(), disc_copies));
+ true ->
+ ignore
+ end,
+ file:delete(consistency_after_fallback2),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_restore(doc) ->
+ ["Verify consistency after restore operations."];
+
+consistency_after_restore(suite) ->
+ [
+ consistency_after_restore_clear_ram,
+ consistency_after_restore_clear_disc,
+ consistency_after_restore_clear_disc_only,
+ consistency_after_restore_recreate_ram,
+ consistency_after_restore_recreate_disc,
+ consistency_after_restore_recreate_disc_only
+ ].
+
+consistency_after_restore_clear_ram(suite) -> [];
+consistency_after_restore_clear_ram(Config) when is_list(Config) ->
+ consistency_after_restore(ram_copies, clear_tables, Config).
+
+consistency_after_restore_clear_disc(suite) -> [];
+consistency_after_restore_clear_disc(Config) when is_list(Config) ->
+ consistency_after_restore(disc_copies, clear_tables, Config).
+
+consistency_after_restore_clear_disc_only(suite) -> [];
+consistency_after_restore_clear_disc_only(Config) when is_list(Config) ->
+ consistency_after_restore(disc_only_copies, clear_tables, Config).
+
+consistency_after_restore_recreate_ram(suite) -> [];
+consistency_after_restore_recreate_ram(Config) when is_list(Config) ->
+ consistency_after_restore(ram_copies, recreate_tables, Config).
+
+consistency_after_restore_recreate_disc(suite) -> [];
+consistency_after_restore_recreate_disc(Config) when is_list(Config) ->
+ consistency_after_restore(disc_copies, recreate_tables, Config).
+
+consistency_after_restore_recreate_disc_only(suite) -> [];
+consistency_after_restore_recreate_disc_only(Config) when is_list(Config) ->
+ consistency_after_restore(disc_only_copies, recreate_tables, Config).
+
+consistency_after_restore(ReplicaType, Op, Config) ->
+ Delay = 1,
+ Nodes = ?acquire_nodes(3, [{tc_timeout, timer:minutes(10)} | Config]),
+ [Node1, Node2, _Node3] = Nodes,
+ File = "cons_backup_restore",
+
+ ?log("consistency_after_restore with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ Tabs = [carA, carB, carC, carD],
+
+ ?match({atomic, ok}, mnesia:create_table(carA, [{ReplicaType, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(carB, [{ReplicaType, Nodes -- [Node1]}])),
+ ?match({atomic, ok}, mnesia:create_table(carC, [{ReplicaType, Nodes -- [Node2]}])),
+ ?match({atomic, ok}, mnesia:create_table(carD, [{ReplicaType, [Node2]}])),
+
+ NList = lists:seq(0, 20),
+ [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 1}) end, NList) ||
+ Tab <- Tabs],
+
+ {ok, Name, _} = mnesia:activate_checkpoint([{max, [schema | Tabs]},
+ {ram_overrides_dump, true}]),
+ ?verbose("Doing backup~n", []),
+ ?match(ok, mnesia:backup_checkpoint(Name, File)),
+ ?match(ok, mnesia:deactivate_checkpoint(Name)),
+
+ [lists:foreach(fun(E) -> ok = mnesia:dirty_write({Tab, E, 2}) end, NList) ||
+ Tab <- Tabs],
+
+ Pids1 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carA, Op]), ok} || _ <- lists:seq(1, 5)],
+ Pids2 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carB, Op]), ok} || _ <- lists:seq(1, 5)],
+ Pids3 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carC, Op]), ok} || _ <- lists:seq(1, 5)],
+ Pids4 = [{'EXIT', spawn_link(?MODULE, change_tab, [self(), carD, Op]), ok} || _ <- lists:seq(1, 5)],
+
+ AllPids = Pids1 ++ Pids2 ++ Pids3 ++ Pids4,
+
+ Restore = fun(F, Args) ->
+ case mnesia:restore(F, Args) of
+ {atomic, List} -> lists:sort(List);
+ Else -> Else
+ end
+ end,
+
+ timer:sleep(timer:seconds(Delay)), %% Let changers grab locks
+ ?verbose("Doing restore~n", []),
+ ?match(Tabs, Restore(File, [{default_op, Op}])),
+
+ timer:sleep(timer:seconds(Delay)), %% Let em die
+
+ ?match_multi_receive(AllPids),
+
+ case ?match(ok, restore_verify_tabs(Tabs)) of
+ {success, ok} ->
+ file:delete(File);
+ _ ->
+ {T, M, S} = time(),
+ File2 = ?flat_format("consistency_error~w~w~w.BUP", [T, M, S]),
+ file:rename(File, File2)
+ end,
+ ?verify_mnesia(Nodes, []).
+
+change_tab(Father, Tab, Test) ->
+ Key = random:uniform(20),
+ Update = fun() ->
+ case mnesia:read({Tab, Key}) of
+ [{Tab, Key, 1}] ->
+ quit;
+ [{Tab, Key, _N}] ->
+ mnesia:write({Tab, Key, 3})
+ end
+ end,
+ case mnesia:transaction(Update) of
+ {atomic, quit} ->
+ exit(ok);
+ {aborted, {no_exists, Tab}} when Test == recreate_tables ->%% I'll allow this
+ change_tab(Father, Tab, Test);
+ {atomic, ok} ->
+ change_tab(Father, Tab, Test)
+ end.
+
+restore_verify_tabs([Tab | R]) ->
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:foldl(fun({_, _, 1}, ok) ->
+ ok;
+ (Else, Acc) ->
+ [Else|Acc]
+ end, ok, Tab)
+ end)),
+ restore_verify_tabs(R);
+restore_verify_tabs([]) ->
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consistency_after_rename_of_node(doc) ->
+ ["Skipped because it is an unimportant case."].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+checkpoint_retainer_consistency(doc) ->
+ ["Verify that the contents of a checkpoint retainer has the expected",
+ "contents in various situations."];
+checkpoint_retainer_consistency(suite) ->
+ [
+ updates_during_checkpoint_activation,
+ updates_during_checkpoint_iteration,
+ load_table_with_activated_checkpoint,
+ add_table_copy_to_table_with_activated_checkpoint
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+updates_during_checkpoint_activation(doc) ->
+ ["Perform updates while the checkpoint getting activated",
+ "and verify that all checkpoint retainers associated with",
+ "different replicas of the same table really has the same",
+ "contents."];
+updates_during_checkpoint_activation(suite) ->
+ [
+ updates_during_checkpoint_activation_2_ram,
+ updates_during_checkpoint_activation_2_disc,
+ updates_during_checkpoint_activation_2_disc_only,
+ updates_during_checkpoint_activation_3_ram,
+ updates_during_checkpoint_activation_3_disc
+ , updates_during_checkpoint_activation_3_disc_only
+ ].
+
+updates_during_checkpoint_activation_2_ram(suite) -> [];
+updates_during_checkpoint_activation_2_ram(Config) when is_list(Config) ->
+ updates_during_checkpoint_activation(ram_copies, 2, Config).
+
+updates_during_checkpoint_activation_2_disc(suite) -> [];
+updates_during_checkpoint_activation_2_disc(Config) when is_list(Config) ->
+ updates_during_checkpoint_activation(disc_copies, 2, Config).
+
+updates_during_checkpoint_activation_2_disc_only(suite) -> [];
+updates_during_checkpoint_activation_2_disc_only(Config) when is_list(Config) ->
+ updates_during_checkpoint_activation(disc_only_copies, 2, Config).
+
+updates_during_checkpoint_activation_3_ram(suite) -> [];
+updates_during_checkpoint_activation_3_ram(Config) when is_list(Config) ->
+ updates_during_checkpoint_activation(ram_copies, 3, Config).
+
+updates_during_checkpoint_activation_3_disc(suite) -> [];
+updates_during_checkpoint_activation_3_disc(Config) when is_list(Config) ->
+ updates_during_checkpoint_activation(disc_copies, 3, Config).
+
+updates_during_checkpoint_activation_3_disc_only(suite) -> [];
+updates_during_checkpoint_activation_3_disc_only(Config) when is_list(Config) ->
+ updates_during_checkpoint_activation(disc_only_copies, 3, Config).
+
+updates_during_checkpoint_activation(ReplicaType,NodeConfig,Config) ->
+ %%?verbose("updates_during_checkpoint_activation2 at ~p~n", [self()]),
+ Delay = 5,
+ Nodes = ?acquire_nodes(NodeConfig, Config),
+ Node1 = hd(Nodes),
+ %%?verbose("Mnesia info: ~p~n", [mnesia:info()]),
+
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("consistency_after_fallback with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config),
+ %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun () -> mnesia_tpcb:run(TpcbConfig) end,
+ timer:sleep(timer:seconds(Delay)),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)}]),
+ timer:sleep(timer:seconds(Delay)),
+
+ %% Stop tpcb
+ ?verbose("Stopping TPC-B~n", []),
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+
+ ?match(ok, mnesia:backup_checkpoint(CPName,
+ updates_during_checkpoint_activation2)),
+ timer:sleep(timer:seconds(Delay)),
+
+ ?match(ok, mnesia:install_fallback(updates_during_checkpoint_activation2)),
+
+ %% Stop and then start mnesia and check table consistency
+ %%?verbose("Restarting Mnesia~n", []),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ file:delete(updates_during_checkpoint_activation2),
+ mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]),
+
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+updates_during_checkpoint_iteration(doc) ->
+ ["Perform updates while someone is iterating over a checkpoint",
+ "and verify that the iterator really finds the expected data",
+ "regardless of ongoing upates."];
+
+updates_during_checkpoint_iteration(suite) ->
+ [
+ updates_during_checkpoint_iteration_2_ram,
+ updates_during_checkpoint_iteration_2_disc
+ , updates_during_checkpoint_iteration_2_disc_only
+ ].
+
+updates_during_checkpoint_iteration_2_ram(suite) -> [];
+updates_during_checkpoint_iteration_2_ram(Config) when is_list(Config) ->
+ updates_during_checkpoint_iteration(ram_copies, 2, Config).
+
+updates_during_checkpoint_iteration_2_disc(suite) -> [];
+updates_during_checkpoint_iteration_2_disc(Config) when is_list(Config) ->
+ updates_during_checkpoint_iteration(disc_copies, 2, Config).
+
+updates_during_checkpoint_iteration_2_disc_only(suite) -> [];
+updates_during_checkpoint_iteration_2_disc_only(Config) when is_list(Config) ->
+ updates_during_checkpoint_iteration(disc_only_copies, 2, Config).
+
+updates_during_checkpoint_iteration(ReplicaType,NodeConfig,Config) ->
+ %?verbose("updates_during_checkpoint_iteration2 at ~p~n", [self()]),
+ Delay = 5,
+ Nodes = ?acquire_nodes(NodeConfig, Config),
+ Node1 = hd(Nodes),
+ %?verbose("Mnesia info: ~p~n", [mnesia:info()]),
+ File = updates_during_checkpoint_iteration2,
+ {success, [A]} = ?start_activities([Node1]),
+ ?log("updates_during_checkpoint_iteration with ~p on ~p~n",
+ [ReplicaType, Nodes]),
+ TpcbConfig = tpcb_config_dist(ReplicaType, NodeConfig, Nodes, Config),
+ %%TpcbConfig = tpcb_config(ReplicaType, NodeConfig, Nodes),
+ TpcbConfigRec = list2rec(TpcbConfig,
+ record_info(fields,tab_config),
+ #tab_config{}),
+ mnesia_tpcb:init(TpcbConfig),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)},
+ {ram_overrides_dump,true}]),
+ A ! fun () -> mnesia:backup_checkpoint(CPName, File) end,
+
+ do_changes_during_backup(TpcbConfigRec),
+
+ ?match_receive({A,ok}),
+
+ timer:sleep(timer:seconds(Delay)),
+ ?match(ok, mnesia:install_fallback(File)),
+ timer:sleep(timer:seconds(Delay)),
+
+ ?match({error,{"Bad balance",_,_}}, mnesia_tpcb:verify_tabs()),
+
+ mnesia_test_lib:kill_mnesia(Nodes),
+ mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller, history]),
+
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+
+ ?match(ok, file:delete(File)),
+ ?verify_mnesia(Nodes, []).
+
+do_changes_during_backup(TpcbConfig) ->
+ loop_branches(TpcbConfig#tab_config.n_branches,
+ TpcbConfig#tab_config.n_accounts_per_branch).
+
+loop_branches(N_br,N_acc) when N_br >= 1 ->
+ loop_accounts(N_br,N_acc),
+ loop_branches(N_br-1,N_acc);
+loop_branches(_,_) -> done.
+
+loop_accounts(N_br, N_acc) when N_acc >= 1 ->
+ A = #account{id=N_acc, branch_id=N_br, balance = 4711},
+ ok = mnesia:dirty_write(A),
+ loop_accounts(N_br, N_acc-1);
+
+loop_accounts(_,_) -> done.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+load_table_with_activated_checkpoint(doc) ->
+ ["Load a table with a checkpoint attached to it and verify that the",
+ "newly loaded replica also gets a checkpoint retainer attached to it",
+ "and that it is consistent with the original retainer."];
+
+load_table_with_activated_checkpoint(suite) ->
+ [
+ load_table_with_activated_checkpoint_ram,
+ load_table_with_activated_checkpoint_disc,
+ load_table_with_activated_checkpoint_disc_only
+ ].
+
+load_table_with_activated_checkpoint_ram(suite) -> [];
+load_table_with_activated_checkpoint_ram(Config) when is_list(Config) ->
+ load_table_with_activated_checkpoint(ram_copies, Config).
+
+load_table_with_activated_checkpoint_disc(suite) -> [];
+load_table_with_activated_checkpoint_disc(Config) when is_list(Config) ->
+ load_table_with_activated_checkpoint(disc_copies, Config).
+
+load_table_with_activated_checkpoint_disc_only(suite) -> [];
+load_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) ->
+ load_table_with_activated_checkpoint(disc_only_copies, Config).
+
+load_table_with_activated_checkpoint(Type, Config) ->
+ Nodes = ?acquire_nodes(2, Config),
+ Node1 = hd(Nodes),
+ Tab = load_test,
+ Def = [{attributes, [key, value]},
+ {Type, Nodes}], %% ??? important that RAM ???
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ timer:sleep(timer:seconds(1)),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)},
+ {ram_overrides_dump,true}]),
+
+ mnesia_test_lib:stop_mnesia([Node1]),
+ mnesia_test_lib:start_mnesia([Node1],[Tab]),
+ %%--- check, whether the checkpiont is attached to both replicas
+ {success, [A,B]} = ?start_activities(Nodes),
+
+ A ! fun () ->
+ mnesia:table_info(Tab,checkpoints)
+ end,
+ ?match_receive({A,[CPName]}),
+
+ B ! fun () ->
+ mnesia:table_info(Tab,checkpoints)
+ end,
+ ?match_receive({B,[CPName]}),
+
+ %%--- check, whether both retainers are consistent
+ ?match(ok, mnesia:dirty_write({Tab, 1, 815})),
+ A ! fun () ->
+ mnesia:backup_checkpoint(CPName, load_table_a)
+ end,
+ ?match_receive({A,ok}),
+ B ! fun () ->
+ mnesia:backup_checkpoint(CPName, load_table_b)
+ end,
+ ?match_receive({B,ok}),
+
+ Mod = mnesia_backup, %% Assume local files
+ List_a = view(load_table_a, Mod),
+ List_b = view(load_table_b, Mod),
+
+ ?match(List_a, List_b),
+
+ ?match(ok,file:delete(load_table_a)),
+ ?match(ok,file:delete(load_table_b)),
+ ?verify_mnesia(Nodes, []).
+
+view(Source, Mod) ->
+ View = fun(Item, Acc) ->
+ ?verbose("tab - item : ~p ~n",[Item]),
+ case Item of
+ {schema, Tab, Cs} -> %% Remove cookie information
+ NewCs = lists:keyreplace(cookie, 1, Cs,
+ {cookie, skip_cookie}),
+ Item2 = {schema, Tab, NewCs},
+ {[Item], [Item2|Acc]};
+ _ ->
+ {[Item], [Item|Acc]}
+ end
+ end,
+ {ok,TabList} =
+ mnesia:traverse_backup(Source, Mod, dummy, read_only, View, []),
+ lists:sort(TabList).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+add_table_copy_to_table_with_activated_checkpoint(doc) ->
+ ["Add a replica to a table with a checkpoint attached to it",
+ "and verify that the new replica also gets a checkpoint",
+ "retainer attached to it and that it is consistent with the",
+ "original retainer."];
+
+add_table_copy_to_table_with_activated_checkpoint(suite) ->
+ [
+ add_table_copy_to_table_with_activated_checkpoint_ram,
+ add_table_copy_to_table_with_activated_checkpoint_disc,
+ add_table_copy_to_table_with_activated_checkpoint_disc_only
+ ].
+
+add_table_copy_to_table_with_activated_checkpoint_ram(suite) -> [];
+add_table_copy_to_table_with_activated_checkpoint_ram(Config) when is_list(Config) ->
+ add_table_copy_to_table_with_activated_checkpoint(ram_copies, Config).
+
+add_table_copy_to_table_with_activated_checkpoint_disc(suite) -> [];
+add_table_copy_to_table_with_activated_checkpoint_disc(Config) when is_list(Config) ->
+ add_table_copy_to_table_with_activated_checkpoint(disc_copies, Config).
+
+add_table_copy_to_table_with_activated_checkpoint_disc_only(suite) -> [];
+add_table_copy_to_table_with_activated_checkpoint_disc_only(Config) when is_list(Config) ->
+ add_table_copy_to_table_with_activated_checkpoint(disc_only_copies, Config).
+
+add_table_copy_to_table_with_activated_checkpoint(Type,Config) ->
+ Nodes = ?acquire_nodes(2, Config),
+ %?verbose("NODES = ~p ~n",[Nodes]),
+ [Node1,Node2] = Nodes,
+
+ Tab = add_test,
+ Def = [{attributes, [key, value]},
+ {Type, [Node1]}], %% ??? important that RAM ???
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)},
+ {ram_overrides_dump,true}]),
+
+ ?match({atomic,ok},mnesia:add_table_copy(Tab,Node2,ram_copies)),
+
+ %%--- check, whether the checkpiont is attached to both replicas
+ {success, [A,B]} = ?start_activities(Nodes),
+
+ A ! fun () ->
+ mnesia:table_info(Tab,checkpoints)
+ end,
+ ?match_receive({A,[CPName]}),
+
+ B ! fun () ->
+ mnesia:table_info(Tab,checkpoints)
+ end,
+ ?match_receive({B,[CPName]}),
+
+ %%--- check, whether both retainers are consistent
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 815})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 815})),
+
+ A ! fun () ->
+ mnesia:backup_checkpoint(CPName, add_table_a)
+ end,
+ ?match_receive({A,ok}),
+ B ! fun () ->
+ mnesia:backup_checkpoint(CPName, add_table_b)
+ end,
+ ?match_receive({B,ok}),
+
+ Mod = mnesia_backup, %% Assume local files
+
+ List_a = view(add_table_a, Mod),
+ List_b = view(add_table_b, Mod),
+
+ ?match(List_a, List_b),
+
+ ?match(ok,file:delete(add_table_a)),
+ ?match(ok, file:delete(add_table_b)),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+backup_consistency(suite) ->
+ [
+ interupted_install_fallback,
+ interupted_uninstall_fallback,
+ mnesia_down_during_backup_causes_switch,
+ mnesia_down_during_backup_causes_abort,
+ schema_transactions_during_backup
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+interupted_install_fallback(doc) ->
+ ["Verify that a interrupted install_fallback really",
+ "is performed on all nodes or none"];
+
+interupted_install_fallback(suite) ->
+ [
+ inst_fallback_process_dies,
+ fatal_when_inconsistency
+ ].
+
+inst_fallback_process_dies(suite) ->
+ [];
+inst_fallback_process_dies(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ {success, [A,_B,_C]} = ?start_activities(Nodes),
+
+ TestPid = self(),
+ DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap},
+ DebugFun =
+ fun(PrevContext, _EvalContext) ->
+ ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n",
+ [self(),PrevContext]),
+ TestPid ! {self(),fallback_preswap},
+ case receive_messages([fallback_continue]) of
+ [{TestPid,fallback_continue}] ->
+ ?deactivate_debug_fun(DebugId),
+ PrevContext+1
+ end
+ end,
+ ?activate_debug_fun(DebugId, DebugFun, 1),
+
+ Tab = install_table,
+ Def = [{attributes, [key, value]}, {disc_copies, Nodes}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)},
+ {ram_overrides_dump,true}]),
+
+ ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)),
+
+ A ! fun() -> mnesia:install_fallback(install_backup) end,
+ [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]),
+ exit(A, kill),
+ AnsPid ! {self(), fallback_continue},
+ ?match_receive({'EXIT', A, killed}),
+ timer:sleep(2000), %% Wait till fallback is installed everywhere
+
+ mnesia_test_lib:kill_mnesia(Nodes),
+ ?verbose("~n---->Mnesia is stopped everywhere<-----~n", []),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes,[Tab])),
+
+ check_data(Nodes, Tab),
+ ?match(ok, file:delete(install_backup)),
+ ?verify_mnesia(Nodes, []).
+
+check_data([N1 | R], Tab) ->
+ ?match([{Tab, 1, 4711}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 2, 42}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])),
+ ?match([{Tab, 3, 256}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])),
+ check_data(R, Tab);
+check_data([], _Tab) ->
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+fatal_when_inconsistency(suite) ->
+ [];
+fatal_when_inconsistency(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+
+ [Node1, Node2, Node3] = Nodes =
+ ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ {success, [A,_B,_C]} = ?start_activities(Nodes),
+
+ TestPid = self(),
+ DebugId = {mnesia_bup, fallback_receiver_loop, pre_swap},
+ DebugFun =
+ fun(PrevContext, _EvalContext) ->
+ ?verbose("fallback_receiver_loop - pre_swap pid ~p #~p~n",
+ [self(),PrevContext]),
+ TestPid ! {self(),fallback_preswap},
+ case receive_messages([fallback_continue]) of
+ [{TestPid,fallback_continue}] ->
+ ?deactivate_debug_fun(DebugId),
+ PrevContext+1
+ end
+ end,
+ ?activate_debug_fun(DebugId, DebugFun, 1),
+
+ Tab = install_table,
+ Def = [{attributes, [key, value]}, {disc_copies, Nodes}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)},
+ {ram_overrides_dump,true}]),
+
+ ?match(ok, mnesia:backup_checkpoint(CPName, install_backup)),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42424242})),
+
+ A ! fun() ->
+ mnesia:install_fallback(install_backup)
+ end,
+
+ [{AnsPid,fallback_preswap}] = receive_messages([fallback_preswap]),
+ exit(AnsPid, kill), %% Kill install-fallback on local node will
+ AnsPid ! {self(), fallback_continue},
+ ?deactivate_debug_fun(DebugId),
+
+ ?match_receive({A,{error,{"Cannot install fallback",
+ {'EXIT',AnsPid,killed}}}}),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ ?verbose("EXPECTING FATAL from 2 nodes WITH CORE DUMP~n", []),
+
+ ?match([], mnesia_test_lib:start_mnesia([Node1],[])),
+ is_running(Node1, yes),
+ ?match([{Node2, mnesia, _}], mnesia_test_lib:start_mnesia([Node2],[])),
+ is_running(Node2, no),
+ ?match([{Node3, mnesia, _}], mnesia_test_lib:start_mnesia([Node3],[])),
+ is_running(Node3, no),
+ mnesia_test_lib:kill_mnesia(Nodes),
+
+ ?match(ok, mnesia:install_fallback(install_backup)),
+ mnesia_test_lib:start_mnesia(Nodes,[Tab]),
+
+ check_data(Nodes, Tab),
+
+ ?match(ok,file:delete(install_backup)),
+ ?verify_mnesia(Nodes, []).
+
+is_running(Node, Shouldbe) ->
+ timer:sleep(1000),
+ Running = rpc:call(Node, mnesia, system_info, [is_running]),
+ case Running of
+ Shouldbe -> ok;
+ _ -> is_running(Node, Shouldbe)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+interupted_uninstall_fallback(doc) ->
+ ["Verify that a interrupted uninstall_fallback really",
+ "is performed on all nodes or none"];
+interupted_uninstall_fallback(suite) ->
+ [
+ after_delete
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+after_delete(doc) ->
+ ["interrupt the uninstall after deletion of ",
+ "fallback files - there shall be no fallback"];
+after_delete(suite) -> [];
+after_delete(Config) when is_list(Config) ->
+ do_uninstall(Config, post_delete).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_uninstall(Config,DebugPoint) ->
+ ?is_debug_compiled,
+
+ Nodes = ?acquire_nodes(3, Config),
+ %%?verbose("NODES = ~p ~n",[Nodes]),
+
+ {success, [P1,P2,P3]} = ?start_activities(Nodes),
+
+ NP1 = node(P1),
+ NP2 = node(P2),
+
+ {A,B,C} = case node() of
+ NP1 ->
+ %%?verbose("first case ~n"),
+ {P3,P2,P1};
+ NP2 ->
+ %%?verbose("second case ~n"),
+ {P3, P1, P2};
+ _ ->
+ { P1, P2, P3}
+ end,
+
+ Node1 = node(A),
+ Node2 = node(B),
+ Node3 = node(C),
+
+ ?verbose(" A pid:~p node:~p ~n",[A,Node1]),
+ ?verbose(" B pid:~p node:~p ~n",[B,Node2]),
+ ?verbose(" C pid:~p node:~p ~n",[C,Node3]),
+
+
+ TestPid = self(),
+ %%?verbose("TestPid : ~p~n",[TestPid]),
+ DebugId = {mnesia_bup, uninstall_fallback2, DebugPoint},
+ DebugFun = fun(PrevContext, _EvalContext) ->
+ ?verbose("uninstall_fallback pid ~p #~p~n"
+ ,[self(),PrevContext]),
+ TestPid ! {self(),uninstall_predelete},
+ case receive_messages([uninstall_continue]) of
+ [{TestPid,uninstall_continue}] ->
+ ?deactivate_debug_fun(DebugId),
+ %%?verbose("uninstall_fallback continues~n"),
+ PrevContext+1
+ end
+ end,
+ ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1),
+
+ Tab = install_table,
+ Def = [{attributes, [key, value]},
+ {ram_copies, Nodes}], %% necessary to test different types ???
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)},
+ {ram_overrides_dump,true}]),
+
+ ?match(ok, mnesia:backup_checkpoint(CPName,install_backup)),
+ timer:sleep(timer:seconds(1)),
+
+ A ! fun () ->
+ mnesia:install_fallback(install_backup)
+ end,
+ ?match_receive({A,ok}),
+
+ A ! fun () ->
+ mnesia:uninstall_fallback()
+ end,
+ %%
+ %% catch the debug entry in mnesia and kill one Mnesia node
+ %%
+
+
+ [{AnsPid,uninstall_predelete}] = receive_messages([uninstall_predelete]),
+
+ ?verbose("AnsPid : ~p~n",[AnsPid]),
+
+ mnesia_test_lib:kill_mnesia([Node2]),
+ timer:sleep(timer:seconds(1)),
+
+ AnsPid ! {self(),uninstall_continue},
+
+ ?match_receive({A,ok}),
+
+ mnesia_test_lib:kill_mnesia(Nodes) ,
+ mnesia_test_lib:start_mnesia(Nodes,[Tab]),
+
+ A ! fun () ->
+ R1 = mnesia:dirty_read({Tab,1}),
+ R2 = mnesia:dirty_read({Tab,2}),
+ R3 = mnesia:dirty_read({Tab,3}),
+ {R1,R2,R3}
+ end,
+ ?match_receive({ A, {[],[],[]} }),
+
+ B ! fun () ->
+ R1 = mnesia:dirty_read({Tab,1}),
+ R2 = mnesia:dirty_read({Tab,2}),
+ R3 = mnesia:dirty_read({Tab,3}),
+ {R1,R2,R3}
+ end,
+ ?match_receive({ B, {[],[],[]} }),
+
+ C ! fun () ->
+ R1 = mnesia:dirty_read({Tab,1}),
+ R2 = mnesia:dirty_read({Tab,2}),
+ R3 = mnesia:dirty_read({Tab,3}),
+ {R1,R2,R3}
+ end,
+ ?match_receive({ C, {[],[],[]} }),
+
+ ?match(ok,file:delete(install_backup)),
+ ?verify_mnesia(Nodes, []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mnesia_down_during_backup_causes_switch(doc) ->
+ ["Verify that an ongoing backup is not disturbed",
+ "even if the node hosting the replica that currently",
+ "is being backup'ed is stopped. The backup utility",
+ "is expected to switch over to another replica and",
+ "fulfill the backup."];
+mnesia_down_during_backup_causes_switch(suite) ->
+ [
+ cause_switch_before,
+ cause_switch_after
+ ].
+
+%%%%%%%%%%%%%%%
+
+cause_switch_before(doc) ->
+ ["interrupt the backup before iterating the retainer"];
+cause_switch_before(suite) -> [];
+cause_switch_before(Config) when is_list(Config) ->
+ do_something_during_backup(cause_switch,pre,Config).
+
+%%%%%%%%%%%%%%%
+
+cause_switch_after(doc) ->
+ ["interrupt the backup after iterating the retainer"];
+cause_switch_after(suite) -> [];
+cause_switch_after(Config) when is_list(Config) ->
+ do_something_during_backup(cause_switch,post,Config).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mnesia_down_during_backup_causes_abort(doc) ->
+ ["Verify that an ongoing backup is aborted nicely",
+ "without leaving any backup file if the last replica",
+ "of a table becomes unavailable due to a node down",
+ "or some crash."];
+mnesia_down_during_backup_causes_abort(suite) ->
+ [
+ cause_abort_before,
+ cause_abort_after
+ ].
+
+%%%%%%%%%%%%%%%%%%
+
+cause_abort_before(doc) ->
+ ["interrupt the backup before iterating the retainer"];
+
+cause_abort_before(suite) -> [];
+cause_abort_before(Config) when is_list(Config) ->
+ do_something_during_backup(cause_abort,pre,Config).
+
+%%%%%%%%%%%%%%%%%%
+
+cause_abort_after(doc) ->
+ ["interrupt the backup after iterating the retainer"];
+
+cause_abort_after(suite) -> [];
+cause_abort_after(Config) when is_list(Config) ->
+ do_something_during_backup(cause_abort,post,Config).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+schema_transactions_during_backup(doc) ->
+ ["Verify that an schema transactions does not",
+ "affect an ongoing backup."];
+schema_transactions_during_backup(suite) ->
+ [
+ change_schema_before,
+ change_schema_after
+ ].
+
+%%%%%%%%%%%%%
+
+change_schema_before(doc) ->
+ ["interrupt the backup before iterating the retainer"];
+change_schema_before(suite) -> [];
+change_schema_before(Config) when is_list(Config) ->
+ do_something_during_backup(change_schema,pre,Config).
+
+%%%%%%%%%%%%%%%%
+
+change_schema_after(doc) ->
+ ["interrupt the backup after iterating the retainer"];
+change_schema_after(suite) -> [];
+change_schema_after(Config) when is_list(Config) ->
+ do_something_during_backup(change_schema,post,Config).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_something_during_backup(Action,DebugPoint,Config) ->
+ ?is_debug_compiled,
+
+ Nodes = ?acquire_nodes(3, Config),
+
+ {success, [A,B,C]} = ?start_activities(Nodes),
+
+ Node1 = node(A),
+ Node2 = node(B),
+ Node3 = node(C),
+
+ TestPid = self(),
+ %%?verbose("TestPid : ~p~n",[TestPid]),
+
+ Tab = interrupt_table,
+ Bak = interrupt_backup,
+ Def = [{attributes, [key, value]},
+ {ram_copies, [Node2,Node3]}],
+ %% necessary to test different types ???
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+
+
+ DebugId = {mnesia_log, tab_copier, DebugPoint},
+ DebugFun = fun(PrevContext, EvalContext) ->
+ ?verbose("interrupt backup pid ~p #~p ~n context ~p ~n"
+ ,[self(),PrevContext,EvalContext]),
+ TestPid ! {self(),interrupt_backup_pre},
+ global:set_lock({{lock_for_backup, Tab}, self()},
+ Nodes,
+ infinity),
+
+ %%?verbose("interrupt backup - continues ~n"),
+ ?deactivate_debug_fun(DebugId),
+ PrevContext+1
+ end,
+ ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ {ok, CPName, _NodeList} =
+ mnesia:activate_checkpoint([{max, mnesia:system_info(tables)},
+ {ram_overrides_dump,true}]),
+
+ A ! fun () ->
+ %%?verbose("node: ~p pid: ~p ~n",[node(),self()]),
+ mnesia:table_info(Tab,where_to_read)
+ end,
+
+ ReadNode_a = receive { A, ReadNode_a_tmp } -> ReadNode_a_tmp end,
+ ?verbose("ReadNode ~p ~n",[ReadNode_a]),
+
+ global:set_lock({{lock_for_backup, Tab}, self()}, Nodes, infinity),
+
+ A ! fun () -> %% A shall perform the backup, so the test proc is
+ %% able to do further actions in between
+ mnesia:backup_checkpoint(CPName, Bak)
+ end,
+
+ %% catch the debug function of mnesia, stop the backup process
+ %% kill the node ReadNode_a and continue the backup process
+ %% As there is a second replica of the table, the backup shall continue
+
+ case receive_messages([interrupt_backup_pre]) of
+ [{_AnsPid,interrupt_backup_pre}] -> ok
+ end,
+
+ case Action of
+ cause_switch ->
+ mnesia_test_lib:kill_mnesia([ReadNode_a]),
+ timer:sleep(timer:seconds(1));
+ cause_abort ->
+ mnesia_test_lib:kill_mnesia([Node2,Node3]),
+ timer:sleep(timer:seconds(1));
+ change_schema ->
+ Tab2 = second_interrupt_table,
+ Def2 = [{attributes, [key, value]},
+ {ram_copies, Nodes}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2))
+ end,
+
+ %% AnsPid ! {self(),interrupt_backup_continue},
+ global:del_lock({{lock_for_backup, Tab}, self()}, Nodes),
+
+ case Action of
+ cause_abort ->
+
+ %% answer of A when finishing the backup
+ ?match_receive({A,{error, _}}),
+
+ ?match({error,{"Cannot install fallback",_}},
+ mnesia:install_fallback(Bak));
+ _ -> %% cause_switch, change_schema
+
+ ?match_receive({A,ok}), %% answer of A when finishing the backup
+
+ %% send a fun to that node where mnesia is still running
+ WritePid = case ReadNode_a of
+ Node2 -> C; %% node(C) == Node3
+ Node3 -> B
+ end,
+ WritePid ! fun () ->
+ ?match(ok, mnesia:dirty_write({Tab, 1, 815})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 816})),
+ ok
+ end,
+ ?match_receive({ WritePid, ok }),
+ ?match(ok, mnesia:install_fallback(Bak))
+ end,
+
+ %% Stop and then start mnesia and check table consistency
+ %%?verbose("Restarting Mnesia~n", []),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ mnesia_test_lib:start_mnesia(Nodes,[Tab]),
+
+ case Action of
+ cause_switch ->
+ %% the backup should exist
+ cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}],
+ [{Tab,2,42}],
+ [{Tab,3,256}] }),
+ ?match(ok,file:delete(Bak));
+ cause_abort ->
+ %% the backup should NOT exist
+ cross_check_tables([A,B,C],Tab,{[],[],[]}),
+ %% file does not exist
+ ?match({error, _},file:delete(Bak));
+ change_schema ->
+ %% the backup should exist
+ cross_check_tables([A,B,C],Tab,{[{Tab,1,4711}],
+ [{Tab,2,42}],
+ [{Tab,3,256}] }),
+ ?match(ok,file:delete(Bak))
+ end,
+ ?verify_mnesia(Nodes, []).
+
+%% check the contents of the table
+cross_check_tables([],_tab,_elements) -> ok;
+cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) ->
+ Pid ! fun () ->
+ R1 = mnesia:dirty_read({Tab,1}),
+ R2 = mnesia:dirty_read({Tab,2}),
+ R3 = mnesia:dirty_read({Tab,3}),
+ {R1,R2,R3}
+ end,
+ ?match_receive({ Pid, {Val1, Val2, Val3 } }),
+ cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ).
diff --git a/lib/mnesia/test/mnesia_cost.erl b/lib/mnesia/test/mnesia_cost.erl
new file mode 100644
index 0000000000..54cb2b3064
--- /dev/null
+++ b/lib/mnesia/test/mnesia_cost.erl
@@ -0,0 +1,222 @@
+%%
+%% %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_cost).
+-compile(export_all).
+
+%% This code exercises the mnesia system and produces a bunch
+%% of measurements on what various things cost
+
+-define(TIMES, 1000). %% set to at least 1000 when running for real !!
+
+%% This is the record we perform all ops on in this test
+
+-record(item, {a = 1234,
+ b = foobar,
+ c = "1.2.3.4",
+ d = {'Lennart', 'Hyland'},
+ e = true
+ }).
+
+go() ->
+ go([node() | nodes()]).
+
+go(Nodes) when hd(Nodes) == node() ->
+ {ok, Out} = file:open("MNESIA_COST", write),
+ put(out, Out),
+
+ rpc:multicall(Nodes, mnesia, lkill, []),
+ ok = mnesia:delete_schema(Nodes),
+ ok = mnesia:create_schema(Nodes),
+ rpc:multicall(Nodes, mnesia, start, []),
+ TabDef = [{attributes, record_info(fields, item)}],
+ {atomic, ok} = mnesia:create_table(item, TabDef),
+
+ round("single ram copy", "no index"),
+ {atomic, ok} = mnesia:add_table_index(item, #item.e),
+ round("single ram copy", "One index"),
+
+ {atomic, ok} = mnesia:add_table_index(item, #item.c),
+ round("single ram copy", "Two indexes"),
+
+ {atomic, ok} = mnesia:del_table_index(item, #item.e),
+ {atomic, ok} = mnesia:del_table_index(item, #item.c),
+
+ {atomic, ok} = mnesia:change_table_copy_type(item, node(), disc_copies),
+ round("single disc copy", "no index"),
+
+ {atomic, ok} = mnesia:change_table_copy_type(item, node(), ram_copies),
+
+ case length(Nodes) of
+ Len when Len < 2 ->
+ format("<WARNING> replication skipped. Too few nodes.", []);
+ _Len ->
+ N2 = lists:nth(2, Nodes),
+ {atomic, ok} = mnesia:add_table_copy(item, N2, ram_copies),
+ round("2 replicated ram copy", "no index")
+ end,
+ file:close(Out),
+ erase(out),
+ ok.
+
+round(Replication, Index) ->
+ run(Replication, Index, [write],
+ fun() -> mnesia:write(#item{}) end),
+
+
+ run(Replication, Index, [read],
+ fun() -> mnesia:read({item, 1234}) end),
+
+ run(Replication, Index, [read, write],
+ fun() -> mnesia:read({item, 1234}),
+ mnesia:write(#item{}) end),
+
+ run(Replication, Index, [wread, write],
+ fun() -> mnesia:wread({item, 1234}),
+ mnesia:write(#item{}) end),
+
+
+ run(Replication, Index, [match, write, write, write],
+ fun() -> mnesia:match_object({item, 1, '_', '_', '_', true}),
+ mnesia:write(#item{a =1}),
+ mnesia:write(#item{a =2}),
+ mnesia:write(#item{a =3}) end).
+
+
+format(F, As) ->
+ io:format(get(out), F, As).
+
+run(What, OtherInfo, Ops, F) ->
+ run(t, What, OtherInfo, Ops, F).
+
+run(How, What, OtherInfo, Ops, F) ->
+ T1 = erlang:now(),
+ statistics(runtime),
+ do_times(How, ?TIMES, F),
+ {_, RunTime} = statistics(runtime),
+ T2 = erlang:now(),
+ RealTime = subtr(T1, T2),
+ report(How, What, OtherInfo, Ops, RunTime, RealTime).
+
+report(t, What, OtherInfo, Ops, RunTime, RealTime) ->
+ format("~s, ~s, transaction call ", [What, OtherInfo]),
+ format("Ops is ", []),
+ lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops),
+
+ format("~n ~w/~w Millisecs/Trans ~w/~w MilliSecs/Operation ~n~n",
+ [RunTime/?TIMES,
+ RealTime/?TIMES,
+ RunTime/(?TIMES*length(Ops)),
+ RealTime/(?TIMES*length(Ops))]);
+
+report(dirty, What, OtherInfo, Ops, RunTime, RealTime) ->
+ format("~s, ~s, dirty calls ", [What, OtherInfo]),
+ format("Ops is ", []),
+ lists:foreach(fun(Op) -> format("~w-", [Op]) end, Ops),
+
+ format("~n ~w/~w Millisecs/Bunch ~w/~w MilliSecs/Operation ~n~n",
+ [RunTime/?TIMES,
+ RealTime/?TIMES,
+ RunTime/(?TIMES*length(Ops)),
+ RealTime/(?TIMES*length(Ops))]).
+
+
+subtr(Before, After) ->
+ E =(element(1,After)*1000000000000
+ +element(2,After)*1000000+element(3,After)) -
+ (element(1,Before)*1000000000000
+ +element(2,Before)*1000000+element(3,Before)),
+ E div 1000.
+
+do_times(t, I, F) ->
+ do_trans_times(I, F);
+do_times(dirty, I, F) ->
+ do_dirty(I, F).
+
+do_trans_times(I, F) when I /= 0 ->
+ {atomic, _} = mnesia:transaction(F),
+ do_trans_times(I-1, F);
+do_trans_times(_,_) -> ok.
+
+do_dirty(I, F) when I /= 0 ->
+ F(),
+ do_dirty(I-1, F);
+do_dirty(_,_) -> ok.
+
+
+
+table_load([N1,N2| _ ] = Ns) ->
+ Nodes = [N1,N2],
+ rpc:multicall(Ns, mnesia, lkill, []),
+ ok = mnesia:delete_schema(Ns),
+ ok = mnesia:create_schema(Nodes),
+ rpc:multicall(Nodes, mnesia, start, []),
+ TabDef = [{disc_copies,[N1]},{ram_copies,[N2]},
+ {attributes,record_info(fields,item)},{record_name,item}],
+ Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1,400)],
+
+ [mnesia:create_table(Tab,TabDef) || Tab <- Tabs],
+
+%% InitTab = fun(Tab) ->
+%% mnesia:write_lock_table(Tab),
+%% InitRec = fun(Key) -> mnesia:write(Tab,#item{a=Key},write) end,
+%% lists:foreach(InitRec, lists:seq(1,100))
+%% end,
+%%
+%% {Time,{atomic,ok}} = timer:tc(mnesia,transaction, [fun() ->lists:foreach(InitTab, Tabs) end]),
+ mnesia:dump_log(),
+%% io:format("Init took ~p msec ~n", [Time/1000]),
+ rpc:call(N2, mnesia, stop, []), timer:sleep(1000),
+ mnesia:stop(), timer:sleep(500),
+ %% Warmup
+ ok = mnesia:start([{no_table_loaders, 1}]),
+ timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
+ mnesia:dump_log(),
+ rpc:call(N2, mnesia, dump_log, []),
+ io:format("Initialized ~n",[]),
+
+ mnesia:stop(), timer:sleep(1000),
+ ok = mnesia:start([{no_table_loaders, 1}]),
+ {T1, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
+ io:format("Loading from disc with 1 loader ~p msec~n",[T1/1000]),
+ mnesia:stop(), timer:sleep(1000),
+ ok = mnesia:start([{no_table_loaders, 4}]),
+ {T2, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
+ io:format("Loading from disc with 4 loader ~p msec~n",[T2/1000]),
+
+ %% Warmup
+ rpc:call(N2, ?MODULE, remote_load, [Tabs,4]),
+ io:format("Initialized ~n",[]),
+
+
+ T3 = rpc:call(N2, ?MODULE, remote_load, [Tabs,1]),
+ io:format("Loading from net with 1 loader ~p msec~n",[T3/1000]),
+
+ T4 = rpc:call(N2, ?MODULE, remote_load, [Tabs,4]),
+ io:format("Loading from net with 4 loader ~p msec~n",[T4/1000]),
+
+ ok.
+
+remote_load(Tabs,Loaders) ->
+ ok = mnesia:start([{no_table_loaders, Loaders}]),
+%% io:format("~p ~n", [mnesia_controller:get_info(500)]),
+ {Time, ok} = timer:tc(mnesia, wait_for_tables, [Tabs, infinity]),
+ timer:sleep(1000), mnesia:stop(), timer:sleep(1000),
+ Time.
diff --git a/lib/mnesia/test/mnesia_dbn_meters.erl b/lib/mnesia/test/mnesia_dbn_meters.erl
new file mode 100644
index 0000000000..feaf90ee75
--- /dev/null
+++ b/lib/mnesia/test/mnesia_dbn_meters.erl
@@ -0,0 +1,242 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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
+%% 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_dbn_meters).
+-export([
+ start/0,
+ local_start/0,
+ distr_start/1,
+ start/3
+ ]).
+
+-record(simple,{key,val=0}).
+-define(key,1).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Configuration and start
+
+start() ->
+ local_start(),
+ distr_start(nodes()).
+
+local_start() ->
+ start(one_ram_only,[node()],some_meters()),
+ start(one_disc_only,[node()],some_meters()).
+
+distr_start([]) ->
+ local_only;
+distr_start(OtherNodes) when is_list(OtherNodes) ->
+ start(ram_and_ram,[node()|OtherNodes],some_meters()),
+ start(disc_and_disc,[node()|OtherNodes],some_meters()).
+
+start(Config,Nodes,Meters) ->
+ Attrs = record_info(fields,simple),
+ Schema = [{name,simple},{type,set},{attributes,Attrs}] ++ config(Config,Nodes),
+ L = '====================',
+ io:format("~n~p dbn_meters: ~p ~p~nSchema = ~p.~n~n",[L,Config,L,Schema]),
+ ok = mnesia:delete_schema(Nodes),
+ ok = mnesia:create_schema(Nodes),
+ rpc:multicall(Nodes, mnesia, start, []),
+ {atomic,_} = mnesia:create_table(Schema),
+ lists:foreach(fun report_meter/1,Meters),
+ {atomic, ok} = mnesia:delete_table(simple),
+ rpc:multicall(Nodes, mnesia, stop, []),
+ ok.
+
+config(one_ram_only,[Single|_]) ->
+ [{ram_copies,[Single]}];
+config(ram_and_ram,[Master|[Slave|_]]) ->
+ [{ram_copies,[Master,Slave]}];
+config(one_disc_only,[Single|_]) ->
+ [{disc_copies,[Single]}];
+config(disc_and_disc,[Master|[Slave|_]]) ->
+ [{disc_copies,[Master,Slave]}];
+config(Config,Nodes) ->
+ io:format("<ERROR> Config ~p not supported or too few nodes ~p given~n",[Config,Nodes]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% The various DBN meters
+some_meters() ->
+ [create,
+ open_safe_read,
+ open_dirty_read,
+ get_int,
+ open_update,
+ put_int,
+ put_int_and_copy,
+ dirty_put_int_and_copy,
+ start_trans,
+ commit_one_update,
+ delete,
+ dirty_delete
+ ].
+
+report_meter(Meter) ->
+ Times = 100,
+ Micros = repeat_meter(Meter,{atomic,{0,ignore}},Times) div Times,
+ io:format("\t~-30w ~-10w micro seconds (mean of ~p repetitions)~n",[Meter,Micros,Times]).
+
+repeat_meter(_Meter,{atomic,{Micros,_Result}},0) ->
+ Micros;
+repeat_meter(Meter,{atomic,{Micros,_Result}},Times) when Times > 0 ->
+ repeat_meter(Meter,catch meter(Meter),Times-1) + Micros;
+repeat_meter(Meter,{aborted,Reason},Times) when Times > 0 ->
+ io:format("<ERROR>\t~-20w\t,aborted, because ~p~n",[Meter,Reason]),
+ 0;
+repeat_meter(Meter,{'EXIT',Reason},Times) when Times > 0 ->
+ io:format("<ERROR>\t~-20w\tcrashed, because ~p~n",[Meter,Reason]),
+ 0.
+
+meter(create) ->
+ Key = 1,
+ mnesia:transaction(fun() -> mnesia:delete({simple,Key}) end),
+ Fun = fun() ->
+ BeforeT = erlang:now(),
+ R = mnesia:write(#simple{key=Key}),
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,R)
+ end,
+ mnesia:transaction(Fun);
+
+meter(open_safe_read) ->
+ Key = 2,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ BeforeT = erlang:now(),
+ R = mnesia:read({simple,Key}),
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,R)
+ end,
+ mnesia:transaction(Fun);
+
+meter(open_dirty_read) ->
+ Key = 21,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ BeforeT = erlang:now(),
+ R = mnesia:dirty_read({simple,Key}),
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,R)
+ end,
+ mnesia:transaction(Fun);
+
+meter(get_int) ->
+ Key = 3,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ [Simple] = mnesia:read({simple,Key}),
+ BeforeT = erlang:now(),
+ Int = Simple#simple.val,
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,Int)
+ end,
+ mnesia:transaction(Fun);
+
+meter(open_update) ->
+ Key = 3,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ BeforeT = erlang:now(),
+ R = mnesia:wread({simple,Key}),
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,R)
+ end,
+ mnesia:transaction(Fun);
+
+meter(put_int) ->
+ Key = 4,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ [Simple] = mnesia:wread({simple,Key}),
+ BeforeT = erlang:now(),
+ R = Simple#simple{val=7},
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,R)
+ end,
+ mnesia:transaction(Fun);
+
+meter(put_int_and_copy) ->
+ Key = 5,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ [Simple] = mnesia:wread({simple,Key}),
+ BeforeT = erlang:now(),
+ Simple2 = Simple#simple{val=17},
+ R = mnesia:write(Simple2),
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,R)
+ end,
+ mnesia:transaction(Fun);
+
+meter(dirty_put_int_and_copy) ->
+ Key = 55,
+ mnesia:dirty_write(#simple{key=Key}),
+ [Simple] = mnesia:dirty_read({simple,Key}),
+ BeforeT = erlang:now(),
+ Simple2 = Simple#simple{val=17},
+ R = mnesia:dirty_write(Simple2),
+ AfterT = erlang:now(),
+ {atomic,elapsed_time(BeforeT,AfterT,R)};
+
+meter(start_trans) ->
+ BeforeT = erlang:now(),
+ {atomic,AfterT} = mnesia:transaction(fun() -> erlang:now() end),
+ {atomic,elapsed_time(BeforeT,AfterT,ok)};
+
+meter(commit_one_update) ->
+ Key = 6,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ [Simple] = mnesia:wread({simple,Key}),
+ Simple2 = Simple#simple{val=27},
+ _R = mnesia:write(Simple2),
+ erlang:now()
+ end,
+ {atomic,BeforeT} = mnesia:transaction(Fun),
+ AfterT = erlang:now(),
+ {atomic,elapsed_time(BeforeT,AfterT,ok)};
+
+meter(delete) ->
+ Key = 7,
+ mnesia:transaction(fun() -> mnesia:write(#simple{key=Key}) end),
+ Fun = fun() ->
+ BeforeT = erlang:now(),
+ R = mnesia:delete({simple,Key}),
+ AfterT = erlang:now(),
+ elapsed_time(BeforeT,AfterT,R)
+ end,
+ mnesia:transaction(Fun);
+
+meter(dirty_delete) ->
+ Key = 75,
+ mnesia:dirty_write(#simple{key=Key}),
+ BeforeT = erlang:now(),
+ R = mnesia:dirty_delete({simple,Key}),
+ AfterT = erlang:now(),
+ {atomic, elapsed_time(BeforeT,AfterT,R)}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Calculate the elapsed time
+elapsed_time(BeforeT,AfterT,Result) ->
+ {(element(1,AfterT)*1000000000000
+ +element(2,AfterT)*1000000+element(3,AfterT)) -
+ (element(1,BeforeT)*1000000000000
+ +element(2,BeforeT)*1000000+element(3,BeforeT)),Result}.
diff --git a/lib/mnesia/test/mnesia_dirty_access_test.erl b/lib/mnesia/test/mnesia_dirty_access_test.erl
new file mode 100644
index 0000000000..5f9f2a9733
--- /dev/null
+++ b/lib/mnesia/test/mnesia_dirty_access_test.erl
@@ -0,0 +1,927 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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
+%% 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_dirty_access_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) ->
+ ["Evil dirty access, regardless of transaction scope.",
+ "Invoke all functions in the API and try to cover all legal uses",
+ "cases as well the illegal dito. This is a complement to the",
+ "other more explicit test cases."];
+all(suite) ->
+ [
+ dirty_write,
+ dirty_read,
+ dirty_update_counter,
+ dirty_delete,
+ dirty_delete_object,
+ dirty_match_object,
+ dirty_index,
+ dirty_iter,
+ admin_tests
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Write records dirty
+
+dirty_write(suite) ->
+ [
+ dirty_write_ram,
+ dirty_write_disc,
+ dirty_write_disc_only
+ ].
+
+dirty_write_ram(suite) -> [];
+dirty_write_ram(Config) when is_list(Config) ->
+ dirty_write(Config, ram_copies).
+
+dirty_write_disc(suite) -> [];
+dirty_write_disc(Config) when is_list(Config) ->
+ dirty_write(Config, disc_copies).
+
+dirty_write_disc_only(suite) -> [];
+dirty_write_disc_only(Config) when is_list(Config) ->
+ dirty_write(Config, disc_only_copies).
+
+dirty_write(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_write,
+ Def = [{attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match({'EXIT', _}, mnesia:dirty_write([])),
+ ?match({'EXIT', _}, mnesia:dirty_write({Tab, 2})),
+ ?match({'EXIT', _}, mnesia:dirty_write({foo, 2})),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 2})),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() ->
+ mnesia:dirty_write({Tab, 1, 2}) end)),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Read records dirty
+
+dirty_read(suite) ->
+ [
+ dirty_read_ram,
+ dirty_read_disc,
+ dirty_read_disc_only
+ ].
+
+dirty_read_ram(suite) -> [];
+dirty_read_ram(Config) when is_list(Config) ->
+ dirty_read(Config, ram_copies).
+
+dirty_read_disc(suite) -> [];
+dirty_read_disc(Config) when is_list(Config) ->
+ dirty_read(Config, disc_copies).
+
+dirty_read_disc_only(suite) -> [];
+dirty_read_disc_only(Config) when is_list(Config) ->
+ dirty_read(Config, disc_only_copies).
+
+dirty_read(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_read,
+ Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match({'EXIT', _}, mnesia:dirty_read([])),
+ ?match({'EXIT', _}, mnesia:dirty_read({Tab})),
+ ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1, 2})),
+ ?match([], mnesia:dirty_read({Tab, 1})),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 2})),
+ ?match([{Tab, 1, 2}], mnesia:dirty_read({Tab, 1})),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 3})),
+ ?match([{Tab, 1, 2}, {Tab, 1, 3}], mnesia:dirty_read({Tab, 1})),
+
+ ?match({atomic, [{Tab, 1, 2}, {Tab, 1, 3}]},
+ mnesia:transaction(fun() -> mnesia:dirty_read({Tab, 1}) end)),
+
+ ?match(false, mnesia:async_dirty(fun() -> mnesia:is_transaction() end)),
+ ?match(false, mnesia:sync_dirty(fun() -> mnesia:is_transaction() end)),
+ ?match(false, mnesia:ets(fun() -> mnesia:is_transaction() end)),
+ ?match(false, mnesia:activity(async_dirty, fun() -> mnesia:is_transaction() end)),
+ ?match(false, mnesia:activity(sync_dirty, fun() -> mnesia:is_transaction() end)),
+ ?match(false, mnesia:activity(ets, fun() -> mnesia:is_transaction() end)),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Update counter record dirty
+
+dirty_update_counter(suite) ->
+ [
+ dirty_update_counter_ram,
+ dirty_update_counter_disc,
+ dirty_update_counter_disc_only
+ ].
+
+dirty_update_counter_ram(suite) -> [];
+dirty_update_counter_ram(Config) when is_list(Config) ->
+ dirty_update_counter(Config, ram_copies).
+
+dirty_update_counter_disc(suite) -> [];
+dirty_update_counter_disc(Config) when is_list(Config) ->
+ dirty_update_counter(Config, disc_copies).
+
+dirty_update_counter_disc_only(suite) -> [];
+dirty_update_counter_disc_only(Config) when is_list(Config) ->
+ dirty_update_counter(Config, disc_only_copies).
+
+dirty_update_counter(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_update_counter,
+ Def = [{attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 2})),
+
+ ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab, 1}, [])),
+ ?match({'EXIT', _}, mnesia:dirty_update_counter({Tab}, 3)),
+ ?match({'EXIT', _}, mnesia:dirty_update_counter({foo, 1}, 3)),
+ ?match(5, mnesia:dirty_update_counter({Tab, 1}, 3)),
+ ?match([{Tab, 1, 5}], mnesia:dirty_read({Tab, 1})),
+
+ ?match({atomic, 8}, mnesia:transaction(fun() ->
+ mnesia:dirty_update_counter({Tab, 1}, 3) end)),
+
+ ?match(1, mnesia:dirty_update_counter({Tab, foo}, 1)),
+ ?match([{Tab, foo,1}], mnesia:dirty_read({Tab,foo})),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Delete record dirty
+
+dirty_delete(suite) ->
+ [
+ dirty_delete_ram,
+ dirty_delete_disc,
+ dirty_delete_disc_only
+ ].
+
+dirty_delete_ram(suite) -> [];
+dirty_delete_ram(Config) when is_list(Config) ->
+ dirty_delete(Config, ram_copies).
+
+dirty_delete_disc(suite) -> [];
+dirty_delete_disc(Config) when is_list(Config) ->
+ dirty_delete(Config, disc_copies).
+
+dirty_delete_disc_only(suite) -> [];
+dirty_delete_disc_only(Config) when is_list(Config) ->
+ dirty_delete(Config, disc_only_copies).
+
+dirty_delete(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_delete,
+ Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match({'EXIT', _}, mnesia:dirty_delete([])),
+ ?match({'EXIT', _}, mnesia:dirty_delete({Tab})),
+ ?match({'EXIT', _}, mnesia:dirty_delete({Tab, 1, 2})),
+ ?match(ok, mnesia:dirty_delete({Tab, 1})),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 2})),
+ ?match(ok, mnesia:dirty_delete({Tab, 1})),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 2})),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 2})),
+ ?match(ok, mnesia:dirty_delete({Tab, 1})),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 2})),
+ ?match({atomic, ok}, mnesia:transaction(fun() ->
+ mnesia:dirty_delete({Tab, 1}) end)),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Delete matching record dirty
+
+dirty_delete_object(suite) ->
+ [
+ dirty_delete_object_ram,
+ dirty_delete_object_disc,
+ dirty_delete_object_disc_only
+ ].
+
+dirty_delete_object_ram(suite) -> [];
+dirty_delete_object_ram(Config) when is_list(Config) ->
+ dirty_delete_object(Config, ram_copies).
+
+dirty_delete_object_disc(suite) -> [];
+dirty_delete_object_disc(Config) when is_list(Config) ->
+ dirty_delete_object(Config, disc_copies).
+
+dirty_delete_object_disc_only(suite) -> [];
+dirty_delete_object_disc_only(Config) when is_list(Config) ->
+ dirty_delete_object(Config, disc_only_copies).
+
+dirty_delete_object(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_delete_object,
+ Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ OneRec = {Tab, 1, 2},
+ ?match({'EXIT', _}, mnesia:dirty_delete_object([])),
+ ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab})),
+ ?match({'EXIT', _}, mnesia:dirty_delete_object({Tab, 1})),
+ ?match(ok, mnesia:dirty_delete_object(OneRec)),
+ ?match(ok, mnesia:dirty_write(OneRec)),
+ ?match(ok, mnesia:dirty_delete_object(OneRec)),
+ ?match(ok, mnesia:dirty_write(OneRec)),
+ ?match(ok, mnesia:dirty_write(OneRec)),
+ ?match(ok, mnesia:dirty_delete_object(OneRec)),
+
+ ?match(ok, mnesia:dirty_write(OneRec)),
+ ?match({atomic, ok}, mnesia:transaction(fun() ->
+ mnesia:dirty_delete_object(OneRec) end)),
+
+ ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['_']}, 21})),
+ ?match({'EXIT', {aborted, {bad_type, Tab, _}}}, mnesia:dirty_delete_object(Tab, {Tab, {['$5']}, 21})),
+
+ ?verify_mnesia(Nodes, []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Read matching records dirty
+
+dirty_match_object(suite) ->
+ [
+ dirty_match_object_ram,
+ dirty_match_object_disc,
+ dirty_match_object_disc_only
+ ].
+
+dirty_match_object_ram(suite) -> [];
+dirty_match_object_ram(Config) when is_list(Config) ->
+ dirty_match_object(Config, ram_copies).
+
+dirty_match_object_disc(suite) -> [];
+dirty_match_object_disc(Config) when is_list(Config) ->
+ dirty_match_object(Config, disc_copies).
+
+dirty_match_object_disc_only(suite) -> [];
+dirty_match_object_disc_only(Config) when is_list(Config) ->
+ dirty_match_object(Config, disc_only_copies).
+
+dirty_match_object(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_match,
+ Def = [{attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ OneRec = {Tab, 1, 2},
+ OnePat = {Tab, '$1', 2},
+ ?match([], mnesia:dirty_match_object(OnePat)),
+ ?match(ok, mnesia:dirty_write(OneRec)),
+ ?match([OneRec], mnesia:dirty_match_object(OnePat)),
+ ?match({atomic, [OneRec]}, mnesia:transaction(fun() ->
+ mnesia:dirty_match_object(OnePat) end)),
+
+ ?match({'EXIT', _}, mnesia:dirty_match_object({foo, '$1', 2})),
+ ?match({'EXIT', _}, mnesia:dirty_match_object({[], '$1', 2})),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dirty_index(suite) ->
+ [
+ dirty_index_match_object,
+ dirty_index_read,
+ dirty_index_update
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Dirty read matching records by using an index
+
+dirty_index_match_object(suite) ->
+ [
+ dirty_index_match_object_ram,
+ dirty_index_match_object_disc,
+ dirty_index_match_object_disc_only
+ ].
+
+dirty_index_match_object_ram(suite) -> [];
+dirty_index_match_object_ram(Config) when is_list(Config) ->
+ dirty_index_match_object(Config, ram_copies).
+
+dirty_index_match_object_disc(suite) -> [];
+dirty_index_match_object_disc(Config) when is_list(Config) ->
+ dirty_index_match_object(Config, disc_copies).
+
+dirty_index_match_object_disc_only(suite) -> [];
+dirty_index_match_object_disc_only(Config) when is_list(Config) ->
+ dirty_index_match_object(Config, disc_only_copies).
+
+dirty_index_match_object(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_index_match_object,
+ ValPos = 3,
+ BadValPos = ValPos + 1,
+ Def = [{attributes, [k, v]}, {Storage, [Node1]}, {index, [ValPos]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match([], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)),
+ OneRec = {Tab, 1, 2},
+ ?match(ok, mnesia:dirty_write(OneRec)),
+
+ ?match([OneRec], mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos)),
+ ?match({'EXIT', _}, mnesia:dirty_index_match_object({Tab, '$1', 2}, BadValPos)),
+ ?match({'EXIT', _}, mnesia:dirty_index_match_object({foo, '$1', 2}, ValPos)),
+ ?match({'EXIT', _}, mnesia:dirty_index_match_object({[], '$1', 2}, ValPos)),
+ ?match({atomic, [OneRec]}, mnesia:transaction(fun() ->
+ mnesia:dirty_index_match_object({Tab, '$1', 2}, ValPos) end)),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Read records by using an index
+
+dirty_index_read(suite) ->
+ [
+ dirty_index_read_ram,
+ dirty_index_read_disc,
+ dirty_index_read_disc_only
+ ].
+
+dirty_index_read_ram(suite) -> [];
+dirty_index_read_ram(Config) when is_list(Config) ->
+ dirty_index_read(Config, ram_copies).
+
+dirty_index_read_disc(suite) -> [];
+dirty_index_read_disc(Config) when is_list(Config) ->
+ dirty_index_read(Config, disc_copies).
+
+dirty_index_read_disc_only(suite) -> [];
+dirty_index_read_disc_only(Config) when is_list(Config) ->
+ dirty_index_read(Config, disc_only_copies).
+
+dirty_index_read(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_index_read,
+ ValPos = 3,
+ BadValPos = ValPos + 1,
+ Def = [{type, set},
+ {attributes, [k, v]},
+ {Storage, [Node1]},
+ {index, [ValPos]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ OneRec = {Tab, 1, 2},
+ ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match(ok, mnesia:dirty_write(OneRec)),
+ ?match([OneRec], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match({atomic, [OneRec]},
+ mnesia:transaction(fun() -> mnesia:dirty_index_read(Tab, 2, ValPos) end)),
+ ?match(42, mnesia:dirty_update_counter({Tab, 1}, 40)),
+ ?match([{Tab,1,42}], mnesia:dirty_read({Tab, 1})),
+ ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match([{Tab, 1, 42}], mnesia:dirty_index_read(Tab, 42, ValPos)),
+
+ ?match({'EXIT', _}, mnesia:dirty_index_read(Tab, 2, BadValPos)),
+ ?match({'EXIT', _}, mnesia:dirty_index_read(foo, 2, ValPos)),
+ ?match({'EXIT', _}, mnesia:dirty_index_read([], 2, ValPos)),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dirty_index_update(suite) ->
+ [
+ dirty_index_update_set_ram,
+ dirty_index_update_set_disc,
+ dirty_index_update_set_disc_only,
+ dirty_index_update_bag_ram,
+ dirty_index_update_bag_disc,
+ dirty_index_update_bag_disc_only
+ ];
+dirty_index_update(doc) ->
+ ["See Ticket OTP-2083, verifies that a table with a index is "
+ "update in the correct way i.e. the index finds the correct "
+ "records after a update"].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dirty_index_update_set_ram(suite) -> [];
+dirty_index_update_set_ram(Config) when is_list(Config) ->
+ dirty_index_update_set(Config, ram_copies).
+
+dirty_index_update_set_disc(suite) -> [];
+dirty_index_update_set_disc(Config) when is_list(Config) ->
+ dirty_index_update_set(Config, disc_copies).
+
+dirty_index_update_set_disc_only(suite) -> [];
+dirty_index_update_set_disc_only(Config) when is_list(Config) ->
+ dirty_index_update_set(Config, disc_only_copies).
+
+dirty_index_update_set(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = index_test,
+ ValPos = v1,
+ ValPos2 = v3,
+ Def = [{attributes, [k, v1, v2, v3]},
+ {Storage, [Node1]},
+ {index, [ValPos]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ Pat1 = {Tab, '$1', 2, '$2', '$3'},
+ Pat2 = {Tab, '$1', '$2', '$3', '$4'},
+
+ Rec1 = {Tab, 1, 2, 3, 4},
+ Rec2 = {Tab, 2, 2, 13, 14},
+ Rec3 = {Tab, 1, 12, 13, 14},
+ Rec4 = {Tab, 4, 2, 13, 14},
+
+ ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match(ok, mnesia:dirty_write(Rec1)),
+ ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)),
+
+ ?match(ok, mnesia:dirty_write(Rec2)),
+ R1 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec2], lists:sort(R1)),
+
+ ?match(ok, mnesia:dirty_write(Rec3)),
+ R2 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec2], lists:sort(R2)),
+ ?match([Rec2], mnesia:dirty_index_match_object(Pat1, ValPos)),
+
+ {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end),
+ ?match([Rec3, Rec2], lists:sort(R3)),
+
+ ?match(ok, mnesia:dirty_write(Rec4)),
+ R4 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec2, Rec4], lists:sort(R4)),
+
+ ?match(ok, mnesia:dirty_delete({Tab, 4})),
+ ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)),
+
+ ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)),
+
+ R5 = mnesia:dirty_match_object(Pat2),
+ ?match([Rec3, Rec2, Rec4], lists:sort(R5)),
+
+ R6 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec2, Rec4], lists:sort(R6)),
+ ?match([], mnesia:dirty_index_read(Tab, 4, ValPos2)),
+ R7 = mnesia:dirty_index_read(Tab, 14, ValPos2),
+ ?match([Rec3, Rec2, Rec4], lists:sort(R7)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)),
+ R8 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec2, Rec4], lists:sort(R8)),
+ ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)),
+ R9 = mnesia:dirty_index_read(Tab, 14, ValPos2),
+ ?match([Rec2, Rec4], lists:sort(R9)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)),
+ R10 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec4], lists:sort(R10)),
+ ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)),
+ ?match([Rec4], mnesia:dirty_index_read(Tab, 14, ValPos2)),
+
+ ?match(ok, mnesia:dirty_delete({Tab, 4})),
+ R11 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1], lists:sort(R11)),
+ ?match([Rec1], mnesia:dirty_index_read(Tab, 4, ValPos2)),
+ ?match([], mnesia:dirty_index_read(Tab, 14, ValPos2)),
+
+ ?verify_mnesia(Nodes, []).
+
+dirty_index_update_bag_ram(suite) -> [];
+dirty_index_update_bag_ram(Config)when is_list(Config) ->
+ dirty_index_update_bag(Config, ram_copies).
+
+dirty_index_update_bag_disc(suite) -> [];
+dirty_index_update_bag_disc(Config)when is_list(Config) ->
+ dirty_index_update_bag(Config, disc_copies).
+
+dirty_index_update_bag_disc_only(suite) -> [];
+dirty_index_update_bag_disc_only(Config)when is_list(Config) ->
+ dirty_index_update_bag(Config, disc_only_copies).
+
+dirty_index_update_bag(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = index_test,
+ ValPos = v1,
+ ValPos2 = v3,
+ Def = [{type, bag},
+ {attributes, [k, v1, v2, v3]},
+ {Storage, [Node1]},
+ {index, [ValPos]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ Pat1 = {Tab, '$1', 2, '$2', '$3'},
+ Pat2 = {Tab, '$1', '$2', '$3', '$4'},
+
+ Rec1 = {Tab, 1, 2, 3, 4},
+ Rec2 = {Tab, 2, 2, 13, 14},
+ Rec3 = {Tab, 1, 12, 13, 14},
+ Rec4 = {Tab, 4, 2, 13, 4},
+ Rec5 = {Tab, 1, 2, 234, 14},
+
+ %% Simple Index
+ ?match([], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match(ok, mnesia:dirty_write(Rec1)),
+ ?match([Rec1], mnesia:dirty_index_read(Tab, 2, ValPos)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec2) end)),
+ R1 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec2], lists:sort(R1)),
+
+ ?match(ok, mnesia:dirty_write(Rec3)),
+ R2 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec2], lists:sort(R2)),
+
+ R3 = mnesia:dirty_index_match_object(Pat1, ValPos),
+ ?match([Rec1, Rec2], lists:sort(R3)),
+
+ R4 = mnesia:dirty_match_object(Pat2),
+ ?match([Rec1, Rec3, Rec2], lists:sort(R4)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)),
+ R5 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec2, Rec4], lists:sort(R5)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)),
+ R6 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec2], lists:sort(R6)),
+
+ ?match(ok, mnesia:dirty_delete_object(Rec1)),
+ ?match([Rec2], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ R7 = mnesia:dirty_match_object(Pat2),
+ ?match([Rec3, Rec2], lists:sort(R7)),
+
+ %% Two indexies
+ ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)),
+
+ R8 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec2, Rec4], lists:sort(R8)),
+
+ R9 = mnesia:dirty_index_read(Tab, 4, ValPos2),
+ ?match([Rec1, Rec4], lists:sort(R9)),
+ R10 = mnesia:dirty_index_read(Tab, 14, ValPos2),
+ ?match([Rec3, Rec2], lists:sort(R10)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)),
+ R11 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)),
+ R12 = mnesia:dirty_index_read(Tab, 4, ValPos2),
+ ?match([Rec1, Rec4], lists:sort(R12)),
+ R13 = mnesia:dirty_index_read(Tab, 14, ValPos2),
+ ?match([Rec5, Rec3, Rec2], lists:sort(R13)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)),
+ R14 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec5, Rec2, Rec4], lists:sort(R14)),
+ ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)),
+ R15 = mnesia:dirty_index_read(Tab, 14, ValPos2),
+ ?match([Rec5, Rec3, Rec2], lists:sort(R15)),
+
+ ?match(ok, mnesia:dirty_delete_object(Rec5)),
+ R16 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec2, Rec4], lists:sort(R16)),
+ ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)),
+ R17 = mnesia:dirty_index_read(Tab, 14, ValPos2),
+ ?match([Rec3, Rec2], lists:sort(R17)),
+
+ ?match(ok, mnesia:dirty_write(Rec1)),
+ ?match(ok, mnesia:dirty_delete({Tab, 1})),
+ R18 = mnesia:dirty_index_read(Tab, 2, ValPos),
+ ?match([Rec2, Rec4], lists:sort(R18)),
+ ?match([Rec4], mnesia:dirty_index_read(Tab, 4, ValPos2)),
+ R19 = mnesia:dirty_index_read(Tab, 14, ValPos2),
+ ?match([Rec2], lists:sort(R19)),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Dirty iteration
+%% dirty_slot, dirty_first, dirty_next
+
+dirty_iter(suite) ->
+ [
+ dirty_iter_ram,
+ dirty_iter_disc,
+ dirty_iter_disc_only
+ ].
+
+dirty_iter_ram(suite) -> [];
+dirty_iter_ram(Config) when is_list(Config) ->
+ dirty_iter(Config, ram_copies).
+
+dirty_iter_disc(suite) -> [];
+dirty_iter_disc(Config) when is_list(Config) ->
+ dirty_iter(Config, disc_copies).
+
+dirty_iter_disc_only(suite) -> [];
+dirty_iter_disc_only(Config) when is_list(Config) ->
+ dirty_iter(Config, disc_only_copies).
+
+dirty_iter(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = dirty_iter,
+ Def = [{type, bag}, {attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match([], all_slots(Tab)),
+ ?match([], all_nexts(Tab)),
+
+ Keys = lists:seq(1, 5),
+ Records = [{Tab, A, B} || A <- Keys, B <- lists:seq(1, 2)],
+ lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records),
+
+ SortedRecords = lists:sort(Records),
+ ?match(SortedRecords, lists:sort(all_slots(Tab))),
+ ?match(Keys, lists:sort(all_nexts(Tab))),
+
+ ?match({'EXIT', _}, mnesia:dirty_first(foo)),
+ ?match({'EXIT', _}, mnesia:dirty_next(foo, foo)),
+ ?match({'EXIT', _}, mnesia:dirty_slot(foo, 0)),
+ ?match({'EXIT', _}, mnesia:dirty_slot(foo, [])),
+ ?match({atomic, Keys},
+ mnesia:transaction(fun() -> lists:sort(all_nexts(Tab)) end)),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Returns a list of all keys in table
+all_slots(Tab) ->
+ all_slots(Tab, [], 0).
+
+all_slots(_Tab, '$end_of_table', _) ->
+ [];
+all_slots(Tab, PrevRecords, PrevSlot) ->
+ Records = mnesia:dirty_slot(Tab, PrevSlot),
+ PrevRecords ++ all_slots(Tab, Records, PrevSlot + 1).
+
+%% Returns a list of all keys in table
+
+all_nexts(Tab) ->
+ FirstKey = mnesia:dirty_first(Tab),
+ all_nexts(Tab, FirstKey).
+
+all_nexts(_Tab, '$end_of_table') ->
+ [];
+all_nexts(Tab, PrevKey) ->
+ Key = mnesia:dirty_next(Tab, PrevKey),
+ [PrevKey] ++ all_nexts(Tab, Key).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+admin_tests(doc) ->
+ ["Verifies that dirty operations work during schema operations"];
+
+admin_tests(suite) ->
+ [del_table_copy_1,
+ del_table_copy_2,
+ del_table_copy_3,
+ add_table_copy_1,
+ add_table_copy_2,
+ add_table_copy_3,
+ add_table_copy_4,
+ move_table_copy_1,
+ move_table_copy_2,
+ move_table_copy_3,
+ move_table_copy_4].
+
+update_trans(Tab, Key, Acc) ->
+ Update =
+ fun() ->
+ Res = (catch mnesia:read({Tab, Key})),
+ case Res of
+ [{Tab, Key, Extra, Acc}] ->
+ mnesia:write({Tab,Key,Extra, Acc+1});
+ Val ->
+ {read, Val, {acc, Acc}}
+ end
+ end,
+ receive
+ {Pid, quit} -> Pid ! {self(), Acc}
+ after
+ 3 ->
+ case catch mnesia:sync_dirty(Update) of
+ ok ->
+ update_trans(Tab, Key, Acc+1);
+ Else ->
+ ?error("Dirty Operation failed on ~p (update no ~p) with ~p~n"
+ "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n",
+ [node(),
+ Acc,
+ Else,
+ mnesia:table_info(Tab, where_to_read),
+ mnesia:table_info(Tab, where_to_write),
+ mnesia:table_info(Tab, where_to_commit),
+ mnesia:table_info(Tab, storage_type)])
+ end
+ end.
+
+del_table_copy_1(suite) -> [];
+del_table_copy_1(Config) when is_list(Config) ->
+ [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config),
+ del_table(Node2, Node2, Nodes). %Called on same Node as deleted
+del_table_copy_2(suite) -> [];
+del_table_copy_2(Config) when is_list(Config) ->
+ [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config),
+ del_table(Node1, Node2, Nodes). %Called from other Node
+del_table_copy_3(suite) -> [];
+del_table_copy_3(Config) when is_list(Config) ->
+ [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ del_table(Node3, Node2, Nodes). %Called from Node w.o. table
+
+del_table(CallFrom, DelNode, [Node1, Node2, Node3]) ->
+ Tab = schema_ops,
+ Def = [{disc_only_copies, [Node1]}, {ram_copies, [Node2]},
+ {attributes, [key, attr1, attr2]}],
+ ?log("Test case removing table from ~w, with ~w~n", [DelNode, Def]),
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 1000),
+
+ Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]),
+ Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]),
+ Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]),
+
+
+ dbg:tracer(process, {fun(Msg,_) -> tracer(Msg) end, void}),
+ %% dbg:n(Node2),
+ %% dbg:n(Node3),
+ %% dbg:tp('_', []),
+ %% dbg:tpl(dets, [timestamp]),
+ dbg:p(Pid1, [m,c,timestamp]),
+
+ ?match({atomic, ok},
+ rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])),
+
+ Pid1 ! {self(), quit}, R1 =
+ receive {Pid1, Res1} -> Res1
+ after
+ 5000 -> io:format("~p~n",[process_info(Pid1)]),error
+ end,
+ Pid2 ! {self(), quit}, R2 =
+ receive {Pid2, Res2} -> Res2
+ after
+ 5000 -> error
+ end,
+ Pid3 ! {self(), quit}, R3 =
+ receive {Pid3, Res3} -> Res3
+ after
+ 5000 -> error
+ end,
+ verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3),
+ ?verify_mnesia([Node1, Node2, Node3], []).
+
+tracer({trace_ts, _, send, Msg, Pid, {_,S,Ms}}) ->
+ io:format("~p:~p ~p >> ~w ~n",[S,Ms,Pid,Msg]);
+tracer({trace_ts, _, 'receive', Msg, {_,S,Ms}}) ->
+ io:format("~p:~p << ~w ~n",[S,Ms,Msg]);
+
+
+tracer(Msg) ->
+ io:format("UMsg ~p ~n",[Msg]),
+ ok.
+
+
+
+add_table_copy_1(suite) -> [];
+add_table_copy_1(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{ram_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node1, Node3, Nodes, Def).
+%% Not so much diff from 1 but I got a feeling of a bug
+%% should behave exactly the same but just checking the internal ordering
+add_table_copy_2(suite) -> [];
+add_table_copy_2(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{ram_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node2, Node3, Nodes, Def).
+add_table_copy_3(suite) -> [];
+add_table_copy_3(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{ram_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node3, Node3, Nodes, Def).
+add_table_copy_4(suite) -> [];
+add_table_copy_4(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_only_copies, [Node1]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node2, Node3, Nodes, Def).
+
+add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) ->
+ ?log("Test case adding table at ~w, with ~w~n", [AddNode, Def]),
+ Tab = schema_ops,
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 1002),
+
+ Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]),
+ Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]),
+ Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]),
+
+ ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy,
+ [Tab, AddNode, ram_copies])),
+ Pid1 ! {self(), quit}, R1 = receive {Pid1, Res1} -> Res1 after 5000 -> error end,
+ Pid2 ! {self(), quit}, R2 = receive {Pid2, Res2} -> Res2 after 5000 -> error end,
+ Pid3 ! {self(), quit}, R3 = receive {Pid3, Res3} -> Res3 after 5000 -> error end,
+ verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3),
+ ?verify_mnesia([Node1, Node2, Node3], []).
+
+move_table_copy_1(suite) -> [];
+move_table_copy_1(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{ram_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node1, Node1, Node3, Nodes, Def).
+move_table_copy_2(suite) -> [];
+move_table_copy_2(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{ram_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node2, Node1, Node3, Nodes, Def).
+move_table_copy_3(suite) -> [];
+move_table_copy_3(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{ram_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node3, Node1, Node3, Nodes, Def).
+move_table_copy_4(suite) -> [];
+move_table_copy_4(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{ram_copies, [Node1]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node2, Node1, Node3, Nodes, Def).
+
+move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) ->
+ ?log("Test case move table from ~w to ~w, with ~w~n", [FromNode, ToNode, Def]),
+ Tab = schema_ops,
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 1002),
+
+ Pid1 = spawn_link(Node1, ?MODULE, update_trans, [Tab, 1, 0]),
+ Pid2 = spawn_link(Node2, ?MODULE, update_trans, [Tab, 2, 0]),
+ Pid3 = spawn_link(Node3, ?MODULE, update_trans, [Tab, 3, 0]),
+
+ ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy,
+ [Tab, FromNode, ToNode])),
+ Pid1 ! {self(), quit},
+ R1 = receive {Pid1, Res1} -> Res1 after 5000 -> ?error("timeout pid1~n", []) end,
+ Pid2 ! {self(), quit},
+ R2 = receive {Pid2, Res2} -> Res2 after 5000 -> ?error("timeout pid2~n", []) end,
+ Pid3 ! {self(), quit},
+ R3 = receive {Pid3, Res3} -> Res3 after 5000 -> ?error("timeout pid3~n", []) end,
+ verify_oids(Tab, Node1, Node2, Node3, R1, R2, R3),
+ ?verify_mnesia([Node1, Node2, Node3], []).
+
+% Verify consistency between different nodes
+% Due to limitations in the current dirty_ops this can wrong from time to time!
+verify_oids(Tab, N1, N2, N3, R1, R2, R3) ->
+ io:format("DEBUG 1=>~p 2=>~p 3=>~p~n", [R1,R2,R3]),
+ ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])),
+ ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])),
+ ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])),
+ ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])),
+ ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])),
+ ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])).
+
+insert(_Tab, 0) -> ok;
+insert(Tab, N) when N > 0 ->
+ ok = mnesia:sync_dirty(fun() -> false = mnesia:is_transaction(), mnesia:write({Tab, N, N, 0}) end),
+ insert(Tab, N-1).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/mnesia/test/mnesia_durability_test.erl b/lib/mnesia/test/mnesia_durability_test.erl
new file mode 100644
index 0000000000..b917b0ca40
--- /dev/null
+++ b/lib/mnesia/test/mnesia_durability_test.erl
@@ -0,0 +1,1470 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(mnesia_durability_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).
+
+-record(test_rec,{key,val}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(doc) ->
+ ["Verify durability",
+ "Verify that the effects of committed transactions are durable.",
+ "The content of the tables tables must be restored at startup."];
+all(suite) ->
+ [
+ load_tables,
+ durability_of_dump_tables,
+ durability_of_disc_copies,
+ durability_of_disc_only_copies
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+load_tables(doc) ->
+ ["Try to provoke all kinds of table load scenarios."];
+load_tables(suite) ->
+ [
+ load_latest_data,
+ load_local_contents_directly,
+ load_directly_when_all_are_ram_copiesA,
+ load_directly_when_all_are_ram_copiesB,
+ late_load_when_all_are_ram_copies_on_ram_nodes,
+ load_when_last_replica_becomes_available,
+ load_when_we_have_down_from_all_other_replica_nodes,
+ late_load_transforms_into_disc_load,
+ late_load_leads_to_hanging,
+ force_load_when_nobody_intents_to_load,
+ force_load_when_someone_has_decided_to_load,
+ force_load_when_someone_else_already_has_loaded,
+ force_load_when_we_has_loaded,
+ force_load_on_a_non_local_table,
+ force_load_when_the_table_does_not_exist,
+ load_tables_with_master_tables
+ ].
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+load_latest_data(doc) ->
+ ["Base functionality, verify that the latest data is loaded"];
+load_latest_data(suite) -> [];
+load_latest_data(Config) when is_list(Config) ->
+ [N1,N2,N3] = Nodes = ?acquire_nodes(3, Config),
+ %%Create a replicated local table
+ ?match({atomic,ok}, mnesia:create_table(t0, [{disc_copies,[N1,N2]}])),
+ ?match({atomic,ok}, mnesia:create_table(t1, [{disc_copies,[N1,N2]}])),
+ ?match({atomic,ok}, mnesia:create_table(t2, [{disc_copies,[N1,N2]}])),
+ ?match({atomic,ok}, mnesia:create_table(t3, [{disc_copies,[N1,N2]}])),
+ ?match({atomic,ok}, mnesia:create_table(t4, [{disc_copies,[N1,N2]}])),
+ ?match({atomic,ok}, mnesia:create_table(t5, [{disc_copies,[N1,N2]}])),
+ Rec1 = {t1, test, ok},
+ Rec2 = {t1, test, 2},
+
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+ ?match(ok, rpc:call(N2, mnesia, dirty_write, [Rec2])),
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+
+ ?match([], mnesia_test_lib:start_mnesia([N1], [])),
+ %% Should wait for N2
+ ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])),
+ ?match([], mnesia_test_lib:start_mnesia([N3], [])),
+ ?match({timeout, [t1]}, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])),
+
+
+ ?match([], mnesia_test_lib:start_mnesia([N2], [])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 3000])),
+ ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 3000])),
+ %% We should find the record
+ ?match([Rec2], rpc:call(N1, mnesia, dirty_read, [t1, test])),
+ ?match([Rec2], rpc:call(N2, mnesia, dirty_read, [t1, test])),
+
+ %% ok, lets switch order
+ ?match(ok, mnesia:dirty_delete_object(Rec1)),
+ ?match(ok, mnesia:dirty_delete_object(Rec2)),
+ %% redo
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ ?match(ok, mnesia:dirty_write(Rec1)),
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+
+ ?match([], mnesia_test_lib:start_mnesia([N2], [])),
+ %% Should wait for N1
+ ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])),
+ ?match([], mnesia_test_lib:start_mnesia([N3], [])),
+ ?match({timeout, [t1]}, rpc:call(N2, mnesia, wait_for_tables, [[t1], 2000])),
+ ?match([], mnesia_test_lib:start_mnesia([N1], [])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[t1], 1000])),
+ ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[t1], 1000])),
+ %% We should find the record
+ ?match([Rec1], rpc:call(N1, mnesia, dirty_read, [t1, test])),
+ ?match([Rec1], rpc:call(N2, mnesia, dirty_read, [t1, test])),
+
+ ?verify_mnesia(Nodes, []).
+
+
+load_local_contents_directly(doc) ->
+ ["Local contents shall always be loaded. Check this by having a local ",
+ "table on two nodes N1, N2, stopping N1 before N2, an then verifying ",
+ "that N1 can start without N2 being started."];
+load_local_contents_directly(suite) -> [];
+load_local_contents_directly(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ %%Create a replicated local table
+ ?match({atomic,ok},
+ mnesia:create_table(test_rec,
+ [{local_content,true},
+ {disc_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}]
+ ) ),
+ %%Verify that it has local contents.
+ ?match( true, mnesia:table_info(test_rec,local_content) ),
+ %%Helper Funs
+ Write_one = fun(Value) -> mnesia:write(#test_rec{key=1,val=Value}) end,
+ Read_one = fun(Key) -> mnesia:read( {test_rec, Key}) end,
+ %%Write a value one N1 that we may test against later
+ ?match({atomic,ok},
+ rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ),
+ %%Stop Mnesia on N1
+ %?match([], mnesia_test_lib:stop_mnesia([N1])),
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+
+ %%Write a value on N2, same key but a different value
+ ?match({atomic,ok},
+ rpc:call( N2, mnesia, transaction, [Write_one,[22]] ) ),
+ %%Stop Mnesia on N2
+ %?match([], mnesia_test_lib:stop_mnesia([N2])),
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+
+ %%Restart Mnesia on N1 verify that we can read from it without
+ %%starting Mnesia on N2.
+ ?match(ok, rpc:call(N1, mnesia, start, [])),
+ ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])),
+ %%Read back the value
+ ?match( {atomic,[#test_rec{key=1,val=11}]},
+ rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ),
+ %%Restart Mnesia on N2 and verify the contents there.
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])),
+ ?match( {atomic,[#test_rec{key=1,val=22}]},
+ rpc:call(N2, mnesia, transaction, [Read_one,[1]] ) ),
+ %%Check that the start of Mnesai on N2 did not affect the contents on N1
+ ?match( {atomic,[#test_rec{key=1,val=11}]},
+ rpc:call(N1, mnesia, transaction, [Read_one,[1]] ) ),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+load_directly_when_all_are_ram_copiesA(doc) ->
+ ["Tables that are RAM copies only shall also be loaded directly. ",
+ "1. N1 and N2 has RAM copies of a table, stop N1 before N2. ",
+ "2. When N1 starts he shall have access to the table ",
+ " without having to start N2" ];
+load_directly_when_all_are_ram_copiesA(suite) -> [];
+load_directly_when_all_are_ram_copiesA(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+
+ ?match({atomic,ok},
+ mnesia:create_table(test_rec,
+ [{ram_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}]
+ ) ),
+ ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ),
+ ?match( [], mnesia:table_info(test_rec,disc_copies) ),
+ ?match( [], mnesia:table_info(test_rec,disc_only_copies) ),
+ Write_one = fun(Value) -> mnesia:write(#test_rec{key=2,val=Value}) end,
+ Read_one = fun() -> mnesia:read({test_rec,2}) end,
+ %%Write a value one N1 that we may test against later
+ ?match({atomic,ok},
+ rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ),
+ %%Stop Mnesia on N1
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+ %%Write a value and check result (on N2; not possible on N1
+ %%since Mnesia is stopped there).
+ ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ),
+ ?match({atomic,[#test_rec{key=2,val=22}]},
+ rpc:call(N2,mnesia,transaction,[Read_one]) ),
+ %%Stop Mnesia on N2
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ %%Restart Mnesia on N1 verify that we can access test_rec from
+ %%N1 without starting Mnesia on N2.
+ ?match(ok, rpc:call(N1, mnesia, start, [])),
+ ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])),
+ ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one])),
+ ?match({atomic,ok}, rpc:call(N1,mnesia,transaction,[Write_one,[33]])),
+ ?match({atomic,[#test_rec{key=2,val=33}]},
+ rpc:call(N1,mnesia,transaction,[Read_one])),
+ %%Restart Mnesia on N2 and verify the contents there.
+ ?match([], mnesia_test_lib:start_mnesia([N2], [test_rec])),
+ ?match( {atomic,[#test_rec{key=2,val=33}]},
+ rpc:call(N2, mnesia, transaction, [Read_one] ) ),
+ %%Check that the start of Mnesai on N2 did not affect the contents on N1
+ ?match( {atomic,[#test_rec{key=2,val=33}]},
+ rpc:call(N1, mnesia, transaction, [Read_one] ) ),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+load_directly_when_all_are_ram_copiesB(doc) ->
+ ["Tables that are RAM copies only shall be loaded from a replicat ",
+ "when possible. ",
+ "1. N1 and N2 has RAM copies of a table, stop N1 before N2.",
+ "2. Now start N2 first and then N1, N1 shall then load the table ",
+ " from N2."];
+load_directly_when_all_are_ram_copiesB(suite) -> [];
+load_directly_when_all_are_ram_copiesB(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ ?match({atomic,ok},
+ mnesia:create_table(test_rec,
+ [{ram_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}]
+ ) ),
+ ?match( Nodes, mnesia:table_info(test_rec,ram_copies) ),
+ ?match( [], mnesia:table_info(test_rec,disc_copies) ),
+ ?match( [], mnesia:table_info(test_rec,disc_only_copies) ),
+ Write_one = fun(Value) -> mnesia:write(#test_rec{key=3,val=Value}) end,
+ Read_one = fun() -> mnesia:read( {test_rec, 3}) end,
+ %%Write a value one N1 that we may test against later
+ ?match({atomic,ok},
+ rpc:call( N1, mnesia, transaction, [Write_one,[11]] ) ),
+ ?match({atomic,[#test_rec{key=3,val=11}]},
+ rpc:call(N2,mnesia,transaction,[Read_one]) ),
+ %%Stop Mnesia on N1
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+ %%Write a value and check result (on N2; not possible on N1
+ %%since Mnesia is stopped there).
+ ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[22]]) ),
+ ?match({atomic,[#test_rec{key=3,val=22}]},
+ rpc:call(N2,mnesia,transaction,[Read_one]) ),
+ %%Stop Mnesia on N2
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ %%Restart Mnesia on N2 verify that we can access test_rec from
+ %%N2 without starting Mnesia on N1.
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])),
+ ?match({atomic,[]}, rpc:call(N2,mnesia,transaction,[Read_one])),
+ ?match({atomic,ok}, rpc:call(N2,mnesia,transaction,[Write_one,[33]])),
+ ?match({atomic,[#test_rec{key=3,val=33}]},
+ rpc:call(N2,mnesia,transaction,[Read_one])),
+ %%Restart Mnesia on N1 and verify the contents there.
+ ?match([], mnesia_test_lib:start_mnesia([N1], [test_rec])),
+ ?match( {atomic,[#test_rec{key=3,val=33}]},
+ rpc:call(N1,mnesia,transaction,[Read_one])),
+ %%Check that the start of Mnesai on N1 did not affect the contents on N2
+ ?match( {atomic,[#test_rec{key=3,val=33}]},
+ rpc:call(N2,mnesia,transaction,[Read_one])),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+late_load_when_all_are_ram_copies_on_ram_nodes(doc) ->
+ ["Load of ram_copies tables when all replicas resides on disc less nodes"];
+late_load_when_all_are_ram_copies_on_ram_nodes(suite) ->
+ [
+ late_load_when_all_are_ram_copies_on_ram_nodes1,
+ late_load_when_all_are_ram_copies_on_ram_nodes2
+ ].
+
+late_load_when_all_are_ram_copies_on_ram_nodes1(suite) -> [];
+late_load_when_all_are_ram_copies_on_ram_nodes1(Config) when is_list(Config) ->
+ [N1, N2] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema,
+ {reload_appls, [mnesia]}],
+ 2, Config, ?FILE, ?LINE),
+ Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2], Config),
+ mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}],
+ 2, Config, ?FILE, ?LINE),
+ Res.
+
+late_load_when_all_are_ram_copies_on_ram_nodes2(suite) -> [];
+late_load_when_all_are_ram_copies_on_ram_nodes2(Config) when is_list(Config) ->
+ [N1, N2, N3] = mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema,
+ {reload_appls, [mnesia]}],
+ 3, Config, ?FILE, ?LINE),
+ Res = late_load_when_all_are_ram_copies_on_ram_nodes(N1, [N2, N3], Config),
+ mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}],
+ 3, Config, ?FILE, ?LINE),
+ Res.
+
+late_load_when_all_are_ram_copies_on_ram_nodes(DiscNode, RamNs, _Config)
+ when DiscNode == node() ->
+ ?match(ok, mnesia:create_schema([DiscNode])),
+ ?match(ok, mnesia:start()),
+ Nodes = [DiscNode | RamNs],
+ Extra = [{extra_db_nodes, Nodes}],
+ Ok = [ok || _ <- RamNs],
+ ?match({Ok, []}, rpc:multicall(RamNs, mnesia, start, [Extra])),
+ ?match([], wait_until_running(Nodes)),
+
+ LastRam = lists:last(RamNs),
+ %% ?match({atomic, ok},
+ %% mnesia:add_table_copy(schema, LastRam, ram_copies)),
+ Def = [{ram_copies, RamNs}, {attributes, record_info(fields, test_rec)}],
+ ?match({atomic,ok}, mnesia:create_table(test_rec, Def)),
+ ?verify_mnesia(Nodes, []),
+ ?match([], mnesia_test_lib:stop_mnesia(RamNs)),
+ ?match(stopped, mnesia:stop()),
+ ?match(ok, mnesia:start()),
+
+ Rec1 = #test_rec{key=3, val=33},
+ Rec2 = #test_rec{key=4, val=44},
+
+ FirstRam = hd(RamNs),
+ ?match(ok, rpc:call(FirstRam, mnesia, start, [Extra])),
+ ?match(ok, rpc:call(FirstRam, mnesia, wait_for_tables,
+ [[test_rec], 30000])),
+ ?match(ok, rpc:call(FirstRam, mnesia, dirty_write,[Rec1])),
+ ?match(ok, mnesia:wait_for_tables([test_rec], 30000)),
+ mnesia:dirty_write(Rec2),
+
+ if
+ FirstRam /= LastRam ->
+ ?match(ok, rpc:call(LastRam, mnesia, start, [Extra])),
+ ?match(ok, rpc:call(LastRam, mnesia, wait_for_tables,
+ [[test_rec], 30000]));
+ true ->
+ ignore
+ end,
+ ?match([Rec1], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 3}])),
+ ?match([Rec2], rpc:call(LastRam, mnesia, dirty_read, [{test_rec, 4}])),
+ ?verify_mnesia(Nodes, []).
+
+wait_until_running(Nodes) ->
+ wait_until_running(Nodes, 30).
+
+wait_until_running(Nodes, Times) when Times > 0->
+ Alive = mnesia:system_info(running_db_nodes),
+ case Nodes -- Alive of
+ [] ->
+ [];
+ Remaining ->
+ timer:sleep(timer:seconds(1)),
+ wait_until_running(Remaining, Times - 1)
+ end;
+wait_until_running(Nodes, _) ->
+ Nodes.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+load_when_last_replica_becomes_available(doc) ->
+ ["Check that when all Mnesia nodes die at the same instant, then the ",
+ "replicated table shall be accessible when the last node is started ",
+ "again.",
+ "Checked by cheating. Start Mnesia on N1, N2, N3. Have a table ",
+ "replicated on disc on all three nodes, fill in some transactions, ",
+ "install a fallback. Restart mnesia on all nodes"
+ "This is the cheat and it simulates that all nodes died at the same ",
+ "time. Check that the table is only accessible after the last node ",
+ "has come up."];
+load_when_last_replica_becomes_available(suite) -> [];
+load_when_last_replica_becomes_available(Config) when is_list(Config) ->
+ [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config),
+ ?match({atomic,ok},
+ mnesia:create_table(test_rec,
+ [{disc_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}]
+ ) ),
+ ?match( [], mnesia:table_info(test_rec,ram_copies) ),
+ ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ),
+ ?match( [], mnesia:table_info(test_rec,disc_only_copies) ),
+ Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end,
+ Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end,
+ %%Write one value from each node.
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,11]])),
+ ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,22]])),
+ ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,33]])),
+ %%Check the values
+ ?match({atomic,[#test_rec{key=1,val=11}]},
+ rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[#test_rec{key=2,val=22}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[#test_rec{key=3,val=33}]},
+ rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ),
+
+ ?match(ok, mnesia:backup("test_last_replica")),
+ ?match(ok, mnesia:install_fallback("test_last_replica")),
+ file:delete("test_last_replica"),
+ %%Stop Mnesia on all three nodes
+ ?match([], mnesia_test_lib:kill_mnesia(Nodes)),
+
+ %%Start Mnesia on one node, make sure that test_rec is not available
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match({timeout,[test_rec]},
+ rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 10000])),
+ ?match(ok, rpc:call(N1, mnesia, start, [])),
+ ?match({timeout,[test_rec]},
+ rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 10000])),
+ %%Start the third node
+ ?match(ok, rpc:call(N3, mnesia, start, [])),
+ %%Make sure that the table is loaded everywhere
+ ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])),
+ ?match(ok, rpc:call(N1, mnesia, wait_for_tables, [[test_rec], 30000])),
+
+ %%Check the values
+ ?match({atomic,[#test_rec{key=1,val=11}]},
+ rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[#test_rec{key=2,val=22}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[#test_rec{key=3,val=33}]},
+ rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+load_when_we_have_down_from_all_other_replica_nodes(doc) ->
+ ["The table can be loaded if this node was the last one surviving. ",
+ "Check this by having N1, N2, N3 and a table replicated on all those ",
+ "nodes. Then kill them in the N1, N2, N3 order. Then start N3 and ",
+ "verify that the table is available with correct contents."];
+load_when_we_have_down_from_all_other_replica_nodes(suite) -> [];
+load_when_we_have_down_from_all_other_replica_nodes(Config) when is_list(Config) ->
+ [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config),
+ ?match({atomic,ok},
+ mnesia:create_table(test_rec,
+ [{disc_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}]
+ ) ),
+ ?match( [], mnesia:table_info(test_rec,ram_copies) ),
+ ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ),
+ ?match( [], mnesia:table_info(test_rec,disc_only_copies) ),
+ Write_one = fun(Key,Val)->mnesia:write(#test_rec{key=Key,val=Val}) end,
+ Read_one = fun(Key) ->mnesia:read( {test_rec, Key}) end,
+ %%Write one value from each node.
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[1,111]])),
+ ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[2,222]])),
+ ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[3,333]])),
+ %%Check the values
+ ?match({atomic,[#test_rec{key=1,val=111}]},
+ rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[#test_rec{key=2,val=222}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[#test_rec{key=3,val=333}]},
+ rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ),
+ %%Stop Mnesia on all three nodes
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+ ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[22,22]])),
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ ?match({atomic,ok},rpc:call(N3,mnesia,transaction,[Write_one,[33,33]])),
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+ ?verbose("Mnesia stoped on all three nodes.~n",[]),
+
+ %%Start Mnesia on N3; wait for 'test_rec' table to load
+ ?match(ok, rpc:call(N3, mnesia, start, [])),
+ ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[test_rec], 30000])),
+
+ %%Check the values
+ ?match({atomic,[#test_rec{key=1,val=111}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[#test_rec{key=2,val=222}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[#test_rec{key=3,val=333}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[3]]) ),
+ ?match({atomic,[#test_rec{key=22,val=22}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[22]]) ),
+ ?match({atomic,[#test_rec{key=33,val=33}]},
+ rpc:call(N3,mnesia,transaction,[Read_one,[33]]) ),
+ ?verify_mnesia([N3], [N1, N2]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+late_load_transforms_into_disc_load(doc) ->
+ ["Difficult case that needs instrumentation of Mnesia.",
+ "A table is force loaded, and Mnesia decides to load it from another ",
+ "Mnesia node because it is avaliable there. The other Mnesia node then ",
+ "dies in mid copy which shall make the first Mnesia node to really ",
+ "force load from disc.",
+ "Check this by starting N1 and N2 and replicating a table between ",
+ "them. Then kill N1 before N2. The idea is to start N2 first, then ",
+ "N1 and then do a force load on N1. This force load will load from ",
+ "N2 BUT N2 must be killed after the decision to load from it has ",
+ "been made. tricky."];
+
+late_load_transforms_into_disc_load(suite) -> [];
+late_load_transforms_into_disc_load(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+
+ {success, [A, B]} = ?start_activities(Nodes),
+
+ ?match(Node1, node(A)),
+ ?match(Node2, node(B)),
+
+ Tab = late_load_table,
+ Def = [{attributes, [key, value]},
+ {disc_copies, Nodes}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 111, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 222, 42})),
+
+ TestPid = self(),
+ DebugId = {mnesia_loader, do_get_network_copy},
+ DebugFun = fun(PrevContext, EvalContext) ->
+ ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n",
+ [self(),PrevContext,EvalContext]),
+
+ mnesia_test_lib:kill_mnesia([Node2]),
+ TestPid ! {self(),debug_fun_was_called},
+
+ ?verbose("interrupt late_log - continues ~n",[]),
+ ?deactivate_debug_fun(DebugId),
+ PrevContext+1
+ end,
+ ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1),
+
+ %% kill mnesia on node1
+ mnesia_test_lib:kill_mnesia([Node1]),
+ %% wait a while, so that mnesia is really down
+ timer:sleep(timer:seconds(1)),
+
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])),
+
+ %% start Mnesia on node1
+ ?match(ok,mnesia:start()),
+ ?match(yes, mnesia:force_load_table(Tab)),
+ ?match(ok, mnesia:wait_for_tables([Tab],timer:seconds(30))),
+
+ receive_messages([debug_fun_was_called]),
+
+ check_tables([A],[{Tab,111},{Tab,222}],[[{Tab,111,4711}],[{Tab,222,42}]]),
+ ?verify_mnesia([Node1], [Node2]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+late_load_leads_to_hanging(doc) ->
+ ["Difficult case that needs instrumentation of Mnesia.",
+ "A table is loaded, and Mnesia decides to load it from another ",
+ "Mnesia node because it has the latest copy there. ",
+ "The other Mnesia node then ",
+ "dies in mid copy which shall make the first Mnesia node not to ",
+ "force load from disc but to wait for the other node to come up again",
+ "Check this by starting N1 and N2 and replicating a table between ",
+ "them. Then kill N1 before N2. The idea is to start N2 first, then ",
+ "N1. This load will load from ",
+ "N2 BUT N2 must be killed after the decision to load from it has ",
+ "been made. tricky."];
+
+late_load_leads_to_hanging(suite) -> [];
+late_load_leads_to_hanging(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+
+ Tab = late_load_table,
+ Def = [{attributes, [key, value]},
+ {disc_copies, Nodes}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 111, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 222, 42})),
+
+ DebugId = {mnesia_loader, do_get_network_copy},
+ DebugFun = fun(PrevContext, EvalContext) ->
+ ?verbose("interrupt late load, pid ~p #~p ~n context ~p ~n",
+ [self(), PrevContext, EvalContext]),
+ mnesia_test_lib:kill_mnesia([Node2]),
+ ?verbose("interrupt late load - continues ~n",[]),
+ ?deactivate_debug_fun(DebugId),
+ PrevContext+1
+ end,
+
+ ?remote_activate_debug_fun(Node1,DebugId, DebugFun, 1),
+ mnesia_test_lib:kill_mnesia([Node1]),
+ %% wait a while, so that mnesia is really down
+ timer:sleep(timer:seconds(1)),
+
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 333, 666}])),
+
+ %% start Mnesia on node1
+ ?match(ok, mnesia:start()),
+
+ ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], timer:seconds(2))),
+
+ ?match({'EXIT', {aborted, _}}, mnesia:dirty_read({Tab, 222})),
+ %% mnesia on node1 is waiting for node2 coming up
+
+ ?match(ok, rpc:call(Node2, mnesia, start, [])),
+ ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))),
+ ?match([{Tab, 333, 666}], mnesia:dirty_read({Tab, 333})),
+ ?verify_mnesia([Node2, Node1], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+force_load_when_nobody_intents_to_load(doc) ->
+ ["Normal force load. Start N1 N2, kill in N1, N2 order. Start N1 do ",
+ "force load. Did it work?"];
+force_load_when_nobody_intents_to_load(suite) -> [];
+force_load_when_nobody_intents_to_load(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ Table = test_rec,
+ Trec1a = #test_rec{key=1,val=111},
+ Trec1b = #test_rec{key=1,val=333},
+ Trec2a = #test_rec{key=2,val=222},
+ Trec3a = #test_rec{key=3,val=333},
+ Trec3b = #test_rec{key=3,val=666},
+
+ ?match({atomic,ok}, rpc:call(N1, mnesia,create_table,
+ [Table,
+ [{disc_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}
+ ] ] ) ),
+ ?match( [], mnesia:table_info(Table,ram_copies) ),
+ ?match( Nodes, mnesia:table_info(Table,disc_copies) ),
+ ?match( [], mnesia:table_info(Table,disc_only_copies) ),
+ Write_one = fun(Rec) -> mnesia:write(Rec) end,
+ Read_one = fun(Key) -> mnesia:read({Table, Key}) end,
+ %%Write one value
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1a]])),
+ %%Check it
+ ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+ %%Shut down mnesia on N1
+ ?match([], mnesia_test_lib:stop_mnesia([N1])),
+ %%Write and check value while N1 is down
+ ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec1b]])),
+ ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2a]])),
+ ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec3a]])),
+ ?match({aborted,{node_not_running,N1}},
+ rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[Trec1b]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[Trec2a]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[Trec3a]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ),
+ %%Shut down Mnesia on N2
+ ?match([], mnesia_test_lib:stop_mnesia([N2])),
+
+ %%Restart Mnesia on N1
+ ?match(ok, rpc:call(N1, mnesia, start, [])),
+ %%Check that table is not available (waiting for N2)
+ ?match({timeout,[Table]},
+ rpc:call(N1, mnesia, wait_for_tables, [[Table], 3000])),
+
+ %%Force load on N1
+ ?match(yes,rpc:call(N1,mnesia,force_load_table,[Table])),
+ %%Check values
+ ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[]}, rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ),
+ %%Write a value for key=3
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec3b]])),
+
+ %%Restart N2 and check values
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[Table], 30000])),
+
+ ?match({atomic,[Trec1a]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[Trec1a]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+
+ ?match({atomic,[]},rpc:call(N1,mnesia,transaction,[Read_one,[2]]) ),
+ ?match({atomic,[]},rpc:call(N2,mnesia,transaction,[Read_one,[2]]) ),
+
+ ?match({atomic,[Trec3b]},rpc:call(N1,mnesia,transaction,[Read_one,[3]]) ),
+ ?match({atomic,[Trec3b]},rpc:call(N2,mnesia,transaction,[Read_one,[3]]) ),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+force_load_when_someone_has_decided_to_load(doc) ->
+ ["Difficult case that needs instrumentation of Mnesia.",
+ "Start N1 and N2, replicate table, kill in N1, N2 order. Start N2 ",
+ "and start N1 before N2 has really loaded the table but after N2 has ",
+ "decided to load it."];
+
+force_load_when_someone_has_decided_to_load(suite) -> [];
+force_load_when_someone_has_decided_to_load(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ {success, [A, B]} = ?start_activities(Nodes),
+ ?match(Node1, node(A)), %% Just to check :)
+ ?match(Node2, node(B)),
+
+ Tab = late_load_table,
+ Def = [{attributes, [key, value]}, {disc_copies, Nodes}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 111, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 222, 42})),
+
+ Self = self(),
+ DebugId = {mnesia_controller, late_disc_load},
+ DebugFun = fun(PrevContext, EvalContext) ->
+ ?verbose("interrupt late disc load,
+ pid ~p #~p ~n context ~p ~n",
+ [self(),PrevContext,EvalContext]),
+ Self ! {self(), fun_in_postion},
+ wait_for_signal(),
+ ?verbose("interrupt late disc load - continues ~n",[]),
+ ?deactivate_debug_fun(DebugId),
+ PrevContext+1
+ end,
+
+ %% kill mnesia on node1
+ mnesia_test_lib:kill_mnesia([Node1]),
+ %% wait a while, so that mnesia is really down
+ timer:sleep(timer:seconds(1)),
+
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 222, 815}])),
+ %% kill mnesia on node2
+ mnesia_test_lib:kill_mnesia([Node2]),
+ %% wait a while, so that mnesia is really down
+ timer:sleep(timer:seconds(1)),
+
+ ?remote_activate_debug_fun(Node2,DebugId, DebugFun, 1),
+
+ B ! fun() -> mnesia:start() end,
+ [{Mnesia_Pid, fun_in_postion}] = receive_messages([fun_in_postion]),
+
+ %% start Mnesia on node1
+ A ! fun() -> mnesia:start() end,
+ ?match_receive(timeout),
+% Got some problem with this testcase when we modified mnesia init
+% These test cases are very implementation dependent!
+% A ! fun() -> mnesia:wait_for_tables([Tab], 3000) end,
+% ?match_receive({A, {timeout, [Tab]}}),
+ A ! fun() -> mnesia:force_load_table(Tab) end,
+ ?match_receive(timeout),
+
+ Mnesia_Pid ! continue,
+ ?match_receive({B, ok}),
+ ?match_receive({A, ok}),
+ ?match_receive({A, yes}),
+
+ B ! fun() -> mnesia:wait_for_tables([Tab], 10000) end,
+ ?match_receive({B, ok}),
+ ?match(ok, mnesia:wait_for_tables([Tab], timer:seconds(30))),
+ ?match([{Tab, 222, 815}], mnesia:dirty_read({Tab, 222})),
+ ?verify_mnesia(Nodes, []).
+
+wait_for_signal() ->
+ receive
+ continue -> ok
+ %% Don't eat any other mnesia internal msg's
+ after
+ timer:minutes(2) -> ?error("Timedout in wait_for_signal~n", [])
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+force_load_when_someone_else_already_has_loaded(doc) ->
+ ["Normal case. Do a force load when somebody else has loaded the table. ",
+ "Start N1, N2, kill in N1, N2 order. Start N2 load the table, start N1 ",
+ "force load. Did it work? (i.e: did N1 load the table from N2 as that",
+ "one is the latest version and it is available on N2)"];
+
+force_load_when_someone_else_already_has_loaded(suite) -> [];
+force_load_when_someone_else_already_has_loaded(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ Table = test_rec,
+ Trec1 = #test_rec{key=1,val=111},
+ Trec2 = #test_rec{key=1,val=222},
+
+ ?match({atomic,ok}, rpc:call(N1, mnesia,create_table,
+ [Table,
+ [{disc_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}
+ ] ] ) ),
+ ?match( [], mnesia:table_info(Table,ram_copies) ),
+ ?match( Nodes, mnesia:table_info(Table,disc_copies) ),
+ ?match( [], mnesia:table_info(Table,disc_only_copies) ),
+ Write_one = fun(Rec) -> mnesia:write(Rec) end,
+ Read_one = fun(Key) -> mnesia:read({Table, Key}) end,
+ %%Write one value
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])),
+ %%Check it
+ ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+ %%Shut down mnesia
+ ?match([], mnesia_test_lib:stop_mnesia([N1])),
+ timer:sleep(500),
+ ?match([], mnesia_test_lib:stop_mnesia([N2])),
+ %%Restart Mnesia on N2;wait for tables to load
+ ?match(ok, rpc:call(N2, mnesia, start, [])),
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [[test_rec], 30000])),
+ %%Write one value
+ ?match({atomic,ok},rpc:call(N2,mnesia,transaction,[Write_one,[Trec2]])),
+ %%Start on N1; force load
+ ?match(ok, rpc:call(N1, mnesia, start, [])),
+ %%Force load from file
+ ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])),
+ %%Check the value
+ ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ),
+ %% === there must be a Trec2 here !!!!
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+force_load_when_we_has_loaded(doc) ->
+ ["Force load a table we already have loaded"];
+force_load_when_we_has_loaded(suite) -> [];
+force_load_when_we_has_loaded(Config) when is_list(Config) ->
+ [N1] = Nodes = ?acquire_nodes(1, Config),
+ Table = test_rec,
+ Trec1 = #test_rec{key=1,val=111},
+ Trec2 = #test_rec{key=1,val=222},
+
+ ?match({atomic,ok}, rpc:call(N1, mnesia,create_table,
+ [Table,
+ [{disc_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}
+ ] ] ) ),
+ ?match( [], mnesia:table_info(Table,ram_copies) ),
+ ?match( Nodes, mnesia:table_info(Table,disc_copies) ),
+ ?match( [], mnesia:table_info(Table,disc_only_copies) ),
+ Write_one = fun(Rec) -> mnesia:write(Rec) end,
+ Read_one = fun(Key) -> mnesia:read({Table, Key}) end,
+ %%Write one value
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])),
+ %%Check it
+ ?match({atomic,[Trec1]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ),
+ %%Shut down mnesia
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+ %%Restart Mnesia;wait for tables to load
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [Table])),
+ %%Write one value
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec2]])),
+ %%Force load from file
+ ?match(yes, rpc:call(N1,mnesia,force_load_table,[Table])),
+ %%Check the value
+ ?match({atomic,[Trec2]},rpc:call(N1,mnesia,transaction,[Read_one,[1]]) ),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+force_load_on_a_non_local_table(doc) ->
+ ["This is NOT allowed, the test case is a negative test",
+ "Force load on a table that isn't replicated on this node."];
+force_load_on_a_non_local_table(suite) -> [];
+force_load_on_a_non_local_table(Config) when is_list(Config) ->
+ [N1, N2, N3] = Nodes = ?acquire_nodes( 3, Config),
+ TableNodes = lists:sublist(Nodes,2),
+ Table = test_rec,
+ Trec1 = #test_rec{key=1,val=11},
+
+ ?match({atomic,ok}, rpc:call(N1, mnesia,create_table,
+ [Table,
+ [{disc_copies,TableNodes},
+ {attributes,record_info(fields,test_rec)}
+ ] ] ) ),
+ ?match( [], mnesia:table_info(Table,ram_copies) ),
+ ?match( TableNodes, mnesia:table_info(Table,disc_copies) ),
+ ?match( [], mnesia:table_info(Table,disc_only_copies) ),
+ Write_one = fun(Rec) -> mnesia:write(Rec) end,
+ Read_one = fun(Key) -> mnesia:read({Table, Key}) end,
+ %%Write one value
+ ?match({atomic,ok},rpc:call(N1,mnesia,transaction,[Write_one,[Trec1]])),
+ %%Check it from the other nodes
+ ?match({atomic,[Trec1]},rpc:call(N2,mnesia,transaction,[Read_one,[1]]) ),
+ ?match({atomic,[Trec1]},rpc:call(N3,mnesia,transaction,[Read_one,[1]]) ),
+
+ %%Make sure that Table is non-local
+ ?match_inverse(N3, rpc:call(N3,mnesia,table_info,[Table,where_to_read])),
+ %%Try to force load it
+ ?match(yes, rpc:call(N3,mnesia,force_load_table,[Table])),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+force_load_when_the_table_does_not_exist(doc) ->
+ ["This is NOT allowed, the test case is a negative test",
+ "Force load on a table that doesn't exist."];
+force_load_when_the_table_does_not_exist(suite) -> [];
+force_load_when_the_table_does_not_exist(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes( 2, Config),
+
+ %%Dummy table
+ ?match({atomic,ok},
+ mnesia:create_table(test_rec,
+ [{disc_copies,Nodes},
+ {attributes,record_info(fields,test_rec)}]
+ ) ),
+ ?match( [], mnesia:table_info(test_rec,ram_copies) ),
+ ?match( Nodes, mnesia:table_info(test_rec,disc_copies) ),
+ ?match( [], mnesia:table_info(test_rec,disc_only_copies) ),
+ Tab = dummy,
+ %%Make sure that Tab is an unknown table
+ ?match( false, lists:member(Tab,mnesia:system_info(tables)) ),
+ ?match( {error, {no_exists, Tab}}, mnesia:force_load_table(Tab) ),
+ ?verify_mnesia(Nodes, []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+load_tables_with_master_tables(doc) ->
+ ["Verifies the semantics of different master nodes settings",
+ "The semantics should be:",
+ "1. Mnesia downs, Normally decides from where mnesia should load tables",
+ "2. Master tables (overrides mnesia downs) ",
+ "3. Force load (overrides Master tables) ",
+ "--- 1st from active master nodes",
+ "--- 2nd from active nodes",
+ "--- 3rd get local copy (if ram create new one)"
+ ];
+
+load_tables_with_master_tables(suite) ->
+ [master_nodes,
+ starting_master_nodes,
+ master_on_non_local_tables,
+ remote_force_load_with_local_master_node].
+
+
+-define(SDwrite(Tup), fun() -> mnesia:write(Tup) end).
+
+master_nodes(suite) -> [];
+master_nodes(Config) when is_list(Config) ->
+ [A, B, C] = Nodes = ?acquire_nodes(3, Config),
+ Tab = test_table_master_nodes,
+ ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])),
+
+ %% Test one: Master A and the table should be loaded from A
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ mnesia_test_lib:stop_mnesia([A]),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test 2: Master [A,B] and B is Up the table should be loaded from B
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ mnesia_test_lib:stop_mnesia([A]),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test 3: Master [A,B] and B is down the table should be loaded from A
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ mnesia_test_lib:stop_mnesia([A]),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ mnesia_test_lib:stop_mnesia([B]),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, _Unknown}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test 4: Master [B] and B is Up the table should be loaded from B
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ mnesia_test_lib:stop_mnesia([A]),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test 5: Master [B] and B is down the table should not be loaded
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ mnesia_test_lib:stop_mnesia([A]),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ mnesia_test_lib:stop_mnesia([B]),
+ ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])),
+
+ %% Test 6: Force load on table that couldn't be loaded due to master
+ %% table setttings, loads other active replicas i.e. from C
+
+ ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?match([{Tab, 1, update_2}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, update_2}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, update_2}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ %% Test 7: Master [B] and B is down the table should not be loaded,
+ %% force_load when there are no active replicas availible
+ %% should generate a load of a local table
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ mnesia_test_lib:stop_mnesia([A]),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ mnesia_test_lib:stop_mnesia([B, C]),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])),
+
+ ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])),
+ ?match([{Tab, 1, init}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+
+ ?verify_mnesia([A], [B,C]).
+
+starting_master_nodes(suite) -> [];
+starting_master_nodes(doc) ->
+ ["Complementory to TEST 5 and 6 above, if the master node (B) starts"
+ " and loads the table it should be loaded on the waiting node (A) "];
+starting_master_nodes(Config) when is_list(Config) ->
+ [A, B, C] = Nodes = ?acquire_nodes(3, Config),
+ Tab = starting_master_nodes,
+ ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])),
+ %% Start by checking TEST 5 above.
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+ mnesia_test_lib:stop_mnesia([A]),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ mnesia_test_lib:stop_mnesia([B]),
+ ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, update_2})])),
+
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])),
+ %% Start the B node and the table should be loaded on A!
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+
+ ?verify_mnesia([A,B,C], []).
+
+
+master_on_non_local_tables(suite) -> [];
+master_on_non_local_tables(Config) when is_list(Config) ->
+ [A, B, C] = Nodes = ?acquire_nodes(3, Config),
+ Tab = test_table_non_local,
+ ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, [B, C]}])),
+
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [B]])),
+ ?match({atomic, ok}, mnesia:sync_transaction(?SDwrite({Tab, 1, init}))),
+
+ %% Test 1: Test that table info are updated when master node comes up
+
+ mnesia_test_lib:stop_mnesia([A, B]),
+ ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])),
+ ErrorRead = {badrpc,{'EXIT', {aborted,{no_exists,[test_table_non_local,1]}}}},
+ ErrorWrite = {badrpc,{'EXIT', {aborted,{no_exists,test_table_non_local}}}},
+ ?match(ErrorRead, rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match(ErrorWrite, rpc:call(A, mnesia, dirty_write, [{Tab, 1, updated_twice}])),
+
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])),
+
+ ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match(B, rpc:call(A, mnesia, table_info, [Tab, where_to_read])),
+ ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, init})])),
+
+ %% Test 2: Test that table info are updated after force_load
+
+ mnesia_test_lib:stop_mnesia([A, B]),
+ ?match({atomic, ok}, rpc:call(C, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated})])),
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+
+ ?match({timeout, [Tab]}, rpc:call(A, mnesia, wait_for_tables, [[Tab], 2000])),
+ ?match(yes, rpc:call(A, mnesia, force_load_table, [Tab])),
+ ?match(C, rpc:call(A, mnesia, table_info, [Tab, where_to_read])),
+
+ ?match([{Tab, 1, updated}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match({atomic, ok}, rpc:call(A, mnesia, sync_transaction, [?SDwrite({Tab, 1, updated_twice})])),
+
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 10000])),
+
+ ?match([{Tab, 1, updated_twice}], rpc:call(A, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated_twice}], rpc:call(B, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{Tab, 1, updated_twice}], rpc:call(C, mnesia, dirty_read, [{Tab, 1}])),
+
+ ?verify_mnesia(Nodes, []).
+
+remote_force_load_with_local_master_node(doc) ->
+ ["Force load a table on a remote node while the ",
+ "local node is down. Start the local node and ",
+ "verfify that the tables is loaded from disc locally "
+ "if the local node has itself as master node and ",
+ "the remote node has both the local and remote node ",
+ "as master nodes"];
+remote_force_load_with_local_master_node(suite) -> [];
+remote_force_load_with_local_master_node(Config) when is_list(Config) ->
+ [A, B] = Nodes = ?acquire_nodes(2, Config),
+
+ Tab = remote_force_load_with_local_master_node,
+ ?match({atomic,ok}, mnesia:create_table(Tab, [{disc_copies, Nodes}])),
+ ?match(ok, rpc:call(A, mnesia, set_master_nodes, [Tab, [A, B]])),
+ ?match(ok, rpc:call(B, mnesia, set_master_nodes, [Tab, [B]])),
+
+ W = fun(Who) -> mnesia:write({Tab, who, Who}) end,
+ ?match({atomic, ok}, rpc:call(A,mnesia, sync_transaction, [W, [a]])),
+ ?match(stopped, rpc:call(A, mnesia, stop, [])),
+ ?match({atomic, ok}, rpc:call(B, mnesia, sync_transaction, [W, [b]])),
+ ?match(stopped, rpc:call(B, mnesia, stop, [])),
+
+ ?match(ok, rpc:call(A, mnesia, start, [])),
+ ?match(ok, rpc:call(A, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match([{Tab, who, a}], rpc:call(A, mnesia, dirty_read, [{Tab, who}])),
+
+ ?match(ok, rpc:call(B, mnesia, start, [])),
+ ?match(ok, rpc:call(B, mnesia, wait_for_tables, [[Tab], 3000])),
+ ?match([{Tab, who, b}], rpc:call(B, mnesia, dirty_read, [{Tab, who}])),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+durability_of_dump_tables(doc) ->
+ [ "Verify that all tables contain the correct data when Mnesia",
+ "is restarted and tables are loaded from disc to recover",
+ " their previous contents. " ];
+durability_of_dump_tables(suite) -> [dump_ram_copies,
+ dump_disc_copies,
+ dump_disc_only].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+dump_ram_copies(doc) ->
+ ["Check that ram_copies tables are loaded with the"
+ "contents that had been dumped before Mnesia",
+ "was restarted. " ];
+dump_ram_copies(suite) -> [];
+dump_ram_copies(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(3, Config),
+ {success, [P1,P2,P3]} = ?start_activities(Nodes),
+
+ NP1 = node(P1),
+ NP2 = node(P2),
+
+ {A,B,C} = case node() of
+ NP1 ->
+ %?verbose("first case ~n"),
+ {P3,P2,P1};
+ NP2 ->
+ %?verbose("second case ~n"),
+ {P3,P1,P2};
+ _ ->
+ {P1,P2,P3}
+ end,
+
+ Node1 = node(A),
+ Node2 = node(B),
+ Node3 = node(C),
+
+ ?verbose(" A pid:~p node:~p ~n",[A,Node1]),
+ ?verbose(" B pid:~p node:~p ~n",[B,Node2]),
+ ?verbose(" C pid:~p node:~p ~n",[C,Node3]),
+
+
+ %% ram copies table on 2 nodes
+
+ Tab = dump_table,
+ Def = [{attributes, [key, value]},
+ {ram_copies, [Node1,Node2]}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ %% dump the table
+
+ ?match( {atomic,ok}, mnesia:dump_tables([Tab])),
+
+ %% perform updates (they shall be lost after kill Mnesia )
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 815})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 915})),
+
+ %% add another replica on node3
+ mnesia:add_table_copy(Tab,Node3,ram_copies),
+
+ %% all 3 replicas shall have the new contents
+ cross_check_tables([A,B,C],Tab,
+ {[{Tab,1,815}],[{Tab,2,915}],[{Tab,3,256}]}),
+
+ %% kill mnesia on node 3
+ mnesia_test_lib:kill_mnesia([Node3]),
+
+ %% wait a while, so that mnesia is really down
+ timer:sleep(timer:seconds(2)),
+
+ mnesia_test_lib:kill_mnesia([Node1,Node2]), %% kill them as well
+ timer:sleep(timer:seconds(2)),
+
+ %% start Mnesia only on node 3
+ ?verbose("starting mnesia on Node3~n",[]),
+
+ %% test_lib:mnesia_start doesnt work, because it waits
+ %% for the schema on all nodes ... ???
+ ?match(ok,rpc:call(Node3,mnesia,start,[]) ),
+ ?match(ok,rpc:call(Node3,mnesia,wait_for_tables,
+ [[Tab],timer:seconds(30)] ) ),
+
+ %% node3 shall have the conents of the dump
+ cross_check_tables([C],Tab,{[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}),
+
+ %% start Mnesia on the other 2 nodes, too
+ mnesia_test_lib:start_mnesia([Node1,Node2],[Tab]),
+
+ cross_check_tables([A,B,C],Tab,
+ {[{Tab,1,4711}],[{Tab,2,42}],[{Tab,3,256}]}),
+ ?verify_mnesia(Nodes, []).
+
+%% check the contents of the table
+
+cross_check_tables([],_tab,_elements) -> ok;
+cross_check_tables([Pid|Rest],Tab,{Val1,Val2,Val3}) ->
+ Pid ! fun () ->
+ R1 = mnesia:dirty_read({Tab,1}),
+ R2 = mnesia:dirty_read({Tab,2}),
+ R3 = mnesia:dirty_read({Tab,3}),
+ {R1,R2,R3}
+ end,
+ ?match_receive({ Pid, {Val1, Val2, Val3 } }),
+ cross_check_tables(Rest,Tab,{Val1,Val2,Val3} ).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Should be in evil test suite !!!
+
+dump_disc_copies(doc) ->
+ ["Check that it is not possible to dump disc_copies tables"];
+dump_disc_copies(suite) -> [];
+dump_disc_copies(Config) when is_list(Config) ->
+ do_dump_copies(Config, disc_copies).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Should be in evil test suite !!!
+dump_disc_only(doc) ->
+ ["Check that it is not possible to dump disc_only_copies tables"];
+dump_disc_only(suite) -> [];
+dump_disc_only(Config) when is_list(Config) ->
+ do_dump_copies(Config,disc_only_copies).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+do_dump_copies(Config,Copies) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+
+ Tab = dump_copies,
+ Def = [{attributes, [key, value]},
+ {Copies, [Node1]}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 4711})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 42})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, 256})),
+
+ %% dump the table
+ ?match( {aborted, {"Only allowed on ram_copies",Tab,[Node1]}},
+ mnesia:dump_tables([Tab])),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, 815})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 915})),
+
+ %% kill mnesia on node1
+ mnesia_test_lib:kill_mnesia([Node1]),
+
+ %% wait a while, so that mnesia is really down
+ timer:sleep(timer:seconds(1)),
+
+ mnesia_test_lib:start_mnesia([Node1],[Tab]),
+
+ ?match([{Tab, 1, 815}], mnesia:dirty_read({Tab,1}) ),
+ ?match([{Tab, 2, 915}], mnesia:dirty_read({Tab,2}) ),
+ ?match([{Tab, 3, 256}], mnesia:dirty_read({Tab,3}) ),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+durability_of_disc_copies(doc) ->
+ ["Perform all possible kinds of updates on tables and check"
+ "whether no data is lost after a restart of Mnesia.",
+ "This test is done for disc_copies"];
+
+durability_of_disc_copies(suite) -> [];
+durability_of_disc_copies(Config) when is_list(Config) ->
+ do_disc_durability(Config,disc_copies).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+durability_of_disc_only_copies(doc) ->
+ ["Perform all possible kinds of updates on tables and check"
+ "whether no data is lost after a restart of Mnesia.",
+ "This test is done for disc_only_copies"];
+durability_of_disc_only_copies(suite) -> [];
+durability_of_disc_only_copies(Config) when is_list(Config) ->
+ do_disc_durability(Config,disc_only_copies).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_disc_durability(Config,CopyType) ->
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(1)}]),
+ {success, [A,B,C]} = ?start_activities(Nodes),
+
+ Tab_set = disc_durability_set,
+ Def_set = [{attributes, [key, value]},
+ {CopyType, Nodes}],
+
+ Tab_bag = disc_durability_bag,
+ Def_bag = [{attributes, [key, value]},
+ {type, bag},
+ {CopyType, Nodes}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab_set, Def_set)),
+ ?match({atomic, ok}, mnesia:create_table(Tab_bag, Def_bag)),
+
+ %% do updates
+ ?match({atomic, ok},
+ mnesia:transaction(fun()->
+ mnesia:write({Tab_set, 11, 1111}),
+ mnesia:write({Tab_set, 22, 2222}),
+ mnesia:write({Tab_set, 33, 3333}),
+ mnesia:write({Tab_set, 55, 5555})
+ end)),
+ mnesia:dirty_write({Tab_set, 44, 4444}),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun()->
+ mnesia:write({Tab_bag, 11, a_1111}),
+ mnesia:write({Tab_bag, 11, b_1111}),
+ mnesia:write({Tab_bag, 22, a_2222}),
+ mnesia:write({Tab_bag, 22, b_2222}),
+ mnesia:write({Tab_bag, 33, a_3333}),
+ mnesia:write({Tab_bag, 33, b_3333})
+ end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun()-> mnesia:delete({Tab_set, 22}) end)),
+ ?match(ok, mnesia:dirty_delete({Tab_set, 33})),
+ ?match(5558, mnesia:dirty_update_counter({Tab_set, 55}, 3)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun()->
+ mnesia:delete_object({Tab_bag, 22, b_2222})
+ end)),
+ ?match(ok, mnesia:dirty_delete_object({Tab_bag, 33, b_3333})),
+ ?match(10, mnesia:dirty_update_counter({Tab_set, counter}, 10)),
+ ?match({atomic, ok}, % Also syncs update_counter
+ mnesia:sync_transaction(fun() -> mnesia:write({Tab_set,66,6666}) end)),
+
+ Updated = {[[{Tab_set,counter,10}],
+ [{Tab_set,counter,10}],
+ [{Tab_set,counter,10}]],[]},
+ ?match(Updated, rpc:multicall(Nodes, mnesia, dirty_read, [Tab_set,counter])),
+
+ %% kill mnesia on all nodes, start it again and check the data
+ mnesia_test_lib:kill_mnesia(Nodes),
+ mnesia_test_lib:start_mnesia(Nodes,[Tab_set,Tab_bag]),
+
+ ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]), %% Debugging strange msgs..
+ ?log("Processes ~p ~p ~p~n", [A,B,C]),
+ check_tables([A,B,C],
+ [{Tab_set,11}, {Tab_set,22},{Tab_set,33},
+ {Tab_set,44},{Tab_set,55}, {Tab_set,66},
+ {Tab_bag,11}, {Tab_bag,22},{Tab_bag,33},
+ {Tab_set, counter}],
+ [[{Tab_set, 11, 1111}], [], [], [{Tab_set, 44, 4444}],
+ [{Tab_set, 55, 5558}], [{Tab_set, 66, 6666}],
+ lists:sort([{Tab_bag, 11, a_1111},{Tab_bag, 11, b_1111}]),
+ [{Tab_bag, 22, a_2222}], [{Tab_bag, 33, a_3333}],
+ [{Tab_set, counter, 10}]]),
+
+ timer:sleep(1000), %% Debugging strange msgs..
+ ?log("Flushed ~p ~n", [mnesia_test_lib:flush()]),
+ ?verify_mnesia(Nodes, []).
+
+%% check the contents of the table
+%%
+%% all the processes in the PidList shall find all
+%% table entries in ValList
+
+check_tables([],_vallist,_resultList) -> ok;
+check_tables([Pid|Rest],ValList,ResultList) ->
+ Pid ! fun () ->
+ check_values(ValList)
+ end,
+ ?match_receive({ Pid, ResultList }),
+ check_tables(Rest,ValList,ResultList).
+
+check_values([]) -> [];
+check_values([{Tab,Key}|Rest]) ->
+ Ret = lists:sort(mnesia:dirty_read({Tab,Key})),
+ [Ret|check_values(Rest)].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+% stolen from mnesia_recovery_test.erl:
+
+receive_messages([]) -> [];
+receive_messages(ListOfMsgs) ->
+ receive
+ timeout ->
+ case lists:member(timeout, ListOfMsgs) of
+ false ->
+ ?warning("I (~p) have received unexpected msg~n ~p ~n",
+ [self(),timeout]),
+ receive_messages(ListOfMsgs);
+ true ->
+ ?verbose("I (~p) got msg ~p ~n", [self(),timeout]),
+ [ timeout | receive_messages(ListOfMsgs -- [timeout])]
+ end;
+
+ {Pid, Msg} ->
+ case lists:member(Msg, ListOfMsgs) of
+ false ->
+ ?warning("I (~p) have received unexpected msg~n ~p ~n",
+ [self(),{Pid, Msg}]),
+ receive_messages(ListOfMsgs);
+ true ->
+ ?verbose("I (~p) got msg ~p from ~p ~n", [self(),Msg, Pid]),
+ [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg])]
+ end;
+
+ Else -> ?warning("Recevied unexpected Msg~n ~p ~n", [Else])
+ after timer:seconds(40) ->
+ ?error("Timeout in receive msgs while waiting for ~p~n",
+ [ListOfMsgs])
+ end.
+
diff --git a/lib/mnesia/test/mnesia_evil_backup.erl b/lib/mnesia/test/mnesia_evil_backup.erl
new file mode 100644
index 0000000000..bbbebeb02c
--- /dev/null
+++ b/lib/mnesia/test/mnesia_evil_backup.erl
@@ -0,0 +1,750 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-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%
+%%
+
+%%
+%%%----------------------------------------------------------------------
+%%% File : mnesia_evil_backup.erl
+%%% Author : Dan Gudmundsson <dgud@legolas>
+%%% Purpose : Evil backup tests
+%%% Created : 3 Jun 1998 by Dan Gudmundsson <[email protected]>
+%%%----------------------------------------------------------------------
+
+-module(mnesia_evil_backup).
+-author('[email protected]').
+-compile(export_all).
+-include("mnesia_test_lib.hrl").
+
+%%-export([Function/Arity, ...]).
+
+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) ->
+ ["Checking all the functionality regarding ",
+ "to the backup and different ",
+ "kinds of restore and fallback interface"];
+
+all(suite) ->
+ [
+ backup,
+ bad_backup,
+ global_backup_checkpoint,
+ restore_tables,
+ traverse_backup,
+ selective_backup_checkpoint,
+ incremental_backup_checkpoint,
+%% local_backup_checkpoint,
+ install_fallback,
+ uninstall_fallback,
+ local_fallback,
+ sops_with_checkpoint
+ ].
+
+backup(doc) -> ["Checking the interface to the function backup",
+ "We don't check that the backups can be used here",
+ "That is checked in install_fallback and in restore"];
+backup(suite) -> [];
+backup(Config) when is_list(Config) ->
+ [Node1, Node2] = _Nodes = ?acquire_nodes(2, Config),
+ Tab = backup_tab,
+ Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})),
+ File = "backup_test.BUP",
+ ?match(ok, mnesia:backup(File)),
+
+ File2 = "backup_test2.BUP",
+ Tab2 = backup_tab2,
+ Def2 = [{disc_only_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match(ok, mnesia:backup(File2, mnesia_backup)),
+
+ File3 = "backup_test3.BUP",
+ mnesia_test_lib:kill_mnesia([Node2]),
+ ?match({error, _}, mnesia:backup(File3, mnesia_backup)),
+
+ ?match(ok, file:delete(File)),
+ ?match(ok, file:delete(File2)),
+ ?match({error, _}, file:delete(File3)),
+ ?verify_mnesia([Node1], [Node2]).
+
+
+bad_backup(suite) -> [];
+bad_backup(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = backup_tab,
+ Def = [{disc_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})),
+ File = "backup_test.BUP",
+ ?match(ok, mnesia:backup(File)),
+ file:write_file(File, "trash", [append]),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_bad})),
+ ?match({atomic,[Tab]}, mnesia:restore(File, [{clear_tables, [Tab]}])),
+ ?match([{Tab,1,test_ok}], mnesia:dirty_read(Tab, 1)),
+
+ ?match(ok, file:delete(File)),
+ ?verify_mnesia([Node1], []).
+
+
+
+global_backup_checkpoint(doc) ->
+ ["Checking the interface to the function backup_checkpoint",
+ "We don't check that the backups can be used here",
+ "That is checked in install_fallback and in restore"];
+global_backup_checkpoint(suite) -> [];
+global_backup_checkpoint(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = backup_cp,
+ Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}],
+ File = "backup_checkpoint.BUP",
+ File2 = "backup_checkpoint2.BUP",
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})),
+ ?match({error, _}, mnesia:backup_checkpoint(cp_name, File)),
+ Spec = [{name, cp_name}, {max, mnesia:system_info(tables)}],
+ ?match({ok, _Name, _Ns}, mnesia:activate_checkpoint(Spec)),
+ ?match(ok, mnesia:backup_checkpoint(cp_name, File)),
+ ?match({error, _}, mnesia:backup_checkpoint(cp_name_nonexist, File)),
+ ?match(ok, mnesia:backup_checkpoint(cp_name, File2, mnesia_backup)),
+ ?match({error, _}, file:delete(File)),
+ ?match(ok, file:delete(File2)),
+ ?verify_mnesia(Nodes, []).
+
+restore_tables(doc) ->
+ ["Tests the interface of restore"];
+
+restore_tables(suite) ->
+ [
+ restore_errors,
+ restore_clear,
+ restore_keep,
+ restore_recreate,
+ restore_clear_ram
+ ].
+
+restore_errors(suite) -> [];
+restore_errors(Config) when is_list(Config) ->
+ [_Node] = ?acquire_nodes(1, Config),
+ ?match({aborted, enoent}, mnesia:restore(notAfile, [])),
+ ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, not_a_list)),
+ ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [test_badarg])),
+ ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{test_badarg, xxx}])),
+ ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{skip_tables, xxx}])),
+ ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{recreate_tables, [schema]}])),
+ ?match({aborted, {badarg, _}}, mnesia:restore(notAfile, [{default_op, asdklasd}])),
+ ok.
+
+restore_clear(suite) -> [];
+restore_clear(Config) when is_list(Config) ->
+ restore(Config, clear_tables).
+
+restore_keep(suite) -> [];
+restore_keep(Config) when is_list(Config) ->
+ restore(Config, keep_tables).
+
+restore_recreate(suite) -> [];
+restore_recreate(Config) when is_list(Config) ->
+ restore(Config, recreate_tables).
+
+check_tab(Records, Line) ->
+ Verify = fun({Table, Key, Val}) ->
+ case catch mnesia:dirty_read({Table, Key}) of
+ [{Table, Key, Val}] -> ok;
+ Else ->
+ mnesia_test_lib:error("Not matching on Node ~p ~n"
+ " Expected ~p~n Actual ~p~n",
+ [node(), {Table, Key, Val}, Else],
+ ?MODULE, Line),
+ exit(error)
+ end;
+ (Recs) ->
+ [{Tab, Key, _}, _] = Recs,
+ SRecs = lists:sort(Recs),
+ R_Recs = lists:sort(catch mnesia:dirty_read({Tab, Key})),
+ case R_Recs of
+ SRecs -> ok;
+ Else ->
+ mnesia_test_lib:error("Not matching on Node ~p ~n"
+ " Expected ~p~n Actual ~p~n",
+ [node(), SRecs, Else],
+ ?MODULE, Line),
+ exit(error)
+ end
+ end,
+ lists:foreach(Verify, Records).
+
+restore(Config, Op) ->
+ [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config),
+
+ Tab1 = ram_snmp,
+ Def1 = [{snmp, [{key, integer}]}, {ram_copies, [Node1]}],
+ Tab2 = disc_index,
+ Def2 = [{index, [val]}, {disc_copies, [Node1, Node2]}],
+ Tab3 = dionly_bag,
+ Def3 = [{type, bag}, {disc_only_copies, Nodes}],
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)),
+
+ File1 = "restore1.BUP",
+ File2 = "restore2.BUP",
+
+ Restore = fun(O, A) ->
+ case mnesia:restore(O, A) of
+ {atomic, Tabs} when is_list(Tabs) -> {atomic, lists:sort(Tabs)};
+ Other -> Other
+ end
+ end,
+ Tabs = lists:sort([Tab1, Tab2, Tab3]),
+
+ [mnesia:dirty_write({Tab1, N, N+42}) || N <- lists:seq(1, 10)],
+ [mnesia:dirty_write({Tab2, N, N+43}) || N <- lists:seq(1, 10)],
+ [mnesia:dirty_write({Tab3, N, N+44}) || N <- lists:seq(1, 10)],
+
+ Res1 = [{Tab1, N, N+42} || N <- lists:seq(1, 10)],
+ Res2 = [{Tab2, N, N+43} || N <- lists:seq(1, 10)],
+ Res3 = [{Tab3, N, N+44} || N <- lists:seq(1, 10)],
+
+ {ok, Name, _} = mnesia:activate_checkpoint([{min, Tabs}, {ram_overrides_dump, true}]),
+ file:delete(File1),
+
+ %% Test standard Restore on one table on one node
+ ?match(ok, mnesia:backup_checkpoint(Name, File1)),
+ ?match(ok, mnesia:deactivate_checkpoint(Name)),
+ ?match(ok, mnesia:backup(File2)),
+ [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)],
+ [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)],
+ [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)],
+ _Res11 = [{Tab1, N, N+1} || N <- lists:seq(1, 11)],
+ Res21 = [{Tab2, N, N+1} || N <- lists:seq(1, 11)],
+ Res31 = [[{Tab3, N, N+1}, {Tab3, N, N+44}] || N <- lists:seq(1, 10)],
+
+ ?match({atomic, [Tab1]}, Restore(File1, [{Op, [Tab1]},
+ {skip_tables, Tabs -- [Tab1]}])),
+ case Op of
+ keep_tables ->
+ ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11}));
+ clear_tables ->
+ ?match([], mnesia:dirty_read({Tab1, 11}));
+ recreate_tables ->
+ ?match([], mnesia:dirty_read({Tab1, 11}))
+ end,
+ [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res21, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes],
+
+ %% Restore all tables on it's nodes
+ mnesia_schema:clear_table(Tab1),
+ mnesia_schema:clear_table(Tab2),
+ mnesia_schema:clear_table(Tab3),
+ [mnesia:dirty_write({Tab1, N, N+1}) || N <- lists:seq(1, 11)],
+ [mnesia:dirty_write({Tab2, N, N+1}) || N <- lists:seq(1, 11)],
+ [mnesia:dirty_write({Tab3, N, N+1}) || N <- lists:seq(1, 11)],
+
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)),
+
+ ?match({ok, Node1}, mnesia:subscribe({table, Tab1})),
+
+ ?match({atomic, Tabs}, Restore(File1, [{default_op, Op},
+ {module, mnesia_backup}])),
+ case Op of
+ clear_tables ->
+ ?match_receive({mnesia_table_event, {delete, {schema, Tab1}, _}}),
+ ?match_receive({mnesia_table_event, {write, {schema, Tab1, _}, _}}),
+ check_subscr(Tab1),
+ [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes],
+ ?match([], mnesia:dirty_read({Tab1, 11})),
+ ?match([], mnesia:dirty_read({Tab2, 11})),
+ ?match([], mnesia:dirty_read({Tab3, 11})),
+ %% Check Index
+ ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)),
+ ?match([], mnesia:dirty_index_read(Tab2, 11, val)),
+ %% Check Snmp
+ ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])),
+ ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])),
+ ?match(undefined, mnesia:snmp_get_row(Tab1, [11])),
+ %% Check schema info
+ ?match([Node2], mnesia:table_info(Tab2, where_to_write));
+ keep_tables ->
+ check_subscr(Tab1),
+ [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res31, ?LINE]) || Node <- Nodes],
+ ?match([{Tab1, 11, 12}], mnesia:dirty_read({Tab1, 11})),
+ ?match([{Tab2, 11, 12}], mnesia:dirty_read({Tab2, 11})),
+ ?match([{Tab3, 11, 12}], mnesia:dirty_read({Tab3, 11})),
+ ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)),
+ %% Check Index
+ ?match([], mnesia:dirty_index_read(Tab2, 11, val)),
+ ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])),
+ %% Check Snmp
+ ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])),
+ ?match({ok, {Tab1, 11, 12}}, mnesia:snmp_get_row(Tab1, [11])),
+ %% Check schema info
+ ?match([Node2], mnesia:table_info(Tab2, where_to_write));
+ recreate_tables ->
+ check_subscr(Tab1, 0),
+ [rpc:call(Node, ?MODULE, check_tab, [Res1, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res2, ?LINE]) || Node <- Nodes],
+ [rpc:call(Node, ?MODULE, check_tab, [Res3, ?LINE]) || Node <- Nodes],
+ ?match([], mnesia:dirty_read({Tab1, 11})),
+ ?match([], mnesia:dirty_read({Tab2, 11})),
+ ?match([], mnesia:dirty_read({Tab3, 11})),
+ %% Check Index
+ ?match([{Tab2, 10, 53}], mnesia:dirty_index_read(Tab2, 53, val)),
+ ?match([], mnesia:dirty_index_read(Tab2, 11, val)),
+ %% Check Snmp
+ ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])),
+ ?match({ok, {Tab1, 1, 43}}, mnesia:snmp_get_row(Tab1, [1])),
+ ?match(undefined, mnesia:snmp_get_row(Tab1, [11])),
+ %% Check schema info
+ Ns = lists:sort([Node1, Node2]),
+ ?match(Ns, lists:sort(mnesia:table_info(Tab2, where_to_write)))
+ end,
+ ?match(ok, file:delete(File1)),
+ ?match(ok, file:delete(File2)),
+ ?verify_mnesia(Nodes, []).
+
+
+check_subscr(Tab) ->
+ check_subscr(Tab, 10).
+
+check_subscr(_Tab, 0) ->
+ receive
+ Msg ->
+ ?error("Too many msgs ~p~n", [Msg])
+ after 500 ->
+ ok
+ end;
+check_subscr(Tab, N) ->
+ V = N +42,
+ receive
+ {mnesia_table_event, {write, {Tab, N, V}, _}} ->
+ check_subscr(Tab, N-1)
+ after 500 ->
+ ?error("Missing ~p~n", [{Tab, N, V}])
+ end.
+
+restore_clear_ram(suite) -> [];
+restore_clear_ram(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(3, [{diskless, true}|Config]),
+
+ ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])),
+
+ Write = fun(What) ->
+ mnesia:write({a,1,What}),
+ mnesia:write({a,2,What}),
+ mnesia:write({a,3,What})
+ end,
+ Bup = "restore_clear_ram.BUP",
+
+ ?match({atomic, ok}, mnesia:transaction(Write, [initial])),
+ ?match({ok, _, _}, mnesia:activate_checkpoint([{name,test},
+ {min, [schema, a]},
+ {ram_overrides_dump, true}])),
+ ?match(ok, mnesia:backup_checkpoint(test, Bup)),
+
+ ?match({atomic, ok}, mnesia:transaction(Write, [data])),
+ ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])),
+
+ restore_clear_ram_loop(100, Nodes, Bup),
+
+ ok.
+
+restore_clear_ram_loop(N, Nodes = [N1,N2,N3], Bup) when N > 0 ->
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+ ?match({_, []}, rpc:multicall([N1,N2], mnesia, start, [[{extra_db_nodes, Nodes}]])),
+ Key = rpc:async_call(N3, mnesia, start, [[{extra_db_nodes, Nodes}]]),
+ ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])),
+ ?match({atomic, [a]}, mnesia:restore(Bup, [{clear_tables,[a]},{default_op,skip_tables}])),
+ ?match(ok, rpc:yield(Key)),
+ ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [[a], 3000])),
+ case rpc:multicall(Nodes, mnesia, table_info, [a,size]) of
+ {[3,3,3], []} ->
+ restore_clear_ram_loop(N-1, Nodes, Bup);
+ Error ->
+ ?match(3, Error)
+ end;
+restore_clear_ram_loop(_,_,_) ->
+ ok.
+
+traverse_backup(doc) ->
+ ["Testing the traverse_backup interface, the resulting file is not tested though",
+ "See install_fallback for result using the output file from traverse_backup",
+ "A side effect is that the backup file contents are tested"];
+traverse_backup(suite) -> [];
+traverse_backup(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = backup_tab,
+ Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})),
+ File = "_treverse_backup.BUP",
+ File2 = "traverse_backup2.BUP",
+ File3 = "traverse_backup3.BUP",
+ ?match(ok, mnesia:backup(File)),
+
+ Fun = fun({backup_tab, N, _}, Acc) -> {[{backup_tab, N, test_ok}], Acc+1};
+ (Other, Acc) -> {[Other], Acc}
+ end,
+
+ ?match({ok, 5}, mnesia:traverse_backup(File, read_only, Fun, 0)),
+ ?match(ok, file:delete(read_only)),
+
+ ?match({ok, 5}, mnesia:traverse_backup(File, mnesia_backup,
+ dummy, read_only, Fun, 0)),
+
+ ?match({ok, 5}, mnesia:traverse_backup(File, File2, Fun, 0)),
+ ?match({ok, 5}, mnesia:traverse_backup(File2, mnesia_backup,
+ File3, mnesia_backup, Fun, 0)),
+
+ BadFun = fun({bad_tab, _N, asd}, Acc) -> {{error, error}, Acc} end,
+ ?match({error, _}, mnesia:traverse_backup(File, read_only, BadFun, 0)),
+ ?match({error, _}, file:delete(read_only)),
+ ?match(ok, file:delete(File)),
+ ?match(ok, file:delete(File2)),
+ ?match(ok, file:delete(File3)),
+ ?verify_mnesia(Nodes, []).
+
+
+install_fallback(doc) ->
+ ["This tests the install_fallback intf.",
+ "It also verifies that the output from backup_checkpoint and traverse_backup",
+ "is valid"];
+install_fallback(suite) -> [];
+install_fallback(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = fallbacks_test,
+ Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 4, test_nok})),
+ ?match(ok, mnesia:dirty_write({Tab, 5, test_nok})),
+
+ Tab2 = fallbacks_test2,
+ Def2 = [{disc_copies, [node()]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ Tab3 = fallbacks_test3,
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def2)),
+ Fun2 = fun(Key) ->
+ Rec = {Tab2, Key, test_ok},
+ mnesia:dirty_write(Rec),
+ [Rec]
+ end,
+ TabSize3 = 1000,
+ OldRecs2 = [Fun2(K) || K <- lists:seq(1, TabSize3)],
+
+ Spec =[{name, cp_name}, {max, mnesia:system_info(tables)}],
+ ?match({ok, _Name, Nodes}, mnesia:activate_checkpoint(Spec)),
+ ?match(ok, mnesia:dirty_write({Tab, 6, test_nok})),
+ [mnesia:dirty_write({Tab2, K, test_nok}) || K <- lists:seq(1, TabSize3 + 10)],
+ File = "install_fallback.BUP",
+ File2 = "install_fallback2.BUP",
+ File3 = "install_fallback3.BUP",
+ ?match(ok, mnesia:backup_checkpoint(cp_name, File)),
+
+ Fun = fun({T, N, _}, Acc) when T == Tab ->
+ case N rem 2 of
+ 0 ->
+ io:format("write ~p -> ~p~n", [N, T]),
+ {[{T, N, test_ok}], Acc + 1};
+ 1 ->
+ io:format("write ~p -> ~p~n", [N, Tab3]),
+ {[{Tab3, N, test_ok}], Acc + 1}
+ end;
+ ({T, N}, Acc) when T == Tab ->
+ case N rem 2 of
+ 0 ->
+ io:format("delete ~p -> ~p~n", [N, T]),
+ {[{T, N}], Acc + 1};
+ 1 ->
+ io:format("delete ~p -> ~p~n", [N, Tab3]),
+ {[{Tab3, N}], Acc + 1}
+ end;
+ (Other, Acc) ->
+ {[Other], Acc}
+ end,
+ ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)),
+ ?match(ok, mnesia:install_fallback(File2)),
+
+ mnesia_test_lib:kill_mnesia([Node1, Node2]),
+ timer:sleep(timer:seconds(1)), % Let it die!
+
+ ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab, Tab2, Tab3])),
+
+ % Verify
+ ?match([], mnesia:dirty_read({Tab, 1})),
+ ?match([{Tab3, 1, test_ok}], mnesia:dirty_read({Tab3, 1})),
+ ?match([{Tab, 2, test_ok}], mnesia:dirty_read({Tab, 2})),
+ ?match([], mnesia:dirty_read({Tab3, 2})),
+ ?match([], mnesia:dirty_read({Tab, 3})),
+ ?match([{Tab3, 3, test_ok}], mnesia:dirty_read({Tab3, 3})),
+ ?match([{Tab, 4, test_ok}], mnesia:dirty_read({Tab, 4})),
+ ?match([], mnesia:dirty_read({Tab3, 4})),
+ ?match([], mnesia:dirty_read({Tab, 5})),
+ ?match([{Tab3, 5, test_ok}], mnesia:dirty_read({Tab3, 5})),
+ ?match([], mnesia:dirty_read({Tab, 6})),
+ ?match([], mnesia:dirty_read({Tab3, 6})),
+ ?match([], [mnesia:dirty_read({Tab2, K}) || K <- lists:seq(1, TabSize3)] -- OldRecs2),
+ ?match(TabSize3, mnesia:table_info(Tab2, size)),
+
+ % Check the interface
+ file:delete(File3),
+ ?match({error, _}, mnesia:install_fallback(File3)),
+ ?match({error, _}, mnesia:install_fallback(File2, mnesia_badmod)),
+ ?match(ok, mnesia:install_fallback(File2, mnesia_backup)),
+ ?match(ok, file:delete(File)),
+ ?match(ok, file:delete(File2)),
+ ?match({error, _}, file:delete(File3)),
+ ?verify_mnesia(Nodes, []).
+
+uninstall_fallback(suite) -> [];
+uninstall_fallback(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = uinst_fallbacks_test,
+ File = "uinst_fallback.BUP",
+ File2 = "uinst_fallback2.BUP",
+ Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})),
+ ?match(ok, mnesia:backup(File)),
+ Fun = fun({T, N, _}, Acc) when T == Tab ->
+ {[{T, N, test_nok}], Acc+1};
+ (Other, Acc) -> {[Other], Acc}
+ end,
+ ?match({ok, _}, mnesia:traverse_backup(File, File2, Fun, 0)),
+ ?match({error, enoent}, mnesia:uninstall_fallback()),
+ ?match(ok, mnesia:install_fallback(File2)),
+ ?match(ok, file:delete(File)),
+ ?match(ok, file:delete(File2)),
+ ?match(ok, mnesia:uninstall_fallback()),
+
+ mnesia_test_lib:kill_mnesia([Node1, Node2]),
+ timer:sleep(timer:seconds(1)), % Let it die!
+ ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab])),
+ ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})),
+ ?verify_mnesia(Nodes, []).
+
+local_fallback(suite) -> [];
+local_fallback(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = local_fallback,
+ File = "local_fallback.BUP",
+ Def = [{disc_copies, Nodes}],
+ Key = foo,
+ Pre = {Tab, Key, pre},
+ Post = {Tab, Key, post},
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write(Pre)),
+ ?match(ok, mnesia:backup(File)),
+ ?match(ok, mnesia:dirty_write(Post)),
+ Local = [{scope, local}],
+ ?match({error, enoent}, mnesia:uninstall_fallback(Local)),
+ ?match(ok, mnesia:install_fallback(File, Local)),
+ ?match(true, mnesia:system_info(fallback_activated)),
+ ?match(ok, mnesia:uninstall_fallback(Local)),
+ ?match(false, mnesia:system_info(fallback_activated)),
+ ?match(ok, mnesia:install_fallback(File, Local)),
+ ?match(true, mnesia:system_info(fallback_activated)),
+
+ ?match(false, rpc:call(Node2, mnesia, system_info , [fallback_activated])),
+ ?match(ok, rpc:call(Node2, mnesia, install_fallback , [File, Local])),
+ ?match([Post], mnesia:dirty_read({Tab, Key})),
+ ?match([Post], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])),
+
+ ?match([], mnesia_test_lib:kill_mnesia(Nodes)),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])),
+ ?match([Pre], mnesia:dirty_read({Tab, Key})),
+ ?match([Pre], rpc:call(Node2, mnesia, dirty_read, [{Tab, Key}])),
+ Dir = rpc:call(Node2, mnesia, system_info , [directory]),
+
+ ?match(ok, mnesia:dirty_write(Post)),
+ ?match([Post], mnesia:dirty_read({Tab, Key})),
+ ?match([], mnesia_test_lib:kill_mnesia([Node2])),
+ ?match(ok, mnesia:install_fallback(File, Local ++ [{mnesia_dir, Dir}])),
+ ?match([], mnesia_test_lib:kill_mnesia([Node1])),
+
+ ?match([], mnesia_test_lib:start_mnesia([Node2], [])),
+ ?match(yes, rpc:call(Node2, mnesia, force_load_table, [Tab])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])),
+ ?match([Pre], mnesia:dirty_read({Tab, Key})),
+
+ ?match(ok, file:delete(File)),
+ ?verify_mnesia(Nodes, []).
+
+selective_backup_checkpoint(doc) ->
+ ["Perform a selective backup of a checkpoint"];
+selective_backup_checkpoint(suite) -> [];
+selective_backup_checkpoint(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = sel_backup,
+ OmitTab = sel_backup_omit,
+ CpName = sel_cp,
+ Def = [{disc_copies, [Node1, Node2]}],
+ File = "selective_backup_checkpoint.BUP",
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match({atomic, ok}, mnesia:create_table(OmitTab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})),
+ ?match(ok, mnesia:dirty_write({OmitTab, 1, test_ok})),
+ CpSpec = [{name, CpName}, {max, mnesia:system_info(tables)}],
+ ?match({ok, CpName, _Ns}, mnesia:activate_checkpoint(CpSpec)),
+
+ BupSpec = [{tables, [Tab]}],
+ ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec)),
+
+ ?match([schema, sel_backup], bup_tables(File, mnesia_backup)),
+ ?match(ok, file:delete(File)),
+
+ BupSpec2 = [{tables, [Tab, OmitTab]}],
+ ?match(ok, mnesia:backup_checkpoint(CpName, File, BupSpec2)),
+
+ ?match([schema, sel_backup, sel_backup_omit],
+ bup_tables(File, mnesia_backup)),
+ ?match(ok, file:delete(File)),
+ ?verify_mnesia(Nodes, []).
+
+bup_tables(File, Mod) ->
+ Fun = fun(Rec, Tabs) ->
+ Tab = element(1, Rec),
+ Tabs2 = [Tab | lists:delete(Tab, Tabs)],
+ {[Rec], Tabs2}
+ end,
+ case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of
+ {ok, Tabs} ->
+ lists:sort(Tabs);
+ {error, Reason} ->
+ exit(Reason)
+ end.
+
+incremental_backup_checkpoint(doc) ->
+ ["Perform a incremental backup of a checkpoint"];
+incremental_backup_checkpoint(suite) -> [];
+incremental_backup_checkpoint(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = incr_backup,
+ Def = [{disc_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)],
+ ?match([ok|_], [mnesia:dirty_write(R) || R <- OldRecs]),
+ OldCpName = old_cp,
+ OldCpSpec = [{name, OldCpName}, {min, [Tab]}],
+ ?match({ok, OldCpName, _Ns}, mnesia:activate_checkpoint(OldCpSpec)),
+
+ BupSpec = [{tables, [Tab]}],
+ OldFile = "old_full_backup.BUP",
+ ?match(ok, mnesia:backup_checkpoint(OldCpName, OldFile, BupSpec)),
+ ?match(OldRecs, bup_records(OldFile, mnesia_backup)),
+ ?match(ok, mnesia:dirty_delete({Tab, 1})),
+ ?match(ok, mnesia:dirty_write({Tab, 2, 2})),
+ ?match(ok, mnesia:dirty_write({Tab, 3, -3})),
+
+ NewCpName = new_cp,
+ NewCpSpec = [{name, NewCpName}, {min, [Tab]}],
+ ?match({ok, NewCpName, _Ns}, mnesia:activate_checkpoint(NewCpSpec)),
+ ?match(ok, mnesia:dirty_write({Tab, 4, 4})),
+
+ NewFile = "new_full_backup.BUP",
+ ?match(ok, mnesia:backup_checkpoint(NewCpName, NewFile, BupSpec)),
+ NewRecs = [{Tab, 2, 2}, {Tab, 3, -3},
+ {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}, {Tab, 5, -5}],
+ ?match(NewRecs, bup_records(NewFile, mnesia_backup)),
+
+ DiffFile = "diff_backup.BUP",
+ DiffBupSpec = [{tables, [Tab]}, {incremental, OldCpName}],
+ ?match(ok, mnesia:backup_checkpoint(NewCpName, DiffFile, DiffBupSpec)),
+ DiffRecs = [{Tab, 1}, {Tab, 2}, {Tab, 2, 2}, {Tab, 3}, {Tab, 3, -3},
+ {Tab, 4}, {Tab, 4, 4}, {Tab, 4}, {Tab, 4, -4}],
+ ?match(DiffRecs, bup_records(DiffFile, mnesia_backup)),
+
+ ?match(ok, mnesia:deactivate_checkpoint(OldCpName)),
+ ?match(ok, mnesia:deactivate_checkpoint(NewCpName)),
+ ?match(ok, file:delete(OldFile)),
+ ?match(ok, file:delete(NewFile)),
+ ?match(ok, file:delete(DiffFile)),
+
+ ?verify_mnesia(Nodes, []).
+
+bup_records(File, Mod) ->
+ Fun = fun(Rec, Recs) when element(1, Rec) == schema ->
+ {[Rec], Recs};
+ (Rec, Recs) ->
+ {[Rec], [Rec | Recs]}
+ end,
+ case mnesia:traverse_backup(File, Mod, dummy, read_only, Fun, []) of
+ {ok, Recs} ->
+ lists:keysort(1, lists:keysort(2, lists:reverse(Recs)));
+ {error, Reason} ->
+ exit(Reason)
+ end.
+
+sops_with_checkpoint(doc) ->
+ ["Test schema operations during a checkpoint"];
+sops_with_checkpoint(suite) -> [];
+sops_with_checkpoint(Config) when is_list(Config) ->
+ Ns = ?acquire_nodes(2, Config),
+
+ ?match({ok, cp1, Ns}, mnesia:activate_checkpoint([{name, cp1},{max,mnesia:system_info(tables)}])),
+ Tab = tab,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies,Ns}])),
+ OldRecs = [{Tab, K, -K} || K <- lists:seq(1, 5)],
+ [mnesia:dirty_write(R) || R <- OldRecs],
+
+ ?match({ok, cp2, Ns}, mnesia:activate_checkpoint([{name, cp2},{max,mnesia:system_info(tables)}])),
+ File1 = "cp1_delete_me.BUP",
+ ?match(ok, mnesia:dirty_write({Tab,6,-6})),
+ ?match(ok, mnesia:backup_checkpoint(cp1, File1)),
+ ?match(ok, mnesia:dirty_write({Tab,7,-7})),
+ File2 = "cp2_delete_me.BUP",
+ ?match(ok, mnesia:backup_checkpoint(cp2, File2)),
+
+ ?match(ok, mnesia:deactivate_checkpoint(cp1)),
+ ?match(ok, mnesia:backup_checkpoint(cp2, File1)),
+ ?match(ok, mnesia:dirty_write({Tab,8,-8})),
+
+ ?match({atomic,ok}, mnesia:delete_table(Tab)),
+ ?match({error,_}, mnesia:backup_checkpoint(cp2, File2)),
+ ?match({'EXIT',_}, mnesia:dirty_write({Tab,9,-9})),
+
+ ?match({atomic,_}, mnesia:restore(File1, [{default_op, recreate_tables}])),
+ Test = fun(N) when N > 5 -> ?error("To many records in backup ~p ~n", [N]);
+ (N) -> case mnesia:dirty_read(Tab,N) of
+ [{Tab,N,B}] when -B =:= N -> ok;
+ Other -> ?error("Not matching ~p ~p~n", [N,Other])
+ end
+ end,
+ [Test(N) || N <- mnesia:dirty_all_keys(Tab)],
+ ?match({aborted,enoent}, mnesia:restore(File2, [{default_op, recreate_tables}])),
+
+ file:delete(File1), file:delete(File2),
+
+ ?verify_mnesia(Ns, []).
diff --git a/lib/mnesia/test/mnesia_evil_coverage_test.erl b/lib/mnesia/test/mnesia_evil_coverage_test.erl
new file mode 100644
index 0000000000..4fbf1b4003
--- /dev/null
+++ b/lib/mnesia/test/mnesia_evil_coverage_test.erl
@@ -0,0 +1,2401 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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
+%% 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_evil_coverage_test).
+-author('[email protected]').
+-include("mnesia_test_lib.hrl").
+
+-compile([export_all]).
+
+-define(cleanup(N, Config),
+ mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}],
+ N, Config, ?FILE, ?LINE)).
+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) ->
+ ["Evil usage of the API.",
+ "Invoke all functions in the API and try to cover all legal uses",
+ "cases as well the illegal dito. This is a complement to the",
+ "other more explicit test cases."];
+all(suite) ->
+ [
+ system_info,
+ table_info,
+ error_description,
+ db_node_lifecycle,
+ evil_delete_db_node,
+ start_and_stop,
+ checkpoint,
+ table_lifecycle,
+ add_copy_conflict,
+ add_copy_when_going_down,
+ replica_management,
+ schema_availability,
+ local_content,
+ table_access_modifications,
+ replica_location,
+ table_sync,
+ user_properties,
+ unsupp_user_props,
+ record_name,
+ snmp_access,
+ subscriptions,
+ iteration,
+ debug_support,
+ sorted_ets,
+ {mnesia_dirty_access_test, all},
+ {mnesia_trans_access_test, all},
+ {mnesia_evil_backup, all}
+ ].
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Get meta info about Mnesia
+
+system_info(suite) -> [];
+system_info(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(all, Config),
+ Ns = ?sort(Nodes),
+ ?match(yes, mnesia:system_info(is_running)),
+ ?match(Ns, ?sort(mnesia:system_info(db_nodes))),
+ ?match(Ns, ?sort(mnesia:system_info(running_db_nodes))),
+ ?match(A when is_atom(A), mnesia:system_info(debug)),
+ ?match(L when is_list(L), mnesia:system_info(directory)),
+ ?match(L when is_list(L), mnesia:system_info(log_version)),
+ ?match({_, _}, mnesia:system_info(schema_version)),
+ ?match(L when is_list(L), mnesia:system_info(tables)),
+ ?match(L when is_list(L), mnesia:system_info(local_tables)),
+ ?match(L when is_list(L), mnesia:system_info(held_locks)),
+ ?match(L when is_list(L), mnesia:system_info(lock_queue)),
+ ?match(L when is_list(L), mnesia:system_info(transactions)),
+ ?match(I when is_integer(I), mnesia:system_info(transaction_failures)),
+ ?match(I when is_integer(I), mnesia:system_info(transaction_commits)),
+ ?match(I when is_integer(I), mnesia:system_info(transaction_restarts)),
+ ?match(L when is_list(L), mnesia:system_info(checkpoints)),
+ ?match(A when is_atom(A), mnesia:system_info(backup_module)),
+ ?match(true, mnesia:system_info(auto_repair)),
+ ?match({_, _}, mnesia:system_info(dump_log_interval)),
+ ?match(A when is_atom(A), mnesia:system_info(dump_log_update_in_place)),
+ ?match(I when is_integer(I), mnesia:system_info(transaction_log_writes)),
+ ?match(I when is_integer(I), mnesia:system_info(send_compressed)),
+ ?match(L when is_list(L), mnesia:system_info(all)),
+ ?match({'EXIT', {aborted, Reason }} when element(1, Reason) == badarg
+ , mnesia:system_info(ali_baba)),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Get meta info about table
+
+table_info(suite) -> [];
+table_info(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+
+ Tab = table_info,
+ Type = bag,
+ ValPos = 3,
+ Attrs = [k, v],
+ Arity = length(Attrs) +1,
+
+ Schema =
+ case mnesia_test_lib:diskless(Config) of
+ true -> [{type, Type}, {attributes, Attrs}, {index, [ValPos]},
+ {ram_copies, Nodes}];
+ false ->
+ [{type, Type}, {attributes, Attrs}, {index, [ValPos]},
+ {disc_only_copies, [Node1]}, {ram_copies, [Node2]},
+ {disc_copies, [Node3]}]
+ end,
+ ?match({atomic, ok}, mnesia:create_table(Tab, Schema)),
+
+ Size = 10,
+ Keys = lists:seq(1, Size),
+ Records = [{Tab, A, 7} || A <- Keys],
+ lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records),
+ ?match(Mem when is_integer(Mem), mnesia:table_info(Tab, memory)),
+ ?match(Size, mnesia:table_info(Tab, size)),
+ ?match(Type, mnesia:table_info(Tab, type)),
+
+ case mnesia_test_lib:diskless(Config) of
+ true ->
+ ?match(Nodes, mnesia:table_info(Tab, ram_copies));
+ false ->
+ ?match([Node3], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_copies, Config))),
+ ?match([Node2], mnesia:table_info(Tab, ram_copies)),
+ ?match([Node1], mnesia:table_info(Tab, mnesia_test_lib:storage_type(disc_only_copies, Config)))
+ end,
+ Read = [Node1, Node2, Node3],
+ ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read)),
+ Write = ?sort([Node1, Node2, Node3]),
+ ?match(Write, ?sort(mnesia:table_info(Tab, where_to_write))),
+ ?match([ValPos], mnesia:table_info(Tab, index)),
+ ?match(Arity, mnesia:table_info(Tab, arity)),
+ ?match(Attrs, mnesia:table_info(Tab, attributes)),
+ ?match({Tab, '_', '_'}, mnesia:table_info(Tab, wild_pattern)),
+ ?match({atomic, Attrs}, mnesia:transaction(fun() ->
+ mnesia:table_info(Tab, attributes) end)),
+
+ ?match(L when is_list(L), mnesia:table_info(Tab, all)),
+
+ %% Table info when table not loaded
+ ?match({atomic, ok},
+ mnesia:create_table(tab_info, Schema)),
+ ?match(stopped, mnesia:stop()),
+ ?match(stopped, rpc:call(Node2, mnesia, stop, [])),
+ ?match(ok, mnesia:start()),
+ ?match(ok, mnesia:wait_for_tables([tab_info], 5000)),
+ ?match(0, mnesia:table_info(tab_info, size)),
+ ?verify_mnesia([Node1, Node3], [Node2]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Check the error descriptions
+
+error_description(suite) -> [];
+error_description(Config) when is_list(Config) ->
+ ?acquire_nodes(1, Config),
+ Errors = [nested_transaction, badarg, no_transaction, combine_error,
+ bad_index, already_exists, index_exists, no_exists, system_limit,
+ mnesia_down, not_a_db_node, bad_type, node_not_running,
+ truncated_binary_file, active, illegal
+ ],
+ ?match(X when is_atom(X), mnesia:error_description({error, bad_error_msg})),
+ ?match(X when is_tuple(X), mnesia:error_description({'EXIT', pid, bad})),
+ %% This is real error msg
+ ?match(X when is_tuple(X), mnesia:error_description(
+ {error,
+ {"Cannot prepare checkpoint (bad reply)",
+ {{877,957351,758147},a@legolas},
+ {error,{node_not_running,a1@legolas}}}})),
+ check_errors(error, Errors),
+ check_errors(aborted, Errors),
+ check_errors('EXIT', Errors).
+
+check_errors(_Err, []) -> ok;
+check_errors(Err, [Desc|R]) ->
+ ?match(X when is_list(X), mnesia:error_description({Err, Desc})),
+ check_errors(Err, R).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Add and drop db nodes
+
+db_node_lifecycle(suite) -> [];
+db_node_lifecycle(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config),
+ Tab = db_node_lifecycle,
+
+ Who = fun(T) ->
+ L1 = mnesia:table_info(T, ram_copies),
+ L2 = mnesia:table_info(T, disc_copies),
+ L3 = mnesia:table_info(T, disc_only_copies),
+ L1 ++ L2 ++ L3
+ end,
+
+ SNs = ?sort(AllNodes),
+
+ Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ ?match([], mnesia_test_lib:stop_mnesia(AllNodes)),
+ ?match(ok, mnesia:delete_schema(AllNodes)),
+ ?match({error, _}, mnesia:create_schema(foo)),
+ ?match({error, _}, mnesia:create_schema([foo])),
+ ?match({error, _}, mnesia:create_schema([foo@bar])),
+ ?match(ok, mnesia:start()),
+ ?match(false, mnesia:system_info(use_dir)),
+ ?match({atomic, ok}, mnesia:create_table(Tab, [])),
+ ?match({aborted, {has_no_disc, Node1}}, mnesia:dump_tables([Tab])),
+ ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_copies)),
+ ?match({aborted, {has_no_disc, Node1}}, mnesia:change_table_copy_type(Tab, node(), disc_only_copies)),
+
+ ?match(stopped, mnesia:stop()),
+
+ ?match(ok, mnesia:create_schema(AllNodes)),
+ ?match([], mnesia_test_lib:start_mnesia(AllNodes)),
+
+ ?match([SNs, SNs, SNs],
+ lists:map({lists, sort},
+ element(1, rpc:multicall(AllNodes, mnesia, table_info,
+ [schema, disc_copies])))),
+
+ ?match({aborted, {already_exists, schema, Node2, _}},
+ mnesia:change_table_copy_type(schema, Node2, disc_copies)),
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(schema, Node2, ram_copies)),
+ ?match({aborted, {already_exists, schema, Node2, _}},
+ mnesia:change_table_copy_type(schema, Node2, ram_copies)),
+
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(schema, Node2, disc_copies)),
+
+ ?match([SNs, SNs, SNs],
+ lists:map({lists, sort},
+ element(1, rpc:multicall(AllNodes, mnesia, table_info,
+ [schema, disc_copies])))),
+
+ %% Delete the DB
+
+ Tab2 = disk_tab,
+ Tab3 = not_local,
+ Tab4 = local,
+ Tab5 = remote,
+
+ Tabs = [Schema,
+ [{name, Tab2}, {disc_copies, AllNodes}],
+ [{name, Tab3}, {ram_copies, [Node2, Node3]}],
+ [{name, Tab4}, {disc_only_copies, [Node1]}],
+ [{name, Tab5}, {disc_only_copies, [Node2]}]],
+
+ [?match({atomic, ok}, mnesia:create_table(T)) || T <- Tabs ],
+
+ ?match({aborted, {active, _, Node2}},
+ mnesia:del_table_copy(schema, Node2)),
+
+ ?match([], mnesia_test_lib:stop_mnesia([Node1])),
+ ?match({aborted, {node_not_running, Node1}},
+ mnesia:del_table_copy(schema, Node2)),
+
+ ?match([], mnesia_test_lib:start_mnesia([Node1],[Tab2,Tab4])),
+ ?match([], mnesia_test_lib:stop_mnesia([Node2])),
+ ?match({atomic, ok},
+ mnesia:del_table_copy(schema, Node2)),
+
+ %% Check
+ RemNodes = AllNodes -- [Node2],
+
+ ?match(RemNodes, mnesia:system_info(db_nodes)),
+ ?match([Node1], Who(Tab)),
+ ?match(RemNodes, Who(Tab2)),
+ ?match([Node3], Who(Tab3)),
+ ?match([Node1], Who(Tab4)),
+ ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)),
+
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(Tab2, Node3, ram_copies)),
+
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(schema, Node3, ram_copies)),
+
+ ?match([], mnesia_test_lib:stop_mnesia([Node3])),
+ ?match({atomic, ok},
+ mnesia:del_table_copy(schema, Node3)),
+ ?match([Node1], mnesia:system_info(db_nodes)),
+ ?match([Node1], Who(Tab)),
+ ?match([Node1], Who(Tab2)),
+ ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab3)),
+ ?match([Node1], Who(Tab4)),
+ ?match({'EXIT', {aborted, {no_exists, _, _}}}, Who(Tab5)),
+
+ ?verify_mnesia([Node1], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Drop a db node when several disk resident nodes are down
+
+evil_delete_db_node(suite) -> [];
+evil_delete_db_node(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = AllNodes = ?acquire_nodes(3, Config),
+ Tab = evil_delete_db_node,
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{disc_copies, AllNodes}])),
+
+ ?match([], mnesia_test_lib:stop_mnesia([Node2, Node3])),
+
+ ?match({atomic, ok}, mnesia:del_table_copy(schema, Node2)),
+
+ RemNodes = AllNodes -- [Node2],
+
+ ?match(RemNodes, mnesia:system_info(db_nodes)),
+ ?match(RemNodes, mnesia:table_info(Tab, disc_copies)),
+
+ ?verify_mnesia([Node1], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Start and stop the system
+
+start_and_stop(suite) -> [];
+start_and_stop(Config) when is_list(Config) ->
+ [Node1 | _] = Nodes = ?acquire_nodes(all, Config),
+
+ ?match(stopped, rpc:call(Node1, mnesia, stop, [])),
+ ?match(stopped, rpc:call(Node1, mnesia, stop, [])),
+ ?match(ok, rpc:call(Node1, mnesia, start, [])),
+ ?match(ok, rpc:call(Node1, mnesia, start, [])),
+ ?match(stopped, rpc:call(Node1, mnesia, stop, [])),
+ ?verify_mnesia(Nodes -- [Node1], [Node1]),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes)),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Checkpoints and backup management
+
+checkpoint(suite) -> [];
+checkpoint(Config) when is_list(Config) ->
+ checkpoint(2, Config),
+ checkpoint(3, Config).
+
+checkpoint(NodeConfig, Config) ->
+ [Node1 | _] = TabNodes = ?acquire_nodes(NodeConfig, Config),
+ CreateTab = fun(Type, N, Ns) ->
+ Tab0 = lists:concat(["local_checkpoint_", Type, N]),
+ Tab = list_to_atom(Tab0),
+ catch mnesia:delete_table(Tab),
+ ?match({atomic, ok},
+ mnesia:create_table(Tab, [{Type, Ns}])),
+ Tab
+ end,
+ CreateTabs = fun(Type, Acc) ->
+ [CreateTab(Type, 1, [hd(TabNodes)]),
+ CreateTab(Type, 2, TabNodes),
+ CreateTab(Type, 3, [lists:last(TabNodes)])] ++
+ Acc
+ end,
+ Types = [ram_copies, disc_copies, disc_only_copies],
+ Tabs = lists:foldl(CreateTabs, [], Types),
+ Recs = ?sort([{T, N, N} || T <- Tabs, N <- lists:seq(1, 10)]),
+ lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs),
+
+ CpName = a_checkpoint_name,
+ MinArgs = [{name, CpName}, {min, Tabs}, {allow_remote, false}],
+ ?match({error, _}, rpc:call(Node1, mnesia, activate_checkpoint, [MinArgs])),
+
+ MaxArgs = [{name, CpName}, {max, Tabs}, {allow_remote, true}],
+ ?match({ok, CpName, L} when is_list(L),
+ rpc:call(Node1, mnesia, activate_checkpoint, [MaxArgs])),
+ ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])),
+
+ Args = [{name, CpName}, {min, Tabs}, {allow_remote, true}],
+ ?match({ok, CpName, L} when is_list(L),
+ rpc:call(Node1, mnesia, activate_checkpoint, [Args])),
+ Recs2 = ?sort([{T, K, 0} || {T, K, _} <- Recs]),
+ lists:foreach(fun(R) -> ?match(ok, mnesia:dirty_write(R)) end, Recs2),
+ ?match(ok, rpc:call(Node1, mnesia, deactivate_checkpoint, [CpName])),
+
+ ?match({error, Reason1 } when element(1, Reason1) == no_exists,
+ mnesia:deactivate_checkpoint(CpName)),
+ ?match({error, Reason2 } when element(1, Reason2) == badarg,
+ mnesia:activate_checkpoint(foo)),
+ ?match({error, Reason3 } when element(1, Reason3) == badarg,
+ mnesia:activate_checkpoint([{foo, foo}])),
+ ?match({error, Reason4 } when element(1, Reason4) == badarg,
+ mnesia:activate_checkpoint([{max, foo}])),
+ ?match({error, Reason5 } when element(1, Reason5) == badarg,
+ mnesia:activate_checkpoint([{min, foo}])),
+ ?match({error, _}, mnesia:activate_checkpoint([{min, [foo@bar]}])),
+ ?match({error, Reason6 } when element(1, Reason6) == badarg,
+ mnesia:activate_checkpoint([{allow_remote, foo}])),
+
+ Fun = fun(Tab) -> ?match({atomic, ok}, mnesia:delete_table(Tab)) end,
+ lists:foreach(Fun, Tabs),
+ ?verify_mnesia(TabNodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create and delete tables
+
+%% Get meta info about table
+
+-define(vrl, mnesia_test_lib:verify_replica_location).
+
+replica_location(suite) -> [];
+replica_location(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Tab = replica_location,
+
+ %% Create three replicas
+ Schema = [{name, Tab}, {disc_only_copies, [Node1]},
+ {ram_copies, [Node2]}, {disc_copies, [Node3]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match([], ?vrl(Tab, [Node1], [Node2], [Node3], Nodes)),
+
+ %% Delete one replica
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)),
+ ?match([], ?vrl(Tab, [Node1], [], [Node3], Nodes)),
+
+ %% Move one replica
+ ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)),
+ ?match([], ?vrl(Tab, [Node2], [], [Node3], Nodes)),
+
+ %% Change replica type
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, ram_copies)),
+ ?match([], ?vrl(Tab, [], [Node2], [Node3], Nodes)),
+
+ ?verify_mnesia(Nodes, []).
+
+table_lifecycle(suite) -> [];
+table_lifecycle(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+
+ ?match({atomic, ok}, mnesia:create_table([{type, bag},
+ {ram_copies, [Node1]},
+ {attributes, [rajtan, tajtan]},
+ {name, order_of_args}])),
+ ?match([], mnesia:dirty_read({order_of_args, 4711})),
+ ?match({atomic, ok}, mnesia:create_table([{name, already_exists},
+ {ram_copies, [Node1]}])),
+ ?match({aborted, Reason23 } when element(1, Reason23) ==already_exists,
+ mnesia:create_table([{name, already_exists},
+ {ram_copies, [Node1]}])),
+ ?match({aborted, Reason21 } when element(1, Reason21) == bad_type,
+ mnesia:create_table([{name, bad_node}, {ram_copies, ["foo"]}])),
+ ?match({aborted, Reason2} when element(1, Reason2) == bad_type,
+ mnesia:create_table([{name, zero_arity}, {attributes, []}])),
+ ?match({aborted, Reason3} when element(1, Reason3) == badarg,
+ mnesia:create_table([])),
+ ?match({aborted, Reason4} when element(1, Reason4) == badarg,
+ mnesia:create_table(atom)),
+ ?match({aborted, Reason5} when element(1, Reason5) == badarg,
+ mnesia:create_table({cstruct, table_name_as_atom})),
+ ?match({aborted, Reason6 } when element(1, Reason6) == bad_type,
+ mnesia:create_table([{name, no_host}, {ram_copies, foo}])),
+ ?match({aborted, Reason7 } when element(1, Reason7) == bad_type,
+ mnesia:create_table([{name, no_host}, {disc_only_copies, foo}])),
+ ?match({aborted, Reason8} when element(1, Reason8) == bad_type,
+ mnesia:create_table([{name, no_host}, {disc_copies, foo}])),
+
+ CreateFun =
+ fun() -> ?match({aborted, nested_transaction},
+ mnesia:create_table([{name, nested_trans}])), ok
+ end,
+ ?match({atomic, ok}, mnesia:transaction(CreateFun)),
+ ?match({atomic, ok}, mnesia:create_table([{name, remote_tab},
+ {ram_copies, [Node2]}])),
+
+ ?match({atomic, ok}, mnesia:create_table([{name, a_brand_new_tab},
+ {ram_copies, [Node1]}])),
+ ?match([], mnesia:dirty_read({a_brand_new_tab, 4711})),
+ ?match({atomic, ok}, mnesia:delete_table(a_brand_new_tab)),
+ ?match({'EXIT', {aborted, Reason31}} when element(1, Reason31) == no_exists,
+ mnesia:dirty_read({a_brand_new_tab, 4711})),
+ ?match({aborted, Reason41} when element(1, Reason41) == no_exists,
+ mnesia:delete_table(a_brand_new_tab)),
+ ?match({aborted, Reason9} when element(1, Reason9) == badarg,
+ mnesia:create_table([])),
+
+ ?match({atomic, ok}, mnesia:create_table([{name, nested_del_trans},
+ {ram_copies, [Node1]}])),
+
+ DeleteFun = fun() -> ?match({aborted, nested_transaction},
+ mnesia:delete_table(nested_del_trans)), ok end,
+ ?match({atomic, ok}, mnesia:transaction(DeleteFun)),
+
+ ?match({aborted, Reason10} when element(1, Reason10) == bad_type,
+ mnesia:create_table([{name, create_with_index}, {index, 2}])),
+ ?match({aborted, Reason32} when element(1, Reason32) == bad_type,
+ mnesia:create_table([{name, create_with_index}, {index, [-1]}])),
+ ?match({aborted, Reason33} when element(1, Reason33) == bad_type,
+ mnesia:create_table([{name, create_with_index}, {index, [0]}])),
+ ?match({aborted, Reason34} when element(1, Reason34) == bad_type,
+ mnesia:create_table([{name, create_with_index}, {index, [1]}])),
+ ?match({aborted, Reason35} when element(1, Reason35) == bad_type,
+ mnesia:create_table([{name, create_with_index}, {index, [2]}])),
+ ?match({atomic, ok},
+ mnesia:create_table([{name, create_with_index}, {index, [3]},
+ {ram_copies, [Node1]}])),
+ ets:new(ets_table, [named_table]),
+
+ ?match({aborted, _}, mnesia:create_table(ets_table, [{ram_copies, Nodes}])),
+
+ ?verify_mnesia(Nodes, []).
+
+add_copy_conflict(suite) -> [];
+add_copy_conflict(doc) ->
+ ["Verify that OTP-5065 doesn't happen again, whitebox testing"];
+add_copy_conflict(Config) when is_list(Config) ->
+ Nodes = [Node1, Node2] =
+ ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]),
+
+ ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(b, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(test, [{ram_copies, [Node2]}])),
+ mnesia:stop(),
+ ?match(ok,mnesia:start([{no_table_loaders, 1}])),
+
+ verify_ll_queue(10),
+
+ Self = self(),
+ Test = fun() ->
+ Res = mnesia:add_table_copy(test, Node1, ram_copies),
+ Self ! {test, Res}
+ end,
+ %% Create conflict with loader queue.
+ spawn_link(Test),
+ ?match_receive(timeout),
+ %% Conflict ok
+ mnesia_controller:unblock_controller(),
+
+ ?match_receive({test, {atomic,ok}}),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(1, Config).
+
+verify_ll_queue(0) ->
+ ?error("Couldn't find anything in queue~n",[]);
+verify_ll_queue(N) ->
+ ?match(granted,mnesia_controller:block_controller()),
+ case mnesia_controller:get_info(1000) of
+ {info,{state,_,true,[],_Loader,[],[],[],_,_,_,_,_,_}} ->
+ %% Very slow SMP machines havn't loaded it yet..
+ mnesia_controller:unblock_controller(),
+ timer:sleep(10),
+ verify_ll_queue(N-1);
+ {info,{state,_,true,[],Loader,LL,[],[],_,_,_,_,_,_}} ->
+ io:format("~p~n", [{Loader,LL}]),
+ ?match([_], LL); %% Verify that something is in the loader queue
+ Else ->
+ ?error("No match ~p maybe the internal format has changed~n",[Else])
+ end.
+
+add_copy_when_going_down(suite) -> [];
+add_copy_when_going_down(doc) ->
+ ["Tests abort when node we load from goes down"];
+add_copy_when_going_down(Config) ->
+ [Node1, Node2] =
+ ?acquire_nodes(2, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ ?match({atomic, ok}, mnesia:create_table(a, [{ram_copies, [Node1]}])),
+ %% Grab a write lock
+ WriteAndWait = fun() ->
+ mnesia:write({a,1,1}),
+ receive continue -> ok
+ end
+ end,
+ _Lock = spawn(fun() -> mnesia:transaction(WriteAndWait) end),
+ Tester = self(),
+ spawn_link(fun() -> Res = rpc:call(Node2,mnesia, add_table_copy,
+ [a, Node2, ram_copies]),
+ Tester ! {test, Res}
+ end),
+ %% We have a lock here we should get a timeout
+ ?match_receive(timeout),
+ mnesia_test_lib:kill_mnesia([Node1]),
+ ?match_receive({test,{aborted,_}}),
+ ?verify_mnesia([Node2], []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Add, drop and move replicas, change storage types
+%% Change table layout (only arity change supported)
+
+-record(replica_management, {k, v}).
+-record(new_replica_management, {k, v, extra}).
+
+-define(SS(R), lists:sort(element(1,R))).
+
+replica_management(doc) ->
+ "Add, drop and move replicas, change storage types.";
+replica_management(suite) ->
+ [];
+replica_management(Config) when is_list(Config) ->
+ %% add_table_copy/3, del_table_copy/2, move_table_copy/3,
+ %% change_table_copy_type/3, transform_table/3
+
+ Nodes = [Node1, Node2, Node3] = ?acquire_nodes(3, Config),
+
+ Tab = replica_management,
+ Attrs = record_info(fields, replica_management),
+
+ %%
+ %% Add, delete and change replicas
+ %%
+ ?match({atomic, ok},
+ mnesia:create_table([{name, Tab}, {attributes, Attrs},
+ {ram_copies, [Node1, Node3]}])),
+ [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)],
+ ?match([], ?vrl(Tab, [], [Node1, Node3], [], Nodes)),
+ %% R - -
+ ?match({atomic, ok}, mnesia:dump_tables([Tab])),
+ ?match({aborted, Reason50 } when element(1, Reason50) == combine_error,
+ mnesia:add_table_copy(Tab, Node2, disc_copies)),
+ ?match({aborted, Reason51 } when element(1, Reason51) == combine_error,
+ mnesia:change_table_copy_type(Tab, Node1, disc_copies)),
+ ?match({atomic, ok}, mnesia:clear_table(Tab)),
+ SyncedCheck = fun() ->
+ mnesia:lock({record,Tab,0}, write),
+ ?match([0,0,0], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size])))
+ end,
+ mnesia:transaction(SyncedCheck),
+
+ ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])),
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)),
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node3)),
+ ?match([], ?vrl(Tab, [], [], [], Nodes)),
+ %% - - -
+ ?match({aborted,Reason52} when element(1, Reason52) == no_exists,
+ mnesia:add_table_copy(Tab, Node3, ram_copies)),
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {attributes, Attrs},
+ {disc_copies, [Node1]}])),
+ ?match([], ?vrl(Tab, [], [], [Node1], Nodes)),
+ %% D - -
+ [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)],
+
+ ?match({aborted, Reason53} when element(1, Reason53) == badarg,
+ mnesia:add_table_copy(Tab, Node2, bad_storage_type)),
+ ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node2, disc_only_copies)),
+ ?match([], ?vrl(Tab, [Node2], [], [Node1], Nodes)),
+ ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% D DO -
+ ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node3, ram_copies)),
+ ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% D DO R
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)),
+ ?match([], ?vrl(Tab, [Node1, Node2], [Node3], [], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% DO DO R
+ ?match({aborted, Reason54} when element(1, Reason54) == already_exists,
+ mnesia:add_table_copy(Tab, Node3, ram_copies)),
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)),
+ ?match([], ?vrl(Tab, [Node2], [Node3], [], Nodes)),
+ %% - DO R
+ ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)),
+ ?match(Tab, ets:new(Tab, [named_table])),
+ ?match({aborted, _}, mnesia:add_table_copy(Tab, Node1, disc_copies)),
+ ?match(true, ets:delete(Tab)),
+ ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, disc_copies)),
+ ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% D DO R
+ ?match({atomic, ok},mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)),
+ ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+
+ %% D DO D0
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, ram_copies)),
+ ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% D DO R
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(Tab, Node2, disc_copies)),
+ ?match([], ?vrl(Tab, [], [Node3], [Node1,Node2], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+
+ %% D D R
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_only_copies)),
+ ?match([], ?vrl(Tab, [Node1], [Node3], [Node2], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+
+ %% DO D R
+ ?match(Tab, ets:new(Tab, [named_table])),
+ ?match({aborted, _}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)),
+ ?match(true, ets:delete(Tab)),
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, ram_copies)),
+ ?match([], ?vrl(Tab, [], [Node3,Node1], [Node2], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% R D R
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node1, disc_copies)),
+ ?match([], ?vrl(Tab, [], [Node3], [Node2,Node1], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+
+ %% D D R
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node2, disc_only_copies)),
+ ?match([], ?vrl(Tab, [Node2], [Node3], [Node1], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+
+ %% D DO R
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, Node3, disc_only_copies)),
+ ?match([], ?vrl(Tab, [Node2, Node3], [], [Node1], Nodes)),
+ ?match([10,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% D DO DO
+ %% test clear
+ ?match({atomic, ok}, mnesia:clear_table(Tab)),
+ mnesia:transaction(SyncedCheck),
+
+ %% rewrite for rest of testcase
+ [?match(ok, mnesia:dirty_write({Tab, K, K + 2})) || K <-lists:seq(1, 10)],
+
+ %% D DO DO
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node2)),
+ ?match([], ?vrl(Tab, [Node3], [], [Node1], Nodes)),
+ %% D - DO
+ ?match({aborted, Reason55} when element(1, Reason55) == already_exists,
+ mnesia:change_table_copy_type(Tab, Node1, disc_copies)),
+
+ %%
+ %% Move replica
+ %%
+ ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)),
+ ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)),
+ ?match([0,10,10], ?SS(rpc:multicall(Nodes, mnesia, table_info, [Tab, size]))),
+ %% - D DO
+ ?match({aborted, _}, mnesia:move_table_copy(Tab, Node1, Node2)),
+ ?match([], mnesia_test_lib:stop_mnesia([Node3])),
+ ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 43, sync_me}) end)),
+ ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])),
+ %% - D DO
+ ?match({aborted,Reason56} when element(1, Reason56) == not_active,
+ mnesia:move_table_copy(Tab, Node3, Node1)),
+ ?match([], ?vrl(Tab, [Node3], [], [Node2],Nodes -- [Node3])),
+ %% DO D -
+ ?match([], mnesia_test_lib:start_mnesia([Node3])),
+ ?match([], ?vrl(Tab, [Node3], [], [Node2], Nodes)),
+ %% DO D -
+
+ %%
+ %% Transformer
+ %%
+
+ NewAttrs = record_info(fields, new_replica_management),
+ Transformer =
+ fun(Rec) when is_record(Rec, replica_management) ->
+ #new_replica_management{k = Rec#replica_management.k,
+ v = Rec#replica_management.v,
+ extra = Rec#replica_management.k * 2}
+ end,
+ ?match({atomic, ok}, mnesia:transform_table(Tab, fun(R) -> R end, Attrs)),
+ ?match({atomic, ok}, mnesia:transform_table(Tab, Transformer, NewAttrs, new_replica_management)),
+
+ ERlist = [#new_replica_management{k = K, v = K+2, extra = K*2} || K <- lists:seq(1, 10)],
+ ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)],
+
+ ?match(ERlist, ARlist),
+
+ ?match({aborted, Reason56} when element(1, Reason56) == bad_type,
+ mnesia:transform_table(Tab, Transformer, 0)),
+ ?match({aborted, Reason57} when element(1, Reason57) == bad_type,
+ mnesia:transform_table(Tab, Transformer, -1)),
+ ?match({aborted, Reason58} when element(1, Reason58) == bad_type,
+ mnesia:transform_table(Tab, Transformer, [])),
+ ?match({aborted, Reason59} when element(1, Reason59) == bad_type,
+ mnesia:transform_table(Tab, no_fun, NewAttrs)),
+ ?match({aborted, Reason59} when element(1, Reason59) == bad_type,
+ mnesia:transform_table(Tab, fun(X) -> X end, NewAttrs, {tuple})),
+
+ %% OTP-3878
+ ?match({atomic, ok}, mnesia:transform_table(Tab, ignore,
+ NewAttrs ++ [dummy])),
+ ?match({atomic, ok}, mnesia:transform_table(Tab, ignore,
+ NewAttrs ++ [dummy], last_rec)),
+
+ ARlist = [hd(mnesia:dirty_read(Tab, K)) || K <- lists:seq(1, 10)],
+ ?match({'EXIT',{aborted,{bad_type,_}}},
+ mnesia:dirty_write(Tab, #new_replica_management{})),
+ ?match(ok, mnesia:dirty_write(Tab, {last_rec, k, v, e, dummy})),
+
+ ?verify_mnesia(Nodes, []).
+
+schema_availability(doc) ->
+ ["Test that schema succeeds (or fails) as intended when some db nodes are down."];
+schema_availability(suite) ->
+ [];
+schema_availability(Config) when is_list(Config) ->
+ [N1, _N2, N3] = Nodes = ?acquire_nodes(3, Config),
+ Tab = schema_availability,
+ Storage = mnesia_test_lib:storage_type(ram_copies, Config),
+ Def1 = [{Storage, [N1, N3]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def1)),
+
+ N = 10,
+ ?match(ok, mnesia:sync_dirty(fun() -> [mnesia:write({Tab, K, K + 2}) || K <- lists:seq(1, N)], ok end)),
+ ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])),
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+ ?match({[N,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])),
+
+ ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])),
+ ?match({[N,0,N], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])),
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+
+ ?match({atomic, ok}, mnesia:clear_table(Tab)),
+ ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])),
+
+ ?match([], mnesia_test_lib:start_mnesia([N3], [Tab])),
+ ?match({[0,0,0], []}, rpc:multicall(Nodes, mnesia, table_info, [Tab, size])),
+
+ ?verify_mnesia(Nodes, []).
+
+-define(badrpc(Tab), {badrpc, {'EXIT', {aborted,{no_exists,Tab}}}}).
+
+local_content(doc) ->
+ ["Test local_content functionality, we want to see that correct"
+ " properties gets propageted correctly between nodes"];
+local_content(suite) -> [];
+local_content(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Tab1 = local1,
+ Def1 = [{local_content, true}, {ram_copies, Nodes}],
+ Tab2 = local2,
+ Def2 = [{local_content, true}, {disc_copies, [Node1]}],
+ Tab3 = local3,
+ Def3 = [{local_content, true}, {disc_only_copies, [Node1]}],
+ Tab4 = local4,
+ Def4 = [{local_content, true}, {ram_copies, [Node1]}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)),
+ ?match({atomic, ok}, mnesia:create_table(Tab4, Def4)),
+
+ ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab1, 1, Node1}])),
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab1, 1, Node2}])),
+ ?match(ok, rpc:call(Node3, mnesia, dirty_write, [{Tab1, 1, Node3}])),
+ ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab2, 1, Node1}])),
+ ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab3, 1, Node1}])),
+ ?match(ok, rpc:call(Node1, mnesia, dirty_write, [{Tab4, 1, Node1}])),
+
+ ?match(?badrpc(Tab2), rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])),
+ ?match(?badrpc(Tab3), rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])),
+ ?match(?badrpc(Tab4), rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])),
+
+ ?match({atomic, ok}, rpc:call(Node1, mnesia, add_table_copy, [Tab2, Node2, ram_copies])),
+ ?match({atomic, ok}, rpc:call(Node2, mnesia, add_table_copy, [Tab3, Node2, disc_copies])),
+ ?match({atomic, ok}, rpc:call(Node3, mnesia, add_table_copy, [Tab4, Node2, disc_only_copies])),
+ ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])),
+ ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])),
+ ?match([], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])),
+
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab2, 1, Node2}])),
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab3, 1, Node2}])),
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab4, 1, Node2}])),
+
+ ?match([{Tab1, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab1, 1}])),
+ ?match([{Tab2, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab2, 1}])),
+ ?match([{Tab3, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab3, 1}])),
+ ?match([{Tab4, 1, Node1}], rpc:call(Node1, mnesia, dirty_read, [{Tab4, 1}])),
+
+ ?match([{Tab1, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab1, 1}])),
+ ?match([{Tab2, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab2, 1}])),
+ ?match([{Tab3, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab3, 1}])),
+ ?match([{Tab4, 1, Node2}], rpc:call(Node2, mnesia, dirty_read, [{Tab4, 1}])),
+
+ ?match([{Tab1, 1, Node3}], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])),
+ ?match(?badrpc([_Tab2, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab2, 1}])),
+ ?match(?badrpc([_Tab3, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab3, 1}])),
+ ?match(?badrpc([_Tab4, 1]), rpc:call(Node3, mnesia, dirty_read, [{Tab4, 1}])),
+
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(schema, Node3, ram_copies)),
+ ?match([], mnesia_test_lib:stop_mnesia([Node3])),
+
+ %% Added for OTP-44306
+ ?match(ok, rpc:call(Node3, mnesia, start, [])),
+ ?match({ok, _}, mnesia:change_config(extra_db_nodes, [Node3])),
+
+ mnesia_test_lib:sync_tables([Node3], [Tab1]),
+
+ ?match([], rpc:call(Node3, mnesia, dirty_read, [{Tab1, 1}])),
+
+ ?match({atomic, ok}, rpc:call(Node1, mnesia, clear_table, [Tab1])),
+
+ SyncedCheck = fun(Tab) ->
+ mnesia:lock({record,Tab,0}, write),
+ {OK, []} = rpc:multicall(Nodes, mnesia, table_info, [Tab, size]),
+ OK
+ end,
+ ?match({atomic, [0,1,0]}, mnesia:transaction(SyncedCheck, [Tab1])),
+ ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab2])),
+ ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab2])),
+ ?match({atomic, ok}, rpc:call(Node2, mnesia, clear_table, [Tab3])),
+ ?match({atomic, [1,0,0]}, mnesia:transaction(SyncedCheck, [Tab3])),
+
+ ?verify_mnesia(Nodes, []).
+
+table_access_modifications(suite) ->
+ [
+ change_table_access_mode,
+ change_table_load_order,
+ set_master_nodes,
+ offline_set_master_nodes
+ ].
+
+change_table_access_mode(suite) -> [];
+change_table_access_mode(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Tab = test_access_mode_tab,
+
+ Def = case mnesia_test_lib:diskless(Config) of
+ true -> [{name, Tab}, {ram_copies, Nodes}];
+ false -> [{name, Tab}, {ram_copies, [Node1]},
+ {disc_copies, [Node2]},
+ {disc_only_copies, [Node3]}]
+ end,
+ ?match({atomic, ok}, mnesia:create_table(Def)),
+
+ Write = fun(What) -> mnesia:write({Tab, 1, What}) end,
+ Read = fun() -> mnesia:read({Tab, 1}) end,
+
+ ?match({atomic, ok}, mnesia:transaction(Write, [test_ok])),
+ %% test read_only
+ ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)),
+ ?match({aborted, _}, mnesia:transaction(Write, [nok])),
+ ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, [nok]})),
+ ?match({aborted, _}, rpc:call(Node2, mnesia, transaction, [Write, [nok]])),
+ ?match({aborted, _}, rpc:call(Node3, mnesia, transaction, [Write, [nok]])),
+ ?match({atomic, [{Tab, 1, test_ok}]}, mnesia:transaction(Read)),
+ %% test read_write
+ ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)),
+ ?match({atomic, ok}, mnesia:transaction(Write, [test_ok1])),
+ ?match({atomic, [{Tab, 1, test_ok1}]}, mnesia:transaction(Read)),
+ ?match({atomic, ok}, rpc:call(Node2, mnesia, transaction, [Write, [test_ok2]])),
+ ?match({atomic, [{Tab, 1, test_ok2}]}, mnesia:transaction(Read)),
+ ?match({atomic, ok}, rpc:call(Node3, mnesia, transaction, [Write, [test_ok3]])),
+ ?match({atomic, [{Tab, 1, test_ok3}]}, mnesia:transaction(Read)),
+
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+
+ Def4 = [{name, Tab}, {access_mode, read_only_bad}],
+ ?match({aborted, {bad_type, _, _}}, mnesia:create_table(Def4)),
+
+ Def2 = [{name, Tab}, {access_mode, read_only}],
+ ?match({atomic, ok}, mnesia:create_table(Def2)),
+ ?match({aborted, _}, mnesia:transaction(Write, [nok])),
+
+ ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)),
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+
+ Def3 = [{name, Tab}, {mnesia_test_lib:storage_type(disc_copies, Config),
+ [Node1, Node2]},
+ {access_mode, read_write}],
+ ?match({atomic, ok}, mnesia:create_table(Def3)),
+ ?match({atomic, ok}, mnesia:transaction(Write, [ok])),
+ ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_only)),
+ ?match({aborted, _}, mnesia:del_table_copy(Tab, Node2)),
+ ?match({aborted, _}, mnesia:del_table_copy(Tab, Node1)),
+ ?match({aborted, _}, mnesia:delete_table(Tab)),
+ ?match({atomic, ok}, mnesia:change_table_access_mode(Tab, read_write)),
+
+ ?match({aborted, {bad_type, _, _}},
+ mnesia:change_table_access_mode(Tab, strange_atom)),
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+
+ ?match({aborted, {no_exists, _}},
+ mnesia:change_table_access_mode(err_tab, read_only)),
+ ?match({aborted, {no_exists, _}},
+ mnesia:change_table_access_mode([Tab], read_only)),
+ ?verify_mnesia(Nodes, []).
+
+change_table_load_order(suite) -> [];
+change_table_load_order(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Tab1 = load_order_tab1,
+ Tab2 = load_order_tab2,
+ Tab3 = load_order_tab3,
+
+ Def = case mnesia_test_lib:diskless(Config) of
+ true -> [{ram_copies, Nodes}];
+ false ->
+ [{ram_copies, [Node1]},
+ {disc_copies, [Node2]},
+ {disc_only_copies, [Node3]}]
+ end,
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def)),
+
+ ?match({aborted, _}, mnesia:change_table_load_order(Tab1, should_be_integer)),
+ ?match({aborted, _}, mnesia:change_table_load_order(err_tab, 5)),
+ ?match({aborted, _}, mnesia:change_table_load_order([err_tab], 5)),
+ ?match({atomic, ok}, mnesia:change_table_load_order(Tab1, 5)),
+ ?match({atomic, ok}, mnesia:change_table_load_order(Tab2, 4)),
+ ?match({atomic, ok}, mnesia:change_table_load_order(Tab3, 73)),
+
+ ?match({aborted, _}, mnesia:change_table_load_order(schema, -32)),
+
+ ?verify_mnesia(Nodes, []).
+
+set_master_nodes(suite) -> [];
+set_master_nodes(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Tab1 = master_node_tab1,
+ Tab2 = master_node_tab2,
+ Tab3 = master_node_tab3,
+ Def1 = [{ram_copies, [Node1, Node2]}],
+ Def2 = [{disc_copies, [Node2, Node3]}],
+ Def3 = [{disc_only_copies, [Node3, Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)),
+
+ ?match({error, _}, mnesia:set_master_nodes(schema, ['[email protected]'])),
+ ?match({error, _}, mnesia:set_master_nodes(badtab, [Node2, Node3])),
+ ?match({error, _}, mnesia:set_master_nodes(Tab1, [Node3])),
+ ?match([], mnesia:table_info(Tab1, master_nodes)),
+
+ ?match(ok, mnesia:set_master_nodes(schema, [Node3, Node1])),
+ ?match([Node3, Node1], mnesia:table_info(schema, master_nodes)),
+ ?match(ok, mnesia:set_master_nodes(Tab1, [Node2])),
+ ?match([Node2], mnesia:table_info(Tab1, master_nodes)),
+ ?match(ok, mnesia:set_master_nodes(Tab1, [Node2, Node1])),
+ ?match([Node2, Node1], mnesia:table_info(Tab1, master_nodes)),
+ ?match(ok, mnesia:set_master_nodes(Tab2, [Node2])), % Should set where_to_read to Node2!
+ ?match([Node2], mnesia:table_info(Tab2, master_nodes)),
+ ?match(ok, mnesia:set_master_nodes(Tab3, [Node3])),
+ ?match([Node3], mnesia:table_info(Tab3, master_nodes)),
+ ?match(ok, mnesia:set_master_nodes(Tab3, [])),
+ ?match([], mnesia:table_info(Tab3, master_nodes)),
+
+ ?match(ok, mnesia:set_master_nodes([Node1])),
+ ?match([Node1], mnesia:table_info(schema, master_nodes)),
+ ?match([Node1], mnesia:table_info(Tab1, master_nodes)),
+ ?match([], mnesia:table_info(Tab2, master_nodes)),
+ ?match([Node1], mnesia:table_info(Tab3, master_nodes)),
+
+ ?match(ok, mnesia:set_master_nodes([Node1, Node2])),
+ ?match([Node1, Node2], mnesia:table_info(schema, master_nodes)),
+ ?match([Node1, Node2], mnesia:table_info(Tab1, master_nodes)),
+ ?match([Node2], mnesia:table_info(Tab2, master_nodes)),
+ ?match([Node1], mnesia:table_info(Tab3, master_nodes)),
+
+ ?verify_mnesia(Nodes, []).
+
+offline_set_master_nodes(suite) -> [];
+offline_set_master_nodes(Config) when is_list(Config) ->
+ [Node] = Nodes = ?acquire_nodes(1, Config),
+ Tab1 = offline_master_node_tab1,
+ Tab2 = offline_master_node_tab2,
+ Tab3 = offline_master_node_tab3,
+ Tabs = ?sort([Tab1, Tab2, Tab3]),
+ Def1 = [{ram_copies, [Node]}],
+ Def2 = [{disc_copies, [Node]}],
+ Def3 = [{disc_only_copies, [Node]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)),
+ ?match([], mnesia:system_info(master_node_tables)),
+ ?match([], mnesia_test_lib:stop_mnesia([Node])),
+
+ ?match(ok, mnesia:set_master_nodes(Tab1, [Node])),
+ ?match(ok, mnesia:set_master_nodes(Tab2, [Node])),
+ ?match(ok, mnesia:set_master_nodes(Tab3, [Node])),
+ ?match([], mnesia_test_lib:start_mnesia([Node])),
+ ?match(Tabs, ?sort(mnesia:system_info(master_node_tables))),
+
+ ?match([], mnesia_test_lib:stop_mnesia([Node])),
+ ?match(ok, mnesia:set_master_nodes(Tab1, [])),
+ ?match(ok, mnesia:set_master_nodes(Tab2, [])),
+ ?match(ok, mnesia:set_master_nodes(Tab3, [])),
+ ?match([], mnesia_test_lib:start_mnesia([Node])),
+ ?match([], mnesia:system_info(master_node_tables)),
+
+ ?match([], mnesia_test_lib:stop_mnesia([Node])),
+ ?match(ok, mnesia:set_master_nodes([Node])),
+ ?match([], mnesia_test_lib:start_mnesia([Node])),
+ AllTabs = ?sort([schema | Tabs]),
+ ?match(AllTabs, ?sort(mnesia:system_info(master_node_tables))),
+
+ ?match([], mnesia_test_lib:stop_mnesia([Node])),
+ ?match(ok, mnesia:set_master_nodes([])),
+ ?match([], mnesia_test_lib:start_mnesia([Node])),
+ ?match([], mnesia:system_info(master_node_tables)),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Syncronize table with log or disc
+%%
+table_sync(suite) ->
+ [
+ dump_tables,
+ dump_log,
+ wait_for_tables,
+ force_load_table
+ ].
+
+%% Dump ram tables on disc
+dump_tables(suite) -> [];
+dump_tables(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = dump_tables,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ %% Dump 10 records
+ Size = 10,
+ Keys = lists:seq(1, Size),
+ Records = [{Tab, A, 7} || A <- Keys],
+ lists:foreach(fun(Rec) -> ?match(ok, mnesia:dirty_write(Rec)) end, Records),
+
+ AllKeys = fun() -> ?sort(mnesia:all_keys(Tab)) end,
+
+ ?match({atomic, Keys}, mnesia:transaction(AllKeys)),
+ ?match({atomic, ok}, mnesia:dump_tables([Tab])),
+
+ %% Delete one record
+ ?match(ok, mnesia:dirty_delete({Tab, 5})),
+ Keys2 = lists:delete(5, Keys),
+
+ ?match({atomic, Keys2}, mnesia:transaction(AllKeys)),
+
+ %% Check that all 10 is restored after a stop
+ ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])),
+ ?match([], mnesia_test_lib:start_mnesia([Node1, Node2])),
+ ?match(ok, mnesia:wait_for_tables([Tab], infinity)),
+
+ ?match({atomic, Keys}, mnesia:transaction(AllKeys)),
+
+ ?match({aborted,Reason} when element(1, Reason) == no_exists,
+ mnesia:dump_tables([foo])),
+ ?verify_mnesia(Nodes, []).
+
+dump_log(suite) -> [];
+dump_log(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = dump_log,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1, Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ Tab1 = dump_log1,
+ Schema1 = [{name, Tab1}, {attributes, [k, v]}, {disc_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema1)),
+ Tab2 = dump_log2,
+ Schema2 = [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema2)),
+
+ ?match(ok, mnesia:dirty_write({Tab, 1, ok})),
+ ?match(ok, mnesia:dirty_write({Tab1, 1, ok})),
+ ?match(ok, mnesia:dirty_write({Tab2, 1, ok})),
+
+ ?match(dumped, mnesia:dump_log()),
+ ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])),
+
+ ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node2, ram_copies)),
+ ?match(dumped, rpc:call(Node2, mnesia, dump_log, [])),
+
+ Self = self(),
+ spawn(fun() -> dump_log(100, Self) end),
+ spawn(fun() -> dump_log(100, Self) end),
+
+ ?match(ok, receive finished -> ok after 3000 -> timeout end),
+ ?match(ok, receive finished -> ok after 3000 -> timeout end),
+
+ ?verify_mnesia(Nodes, []).
+
+dump_log(N, Tester) when N > 0 ->
+ mnesia:dump_log(),
+ dump_log(N-1, Tester);
+dump_log(_, Tester) ->
+ Tester ! finished.
+
+
+wait_for_tables(doc) ->
+ ["Intf. test of wait_for_tables, see also force_load_table"];
+wait_for_tables(suite) -> [];
+wait_for_tables(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = wf_tab,
+ Schema = [{name, Tab}, {ram_copies, [Node1, Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match(ok, mnesia:wait_for_tables([wf_tab], infinity)),
+ ?match(ok, mnesia:wait_for_tables([], timer:seconds(5))),
+ ?match({timeout, [bad_tab]}, mnesia:wait_for_tables([bad_tab], timer:seconds(5))),
+ ?match(ok, mnesia:wait_for_tables([wf_tab], 0)),
+ ?match({error,_}, mnesia:wait_for_tables([wf_tab], -1)),
+ ?verify_mnesia(Nodes, []).
+
+force_load_table(suite) -> [];
+force_load_table(Config) when is_list(Config) ->
+ [Node1, Node2] = ?acquire_nodes(2, Config),
+ Tab = wf_tab,
+
+ Schema = [{name, Tab}, {disc_copies, [Node1, Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, test_ok})),
+ mnesia_test_lib:kill_mnesia([Node1]),
+ ?match(ok, rpc:call(Node2, mnesia, dirty_write, [{Tab, 1, test_nok}])),
+ mnesia_test_lib:kill_mnesia([Node2]),
+ %% timer:sleep(timer:seconds(5)),
+ ?match(ok, mnesia:start()),
+ ?match({timeout, [Tab]}, mnesia:wait_for_tables([Tab], 5)),
+ ?match({'EXIT', _}, mnesia:dirty_read({Tab, 1})),
+ ?match(yes, mnesia:force_load_table(Tab)),
+ ?match([{Tab, 1, test_ok}], mnesia:dirty_read({Tab, 1})),
+
+ ?match({error, _}, mnesia:force_load_table(error_tab)),
+ ?verify_mnesia([Node1], [Node2]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+user_properties(doc) ->
+ ["Test of reading, writing and deletion of user properties",
+ "Plus initialization of user properties when a table is created",
+ "Do also test mnesia:table_info(Tab, user_properties)"];
+user_properties(suite) -> [];
+user_properties(Config) when is_list(Config) ->
+ [Node] = Nodes = ?acquire_nodes(1, Config),
+ Tab1 = user_properties_1,
+ Tab2 = user_properties_2,
+ Tab3 = user_properties_3,
+ Def1 = [{ram_copies, [Node]}, {user_properties, []}],
+ Def2 = [{mnesia_test_lib:storage_type(disc_copies, Config), [Node]}],
+ Def3 = [{mnesia_test_lib:storage_type(disc_only_copies, Config), [Node]},
+ {user_properties, []}],
+
+ PropKey = my_prop,
+ Prop = {PropKey, some, elements},
+ Prop2 = {PropKey, some, other, elements},
+ YourProp= {your_prop},
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)),
+
+ ?match([], mnesia:table_info(Tab1, user_properties)),
+ ?match([], mnesia:table_info(Tab2, user_properties)),
+ ?match([], mnesia:table_info(Tab3, user_properties)),
+
+ ?match({'EXIT', {no_exists, {Tab1, user_property, PropKey}}},
+ mnesia:read_table_property(Tab1, PropKey)),
+ ?match({'EXIT', {no_exists, {Tab2, user_property, PropKey}}},
+ mnesia:read_table_property(Tab2, PropKey)),
+ ?match({'EXIT', {no_exists, {Tab3, user_property, PropKey}}},
+ mnesia:read_table_property(Tab3, PropKey)),
+
+ ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop)),
+ ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop)),
+ ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop)),
+ ?match({atomic, ok}, mnesia:write_table_property(Tab1, YourProp)),
+ ?match({atomic, ok}, mnesia:write_table_property(Tab2, YourProp)),
+ ?match({atomic, ok}, mnesia:write_table_property(Tab3, YourProp)),
+
+ ?match(Prop, mnesia:read_table_property(Tab1, PropKey)),
+ ?match(Prop, mnesia:read_table_property(Tab2, PropKey)),
+ ?match(Prop, mnesia:read_table_property(Tab3, PropKey)),
+
+ ?match({atomic, ok}, mnesia:write_table_property(Tab1, Prop2)),
+ ?match({atomic, ok}, mnesia:write_table_property(Tab2, Prop2)),
+ ?match({atomic, ok}, mnesia:write_table_property(Tab3, Prop2)),
+ ?match(Prop2, mnesia:read_table_property(Tab1, PropKey)),
+ ?match(Prop2, mnesia:read_table_property(Tab2, PropKey)),
+ ?match(Prop2, mnesia:read_table_property(Tab3, PropKey)),
+
+ ?match({atomic, ok}, mnesia:delete_table_property(Tab1, PropKey)),
+ ?match({atomic, ok}, mnesia:delete_table_property(Tab2, PropKey)),
+ ?match({atomic, ok}, mnesia:delete_table_property(Tab3, PropKey)),
+
+ ?match([YourProp], mnesia:table_info(Tab1, user_properties)),
+ ?match([YourProp], mnesia:table_info(Tab2, user_properties)),
+ ?match([YourProp], mnesia:table_info(Tab3, user_properties)),
+
+ Tab4 = user_properties_4,
+ ?match({atomic, ok},
+ mnesia:create_table(Tab4, [{user_properties, [Prop]}])),
+
+ ?match([Prop], mnesia:table_info(Tab4, user_properties)),
+
+ %% Some error cases
+
+ ?match({aborted, {bad_type, Tab1, {}}},
+ mnesia:write_table_property(Tab1, {})),
+ ?match({aborted, {bad_type, Tab1, ali}},
+ mnesia:write_table_property(Tab1, ali)),
+
+ Tab5 = user_properties_5,
+ ?match({aborted, {bad_type, Tab5, {user_properties, {}}}},
+ mnesia:create_table(Tab5, [{user_properties, {}}])),
+ ?match({aborted, {bad_type, Tab5, {user_properties, ali}}},
+ mnesia:create_table(Tab5, [{user_properties, ali}])),
+ ?match({aborted, {bad_type, Tab5, {user_properties, [{}]}}},
+ mnesia:create_table(Tab5, [{user_properties, [{}]}])),
+ ?match({aborted, {bad_type, Tab5, {user_properties, [ali]}}},
+ mnesia:create_table(Tab5, [{user_properties, [ali]}])),
+
+ ?verify_mnesia(Nodes, []).
+
+
+unsupp_user_props(doc) ->
+ ["Simple test of adding user props in a schema_transaction"];
+unsupp_user_props(suite) -> [];
+unsupp_user_props(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab1 = silly1,
+ Tab2 = silly2,
+ Storage = mnesia_test_lib:storage_type(ram_copies, Config),
+
+ ?match({atomic, ok}, rpc:call(Node1, mnesia,
+ create_table, [Tab1, [{Storage, [Node1]}]])),
+ ?match({atomic, ok}, rpc:call(Node1, mnesia,
+ create_table, [Tab2, [{Storage, [Node1]}]])),
+
+ F1 = fun() ->
+ mnesia_schema:do_write_table_property(
+ silly1, {prop, propval1}),
+ mnesia_schema:do_write_table_property(
+ silly2, {prop, propval2}), % same key as above
+ mnesia_schema:do_write_table_property(
+ schema, {prop, propval3}) % same key as above
+ end,
+ ?match({atomic, ok}, rpc:call(Node1, mnesia_schema,
+ schema_transaction, [F1])),
+
+ ?match([{prop,propval1}], rpc:call(Node1, mnesia,
+ table_info, [silly1, user_properties])),
+ ?match([{prop,propval2}], rpc:call(Node1, mnesia,
+ table_info, [silly2, user_properties])),
+ ?match([{prop,propval3}], rpc:call(Node1, mnesia,
+ table_info, [schema, user_properties])),
+
+ F2 = fun() ->
+ mnesia_schema:do_write_table_property(
+ silly1, {prop, propval1a}),
+ mnesia_schema:do_write_table_property(
+ silly1, {prop, propval1b}) % same key as above
+ end,
+ ?match({atomic, ok}, rpc:call(Node1, mnesia_schema,
+ schema_transaction, [F2])),
+
+ ?match([{prop,propval1b}], rpc:call(Node1, mnesia,
+ table_info,
+ [silly1, user_properties])),
+ ?verify_mnesia([Node1], []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+snmp_access(doc) ->
+ ["Make Mnesia table accessible via SNMP"];
+
+snmp_access(suite) ->
+ [
+ snmp_open_table,
+ snmp_close_table,
+ snmp_get_next_index,
+ snmp_get_row,
+ snmp_get_mnesia_key,
+ snmp_update_counter,
+ snmp_order
+ ].
+
+snmp_open_table(suite) -> [];
+snmp_open_table(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab1 = local_snmp_table,
+
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ Def1 =
+ case mnesia_test_lib:diskless(Config) of
+ true -> [{ram_copies, Nodes}];
+ false ->
+ [{disc_copies, [Node1]}, {ram_copies, [Node2]}]
+ end,
+
+ Tab2 = ext_snmp_table,
+ Def2 = [{Storage, [Node2]}],
+ ErrTab = non_existing_tab,
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])),
+ ?match({aborted, _}, mnesia:snmp_open_table(ErrTab, [{key, integer}])),
+ ?verify_mnesia(Nodes, []).
+
+snmp_close_table(suite) -> [];
+snmp_close_table(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab1 = local_snmp_table,
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ Def1 =
+ case mnesia_test_lib:diskless(Config) of
+ true -> [{ram_copies, Nodes}];
+ false ->
+ [{disc_copies, [Node1]}, {ram_copies, [Node2]}]
+ end,
+
+ Tab2 = ext_snmp_table,
+ Def2 = [{snmp, [{key, integer}]}, {Storage, [Node2]}],
+ ErrTab = non_existing_tab,
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(no_snmp_tab, [])),
+ add_some_records(Tab1, Tab2, 3),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])),
+ add_some_records(Tab1, Tab2, 5),
+ ?match({atomic, ok}, mnesia:snmp_close_table(Tab1)),
+
+ Transform = fun(Tab, Key) ->
+ [{T,K,V}] = mnesia:read(Tab, Key, write),
+ mnesia:delete(T,K, write),
+ mnesia:write({T, {K,K}, V, 43+V})
+ end,
+
+ ?match({atomic, ok}, mnesia:transform_table(Tab1, ignore, [key,val,new])),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() ->
+ mnesia:write_lock_table(Tab1),
+ Keys = mnesia:select(Tab1, [{{'_','$1','_'}, [],
+ ['$1']}]),
+ [Transform(Tab1, Key) || Key <- Keys],
+ ok
+ end)),
+ ?match([{Tab1, {1, 1}, 1, 44}], mnesia:dirty_read(Tab1, {1, 1})),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key,{integer,integer}}])),
+
+ ?match({atomic, ok}, mnesia:snmp_close_table(Tab2)),
+ ?match({atomic, ok}, mnesia:transform_table(Tab2, ignore, [key,val,new])),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() ->
+ mnesia:write_lock_table(Tab2),
+ Keys = mnesia:select(Tab2, [{{'_','$1','_'}, [],
+ ['$1']}]),
+ [Transform(Tab2, Key) || Key <- Keys],
+ ok
+ end)),
+
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key,{integer,integer}}])),
+
+ %% Should be aborted ????
+ ?match({atomic, ok}, mnesia:snmp_close_table(no_snmp_tab)),
+ ?match({aborted, _}, mnesia:snmp_close_table(ErrTab)),
+ ?verify_mnesia(Nodes, []).
+
+snmp_get_next_index(suite) -> [];
+snmp_get_next_index(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab1 = local_snmp_table,
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ Def1 =
+ case mnesia_test_lib:diskless(Config) of
+ true -> [{ram_copies, Nodes}];
+ false ->
+ [{disc_copies, [Node1]}, {ram_copies, [Node2]}]
+ end,
+
+ Tab2 = ext_snmp_table,
+ Def2 = [{Storage, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])),
+ add_some_records(Tab1, Tab2, 1),
+ Test =
+ fun() ->
+ %% Test local tables
+ {success, Res11} = ?match({ok, _}, mnesia:snmp_get_next_index(Tab1,[])),
+ {ok, Index11} = Res11,
+ {success, _Res12} =
+ ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)),
+ ?match({'EXIT',_}, mnesia:snmp_get_next_index(Tab1, endOfTable)),
+
+ %% Test external table
+ {success, Res21} =
+ ?match({ok, _}, mnesia:snmp_get_next_index(Tab2, [])),
+ {ok, Index21} = Res21,
+ {success, _Res22} =
+ ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)),
+
+ {ok, Row} = mnesia:snmp_get_row(Tab1, Index11),
+ ?match(ok, mnesia:dirty_delete(Tab1, hd(Index11))),
+
+ ?match(endOfTable, mnesia:snmp_get_next_index(Tab1,[])),
+
+ ok = mnesia:dirty_write(Row), %% Reset to coming tests
+
+ %% Test of non existing table
+ %%?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])),
+ ok
+ end,
+ ?match(ok, Test()),
+ ?match({atomic,ok}, mnesia:transaction(Test)),
+ ?match(ok, mnesia:sync_dirty(Test)),
+ ?match(ok, mnesia:activity(transaction,Test,mnesia)),
+
+ %%io:format("**********Before ~p~n", [mnesia_lib:val({Tab1,snmp})]),
+ %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]),
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])),
+ %%io:format("**********After ~p~n", [mnesia_lib:val({Tab1,snmp})]),
+ %%io:format(" ~p ~n", [ets:tab2list(mnesia_lib:val({local_snmp_table,{index,snmp}}))]),
+
+ ?match(ok, Test()),
+ ?match({atomic,ok}, mnesia:transaction(Test)),
+ ?match(ok, mnesia:sync_dirty(Test)),
+ ?match(ok, mnesia:activity(transaction,Test,mnesia)),
+
+ ?verify_mnesia(Nodes, []).
+
+add_some_records(Tab1, Tab2, N) ->
+ Recs1 = [{Tab1, I, I} || I <- lists:reverse(lists:seq(1, N))],
+ Recs2 = [{Tab2, I, I} || I <- lists:reverse(lists:seq(20, 20+N-1))],
+ lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1),
+ Fun = fun(R) -> mnesia:write(R) end,
+ Trans = fun() -> lists:foreach(Fun, Recs2) end,
+ {atomic, ok} = mnesia:transaction(Trans),
+ %% Sync things, so everything gets everywhere!
+ {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs1)) end),
+ {atomic, ok} = mnesia:sync_transaction(fun() -> mnesia:write(lists:last(Recs2)) end),
+ ?sort(Recs1 ++ Recs2).
+
+snmp_get_row(suite) -> [];
+snmp_get_row(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab1 = local_snmp_table,
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ Def1 =
+ case mnesia_test_lib:diskless(Config) of
+ true -> [{ram_copies, Nodes}];
+ false ->
+ [{disc_copies, [Node1]}, {ram_copies, [Node2]}]
+ end,
+ Tab2 = ext_snmp_table,
+ Def2 = [{Storage, [Node2]}],
+ Tab3 = snmp_table,
+ Def3 = [{Storage, [Node1]},
+ {attributes, [key, data1, data2]}],
+
+ Setup = fun() ->
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])),
+ ?match({atomic, ok}, mnesia:snmp_open_table(
+ Tab3, [{key, {fix_string,integer}}])),
+ add_some_records(Tab1, Tab2, 1)
+ end,
+ Clear = fun() ->
+ ?match({atomic, ok}, mnesia:delete_table(Tab1)),
+ ?match({atomic, ok}, mnesia:delete_table(Tab2)),
+ ?match({atomic, ok}, mnesia:delete_table(Tab3))
+ end,
+ Test =
+ fun() ->
+ %% Test local tables
+ {success, Res11} =
+ ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])),
+ {ok, Index11} = Res11,
+ ?match({ok, {Tab1,1,1}}, mnesia:snmp_get_row(Tab1, Index11)),
+ ?match(endOfTable, mnesia:snmp_get_next_index(Tab1, Index11)),
+ ?match({'EXIT',_}, mnesia:snmp_get_row(Tab1, endOfTable)),
+ ?match(undefined, mnesia:snmp_get_row(Tab1, [73])),
+
+ Add = fun() -> mnesia:write({Tab3, {"f_string", 3}, data1, data2}) end,
+ ?match({atomic, ok}, mnesia:transaction(Add)),
+ {success, {ok, Index31}} = ?match({ok, RowIndex31} when is_list(RowIndex31),
+ mnesia:snmp_get_next_index(Tab3,[])),
+ ?match({ok, Row31} when is_tuple(Row31),
+ mnesia:snmp_get_row(Tab3, Index31)),
+ ?match(endOfTable, mnesia:snmp_get_next_index(Tab3, Index31)),
+ Del = fun() -> mnesia:delete({Tab3,{"f_string",3}}) end,
+ ?match({atomic, ok}, mnesia:transaction(Del)),
+ ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [3])),
+ ?match(undefined, mnesia:snmp_get_row(Tab3, "f_string" ++ [73])),
+
+ %% Test external table
+ {success, Res21} = ?match({ok,[20]}, mnesia:snmp_get_next_index(Tab2, [])),
+ {ok, Index21} = Res21,
+ ?match({ok, Row2} when is_tuple(Row2), mnesia:snmp_get_row(Tab2, Index21)),
+ ?match(endOfTable, mnesia:snmp_get_next_index(Tab2, Index21)),
+ %% Test of non existing table
+ %% ?match(endOfTable, mnesia:snmp_get_next_index(ErrTab, [])),
+ ok
+ end,
+ Setup(),
+ ?match(ok, Test()),
+ Clear(), Setup(),
+ ?match({atomic,ok}, mnesia:transaction(Test)),
+ Clear(), Setup(),
+ ?match(ok, mnesia:sync_dirty(Test)),
+ Clear(), Setup(),
+ ?match(ok, mnesia:activity(transaction,Test,mnesia)),
+
+ Clear(), Setup(),
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])),
+ ?match(ok, Test()),
+ Clear(), Setup(),
+ ?match([], mnesia_test_lib:stop_mnesia(Nodes)),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab1, Tab2])),
+ ?match({atomic,ok}, mnesia:transaction(Test)),
+
+ ?verify_mnesia(Nodes, []).
+
+snmp_get_mnesia_key(suite) -> [];
+snmp_get_mnesia_key(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab1 = local_snmp_table,
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ Def1 =
+ case mnesia_test_lib:diskless(Config) of
+ true -> [{ram_copies, Nodes}];
+ false ->
+ [{disc_copies, [Node1]}, {ram_copies, [Node2]}]
+ end,
+
+ Tab2 = ext_snmp_table,
+ Def2 = [{Storage, [Node2]}],
+
+ Tab3 = fix_string,
+ Setup = fun() ->
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def1)),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab1, [{key, integer}])),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab2, [{key, integer}])),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab3, [{key, {fix_string,integer}}])),
+
+ add_some_records(Tab1, Tab2, 1)
+ end,
+ Clear = fun() ->
+ ?match({atomic, ok}, mnesia:delete_table(Tab1)),
+ ?match({atomic, ok}, mnesia:delete_table(Tab2)),
+ ?match({atomic, ok}, mnesia:delete_table(Tab3))
+ end,
+ Test =
+ fun() ->
+ %% Test local tables
+ {success, Res11} =
+ ?match({ok, [1]}, mnesia:snmp_get_next_index(Tab1,[])),
+ {ok, Index11} = Res11,
+ ?match({ok, 1}, mnesia:snmp_get_mnesia_key(Tab1, Index11)),
+ %% Test external tables
+ {success, Res21} =
+ ?match({ok, [20]}, mnesia:snmp_get_next_index(Tab2, [])),
+ {ok, Index21} = Res21,
+ ?match({ok, 20}, mnesia:snmp_get_mnesia_key(Tab2, Index21)),
+ ?match(undefined, mnesia:snmp_get_mnesia_key(Tab2, [97])),
+ ?match({'EXIT', _}, mnesia:snmp_get_mnesia_key(Tab2, 97)),
+
+ ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,1}) end)),
+ ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, Index11)),
+
+ ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab1,73,7}) end)),
+ ?match({ok, 73}, mnesia:snmp_get_mnesia_key(Tab1, [73])),
+ ?match({atomic,ok}, mnesia:transaction(fun() -> mnesia:delete({Tab1,73}) end)),
+ ?match(undefined, mnesia:snmp_get_mnesia_key(Tab1, [73])),
+
+ ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:write({Tab3,{"S",5},7}) end)),
+ ?match({ok,{"S",5}}, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])),
+ ?match({atomic,ok},mnesia:transaction(fun() -> mnesia:delete({Tab3,{"S",5}}) end)),
+ ?match(undefined, mnesia:snmp_get_mnesia_key(Tab3, [$S,5])),
+
+ ok
+ end,
+ Setup(),
+ ?match(ok, Test()),
+ Clear(), Setup(),
+ ?match({atomic,ok}, mnesia:transaction(Test)),
+ Clear(), Setup(),
+ ?match(ok, mnesia:sync_dirty(Test)),
+ Clear(), Setup(),
+ ?match(ok, mnesia:activity(transaction,Test,mnesia)),
+ ?verify_mnesia(Nodes, []).
+
+snmp_update_counter(doc) ->
+ ["Verify that counters may be updated for tables with SNMP property"];
+snmp_update_counter(suite) -> [];
+snmp_update_counter(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = snmp_update_counter,
+ Def = [{attributes, [key, value]},
+ {snmp, [{key, integer}]},
+ {ram_copies, [Node1]}
+ ],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ Oid = {Tab, 1},
+ ?match([], mnesia:dirty_read(Oid)),
+ ?match(ok, mnesia:dirty_write({Tab, 1, 1})),
+ ?match([{Tab, _Key, 1}], mnesia:dirty_read(Oid)),
+ ?match(3, mnesia:dirty_update_counter(Oid, 2)),
+ ?match([{Tab, _Key, 3}], mnesia:dirty_read(Oid)),
+ ?verify_mnesia(Nodes, []).
+
+snmp_order(doc) ->
+ ["Verify that sort order is correct in transactions and dirty variants"];
+snmp_order(suite) -> [];
+snmp_order(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = snmp_order,
+ Def = [{attributes, [key, value]},
+ {snmp, [{key, {integer, integer, integer}}]},
+ {ram_copies, [Node1]}
+ ],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ Oid = {Tab, 1},
+ ?match([], mnesia:dirty_read(Oid)),
+ ?match({'EXIT', {aborted, _}}, mnesia:dirty_write({Tab, 1, 1})),
+ [mnesia:dirty_write({Tab, {A,B,2}, default}) ||
+ A <- lists:seq(1, 9, 2),
+ B <- lists:seq(2, 8, 2)],
+
+ Test1 = fun() ->
+ Keys0 = get_keys(Tab, []),
+ ?match(Keys0, lists:sort(Keys0)),
+ ?match([[1,2,2]|_], Keys0),
+ Keys1 = get_keys(Tab, [5]),
+ ?match(Keys1, lists:sort(Keys1)),
+ ?match([[5,2,2]|_], Keys1),
+ Keys2 = get_keys(Tab, [7, 4]),
+ ?match(Keys2, lists:sort(Keys2)),
+ ?match([[7,4,2]|_], Keys2),
+ ok
+ end,
+ ?match(ok, Test1()),
+ ?match({atomic, ok},mnesia:transaction(Test1)),
+ ?match(ok,mnesia:sync_dirty(Test1)),
+
+ Test2 = fun() ->
+ mnesia:write(Tab, {Tab,{0,0,2},updated}, write),
+ mnesia:write(Tab, {Tab,{5,3,2},updated}, write),
+ mnesia:write(Tab, {Tab,{10,10,2},updated}, write),
+ Keys0 = get_keys(Tab, []),
+ ?match([[0,0,2],[1,2,2]|_], Keys0),
+ ?match(Keys0, lists:sort(Keys0)),
+
+ Keys1 = get_keys(Tab, [5]),
+ ?match([[5,2,2],[5,3,2]|_], Keys1),
+ ?match(Keys1, lists:sort(Keys1)),
+
+ Keys2 = get_keys(Tab, [7,4]),
+ ?match([[7,4,2]|_], Keys2),
+ ?match(Keys2, lists:sort(Keys2)),
+ ?match([10,10,2], lists:last(Keys0)),
+ ?match([10,10,2], lists:last(Keys1)),
+ ?match([10,10,2], lists:last(Keys2)),
+
+ ?match([[10,10,2]], get_keys(Tab, [10])),
+ ok
+ end,
+
+ ?match({atomic, ok},mnesia:transaction(Test2)),
+
+ ?verify_mnesia(Nodes, []).
+
+get_keys(Tab, Key) ->
+ case mnesia:snmp_get_next_index(Tab, Key) of
+ endOfTable -> [];
+ {ok, Next} ->
+ [Next|get_keys(Tab, Next)]
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(tab, {i, e1, e2}). % Simple test table
+
+subscriptions(doc) ->
+ ["Test the event subscription mechanism"];
+subscriptions(suite) ->
+ [subscribe_standard,
+ subscribe_extended].
+
+subscribe_extended(doc) ->
+ ["Test the extended set of events, test with and without checkpoints. "];
+subscribe_extended(suite) ->
+ [];
+subscribe_extended(Config) when is_list(Config) ->
+ [N1, N2]=Nodes=?acquire_nodes(2, Config),
+ Tab1 = etab,
+ Storage = mnesia_test_lib:storage_type(ram_copies, Config),
+ Def1 = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}],
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+
+ Tab2 = bag,
+ Def2 = [{Storage, [N1, N2]},
+ {type, bag},
+ {record_name, Tab1},
+ {attributes, record_info(fields, tab)}],
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+
+ ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})),
+ ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})),
+
+ ?match({error, {already_exists, _}}, mnesia:subscribe({table, Tab1, simple})),
+ ?match({error, {badarg, {table, Tab1, bad}}}, mnesia:subscribe({table, Tab1, bad})),
+
+ ?match({ok, N1}, mnesia:subscribe(activity)),
+ test_ext_sub(Tab1, Tab2),
+
+ ?match({ok, N1}, mnesia:unsubscribe(activity)),
+ ?match({ok, N1}, mnesia:subscribe({table, Tab1, detailed})),
+ ?match({atomic, ok}, mnesia:clear_table(Tab1)),
+ ?match({mnesia_table_event, {delete, schema, {schema, Tab1}, [{schema, Tab1, _}],_}}, recv_event()),
+ ?match({mnesia_table_event, {write, schema, {schema, Tab1, _}, [], _}}, recv_event()),
+
+ ?match({atomic, ok}, mnesia_schema:clear_table(Tab2)),
+ ?match({mnesia_table_event, {delete, schema, {schema, Tab2}, [{schema, Tab2, _}],_}},
+ recv_event()),
+ ?match({mnesia_table_event, {write, schema, {schema, Tab2, _}, [], _}}, recv_event()),
+
+ ?match({ok, N1}, mnesia:unsubscribe({table, Tab2, detailed})),
+ {ok, _, _} = mnesia:activate_checkpoint([{name, testing},
+ {ram_overrides_dump, true},
+ {max, [Tab1, Tab2]}]),
+ ?match({ok, N1}, mnesia:subscribe({table, Tab2, detailed})),
+ ?match({ok, N1}, mnesia:subscribe(activity)),
+ test_ext_sub(Tab1, Tab2),
+
+ ?verify_mnesia(Nodes, []).
+
+test_ext_sub(Tab1, Tab2) ->
+ %% The basics
+ Rec1 = {Tab1, 1, 0, 0},
+ Rec2 = {Tab1, 1, 1, 0},
+ Rec3 = {Tab1, 2, 1, 0},
+ Rec4 = {Tab1, 2, 2, 0},
+
+ Write = fun(Tab, Rec) ->
+ mnesia:transaction(fun() -> mnesia:write(Tab, Rec, write)
+ end)
+ end,
+ Delete = fun(Tab, Rec) ->
+ mnesia:transaction(fun() -> mnesia:delete(Tab, Rec, write)
+ end)
+ end,
+ DelObj = fun(Tab, Rec) ->
+ mnesia:transaction(fun() -> mnesia:delete_object(Tab, Rec, write)
+ end)
+ end,
+
+ S = self(),
+ D = {dirty, self()},
+ %% SET
+ ?match(ok, mnesia:dirty_write(Rec1)),
+ ?match({mnesia_table_event, {write, Tab1, Rec1, [], D}}, recv_event()),
+ ?match(ok, mnesia:dirty_write(Rec3)),
+ ?match({mnesia_table_event, {write, Tab1, Rec3, [], D}}, recv_event()),
+ ?match(ok, mnesia:dirty_write(Rec1)),
+ ?match({mnesia_table_event, {write, Tab1, Rec1, [Rec1], D}}, recv_event()),
+ ?match({atomic, ok}, Write(Tab1, Rec2)),
+ ?match({mnesia_table_event, {write, Tab1, Rec2, [Rec1], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match(ok, mnesia:dirty_delete({Tab1, 2})),
+ ?match({mnesia_table_event, {delete, Tab1, {Tab1, 2}, [Rec3], D}}, recv_event()),
+ ?match({atomic, ok}, DelObj(Tab1, Rec2)),
+ ?match({mnesia_table_event, {delete, Tab1, Rec2, [Rec2], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+
+ ?match({atomic, ok}, Delete(Tab1, 1)),
+ ?match({mnesia_table_event, {delete, Tab1, {Tab1, 1}, [], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+
+ ?match({ok, _N1}, mnesia:unsubscribe({table, Tab1, detailed})),
+
+ %% BAG
+
+ ?match({atomic, ok}, Write(Tab2, Rec1)),
+ ?match({mnesia_table_event, {write, Tab2, Rec1, [], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match({atomic, ok}, Write(Tab2, Rec4)),
+ ?match({mnesia_table_event, {write, Tab2, Rec4, [], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match({atomic, ok}, Write(Tab2, Rec3)),
+ ?match({mnesia_table_event, {write, Tab2, Rec3, [Rec4], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match({atomic, ok}, Write(Tab2, Rec2)),
+ ?match({mnesia_table_event, {write, Tab2, Rec2, [Rec1], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match({atomic, ok}, Write(Tab2, Rec1)),
+ ?match({mnesia_table_event, {write, Tab2, Rec1, [Rec1, Rec2], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match({atomic, ok}, DelObj(Tab2, Rec2)),
+ ?match({mnesia_table_event, {delete, Tab2, Rec2, [Rec2], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match({atomic, ok}, Delete(Tab2, 1)),
+ ?match({mnesia_table_event, {delete, Tab2, {Tab2, 1}, [Rec1], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ?match({atomic, ok}, Delete(Tab2, 2)),
+ ?match({mnesia_table_event, {delete, Tab2, {Tab2, 2}, [Rec4, Rec3], {tid,_,S}}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid,_,S}}}, recv_event()),
+ ok.
+
+
+subscribe_standard(doc) ->
+ ["Tests system events and the orignal table events"];
+subscribe_standard(suite) -> [];
+subscribe_standard(Config) when is_list(Config)->
+ [N1, N2]=?acquire_nodes(2, Config),
+ Tab = tab,
+
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ Def = [{Storage, [N1, N2]}, {attributes, record_info(fields, tab)}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+
+ %% Check system events
+ ?match({ok, N1}, mnesia:subscribe(system)),
+ ?match({ok, N1}, mnesia:subscribe(activity)),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ ?match({mnesia_system_event, {mnesia_down, N2}}, recv_event()),
+ ?match(timeout, recv_event()),
+
+ ?match([], mnesia_test_lib:start_mnesia([N2], [Tab])),
+ ?match({mnesia_activity_event, _}, recv_event()),
+ ?match({mnesia_system_event,{mnesia_up, N2}}, recv_event()),
+
+ ?match(true, lists:member(self(), mnesia:system_info(subscribers))),
+ ?match([], mnesia_test_lib:kill_mnesia([N1])),
+ timer:sleep(500),
+ mnesia_test_lib:flush(),
+ ?match([], mnesia_test_lib:start_mnesia([N1], [Tab])),
+ ?match(timeout, recv_event()),
+
+ ?match({ok, N1}, mnesia:subscribe(system)),
+ ?match({error, {already_exists, system}}, mnesia:subscribe(system)),
+ ?match(stopped, mnesia:stop()),
+ ?match({mnesia_system_event, {mnesia_down, N1}}, recv_event()),
+ ?match({error, {node_not_running, N1}}, mnesia:subscribe(system)),
+ ?match([], mnesia_test_lib:start_mnesia([N1, N2], [Tab])),
+
+ %% Check table events
+ ?match({ok, N1}, mnesia:subscribe(activity)),
+ Old_Level = mnesia:set_debug_level(trace),
+ ?match({ok, N1}, mnesia:subscribe({table,Tab})),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(#tab{i=155}) end)),
+ Self = self(),
+ ?match({mnesia_table_event, {write, _, _}}, recv_event()),
+ ?match({mnesia_activity_event, {complete, {tid, _, Self}}}, recv_event()),
+
+ ?match({ok, N1}, mnesia:unsubscribe({table,Tab})),
+ ?match({ok, N1}, mnesia:unsubscribe(activity)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(#tab{i=255}) end)),
+
+ ?match(timeout, recv_event()),
+ mnesia:set_debug_level(Old_Level),
+
+ %% Check deletion of replica
+
+ ?match({ok, N1}, mnesia:subscribe({table,Tab})),
+ ?match({ok, N1}, mnesia:subscribe(activity)),
+ ?match(ok, mnesia:dirty_write(#tab{i=355})),
+ ?match({mnesia_table_event, {write, _, _}}, recv_event()),
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)),
+ ?match({mnesia_activity_event, _}, recv_event()),
+ ?match(ok, mnesia:dirty_write(#tab{i=455})),
+ ?match(timeout, recv_event()),
+
+ ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)),
+ ?match({mnesia_activity_event, _}, recv_event()),
+ ?match({ok, N1}, mnesia:subscribe({table,Tab})),
+ ?match(ok, mnesia:dirty_write(#tab{i=555})),
+ ?match({mnesia_table_event, {write, _, _}}, recv_event()),
+ ?match({atomic, ok}, mnesia:move_table_copy(Tab, N1, N2)),
+ ?match({mnesia_activity_event, _}, recv_event()),
+ ?match(ok, mnesia:dirty_write(#tab{i=655})),
+ ?match(timeout, recv_event()),
+
+ ?match({atomic, ok}, mnesia:add_table_copy(Tab, N1, ram_copies)),
+ ?match({mnesia_activity_event, _}, recv_event()),
+ ?match({ok, N1}, mnesia:subscribe({table,Tab})),
+ ?match({error, {already_exists, {table,Tab, simple}}},
+ mnesia:subscribe({table,Tab})),
+ ?match(ok, mnesia:dirty_write(#tab{i=755})),
+ ?match({mnesia_table_event, {write, _, _}}, recv_event()),
+
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+ ?match({mnesia_activity_event, _}, recv_event()),
+ ?match(timeout, recv_event()),
+
+ mnesia_test_lib:kill_mnesia([N1]),
+
+ ?verify_mnesia([N2], [N1]).
+
+recv_event() ->
+ receive
+ Event -> Event
+ after 1000 ->
+ timeout
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+iteration(doc) ->
+ ["Verify that the iteration functions works as expected"];
+iteration(suite) ->
+ [foldl].
+
+
+foldl(suite) ->
+ [];
+foldl(doc) ->
+ [""];
+foldl(Config) when is_list(Config) ->
+ Nodes = [_N1, N2] = ?acquire_nodes(2, Config),
+ Tab1 = fold_local,
+ Tab2 = fold_remote,
+ Tab3 = fold_ordered,
+ ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes},
+ {type, ordered_set}])),
+
+ Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)],
+ Tab2Els = ?sort([{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]]),
+ Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)],
+
+ [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab1Els],
+ [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab2Els],
+ [mnesia:sync_transaction(fun() -> mnesia:write(E) end) || E <- Tab3Els],
+
+ Fold = fun(Tab) ->
+ lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab))
+ end,
+ Fold2 = fun(Tab, Lock) ->
+ lists:reverse(mnesia:foldl(fun(E, A) -> [E | A] end, [], Tab, Lock))
+ end,
+ Exit = fun(Tab) ->
+ lists:reverse(mnesia:foldl(fun(_E, _A) -> exit(testing) end, [], Tab))
+ end,
+ %% Errors
+ ?match({aborted, _}, mnesia:transaction(Fold, [error])),
+ ?match({aborted, _}, mnesia:transaction(fun(Tab) -> mnesia:foldl(badfun,[],Tab) end,
+ [Tab1])),
+ ?match({aborted, testing}, mnesia:transaction(Exit, [Tab1])),
+ ?match({aborted, _}, mnesia:transaction(Fold2, [Tab1, read_lock])),
+
+ %% Success
+ ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold, [Tab1]))),
+ ?match({atomic, Tab2Els}, sort_res(mnesia:transaction(Fold, [Tab2]))),
+ ?match({atomic, Tab3Els}, mnesia:transaction(Fold, [Tab3])),
+
+ ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, read]))),
+ ?match({atomic, Tab1Els}, sort_res(mnesia:transaction(Fold2, [Tab1, write]))),
+
+ ?match(Tab1Els, sort_res(mnesia:sync_dirty(Fold, [Tab1]))),
+ ?match(Tab2Els, sort_res(mnesia:async_dirty(Fold, [Tab2]))),
+
+ ?verify_mnesia(Nodes, []).
+
+sort_res({atomic, List}) ->
+ {atomic, ?sort(List)};
+sort_res(Else) when is_list(Else) ->
+ ?sort(Else);
+sort_res(Else) ->
+ Else.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+debug_support(doc) ->
+ ["Check that the debug support has not decayed."];
+debug_support(suite) ->
+ [
+ info,
+ schema_0,
+ schema_1,
+ view_0,
+ view_1,
+ view_2,
+ lkill,
+ kill
+ ].
+
+info(suite) -> [];
+info(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ ?match(ok, mnesia:info()),
+ ?verify_mnesia(Nodes, []).
+
+schema_0(suite) -> [];
+schema_0(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ ?match(ok, mnesia:schema()),
+ ?verify_mnesia(Nodes, []).
+
+schema_1(suite) -> [];
+schema_1(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ Tab = schema_1,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [])),
+ ?match(ok, mnesia:schema(Tab)),
+ ?verify_mnesia(Nodes, []).
+
+view_0(suite) -> [];
+view_0(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ ?match(ok, mnesia_lib:view()),
+ ?verify_mnesia(Nodes, []).
+
+view_1(suite) -> [];
+view_1(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ BinCore = mnesia_lib:mkcore({crashinfo, "Just testing..."}),
+ CoreFile = lists:concat(["MnesiaCore.", node(), ".view_1.", ?MODULE]),
+ ?match(ok, file:write_file(CoreFile, BinCore)),
+ ?match(ok, mnesia_lib:view(CoreFile)),
+ ?match(ok, file:delete(CoreFile)),
+
+ ?match(stopped, mnesia:stop()),
+ Dir = mnesia:system_info(directory),
+ ?match(eof, mnesia_lib:view(filename:join(Dir, "LATEST.LOG"))),
+ ?match(ok, mnesia_lib:view(filename:join(Dir, "schema.DAT"))),
+ ?verify_mnesia([], Nodes).
+
+view_2(suite) -> [];
+view_2(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ BinCore = mnesia_lib:mkcore({crashinfo, "More testing..."}),
+ File = lists:concat([?MODULE, "view_2.", node()]),
+ ?match(ok, file:write_file(File, BinCore)),
+ ?match(ok, mnesia_lib:view(File, core)),
+ ?match(ok, file:delete(File)),
+
+ ?match(stopped, mnesia:stop()),
+ Dir = mnesia:system_info(directory),
+ ?match(ok, file:rename(filename:join(Dir, "LATEST.LOG"), File)),
+ ?match(eof, mnesia_lib:view(File, log)),
+ ?match(ok, file:delete(File)),
+
+ ?match(ok, file:rename(filename:join(Dir, "schema.DAT"), File)),
+ ?match(ok, mnesia_lib:view(File, dat)),
+ ?match(ok, file:delete(File)),
+ ?verify_mnesia([], Nodes).
+
+lkill(suite) -> [];
+lkill(Config) when is_list(Config) ->
+ [Node1, Node2] = ?acquire_nodes(2, Config),
+
+ ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])),
+ ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])),
+ ?match(ok, rpc:call(Node2, mnesia, lkill, [])),
+ ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])),
+ ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])),
+ ?verify_mnesia([Node1], [Node2]).
+
+kill(suite) -> [];
+kill(Config) when is_list(Config) ->
+ [Node1, Node2] = ?acquire_nodes(2, Config),
+
+ ?match(yes, rpc:call(Node1, mnesia, system_info, [is_running])),
+ ?match(yes, rpc:call(Node2, mnesia, system_info, [is_running])),
+ ?match({_, []}, rpc:call(Node2, mnesia, kill, [])),
+ ?match(no, rpc:call(Node1, mnesia, system_info, [is_running])),
+ ?match(no, rpc:call(Node2, mnesia, system_info, [is_running])),
+ ?verify_mnesia([], [Node1, Node2]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+record_name(doc) ->
+ ["Verify that record names may be differ from the name of ",
+ "the hosting table. Check at least access, restore, "
+ "registry, subscriptions and traveres_backup"];
+record_name(suite) ->
+ [
+ record_name_dirty_access
+ ].
+
+record_name_dirty_access(suite) ->
+ [
+ record_name_dirty_access_ram,
+ record_name_dirty_access_disc,
+ record_name_dirty_access_disc_only
+ ].
+
+record_name_dirty_access_ram(suite) ->
+ [];
+record_name_dirty_access_ram(Config) when is_list(Config) ->
+ record_name_dirty_access(ram_copies, Config).
+
+record_name_dirty_access_disc(suite) ->
+ [];
+record_name_dirty_access_disc(Config) when is_list(Config) ->
+ record_name_dirty_access(disc_copies, Config).
+
+record_name_dirty_access_disc_only(suite) ->
+ [];
+record_name_dirty_access_disc_only(Config) when is_list(Config) ->
+ record_name_dirty_access(disc_only_copies, Config).
+
+record_name_dirty_access(Storage, Config) ->
+ [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config),
+
+ List = lists:concat([record_name_dirty_access_, Storage]),
+ Tab = list_to_atom(List),
+ RecName = some_record,
+ Attr = val,
+ TabDef = [{type, bag},
+ {record_name, RecName},
+ {index, [Attr]},
+ {Storage, Nodes}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, TabDef)),
+
+ ?match(RecName, mnesia:table_info(Tab, record_name)),
+
+ ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 20})),
+ ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 21})),
+ ?match(ok, mnesia:dirty_write(Tab, {RecName, 2, 22})),
+
+ %% Backup test
+ BupFile = List ++ ".BUP",
+ CpName = cpname,
+ CpArgs = [{name, CpName}, {min, [Tab]}, {ram_overrides_dump, true}],
+ ?match({ok, CpName, _}, mnesia:activate_checkpoint(CpArgs)),
+ ?match(ok, mnesia:backup_checkpoint(CpName, BupFile)),
+ ?match(ok, mnesia:deactivate_checkpoint(CpName)),
+
+ ?match(ok, mnesia:dirty_write(Tab, {RecName, 1, 10})),
+ ?match({ok, Node1}, mnesia:subscribe({table, Tab})),
+ ?match(ok, mnesia:dirty_write(Tab, {RecName, 3, 10})),
+
+ Twos =?sort( [{RecName, 2, 20}, {RecName, 2, 21}, {RecName, 2, 22}]),
+ ?match(Twos, ?sort(mnesia:dirty_read(Tab, 2))),
+
+ ?match(ok, mnesia:dirty_delete_object(Tab, {RecName, 2, 21})),
+
+ Tens = ?sort([{RecName, 1, 10}, {RecName, 3, 10}]),
+ TenPat = {RecName, '_', 10},
+ ?match(Tens, ?sort(mnesia:dirty_match_object(Tab, TenPat))),
+ ?match(Tens, ?sort(mnesia:dirty_select(Tab, [{TenPat, [], ['$_']}]))),
+
+ %% Subscription test
+ E = mnesia_table_event,
+ ?match_receive({E, {write, {Tab, 3, 10}, _}}),
+ ?match_receive({E, {delete_object, {Tab, 2, 21}, _}}),
+ ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})),
+
+ ?match([], mnesia_test_lib:stop_mnesia([Node1])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [Tab])),
+
+ ?match(Tens, ?sort(mnesia:dirty_index_match_object(Tab, TenPat, Attr) )),
+ ?match(Tens, ?sort(mnesia:dirty_index_read(Tab, 10, Attr))),
+
+ ?match([1, 2, 3], ?sort(mnesia:dirty_all_keys(Tab))),
+
+ ?match({ok, Node1}, mnesia:subscribe({table, Tab})),
+ ?match(ok, mnesia:dirty_delete(Tab, 2)),
+ ?match([], mnesia:dirty_read(Tab, 2)),
+
+ ?match_receive({E, {delete, {Tab, 2}, _}}),
+ ?match([], mnesia_test_lib:flush()),
+ ?match({ok, Node1}, mnesia:unsubscribe({table, Tab})),
+
+ %% Restore test
+ ?match({atomic, [Tab]}, mnesia:restore(BupFile, [{recreate_tables, [Tab]}])),
+ ?match(RecName, mnesia:table_info(Tab, record_name)),
+
+ ?match(Twos, ?sort(mnesia:dirty_match_object(Tab, mnesia:table_info(Tab, wild_pattern)))),
+ ?match(Twos, ?sort(mnesia:dirty_select(Tab,
+ [{mnesia:table_info(Tab, wild_pattern),
+ [],['$_']}]))),
+
+ %% Traverse backup test
+
+ Fun = fun(Rec, {Good, Bad}) ->
+ ?verbose("BUP: ~p~n", [Rec]),
+ case Rec of
+ {T, K, V} when T == Tab ->
+ Good2 = Good ++ [{RecName, K, V}],
+ {[Rec], {?sort(Good2), Bad}};
+ {T, K} when T == Tab ->
+ Good2 = [G || G <- Good, element(2, G) /= K],
+ {[Rec], {?sort(Good2), Bad}};
+ _ when element(1, Rec) == schema ->
+ {[Rec], {Good, Bad}};
+ _ ->
+ Bad2 = Bad ++ [Rec],
+ {[Rec], {Good, ?sort(Bad2)}}
+ end
+ end,
+
+ ?match({ok, {Twos, []}}, mnesia:traverse_backup(BupFile, mnesia_backup,
+ dummy, read_only,
+ Fun, {[], []})),
+ ?match(ok, file:delete(BupFile)),
+
+ %% Update counter test
+
+ CounterTab = list_to_atom(lists:concat([Tab, "_counter"])),
+ CounterTabDef = [{record_name, some_counter}],
+ C = my_counter,
+ ?match({atomic, ok}, mnesia:create_table(CounterTab, CounterTabDef)),
+ ?match(some_counter, mnesia:table_info(CounterTab, record_name)),
+ ?match(0, mnesia:dirty_update_counter(CounterTab, gurka, -10)),
+ ?match(10, mnesia:dirty_update_counter(CounterTab, C, 10)),
+ ?match(11, mnesia:dirty_update_counter(CounterTab, C, 1)),
+ ?match(4711, mnesia:dirty_update_counter(CounterTab, C, 4700)),
+ ?match([{some_counter, C, 4711}], mnesia:dirty_read(CounterTab, C)),
+ ?match(0, mnesia:dirty_update_counter(CounterTab, C, -4747)),
+
+ %% Registry tests
+
+ RegTab = list_to_atom(lists:concat([Tab, "_registry"])),
+ RegTabDef = [{record_name, some_reg}],
+ ?match(ok, mnesia_registry:create_table(RegTab, RegTabDef)),
+ ?match(some_reg, mnesia:table_info(RegTab, record_name)),
+ {success, RegRecs} =
+ ?match([_ | _], mnesia_registry_test:dump_registry(node(), RegTab)),
+
+ R = ?sort(RegRecs),
+ ?match(R, ?sort(mnesia_registry_test:restore_registry(node(), RegTab))),
+
+ ?verify_mnesia(Nodes, []).
+
+sorted_ets(suite) ->
+ [];
+sorted_ets(Config) when is_list(Config) ->
+ [N1, N2, N3] = All = ?acquire_nodes(3, Config),
+
+ Tab = sorted_tab,
+ Def = case mnesia_test_lib:diskless(Config) of
+ true -> [{name, Tab}, {type, ordered_set}, {ram_copies, All}];
+ false -> [{name, Tab}, {type, ordered_set},
+ {ram_copies, [N1]},
+ {disc_copies,[N2, N3]}]
+ end,
+
+ ?match({atomic, ok}, mnesia:create_table(Def)),
+ ?match({aborted, _}, mnesia:create_table(fel, [{disc_only_copies, N1}])),
+
+ ?match([ok | _],
+ [mnesia:dirty_write({Tab, {dirty, N}, N}) || N <- lists:seq(1, 10)]),
+ ?match({atomic, _},
+ mnesia:sync_transaction(fun() ->
+ [mnesia:write({Tab, {trans, N}, N}) ||
+ N <- lists:seq(1, 10)]
+ end)),
+
+ List = mnesia:dirty_match_object({Tab, '_', '_'}),
+ ?match(List, ?sort(List)),
+ ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])),
+ ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])),
+
+ mnesia_test_lib:stop_mnesia(All),
+ mnesia_test_lib:start_mnesia(All, [sorted_tab]),
+
+ List = mnesia:dirty_match_object({Tab, '_', '_'}),
+ ?match(List, ?sort(List)),
+ ?match(List, rpc:call(N2, mnesia, dirty_match_object, [{Tab, '_', '_'}])),
+ ?match(List, rpc:call(N3, mnesia, dirty_match_object, [{Tab, '_', '_'}])),
+
+ ?match(List, rpc:call(N3, mnesia, dirty_select, [Tab, [{{Tab, '_', '_'},[],['$_']}]])),
+
+ TransMatch = fun() ->
+ mnesia:write({Tab, {trans, 0}, 0}),
+ mnesia:write({Tab, {trans, 11}, 11}),
+ mnesia:match_object({Tab, '_', '_'})
+ end,
+ TransSelect = fun() ->
+ mnesia:write({Tab, {trans, 0}, 0}),
+ mnesia:write({Tab, {trans, 11}, 11}),
+ mnesia:select(Tab, [{{Tab, '_', '_'},[],['$_']}])
+ end,
+
+ TList = mnesia:transaction(TransMatch),
+ STList = ?sort(TList),
+ ?match(STList, TList),
+ ?match(STList, rpc:call(N2, mnesia, transaction, [TransMatch])),
+ ?match(STList, rpc:call(N3, mnesia, transaction, [TransMatch])),
+
+ TSel = mnesia:transaction(TransSelect),
+ ?match(STList, TSel),
+ ?match(STList, rpc:call(N2, mnesia, transaction, [TransSelect])),
+ ?match(STList, rpc:call(N3, mnesia, transaction, [TransSelect])),
+
+ ?match({atomic, ok}, mnesia:create_table(rec, [{type, ordered_set}])),
+ [ok = mnesia:dirty_write(R) || R <- [{rec,1,1}, {rec,2,1}]],
+ ?match({atomic, ok}, mnesia:add_table_index(rec, 3)),
+ TestIt = fun() ->
+ ok = mnesia:write({rec,1,1}),
+ mnesia:index_read(rec, 1, 3)
+ end,
+ ?match({atomic, [{rec,1,1}, {rec,2,1}]}, mnesia:transaction(TestIt)).
+
+
diff --git a/lib/mnesia/test/mnesia_examples_test.erl b/lib/mnesia/test/mnesia_examples_test.erl
new file mode 100644
index 0000000000..d1b1409c9d
--- /dev/null
+++ b/lib/mnesia/test/mnesia_examples_test.erl
@@ -0,0 +1,160 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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(mnesia_examples_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).
+
+-define(init(N, Config),
+ mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema],
+ N, Config, ?FILE, ?LINE)).
+
+opt_net_load(ExampleMod) ->
+ opt_net_load([node() | nodes()], ExampleMod, ok).
+
+opt_net_load([Node | Nodes], ExampleMod, Res) ->
+ case rpc:call(Node, ?MODULE, opt_load, [ExampleMod]) of
+ {module, ExampleMod} ->
+ opt_net_load(Nodes, ExampleMod, Res);
+ {error, Reason} ->
+ Error = {opt_net_load, ExampleMod, Node, Reason},
+ opt_net_load(Nodes, ExampleMod, {error, Error});
+ {badrpc, Reason} ->
+ Error = {opt_net_load, ExampleMod, Node, Reason},
+ opt_net_load(Nodes, ExampleMod, {error, Error})
+ end;
+opt_net_load([], _ExampleMod, Res) ->
+ Res.
+
+opt_load(Mod) ->
+ case code:is_loaded(Mod) of
+ {file, _} ->
+ {module, Mod};
+ false ->
+ Abs = filename:join([code:lib_dir(mnesia), examples, Mod]),
+ code:load_abs(Abs)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+all(doc) ->
+ ["Run all examples mentioned in the documentation",
+ "Are really all examples covered?"];
+all(suite) ->
+ [
+ bup,
+ company,
+ meter,
+ tpcb
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+bup(doc) -> ["Run the backup examples in bup.erl"];
+bup(suite) -> [];
+bup(Config) when is_list(Config) ->
+ Nodes = ?init(3, Config),
+ opt_net_load(bup),
+ ?match(ok, bup:test(Nodes)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+company(doc) ->
+ ["Run the company examples in company.erl and company_o.erl"].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+tpcb(doc) ->
+ ["Run the sample configurations of the stress tests in mnesia_tpcb.erl"];
+tpcb(suite) ->
+ [
+ replica_test,
+ sticky_replica_test,
+ dist_test,
+ conflict_test,
+ frag_test,
+ frag2_test,
+ remote_test,
+ remote_frag2_test
+ ].
+
+replica_test(suite) -> [];
+replica_test(Config) when is_list(Config) ->
+ ?init(3, Config),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(replica_test, ram_copies))).
+
+sticky_replica_test(suite) -> [];
+sticky_replica_test(Config) when is_list(Config) ->
+ ?init(3, Config),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(sticky_replica_test, ram_copies))).
+
+dist_test(suite) -> [];
+dist_test(Config) when is_list(Config) ->
+ ?init(3, [{tc_timeout, timer:minutes(10)} | Config]),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(dist_test, ram_copies))).
+
+conflict_test(suite) -> [];
+conflict_test(Config) when is_list(Config) ->
+ ?init(3, Config),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(conflict_test, ram_copies))).
+
+frag_test(suite) -> [];
+frag_test(Config) when is_list(Config) ->
+ ?init(3, Config),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag_test, ram_copies))).
+
+frag2_test(suite) -> [];
+frag2_test(Config) when is_list(Config) ->
+ ?init(3, Config),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(frag2_test, ram_copies))).
+
+remote_test(suite) -> [];
+remote_test(Config) when is_list(Config) ->
+ ?init(3, Config),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_test, ram_copies))).
+
+remote_frag2_test(suite) -> [];
+remote_frag2_test(Config) when is_list(Config) ->
+ ?init(3, Config),
+ opt_net_load(mnesia_tpcb),
+ ?match({ok, _}, mnesia_tpcb:start(mnesia_tpcb:config(remote_frag2_test, ram_copies))).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+meter(doc) ->
+ ["Run the meter example in mnesia_meter.erl"];
+meter(suite) ->
+ [];
+meter(Config) when is_list(Config) ->
+ [N | _] = ?init(3, Config),
+ opt_net_load(mnesia_meter),
+ ?match(ok, mnesia_meter:go(ram_copies, [N])).
+
+
diff --git a/lib/mnesia/test/mnesia_frag_test.erl b/lib/mnesia/test/mnesia_frag_test.erl
new file mode 100644
index 0000000000..4add340254
--- /dev/null
+++ b/lib/mnesia/test/mnesia_frag_test.erl
@@ -0,0 +1,875 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-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(mnesia_frag_test).
+-author('[email protected]').
+-include("mnesia_test_lib.hrl").
+
+-compile([export_all]).
+
+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).
+
+-define(match_dist(ExpectedRes, Expr),
+ case ?match(ExpectedRes, Expr) of
+
+ mnesia_test_lib:error(Format, Args,?FILE,?LINE)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(doc) ->
+ ["Verify the functionality of fragmented tables"];
+all(suite) ->
+ [
+ light,
+ medium
+ ].
+
+light(suite) ->
+ [
+ nice,
+ evil
+ ].
+
+medium(suite) ->
+ [
+ consistency
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+nice(suite) ->
+ [
+ nice_single,
+ nice_multi,
+ nice_access,
+ iter_access
+ ].
+
+nice_single(suite) -> [];
+nice_single(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+
+ %% Create a table with 2 fragments and 12 records
+ Tab = nice_frag,
+ Props = [{n_fragments, 2}, {node_pool, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, Props}])),
+ Records = [{Tab, N, -N} || N <- lists:seq(1, 12)],
+ [frag_write(Tab, R) || R <- Records],
+ ?match([{Node1, 2}], frag_dist(Tab)),
+ ?match([8, 4], frag_rec_dist(Tab)),
+
+ %% Adding a new node to pool should not affect distribution
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node2})),
+ Dist = frag_dist(Tab),
+ ?match([{Node2, 0}, {Node1, 2}], Dist),
+ ?match([8, 4], frag_rec_dist(Tab)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})),
+ Dist2 = frag_dist(Tab),
+ ?match([{Node2, 1}, {Node1, 2}], Dist2),
+ ?match([3, 4, 5], frag_rec_dist(Tab)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})),
+ Dist3 = frag_dist(Tab),
+ ?match([{Node1, 2}, {Node2, 2}], Dist3),
+ ?match([3, 2, 5, 2], frag_rec_dist(Tab)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})),
+ Dist4 = frag_dist(Tab),
+ ?match([{Node2, 2}, {Node1, 3}], Dist4),
+ ?match([_, _, _, _, _], frag_rec_dist(Tab)),
+
+ %% Dropping a node in pool should not affect distribution
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})),
+ ?match([{Node2, 2}, {Node1, 3}], frag_dist(Tab)),
+ ?match([_, _, _, _, _], frag_rec_dist(Tab)),
+
+ %% Dropping a fragment
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ Dist5 = frag_dist(Tab),
+ ?match([{Node2, 2}, {Node1, 2}], Dist5),
+ ?match([3, 2, 5, 2], frag_rec_dist(Tab)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})),
+ Dist6 = frag_dist(Tab),
+ ?match([{Node2, 3}, {Node1, 2}], Dist6),
+ ?match([_, _, _, _, _], frag_rec_dist(Tab)),
+
+ %% Dropping all fragments but one
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([3, 2, 5, 2], frag_rec_dist(Tab)),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([3, 4, 5], frag_rec_dist(Tab)),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([8, 4], frag_rec_dist(Tab)),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([{Node2, 0}, {Node1, 1}], frag_dist(Tab)),
+ ?match([12], frag_rec_dist(Tab)),
+
+ %% Defragmenting the table clears frag_properties
+ ?match(Len when Len > 0,
+ length(mnesia:table_info(Tab, frag_properties))),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)),
+ ?match(0, length(mnesia:table_info(Tab, frag_properties))),
+
+ %% Making the table fragmented again
+ Props2 = [{n_fragments, 1}],
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})),
+ Dist7 = frag_dist(Tab),
+ ?match([{Node1, 1}, {Node2, 1}], Dist7),
+ ?match([8, 4], frag_rec_dist(Tab)),
+
+ %% Deleting the fragmented table
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+ ?match(false, lists:member(Tab, mnesia:system_info(tables))),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+nice_multi(doc) ->
+ ["Extending the nice case with one more node, ",
+ "one more replica and a foreign key"];
+nice_multi(suite) -> [];
+nice_multi(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+
+ %% Create a table with 2 fragments and 8 records
+ Tab = frag_master,
+ Name = frag_rec,
+ Type = case mnesia_test_lib:diskless(Config) of
+ true -> n_ram_copies;
+ false -> n_disc_copies
+ end,
+ Props = [{n_fragments, 2},
+ {Type, 2},
+ {node_pool, [Node2, Node1]}],
+ Def = [{frag_properties, Props},
+ {attributes, [id, data]},
+ {record_name, Name}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ [frag_write(Tab, {Name, Id, -Id}) || Id <- lists:seq(1, 8)],
+ ?match([6, 2], frag_rec_dist(Tab)),
+ ?match([{Node2, 2}, {Node1, 2}], frag_dist(Tab)),
+
+ %% And connect another table to it, via a foreign key
+ TabF = frag_slave,
+ PropsF = [{foreign_key, {Tab, foreign_id}}],
+ DefF = [{frag_properties, PropsF},
+ {attributes, [id, foreign_id]}],
+
+ ?match({atomic, ok}, mnesia:create_table(TabF, DefF)),
+ [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)],
+ ?match([10, 6], frag_rec_dist(TabF)),
+ ?match([{Node2, 2}, {Node1, 2}], frag_dist(TabF)),
+
+ %% Adding a new node to pool should not affect distribution
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_node, Node3})),
+ Dist = frag_dist(Tab),
+ ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], Dist),
+ ?match([6, 2], frag_rec_dist(Tab)),
+ DistF = frag_dist(TabF),
+ ?match([{Node3, 0}, {Node2, 2}, {Node1, 2}], DistF),
+ ?match([10, 6], frag_rec_dist(TabF)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist})),
+ Dist2 = frag_dist(Tab),
+ ?match([{Node3, 1},{Node1, 2},{Node2,3}], Dist2),
+ ?match([_, _, _], frag_rec_dist(Tab)),
+ DistF2 = frag_dist(TabF),
+ ?match([{Node3, 1},{Node1, 2},{Node2,3}], DistF2),
+ ?match([_, _, _], frag_rec_dist(TabF)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist2})),
+ Dist3 = frag_dist(Tab),
+ ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist3),
+ ?match([3, 0, 3, 2], frag_rec_dist(Tab)),
+ DistF3 = frag_dist(TabF),
+ ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF3),
+ ?match([3, 3, 7, 3], frag_rec_dist(TabF)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist3})),
+ Dist4 = frag_dist(Tab),
+ ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], Dist4),
+ ?match([_, _, _, _, _], frag_rec_dist(Tab)),
+ DistF4 = frag_dist(TabF),
+ ?match([{Node1, 3}, {Node3, 3},{Node2, 4}], DistF4),
+ ?match([_, _, _, _, _], frag_rec_dist(TabF)),
+
+ %% Dropping a node in pool should not affect distribution
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {del_node, Node1})),
+ ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(Tab)),
+ ?match([_, _, _, _, _], frag_rec_dist(Tab)),
+ ?match([{Node3, 3},{Node2, 4}, {Node1, 3}], frag_dist(TabF)),
+ ?match([_, _, _, _, _], frag_rec_dist(TabF)),
+
+ %% Dropping a fragment
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ Dist5 = frag_dist(Tab),
+ ?match([{Node3, 2},{Node2,3},{Node1, 3}], Dist5),
+ ?match([3, 0, 3, 2], frag_rec_dist(Tab)),
+ DistF5 = frag_dist(Tab),
+ ?match([{Node3, 2},{Node2,3},{Node1, 3}], DistF5),
+ ?match([3, 3, 7, 3], frag_rec_dist(TabF)),
+
+ %% Add new fragment hopefully on the new node
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist5})),
+ Dist6 = frag_dist(Tab),
+ ?match([{Node3, 3},{Node2, 4},{Node1, 3}], Dist6),
+ ?match([_, _, _, _, _], frag_rec_dist(Tab)),
+ DistF6 = frag_dist(TabF),
+ ?match([{Node3, 3},{Node2, 4},{Node1, 3}], DistF6),
+ ?match([_, _, _, _, _], frag_rec_dist(TabF)),
+
+ %% Dropping all fragments but one
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([3, 0, 3, 2], frag_rec_dist(Tab)),
+ ?match([3, 3, 7, 3], frag_rec_dist(TabF)),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([_, _, _], frag_rec_dist(Tab)),
+ ?match([_, _, _], frag_rec_dist(TabF)),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([6, 2], frag_rec_dist(Tab)),
+ ?match([10, 6], frag_rec_dist(TabF)),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, del_frag)),
+ ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(Tab)),
+ ?match([8], frag_rec_dist(Tab)),
+ ?match([{Node3, 0}, {Node2, 1}, {Node1, 1}], frag_dist(TabF)),
+ ?match([16], frag_rec_dist(TabF)),
+
+ %% Defragmenting the tables clears frag_properties
+ ?match(Len when Len > 0,
+ length(mnesia:table_info(TabF, frag_properties))),
+ ?match({atomic, ok}, mnesia:change_table_frag(TabF, deactivate)),
+ ?match(0, length(mnesia:table_info(TabF, frag_properties))),
+ ?match(Len when Len > 0,
+ length(mnesia:table_info(Tab, frag_properties))),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, deactivate)),
+ ?match(0, length(mnesia:table_info(Tab, frag_properties))),
+
+ %% Making the tables fragmented again
+ Props2 = [{n_fragments, 1}, {node_pool, [Node1, Node2]}],
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, Props2})),
+ ?match({atomic, ok}, mnesia:delete_table(TabF)),
+ ?match({atomic, ok}, mnesia:create_table(TabF, DefF)),
+ [frag_write(TabF, {TabF, {Id}, Id}) || Id <- lists:seq(1, 16)],
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, frag_dist(Tab)})),
+ ?match([{Node1, 2}, {Node2, 2}], frag_dist(Tab)),
+ ?match([6, 2], frag_rec_dist(Tab)),
+ ?match([{Node1, 2}, {Node2, 2}], frag_dist(TabF)),
+ ?match([10, 6], frag_rec_dist(TabF)),
+
+ %% Deleting the fragmented tables
+ ?match({atomic, ok}, mnesia:delete_table(TabF)),
+ ?match(false, lists:member(TabF, mnesia:system_info(tables))),
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+ ?match(false, lists:member(Tab, mnesia:system_info(tables))),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+nice_access(doc) ->
+ ["Cover entire callback interface"];
+nice_access(suite) -> [];
+nice_access(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(3, Config),
+
+ Tab = frag_access,
+ Pool = lists:sort(Nodes),
+ Props = [{n_fragments, 20},
+ {n_ram_copies, 2},
+ {node_pool, Pool}],
+ Def = [{frag_properties, Props},
+ {type, ordered_set},
+ {index, [val]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)],
+
+ %% And connect another table to it, via a foreign key
+ TabF = frag_access_slave,
+ PropsF = [{foreign_key, {Tab, val}}],
+ DefF = [{frag_properties, PropsF},
+ {index, [val]}],
+ ?match({atomic, ok}, mnesia:create_table(TabF, DefF)),
+ [frag_write(TabF, {TabF, Id, Id}) || Id <- lists:seq(1, 400)],
+
+ ?match(done, mnesia:activity(transaction, fun do_access/3, [Tab, Tab, Pool], mnesia_frag)),
+ ?match(done, mnesia:activity(transaction, fun do_access/3, [TabF, Tab, Pool], mnesia_frag)),
+
+ ?verify_mnesia(Nodes, []).
+
+do_access(Tab, Master, Pool) ->
+ ?match(20, mnesia:table_info(Tab, n_fragments)),
+ ?match(Pool, mnesia:table_info(Tab, node_pool)),
+ ?match(2, mnesia:table_info(Tab, n_ram_copies)),
+ ?match(0, mnesia:table_info(Tab, n_disc_copies)),
+ ?match(0, mnesia:table_info(Tab, n_disc_only_copies)),
+ ?match(20, length(mnesia:table_info(Tab, frag_names))),
+ ?match(20, length(mnesia:table_info(Tab, frag_size))),
+ ?match(20, length(mnesia:table_info(Tab, frag_memory))),
+ PoolSize = length(Pool),
+ ?match(PoolSize, length(mnesia:table_info(Tab, frag_dist))),
+ ?match(400, mnesia:table_info(Tab, size)),
+ ?match(I when is_integer(I), mnesia:table_info(Tab, memory)),
+ ?match(Tab, mnesia:table_info(Tab, base_table)),
+
+ Foreign =
+ if
+ Master == Tab ->
+ ?match(undefined, mnesia:table_info(Tab, foreign_key)),
+ ?match([_], mnesia:table_info(Tab, foreigners)),
+ ?match({'EXIT', {aborted, {combine_error, Tab, frag_properties, {foreign_key, undefined}}}},
+ mnesia:read({Tab, 5}, 5, read)),
+ fun({T, _K}) -> T end;
+ true ->
+ ?match({Master, 3}, mnesia:table_info(Tab, foreign_key)),
+ ?match([], mnesia:table_info(Tab, foreigners)),
+ fun({T, K}) -> {T, K} end
+ end,
+
+ Attr = val,
+ ?match(400, mnesia:table_info(Tab, size)),
+ Count = fun(_, N) -> N + 1 end,
+ ?match(400, mnesia:foldl(Count, 0, Tab)),
+ ?match(400, mnesia:foldr(Count, 0, Tab)),
+ ?match(ok, mnesia:write({Tab, [-1], 1})),
+ ?match(401, length(mnesia:match_object(Tab, {Tab, '_', '_'}, read))),
+ ?match(401, length(mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], read))),
+
+ First = mnesia:select(Tab, [{{Tab, '_', '$1'}, [], ['$1']}], 10, read),
+ TestCont = fun('$end_of_table', Total, _This) ->
+ Total;
+ ({Res,Cont1}, Total, This) ->
+ Cont = mnesia:select(Cont1),
+ This(Cont, length(Res) + Total, This)
+ end,
+ ?match(401, TestCont(First, 0, TestCont)),
+
+ %% OTP
+ [_, Frag2|_] = frag_names(Tab),
+ Frag2key = mnesia:dirty_first(Frag2),
+ ?match({[Frag2key],_},mnesia:select(Tab,[{{Tab,Frag2key,'$1'},[],['$1']}],100,read)),
+
+ ?match([{Tab, [-1], 1}], mnesia:read(Foreign({Tab, 1}), [-1], read)),
+ ?match(401, mnesia:foldl(Count, 0, Tab)),
+ ?match(401, mnesia:foldr(Count, 0, Tab)),
+ ?match(ok, mnesia:delete(Foreign({Tab, 2}), 2, write)),
+ ?match([], mnesia:read(Foreign({Tab, 2}), 2, read)),
+ ?match([{Tab, 3, 3}], mnesia:read(Foreign({Tab, 3}), 3, read)),
+ ?match(400, mnesia:foldl(Count, 0, Tab)),
+ ?match(400, mnesia:foldr(Count, 0, Tab)),
+ ?match(ok, mnesia:delete_object({Tab, 3, 3})),
+ ?match([], mnesia:read(Foreign({Tab, 3}), 3, read)),
+ One = lists:sort([{Tab, 1, 1}, {Tab, [-1], 1}]),
+ Pat = {Tab, '$1', 1},
+ ?match(One, lists:sort(mnesia:match_object(Tab, Pat, read))),
+ ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [], ['$1']}], read))),
+ ?match([[[-1]]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], [['$1']]}], read))),
+ ?match([[1, 100]], lists:sort(mnesia:select(Tab, [{Pat, [{is_integer, '$1'}], [['$1',100]]}], read))),
+ ?match([1,[-1]], lists:sort(mnesia:select(Tab, [{Pat, [{is_list, '$1'}], ['$1']},{Pat, [{is_integer, '$1'}], ['$1']}], read))),
+ ?match(One, lists:sort(mnesia:index_match_object(Tab, Pat, Attr, read) )),
+ ?match(One, lists:sort(mnesia:index_read(Tab, 1, Attr))),
+ Keys = mnesia:all_keys(Tab),
+ ?match([-1], lists:max(Keys)), %% OTP-3779
+ ?match(399, length(Keys)),
+ ?match(399, mnesia:foldl(Count, 0, Tab)),
+ ?match(399, mnesia:foldr(Count, 0, Tab)),
+
+ ?match(Pool, lists:sort(mnesia:lock({table, Tab}, write))),
+
+ done.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+iter_access(doc) ->
+ ["Cover table iteration via callback interface"];
+iter_access(suite) -> [];
+iter_access(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(3, Config),
+
+ Tab = frag_access,
+ Pool = lists:sort(Nodes),
+ Props = [{n_fragments, 20},
+ {n_ram_copies, 2},
+ {node_pool, Pool}],
+ Def = [{frag_properties, Props},
+ {type, ordered_set},
+ {index, [val]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ [frag_write(Tab, {Tab, Id, Id}) || Id <- lists:seq(1, 400)],
+
+ FragNames = frag_names(Tab),
+ RawRead =
+ fun(Frag) ->
+ Node = mnesia:table_info(Frag, where_to_read),
+ {Frag, rpc:call(Node, ets, tab2list, [Frag])}
+ end,
+
+ ?match(done, mnesia:activity(transaction, fun nice_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)),
+
+ FragNames = frag_names(Tab),
+ [First, Second | _] = FragNames,
+ [Last, LastButOne | _] = lists:reverse(FragNames),
+
+ ?match({atomic, ok}, mnesia:clear_table(First)),
+ ?match({atomic, ok}, mnesia:clear_table(Second)),
+ ?match({atomic, ok}, mnesia:clear_table(lists:nth(8, FragNames))),
+ ?match({atomic, ok}, mnesia:clear_table(lists:nth(9, FragNames))),
+ ?match({atomic, ok}, mnesia:clear_table(lists:nth(10, FragNames))),
+ ?match({atomic, ok}, mnesia:clear_table(lists:nth(11, FragNames))),
+ ?match({atomic, ok}, mnesia:clear_table(LastButOne)),
+ ?match({atomic, ok}, mnesia:clear_table(Last)),
+
+ ?match(done, mnesia:activity(transaction, fun evil_iter_access/3, [Tab, FragNames, RawRead], mnesia_frag)),
+ Size = fun(Table) -> mnesia:table_info(Table, size) end,
+ ?match(true, 0 < mnesia:activity(transaction, Size, [Tab], mnesia_frag)),
+ ?match({atomic, ok}, mnesia:activity(ets, fun() -> mnesia:clear_table(Tab) end, mnesia_frag)),
+ ?match(0, mnesia:activity(transaction, Size, [Tab], mnesia_frag)),
+
+ ?verify_mnesia(Nodes, []).
+
+nice_iter_access(Tab, FragNames, RawRead) ->
+ RawData = ?ignore(lists:map(RawRead, FragNames)),
+ Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs],
+ ExpectedFirst = hd(Keys),
+ ?match(ExpectedFirst, mnesia:first(Tab)),
+ ExpectedLast = lists:last(Keys),
+ ?match(ExpectedLast, mnesia:last(Tab)),
+
+ ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))],
+ ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)),
+
+ ExpectedAllNext = tl(Keys) ++ ['$end_of_table'],
+ ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)),
+
+ done.
+
+evil_iter_access(Tab, FragNames, RawRead) ->
+ RawData = ?ignore(lists:map(RawRead, FragNames)),
+ Keys = [K || {_, Recs} <- RawData, {_, K, _} <- Recs],
+ ExpectedFirst = hd(Keys),
+ ?match(ExpectedFirst, mnesia:first(Tab)),
+ ExpectedLast = lists:last(Keys),
+ ?match(ExpectedLast, mnesia:last(Tab)),
+
+ ExpectedAllPrev = ['$end_of_table' | lists:reverse(tl(lists:reverse(Keys)))],
+ ?match(ExpectedAllPrev, lists:map(fun(K) -> mnesia:prev(Tab, K) end, Keys)),
+
+ ExpectedAllNext = tl(Keys) ++ ['$end_of_table'],
+ ?match(ExpectedAllNext, lists:map(fun(K) -> mnesia:next(Tab, K) end, Keys)),
+
+ done.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+consistency(doc) ->
+ ["Add and delete fragments during TPC-B"];
+consistency(suite) -> [];
+consistency(Config) when is_list(Config) ->
+ ?skip("Not yet implemented (NYI).~n", []),
+ Nodes = ?acquire_nodes(2, Config),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+evil(doc) ->
+ ["Evil coverage of fragmentation API."];
+evil(suite) ->
+ [
+ evil_create,
+ evil_delete,
+ evil_change,
+ evil_combine,
+ evil_loop,
+ evil_delete_db_node
+ ].
+
+evil_create(suite) -> [];
+evil_create(Config) when is_list(Config) ->
+ [Node1, _Node2] = Nodes = ?acquire_nodes(2, Config),
+
+ Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end,
+
+ Tab = evil_create,
+ %% Props in general
+ ?match({aborted, {badarg, Tab, {frag_properties, no_list}}},
+ Create(Tab, [], no_list)),
+ ?match({aborted, {badarg,Tab , [no_tuple]}},
+ Create(Tab, [], [no_tuple])),
+ ?match({aborted,{badarg, Tab, bad_key}},
+ Create(Tab, [], [{bad_key, 7}])),
+
+ %% n_fragments
+ ?match({aborted,{badarg, Tab, [{n_fragments}]}},
+ Create(Tab, [], [{n_fragments}])),
+ ?match({aborted,{badarg, Tab, [{n_fragments, 1, 1}]}},
+ Create(Tab, [], [{n_fragments, 1, 1}])),
+ ?match({aborted, {bad_type,Tab, {n_fragments, a}}},
+ Create(Tab, [], [{n_fragments, a}])),
+ ?match({aborted, {bad_type, Tab, {n_fragments, 0}}},
+ Create(Tab, [], [{n_fragments, 0}])),
+
+ %% *_copies
+ ?match({aborted, {bad_type, Tab, {n_ram_copies, -1}}},
+ Create(Tab, [], [{n_ram_copies, -1}, {n_fragments, 1}])),
+ ?match({aborted, {bad_type, Tab, {n_disc_copies, -1}}},
+ Create(Tab, [], [{n_disc_copies, -1}, {n_fragments, 1}])),
+ ?match({aborted, {bad_type, Tab, {n_disc_only_copies, -1}}},
+ Create(Tab, [], [{n_disc_only_copies, -1}, {n_fragments, 1}])),
+
+ %% node_pool
+ ?match({aborted, {bad_type, Tab, {node_pool, 0}}},
+ Create(Tab, [], [{node_pool, 0}])),
+ ?match({aborted, {combine_error, Tab, "Too few nodes in node_pool"}},
+ Create(Tab, [], [{n_ram_copies, 2}, {node_pool, [Node1]}])),
+
+ %% foreign_key
+ ?match({aborted, {bad_type, Tab, {foreign_key, bad_key}}},
+ Create(Tab, [], [{foreign_key, bad_key}])),
+ ?match({aborted,{bad_type, Tab, {foreign_key, {bad_key}}}},
+ Create(Tab, [], [{foreign_key, {bad_key}}])),
+ ?match({aborted, {no_exists, {bad_tab, frag_properties}}},
+ Create(Tab, [], [{foreign_key, {bad_tab, val}}])),
+ ?match({aborted, {combine_error, Tab, {Tab, val}}},
+ Create(Tab, [], [{foreign_key, {Tab, val}}])),
+ ?match({atomic, ok},
+ Create(Tab, [], [{n_fragments, 1}])),
+
+ ?match({aborted, {already_exists, Tab}},
+ Create(Tab, [], [{n_fragments, 1}])),
+
+ Tab2 = evil_create2,
+ ?match({aborted, {bad_type, no_attr}},
+ Create(Tab2, [], [{foreign_key, {Tab, no_attr}}])),
+ ?match({aborted, {combine_error, Tab2, _, _, _}},
+ Create(Tab2, [], [{foreign_key, {Tab, val}},
+ {node_pool, [Node1]}])),
+ ?match({aborted, {combine_error, Tab2, _, _, _}},
+ Create(Tab2, [], [{foreign_key, {Tab, val}},
+ {n_fragments, 2}])),
+ ?match({atomic, ok},
+ Create(Tab2, [{attributes, [a, b, c]}], [{foreign_key, {Tab, c}}])),
+ Tab3 = evil_create3,
+ ?match({aborted, {combine_error, Tab3, _, _, _}},
+ Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab2, b}}])),
+ ?match({atomic, ok},
+ Create(Tab3, [{attributes, [a, b]}], [{foreign_key, {Tab, b}}])),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+evil_delete(suite) -> [];
+evil_delete(Config) when is_list(Config) ->
+ ?skip("Not yet implemented (NYI).~n", []),
+ Nodes = ?acquire_nodes(2, Config),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+evil_change(suite) -> [];
+evil_change(Config) when is_list(Config) ->
+ [N1,N2,_N3] = Nodes = ?acquire_nodes(3, Config),
+ Create = fun(T, D, P) -> mnesia:create_table(T, [{frag_properties, P}| D]) end,
+ Props1 = [{n_fragments, 2}, {node_pool, [N1]}],
+ Tab1 = evil_change_ram,
+ ?match({atomic, ok}, Create(Tab1, [], Props1)),
+
+ ?match({atomic,ok}, mnesia:change_table_frag(Tab1, {add_frag, Nodes})),
+ Dist10 = frag_dist(Tab1),
+ ?match([{N1,3}], Dist10),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab1, {add_node, N2})),
+ Dist11 = frag_dist(Tab1),
+ ?match([{N2,0},{N1,3}], Dist11),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({aborted,_}, mnesia:change_table_frag(Tab1, {add_frag, [N2,N1]})),
+ ?verbose("~p~n",[frag_dist(Tab1)]),
+ mnesia_test_lib:start_mnesia([N2]),
+
+ Tab2 = evil_change_disc,
+ ?match({atomic,ok}, Create(Tab2,[],[{n_disc_copies,1},{n_fragments,1},{node_pool,[N1,N2]}])),
+ ?verbose("~p~n", [frag_dist(Tab2)]),
+ ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})),
+ _Dist20 = frag_dist(Tab2),
+ mnesia_test_lib:kill_mnesia([N2]),
+ ?match({atomic,ok}, mnesia:change_table_frag(Tab2, {add_frag, [N1,N2]})),
+ ?match({aborted,_}, mnesia:change_table_frag(Tab2, {add_frag, [N2,N1]})),
+
+ mnesia_test_lib:start_mnesia([N2]),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+evil_combine(doc) -> ["Bug in mnesia_4.1.5. and earlier"];
+evil_combine(suite) -> [];
+evil_combine(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ ?match({atomic, ok},mnesia:create_table(tab1, [{disc_copies, [Node1]},
+ {frag_properties, [{n_fragments, 2},
+ {node_pool, [Node1]},
+ {n_disc_copies, 1}]}])),
+ ?match({atomic, ok},mnesia:create_table(tab2, [{disc_copies, [Node1]}])),
+ mnesia:wait_for_tables([tab1, tab2], infinity),
+
+ Add2 = fun() ->
+ mnesia:transaction(fun() ->
+ mnesia:write({tab2,1,1})
+ end)
+ end,
+ Fun = fun() ->
+ Add2(),
+ mnesia:write({tab1,9,10})
+ end,
+ ?match(ok, mnesia:activity({transaction, 1}, Fun, [], mnesia_frag)),
+
+ Read = fun(T, K) ->
+ mnesia:read(T, K, read)
+ end,
+
+ ?match([{tab1,9,10}],mnesia:activity(async_dirty, Read, [tab1, 9], mnesia_frag)),
+ ?match([{tab2,1,1}],mnesia:activity(async_dirty, Read, [tab2, 1], mnesia_frag)),
+
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+evil_loop(doc) -> ["Test select/[14]"];
+evil_loop(suite) -> [];
+evil_loop(Config) when is_list(Config) ->
+ [Node1,_Node2] = ?acquire_nodes(2, Config),
+ Tab1 = ss_oset,
+ Tab2 = ss_set,
+ Tab3 = ss_bag,
+ Tabs = [Tab1, Tab2, Tab3],
+ RecName = ss,
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab1},
+ {ram_copies, [Node1]},
+ {record_name, RecName},
+ {type, ordered_set}])),
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab2},
+ {record_name, RecName},
+ {ram_copies, [Node1]},
+ {type, set}])),
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab3},
+ {record_name, RecName},
+ {ram_copies, [Node1]},
+ {type, bag}])),
+ Keys = [-3, -2] ++ lists:seq(1, 5, 2) ++ lists:seq(6, 10),
+ Recs = [{RecName, K, K} || K <- Keys],
+ [mnesia:dirty_write(Tab1, R) || R <- Recs],
+ [mnesia:dirty_write(Tab2, R) || R <- Recs],
+ [mnesia:dirty_write(Tab3, R) || R <- Recs],
+
+ Activate =
+ fun(Tab) ->
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {activate, []})),
+ Dist = frag_dist(Tab),
+ ?match({atomic, ok}, mnesia:change_table_frag(Tab, {add_frag, Dist}))
+ end,
+
+ Activate(Tab1),
+ Activate(Tab2),
+ Activate(Tab3),
+
+ Match = fun(Tab) -> mnesia:match_object(Tab, {'_', '_', '_'}, write) end,
+ Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end,
+ Trans = fun(Fun, Args) -> mnesia:activity(transaction, Fun, Args, mnesia_frag) end,
+ LoopHelp = fun('$end_of_table',_) ->
+ [];
+ ({Res,Cont},Fun) ->
+ Sel = mnesia:select(Cont),
+ Res ++ Fun(Sel, Fun)
+ end,
+ SelLoop = fun(Table) ->
+ Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read),
+ LoopHelp(Sel, LoopHelp)
+ end,
+
+ R1 = {RecName, 2, 2},
+ R2 = {RecName, 4, 4},
+ R3 = {RecName, 2, 3},
+ R4 = {RecName, 3, 1},
+ R5 = {RecName, 104, 104},
+ W1 = fun(Tab,Search) ->
+ mnesia:write(Tab,R1,write),
+ mnesia:write(Tab,R2,write),
+ Search(Tab)
+ end,
+ S1 = lists:sort([R1, R2| Recs]),
+ ?match(S1, sort_res(Trans(W1, [Tab1, Select]))),
+ ?match(S1, sort_res(Trans(W1, [Tab1, Match]))),
+ ?match(S1, sort_res(Trans(W1, [Tab1, SelLoop]))),
+ ?match(S1, sort_res(Trans(W1, [Tab2, Select]))),
+ ?match(S1, sort_res(Trans(W1, [Tab2, SelLoop]))),
+ ?match(S1, sort_res(Trans(W1, [Tab2, Match]))),
+ ?match(S1, sort_res(Trans(W1, [Tab3, Select]))),
+ ?match(S1, sort_res(Trans(W1, [Tab3, SelLoop]))),
+ ?match(S1, sort_res(Trans(W1, [Tab3, Match]))),
+ [mnesia:dirty_delete_object(Frag, R) || R <- [R1, R2],
+ Tab <- Tabs,
+ Frag <- frag_names(Tab)],
+
+ W2 = fun(Tab, Search) ->
+ mnesia:write(Tab, R3, write),
+ mnesia:write(Tab, R1, write),
+ Search(Tab)
+ end,
+ S2 = lists:sort([R1 | Recs]),
+ S2Bag = lists:sort([R1, R3 | Recs]),
+ io:format("S2 = ~p\n", [S2]),
+ ?match(S2, sort_res(Trans(W2, [Tab1, Select]))),
+ ?match(S2, sort_res(Trans(W2, [Tab1, SelLoop]))),
+ ?match(S2, sort_res(Trans(W2, [Tab1, Match]))),
+ ?match(S2, sort_res(Trans(W2, [Tab2, Select]))),
+ ?match(S2, sort_res(Trans(W2, [Tab2, SelLoop]))),
+ ?match(S2, sort_res(Trans(W2, [Tab2, Match]))),
+ io:format("S2Bag = ~p\n", [S2Bag]),
+ ?match(S2Bag, sort_res(Trans(W2, [Tab3, Select]))),
+ ?match(S2Bag, sort_res(Trans(W2, [Tab3, SelLoop]))),
+ ?match(S2Bag, sort_res(Trans(W2, [Tab3, Match]))),
+
+ W3 = fun(Tab,Search) ->
+ mnesia:write(Tab, R4, write),
+ mnesia:delete(Tab, element(2, R1), write),
+ Search(Tab)
+ end,
+ S3Bag = lists:sort([R4 | lists:delete(R1, Recs)]),
+ S3 = lists:delete({RecName, 3, 3}, S3Bag),
+ ?match(S3, sort_res(Trans(W3, [Tab1, Select]))),
+ ?match(S3, sort_res(Trans(W3, [Tab1, SelLoop]))),
+ ?match(S3, sort_res(Trans(W3, [Tab1, Match]))),
+ ?match(S3, sort_res(Trans(W3, [Tab2, SelLoop]))),
+ ?match(S3, sort_res(Trans(W3, [Tab2, Select]))),
+ ?match(S3, sort_res(Trans(W3, [Tab2, Match]))),
+ ?match(S3Bag, sort_res(Trans(W3, [Tab3, Select]))),
+ ?match(S3Bag, sort_res(Trans(W3, [Tab3, SelLoop]))),
+ ?match(S3Bag, sort_res(Trans(W3, [Tab3, Match]))),
+
+ W4 = fun(Tab,Search) ->
+ mnesia:delete(Tab, -1, write),
+ mnesia:delete(Tab, 4 , write),
+ mnesia:delete(Tab, 17, write),
+ mnesia:delete_object(Tab, {RecName, -1, x}, write),
+ mnesia:delete_object(Tab, {RecName, 4, x}, write),
+ mnesia:delete_object(Tab, {RecName, 42, x}, write),
+ mnesia:delete_object(Tab, R2, write),
+ mnesia:write(Tab, R5, write),
+ Search(Tab)
+ end,
+ S4Bag = lists:sort([R5 | S3Bag]),
+ S4 = lists:sort([R5 | S3]),
+ ?match(S4, sort_res(Trans(W4, [Tab1, Select]))),
+ ?match(S4, sort_res(Trans(W4, [Tab1, SelLoop]))),
+ ?match(S4, sort_res(Trans(W4, [Tab1, Match]))),
+ ?match(S4, sort_res(Trans(W4, [Tab2, Select]))),
+ ?match(S4, sort_res(Trans(W4, [Tab2, SelLoop]))),
+ ?match(S4, sort_res(Trans(W4, [Tab2, Match]))),
+ ?match(S4Bag, sort_res(Trans(W4, [Tab3, Select]))),
+ ?match(S4Bag, sort_res(Trans(W4, [Tab3, SelLoop]))),
+ ?match(S4Bag, sort_res(Trans(W4, [Tab3, Match]))),
+ [mnesia:dirty_delete_object(Tab, R) || R <- [{RecName, 3, 3}, R5], Tab <- Tabs],
+
+ %% hmmm anything more??
+
+ ?verify_mnesia([Node1], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+evil_delete_db_node(doc) ->
+ ["Delete db_node with a repicated table with foreign key"];
+evil_delete_db_node(suite) -> [];
+evil_delete_db_node(Config) when is_list(Config) ->
+ Nodes = lists:sort(?acquire_nodes(2, Config)),
+ Local = node(),
+ Remote = hd(Nodes -- [Local]),
+
+ Type = case mnesia_test_lib:diskless(Config) of
+ true -> n_ram_copies;
+ false -> n_disc_copies
+ end,
+ Tab = frag_master,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{frag_properties, [{Type, 2}, {node_pool, Nodes}]}])),
+ ExtraTab = frag_foreigner,
+ ?match({atomic, ok}, mnesia:create_table(ExtraTab, [{frag_properties, [{foreign_key, {Tab, key}}, {node_pool, Nodes}]}])),
+
+ GetPool = fun(T) ->
+ case lists:keysearch(node_pool, 1, mnesia:table_info (T, frag_properties)) of
+ {value, {node_pool, N}} -> lists:sort(N);
+ false -> []
+ end
+ end,
+ ?match(Nodes, GetPool(Tab)),
+ ?match(Nodes, GetPool(ExtraTab)),
+
+
+ ?match(stopped, rpc:call(Remote, mnesia, stop, [])),
+ ?match({atomic, ok}, mnesia:del_table_copy(schema, Remote)),
+
+ ?match([Local], GetPool(Tab)),
+ ?match([Local], GetPool(ExtraTab)),
+
+ ?verify_mnesia([Local], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Misc convenient helpers
+
+frag_write(Tab, Rec) ->
+ Fun = fun() -> mnesia:write(Tab, Rec, write) end,
+ mnesia:activity(sync_dirty, Fun, mnesia_frag).
+
+frag_dist(Tab) ->
+ Fun = fun() -> mnesia:table_info(Tab, frag_dist) end,
+ mnesia:activity(sync_dirty, Fun, mnesia_frag).
+
+frag_names(Tab) ->
+ Fun = fun() -> mnesia:table_info(Tab, frag_names) end,
+ mnesia:activity(sync_dirty, Fun, mnesia_frag).
+
+frag_rec_dist(Tab) ->
+ Fun = fun() -> mnesia:table_info(Tab, frag_size) end,
+ [Size || {_, Size} <- mnesia:activity(sync_dirty, Fun, mnesia_frag)].
+
+table_size(Tab) ->
+ Node = mnesia:table_info(Tab, where_to_read),
+ rpc:call(Node, mnesia, table_info, [Tab, size]).
+
+sort_res(List) when is_list(List) ->
+ lists:sort(List);
+sort_res(Else) ->
+ Else.
+
+rev_res(List) when is_list(List) ->
+ lists:reverse(List);
+rev_res(Else) ->
+ Else.
diff --git a/lib/mnesia/test/mnesia_inconsistent_database_test.erl b/lib/mnesia/test/mnesia_inconsistent_database_test.erl
new file mode 100644
index 0000000000..b19cd8e01b
--- /dev/null
+++ b/lib/mnesia/test/mnesia_inconsistent_database_test.erl
@@ -0,0 +1,74 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-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_inconsistent_database_test).
+-author('[email protected]').
+
+-behaviour(gen_event).
+
+%%-compile([export_all]).
+-include("mnesia_test_lib.hrl").
+
+%% gen_event callback interface
+-export([init/1, handle_event/2, handle_call/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+
+init(_Args) ->
+ ?verbose("~p installed as event_module~n", [?MODULE]),
+ {ok, []}.
+
+handle_event(Msg, State) ->
+ handle_any_event(Msg, State).
+
+handle_info(Msg, State) ->
+ handle_any_event(Msg, State).
+
+
+handle_call(Msg, State) ->
+ handle_any_event(Msg, State).
+
+
+%% The main...
+
+handle_any_event({mnesia_system_event, Event}, State)
+ when element(1, Event) == inconsistent_database ->
+ ?error("Got event: ~p~n", [Event]),
+ {ok, State};
+handle_any_event(Msg, State) ->
+ ?verbose("Got event: ~p~n", [Msg]),
+ {ok, State}.
+
+%%-----------------------------------------------------------------
+%% terminate(Reason, State) ->
+%% AnyVal
+%%-----------------------------------------------------------------
+
+terminate(_Reason, _State) ->
+ ok.
+
+%%----------------------------------------------------------------------
+%% Func: code_change/3
+%% Purpose: Upgrade process when its code is to be changed
+%% Returns: {ok, NewState}
+%%----------------------------------------------------------------------
+code_change(_OldVsn, _State, _Extra) ->
+ exit(not_supported).
+
diff --git a/lib/mnesia/test/mnesia_install_test.erl b/lib/mnesia/test/mnesia_install_test.erl
new file mode 100644
index 0000000000..42a2a19f37
--- /dev/null
+++ b/lib/mnesia/test/mnesia_install_test.erl
@@ -0,0 +1,342 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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
+%% 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_install_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) ->
+ ["Run some small but demanding test cases in order to verify",
+ "that the basic functionality in Mnesia still works.",
+ "",
+ "Try some very simple things to begin with and increase the",
+ "difficulty stepwise. This test suite should be run before",
+ "all the others if you expect to find bugs.",
+ "",
+ "The function mnesia_install_test:silly() does not use the whole",
+ "infra structure of the test suite. Invoke it on a single node to",
+ "begin with. If that works, proceed with pong = net_adm:ping(SomeOtherNode)",
+ "and rerun silly() in order to perform some distributed tests."];
+all(suite) ->
+ [
+ silly_durability,
+ silly_move,
+ silly_upgrade
+ %,stress
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Stepwise of more and more advanced features
+silly() ->
+ Nodes = [node()] ++ nodes(),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ Config = [{nodes, Nodes}],
+ mnesia_test_lib:eval_test_case(?MODULE, silly2, Config).
+
+silly2(Config) when is_list(Config) ->
+ [Node1 | _] = Nodes = ?acquire_nodes(3, Config),
+ mnesia_test_lib:kill_mnesia(Nodes),
+ ?ignore([mnesia:delete_schema([N]) || N <- Nodes]),
+ ?match(ok, mnesia:create_schema([Node1])),
+ ?match(ok, rpc:call(Node1, mnesia, start, [])),
+ ?match(ok, rpc:call(Node1, mnesia, wait_for_tables,
+ [[schema], infinity])),
+ Res = silly_durability(Config),
+ StressFun = fun(F) -> apply(?MODULE, F, [Config]) end,
+ R =
+ case length(Nodes) of
+ L when L > 1 ->
+ Node2 = lists:nth(2, Nodes),
+ AddDb = [schema, Node2, ram_copies],
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia, add_table_copy, AddDb)),
+ Args = [[{extra_db_nodes, [Node1]}]],
+ ?match(ok, rpc:call(Node2, mnesia, start, Args)),
+ ChangeDb = [schema, Node2, disc_copies],
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia, change_table_copy_type,
+ ChangeDb)),
+ ?match([], mnesia_test_lib:sync_tables([Node1, Node2],
+ [schema])),
+ MoveRes = silly_move(Config),
+ UpgradeRes = silly_upgrade(Config),
+ StressRes = [StressFun(F) || F <- stress(suite)],
+ ?verify_mnesia([Node2], []),
+ [Res, MoveRes, UpgradeRes] ++ StressRes;
+ _ ->
+ StressRes = [StressFun(F) || F <- stress(suite)],
+ ?warning("Too few nodes. Perform net_adm:ping(OtherNode) "
+ "and rerun!!!~n", []),
+ [Res | StressRes]
+ end,
+ ?verify_mnesia([Node1], []),
+ R.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+silly_durability(doc) ->
+ ["Simple test of durability"];
+silly_durability(suite) -> [];
+silly_durability(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = silly,
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+
+ ?match({atomic, ok}, rpc:call(Node1, mnesia,
+ create_table, [Tab, [{Storage, [Node1]}]])),
+
+ Read = fun() -> mnesia:read({Tab, a}) end,
+ Write = fun() -> mnesia:write({Tab, a, b}) end,
+
+ ?match({atomic, []},
+ rpc:call(Node1, mnesia, transaction, [Read])),
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia, transaction, [Write])),
+ ?match({atomic, [{Tab, a, b}]},
+ rpc:call(Node1, mnesia, transaction, [Read])),
+
+ ?match(stopped, rpc:call(Node1, mnesia, stop, [])),
+ ?match(ok, rpc:call(Node1, mnesia, start, [])),
+ case mnesia_test_lib:diskless(Config) of
+ true ->
+ skip;
+ false ->
+ ?match(ok, rpc:call(Node1, mnesia, wait_for_tables, [[Tab], infinity])),
+ ?match({atomic, [{Tab, a, b}]},
+ rpc:call(Node1, mnesia, transaction, [Read]))
+ end,
+ ?verify_mnesia([Node1], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+silly_move(doc) ->
+ ["Simple test of movement of a replica from one node to another"];
+silly_move(suite) -> [];
+silly_move(Config) when is_list(Config) ->
+ [Node1, Node2] = ?acquire_nodes(2, Config),
+ Tab = silly_move,
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia,
+ create_table, [Tab, [{ram_copies, [Node2]}]])),
+ ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])),
+
+ Read = fun() -> mnesia:read({Tab, a}) end,
+ Write = fun() -> mnesia:write({Tab, a, b}) end,
+
+ ?match({atomic, []},
+ rpc:call(Node1, mnesia, transaction, [Read])),
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia, transaction, [Write])),
+ ?match({atomic, [{Tab, a, b}]},
+ rpc:call(Node1, mnesia, transaction, [Read])),
+
+ case mnesia_test_lib:diskless(Config) of
+ true -> skip;
+ false ->
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia,
+ change_table_copy_type, [Tab, Node2, disc_only_copies])),
+ ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab]))
+ end,
+ ?match({atomic, [{Tab, a, b}]}, rpc:call(Node1, mnesia, transaction, [Read])),
+
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia,
+ move_table_copy, [Tab, Node2, Node1])),
+ ?match([], mnesia_test_lib:sync_tables([Node1, Node2], [Tab])),
+ ?match({atomic, [{Tab, a, b}]},
+ rpc:call(Node1, mnesia, transaction, [Read])),
+ ?verify_mnesia([Node1], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+silly_upgrade(doc) ->
+ ["Simple test of a schema upgrade and restore from backup"];
+silly_upgrade(suite) -> [];
+silly_upgrade(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Name = silly_upgrade,
+ Tab1 = silly_upgrade1,
+ Tab2 = silly_upgrade2,
+ Bup = "silly_upgrade.BUP",
+ Bup2 = "silly_upgrade_part.BUP",
+ ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, [{disc_only_copies, Nodes}])),
+
+ CpState = add_some_records(Tab1, Tab2, []),
+ ?match(match, verify_state(Tab1, Tab2, CpState)),
+ file:delete(Bup),
+ ?match(ok, mnesia:backup(Bup)),
+ Args = [{name, Name}, {ram_overrides_dump, true},
+ {min, [Tab1, schema]}, {max, [Tab2]}],
+ ?match({ok, Name, _}, mnesia:activate_checkpoint(Args)),
+
+ IgnoreState = add_more_records(Tab1, Tab2, CpState),
+ ?match(match, verify_state(Tab1, Tab2, IgnoreState)),
+ ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)),
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab2, Node1)),
+ file:delete(Bup2),
+ ?match(ok, mnesia:backup_checkpoint(Name, Bup2)),
+
+ UpgradeState = transform_some_records(Tab1, Tab2, IgnoreState),
+ ?match({mismatch, _, _}, verify_state(Tab1, Tab2, CpState)),
+ ?match({mismatch, _, _}, verify_state(Tab1, Tab2, IgnoreState)),
+ ?match(match, verify_state(Tab1, Tab2, UpgradeState)),
+
+ ?match(ok, mnesia:deactivate_checkpoint(Name)),
+ ?match(match, verify_state(Tab1, Tab2, UpgradeState)),
+
+ ?match(ok, mnesia:install_fallback(Bup2)),
+ file:delete(Bup2),
+ %% Will generate intentional crash, fatal error
+ ?match([], mnesia_test_lib:stop_mnesia([Node2])),
+ wait_till_dead([Node1, Node2]),
+ ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])),
+ ?match(match, verify_state(Tab1, Tab2, CpState)),
+
+ ?match(ok, mnesia:install_fallback(Bup)),
+ file:delete(Bup),
+ %% Will generate intentional crash, fatal error
+ ?match([], mnesia_test_lib:stop_mnesia([Node1, Node2])),
+ wait_till_dead([Node1, Node2]),
+ ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [Tab1, Tab2])),
+ CpState2 = [X || X <- CpState, element(1, X) /= Tab1],
+ ?match(match, verify_state(Tab1, Tab2, CpState2)),
+ ?verify_mnesia(Nodes, []).
+
+wait_till_dead([]) -> ok;
+wait_till_dead([N|Ns]) ->
+ Apps = rpc:call(N, application, which_applications, []),
+ case lists:keymember(mnesia, 1, Apps) of
+ true ->
+ timer:sleep(10),
+ wait_till_dead([N|Ns]);
+ false ->
+ wait_till_dead(Ns)
+ end.
+
+add_some_records(Tab1, Tab2, Old) ->
+ Recs1 = [{Tab1, I, I} || I <- lists:seq(1, 30)],
+ Recs2 = [{Tab2, I, I} || I <- lists:seq(20, 40)],
+ lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Recs1),
+ Fun = fun(R) -> mnesia:write(R) end,
+ Trans = fun() -> lists:foreach(Fun, Recs2) end,
+ ?match({atomic, _}, mnesia:transaction(Trans)),
+ lists:sort(Old ++ Recs1 ++ Recs2).
+
+add_more_records(Tab1, Tab2, Old) ->
+ Change1 = [{T, K, V+100} || {T, K, V} <- Old, K==23],
+ Change2 = [{T, K, V+100} || {T, K, V} <- Old, K==24],
+ Del = [{T, K} || {T, K, _V} <- Old, K>=25],
+ New = [{Tab1, 50, 50}, {Tab2, 50, 50}],
+ lists:foreach(fun(R) -> mnesia:dirty_write(R) end, Change1),
+ lists:foreach(fun(R) -> mnesia:dirty_delete(R) end, Del),
+ Fun = fun(R) -> mnesia:write(R) end,
+ Trans = fun() -> lists:foreach(Fun, Change2 ++ New) end,
+ ?match({atomic, ok}, mnesia:transaction(Trans)),
+ Recs = [{T, K, V} || {T, K, V} <- Old, K<23] ++ Change1 ++ Change2 ++ New,
+ lists:sort(Recs).
+
+
+verify_state(Tab1, Tab2, Exp) ->
+ Fun = fun() ->
+ Act1 = [mnesia:read({Tab1, K}) || K <- mnesia:all_keys(Tab1)],
+ Act2 = [mnesia:read({Tab2, K}) || K <- mnesia:all_keys(Tab2)],
+ Act = lists:append(Act1) ++ lists:append(Act2),
+ {ok, Act -- Exp, Exp -- Act}
+ end,
+ case mnesia:transaction(Fun) of
+ {atomic, {ok, [], []}} -> match;
+ {atomic, {ok, More, Less}} -> {mismatch, More, Less};
+ {aborted, Reason} -> {error, Reason}
+ end.
+
+transform_some_records(Tab1, _Tab2, Old) ->
+ Fun = fun(Rec) ->
+ list_to_tuple(tuple_to_list(Rec) ++ [4711])
+ end,
+ ?match({atomic, ok},
+ mnesia:transform_table(Tab1, Fun, [key, val, extra])),
+ Filter = fun(Rec) when element(1, Rec) == Tab1 -> {true, Fun(Rec)};
+ (_) -> true
+ end,
+ lists:sort(lists:zf(Filter, Old)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+stress(doc) ->
+ ["Stress the system a little"];
+stress(suite) ->
+ [
+ conflict,
+ dist
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dist(doc) ->
+ ["Avoid lock conflicts in order to maximize thruput",
+ "Ten drivers per node, tables replicated to all nodes, lots of branches"];
+dist(suite) -> [];
+dist(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]),
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ ?match({ok, _}, mnesia_tpcb:start(dist_args(Nodes, Storage))).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+conflict(doc) ->
+ ["Provoke a lot of lock conflicts.",
+ "Ten drivers per node, tables replicated to all nodes, single branch"];
+conflict(suite) -> [];
+conflict(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, 10 * 60000}]),
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ ?match({ok, _}, mnesia_tpcb:start(conflict_args(Nodes, Storage))).
+
+conflict_args(Nodes, ReplicaType) ->
+ [{db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {replica_nodes, Nodes},
+ {n_drivers_per_node, 10},
+ {n_branches, 1},
+ {n_accounts_per_branch, 10},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(5)},
+ {report_interval, timer:seconds(10)},
+ {use_running_mnesia, true},
+ {reuse_history_id, true}].
+
+dist_args(Nodes, ReplicaType) ->
+ [{db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {replica_nodes, Nodes},
+ {n_drivers_per_node, 10},
+ {n_branches, length(Nodes) * 100},
+ {n_accounts_per_branch, 10},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(5)},
+ {report_interval, timer:seconds(10)},
+ {use_running_mnesia, true},
+ {reuse_history_id, true}].
+
diff --git a/lib/mnesia/test/mnesia_isolation_test.erl b/lib/mnesia/test/mnesia_isolation_test.erl
new file mode 100644
index 0000000000..4fc6e8fe58
--- /dev/null
+++ b/lib/mnesia/test/mnesia_isolation_test.erl
@@ -0,0 +1,2419 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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_isolation_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 the isolation property.",
+ "Operations of concurrent transactions must yield results which",
+ "are indistinguishable from the results which would be obtained by",
+ "forcing each transaction to be serially executed to completion in",
+ "some order. This means that repeated reads of the same records",
+ "within any committed transaction must have returned identical",
+ "data when run concurrently with any mix of arbitary transactions.",
+ "Updates in one transaction must not be visible in any other",
+ "transaction before the transaction has been committed."];
+all(suite) ->
+ [
+ locking,
+ visibility
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+locking(doc) ->
+ ["Verify locking semantics for various configurations",
+ " NoLock = lock_funs(no_lock, any_granularity)",
+ " SharedLock = lock_funs(shared_lock, any_granularity)",
+ " ExclusiveLock = lock_funs(exclusive_lock, any_granularity)",
+ " AnyLock = lock_funs(any_lock, any_granularity)"];
+locking(suite) ->
+ [no_conflict,
+ simple_queue_conflict,
+ advanced_queue_conflict,
+ simple_deadlock_conflict,
+ advanced_deadlock_conflict,
+ lock_burst,
+ sticky_locks,
+ unbound_locking,
+ admin_conflict,
+%% removed_resources,
+ nasty
+ ].
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+no_conflict(suite) -> [];
+no_conflict(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = no_conflict,
+ create_conflict_table(Tab, [Node1]),
+ Fun = fun(OtherOid, Lock1, Lock2) ->
+ %% Start two transactions
+ {success, [B, A]} = ?start_activities([Node1, Node1]),
+ ?start_transactions([B, A]),
+
+ A ! fun() -> Lock1(one_oid(Tab)), ok end,
+ ?match_receive({A, ok}),
+ B ! fun() -> Lock2(OtherOid), ok end,
+ ?match_receive({B, ok}),
+ A ! fun() -> mnesia:abort(ok) end,
+ ?match_receive({A, {aborted, ok}}),
+ B ! fun() -> mnesia:abort(ok) end,
+ ?match_receive({B, {aborted, ok}})
+ end,
+ NoLocks = lock_funs(no_lock, any_granularity),
+ SharedLocks = lock_funs(shared_lock, any_granularity),
+ AnyLocks = lock_funs(any_lock, any_granularity),
+ OneOneFun = fun(Lock1, Lock2) -> Fun(one_oid(Tab), Lock1, Lock2) end,
+ fun_loop(OneOneFun, NoLocks, AnyLocks),
+ fun_loop(OneOneFun, AnyLocks, NoLocks),
+ fun_loop(OneOneFun, SharedLocks, SharedLocks),
+
+ %% Lock different objects
+ OneOtherFun = fun(Lock1, Lock2) -> Fun(other_oid(Tab), Lock1, Lock2) end,
+ OneSharedLocks = lock_funs(shared_lock, one),
+ OneExclusiveLocks = lock_funs(exclusive_lock, one),
+ fun_loop(OneOtherFun, OneSharedLocks, OneExclusiveLocks),
+ fun_loop(OneOtherFun, OneExclusiveLocks, OneSharedLocks),
+ fun_loop(OneOtherFun, OneExclusiveLocks, OneExclusiveLocks),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+simple_queue_conflict(suite) -> [];
+simple_queue_conflict(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = simple_queue_conflict,
+ create_conflict_table(Tab, [Node1]),
+ Fun = fun(OneLock, OtherLock) ->
+ %% Start two transactions
+ {success, [B, A]} = ?start_activities([Node1, Node1]),
+ ?start_transactions([B, A]),
+
+ A ! fun() -> OneLock(one_oid(Tab)), ok end,
+ ?match_receive({A, ok}),
+ B ! fun() -> OtherLock(one_oid(Tab)), ok end,
+ wait_for_lock(B, [Node1], 20), % Max 10 sec
+ A ! end_trans,
+ ?match_multi_receive([{A, {atomic, end_trans}}, {B, ok}]),
+ B ! fun() -> mnesia:abort(ok) end,
+ ?match_receive({B, {aborted, ok}})
+ end,
+ OneSharedLocks = lock_funs(shared_lock, one),
+ AllSharedLocks = lock_funs(shared_lock, all),
+ OneExclusiveLocks = lock_funs(exclusive_lock, one),
+ AllExclusiveLocks = lock_funs(exclusive_lock, all),
+ fun_loop(Fun, OneExclusiveLocks, OneExclusiveLocks),
+ fun_loop(Fun, AllExclusiveLocks, AllExclusiveLocks),
+ fun_loop(Fun, OneExclusiveLocks, AllExclusiveLocks),
+ fun_loop(Fun, AllExclusiveLocks, OneExclusiveLocks),
+ fun_loop(Fun, OneSharedLocks, AllExclusiveLocks),
+ fun_loop(Fun, AllSharedLocks, OneExclusiveLocks),
+ ok.
+
+wait_for_lock(Pid, _Nodes, 0) ->
+ Queue = mnesia:system_info(lock_queue),
+ ?error("Timeout while waiting for lock on Pid ~p in queue ~p~n", [Pid, Queue]);
+wait_for_lock(Pid, Nodes, N) ->
+ rpc:multicall(Nodes, sys, get_status, [mnesia_locker]),
+ List = [rpc:call(Node, mnesia, system_info, [lock_queue]) || Node <- Nodes],
+ Q = lists:append(List),
+ check_q(Pid, Q, Nodes, N).
+
+check_q(Pid, [{_Oid, _Op, Pid, _Tid, _WFT} | _Tail], _N, _Count) -> ok;
+check_q(Pid, [_ | Tail], N, Count) -> check_q(Pid, Tail, N, Count);
+check_q(Pid, [], N, Count) ->
+ timer:sleep(500),
+ wait_for_lock(Pid, N, Count - 1).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+advanced_queue_conflict(suite) -> [];
+advanced_queue_conflict(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = advanced_queue_conflict,
+ create_conflict_table(Tab, [Node1]),
+ OneRec = {Tab, 3, 3},
+ OneOid = {Tab, 3},
+ OtherRec = {Tab, 4, 4},
+ OtherOid = {Tab, 4},
+
+ %% Start four transactions
+ {success, [D, C, B, A]} = ?start_activities(lists:duplicate(4, Node1)),
+ ?start_transactions([D, C, B, A]),
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+
+ %% Acquire some locks
+ A ! fun() -> mnesia:write(OneRec) end,
+ ?match_receive({A, ok}),
+ A ! fun() -> mnesia:read(OneOid) end,
+ ?match_receive({A, [OneRec]}),
+
+ B ! fun() -> mnesia:write(OtherRec) end,
+ ?match_receive({B, ok}),
+ B ! fun() -> mnesia:read(OneOid) end,
+ ?match_receive(timeout),
+
+ C ! fun() -> mnesia:read(OtherOid) end,
+ ?match_receive(timeout),
+ D ! fun() -> mnesia:wread(OtherOid) end,
+ ?match_receive(timeout),
+
+ %% and release them in a certain order
+ A ! end_trans,
+ ?match_multi_receive([{A, {atomic, end_trans}}, {B, [OneRec]}]),
+ B ! end_trans,
+ ?match_multi_receive([{B, {atomic, end_trans}}, {C, [OtherRec]}]),
+ C ! end_trans,
+ ?match_multi_receive([{C, {atomic, end_trans}}, {D, [OtherRec]}]),
+ D ! end_trans,
+ ?match_receive({D, {atomic, end_trans}}),
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+simple_deadlock_conflict(suite) -> [];
+simple_deadlock_conflict(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = simple_deadlock_conflict,
+ create_conflict_table(Tab, [Node1]),
+ Rec = {Tab, 4, 4},
+ Oid = {Tab, 4},
+
+ %% Start two transactions
+ {success, [B, A]} = ?start_activities(lists:duplicate(2, Node1)),
+ mnesia_test_lib:start_transactions([B, A], 0), % A is newest
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+
+ B ! fun() -> mnesia:write(Rec) end,
+ ?match_receive({B, ok}),
+ A ! fun() -> mnesia:read(Oid) end,
+ ?match_receive({A, {aborted, nomore}}),
+ B ! end_trans,
+ ?match_receive({B, {atomic, end_trans}}),
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+advanced_deadlock_conflict(suite) -> [];
+advanced_deadlock_conflict(Config) when is_list(Config) ->
+ [Node1, Node2] = ?acquire_nodes(2, Config),
+ Tab = advanced_deadlock_conflict,
+ create_conflict_table(Tab, [Node2]),
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ Rec = {Tab, 4, 4},
+ Oid = {Tab, 4},
+
+ %% Start two transactions
+ {success, [B, A]} = ?start_activities([Node1, Node2]),
+ mnesia_test_lib:start_sync_transactions([B, A], 0), % A is newest
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+
+ B ! fun() -> mnesia:write(Rec) end,
+ ?match_receive({B, ok}),
+ A ! fun() -> mnesia:read(Oid) end,
+ ?match_receive({A, {aborted, nomore}}),
+ B ! end_trans,
+ ?match_receive({B, {atomic, end_trans}}),
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+one_oid(Tab) -> {Tab, 1}.
+other_oid(Tab) -> {Tab, 2}.
+
+create_conflict_table(Tab, Nodes) ->
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, Nodes},
+ {attributes, [key, val]},
+ {index, [val]}
+ ])),
+ ?match([], mnesia_test_lib:sync_tables(Nodes, [Tab])),
+ init_conflict_table(Tab).
+
+init_conflict_table(Tab) ->
+ Recs = mnesia:dirty_match_object({Tab, '_', '_'}),
+ lists:foreach(fun(R) -> mnesia:dirty_delete_object(R) end, Recs),
+ Keys = [one_oid(Tab), other_oid(Tab)],
+ [mnesia:dirty_write({T, K, K}) || {T, K} <- Keys].
+
+%% Apply Fun for each X and Y
+fun_loop(Fun, Xs, Ys) ->
+ lists:foreach(fun(X) -> lists:foreach(fun(Y) -> do_fun(Fun, X, Y) end, Ys) end, Xs).
+
+do_fun(Fun, X, Y) ->
+ Pid = spawn_link(?MODULE, do_fun, [self(), Fun, X, Y]),
+ receive
+ {done_fun, Pid} -> done_fun
+ end.
+
+do_fun(Monitor, Fun, X, Y) ->
+ ?log("{do_fun ~p~n", [[Fun, X, Y]]),
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ Fun(X, Y),
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ unlink(Monitor),
+ Monitor ! {done_fun, self()},
+ exit(done_fun).
+
+%% Returns a list of fun's
+lock_funs(no_lock, one) ->
+ [
+ fun(Oid) -> mnesia:dirty_read(Oid) end,
+ fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}) end,
+ fun({Tab, Key}) -> mnesia:dirty_write({Tab, Key, Key}),
+ mnesia:dirty_update_counter({Tab, Key}, 0) end,
+ fun(Oid) -> mnesia:dirty_delete(Oid) end,
+ fun({Tab, Key}) -> mnesia:dirty_delete_object({Tab, Key, Key}) end,
+ fun({Tab, Key}) -> mnesia:dirty_match_object({Tab, Key, Key}) end,
+ fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, Key, Key}, val) end,
+ fun({Tab, Key}) -> mnesia:dirty_index_read(Tab, Key, val) end,
+ fun({Tab, Key}) -> mnesia:dirty_index_match_object({Tab, '_', Key}, val) end
+ ];
+lock_funs(no_lock, all) ->
+ [
+ fun({Tab, _}) -> mnesia:dirty_match_object({Tab, '_', '_'}) end,
+ fun({Tab, _}) -> slot_iter(Tab) end,
+ fun({Tab, _}) -> key_iter(Tab) end
+ ];
+lock_funs(shared_lock, one) ->
+
+ [
+ fun(Oid) -> mnesia:read(Oid) end,
+ fun({Tab, Key}) ->
+ init_conflict_table(Tab),
+ mnesia:dirty_delete(other_oid(Tab)),
+ mnesia:match_object({Tab, Key, Key}) end
+ ];
+lock_funs(shared_lock, all) ->
+ [
+ fun({Tab, _}) -> mnesia:read_lock_table(Tab) end,
+ fun({Tab, Key}) -> mnesia:match_object({Tab, '_', Key}) end,
+ fun({Tab, _}) -> mnesia:match_object({Tab, '_', '_'}) end,
+ fun({Tab, _}) -> mnesia:all_keys(Tab) end,
+ fun({Tab, Key}) -> mnesia:index_match_object({Tab, '_', Key}, val) end,
+ fun({Tab, Key}) -> mnesia:index_read(Tab, Key, val) end
+ ];
+lock_funs(exclusive_lock, one) ->
+ [
+ fun(Oid) -> mnesia:wread(Oid) end,
+ fun({Tab, Key}) -> mnesia:write({Tab, Key, Key}) end,
+ fun(Oid) -> mnesia:delete(Oid) end,
+ fun({Tab, Key}) -> mnesia:delete_object({Tab, Key, Key}) end,
+ fun({Tab, Key}) -> mnesia:s_write({Tab, Key, Key}) end,
+ fun(Oid) -> mnesia:s_delete(Oid) end,
+ fun({Tab, Key}) -> mnesia:s_delete_object({Tab, Key, Key}) end
+ ];
+lock_funs(exclusive_lock, all) ->
+ [
+ fun({Tab, _}) -> mnesia:write_lock_table(Tab) end
+ ];
+lock_funs(Compatibility, any_granularity) ->
+ lists:append([lock_funs(Compatibility, Granularity) ||
+ Granularity <- [one, all]]);
+lock_funs(any_lock, Granularity) ->
+ lists:append([lock_funs(Compatibility, Granularity) ||
+ Compatibility <- [no_lock, shared_lock, exclusive_lock]]).
+
+slot_iter(Tab) ->
+ slot_iter(Tab, mnesia:dirty_slot(Tab, 0), 1).
+slot_iter(_Tab, '$end_of_table', _) ->
+ [];
+slot_iter(Tab, Recs, Slot) ->
+ Recs ++ slot_iter(Tab, mnesia:dirty_slot(Tab, Slot), Slot+1).
+
+key_iter(Tab) ->
+ key_iter(Tab, mnesia:dirty_first(Tab)).
+key_iter(_Tab, '$end_of_table') ->
+ [];
+key_iter(Tab, Key) ->
+ [Key | key_iter(Tab, mnesia:dirty_next(Tab, Key))].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+lock_burst(suite) -> [];
+lock_burst(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = burst,
+ ?match({atomic, ok}, mnesia:create_table(Tab,
+ [{attributes, [a, b]},
+ {ram_copies, [Node1]}])),
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ?match(ok, burst_em(Tab, 1000)),
+ ?match([{burst,1,1000}], mnesia:dirty_read(Tab,1)),
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+burst_em(Tab, N) ->
+ spawn_link(?MODULE, burst_counter, [self(), Tab, N]),
+ receive
+ burst_counter_done -> ok
+ end.
+
+burst_counter(Monitor, Tab, N) when N > 0 ->
+ ?match(ok, burst_gen(Tab, N, self())),
+ Monitor ! burst_receiver(N).
+
+burst_receiver(0) ->
+ burst_counter_done;
+burst_receiver(N) ->
+ receive
+ burst_incr_done ->
+ burst_receiver(N-1)
+ end.
+
+burst_gen(_, 0, _) ->
+ ok;
+burst_gen(Tab, N, Father) when is_integer(N), N > 0 ->
+ spawn_link(?MODULE, burst_incr, [Tab, Father]),
+ burst_gen(Tab, N-1, Father).
+
+burst_incr(Tab, Father) ->
+ Fun = fun() ->
+ Val =
+ case mnesia:read({Tab, 1}) of
+ [{Tab, 1, V}] -> V;
+ [] -> 0
+ end,
+ mnesia:write({Tab, 1, Val+1})
+ end,
+ ?match({atomic, ok}, mnesia:transaction(Fun)),
+ Father ! burst_incr_done.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+sticky_locks(doc) ->
+ ["Simple Tests of sticky locks"];
+
+sticky_locks(suite) ->
+ [
+ basic_sticky_functionality
+ %% Needs to be expandand a little bit further
+ ].
+
+basic_sticky_functionality(suite) -> [];
+basic_sticky_functionality(Config) when is_list(Config) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = basic_table,
+ Storage = mnesia_test_lib:storage_type(disc_copies, Config),
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Storage, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(sync, [{ram_copies, Nodes}])),
+ Trans1 = fun() ->
+ ?match(ok, mnesia:s_write({Tab, 1, 2})),
+ ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})),
+ ?match(timeout, receive M -> M after 500 -> timeout end),
+ ?match(ok, mnesia:s_write({Tab, 2, 2})),
+ ?match(ok, mnesia:write({Tab, 42, 4711}))
+ end,
+ Trans2 = fun() ->
+ ?match([{Tab, 1, 2}], mnesia:read({Tab, 1})),
+ ?match(timeout, receive M -> M after 500 -> timeout end),
+ ?match(ok, mnesia:write({Tab, 1, 4711})),
+ ?match(ok, mnesia:s_write({Tab, 2, 4})),
+ ?match(ok, mnesia:delete({Tab, 42}))
+ end,
+ rpc:call(N1, mnesia, transaction, [Trans1]),
+ ?match([{Tab,N1}], rpc:call(N1, ?MODULE, get_sticky, [])),
+ ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])),
+
+ rpc:call(N2, mnesia, transaction, [Trans2]),
+ ?match([], rpc:call(N1, ?MODULE, get_sticky, [])),
+ ?match([], rpc:call(N2, ?MODULE, get_sticky, [])),
+
+ Slock = fun() -> mnesia:read({sync,sync}),get_sticky() end,
+ ?match({atomic, [{Tab,1, 4711}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
+ ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
+ ?match({atomic, [{Tab,N1}]}, rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 3}),Slock() end])),
+ ?match([{Tab,N1}], rpc:call(N2, ?MODULE, get_sticky, [])),
+
+ ?match({atomic,[]}, rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])),
+
+ ?match([], rpc:call(N1, ?MODULE, get_sticky, [])),
+ ?match([], rpc:call(N2, ?MODULE, get_sticky, [])),
+
+ ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 4}),Slock() end])),
+
+ ?match({atomic,[]}, rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])),
+ ?match({atomic,[{Tab,N1}]}, rpc:call(N1, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 5}),Slock() end])),
+ ?match({atomic,[]}, rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])),
+ ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 7}),Slock() end])),
+
+ ?match([{Tab,N2}], get_sticky()),
+ ?match({atomic, [{Tab,1, 7}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
+ ?match([{Tab,N2}], get_sticky()),
+ ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
+ ?match([{Tab,N2}], get_sticky()),
+ ?match({atomic,[{Tab,N2}]}, rpc:call(N2, mnesia, transaction,
+ [fun() -> mnesia:s_write({Tab, 1, 6}),Slock() end])),
+ ?match([{Tab,N2}], get_sticky()),
+ ?match({atomic, [{Tab,1, 6}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
+ ?match([{Tab,N2}], get_sticky()),
+ ?match({atomic, [{Tab,2, 4}]}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
+ ?match([{Tab,N2}], get_sticky()),
+ ?verify_mnesia(Nodes, []).
+
+get_sticky() ->
+ mnesia_locker ! {get_table, self(), mnesia_sticky_locks},
+ receive {mnesia_sticky_locks, Locks} -> Locks end.
+
+get_held() ->
+ mnesia_locker ! {get_table, self(), mnesia_sticky_locks},
+ receive {mnesia_sticky_locks, Locks} -> Locks end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+unbound_locking(suite) ->
+ [unbound1, unbound2];
+
+unbound_locking(doc) ->
+ ["Check that mnesia handles unbound key variables, GPRS bug."
+ "Ticket id: OTP-3342"].
+
+unbound1(suite) -> [];
+unbound1(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+
+ ?match({atomic, ok}, mnesia:create_table(ul, [])),
+
+ Tester = self(),
+ Write = fun() ->
+ mnesia:write({ul, {key, {17,42}}, val}),
+ ?log("~p Got write lock waiting...~n", [self()]),
+ Tester ! continue,
+ receive
+ continue ->
+ ok
+ end,
+ ?log("..continuing~n", []),
+ ok
+ end,
+
+ {success, [A]} = ?start_activities([Node1]),
+ ?start_transactions([A]),
+ A ! Write,
+
+ receive continue -> ok end,
+
+ Match = fun() ->
+ case catch mnesia:match_object({ul, {key, {'_', '$0'}}, '_'}) of
+ {'EXIT', What} -> %% Cyclic first time
+ ?log("Cyclic Restarting~n", []),
+ A ! continue,
+ A ! end_trans,
+ exit(What);
+ Res ->
+ ?log("Got match log ~p...~n", [Res]),
+ Res
+ end
+ end,
+ ?match({atomic, [{ul,{key,{17,42}},val}]}, mnesia:transaction(Match)),
+
+ ?match_receive({A, ok}),
+ ?match_receive({A, {atomic, end_trans}}),
+ ok.
+
+unbound2(suite) -> [];
+unbound2(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+
+ ?match({atomic, ok}, mnesia:create_table(ul, [])),
+
+ {success, [B, A]} = ?start_activities([Node1, Node1]),
+
+ Me = self(),
+
+ Write = fun() ->
+ mnesia:write({ul, {key, {17,42}}, val}),
+ ?log("~p Got write lock waiting... Tid ~p ~n",
+ [self(), get(mnesia_activity_state)]),
+ Me ! ok_lock,
+ receive
+ continue ->
+ ok
+ end,
+ ?log("..continuing~n", []),
+ ok
+ end,
+
+ Match = fun() ->
+ receive
+ continueB ->
+ ?log("~p, moving on TID ~p~n",
+ [self(), get(mnesia_activity_state)]),
+ Me ! {self(), continuing}
+ end,
+ case catch mnesia:match_object({ul, {key, {'_', '$0'}},
+ '_'}) of
+ {'EXIT', What} -> %% Cyclic first time
+ ?log("Cyclic Restarting ~p ~n", [What]),
+ {should_not_happen,What};
+ Res ->
+ ?log("Got match log ~p...~n", [Res]),
+ Res
+ end
+ end,
+
+ B ! fun() -> mnesia:transaction(Match) end,
+ timer:sleep(100), %% Let B be started first..
+ A ! fun() -> mnesia:transaction(Write) end,
+
+ receive ok_lock -> ok end,
+
+ B ! continueB,
+ ?match_receive({B, continuing}),
+
+ %% B should now be in lock queue.
+ A ! continue,
+ ?match_receive({A, {atomic, ok}}),
+ ?match_receive({B, {atomic, [{ul,{key,{17,42}},val}]}}),
+ ok.
+
+receiver() ->
+ receive
+ {_Pid, begin_trans} ->
+ receiver();
+ Else ->
+ Else
+ after
+ 10000 ->
+ timeout
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+admin_conflict(doc) ->
+ ["Provoke lock conflicts with schema transactions and checkpoints."];
+admin_conflict(suite) ->
+ [
+ create_table,
+ delete_table,
+ move_table_copy,
+ add_table_index,
+ del_table_index,
+ transform_table,
+ snmp_open_table,
+ snmp_close_table,
+ change_table_copy_type,
+ change_table_access,
+ add_table_copy,
+ del_table_copy,
+ dump_tables,
+ extra_admin_tests
+ ].
+
+create_table(suite) -> [];
+create_table(Config) when is_list(Config) ->
+ [ThisNode, Node2] = ?acquire_nodes(2, Config),
+ Tab = c_t_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ DiskMaybe = mnesia_test_lib:storage_type(disc_copies, Config),
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, create_table,
+ [test_tab1, [{DiskMaybe, [ThisNode]}]]]),
+ ?match_multi_receive([{Pid, {atomic, ok}},
+ {'EXIT', Pid, normal}]), %% No Locks! op should be exec.
+
+ Pid2 = spawn_link(?MODULE, op, [self(), mnesia, create_table,
+ [test_tab2, [{ram_copies, [Node2]}]]]),
+
+ ?match_multi_receive([{Pid2, {atomic, ok}},
+ {'EXIT', Pid2, normal}]), %% No Locks! op should be exec.
+
+ A ! end_trans,
+ ?match_receive({A,{atomic,end_trans}}),
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+delete_table(suite) -> [];
+delete_table(Config) when is_list(Config) ->
+ [ThisNode, Node2] = ?acquire_nodes(2, Config),
+ Tab = d_t_tab,
+ Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:read({Tab, 1}) end,
+ ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, delete_table,
+ [Tab]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+move_table_copy(suite) -> [];
+move_table_copy(Config) when is_list(Config) ->
+ [ThisNode, Node2] = ?acquire_nodes(2, Config),
+ Tab = m_t_c_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 1, 2, 3}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, move_table_copy,
+ [Tab, ThisNode, Node2]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ timer:sleep(500), %% Don't know how to sync this !!!
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+add_table_index(suite) -> [];
+add_table_index(Config) when is_list(Config) ->
+ [ThisNode, _Node2] = ?acquire_nodes(2, Config ++ [{tc_timeout, 60000}]),
+ Tab = a_t_i_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia,
+ add_table_index, [Tab, attr1]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+del_table_index(suite) -> [];
+del_table_index(Config) when is_list(Config) ->
+ [ThisNode, _Node2] = ?acquire_nodes(2, Config),
+ Tab = d_t_i_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, attr1)),
+
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 51, 51, attr2}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_index,
+ [Tab, attr1]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+ %% Locks released! op should be exec.
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+transform_table(suite) -> [];
+transform_table(Config) when is_list(Config) ->
+ [ThisNode, Node2] = ?acquire_nodes(2, Config),
+ Tab = t_t_tab,
+ Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:read({Tab, 1}) end,
+ ?match_receive({A, [{Tab, 1, 1, 0}]}), %% A is executed
+
+ Transform = fun({Table, Key, Attr1, Attr2}) -> % Need todo a transform
+ {Table, Key, {Attr1, Attr2}} end,
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, transform_table,
+ [Tab, Transform, [key, attr1]]]),
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+snmp_open_table(suite) -> [];
+snmp_open_table(Config) when is_list(Config) ->
+ [ThisNode, _Node2] = ?acquire_nodes(2, Config),
+ Tab = s_o_t_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_open_table,
+ [Tab, [{key, integer}]]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ %% Locks released! op should be exec. Can take a while (thats the timeout)
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+snmp_close_table(suite) -> [];
+snmp_close_table(Config) when is_list(Config) ->
+ [ThisNode, _Node2] = ?acquire_nodes(2, Config),
+ Tab = s_c_t_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 1, 1, 100}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, snmp_close_table, [Tab]]),
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ %% Locks released! op should be exec. Can take a while (thats the timeout)
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+change_table_copy_type(suite) -> [];
+change_table_copy_type(Config) when is_list(Config) ->
+ [ThisNode, _Node2] = ?acquire_nodes(2, Config),
+ Tab = c_t_c_t_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+ A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_copy_type,
+ [Tab, ThisNode, disc_copies]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+change_table_access(suite) -> [];
+change_table_access(Config) when is_list(Config) ->
+ [ThisNode, _Node2] = ?acquire_nodes(2, Config),
+ Tab = c_t_a_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, change_table_access_mode,
+ [Tab, read_only]]),
+
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+add_table_copy(suite) -> [];
+add_table_copy(Config) when is_list(Config) ->
+ [ThisNode, Node2] = ?acquire_nodes(2, Config),
+ Tab = a_t_c_tab,
+ Def = [{ram_copies, [ThisNode]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+
+ A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, add_table_copy,
+ [Tab, Node2, ram_copies]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+del_table_copy(suite) -> [];
+del_table_copy(Config) when is_list(Config) ->
+ [ThisNode, Node2] = ?acquire_nodes(2, Config),
+ Tab = d_t_c_tab,
+ Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+ A ! fun() -> mnesia:write({Tab, 1, 2, 5}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, del_table_copy,
+ [Tab, ThisNode]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A, {atomic,end_trans}}),
+
+ ?match_receive({Pid, {atomic, ok}}),
+ ?match_receive({'EXIT', Pid, normal}),
+
+ timer:sleep(500), %% Don't know how to sync this !!!
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ sys:get_status(whereis(mnesia_tm)), % Explicit sync, release locks is async
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+dump_tables(suite) -> [];
+dump_tables(Config) when is_list(Config) ->
+ [ThisNode, Node2] = ?acquire_nodes(2, Config),
+ Tab = dump_t_tab,
+ Def = [{ram_copies, [ThisNode, Node2]}, {attributes, [key, attr1, attr2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 50),
+ {success, [A]} = ?start_activities([ThisNode]),
+ mnesia_test_lib:start_sync_transactions([A], 0),
+ A ! fun() -> mnesia:write({Tab, 1, 1, updated}) end,
+ ?match_receive({A, ok}), %% A is executed
+
+ Pid = spawn_link(?MODULE, op, [self(), mnesia, dump_tables,
+ [[Tab]]]),
+
+ ?match_receive(timeout), %% op waits for locks occupied by A
+
+ A ! end_trans, %% Kill A, locks should be released
+ ?match_receive({A,{atomic,end_trans}}),
+
+ receive
+ Msg -> ?match({Pid, {atomic, ok}}, Msg)
+ after
+ timer:seconds(20) -> ?error("Operation timed out", [])
+ end,
+
+ sys:get_status(whereis(mnesia_locker)), % Explicit sync, release locks is async
+ ?match([], mnesia:system_info(held_locks)),
+ ?match([], mnesia:system_info(lock_queue)),
+ ok.
+
+op(Father, Mod, Fun, Args) ->
+ Res = apply(Mod, Fun, Args),
+ Father ! {self(), Res}.
+
+insert(_Tab, 0) -> ok;
+insert(Tab, N) when N > 0 ->
+ ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N, 0}) end),
+ insert(Tab, N-1).
+
+extra_admin_tests(suite) ->
+ [del_table_copy_1,
+ del_table_copy_2,
+ del_table_copy_3,
+ add_table_copy_1,
+ add_table_copy_2,
+ add_table_copy_3,
+ add_table_copy_4,
+ move_table_copy_1,
+ move_table_copy_2,
+ move_table_copy_3,
+ move_table_copy_4].
+
+update_own(Tab, Key, Acc) ->
+ Update =
+ fun() ->
+ Res = mnesia:read({Tab, Key}),
+ case Res of
+ [{Tab, Key, Extra, Acc}] ->
+ mnesia:write({Tab,Key,Extra, Acc+1});
+ Val ->
+ {read, Val, {acc, Acc}}
+ end
+ end,
+ receive
+ {Pid, quit} -> Pid ! {self(), Acc}
+ after
+ 0 ->
+ case mnesia:transaction(Update) of
+ {atomic, ok} ->
+ update_own(Tab, Key, Acc+1);
+ Else ->
+ ?error("Trans failed on ~p with ~p~n"
+ "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n",
+ [node(),
+ Else,
+ mnesia:table_info(Tab, where_to_read),
+ mnesia:table_info(Tab, where_to_write),
+ mnesia:table_info(Tab, where_to_commit),
+ mnesia:table_info(Tab, storage_type)])
+ end
+ end.
+
+update_shared(Tab, Me, Acc) ->
+ Update =
+ fun() ->
+ W2R = mnesia:table_info(Tab, where_to_read),
+ Res = mnesia:read({Tab, 0}),
+ case Res of
+ [{Tab, Key, Extra, Val}] when element(Me, Extra) == Acc ->
+ Extra1 = setelement(Me, Extra, Acc+1),
+ Term = {Tab, Key, Extra1, Val+1},
+ ok = mnesia:write(Term),
+% ?log("At ~p: ~p w2r ~p w2w ~p ~n",
+% [node(), Term,
+% mnesia:table_info(Tab, where_to_read),
+ W2W = mnesia:table_info(Tab, where_to_write),
+ W2C = mnesia:table_info(Tab, where_to_commit),
+%% mnesia:table_info(Tab, storage_type)
+% ]),
+ {_Mod, Tid, Ts} = get(mnesia_activity_state),
+ io:format("~p ~p~n", [Tid, ets:tab2list(element(2,Ts))]),
+ {ok,Term,{W2R,W2W,W2C}};
+ Val ->
+ Info = [{acc, Acc}, {me, Me},
+ {tid, element(2, mnesia:get_activity_id())},
+ {locks, mnesia:system_info(held_locks)}],
+ {read, Val, Info}
+ end
+ end,
+ receive
+ {Pid, quit} -> Pid ! {self(), Acc}
+ after
+ 0 ->
+ case mnesia:transaction(Update) of
+ {atomic, {ok,Term,W2}} ->
+ io:format("~p:~p:(~p,~p) ~w@~w~n", [erlang:now(),node(),Me,Acc,Term,W2]),
+ update_shared(Tab, Me, Acc+1);
+ Else ->
+ ?error("Trans failed on ~p with ~p~n"
+ "Info w2read ~p w2write ~p w2commit ~p storage ~p ~n",
+ [node(),
+ Else,
+ mnesia:table_info(Tab, where_to_read),
+ mnesia:table_info(Tab, where_to_write),
+ mnesia:table_info(Tab, where_to_commit),
+ mnesia:table_info(Tab, storage_type)
+ ])
+ end
+ end.
+
+init_admin(Def, N1, N2, N3) ->
+ Tab = schema_ops,
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert(Tab, 1002),
+
+ Pid1 = spawn_link(N1, ?MODULE, update_own, [Tab, 1, 0]),
+ Pid2 = spawn_link(N2, ?MODULE, update_own, [Tab, 2, 0]),
+ Pid3 = spawn_link(N3, ?MODULE, update_own, [Tab, 3, 0]),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 0, {0,0,0}, 0}) end)),
+
+ Pid4 = spawn_link(N1, ?MODULE, update_shared, [Tab, 1, 0]),
+ Pid5 = spawn_link(N2, ?MODULE, update_shared, [Tab, 2, 0]),
+ Pid6 = spawn_link(N3, ?MODULE, update_shared, [Tab, 3, 0]),
+
+ {Pid1, Pid2, Pid3, Pid4, Pid5, Pid6}.
+
+verify_results({P1, P2, P3, P4, P5, P6}) ->
+ Tab = schema_ops, N1 = node(P1), N2 = node(P2), N3 = node(P3),
+
+ try
+ P1 ! {self(), quit},
+ R1 = receive {P1, Res1} -> Res1 after 9000 -> throw({timeout,P1}) end,
+ P2 ! {self(), quit},
+ R2 = receive {P2, Res2} -> Res2 after 9000 -> throw({timeout,P2}) end,
+ P3 ! {self(), quit},
+ R3 = receive {P3, Res3} -> Res3 after 9000 -> throw({timeout,P3}) end,
+
+ P4 ! {self(), quit},
+ R4 = receive {P4, Res4} -> Res4 after 9000 -> throw({timeout,P4}) end,
+ P5 ! {self(), quit},
+ R5 = receive {P5, Res5} -> Res5 after 9000 -> throw({timeout,P5}) end,
+ P6 ! {self(), quit},
+ R6 = receive {P6, Res6} -> Res6 after 9000 -> throw({timeout,P6}) end,
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write_lock_table(Tab) end)),
+ ?log("Should be ~p~n", [R1]),
+ ?match([{_, _, _, R1}], rpc:call(N1, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{_, _, _, R1}], rpc:call(N2, mnesia, dirty_read, [{Tab, 1}])),
+ ?match([{_, _, _, R1}], rpc:call(N3, mnesia, dirty_read, [{Tab, 1}])),
+ ?log("Should be ~p~n", [R2]),
+ ?match([{_, _, _, R2}], rpc:call(N1, mnesia, dirty_read, [{Tab, 2}])),
+ ?match([{_, _, _, R2}], rpc:call(N2, mnesia, dirty_read, [{Tab, 2}])),
+ ?match([{_, _, _, R2}], rpc:call(N3, mnesia, dirty_read, [{Tab, 2}])),
+ ?log("Should be ~p~n", [R3]),
+ ?match([{_, _, _, R3}], rpc:call(N1, mnesia, dirty_read, [{Tab, 3}])),
+ ?match([{_, _, _, R3}], rpc:call(N2, mnesia, dirty_read, [{Tab, 3}])),
+ ?match([{_, _, _, R3}], rpc:call(N3, mnesia, dirty_read, [{Tab, 3}])),
+
+ Res = R4+R5+R6,
+ ?log("Should be {~p+~p+~p}= ~p~n", [R4, R5, R6, Res]),
+ ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N1, mnesia, dirty_read, [{Tab, 0}])),
+ ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N2, mnesia, dirty_read, [{Tab, 0}])),
+ ?match([{_, _, {R4,R5,R6}, Res}], rpc:call(N3, mnesia, dirty_read, [{Tab, 0}]))
+ catch throw:{timeout, Pid} ->
+ mnesia_lib:dist_coredump(),
+ ?error("Timeout ~p ~n", [Pid])
+ end.
+
+
+get_info(Tab) ->
+ Info = mnesia:table_info(Tab, all),
+ mnesia_lib:verbose("~p~n", [Info]).
+
+del_table_copy_1(suite) -> [];
+del_table_copy_1(Config) when is_list(Config) ->
+ [_Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config),
+ del_table(Node2, Node2, Nodes). %Called on same Node as deleted
+del_table_copy_2(suite) -> [];
+del_table_copy_2(Config) when is_list(Config) ->
+ [Node1, Node2, _Node3] = Nodes = ?acquire_nodes(3, Config),
+ del_table(Node1, Node2, Nodes). %Called from other Node
+del_table_copy_3(suite) -> [];
+del_table_copy_3(Config) when is_list(Config) ->
+ [_Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ del_table(Node3, Node2, Nodes). %Called from Node w.o. table
+
+%%% The actual test
+del_table(CallFrom, DelNode, [Node1, Node2, Node3]) ->
+ Def = [{ram_copies, [Node1]}, {disc_copies, [Node2]},
+ {attributes, [key, attr1, attr2]}],
+ Tab = schema_ops,
+ Pids = init_admin(Def, Node1, Node2, Node3),
+
+ ?log("Call from ~p delete table from ~p ~n", [CallFrom, DelNode]),
+ rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
+
+ ?match({atomic, ok},
+ rpc:call(CallFrom, mnesia, del_table_copy, [Tab, DelNode])),
+
+ verify_results(Pids),
+ rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
+ ?verify_mnesia([Node1, Node2, Node3], []).
+
+add_table_copy_1(suite) -> [];
+add_table_copy_1(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_only_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node1, Node3, Nodes, Def).
+add_table_copy_2(suite) -> [];
+add_table_copy_2(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_only_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node2, Node3, Nodes, Def).
+add_table_copy_3(suite) -> [];
+add_table_copy_3(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_only_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node3, Node3, Nodes, Def).
+add_table_copy_4(suite) -> [];
+add_table_copy_4(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_only_copies, [Node1]},
+ {attributes, [key, attr1, attr2]}],
+ add_table(Node2, Node3, Nodes, Def).
+%%% The actual test
+add_table(CallFrom, AddNode, [Node1, Node2, Node3], Def) ->
+ Pids = init_admin(Def, Node1, Node2, Node3),
+ Tab = schema_ops,
+ ?log("Call from ~p add table to ~p ~n", [CallFrom, AddNode]),
+ rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
+ ?match({atomic, ok}, rpc:call(CallFrom, mnesia, add_table_copy,
+ [Tab, AddNode, ram_copies])),
+ verify_results(Pids),
+ rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
+ ?verify_mnesia([Node1, Node2, Node3], []).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+move_table_copy_1(suite) -> [];
+move_table_copy_1(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node1, Node1, Node3, Nodes, Def).
+move_table_copy_2(suite) -> [];
+move_table_copy_2(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node2, Node1, Node3, Nodes, Def).
+move_table_copy_3(suite) -> [];
+move_table_copy_3(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_copies, [Node1, Node2]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node3, Node1, Node3, Nodes, Def).
+move_table_copy_4(suite) -> [];
+move_table_copy_4(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Def = [{disc_copies, [Node1]},
+ {attributes, [key, attr1, attr2]}],
+ move_table(Node2, Node1, Node3, Nodes, Def).
+%%% The actual test
+move_table(CallFrom, FromNode, ToNode, [Node1, Node2, Node3], Def) ->
+ Pids = init_admin(Def, Node1, Node2, Node3),
+ Tab = schema_ops,
+ ?log("Call from ~p move table from ~p to ~p ~n", [CallFrom, FromNode, ToNode]),
+ rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
+ ?match({atomic, ok}, rpc:call(CallFrom, mnesia, move_table_copy,
+ [Tab, FromNode, ToNode])),
+ verify_results(Pids),
+ rpc:multicall([Node1, Node2, Node3], ?MODULE, get_info, [Tab]),
+ ?verify_mnesia([Node1, Node2, Node3], []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+visibility(doc) ->
+ ["Verify the visibility semantics for various configurations"];
+visibility(suite) ->
+ [
+ dirty_updates_visible_direct,
+ dirty_reads_regardless_of_trans,
+ trans_update_invisibible_outside_trans,
+ trans_update_visible_inside_trans,
+ write_shadows,
+ delete_shadows,
+%% delete_shadows2,
+ write_delete_shadows_bag,
+ write_delete_shadows_bag2,
+ iteration,
+ shadow_search,
+ snmp_shadows
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+dirty_updates_visible_direct(doc) ->
+ ["One process can immediately see dirty updates of another"];
+dirty_updates_visible_direct(suite) -> [];
+dirty_updates_visible_direct(Config) when is_list(Config) ->
+ dirty_visibility(outside_trans, Config).
+
+dirty_reads_regardless_of_trans(doc) ->
+ ["Dirty reads are not affected by transaction context"];
+dirty_reads_regardless_of_trans(suite) -> [];
+dirty_reads_regardless_of_trans(Config) when is_list(Config) ->
+ dirty_visibility(inside_trans, Config).
+
+dirty_visibility(Mode, Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = list_to_atom(lists:concat([dirty_visibility, '_', Mode])),
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab}, {ram_copies, [Node1]}])),
+ ValPos = 3,
+
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ %% Start two processes
+ {success, [A]} = ?start_activities([Node1]),
+
+ case Mode of
+ inside_trans ->
+ ?start_transactions([A]),
+ A ! fun() ->
+ mnesia:write({Tab, a, 11}),
+ mnesia:write({Tab, b, 22}),
+ mnesia:write({Tab, c, 1}),
+ mnesia:write({Tab, d, 2}),
+ mnesia:write({Tab, e, 3}),
+ lists:sort(mnesia:all_keys(Tab))
+ end,
+ ?match_receive({A, [a, b, c, d, e]});
+ outside_trans ->
+ ignore
+ end,
+
+ RecA = {Tab, a, 1},
+ PatA = {Tab, '$1', 1},
+ RecB = {Tab, b, 3},
+ PatB = {Tab, '$1', 3},
+ RecB2 = {Tab, b, 2},
+ PatB2 = {Tab, '$1', 2},
+ ?match([], mnesia:dirty_read({Tab, a})),
+ ?match([], mnesia:dirty_read({Tab, b})),
+ ?match([], mnesia:dirty_match_object(PatA)),
+ ?match([], mnesia:dirty_match_object(PatB)),
+ ?match([], mnesia:dirty_match_object(PatB2)),
+ ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)),
+ ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatA, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatB, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)),
+ ?match('$end_of_table', mnesia:dirty_first(Tab)),
+
+ %% dirty_write
+ A ! fun() -> mnesia:dirty_write(RecA) end,
+ ?match_receive({A, ok}),
+ ?match([RecA], mnesia:dirty_read({Tab, a})),
+ ?match([RecA], mnesia:dirty_match_object(PatA)),
+ ?match(a, mnesia:dirty_first(Tab)),
+ ?match([RecA], mnesia:dirty_index_read(Tab, 1, ValPos)),
+ ?match([RecA], mnesia:dirty_index_match_object(PatA, ValPos)),
+ ?match('$end_of_table', mnesia:dirty_next(Tab, a)),
+
+ %% dirty_create
+ A ! fun() -> mnesia:dirty_write(RecB) end,
+ ?match_receive({A, ok}),
+ ?match([RecB], mnesia:dirty_read({Tab, b})),
+ ?match([RecB], mnesia:dirty_match_object(PatB)),
+ ?match([RecB], mnesia:dirty_index_read(Tab, 3, ValPos)),
+ ?match([RecB], mnesia:dirty_index_match_object(PatB, ValPos)),
+ ?match('$end_of_table',
+ mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))),
+
+ %% dirty_update_counter
+ A ! fun() -> mnesia:dirty_update_counter({Tab, b}, -1) end,
+ ?match_receive({A, _}),
+ ?match([RecB2], mnesia:dirty_read({Tab, b})),
+ ?match([], mnesia:dirty_match_object(PatB)),
+ ?match([RecB2], mnesia:dirty_match_object(PatB2)),
+ ?match([RecB2], mnesia:dirty_index_read(Tab, 2, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatB, ValPos)),
+ ?match([RecB2], mnesia:dirty_index_match_object(PatB2, ValPos)),
+ ?match('$end_of_table',
+ mnesia:dirty_next(Tab, mnesia:dirty_next(Tab, mnesia:dirty_first(Tab)))),
+
+ %% dirty_delete
+ A ! fun() -> mnesia:dirty_delete({Tab, b}) end,
+ ?match_receive({A, ok}),
+ ?match([], mnesia:dirty_read({Tab, b})),
+ ?match([], mnesia:dirty_match_object(PatB2)),
+ ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatB2, ValPos)),
+ ?match(a, mnesia:dirty_first(Tab)),
+ ?match('$end_of_table', mnesia:dirty_next(Tab, a)),
+
+ %% dirty_delete_object
+ ?match([RecA], mnesia:dirty_match_object(PatA)),
+ A ! fun() -> mnesia:dirty_delete_object(RecA) end,
+ ?match_receive({A, ok}),
+ ?match([], mnesia:dirty_read({Tab, a})),
+ ?match([], mnesia:dirty_match_object(PatA)),
+ ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatA, ValPos)),
+ ?match('$end_of_table', mnesia:dirty_first(Tab)),
+
+ case Mode of
+ inside_trans ->
+ A ! end_trans,
+ ?match_receive({A, {atomic, end_trans}});
+ outside_trans ->
+ ignore
+ end,
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+trans_update_invisibible_outside_trans(doc) ->
+ ["Updates in a transaction are invisible outside the transaction"];
+trans_update_invisibible_outside_trans(suite) -> [];
+trans_update_invisibible_outside_trans(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = trans_update_invisibible_outside_trans,
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]}])),
+ ValPos = 3,
+ RecA = {Tab, a, 1},
+ PatA = {Tab, '$1', 1},
+ RecB = {Tab, b, 3},
+ PatB = {Tab, '$1', 3},
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ Verify =
+ fun() ->
+ ?match([], mnesia:dirty_read({Tab, a})),
+ ?match([], mnesia:dirty_read({Tab, b})),
+ ?match([], mnesia:dirty_match_object(PatA)),
+ ?match([], mnesia:dirty_match_object(PatB)),
+ ?match([], mnesia:dirty_index_read(Tab, 1, ValPos)),
+ ?match([], mnesia:dirty_index_read(Tab, 3, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatA, ValPos)),
+ ?match([], mnesia:dirty_index_match_object(PatB, ValPos)),
+ ?match('$end_of_table', mnesia:dirty_first(Tab))
+ end,
+
+ Fun = fun() ->
+ ?match(ok, mnesia:write(RecA)),
+ Verify(),
+
+ ?match(ok, mnesia:write(RecB)),
+ Verify(),
+
+ ?match(ok, mnesia:delete({Tab, b})),
+ Verify(),
+
+ ?match([RecA], mnesia:match_object(PatA)),
+ Verify(),
+
+ ?match(ok, mnesia:delete_object(RecA)),
+ Verify(),
+ ok
+ end,
+ ?match({atomic, ok}, mnesia:transaction(Fun)),
+ Verify(),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+trans_update_visible_inside_trans(doc) ->
+ ["Updates in a transaction are visible in the same transaction"];
+trans_update_visible_inside_trans(suite) -> [];
+trans_update_visible_inside_trans(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = trans_update_visible_inside_trans,
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]}])),
+ ValPos = 3,
+ RecA = {Tab, a, 1},
+ PatA = {Tab, '$1', 1},
+ RecB = {Tab, b, 3},
+ PatB = {Tab, '$1', 3},
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ Fun = fun() ->
+ %% write
+ ?match(ok, mnesia:write(RecA)),
+ ?match([RecA], mnesia:read({Tab, a})),
+ ?match([RecA], mnesia:wread({Tab, a})),
+ ?match([RecA], mnesia:match_object(PatA)),
+ ?match([a], mnesia:all_keys(Tab)),
+ ?match([RecA], mnesia:index_match_object(PatA, ValPos)),
+ ?match([RecA], mnesia:index_read(Tab, 1, ValPos)),
+
+ %% create
+ ?match(ok, mnesia:write(RecB)),
+ ?match([RecB], mnesia:read({Tab, b})),
+ ?match([RecB], mnesia:wread({Tab, b})),
+ ?match([RecB], mnesia:match_object(PatB)),
+ ?match([RecB], mnesia:index_match_object(PatB, ValPos)),
+ ?match([RecB], mnesia:index_read(Tab, 3, ValPos)),
+
+ %% delete
+ ?match(ok, mnesia:delete({Tab, b})),
+ ?match([], mnesia:read({Tab, b})),
+ ?match([], mnesia:wread({Tab, b})),
+ ?match([], mnesia:match_object(PatB)),
+ ?match([a], mnesia:all_keys(Tab)),
+ ?match([], mnesia:index_match_object(PatB, ValPos)),
+ ?match([], mnesia:index_read(Tab, 2, ValPos)),
+ ?match([], mnesia:index_read(Tab, 3, ValPos)),
+
+ %% delete_object
+ ?match(ok, mnesia:delete_object(RecA)),
+ ?match([], mnesia:read({Tab, a})),
+ ?match([], mnesia:wread({Tab, a})),
+ ?match([], mnesia:match_object(PatA)),
+ ?match([], mnesia:all_keys(Tab)),
+ ?match([], mnesia:index_match_object(PatA, ValPos)),
+ ?match([], mnesia:index_read(Tab, 2, ValPos)),
+ ?match([], mnesia:index_read(Tab, 3, ValPos)),
+ ok
+ end,
+ ?match({atomic, ok}, mnesia:transaction(Fun)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+write_shadows(doc) ->
+ ["Tests whether the shadow shows the correct object when",
+ "writing to the table"];
+write_shadows(suite) -> [];
+write_shadows(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = write_shadows,
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]},
+ {type, set}])),
+ ValPos = 3,
+ RecA1 = {Tab, a, 1},
+ PatA1 = {Tab, '$1', 1},
+ RecA2 = {Tab, a, 2},
+ PatA2 = {Tab, '$1', 2},
+
+
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ Fun1 = fun() ->
+ ?match(ok, mnesia:write(RecA1)),
+ ok
+ end,
+
+ ?match({atomic, ok}, mnesia:transaction(Fun1)),
+
+ Fun2 = fun() ->
+ %% write shadow old write - is the confirmed value visable
+ %% in the shadow ?
+ ?match([RecA1], mnesia:read({Tab, a})),
+ ?match([RecA1], mnesia:wread({Tab, a})),
+ ?match([RecA1], mnesia:match_object(PatA1)),
+ ?match([a], mnesia:all_keys(Tab)),
+ ?match([RecA1], mnesia:index_match_object(PatA1, ValPos)),
+ ?match([RecA1], mnesia:index_read(Tab, 1, ValPos)),
+
+ %% write shadow new write - is a new value visable instead
+ %% of the old value ?
+ ?match(ok, mnesia:write(RecA2)),
+
+ ?match([RecA2], mnesia:read({Tab, a})),
+ ?match([RecA2], mnesia:wread({Tab, a})),
+ ?match([RecA2], mnesia:match_object(PatA2)), %% delete shadow old but not new write - is the new value visable
+
+ ?match([a], mnesia:all_keys(Tab)),
+ ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
+ ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
+ ok
+
+ end,
+ ?match({atomic, ok}, mnesia:transaction(Fun2)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+delete_shadows(doc) ->
+ ["Test whether the shadow shows the correct object when deleting objects"];
+delete_shadows(suite) -> [];
+delete_shadows(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = delete_shadows,
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]},
+ {type, set}])),
+ ValPos = 3,
+ OidA = {Tab, a},
+ RecA1 = {Tab, a, 1},
+ PatA1 = {Tab, '$1', 1},
+ RecA2 = {Tab, a, 2},
+ PatA2 = {Tab, '$1', 2},
+
+
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ Fun1 = fun() ->
+ ?match(ok, mnesia:write(RecA1)),
+ ok
+ end,
+
+ ?match({atomic, ok}, mnesia:transaction(Fun1)),
+
+ Fun2 = fun() ->
+
+
+ %% delete shadow old write - is the confirmed value invisible
+ %% when deleted in the transaction ?
+ ?match(ok, mnesia:delete(OidA)),
+
+ ?match([], mnesia:read({Tab, a})),
+ ?match([], mnesia:wread({Tab, a})),
+ ?match([], mnesia:match_object(PatA1)),
+ ?match([], mnesia:all_keys(Tab)),
+ ?match([], mnesia:index_match_object(PatA1, ValPos)),
+ ?match([], mnesia:index_read(Tab, 1, ValPos)),
+
+ %% delete shadow old but not new write - is the new value visable
+ %% when the old one was deleted ?
+ ?match(ok, mnesia:write(RecA2)),
+
+ ?match([RecA2], mnesia:read({Tab, a})),
+ ?match([RecA2], mnesia:wread({Tab, a})),
+ ?match([RecA2], mnesia:match_object(PatA2)),
+ ?match([a], mnesia:all_keys(Tab)),
+ ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
+ ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
+
+ %% delete shadow old and new write - is the new value invisable
+ %% when deleted ?
+ ?match(ok, mnesia:delete(OidA)),
+
+ ?match([], mnesia:read({Tab, a})),
+ ?match([], mnesia:wread({Tab, a})),
+ ?match([], mnesia:match_object(PatA2)),
+ ?match([], mnesia:all_keys(Tab)),
+ ?match([], mnesia:index_match_object(PatA2, ValPos)),
+ ?match([], mnesia:index_read(Tab, 2, ValPos)),
+ ok
+
+ end,
+ ?match({atomic, ok}, mnesia:transaction(Fun2)),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+write_delete_shadows_bag(doc) ->
+ ["Test the visibility of written and deleted objects in an bag type table"];
+write_delete_shadows_bag(suite) -> [];
+write_delete_shadows_bag(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = write_delete_shadows_bag,
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]},
+ {type, bag}])),
+ ValPos = 3,
+ OidA = {Tab, a},
+
+ RecA1 = {Tab, a, 1},
+ PatA1 = {Tab, '$1', 1},
+
+ RecA2 = {Tab, a, 2},
+ PatA2 = {Tab, '$1', 2},
+
+ RecA3 = {Tab, a, 3},
+ PatA3 = {Tab, '$1', 3},
+
+ PatA = {Tab, a, '_'},
+
+
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ Fun1 = fun() ->
+ ?match(ok, mnesia:write(RecA1)),
+ ?match(ok, mnesia:write(RecA2)),
+ ok
+ end,
+
+ ?match({atomic, ok}, mnesia:transaction(Fun1)),
+
+ Fun2 = fun() ->
+ %% delete shadow old write - is the confirmed value invisible
+ %% when deleted in the transaction ?
+ ?match(ok, mnesia:delete_object(RecA1)),
+
+ ?match([RecA2], mnesia:read({Tab, a})),
+ ?match([RecA2], mnesia:wread({Tab, a})),
+ ?match([RecA2], mnesia:match_object(PatA2)),
+ ?match([a], mnesia:all_keys(Tab)),
+ ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
+ ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
+
+ ?match(ok, mnesia:delete(OidA)),
+
+ ?match([], mnesia:read({Tab, a})),
+ ?match([], mnesia:wread({Tab, a})),
+ ?match([], mnesia:match_object(PatA1)),
+ ?match([], mnesia:all_keys(Tab)),
+ ?match([], mnesia:index_match_object(PatA1, ValPos)),
+ ?match([], mnesia:index_read(Tab, 1, ValPos)),
+
+ %% delete shadow old but not new write - are both new value visable
+ %% when the old one was deleted ?
+ ?match(ok, mnesia:write(RecA2)),
+ ?match(ok, mnesia:write(RecA3)),
+
+
+ ?match([RecA2, RecA3], lists:sort(mnesia:read({Tab, a}))),
+ ?match([RecA2, RecA3], lists:sort(mnesia:wread({Tab, a}))),
+ ?match([RecA2], mnesia:match_object(PatA2)),
+ ?match([a], mnesia:all_keys(Tab)),
+ ?match([RecA2, RecA3], lists:sort(mnesia:match_object(PatA))),
+ ?match([RecA2], mnesia:index_match_object(PatA2, ValPos)),
+ ?match([RecA3], mnesia:index_match_object(PatA3, ValPos)),
+ ?match([RecA2], mnesia:index_read(Tab, 2, ValPos)),
+
+ %% delete shadow old and new write - is the new value invisable
+ %% when deleted ?
+ ?match(ok, mnesia:delete(OidA)),
+
+ ?match([], mnesia:read({Tab, a})),
+ ?match([], mnesia:wread({Tab, a})),
+ ?match([], mnesia:match_object(PatA2)),
+ ?match([], mnesia:all_keys(Tab)),
+ ?match([], mnesia:index_match_object(PatA2, ValPos)),
+ ?match([], mnesia:index_read(Tab, 2, ValPos)),
+ ok
+ end,
+ ?match({atomic, ok}, mnesia:transaction(Fun2)),
+ ok.
+
+write_delete_shadows_bag2(doc) ->
+ ["Test the visibility of written and deleted objects in an bag type table "
+ "and verifies the results"];
+write_delete_shadows_bag2(suite) -> [];
+write_delete_shadows_bag2(Config) when is_list(Config) ->
+
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab = w_d_s_b,
+
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab},
+ {ram_copies, [Node1]},
+ {type, bag}])),
+ Del = fun() ->
+ R1 = mnesia:read({Tab, 1}),
+ mnesia:delete({Tab, 1}),
+ R2 = mnesia:read({Tab, 1}),
+ mnesia:write({Tab, 1, 1}),
+ mnesia:write({Tab, 1, 2}),
+ R3 = mnesia:read({Tab, 1}),
+ {R1, R2, R3}
+ end,
+ DelObj = fun() ->
+ R1 = mnesia:read({Tab, 2}),
+ mnesia:delete_object({Tab, 2, 2}),
+ R2 = mnesia:read({Tab, 2}),
+ mnesia:write({Tab, 2, 1}),
+ mnesia:write({Tab, 2, 2}),
+ R3 = mnesia:read({Tab, 2}),
+ {R1, R2, R3}
+ end,
+ Both1 = [{Tab, 1, 1}, {Tab, 1, 2}],
+ Both2 = [{Tab, 2, 1}, {Tab, 2, 2}],
+ ?match({atomic, {[], [], Both1}}, mnesia:transaction(Del)),
+ ?match({atomic, {Both1, [], Both1}}, mnesia:transaction(Del)),
+ ?match({atomic, Both1}, mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
+ ?match({atomic, {[], [], Both2}}, mnesia:transaction(DelObj)),
+ ?match({atomic, {Both2, [{Tab, 2, 1}], Both2}}, mnesia:transaction(DelObj)),
+ ?match({atomic, Both2}, mnesia:transaction(fun() -> mnesia:read({Tab, 2}) end)),
+ ?verify_mnesia([Node1], []).
+
+shadow_search(doc) ->
+ ["Verifies that ordered_set tables are ordered, and the order is kept"
+ "even when table is shadowed by transaction updates"];
+shadow_search(suite) -> [];
+shadow_search(Config) when is_list(Config) ->
+ [Node1] = ?acquire_nodes(1, Config),
+ Tab1 = ss_oset,
+ Tab2 = ss_set,
+ Tab3 = ss_bag,
+ Tabs = [Tab1,Tab2,Tab3],
+ RecName = ss,
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab1},
+ {ram_copies, [Node1]},
+ {record_name, RecName},
+ {type, ordered_set}])),
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab2},
+ {record_name, RecName},
+ {ram_copies, [Node1]},
+ {type, set}])),
+ ?match({atomic, ok}, mnesia:create_table([{name, Tab3},
+ {record_name, RecName},
+ {ram_copies, [Node1]},
+ {type, bag}])),
+ Recs = [{RecName, K, K} || K <- [1,3,5]],
+ [mnesia:dirty_write(Tab1, R) || R <- Recs],
+ [mnesia:dirty_write(Tab2, R) || R <- Recs],
+ [mnesia:dirty_write(Tab3, R) || R <- Recs],
+
+ Match = fun(Tab) -> mnesia:match_object(Tab, {'_','_','_'}, write) end,
+ Select = fun(Tab) -> mnesia:select(Tab, [{'_', [], ['$_']}]) end,
+% Trans = fun(Fun,Args) -> mnesia:transaction(Fun,Args) end,
+ LoopHelp = fun('$end_of_table',_) -> [];
+ ({Res,Cont},Fun) ->
+ Sel = mnesia:select(Cont),
+ Res ++ Fun(Sel, Fun)
+ end,
+ SelLoop = fun(Table) ->
+ Sel = mnesia:select(Table, [{'_', [], ['$_']}], 1, read),
+ LoopHelp(Sel,LoopHelp)
+ end,
+
+ R1 = {RecName, 2, 2}, R2 = {RecName, 4, 4},
+ R3 = {RecName, 2, 3}, R4 = {RecName, 3, 1},
+ R5 = {RecName, 104, 104},
+ W1 = fun(Tab,Search) -> mnesia:write(Tab,R1,write),
+ mnesia:write(Tab,R2,write),
+ Search(Tab)
+ end,
+ S1 = lists:sort([R1,R2|Recs]),
+ ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Select])),
+ ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,Match])),
+ ?match({atomic,S1}, mnesia:transaction(W1, [Tab1,SelLoop])),
+ ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Select]))),
+ ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,SelLoop]))),
+ ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab2,Match]))),
+ ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Select]))),
+ ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,SelLoop]))),
+ ?match({atomic,S1}, sort_res(mnesia:transaction(W1, [Tab3,Match]))),
+ [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R2], Tab <- Tabs],
+
+ W2 = fun(Tab,Search) ->
+ mnesia:write(Tab,R3,write),
+ mnesia:write(Tab,R1,write),
+ Search(Tab)
+ end,
+ S2 = lists:sort([R1|Recs]),
+ S2Bag = lists:sort([R1,R3|Recs]),
+ ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Select])),
+ ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,SelLoop])),
+ ?match({atomic,S2}, mnesia:transaction(W2, [Tab1,Match])),
+ ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Select]))),
+ ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,SelLoop]))),
+ ?match({atomic,S2}, sort_res(mnesia:transaction(W2, [Tab2,Match]))),
+ ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Select]))),
+ ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,SelLoop]))),
+ ?match({atomic,S2Bag}, sort_res(mnesia:transaction(W2, [Tab3,Match]))),
+%% [mnesia:dirty_delete_object(Tab,R) || R <- [R1,R3], Tab <- Tabs],
+
+ W3 = fun(Tab,Search) ->
+ mnesia:write(Tab,R4,write),
+ mnesia:delete(Tab,element(2,R1),write),
+ Search(Tab)
+ end,
+ S3Bag = lists:sort([R4|lists:delete(R1,Recs)]),
+ S3 = lists:delete({RecName,3,3},S3Bag),
+ ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Select])),
+ ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,SelLoop])),
+ ?match({atomic,S3}, mnesia:transaction(W3, [Tab1,Match])),
+ ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,SelLoop]))),
+ ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Select]))),
+ ?match({atomic,S3}, sort_res(mnesia:transaction(W3, [Tab2,Match]))),
+ ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Select]))),
+ ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,SelLoop]))),
+ ?match({atomic,S3Bag}, sort_res(mnesia:transaction(W3, [Tab3,Match]))),
+
+ W4 = fun(Tab,Search) ->
+ mnesia:delete(Tab,-1,write),
+ mnesia:delete(Tab,4 ,write),
+ mnesia:delete(Tab,17,write),
+ mnesia:delete_object(Tab,{RecName, -1, x},write),
+ mnesia:delete_object(Tab,{RecName, 4, x},write),
+ mnesia:delete_object(Tab,{RecName, 42, x},write),
+ mnesia:delete_object(Tab,R2,write),
+ mnesia:write(Tab, R5, write),
+ Search(Tab)
+ end,
+ S4Bag = lists:sort([R5|S3Bag]),
+ S4 = lists:sort([R5|S3]),
+ ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Select])),
+ ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,SelLoop])),
+ ?match({atomic,S4}, mnesia:transaction(W4, [Tab1,Match])),
+ ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Select]))),
+ ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,SelLoop]))),
+ ?match({atomic,S4}, sort_res(mnesia:transaction(W4, [Tab2,Match]))),
+ ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Select]))),
+ ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,SelLoop]))),
+ ?match({atomic,S4Bag}, sort_res(mnesia:transaction(W4, [Tab3,Match]))),
+ [mnesia:dirty_delete_object(Tab,R) || R <- [{RecName,3,3},R5], Tab <- Tabs],
+
+ %% hmmm anything more??
+
+ ?verify_mnesia([Node1], []).
+
+removed_resources(suite) ->
+ [rr_kill_copy];
+removed_resources(doc) ->
+ ["Verify that the locking behave when resources are removed"].
+
+rr_kill_copy(suite) -> [];
+rr_kill_copy(Config) when is_list(Config) ->
+ Ns = ?acquire_nodes(3,Config ++ [{tc_timeout, 60000}]),
+ DeleteMe = fun(_Tab,Where2read) ->
+ ?match([], mnesia_test_lib:kill_mnesia([Where2read]))
+ end,
+ Del = removed_resources(Ns, DeleteMe),
+ ?verify_mnesia(Ns -- [Del], []).
+
+removed_resources([_N1,N2,N3], DeleteRes) ->
+ Tab = del_res,
+ ?match({atomic, ok}, mnesia:create_table(Tab,[{ram_copies, [N2,N3]}])),
+
+ Init = fun() -> [mnesia:write({Tab,Key,Key}) || Key <- lists:seq(0,99)] end,
+ ?match([], [Bad || Bad <- mnesia:sync_dirty(Init), Bad /= ok]),
+
+ Where2Read = mnesia:table_info(Tab, where_to_read),
+ [Keep] = [N2,N3] -- [Where2Read],
+ Tester = self(),
+
+ Conflict = fun() ->
+ %% Read a value..
+ [{Tab,1,Val}] = mnesia:read({Tab,1}),
+ case get(restart) of
+ undefined ->
+ Tester ! {pid_1, self()},
+ %% Wait for sync, the read value have been
+ %% updated and this function should be restarted.
+ receive {Tester,sync} -> ok end,
+ put(restart, restarted);
+ restarted ->
+ ok
+ end,
+ mnesia:write({Tab,1,Val+10})
+ end,
+ Lucky = fun() ->
+ [{Tab,1,Val}] = mnesia:read({Tab,1}),
+ mnesia:write({Tab,1,Val+100})
+ end,
+
+ CPid = spawn_link(fun() -> Tester ! {self(), mnesia:transaction(Conflict)} end),
+
+ %% sync first transaction
+ receive {pid_1, CPid} -> synced end,
+
+ DeleteRes(Tab, Where2Read),
+
+ ?match(Keep, mnesia:table_info(Tab, where_to_read)),
+
+ %% Run the other/Lucky transaction, this should work since
+ %% it won't grab a lock on the conflicting transactions Where2Read node.
+
+ LPid = spawn_link(Keep, fun() -> Tester ! {self(),mnesia:transaction(Lucky)} end),
+ ?match_receive({LPid,{atomic,ok}}),
+
+ %% Continue Transaction no 1
+ CPid ! {self(), sync},
+
+ ?match(ok, receive {CPid,{atomic,ok}} -> ok after 2000 -> process_info(self()) end),
+
+ ?match({atomic,[{del_res,1,111}]}, mnesia:transaction(fun() -> mnesia:read({Tab,1}) end)),
+ Where2Read.
+
+nasty(suite) -> [];
+
+nasty(doc) ->
+ ["Tries to fullfill a rather nasty locking scenario, where we have had "
+ "bugs, the testcase tries a combination of locks in locker queue"];
+
+%% This testcase no longer works as it was intended to show errors when
+%% tablelocks was allowed to be placed in the queue though locks existed
+%% in the queue with less Tid's. This is no longer allowed and the testcase
+%% has been update.
+
+nasty(Config) ->
+ ?acquire_nodes(1, Config),
+ Tab = nasty,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [])),
+ Coord = self(),
+ Write = fun(Key) ->
+ mnesia:write({Tab, Key, write}),
+ Coord ! {write, Key, self(), mnesia:get_activity_id()},
+ receive
+ continue ->
+ ok
+ end,
+ Coord ! {done, {write, Key}, self()}
+ end,
+
+ Update = fun(Key) ->
+ Coord ! {update, Key, self(), mnesia:get_activity_id()},
+ receive
+ continue ->
+ ok
+ end,
+ mnesia:read({Tab, Key}),
+ mnesia:write({Tab, Key, update}),
+ receive
+ continue ->
+ ok
+ end,
+
+ Coord ! {done, {update, Key}, self()}
+ end,
+
+ TabLock = fun() ->
+ Coord ! {tablock, Tab, self(), mnesia:get_activity_id()},
+ receive
+ continue ->
+ ok
+ end,
+ mnesia:lock({table, Tab}, write),
+ Coord ! {done, {tablock, Tab}, self()}
+ end,
+
+ Up = spawn_link(mnesia, transaction, [Update, [0]]),
+ ?match_receive({update, 0, Up, _Tid}),
+ TL = spawn_link(mnesia, transaction, [TabLock]),
+ ?match_receive({tablock, Tab, _Tl, _Tid}),
+ W0 = spawn_link(mnesia, transaction, [Write, [0]]),
+ ?match_receive({write, 0, W0, _Tid}),
+ W1 = spawn_link(mnesia, transaction, [Write, [1]]),
+ ?match_receive({write, 1, W1, _Tid}),
+
+ %% Nothing should be in msg queue!
+ ?match(timeout, receive A -> A after 1000 -> timeout end),
+ Up ! continue, %% Should be queued
+ ?match(timeout, receive A -> A after 1000 -> timeout end),
+ TL ! continue, %% Should be restarted
+% ?match({tablock, _, _, _}, receive A -> A after 1000 -> timeout end),
+ ?match(timeout, receive A -> A after 1000 -> timeout end),
+
+ LQ1 = mnesia_locker:get_lock_queue(),
+ ?match({2, _}, {length(LQ1), LQ1}),
+ W0 ! continue, % Up should be in queue
+ ?match_receive({done, {write, 0}, W0}),
+ ?match_receive({'EXIT', W0, normal}),
+
+ TL ! continue, % Should stay in queue W1
+ ?match(timeout, receive A -> A after 1000 -> timeout end),
+ Up ! continue, % Should stay in queue (TL got higher tid)
+ ?match(timeout, receive A -> A after 1000 -> timeout end),
+
+ LQ2 = mnesia_locker:get_lock_queue(),
+ ?match({2, _}, {length(LQ2), LQ2}),
+
+ W1 ! continue,
+ ?match_receive({done, {write, 1}, W1}),
+ get_exit(W1),
+ get_exit(TL),
+ ?match_receive({done, {tablock,Tab}, TL}),
+ get_exit(Up),
+ ?match_receive({done, {update, 0}, Up}),
+
+ ok.
+
+get_exit(Pid) ->
+ receive
+ {'EXIT', Pid, normal} ->
+ ok
+ after 10000 ->
+ ?error("Timeout EXIT ~p~n", [Pid])
+ end.
+
+iteration(doc) ->
+ ["Verify that the updates before/during iteration are visable "
+ "and that the order is preserved for ordered_set tables"];
+iteration(suite) ->
+ [foldl,first_next].
+
+foldl(doc) ->
+ [""];
+foldl(suite) ->
+ [];
+foldl(Config) when is_list(Config) ->
+ Nodes = [_,N2] = ?acquire_nodes(2, Config),
+ Tab1 = foldl_local,
+ Tab2 = foldl_remote,
+ Tab3 = foldl_ordered,
+ Tab11 = foldr_local,
+ Tab21 = foldr_remote,
+ Tab31 = foldr_ordered,
+ ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}, {type, bag}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes},
+ {type, ordered_set}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab11, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab21, [{ram_copies, [N2]}, {type, bag}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab31, [{ram_copies, Nodes},
+ {type, ordered_set}])),
+
+
+ Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 10)],
+ Tab2Els = [{Tab2, 1, 2} | [{Tab2, N, N} || N <- lists:seq(1, 10)]],
+ Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 10)],
+ Tab11Els = [{Tab11, N, N} || N <- lists:seq(1, 10)],
+ Tab21Els = [{Tab21, 1, 2} | [{Tab21, N, N} || N <- lists:seq(1, 10)]],
+ Tab31Els = [{Tab31, N, N} || N <- lists:seq(1, 10)],
+
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab11Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab21Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab31Els],
+
+ Get = fun(E, A) -> [E | A] end,
+
+ %% Before
+ AddB = fun(Tab, Func) ->
+ mnesia:write({Tab, 0, 0}),
+ mnesia:write({Tab, 1, 0}),
+ mnesia:write({Tab, 11, 0}),
+ mnesia:Func(Get, [], Tab)
+ end,
+ AddT1 = [{Tab1, 0, 0}, {Tab1, 1, 0}] ++ tl(Tab1Els) ++ [{Tab1, 11, 0}],
+ AddT2 = lists:sort([{Tab2, 0, 0}, {Tab2, 1, 0}] ++ Tab2Els ++ [{Tab2, 11, 0}]),
+ AddT3 = [{Tab3, 0, 0}, {Tab3, 1, 0}] ++ tl(Tab3Els) ++ [{Tab3, 11, 0}],
+ AddT11 = [{Tab11, 0, 0}, {Tab11, 1, 0}] ++ tl(Tab11Els) ++ [{Tab11, 11, 0}],
+ AddT21 = lists:sort([{Tab21, 0, 0}, {Tab21, 1, 0}] ++ Tab21Els ++ [{Tab21, 11, 0}]),
+ AddT31 = [{Tab31, 0, 0}, {Tab31, 1, 0}] ++ tl(Tab31Els) ++ [{Tab31, 11, 0}],
+
+ ?match({atomic, AddT1}, sort_res(mnesia:transaction(AddB, [Tab1, foldl]))),
+ ?match({atomic, AddT2}, sort_res(mnesia:transaction(AddB, [Tab2, foldl]))),
+ ?match({atomic, AddT3}, rev_res(mnesia:transaction(AddB, [Tab3, foldl]))),
+ ?match({atomic, AddT11}, sort_res(mnesia:transaction(AddB, [Tab11, foldr]))),
+ ?match({atomic, AddT21}, sort_res(mnesia:transaction(AddB, [Tab21, foldr]))),
+ ?match({atomic, AddT31}, mnesia:transaction(AddB, [Tab31, foldr])),
+
+ ?match({atomic, ok}, mnesia:create_table(copy, [{ram_copies, [N2]},
+ {record_name, Tab1}])),
+ CopyRec = fun(NewRec, Acc) ->
+ %% OTP-5495
+ W = fun() -> mnesia:write(copy, NewRec, write), [NewRec| Acc] end,
+ {atomic,Res} = sort_res(mnesia:transaction(W)),
+ Res
+ end,
+ Copy = fun() ->
+ AddT1 = mnesia:foldl(CopyRec, [], Tab1),
+ AddT1 = sort_res(mnesia:foldl(Get, [], copy))
+ end,
+ ?match({atomic, AddT1}, sort_res(mnesia:transaction(Copy))),
+
+ Del = fun(E, A) -> mnesia:delete_object(E), [E|A] end,
+ DelD = fun(Tab) ->
+ mnesia:write({Tab, 12, 12}),
+ mnesia:delete({Tab, 0}),
+ mnesia:foldr(Del, [], Tab),
+ mnesia:foldl(Get, [], Tab)
+ end,
+ ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab1]))),
+ ?match({atomic, []}, sort_res(mnesia:transaction(DelD, [Tab2]))),
+ ?match({atomic, []}, rev_res(mnesia:transaction(DelD, [Tab3]))),
+
+ ListWrite = fun(Tab) -> %% OTP-3893
+ mnesia:write({Tab, [12], 12}),
+ mnesia:foldr(Get, [], Tab)
+ end,
+ ?match({atomic, [{Tab1, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab1]))),
+ ?match({atomic, [{Tab2, [12], 12}]}, sort_res(mnesia:transaction(ListWrite, [Tab2]))),
+ ?match({atomic, [{Tab3, [12], 12}]}, rev_res(mnesia:transaction(ListWrite, [Tab3]))),
+
+ ?verify_mnesia(Nodes, []).
+
+sort_res({atomic, List}) when is_list(List) ->
+ {atomic, lists:sort(List)};
+sort_res(Else) when is_list(Else) ->
+ lists:sort(Else);
+sort_res(Else) ->
+ Else.
+
+rev_res({atomic, List}) ->
+ {atomic, lists:reverse(List)};
+rev_res(Else) ->
+ Else.
+
+
+first_next(doc) -> [""];
+first_next(suite) -> [];
+first_next(Config) when is_list(Config) ->
+ Nodes = [_,N2] = ?acquire_nodes(2, Config),
+ Tab1 = local,
+ Tab2 = remote,
+ Tab3 = ordered,
+ Tab4 = bag,
+ Tabs = [Tab1,Tab2,Tab3,Tab4],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab1, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, [{ram_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, [{ram_copies, Nodes},
+ {type, ordered_set}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab4, [{ram_copies, Nodes},
+ {type, bag}])),
+
+ %% Some Helpers
+ Trans = fun(Fun) -> mnesia:transaction(Fun) end,
+ Continue = fun(first) -> next;
+ (last) -> prev
+ end,
+ LoopHelp = fun('$end_of_table',_,_,_Fun) -> [];
+ (Key,Tab,Op,Fun) ->
+ Next = mnesia:Op(Tab,Key),
+ [Next |Fun(Next,Tab,Op,Fun)]
+ end,
+ Loop = fun(Tab,Start) ->
+ First = mnesia:Start(Tab),
+ Res = [First|LoopHelp(First,Tab,Continue(Start),LoopHelp)],
+ case mnesia:table_info(Tab, type) of
+ ordered_set when Start == first -> Res;
+ ordered_set ->
+ {L1,L2} = lists:split(length(Res)-1,Res),
+ lists:reverse(L1) ++ L2;
+ _ -> lists:sort(Res)
+ end
+ end,
+
+ %% Verify empty tables
+ [?match({atomic, ['$end_of_table']},
+ Trans(fun() -> Loop(Tab,first) end))
+ || Tab <- Tabs],
+ [?match({atomic, ['$end_of_table']},
+ Trans(fun() -> Loop(Tab,last) end))
+ || Tab <- Tabs],
+ %% Verify that trans write is visible inside trans
+ [?match({atomic, [0,10,'$end_of_table']},
+ Trans(fun() ->
+ mnesia:write({Tab,0,0}),
+ mnesia:write({Tab,10,10}),
+ Loop(Tab,first) end))
+ || Tab <- Tabs],
+ [?match({atomic, ['$end_of_table']},
+ Trans(fun() ->
+ mnesia:delete({Tab,0}),
+ mnesia:delete({Tab,10}),
+ Loop(Tab,first) end))
+ || Tab <- Tabs],
+
+ [?match({atomic, [0,10,'$end_of_table']},
+ Trans(fun() ->
+ mnesia:write({Tab,0,0}),
+ mnesia:write({Tab,10,10}),
+ Loop(Tab,last) end))
+ || Tab <- Tabs],
+ [?match({atomic, ['$end_of_table']},
+ Trans(fun() ->
+ mnesia:delete({Tab,0}),
+ mnesia:delete({Tab,10}),
+ Loop(Tab,last) end))
+ || Tab <- Tabs],
+
+ Tab1Els = [{Tab1, N, N} || N <- lists:seq(1, 5)],
+ Tab2Els = [{Tab2, N, N} || N <- lists:seq(1, 5)],
+ Tab3Els = [{Tab3, N, N} || N <- lists:seq(1, 5)],
+ Tab4Els = [{Tab4, 1, 2} | [{Tab4, N, N} || N <- lists:seq(1, 5)]],
+
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab1Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab2Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab3Els],
+ [mnesia:sync_dirty(fun() -> mnesia:write(E) end) || E <- Tab4Els],
+ Keys = lists:sort(mnesia:dirty_all_keys(Tab1)),
+ R1 = Keys++ ['$end_of_table'],
+ [?match({atomic, R1}, Trans(fun() -> Loop(Tab,first) end))
+ || Tab <- Tabs],
+
+ [?match({atomic, R1}, Trans(fun() -> Loop(Tab,last) end))
+ || Tab <- Tabs],
+ R2 = R1 -- [3],
+
+ [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,first) end))
+ || Tab <- Tabs],
+ [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,first) end))
+ || Tab <- Tabs],
+ [?match({atomic, R2}, Trans(fun() -> mnesia:delete({Tab,3}),Loop(Tab,last) end))
+ || Tab <- Tabs],
+ [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,3,3}),Loop(Tab,last) end))
+ || Tab <- Tabs],
+ [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,19}),Loop(Tab,first) end))
+ || Tab <- Tabs],
+ [?match({atomic, R1}, Trans(fun() -> mnesia:write({Tab,4,4}),Loop(Tab,last) end))
+ || Tab <- Tabs],
+
+ ?verify_mnesia(Nodes, []).
+
+
+snmp_shadows(doc) -> [""];
+snmp_shadows(suite) -> [];
+snmp_shadows(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ Tab = snmp_shadows,
+ io:format("With fixstring~n", []),
+ ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{fix_string,integer}}]}])),
+ snmp_shadows_test(Tab),
+ ?match({atomic, ok}, mnesia:delete_table(Tab)),
+ io:format("Without fixstring~n", []),
+ ?match({atomic, ok}, mnesia:create_table(Tab,[{snmp,[{key,{string,integer}}]}])),
+ snmp_shadows_test(Tab),
+ ?verify_mnesia(Nodes, []).
+
+snmp_shadows_test(Tab) ->
+ [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)],
+
+ CheckOrder = fun(A={_,_,{_,_,State}}, Prev) ->
+ ?match({true, A, Prev}, {Prev < A, A, Prev}),
+ {State,A}
+ end,
+ R1 = mnesia:sync_dirty(fun() -> loop_snmp(Tab, []) end),
+ lists:mapfoldl(CheckOrder, {[],foo,foo}, R1),
+ R2 = mnesia:transaction(fun() -> loop_snmp(Tab, []) end),
+ ?match({atomic, R1}, R2),
+
+ Shadow = fun() ->
+ ok = mnesia:write({Tab, {"string",1}, {1,update}}),
+ ok = mnesia:write({Tab, {"string",4}, {4,update}}),
+ ok = mnesia:write({Tab, {"string",6}, {6,update}}),
+ ok = mnesia:delete({Tab, {"string",6}}),
+ ok = mnesia:write({Tab, {"string",9}, {9,update}}),
+ ok = mnesia:write({Tab, {"string",3}, {3,update}}),
+ ok = mnesia:write({Tab, {"string",5}, {5,update}}),
+ [Row5] = mnesia:read({Tab, {"string",5}}),
+ ok = mnesia:delete_object(Row5),
+ loop_snmp(Tab, [])
+ end,
+ R3 = mnesia:sync_dirty(Shadow),
+ {L3,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3),
+ ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L3),
+ ?match({atomic, ok}, mnesia:clear_table(Tab)),
+
+ [mnesia:dirty_write({Tab, {"string", N}, {N, init}}) || N <- lists:seq(2,8,2)],
+ {atomic, R3} = mnesia:transaction(Shadow),
+ {L4,_} = lists:mapfoldl(CheckOrder, {[],foo,foo}, R3),
+ ?match([{1,update},{2,init},{3,update},{4,update},{8,init},{9,update}], L4),
+ ok.
+
+loop_snmp(Tab,Prev) ->
+ case mnesia:snmp_get_next_index(Tab,Prev) of
+ {ok, SKey} ->
+ {{ok,Row},_} = {mnesia:snmp_get_row(Tab, SKey),{?LINE,Prev,SKey}},
+ {{ok,MKey},_} = {mnesia:snmp_get_mnesia_key(Tab,SKey),{?LINE,Prev,SKey}},
+ ?match({[Row],Row,SKey,MKey}, {mnesia:read({Tab,MKey}),Row,SKey,MKey}),
+ [{SKey, MKey, Row} | loop_snmp(Tab, SKey)];
+ endOfTable ->
+ []
+ end.
diff --git a/lib/mnesia/test/mnesia_measure_test.erl b/lib/mnesia/test/mnesia_measure_test.erl
new file mode 100644
index 0000000000..fbf804dbec
--- /dev/null
+++ b/lib/mnesia/test/mnesia_measure_test.erl
@@ -0,0 +1,203 @@
+%%
+%% %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_measure_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).
+
+-define(init(N, Config),
+ mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema],
+ N, Config, ?FILE, ?LINE)).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(doc) ->
+ ["Measure various aspects of Mnesia",
+ "Verify that Mnesia has predictable response times,",
+ "that the transaction system has fair algoritms,",
+ "resource consumption, scalabilitym system limits etc.",
+ "Perform some benchmarks."];
+all(suite) ->
+ [
+ prediction,
+ consumption,
+ scalability,
+ benchmarks
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+prediction(doc) ->
+ ["The system must have predictable response times.",
+ "The maintenance of the system should not impact on the",
+ "availability. Make sure that the response times does not vary too",
+ "much from the undisturbed normal usage.",
+ "Verify that deadlocks never occurs."];
+prediction(suite) ->
+ [
+ reader_disturbed_by_node_down,
+ writer_disturbed_by_node_down,
+ reader_disturbed_by_node_up,
+ writer_disturbed_by_node_up,
+ reader_disturbed_by_schema_ops,
+ writer_disturbed_by_schema_ops,
+ reader_disturbed_by_checkpoint,
+ writer_disturbed_by_checkpoint,
+ reader_disturbed_by_dump_log,
+ writer_disturbed_by_dump_log,
+ reader_disturbed_by_backup,
+ writer_disturbed_by_backup,
+ reader_disturbed_by_restore,
+ writer_disturbed_by_restore,
+ fairness
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+fairness(doc) ->
+ ["Verify that the transaction system behaves fair, even under intense",
+ "stress. Combine different access patterns (transaction profiles)",
+ "in order to verify that concurrent applications gets a fair share",
+ "of the database resource. Verify that starvation never may occur."];
+fairness(suite) ->
+ [
+ reader_competing_with_reader,
+ reader_competing_with_writer,
+ writer_competing_with_reader,
+ writer_competing_with_writer
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+consumption(doc) ->
+ ["Measure the resource consumption and publish the outcome. Make",
+ "sure that resources are released after failures."];
+consumption(suite) ->
+ [
+ measure_resource_consumption,
+ determine_resource_leakage
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+scalability(doc) ->
+ ["Try out where the system limits are. We must at least meet the",
+ "documented system limits.",
+ "Redo the performance meters for various configurations and load,",
+ "especially near system limits."];
+scalability(suite) ->
+ [
+ determine_system_limits,
+ performance_at_min_config,
+ performance_at_max_config,
+ performance_at_full_load,
+ resource_consumption_at_min_config,
+ resource_consumption_at_max_config,
+ resource_consumption_at_full_load
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+benchmarks(doc) ->
+ ["Measure typical database operations and publish them. Try to",
+ "verify that new releases of Mnesia always outperforms old",
+ "releases, or at least that the meters does not get worse."];
+benchmarks(suite) ->
+ [
+ meter,
+ cost,
+ dbn_meters,
+ measure_all_api_functions,
+ tpcb,
+ mnemosyne_vs_mnesia_kernel
+ ].
+
+dbn_meters(suite) -> [];
+dbn_meters(Config) when is_list(Config) ->
+ _Nodes = ?init(3, Config),
+ ?match(ok, mnesia_dbn_meters:start()),
+ ok.
+
+tpcb(suite) ->
+ [
+ ram_tpcb,
+ disc_tpcb,
+ disc_only_tpcb
+ ].
+
+tpcb(ReplicaType, Config) ->
+ HarakiriDelay = {tc_timeout, timer:minutes(20)},
+ Nodes = ?acquire_nodes(2, Config ++ [HarakiriDelay]),
+ Args = [{n_branches, 2},
+ {n_drivers_per_node, 1},
+ {replica_nodes, Nodes},
+ {driver_nodes, [hd(Nodes)]},
+ {use_running_mnesia, true},
+ {use_sticky_locks, true},
+ {replica_type, ReplicaType}],
+ ?match({ok, _}, mnesia_tpcb:start(Args)),
+ ?verify_mnesia(Nodes, []).
+
+ram_tpcb(suite) -> [];
+ram_tpcb(Config) when is_list(Config) ->
+ tpcb(ram_copies, Config).
+
+disc_tpcb(suite) -> [];
+disc_tpcb(Config) when is_list(Config) ->
+ tpcb(disc_copies, Config).
+
+disc_only_tpcb(suite) -> [];
+disc_only_tpcb(Config) when is_list(Config) ->
+ tpcb(disc_only_copies, Config).
+
+meter(suite) ->
+ [
+ ram_meter,
+ disc_meter,
+ disc_only_meter
+ ].
+
+ram_meter(suite) -> [];
+ram_meter(Config) when is_list(Config) ->
+ HarakiriDelay = [{tc_timeout, timer:minutes(20)}],
+ Nodes = ?init(3, Config ++ HarakiriDelay),
+ ?match(ok, mnesia_meter:go(ram_copies, Nodes)).
+
+disc_meter(suite) -> [];
+disc_meter(Config) when is_list(Config) ->
+ HarakiriDelay = [{tc_timeout, timer:minutes(20)}],
+ Nodes = ?init(3, Config ++ HarakiriDelay),
+ ?match(ok, mnesia_meter:go(disc_copies, Nodes)).
+
+disc_only_meter(suite) -> [];
+disc_only_meter(Config) when is_list(Config) ->
+ HarakiriDelay = [{tc_timeout, timer:minutes(20)}],
+ Nodes = ?init(3, Config ++ HarakiriDelay),
+ ?match(ok, mnesia_meter:go(disc_only_copies, Nodes)).
+
+cost(suite) -> [];
+cost(Config) when is_list(Config) ->
+ Nodes = ?init(3, Config),
+ ?match(ok, mnesia_cost:go(Nodes)),
+ file:delete("MNESIA_COST").
diff --git a/lib/mnesia/test/mnesia_meter.erl b/lib/mnesia/test/mnesia_meter.erl
new file mode 100644
index 0000000000..68094c4431
--- /dev/null
+++ b/lib/mnesia/test/mnesia_meter.erl
@@ -0,0 +1,465 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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%
+%%
+
+%%
+%% Getting started:
+%%
+%% 1 Start one or more distributed Erlang nodes
+%% 2a Connect the nodes, e.g. with net_adm:ping/1
+%% 3a Run mnesia_meter:go()
+%% 3b Run mnesia_meter:go(ReplicaType)
+%% 3c Run mnesia_meter:go(ReplicaType, Nodes)
+
+-module(mnesia_meter).
+-author('[email protected]').
+-export([
+ go/0,
+ go/1,
+ go/2,
+ repeat_meter/2
+ ]).
+
+-record(person, {name, %% atomic, unique key
+ data, %% compound structure
+ married_to, %% name of partner or undefined
+ children}). %% list of children
+
+-record(meter, {desc, init, meter, micros}).
+
+-record(result, {desc, list}).
+
+-define(TIMES, 1000).
+
+go() ->
+ go(ram_copies).
+
+go(ReplicaType) ->
+ go(ReplicaType, [node() | nodes()]).
+
+go(ReplicaType, Nodes) ->
+ {ok, FunOverhead} = tc(fun(_) -> {atomic, ok} end, ?TIMES),
+ Size = size(term_to_binary(#person{})),
+ io:format("A fun apply costs ~p micro seconds. Record size is ~p bytes.~n",
+ [FunOverhead, Size]),
+ Res = go(ReplicaType, Nodes, [], FunOverhead, []),
+ NewRes = rearrange(Res, []),
+ DescHeader = lists:flatten(io_lib:format("~w on ~w", [ReplicaType, Nodes])),
+ ItemHeader = lists:seq(1, length(Nodes)),
+ Header = #result{desc = DescHeader, list = ItemHeader},
+ SepList = ['--------' || _ <- Nodes],
+ Separator = #result{desc = "", list = SepList},
+ display([Separator, Header, Separator | NewRes] ++ [Separator]).
+
+go(_ReplicaType, [], _Config, _FunOverhead, Acc) ->
+ Acc;
+go(ReplicaType, [H | T], OldNodes, FunOverhead, Acc) ->
+ Nodes = [H | OldNodes],
+ Config = [{ReplicaType, Nodes}],
+ Res = run(Nodes, Config, FunOverhead),
+ go(ReplicaType, T, Nodes, FunOverhead, [{ReplicaType, Nodes, Res} | Acc]).
+
+rearrange([{_ReplicaType, _Nodes, Meters} | Tail], Acc) ->
+ Acc2 = [add_meter(M, Acc) || M <- Meters],
+ rearrange(Tail, Acc2);
+rearrange([], Acc) ->
+ Acc.
+
+add_meter(M, Acc) ->
+ case lists:keysearch(M#meter.desc, #result.desc, Acc) of
+ {value, R} ->
+ R#result{list = [M#meter.micros | R#result.list]};
+ false ->
+ #result{desc = M#meter.desc, list = [M#meter.micros]}
+ end.
+
+display(Res) ->
+ MaxDesc = lists:max([length(R#result.desc) || R <- Res]),
+ Format = lists:concat(["! ~-", MaxDesc, "s"]),
+ display(Res, Format, MaxDesc).
+
+display([R | Res], Format, MaxDesc) ->
+ case R#result.desc of
+ "" ->
+ io:format(Format, [lists:duplicate(MaxDesc, "-")]);
+ Desc ->
+ io:format(Format, [Desc])
+ end,
+ display_items(R#result.list, R#result.desc),
+ io:format(" !~n", []),
+ display(Res, Format, MaxDesc);
+display([], _Format, _MaxDesc) ->
+ ok.
+
+display_items([_Item | Items], "") ->
+ io:format(" ! ~s", [lists:duplicate(10, $-)]),
+ display_items(Items, "");
+display_items([Micros | Items], Desc) ->
+ io:format(" ! ~10w", [Micros]),
+ display_items(Items, Desc);
+display_items([], _Desc) ->
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+meters() ->
+ [#meter{desc = "transaction update two records with read and write",
+ init = fun write_records/2,
+ meter = fun update_records/1},
+ #meter{desc = "transaction update two records with wread and write",
+ init = fun write_records/2,
+ meter = fun w_update_records/1},
+ #meter{desc = "transaction update two records with read and s_write",
+ init = fun s_write_records/2,
+ meter = fun s_update_records/1},
+ #meter{desc = "sync_dirty update two records with read and write",
+ init = fun sync_dirty_write_records/2,
+ meter = fun sync_dirty_update_records/1},
+ #meter{desc = "async_dirty update two records with read and write",
+ init = fun async_dirty_write_records/2,
+ meter = fun async_dirty_update_records/1},
+ #meter{desc = "plain fun update two records with dirty_read and dirty_write",
+ init = fun dirty_write_records/2,
+ meter = fun dirty_update_records/1},
+ #meter{desc = "ets update two records with read and write (local only)",
+ init = fun ets_opt_write_records/2,
+ meter = fun ets_update_records/1},
+ #meter{desc = "plain fun update two records with ets:lookup and ets:insert (local only)",
+ init = fun bif_opt_write_records/2,
+ meter = fun bif_update_records/1},
+ #meter{desc = "plain fun update two records with dets:lookup and dets:insert (local only)",
+ init = fun dets_opt_write_records/2,
+ meter = fun dets_update_records/1},
+
+ #meter{desc = "transaction write two records with write",
+ init = fun write_records/2,
+ meter = fun(X) -> write_records(X, 0-X) end},
+ #meter{desc = "transaction write two records with s_write",
+ init = fun s_write_records/2,
+ meter = fun(X) -> s_write_records(X, 0-X) end},
+ #meter{desc = "sync_dirty write two records with write",
+ init = fun sync_dirty_write_records/2,
+ meter = fun(X) -> sync_dirty_write_records(X, 0-X) end},
+ #meter{desc = "async_dirty write two records with write",
+ init = fun async_dirty_write_records/2,
+ meter = fun(X) -> async_dirty_write_records(X, 0-X) end},
+ #meter{desc = "plain fun write two records with dirty_write",
+ init = fun dirty_write_records/2,
+ meter = fun(X) -> dirty_write_records(X, 0-X) end},
+ #meter{desc = "ets write two records with write (local only)",
+ init = fun ets_opt_write_records/2,
+ meter = fun(X) -> ets_write_records(X, 0-X) end},
+ #meter{desc = "plain fun write two records with ets:insert (local only)",
+ init = fun bif_opt_write_records/2,
+ meter = fun(X) -> bif_write_records(X, 0-X) end},
+ #meter{desc = "plain fun write two records with dets:insert (local only)",
+ init = fun dets_opt_write_records/2,
+ meter = fun(X) -> dets_write_records(X, 0-X) end},
+
+ #meter{desc = "transaction read two records with read",
+ init = fun write_records/2,
+ meter = fun(X) -> read_records(X, 0-X) end},
+ #meter{desc = "sync_dirty read two records with read",
+ init = fun sync_dirty_write_records/2,
+ meter = fun(X) -> sync_dirty_read_records(X, 0-X) end},
+ #meter{desc = "async_dirty read two records with read",
+ init = fun async_dirty_write_records/2,
+ meter = fun(X) -> async_dirty_read_records(X, 0-X) end},
+ #meter{desc = "plain fun read two records with dirty_read",
+ init = fun dirty_write_records/2,
+ meter = fun(X) -> dirty_read_records(X, 0-X) end},
+ #meter{desc = "ets read two records with read",
+ init = fun ets_opt_write_records/2,
+ meter = fun(X) -> ets_read_records(X, 0-X) end},
+ #meter{desc = "plain fun read two records with ets:lookup",
+ init = fun bif_opt_write_records/2,
+ meter = fun(X) -> bif_read_records(X, 0-X) end},
+ #meter{desc = "plain fun read two records with dets:lookup",
+ init = fun dets_opt_write_records/2,
+ meter = fun(X) -> dets_read_records(X, 0-X) end}
+ ].
+
+update_fun(Name) ->
+ fun() ->
+ case mnesia:read({person, Name}) of
+ [] ->
+ mnesia:abort(no_such_person);
+ [Pers] ->
+ [Partner] = mnesia:read({person, Pers#person.married_to}),
+ mnesia:write(Pers#person{married_to = undefined}),
+ mnesia:write(Partner#person{married_to = undefined})
+ end
+ end.
+
+update_records(Name) ->
+ mnesia:transaction(update_fun(Name)).
+
+sync_dirty_update_records(Name) ->
+ {atomic, mnesia:sync_dirty(update_fun(Name))}.
+
+async_dirty_update_records(Name) ->
+ {atomic, mnesia:async_dirty(update_fun(Name))}.
+
+ets_update_records(Name) ->
+ {atomic, mnesia:ets(update_fun(Name))}.
+
+w_update_records(Name) ->
+ F = fun() ->
+ case mnesia:wread({person, Name}) of
+ [] ->
+ mnesia:abort(no_such_person);
+ [Pers] ->
+ [Partner] = mnesia:wread({person, Pers#person.married_to}),
+ mnesia:write(Pers#person{married_to = undefined}),
+ mnesia:write(Partner#person{married_to = undefined})
+ end
+ end,
+ mnesia:transaction(F).
+
+s_update_records(Name) ->
+ F = fun() ->
+ case mnesia:read({person, Name}) of
+ [] ->
+ mnesia:abort(no_such_person);
+ [Pers] ->
+ [Partner] = mnesia:read({person, Pers#person.married_to}),
+ mnesia:s_write(Pers#person{married_to = undefined}),
+ mnesia:s_write(Partner#person{married_to = undefined})
+ end
+ end,
+ mnesia:transaction(F).
+
+dirty_update_records(Name) ->
+ case mnesia:dirty_read({person, Name}) of
+ [] ->
+ mnesia:abort(no_such_person);
+ [Pers] ->
+ [Partner] = mnesia:dirty_read({person, Pers#person.married_to}),
+ mnesia:dirty_write(Pers#person{married_to = undefined}),
+ mnesia:dirty_write(Partner#person{married_to = undefined})
+ end,
+ {atomic, ok}.
+
+bif_update_records(Name) ->
+ case ets:lookup(person, Name) of
+ [] ->
+ mnesia:abort(no_such_person);
+ [Pers] ->
+ [Partner] = ets:lookup(person, Pers#person.married_to),
+ ets:insert(person, Pers#person{married_to = undefined}),
+ ets:insert(person, Partner#person{married_to = undefined})
+ end,
+ {atomic, ok}.
+
+dets_update_records(Name) ->
+ case dets:lookup(person, Name) of
+ [] ->
+ mnesia:abort(no_such_person);
+ [Pers] ->
+ [Partner] = dets:lookup(person, Pers#person.married_to),
+ dets:insert(person, Pers#person{married_to = undefined}),
+ dets:insert(person, Partner#person{married_to = undefined})
+ end,
+ {atomic, ok}.
+
+write_records_fun(Pers, Partner) ->
+ fun() ->
+ P = #person{children = [ulla, bella]},
+ mnesia:write(P#person{name = Pers, married_to = Partner}),
+ mnesia:write(P#person{name = Partner, married_to = Pers})
+ end.
+
+write_records(Pers, Partner) ->
+ mnesia:transaction(write_records_fun(Pers, Partner)).
+
+sync_dirty_write_records(Pers, Partner) ->
+ {atomic, mnesia:sync_dirty(write_records_fun(Pers, Partner))}.
+
+async_dirty_write_records(Pers, Partner) ->
+ {atomic, mnesia:async_dirty(write_records_fun(Pers, Partner))}.
+
+ets_write_records(Pers, Partner) ->
+ {atomic, mnesia:ets(write_records_fun(Pers, Partner))}.
+
+s_write_records(Pers, Partner) ->
+ F = fun() ->
+ P = #person{children = [ulla, bella]},
+ mnesia:s_write(P#person{name = Pers, married_to = Partner}),
+ mnesia:s_write(P#person{name = Partner, married_to = Pers})
+ end,
+ mnesia:transaction(F).
+
+dirty_write_records(Pers, Partner) ->
+ P = #person{children = [ulla, bella]},
+ mnesia:dirty_write(P#person{name = Pers, married_to = Partner}),
+ mnesia:dirty_write(P#person{name = Partner, married_to = Pers}),
+ {atomic, ok}.
+
+ets_opt_write_records(Pers, Partner) ->
+ case mnesia:table_info(person, where_to_commit) of
+ [{N, ram_copies}] when N == node() ->
+ ets_write_records(Pers, Partner);
+ _ ->
+ throw(skipped)
+ end.
+
+bif_opt_write_records(Pers, Partner) ->
+ case mnesia:table_info(person, where_to_commit) of
+ [{N, ram_copies}] when N == node() ->
+ bif_write_records(Pers, Partner);
+ _ ->
+ throw(skipped)
+ end.
+
+bif_write_records(Pers, Partner) ->
+ P = #person{children = [ulla, bella]},
+ ets:insert(person, P#person{name = Pers, married_to = Partner}),
+ ets:insert(person, P#person{name = Partner, married_to = Pers}),
+ {atomic, ok}.
+
+dets_opt_write_records(Pers, Partner) ->
+ case mnesia:table_info(person, where_to_commit) of
+ [{N, disc_only_copies}] when N == node() ->
+ dets_write_records(Pers, Partner);
+ _ ->
+ throw(skipped)
+ end.
+
+dets_write_records(Pers, Partner) ->
+ P = #person{children = [ulla, bella]},
+ dets:insert(person, P#person{name = Pers, married_to = Partner}),
+ dets:insert(person, P#person{name = Partner, married_to = Pers}),
+ {atomic, ok}.
+
+read_records_fun(Pers, Partner) ->
+ fun() ->
+ case {mnesia:read({person, Pers}),
+ mnesia:read({person, Partner})} of
+ {[_], [_]} ->
+ ok;
+ _ ->
+ mnesia:abort(no_such_person)
+ end
+ end.
+
+read_records(Pers, Partner) ->
+ mnesia:transaction(read_records_fun(Pers, Partner)).
+
+sync_dirty_read_records(Pers, Partner) ->
+ {atomic, mnesia:sync_dirty(read_records_fun(Pers, Partner))}.
+
+async_dirty_read_records(Pers, Partner) ->
+ {atomic, mnesia:async_dirty(read_records_fun(Pers, Partner))}.
+
+ets_read_records(Pers, Partner) ->
+ {atomic, mnesia:ets(read_records_fun(Pers, Partner))}.
+
+dirty_read_records(Pers, Partner) ->
+ case {mnesia:dirty_read({person, Pers}),
+ mnesia:dirty_read({person, Partner})} of
+ {[_], [_]} ->
+ {atomic, ok};
+ _ ->
+ mnesia:abort(no_such_person)
+ end.
+
+bif_read_records(Pers, Partner) ->
+ case {ets:lookup(person, Pers),
+ ets:lookup(person, Partner)} of
+ {[_], [_]} ->
+ {atomic, ok};
+ _ ->
+ mnesia:abort(no_such_person)
+ end.
+
+dets_read_records(Pers, Partner) ->
+ case {dets:lookup(person, Pers),
+ dets:lookup(person, Partner)} of
+ {[_], [_]} ->
+ {atomic, ok};
+ _ ->
+ mnesia:abort(no_such_person)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+run(Nodes, Config, FunOverhead) ->
+ Meters = meters(),
+ io:format("Run ~w meters with table config: ~w~n", [length(Meters), Config]),
+ rpc:multicall(Nodes, mnesia, lkill, []),
+ start(Nodes, Config),
+ Res = [run_meter(Data, Nodes, FunOverhead) || Data <- Meters],
+ stop(Nodes),
+ Res.
+
+run_meter(M, Nodes, FunOverhead) when is_record(M, meter) ->
+ io:format(".", []),
+ case catch init_records(M#meter.init, ?TIMES) of
+ {atomic, ok} ->
+ rpc:multicall(Nodes, mnesia, dump_log, []),
+ case tc(M#meter.meter, ?TIMES) of
+ {ok, Micros} ->
+ M#meter{micros = lists:max([0, Micros - FunOverhead])};
+ {error, Reason} ->
+ M#meter{micros = Reason}
+ end;
+ Res ->
+ M#meter{micros = Res}
+ end.
+
+start(Nodes, Config) ->
+ mnesia:delete_schema(Nodes),
+ ok = mnesia:create_schema(Nodes),
+ Args = [[{dump_log_write_threshold, ?TIMES div 2},
+ {dump_log_time_threshold, timer:hours(10)}]],
+ lists:foreach(fun(Node) -> rpc:call(Node, mnesia, start, Args) end, Nodes),
+ Attrs = record_info(fields, person),
+ TabDef = [{attributes, Attrs} | Config],
+ {atomic, _} = mnesia:create_table(person, TabDef).
+
+stop(Nodes) ->
+ rpc:multicall(Nodes, mnesia, stop, []).
+
+%% Generate some dummy persons
+init_records(_Fun, 0) ->
+ {atomic, ok};
+init_records(Fun, Times) ->
+ {atomic, ok} = Fun(Times, 0 - Times),
+ init_records(Fun, Times - 1).
+
+tc(Fun, Times) ->
+ case catch timer:tc(?MODULE, repeat_meter, [Fun, Times]) of
+ {Micros, ok} ->
+ {ok, Micros div Times};
+ {_Micros, {error, Reason}} ->
+ {error, Reason};
+ {'EXIT', Reason} ->
+ {error, Reason}
+ end.
+
+%% The meter must return {atomic, ok}
+repeat_meter(Meter, Times) ->
+ repeat_meter(Meter, {atomic, ok}, Times).
+
+repeat_meter(_, {atomic, ok}, 0) ->
+ ok;
+repeat_meter(Meter, {atomic, _Result}, Times) when Times > 0 ->
+ repeat_meter(Meter, Meter(Times), Times - 1);
+repeat_meter(_Meter, Reason, _Times) ->
+ {error, Reason}.
+
diff --git a/lib/mnesia/test/mnesia_nice_coverage_test.erl b/lib/mnesia/test/mnesia_nice_coverage_test.erl
new file mode 100644
index 0000000000..aa9339f6b9
--- /dev/null
+++ b/lib/mnesia/test/mnesia_nice_coverage_test.erl
@@ -0,0 +1,227 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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
+%% 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_nice_coverage_test).
+-author('[email protected]').
+-compile([export_all]).
+-include("mnesia_test_lib.hrl").
+
+-record(nice_tab, {key, val}).
+
+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) ->
+ ["Test nice usage of the entire API",
+ "Invoke all functions in the API, at least once.",
+ "Try to verify that all functions exists and that they perform",
+ "reasonable things when used in the most simple way."];
+all(suite) -> [nice].
+
+nice(doc) -> [""];
+nice(suite) -> [];
+nice(Config) when is_list(Config) ->
+ %% The whole test suite is one huge test case for the time beeing
+
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Attrs = record_info(fields, nice_tab),
+
+ initialize(Attrs, Node1),
+ dirty_access(Node1),
+ success_and_fail(),
+ index_mgt(),
+
+ adm(Attrs, Node1, Node2),
+ snmp(Node1, Node2),
+ backup(Node1),
+ ?verify_mnesia(Nodes, []).
+
+initialize(Attrs, Node1) ->
+ ?match(Version when is_list(Version), mnesia:system_info(version)),
+
+ Schema = [{name, nice_tab},
+ {attributes, Attrs}, {ram_copies, [Node1]}],
+
+ ?match({_, _}, mnesia:system_info(schema_version)),
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ ?match(ok, mnesia:info()),
+ ?match(set, mnesia:table_info(nice_tab, type)),
+ ?match(ok, mnesia:schema()),
+ ?match(ok, mnesia:schema(nice_tab)),
+ ok.
+
+dirty_access(Node1) ->
+ TwoThree = #nice_tab{key=23, val=23},
+ TwoFive = #nice_tab{key=25, val=25},
+ ?match([], mnesia:dirty_slot(nice_tab, 0)),
+ ?match(ok, mnesia:dirty_write(TwoThree)),
+ ?match([TwoThree], mnesia:dirty_read({nice_tab, 23})),
+ ?match(ok, mnesia:dirty_write(TwoFive)),
+ ?match(ok, mnesia:dirty_delete_object(TwoFive)),
+
+ ?match(23, mnesia:dirty_first(nice_tab)),
+ ?match('$end_of_table', mnesia:dirty_next(nice_tab, 23)),
+ ?match([TwoThree], mnesia:dirty_match_object(TwoThree)),
+ ?match(ok, mnesia:dirty_delete({nice_tab, 23})),
+
+ CounterSchema = [{ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(nice_counter_tab, CounterSchema)),
+ TwoFour = {nice_counter_tab, 24, 24},
+ ?match(ok, mnesia:dirty_write(TwoFour)),
+ ?match(34, mnesia:dirty_update_counter({nice_counter_tab, 24}, 10)),
+ TF = {nice_counter_tab, 24, 34},
+ ?match([TF], mnesia:dirty_read({nice_counter_tab, 24})),
+ ?match(ok, mnesia:dirty_delete({nice_counter_tab, 24})),
+ ?match(ok, mnesia:dirty_delete_object(TF)),
+ ok.
+
+success_and_fail() ->
+ ?match({atomic, a_good_trans}, mnesia:transaction(fun() ->good_trans()end)),
+
+ BadFun =
+ fun() ->
+ Two = #nice_tab{key=2, val=12},
+ ?match([Two], mnesia:match_object(#nice_tab{key='$1', val=12})),
+ ?match([#nice_tab{key=3, val=13}], mnesia:wread({nice_tab, 3})),
+ ?match(ok, mnesia:delete({nice_tab, 1})),
+ ?match(ok, mnesia:delete_object(Two)),
+ mnesia:abort(bad_trans),
+ ?match(bad, trans)
+ end,
+ ?match({aborted, bad_trans}, mnesia:transaction(BadFun)),
+ ?match(L when is_list(L), mnesia:error_description(no_exists)),
+ ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok])),
+ ?match({atomic, ok}, mnesia:transaction(fun(A) -> lock(), A end, [ok], 3)),
+ ok.
+
+good_trans() ->
+ ?match([], mnesia:read(nice_tab, 3)),
+ ?match([], mnesia:read({nice_tab, 3})),
+ ?match(ok, mnesia:write(#nice_tab{key=14, val=4})),
+ ?match([14], mnesia:all_keys(nice_tab)),
+
+ Records = [ #nice_tab{key=K, val=K+10} || K <- lists:seq(1, 10) ],
+ Ok = [ ok || _ <- Records],
+ ?match(Ok, lists:map(fun(R) -> mnesia:write(R) end, Records)),
+ a_good_trans.
+
+
+lock() ->
+ ?match(ok, mnesia:s_write(#nice_tab{key=22, val=22})),
+ ?match(ok, mnesia:read_lock_table(nice_tab)),
+ ?match(ok, mnesia:write_lock_table(nice_tab)),
+ ok.
+
+index_mgt() ->
+ UniversalRec = #nice_tab{key=4711, val=4711},
+ ?match(ok, mnesia:dirty_write(UniversalRec)),
+ ValPos = #nice_tab.val,
+ ?match({atomic, ok}, mnesia:add_table_index(nice_tab, ValPos)),
+
+ IndexFun =
+ fun() ->
+ ?match([UniversalRec],
+ mnesia:index_read(nice_tab, 4711, ValPos)),
+ Pat = #nice_tab{key='$1', val=4711},
+ ?match([UniversalRec],
+ mnesia:index_match_object(Pat, ValPos)),
+ index_trans
+ end,
+ ?match({atomic, index_trans}, mnesia:transaction(IndexFun, infinity)),
+ ?match([UniversalRec],
+ mnesia:dirty_index_read(nice_tab, 4711, ValPos)),
+ ?match([UniversalRec],
+ mnesia:dirty_index_match_object(#nice_tab{key='$1', val=4711}, ValPos)),
+
+ ?match({atomic, ok}, mnesia:del_table_index(nice_tab, ValPos)),
+ ok.
+
+adm(Attrs, Node1, Node2) ->
+ This = node(),
+ ?match({ok, This}, mnesia:subscribe(system)),
+ ?match({atomic, ok},
+ mnesia:add_table_copy(nice_tab, Node2, disc_only_copies)),
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(nice_tab, Node2, ram_copies)),
+ ?match({atomic, ok}, mnesia:del_table_copy(nice_tab, Node1)),
+ ?match(stopped, rpc:call(Node1, mnesia, stop, [])),
+ ?match([], mnesia_test_lib:start_mnesia([Node1, Node2], [nice_tab])),
+ ?match(ok, mnesia:wait_for_tables([schema], infinity)),
+
+ Transformer = fun(Rec) ->
+ list_to_tuple(tuple_to_list(Rec) ++ [initial_value])
+ end,
+ ?match({atomic, ok},
+ mnesia:transform_table(nice_tab, Transformer, Attrs ++ [extra])),
+
+ ?match({atomic, ok}, mnesia:delete_table(nice_tab)),
+ DumpSchema = [{name, nice_tab}, {attributes, Attrs}, {ram_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(DumpSchema)),
+ ?match({atomic, ok}, mnesia:dump_tables([nice_tab])),
+ ?match({atomic, ok}, mnesia:move_table_copy(nice_tab, Node2, Node1)),
+
+ ?match(yes, mnesia:force_load_table(nice_counter_tab)),
+ ?match(dumped, mnesia:dump_log()),
+ ok.
+
+backup(Node1) ->
+ Tab = backup_nice,
+ Def = [{disc_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match({ok,_,_}, mnesia:activate_checkpoint([{name, cp}, {max, [Tab]}])),
+ File = "nice_backup.BUP",
+ File2 = "nice_backup2.BUP",
+ File3 = "nice_backup3.BUP",
+ ?match(ok, mnesia:backup_checkpoint(cp, File)),
+ ?match(ok, mnesia:backup_checkpoint(cp, File, mnesia_backup)),
+ ?match(ok, mnesia:deactivate_checkpoint(cp)),
+ ?match(ok, mnesia:backup(File)),
+ ?match(ok, mnesia:backup(File, mnesia_backup)),
+
+ Fun = fun(X, Acc) -> {[X], Acc} end,
+ ?match({ok, 0}, mnesia:traverse_backup(File, File2, Fun, 0)),
+ ?match({ok, 0}, mnesia:traverse_backup(File, mnesia_backup, dummy, read_only, Fun, 0)),
+ ?match(ok, mnesia:install_fallback(File)),
+ ?match(ok, mnesia:uninstall_fallback()),
+ ?match(ok, mnesia:install_fallback(File, mnesia_backup)),
+ ?match(ok, mnesia:dump_to_textfile(File3)),
+ ?match({atomic, ok}, mnesia:load_textfile(File3)),
+ ?match(ok, file:delete(File)),
+ ?match(ok, file:delete(File2)),
+ ?match(ok, file:delete(File3)),
+ ok.
+
+snmp(Node1, Node2) ->
+ Tab = nice_snmp,
+ Def = [{disc_copies, [Node1]}, {ram_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match({aborted, {badarg, Tab, _}}, mnesia:snmp_open_table(Tab, [])),
+ ?match({atomic, ok}, mnesia:snmp_open_table(Tab, [{key, integer}])),
+ ?match(endOfTable, mnesia:snmp_get_next_index(Tab, [0])),
+ ?match(undefined, mnesia:snmp_get_row(Tab, [0])),
+ ?match(undefined, mnesia:snmp_get_mnesia_key(Tab, [0])),
+ ?match({atomic, ok}, mnesia:snmp_close_table(Tab)),
+ ok.
+
diff --git a/lib/mnesia/test/mnesia_qlc_test.erl b/lib/mnesia/test/mnesia_qlc_test.erl
new file mode 100644
index 0000000000..1e4f776c7d
--- /dev/null
+++ b/lib/mnesia/test/mnesia_qlc_test.erl
@@ -0,0 +1,475 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2004-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(mnesia_qlc_test).
+
+-compile(export_all).
+
+-export([all/1]).
+
+-include("mnesia_test_lib.hrl").
+-include_lib("stdlib/include/qlc.hrl").
+
+init_per_testcase(Func, Conf) ->
+ setup(Conf),
+ mnesia_test_lib:init_per_testcase(Func, Conf).
+
+fin_per_testcase(Func, Conf) ->
+ mnesia_test_lib:fin_per_testcase(Func, Conf).
+
+all(doc) ->
+ ["Test that the qlc mnesia interface works as expected."];
+all(suite) ->
+ case code:which(qlc) of
+ non_existing -> [];
+ _ ->
+ all_qlc()
+ end.
+
+all_qlc() ->
+ [dirty, trans, frag, info, mnesia_down].
+
+init_testcases(Type,Config) ->
+ Nodes = [N1,N2] = ?acquire_nodes(2, Config),
+ ?match({atomic, ok}, mnesia:create_table(a, [{Type,[N1]}, {index,[3]}])),
+ ?match({atomic, ok}, mnesia:create_table(b, [{Type,[N2]}])),
+ Write = fun(Id) ->
+ ok = mnesia:write({a, {a,Id}, 100 - Id}),
+ ok = mnesia:write({b, {b,100-Id}, Id})
+ end,
+ All = fun() -> [Write(Id) || Id <- lists:seq(1,10)], ok end,
+ ?match({atomic, ok}, mnesia:sync_transaction(All)),
+ Nodes.
+
+%% Test cases
+dirty(suite) ->
+ [dirty_nice_ram_copies,
+ dirty_nice_disc_copies,
+ dirty_nice_disc_only_copies].
+
+dirty_nice_ram_copies(Setup) -> dirty_nice(Setup,ram_copies).
+dirty_nice_disc_copies(Setup) -> dirty_nice(Setup,disc_copies).
+dirty_nice_disc_only_copies(Setup) -> dirty_nice(Setup,disc_only_copies).
+
+dirty_nice(suite, _) -> [];
+dirty_nice(doc, _) -> [];
+dirty_nice(Config, Type) when is_list(Config) ->
+ Ns = init_testcases(Type,Config),
+ QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a),"
+ " Val == 90 + Key]">>),
+ QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
+ " Key == 90 + Val]">>),
+ QC = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])),
+ QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])),
+
+ FA = fun() -> qlc:e(QA) end,
+ FB = fun() -> qlc:e(QB) end,
+ FC = fun() -> qlc:e(QC) end,
+ FD = fun() -> qlc:e(QD) end,
+
+ %% Currently unsupported
+ ?match({'EXIT',{aborted,no_transaction}}, FA()),
+ ?match({'EXIT',{aborted,no_transaction}}, FB()),
+ %%
+ CRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})),
+ ?match([{a,{a,5},95}], mnesia:async_dirty(FA)),
+ ?match([{b,{b,95},5}], mnesia:async_dirty(FB)),
+ ?match(CRes, mnesia:async_dirty(FC)),
+ ?match(CRes, mnesia:async_dirty(FD)),
+ ?match([{a,{a,5},95}], mnesia:sync_dirty(FA)),
+ ?match([{b,{b,95},5}], mnesia:sync_dirty(FB)),
+ ?match(CRes, mnesia:sync_dirty(FC)),
+ ?match([{a,{a,5},95}], mnesia:activity(async_dirty, FA)),
+ ?match([{b,{b,95},5}], mnesia:activity(async_dirty, FB)),
+ ?match([{a,{a,5},95}], mnesia:activity(sync_dirty, FA)),
+ ?match([{b,{b,95},5}], mnesia:activity(sync_dirty, FB)),
+ ?match(CRes, mnesia:activity(async_dirty,FC)),
+ case Type of
+ disc_only_copies -> skip;
+ _ ->
+ ?match([{a,{a,5},95}], mnesia:ets(FA)),
+ ?match([{a,{a,5},95}], mnesia:activity(ets, FA))
+ end,
+ ?verify_mnesia(Ns, []).
+
+trans(suite) ->
+ [trans_nice_ram_copies,
+ trans_nice_disc_copies,
+ trans_nice_disc_only_copies,
+ atomic
+ ].
+
+trans_nice_ram_copies(Setup) -> trans_nice(Setup,ram_copies).
+trans_nice_disc_copies(Setup) -> trans_nice(Setup,disc_copies).
+trans_nice_disc_only_copies(Setup) -> trans_nice(Setup,disc_only_copies).
+
+trans_nice(suite, _) -> [];
+trans_nice(doc, _) -> [];
+trans_nice(Config, Type) when is_list(Config) ->
+ Ns = init_testcases(Type,Config),
+ QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a),"
+ " Val == 90 + Key]">>),
+ QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
+ " Key == 90 + Val]">>),
+ QC = handle(recs(),
+ <<"[Q || Q = #a{v=91} <- mnesia:table(a)]"
+ >>),
+
+ QD = qlc:sort(mnesia:table(a, [{n_objects,1}, {lock,write}, {traverse, select}])),
+ QE = qlc:sort(mnesia:table(a, [{n_objects,1}, {traverse,{select,[{'$1',[],['$1']}]}}])),
+
+ DRes = lists:sort(mnesia:dirty_match_object(a, {'_','_','_'})),
+
+ FA = fun() -> qlc:e(QA) end,
+ FB = fun() -> qlc:e(QB) end,
+ FC = fun() -> qlc:e(QC) end,
+ FD = fun() -> qlc:e(QD) end,
+ FE = fun() -> qlc:e(QE) end,
+
+ ?match({atomic,[{a,{a,5},95}]}, mnesia:transaction(FA)),
+ ?match({atomic,[{b,{b,95},5}]}, mnesia:transaction(FB)),
+ ?match({atomic,[{a,{a,9},91}]}, mnesia:transaction(FC)),
+ ?match({atomic,[{a,{a,5},95}]}, mnesia:sync_transaction(FA)),
+ ?match({atomic,[{b,{b,95},5}]}, mnesia:sync_transaction(FB)),
+ ?match({atomic,[{a,{a,9},91}]}, mnesia:sync_transaction(FC)),
+ ?match([{a,{a,5},95}], mnesia:activity(transaction,FA)),
+ ?match([{b,{b,95},5}], mnesia:activity(transaction,FB)),
+ ?match([{a,{a,9},91}], mnesia:activity(transaction,FC)),
+ ?match([{a,{a,5},95}], mnesia:activity(sync_transaction,FA)),
+ ?match([{b,{b,95},5}], mnesia:activity(sync_transaction,FB)),
+ ?match([{a,{a,9},91}], mnesia:activity(sync_transaction,FC)),
+
+ ?match({atomic, DRes}, mnesia:transaction(FD)),
+ ?match({atomic, DRes}, mnesia:transaction(FE)),
+
+ Rest = fun(Cursor,Loop) ->
+ case qlc:next_answers(Cursor, 1) of
+ [] -> [];
+ [A]-> [A|Loop(Cursor,Loop)]
+ end
+ end,
+ Loop = fun() ->
+ Cursor = qlc:cursor(QD),
+ Rest(Cursor,Rest)
+ end,
+ ?match({atomic, DRes}, mnesia:transaction(Loop)),
+
+ ?verify_mnesia(Ns, []).
+
+%% -record(a, {k,v}).
+%% -record(b, {k,v}).
+%% -record(k, {t,v}).
+
+recs() ->
+ <<"-record(a, {k,v}). "
+ "-record(b, {k,v}). "
+ "-record(k, {t,v}). "
+ >>.
+
+atomic(suite) -> [atomic_eval];
+atomic(doc) -> [].
+
+atomic_eval(suite) -> [];
+atomic_eval(doc) -> [];
+atomic_eval(Config) ->
+ Ns = init_testcases(ram_copies, Config),
+ Q1 = handle(recs(),
+ <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]"
+ >>),
+ Eval = fun(Q) ->
+ {qlc:e(Q),
+ mnesia:system_info(held_locks)}
+ end,
+ Self = self(),
+ ?match({[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]},
+ ok(Eval,[Q1])),
+
+ Q2 = handle(recs(),
+ <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]"
+ >>),
+
+ ?match({[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]},
+ ok(Eval,[Q2])),
+
+ Flush = fun(Loop) -> %% Clean queue
+ receive _ -> Loop(Loop)
+ after 0 -> ok end
+ end,
+
+ Flush(Flush),
+
+ GrabLock = fun(Father) ->
+ mnesia:read(a, {a,9}, write),
+ Father ! locked,
+ receive cont -> ok end end,
+
+ Pid1 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end),
+ ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait
+
+ put(count,0),
+ Restart = fun(Locker,Fun) ->
+ Count = get(count),
+ case {Count,(catch Fun())} of
+ {0, {'EXIT', R}} ->
+ Locker ! cont,
+ put(count, Count+1),
+ erlang:yield(),
+ exit(R);
+ Else ->
+ Else
+ end
+ end,
+
+ ?match({1,{[{a,{a,9},91}], [{{a,'______WHOLETABLE_____'},read,{tid,_,Self}}]}},
+ ok(Restart,[Pid1,fun() -> Eval(Q1) end])),
+
+ Pid2 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end),
+ ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait
+ put(count,0),
+ ?match({1,{[{a,{a,9},91}],[{{a,{a,9}},read,{tid,_,Self}}]}},
+ ok(Restart,[Pid2, fun() -> Eval(Q2) end])),
+
+%% Basic test
+ Cursor = fun() ->
+ QC = qlc:cursor(Q1),
+ qlc:next_answers(QC)
+ end,
+
+ ?match([{a,{a,9},91}], ok(Cursor, [])),
+ %% Lock
+
+ Pid3 = spawn(fun() -> ?match(ok, ok(GrabLock, [Self])) end),
+ ?match(locked,receive locked -> locked after 5000 -> timeout end), %% Wait
+ put(count,0),
+
+ ?match({1,[{a,{a,9},91}]}, ok(Restart,[Pid3, Cursor])),
+ QC1 = ok(fun() -> qlc:cursor(Q1) end, []),
+ ?match({'EXIT', _}, qlc:next_answers(QC1)),
+ ?match({aborted,_}, ok(fun()->qlc:next_answers(QC1)end,[])),
+ ?verify_mnesia(Ns, []).
+
+
+frag(suite) -> [];
+frag(doc) -> [];
+frag(Config) ->
+ Ns = init_testcases(ram_copies,Config),
+ QA = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(a),"
+ " Val == 90 + Key]">>),
+ QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
+ " Key == 90 + Val]">>),
+
+ Activate =
+ fun(Tab) ->
+ ?match({atomic,ok},mnesia:change_table_frag(Tab, {activate, []})),
+ Dist = mnesia_frag_test:frag_dist(Tab),
+ ?match({atomic,ok},mnesia:change_table_frag(Tab,{add_frag,Dist}))
+ end,
+ Activate(a),
+ Activate(b),
+
+ Fun = fun(Tab) -> mnesia:table_info(Tab, frag_names) end,
+ FTs = mnesia:activity(sync_dirty, Fun, [a], mnesia_frag) ++
+ mnesia:activity(sync_dirty, Fun, [b], mnesia_frag),
+ Size = fun(Tab) -> mnesia:dirty_rpc(Tab, mnesia, table_info, [Tab,size]) end,
+
+ %% Verify that all data doesn't belong to the same frag.
+ ?match([], [{Tab,Size(Tab)} || Tab <- FTs,
+ Size(Tab) =< 0]),
+
+ FA = fun() -> qlc:e(QA) end,
+ FB = fun() -> qlc:e(QB) end,
+ ?match([{a,{a,5},95}], mnesia:activity(transaction,FA,[],mnesia_frag)),
+ ?match([{b,{b,95},5}], mnesia:activity(transaction,FB,[],mnesia_frag)),
+
+ ?verify_mnesia(Ns, []).
+
+info(suite) -> [];
+info(doc) -> [];
+info(Config) ->
+ Ns = init_testcases(ram_copies, Config),
+ Q1 = handle(recs(),
+ <<"[Q || Q = #a{k={_,9}} <- mnesia:table(a)]"
+ >>),
+
+ Q2 = handle(recs(),
+ <<"[Q || Q = #a{k={a,9}} <- mnesia:table(a)]"
+ >>),
+
+ Q3 = handle(recs(),
+ <<"[Q || Q = #a{v=91} <- mnesia:table(a)]"
+ >>),
+
+ %% FIXME compile and check results!
+
+ ?match(ok,io:format("~s~n",[qlc:info(Q1)])),
+ ?match(ok,io:format("~s~n",[qlc:info(Q2)])),
+ ?match(ok,io:format("~s~n",[qlc:info(Q3)])),
+
+ ?verify_mnesia(Ns, []).
+
+ok(Fun,A) ->
+ case mnesia:transaction(Fun,A) of
+ {atomic, R} -> R;
+ E -> E
+ end.
+
+
+mnesia_down(suite) -> [];
+mnesia_down(doc) ->
+ ["Test bug OTP-7968, which crashed mnesia when a"
+ "mnesia_down came after qlc had been invoked"];
+mnesia_down(Config) when is_list(Config) ->
+ [N1,N2] = init_testcases(ram_copies,Config),
+ QB = handle(<<"[Q || Q = {_,{_,Key},Val} <- mnesia:table(b),"
+ " Val == Key - 90]">>),
+
+ Tester = self(),
+
+ Eval = fun() ->
+ Cursor = qlc:cursor(QB), %% Forces another process
+ Res = qlc:next_answers(Cursor),
+ Tester ! {qlc, self(), Res},
+ {Mod, Tid, Ts} = get(mnesia_activity_state),
+ receive
+ continue ->
+ io:format("Continuing ~p ~p ~n",[self(), {Mod, Tid, Ts}]),
+ io:format("ETS ~p~n",[ets:tab2list(element(2,Ts))]),
+ io:format("~p~n",[process_info(self(),messages)]),
+ Res
+ end
+ end,
+ spawn(fun() -> TransRes = mnesia:transaction(Eval), Tester ! {test,TransRes} end),
+
+ TMInfo = fun() ->
+ TmInfo = mnesia_tm:get_info(5000),
+ mnesia_tm:display_info(user, TmInfo)
+ end,
+ receive
+ {qlc, QPid, QRes} ->
+ ?match([{b,{b,95},5}], QRes),
+ TMInfo(),
+ mnesia_test_lib:kill_mnesia([N2]),
+ %%timer:sleep(1000),
+ QPid ! continue
+ after 2000 ->
+ exit(timeout1)
+ end,
+
+ receive
+ {test, QRes2} ->
+ ?match({atomic, [{b,{b,95},5}]}, QRes2)
+ after 2000 ->
+ exit(timeout2)
+ end,
+
+ ?verify_mnesia([N1], [N2]).
+
+
+nested_qlc(suite) -> [];
+nested_qlc(doc) ->
+ ["Test bug in OTP-7968 (the second problem) where nested"
+ "transaction don't work as expected"];
+nested_qlc(Config) when is_list(Config) ->
+ Ns = init_testcases(ram_copies,Config),
+ Res = as_with_bs(),
+ ?match([_|_], Res),
+ top_as_with_some_bs(10),
+
+ ?verify_mnesia(Ns, []).
+
+
+%% Code from Daniel
+bs_by_a_id(A_id) ->
+ find(qlc:q([ B || B={_,_,F_id} <- mnesia:table(b), F_id == A_id])).
+
+as_with_bs() ->
+ find(qlc:q([ {A,bs_by_a_id(Id)} ||
+ A = {_, {a,Id}, _} <- mnesia:table(a)])).
+
+top_as_with_some_bs(Limit) ->
+ top(
+ qlc:q([ {A,bs_by_a_id(Id)} ||
+ A = {_, {a,Id}, _} <- mnesia:table(a)]),
+ Limit,
+ fun(A1,A2) -> A1 < A2 end
+ ).
+
+% --- utils
+
+find(Q) ->
+ F = fun() -> qlc:e(Q) end,
+ {atomic, Res} = mnesia:transaction(F),
+ Res.
+
+% --- it returns top Limit results from query Q ordered by Order sort function
+top(Q, Limit, Order) ->
+ Do = fun() ->
+ OQ = qlc:sort(Q, [{order,Order}]),
+ QC = qlc:cursor(OQ),
+ Res = qlc:next_answers(QC, Limit),
+ qlc:delete_cursor(QC),
+ Res
+ end,
+ {atomic, Res} = mnesia:transaction(Do),
+ Res.
+
+%% To keep mnesia suite backward compatible,
+%% we compile the queries in runtime when qlc is available
+%% Compiles and returns a handle to a qlc
+handle(Expr) ->
+ handle(<<>>,Expr).
+handle(Records,Expr) ->
+ case catch handle2(Records,Expr) of
+ {ok, Handle} ->
+ Handle;
+ Else ->
+ ?match(ok, Else)
+ end.
+
+handle2(Records,Expr) ->
+ {FN,Mod} = temp_name(),
+ ModStr = list_to_binary("-module(" ++ atom_to_list(Mod) ++ ").\n"),
+ Prog = <<
+ ModStr/binary,
+ "-include_lib(\"stdlib/include/qlc.hrl\").\n",
+ "-export([tmp/0]).\n",
+ Records/binary,"\n",
+ "tmp() ->\n",
+%% " _ = (catch throw(fvalue_not_reset)),"
+ " qlc:q( ",
+ Expr/binary,").\n">>,
+
+ ?match(ok,file:write_file(FN,Prog)),
+ {ok,Forms} = epp:parse_file(FN,"",""),
+ {ok,Mod,Bin} = compile:forms(Forms),
+ code:load_binary(Mod,FN,Bin),
+ {ok, Mod:tmp()}.
+
+setup(Config) ->
+ put(mts_config,Config),
+ put(mts_tf_counter,0).
+
+temp_name() ->
+ Conf = get(mts_config),
+ C = get(mts_tf_counter),
+ put(mts_tf_counter,C+1),
+ {filename:join([proplists:get_value(priv_dir,Conf, "."),
+ "tempfile"++integer_to_list(C)++".tmp"]),
+ list_to_atom("tmp" ++ integer_to_list(C))}.
diff --git a/lib/mnesia/test/mnesia_recovery_test.erl b/lib/mnesia/test/mnesia_recovery_test.erl
new file mode 100644
index 0000000000..f6ecf2ce2e
--- /dev/null
+++ b/lib/mnesia/test/mnesia_recovery_test.erl
@@ -0,0 +1,1701 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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
+%% 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_recovery_test).
+-author('[email protected]').
+-compile([export_all]).
+
+-include("mnesia_test_lib.hrl").
+-include_lib("kernel/include/file.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).
+
+-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)).
+
+% First Some debug logging
+-define(dgb, true).
+-ifdef(dgb).
+-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)).
+-else.
+-define(dl(X, Y), ok).
+-endif.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+all(doc) ->
+ ["Verify recoverability",
+ "Verify that the effects of committed transactions are preserved",
+ "after recovery from system failures. It must be possible to",
+ "restore the tables to a consistent state on a node, from (any kind",
+ "of) replica on other nodes as well as from local disk on the failed",
+ "node. The system must also recover from instantaneous",
+ "interruption causing disk files to not be completely synchronized."];
+
+all(suite) ->
+ [
+ mnesia_down,
+ explicit_stop,
+ coord_dies,
+ schema_trans,
+ async_dirty,
+ sync_dirty,
+ sym_trans,
+ asym_trans,
+ after_full_disc_partition,
+ after_corrupt_files,
+ disc_less,
+ garb_decision,
+ system_upgrade
+ ].
+
+schema_trans(suite) ->
+ [{mnesia_schema_recovery_test, all}].
+
+tpcb_config(ReplicaType, _NodeConfig, Nodes) ->
+ [{n_branches, 5},
+ {n_drivers_per_node, 5},
+ {replica_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {use_running_mnesia, true},
+ {report_interval, infinity},
+ {n_accounts_per_branch, 20},
+ {replica_type, ReplicaType}].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+mnesia_down(doc) ->
+ [" Various tests about recovery when mnesia goes down on one or several nodes."];
+mnesia_down(suite) ->
+ [
+ mnesia_down_during_startup,
+ master_node_tests,
+ read_during_down,
+ with_checkpoint,
+ delete_during_start
+ ].
+
+master_node_tests(doc) ->
+ ["Verify that mnesia loads the correct data after it has been down, regarding master node settings."];
+master_node_tests(suite) ->
+ [
+ no_master_2,
+ no_master_3,
+ one_master_2,
+ one_master_3,
+ two_master_2,
+ two_master_3,
+ all_master_2,
+ all_master_3
+ ].
+
+no_master_2(suite) -> [];
+no_master_2(Config) when is_list(Config) -> mnesia_down_2(no, Config).
+
+no_master_3(suite) -> [];
+no_master_3(Config) when is_list(Config) -> mnesia_down_3(no, Config).
+
+one_master_2(suite) -> [];
+one_master_2(Config) when is_list(Config) -> mnesia_down_2(one, Config).
+
+one_master_3(suite) -> [];
+one_master_3(Config) when is_list(Config) -> mnesia_down_3(one, Config).
+
+two_master_2(suite) -> [];
+two_master_2(Config) when is_list(Config) -> mnesia_down_2(two, Config).
+
+two_master_3(suite) -> [];
+two_master_3(Config) when is_list(Config) -> mnesia_down_3(two, Config).
+
+all_master_2(suite) -> [];
+all_master_2(Config) when is_list(Config) -> mnesia_down_2(all, Config).
+
+all_master_3(suite) -> [];
+all_master_3(Config) when is_list(Config) -> mnesia_down_3(all, Config).
+
+mnesia_down_2(Masters, Config) ->
+ Nodes = [N1, N2] = ?acquire_nodes(2, Config),
+ ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N1]}])),
+
+ Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8,
+ tab9, tab10, tab11, tab12, tab13, tab14, tab15],
+ [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes],
+ [insert_data(Tab, 20) || Tab <- Tabs],
+
+ VTabs =
+ case Masters of
+ no ->
+ Tabs -- [tab4, tab5]; % ram copies
+ one ->
+ ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])),
+ Tabs -- [tab1, tab4, tab5, tab10, tab12]; % ram_copies
+ two ->
+ ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])),
+ Tabs -- [tab4, tab5];
+ all ->
+ [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes],
+ Tabs -- [tab1, tab4, tab5, tab10, tab11, tab12, tab13]
+ end,
+
+ mnesia_test_lib:kill_mnesia([N1]),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs],
+ [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs],
+ ?verify_mnesia(Nodes, []).
+
+mnesia_down_3(Masters, Config) ->
+ Nodes = [N1, N2, N3] = ?acquire_nodes(3, Config),
+ ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(tab2, [{disc_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(tab3, [{disc_only_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(tab4, [{ram_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab5, [{ram_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab16, [{ram_copies, [N3]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab6, [{disc_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab7, [{disc_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab17, [{disc_copies, [N3]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab8, [{disc_only_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab9, [{disc_only_copies, [N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab18, [{disc_only_copies, [N3]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab10, [{ram_copies, [N1]}, {disc_copies, [N2, N3]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab11, [{ram_copies, [N2]}, {disc_copies, [N3, N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab19, [{ram_copies, [N3]}, {disc_copies, [N1, N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab12, [{ram_copies, [N1]}, {disc_only_copies, [N2, N3]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab13, [{ram_copies, [N2]}, {disc_only_copies, [N3, N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab20, [{ram_copies, [N3]}, {disc_only_copies, [N1, N2]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab14, [{disc_only_copies, [N1]}, {disc_copies, [N2, N3]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab15, [{disc_only_copies, [N2]}, {disc_copies, [N3, N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab21, [{disc_only_copies, [N3]}, {disc_copies, [N1, N2]}])),
+
+ Tabs = [tab1, tab2, tab3, tab4, tab5, tab6, tab7, tab8,
+ tab9, tab10, tab11, tab12, tab13, tab14, tab15,
+ tab16, tab17, tab18, tab19, tab20, tab21],
+ [?match(ok, rpc:call(Node, mnesia, wait_for_tables, [Tabs, 10000])) || Node <- Nodes],
+ [insert_data(Tab, 20) || Tab <- Tabs],
+
+ VTabs =
+ case Masters of
+ no ->
+ Tabs -- [tab4, tab5, tab16]; % ram copies
+ one ->
+ ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [[N1]])),
+ Tabs -- [tab1, tab4, tab5, tab16, tab10, tab12]; % ram copies
+ two ->
+ ?match(ok, rpc:call(N1, mnesia, set_master_nodes, [Nodes])),
+ Tabs -- [tab4, tab5, tab16]; % ram copies
+ all ->
+ [?match(ok, rpc:call(Node, mnesia, set_master_nodes, [[Node]])) || Node <- Nodes],
+ Tabs -- [tab1, tab4, tab5, tab16, tab10,
+ tab11, tab19, tab12, tab13, tab20] % ram copies
+ end,
+
+ mnesia_test_lib:kill_mnesia([N1]),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N3])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2, N1])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N2, N3])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ ?match([], mnesia_test_lib:kill_mnesia([N1, N3])),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, Tabs)),
+
+ [?match(ok, rpc:call(N1, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs],
+ [?match(ok, rpc:call(N2, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs],
+ [?match(ok, rpc:call(N3, ?MODULE, verify_data, [Tab, 20])) || Tab <- VTabs],
+
+ ?verify_mnesia(Nodes, []).
+
+
+read_during_down(doc) ->
+ ["Verify that read operation can continue to read when mnesia goes down"];
+read_during_down(suite) ->
+ [
+ dirty_read_during_down,
+ trans_read_during_down
+ ].
+
+dirty_read_during_down(suite) ->
+ [];
+dirty_read_during_down(Config) when is_list(Config) ->
+ read_during_down(dirty, Config).
+
+trans_read_during_down(suite) ->
+ [];
+trans_read_during_down(Config) when is_list(Config) ->
+ read_during_down(trans, Config).
+
+
+read_during_down(Op, Config) when is_list(Config) ->
+ Ns = [N1|TNs] = ?acquire_nodes(3, Config),
+ Tabs = [ram, disc, disco],
+
+ ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, TNs}])),
+ ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, TNs}])),
+ ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, TNs}])),
+
+ %% Create some work for mnesia_controller when a node goes down
+ [{atomic, ok} = mnesia:create_table(list_to_atom("temp" ++ integer_to_list(N)),
+ [{ram_copies, Ns}]) || N <- lists:seq(1, 50)],
+
+ Write = fun(Tab) -> mnesia:write({Tab, key, val}) end,
+ ?match([ok,ok,ok],
+ [mnesia:sync_dirty(Write, [Tab]) || Tab <- Tabs]),
+
+ Readers = [spawn_link(N1, ?MODULE, reader, [Tab, Op]) || Tab <- Tabs],
+ [_|_] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs],
+ ?log("W2R ~p~n", [W2R]),
+ loop_and_kill_mnesia(10, hd(W2R), Tabs),
+ [Pid ! self() || Pid <- Readers],
+ ?match([ok, ok, ok], [receive ok -> ok after 1000 -> {Pid, mnesia_lib:dist_coredump()} end || Pid <- Readers]),
+ ?verify_mnesia(Ns, []).
+
+reader(Tab, OP) ->
+ Res = case OP of
+ dirty ->
+ catch mnesia:dirty_read({Tab, key});
+ trans ->
+ Read = fun() -> mnesia:read({Tab, key}) end,
+ {_, Temp} = mnesia:transaction(Read),
+ Temp
+ end,
+ case Res of
+ [{Tab, key, val}] -> ok;
+ Else ->
+ ?error("Expected ~p Got ~p ~n", [[{Tab, key, val}], Else]),
+ erlang:error(test_failed)
+ end,
+ receive Pid ->
+ Pid ! ok
+ after 50 ->
+ reader(Tab, OP)
+ end.
+
+loop_and_kill_mnesia(0, _Node, _Tabs) -> ok;
+loop_and_kill_mnesia(N, Node, Tabs) ->
+ mnesia_test_lib:kill_mnesia([Node]),
+ timer:sleep(100),
+ ?match([], mnesia_test_lib:start_mnesia([Node], Tabs)),
+ [KN | _] = W2R= [mnesia:table_info(Tab, where_to_read) || Tab <- Tabs],
+ ?match([KN, KN,KN], W2R),
+ timer:sleep(100),
+ loop_and_kill_mnesia(N-1, KN, Tabs).
+
+mnesia_down_during_startup(doc) ->
+ ["Verify that mnesia can come back up again in a consistent state",
+ "after it has gone down during startup (with different store and",
+ "when it goes down in different situations"];
+mnesia_down_during_startup(suite) ->
+ [
+ mnesia_down_during_startup_disk_ram,
+ mnesia_down_during_startup_init_ram,
+ mnesia_down_during_startup_init_disc,
+ mnesia_down_during_startup_init_disc_only,
+ mnesia_down_during_startup_tm_ram,
+ mnesia_down_during_startup_tm_disc,
+ mnesia_down_during_startup_tm_disc_only
+ ].
+
+mnesia_down_during_startup_disk_ram(suite) -> [];
+mnesia_down_during_startup_disk_ram(Config) when is_list(Config)->
+ [Node1, Node2] = ?acquire_nodes(2, Config ++
+ [{tc_timeout, timer:minutes(2)}]),
+ Tab = down_during_startup,
+ Def = [{ram_copies, [Node2]}, {disc_copies, [Node1]}],
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match(ok, mnesia:dirty_write({Tab, 876234, test_ok})),
+ timer:sleep(500),
+ mnesia_test_lib:kill_mnesia([Node1, Node2]),
+ timer:sleep(500),
+ mnesia_test_lib:start_mnesia([Node1, Node2], [Tab]),
+ mnesia_test_lib:kill_mnesia([Node1]),
+ timer:sleep(500),
+ ?match([], mnesia_test_lib:start_mnesia([Node1], [Tab])),
+ ?match([{Tab, 876234, test_ok}], mnesia:dirty_read({Tab,876234})),
+ ?verify_mnesia([Node1, Node2], []).
+
+mnesia_down_during_startup_init_ram(suite) -> [];
+mnesia_down_during_startup_init_ram(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ DP = {mnesia_loader, do_get_network_copy},
+ Type = ram_copies,
+ mnesia_down_during_startup2(Config, Type, DP, self()).
+
+mnesia_down_during_startup_init_disc(suite) -> [];
+mnesia_down_during_startup_init_disc(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ DP = {mnesia_loader, do_get_network_copy},
+ Type = disc_copies,
+ mnesia_down_during_startup2(Config, Type, DP, self()).
+
+mnesia_down_during_startup_init_disc_only(suite) -> [];
+mnesia_down_during_startup_init_disc_only(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ DP = {mnesia_loader, do_get_network_copy},
+ Type = disc_only_copies,
+ mnesia_down_during_startup2(Config, Type, DP, self()).
+
+mnesia_down_during_startup_tm_ram(suite) -> [];
+mnesia_down_during_startup_tm_ram(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ DP = {mnesia_tm, init},
+ Type = ram_copies,
+ mnesia_down_during_startup2(Config, Type, DP, self()).
+
+mnesia_down_during_startup_tm_disc(suite) -> [];
+mnesia_down_during_startup_tm_disc(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ DP = {mnesia_tm, init},
+ Type = disc_copies,
+ mnesia_down_during_startup2(Config, Type, DP, self()).
+
+mnesia_down_during_startup_tm_disc_only(suite) -> [];
+mnesia_down_during_startup_tm_disc_only(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ DP = {mnesia_tm, init},
+ Type = disc_only_copies,
+ mnesia_down_during_startup2(Config, Type, DP, self()).
+
+mnesia_down_during_startup2(Config, ReplicaType, Debug_Point, _Father) ->
+ ?log("TC~n mnesia_down_during_startup with type ~w and stops at ~w~n",
+ [ReplicaType, Debug_Point]),
+ Tpcb_tabs = [history,teller,account,branch],
+ Nodes = ?acquire_nodes(2, Config),
+ Node1 = hd(Nodes),
+ {success, [A]} = ?start_activities([Node1]),
+ TpcbConfig = tpcb_config(ReplicaType, 2, Nodes),
+ mnesia_tpcb:init(TpcbConfig),
+ A ! fun () -> mnesia_tpcb:run(TpcbConfig) end,
+ ?match_receive(timeout),
+ timer:sleep(timer:seconds(10)), % Let tpcb run for a while
+ mnesia_tpcb:stop(),
+ ?match(ok, mnesia_tpcb:verify_tabs()),
+ mnesia_test_lib:kill_mnesia([Node1]),
+ timer:sleep(timer:seconds(2)),
+ Self = self(),
+ TestFun = fun(_MnesiaEnv, _EvalEnv) ->
+ ?deactivate_debug_fun(Debug_Point),
+ Self ! fun_done,
+ spawn(mnesia_test_lib, kill_mnesia, [[Node1]])
+ end,
+ ?activate_debug_fun(Debug_Point, TestFun, []), % Kill when debug has been reached
+ mnesia:start(),
+ Res = receive fun_done -> ok after timer:minutes(3) -> timeout end, % Wait till it's killed
+ ?match(ok, Res),
+ ?match(ok, timer:sleep(timer:seconds(2))), % Wait a while, at least till it dies;
+ ?match([], mnesia_test_lib:start_mnesia([Node1], Tpcb_tabs)),
+ ?match(ok, mnesia_tpcb:verify_tabs()), % Verify it
+ ?verify_mnesia(Nodes, []).
+
+
+with_checkpoint(doc) ->
+ ["Restart mnesia with checkpoint"];
+with_checkpoint(suite) ->
+ [with_checkpoint_same, with_checkpoint_other].
+
+with_checkpoint_same(suite) -> [];
+with_checkpoint_same(Config) when is_list(Config) ->
+ with_checkpoint(Config, same).
+
+with_checkpoint_other(suite) -> [];
+with_checkpoint_other(Config) when is_list(Config) ->
+ with_checkpoint(Config, other).
+
+with_checkpoint(Config, Type) when is_list(Config) ->
+ Nodes = [Node1, Node2] = ?acquire_nodes(2, Config),
+ Kill = case Type of
+ same -> %% Node1 is the one used for creating the checkpoint
+ Node1; %% and which we bring down
+ other ->
+ Node2 %% Here we bring node2 down..
+ end,
+
+ ?match({atomic, ok}, mnesia:create_table(ram, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(disc, [{disc_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(disco, [{disc_only_copies, Nodes}])),
+ Tabs = [ram, disc, disco],
+
+ ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune},
+ {max, mnesia:system_info(tables)},
+ {ram_overrides_dump, true}])),
+
+ ?match([], check_retainers(sune, Nodes)),
+
+ ?match(ok, mnesia:deactivate_checkpoint(sune)),
+ ?match([], check_chkp(Nodes)),
+
+ timer:sleep(500), %% Just to help debugging the io:formats now comes in the
+ %% correct order... :-)
+
+ ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune},
+ {max, mnesia:system_info(tables)},
+ {ram_overrides_dump, true}])),
+
+ [[mnesia:dirty_write({Tab,Key,Key}) || Key <- lists:seq(1,10)] || Tab <- Tabs],
+
+ mnesia_test_lib:kill_mnesia([Kill]),
+ timer:sleep(100),
+ mnesia_test_lib:start_mnesia([Kill], Tabs),
+ io:format("Mnesia on ~p started~n", [Kill]),
+ ?match([], check_retainers(sune, Nodes)),
+ ?match(ok, mnesia:deactivate_checkpoint(sune)),
+ ?match([], check_chkp(Nodes)),
+
+ case Kill of
+ Node1 ->
+ ignore;
+ Node2 ->
+ mnesia_test_lib:kill_mnesia([Kill]),
+ timer:sleep(500), %% Just to help debugging
+ ?match({ok, sune, _}, mnesia:activate_checkpoint([{name, sune},
+ {max, mnesia:system_info(tables)},
+ {ram_overrides_dump, true}])),
+
+ [[mnesia:dirty_write({Tab,Key,Key+2}) || Key <- lists:seq(1,10)] ||
+ Tab <- Tabs],
+
+ mnesia_test_lib:start_mnesia([Kill], Tabs),
+ io:format("Mnesia on ~p started ~n", [Kill]),
+ ?match([], check_retainers(sune, Nodes)),
+ ?match(ok, mnesia:deactivate_checkpoint(sune)),
+ ?match([], check_chkp(Nodes)),
+ ok
+ end,
+ ?verify_mnesia(Nodes, []).
+
+check_chkp(Nodes) ->
+ {Good, Bad} = rpc:multicall(Nodes, ?MODULE, check, []),
+ lists:flatten(Good ++ Bad).
+
+check() ->
+ [PCP] = ets:match_object(mnesia_gvar, {pending_checkpoint_pids, '_'}),
+ [PC] = ets:match_object(mnesia_gvar, {pending_checkpoints, '_'}),
+ [CPN] = ets:match_object(mnesia_gvar, {checkpoints, '_'}),
+ F = lists:filter(fun({_, []}) -> false; (_W) -> true end,
+ [PCP,PC,CPN]),
+ CPP = ets:match_object(mnesia_gvar, {{checkpoint, '_'}, '_'}),
+ Rt = ets:match_object(mnesia_gvar, {{'_', {retainer, '_'}}, '_'}),
+ F ++ CPP ++ Rt.
+
+
+check_retainers(CHP, Nodes) ->
+ {[R1,R2], []} = rpc:multicall(Nodes, ?MODULE, get_all_retainers, [CHP]),
+ (R1 -- R2) ++ (R2 -- R1).
+
+get_all_retainers(CHP) ->
+ Tabs = mnesia:system_info(local_tables),
+ Iter = fun(Tab) ->
+ {ok, Res} =
+ mnesia_checkpoint:iterate(CHP, Tab, fun(R, A) -> [R|A] end, [],
+ retainer, checkpoint),
+%% io:format("Retainer content ~w ~n", [Res]),
+ Res
+ end,
+ Elements = [Iter(Tab) || Tab <- Tabs],
+ lists:sort(lists:flatten(Elements)).
+
+delete_during_start(doc) ->
+ ["Test that tables can be delete during start, hopefully with tables"
+ " in the loader queue or soon to be"];
+delete_during_start(suite) -> [];
+delete_during_start(Config) when is_list(Config) ->
+ [N1, N2, N3] = Nodes = ?acquire_nodes(3, Config),
+ Tabs = [list_to_atom("tab" ++ integer_to_list(I)) || I <- lists:seq(1, 30)],
+ ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N2, ram_copies)),
+ ?match({atomic, ok}, mnesia:change_table_copy_type(schema, N3, ram_copies)),
+
+ [?match({atomic, ok},mnesia:create_table(Tab, [{ram_copies,Nodes}])) || Tab <- Tabs],
+ lists:foldl(fun(Tab, I) ->
+ ?match({atomic, ok},
+ mnesia:change_table_load_order(Tab,I)),
+ I+1
+ end, 1, Tabs),
+ mnesia_test_lib:kill_mnesia([N2,N3]),
+%% timer:sleep(500),
+ ?match({[ok,ok],[]}, rpc:multicall([N2,N3], mnesia,start,
+ [[{extra_db_nodes,[N1]}]])),
+ [Tab1,Tab2,Tab3|_] = Tabs,
+ ?match({atomic, ok}, mnesia:delete_table(Tab1)),
+ ?match({atomic, ok}, mnesia:delete_table(Tab2)),
+
+ ?log("W4T ~p~n", [rpc:multicall([N2,N3], mnesia, wait_for_tables, [[Tab1,Tab2,Tab3],1])]),
+
+ Remain = Tabs--[Tab1,Tab2],
+ ?match(ok, rpc:call(N2, mnesia, wait_for_tables, [Remain,10000])),
+ ?match(ok, rpc:call(N3, mnesia, wait_for_tables, [Remain,10000])),
+
+ ?match(ok, rpc:call(N2, ?MODULE, verify_where2read, [Remain])),
+ ?match(ok, rpc:call(N3, ?MODULE, verify_where2read, [Remain])),
+
+ ?verify_mnesia(Nodes, []).
+
+verify_where2read([Tab|Tabs]) ->
+ true = (node() == mnesia:table_info(Tab,where_to_read)),
+ verify_where2read(Tabs);
+verify_where2read([]) -> ok.
+
+
+%%-------------------------------------------------------------------------------------------
+explicit_stop(doc) ->
+ ["Stop Mnesia in different situations"];
+explicit_stop(suite) ->
+ [explicit_stop_during_snmp].
+%% This is a bad implementation, but at least gives a indication if something is wrong
+explicit_stop_during_snmp(suite) -> [];
+explicit_stop_during_snmp(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(2, Config),
+ [Node1, Node2] = Nodes,
+ Tab = snmp_tab,
+ Def = [{attributes, [key, value]},
+ {snmp, [{key, integer}]},
+ {mnesia_test_lib:storage_type(disc_copies, Config),
+ [Node1, Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write({Tab, 1, 1}) end)),
+
+ Do_trans_Pid1 = spawn_link(Node2, ?MODULE, do_trans_loop, [Tab, self()]),
+ Do_trans_Pid2 = spawn_link(?MODULE, do_trans_loop, [Tab, self()]),
+ Start_stop_Pid = spawn_link(?MODULE, start_stop, [Node1, 10, self()]),
+ receive
+ test_done ->
+ ok
+ after timer:minutes(5) ->
+ ?error("test case time out~n", [])
+ end,
+ ?verify_mnesia(Nodes, []),
+ exit(Do_trans_Pid1, kill),
+ exit(Do_trans_Pid2, kill),
+ exit(Start_stop_Pid, kill),
+ ok.
+
+do_trans_loop(Tab, Father) ->
+ %% Do not trap exit
+ do_trans_loop2(Tab, Father).
+do_trans_loop2(Tab, Father) ->
+ Trans =
+ fun() ->
+ [{Tab, 1, Val}] = mnesia:read({Tab, 1}),
+ mnesia:write({Tab, 1, Val + 1})
+ end,
+ case mnesia:transaction(Trans) of
+ {atomic, ok} ->
+ timer:sleep(200),
+ do_trans_loop2(Tab, Father);
+ {aborted, {node_not_running, N}} when N == node() ->
+ timer:sleep(200),
+ do_trans_loop2(Tab, Father);
+ {aborted, {no_exists, Tab}} ->
+ timer:sleep(200),
+ do_trans_loop2(Tab, Father);
+ Else ->
+ ?error("Transaction failed: ~p ~n", [Else]),
+ Father ! test_done,
+ exit(shutdown)
+ end.
+
+start_stop(_Node1, 0, Father) ->
+ Father ! test_done,
+ exit(shutdown);
+start_stop(Node1, N, Father) when N > 0->
+ timer:sleep(timer:seconds(5)),
+ ?match(stopped, rpc:call(Node1, mnesia, stop, [])),
+ timer:sleep(timer:seconds(2)),
+ ?match([], mnesia_test_lib:start_mnesia([Node1])),
+ start_stop(Node1, N-1, Father).
+
+coord_dies(suite) -> [];
+coord_dies(doc) -> [""];
+coord_dies(Config) when is_list(Config) ->
+ Nodes = [N1, N2] = ?acquire_nodes(2, Config),
+ ?match({atomic, ok}, mnesia:create_table(tab1, [{ram_copies, Nodes}])),
+ ?match({atomic, ok}, mnesia:create_table(tab2, [{ram_copies, [N1]}])),
+ ?match({atomic, ok}, mnesia:create_table(tab3, [{ram_copies, [N2]}])),
+ Tester = self(),
+
+ U1 = fun(Tab) ->
+ [{Tab,key,Val}] = mnesia:read(Tab,key,write),
+ mnesia:write({Tab,key, Val+1}),
+ Tester ! {self(),continue},
+ receive
+ continue -> exit(crash)
+ end
+ end,
+ U2 = fun(Tab) ->
+ [{Tab,key,Val}] = mnesia:read(Tab,key,write),
+ mnesia:write({Tab,key, Val+1}),
+ mnesia:transaction(U1, [Tab])
+ end,
+ [mnesia:dirty_write(Tab,{Tab,key,0}) || Tab <- [tab1,tab2,tab3]],
+ Pid1 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end),
+ Pid2 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end),
+ Pid3 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end),
+ [receive {Pid,continue} -> ok end || Pid <- [Pid1,Pid2,Pid3]],
+ Pid1 ! continue, Pid2 ! continue, Pid3 ! continue,
+ ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)),
+ ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)),
+ ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)),
+
+ Pid4 = spawn(fun() -> mnesia:transaction(U2, [tab1]) end),
+ Pid5 = spawn(fun() -> mnesia:transaction(U2, [tab2]) end),
+ Pid6 = spawn(fun() -> mnesia:transaction(U2, [tab3]) end),
+ erlang:monitor(process, Pid4),erlang:monitor(process, Pid5),erlang:monitor(process, Pid6),
+
+ [receive {Pid,continue} -> ok end || Pid <- [Pid4,Pid5,Pid6]],
+ exit(Pid4,crash),
+ ?match_receive({'DOWN',_,_,Pid4, _}),
+ ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab1,key}) end)),
+ exit(Pid5,crash),
+ ?match_receive({'DOWN',_,_,Pid5, _}),
+ ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab2,key}) end)),
+ exit(Pid6,crash),
+ ?match_receive({'DOWN',_,_,Pid6, _}),
+ ?match({atomic,[{_,key,1}]}, mnesia:transaction(fun() -> mnesia:read({tab3,key}) end)),
+
+ ?verify_mnesia(Nodes, []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sym_trans(doc) ->
+ ["Recovery of symmetrical transactions in a couple of different",
+ "situations; when coordinator or participant or node dies"];
+
+sym_trans(suite) ->
+ [sym_trans_before_commit_kill_coord_node, %% coordinator node dies
+ sym_trans_before_commit_kill_coord_pid, %% coordinator process dies
+ sym_trans_before_commit_kill_part_after_ask, %% participating node dies
+ sym_trans_before_commit_kill_part_before_ask,
+ sym_trans_after_commit_kill_coord_node,
+ sym_trans_after_commit_kill_coord_pid,
+ sym_trans_after_commit_kill_part_after_ask,
+ sym_trans_after_commit_kill_part_do_commit_pre,
+ sym_trans_after_commit_kill_part_do_commit_post].
+
+%kill_after_debug_point(Config, TestCase, {Debug_node, Debug_Point}, TransFun, Tab)
+
+sym_trans_before_commit_kill_coord_node(suite) -> [];
+sym_trans_before_commit_kill_coord_node(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_before_commit_kill_coord,
+ Def = [{attributes, [key, value]}, {ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym}},
+ do_sym_trans, [{Tab, Def}], Nodes).
+
+sym_trans_before_commit_kill_coord_pid(suite) -> [];
+sym_trans_before_commit_kill_coord_pid(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_before_commit_kill_coord,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym}},
+ do_sym_trans, [{Tab, Def}], Nodes).
+
+sym_trans_before_commit_kill_part_after_ask(suite) -> [];
+sym_trans_before_commit_kill_part_after_ask(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_before_commit_kill_part_after_ask,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym}},
+ do_sym_trans, [{Tab, Def}], Nodes).
+
+sym_trans_before_commit_kill_part_before_ask(suite) -> [];
+sym_trans_before_commit_kill_part_before_ask(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_before_commit_kill_part_before_ask,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}},
+ do_sym_trans, [{Tab, Def}], Nodes).
+
+sym_trans_after_commit_kill_coord_node(suite) -> [];
+sym_trans_after_commit_kill_coord_node(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_after_commit_kill_coord,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_sym, post}},
+ do_sym_trans, [{Tab, Def}], Nodes).
+
+sym_trans_after_commit_kill_coord_pid(suite) -> [];
+sym_trans_after_commit_kill_coord_pid(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_after_commit_kill_coord,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_sym, post}},
+ do_sym_trans, [{Tab,Def}], Nodes).
+
+sym_trans_after_commit_kill_part_after_ask(suite) -> [];
+sym_trans_after_commit_kill_part_after_ask(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_after_commit_kill_part_after_ask,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ kill_after_debug_point(Part1, {Coord, {mnesia_tm, multi_commit_sym, post}},
+ do_sym_trans, [{Tab, Def}], Nodes).
+
+sym_trans_after_commit_kill_part_do_commit_pre(suite) -> [];
+sym_trans_after_commit_kill_part_do_commit_pre(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_after_commit_kill_part_do_commit_pre,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sym_trans,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, pre}},
+ TransFun, [{Tab, Def}], Nodes).
+
+sym_trans_after_commit_kill_part_do_commit_post(suite) -> [];
+sym_trans_after_commit_kill_part_do_commit_post(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sym_trans_after_commit_kill_part_do_commit_post,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sym_trans,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, do_commit, post}},
+ TransFun, [{Tab, Def}], Nodes).
+
+do_sym_trans([Tab], _Fahter) ->
+ ?dl("Starting SYM_TRANS with active debug fun ", []),
+ Trans = fun() ->
+ [{_,_,Val}] = mnesia:read({Tab, 1}),
+ mnesia:write({Tab, 1, Val+1})
+ end,
+ Res = mnesia:transaction(Trans),
+ case Res of
+ {atomic, ok} -> ok;
+ {aborted, _Reason} -> ok;
+ Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n",
+ [Else])
+ end,
+ ?dl("SYM_TRANSACTION done: ~p (deactiv dbgfun) ", [Res]),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sync_dirty(doc) ->
+ ["Verify recovery of synchronously operations in a couple of different",
+ "situations"];
+sync_dirty(suite) ->
+ [sync_dirty_pre_kill_part,
+ sync_dirty_pre_kill_coord_node,
+ sync_dirty_pre_kill_coord_pid,
+ sync_dirty_post_kill_part,
+ sync_dirty_post_kill_coord_node,
+ sync_dirty_post_kill_coord_pid
+ ].
+
+sync_dirty_pre_kill_part(suite) -> [];
+sync_dirty_pre_kill_part(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sync_dirty_pre,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sync_dirty,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, pre}},
+ TransFun, [{Tab, Def}], Nodes).
+
+sync_dirty_pre_kill_coord_node(suite) -> [];
+sync_dirty_pre_kill_coord_node(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sync_dirty_pre,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sync_dirty,
+ kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, pre}},
+ TransFun, [{Tab, Def}], Nodes).
+
+sync_dirty_pre_kill_coord_pid(suite) -> [];
+sync_dirty_pre_kill_coord_pid(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sync_dirty_pre,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sync_dirty,
+ kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, pre}},
+ TransFun, [{Tab, Def}], Nodes).
+
+sync_dirty_post_kill_part(suite) -> [];
+sync_dirty_post_kill_part(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sync_dirty_post,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sync_dirty,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, sync_dirty, post}},
+ TransFun, [{Tab, Def}], Nodes).
+
+sync_dirty_post_kill_coord_node(suite) -> [];
+sync_dirty_post_kill_coord_node(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sync_dirty_post,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sync_dirty,
+ kill_after_debug_point(Coord, {Part1, {mnesia_tm, sync_dirty, post}},
+ TransFun, [{Tab, Def}], Nodes).
+
+sync_dirty_post_kill_coord_pid(suite) -> [];
+sync_dirty_post_kill_coord_pid(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = sync_dirty_post,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_sync_dirty,
+ kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, sync_dirty, post}},
+ TransFun, [{Tab, Def}], Nodes).
+
+do_sync_dirty([Tab], _Father) ->
+ ?dl("Starting SYNC_DIRTY", []),
+ SYNC = fun() ->
+ [{_,_,Val}] = mnesia:read({Tab, 1}),
+ mnesia:write({Tab, 1, Val+1})
+ end,
+ {_, Res} = ?match(ok, mnesia:sync_dirty(SYNC)),
+ ?dl("SYNC_DIRTY done: ~p ", [Res]),
+ ok.
+
+async_dirty(doc) ->
+ ["Verify recovery of asynchronously dirty operations in a couple of different",
+ "situations"];
+async_dirty(suite) ->
+ [async_dirty_pre_kill_part,
+ async_dirty_pre_kill_coord_node,
+ async_dirty_pre_kill_coord_pid,
+ async_dirty_post_kill_part,
+ async_dirty_post_kill_coord_node,
+ async_dirty_post_kill_coord_pid].
+
+async_dirty_pre_kill_part(suite) -> [];
+async_dirty_pre_kill_part(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = async_dirty_pre,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_async_dirty,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, pre}},
+ TransFun, [{Tab, Def}], Nodes).
+
+async_dirty_pre_kill_coord_node(suite) -> [];
+async_dirty_pre_kill_coord_node(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = async_dirty_pre,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_async_dirty,
+ kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, pre}},
+ TransFun, [{Tab, Def}], Nodes).
+
+async_dirty_pre_kill_coord_pid(suite) -> [];
+async_dirty_pre_kill_coord_pid(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = async_dirty_pre,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_async_dirty,
+ kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, pre}},
+ TransFun, [{Tab, Def}], Nodes).
+
+async_dirty_post_kill_part(suite) -> [];
+async_dirty_post_kill_part(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = async_dirty_post,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_async_dirty,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, async_dirty, post}},
+ TransFun, [{Tab, Def}], Nodes).
+
+async_dirty_post_kill_coord_node(suite) -> [];
+async_dirty_post_kill_coord_node(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = async_dirty_post,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_async_dirty,
+ kill_after_debug_point(Coord, {Part1, {mnesia_tm, async_dirty, post}},
+ TransFun, [{Tab, Def}], Nodes).
+
+async_dirty_post_kill_coord_pid(suite) -> [];
+async_dirty_post_kill_coord_pid(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab = async_dirty_post,
+ Def = [{attributes, [key, value]},{ram_copies, [Part2]},{disc_copies, [Coord, Part1]}],
+ TransFun = do_async_dirty,
+ kill_after_debug_point(coord_pid, {Part1, {mnesia_tm, async_dirty, post}},
+ TransFun, [{Tab, Def}], Nodes).
+
+do_async_dirty([Tab], _Fahter) ->
+ ?dl("Starting ASYNC", []),
+ ASYNC = fun() ->
+ [{_,_,Val}] = mnesia:read({Tab, 1}),
+ mnesia:write({Tab, 1, Val+1})
+ end,
+ {_, Res} = ?match(ok, mnesia:async_dirty(ASYNC)),
+ ?dl("ASYNC done: ~p ", [Res]),
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+asym_trans(doc) ->
+ ["Recovery of asymmetrical transactions in a couple of different",
+ "situations, currently the error cases are not covered, i.e. ",
+ "not tested are the situations when we kill mnesia or a process",
+ "during a recovery"];
+asym_trans(suite) ->
+ [
+ asym_trans_kill_part_ask,
+ asym_trans_kill_part_commit_vote,
+ asym_trans_kill_part_pre_commit,
+ asym_trans_kill_part_log_commit,
+ asym_trans_kill_part_do_commit,
+ asym_trans_kill_coord_got_votes,
+ asym_trans_kill_coord_pid_got_votes,
+ asym_trans_kill_coord_log_commit_rec,
+ asym_trans_kill_coord_pid_log_commit_rec,
+ asym_trans_kill_coord_log_commit_dec,
+ asym_trans_kill_coord_pid_log_commit_dec,
+ asym_trans_kill_coord_rec_acc_pre_commit_log_commit,
+ asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit,
+ asym_trans_kill_coord_rec_acc_pre_commit_done_commit,
+ asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit
+ ].
+
+asym_trans_kill_part_ask(suite) -> [];
+asym_trans_kill_part_ask(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, doit_ask_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_part_commit_vote(suite) -> [];
+asym_trans_kill_part_commit_vote(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, vote_yes}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_part_pre_commit(suite) -> [];
+asym_trans_kill_part_pre_commit(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, pre_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_part_log_commit(suite) -> [];
+asym_trans_kill_part_log_commit(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, log_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_part_do_commit(suite) -> [];
+asym_trans_kill_part_do_commit(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Part1, {Part1, {mnesia_tm, commit_participant, do_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_got_votes(suite) -> [];
+asym_trans_kill_coord_got_votes(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_got_votes}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_pid_got_votes(suite) -> [];
+asym_trans_kill_coord_pid_got_votes(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_got_votes}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_log_commit_rec(suite) -> [];
+asym_trans_kill_coord_log_commit_rec(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_pid_log_commit_rec(suite) -> [];
+asym_trans_kill_coord_pid_log_commit_rec(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_rec}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_log_commit_dec(suite) -> [];
+asym_trans_kill_coord_log_commit_dec(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Coord, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_pid_log_commit_dec(suite) -> [];
+asym_trans_kill_coord_pid_log_commit_dec(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, multi_commit_asym_log_commit_dec}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_rec_acc_pre_commit_log_commit(suite) -> [];
+asym_trans_kill_coord_rec_acc_pre_commit_log_commit(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(suite) -> [];
+asym_trans_kill_coord_pid_rec_acc_pre_commit_log_commit(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_log_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_rec_acc_pre_commit_done_commit(suite) -> [];
+asym_trans_kill_coord_rec_acc_pre_commit_done_commit(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(Coord, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(suite) -> [];
+asym_trans_kill_coord_pid_rec_acc_pre_commit_done_commit(Config) when is_list(Config) ->
+ ?is_debug_compiled,
+ Nodes = ?acquire_nodes(3, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ [Coord, Part1, Part2] = Nodes,
+ Tab1 = {asym1, [{ram_copies, [Part2]}, {disc_copies, [Coord]}]},
+ Tab2 = {asym2, [{ram_copies, [Coord]}, {disc_copies, [Part1]}]},
+ TransFun = do_asym_trans,
+ kill_after_debug_point(coord_pid, {Coord, {mnesia_tm, rec_acc_pre_commit_done_commit}},
+ TransFun, [Tab1, Tab2], Nodes).
+
+do_asym_trans([Tab1, Tab2 | _R], Garbhandler) ->
+ ?dl("Starting asym trans ", []),
+ ASym_Trans = fun() ->
+ TidTs = {_Mod, Tid, _Store} =
+ mnesia:get_activity_id(),
+ ?verbose("===> asym_trans: ~w~n", [TidTs]),
+ Garbhandler ! {trans_id, Tid},
+ [{_, _, Val1}] = mnesia:read({Tab1, 1}),
+ [{_, _, Val2}] = mnesia:read({Tab2, 1}),
+ mnesia:write({Tab1, 1, Val1+1}),
+ mnesia:write({Tab2, 1, Val2+1})
+ end,
+ Res = mnesia:transaction(ASym_Trans),
+ case Res of
+ {atomic, ok} -> ok;
+ {aborted, _Reason} -> ok;
+ _Else -> ?error("Wrong output from mensia:transaction(FUN):~n ~p~n", [Res])
+ end,
+ ?dl("Asym trans finished with: ~p ", [Res]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+kill_after_debug_point(Kill, {DebugNode, Debug_Point}, TransFun, TabsAndDefs, Nodes) ->
+ [Coord | _rest] = Nodes,
+
+ Create = fun({Tab, Def}) -> ?match({atomic, ok}, mnesia:create_table(Tab, Def)) end,
+ lists:foreach(Create, TabsAndDefs),
+ Tabs = [T || {T, _} <- TabsAndDefs],
+ Write = fun(Tab) -> ?match(ok, mnesia:dirty_write({Tab, 1, 100})) end,
+ lists:foreach(Write, Tabs),
+
+ Self = self(),
+ SyncFun = fun(_Env1, _Env2) -> % Just Sync with test prog
+ Self ! {self(), fun_in_position},
+ ?dl("SyncFun, sending fun_in_position ", []),
+ receive continue ->
+ ?dl("SyncFun received continue ",[]),
+ ok
+ after timer:seconds(60) ->
+ ?error("Timeout in sync_fun on ~p~n", [node()])
+ end
+ end,
+
+ Garb_handler = spawn_link(?MODULE, garb_handler, [[]]),
+
+ ?remote_activate_debug_fun(DebugNode, Debug_Point, SyncFun, []),
+ ?dl("fun_in_position activated at ~p with ~p", [DebugNode, Debug_Point]),
+ %% Spawn and do the transaction
+ Pid = spawn(Coord, ?MODULE, TransFun, [Tabs, Garb_handler]),
+ %% Wait till all the Nodes are in correct position
+ [{StoppedPid,_}] = ?receive_messages([fun_in_position]),
+ ?dl("Received fun_in_position; Removing the debug funs ~p", [DebugNode]),
+ ?remote_deactivate_debug_fun(DebugNode, Debug_Point),
+
+ case Kill of
+ coord_pid ->
+ ?dl("Intentionally killing pid ~p ", [Pid]),
+ exit(Pid, normal);
+ Node ->
+ mnesia_test_lib:kill_mnesia([Node])
+ end,
+
+ StoppedPid ! continue, %% Send continue, it may still be alive
+
+ %% Start and check that the databases are consistent
+ ?dl("Done, Restarting and verifying result ",[]),
+ case Kill of
+ coord_pid -> ok;
+ _ -> % Ok, mnesia on some node was killed restart it
+ timer:sleep(timer:seconds(3)), %% Just let it have the time to die
+ ?match(ok, rpc:call(Kill, mnesia, start, [[]])),
+ ?match(ok, rpc:call(Kill, mnesia, wait_for_tables, [Tabs, 60000]))
+ end,
+ Trans_res = verify_tabs(Tabs, Nodes),
+ case TransFun of
+ do_asym_trans ->
+ %% Verifies that decisions are garbed, only valid for asym_tran
+ Garb_handler ! {get_tids, self()},
+ Tid_list = receive
+ {tids, List} ->
+ ?dl("Fun rec ~w", [List]),
+ List
+ end,
+ garb_of_decisions(Kill, Nodes, Tid_list, Trans_res);
+ _ ->
+ ignore
+ end,
+ ?verify_mnesia(Nodes, []).
+
+garb_of_decisions(Kill, Nodes, Tid_list, Trans_res) ->
+ [Coord, Part1, Part2] = Nodes,
+ %% Check that decision log is empty on all nodes after the trans is finished
+ verify_garb_decision_log(Nodes, Tid_list),
+ case Trans_res of
+ aborted ->
+ %% Check that aborted trans have not been restarted!!
+ ?match(1, length(Tid_list)),
+ %% Check the transient decision logs
+ %% A transaction should only be aborted in an early stage of
+ %% the trans before the any Node have logged anything
+ verify_garb_transient_logs(Nodes, Tid_list, aborted),
+ %% And only when the coordinator are have died
+ %% Else he would have restarted the transaction
+ ?match(Kill, Coord);
+ updated ->
+ case length(Tid_list) of
+ 1 ->
+ %% If there was only one transaction, it should be logged as
+ %% comitted on every node!
+ [Tid1] = Tid_list,
+ verify_garb_transient_logs(Nodes, [Tid1], committed);
+ 2 ->
+ %% If there is two transaction id, then the first
+ %% TID should have been aborted and the transaction
+ %% restarted with a new TID
+ [Tid1, Tid2] = Tid_list,
+ verify_garb_transient_logs(Nodes, [Tid1], aborted),
+ %% If mnesia is killed on a node i.e Coord and Part1 than they
+ %% won't know about the restarted trans! The rest of the nodes
+ %% should know that the trans was committed
+ case Kill of
+ coord_pid ->
+ verify_garb_transient_logs(Nodes, [Tid2], committed);
+ Coord ->
+ verify_garb_transient_logs([Part1, Part2], [Tid2], committed),
+ verify_garb_transient_logs([Coord], [Tid2], not_found);
+ Part1 ->
+ verify_garb_transient_logs([Coord, Part2], [Tid2], committed),
+ verify_garb_transient_logs([Part1], [Tid2], not_found)
+ end
+ end
+ end.
+
+verify_garb_decision_log([], _Tids) -> ok;
+verify_garb_decision_log([Node|R], Tids) ->
+ Check = fun(Tid) -> %% Node, Tid used in debugging!
+ ?match({{not_found, _}, Node, Tid},
+ {outcome(Tid, [mnesia_decision]), Node, Tid})
+ end,
+ rpc:call(Node, lists, foreach, [Check, Tids]),
+ verify_garb_decision_log(R, Tids).
+
+verify_garb_transient_logs([], _Tids, _) -> ok;
+verify_garb_transient_logs([Node|R], Tids, Exp_Res) ->
+ Check = fun(Tid) ->
+ LatestTab = mnesia_lib:val(latest_transient_decision),
+ PrevTabs = mnesia_lib:val(previous_transient_decisions),
+ case outcome(Tid, [LatestTab | PrevTabs]) of
+ {found, {_, [{_,_Tid, Exp_Res}]}} -> ok;
+ {not_found, _} when Exp_Res == not_found -> ok;
+ {not_found, _} when Exp_Res == aborted -> ok;
+ Else -> ?error("Expected ~p in trans ~p on ~p got ~p~n",
+ [Exp_Res, Tid, Node, Else])
+ end
+ end,
+ rpc:call(Node, lists, foreach, [Check, Tids]),
+ verify_garb_transient_logs(R, Tids, Exp_Res).
+
+outcome(Tid, Tabs) ->
+ outcome(Tid, Tabs, Tabs).
+
+outcome(Tid, [Tab | Tabs], AllTabs) ->
+ case catch ets:lookup(Tab, Tid) of
+ {'EXIT', _} ->
+ outcome(Tid, Tabs, AllTabs);
+ [] ->
+ outcome(Tid, Tabs, AllTabs);
+ Val ->
+ {found, {Tab, Val}}
+ end;
+outcome(_Tid, [], AllTabs) ->
+ {not_found, AllTabs}.
+
+
+verify_tabs([Tab|R], Nodes) ->
+ [_Coord, Part1, Part2 | _rest] = Nodes,
+ Read = fun() -> mnesia:read({Tab, 1}) end,
+ {success, A} = ?match({atomic, _}, mnesia:transaction(Read)),
+ ?match(A, rpc:call(Part1, mnesia, transaction, [Read])),
+ ?match(A, rpc:call(Part2, mnesia, transaction, [Read])),
+ {atomic, [{Tab, 1, Res}]} = A,
+ verify_tabs(R, Nodes, Res).
+
+verify_tabs([], _Nodes, Res) ->
+ case Res of
+ 100 -> aborted;
+ 101 -> updated
+ end;
+
+verify_tabs([Tab | Rest], Nodes, Res) ->
+ [Coord, Part1, Part2 | _rest] = Nodes,
+ Read = fun() -> mnesia:read({Tab, 1}) end,
+ Exp = {atomic, [{Tab, 1, Res}]},
+ ?match(Exp, rpc:call(Coord, mnesia, transaction, [Read])),
+ ?match(Exp, rpc:call(Part1, mnesia, transaction, [Read])),
+ ?match(Exp, rpc:call(Part2, mnesia, transaction, [Read])),
+ verify_tabs(Rest, Nodes, Res).
+
+%% Gather TIDS and send them to requesting process and exit!
+garb_handler(List) ->
+ receive
+ {trans_id, ID} -> garb_handler([ID|List]);
+ {get_tids, Pid} -> Pid ! {tids, lists:reverse(List)}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%
+receive_messages([], _File, _Line) -> [];
+receive_messages(ListOfMsgs, File, Line) ->
+ receive
+ {Pid, Msg} ->
+ case lists:member(Msg, ListOfMsgs) of
+ false ->
+ mnesia_test_lib:log("<>WARNING<>~n"
+ "Received unexpected msg~n ~p ~n"
+ "While waiting for ~p~n",
+ [{Pid, Msg}, ListOfMsgs], File, Line),
+ receive_messages(ListOfMsgs, File, Line);
+ true ->
+ ?dl("Got msg ~p from ~p ", [Msg, node(Pid)]),
+ [{Pid, Msg} | receive_messages(ListOfMsgs -- [Msg], File, Line)]
+ end;
+ Else -> mnesia_test_lib:log("<>WARNING<>~n"
+ "Recevied unexpected or bad formatted msg~n ~p ~n"
+ "While waiting for ~p~n",
+ [Else, ListOfMsgs], File, Line),
+ receive_messages(ListOfMsgs, File, Line)
+ after timer:minutes(2) ->
+ ?error("Timeout in receive msgs while waiting for ~p~n",
+ [ListOfMsgs])
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+after_full_disc_partition(doc) ->
+ ["Verify that the database does not get corrupt",
+ "when Mnesia encounters a full disc partition"].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% interrupted_fallback_start
+%% is implemented in consistency interupted_install_fallback!
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+after_corrupt_files(doc) ->
+ ["Verify that mnesia (and dets) can handle corrupt files"];
+after_corrupt_files(suite) -> % cope with unsynced disks
+ [after_corrupt_files_decision_log_head,
+ after_corrupt_files_decision_log_tail,
+ after_corrupt_files_latest_log_head,
+ after_corrupt_files_latest_log_tail,
+ after_corrupt_files_table_dat_head,
+ after_corrupt_files_table_dat_tail,
+ after_corrupt_files_schema_dat_head,
+ after_corrupt_files_schema_dat_tail
+ ].
+
+after_corrupt_files_decision_log_head(suite) -> [];
+after_corrupt_files_decision_log_head(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "DECISION.LOG", head, repair).
+
+after_corrupt_files_decision_log_tail(suite) -> [];
+after_corrupt_files_decision_log_tail(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "DECISION.LOG", tail, repair).
+
+after_corrupt_files_latest_log_head(suite) -> [];
+after_corrupt_files_latest_log_head(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "LATEST.LOG", head, repair).
+
+after_corrupt_files_latest_log_tail(suite) -> [];
+after_corrupt_files_latest_log_tail(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "LATEST.LOG", tail, repair).
+
+after_corrupt_files_table_dat_head(suite) -> [];
+after_corrupt_files_table_dat_head(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "rec_files.DAT", head, crash).
+
+after_corrupt_files_table_dat_tail(suite) -> [];
+after_corrupt_files_table_dat_tail(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "rec_files.DAT", tail, repair).
+
+after_corrupt_files_schema_dat_head(suite) -> [];
+after_corrupt_files_schema_dat_head(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "schema.DAT", head, crash).
+
+after_corrupt_files_schema_dat_tail(suite) -> [];
+after_corrupt_files_schema_dat_tail(Config) when is_list(Config) ->
+ after_corrupt_files(Config, "schema.DAT", tail, crash).
+
+
+
+%%% BUGBUG: We should also write testcase's for autorepair=false i.e.
+%%% not the standard case!
+after_corrupt_files(Config, File, Where, Behaviour) ->
+ [Node] = ?acquire_nodes(1, Config ++ [{tc_timeout, timer:minutes(2)}]),
+ Tab = rec_files,
+ Def = [{disc_only_copies, [Node]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab, Def)),
+ insert_data(Tab, 100),
+ Dir = mnesia:system_info(directory),
+ mnesia_test_lib:kill_mnesia([Node]),
+ timer:sleep(timer:seconds(10)), % Let dets finish whatever it does
+
+ DirFile = Dir ++ "/" ++ File,
+
+ {ok, Fd} = file:open(DirFile, read_write),
+ {ok, FileInfo} = file:read_file_info(DirFile),
+ case Where of
+ head ->
+ ?match({ok, _NewP}, file:position(Fd, {bof, 1})),
+ ?match(ok, file:write(Fd, [255, 255, 255, 255, 255, 255, 255, 255, 254])),
+ ok;
+ tail ->
+ Size = FileInfo#file_info.size,
+ Half = Size div 2,
+
+ ?dl(" Size = ~p Half = ~p ", [Size, Half]),
+ ?match({ok, _NewP}, file:position(Fd, {bof, Half})),
+ ?match(ok, file:truncate(Fd)),
+ ok
+ end,
+ ?match(ok, file:close(Fd)),
+
+ ?warning("++++++SOME OF THE after_corrupt* TEST CASES WILL INTENTIONALLY CRASH MNESIA+++++++~n", []),
+ Pid = spawn_link(?MODULE, mymnesia_start, [self()]),
+ receive
+ {Pid, ok} ->
+ ?match(ok, mnesia:wait_for_tables([schema, Tab], 10000)),
+ ?match(ok, verify_data(Tab, 100)),
+ case mnesia_monitor:get_env(auto_repair) of
+ false ->
+ ?error("Mnesia should have crashed in ~p ~p ~n",
+ [File, Where]);
+ true ->
+ ok
+ end,
+ ?verify_mnesia([Node], []);
+ {Pid, {error, ED}} ->
+ case {mnesia_monitor:get_env(auto_repair), Behaviour} of
+ {true, repair} ->
+ ?error("Mnesia crashed with ~p: in ~p ~p ~n",
+ [ED, File, Where]);
+ _ -> %% Every other can crash!
+ ok
+ end,
+ ?verify_mnesia([], [Node]);
+ Msg ->
+ ?error("~p ~p: Got ~p during start of Mnesia~n",
+ [File, Where, Msg])
+ end.
+
+mymnesia_start(Tester) ->
+ Res = mnesia:start(),
+ unlink(Tester),
+ Tester ! {self(), Res}.
+
+verify_data(_, 0) -> ok;
+verify_data(Tab, N) ->
+ Actual = mnesia:dirty_read({Tab, N}),
+ Expected = [{Tab, N, N}],
+ if
+ Expected == Actual ->
+ verify_data(Tab, N - 1);
+ true ->
+ mnesia:schema(Tab),
+ {not_equal, node(), Expected, Actual}
+ end.
+
+insert_data(_Tab, 0) -> ok;
+insert_data(Tab, N) ->
+ ok = mnesia:sync_dirty(fun() -> mnesia:write({Tab, N, N}) end),
+ insert_data(Tab, N-1).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+disc_less(doc) ->
+ ["Here is a simple test case of a simple recovery of a disc less node. "
+ "However a lot more test cases involving disc less nodes should "
+ "be written"];
+disc_less(suite) -> [];
+disc_less(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ case mnesia_test_lib:diskless(Config) of
+ true -> skip;
+ false ->
+ ?match({atomic, ok}, mnesia:change_table_copy_type(schema, Node3, ram_copies))
+ end,
+ Tab1 = disc_less1,
+ Tab2 = disc_less2,
+ Tab3 = disc_less3,
+ Def1 = [{ram_copies, [Node3]}, {disc_copies, [Node1, Node2]}],
+ Def2 = [{ram_copies, [Node3]}, {disc_copies, [Node1]}],
+ Def3 = [{ram_copies, [Node3]}, {disc_copies, [Node2]}],
+ ?match({atomic, ok}, mnesia:create_table(Tab1, Def1)),
+ ?match({atomic, ok}, mnesia:create_table(Tab2, Def2)),
+ ?match({atomic, ok}, mnesia:create_table(Tab3, Def3)),
+ insert_data(Tab1, 100),
+ insert_data(Tab2, 100),
+ insert_data(Tab3, 100),
+
+ mnesia_test_lib:kill_mnesia([Node1, Node2]),
+ timer:sleep(500),
+ mnesia_test_lib:kill_mnesia([Node3]),
+ ?match(ok, rpc:call(Node1, mnesia, start, [])),
+ ?match(ok, rpc:call(Node2, mnesia, start, [])),
+
+ timer:sleep(500),
+ ?match(ok, rpc:call(Node3, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])),
+ ?match(ok, rpc:call(Node3, mnesia, wait_for_tables, [[Tab1, Tab2, Tab3], 20000])),
+
+ ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab1, 100])),
+ ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab2, 100])),
+ ?match(ok, rpc:call(Node3, ?MODULE, verify_data, [Tab3, 100])),
+
+
+ ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab1, 100])),
+ ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab2, 100])),
+ ?match(ok, rpc:call(Node2, ?MODULE, verify_data, [Tab3, 100])),
+
+ ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab1, 100])),
+ ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab2, 100])),
+ ?match(ok, rpc:call(Node1, ?MODULE, verify_data, [Tab3, 100])),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+system_upgrade(doc) ->
+ ["Test on-line and off-line upgrade of the Mnesia application"].
+
+garb_decision(doc) ->
+ ["Test that decisions are garbed correctly."];
+garb_decision(suite) -> [];
+garb_decision(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ check_garb(Nodes),
+ ?match({atomic, ok},mnesia:create_table(a, [{disc_copies, Nodes}])),
+ check_garb(Nodes),
+ ?match({atomic, ok},mnesia:create_table(b, [{ram_copies, Nodes}])),
+ check_garb(Nodes),
+ ?match({atomic, ok},mnesia:create_table(c, [{ram_copies, [Node1, Node3]},
+ {disc_copies, [Node2]}])),
+ check_garb(Nodes),
+ ?match({atomic, ok},mnesia:create_table(d, [{disc_copies, [Node1, Node3]},
+ {ram_copies, [Node2]}])),
+ check_garb(Nodes),
+
+ W = fun(Tab) -> mnesia:write({Tab,1,1}) end,
+ A = fun(Tab) -> mnesia:write({Tab,1,1}), exit(1) end,
+
+ ?match({atomic, ok}, mnesia:transaction(W,[a])),
+ check_garb(Nodes),
+ ?match({atomic, ok}, mnesia:transaction(W,[b])),
+ check_garb(Nodes),
+ ?match({atomic, ok}, mnesia:transaction(W,[c])),
+ check_garb(Nodes),
+ ?match({atomic, ok}, mnesia:transaction(W,[d])),
+ check_garb(Nodes),
+ ?match({aborted,1}, mnesia:transaction(A,[a])),
+ check_garb(Nodes),
+ ?match({aborted,1}, mnesia:transaction(A,[b])),
+ check_garb(Nodes),
+ ?match({aborted,1}, mnesia:transaction(A,[c])),
+ check_garb(Nodes),
+ ?match({aborted,1}, mnesia:transaction(A,[d])),
+ check_garb(Nodes),
+
+ rpc:call(Node2, mnesia, lkill, []),
+ ?match({atomic, ok}, mnesia:transaction(W,[a])),
+ ?match({atomic, ok}, mnesia:transaction(W,[b])),
+ ?match({atomic, ok}, mnesia:transaction(W,[c])),
+ ?match({atomic, ok}, mnesia:transaction(W,[d])),
+ check_garb(Nodes),
+ ?match([], mnesia_test_lib:start_mnesia([Node2])),
+ check_garb(Nodes),
+ timer:sleep(2000),
+ check_garb(Nodes),
+ %%%%%% Check transient_decision logs %%%%%
+
+ ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync
+ [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)],
+ ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync
+ TD0 = mnesia_lib:val(latest_transient_decision),
+ ?match(0, ets:info(TD0, size)),
+ {atomic, ok} = mnesia:transaction(W,[a]),
+ ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync
+ ?match(TD0, mnesia_lib:val(latest_transient_decision)),
+ [{atomic, ok} = mnesia:transaction(W,[a]) || _ <- lists:seq(1,30)],
+ ?match(dumped, mnesia:dump_log()), sys:get_status(mnesia_recover), % sync
+ ?match(false, TD0 =:= mnesia_lib:val(latest_transient_decision)),
+ ?match(true, lists:member(TD0, mnesia_lib:val(previous_transient_decisions))),
+ ?verify_mnesia(Nodes, []).
+
+check_garb(Nodes) ->
+ rpc:multicall(Nodes, sys, get_status, [mnesia_recover]),
+ ?match({_, []},rpc:multicall(Nodes, erlang, apply, [fun check_garb/0, []])).
+
+check_garb() ->
+ try
+ Ds = ets:tab2list(mnesia_decision),
+ Check = fun({trans_tid,serial, _}) -> false;
+ ({mnesia_down,_,_,_}) -> false;
+ (_Else) -> true
+ end,
+ Node = node(),
+ ?match({Node, []}, {node(), lists:filter(Check, Ds)})
+ catch _:_ -> ok
+ end,
+ ok.
diff --git a/lib/mnesia/test/mnesia_registry_test.erl b/lib/mnesia/test/mnesia_registry_test.erl
new file mode 100644
index 0000000000..2305ef93b7
--- /dev/null
+++ b/lib/mnesia/test/mnesia_registry_test.erl
@@ -0,0 +1,137 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-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(mnesia_registry_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) ->
+ ["Test the mnesia_registry module"];
+all(suite) ->
+ [
+ good_dump,
+ bad_dump
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+good_dump(doc) ->
+ ["Dump a faked C-node registry"];
+good_dump(suite) -> [];
+good_dump(Config) when is_list(Config) ->
+ [Node] = Nodes = ?acquire_nodes(1, Config),
+ T1 = gordon,
+ ?match(ok, mnesia_registry:create_table(T1)),
+ One = {T1, 1, 0, integer, 0, 10},
+ Two = {T1, "two", 3, integer, 0, 20},
+ Three = {T1, 3, 0, string, 6, "thirty"},
+ ?match(ok, mnesia:dirty_write(One)),
+ ?match(ok, mnesia:dirty_write(Two)),
+ ?match(ok, mnesia:dirty_write(Three)),
+ ?match([One], mnesia:dirty_read({T1, 1})),
+ ?match([_ | _], dump_registry(Node, T1)),
+
+ NewOne = {T1, 1, 0, integer, 0, 1},
+ NewFour = {T1, "4", 1, string, 4, "four"},
+
+ ?match([NewOne], mnesia:dirty_read({T1, 1})),
+ ?match([Two], mnesia:dirty_read({T1, "two"})),
+ ?match([], mnesia:dirty_read({T1, 3})),
+ ?match([NewFour], mnesia:dirty_read({T1, "4"})),
+
+ T2 = blixt,
+ ?match({'EXIT', {aborted, {no_exists, _}}},
+ mnesia:dirty_read({T2, 1})),
+ ?match([_ |_], dump_registry(Node, T2)),
+
+ NewOne2 = setelement(1, NewOne, T2),
+ NewFour2 = setelement(1, NewFour, T2),
+
+ ?match([NewOne2], mnesia:dirty_read({T2, 1})),
+ ?match([], mnesia:dirty_read({T2, "two"})),
+ ?match([], mnesia:dirty_read({T2, 3})),
+ ?match([NewFour2], mnesia:dirty_read({T2, "4"})),
+ ?match([_One2, NewFour2], lists:sort(restore_registry(Node, T2))),
+
+ ?verify_mnesia(Nodes, []).
+
+dump_registry(Node, Tab) ->
+ case rpc:call(Node, mnesia_registry, start_dump, [Tab, self()]) of
+ Pid when is_pid(Pid) ->
+ Pid ! {write, 1, 0, integer, 0, 1},
+ Pid ! {delete, 3},
+ Pid ! {write, "4", 1, string, 4, "four"},
+ Pid ! {commit, self()},
+ receive
+ {ok, Pid} ->
+ [{Tab, "4", 1, string, 4, "four"},
+ {Tab, 1, 0, integer, 0, 1}];
+ {'EXIT', Pid, Reason} ->
+ exit(Reason)
+ end;
+ {badrpc, Reason} ->
+ exit(Reason)
+ end.
+
+restore_registry(Node, Tab) ->
+ case rpc:call(Node, mnesia_registry, start_restore, [Tab, self()]) of
+ {size, Pid, N, _LargestKeySize, _LargestValSize} ->
+ Pid ! {send_records, self()},
+ receive_records(Tab, N);
+ {badrpc, Reason} ->
+ exit(Reason)
+ end.
+
+receive_records(Tab, N) when N > 0 ->
+ receive
+ {restore, KeySize, ValSize, ValType, Key, Val} ->
+ [{Tab, Key, KeySize, ValType, ValSize, Val} | receive_records(Tab, N -1)];
+ {'EXIT', _Pid, Reason} ->
+ exit(Reason)
+ end;
+receive_records(_Tab, 0) ->
+ [].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+bad_dump(doc) ->
+ ["Intentionally fail with the dump of a faked C-node registry"];
+bad_dump(suite) -> [];
+bad_dump(Config) when is_list(Config) ->
+ [Node] = Nodes = ?acquire_nodes(1, Config),
+
+ OldTab = ming,
+ ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())),
+ ?match({atomic, ok}, mnesia:create_table(OldTab, [{attributes, [a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q]}])),
+ ?match({'EXIT',{aborted,{bad_type,_}}}, dump_registry(Node, OldTab)),
+ ?match(stopped, mnesia:stop()),
+
+ ?match({'EXIT', {aborted, _}}, mnesia_registry:create_table(down_table)),
+ ?match({'EXIT', {aborted, _}}, mnesia_registry:start_restore(no_tab, self())),
+ ?match({'EXIT', {aborted, _}}, dump_registry(Node, down_dump)),
+
+ ?verify_mnesia([], Nodes).
+
diff --git a/lib/mnesia/test/mnesia_schema_recovery_test.erl b/lib/mnesia/test/mnesia_schema_recovery_test.erl
new file mode 100644
index 0000000000..387238ae6b
--- /dev/null
+++ b/lib/mnesia/test/mnesia_schema_recovery_test.erl
@@ -0,0 +1,787 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1998-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(mnesia_schema_recovery_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).
+
+-define(receive_messages(Msgs), receive_messages(Msgs, ?FILE, ?LINE)).
+
+% First Some debug logging
+-define(dgb, true).
+-ifdef(dgb).
+-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)).
+-else.
+-define(dl(X, Y), ok).
+-endif.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all(doc) ->
+ ["Verify recoverabiliy of schema transactions.",
+ " Verify that a schema transaction",
+ " can be completed when it has been logged correctly and Mnesia",
+ " crashed before the log has been dumped. Then the transaction ",
+ " should be handled during the log dump at startup"
+ ];
+all(suite) ->
+ [interrupted_before_log_dump,
+ interrupted_after_log_dump].
+
+interrupted_before_log_dump(suite) ->
+ [interrupted_before_create_ram,
+ interrupted_before_create_disc,
+ interrupted_before_create_disc_only,
+ interrupted_before_create_nostore,
+ interrupted_before_delete_ram,
+ interrupted_before_delete_disc,
+ interrupted_before_delete_disc_only,
+ interrupted_before_add_ram,
+ interrupted_before_add_disc,
+ interrupted_before_add_disc_only,
+ interrupted_before_add_kill_copier,
+ interrupted_before_move_ram,
+ interrupted_before_move_disc,
+ interrupted_before_move_disc_only,
+ interrupted_before_move_kill_copier,
+ interrupted_before_delcopy_ram,
+ interrupted_before_delcopy_disc,
+ interrupted_before_delcopy_disc_only,
+ interrupted_before_delcopy_kill_copier,
+ interrupted_before_addindex_ram,
+ interrupted_before_addindex_disc,
+ interrupted_before_addindex_disc_only,
+ interrupted_before_delindex_ram,
+ interrupted_before_delindex_disc,
+ interrupted_before_delindex_disc_only,
+ interrupted_before_change_type_ram2disc,
+ interrupted_before_change_type_ram2disc_only,
+ interrupted_before_change_type_disc2ram,
+ interrupted_before_change_type_disc2disc_only,
+ interrupted_before_change_type_disc_only2ram,
+ interrupted_before_change_type_disc_only2disc,
+ interrupted_before_change_type_other_node,
+ interrupted_before_change_schema_type %% Change schema table copy type!!
+ ].
+
+interrupted_after_log_dump(suite) ->
+ [interrupted_after_create_ram,
+ interrupted_after_create_disc,
+ interrupted_after_create_disc_only,
+ interrupted_after_create_nostore,
+ interrupted_after_delete_ram,
+ interrupted_after_delete_disc,
+ interrupted_after_delete_disc_only,
+ interrupted_after_add_ram,
+ interrupted_after_add_disc,
+ interrupted_after_add_disc_only,
+ interrupted_after_add_kill_copier,
+ interrupted_after_move_ram,
+ interrupted_after_move_disc,
+ interrupted_after_move_disc_only,
+ interrupted_after_move_kill_copier,
+ interrupted_after_delcopy_ram,
+ interrupted_after_delcopy_disc,
+ interrupted_after_delcopy_disc_only,
+ interrupted_after_delcopy_kill_copier,
+ interrupted_after_addindex_ram,
+ interrupted_after_addindex_disc,
+ interrupted_after_addindex_disc_only,
+ interrupted_after_delindex_ram,
+ interrupted_after_delindex_disc,
+ interrupted_after_delindex_disc_only,
+ interrupted_after_change_type_ram2disc,
+ interrupted_after_change_type_ram2disc_only,
+ interrupted_after_change_type_disc2ram,
+ interrupted_after_change_type_disc2disc_only,
+ interrupted_after_change_type_disc_only2ram,
+ interrupted_after_change_type_disc_only2disc,
+ interrupted_after_change_type_other_node,
+ interrupted_after_change_schema_type %% Change schema table copy type!!
+
+% interrupted_before_change_access_mode,
+% interrupted_before_transform,
+% interrupted_before_restore,
+ ].
+
+interrupted_before_create_ram(suite) -> [];
+interrupted_before_create_ram(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, dump_schema_op},
+ interrupted_create(Config, ram_copies, all, KillAt).
+
+interrupted_before_create_disc(suite) -> [];
+interrupted_before_create_disc(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, dump_schema_op},
+ interrupted_create(Config, disc_copies, all, KillAt).
+
+interrupted_before_create_disc_only(suite) -> [];
+interrupted_before_create_disc_only(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, dump_schema_op},
+ interrupted_create(Config, disc_only_copies, all, KillAt).
+
+interrupted_before_create_nostore(suite) -> [];
+interrupted_before_create_nostore(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, dump_schema_op},
+ interrupted_create(Config, ram_copies, one, KillAt).
+
+interrupted_after_create_ram(suite) -> [];
+interrupted_after_create_ram(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, post_dump},
+ interrupted_create(Config, ram_copies, all, KillAt).
+
+interrupted_after_create_disc(suite) -> [];
+interrupted_after_create_disc(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, post_dump},
+ interrupted_create(Config, disc_copies, all, KillAt).
+
+interrupted_after_create_disc_only(suite) -> [];
+interrupted_after_create_disc_only(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, post_dump},
+ interrupted_create(Config, disc_only_copies, all, KillAt).
+
+interrupted_after_create_nostore(suite) -> [];
+interrupted_after_create_nostore(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, post_dump},
+ interrupted_create(Config, ram_copies, one, KillAt).
+
+%%% After dump don't need debug point
+interrupted_create(Config, Type, _Where, {mnesia_dumper, post_dump}) ->
+ [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]),
+ ?match({atomic, ok},mnesia:create_table(itrpt, [{Type, Nodes}])),
+ ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])),
+ ?match(ok, mnesia:dirty_write({itrpt, before, 1})),
+ ?match(ok, mnesia:dirty_write({test, found_in_log, 1})),
+ ?match(stopped, mnesia:stop()),
+ ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])),
+ %% Verify
+ ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})),
+ case Type of
+ ram_copies ->
+ ?match([], mnesia:dirty_read({itrpt, before}));
+ _ ->
+ ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before}))
+ end,
+ ?verify_mnesia(Nodes, []);
+interrupted_create(Config, Type, Where, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ {success, [A]} = ?start_activities([Node2]),
+ setup_dbgpoint(KillAt, Node2),
+
+ if %% CREATE TABLE
+ Where == all -> % tables on both nodes
+ A ! fun() -> mnesia:create_table(itrpt, [{Type, Nodes}]) end;
+ true -> % no table on the killed node
+ A ! fun() -> mnesia:create_table(itrpt, [{Type, [Node1]}]) end
+ end,
+
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia([Node2], [itrpt])),
+ %% Verify
+ ?match(ok, mnesia:dirty_write({itrpt, before, 1})),
+ verify_tab(Node1, Node2),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_delete_ram(suite) -> [];
+interrupted_before_delete_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delete(Config, ram_copies, Debug_Point).
+interrupted_before_delete_disc(suite) -> [];
+interrupted_before_delete_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delete(Config, disc_copies, Debug_Point).
+interrupted_before_delete_disc_only(suite) -> [];
+interrupted_before_delete_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delete(Config, disc_only_copies, Debug_Point).
+
+interrupted_after_delete_ram(suite) -> [];
+interrupted_after_delete_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delete(Config, ram_copies, Debug_Point).
+interrupted_after_delete_disc(suite) -> [];
+interrupted_after_delete_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delete(Config, disc_copies, Debug_Point).
+interrupted_after_delete_disc_only(suite) -> [];
+interrupted_after_delete_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delete(Config, disc_only_copies, Debug_Point).
+
+interrupted_delete(Config, Type, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+ {_Alive, Kill} = {Node1, Node2},
+ {success, [A]} = ?start_activities([Kill]),
+
+ setup_dbgpoint(KillAt, Kill),
+ A ! fun() -> mnesia:delete_table(Tab) end,
+
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia([Node2], [])),
+ Bad = {badrpc, {'EXIT', {aborted,{no_exists, Tab, all}}}},
+ ?match(Bad, rpc:call(Node1, mnesia, table_info, [Tab, all])),
+ ?match(Bad, rpc:call(Node2, mnesia, table_info, [Tab, all])),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_add_ram(suite) -> [];
+interrupted_before_add_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_add(Config, ram_copies, kill_reciever, Debug_Point).
+interrupted_before_add_disc(suite) -> [];
+interrupted_before_add_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_add(Config, disc_copies, kill_reciever, Debug_Point).
+interrupted_before_add_disc_only(suite) -> [];
+interrupted_before_add_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point).
+interrupted_before_add_kill_copier(suite) -> [];
+interrupted_before_add_kill_copier(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_add(Config, ram_copies, kill_copier, Debug_Point).
+
+interrupted_after_add_ram(suite) -> [];
+interrupted_after_add_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_add(Config, ram_copies, kill_reciever, Debug_Point).
+interrupted_after_add_disc(suite) -> [];
+interrupted_after_add_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_add(Config, disc_copies, kill_reciever, Debug_Point).
+interrupted_after_add_disc_only(suite) -> [];
+interrupted_after_add_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_add(Config, disc_only_copies, kill_reciever, Debug_Point).
+interrupted_after_add_kill_copier(suite) -> [];
+interrupted_after_add_kill_copier(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_add(Config, ram_copies, kill_copier, Debug_Point).
+
+%%% After dump don't need debug point
+interrupted_add(Config, Type, _Where, {mnesia_dumper, post_dump}) ->
+ [Node1, Node2] = Nodes =
+ ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node2]}, {local_content,true}])),
+ ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])),
+ ?match({atomic, ok}, mnesia:add_table_copy(Tab, Node1, Type)),
+ ?match(ok, mnesia:dirty_write({itrpt, before, 1})),
+ ?match(ok, mnesia:dirty_write({test, found_in_log, 1})),
+ ?match(stopped, mnesia:stop()),
+ ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])),
+ %% Verify
+ ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})),
+ case Type of
+ ram_copies ->
+ ?match([], mnesia:dirty_read({itrpt, before}));
+ _ ->
+ ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before}))
+ end,
+ ?verify_mnesia(Nodes, []);
+interrupted_add(Config, Type, Who, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes =
+ ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ {_Alive, Kill} =
+ if Who == kill_reciever ->
+ {Node1, Node2};
+ true ->
+ {Node2, Node1}
+ end,
+ {success, [A]} = ?start_activities([Kill]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+
+ setup_dbgpoint(KillAt, Kill),
+
+ A ! fun() -> mnesia:add_table_copy(Tab, Node2, Type) end,
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])),
+ verify_tab(Node1, Node2),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_move_ram(suite) -> [];
+interrupted_before_move_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_move(Config, ram_copies, kill_reciever, Debug_Point).
+interrupted_before_move_disc(suite) -> [];
+interrupted_before_move_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_move(Config, disc_copies, kill_reciever, Debug_Point).
+interrupted_before_move_disc_only(suite) -> [];
+interrupted_before_move_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point).
+interrupted_before_move_kill_copier(suite) -> [];
+interrupted_before_move_kill_copier(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_move(Config, ram_copies, kill_copier, Debug_Point).
+
+interrupted_after_move_ram(suite) -> [];
+interrupted_after_move_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_move(Config, ram_copies, kill_reciever, Debug_Point).
+interrupted_after_move_disc(suite) -> [];
+interrupted_after_move_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_move(Config, disc_copies, kill_reciever, Debug_Point).
+interrupted_after_move_disc_only(suite) -> [];
+interrupted_after_move_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_move(Config, disc_only_copies, kill_reciever, Debug_Point).
+interrupted_after_move_kill_copier(suite) -> [];
+interrupted_after_move_kill_copier(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_move(Config, ram_copies, kill_copier, Debug_Point).
+
+%%% After dump don't need debug point
+interrupted_move(Config, Type, _Where, {mnesia_dumper, post_dump}) ->
+ [Node1, Node2] = Nodes =
+ ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])),
+ ?match(ok, mnesia:dirty_write({itrpt, before, 1})),
+ ?match({atomic, ok}, mnesia:move_table_copy(Tab, Node1, Node2)),
+ ?match(ok, mnesia:dirty_write({itrpt, aFter, 1})),
+ ?match(ok, mnesia:dirty_write({test, found_in_log, 1})),
+ ?match(stopped, mnesia:stop()),
+ ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])),
+ %% Verify
+ ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})),
+ ?match([{itrpt, before, 1}], mnesia:dirty_read({itrpt, before})),
+ ?match([{itrpt, aFter, 1}], mnesia:dirty_read({itrpt, aFter})),
+ ?verify_mnesia(Nodes, []);
+interrupted_move(Config, Type, Who, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes =
+ ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+
+ {_Alive, Kill} =
+ if Who == kill_reciever ->
+ if Type == ram_copies ->
+ {atomic, ok} = mnesia:dump_tables([Tab]);
+ true ->
+ ignore
+ end,
+ {Node1, Node2};
+ true ->
+ {Node2, Node1}
+ end,
+
+ {success, [A]} = ?start_activities([Kill]),
+
+ setup_dbgpoint(KillAt, Kill),
+ A ! fun() -> mnesia:move_table_copy(Tab, Node1, Node2) end,
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])),
+ verify_tab(Node1, Node2),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_delcopy_ram(suite) -> [];
+interrupted_before_delcopy_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point).
+interrupted_before_delcopy_disc(suite) -> [];
+interrupted_before_delcopy_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point).
+interrupted_before_delcopy_disc_only(suite) -> [];
+interrupted_before_delcopy_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point).
+interrupted_before_delcopy_kill_copier(suite) -> [];
+interrupted_before_delcopy_kill_copier(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point).
+
+interrupted_after_delcopy_ram(suite) -> [];
+interrupted_after_delcopy_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delcopy(Config, ram_copies, kill_reciever, Debug_Point).
+interrupted_after_delcopy_disc(suite) -> [];
+interrupted_after_delcopy_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delcopy(Config, disc_copies, kill_reciever, Debug_Point).
+interrupted_after_delcopy_disc_only(suite) -> [];
+interrupted_after_delcopy_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delcopy(Config, disc_only_copies, kill_reciever, Debug_Point).
+interrupted_after_delcopy_kill_copier(suite) -> [];
+interrupted_after_delcopy_kill_copier(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delcopy(Config, ram_copies, kill_copier, Debug_Point).
+
+
+%%% After dump don't need debug point
+interrupted_delcopy(Config, Type, _Where, {mnesia_dumper, post_dump}) ->
+ [Node1, Node2] = Nodes =
+ ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok},mnesia:create_table(test, [{disc_copies,[Node1]}])),
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1,Node2]}])),
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, Node1)),
+ ?match(ok, mnesia:dirty_write({test, found_in_log, 1})),
+ ?match(stopped, mnesia:stop()),
+ ?match([], mnesia_test_lib:start_mnesia([Node1], [test])),
+ %% Verify
+ ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})),
+ ?match([Node2], mnesia:table_info(itrpt,Type)),
+ ?verify_mnesia(Nodes, []);
+interrupted_delcopy(Config, Type, Who, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes =
+ ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1, Node2]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+
+ {_Alive, Kill} =
+ if Who == kill_reciever ->
+ {Node1, Node2};
+ true ->
+ if
+ Type == ram_copies ->
+ {atomic, ok} = mnesia:dump_tables([Tab]);
+ true ->
+ ignore
+ end,
+ {Node2, Node1}
+ end,
+
+ {success, [A]} = ?start_activities([Kill]),
+ setup_dbgpoint(KillAt, Kill),
+ A ! fun() -> mnesia:del_table_copy(Tab, Node2) end,
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia([Kill], [itrpt])),
+ verify_tab(Node1, Node2),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_addindex_ram(suite) -> [];
+interrupted_before_addindex_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_addindex(Config, ram_copies, Debug_Point).
+interrupted_before_addindex_disc(suite) -> [];
+interrupted_before_addindex_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_addindex(Config, disc_copies, Debug_Point).
+interrupted_before_addindex_disc_only(suite) -> [];
+interrupted_before_addindex_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_addindex(Config, disc_only_copies, Debug_Point).
+
+interrupted_after_addindex_ram(suite) -> [];
+interrupted_after_addindex_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_addindex(Config, ram_copies, Debug_Point).
+interrupted_after_addindex_disc(suite) -> [];
+interrupted_after_addindex_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_addindex(Config, disc_copies, Debug_Point).
+interrupted_after_addindex_disc_only(suite) -> [];
+interrupted_after_addindex_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_addindex(Config, disc_only_copies, Debug_Point).
+
+
+%%% After dump don't need debug point
+interrupted_addindex(Config, Type, {mnesia_dumper, post_dump}) ->
+ [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes}])),
+ ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])),
+ ?match({atomic,ok}, mnesia:add_table_index(Tab, val)),
+ ?match(ok, mnesia:dirty_write({itrpt, before, 1})),
+ ?match(ok, mnesia:dirty_write({test, found_in_log, 1})),
+ ?match(stopped, mnesia:stop()),
+ ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])),
+ %% Verify
+ ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})),
+ case Type of
+ ram_copies ->
+ ?match([], mnesia:dirty_index_read(itrpt, 1, val));
+ _ ->
+ ?match([{itrpt, before, 1}], mnesia:dirty_index_read(itrpt, 1, val))
+ end,
+ ?verify_mnesia(Nodes, []);
+interrupted_addindex(Config, Type, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{Type, [Node1]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+ {_Alive, Kill} = {Node1, Node2},
+ {success, [A]} = ?start_activities([Kill]),
+
+ setup_dbgpoint(KillAt, Kill),
+ A ! fun() -> mnesia:add_table_index(Tab, val) end,
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia([Node2], [])),
+
+ verify_tab(Node1, Node2),
+ ?match([{Tab, b, a}, {Tab, a, a}],
+ rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])),
+ ?match([{Tab, b, a}, {Tab, a, a}],
+ rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_delindex_ram(suite) -> [];
+interrupted_before_delindex_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delindex(Config, ram_copies, Debug_Point).
+interrupted_before_delindex_disc(suite) -> [];
+interrupted_before_delindex_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delindex(Config, disc_copies, Debug_Point).
+interrupted_before_delindex_disc_only(suite) -> [];
+interrupted_before_delindex_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_delindex(Config, disc_only_copies, Debug_Point).
+
+interrupted_after_delindex_ram(suite) -> [];
+interrupted_after_delindex_ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delindex(Config, ram_copies, Debug_Point).
+interrupted_after_delindex_disc(suite) -> [];
+interrupted_after_delindex_disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delindex(Config, disc_copies, Debug_Point).
+interrupted_after_delindex_disc_only(suite) -> [];
+interrupted_after_delindex_disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_delindex(Config, disc_only_copies, Debug_Point).
+
+%%% After dump don't need debug point
+interrupted_delindex(Config, Type, {mnesia_dumper, post_dump}) ->
+ [Node1] = Nodes = ?acquire_nodes(1, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic,ok},mnesia:create_table(Tab, [{Type, Nodes},{index,[val]}])),
+ ?match({atomic,ok},mnesia:create_table(test, [{disc_copies,[Node1]}])),
+ ?match({atomic,ok}, mnesia:del_table_index(Tab, val)),
+ ?match(ok, mnesia:dirty_write({itrpt, before, 1})),
+ ?match(ok, mnesia:dirty_write({test, found_in_log, 1})),
+ ?match(stopped, mnesia:stop()),
+ ?match([], mnesia_test_lib:start_mnesia([Node1], [itrpt,test])),
+ %% Verify
+ ?match([{test, found_in_log, 1}], mnesia:dirty_read({test, found_in_log})),
+ ?match({'EXIT',{aborted,{badarg,_}}}, mnesia:dirty_index_read(itrpt, 1, val)),
+ ?verify_mnesia(Nodes, []);
+
+interrupted_delindex(Config, Type, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{index, [val]},
+ {Type, [Node1]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+ {_Alive, Kill} = {Node1, Node2},
+ {success, [A]} = ?start_activities([Kill]),
+ setup_dbgpoint(KillAt, Kill),
+ A ! fun() -> mnesia:del_table_index(Tab, val) end,
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia([Node2], [])),
+ verify_tab(Node1, Node2),
+ ?match({badrpc, _}, rpc:call(Node1, mnesia, dirty_index_read, [itrpt, a, val])),
+ ?match({badrpc, _}, rpc:call(Node2, mnesia, dirty_index_read, [itrpt, a, val])),
+ ?match([], rpc:call(Node1, mnesia, table_info, [Tab, index])),
+ ?match([], rpc:call(Node2, mnesia, table_info, [Tab, index])),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_change_type_ram2disc(suite) -> [];
+interrupted_before_change_type_ram2disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point).
+interrupted_before_change_type_ram2disc_only(suite) -> [];
+interrupted_before_change_type_ram2disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point).
+interrupted_before_change_type_disc2ram(suite) -> [];
+interrupted_before_change_type_disc2ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point).
+interrupted_before_change_type_disc2disc_only(suite) -> [];
+interrupted_before_change_type_disc2disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point).
+interrupted_before_change_type_disc_only2ram(suite) -> [];
+interrupted_before_change_type_disc_only2ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point).
+interrupted_before_change_type_disc_only2disc(suite) -> [];
+interrupted_before_change_type_disc_only2disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point).
+interrupted_before_change_type_other_node(suite) -> [];
+interrupted_before_change_type_other_node(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, dump_schema_op},
+ interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point).
+
+interrupted_after_change_type_ram2disc(suite) -> [];
+interrupted_after_change_type_ram2disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_change_type(Config, ram_copies, disc_copies, changer, Debug_Point).
+interrupted_after_change_type_ram2disc_only(suite) -> [];
+interrupted_after_change_type_ram2disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_change_type(Config, ram_copies, disc_only_copies, changer, Debug_Point).
+interrupted_after_change_type_disc2ram(suite) -> [];
+interrupted_after_change_type_disc2ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_change_type(Config, disc_copies, ram_copies, changer, Debug_Point).
+interrupted_after_change_type_disc2disc_only(suite) -> [];
+interrupted_after_change_type_disc2disc_only(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_change_type(Config, disc_copies, disc_only_copies, changer, Debug_Point).
+interrupted_after_change_type_disc_only2ram(suite) -> [];
+interrupted_after_change_type_disc_only2ram(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_change_type(Config, disc_only_copies, ram_copies, changer, Debug_Point).
+interrupted_after_change_type_disc_only2disc(suite) -> [];
+interrupted_after_change_type_disc_only2disc(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_change_type(Config, disc_only_copies, disc_copies, changer, Debug_Point).
+interrupted_after_change_type_other_node(suite) -> [];
+interrupted_after_change_type_other_node(Config) when is_list(Config) ->
+ Debug_Point = {mnesia_dumper, post_dump},
+ interrupted_change_type(Config, ram_copies, disc_copies, the_other_one, Debug_Point).
+
+interrupted_change_type(Config, FromType, ToType, Who, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{FromType, [Node2, Node1]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+
+ {_Alive, Kill} =
+ if Who == changer -> {Node1, Node2};
+ true -> {Node2, Node1}
+ end,
+
+ {success, [A]} = ?start_activities([Kill]),
+ setup_dbgpoint(KillAt, Kill),
+ A ! fun() -> mnesia:change_table_copy_type(Tab, Node2, ToType) end,
+ kill_at_debug(),
+ ?match([], mnesia_test_lib:start_mnesia(Nodes, [itrpt])),
+ verify_tab(Node1, Node2),
+ ?match(FromType, rpc:call(Node1, mnesia, table_info, [Tab, storage_type])),
+ ?match(ToType, rpc:call(Node2, mnesia, table_info, [Tab, storage_type])),
+ ?verify_mnesia(Nodes, []).
+
+interrupted_before_change_schema_type(suite) -> [];
+interrupted_before_change_schema_type(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, dump_schema_op},
+ interrupted_change_schema_type(Config, KillAt).
+
+interrupted_after_change_schema_type(suite) -> [];
+interrupted_after_change_schema_type(Config) when is_list(Config) ->
+ KillAt = {mnesia_dumper, post_dump},
+ interrupted_change_schema_type(Config, KillAt).
+
+-define(cleanup(N, Config),
+ mnesia_test_lib:prepare_test_case([{reload_appls, [mnesia]}],
+ N, Config, ?FILE, ?LINE)).
+
+interrupted_change_schema_type(Config, KillAt) ->
+ ?is_debug_compiled,
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, [{tc_timeout, timer:seconds(30)} | Config]),
+
+ Tab = itrpt,
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{ram_copies, [Node2, Node1]}])),
+ ?match(ok, mnesia:dirty_write({Tab, before, 1})),
+
+ {success, [A]} = ?start_activities([Node2]),
+ setup_dbgpoint(KillAt, Node2),
+
+ A ! fun() -> mnesia:change_table_copy_type(schema, Node2, ram_copies) end,
+ kill_at_debug(),
+ ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])),
+ ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])),
+ ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])),
+ ?match(ram_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])),
+
+ %% Go back to disc_copies !!
+ {success, [B]} = ?start_activities([Node2]),
+ setup_dbgpoint(KillAt, Node2),
+ B ! fun() -> mnesia:change_table_copy_type(schema, Node2, disc_copies) end,
+ kill_at_debug(),
+
+ ?match(ok, rpc:call(Node2, mnesia, start, [[{extra_db_nodes, [Node1, Node2]}]])),
+ ?match(ok, rpc:call(Node2, mnesia, wait_for_tables, [[itrpt, schema], 2000])),
+ ?match(disc_copies, rpc:call(Node1, mnesia, table_info, [schema, storage_type])),
+ ?match(disc_copies, rpc:call(Node2, mnesia, table_info, [schema, storage_type])),
+
+ ?verify_mnesia(Nodes, []),
+ ?cleanup(2, Config).
+
+%%% Helpers
+verify_tab(Node1, Node2) ->
+ ?match({atomic, ok},
+ rpc:call(Node1, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, a, a}) end])),
+ ?match({atomic, ok},
+ rpc:call(Node2, mnesia, transaction, [fun() -> mnesia:dirty_write({itrpt, b, a}) end])),
+ ?match([{itrpt,a,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, a}])),
+ ?match([{itrpt,a,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, a}])),
+ ?match([{itrpt,b,a}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, b}])),
+ ?match([{itrpt,b,a}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, b}])),
+ ?match([{itrpt,before,1}], rpc:call(Node1, mnesia, dirty_read, [{itrpt, before}])),
+ ?match([{itrpt,before,1}], rpc:call(Node2, mnesia, dirty_read, [{itrpt, before}])).
+
+setup_dbgpoint(DbgPoint, Where) ->
+ Self = self(),
+ TestFun = fun(_, [InitBy]) ->
+ case InitBy of
+ schema_prepare ->
+ ignore;
+ schema_begin ->
+ ignore;
+ _Other ->
+ ?deactivate_debug_fun(DbgPoint),
+ unlink(Self),
+ Self ! {fun_done, node()},
+ timer:sleep(infinity)
+ end
+ end,
+ %% Kill when debug has been reached
+ ?remote_activate_debug_fun(Where, DbgPoint, TestFun, []).
+
+kill_at_debug() ->
+ %% Wait till it's killed
+ receive
+ {fun_done, Node} ->
+ ?match([], mnesia_test_lib:kill_mnesia([Node]))
+ after
+ timer:minutes(1) -> ?error("Timeout in kill_at_debug", [])
+ end.
+
diff --git a/lib/mnesia/test/mnesia_test_lib.erl b/lib/mnesia/test/mnesia_test_lib.erl
new file mode 100644
index 0000000000..1e98f017f7
--- /dev/null
+++ b/lib/mnesia/test/mnesia_test_lib.erl
@@ -0,0 +1,1058 @@
+%%
+%% %CopyrightBegin%
+%%
+%% 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
+%% 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%
+%%
+
+%%
+%%% Author: Hakan Mattsson [email protected]
+%%% Purpose: Test case support library
+%%%
+%%% This test suite may be run as a part of the Grand Test Suite
+%%% of Erlang. The Mnesia test suite is structured in a hierarchy.
+%%% Each test case is implemented as an exported function with arity 1.
+%%% Test case identifiers must have the following syntax: {Module, Function}.
+%%%
+%%% The driver of the test suite runs in two passes as follows:
+%%% first the test case function is invoked with the atom 'suite' as
+%%% single argument. The returned value is treated as a list of sub
+%%% test cases. If the list of sub test cases is [] the test case
+%%% function is invoked again, this time with a list of nodes as
+%%% argument. If the list of sub test cases is not empty, the test
+%%% case driver applies the algorithm recursively on each element
+%%% in the list.
+%%%
+%%% All test cases are written in such a manner
+%%% that they start to invoke ?acquire_nodes(X, Config)
+%%% in order to prepare the test case execution. When that is
+%%% done, the test machinery ensures that at least X number
+%%% of nodes are connected to each other. If too few nodes was
+%%% specified in the Config, the test case is skipped. If there
+%%% was enough node names in the Config, X of them are selected
+%%% and if some of them happens to be down they are restarted
+%%% via the slave module. When all nodes are up and running a
+%%% disk resident schema is created on all nodes and Mnesia is
+%%% started a on all nodes. This means that all test cases may
+%%% assume that Mnesia is up and running on all acquired nodes.
+%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%%
+%%% doc(TestCases)
+%%%
+%%% Generates a test spec from parts of the test case structure
+%%%
+%%% struct(TestCases)
+%%%
+%%% Prints out the test case structure
+%%%
+%%% test(TestCases)
+%%%
+%%% Run parts of the test suite. Uses test/2.
+%%% Reads Config from mnesia_test.config and starts them if neccessary.
+%%% Kills Mnesia and wipes out the Mnesia directories as a starter.
+%%%
+%%% test(TestCases, Config)
+%%%
+%%% Run parts of the test suite on the given Nodes,
+%%% assuming that the nodes are up and running.
+%%% Kills Mnesia and wipes out the Mnesia directories as a starter.
+%%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-module(mnesia_test_lib).
+-author('[email protected]').
+-export([
+ log/2,
+ log/4,
+ verbose/4,
+ default_config/0,
+ diskless/1,
+ eval_test_case/3,
+ test_driver/2,
+ test_case_evaluator/3,
+ activity_evaluator/1,
+ flush/0,
+ pick_msg/0,
+ start_activities/1,
+ start_transactions/1,
+ start_transactions/2,
+ start_sync_transactions/1,
+ start_sync_transactions/2,
+ sync_trans_tid_serial/1,
+ prepare_test_case/5,
+ select_nodes/4,
+ init_nodes/3,
+ error/4,
+ slave_start_link/0,
+ slave_start_link/1,
+ slave_sup/0,
+
+ start_mnesia/1,
+ start_mnesia/2,
+ start_appls/2,
+ start_appls/3,
+ start_wait/2,
+ storage_type/2,
+ stop_mnesia/1,
+ stop_appls/2,
+ sort/1,
+ kill_mnesia/1,
+ kill_appls/2,
+ verify_mnesia/4,
+ shutdown/0,
+ verify_replica_location/5,
+ lookup_config/2,
+ sync_tables/2,
+ remote_start/3,
+ remote_stop/1,
+ remote_kill/1,
+
+ reload_appls/2,
+
+ remote_activate_debug_fun/6,
+ do_remote_activate_debug_fun/6,
+
+ test/1,
+ test/2,
+ doc/1,
+ struct/1,
+ init_per_testcase/2,
+ fin_per_testcase/2,
+ kill_tc/2
+ ]).
+
+-include("mnesia_test_lib.hrl").
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% included for test server compatibility
+%% assume that all test cases only takes Config as sole argument
+init_per_testcase(_Func, Config) ->
+ global:register_name(mnesia_global_logger, group_leader()),
+ Config.
+
+fin_per_testcase(_Func, Config) ->
+ global:unregister_name(mnesia_global_logger),
+ %% Nodes = select_nodes(all, Config, ?FILE, ?LINE),
+ %% rpc:multicall(Nodes, mnesia, lkill, []),
+ Config.
+
+%% Use ?log(Format, Args) as wrapper
+log(Format, Args, LongFile, Line) ->
+ File = filename:basename(LongFile),
+ Format2 = lists:concat([File, "(", Line, ")", ": ", Format]),
+ log(Format2, Args).
+
+log(Format, Args) ->
+ case global:whereis_name(mnesia_global_logger) of
+ undefined ->
+ io:format(user, Format, Args);
+ Pid ->
+ io:format(Pid, Format, Args)
+ end.
+
+verbose(Format, Args, File, Line) ->
+ Arg = mnesia_test_verbose,
+ case get(Arg) of
+ false ->
+ ok;
+ true ->
+ log(Format, Args, File, Line);
+ undefined ->
+ case init:get_argument(Arg) of
+ {ok, List} when is_list(List) ->
+ case lists:last(List) of
+ ["true"] ->
+ put(Arg, true),
+ log(Format, Args, File, Line);
+ _ ->
+ put(Arg, false),
+ ok
+ end;
+ _ ->
+ put(Arg, false),
+ ok
+ end
+ end.
+
+-record('REASON', {file, line, desc}).
+
+error(Format, Args, File, Line) ->
+ global:send(mnesia_global_logger, {failed, File, Line}),
+ Fail = #'REASON'{file = filename:basename(File),
+ line = Line,
+ desc = Args},
+ case global:whereis_name(mnesia_test_case_sup) of
+ undefined ->
+ ignore;
+ Pid ->
+ Pid ! Fail
+%% global:send(mnesia_test_case_sup, Fail),
+ end,
+ log("<>ERROR<>~n" ++ Format, Args, File, Line).
+
+storage_type(Default, Config) ->
+ case diskless(Config) of
+ true ->
+ ram_copies;
+ false ->
+ Default
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+default_config() ->
+ [{nodes, default_nodes()}].
+
+default_nodes() ->
+ mk_nodes(3, []).
+
+mk_nodes(0, Nodes) ->
+ Nodes;
+mk_nodes(N, []) ->
+ mk_nodes(N - 1, [node()]);
+mk_nodes(N, Nodes) when N > 0 ->
+ Head = hd(Nodes),
+ [Name, Host] = node_to_name_and_host(Head),
+ Nodes ++ [mk_node(I, Name, Host) || I <- lists:seq(1, N)].
+
+mk_node(N, Name, Host) ->
+ list_to_atom(lists:concat([Name ++ integer_to_list(N) ++ "@" ++ Host])).
+
+slave_start_link() ->
+ slave_start_link(node()).
+
+slave_start_link(Node) ->
+ [Local, Host] = node_to_name_and_host(Node),
+ {Mega, Sec, Micro} = erlang:now(),
+ List = [Local, "_", Mega, "_", Sec, "_", Micro],
+ Name = list_to_atom(lists:concat(List)),
+ slave_start_link(list_to_atom(Host), Name).
+
+slave_start_link(Host, Name) ->
+ slave_start_link(Host, Name, 10).
+
+slave_start_link(Host, Name, Retries) ->
+ Debug = atom_to_list(mnesia:system_info(debug)),
+ Args = "-mnesia debug " ++ Debug ++
+ " -pa " ++
+ filename:dirname(code:which(?MODULE)) ++
+ " -pa " ++
+ filename:dirname(code:which(mnesia)),
+ case starter(Host, Name, Args) of
+ {ok, NewNode} ->
+ ?match(pong, net_adm:ping(NewNode)),
+ {ok, Cwd} = file:get_cwd(),
+ Path = code:get_path(),
+ ok = rpc:call(NewNode, file, set_cwd, [Cwd]),
+ true = rpc:call(NewNode, code, set_path, [Path]),
+ spawn_link(NewNode, ?MODULE, slave_sup, []),
+ rpc:multicall([node() | nodes()], global, sync, []),
+ {ok, NewNode};
+ {error, Reason} when Retries == 0->
+ {error, Reason};
+ {error, Reason} ->
+ io:format("Could not start slavenode ~p ~p retrying~n",
+ [{Host, Name, Args}, Reason]),
+ timer:sleep(500),
+ slave_start_link(Host, Name, Retries - 1)
+ end.
+
+starter(Host, Name, Args) ->
+ case os:type() of
+ vxworks ->
+ X = test_server:start_node(Name, slave, [{args,Args}]),
+ timer:sleep(5000),
+ X;
+ _ ->
+ slave:start(Host, Name, Args)
+ end.
+
+slave_sup() ->
+ process_flag(trap_exit, true),
+ receive
+ {'EXIT', _, _} ->
+ case os:type() of
+ vxworks ->
+ erlang:halt();
+ _ ->
+ ignore
+ end
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Index the test case structure
+
+doc(TestCases) when is_list(TestCases) ->
+ test(TestCases, suite),
+ SuiteFname = "index.html",
+ io:format("Generating HTML test specification to file: ~s~n",
+ [SuiteFname]),
+ {ok, Fd} = file:open(SuiteFname, [write]),
+ io:format(Fd, "<TITLE>Test specification for ~p</TITLE>.~n", [TestCases]),
+ io:format(Fd, "<H1>Test specification for ~p</H1>~n", [TestCases]),
+ io:format(Fd, "Test cases which not are implemented yet are written in <B>bold face</B>.~n~n", []),
+
+ io:format(Fd, "<BR><BR>~n", []),
+ io:format(Fd, "~n<DL>~n", []),
+ do_doc(Fd, TestCases, []),
+ io:format(Fd, "</DL>~n", []),
+ file:close(Fd);
+doc(TestCases) ->
+ doc([TestCases]).
+
+do_doc(Fd, [H | T], List) ->
+ case H of
+ {Module, TestCase} when is_atom(Module), is_atom(TestCase) ->
+ do_doc(Fd, Module, TestCase, List);
+ TestCase when is_atom(TestCase), List == [] ->
+ do_doc(Fd, mnesia_SUITE, TestCase, List);
+ TestCase when is_atom(TestCase) ->
+ do_doc(Fd, hd(List), TestCase, List)
+ end,
+ do_doc(Fd, T, List);
+do_doc(_, [], _) ->
+ ok.
+
+do_doc(Fd, Module, TestCase, List) ->
+ case get_suite(Module, TestCase) of
+ [] ->
+ %% Implemented leaf test case
+ Head = ?flat_format("<A HREF=~p.html#~p_1>{~p, ~p}</A>}",
+ [Module, TestCase, Module, TestCase]),
+ print_doc(Fd, Module, TestCase, Head);
+ Suite when is_list(Suite) ->
+ %% Test suite
+ Head = ?flat_format("{~p, ~p}", [Module, TestCase]),
+ print_doc(Fd, Module, TestCase, Head),
+ io:format(Fd, "~n<DL>~n", []),
+ do_doc(Fd, Suite, [Module | List]),
+ io:format(Fd, "</DL>~n", []);
+ 'NYI' ->
+ %% Not yet implemented
+ Head = ?flat_format("<B>{~p, ~p}</B>", [Module, TestCase]),
+ print_doc(Fd, Module, TestCase, Head)
+ end.
+
+print_doc(Fd, Mod, Fun, Head) ->
+ case catch (apply(Mod, Fun, [doc])) of
+ {'EXIT', _} ->
+ io:format(Fd, "<DT>~s</DT>~n", [Head]);
+ Doc when is_list(Doc) ->
+ io:format(Fd, "<DT><U>~s</U><BR><DD>~n", [Head]),
+ print_rows(Fd, Doc),
+ io:format(Fd, "</DD><BR><BR>~n", [])
+ end.
+
+print_rows(_Fd, []) ->
+ ok;
+print_rows(Fd, [H | T]) when is_list(H) ->
+ io:format(Fd, "~s~n", [H]),
+ print_rows(Fd, T);
+print_rows(Fd, [H | T]) when is_integer(H) ->
+ io:format(Fd, "~s~n", [[H | T]]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Show the test case structure
+
+struct(TestCases) ->
+ T = test(TestCases, suite),
+ struct(T, "").
+
+struct({Module, TestCase}, Indentation)
+ when is_atom(Module), is_atom(TestCase) ->
+ log("~s{~p, ~p} ...~n", [Indentation, Module, TestCase]);
+struct({Module, TestCase, Other}, Indentation)
+ when is_atom(Module), is_atom(TestCase) ->
+ log("~s{~p, ~p} ~p~n", [Indentation, Module, TestCase, Other]);
+struct([], _) ->
+ ok;
+struct([TestCase | TestCases], Indentation) ->
+ struct(TestCase, Indentation),
+ struct(TestCases, Indentation);
+struct({TestCase, []}, Indentation) ->
+ struct(TestCase, Indentation);
+struct({TestCase, SubTestCases}, Indentation) when is_list(SubTestCases) ->
+ struct(TestCase, Indentation),
+ struct(SubTestCases, Indentation ++ " ").
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Execute the test cases
+
+test(TestCases) ->
+ test(TestCases, []).
+
+test(TestCases, suite) when is_list(TestCases) ->
+ test_driver(TestCases, suite);
+test(TestCases, Config) when is_list(TestCases) ->
+ D1 = lists:duplicate(10, $=),
+ D2 = lists:duplicate(10, $ ),
+ log("~n~s TEST CASES: ~p~n ~sCONFIG: ~p~n~n", [D1, TestCases, D2, Config]),
+ test_driver(TestCases, Config);
+test(TestCase, Config) ->
+ test([TestCase], Config).
+
+test_driver([], _Config) ->
+ [];
+test_driver([T|TestCases], Config) ->
+ L1 = test_driver(T, Config),
+ L2 = test_driver(TestCases, Config),
+ [L1|L2];
+test_driver({Module, TestCases}, Config) when is_list(TestCases)->
+ test_driver(default_module(Module, TestCases), Config);
+test_driver({_, {Module, TestCase}}, Config) ->
+ test_driver({Module, TestCase}, Config);
+test_driver({Module, TestCase}, Config) ->
+ Sec = timer:seconds(1) * 1000,
+ case get_suite(Module, TestCase) of
+ [] when Config == suite ->
+ {Module, TestCase, 'IMPL'};
+ [] ->
+ log("Eval test case: ~w~n", [{Module, TestCase}]),
+ {T, Res} =
+ timer:tc(?MODULE, eval_test_case, [Module, TestCase, Config]),
+ log("Tested ~w in ~w sec~n", [TestCase, T div Sec]),
+ {T div Sec, Res};
+ Suite when is_list(Suite), Config == suite ->
+ Res = test_driver(default_module(Module, Suite), Config),
+ {{Module, TestCase}, Res};
+ Suite when is_list(Suite) ->
+ log("Expand test case ~w~n", [{Module, TestCase}]),
+ Def = default_module(Module, Suite),
+ {T, Res} = timer:tc(?MODULE, test_driver, [Def, Config]),
+ {T div Sec, {{Module, TestCase}, Res}};
+ 'NYI' when Config == suite ->
+ {Module, TestCase, 'NYI'};
+ 'NYI' ->
+ log("<WARNING> Test case ~w NYI~n", [{Module, TestCase}]),
+ {0, {skip, {Module, TestCase}, "NYI"}}
+ end;
+test_driver(TestCase, Config) ->
+ DefaultModule = mnesia_SUITE,
+ log("<>WARNING<> Missing module in test case identifier. "
+ "{~w, ~w} assumed~n", [DefaultModule, TestCase]),
+ test_driver({DefaultModule, TestCase}, Config).
+
+default_module(DefaultModule, TestCases) when is_list(TestCases) ->
+ Fun = fun(T) ->
+ case T of
+ {_, _} -> true;
+ T -> {true, {DefaultModule, T}}
+ end
+ end,
+ lists:zf(Fun, TestCases).
+
+%% Returns a list (possibly empty) or the atom 'NYI'
+get_suite(Mod, Fun) ->
+ case catch (apply(Mod, Fun, [suite])) of
+ {'EXIT', _} -> 'NYI';
+ List when is_list(List) -> List
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+eval_test_case(Mod, Fun, Config) ->
+ flush(),
+ global:register_name(mnesia_test_case_sup, self()),
+ Flag = process_flag(trap_exit, true),
+ Pid = spawn_link(?MODULE, test_case_evaluator, [Mod, Fun, [Config]]),
+ R = wait_for_evaluator(Pid, Mod, Fun, Config),
+ global:unregister_name(mnesia_test_case_sup),
+ process_flag(trap_exit, Flag),
+ R.
+
+flush() ->
+ receive Msg -> [Msg | flush()]
+ after 0 -> []
+ end.
+
+wait_for_evaluator(Pid, Mod, Fun, Config) ->
+ receive
+ {'EXIT', Pid, {test_case_ok, _PidRes}} ->
+ Errors = flush(),
+ Res =
+ case Errors of
+ [] -> ok;
+ Errors -> failed
+ end,
+ {Res, {Mod, Fun}, Errors};
+ {'EXIT', Pid, {skipped, Reason}} ->
+ log("<WARNING> Test case ~w skipped, because ~p~n",
+ [{Mod, Fun}, Reason]),
+ Mod:fin_per_testcase(Fun, Config),
+ {skip, {Mod, Fun}, Reason};
+ {'EXIT', Pid, Reason} ->
+ log("<>ERROR<> Eval process ~w exited, because ~p~n",
+ [{Mod, Fun}, Reason]),
+ Mod:fin_per_testcase(Fun, Config),
+ {crash, {Mod, Fun}, Reason}
+ end.
+
+test_case_evaluator(Mod, Fun, [Config]) ->
+ NewConfig = Mod:init_per_testcase(Fun, Config),
+ R = apply(Mod, Fun, [NewConfig]),
+ Mod:fin_per_testcase(Fun, NewConfig),
+ exit({test_case_ok, R}).
+
+activity_evaluator(Coordinator) ->
+ activity_evaluator_loop(Coordinator),
+ exit(normal).
+
+activity_evaluator_loop(Coordinator) ->
+ receive
+ begin_trans ->
+ transaction(Coordinator, 0);
+ {begin_trans, MaxRetries} ->
+ transaction(Coordinator, MaxRetries);
+ end_trans ->
+ end_trans;
+ Fun when is_function(Fun) ->
+ Coordinator ! {self(), Fun()},
+ activity_evaluator_loop(Coordinator);
+% {'EXIT', Coordinator, Reason} ->
+% Reason;
+ ExitExpr ->
+% ?error("activity_evaluator_loop ~p ~p: exit(~p)~n}", [Coordinator, self(), ExitExpr]),
+ exit(ExitExpr)
+ end.
+
+transaction(Coordinator, MaxRetries) ->
+ Fun = fun() ->
+ Coordinator ! {self(), begin_trans},
+ activity_evaluator_loop(Coordinator)
+ end,
+ Coordinator ! {self(), mnesia:transaction(Fun, MaxRetries)},
+ activity_evaluator_loop(Coordinator).
+
+pick_msg() ->
+ receive
+ Message -> Message
+ after 4000 -> timeout
+ end.
+
+start_activities(Nodes) ->
+ Fun = fun(N) -> spawn_link(N, ?MODULE, activity_evaluator, [self()]) end,
+ Pids = mapl(Fun, Nodes),
+ {success, Pids}.
+
+mapl(Fun, [H|T]) ->
+ Res = Fun(H),
+ [Res|mapl(Fun, T)];
+mapl(_Fun, []) ->
+ [].
+
+diskless(Config) ->
+ case lists:keysearch(diskless, 1, Config) of
+ {value, {diskless, true}} ->
+ true;
+ _Else ->
+ false
+ end.
+
+
+start_transactions(Pids) ->
+ Fun = fun(Pid) ->
+ Pid ! begin_trans,
+ ?match_receive({Pid, begin_trans})
+ end,
+ mapl(Fun, Pids).
+
+start_sync_transactions(Pids) ->
+ Nodes = [node(Pid) || Pid <- Pids],
+ Fun = fun(Pid) ->
+ sync_trans_tid_serial(Nodes),
+ Pid ! begin_trans,
+ ?match_receive({Pid, begin_trans})
+ end,
+ mapl(Fun, Pids).
+
+
+start_transactions(Pids, MaxRetries) ->
+ Fun = fun(Pid) ->
+ Pid ! {begin_trans, MaxRetries},
+ ?match_receive({Pid, begin_trans})
+ end,
+ mapl(Fun, Pids).
+
+start_sync_transactions(Pids, MaxRetries) ->
+ Nodes = [node(Pid) || Pid <- Pids],
+ Fun = fun(Pid) ->
+ sync_trans_tid_serial(Nodes),
+ Pid ! {begin_trans, MaxRetries},
+ ?match_receive({Pid, begin_trans})
+ end,
+ mapl(Fun, Pids).
+
+sync_trans_tid_serial(Nodes) ->
+ Fun = fun() -> mnesia:write_lock_table(schema) end,
+ rpc:multicall(Nodes, mnesia, transaction, [Fun]).
+
+select_nodes(N, Config, File, Line) ->
+ prepare_test_case([], N, Config, File, Line).
+
+prepare_test_case(Actions, N, Config, File, Line) ->
+ NodeList1 = lookup_config(nodes, Config),
+ NodeList2 = lookup_config(nodenames, Config), %% For testserver
+ NodeList3 = append_unique(NodeList1, NodeList2),
+ This = node(),
+ All = [This | lists:delete(This, NodeList3)],
+ Selected = pick_nodes(N, All, File, Line),
+ case diskless(Config) of
+ true ->
+ ok;
+ false ->
+ rpc:multicall(Selected, application, set_env,[mnesia, schema_location, opt_disc])
+ end,
+ do_prepare(Actions, Selected, All, Config, File, Line).
+
+do_prepare([], Selected, _All, _Config, _File, _Line) ->
+ Selected;
+do_prepare([{init_test_case, Appls} | Actions], Selected, All, Config, File, Line) ->
+ set_kill_timer(Config),
+ Started = init_nodes(Selected, File, Line),
+ All2 = append_unique(Started, All),
+ Alive = mnesia_lib:intersect(nodes() ++ [node()], All2),
+ kill_appls(Appls, Alive),
+ process_flag(trap_exit, true),
+ do_prepare(Actions, Started, All2, Config, File, Line);
+do_prepare([delete_schema | Actions], Selected, All, Config, File, Line) ->
+ Alive = mnesia_lib:intersect(nodes() ++ [node()], All),
+ case diskless(Config) of
+ true ->
+ skip;
+ false ->
+ Del = fun(Node) ->
+ case mnesia:delete_schema([Node]) of
+ ok -> ok;
+ {error, {"All nodes not running",_}} ->
+ ok;
+ Else ->
+ ?log("Delete schema error ~p ~n", [Else])
+ end
+ end,
+ lists:foreach(Del, Alive)
+ end,
+ do_prepare(Actions, Selected, All, Config, File, Line);
+do_prepare([create_schema | Actions], Selected, All, Config, File, Line) ->
+ case diskless(Config) of
+ true ->
+ skip;
+ _Else ->
+ case mnesia:create_schema(Selected) of
+ ok ->
+ ignore;
+ BadNodes ->
+ ?fatal("Cannot create Mnesia schema on ~p~n", [BadNodes])
+ end
+ end,
+ do_prepare(Actions, Selected, All, Config, File, Line);
+do_prepare([{start_appls, Appls} | Actions], Selected, All, Config, File, Line) ->
+ case start_appls(Appls, Selected, Config) of
+ [] -> ok;
+ Bad -> ?fatal("Cannot start appls ~p: ~p~n", [Appls, Bad])
+ end,
+ do_prepare(Actions, Selected, All, Config, File, Line);
+do_prepare([{reload_appls, Appls} | Actions], Selected, All, Config, File, Line) ->
+ reload_appls(Appls, Selected),
+ do_prepare(Actions, Selected, All, Config, File, Line).
+
+set_kill_timer(Config) ->
+ case init:get_argument(mnesia_test_timeout) of
+ {ok, _ } -> ok;
+ _ ->
+ Time0 =
+ case lookup_config(tc_timeout, Config) of
+ [] -> timer:minutes(5);
+ ConfigTime when is_integer(ConfigTime) -> ConfigTime
+ end,
+ Mul = try
+ test_server:timetrap_scale_factor()
+ catch _:_ -> 1 end,
+ (catch test_server:timetrap(Mul*Time0 + 1000)),
+ spawn_link(?MODULE, kill_tc, [self(),Time0*Mul])
+ end.
+
+kill_tc(Pid, Time) ->
+ receive
+ after Time ->
+ case process_info(Pid) of
+ undefined -> ok;
+ _ ->
+ ?error("Watchdog in test case timed out "
+ "in ~p min~n", [Time div (1000*60)]),
+ Files = mnesia_lib:dist_coredump(),
+ ?log("Cores dumped to:~n ~p~n", [Files]),
+ %% Genarate erlang crashdumps.
+ %% GenDump = fun(Node) ->
+ %% File = "CRASH_" ++ atom_to_list(Node) ++ ".dump",
+ %% rpc:call(Node, os, putenv, ["ERL_CRASH_DUMP", File]),
+ %% rpc:cast(Node, erlang, halt, ["RemoteTimeTrap"])
+ %% end,
+ %% [GenDump(Node) || Node <- nodes()],
+
+ %% erlang:halt("DebugTimeTrap"),
+ exit(Pid, kill)
+ end
+ end.
+
+
+append_unique([], List) -> List;
+append_unique([H|R], List) ->
+ case lists:member(H, List) of
+ true -> append_unique(R, List);
+ false -> [H | append_unique(R, List)]
+ end.
+
+pick_nodes(all, Nodes, File, Line) ->
+ pick_nodes(length(Nodes), Nodes, File, Line);
+pick_nodes(N, [H | T], File, Line) when N > 0 ->
+ [H | pick_nodes(N - 1, T, File, Line)];
+pick_nodes(0, _Nodes, _File, _Line) ->
+ [];
+pick_nodes(N, [], File, Line) ->
+ ?skip("Test case (~p(~p)) ignored: ~p nodes missing~n",
+ [File, Line, N]).
+
+init_nodes([Node | Nodes], File, Line) ->
+ case net_adm:ping(Node) of
+ pong ->
+ [Node | init_nodes(Nodes, File, Line)];
+ pang ->
+ [Name, Host] = node_to_name_and_host(Node),
+ case slave_start_link(Host, Name) of
+ {ok, Node1} ->
+ Path = code:get_path(),
+ true = rpc:call(Node1, code, set_path, [Path]),
+ [Node1 | init_nodes(Nodes, File, Line)];
+ Other ->
+ ?skip("Test case (~p(~p)) ignored: cannot start node ~p: ~p~n",
+ [File, Line, Node, Other])
+ end
+ end;
+init_nodes([], _File, _Line) ->
+ [].
+
+%% Returns [Name, Host]
+node_to_name_and_host(Node) ->
+ string:tokens(atom_to_list(Node), [$@]).
+
+lookup_config(Key,Config) ->
+ case lists:keysearch(Key,1,Config) of
+ {value,{Key,Val}} ->
+ Val;
+ _ ->
+ []
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+start_appls(Appls, Nodes) ->
+ start_appls(Appls, Nodes, [], [schema]).
+
+start_appls(Appls, Nodes, Config) ->
+ start_appls(Appls, Nodes, Config, [schema]).
+
+start_appls([Appl | Appls], Nodes, Config, Tabs) ->
+ {Started, BadStarters} =
+ rpc:multicall(Nodes, ?MODULE, remote_start, [Appl, Config, Nodes]),
+ BadS = [{Node, Appl, Res} || {Node, Res} <- Started, Res /= ok],
+ BadN = [{BadNode, Appl, bad_start} || BadNode <- BadStarters],
+ Bad = BadS ++ BadN,
+ case Appl of
+ mnesia when Bad == [] ->
+ sync_tables(Nodes, Tabs);
+ _ ->
+ ignore
+ end,
+ Bad ++ start_appls(Appls, Nodes, Config, Tabs);
+start_appls([], _Nodes, _Config, _Tabs) ->
+ [].
+
+remote_start(mnesia, Config, Nodes) ->
+ case diskless(Config) of
+ true ->
+ application_controller:set_env(mnesia,
+ extra_db_nodes,
+ Nodes -- [node()]),
+ application_controller:set_env(mnesia,
+ schema_location,
+ ram);
+ false ->
+ application_controller:set_env(mnesia,
+ schema_location,
+ opt_disc),
+ ignore
+ end,
+ {node(), mnesia:start()};
+remote_start(Appl, _Config, _Nodes) ->
+ Res =
+ case application:start(Appl) of
+ {error, {already_started, Appl}} ->
+ ok;
+ Other ->
+ Other
+ end,
+ {node(), Res}.
+
+%% Start Mnesia on all given nodes and wait for specified
+%% tables to be accessible on each node. The atom all means
+%% that we should wait for all tables to be loaded
+%%
+%% Returns a list of error tuples {BadNode, mnesia, Reason}
+start_mnesia(Nodes) ->
+ start_appls([mnesia], Nodes).
+start_mnesia(Nodes, Tabs) when is_list(Nodes) ->
+ start_appls([mnesia], Nodes, [], Tabs).
+
+%% Wait for the tables to be accessible from all nodes in the list
+%% and that all nodes are aware of that the other nodes also ...
+sync_tables(Nodes, Tabs) ->
+ Res = send_wait(Nodes, Tabs, []),
+ if
+ Res == [] ->
+ mnesia:transaction(fun() -> mnesia:write_lock_table(schema) end),
+ Res;
+ true ->
+ Res
+ end.
+
+send_wait([Node | Nodes], Tabs, Pids) ->
+ Pid = spawn_link(Node, ?MODULE, start_wait, [self(), Tabs]),
+ send_wait(Nodes, Tabs, [Pid | Pids]);
+send_wait([], _Tabs, Pids) ->
+ rec_wait(Pids, []).
+
+rec_wait([Pid | Pids], BadRes) ->
+ receive
+ {'EXIT', Pid, R} ->
+ rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes]);
+ {Pid, ok} ->
+ rec_wait(Pids, BadRes);
+ {Pid, {error, R}} ->
+ rec_wait(Pids, [{node(Pid), bad_wait, R} | BadRes])
+ end;
+rec_wait([], BadRes) ->
+ BadRes.
+
+start_wait(Coord, Tabs) ->
+ process_flag(trap_exit, true),
+ Mon = whereis(mnesia_monitor),
+ case catch link(Mon) of
+ {'EXIT', _} ->
+ unlink(Coord),
+ Coord ! {self(), {error, {node_not_running, node()}}};
+ _ ->
+ Res = start_wait_loop(Tabs),
+ unlink(Mon),
+ unlink(Coord),
+ Coord ! {self(), Res}
+ end.
+
+start_wait_loop(Tabs) ->
+ receive
+ {'EXIT', Pid, Reason} ->
+ {error, {start_wait, Pid, Reason}}
+ after 0 ->
+ case mnesia:wait_for_tables(Tabs, timer:seconds(30)) of
+ ok ->
+ verify_nodes(Tabs);
+ {timeout, BadTabs} ->
+ log("<>WARNING<> Wait for tables ~p: ~p~n", [node(), Tabs]),
+ start_wait_loop(BadTabs);
+ {error, Reason} ->
+ {error, {start_wait, Reason}}
+ end
+ end.
+
+verify_nodes(Tabs) ->
+ verify_nodes(Tabs, 0).
+
+verify_nodes([], _) ->
+ ok;
+
+verify_nodes([Tab| Tabs], N) ->
+ ?match(X when is_atom(X), mnesia_lib:val({Tab, where_to_read})),
+ Nodes = mnesia:table_info(Tab, where_to_write),
+ Copies =
+ mnesia:table_info(Tab, disc_copies) ++
+ mnesia:table_info(Tab, disc_only_copies) ++
+ mnesia:table_info(Tab, ram_copies),
+ Local = mnesia:table_info(Tab, local_content),
+ case Copies -- Nodes of
+ [] ->
+ verify_nodes(Tabs, 0);
+ _Else when Local == true, Nodes /= [] ->
+ verify_nodes(Tabs, 0);
+ Else ->
+ N2 =
+ if
+ N > 20 ->
+ log("<>WARNING<> ~w Waiting for table: ~p on ~p ~n",
+ [node(), Tab, Else]),
+ 0;
+ true -> N+1
+ end,
+ timer:sleep(500),
+ verify_nodes([Tab| Tabs], N2)
+ end.
+
+
+%% Nicely stop Mnesia on all given nodes
+%%
+%% Returns a list of error tuples {BadNode, Reason}
+stop_mnesia(Nodes) when is_list(Nodes) ->
+ stop_appls([mnesia], Nodes).
+
+stop_appls([Appl | Appls], Nodes) when is_list(Nodes) ->
+ {Stopped, BadNodes} = rpc:multicall(Nodes, ?MODULE, remote_stop, [Appl]),
+ BadS =[{Node, Appl, Res} || {Node, Res} <- Stopped, Res /= stopped],
+ BadN =[{BadNode, Appl, bad_node} || BadNode <- BadNodes],
+ BadS ++ BadN ++ stop_appls(Appls, Nodes);
+stop_appls([], _Nodes) ->
+ [].
+
+remote_stop(mnesia) ->
+ {node(), mnesia:stop()};
+remote_stop(Appl) ->
+ {node(), application:stop(Appl)}.
+
+remote_kill([Appl | Appls]) ->
+ catch Appl:lkill(),
+ application:stop(Appl),
+ remote_kill(Appls);
+remote_kill([]) ->
+ ok.
+
+%% Abruptly kill Mnesia on all given nodes
+%% Returns []
+kill_appls(Appls, Nodes) when is_list(Nodes) ->
+ verbose("<>WARNING<> Intentionally killing ~p: ~w...~n",
+ [Appls, Nodes], ?FILE, ?LINE),
+ rpc:multicall(Nodes, ?MODULE, remote_kill, [Appls]),
+ [].
+
+kill_mnesia(Nodes) when is_list(Nodes) ->
+ kill_appls([mnesia], Nodes).
+
+reload_appls([Appl | Appls], Selected) ->
+ kill_appls([Appl], Selected),
+ timer:sleep(1000),
+ Ok = {[ok || _N <- Selected], []},
+ {Ok2temp, Empty} = rpc:multicall(Selected, application, unload, [Appl]),
+ Conv = fun({error,{not_loaded,mnesia}}) -> ok; (Else) -> Else end,
+ Ok2 = {lists:map(Conv, Ok2temp), Empty},
+
+ Ok3 = rpc:multicall(Selected, application, load, [Appl]),
+ if
+ Ok /= Ok2 ->
+ ?fatal("Cannot unload appl ~p: ~p~n", [Appl, Ok2]);
+ Ok /= Ok3 ->
+ ?fatal("Cannot load appl ~p: ~p~n", [Appl, Ok3]);
+ true ->
+ ok
+ end,
+ reload_appls(Appls, Selected);
+reload_appls([], _Selected) ->
+ ok.
+
+shutdown() ->
+ log("<>WARNING<> Intentionally shutting down all nodes... ~p~n",
+ [nodes() ++ [node()]]),
+ rpc:multicall(nodes(), erlang, halt, []),
+ erlang:halt().
+
+verify_mnesia(Ups, Downs, File, Line) when is_list(Ups), is_list(Downs) ->
+ BadUps =
+ [N || N <- Ups, rpc:call(N, mnesia, system_info, [is_running]) /= yes],
+ BadDowns =
+ [N || N <- Downs, rpc:call(N, mnesia, system_info, [is_running]) == yes],
+ if
+ BadUps == [] ->
+ ignore;
+ true ->
+ error("Mnesia is not running as expected: ~p~n",
+ [BadUps], File, Line)
+ end,
+ if
+ BadDowns == [] ->
+ ignore;
+ true ->
+ error("Mnesia is not stopped as expected: ~p~n",
+ [BadDowns], File, Line)
+ end,
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+verify_replica_location(Tab, [], [], [], _) ->
+ ?match({'EXIT', _}, mnesia:table_info(Tab, ram_copies)),
+ ?match({'EXIT', _}, mnesia:table_info(Tab, disc_copies)),
+ ?match({'EXIT', _}, mnesia:table_info(Tab, disc_only_copies)),
+ ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_write)),
+ ?match({'EXIT', _}, mnesia:table_info(Tab, where_to_read)),
+ [];
+
+verify_replica_location(Tab, DiscOnly0, Ram0, Disc0, AliveNodes0) ->
+%% sync_tables(AliveNodes0, [Tab]),
+ AliveNodes = lists:sort(AliveNodes0),
+ DiscOnly = lists:sort(DiscOnly0),
+ Ram = lists:sort(Ram0),
+ Disc = lists:sort(Disc0),
+ Write = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes),
+ Read = ignore_dead(DiscOnly ++ Ram ++ Disc, AliveNodes),
+ This = node(),
+
+ timer:sleep(100),
+
+ S1 = ?match(AliveNodes, lists:sort(mnesia:system_info(running_db_nodes))),
+ S2 = ?match(DiscOnly, lists:sort(mnesia:table_info(Tab, disc_only_copies))),
+ S3 = ?match(Ram, lists:sort(mnesia:table_info(Tab, ram_copies))),
+ S4 = ?match(Disc, lists:sort(mnesia:table_info(Tab, disc_copies))),
+ S5 = ?match(Write, lists:sort(mnesia:table_info(Tab, where_to_write))),
+ S6 = case lists:member(This, Read) of
+ true ->
+ ?match(This, mnesia:table_info(Tab, where_to_read));
+ false ->
+ ?match(true, lists:member(mnesia:table_info(Tab, where_to_read), Read))
+ end,
+ lists:filter(fun({success,_}) -> false; (_) -> true end, [S1,S2,S3,S4,S5,S6]).
+
+ignore_dead(Nodes, AliveNodes) ->
+ Filter = fun(Node) -> lists:member(Node, AliveNodes) end,
+ lists:sort(lists:zf(Filter, Nodes)).
+
+
+remote_activate_debug_fun(N, I, F, C, File, Line) ->
+ Pid = spawn_link(N, ?MODULE, do_remote_activate_debug_fun, [self(), I, F, C, File, Line]),
+ receive
+ {activated, Pid} -> ok;
+ {'EXIT', Pid, Reason} -> {error, Reason}
+ end.
+
+do_remote_activate_debug_fun(From, I, F, C, File, Line) ->
+ mnesia_lib:activate_debug_fun(I, F, C, File, Line),
+ From ! {activated, self()},
+ timer:sleep(infinity). % Dies whenever the test process dies !!
+
+
+sort(L) when is_list(L) ->
+ lists:sort(L);
+sort({atomic, L}) when is_list(L) ->
+ {atomic, lists:sort(L)};
+sort({ok, L}) when is_list(L) ->
+ {ok, lists:sort(L)};
+sort(W) ->
+ W.
diff --git a/lib/mnesia/test/mnesia_test_lib.hrl b/lib/mnesia/test/mnesia_test_lib.hrl
new file mode 100644
index 0000000000..85f12200d4
--- /dev/null
+++ b/lib/mnesia/test/mnesia_test_lib.hrl
@@ -0,0 +1,132 @@
+%%
+%% %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%
+%%
+
+%%
+-define(log(Format,Args),mnesia_test_lib:log(Format,Args,?FILE,?LINE)).
+-define(warning(Format,Args),?log("<>WARNING<>~n " ++ Format,Args)).
+-define(error(Format,Args),
+ mnesia_test_lib:error(Format,Args,?FILE,?LINE)).
+-define(verbose(Format,Args),mnesia_test_lib:verbose(Format,Args,?FILE,?LINE)).
+
+-define(fatal(Format,Args),
+ ?error(Format, Args),
+ exit({test_case_fatal, Format, Args, ?FILE, ?LINE})).
+
+-define(skip(Format,Args),
+ ?warning(Format, Args),
+ exit({skipped, ?flat_format(Format, Args)})).
+
+-define(flat_format(Format,Args),
+ lists:flatten(io_lib:format(Format, Args))).
+
+-define(sort(What), mnesia_test_lib:sort(What)).
+
+-define(ignore(Expr),
+ fun() ->
+ AcTuAlReS = (catch (Expr)),
+ ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]),
+ AcTuAlReS
+ end()).
+
+-define(match(ExpectedRes,Expr),
+ fun() ->
+ AcTuAlReS = (catch (Expr)),
+ case AcTuAlReS of
+ ExpectedRes ->
+ ?verbose("ok, ~n Result as expected:~p~n",[AcTuAlReS]),
+ {success,AcTuAlReS};
+ _ ->
+ ?error("Not Matching Actual result was:~n ~p~n",
+ [AcTuAlReS]),
+ {fail,AcTuAlReS}
+ end
+ end()).
+
+-define(match_inverse(NotExpectedRes,Expr),
+ fun() ->
+ AcTuAlReS = (catch (Expr)),
+ case AcTuAlReS of
+ NotExpectedRes ->
+ ?error("Not matching Actual result was:~n ~p~n",
+ [AcTuAlReS]),
+ {fail,AcTuAlReS};
+ _ ->
+ ?verbose("ok, ~n Result as expected: ~p~n",[AcTuAlReS]),
+ {success,AcTuAlReS}
+ end
+ end()).
+
+-define(match_receive(ExpectedMsg),
+ ?match(ExpectedMsg,mnesia_test_lib:pick_msg())).
+
+%% ExpectedMsgs must be completely bound
+-define(match_multi_receive(ExpectedMsgs),
+ fun() ->
+ TmPeXpCtEdMsGs = lists:sort(ExpectedMsgs),
+ ?match(TmPeXpCtEdMsGs,
+ lists:sort(lists:map(fun(_) ->
+ mnesia_test_lib:pick_msg()
+ end,
+ TmPeXpCtEdMsGs)))
+ end()).
+
+-define(start_activities(Nodes),
+ mnesia_test_lib:start_activities(Nodes)).
+
+-define(start_transactions(Pids),
+ mnesia_test_lib:start_transactions(Pids)).
+
+-define(acquire_nodes(N, Config),
+ mnesia_test_lib:prepare_test_case([{init_test_case, [mnesia]},
+ delete_schema,
+ create_schema,
+ {start_appls, [mnesia]}],
+ N, Config, ?FILE, ?LINE)).
+
+-define(activate_debug_fun(I, F, C),
+ mnesia_lib:activate_debug_fun(I, F, C, ?FILE, ?LINE)).
+
+-define(remote_activate_debug_fun(N, I, F, C),
+ ?match(ok, mnesia_test_lib:remote_activate_debug_fun(N, I, F, C,
+ ?FILE, ?LINE))).
+
+-define(deactivate_debug_fun(I),
+ mnesia_lib:deactivate_debug_fun(I, ?FILE, ?LINE)).
+
+-define(remote_deactivate_debug_fun(N, I),
+ rpc:call(N, mnesia_lib, deactivate_debug_fun, [I, ?FILE, ?LINE])).
+
+-define(is_debug_compiled,
+ case mnesia_lib:is_debug_compiled() of
+ false ->
+ ?skip("Mnesia is not debug compiled, test case ignored.~n", []);
+ _OhTeR ->
+ ok
+ end).
+
+-define(needs_disc(Config),
+ case mnesia_test_lib:diskless(Config) of
+ false ->
+ ok;
+ true ->
+ ?skip("Must have disc, test case ignored.~n", [])
+ end).
+
+-define(verify_mnesia(Ups, Downs),
+ mnesia_test_lib:verify_mnesia(Ups, Downs, ?FILE, ?LINE)).
diff --git a/lib/mnesia/test/mnesia_tpcb.erl b/lib/mnesia/test/mnesia_tpcb.erl
new file mode 100644
index 0000000000..903c53a21c
--- /dev/null
+++ b/lib/mnesia/test/mnesia_tpcb.erl
@@ -0,0 +1,1268 @@
+%%
+%% %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_tpcb - TPC-B benchmarking of Mnesia
+%%
+%% DESCRIPTION
+%%
+%% The metrics used in the TPC-B benchmark are throughput as measured
+%% in transactions per second (TPS). The benchmark uses a single,
+%% simple update-intensive transaction to load the database system.
+%% The single transaction type provides a simple, repeatable
+%% unit of work, and is designed to exercise the basic components of
+%% a database system.
+%%
+%% The definition of the TPC-B states lots of detailed rules and
+%% conditions that must be fullfilled, e.g. how the ACID (atomicity,
+%% consistency, isolation and durability) properties are verified,
+%% how the random numbers must be distributed, minimum sizes of
+%% the different types of records, minimum duration of the benchmark,
+%% formulas to calculate prices (dollars per tps), disclosure issues
+%% etc. Please, see http://www.tpc.org/ about the nitty gritty details.
+%%
+%% The TPC-B benchmark is stated in terms of a hypothetical bank. The
+%% bank has one or more branches. Each branch has multiple tellers. The
+%% bank has many customers, each with an account. The database represents
+%% the cash position of each entity (branch, teller and account) and a
+%% history of recent transactions run by the bank. The transaction
+%% represents the work done when a customer makes a deposit or a
+%% withdrawal against his account. The transaction is performed by a
+%% teller at some branch.
+%%
+%% Each process that performs TPC-B transactions is called a driver.
+%% Drivers generates teller_id, account_id and delta amount of
+%% money randomly. An account, a teller and a branch are read, their
+%% balances are adjusted and a history record is created. The driver
+%% measures the time for 3 reads, 3 writes and 1 create.
+%%
+%% GETTING STARTED
+%%
+%% Generate tables and run with default configuration:
+%%
+%% mnesia_tpcb:start().
+%%
+%% A little bit more advanced;
+%%
+%% spawn(mnesia_tpcb, start, [[[{n_drivers_per_node, 8}, {stop_after, infinity}]]),
+%% mnesia_tpcb:stop().
+%%
+%% Really advanced;
+%%
+%% mnesia_tpcb:init(([{n_branches, 8}, {replica_type, disc_only_copies}]),
+%% mnesia_tpcb:run(([{n_drivers_per_node, 8}]),
+%% mnesia_tpcb:run(([{n_drivers_per_node, 64}]).
+%%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-module(mnesia_tpcb).
+-author('[email protected]').
+
+-export([
+ config/2,
+ count_balance/0,
+ driver_init/2,
+ init/1,
+ reporter_init/2,
+ run/1,
+ start/0,
+ start/1,
+ start/2,
+ stop/0,
+ real_trans/5,
+ verify_tabs/0,
+ reply_gen_branch/3,
+ frag_add_delta/7,
+
+ conflict_test/1,
+ dist_test/1,
+ replica_test/1,
+ sticky_replica_test/1,
+ remote_test/1,
+ remote_frag2_test/1
+ ]).
+
+-define(SECOND, 1000000).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Account record, total size must be at least 100 bytes
+
+-define(ACCOUNT_FILLER,
+ {123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234}).
+
+-record(account,
+ {
+ id = 0, % Unique account id
+ branch_id = 0, % Branch where the account is held
+ balance = 0, % Account balance
+ filler = ?ACCOUNT_FILLER % Gap filler to ensure size >= 100 bytes
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Branch record, total size must be at least 100 bytes
+
+-define(BRANCH_FILLER,
+ {123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890}).
+
+-record(branch,
+ {
+ id = 0, % Unique branch id
+ balance = 0, % Total balance of whole branch
+ filler = ?BRANCH_FILLER % Gap filler to ensure size >= 100 bytes
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Teller record, total size must be at least 100 bytes
+
+-define(TELLER_FILLER,
+ {123456789012345678901234567890123456789012345678901234567890,
+ 123456789012345678901234567890123456789012345678901234567890,
+ 1234567890123456789012345678901234567890123456789012345678}).
+
+-record(teller,
+ {
+ id = 0, % Unique teller id
+ branch_id = 0, % Branch where the teller is located
+ balance = 0, % Teller balance
+ filler = ?TELLER_FILLER % Gap filler to ensure size >= 100 bytes
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% History record, total size must be at least 50 bytes
+
+-define(HISTORY_FILLER, 1234567890).
+
+-record(history,
+ {
+ history_id = {0, 0}, % {DriverId, DriverLocalHistoryid}
+ time_stamp = now(), % Time point during active transaction
+ branch_id = 0, % Branch associated with teller
+ teller_id = 0, % Teller invlolved in transaction
+ account_id = 0, % Account updated by transaction
+ amount = 0, % Amount (delta) specified by transaction
+ filler = ?HISTORY_FILLER % Gap filler to ensure size >= 50 bytes
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-record(tab_config,
+ {
+ db_nodes = [node()],
+ n_replicas = 1, % Ignored for non-fragmented tables
+ replica_nodes = [node()],
+ replica_type = ram_copies,
+ use_running_mnesia = false,
+ n_fragments = 0,
+ n_branches = 1,
+ n_tellers_per_branch = 10, % Must be 10
+ n_accounts_per_branch = 100000, % Must be 100000
+ branch_filler = ?BRANCH_FILLER,
+ account_filler = ?ACCOUNT_FILLER,
+ teller_filler = ?TELLER_FILLER
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(run_config,
+ {
+ driver_nodes = [node()],
+ n_drivers_per_node = 1,
+ use_running_mnesia = false,
+ stop_after = timer:minutes(15), % Minimum 15 min
+ report_interval = timer:minutes(1),
+ use_sticky_locks = false,
+ spawn_near_branch = false,
+ activity_type = transaction,
+ reuse_history_id = false
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(time,
+ {
+ n_trans = 0,
+ min_n = 0,
+ max_n = 0,
+ acc_time = 0,
+ max_time = 0
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(driver_state,
+ {
+ driver_id,
+ driver_node,
+ seed,
+ n_local_branches,
+ local_branches,
+ tab_config,
+ run_config,
+ history_id,
+ time = #time{},
+ acc_time = #time{},
+ reuse_history_id
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(reporter_state,
+ {
+ driver_pids,
+ starter_pid,
+ n_iters = 0,
+ prev_tps = 0,
+ curr = #time{},
+ acc = #time{},
+ init_micros,
+ prev_micros,
+ run_config
+ }).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% One driver on each node, table not replicated
+
+config(frag_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {n_branches, length(Nodes)},
+ {n_fragments, length(Nodes)},
+ {replica_nodes, Nodes},
+ {db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {n_accounts_per_branch, 100},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ];
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% One driver on each node, table replicated to two nodes.
+
+config(frag2_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {n_branches, length(Nodes)},
+ {n_fragments, length(Nodes)},
+ {n_replicas, 2},
+ {replica_nodes, Nodes},
+ {db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {n_accounts_per_branch, 100},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ];
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% One driver on this node, table replicated to all nodes.
+
+config(replica_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {db_nodes, Nodes},
+ {driver_nodes, [Local]},
+ {replica_nodes, Nodes},
+ {n_accounts_per_branch, 100},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ];
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% One driver on this node, table replicated to all nodes.
+
+config(sticky_replica_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {db_nodes, Nodes},
+ {driver_nodes, [node()]},
+ {replica_nodes, Nodes},
+ {n_accounts_per_branch, 100},
+ {replica_type, ReplicaType},
+ {use_sticky_locks, true},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ];
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Ten drivers per node, tables replicated to all nodes, lots of branches
+
+config(dist_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {replica_nodes, Nodes},
+ {n_drivers_per_node, 10},
+ {n_branches, 10 * length(Nodes) * 100},
+ {n_accounts_per_branch, 10},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ];
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Ten drivers per node, tables replicated to all nodes, single branch
+
+config(conflict_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {db_nodes, Nodes},
+ {driver_nodes, Nodes},
+ {replica_nodes, Nodes},
+ {n_drivers_per_node, 10},
+ {n_branches, 1},
+ {n_accounts_per_branch, 10},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ];
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% One driver on this node, table replicated to all other nodes.
+
+config(remote_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {db_nodes, Nodes},
+ {driver_nodes, [Local]},
+ {replica_nodes, Remote},
+ {n_accounts_per_branch, 100},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ];
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% One driver on this node, table replicated to two other nodes.
+
+config(remote_frag2_test, ReplicaType) ->
+ Remote = nodes(),
+ Local = node(),
+ Nodes = [Local | Remote],
+ [
+ {n_branches, length(Remote)},
+ {n_fragments, length(Remote)},
+ {n_replicas, 2},
+ {replica_nodes, Remote},
+ {db_nodes, Nodes},
+ {driver_nodes, [Local]},
+ {n_accounts_per_branch, 100},
+ {replica_type, ReplicaType},
+ {stop_after, timer:minutes(1)},
+ {report_interval, timer:seconds(10)},
+ {reuse_history_id, true}
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+start(What, ReplicaType) ->
+ spawn_link(?MODULE, start, [config(What, ReplicaType)]).
+
+replica_test(ReplicaType) ->
+ start(replica_test, ReplicaType).
+
+sticky_replica_test(ReplicaType) ->
+ start(sticky_replica_test, ReplicaType).
+
+dist_test(ReplicaType) ->
+ start(dist_test, ReplicaType).
+
+conflict_test(ReplicaType) ->
+ start(conflict_test, ReplicaType).
+
+remote_test(ReplicaType) ->
+ start(remote_test, ReplicaType).
+
+remote_frag2_test(ReplicaType) ->
+ start(remote_frag2_test, ReplicaType).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Args is a list of {Key, Val} tuples where Key is a field name
+%% in either the record tab_config or run_config. Unknown keys are ignored.
+
+start() ->
+ start([]).
+start(Args) ->
+ init(Args),
+ run(Args).
+
+list2rec(List, Fields, DefaultTuple) ->
+ [Name|Defaults] = tuple_to_list(DefaultTuple),
+ List2 = list2rec(List, Fields, Defaults, []),
+ list_to_tuple([Name] ++ List2).
+
+list2rec(_List, [], [], Acc) ->
+ Acc;
+list2rec(List, [F|Fields], [D|Defaults], Acc) ->
+ {Val, List2} =
+ case lists:keysearch(F, 1, List) of
+ false ->
+ {D, List};
+ {value, {F, NewVal}} ->
+ {NewVal, lists:keydelete(F, 1, List)}
+ end,
+ list2rec(List2, Fields, Defaults, Acc ++ [Val]).
+
+stop() ->
+ case whereis(mnesia_tpcb) of
+ undefined ->
+ {error, not_running};
+ Pid ->
+ sync_stop(Pid)
+ end.
+
+sync_stop(Pid) ->
+ Pid ! {self(), stop},
+ receive
+ {Pid, {stopped, Res}} -> Res
+ after timer:minutes(1) ->
+ exit(Pid, kill),
+ {error, brutal_kill}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Initialization
+
+%% Args is a list of {Key, Val} tuples where Key is a field name
+%% in the record tab_config, unknown keys are ignored.
+
+init(Args) ->
+ TabConfig0 = list2rec(Args, record_info(fields, tab_config), #tab_config{}),
+ TabConfig =
+ if
+ TabConfig0#tab_config.n_fragments =:= 0 ->
+ TabConfig0#tab_config{n_replicas = length(TabConfig0#tab_config.replica_nodes)};
+ true ->
+ TabConfig0
+ end,
+ Tags = record_info(fields, tab_config),
+ Fun = fun(F, Pos) -> {{F, element(Pos, TabConfig)}, Pos + 1} end,
+ {List, _} = lists:mapfoldl(Fun, 2, Tags),
+ io:format("TPC-B: Table config: ~p ~n", [List]),
+
+ DbNodes = TabConfig#tab_config.db_nodes,
+ stop(),
+ if
+ TabConfig#tab_config.use_running_mnesia =:= true ->
+ ignore;
+ true ->
+ rpc:multicall(DbNodes, mnesia, lkill, []),
+ case mnesia:delete_schema(DbNodes) of
+ ok ->
+ case mnesia:create_schema(DbNodes) of
+ ok ->
+ {Replies, BadNodes} =
+ rpc:multicall(DbNodes, mnesia, start, []),
+ case [Res || Res <- Replies, Res =/= ok] of
+ [] when BadNodes =:= [] ->
+ ok;
+ BadRes ->
+ io:format("TPC-B: <ERROR> "
+ "Failed to start ~p: ~p~n",
+ [BadNodes, BadRes]),
+ exit({start_failed, BadRes, BadNodes})
+ end;
+ {error, Reason} ->
+ io:format("TPC-B: <ERROR> "
+ "Failed to create schema on disc: ~p~n",
+ [Reason]),
+ exit({create_schema_failed, Reason})
+ end;
+ {error, Reason} ->
+ io:format("TPC-B: <ERROR> "
+ "Failed to delete schema on disc: ~p~n",
+ [Reason]),
+ exit({delete_schema_failed, Reason})
+ end
+ end,
+ gen_tabs(TabConfig).
+
+gen_tabs(TC) ->
+ create_tab(TC, branch, record_info(fields, branch),
+ undefined),
+ create_tab(TC, account, record_info(fields, account),
+ {branch, #account.branch_id}),
+ create_tab(TC, teller, record_info(fields, teller),
+ {branch, #teller.branch_id}),
+ create_tab(TC, history, record_info(fields, history),
+ {branch, #history.branch_id}),
+
+ NB = TC#tab_config.n_branches,
+ NT = TC#tab_config.n_tellers_per_branch,
+ NA = TC#tab_config.n_accounts_per_branch,
+ io:format("TPC-B: Generating ~p branches a ~p bytes~n",
+ [NB, size(term_to_binary(default_branch(TC)))]),
+ io:format("TPC-B: Generating ~p * ~p tellers a ~p bytes~n",
+ [NB, NT, size(term_to_binary(default_teller(TC)))]),
+ io:format("TPC-B: Generating ~p * ~p accounts a ~p bytes~n",
+ [NB, NA, size(term_to_binary(default_account(TC)))]),
+ io:format("TPC-B: Generating 0 history records a ~p bytes~n",
+ [size(term_to_binary(default_history(TC)))]),
+ gen_branches(TC),
+
+ case verify_tabs() of
+ ok ->
+ ignore;
+ {error, Reason} ->
+ io:format("TPC-B: <ERROR> Inconsistent tables: ~w~n",
+ [Reason]),
+ exit({inconsistent_tables, Reason})
+ end.
+
+create_tab(TC, Name, Attrs, _ForeignKey) when TC#tab_config.n_fragments =:= 0 ->
+ Nodes = TC#tab_config.replica_nodes,
+ Type = TC#tab_config.replica_type,
+ Def = [{Type, Nodes}, {attributes, Attrs}],
+ create_tab(Name, Def);
+create_tab(TC, Name, Attrs, ForeignKey) ->
+ NReplicas = TC#tab_config.n_replicas,
+ NodePool = TC#tab_config.replica_nodes,
+ Type = TC#tab_config.replica_type,
+ NF = TC#tab_config.n_fragments,
+ Props = [{n_fragments, NF},
+ {node_pool, NodePool},
+ {n_copies(Type), NReplicas},
+ {foreign_key, ForeignKey}],
+ Def = [{frag_properties, Props},
+ {attributes, Attrs}],
+ create_tab(Name, Def).
+
+create_tab(Name, Def) ->
+ mnesia:delete_table(Name),
+ case mnesia:create_table(Name, Def) of
+ {atomic, ok} ->
+ ok;
+ {aborted, Reason} ->
+ io:format("TPC-B: <ERROR> failed to create table ~w ~w: ~p~n",
+ [Name, Def, Reason]),
+ exit({create_table_failed, Reason})
+ end.
+
+n_copies(Type) ->
+ case Type of
+ ram_copies -> n_ram_copies;
+ disc_copies -> n_disc_copies;
+ disc_only_copies -> n_disc_only_copies
+ end.
+
+gen_branches(TC) ->
+ First = 0,
+ Last = First + TC#tab_config.n_branches - 1,
+ GenPids = gen_branches(TC, First, Last, []),
+ wait_for_gen(GenPids).
+
+wait_for_gen([]) ->
+ ok;
+wait_for_gen(Pids) ->
+ receive
+ {branch_generated, Pid} -> wait_for_gen(lists:delete(Pid, Pids));
+ Exit ->
+ exit({tpcb_failed, Exit})
+ end.
+
+gen_branches(TC, BranchId, Last, UsedNs) when BranchId =< Last ->
+ UsedNs2 = get_branch_nodes(BranchId, UsedNs),
+ Node = hd(UsedNs2),
+ Pid = spawn_link(Node, ?MODULE, reply_gen_branch,
+ [self(), TC, BranchId]),
+ [Pid | gen_branches(TC, BranchId + 1, Last, UsedNs2)];
+gen_branches(_, _, _, _) ->
+ [].
+
+reply_gen_branch(ReplyTo, TC, BranchId) ->
+ gen_branch(TC, BranchId),
+ ReplyTo ! {branch_generated, self()},
+ unlink(ReplyTo).
+
+%% Returns a new list of nodes with the best node as head
+get_branch_nodes(BranchId, UsedNs) ->
+ WriteNs = table_info({branch, BranchId}, where_to_write),
+ WeightedNs = [{n_duplicates(N, UsedNs, 0), N} || N <- WriteNs],
+ [{_, LeastUsed} | _ ] = lists:sort(WeightedNs),
+ [LeastUsed | UsedNs].
+
+n_duplicates(_N, [], Count) ->
+ Count;
+n_duplicates(N, [N | Tail], Count) ->
+ n_duplicates(N, Tail, Count + 1);
+n_duplicates(N, [_ | Tail], Count) ->
+ n_duplicates(N, Tail, Count).
+
+gen_branch(TC, BranchId) ->
+ A = default_account(TC),
+ NA = TC#tab_config.n_accounts_per_branch,
+ FirstA = BranchId * NA,
+ ArgsA = [FirstA, FirstA + NA - 1, BranchId, A],
+ ok = mnesia:activity(async_dirty, fun gen_accounts/4, ArgsA, mnesia_frag),
+
+ T = default_teller(TC),
+ NT = TC#tab_config.n_tellers_per_branch,
+ FirstT = BranchId * NT,
+ ArgsT = [FirstT, FirstT + NT - 1, BranchId, T],
+ ok = mnesia:activity(async_dirty, fun gen_tellers/4, ArgsT, mnesia_frag),
+
+ B = default_branch(TC),
+ FunB = fun() -> mnesia:write(branch, B#branch{id = BranchId}, write) end,
+ ok = mnesia:activity(sync_dirty, FunB, [], mnesia_frag).
+
+gen_tellers(Id, Last, BranchId, T) when Id =< Last ->
+ mnesia:write(teller, T#teller{id = Id, branch_id=BranchId}, write),
+ gen_tellers(Id + 1, Last, BranchId, T);
+gen_tellers(_, _, _, _) ->
+ ok.
+
+gen_accounts(Id, Last, BranchId, A) when Id =< Last ->
+ mnesia:write(account, A#account{id = Id, branch_id=BranchId}, write),
+ gen_accounts(Id + 1, Last, BranchId, A);
+gen_accounts(_, _, _, _) ->
+ ok.
+
+default_branch(TC) -> #branch{filler = TC#tab_config.branch_filler}.
+default_teller(TC) -> #teller{filler = TC#tab_config.teller_filler}.
+default_account(TC) -> #account{filler = TC#tab_config.account_filler}.
+default_history(_TC) -> #history{}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%%% Run the benchmark
+
+%% Args is a list of {Key, Val} tuples where Key is a field name
+%% in the record run_config, unknown keys are ignored.
+run(Args) ->
+ RunConfig = list2rec(Args, record_info(fields, run_config), #run_config{}),
+ Tags = record_info(fields, run_config),
+ Fun = fun(F, Pos) -> {{F, element(Pos, RunConfig)}, Pos + 1} end,
+ {List, _} = lists:mapfoldl(Fun, 2, Tags),
+ io:format("TPC-B: Run config: ~p ~n", [List]),
+
+ Pid = spawn_link(?MODULE, reporter_init, [self(), RunConfig]),
+ receive
+ {Pid, {stopped, Res}} ->
+ Res; % Stopped by other process
+ Else ->
+ {tpcb_got, Else}
+ after RunConfig#run_config.stop_after ->
+ sync_stop(Pid)
+ end.
+
+reporter_init(Starter, RC) ->
+ register(mnesia_tpcb, self()),
+ process_flag(trap_exit, true),
+ DbNodes = mnesia:system_info(db_nodes),
+ if
+ RC#run_config.use_running_mnesia =:= true ->
+ ignore;
+ true ->
+ {Replies, BadNodes} =
+ rpc:multicall(DbNodes, mnesia, start, []),
+ case [Res || Res <- Replies, Res =/= ok] of
+ [] when BadNodes =:= [] ->
+ ok;
+ BadRes ->
+ io:format("TPC-B: <ERROR> "
+ "Failed to start ~w: ~p~n",
+ [BadNodes, BadRes]),
+ exit({start_failed, BadRes, BadNodes})
+ end,
+ verify_tabs()
+ end,
+
+ N = table_info(branch, size),
+ NT = table_info(teller, size) div N,
+ NA = table_info(account, size) div N,
+
+ {Type, NF, RepNodes} = table_storage(branch),
+ TC = #tab_config{n_fragments = NF,
+ n_branches = N,
+ n_tellers_per_branch = NT,
+ n_accounts_per_branch = NA,
+ db_nodes = DbNodes,
+ replica_nodes = RepNodes,
+ replica_type = Type
+ },
+ Drivers = start_drivers(RC, TC),
+ Now = now_to_micros(erlang:now()),
+ State = #reporter_state{driver_pids = Drivers,
+ run_config = RC,
+ starter_pid = Starter,
+ init_micros = Now,
+ prev_micros = Now
+ },
+ case catch reporter_loop(State) of
+ {'EXIT', Reason} ->
+ io:format("TPC-B: Abnormal termination: ~p~n", [Reason]),
+ if
+ RC#run_config.use_running_mnesia =:= true ->
+ ignore;
+ true ->
+ rpc:multicall(DbNodes, mnesia, lkill, [])
+ end,
+ unlink(Starter),
+ Starter ! {self(), {stopped, {error, Reason}}}, % To be sure
+ exit(shutdown);
+ {ok, Stopper, State2} ->
+ Time = State2#reporter_state.acc,
+ Res =
+ case verify_tabs() of
+ ok ->
+ {ok, Time};
+ {error, Reason} ->
+ io:format("TPC-B: <ERROR> Inconsistent tables, ~p~n",
+ [{error, Reason}]),
+ {error, Reason}
+ end,
+ if
+ RC#run_config.use_running_mnesia =:= true ->
+ ignore;
+ true ->
+ rpc:multicall(DbNodes, mnesia, stop, [])
+ end,
+ unlink(Starter),
+ Starter ! {self(), {stopped, Res}},
+ if
+ Stopper =/= Starter ->
+ Stopper ! {self(), {stopped, Res}};
+ true ->
+ ignore
+ end,
+ exit(shutdown)
+ end.
+
+table_info(Tab, Item) ->
+ Fun = fun() -> mnesia:table_info(Tab, Item) end,
+ mnesia:activity(sync_dirty, Fun, mnesia_frag).
+
+%% Returns {Storage, NFragments, ReplicaNodes}
+table_storage(Tab) ->
+ case mnesia:table_info(branch, frag_properties) of
+ [] ->
+ NFO = 0,
+ NR = length(mnesia:table_info(Tab, ram_copies)),
+ ND = length(mnesia:table_info(Tab, disc_copies)),
+ NDO = length(mnesia:table_info(Tab, disc_only_copies)),
+ if
+ NR =/= 0 -> {ram_copies, NFO, NR};
+ ND =/= 0 -> {disc_copies, NFO, ND};
+ NDO =/= 0 -> {disc_copies, NFO, NDO}
+ end;
+ Props ->
+ {value, NFO} = lists:keysearch(n_fragments, 1, Props),
+ NR = table_info(Tab, n_ram_copies),
+ ND = table_info(Tab, n_disc_copies),
+ NDO = table_info(Tab, n_disc_only_copies),
+ if
+ NR =/= 0 -> {ram_copies, NFO, NR};
+ ND =/= 0 -> {disc_copies, NFO, ND};
+ NDO =/= 0 -> {disc_copies, NFO, NDO}
+ end
+ end.
+
+reporter_loop(State) ->
+ RC = State#reporter_state.run_config,
+ receive
+ {From, stop} ->
+ {ok, From, call_drivers(State, stop)};
+ {'EXIT', Pid, Reason} when Pid =:= State#reporter_state.starter_pid ->
+ %% call_drivers(State, stop),
+ exit({starter_died, Pid, Reason})
+ after RC#run_config.report_interval ->
+ Iters = State#reporter_state.n_iters,
+ State2 = State#reporter_state{n_iters = Iters + 1},
+ case call_drivers(State2, report) of
+ State3 when State3#reporter_state.driver_pids =/= [] ->
+ State4 = State3#reporter_state{curr = #time{}},
+ reporter_loop(State4);
+ _ ->
+ exit(drivers_died)
+ end
+ end.
+
+call_drivers(State, Msg) ->
+ Drivers = State#reporter_state.driver_pids,
+ lists:foreach(fun(Pid) -> Pid ! {self(), Msg} end, Drivers),
+ State2 = show_report(calc_reports(Drivers, State)),
+ case Msg =:= stop of
+ true ->
+ Acc = State2#reporter_state.acc,
+ Init = State2#reporter_state.init_micros,
+ show_report(State2#reporter_state{n_iters = 0,
+ curr = Acc,
+ prev_micros = Init});
+ false ->
+ ignore
+ end,
+ State2.
+
+calc_reports([], State) ->
+ State;
+calc_reports([Pid|Drivers], State) ->
+ receive
+ {'EXIT', P, Reason} when P =:= State#reporter_state.starter_pid ->
+ exit({starter_died, P, Reason});
+ {'EXIT', Pid, Reason} ->
+ exit({driver_died, Pid, Reason});
+ {Pid, Time} when is_record(Time, time) ->
+ %% io:format("~w: ~w~n", [Pid, Time]),
+ A = add_time(State#reporter_state.acc, Time),
+ C = add_time(State#reporter_state.curr, Time),
+ State2 = State#reporter_state{acc = A, curr = C},
+ calc_reports(Drivers, State2)
+ end.
+
+add_time(Acc, New) ->
+ Acc#time{n_trans = New#time.n_trans + Acc#time.n_trans,
+ min_n = lists:min([New#time.n_trans, Acc#time.min_n] -- [0]),
+ max_n = lists:max([New#time.n_trans, Acc#time.max_n]),
+ acc_time = New#time.acc_time + Acc#time.acc_time,
+ max_time = lists:max([New#time.max_time, Acc#time.max_time])}.
+
+-define(AVOID_DIV_ZERO(_What_), try (_What_) catch _:_ -> 0 end).
+
+show_report(State) ->
+ Now = now_to_micros(erlang:now()),
+ Iters = State#reporter_state.n_iters,
+ Time = State#reporter_state.curr,
+ Max = Time#time.max_time,
+ N = Time#time.n_trans,
+ Avg = ?AVOID_DIV_ZERO(Time#time.acc_time div N),
+ AliveN = length(State#reporter_state.driver_pids),
+ Tps = ?AVOID_DIV_ZERO((?SECOND * AliveN) div Avg),
+ PrevTps= State#reporter_state.prev_tps,
+ {DiffSign, DiffTps} = signed_diff(Iters, Tps, PrevTps),
+ Unfairness = ?AVOID_DIV_ZERO(Time#time.max_n / Time#time.min_n),
+ BruttoAvg = ?AVOID_DIV_ZERO((Now - State#reporter_state.prev_micros) div N),
+%% io:format("n_iters=~p, n_trans=~p, n_drivers=~p, avg=~p, now=~p, prev=~p~n",
+%% [Iters, N, AliveN, BruttoAvg, Now, State#reporter_state.prev_micros]),
+ BruttoTps = ?AVOID_DIV_ZERO(?SECOND div BruttoAvg),
+ case Iters > 0 of
+ true ->
+ io:format("TPC-B: ~p iter ~s~p diff ~p (~p) tps ~p avg micros ~p max micros ~p unfairness~n",
+ [Iters, DiffSign, DiffTps, Tps, BruttoTps, Avg, Max, Unfairness]);
+ false ->
+ io:format("TPC-B: ~p (~p) transactions per second, "
+ "duration of longest transaction was ~p milliseconds~n",
+ [Tps, BruttoTps, Max div 1000])
+ end,
+ State#reporter_state{prev_tps = Tps, prev_micros = Now}.
+
+signed_diff(Iters, Curr, Prev) ->
+ case Iters > 1 of
+ true -> sign(Curr - Prev);
+ false -> sign(0)
+ end.
+
+sign(N) when N > 0 -> {"+", N};
+sign(N) -> {"", N}.
+
+now_to_micros({Mega, Secs, Micros}) ->
+ DT = calendar:now_to_datetime({Mega, Secs, 0}),
+ S = calendar:datetime_to_gregorian_seconds(DT),
+ (S * ?SECOND) + Micros.
+
+start_drivers(RC, TC) ->
+ LastHistoryId = table_info(history, size),
+ Reuse = RC#run_config.reuse_history_id,
+ DS = #driver_state{tab_config = TC,
+ run_config = RC,
+ n_local_branches = 0,
+ local_branches = [],
+ history_id = LastHistoryId,
+ reuse_history_id = Reuse},
+ Nodes = RC#run_config.driver_nodes,
+ NB = TC#tab_config.n_branches,
+ First = 0,
+ AllBranches = lists:seq(First, First + NB - 1),
+ ND = RC#run_config.n_drivers_per_node,
+ Spawn = fun(Spec) ->
+ Node = Spec#driver_state.driver_node,
+ spawn_link(Node, ?MODULE, driver_init, [Spec, AllBranches])
+ end,
+ Specs = [DS#driver_state{driver_id = Id, driver_node = N}
+ || N <- Nodes,
+ Id <- lists:seq(1, ND)],
+ Specs2 = lists:sort(lists:flatten(Specs)),
+ {Specs3, OrphanBranches} = alloc_local_branches(AllBranches, Specs2, []),
+ case length(OrphanBranches) of
+ N when N =< 10 ->
+ io:format("TPC-B: Orphan branches: ~p~n", [OrphanBranches]);
+ N ->
+ io:format("TPC-B: Orphan branches: ~p~n", [N])
+ end,
+ [Spawn(Spec) || Spec <- Specs3].
+
+alloc_local_branches([BranchId | Tail], Specs, OrphanBranches) ->
+ Nodes = table_info({branch, BranchId}, where_to_write),
+ LocalSpecs = [DS || DS <- Specs,
+ lists:member(DS#driver_state.driver_node, Nodes)],
+ case lists:keysort(#driver_state.n_local_branches, LocalSpecs) of
+ [] ->
+ alloc_local_branches(Tail, Specs, [BranchId | OrphanBranches]);
+ [DS | _] ->
+ LocalNB = DS#driver_state.n_local_branches + 1,
+ LocalBranches = [BranchId | DS#driver_state.local_branches],
+ DS2 = DS#driver_state{n_local_branches = LocalNB,
+ local_branches = LocalBranches},
+ Specs2 = Specs -- [DS],
+ Specs3 = [DS2 | Specs2],
+ alloc_local_branches(Tail, Specs3, OrphanBranches)
+ end;
+alloc_local_branches([], Specs, OrphanBranches) ->
+ {Specs, OrphanBranches}.
+
+driver_init(DS, AllBranches) ->
+ Seed = erlang:now(),
+ DS2 =
+ if
+ DS#driver_state.n_local_branches =:= 0 ->
+ DS#driver_state{seed = Seed,
+ n_local_branches = length(AllBranches),
+ local_branches = AllBranches};
+ true ->
+ DS#driver_state{seed = Seed}
+ end,
+ io:format("TPC-B: Driver ~p started as ~p on node ~p with ~p local branches~n",
+ [DS2#driver_state.driver_id, self(), node(), DS2#driver_state.n_local_branches]),
+ driver_loop(DS2).
+
+driver_loop(DS) ->
+ receive
+ {From, report} ->
+ From ! {self(), DS#driver_state.time},
+ Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time),
+ DS2 = DS#driver_state{time=#time{}, acc_time = Acc}, % Reset timer
+ DS3 = calc_trans(DS2),
+ driver_loop(DS3);
+ {From, stop} ->
+ Acc = add_time(DS#driver_state.time, DS#driver_state.acc_time),
+ io:format("TPC-B: Driver ~p (~p) on node ~p stopped: ~w~n",
+ [DS#driver_state.driver_id, self(), node(self()), Acc]),
+ From ! {self(), DS#driver_state.time},
+ unlink(From),
+ exit(stopped)
+ after 0 ->
+ DS2 = calc_trans(DS),
+ driver_loop(DS2)
+ end.
+
+calc_trans(DS) ->
+ {Micros, DS2} = time_trans(DS),
+ Time = DS2#driver_state.time,
+ Time2 = Time#time{n_trans = Time#time.n_trans + 1,
+ acc_time = Time#time.acc_time + Micros,
+ max_time = lists:max([Micros, Time#time.max_time])
+ },
+ case DS#driver_state.reuse_history_id of
+ false ->
+ HistoryId = DS#driver_state.history_id + 1,
+ DS2#driver_state{time=Time2, history_id = HistoryId};
+ true ->
+ DS2#driver_state{time=Time2}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Generate teller_id, account_id and delta
+%% Time the TPC-B transaction
+time_trans(DS) ->
+ OldSeed = get(random_seed), % Avoid interference with Mnesia
+ put(random_seed, DS#driver_state.seed),
+ Random = random:uniform(),
+ NewSeed = get(random_seed),
+ case OldSeed of
+ undefined -> erase(random_seed);
+ _ -> put(random_seed, OldSeed)
+ end,
+
+ TC = DS#driver_state.tab_config,
+ RC = DS#driver_state.run_config,
+ {Branchid, Args} = random_to_args(Random, DS),
+ {Fun, Mod} = trans_type(TC, RC),
+ {Time, Res} = timer:tc(?MODULE, real_trans, [RC, Branchid, Fun, Args, Mod]),
+
+ case Res of
+ AccountBal when is_integer(AccountBal) ->
+ {Time, DS#driver_state{seed = NewSeed}};
+ Other ->
+ exit({crash, Other, Args, Random, DS})
+ end.
+
+random_to_args(Random, DS) ->
+ DriverId = DS#driver_state.driver_id,
+ TC = DS#driver_state.tab_config,
+ HistoryId = DS#driver_state.history_id,
+ Delta = trunc(Random * 1999998) - 999999, % -999999 <= Delta <= +999999
+
+ Branches = DS#driver_state.local_branches,
+ NB = DS#driver_state.n_local_branches,
+ NT = TC#tab_config.n_tellers_per_branch,
+ NA = TC#tab_config.n_accounts_per_branch,
+ Tmp = trunc(Random * NB * NT),
+ BranchPos = (Tmp div NT) + 1,
+ BranchId =
+ case TC#tab_config.n_fragments of
+ 0 -> BranchPos - 1;
+ _ -> lists:nth(BranchPos, Branches)
+ end,
+ RelativeTellerId = Tmp div NT,
+ TellerId = (BranchId * NT) + RelativeTellerId,
+ {AccountBranchId, AccountId} =
+ if
+ Random >= 0.85, NB > 1 ->
+ %% Pick from a remote account
+ TmpAccountId= trunc(Random * (NB - 1) * NA),
+ TmpAccountBranchId = TmpAccountId div NA,
+ if
+ TmpAccountBranchId =:= BranchId ->
+ {TmpAccountBranchId + 1, TmpAccountId + NA};
+ true ->
+ {TmpAccountBranchId, TmpAccountId}
+ end;
+ true ->
+ %% Pick from a local account
+ RelativeAccountId = trunc(Random * NA),
+ TmpAccountId = (BranchId * NA) + RelativeAccountId,
+ {BranchId, TmpAccountId}
+ end,
+
+ {BranchId, [DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta]}.
+
+real_trans(RC, BranchId, Fun, Args, Mod) ->
+ Type = RC#run_config.activity_type,
+ case RC#run_config.spawn_near_branch of
+ false ->
+ mnesia:activity(Type, Fun, Args, Mod);
+ true ->
+ Node = table_info({branch, BranchId}, where_to_read),
+ case rpc:call(Node, mnesia, activity, [Type, Fun, Args, Mod]) of
+ {badrpc, Reason} -> exit(Reason);
+ Other -> Other
+ end
+ end.
+
+trans_type(TC, RC) ->
+ if
+ TC#tab_config.n_fragments =:= 0,
+ RC#run_config.use_sticky_locks =:= false ->
+ {fun add_delta/7, mnesia};
+ TC#tab_config.n_fragments =:= 0,
+ RC#run_config.use_sticky_locks =:= true ->
+ {fun sticky_add_delta/7, mnesia};
+ TC#tab_config.n_fragments > 0,
+ RC#run_config.use_sticky_locks =:= false ->
+ {fun frag_add_delta/7, mnesia_frag}
+ end.
+
+%%
+%% Runs the TPC-B defined transaction and returns NewAccountBalance
+%%
+
+add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) ->
+ %% Grab write lock already when the record is read
+
+ %% Add delta to branch balance
+ [B] = mnesia:read(branch, BranchId, write),
+ NewB = B#branch{balance = B#branch.balance + Delta},
+ ok = mnesia:write(branch, NewB, write),
+
+ %% Add delta to teller balance
+ [T] = mnesia:read(teller, TellerId, write),
+ NewT = T#teller{balance = T#teller.balance + Delta},
+ ok = mnesia:write(teller, NewT, write),
+
+ %% Add delta to account balance
+ [A] = mnesia:read(account, AccountId, write),
+ NewA = A#account{balance = A#account.balance + Delta},
+ ok = mnesia:write(account, NewA, write),
+
+ %% Append to history log
+ History = #history{history_id = {DriverId, HistoryId},
+ account_id = AccountId,
+ teller_id = TellerId,
+ branch_id = BranchId,
+ amount = Delta
+ },
+ ok = mnesia:write(history, History, write),
+
+ %% Return account balance
+ NewA#account.balance.
+
+sticky_add_delta(DriverId, BranchId, TellerId, _AccountBranchId, AccountId, HistoryId, Delta) ->
+ %% Grab orinary read lock when the record is read
+ %% Grab sticky write lock when the record is written
+ %% This transaction would benefit of an early stick_write lock at read
+
+ %% Add delta to branch balance
+ [B] = mnesia:read(branch, BranchId, read),
+ NewB = B#branch{balance = B#branch.balance + Delta},
+ ok = mnesia:write(branch, NewB, sticky_write),
+
+ %% Add delta to teller balance
+ [T] = mnesia:read(teller, TellerId, read),
+ NewT = T#teller{balance = T#teller.balance + Delta},
+ ok = mnesia:write(teller, NewT, sticky_write),
+
+ %% Add delta to account balance
+ [A] = mnesia:read(account, AccountId, read),
+ NewA = A#account{balance = A#account.balance + Delta},
+ ok = mnesia:write(account, NewA, sticky_write),
+
+ %% Append to history log
+ History = #history{history_id = {DriverId, HistoryId},
+ account_id = AccountId,
+ teller_id = TellerId,
+ branch_id = BranchId,
+ amount = Delta
+ },
+ ok = mnesia:write(history, History, sticky_write),
+
+ %% Return account balance
+ NewA#account.balance.
+
+frag_add_delta(DriverId, BranchId, TellerId, AccountBranchId, AccountId, HistoryId, Delta) ->
+ %% Access fragmented table
+ %% Grab write lock already when the record is read
+
+ %% Add delta to branch balance
+ [B] = mnesia:read(branch, BranchId, write),
+ NewB = B#branch{balance = B#branch.balance + Delta},
+ ok = mnesia:write(NewB),
+
+ %% Add delta to teller balance
+ [T] = mnesia:read({teller, BranchId}, TellerId, write),
+ NewT = T#teller{balance = T#teller.balance + Delta},
+ ok = mnesia:write(NewT),
+
+ %% Add delta to account balance
+ %%io:format("frag_add_delta(~p): ~p\n", [node(), {account, BranchId, AccountId}]),
+ [A] = mnesia:read({account, AccountBranchId}, AccountId, write),
+ NewA = A#account{balance = A#account.balance + Delta},
+ ok = mnesia:write(NewA),
+
+ %% Append to history log
+ History = #history{history_id = {DriverId, HistoryId},
+ account_id = AccountId,
+ teller_id = TellerId,
+ branch_id = BranchId,
+ amount = Delta
+ },
+ ok = mnesia:write(History),
+
+ %% Return account balance
+ NewA#account.balance.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Verify table consistency
+
+verify_tabs() ->
+ Nodes = mnesia:system_info(running_db_nodes),
+ case lists:member(node(), Nodes) of
+ true ->
+ Tabs = [branch, teller, account, history],
+ io:format("TPC-B: Verifying tables: ~w~n", [Tabs]),
+ rpc:multicall(Nodes, mnesia, wait_for_tables, [Tabs, infinity]),
+
+ Fun = fun() ->
+ mnesia:write_lock_table(branch),
+ mnesia:write_lock_table(teller),
+ mnesia:write_lock_table(account),
+ mnesia:write_lock_table(history),
+ {Res, BadNodes} =
+ rpc:multicall(Nodes, ?MODULE, count_balance, []),
+ check_balance(Res, BadNodes)
+ end,
+ case mnesia:transaction(Fun) of
+ {atomic, Res} -> Res;
+ {aborted, Reason} -> {error, Reason}
+ end;
+ false ->
+ {error, "Must be initiated from a running db_node"}
+ end.
+
+%% Returns a list of {Table, Node, Balance} tuples
+%% Assumes that no updates are performed
+
+-record(summary, {table, node, balance, size}).
+
+count_balance() ->
+ [count_balance(branch, #branch.balance),
+ count_balance(teller, #teller.balance),
+ count_balance(account, #account.balance)].
+
+count_balance(Tab, BalPos) ->
+ Frags = table_info(Tab, frag_names),
+ count_balance(Tab, Frags, 0, 0, BalPos).
+
+count_balance(Tab, [Frag | Frags], Bal, Size, BalPos) ->
+ First = mnesia:dirty_first(Frag),
+ {Bal2, Size2} = count_frag_balance(Frag, First, Bal, Size, BalPos),
+ count_balance(Tab, Frags, Bal2, Size2, BalPos);
+count_balance(Tab, [], Bal, Size, _BalPos) ->
+ #summary{table = Tab, node = node(), balance = Bal, size = Size}.
+
+count_frag_balance(_Frag, '$end_of_table', Bal, Size, _BalPos) ->
+ {Bal, Size};
+count_frag_balance(Frag, Key, Bal, Size, BalPos) ->
+ [Record] = mnesia:dirty_read({Frag, Key}),
+ Bal2 = Bal + element(BalPos, Record),
+ Next = mnesia:dirty_next(Frag, Key),
+ count_frag_balance(Frag, Next, Bal2, Size + 1, BalPos).
+
+check_balance([], []) ->
+ mnesia:abort({"No balance"});
+check_balance(Summaries, []) ->
+ [One | Rest] = lists:flatten(Summaries),
+ Balance = One#summary.balance,
+ %% Size = One#summary.size,
+ case [S || S <- Rest, S#summary.balance =/= Balance] of
+ [] ->
+ ok;
+ BadSummaries ->
+ mnesia:abort({"Bad balance", One, BadSummaries})
+ end;
+check_balance(_, BadNodes) ->
+ mnesia:abort({"Bad nodes", BadNodes}).
diff --git a/lib/mnesia/test/mnesia_trans_access_test.erl b/lib/mnesia/test/mnesia_trans_access_test.erl
new file mode 100644
index 0000000000..c67382e694
--- /dev/null
+++ b/lib/mnesia/test/mnesia_trans_access_test.erl
@@ -0,0 +1,1254 @@
+%%
+%% %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_trans_access_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).
+
+-define(receive_messages(Msgs), mnesia_recovery_test:receive_messages(Msgs, ?FILE, ?LINE)).
+
+% First Some debug logging
+-define(dgb, true).
+-ifdef(dgb).
+-define(dl(X, Y), ?verbose("**TRACING: " ++ X ++ "**~n", Y)).
+-else.
+-define(dl(X, Y), ok).
+-endif.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+all(doc) ->
+ ["Evil access of records in the scope of transactions",
+ "Invoke all functions in the API and try to cover all legal uses",
+ "cases as well the illegal dito. This is a complement to the",
+ "other more explicit test cases."];
+all(suite) ->
+ [
+ write, read, wread, delete, delete_object,
+ match_object, select, select14, all_keys,
+ transaction, nested_activities,
+ index_tabs, index_lifecycle
+ ].
+
+%% Write records
+
+write(suite) -> [];
+write(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = write,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:write([]) end)),
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 2}) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:write({foo, 2}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:write({Tab, 1, 2})),
+ ?verify_mnesia(Nodes, []).
+
+%% Read records
+
+read(suite) -> [];
+read(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = read,
+ Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ OneRec = {Tab, 1, 2},
+ TwoRec = {Tab, 1, 3},
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:read([]) end)),
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:read({Tab}) end)),
+ ?match({aborted, {bad_type, _}}
+ , mnesia:transaction(fun() -> mnesia:read(OneRec) end)),
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, [OneRec]},
+ mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(TwoRec) end)),
+ ?match({atomic, [OneRec, TwoRec]},
+ mnesia:transaction(fun() -> mnesia:read({Tab, 1}) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:read({Tab, 1})),
+ ?verify_mnesia(Nodes, []).
+
+%% Read records and set write lock
+
+wread(suite) -> [];
+wread(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = wread,
+ Schema = [{name, Tab}, {type, set}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ OneRec = {Tab, 1, 2},
+ TwoRec = {Tab, 1, 3},
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:wread([]) end)),
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:wread({Tab}) end)),
+ ?match({aborted, {bad_type, _}}
+ , mnesia:transaction(fun() -> mnesia:wread(OneRec) end)),
+
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+
+ ?match({atomic, [OneRec]},
+ mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(TwoRec) end)),
+ ?match({atomic, [TwoRec]},
+ mnesia:transaction(fun() -> mnesia:wread({Tab, 1}) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:wread({Tab, 1})),
+ ?verify_mnesia(Nodes, []).
+
+%% Delete record
+
+delete(suite) -> [];
+delete(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = delete,
+ Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:delete([]) end)),
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:delete({Tab}) end)),
+ ?match({aborted, {bad_type, _}}
+ , mnesia:transaction(fun() -> mnesia:delete({Tab, 1, 2}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 1, 2}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete({Tab, 1})),
+ ?verify_mnesia(Nodes, []).
+
+%% Delete matching record
+
+delete_object(suite) -> [];
+delete_object(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = delete_object,
+ Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ OneRec = {Tab, 1, 2},
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:delete_object([]) end)),
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:delete_object({Tab}) end)),
+ ?match({aborted, {bad_type, _}},
+ mnesia:transaction(fun() -> mnesia:delete_object({Tab, 1}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:delete_object(OneRec) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:delete_object(OneRec)),
+
+ ?match({aborted, {bad_type, Tab, _}},
+ mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['_']}, 21}) end)),
+ ?match({aborted, {bad_type, Tab, _}},
+ mnesia:transaction(fun() -> mnesia:delete_object({Tab, {['$5']}, 21}) end)),
+
+ ?verify_mnesia(Nodes, []).
+
+%% Read matching records
+
+match_object(suite) -> [];
+match_object(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = match,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ OneRec = {Tab, 1, 2},
+ OnePat = {Tab, '$1', 2},
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, [OneRec]},
+ mnesia:transaction(fun() -> mnesia:match_object(OnePat) end)),
+
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:match_object({foo, '$1', 2}) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:match_object({[], '$1', 2}) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:match_object(OnePat)),
+ ?verify_mnesia(Nodes, []).
+
+%% select
+select(suite) -> [];
+select(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = match,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ OneRec = {Tab, 1, 2},
+ TwoRec = {Tab, 2, 3},
+ OnePat = [{{Tab, '$1', 2}, [], ['$_']}],
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(TwoRec) end)),
+ ?match({atomic, [OneRec]},
+ mnesia:transaction(fun() -> mnesia:select(Tab, OnePat) end)),
+
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:select(Tab, {match, '$1', 2}) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:select(Tab, [{'_', [], '$1'}]) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat)),
+ ?verify_mnesia(Nodes, []).
+
+
+%% more select
+select14(suite) -> [];
+select14(Config) when is_list(Config) ->
+ [Node1,Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab1 = select14_ets,
+ Tab2 = select14_dets,
+ Tab3 = select14_remote,
+ Tab4 = select14_remote_dets,
+ Schemas = [[{name, Tab1}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ [{name, Tab2}, {attributes, [k, v]}, {disc_only_copies, [Node1]}],
+ [{name, Tab3}, {attributes, [k, v]}, {ram_copies, [Node2]}],
+ [{name, Tab4}, {attributes, [k, v]}, {disc_only_copies, [Node2]}]],
+ [?match({atomic, ok}, mnesia:create_table(Schema)) || Schema <- Schemas],
+
+ %% Some Helpers
+ Trans = fun(Fun) -> mnesia:transaction(Fun) end,
+ LoopHelp = fun('$end_of_table',_) -> [];
+ ({Recs,Cont},Fun) ->
+ Sel = mnesia:select(Cont),
+ Recs ++ Fun(Sel, Fun)
+ end,
+ Loop = fun(Table,Pattern) ->
+ Sel = mnesia:select(Table, Pattern, 1, read),
+ Res = LoopHelp(Sel,LoopHelp),
+ case mnesia:table_info(Table, type) of
+ ordered_set -> Res;
+ _ -> lists:sort(Res)
+ end
+ end,
+ Test =
+ fun(Tab) ->
+ OneRec = {Tab, 1, 2},
+ TwoRec = {Tab, 2, 3},
+ OnePat = [{{Tab, '$1', 2}, [], ['$_']}],
+ All = [OneRec,TwoRec],
+ AllPat = [{'_', [], ['$_']}],
+
+ ?match({atomic, []}, Trans(fun() -> Loop(Tab, OnePat) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(TwoRec) end)),
+ ?match({atomic, [OneRec]}, Trans(fun() -> Loop(Tab, OnePat) end)),
+ ?match({atomic, All}, Trans(fun() -> Loop(Tab, AllPat) end)),
+
+ {atomic,{_, Cont}} = Trans(fun() -> mnesia:select(Tab, OnePat, 1, read) end),
+ ?match({aborted, wrong_transaction}, Trans(fun() -> mnesia:select(Cont) end)),
+
+ ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, {match, '$1', 2},1,read) end)),
+ ?match({aborted, _}, Trans(fun() -> mnesia:select(Tab, [{'_', [], '$1'}],1,read) end)),
+ ?match({aborted, _}, Trans(fun() -> mnesia:select(sune) end)),
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:select(Tab, OnePat,1,read)),
+ ?match({aborted, {badarg,sune}},
+ Trans(fun() -> mnesia:select(sune) end))
+ end,
+ Test(Tab1),
+ Test(Tab2),
+ Test(Tab3),
+ Test(Tab4),
+ ?verify_mnesia(Nodes, []).
+
+
+%% Pick all keys from table
+
+all_keys(suite) ->[];
+all_keys(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = all_keys,
+ Schema = [{name, Tab}, {type, bag}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ Write = fun() -> mnesia:write({Tab, 14, 4}) end,
+ AllKeys = fun() -> mnesia:all_keys(Tab) end,
+
+ ?match({atomic, []}, mnesia:transaction(AllKeys)),
+
+ ?match({atomic, ok}, mnesia:transaction(Write)),
+ ?match({atomic, [14]}, mnesia:transaction(AllKeys)),
+
+ ?match({atomic, ok}, mnesia:transaction(Write)),
+ ?match({atomic, [14]}, mnesia:transaction(AllKeys)),
+
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:all_keys(foo) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:all_keys([]) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:all_keys(Tab)),
+ ?verify_mnesia(Nodes, []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Use and misuse transactions
+
+transaction(suite) -> [];
+transaction(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end)),
+ ?match({aborted, _}, mnesia:transaction(no_fun)),
+ ?match({aborted, _}, mnesia:transaction(?MODULE, no_fun, [foo])),
+
+ {success, [A, B, C, D, E, F, G, H]} =
+ ?start_activities(lists:duplicate(8, Node1)),
+ ?start_transactions([A, B, C, D, E, F, G, H]),
+
+ A ! fun() -> mnesia:abort(abort_bad_trans) end,
+ ?match_receive({A, {aborted, abort_bad_trans}}),
+
+ B ! fun() -> erlang:error(exit_here) end,
+ ?match_receive({B, {aborted, _}}),
+
+ C ! fun() -> throw(throw_bad_trans) end,
+ ?match_receive({C, {aborted, {throw, throw_bad_trans}}}),
+
+ D ! fun() -> exit(exit_bad_trans) end,
+ ?match_receive({D, {aborted, exit_bad_trans}}),
+
+ E ! fun() -> exit(normal) end,
+ ?match_receive({E, {aborted, normal}}),
+
+ F ! fun() -> exit(abnormal) end,
+ ?match_receive({F, {aborted, abnormal}}),
+
+ G ! fun() -> exit(G, abnormal) end,
+ ?match_receive({'EXIT', G, abnormal}),
+
+ H ! fun() -> exit(H, kill) end,
+ ?match_receive({'EXIT', H, killed}),
+
+ ?match({atomic, ali_baba},
+ mnesia:transaction(fun() -> ali_baba end, infinity)),
+ ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 1)),
+ ?match({atomic, ali_baba}, mnesia:transaction(fun() -> ali_baba end, 0)),
+ ?match({aborted, Reason8} when element(1, Reason8) == badarg, mnesia:transaction(fun() -> ali_baba end, -1)),
+ ?match({aborted, Reason1} when element(1, Reason1) == badarg, mnesia:transaction(fun() -> ali_baba end, foo)),
+ Fun = fun() ->
+ ?match(true, mnesia:is_transaction()),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> ?match(true, mnesia:is_transaction()),ok end)), ok end,
+ ?match({atomic, ok}, mnesia:transaction(Fun)),
+ ?verify_mnesia(Nodes, []).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+nested_activities(suite) ->
+ [
+ basic_nested,
+ nested_transactions,
+ mix_of_nested_activities
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% ensure that nested transactions behave correctly
+%% We create a particular table that is used by this test only
+-record(ntab, {a, b}).
+basic_nested(doc) -> ["Test the basic functionality of nested transactions"];
+basic_nested(suite) -> [];
+basic_nested(Config) when is_list(Config) ->
+ Nodes = ?acquire_nodes(3, Config),
+ Args = [{ram_copies, Nodes},
+ {attributes, record_info(fields, ntab)}],
+ ?match({atomic, ok}, mnesia:create_table(ntab, Args)),
+ do_nested(top),
+ case mnesia_test_lib:diskless(Config) of
+ false ->
+ lists:foreach(fun(N) ->
+ ?match({atomic, ok},
+ mnesia:change_table_copy_type(ntab, N, disc_only_copies))
+ end, Nodes),
+ do_nested(top);
+ true ->
+ skip
+ end,
+ ?verify_mnesia(Nodes, []).
+
+do_nested(How) ->
+ F1 = fun() ->
+ mnesia:write(#ntab{a= 1}),
+ mnesia:write(#ntab{a= 2})
+ end,
+ F2 = fun() ->
+ mnesia:read({ntab, 1})
+ end,
+ ?match({atomic, ok}, mnesia:transaction(F1)),
+ ?match({atomic, _}, mnesia:transaction(F2)),
+
+ ?match({atomic, {aborted, _}},
+ mnesia:transaction(fun() -> n_f1(),
+ mnesia:transaction(fun() -> n_f2() end)
+ end)),
+
+ ?match({atomic, {aborted, _}},
+ mnesia:transaction(fun() -> n_f1(),
+ mnesia:transaction(fun() -> n_f3() end)
+ end)),
+ ?match({atomic, {atomic, [#ntab{a = 5}]}},
+ mnesia:transaction(fun() -> mnesia:write(#ntab{a = 5}),
+ mnesia:transaction(fun() -> n_f4() end)
+ end)),
+ Cyclic = fun() -> mnesia:abort({cyclic,a,a,a,a,a}) end, %% Ugly
+ NodeNotR = fun() -> mnesia:abort({node_not_running, testNode}) end,
+
+ TestAbort = fun(Fun) ->
+ case get(restart_counter) of
+ undefined ->
+ put(restart_counter, 1),
+ Fun();
+ _ ->
+ erase(restart_counter),
+ ok
+ end
+ end,
+
+ ?match({atomic,{atomic,ok}},
+ mnesia:transaction(fun()->mnesia:transaction(TestAbort,
+ [Cyclic])end)),
+
+ ?match({atomic,{atomic,ok}},
+ mnesia:transaction(fun()->mnesia:transaction(TestAbort,
+ [NodeNotR])end)),
+
+ %% Now try the restart thingie
+ case How of
+ top ->
+ Pids = [spawn(?MODULE, do_nested, [{spawned, self()}]),
+ spawn(?MODULE, do_nested, [{spawned, self()}]),
+ spawn(?MODULE, do_nested, [{spawned, self()}]),
+ spawn(?MODULE, do_nested, [{spawned, self()}])],
+ ?match({info, _, _}, mnesia_tm:get_info(2000)),
+ lists:foreach(fun(P) -> receive
+ {P, ok} -> ok
+ end
+ end, Pids),
+ ?match([], [Tab || Tab <- ets:all(), mnesia_trans_store == ets:info(Tab, name)]);
+
+ {spawned, Pid} ->
+ ?match({info, _, _}, mnesia_tm:get_info(2000)),
+ Pid ! {self(), ok},
+ exit(normal)
+ end.
+
+
+n_f1() ->
+ mnesia:read({ntab, 1}),
+ mnesia:write(#ntab{a = 3}).
+
+n_f2() ->
+ mnesia:write(#ntab{a = 4}),
+ erlang:error(exit_here).
+
+n_f3() ->
+ mnesia:write(#ntab{a = 4}),
+ throw(funky).
+
+n_f4() ->
+ mnesia:read({ntab, 5}).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+nested_transactions(doc) ->
+ ["Verify that nested_transactions are handled as expected"];
+nested_transactions(suite) ->
+ [nested_trans_both_ok,
+ nested_trans_child_dies,
+ nested_trans_parent_dies,
+ nested_trans_both_dies].
+
+nested_trans_both_ok(suite) -> [];
+nested_trans_both_ok(Config) when is_list(Config) ->
+ nested_transactions(Config, ok, ok).
+
+nested_trans_child_dies(suite) -> [];
+nested_trans_child_dies(Config) when is_list(Config) ->
+ nested_transactions(Config, abort, ok).
+
+nested_trans_parent_dies(suite) -> [];
+nested_trans_parent_dies(Config) when is_list(Config) ->
+ nested_transactions(Config, ok, abort).
+
+nested_trans_both_dies(suite) -> [];
+nested_trans_both_dies(Config) when is_list(Config) ->
+ nested_transactions(Config, abort, abort).
+
+nested_transactions(Config, Child, Father) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Tab = nested_trans,
+
+ Def =
+ case mnesia_test_lib:diskless(Config) of
+ true ->
+ [{name, Tab}, {ram_copies, Nodes}];
+ false ->
+ [{name, Tab}, {ram_copies, [Node1]},
+ {disc_copies, [Node2]}, {disc_only_copies, [Node3]}]
+ end,
+
+ ?match({atomic, ok}, mnesia:create_table(Def)),
+ ?match(ok, mnesia:dirty_write({Tab, father, not_updated})),
+ ?match(ok, mnesia:dirty_write({Tab, child, not_updated})),
+
+ ChildOk = fun() -> mnesia:write({Tab, child, updated}) end,
+ ChildAbort = fun() ->
+ mnesia:write({Tab, child, updated}),
+ erlang:error(exit_here)
+ end,
+
+ Child_Fun = % Depending of test case
+ case Child of
+ ok -> ChildOk;
+ abort -> ChildAbort
+ end,
+
+ FatherOk = fun() -> mnesia:transaction(Child_Fun),
+ mnesia:write({Tab, father, updated})
+ end,
+
+ FatherAbort = fun() -> mnesia:transaction(Child_Fun),
+ mnesia:write({Tab, father, updated}),
+ erlang:error(exit_here)
+ end,
+
+ {FatherRes, ChildRes} = % Depending of test case
+ case Father of
+ ok -> ?match({atomic, ok}, mnesia:transaction(FatherOk)),
+ case Child of
+ ok -> {[{Tab, father, updated}], [{Tab, child, updated}]};
+ _ -> {[{Tab, father, updated}], [{Tab, child, not_updated}]}
+ end;
+ abort -> ?match({aborted, _}, mnesia:transaction(FatherAbort)),
+ {[{Tab, father, not_updated}], [{Tab, child, not_updated}]}
+ end,
+
+ %% Syncronize things!!
+ ?match({atomic, ok}, mnesia:sync_transaction(fun() -> mnesia:write({Tab, sync, sync}) end)),
+
+ ?match(ChildRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, child}])),
+ ?match(ChildRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, child}])),
+ ?match(ChildRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, child}])),
+
+ ?match(FatherRes, rpc:call(Node1, mnesia, dirty_read, [{Tab, father}])),
+ ?match(FatherRes, rpc:call(Node2, mnesia, dirty_read, [{Tab, father}])),
+ ?match(FatherRes, rpc:call(Node3, mnesia, dirty_read, [{Tab, father}])),
+ ?verify_mnesia(Nodes, []).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+mix_of_nested_activities(doc) ->
+ ["Verify that dirty operations in a transaction are handled like ",
+ "normal transactions"];
+mix_of_nested_activities(suite) -> [];
+mix_of_nested_activities(Config) when is_list(Config) ->
+ [Node1, Node2, Node3] = Nodes = ?acquire_nodes(3, Config),
+ Tab = tab,
+
+ Def =
+ case mnesia_test_lib:diskless(Config) of
+ true -> [{ram_copies, Nodes}];
+ false ->
+ [{ram_copies, [Node1]},
+ {disc_copies, [Node2]},
+ {disc_only_copies, [Node3]}]
+ end,
+
+ ?match({atomic, ok}, mnesia:create_table(Tab, [{type,bag}|Def])),
+ Activities = [transaction, sync_transaction,
+ ets, async_dirty, sync_dirty],
+ %% Make a test for all 3000 combinations
+ Tests = [[A,B,C,D,E] ||
+ A <- Activities,
+ B <- Activities,
+ C <- Activities,
+ D <- Activities,
+ E <- Activities],
+ Foreach =
+ fun(Test,No) ->
+ Result = lists:reverse(Test),
+ ?match({No,Result},{No,catch apply_op({Tab,No},Test)}),
+ No+1
+ end,
+ lists:foldl(Foreach, 0, Tests),
+ ?verify_mnesia(Nodes, []).
+
+apply_op(Oid,[Type]) ->
+ check_res(Type,mnesia:Type(fun() -> [Type|read_op(Oid)] end));
+apply_op(Oid = {Tab,Key},[Type|Next]) ->
+ check_res(Type,mnesia:Type(fun() ->
+ Prev = read_op(Oid),
+ mnesia:write({Tab,Key,[Type|Prev]}),
+ apply_op(Oid,Next)
+ end)).
+
+check_res(transaction, {atomic,Res}) ->
+ Res;
+check_res(sync_transaction, {atomic,Res}) ->
+ Res;
+check_res(async_dirty, Res) when is_list(Res) ->
+ Res;
+check_res(sync_dirty, Res) when is_list(Res) ->
+ Res;
+check_res(ets, Res) when is_list(Res) ->
+ Res;
+check_res(Type,Res) ->
+ ?match(bug,{Type,Res}).
+
+read_op(Oid) ->
+ case lists:reverse(mnesia:read(Oid)) of
+ [] -> [];
+ [{_,_,Ops}|_] ->
+ Ops
+ end.
+
+index_tabs(suite) ->
+ [
+ index_match_object,
+ index_read,
+ index_update,
+ index_write
+ ].
+
+%% Read matching records by using an index
+
+index_match_object(suite) -> [];
+index_match_object(Config) when is_list(Config) ->
+ [Node1, Node2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = index_match_object,
+ Schema = [{name, Tab}, {attributes, [k, v, e]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ValPos = 3,
+ BadValPos = ValPos + 2,
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:index_match_object({Tab, '$1', 2}, ValPos) end)),
+ OneRec = {Tab, {1, 1}, 2, {1, 1}},
+ OnePat = {Tab, '$1', 2, '_'},
+ BadPat = {Tab, '$1', '$2', '_'}, %% See ref guide
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+
+ Imatch = fun(Patt, Pos) ->
+ mnesia:transaction(fun() -> lists:sort(mnesia:index_match_object(Patt, Pos)) end)
+ end,
+ ?match({atomic, [OneRec]}, Imatch(OnePat, ValPos)),
+ ?match({aborted, _}, Imatch(OnePat, BadValPos)),
+ ?match({aborted, _}, Imatch({foo, '$1', 2, '_'}, ValPos)),
+ ?match({aborted, _}, Imatch({[], '$1', 2, '_'}, ValPos)),
+ ?match({aborted, _}, Imatch(BadPat, ValPos)),
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_match_object(OnePat, ValPos)),
+
+ Another = {Tab, {3,1}, 2, {4,4}},
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Another) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, {4, 4}, 3, {4, 4}}) end)),
+
+ ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, {1,1}}, ValPos)),
+ ?match({atomic, [OneRec]}, Imatch({Tab, {1,1}, 2, '$1'}, ValPos)),
+ ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, {1,1}}, ValPos)),
+ ?match({atomic, [OneRec]}, Imatch({Tab, '$1', 2, '$1'}, ValPos)),
+ ?match({atomic, [OneRec]}, Imatch({Tab, {1, '$1'}, 2, '_'}, ValPos)),
+ ?match({atomic, [OneRec]}, Imatch({Tab, {'$2', '$1'}, 2, {'_', '$1'}}, ValPos)),
+ ?match({atomic, [OneRec, Another]}, Imatch({Tab, '_', 2, '_'}, ValPos)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 4, 5, {7, 4}}) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write({Tab, 7, 5, {7, 5}}) end)),
+
+ ?match({atomic, [{Tab, 4, 5, {7, 4}}]}, Imatch({Tab, '$1', 5, {'_', '$1'}}, ValPos)),
+
+ ?match({atomic, [OneRec]}, rpc:call(Node2, mnesia, transaction,
+ [fun() ->
+ lists:sort(mnesia:index_match_object({Tab, {1,1}, 2,
+ {1,1}}, ValPos))
+ end])),
+ ?verify_mnesia(Nodes, []).
+
+%% Read records by using an index
+
+index_read(suite) -> [];
+index_read(Config) when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = index_read,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ValPos = 3,
+ BadValPos = ValPos + 1,
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ OneRec = {Tab, 1, 2},
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(OneRec) end)),
+ ?match({atomic, [OneRec]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, BadValPos) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:index_read(foo, 2, ValPos) end)),
+ ?match({aborted, _},
+ mnesia:transaction(fun() -> mnesia:index_read([], 2, ValPos) end)),
+
+ ?match({'EXIT', {aborted, no_transaction}}, mnesia:index_read(Tab, 2, ValPos)),
+ ?verify_mnesia(Nodes, []).
+
+index_update(suite) -> [index_update_set, index_update_bag];
+index_update(doc) -> ["See Ticket OTP-2083, verifies that a table with a index is "
+ "update in the correct way i.e. the index finds the correct "
+ "records after a update"].
+index_update_set(suite) -> [];
+index_update_set(Config)when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = index_test,
+ Schema = [{name, Tab}, {attributes, [k, v1, v2, v3]}, {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ValPos = v1,
+ ValPos2 = v3,
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ Pat1 = {Tab, '$1', 2, '$2', '$3'},
+ Pat2 = {Tab, '$1', '$2', '$3', '$4'},
+
+ Rec1 = {Tab, 1, 2, 3, 4},
+ Rec2 = {Tab, 2, 2, 13, 14},
+ Rec3 = {Tab, 1, 12, 13, 14},
+ Rec4 = {Tab, 4, 2, 13, 14},
+
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec1) end)),
+ ?match({atomic, [Rec1]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec2) end)),
+ {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2], lists:sort(R1)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec3) end)),
+ {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec2], lists:sort(R2)),
+ ?match({atomic, [Rec2]},
+ mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end)),
+
+ {atomic, R3} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end),
+ ?match([Rec3, Rec2], lists:sort(R3)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec4) end)),
+ {atomic, R4} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec2, Rec4], lists:sort(R4)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)),
+ ?match({atomic, [Rec2]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+
+ ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)),
+
+ {atomic, R5} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end),
+ ?match([Rec3, Rec2, Rec4], lists:sort(R5)),
+
+ {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec2, Rec4], lists:sort(R6)),
+
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)),
+ {atomic, R7} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end),
+ ?match([Rec3, Rec2, Rec4], lists:sort(R7)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)),
+ {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2, Rec4], lists:sort(R8)),
+ ?match({atomic, [Rec1]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)),
+ {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end),
+ ?match([Rec2, Rec4], lists:sort(R9)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec2) end)),
+ {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec4], lists:sort(R10)),
+ ?match({atomic, [Rec1]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)),
+ ?match({atomic, [Rec4]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)),
+ {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1], lists:sort(R11)),
+ ?match({atomic, [Rec1]},mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)),
+ ?match({atomic, []},mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end)),
+
+ ?verify_mnesia(Nodes, []).
+
+index_update_bag(suite) -> [];
+index_update_bag(Config)when is_list(Config) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = index_test,
+ Schema = [{name, Tab},
+ {type, bag},
+ {attributes, [k, v1, v2, v3]},
+ {ram_copies, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ValPos = v1,
+ ValPos2 = v3,
+
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+
+ Pat1 = {Tab, '$1', 2, '$2', '$3'},
+ Pat2 = {Tab, '$1', '$2', '$3', '$4'},
+
+ Rec1 = {Tab, 1, 2, 3, 4},
+ Rec2 = {Tab, 2, 2, 13, 14},
+ Rec3 = {Tab, 1, 12, 13, 14},
+ Rec4 = {Tab, 4, 2, 13, 4},
+ Rec5 = {Tab, 1, 2, 234, 14},
+
+ %% Simple Index
+ ?match({atomic, []},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec1) end)),
+ ?match({atomic, [Rec1]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec2) end)),
+ {atomic, R1} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2], lists:sort(R1)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec3) end)),
+ {atomic, R2} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2], lists:sort(R2)),
+
+ {atomic, R3} = mnesia:transaction(fun() -> mnesia:index_match_object(Pat1, ValPos) end),
+ ?match([Rec1, Rec2], lists:sort(R3)),
+
+ {atomic, R4} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end),
+ ?match([Rec1, Rec3, Rec2], lists:sort(R4)),
+
+ ?match({atomic, ok},
+ mnesia:transaction(fun() -> mnesia:write(Rec4) end)),
+ {atomic, R5} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2, Rec4], lists:sort(R5)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 4}) end)),
+ {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2], lists:sort(R6)),
+
+ %% OTP-6587 Needs some whitebox testing to see that the index table is cleaned correctly
+
+ [IPos] = mnesia_lib:val({Tab,index}),
+ ITab = mnesia_lib:val({index_test,{index, IPos}}),
+ io:format("~n Index ~p @ ~p => ~p ~n~n",[IPos,ITab, ets:tab2list(ITab)]),
+ ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)),
+ {atomic, R60} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1,Rec5,Rec2], lists:sort(R60)),
+
+ ?match([{2,1},{2,2},{12,1}], ets:tab2list(ITab)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec3) end)),
+ {atomic, R61} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1,Rec5,Rec2], lists:sort(R61)),
+ {atomic, R62} = mnesia:transaction(fun() -> mnesia:index_read(Tab,12, ValPos) end),
+ ?match([], lists:sort(R62)),
+ ?match([{2,1},{2,2}], ets:tab2list(ITab)),
+
+ %% reset for rest of testcase
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec3) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)),
+ {atomic, R6} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2], lists:sort(R6)),
+ %% OTP-6587
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)),
+ ?match({atomic, [Rec2]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end)),
+ {atomic, R7} = mnesia:transaction(fun() -> mnesia:match_object(Pat2) end),
+ ?match([Rec3, Rec2], lists:sort(R7)),
+
+ %% Two indexies
+ ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec4) end)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos2)),
+
+ {atomic, R8} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec2, Rec4], lists:sort(R8)),
+
+ {atomic, R9} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end),
+ ?match([Rec1, Rec4], lists:sort(R9)),
+ {atomic, R10} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end),
+ ?match([Rec3, Rec2], lists:sort(R10)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec5) end)),
+ {atomic, R11} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec1, Rec5, Rec2, Rec4], lists:sort(R11)),
+ {atomic, R12} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end),
+ ?match([Rec1, Rec4], lists:sort(R12)),
+ {atomic, R13} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end),
+ ?match([Rec5, Rec3, Rec2], lists:sort(R13)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec1) end)),
+ {atomic, R14} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec5, Rec2, Rec4], lists:sort(R14)),
+ ?match({atomic, [Rec4]},
+ mnesia:transaction(fun() -> mnesia:index_read(Tab, 4, ValPos2) end)),
+ {atomic, R15} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end),
+ ?match([Rec5, Rec3, Rec2], lists:sort(R15)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete_object(Rec5) end)),
+ {atomic, R16} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec2, Rec4], lists:sort(R16)),
+ ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)),
+ {atomic, R17} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end),
+ ?match([Rec3, Rec2], lists:sort(R17)),
+
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:write(Rec1) end)),
+ ?match({atomic, ok}, mnesia:transaction(fun() -> mnesia:delete({Tab, 1}) end)),
+ {atomic, R18} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 2, ValPos) end),
+ ?match([Rec2, Rec4], lists:sort(R18)),
+ ?match({atomic, [Rec4]}, mnesia:transaction(fun()->mnesia:index_read(Tab, 4, ValPos2) end)),
+ {atomic, R19} = mnesia:transaction(fun() -> mnesia:index_read(Tab, 14, ValPos2) end),
+ ?match([Rec2], lists:sort(R19)),
+
+ ?verify_mnesia(Nodes, []).
+
+
+index_write(suite) -> [];
+index_write(doc) -> ["See ticket OTP-8072"];
+index_write(Config)when is_list(Config) ->
+ Nodes = ?acquire_nodes(1, Config),
+ mnesia:create_table(a, [{index, [val]}]),
+ mnesia:create_table(counter, []),
+
+ CreateIfNonExist =
+ fun(Index) ->
+ case mnesia:index_read(a, Index, 3) of
+ [] ->
+ Id = mnesia:dirty_update_counter(counter, id, 1),
+ New = {a, Id, Index},
+ mnesia:write(New),
+ New;
+ [Found] ->
+ Found
+ end
+ end,
+
+ Trans = fun(A) ->
+ mnesia:transaction(CreateIfNonExist, [A])
+ %% This works better most of the time
+ %% And it is allowed to fail since it's dirty
+ %% mnesia:async_dirty(CreateIfNonExist, [A])
+ end,
+
+ Self = self(),
+ Update = fun() ->
+ Res = lists:map(Trans, lists:seq(1,10)),
+ Self ! {self(), Res}
+ end,
+
+ Pids = [spawn(Update) || _ <- lists:seq(1,5)],
+
+ Gather = fun(Pid, Acc) -> receive {Pid, Res} -> [Res|Acc] end end,
+ Results = lists:foldl(Gather, [], Pids),
+ Expected = hd(Results),
+ Check = fun(Res) -> ?match(Expected, Res) end,
+ lists:foreach(Check, Results),
+ ?verify_mnesia(Nodes, []).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Add and drop indecies
+
+index_lifecycle(suite) ->
+ [
+ add_table_index_ram,
+ add_table_index_disc,
+ add_table_index_disc_only,
+ create_live_table_index_ram,
+ create_live_table_index_disc,
+ create_live_table_index_disc_only,
+ del_table_index_ram,
+ del_table_index_disc,
+ del_table_index_disc_only,
+ idx_schema_changes
+ ].
+
+add_table_index_ram(suite) -> [];
+add_table_index_ram(Config) when is_list(Config) ->
+ add_table_index(Config, ram_copies).
+
+add_table_index_disc(suite) -> [];
+add_table_index_disc(Config) when is_list(Config) ->
+ add_table_index(Config, disc_copies).
+
+add_table_index_disc_only(suite) -> [];
+add_table_index_disc_only(Config) when is_list(Config) ->
+ add_table_index(Config, disc_only_copies).
+
+%% Add table index
+
+add_table_index(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = add_table_index,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ValPos = 3,
+ BadValPos = ValPos + 1,
+ ?match({aborted, Reason41 } when element(1, Reason41) == bad_type,
+ mnesia:add_table_index(Tab, BadValPos)),
+ ?match({aborted,Reason42 } when element(1, Reason42) == bad_type,
+ mnesia:add_table_index(Tab, 2)),
+ ?match({aborted, Reason43 } when element(1, Reason43) == bad_type,
+ mnesia:add_table_index(Tab, 1)),
+ ?match({aborted, Reason44 } when element(1, Reason44) == bad_type,
+ mnesia:add_table_index(Tab, 0)),
+ ?match({aborted, Reason45 } when element(1, Reason45) == bad_type,
+ mnesia:add_table_index(Tab, -1)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+ ?match({aborted, Reason46 } when element(1, Reason46) == already_exists,
+ mnesia:add_table_index(Tab, ValPos)),
+
+ NestedFun = fun() ->
+ ?match({aborted, nested_transaction},
+ mnesia:add_table_index(Tab, ValPos)),
+ ok
+ end,
+ ?match({atomic, ok}, mnesia:transaction(NestedFun)),
+ ?verify_mnesia(Nodes, []).
+
+create_live_table_index_ram(suite) -> [];
+create_live_table_index_ram(Config) when is_list(Config) ->
+ create_live_table_index(Config, ram_copies).
+
+create_live_table_index_disc(suite) -> [];
+create_live_table_index_disc(Config) when is_list(Config) ->
+ create_live_table_index(Config, disc_copies).
+
+create_live_table_index_disc_only(suite) -> [];
+create_live_table_index_disc_only(Config) when is_list(Config) ->
+ create_live_table_index(Config, disc_only_copies).
+
+create_live_table_index(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = create_live_table_index,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ValPos = 3,
+ mnesia:dirty_write({Tab, 1, 2}),
+
+ Fun = fun() ->
+ ?match(ok, mnesia:write({Tab, 2, 2})),
+ ok
+ end,
+ ?match({atomic, ok}, mnesia:transaction(Fun)),
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+ ?match({atomic, [{Tab, 1, 2},{Tab, 2, 2}]},
+ mnesia:transaction(fun() -> lists:sort(mnesia:index_read(Tab, 2, ValPos))
+ end)),
+ ?verify_mnesia(Nodes, []).
+
+%% Drop table index
+
+del_table_index_ram(suite) ->[];
+del_table_index_ram(Config) when is_list(Config) ->
+ del_table_index(Config, ram_copies).
+
+del_table_index_disc(suite) ->[];
+del_table_index_disc(Config) when is_list(Config) ->
+ del_table_index(Config, disc_copies).
+
+del_table_index_disc_only(suite) ->[];
+del_table_index_disc_only(Config) when is_list(Config) ->
+ del_table_index(Config, disc_only_copies).
+
+del_table_index(Config, Storage) ->
+ [Node1] = Nodes = ?acquire_nodes(1, Config),
+ Tab = del_table_index,
+ Schema = [{name, Tab}, {attributes, [k, v]}, {Storage, [Node1]}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+ ValPos = 3,
+ BadValPos = ValPos + 1,
+ ?match({atomic, ok}, mnesia:add_table_index(Tab, ValPos)),
+ ?match({aborted,Reason} when element(1, Reason) == no_exists,
+ mnesia:del_table_index(Tab, BadValPos)),
+ ?match({atomic, ok}, mnesia:del_table_index(Tab, ValPos)),
+
+ ?match({aborted,Reason1} when element(1, Reason1) == no_exists,
+ mnesia:del_table_index(Tab, ValPos)),
+ NestedFun =
+ fun() ->
+ ?match({aborted, nested_transaction},
+ mnesia:del_table_index(Tab, ValPos)),
+ ok
+ end,
+ ?match({atomic, ok}, mnesia:transaction(NestedFun)),
+ ?verify_mnesia(Nodes, []).
+
+idx_schema_changes(suite) -> [idx_schema_changes_ram,
+ idx_schema_changes_disc,
+ idx_schema_changes_disc_only];
+idx_schema_changes(doc) ->
+ ["Tests that index tables are handled correctly when schema changes.",
+ "For example when a replica is deleted or inserted",
+ "TICKET OTP-2XXX (ELVIRA)"].
+
+idx_schema_changes_ram(suite) -> [];
+idx_schema_changes_ram(Config) when is_list(Config) ->
+ idx_schema_changes(Config, ram_copies).
+idx_schema_changes_disc(suite) -> [];
+idx_schema_changes_disc(Config) when is_list(Config) ->
+ idx_schema_changes(Config, disc_copies).
+idx_schema_changes_disc_only(suite) -> [];
+idx_schema_changes_disc_only(Config) when is_list(Config) ->
+ idx_schema_changes(Config, disc_only_copies).
+
+idx_schema_changes(Config, Storage) ->
+ [N1, N2] = Nodes = ?acquire_nodes(2, Config),
+ Tab = index_schema_changes,
+ Idx = 3,
+ Schema = [{name, Tab}, {index, [Idx]}, {attributes, [k, v]}, {Storage, Nodes}],
+ ?match({atomic, ok}, mnesia:create_table(Schema)),
+
+ {Storage1, Storage2} =
+ case Storage of
+ disc_only_copies ->
+ {ram_copies, disc_copies};
+ disc_copies ->
+ {disc_only_copies, ram_copies};
+ ram_copies ->
+ {disc_copies, disc_only_copies}
+ end,
+
+ Write = fun(N) ->
+ mnesia:write({Tab, N, N+50})
+ end,
+
+ [mnesia:sync_transaction(Write, [N]) || N <- lists:seq(1, 10)],
+ ?match([{Tab, 1, 51}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 51, Idx])),
+ ?match([{Tab, 1, 51}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 51, Idx])),
+
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N1, Storage1)),
+
+ ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [17]])),
+ ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [18]])),
+
+ ?match([{Tab, 17, 67}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 67, Idx])),
+ ?match([{Tab, 18, 68}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 68, Idx])),
+
+ ?match({atomic, ok}, mnesia:del_table_copy(Tab, N1)),
+ ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [11]])),
+ ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [12]])),
+
+ ?match([{Tab, 11, 61}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 61, Idx])),
+ ?match([{Tab, 12, 62}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 62, Idx])),
+
+ ?match({atomic, ok}, mnesia:move_table_copy(Tab, N2, N1)),
+ ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [19]])),
+ ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [20]])),
+
+ ?match([{Tab, 19, 69}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 69, Idx])),
+ ?match([{Tab, 20, 70}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 70, Idx])),
+
+ ?match({atomic, ok}, mnesia:add_table_copy(Tab, N2, Storage)),
+ ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [13]])),
+ ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [14]])),
+
+ ?match([{Tab, 13, 63}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 63, Idx])),
+ ?match([{Tab, 14, 64}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 64, Idx])),
+
+ ?match({atomic, ok}, mnesia:change_table_copy_type(Tab, N2, Storage2)),
+
+ ?match({atomic, ok}, rpc:call(N1, mnesia, sync_transaction, [Write, [15]])),
+ ?match({atomic, ok}, rpc:call(N2, mnesia, sync_transaction, [Write, [16]])),
+
+ ?match([{Tab, 15, 65}], rpc:call(N2, mnesia, dirty_index_read, [Tab, 65, Idx])),
+ ?match([{Tab, 16, 66}], rpc:call(N1, mnesia, dirty_index_read, [Tab, 66, Idx])),
+
+ ?verify_mnesia(Nodes, []).
diff --git a/lib/mnesia/test/mt b/lib/mnesia/test/mt
new file mode 100755
index 0000000000..25243f1149
--- /dev/null
+++ b/lib/mnesia/test/mt
@@ -0,0 +1,60 @@
+#! /bin/sh -f
+# ``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$
+#
+#
+# Author: Hakan Mattsson <[email protected]>
+# Purpose: Simplified execution of the test suite
+#
+# Usage: mt <args to erlang startup script>
+
+#top=".."
+top="$ERL_TOP/lib/mnesia"
+h=`hostname`
+p="-pa $top/examples -pa $top/ebin -pa $top/test -mnesia_test_verbose true"
+log=test_log$$
+latest=test_log_latest
+args=${1+"$@"}
+erlcmd="erl -sname a $p $args -mnesia_test_timeout"
+erlcmd1="erl -sname a1 $p $args"
+erlcmd2="erl -sname a2 $p $args"
+
+xterm -geometry 70x20+0+550 -T a1 -e $erlcmd1 &
+xterm -geometry 70x20+450+550 -T a2 -e $erlcmd2 &
+
+rm "$latest" 2>/dev/null
+ln -s "$log" "$latest"
+touch "$log"
+
+echo "$erlcmd1"
+echo ""
+echo "$erlcmd2"
+echo ""
+echo "$erlcmd"
+echo ""
+echo "Give the following command in order to see the outcome from node a@$h"":"
+echo ""
+echo " less test_log$$"
+
+ostype=`uname -s`
+if [ "$ostype" = "SunOS" ] ; then
+ /usr/openwin/bin/xterm -geometry 145x40+0+0 -T a -l -lf "$log" -e $erlcmd &
+else
+ xterm -geometry 145x40+0+0 -T a -e script -f -c "$erlcmd" "$log" &
+fi
+tail -f "$log" | egrep 'Eval|<>ERROR|NYI'
+
diff --git a/lib/mnesia/test/mt.erl b/lib/mnesia/test/mt.erl
new file mode 100644
index 0000000000..f69c4a11fd
--- /dev/null
+++ b/lib/mnesia/test/mt.erl
@@ -0,0 +1,262 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1997-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%
+%%
+
+%%
+%%% Author: Hakan Mattsson [email protected]
+%%% Purpose: Nice shortcuts intended for testing of Mnesia
+%%%
+%%% See the mnesia_SUITE module about the structure of
+%%% the test suite.
+%%%
+%%% See the mnesia_test_lib module about the test case execution.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-module(mt).
+-author('[email protected]').
+-export([
+ t/0, t/1, t/2, t/3, % Run test cases
+ loop/1, loop/2, loop/3, % loop test cases
+ doc/0, doc/1, % Generate test case doc
+ struct/0, struct/1, % View test suite struct
+ shutdown/0, ping/0, start_nodes/0, % Node admin
+ read_config/0, write_config/1 % Config admin
+ ]).
+
+-include("mnesia_test_lib.hrl").
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Aliases for the (sub) test suites
+alias(all) -> mnesia_SUITE;
+alias(atomicity) -> mnesia_atomicity_test;
+alias(backup) -> mnesia_evil_backup;
+alias(config) -> mnesia_config_test;
+alias(consistency) -> mnesia_consistency_test;
+alias(dirty) -> mnesia_dirty_access_test;
+alias(durability) -> mnesia_durability_test;
+alias(evil) -> mnesia_evil_coverage_test;
+alias(qlc) -> mnesia_qlc_test;
+alias(examples) -> mnesia_examples_test;
+alias(frag) -> mnesia_frag_test;
+alias(heavy) -> {mnesia_SUITE, heavy};
+alias(install) -> mnesia_install_test;
+alias(isolation) -> mnesia_isolation_test;
+alias(light) -> {mnesia_SUITE, light};
+alias(measure) -> mnesia_measure_test;
+alias(medium) -> {mnesia_SUITE, medium};
+alias(nice) -> mnesia_nice_coverage_test;
+alias(recover) -> mnesia_recover_test;
+alias(recovery) -> mnesia_recovery_test;
+alias(registry) -> mnesia_registry_test;
+alias(suite) -> mnesia_SUITE;
+alias(trans) -> mnesia_trans_access_test;
+alias(Other) -> Other.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Resolves the name of test suites and test cases
+%% according to the alias definitions. Single atoms
+%% are assumed to be the name of a test suite.
+resolve(Suite0) when is_atom(Suite0) ->
+ case alias(Suite0) of
+ Suite when is_atom(Suite) ->
+ {Suite, all};
+ {Suite, Case} ->
+ {Suite, Case}
+ end;
+resolve({Suite0, Case}) when is_atom(Suite0), is_atom(Case) ->
+ case alias(Suite0) of
+ Suite when is_atom(Suite) ->
+ {Suite, Case};
+ {Suite, Case2} ->
+ {Suite, Case2}
+ end;
+resolve(List) when is_list(List) ->
+ [resolve(Case) || Case <- List].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Run one or more test cases
+
+%% Run the default test case with default config
+t() ->
+ t(read_test_case()).
+
+%% Resolve the test case name and run the test case
+%% The test case is noted as default test case
+%% and the outcome of the tests are written to
+%% to a file.
+t(silly) ->
+ mnesia_install_test:silly();
+t(diskless) ->
+ %% Run the default test case with default config,
+ %% but diskless
+ t(read_test_case(), diskless);
+t(Case) ->
+ %% Use the default config
+ t(Case, read_config()).
+
+t(Case, Config) when Config == diskless ->
+ %% Run the test case with default config, but diskless
+ Config2 = [{diskless, true} | read_config()],
+ t(Case, Config2);
+t(Mod, Fun) when is_atom(Mod), is_atom(Fun) ->
+ %% Run the test case with default config
+ t({Mod, Fun}, read_config());
+t(RawCase, Config) when is_list(Config) ->
+ %% Resolve the test case name and run the test case
+ Case = resolve(RawCase),
+ write_test_case(Case),
+ Res = mnesia_test_lib:test(Case, Config),
+ append_test_case_info(Case, Res).
+
+t(Mod, Fun, Config) when Config == diskless ->
+ t({Mod, Fun}, diskless).
+
+config_fname() ->
+ "mnesia_test_case_config".
+
+%% Read default config file
+read_config() ->
+ Fname = config_fname(),
+ mnesia_test_lib:log("Consulting file ~s...~n", [Fname]),
+ case file:consult(Fname) of
+ {ok, Config} ->
+ mnesia_test_lib:log("Read config ~w~n", [Config]),
+ Config;
+ _Error ->
+ Config = mnesia_test_lib:default_config(),
+ mnesia_test_lib:log("<>WARNING<> Using default config: ~w~n", [Config]),
+ Config
+ end.
+
+%% Write new default config file
+write_config(Config) when is_list(Config) ->
+ Fname = config_fname(),
+ {ok, Fd} = file:open(Fname, write),
+ write_list(Fd, Config),
+ file:close(Fd).
+
+write_list(Fd, [H | T]) ->
+ ok = io:format(Fd, "~p.~n",[H]),
+ write_list(Fd, T);
+write_list(_, []) ->
+ ok.
+
+test_case_fname() ->
+ "mnesia_test_case_info".
+
+%% Read name of test case
+read_test_case() ->
+ Fname = test_case_fname(),
+ case file:open(Fname, [read]) of
+ {ok, Fd} ->
+ Res = io:read(Fd, []),
+ file:close(Fd),
+ case Res of
+ {ok, TestCase} ->
+ mnesia_test_lib:log("Using test case ~w from file ~s~n",
+ [TestCase, Fname]),
+ TestCase;
+ {error, _} ->
+ default_test_case(Fname)
+ end;
+ {error, _} ->
+ default_test_case(Fname)
+ end.
+
+default_test_case(Fname) ->
+ TestCase = all,
+ mnesia_test_lib:log("<>WARNING<> Cannot read file ~s, "
+ "using default test case: ~w~n",
+ [Fname, TestCase]),
+ TestCase.
+
+write_test_case(TestCase) ->
+ Fname = test_case_fname(),
+ {ok, Fd} = file:open(Fname, write),
+ ok = io:format(Fd, "~p.~n",[TestCase]),
+ file:close(Fd).
+
+append_test_case_info(TestCase, TestCaseInfo) ->
+ Fname = test_case_fname(),
+ {ok, Fd} = file:open(Fname, [read, write]),
+ ok = io:format(Fd, "~p.~n",[TestCase]),
+ ok = io:format(Fd, "~p.~n",[TestCaseInfo]),
+ file:close(Fd),
+ TestCaseInfo.
+
+%% Generate HTML pages from the test case structure
+doc() ->
+ doc(suite).
+
+doc(Case) ->
+ mnesia_test_lib:doc(resolve(Case)).
+
+%% Display out the test case structure
+struct() ->
+ struct(suite).
+
+struct(Case) ->
+ mnesia_test_lib:struct([resolve(Case)]).
+
+%% Shutdown all nodes with erlang:halt/0
+shutdown() ->
+ mnesia_test_lib:shutdown().
+
+%% Ping all nodes in config spec
+ping() ->
+ Config = read_config(),
+ Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE),
+ [{N, net_adm:ping(N)} || N <- Nodes].
+
+%% Slave start all nodes in config spec
+start_nodes() ->
+ Config = read_config(),
+ Nodes = mnesia_test_lib:select_nodes(all, Config, ?FILE, ?LINE),
+ mnesia_test_lib:init_nodes(Nodes, ?FILE, ?LINE),
+ ping().
+
+%% loop one testcase /suite until it fails
+
+loop(Case) ->
+ loop_1(Case,-1,read_config()).
+
+loop(M,F) when is_atom(F) ->
+ loop_1({M,F},-1,read_config());
+loop(Case,N) when is_integer(N) ->
+ loop_1(Case, N,read_config()).
+
+loop(M,F,N) when is_integer(N) ->
+ loop_1({M,F},N,read_config()).
+
+loop_1(Case,N,Config) when N /= 0 ->
+ io:format("Loop test ~p ~n", [abs(N)]),
+ case ok_result(Res = t(Case,Config)) of
+ true ->
+ loop_1(Case,N-1,Config);
+ error ->
+ Res
+ end;
+loop_1(_,_,_) ->
+ ok.
+
+ok_result([{_T,{ok,_,_}}|R]) ->
+ ok_result(R);
+ok_result([{_T,{TC,List}}|R]) when is_tuple(TC), is_list(List) ->
+ ok_result(List) andalso ok_result(R);
+ok_result([]) -> true;
+ok_result(_) -> error.
diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk
index 31cc8f8513..2780b737b6 100644
--- a/lib/mnesia/vsn.mk
+++ b/lib/mnesia/vsn.mk
@@ -1,7 +1,8 @@
-MNESIA_VSN = 4.4.13
+MNESIA_VSN = 4.4.14
-TICKETS = OTP-8402 OTP-8406
+TICKETS = OTP-8519
+#TICKETS_4.4.13 = OTP-8402 OTP-8406
#TICKETS_4.4.12 = OTP-8250
#TICKETS_4.4.11 = OTP-8074
#TICKETS_4.4.10 = OTP-7928 OTP-7968 OTP-8002
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index 48c7c21363..3d7c4fa269 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -31,6 +31,21 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 0.9.8.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The test suite has been updated for R14A.</p>
+ <p>
+ Own Id: OTP-8708</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 0.9.8.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index ff06fb992d..499cce6b97 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 0.9.8.2
+OBSERVER_VSN = 0.9.8.3
diff --git a/lib/odbc/test/Makefile b/lib/odbc/test/Makefile
new file mode 100644
index 0000000000..935ecbf5a7
--- /dev/null
+++ b/lib/odbc/test/Makefile
@@ -0,0 +1,114 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 1999-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%
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+
+INCLUDES= -I. -I$(ERL_TOP)/lib/test_server/include/ -I$(ERL_TOP)/lib/odbc/src
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES= \
+ odbc_start_SUITE \
+ odbc_connect_SUITE \
+ odbc_query_SUITE \
+ odbc_data_type_SUITE \
+ odbc_test_lib \
+ oracle \
+ sqlserver \
+ postgres
+
+EBIN = .
+
+ERL_FILES= $(MODULES:%=%.erl)
+
+HRL_FILES= odbc_test.hrl\
+
+TARGET_FILES= \
+ $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+SPEC_FILES = odbc.spec odbc.dynspec \
+ odbc.spec.win
+
+EMAKEFILE = Emakefile
+MAKE_EMAKE = $(wildcard $(ERL_TOP)/make/make_emakefile)
+
+ifeq ($(MAKE_EMAKE),)
+BUILDTARGET = $(TARGET_FILES)
+RELTEST_FILES = $(SPEC_FILES) $(SOURCE)
+else
+BUILDTARGET = emakebuild
+RELTEST_FILES = $(EMAKEFILE) $(SPEC_FILES) $(SOURCE)
+endif
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/odbc_test
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+ERL_COMPILE_FLAGS += $(INCLUDES) \
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+tests debug opt: $(BUILDTARGET)
+
+targets: $(TARGET_FILES)
+
+.PHONY: emakebuild
+
+emakebuild: $(EMAKEFILE)
+
+$(EMAKEFILE):
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) '*_SUITE_make' | grep -v Warning > $(EMAKEFILE)
+ $(MAKE_EMAKE) $(ERL_COMPILE_FLAGS) -o$(EBIN) $(MODULES) | grep -v Warning >> $(EMAKEFILE)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+
+release_tests_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)
+ $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(HRL_FILES) $(RELSYSDIR)
+
+
+release_docs_spec:
+
+
+
+
+
+
+
diff --git a/lib/odbc/test/README b/lib/odbc/test/README
new file mode 100644
index 0000000000..1f3c659e28
--- /dev/null
+++ b/lib/odbc/test/README
@@ -0,0 +1,86 @@
+-------------------------------------------------------------------------
+ TEST SUITE REQUIREMENTS
+-------------------------------------------------------------------------
+As third party products are involved when using ODBC you will have to
+setup your own test environment to be able to run the ODBC test
+suites.
+
+You need to install a database such as postgres, sql-server, oracle
+etc, and ODBC-drivers for that database.
+
+Then you need to setup a test database, however you do not
+need to create any tables that will be done by the test suites.
+The test suites will also remove all tables that it creates when
+the test is complete.
+
+-------------------------------------------------------------------------
+ERLANG FILES YOU MAY NEED TO CHANGE
+-------------------------------------------------------------------------
+
+A remote database management system has a callback module to handle
+possible differences in data type handling etc, the callback module
+also defines the ODBC connection string. Currently available callback
+modules are postgres.erl, sqlserver.erl and oracle.erl. Depending on
+how you set things up you might want to edit the connection string in
+the callback module or even add your own callback module.
+
+The callback module used in each test case is defined by the ?RDBMS
+macro defined in odbc_test.hrl so you might need to change this to
+suite your purposes.
+
+-------------------------------------------------------------------------
+EXAMPLE
+-------------------------------------------------------------------------
+
+As an example say we have the database odbctestdb, with
+the user odbctest that has the password Sesame. The database
+runs on the host myhost.
+
+UINX/LINUX
+-----------
+
+Set up a database and install the unixODBC drivers.
+Then the unix/linux user that should run the test suits needs an .odbc.ini
+file to map connection data. For example ODBC connection string:
+"DSN=Postgres;UID=odbctest" will need an .odbc.ini entry that looks
+something like this:
+
+--- Start example of .odbc.ini ----
+
+[Postgres]
+Driver=/usr/lib/psqlodbc.so
+Description=Postgres driver
+ServerName=myhost
+Database=odbctestdb
+Port=5432
+LogonID=odbctest
+Password=Sesame
+
+---End example of .odbc.ini ------------
+
+
+WINDOWS MOST FLAVORS
+--------------------
+
+There will be a "ODBC data source administrator" tool under
+Control Panel -> Administrative Tools, use this to set up
+your database. Choose to connect with SQL Server authentication.
+As odbc connection string use: "DSN=odbctestdb;UID=odbctest;PWD=Sesame"
+
+
+> %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%
diff --git a/lib/odbc/test/odbc.dynspec b/lib/odbc/test/odbc.dynspec
new file mode 100644
index 0000000000..bb15edceed
--- /dev/null
+++ b/lib/odbc/test/odbc.dynspec
@@ -0,0 +1,31 @@
+%% -*- erlang -*-
+%% You can test this file using this command.
+%% file:script("odbc.dynspec", [{'Os',"Unix"}]).
+
+Exists =
+fun() ->
+ case code:lib_dir(odbc) of
+ {error,bad_name} ->
+ false;
+ P ->
+ %% Make sure that the odbc directory really
+ %% contains the application (and not only documentation).
+ case filelib:is_file(filename:join(P, "ebin/odbc.beam")) of
+ false -> false;
+ true ->
+ %% We know that we don't have any odbc libraries
+ %% installed on this computer.
+ {ok,Host} = inet:gethostname(),
+ Host =/= "netsim200"
+ end
+ end
+end,
+case Exists() of
+ false ->
+ NoOdbc = "No odbc application",
+ [{skip, {odbc_connect_SUITE, NoOdbc}},
+ {skip, {odbc_data_type_SUITE, NoOdbc}},
+ {skip, {odbc_query_SUITE, NoOdbc}}];
+ true ->
+ []
+end.
diff --git a/lib/odbc/test/odbc.spec b/lib/odbc/test/odbc.spec
new file mode 100644
index 0000000000..acba9f8d98
--- /dev/null
+++ b/lib/odbc/test/odbc.spec
@@ -0,0 +1,9 @@
+{topcase, {dir, "../odbc_test"}}.
+{skip, {odbc_data_type_SUITE, varchar_upper_limit, "Known bug in database"}}.
+{skip, {odbc_data_type_SUITE, text_upper_limit, "Consumes too much resources"}}.
+{skip, {odbc_data_type_SUITE, bit_true , "Not supported by driver"}}.
+{skip, {odbc_data_type_SUITE, bit_false, "Not supported by driver"}}.
+{skip, {odbc_query_SUITE, multiple_select_result_sets,"Not supported by driver"}}.
+{skip, {odbc_query_SUITE, multiple_mix_result_sets, "Not supported by driver"}}.
+{skip, {odbc_query_SUITE, multiple_result_sets_error, "Not supported by driver"}}.
+{skip, {odbc_query_SUITE, param_insert_tiny_int, "Not supported by driver"}}. \ No newline at end of file
diff --git a/lib/odbc/test/odbc.spec.win b/lib/odbc/test/odbc.spec.win
new file mode 100644
index 0000000000..1fd349d2c3
--- /dev/null
+++ b/lib/odbc/test/odbc.spec.win
@@ -0,0 +1,5 @@
+{topcase, {dir, "../odbc_test"}}.
+{skip, {odbc_data_type_SUITE, big_int_lower_limit, "Not supported by sqlserver 7.0"}}.
+{skip, {odbc_data_type_SUITE, big_int_upper_limit, "Not supported by sqlserver7.0"}}.
+{skip, {odbc_data_type_SUITE, text_upper_limit, "Consumes too much resources"}}.
+
diff --git a/lib/odbc/test/odbc_connect_SUITE.erl b/lib/odbc/test/odbc_connect_SUITE.erl
new file mode 100644
index 0000000000..4d37a8f543
--- /dev/null
+++ b/lib/odbc/test/odbc_connect_SUITE.erl
@@ -0,0 +1,816 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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(odbc_connect_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+-define(MAX_SEQ_TIMEOUTS, 10).
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Tests the ability to connect and disconnet to/from the database"];
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip, Other}
+ end.
+
+all() ->
+ [not_exist_db, commit, rollback, not_explicit_commit,
+ no_c_node, port_dies, control_process_dies, client_dies,
+ connect_timeout, timeout, many_timeouts, timeout_reset,
+ disconnect_on_timeout, connection_closed,
+ disable_scrollable_cursors, return_rows_as_lists, api_missuse].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ application:start(odbc),
+ case odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]) of
+ {ok, Ref} ->
+ odbc:disconnect(Ref),
+ [{tableName, odbc_test_lib:unique_table_name()} | Config];
+ _ ->
+ {skip, "ODBC is not properly setup"}
+ end.
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ application:stop(odbc),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config) ->
+ test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]),
+ Dog = test_server:timetrap(?default_timeout),
+ Temp = lists:keydelete(connection_ref, 1, Config),
+ NewConfig = lists:keydelete(watchdog, 1, Temp),
+ [{watchdog, Dog} | NewConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ %% Clean up if needed
+ Table = ?config(tableName, Config),
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Result = odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+ io:format("Drop table: ~p ~p~n", [Table, Result]),
+ odbc:disconnect(Ref),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+commit(doc)->
+ ["Test the use of explicit commit"];
+commit(suite) -> [];
+commit(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+
+ Table = ?config(tableName, Config),
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1,'bar')"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ ok = odbc:commit(Ref, commit),
+ UpdateResult = ?RDBMS:update_result(),
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'bar' WHERE ID = 1"),
+ ok = odbc:commit(Ref, commit, ?TIMEOUT),
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT', {function_clause, _}} =
+ (catch odbc:commit(Ref, commit, -1)),
+
+ ok = odbc:disconnect(Ref),
+
+ ok.
+%%-------------------------------------------------------------------------
+
+rollback(doc)->
+ ["Test the use of explicit rollback"];
+rollback(suite) -> [];
+rollback(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+ ok = odbc:commit(Ref, rollback),
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+ ok = odbc:commit(Ref, rollback, ?TIMEOUT),
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+
+ {'EXIT', {function_clause, _}} =
+ (catch odbc:commit(Ref, rollback, -1)),
+
+ ok = odbc:disconnect(Ref),
+ ok.
+
+%%-------------------------------------------------------------------------
+not_explicit_commit(doc) ->
+ ["Test what happens if you try using commit on a auto_commit connection."];
+not_explicit_commit(suite) -> [];
+not_explicit_commit(_Config) ->
+ {ok, Ref} =
+ odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}]),
+ {error, _} = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+%%-------------------------------------------------------------------------
+not_exist_db(doc) ->
+ ["Tests valid data format but invalid data in the connection parameters."];
+not_exist_db(suite) -> [];
+not_exist_db(_Config) ->
+ {error, _} = odbc:connect("DSN=foo;UID=bar;PWD=foobar", []),
+ %% So that the odbc control server can be stoped "in the correct way"
+ test_server:sleep(100),
+ ok.
+
+%%-------------------------------------------------------------------------
+no_c_node(doc) ->
+ "Test what happens if the port-program can not be found";
+no_c_node(suite) -> [];
+no_c_node(_Config) ->
+ process_flag(trap_exit, true),
+ Dir = filename:nativename(filename:join(code:priv_dir(odbc),
+ "bin")),
+ FileName1 = filename:nativename(os:find_executable("odbcserver",
+ Dir)),
+ FileName2 = filename:nativename(filename:join(Dir, "odbcsrv")),
+ ok = file:rename(FileName1, FileName2),
+ Result =
+ case catch odbc:connect(?RDBMS:connection_string(), []) of
+ {error, port_program_executable_not_found} ->
+ ok;
+ Else ->
+ Else
+ end,
+
+ ok = file:rename(FileName2, FileName1),
+ ok = Result.
+%%------------------------------------------------------------------------
+
+port_dies(doc) ->
+ "Tests what happens if the port program dies";
+port_dies(suite) -> [];
+port_dies(_Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ {status, _} = process_info(Ref, status),
+ process_flag(trap_exit, true),
+ Port = lists:last(erlang:ports()),
+ exit(Port, kill),
+ %% Wait for exit_status from port 5000 ms (will not get a exit
+ %% status in this case), then wait a little longer to make sure
+ %% the port and the controlprocess has had time to terminate.
+ test_server:sleep(7000),
+ undefined = process_info(Ref, status),
+ ok.
+
+%%-------------------------------------------------------------------------
+control_process_dies(doc) ->
+ "Tests what happens if the Erlang control process dies";
+control_process_dies(suite) -> [];
+control_process_dies(_Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ process_flag(trap_exit, true),
+ Port = lists:last(erlang:ports()),
+ {connected, Ref} = erlang:port_info(Port, connected),
+ exit(Ref, kill),
+ test_server:sleep(100),
+ undefined = erlang:port_info(Port, connected),
+ %% Check for c-program still running, how?
+ ok.
+
+%%-------------------------------------------------------------------------
+client_dies(doc) ->
+ ["Test that the odbc process is terminated when the client process "
+ "dies"];
+client_dies(suite) ->
+ [client_dies_normal, client_dies_timeout, client_dies_error].
+
+%%-------------------------------------------------------------------------
+client_dies_normal(doc) ->
+ ["Client dies with reason normal."];
+client_dies_normal(suite) -> [];
+client_dies_normal(Config) when is_list(Config) ->
+ Pid = spawn(?MODULE, client_normal, [self()]),
+
+ MonitorReference =
+ receive
+ {dbRef, Ref} ->
+ MRef = erlang:monitor(process, Ref),
+ Pid ! continue,
+ MRef
+ end,
+
+ receive
+ {'DOWN', MonitorReference, _Type, _Object, _Info} ->
+ ok
+ after 5000 ->
+ test_server:fail(control_process_not_stopped)
+ end.
+
+client_normal(Pid) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Pid ! {dbRef, Ref},
+ receive
+ continue ->
+ ok
+ end,
+ exit(self(), normal).
+
+
+%%-------------------------------------------------------------------------
+client_dies_timeout(doc) ->
+ ["Client dies with reason timeout."];
+client_dies_timeout(suite) -> [];
+client_dies_timeout(Config) when is_list(Config) ->
+ Pid = spawn(?MODULE, client_timeout, [self()]),
+
+ MonitorReference =
+ receive
+ {dbRef, Ref} ->
+ MRef = erlang:monitor(process, Ref),
+ Pid ! continue,
+ MRef
+ end,
+
+ receive
+ {'DOWN', MonitorReference, _Type, _Object, _Info} ->
+ ok
+ after 5000 ->
+ test_server:fail(control_process_not_stopped)
+ end.
+
+client_timeout(Pid) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Pid ! {dbRef, Ref},
+ receive
+ continue ->
+ ok
+ end,
+ exit(self(), timeout).
+
+
+%%-------------------------------------------------------------------------
+client_dies_error(doc) ->
+ ["Client dies with reason error."];
+client_dies_error(suite) -> [];
+client_dies_error(Config) when is_list(Config) ->
+ Pid = spawn(?MODULE, client_error, [self()]),
+
+ MonitorReference =
+ receive
+ {dbRef, Ref} ->
+ MRef = erlang:monitor(process, Ref),
+ Pid ! continue,
+ MRef
+ end,
+
+ receive
+ {'DOWN', MonitorReference, _Type, _Object, _Info} ->
+ ok
+ after 5000 ->
+ test_server:fail(control_process_not_stopped)
+ end.
+
+client_error(Pid) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Pid ! {dbRef, Ref},
+ receive
+ continue ->
+ ok
+ end,
+ exit(self(), error).
+
+
+%%-------------------------------------------------------------------------
+connect_timeout(doc) ->
+ ["Test the timeout for the connect function."];
+connect_timeout(suite) -> [];
+connect_timeout(Config) when is_list(Config) ->
+ {'EXIT',timeout} = (catch odbc:connect(?RDBMS:connection_string(),
+ [{timeout, 0}])),
+ ok.
+%%-------------------------------------------------------------------------
+timeout(doc) ->
+ ["Test that timeouts don't cause unwanted behavior sush as receiving"
+ " an anwser to a previously tiemed out query."];
+timeout(suite) -> [];
+timeout(Config) when is_list(Config) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"),
+
+ Pid = spawn_link(?MODULE, update_table_timeout, [Table, 5000, self()]),
+
+ receive
+ timout_occurred ->
+ ok = odbc:commit(Ref, commit),
+ Pid ! continue
+ end,
+
+ receive
+ altered ->
+ ok
+ end,
+
+ {selected, Fields, [{"foobar"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ ok = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+
+update_table_timeout(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT', timeout} ->
+ Pid ! timout_occurred;
+ {updated, 1} ->
+ test_server:fail(database_locker_failed)
+ end,
+
+ receive
+ continue ->
+ ok
+ end,
+
+ %% Make sure we receive the correct result and not the answer
+ %% to the previous query.
+ {selected, Fields, [{"baz"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ {updated, 1} = odbc:sql_query(Ref, UpdateQuery, TimeOut),
+
+ ok = odbc:commit(Ref, commit),
+
+ Pid ! altered,
+
+ ok = odbc:disconnect(Ref),
+
+ ok.
+%%-------------------------------------------------------------------------
+many_timeouts(doc) ->
+ ["Tests that many consecutive timeouts lead to that the connection "
+ "is shutdown."];
+many_timeouts(suite) -> [];
+many_timeouts(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ _Pid = spawn_link(?MODULE, update_table_many_timeouts,
+ [Table, 5000, self()]),
+
+ receive
+ many_timeouts_occurred ->
+ ok
+ end,
+
+ ok = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+
+update_table_many_timeouts(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ ok = loop_many_timouts(Ref, UpdateQuery, TimeOut),
+
+ Pid ! many_timeouts_occurred,
+
+ ok = odbc:disconnect(Ref),
+ ok.
+
+
+loop_many_timouts(Ref, UpdateQuery, TimeOut) ->
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT',timeout} ->
+ loop_many_timouts(Ref, UpdateQuery, TimeOut);
+ {updated, 1} ->
+ test_server:fail(database_locker_failed);
+ {error, connection_closed} ->
+ ok
+ end.
+%%-------------------------------------------------------------------------
+timeout_reset(doc) ->
+ ["Check that the number of consecutive timouts is reset to 0 when "
+ "a successful call to the database is made."];
+timeout_reset(suite) -> [];
+timeout_reset(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'baz')"),
+
+
+ Pid = spawn_link(?MODULE, update_table_timeout_reset,
+ [Table, 5000, self()]),
+
+ receive
+ many_timeouts_occurred ->
+ ok
+ end,
+
+ ok = odbc:commit(Ref, commit),
+ Pid ! continue,
+
+ receive
+ altered ->
+ ok
+ end,
+
+ {selected, Fields, [{"foobar"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 1"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ ok = odbc:commit(Ref, commit),
+ ok = odbc:disconnect(Ref),
+ ok.
+
+update_table_timeout_reset(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ ok = loop_timout_reset(Ref, UpdateQuery, TimeOut,
+ ?MAX_SEQ_TIMEOUTS-1),
+
+ Pid ! many_timeouts_occurred,
+
+ receive
+ continue ->
+ ok
+ end,
+
+ {selected, Fields, [{"baz"}]} =
+ odbc:sql_query(Ref, "SELECT DATA FROM " ++ Table ++ " WHERE ID = 2"),
+ ["DATA"] = odbc_test_lib:to_upper(Fields),
+
+ {updated,1} = odbc:sql_query(Ref, UpdateQuery, TimeOut),
+
+ ok = odbc:commit(Ref, commit),
+
+ Pid ! altered,
+
+ ok = odbc:disconnect(Ref),
+
+ ok.
+
+loop_timout_reset(_, _, _, 0) ->
+ ok;
+
+loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts) ->
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT',timeout} ->
+ loop_timout_reset(Ref, UpdateQuery,
+ TimeOut, NumTimeouts - 1);
+ {updated, 1} ->
+ test_server:fail(database_locker_failed);
+ {error, connection_closed} ->
+ test_server:fail(connection_closed_premature)
+ end.
+
+%%-------------------------------------------------------------------------
+
+disconnect_on_timeout(doc) ->
+ ["Check that disconnect after a time out works properly"];
+disconnect_on_timeout(suite) -> [];
+disconnect_on_timeout(Config) when is_list(Config) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ ok = odbc:commit(Ref, commit),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+
+ _Pid = spawn_link(?MODULE, update_table_disconnect_on_timeout,
+ [Table, 5000, self()]),
+ receive
+ ok ->
+ ok = odbc:commit(Ref, commit);
+ nok ->
+ test_server:fail(database_locker_failed)
+ end.
+
+update_table_disconnect_on_timeout(Table, TimeOut, Pid) ->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{auto_commit, off}]),
+ UpdateQuery = "UPDATE " ++ Table ++ " SET DATA = 'foobar' WHERE ID = 1",
+
+ case catch odbc:sql_query(Ref, UpdateQuery, TimeOut) of
+ {'EXIT', timeout} ->
+ ok = odbc:disconnect(Ref),
+ Pid ! ok;
+ {updated, 1} ->
+ Pid ! nok
+ end.
+
+%%-------------------------------------------------------------------------
+connection_closed(doc) ->
+ ["Checks that you get an appropriate error message if you try to"
+ " use a connection that has been closed"];
+connection_closed(suite) -> [];
+connection_closed(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+
+ Table = ?config(tableName, Config),
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA char(10), PRIMARY KEY(ID))"),
+
+ ok = odbc:disconnect(Ref),
+
+ {error, connection_closed} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+ {error, connection_closed} =
+ odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+ {error, connection_closed} = odbc:first(Ref),
+ {error, connection_closed} = odbc:last(Ref),
+ {error, connection_closed} = odbc:next(Ref),
+ {error, connection_closed} = odbc:prev(Ref),
+ {error, connection_closed} = odbc:select(Ref, next, 3),
+ {error, connection_closed} = odbc:commit(Ref, commit),
+ ok.
+
+%%-------------------------------------------------------------------------
+disable_scrollable_cursors(doc) ->
+ ["Test disabling of scrollable cursors."];
+disable_scrollable_cursors(suite) -> [];
+disable_scrollable_cursors(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{scrollable_cursors, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT ID FROM " ++ Table),
+
+ NextResult = ?RDBMS:selected_ID(1, next),
+
+ test_server:format("Expected: ~p~n", [NextResult]),
+
+ Result = odbc:next(Ref),
+ test_server:format("Got: ~p~n", [Result]),
+ NextResult = Result,
+
+ {error, scrollable_cursors_disabled} = odbc:first(Ref),
+ {error, scrollable_cursors_disabled} = odbc:last(Ref),
+ {error, scrollable_cursors_disabled} = odbc:prev(Ref),
+ {error, scrollable_cursors_disabled} =
+ odbc:select(Ref, {relative, 2}, 5),
+ {error, scrollable_cursors_disabled} =
+ odbc:select(Ref, {absolute, 2}, 5),
+
+ {selected, _ColNames,[]} = odbc:select(Ref, next, 1),
+ ok.
+
+%%-------------------------------------------------------------------------
+return_rows_as_lists(doc)->
+ ["Test the option that a row may be returned as a list instead "
+ "of a tuple. Too be somewhat backward compatible."];
+return_rows_as_lists(suite) -> [];
+return_rows_as_lists(Config) when is_list(Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{tuple_row, off}]),
+
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), PRIMARY KEY(ID))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(2,'foo')"),
+
+ ListRows = ?RDBMS:selected_list_rows(),
+ ListRows =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ First = ?RDBMS:first_list_rows(),
+ Last = ?RDBMS:last_list_rows(),
+ Prev = ?RDBMS:prev_list_rows(),
+ Next = ?RDBMS:next_list_rows(),
+
+ Last = odbc:last(Ref),
+ Prev = odbc:prev(Ref),
+ First = odbc:first(Ref),
+ Next = odbc:next(Ref),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+api_missuse(doc)->
+ ["Test that behaviour of the control process if the api is abused"];
+api_missuse(suite) -> [];
+api_missuse(Config) when is_list(Config)->
+
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ %% Serious programming fault, connetion will be shut down
+ gen_server:call(Ref, {self(), foobar, 10}, infinity),
+ test_server:sleep(10),
+ undefined = process_info(Ref, status),
+
+ {ok, Ref2} = odbc:connect(?RDBMS:connection_string(), []),
+ %% Serious programming fault, connetion will be shut down
+ gen_server:cast(Ref2, {self(), foobar, 10}),
+ test_server:sleep(10),
+ undefined = process_info(Ref2, status),
+
+ {ok, Ref3} = odbc:connect(?RDBMS:connection_string(), []),
+ %% Could be an innocent misstake the connection lives.
+ Ref3 ! foobar,
+ test_server:sleep(10),
+ {status, _} = process_info(Ref3, status),
+ ok.
+
diff --git a/lib/odbc/test/odbc_data_type_SUITE.erl b/lib/odbc/test/odbc_data_type_SUITE.erl
new file mode 100644
index 0000000000..7d4a0ca15f
--- /dev/null
+++ b/lib/odbc/test/odbc_data_type_SUITE.erl
@@ -0,0 +1,1498 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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(odbc_data_type_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Tests data types"];
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip,Other}
+ end.
+
+all() ->
+ [char, int, floats, dec_and_num, timestamp].
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ application:start(odbc),
+ [{tableName, odbc_test_lib:unique_table_name()} | Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ application:stop(odbc),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(Case, Config) ->
+ case atom_to_list(Case) of
+ "binary" ++ _ ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{binary_strings, on}]);
+ "unicode" ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(),
+ [{binary_strings, on}]);
+ _ ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [])
+ end,
+ Dog = test_server:timetrap(?default_timeout),
+ Temp = lists:keydelete(connection_ref, 1, Config),
+ NewConfig = lists:keydelete(watchdog, 1, Temp),
+ [{watchdog, Dog}, {connection_ref, Ref} | NewConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ Ref = ?config(connection_ref, Config),
+ ok = odbc:disconnect(Ref),
+ %% Clean up if needed
+ Table = ?config(tableName, Config),
+ {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), []),
+ odbc:sql_query(NewRef, "DROP TABLE " ++ Table),
+ odbc:disconnect(NewRef),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+char(doc) ->
+ ["Tests char data types"];
+
+char(suite) ->
+ [char_fixed_lower_limit, char_fixed_upper_limit,
+ char_fixed_padding, varchar_lower_limit, varchar_upper_limit,
+ varchar_no_padding, text_lower_limit, text_upper_limit, unicode
+ ].
+
+char_fixed_lower_limit(doc) ->
+ ["Tests fixed length char data type lower boundaries."];
+char_fixed_lower_limit(suite) ->
+ [];
+char_fixed_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_min() - 1))),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:fixed_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields,[{"a"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_min()
+ + 1))
+ ++ "')"),
+ ok.
+%%-------------------------------------------------------------------------
+
+char_fixed_upper_limit(doc) ->
+ ["Tests fixed length char data type upper boundaries."];
+char_fixed_upper_limit(suite) ->
+ [];
+char_fixed_upper_limit(Config) when is_list(Config) ->
+
+ case ?RDBMS of
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_max())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) == ?RDBMS:fixed_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_max()
+ + 1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_max() + 1))),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+char_fixed_padding(doc) ->
+ ["Tests that data that is shorter than the given size is padded "
+ "with blanks."];
+char_fixed_padding(suite) ->
+ [];
+char_fixed_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) == ?RDBMS:fixed_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%-------------------------------------------------------------------------
+
+varchar_lower_limit(doc) ->
+ ["Tests variable length char data type lower boundaries."];
+varchar_lower_limit(suite) ->
+ [];
+varchar_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min() - 1)),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{"a"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_min()+1))
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+varchar_upper_limit(doc) ->
+ ["Tests variable length char data type upper boundaries."];
+varchar_upper_limit(suite) ->
+ [];
+varchar_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ case ?RDBMS of
+ oracle ->
+ {skip, "Known bug in database"};
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:var_char_max())
+ ++ "')"),
+
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) == ?RDBMS:var_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_max()+1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ (?RDBMS:var_char_max() + 1))),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+
+varchar_no_padding(doc) ->
+ ["Tests that data that is shorter than the given max size is not padded "
+ "with blanks."];
+varchar_no_padding(suite) ->
+ [];
+varchar_no_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should NOT be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharStr}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = length(CharStr) /= ?RDBMS:var_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+text_lower_limit(doc) ->
+ ["Tests 'long' char data type lower boundaries."];
+text_lower_limit(suite) ->
+ [];
+text_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_text_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:text_min())
+ ++ "')"),
+
+ {selected, Fields, [{"a"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+text_upper_limit(doc) ->
+ [];
+text_upper_limit(suite) ->
+ [];
+text_upper_limit(Config) when is_list(Config) ->
+
+ {skip,"Consumes too much resources" }.
+%% Ref = ?config(connection_ref, Config),
+%% Table = ?config(tableName, Config),
+
+%% {updated, _} = % Value == 0 || -1 driver dependent!
+%% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+%% ?RDBMS:create_text_table()),
+%% {updated, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, ?RDBMS:text_max())
+%% ++ "')"),
+
+%% {selected, Fields, [{CharStr}]} =
+%% odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+%% length(CharStr) == ?RDBMS:text_max(),
+%% ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+%% {error, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, (?RDBMS:text_max()+1))
+%% ++ "')"),
+%% ok.
+
+%%-------------------------------------------------------------------------
+binary_char(doc) ->
+ ["Tests char data types returned as erlang binaries"];
+
+binary_char(suite) ->
+ [binary_char_fixed_lower_limit, binary_char_fixed_upper_limit,
+ binary_char_fixed_padding, binary_varchar_lower_limit, binary_varchar_upper_limit,
+ binary_varchar_no_padding, binary_text_lower_limit, binary_text_upper_limit, unicode
+ ].
+
+binary_char_fixed_lower_limit(doc) ->
+ ["Tests fixed length char data type lower boundaries."];
+binary_char_fixed_lower_limit(suite) ->
+ [];
+binary_char_fixed_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_min() - 1))),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:fixed_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields,[{<<"a">>}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_min()
+ + 1))
+ ++ "')"),
+ ok.
+%%-------------------------------------------------------------------------
+
+binary_char_fixed_upper_limit(doc) ->
+ ["Tests fixed length char data type upper boundaries."];
+binary_char_fixed_upper_limit(suite) ->
+ [];
+binary_char_fixed_upper_limit(Config) when is_list(Config) ->
+
+ case ?RDBMS of
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_max())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) == ?RDBMS:fixed_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:fixed_char_max()
+ + 1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ (?RDBMS:fixed_char_max() + 1))),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+binary_char_fixed_padding(doc) ->
+ ["Tests that data that is shorter than the given size is padded "
+ "with blanks."];
+binary_char_fixed_padding(suite) ->
+ [];
+binary_char_fixed_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_fixed_char_table(
+ ?RDBMS:fixed_char_max())),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:fixed_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) == ?RDBMS:fixed_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%-------------------------------------------------------------------------
+
+binary_varchar_lower_limit(doc) ->
+ ["Tests variable length char data type lower boundaries."];
+binary_varchar_lower_limit(suite) ->
+ [];
+binary_varchar_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Below limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min() - 1)),
+ %% Lower limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_min())),
+
+ %% Right length data
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+ %% Select data
+ {selected, Fields, [{<<"a">>}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_min()+1))
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+binary_varchar_upper_limit(doc) ->
+ ["Tests variable length char data type upper boundaries."];
+binary_varchar_upper_limit(suite) ->
+ [];
+binary_varchar_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ case ?RDBMS of
+ oracle ->
+ {skip, "Known bug in database"};
+ postgres ->
+ {skip, "Limit unknown"};
+ _ ->
+ %% Upper limit
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ ?RDBMS:var_char_max())
+ ++ "')"),
+
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) == ?RDBMS:var_char_max(),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Too long data
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a,
+ (?RDBMS:var_char_max()+1))
+ ++ "')"),
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ %% Above limit
+ {error, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ (?RDBMS:var_char_max() + 1))),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+
+binary_varchar_no_padding(doc) ->
+ ["Tests that data that is shorter than the given max size is not padded "
+ "with blanks."];
+binary_varchar_no_padding(suite) ->
+ [];
+binary_varchar_no_padding(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ %% Data should NOT be padded with blanks
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_var_char_table(
+ ?RDBMS:var_char_max())),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:var_char_min())
+ ++ "')"),
+
+ {selected, Fields, [{CharBin}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ true = size(CharBin) /= ?RDBMS:var_char_max(),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+binary_text_lower_limit(doc) ->
+ ["Tests 'long' char data type lower boundaries."];
+binary_text_lower_limit(suite) ->
+ [];
+binary_text_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_text_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ string:chars($a, ?RDBMS:text_min())
+ ++ "')"),
+
+ {selected, Fields, [{<<"a">>}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+binary_text_upper_limit(doc) ->
+ [];
+binary_text_upper_limit(suite) ->
+ [];
+binary_text_upper_limit(Config) when is_list(Config) ->
+
+ {skip,"Consumes too much resources" }.
+%% Ref = ?config(connection_ref, Config),
+%% Table = ?config(tableName, Config),
+
+%% {updated, _} = % Value == 0 || -1 driver dependent!
+%% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+%% ?RDBMS:create_text_table()),
+%% {updated, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, ?RDBMS:text_max())
+%% ++ "')"),
+
+%% {selected, Fields, [{CharBin}]} =
+%% odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+%% size(CharBin) == ?RDBMS:text_max(),
+%% ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+%% {error, _} =
+%% odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+%% "'" ++ string:chars($a, (?RDBMS:text_max()+1))
+%% ++ "')"),
+%% ok.
+
+
+%%-------------------------------------------------------------------------
+
+int(doc) ->
+ ["Tests integer data types"];
+
+int(suite) ->
+ [tiny_int_lower_limit, tiny_int_upper_limit, small_int_lower_limit,
+ small_int_upper_limit, int_lower_limit, int_upper_limit,
+ big_int_lower_limit, big_int_upper_limit, bit_false, bit_true].
+
+%%-------------------------------------------------------------------------
+
+tiny_int_lower_limit(doc) ->
+ ["Tests integer of type tinyint."];
+tiny_int_lower_limit(suite) ->
+ [];
+tiny_int_lower_limit(Config) when is_list(Config) ->
+ case ?RDBMS of
+ postgres ->
+ {skip, "Type tiniyint not supported"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_tiny_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:tiny_int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_min()
+ - 1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+tiny_int_upper_limit(doc) ->
+ ["Tests integer of type tinyint."];
+tiny_int_upper_limit(suite) ->
+ [];
+tiny_int_upper_limit(Config) when is_list(Config) ->
+ case ?RDBMS of
+ postgres ->
+ {skip, "Type tiniyint not supported"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_tiny_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:tiny_int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:tiny_int_max()
+ + 1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+small_int_lower_limit(doc) ->
+ ["Tests integer of type smallint."];
+small_int_lower_limit(suite) ->
+ [];
+small_int_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_small_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:small_int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_min()
+ - 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+small_int_upper_limit(doc) ->
+ ["Tests integer of type smallint."];
+small_int_upper_limit(suite) ->
+ [];
+small_int_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_small_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:small_int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref,"INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:small_int_max()
+ + 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+int_lower_limit(doc) ->
+ ["Tests integer of type int."];
+int_lower_limit(suite) ->
+ [];
+int_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_min() - 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+int_upper_limit(doc) ->
+ ["Tests integer of type int."];
+int_upper_limit(suite) ->
+ [];
+int_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:int_max() + 1)
+ ++ "')"),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+big_int_lower_limit(doc) ->
+ ["Tests integer of type bigint"];
+big_int_lower_limit(suite) ->
+ [];
+big_int_lower_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_big_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_min())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:big_int_min_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_min()
+ - 1)
+ ++ "')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+
+big_int_upper_limit(doc) ->
+ ["Tests integer of type bigint."];
+big_int_upper_limit(suite) ->
+ [];
+big_int_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_big_int_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_max())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:big_int_max_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:big_int_max()
+ + 1)
+ ++ "')"),
+ ok.
+%%-------------------------------------------------------------------------
+
+bit_false(doc) ->
+ [""];
+bit_false(suite) ->
+ [];
+bit_false(Config) when is_list(Config) ->
+ case ?RDBMS of
+ oracle ->
+ {skip, "Not supported by driver"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_bit_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:bit_false())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:bit_false_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(-1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+bit_true(doc) ->
+ [""];
+bit_true(suite) ->
+ [];
+bit_true(Config) when is_list(Config) ->
+ case ?RDBMS of
+ oracle ->
+ {skip, "Not supported by driver"};
+ _ ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_bit_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(?RDBMS:bit_true())
+ ++ "')"),
+
+ SelectResult = ?RDBMS:bit_true_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ integer_to_list(-1)
+ ++ "')"),
+ ok
+ end.
+
+%%-------------------------------------------------------------------------
+
+floats(doc) ->
+ ["Test the datatype float."];
+floats(suite) ->
+ [float_lower_limit, float_upper_limit, float_zero, real_zero].
+
+%%-------------------------------------------------------------------------
+float_lower_limit(doc) ->
+ [""];
+float_lower_limit(suite) ->
+ [];
+float_lower_limit(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ float_to_list(
+ ?RDBMS:float_min())
+ ++ "')"),
+ {selected,[_ColName],[{MinFloat}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ true = ?RDBMS:float_min() == MinFloat,
+
+ case ?RDBMS of
+ oracle ->
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "INSERT INTO " ++ Table ++" VALUES(" ++
+ ?RDBMS:float_underflow() ++ ")"),
+
+ SelectResult = ?RDBMS:float_zero_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table);
+ _ ->
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ ?RDBMS:float_underflow() ++ ")")
+ end,
+ ok.
+
+
+%%-------------------------------------------------------------------------
+float_upper_limit(doc) ->
+ [""];
+float_upper_limit(suite) ->
+ [];
+float_upper_limit(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ "'" ++ float_to_list(
+ ?RDBMS:float_max())
+ ++ "')"),
+
+
+ {selected,[_ColName],[{MaxFloat}]}
+ = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+
+ true = ?RDBMS:float_max() == MaxFloat,
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(" ++
+ ?RDBMS:float_overflow() ++ ")"),
+ ok.
+
+%%-------------------------------------------------------------------------
+float_zero(doc) ->
+ ["Test the float value zero."];
+float_zero(suite) ->
+ [];
+float_zero(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_float_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES('0')"),
+
+ SelectResult = ?RDBMS:float_zero_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ok.
+%%-------------------------------------------------------------------------
+real_zero(doc) ->
+ ["Test the real value zero."];
+real_zero(suite) ->
+ [];
+real_zero(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ case ?RDBMS of
+ oracle ->
+ {skip, "Not supported in Oracle"};
+ _ ->
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_real_table()),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES('0')"),
+
+ SelectResult = ?RDBMS:real_zero_selected(),
+ SelectResult =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ok
+ end.
+%%-------------------------------------------------------------------------
+dec_and_num(doc) ->
+ ["Tests decimal and numeric datatypes."];
+dec_and_num(suite) ->
+ [dec_long, dec_double, dec_bignum, num_long, num_double, num_bignum].
+%%------------------------------------------------------------------------
+dec_long(doc) ->
+ [""];
+dec_long(suit) ->
+ [];
+dec_long(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (9,0))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{2}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%------------------------------------------------------------------------
+dec_double(doc) ->
+ [""];
+dec_double(suit) ->
+ [];
+dec_double(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (10,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{2.00000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{2.00000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15, 1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields2, [{1.60000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields2),
+ ok.
+
+%%------------------------------------------------------------------------
+dec_bignum(doc) ->
+ [""];
+dec_bignum(suit) ->
+ [];
+dec_bignum(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{"2"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{"1.6"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+ ok.
+%%------------------------------------------------------------------------
+num_long(doc) ->
+ [""];
+num_long(suit) ->
+ [];
+num_long(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (9,0))"),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.5)"),
+
+ {selected, Fields, [{2}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+ ok.
+%%------------------------------------------------------------------------
+num_double(doc) ->
+ [""];
+num_double(suit) ->
+ [];
+num_double(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (10,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{2.0000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{2.0000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (15,1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields2, [{1.6000}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields2),
+ ok.
+%%------------------------------------------------------------------------
+num_bignum(doc) ->
+ [""];
+num_bignum(suit) ->
+ [];
+num_bignum(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,0))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields, [{"2"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ %% Clean up
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ "(FIELD DECIMAL (16,1))"),
+ {updated, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++" VALUES(1.6)"),
+
+ {selected, Fields1, [{"1.6"}]} =
+ odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table),
+ ["FIELD"] = odbc_test_lib:to_upper(Fields1),
+ ok.
+
+%%------------------------------------------------------------------------
+unicode(doc) ->
+ ["Test unicode support"];
+unicode(suit) ->
+ [];
+unicode(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_unicode_table()),
+
+ Latin1Data = ["���������",
+ "testasdf",
+ "Row 3",
+ "Row 4",
+ "Row 5",
+ "Row 6",
+ "Row 7",
+ "Row 8",
+ "Row 9",
+ "Row 10",
+ "Row 11",
+ "Row 12"],
+
+ case ?RDBMS of
+ sqlserver ->
+ w_char_support_win(Ref, Table, Latin1Data);
+ postgres ->
+ direct_utf8(Ref, Table, Latin1Data);
+ oracle ->
+ {skip, "not currently supported"}
+ end.
+
+w_char_support_win(Ref, Table, Latin1Data) ->
+ UnicodeIn = lists:map(fun(S) ->
+ unicode:characters_to_binary(S,latin1,{utf16,little})
+ end,
+ Latin1Data),
+
+ test_server:format("UnicodeIn (utf 16): ~p ~n",[UnicodeIn]),
+
+ {updated, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++ "(FIELD) values(?)",
+ [{{sql_wvarchar,50},UnicodeIn}]),
+
+ {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table),
+
+ test_server:format("UnicodeOut: ~p~n", [UnicodeOut]),
+
+ Result = lists:map(fun({Unicode}) ->
+ unicode:characters_to_list(Unicode,{utf16,little})
+ end,
+ UnicodeOut),
+ Latin1Data = Result.
+
+
+direct_utf8(Ref, Table, Latin1Data) ->
+ UnicodeIn = lists:map(fun(String) ->
+ unicode:characters_to_binary(String,latin1,utf8)
+ end,
+ Latin1Data),
+
+ test_server:format("UnicodeIn: ~p ~n",[UnicodeIn]),
+ {updated, _} = odbc:param_query(Ref,"INSERT INTO " ++ Table ++ "(FIELD) values(?)",
+ [{{sql_varchar,50}, UnicodeIn}]),
+
+ {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table),
+
+ test_server:format("UnicodeOut: ~p~n", [UnicodeOut]),
+
+ Result = lists:map(fun({Char}) ->
+ unicode:characters_to_list(Char,utf8)
+ end, UnicodeOut),
+
+ test_server:format("Result: ~p ~n", [Result]),
+
+ Latin1Data = Result.
+
+%%------------------------------------------------------------------------
+timestamp(doc) ->
+ [""];
+timestamp(suit) ->
+ [];
+timestamp(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_timestamp_table()),
+
+ Data = [calendar:local_time(),
+ {{2009,6,17},{20,54,59}},
+ {{2009,6,18},{20,54,59}},
+ {{2009,6,19},{20,54,59}},
+ {{2009,6,20},{20,54,59}},
+ {{2009,6,21},{20,54,59}}],
+
+ {updated, _} = odbc:param_query(Ref,"INSERT INTO " ++ Table ++ "(FIELD) values(?)",
+ [{sql_timestamp,Data}]),
+
+ %%% Crate list or database table rows
+ TimeStamps = lists:map(fun(Value) -> {Value} end, Data),
+
+ {selected,_, TimeStamps} = odbc:sql_query(Ref, "SELECT * FROM " ++ Table).
diff --git a/lib/odbc/test/odbc_query_SUITE.erl b/lib/odbc/test/odbc_query_SUITE.erl
new file mode 100644
index 0000000000..12b39be3b7
--- /dev/null
+++ b/lib/odbc/test/odbc_query_SUITE.erl
@@ -0,0 +1,1453 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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(odbc_query_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+%%--------------------------------------------------------------------
+%% all(Arg) -> [Doc] | [Case] | {skip, Comment}
+%% Arg - doc | suite
+%% Doc - string()
+%% Case - atom()
+%% Name of a test case function.
+%% Comment - string()
+%% Description: Returns documentation/test cases in this test suite
+%% or a skip tuple if the platform is not supported.
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Tests SQL queries"];
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip, Other}
+ end.
+
+all() ->
+ [sql_query, first, last, next, prev, select_count,select_next,
+ select_relative, select_absolute, create_table_twice,
+ delete_table_twice, duplicate_key, not_connection_owner,
+ no_result_set, query_error, multiple_select_result_sets,
+ multiple_mix_result_sets, multiple_result_sets_error,
+ parameterized_queries, describe_table,
+ delete_nonexisting_row].
+
+
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initiation before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) when is_list(Config) ->
+ application:start(odbc),
+ [{tableName, odbc_test_lib:unique_table_name()}| Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ application:stop(odbc),
+ ok.
+
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(Case, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initiation before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_testcase(_Case, Config) ->
+ {ok, Ref} = odbc:connect(?RDBMS:connection_string(), []),
+ Dog = test_server:timetrap(?default_timeout),
+ Temp = lists:keydelete(connection_ref, 1, Config),
+ NewConfig = lists:keydelete(watchdog, 1, Temp),
+ [{watchdog, Dog}, {connection_ref, Ref} | NewConfig].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(Case, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_Case, Config) ->
+ Ref = ?config(connection_ref, Config),
+ ok = odbc:disconnect(Ref),
+ %% Clean up if needed
+ Table = ?config(tableName, Config),
+ {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), []),
+ odbc:sql_query(NewRef, "DROP TABLE " ++ Table),
+ odbc:disconnect(NewRef),
+ Dog = ?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Test cases starts here.
+%%-------------------------------------------------------------------------
+sql_query(doc)->
+ ["Test the common cases"];
+sql_query(suite) -> [];
+sql_query(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+
+ {updated, Count} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ true = odbc_test_lib:check_row_count(1, Count),
+
+ InsertResult = ?RDBMS:insert_result(),
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, NewCount} =
+ odbc:sql_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foo' WHERE ID = 1"),
+
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ UpdateResult = ?RDBMS:update_result(),
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {updated, NewCount1} = odbc:sql_query(Ref, "DELETE FROM " ++ Table ++
+ " WHERE ID = 1"),
+
+ true = odbc_test_lib:check_row_count(1, NewCount1),
+
+ {selected, Fields, []} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["ID","DATA"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+select_count(doc) ->
+ ["Tests select_count/[2,3]'s timeout, "
+ " select_count's functionality will be better tested by other tests "
+ " such as first."];
+select_count(sute) -> [];
+select_count(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {ok, _} =
+ odbc:select_count(Ref, "SELECT * FROM " ++ Table, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} =
+ (catch odbc:select_count(Ref, "SELECT * FROM ", -1)),
+ ok.
+%%-------------------------------------------------------------------------
+first(doc) ->
+ ["Tests first/[1,2]"];
+first(suite) -> [];
+first(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+
+ FirstResult = ?RDBMS:selected_ID(1, first),
+ FirstResult = odbc:first(Ref),
+ FirstResult = odbc:first(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:first(Ref, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+last(doc) ->
+ ["Tests last/[1,2]"];
+last(suite) -> [];
+last(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ LastResult = ?RDBMS:selected_ID(2, last),
+ LastResult = odbc:last(Ref),
+
+ LastResult = odbc:last(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:last(Ref, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+next(doc) ->
+ ["Tests next/[1,2]"];
+next(suite) -> [];
+next(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ NextResult = ?RDBMS:selected_ID(1, next),
+ NextResult = odbc:next(Ref),
+ NextResult2 = ?RDBMS:selected_ID(2, next),
+ NextResult2 = odbc:next(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:next(Ref, -1)),
+ ok.
+%%-------------------------------------------------------------------------
+prev(doc) ->
+ ["Tests prev/[1,2]"];
+prev(suite) -> [];
+prev(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, Count} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ true = odbc_test_lib:check_row_count(1, Count),
+ {updated, NewCount} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ odbc:last(Ref), % Position cursor last so there will be a prev
+ PrevResult = ?RDBMS:selected_ID(1, prev),
+ PrevResult = odbc:prev(Ref),
+
+ odbc:last(Ref), % Position cursor last so there will be a prev
+ PrevResult = odbc:prev(Ref, ?TIMEOUT),
+ {'EXIT', {function_clause, _}} = (catch odbc:prev(Ref, -1)),
+ ok.
+%%-------------------------------------------------------------------------
+select_next(doc) ->
+ ["Tests select/[4,5] with CursorRelation = next "];
+select_next(suit) -> [];
+select_next(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(3)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(4)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(5)"),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ SelectResult1 = ?RDBMS:selected_next_N(1),
+ SelectResult1 = odbc:select(Ref, next, 3),
+
+ %% Test that selecting stops at the end of the result set
+ SelectResult2 = ?RDBMS:selected_next_N(2),
+ SelectResult2 = odbc:select(Ref, next, 3, ?TIMEOUT),
+ {'EXIT',{function_clause, _}} =
+ (catch odbc:select(Ref, next, 2, -1)),
+
+ %% If you try fetching data beyond the the end of result set,
+ %% you get an empty list.
+ {selected, Fields, []} = odbc:select(Ref, next, 1),
+
+ ["ID"] = odbc_test_lib:to_upper(Fields),
+ ok.
+
+%%-------------------------------------------------------------------------
+select_relative(doc) ->
+ ["Tests select/[4,5] with CursorRelation = relative "];
+select_relative(suit) -> [];
+select_relative(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(3)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(4)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(5)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(6)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(7)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(8)"),
+
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ SelectResult1 = ?RDBMS:selected_relative_N(1),
+ SelectResult1 = odbc:select(Ref, {relative, 2}, 3),
+
+ %% Test that selecting stops at the end of the result set
+ SelectResult2 = ?RDBMS:selected_relative_N(2),
+ SelectResult2 = odbc:select(Ref, {relative, 3}, 3, ?TIMEOUT),
+ {'EXIT',{function_clause, _}} =
+ (catch odbc:select(Ref, {relative, 3} , 2, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+select_absolute(doc) ->
+ ["Tests select/[4,5] with CursorRelation = absolute "];
+select_absolute(suit) -> [];
+select_absolute(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer)"),
+
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(3)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(4)"),
+ {updated, 1} = odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(5)"),
+ {ok, _} = odbc:select_count(Ref, "SELECT * FROM " ++ Table),
+
+ SelectResult1 = ?RDBMS:selected_absolute_N(1),
+ SelectResult1 = odbc:select(Ref, {absolute, 1}, 3),
+
+ %% Test that selecting stops at the end of the result set
+ SelectResult2 = ?RDBMS:selected_absolute_N(2),
+ SelectResult2 = odbc:select(Ref, {absolute, 1}, 6, ?TIMEOUT),
+ {'EXIT',{function_clause, _}} =
+ (catch odbc:select(Ref, {absolute, 1}, 2, -1)),
+ ok.
+
+%%-------------------------------------------------------------------------
+create_table_twice(doc) ->
+ ["Test what happens if you try to create the same table twice."];
+create_table_twice(suite) -> [];
+create_table_twice(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ {error, Error} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ is_driver_error(Error),
+ ok.
+
+%%-------------------------------------------------------------------------
+delete_table_twice(doc) ->
+ ["Test what happens if you try to delete the same table twice."];
+delete_table_twice(suite) -> [];
+delete_table_twice(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10))"),
+ {updated, _} = odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+ {error, Error} = odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+ is_driver_error(Error),
+ ok.
+
+%-------------------------------------------------------------------------
+duplicate_key(doc) ->
+ ["Test what happens if you try to use the same key twice"];
+duplicate_key(suit) -> [];
+duplicate_key(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA char(10), PRIMARY KEY(ID))"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {error, Error} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'foo')"),
+ is_driver_error(Error),
+ ok.
+
+%%-------------------------------------------------------------------------
+not_connection_owner(doc) ->
+ ["Test what happens if a process that did not start the connection"
+ " tries to acess it."];
+not_connection_owner(suite) -> [];
+not_connection_owner(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ spawn_link(?MODULE, not_owner, [self(), Ref, Table]),
+
+ receive
+ continue ->
+ ok
+ end.
+
+not_owner(Pid, Ref, Table) ->
+ {error, process_not_owner_of_odbc_connection} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ " (ID integer)"),
+
+ {error, process_not_owner_of_odbc_connection} =
+ odbc:disconnect(Ref),
+
+ Pid ! continue.
+
+%%-------------------------------------------------------------------------
+no_result_set(doc) ->
+ ["Tests what happens if you try to use a function that needs an "
+ "associated result set when there is none."];
+no_result_set(suite) -> [];
+no_result_set(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+
+ {error, result_set_does_not_exist} = odbc:first(Ref),
+ {error, result_set_does_not_exist} = odbc:last(Ref),
+ {error, result_set_does_not_exist} = odbc:next(Ref),
+ {error, result_set_does_not_exist} = odbc:prev(Ref),
+ {error, result_set_does_not_exist} = odbc:select(Ref, next, 1),
+ {error, result_set_does_not_exist} =
+ odbc:select(Ref, {absolute, 2}, 1),
+ {error, result_set_does_not_exist} =
+ odbc:select(Ref, {relative, 2}, 1),
+ ok.
+%%-------------------------------------------------------------------------
+query_error(doc) ->
+ ["Test what happens if there is an error in the query."];
+query_error(suite) ->
+ [];
+query_error(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA char(10), PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++ " VALUES(1,'bar')"),
+
+ {error, _} =
+ odbc:sql_query(Ref, "INSERT ONTO " ++ Table ++ " VALUES(1,'bar')"),
+ ok.
+
+%%-------------------------------------------------------------------------
+multiple_select_result_sets(doc) ->
+ ["Test what happens if you have a batch of select queries."];
+multiple_select_result_sets(suite) ->
+ [];
+multiple_select_result_sets(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), "
+ "PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'bar')"),
+
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2, 'foo')"),
+
+ MultipleResult = ?RDBMS:multiple_select(),
+
+ MultipleResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table ++
+ "; SELECT DATA FROM "++ Table ++
+ " WHERE ID=2"),
+ ok;
+ _ ->
+ {skip, "multiple result_set not supported"}
+ end.
+
+%%-------------------------------------------------------------------------
+multiple_mix_result_sets(doc) ->
+ ["Test what happens if you have a batch of select and other type of"
+ " queries."];
+multiple_mix_result_sets(suite) ->
+ [];
+multiple_mix_result_sets(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), "
+ "PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'bar')"),
+
+ MultipleResult = ?RDBMS:multiple_mix(),
+
+ MultipleResult =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(2,'foo'); UPDATE " ++ Table ++
+ " SET DATA = 'foobar' WHERE ID =1;SELECT "
+ "* FROM "
+ ++ Table ++ ";DELETE FROM " ++ Table ++
+ " WHERE ID =1; SELECT DATA FROM " ++ Table),
+ ok;
+ _ ->
+ {skip, "multiple result_set not supported"}
+ end.
+%%-------------------------------------------------------------------------
+multiple_result_sets_error(doc) ->
+ ["Test what happens if one of the batched queries fails."];
+multiple_result_sets_error(suite) ->
+ [];
+multiple_result_sets_error(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID integer, DATA varchar(10), "
+ "PRIMARY KEY(ID))"),
+ {updated, 1} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'bar')"),
+
+ {error, Error} =
+ odbc:sql_query(Ref, "INSERT INTO " ++ Table ++
+ " VALUES(1,'foo'); SELECT * FROM " ++ Table),
+ is_driver_error(Error),
+
+ {error, NewError} =
+ odbc:sql_query(Ref, "SELECT * FROM "
+ ++ Table ++ ";INSERT INTO " ++ Table ++
+ " VALUES(1,'foo')"),
+ is_driver_error(NewError),
+ ok;
+ _ ->
+ {skip, "multiple result_set not supported"}
+ end.
+
+%%-------------------------------------------------------------------------
+parameterized_queries(doc)->
+ ["Tests diffrent variants of parameterized queries."];
+parameterized_queries(suite) ->
+ %% Note timestamps are inserted with param_query in odbc_data_type_SUITE
+ %% so no need to test this again.
+ [param_integers,
+ param_insert_decimal, param_insert_numeric,
+ param_insert_string,
+ param_insert_float, param_insert_real, param_insert_double,
+ param_insert_mix, param_update, param_delete, param_select].
+
+%%-------------------------------------------------------------------------
+param_integers(doc)->
+ ["Test insertion of integers by parameterized queries."];
+param_integers(suite) ->
+ [param_insert_tiny_int,
+ param_insert_small_int, param_insert_int, param_insert_integer].
+%%-------------------------------------------------------------------------
+param_insert_tiny_int(doc)->
+ ["Test insertion of tiny ints by parameterized queries."];
+param_insert_tiny_int(suite) ->
+ [];
+param_insert_tiny_int(Config) when is_list(Config) ->
+ case ?RDBMS of
+ sqlserver ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD TINYINT)"),
+
+ {updated, Count} =
+ odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_tinyint, [1, 2]}],
+ ?TIMEOUT),%Make sure to test timeout clause
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_tiny_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_tinyint, [1, "2"]}])),
+ ok;
+ _ ->
+ {skip, "Type tiniyint not supported"}
+ end.
+%%-------------------------------------------------------------------------
+param_insert_small_int(doc)->
+ ["Test insertion of small ints by parameterized queries."];
+param_insert_small_int(suite) ->
+ [];
+param_insert_small_int(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD SMALLINT)"),
+
+ {updated, Count} =
+ odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)", [{sql_smallint, [1, 2]}],
+ ?TIMEOUT), %% Make sure to test timeout clause
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_small_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_smallint, [1, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_int(doc)->
+ ["Test insertion of ints by parameterized queries."];
+param_insert_int(suite) ->
+ [];
+param_insert_int(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD INT)"),
+
+ Int = ?RDBMS:small_int_max() + 1,
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, Int]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_integer(doc)->
+ ["Test insertion of integers by parameterized queries."];
+param_insert_integer(suite) ->
+ [];
+param_insert_integer(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD INTEGER)"),
+
+ Int = ?RDBMS:small_int_max() + 1,
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, Int]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_int(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_integer, [1, 2.3]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_decimal(doc)->
+ ["Test insertion of decimal numbers by parameterized queries."];
+param_insert_decimal(suite) ->
+ [];
+param_insert_decimal(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD DECIMAL (3,0))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 0}, [1, 2]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_decimal(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 0}, [1, "2"]}])),
+
+
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD DECIMAL (3,1))"),
+
+ {updated, NewCount} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 1}, [0.25]}]),
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ {selected, Fields, [{Value}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fields),
+
+ odbc_test_lib:match_float(Value, 0.3, 0.01),
+
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_numeric(doc)->
+ ["Test insertion of numeric numbers by parameterized queries."];
+param_insert_numeric(suite) ->
+ [];
+param_insert_numeric(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD NUMERIC (3,0))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_numeric,3,0}, [1, 2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_numeric(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_decimal, 3, 0}, [1, "2"]}])),
+
+ odbc:sql_query(Ref, "DROP TABLE " ++ Table),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD NUMERIC (3,1))"),
+
+ {updated, NewCount} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_numeric, 3, 1}, [0.25]}]),
+
+ true = odbc_test_lib:check_row_count(1, NewCount),
+
+ {selected, Fileds, [{Value}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ odbc_test_lib:match_float(Value, 0.3, 0.01),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_string(doc) ->
+ ["Test insertion of strings by parameterized queries."];
+param_insert_string(suite) ->
+ [param_insert_char, param_insert_character, param_insert_char_varying,
+ param_insert_character_varying].
+
+%%-------------------------------------------------------------------------
+param_insert_char(doc)->
+ ["Test insertion of fixed length string by parameterized queries."];
+param_insert_char(suite) ->
+ [];
+param_insert_char(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHAR (10))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foofoofoof", "0123456789"]}]),
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected,Fileds,[{"foofoofoof"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10}, ["1", 2.3]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_character(doc)->
+ ["Test insertion of fixed length string by parameterized queries."];
+param_insert_character(suite) ->
+ [];
+param_insert_character(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHARACTER (10))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foofoofoof", "0123456789"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{"foofoofoof"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_char, 10}, ["1", 2]}])),
+ ok.
+
+%%------------------------------------------------------------------------
+param_insert_char_varying(doc)->
+ ["Test insertion of variable length strings by parameterized queries."];
+param_insert_char_varying(suite) ->
+ [];
+param_insert_char_varying(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHAR VARYING(10))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "0123456789"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{"foo"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10}, ["1", 2.3]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_character_varying(doc)->
+ ["Test insertion of variable length strings by parameterized queries."];
+param_insert_character_varying(suite) ->
+ [];
+param_insert_character_varying(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD CHARACTER VARYING(10))"),
+
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "0123456789"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{"foo"}, {"0123456789"}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ {error, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10},
+ ["foo", "01234567890"]}]),
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_varchar, 10}, ["1", 2]}])),
+ ok.
+%%-------------------------------------------------------------------------
+param_insert_float(doc)->
+ ["Test insertion of floats by parameterized queries."];
+param_insert_float(suite) ->
+ [];
+param_insert_float(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD FLOAT(5))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_float,5}, [1.3, 1.2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{Float1},{Float2}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ case (odbc_test_lib:match_float(Float1, 1.3, 0.000001) and
+ odbc_test_lib:match_float(Float2, 1.2, 0.000001)) of
+ true ->
+ ok;
+ false ->
+ test_server:fail(float_numbers_do_not_match)
+ end,
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{{sql_float, 5}, [1.0, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_real(doc)->
+ ["Test insertion of real numbers by parameterized queries."];
+param_insert_real(suite) ->
+ [];
+param_insert_real(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD REAL)"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_real, [1.3, 1.2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ %_InsertResult = ?RDBMS:param_select_real(),
+
+ {selected, Fileds, [{Real1},{Real2}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ case (odbc_test_lib:match_float(Real1, 1.3, 0.000001) and
+ odbc_test_lib:match_float(Real2, 1.2, 0.000001)) of
+ true ->
+ ok;
+ false ->
+ test_server:fail(real_numbers_do_not_match)
+ end,
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_real,[1.0, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_double(doc)->
+ ["Test insertion of doubles by parameterized queries."];
+param_insert_double(suite) ->
+ [];
+param_insert_double(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (FIELD DOUBLE PRECISION)"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_double, [1.3, 1.2]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ {selected, Fileds, [{Double1},{Double2}]} =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+
+ ["FIELD"] = odbc_test_lib:to_upper(Fileds),
+
+ case (odbc_test_lib:match_float(Double1, 1.3, 0.000001) and
+ odbc_test_lib:match_float(Double2, 1.2, 0.000001)) of
+ true ->
+ ok;
+ false ->
+ test_server:fail(double_numbers_do_not_match)
+ end,
+
+ {'EXIT',{badarg,odbc,param_query,'Params'}} =
+ (catch odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(FIELD) VALUES(?)",
+ [{sql_double, [1.0, "2"]}])),
+ ok.
+
+%%-------------------------------------------------------------------------
+param_insert_mix(doc)->
+ ["Test insertion of a mixture of datatypes by parameterized queries."];
+param_insert_mix(suite) ->
+ [];
+param_insert_mix(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2]},
+ {{sql_varchar, 10}, ["foo", "bar"]}]),
+
+ true = odbc_test_lib:check_row_count(2, Count),
+
+ InsertResult = ?RDBMS:param_select_mix(),
+
+ InsertResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+ ok.
+%%-------------------------------------------------------------------------
+param_update(doc)->
+ ["Test parameterized update query."];
+param_update(suite) ->
+ [];
+param_update(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10},
+ ["foo", "bar", "baz"]}]),
+
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ {updated, NewCount} = odbc:param_query(Ref, "UPDATE " ++ Table ++
+ " SET DATA = 'foobar' WHERE ID = ?",
+ [{sql_integer, [1, 2]}]),
+
+ true = odbc_test_lib:check_row_count(2, NewCount),
+
+ UpdateResult = ?RDBMS:param_update(),
+
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+delete_nonexisting_row(doc) -> % OTP-5759
+ ["Make a delete...where with false conditions (0 rows deleted). ",
+ "This used to give an error message (see ticket OTP-5759)."];
+delete_nonexisting_row(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table
+ ++ " (ID INTEGER, DATA CHARACTER VARYING(10))"),
+ {updated, Count} =
+ odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10}, ["foo", "bar", "baz"]}]),
+
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ {updated, NewCount} =
+ odbc:sql_query(Ref, "DELETE FROM " ++ Table ++ " WHERE ID = 8"),
+
+ true = odbc_test_lib:check_row_count(0, NewCount),
+
+ {updated, _} =
+ odbc:sql_query(Ref, "DROP TABLE "++ Table),
+
+ ok.
+
+%%-------------------------------------------------------------------------
+param_delete(doc) ->
+ ["Test parameterized delete query."];
+param_delete(suite) ->
+ [];
+param_delete(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10},
+ ["foo", "bar", "baz"]}]),
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ {updated, NewCount} = odbc:param_query(Ref, "DELETE FROM " ++ Table ++
+ " WHERE ID = ?",
+ [{sql_integer, [1, 2]}]),
+
+ true = odbc_test_lib:check_row_count(2, NewCount),
+
+ UpdateResult = ?RDBMS:param_delete(),
+
+ UpdateResult =
+ odbc:sql_query(Ref, "SELECT * FROM " ++ Table),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+param_select(doc) ->
+ ["Test parameterized select query."];
+param_select(suite) ->
+ [];
+param_select(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (ID INTEGER, DATA CHARACTER VARYING(10),"
+ " PRIMARY KEY(ID))"),
+
+ {updated, Count} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++
+ "(ID, DATA) VALUES(?, ?)",
+ [{sql_integer, [1, 2, 3]},
+ {{sql_varchar, 10},
+ ["foo", "bar", "foo"]}]),
+
+ true = odbc_test_lib:check_row_count(3, Count),
+
+ SelectResult = ?RDBMS:param_select(),
+
+ SelectResult = odbc:param_query(Ref, "SELECT * FROM " ++ Table ++
+ " WHERE DATA = ?",
+ [{{sql_varchar, 10}, ["foo"]}]),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_table(doc) ->
+ ["Test describe_table/[2,3]"];
+describe_table(suite) ->
+ [describe_integer, describe_string, describe_floating, describe_dec_num,
+ describe_no_such_table].
+
+%%-------------------------------------------------------------------------
+describe_integer(doc) ->
+ ["Test describe_table/[2,3] for integer columns."];
+describe_integer(suite) ->
+ [];
+describe_integer(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (int1 SMALLINT, int2 INT, int3 INTEGER)"),
+
+ Decs = ?RDBMS:describe_integer(),
+ %% Make sure to test timeout clause
+ Decs = odbc:describe_table(Ref, Table, ?TIMEOUT),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_string(doc) ->
+ ["Test describe_table/[2,3] for string columns."];
+describe_string(suite) ->
+ [];
+describe_string(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (str1 char(10), str2 character(10), "
+ "str3 CHAR VARYING(10), str4 "
+ "CHARACTER VARYING(10))"),
+
+ Decs = ?RDBMS:describe_string(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_floating(doc) ->
+ ["Test describe_table/[2,3] for floting columns."];
+describe_floating(suite) ->
+ [];
+describe_floating(Config) when is_list(Config) ->
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (f FLOAT(5), r REAL, "
+ "d DOUBLE PRECISION)"),
+
+ Decs = ?RDBMS:describe_floating(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_dec_num(doc) ->
+ ["Test describe_table/[2,3] for decimal and numerical columns"];
+describe_dec_num(suite) ->
+ [];
+describe_dec_num(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} =
+ odbc:sql_query(Ref,
+ "CREATE TABLE " ++ Table ++
+ " (dec DECIMAL(9,3), num NUMERIC(9,2))"),
+
+ Decs = ?RDBMS:describe_dec_num(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+
+%%-------------------------------------------------------------------------
+describe_timestamp(doc) ->
+ ["Test describe_table/[2,3] for tinmestap columns"];
+describe_timestamp(suite) ->
+ [];
+describe_timestamp(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {updated, _} = % Value == 0 || -1 driver dependent!
+ odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++
+ ?RDBMS:create_timestamp_table()),
+
+ Decs = ?RDBMS:describe_timestamp(),
+
+ Decs = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+describe_no_such_table(doc) ->
+ ["Test what happens if you try to describe a table that does not exist."];
+describe_no_such_table(suite) ->
+ [];
+describe_no_such_table(Config) when is_list(Config) ->
+
+ Ref = ?config(connection_ref, Config),
+ Table = ?config(tableName, Config),
+
+ {error, _ } = odbc:describe_table(Ref, Table),
+ ok.
+
+%%-------------------------------------------------------------------------
+%% Internal functions
+%%-------------------------------------------------------------------------
+
+is_driver_error(Error) ->
+ case is_list(Error) of
+ true ->
+ test_server:format("Driver error ~p~n", [Error]),
+ ok;
+ false ->
+ test_server:fail(Error)
+ end.
diff --git a/lib/odbc/test/odbc_start_SUITE.erl b/lib/odbc/test/odbc_start_SUITE.erl
new file mode 100644
index 0000000000..2cca8e4546
--- /dev/null
+++ b/lib/odbc/test/odbc_start_SUITE.erl
@@ -0,0 +1,147 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-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(odbc_start_SUITE).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("test_server.hrl").
+-include("test_server_line.hrl").
+-include("odbc_test.hrl").
+
+%% Test server callback functions
+%%--------------------------------------------------------------------
+%% Function: init_per_suite(Config) -> Config
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Initialization before the whole suite
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%%--------------------------------------------------------------------
+init_per_suite(Config) ->
+ case code:which(odbc) of
+ non_existing ->
+ {skip, "No ODBC built"};
+ _ ->
+ [{tableName, odbc_test_lib:unique_table_name()} | Config]
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: end_per_suite(Config) -> _
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after the whole suite
+%%--------------------------------------------------------------------
+end_per_suite(_Config) ->
+ ok.
+%%--------------------------------------------------------------------
+%% Function: init_per_testcase(TestCase, Config) -> Config
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%%
+%% Description: Initialization before each test case
+%%
+%% Note: This function is free to add any key/value pairs to the Config
+%% variable, but should NOT alter/remove any existing entries.
+%% Description: Initialization before each test case
+%%--------------------------------------------------------------------
+init_per_testcase(_TestCase, Config0) ->
+ test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]),
+ Config = lists:keydelete(watchdog, 1, Config0),
+ Dog = test_server:timetrap(?TIMEOUT),
+ [{watchdog, Dog} | Config].
+
+%%--------------------------------------------------------------------
+%% Function: end_per_testcase(TestCase, Config) -> _
+%% Case - atom()
+%% Name of the test case that is about to be run.
+%% Config - [tuple()]
+%% A list of key/value pairs, holding the test case configuration.
+%% Description: Cleanup after each test case
+%%--------------------------------------------------------------------
+end_per_testcase(_TestCase, Config) ->
+ Dog = ?config(watchdog, Config),
+ case Dog of
+ undefined ->
+ ok;
+ _ ->
+ test_server:timetrap_cancel(Dog)
+ end.
+
+%%--------------------------------------------------------------------
+%% Function: all(Clause) -> TestCases
+%% Clause - atom() - suite | doc
+%% TestCases - [Case]
+%% Case - atom()
+%% Name of a test case.
+%% Description: Returns a list of all test cases in this test suite
+%%--------------------------------------------------------------------
+all(doc) ->
+ ["Test start/stop of odbc"];
+
+all(suite) ->
+ case odbc_test_lib:odbc_check() of
+ ok -> all();
+ Other -> {skip, Other}
+ end.
+
+all() ->
+ [start].
+
+
+%% Test cases starts here.
+%%--------------------------------------------------------------------
+
+start(doc) ->
+ ["Test start/stop of odbc"];
+start(suite) ->
+ [];
+start(Config) when is_list(Config) ->
+ {error,odbc_not_started} = odbc:connect(?RDBMS:connection_string(), []),
+ odbc:start(),
+ case odbc:connect(?RDBMS:connection_string(), []) of
+ {ok, Ref0} ->
+ ok = odbc:disconnect(Ref0),
+ odbc:stop(),
+ {error,odbc_not_started} =
+ odbc:connect(?RDBMS:connection_string(), []),
+ start_odbc(transient),
+ start_odbc(permanent);
+ {error, odbc_not_started} ->
+ test_server:fail(start_failed);
+ Error ->
+ test_server:format("Connection failed: ~p~n", [Error]),
+ {skip, "ODBC is not properly setup"}
+ end.
+
+start_odbc(Type) ->
+ ok = odbc:start(Type),
+ case odbc:connect(?RDBMS:connection_string(), []) of
+ {ok, Ref} ->
+ ok = odbc:disconnect(Ref),
+ odbc:stop();
+ {error, odbc_not_started} ->
+ test_server:fail(start_failed)
+ end.
diff --git a/lib/odbc/test/odbc_test.hrl b/lib/odbc/test/odbc_test.hrl
new file mode 100644
index 0000000000..87f50043db
--- /dev/null
+++ b/lib/odbc/test/odbc_test.hrl
@@ -0,0 +1,37 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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%
+%%
+
+
+% Default timetrap timeout (set in init_per_testcase).
+% This should be set relatively high (10-15 times the expected
+% max testcasetime).
+-define(default_timeout, ?t:minutes(10)).
+
+-define(RDBMS, case os:type() of
+ {unix, sunos} ->
+ postgres;
+ {unix,linux} ->
+ postgres;
+ {win32, _} ->
+ sqlserver
+ end).
+
+-define(TIMEOUT, 100000).
+
+
diff --git a/lib/odbc/test/odbc_test_lib.erl b/lib/odbc/test/odbc_test_lib.erl
new file mode 100644
index 0000000000..92e895eb87
--- /dev/null
+++ b/lib/odbc/test/odbc_test_lib.erl
@@ -0,0 +1,77 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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(odbc_test_lib).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+-include("odbc_test.hrl").
+-include("test_server.hrl").
+
+unique_table_name() ->
+ lists:reverse(lists:foldl(fun($@, Acc) -> [$t, $A |Acc] ;
+ (X, Acc) -> [X |Acc] end,
+ [], atom_to_list(node()))).
+
+match_float(Float, Match, Delta) ->
+ (Float < Match + Delta) and (Float > Match - Delta).
+
+odbc_check() ->
+ case erlang:system_info(wordsize) of
+ 4 ->
+ case test_server:os_type() of
+ {unix, sunos} ->
+ ok;
+ {unix, linux} ->
+ ok;
+ {win32, _} ->
+ ok;
+ Other ->
+ lists:flatten(
+ io_lib:format("Platform not supported: ~w",
+ [Other]))
+ end;
+ Other ->
+ case test_server:os_type() of
+ {unix, linux} ->
+ ok;
+ Platform ->
+ lists:flatten(
+ io_lib:format("Word on platform ~w size"
+ " ~w not supported", [Other,
+ Platform]))
+ end
+ end.
+
+check_row_count(Count, Count) ->
+ test_server:format("Correct row count Count: ~p~n", [Count]),
+ true;
+check_row_count(_, undefined) ->
+ test_server:format("Undefined row count ~n", []),
+ true;
+check_row_count(Expected, Count) ->
+ test_server:format("Incorrect row count Expected ~p Got ~p~n",
+ [Expected, Count]),
+ false.
+
+to_upper(List) ->
+ lists:map(fun(Str) -> string:to_upper(Str) end, List).
diff --git a/lib/odbc/test/oracle.erl b/lib/odbc/test/oracle.erl
new file mode 100644
index 0000000000..ebf6dbb6bf
--- /dev/null
+++ b/lib/odbc/test/oracle.erl
@@ -0,0 +1,246 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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(oracle).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%-------------------------------------------------------------------------
+connection_string() ->
+ "DSN=Oracle8;UID=odbctest".
+
+%-------------------------------------------------------------------------
+insert_result() ->
+ {selected,["ID","DATA"],[{"1","bar"}]}.
+
+update_result() ->
+ {selected,["ID","DATA"],[{"1","foo"}]}.
+
+selected_ID(N, next) ->
+ {selected,["ID"],[{integer_to_list(N)}]};
+
+selected_ID(_, _) ->
+ {error, driver_does_not_support_function}.
+
+selected_next_N(1)->
+ {selected,["ID"],
+ [{"1"},
+ {"2"},
+ {"3"}]};
+
+selected_next_N(2)->
+ {selected,["ID"],
+ [{"4"},
+ {"5"}]}.
+
+selected_relative_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_absolute_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_list_rows() ->
+ {selected,["ID", "DATA"],[["1", "bar"],["2","foo"]]}.
+
+first_list_rows() ->
+ {error, driver_does_not_support_function}.
+last_list_rows() ->
+ {error, driver_does_not_support_function}.
+prev_list_rows() ->
+ {error, driver_does_not_support_function}.
+next_list_rows() ->
+ {selected,["ID","DATA"],[["1","bar"]]}.
+
+%% In case we get a better oracle driver that support this some day .....
+multiple_select()->
+ [{selected,["ID", "DATA"],[{"1", "bar"},{"2", "foo"}]},
+ {selected,["ID"],[{"foo"}]}].
+
+multiple_mix()->
+ [{updated, 1},{updated, 1},
+ {selected,["ID", "DATA"],[{"1", "foobar"},{"2", "foo"}]},
+ {updated, 1}, {selected,["DATA"],[{"foo"}]}].
+
+%-------------------------------------------------------------------------
+fixed_char_min() ->
+ 1.
+fixed_char_max() ->
+ 2000. %% Should be 255 acording to manual but empirical tests say 2000
+
+create_fixed_char_table(Size) ->
+ " (FIELD char(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+var_char_min() ->
+ 1.
+var_char_max() ->
+ 2000.
+
+create_var_char_table(Size) ->
+ " (FIELD varchar2(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+text_min() ->
+ 1.
+text_max() ->
+ 2147483646. % 2147483647. %% 2^31 - 1
+
+create_text_table() ->
+ " (FIELD long)". %Oracle long is variable length char data
+
+%-------------------------------------------------------------------------
+create_unicode_table() ->
+ " (FIELD nvarchar(50))".
+
+%-------------------------------------------------------------------------
+create_timestamp_table() ->
+ " (FIELD DATETIME)".
+
+%-------------------------------------------------------------------------
+tiny_int_min() ->
+ -999.
+tiny_int_max() ->
+ 999.
+
+create_tiny_int_table() ->
+ " (FIELD number(3, 0))".
+
+tiny_int_min_selected() ->
+ {selected,["FIELD"],[{-999}]}.
+
+tiny_int_max_selected() ->
+ {selected,["FIELD"], [{999}]}.
+
+%-------------------------------------------------------------------------
+small_int_min() ->
+ -99999.
+small_int_max() ->
+ 99999.
+
+create_small_int_table() ->
+ " (FIELD number(5, 0))".
+
+small_int_min_selected() ->
+ {selected,["FIELD"],[{-99999}]}.
+
+small_int_max_selected() ->
+ {selected,["FIELD"], [{99999}]}.
+
+%-------------------------------------------------------------------------
+int_min() ->
+ -999999999.
+int_max() ->
+ 999999999.
+
+create_int_table() ->
+ " (FIELD number(9, 0))".
+
+int_min_selected() ->
+ {selected,["FIELD"],[{-999999999}]}.
+
+int_max_selected() ->
+ {selected,["FIELD"], [{999999999}]}.
+
+%-------------------------------------------------------------------------
+big_int_min() ->
+ -99999999999999999999999999999999999999.
+
+big_int_max() ->
+ 99999999999999999999999999999999999999.
+
+create_big_int_table() ->
+ " (FIELD number(38,0))".
+
+big_int_min_selected() ->
+ {selected,["FIELD"], [{"-99999999999999999999999999999999999999"}]}.
+
+big_int_max_selected() ->
+ {selected,["FIELD"], [{"99999999999999999999999999999999999999"}]}.
+
+%-------------------------------------------------------------------------
+float_min() ->
+ 1.40129846432481707e-45.
+
+float_max() ->
+ 3.40282346638528860e+38.
+
+create_float_table() ->
+ " (FIELD float(32))".
+
+float_underflow() ->
+ "'4.94065645841246544e-324'".
+float_overflow() ->
+ "'1.79769313486231570e+308'".
+
+float_zero_selected() ->
+ {selected,["FIELD"],[{0.00000e+0}]}.
+
+%-------------------------------------------------------------------------
+param_select_small_int() ->
+ {selected,["FIELD"],[{"1"}, {"2"}]}.
+
+param_select_int() ->
+ Int = small_int_max() + 1,
+ {selected,["FIELD"],[{"1"}, {integer_to_list(Int)}]}.
+
+param_select_decimal() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_numeric() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_float() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_real() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_double() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_mix() ->
+ {selected,["ID","DATA"],[{"1", "foo"}, {"2", "bar"}]}.
+
+param_update() ->
+ {selected,["ID","DATA"],[{"1", "foobar"}, {"2", "foobar"}, {"3", "baz"}]}.
+
+param_delete() ->
+ {selected,["ID","DATA"],[{"3", "baz"}]}.
+
+param_select() ->
+ {selected,["ID","DATA"],[{"1", "foo"},{"3", "foo"}]}.
+
+%-------------------------------------------------------------------------
+describe_integer() ->
+ {ok,[{"INT1",{sql_decimal,38,0}},{"INT2",{sql_decimal,38,0}},
+ {"INT3",{sql_decimal,38,0}}]}.
+
+describe_string() ->
+ {ok,[{"STR1",{sql_char,10}},
+ {"STR2",{sql_char,10}},
+ {"STR3",{sql_varchar,10}},
+ {"STR4",{sql_varchar,10}}]}.
+
+describe_floating() ->
+ {ok,[{"F",sql_double},{"R",sql_double},{"D",sql_double}]}.
+describe_dec_num() ->
+ {ok,[{"DEC",{sql_decimal,9,3}},{"NUM",{sql_decimal,9,2}}]}.
diff --git a/lib/odbc/test/postgres.erl b/lib/odbc/test/postgres.erl
new file mode 100644
index 0000000000..169ca26e43
--- /dev/null
+++ b/lib/odbc/test/postgres.erl
@@ -0,0 +1,294 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2006-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(postgres).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%-------------------------------------------------------------------------
+connection_string() ->
+ case test_server:os_type() of
+ {unix, sunos} ->
+ "DSN=Postgres;UID=odbctest";
+ {unix, linux} ->
+ Size = erlang:system_info(wordsize),
+ linux_dist_connection_string(Size)
+ end.
+
+linux_dist_connection_string(4) ->
+ case linux_dist() of
+ "ubuntu" ->
+ "DSN=PostgresLinuxUbuntu;UID=odbctest";
+ _ ->
+ "DSN=PostgresLinux;UID=odbctest"
+ end;
+
+linux_dist_connection_string(_) ->
+ "DSN=PostgresLinux64;UID=odbctest".
+
+linux_dist() ->
+ case file:read_file("/etc/issue") of
+ {ok, Binary} ->
+ [Dist | _ ] = string:tokens(binary_to_list(Binary), " "),
+ string:to_lower(Dist);
+ {error, _} ->
+ other
+ end.
+
+
+%-------------------------------------------------------------------------
+insert_result() ->
+ {selected,["id","data"],[{1,"bar"}]}.
+
+update_result() ->
+ {selected,["id","data"],[{1,"foo"}]}.
+
+selected_ID(N, next) ->
+ {selected,["id"],[{N}]};
+
+selected_ID(_, _) ->
+ {error, driver_does_not_support_function}.
+
+selected_next_N(1)->
+ {selected,["id"],
+ [{1},
+ {2},
+ {3}]};
+
+selected_next_N(2)->
+ {selected,["id"],
+ [{4},
+ {5}]}.
+
+selected_relative_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_absolute_N(_)->
+ {error, driver_does_not_support_function}.
+
+selected_list_rows() ->
+ {selected,["id", "data"],[[1, "bar"],[2,"foo"]]}.
+
+first_list_rows() ->
+ {error, driver_does_not_support_function}.
+last_list_rows() ->
+ {error, driver_does_not_support_function}.
+prev_list_rows() ->
+ {error, driver_does_not_support_function}.
+next_list_rows() ->
+ {selected,["id","data"],[[1,"bar"]]}.
+
+%% In case we get a better postgres driver that support this some day .....
+multiple_select()->
+ [{selected,["id", "data"],[{1, "bar"},{2, "foo"}]},
+ {selected,["id"],[{"foo"}]}].
+
+multiple_mix()->
+ [{updated, 1},{updated, 1},
+ {selected,["id", "data"],[{1, "foobar"},{2, "foo"}]},
+ {updated, 1}, {selected,["data"],[{"foo"}]}].
+
+%-------------------------------------------------------------------------
+fixed_char_min() ->
+ 1.
+fixed_char_max() ->
+ 2000.
+
+create_fixed_char_table(Size) ->
+ " (FIELD char(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+var_char_min() ->
+ 1.
+var_char_max() ->
+ 2000.
+
+create_var_char_table(Size) ->
+ " (FIELD varchar(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+text_min() ->
+ 1.
+text_max() ->
+ 2147483646. % 2147483647. %% 2^31 - 1
+
+create_text_table() ->
+ " (FIELD text)".
+
+%-------------------------------------------------------------------------
+create_unicode_table() ->
+ " (FIELD text)".
+
+%-------------------------------------------------------------------------
+create_timestamp_table() ->
+ " (FIELD TIMESTAMP)".
+
+%-------------------------------------------------------------------------
+small_int_min() ->
+ -32768.
+small_int_max() ->
+ 32767.
+
+create_small_int_table() ->
+ " (FIELD smallint)".
+
+small_int_min_selected() ->
+ {selected,["field"],[{-32768}]}.
+
+small_int_max_selected() ->
+ {selected,["field"], [{32767}]}.
+
+%-------------------------------------------------------------------------
+int_min() ->
+ -2147483648.
+int_max() ->
+ 2147483647.
+
+create_int_table() ->
+ " (FIELD int)".
+
+int_min_selected() ->
+ {selected,["field"],[{-2147483648}]}.
+
+int_max_selected() ->
+ {selected,["field"], [{2147483647}]}.
+
+%-------------------------------------------------------------------------
+big_int_min() ->
+ -9223372036854775808.
+
+big_int_max() ->
+ 9223372036854775807.
+
+create_big_int_table() ->
+ " (FIELD bigint )".
+
+big_int_min_selected() ->
+ {selected,["field"], [{"-9223372036854775808"}]}.
+
+big_int_max_selected() ->
+ {selected,["field"], [{"9223372036854775807"}]}.
+
+%-------------------------------------------------------------------------
+bit_false() ->
+ 0.
+bit_true() ->
+ 1.
+
+create_bit_table() ->
+ " (FIELD bit)".
+
+bit_false_selected() ->
+ {selected,["field"],[{"0"}]}.
+
+bit_true_selected() ->
+ {selected,["field"], [{"1"}]}.
+
+%-------------------------------------------------------------------------
+float_min() ->
+ 1.79e-307.
+float_max() ->
+ 1.79e+308.
+
+create_float_table() ->
+ " (FIELD float)".
+
+float_underflow() ->
+ "1.80e-308".
+float_overflow() ->
+ "1.80e+308".
+
+float_zero_selected() ->
+ {selected,["field"],[{0.00000e+0}]}.
+
+%-------------------------------------------------------------------------
+real_min() ->
+ -3.40e+38.
+real_max() ->
+ 3.40e+38.
+
+real_underflow() ->
+ "-3.41e+38".
+
+real_overflow() ->
+ "3.41e+38".
+
+create_real_table() ->
+ " (FIELD real)".
+
+real_zero_selected() ->
+ {selected,["field"],[{0.00000e+0}]}.
+
+%-------------------------------------------------------------------------
+param_select_small_int() ->
+ {selected,["field"],[{1}, {2}]}.
+
+param_select_int() ->
+ Int = small_int_max() + 1,
+ {selected,["field"],[{1}, {Int}]}.
+
+param_select_decimal() ->
+ {selected,["field"],[{1},{2}]}.
+
+param_select_numeric() ->
+ {selected,["field"],[{1},{2}]}.
+
+param_select_float() ->
+ {selected,["field"],[{1.30000},{1.20000}]}.
+
+param_select_real() ->
+ {selected,["field"],[{1.30000},{1.20000}]}.
+
+param_select_double() ->
+ {selected,["field"],[{1.30000},{1.20000}]}.
+
+param_select_mix() ->
+ {selected,["id","data"],[{1, "foo"}, {2, "bar"}]}.
+
+param_update() ->
+ {selected,["id","data"],[{3, "baz"},{1, "foobar"}, {2, "foobar"}]}.
+
+param_delete() ->
+ {selected,["id","data"],[{3, "baz"}]}.
+
+param_select() ->
+ {selected,["id","data"],[{1, "foo"},{3, "foo"}]}.
+
+%-------------------------------------------------------------------------
+describe_integer() ->
+ {ok,[{"int1",sql_smallint},
+ {"int2",sql_integer},
+ {"int3",sql_integer}]}.
+
+describe_string() ->
+ {ok,[{"str1",{sql_char,10}},
+ {"str2",{sql_char,10}},
+ {"str3",{sql_varchar,10}},
+ {"str4",{sql_varchar,10}}]}.
+
+describe_floating() ->
+ {ok,[{"f",sql_real},{"r",sql_real},{"d",{sql_float,15}}]}.
+describe_dec_num() ->
+ {ok,[{"dec",{sql_numeric,9,3}},{"num",{sql_numeric,9,2}}]}.
+
+describe_timestamp() ->
+ {ok, [{"field", sql_timestamp}]}.
diff --git a/lib/odbc/test/sqlserver.erl b/lib/odbc/test/sqlserver.erl
new file mode 100644
index 0000000000..e3fe30e0bc
--- /dev/null
+++ b/lib/odbc/test/sqlserver.erl
@@ -0,0 +1,298 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2002-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(sqlserver).
+
+%% Note: This directive should only be used in test suites.
+-compile(export_all).
+
+%-------------------------------------------------------------------------
+connection_string() ->
+ "DSN=sql-server;UID=odbctest;PWD=gurka".
+
+%-------------------------------------------------------------------------
+insert_result() ->
+ {selected,["ID","DATA"],[{1,"bar"}]}.
+
+update_result() ->
+ {selected,["ID","DATA"],[{1,"foo"}]}.
+
+selected_ID(N, _) ->
+ {selected,["ID"],[{N}]}.
+
+selected_next_N(1)->
+ {selected,["ID"],
+ [{1},
+ {2},
+ {3}]};
+
+selected_next_N(2)->
+ {selected,["ID"],
+ [{4},
+ {5}]}.
+
+selected_relative_N(1)->
+ {selected,["ID"],
+ [{2},
+ {3},
+ {4}]};
+
+selected_relative_N(2)->
+ {selected,["ID"],
+ [{7},
+ {8}]}.
+
+selected_absolute_N(1)->
+ {selected,["ID"],
+ [{1},
+ {2},
+ {3}]};
+
+selected_absolute_N(2)->
+ {selected,["ID"],
+ [{1},
+ {2},
+ {3},
+ {4},
+ {5}]}.
+
+selected_list_rows() ->
+ {selected,["ID", "DATA"],[[1, "bar"],[2,"foo"]]}.
+
+first_list_rows() ->
+ {selected,["ID", "DATA"],[[1, "bar"]]}.
+last_list_rows() ->
+ {selected,["ID", "DATA"],[[2, "foo"]]}.
+prev_list_rows() ->
+ {selected,["ID", "DATA"],[[1, "bar"]]}.
+next_list_rows() ->
+ {selected,["ID", "DATA"],[[2, "foo"]]}.
+
+multiple_select()->
+ [{selected,["ID", "DATA"],[{1, "bar"},{2, "foo"}]},
+ {selected,["DATA"],[{"foo"}]}].
+
+multiple_mix()->
+ [{updated, 1},{updated, 1},
+ {selected,["ID", "DATA"],[{1, "foobar"},{2, "foo"}]},
+ {updated, 1}, {selected,["DATA"],[{"foo"}]}].
+
+%-------------------------------------------------------------------------
+fixed_char_min() ->
+ 1.
+
+fixed_char_max() ->
+ 8000.
+
+create_fixed_char_table(Size) ->
+ " (FIELD char(" ++ integer_to_list(Size) ++ "))".
+
+%-------------------------------------------------------------------------
+var_char_min() ->
+ 1.
+var_char_max() ->
+ 8000.
+
+create_var_char_table(Size) ->
+ " (FIELD varchar(" ++ integer_to_list(Size) ++ "))".
+%-------------------------------------------------------------------------
+text_min() ->
+ 1.
+text_max() ->
+ 2147483647. %% 2^31 - 1
+
+create_text_table() ->
+ " (FIELD text)".
+
+%-------------------------------------------------------------------------
+create_unicode_table() ->
+ " (FIELD nvarchar(50))".
+
+%-------------------------------------------------------------------------
+create_timestamp_table() ->
+ " (FIELD DATETIME)".
+
+%-------------------------------------------------------------------------
+tiny_int_min() ->
+ 0.
+tiny_int_max() ->
+ 255.
+
+create_tiny_int_table() ->
+ " (FIELD tinyint)".
+
+tiny_int_min_selected() ->
+ {selected,["FIELD"],[{tiny_int_min()}]}.
+
+tiny_int_max_selected() ->
+ {selected,["FIELD"], [{tiny_int_max()}]}.
+
+%-------------------------------------------------------------------------
+small_int_min() ->
+ -32768. % -2^15
+small_int_max() ->
+ 32767. % 2^15-1
+
+create_small_int_table() ->
+ " (FIELD smallint)".
+
+small_int_min_selected() ->
+ {selected,["FIELD"],[{small_int_min()}]}.
+
+small_int_max_selected() ->
+ {selected,["FIELD"], [{small_int_max()}]}.
+
+%-------------------------------------------------------------------------
+int_min() ->
+ -2147483648. % -2^31
+int_max() ->
+ 2147483647. % 2^31-1
+
+create_int_table() ->
+ " (FIELD int)".
+
+int_min_selected() ->
+ {selected,["FIELD"],[{int_min()}]}.
+
+int_max_selected() ->
+ {selected,["FIELD"], [{int_max()}]}.
+
+%-------------------------------------------------------------------------
+big_int_min() ->
+ -9223372036854775808. % -2^63
+big_int_max() ->
+ 9223372036854775807. % 2^63-1
+
+create_big_int_table() ->
+ " (FIELD bigint)".
+
+big_int_min_selected() ->
+ {selected,["FIELD"],[{integer_to_list(big_int_min())}]}.
+
+big_int_max_selected() ->
+ {selected,["FIELD"], [{integer_to_list(big_int_max())}]}.
+
+%-------------------------------------------------------------------------
+bit_false() ->
+ 0.
+bit_true() ->
+ 1.
+
+create_bit_table() ->
+ " (FIELD bit)".
+
+bit_false_selected() ->
+ {selected,["FIELD"],[{false}]}.
+
+bit_true_selected() ->
+ {selected,["FIELD"], [{true}]}.
+%-------------------------------------------------------------------------
+float_min() ->
+ -1.79e+308.
+float_max() ->
+ 1.79e+308.
+
+float_underflow() ->
+ "'-1.80e+308'".
+
+float_overflow() ->
+ "'-1.80e+308'".
+
+create_float_table() ->
+ " (FIELD float)".
+
+float_zero_selected() ->
+ {selected,["FIELD"],[{0.00000e+0}]}.
+%-------------------------------------------------------------------------
+real_min() ->
+ -3.40e+38.
+real_max() ->
+ 3.40e+38.
+
+real_underflow() ->
+ -3.41e+38.
+
+real_overflow() ->
+ 3.41e+38.
+
+create_real_table() ->
+ " (FIELD real)".
+
+real_zero_selected() ->
+ {selected,["FIELD"],[{0.00000e+0}]}.
+%-------------------------------------------------------------------------
+param_select_tiny_int() ->
+ {selected,["FIELD"],[{1}, {2}]}.
+
+param_select_small_int() ->
+ {selected,["FIELD"],[{1}, {2}]}.
+
+param_select_int() ->
+ Int = small_int_max() + 1,
+ {selected,["FIELD"],[{1}, {Int}]}.
+
+param_select_decimal() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_numeric() ->
+ {selected,["FIELD"],[{1},{2}]}.
+
+param_select_float() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_real() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_double() ->
+ {selected,["FIELD"],[{1.30000},{1.20000}]}.
+
+param_select_mix() ->
+ {selected,["ID","DATA"],[{1, "foo"}, {2, "bar"}]}.
+
+param_update() ->
+ {selected,["ID","DATA"],[{1, "foobar"}, {2, "foobar"}, {3, "baz"}]}.
+
+param_delete() ->
+ {selected,["ID","DATA"],[{3, "baz"}]}.
+
+param_select() ->
+ {selected,["ID","DATA"],[{1, "foo"},{3, "foo"}]}.
+
+%-------------------------------------------------------------------------
+
+describe_integer() ->
+ {ok,[{"int1", sql_smallint},{"int2", sql_integer},
+ {"int3", sql_integer}]}.
+
+describe_string() ->
+ {ok,[{"str1",{sql_char,10}},
+ {"str2",{sql_char,10}},
+ {"str3",{sql_varchar,10}},
+ {"str4",{sql_varchar,10}}]}.
+
+describe_floating() ->
+ {ok,[{"f", sql_real},{"r", sql_real}, {"d", {sql_float, 53}}]}.
+
+describe_dec_num() ->
+ {ok,[{"dec",{sql_decimal,9,3}},{"num",{sql_numeric,9,2}}]}.
+
+describe_timestamp() ->
+ {ok, [{"field", sql_timestamp}]}.
diff --git a/lib/parsetools/doc/src/notes.xml b/lib/parsetools/doc/src/notes.xml
index 8a6f2c2714..63c37bc27d 100644
--- a/lib/parsetools/doc/src/notes.xml
+++ b/lib/parsetools/doc/src/notes.xml
@@ -30,6 +30,28 @@
</header>
<p>This document describes the changes made to the Parsetools application.</p>
+<section><title>Parsetools 2.0.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Yecc failed to report reduce/reduce conflicts where
+ one of the reductions involved the root symbol. This bug
+ has been fixed. (Thanks to Manolis Papadakis.)</p>
+ <p>
+ Own Id: OTP-8483</p>
+ </item>
+ <item>
+ <p>A bug introduced in Parsetools 1.4.4 (R12B-2) has been
+ fixed. (Thanks to Manolis Papadakis.)</p>
+ <p>
+ Own Id: OTP-8486</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Parsetools 2.0.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/parsetools/src/leex.erl b/lib/parsetools/src/leex.erl
index fd494eaf06..d0b4b9efe7 100644
--- a/lib/parsetools/src/leex.erl
+++ b/lib/parsetools/src/leex.erl
@@ -35,7 +35,7 @@
-export([compile/3,file/1,file/2,format_error/1]).
-import(lists, [member/2,reverse/1,sort/1,delete/2,
- keysearch/3,keysort/2,keydelete/3,keyfind/3,
+ keysort/2,keydelete/3,keyfind/3,
map/2,foldl/3,foreach/2,flatmap/2]).
-import(string, [substr/2,substr/3,span/2]).
-import(ordsets, [is_element/2,add_element/2,union/2]).
@@ -182,18 +182,18 @@ options(Options0, [Key|Keys], L) when is_list(Options0) ->
false ->
Options0
end,
- V = case keysearch(Key, 1, Options) of
- {value, {Key, Filename0}} when Key =:= includefile;
- Key =:= scannerfile ->
+ V = case lists:keyfind(Key, 1, Options) of
+ {Key, Filename0} when Key =:= includefile;
+ Key =:= scannerfile ->
case is_filename(Filename0) of
no ->
badarg;
Filename ->
{ok,[{Key,Filename}]}
end;
- {value,{Key,Bool}} when Bool; not Bool ->
- {ok,[{Key, Bool}]};
- {value,{Key, _}} ->
+ {Key, Bool} = KB when is_boolean(Bool) ->
+ {ok, [KB]};
+ {Key, _} ->
badarg;
false ->
{ok,[{Key,default_option(Key)}]}
@@ -231,8 +231,7 @@ atom_option(verbose) -> {verbose,true};
atom_option(Key) -> Key.
is_filename(T) ->
- try filename:flatten(T) of
- Filename -> Filename
+ try filename:flatten(T)
catch error: _ -> no
end.
@@ -320,10 +319,10 @@ filenames(File, Opts, St0) ->
St1 = St0#leex{xfile=Xfile,
opts=Opts,
module=Module},
- {value,{includefile,Ifile0}} = keysearch(includefile, 1, Opts),
+ {includefile,Ifile0} = lists:keyfind(includefile, 1, Opts),
Ifile = inc_file_name(Ifile0),
%% Test for explicit scanner file.
- {value,{scannerfile,Ofile}} = keysearch(scannerfile, 1, Opts),
+ {scannerfile,Ofile} = lists:keyfind(scannerfile, 1, Opts),
if
Ofile =:= [] ->
St1#leex{efile=filename:join(Dir, Efile),
@@ -495,7 +494,7 @@ parse_rule(S, Line, Atoks, Ms, N, St) ->
end.
var_used(Name, Toks) ->
- case keyfind(Name, 3, Toks) of
+ case lists:keyfind(Name, 3, Toks) of
{var,_,Name} -> true; %It's the var we want
_ -> false
end.
@@ -629,7 +628,7 @@ re_seq(Cs0, Sn0, St) ->
{Rs,Sn1,Cs1} -> {{seq,Rs},Sn1,Cs1}
end.
-re_seq1([C|_]=Cs0, Sn0, St) when C /= $|, C /= $) ->
+re_seq1([C|_]=Cs0, Sn0, St) when C =/= $|, C =/= $) ->
{L,Sn1,Cs1} = re_repeat(Cs0, Sn0, St),
{Rs,Sn2,Cs2} = re_seq1(Cs1, Sn1, St),
{[L|Rs],Sn2,Cs2};
@@ -751,9 +750,9 @@ re_char_class("[:" ++ Cs0, Cc, #leex{posix=true}=St) ->
{Pcl,":]" ++ Cs1} -> re_char_class(Cs1, [{posix,Pcl}|Cc], St);
{_,Cs1} -> parse_error({posix_cc,string_between(Cs0, Cs1)})
end;
-re_char_class([C1|Cs0], Cc, St) when C1 /= $] ->
+re_char_class([C1|Cs0], Cc, St) when C1 =/= $] ->
case re_char(C1, Cs0) of
- {Cf,[$-,C2|Cs1]} when C2 /= $] ->
+ {Cf,[$-,C2|Cs1]} when C2 =/= $] ->
case re_char(C2, Cs1) of
{Cl,Cs2} when Cf < Cl ->
re_char_class(Cs2, [{range,Cf,Cl}|Cc], St);
@@ -998,7 +997,7 @@ pack_crs([{C1,C2},{C3,C4}|Crs]) when C2 >= C3, C2 < C4 ->
%% C1 C2
%% C3 C4
pack_crs([{C1,C4}|Crs]);
-pack_crs([{C1,C2},{C3,C4}|Crs]) when C2 + 1 == C3 ->
+pack_crs([{C1,C2},{C3,C4}|Crs]) when C2 + 1 =:= C3 ->
%% C1 C2
%% C3 C4
pack_crs([{C1,C4}|Crs]);
@@ -1055,7 +1054,7 @@ build_dfa(Set, Us, N, Ts, Ms, NFA) ->
%% List of all transition sets.
Crs0 = [Cr || S <- Set,
{Crs,_St} <- (element(S, NFA))#nfa_state.edges,
- Crs /= epsilon, % Not an epsilon transition
+ Crs =/= epsilon, % Not an epsilon transition
Cr <- Crs ],
Crs1 = lists:usort(Crs0), % Must remove duplicates!
%% Build list of disjoint test ranges.
@@ -1072,7 +1071,7 @@ disjoint_crs([{_C1,C2}=Cr1,{C3,_C4}=Cr2|Crs]) when C2 < C3 ->
%% C1 C2
%% C3 C4
[Cr1|disjoint_crs([Cr2|Crs])];
-disjoint_crs([{C1,C2},{C3,C4}|Crs]) when C1 == C3 ->
+disjoint_crs([{C1,C2},{C3,C4}|Crs]) when C1 =:= C3 ->
%% C1 C2
%% C3 C4
[{C1,C2}|disjoint_crs(add_element({C2+1,C4}, Crs))];
@@ -1080,7 +1079,7 @@ disjoint_crs([{C1,C2},{C3,C4}|Crs]) when C1 < C3, C2 >= C3, C2 < C4 ->
%% C1 C2
%% C3 C4
[{C1,C3-1}|disjoint_crs(union([{C3,C2},{C2+1,C4}], Crs))];
-disjoint_crs([{C1,C2},{C3,C4}|Crs]) when C1 < C3, C2 == C4 ->
+disjoint_crs([{C1,C2},{C3,C4}|Crs]) when C1 < C3, C2 =:= C4 ->
%% C1 C2
%% C3 C4
[{C1,C3-1}|disjoint_crs(add_element({C3,C4}, Crs))];
@@ -1093,7 +1092,7 @@ disjoint_crs([]) -> [].
build_dfa([Cr|Crs], Set, Us, N, Ts, Ms, NFA) ->
case eclosure(move(Set, Cr, NFA), NFA) of
- S when S /= [] ->
+ S when S =/= [] ->
case dfa_state_exist(S, Us, Ms) of
{yes,T} ->
build_dfa(Crs, Set, Us, N, store(Cr, T, Ts), Ms, NFA);
@@ -1110,11 +1109,11 @@ build_dfa([], _, Us, N, Ts, _, _) ->
%% dfa_state_exist(Set, Unmarked, Marked) -> {yes,State} | no.
dfa_state_exist(S, Us, Ms) ->
- case keysearch(S, #dfa_state.nfa, Us) of
- {value,#dfa_state{no=T}} -> {yes,T};
+ case lists:keyfind(S, #dfa_state.nfa, Us) of
+ #dfa_state{no=T} -> {yes,T};
false ->
- case keysearch(S, #dfa_state.nfa, Ms) of
- {value,#dfa_state{no=T}} -> {yes,T};
+ case lists:keyfind(S, #dfa_state.nfa, Ms) of
+ #dfa_state{no=T} -> {yes,T};
false -> no
end
end.
@@ -1129,7 +1128,7 @@ eclosure(Sts, NFA) -> eclosure(Sts, NFA, []).
eclosure([St|Sts], NFA, Ec) ->
#nfa_state{edges=Es} = element(St, NFA),
eclosure([ N || {epsilon,N} <- Es,
- not is_element(N, Ec) ] ++ Sts,
+ not is_element(N, Ec) ] ++ Sts,
NFA, add_element(St, Ec));
eclosure([], _, Ec) -> Ec.
@@ -1137,7 +1136,7 @@ move(Sts, Cr, NFA) ->
%% io:fwrite("move1: ~p\n", [{Sts,Cr}]),
[ St || N <- Sts,
{Crs,St} <- (element(N, NFA))#nfa_state.edges,
- Crs /= epsilon, % Not an epsilon transition
+ Crs =/= epsilon, % Not an epsilon transition
in_crs(Cr, Crs) ].
in_crs({C1,C2}, [{C3,C4}|_Crs]) when C1 >= C3, C2 =< C4 -> true;
@@ -1436,7 +1435,7 @@ pack_trans([{{$\n,Cl},S}|Trs], Pt) ->
pack_trans([{{Cf,Cl},S}|Trs], Pt) when Cf < $\n, Cl > $\n ->
pack_trans([{{Cf,$\n-1},S},{{$\n+1,Cl},S}|Trs], [{$\n,S}|Pt]);
%% Small ranges become singletons.
-pack_trans([{{Cf,Cl},S}|Trs], Pt) when Cl == Cf + 1 ->
+pack_trans([{{Cf,Cl},S}|Trs], Pt) when Cl =:= Cf + 1 ->
pack_trans(Trs, [{Cf,S},{Cl,S}|Pt]);
pack_trans([Tr|Trs], Pt) -> % The default uninteresting case
pack_trans(Trs, Pt ++ [Tr]);
diff --git a/lib/parsetools/vsn.mk b/lib/parsetools/vsn.mk
index b1354e89d8..f3e2dc0fb4 100644
--- a/lib/parsetools/vsn.mk
+++ b/lib/parsetools/vsn.mk
@@ -1 +1 @@
-PARSETOOLS_VSN = 2.0.2
+PARSETOOLS_VSN = 2.0.3
diff --git a/lib/public_key/asn1/OTP-PKIX.asn1 b/lib/public_key/asn1/OTP-PKIX.asn1
index 2bcacc0990..c0cf440496 100644
--- a/lib/public_key/asn1/OTP-PKIX.asn1
+++ b/lib/public_key/asn1/OTP-PKIX.asn1
@@ -313,7 +313,7 @@ SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
dsa-with-sha1 SIGNATURE-ALGORITHM-CLASS ::= {
ID id-dsa-with-sha1
- TYPE NULL } -- XXX Must be empty and not NULL
+ TYPE Dss-Parms }
--
-- RSA Keys and Signatures
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index a5fbc81093..13a9151869 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -34,6 +34,56 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 0.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Certificates without any extensions could not be handled
+ by public_key.</p>
+ <p>
+ Own Id: OTP-8626</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Code cleanup and minor bugfixes.</p>
+ <p>
+ Own Id: OTP-8649</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Public_Key 0.6</title>
+
+<section><title>Improvements and New Features</title>
+<list>
+ <item>
+ <p>
+ Support for Diffie-Hellman. ssl-3.11 requires
+ public_key-0.6.</p>
+ <p>
+ Own Id: OTP-7046</p>
+ </item>
+ <item>
+ <p>
+ Moved extended key usage test for ssl values to ssl.</p>
+ <p>
+ Own Id: OTP-8553 Aux Id: seq11541, OTP-8554 </p>
+ </item>
+</list>
+</section>
+
+</section>
+
<section><title>Public_Key 0.5</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/public_key/src/pubkey_cert.erl b/lib/public_key/src/pubkey_cert.erl
index 0ccc74799c..0651dcec29 100644
--- a/lib/public_key/src/pubkey_cert.erl
+++ b/lib/public_key/src/pubkey_cert.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -29,7 +29,7 @@
validate_issuer/4, validate_names/6,
validate_revoked_status/3, validate_extensions/4,
validate_unknown_extensions/3,
- normalize_general_name/1, digest_type/1, digest/2, is_self_signed/1,
+ normalize_general_name/1, digest_type/1, is_self_signed/1,
is_issuer/2, issuer_id/2, is_fixed_dh_cert/1]).
-define(NULL, 0).
@@ -130,13 +130,14 @@ validate_signature(OtpCert, DerCert, Key, KeyParams,
validate_names(OtpCert, Permit, Exclude, Last, AccErr, Verify) ->
case is_self_signed(OtpCert) andalso (not Last) of
true ->
- ok;
+ AccErr;
false ->
TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
Subject = TBSCert#'OTPTBSCertificate'.subject,
+ Extensions =
+ extensions_list(TBSCert#'OTPTBSCertificate'.extensions),
AltSubject =
- select_extension(?'id-ce-subjectAltName',
- TBSCert#'OTPTBSCertificate'.extensions),
+ select_extension(?'id-ce-subjectAltName', Extensions),
EmailAddress = extract_email(Subject),
Name = [{directoryName, Subject}|EmailAddress],
@@ -196,7 +197,7 @@ normalize_general_name({rdnSequence, Issuer}) ->
normalize_general_name(Issuer) ->
Normalize = fun([{Description, Type, {printableString, Value}}]) ->
NewValue = string:to_lower(strip_spaces(Value)),
- {Description, Type, {printableString, NewValue}};
+ [{Description, Type, {printableString, NewValue}}];
(Atter) ->
Atter
end,
@@ -212,7 +213,7 @@ is_issuer({rdnSequence, Issuer}, {rdnSequence, Candidate}) ->
issuer_id(Otpcert, other) ->
TBSCert = Otpcert#'OTPCertificate'.tbsCertificate,
- Extensions = TBSCert#'OTPTBSCertificate'.extensions,
+ Extensions = extensions_list(TBSCert#'OTPTBSCertificate'.extensions),
case select_extension(?'id-ce-authorityKeyIdentifier', Extensions) of
undefined ->
{error, issuer_not_found};
@@ -232,12 +233,17 @@ is_fixed_dh_cert(#'OTPCertificate'{tbsCertificate =
SubjectPublicKeyInfo,
extensions =
Extensions}}) ->
- is_fixed_dh_cert(SubjectPublicKeyInfo, Extensions).
+ is_fixed_dh_cert(SubjectPublicKeyInfo, extensions_list(Extensions)).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
+extensions_list(asn1_NOVALUE) ->
+ [];
+extensions_list(Extensions) ->
+ Extensions.
+
not_valid(Error, true, _) ->
throw(Error);
not_valid(Error, false, AccErrors) ->
@@ -269,13 +275,6 @@ digest_type(?md5WithRSAEncryption) ->
digest_type(?'id-dsa-with-sha1') ->
sha.
-digest(?sha1WithRSAEncryption, Msg) ->
- crypto:sha(Msg);
-digest(?md5WithRSAEncryption, Msg) ->
- crypto:md5(Msg);
-digest(?'id-dsa-with-sha1', Msg) ->
- crypto:sha(Msg).
-
public_key_info(PublicKeyInfo,
#path_validation_state{working_public_key_algorithm =
WorkingAlgorithm,
@@ -326,12 +325,6 @@ is_dir_name([[{'AttributeTypeAndValue', Type, What1}]|Rest1],
true -> is_dir_name(Rest1,Rest2,Exact);
false -> false
end;
-is_dir_name([{'AttributeTypeAndValue', Type, What1}|Rest1],
- [{'AttributeTypeAndValue', Type, What2}|Rest2], Exact) ->
- case is_dir_name2(What1,What2) of
- true -> is_dir_name(Rest1,Rest2,Exact);
- false -> false
- end;
is_dir_name(_,[],false) ->
true;
is_dir_name(_,_,_) ->
@@ -455,24 +448,6 @@ validate_extensions([#'Extension'{extnID = ?'id-ce-keyUsage',
AccErr)
end;
-validate_extensions([#'Extension'{extnID = ?'id-ce-extKeyUsage',
- extnValue = KeyUse,
- critical = true} | Rest],
- #path_validation_state{} = ValidationState,
- ExistBasicCon, SelfSigned, UnknownExtensions, Verify,
- AccErr0) ->
- case is_valid_extkey_usage(KeyUse) of
- true ->
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions,
- Verify, AccErr0);
- false ->
- AccErr =
- not_valid({bad_cert, invalid_ext_key_usage}, Verify, AccErr0),
- validate_extensions(Rest, ValidationState, ExistBasicCon,
- SelfSigned, UnknownExtensions, Verify, AccErr)
- end;
-
validate_extensions([#'Extension'{extnID = ?'id-ce-subjectAltName',
extnValue = Names} | Rest],
ValidationState, ExistBasicCon,
@@ -590,13 +565,6 @@ validate_extensions([Extension | Rest], ValidationState,
is_valid_key_usage(KeyUse, Use) ->
lists:member(Use, KeyUse).
-is_valid_extkey_usage(?'id-kp-clientAuth') ->
- true;
-is_valid_extkey_usage(?'id-kp-serverAuth') ->
- true;
-is_valid_extkey_usage(_) ->
- false.
-
validate_subject_alt_names([]) ->
true;
validate_subject_alt_names([AltName | Rest]) ->
diff --git a/lib/public_key/src/pubkey_cert_records.erl b/lib/public_key/src/pubkey_cert_records.erl
index c7d4080adb..ac04e1c2cb 100644
--- a/lib/public_key/src/pubkey_cert_records.erl
+++ b/lib/public_key/src/pubkey_cert_records.erl
@@ -25,8 +25,6 @@
-export([decode_cert/2, encode_cert/1, encode_tbs_cert/1, transform/2]).
--export([old_decode_cert/2, old_encode_cert/1]). %% Debugging and testing new code.
-
%%====================================================================
%% Internal application API
%%====================================================================
@@ -35,77 +33,25 @@ decode_cert(DerCert, plain) ->
'OTP-PUB-KEY':decode('Certificate', DerCert);
decode_cert(DerCert, otp) ->
{ok, Cert} = 'OTP-PUB-KEY':decode('OTPCertificate', DerCert),
- {ok, decode_all_otp(Cert)}.
-
-old_decode_cert(DerCert, otp) ->
- {ok, Cert} = 'OTP-PUB-KEY':decode('Certificate', DerCert),
- {ok, plain_to_otp(Cert)}.
-
-old_encode_cert(Cert) ->
- PlainCert = otp_to_plain(Cert),
- {ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', PlainCert),
- list_to_binary(EncCert).
-
+ #'OTPCertificate'{tbsCertificate = TBS} = Cert,
+ {ok, Cert#'OTPCertificate'{tbsCertificate = decode_tbs(TBS)}}.
encode_cert(Cert = #'Certificate'{}) ->
{ok, EncCert} = 'OTP-PUB-KEY':encode('Certificate', Cert),
list_to_binary(EncCert);
-encode_cert(C = #'OTPCertificate'{tbsCertificate = TBS =
- #'OTPTBSCertificate'{
- issuer=Issuer0,
- subject=Subject0,
- subjectPublicKeyInfo=Spki0,
- extensions=Exts0}
- }) ->
- Issuer = transform(Issuer0,encode),
- Subject = transform(Subject0,encode),
- Spki = encode_supportedPublicKey(Spki0),
- Exts = encode_extensions(Exts0),
- %% io:format("Extensions ~p~n",[Exts]),
- Cert = C#'OTPCertificate'{tbsCertificate=
- TBS#'OTPTBSCertificate'{
- issuer=Issuer, subject=Subject,
- subjectPublicKeyInfo=Spki,
- extensions=Exts}},
+encode_cert(C = #'OTPCertificate'{tbsCertificate = TBS}) ->
+ Cert = C#'OTPCertificate'{tbsCertificate=encode_tbs(TBS)},
{ok, EncCert} = 'OTP-PUB-KEY':encode('OTPCertificate', Cert),
list_to_binary(EncCert).
-encode_tbs_cert(TBS = #'OTPTBSCertificate'{
- issuer=Issuer0,
- subject=Subject0,
- subjectPublicKeyInfo=Spki0,
- extensions=Exts0}) ->
- Issuer = transform(Issuer0,encode),
- Subject = transform(Subject0,encode),
- Spki = encode_supportedPublicKey(Spki0),
- Exts = encode_extensions(Exts0),
- TBSCert = TBS#'OTPTBSCertificate'{issuer=Issuer,subject=Subject,
- subjectPublicKeyInfo=Spki,extensions=Exts},
- {ok, EncTBSCert} = 'OTP-PUB-KEY':encode('OTPTBSCertificate', TBSCert),
+encode_tbs_cert(TBS) ->
+ {ok, EncTBSCert} = 'OTP-PUB-KEY':encode('OTPTBSCertificate', encode_tbs(TBS)),
list_to_binary(EncTBSCert).
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-decode_all_otp(C = #'OTPCertificate'{tbsCertificate = TBS =
- #'OTPTBSCertificate'{
- issuer=Issuer0,
- subject=Subject0,
- subjectPublicKeyInfo=Spki0,
- extensions=Exts0}
- }) ->
- Issuer = transform(Issuer0,decode),
- Subject = transform(Subject0,decode),
- Spki = decode_supportedPublicKey(Spki0),
- Exts = decode_extensions(Exts0),
- %% io:format("Extensions ~p~n",[Exts]),
- C#'OTPCertificate'{tbsCertificate=
- TBS#'OTPTBSCertificate'{
- issuer=Issuer, subject=Subject,
- subjectPublicKeyInfo=Spki,extensions=Exts}}.
-
-
%%% SubjectPublicKey
supportedPublicKeyAlgorithms(?'rsaEncryption') -> 'RSAPublicKey';
supportedPublicKeyAlgorithms(?'id-dsa') -> 'DSAPublicKey';
@@ -204,15 +150,35 @@ transform({rdnSequence, SeqList},Func) when is_list(SeqList) ->
lists:map(fun(Seq) ->
lists:map(fun(Element) -> transform(Element,Func) end, Seq)
end, SeqList)};
-%% transform(List = [{rdnSequence, _}|_],Func) ->
-%% lists:map(fun(Element) -> transform(Element,Func) end, List);
transform(#'NameConstraints'{permittedSubtrees=Permitted, excludedSubtrees=Excluded}, Func) ->
- Res = #'NameConstraints'{permittedSubtrees=transform_sub_tree(Permitted,Func),
- excludedSubtrees=transform_sub_tree(Excluded,Func)},
-%% io:format("~p~n",[Res]),
- Res;
+ #'NameConstraints'{permittedSubtrees=transform_sub_tree(Permitted,Func),
+ excludedSubtrees=transform_sub_tree(Excluded,Func)};
+
transform(Other,_) ->
Other.
+
+encode_tbs(TBS=#'OTPTBSCertificate'{issuer=Issuer0,
+ subject=Subject0,
+ subjectPublicKeyInfo=Spki0,
+ extensions=Exts0}) ->
+ Issuer = transform(Issuer0,encode),
+ Subject = transform(Subject0,encode),
+ Spki = encode_supportedPublicKey(Spki0),
+ Exts = encode_extensions(Exts0),
+ TBS#'OTPTBSCertificate'{issuer=Issuer, subject=Subject,
+ subjectPublicKeyInfo=Spki,extensions=Exts}.
+
+decode_tbs(TBS = #'OTPTBSCertificate'{issuer=Issuer0,
+ subject=Subject0,
+ subjectPublicKeyInfo=Spki0,
+ extensions=Exts0}) ->
+ Issuer = transform(Issuer0,decode),
+ Subject = transform(Subject0,decode),
+ Spki = decode_supportedPublicKey(Spki0),
+ Exts = decode_extensions(Exts0),
+ TBS#'OTPTBSCertificate'{issuer=Issuer, subject=Subject,
+ subjectPublicKeyInfo=Spki,extensions=Exts}.
+
transform_sub_tree(asn1_NOVALUE,_) -> asn1_NOVALUE;
transform_sub_tree(TreeList,Func) ->
[Tree#'GeneralSubtree'{base=transform(Name,Func)} ||
@@ -236,303 +202,3 @@ attribute_type(?'id-at-pseudonym') -> 'X520Pseudonym';
attribute_type(?'id-domainComponent') -> 'DomainComponent';
attribute_type(?'id-emailAddress') -> 'EmailAddress';
attribute_type(Type) -> Type.
-
-%%% Old code transforms
-
-plain_to_otp(#'Certificate'{tbsCertificate = TBSCert,
- signatureAlgorithm = SigAlg,
- signature = Signature} = Cert) ->
- Cert#'Certificate'{tbsCertificate = plain_to_otp(TBSCert),
- signatureAlgorithm = plain_to_otp(SigAlg),
- signature = plain_to_otp(Signature)};
-
-plain_to_otp(#'TBSCertificate'{signature = Signature,
- issuer = Issuer,
- subject = Subject,
- subjectPublicKeyInfo = SPubKeyInfo,
- extensions = Extensions} = TBSCert) ->
-
- TBSCert#'TBSCertificate'{signature = plain_to_otp(Signature),
- issuer = plain_to_otp(Issuer),
- subject =
- plain_to_otp(Subject),
- subjectPublicKeyInfo =
- plain_to_otp(SPubKeyInfo),
- extensions =
- plain_to_otp_extensions(Extensions)
- };
-
-plain_to_otp(#'AlgorithmIdentifier'{algorithm = Algorithm,
- parameters = Params}) ->
- SignAlgAny =
- #'SignatureAlgorithm-Any'{algorithm = Algorithm,
- parameters = Params},
- {ok, AnyEnc} = 'OTP-PUB-KEY':encode('SignatureAlgorithm-Any',
- SignAlgAny),
- {ok, SignAlg} = 'OTP-PUB-KEY':decode('SignatureAlgorithm',
- list_to_binary(AnyEnc)),
- SignAlg;
-
-plain_to_otp({rdnSequence, SeqList}) when is_list(SeqList) ->
- {rdnSequence,
- lists:map(fun(Seq) ->
- lists:map(fun(Element) ->
- plain_to_otp(Element)
- end,
- Seq)
- end, SeqList)};
-
-plain_to_otp(#'AttributeTypeAndValue'{} = ATAV) ->
- {ok, ATAVEnc} =
- 'OTP-PUB-KEY':encode('AttributeTypeAndValue', ATAV),
- {ok, ATAVDec} = 'OTP-PUB-KEY':decode('OTPAttributeTypeAndValue',
- list_to_binary(ATAVEnc)),
- #'AttributeTypeAndValue'{type = ATAVDec#'OTPAttributeTypeAndValue'.type,
- value =
- ATAVDec#'OTPAttributeTypeAndValue'.value};
-
-plain_to_otp(#'SubjectPublicKeyInfo'{algorithm =
- #'AlgorithmIdentifier'{algorithm
- = Algo,
- parameters =
- Params},
- subjectPublicKey = PublicKey}) ->
-
- AnyAlgo = #'PublicKeyAlgorithm'{algorithm = Algo,
- parameters = Params},
- {0, AnyKey} = PublicKey,
- AnyDec = #'OTPSubjectPublicKeyInfo-Any'{algorithm = AnyAlgo,
- subjectPublicKey = AnyKey},
- {ok, AnyEnc} =
- 'OTP-PUB-KEY':encode('OTPSubjectPublicKeyInfo-Any', AnyDec),
- {ok, InfoDec} = 'OTP-PUB-KEY':decode('OTPOLDSubjectPublicKeyInfo',
- list_to_binary(AnyEnc)),
-
- AlgorithmDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.algorithm,
- AlgoDec = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.algo,
- NewParams = AlgorithmDec#'OTPOLDSubjectPublicKeyInfo_algorithm'.parameters,
- PublicKeyDec = InfoDec#'OTPOLDSubjectPublicKeyInfo'.subjectPublicKey,
- NewAlgorithmDec =
- #'SubjectPublicKeyInfoAlgorithm'{algorithm = AlgoDec,
- parameters = NewParams},
- #'SubjectPublicKeyInfo'{algorithm = NewAlgorithmDec,
- subjectPublicKey = PublicKeyDec
- };
-
-plain_to_otp(#'Extension'{extnID = ExtID,
- critical = Critical,
- extnValue = Value})
- when ExtID == ?'id-ce-authorityKeyIdentifier';
- ExtID == ?'id-ce-subjectKeyIdentifier';
- ExtID == ?'id-ce-keyUsage';
- ExtID == ?'id-ce-privateKeyUsagePeriod';
- ExtID == ?'id-ce-certificatePolicies';
- ExtID == ?'id-ce-policyMappings';
- ExtID == ?'id-ce-subjectAltName';
- ExtID == ?'id-ce-issuerAltName';
- ExtID == ?'id-ce-subjectDirectoryAttributes';
- ExtID == ?'id-ce-basicConstraints';
- ExtID == ?'id-ce-nameConstraints';
- ExtID == ?'id-ce-policyConstraints';
- ExtID == ?'id-ce-extKeyUsage';
- ExtID == ?'id-ce-cRLDistributionPoints';
- ExtID == ?'id-ce-inhibitAnyPolicy';
- ExtID == ?'id-ce-freshestCRL' ->
- ExtAny = #'Extension-Any'{extnID = ExtID,
- critical = Critical,
- extnValue = Value},
- {ok, AnyEnc} = 'OTP-PUB-KEY':encode('Extension-Any', ExtAny),
- {ok, ExtDec} = 'OTP-PUB-KEY':decode('OTPExtension',
- list_to_binary(AnyEnc)),
-
- ExtValue = plain_to_otp_extension_value(ExtID,
- ExtDec#'OTPExtension'.extnValue),
- #'Extension'{extnID = ExtID,
- critical = ExtDec#'OTPExtension'.critical,
- extnValue = ExtValue};
-
-plain_to_otp(#'Extension'{} = Ext) ->
- Ext;
-
-plain_to_otp(#'AuthorityKeyIdentifier'{} = Ext) ->
- CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer,
- Ext#'AuthorityKeyIdentifier'{authorityCertIssuer =
- plain_to_otp(CertIssuer)};
-
-
-plain_to_otp([{directoryName, Value}]) ->
- [{directoryName, plain_to_otp(Value)}];
-
-plain_to_otp(Value) ->
- Value.
-
-otp_to_plain(#'Certificate'{tbsCertificate = TBSCert,
- signatureAlgorithm = SigAlg,
- signature = Signature} = Cert) ->
- Cert#'Certificate'{tbsCertificate = otp_to_plain(TBSCert),
- signatureAlgorithm =
- otp_to_plain(SigAlg),
- signature = otp_to_plain(Signature)};
-
-otp_to_plain(#'TBSCertificate'{signature = Signature,
- issuer = Issuer,
- subject = Subject,
- subjectPublicKeyInfo = SPubKeyInfo,
- extensions = Extensions} = TBSCert) ->
-
- TBSCert#'TBSCertificate'{signature = otp_to_plain(Signature),
- issuer = otp_to_plain(Issuer),
- subject =
- otp_to_plain(Subject),
- subjectPublicKeyInfo =
- otp_to_plain(SPubKeyInfo),
- extensions = otp_to_plain_extensions(Extensions)
- };
-
-otp_to_plain(#'SignatureAlgorithm'{} = SignAlg) ->
- {ok, EncSignAlg} = 'OTP-PUB-KEY':encode('SignatureAlgorithm', SignAlg),
- {ok, #'SignatureAlgorithm-Any'{algorithm = Algorithm,
- parameters = Params}} =
- 'OTP-PUB-KEY':decode('SignatureAlgorithm-Any',
- list_to_binary(EncSignAlg)),
- #'AlgorithmIdentifier'{algorithm = Algorithm,
- parameters = Params};
-
-otp_to_plain({rdnSequence, SeqList}) when is_list(SeqList) ->
- {rdnSequence,
- lists:map(fun(Seq) ->
- lists:map(fun(Element) ->
- otp_to_plain(Element)
- end,
- Seq)
- end, SeqList)};
-
-otp_to_plain(#'AttributeTypeAndValue'{type = Type, value = Value}) ->
- {ok, ATAVEnc} =
- 'OTP-PUB-KEY':encode('OTPAttributeTypeAndValue',
- #'OTPAttributeTypeAndValue'{type = Type,
- value = Value}),
- {ok, ATAVDec} = 'OTP-PUB-KEY':decode('AttributeTypeAndValue',
- list_to_binary(ATAVEnc)),
- ATAVDec;
-
-otp_to_plain(#'SubjectPublicKeyInfo'{algorithm =
- #'SubjectPublicKeyInfoAlgorithm'{
- algorithm = Algo,
- parameters =
- Params},
- subjectPublicKey = PublicKey}) ->
-
- OtpAlgo = #'OTPOLDSubjectPublicKeyInfo_algorithm'{algo = Algo,
- parameters = Params},
- OtpDec = #'OTPOLDSubjectPublicKeyInfo'{algorithm = OtpAlgo,
- subjectPublicKey = PublicKey},
- {ok, OtpEnc} =
- 'OTP-PUB-KEY':encode('OTPOLDSubjectPublicKeyInfo', OtpDec),
-
- {ok, AnyDec} = 'OTP-PUB-KEY':decode('OTPSubjectPublicKeyInfo-Any',
- list_to_binary(OtpEnc)),
-
- #'OTPSubjectPublicKeyInfo-Any'{algorithm = #'PublicKeyAlgorithm'{
- algorithm = NewAlgo,
- parameters = NewParams},
- subjectPublicKey = Bin} = AnyDec,
-
- #'SubjectPublicKeyInfo'{algorithm =
- #'AlgorithmIdentifier'{
- algorithm = NewAlgo,
- parameters = plain_key_params(NewParams)},
- subjectPublicKey =
- {0, Bin}
- };
-
-otp_to_plain(#'Extension'{extnID = ExtID,
- extnValue = Value} = Ext) ->
- ExtValue =
- otp_to_plain_extension_value(ExtID, Value),
-
- Ext#'Extension'{extnValue = ExtValue};
-
-otp_to_plain(#'AuthorityKeyIdentifier'{} = Ext) ->
- CertIssuer = Ext#'AuthorityKeyIdentifier'.authorityCertIssuer,
- Ext#'AuthorityKeyIdentifier'{authorityCertIssuer =
- otp_to_plain(CertIssuer)};
-
-otp_to_plain([{directoryName, Value}]) ->
- [{directoryName, otp_to_plain(Value)}];
-
-otp_to_plain(Value) ->
- Value.
-
-plain_key_params('NULL') ->
- <<5,0>>;
-plain_key_params(Value) ->
- Value.
-
-plain_to_otp_extension_value(?'id-ce-authorityKeyIdentifier', Value) ->
- plain_to_otp(Value);
-plain_to_otp_extension_value(_, Value) ->
- Value.
-
-plain_to_otp_extensions(Exts) when is_list(Exts) ->
- lists:map(fun(Ext) -> plain_to_otp(Ext) end, Exts).
-
-otp_to_plain_extension_value(?'id-ce-authorityKeyIdentifier', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('AuthorityKeyIdentifier',
- otp_to_plain(Value)),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-subjectKeyIdentifier', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectKeyIdentifier', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-keyUsage', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('KeyUsage', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-privateKeyUsagePeriod', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('PrivateKeyUsagePeriod', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-certificatePolicies', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('CertificatePolicies', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-policyMappings', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyMappings', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-subjectAltName', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectAltName', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-issuerAltName', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('IssuerAltName', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-subjectDirectoryAttributes', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('SubjectDirectoryAttributes', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-basicConstraints', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('BasicConstraints', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-nameConstraints', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('NameConstraints', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-policyConstraints', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('PolicyConstraints', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-extKeyUsage', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('ExtKeyUsage', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-cRLDistributionPoints', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('CRLDistributionPoints', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-inhibitAnyPolicy', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('InhibitAnyPolicy', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(?'id-ce-freshestCRL', Value) ->
- {ok, Enc} = 'OTP-PUB-KEY':encode('FreshestCRL', Value),
- otp_to_plain_extension_value_format(Enc);
-otp_to_plain_extension_value(_Id, Value) ->
- Value.
-
-otp_to_plain_extension_value_format(Value) ->
- list_to_binary(Value).
-
-otp_to_plain_extensions(Exts) when is_list(Exts) ->
- lists:map(fun(Ext) ->
- otp_to_plain(Ext)
- end, Exts).
diff --git a/lib/public_key/src/pubkey_crypto.erl b/lib/public_key/src/pubkey_crypto.erl
index 4ab655e977..7b7abb1c56 100644
--- a/lib/public_key/src/pubkey_crypto.erl
+++ b/lib/public_key/src/pubkey_crypto.erl
@@ -106,6 +106,11 @@ sign(DigestType, PlainText, #'RSAPrivateKey'{modulus = N, publicExponent = E,
crypto:mpint(N),
crypto:mpint(D)]);
+sign(none, Hash, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) ->
+ crypto:dss_sign(none, Hash,
+ [crypto:mpint(P), crypto:mpint(Q),
+ crypto:mpint(G), crypto:mpint(X)]);
+
sign(sha, PlainText, #'DSAPrivateKey'{p = P, q = Q, g = G, x = X}) ->
crypto:dss_sign(sized_binary(PlainText),
[crypto:mpint(P), crypto:mpint(Q),
@@ -128,6 +133,12 @@ verify(DigestType, PlainText, Signature,
sized_binary(Signature),
[crypto:mpint(Exp), crypto:mpint(Mod)]);
+verify(none, Hash, Signature, Key, #'Dss-Parms'{p = P, q = Q, g = G}) ->
+ crypto:dss_verify(none, Hash,
+ sized_binary(Signature),
+ [crypto:mpint(P), crypto:mpint(Q),
+ crypto:mpint(G), crypto:mpint(Key)]);
+
verify(sha, PlainText, Signature, Key, #'Dss-Parms'{p = P, q = Q, g = G}) ->
crypto:dss_verify(sized_binary(PlainText),
sized_binary(Signature),
diff --git a/lib/public_key/src/pubkey_pem.erl b/lib/public_key/src/pubkey_pem.erl
index abd46fa00e..65879f1bbe 100644
--- a/lib/public_key/src/pubkey_pem.erl
+++ b/lib/public_key/src/pubkey_pem.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -124,25 +124,31 @@ decode_file2([L|Rest], RLs, Ens, Tag, Info0) ->
decode_file2([], _, Ens, _, _) ->
{ok, lists:reverse(Ens)}.
-%% TODO Support same as decode_file
+%% Support same as decode_file
encode_file(Ds) ->
lists:map(
- fun({cert, Bin}) ->
+ fun({cert, Bin, not_encrypted}) ->
%% PKIX (X.509)
["-----BEGIN CERTIFICATE-----\n",
b64encode_and_split(Bin),
"-----END CERTIFICATE-----\n\n"];
- ({cert_req, Bin}) ->
+ ({cert_req, Bin, not_encrypted}) ->
%% PKCS#10
["-----BEGIN CERTIFICATE REQUEST-----\n",
b64encode_and_split(Bin),
"-----END CERTIFICATE REQUEST-----\n\n"];
- ({rsa_private_key, Bin}) ->
+ ({rsa_private_key, Bin, not_encrypted}) ->
%% PKCS#?
["XXX Following key assumed not encrypted\n",
"-----BEGIN RSA PRIVATE KEY-----\n",
b64encode_and_split(Bin),
- "-----END RSA PRIVATE KEY-----\n\n"]
+ "-----END RSA PRIVATE KEY-----\n\n"];
+ ({dsa_private_key, Bin, not_encrypted}) ->
+ %% PKCS#?
+ ["XXX Following key assumed not encrypted\n",
+ "-----BEGIN DSA PRIVATE KEY-----\n",
+ b64encode_and_split(Bin),
+ "-----END DSA PRIVATE KEY-----\n\n"]
end, Ds).
dek_info(Line0, Info) ->
@@ -155,7 +161,7 @@ unhex(S) ->
unhex(S, []).
unhex("", Acc) ->
- lists:reverse(Acc);
+ list_to_binary(lists:reverse(Acc));
unhex([D1, D2 | Rest], Acc) ->
unhex(Rest, [erlang:list_to_integer([D1, D2], 16) | Acc]).
diff --git a/lib/public_key/src/public_key.app.src b/lib/public_key/src/public_key.app.src
index edede7c874..d5e1705827 100644
--- a/lib/public_key/src/public_key.app.src
+++ b/lib/public_key/src/public_key.app.src
@@ -13,4 +13,5 @@
{registered, []},
{env, []}
]
-}. \ No newline at end of file
+}.
+
diff --git a/lib/public_key/src/public_key.appup.src b/lib/public_key/src/public_key.appup.src
index ee0f9a3cc1..2eb5750923 100644
--- a/lib/public_key/src/public_key.appup.src
+++ b/lib/public_key/src/public_key.appup.src
@@ -1,18 +1,44 @@
%% -*- erlang -*-
{"%VSN%",
[
- {"0.4",
+ {"0.6",
[
+ {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []},
{update, public_key, soft, soft_purge, soft_purge, []},
+ {update, pubkey_pem, soft, soft_purge, soft_purge, []},
{update, pubkey_cert_records, soft, soft_purge, soft_purge, []}
- ]
+ {update, pubkey_cert, soft, soft_purge, soft_purge, []}
+ ]
+ },
+ {"0.5",
+ [
+ {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []},
+ {update, public_key, soft, soft_purge, soft_purge, []},
+ {update, pubkey_crypto, soft, soft_purge, soft_purge, []},
+ {update, pubkey_pem, soft, soft_purge, soft_purge, []},
+ {update, pubkey_cert_records, soft, soft_purge, soft_purge, []},
+ {update, pubkey_cert, soft, soft_purge, soft_purge, []}
+ ]
}
],
[
- {"0.4",
+ {"0.6",
[
+ {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []},
{update, public_key, soft, soft_purge, soft_purge, []},
+ {update, pubkey_pem, soft, soft_purge, soft_purge, []},
{update, pubkey_cert_records, soft, soft_purge, soft_purge, []}
+ {update, pubkey_cert, soft, soft_purge, soft_purge, []}
+ ]
+ },
+ {"0.5",
+ [
+ {update, 'OTP-PUB-KEY', soft, soft_purge, soft_purge, []},
+ {update, public_key, soft, soft_purge, soft_purge, []},
+ {update, pubkey_crypto, soft, soft_purge, soft_purge, []},
+ {update, pubkey_pem, soft, soft_purge, soft_purge, []},
+ {update, pubkey_cert_records, soft, soft_purge, soft_purge, []},
+ {update, pubkey_cert, soft, soft_purge, soft_purge, []}
]
}
]}.
diff --git a/lib/public_key/src/public_key.erl b/lib/public_key/src/public_key.erl
index 9a90ffe888..12354eee5d 100644
--- a/lib/public_key/src/public_key.erl
+++ b/lib/public_key/src/public_key.erl
@@ -28,7 +28,7 @@
encrypt_public/3, decrypt_public/2, decrypt_public/3,
encrypt_private/2, encrypt_private/3, gen_key/1, sign/2, sign/3,
verify_signature/3, verify_signature/4, verify_signature/5,
- pem_to_der/1, pem_to_der/2,
+ pem_to_der/1, pem_to_der/2, der_to_pem/2,
pkix_decode_cert/2, pkix_encode_cert/1, pkix_transform/2,
pkix_is_self_signed/1, pkix_is_fixed_dh_cert/1,
pkix_issuer_id/2,
@@ -113,13 +113,13 @@ decrypt_public(CipherText, Key, Options) ->
encrypt_public(PlainText, Key) ->
encrypt_public(PlainText, Key, []).
encrypt_public(PlainText, Key, Options) ->
- Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding),
+ Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding),
pubkey_crypto:encrypt_public(PlainText, Key, Padding).
encrypt_private(PlainText, Key) ->
encrypt_private(PlainText, Key, []).
encrypt_private(PlainText, Key, Options) ->
- Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_oaep_padding),
+ Padding = proplists:get_value(rsa_pad, Options, rsa_pkcs1_padding),
pubkey_crypto:encrypt_private(PlainText, Key, Padding).
%%--------------------------------------------------------------------
@@ -163,6 +163,10 @@ pem_to_der(File, Password) when is_list(File) ->
pubkey_pem:read_file(File, Password);
pem_to_der(PemBin, Password) when is_binary(PemBin) ->
pubkey_pem:decode(PemBin, Password).
+
+der_to_pem(File, TypeDerList) ->
+ pubkey_pem:write_file(File, TypeDerList).
+
%%--------------------------------------------------------------------
%% Function: pkix_decode_cert(BerCert, Type) -> {ok, Cert} | {error, Reason}
%%
@@ -314,9 +318,10 @@ sign(Msg, #'RSAPrivateKey'{} = Key) when is_binary(Msg) ->
sign(Msg, #'DSAPrivateKey'{} = Key) when is_binary(Msg) ->
pubkey_crypto:sign(Msg, Key);
-sign(#'OTPTBSCertificate'{signature = SigAlg} = TBSCert, Key) ->
+sign(#'OTPTBSCertificate'{signature = #'SignatureAlgorithm'{algorithm = Alg}
+ = SigAlg} = TBSCert, Key) ->
Msg = pubkey_cert_records:encode_tbs_cert(TBSCert),
- DigestType = pubkey_cert:digest_type(SigAlg),
+ DigestType = pubkey_cert:digest_type(Alg),
Signature = pubkey_crypto:sign(DigestType, Msg, Key),
Cert = #'OTPCertificate'{tbsCertificate= TBSCert,
signatureAlgorithm = SigAlg,
@@ -355,7 +360,9 @@ verify_signature(PlainText, DigestType, Signature, #'RSAPublicKey'{} = Key,
pubkey_crypto:verify(DigestType, PlainText, Signature, Key, KeyParams);
verify_signature(PlainText, sha, Signature, Key, #'Dss-Parms'{} = KeyParams)
when is_binary(PlainText), is_binary(Signature), is_integer(Key) ->
- pubkey_crypto:verify(sha, PlainText, Signature, Key, KeyParams).
+ pubkey_crypto:verify(sha, PlainText, Signature, Key, KeyParams);
+verify_signature(Hash, none, Signature, Key, KeyParams) ->
+ pubkey_crypto:verify(none, Hash, Signature, Key, KeyParams).
verify_signature(DerCert, Key, #'Dss-Parms'{} = KeyParams)
when is_binary(DerCert), is_integer(Key) ->
diff --git a/lib/public_key/test/Makefile b/lib/public_key/test/Makefile
index c7215020c7..5544339ff2 100644
--- a/lib/public_key/test/Makefile
+++ b/lib/public_key/test/Makefile
@@ -28,6 +28,7 @@ INCLUDES= -I. -I ../include
# ----------------------------------------------------
MODULES= \
+ pkey_test \
public_key_SUITE \
pkits_SUITE
@@ -40,6 +41,9 @@ TARGET_FILES= \
SPEC_FILES = public_key.spec
+COVER_FILE = public_key.cover
+
+
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
@@ -74,7 +78,7 @@ release_spec: opt
release_tests_spec: opt
$(INSTALL_DIR) $(RELSYSDIR)
- $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(HRL_FILES)$(RELSYSDIR)
+ $(INSTALL_DATA) $(SPEC_FILES) $(ERL_FILES) $(COVER_FILE) $(HRL_FILES) $(RELSYSDIR)
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)
chmod -f -R u+w $(RELSYSDIR)
@tar cf - *_SUITE_data | (cd $(RELSYSDIR); tar xf -)
diff --git a/lib/public_key/test/pkey_test.erl b/lib/public_key/test/pkey_test.erl
new file mode 100644
index 0000000000..4cf20f0174
--- /dev/null
+++ b/lib/public_key/test/pkey_test.erl
@@ -0,0 +1,412 @@
+%%
+%% %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%
+%%
+
+%% Create test certificates
+
+-module(pkey_test).
+-include_lib("public_key/include/public_key.hrl").
+
+-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]).
+-compile(export_all).
+
+%%--------------------------------------------------------------------
+%% @doc Create and return a der encoded certificate
+%% Option Default
+%% -------------------------------------------------------
+%% digest sha1
+%% validity {date(), date() + week()}
+%% version 3
+%% subject [] list of the following content
+%% {name, Name}
+%% {email, Email}
+%% {city, City}
+%% {state, State}
+%% {org, Org}
+%% {org_unit, OrgUnit}
+%% {country, Country}
+%% {serial, Serial}
+%% {title, Title}
+%% {dnQualifer, DnQ}
+%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created)
+%% (obs IssuerKey migth be {Key, Password}
+%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key
+%%
+%%
+%% (OBS: The generated keys are for testing only)
+%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()}
+%% @end
+%%--------------------------------------------------------------------
+
+make_cert(Opts) ->
+ SubjectPrivateKey = get_key(Opts),
+ {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts),
+ Cert = public_key:sign(TBSCert, IssuerKey),
+ true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok
+ {Cert, encode_key(SubjectPrivateKey)}.
+
+%%--------------------------------------------------------------------
+%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem"
+%% @spec (::string(), ::string(), {Cert,Key}) -> ok
+%% @end
+%%--------------------------------------------------------------------
+write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) ->
+ ok = public_key:der_to_pem(filename:join(Dir, FileName ++ ".pem"), [{cert, Cert, not_encrypted}]),
+ ok = public_key:der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]).
+
+%%--------------------------------------------------------------------
+%% @doc Creates a rsa key (OBS: for testing only)
+%% the size are in bytes
+%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()}
+%% @end
+%%--------------------------------------------------------------------
+gen_rsa(Size) when is_integer(Size) ->
+ Key = gen_rsa2(Size),
+ {Key, encode_key(Key)}.
+
+%%--------------------------------------------------------------------
+%% @doc Creates a dsa key (OBS: for testing only)
+%% the sizes are in bytes
+%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()}
+%% @end
+%%--------------------------------------------------------------------
+gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) ->
+ Key = gen_dsa2(LSize, NSize),
+ {Key, encode_key(Key)}.
+
+%%--------------------------------------------------------------------
+%% @doc Verifies cert signatures
+%% @spec (::binary(), ::tuple()) -> ::boolean()
+%% @end
+%%--------------------------------------------------------------------
+verify_signature(DerEncodedCert, DerKey, KeyParams) ->
+ Key = decode_key(DerKey),
+ case Key of
+ #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} ->
+ public_key:verify_signature(DerEncodedCert,
+ #'RSAPublicKey'{modulus=Mod, publicExponent=Exp},
+ 'NULL');
+ #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} ->
+ public_key:verify_signature(DerEncodedCert, Y, #'Dss-Parms'{p=P, q=Q, g=G});
+
+ _ ->
+ public_key:verify_signature(DerEncodedCert, Key, KeyParams)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_key(Opts) ->
+ case proplists:get_value(key, Opts) of
+ undefined -> make_key(rsa, Opts);
+ rsa -> make_key(rsa, Opts);
+ dsa -> make_key(dsa, Opts);
+ Key ->
+ Password = proplists:get_value(password, Opts, no_passwd),
+ decode_key(Key, Password)
+ end.
+
+decode_key({Key, Pw}) ->
+ decode_key(Key, Pw);
+decode_key(Key) ->
+ decode_key(Key, no_passwd).
+
+
+decode_key(#'RSAPublicKey'{} = Key,_) ->
+ Key;
+decode_key(#'RSAPrivateKey'{} = Key,_) ->
+ Key;
+decode_key(#'DSAPrivateKey'{} = Key,_) ->
+ Key;
+decode_key(Der = {_,_,_}, Pw) ->
+ {ok, Key} = public_key:decode_private_key(Der, Pw),
+ Key;
+decode_key(FileOrDer, Pw) ->
+ {ok, [KeyInfo]} = public_key:pem_to_der(FileOrDer),
+ decode_key(KeyInfo, Pw).
+
+encode_key(Key = #'RSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key),
+ {rsa_private_key, list_to_binary(Der), not_encrypted};
+encode_key(Key = #'DSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key),
+ {dsa_private_key, list_to_binary(Der), not_encrypted}.
+
+make_tbs(SubjectKey, Opts) ->
+ Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))),
+ {Issuer, IssuerKey} = issuer(Opts, SubjectKey),
+
+ {Algo, Parameters} = sign_algorithm(IssuerKey, Opts),
+
+ SignAlgo = #'SignatureAlgorithm'{algorithm = Algo,
+ parameters = Parameters},
+
+ {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1,
+ signature = SignAlgo,
+ issuer = Issuer,
+ validity = validity(Opts),
+ subject = subject(proplists:get_value(subject, Opts),false),
+ subjectPublicKeyInfo = publickey(SubjectKey),
+ version = Version,
+ extensions = extensions(Opts)
+ }, IssuerKey}.
+
+issuer(Opts, SubjectKey) ->
+ IssuerProp = proplists:get_value(issuer, Opts, true),
+ case IssuerProp of
+ true -> %% Self signed
+ {subject(proplists:get_value(subject, Opts), true), SubjectKey};
+ {Issuer, IssuerKey} when is_binary(Issuer) ->
+ {issuer_der(Issuer), decode_key(IssuerKey)};
+ {File, IssuerKey} when is_list(File) ->
+ {ok, [{cert, Cert, _}|_]} = public_key:pem_to_der(File),
+ {issuer_der(Cert), decode_key(IssuerKey)}
+ end.
+
+issuer_der(Issuer) ->
+ {ok, Decoded} = public_key:pkix_decode_cert(Issuer, otp),
+ #'OTPCertificate'{tbsCertificate=Tbs} = Decoded,
+ #'OTPTBSCertificate'{subject=Subject} = Tbs,
+ Subject.
+
+subject(undefined, IsCA) ->
+ User = if IsCA -> "CA"; true -> os:getenv("USER") end,
+ Opts = [{email, User ++ "@erlang.org"},
+ {name, User},
+ {city, "Stockholm"},
+ {country, "SE"},
+ {org, "erlang"},
+ {org_unit, "testing dep"}],
+ subject(Opts);
+subject(Opts, _) ->
+ subject(Opts).
+
+subject(SubjectOpts) when is_list(SubjectOpts) ->
+ Encode = fun(Opt) ->
+ {Type,Value} = subject_enc(Opt),
+ [#'AttributeTypeAndValue'{type=Type, value=Value}]
+ end,
+ {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}.
+
+%% Fill in the blanks
+subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}};
+subject_enc({email, Email}) -> {?'id-emailAddress', Email};
+subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}};
+subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}};
+subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}};
+subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}};
+subject_enc({country, Country}) -> {?'id-at-countryName', Country};
+subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial};
+subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}};
+subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ};
+subject_enc(Other) -> Other.
+
+
+extensions(Opts) ->
+ case proplists:get_value(extensions, Opts, []) of
+ false ->
+ asn1_NOVALUE;
+ Exts ->
+ lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)])
+ end.
+
+default_extensions(Exts) ->
+ Def = [{key_usage,undefined},
+ {subject_altname, undefined},
+ {issuer_altname, undefined},
+ {basic_constraints, default},
+ {name_constraints, undefined},
+ {policy_constraints, undefined},
+ {ext_key_usage, undefined},
+ {inhibit_any, undefined},
+ {auth_key_id, undefined},
+ {subject_key_id, undefined},
+ {policy_mapping, undefined}],
+ Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end,
+ Exts ++ lists:foldl(Filter, Def, Exts).
+
+extension({_, undefined}) -> [];
+extension({basic_constraints, Data}) ->
+ case Data of
+ default ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = #'BasicConstraints'{cA=true},
+ critical=true};
+ false ->
+ [];
+ Len when is_integer(Len) ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len},
+ critical=true};
+ _ ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = Data}
+ end;
+extension({Id, Data, Critical}) ->
+ #'Extension'{extnID = Id, extnValue = Data, critical = Critical}.
+
+
+publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) ->
+ Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = Public};
+publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) ->
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa',
+ parameters=#'Dss-Parms'{p=P, q=Q, g=G}},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}.
+
+validity(Opts) ->
+ DefFrom0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())-1),
+ DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7),
+ {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}),
+ Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end,
+ #'Validity'{notBefore={generalTime, Format(DefFrom)},
+ notAfter ={generalTime, Format(DefTo)}}.
+
+sign_algorithm(#'RSAPrivateKey'{}, Opts) ->
+ Type = case proplists:get_value(digest, Opts, sha1) of
+ sha1 -> ?'sha1WithRSAEncryption';
+ sha512 -> ?'sha512WithRSAEncryption';
+ sha384 -> ?'sha384WithRSAEncryption';
+ sha256 -> ?'sha256WithRSAEncryption';
+ md5 -> ?'md5WithRSAEncryption';
+ md2 -> ?'md2WithRSAEncryption'
+ end,
+ {Type, 'NULL'};
+sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
+ {?'id-dsa-with-sha1', #'Dss-Parms'{p=P, q=Q, g=G}}.
+
+make_key(rsa, _Opts) ->
+ %% (OBS: for testing only)
+ gen_rsa2(64);
+make_key(dsa, _Opts) ->
+ gen_dsa2(128, 20). %% Bytes i.e. {1024, 160}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RSA key generation (OBS: for testing only)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53,
+ 47,43,41,37,31,29,23,19,17,13,11,7,5,3]).
+
+gen_rsa2(Size) ->
+ P = prime(Size),
+ Q = prime(Size),
+ N = P*Q,
+ Tot = (P - 1) * (Q - 1),
+ [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES),
+ {D1,D2} = extended_gcd(E, Tot),
+ D = erlang:max(D1,D2),
+ case D < E of
+ true ->
+ gen_rsa2(Size);
+ false ->
+ {Co1,Co2} = extended_gcd(Q, P),
+ Co = erlang:max(Co1,Co2),
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = N,
+ publicExponent = E,
+ privateExponent = D,
+ prime1 = P,
+ prime2 = Q,
+ exponent1 = D rem (P-1),
+ exponent2 = D rem (Q-1),
+ coefficient = Co
+ }
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% DSA key generation (OBS: for testing only)
+%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
+%% and the fips_186-3.pdf
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+gen_dsa2(LSize, NSize) ->
+ Q = prime(NSize), %% Choose N-bit prime Q
+ X0 = prime(LSize),
+ P0 = prime((LSize div 2) +1),
+
+ %% Choose L-bit prime modulus P such that p–1 is a multiple of q.
+ case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of
+ error ->
+ gen_dsa2(LSize, NSize);
+ P ->
+ G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
+ %% such that This may be done by setting g = h^(p–1)/q mod p, commonly h=2 is used.
+
+ X = prime(20), %% Choose x by some random method, where 0 < x < q.
+ Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p.
+
+ #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X}
+ end.
+
+%% See fips_186-3.pdf
+dsa_search(T, P0, Q, Iter) when Iter > 0 ->
+ P = 2*T*Q*P0 + 1,
+ case is_prime(crypto:mpint(P), 50) of
+ true -> P;
+ false -> dsa_search(T+1, P0, Q, Iter-1)
+ end;
+dsa_search(_,_,_,_) ->
+ error.
+
+
+%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+prime(ByteSize) ->
+ Rand = odd_rand(ByteSize),
+ crypto:erlint(prime_odd(Rand, 0)).
+
+prime_odd(Rand, N) ->
+ case is_prime(Rand, 50) of
+ true ->
+ Rand;
+ false ->
+ NotPrime = crypto:erlint(Rand),
+ prime_odd(crypto:mpint(NotPrime+2), N+1)
+ end.
+
+%% see http://en.wikipedia.org/wiki/Fermat_primality_test
+is_prime(_, 0) -> true;
+is_prime(Candidate, Test) ->
+ CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate),
+ case crypto:mod_exp(CoPrime, Candidate, Candidate) of
+ CoPrime -> is_prime(Candidate, Test-1);
+ _ -> false
+ end.
+
+odd_rand(Size) ->
+ Min = 1 bsl (Size*8-1),
+ Max = (1 bsl (Size*8))-1,
+ odd_rand(crypto:mpint(Min), crypto:mpint(Max)).
+
+odd_rand(Min,Max) ->
+ Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max),
+ BitSkip = (Sz+4)*8-1,
+ case Rand of
+ Odd = <<_:BitSkip, 1:1>> -> Odd;
+ Even = <<_:BitSkip, 0:1>> ->
+ crypto:mpint(crypto:erlint(Even)+1)
+ end.
+
+extended_gcd(A, B) ->
+ case A rem B of
+ 0 ->
+ {0, 1};
+ N ->
+ {X, Y} = extended_gcd(B, N),
+ {Y, X-Y*(A div B)}
+ end.
diff --git a/lib/public_key/test/public_key.cover b/lib/public_key/test/public_key.cover
new file mode 100644
index 0000000000..8477c76ef6
--- /dev/null
+++ b/lib/public_key/test/public_key.cover
@@ -0,0 +1,2 @@
+
+{exclude, ['OTP-PUB-KEY']}. \ No newline at end of file
diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl
index 8cc36e490d..dc1015969a 100644
--- a/lib/public_key/test/public_key_SUITE.erl
+++ b/lib/public_key/test/public_key_SUITE.erl
@@ -101,14 +101,13 @@ all(doc) ->
all(suite) ->
[app,
+ dh,
pem_to_der,
- decode_private_key
-%% encrypt_decrypt,
-%% rsa_verify
-%% dsa_verify_sign,
-%% pkix_encode_decode,
-%% pkix_verify_sign,
-%% pkix_path_validation
+ decode_private_key,
+ encrypt_decrypt,
+ sign_verify,
+ pkix,
+ pkix_path_validation
].
%% Test cases starts here.
@@ -118,20 +117,35 @@ app(doc) ->
"Test that the public_key app file is ok";
app(suite) ->
[];
-app(Config) when list(Config) ->
+app(Config) when is_list(Config) ->
ok = test_server:app_test(public_key).
+dh(doc) ->
+ "Test diffie-hellman functions file is ok";
+dh(suite) ->
+ [];
+dh(Config) when is_list(Config) ->
+ Datadir = ?config(data_dir, Config),
+ {ok,[DerDHparams = {dh_params, _, _}]} =
+ public_key:pem_to_der(filename:join(Datadir, "dh.pem")),
+ {ok, DHps = #'DHParameter'{prime=P,base=G}} = public_key:decode_dhparams(DerDHparams),
+ DHKeys = {Private,_Public} = public_key:gen_key(DHps),
+ test_server:format("DHparams = ~p~nDH Keys~p~n", [DHps, DHKeys]),
+ {_Private,_Public2} = pubkey_crypto:gen_key(diffie_hellman, [crypto:erlint(Private), P, G]),
+ ok.
+
+
pem_to_der(doc) ->
["Check that supported PEM files are decoded into the expected entry type"];
pem_to_der(suite) ->
[];
pem_to_der(Config) when is_list(Config) ->
Datadir = ?config(data_dir, Config),
- {ok,[{dsa_private_key, _, not_encrypted}]} =
+ {ok,DSAKey =[{dsa_private_key, _, not_encrypted}]} =
public_key:pem_to_der(filename:join(Datadir, "dsa.pem")),
{ok,[{rsa_private_key, _, _}]} =
public_key:pem_to_der(filename:join(Datadir, "client_key.pem")),
- {ok,[{rsa_private_key, _, _}]} =
+ {ok, [{rsa_private_key, _, _}]} =
public_key:pem_to_der(filename:join(Datadir, "rsa.pem")),
{ok,[{rsa_private_key, _, _}]} =
public_key:pem_to_der(filename:join(Datadir, "rsa.pem"), "abcd1234"),
@@ -144,12 +158,18 @@ pem_to_der(Config) when is_list(Config) ->
public_key:pem_to_der(filename:join(Datadir, "client_cert.pem")),
{ok,[{cert_req, _, _}]} =
public_key:pem_to_der(filename:join(Datadir, "req.pem")),
- {ok,[{cert, _, _}, {cert, _, _}]} =
+ {ok, Certs = [{cert, _, _}, {cert, _, _}]} =
public_key:pem_to_der(filename:join(Datadir, "cacerts.pem")),
- {ok, Bin1} = file:read_file(filename:join(Datadir, "cacerts.pem")),
+ {ok, Bin1} = file:read_file(filename:join(Datadir, "cacerts.pem")),
{ok, [{cert, _, _}, {cert, _, _}]} = public_key:pem_to_der(Bin1),
+
+ ok = public_key:der_to_pem(filename:join(Datadir, "wcacerts.pem"), Certs),
+ ok = public_key:der_to_pem(filename:join(Datadir, "wdsa.pem"), DSAKey),
+ {ok, Certs} = public_key:pem_to_der(filename:join(Datadir, "wcacerts.pem")),
+ {ok, DSAKey} = public_key:pem_to_der(filename:join(Datadir, "wdsa.pem")),
+
ok.
%%--------------------------------------------------------------------
decode_private_key(doc) ->
@@ -178,84 +198,148 @@ encrypt_decrypt(doc) ->
encrypt_decrypt(suite) ->
[];
encrypt_decrypt(Config) when is_list(Config) ->
- RSAPrivateKey = #'RSAPrivateKey'{publicExponent = 17,
- modulus = 3233,
- privateExponent = 2753,
- prime1 = 61,
- prime2 = 53,
- version = 'two-prime'},
- Msg = <<0,123>>,
- {ok, Encrypted} = public_key:encrypt(Msg, RSAPrivateKey, [{block_type, 2}]),
- test_server:format("Expected 855, Encrypted ~p ~n", [Encrypted]),
+ {PrivateKey, _DerKey} = pkey_test:gen_rsa(64),
+ #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} = PrivateKey,
+ PublicKey = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp},
+ Msg = list_to_binary(lists:duplicate(5, "Foo bar 100")),
+ RsaEncrypted = public_key:encrypt_private(Msg, PrivateKey),
+ Msg = public_key:decrypt_public(RsaEncrypted, PublicKey),
+ Msg = public_key:decrypt_public(RsaEncrypted, PrivateKey),
+ RsaEncrypted2 = public_key:encrypt_public(Msg, PublicKey),
+ RsaEncrypted3 = public_key:encrypt_public(Msg, PrivateKey),
+ Msg = public_key:decrypt_private(RsaEncrypted2, PrivateKey),
+ Msg = public_key:decrypt_private(RsaEncrypted3, PrivateKey),
+
ok.
+
+%%--------------------------------------------------------------------
+sign_verify(doc) ->
+ ["Checks that we can sign and verify signatures."];
+sign_verify(suite) ->
+ [];
+sign_verify(Config) when is_list(Config) ->
+ %% Make cert signs and validates the signature using RSA and DSA
+ Ca = {_, CaKey} = pkey_test:make_cert([]),
+ {ok, PrivateRSA = #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp}} =
+ public_key:decode_private_key(CaKey),
+
+ CertInfo = {Cert1,CertKey1} = pkey_test:make_cert([{key, dsa}, {issuer, Ca}]),
+
+ PublicRSA = #'RSAPublicKey'{modulus=Mod, publicExponent=Exp},
+ true = public_key:verify_signature(Cert1, PublicRSA, undefined),
+
+ {Cert2,_CertKey} = pkey_test:make_cert([{issuer, CertInfo}]),
+
+ {ok, #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y, x=_X}} =
+ public_key:decode_private_key(CertKey1),
+ true = public_key:verify_signature(Cert2, Y, #'Dss-Parms'{p=P, q=Q, g=G}),
+ %% RSA sign
+ Msg0 = lists:duplicate(5, "Foo bar 100"),
+ Msg = list_to_binary(Msg0),
+ RSASign = public_key:sign(sha, Msg0, PrivateRSA),
+ RSASign = public_key:sign(Msg, PrivateRSA),
+ true = public_key:verify_signature(Msg, sha, RSASign, PublicRSA),
+ false = public_key:verify_signature(<<1:8, Msg/binary>>, sha, RSASign, PublicRSA),
+ false = public_key:verify_signature(Msg, sha, <<1:8, RSASign/binary>>, PublicRSA),
+ RSASign = public_key:sign(sha, Msg, PrivateRSA),
+ RSASign1 = public_key:sign(md5, Msg, PrivateRSA),
+ true = public_key:verify_signature(Msg, md5, RSASign1, PublicRSA),
+
+ %% DSA sign
+ Datadir = ?config(data_dir, Config),
+ {ok,[DsaKey = {dsa_private_key, _, _}]} =
+ public_key:pem_to_der(filename:join(Datadir, "dsa.pem")),
+ {ok, DSAPrivateKey} = public_key:decode_private_key(DsaKey),
+ #'DSAPrivateKey'{p=P1, q=Q1, g=G1, y=Y1, x=_X1} = DSAPrivateKey,
+ DSASign = public_key:sign(Msg, DSAPrivateKey),
+ DSAPublicKey = Y1,
+ DSAParams = #'Dss-Parms'{p=P1, q=Q1, g=G1},
+ true = public_key:verify_signature(Msg, sha, DSASign, DSAPublicKey, DSAParams),
+ false = public_key:verify_signature(<<1:8, Msg/binary>>, sha, DSASign, DSAPublicKey, DSAParams),
+ false = public_key:verify_signature(Msg, sha, <<1:8, DSASign/binary>>, DSAPublicKey, DSAParams),
+
+ ok.
+pkix(doc) ->
+ "Misc pkix tests not covered elsewhere";
+pkix(suite) ->
+ [];
+pkix(Config) when is_list(Config) ->
+ Datadir = ?config(data_dir, Config),
+ {ok,Certs0} = public_key:pem_to_der(filename:join(Datadir, "cacerts.pem")),
+ {ok,Certs1} = public_key:pem_to_der(filename:join(Datadir, "client_cert.pem")),
+ TestTransform = fun({cert, CertDer, not_encrypted}) ->
+ {ok, PlainCert} = public_key:pkix_decode_cert(CertDer, plain),
+ {ok, OtpCert} = public_key:pkix_decode_cert(CertDer, otp),
+ CertDer = public_key:pkix_encode_cert(OtpCert),
+ CertDer = public_key:pkix_encode_cert(PlainCert),
+ OTPSubj = (OtpCert#'OTPCertificate'.tbsCertificate)#'OTPTBSCertificate'.subject,
+ Subj = public_key:pkix_transform(OTPSubj, encode),
+ {ok, DNEncoded} = 'OTP-PUB-KEY':encode('Name', Subj),
+ Subj2 = (PlainCert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject,
+ {ok, DNEncoded} = 'OTP-PUB-KEY':encode('Name', Subj2),
+ OTPSubj = public_key:pkix_transform(Subj2, decode),
+ false = public_key:pkix_is_fixed_dh_cert(CertDer)
+ end,
+ [TestTransform(Cert) || Cert <- Certs0 ++ Certs1],
+ true = public_key:pkix_is_self_signed(element(2,hd(Certs0))),
+ false = public_key:pkix_is_self_signed(element(2,hd(Certs1))),
+ CaIds = [element(2, public_key:pkix_issuer_id(Cert, self)) || {cert, Cert, _} <- Certs0],
+ {ok, IssuerId = {_, IssuerName}} = public_key:pkix_issuer_id(element(2,hd(Certs1)), other),
+ true = lists:member(IssuerId, CaIds),
+ %% Should be normalized allready
+ TestStr = {rdnSequence, [[{'AttributeTypeAndValue', {2,5,4,3},{printableString,"ERLANGCA"}}],
+ [{'AttributeTypeAndValue', {2,5,4,3},{printableString," erlang ca "}}]]},
+ VerifyStr = {rdnSequence, [[{'AttributeTypeAndValue', {2,5,4,3},{printableString,"erlang ca"}}],
+ [{'AttributeTypeAndValue', {2,5,4,3},{printableString,"erlangca"}}]]},
+ VerifyStr = public_key:pkix_normalize_general_name(TestStr),
-%% Datadir = ?config(data_dir, Config),
-%% {ok,[{rsa_private_key, EncKey}]} =
-%% public_key:pem_to_der(filename:join(Datadir, "server_key.pem")),
-%% {ok, Key} = public_key:decode_private_key(EncKey, rsa),
-%% RSAPublicKey = #'RSAPublicKey'{publicExponent =
-%% Key#'RSAPrivateKey'.publicExponent,
-%% modulus = Key#'RSAPrivateKey'.modulus},
-%% {ok, Msg} = file:read_file(filename:join(Datadir, "msg.txt")),
-%% Hash = crypto:sha(Msg),
-%% {ok, Encrypted} = public_key:encrypt(Hash, Key, [{block_type, 2}]),
-%% test_server:format("Encrypted ~p", [Encrypted]),
-%% {ok, Decrypted} = public_key:decrypt(Encrypted,
-%% RSAPublicKey, [{block_type, 1}]),
-%% test_server:format("Encrypted ~p", [Decrypted]),
-%% true = Encrypted == Decrypted.
-
-%%--------------------------------------------------------------------
-rsa_verify(doc) ->
- ["Cheks that we can verify an rsa signature."];
-rsa_verify(suite) ->
+ ok.
+
+pkix_path_validation(doc) ->
+ "Misc pkix tests not covered elsewhere";
+pkix_path_validation(suite) ->
[];
-rsa_verify(Config) when is_list(Config) ->
- Datadir = ?config(data_dir, Config),
+pkix_path_validation(Config) when is_list(Config) ->
+ CaK = {Trusted,_} =
+ pkey_test:make_cert([{key, dsa},
+ {subject, [
+ {name, "Public Key"},
+ {?'id-at-name', {printableString, "public_key"}},
+ {?'id-at-pseudonym', {printableString, "pubkey"}},
+ {city, "Stockholm"},
+ {country, "SE"},
+ {org, "erlang"},
+ {org_unit, "testing dep"}
+ ]}
+ ]),
+ ok = pkey_test:write_pem("./", "public_key_cacert", CaK),
+
+ CertK1 = {Cert1, _} = pkey_test:make_cert([{issuer, CaK}]),
+ CertK2 = {Cert2,_} = pkey_test:make_cert([{issuer, CertK1}, {digest, md5}, {extensions, false}]),
+ ok = pkey_test:write_pem("./", "public_key_cert", CertK2),
- {ok,[{cert, DerCert}]} =
- public_key:pem_to_der(filename:join(Datadir, "server_cert.pem")),
+ {ok, _} = public_key:pkix_path_validation(Trusted, [Cert1], []),
- {ok, OTPCert} = public_key:pkix_decode_cert(DerCert, otp),
+ {error, {bad_cert,invalid_issuer}} = public_key:pkix_path_validation(Trusted, [Cert2], []),
+ %%{error, {bad_cert,invalid_issuer}} = public_key:pkix_path_validation(Trusted, [Cert2], [{verify,false}]),
- {0, Signature} = OTPCert#'Certificate'.signature,
- TBSCert = OTPCert#'Certificate'.tbsCertificate,
+ {ok, _} = public_key:pkix_path_validation(Trusted, [Cert1, Cert2], []),
+ {error, issuer_not_found} = public_key:pkix_issuer_id(Cert2, other),
- #'TBSCertificate'{subjectPublicKeyInfo = Info} = TBSCert,
-
- #'SubjectPublicKeyInfo'{subjectPublicKey = RSAPublicKey} = Info,
+ CertK3 = {Cert3,_} = pkey_test:make_cert([{issuer, CertK1}, {extensions, [{basic_constraints, false}]}]),
+ {Cert4,_} = pkey_test:make_cert([{issuer, CertK3}]),
+ {error, E={bad_cert,missing_basic_constraint}} =
+ public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4], []),
- EncTBSCert = encoded_tbs_cert(DerCert),
- Digest = crypto:sha(EncTBSCert),
-
- public_key:verify_signature(Digest, Signature, RSAPublicKey).
-
-
-%% Signature is generated in the following way (in datadir):
-%% openssl dgst -sha1 -binary -out rsa_signature -sign server_key.pem msg.txt
-%%{ok, Signature} = file:read_file(filename:join(Datadir, "rsa_signature")),
-%%{ok, Signature} = file:read_file(filename:join(Datadir, "rsa_signature")),
-%% {ok, Msg} = file:read_file(filename:join(Datadir, "msg.txt")),
-%% Digest = crypto:sha(Msg),
-%% {ok,[{rsa_private_key, EncKey}]} =
-%% public_key:pem_to_der(filename:join(Datadir, "server_key.pem")),
-%% {ok, Key} = public_key:decode_private_key(EncKey, rsa),
-%% RSAPublicKey = #'RSAPublicKey'{publicExponent =
-%% Key#'RSAPrivateKey'.publicExponent,
-%% modulus = Key#'RSAPrivateKey'.modulus},
-
-encoded_tbs_cert(Cert) ->
- {ok, PKIXCert} =
- 'OTP-PUB-KEY':decode_TBSCert_exclusive(Cert),
- {'Certificate',
- {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = PKIXCert,
- EncodedTBSCert.
+ {ok, {_,_,[E]}} = public_key:pkix_path_validation(Trusted, [Cert1, Cert3,Cert4], [{verify,false}]),
+ % test_server:format("PV ~p ~n", [Result]),
+ ok.
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index f395b919c6..4b3071a85b 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1,6 +1,10 @@
-PUBLIC_KEY_VSN = 0.6
-TICKETS = OTP-7046
-#TICKETS_o.5 = OTP-8372
+PUBLIC_KEY_VSN = 0.7
+
+TICKETS = OTP-8626 OTP-8649
+
+#TICKETS_0.6 = OTP-7046 \
+# OTP-8553
+#TICKETS_0.5 = OTP-8372
#TICKETS_0.4 = OTP-8250
#TICKETS_0.3 = OTP-8100 OTP-8142
#TICKETS_0.2 = OTP-7860
diff --git a/lib/reltool/bin/reltool.escript b/lib/reltool/bin/reltool.escript
new file mode 100755
index 0000000000..0dcd5ad1e9
--- /dev/null
+++ b/lib/reltool/bin/reltool.escript
@@ -0,0 +1,249 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%
+%% %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_lib("reltool/src/reltool.hrl").
+
+main(Args) ->
+ process_flag(trap_exit, true),
+ try
+ Tokens = scan_args(Args, [], []),
+ {Options, Actions} = parse_args(Tokens, []),
+ case invoke(Options, Actions) of
+ ok ->
+ safe_stop(0);
+ {error, ReasonString} ->
+ fatal_error(ReasonString, 2)
+ end
+ catch
+ throw:usage ->
+ usage(),
+ safe_stop(1);
+ exit:Reason ->
+ String = lists:flatten(io_lib:format("EXIT: ~p", [Reason])),
+ fatal_error(String, 3)
+ end.
+
+usage() ->
+ Usage =
+ [
+ "[Config] [--window]",
+ "[Config] --create_config [-defaults] [-derived] [ConfigFile]",
+ "[Config] --create_rel RelName [RelFile]",
+ "[Config] --create_script RelName [ScriptFile]",
+ "[Config] --create_target TargetDir",
+ "[Config] --create_target_spec [SpecFile]",
+ "[Config] --eval_target_spec Spec TargetDir RootDir"
+ ],
+ Script = script_name(),
+ String = lists:flatten([[Script, " ", U, "\n"] || U <- Usage]),
+ io:format("Erlang/OTP release management tool\n\n"
+ "~s\nConfig = ConfigFile | '{sys, [sys()]}'\n"
+ "Spec = SpecFile | '{spec, [target_spec()}']\n\n"
+ "See User's guide and Reference manual for more info.\n",
+ [String]).
+
+safe_stop(Code) ->
+ init:stop(Code),
+ timer:sleep(infinity).
+
+invoke(Options, Actions) ->
+ case Actions of
+ [] ->
+ invoke(Options, [["--window"]]);
+ [["--window"]] ->
+ start_window(Options);
+ [["--create_config" | OptArgs]] ->
+ DefArg = "-defaults",
+ DerivArg = "-derived",
+ InclDef = lists:member(DefArg, OptArgs),
+ InclDeriv = lists:member(DerivArg, OptArgs),
+ case reltool:get_config(Options, InclDef, InclDeriv) of
+ {ok, Config} ->
+ String = pretty("config", Config),
+ case OptArgs -- [DefArg, DerivArg] of
+ [] ->
+ format("~s", [String]);
+ [ConfigFile] ->
+ write_file(ConfigFile, String);
+ _ ->
+ throw(usage)
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ [["--create_rel", RelName | OptArgs]] ->
+ case reltool:get_rel(Options, RelName) of
+ {ok, Rel} ->
+ String = pretty("rel", Rel),
+ case OptArgs of
+ [] ->
+ format("~s", [String]);
+ [RelFile] ->
+ write_file(RelFile, String);
+ _ ->
+ throw(usage)
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ [["--create_script", RelName | OptArgs]] ->
+ case reltool:get_script(Options, RelName) of
+ {ok, Script} ->
+ String = pretty("script", Script),
+ case OptArgs of
+ [] ->
+ format("~s", [String]);
+ [ScriptFile] ->
+ write_file(ScriptFile, String);
+ _ ->
+ throw(usage)
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ [["--create_target", TargetDir]] ->
+ reltool:create_target(Options, TargetDir);
+ [["--create_target_spec" | OptArgs]] ->
+ case reltool:get_target_spec(Options) of
+ {ok, Script} ->
+ String = pretty("target_spec", Script),
+ case OptArgs of
+ [] ->
+ format("~s", [String]);
+ [SpecFile] ->
+ write_file(SpecFile, String);
+ _ ->
+ throw(usage)
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end;
+ [["--eval_target_spec", TargetSpec, TargetDir, RootDir]] ->
+ try
+ {ok, Tokens, _} = erl_scan:string(TargetSpec ++ ". "),
+ {ok, {spec, Spec}} = erl_parse:parse_term(Tokens),
+ reltool:eval_target_spec(Spec, TargetDir, RootDir)
+ catch
+ error:{badmatch, _} ->
+ case file:consult(TargetSpec) of
+ {ok, Spec2} ->
+ reltool:eval_target_spec(Spec2, TargetDir, RootDir);
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ {error, TargetSpec ++ ": " ++ Text}
+ end
+ end;
+ _ ->
+ throw(usage)
+ end.
+
+start_window(Options) ->
+ case reltool:start_link(Options) of
+ {ok, WinPid} ->
+ receive
+ {'EXIT', WinPid, shutdown} ->
+ ok;
+ {'EXIT', WinPid, normal} ->
+ ok;
+ {'EXIT', WinPid, Reason} ->
+ exit(Reason)
+ end;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Helpers
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+script_name() ->
+ filename:basename(escript:script_name(), ".escript").
+
+fatal_error(String, Code) ->
+ io:format(standard_error, "~s: ~s\n", [script_name(), String]),
+ safe_stop(Code).
+
+write_file(File, IoList) ->
+ case file:write_file(File, IoList) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ {error, file:format_error(Reason)}
+ end.
+
+format(Format, Args) ->
+ io:format(Format, Args),
+ %% Wait a while for the I/O to be processed
+ timer:sleep(timer:seconds(1)).
+
+pretty(Tag, Term) ->
+ lists:flatten(io_lib:format("%% ~s generated at ~w ~w\n~p.\n\n",
+ [Tag, date(), time(), Term])).
+
+scan_args([H | T], Single, Multi) ->
+ case H of
+ "--" ++ _ when Single =:= [] ->
+ scan_args(T, [H], Multi);
+ "--" ++ _ ->
+ scan_args(T, [H], [lists:reverse(Single) | Multi]);
+ _ ->
+ scan_args(T, [H | Single], Multi)
+ end;
+scan_args([], [], Multi) ->
+ lists:reverse(Multi);
+scan_args([], Single, Multi) ->
+ lists:reverse([lists:reverse(Single) | Multi]).
+
+parse_args([H | T] = Args, Options) ->
+ case H of
+ ["--wx_debug" | Levels] ->
+ Dbg =
+ fun(L) ->
+ case catch list_to_integer(L) of
+ {'EXIT', _} ->
+ case catch list_to_atom(L) of
+ {'EXIT', _} ->
+ exit("Illegal wx debug level: " ++ L);
+ Atom ->
+ Atom
+ end;
+ Int ->
+ Int
+ end
+ end,
+ Levels2 = lists:map(Dbg, Levels),
+ parse_args(T, [{wx_debug, Levels2} | Options]);
+ ["--" ++ _ | _] ->
+ %% No more options
+ {lists:reverse(Options), Args};
+ [Config] ->
+ try
+ {ok, Tokens, _} = erl_scan:string(Config ++ ". "),
+ {ok, {sys, _} = Sys} = erl_parse:parse_term(Tokens),
+ parse_args(T, [{config, Sys} | Options])
+ catch
+ error:{badmatch, _} ->
+ parse_args(T, [{config, Config} | Options]);
+ X:Y ->
+ io:format("\n\n~p\n\n", [{X, Y}])
+ end
+ end;
+parse_args([], Options) ->
+ {lists:reverse(Options), []}.
diff --git a/lib/reltool/doc/src/notes.xml b/lib/reltool/doc/src/notes.xml
index 524d728901..95e379db53 100644
--- a/lib/reltool/doc/src/notes.xml
+++ b/lib/reltool/doc/src/notes.xml
@@ -37,6 +37,73 @@
thus constitutes one section in this document. The title of each
section is the version number of Reltool.</p>
+ <section><title>Reltool 0.5.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added function <c>zip:foldl/3</c> to iterate over zip
+ archives.</p>
+ <p>
+ Added functions to create and extract escripts. See
+ <c>escript:create/2</c> and <c>escript:extract/2</c>.</p>
+ <p>
+ The undocumented function <c>escript:foldl/3</c> has been
+ removed. The same functionality can be achieved with the
+ more flexible functions <c>escript:extract/2</c> and
+ <c>zip:foldl/3</c>.</p>
+ <p>
+ Record fields has been annotated with type info. Source
+ files as been adapted to fit within 80 chars and trailing
+ whitespace has been removed.</p>
+ <p>
+ Own Id: OTP-8521</p>
+ </item>
+ <item>
+ <p>A new escript, called <c>reltool</c>, has been
+ introduced in order to simplify the usage of the reltool
+ application from makefiles.</p>
+ <p>The handling of applications included in releases has
+ been improved. Applications that are required to be
+ started before other applications in a release are now
+ automatically included in the release. The <c>kernel</c>
+ and <c>stdlib</c> applications are always included as
+ they are mandatory.</p>
+ <p>Applications that are (explicitly or implicitly)
+ included in a release are now automatically included as
+ if they were explicitly included with the incl_cond
+ flag.</p>
+ <p>A new <c>embedded_app_type</c> option has been
+ introduced. It is intended to be used for embedded
+ systems where all included applications must be loaded
+ from the boot script, as these systems does not utilize
+ dynamic code loading. If <c>embedded_app_type </c> is set
+ to something else than <c>undefined</c>, all included
+ applications will be included in both the release as well
+ as in the boot script. If the <c>profile</c> is
+ <c>embedded</c> the <c>embedded_app_type</c> option
+ defaults to <c>load</c>.</p>
+ <p>A new function called <c>reltool:get_status/1</c> has
+ been introduced. It returns status about the
+ configuration in the server.</p>
+ <p>The API functions that may take <c>PidOrOptions</c> as
+ input and actually gets <c>Options</c> does now print out
+ warnings.</p>
+ <p>The internal error handling has been improved. For
+ example <c>{error,Reason}</c> is always returned in case
+ of errors even when the server dies.</p>
+ <p><c>app</c> and <c>appup</c> files has been added as
+ well as a corresponding test suite.</p>
+ <p>Various cleanups has been made in the code and in the
+ documentation.</p>
+ <p>
+ Own Id: OTP-8590</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
<section><title>Reltool 0.5.3</title>
diff --git a/lib/reltool/doc/src/reltool.xml b/lib/reltool/doc/src/reltool.xml
index 9786928ae8..0c2b7d2a2b 100644
--- a/lib/reltool/doc/src/reltool.xml
+++ b/lib/reltool/doc/src/reltool.xml
@@ -67,14 +67,14 @@
<taglist>
- <tag><c><![CDATA[config]]></c></tag>
+ <tag><c>config</c></tag>
<item>
<p>This is the main option and it controls the configuration
of <c>reltool</c>. It can either be a <c>sys</c> tuple or
a name of a <c>file</c> containing a sys tuple.</p>
</item>
- <tag><c><![CDATA[trap_exit]]></c></tag>
+ <tag><c>trap_exit></c></tag>
<item>
<p>This option controls the error handling behavior of
<c>reltool</c>. By default the window processes traps
@@ -82,7 +82,7 @@
<c>trap_exit</c> to <c>false</c>.</p>
</item>
- <tag><c><![CDATA[wx_debug]]></c></tag>
+ <tag><c>wx_debug</c></tag>
<item>
<p>This option controls the debug level of <c>wx</c>. As its
name indicates it is only useful for debugging. See
@@ -97,27 +97,27 @@
<taglist>
- <tag><c><![CDATA[erts]]></c></tag>
+ <tag><c>erts</c></tag>
<item>
<p>Erts specific configuration. See application level options
below.</p>
</item>
- <tag><c><![CDATA[escript]]></c></tag>
+ <tag><c>escript</c></tag>
<item>
<p>Escript specific configuration. An escript has a mandatory
file name and escript level options that are described
below.</p>
</item>
- <tag><c><![CDATA[app]]></c></tag>
+ <tag><c>app</c></tag>
<item>
<p>Application specific configuration. An application has a
mandatory name and application level options that are
described below.</p>
</item>
- <tag><c><![CDATA[mod_cond]]></c></tag>
+ <tag><c>mod_cond</c></tag>
<item>
<p>This parameter controls the module inclusion policy. It
defaults to <c>all</c> which means that if an application is
@@ -134,28 +134,28 @@
system level is used as default for all applications.</p>
</item>
- <tag><c><![CDATA[incl_cond]]></c></tag>
+ <tag><c>incl_cond</c></tag>
<item>
- <p>This parameter controls the application and escript
- inclusion policy. It defaults to <c>derived</c> which means
- that the applications that not have any explicit
- <c>incl_cond</c> setting, will only be included if any other
- (explicitly or implicitly included) application uses it. The
- value <c>include</c> implies that all applications and
- escripts that that not have any explicit <c>incl_cond</c>
- setting will be included. <c>exclude</c> implies that all
- applications and escripts) that that not have any explicit
- <c>incl_cond</c> setting will be excluded.</p>
+ <p>This parameter controls the application and escript
+ inclusion policy. It defaults to <c>derived</c> which means
+ that the applications that not have any explicit
+ <c>incl_cond</c> setting, will only be included if any other
+ (explicitly or implicitly included) application uses it. The
+ value <c>include</c> implies that all applications and
+ escripts that that not have any explicit <c>incl_cond</c>
+ setting will be included. <c>exclude</c> implies that all
+ applications and escripts) that that not have any explicit
+ <c>incl_cond</c> setting will be excluded.</p>
</item>
- <tag><c><![CDATA[boot_rel]]></c></tag>
+ <tag><c>boot_rel</c></tag>
<item>
<p>A target system may have several releases but the one given
as <c>boot_rel</c> will be used as default when the system is
booting up.</p>
</item>
- <tag><c><![CDATA[rel]]></c></tag>
+ <tag><c>rel</c></tag>
<item>
<p>Release specific configuration. Each release maps to a
<c>rel</c>, <c>script</c> and <c>boot </c> file. See the
@@ -165,38 +165,38 @@
applications.</p>
</item>
- <tag><c><![CDATA[relocatable]]></c></tag>
+ <tag><c>relocatable</c></tag>
<item>
- <p>This parameter controls whether the <c>erl</c> executable
- in the target system automatically should determine where it
- is installed or if it should use a hardcoded path to the
- installation. In the latter case the target system must be
- installed with <c>reltool:install/2</c> before it can be
- used. If the system is relocatable, the file tree containing
- the target system can be moved to another location without
- re-installation. The default is <c>true</c>.</p>
+ <p>This parameter controls whether the <c>erl</c> executable
+ in the target system automatically should determine where it
+ is installed or if it should use a hardcoded path to the
+ installation. In the latter case the target system must be
+ installed with <c>reltool:install/2</c> before it can be
+ used. If the system is relocatable, the file tree containing
+ the target system can be moved to another location without
+ re-installation. The default is <c>true</c>.</p>
</item>
- <tag><c><![CDATA[profile]]></c></tag>
+ <tag><c>profile</c></tag>
<item>
- <p>The creation of the specification for a target system is
- performed in two steps. In the first step a complete
- specification is generated. It will likely contain much more
- files than you are interested in your customized target
- system. In the second step the specification will be filtered
- according to your filters. There you have the ability to
- specify filters per application as well as system wide
- filters. You can also select a <c>profile</c> for your
- system. Depending on the <c>profile</c>, different default
- filters will be used. There are three different profiles to
- choose from: <c>development</c>, <c>embedded</c> and
- <c>standalone</c>. <c>development</c> is default. The
- parameters that are affected by the <c>profile</c> are:
- <c>incl_sys_filters</c>, <c>excl_sys_filters</c>,
- <c>incl_app_filters</c> and <c>excl_app_filters</c>.</p>
+ <p>The creation of the specification for a target system is
+ performed in two steps. In the first step a complete
+ specification is generated. It will likely contain much more
+ files than you are interested in your customized target
+ system. In the second step the specification will be filtered
+ according to your filters. There you have the ability to
+ specify filters per application as well as system wide
+ filters. You can also select a <c>profile</c> for your
+ system. Depending on the <c>profile</c>, different default
+ filters will be used. There are three different profiles to
+ choose from: <c>development</c>, <c>embedded</c> and
+ <c>standalone</c>. <c>development</c> is default. The
+ parameters that are affected by the <c>profile</c> are:
+ <c>incl_sys_filters</c>, <c>excl_sys_filters</c>,
+ <c>incl_app_filters</c> and <c>excl_app_filters</c>.</p>
</item>
- <tag><c><![CDATA[app_file]]></c></tag>
+ <tag><c>app_file</c></tag>
<item>
<p>This parameter controls the default handling of the
<c>app</c> files when a target system is generated. It
@@ -213,7 +213,7 @@
and <c>strip</c>.</p>
</item>
- <tag><c><![CDATA[debug_info]]></c></tag>
+ <tag><c>debug_info</c></tag>
<item>
<p>The <c>debug_info</c> parameter controls whether the debug
information in the beam file should be kept (<c>keep</c>) or
@@ -221,7 +221,7 @@
system.</p>
</item>
- <tag><c><![CDATA[incl_sys_filters]]></c></tag>
+ <tag><c>incl_sys_filters</c></tag>
<item>
<p>This parameter normally contains a list of regular
expressions that controls which files in the system that
@@ -235,7 +235,7 @@
<c>[".*"]</c>.</p>
</item>
- <tag><c><![CDATA[excl_sys_filters]]></c></tag>
+ <tag><c>excl_sys_filters</c></tag>
<item>
<p>This parameter normally contains a list of regular
expressions that controls which files in the system that not
@@ -245,7 +245,7 @@
<c>excl_sys_filters</c>. This parameter defaults to
<c>[]</c>.</p>
</item>
- <tag><c><![CDATA[incl_app_filters]]></c></tag>
+ <tag><c>incl_app_filters</c></tag>
<item>
<p>This parameter normally contains a list of regular
expressions that controls which application specific files
@@ -256,7 +256,7 @@
parameter defaults to <c>[".*"]</c>.</p>
</item>
- <tag><c><![CDATA[excl_app_filters]]></c></tag>
+ <tag><c>excl_app_filters</c></tag>
<item>
<p>This parameter normally contains a list of regular
expressions that controls which application specific files
@@ -267,7 +267,7 @@
<c>[]</c>.</p>
</item>
- <tag><c><![CDATA[incl_archive_filters]]></c></tag>
+ <tag><c>incl_archive_filters</c></tag>
<item>
<p>This parameter normally contains a list of regular
expressions that controls which top level directories in an
@@ -280,7 +280,7 @@
parameter defaults to <c>[".*"]</c>.</p>
</item>
- <tag><c><![CDATA[excl_archive_filters]]></c></tag>
+ <tag><c>excl_archive_filters</c></tag>
<item>
<p>This parameter normally contains a list of regular
expressions that controls which top level directories in an
@@ -291,7 +291,7 @@
parameter defaults to <c>["^include$","^priv$"]</c>.</p>
</item>
- <tag><c><![CDATA[archive_opts]]></c></tag>
+ <tag><c>archive_opts</c></tag>
<item>
<p>This parameter contains a list of options that are given to
<c>zip:create/3</c> when application specific files are
@@ -307,7 +307,7 @@
supported:</p>
<taglist>
- <tag><c><![CDATA[incl_cond]]></c></tag>
+ <tag><c>incl_cond</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
@@ -318,62 +318,62 @@
supported:</p>
<taglist>
- <tag><c><![CDATA[vsn]]></c></tag>
+ <tag><c>vsn</c></tag>
<item>
<p>The version of the application. In an installed system there may
exist several versions of an application. The <c>vsn</c> parameter
controls which version of the application that will be choosen. If it
is omitted, the latest version will be choosen.</p>
</item>
- <tag><c><![CDATA[mod]]></c></tag>
+ <tag><c>mod</c></tag>
<item>
<p>Module specific configuration. A module has a mandatory
name and module level options that are described below.</p>
</item>
- <tag><c><![CDATA[mod_cond]]></c></tag>
+ <tag><c>mod_cond</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[incl_cond]]></c></tag>
+ <tag><c>incl_cond</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[app_file]]></c></tag>
+ <tag><c>app_file</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[debug_info]]></c></tag>
+ <tag><c>debug_info</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[incl_app_filters]]></c></tag>
+ <tag><c>incl_app_filters</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[excl_app_filters]]></c></tag>
+ <tag><c>excl_app_filters</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[incl_archive_filters]]></c></tag>
+ <tag><c>incl_archive_filters</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[excl_archive_filters]]></c></tag>
+ <tag><c>excl_archive_filters</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
</item>
- <tag><c><![CDATA[archive_opts]]></c></tag>
+ <tag><c>archive_opts</c></tag>
<item>
<p>The value of this parameter overrides the parameter with the
same name on system level.</p>
@@ -384,18 +384,18 @@
supported:</p>
<taglist>
- <tag><c><![CDATA[incl_cond]]></c></tag>
+ <tag><c>incl_cond</c></tag>
<item>
- <p>This parameter controls whether the module is included or not. By
- default the <c>mod_incl</c> parameter on application and system level
- will be used to control whether the module is included or not. The
- value of <c>incl_cond</c> overrides the module inclusion policy.
- <c>include</c> implies that the module is included, while
- <c>exclude</c> implies that the module not is included.
- <c>derived</c> implies that the is included if any included uses the
- module.</p>
+ <p>This parameter controls whether the module is included or not. By
+ default the <c>mod_incl</c> parameter on application and system level
+ will be used to control whether the module is included or not. The
+ value of <c>incl_cond</c> overrides the module inclusion policy.
+ <c>include</c> implies that the module is included, while
+ <c>exclude</c> implies that the module not is included.
+ <c>derived</c> implies that the is included if any included uses the
+ module.</p>
</item>
- <tag><c><![CDATA[debug_info]]></c></tag>
+ <tag><c>debug_info</c></tag>
<item>
<p>The value of this parameter overrides the parameter with
the same name on application level.</p>
@@ -477,7 +477,9 @@ mod_name() = atom()
profile() = development | embedded | standalone
re_regexp() = string()
reason() = string()
-regexps() = [re_regexp()] | {add, [re_regexp()]} | {del, [re_regexp()]}
+regexps() = [re_regexp()]
+ | {add, [re_regexp()]}
+ | {del, [re_regexp()]}
rel_file() = term()
rel_name() = string()
rel_vsn() = string()
@@ -487,7 +489,19 @@ script_file() = term()
server() = server_pid() | options()
server_pid() = pid()
target_dir() = file()
-window_pid() = pid()]]></code>
+window_pid() = pid()
+base_dir() = dir()
+base_file() = file()
+top_dir() = file()
+top_file() = file()
+target_spec() = [target_spec()]
+ | {create_dir, base_dir(), [target_spec()]}
+ | {create_dir, base_dir(), top_dir(), [target_spec()]}
+ | {archive, base_file(), [archive_opt()], [target_spec()]}
+ | {copy_file, base_file()}
+ | {copy_file, base_file(), top_file()}
+ | {write_file, base_file(), iolist()}
+ | {strip_beam_file, base_file()}]]></code>
<marker id="start"></marker>
</section>
@@ -497,9 +511,9 @@ window_pid() = pid()]]></code>
<name>create_target(Server, TargetDir) -> ok | {error, Reason}</name>
<fsummary>Create a target system</fsummary>
<type>
- <v>Server = server()</v>
+ <v>Server = server()</v>
<v>TargetDir = target_dir()</v>
- <v>Reason = reason()</v>
+ <v>Reason = reason()</v>
</type>
<desc><p>Create a target system. Gives the same result as
<c>{ok,TargetSpec}=reltool:get_target_spec(Server)</c> and
@@ -604,6 +618,17 @@ window_pid() = pid()]]></code>
</func>
<func>
+ <name>get_status(Server) -> {ok, [Warning]} | {error, Reason}</name>
+ <fsummary>Get contents of a release file</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>Warning = string()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Get status about the configuration</p></desc>
+ </func>
+
+ <func>
<name>get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name>
<fsummary>Start server process with options</fsummary>
<type>
diff --git a/lib/reltool/doc/src/reltool_examples.xml b/lib/reltool/doc/src/reltool_examples.xml
index d6db246f6c..bce9413b52 100644
--- a/lib/reltool/doc/src/reltool_examples.xml
+++ b/lib/reltool/doc/src/reltool_examples.xml
@@ -249,11 +249,11 @@ Eshell V5.7.3 (abort with ^G)
<pre>
5&gt; {ok, Server} = reltool:start_server([{config, {sys, [{boot_rel, "NAME"},
{rel, "NAME", "VSN",
- [kernel, stdlib, sasl]}]}}]).
+ [sasl]}]}}]).
{ok,&lt;0.1288.0&gt;}
6&gt; reltool:get_config(Server).
{ok,{sys,[{boot_rel,"NAME"},
- {rel,"NAME","VSN",[kernel,stdlib,sasl]}]}}
+ {rel,"NAME","VSN",[sasl]}]}}
7&gt; reltool:get_rel(Server, "NAME").
{ok,{release,{"NAME","VSN"},
{erts,"5.7"},
diff --git a/lib/reltool/src/Makefile b/lib/reltool/src/Makefile
index 7fac7cbf88..4e6a112b7e 100644
--- a/lib/reltool/src/Makefile
+++ b/lib/reltool/src/Makefile
@@ -28,7 +28,6 @@ include ../vsn.mk
VSN = $(RELTOOL_VSN)
APP_VSN = "reltool-$(VSN)"
-
# ----------------------------------------------------
# Release directory specification
# ----------------------------------------------------
@@ -39,25 +38,20 @@ RELSYSDIR = $(RELEASE_PATH)/lib/reltool-$(VSN)
# Target Specs
# ----------------------------------------------------
-MODULES = \
- reltool \
- reltool_app_win \
- reltool_fgraph \
- reltool_fgraph_win \
- reltool_mod_win \
- reltool_sys_win \
- reltool_server \
- reltool_target \
- reltool_utils
-
-HRL_FILES =
-
-INTERNAL_HRL_FILES = reltool.hrl reltool_fgraph.hrl
+include files.mk
ERL_FILES = $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+APP_FILE = reltool.app
+APP_SRC = $(APP_FILE).src
+APP_TARGET = $(EBIN)/$(APP_FILE)
+
+APPUP_FILE = reltool.appup
+APPUP_SRC = $(APPUP_FILE).src
+APPUP_TARGET = $(EBIN)/$(APPUP_FILE)
+
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
@@ -69,15 +63,28 @@ ERL_COMPILE_FLAGS += +'{parse_transform,sys_pre_attributes}' \
# Targets
# ----------------------------------------------------
-debug opt: $(TARGET_FILES) $(HRL_FILES)
+debug:
+ @${MAKE} TYPE=debug opt
+
+opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
clean:
- rm -f $(TARGET_FILES)
+ rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
rm -f core
docs:
# ----------------------------------------------------
+# Special Build Targets
+# ----------------------------------------------------
+
+$(APP_TARGET): $(APP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
+ sed -e 's;%VSN%;$(VSN);' $< > $@
+
+# ----------------------------------------------------
# Dependencies
# ----------------------------------------------------
@@ -94,6 +101,7 @@ release_spec: opt
$(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
$(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(APP_TARGET) $(APPUP_TARGET) $(RELSYSDIR)/ebin
release_docs_spec:
diff --git a/lib/reltool/src/files.mk b/lib/reltool/src/files.mk
new file mode 100644
index 0000000000..99a1f1c14a
--- /dev/null
+++ b/lib/reltool/src/files.mk
@@ -0,0 +1,32 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+# %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%
+
+MODULES = \
+ reltool \
+ reltool_app_win \
+ reltool_fgraph \
+ reltool_fgraph_win \
+ reltool_mod_win \
+ reltool_sys_win \
+ reltool_server \
+ reltool_target \
+ reltool_utils
+
+HRL_FILES =
+
+INTERNAL_HRL_FILES = reltool.hrl reltool_fgraph.hrl
diff --git a/lib/reltool/src/reltool.app.src b/lib/reltool/src/reltool.app.src
index f83042c157..b80753e8fc 100644
--- a/lib/reltool/src/reltool.app.src
+++ b/lib/reltool/src/reltool.app.src
@@ -1,37 +1,38 @@
%% This is an -*- erlang -*- file.
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
-%% %CopyrightEnd%
%%
+%% %CopyrightEnd%
{application, reltool,
- [{description, "Release management tool"},
- {vsn, "%VSN%"},
- {modules, [
- reltool,
- reltool_app,
- reltool_fgraph,
- reltool_fgraph_win,
- reltool_gen,
- reltool_mod,
- reltool_sys,
- reltool_server,
- reltool_utils
- ]},
- {applications, [kernel, stdlib]}
- ]
-}.
+ [{description, "Reltool the release management tool"},
+ {vsn, "%VSN%"},
+ {modules,
+ [
+ reltool_app_win,
+ reltool,
+ reltool_fgraph,
+ reltool_fgraph_win,
+ reltool_mod_win,
+ reltool_server,
+ reltool_sys_win,
+ reltool_target,
+ reltool_utils
+ ]},
+ {registered, []},
+ {applications, [stdlib, kernel]},
+ {env, []}
+ ]}.
diff --git a/lib/reltool/src/reltool.appup.src b/lib/reltool/src/reltool.appup.src
new file mode 100644
index 0000000000..c02edd2afb
--- /dev/null
+++ b/lib/reltool/src/reltool.appup.src
@@ -0,0 +1,22 @@
+%% This is an -*- erlang -*- file.
+%%
+%% %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%
+
+{"%VSN%",
+ [ ]
+}.
diff --git a/lib/reltool/src/reltool.erl b/lib/reltool/src/reltool.erl
index e6a8bca069..9dd0a24f46 100644
--- a/lib/reltool/src/reltool.erl
+++ b/lib/reltool/src/reltool.erl
@@ -20,9 +20,8 @@
%% Public
-export([
- main/1, % Escript
start/0, start/1, start_link/1, debug/0, % GUI
- start_server/1, get_server/1, stop/1,
+ start_server/1, get_server/1, get_status/1, stop/1,
get_config/1, get_config/3, get_rel/2, get_script/2,
create_target/2, get_target_spec/1, eval_target_spec/3,
install/2
@@ -32,39 +31,26 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Main function for escript
--spec main([escript_arg()]) -> ok.
-main(_) ->
- process_flag(trap_exit, true),
- {ok, WinPid} = start_link([]),
- receive
- {'EXIT', WinPid, shutdown} ->
- ok;
- {'EXIT', WinPid, normal} ->
- ok;
- {'EXIT', WinPid, Reason} ->
- io:format("EXIT: ~p\n", [Reason]),
- erlang:halt(1)
- end.
-
%% Start main window process
--spec start() -> {ok, window_pid()}.
+-spec start() -> {ok, window_pid()} | {error, reason()}.
start() ->
start([]).
%% Start main window process
--spec start(options()) -> {ok, window_pid() | {error, reason()}}.
+-spec start(options()) -> {ok, window_pid()} | {error, reason()}.
start(Options)when is_list(Options) ->
- {ok, WinPid} = start_link(Options),
- unlink(WinPid),
- {ok, WinPid}.
+ case start_link(Options) of
+ {ok, WinPid} ->
+ unlink(WinPid),
+ {ok, WinPid};
+ Other->
+ Other
+ end.
%% Start main window process with wx debugging enabled
--spec debug() -> {ok, window_pid()}.
+-spec debug() -> {ok, window_pid()} | {error, reason()}.
debug() ->
- {ok, WinPid} = start_link([{wx_debug, 2}]),
- unlink(WinPid),
- {ok, WinPid}.
+ start([{wx_debug, 2}]).
%% Start main window process with options
-spec start_link(options()) -> {ok, window_pid() | {error, reason()}}.
@@ -110,20 +96,48 @@ stop(Pid) when is_pid(Pid) ->
end.
%% Internal library function
--spec eval_server(server(), fun((server_pid()) -> term())) ->
+-spec eval_server(server(), boolean(), fun((server_pid()) -> term())) ->
{ok, server_pid()} | {error, reason()}.
-eval_server(Pid, Fun) when is_pid(Pid) ->
+eval_server(Pid, DisplayWarnings, Fun)
+ when is_pid(Pid) ->
Fun(Pid);
-eval_server(Options, Fun) when is_list(Options), is_function(Fun, 1) ->
- case start_server(Options) of
- {ok, Pid} ->
- Res = Fun(Pid),
- stop(Pid),
- Res;
- {error, Reason} ->
- {error, Reason}
+eval_server(Options, DisplayWarnings, Fun)
+ when is_list(Options) ->
+ TrapExit = process_flag(trap_exit, true),
+ Res = case start_server(Options) of
+ {ok, Pid} ->
+ apply_fun(Pid, DisplayWarnings, Fun);
+ {error, Reason} ->
+ {error, Reason}
+ end,
+ process_flag(trap_exit, TrapExit),
+ Res.
+
+apply_fun(Pid, false, Fun) ->
+ Res = Fun(Pid),
+ stop(Pid),
+ Res;
+apply_fun(Pid, true, Fun) ->
+ case get_status(Pid) of
+ {ok, Warnings} ->
+ [io:format("~p: ~s\n", [?APPLICATION, W]) || W <- Warnings],
+ apply_fun(Pid, false, Fun);
+ {error, Reason} ->
+ stop(Pid),
+ {error, Reason}
end.
+%% Get status about the configuration
+-type warning() :: string().
+-spec get_status(server()) ->
+ {ok, [warning()]} | {error, reason()}.
+get_status(PidOrOptions)
+ when is_pid(PidOrOptions); is_list(PidOrOptions) ->
+ eval_server(PidOrOptions, false,
+ fun(Pid) ->
+ reltool_server:get_status(Pid)
+ end).
+
%% Get reltool configuration
-spec get_config(server()) -> {ok, config()} | {error, reason()}.
get_config(PidOrOption) ->
@@ -133,7 +147,7 @@ get_config(PidOrOption) ->
{ok, config()} | {error, reason()}.
get_config(PidOrOptions, InclDef, InclDeriv)
when is_pid(PidOrOptions); is_list(PidOrOptions) ->
- eval_server(PidOrOptions,
+ eval_server(PidOrOptions, true,
fun(Pid) ->
reltool_server:get_config(Pid, InclDef, InclDeriv)
end).
@@ -142,7 +156,7 @@ get_config(PidOrOptions, InclDef, InclDeriv)
-spec get_rel(server(), rel_name()) -> {ok, rel_file()} | {error, reason()}.
get_rel(PidOrOptions, RelName)
when is_pid(PidOrOptions); is_list(PidOrOptions) ->
- eval_server(PidOrOptions,
+ eval_server(PidOrOptions, true,
fun(Pid) -> reltool_server:get_rel(Pid, RelName) end).
%% Get contents of boot script file
@@ -150,21 +164,22 @@ get_rel(PidOrOptions, RelName)
{ok, script_file()} | {error, reason()}.
get_script(PidOrOptions, RelName)
when is_pid(PidOrOptions); is_list(PidOrOptions) ->
- eval_server(PidOrOptions,
+ eval_server(PidOrOptions, true,
fun(Pid) -> reltool_server:get_script(Pid, RelName) end).
%% Generate a target system
-spec create_target(server(), target_dir()) -> ok | {error, reason()}.
create_target(PidOrOptions, TargetDir)
when is_pid(PidOrOptions); is_list(PidOrOptions) ->
- eval_server(PidOrOptions,
+ eval_server(PidOrOptions, true,
fun(Pid) -> reltool_server:gen_target(Pid, TargetDir) end).
%% Generate a target system
-spec get_target_spec(server()) -> {ok, target_spec()} | {error, reason()}.
get_target_spec(PidOrOptions)
when is_pid(PidOrOptions); is_list(PidOrOptions) ->
- eval_server(PidOrOptions, fun(Pid) -> reltool_server:gen_spec(Pid) end).
+ eval_server(PidOrOptions, true,
+ fun(Pid) -> reltool_server:gen_spec(Pid) end).
%% Generate a target system
-spec eval_target_spec(target_spec(), root_dir(), target_dir()) ->
diff --git a/lib/reltool/src/reltool.hrl b/lib/reltool/src/reltool.hrl
index 8a6a2142fd..1a34ced89d 100644
--- a/lib/reltool/src/reltool.hrl
+++ b/lib/reltool/src/reltool.hrl
@@ -17,7 +17,7 @@
%% %CopyrightEnd%
-define(APPLICATION, reltool).
--define(MISSING_APP, '*MISSING*').
+-define(MISSING_APP_NAME, '*MISSING*').
-define(MISSING_APP_TEXT, "*MISSING*").
-type file() :: string().
@@ -104,7 +104,7 @@
-type rel_file() :: term().
-type script_file() :: term().
-type reason() :: string().
--type escript_arg() :: string().
+
-type base_dir() :: dir().
-type base_file() :: file().
-type top_dir() :: file().
@@ -112,10 +112,7 @@
-type target_spec() :: [target_spec()]
| {create_dir, base_dir(), [target_spec()]}
| {create_dir, base_dir(), top_dir(), [target_spec()]}
- | {archive,
- base_file(),
- [archive_opt()],
- [target_spec()]}
+ | {archive, base_file(), [archive_opt()], [target_spec()]}
| {copy_file, base_file()}
| {copy_file, base_file(), top_file()}
| {write_file, base_file(), iolist()}
@@ -139,57 +136,58 @@
-record(mod,
{%% Static
- name :: mod_name(),
- app_name :: app_name(),
- incl_cond :: incl_cond() | undefined,
- debug_info :: debug_info() | undefined,
- is_app_mod :: boolean(),
+ name :: mod_name(),
+ app_name :: app_name(),
+ incl_cond :: incl_cond() | undefined,
+ debug_info :: debug_info() | undefined,
+ is_app_mod :: boolean(),
is_ebin_mod :: boolean(),
- uses_mods :: [mod_name()],
- exists :: boolean(),
+ uses_mods :: [mod_name()],
+ exists :: boolean(),
+
%% Dynamic
- status :: status(),
- used_by_mods :: [mod_name()],
+ status :: status(),
+ used_by_mods :: [mod_name()],
is_pre_included :: boolean() | undefined,
- is_included :: boolean() | undefined
+ is_included :: boolean() | undefined
}).
-record(app_info,
{
- description = "",
- id = "",
- vsn = "",
- modules = [],
- maxP = infinity,
- maxT = infinity,
- registered = [],
- incl_apps = [],
- applications = [],
- env = [],
- mod = undefined,
- start_phases = undefined
+ description = "" :: string(),
+ id = "" :: string(),
+ vsn = "" :: app_vsn(),
+ modules = [] :: [mod_name()],
+ maxP = infinity :: integer() | infinity,
+ maxT = infinity :: integer() | infinity,
+ registered = [] :: [atom()],
+ incl_apps = [] :: [app_name()],
+ applications = [] :: [app_name()],
+ env = [] :: [{atom(), term()}],
+ mod = undefined :: {mod_name(), [term()]} | undefined,
+ start_phases = undefined :: [{atom(), term()}] | undefined
}).
-record(app,
{%% Static info
- name :: app_name(),
- is_escript :: boolean(),
+ name :: app_name(),
+ is_escript :: boolean(),
use_selected_vsn :: boolean() | undefined,
- active_dir :: dir(),
- sorted_dirs :: [dir()],
- vsn :: app_vsn(),
- label :: app_label(),
- info :: #app_info{} | undefined,
- mods :: [#mod{}],
+ active_dir :: dir(),
+ sorted_dirs :: [dir()],
+ vsn :: app_vsn(),
+ label :: app_label(),
+ info :: #app_info{} | undefined,
+ mods :: [#mod{}],
%% Static source cond
- mod_cond :: mod_cond() | undefined,
+ mod_cond :: mod_cond() | undefined,
incl_cond :: incl_cond() | undefined,
%% Static target cond
- debug_info :: debug_info() | undefined,
- app_file :: app_file() | undefined,
- app_type :: app_type(),
+ debug_info :: debug_info() | undefined,
+ app_file :: app_file() | undefined,
+ app_type :: app_type() | undefined,
incl_app_filters :: incl_app_filters(),
excl_app_filters :: excl_app_filters(),
incl_archive_filters :: incl_archive_filters(),
@@ -197,19 +195,20 @@
archive_opts :: [archive_opt()],
%% Dynamic
- status :: status(),
- uses_mods :: [mod_name()],
- used_by_mods :: [mod_name()],
- uses_apps :: [app_name()],
- used_by_apps :: [app_name()],
+ status :: status(),
+ uses_mods :: [mod_name()],
+ used_by_mods :: [mod_name()],
+ uses_apps :: [app_name()],
+ used_by_apps :: [app_name()],
is_pre_included :: boolean(),
- is_included :: boolean()
+ is_included :: boolean(),
+ rels :: [rel_name()]
}).
-record(rel_app,
{
- name :: app_name(),
- app_type :: app_type(),
+ name :: app_name(),
+ app_type :: app_type(),
incl_apps :: [incl_app()]
}).
@@ -231,21 +230,22 @@
apps :: [#app{}],
%% Target cond
- boot_rel :: boot_rel(),
- rels :: [#rel{}],
- emu_name :: emu_name(),
- profile :: profile(),
- incl_sys_filters :: incl_sys_filters(),
- excl_sys_filters :: excl_sys_filters(),
- incl_app_filters :: incl_app_filters(),
- excl_app_filters :: excl_app_filters(),
+ boot_rel :: boot_rel(),
+ rels :: [#rel{}],
+ emu_name :: emu_name(),
+ profile :: profile(),
+ incl_sys_filters :: incl_sys_filters(),
+ excl_sys_filters :: excl_sys_filters(),
+ incl_app_filters :: incl_app_filters(),
+ excl_app_filters :: excl_app_filters(),
incl_archive_filters :: incl_archive_filters(),
excl_archive_filters :: excl_archive_filters(),
- archive_opts :: [archive_opt()],
- relocatable :: boolean(),
- app_type :: app_type(),
- app_file :: app_file(),
- debug_info :: debug_info()
+ archive_opts :: [archive_opt()],
+ relocatable :: boolean(),
+ rel_app_type :: app_type(),
+ embedded_app_type :: app_type() | undefined,
+ app_file :: app_file(),
+ debug_info :: debug_info()
}).
-record(regexp, {source, compiled}).
@@ -268,7 +268,8 @@
-define(DEFAULT_EMU_NAME, "beam").
-define(DEFAULT_PROFILE, development).
-define(DEFAULT_RELOCATABLE, true).
--define(DEFAULT_APP_TYPE, permanent).
+-define(DEFAULT_REL_APP_TYPE, permanent).
+-define(DEFAULT_EMBEDDED_APP_TYPE, undefined).
-define(DEFAULT_APP_FILE, keep).
-define(DEFAULT_DEBUG_INFO, keep).
@@ -282,22 +283,23 @@
-define(DEFAULT_EXCL_APP_FILTERS, []).
-define(EMBEDDED_INCL_SYS_FILTERS, ["^bin",
- "^erts",
- "^lib",
- "^releases"]).
+ "^erts",
+ "^lib",
+ "^releases"]).
-define(EMBEDDED_EXCL_SYS_FILTERS,
["^bin/(erlc|dialyzer|typer)(|\\.exe)\$",
"^erts.*/bin/(erlc|dialyzer|typer)(|\\.exe)\$",
"^erts.*/bin/.*(debug|pdb)"]).
-define(EMBEDDED_INCL_APP_FILTERS, ["^ebin",
- "^priv",
- "^include"]).
+ "^include",
+ "^priv"]).
-define(EMBEDDED_EXCL_APP_FILTERS, []).
+-define(EMBEDDED_APP_TYPE, load).
-define(STANDALONE_INCL_SYS_FILTERS, ["^bin/(erl|epmd)(|\\.exe|\\.ini)\$",
- "^bin/start(|_clean).boot\$",
- "^erts.*/bin",
- "^lib\$"]).
+ "^bin/start(|_clean).boot\$",
+ "^erts.*/bin",
+ "^lib\$"]).
-define(STANDALONE_EXCL_SYS_FILTERS,
["^erts.*/bin/(erlc|dialyzer|typer)(|\\.exe)\$",
"^erts.*/bin/(start|escript|to_erl|run_erl)(|\\.exe)\$",
diff --git a/lib/reltool/src/reltool_mod_win.erl b/lib/reltool/src/reltool_mod_win.erl
index c2544cc2d8..281d2c8ad4 100644
--- a/lib/reltool/src/reltool_mod_win.erl
+++ b/lib/reltool/src/reltool_mod_win.erl
@@ -334,9 +334,9 @@ find_regular_bin(App, Mod) ->
ActiveDir = App#app.active_dir,
SrcDir = filename:join([ActiveDir, "src"]),
ModStr = atom_to_list(Mod#mod.name),
- Base = ModStr ++ ".erl",
- Find = fun(F, _Acc) -> file:read_file(F) end,
- case filelib:fold_files(SrcDir, Base, true, Find, {error, enoent}) of
+ Base = "^" ++ ModStr ++ "\\.erl$",
+ Find = fun(F, _Acc) -> throw(file:read_file(F)) end,
+ case catch filelib:fold_files(SrcDir, Base, true, Find, {error, enoent}) of
{ok, Bin} ->
Bin;
{error, enoent} ->
@@ -514,7 +514,7 @@ select_image(Xref, ModName) ->
{ok, M} = reltool_server:get_mod(Xref, ModName),
Image =
case M#mod.is_included of
- _ when M#mod.app_name =:= ?MISSING_APP -> ?ERR_IMAGE;
+ _ when M#mod.app_name =:= ?MISSING_APP_NAME -> ?ERR_IMAGE;
true -> ?TICK_IMAGE;
false -> ?WARN_IMAGE;
undefined -> ?ERR_IMAGE
@@ -674,7 +674,7 @@ do_goto_function(#state{active_page = P} = S, [FunName]) ->
find_regexp_forward(S, "^" ++ FunName ++ "(");
do_goto_function(S, [ModStr, FunStr]) ->
case reltool_server:get_mod(S#state.xref_pid, list_to_atom(ModStr)) of
- {ok, Mod} when Mod#mod.app_name =/= ?MISSING_APP ->
+ {ok, Mod} when Mod#mod.app_name =/= ?MISSING_APP_NAME ->
S2 = create_code_page(S#state{mod = Mod}, ModStr),
find_regexp_forward(S2, "^" ++ FunStr ++ "(");
{ok, _} ->
diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl
index a7064f7651..039ad56aa8 100644
--- a/lib/reltool/src/reltool_server.erl
+++ b/lib/reltool/src/reltool_server.erl
@@ -132,19 +132,20 @@ init(Options) ->
end.
do_init(Options) ->
- case parse_options(Options) of
- {#state{parent_pid = ParentPid, common = C, sys = Sys} = S, Status} ->
- %% process_flag(trap_exit, (S#state.common)#common.trap_exit),
- proc_lib:init_ack(ParentPid,
- {ok, self(), C, Sys#sys{apps = undefined}}),
- {S2, Status2} = refresh(S, true, Status),
- {S3, Status3} = analyse(S2#state{old_sys = S2#state.sys}, Status2),
- case Status3 of
- {ok, _Warnings} ->
- loop(S3#state{status = Status3, old_status = {ok, []}});
- {error, Reason} ->
- exit(Reason)
- end
+ {S, Status} = parse_options(Options),
+ #state{parent_pid = ParentPid, common = C, sys = Sys} = S,
+
+ %% process_flag(trap_exit, (S#state.common)#common.trap_exit),
+ proc_lib:init_ack(ParentPid,
+ {ok, self(), C, Sys#sys{apps = undefined}}),
+ {S2, Status2} = refresh(S, true, Status),
+ {S3, Status3} =
+ analyse(S2#state{old_sys = S2#state.sys}, Status2),
+ case Status3 of
+ {ok, _Warnings} -> % BUGBUG: handle warnings
+ loop(S3#state{status = Status3, old_status = {ok, []}});
+ {error, Reason} ->
+ exit(Reason)
end.
parse_options(Opts) ->
@@ -161,33 +162,28 @@ parse_options(Opts) ->
rels = reltool_utils:default_rels(),
emu_name = ?DEFAULT_EMU_NAME,
profile = ?DEFAULT_PROFILE,
- incl_sys_filters =
- reltool_utils:decode_regexps(incl_sys_filters,
- ?DEFAULT_INCL_SYS_FILTERS,
- []),
- excl_sys_filters =
- reltool_utils:decode_regexps(excl_sys_filters,
- ?DEFAULT_EXCL_SYS_FILTERS,
- []),
- incl_app_filters =
- reltool_utils:decode_regexps(incl_app_filters,
- ?DEFAULT_INCL_APP_FILTERS,
- []),
- excl_app_filters =
- reltool_utils:decode_regexps(excl_app_filters,
- ?DEFAULT_EXCL_APP_FILTERS,
- []),
+ incl_sys_filters = dec_re(incl_sys_filters,
+ ?DEFAULT_INCL_SYS_FILTERS,
+ []),
+ excl_sys_filters = dec_re(excl_sys_filters,
+ ?DEFAULT_EXCL_SYS_FILTERS,
+ []),
+ incl_app_filters = dec_re(incl_app_filters,
+ ?DEFAULT_INCL_APP_FILTERS,
+ []),
+ excl_app_filters = dec_re(excl_app_filters,
+ ?DEFAULT_EXCL_APP_FILTERS,
+ []),
relocatable = ?DEFAULT_RELOCATABLE,
- app_type = ?DEFAULT_APP_TYPE,
+ rel_app_type = ?DEFAULT_REL_APP_TYPE,
+ embedded_app_type = ?DEFAULT_EMBEDDED_APP_TYPE,
app_file = ?DEFAULT_APP_FILE,
- incl_archive_filters =
- reltool_utils:decode_regexps(incl_archive_filters,
- ?DEFAULT_INCL_ARCHIVE_FILTERS,
- []),
- excl_archive_filters =
- reltool_utils:decode_regexps(excl_archive_filters,
- ?DEFAULT_EXCL_ARCHIVE_FILTERS,
- []),
+ incl_archive_filters = dec_re(incl_archive_filters,
+ ?DEFAULT_INCL_ARCHIVE_FILTERS,
+ []),
+ excl_archive_filters = dec_re(excl_archive_filters,
+ ?DEFAULT_EXCL_ARCHIVE_FILTERS,
+ []),
archive_opts = ?DEFAULT_ARCHIVE_OPTS,
debug_info = ?DEFAULT_DEBUG_INFO},
C2 = #common{sys_debug = [],
@@ -199,6 +195,9 @@ parse_options(Opts) ->
S = #state{options = Opts},
parse_options(Opts, S, C2, Sys, {ok, []}).
+dec_re(Key, Regexps, Old) ->
+ reltool_utils:decode_regexps(Key, Regexps, Old).
+
parse_options([{Key, Val} | KeyVals], S, C, Sys, Status) ->
case Key of
parent ->
@@ -261,6 +260,7 @@ loop(#state{common = C, sys = Sys} = S) ->
{ok, _Warnings} ->
S5#state{status = Status3, old_status = S#state.status};
{error, _} ->
+ %% Keep old state
S
end,
reltool_utils:reply(ReplyTo, Ref, Status3),
@@ -277,9 +277,9 @@ loop(#state{common = C, sys = Sys} = S) ->
Reply =
case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
{value, Rel} ->
- {ok, reltool_target:gen_rel(Rel, Sys)};
+ reltool_target:gen_rel(Rel, Sys);
false ->
- {error, "No such release"}
+ {error, "No such release: " ++ RelName}
end,
reltool_utils:reply(ReplyTo, Ref, Reply),
?MODULE:loop(S);
@@ -292,7 +292,7 @@ loop(#state{common = C, sys = Sys} = S) ->
Vars = [],
reltool_target:gen_script(Rel, Sys, PathFlag, Vars);
false ->
- {error, "No such release"}
+ {error, "No such release: " ++ RelName}
end,
reltool_utils:reply(ReplyTo, Ref, Reply),
?MODULE:loop(S);
@@ -302,7 +302,7 @@ loop(#state{common = C, sys = Sys} = S) ->
[M] ->
{ok, M};
[] ->
- {ok, missing_mod(ModName, ?MISSING_APP)}
+ {ok, missing_mod(ModName, ?MISSING_APP_NAME)}
end,
reltool_utils:reply(ReplyTo, Ref, Reply),
?MODULE:loop(S);
@@ -312,7 +312,8 @@ loop(#state{common = C, sys = Sys} = S) ->
{value, App} ->
{ok, App};
false ->
- {error, enoent}
+ {error, "No such application: " ++
+ atom_to_list(AppName)}
end,
reltool_utils:reply(ReplyTo, Ref, Reply),
?MODULE:loop(S);
@@ -327,6 +328,7 @@ loop(#state{common = C, sys = Sys} = S) ->
reltool_utils:reply(ReplyTo, Ref, {ok, App2, Warnings}),
?MODULE:loop(S3);
{error, Reason} ->
+ %% Keep old state
reltool_utils:reply(ReplyTo, Ref, {error, Reason}),
?MODULE:loop(S)
end;
@@ -372,16 +374,20 @@ loop(#state{common = C, sys = Sys} = S) ->
(Sys2#sys.lib_dirs =/= Sys#sys.lib_dirs) orelse
(Sys2#sys.escripts =/= Sys#sys.escripts),
{S3, Status} = refresh(S2, Force, {ok, []}),
- {S4, Status2} = analyse(S3#state{old_sys = S#state.sys}, Status),
- S6 =
- case Status2 of
- {ok, _Warnings} ->
- S4#state{status = Status2, old_status = S#state.status};
- {error, _} ->
- S
- end,
- reltool_utils:reply(ReplyTo, Ref, Status2),
- ?MODULE:loop(S6);
+ {S4, Status2} =
+ analyse(S3#state{old_sys = S#state.sys}, Status),
+ {S5, Status3} =
+ case Status2 of
+ {ok, _Warnings} -> % BUGBUG: handle warnings
+ {S4#state{status = Status2,
+ old_status = S#state.status},
+ Status2};
+ {error, _} ->
+ %% Keep old state
+ {S, Status2}
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Status3),
+ ?MODULE:loop(S5);
{call, ReplyTo, Ref, get_status} ->
reltool_utils:reply(ReplyTo, Ref, S#state.status),
?MODULE:loop(S);
@@ -427,15 +433,24 @@ do_set_app(#state{sys = Sys} = S, App, Status) ->
Sys2 = Sys#sys{apps = Apps2, escripts = Escripts},
{S#state{sys = Sys2}, Status2}.
-analyse(#state{common = C, sys = #sys{apps = Apps0} = Sys} = S, Status) ->
- Apps = lists:keydelete(?MISSING_APP, #app.name, Apps0),
+analyse(#state{common = C,
+ sys = #sys{apps = Apps0, rels = Rels} = Sys} = S,
+ Status) ->
+ Apps = lists:keydelete(?MISSING_APP_NAME, #app.name, Apps0),
ets:delete_all_objects(C#common.app_tab),
ets:delete_all_objects(C#common.mod_tab),
ets:delete_all_objects(C#common.mod_used_by_tab),
- MissingApp = default_app(?MISSING_APP, "missing"),
+ MissingApp = default_app(?MISSING_APP_NAME, "missing"),
ets:insert(C#common.app_tab, MissingApp),
- Apps2 = lists:map(fun(App) -> app_init_is_included(C, Sys, App) end, Apps),
+ {RevRelApps, Status2} = apps_in_rels(Rels, Apps, Status),
+ RelApps2 = lists:reverse(RevRelApps),
+ {Apps2, Status3} =
+ lists:mapfoldl(fun(App, Acc) ->
+ app_init_is_included(C, Sys, App, RelApps2, Acc)
+ end,
+ Status2,
+ Apps),
Apps3 =
case app_propagate_is_included(C, Sys, Apps2, []) of
[] ->
@@ -452,17 +467,59 @@ analyse(#state{common = C, sys = #sys{apps = Apps0} = Sys} = S, Status) ->
app_propagate_is_used_by(C, Apps3),
Apps4 = read_apps(C, Sys, Apps3, []),
%% io:format("Missing app: ~p\n",
- %% [lists:keysearch(?MISSING_APP, #app.name, Apps4)]),
+ %% [lists:keysearch(?MISSING_APP_NAME, #app.name, Apps4)]),
Sys2 = Sys#sys{apps = Apps4},
- try
- Status2 = verify_config(Sys2, Status),
- {S#state{sys = Sys2}, Status2}
- catch
- throw:{error, Status3} ->
- {S, Status3}
+
+ case verify_config(RelApps2, Sys2, Status3) of
+ {ok, _Warnings} = Status4 ->
+ {S#state{sys = Sys2}, Status4};
+ {error, _} = Status4 ->
+ {S, Status4}
end.
-app_init_is_included(C, Sys, #app{mods = Mods} = A) ->
+apps_in_rels(Rels, Apps, Status) ->
+ lists:foldl(fun(Rel, {RelApps, S}) ->
+ {MoreRelApps, S2} = apps_in_rel(Rel, Apps, S),
+ {MoreRelApps ++ RelApps, S2}
+ end,
+ {[], Status},
+ Rels).
+
+apps_in_rel(#rel{name = RelName, rel_apps = RelApps}, Apps, Status) ->
+ Mandatory = [{RelName, kernel}, {RelName, stdlib}],
+ Other = [{RelName, AppName} ||
+ RA <- RelApps,
+ AppName <- [RA#rel_app.name | RA#rel_app.incl_apps],
+ not lists:keymember(AppName, 2, Mandatory)],
+ more_apps_in_rels(Mandatory ++ Other, Apps, [], Status).
+
+more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc, Status) ->
+ case lists:member(RA, Acc) of
+ true ->
+ more_apps_in_rels(RelApps, Apps, Acc, Status);
+ false ->
+ case lists:keysearch(AppName, #app.name, Apps) of
+ {value, #app{info = #app_info{applications = InfoApps}}} ->
+ Extra = [{RelName, N} || N <- InfoApps],
+ {Acc2, Status2} =
+ more_apps_in_rels(Extra, Apps, [RA | Acc], Status),
+ more_apps_in_rels(RelApps, Apps, Acc2, Status2);
+ false ->
+ Text = lists:concat(["Release ", RelName,
+ " uses non existing application ",
+ AppName]),
+ Status2 = reltool_utils:return_first_error(Status, Text),
+ more_apps_in_rels(RelApps, Apps, Acc, Status2)
+ end
+ end;
+more_apps_in_rels([], _Apps, Acc, Status) ->
+ {Acc, Status}.
+
+app_init_is_included(C,
+ Sys,
+ #app{name = AppName, mods = Mods} = A,
+ RelApps,
+ Status) ->
AppCond =
case A#app.incl_cond of
undefined -> Sys#sys.incl_cond;
@@ -473,24 +530,37 @@ app_init_is_included(C, Sys, #app{mods = Mods} = A) ->
undefined -> Sys#sys.mod_cond;
_ -> A#app.mod_cond
end,
- IsIncl =
- case AppCond of
- include -> true;
- exclude -> false;
- derived -> undefined
+ Rels = [RelName || {RelName, AN} <- RelApps, AN =:= AppName],
+ {Default, IsPreIncl, IsIncl, Status2} =
+ case {AppCond, Rels} of
+ {include, _} ->
+ {undefined, true, true, Status};
+ {exclude, []} ->
+ {undefined, false, false, Status};
+ {exclude, [RelName | _]} -> % App is included in at least one rel
+ Text = lists:concat(["Application ", AppName, " is used "
+ "in release ", RelName, " and cannot "
+ "be excluded"]),
+ TmpStatus = reltool_utils:return_first_error(Status, Text),
+ {undefined, false, false, TmpStatus};
+ {derived, []} ->
+ {undefined, undefined, undefined, Status};
+ {derived, [_ | _]} -> % App is included in at least one rel
+ {true, undefined, true, Status}
end,
- A2 = A#app{is_pre_included = IsIncl, is_included = IsIncl},
+ A2 = A#app{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,
- undefined)
+ Default)
end,
Mods),
- %%app_mod_init_is_included(C, AppName, Info, ModCond, AppCond),
- A2.
+ {A2, Status2}.
mod_init_is_included(C, M, ModCond, AppCond, Default) ->
%% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]),
@@ -635,7 +705,7 @@ mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) ->
Acc2)
end;
[] ->
- M = missing_mod(ModName, ?MISSING_APP),
+ M = missing_mod(ModName, ?MISSING_APP_NAME),
M2 = M#mod{is_included = true},
ets:insert(C#common.mod_tab, M2),
ets:insert(C#common.mod_used_by_tab, {UsedByName, ModName}),
@@ -646,7 +716,7 @@ mod_mark_is_included(_C, _Sys, _UsedByName, [], Acc) ->
Acc.
app_propagate_is_used_by(C, [#app{mods = Mods, name = Name} | Apps]) ->
- case Name =:= ?MISSING_APP of
+ case Name =:= ?MISSING_APP_NAME of
true -> ok;
false -> ok
end,
@@ -672,8 +742,6 @@ mod_propagate_is_used_by(_C, []) ->
read_apps(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc) ->
{Mods2, IsIncl2} = read_apps(C, Sys, A, Mods, [], IsIncl),
- %% reltool_utils:print(A#app.name, stdlib, "Mods2: ~p\n",
- %% [[M#mod.status || M <- Mods2]]),
Status =
case lists:keysearch(missing, #mod.status, Mods2) of
{value, _} -> missing;
@@ -736,9 +804,7 @@ filter_app(A) ->
Mods = [M#mod{is_app_mod = undefined,
is_ebin_mod = undefined,
uses_mods = undefined,
- exists = false,
- is_pre_included = undefined,
- is_included = undefined} ||
+ exists = false} ||
M <- A#app.mods,
M#mod.incl_cond =/= undefined],
if
@@ -747,8 +813,7 @@ filter_app(A) ->
label = undefined,
info = undefined,
mods = [],
- uses_mods = undefined,
- is_included = undefined}};
+ uses_mods = undefined}};
Mods =:= [],
A#app.mod_cond =:= undefined,
A#app.incl_cond =:= undefined,
@@ -778,8 +843,7 @@ filter_app(A) ->
label = undefined,
info = undefined,
mods = Mods,
- uses_mods = undefined,
- is_included = undefined}}
+ uses_mods = undefined}}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -816,16 +880,16 @@ refresh_app(#app{name = AppName,
end,
%% Add non-existing modules
+ AppInfoMods = AppInfo#app_info.modules,
AppModNames =
case AppInfo#app_info.mod of
{StartModName, _} ->
- case lists:member(StartModName,
- AppInfo#app_info.modules) of
- true -> AppInfo#app_info.modules;
- false -> [StartModName | AppInfo#app_info.modules]
+ case lists:member(StartModName, AppInfoMods) of
+ true -> AppInfoMods;
+ false -> [StartModName | AppInfoMods]
end;
undefined ->
- AppInfo#app_info.modules
+ AppInfoMods
end,
MissingMods = add_missing_mods(AppName, EbinMods, AppModNames),
@@ -862,11 +926,15 @@ read_app_info(AppFileOrBin, AppFile, AppName, DefaultVsn, Status) ->
parse_app_info(AppFile, Info, AI, Status);
{ok, _BadApp} ->
Text = lists:concat([AppName,
- ": Illegal contents in app file ", AppFile]),
+ ": Illegal contents in app file ", AppFile,
+ ", application tuple with arity 3 expected."]),
{missing_app_info(DefaultVsn),
reltool_utils:add_warning(Status, Text)};
- {error, Text} when Text =:= EnoentText->
- {missing_app_info(DefaultVsn), Status};
+ {error, Text} when Text =:= EnoentText ->
+ Text2 = lists:concat([AppName,
+ ": Missing app file ", AppFile, "."]),
+ {missing_app_info(DefaultVsn),
+ reltool_utils:add_warning(Status, Text2)};
{error, Text} ->
Text2 = lists:concat([AppName,
": Cannot parse app file ",
@@ -1045,7 +1113,7 @@ do_get_config(S, InclDef, InclDeriv) ->
false -> shrink_sys(S);
true -> S
end,
- {ok, reltool_target:gen_config(S2#state.sys, InclDef)}.
+ reltool_target:gen_config(S2#state.sys, InclDef).
do_save_config(S, Filename, InclDef, InclDeriv) ->
{ok, Config} = do_get_config(S, InclDef, InclDeriv),
@@ -1072,6 +1140,7 @@ do_load_config(S, SysConfig) ->
{ok, _Warnings2} ->
S3#state{status = Status3, old_status = S#state.status};
{error, _} ->
+ %% Keep old state
S
end,
{S4, Status3};
@@ -1093,29 +1162,36 @@ read_config(OldSys, Filename, Status) when is_list(Filename) ->
{error, Reason} ->
Text = file:format_error(Reason),
{OldSys,
- reltool_utils:return_first_error(Status, "File access: " ++
- Text)}
+ reltool_utils:return_first_error(Status,
+ "Illegal config file " ++
+ Filename ++ ": " ++ Text)}
end;
read_config(OldSys, {sys, KeyVals}, Status) ->
{NewSys, Status2} =
- try
- decode(OldSys#sys{apps = [], rels = []}, KeyVals, Status)
- catch
- throw:{error, Text} ->
- {OldSys, reltool_utils:return_first_error(Status, Text)}
- end,
- Apps = [A#app{mods = lists:sort(A#app.mods)} || A <- NewSys#sys.apps],
- case NewSys#sys.rels of
- [] -> Rels = reltool_utils:default_rels();
- Rels -> ok
- end,
- NewSys2 = NewSys#sys{apps = lists:sort(Apps), rels = lists:sort(Rels)},
- case lists:keysearch(NewSys2#sys.boot_rel, #rel.name, NewSys2#sys.rels) of
- {value, _} ->
- {NewSys2, Status2};
- false ->
- Text2 = "Missing rel: " ++ NewSys2#sys.boot_rel,
- {OldSys, reltool_utils:return_first_error(Status2, Text2)}
+ decode(OldSys#sys{apps = [], rels = []}, KeyVals, Status),
+ case Status2 of
+ {ok, _Warnings} -> % BUGBUG: handle warnings
+ Apps = [A#app{mods = lists:sort(A#app.mods)} ||
+ A <- NewSys#sys.apps],
+ case NewSys#sys.rels of
+ [] -> Rels = reltool_utils:default_rels();
+ Rels -> ok
+ end,
+ NewSys2 = NewSys#sys{apps = lists:sort(Apps),
+ rels = lists:sort(Rels)},
+ case lists:keysearch(NewSys2#sys.boot_rel,
+ #rel.name,
+ NewSys2#sys.rels) of
+ {value, _} ->
+ {NewSys2, Status2};
+ false ->
+ Text2 = lists:concat(["Release " ++ NewSys2#sys.boot_rel,
+ " is mandatory (used as boot_rel)"]),
+ {OldSys, reltool_utils:return_first_error(Status2, Text2)}
+ end;
+ {error, _} ->
+ %% Keep old state
+ {OldSys, Status2}
end;
read_config(OldSys, BadConfig, Status) ->
Text = lists:flatten(io_lib:format("~p", [BadConfig])),
@@ -1160,120 +1236,96 @@ decode(#sys{} = Sys, [{Key, Val} | KeyVals], Status) ->
{Sys#sys{root_dir = Val}, Status};
lib_dirs when is_list(Val) ->
{Sys#sys{lib_dirs = Val}, Status};
- mod_cond when Val =:= all; Val =:= app;
- Val =:= ebin; Val =:= derived;
+ mod_cond when Val =:= all;
+ Val =:= app;
+ Val =:= ebin;
+ Val =:= derived;
Val =:= none ->
{Sys#sys{mod_cond = Val}, Status};
- incl_cond when Val =:= include; Val =:= exclude;
+ incl_cond when Val =:= include;
+ Val =:= exclude;
Val =:= derived ->
{Sys#sys{incl_cond = Val}, Status};
boot_rel when is_list(Val) ->
{Sys#sys{boot_rel = Val}, Status};
emu_name when is_list(Val) ->
{Sys#sys{emu_name = Val}, Status};
- profile when Val =:= development ->
- Val = ?DEFAULT_PROFILE, % assert,
- {Sys#sys{profile = Val,
- incl_sys_filters =
- reltool_utils:decode_regexps(incl_sys_filters,
- ?DEFAULT_INCL_SYS_FILTERS,
- Sys#sys.incl_sys_filters),
- excl_sys_filters =
- reltool_utils:decode_regexps(excl_sys_filters,
- ?DEFAULT_EXCL_SYS_FILTERS,
- Sys#sys.excl_sys_filters),
- incl_app_filters =
- reltool_utils:decode_regexps(incl_app_filters,
- ?DEFAULT_INCL_APP_FILTERS,
- Sys#sys.incl_app_filters),
- excl_app_filters =
- reltool_utils:decode_regexps(excl_app_filters,
- ?DEFAULT_EXCL_APP_FILTERS,
- Sys#sys.excl_app_filters)},
- Status};
- profile when Val =:= embedded ->
- {Sys#sys{profile = Val,
- incl_sys_filters =
- reltool_utils:decode_regexps(incl_sys_filters,
- ?EMBEDDED_INCL_SYS_FILTERS,
- Sys#sys.incl_sys_filters),
- excl_sys_filters =
- reltool_utils:decode_regexps(excl_sys_filters,
- ?EMBEDDED_EXCL_SYS_FILTERS,
- Sys#sys.excl_sys_filters),
- incl_app_filters =
- reltool_utils:decode_regexps(incl_app_filters,
- ?EMBEDDED_INCL_APP_FILTERS,
- Sys#sys.incl_app_filters),
- excl_app_filters =
- reltool_utils:decode_regexps(excl_app_filters,
- ?EMBEDDED_EXCL_APP_FILTERS,
- Sys#sys.excl_app_filters)},
- Status};
- profile when Val =:= standalone ->
- {Sys#sys{profile = Val,
- incl_sys_filters =
- reltool_utils:decode_regexps(incl_sys_filters,
- ?STANDALONE_INCL_SYS_FILTERS,
- Sys#sys.incl_sys_filters),
- excl_sys_filters =
- reltool_utils:decode_regexps(excl_sys_filters,
- ?STANDALONE_EXCL_SYS_FILTERS,
- Sys#sys.excl_sys_filters),
- incl_app_filters =
- reltool_utils:decode_regexps(incl_app_filters,
- ?STANDALONE_INCL_APP_FILTERS,
- Sys#sys.incl_app_filters),
- excl_app_filters =
- reltool_utils:decode_regexps(excl_app_filters,
- ?STANDALONE_EXCL_APP_FILTERS,
- Sys#sys.excl_app_filters)},
+ profile when Val =:= development;
+ Val =:= embedded;
+ Val =:= standalone ->
+ InclSys = reltool_utils:choose_default(incl_sys_filters, Val, false),
+ ExclSys = reltool_utils:choose_default(excl_sys_filters, Val, false),
+ InclApp = reltool_utils:choose_default(incl_app_filters, Val, false),
+ ExclApp = reltool_utils:choose_default(excl_app_filters, Val, false),
+ AppType = reltool_utils:choose_default(embedded_app_type, Val, false),
+ {Sys#sys{profile = Val,
+ incl_sys_filters = dec_re(incl_sys_filters,
+ InclSys,
+ Sys#sys.incl_sys_filters),
+ excl_sys_filters = dec_re(excl_sys_filters,
+ ExclSys,
+ Sys#sys.excl_sys_filters),
+ incl_app_filters = dec_re(incl_app_filters,
+ InclApp,
+ Sys#sys.incl_app_filters),
+ excl_app_filters = dec_re(excl_app_filters,
+ ExclApp,
+ Sys#sys.excl_app_filters),
+ embedded_app_type = AppType},
Status};
incl_sys_filters ->
{Sys#sys{incl_sys_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- Sys#sys.incl_sys_filters)},
+ dec_re(Key,
+ Val,
+ Sys#sys.incl_sys_filters)},
Status};
excl_sys_filters ->
{Sys#sys{excl_sys_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- Sys#sys.excl_sys_filters)},
+ dec_re(Key,
+ Val,
+ Sys#sys.excl_sys_filters)},
Status};
incl_app_filters ->
{Sys#sys{incl_app_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- Sys#sys.incl_app_filters)},
+ dec_re(Key,
+ Val,
+ Sys#sys.incl_app_filters)},
Status};
excl_app_filters ->
{Sys#sys{excl_app_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- Sys#sys.excl_app_filters)},
+ dec_re(Key,
+ Val,
+ Sys#sys.excl_app_filters)},
Status};
incl_archive_filters ->
{Sys#sys{incl_archive_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- Sys#sys.incl_archive_filters)},
+ dec_re(Key,
+ Val,
+ Sys#sys.incl_archive_filters)},
Status};
excl_archive_filters ->
{Sys#sys{excl_archive_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- Sys#sys.excl_archive_filters)},
+ dec_re(Key,
+ Val,
+ Sys#sys.excl_archive_filters)},
Status};
archive_opts when is_list(Val) ->
{Sys#sys{archive_opts = Val}, Status};
relocatable when Val =:= true; Val =:= false ->
{Sys#sys{relocatable = Val}, Status};
- app_type when Val =:= permanent;
- Val =:= transient;
- Val =:= temporary;
- Val =:= load; Val =:= none ->
- {Sys#sys{app_type = Val}, Status};
+ rel_app_type when Val =:= permanent;
+ Val =:= transient;
+ Val =:= temporary;
+ Val =:= load;
+ Val =:= none ->
+ {Sys#sys{rel_app_type = Val}, Status};
+ embedded_app_type when Val =:= permanent;
+ Val =:= transient;
+ Val =:= temporary;
+ Val =:= load;
+ Val =:= none;
+ Val =:= undefined ->
+ {Sys#sys{embedded_app_type = Val}, Status};
app_file when Val =:= keep; Val =:= strip, Val =:= all ->
{Sys#sys{app_file = Val}, Status};
debug_info when Val =:= keep; Val =:= strip ->
@@ -1303,38 +1355,39 @@ decode(#app{} = App, [{Key, Val} | KeyVals], Status) ->
Val =:= strip ->
{App#app{debug_info = Val}, Status};
app_file when Val =:= keep;
- Val =:= strip,
+ Val =:= strip;
Val =:= all ->
{App#app{app_file = Val}, Status};
app_type when Val =:= permanent;
Val =:= transient;
Val =:= temporary;
Val =:= load;
- Val =:= none ->
+ Val =:= none;
+ Val =:= undefined ->
{App#app{app_type = Val}, Status};
incl_app_filters ->
{App#app{incl_app_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- App#app.incl_app_filters)},
+ dec_re(Key,
+ Val,
+ App#app.incl_app_filters)},
Status};
excl_app_filters ->
{App#app{excl_app_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- App#app.excl_app_filters)},
+ dec_re(Key,
+ Val,
+ App#app.excl_app_filters)},
Status};
incl_archive_filters ->
{App#app{incl_archive_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- App#app.incl_archive_filters)},
+ dec_re(Key,
+ Val,
+ App#app.incl_archive_filters)},
Status};
excl_archive_filters ->
{App#app{excl_archive_filters =
- reltool_utils:decode_regexps(Key,
- Val,
- App#app.excl_archive_filters)},
+ dec_re(Key,
+ Val,
+ App#app.excl_archive_filters)},
Status};
archive_opts when is_list(Val) ->
{App#app{archive_opts = Val}, Status};
@@ -1439,54 +1492,53 @@ merge_config(OldSys, NewSys, Force, Status) ->
apps = PatchedApps},
{NewSys2, Status5}.
-verify_config(Sys, Status) ->
- case lists:keymember(Sys#sys.boot_rel, #rel.name, Sys#sys.rels) of
+verify_config(RelApps, #sys{boot_rel = BootRel, rels = Rels, apps = Apps}, Status) ->
+ case lists:keymember(BootRel, #rel.name, Rels) of
true ->
- lists:foreach(fun(Rel)-> check_rel(Rel, Sys, Status) end,
- Sys#sys.rels),
- Status;
+ Status2 = lists:foldl(fun(RA, Acc) ->
+ check_app(RA, Apps, Acc) end,
+ Status,
+ RelApps),
+ lists:foldl(fun(#rel{name = RelName}, Acc)->
+ check_rel(RelName, RelApps, Acc)
+ end,
+ Status2,
+ Rels);
false ->
- Text = lists:concat([Sys#sys.boot_rel, ": release is mandatory"]),
- Status2 = reltool_utils:return_first_error(Status, Text),
- throw({error, Status2})
+ Text = lists:concat(["Release ", BootRel,
+ " is mandatory (used as boot_rel)"]),
+ reltool_utils:return_first_error(Status, Text)
+ end.
+
+check_app({RelName, AppName}, Apps, Status) ->
+ case lists:keysearch(AppName, #app.name, Apps) of
+ {value, App} when App#app.is_pre_included ->
+ Status;
+ {value, App} when App#app.is_included ->
+ Status;
+ _ ->
+ Text = lists:concat(["Release ", RelName,
+ " uses non included application ",
+ AppName]),
+ reltool_utils:return_first_error(Status, Text)
end.
-check_rel(#rel{name = RelName, rel_apps = RelApps},
- #sys{apps = Apps},
- Status) ->
+check_rel(RelName, RelApps, Status) ->
EnsureApp =
- fun(AppName) ->
- case lists:keymember(AppName, #rel_app.name, RelApps) of
+ fun(AppName, Acc) ->
+ case lists:member({RelName, AppName}, RelApps) of
true ->
- ok;
+ Acc;
false ->
- Text = lists:concat([RelName, ": ", AppName,
- " is not included."]),
- Status2 =
- reltool_utils:return_first_error(Status, Text),
- throw({error, Status2})
- end
- end,
- EnsureApp(kernel),
- EnsureApp(stdlib),
- CheckRelApp =
- fun(#rel_app{name = AppName}) ->
- case lists:keysearch(AppName, #app.name, Apps) of
- {value, App} when App#app.is_pre_included ->
- ok;
- {value, App} when App#app.is_included ->
- ok;
- _ ->
- Text =
- lists:concat([RelName, ": uses application ",
- AppName, " that not is included."]),
- Status2 =
- reltool_utils:return_first_error(Status, Text),
- %% throw BUGBUG: add throw
- ({error, Status2})
+ Text = lists:concat(["Mandatory application ",
+ AppName,
+ " is not included in release ",
+ RelName]),
+ reltool_utils:return_first_error(Acc, Text)
end
end,
- lists:foreach(CheckRelApp, RelApps).
+ Mandatory = [kernel, stdlib],
+ lists:foldl(EnsureApp, Status, Mandatory).
patch_erts_version(RootDir, Apps, Status) ->
AppName = erts,
@@ -1507,7 +1559,7 @@ patch_erts_version(RootDir, Apps, Status) ->
{Apps, Status}
end;
false ->
- Text = "erts cannnot be found in the root directory " ++ RootDir,
+ Text = "erts cannot be found in the root directory " ++ RootDir,
Status2 = reltool_utils:return_first_error(Status, Text),
{Apps, Status2}
end.
@@ -1813,7 +1865,8 @@ default_app(Name) ->
status = missing,
uses_mods = undefined,
is_pre_included = undefined,
- is_included = undefined}.
+ is_included = undefined,
+ rels = undefined}.
%% Assume that the application are sorted
refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status)
@@ -1842,7 +1895,7 @@ refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status)
when New#app.name > Old#app.name ->
%% No new version. Remove the old.
Status2 =
- case Old#app.name =:= ?MISSING_APP of
+ case Old#app.name =:= ?MISSING_APP_NAME of
true ->
Status;
false ->
@@ -1860,7 +1913,7 @@ refresh_apps([], [New | NewApps], Acc, Force, Status) ->
refresh_apps([Old | OldApps], [], Acc, Force, Status) ->
%% No new version. Remove the old.
Status2 =
- case Old#app.name =:= ?MISSING_APP of
+ case Old#app.name =:= ?MISSING_APP_NAME of
true ->
Status;
false ->
diff --git a/lib/reltool/src/reltool_sys_win.erl b/lib/reltool/src/reltool_sys_win.erl
index 2e226d9147..dbb8e32aa2 100644
--- a/lib/reltool/src/reltool_sys_win.erl
+++ b/lib/reltool/src/reltool_sys_win.erl
@@ -1227,7 +1227,7 @@ create_fgraph_window(S, Title, Nodes, Links) ->
Panel = wxPanel:new(Frame, []),
Options = [{size, {lists:max([100, ?WIN_WIDTH - 100]), ?WIN_HEIGHT}}],
{Server, Fgraph} = reltool_fgraph_win:new(Panel, Options),
- Choose = fun(?MISSING_APP) -> alternate;
+ Choose = fun(?MISSING_APP_NAME) -> alternate;
(_) -> default
end,
[reltool_fgraph_win:add_node(Server, N, Choose(N)) || N <- Nodes],
diff --git a/lib/reltool/src/reltool_target.erl b/lib/reltool/src/reltool_target.erl
index e079a02f3c..dd6f75b9fc 100644
--- a/lib/reltool/src/reltool_target.erl
+++ b/lib/reltool/src/reltool_target.erl
@@ -31,7 +31,7 @@
gen_target/2,
install/2
]).
--compile(export_all).
+
-include("reltool.hrl").
-include_lib("kernel/include/file.hrl").
@@ -64,54 +64,60 @@ kernel_processes(KernelApp) ->
%% Generate the contents of a config file
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-gen_config(#sys{root_dir = RootDir,
- lib_dirs = LibDirs,
- mod_cond = ModCond,
- incl_cond = AppCond,
- apps = Apps,
- boot_rel = BootRel,
- rels = Rels,
- emu_name = EmuName,
- profile = Profile,
- incl_sys_filters = InclSysFiles,
- excl_sys_filters = ExclSysFiles,
- incl_app_filters = InclAppFiles,
- excl_app_filters = ExclAppFiles,
- incl_archive_filters = InclArchiveDirs,
- excl_archive_filters = ExclArchiveDirs,
- archive_opts = ArchiveOpts,
- relocatable = Relocatable,
- app_type = AppType,
- app_file = AppFile,
- debug_info = DebugInfo},
- InclDefs) ->
+gen_config(Sys, InclDefs) ->
+ {ok, do_gen_config(Sys, InclDefs)}.
+
+do_gen_config(#sys{root_dir = RootDir,
+ lib_dirs = LibDirs,
+ mod_cond = ModCond,
+ incl_cond = AppCond,
+ apps = Apps,
+ boot_rel = BootRel,
+ rels = Rels,
+ emu_name = EmuName,
+ profile = Profile,
+ incl_sys_filters = InclSysFiles,
+ excl_sys_filters = ExclSysFiles,
+ incl_app_filters = InclAppFiles,
+ excl_app_filters = ExclAppFiles,
+ incl_archive_filters = InclArchiveDirs,
+ excl_archive_filters = ExclArchiveDirs,
+ archive_opts = ArchiveOpts,
+ relocatable = Relocatable,
+ rel_app_type = RelAppType,
+ embedded_app_type = InclAppType,
+ app_file = AppFile,
+ debug_info = DebugInfo},
+ InclDefs) ->
ErtsItems =
case lists:keysearch(erts, #app.name, Apps) of
{value, Erts} ->
- [{erts, gen_config(Erts, InclDefs)}];
+ [{erts, do_gen_config(Erts, InclDefs)}];
false ->
[]
end,
AppsItems =
- [{app, A#app.name, gen_config(A, InclDefs)}
+ [do_gen_config(A, InclDefs)
|| A <- Apps,
- A#app.name =/= ?MISSING_APP,
+ A#app.name =/= ?MISSING_APP_NAME,
A#app.name =/= erts,
- A#app.is_included =:= true,
- A#app.is_escript =/= true],
+ not A#app.is_escript],
EscriptItems = [{escript,
A#app.active_dir,
emit(incl_cond, A#app.incl_cond, undefined, InclDefs)}
- || A <- Apps, A#app.is_escript],
+ || A <- Apps, A#app.is_escript],
DefaultRels = reltool_utils:default_rels(),
RelsItems =
- case {[{rel, R#rel.name, R#rel.vsn, gen_config(R, InclDefs)} ||
- R <- Rels],
- [{rel, R#rel.name, R#rel.vsn, gen_config(R, InclDefs)} ||
- R <- DefaultRels]} of
- {RI, RI} -> [];
- {RI, _} -> RI
- end,
+ [{rel, R#rel.name, R#rel.vsn, do_gen_config(R, InclDefs)} ||
+ R <- Rels],
+ DefaultRelsItems =
+ [{rel, R#rel.name, R#rel.vsn, do_gen_config(R, InclDefs)} ||
+ R <- DefaultRels],
+ RelsItems2 =
+ case InclDefs of
+ true -> RelsItems;
+ false -> RelsItems -- DefaultRelsItems
+ end,
X = fun(List) -> [Re || #regexp{source = Re} <- List] end,
{sys,
emit(root_dir, RootDir, code:root_dir(), InclDefs) ++
@@ -120,78 +126,103 @@ gen_config(#sys{root_dir = RootDir,
emit(mod_cond, ModCond, ?DEFAULT_MOD_COND, InclDefs) ++
emit(incl_cond, AppCond, ?DEFAULT_INCL_COND, InclDefs) ++
ErtsItems ++
- AppsItems ++
+ lists:flatten(AppsItems) ++
emit(boot_rel, BootRel, ?DEFAULT_REL_NAME, InclDefs) ++
- RelsItems ++
+ RelsItems2 ++
emit(emu_name, EmuName, ?DEFAULT_EMU_NAME, InclDefs) ++
emit(relocatable, Relocatable, ?DEFAULT_RELOCATABLE, InclDefs) ++
emit(profile, Profile, ?DEFAULT_PROFILE, InclDefs) ++
- emit(incl_sys_filters, X(InclSysFiles), ?DEFAULT_INCL_SYS_FILTERS, InclDefs) ++
- emit(excl_sys_filters, X(ExclSysFiles), ?DEFAULT_EXCL_SYS_FILTERS, InclDefs) ++
- emit(incl_app_filters, X(InclAppFiles), ?DEFAULT_INCL_APP_FILTERS, InclDefs) ++
- emit(excl_app_filters, X(ExclAppFiles), ?DEFAULT_EXCL_APP_FILTERS, InclDefs) ++
+ emit(incl_sys_filters, X(InclSysFiles), reltool_utils:choose_default(incl_sys_filters, Profile, InclDefs), InclDefs) ++
+ emit(excl_sys_filters, X(ExclSysFiles), reltool_utils:choose_default(excl_sys_filters, Profile, InclDefs), InclDefs) ++
+ emit(incl_app_filters, X(InclAppFiles), reltool_utils:choose_default(incl_app_filters, Profile, InclDefs), InclDefs) ++
+ emit(excl_app_filters, X(ExclAppFiles), reltool_utils:choose_default(excl_app_filters, Profile, InclDefs), InclDefs) ++
emit(incl_archive_filters, X(InclArchiveDirs), ?DEFAULT_INCL_ARCHIVE_FILTERS, InclDefs) ++
emit(excl_archive_filters, X(ExclArchiveDirs), ?DEFAULT_EXCL_ARCHIVE_FILTERS, InclDefs) ++
emit(archive_opts, ArchiveOpts, ?DEFAULT_ARCHIVE_OPTS, InclDefs) ++
- emit(app_type, AppType, ?DEFAULT_APP_TYPE, InclDefs) ++
+ emit(rel_app_type, RelAppType, ?DEFAULT_REL_APP_TYPE, InclDefs) ++
+ emit(embedded_app_type, InclAppType, reltool_utils:choose_default(embedded_app_type, Profile, InclDefs), InclDefs) ++
emit(app_file, AppFile, ?DEFAULT_APP_FILE, InclDefs) ++
emit(debug_info, DebugInfo, ?DEFAULT_DEBUG_INFO, InclDefs)};
-gen_config(#app{name = _Name,
- mod_cond = ModCond,
- incl_cond = AppCond,
- debug_info = DebugInfo,
- app_file = AppFile,
- incl_app_filters = InclAppFiles,
- excl_app_filters = ExclAppFiles,
- incl_archive_filters = InclArchiveDirs,
- excl_archive_filters = ExclArchiveDirs,
- archive_opts = ArchiveOpts,
- use_selected_vsn = UseSelected,
- vsn = Vsn,
- mods = Mods},
- InclDefs) ->
- emit(mod_cond, ModCond, undefined, InclDefs) ++
- emit(incl_cond, AppCond, undefined, InclDefs) ++
- emit(debug_info, DebugInfo, undefined, InclDefs) ++
- emit(app_file, AppFile, undefined, InclDefs) ++
- emit(incl_app_filters, InclAppFiles, undefined, InclDefs) ++
- emit(excl_app_filters, ExclAppFiles, undefined, InclDefs) ++
- emit(incl_archive_filters, InclArchiveDirs, undefined, InclDefs) ++
- emit(excl_archive_filters, ExclArchiveDirs, undefined, InclDefs) ++
- emit(archive_opts, ArchiveOpts, undefined, InclDefs) ++
- emit(vsn, Vsn, undefined, InclDefs orelse UseSelected =/= true) ++
- [{mod, M#mod.name, gen_config(M, InclDefs)} ||
- M <- Mods,
- M#mod.is_included =:= true];
-gen_config(#mod{name = _Name,
- incl_cond = AppCond,
- debug_info = DebugInfo},
- InclDefs) ->
- emit(incl_cond, AppCond, undefined, InclDefs) ++
- emit(debug_info, DebugInfo, undefined, InclDefs);
-gen_config(#rel{name = _Name,
- vsn = _Vsn,
- rel_apps = RelApps},
- InclDefs) ->
- [gen_config(RA, InclDefs) || RA <- RelApps];
-gen_config(#rel_app{name = Name,
- app_type = Type,
- incl_apps = InclApps},
- _InclDefs) ->
+do_gen_config(#app{name = Name,
+ mod_cond = ModCond,
+ incl_cond = AppCond,
+ debug_info = DebugInfo,
+ app_file = AppFile,
+ incl_app_filters = InclAppFiles,
+ excl_app_filters = ExclAppFiles,
+ incl_archive_filters = InclArchiveDirs,
+ excl_archive_filters = ExclArchiveDirs,
+ archive_opts = ArchiveOpts,
+ use_selected_vsn = UseSelected,
+ vsn = Vsn,
+ mods = Mods,
+ is_included = IsIncl},
+ InclDefs) ->
+ AppConfig =
+ [
+ emit(mod_cond, ModCond, undefined, InclDefs),
+ emit(incl_cond, AppCond, undefined, InclDefs),
+ emit(debug_info, DebugInfo, undefined, InclDefs),
+ emit(app_file, AppFile, undefined, InclDefs),
+ emit(incl_app_filters, InclAppFiles, undefined, InclDefs),
+ emit(excl_app_filters, ExclAppFiles, undefined, InclDefs),
+ emit(incl_archive_filters, InclArchiveDirs, undefined, InclDefs),
+ emit(excl_archive_filters, ExclArchiveDirs, undefined, InclDefs),
+ emit(archive_opts, ArchiveOpts, undefined, InclDefs),
+ if
+ IsIncl, InclDefs -> [{vsn, Vsn}];
+ UseSelected -> [{vsn, Vsn}];
+ true -> []
+ end,
+ [do_gen_config(M, InclDefs) || M <- Mods]
+ ],
+ case lists:flatten(AppConfig) of
+ FlatAppConfig when FlatAppConfig =/= []; IsIncl ->
+ [{app, Name, FlatAppConfig}];
+ [] ->
+ []
+ end;
+do_gen_config(#mod{name = Name,
+ incl_cond = AppCond,
+ debug_info = DebugInfo,
+ is_included = IsIncl},
+ InclDefs) ->
+ ModConfig =
+ [
+ emit(incl_cond, AppCond, undefined, InclDefs),
+ emit(debug_info, DebugInfo, undefined, InclDefs)
+ ],
+ case lists:flatten(ModConfig) of
+ FlatModConfig when FlatModConfig =/= []; IsIncl ->
+ [{mod, Name, FlatModConfig}];
+ _ ->
+ []
+ end;
+do_gen_config(#rel{name = _Name,
+ vsn = _Vsn,
+ rel_apps = RelApps},
+ InclDefs) ->
+ [do_gen_config(RA, InclDefs) || RA <- RelApps];
+do_gen_config(#rel_app{name = Name,
+ app_type = Type,
+ incl_apps = InclApps},
+ _InclDefs) ->
case {Type, InclApps} of
{undefined, []} -> Name;
{undefined, _} -> {Name, InclApps};
{_, []} -> {Name, Type};
{_, _} -> {Name, Type, InclApps}
end;
-gen_config({Tag, Val}, InclDefs) ->
+do_gen_config({Tag, Val}, InclDefs) ->
emit(Tag, Val, undefined, InclDefs);
-gen_config([], _InclDefs) ->
+do_gen_config([], _InclDefs) ->
[];
-gen_config([H | T], InclDefs) ->
- lists:flatten([gen_config(H, InclDefs), gen_config(T, InclDefs)]).
+do_gen_config([H | T], InclDefs) ->
+ lists:flatten([do_gen_config(H, InclDefs), do_gen_config(T, InclDefs)]).
emit(Tag, Val, Default, InclDefs) ->
+ %% io:format("~p(~p):\n\t~p\n\t~p\n",
+ %% [Tag, Val =/= Default, Val, Default]),
if
Val == undefined -> [];
InclDefs -> [{Tag, Val}];
@@ -239,24 +270,127 @@ gen_app(#app{name = Name,
%% Generate the contents of a rel file
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-gen_rel(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps},
- #sys{apps = Apps}) ->
- {value, Erts} = lists:keysearch(erts, #app.name, Apps),
- {release,
- {RelName, RelVsn},
- {erts, Erts#app.vsn},
- [app_to_rel(RA, Apps ) || RA <- RelApps]}.
-
-app_to_rel(#rel_app{name = Name, app_type = Type, incl_apps = InclApps},
- Apps) ->
- {value, #app{vsn = Vsn}} = lists:keysearch(Name, #app.name, Apps),
+gen_rel(Rel, Sys) ->
+ try
+ MergedApps = merge_apps(Rel, Sys),
+ {ok, do_gen_rel(Rel, Sys, MergedApps)}
+ catch
+ throw:{error, Text} ->
+ {error, Text}
+ end.
+
+do_gen_rel(#rel{name = RelName, vsn = RelVsn},
+ #sys{apps = Apps},
+ MergedApps) ->
+ ErtsName = erts,
+ case lists:keysearch(ErtsName, #app.name, Apps) of
+ {value, Erts} ->
+ {release,
+ {RelName, RelVsn},
+ {ErtsName, Erts#app.vsn},
+ [strip_rel_info(App) || App <- MergedApps]};
+ false ->
+ reltool_utils:throw_error("Mandatory application ~p is "
+ "not included",
+ [ErtsName])
+ end.
+
+strip_rel_info(#app{name = Name,
+ vsn = Vsn,
+ app_type = Type,
+ info = #app_info{incl_apps = InclApps}})
+ when Type =/= undefined ->
case {Type, InclApps} of
- {undefined, []} -> {Name, Vsn};
- {undefined, _} -> {Name, Vsn, InclApps};
+ {permanent, []} -> {Name, Vsn};
+ {permanent, _} -> {Name, Vsn, InclApps};
{_, []} -> {Name, Vsn, Type};
{_, _} -> {Name, Vsn, Type, InclApps}
end.
+merge_apps(#rel{name = RelName,
+ rel_apps = RelApps},
+ #sys{apps = Apps,
+ rel_app_type = RelAppType,
+ embedded_app_type = EmbAppType}) ->
+ Mandatory = [kernel, stdlib],
+ MergedApps = do_merge_apps(RelName, Mandatory, Apps, permanent, []),
+ MergedApps2 = do_merge_apps(RelName, RelApps, Apps, RelAppType, MergedApps),
+ Embedded =
+ [A#app.name || A <- Apps,
+ EmbAppType =/= undefined,
+ A#app.is_included,
+ A#app.name =/= erts,
+ A#app.name =/= ?MISSING_APP_NAME,
+ not lists:keymember(A#app.name, #app.name, MergedApps2)],
+ MergedApps3 = do_merge_apps(RelName, Embedded, Apps, EmbAppType, MergedApps2),
+ sort_apps(MergedApps3).
+
+do_merge_apps(RelName, [#rel_app{name = Name} = RA | RelApps], Apps, RelAppType, Acc) ->
+ case is_already_merged(Name, RelApps, Acc) of
+ true ->
+ do_merge_apps(RelName, RelApps, Apps, RelAppType, Acc);
+ false ->
+ {value, App} = lists:keysearch(Name, #app.name, Apps),
+ MergedApp = merge_app(RelName, RA, RelAppType, App),
+ MoreNames = (MergedApp#app.info)#app_info.applications,
+ Acc2 = [MergedApp | Acc],
+ do_merge_apps(RelName, MoreNames ++ RelApps, Apps, RelAppType, Acc2)
+ end;
+do_merge_apps(RelName, [Name | RelApps], Apps, RelAppType, Acc) ->
+ case is_already_merged(Name, RelApps, Acc) of
+ true ->
+ do_merge_apps(RelName, RelApps, Apps, RelAppType, Acc);
+ false ->
+ RelApp = init_rel_app(Name, Apps),
+ do_merge_apps(RelName, [RelApp | RelApps], Apps, RelAppType, Acc)
+ end;
+do_merge_apps(_RelName, [], _Apps, _RelAppType, Acc) ->
+ lists:reverse(Acc).
+
+init_rel_app(Name, Apps) ->
+ {value, App} = lists:keysearch(Name, #app.name, Apps),
+ Info = App#app.info,
+ #rel_app{name = Name,
+ app_type = undefined,
+ incl_apps = Info#app_info.incl_apps}.
+
+merge_app(RelName,
+ #rel_app{name = Name,
+ app_type = Type,
+ incl_apps = InclApps},
+ RelAppType,
+ App) ->
+ Type2 =
+ case {Type, App#app.app_type} of
+ {undefined, undefined} -> RelAppType;
+ {undefined, AppAppType} -> AppAppType;
+ {_, _} -> Type
+ end,
+ Info = App#app.info,
+ case InclApps -- Info#app_info.incl_apps of
+ [] ->
+ App#app{app_type = Type2, info = Info#app_info{incl_apps = InclApps}};
+ BadIncl ->
+ reltool_utils:throw_error("~p: These applications are "
+ "used by release ~s but are "
+ "missing as included_applications "
+ "in the app file: ~p",
+ [Name, RelName, BadIncl])
+ end.
+
+is_already_merged(Name, [Name | _], _MergedApps) ->
+ true;
+is_already_merged(Name, [#rel_app{name = Name} | _], _MergedApps) ->
+ true;
+is_already_merged(Name, [_ | RelApps], MergedApps) ->
+ is_already_merged(Name, RelApps, MergedApps);
+is_already_merged(Name, [], [#app{name = Name} | _MergedApps]) ->
+ true;
+is_already_merged(Name, [] = RelApps, [_ | MergedApps]) ->
+ is_already_merged(Name, RelApps, MergedApps);
+is_already_merged(_Name, [], []) ->
+ false.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Generate the contents of a boot file
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -270,27 +404,24 @@ gen_boot({script, {_, _}, _} = Script) ->
gen_script(Rel, Sys, PathFlag, Variables) ->
try
- do_gen_script(Rel, Sys, PathFlag, Variables)
+ MergedApps = merge_apps(Rel, Sys),
+ do_gen_script(Rel, Sys, MergedApps, PathFlag, Variables)
catch
throw:{error, Text} ->
{error, Text}
end.
-do_gen_script(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps},
- #sys{apps = Apps, app_type = DefaultType},
+do_gen_script(#rel{name = RelName, vsn = RelVsn},
+ #sys{apps = Apps},
+ MergedApps,
PathFlag,
Variables) ->
{value, Erts} = lists:keysearch(erts, #app.name, Apps),
Preloaded = [Mod#mod.name || Mod <- Erts#app.mods],
Mandatory = mandatory_modules(),
Early = Mandatory ++ Preloaded,
- MergedApps = [merge_app(RA, Apps, DefaultType) || RA <- RelApps],
- SortedApps = sort_apps(MergedApps),
- {value, KernelApp} = lists:keysearch(kernel, #app.name, SortedApps),
-
- InclApps =
- lists:append([I ||
- #app{info = #app_info{incl_apps = I}} <- SortedApps]),
+ {value, KernelApp} = lists:keysearch(kernel, #app.name, MergedApps),
+ InclApps = [I || #app{info = #app_info{incl_apps = I}} <- MergedApps],
%% Create the script
DeepList =
@@ -300,30 +431,30 @@ do_gen_script(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps},
{progress, preloaded},
%% Load mandatory modules
- {path, create_mandatory_path(SortedApps, PathFlag, Variables)},
+ {path, create_mandatory_path(MergedApps, PathFlag, Variables)},
{primLoad, lists:sort(Mandatory)},
{kernel_load_completed},
{progress, kernel_load_completed},
%% Load remaining modules
- [load_app_mods(A, Early, PathFlag, Variables) || A <- SortedApps],
+ [load_app_mods(A, Early, PathFlag, Variables) || A <- MergedApps],
{progress, modules_loaded},
%% Start kernel processes
- {path, create_path(SortedApps, PathFlag, Variables)},
+ {path, create_path(MergedApps, PathFlag, Variables)},
kernel_processes(gen_app(KernelApp)),
{progress, init_kernel_started},
%% Load applications
[{apply, {application, load, [gen_app(A)]}} ||
- A = #app{name = Name, app_type = Type} <- SortedApps,
+ A = #app{name = Name, app_type = Type} <- MergedApps,
Name =/= kernel,
Type =/= none],
{progress, applications_loaded},
%% Start applications
[{apply, {application, start_boot, [Name, Type]}} ||
- #app{name = Name, app_type = Type} <- SortedApps,
+ #app{name = Name, app_type = Type} <- MergedApps,
Type =/= none,
Type =/= load,
not lists:member(Name, InclApps)],
@@ -334,28 +465,6 @@ do_gen_script(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps},
],
{ok, {script, {RelName, RelVsn}, lists:flatten(DeepList)}}.
-merge_app(#rel_app{name = Name, app_type = Type, incl_apps = RelIncl},
- Apps,
- DefaultType) ->
- {value, App} = lists:keysearch(Name, #app.name, Apps),
- Type2 =
- case {Type, App#app.app_type} of
- {undefined, undefined} -> DefaultType;
- {undefined, AppType} -> AppType;
- {_, _} -> Type
- end,
- Info = App#app.info,
- case RelIncl -- Info#app_info.incl_apps of
- [] ->
- App#app{app_type = Type2,
- info = Info#app_info{incl_apps = RelIncl}};
- BadIncl ->
- reltool_utils:throw_error("~p: These applications are "
- "missing as included_applications "
- "in the app file: ~p\n",
- [Name, BadIncl])
- end.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
load_app_mods(#app{mods = Mods} = App, Mand, PathFlag, Variables) ->
@@ -438,13 +547,13 @@ sort_apps([], [], [], _) ->
[];
sort_apps([], Missing, [], _) ->
%% this has already been checked before, but as we have the info...
- reltool_utils:throw_error("Undefined applications: ~p\n",
+ reltool_utils:throw_error("Undefined applications: ~p",
[make_set(Missing)]);
sort_apps([], [], Circular, _) ->
- reltool_utils:throw_error("Circular dependencies: ~p\n",
+ reltool_utils:throw_error("Circular dependencies: ~p",
[make_set(Circular)]);
sort_apps([], Missing, Circular, _) ->
- reltool_utils:throw_error("Circular dependencies: ~p\n"
+ reltool_utils:throw_error("Circular dependencies: ~p"
"Undefined applications: ~p\n",
[make_set(Circular), make_set(Missing)]).
@@ -603,14 +712,15 @@ gen_rel_files(Sys, TargetDir) ->
spec_rel_files(#sys{rels = Rels} = Sys) ->
lists:append([do_spec_rel_files(R, Sys) || R <- Rels]).
-do_spec_rel_files(#rel{name = Name} = Rel, Sys) ->
- RelFile = Name ++ ".rel",
- ScriptFile = Name ++ ".script",
- BootFile = Name ++ ".boot",
- GenRel = gen_rel(Rel, Sys),
+do_spec_rel_files(#rel{name = RelName} = Rel, Sys) ->
+ RelFile = RelName ++ ".rel",
+ ScriptFile = RelName ++ ".script",
+ BootFile = RelName ++ ".boot",
+ MergedApps = merge_apps(Rel, Sys),
+ GenRel = do_gen_rel(Rel, Sys, MergedApps),
PathFlag = true,
Variables = [],
- {ok, Script} = do_gen_script(Rel, Sys, PathFlag, Variables),
+ {ok, Script} = do_gen_script(Rel, Sys, MergedApps, PathFlag, Variables),
{ok, BootBin} = gen_boot(Script),
Date = date(),
Time = time(),
@@ -665,8 +775,6 @@ do_gen_spec(#sys{root_dir = RootDir,
{create_dir,BootVsn, RelFiles}]},
{create_dir, "bin", BinFiles}
] ++ SysFiles2,
- %% io:format("InclRegexps2: ~p\n", [InclRegexps2]),
- %% io:format("ExclRegexps2: ~p\n", [ExclRegexps2]),
SysFiles4 = filter_spec(SysFiles3, InclRegexps2, ExclRegexps2),
SysFiles5 = SysFiles4 ++ [{create_dir, "lib", LibFiles}],
check_sys(["bin", "erts", "lib"], SysFiles5),
@@ -678,23 +786,25 @@ strip_sys_files(Relocatable, SysFiles, Apps, ExclRegexps) ->
true ->
ExtraExcl = ["^erts.*/bin/.*src\$"],
reltool_utils:decode_regexps(excl_sys_filters,
- {add, ExtraExcl}, ExclRegexps);
+ {add, ExtraExcl},
+ ExclRegexps);
false ->
ExclRegexps
end,
{value, Erts} = lists:keysearch(erts, #app.name, Apps),
FilterErts =
fun(Spec) ->
- File = element(2, Spec),
- case lists:prefix("erts", File) of
- true ->
- if
- File =:= Erts#app.label ->
- replace_dyn_erl(Relocatable, Spec);
- true ->
- false
- end;
- false ->
+ File = element(2, Spec),
+ case File of
+ "erts" ->
+ reltool_utils:throw_error("This system is not installed. "
+ "The directory ~s is missing.",
+ [Erts#app.label]);
+ _ when File =:= Erts#app.label ->
+ replace_dyn_erl(Relocatable, Spec);
+ "erts-" ++ _ ->
+ false;
+ _ ->
true
end
end,
@@ -707,7 +817,8 @@ strip_sys_files(Relocatable, SysFiles, Apps, ExclRegexps) ->
replace_dyn_erl(false, _ErtsSpec) ->
true;
replace_dyn_erl(true, {create_dir, ErtsDir, ErtsFiles}) ->
- [{create_dir, _, BinFiles}] = safe_lookup_spec("bin", ErtsFiles),
+ [{create_dir, _, BinFiles}] =
+ safe_lookup_spec("bin", ErtsFiles),
case lookup_spec("dyn_erl", BinFiles) of
[] ->
case lookup_spec("erl.ini", BinFiles) of
@@ -759,20 +870,31 @@ spec_bin_files(Sys, AllSysFiles, StrippedSysFiles, RelFiles, InclRegexps) ->
GoodNames = [F || {copy_file, F} <- OldBinFiles,
not lists:suffix(".boot", F),
not lists:suffix(".script", F)],
- BinFiles2 = [Map(S) || S <- BinFiles, lists:member(element(2, S), GoodNames)],
+ BinFiles2 = [Map(S) || S <- BinFiles,
+ lists:member(element(2, S), GoodNames)],
BootFiles = [F || F <- RelFiles, lists:suffix(".boot", element(2, F))],
- [{write_file, _, BootRel}] = safe_lookup_spec(Sys#sys.boot_rel ++ ".boot", BootFiles),
- BootFiles2 = lists:keystore("start.boot", 2, BootFiles, {write_file, "start.boot", BootRel}),
- MakeRegexp = fun(File) -> "^bin/" ++ element(2, File) ++ "(|.escript)\$" end,
+ [{write_file, _, BootRel}] =
+ safe_lookup_spec(Sys#sys.boot_rel ++ ".boot", BootFiles),
+ BootFiles2 = lists:keystore("start.boot",
+ 2,
+ BootFiles,
+ {write_file, "start.boot", BootRel}),
+ MakeRegexp =
+ fun(File) -> "^bin/" ++ element(2, File) ++ "(|.escript)\$" end,
ExtraIncl = lists:map(MakeRegexp, Escripts),
- InclRegexps2 = reltool_utils:decode_regexps(incl_sys_filters, {add, ExtraIncl}, InclRegexps),
+ InclRegexps2 = reltool_utils:decode_regexps(incl_sys_filters,
+ {add, ExtraIncl},
+ InclRegexps),
{InclRegexps2, Escripts ++ BinFiles2 ++ BootFiles2}.
spec_escripts(#sys{apps = Apps}, ErtsBin, BinFiles) ->
- Filter = fun(#app{is_escript = IsEscript, is_included = IsIncl,
- is_pre_included = IsPre, name = Name, active_dir = File}) ->
+ Filter = fun(#app{is_escript = IsEscript,
+ is_included = IsIncl,
+ is_pre_included = IsPre,
+ name = Name,
+ active_dir = File}) ->
if
- Name =:= ?MISSING_APP ->
+ Name =:= ?MISSING_APP_NAME ->
false;
not IsEscript ->
false;
@@ -796,7 +918,6 @@ check_sys(Mandatory, SysFiles) ->
lists:foreach(fun(M) -> do_check_sys(M, SysFiles) end, Mandatory).
do_check_sys(Prefix, Specs) ->
- %%io:format("Prefix: ~p\n", [Prefix]),
case lookup_spec(Prefix, Specs) of
[] ->
reltool_utils:throw_error("Mandatory system directory ~s "
@@ -820,6 +941,7 @@ lookup_spec(Prefix, Specs) ->
safe_lookup_spec(Prefix, Specs) ->
case lookup_spec(Prefix, Specs) of
[] ->
+ %% io:format("lookup fail ~s:\n\t~p\n", [Prefix, Specs]),
reltool_utils:throw_error("Mandatory system file ~s is "
"not included", [Prefix]);
Match ->
@@ -834,7 +956,7 @@ spec_lib_files(#sys{apps = Apps} = Sys) ->
Filter = fun(#app{is_escript = IsEscript, is_included = IsIncl,
is_pre_included = IsPre, name = Name}) ->
if
- Name =:= ?MISSING_APP ->
+ Name =:= ?MISSING_APP_NAME ->
false;
IsEscript ->
false;
@@ -863,10 +985,10 @@ check_apps([], _) ->
spec_app(#app{name = Name,
mods = Mods,
active_dir = SourceDir,
- incl_app_filters = AppInclRegexps,
- excl_app_filters = AppExclRegexps} = App,
- #sys{incl_app_filters = SysInclRegexps,
- excl_app_filters = SysExclRegexps,
+ incl_app_filters = AppInclRegexps,
+ excl_app_filters = AppExclRegexps} = App,
+ #sys{incl_app_filters = SysInclRegexps,
+ excl_app_filters = SysExclRegexps,
debug_info = SysDebugInfo} = Sys) ->
%% List files recursively
{create_dir, _, AppFiles} = spec_dir(SourceDir),
@@ -942,13 +1064,13 @@ spec_dir(Dir) ->
Base,
[spec_dir(filename:join([Dir, F])) || F <- Files]};
error ->
- reltool_utils:throw_error("list dir ~s failed\n", [Dir])
+ reltool_utils:throw_error("list dir ~s failed", [Dir])
end;
{ok, #file_info{type = regular}} ->
%% Plain file
{copy_file, Base};
_ ->
- reltool_utils:throw_error("read file info ~s failed\n", [Dir])
+ reltool_utils:throw_error("read file info ~s failed", [Dir])
end.
spec_mod(Mod, DebugInfo) ->
@@ -1082,7 +1204,7 @@ do_eval_spec({archive, Archive, Options, Files},
{ok, _} ->
ok;
{error, Reason} ->
- reltool_utils:throw_error("create archive ~s: ~p\n",
+ reltool_utils:throw_error("create archive ~s failed: ~p",
[ArchiveFile, Reason])
end;
do_eval_spec({copy_file, File}, _OrigSourceDir, SourceDir, TargetDir) ->
@@ -1216,18 +1338,7 @@ opt_join(Path, File) ->
filename:join([Path, File]).
match(String, InclRegexps, ExclRegexps) ->
- %%case
- match(String, InclRegexps) andalso not match(String, ExclRegexps).
-%% of
-%% true ->
-%% true;
-%% false ->
-%% io:format("no match: ~p\n"
-%% " incl: ~p\n"
-%% " excl: ~p\n",
-%% [String, InclRegexps, ExclRegexps]),
-%% false
-%% end.
+ match(String, InclRegexps) andalso not match(String, ExclRegexps).
%% Match at least one regexp
match(_String, []) ->
@@ -1284,7 +1395,7 @@ do_install(RelName, TargetDir) ->
ok = release_handler:create_RELEASES(TargetDir2, RelFile),
ok;
_ ->
- reltool_utils:throw_error("~s: Illegal syntax.\n", [DataFile])
+ reltool_utils:throw_error("~s: Illegal data file syntax", [DataFile])
end.
subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
diff --git a/lib/reltool/src/reltool_utils.erl b/lib/reltool/src/reltool_utils.erl
index 403fa574c5..39d057d994 100644
--- a/lib/reltool/src/reltool_utils.erl
+++ b/lib/reltool/src/reltool_utils.erl
@@ -19,7 +19,30 @@
-module(reltool_utils).
%% Public
--compile([export_all]).
+-export([root_dir/0, erl_libs/0, lib_dirs/1,
+ split_app_name/1, prim_consult/1,
+ default_rels/0, choose_default/3,
+
+ assign_image_list/1, get_latest_resize/1,
+ mod_conds/0, list_to_mod_cond/1, mod_cond_to_index/1,
+ incl_conds/0, list_to_incl_cond/1, incl_cond_to_index/1, elem_to_index/2,
+ app_dir_test/2, split_app_dir/1,
+ get_item/1, get_items/1, get_selected_items/3,
+ select_items/3, select_item/2,
+
+ safe_keysearch/5, print/4, return_first_error/2, add_warning/2,
+
+ create_dir/1, list_dir/1, read_file_info/1,
+ write_file_info/2, read_file/1, write_file/2,
+ recursive_delete/1, delete/2, recursive_copy_file/2, copy_file/2,
+
+ throw_error/2,
+
+ decode_regexps/3,
+ default_val/2,
+ escript_foldl/3,
+
+ call/2, cast/2, reply/3]).
-include_lib("kernel/include/file.hrl").
-include_lib("wx/include/wx.hrl").
@@ -103,18 +126,46 @@ prim_parse(Tokens, Acc) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
default_rels() ->
- Kernel = #rel_app{name = kernel, incl_apps = []},
- Stdlib = #rel_app{name = stdlib, incl_apps = []},
- Sasl = #rel_app{name = sasl, incl_apps = []},
+ %%Kernel = #rel_app{name = kernel, incl_apps = []},
+ %%Stdlib = #rel_app{name = stdlib, incl_apps = []},
+ Sasl = #rel_app{name = sasl, incl_apps = []},
[
#rel{name = ?DEFAULT_REL_NAME,
vsn = "1.0",
- rel_apps = [Kernel, Stdlib]},
+ rel_apps = []},
+ %%rel_apps = [Kernel, Stdlib]},
#rel{name = "start_sasl",
vsn = "1.0",
- rel_apps = [Kernel, Sasl, Stdlib]}
+ rel_apps = [Sasl]}
+ %%rel_apps = [Kernel, Sasl, Stdlib]}
].
+choose_default(Tag, Profile, InclDefs)
+ when Profile =:= ?DEFAULT_PROFILE; InclDefs ->
+ case Tag of
+ incl_sys_filters -> ?DEFAULT_INCL_SYS_FILTERS;
+ excl_sys_filters -> ?DEFAULT_EXCL_SYS_FILTERS;
+ incl_app_filters -> ?DEFAULT_INCL_APP_FILTERS;
+ excl_app_filters -> ?DEFAULT_EXCL_APP_FILTERS;
+ embedded_app_type -> ?DEFAULT_EMBEDDED_APP_TYPE
+ end;
+choose_default(Tag, standalone, _InclDefs) ->
+ case Tag of
+ incl_sys_filters -> ?STANDALONE_INCL_SYS_FILTERS;
+ excl_sys_filters -> ?STANDALONE_EXCL_SYS_FILTERS;
+ incl_app_filters -> ?STANDALONE_INCL_APP_FILTERS;
+ excl_app_filters -> ?STANDALONE_EXCL_APP_FILTERS;
+ embedded_app_type -> ?DEFAULT_EMBEDDED_APP_TYPE
+ end;
+choose_default(Tag, embedded, _InclDefs) ->
+ case Tag of
+ incl_sys_filters -> ?EMBEDDED_INCL_SYS_FILTERS;
+ excl_sys_filters -> ?EMBEDDED_EXCL_SYS_FILTERS;
+ incl_app_filters -> ?EMBEDDED_INCL_APP_FILTERS;
+ excl_app_filters -> ?EMBEDDED_EXCL_APP_FILTERS;
+ embedded_app_type -> ?EMBEDDED_APP_TYPE
+ end.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
assign_image_list(ListCtrl) ->
@@ -379,7 +430,7 @@ create_dir(Dir) ->
ok;
{error, Reason} ->
Text = file:format_error(Reason),
- throw_error("create dir ~s: ~s\n", [Dir, Text])
+ throw_error("create dir ~s: ~s", [Dir, Text])
end.
list_dir(Dir) ->
@@ -388,7 +439,7 @@ list_dir(Dir) ->
Files;
error ->
Text = file:format_error(enoent),
- throw_error("list dir ~s: ~s\n", [Dir, Text])
+ throw_error("list dir ~s: ~s", [Dir, Text])
end.
read_file_info(File) ->
@@ -397,7 +448,7 @@ read_file_info(File) ->
Info;
{error, Reason} ->
Text = file:format_error(Reason),
- throw_error("read file info ~s: ~s\n", [File, Text])
+ throw_error("read file info ~s: ~s", [File, Text])
end.
write_file_info(File, Info) ->
@@ -406,7 +457,7 @@ write_file_info(File, Info) ->
ok;
{error, Reason} ->
Text = file:format_error(Reason),
- throw_error("write file info ~s: ~s\n", [File, Text])
+ throw_error("write file info ~s: ~s", [File, Text])
end.
read_file(File) ->
@@ -415,7 +466,7 @@ read_file(File) ->
Bin;
{error, Reason} ->
Text = file:format_error(Reason),
- throw_error("read file ~s: ~s\n", [File, Text])
+ throw_error("read file ~s: ~s", [File, Text])
end.
write_file(File, IoList) ->
@@ -424,7 +475,7 @@ write_file(File, IoList) ->
ok;
{error, Reason} ->
Text = file:format_error(Reason),
- throw_error("write file ~s: ~s\n", [File, Text])
+ throw_error("write file ~s: ~s", [File, Text])
end.
recursive_delete(Dir) ->
@@ -560,7 +611,12 @@ escript_foldl(Fun, Acc, File) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
call(Name, Msg) when is_atom(Name) ->
- call(whereis(Name), Msg);
+ case whereis(Name) of
+ undefined ->
+ {error, {noproc, Name}};
+ Pid ->
+ call(Pid, Msg)
+ end;
call(Pid, Msg) when is_pid(Pid) ->
Ref = erlang:monitor(process, Pid),
Pid ! {call, self(), Ref, Msg},
diff --git a/lib/reltool/test/Makefile b/lib/reltool/test/Makefile
index 00d2add3e5..34781ae720 100644
--- a/lib/reltool/test/Makefile
+++ b/lib/reltool/test/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2009-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
include $(ERL_TOP)/make/target.mk
@@ -25,6 +25,7 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk
MODULES= \
rtt \
+ reltool_app_SUITE \
reltool_wx_SUITE \
reltool_server_SUITE \
reltool_test_lib
diff --git a/lib/reltool/test/reltool_app_SUITE.erl b/lib/reltool/test/reltool_app_SUITE.erl
new file mode 100644
index 0000000000..f8433f73d0
--- /dev/null
+++ b/lib/reltool/test/reltool_app_SUITE.erl
@@ -0,0 +1,291 @@
+%%
+%% %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%
+%%
+
+%%
+%%----------------------------------------------------------------------
+%% Purpose: Verify the application specifics of the Reltool application
+%%----------------------------------------------------------------------
+-module(reltool_app_SUITE).
+
+-compile(export_all).
+
+-include("reltool_test_lib.hrl").
+
+
+t() -> reltool_test_lib:t(?MODULE).
+t(Case) -> reltool_test_lib:t({?MODULE, Case}).
+
+%% Test server callbacks
+init_per_suite(Config) ->
+ Config2 = reltool_test_lib:init_per_suite(Config),
+ case is_app(reltool) of
+ {ok, AppFile} ->
+ %% io:format("AppFile: ~n~p~n", [AppFile]),
+ [{app_file, AppFile} | Config2];
+ {error, Reason} ->
+ fail(Reason)
+ end.
+
+end_per_suite(Config) ->
+ reltool_test_lib:end_per_suite(Config).
+
+init_per_testcase(Case, Config) ->
+ Config2 =
+ case Case of
+ undef_funcs ->
+ [{tc_timeout, timer:minutes(10)} | Config];
+ _ ->
+ Config
+ end,
+ reltool_test_lib:init_per_testcase(Case, Config2).
+
+end_per_testcase(Func,Config) ->
+ reltool_test_lib:end_per_testcase(Func,Config).
+
+fin_per_testcase(Case, Config) ->
+ reltool_test_lib:end_per_testcase(Case, Config).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+all() ->
+ all(suite).
+
+all(suite) ->
+ [
+ fields,
+ modules,
+ export_all,
+ app_depend,
+ undef_funcs
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+is_app(App) ->
+ LibDir = code:lib_dir(App),
+ File = filename:join([LibDir, "ebin", atom_to_list(App) ++ ".app"]),
+ case file:consult(File) of
+ {ok, [{application, App, AppFile}]} ->
+ {ok, AppFile};
+ {error, {LineNo, Mod, Code}} ->
+ IoList = lists:concat([File, ":", LineNo, ": ",
+ Mod:format_error(Code)]),
+ {error, list_to_atom(lists:flatten(IoList))}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+fields(suite) ->
+ [];
+fields(doc) ->
+ [];
+fields(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Fields = [vsn, description, modules, registered, applications],
+ case check_fields(Fields, AppFile, []) of
+ [] ->
+ ok;
+ Missing ->
+ fail({missing_fields, Missing})
+ end.
+
+check_fields([], _AppFile, Missing) ->
+ Missing;
+check_fields([Field|Fields], AppFile, Missing) ->
+ check_fields(Fields, AppFile, check_field(Field, AppFile, Missing)).
+
+check_field(Name, AppFile, Missing) ->
+ io:format("checking field: ~p~n", [Name]),
+ case lists:keymember(Name, 1, AppFile) of
+ true ->
+ Missing;
+ false ->
+ [Name|Missing]
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+modules(suite) ->
+ [];
+modules(doc) ->
+ [];
+modules(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Mods = key1search(modules, AppFile),
+ EbinList = get_ebin_mods(reltool),
+ case missing_modules(Mods, EbinList, []) of
+ [] ->
+ ok;
+ Missing ->
+ throw({error, {missing_modules, Missing}})
+ end,
+ case extra_modules(Mods, EbinList, []) of
+ [] ->
+ ok;
+ Extra ->
+ throw({error, {extra_modules, Extra}})
+ end,
+ {ok, Mods}.
+
+get_ebin_mods(App) ->
+ LibDir = code:lib_dir(App),
+ EbinDir = filename:join([LibDir,"ebin"]),
+ {ok, Files0} = file:list_dir(EbinDir),
+ Files1 = [lists:reverse(File) || File <- Files0],
+ [list_to_atom(lists:reverse(Name)) || [$m,$a,$e,$b,$.|Name] <- Files1].
+
+missing_modules([], _Ebins, Missing) ->
+ Missing;
+missing_modules([Mod|Mods], Ebins, Missing) ->
+ case lists:member(Mod, Ebins) of
+ true ->
+ missing_modules(Mods, Ebins, Missing);
+ false ->
+ io:format("missing module: ~p~n", [Mod]),
+ missing_modules(Mods, Ebins, [Mod|Missing])
+ end.
+
+
+extra_modules(_Mods, [], Extra) ->
+ Extra;
+extra_modules(Mods, [Mod|Ebins], Extra) ->
+ case lists:member(Mod, Mods) of
+ true ->
+ extra_modules(Mods, Ebins, Extra);
+ false ->
+ io:format("supefluous module: ~p~n", [Mod]),
+ extra_modules(Mods, Ebins, [Mod|Extra])
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+export_all(suite) ->
+ [];
+export_all(doc) ->
+ [];
+export_all(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Mods = key1search(modules, AppFile),
+ check_export_all(Mods).
+
+
+check_export_all([]) ->
+ ok;
+check_export_all([Mod|Mods]) ->
+ case (catch apply(Mod, module_info, [compile])) of
+ {'EXIT', {undef, _}} ->
+ check_export_all(Mods);
+ O ->
+ case lists:keysearch(options, 1, O) of
+ false ->
+ check_export_all(Mods);
+ {value, {options, List}} ->
+ case lists:member(export_all, List) of
+ true ->
+ throw({error, {export_all, Mod}});
+ false ->
+ check_export_all(Mods)
+ end
+ end
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+app_depend(suite) ->
+ [];
+app_depend(doc) ->
+ [];
+app_depend(Config) when is_list(Config) ->
+ AppFile = key1search(app_file, Config),
+ Apps = key1search(applications, AppFile),
+ check_apps(Apps).
+
+check_apps([]) ->
+ ok;
+check_apps([App|Apps]) ->
+ case is_app(App) of
+ {ok, _} ->
+ check_apps(Apps);
+ Error ->
+ throw({error, {missing_app, {App, Error}}})
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+undef_funcs(suite) ->
+ [];
+undef_funcs(doc) ->
+ [];
+undef_funcs(Config) when is_list(Config) ->
+ App = reltool,
+ AppFile = key1search(app_file, Config),
+ Mods = key1search(modules, AppFile),
+ Root = code:root_dir(),
+ LibDir = code:lib_dir(App),
+ EbinDir = filename:join([LibDir,"ebin"]),
+ XRefTestName = undef_funcs_make_name(App, xref_test_name),
+ {ok, XRef} = xref:start(XRefTestName),
+ ok = xref:set_default(XRef,
+ [{verbose,false},{warnings,false}]),
+ XRefName = undef_funcs_make_name(App, xref_name),
+ {ok, XRefName} = xref:add_release(XRef, Root, {name,XRefName}),
+ {ok, App} = xref:replace_application(XRef, App, EbinDir),
+ {ok, Undefs} = xref:analyze(XRef, undefined_function_calls),
+ xref:stop(XRef),
+ analyze_undefined_function_calls(Undefs, Mods, []).
+
+analyze_undefined_function_calls([], _, []) ->
+ ok;
+analyze_undefined_function_calls([], _, AppUndefs) ->
+ exit({suite_failed, {undefined_function_calls, AppUndefs}});
+analyze_undefined_function_calls([{{Mod, _F, _A}, _C} = AppUndef|Undefs],
+ AppModules, AppUndefs) ->
+ %% Check that this module is our's
+ case lists:member(Mod,AppModules) of
+ true ->
+ {Calling,Called} = AppUndef,
+ {Mod1,Func1,Ar1} = Calling,
+ {Mod2,Func2,Ar2} = Called,
+ io:format("undefined function call: "
+ "~n ~w:~w/~w calls ~w:~w/~w~n",
+ [Mod1,Func1,Ar1,Mod2,Func2,Ar2]),
+ analyze_undefined_function_calls(Undefs, AppModules,
+ [AppUndef|AppUndefs]);
+ false ->
+ io:format("dropping ~p~n", [Mod]),
+ analyze_undefined_function_calls(Undefs, AppModules, AppUndefs)
+ end.
+
+%% This function is used simply to avoid cut-and-paste errors later...
+undef_funcs_make_name(App, PostFix) ->
+ list_to_atom(atom_to_list(App) ++ "_" ++ atom_to_list(PostFix)).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+fail(Reason) ->
+ exit({suite_failed, Reason}).
+
+key1search(Key, L) ->
+ case lists:keysearch(Key, 1, L) of
+ false ->
+ fail({not_found, Key, L});
+ {value, {Key, Value}} ->
+ Value
+ end.
diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl
index cf951191a0..faf1bdbba2 100644
--- a/lib/reltool/test/reltool_server_SUITE.erl
+++ b/lib/reltool/test/reltool_server_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
-module(reltool_server_SUITE).
@@ -26,11 +26,13 @@
-include("reltool_test_lib.hrl").
-define(NODE_NAME, '__RELTOOL__TEMPORARY_TEST__NODE__').
+-define(WORK_DIR, "reltool_work_dir").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Initialization functions.
init_per_suite(Config) ->
+ ?ignore(file:make_dir(?WORK_DIR)),
reltool_test_lib:init_per_suite(Config).
end_per_suite(Config) ->
@@ -128,8 +130,7 @@ create_release(_Config) ->
{value, {_, _, StdlibVsn}} = lists:keysearch(stdlib, 1, Apps),
Rel = {release, {RelName, RelVsn},
{erts, ErtsVsn},
- [{kernel, KernelVsn},
- {stdlib, StdlibVsn}]},
+ [{kernel, KernelVsn}, {stdlib, StdlibVsn}]},
?m({ok, Rel}, reltool:get_rel([{config, Config}], RelName)),
ok.
@@ -159,15 +160,17 @@ create_script(_Config) ->
Rel = {release,
{RelName, RelVsn},
{erts, ErtsVsn},
- [{stdlib, StdlibVsn}, {kernel, KernelVsn}]},
+ [{kernel, KernelVsn}, {stdlib, StdlibVsn}]},
?m({ok, Rel}, reltool:get_rel(Pid, RelName)),
- RelFile = RelName ++ ".rel",
- ?m(ok, file:write_file(RelFile, io_lib:format("~p.\n", [Rel]))),
+ ?m(ok, file:write_file(filename:join([?WORK_DIR, RelName ++ ".rel"]),
+ io_lib:format("~p.\n", [Rel]))),
%% Generate script file
+ {ok, Cwd} = file:get_cwd(),
+ ?m(ok, file:set_cwd(?WORK_DIR)),
?m(ok, systools:make_script(RelName, [])),
- ScriptFile = RelName ++ ".script",
- {ok, [OrigScript]} = ?msym({ok, [_]}, file:consult(ScriptFile)),
+ {ok, [OrigScript]} = ?msym({ok, [_]}, file:consult(RelName ++ ".script")),
+ ?m(ok, file:set_cwd(Cwd)),
{ok, Script} = ?msym({ok, _}, reltool:get_script(Pid, RelName)),
%% OrigScript2 = sort_script(OrigScript),
%% Script2 = sort_script(Script),
@@ -201,9 +204,10 @@ create_target(_Config) ->
]},
%% Generate target file
- TargetDir = "reltool_target_dir_development",
+ TargetDir = filename:join([?WORK_DIR, "target_development"]),
?m(ok, reltool_utils:recursive_delete(TargetDir)),
?m(ok, file:make_dir(TargetDir)),
+ ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, Config}])]),
?m(ok, reltool:create_target([{config, Config}], TargetDir)),
Erl = filename:join([TargetDir, "bin", "erl"]),
@@ -234,7 +238,7 @@ create_embedded(_Config) ->
]},
%% Generate target file
- TargetDir = "reltool_target_dir_embedded",
+ TargetDir = filename:join([?WORK_DIR, "target_embedded"]),
?m(ok, reltool_utils:recursive_delete(TargetDir)),
?m(ok, file:make_dir(TargetDir)),
?m(ok, reltool:create_target([{config, Config}], TargetDir)),
@@ -264,7 +268,7 @@ create_standalone(_Config) ->
]},
%% Generate target file
- TargetDir = "reltool_target_dir_standalone",
+ TargetDir = filename:join([?WORK_DIR, "target_standalone"]),
?m(ok, reltool_utils:recursive_delete(TargetDir)),
?m(ok, file:make_dir(TargetDir)),
?m(ok, reltool:create_target([{config, Config}], TargetDir)),
@@ -290,6 +294,8 @@ create_standalone(_Config) ->
create_old_target(TestInfo) when is_atom(TestInfo) ->
reltool_test_lib:tc_info(TestInfo);
create_old_target(_Config) ->
+ ?skip("Old style of target", []),
+
%% Configure the server
RelName1 = "Just testing",
RelName2 = "Just testing with SASL",
@@ -306,7 +312,7 @@ create_old_target(_Config) ->
]},
%% Generate target file
- TargetDir = "reltool_target_dir_old",
+ TargetDir = filename:join([?WORK_DIR, "target_old_style"]),
?m(ok, reltool_utils:recursive_delete(TargetDir)),
?m(ok, file:make_dir(TargetDir)),
?m(ok, reltool:create_target([{config, Config}], TargetDir)),
diff --git a/lib/reltool/test/reltool_test_lib.erl b/lib/reltool/test/reltool_test_lib.erl
index 25978294ee..5390b0a75e 100644
--- a/lib/reltool/test/reltool_test_lib.erl
+++ b/lib/reltool/test/reltool_test_lib.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
-module(reltool_test_lib).
@@ -24,9 +24,11 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
init_per_suite(Config) when is_list(Config)->
+ global:register_name(reltool_global_logger, group_leader()),
incr_timetrap(Config, 5).
end_per_suite(Config) when is_list(Config)->
+ global:unregister_name(reltool_global_logger),
ok.
incr_timetrap(Config, Times) ->
@@ -130,11 +132,9 @@ wx_end_per_suite(Config) ->
init_per_testcase(_Func, Config) when is_list(Config) ->
set_kill_timer(Config),
- global:register_name(reltool_global_logger, group_leader()),
Config.
end_per_testcase(_Func, Config) when is_list(Config) ->
- global:unregister_name(reltool_global_logger),
reset_kill_timer(Config),
Config.
diff --git a/lib/reltool/test/reltool_test_lib.hrl b/lib/reltool/test/reltool_test_lib.hrl
index 93134144ea..b592ebb2f0 100644
--- a/lib/reltool/test/reltool_test_lib.hrl
+++ b/lib/reltool/test/reltool_test_lib.hrl
@@ -1,23 +1,24 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
-include_lib("wx/include/wx.hrl").
+-define(flat_format(Format,Args), lists:flatten(io_lib:format(Format,Args))).
-define(log(Format,Args), reltool_test_lib:log(Format,Args,?FILE,?LINE)).
-define(warning(Format,Args), ?log("<WARNING>\n " ++ Format,Args)).
-define(error(Format,Args), reltool_test_lib:error(Format,Args,?FILE,?LINE)).
diff --git a/lib/reltool/test/rtt b/lib/reltool/test/rtt
index 2411195338..1f93396196 100755
--- a/lib/reltool/test/rtt
+++ b/lib/reltool/test/rtt
@@ -1,19 +1,19 @@
#! /bin/sh -f
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2009-2010. All Rights Reserved.
+#
# The contents of this file are subject to the Erlang Public License,
# Version 1.1, (the "License"); you may not use this file except in
# compliance with the License. You should have received a copy of the
# Erlang Public License along with this software. If not, it can be
# retrieved online at http://www.erlang.org/.
-#
+#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and limitations
# under the License.
-#
+#
# %CopyrightEnd%
# Usage: rtt [-cerl] <args to erlang startup script>
@@ -23,7 +23,7 @@ while [ $# -gt 0 ]; do
case "$1" in
"-cerl")
shift
- emu=cerl
+ emu="$ERL_TOP/bin/cerl"
;;
*)
break
diff --git a/lib/reltool/vsn.mk b/lib/reltool/vsn.mk
index b0561a6110..f23a7e84a2 100644
--- a/lib/reltool/vsn.mk
+++ b/lib/reltool/vsn.mk
@@ -16,9 +16,10 @@
#
# %CopyrightEnd%
-RELTOOL_VSN = 0.5.3
+RELTOOL_VSN = 0.5.4
-TICKETS = OTP-8057
+TICKETS = OTP-8521 OTP-8590
+TICKETS_0_5_3 = OTP-8057
TICKETS_0_5_2 = OTP-8254
TICKETS_0_5_1 = OTP-8199
TICKETS_0_5 = OTP-7949
diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml
index 9eb13727c7..e1f954dda7 100644
--- a/lib/runtime_tools/doc/src/notes.xml
+++ b/lib/runtime_tools/doc/src/notes.xml
@@ -31,6 +31,21 @@
<p>This document describes the changes made to the Runtime_Tools
application.</p>
+<section><title>Runtime_Tools 1.8.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Miscellaneous updates.</p>
+ <p>
+ Own Id: OTP-8705</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Runtime_Tools 1.8.3</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk
index 4bbdef19de..9e87d5b144 100644
--- a/lib/runtime_tools/vsn.mk
+++ b/lib/runtime_tools/vsn.mk
@@ -1 +1 @@
-RUNTIME_TOOLS_VSN = 1.8.3
+RUNTIME_TOOLS_VSN = 1.8.4
diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml
index 4c4b11d3c4..e528af2522 100644
--- a/lib/sasl/doc/src/notes.xml
+++ b/lib/sasl/doc/src/notes.xml
@@ -30,6 +30,33 @@
</header>
<p>This document describes the changes made to the SASL application.</p>
+<section><title>SASL 2.1.9.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>In R13B04 sys:get_status was modified to invoke
+ format_status/2 in the callback module if the module
+ exports that function. This resulted in a change to the
+ term returned from calling sys:get_status on the
+ supervisor module, since supervisor is a gen_server and
+ gen_server exports format_status. The sasl
+ release_handler_1 module had a dependency on the
+ pre-R13B04 term returned by sys:get_status when invoked
+ on a supervisor, so the R13B04 change broke that
+ dependency.</p>
+ <p>This problem has been fixed by change
+ release_handler_1 to handle both the pre-R13B04 and
+ R13B04 terms that sys:get_status can return from a
+ supervisor.</p>
+ <p>
+ Own Id: OTP-8619 Aux Id: seq11570 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SASL 2.1.9.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/sasl/src/release_handler_1.erl b/lib/sasl/src/release_handler_1.erl
index e3e3caba99..9c0edf4e99 100644
--- a/lib/sasl/src/release_handler_1.erl
+++ b/lib/sasl/src/release_handler_1.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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_1).
@@ -554,7 +554,13 @@ get_supervisor_module(SupPid) ->
get_supervisor_module1(SupPid) ->
{status, _Pid, {module, _Mod},
[_PDict, _SysState, _Parent, _Dbg, Misc]} = sys:get_status(SupPid),
- [_Name, State, _Type, _Time] = Misc,
+ %% supervisor Misc field changed at R13B04, handle old and new variants here
+ State = case Misc of
+ [_Name, State1, _Type, _Time] ->
+ State1;
+ [_Header, _Data, {data, [{"State", State2}]}] ->
+ State2
+ end,
%% Cannot use #supervisor_state{module = Module} = State.
{ok, element(#supervisor_state.module, State)}.
diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk
index cad33a5d9c..d01a9bc4f1 100644
--- a/lib/sasl/vsn.mk
+++ b/lib/sasl/vsn.mk
@@ -1 +1 @@
-SASL_VSN = 2.1.9.1
+SASL_VSN = 2.1.9.2
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index 33e304abfa..3f4954cfbd 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -33,6 +33,138 @@
</header>
<section>
+ <title>SNMP Development Toolkit 4.17</title>
+ <p>Version 4.17 supports code replacement in runtime from/to
+ version 4.16.2, 4.16.1, 4.16, 4.15, 4.14 and 4.13.5.</p>
+
+ <section>
+ <title>Improvements and new features</title>
+ <!--
+ <p>-</p>
+ -->
+ <list type="bulleted">
+ <item>
+ <p>[agent] Added very basic support for multiple SNMPv3
+ EngineIDs in a single agent. See
+ <seealso marker="snmpa#send_notification">send_notification/7</seealso>,
+ <seealso marker="snmpa_mpd#process_packet">process_packet/7</seealso>,
+ <seealso marker="snmpa_mpd#generate_response_msg">generate_response_msg/6</seealso> or
+ <seealso marker="snmpa_mpd#generate_msg">generate_msg/6</seealso>
+ for more info. </p>
+
+ <p>Own Id: OTP-8478</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Reported Fixed Bugs and Malfunctions</title>
+ <p>-</p>
+
+ <!--
+ <list type="bulleted">
+ <item>
+ <p>The config utility
+ (<seealso marker="snmp#config">snmp:config/0</seealso>)
+ generated a default notify.conf
+ with a bad name for the starndard trap entry (was "stadard trap",
+ but should have been "standard trap"). This has been corrected. </p>
+ <p>Kenji Rikitake</p>
+ <p>Own Id: OTP-8433</p>
+ </item>
+
+ </list>
+ -->
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+ </section>
+ </section> <!-- 4.17 -->
+
+
+ <section>
+ <title>SNMP Development Toolkit 4.16.2</title>
+ <p>Version 4.16.2 supports code replacement in runtime from/to
+ version 4.16.1, 4.16, 4.15, 4.14 and 4.13.5.</p>
+
+ <section>
+ <title>Improvements and new features</title>
+ <!--
+ <p>-</p>
+ -->
+ <list type="bulleted">
+ <item>
+ <p>[compiler] The SMI specifies that a table row OID should be
+ named: { &lt;tableIdentifier&gt; "1" }. </p>
+ <p>A new option has been introduced,
+ <seealso marker="snmpc#compiler_opts">relaxed_row_name_assign_check</seealso>,
+ that allows for a more liberal numbering scheme</p>
+ <p>Own Id: OTP-8574</p>
+ </item>
+
+ <item>
+ <p>[agent|manager] Changes to make snmp (forward) compatible with
+ the new version of the crypto application (released in R14).
+ As of R14, crypto is implemented using NIFs. Also,
+ the API is more strict. </p>
+ <p>Own Id: OTP-8594</p>
+ </item>
+
+ <item>
+ <p>Auto [agent] Changed default value for the MIB server cache.
+ GC is now on by default. </p>
+ <p>Own Id: OTP-8648</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Reported Fixed Bugs and Malfunctions</title>
+ <!--
+ <p>-</p>
+ -->
+
+ <list type="bulleted">
+ <item>
+ <p>Encode/decode of Counter64 values larger than
+ 16#7fffffffffffffff (9223372036854775807) failed. </p>
+ <p>Own Id: OTP-8563</p>
+ </item>
+
+ <item>
+ <p>[compiler] Fails to compile non-contiguous BITS. </p>
+ <p>Per Hedeland</p>
+ <p>Own Id: OTP-8595</p>
+ </item>
+
+ <item>
+ <p>[manager] Raise condition causing the manager server process to
+ crash. Unregistering an agent while traffic (set/get-operations)
+ is ongoing could cause a crash in the manager server process
+ (raise condition). </p>
+ <p>Own Id: OTP-8646</p>
+ <p>Aux Id: Seq 11585</p>
+ </item>
+
+ </list>
+
+ </section>
+
+ <section>
+ <title>Incompatibilities</title>
+ <p>-</p>
+ </section>
+ </section> <!-- 4.16.2 -->
+
+
+ <section>
<title>SNMP Development Toolkit 4.16.1</title>
<p>Version 4.16.1 supports code replacement in runtime from/to
version 4.16, 4.15, 4.14 and 4.13.5.</p>
diff --git a/lib/snmp/doc/src/snmp_app.xml b/lib/snmp/doc/src/snmp_app.xml
index 57eb87a759..694e619da1 100644
--- a/lib/snmp/doc/src/snmp_app.xml
+++ b/lib/snmp/doc/src/snmp_app.xml
@@ -346,7 +346,7 @@
<p>Defines if the mib server shall perform cache gc automatically or
leave it to the user (see
<seealso marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seealso>). </p>
- <p>Default is <c>false</c>.</p>
+ <p>Default is <c>true</c>.</p>
</item>
<tag><c><![CDATA[mibs_cache_age() = integer() > 0 <optional>]]></c></tag>
diff --git a/lib/snmp/doc/src/snmp_config.xml b/lib/snmp/doc/src/snmp_config.xml
index 5bd36305fc..769b908adc 100644
--- a/lib/snmp/doc/src/snmp_config.xml
+++ b/lib/snmp/doc/src/snmp_config.xml
@@ -343,7 +343,7 @@
<p>Defines if the mib server shall perform cache gc automatically or
leave it to the user (see
<seealso marker="snmpa#gc_mibs_cache">gc_mibs_cache/0,1,2,3</seealso>). </p>
- <p>Default is <c>false</c>.</p>
+ <p>Default is <c>true</c>.</p>
</item>
<tag><c><![CDATA[mibs_cache_age() = integer() > 0 <optional>]]></c></tag>
diff --git a/lib/snmp/doc/src/snmpa.xml b/lib/snmp/doc/src/snmpa.xml
index b3661ae9b0..1be6abe6dd 100644
--- a/lib/snmp/doc/src/snmpa.xml
+++ b/lib/snmp/doc/src/snmpa.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>snmpa</title>
@@ -648,6 +648,20 @@ notification_delivery_info() = #snmpa_notification_delivery_info{}
<desc>
<p>Disable the mib server cache. </p>
+ <marker id="which_mibs_cache_size"></marker>
+ </desc>
+ </func>
+
+ <func>
+ <name>which_mibs_cache_size() -> void()</name>
+ <name>which_mibs_cache_size(Agent) -> void()</name>
+ <fsummary>The size of the mib server cache</fsummary>
+ <type>
+ <v>Agent = pid() | atom()</v>
+ </type>
+ <desc>
+ <p>Retreive the size of the mib server cache. </p>
+
<marker id="gc_mibs_cache"></marker>
</desc>
</func>
@@ -867,6 +881,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<name>send_notification(Agent, Notification, Receiver, Varbinds)</name>
<name>send_notification(Agent, Notification, Receiver, NotifyName, Varbinds)</name>
<name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds) -> void() </name>
+ <name>send_notification(Agent, Notification, Receiver, NotifyName, ContextName, Varbinds, LocalEngineID) -> void() </name>
<fsummary>Send a notification</fsummary>
<type>
<v>Agent = pid() | atom()</v>
@@ -888,6 +903,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<v>OID = oid()</v>
<v>Value = term()</v>
<v>RowIndex = [int()]</v>
+ <v>LocalEngineID = string()</v>
</type>
<desc>
<p>Sends the notification <c>Notification</c> to the
@@ -1027,6 +1043,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<item><c>{?sysLocation_instance, "upstairs"}</c> (provided
that the generated <c>.hrl</c> file is included)</item>
</list>
+
<p>If a variable in the notification is a table element, the
<c>RowIndex</c> for the element must be given in the
<c>Varbinds</c> list. In this case, the OBJECT IDENTIFIER sent
@@ -1034,15 +1051,27 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
element. This OBJECT IDENTIFIER could be used in a get
operation later.
</p>
+
<p>This function is asynchronous, and does not return any
information. If an error occurs, <c>user_err/2</c> of the error
report module is called and the notification is discarded.
</p>
+ <note>
+ <p>Note that the use of the LocalEngineID argument is only intended
+ for special cases, if the agent is to "emulate" multiple EngineIDs!
+ By default, the agent uses the value of <c>SnmpEngineID</c>
+ (see SNMP-FRAMEWORK-MIB). </p>
+ </note>
+
+<!--
<marker id="send_trap"></marker>
+-->
+ <marker id="discovery"></marker>
</desc>
</func>
+<!--
<func>
<name>send_trap(Agent,Trap,Community)</name>
<name>send_trap(Agent,Trap,Community,Varbinds) -> void()</name>
@@ -1114,6 +1143,7 @@ snmp_agent:register_subagent(SA1,[1,2,3], SA2).
<marker id="discovery"></marker>
</desc>
</func>
+-->
<func>
<name>discovery(TargetName, Notification) -> {ok, ManagerEngineID} | {error, Reason}</name>
diff --git a/lib/snmp/doc/src/snmpa_mpd.xml b/lib/snmp/doc/src/snmpa_mpd.xml
index ea5bde8956..202e6b5661 100644
--- a/lib/snmp/doc/src/snmpa_mpd.xml
+++ b/lib/snmp/doc/src/snmpa_mpd.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1999</year><year>2009</year>
+ <year>1999</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>snmpa_mpd</title>
@@ -63,15 +63,19 @@
</func>
<func>
- <name>process_packet(Packet, TDomain, TAddress, State) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
+ <name>process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
+ <name>process_packet(Packet, TDomain, TAddress, LocalEngineID, State, NoteStore, Log) -> {ok, Vsn, Pdu, PduMS, ACMData} | {discarded, Reason} | {discovery, DiscoPacket}</name>
<fsummary>Process a packet received from the network</fsummary>
<type>
<v>Packet = binary()</v>
<v>TDomain = snmpUDPDomain</v>
<v>TAddress = {Ip, Udp}</v>
+ <v>LocalEngineID = string()</v>
<v>Ip = {integer(), integer(), integer(), integer()}</v>
<v>Udp = integer()</v>
<v>State = mpd_state()</v>
+ <v>NoteStore = pid()</v>
+ <v>Log = snmp_log()</v>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
<v>Pdu = #pdu</v>
<v>PduMs = integer()</v>
@@ -84,18 +88,27 @@
decryption as necessary. The return values should be passed the
agent.</p>
+ <note>
+ <p>Note that the use of the LocalEngineID argument is only intended
+ for special cases, if the agent is to "emulate" multiple EngineIDs!
+ By default, the agent uses the value of <c>SnmpEngineID</c>
+ (see SNMP-FRAMEWORK-MIB). </p>
+ </note>
+
<marker id="generate_response_msg"></marker>
</desc>
</func>
<func>
- <name>generate_response_msg(Vsn, RePdu, Type, ACMData) -> {ok, Packet} | {discarded, Reason}</name>
+ <name>generate_response_msg(Vsn, RePdu, Type, ACMData, Log) -> {ok, Packet} | {discarded, Reason}</name>
+ <name>generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) -> {ok, Packet} | {discarded, Reason}</name>
<fsummary>Generate a response packet to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
<v>RePdu = #pdu</v>
<v>Type = atom()</v>
<v>ACMData = acm_data()</v>
+ <v>LocalEngineID = string()</v>
<v>Packet = binary()</v>
</type>
<desc>
@@ -103,17 +116,27 @@
network. <c>Type</c> is the <c>#pdu.type</c> of the original
request.</p>
+ <note>
+ <p>Note that the use of the LocalEngineID argument is only intended
+ for special cases, if the agent is to "emulate" multiple EngineIDs!
+ By default, the agent uses the value of <c>SnmpEngineID</c>
+ (see SNMP-FRAMEWORK-MIB). </p>
+ </note>
+
<marker id="generate_msg"></marker>
</desc>
</func>
<func>
- <name>generate_msg(Vsn, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
+ <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
+ <name>generate_msg(Vsn, NoteStore, Pdu, MsgData, LocalEngineID, To) -> {ok, PacketsAndAddresses} | {discarded, Reason}</name>
<fsummary>Generate a request message to be sent to the network</fsummary>
<type>
<v>Vsn = 'version-1' | 'version-2' | 'version-3'</v>
+ <v>NoteStore = pid()</v>
<v>Pdu = #pdu</v>
<v>MsgData = msg_data()</v>
+ <v>LocalEngineID = string()</v>
<v>To = [dest_addrs()]</v>
<v>PacketsAndAddresses = [{TDomain, TAddress, Packet}]</v>
<v>TDomain = snmpUDPDomain</v>
@@ -136,6 +159,13 @@
also received from the requests mentioned above.
</p>
+ <note>
+ <p>Note that the use of the LocalEngineID argument is only intended
+ for special cases, if the agent is to "emulate" multiple EngineIDs!
+ By default, the agent uses the value of <c>SnmpEngineID</c>
+ (see SNMP-FRAMEWORK-MIB). </p>
+ </note>
+
<marker id="discarded_pdu"></marker>
</desc>
</func>
diff --git a/lib/snmp/doc/src/snmpc.xml b/lib/snmp/doc/src/snmpc.xml
index 48d63d6c91..fbd0950c69 100644
--- a/lib/snmp/doc/src/snmpc.xml
+++ b/lib/snmp/doc/src/snmpc.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>snmpc</title>
@@ -47,9 +47,10 @@
<type>
<v>File = string()</v>
<v>Options = [opt()]</v>
- <v>opt() = db() | deprecated() | description() | reference() | group_check() | i() | il() | imports() | module() | module_identity() | outdir() | no_defs() | verbosity() | warnings()</v>
+ <v>opt() = db() | relaxed_row_name_assign_check() | deprecated() | description() | reference() | group_check() | i() | il() | imports() | module() | module_identity() | outdir() | no_defs() | verbosity() | warnings()</v>
<v>db() = {db, volatile|persistent|mnesia}</v>
<v>deprecated() = {deprecated, bool()}</v>
+ <v>relaxed_row_name_assign_check() = relaxed_row_name_assign_check</v>
<v>description() = description</v>
<v>reference() = reference</v>
<v>group_check() = {group_check, bool()}</v>
@@ -71,75 +72,104 @@
compiled file <c>BinFileName</c> is called
<c><![CDATA[<File>.bin]]></c>. </p>
<list type="bulleted">
- <item>The option <c>db</c> specifies which database should
- be used for the default instrumentation. Default is
- <c>volatile</c>.
+ <item>
+ <p>The option <c>db</c> specifies which database should
+ be used for the default instrumentation. </p>
+ <p>Default is <c>volatile</c>. </p>
+ </item>
+ <item>
+ <p>The option <c>deprecated</c> specifies if a deprecated
+ definition should be kept or not. If the option is
+ false the MIB compiler will ignore all deprecated
+ definitions. </p>
+ <p>Default is <c>true</c>. </p>
</item>
- <item>The option <c>deprecated</c> specifies if a deprecated
- definition should be kept or not. If the option is
- false the MIB compiler will ignore all deprecated
- definitions. Default is <c>true</c>.
+ <item>
+ <p>The option <c>relaxed_row_name_assign_check</c>, if present,
+ specifies that the row name assign check shall not be done
+ strictly according to the SMI (which allows only the value 1).
+ With this option, all values greater than zero is allowed
+ (>= 1). This means that the error will be converted to a
+ warning. </p>
+ <p>By default it is not included, but if this option is present
+ it will be. </p>
</item>
- <item>The option <c>description</c> specifies if the text
- of the DESCRIPTION field will be included or not. By default
- it is not included, but if this option is present it will
- be.
+ <item>
+ <p>The option <c>description</c> specifies if the text
+ of the DESCRIPTION field will be included or not. </p>
+ <p>By default it is not included, but if this option is
+ present it will be. </p>
</item>
- <item>The option <c>reference</c> specifies if the text
- of the REFERENCE field, when found in a table definition,
- will be included or not. By default
- it is not included, but if this option is present it will
- be. The reference text will be placed in the allocList field
- of the mib-entry record (#me{}) for the table.
+ <item>
+ <p>The option <c>reference</c> specifies if the text
+ of the REFERENCE field, when found in a table definition,
+ will be included or not. </p>
+ <p>By default it is not included, but if this option is present
+ it will be. The reference text will be placed in the allocList
+ field of the mib-entry record (#me{}) for the table. </p>
</item>
- <item>The option <c>group_check</c> specifies whether the
- mib compiler should check the OBJECT-GROUP macro and
- the NOTIFICATION-GROUP macro for correctness or not.
- Default is <c>true</c>.
+ <item>
+ <p>The option <c>group_check</c> specifies whether the
+ mib compiler should check the OBJECT-GROUP macro and
+ the NOTIFICATION-GROUP macro for correctness or not. </p>
+ <p>Default is <c>true</c>. </p>
</item>
- <item>The option <c>i</c> specifies the path to search for
- imported (compiled) MIB files. The directories should be
- strings with a trailing directory delimiter. Default is
- <c>["./"]</c>.
+ <item>
+ <p>The option <c>i</c> specifies the path to search for
+ imported (compiled) MIB files. The directories should be
+ strings with a trailing directory delimiter. </p>
+ <p>Default is <c>["./"]</c>. </p>
</item>
- <item>The option <c>il</c> (include_lib) also specifies a
- list of directories to search for imported MIBs. It
- assumes that the first element in the directory name
- corresponds to an OTP application. The compiler will find
- the current installed version. For example, the value
- ["snmp/mibs/"] will be replaced by ["snmp-3.1.1/mibs/"]
- (or what the current version may be in the system). The
- current directory and the <c><![CDATA[<snmp-home>/priv/mibs/]]></c>
- are always listed last in the include path.
+ <item>
+ <p>The option <c>il</c> (include_lib) also specifies a
+ list of directories to search for imported MIBs. It
+ assumes that the first element in the directory name
+ corresponds to an OTP application. The compiler will find
+ the current installed version. For example, the value
+ ["snmp/mibs/"] will be replaced by ["snmp-3.1.1/mibs/"]
+ (or what the current version may be in the system). The
+ current directory and the
+ <c><![CDATA[<snmp-home>/priv/mibs/]]></c>
+ are always listed last in the include path. </p>
</item>
- <item>The option <c>imports</c>, if present, specifies that the
- IMPORT statement of the MIB shall be included in the compiled mib.
+ <item>
+ <p>The option <c>imports</c>, if present, specifies that
+ the IMPORT statement of the MIB shall be included in the
+ compiled mib. </p>
</item>
- <item>The option <c>module</c>, if present, specifies the
- name of a module which implements all instrumentation
- functions for the MIB. The name of all instrumentation
- functions must be the same as the corresponding managed
- object it implements.
+ <item>
+ <p>The option <c>module</c>, if present, specifies the
+ name of a module which implements all instrumentation
+ functions for the MIB. </p>
+ <p>The name of all instrumentation
+ functions must be the same as the corresponding managed
+ object it implements. </p>
</item>
- <item>The option <c>module_identity</c>, if present, specifies
- that the info part of the MODULE-IDENTITY statement of the MIB
- shall be included in the compiled mib.
+ <item>
+ <p>The option <c>module_identity</c>, if present, specifies
+ that the info part of the MODULE-IDENTITY statement of the MIB
+ shall be included in the compiled mib. </p>
</item>
- <item>The option <c>no_defs</c>, if present, specifies
- that if a managed object does not have an instrumentation
- function, the default instrumentation function should NOT
- be used, instead this is reported as an error, and the
- compilation aborts.
+ <item>
+ <p>The option <c>no_defs</c>, if present, specifies
+ that if a managed object does not have an instrumentation
+ function, the default instrumentation function should NOT
+ be used, instead this is reported as an error, and the
+ compilation aborts. </p>
</item>
- <item>The option <c>verbosity</c> specifies the verbosity of
- the SNMP mib compiler. I.e. if warning, info, log, debug
- and trace messages shall be shown. Default is <c>silence</c>.
- Note that if the option <c>warnings</c> is <c>true</c> and the
- option <c>verbosity</c> is <c>silence</c>, warning messages will
- still be shown.
+ <item>
+ <p>The option <c>verbosity</c> specifies the verbosity of
+ the SNMP mib compiler. I.e. if warning, info, log, debug
+ and trace messages shall be shown. </p>
+ <p>Default is <c>silence</c>. </p>
+ <p>Note that if the option <c>warnings</c> is <c>true</c> and the
+ option <c>verbosity</c> is <c>silence</c>, warning messages will
+ still be shown. </p>
</item>
- <item>The option <c>warnings</c> specifies whether warning
- messages should be shown. Default is <c>true</c>.
+ <item>
+ <p>The option <c>warnings</c> specifies whether warning
+ messages should be shown. </p>
+ <p>Default is <c>true</c>. </p>
</item>
</list>
<p>The MIB compiler understands both SMIv1 and SMIv2 MIBs. It
diff --git a/lib/snmp/src/agent/snmpa.erl b/lib/snmp/src/agent/snmpa.erl
index a113bba3a7..87b191caed 100644
--- a/lib/snmp/src/agent/snmpa.erl
+++ b/lib/snmp/src/agent/snmpa.erl
@@ -47,6 +47,7 @@
mib_of/1, mib_of/2,
me_of/1, me_of/2,
invalidate_mibs_cache/0, invalidate_mibs_cache/1,
+ which_mibs_cache_size/0, which_mibs_cache_size/1,
enable_mibs_cache/0, enable_mibs_cache/1,
disable_mibs_cache/0, disable_mibs_cache/1,
gc_mibs_cache/0, gc_mibs_cache/1, gc_mibs_cache/2, gc_mibs_cache/3,
@@ -60,7 +61,7 @@
register_subagent/3, unregister_subagent/2,
send_notification/3, send_notification/4, send_notification/5,
- send_notification/6,
+ send_notification/6, send_notification/7,
send_trap/3, send_trap/4,
discovery/2, discovery/3, discovery/4, discovery/5, discovery/6,
@@ -302,6 +303,13 @@ invalidate_mibs_cache(Agent) ->
snmpa_agent:invalidate_mibs_cache(Agent).
+which_mibs_cache_size() ->
+ which_mibs_cache_size(snmp_master_agent).
+
+which_mibs_cache_size(Agent) ->
+ snmpa_agent:which_mibs_cache_size(Agent).
+
+
enable_mibs_cache() ->
enable_mibs_cache(snmp_master_agent).
@@ -415,14 +423,23 @@ send_notification(Agent, Notification, Recv, Varbinds) ->
send_notification(Agent, Notification, Recv, NotifyName, Varbinds) ->
send_notification(Agent, Notification, Recv, NotifyName, "", Varbinds).
-send_notification(Agent, Notification, Recv,
- NotifyName, ContextName, Varbinds)
+send_notification(Agent, Notification, Recv, NotifyName,
+ ContextName, Varbinds)
when (is_list(NotifyName) andalso
is_list(ContextName) andalso
is_list(Varbinds)) ->
snmpa_agent:send_trap(Agent, Notification, NotifyName,
ContextName, Recv, Varbinds).
+send_notification(Agent, Notification, Recv,
+ NotifyName, ContextName, Varbinds, LocalEngineID)
+ when (is_list(NotifyName) andalso
+ is_list(ContextName) andalso
+ is_list(Varbinds) andalso
+ is_list(LocalEngineID)) ->
+ snmpa_agent:send_trap(Agent, Notification, NotifyName,
+ ContextName, Recv, Varbinds, LocalEngineID).
+
%% Kept for backwards compatibility
send_trap(Agent, Trap, Community) ->
send_notification(Agent, Trap, no_receiver, Community, "", []).
diff --git a/lib/snmp/src/agent/snmpa_agent.erl b/lib/snmp/src/agent/snmpa_agent.erl
index fb04fca632..f70885b2ec 100644
--- a/lib/snmp/src/agent/snmpa_agent.erl
+++ b/lib/snmp/src/agent/snmpa_agent.erl
@@ -30,7 +30,7 @@
-export([subagent_set/2,
load_mibs/2, unload_mibs/2, which_mibs/1, whereis_mib/2, info/1,
register_subagent/3, unregister_subagent/2,
- send_trap/6,
+ send_trap/6, send_trap/7,
register_notification_filter/5,
unregister_notification_filter/2,
which_notification_filter/1,
@@ -48,6 +48,7 @@
get/2, get/3, get_next/2, get_next/3]).
-export([mib_of/1, mib_of/2, me_of/1, me_of/2,
invalidate_mibs_cache/1,
+ which_mibs_cache_size/1,
enable_mibs_cache/1, disable_mibs_cache/1,
gc_mibs_cache/1, gc_mibs_cache/2, gc_mibs_cache/3,
enable_mibs_cache_autogc/1, disable_mibs_cache_autogc/1,
@@ -64,7 +65,7 @@
%% Internal exports
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3, tr_var/2, tr_varbind/1,
- handle_pdu/7, worker/2, worker_loop/1, do_send_trap/6]).
+ handle_pdu/7, worker/2, worker_loop/1, do_send_trap/7]).
-ifndef(default_verbosity).
-define(default_verbosity,silence).
@@ -245,6 +246,10 @@ disable_mibs_cache(Agent) ->
call(Agent, {mibs_cache_request, disable_cache}).
+which_mibs_cache_size(Agent) ->
+ call(Agent, {mibs_cache_request, cache_size}).
+
+
enable_mibs_cache_autogc(Agent) ->
call(Agent, {mibs_cache_request, enable_autogc}).
@@ -524,14 +529,15 @@ which_notification_filter(Agent) ->
send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds) ->
?d("send_trap -> entry with"
- "~n self(): ~p"
- "~n Agent: ~p [~p]"
- "~n Trap: ~p"
- "~n NotifyName: ~p"
- "~n CtxName: ~p"
- "~n Recv: ~p"
- "~n Varbinds: ~p",
- [self(), Agent, wis(Agent), Trap, NotifyName, CtxName, Recv, Varbinds]),
+ "~n self(): ~p"
+ "~n Agent: ~p [~p]"
+ "~n Trap: ~p"
+ "~n NotifyName: ~p"
+ "~n CtxName: ~p"
+ "~n Recv: ~p"
+ "~n Varbinds: ~p",
+ [self(), Agent, wis(Agent),
+ Trap, NotifyName, CtxName, Recv, Varbinds]),
Msg = {send_trap, Trap, NotifyName, CtxName, Recv, Varbinds},
case (wis(Agent) =:= self()) of
false ->
@@ -540,6 +546,27 @@ send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds) ->
Agent ! Msg
end.
+send_trap(Agent, Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID) ->
+ ?d("send_trap -> entry with"
+ "~n self(): ~p"
+ "~n Agent: ~p [~p]"
+ "~n Trap: ~p"
+ "~n NotifyName: ~p"
+ "~n CtxName: ~p"
+ "~n Recv: ~p"
+ "~n Varbinds: ~p"
+ "~n LocalEngineID: ~p",
+ [self(), Agent, wis(Agent),
+ Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID]),
+ Msg =
+ {send_trap, Trap, NotifyName, CtxName, Recv, Varbinds, LocalEngineID},
+ case (wis(Agent) =:= self()) of
+ false ->
+ call(Agent, Msg);
+ true ->
+ Agent ! Msg
+ end.
+
%% -- Discovery functions --
@@ -626,6 +653,7 @@ wis(Pid) when is_pid(Pid) ->
wis(Atom) when is_atom(Atom) ->
whereis(Atom).
+
forward_trap(Agent, TrapRecord, NotifyName, CtxName, Recv, Varbinds) ->
Agent ! {forward_trap, TrapRecord, NotifyName, CtxName, Recv, Varbinds}.
@@ -719,14 +747,15 @@ handle_info(worker_available, S) ->
handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, S) ->
?vlog("[handle_info] send trap request:"
- "~n Trap: ~p"
- "~n NotifyName: ~p"
- "~n ContextName: ~p"
- "~n Recv: ~p"
- "~n Varbinds: ~p",
- [Trap,NotifyName,ContextName,Recv,Varbinds]),
+ "~n Trap: ~p"
+ "~n NotifyName: ~p"
+ "~n ContextName: ~p"
+ "~n Recv: ~p"
+ "~n Varbinds: ~p",
+ [Trap, NotifyName, ContextName, Recv, Varbinds]),
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
case catch handle_send_trap(S, Trap, NotifyName, ContextName,
- Recv, Varbinds) of
+ Recv, Varbinds, LocalEngineID) of
{ok, NewS} ->
{noreply, NewS};
{'EXIT', R} ->
@@ -736,17 +765,39 @@ handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds}, S) ->
{noreply, S}
end;
-handle_info({forward_trap, TrapRecord, NotifyName, ContextName,
- Recv, Varbinds},S) ->
+handle_info({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds,
+ LocalEngineID}, S) ->
+ ?vlog("[handle_info] send trap request:"
+ "~n Trap: ~p"
+ "~n NotifyName: ~p"
+ "~n ContextName: ~p"
+ "~n Recv: ~p"
+ "~n Varbinds: ~p"
+ "~n LocalEngineID: ~p",
+ [Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID]),
+ case catch handle_send_trap(S, Trap, NotifyName, ContextName,
+ Recv, Varbinds, LocalEngineID) of
+ {ok, NewS} ->
+ {noreply, NewS};
+ {'EXIT', R} ->
+ ?vinfo("Trap not sent:~n ~p", [R]),
+ {noreply, S};
+ _ ->
+ {noreply, S}
+ end;
+
+handle_info({forward_trap, TrapRecord, NotifyName, ContextName,
+ Recv, Varbinds}, S) ->
?vlog("[handle_info] forward trap request:"
- "~n TrapRecord: ~p"
- "~n NotifyName: ~p"
- "~n ContextName: ~p"
- "~n Recv: ~p"
- "~n Varbinds: ~p",
- [TrapRecord,NotifyName,ContextName,Recv,Varbinds]),
+ "~n TrapRecord: ~p"
+ "~n NotifyName: ~p"
+ "~n ContextName: ~p"
+ "~n Recv: ~p"
+ "~n Varbinds: ~p",
+ [TrapRecord, NotifyName, ContextName, Recv, Varbinds]),
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
case (catch maybe_send_trap(S, TrapRecord, NotifyName, ContextName,
- Recv, Varbinds)) of
+ Recv, Varbinds, LocalEngineID)) of
{ok, NewS} ->
{noreply, NewS};
{'EXIT', R} ->
@@ -856,17 +907,52 @@ handle_call(restart_set_worker, _From, #state{set_worker = Pid} = S) ->
ok
end,
{reply, ok, S};
+
handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds},
_From, S) ->
?vlog("[handle_call] send trap request:"
- "~n Trap: ~p"
- "~n NotifyName: ~p"
- "~n ContextName: ~p"
- "~n Recv: ~p"
- "~n Varbinds: ~p",
- [Trap,NotifyName,ContextName,Recv,Varbinds]),
+ "~n Trap: ~p"
+ "~n NotifyName: ~p"
+ "~n ContextName: ~p"
+ "~n Recv: ~p"
+ "~n Varbinds: ~p",
+ [Trap, NotifyName, ContextName, Recv, Varbinds]),
+ LocalEngineID =
+ case S#state.type of
+ master_agent ->
+ ?DEFAULT_LOCAL_ENGINE_ID;
+ _ ->
+ %% subagent -
+ %% we don't need this, eventually the trap sent request
+ %% will reach the master-agent and then it will look up
+ %% the proper engine id.
+ ignore
+ end,
+ case (catch handle_send_trap(S, Trap, NotifyName, ContextName,
+ Recv, Varbinds, LocalEngineID)) of
+ {ok, NewS} ->
+ {reply, ok, NewS};
+ {'EXIT', Reason} ->
+ ?vinfo("Trap not sent:~n ~p", [Reason]),
+ {reply, {error, {send_failed, Reason}}, S};
+ _ ->
+ ?vinfo("Trap not sent", []),
+ {reply, {error, send_failed}, S}
+ end;
+
+handle_call({send_trap, Trap, NotifyName,
+ ContextName, Recv, Varbinds, LocalEngineID},
+ _From, S) ->
+ ?vlog("[handle_call] send trap request:"
+ "~n Trap: ~p"
+ "~n NotifyName: ~p"
+ "~n ContextName: ~p"
+ "~n Recv: ~p"
+ "~n Varbinds: ~p"
+ "~n LocalEngineID: ~p",
+ [Trap, NotifyName, ContextName, Recv, Varbinds, LocalEngineID]),
case (catch handle_send_trap(S, Trap, NotifyName, ContextName,
- Recv, Varbinds)) of
+ Recv, Varbinds, LocalEngineID)) of
{ok, NewS} ->
{reply, ok, NewS};
{'EXIT', Reason} ->
@@ -876,8 +962,10 @@ handle_call({send_trap, Trap, NotifyName, ContextName, Recv, Varbinds},
?vinfo("Trap not sent", []),
{reply, {error, send_failed}, S}
end;
+
handle_call({discovery,
- TargetName, Notification, ContextName, Vbs, DiscoHandler, ExtraInfo},
+ TargetName, Notification, ContextName, Vbs, DiscoHandler,
+ ExtraInfo},
From,
#state{disco = undefined} = S) ->
?vlog("[handle_call] initiate discovery process:"
@@ -1219,6 +1307,8 @@ handle_mibs_cache_request(MibServer, Req) ->
snmpa_mib:gc_cache(MibServer, Age);
{gc_cache, Age, GcLimit} ->
snmpa_mib:gc_cache(MibServer, Age, GcLimit);
+ cache_size ->
+ snmpa_mib:which_cache_size(MibServer);
enable_cache ->
snmpa_mib:enable_cache(MibServer);
disable_cache ->
@@ -1432,17 +1522,20 @@ spawn_thread(Vsn, Pdu, PduMS, ACMData, Address, Extra) ->
Args = [Vsn, Pdu, PduMS, ACMData, Address, Extra, Dict],
proc_lib:spawn_link(?MODULE, handle_pdu, Args).
-spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V) ->
+spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID) ->
Dict = get(),
proc_lib:spawn_link(?MODULE, do_send_trap,
- [TrapRec, NotifyName, ContextName, Recv, V, Dict]).
+ [TrapRec, NotifyName, ContextName,
+ Recv, Vbs, LocalEngineID, Dict]).
-do_send_trap(TrapRec, NotifyName, ContextName, Recv, V, Dict) ->
+do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID, Dict) ->
lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict),
put(sname,trap_sender_short_name(get(sname))),
?vlog("starting",[]),
- snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, V,
- get(net_if)).
+ snmpa_trap:send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID, get(net_if)).
worker(Master, Dict) ->
lists:foreach(fun({Key, Val}) -> put(Key, Val) end, Dict),
@@ -1457,17 +1550,22 @@ worker_loop(Master) ->
handle_pdu(Vsn, Pdu, PduMS, ACMData, Address, Extra),
Master ! worker_available;
- %% Old style message
- {MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra} ->
- ?vtrace("worker_loop -> received (old) request", []),
- do_handle_pdu(MibView, Vsn, Pdu, PduMS, ACMData, AgentData, Extra),
+ %% We don't trap exits!
+ {TrapRec, NotifyName, ContextName, Recv, Vbs} ->
+ ?vtrace("worker_loop -> send trap:"
+ "~n ~p", [TrapRec]),
+ snmpa_trap:send_trap(TrapRec, NotifyName,
+ ContextName, Recv, Vbs, get(net_if)),
Master ! worker_available;
- {TrapRec, NotifyName, ContextName, Recv, V} -> % We don't trap exits!
+ %% We don't trap exits!
+ {send_trap,
+ TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID} ->
?vtrace("worker_loop -> send trap:"
"~n ~p", [TrapRec]),
snmpa_trap:send_trap(TrapRec, NotifyName,
- ContextName, Recv, V, get(net_if)),
+ ContextName, Recv, Vbs, LocalEngineID,
+ get(net_if)),
Master ! worker_available;
{verbosity, Verbosity} ->
@@ -1616,13 +1714,15 @@ handle_acm_error(Vsn, Reason, Pdu, ACMData, Address, Extra) ->
end.
-handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds) ->
+handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds,
+ LocalEngineID) ->
?vtrace("handle_send_trap -> entry with"
- "~n S#state.type: ~p"
- "~n TrapName: ~p"
- "~n NotifyName: ~p"
- "~n ContextName: ~p",
- [S#state.type, TrapName, NotifyName, ContextName]),
+ "~n S#state.type: ~p"
+ "~n TrapName: ~p"
+ "~n NotifyName: ~p"
+ "~n ContextName: ~p"
+ "~n LocalEngineID: ~p",
+ [S#state.type, TrapName, NotifyName, ContextName, LocalEngineID]),
case snmpa_trap:construct_trap(TrapName, Varbinds) of
{ok, TrapRecord, VarList} ->
?vtrace("handle_send_trap -> construction complete: "
@@ -1639,7 +1739,8 @@ handle_send_trap(S, TrapName, NotifyName, ContextName, Recv, Varbinds) ->
?vtrace("handle_send_trap -> "
"[master] handle send trap",[]),
maybe_send_trap(S, TrapRecord, NotifyName,
- ContextName, Recv, VarList)
+ ContextName, Recv, VarList,
+ LocalEngineID)
end;
error ->
error
@@ -1676,7 +1777,8 @@ maybe_forward_trap(#state{parent = Parent, nfilters = NFs} = S,
maybe_send_trap(#state{nfilters = NFs} = S,
- TrapRec, NotifyName, ContextName, Recv, Varbinds) ->
+ TrapRec, NotifyName, ContextName, Recv, Varbinds,
+ LocalEngineID) ->
?vtrace("maybe_send_trap -> entry with"
"~n NFs: ~p", [NFs]),
case filter_notification(NFs, [], TrapRec) of
@@ -1693,39 +1795,45 @@ maybe_send_trap(#state{nfilters = NFs} = S,
?vtrace("maybe_send_trap -> send trap:"
"~n ~p", [TrapRec2]),
do_handle_send_trap(S, TrapRec2,
- NotifyName, ContextName, Recv, Varbinds);
+ NotifyName, ContextName, Recv, Varbinds,
+ LocalEngineID);
{send, Removed, TrapRec2} ->
?vtrace("maybe_send_trap -> send trap:"
"~n ~p", [TrapRec2]),
NFs2 = del_notification_filter(Removed, NFs),
do_handle_send_trap(S#state{nfilters = NFs2}, TrapRec2,
- NotifyName, ContextName, Recv, Varbinds)
+ NotifyName, ContextName, Recv, Varbinds,
+ LocalEngineID)
end.
-do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds) ->
- V = snmpa_trap:try_initialise_vars(get(mibserver), Varbinds),
+do_handle_send_trap(S, TrapRec, NotifyName, ContextName, Recv, Varbinds,
+ LocalEngineID) ->
+ Vbs = snmpa_trap:try_initialise_vars(get(mibserver), Varbinds),
case S#state.type of
subagent ->
forward_trap(S#state.parent, TrapRec, NotifyName, ContextName,
- Recv, V),
+ Recv, Vbs),
{ok, S};
master_agent when S#state.multi_threaded =:= false ->
?vtrace("do_handle_send_trap -> send trap:"
"~n ~p", [TrapRec]),
snmpa_trap:send_trap(TrapRec, NotifyName, ContextName,
- Recv, V, get(net_if)),
+ Recv, Vbs, LocalEngineID, get(net_if)),
{ok, S};
master_agent when S#state.worker_state =:= busy ->
%% Main worker busy => create new worker
?vtrace("do_handle_send_trap -> main worker busy: "
"spawn a trap sender", []),
- spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, V),
+ spawn_trap_thread(TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID),
{ok, S};
master_agent ->
%% Send to main worker
?vtrace("do_handle_send_trap -> send to main worker",[]),
- S#state.worker ! {TrapRec, NotifyName, ContextName, Recv, V},
+ S#state.worker ! {send_trap,
+ TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID},
{ok, S#state{worker_state = busy}}
end.
diff --git a/lib/snmp/src/agent/snmpa_internal.hrl b/lib/snmp/src/agent/snmpa_internal.hrl
index a33a6809dc..9fa874f119 100644
--- a/lib/snmp/src/agent/snmpa_internal.hrl
+++ b/lib/snmp/src/agent/snmpa_internal.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-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%
%%
@@ -22,6 +22,8 @@
-include_lib("snmp/src/app/snmp_internal.hrl").
+-define(DEFAULT_LOCAL_ENGINE_ID, snmp_framework_mib:get_engine_id()).
+
-define(snmpa_info(F, A), ?snmp_info("agent", F, A)).
-define(snmpa_warning(F, A), ?snmp_warning("agent", F, A)).
-define(snmpa_error(F, A), ?snmp_error("agent", F, A)).
diff --git a/lib/snmp/src/agent/snmpa_mib.erl b/lib/snmp/src/agent/snmpa_mib.erl
index 370989d0be..ce90db18b3 100644
--- a/lib/snmp/src/agent/snmpa_mib.erl
+++ b/lib/snmp/src/agent/snmpa_mib.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(snmpa_mib).
@@ -55,7 +55,7 @@
-define(NO_CACHE, no_mibs_cache).
-define(DEFAULT_CACHE_USAGE, true).
-define(CACHE_GC_TICKTIME, timer:minutes(1)).
--define(DEFAULT_CACHE_AUTOGC, false).
+-define(DEFAULT_CACHE_AUTOGC, true).
-define(DEFAULT_CACHE_GCLIMIT, 100).
-define(DEFAULT_CACHE_AGE, timer:minutes(10)).
-define(CACHE_GC_TRIGGER, cache_gc_trigger).
diff --git a/lib/snmp/src/agent/snmpa_mpd.erl b/lib/snmp/src/agent/snmpa_mpd.erl
index 2e09286b87..fd75b98f84 100644
--- a/lib/snmp/src/agent/snmpa_mpd.erl
+++ b/lib/snmp/src/agent/snmpa_mpd.erl
@@ -1,27 +1,28 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(snmpa_mpd).
-export([init/1, reset/0, inc/1, counters/0,
discarded_pdu/1,
- process_packet/6,
- generate_response_msg/5, generate_msg/5,
+ process_packet/6, process_packet/7,
+ generate_response_msg/5, generate_response_msg/6,
+ generate_msg/5, generate_msg/6,
generate_discovery_msg/4,
process_taddrs/1,
generate_req_id/0]).
@@ -34,6 +35,7 @@
-define(VMODULE,"MPD").
-include("snmp_verbosity.hrl").
+-include("snmpa_internal.hrl").
-define(empty_msg_size, 24).
@@ -120,6 +122,12 @@ reset() ->
%% section 4.2.1 in rfc2272)
%%-----------------------------------------------------------------
process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) ->
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
+ process_packet(Packet, TDomain, TAddress, LocalEngineID,
+ State, NoteStore, Log).
+
+process_packet(Packet, TDomain, TAddress, LocalEngineID,
+ State, NoteStore, Log) ->
inc(snmpInPkts),
case catch snmp_pdus:dec_message_only(binary_to_list(Packet)) of
@@ -127,15 +135,17 @@ process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) ->
when State#state.v1 =:= true ->
?vlog("v1, community: ~s", [Community]),
HS = ?empty_msg_size + length(Community),
- v1_v2c_proc('version-1', NoteStore, Community, TDomain, TAddress,
- Data, HS, Log, Packet);
+ v1_v2c_proc('version-1', NoteStore, Community,
+ TDomain, TAddress,
+ LocalEngineID, Data, HS, Log, Packet);
#message{version = 'version-2', vsn_hdr = Community, data = Data}
when State#state.v2c =:= true ->
?vlog("v2c, community: ~s", [Community]),
HS = ?empty_msg_size + length(Community),
- v1_v2c_proc('version-2', NoteStore, Community, TDomain, TAddress,
- Data, HS, Log, Packet);
+ v1_v2c_proc('version-2', NoteStore, Community,
+ TDomain, TAddress,
+ LocalEngineID, Data, HS, Log, Packet);
#message{version = 'version-3', vsn_hdr = V3Hdr, data = Data}
when State#state.v3 =:= true ->
@@ -143,7 +153,9 @@ process_packet(Packet, TDomain, TAddress, State, NoteStore, Log) ->
[V3Hdr#v3_hdr.msgID,
V3Hdr#v3_hdr.msgFlags,
V3Hdr#v3_hdr.msgSecurityModel]),
- v3_proc(NoteStore, Packet, TDomain, TAddress, V3Hdr, Data, Log);
+ v3_proc(NoteStore, Packet,
+ TDomain, TAddress,
+ LocalEngineID, V3Hdr, Data, Log);
{'EXIT', {bad_version, Vsn}} ->
?vtrace("exit: bad version: ~p",[Vsn]),
@@ -170,10 +182,11 @@ discarded_pdu(Variable) -> inc(Variable).
%%-----------------------------------------------------------------
%% Handles a Community based message (v1 or v2c).
%%-----------------------------------------------------------------
-v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain, {Ip, Udp},
+v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain,
+ {Ip, Udp}, LocalEngineID,
Data, HS, Log, Packet) ->
TAddress = tuple_to_list(Ip) ++ [Udp div 256, Udp rem 256],
- AgentMS = snmp_framework_mib:get_engine_max_message_size(),
+ AgentMS = get_engine_max_message_size(LocalEngineID),
MgrMS = snmp_community_mib:get_target_addr_ext_mms(?snmpUDPDomain,
TAddress),
PduMS = case MgrMS of
@@ -220,10 +233,10 @@ v1_v2c_proc(Vsn, NoteStore, Community, snmpUDPDomain, {Ip, Udp},
{discarded, trap_pdu}
end;
v1_v2c_proc(_Vsn, _NoteStore, _Community, snmpUDPDomain, TAddress,
- _Data, _HS, _Log, _Packet) ->
+ _LocalEngineID, _Data, _HS, _Log, _Packet) ->
{discarded, {badarg, TAddress}};
v1_v2c_proc(_Vsn, _NoteStore, _Community, TDomain, _TAddress,
- _Data, _HS, _Log, _Packet) ->
+ _LocalEngineID, _Data, _HS, _Log, _Packet) ->
{discarded, {badarg, TDomain}}.
sec_model('version-1') -> ?SEC_V1;
@@ -234,15 +247,19 @@ sec_model('version-2') -> ?SEC_V2C.
%% Handles a SNMPv3 Message, following the procedures in rfc2272,
%% section 4.2 and 7.2
%%-----------------------------------------------------------------
-v3_proc(NoteStore, Packet, _TDomain, _TAddress, V3Hdr, Data, Log) ->
- case (catch v3_proc(NoteStore, Packet, V3Hdr, Data, Log)) of
+v3_proc(NoteStore, Packet, _TDomain, _TAddress, LocalEngineID,
+ V3Hdr, Data, Log) ->
+ case (catch v3_proc(NoteStore, Packet, LocalEngineID, V3Hdr, Data, Log)) of
{'EXIT', Reason} ->
exit(Reason);
Result ->
Result
end.
-v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
+v3_proc(NoteStore, Packet, LocalEngineID, V3Hdr, Data, Log) ->
+ ?vtrace("v3_proc -> entry with"
+ "~n LocalEngineID: ~p",
+ [LocalEngineID]),
%% 7.2.3
#v3_hdr{msgID = MsgID,
msgMaxSize = MMS,
@@ -250,7 +267,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
msgSecurityModel = MsgSecurityModel,
msgSecurityParameters = SecParams,
hdr_size = HdrSize} = V3Hdr,
- ?vdebug("v3_proc -> version 3 message header:"
+ ?vdebug("v3_proc -> version 3 message header [7.2.3]:"
"~n msgID = ~p"
"~n msgMaxSize = ~p"
"~n msgFlags = ~p"
@@ -263,17 +280,19 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
SecLevel = check_sec_level(MsgFlags),
IsReportable = snmp_misc:is_reportable(MsgFlags),
%% 7.2.6
- ?vtrace("v3_proc -> "
+ ?vtrace("v3_proc -> [7.2.6]"
"~n SecModule = ~p"
"~n SecLevel = ~p"
"~n IsReportable = ~p",
- [SecModule,SecLevel,IsReportable]),
+ [SecModule, SecLevel, IsReportable]),
SecRes = (catch SecModule:process_incoming_msg(Packet, Data,
- SecParams, SecLevel)),
+ SecParams, SecLevel,
+ LocalEngineID)),
?vtrace("v3_proc -> message processing result: "
"~n SecRes: ~p", [SecRes]),
{SecEngineID, SecName, ScopedPDUBytes, SecData, DiscoOrPlain} =
- check_sec_module_result(SecRes, V3Hdr, Data, IsReportable, Log),
+ check_sec_module_result(SecRes, V3Hdr, Data,
+ LocalEngineID, IsReportable, Log),
?vtrace("v3_proc -> "
"~n DiscoOrPlain: ~w"
"~n SecEngineID: ~w"
@@ -311,7 +330,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
Log(PDU#pdu.type, Packet)
end,
%% Make sure a get_bulk doesn't get too big.
- AgentMS = snmp_framework_mib:get_engine_max_message_size(),
+ AgentMS = get_engine_max_message_size(LocalEngineID),
%% PduMMS is supposed to be the maximum total length of the response
%% PDU we can send. From the MMS, we need to subtract everything before
%% the PDU, i.e. Message and ScopedPDU.
@@ -415,8 +434,8 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
throw({discarded, received_v2_trap});
Type ->
%% 7.2.13
- SnmpEngineID = snmp_framework_mib:get_engine_id(),
- ?vtrace("v3_proc -> SnmpEngineID = ~w", [SnmpEngineID]),
+ SnmpEngineID = LocalEngineID,
+ ?vtrace("v3_proc -> 7.2.13", []),
case SecEngineID of
SnmpEngineID when (DiscoOrPlain =:= discovery) ->
%% This is a discovery step 2 message!
@@ -429,6 +448,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
ContextName,
SecData,
PDU,
+ LocalEngineID,
Log);
SnmpEngineID when (DiscoOrPlain =:= plain) ->
@@ -444,17 +464,18 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
%% 4.2.2.1.2
NIsReportable = snmp_misc:is_reportable_pdu(Type),
Val = inc(snmpUnknownPDUHandlers),
- ErrorInfo = {#varbind{oid = ?snmpUnknownPDUHandlers,
- variabletype = 'Counter32',
- value = Val},
- SecName,
- [{securityLevel, SecLevel},
- {contextEngineID, ContextEngineID},
- {contextName, ContextName}]},
+ ErrorInfo =
+ {#varbind{oid = ?snmpUnknownPDUHandlers,
+ variabletype = 'Counter32',
+ value = Val},
+ SecName,
+ [{securityLevel, SecLevel},
+ {contextEngineID, ContextEngineID},
+ {contextName, ContextName}]},
case generate_v3_report_msg(MsgID,
MsgSecurityModel,
- Data, ErrorInfo,
- Log) of
+ Data, LocalEngineID,
+ ErrorInfo, Log) of
{ok, Report} when NIsReportable =:= true ->
{discarded, snmpUnknownPDUHandlers, Report};
_ ->
@@ -473,6 +494,7 @@ v3_proc(NoteStore, Packet, V3Hdr, Data, Log) ->
ContextName,
SecData,
PDU,
+ LocalEngineID,
Log);
_ ->
@@ -501,7 +523,7 @@ check_sec_level(Unknown) ->
inc(snmpInvalidMsgs),
throw({discarded, snmpInvalidMsgs}).
-check_sec_module_result(Res, V3Hdr, Data, IsReportable, Log) ->
+check_sec_module_result(Res, V3Hdr, Data, LocalEngineID, IsReportable, Log) ->
case Res of
{ok, X} ->
X;
@@ -516,7 +538,7 @@ check_sec_module_result(Res, V3Hdr, Data, IsReportable, Log) ->
#v3_hdr{msgID = MsgID, msgSecurityModel = MsgSecModel} = V3Hdr,
Pdu = get_scoped_pdu(Data),
case generate_v3_report_msg(MsgID, MsgSecModel, Pdu,
- ErrorInfo, Log) of
+ LocalEngineID, ErrorInfo, Log) of
{ok, Report} ->
throw({discarded, {securityError, Reason}, Report});
{discarded, _SomeOtherReason} ->
@@ -545,8 +567,15 @@ get_scoped_pdu(D) ->
generate_response_msg(Vsn, RePdu, Type, ACMData, Log) ->
generate_response_msg(Vsn, RePdu, Type, ACMData, Log, 1).
+generate_response_msg(Vsn, RePdu, Type, ACMData, Log, N) when is_integer(N) ->
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
+ generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log, N);
+generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log) ->
+ generate_response_msg(Vsn, RePdu, Type, ACMData, LocalEngineID, Log, 1).
+
generate_response_msg(Vsn, RePdu, Type,
{community, _SecModel, Community, _IpUdp},
+ LocalEngineID,
Log, _) ->
case catch snmp_pdus:enc_pdu(RePdu) of
{'EXIT', Reason} ->
@@ -555,8 +584,9 @@ generate_response_msg(Vsn, RePdu, Type,
[RePdu, Community, Reason]),
{discarded, Reason};
PduBytes ->
- Message = #message{version = Vsn, vsn_hdr = Community,
- data = PduBytes},
+ Message = #message{version = Vsn,
+ vsn_hdr = Community,
+ data = PduBytes},
case catch list_to_binary(
snmp_pdus:enc_message_only(Message)) of
{'EXIT', Reason} ->
@@ -565,7 +595,7 @@ generate_response_msg(Vsn, RePdu, Type,
[RePdu, Community, Reason]),
{discarded, Reason};
Packet ->
- MMS = snmp_framework_mib:get_engine_max_message_size(),
+ MMS = get_engine_max_message_size(LocalEngineID),
case size(Packet) of
Len when Len =< MMS ->
Log(Type, Packet),
@@ -584,6 +614,7 @@ generate_response_msg(Vsn, RePdu, Type,
generate_response_msg(Vsn, RePdu, Type,
{v3, MsgID, MsgSecurityModel, SecName, SecLevel,
ContextEngineID, ContextName, SecData},
+ LocalEngineID,
Log, N) ->
%% rfc2272: 7.1 steps 6-8
ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID,
@@ -596,7 +627,7 @@ generate_response_msg(Vsn, RePdu, Type,
[RePdu, ContextName, Reason]),
{discarded, Reason};
ScopedPDUBytes ->
- AgentMS = snmp_framework_mib:get_engine_max_message_size(),
+ AgentMS = get_engine_max_message_size(LocalEngineID),
V3Hdr = #v3_hdr{msgID = MsgID,
msgMaxSize = AgentMS,
msgFlags = snmp_misc:mk_msg_flags(Type, SecLevel),
@@ -611,13 +642,14 @@ generate_response_msg(Vsn, RePdu, Type,
?SEC_USM ->
snmpa_usm
end,
- SecEngineID = snmp_framework_mib:get_engine_id(),
+ SecEngineID = LocalEngineID,
?vtrace("generate_response_msg -> SecEngineID: ~w", [SecEngineID]),
case (catch SecModule:generate_outgoing_msg(Message,
SecEngineID,
SecName,
SecData,
- SecLevel)) of
+ SecLevel,
+ LocalEngineID)) of
{'EXIT', Reason} ->
config_err("~p (message: ~p)", [Reason, Message]),
{discarded, Reason};
@@ -668,12 +700,14 @@ generate_response_msg(Vsn, RePdu, Type,
SecName, SecLevel,
ContextEngineID,
ContextName,
- SecData}, Log, N+1)
+ SecData},
+ LocalEngineID, Log, N+1)
end
end
end.
-generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) ->
+generate_v3_report_msg(MsgID, MsgSecurityModel, Data, LocalEngineID,
+ ErrorInfo, Log) ->
{Varbind, SecName, Opts} = ErrorInfo,
ReqId =
if
@@ -689,7 +723,7 @@ generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) ->
error_index = 0,
varbinds = [Varbind]},
SecLevel = snmp_misc:get_option(securityLevel, Opts, 0),
- SnmpEngineID = snmp_framework_mib:get_engine_id(),
+ SnmpEngineID = LocalEngineID,
ContextEngineID =
snmp_misc:get_option(contextEngineID, Opts, SnmpEngineID),
ContextName = snmp_misc:get_option(contextName, Opts, ""),
@@ -697,7 +731,8 @@ generate_v3_report_msg(MsgID, MsgSecurityModel, Data, ErrorInfo, Log) ->
generate_response_msg('version-3', Pdu, report,
{v3, MsgID, MsgSecurityModel, SecName, SecLevel,
- ContextEngineID, ContextName, SecData}, Log).
+ ContextEngineID, ContextName, SecData},
+ LocalEngineID, Log).
%% req_id(#scopedPdu{data = #pdu{request_id = ReqId}}) ->
%% ?vtrace("Report ReqId: ~p",[ReqId]),
@@ -719,7 +754,8 @@ generate_discovery1_report_msg(MsgID, MsgSecurityModel,
SecName, SecLevel,
ContextEngineID, ContextName,
{SecData, Oid, Value},
- #pdu{request_id = ReqId}, Log) ->
+ #pdu{request_id = ReqId},
+ LocalEngineID, Log) ->
?vtrace("generate_discovery1_report_msg -> entry with"
"~n ReqId: ~p"
"~n Value: ~p", [ReqId, Value]),
@@ -734,7 +770,8 @@ generate_discovery1_report_msg(MsgID, MsgSecurityModel,
varbinds = [Varbind]},
case generate_response_msg('version-3', PduOut, report,
{v3, MsgID, MsgSecurityModel, SecName, SecLevel,
- ContextEngineID, ContextName, SecData}, Log) of
+ ContextEngineID, ContextName, SecData},
+ LocalEngineID, Log) of
{ok, Packet} ->
{discovery, Packet};
Error ->
@@ -745,7 +782,8 @@ generate_discovery1_report_msg(MsgID, MsgSecurityModel,
generate_discovery2_report_msg(MsgID, MsgSecurityModel,
SecName, SecLevel,
ContextEngineID, ContextName,
- SecData, #pdu{request_id = ReqId}, Log) ->
+ SecData, #pdu{request_id = ReqId},
+ LocalEngineID, Log) ->
?vtrace("generate_discovery2_report_msg -> entry with"
"~n ReqId: ~p", [ReqId]),
SecModule = get_security_module(MsgSecurityModel),
@@ -757,7 +795,8 @@ generate_discovery2_report_msg(MsgID, MsgSecurityModel,
varbinds = [Vb]},
case generate_response_msg('version-3', PduOut, report,
{v3, MsgID, MsgSecurityModel, SecName, SecLevel,
- ContextEngineID, ContextName, SecData}, Log) of
+ ContextEngineID, ContextName, SecData},
+ LocalEngineID, Log) of
{ok, Packet} ->
{discovery, Packet};
Error ->
@@ -816,7 +855,11 @@ set_vb_null([]) ->
%% Executed when a message that isn't a response is generated, i.e.
%% a trap or an inform.
%%-----------------------------------------------------------------
-generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) ->
+generate_msg(Vsn, NoteStore, Pdu, ACMData, To) ->
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
+ generate_msg(Vsn, NoteStore, Pdu, ACMData, LocalEngineID, To).
+
+generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, LocalEngineID, To) ->
Message = #message{version = Vsn, vsn_hdr = Community, data = Pdu},
case catch list_to_binary(snmp_pdus:enc_message(Message)) of
{'EXIT', Reason} ->
@@ -825,7 +868,7 @@ generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) ->
[Pdu, Community, Reason]),
{discarded, Reason};
Packet ->
- AgentMax = snmp_framework_mib:get_engine_max_message_size(),
+ AgentMax = get_engine_max_message_size(LocalEngineID),
case size(Packet) of
Len when Len =< AgentMax ->
{ok, mk_v1_v2_packet_list(To, Packet, Len, Pdu)};
@@ -838,9 +881,9 @@ generate_msg(Vsn, _NoteStore, Pdu, {community, Community}, To) ->
end
end;
generate_msg('version-3', NoteStore, Pdu,
- {v3, ContextEngineID, ContextName}, To) ->
- %% rfc2272: 7.1.6
- ScopedPDU = #scopedPdu{contextEngineID = ContextEngineID,
+ {v3, ContextEngineID, ContextName}, LocalEngineID, To) ->
+ %% rfc2272: 7.1 step 6
+ ScopedPDU = #scopedPdu{contextEngineID = LocalEngineID,
contextName = ContextName,
data = Pdu},
case (catch snmp_pdus:enc_scoped_pdu(ScopedPDU)) of
@@ -851,7 +894,8 @@ generate_msg('version-3', NoteStore, Pdu,
{discarded, Reason};
ScopedPDUBytes ->
{ok, mk_v3_packet_list(NoteStore, To, ScopedPDUBytes, Pdu,
- ContextEngineID, ContextName)}
+ ContextEngineID, ContextName,
+ LocalEngineID)}
end.
@@ -1094,17 +1138,21 @@ mk_msg_flags(PduType, SecLevel) ->
mk_v3_packet_entry(NoteStore, Domain, Addr,
{SecModel, SecName, SecLevel, TargetAddrName},
- ScopedPDUBytes, Pdu, ContextEngineID, ContextName) ->
- %% 7.1.7
- ?vtrace("mk_v3_packet_entry -> entry - 7.1.7", []),
- MsgID = generate_msg_id(),
- PduType = Pdu#pdu.type,
- MsgFlags = mk_msg_flags(PduType, SecLevel),
+ ScopedPDUBytes, Pdu, _ContextEngineID, ContextName,
+ LocalEngineID) ->
+ %% rfc2272 7.1 step 77
+ ?vtrace("mk_v3_packet_entry -> entry - RFC2272-7.1:7", []),
+ MsgVersion = 'version-3', % 7.1:7a
+ MsgID = generate_msg_id(), % 7.1:7b
+ MaxMsgSz = get_max_message_size(), % 7.1:7c
+ PduType = Pdu#pdu.type,
+ MsgFlags = mk_msg_flags(PduType, SecLevel), % 7.1:7d
+ MsgSecModel = SecModel, % 7.1:7e
V3Hdr = #v3_hdr{msgID = MsgID,
- msgMaxSize = get_max_message_size(),
+ msgMaxSize = MaxMsgSz,
msgFlags = MsgFlags,
- msgSecurityModel = SecModel},
- Message = #message{version = 'version-3',
+ msgSecurityModel = MsgSecModel},
+ Message = #message{version = MsgVersion,
vsn_hdr = V3Hdr,
data = ScopedPDUBytes},
SecModule =
@@ -1113,12 +1161,21 @@ mk_v3_packet_entry(NoteStore, Domain, Addr,
snmpa_usm
end,
+ %%
+ %% 7.1:8 - If the PDU is from the Response Class or the Internal Class
+ %% securityEngineID = snmpEngineID (local/source)
+ %% 7.1:9 - If the PDU is from the Unconfirmed Class
+ %% securityEngineID = snmpEngineID (local/source)
+ %% else
+ %% securityEngineID = targetEngineID (remote/destination)
+ %%
+
%% 7.1.9a
?vtrace("mk_v3_packet_entry -> sec engine id - 7.1.9a", []),
SecEngineID =
case PduType of
'snmpv2-trap' ->
- snmp_framework_mib:get_engine_id();
+ LocalEngineID;
_ ->
%% This is the implementation dependent target engine id
%% procedure.
@@ -1141,8 +1198,9 @@ mk_v3_packet_entry(NoteStore, Domain, Addr,
?vdebug("mk_v3_packet_entry -> secEngineID: ~p", [SecEngineID]),
%% 7.1.9b
- case catch SecModule:generate_outgoing_msg(Message, SecEngineID,
- SecName, [], SecLevel) of
+ case (catch SecModule:generate_outgoing_msg(Message, SecEngineID,
+ SecName, [], SecLevel,
+ LocalEngineID)) of
{'EXIT', Reason} ->
config_err("~p (message: ~p)", [Reason, Message]),
skip;
@@ -1169,7 +1227,7 @@ mk_v3_packet_entry(NoteStore, Domain, Addr,
sec_model = SecModel,
sec_name = SecName,
sec_level = SecLevel,
- ctx_engine_id = ContextEngineID,
+ ctx_engine_id = LocalEngineID,
ctx_name = ContextName,
disco = false,
req_id = Pdu#pdu.request_id},
@@ -1180,15 +1238,16 @@ mk_v3_packet_entry(NoteStore, Domain, Addr,
mk_v3_packet_list(NoteStore, To,
- ScopedPDUBytes, Pdu, ContextEngineID, ContextName) ->
+ ScopedPDUBytes, Pdu, ContextEngineID, ContextName,
+ LocalEngineID) ->
mk_v3_packet_list(NoteStore, To,
ScopedPDUBytes, Pdu,
- ContextEngineID, ContextName, []).
+ ContextEngineID, ContextName, LocalEngineID, []).
mk_v3_packet_list(_, [],
_ScopedPDUBytes, _Pdu,
_ContextEngineID, _ContextName,
- Acc) ->
+ _LocalEngineID, Acc) ->
lists:reverse(Acc);
%% This clause is for backward compatibillity reasons
@@ -1196,20 +1255,21 @@ mk_v3_packet_list(_, [],
mk_v3_packet_list(NoteStore,
[{{?snmpUDPDomain, [A,B,C,D,U1,U2]}, SecData} | T],
ScopedPDUBytes, Pdu, ContextEngineID, ContextName,
- Acc) ->
+ LocalEngineID, Acc) ->
case mk_v3_packet_entry(NoteStore,
snmpUDPDomain, {{A,B,C,D}, U1 bsl 8 + U2}, SecData,
ScopedPDUBytes, Pdu,
- ContextEngineID, ContextName) of
+ ContextEngineID, ContextName, LocalEngineID) of
skip ->
mk_v3_packet_list(NoteStore, T,
ScopedPDUBytes, Pdu,
- ContextEngineID, ContextName,
+ ContextEngineID, ContextName, LocalEngineID,
Acc);
{ok, Entry} ->
mk_v3_packet_list(NoteStore, T,
ScopedPDUBytes, Pdu,
- ContextEngineID, ContextName, [Entry | Acc])
+ ContextEngineID, ContextName, LocalEngineID,
+ [Entry | Acc])
end;
%% This is the new clause
@@ -1218,11 +1278,11 @@ mk_v3_packet_list(NoteStore,
mk_v3_packet_list(NoteStore,
[{{Domain, Addr}, SecData} | T],
ScopedPDUBytes, Pdu, ContextEngineID, ContextName,
- Acc) ->
+ LocalEngineID, Acc) ->
case mk_v3_packet_entry(NoteStore,
Domain, Addr, SecData,
ScopedPDUBytes, Pdu,
- ContextEngineID, ContextName) of
+ ContextEngineID, ContextName, LocalEngineID) of
skip ->
mk_v3_packet_list(NoteStore, T,
ScopedPDUBytes, Pdu,
@@ -1230,7 +1290,8 @@ mk_v3_packet_list(NoteStore,
{ok, Entry} ->
mk_v3_packet_list(NoteStore, T,
ScopedPDUBytes, Pdu,
- ContextEngineID, ContextName, [Entry | Acc])
+ ContextEngineID, ContextName,
+ LocalEngineID, [Entry | Acc])
end.
@@ -1253,6 +1314,9 @@ gen(Id) ->
get_target_engine_id(TargetAddrName) ->
snmp_target_mib:get_target_engine_id(TargetAddrName).
+get_engine_max_message_size(_LocalEngineID) ->
+ snmp_framework_mib:get_engine_max_message_size().
+
sec_module(?SEC_USM) ->
snmpa_usm.
diff --git a/lib/snmp/src/agent/snmpa_trap.erl b/lib/snmp/src/agent/snmpa_trap.erl
index b1096b1135..450cb2e9f4 100644
--- a/lib/snmp/src/agent/snmpa_trap.erl
+++ b/lib/snmp/src/agent/snmpa_trap.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(snmpa_trap).
@@ -23,14 +23,18 @@
%%%-----------------------------------------------------------------
%% External exports
-export([construct_trap/2,
- try_initialise_vars/2, send_trap/6]).
+ try_initialise_vars/2,
+ send_trap/6, send_trap/7]).
-export([send_discovery/5]).
%% Internal exports
--export([init_v2_inform/9, init_v3_inform/9, send_inform/6]).
+-export([init_v2_inform/9,
+ init_v3_inform/9, init_v3_inform/10,
+ send_inform/6]).
-export([init_discovery_inform/12, send_discovery_inform/5]).
-include("snmp_types.hrl").
+-include("snmpa_internal.hrl").
-include("SNMPv2-MIB.hrl").
-include("SNMPv2-TM.hrl").
-include("SNMPv2-TC.hrl").
@@ -331,13 +335,20 @@ make_varbind_list(Varbinds) ->
%% SnmpTargetAddrTable (using the Tag).
%%-----------------------------------------------------------------
send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) ->
- (catch do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf)).
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
+ send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID, NetIf).
+
+send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, LocalEngineID, NetIf) ->
+ (catch do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID, NetIf)).
-do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs, NetIf) ->
+do_send_trap(TrapRec, NotifyName, ContextName, Recv, Vbs,
+ LocalEngineID, NetIf) ->
VarbindList = make_varbind_list(Vbs),
Dests = find_dests(NotifyName),
send_trap_pdus(Dests, ContextName, {TrapRec, VarbindList}, [], [], [],
- Recv, NetIf).
+ Recv, LocalEngineID, NetIf).
send_discovery(TargetName, Record, ContextName, Vbs, NetIf) ->
case find_dest(TargetName) of
@@ -619,7 +630,9 @@ send_discovery_inform(Parent, Timeout, Retry, Msg, NetIf) ->
%%-----------------------------------------------------------------
send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel},
Type} | T],
- ContextName,{TrapRec, Vbs}, V1Res, V2Res, V3Res, Recv, NetIf) ->
+ ContextName,
+ {TrapRec, Vbs}, V1Res, V2Res, V3Res, Recv,
+ LocalEngineID, NetIf) ->
?vdebug("send trap pdus: "
"~n Destination address: ~p"
"~n Target name: ~p"
@@ -634,7 +647,7 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel},
case check_all_varbinds(TrapRec, Vbs, MibView) of
true when MpModel =:= ?MP_V1 ->
?vtrace("send_trap_pdus -> v1 mp model",[]),
- ContextEngineId = snmp_framework_mib:get_engine_id(),
+ ContextEngineId = LocalEngineID,
case snmp_community_mib:vacm2community({SecName,
ContextEngineId,
ContextName},
@@ -644,16 +657,18 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel},
[element(2, DestAddr)]),
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
[{DestAddr, Community} | V1Res],
- V2Res, V3Res, Recv, NetIf);
+ V2Res, V3Res, Recv,
+ LocalEngineID, NetIf);
undefined ->
?vdebug("No community found for v1 dest: ~p",
[element(2, DestAddr)]),
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
- V1Res, V2Res, V3Res, Recv, NetIf)
+ V1Res, V2Res, V3Res, Recv,
+ LocalEngineID, NetIf)
end;
true when MpModel =:= ?MP_V2C ->
?vtrace("send_trap_pdus -> v2c mp model",[]),
- ContextEngineId = snmp_framework_mib:get_engine_id(),
+ ContextEngineId = LocalEngineID,
case snmp_community_mib:vacm2community({SecName,
ContextEngineId,
ContextName},
@@ -664,12 +679,13 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel},
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
V1Res,
[{DestAddr, Community, Type}|V2Res],
- V3Res, Recv, NetIf);
+ V3Res, Recv, LocalEngineID, NetIf);
undefined ->
?vdebug("No community found for v2c dest: ~p",
[element(2, DestAddr)]),
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
- V1Res, V2Res, V3Res, Recv, NetIf)
+ V1Res, V2Res, V3Res, Recv,
+ LocalEngineID, NetIf)
end;
true when MpModel =:= ?MP_V3 ->
?vtrace("send_trap_pdus -> v3 mp model",[]),
@@ -678,18 +694,20 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel},
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
V1Res, V2Res,
[{DestAddr, MsgData, Type} | V3Res],
- Recv, NetIf);
+ Recv, LocalEngineID, NetIf);
true ->
?vlog("bad MpModel ~p for dest ~p",
[MpModel, element(2, DestAddr)]),
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
- V1Res, V2Res, V3Res, Recv, NetIf);
+ V1Res, V2Res, V3Res, Recv,
+ LocalEngineID, NetIf);
_ ->
?vlog("no access for dest: "
"~n ~p in target ~p",
[element(2, DestAddr), TargetName]),
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
- V1Res, V2Res, V3Res, Recv, NetIf)
+ V1Res, V2Res, V3Res, Recv,
+ LocalEngineID, NetIf)
end;
{discarded, Reason} ->
?vlog("mib view error ~p for"
@@ -697,10 +715,10 @@ send_trap_pdus([{DestAddr, TargetName, {MpModel, SecModel, SecName, SecLevel},
"~n SecName: ~w",
[Reason, element(2, DestAddr), SecName]),
send_trap_pdus(T, ContextName, {TrapRec, Vbs},
- V1Res, V2Res, V3Res, Recv, NetIf)
+ V1Res, V2Res, V3Res, Recv, LocalEngineID, NetIf)
end;
send_trap_pdus([], ContextName, {TrapRec, Vbs}, V1Res, V2Res, V3Res,
- Recv, NetIf) ->
+ Recv, LocalEngineID, NetIf) ->
SysUpTime = snmp_standard_mib:sys_up_time(),
?vdebug("send trap pdus with sysUpTime ~p", [SysUpTime]),
InformRecvs = get_inform_recvs(V2Res ++ V3Res),
@@ -708,7 +726,8 @@ send_trap_pdus([], ContextName, {TrapRec, Vbs}, V1Res, V2Res, V3Res,
deliver_recv(Recv, snmp_targets, InformTargets),
send_v1_trap(TrapRec, V1Res, Vbs, NetIf, SysUpTime),
send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime),
- send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName).
+ send_v3_trap(TrapRec, V3Res, Vbs, Recv, LocalEngineID, NetIf,
+ SysUpTime, ContextName).
send_v1_trap(_TrapRec, [], _Vbs, _NetIf, _SysUpTime) ->
ok;
@@ -762,21 +781,25 @@ send_v2_trap(TrapRec, V2Res, Vbs, Recv, NetIf, SysUpTime) ->
do_send_v2_trap(TrapRecvs, IVbs, NetIf),
do_send_v2_inform(InformRecvs, IVbs, Recv, NetIf).
-send_v3_trap(_TrapRec, [], _Vbs, _Recv, _NetIf, _SysUpTime, _ContextName) ->
+send_v3_trap(_TrapRec, [], _Vbs, _Recv, _LocalEngineID,
+ _NetIf, _SysUpTime, _ContextName) ->
ok;
-send_v3_trap(TrapRec, V3Res, Vbs, Recv, NetIf, SysUpTime, ContextName) ->
+send_v3_trap(TrapRec, V3Res, Vbs, Recv, LocalEngineID,
+ NetIf, SysUpTime, ContextName) ->
?vdebug("prepare to send v3 trap",[]),
{_Oid, IVbs} = mk_v2_trap(TrapRec, Vbs, SysUpTime), % v2 refers to SMIv2;
- TrapRecvs = get_trap_recvs(V3Res), % same SMI for v3
+ TrapRecvs = get_trap_recvs(V3Res), % same SMI for v3
InformRecvs = get_inform_recvs(V3Res),
do_send_v3_trap(TrapRecvs, ContextName, IVbs, NetIf),
- do_send_v3_inform(InformRecvs, ContextName, IVbs, Recv, NetIf).
+ do_send_v3_inform(InformRecvs, ContextName, IVbs, Recv,
+ LocalEngineID, NetIf).
mk_v2_trap(#notification{oid = Oid}, Vbs, SysUpTime) ->
?vtrace("make v2 notification '~p'",[Oid]),
mk_v2_notif(Oid, Vbs, SysUpTime);
-mk_v2_trap(#trap{enterpriseoid = Enter, specificcode = Spec}, Vbs, SysUpTime) ->
+mk_v2_trap(#trap{enterpriseoid = Enter, specificcode = Spec},
+ Vbs, SysUpTime) ->
%% Use alg. in rfc1908 to map a v1 trap to a v2 trap
?vtrace("make v2 trap for '~p' with ~p",[Enter,Spec]),
{Oid,Enterp} =
@@ -845,16 +868,16 @@ do_send_v3_trap(Recvs, ContextName, Vbs, NetIf) ->
end, Recvs),
ok.
-do_send_v3_inform([], _ContextName, _Vbs, _Recv, _NetIf) ->
+do_send_v3_inform([], _ContextName, _Vbs, _Recv, _LocalEngineID, _NetIf) ->
ok;
-do_send_v3_inform(Recvs, ContextName, Vbs, Recv, NetIf) ->
+do_send_v3_inform(Recvs, ContextName, Vbs, Recv, LocalEngineID, NetIf) ->
lists:foreach(
fun({Addr, MsgData, Timeout, Retry}) ->
?vtrace("~n start inform sender to send v3 inform to ~p",
[Addr]),
proc_lib:spawn_link(?MODULE, init_v3_inform,
[{Addr, MsgData}, Timeout, Retry, Vbs,
- Recv, NetIf, ContextName,
+ Recv, LocalEngineID, NetIf, ContextName,
get(verbosity), get(sname)])
end,
Recvs).
@@ -874,7 +897,13 @@ init_v2_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, Community,V,S) ->
%% New process
-init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName,V,S) ->
+init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName, V, S) ->
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
+ init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, LocalEngineID,
+ NetIf, ContextName, V, S).
+
+init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, LocalEngineID,
+ NetIf, ContextName, V, S) ->
%% Make a new Inform for each recipient; they need unique
%% request-ids!
put(verbosity,V),
@@ -882,7 +911,7 @@ init_v3_inform(Addr, Timeout, Retry, Vbs, Recv, NetIf, ContextName,V,S) ->
?vdebug("~n starting with timeout = ~p and retry = ~p",
[Timeout,Retry]),
InformPdu = make_v2_notif_pdu(Vbs, 'inform-request'), % Yes, v2
- ContextEngineId = snmp_framework_mib:get_engine_id(),
+ ContextEngineId = LocalEngineID,
Msg = {send_pdu_req, 'version-3', InformPdu,
{v3, ContextEngineId, ContextName}, [Addr], self()},
?MODULE:send_inform(Addr, Timeout*10, Retry, Msg, Recv, NetIf).
diff --git a/lib/snmp/src/agent/snmpa_usm.erl b/lib/snmp/src/agent/snmpa_usm.erl
index 12a6b996ff..ae584bb3c1 100644
--- a/lib/snmp/src/agent/snmpa_usm.erl
+++ b/lib/snmp/src/agent/snmpa_usm.erl
@@ -19,8 +19,8 @@
-module(snmpa_usm).
-export([
- process_incoming_msg/4,
- generate_outgoing_msg/5,
+ process_incoming_msg/4, process_incoming_msg/5,
+ generate_outgoing_msg/5, generate_outgoing_msg/6,
generate_discovery_msg/4, generate_discovery_msg/5,
current_statsNotInTimeWindows_vb/0
]).
@@ -33,6 +33,7 @@
-define(VMODULE,"A-USM").
-include("snmp_verbosity.hrl").
+-include("snmpa_internal.hrl").
%%-----------------------------------------------------------------
@@ -58,7 +59,11 @@
%%-----------------------------------------------------------------
process_incoming_msg(Packet, Data, SecParams, SecLevel) ->
- TermDiscoEnabled = is_terminating_discovery_enabled(),
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
+ process_incoming_msg(Packet, Data, SecParams, SecLevel, LocalEngineID).
+
+process_incoming_msg(Packet, Data, SecParams, SecLevel, LocalEngineID) ->
+ TermDiscoEnabled = is_terminating_discovery_enabled(),
TermTriggerUsername = terminating_trigger_username(),
%% 3.2.1
?vtrace("process_incoming_msg -> check security parms: 3.2.1",[]),
@@ -124,7 +129,7 @@ process_incoming_msg(Packet, Data, SecParams, SecLevel) ->
"~n ~p",[UsmUser]),
DiscoOrPlain = authenticate_incoming(Packet,
UsmSecParams, UsmUser,
- SecLevel),
+ SecLevel, LocalEngineID),
%% 3.2.8
?vtrace("process_incoming_msg -> "
"decrypt scoped data: 3.2.8",[]),
@@ -166,7 +171,8 @@ process_discovery_msg(MsgAuthEngineID, Data, SecLevel) ->
end.
-authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) ->
+authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel,
+ LocalEngineID) ->
%% 3.2.6
?vtrace("authenticate_incoming -> 3.2.6", []),
AuthProtocol = element(?usmUserAuthProtocol, UsmUser),
@@ -190,7 +196,8 @@ authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) ->
SecName,
MsgAuthEngineID,
MsgAuthEngineBoots,
- MsgAuthEngineTime) of
+ MsgAuthEngineTime,
+ LocalEngineID) of
discovery ->
discovery;
true ->
@@ -205,15 +212,15 @@ authenticate_incoming(Packet, UsmSecParams, UsmUser, SecLevel) ->
plain
end.
-authoritative(SecName, MsgAuthEngineBoots, MsgAuthEngineTime) ->
+authoritative(SecName, MsgAuthEngineBoots, MsgAuthEngineTime, LocalEngineID) ->
?vtrace("authoritative -> entry with"
"~n SecName: ~p"
"~n MsgAuthEngineBoots: ~p"
"~n MsgAuthEngineTime: ~p",
[SecName, MsgAuthEngineBoots, MsgAuthEngineTime]),
- SnmpEngineBoots = snmp_framework_mib:get_engine_boots(),
+ SnmpEngineBoots = get_local_engine_boots(LocalEngineID),
?vtrace("authoritative -> SnmpEngineBoots: ~p", [SnmpEngineBoots]),
- SnmpEngineTime = snmp_framework_mib:get_engine_time(),
+ SnmpEngineTime = get_local_engine_time(LocalEngineID),
?vtrace("authoritative -> SnmpEngineTime: ~p", [SnmpEngineTime]),
InTimeWindow =
if
@@ -320,11 +327,12 @@ non_authoritative(SecName,
end.
-is_auth(?usmNoAuthProtocol, _, _, _, SecName, _, _, _) -> % 3.2.5
+is_auth(?usmNoAuthProtocol, _, _, _, SecName, _, _, _, _) -> % 3.2.5
error(usmStatsUnsupportedSecLevels,
?usmStatsUnsupportedSecLevels_instance, SecName); % OTP-5464
is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName,
- MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime) ->
+ MsgAuthEngineID, MsgAuthEngineBoots, MsgAuthEngineTime,
+ LocalEngineID) ->
TermDiscoEnabled = is_terminating_discovery_enabled(),
TermDiscoStage2 = terminating_discovery_stage2(),
IsAuth = auth_in(AuthProtocol, AuthKey, AuthParams, Packet),
@@ -334,7 +342,7 @@ is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName,
%% 3.2.7
?vtrace("is_auth -> "
"retrieve EngineBoots and EngineTime: 3.2.7",[]),
- SnmpEngineID = snmp_framework_mib:get_engine_id(),
+ SnmpEngineID = LocalEngineID,
?vtrace("is_auth -> SnmpEngineID: ~p", [SnmpEngineID]),
case MsgAuthEngineID of
SnmpEngineID when ((MsgAuthEngineBoots =:= 0) andalso
@@ -351,12 +359,14 @@ is_auth(AuthProtocol, AuthKey, AuthParams, Packet, SecName,
%% This will *always* result in the manager *not*
%% beeing in timewindow
authoritative(SecName,
- MsgAuthEngineBoots, MsgAuthEngineTime);
+ MsgAuthEngineBoots, MsgAuthEngineTime,
+ LocalEngineID);
SnmpEngineID -> %% 3.2.7a
?vtrace("is_auth -> we are authoritative: 3.2.7a", []),
authoritative(SecName,
- MsgAuthEngineBoots, MsgAuthEngineTime);
+ MsgAuthEngineBoots, MsgAuthEngineTime,
+ LocalEngineID);
_ -> %% 3.2.7b - we're non-authoritative
?vtrace("is_auth -> we are non-authoritative: 3.2.7b",[]),
@@ -418,12 +428,19 @@ try_decrypt(?usmAesCfb128Protocol,
generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
+ LocalEngineID = ?DEFAULT_LOCAL_ENGINE_ID,
+ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel,
+ LocalEngineID).
+
+generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel,
+ LocalEngineID) ->
%% 3.1.1
?vtrace("generate_outgoing_msg -> [3.1.1] entry with"
- "~n SecEngineID: ~p"
- "~n SecName: ~p"
- "~n SecLevel: ~w",
- [SecEngineID, SecName, SecLevel]),
+ "~n SecEngineID: ~p"
+ "~n SecName: ~p"
+ "~n SecLevel: ~w"
+ "~n LocalEngineID: ~p",
+ [SecEngineID, SecName, SecLevel, LocalEngineID]),
{UserName, AuthProtocol, PrivProtocol, AuthKey, PrivKey} =
case SecData of
[] -> % 3.1.1b
@@ -439,7 +456,7 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
element(?usmUserPrivKey, User)};
{_, Name,_,_,_,_,_,_,_,_,_,_,_, RowStatus,_,_} ->
?vdebug("generate_outgoing_msg -> "
- "found user ~p with wrong row status: ~p",
+ "found not active user ~p: ~p",
[Name, RowStatus]),
error(unknownSecurityName);
_ ->
@@ -460,7 +477,7 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
ScopedPduBytes = Message#message.data,
{ScopedPduData, MsgPrivParams} =
encrypt(ScopedPduBytes, PrivProtocol, PrivKey, SecLevel),
- SnmpEngineID = snmp_framework_mib:get_engine_id(),
+ SnmpEngineID = LocalEngineID,
?vtrace("generate_outgoing_msg -> SnmpEngineID: ~p [3.1.6]",
[SnmpEngineID]),
%% 3.1.6
@@ -474,8 +491,8 @@ generate_outgoing_msg(Message, SecEngineID, SecName, SecData, SecLevel) ->
{get_engine_boots(SecEngineID),
get_engine_time(SecEngineID)};
_ ->
- {snmp_framework_mib:get_engine_boots(),
- snmp_framework_mib:get_engine_time()}
+ {get_local_engine_boots(SnmpEngineID),
+ get_local_engine_time(SnmpEngineID)}
end,
%% 3.1.5 - 3.1.7
?vtrace("generate_outgoing_msg -> [3.1.5 - 3.1.7]",[]),
@@ -560,11 +577,15 @@ encrypt(Data, PrivProtocol, PrivKey, SecLevel) ->
?vtrace("encrypt -> 3.1.4a",[]),
case (catch try_encrypt(PrivProtocol, PrivKey, Data)) of
{ok, ScopedPduData, MsgPrivParams} ->
- ?vtrace("encrypt -> encode tag",[]),
+ ?vtrace("encrypt -> encrypted - now encode tag",[]),
{snmp_pdus:enc_oct_str_tag(ScopedPduData), MsgPrivParams};
{error, Reason} ->
+ ?vtrace("encrypt -> error: "
+ "~n Reason: ~p", [Reason]),
error(Reason);
- _Error ->
+ Error ->
+ ?vtrace("encrypt -> other: "
+ "~n Error: ~p", [Error]),
error(encryptionError)
end
end.
@@ -677,6 +698,19 @@ current_statsNotInTimeWindows_vb() ->
value = get_counter(usmStatsNotInTimeWindows)}.
+
+%%-----------------------------------------------------------------
+%% Future profing...
+%%-----------------------------------------------------------------
+
+get_local_engine_boots(_LocalEngineID) ->
+ snmp_framework_mib:get_engine_boots().
+
+get_local_engine_time(_LocalEngineID) ->
+ snmp_framework_mib:get_engine_time().
+
+
+
%%-----------------------------------------------------------------
%% We cache the local values of all non-auth engines we know.
%% Keep the values in the snmp_agent_table.
diff --git a/lib/snmp/src/app/snmp.appup.src b/lib/snmp/src/app/snmp.appup.src
index 204de71c2e..9ad16ffad2 100644
--- a/lib/snmp/src/app/snmp.appup.src
+++ b/lib/snmp/src/app/snmp.appup.src
@@ -22,37 +22,91 @@
%% ----- U p g r a d e -------------------------------------------------------
[
+ {"4.16.2",
+ [
+ {load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []}
+ ]
+ },
+ {"4.16.1",
+ [
+ {load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
+ {update, snmpm_server, soft, soft_purge, soft_purge, []}
+ ]
+ },
{"4.16",
[
+ {load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
- {update, snmpm_net_if, soft, soft_purge, soft_purge, []}
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
+ {update, snmpm_net_if, soft, soft_purge, soft_purge, []},
+ {update, snmpm_server, soft, soft_purge, soft_purge, []}
]
},
{"4.15",
[
- {load_module, snmpa, soft_purge, soft_purge, [snmp_log]},
{load_module, snmp_config, soft_purge, soft_purge, []},
{load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
- {update, snmpm_net_if, {advanced, upgrade_from_pre_4_16},
- soft_purge, soft_purge, [snmpm_config, snmp_log]},
- {update, snmpa_net_if, {advanced, upgrade_from_pre_4_16},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
+ {update, snmpa_net_if, {advanced, upgrade_from_pre_4_16},
soft_purge, soft_purge, [snmpa_agent, snmp_log]},
- {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
- {update, snmpm_config, soft, soft_purge, soft_purge, []}
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
+ {update, snmpm_net_if, {advanced, upgrade_from_pre_4_16},
+ soft_purge, soft_purge, [snmpm_config, snmp_log]},
+ {update, snmpm_config, soft, soft_purge, soft_purge, []},
+ {update, snmpm_server, soft, soft_purge, soft_purge, []}
]
},
{"4.14",
[
- {load_module, snmpa, soft_purge, soft_purge, [snmp_log]},
{load_module, snmp_config, soft_purge, soft_purge, []},
{load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
{update, snmpa_net_if, {advanced, upgrade_from_pre_4_16},
- soft_purge, soft_purge, [snmpa_agent, snmp_log]},
- {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+ soft_purge, soft_purge, [snmp_log, snmpa_agent]},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
{load_module, snmpm_user, soft_purge, soft_purge, []},
{load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]},
{update, snmpm_net_if, {advanced, upgrade_from_pre_4_16},
@@ -64,15 +118,22 @@
},
{"4.13.5",
[
- {load_module, snmpa_mib_data, soft_purge, soft_purge, []},
- {load_module, snmpa, soft_purge, soft_purge, [snmp_log]},
{load_module, snmp_config, soft_purge, soft_purge, []},
{load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
+ {load_module, snmpa_mib_data, soft_purge, soft_purge, []},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
{update, snmpa_net_if, {advanced, upgrade_from_pre_4_16},
soft_purge, soft_purge, [snmpa_agent, snmp_log]},
- {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, [snmpa_mib_data]},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
{load_module, snmpm_user, soft_purge, soft_purge, []},
{load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]},
{update, snmpm_net_if, {advanced, upgrade_from_pre_4_14},
@@ -88,38 +149,92 @@
%% ------D o w n g r a d e ---------------------------------------------------
[
+ {"4.16.2",
+ [
+ {load_module, snmp_log, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []}
+ ]
+ },
+ {"4.16.1",
+ [
+ {load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
+ {update, snmpm_server, soft, soft_purge, soft_purge, []}
+ ]
+ },
{"4.16",
[
+ {load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
- {update, snmpm_net_if, soft, soft_purge, soft_purge, []}
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
+ {update, snmpm_net_if, soft, soft_purge, soft_purge, []},
+ {update, snmpm_server, soft, soft_purge, soft_purge, []}
]
},
{"4.15",
[
- {load_module, snmpa, soft_purge, soft_purge, [snmp_log]},
{load_module, snmp_config, soft_purge, soft_purge, []},
{load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
- {update, snmpa_net_if, {advanced, downgrade_to_pre_4_16},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
+ {update, snmpa_net_if, {advanced, downgrade_to_pre_4_16},
soft_purge, soft_purge, [snmpa_agent, snmp_log]},
- {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
- {load_module, snmpa_general_db, soft_purge, soft_purge, []},
- {update, snmpm_net_if, {advanced, downgrade_to_pre_4_16},
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
+ {update, snmpm_net_if, {advanced, downgrade_to_pre_4_16},
soft_purge, soft_purge, [snmpm_config, snmp_log]},
- {update, snmpm_config, soft, soft_purge, soft_purge, []}
+ {update, snmpm_config, soft, soft_purge, soft_purge, []},
+ {update, snmpm_server, soft, soft_purge, soft_purge, []}
]
},
{"4.14",
[
- {load_module, snmpa, soft_purge, soft_purge, [snmp_log]},
{load_module, snmp_config, soft_purge, soft_purge, []},
{load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmpa_agent, snmp_log]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
- {update, snmpa_net_if, {advanced, downgrade_to_pre_4_16},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
+ {update, snmpa_net_if, {advanced, downgrade_to_pre_4_16},
soft_purge, soft_purge, [snmpa_agent, snmp_log]},
- {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, []},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
{load_module, snmpm_user, soft_purge, soft_purge, []},
{load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]},
{update, snmpm_net_if, {advanced, downgrade_to_pre_4_16},
@@ -131,15 +246,22 @@
},
{"4.13.5",
[
- {load_module, snmpa_mib_data, soft_purge, soft_purge, []},
{load_module, snmp_config, soft_purge, soft_purge, []},
- {load_module, snmpa, soft_purge, soft_purge, [snmp_log]},
{load_module, snmp_log, soft_purge, soft_purge, []},
+ {load_module, snmp_pdus, soft_purge, soft_purge, []},
+ {load_module, snmp_usm, soft_purge, soft_purge, []},
+
+ {load_module, snmpa, soft_purge, soft_purge, [snmp_log, snmpa_agent]},
{load_module, snmpa_general_db, soft_purge, soft_purge, []},
+ {load_module, snmpa_mib_data, soft_purge, soft_purge, []},
+ {load_module, snmpa_mpd, soft_purge, soft_purge, [snmpa_usm]},
+ {load_module, snmpa_usm, soft_purge, soft_purge, [snmp_usm]},
{update, snmpa_net_if, {advanced, downgrade_to_pre_4_16},
soft_purge, soft_purge, [snmpa_agent, snmp_log]},
- {update, snmpa_agent, soft, soft_purge, soft_purge, []},
+ {update, snmpa_mib, soft, soft_purge, soft_purge, [snmpa_mib_data]},
+ {update, snmpa_agent, soft, soft_purge, soft_purge, [snmpa_mib]},
+ {load_module, snmpm_mpd, soft_purge, soft_purge, []},
{load_module, snmpm_user, soft_purge, soft_purge, []},
{load_module, snmpm_user_default, soft_purge, soft_purge, [snmpm_user]},
{update, snmpm_net_if, {advanced, downgrade_to_pre_4_14},
diff --git a/lib/snmp/src/compile/snmpc.erl b/lib/snmp/src/compile/snmpc.erl
index 8a1f15d4a4..a7f2cdc2bc 100644
--- a/lib/snmp/src/compile/snmpc.erl
+++ b/lib/snmp/src/compile/snmpc.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(snmpc).
@@ -34,6 +34,7 @@
-include("snmpc.hrl").
-include("snmpc_lib.hrl").
+-record(dldata, {deprecated, relaxed_row_name_assign_check}).
look_at(Mib) ->
io:format("~p ~n", [snmpc_lib:look_at(Mib)]).
@@ -114,6 +115,7 @@ compile(FileName) ->
%% module_identity
%% {module, string()}
%% no_defs
+%% relaxed_row_name_assign_check
%% (hidden) {verbosity, trace|debug|log|info|silence} silence
%% (hidden) version
%% (hidden) options
@@ -201,6 +203,8 @@ get_options([imports|Opts], Formats, Args) ->
get_options(Opts, ["~n imports"|Formats], Args);
get_options([module_identity|Opts], Formats, Args) ->
get_options(Opts, ["~n module_identity"|Formats], Args);
+get_options([relaxed_row_name_assign_check|Opts], Formats, Args) ->
+ get_options(Opts, ["~n relaxed_row_name_assign_check"|Formats], Args);
get_options([_|Opts], Formats, Args) ->
get_options(Opts, Formats, Args).
@@ -284,6 +288,8 @@ check_options([imports| T]) ->
check_options(T);
check_options([module_identity| T]) ->
check_options(T);
+check_options([relaxed_row_name_assign_check| T]) ->
+ check_options(T);
check_options([{module, M} | T]) when is_atom(M) ->
check_options(T);
check_options([no_defs| T]) ->
@@ -309,6 +315,9 @@ get_description(Options) ->
get_reference(Options) ->
get_bool_option(reference, Options).
+get_relaxed_row_name_assign_check(Options) ->
+ lists:member(relaxed_row_name_assign_check, Options).
+
get_bool_option(Option, Options) ->
case lists:member(Option, Options) of
false ->
@@ -406,8 +415,12 @@ compile_parsed_data(#pdata{mib_name = MibName,
defs = Definitions}) ->
snmpc_lib:import(Imports),
update_imports(Imports),
- Deprecated = get_deprecated(get(options)),
- definitions_loop(Definitions, Deprecated),
+ Opts = get(options),
+ Deprecated = get_deprecated(Opts),
+ RelChk = get_relaxed_row_name_assign_check(Opts),
+ Data = #dldata{deprecated = Deprecated,
+ relaxed_row_name_assign_check = RelChk},
+ definitions_loop(Definitions, Data),
MibName.
update_imports(Imports) ->
@@ -436,21 +449,21 @@ update_status(Name, Status) ->
%% A deprecated object
definitions_loop([{#mc_object_type{name = ObjName, status = deprecated},
Line}|T],
- false) ->
+ #dldata{deprecated = false} = Data) ->
%% May be implemented but the compiler chooses not to.
?vinfo2("object_type ~w is deprecated => ignored", [ObjName], Line),
update_status(ObjName, deprecated),
- definitions_loop(T, false);
+ definitions_loop(T, Data);
%% A obsolete object
definitions_loop([{#mc_object_type{name = ObjName, status = obsolete},
Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("object_type ~w is obsolete => ignored", [ObjName], Line),
%% No need to implement a obsolete object
update_status(ObjName, obsolete),
ensure_macro_imported('OBJECT-TYPE', Line),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
%% Defining a table
definitions_loop([{#mc_object_type{name = NameOfTable,
@@ -475,7 +488,7 @@ definitions_loop([{#mc_object_type{name = NameOfTable,
{#mc_sequence{name = SeqName,
fields = FieldList},
Sline}|ColsEtc],
- Deprecated) ->
+ Data) ->
?vlog("defloop -> "
"[object_type(sequence_of),object_type(type,[1]),sequence]:"
"~n NameOfTable: ~p"
@@ -529,7 +542,89 @@ definitions_loop([{#mc_object_type{name = NameOfTable,
TableME#me{assocList=[{table_info,
TableInfo} | make_reference(Ref)]} |
ColMEs]),
- definitions_loop(RestObjs, Deprecated);
+ definitions_loop(RestObjs, Data);
+
+definitions_loop([{#mc_object_type{name = NameOfTable,
+ syntax = {{sequence_of, SeqName}, _},
+ max_access = Taccess,
+ kind = Kind,
+ status = Tstatus,
+ description = Desc1,
+ units = Tunits,
+ reference = Ref,
+ name_assign = Tindex},
+ Tline},
+ {#mc_object_type{name = NameOfEntry,
+ syntax = {{type, SeqName}, TEline},
+ max_access = 'not-accessible',
+ kind = {table_entry, IndexingInfo},
+ status = Estatus,
+ description = Desc2,
+ units = Eunits,
+ name_assign = {NameOfTable,[Idx]} = BadOID},
+ Eline},
+ {#mc_sequence{name = SeqName,
+ fields = FieldList},
+ Sline}|ColsEtc],
+ #dldata{relaxed_row_name_assign_check = true} = Data)
+ when is_integer(Idx) andalso (Idx > 1) ->
+ ?vlog("defloop -> "
+ "[object_type(sequence_of),object_type(type,[~w]),sequence]:"
+ "~n NameOfTable: ~p"
+ "~n SeqName: ~p"
+ "~n Taccess: ~p"
+ "~n Kind: ~p"
+ "~n Tstatus: ~p"
+ "~n Tindex: ~p"
+ "~n Tunits: ~p"
+ "~n Tline: ~p"
+ "~n NameOfEntry: ~p"
+ "~n TEline: ~p"
+ "~n IndexingInfo: ~p"
+ "~n Estatus: ~p"
+ "~n Eunits: ~p"
+ "~n Ref: ~p"
+ "~n Eline: ~p"
+ "~n FieldList: ~p"
+ "~n Sline: ~p",
+ [Idx,
+ NameOfTable,SeqName,Taccess,Kind,Tstatus,
+ Tindex,Tunits,Tline,
+ NameOfEntry,TEline,IndexingInfo,Estatus,Eunits,Ref,Eline,
+ FieldList,Sline]),
+ update_status(NameOfTable, Tstatus),
+ update_status(NameOfEntry, Estatus),
+ update_status(SeqName, undefined),
+ ensure_macro_imported('OBJECT-TYPE', Tline),
+ ?vwarning2("Bad TableEntry OID definition (~w)",
+ [BadOID], Eline),
+ test_table(NameOfTable,Taccess,Kind,Tindex,Tline),
+ {Tfather,Tsubindex} = Tindex,
+ snmpc_lib:register_oid(Tline,NameOfTable,Tfather,Tsubindex),
+ Description1 = make_description(Desc1),
+ TableME = #me{aliasname = NameOfTable,
+ entrytype = table,
+ access = 'not-accessible',
+ description = Description1,
+ units = Tunits},
+ snmpc_lib:register_oid(TEline,NameOfEntry,NameOfTable,[Idx]),
+ Description2 = make_description(Desc2),
+ TableEntryME = #me{aliasname = NameOfEntry,
+ entrytype = table_entry,
+ assocList = [{table_entry_with_sequence, SeqName}],
+ access = 'not-accessible',
+ description = Description2,
+ units = Eunits},
+ {ColMEs, RestObjs} =
+ define_cols(ColsEtc, 1, FieldList, NameOfEntry, NameOfTable, []),
+ TableInfo = snmpc_lib:make_table_info(Eline, NameOfTable,
+ IndexingInfo, ColMEs),
+ snmpc_lib:add_cdata(#cdata.mes,
+ [TableEntryME,
+ TableME#me{assocList=[{table_info,
+ TableInfo} | make_reference(Ref)]} |
+ ColMEs]),
+ definitions_loop(RestObjs, Data);
definitions_loop([{#mc_object_type{name = NameOfTable,
syntax = {{sequence_of, SeqName},_},
@@ -550,7 +645,7 @@ definitions_loop([{#mc_object_type{name = NameOfTable,
name_assign = BadOID}, Eline},
{#mc_sequence{name = SeqName,
fields = FieldList}, Sline}|ColsEtc],
- Deprecated) ->
+ Data) ->
?vlog("defloop -> "
"[object_type(sequence_of),object_type(type),sequence(fieldList)]:"
"~n NameOfTable: ~p"
@@ -605,13 +700,13 @@ definitions_loop([{#mc_object_type{name = NameOfTable,
TableME#me{assocList=[{table_info,
TableInfo} | make_reference(Ref)]} |
ColMEs]),
- definitions_loop(RestObjs, Deprecated);
+ definitions_loop(RestObjs, Data);
definitions_loop([{#mc_new_type{name = NewTypeName,
macro = Macro,
syntax = OldType,
display_hint = DisplayHint},Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> new_type:"
"~n Macro: ~p"
"~n NewTypeName: ~p"
@@ -632,7 +727,7 @@ definitions_loop([{#mc_new_type{name = NewTypeName,
imported = false,
display_hint = DisplayHint}])
end,
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
%% Plain variable
definitions_loop([{#mc_object_type{name = NewVarName,
@@ -643,7 +738,7 @@ definitions_loop([{#mc_object_type{name = NewVarName,
description = Desc1,
units = Units,
name_assign = {Parent,SubIndex}},Line} |T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> object_type (variable):"
"~n NewVarName: ~p"
"~n Type: ~p"
@@ -672,7 +767,7 @@ definitions_loop([{#mc_object_type{name = NewVarName,
VI = snmpc_lib:make_variable_info(NewME2),
snmpc_lib:add_cdata(#cdata.mes,
[NewME2#me{assocList = [{variable_info, VI}]}]),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_module_identity{name = NewVarName,
last_updated = LU,
@@ -682,7 +777,7 @@ definitions_loop([{#mc_module_identity{name = NewVarName,
revisions = Revs0,
name_assign = {Parent, SubIndex}},
Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> module-identity: "
"~n NewVarName: ~p"
"~n LU: ~p"
@@ -706,13 +801,13 @@ definitions_loop([{#mc_module_identity{name = NewVarName,
snmpc_lib:add_cdata(
#cdata.mes,
[snmpc_lib:makeInternalNode2(false, NewVarName)]),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_internal{name = NewVarName,
macro = Macro,
parent = Parent,
sub_index = SubIndex},Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> internal:"
"~n NewVarName: ~p"
"~n Macro: ~p"
@@ -724,7 +819,7 @@ definitions_loop([{#mc_internal{name = NewVarName,
snmpc_lib:add_cdata(
#cdata.mes,
[snmpc_lib:makeInternalNode2(false, NewVarName)]),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
%% A trap message
definitions_loop([{#mc_trap{name = TrapName,
@@ -732,7 +827,7 @@ definitions_loop([{#mc_trap{name = TrapName,
vars = Variables,
description = Desc1,
num = SpecificCode}, Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> trap:"
"~n TrapName: ~p"
"~n EnterPrise: ~p"
@@ -755,7 +850,7 @@ definitions_loop([{#mc_trap{name = TrapName,
lists:foreach(fun(Trap2) -> snmpc_lib:check_trap(Trap2, Trap, Line) end,
CDATA#cdata.traps),
snmpc_lib:add_cdata(#cdata.traps, [Trap]),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_object_type{name = NameOfEntry,
syntax = Type,
@@ -763,7 +858,7 @@ definitions_loop([{#mc_object_type{name = NameOfEntry,
kind = {table_entry, Index},
status = Estatus,
name_assign = SubIndex},Eline}|T],
- Deprecated) ->
+ Data) ->
?vlog("defloop -> object_type (table_entry):"
"~n NameOfEntry: ~p"
"~n Type: ~p"
@@ -777,7 +872,7 @@ definitions_loop([{#mc_object_type{name = NameOfEntry,
update_status(NameOfEntry, Estatus),
snmpc_lib:print_error("Misplaced TableEntry definition (~w)",
[NameOfEntry], Eline),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_notification{name = TrapName,
status = deprecated}, Line}|T],
@@ -790,19 +885,19 @@ definitions_loop([{#mc_notification{name = TrapName,
definitions_loop([{#mc_notification{name = TrapName,
status = obsolete}, Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> notification ~w is obsolete => ignored",
[TrapName], Line),
update_status(TrapName, obsolete),
ensure_macro_imported('NOTIFICATION-TYPE', Line),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_notification{name = TrapName,
vars = Variables,
status = Status,
description = Desc,
name_assign = {Parent, SubIndex}},Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> notification:"
"~n TrapName: ~p"
"~n Variables: ~p"
@@ -824,13 +919,13 @@ definitions_loop([{#mc_notification{name = TrapName,
oidobjects = Variables},
snmpc_lib:check_notification(Notif, Line, CDATA#cdata.traps),
snmpc_lib:add_cdata(#cdata.traps, [Notif]),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
-definitions_loop([{#mc_module_compliance{name = Name},Line}|T], Deprecated) ->
+definitions_loop([{#mc_module_compliance{name = Name},Line}|T], Data) ->
?vlog2("defloop -> module_compliance:"
"~n Name: ~p", [Name], Line),
ensure_macro_imported('MODULE-COMPLIANCE', Line),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_object_group{name = Name,
objects = GroupObjects,
@@ -838,7 +933,7 @@ definitions_loop([{#mc_object_group{name = Name,
description = Desc,
reference = Ref,
name_assign = {Parent,SubIndex}}, Line}|T],
- Deprecated) ->
+ Data) ->
?vlog2("defloop -> object_group ~p:"
"~n GroupObjects: ~p"
"~n Status: ~p"
@@ -873,7 +968,7 @@ definitions_loop([{#mc_object_group{name = Name,
{objects, GroupObjects}]},
snmpc_lib:add_cdata(#cdata.mes, [NewME]),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_notification_group{name = Name,
objects = GroupObjects,
@@ -882,7 +977,7 @@ definitions_loop([{#mc_notification_group{name = Name,
reference = Ref,
name_assign = {Parent,SubIndex}},
Line}
- |T], Deprecated) ->
+ |T], Data) ->
?vlog2("defloop -> notification_group ~p:"
"~n GroupObjects: ~p"
"~n Status: ~p"
@@ -918,13 +1013,13 @@ definitions_loop([{#mc_notification_group{name = Name,
{objects, GroupObjects}]},
snmpc_lib:add_cdata(#cdata.mes, [NewME]),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_object_type{name = NameOfTable,
syntax = {{sequence_of, SeqName},_},
status = Tstatus},Tline},
Entry, Seq|T],
- Deprecated) ->
+ Data) ->
?vlog("defloop -> object_type (sequence_of): "
"~n NameOfTable: ~p"
"~n SeqName: ~p"
@@ -956,12 +1051,12 @@ definitions_loop([{#mc_object_type{name = NameOfTable,
"Invalid TableEntry '~p' (check STATUS, Sequence name, Oid)",
[safe_elem(1,safe_elem(2,Entry))],Tline)
end,
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_object_type{name = NameOfTable,
syntax = {{sequence_of, SeqName},_},
status = Tstatus},Tline}|T],
- Deprecated) ->
+ Data) ->
?vlog("defloop -> object_type (sequence_of):"
"~n object_type: ~p"
"~n sequence_of: ~p"
@@ -969,24 +1064,24 @@ definitions_loop([{#mc_object_type{name = NameOfTable,
update_status(NameOfTable, Tstatus),
snmpc_lib:print_error("Invalid statements following table ~p.",
[NameOfTable],Tline),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
definitions_loop([{#mc_sequence{name = SeqName,
fields = _FieldList},Line}|T],
- Deprecated) ->
+ Data) ->
?vwarning2("Unexpected SEQUENCE ~w => ignoring", [SeqName], Line),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
-definitions_loop([{Obj,Line}|T], Deprecated) ->
+definitions_loop([{Obj,Line}|T], Data) ->
?vinfo2("defloop -> unknown error"
"~n Obj: ~p", [Obj], Line),
snmpc_lib:print_error("Unknown Error in MIB. "
"Can't describe the error better than this: ~999p ignored."
" Please send a trouble report to [email protected].",
[Obj], Line),
- definitions_loop(T, Deprecated);
+ definitions_loop(T, Data);
-definitions_loop([], _Deprecated) ->
+definitions_loop([], _Data) ->
?vlog("defloop -> done", []),
ok.
diff --git a/lib/snmp/src/compile/snmpc_lib.erl b/lib/snmp/src/compile/snmpc_lib.erl
index b7e84e7d6b..4e5bc69f81 100644
--- a/lib/snmp/src/compile/snmpc_lib.erl
+++ b/lib/snmp/src/compile/snmpc_lib.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
@@ -125,7 +125,8 @@ test_kibbles(Kibbles,Line) ->
test_kibbles2([],_,_) ->
ok;
-test_kibbles2([{_KibbleName,BitNo}|Ks],BitNo,Line) ->
+test_kibbles2([{_KibbleName,BitNo}|Ks],ExpectBitNo,Line)
+ when BitNo >= ExpectBitNo ->
test_kibbles2(Ks,BitNo+1,Line);
test_kibbles2([{_KibbleName,BitNo}|_Ks],ExpectBitNo,Line) ->
print_error("Expected kibble no ~p but got ~p.",[ExpectBitNo,BitNo],Line).
diff --git a/lib/snmp/src/manager/snmpm_mpd.erl b/lib/snmp/src/manager/snmpm_mpd.erl
index d76ad20051..7712370d28 100644
--- a/lib/snmp/src/manager/snmpm_mpd.erl
+++ b/lib/snmp/src/manager/snmpm_mpd.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
@@ -257,11 +257,11 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) ->
end,
?vlog("7.2.7"
- "~n ContextEngineID: \"~s\" "
+ "~n ContextEngineID: ~p "
"~n context: \"~s\" ",
[CtxEngineID, CtxName]),
if
- SecLevel == 3 -> % encrypted message - log decrypted pdu
+ SecLevel =:= 3 -> % encrypted message - log decrypted pdu
Log({Hdr, ScopedPDUBytes});
true -> % otherwise, log binary
Log(Msg)
@@ -338,7 +338,8 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) ->
SnmpEngineID = get_engine_id(),
case SecEngineID of
SnmpEngineID -> % 7.2.13.b
- ?vtrace("valid securityEngineID: ~p", [SecEngineID]),
+ ?vtrace("7.2.13d - valid securityEngineID: ~p",
+ [SecEngineID]),
%% 4.2.2.1.1 - we don't handle proxys yet => we only
%% handle CtxEngineID to ourselves
%% Check that we actually know of an agent with this
@@ -353,7 +354,9 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) ->
{MsgID, MsgSecModel, SecName, SecLevel,
CtxEngineID, CtxName, SecData},
{ok, 'version-3', PDU, PduMMS, ACMData};
- _ ->
+ UnknownEngineID ->
+ ?vtrace("4.2.2.1.2 - UnknownEngineId: ~p",
+ [UnknownEngineID]),
%% 4.2.2.1.2
NIsReportable = snmp_misc:is_reportable_pdu(Type),
Val = inc(snmpUnknownPDUHandlers),
@@ -377,7 +380,8 @@ process_v3_msg(NoteStore, Msg, Hdr, Data, Addr, Port, Log) ->
end
end;
_ -> % 7.2.13.a
- ?vinfo("invalid securityEngineID: ~p",[SecEngineID]),
+ ?vinfo("7.2.13a - invalid securityEngineID: ~p",
+ [SecEngineID]),
discard({badSecurityEngineID, SecEngineID})
end;
diff --git a/lib/snmp/src/manager/snmpm_server.erl b/lib/snmp/src/manager/snmpm_server.erl
index 30aacc0ec3..d64b5b1d53 100644
--- a/lib/snmp/src/manager/snmpm_server.erl
+++ b/lib/snmp/src/manager/snmpm_server.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
@@ -2804,16 +2804,16 @@ agent_data(TargetName, CtxName) ->
agent_data(TargetName, CtxName, Config) ->
case snmpm_config:agent_info(TargetName, all) of
{ok, Info} ->
- {value, {_, Version}} = lists:keysearch(version, 1, Info),
+ Version = agent_data_item(version, Info),
MsgData =
case Version of
v3 ->
DefSecModel = agent_data_item(sec_model, Info),
DefSecName = agent_data_item(sec_name, Info),
DefSecLevel = agent_data_item(sec_level, Info),
-
+
EngineId = agent_data_item(engine_id, Info),
-
+
SecModel = agent_data_item(sec_model,
Config,
DefSecModel),
@@ -2829,7 +2829,7 @@ agent_data(TargetName, CtxName, Config) ->
_ ->
DefComm = agent_data_item(community, Info),
DefSecModel = agent_data_item(sec_model, Info),
-
+
Comm = agent_data_item(community,
Config,
DefComm),
@@ -2848,8 +2848,12 @@ agent_data(TargetName, CtxName, Config) ->
end.
agent_data_item(Item, Info) ->
- {value, {_, Val}} = lists:keysearch(Item, 1, Info),
- Val.
+ case lists:keysearch(Item, 1, Info) of
+ {value, {_, Val}} ->
+ Val;
+ false ->
+ throw({error, {not_found, Item, Info}})
+ end.
agent_data_item(Item, Info, Default) ->
case lists:keysearch(Item, 1, Info) of
diff --git a/lib/snmp/src/misc/snmp_pdus.erl b/lib/snmp/src/misc/snmp_pdus.erl
index 6c80fc3876..dc8900c8cd 100644
--- a/lib/snmp/src/misc/snmp_pdus.erl
+++ b/lib/snmp/src/misc/snmp_pdus.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -38,7 +38,10 @@
dec_usm_security_parameters/1,
strip_encrypted_scoped_pdu_data/1,
octet_str_to_bits/1, bits_to_str/1,
- get_encoded_length/1]).
+ get_encoded_length/1,
+ enc_value/2, dec_value/1]).
+
+%% -compile(export_all).
%% Returns the number of octets required to encode Length.
get_encoded_length(Length) ->
@@ -290,12 +293,18 @@ dec_value([68 | Bytes]) ->
{Value, Rest} = dec_oct_str_notag(Bytes),
{{'Opaque', Value}, Rest};
dec_value([70 | Bytes]) ->
+ %% Counter64 is an unsigned 64 but is actually encoded as
+ %% a signed integer 64.
{Value, Rest} = dec_integer_notag(Bytes),
- if Value >= 0, Value =< 18446744073709551615 ->
- {{'Counter64', Value}, Rest};
- true ->
- exit({error, {bad_counter64, Value}})
- end;
+ Value2 =
+ if
+ (Value >= 0) andalso (Value < 16#8000000000000000) ->
+ Value;
+ (Value < 0) ->
+ 18446744073709551615 + Value + 1;
+ true ->
+ exit({error, {bad_counter64, Value}}) end,
+ {{'Counter64', Value2}, Rest};
dec_value([128,0|T]) ->
{{'NULL', noSuchObject}, T};
dec_value([129,0|T]) ->
@@ -633,6 +642,21 @@ enc_value(_Type, endOfMibView) ->
[130,0];
enc_value('NULL', _Val) ->
[5,0];
+enc_value('Counter64', Val) ->
+ Val2 =
+ if
+ Val > 16#ffffffffffffffff ->
+ exit({error, {bad_counter64, Val}});
+ Val >= 16#8000000000000000 ->
+ (Val band 16#7fffffffffffffff) - 16#8000000000000000;
+ Val >= 0 ->
+ Val;
+ true ->
+ exit({error, {bad_counter64, Val}})
+ end,
+ Bytes2 = enc_integer_notag(Val2),
+ Len2 = elength(length(Bytes2)),
+ lists:append([70 | Len2],Bytes2);
enc_value(Type, Val) ->
Bytes2 = enc_integer_notag(Val),
Len2 = elength(length(Bytes2)),
@@ -643,10 +667,7 @@ enc_val_tag('Counter32',Val) when (Val >= 0) andalso (Val =< 4294967295) ->
enc_val_tag('Unsigned32', Val) when (Val >= 0) andalso (Val =< 4294967295) ->
66;
enc_val_tag('TimeTicks', Val) when (Val >= 0) andalso (Val =< 4294967295) ->
- 67;
-enc_val_tag('Counter64', Val) when ((Val >= 0) andalso
- (Val =< 18446744073709551615)) ->
- 70.
+ 67.
%%----------------------------------------------------------------------
diff --git a/lib/snmp/src/misc/snmp_usm.erl b/lib/snmp/src/misc/snmp_usm.erl
index 19be564a8e..3508f9e1c2 100644
--- a/lib/snmp/src/misc/snmp_usm.erl
+++ b/lib/snmp/src/misc/snmp_usm.erl
@@ -198,7 +198,7 @@ des_encrypt(PrivKey, Data, SaltFun) ->
[A,B,C,D,E,F,G,H | PreIV] = PrivKey,
DesKey = [A,B,C,D,E,F,G,H],
Salt = SaltFun(),
- IV = snmp_misc:str_xor(PreIV, Salt),
+ IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)),
TailLen = (8 - (length(Data) rem 8)) rem 8,
Tail = mk_tail(TailLen),
EncData = crypto:des_cbc_encrypt(DesKey, IV, [Data,Tail]),
@@ -213,13 +213,13 @@ des_decrypt(PrivKey, MsgPrivParams, EncData)
[A,B,C,D,E,F,G,H | PreIV] = PrivKey,
DesKey = [A,B,C,D,E,F,G,H],
Salt = MsgPrivParams,
- IV = snmp_misc:str_xor(PreIV, Salt),
+ IV = list_to_binary(snmp_misc:str_xor(PreIV, Salt)),
%% Whatabout errors here??? E.g. not a mulitple of 8!
Data = binary_to_list(crypto:des_cbc_decrypt(DesKey, IV, EncData)),
Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data),
{ok, Data2};
des_decrypt(PrivKey, BadMsgPrivParams, EncData) ->
- ?vtrace("des_decrypt -> entry with when bad MsgPrivParams"
+ ?vtrace("des_decrypt -> entry when bad MsgPrivParams"
"~n PrivKey: ~p"
"~n BadMsgPrivParams: ~p"
"~n EncData: ~p",
@@ -232,7 +232,7 @@ aes_encrypt(PrivKey, Data, SaltFun) ->
Salt = SaltFun(),
EngineBoots = snmp_framework_mib:get_engine_boots(),
EngineTime = snmp_framework_mib:get_engine_time(),
- IV = [?i32(EngineBoots), ?i32(EngineTime) | Salt],
+ IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]),
EncData = crypto:aes_cfb_128_encrypt(AesKey, IV, Data),
{ok, binary_to_list(EncData), Salt}.
@@ -240,7 +240,7 @@ aes_decrypt(PrivKey, MsgPrivParams, EncData, EngineBoots, EngineTime)
when length(MsgPrivParams) =:= 8 ->
AesKey = PrivKey,
Salt = MsgPrivParams,
- IV = [?i32(EngineBoots), ?i32(EngineTime) | Salt],
+ IV = list_to_binary([?i32(EngineBoots), ?i32(EngineTime) | Salt]),
%% Whatabout errors here??? E.g. not a mulitple of 8!
Data = binary_to_list(crypto:aes_cfb_128_decrypt(AesKey, IV, EncData)),
Data2 = snmp_pdus:strip_encrypted_scoped_pdu_data(Data),
diff --git a/lib/snmp/test/modules.mk b/lib/snmp/test/modules.mk
index ff848cad1b..6a0c3e9481 100644
--- a/lib/snmp/test/modules.mk
+++ b/lib/snmp/test/modules.mk
@@ -1,20 +1,20 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2004-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2004-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%
SUITE_MODULES = \
@@ -57,6 +57,10 @@ MODULES = \
HRL_FILES = snmp_test_lib.hrl
+# These are MIBs that aure used by the compiler test-suite.
+COMPILER_MIB_FILES = \
+ OTP8574-MIB
+
MIB_FILES = \
OLD-SNMPEA-MIB.mib \
OLD-SNMPEA-MIB-v2.mib \
diff --git a/lib/snmp/test/snmp_agent_test.erl b/lib/snmp/test/snmp_agent_test.erl
index af0581150a..9d2e9969c4 100644
--- a/lib/snmp/test/snmp_agent_test.erl
+++ b/lib/snmp/test/snmp_agent_test.erl
@@ -1046,7 +1046,7 @@ v1_cases() ->
sparse_table,
cnt_64,
opaque,
-
+
change_target_addr_config
].
@@ -1977,7 +1977,8 @@ inform_i(Config) ->
?P1("unload TestTrap & TestTrapv2..."),
?line unload_master("TestTrap"),
- ?line unload_master("TestTrapv2").
+ ?line unload_master("TestTrapv2"),
+ ok.
v3_inform_i(X) ->
%% <CONDITIONAL-SKIP>
@@ -3446,7 +3447,7 @@ do_mul_set_err() ->
?line ?v1_2(expect(2, noSuchName, 1, any),
expect(2, [{[friendsEntry, [2,3]], noSuchInstance}])),
g([NewKeyc4]),
- ?line ?v1_2(expect(3, noSuchName, 1, any),
+ ?line ?v1_2(expect(3, noSuchName, 1, any),
expect(3, [{NewKeyc4, noSuchInstance}])).
%% Req. SA-MIB
@@ -3457,10 +3458,10 @@ sa_mib() ->
?line expect(2, [{[sa, [1,0]], "sa_test"}]).
ma_trap1(MA) ->
- snmpa:send_trap(MA, testTrap2, "standard trap"),
+ ok = snmpa:send_trap(MA, testTrap2, "standard trap"),
?line expect(1, trap, [system], 6, 1, [{[system, [4,0]],
"{mbj,eklas}@erlang.ericsson.se"}]),
- snmpa:send_trap(MA, testTrap1, "standard trap"),
+ ok = snmpa:send_trap(MA, testTrap1, "standard trap"),
?line expect(2, trap, [1,2,3] , 1, 0, [{[system, [4,0]],
"{mbj,eklas}@erlang.ericsson.se"}]).
@@ -3509,7 +3510,8 @@ ma_v2_trap1(MA) ->
?DBG("ma_v2_traps -> send standard trap: testTrapv21",[]),
snmpa:send_trap(MA, testTrapv21, "standard trap"),
?line expect(2, v2trap, [{[sysUpTime, 0], any},
- {[snmpTrapOID, 0], ?snmp ++ [1]}]).
+ {[snmpTrapOID, 0], ?snmp ++ [1]}]),
+ ok.
ma_v2_trap2(MA) ->
snmpa:send_trap(MA,testTrapv22,"standard trap",[{sysContact,"pelle"}]),
@@ -3517,7 +3519,7 @@ ma_v2_trap2(MA) ->
{[snmpTrapOID, 0], ?system ++ [0,1]},
{[system, [4,0]], "pelle"}]).
-%% Note: This test case takes a while... actually a couple of minutes.
+%% Note: This test case takes a while... actually a couple of minutes.
ma_v2_inform1(MA) ->
?DBG("ma_v2_inform1 -> entry with"
"~n MA = ~p => "
@@ -5258,7 +5260,35 @@ otp_1131_2(X) -> ?P(otp_1131_2), otp_1131(X).
otp_1131_3(X) ->
%% <CONDITIONAL-SKIP>
- Skippable = [{unix, [darwin]}],
+ %% This is intended to catch Montavista Linux 4.0/ppc (2.6.5)
+ %% Montavista Linux looks like a Debian distro (/etc/issue)
+ LinuxVersionVerify =
+ fun() ->
+ case os:cmd("uname -m") of
+ "ppc" ++ _ ->
+ case file:read_file_info("/etc/issue") of
+ {ok, _} ->
+ case os:cmd("grep -i montavista /etc/issue") of
+ Info when (is_list(Info) andalso
+ (length(Info) > 0)) ->
+ case os:version() of
+ {2, 6, 10} ->
+ true;
+ _ ->
+ false
+ end;
+ _ -> % Maybe plain Debian or Ubuntu
+ false
+ end;
+ _ ->
+ %% Not a Debian based distro
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ Skippable = [{unix, [darwin, {linux, LinuxVersionVerify}]}],
Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
?NON_PC_TC_MAYBE_SKIP(X, Condition),
%% </CONDITIONAL-SKIP>
@@ -6219,12 +6249,15 @@ verify_old_info([Key|Keys], Info) ->
is(S) -> [length(S) | S].
try_test(Func) ->
+ ?P2("try test ~w...", [Func]),
snmp_agent_test_lib:try_test(?MODULE, Func).
try_test(Func, A) ->
+ ?P2("try test ~w...", [Func]),
snmp_agent_test_lib:try_test(?MODULE, Func, A).
try_test(Func, A, Opts) ->
+ ?P2("try test ~w...", [Func]),
snmp_agent_test_lib:try_test(?MODULE, Func, A, Opts).
diff --git a/lib/snmp/test/snmp_agent_test_lib.erl b/lib/snmp/test/snmp_agent_test_lib.erl
index 31b375efa9..9e89aa889c 100644
--- a/lib/snmp/test/snmp_agent_test_lib.erl
+++ b/lib/snmp/test/snmp_agent_test_lib.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
@@ -421,7 +421,7 @@ start_agent(Config, Vsns, Opts) ->
?LOG("start_agent -> entry (~p) with"
"~n Config: ~p"
"~n Vsns: ~p"
- "~n Opts: ~p",[node(), Config, Vsns, Opts]),
+ "~n Opts: ~p", [node(), Config, Vsns, Opts]),
?line AgentDir = ?config(agent_dir, Config),
?line SaNode = ?config(snmp_sa, Config),
diff --git a/lib/snmp/test/snmp_compiler_test.erl b/lib/snmp/test/snmp_compiler_test.erl
index 9a9127a130..ad77b01362 100644
--- a/lib/snmp/test/snmp_compiler_test.erl
+++ b/lib/snmp/test/snmp_compiler_test.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -46,7 +46,9 @@
module_identity/1,
tickets/1,
- otp_6150/1
+ otp_6150/1,
+ otp_8574/1,
+ otp_8595/1
]).
@@ -56,6 +58,7 @@
-export([
]).
+
%%----------------------------------------------------------------------
%% Macros
%%----------------------------------------------------------------------
@@ -98,7 +101,9 @@ all(suite) ->
tickets(suite) ->
[
- otp_6150
+ otp_6150,
+ otp_8574,
+ otp_8595
].
@@ -178,6 +183,54 @@ otp_6150(Config) when is_list(Config) ->
ok.
+otp_8574(suite) ->
+ [];
+otp_8574(Config) when is_list(Config) ->
+ put(tname,otp_8574),
+ p("starting with Config: ~p~n", [Config]),
+
+ Dir = ?config(comp_dir, Config),
+ MibDir = ?config(mib_dir, Config),
+ MibFile = join(MibDir, "OTP8574-MIB.mib"),
+
+ p("ensure compile fail without relaxed assign check"),
+ case snmpc:compile(MibFile, [{group_check, false}, {outdir, Dir}]) of
+ {error, compilation_failed} ->
+ p("with relaxed assign check MIB compiles with warning"),
+ case snmpc:compile(MibFile, [{group_check, false},
+ {outdir, Dir},
+ relaxed_row_name_assign_check]) of
+ {ok, _Mib} ->
+ ok;
+ {error, Reason} ->
+ p("unexpected compile failure: "
+ "~n Reason: ~p", [Reason]),
+ exit({unexpected_compile_failure, Reason})
+ end;
+
+ {ok, _} ->
+ p("unexpected compile success"),
+ exit(unexpected_compile_success)
+ end.
+
+
+otp_8595(suite) ->
+ [];
+otp_8595(Config) when is_list(Config) ->
+ put(tname,otp_8595),
+ p("starting with Config: ~p~n", [Config]),
+
+ Dir = ?config(comp_dir, Config),
+ MibDir = ?config(mib_dir, Config),
+ MibFile = join(MibDir, "OTP8595-MIB.mib"),
+ ?line {ok, Mib} =
+ snmpc:compile(MibFile, [{outdir, Dir},
+ {verbosity, trace},
+ {group_check, false}]),
+ io:format("otp_8595 -> Mib: ~n~p~n", [Mib]),
+ ok.
+
+
%%======================================================================
%% Internal functions
%%======================================================================
@@ -373,6 +426,9 @@ join(A,B) ->
%% p(F) ->
%% p(F, []).
+p(F) ->
+ p(F, []).
+
p(F, A) ->
p(get(tname), F, A).
diff --git a/lib/snmp/test/snmp_manager_config_test.erl b/lib/snmp/test/snmp_manager_config_test.erl
index fcb3d7e30c..d5dc1387f7 100644
--- a/lib/snmp/test/snmp_manager_config_test.erl
+++ b/lib/snmp/test/snmp_manager_config_test.erl
@@ -1444,10 +1444,9 @@ start_with_invalid_usm_conf_file1(Conf) when is_list(Conf) ->
p("[test 54] write usm config file with invalid auth-key (4)"),
Usm54 = setelement(4, Usm51, "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,kalle]"),
write_usm_conf(ConfDir, [Usm54]),
- %% ?line ok = crypto:start(), %% Varf�r k�r den redan?
- ?line crypto:start(), %% Make sure it's started...
+ ?line maybe_start_crypto(), %% Make sure it's started...
?line {error, Reason54} = config_start(Opts),
- ?line ok = crypto:stop(),
+ ?line ok = maybe_stop_crypto(),
p("start failed (as expected): ~p", [Reason54]),
?line {failed_check, _, _, _, {invalid_auth_key, _}} = Reason54,
await_config_not_running(),
@@ -1492,21 +1491,35 @@ start_with_invalid_usm_conf_file1(Conf) when is_list(Conf) ->
p("[test 59] write usm config file with invalid auth-key (9)"),
Usm59 = setelement(4, Usm57, "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,ka]"),
write_usm_conf(ConfDir, [Usm59]),
- ?line ok = crypto:start(),
+ ?line ok = maybe_start_crypto(),
?line {error, Reason59} = config_start(Opts),
- ?line ok = crypto:stop(),
+ ?line ok = maybe_stop_crypto(),
p("start failed (as expected): ~p", [Reason59]),
?line {failed_check, _, _, _, {invalid_auth_key, _}} = Reason59,
await_config_not_running(),
%% --
- p("[test 5A] write usm config file with valid auth-key when crypto not started (10)"),
- Usm5A = setelement(4, Usm57, "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]"),
- write_usm_conf(ConfDir, [Usm5A]),
- ?line {error, Reason5A} = config_start(Opts),
- p("start failed (as expected): ~p", [Reason5A]),
- ?line {failed_check, _, _, _, {unsupported_crypto, _}} = Reason5A,
- await_config_not_running(),
+ %% <CRYPTO-MODIFICATIONS>
+ %% The crypto application do no longer need to be started
+ %% explicitly (all of it is as of R14 implemented with NIFs).
+ case (catch crypto:version()) of
+ {'EXIT', {undef, _}} ->
+ p("[test 5A] write usm config file with valid auth-key "
+ "when crypto not started (10)"),
+ Usm5A = setelement(4,
+ Usm57,
+ "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0]"),
+ write_usm_conf(ConfDir, [Usm5A]),
+ ?line {error, Reason5A} = config_start(Opts),
+ p("start failed (as expected): ~p", [Reason5A]),
+ ?line {failed_check, _, _, _, {unsupported_crypto, _}} = Reason5A,
+ await_config_not_running();
+ _ ->
+ %% This function is only present in version 2.0 or greater.
+ %% The crypto app no longer needs to be explicitly started
+ ok
+ end,
+ %% </CRYPTO-MODIFICATIONS>
%% --
p("[test 61] write usm config file with invalid priv-protocol (1)"),
@@ -1566,9 +1579,9 @@ start_with_invalid_usm_conf_file1(Conf) when is_list(Conf) ->
p("[test 74] write usm config file with invalid priv-key (4)"),
Usm74 = setelement(6, Usm71, "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,kalle]"),
write_usm_conf(ConfDir, [Usm74]),
- ?line ok = crypto:start(),
+ ?line ok = maybe_start_crypto(),
?line {error, Reason74} = config_start(Opts),
- ?line ok = crypto:stop(),
+ ?line ok = maybe_stop_crypto(),
p("start failed (as expected): ~p", [Reason74]),
?line {failed_check, _, _, _, {invalid_priv_key, _}} = Reason74,
await_config_not_running(),
@@ -1592,15 +1605,27 @@ start_with_invalid_usm_conf_file1(Conf) when is_list(Conf) ->
await_config_not_running(),
%% --
- p("[test 77] write usm config file with valid priv-key when crypto not started (7)"),
- Usm77 = setelement(6, Usm71, "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]"),
- write_usm_conf(ConfDir, [Usm77]),
- ?line {error, Reason77} = config_start(Opts),
- p("start failed (as expected): ~p", [Reason77]),
- ?line {failed_check, _, _, _, {unsupported_crypto, _}} = Reason77,
- await_config_not_running(),
+ %% <CRYPTO-MODIFICATIONS>
+ %% The crypto application do no longer need to be started
+ %% explicitly (all of it is as of R14 implemented with NIFs).
+ case (catch crypto:version()) of
+ {'EXIT', {undef, _}} ->
+ p("[test 77] write usm config file with valid priv-key "
+ "when crypto not started (7)"),
+ Usm77 = setelement(6, Usm71, "[1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6]"),
+ write_usm_conf(ConfDir, [Usm77]),
+ ?line {error, Reason77} = config_start(Opts),
+ p("start failed (as expected): ~p", [Reason77]),
+ ?line {failed_check, _, _, _, {unsupported_crypto, _}} = Reason77,
+ await_config_not_running();
+ _ ->
+ %% This function is only present in version 2.0 or greater.
+ %% The crypto app no longer needs to be explicitly started
+ ok
+ end,
+ %% </CRYPTO-MODIFICATIONS>
- %% --
+ %% --
p("[test 78] write usm config file with invalid usm (1)"),
write_usm_conf2(ConfDir, "{\"bmkEngine\", \"swiusmcf\"}."),
?line {error, Reason81} = config_start(Opts),
@@ -2676,6 +2701,27 @@ write_conf_file(Dir, File, Str) ->
file:close(Fd).
+maybe_start_crypto() ->
+ case (catch crypto:version()) of
+ {'EXIT', {undef, _}} ->
+ %% This is the version of crypto before the NIFs...
+ ?CRYPTO_START();
+ _ ->
+ %% No need to start this version of crypto..
+ ok
+ end.
+
+maybe_stop_crypto() ->
+ case (catch crypto:version()) of
+ {'EXIT', {undef, _}} ->
+ %% This is the version of crypto before the NIFs...
+ crypto:stop();
+ _ ->
+ %% There is nothing to stop in this version of crypto..
+ ok
+ end.
+
+
%% ------
str(X) ->
diff --git a/lib/snmp/test/snmp_manager_test.erl b/lib/snmp/test/snmp_manager_test.erl
index 518b8b34de..cef96417dc 100644
--- a/lib/snmp/test/snmp_manager_test.erl
+++ b/lib/snmp/test/snmp_manager_test.erl
@@ -795,6 +795,35 @@ notify_started02(suite) -> [];
notify_started02(Config) when is_list(Config) ->
process_flag(trap_exit, true),
put(tname,ns02),
+
+ %% <CONDITIONAL-SKIP>
+ %% The point of this is to catch machines running
+ %% SLES9 (2.6.5)
+ LinuxVersionVerify =
+ fun() ->
+ case os:cmd("uname -m") of
+ "i686" ++ _ ->
+%% io:format("found an i686 machine, "
+%% "now check version~n", []),
+ case os:version() of
+ {2, 6, Rev} when Rev >= 16 ->
+ true;
+ {2, Min, _} when Min > 6 ->
+ true;
+ {Maj, _, _} when Maj > 2 ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ true
+ end
+ end,
+ Skippable = [{unix, [{linux, LinuxVersionVerify}]}],
+ Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
+ ?NON_PC_TC_MAYBE_SKIP(Config, Condition),
+ %% </CONDITIONAL-SKIP>
+
p("starting with Config: ~n~p", [Config]),
ConfDir = ?config(manager_conf_dir, Config),
diff --git a/lib/snmp/test/snmp_manager_user_test.erl b/lib/snmp/test/snmp_manager_user_test.erl
index 24ed3b0b73..0f47d70873 100644
--- a/lib/snmp/test/snmp_manager_user_test.erl
+++ b/lib/snmp/test/snmp_manager_user_test.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
@@ -822,10 +822,39 @@ register_monitor_and_crash3(doc) ->
"Start a single user process, "
"register-monitor one user and register one user, "
"crash the single user process.";
-register_monitor_and_crash3(Conf) when is_list(Conf) ->
+register_monitor_and_crash3(Conf) when is_list(Conf) ->
+ process_flag(trap_exit, true),
put(tname,rlac3),
+
+ %% <CONDITIONAL-SKIP>
+ %% The point of this is to catch machines running
+ %% SLES9 (2.6.5)
+ LinuxVersionVerify =
+ fun() ->
+ case os:cmd("uname -m") of
+ "i686" ++ _ ->
+%% io:format("found an i686 machine, "
+%% "now check version~n", []),
+ case os:version() of
+ {2, 6, Rev} when Rev >= 16 ->
+ true;
+ {2, Min, _} when Min > 6 ->
+ true;
+ {Maj, _, _} when Maj > 2 ->
+ true;
+ _ ->
+ false
+ end;
+ _ ->
+ true
+ end
+ end,
+ Skippable = [{unix, [{linux, LinuxVersionVerify}]}],
+ Condition = fun() -> ?OS_BASED_SKIP(Skippable) end,
+ ?NON_PC_TC_MAYBE_SKIP(Conf, Condition),
+ %% </CONDITIONAL-SKIP>
+
p("start"),
- process_flag(trap_exit, true),
ConfDir = ?config(manager_conf_dir, Conf),
DbDir = ?config(manager_db_dir, Conf),
diff --git a/lib/snmp/test/snmp_pdus_test.erl b/lib/snmp/test/snmp_pdus_test.erl
index d5add50f52..6dc5b779aa 100644
--- a/lib/snmp/test/snmp_pdus_test.erl
+++ b/lib/snmp/test/snmp_pdus_test.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -37,6 +37,7 @@
all/1,
tickets/1,
otp7575/1,
+ otp8563/1,
init_per_testcase/2, fin_per_testcase/2
]).
@@ -66,6 +67,7 @@ init_per_testcase(_Case, Config) when is_list(Config) ->
fin_per_testcase(_Case, Config) when is_list(Config) ->
Config.
+
%%======================================================================
%% Test case definitions
%%======================================================================
@@ -76,7 +78,8 @@ all(suite) ->
tickets(suite) ->
[
- otp7575
+ otp7575,
+ otp8563
].
@@ -118,6 +121,55 @@ otp7575(Config) when is_list(Config) ->
ok.
+otp8563(suite) -> [];
+otp8563(doc) -> ["OTP-8563"];
+otp8563(Config) when is_list(Config) ->
+ Val1 = 16#7fffffffffffffff,
+ io:format("try encode and decode ~w~n", [Val1]),
+ Enc1 = snmp_pdus:enc_value('Counter64', Val1),
+ {{'Counter64', Val1}, []} = snmp_pdus:dec_value(Enc1),
+
+ Val2 = Val1 + 1,
+ io:format("try encode and decode ~w~n", [Val2]),
+ Enc2 = snmp_pdus:enc_value('Counter64', Val2),
+ {{'Counter64', Val2}, []} = snmp_pdus:dec_value(Enc2),
+
+ Val3 = Val2 + 1,
+ io:format("try encode and decode ~w~n", [Val3]),
+ Enc3 = snmp_pdus:enc_value('Counter64', Val3),
+ {{'Counter64', Val3}, []} = snmp_pdus:dec_value(Enc3),
+
+ Val4 = 16#fffffffffffffffe,
+ io:format("try encode and decode ~w~n", [Val4]),
+ Enc4 = snmp_pdus:enc_value('Counter64', Val4),
+ {{'Counter64', Val4}, []} = snmp_pdus:dec_value(Enc4),
+
+ Val5 = Val4 + 1,
+ io:format("try encode and decode ~w~n", [Val5]),
+ Enc5 = snmp_pdus:enc_value('Counter64', Val5),
+ {{'Counter64', Val5}, []} = snmp_pdus:dec_value(Enc5),
+
+ Val6 = 16#ffffffffffffffff + 1,
+ io:format("try and fail to encode ~w~n", [Val6]),
+ case (catch snmp_pdus:enc_value('Counter64', Val6)) of
+ {'EXIT', {error, {bad_counter64, Val6}}} ->
+ ok;
+ Unexpected6 ->
+ exit({unexpected_encode_result, Unexpected6, Val6})
+ end,
+
+ Val7 = -1,
+ io:format("try and fail to encode ~w~n", [Val7]),
+ case (catch snmp_pdus:enc_value('Counter64', Val7)) of
+ {'EXIT', {error, {bad_counter64, Val7}}} ->
+ ok;
+ Unexpected7 ->
+ exit({unexpected_encode_result, Unexpected7, Val7})
+ end,
+
+ ok.
+
+
%%======================================================================
%% Internal functions
%%======================================================================
diff --git a/lib/snmp/test/snmp_test_data/OLD-SNMPEA-MIB.mib b/lib/snmp/test/snmp_test_data/OLD-SNMPEA-MIB.mib
index dd90d0ab50..2ba1a6fd67 100644
--- a/lib/snmp/test/snmp_test_data/OLD-SNMPEA-MIB.mib
+++ b/lib/snmp/test/snmp_test_data/OLD-SNMPEA-MIB.mib
@@ -12,18 +12,12 @@ OLD-SNMPEA-MIB DEFINITIONS ::= BEGIN
;
-- MODULE-IDENTITY
--- LAST-UPDATED "9709220900Z"
--- ORGANIZATION "ETX/DN/S"
--- CONTACT-INFO
--- " Martin Bj�rklund
---
--- Postal: ERICSSON SOFTWARE TECHNOLOGY AB
--- ERLANG SYSTEMS
--- Box 1214
--- S-164 28 KISTA, SWEDEN
---
--- Tel: +46 8 719 20 89
--- E-mail: [email protected]"
+-- LAST-UPDATED "1004200000Z"
+-- ORGANIZATION "Erlang/OTP"
+-- CONTACT-INFO ""
+-- DESCRIPTION
+-- "Header cleanup."
+-- REVISION "1004200000Z"
-- DESCRIPTION
-- "This MIB module defines MIB objects for the SNMPEA
-- component in OTP."
diff --git a/lib/snmp/test/snmp_test_data/OTP8574-MIB.mib b/lib/snmp/test/snmp_test_data/OTP8574-MIB.mib
new file mode 100644
index 0000000000..b5e5ed1848
--- /dev/null
+++ b/lib/snmp/test/snmp_test_data/OTP8574-MIB.mib
@@ -0,0 +1,77 @@
+OTP8574-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, enterprises, IpAddress FROM SNMPv2-SMI
+ RowStatus FROM SNMPv2-TC
+ ;
+
+otp8574MIB MODULE-IDENTITY
+ LAST-UPDATED "1004200000Z"
+ ORGANIZATION "Erlang/OTP"
+ CONTACT-INFO "www.erlang.org"
+ DESCRIPTION "The MIB module is used for testing a compiler feature"
+ ::= { otpSnmp 1 }
+
+ericsson OBJECT IDENTIFIER ::= { enterprises 193 }
+otp OBJECT IDENTIFIER ::= { ericsson 19 }
+otpApplications OBJECT IDENTIFIER ::= { otp 3 }
+otpSnmp OBJECT IDENTIFIER ::= { otpApplications 3 }
+
+testMIBObjects OBJECT IDENTIFIER ::= { otp8574MIB 1 }
+
+testMIBObjectGroup OBJECT IDENTIFIER ::= { testMIBObjects 1 }
+
+example-Table OBJECT-TYPE
+ SYNTAX SEQUENCE OF ExampleEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION "An example table"
+ ::= { testMIBObjectGroup 1 }
+
+example-Entry OBJECT-TYPE
+ SYNTAX ExampleEntry
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION "Example table entry"
+ INDEX { exampleIndex }
+ ::= { example-Table 5 }
+
+ExampleEntry ::= SEQUENCE {
+ exampleIndex INTEGER,
+ exampleColumn OCTET STRING,
+ exampleNotAccessible OCTET STRING,
+ exampleRowStatus RowStatus
+}
+
+exampleIndex OBJECT-TYPE
+ SYNTAX INTEGER (1..100)
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION "The index for this entry."
+ ::= { example-Entry 1 }
+
+exampleColumn OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Example table column"
+ ::= { example-Entry 2 }
+
+exampleNotAccessible OBJECT-TYPE
+ SYNTAX OCTET STRING
+ MAX-ACCESS not-accessible
+ STATUS current
+ DESCRIPTION
+ "Example table column"
+ ::= { example-Entry 3 }
+
+exampleRowStatus OBJECT-TYPE
+ SYNTAX RowStatus
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ "Example table RowStatus"
+ ::= { example-Entry 4 }
+
+END
diff --git a/lib/snmp/test/snmp_test_data/OTP8595-MIB.mib b/lib/snmp/test/snmp_test_data/OTP8595-MIB.mib
new file mode 100644
index 0000000000..23245bce37
--- /dev/null
+++ b/lib/snmp/test/snmp_test_data/OTP8595-MIB.mib
@@ -0,0 +1,45 @@
+OTP8595-MIB DEFINITIONS ::= BEGIN
+
+IMPORTS
+ MODULE-IDENTITY, OBJECT-TYPE, snmpModules, mib-2
+ FROM SNMPv2-SMI
+ DisplayString, TestAndIncr, TimeStamp, RowStatus, TruthValue,
+ TEXTUAL-CONVENTION
+ FROM SNMPv2-TC
+ MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP
+ FROM SNMPv2-CONF
+ sysLocation, sysContact
+ FROM SNMPv2-MIB
+ ;
+
+otp8595MIB MODULE-IDENTITY
+ LAST-UPDATED "1004210000Z"
+ ORGANIZATION ""
+ CONTACT-INFO
+ ""
+ DESCRIPTION
+ "Test mib for OTP-8595"
+ ::= { snmpModules 1 }
+
+
+test OBJECT IDENTIFIER ::= { mib-2 15 }
+
+bits1 OBJECT-TYPE
+ SYNTAX BITS {
+ b0(0),
+ b1(1),
+ b2(2),
+ -- The following are extensions to the original set of
+ -- labels. The extensions start at an octet boundary.
+ -- So for bits 3 - 7, one MUST set them to zero on send
+ -- and one MUST ignore them on receipt.
+ b8(8),
+ b9(9)
+ }
+ MAX-ACCESS read-write
+ STATUS current
+ DESCRIPTION
+ ""
+ ::= { test 1 }
+
+END
diff --git a/lib/snmp/test/snmp_test_lib.erl b/lib/snmp/test/snmp_test_lib.erl
index 2586b66a13..54839d989b 100644
--- a/lib/snmp/test/snmp_test_lib.erl
+++ b/lib/snmp/test/snmp_test_lib.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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%
%%
@@ -172,7 +172,17 @@ os_based_skip(Skippable) when is_list(Skippable) ->
{value, {OsFam, OsName}} ->
true;
{value, {OsFam, OsNames}} when is_list(OsNames) ->
- lists:member(OsName, OsNames);
+ case lists:member(OsName, OsNames) of
+ true ->
+ true;
+ false ->
+ case lists:keymember(OsName, 1, OsNames) of
+ {value, {OsName, Check}} when is_function(Check) ->
+ Check();
+ _ ->
+ false
+ end
+ end;
_ ->
false
end
diff --git a/lib/snmp/test/snmp_test_mgr_misc.erl b/lib/snmp/test/snmp_test_mgr_misc.erl
index e6220f9241..ef1ba0b948 100644
--- a/lib/snmp/test/snmp_test_mgr_misc.erl
+++ b/lib/snmp/test/snmp_test_mgr_misc.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -101,8 +101,8 @@ init_packet(Parent, SnmpMgr,
init_debug(Dbg) when is_atom(Dbg) ->
put(debug,Dbg),
- put(verbosity,silence);
- %% put(verbosity,trace);
+ %% put(verbosity, silence);
+ put(verbosity, trace);
init_debug(DbgOptions) when is_list(DbgOptions) ->
case lists:keysearch(debug, 1, DbgOptions) of
{value, {_, Dbg}} when is_atom(Dbg) ->
diff --git a/lib/snmp/vsn.mk b/lib/snmp/vsn.mk
index 50c72b33b5..4ca1fb7901 100644
--- a/lib/snmp/vsn.mk
+++ b/lib/snmp/vsn.mk
@@ -17,160 +17,41 @@
#
# %CopyrightEnd%
-SNMP_VSN = 4.16.1
+SNMP_VSN = 4.17
PRE_VSN =
APP_VSN = "snmp-$(SNMP_VSN)$(PRE_VSN)"
-TICKETS = OTP-8480 OTP-8481
+TICKETS = OTP-8478
+
+TICKETS_4_16_2 = \
+ OTP-8563 \
+ OTP-8574 \
+ OTP-8594 \
+ OTP-8595 \
+ OTP-8646 \
+ OTP-8648
+
+TICKETS_4_16_1 = \
+ OTP-8480 \
+ OTP-8481
TICKETS_4_16 = \
OTP-8395 \
OTP-8433 \
OTP-8442
-TICKETS_4_15 = OTP-8229 OTP-8249
-
-TICKETS_4_14 = OTP-8223 OTP-8228 OTP-8237
-
-TICKETS_4_13_5 = OTP-8116 OTP-8120 OTP-8181 OTP-8182
-
-TICKETS_4_13_4 = OTP-8044 OTP-8062 OTP-8098
-
-TICKETS_4_13_3 = OTP-8015 OTP-8020
-
-TICKETS_4_13_2 = OTP-7961 OTP-7977 OTP-7983 OTP-7989
-
-TICKETS_4_13_1 = OTP-7902
-
-TICKETS_4_13 = OTP-7571 OTP-7735 OTP-7836 OTP-7851
-
-TICKETS_4_12_2 = OTP-7868
-
-TICKETS_4_12_1 = OTP-7695 OTP-7698
-
-TICKETS_4_12 = OTP-7346 OTP-7525
-
-TICKETS_4_11_2 = OTP-7570 OTP-7575
-
-TICKETS_4_11_1 = OTP-7390 OTP-7412 OTP-7426 OTP-7432
-
-TICKETS_4_11 = OTP-7201 OTP-7287 OTP-7319 OTP-7369 OTP-7371 OTP-7377 OTP-7381
-
-TICKETS_4_10_3 = OTP-7219
-
-TICKETS_4_10_2 = OTP-7152 OTP-7153 OTP-7157 OTP-7158 OTP-7159 OTP-7160
-
-TICKETS_4_10_1 = OTP-7083 OTP-7109 OTP-7110 OTP-7119 OTP-7121 OTP-7123
-
-TICKETS_4_10 = OTP-6649 OTP-6841 OTP-6898 OTP-6945
-
-TICKETS_4_9_6 = OTP-6840 OTP-6843
-
-TICKETS_4_9_5 = OTP-6805 OTP-6815
-
-TICKETS_4_9_4 = OTP-6784 OTP-6771
-
-TICKETS_4_9_3 = OTP-6605 OTP-6712 OTP-6713
-
-TICKETS_4_9_2 = OTP-6571
-
-TICKETS_4_9_1 = OTP-6566 OTP-6569
-
-TICKETS_4_9 = \
- OTP-6317 \
- OTP-6318 \
- OTP-6383 \
- OTP-6487 \
- OTP-6515 \
- OTP-6518 \
- OTP-6529 \
- OTP-6532 \
- OTP-6533 \
- OTP-6540
-
-TICKETS_4_8_4 = OTP-6408
-
-TICKETS_4_8_3 = OTP-6337 OTP-6340
-
-TICKETS_4_8_2 = OTP-6214 OTP-6247 OTP-6293
-
-TICKETS_4_8_1 = OTP-6176 OTP-6177
-
-TICKETS_4_8 = OTP-6137 OTP-6149 OTP-6150 OTP-6164
-
-TICKETS_4_7_4 = \
- OTP-6042 \
- OTP-6044 \
- OTP-6049 \
- OTP-6062 \
- OTP-6068 \
- OTP-6074 \
- OTP-6077 \
- OTP-6081
-
-TICKETS_4_7_3 = \
- OTP-6031 \
- OTP-6032
-
-TICKETS_4_7_2 = \
- OTP-5992 \
- OTP-6024
-
-TICKETS_4_7_1 = \
- OTP-5963 \
- OTP-5968 \
- OTP-5969
-
-TICKETS_4_7 = \
- OTP-5870 \
- OTP-5934 \
- OTP-5935 \
- OTP-5937
-
-TICKETS_4_6_1 = \
- OTP-5834 \
- OTP-5838
-
-TICKETS_4_6 = \
- OTP-5763 \
- OTP-5771 \
- OTP-5787 \
- OTP-5797 \
- OTP-5829
-
-TICKETS_4_5 = \
- OTP-5581 \
- OTP-5726 \
- OTP-5727 \
- OTP-5732 \
- OTP-5733 \
- OTP-5740 \
- OTP-5742
-
-TICKETS_4_4_1 = \
- OTP-5719 \
- OTP-5720
-
-TICKETS_4_4 = \
- OTP-5666 \
- OTP-5668 \
- OTP-5669 \
- OTP-5675 \
- OTP-5676 \
- OTP-5678 \
- OTP-5703
+TICKETS_4_15 = \
+ OTP-8229 \
+ OTP-8249
-TICKETS_4_3 = \
- OTP-5636 \
- OTP-5637 \
- OTP-5490
+TICKETS_4_14 = \
+ OTP-8223 \
+ OTP-8228 \
+ OTP-8237
-TICKETS_4_2 = \
- OTP-5574 \
- OTP-5578 \
- OTP-5579 \
- OTP-5580 \
- OTP-5590 \
- OTP-5591 \
- OTP-5592
+TICKETS_4_13_5 = \
+ OTP-8116 \
+ OTP-8120 \
+ OTP-8181 \
+ OTP-8182
diff --git a/lib/ssh/Makefile b/lib/ssh/Makefile
index 1ad69a9ca1..b8c7eebcc1 100644
--- a/lib/ssh/Makefile
+++ b/lib/ssh/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2004-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2004-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%
#
diff --git a/lib/ssh/doc/src/book.xml b/lib/ssh/doc/src/book.xml
index 0375c441af..fcec1d6f70 100644
--- a/lib/ssh/doc/src/book.xml
+++ b/lib/ssh/doc/src/book.xml
@@ -4,7 +4,7 @@
<book xmlns:xi="http://www.w3.org/2001/XInclude">
<header titlestyle="normal">
<copyright>
- <year>2005</year><year>2009</year>
+ <year>2005</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>SSH</title>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 0ce83fa5d1..7c8735cf56 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -29,7 +29,7 @@
<file>notes.xml</file>
</header>
- <section><title>Ssh 1.1.9</title>
+ <section><title>Ssh 2.0</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
@@ -37,6 +37,18 @@
<p>The function ssh:connect/4 was not exported.</p>
<p>Own Id: OTP-8550 Aux Id:</p>
</item>
+ <item>
+ <p>Aligned error message with used version (SSH_FX_FAILURE vs
+ SSH_FX_NOT_A_DIRECTORY, the latter introduced in version 6).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-8644 Aux Id: seq11574</p>
+ </item>
+ <item>
+ <p>Resolved race condition when another connection is started
+ before a channel is opened in the first connection.</p>
+ <p>Own Id: OTP-8645 Aux Id: seq11577</p>
+ </item>
</list>
</section>
@@ -46,6 +58,8 @@
<p>The configuration parameter ip_v6_disabled is now available,
which makes it possible for the user to alter the IP version
SSH shall use.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
<p>Own Id: OTP-8535 Aux Id:</p>
</item>
<item>
@@ -57,6 +71,13 @@
message is not handled correctly.</p>
<p>Own Id: OTP-8524 Aux Id:</p>
</item>
+ <item>
+ <p>Removed deprecated modules (ssh_ssh, ssh_sshd and ssh_cm) and
+ functions (ssh_sftp:connect and ssh_sftp:stop).</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>Own Id: OTP-8596 Aux Id:</p>
+ </item>
</list>
</section>
diff --git a/lib/ssh/doc/src/ref_man.xml b/lib/ssh/doc/src/ref_man.xml
index c05c3051b0..9ab56b28ec 100644
--- a/lib/ssh/doc/src/ref_man.xml
+++ b/lib/ssh/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>SSH Reference Manual</title>
diff --git a/lib/ssh/doc/src/ssh_sftp.xml b/lib/ssh/doc/src/ssh_sftp.xml
index 208b2b4e72..c1f75461b1 100644
--- a/lib/ssh/doc/src/ssh_sftp.xml
+++ b/lib/ssh/doc/src/ssh_sftp.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2005</year><year>2009</year>
+ <year>2005</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>ssh_sftp</title>
diff --git a/lib/ssh/doc/src/ssh_sftpd.xml b/lib/ssh/doc/src/ssh_sftpd.xml
index c857983565..b3d64e72b4 100644
--- a/lib/ssh/doc/src/ssh_sftpd.xml
+++ b/lib/ssh/doc/src/ssh_sftpd.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2005</year><year>2009</year>
+ <year>2005</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>ssh_sftpd</title>
diff --git a/lib/ssh/examples/Makefile b/lib/ssh/examples/Makefile
index cd8b3c797a..5f17542fb8 100644
--- a/lib/ssh/examples/Makefile
+++ b/lib/ssh/examples/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2005-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2005-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%
#
diff --git a/lib/ssh/examples/ssh_sample_cli.erl b/lib/ssh/examples/ssh_sample_cli.erl
index 2505c1b759..6f3092e567 100644
--- a/lib/ssh/examples/ssh_sample_cli.erl
+++ b/lib/ssh/examples/ssh_sample_cli.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
@@ -30,19 +30,15 @@
%% our command functions
-export([cli_prime/1, cli_primes/1, cli_gcd/2, cli_lcm/2,
cli_factors/1, cli_exit/0, cli_rho/1, cli_help/0,
- cli_crash/0, cli_users/0, cli_self/0,
- cli_user/0, cli_host/0]).
-
-%% imports
--import(lists, [reverse/1, reverse/2, seq/2, prefix/2]).
--import(math, [sqrt/1]).
-
+ cli_crash/0, cli_self/0, cli_user/0, cli_host/0]).
listen(Port) ->
listen(Port, []).
listen(Port, Options) ->
- ssh_cli:listen(fun(U, H) -> start_our_shell(U, H) end, Port, Options).
+ crypto:start(),
+ ssh:start(),
+ ssh:daemon(any, Port, [{shell, fun(U, H) -> start_our_shell(U, H) end} | Options]).
%% our_routines
our_routines() ->
@@ -56,7 +52,6 @@ our_routines() ->
{"prime", cli_prime, "<int> check for primality"},
{"primes", cli_primes, "<int> print all primes up to <int>"},
{"rho", cli_rho, "<int> prime factors using rho's alg."},
- {"who", cli_users, " lists users"},
{"user", cli_user, " print name of user"},
{"host", cli_host, " print host addr"},
{"self", cli_self, " print my pid"}
@@ -85,11 +80,11 @@ our_routines() ->
common_prefix([C | R1], [C | R2], Acc) ->
common_prefix(R1, R2, [C | Acc]);
common_prefix(_, _, Acc) ->
- reverse(Acc).
+ lists:reverse(Acc).
%% longest prefix in a list, given a prefix
longest_prefix(List, Prefix) ->
- case [A || {A, _, _} <- List, prefix(Prefix, A)] of
+ case [A || {A, _, _} <- List, lists:prefix(Prefix, A)] of
[] ->
{none, List};
[S | Rest] ->
@@ -112,7 +107,7 @@ longest_prefix(List, Prefix) ->
expand([$ | _]) ->
{no, "", []};
expand(RevBefore) ->
- Before = reverse(RevBefore),
+ Before = lists:reverse(RevBefore),
case longest_prefix(our_routines(), Before) of
{prefix, P, [_]} ->
{yes, P ++ " ", []};
@@ -139,22 +134,27 @@ start_our_shell(User, Peer) ->
%%% an ordinary Read-Eval-Print-loop
our_shell_loop() ->
% Read
- Line = io:get_line({format, "CLI> ", []}),
+ Line = io:get_line("CLI> "),
% Eval
Result = eval_cli(Line),
% Print
io:format("---> ~p\n", [Result]),
case Result of
- done -> exit(normal);
- crash -> 1 / 0;
- _ -> our_shell_loop()
+ done ->
+ exit(normal);
+ crash ->
+ 1 / 0;
+ _ ->
+ our_shell_loop()
end.
%%% translate a command to a function
command_to_function(Command) ->
case lists:keysearch(Command, 1, our_routines()) of
- {value, {_, Proc, _}} -> Proc;
- false -> unknown_cli
+ {value, {_, Proc, _}} ->
+ Proc;
+ false ->
+ unknown_cli
end.
%%% evaluate a command line
@@ -209,14 +209,6 @@ cli_user() ->
cli_host() ->
get(peer_name).
-cli_users() ->
- case ssh_userauth:get_auth_users() of
- {ok, UsersPids} ->
- UsersPids; % [U || {U, _} <- UsersPids];
- E ->
- E
- end.
-
cli_self() ->
self().
@@ -243,7 +235,7 @@ cli_help() ->
%% a quite simple Sieve of Erastothenes (not tail-recursive, though)
primes(Size) ->
- era(sqrt(Size), seq(2,Size)).
+ era(math:sqrt(Size), lists:seq(2,Size)).
era(Max, [H|T]) when H =< Max ->
[H | era(Max, sieve([H|T], H))];
@@ -267,7 +259,7 @@ next_prime(Primes, P) ->
next_prime1(Primes, P) ->
P1 = P + 2,
- case divides(Primes, trunc(sqrt(P1)), P1) of
+ case divides(Primes, trunc(math:sqrt(P1)), P1) of
false -> P1;
true -> next_prime1(Primes, P1)
end.
@@ -282,7 +274,7 @@ divides([_ | R], Nsqrt, N) ->
divides(R, Nsqrt, N).
is_prime(P) ->
- lists:all(fun(A) -> P rem A =/= 0 end, primes(trunc(sqrt(P)))).
+ lists:all(fun(A) -> P rem A =/= 0 end, primes(trunc(math:sqrt(P)))).
%% Normal gcd, Euclid
gcd(R, Q) when abs(Q) < abs(R) -> gcd1(Q,R);
@@ -300,17 +292,17 @@ lcm(R, Q) ->
%%% Prime factors of a number (na�ve implementation)
factors(N) ->
- Nsqrt = trunc(sqrt(N)),
+ Nsqrt = trunc(math:sqrt(N)),
factors([], N, 2, Nsqrt, []).
factors(_Primes, N, Prime, Nsqrt, Factors) when Prime > Nsqrt ->
- reverse(Factors, [N]);
+ lists:reverse(Factors, [N]);
factors(Primes, N, Prime, Nsqrt, Factors) ->
case N rem Prime of
0 ->
%%io:format("factor ------- ~p\n", [Prime]),
N1 = N div Prime,
- factors(Primes, N1, Prime, trunc(sqrt(N1)), [Prime|Factors]);
+ factors(Primes, N1, Prime, trunc(math:sqrt(N1)), [Prime|Factors]);
_ ->
Primes1 = Primes ++ [Prime],
Prime1 = next_prime(Primes1, Prime),
diff --git a/lib/ssh/src/Makefile b/lib/ssh/src/Makefile
index 7abf06e52b..42880fa80b 100644
--- a/lib/ssh/src/Makefile
+++ b/lib/ssh/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2004-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 2004-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%
#
@@ -56,7 +56,6 @@ MODULES= \
ssh_auth\
ssh_bits \
ssh_cli \
- ssh_cm \
ssh_dsa \
ssh_file \
ssh_io \
@@ -67,8 +66,6 @@ MODULES= \
ssh_sftpd \
ssh_sftpd_file\
ssh_sftpd_file_api \
- ssh_ssh \
- ssh_sshd \
ssh_transport \
ssh_userreg \
ssh_xfer
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 9319f39591..8a3e15841f 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -14,7 +14,6 @@
ssh_cli,
ssh_channel,
ssh_channel_sup,
- ssh_cm,
ssh_connection,
ssh_connection_handler,
ssh_connection_manager,
@@ -32,8 +31,6 @@
ssh_sftpd,
ssh_sftpd_file,
ssh_sftpd_file_api,
- ssh_ssh,
- ssh_sshd,
ssh_subsystem_sup,
ssh_sup,
ssh_system_sup,
diff --git a/lib/ssh/src/ssh.appup.src b/lib/ssh/src/ssh.appup.src
index b082d66988..21f7508555 100644
--- a/lib/ssh/src/ssh.appup.src
+++ b/lib/ssh/src/ssh.appup.src
@@ -19,32 +19,8 @@
{"%VSN%",
[
- {"1.1.8", [{load_module, ssh_connection_manager, soft_purge, soft_purge, []},
- {load_module, ssh, soft_purge, soft_purge, []},
- {load_module, ssh_connection, soft_purge, soft_purge, []}]},
- {"1.1.7", [{load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh_connection, soft_purge, soft_purge, []},
- {load_module, ssh, soft_purge, soft_purge, []},
- {load_module, ssh_connection_manager, soft_purge, soft_purge, []}]},
- {"1.1.6", [{restart_application, ssh}]},
- {"1.1.5", [{restart_application, ssh}]},
- {"1.1.4", [{restart_application, ssh}]},
- {"1.1.3", [{restart_application, ssh}]},
- {"1.1.2", [{restart_application, ssh}]}
],
[
- {"1.1.8", [{load_module, ssh_connection_manager, soft_purge, soft_purge, []},
- {load_module, ssh, soft_purge, soft_purge, []},
- {load_module, ssh_connection, soft_purge, soft_purge, []}]},
- {"1.1.7", [{load_module, ssh_connection_handler, soft_purge, soft_purge, []},
- {load_module, ssh_connection, soft_purge, soft_purge, []},
- {load_module, ssh, soft_purge, soft_purge, []},
- {load_module, ssh_connection_manager, soft_purge, soft_purge, []}]},
- {"1.1.6", [{restart_application, ssh}]},
- {"1.1.5", [{restart_application, ssh}]},
- {"1.1.4", [{restart_application, ssh}]},
- {"1.1.3", [{restart_application, ssh}]},
- {"1.1.2", [{restart_application, ssh}]}
]
}.
diff --git a/lib/ssh/src/ssh.hrl b/lib/ssh/src/ssh.hrl
index 0e4285295c..ac249b05e3 100644
--- a/lib/ssh/src/ssh.hrl
+++ b/lib/ssh/src/ssh.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
diff --git a/lib/ssh/src/ssh_acceptor.erl b/lib/ssh/src/ssh_acceptor.erl
index d19fee14e1..9060626ab3 100644
--- a/lib/ssh/src/ssh_acceptor.erl
+++ b/lib/ssh/src/ssh_acceptor.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -79,11 +79,11 @@ acceptor_loop(Callback, Port, Address, Opts, ListenSocket, AcceptTimeout) ->
handle_connection(Callback, Address, Port, Options, Socket) ->
SystemSup = ssh_system_sup:system_supervisor(Address, Port),
- ssh_system_sup:start_subsystem(SystemSup, Options),
+ {ok, SubSysSup} = ssh_system_sup:start_subsystem(SystemSup, Options),
ConnectionSup = ssh_system_sup:connection_supervisor(SystemSup),
{ok, Pid} =
ssh_connection_controler:start_manager_child(ConnectionSup,
- [server, Socket, Options]),
+ [server, Socket, Options, SubSysSup]),
Callback:controlling_process(Socket, Pid),
SshOpts = proplists:get_value(ssh_opts, Options),
Pid ! {start_connection, server, [Address, Port, Socket, SshOpts]}.
diff --git a/lib/ssh/src/ssh_acceptor_sup.erl b/lib/ssh/src/ssh_acceptor_sup.erl
index 707e3d3a5e..f37e1fe4ff 100644
--- a/lib/ssh/src/ssh_acceptor_sup.erl
+++ b/lib/ssh/src/ssh_acceptor_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_app.erl b/lib/ssh/src/ssh_app.erl
index 5793d3a321..38659b1a2d 100644
--- a/lib/ssh/src/ssh_app.erl
+++ b/lib/ssh/src/ssh_app.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl
index aa74528544..9dbd95886e 100644
--- a/lib/ssh/src/ssh_auth.erl
+++ b/lib/ssh/src/ssh_auth.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_auth.hrl b/lib/ssh/src/ssh_auth.hrl
index 80c5a6819b..7d7bad4436 100644
--- a/lib/ssh/src/ssh_auth.hrl
+++ b/lib/ssh/src/ssh_auth.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_bits.erl b/lib/ssh/src/ssh_bits.erl
index 21ddc5e8fe..399581a0fd 100755
--- a/lib/ssh/src/ssh_bits.erl
+++ b/lib/ssh/src/ssh_bits.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_channel.erl b/lib/ssh/src/ssh_channel.erl
index 3d67065ee1..8a49a44a6c 100644
--- a/lib/ssh/src/ssh_channel.erl
+++ b/lib/ssh/src/ssh_channel.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_channel_sup.erl b/lib/ssh/src/ssh_channel_sup.erl
index c184fed627..0093bce9c2 100644
--- a/lib/ssh/src/ssh_channel_sup.erl
+++ b/lib/ssh/src/ssh_channel_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_cli.erl b/lib/ssh/src/ssh_cli.erl
index 964f35121a..e3b6ffa125 100644
--- a/lib/ssh/src/ssh_cli.erl
+++ b/lib/ssh/src/ssh_cli.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
@@ -327,7 +327,7 @@ window_change(Tty, OldTty, Buf)
{[], Buf};
window_change(Tty, OldTty, {Buf, BufTail, Col}) ->
M1 = move_cursor(Col, 0, OldTty),
- N = max(Tty#ssh_pty.width - OldTty#ssh_pty.width, 0) * 2,
+ N = erlang:max(Tty#ssh_pty.width - OldTty#ssh_pty.width, 0) * 2,
S = lists:reverse(Buf, [BufTail | lists:duplicate(N, $ )]),
M2 = move_cursor(length(Buf) + length(BufTail) + N, Col, Tty),
{[M1, S | M2], {Buf, BufTail, Col}}.
@@ -398,10 +398,6 @@ nthtail(0, A) -> A;
nthtail(N, [_ | A]) when N > 0 -> nthtail(N-1, A);
nthtail(_, _) -> [].
-%%% utils
-max(A, B) when A > B -> A;
-max(_A, B) -> B.
-
ifelse(Cond, A, B) ->
case Cond of
true -> A;
@@ -428,7 +424,7 @@ start_shell(ConnectionManager, State) ->
{ok, User} =
ssh_userreg:lookup_user(ConnectionManager),
{ok, PeerAddr} =
- ssh_cm:get_peer_addr(ConnectionManager),
+ ssh_connection_manager:peer_addr(ConnectionManager),
fun() -> Shell(User, PeerAddr) end;
_ ->
Shell
diff --git a/lib/ssh/src/ssh_cm.erl b/lib/ssh/src/ssh_cm.erl
deleted file mode 100755
index c4d535df9a..0000000000
--- a/lib/ssh/src/ssh_cm.erl
+++ /dev/null
@@ -1,237 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-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%
-%%
-
-%%
-
-%%% Description : Backwards compatibility wrapper
-
--module(ssh_cm).
-
--include("ssh.hrl").
--include("ssh_connect.hrl").
-
-%% -define(DEFAULT_PACKET_SIZE, 32768).
-%% -define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE).
-%%-define(DEFAULT_TIMEOUT, 5000).
-
--export([connect/1, connect/2, connect/3]).
--export([listen/2, listen/3, listen/4, stop_listener/1]).
--export([stop/1]).
-
--deprecated({connect, 1, next_major_release}).
--deprecated({connect, 2, next_major_release}).
--deprecated({connect, 3, next_major_release}).
--deprecated({listen, 2, next_major_release}).
--deprecated({listen, 3, next_major_release}).
--deprecated({listen, 4, next_major_release}).
--deprecated({stop_listener, 1, next_major_release}).
--deprecated({stop, 1, next_major_release}).
-
--export([adjust_window/3, attach/2, attach/3, detach/2,
- tcpip_forward/3, cancel_tcpip_forward/3, direct_tcpip/6,
- direct_tcpip/8, close/2, shell/2, exec/4,
- send/3, send/4,
- send_ack/3, send_ack/4, send_ack/5, send_eof/2,
- session_open/2, session_open/4, subsystem/4,
- open_pty/3, open_pty/7, open_pty/9,
- set_user_ack/4,
- setenv/5, signal/3, winch/4]).
-
--deprecated({adjust_window, 3, next_major_release}).
--deprecated({attach, 2, next_major_release}).
--deprecated({attach, 3, next_major_release}).
--deprecated({detach, 2, next_major_release}).
--deprecated({tcpip_forward, 3, next_major_release}).
--deprecated({cancel_tcpip_forward, 3, next_major_release}).
--deprecated({direct_tcpip, 6, next_major_release}).
--deprecated({direct_tcpip, 8, next_major_release}).
--deprecated({close, 2, next_major_release}).
--deprecated({shell, 2, next_major_release}).
--deprecated({exec, 4, next_major_release}).
--deprecated({send, 3, next_major_release}).
--deprecated({send, 4, next_major_release}).
--deprecated({send_ack, 3, next_major_release}).
--deprecated({send_ack, 4, next_major_release}).
--deprecated({send_ack, 5, next_major_release}).
--deprecated({send_eof, 2, next_major_release}).
--deprecated({session_open, 2, next_major_release}).
--deprecated({session_open, 4, next_major_release}).
--deprecated({subsystem, 4, next_major_release}).
--deprecated({open_pty, 3, next_major_release}).
--deprecated({open_pty, 7, next_major_release}).
--deprecated({open_pty, 9, next_major_release}).
--deprecated({set_user_ack, 4, next_major_release}).
--deprecated({setenv, 5, next_major_release}).
--deprecated({signal, 3, next_major_release}).
--deprecated({winch, 4, next_major_release}).
-
--export([info/1, info/2, recv_window/3,
- send_window/3, renegotiate/1, renegotiate/2,
- get_peer_addr/1]).
-
-%%====================================================================
-%% API
-%%====================================================================
-connect(Host) ->
- connect(Host, []).
-connect(Host, Opts) ->
- connect(Host, ?SSH_DEFAULT_PORT, Opts).
-connect(Host, Port, Opts) ->
- ssh:connect(Host, Port, Opts).
-
-listen(ChannelSpec, Port) ->
- listen(ChannelSpec, Port, []).
-listen(ChannelSpec, Port, Opts) ->
- listen(ChannelSpec, any, Port, Opts).
-listen(ChannelSpec, "localhost", Port, Opts) ->
- listen(ChannelSpec, any, Port, Opts);
-listen(_ChannelSpec, Host, Port, Opts) ->
- ssh:daemon(Host, Port, Opts).
-
-stop_listener(SysSup) ->
- ssh_system_sup:stop_listener(SysSup).
-stop(Cm) ->
- ssh:close(Cm).
-
-%% CM Client commands
-session_open(Cm, Timeout) ->
- session_open(Cm, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
-
-session_open(Cm, InitialWindowSize, MaxPacketSize, Timeout) ->
- ssh_connection:session_channel(Cm, InitialWindowSize, MaxPacketSize,
- Timeout).
-
-
-setenv(Cm, Channel, Var, Value, Timeout) ->
- ssh_connection:setenv(Cm, Channel, Var, Value, Timeout).
-
-shell(Cm, Channel) ->
- ssh_connection:shell(Cm, Channel).
-
-exec(Cm, Channel, Command, Timeout) ->
- ssh_connection:exec(Cm, Channel, Command, Timeout).
-
-subsystem(Cm, Channel, SubSystem, Timeout) ->
- ssh_connection:subsystem(Cm, Channel, SubSystem, Timeout).
-
-%% Not needed for backwards compatibility for now
-attach(_Cm, _Timeout) ->
- ok.
-
-attach(_Cm, _ChannelPid, _Timeout) ->
- ok.
-
-detach(_Cm, _Timeout) ->
- ok.
-
-%% Not needed, send_ack is now call! Temp backwardcompability
-set_user_ack(_, _, _, _) ->
- ok.
-
-adjust_window(Cm, Channel, Bytes) ->
- ssh_connection:adjust_window(Cm, Channel, Bytes).
-
-close(Cm, Channel) ->
- ssh_connection:close(Cm, Channel).
-
-send_eof(Cm, Channel) ->
- ssh_connection:send_eof(Cm, Channel).
-
-send(Cm, Channel, Data) ->
- ssh_connection:send(Cm, Channel, 0, Data).
-
-send(Cm, Channel, Type, Data) ->
- ssh_connection:send(Cm, Channel, Type, Data).
-
-%% Send ack is not needed
-send_ack(Cm, Channel, Data) ->
- send_ack(Cm, Channel, 0, Data, infinity).
-
-send_ack(Cm, Channel, Type, Data) ->
- send_ack(Cm, Channel, Type, Data, infinity).
-
-send_ack(Cm, Channel, Type, Data, Timeout) ->
- ssh_connection:send(Cm, Channel, Type, Data, Timeout).
-
-%% ----------------------------------------------------------------------
-%% These functions replacers are not officially supported but proably will be
-%% when we had time to test them.
-%% ----------------------------------------------------------------------
-direct_tcpip(Cm, RemoteHost, RemotePort, OrigIP, OrigPort, Timeout) ->
- direct_tcpip(Cm, RemoteHost, RemotePort, OrigIP, OrigPort,
- ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, Timeout).
-
-direct_tcpip(Cm, RemoteIP, RemotePort, OrigIP, OrigPort,
- InitialWindowSize, MaxPacketSize, Timeout) ->
- ssh_connection:direct_tcpip(Cm, RemoteIP, RemotePort,
- OrigIP, OrigPort,
- InitialWindowSize,
- MaxPacketSize, Timeout).
-
-tcpip_forward(Cm, BindIP, BindPort) ->
- ssh_connection:tcpip_forward(Cm, BindIP, BindPort).
-
-cancel_tcpip_forward(Cm, BindIP, Port) ->
- ssh_connection:cancel_tcpip_forward(Cm, BindIP, Port).
-
-open_pty(Cm, Channel, Timeout) ->
- open_pty(Cm, Channel, os:getenv("TERM"), 80, 24, [], Timeout).
-
-open_pty(Cm, Channel, Term, Width, Height, PtyOpts, Timeout) ->
- open_pty(Cm, Channel, Term, Width, Height, 0, 0, PtyOpts, Timeout).
-
-open_pty(Cm, Channel, Term, Width, Height, PixWidth, PixHeight,
- PtyOpts, Timeout) ->
- ssh_connection:open_pty(Cm, Channel, Term,
- Width, Height, PixWidth,
- PixHeight, PtyOpts, Timeout).
-winch(Cm, Channel, Width, Height) ->
- winch(Cm, Channel, Width, Height, 0, 0).
-winch(Cm, Channel, Width, Height, PixWidth, PixHeight) ->
- ssh_connection:window_change(Cm, Channel, Width,
- Height, PixWidth, PixHeight).
-signal(Cm, Channel, Sig) ->
- ssh_connection:signal(Cm, Channel, Sig).
-
-%% ----------------------------------------------------------------------
-%% These functions replacers are not officially supported and
-%% the format of them will proably change when and
-%% if they get supported.
-%% ----------------------------------------------------------------------
-info(Cm) ->
- info(Cm, all).
-
-info(Cm, ChannelPid) ->
- ssh_connection_manager:info(Cm, ChannelPid).
-
-send_window(Cm, Channel, Timeout) ->
- ssh_connection_manager:send_window(Cm, Channel, Timeout).
-
-recv_window(Cm, Channel, Timeout) ->
- ssh_connection_manager:recv_window(Cm, Channel, Timeout).
-
-renegotiate(Cm) ->
- renegotiate(Cm, []).
-renegotiate(Cm, _Opts) ->
- %%TODO: How should this work, backwards compat?
- ssh_connection_manager:renegotiate(Cm).
-
-get_peer_addr(Cm) ->
- ssh_connection_manager:peer_addr(Cm).
-
diff --git a/lib/ssh/src/ssh_connect.hrl b/lib/ssh/src/ssh_connect.hrl
index a5a4e42cd8..34d4ff8fc1 100755
--- a/lib/ssh/src/ssh_connect.hrl
+++ b/lib/ssh/src/ssh_connect.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
@@ -260,5 +260,6 @@
address,
port,
options,
- exec
+ exec,
+ sub_system_supervisor
}).
diff --git a/lib/ssh/src/ssh_connection.erl b/lib/ssh/src/ssh_connection.erl
index b9827c90ea..7b9e9185bf 100644
--- a/lib/ssh/src/ssh_connection.erl
+++ b/lib/ssh/src/ssh_connection.erl
@@ -946,13 +946,12 @@ encode_ip(Addr) when is_list(Addr) ->
end
end.
-start_channel(Address, Port, Cb, Id, Args) ->
- start_channel(Address, Port, Cb, Id, Args, undefined).
+start_channel(Cb, Id, Args, SubSysSup) ->
+ start_channel(Cb, Id, Args, SubSysSup, undefined).
-start_channel(Address, Port, Cb, Id, Args, Exec) ->
+start_channel(Cb, Id, Args, SubSysSup, Exec) ->
ChildSpec = child_spec(Cb, Id, Args, Exec),
- SystemSup = ssh_system_sup:system_supervisor(Address, Port),
- ChannelSup = ssh_system_sup:channel_supervisor(SystemSup),
+ ChannelSup =ssh_subsystem_sup:channel_supervisor(SubSysSup),
ssh_channel_sup:start_child(ChannelSup, ChildSpec).
%%--------------------------------------------------------------------
@@ -1017,18 +1016,19 @@ start_cli(#connection{address = Address, port = Port, cli_spec = {Fun, [Shell]},
{ok, Pid}
end;
-start_cli(#connection{address = Address, port = Port,
- cli_spec = {CbModule, Args}, exec = Exec}, ChannelId) ->
- start_channel(Address, Port, CbModule, ChannelId, Args, Exec).
+start_cli(#connection{cli_spec = {CbModule, Args}, exec = Exec,
+ sub_system_supervisor = SubSysSup}, ChannelId) ->
+ start_channel(CbModule, ChannelId, Args, SubSysSup, Exec).
start_subsytem(BinName, #connection{address = Address, port = Port,
- options = Options},
+ options = Options,
+ sub_system_supervisor = SubSysSup},
#channel{local_id = ChannelId, remote_id = RemoteChannelId},
ReplyMsg) ->
Name = binary_to_list(BinName),
case check_subsystem(Name, Options) of
{Callback, Opts} when is_atom(Callback), Callback =/= none ->
- start_channel(Address, Port, Callback, ChannelId, Opts);
+ start_channel(Callback, ChannelId, Opts, SubSysSup);
{Other, _} when Other =/= none ->
handle_backwards_compatibility(Other, self(),
ChannelId, RemoteChannelId,
diff --git a/lib/ssh/src/ssh_connection_controler.erl b/lib/ssh/src/ssh_connection_controler.erl
index 7960eb11c6..636ecba532 100644
--- a/lib/ssh/src/ssh_connection_controler.erl
+++ b/lib/ssh/src/ssh_connection_controler.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
%%--------------------------------------------------------------------
@@ -99,8 +99,8 @@ terminate(_Reason, #state{}) ->
handle_call({handler, Pid, [Role, Socket, Opts]}, _From, State) ->
{ok, Handler} = ssh_connection_handler:start_link(Role, Pid, Socket, Opts),
{reply, {ok, Handler}, State#state{handler = Handler}};
-handle_call({manager, [server = Role, Socket, Opts]}, _From, State) ->
- {ok, Manager} = ssh_connection_manager:start_link([Role, Socket, Opts]),
+handle_call({manager, [server = Role, Socket, Opts, SubSysSup]}, _From, State) ->
+ {ok, Manager} = ssh_connection_manager:start_link([Role, Socket, Opts, SubSysSup]),
{reply, {ok, Manager}, State#state{manager = Manager}};
handle_call({manager, [client = Role | Opts]}, _From, State) ->
{ok, Manager} = ssh_connection_manager:start_link([Role, Opts]),
diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl
index 822ef8f8f9..d46002c494 100644
--- a/lib/ssh/src/ssh_connection_handler.erl
+++ b/lib/ssh/src/ssh_connection_handler.erl
@@ -527,7 +527,7 @@ handle_info({Protocol, Socket, Data}, Statename,
%% Implementations SHOULD decrypt the length after receiving the
%% first 8 (or cipher block size, whichever is larger) bytes of a
%% packet. (RFC 4253: Section 6 - Binary Packet Protocol)
- case size(EncData0) + size(Data) >= max(8, BlockSize) of
+ case size(EncData0) + size(Data) >= erlang:max(8, BlockSize) of
true ->
{Ssh, SshPacketLen, DecData, EncData} =
@@ -758,11 +758,6 @@ after_new_keys(#state{renegotiate = false,
ssh_params = #ssh{role = server}} = State) ->
{userauth, State}.
-max(N, M) when N > M ->
- N;
-max(_, M) ->
- M.
-
handle_ssh_packet_data(RemainingSshPacketLen, DecData, EncData, StateName,
State) ->
EncSize = size(EncData),
diff --git a/lib/ssh/src/ssh_connection_manager.erl b/lib/ssh/src/ssh_connection_manager.erl
index a2effc177e..cffeade485 100644
--- a/lib/ssh/src/ssh_connection_manager.erl
+++ b/lib/ssh/src/ssh_connection_manager.erl
@@ -178,7 +178,7 @@ send_eof(ConnectionManager, ChannelId) ->
%% {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
-init([server, _Socket, Opts]) ->
+init([server, _Socket, Opts, SubSysSup]) ->
process_flag(trap_exit, true),
ssh_bits:install_messages(ssh_connection:messages()),
Cache = ssh_channel:cache_create(),
@@ -187,7 +187,8 @@ init([server, _Socket, Opts]) ->
channel_id_seed = 0,
port_bindings = [],
requests = [],
- channel_pids = []},
+ channel_pids = [],
+ sub_system_supervisor = SubSysSup},
opts = Opts,
connected = false}};
@@ -400,7 +401,7 @@ handle_call({close, ChannelId}, _,
end;
handle_call(stop, _, #state{role = _client,
- client = ChannelPid,
+ client = _ChannelPid,
connection = Pid} = State) ->
DisconnectMsg =
#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION,
diff --git a/lib/ssh/src/ssh_dsa.erl b/lib/ssh/src/ssh_dsa.erl
index ec24fbcd01..1b9a396f0c 100755
--- a/lib/ssh/src/ssh_dsa.erl
+++ b/lib/ssh/src/ssh_dsa.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_file.erl b/lib/ssh/src/ssh_file.erl
index 8a3c903e51..5572349fe7 100755
--- a/lib/ssh/src/ssh_file.erl
+++ b/lib/ssh/src/ssh_file.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl
index 0e343c20b4..915fd63e4f 100755
--- a/lib/ssh/src/ssh_io.erl
+++ b/lib/ssh/src/ssh_io.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_math.erl b/lib/ssh/src/ssh_math.erl
index efe7f56979..510eb16aa6 100755
--- a/lib/ssh/src/ssh_math.erl
+++ b/lib/ssh/src/ssh_math.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_no_io.erl b/lib/ssh/src/ssh_no_io.erl
index 5f363ae6c2..2c8dd92ee2 100644
--- a/lib/ssh/src/ssh_no_io.erl
+++ b/lib/ssh/src/ssh_no_io.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_rsa.erl b/lib/ssh/src/ssh_rsa.erl
index 7c2bf9a2bf..e27cdcf7bd 100755
--- a/lib/ssh/src/ssh_rsa.erl
+++ b/lib/ssh/src/ssh_rsa.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_sftp.erl b/lib/ssh/src/ssh_sftp.erl
index cbfa208f6f..59e09fdd0f 100755
--- a/lib/ssh/src/ssh_sftp.erl
+++ b/lib/ssh/src/ssh_sftp.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
@@ -46,14 +46,6 @@
recv_window/1, list_dir/2, read_file/2, write_file/3,
recv_window/2, list_dir/3, read_file/3, write_file/4]).
-%% Deprecated
--export([connect/1, connect/2, connect/3, stop/1]).
-
--deprecated({connect, 1, next_major_release}).
--deprecated({connect, 2, next_major_release}).
--deprecated({connect, 3, next_major_release}).
--deprecated({stop, 1, next_major_release}).
-
%% ssh_channel callbacks
-export([init/1, handle_call/3, handle_msg/2, handle_ssh_msg/2, terminate/2]).
%% TODO: Should be placed elsewhere ssh_sftpd should not call functions in ssh_sftp!
@@ -1116,33 +1108,3 @@ lseek_pos(_, _, _) ->
{error, einval}.
-%%%%%% Deprecated %%%%
-connect(Cm) when is_pid(Cm) ->
- connect(Cm, []);
-connect(Host) when is_list(Host) ->
- connect(Host, []).
-connect(Cm, Opts) when is_pid(Cm) ->
- Timeout = proplists:get_value(timeout, Opts, infinity),
- case ssh_xfer:attach(Cm, []) of
- {ok, ChannelId, Cm} ->
- ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm, ChannelId,
- Timeout]);
- Error ->
- Error
- end;
-connect(Host, Opts) ->
- connect(Host, 22, Opts).
-connect(Host, Port, Opts) ->
- Timeout = proplists:get_value(timeout, Opts, infinity),
- case ssh_xfer:connect(Host, Port, proplists:delete(timeout, Opts)) of
- {ok, ChannelId, Cm} ->
- ssh_channel:start(Cm, ChannelId, ?MODULE, [Cm,
- ChannelId, Timeout]);
- Error ->
- Error
- end.
-
-
-stop(Pid) ->
- call(Pid, stop, infinity).
-
diff --git a/lib/ssh/src/ssh_sftpd.erl b/lib/ssh/src/ssh_sftpd.erl
index 10b8ede1e4..da91817fd7 100644
--- a/lib/ssh/src/ssh_sftpd.erl
+++ b/lib/ssh/src/ssh_sftpd.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
@@ -242,7 +242,8 @@ handle_op(?SSH_FXP_REALPATH, ReqId,
end;
handle_op(?SSH_FXP_OPENDIR, ReqId,
<<?UINT32(RLen), RPath:RLen/binary>>,
- State0 = #state{file_handler = FileMod, file_state = FS0}) ->
+ State0 = #state{xf = #ssh_xfer{vsn = Vsn},
+ file_handler = FileMod, file_state = FS0}) ->
RelPath = binary_to_list(RPath),
AbsPath = relate_file_name(RelPath, State0),
@@ -250,10 +251,14 @@ handle_op(?SSH_FXP_OPENDIR, ReqId,
{IsDir, FS1} = FileMod:is_dir(AbsPath, FS0),
State1 = State0#state{file_state = FS1},
case IsDir of
- false ->
+ false when Vsn > 5 ->
ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_NOT_A_DIRECTORY,
"Not a directory"),
State1;
+ false ->
+ ssh_xfer:xf_send_status(XF, ReqId, ?SSH_FX_FAILURE,
+ "Not a directory"),
+ State1;
true ->
add_handle(State1, XF, ReqId, directory, {RelPath,unread})
end;
diff --git a/lib/ssh/src/ssh_sftpd_file.erl b/lib/ssh/src/ssh_sftpd_file.erl
index f0b6bb4de5..91ba228e38 100644
--- a/lib/ssh/src/ssh_sftpd_file.erl
+++ b/lib/ssh/src/ssh_sftpd_file.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-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%
%%
diff --git a/lib/ssh/src/ssh_sftpd_file_api.erl b/lib/ssh/src/ssh_sftpd_file_api.erl
index 8decfb38d9..176aa98194 100644
--- a/lib/ssh/src/ssh_sftpd_file_api.erl
+++ b/lib/ssh/src/ssh_sftpd_file_api.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
diff --git a/lib/ssh/src/ssh_shell.erl b/lib/ssh/src/ssh_shell.erl
index f81b949119..6590486a4c 100644
--- a/lib/ssh/src/ssh_shell.erl
+++ b/lib/ssh/src/ssh_shell.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
+%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
diff --git a/lib/ssh/src/ssh_ssh.erl b/lib/ssh/src/ssh_ssh.erl
deleted file mode 100644
index 6be8bf7a5a..0000000000
--- a/lib/ssh/src/ssh_ssh.erl
+++ /dev/null
@@ -1,65 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-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%
-%%
-
-%%
-
-%%% Description: THIS MODULE IS DEPRECATD AND SHOULD BE REMOVED IN R14
-
--module(ssh_ssh).
-
--export([connect/1, connect/2, connect/3]).
--deprecated({connect, 1, next_major_release}).
--deprecated({connect, 2, next_major_release}).
--deprecated({connect, 3, next_major_release}).
-
--include("ssh.hrl").
--include("ssh_connect.hrl").
-
--define(default_timeout, 10000).
-
-%%% Backwards compatibility
-connect(A) ->
- connect(A, []).
-
-connect(Host, Opts) when is_list(Host) ->
- connect(Host, 22, Opts);
-connect(CM, Opts) ->
- Timeout = proplists:get_value(connect_timeout, Opts, ?default_timeout),
- session(CM, Timeout).
-
-connect(Host, Port, Opts) ->
- case ssh:connect(Host, Port, Opts) of
- {ok, CM} ->
- session(CM, proplists:get_value(connect_timeout,
- Opts, ?default_timeout));
- Error ->
- Error
- end.
-
-session(CM, Timeout) ->
- case ssh_connection:session_channel(CM, Timeout) of
- {ok, ChannelId} ->
- Args = [{channel_cb, ssh_shell},
- {init_args,[CM, ChannelId]},
- {cm, CM}, {channel_id, ChannelId}],
- {ok, State} = ssh_channel:init([Args]),
- ssh_channel:enter_loop(State);
- Error ->
- Error
- end.
diff --git a/lib/ssh/src/ssh_sshd.erl b/lib/ssh/src/ssh_sshd.erl
deleted file mode 100644
index 4bc0469061..0000000000
--- a/lib/ssh/src/ssh_sshd.erl
+++ /dev/null
@@ -1,48 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-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%
-%%
-
-%%
-%% Description: This module uses the erlang shell and
-%% ssh_cli to make an erlang sshd
-
--module(ssh_sshd).
-
-%% API
--export([listen/0, listen/1, listen/2, listen/3, stop/1]).
-
--deprecated({listen, 0, next_major_release}).
--deprecated({listen, 1, next_major_release}).
--deprecated({listen, 2, next_major_release}).
--deprecated({listen, 3, next_major_release}).
--deprecated({stop, 1, next_major_release}).
-
-listen() ->
- listen(22).
-
-listen(Port) ->
- listen(Port, []).
-
-listen(Port, Opts) ->
- listen(any, Port, Opts).
-
-listen(Addr, Port, Opts) ->
- ssh:daemon(Addr, Port, Opts).
-
-stop(Pid) ->
- ssh:stop_daemon(Pid).
diff --git a/lib/ssh/src/ssh_subsystem_sup.erl b/lib/ssh/src/ssh_subsystem_sup.erl
index 17d47a91d5..d71b6bbc56 100644
--- a/lib/ssh/src/ssh_subsystem_sup.erl
+++ b/lib/ssh/src/ssh_subsystem_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
%%
diff --git a/lib/ssh/src/ssh_sup.erl b/lib/ssh/src/ssh_sup.erl
index 4c46b1586b..f307d1f833 100644
--- a/lib/ssh/src/ssh_sup.erl
+++ b/lib/ssh/src/ssh_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_system_sup.erl b/lib/ssh/src/ssh_system_sup.erl
index 477f60f993..40db2e4adf 100644
--- a/lib/ssh/src/ssh_system_sup.erl
+++ b/lib/ssh/src/ssh_system_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_transport.erl b/lib/ssh/src/ssh_transport.erl
index 5617231c60..e79ccdda0c 100644
--- a/lib/ssh/src/ssh_transport.erl
+++ b/lib/ssh/src/ssh_transport.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
diff --git a/lib/ssh/src/ssh_transport.hrl b/lib/ssh/src/ssh_transport.hrl
index 18a23f0533..27d3e32355 100644
--- a/lib/ssh/src/ssh_transport.hrl
+++ b/lib/ssh/src/ssh_transport.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_userauth.hrl b/lib/ssh/src/ssh_userauth.hrl
index 39cc032ca5..8eb2d46ed1 100755
--- a/lib/ssh/src/ssh_userauth.hrl
+++ b/lib/ssh/src/ssh_userauth.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_userreg.erl b/lib/ssh/src/ssh_userreg.erl
index 06f4076b51..33c801f490 100644
--- a/lib/ssh/src/ssh_userreg.erl
+++ b/lib/ssh/src/ssh_userreg.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/ssh_xfer.erl b/lib/ssh/src/ssh_xfer.erl
index a347a9c095..c9631a73b1 100644
--- a/lib/ssh/src/ssh_xfer.erl
+++ b/lib/ssh/src/ssh_xfer.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/ssh_xfer.hrl b/lib/ssh/src/ssh_xfer.hrl
index f32ec5f774..4a4f1a4291 100755
--- a/lib/ssh/src/ssh_xfer.hrl
+++ b/lib/ssh/src/ssh_xfer.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
diff --git a/lib/ssh/src/sshc_sup.erl b/lib/ssh/src/sshc_sup.erl
index 265d1a1cd6..7c29c669e4 100644
--- a/lib/ssh/src/sshc_sup.erl
+++ b/lib/ssh/src/sshc_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
diff --git a/lib/ssh/src/sshd_sup.erl b/lib/ssh/src/sshd_sup.erl
index 9c9ba5958c..747906b2cf 100644
--- a/lib/ssh/src/sshd_sup.erl
+++ b/lib/ssh/src/sshd_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
%%
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 074826cafd..ccdbfe4f9a 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,12 +1,15 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 1.1.9
+SSH_VSN = 2.0
APP_VSN = "ssh-$(SSH_VSN)"
TICKETS = OTP-8524 \
OTP-8534 \
OTP-8535 \
- OTP-8550
+ OTP-8550 \
+ OTP-8596 \
+ OTP-8644 \
+ OTP-8645
TICKETS_1.1.8 = OTP-8356 \
OTP-8401
diff --git a/lib/ssl/Makefile b/lib/ssl/Makefile
index a3dec8da38..b8b51270c9 100644
--- a/lib/ssl/Makefile
+++ b/lib/ssl/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1999-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1999-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%
#
@@ -36,9 +36,9 @@ SKIP_BUILDING_BINARIES := false
endif
ifeq ($(SKIP_BUILDING_BINARIES), true)
-SUB_DIRECTORIES = pkix src c_src doc/src
+SUB_DIRECTORIES = src c_src doc/src
else
-SUB_DIRECTORIES = pkix src c_src doc/src examples/certs examples/src
+SUB_DIRECTORIES = src c_src doc/src examples/certs examples/src
endif
include vsn.mk
diff --git a/lib/ssl/doc/src/Makefile b/lib/ssl/doc/src/Makefile
index fa263d28ab..3119d37af0 100644
--- a/lib/ssl/doc/src/Makefile
+++ b/lib/ssl/doc/src/Makefile
@@ -37,7 +37,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
# Target Specs
# ----------------------------------------------------
XML_APPLICATION_FILES = refman.xml
-XML_REF3_FILES = ssl.xml new_ssl.xml
+XML_REF3_FILES = ssl.xml old_ssl.xml ssl_session_cache_api.xml
XML_REF6_FILES = ssl_app.xml
XML_PART_FILES = release_notes.xml usersguide.xml
@@ -45,9 +45,7 @@ XML_CHAPTER_FILES = \
ssl_protocol.xml \
using_ssl.xml \
pkix_certs.xml \
- create_certs.xml \
ssl_distribution.xml \
- licenses.xml \
notes.xml
BOOK_FILES = book.xml
diff --git a/lib/ssl/doc/src/book.xml b/lib/ssl/doc/src/book.xml
index 9122addb74..85d6b56b26 100644
--- a/lib/ssl/doc/src/book.xml
+++ b/lib/ssl/doc/src/book.xml
@@ -28,9 +28,6 @@
<rev>A</rev>
<file>book.sgml</file>
</header>
- <insidecover>
- <include file="insidecover"></include>
- </insidecover>
<pagetext>SSL Application</pagetext>
<preamble>
<contents level="2"></contents>
diff --git a/lib/ssl/doc/src/create_certs.xml b/lib/ssl/doc/src/create_certs.xml
deleted file mode 100644
index 79cc8a0537..0000000000
--- a/lib/ssl/doc/src/create_certs.xml
+++ /dev/null
@@ -1,148 +0,0 @@
-<?xml version="1.0" encoding="latin1" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>2003</year><year>2009</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>Creating Certificates</title>
- <prepared>UAB/F/P Peter H&ouml;gfeldt</prepared>
- <docno></docno>
- <date>2003-06-16</date>
- <rev>A</rev>
- <file>create_certs.xml</file>
- </header>
- <p>Here we consider the creation of example certificates.
- </p>
-
- <section>
- <title>The openssl Command</title>
- <p>The <c>openssl</c> command is a utility that comes with the
- OpenSSL distribution. It provides a variety of subcommands. Each
- subcommand is invoked as</p>
- <code type="none"><![CDATA[
- openssl subcmd <options and arguments> ]]></code>
- <p>where <c>subcmd</c> denotes the subcommand in question.
- </p>
- <p>We shall use the following subcommands to create certificates for
- the purpose of testing Erlang/OTP SSL:
- </p>
- <list type="bulleted">
- <item><em>req</em> to create certificate requests and a
- self-signed certificates,
- </item>
- <item><em>ca</em> to create certificates from certificate requests.</item>
- </list>
- <p>We create the following certificates:
- </p>
- <list type="bulleted">
- <item>the <em>erlangCA</em> root certificate (a self-signed
- certificate), </item>
- <item>the <em>otpCA</em> certificate signed by the <em>erlangCA</em>, </item>
- <item>a client certificate signed by the <em>otpCA</em>, and</item>
- <item>a server certificate signed by the <em>otpCA</em>.</item>
- </list>
-
- <section>
- <title>The openssl configuration file</title>
- <p>An <c>openssl</c> configuration file consist of a number of
- sections, where each section starts with one line containing
- <c>[ section_name ]</c>, where <c>section_name</c> is the name
- of the section. The first section of the file is either
- unnamed, or is named <c>[ default ]</c>. For further details
- see the OpenSSL config(5) manual page.
- </p>
- <p>The required sections for the subcommands we are going to
- use are as follows:
- </p>
- <table>
- <row>
- <cell align="left" valign="middle">subcommand</cell>
- <cell align="left" valign="middle">required/default section</cell>
- <cell align="left" valign="middle">override command line option</cell>
- <cell align="left" valign="middle">configuration file option</cell>
- </row>
- <row>
- <cell align="left" valign="middle">req</cell>
- <cell align="left" valign="middle">[req]</cell>
- <cell align="left" valign="middle">-</cell>
- <cell align="left" valign="middle"><c>-config FILE</c></cell>
- </row>
- <row>
- <cell align="left" valign="middle">ca</cell>
- <cell align="left" valign="middle">[ca]</cell>
- <cell align="left" valign="middle"><c>-name section</c></cell>
- <cell align="left" valign="middle"><c>-config FILE</c></cell>
- </row>
- <tcaption>openssl subcommands to use</tcaption>
- </table>
- </section>
-
- <section>
- <title>Creating the Erlang root CA</title>
- <p>The Erlang root CA is created with the command</p>
- <code type="none">
- openssl req -new -x509 -config /some/path/req.cnf \\
- -keyout /some/path/key.pem -out /some/path/cert.pem </code>
- <p>where the option <c>-new</c> indicates that we want to create
- a new certificate request and the option <c>-x509</c> implies
- that a self-signed certificate is created.
- </p>
- </section>
-
- <section>
- <title>Creating the OTP CA</title>
- <p>The OTP CA is created by first creating a certificate request
- with the command</p>
- <code type="none">
- openssl req -new -config /some/path/req.cnf \\
- -keyout /some/path/key.pem -out /some/path/req.pem </code>
- <p>and the ask the Erlang CA to sign it:</p>
- <code type="none">
- openssl ca -batch -notext -config /some/path/req.cnf \\
- -extensions ca_cert -in /some/path/req.pem -out /some/path/cert.pem </code>
- <p>where the option <c>-extensions</c> refers to a section in the
- configuration file saying that it should create a CA certificate,
- and not a plain user certificate.
- </p>
- <p>The <c>client</c> and <c>server</c> certificates are created
- similarly, except that the option <c>-extensions</c> then has the
- value <c>user_cert</c>.
- </p>
- </section>
- </section>
-
- <section>
- <title>An Example</title>
- <p>The following module <c>create_certs</c> is used by the Erlang/OTP
- SSL application for generating certificates to be used in tests. The
- source code is also found in <c>ssl-X.Y.Z/examples/certs/src</c>.
- </p>
- <p>The purpose of the <c>create_certs:all/1</c> function is to make
- it possible to provide from the <c>erl</c> command line, the
- full path name of the <c>openssl</c> command.
- </p>
- <p>Note that the module creates temporary OpenSSL configuration files
- for the <c>req</c> and <c>ca</c> subcommands.
- </p>
- <codeinclude file="../../examples/certs/src/make_certs.erl" tag="" type="erl"></codeinclude>
- </section>
-</chapter>
-
-
diff --git a/lib/ssl/doc/src/insidecover.xml b/lib/ssl/doc/src/insidecover.xml
deleted file mode 100644
index 4f3f5e5951..0000000000
--- a/lib/ssl/doc/src/insidecover.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="latin1" ?>
-<!DOCTYPE bookinsidecover SYSTEM "bookinsidecover.dtd">
-
-<bookinsidecover>
-The Erlang/OTP SSL application includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/). Copyright (c) 1998-2002 The OpenSSL Project. All rights reserved. <br></br>
-This product includes cryptographic software written by Eric Young ([email protected]). This product includes software written by Tim Hudson ([email protected]). Copyright (C) 1995-1998 Eric Young ([email protected]). All rights reserved. <br></br>
-For further OpenSSL and SSLeay license information se the chapter <bold>Licenses</bold>
-. <vfill></vfill>
- <br></br>
- <tt>http://www.erlang.org</tt>
- <br></br>
-</bookinsidecover>
-
-
diff --git a/lib/ssl/doc/src/licenses.xml b/lib/ssl/doc/src/licenses.xml
deleted file mode 100644
index 0969f9ad6e..0000000000
--- a/lib/ssl/doc/src/licenses.xml
+++ /dev/null
@@ -1,156 +0,0 @@
-<?xml version="1.0" encoding="latin1" ?>
-<!DOCTYPE chapter SYSTEM "chapter.dtd">
-
-<chapter>
- <header>
- <copyright>
- <year>2003</year><year>2009</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>Licenses</title>
- <prepared>Peter H&ouml;gfeldt</prepared>
- <docno></docno>
- <date>2003-05-26</date>
- <rev>A</rev>
- <file>licenses.xml</file>
- </header>
- <p> <marker id="licenses"></marker>
-This chapter contains in extenso versions
- of the OpenSSL and SSLeay licenses.
- </p>
-
- <section>
- <title>OpenSSL License</title>
- <code type="none">
-/* ====================================================================
- * Copyright (c) 1998-2002 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
- * are met:
- *
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in
- * the documentation and/or other materials provided with the
- * distribution.
- *
- * 3. All advertising materials mentioning features or use of this
- * software must display the following acknowledgment:
- * "This product includes software developed by the OpenSSL Project
- * for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
- *
- * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
- * endorse or promote products derived from this software without
- * prior written permission. For written permission, please contact
- *
- * 5. Products derived from this software may not be called "OpenSSL"
- * nor may "OpenSSL" appear in their names without prior written
- * permission of the OpenSSL Project.
- *
- * 6. Redistributions of any form whatsoever must retain the following
- * acknowledgment:
- * "This product includes software developed by the OpenSSL Project
- * for use in the OpenSSL Toolkit (http://www.openssl.org/)"
- *
- * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
- * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR
- * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- * OF THE POSSIBILITY OF SUCH DAMAGE.
- * ====================================================================
- *
- * This product includes cryptographic software written by Eric Young
- * ([email protected]). This product includes software written by Tim
- * Hudson ([email protected]).
- *
- */ </code>
- </section>
-
- <section>
- <title>SSLeay License</title>
- <code type="none">
-/* Copyright (C) 1995-1998 Eric Young ([email protected])
- * All rights reserved.
- *
- * This package is an SSL implementation written
- * by Eric Young ([email protected]).
- * The implementation was written so as to conform with Netscapes SSL.
- *
- * This library is free for commercial and non-commercial use as long as
- * the following conditions are aheared to. The following conditions
- * apply to all code found in this distribution, be it the RC4, RSA,
- * lhash, DES, etc., code; not just the SSL code. The SSL documentation
- * included with this distribution is covered by the same copyright terms
- * except that the holder is Tim Hudson ([email protected]).
- *
- * Copyright remains Eric Young's, and as such any Copyright notices in
- * the code are not to be removed.
- * If this package is used in a product, Eric Young should be given attribution
- * as the author of the parts of the library used.
- * This can be in the form of a textual message at program startup or
- * in documentation (online or textual) provided with the package.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgement:
- * "This product includes cryptographic software written by
- * Eric Young ([email protected])"
- * The word 'cryptographic' can be left out if the rouines from the library
- * being used are not cryptographic related :-).
- * 4. If you include any Windows specific code (or a derivative thereof) from
- * the apps directory (application code) you must include an acknowledgement:
- * "This product includes software written by Tim Hudson ([email protected])"
- *
- * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- * The licence and distribution terms for any publically available version or
- * derivative of this code cannot be changed. i.e. this code cannot simply be
- * copied and put under another distribution licence
- * [including the GNU Public Licence.]
- */ </code>
- </section>
-</chapter>
-
-
diff --git a/lib/ssl/doc/src/make.dep b/lib/ssl/doc/src/make.dep
deleted file mode 100644
index 2ff81bee1f..0000000000
--- a/lib/ssl/doc/src/make.dep
+++ /dev/null
@@ -1,30 +0,0 @@
-# ----------------------------------------------------
-# >>>> Do not edit this file <<<<
-# This file was automaticly generated by
-# /home/otp/bin/docdepend
-# ----------------------------------------------------
-
-
-# ----------------------------------------------------
-# TeX files that the DVI file depend on
-# ----------------------------------------------------
-
-book.dvi: book.tex create_certs.tex licenses.tex new_ssl.tex \
- pkix_certs.tex refman.tex ssl.tex ssl_app.tex \
- ssl_distribution.tex ssl_protocol.tex usersguide.tex \
- using_ssl.tex
-
-# ----------------------------------------------------
-# Source inlined when transforming from source to LaTeX
-# ----------------------------------------------------
-
-book.tex: refman.xml
-
-create_certs.tex: ../../examples/certs/src/make_certs.erl
-
-using_ssl.tex: ../../examples/src/client_server.erl
-
-pkix_certs.tex: ../../../../system/doc/definitions/cite.defs
-
-ssl_protocol.tex: ../../../../system/doc/definitions/cite.defs
-
diff --git a/lib/ssl/doc/src/new_ssl.xml b/lib/ssl/doc/src/new_ssl.xml
deleted file mode 100644
index a83c2d1383..0000000000
--- a/lib/ssl/doc/src/new_ssl.xml
+++ /dev/null
@@ -1,678 +0,0 @@
-<?xml version="1.0" encoding="latin1" ?>
-<!DOCTYPE erlref SYSTEM "erlref.dtd">
-
-<erlref>
- <header>
- <copyright>
- <year>1999</year>
- <year>2007</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 aniline's 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 AB.
- </legalnotice>
-
- <title>ssl</title>
- <prepared>Ingela Anderton Andin</prepared>
- <responsible>Ingela Anderton Andin</responsible>
- <docno></docno>
- <approved></approved>
- <checked></checked>
- <date>2003-03-25</date>
- <rev></rev>
- <file>new_ssl.xml</file>
- </header>
- <module>new_ssl</module>
- <modulesummary>Interface Functions for Secure Socket Layer</modulesummary>
- <description>
- <p>This module contains interface functions to the Secure Socket
- Layer.
- </p>
- </description>
-
- <section>
- <title>NEW SSL</title>
-
- <p>This manual page describes functions that are defined
- in the ssl module and represents the new ssl implementation
- that coexists with the old one, as the new implementation
- is not yet complete enough to replace the old one.</p>
-
- <p>The new implementation can be
- accessed by providing the option {ssl_imp, new} to the
- ssl:connect and ssl:listen functions.</p>
-
- <p>The new implementation is Erlang based and all logic
- is in Erlang and only payload encryption calculations are
- done in C via the crypto application. The main reason for
- making a new implementation is that the old solution was
- very crippled as the control of the ssl-socket was deep
- down in openssl making it hard if not impossible to
- support all inet options, ipv6 and upgrade of a tcp
- connection to a ssl connection. This version has a
- few limitations that will be removed before the ssl-4.0
- release. Main differences and limitations are listed below.</p>
-
- <list type="bulleted">
- <item>New ssl requires the crypto
- application.</item>
- <item>The option reuseaddr is
- supported and the default value is false as in gen_tcp.
- Old ssl is patched to accept that the option is set to
- true to provide a smoother migration between the
- versions. In old ssl the option is hard coded to
- true.</item>
- <item>ssl:version/0 is replaced by
- ssl:versions/0</item>
- <item>ssl:ciphers/0 is replaced by
- ssl:cipher_suites/0</item>
- <item>ssl:pid/1 is a
- meaningless function in new ssl and will be deprecated in
- ssl-4.0 until it is removed it will return a valid but
- meaningless pid.</item>
- <item>New API functions are
- ssl:shutdown/2, ssl:cipher_suites/[0,1] and
- ssl:versions/0</item>
- <item>CRL and policy certificate
- extensions are not supported yet. </item>
- <item>Supported SSL/TLS-versions are SSL-3.0 and TLS-1.0 </item>
- <item>For security reasons sslv2 is not supported.</item>
- </list>
-
- </section>
-
- <section>
- <title>COMMON DATA TYPES</title>
- <p>The following data types are used in the functions below:
- </p>
-
- <p><c>boolean() = true | false</c></p>
-
- <p><c>property() = atom()</c></p>
-
- <p><c>option() = socketoption() | ssloption() | transportoption()</c></p>
-
- <p><c>socketoption() = [{property(), term()}] - defaults to
- [{mode,list},{packet, 0},{header, 0},{active, true}].
- </c></p>
-
- <p>For valid options
- see <seealso marker="kernel:inet">inet(3) </seealso> and
- <seealso marker="kernel:gen_tcp">gen_tcp(3) </seealso>.
- </p>
-
- <p> <c>ssloption() = {verify, verify_type()} |
- {fail_if_no_peer_cert, boolean()}
- {depth, integer()} |
- {certfile, path()} | {keyfile, path()} | {password, string()} |
- {cacertfile, path()} | {dhfile, path()} | {ciphers, ciphers()} |
- {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()}
- </c></p>
-
- <p><c>transportoption() = {CallbackModule, DataTag, ClosedTag}
- - defaults to {gen_tcp, tcp, tcp_closed}. Ssl may be
- run over any reliable transport protocol that has
- an equivalent API to gen_tcp's.</c></p>
-
- <p><c>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CallbackModule =
- atom()</c>
- </p> <p><c>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataTag =
- atom() - tag used in socket data message.</c></p>
- <p><c>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ClosedTag = atom() - tag used in
- socket close message.</c></p>
-
- <p><c>verify_type() = verify_none | verify_peer</c></p>
-
- <p><c>path() = string() - representing a file path.</c></p>
-
- <p><c>host() = hostname() | ipaddress()</c></p>
-
- <p><c>hostname() = string()</c></p>
-
- <p><c>
- ip_address() = {N1,N2,N3,N4} % IPv4
- | {K1,K2,K3,K4,K5,K6,K7,K8} % IPv6 </c></p>
-
- <p><c>sslsocket() - opaque to the user. </c></p>
-
- <p><c>protocol() = sslv3 | tlsv1 </c></p>
-
- <p><c>ciphers() = [ciphersuite()] | sting() (according to old API)</c></p>
-
- <p><c>ciphersuite() =
- {key_exchange(), cipher(), hash(), exportable()}</c></p>
-
- <p><c>key_exchange() = rsa | dh_dss | dh_rsa | dh_anon | dhe_dss
- | dhe_rsa | krb5 | KeyExchange_export
- </c></p>
-
- <p><c>cipher() = rc4_128 | idea_cbc | des_cbc | '3des_ede_cbc'
- des40_cbc | dh_dss | aes_128_cbc | aes_256_cbc |
- rc2_cbc_40 | rc4_40 </c></p>
-
- <p> <c>hash() = md5 | sha
- </c></p>
-
- <p> <c>exportable() = export | no_export | ignore
- </c></p>
-
- <p><c>ssl_imp() = new | old - default is old.</c></p>
-
- </section>
-
-<section>
- <title>SSL OPTION DESCRIPTIONS</title>
-
- <taglist>
- <tag>{verify, verify_type()}</tag>
- <item> If <c>verify_none</c> is specified x509-certificate
- path validation errors at the client side
- will not automatically cause the connection to fail, as
- it will if the verify type is <c>verify_peer</c>. See also
- the option verify_fun.
- Servers only do the path validation if <c>verify_peer</c> is set to
- true, as it then will
- send a certificate request to
- the client (this message is not sent if the verify option is
- <c>verify_none</c>) and you may then also want to specify
- the option <c>fail_if_no_peer_cert</c>.
- </item>
-
- <tag>{fail_if_no_peer_cert, boolean()}</tag>
- <item>Used together with {verify, verify_peer} by a ssl server.
- If set to true,
- the server will fail if the client does not have a certificate
- to send, e.i sends a empty certificate, if set to false it will
- only fail if the client sends a invalid certificate (an empty
- certificate is considered valid).
- </item>
-
- <tag>{verify_fun, fun(ErrorList) -> boolean()}</tag>
- <item>Used by the ssl client to determine if
- x509-certificate path validations errors are acceptable or
- if the connection should fail. Defaults to:
-
-<code>
-fun(ErrorList) ->
- case lists:foldl(fun({bad_cert,unknown_ca}, Acc) ->
- Acc;
- (Other, Acc) ->
- [Other | Acc]
- end, [], ErrorList) of
- [] ->
- true;
- [_|_] ->
- false
- end
-end
-</code>
- I.e. by default if the only error found was that the CA-certificate
- holder was unknown this will be accepted.
-
- Possible errors in the error list are:
- {bad_cert, cert_expired}, {bad_cert, invalid_issuer},
- {bad_cert, invalid_signature}, {bad_cert, name_not_permitted},
- {bad_cert, unknown_ca},
- {bad_cert, cert_expired}, {bad_cert, invalid_issuer},
- {bad_cert, invalid_signature}, {bad_cert, name_not_permitted},
- {bad_cert, cert_revoked} (not implemented yet),
- {bad_cert, unknown_critical_extension} or {bad_cert, term()} (Will
- be relevant later when an option is added for the user to be able to verify application specific extensions.)
- </item>
-
- <tag>{depth, integer()}</tag>
- <item>Specifies the maximum
- verification depth, i.e. how far in a chain of certificates the
- verification process can proceed before the verification is
- considered to fail. Peer certificate = 0, CA certificate = 1,
- higher level CA certificate = 2, etc. The value 2 thus means
- that a chain can at most contain peer cert, CA cert, next CA
- cert, and an additional CA cert. The default value is 1.
- </item>
-
- <tag>{certfile, path()}</tag>
- <item>Path to a file containing the
- user's certificate. Optional for clients but note
- that some servers requires that the client can certify
- itself. </item>
- <tag>{keyfile, path()}</tag>
- <item>Path to file containing user's
- private PEM encoded key. As PEM-files may contain several
- entries this option defaults to the same file as given by
- certfile option.</item>
- <tag>{password, string()}</tag>
- <item>String containing the user's password.
- Only used if the private keyfile is password protected.
- </item>
- <tag>{cacertfile, path()}</tag>
- <item>Path to file containing PEM encoded
- CA certificates (trusted certificates used for verifying a peer
- certificate). May be omitted if you do not want to verify
- the peer.</item>
-
- <tag>{dhfile, path()}</tag>
- <item>Path to file containing PEM encoded Diffie Hellman parameters,
- for the server to use if a cipher suite using Diffie Hellman key exchange
- is negotiated. If not specified hardcode parameters will be used.
- </item>
-
- <tag>{ciphers, ciphers()}</tag>
- <item>The function <c>ciphers_suites/0</c> can
- be used to find all available ciphers.
- </item>
-
- <tag>{ssl_imp, ssl_imp()}</tag>
- <item>Specify which ssl implementation you want to use.
- </item>
-
- <tag>{reuse_sessions, boolean()}</tag>
- <item>Specifies if ssl sessions should be reused
- when possible.
- </item>
-
- <tag>{reuse_session, fun(SuggestedSessionId,
- PeerCert, Compression, CipherSuite) -> boolean()}</tag>
- <item>Enables the ssl server to have a local policy
- for deciding if a session should be reused or not,
- only meaning full if <c>reuse_sessions</c> is set to true.
- SuggestedSessionId is a binary(), PeerCert is a DER encoded
- certificate, Compression is an enumeration integer
- and CipherSuite of type ciphersuite().
- </item>
- </taglist>
- </section>
-
- <section>
- <title>General</title>
-
- <p>When a ssl socket is in active mode (the default), data from the
- socket is delivered to the owner of the socket in the form of
- messages:
- </p>
- <list type="bulleted">
- <item>{ssl, Socket, Data}
- </item>
- <item>{ssl_closed, Socket}
- </item>
- <item>
- {ssl_error, Socket, Reason}
- </item>
- </list>
-
- <p>A <c>Timeout</c> argument specifies a timeout in milliseconds. The
- default value for a <c>Timeout</c> argument is <c>infinity</c>.
- </p>
- </section>
-
- <funcs>
- <func>
- <name>cipher_suites() -></name>
- <name>cipher_suites(Type) -> ciphers()</name>
- <fsummary> Returns a list of supported cipher suites</fsummary>
- <type>
- <v>Type = erlang | openssl</v>
-
- </type>
- <desc><p>Returns a list of supported cipher suites.
- cipher_suites() is equivalent to cipher_suites(erlang).
- Type openssl is provided for backwards compatibility with
- old ssl that used openssl.
- </p>
- </desc>
- </func>
-
- <func>
- <name>connect(Socket, SslOptions) -> </name>
- <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket}
- | {error, Reason}</name>
- <fsummary> Upgrades a gen_tcp, or
- equivalent, connected socket to a ssl socket. </fsummary>
- <type>
- <v>Socket = socket()</v>
- <v>SslOptions = [ssloption()]</v>
- <v>Timeout = integer() | infinity</v>
- <v>SslSocket = sslsocket()</v>
- <v>Reason = term()</v>
- </type>
- <desc> <p>Upgrades a gen_tcp, or equivalent,
- connected socket to a ssl socket e.i performs the
- client-side ssl handshake.</p>
- </desc>
- </func>
-
- <func>
- <name>connect(Host, Port, Options) -></name>
- <name>connect(Host, Port, Options, Timeout) ->
- {ok, SslSocket} | {error, Reason}</name>
- <fsummary>Opens an ssl connection to Host, Port. </fsummary>
- <type>
- <v>Host = host()</v>
- <v>Port = integer()</v>
- <v>Options = [option()]</v>
- <v>Timeout = integer() | infinity</v>
- <v>SslSocket = sslsocket()</v>
- <v>Reason = term()</v>
- </type>
- <desc> <p>Opens an ssl connection to Host, Port.</p> </desc>
- </func>
-
- <func>
- <name>close(SslSocket) -> ok | {error, Reason}</name>
- <fsummary>Close a ssl connection</fsummary>
- <type>
- <v>SslSocket = sslsocket()</v>
- <v>Reason = term()</v>
- </type>
- <desc><p>Close a ssl connection.</p>
- </desc>
- </func>
-
- <func>
- <name>controlling_process(SslSocket, NewOwner) ->
- ok | {error, Reason}</name>
-
- <fsummary>Assigns a new controlling process to the
- ssl-socket.</fsummary>
-
- <type>
- <v>SslSocket = sslsocket()</v>
- <v>NewOwner = pid()</v>
- <v>Reason = term()</v>
- </type>
- <desc><p>Assigns a new controlling process to the ssl-socket. A
- controlling process is the owner of a ssl-socket, and receives
- all messages from the socket.</p>
- </desc>
- </func>
-
- <func>
- <name>connection_info(SslSocket) ->
- {ok, {ProtocolVersion, CipherSuite}} | {error, Reason} </name>
- <fsummary>Returns the negotiated protocol version and cipher suite.
- </fsummary>
- <type>
- <v>CipherSuite = ciphersuite()</v>
- <v>ProtocolVersion = protocol()</v>
- </type>
- <desc><p>Returns the negotiated protocol version and cipher suite.</p>
- </desc>
- </func>
-
- <func>
- <name>getopts(Socket) -> </name>
- <name>getopts(Socket, OptionNames) ->
- {ok, [socketoption()]} | {error, Reason}</name>
- <fsummary>Get the value of the specified options.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>OptionNames = [property()]</v>
- </type>
- <desc>
- <p>Get the value of the specified socket options, if no
- options are specified all options are returned.
- </p>
- </desc>
- </func>
-
- <func>
- <name>listen(Port, Options) ->
- {ok, ListenSocket} | {error, Reason}</name>
- <fsummary>Creates a ssl listen socket.</fsummary>
- <type>
- <v>Port = integer()</v>
- <v>Options = options()</v>
- <v>ListenSocket = sslsocket()</v>
- </type>
- <desc>
- <p>Creates a ssl listen socket.</p>
- </desc>
- </func>
-
- <func>
- <name>peercert(Socket) -> {ok, Cert} | {error, Reason}</name>
- <fsummary>Return the peer certificate.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>Cert = binary()</v>
- <v>Subject = term()</v>
- </type>
- <desc>
- <p>The peer certificate is returned as a DER encoded binary.
- The certificate can be decoded with <c>public_key:pkix_decode_cert/2</c>.
- </p>
- </desc>
- </func>
- <func>
- <name>peername(Socket) -> {ok, {Address, Port}} |
- {error, Reason}</name>
- <fsummary>Return peer address and port.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>Address = ipaddress()</v>
- <v>Port = integer()</v>
- </type>
- <desc>
- <p>Returns the address and port number of the peer.</p>
- </desc>
- </func>
-
- <func>
- <name>recv(Socket, Length) -> </name>
- <name>recv(Socket, Length, Timeout) -> {ok, Data} | {error,
- Reason}</name>
- <fsummary>Receive data on a socket.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>Length = integer()</v>
- <v>Timeout = integer()</v>
- <v>Data = [char()] | binary()</v>
- </type>
- <desc>
- <p>This function receives a packet from a socket in passive
- mode. A closed socket is indicated by a return value
- <c>{error, closed}</c>.</p>
- <p>The <c>Length</c> argument is only meaningful when
- the socket is in <c>raw</c> mode and denotes the number of
- bytes to read. If <c>Length</c> = 0, all available bytes are
- returned. If <c>Length</c> &gt; 0, exactly <c>Length</c>
- bytes are returned, or an error; possibly discarding less
- than <c>Length</c> bytes of data when the socket gets closed
- from the other side.</p>
- <p>The optional <c>Timeout</c> parameter specifies a timeout in
- milliseconds. The default value is <c>infinity</c>.</p>
- </desc>
- </func>
-
- <func>
- <name>renegotiate(Socket) -> ok</name>
- <fsummary> Initiates a new handshake.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- </type>
- <desc><p>Initiates a new handshake.</p>
- </desc>
- </func>
-
- <func>
- <name>send(Socket, Data) -> ok | {error, Reason}</name>
- <fsummary>Write data to a socket.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>Data = iolist() | binary()</v>
- </type>
- <desc>
- <p>Writes <c>Data</c> to <c>Socket</c>. </p>
- <p>A notable return value is <c>{error, closed}</c> indicating that
- the socket is closed.</p>
- </desc>
- </func>
- <func>
- <name>setopts(Socket, Options) -> ok | {error, Reason}</name>
- <fsummary>Set socket options.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>Options = [socketoption]()</v>
- </type>
- <desc>
- <p>Sets options according to <c>Options</c> for the socket
- <c>Socket</c>. </p>
- </desc>
- </func>
-
- <func>
- <name>shutdown(Socket, How) -> ok | {error, Reason}</name>
- <fsummary>Immediately close a socket</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>How = read | write | read_write</v>
- <v>Reason = reason()</v>
- </type>
- <desc>
- <p>Immediately close a socket in one or two directions.</p>
- <p><c>How == write</c> means closing the socket for writing,
- reading from it is still possible.</p>
- <p>To be able to handle that the peer has done a shutdown on
- the write side, the <c>{exit_on_close, false}</c> option
- is useful.</p>
- </desc>
- </func>
-
- <func>
- <name>ssl_accept(ListenSocket) -> </name>
- <name>ssl_accept(ListenSocket, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Perform server-side SSL handshake</fsummary>
- <type>
- <v>ListenSocket = sslsocket()</v>
- <v>Timeout = integer()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p>The <c>ssl_accept</c> function establish the SSL connection
- on the server side. It should be called directly after
- <c>transport_accept</c>, in the spawned server-loop.</p>
- </desc>
- </func>
-
- <func>
- <name>ssl_accept(ListenSocket, SslOptions) -> </name>
- <name>ssl_accept(ListenSocket, SslOptions, Timeout) -> {ok, Socket} | {error, Reason}</name>
- <fsummary>Perform server-side SSL handshake</fsummary>
- <type>
- <v>ListenSocket = socket()</v>
- <v>SslOptions = ssloptions()</v>
- <v>Timeout = integer()</v>
- <v>Reason = term()</v>
- </type>
- <desc>
- <p> Upgrades a gen_tcp, or
- equivalent, socket to a ssl socket e.i performs the
- ssl server-side handshake.</p>
- </desc>
- </func>
-
- <func>
- <name>sockname(Socket) -> {ok, {Address, Port}} |
- {error, Reason}</name>
- <fsummary>Return the local address and port.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- <v>Address = ipaddress()</v>
- <v>Port = integer()</v>
- </type>
- <desc>
- <p>Returns the local address and port number of the socket
- <c>Socket</c>.</p>
- </desc>
- </func>
-
- <func>
- <name>start() -> </name>
- <name>start(Type) -> ok | {error, Reason}</name>
- <fsummary>Starts the Ssl application. </fsummary>
- <type>
- <v>Type = permanent | transient | temporary</v>
- </type>
- <desc>
- <p>Starts the Ssl application. Default type
- is temporary.
- <seealso marker="kernel:application">application(3)</seealso></p>
- </desc>
- </func>
- <func>
- <name>stop() -> ok </name>
- <fsummary>Stops the Ssl application.</fsummary>
- <desc>
- <p>Stops the Ssl application.
- <seealso marker="kernel:application">application(3)</seealso></p>
- </desc>
- </func>
-
- <func>
- <name>transport_accept(Socket) -></name>
- <name>transport_accept(Socket, Timeout) ->
- {ok, NewSocket} | {error, Reason}</name>
- <fsummary>Accept an incoming connection and
- prepare for <c>ssl_accept</c></fsummary>
- <type>
- <v>Socket = NewSocket = sslsocket()</v>
- <v>Timeout = integer()</v>
- <v>Reason = reason()</v>
- </type>
- <desc>
- <p>Accepts an incoming connection request on a listen socket.
- <c>ListenSocket</c> must be a socket returned from
- <c>listen/2</c>. The socket returned should be passed to
- <c>ssl_accept</c> to complete ssl handshaking and
- establishing the connection.</p>
- <warning>
- <p>The socket returned can only be used with <c>ssl_accept</c>,
- no traffic can be sent or received before that call.</p>
- </warning>
- <p>The accepted socket inherits the options set for
- <c>ListenSocket</c> in <c>listen/2</c>.</p>
- <p>The default
- value for <c>Timeout</c> is <c>infinity</c>. If
- <c>Timeout</c> is specified, and no connection is accepted
- within the given time, <c>{error, timeout}</c> is
- returned.</p>
- </desc>
- </func>
-
- <func>
- <name>versions() ->
- [{SslAppVer, SupportedSslVer, AvailableSslVsn}]</name>
- <fsummary>Returns version information relevant for the
- ssl application.</fsummary>
- <type>
- <v>SslAppVer = string()</v>
- <v>SupportedSslVer = [protocol()]</v>
- <v>AvailableSslVsn = [protocol()]</v>
- </type>
- <desc>
- <p>
- Returns version information relevant for the
- ssl application.</p>
- </desc>
- </func>
- </funcs>
-
- <section>
- <title>SEE ALSO</title>
- <p><seealso marker="kernel:inet">inet(3) </seealso> and
- <seealso marker="kernel:gen_tcp">gen_tcp(3) </seealso>
- </p>
- </section>
-
-</erlref>
-
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 2dd11bc88e..151b685941 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -31,6 +31,153 @@
<p>This document describes the changes made to the SSL application.
</p>
+ <section><title>SSL 4.0</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ New ssl now support client/server-certificates signed by
+ dsa keys.</p>
+ <p>
+ Own Id: OTP-8587</p>
+ </item>
+ <item>
+ <p>
+ Ssl has now switched default implementation and removed
+ deprecated certificate handling. All certificate handling
+ is done by the public_key application.</p>
+ <p>
+ Own Id: OTP-8695</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
+
+
+ <section><title>SSL 3.11.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed handling of several ssl/tls packets arriving at the
+ same time. This was broken during a refactoring of the
+ code.</p>
+ <p>
+ Own Id: OTP-8679</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Added missing checks for padding and Mac value. Removed
+ code for export ciphers and DH certificates as we decided
+ not to support them.</p>
+ <p>
+ Own Id: OTP-7047</p>
+ </item>
+ <item>
+ <p>
+ New ssl will no longer return esslerrssl to be backwards
+ compatible with old ssl as this hids infomation from the
+ user. format_error/1 has been updated to support new ssl.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7049</p>
+ </item>
+ <item>
+ <p>
+ New ssl now supports secure renegotiation as described by
+ RFC 5746.</p>
+ <p>
+ Own Id: OTP-8568</p>
+ </item>
+ <item>
+ <p>
+ Alert handling has been improved to better handle
+ unexpected but valid messages and the implementation is
+ also changed to avoid timing related issues that could
+ cause different error messages depending on network
+ latency. Packet handling was sort of broken but would
+ mostly work as expected when socket was in binary mode.
+ This has now been fixed.</p>
+ <p>
+ Own Id: OTP-8588</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 3.11</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixes handling of the option fail_if_no_peer_cert and
+ some undocumented options. Thanks to Rory Byrne.</p>
+ <p>
+ Own Id: OTP-8557</p>
+ </item>
+ </list>
+ </section>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Support for Diffie-Hellman. ssl-3.11 requires
+ public_key-0.6.</p>
+ <p>
+ Own Id: OTP-7046</p>
+ </item>
+ <item>
+ <p>
+ New ssl now properly handles ssl renegotiation, and
+ initiates a renegotiation if ssl/ltls-sequence numbers
+ comes close to the max value. However RFC-5746 is not yet
+ supported, but will be in an upcoming release.</p>
+ <p>
+ Own Id: OTP-8517</p>
+ </item>
+ <item>
+ <p>
+ When gen_tcp is configured with the {packet,http} option,
+ it automatically switches to expect HTTP Headers after a
+ HTTP Request/Response line has been received. This update
+ fixes ssl to behave in the same way. Thanks to Rory
+ Byrne.</p>
+ <p>
+ Own Id: OTP-8545</p>
+ </item>
+ <item>
+ <p>
+ Ssl now correctly verifies the extended_key_usage
+ extension and also allows the user to verify application
+ specific extensions by supplying an appropriate fun.</p>
+ <p>
+ Own Id: OTP-8554 Aux Id: OTP-8553 </p>
+ </item>
+ <item>
+ <p>
+ Fixed ssl:transport_accept/2 to return properly when
+ socket is closed. Thanks to Rory Byrne.</p>
+ <p>
+ Own Id: OTP-8560</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 3.10.9</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/doc/src/old_ssl.xml b/lib/ssl/doc/src/old_ssl.xml
new file mode 100644
index 0000000000..0d2e1afdbd
--- /dev/null
+++ b/lib/ssl/doc/src/old_ssl.xml
@@ -0,0 +1,709 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1999</year><year>2010</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>ssl</title>
+ <prepared>Peter H&ouml;gfeldt</prepared>
+ <responsible>Peter H&ouml;gfeldt</responsible>
+ <docno></docno>
+ <approved>Peter H&ouml;gfeldt</approved>
+ <checked></checked>
+ <date>2003-03-25</date>
+ <rev>D</rev>
+ <file>old_ssl.xml</file>
+ </header>
+ <module>old_ssl</module>
+ <modulesummary>Interface Functions for Secure Socket Layer</modulesummary>
+ <description>
+ <p>This module contains interface functions to the Secure Socket Layer.</p>
+ </description>
+
+ <section>
+ <title>General</title>
+
+ <p>This manual page describes functions that are defined
+ in the ssl module and represents the old ssl implementation
+ that coexists with the new one until it has been
+ totally phased out. </p>
+
+ <p>The old implementation can be
+ accessed by providing the option {ssl_imp, old} to the
+ ssl:connect and ssl:listen functions.</p>
+
+ <p>The reader is advised to also read the <c>ssl(6)</c> manual page
+ describing the SSL application.
+ </p>
+ <warning>
+ <p>It is strongly advised to seed the random generator after
+ the ssl application has been started (see <c>seed/1</c>
+ below), and before any connections are established. Although
+ the port program interfacing to the ssl libraries does a
+ "random" seeding of its own in order to make everything work
+ properly, that seeding is by no means random for the world
+ since it has a constant value which is known to everyone
+ reading the source code of the port program.</p>
+ </warning>
+ </section>
+
+ <section>
+ <title>Common data types</title>
+ <p>The following datatypes are used in the functions below:
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>options() = [option()]</c></p>
+ </item>
+ <item>
+ <p><c>option() = socketoption() | ssloption()</c></p>
+ </item>
+ <item>
+ <p><c>socketoption() = {mode, list} | {mode, binary} | binary | {packet, packettype()} | {header, integer()} | {nodelay, boolean()} | {active, activetype()} | {backlog, integer()} | {ip, ipaddress()} | {port, integer()}</c></p>
+ </item>
+ <item>
+ <p><c>ssloption() = {verify, code()} | {depth, depth()} | {certfile, path()} | {keyfile, path()} | {password, string()} | {cacertfile, path()} | {ciphers, string()}</c></p>
+ </item>
+ <item>
+ <p><c>packettype()</c> (see inet(3))</p>
+ </item>
+ <item>
+ <p><c>activetype()</c> (see inet(3))</p>
+ </item>
+ <item>
+ <p><c>reason() = atom() | {atom(), string()}</c></p>
+ </item>
+ <item>
+ <p><c>bytes() = [byte()]</c></p>
+ </item>
+ <item>
+ <p><c>string() = [byte()]</c></p>
+ </item>
+ <item>
+ <p><c>byte() = 0 | 1 | 2 | ... | 255</c></p>
+ </item>
+ <item>
+ <p><c>code() = 0 | 1 | 2</c></p>
+ </item>
+ <item>
+ <p><c>depth() = byte()</c></p>
+ </item>
+ <item>
+ <p><c>address() = hostname() | ipstring() | ipaddress()</c></p>
+ </item>
+ <item>
+ <p><c>ipaddress() = ipstring() | iptuple()</c></p>
+ </item>
+ <item>
+ <p><c>hostname() = string()</c></p>
+ </item>
+ <item>
+ <p><c>ipstring() = string()</c></p>
+ </item>
+ <item>
+ <p><c>iptuple() = {byte(), byte(), byte(), byte()}</c></p>
+ </item>
+ <item>
+ <p><c>sslsocket()</c></p>
+ </item>
+ <item>
+ <p><c>protocol() = sslv2 | sslv3 | tlsv1</c></p>
+ </item>
+ <item>
+ <p><c></c></p>
+ </item>
+ </list>
+ <p>The socket option <c>{backlog, integer()}</c> is for
+ <c>listen/2</c> only, and the option <c>{port, integer()}</c>
+ is for <c>connect/3/4</c> only.
+ </p>
+ <p>The following socket options are set by default: <c>{mode, list}</c>, <c>{packet, 0}</c>, <c>{header, 0}</c>, <c>{nodelay, false}</c>, <c>{active, true}</c>, <c>{backlog, 5}</c>,
+ <c>{ip, {0,0,0,0}}</c>, and <c>{port, 0}</c>.
+ </p>
+ <p>Note that the options <c>{mode, binary}</c> and <c>binary</c>
+ are equivalent. Similarly <c>{mode, list}</c> and the absence of
+ option <c>binary</c> are equivalent.
+ </p>
+ <p>The ssl options are for setting specific SSL parameters as follows:
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>{verify, code()}</c> Specifies type of verification:
+ 0 = do not verify peer; 1 = verify peer, 2 = verify peer,
+ fail if no peer certificate. The default value is 0.
+ </p>
+ </item>
+ <item>
+ <p><c>{depth, depth()}</c> Specifies the maximum
+ verification depth, i.e. how far in a chain of certificates
+ the verification process can proceed before the verification
+ is considered to fail.
+ </p>
+ <p>Peer certificate = 0, CA certificate = 1, higher level CA
+ certificate = 2, etc. The value 2 thus means that a chain
+ can at most contain peer cert, CA cert, next CA cert, and an
+ additional CA cert.
+ </p>
+ <p>The default value is 1.
+ </p>
+ </item>
+ <item>
+ <p><c>{certfile, path()}</c> Path to a file containing the
+ user's certificate.
+ chain of PEM encoded certificates.</p>
+ </item>
+ <item>
+ <p><c>{keyfile, path()}</c> Path to file containing user's
+ private PEM encoded key.</p>
+ </item>
+ <item>
+ <p><c>{password, string()}</c> String containing the user's
+ password. Only used if the private keyfile is password protected.</p>
+ </item>
+ <item>
+ <p><c>{cacertfile, path()}</c> Path to file containing PEM encoded
+ CA certificates (trusted certificates used for verifying a peer
+ certificate).</p>
+ </item>
+ <item>
+ <p><c>{ciphers, string()}</c> String of ciphers as a colon
+ separated list of ciphers. The function <c>ciphers/0</c> can
+ be used to find all available ciphers.</p>
+ </item>
+ </list>
+ <p>The type <c>sslsocket()</c> is opaque to the user.
+ </p>
+ <p>The owner of a socket is the one that created it by a call to
+ <c>transport_accept/[1,2]</c>, <c>connect/[3,4]</c>,
+ or <c>listen/2</c>.
+ </p>
+ <p>When a socket is in active mode (the default), data from the
+ socket is delivered to the owner of the socket in the form of
+ messages:
+ </p>
+ <list type="bulleted">
+ <item>
+ <p><c>{ssl, Socket, Data}</c></p>
+ </item>
+ <item>
+ <p><c>{ssl_closed, Socket}</c></p>
+ </item>
+ <item>
+ <p><c>{ssl_error, Socket, Reason}</c></p>
+ </item>
+ </list>
+ <p>A <c>Timeout</c> argument specifies a timeout in milliseconds. The
+ default value for a <c>Timeout</c> argument is <c>infinity</c>.
+ </p>
+ <p>Functions listed below may return the value <c>{error, closed}</c>, which only indicates that the SSL socket is
+ considered closed for the operation in question. It is for
+ instance possible to have <c>{error, closed}</c> returned from
+ an call to <c>send/2</c>, and a subsequent call to <c>recv/3</c>
+ returning <c>{ok, Data}</c>.
+ </p>
+ <p>Hence a return value of <c>{error, closed}</c> must not be
+ interpreted as if the socket was completely closed. On the
+ contrary, in order to free all resources occupied by an SSL
+ socket, <c>close/1</c> must be called, or else the process owning
+ the socket has to terminate.
+ </p>
+ <p>For each SSL socket there is an Erlang process representing the
+ socket. When a socket is opened, that process links to the
+ calling client process. Implementations that want to detect
+ abnormal exits from the socket process by receiving <c>{'EXIT', Pid, Reason}</c> messages, should use the function <c>pid/1</c>
+ to retrieve the process identifier from the socket, in order to
+ be able to match exit messages properly.</p>
+ </section>
+ <funcs>
+ <func>
+ <name>ciphers() -> {ok, string()} | {error, enotstarted}</name>
+ <fsummary>Get supported ciphers.</fsummary>
+ <desc>
+ <p>Returns a string consisting of colon separated cipher
+ designations that are supported by the current SSL library
+ implementation.
+ </p>
+ <p>The SSL application has to be started to return the string
+ of ciphers.</p>
+ </desc>
+ </func>
+ <func>
+ <name>close(Socket) -> ok | {error, Reason}</name>
+ <fsummary>Close a socket returned by <c>transport_accept/[1,2]</c>, <c>connect/3/4</c>, or <c>listen/2</c>.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ </type>
+ <desc>
+ <p>Closes a socket returned by <c>transport_accept/[1,2]</c>,
+ <c>connect/[3,4]</c>, or <c>listen/2</c></p>
+ </desc>
+ </func>
+ <func>
+ <name>connect(Address, Port, Options) -> {ok, Socket} | {error, Reason}</name>
+ <name>connect(Address, Port, Options, Timeout) -> {ok, Socket} | {error, Reason}</name>
+ <fsummary>Connect to <c>Port</c>at <c>Address</c>.</fsummary>
+ <type>
+ <v>Address = address()</v>
+ <v>Port = integer()</v>
+ <v>Options = [connect_option()]</v>
+ <v>connect_option() = {mode, list} | {mode, binary} | binary | {packet, packettype()} | {header, integer()} | {nodelay, boolean()} | {active, activetype()} | {ip, ipaddress()} | {port, integer()} | {verify, code()} | {depth, depth()} | {certfile, path()} | {keyfile, path()} | {password, string()} | {cacertfile, path()} | {ciphers, string()}</v>
+ <v>Timeout = integer()</v>
+ <v>Socket = sslsocket()</v>
+ </type>
+ <desc>
+ <p>Connects to <c>Port</c> at <c>Address</c>. If the optional
+ <c>Timeout</c> argument is specified, and a connection could not
+ be established within the given time, <c>{error, timeout}</c> is
+ returned. The default value for <c>Timeout</c> is <c>infinity</c>.
+ </p>
+ <p>The <c>ip</c> and <c>port</c> options are for binding to a
+ particular <em>local</em> address and port, respectively.</p>
+ </desc>
+ </func>
+ <func>
+ <name>connection_info(Socket) -> {ok, {Protocol, Cipher}} | {error, Reason}</name>
+ <fsummary>Get current protocol version and cipher.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Protocol = protocol()</v>
+ <v>Cipher = string()</v>
+ </type>
+ <desc>
+ <p>Gets the chosen protocol version and cipher for an established
+ connection (accepted och connected). </p>
+ </desc>
+ </func>
+ <func>
+ <name>controlling_process(Socket, NewOwner) -> ok | {error, Reason}</name>
+ <fsummary>Assign a new controlling process to the socket.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>NewOwner = pid()</v>
+ </type>
+ <desc>
+ <p>Assigns a new controlling process to <c>Socket</c>. A controlling
+ process is the owner of a socket, and receives all messages from
+ the socket.</p>
+ </desc>
+ </func>
+ <func>
+ <name>format_error(ErrorCode) -> string()</name>
+ <fsummary>Return an error string.</fsummary>
+ <type>
+ <v>ErrorCode = term()</v>
+ </type>
+ <desc>
+ <p>Returns a diagnostic string describing an error.</p>
+ </desc>
+ </func>
+ <func>
+ <name>getopts(Socket, OptionsTags) -> {ok, Options} | {error, Reason}</name>
+ <fsummary>Get options set for socket</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>OptionTags = [optiontag()]()</v>
+ </type>
+ <desc>
+ <p>Returns the options the tags of which are <c>OptionTags</c> for
+ for the socket <c>Socket</c>. </p>
+ </desc>
+ </func>
+ <func>
+ <name>listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}</name>
+ <fsummary>Set up a socket to listen on a port on the local host.</fsummary>
+ <type>
+ <v>Port = integer()</v>
+ <v>Options = [listen_option()]</v>
+ <v>listen_option() = {mode, list} | {mode, binary} | binary | {packet, packettype()} | {header, integer()} | {active, activetype()} | {backlog, integer()} | {ip, ipaddress()} | {verify, code()} | {depth, depth()} | {certfile, path()} | {keyfile, path()} | {password, string()} | {cacertfile, path()} | {ciphers, string()}</v>
+ <v>ListenSocket = sslsocket()</v>
+ </type>
+ <desc>
+ <p>Sets up a socket to listen on port <c>Port</c> at the local host.
+ If <c>Port</c> is zero, <c>listen/2</c> picks an available port
+ number (use <c>port/1</c> to retrieve it).
+ </p>
+ <p>The listen queue size defaults to 5. If a different value is
+ wanted, the option <c>{backlog, Size}</c> should be added to the
+ list of options.
+ </p>
+ <p>An empty <c>Options</c> list is considered an error, and
+ <c>{error, enooptions}</c> is returned.
+ </p>
+ <p>The returned <c>ListenSocket</c> can only be used in calls to
+ <c>transport_accept/[1,2]</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>peercert(Socket) -> {ok, Cert} | {error, Reason}</name>
+ <fsummary>Return the peer certificate.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Cert = binary()()</v>
+ <v>Subject = term()()</v>
+ </type>
+ <desc>
+ <p>Returns the DER encoded peer certificate, the certificate can be decoded with
+ <c>public_key:pkix_decode_cert/2</c>.
+ </p>
+ </desc>
+ </func>
+ <func>
+ <name>peername(Socket) -> {ok, {Address, Port}} | {error, Reason}</name>
+ <fsummary>Return peer address and port.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Address = ipaddress()</v>
+ <v>Port = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the address and port number of the peer.</p>
+ </desc>
+ </func>
+ <func>
+ <name>pid(Socket) -> pid()</name>
+ <fsummary>Return the pid of the socket process.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ </type>
+ <desc>
+ <p>Returns the pid of the socket process. The returned pid should
+ only be used for receiving exit messages.</p>
+ </desc>
+ </func>
+ <func>
+ <name>recv(Socket, Length) -> {ok, Data} | {error, Reason}</name>
+ <name>recv(Socket, Length, Timeout) -> {ok, Data} | {error, Reason}</name>
+ <fsummary>Receive data on socket.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Length = integer() >= 0</v>
+ <v>Timeout = integer()</v>
+ <v>Data = bytes() | binary()</v>
+ </type>
+ <desc>
+ <p>Receives data on socket <c>Socket</c> when the socket is in
+ passive mode, i.e. when the option <c>{active, false}</c>
+ has been specified.
+ </p>
+ <p>A notable return value is <c>{error, closed}</c> which
+ indicates that the socket is closed.
+ </p>
+ <p>A positive value of the <c>Length</c> argument is only
+ valid when the socket is in raw mode (option <c>{packet, 0}</c> is set, and the option <c>binary</c> is <em>not</em>
+ set); otherwise it should be set to 0, whence all available
+ bytes are returned.
+ </p>
+ <p>If the optional <c>Timeout</c> parameter is specified, and
+ no data was available within the given time, <c>{error, timeout}</c> is returned. The default value for
+ <c>Timeout</c> is <c>infinity</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>seed(Data) -> ok | {error, Reason}</name>
+ <fsummary>Seed the ssl random generator.</fsummary>
+ <type>
+ <v>Data = iolist() | binary()</v>
+ </type>
+ <desc>
+ <p>Seeds the ssl random generator.
+ </p>
+ <p>It is strongly advised to seed the random generator after
+ the ssl application has been started, and before any
+ connections are established. Although the port program
+ interfacing to the OpenSSL libraries does a "random" seeding
+ of its own in order to make everything work properly, that
+ seeding is by no means random for the world since it has a
+ constant value which is known to everyone reading the source
+ code of the seeding.
+ </p>
+ <p>A notable return value is <c>{error, edata}}</c> indicating that
+ <c>Data</c> was not a binary nor an iolist.</p>
+ </desc>
+ </func>
+ <func>
+ <name>send(Socket, Data) -> ok | {error, Reason}</name>
+ <fsummary>Write data to a socket.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Data = iolist() | binary()</v>
+ </type>
+ <desc>
+ <p>Writes <c>Data</c> to <c>Socket</c>. </p>
+ <p>A notable return value is <c>{error, closed}</c> indicating that
+ the socket is closed.</p>
+ </desc>
+ </func>
+ <func>
+ <name>setopts(Socket, Options) -> ok | {error, Reason}</name>
+ <fsummary>Set socket options.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Options = [socketoption]()</v>
+ </type>
+ <desc>
+ <p>Sets options according to <c>Options</c> for the socket
+ <c>Socket</c>. </p>
+ </desc>
+ </func>
+ <func>
+ <name>ssl_accept(Socket) -> ok | {error, Reason}</name>
+ <name>ssl_accept(Socket, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Perform server-side SSL handshake and key exchange</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Timeout = integer()</v>
+ <v>Reason = atom()</v>
+ </type>
+ <desc>
+ <p>The <c>ssl_accept</c> function establish the SSL connection
+ on the server side. It should be called directly after
+ <c>transport_accept</c>, in the spawned server-loop.</p>
+ <p>Note that the ssl connection is not complete until <c>ssl_accept</c>
+ has returned <c>true</c>, and if an error is returned, the socket
+ is unavailable and for instance <c>close/1</c> will crash.</p>
+ </desc>
+ </func>
+ <func>
+ <name>sockname(Socket) -> {ok, {Address, Port}} | {error, Reason}</name>
+ <fsummary>Return the local address and port.</fsummary>
+ <type>
+ <v>Socket = sslsocket()</v>
+ <v>Address = ipaddress()</v>
+ <v>Port = integer()</v>
+ </type>
+ <desc>
+ <p>Returns the local address and port number of the socket
+ <c>Socket</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>transport_accept(Socket) -> {ok, NewSocket} | {error, Reason}</name>
+ <name>transport_accept(Socket, Timeout) -> {ok, NewSocket} | {error, Reason}</name>
+ <fsummary>Accept an incoming connection and prepare for <c>ssl_accept</c></fsummary>
+ <type>
+ <v>Socket = NewSocket = sslsocket()</v>
+ <v>Timeout = integer()</v>
+ <v>Reason = atom()</v>
+ </type>
+ <desc>
+ <p>Accepts an incoming connection request on a listen socket.
+ <c>ListenSocket</c> must be a socket returned from <c>listen/2</c>.
+ The socket returned should be passed to <c>ssl_accept</c> to
+ complete ssl handshaking and establishing the connection.</p>
+ <warning>
+ <p>The socket returned can only be used with <c>ssl_accept</c>,
+ no traffic can be sent or received before that call.</p>
+ </warning>
+ <p>The accepted socket inherits the options set for <c>ListenSocket</c>
+ in <c>listen/2</c>.</p>
+ <p>The default value for <c>Timeout</c> is <c>infinity</c>. If
+ <c>Timeout</c> is specified, and no connection is accepted within
+ the given time, <c>{error, timeout}</c> is returned.</p>
+ </desc>
+ </func>
+ <func>
+ <name>version() -> {ok, {SSLVsn, CompVsn, LibVsn}}</name>
+ <fsummary>Return the version of SSL.</fsummary>
+ <type>
+ <v>SSLVsn = CompVsn = LibVsn = string()()</v>
+ </type>
+ <desc>
+ <p>Returns the SSL application version (<c>SSLVsn</c>), the library
+ version used when compiling the SSL application port program
+ (<c>CompVsn</c>), and the actual library version used when
+ dynamically linking in runtime (<c>LibVsn</c>).
+ </p>
+ <p>If the SSL application has not been started, <c>CompVsn</c> and
+ <c>LibVsn</c> are empty strings.
+ </p>
+ </desc>
+ </func>
+ </funcs>
+
+ <section>
+ <title>ERRORS</title>
+ <p>The possible error reasons and the corresponding diagnostic strings
+ returned by <c>format_error/1</c> are either the same as those defined
+ in the <c>inet(3)</c> reference manual, or as follows:
+ </p>
+ <taglist>
+ <tag><c>closed</c></tag>
+ <item>
+ <p>Connection closed for the operation in question.
+ </p>
+ </item>
+ <tag><c>ebadsocket</c></tag>
+ <item>
+ <p>Connection not found (internal error).
+ </p>
+ </item>
+ <tag><c>ebadstate</c></tag>
+ <item>
+ <p>Connection not in connect state (internal error).
+ </p>
+ </item>
+ <tag><c>ebrokertype</c></tag>
+ <item>
+ <p>Wrong broker type (internal error).
+ </p>
+ </item>
+ <tag><c>ecacertfile</c></tag>
+ <item>
+ <p>Own CA certificate file is invalid.
+ </p>
+ </item>
+ <tag><c>ecertfile</c></tag>
+ <item>
+ <p>Own certificate file is invalid.
+ </p>
+ </item>
+ <tag><c>echaintoolong</c></tag>
+ <item>
+ <p>The chain of certificates provided by peer is too long.
+ </p>
+ </item>
+ <tag><c>ecipher</c></tag>
+ <item>
+ <p>Own list of specified ciphers is invalid.
+ </p>
+ </item>
+ <tag><c>ekeyfile</c></tag>
+ <item>
+ <p>Own private key file is invalid.
+ </p>
+ </item>
+ <tag><c>ekeymismatch</c></tag>
+ <item>
+ <p>Own private key does not match own certificate.
+ </p>
+ </item>
+ <tag><c>enoissuercert</c></tag>
+ <item>
+ <p>Cannot find certificate of issuer of certificate provided
+ by peer.
+ </p>
+ </item>
+ <tag><c>enoservercert</c></tag>
+ <item>
+ <p>Attempt to do accept without having set own certificate.
+ </p>
+ </item>
+ <tag><c>enotlistener</c></tag>
+ <item>
+ <p>Attempt to accept on a non-listening socket.
+ </p>
+ </item>
+ <tag><c>enoproxysocket</c></tag>
+ <item>
+ <p>No proxy socket found (internal error).
+ </p>
+ </item>
+ <tag><c>enooptions</c></tag>
+ <item>
+ <p>The list of options is empty.
+ </p>
+ </item>
+ <tag><c>enotstarted</c></tag>
+ <item>
+ <p>The SSL application has not been started.
+ </p>
+ </item>
+ <tag><c>eoptions</c></tag>
+ <item>
+ <p>Invalid list of options.
+ </p>
+ </item>
+ <tag><c>epeercert</c></tag>
+ <item>
+ <p>Certificate provided by peer is in error.
+ </p>
+ </item>
+ <tag><c>epeercertexpired</c></tag>
+ <item>
+ <p>Certificate provided by peer has expired.
+ </p>
+ </item>
+ <tag><c>epeercertinvalid</c></tag>
+ <item>
+ <p>Certificate provided by peer is invalid.
+ </p>
+ </item>
+ <tag><c>eselfsignedcert</c></tag>
+ <item>
+ <p>Certificate provided by peer is self signed.
+ </p>
+ </item>
+ <tag><c>esslaccept</c></tag>
+ <item>
+ <p>Server SSL handshake procedure between client and server failed.
+ </p>
+ </item>
+ <tag><c>esslconnect</c></tag>
+ <item>
+ <p>Client SSL handshake procedure between client and server failed.
+ </p>
+ </item>
+ <tag><c>esslerrssl</c></tag>
+ <item>
+ <p>SSL protocol failure. Typically because of a fatal alert
+ from peer.
+ </p>
+ </item>
+ <tag><c>ewantconnect</c></tag>
+ <item>
+ <p>Protocol wants to connect, which is not supported in
+ this version of the SSL application.
+ </p>
+ </item>
+ <tag><c>ex509lookup</c></tag>
+ <item>
+ <p>Protocol wants X.509 lookup, which is not supported in
+ this version of the SSL application.
+ </p>
+ </item>
+ <tag><c>{badcall, Call}</c></tag>
+ <item>
+ <p>Call not recognized for current mode (active or passive) and
+ state of socket.
+ </p>
+ </item>
+ <tag><c>{badcast, Cast}</c></tag>
+ <item>
+ <p>Call not recognized for current mode (active or passive) and
+ state of socket.
+ </p>
+ </item>
+ <tag><c>{badinfo, Info}</c></tag>
+ <item>
+ <p>Call not recognized for current mode (active or passive) and
+ state of socket.
+ </p>
+ </item>
+ </taglist>
+ </section>
+
+ <section>
+ <title>SEE ALSO</title>
+ <p>gen_tcp(3), inet(3) public_key(3) </p>
+ </section>
+
+</erlref>
+
+
diff --git a/lib/ssl/doc/src/refman.xml b/lib/ssl/doc/src/refman.xml
index 3ad5a01b46..68f84660f3 100644
--- a/lib/ssl/doc/src/refman.xml
+++ b/lib/ssl/doc/src/refman.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1999</year><year>2009</year>
+ <year>1999</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>SSL Reference Manual</title>
@@ -45,7 +45,8 @@
</description>
<xi:include href="ssl_app.xml"/>
<xi:include href="ssl.xml"/>
- <xi:include href="new_ssl.xml"/>
+ <xi:include href="old_ssl.xml"/>
+ <xi:include href="ssl_session_cache_api.xml"/>
</application>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 217eb791d0..def61bcf03 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1999</year><year>2009</year>
+ <year>1999</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,355 +13,429 @@
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>
+ </legalnotice>
<title>ssl</title>
- <prepared>Peter H&ouml;gfeldt</prepared>
- <responsible>Peter H&ouml;gfeldt</responsible>
- <docno></docno>
- <approved>Peter H&ouml;gfeldt</approved>
- <checked></checked>
- <date>2003-03-25</date>
- <rev>D</rev>
- <file>ssl.sgml</file>
+ <file>ssl.xml</file>
</header>
<module>ssl</module>
<modulesummary>Interface Functions for Secure Socket Layer</modulesummary>
<description>
- <p>This module contains interface functions to the Secure Socket Layer.</p>
+ <p>This module contains interface functions to the Secure Socket
+ Layer.
+ </p>
</description>
+
+ <section>
+ <title>SSL</title>
+ <list type="bulleted">
+ <item>ssl requires the crypto an public_key applications.</item>
+ <item>Supported SSL/TLS-versions are SSL-3.0 and TLS-1.0 </item>
+ <item>For security reasons sslv2 is not supported.</item>
+ <item>Ephemeral Diffie-Hellman cipher suites are supported
+ but not Diffie Hellman Certificates cipher suites.</item>
+ <item>Export cipher suites are not supported as the
+ U.S. lifted its export restrictions in early 2000.</item>
+ <item>CRL and policy certificate
+ extensions are not supported yet. </item>
+ </list>
+
+ </section>
+
<section>
- <title>General</title>
+ <title>COMMON DATA TYPES</title>
+ <p>The following data types are used in the functions below:
+ </p>
- <p>There is a new implementation of ssl available in
- this module but until it is 100 % complete, so that it can replace
- the old implementation in all aspects it will be
- described here <seealso marker="new_ssl"> new ssl API </seealso></p>
+ <p><c>boolean() = true | false</c></p>
- <p>The reader is advised to also read the <c>ssl(6)</c> manual page
- describing the SSL application.
- </p>
- <warning>
- <p>It is strongly advised to seed the random generator after
- the ssl application has been started (see <c>seed/1</c>
- below), and before any connections are established. Although
- the port program interfacing to the ssl libraries does a
- "random" seeding of its own in order to make everything work
- properly, that seeding is by no means random for the world
- since it has a constant value which is known to everyone
- reading the source code of the port program.</p>
- </warning>
- </section>
+ <p><c>property() = atom()</c></p>
+
+ <p><c>option() = socketoption() | ssloption() | transportoption()</c></p>
- <section>
- <title>Common data types</title>
- <p>The following datatypes are used in the functions below:
- </p>
- <list type="bulleted">
- <item>
- <p><c>options() = [option()]</c></p>
- </item>
- <item>
- <p><c>option() = socketoption() | ssloption()</c></p>
- </item>
- <item>
- <p><c>socketoption() = {mode, list} | {mode, binary} | binary | {packet, packettype()} | {header, integer()} | {nodelay, boolean()} | {active, activetype()} | {backlog, integer()} | {ip, ipaddress()} | {port, integer()}</c></p>
- </item>
- <item>
- <p><c>ssloption() = {verify, code()} | {depth, depth()} | {certfile, path()} | {keyfile, path()} | {password, string()} | {cacertfile, path()} | {ciphers, string()}</c></p>
- </item>
- <item>
- <p><c>packettype()</c> (see inet(3))</p>
- </item>
- <item>
- <p><c>activetype()</c> (see inet(3))</p>
- </item>
- <item>
- <p><c>reason() = atom() | {atom(), string()}</c></p>
- </item>
- <item>
- <p><c>bytes() = [byte()]</c></p>
- </item>
- <item>
- <p><c>string() = [byte()]</c></p>
- </item>
- <item>
- <p><c>byte() = 0 | 1 | 2 | ... | 255</c></p>
- </item>
- <item>
- <p><c>code() = 0 | 1 | 2</c></p>
- </item>
- <item>
- <p><c>depth() = byte()</c></p>
- </item>
- <item>
- <p><c>address() = hostname() | ipstring() | ipaddress()</c></p>
- </item>
- <item>
- <p><c>ipaddress() = ipstring() | iptuple()</c></p>
- </item>
- <item>
- <p><c>hostname() = string()</c></p>
- </item>
- <item>
- <p><c>ipstring() = string()</c></p>
- </item>
- <item>
- <p><c>iptuple() = {byte(), byte(), byte(), byte()}</c></p>
- </item>
- <item>
- <p><c>sslsocket()</c></p>
- </item>
- <item>
- <p><c>protocol() = sslv2 | sslv3 | tlsv1</c></p>
+ <p><c>socketoption() = [{property(), term()}] - defaults to
+ [{mode,list},{packet, 0},{header, 0},{active, true}].
+ </c></p>
+
+ <p>For valid options
+ see <seealso marker="kernel:inet">inet(3) </seealso> and
+ <seealso marker="kernel:gen_tcp">gen_tcp(3) </seealso>.
+ </p>
+
+ <p> <c>ssloption() = {verify, verify_type()} |
+ {fail_if_no_peer_cert, boolean()}
+ {depth, integer()} |
+ {certfile, path()} | {keyfile, path()} | {password, string()} |
+ {cacertfile, path()} | {dhfile, path()} | {ciphers, ciphers()} |
+ {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()}
+ </c></p>
+
+ <p><c>transportoption() = {CallbackModule, DataTag, ClosedTag}
+ - defaults to {gen_tcp, tcp, tcp_closed}. Ssl may be
+ run over any reliable transport protocol that has
+ an equivalent API to gen_tcp's.</c></p>
+
+ <p><c>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CallbackModule =
+ atom()</c>
+ </p> <p><c>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DataTag =
+ atom() - tag used in socket data message.</c></p>
+ <p><c>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ClosedTag = atom() - tag used in
+ socket close message.</c></p>
+
+ <p><c>verify_type() = verify_none | verify_peer</c></p>
+
+ <p><c>path() = string() - representing a file path.</c></p>
+
+ <p><c>host() = hostname() | ipaddress()</c></p>
+
+ <p><c>hostname() = string()</c></p>
+
+ <p><c>
+ ip_address() = {N1,N2,N3,N4} % IPv4
+ | {K1,K2,K3,K4,K5,K6,K7,K8} % IPv6 </c></p>
+
+ <p><c>sslsocket() - opaque to the user. </c></p>
+
+ <p><c>protocol() = sslv3 | tlsv1 </c></p>
+
+ <p><c>ciphers() = [ciphersuite()] | string() (according to old API)</c></p>
+
+ <p><c>ciphersuite() =
+ {key_exchange(), cipher(), hash()}</c></p>
+
+ <p><c>key_exchange() = rsa | dhe_dss | dhe_rsa
+ </c></p>
+
+ <p><c>cipher() = rc4_128 | des_cbc | '3des_ede_cbc'
+ | aes_128_cbc | aes_256_cbc </c></p>
+
+ <p> <c>hash() = md5 | sha
+ </c></p>
+
+ <p><c>ssl_imp() = new | old - default is new.</c></p>
+
+ </section>
+
+<section>
+ <title>SSL OPTION DESCRIPTIONS</title>
+
+ <taglist>
+ <tag>{verify, verify_type()}</tag>
+ <item> If <c>verify_none</c> is specified x509-certificate
+ path validation errors at the client side
+ will not automatically cause the connection to fail, as
+ it will if the verify type is <c>verify_peer</c>. See also
+ the option verify_fun.
+ Servers only do the path validation if <c>verify_peer</c> is set to
+ true, as it then will
+ send a certificate request to
+ the client (this message is not sent if the verify option is
+ <c>verify_none</c>) and you may then also want to specify
+ the option <c>fail_if_no_peer_cert</c>.
</item>
- <item>
- <p><c></c></p>
+
+ <tag>{fail_if_no_peer_cert, boolean()}</tag>
+ <item>Used together with {verify, verify_peer} by a ssl server.
+ If set to true,
+ the server will fail if the client does not have a certificate
+ to send, e.i sends a empty certificate, if set to false it will
+ only fail if the client sends a invalid certificate (an empty
+ certificate is considered valid).
+ </item>
+
+ <tag>{verify_fun, fun(ErrorList) -> boolean()}</tag>
+ <item>Used by the ssl client to determine if
+ x509-certificate path validations errors are acceptable or
+ if the connection should fail. Defaults to:
+
+<code>
+fun(ErrorList) ->
+ case lists:foldl(fun({bad_cert,unknown_ca}, Acc) ->
+ Acc;
+ (Other, Acc) ->
+ [Other | Acc]
+ end, [], ErrorList) of
+ [] ->
+ true;
+ [_|_] ->
+ false
+ end
+end
+</code>
+ I.e. by default if the only error found was that the CA-certificate
+ holder was unknown this will be accepted.
+
+ Possible errors in the error list are:
+ {bad_cert, cert_expired}, {bad_cert, invalid_issuer},
+ {bad_cert, invalid_signature}, {bad_cert, name_not_permitted},
+ {bad_cert, unknown_ca},
+ {bad_cert, cert_expired}, {bad_cert, invalid_issuer},
+ {bad_cert, invalid_signature}, {bad_cert, name_not_permitted},
+ {bad_cert, cert_revoked} (not implemented yet),
+ {bad_cert, unknown_critical_extension} or {bad_cert, term()}
</item>
- </list>
- <p>The socket option <c>{backlog, integer()}</c> is for
- <c>listen/2</c> only, and the option <c>{port, integer()}</c>
- is for <c>connect/3/4</c> only.
- </p>
- <p>The following socket options are set by default: <c>{mode, list}</c>, <c>{packet, 0}</c>, <c>{header, 0}</c>, <c>{nodelay, false}</c>, <c>{active, true}</c>, <c>{backlog, 5}</c>,
- <c>{ip, {0,0,0,0}}</c>, and <c>{port, 0}</c>.
- </p>
- <p>Note that the options <c>{mode, binary}</c> and <c>binary</c>
- are equivalent. Similarly <c>{mode, list}</c> and the absence of
- option <c>binary</c> are equivalent.
- </p>
- <p>The ssl options are for setting specific SSL parameters as follows:
- </p>
- <list type="bulleted">
- <item>
- <p><c>{verify, code()}</c> Specifies type of verification:
- 0 = do not verify peer; 1 = verify peer, 2 = verify peer,
- fail if no peer certificate. The default value is 0.
- </p>
+
+
+ <tag>{validate_extensions_fun, fun()}</tag>
+ <item>
+ This options makes it possible to supply a fun to validate
+ possible application specific certificate extensions
+ during the certificat path validation. This option
+ will be better documented onec the public_key API is more
+ mature.
+ </item>
+
+ <tag>{depth, integer()}</tag>
+ <item>Specifies the maximum
+ verification depth, i.e. how far in a chain of certificates the
+ verification process can proceed before the verification is
+ considered to fail. Peer certificate = 0, CA certificate = 1,
+ higher level CA certificate = 2, etc. The value 2 thus means
+ that a chain can at most contain peer cert, CA cert, next CA
+ cert, and an additional CA cert. The default value is 1.
</item>
- <item>
- <p><c>{depth, depth()}</c> Specifies the maximum
- verification depth, i.e. how far in a chain of certificates
- the verification process can proceed before the verification
- is considered to fail.
- </p>
- <p>Peer certificate = 0, CA certificate = 1, higher level CA
- certificate = 2, etc. The value 2 thus means that a chain
- can at most contain peer cert, CA cert, next CA cert, and an
- additional CA cert.
- </p>
- <p>The default value is 1.
- </p>
+
+ <tag>{certfile, path()}</tag>
+ <item>Path to a file containing the
+ user's certificate. Optional for clients but note
+ that some servers requires that the client can certify
+ itself. </item>
+ <tag>{keyfile, path()}</tag>
+ <item>Path to file containing user's
+ private PEM encoded key. As PEM-files may contain several
+ entries this option defaults to the same file as given by
+ certfile option.</item>
+ <tag>{password, string()}</tag>
+ <item>String containing the user's password.
+ Only used if the private keyfile is password protected.
+ </item>
+ <tag>{cacertfile, path()}</tag>
+ <item>Path to file containing PEM encoded
+ CA certificates (trusted certificates used for verifying a peer
+ certificate). May be omitted if you do not want to verify
+ the peer.</item>
+
+ <tag>{dhfile, path()}</tag>
+ <item>Path to file containing PEM encoded Diffie Hellman parameters,
+ for the server to use if a cipher suite using Diffie Hellman key exchange
+ is negotiated. If not specified hardcode parameters will be used.
</item>
- <item>
- <p><c>{certfile, path()}</c> Path to a file containing the
- user's certificate.
- chain of PEM encoded certificates.</p>
+
+ <tag>{ciphers, ciphers()}</tag>
+ <item>The function <c>ciphers_suites/0</c> can
+ be used to find all available ciphers.
</item>
- <item>
- <p><c>{keyfile, path()}</c> Path to file containing user's
- private PEM encoded key.</p>
+
+ <tag>{ssl_imp, ssl_imp()}</tag>
+ <item>Specify which ssl implementation you want to use. Defaults to
+ new.
</item>
- <item>
- <p><c>{password, string()}</c> String containing the user's
- password. Only used if the private keyfile is password protected.</p>
+
+ <tag>{reuse_sessions, boolean()}</tag>
+ <item>Specifies if ssl sessions should be reused
+ when possible.
</item>
- <item>
- <p><c>{cacertfile, path()}</c> Path to file containing PEM encoded
- CA certificates (trusted certificates used for verifying a peer
- certificate).</p>
+
+ <tag>{reuse_session, fun(SuggestedSessionId,
+ PeerCert, Compression, CipherSuite) -> boolean()}</tag>
+ <item>Enables the ssl server to have a local policy
+ for deciding if a session should be reused or not,
+ only meaning full if <c>reuse_sessions</c> is set to true.
+ SuggestedSessionId is a binary(), PeerCert is a DER encoded
+ certificate, Compression is an enumeration integer
+ and CipherSuite of type ciphersuite().
</item>
- <item>
- <p><c>{ciphers, string()}</c> String of ciphers as a colon
- separated list of ciphers. The function <c>ciphers/0</c> can
- be used to find all available ciphers.</p>
+
+ <tag>{secure_renegotiate, boolean()}</tag>
+ <item>Specifies if to reject renegotiation attempt that does
+ not live up to RFC 5746. By default secure_renegotiate is
+ set to false e.i. secure renegotiation will be used if possible
+ but it will fallback to unsecure renegotiation if the peer
+ does not support RFC 5746.
</item>
- </list>
- <p>The type <c>sslsocket()</c> is opaque to the user.
- </p>
- <p>The owner of a socket is the one that created it by a call to
- <c>transport_accept/[1,2]</c>, <c>connect/[3,4]</c>,
- or <c>listen/2</c>.
- </p>
- <p>When a socket is in active mode (the default), data from the
+
+ </taglist>
+ </section>
+
+ <section>
+ <title>General</title>
+
+ <p>When a ssl socket is in active mode (the default), data from the
socket is delivered to the owner of the socket in the form of
messages:
- </p>
+ </p>
<list type="bulleted">
- <item>
- <p><c>{ssl, Socket, Data}</c></p>
+ <item>{ssl, Socket, Data}
</item>
- <item>
- <p><c>{ssl_closed, Socket}</c></p>
+ <item>{ssl_closed, Socket}
</item>
<item>
- <p><c>{ssl_error, Socket, Reason}</c></p>
+ {ssl_error, Socket, Reason}
</item>
</list>
+
<p>A <c>Timeout</c> argument specifies a timeout in milliseconds. The
default value for a <c>Timeout</c> argument is <c>infinity</c>.
- </p>
- <p>Functions listed below may return the value <c>{error, closed}</c>, which only indicates that the SSL socket is
- considered closed for the operation in question. It is for
- instance possible to have <c>{error, closed}</c> returned from
- an call to <c>send/2</c>, and a subsequent call to <c>recv/3</c>
- returning <c>{ok, Data}</c>.
- </p>
- <p>Hence a return value of <c>{error, closed}</c> must not be
- interpreted as if the socket was completely closed. On the
- contrary, in order to free all resources occupied by an SSL
- socket, <c>close/1</c> must be called, or else the process owning
- the socket has to terminate.
- </p>
- <p>For each SSL socket there is an Erlang process representing the
- socket. When a socket is opened, that process links to the
- calling client process. Implementations that want to detect
- abnormal exits from the socket process by receiving <c>{'EXIT', Pid, Reason}</c> messages, should use the function <c>pid/1</c>
- to retrieve the process identifier from the socket, in order to
- be able to match exit messages properly.</p>
+ </p>
</section>
+
<funcs>
<func>
- <name>ciphers() -> {ok, string()} | {error, enotstarted}</name>
- <fsummary>Get supported ciphers.</fsummary>
- <desc>
- <p>Returns a string consisting of colon separated cipher
- designations that are supported by the current SSL library
- implementation.
- </p>
- <p>The SSL application has to be started to return the string
- of ciphers.</p>
- </desc>
+ <name>cipher_suites() -></name>
+ <name>cipher_suites(Type) -> ciphers()</name>
+ <fsummary> Returns a list of supported cipher suites</fsummary>
+ <type>
+ <v>Type = erlang | openssl</v>
+
+ </type>
+ <desc><p>Returns a list of supported cipher suites.
+ cipher_suites() is equivalent to cipher_suites(erlang).
+ Type openssl is provided for backwards compatibility with
+ old ssl that used openssl.
+ </p>
+ </desc>
</func>
+
<func>
- <name>close(Socket) -> ok | {error, Reason}</name>
- <fsummary>Close a socket returned by <c>transport_accept/[1,2]</c>, <c>connect/3/4</c>, or <c>listen/2</c>.</fsummary>
+ <name>connect(Socket, SslOptions) -> </name>
+ <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket}
+ | {error, Reason}</name>
+ <fsummary> Upgrades a gen_tcp, or
+ equivalent, connected socket to a ssl socket. </fsummary>
<type>
- <v>Socket = sslsocket()</v>
+ <v>Socket = socket()</v>
+ <v>SslOptions = [ssloption()]</v>
+ <v>Timeout = integer() | infinity</v>
+ <v>SslSocket = sslsocket()</v>
+ <v>Reason = term()</v>
</type>
- <desc>
- <p>Closes a socket returned by <c>transport_accept/[1,2]</c>,
- <c>connect/[3,4]</c>, or <c>listen/2</c></p>
- </desc>
+ <desc> <p>Upgrades a gen_tcp, or equivalent,
+ connected socket to a ssl socket e.i performs the
+ client-side ssl handshake.</p>
+ </desc>
</func>
+
<func>
- <name>connect(Address, Port, Options) -> {ok, Socket} | {error, Reason}</name>
- <name>connect(Address, Port, Options, Timeout) -> {ok, Socket} | {error, Reason}</name>
- <fsummary>Connect to <c>Port</c>at <c>Address</c>.</fsummary>
+ <name>connect(Host, Port, Options) -></name>
+ <name>connect(Host, Port, Options, Timeout) ->
+ {ok, SslSocket} | {error, Reason}</name>
+ <fsummary>Opens an ssl connection to Host, Port. </fsummary>
<type>
- <v>Address = address()</v>
- <v>Port = integer()</v>
- <v>Options = [connect_option()]</v>
- <v>connect_option() = {mode, list} | {mode, binary} | binary | {packet, packettype()} | {header, integer()} | {nodelay, boolean()} | {active, activetype()} | {ip, ipaddress()} | {port, integer()} | {verify, code()} | {depth, depth()} | {certfile, path()} | {keyfile, path()} | {password, string()} | {cacertfile, path()} | {ciphers, string()}</v>
- <v>Timeout = integer()</v>
- <v>Socket = sslsocket()</v>
+ <v>Host = host()</v>
+ <v>Port = integer()</v>
+ <v>Options = [option()]</v>
+ <v>Timeout = integer() | infinity</v>
+ <v>SslSocket = sslsocket()</v>
+ <v>Reason = term()</v>
</type>
- <desc>
- <p>Connects to <c>Port</c> at <c>Address</c>. If the optional
- <c>Timeout</c> argument is specified, and a connection could not
- be established within the given time, <c>{error, timeout}</c> is
- returned. The default value for <c>Timeout</c> is <c>infinity</c>.
- </p>
- <p>The <c>ip</c> and <c>port</c> options are for binding to a
- particular <em>local</em> address and port, respectively.</p>
- </desc>
+ <desc> <p>Opens an ssl connection to Host, Port.</p> </desc>
</func>
+
<func>
- <name>connection_info(Socket) -> {ok, {Protocol, Cipher}} | {error, Reason}</name>
- <fsummary>Get current protocol version and cipher.</fsummary>
+ <name>close(SslSocket) -> ok | {error, Reason}</name>
+ <fsummary>Close a ssl connection</fsummary>
<type>
- <v>Socket = sslsocket()</v>
- <v>Protocol = protocol()</v>
- <v>Cipher = string()</v>
+ <v>SslSocket = sslsocket()</v>
+ <v>Reason = term()</v>
</type>
- <desc>
- <p>Gets the chosen protocol version and cipher for an established
- connection (accepted och connected). </p>
+ <desc><p>Close a ssl connection.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>controlling_process(SslSocket, NewOwner) ->
+ ok | {error, Reason}</name>
+
+ <fsummary>Assigns a new controlling process to the
+ ssl-socket.</fsummary>
+
+ <type>
+ <v>SslSocket = sslsocket()</v>
+ <v>NewOwner = pid()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc><p>Assigns a new controlling process to the ssl-socket. A
+ controlling process is the owner of a ssl-socket, and receives
+ all messages from the socket.</p>
</desc>
</func>
+
<func>
- <name>controlling_process(Socket, NewOwner) -> ok | {error, Reason}</name>
- <fsummary>Assign a new controlling process to the socket.</fsummary>
+ <name>connection_info(SslSocket) ->
+ {ok, {ProtocolVersion, CipherSuite}} | {error, Reason} </name>
+ <fsummary>Returns the negotiated protocol version and cipher suite.
+ </fsummary>
<type>
- <v>Socket = sslsocket()</v>
- <v>NewOwner = pid()</v>
+ <v>CipherSuite = ciphersuite()</v>
+ <v>ProtocolVersion = protocol()</v>
</type>
- <desc>
- <p>Assigns a new controlling process to <c>Socket</c>. A controlling
- process is the owner of a socket, and receives all messages from
- the socket.</p>
+ <desc><p>Returns the negotiated protocol version and cipher suite.</p>
</desc>
</func>
- <func>
- <name>format_error(ErrorCode) -> string()</name>
+
+ <func>
+ <name>format_error(Reason) -> string()</name>
<fsummary>Return an error string.</fsummary>
<type>
- <v>ErrorCode = term()</v>
+ <v>Reason = term()</v>
</type>
<desc>
- <p>Returns a diagnostic string describing an error.</p>
+ <p>Presents the error returned by an ssl function as a printable string.</p>
</desc>
</func>
+
<func>
- <name>getopts(Socket, OptionsTags) -> {ok, Options} | {error, Reason}</name>
- <fsummary>Get options set for socket</fsummary>
+ <name>getopts(Socket) -> </name>
+ <name>getopts(Socket, OptionNames) ->
+ {ok, [socketoption()]} | {error, Reason}</name>
+ <fsummary>Get the value of the specified options.</fsummary>
<type>
- <v>Socket = sslsocket()</v>
- <v>OptionTags = [optiontag()]()</v>
+ <v>Socket = sslsocket()</v>
+ <v>OptionNames = [property()]</v>
</type>
<desc>
- <p>Returns the options the tags of which are <c>OptionTags</c> for
- for the socket <c>Socket</c>. </p>
+ <p>Get the value of the specified socket options, if no
+ options are specified all options are returned.
+ </p>
</desc>
</func>
+
<func>
- <name>listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}</name>
- <fsummary>Set up a socket to listen on a port on the local host.</fsummary>
+ <name>listen(Port, Options) ->
+ {ok, ListenSocket} | {error, Reason}</name>
+ <fsummary>Creates a ssl listen socket.</fsummary>
<type>
- <v>Port = integer()</v>
- <v>Options = [listen_option()]</v>
- <v>listen_option() = {mode, list} | {mode, binary} | binary | {packet, packettype()} | {header, integer()} | {active, activetype()} | {backlog, integer()} | {ip, ipaddress()} | {verify, code()} | {depth, depth()} | {certfile, path()} | {keyfile, path()} | {password, string()} | {cacertfile, path()} | {ciphers, string()}</v>
- <v>ListenSocket = sslsocket()</v>
+ <v>Port = integer()</v>
+ <v>Options = options()</v>
+ <v>ListenSocket = sslsocket()</v>
</type>
<desc>
- <p>Sets up a socket to listen on port <c>Port</c> at the local host.
- If <c>Port</c> is zero, <c>listen/2</c> picks an available port
- number (use <c>port/1</c> to retrieve it).
- </p>
- <p>The listen queue size defaults to 5. If a different value is
- wanted, the option <c>{backlog, Size}</c> should be added to the
- list of options.
- </p>
- <p>An empty <c>Options</c> list is considered an error, and
- <c>{error, enooptions}</c> is returned.
- </p>
- <p>The returned <c>ListenSocket</c> can only be used in calls to
- <c>transport_accept/[1,2]</c>.</p>
+ <p>Creates a ssl listen socket.</p>
</desc>
</func>
+
<func>
- <name>peercert(Socket) -> {ok, Cert} | {error, Reason}</name>
+ <name>peercert(Socket) -> {ok, Cert} | {error, Reason}</name>
<fsummary>Return the peer certificate.</fsummary>
- <type>
+ <type>
<v>Socket = sslsocket()</v>
- <v>Cert = binary()()</v>
- <v>Subject = term()()</v>
+ <v>Cert = binary()</v>
</type>
<desc>
- <p>Returns the DER encoded peer certificate, the certificate can be decoded with
- <c>public_key:pkix_decode_cert/2</c>.
- </p>
+ <p>The peer certificate is returned as a DER encoded binary.
+ The certificate can be decoded with <c>public_key:pkix_decode_cert/2</c>.
+ </p>
</desc>
</func>
<func>
- <name>peername(Socket) -> {ok, {Address, Port}} | {error, Reason}</name>
+ <name>peername(Socket) -> {ok, {Address, Port}} |
+ {error, Reason}</name>
<fsummary>Return peer address and port.</fsummary>
<type>
<v>Socket = sslsocket()</v>
@@ -372,67 +446,47 @@
<p>Returns the address and port number of the peer.</p>
</desc>
</func>
+
<func>
- <name>pid(Socket) -> pid()</name>
- <fsummary>Return the pid of the socket process.</fsummary>
- <type>
- <v>Socket = sslsocket()</v>
- </type>
- <desc>
- <p>Returns the pid of the socket process. The returned pid should
- only be used for receiving exit messages.</p>
- </desc>
- </func>
- <func>
- <name>recv(Socket, Length) -> {ok, Data} | {error, Reason}</name>
- <name>recv(Socket, Length, Timeout) -> {ok, Data} | {error, Reason}</name>
- <fsummary>Receive data on socket.</fsummary>
+ <name>recv(Socket, Length) -> </name>
+ <name>recv(Socket, Length, Timeout) -> {ok, Data} | {error,
+ Reason}</name>
+ <fsummary>Receive data on a socket.</fsummary>
<type>
<v>Socket = sslsocket()</v>
- <v>Length = integer() >= 0</v>
+ <v>Length = integer()</v>
<v>Timeout = integer()</v>
- <v>Data = bytes() | binary()</v>
+ <v>Data = [char()] | binary()</v>
</type>
<desc>
- <p>Receives data on socket <c>Socket</c> when the socket is in
- passive mode, i.e. when the option <c>{active, false}</c>
- has been specified.
- </p>
- <p>A notable return value is <c>{error, closed}</c> which
- indicates that the socket is closed.
- </p>
- <p>A positive value of the <c>Length</c> argument is only
- valid when the socket is in raw mode (option <c>{packet, 0}</c> is set, and the option <c>binary</c> is <em>not</em>
- set); otherwise it should be set to 0, whence all available
- bytes are returned.
- </p>
- <p>If the optional <c>Timeout</c> parameter is specified, and
- no data was available within the given time, <c>{error, timeout}</c> is returned. The default value for
- <c>Timeout</c> is <c>infinity</c>.</p>
+ <p>This function receives a packet from a socket in passive
+ mode. A closed socket is indicated by a return value
+ <c>{error, closed}</c>.</p>
+ <p>The <c>Length</c> argument is only meaningful when
+ the socket is in <c>raw</c> mode and denotes the number of
+ bytes to read. If <c>Length</c> = 0, all available bytes are
+ returned. If <c>Length</c> &gt; 0, exactly <c>Length</c>
+ bytes are returned, or an error; possibly discarding less
+ than <c>Length</c> bytes of data when the socket gets closed
+ from the other side.</p>
+ <p>The optional <c>Timeout</c> parameter specifies a timeout in
+ milliseconds. The default value is <c>infinity</c>.</p>
</desc>
</func>
+
<func>
- <name>seed(Data) -> ok | {error, Reason}</name>
- <fsummary>Seed the ssl random generator.</fsummary>
+ <name>renegotiate(Socket) -> ok | {error, Reason}</name>
+ <fsummary> Initiates a new handshake.</fsummary>
<type>
- <v>Data = iolist() | binary()</v>
+ <v>Socket = sslsocket()</v>
</type>
- <desc>
- <p>Seeds the ssl random generator.
- </p>
- <p>It is strongly advised to seed the random generator after
- the ssl application has been started, and before any
- connections are established. Although the port program
- interfacing to the OpenSSL libraries does a "random" seeding
- of its own in order to make everything work properly, that
- seeding is by no means random for the world since it has a
- constant value which is known to everyone reading the source
- code of the seeding.
- </p>
- <p>A notable return value is <c>{error, edata}}</c> indicating that
- <c>Data</c> was not a binary nor an iolist.</p>
+ <desc><p>Initiates a new handshake. A notable return value is
+ <c>{error, renegotiation_rejected}</c> indicating that the peer
+ refused to go through with the renegotiation but the connection
+ is still active using the previously negotiated session.</p>
</desc>
</func>
+
<func>
<name>send(Socket, Data) -> ok | {error, Reason}</name>
<fsummary>Write data to a socket.</fsummary>
@@ -458,26 +512,65 @@
<c>Socket</c>. </p>
</desc>
</func>
+
<func>
- <name>ssl_accept(Socket) -> ok | {error, Reason}</name>
- <name>ssl_accept(Socket, Timeout) -> ok | {error, Reason}</name>
- <fsummary>Perform server-side SSL handshake and key exchange</fsummary>
+ <name>shutdown(Socket, How) -> ok | {error, Reason}</name>
+ <fsummary>Immediately close a socket</fsummary>
<type>
<v>Socket = sslsocket()</v>
+ <v>How = read | write | read_write</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc>
+ <p>Immediately close a socket in one or two directions.</p>
+ <p><c>How == write</c> means closing the socket for writing,
+ reading from it is still possible.</p>
+ <p>To be able to handle that the peer has done a shutdown on
+ the write side, the <c>{exit_on_close, false}</c> option
+ is useful.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>ssl_accept(ListenSocket) -> </name>
+ <name>ssl_accept(ListenSocket, Timeout) -> ok | {error, Reason}</name>
+ <fsummary>Perform server-side SSL handshake</fsummary>
+ <type>
+ <v>ListenSocket = sslsocket()</v>
<v>Timeout = integer()</v>
- <v>Reason = atom()</v>
+ <v>Reason = term()</v>
</type>
<desc>
<p>The <c>ssl_accept</c> function establish the SSL connection
on the server side. It should be called directly after
<c>transport_accept</c>, in the spawned server-loop.</p>
- <p>Note that the ssl connection is not complete until <c>ssl_accept</c>
- has returned <c>true</c>, and if an error is returned, the socket
- is unavailable and for instance <c>close/1</c> will crash.</p>
</desc>
</func>
+
+ <func>
+ <name>ssl_accept(ListenSocket, SslOptions) -> </name>
+ <name>ssl_accept(ListenSocket, SslOptions, Timeout) -> {ok, Socket} | {error, Reason}</name>
+ <fsummary>Perform server-side SSL handshake</fsummary>
+ <type>
+ <v>ListenSocket = socket()</v>
+ <v>SslOptions = ssloptions()</v>
+ <v>Timeout = integer()</v>
+ <v>Reason = term()</v>
+ </type>
+ <desc>
+ <p> Upgrades a gen_tcp, or
+ equivalent, socket to a ssl socket e.i performs the
+ ssl server-side handshake.</p>
+ <p><note>Note that the listen socket should be in {active, false} mode
+ before telling the client that the server is ready to upgrade
+ and calling this function, otherwise the upgrade may
+ or may not succeed depending on timing.</note></p>
+ </desc>
+ </func>
+
<func>
- <name>sockname(Socket) -> {ok, {Address, Port}} | {error, Reason}</name>
+ <name>sockname(Socket) -> {ok, {Address, Port}} |
+ {error, Reason}</name>
<fsummary>Return the local address and port.</fsummary>
<type>
<v>Socket = sslsocket()</v>
@@ -489,217 +582,84 @@
<c>Socket</c>.</p>
</desc>
</func>
+
<func>
- <name>transport_accept(Socket) -> {ok, NewSocket} | {error, Reason}</name>
- <name>transport_accept(Socket, Timeout) -> {ok, NewSocket} | {error, Reason}</name>
- <fsummary>Accept an incoming connection and prepare for <c>ssl_accept</c></fsummary>
+ <name>start() -> </name>
+ <name>start(Type) -> ok | {error, Reason}</name>
+ <fsummary>Starts the Ssl application. </fsummary>
+ <type>
+ <v>Type = permanent | transient | temporary</v>
+ </type>
+ <desc>
+ <p>Starts the Ssl application. Default type
+ is temporary.
+ <seealso marker="kernel:application">application(3)</seealso></p>
+ </desc>
+ </func>
+ <func>
+ <name>stop() -> ok </name>
+ <fsummary>Stops the Ssl application.</fsummary>
+ <desc>
+ <p>Stops the Ssl application.
+ <seealso marker="kernel:application">application(3)</seealso></p>
+ </desc>
+ </func>
+
+ <func>
+ <name>transport_accept(Socket) -></name>
+ <name>transport_accept(Socket, Timeout) ->
+ {ok, NewSocket} | {error, Reason}</name>
+ <fsummary>Accept an incoming connection and
+ prepare for <c>ssl_accept</c></fsummary>
<type>
<v>Socket = NewSocket = sslsocket()</v>
<v>Timeout = integer()</v>
- <v>Reason = atom()</v>
+ <v>Reason = reason()</v>
</type>
<desc>
<p>Accepts an incoming connection request on a listen socket.
- <c>ListenSocket</c> must be a socket returned from <c>listen/2</c>.
- The socket returned should be passed to <c>ssl_accept</c> to
- complete ssl handshaking and establishing the connection.</p>
+ <c>ListenSocket</c> must be a socket returned from
+ <c>listen/2</c>. The socket returned should be passed to
+ <c>ssl_accept</c> to complete ssl handshaking and
+ establishing the connection.</p>
<warning>
<p>The socket returned can only be used with <c>ssl_accept</c>,
no traffic can be sent or received before that call.</p>
</warning>
- <p>The accepted socket inherits the options set for <c>ListenSocket</c>
- in <c>listen/2</c>.</p>
- <p>The default value for <c>Timeout</c> is <c>infinity</c>. If
- <c>Timeout</c> is specified, and no connection is accepted within
- the given time, <c>{error, timeout}</c> is returned.</p>
+ <p>The accepted socket inherits the options set for
+ <c>ListenSocket</c> in <c>listen/2</c>.</p>
+ <p>The default
+ value for <c>Timeout</c> is <c>infinity</c>. If
+ <c>Timeout</c> is specified, and no connection is accepted
+ within the given time, <c>{error, timeout}</c> is
+ returned.</p>
</desc>
</func>
+
<func>
- <name>version() -> {ok, {SSLVsn, CompVsn, LibVsn}}</name>
- <fsummary>Return the version of SSL.</fsummary>
+ <name>versions() ->
+ [{SslAppVer, SupportedSslVer, AvailableSslVsn}]</name>
+ <fsummary>Returns version information relevant for the
+ ssl application.</fsummary>
<type>
- <v>SSLVsn = CompVsn = LibVsn = string()()</v>
+ <v>SslAppVer = string()</v>
+ <v>SupportedSslVer = [protocol()]</v>
+ <v>AvailableSslVsn = [protocol()]</v>
</type>
<desc>
- <p>Returns the SSL application version (<c>SSLVsn</c>), the library
- version used when compiling the SSL application port program
- (<c>CompVsn</c>), and the actual library version used when
- dynamically linking in runtime (<c>LibVsn</c>).
- </p>
- <p>If the SSL application has not been started, <c>CompVsn</c> and
- <c>LibVsn</c> are empty strings.
- </p>
+ <p>
+ Returns version information relevant for the
+ ssl application.</p>
</desc>
</func>
- </funcs>
-
- <section>
- <title>ERRORS</title>
- <p>The possible error reasons and the corresponding diagnostic strings
- returned by <c>format_error/1</c> are either the same as those defined
- in the <c>inet(3)</c> reference manual, or as follows:
- </p>
- <taglist>
- <tag><c>closed</c></tag>
- <item>
- <p>Connection closed for the operation in question.
- </p>
- </item>
- <tag><c>ebadsocket</c></tag>
- <item>
- <p>Connection not found (internal error).
- </p>
- </item>
- <tag><c>ebadstate</c></tag>
- <item>
- <p>Connection not in connect state (internal error).
- </p>
- </item>
- <tag><c>ebrokertype</c></tag>
- <item>
- <p>Wrong broker type (internal error).
- </p>
- </item>
- <tag><c>ecacertfile</c></tag>
- <item>
- <p>Own CA certificate file is invalid.
- </p>
- </item>
- <tag><c>ecertfile</c></tag>
- <item>
- <p>Own certificate file is invalid.
- </p>
- </item>
- <tag><c>echaintoolong</c></tag>
- <item>
- <p>The chain of certificates provided by peer is too long.
- </p>
- </item>
- <tag><c>ecipher</c></tag>
- <item>
- <p>Own list of specified ciphers is invalid.
- </p>
- </item>
- <tag><c>ekeyfile</c></tag>
- <item>
- <p>Own private key file is invalid.
- </p>
- </item>
- <tag><c>ekeymismatch</c></tag>
- <item>
- <p>Own private key does not match own certificate.
- </p>
- </item>
- <tag><c>enoissuercert</c></tag>
- <item>
- <p>Cannot find certificate of issuer of certificate provided
- by peer.
- </p>
- </item>
- <tag><c>enoservercert</c></tag>
- <item>
- <p>Attempt to do accept without having set own certificate.
- </p>
- </item>
- <tag><c>enotlistener</c></tag>
- <item>
- <p>Attempt to accept on a non-listening socket.
- </p>
- </item>
- <tag><c>enoproxysocket</c></tag>
- <item>
- <p>No proxy socket found (internal error).
- </p>
- </item>
- <tag><c>enooptions</c></tag>
- <item>
- <p>The list of options is empty.
- </p>
- </item>
- <tag><c>enotstarted</c></tag>
- <item>
- <p>The SSL application has not been started.
- </p>
- </item>
- <tag><c>eoptions</c></tag>
- <item>
- <p>Invalid list of options.
- </p>
- </item>
- <tag><c>epeercert</c></tag>
- <item>
- <p>Certificate provided by peer is in error.
- </p>
- </item>
- <tag><c>epeercertexpired</c></tag>
- <item>
- <p>Certificate provided by peer has expired.
- </p>
- </item>
- <tag><c>epeercertinvalid</c></tag>
- <item>
- <p>Certificate provided by peer is invalid.
- </p>
- </item>
- <tag><c>eselfsignedcert</c></tag>
- <item>
- <p>Certificate provided by peer is self signed.
- </p>
- </item>
- <tag><c>esslaccept</c></tag>
- <item>
- <p>Server SSL handshake procedure between client and server failed.
- </p>
- </item>
- <tag><c>esslconnect</c></tag>
- <item>
- <p>Client SSL handshake procedure between client and server failed.
- </p>
- </item>
- <tag><c>esslerrssl</c></tag>
- <item>
- <p>SSL protocol failure. Typically because of a fatal alert
- from peer.
- </p>
- </item>
- <tag><c>ewantconnect</c></tag>
- <item>
- <p>Protocol wants to connect, which is not supported in
- this version of the SSL application.
- </p>
- </item>
- <tag><c>ex509lookup</c></tag>
- <item>
- <p>Protocol wants X.509 lookup, which is not supported in
- this version of the SSL application.
- </p>
- </item>
- <tag><c>{badcall, Call}</c></tag>
- <item>
- <p>Call not recognized for current mode (active or passive) and
- state of socket.
- </p>
- </item>
- <tag><c>{badcast, Cast}</c></tag>
- <item>
- <p>Call not recognized for current mode (active or passive) and
- state of socket.
- </p>
- </item>
- <tag><c>{badinfo, Info}</c></tag>
- <item>
- <p>Call not recognized for current mode (active or passive) and
- state of socket.
- </p>
- </item>
- </taglist>
- </section>
-
+ </funcs>
+
<section>
<title>SEE ALSO</title>
- <p>gen_tcp(3), inet(3) public_key(3) </p>
+ <p><seealso marker="kernel:inet">inet(3) </seealso> and
+ <seealso marker="kernel:gen_tcp">gen_tcp(3) </seealso>
+ </p>
</section>
-
-</erlref>
+</erlref>
diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml
index ae8bd87781..2ba6f48611 100644
--- a/lib/ssl/doc/src/ssl_app.xml
+++ b/lib/ssl/doc/src/ssl_app.xml
@@ -4,7 +4,7 @@
<appref>
<header>
<copyright>
- <year>1999</year><year>2009</year>
+ <year>1999</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,45 +13,20 @@
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>ssl</title>
- <prepared>Peter H&ouml;gfeldt</prepared>
- <responsible>Peter H&ouml;gfeldt</responsible>
- <docno></docno>
- <approved>Peter H&ouml;gfeldt</approved>
- <checked>Peter H&ouml;gfeldt</checked>
- <date>2005-03-10</date>
- <rev>E</rev>
<file>ssl_app.sgml</file>
</header>
<app>ssl</app>
- <appsummary>The SSL Application</appsummary>
- <description>
- <p>The Secure Socket Layer (SSL) application provides secure
- socket communication over TCP/IP.
- </p>
- </description>
-
- <section>
- <title>Warning</title>
- <p>In previous versions of Erlang/OTP SSL it was advised, as a
- work-around, to set the operating system environment variable
- <c>SSL_CERT_FILE</c> to point at a file containing CA
- certificates. That variable is no longer needed, and is not
- recognised by Erlang/OTP SSL any more.
- </p>
- <p>However, the OpenSSL package does interpret that environment
- variable. Hence a setting of that variable might have
- unpredictable effects on the Erlang/OTP SSL application. It is
- therefore adviced to not used that environment variable at all.</p>
- </section>
+ <appsummary>The SSL application provides secure communication over
+ sockets.</appsummary>
<section>
<title>Environment</title>
@@ -61,115 +36,43 @@
</p>
<p>Note that the environment parameters can be set on the command line,
for instance,</p>
- <p><c>erl ... -ssl protocol_version '[sslv2,sslv3]' ...</c>.
+ <p><c>erl ... -ssl protocol_version '[sslv3, tlsv1]' ...</c>.
</p>
<taglist>
- <tag><c><![CDATA[ephemeral_rsa = true | false <optional>]]></c></tag>
- <item>
- <p>Enables all SSL servers (those that listen and accept)
- to use ephemeral RSA key generation when a clients connect with
- weak handshake cipher specifications, that need equally weak
- ciphers from the server (i.e. obsolete restrictions on export
- ciphers). Default is <c>false</c>.
- </p>
- </item>
- <tag><c><![CDATA[debug = true | false <optional>]]></c></tag>
- <item>
- <p>Causes debug information to be written to standard
- output. Default is <c>false</c>.
- </p>
- </item>
- <tag><c><![CDATA[debugdir = path() | false <optional>]]></c></tag>
- <item>
- <p>Causes debug information output controlled by <c>debug</c>
- and <c>msgdebug</c> to be printed to a file named
- <c><![CDATA[ssl_esock.<pid>.log]]></c> in the directory specified by
- <c>debugdir</c>, where <c><![CDATA[<pid>]]></c> is the operating system
- specific textual representation of the process identifier
- of the external port program of the SSL application. Default
- is <c>false</c>, i.e. no log file is produced.
- </p>
- </item>
- <tag><c><![CDATA[msgdebug = true | false <optional>]]></c></tag>
+ <tag><c><![CDATA[protocol_version = [sslv3|tlsv1] <optional>]]></c>.</tag>
<item>
- <p>Sets <c>debug = true</c> and causes also the contents
- of low level messages to be printed to standard output.
- Default is <c>false</c>.
- </p>
+ <p>Protocol that will be supported by started clients and
+ servers. If this option is not set it will default to all
+ protocols currently supported by the erlang ssl application.
+ Note that this option may be overridden by the version option
+ to ssl:connect/[2,3] and ssl:listen/2.
+ </p>
</item>
- <tag><c><![CDATA[port_program = string() | false <optional>]]></c></tag>
- <item>
- <p>Name of port program. The default is <c>ssl_esock</c>.
- </p>
- </item>
- <tag><c><![CDATA[protocol_version = [sslv2|sslv3|tlsv1] <optional>]]></c>.</tag>
+
+ <tag><c><![CDATA[session_lifetime = integer() <optional>]]></c></tag>
<item>
- <p>Name of protocols to use. If this option is not set,
- all protocols are assumed, i.e. the default value is
- <c>[sslv2, sslv3, tlsv1]</c>.
- </p>
+ <p>The lifetime of session data in seconds.
+ </p>
</item>
- <tag><c><![CDATA[proxylsport = integer() | false <optional>]]></c></tag>
+
+ <tag><c><![CDATA[session_cb = atom() <optional>]]></c></tag>
<item>
- <p>Define the port number of the listen port of the
- SSL port program. Almost never is this option needed.
+ <p>
+ Name of session cache callback module that implements
+ the ssl_session_cache_api behavior, defaults to
+ ssl_session_cache.erl.
</p>
</item>
- <tag><c><![CDATA[proxylsbacklog = integer() | false <optional>]]></c></tag>
+
+ <tag><c><![CDATA[session_cb_init_args = list() <optional>]]></c></tag>
<item>
- <p>Set the listen queue size of the listen port of the
- SSL port program. The default is 128.
- </p>
+ <p>
+ List of arguments to the init function in session cache
+ callback module, defaults to [].
+ </p>
</item>
- </taglist>
- </section>
- <section>
- <title>OpenSSL libraries</title>
- <p>The current implementation of the Erlang SSL application is
- based on the <em>OpenSSL</em> package version 0.9.7 or higher.
- There are source and binary releases on the web.
- </p>
- <p>Source releases of OpenSSL can be downloaded from the <url href="http://www.openssl.org">OpenSSL</url> project home page,
- or mirror sites listed there.
- </p>
- <p>The same URL also contains links to some compiled binaries and
- libraries of OpenSSL (see the <c>Related/Binaries</c> menu) of
- which the <url href="http://www.shininglightpro.com/search.php?searchname=Win32+OpenSSL">Shining Light Productions Win32 and OpenSSL</url> pages are of
- interest for the Win32 user.
- </p>
- <p>For some Unix flavours there are binary packages available
- on the net.
- </p>
- <p>If you cannot find a suitable binary OpenSSL package, you
- have to fetch an OpenSSL source release and compile it.
- </p>
- <p>You then have to compile and install the libraries
- <c>libcrypto.so</c> and <c>libssl.so</c> (Unix), or the
- libraries <c>libeay32.dll</c> and <c>ssleay32.dll</c> (Win32).
- </p>
- <p>For Unix The <c>ssl_esock</c> port program is delivered linked
- to OpenSSL libraries in <c>/usr/local/lib</c>, but the default
- dynamic linking will also accept libraries in <c>/lib</c> and
- <c>/usr/lib</c>.
- </p>
- <p>If that is not applicable to the particular Unix operating
- system used, the example <c>Makefile</c> in the SSL
- <c>priv/obj</c> directory, should be used as a guide to
- relinking the final version of the port program.
- </p>
- <p>For <c>Win32</c> it is only required that the libraries can be
- found from the <c>PATH</c> environment variable, or that they
- reside in the appropriate <c>SYSTEM32</c> directory; hence no
- particular relinking is need. Hence no example <c>Makefile</c>
- for Win32 is provided.</p>
- </section>
-
- <section>
- <title>Restrictions</title>
- <p>Users must be aware of export restrictions and patent rights
- concerning cryptographic software.
- </p>
+ </taglist>
</section>
<section>
@@ -178,5 +81,3 @@
</section>
</appref>
-
-
diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml
index c743cd67a3..4067fb8a22 100644
--- a/lib/ssl/doc/src/ssl_distribution.xml
+++ b/lib/ssl/doc/src/ssl_distribution.xml
@@ -32,7 +32,13 @@
<file>ssl_distribution.xml</file>
</header>
<p>This chapter describes how the Erlang distribution can use
- SSL to get additional verification and security.</p>
+ SSL to get additional verification and security.
+
+ <note><p>Note this
+ documentation is written for the old ssl implementation and
+ will be updated for the new one once this functionallity is
+ supported by the new implementation.</p></note>
+ </p>
<section>
<title>Introduction</title>
diff --git a/lib/ssl/doc/src/ssl_protocol.xml b/lib/ssl/doc/src/ssl_protocol.xml
index 3dc2332795..726b9a4eeb 100644
--- a/lib/ssl/doc/src/ssl_protocol.xml
+++ b/lib/ssl/doc/src/ssl_protocol.xml
@@ -13,337 +13,138 @@
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>The SSL Protocol</title>
- <prepared>Peter H&ouml;gfeldt</prepared>
- <docno></docno>
- <date>2003-04-28</date>
- <rev>PA2</rev>
+ <title>Transport Layer Security (TLS) and its predecessor, Secure Socket Layer (SSL)</title>
<file>ssl_protocol.xml</file>
</header>
- <p>Here we provide a short introduction to the SSL protocol. We only
- consider those part of the protocol that are important from a
- programming point of view.
- </p>
- <p>For a very good general introduction to SSL and TLS see the book
- <cite id="rescorla"></cite>.
- </p>
- <p><em>Outline:</em></p>
- <list type="bulleted">
- <item>Two types of connections - connection: handshake, data transfer, and
- shutdown -
- SSL/TLS protocol - server must have certificate - what the the
- server sends to the client - client may verify the server -
- server may ask client for certificate - what the client sends to
- the server - server may then verify the client - verification -
- certificate chains - root certificates - public keys - key
- agreement - purpose of certificate - references</item>
- </list>
+
+ <p>The erlang ssl application currently supports SSL 3.0 and TLS 1.0
+ RFC 2246, and will in the future also support later versions of TLS.
+ SSL 2.0 is not supported.
+ </p>
- <section>
- <title>SSL Connections</title>
- <p>The SSL protocol is implemented on top of the TCP/IP protocol.
- From an endpoint view it also has the same type of connections
- as that protocol, almost always created by calls to socket
- interface functions <em>listen</em>, <em>accept</em> and
- <em>connect</em>. The endpoints are <em>servers</em> and
- <em>clients</em>.
- </p>
- <p>A <em>server</em><em>listen</em>s for connections on a
- specific address and port. This is done once. The server then
- <em>accept</em>s each connections on that same address and
- port. This is typically done indefinitely many times.
- </p>
- <p>A <em>client</em> connects to a server on a specific address
- and port. For each purpose this is done once.
- </p>
- <p>For a plain TCP/IP connection the establishment of a connection
- (through an accept or a connect) is followed by data transfer between
- the client and server, finally ended by a connection close.
- </p>
- <p>An SSL connection also consists of data transfer and connection
- close, However, the data transfer contains encrypted data, and
- in order to establish the encryption parameters, the data
- transfer is preceded by an SSL <em>handshake</em>. In this
- handshake the server plays a dominant role, and the main
- instrument used in achieving a valid SSL connection is the
- server's <em>certificate</em>. We consider certificates in the
- next section, and the SSL handshake in a subsequent section.</p>
- </section>
+ <p>By default erlang ssl is run over the TCP/IP protocol even
+ though you could plug in an other reliable transport protocol
+ with the same API as gen_tcp.</p>
+
+ <p>If a client and server wants to use an upgrade mechanism, such as
+ defined by RFC2817, to upgrade a regular TCP/IP connection to a ssl
+ connection the erlang ssl API supports this. This can be useful for
+ things such as supporting HTTP and HTTPS on the same port and
+ implementing virtual hosting.
+ </p>
<section>
- <title>Certificates</title>
- <p>A certificate is similar to a driver's license, or a
- passport. The holder of the certificate is called the
- <em>subject</em>. First of all the certificate identifies the
- subject in terms of the name of the subject, its postal address,
- country name, company name (if applicable), etc.
- </p>
- <p>Although a driver's license is always issued by a well-known and
- distinct authority, a certificate may have an <em>issuer</em>
- that is not so well-known. Therefore a certificate also always
- contains information on the issuer of the certificate. That
- information is of the same type as the information on the
- subject. The issuer of a certificate also signs the certificate
- with a <em>digital signature</em> (the signature is an inherent
- part of the certificate), which allow others to verify that the
- issuer really is the issuer of the certificate.
- </p>
- <p>Now that a certificate can be checked by verifying the
- signature of the issuer, the question is how to trust the
- issuer. The answer to this question is to require that there is
- a certificate for the issuer as well. That issuer has in turn an
- issuer, which must also have a certificate, and so on. This
- <em>certificate chain</em> has to have en end, which then must
- be a certificate that is trusted by other means. We shall cover
- this problem of <em>authentication</em> in a subsequent
- section.
- </p>
+ <title>Security overview</title>
+
+ <p>To achive authentication and privacy the client and server will
+ perform a TLS Handshake procedure before transmitting or receiving
+ any data. During the handshake they agree on a protocol version and
+ cryptographic algorithms, they generate shared secrets using public
+ key cryptographics and optionally authenticate each other with
+ digital certificates.</p>
</section>
-
+
<section>
- <title>Encryption Algorithms</title>
- <p>An encryption algorithm is a mathematical algorithm for
- encryption and decryption of messages (arrays of bytes,
- say). The algorithm as such is always required to be publicly
- known, otherwise its strength cannot be evaluated, and hence it
- cannot be used reliably. The secrecy of an encrypted message is
- not achieved by the secrecy of the algorithm used, but by the
- secrecy of the <em>keys</em> used as input to the encryption and
- decryption algorithms. For an account of cryptography in general
- see <cite id="schneier"></cite>.
- </p>
- <p>There are two classes of encryption algorithms: <em>symmetric key</em> algorithms and <em>public key</em> algorithms. Both
- types of algorithms are used in the SSL protocol.
- </p>
- <p>In the sequel we assume holders of keys keep them secret (except
- public keys) and that they in that sense are trusted. How a
- holder of a secret key is proved to be the one it claims to be
- is a question of <em>authentication</em>, which, in the context
- of the SSL protocol, is described in a section further below.
- </p>
-
- <section>
- <title>Symmetric Key Algorithms</title>
- <p>A <em>symmetric key</em> algorithm has one key only. The key
- is used for both encryption and decryption. Obviously the key
- of a symmetric key algorithm must always be kept secret by the
- users of the key. DES is an example of a symmetric key
- algorithm.
- </p>
- <p>Symmetric key algorithms are fast compared to public key
- algorithms. They are therefore typically used for encrypting
- bulk data.
- </p>
- </section>
-
- <section>
- <title>Public Key Algorithms</title>
- <p>A <em>public key</em> algorithm has two keys. Any of the two
- keys can be used for encryption. A message encrypted with one
- of the keys, can only be decrypted with the other key. One of
- the keys is public (known to the world), while the other key
- is private (i.e. kept secret) by the owner of the two keys.
- </p>
- <p>RSA is an example of a public key algorithm.
- </p>
- <p>Public key algorithms are slow compared to symmetric key
- algorithms, and they are therefore seldom used for bulk data
- encryption. They are therefore only used in cases where the
- fact that one key is public and the other is private, provides
- features that cannot be provided by symmetric algorithms.
- </p>
- </section>
-
- <section>
- <title>Digital Signature Algorithms</title>
- <p>An interesting feature of a public key algorithm is that its
- public and private keys can both be used for encryption.
- Anyone can use the public key to encrypt a message, and send
- that message to the owner of the private key, and be sure of
- that only the holder of the private key can decrypt the
- message.
- </p>
- <p>On the other hand, the owner of the private key can encrypt a
- message with the private key, thus obtaining an encrypted
- message that can decrypted by anyone having the public key.
- </p>
- <p>The last approach can be used as a digital signature
- algorithm. The holder of the private key signs an array of
- bytes by performing a specified well-known <em>message digest algorithm</em> to compute a hash of the array, encrypts the
- hash value with its private key, an then presents the original
- array, the name of the digest algorithm, and the encryption of
- the hash value as a <em>signed array of bytes</em>.
- </p>
- <p>Now anyone having the public key, can decrypt the encrypted
- hash value with that key, compute the hash with the specified
- digest algorithm, and check that the hash values compare equal
- in order to verify that the original array was indeed signed
- by the holder of the private key.
- </p>
- <p>What we have accounted for so far is by no means all that can
- be said about digital signatures (see <cite id="schneier"></cite>for
- further details).
- </p>
- </section>
-
- <section>
- <title>Message Digests Algorithms</title>
- <p>A message digest algorithm is a hash function that accepts
- an array bytes of arbitrary but finite length of input, and
- outputs an array of bytes of fixed length. Such an algorithm
- is also required to be very hard to invert.
- </p>
- <p>MD5 (16 bytes output) and SHA1 (20 bytes output) are examples
- of message digest algorithms.
- </p>
- </section>
+ <title>Data Privacy and Integrity</title>
+
+ <p>A <em>symmetric key</em> algorithm has one key only. The key is
+ used for both encryption and decryption. These algoritms are fast
+ compared to public key algorithms (using two keys, a public and a
+ private one) and are therefore typically used for encrypting bulk
+ data.
+ </p>
+
+ <p>The keys for the symmetric encryption are generated uniquely
+ for each connection and are based on a secret negotiated
+ in the TLS handshake. </p>
+
+ <p>The TLS handsake protocol and data transfer is run on top of
+ the TLS Record Protocol that uses a keyed-hash MAC (Message
+ Authenticity Code), or HMAC, to protect the message's data
+ integrity. From the TLS RFC "A Message Authentication Code is a
+ one-way hash computed from a message and some secret data. It is
+ difficult to forge without knowing the secret data. Its purpose is
+ to detect if the message has been altered."
+ </p>
+
</section>
- <section>
- <title>SSL Handshake</title>
- <p>The main purpose of the handshake performed before an an SSL
- connection is established is to negotiate the encryption
- algorithm and key to be used for the bulk data transfer between
- the client and the server. We are writing <em>the</em> key,
- since the algorithm to choose for bulk encryption one of the
- symmetric algorithms.
- </p>
- <p>There is thus only one key to agree upon, and obviously that
- key has to be kept secret between the client and the server. To
- obtain that the handshake has to be encrypted as well.
- </p>
- <p>The SSL protocol requires that the server always sends its
- certificate to the client in the beginning of the handshake. The
- client then retrieves the server's public key from the
- certificate, which means that the client can use the server's
- public key to encrypt messages to the server, and the server can
- decrypt those messages with its private key. Similarly, the
- server can encrypt messages to the client with its private key,
- and the client can decrypt messages with the server's public
- key. It is thus is with the server's public and private keys
- that messages in the handshake are encrypted and decrypted, and
- hence the key agreed upon for symmetric encryption of bulk data
- can be kept secret (there are more things to consider to really
- keep it secret, see <cite id="rescorla"></cite>).
- </p>
- <p>The above indicates that the server does not care who is
- connecting, and that only the client has the possibility to
- properly identify the server based on the server's certificate.
- That is indeed true in the minimal use of the protocol, but it
- is possible to instruct the server to request the certificate of
- the client, in order to have a means to identify the client, but
- it is by no means required to establish an SSL connection.
- </p>
- <p>If a server request the client certificate, it verifies, as a
- part of the protocol, that the client really holds the private
- key of the certificate by sending the client a string of bytes
- to encrypt with its private key, which the server then decrypts
- with the client's public key, the result of which is compared
- with the original string of bytes (a similar procedure is always
- performed by the client when it has received the server's
- certificate).
- </p>
- <p>The way clients and servers <em>authenticate</em> each other,
- i.e. proves that their respective peers are what they claim to
- be, is the topic of the next section.
- </p>
- </section>
+ <section>
+ <title>Digital Certificates</title>
+ <p>A certificate is similar to a driver's license, or a
+ passport. The holder of the certificate is called the
+ <em>subject</em>. The certificate is signed
+ with the private key of the issuer of the certificate. A chain
+ of trust is build by having the issuer in its turn being
+ certified by an other certificate and so on until you reach the
+ so called root certificate that is self signed e.i. issued
+ by itself.</p>
+
+ <p>Certificates are issued by <em>certification
+ authorities</em> (<em>CA</em>s) only. There are a handful of
+ top CAs in the world that issue root certificates. You can
+ examine the certificates of several of them by clicking
+ through the menus of your web browser.
+ </p>
+ </section>
+
+ <section>
+ <title>Authentication of Sender</title>
+
+ <p>Authentication of the sender is done by public key path
+ validation as defined in RFC 3280. Simplified that means that
+ each certificate in the certificate chain is issued by the one
+ before, the certificates attributes are valid ones, and the
+ root cert is a trusted cert that is present in the trusted
+ certs database kept by the peer.</p>
+
+ <p>The server will always send a certificate chain as part of
+ the TLS handshake, but the client will only send one if
+ the server requests it. If the client does not have
+ an appropriate certificate it may send an "empty" certificate
+ to the server.</p>
+
+ <p>The client may choose to accept some path evaluation errors
+ for instance a web browser may ask the user if they want to
+ accept an unknown CA root certificate. The server, if it request
+ a certificate, will on the other hand not accept any path validation
+ errors. It is configurable if the server should accept
+ or reject an "empty" certificate as response to
+ a certificate request.</p>
+ </section>
+
+ <section>
+ <title>TLS Sessions</title>
+
+ <p>From the TLS RFC "A TLS session is an association between a
+ client and a server. Sessions are created by the handshake
+ protocol. Sessions define a set of cryptographic security
+ parameters, which can be shared among multiple
+ connections. Sessions are used to avoid the expensive negotiation
+ of new security parameters for each connection."</p>
- <section>
- <title>Authentication</title>
- <p>As we have already seen the reception of a certificate from a
- peer is not enough to prove that the peer is authentic. More
- certificates are needed, and we have to consider how certificates
- are issued and on what grounds.
- </p>
- <p>Certificates are issued by <em>certification authorities</em>
- (<em>CA</em>s) only. They issue certificates both for other CAs
- and ordinary users (which are not CAs).
- </p>
- <p>Certain CAs are <em>top CAs</em>, i.e. they do not have a
- certificate issued by another CA. Instead they issue their own
- certificate, where the subject and issuer part of the
- certificate are identical (such a certificate is called a
- self-signed certificate). A top CA has to be well-known, and has
- to have a publicly available policy telling on what grounds it
- issues certificates.
- </p>
- <p>There are a handful of top CAs in the world. You can examine the
- certificates of several of them by clicking through the menus of
- your web browser.
- </p>
- <p>A top CA typically issues certificates for other CAs, called
- <em>intermediate CAs</em>, but possibly also to ordinary users. Thus
- the certificates derivable from a top CA constitute a tree, where
- the leaves of the tree are ordinary user certificates.
- </p>
- <p>A <em>certificate chain</em> is an ordered sequence of
- certificates, <c>C1, C2, ..., Cn</c>, say, where <c>C1</c> is a
- top CA certificate, and where <c>Cn</c> is an ordinary user
- certificate, and where the holder of <c>C1</c> is the issuer of
- <c>C2</c>, the holder of <c>C2</c> is the issuer of <c>C3</c>,
- ..., and the holder of <c>Cn-1</c> is the issuer of <c>Cn</c>,
- the ordinary user certificate. The holders of <c>C2, C3, ..., Cn-1</c> are then intermediate CAs.
- </p>
- <p>Now to verify that a certificate chain is unbroken we have to
- take the public key from each certificate <c>Ck</c>, and apply
- that key to decrypt the signature of certificate <c>Ck-1</c>,
- thus obtaining the message digest computed by the holder of the
- <c>Ck</c> certificate, compute the real message digest of the
- <c>Ck-1</c> certificate and compare the results. If they compare
- equal the link of the chain between <c>Ck</c> and <c>Ck-1</c> is
- considered to unbroken. This is done for each link k = 1, 2,
- ..., n-1. If all links are found to be unbroken, the user
- certificate <c>Cn</c> is considered authenticated.
- </p>
+ <p>Session data is by default kept by the ssl application in a
+ memory storage hence session data will be lost at application
+ restart or takeover. Users may define their own callback module
+ to handle session data storage if persistent data storage is
+ required. Session data will also be invalidated after 24 hours
+ from it was saved, for security reasons. It is of course
+ possible to configure the amount of time the session data should be
+ saved.</p>
- <section>
- <title>Trusted Certificates</title>
- <p>Now that there is a way to authenticate a certificate by
- checking that all links of a certificate chain are unbroken,
- the question is how you can be sure to trust the certificates
- in the chain, and in particular the top CA certificate of the
- chain.
- </p>
- <p>To provide an answer to that question consider the
- perspective of a client, which have just received the
- certificate of the server. In order to authenticate the server
- the client has to construct a certificate chain and to prove
- that the chain is unbroken. The client has to have a set of CA
- certificates (top CA or intermediate CA certificates) not
- obtained from the server, but obtained by other means. Those
- certificates are kept <c>locally</c> by the client, and are
- trusted by the client.
- </p>
- <p>More specifically, the client does not really have to have
- top CA certificates in its local storage. In order to
- authenticate a server it is sufficient for the client to
- posses the trusted certificate of the issuer of the server
- certificate.
- </p>
- <p>Now that is not the whole story. A server can send an
- (incomplete) certificate chain to its client, and then the
- task of the client is to construct a certificate chain that
- begins with a trusted certificate and ends with the server's
- certificate. (A client can also send a chain to its server,
- provided the server requested the client's certificate.)
- </p>
- <p>All this means that an unbroken certificate chain begins with
- a trusted certificate (top CA or not), and ends with the peer
- certificate. That is the end of the chain is obtained from the
- peer, but the beginning of the chain is obtained from local
- storage, which is considered trusted.
- </p>
- </section>
- </section>
-</chapter>
+ <p>Ssl clients will by default try to reuse an available session,
+ ssl servers will by default agree to reuse sessions when clients
+ ask to do so.</p>
+
+ </section>
+ </chapter>
diff --git a/lib/ssl/doc/src/ssl_session_cache_api.xml b/lib/ssl/doc/src/ssl_session_cache_api.xml
new file mode 100644
index 0000000000..7b70c6cf34
--- /dev/null
+++ b/lib/ssl/doc/src/ssl_session_cache_api.xml
@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>1999</year><year>2010</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>ssl</title>
+ <file>ssl_session_cache_api.xml</file>
+ </header>
+ <module>ssl_session_cache_api</module>
+ <modulesummary>Defines the API for the TLS session cache so
+ that the datastorge scheme can be replaced by
+ defining a new callback module implementing this API.</modulesummary>
+
+ <section>
+ <title>Common Data Types</title>
+
+ <p>The following data types are used in the functions below:
+ </p>
+
+ <p><c>cache_ref() = opaque()</c></p>
+
+ <p><c>key() = {partialkey(), session_id()}</c></p>
+
+ <p><c>partialkey() = opaque()</c></p>
+
+ <p><c>session_id() = binary()</c></p>
+
+ <p><c>session() = opaque()</c></p>
+
+ </section>
+
+ <funcs>
+
+ <func>
+ <name>delete(Cache, Key) -> _</name>
+ <fsummary></fsummary>
+ <type>
+ <v> Cache = cache_ref()</v>
+ <v> Key = key()</v>
+ </type>
+ <desc>
+ <p> Delets a cache entry. Will only be called from the cache
+ handling process.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>foldl(Fun, Acc0, Cache) -> Acc</name>
+ <fsummary></fsummary>
+ <type>
+ <v></v>
+ </type>
+ <desc>
+ <p>Calls Fun(Elem, AccIn) on successive elements of the
+ cache, starting with AccIn == Acc0. Fun/2 must return a new
+ accumulator which is passed to the next call. The function returns
+ the final value of the accumulator. Acc0 is returned if the cache is
+ empty.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>init() -> opaque() </name>
+ <fsummary>Return cache reference</fsummary>
+ <type>
+ <v></v>
+ </type>
+ <desc>
+ <p>Performes possible initializations of the cache and returns
+ a reference to it that will be used as parameter to the other
+ api functions. Will be called by the cache handling processes
+ init function, hence puting the same requierments on it as
+ a normal process init function.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>lookup(Cache, Key) -> Entry</name>
+ <fsummary> Looks up a cach entry.</fsummary>
+ <type>
+ <v> Cache = cache_ref()</v>
+ <v> Key = key()</v>
+ <v> Entry = session() | undefined </v>
+ </type>
+ <desc>
+ <p>Looks up a cach entry. Should be callable from any
+ process.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>select_session(Cache, PartialKey) -> [session()]</name>
+ <fsummary>>Selects sessions that could be reused.</fsummary>
+ <type>
+ <v> Cache = cache_ref()</v>
+ <v> PartialKey = partialkey()</v>
+ <v> Session = session()</v>
+ </type>
+ <desc>
+ <p>Selects sessions that could be reused. Should be callable
+ from any process.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>terminate(Cache) -> _</name>
+ <fsummary>Called by the process that handles the cache when it
+ is aboute to terminat.</fsummary>
+ <type>
+ <v>Cache = term() - as returned by init/0</v>
+ </type>
+ <desc>
+ <p>Takes care of possible cleanup that is needed when the
+ cache handling process terminates.
+ </p>
+ </desc>
+ </func>
+
+ <func>
+ <name>update(Cache, Key, Session) -> _</name>
+ <fsummary> Caches a new session or updates a already cached one.</fsummary>
+ <type>
+ <v> Cache = cache_ref()</v>
+ <v> Key = key()</v>
+ <v> Session = session()</v>
+ </type>
+ <desc>
+ <p> Caches a new session or updates a already cached one. Will
+ only be called from the cache handling process.
+ </p>
+ </desc>
+ </func>
+
+ </funcs>
+
+</erlref>
diff --git a/lib/ssl/doc/src/usersguide.xml b/lib/ssl/doc/src/usersguide.xml
index 98071f5742..6528c00a0b 100644
--- a/lib/ssl/doc/src/usersguide.xml
+++ b/lib/ssl/doc/src/usersguide.xml
@@ -4,7 +4,7 @@
<part xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>2000</year><year>2009</year>
+ <year>2000</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,43 +13,27 @@
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>SSL User's Guide</title>
<prepared>OTP Team</prepared>
- <docno></docno>
<date>2003-05-26</date>
- <rev>B</rev>
<file>usersguide.sgml</file>
</header>
<description>
<p>The <em>SSL</em> application provides secure communication over
sockets.
</p>
- <p>This product includes software developed by the OpenSSL Project for
- use in the OpenSSL Toolkit (http://www.openssl.org/).
- </p>
- <p>This product includes cryptographic software written by Eric Young
- </p>
- <p>This product includes software written by Tim Hudson
- </p>
- <p>For full OpenSSL and SSLeay license texts, see <seealso marker="licenses#licenses">Licenses</seealso>.
- </p>
</description>
<xi:include href="ssl_protocol.xml"/>
<xi:include href="using_ssl.xml"/>
- <xi:include href="pkix_certs.xml"/>
- <xi:include href="create_certs.xml"/>
<xi:include href="ssl_distribution.xml"/>
- <xi:include href="licenses.xml"/>
</part>
diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml
index ba74dcfef4..4bdd8f97b4 100644
--- a/lib/ssl/doc/src/using_ssl.xml
+++ b/lib/ssl/doc/src/using_ssl.xml
@@ -21,93 +21,129 @@
</legalnotice>
- <title>Using the SSL application</title>
- <prepared>Peter H&ouml;gfeldt</prepared>
- <docno></docno>
- <date>2003-04-23</date>
- <rev>PA2</rev>
+ <title>Using the SSL API</title>
<file>using_ssl.xml</file>
</header>
- <p>Here we provide an introduction to using the Erlang/OTP SSL
- application, which is accessed through the <c>ssl</c> interface
- module.
- </p>
- <p>We also present example code in the Erlang module
- <c>client_server</c>, also provided in the directory
- <c>ssl-X.Y.Z/examples</c>, with source code in <c>src</c> and the
- compiled module in <c>ebin</c> of that directory.
- </p>
<section>
- <title>The ssl Module</title>
- <p>The <c>ssl</c> module provides the user interface to the Erlang/OTP
- SSL application. The interface functions provided are very similar
- to those provided by the <c>gen_tcp</c> and <c>inet</c> modules.
- </p>
- <p>Servers use the interface functions <c>listen</c> and
- <c>accept</c>. The <c>listen</c> function specifies a TCP port
- to to listen to, and each call to the <c>accept</c> function
- establishes an incoming connection.
- </p>
- <p>Clients use the <c>connect</c> function which specifies the address
- and port of a server to connect to, and a successful call establishes
- such a connection.
- </p>
- <p>The <c>listen</c> and <c>connect</c> functions have almost all
- the options that the corresponding functions in <c>gen_tcp/</c> have,
- but there are also additional options specific to the SSL protocol.
- </p>
- <p>The most important SSL specific option is the <c>cacertfile</c>
- option which specifies a local file containing trusted CA
- certificates which are and used for peer authentication. This
- option is used by clients and servers in case they want to
- authenticate their peers.
- </p>
- <p>The <c>certfile</c> option specifies a local path to a file
- containing the certificate of the holder of the connection
- endpoint. In case of a server endpoint this option is mandatory
- since the contents of the sever certificate is needed in the
- the handshake preceding the establishment of a connection.
- </p>
- <p>Similarly, the <c>keyfile</c> option points to a local file
- containing the private key of the holder of the endpoint. If the
- <c>certfile</c> option is present, this option has to be
- specified as well, unless the private key is provided in the
- same file as specified by the <c>certfile</c> option (a
- certificate and a private key can thus coexist in the same file).
- </p>
- <p>The <c>verify</c> option specifies how the peer should be verified:
- </p>
- <taglist>
- <tag>0</tag>
- <item>Do not verify the peer,</item>
- <tag>1</tag>
- <item>Verify peer,</item>
- <tag>2</tag>
- <item>Verify peer, fail the verification if the peer has no
- certificate. </item>
- </taglist>
- <p>The <c>depth</c> option specifies the maximum length of the
- verification certificate chain. Depth = 0 means the peer
- certificate, depth = 1 the CA certificate, depth = 2 the next CA
- certificate etc. If the verification process does not find a
- trusted CA certificate within the maximum length, the verification
- fails.
- </p>
- <p>The <c>ciphers</c> option specifies which ciphers to use (a
- string of colon separated cipher names). To obtain a list of
- available ciphers, evaluate the <c>ssl:ciphers/0</c> function
- (the SSL application has to be running).
- </p>
- </section>
+ <title>General information</title>
+ <p>To see relevant version information for ssl you can
+ call ssl:versions/0</p>
+
+ <p>To see all supported cipher suites
+ call ssl:cipher_suites/0. Note that available cipher suites
+ for a connection will depend on your certificate. It is also
+ possible to specify a specific cipher suite(s) that you
+ want your connection to use. Default is to use the strongest
+ available.</p>
- <section>
- <title>A Client-Server Example</title>
- <p>Here is a simple client server example.
- </p>
- <codeinclude file="../../examples/src/client_server.erl" tag="" type="erl"></codeinclude>
</section>
-</chapter>
-
-
+
+ <section>
+ <title>Setting up connections</title>
+
+ <p>Here follows some small example of how to set up client/server connections
+ using the erlang shell. The returned value of the sslsocket has been abbreviated with
+ <c>[...]</c> as it can be fairly large and is opaque.</p>
+
+ <section>
+ <title>Minmal example</title>
+
+ <note><p> The minimal setup is not the most secure setup of ssl.</p>
+ </note>
+
+ <p> Start server side</p>
+ <code type="erl">1 server> ssl:start().
+ok</code>
+
+ <p>Create a ssl listen socket</p>
+ <code type="erl">2 server> {ok, ListenSocket} =
+ssl:listen(9999, [{certfile, "cert.pem"}, {keyfile, "key.pem"},{reuseaddr, true}]).
+{ok,{sslsocket, [...]}}</code>
+
+ <p>Do a transport accept on the ssl listen socket</p>
+ <code type="erl">3 server> {ok, Socket} = ssl:transport_accept(ListenSocket).
+{ok,{sslsocket, [...]}}</code>
+ <p>Start client side</p>
+ <code type="erl">1 client> ssl:start().
+ok</code>
+
+ <code type="erl">2 client> {ok, Socket} = ssl:connect("localhost", 9999, [], infinity).
+{ok,{sslsocket, [...]}}</code>
+
+ <p>Do the ssl handshake</p>
+ <code type="erl">4 server> ok = ssl:ssl_accept(Socket).
+ok</code>
+
+ <p>Send a messag over ssl</p>
+ <code type="erl">5 server> ssl:send(Socket, "foo").
+ok</code>
+
+ <p>Flush the shell message queue to see that we got the message
+ sent on the server side</p>
+ <code type="erl">3 client> flush().
+Shell got {ssl,{sslsocket,[...]},"foo"}
+ok</code>
+ </section>
+
+ <section>
+ <title>Upgrade example</title>
+
+ <note><p> To upgrade a TCP/IP connection to a ssl connection the
+ client and server have to aggre to do so. Agreement
+ may be accompliced by using a protocol such the one used by HTTP
+ specified in RFC 2817.</p> </note>
+
+ <p>Start server side</p>
+ <code type="erl">1 server> ssl:start().
+ok</code>
+
+ <p>Create a normal tcp listen socket</p>
+ <code type="erl">2 server> {ok, ListenSocket} = gen_tcp:listen(9999, [{reuseaddr, true}]).
+{ok, #Port&lt;0.475&gt;}</code>
+
+ <p>Accept client connection</p>
+ <code type="erl">3 server> {ok, Socket} = gen_tcp:accept(ListenSocket).
+{ok, #Port&lt;0.476&gt;}</code>
+
+ <p>Start client side</p>
+ <code type="erl">1 client> ssl:start().
+ok</code>
+
+ <code type="erl">2 client> {ok, Socket} = gen_tcp:connect("localhost", 9999, [], infinity).</code>
+
+ <p>Make sure active is set to false before trying
+ to upgrade a connection to a ssl connection, otherwhise
+ ssl handshake messages may be deliverd to the wrong process.</p>
+ <code type="erl">4 server> inet:setopts(Socket, [{active, false}]).
+ok</code>
+
+ <p>Do the ssl handshake.</p>
+ <code type="erl">5 server> {ok, SSLSocket} = ssl:ssl_accept(Socket, [{cacertfile, "cacerts.pem"},
+{certfile, "cert.pem"}, {keyfile, "key.pem"}]).
+{ok,{sslsocket,[...]}}</code>
+
+ <p> Upgrade to a ssl connection. Note that the client and server
+ must agree upon the upgrade and the server must call
+ ssl:accept/2 before the client calls ssl:connect/3.</p>
+ <code type="erl">3 client>{ok, SSLSocket} = ssl:connect(Socket, [{cacertfile, "cacerts.pem"},
+{certfile, "cert.pem"}, {keyfile, "key.pem"}], infinity).
+{ok,{sslsocket,[...]}}</code>
+
+ <p>Send a messag over ssl</p>
+ <code type="erl">4 client> ssl:send(SSLSocket, "foo").
+ok</code>
+
+ <p>Set active true on the ssl socket</p>
+ <code type="erl">4 server> ssl:setopts(SSLSocket, [{active, true}]).
+ok</code>
+
+ <p>Flush the shell message queue to see that we got the message
+ sent on the client side</p>
+ <code type="erl">5 server> flush().
+Shell got {ssl,{sslsocket,[...]},"foo"}
+ok</code>
+ </section>
+ </section>
+ </chapter>
diff --git a/lib/ssl/pkix/Makefile b/lib/ssl/pkix/Makefile
deleted file mode 100644
index 260361c025..0000000000
--- a/lib/ssl/pkix/Makefile
+++ /dev/null
@@ -1,121 +0,0 @@
-#
-# %CopyrightBegin%
-#
-# Copyright Ericsson AB 2003-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%
-#
-
-#
-
-include $(ERL_TOP)/make/target.mk
-include $(ERL_TOP)/make/$(TARGET)/otp.mk
-
-# ----------------------------------------------------
-# Application version
-# ----------------------------------------------------
-include ../vsn.mk
-VSN=$(SSL_VSN)
-
-# ----------------------------------------------------
-# Release directory specification
-# ----------------------------------------------------
-RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN)
-
-# ----------------------------------------------------
-# Common Macros
-# ----------------------------------------------------
-
-.SUFFIXES: .asn1
-.PRECIOUS: %.erl
-
-ASN_TOP = OTP-PKIX
-ASN_MODULES = PKIX1Explicit88 PKIX1Implicit88 PKIX1Algorithms88 \
- PKIXAttributeCertificate SSL-PKIX
-ASN_ASNS = $(ASN_MODULES:%=%.asn1)
-ASN_ERLS = $(ASN_TOP).erl
-ASN_HRLS = $(ASN_TOP).hrl
-ASN_CONFIGS = OTP-PKIX.asn1config
-ASN_DBS = $(ASN_MODULES:%=%.asn1db)
-ASN_TABLES = $(ASN_MODULES:%=%.table)
-
-GEN_MODULES = ssl_pkix_oid $(ORBER_TMP_FIX_ERL)
-GEN_ERLS = $(GEN_MODULES:%=%.erl)
-ERL_MODULES = $(ASN_TOP) $(GEN_MODULES)
-
-TARGET_FILES= $(ERL_MODULES:%=$(EBIN)/%.$(EMULATOR))
-
-HRL_FILES = $(ASN_HRLS:%=$(INCLUDE)/%)
-
-ORBER_TMP_FIX_HRL = PKIX1Algorithms88.hrl PKIX1Explicit88.hrl \
- PKIX1Implicit88.hrl PKIXAttributeCertificate.hrl
-
-INCLUDE = ../include
-EBIN = ../ebin
-
-# ----------------------------------------------------
-# FLAGS
-# ----------------------------------------------------
-EXTRA_ERLC_FLAGS =
-ERL_COMPILE_FLAGS += $(EXTRA_ERLC_FLAGS)
-
-ASN_FLAGS = -bber_bin +der +compact_bit_string +optimize +noobj +asn1config +inline
-
-# ----------------------------------------------------
-# Targets
-# ----------------------------------------------------
-
-debug opt: $(TARGET_FILES) $(HRL_FILES)
-
-clean:
- -rm -f $(ASN_ERLS) $(GEN_ERLS) $(ASN_HRLS) $(HRL_FILES) $(ASN_DBS) \
- $(ASN_TABLES) $(TARGET_FILES) *.beam *~
-
-docs:
-
-%.erl: %.set.asn
- erlc $(ASN_FLAGS) $<
-
-ssl_pkix_oid.erl: mk_ssl_pkix_oid.beam $(EBIN)/OTP-PKIX.beam
- erl -pa $(EBIN) -noshell -s mk_ssl_pkix_oid make -s erlang halt
-
-$(HRL_FILES): $(ASN_HRLS)
- cp -p $(ASN_HRLS) $(INCLUDE)
-
-# ----------------------------------------------------
-# Release Target
-# ----------------------------------------------------
-include $(ERL_TOP)/make/otp_release_targets.mk
-
-release_spec: opt
- $(INSTALL_DIR) $(RELSYSDIR)/include
- $(INSTALL_DATA) $(HRL_FILES) $(RELSYSDIR)/include
- $(INSTALL_DIR) $(RELSYSDIR)/pkix
- $(INSTALL_DATA) $(ASN_ASNS) $(ASN_ERLS) $(ASN_HRLS) $(ASN_CONFIGS) \
- $(ORBER_TMP_FIX_HRL) $(GEN_ERLS) mk_ssl_pkix_oid.erl $(RELSYSDIR)/pkix
- $(INSTALL_DIR) $(RELSYSDIR)/ebin
- $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
-
-release_docs_spec:
-
-#
-# Dependencies
-
-$(EBIN)/OTP-PKIX.beam: OTP-PKIX.erl OTP-PKIX.hrl
-OTP-PKIX.erl OTP-PKIX.hrl: OTP-PKIX.asn1db
-OTP-PKIX.asn1db: PKIX1Algorithms88.asn1 \
- PKIX1Explicit88.asn1 \
- PKIX1Implicit88.asn1 \
- PKIXAttributeCertificate.asn1 \
- SSL-PKIX.asn1
diff --git a/lib/ssl/pkix/OTP-PKIX.asn1config b/lib/ssl/pkix/OTP-PKIX.asn1config
deleted file mode 100644
index 0caa158f52..0000000000
--- a/lib/ssl/pkix/OTP-PKIX.asn1config
+++ /dev/null
@@ -1,2 +0,0 @@
-{exclusive_decode,{'OTP-PKIX',
- [{decode_TBSCert_exclusive,['Certificate',[{tbsCertificate,undecoded}]]}]}}.
diff --git a/lib/ssl/pkix/OTP-PKIX.set.asn b/lib/ssl/pkix/OTP-PKIX.set.asn
deleted file mode 100644
index 1c3483d519..0000000000
--- a/lib/ssl/pkix/OTP-PKIX.set.asn
+++ /dev/null
@@ -1,6 +0,0 @@
-SSL-PKIX.asn1
-PKIX1Explicit88.asn1
-PKIX1Implicit88.asn1
-PKIXAttributeCertificate.asn1
-PKIX1Algorithms88.asn1
-PKCS-1.asn1
diff --git a/lib/ssl/pkix/PKCS-1.asn1 b/lib/ssl/pkix/PKCS-1.asn1
deleted file mode 100755
index 547cc2e072..0000000000
--- a/lib/ssl/pkix/PKCS-1.asn1
+++ /dev/null
@@ -1,54 +0,0 @@
-PKCS-1 {
- iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-1(1)
- modules(0) pkcs-1(1)
-}
-
-
-DEFINITIONS IMPLICIT TAGS ::= BEGIN
-
--- EXPORTS ALL --
-
-IMPORTS
- AlgorithmIdentifier
- FROM PKIX1Explicit88 {iso(1) identified-organization(3)
- dod(6) internet(1) security(5) mechanisms(5)
- pkix(7) id-mod(0) id-pkix1-explicit-88(1)} ;
-
-pkcs-1 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 }
-
-RSAPrivateKey ::= SEQUENCE {
- version Version,
- modulus INTEGER, -- n
- publicExponent INTEGER, -- e
- privateExponent INTEGER, -- d
- prime1 INTEGER, -- p
- prime2 INTEGER, -- q
- exponent1 INTEGER, -- d mod (p-1)
- exponent2 INTEGER, -- d mod (q-1)
- coefficient INTEGER, -- (inverse of q) mod p
- otherPrimeInfos OtherPrimeInfos OPTIONAL
-}
-
-Version ::= INTEGER { two-prime(0), multi(1) }
- (CONSTRAINED BY {
- -- version must be multi if otherPrimeInfos present --
- })
-
-OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
-
-OtherPrimeInfo ::= SEQUENCE {
- prime INTEGER, -- ri
- exponent INTEGER, -- di
- coefficient INTEGER -- ti
-}
-
-DigestInfo ::= SEQUENCE {
- digestAlgorithm DigestAlgorithmIdentifier,
- digest OCTET STRING
-}
-
-DigestAlgorithmIdentifier ::= AlgorithmIdentifier
-
-END -- PKCS1Definitions
-
diff --git a/lib/ssl/pkix/PKIX1Algorithms88.asn1 b/lib/ssl/pkix/PKIX1Algorithms88.asn1
deleted file mode 100644
index e78de69b0e..0000000000
--- a/lib/ssl/pkix/PKIX1Algorithms88.asn1
+++ /dev/null
@@ -1,274 +0,0 @@
- PKIX1Algorithms88 { iso(1) identified-organization(3) dod(6)
- internet(1) security(5) mechanisms(5) pkix(7) id-mod(0)
- id-mod-pkix1-algorithms(17) }
-
- DEFINITIONS EXPLICIT TAGS ::= BEGIN
-
- -- EXPORTS All;
-
- -- IMPORTS NONE;
-
- --
- -- One-way Hash Functions
- --
-
- md2 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549)
- digestAlgorithm(2) 2 }
-
- md5 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549)
- digestAlgorithm(2) 5 }
-
- id-sha1 OBJECT IDENTIFIER ::= {
- iso(1) identified-organization(3) oiw(14) secsig(3)
- algorithms(2) 26 }
-
- --
- -- DSA Keys and Signatures
- --
-
- -- OID for DSA public key
-
- id-dsa OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) x9-57(10040) x9algorithm(4) 1 }
-
- -- encoding for DSA public key
-
- DSAPublicKey ::= INTEGER -- public key, y
-
- Dss-Parms ::= SEQUENCE {
- p INTEGER,
- q INTEGER,
- g INTEGER }
-
- -- OID for DSA signature generated with SHA-1 hash
-
- id-dsa-with-sha1 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) x9-57 (10040) x9algorithm(4) 3 }
-
- -- encoding for DSA signature generated with SHA-1 hash
-
- Dss-Sig-Value ::= SEQUENCE {
- r INTEGER,
- s INTEGER }
-
- --
- -- RSA Keys and Signatures
- --
-
- -- arc for RSA public key and RSA signature OIDs
-
- pkcs-1 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 }
-
- -- OID for RSA public keys
-
- rsaEncryption OBJECT IDENTIFIER ::= { pkcs-1 1 }
-
- -- OID for RSA signature generated with MD2 hash
-
- md2WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 2 }
-
- -- OID for RSA signature generated with MD5 hash
-
- md5WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 4 }
-
- -- OID for RSA signature generated with SHA-1 hash
-
- sha1WithRSAEncryption OBJECT IDENTIFIER ::= { pkcs-1 5 }
-
- -- encoding for RSA public key
-
- RSAPublicKey ::= SEQUENCE {
- modulus INTEGER, -- n
- publicExponent INTEGER } -- e
-
- --
- -- Diffie-Hellman Keys
- --
-
- dhpublicnumber OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) ansi-x942(10046)
- number-type(2) 1 }
-
- -- encoding for DSA public key
-
- DHPublicKey ::= INTEGER -- public key, y = g^x mod p
-
- DomainParameters ::= SEQUENCE {
- p INTEGER, -- odd prime, p=jq +1
- g INTEGER, -- generator, g
- q INTEGER, -- factor of p-1
- j INTEGER OPTIONAL, -- subgroup factor, j>= 2
- validationParms ValidationParms OPTIONAL }
-
- ValidationParms ::= SEQUENCE {
- seed BIT STRING,
- pgenCounter INTEGER }
-
- --
- -- KEA Keys
- --
-
- id-keyExchangeAlgorithm OBJECT IDENTIFIER ::=
- { 2 16 840 1 101 2 1 1 22 }
-
- KEA-Parms-Id ::= OCTET STRING
-
- --
- -- Elliptic Curve Keys, Signatures, and Curves
- --
-
- ansi-X9-62 OBJECT IDENTIFIER ::= {
- iso(1) member-body(2) us(840) 10045 }
-
- FieldID ::= SEQUENCE { -- Finite field
- fieldType OBJECT IDENTIFIER,
- parameters ANY DEFINED BY fieldType }
-
- -- Arc for ECDSA signature OIDS
-
- id-ecSigType OBJECT IDENTIFIER ::= { ansi-X9-62 signatures(4) }
-
- -- OID for ECDSA signatures with SHA-1
-
- ecdsa-with-SHA1 OBJECT IDENTIFIER ::= { id-ecSigType 1 }
-
- -- OID for an elliptic curve signature
- -- format for the value of an ECDSA signature value
-
- ECDSA-Sig-Value ::= SEQUENCE {
- r INTEGER,
- s INTEGER }
-
- -- recognized field type OIDs are defined in the following arc
-
- id-fieldType OBJECT IDENTIFIER ::= { ansi-X9-62 fieldType(1) }
-
- -- where fieldType is prime-field, the parameters are of type Prime-p
-
- prime-field OBJECT IDENTIFIER ::= { id-fieldType 1 }
-
- Prime-p ::= INTEGER -- Finite field F(p), where p is an odd prime
-
- -- where fieldType is characteristic-two-field, the parameters are
- -- of type Characteristic-two
-
- characteristic-two-field OBJECT IDENTIFIER ::= { id-fieldType 2 }
-
- Characteristic-two ::= SEQUENCE {
- m INTEGER, -- Field size 2^m
- basis OBJECT IDENTIFIER,
- parameters ANY DEFINED BY basis }
-
- -- recognized basis type OIDs are defined in the following arc
-
- id-characteristic-two-basis OBJECT IDENTIFIER ::= {
- characteristic-two-field basisType(3) }
-
- -- gnbasis is identified by OID gnBasis and indicates
- -- parameters are NULL
-
- gnBasis OBJECT IDENTIFIER ::= { id-characteristic-two-basis 1 }
-
- -- parameters for this basis are NULL
-
- -- trinomial basis is identified by OID tpBasis and indicates
- -- parameters of type Pentanomial
-
- tpBasis OBJECT IDENTIFIER ::= { id-characteristic-two-basis 2 }
-
- -- Trinomial basis representation of F2^m
- -- Integer k for reduction polynomial xm + xk + 1
-
- Trinomial ::= INTEGER
-
- -- for pentanomial basis is identified by OID ppBasis and indicates
- -- parameters of type Pentanomial
-
- ppBasis OBJECT IDENTIFIER ::= { id-characteristic-two-basis 3 }
-
- -- Pentanomial basis representation of F2^m
- -- reduction polynomial integers k1, k2, k3
- -- f(x) = x**m + x**k3 + x**k2 + x**k1 + 1
-
- Pentanomial ::= SEQUENCE {
- k1 INTEGER,
- k2 INTEGER,
- k3 INTEGER }
-
- -- The object identifiers gnBasis, tpBasis and ppBasis name
- -- three kinds of basis for characteristic-two finite fields
-
- FieldElement ::= OCTET STRING -- Finite field element
-
- ECPoint ::= OCTET STRING -- Elliptic curve point
-
- -- Elliptic Curve parameters may be specified explicitly,
- -- specified implicitly through a "named curve", or
- -- inherited from the CA
-
- EcpkParameters ::= CHOICE {
- ecParameters ECParameters,
- namedCurve OBJECT IDENTIFIER,
- implicitlyCA NULL }
-
- ECParameters ::= SEQUENCE { -- Elliptic curve parameters
- version ECPVer,
- fieldID FieldID,
- curve Curve,
- base ECPoint, -- Base point G
- order INTEGER, -- Order n of the base point
- cofactor INTEGER OPTIONAL } -- The integer h = #E(Fq)/n
-
- ECPVer ::= INTEGER {ecpVer1(1)}
-
- Curve ::= SEQUENCE {
- a FieldElement, -- Elliptic curve coefficient a
- b FieldElement, -- Elliptic curve coefficient b
- seed BIT STRING OPTIONAL }
-
- id-publicKeyType OBJECT IDENTIFIER ::= { ansi-X9-62 keyType(2) }
-
- id-ecPublicKey OBJECT IDENTIFIER ::= { id-publicKeyType 1 }
-
- -- Named Elliptic Curves in ANSI X9.62.
-
- ellipticCurve OBJECT IDENTIFIER ::= { ansi-X9-62 curves(3) }
-
- c-TwoCurve OBJECT IDENTIFIER ::= {
- ellipticCurve characteristicTwo(0) }
-
- c2pnb163v1 OBJECT IDENTIFIER ::= { c-TwoCurve 1 }
- c2pnb163v2 OBJECT IDENTIFIER ::= { c-TwoCurve 2 }
- c2pnb163v3 OBJECT IDENTIFIER ::= { c-TwoCurve 3 }
- c2pnb176w1 OBJECT IDENTIFIER ::= { c-TwoCurve 4 }
- c2tnb191v1 OBJECT IDENTIFIER ::= { c-TwoCurve 5 }
- c2tnb191v2 OBJECT IDENTIFIER ::= { c-TwoCurve 6 }
- c2tnb191v3 OBJECT IDENTIFIER ::= { c-TwoCurve 7 }
- c2onb191v4 OBJECT IDENTIFIER ::= { c-TwoCurve 8 }
- c2onb191v5 OBJECT IDENTIFIER ::= { c-TwoCurve 9 }
- c2pnb208w1 OBJECT IDENTIFIER ::= { c-TwoCurve 10 }
- c2tnb239v1 OBJECT IDENTIFIER ::= { c-TwoCurve 11 }
- c2tnb239v2 OBJECT IDENTIFIER ::= { c-TwoCurve 12 }
- c2tnb239v3 OBJECT IDENTIFIER ::= { c-TwoCurve 13 }
- c2onb239v4 OBJECT IDENTIFIER ::= { c-TwoCurve 14 }
- c2onb239v5 OBJECT IDENTIFIER ::= { c-TwoCurve 15 }
- c2pnb272w1 OBJECT IDENTIFIER ::= { c-TwoCurve 16 }
- c2pnb304w1 OBJECT IDENTIFIER ::= { c-TwoCurve 17 }
- c2tnb359v1 OBJECT IDENTIFIER ::= { c-TwoCurve 18 }
- c2pnb368w1 OBJECT IDENTIFIER ::= { c-TwoCurve 19 }
- c2tnb431r1 OBJECT IDENTIFIER ::= { c-TwoCurve 20 }
-
- primeCurve OBJECT IDENTIFIER ::= { ellipticCurve prime(1) }
-
- prime192v1 OBJECT IDENTIFIER ::= { primeCurve 1 }
- prime192v2 OBJECT IDENTIFIER ::= { primeCurve 2 }
- prime192v3 OBJECT IDENTIFIER ::= { primeCurve 3 }
- prime239v1 OBJECT IDENTIFIER ::= { primeCurve 4 }
- prime239v2 OBJECT IDENTIFIER ::= { primeCurve 5 }
- prime239v3 OBJECT IDENTIFIER ::= { primeCurve 6 }
- prime256v1 OBJECT IDENTIFIER ::= { primeCurve 7 }
-
- END
diff --git a/lib/ssl/pkix/PKIX1Algorithms88.hrl b/lib/ssl/pkix/PKIX1Algorithms88.hrl
deleted file mode 100644
index a11793618d..0000000000
--- a/lib/ssl/pkix/PKIX1Algorithms88.hrl
+++ /dev/null
@@ -1,94 +0,0 @@
-%% Generated by the Erlang ASN.1 compiler version:1.4.4.8
-%% Purpose: Erlang record definitions for each named and unnamed
-%% SEQUENCE and SET, and macro definitions for each value
-%% definition,in module PKIX1Algorithms88
-
-
-
--record('Dss-Parms',{
-p, q, g}).
-
--record('Dss-Sig-Value',{
-r, s}).
-
--record('RSAPublicKey',{
-modulus, publicExponent}).
-
--record('DomainParameters',{
-p, g, q, j = asn1_NOVALUE, validationParms = asn1_NOVALUE}).
-
--record('ValidationParms',{
-seed, pgenCounter}).
-
--record('FieldID',{
-fieldType, parameters}).
-
--record('ECDSA-Sig-Value',{
-r, s}).
-
--record('Characteristic-two',{
-m, basis, parameters}).
-
--record('Pentanomial',{
-k1, k2, k3}).
-
--record('ECParameters',{
-version, fieldID, curve, base, order, cofactor = asn1_NOVALUE}).
-
--record('Curve',{
-a, b, seed = asn1_NOVALUE}).
-
--define('md2', {1,2,840,113549,2,2}).
--define('md5', {1,2,840,113549,2,5}).
--define('id-sha1', {1,3,14,3,2,26}).
--define('id-dsa', {1,2,840,10040,4,1}).
--define('id-dsa-with-sha1', {1,2,840,10040,4,3}).
--define('pkcs-1', {1,2,840,113549,1,1}).
--define('rsaEncryption', {1,2,840,113549,1,1,1}).
--define('md2WithRSAEncryption', {1,2,840,113549,1,1,2}).
--define('md5WithRSAEncryption', {1,2,840,113549,1,1,4}).
--define('sha1WithRSAEncryption', {1,2,840,113549,1,1,5}).
--define('dhpublicnumber', {1,2,840,10046,2,1}).
--define('id-keyExchangeAlgorithm', {2,16,840,1,101,2,1,1,22}).
--define('ansi-X9-62', {1,2,840,10045}).
--define('id-ecSigType', {1,2,840,10045,4}).
--define('ecdsa-with-SHA1', {1,2,840,10045,4,1}).
--define('id-fieldType', {1,2,840,10045,1}).
--define('prime-field', {1,2,840,10045,1,1}).
--define('characteristic-two-field', {1,2,840,10045,1,2}).
--define('id-characteristic-two-basis', {1,2,840,10045,1,2,3}).
--define('gnBasis', {1,2,840,10045,1,2,3,1}).
--define('tpBasis', {1,2,840,10045,1,2,3,2}).
--define('ppBasis', {1,2,840,10045,1,2,3,3}).
--define('id-publicKeyType', {1,2,840,10045,2}).
--define('id-ecPublicKey', {1,2,840,10045,2,1}).
--define('ellipticCurve', {1,2,840,10045,3}).
--define('c-TwoCurve', {1,2,840,10045,3,0}).
--define('c2pnb163v1', {1,2,840,10045,3,0,1}).
--define('c2pnb163v2', {1,2,840,10045,3,0,2}).
--define('c2pnb163v3', {1,2,840,10045,3,0,3}).
--define('c2pnb176w1', {1,2,840,10045,3,0,4}).
--define('c2tnb191v1', {1,2,840,10045,3,0,5}).
--define('c2tnb191v2', {1,2,840,10045,3,0,6}).
--define('c2tnb191v3', {1,2,840,10045,3,0,7}).
--define('c2onb191v4', {1,2,840,10045,3,0,8}).
--define('c2onb191v5', {1,2,840,10045,3,0,9}).
--define('c2pnb208w1', {1,2,840,10045,3,0,10}).
--define('c2tnb239v1', {1,2,840,10045,3,0,11}).
--define('c2tnb239v2', {1,2,840,10045,3,0,12}).
--define('c2tnb239v3', {1,2,840,10045,3,0,13}).
--define('c2onb239v4', {1,2,840,10045,3,0,14}).
--define('c2onb239v5', {1,2,840,10045,3,0,15}).
--define('c2pnb272w1', {1,2,840,10045,3,0,16}).
--define('c2pnb304w1', {1,2,840,10045,3,0,17}).
--define('c2tnb359v1', {1,2,840,10045,3,0,18}).
--define('c2pnb368w1', {1,2,840,10045,3,0,19}).
--define('c2tnb431r1', {1,2,840,10045,3,0,20}).
--define('primeCurve', {1,2,840,10045,3,1}).
--define('prime192v1', {1,2,840,10045,3,1,1}).
--define('prime192v2', {1,2,840,10045,3,1,2}).
--define('prime192v3', {1,2,840,10045,3,1,3}).
--define('prime239v1', {1,2,840,10045,3,1,4}).
--define('prime239v2', {1,2,840,10045,3,1,5}).
--define('prime239v3', {1,2,840,10045,3,1,6}).
--define('prime256v1', {1,2,840,10045,3,1,7}).
diff --git a/lib/ssl/pkix/PKIX1Explicit88.asn1 b/lib/ssl/pkix/PKIX1Explicit88.asn1
deleted file mode 100644
index 9b8068fed0..0000000000
--- a/lib/ssl/pkix/PKIX1Explicit88.asn1
+++ /dev/null
@@ -1,619 +0,0 @@
-PKIX1Explicit88 { iso(1) identified-organization(3) dod(6) internet(1)
- security(5) mechanisms(5) pkix(7) id-mod(0) id-pkix1-explicit(18) }
-
-DEFINITIONS EXPLICIT TAGS ::=
-
-BEGIN
-
--- EXPORTS ALL --
-
--- IMPORTS NONE --
-
--- UNIVERSAL Types defined in 1993 and 1998 ASN.1
--- and required by this specification
-
--- UniversalString ::= [UNIVERSAL 28] IMPLICIT OCTET STRING
- -- UniversalString is defined in ASN.1:1993
-
--- BMPString ::= [UNIVERSAL 30] IMPLICIT OCTET STRING
- -- BMPString is the subtype of UniversalString and models
- -- the Basic Multilingual Plane of ISO/IEC/ITU 10646-1
-
--- UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING
- -- The content of this type conforms to RFC 2279.
-
--- PKIX specific OIDs
-
-id-pkix OBJECT IDENTIFIER ::=
- { iso(1) identified-organization(3) dod(6) internet(1)
- security(5) mechanisms(5) pkix(7) }
-
--- PKIX arcs
-
-id-pe OBJECT IDENTIFIER ::= { id-pkix 1 }
- -- arc for private certificate extensions
-id-qt OBJECT IDENTIFIER ::= { id-pkix 2 }
- -- arc for policy qualifier types
-id-kp OBJECT IDENTIFIER ::= { id-pkix 3 }
- -- arc for extended key purpose OIDS
-id-ad OBJECT IDENTIFIER ::= { id-pkix 48 }
- -- arc for access descriptors
-
--- policyQualifierIds for Internet policy qualifiers
-
-id-qt-cps OBJECT IDENTIFIER ::= { id-qt 1 }
- -- OID for CPS qualifier
-id-qt-unotice OBJECT IDENTIFIER ::= { id-qt 2 }
- -- OID for user notice qualifier
-
--- access descriptor definitions
-
-id-ad-ocsp OBJECT IDENTIFIER ::= { id-ad 1 }
-id-ad-caIssuers OBJECT IDENTIFIER ::= { id-ad 2 }
-id-ad-timeStamping OBJECT IDENTIFIER ::= { id-ad 3 }
-id-ad-caRepository OBJECT IDENTIFIER ::= { id-ad 5 }
-
--- attribute data types
-
-Attribute ::= SEQUENCE {
- type AttributeType,
- values SET OF AttributeValue }
- -- at least one value is required
-
-AttributeType ::= OBJECT IDENTIFIER
-
-AttributeValue ::= ANY
-
-AttributeTypeAndValue ::= SEQUENCE {
- type AttributeType,
- value AttributeValue }
-
--- suggested naming attributes: Definition of the following
--- information object set may be augmented to meet local
--- requirements. Note that deleting members of the set may
--- prevent interoperability with conforming implementations.
--- presented in pairs: the AttributeType followed by the
--- type definition for the corresponding AttributeValue
---Arc for standard naming attributes
-id-at OBJECT IDENTIFIER ::= { joint-iso-ccitt(2) ds(5) 4 }
-
--- Naming attributes of type X520name
-
-id-at-name AttributeType ::= { id-at 41 }
-id-at-surname AttributeType ::= { id-at 4 }
-id-at-givenName AttributeType ::= { id-at 42 }
-id-at-initials AttributeType ::= { id-at 43 }
-id-at-generationQualifier AttributeType ::= { id-at 44 }
-
-X520name ::= CHOICE {
- teletexString TeletexString (SIZE (1..ub-name)),
- printableString PrintableString (SIZE (1..ub-name)),
- universalString UniversalString (SIZE (1..ub-name)),
- utf8String UTF8String (SIZE (1..ub-name)),
- bmpString BMPString (SIZE (1..ub-name)) }
-
--- Naming attributes of type X520CommonName
-
-id-at-commonName AttributeType ::= { id-at 3 }
-
-X520CommonName ::= CHOICE {
- teletexString TeletexString (SIZE (1..ub-common-name)),
- printableString PrintableString (SIZE (1..ub-common-name)),
- universalString UniversalString (SIZE (1..ub-common-name)),
- utf8String UTF8String (SIZE (1..ub-common-name)),
- bmpString BMPString (SIZE (1..ub-common-name)) }
-
--- Naming attributes of type X520LocalityName
-
-id-at-localityName AttributeType ::= { id-at 7 }
-
-X520LocalityName ::= CHOICE {
- teletexString TeletexString (SIZE (1..ub-locality-name)),
- printableString PrintableString (SIZE (1..ub-locality-name)),
- universalString UniversalString (SIZE (1..ub-locality-name)),
- utf8String UTF8String (SIZE (1..ub-locality-name)),
- bmpString BMPString (SIZE (1..ub-locality-name)) }
-
--- Naming attributes of type X520StateOrProvinceName
-
-id-at-stateOrProvinceName AttributeType ::= { id-at 8 }
-
-X520StateOrProvinceName ::= CHOICE {
- teletexString TeletexString (SIZE (1..ub-state-name)),
- printableString PrintableString (SIZE (1..ub-state-name)),
- universalString UniversalString (SIZE (1..ub-state-name)),
- utf8String UTF8String (SIZE (1..ub-state-name)),
- bmpString BMPString (SIZE(1..ub-state-name)) }
-
--- Naming attributes of type X520OrganizationName
-
-id-at-organizationName AttributeType ::= { id-at 10 }
-
-X520OrganizationName ::= CHOICE {
- teletexString TeletexString
- (SIZE (1..ub-organization-name)),
- printableString PrintableString
- (SIZE (1..ub-organization-name)),
- universalString UniversalString
- (SIZE (1..ub-organization-name)),
- utf8String UTF8String
- (SIZE (1..ub-organization-name)),
- bmpString BMPString
- (SIZE (1..ub-organization-name)) }
-
--- Naming attributes of type X520OrganizationalUnitName
-
-id-at-organizationalUnitName AttributeType ::= { id-at 11 }
-
-X520OrganizationalUnitName ::= CHOICE {
- teletexString TeletexString
- (SIZE (1..ub-organizational-unit-name)),
- printableString PrintableString
- (SIZE (1..ub-organizational-unit-name)),
- universalString UniversalString
- (SIZE (1..ub-organizational-unit-name)),
- utf8String UTF8String
- (SIZE (1..ub-organizational-unit-name)),
- bmpString BMPString
- (SIZE (1..ub-organizational-unit-name)) }
-
--- Naming attributes of type X520Title
-
-id-at-title AttributeType ::= { id-at 12 }
-
-X520Title ::= CHOICE {
- teletexString TeletexString (SIZE (1..ub-title)),
- printableString PrintableString (SIZE (1..ub-title)),
- universalString UniversalString (SIZE (1..ub-title)),
- utf8String UTF8String (SIZE (1..ub-title)),
- bmpString BMPString (SIZE (1..ub-title)) }
-
--- Naming attributes of type X520dnQualifier
-
-id-at-dnQualifier AttributeType ::= { id-at 46 }
-
-X520dnQualifier ::= PrintableString
-
--- Naming attributes of type X520countryName (digraph from IS 3166)
-
-id-at-countryName AttributeType ::= { id-at 6 }
-
-X520countryName ::= PrintableString (SIZE (2))
-
--- Naming attributes of type X520SerialNumber
-
-id-at-serialNumber AttributeType ::= { id-at 5 }
-
-X520SerialNumber ::= PrintableString (SIZE (1..ub-serial-number))
-
--- Naming attributes of type X520Pseudonym
-
-id-at-pseudonym AttributeType ::= { id-at 65 }
-
-X520Pseudonym ::= CHOICE {
- teletexString TeletexString (SIZE (1..ub-pseudonym)),
- printableString PrintableString (SIZE (1..ub-pseudonym)),
- universalString UniversalString (SIZE (1..ub-pseudonym)),
- utf8String UTF8String (SIZE (1..ub-pseudonym)),
- bmpString BMPString (SIZE (1..ub-pseudonym)) }
-
--- Naming attributes of type DomainComponent (from RFC 2247)
-
-id-domainComponent AttributeType ::=
- { 0 9 2342 19200300 100 1 25 }
-
-DomainComponent ::= IA5String
-
--- Legacy attributes
-
-pkcs-9 OBJECT IDENTIFIER ::=
- { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
-
-id-emailAddress AttributeType ::= { pkcs-9 1 }
-
-EmailAddress ::= IA5String (SIZE (1..ub-emailaddress-length))
-
--- naming data types --
-
-Name ::= CHOICE { -- only one possibility for now --
- rdnSequence RDNSequence }
-
-RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
-
-DistinguishedName ::= RDNSequence
-
-RelativeDistinguishedName ::=
- SET SIZE (1 .. MAX) OF AttributeTypeAndValue
-
--- Directory string type --
-
-DirectoryString ::= CHOICE {
- teletexString TeletexString (SIZE (1..MAX)),
- printableString PrintableString (SIZE (1..MAX)),
- universalString UniversalString (SIZE (1..MAX)),
- utf8String UTF8String (SIZE (1..MAX)),
- bmpString BMPString (SIZE (1..MAX)) }
-
--- certificate and CRL specific structures begin here
-
-Certificate ::= SEQUENCE {
- tbsCertificate TBSCertificate,
- signatureAlgorithm AlgorithmIdentifier,
- signature BIT STRING }
-
-TBSCertificate ::= SEQUENCE {
- version [0] Version DEFAULT v1,
- serialNumber CertificateSerialNumber,
- signature AlgorithmIdentifier,
- issuer Name,
- validity Validity,
- subject Name,
- subjectPublicKeyInfo SubjectPublicKeyInfo,
- issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
- -- If present, version MUST be v2 or v3
- subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
- -- If present, version MUST be v2 or v3
- extensions [3] Extensions OPTIONAL
- -- If present, version MUST be v3 -- }
-
-Version ::= INTEGER { v1(0), v2(1), v3(2) }
-
-CertificateSerialNumber ::= INTEGER
-
-Validity ::= SEQUENCE {
- notBefore Time,
- notAfter Time }
-
-Time ::= CHOICE {
- utcTime UTCTime,
- generalTime GeneralizedTime }
-
-UniqueIdentifier ::= BIT STRING
-
-SubjectPublicKeyInfo ::= SEQUENCE {
- algorithm AlgorithmIdentifier,
- subjectPublicKey BIT STRING }
-
-Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
-
-Extension ::= SEQUENCE {
- extnID OBJECT IDENTIFIER,
- critical BOOLEAN DEFAULT FALSE,
- extnValue OCTET STRING }
-
--- CRL structures
-
-CertificateList ::= SEQUENCE {
- tbsCertList TBSCertList,
- signatureAlgorithm AlgorithmIdentifier,
- signature BIT STRING }
-
-TBSCertList ::= SEQUENCE {
- version Version OPTIONAL,
- -- if present, MUST be v2
- signature AlgorithmIdentifier,
- issuer Name,
- thisUpdate Time,
- nextUpdate Time OPTIONAL,
- revokedCertificates SEQUENCE OF SEQUENCE {
- userCertificate CertificateSerialNumber,
- revocationDate Time,
- crlEntryExtensions Extensions OPTIONAL
- -- if present, MUST be v2
- } OPTIONAL,
- crlExtensions [0] Extensions OPTIONAL }
- -- if present, MUST be v2
-
--- Version, Time, CertificateSerialNumber, and Extensions were
--- defined earlier for use in the certificate structure
-
-AlgorithmIdentifier ::= SEQUENCE {
- algorithm OBJECT IDENTIFIER,
- parameters ANY DEFINED BY algorithm OPTIONAL }
- -- contains a value of the type
- -- registered for use with the
- -- algorithm object identifier value
-
--- X.400 address syntax starts here
-
-ORAddress ::= SEQUENCE {
- built-in-standard-attributes BuiltInStandardAttributes,
- built-in-domain-defined-attributes
- BuiltInDomainDefinedAttributes OPTIONAL,
- -- see also teletex-domain-defined-attributes
- extension-attributes ExtensionAttributes OPTIONAL }
-
--- Built-in Standard Attributes
-
-BuiltInStandardAttributes ::= SEQUENCE {
- country-name CountryName OPTIONAL,
- administration-domain-name AdministrationDomainName OPTIONAL,
- network-address [0] IMPLICIT NetworkAddress OPTIONAL,
- -- see also extended-network-address
- terminal-identifier [1] IMPLICIT TerminalIdentifier OPTIONAL,
- private-domain-name [2] PrivateDomainName OPTIONAL,
- organization-name [3] IMPLICIT OrganizationName OPTIONAL,
- -- see also teletex-organization-name
- numeric-user-identifier [4] IMPLICIT NumericUserIdentifier
- OPTIONAL,
- personal-name [5] IMPLICIT PersonalName OPTIONAL,
- -- see also teletex-personal-name
- organizational-unit-names [6] IMPLICIT OrganizationalUnitNames
- OPTIONAL }
- -- see also teletex-organizational-unit-names
-
-CountryName ::= [APPLICATION 1] CHOICE {
- x121-dcc-code NumericString
- (SIZE (ub-country-name-numeric-length)),
- iso-3166-alpha2-code PrintableString
- (SIZE (ub-country-name-alpha-length)) }
-
-AdministrationDomainName ::= [APPLICATION 2] CHOICE {
- numeric NumericString (SIZE (0..ub-domain-name-length)),
- printable PrintableString (SIZE (0..ub-domain-name-length)) }
-
-NetworkAddress ::= X121Address -- see also extended-network-address
-
-X121Address ::= NumericString (SIZE (1..ub-x121-address-length))
-
-TerminalIdentifier ::= PrintableString (SIZE
-(1..ub-terminal-id-length))
-
-PrivateDomainName ::= CHOICE {
- numeric NumericString (SIZE (1..ub-domain-name-length)),
- printable PrintableString (SIZE (1..ub-domain-name-length)) }
-
-OrganizationName ::= PrintableString
- (SIZE (1..ub-organization-name-length))
- -- see also teletex-organization-name
-
-NumericUserIdentifier ::= NumericString
- (SIZE (1..ub-numeric-user-id-length))
-
-PersonalName ::= SET {
- surname [0] IMPLICIT PrintableString
- (SIZE (1..ub-surname-length)),
- given-name [1] IMPLICIT PrintableString
- (SIZE (1..ub-given-name-length)) OPTIONAL,
- initials [2] IMPLICIT PrintableString
- (SIZE (1..ub-initials-length)) OPTIONAL,
- generation-qualifier [3] IMPLICIT PrintableString
- (SIZE (1..ub-generation-qualifier-length))
- OPTIONAL }
- -- see also teletex-personal-name
-
-OrganizationalUnitNames ::= SEQUENCE SIZE (1..ub-organizational-units)
- OF OrganizationalUnitName
- -- see also teletex-organizational-unit-names
-
-OrganizationalUnitName ::= PrintableString (SIZE
- (1..ub-organizational-unit-name-length))
-
--- Built-in Domain-defined Attributes
-
-BuiltInDomainDefinedAttributes ::= SEQUENCE SIZE
- (1..ub-domain-defined-attributes) OF
- BuiltInDomainDefinedAttribute
-
-BuiltInDomainDefinedAttribute ::= SEQUENCE {
- type PrintableString (SIZE
- (1..ub-domain-defined-attribute-type-length)),
- value PrintableString (SIZE
- (1..ub-domain-defined-attribute-value-length)) }
-
--- Extension Attributes
-
-ExtensionAttributes ::= SET SIZE (1..ub-extension-attributes) OF
- ExtensionAttribute
-
-ExtensionAttribute ::= SEQUENCE {
- extension-attribute-type [0] IMPLICIT INTEGER
- (0..ub-extension-attributes),
- extension-attribute-value [1]
- ANY DEFINED BY extension-attribute-type }
-
--- Extension types and attribute values
-
-common-name INTEGER ::= 1
-
-CommonName ::= PrintableString (SIZE (1..ub-common-name-length))
-
-teletex-common-name INTEGER ::= 2
-
-TeletexCommonName ::= TeletexString (SIZE (1..ub-common-name-length))
-
-teletex-organization-name INTEGER ::= 3
-
-TeletexOrganizationName ::=
- TeletexString (SIZE (1..ub-organization-name-length))
-
-teletex-personal-name INTEGER ::= 4
-
-TeletexPersonalName ::= SET {
- surname [0] IMPLICIT TeletexString
- (SIZE (1..ub-surname-length)),
- given-name [1] IMPLICIT TeletexString
- (SIZE (1..ub-given-name-length)) OPTIONAL,
- initials [2] IMPLICIT TeletexString
- (SIZE (1..ub-initials-length)) OPTIONAL,
- generation-qualifier [3] IMPLICIT TeletexString
- (SIZE (1..ub-generation-qualifier-length))
- OPTIONAL }
-
-teletex-organizational-unit-names INTEGER ::= 5
-
-TeletexOrganizationalUnitNames ::= SEQUENCE SIZE
- (1..ub-organizational-units) OF TeletexOrganizationalUnitName
-
-TeletexOrganizationalUnitName ::= TeletexString
- (SIZE (1..ub-organizational-unit-name-length))
-
-pds-name INTEGER ::= 7
-
-PDSName ::= PrintableString (SIZE (1..ub-pds-name-length))
-
-physical-delivery-country-name INTEGER ::= 8
-
-PhysicalDeliveryCountryName ::= CHOICE {
- x121-dcc-code NumericString (SIZE
-(ub-country-name-numeric-length)),
- iso-3166-alpha2-code PrintableString
- (SIZE (ub-country-name-alpha-length)) }
-
-postal-code INTEGER ::= 9
-
-PostalCode ::= CHOICE {
- numeric-code NumericString (SIZE (1..ub-postal-code-length)),
- printable-code PrintableString (SIZE (1..ub-postal-code-length)) }
-
-physical-delivery-office-name INTEGER ::= 10
-
-PhysicalDeliveryOfficeName ::= PDSParameter
-
-physical-delivery-office-number INTEGER ::= 11
-
-PhysicalDeliveryOfficeNumber ::= PDSParameter
-
-extension-OR-address-components INTEGER ::= 12
-
-ExtensionORAddressComponents ::= PDSParameter
-
-physical-delivery-personal-name INTEGER ::= 13
-
-PhysicalDeliveryPersonalName ::= PDSParameter
-
-physical-delivery-organization-name INTEGER ::= 14
-
-PhysicalDeliveryOrganizationName ::= PDSParameter
-
-extension-physical-delivery-address-components INTEGER ::= 15
-
-ExtensionPhysicalDeliveryAddressComponents ::= PDSParameter
-
-unformatted-postal-address INTEGER ::= 16
-
-UnformattedPostalAddress ::= SET {
- printable-address SEQUENCE SIZE (1..ub-pds-physical-address-lines)
- OF PrintableString (SIZE (1..ub-pds-parameter-length))
- OPTIONAL,
- teletex-string TeletexString
- (SIZE (1..ub-unformatted-address-length)) OPTIONAL }
-
-street-address INTEGER ::= 17
-
-StreetAddress ::= PDSParameter
-
-post-office-box-address INTEGER ::= 18
-
-PostOfficeBoxAddress ::= PDSParameter
-
-poste-restante-address INTEGER ::= 19
-
-PosteRestanteAddress ::= PDSParameter
-
-unique-postal-name INTEGER ::= 20
-
-UniquePostalName ::= PDSParameter
-
-local-postal-attributes INTEGER ::= 21
-
-LocalPostalAttributes ::= PDSParameter
-
-PDSParameter ::= SET {
- printable-string PrintableString
- (SIZE(1..ub-pds-parameter-length)) OPTIONAL,
- teletex-string TeletexString
- (SIZE(1..ub-pds-parameter-length)) OPTIONAL }
-
-extended-network-address INTEGER ::= 22
-
-ExtendedNetworkAddress ::= CHOICE {
- e163-4-address SEQUENCE {
- number [0] IMPLICIT NumericString
- (SIZE (1..ub-e163-4-number-length)),
- sub-address [1] IMPLICIT NumericString
- (SIZE (1..ub-e163-4-sub-address-length))
- OPTIONAL },
- psap-address [0] IMPLICIT PresentationAddress }
-
-PresentationAddress ::= SEQUENCE {
- pSelector [0] EXPLICIT OCTET STRING OPTIONAL,
- sSelector [1] EXPLICIT OCTET STRING OPTIONAL,
- tSelector [2] EXPLICIT OCTET STRING OPTIONAL,
- nAddresses [3] EXPLICIT SET SIZE (1..MAX) OF OCTET STRING }
-
-terminal-type INTEGER ::= 23
-
-TerminalType ::= INTEGER {
- telex (3),
- teletex (4),
- g3-facsimile (5),
- g4-facsimile (6),
- ia5-terminal (7),
- videotex (8) } (0..ub-integer-options)
-
--- Extension Domain-defined Attributes
-
-teletex-domain-defined-attributes INTEGER ::= 6
-
-TeletexDomainDefinedAttributes ::= SEQUENCE SIZE
- (1..ub-domain-defined-attributes) OF TeletexDomainDefinedAttribute
-
-TeletexDomainDefinedAttribute ::= SEQUENCE {
- type TeletexString
- (SIZE (1..ub-domain-defined-attribute-type-length)),
- value TeletexString
- (SIZE (1..ub-domain-defined-attribute-value-length)) }
-
--- specifications of Upper Bounds MUST be regarded as mandatory
--- from Annex B of ITU-T X.411 Reference Definition of MTS Parameter
--- Upper Bounds
-
--- Upper Bounds
-ub-name INTEGER ::= 32768
-ub-common-name INTEGER ::= 64
-ub-locality-name INTEGER ::= 128
-ub-state-name INTEGER ::= 128
-ub-organization-name INTEGER ::= 64
-ub-organizational-unit-name INTEGER ::= 64
-ub-title INTEGER ::= 64
-ub-serial-number INTEGER ::= 64
-ub-match INTEGER ::= 128
-ub-emailaddress-length INTEGER ::= 128
-ub-common-name-length INTEGER ::= 64
-ub-country-name-alpha-length INTEGER ::= 2
-ub-country-name-numeric-length INTEGER ::= 3
-ub-domain-defined-attributes INTEGER ::= 4
-ub-domain-defined-attribute-type-length INTEGER ::= 8
-ub-domain-defined-attribute-value-length INTEGER ::= 128
-ub-domain-name-length INTEGER ::= 16
-ub-extension-attributes INTEGER ::= 256
-ub-e163-4-number-length INTEGER ::= 15
-ub-e163-4-sub-address-length INTEGER ::= 40
-ub-generation-qualifier-length INTEGER ::= 3
-ub-given-name-length INTEGER ::= 16
-ub-initials-length INTEGER ::= 5
-ub-integer-options INTEGER ::= 256
-ub-numeric-user-id-length INTEGER ::= 32
-ub-organization-name-length INTEGER ::= 64
-ub-organizational-unit-name-length INTEGER ::= 32
-ub-organizational-units INTEGER ::= 4
-ub-pds-name-length INTEGER ::= 16
-ub-pds-parameter-length INTEGER ::= 30
-ub-pds-physical-address-lines INTEGER ::= 6
-ub-postal-code-length INTEGER ::= 16
-ub-pseudonym INTEGER ::= 128
-ub-surname-length INTEGER ::= 40
-ub-terminal-id-length INTEGER ::= 24
-ub-unformatted-address-length INTEGER ::= 180
-ub-x121-address-length INTEGER ::= 16
-
--- Note - upper bounds on string types, such as TeletexString, are
--- measured in characters. Excepting PrintableString or IA5String, a
--- significantly greater number of octets will be required to hold
--- such a value. As a minimum, 16 octets, or twice the specified
--- upper bound, whichever is the larger, should be allowed for
--- TeletexString. For UTF8String or UniversalString at least four
--- times the upper bound should be allowed.
-
-END
diff --git a/lib/ssl/pkix/PKIX1Explicit88.hrl b/lib/ssl/pkix/PKIX1Explicit88.hrl
deleted file mode 100644
index 5940c1e245..0000000000
--- a/lib/ssl/pkix/PKIX1Explicit88.hrl
+++ /dev/null
@@ -1,163 +0,0 @@
-%% Generated by the Erlang ASN.1 compiler version:1.4.4.8
-%% Purpose: Erlang record definitions for each named and unnamed
-%% SEQUENCE and SET, and macro definitions for each value
-%% definition,in module PKIX1Explicit88
-
-
-
--record('Attribute',{
-type, values}).
-
--record('AttributeTypeAndValue',{
-type, value}).
-
--record('Certificate',{
-tbsCertificate, signatureAlgorithm, signature}).
-
--record('TBSCertificate',{
-version = asn1_DEFAULT, serialNumber, signature, issuer, validity, subject, subjectPublicKeyInfo, issuerUniqueID = asn1_NOVALUE, subjectUniqueID = asn1_NOVALUE, extensions = asn1_NOVALUE}).
-
--record('Validity',{
-notBefore, notAfter}).
-
--record('SubjectPublicKeyInfo',{
-algorithm, subjectPublicKey}).
-
--record('Extension',{
-extnID, critical = asn1_DEFAULT, extnValue}).
-
--record('CertificateList',{
-tbsCertList, signatureAlgorithm, signature}).
-
--record('TBSCertList',{
-version = asn1_NOVALUE, signature, issuer, thisUpdate, nextUpdate = asn1_NOVALUE, revokedCertificates = asn1_NOVALUE, crlExtensions = asn1_NOVALUE}).
-
--record('TBSCertList_revokedCertificates_SEQOF',{
-userCertificate, revocationDate, crlEntryExtensions = asn1_NOVALUE}).
-
--record('AlgorithmIdentifier',{
-algorithm, parameters = asn1_NOVALUE}).
-
--record('ORAddress',{
-'built-in-standard-attributes', 'built-in-domain-defined-attributes' = asn1_NOVALUE, 'extension-attributes' = asn1_NOVALUE}).
-
--record('BuiltInStandardAttributes',{
-'country-name' = asn1_NOVALUE, 'administration-domain-name' = asn1_NOVALUE, 'network-address' = asn1_NOVALUE, 'terminal-identifier' = asn1_NOVALUE, 'private-domain-name' = asn1_NOVALUE, 'organization-name' = asn1_NOVALUE, 'numeric-user-identifier' = asn1_NOVALUE, 'personal-name' = asn1_NOVALUE, 'organizational-unit-names' = asn1_NOVALUE}).
-
--record('PersonalName',{
-surname, 'given-name' = asn1_NOVALUE, initials = asn1_NOVALUE, 'generation-qualifier' = asn1_NOVALUE}).
-
--record('BuiltInDomainDefinedAttribute',{
-type, value}).
-
--record('ExtensionAttribute',{
-'extension-attribute-type', 'extension-attribute-value'}).
-
--record('TeletexPersonalName',{
-surname, 'given-name' = asn1_NOVALUE, initials = asn1_NOVALUE, 'generation-qualifier' = asn1_NOVALUE}).
-
--record('UnformattedPostalAddress',{
-'printable-address' = asn1_NOVALUE, 'teletex-string' = asn1_NOVALUE}).
-
--record('PDSParameter',{
-'printable-string' = asn1_NOVALUE, 'teletex-string' = asn1_NOVALUE}).
-
--record('ExtendedNetworkAddress_e163-4-address',{
-number, 'sub-address' = asn1_NOVALUE}).
-
--record('PresentationAddress',{
-pSelector = asn1_NOVALUE, sSelector = asn1_NOVALUE, tSelector = asn1_NOVALUE, nAddresses}).
-
--record('TeletexDomainDefinedAttribute',{
-type, value}).
-
--define('id-pkix', {1,3,6,1,5,5,7}).
--define('id-pe', {1,3,6,1,5,5,7,1}).
--define('id-qt', {1,3,6,1,5,5,7,2}).
--define('id-kp', {1,3,6,1,5,5,7,3}).
--define('id-ad', {1,3,6,1,5,5,7,48}).
--define('id-qt-cps', {1,3,6,1,5,5,7,2,1}).
--define('id-qt-unotice', {1,3,6,1,5,5,7,2,2}).
--define('id-ad-ocsp', {1,3,6,1,5,5,7,48,1}).
--define('id-ad-caIssuers', {1,3,6,1,5,5,7,48,2}).
--define('id-ad-timeStamping', {1,3,6,1,5,5,7,48,3}).
--define('id-ad-caRepository', {1,3,6,1,5,5,7,48,5}).
--define('id-at', {2,5,4}).
--define('id-at-name', {2,5,4,41}).
--define('id-at-surname', {2,5,4,4}).
--define('id-at-givenName', {2,5,4,42}).
--define('id-at-initials', {2,5,4,43}).
--define('id-at-generationQualifier', {2,5,4,44}).
--define('id-at-commonName', {2,5,4,3}).
--define('id-at-localityName', {2,5,4,7}).
--define('id-at-stateOrProvinceName', {2,5,4,8}).
--define('id-at-organizationName', {2,5,4,10}).
--define('id-at-organizationalUnitName', {2,5,4,11}).
--define('id-at-title', {2,5,4,12}).
--define('id-at-dnQualifier', {2,5,4,46}).
--define('id-at-countryName', {2,5,4,6}).
--define('id-at-serialNumber', {2,5,4,5}).
--define('id-at-pseudonym', {2,5,4,65}).
--define('id-domainComponent', {0,9,2342,19200300,100,1,25}).
--define('pkcs-9', {1,2,840,113549,1,9}).
--define('id-emailAddress', {1,2,840,113549,1,9,1}).
--define('common-name', 1).
--define('teletex-common-name', 2).
--define('teletex-organization-name', 3).
--define('teletex-personal-name', 4).
--define('teletex-organizational-unit-names', 5).
--define('pds-name', 7).
--define('physical-delivery-country-name', 8).
--define('postal-code', 9).
--define('physical-delivery-office-name', 10).
--define('physical-delivery-office-number', 11).
--define('extension-OR-address-components', 12).
--define('physical-delivery-personal-name', 13).
--define('physical-delivery-organization-name', 14).
--define('extension-physical-delivery-address-components', 15).
--define('unformatted-postal-address', 16).
--define('street-address', 17).
--define('post-office-box-address', 18).
--define('poste-restante-address', 19).
--define('unique-postal-name', 20).
--define('local-postal-attributes', 21).
--define('extended-network-address', 22).
--define('terminal-type', 23).
--define('teletex-domain-defined-attributes', 6).
--define('ub-name', 32768).
--define('ub-common-name', 64).
--define('ub-locality-name', 128).
--define('ub-state-name', 128).
--define('ub-organization-name', 64).
--define('ub-organizational-unit-name', 64).
--define('ub-title', 64).
--define('ub-serial-number', 64).
--define('ub-match', 128).
--define('ub-emailaddress-length', 128).
--define('ub-common-name-length', 64).
--define('ub-country-name-alpha-length', 2).
--define('ub-country-name-numeric-length', 3).
--define('ub-domain-defined-attributes', 4).
--define('ub-domain-defined-attribute-type-length', 8).
--define('ub-domain-defined-attribute-value-length', 128).
--define('ub-domain-name-length', 16).
--define('ub-extension-attributes', 256).
--define('ub-e163-4-number-length', 15).
--define('ub-e163-4-sub-address-length', 40).
--define('ub-generation-qualifier-length', 3).
--define('ub-given-name-length', 16).
--define('ub-initials-length', 5).
--define('ub-integer-options', 256).
--define('ub-numeric-user-id-length', 32).
--define('ub-organization-name-length', 64).
--define('ub-organizational-unit-name-length', 32).
--define('ub-organizational-units', 4).
--define('ub-pds-name-length', 16).
--define('ub-pds-parameter-length', 30).
--define('ub-pds-physical-address-lines', 6).
--define('ub-postal-code-length', 16).
--define('ub-pseudonym', 128).
--define('ub-surname-length', 40).
--define('ub-terminal-id-length', 24).
--define('ub-unformatted-address-length', 180).
--define('ub-x121-address-length', 16).
diff --git a/lib/ssl/pkix/PKIX1Implicit88.asn1 b/lib/ssl/pkix/PKIX1Implicit88.asn1
deleted file mode 100644
index ced270baf6..0000000000
--- a/lib/ssl/pkix/PKIX1Implicit88.asn1
+++ /dev/null
@@ -1,349 +0,0 @@
-PKIX1Implicit88 { iso(1) identified-organization(3) dod(6) internet(1)
- security(5) mechanisms(5) pkix(7) id-mod(0) id-pkix1-implicit(19) }
-
-DEFINITIONS IMPLICIT TAGS ::=
-
-BEGIN
-
--- EXPORTS ALL --
-
-IMPORTS
- id-pe, id-kp, id-qt-unotice, id-qt-cps,
- -- delete following line if "new" types are supported --
- -- BMPString,
- -- UTF8String, end "new" types --
- ORAddress, Name, RelativeDistinguishedName,
- CertificateSerialNumber, Attribute, DirectoryString
- FROM PKIX1Explicit88 { iso(1) identified-organization(3)
- dod(6) internet(1) security(5) mechanisms(5) pkix(7)
- id-mod(0) id-pkix1-explicit(18) };
-
-
--- ISO arc for standard certificate and CRL extensions
-
-id-ce OBJECT IDENTIFIER ::= {joint-iso-ccitt(2) ds(5) 29}
-
--- authority key identifier OID and syntax
-
-id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 35 }
-
-AuthorityKeyIdentifier ::= SEQUENCE {
- keyIdentifier [0] KeyIdentifier OPTIONAL,
- authorityCertIssuer [1] GeneralNames OPTIONAL,
- authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
- -- authorityCertIssuer and authorityCertSerialNumber MUST both
- -- be present or both be absent
-
-KeyIdentifier ::= OCTET STRING
-
--- subject key identifier OID and syntax
-
-id-ce-subjectKeyIdentifier OBJECT IDENTIFIER ::= { id-ce 14 }
-
-SubjectKeyIdentifier ::= KeyIdentifier
-
--- key usage extension OID and syntax
-
-id-ce-keyUsage OBJECT IDENTIFIER ::= { id-ce 15 }
-
-KeyUsage ::= BIT STRING {
- digitalSignature (0),
- nonRepudiation (1),
- keyEncipherment (2),
- dataEncipherment (3),
- keyAgreement (4),
- keyCertSign (5),
- cRLSign (6),
- encipherOnly (7),
- decipherOnly (8) }
-
--- private key usage period extension OID and syntax
-
-id-ce-privateKeyUsagePeriod OBJECT IDENTIFIER ::= { id-ce 16 }
-
-PrivateKeyUsagePeriod ::= SEQUENCE {
- notBefore [0] GeneralizedTime OPTIONAL,
- notAfter [1] GeneralizedTime OPTIONAL }
- -- either notBefore or notAfter MUST be present
-
--- certificate policies extension OID and syntax
-
-id-ce-certificatePolicies OBJECT IDENTIFIER ::= { id-ce 32 }
-
-anyPolicy OBJECT IDENTIFIER ::= { id-ce-certificatePolicies 0 }
-
-CertificatePolicies ::= SEQUENCE SIZE (1..MAX) OF PolicyInformation
-
-PolicyInformation ::= SEQUENCE {
- policyIdentifier CertPolicyId,
- policyQualifiers SEQUENCE SIZE (1..MAX) OF
- PolicyQualifierInfo OPTIONAL }
-
-CertPolicyId ::= OBJECT IDENTIFIER
-
-PolicyQualifierInfo ::= SEQUENCE {
- policyQualifierId PolicyQualifierId,
- qualifier ANY DEFINED BY policyQualifierId }
-
--- Implementations that recognize additional policy qualifiers MUST
--- augment the following definition for PolicyQualifierId
-
-PolicyQualifierId ::=
- OBJECT IDENTIFIER ( id-qt-cps | id-qt-unotice )
-
--- CPS pointer qualifier
-
-CPSuri ::= IA5String
-
--- user notice qualifier
-
-UserNotice ::= SEQUENCE {
- noticeRef NoticeReference OPTIONAL,
- explicitText DisplayText OPTIONAL}
-
-NoticeReference ::= SEQUENCE {
- organization DisplayText,
- noticeNumbers SEQUENCE OF INTEGER }
-
-DisplayText ::= CHOICE {
- ia5String IA5String (SIZE (1..200)),
- visibleString VisibleString (SIZE (1..200)),
- bmpString BMPString (SIZE (1..200)),
- utf8String UTF8String (SIZE (1..200)) }
-
--- policy mapping extension OID and syntax
-
-id-ce-policyMappings OBJECT IDENTIFIER ::= { id-ce 33 }
-
-PolicyMappings ::= SEQUENCE SIZE (1..MAX) OF SEQUENCE {
- issuerDomainPolicy CertPolicyId,
- subjectDomainPolicy CertPolicyId }
-
--- subject alternative name extension OID and syntax
-
-id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
-
-SubjectAltName ::= GeneralNames
-
-GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
-
-GeneralName ::= CHOICE {
- otherName [0] AnotherName,
- rfc822Name [1] IA5String,
- dNSName [2] IA5String,
- x400Address [3] ORAddress,
- directoryName [4] Name,
- ediPartyName [5] EDIPartyName,
- uniformResourceIdentifier [6] IA5String,
- iPAddress [7] OCTET STRING,
- registeredID [8] OBJECT IDENTIFIER }
-
--- AnotherName replaces OTHER-NAME ::= TYPE-IDENTIFIER, as
--- TYPE-IDENTIFIER is not supported in the '88 ASN.1 syntax
-
-AnotherName ::= SEQUENCE {
- type-id OBJECT IDENTIFIER,
- value [0] EXPLICIT ANY DEFINED BY type-id }
-
-EDIPartyName ::= SEQUENCE {
- nameAssigner [0] DirectoryString OPTIONAL,
- partyName [1] DirectoryString }
-
--- issuer alternative name extension OID and syntax
-
-id-ce-issuerAltName OBJECT IDENTIFIER ::= { id-ce 18 }
-
-IssuerAltName ::= GeneralNames
-
-id-ce-subjectDirectoryAttributes OBJECT IDENTIFIER ::= { id-ce 9 }
-
-SubjectDirectoryAttributes ::= SEQUENCE SIZE (1..MAX) OF Attribute
-
--- basic constraints extension OID and syntax
-
-id-ce-basicConstraints OBJECT IDENTIFIER ::= { id-ce 19 }
-
-BasicConstraints ::= SEQUENCE {
- cA BOOLEAN DEFAULT FALSE,
- pathLenConstraint INTEGER (0..MAX) OPTIONAL }
-
--- name constraints extension OID and syntax
-
-id-ce-nameConstraints OBJECT IDENTIFIER ::= { id-ce 30 }
-
-NameConstraints ::= SEQUENCE {
- permittedSubtrees [0] GeneralSubtrees OPTIONAL,
- excludedSubtrees [1] GeneralSubtrees OPTIONAL }
-
-GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
-
-GeneralSubtree ::= SEQUENCE {
- base GeneralName,
- minimum [0] BaseDistance DEFAULT 0,
- maximum [1] BaseDistance OPTIONAL }
-
-BaseDistance ::= INTEGER (0..MAX)
-
--- policy constraints extension OID and syntax
-
-id-ce-policyConstraints OBJECT IDENTIFIER ::= { id-ce 36 }
-
-PolicyConstraints ::= SEQUENCE {
- requireExplicitPolicy [0] SkipCerts OPTIONAL,
- inhibitPolicyMapping [1] SkipCerts OPTIONAL }
-
-SkipCerts ::= INTEGER (0..MAX)
-
--- CRL distribution points extension OID and syntax
-
-id-ce-cRLDistributionPoints OBJECT IDENTIFIER ::= {id-ce 31}
-
-CRLDistributionPoints ::= SEQUENCE SIZE (1..MAX) OF DistributionPoint
-
-DistributionPoint ::= SEQUENCE {
- distributionPoint [0] DistributionPointName OPTIONAL,
- reasons [1] ReasonFlags OPTIONAL,
- cRLIssuer [2] GeneralNames OPTIONAL }
-
-DistributionPointName ::= CHOICE {
- fullName [0] GeneralNames,
- nameRelativeToCRLIssuer [1] RelativeDistinguishedName }
-
-ReasonFlags ::= BIT STRING {
- unused (0),
- keyCompromise (1),
- cACompromise (2),
- affiliationChanged (3),
- superseded (4),
- cessationOfOperation (5),
- certificateHold (6),
- privilegeWithdrawn (7),
- aACompromise (8) }
-
--- extended key usage extension OID and syntax
-
-id-ce-extKeyUsage OBJECT IDENTIFIER ::= {id-ce 37}
-
-ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId
-
-
-KeyPurposeId ::= OBJECT IDENTIFIER
-
--- permit unspecified key uses
-
-anyExtendedKeyUsage OBJECT IDENTIFIER ::= { id-ce-extKeyUsage 0 }
-
--- extended key purpose OIDs
-
-id-kp-serverAuth OBJECT IDENTIFIER ::= { id-kp 1 }
-id-kp-clientAuth OBJECT IDENTIFIER ::= { id-kp 2 }
-id-kp-codeSigning OBJECT IDENTIFIER ::= { id-kp 3 }
-id-kp-emailProtection OBJECT IDENTIFIER ::= { id-kp 4 }
-id-kp-timeStamping OBJECT IDENTIFIER ::= { id-kp 8 }
-id-kp-OCSPSigning OBJECT IDENTIFIER ::= { id-kp 9 }
-
--- inhibit any policy OID and syntax
-
-id-ce-inhibitAnyPolicy OBJECT IDENTIFIER ::= { id-ce 54 }
-
-InhibitAnyPolicy ::= SkipCerts
-
--- freshest (delta)CRL extension OID and syntax
-
-id-ce-freshestCRL OBJECT IDENTIFIER ::= { id-ce 46 }
-
-FreshestCRL ::= CRLDistributionPoints
-
--- authority info access
-
-id-pe-authorityInfoAccess OBJECT IDENTIFIER ::= { id-pe 1 }
-
-AuthorityInfoAccessSyntax ::=
- SEQUENCE SIZE (1..MAX) OF AccessDescription
-
-AccessDescription ::= SEQUENCE {
- accessMethod OBJECT IDENTIFIER,
- accessLocation GeneralName }
-
--- subject info access
-
-id-pe-subjectInfoAccess OBJECT IDENTIFIER ::= { id-pe 11 }
-
-SubjectInfoAccessSyntax ::=
- SEQUENCE SIZE (1..MAX) OF AccessDescription
-
--- CRL number extension OID and syntax
-
-id-ce-cRLNumber OBJECT IDENTIFIER ::= { id-ce 20 }
-
-CRLNumber ::= INTEGER (0..MAX)
-
--- issuing distribution point extension OID and syntax
-
-id-ce-issuingDistributionPoint OBJECT IDENTIFIER ::= { id-ce 28 }
-
-IssuingDistributionPoint ::= SEQUENCE {
- distributionPoint [0] DistributionPointName OPTIONAL,
- onlyContainsUserCerts [1] BOOLEAN DEFAULT FALSE,
- onlyContainsCACerts [2] BOOLEAN DEFAULT FALSE,
- onlySomeReasons [3] ReasonFlags OPTIONAL,
- indirectCRL [4] BOOLEAN DEFAULT FALSE,
- onlyContainsAttributeCerts [5] BOOLEAN DEFAULT FALSE }
-
-id-ce-deltaCRLIndicator OBJECT IDENTIFIER ::= { id-ce 27 }
-
-BaseCRLNumber ::= CRLNumber
-
--- CRL reasons extension OID and syntax
-
-id-ce-cRLReasons OBJECT IDENTIFIER ::= { id-ce 21 }
-
-CRLReason ::= ENUMERATED {
- unspecified (0),
- keyCompromise (1),
- cACompromise (2),
- affiliationChanged (3),
- superseded (4),
- cessationOfOperation (5),
- certificateHold (6),
- removeFromCRL (8),
- privilegeWithdrawn (9),
- aACompromise (10) }
-
--- certificate issuer CRL entry extension OID and syntax
-
-id-ce-certificateIssuer OBJECT IDENTIFIER ::= { id-ce 29 }
-
-CertificateIssuer ::= GeneralNames
-
--- hold instruction extension OID and syntax
-
-id-ce-holdInstructionCode OBJECT IDENTIFIER ::= { id-ce 23 }
-
-HoldInstructionCode ::= OBJECT IDENTIFIER
-
--- ANSI x9 holdinstructions
-
--- ANSI x9 arc holdinstruction arc
-
-holdInstruction OBJECT IDENTIFIER ::=
- {joint-iso-itu-t(2) member-body(2) us(840) x9cm(10040) 2}
-
--- ANSI X9 holdinstructions referenced by this standard
-
-id-holdinstruction-none OBJECT IDENTIFIER ::=
- {holdInstruction 1} -- deprecated
-
-id-holdinstruction-callissuer OBJECT IDENTIFIER ::=
- {holdInstruction 2}
-
-id-holdinstruction-reject OBJECT IDENTIFIER ::=
- {holdInstruction 3}
-
--- invalidity date CRL entry extension OID and syntax
-
-id-ce-invalidityDate OBJECT IDENTIFIER ::= { id-ce 24 }
-
-InvalidityDate ::= GeneralizedTime
-
-END
diff --git a/lib/ssl/pkix/PKIX1Implicit88.hrl b/lib/ssl/pkix/PKIX1Implicit88.hrl
deleted file mode 100644
index 8fa1836284..0000000000
--- a/lib/ssl/pkix/PKIX1Implicit88.hrl
+++ /dev/null
@@ -1,93 +0,0 @@
-%% Generated by the Erlang ASN.1 compiler version:1.4.4.8
-%% Purpose: Erlang record definitions for each named and unnamed
-%% SEQUENCE and SET, and macro definitions for each value
-%% definition,in module PKIX1Implicit88
-
-
-
--record('AuthorityKeyIdentifier',{
-keyIdentifier = asn1_NOVALUE, authorityCertIssuer = asn1_NOVALUE, authorityCertSerialNumber = asn1_NOVALUE}).
-
--record('PrivateKeyUsagePeriod',{
-notBefore = asn1_NOVALUE, notAfter = asn1_NOVALUE}).
-
--record('PolicyInformation',{
-policyIdentifier, policyQualifiers = asn1_NOVALUE}).
-
--record('PolicyQualifierInfo',{
-policyQualifierId, qualifier}).
-
--record('UserNotice',{
-noticeRef = asn1_NOVALUE, explicitText = asn1_NOVALUE}).
-
--record('NoticeReference',{
-organization, noticeNumbers}).
-
--record('PolicyMappings_SEQOF',{
-issuerDomainPolicy, subjectDomainPolicy}).
-
--record('AnotherName',{
-'type-id', value}).
-
--record('EDIPartyName',{
-nameAssigner = asn1_NOVALUE, partyName}).
-
--record('BasicConstraints',{
-cA = asn1_DEFAULT, pathLenConstraint = asn1_NOVALUE}).
-
--record('NameConstraints',{
-permittedSubtrees = asn1_NOVALUE, excludedSubtrees = asn1_NOVALUE}).
-
--record('GeneralSubtree',{
-base, minimum = asn1_DEFAULT, maximum = asn1_NOVALUE}).
-
--record('PolicyConstraints',{
-requireExplicitPolicy = asn1_NOVALUE, inhibitPolicyMapping = asn1_NOVALUE}).
-
--record('DistributionPoint',{
-distributionPoint = asn1_NOVALUE, reasons = asn1_NOVALUE, cRLIssuer = asn1_NOVALUE}).
-
--record('AccessDescription',{
-accessMethod, accessLocation}).
-
--record('IssuingDistributionPoint',{
-distributionPoint = asn1_NOVALUE, onlyContainsUserCerts = asn1_DEFAULT, onlyContainsCACerts = asn1_DEFAULT, onlySomeReasons = asn1_NOVALUE, indirectCRL = asn1_DEFAULT, onlyContainsAttributeCerts = asn1_DEFAULT}).
-
--define('id-ce', {2,5,29}).
--define('id-ce-authorityKeyIdentifier', {2,5,29,35}).
--define('id-ce-subjectKeyIdentifier', {2,5,29,14}).
--define('id-ce-keyUsage', {2,5,29,15}).
--define('id-ce-privateKeyUsagePeriod', {2,5,29,16}).
--define('id-ce-certificatePolicies', {2,5,29,32}).
--define('anyPolicy', {2,5,29,32,0}).
--define('id-ce-policyMappings', {2,5,29,33}).
--define('id-ce-subjectAltName', {2,5,29,17}).
--define('id-ce-issuerAltName', {2,5,29,18}).
--define('id-ce-subjectDirectoryAttributes', {2,5,29,9}).
--define('id-ce-basicConstraints', {2,5,29,19}).
--define('id-ce-nameConstraints', {2,5,29,30}).
--define('id-ce-policyConstraints', {2,5,29,36}).
--define('id-ce-cRLDistributionPoints', {2,5,29,31}).
--define('id-ce-extKeyUsage', {2,5,29,37}).
--define('anyExtendedKeyUsage', {2,5,29,37,0}).
--define('id-kp-serverAuth', {1,3,6,1,5,5,7,3,1}).
--define('id-kp-clientAuth', {1,3,6,1,5,5,7,3,2}).
--define('id-kp-codeSigning', {1,3,6,1,5,5,7,3,3}).
--define('id-kp-emailProtection', {1,3,6,1,5,5,7,3,4}).
--define('id-kp-timeStamping', {1,3,6,1,5,5,7,3,8}).
--define('id-kp-OCSPSigning', {1,3,6,1,5,5,7,3,9}).
--define('id-ce-inhibitAnyPolicy', {2,5,29,54}).
--define('id-ce-freshestCRL', {2,5,29,46}).
--define('id-pe-authorityInfoAccess', {1,3,6,1,5,5,7,1,1}).
--define('id-pe-subjectInfoAccess', {1,3,6,1,5,5,7,1,11}).
--define('id-ce-cRLNumber', {2,5,29,20}).
--define('id-ce-issuingDistributionPoint', {2,5,29,28}).
--define('id-ce-deltaCRLIndicator', {2,5,29,27}).
--define('id-ce-cRLReasons', {2,5,29,21}).
--define('id-ce-certificateIssuer', {2,5,29,29}).
--define('id-ce-holdInstructionCode', {2,5,29,23}).
--define('holdInstruction', {2,2,840,10040,2}).
--define('id-holdinstruction-none', {2,2,840,10040,2,1}).
--define('id-holdinstruction-callissuer', {2,2,840,10040,2,2}).
--define('id-holdinstruction-reject', {2,2,840,10040,2,3}).
--define('id-ce-invalidityDate', {2,5,29,24}).
diff --git a/lib/ssl/pkix/PKIXAttributeCertificate.asn1 b/lib/ssl/pkix/PKIXAttributeCertificate.asn1
deleted file mode 100644
index 7d93e6b37e..0000000000
--- a/lib/ssl/pkix/PKIXAttributeCertificate.asn1
+++ /dev/null
@@ -1,189 +0,0 @@
- PKIXAttributeCertificate {iso(1) identified-organization(3) dod(6)
- internet(1) security(5) mechanisms(5) pkix(7) id-mod(0)
- id-mod-attribute-cert(12)}
-
- DEFINITIONS IMPLICIT TAGS ::=
-
- BEGIN
-
- -- EXPORTS ALL --
-
- IMPORTS
-
- -- IMPORTed module OIDs MAY change if [PKIXPROF] changes
- -- PKIX Certificate Extensions
- Attribute, AlgorithmIdentifier, CertificateSerialNumber,
- Extensions, UniqueIdentifier,
- id-pkix, id-pe, id-kp, id-ad, id-at
- FROM PKIX1Explicit88 {iso(1) identified-organization(3)
- dod(6) internet(1) security(5) mechanisms(5)
- pkix(7) id-mod(0) id-pkix1-explicit-88(1)}
-
- GeneralName, GeneralNames, id-ce
- FROM PKIX1Implicit88 {iso(1) identified-organization(3)
- dod(6) internet(1) security(5) mechanisms(5)
- pkix(7) id-mod(0) id-pkix1-implicit-88(2)} ;
-
- id-pe-ac-auditIdentity OBJECT IDENTIFIER ::= { id-pe 4 }
- id-pe-aaControls OBJECT IDENTIFIER ::= { id-pe 6 }
- id-pe-ac-proxying OBJECT IDENTIFIER ::= { id-pe 10 }
- id-ce-targetInformation OBJECT IDENTIFIER ::= { id-ce 55 }
-
- id-aca OBJECT IDENTIFIER ::= { id-pkix 10 }
- id-aca-authenticationInfo OBJECT IDENTIFIER ::= { id-aca 1 }
- id-aca-accessIdentity OBJECT IDENTIFIER ::= { id-aca 2 }
- id-aca-chargingIdentity OBJECT IDENTIFIER ::= { id-aca 3 }
- id-aca-group OBJECT IDENTIFIER ::= { id-aca 4 }
- -- { id-aca 5 } is reserved
- id-aca-encAttrs OBJECT IDENTIFIER ::= { id-aca 6 }
-
- id-at-role OBJECT IDENTIFIER ::= { id-at 72}
- id-at-clearance OBJECT IDENTIFIER ::=
- { joint-iso-ccitt(2) ds(5) module(1)
- selected-attribute-types(5) clearance (55) }
-
- -- Uncomment this if using a 1988 level ASN.1 compiler
- -- UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING
-
- AttributeCertificate ::= SEQUENCE {
- acinfo AttributeCertificateInfo,
- signatureAlgorithm AlgorithmIdentifier,
- signatureValue BIT STRING
- }
-
- AttributeCertificateInfo ::= SEQUENCE {
- version AttCertVersion, -- version is v2
- holder Holder,
- issuer AttCertIssuer,
- signature AlgorithmIdentifier,
- serialNumber CertificateSerialNumber,
- attrCertValidityPeriod AttCertValidityPeriod,
- attributes SEQUENCE OF Attribute,
- issuerUniqueID UniqueIdentifier OPTIONAL,
- extensions Extensions OPTIONAL
- }
-
- AttCertVersion ::= INTEGER { v2(1) }
-
- Holder ::= SEQUENCE {
- baseCertificateID [0] IssuerSerial OPTIONAL,
- -- the issuer and serial number of
- -- the holder's Public Key Certificate
- entityName [1] GeneralNames OPTIONAL,
- -- the name of the claimant or role
- objectDigestInfo [2] ObjectDigestInfo OPTIONAL
- -- used to directly authenticate the
- -- holder, for example, an executable
- }
-
- ObjectDigestInfo ::= SEQUENCE {
- digestedObjectType ENUMERATED {
- publicKey (0),
- publicKeyCert (1),
- otherObjectTypes (2) },
- -- otherObjectTypes MUST NOT
- -- MUST NOT be used in this profile
- otherObjectTypeID OBJECT IDENTIFIER OPTIONAL,
- digestAlgorithm AlgorithmIdentifier,
- objectDigest BIT STRING
- }
-
- AttCertIssuer ::= CHOICE {
- v1Form GeneralNames, -- MUST NOT be used in this
- -- profile
- v2Form [0] V2Form -- v2 only
- }
-
- V2Form ::= SEQUENCE {
- issuerName GeneralNames OPTIONAL,
- baseCertificateID [0] IssuerSerial OPTIONAL,
- objectDigestInfo [1] ObjectDigestInfo OPTIONAL
- -- issuerName MUST be present in this profile
- -- baseCertificateID and objectDigestInfo MUST
- -- NOT be present in this profile
- }
-
- IssuerSerial ::= SEQUENCE {
- issuer GeneralNames,
- serial CertificateSerialNumber,
- issuerUID UniqueIdentifier OPTIONAL
- }
-
- AttCertValidityPeriod ::= SEQUENCE {
- notBeforeTime GeneralizedTime,
- notAfterTime GeneralizedTime
- }
-
- Targets ::= SEQUENCE OF Target
-
- Target ::= CHOICE {
- targetName [0] GeneralName,
- targetGroup [1] GeneralName,
- targetCert [2] TargetCert
- }
-
- TargetCert ::= SEQUENCE {
- targetCertificate IssuerSerial,
- targetName GeneralName OPTIONAL,
- certDigestInfo ObjectDigestInfo OPTIONAL
- }
-
- IetfAttrSyntax ::= SEQUENCE {
- policyAuthority[0] GeneralNames OPTIONAL,
- values SEQUENCE OF CHOICE {
- octets OCTET STRING,
- oid OBJECT IDENTIFIER,
- string UTF8String
- }
- }
-
- SvceAuthInfo ::= SEQUENCE {
- service GeneralName,
- ident GeneralName,
- authInfo OCTET STRING OPTIONAL
- }
-
- RoleSyntax ::= SEQUENCE {
- roleAuthority [0] GeneralNames OPTIONAL,
- roleName [1] GeneralName
- }
-
- Clearance ::= SEQUENCE {
- policyId [0] OBJECT IDENTIFIER,
- classList [1] ClassList DEFAULT {unclassified},
- securityCategories
- [2] SET OF SecurityCategory OPTIONAL
- }
-
- ClassList ::= BIT STRING {
- unmarked (0),
- unclassified (1),
- restricted (2),
- confidential (3),
- secret (4),
- topSecret (5)
- }
-
- SecurityCategory ::= SEQUENCE {
- type [0] IMPLICIT OBJECT IDENTIFIER,
- value [1] ANY DEFINED BY type
- }
-
- AAControls ::= SEQUENCE {
- pathLenConstraint INTEGER (0..MAX) OPTIONAL,
- permittedAttrs [0] AttrSpec OPTIONAL,
- excludedAttrs [1] AttrSpec OPTIONAL,
- permitUnSpecified BOOLEAN DEFAULT TRUE
- }
-
- AttrSpec::= SEQUENCE OF OBJECT IDENTIFIER
-
- ACClearAttrs ::= SEQUENCE {
- acIssuer GeneralName,
- acSerial INTEGER,
- attrs SEQUENCE OF Attribute
- }
-
- ProxyInfo ::= SEQUENCE OF Targets
-
- END
diff --git a/lib/ssl/pkix/PKIXAttributeCertificate.hrl b/lib/ssl/pkix/PKIXAttributeCertificate.hrl
deleted file mode 100644
index 99389c4852..0000000000
--- a/lib/ssl/pkix/PKIXAttributeCertificate.hrl
+++ /dev/null
@@ -1,64 +0,0 @@
-%% Generated by the Erlang ASN.1 compiler version:1.4.4.8
-%% Purpose: Erlang record definitions for each named and unnamed
-%% SEQUENCE and SET, and macro definitions for each value
-%% definition,in module PKIXAttributeCertificate
-
-
-
--record('AttributeCertificate',{
-acinfo, signatureAlgorithm, signatureValue}).
-
--record('AttributeCertificateInfo',{
-version, holder, issuer, signature, serialNumber, attrCertValidityPeriod, attributes, issuerUniqueID = asn1_NOVALUE, extensions = asn1_NOVALUE}).
-
--record('Holder',{
-baseCertificateID = asn1_NOVALUE, entityName = asn1_NOVALUE, objectDigestInfo = asn1_NOVALUE}).
-
--record('ObjectDigestInfo',{
-digestedObjectType, otherObjectTypeID = asn1_NOVALUE, digestAlgorithm, objectDigest}).
-
--record('V2Form',{
-issuerName = asn1_NOVALUE, baseCertificateID = asn1_NOVALUE, objectDigestInfo = asn1_NOVALUE}).
-
--record('IssuerSerial',{
-issuer, serial, issuerUID = asn1_NOVALUE}).
-
--record('AttCertValidityPeriod',{
-notBeforeTime, notAfterTime}).
-
--record('TargetCert',{
-targetCertificate, targetName = asn1_NOVALUE, certDigestInfo = asn1_NOVALUE}).
-
--record('IetfAttrSyntax',{
-policyAuthority = asn1_NOVALUE, values}).
-
--record('SvceAuthInfo',{
-service, ident, authInfo = asn1_NOVALUE}).
-
--record('RoleSyntax',{
-roleAuthority = asn1_NOVALUE, roleName}).
-
--record('Clearance',{
-policyId, classList = asn1_DEFAULT, securityCategories = asn1_NOVALUE}).
-
--record('SecurityCategory',{
-type, value}).
-
--record('AAControls',{
-pathLenConstraint = asn1_NOVALUE, permittedAttrs = asn1_NOVALUE, excludedAttrs = asn1_NOVALUE, permitUnSpecified = asn1_DEFAULT}).
-
--record('ACClearAttrs',{
-acIssuer, acSerial, attrs}).
-
--define('id-pe-ac-auditIdentity', {1,3,6,1,5,5,7,1,4}).
--define('id-pe-aaControls', {1,3,6,1,5,5,7,1,6}).
--define('id-pe-ac-proxying', {1,3,6,1,5,5,7,1,10}).
--define('id-ce-targetInformation', {2,5,29,55}).
--define('id-aca', {1,3,6,1,5,5,7,10}).
--define('id-aca-authenticationInfo', {1,3,6,1,5,5,7,10,1}).
--define('id-aca-accessIdentity', {1,3,6,1,5,5,7,10,2}).
--define('id-aca-chargingIdentity', {1,3,6,1,5,5,7,10,3}).
--define('id-aca-group', {1,3,6,1,5,5,7,10,4}).
--define('id-aca-encAttrs', {1,3,6,1,5,5,7,10,6}).
--define('id-at-role', {2,5,4,72}).
--define('id-at-clearance', {2,5,1,5,55}).
diff --git a/lib/ssl/pkix/README b/lib/ssl/pkix/README
deleted file mode 100644
index 8be2c15de5..0000000000
--- a/lib/ssl/pkix/README
+++ /dev/null
@@ -1,49 +0,0 @@
-The files
-
- PKIX1Algorithms88.asn1
- PKIX1Explicit88.asn1
- PKIX1Implicit88.asn1
- PKIXAttributeCertificate.asn1
-
-are from RFCs 3279, 3280 and 3281.
-
-We have edited PKIX1Explicit88.asn1, PKIX1Implicit88.asn1, and
-PKIXAttributeCertificate.asn1 as follows:
-
-
-1. Removal of definition of UniversalString and BMPString:
-
-diff -r1.1 PKIX1Explicit88.asn1
-15c15
-< UniversalString ::= [UNIVERSAL 28] IMPLICIT OCTET STRING
----
-> -- UniversalString ::= [UNIVERSAL 28] IMPLICIT OCTET STRING
-18c18
-< BMPString ::= [UNIVERSAL 30] IMPLICIT OCTET STRING
----
-> -- BMPString ::= [UNIVERSAL 30] IMPLICIT OCTET STRING
-
-
-2. Removal of definition of BMPString:
-
-diff -r1.1 PKIX1Implicit88.asn1
-13c13,14
-< BMPString, UTF8String, -- end "new" types --
----
-> -- BMPString,
-> UTF8String, -- end "new" types --
-
-
-3. Addition of definition of UTF8String, and correction of a typo.
-
-diff -r1.1 PKIXAttributeCertificate.asn1
-46c46
-< -- UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING
----
-> UTF8String ::= [UNIVERSAL 12] IMPLICIT OCTET STRING
-55c55
-< version AttCertVersion -- version is v2,
----
-> version AttCertVersion, -- version is v2
-
-PKIX1Algorithms88.asn1 is unchanged.
diff --git a/lib/ssl/pkix/SSL-PKIX.asn1 b/lib/ssl/pkix/SSL-PKIX.asn1
deleted file mode 100644
index ea6333f953..0000000000
--- a/lib/ssl/pkix/SSL-PKIX.asn1
+++ /dev/null
@@ -1,704 +0,0 @@
-SSL-PKIX {iso(1) identified-organization(3) dod(6) internet(1)
- private(4) enterprices(1) ericsson(193) otp(19) ssl(10)
- pkix1(1)}
-
-DEFINITIONS EXPLICIT TAGS ::=
-
-BEGIN
-
--- EXPORTS ALL
-
-IMPORTS
- -- Certificate (parts of)
- Version,
- CertificateSerialNumber,
- --AlgorithmIdentifier,
- Validity,
- UniqueIdentifier,
-
- -- AttribyteTypeAndValue
- Name,
- AttributeType,
- id-at-name,
- id-at-surname,
- id-at-givenName,
- id-at-initials,
- id-at-generationQualifier, X520name,
- id-at-commonName, X520CommonName,
- id-at-localityName, X520LocalityName,
- id-at-stateOrProvinceName, X520StateOrProvinceName,
- id-at-organizationName, X520OrganizationName,
- id-at-organizationalUnitName, X520OrganizationalUnitName,
- id-at-title, X520Title,
- id-at-dnQualifier, X520dnQualifier,
- id-at-countryName, X520countryName,
- id-at-serialNumber, X520SerialNumber,
- id-at-pseudonym, X520Pseudonym,
- id-domainComponent, DomainComponent,
- id-emailAddress, EmailAddress,
-
- -- Extension Attributes
- common-name, CommonName,
- teletex-common-name, TeletexCommonName,
- teletex-personal-name, TeletexPersonalName,
- pds-name, PDSName,
- physical-delivery-country-name, PhysicalDeliveryCountryName,
- postal-code, PostalCode,
- physical-delivery-office-name, PhysicalDeliveryOfficeName,
- physical-delivery-office-number, PhysicalDeliveryOfficeNumber,
- extension-OR-address-components, ExtensionORAddressComponents,
- physical-delivery-personal-name, PhysicalDeliveryPersonalName,
- physical-delivery-organization-name, PhysicalDeliveryOrganizationName,
- extension-physical-delivery-address-components,
- ExtensionPhysicalDeliveryAddressComponents,
- unformatted-postal-address, UnformattedPostalAddress,
- street-address, StreetAddress,
- post-office-box-address, PostOfficeBoxAddress,
- poste-restante-address, PosteRestanteAddress,
- unique-postal-name, UniquePostalName,
- local-postal-attributes, LocalPostalAttributes,
- extended-network-address, ExtendedNetworkAddress,
- terminal-type, TerminalType,
- teletex-domain-defined-attributes, TeletexDomainDefinedAttributes
-
- FROM PKIX1Explicit88 { iso(1) identified-organization(3) dod(6)
- internet(1) security(5) mechanisms(5) pkix(7) id-mod(0)
- id-pkix1-explicit(18) }
-
- -- Extensions
- id-ce-authorityKeyIdentifier, AuthorityKeyIdentifier,
- id-ce-subjectKeyIdentifier, SubjectKeyIdentifier,
- id-ce-keyUsage, KeyUsage,
- id-ce-privateKeyUsagePeriod, PrivateKeyUsagePeriod,
- id-ce-certificatePolicies, CertificatePolicies,
- id-ce-policyMappings, PolicyMappings,
- id-ce-subjectAltName, SubjectAltName,
- id-ce-issuerAltName, IssuerAltName,
- id-ce-subjectDirectoryAttributes, SubjectDirectoryAttributes,
- id-ce-basicConstraints, BasicConstraints,
- id-ce-nameConstraints, NameConstraints,
- id-ce-policyConstraints, PolicyConstraints,
- id-ce-cRLDistributionPoints, CRLDistributionPoints,
- id-ce-extKeyUsage, ExtKeyUsageSyntax,
- id-ce-inhibitAnyPolicy, InhibitAnyPolicy,
- id-ce-freshestCRL, FreshestCRL,
- id-pe-authorityInfoAccess, AuthorityInfoAccessSyntax,
- id-pe-subjectInfoAccess, SubjectInfoAccessSyntax,
- id-ce-cRLNumber, CRLNumber,
- id-ce-issuingDistributionPoint, IssuingDistributionPoint,
- id-ce-deltaCRLIndicator, BaseCRLNumber,
- id-ce-cRLReasons, CRLReason,
- id-ce-certificateIssuer, CertificateIssuer,
- id-ce-holdInstructionCode, HoldInstructionCode,
- id-ce-invalidityDate, InvalidityDate
-
- FROM PKIX1Implicit88 { iso(1) identified-organization(3) dod(6)
- internet(1) security(5) mechanisms(5) pkix(7) id-mod(0)
- id-pkix1-implicit(19) }
-
- --Keys and Signatures
- id-dsa, Dss-Parms, DSAPublicKey,
- id-dsa-with-sha1,
- md2WithRSAEncryption,
- md5WithRSAEncryption,
- sha1WithRSAEncryption,
- rsaEncryption, RSAPublicKey,
- dhpublicnumber, DomainParameters, DHPublicKey,
- id-keyExchangeAlgorithm, KEA-Parms-Id, --KEA-PublicKey,
- ecdsa-with-SHA1,
- prime-field, Prime-p,
- characteristic-two-field, --Characteristic-two,
- gnBasis,
- tpBasis, Trinomial,
- ppBasis, Pentanomial,
- id-ecPublicKey, EcpkParameters, ECPoint
- FROM PKIX1Algorithms88 { iso(1) identified-organization(3) dod(6)
- internet(1) security(5) mechanisms(5) pkix(7) id-mod(0)
- id-mod-pkix1-algorithms(17) };
-
---
--- Certificate
---
-
-SSLCertificate ::= SEQUENCE {
- tbsCertificate TBSCertificate,
- signatureAlgorithm SignatureAlgorithm,
- signature BIT STRING }
-
-SSLTBSCertificate ::= SEQUENCE {
- version [0] Version DEFAULT v1,
- serialNumber CertificateSerialNumber,
- signature SignatureAlgorithm,
- issuer Name,
- validity Validity,
- subject Name,
- subjectPublicKeyInfo SubjectPublicKeyInfo,
- issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
- -- If present, version MUST be v2 or v3
- subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
- -- If present, version MUST be v2 or v3
- extensions [3] Extensions OPTIONAL
- -- If present, version MUST be v3 -- }
-
-
--- Attribute type and values
---
-
-ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= CLASS {
- &id AttributeType UNIQUE,
- &Type }
- WITH SYNTAX {
- ID &id
- TYPE &Type }
-
-SSLAttributeTypeAndValue ::= SEQUENCE {
- type ATTRIBUTE-TYPE-AND-VALUE-CLASS.&id
- ({SupportedAttributeTypeAndValues}),
- value ATTRIBUTE-TYPE-AND-VALUE-CLASS.&Type
- ({SupportedAttributeTypeAndValues}{@type}) }
-
-SupportedAttributeTypeAndValues ATTRIBUTE-TYPE-AND-VALUE-CLASS ::=
- { name | surname | givenName | initials | generationQualifier |
- commonName | localityName | stateOrProvinceName | organizationName |
- organizationalUnitName | title | dnQualifier | countryName |
- serialNumber | pseudonym | domainComponent | emailAddress }
-
-name ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-name
- TYPE X520name }
-
-surname ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-surname
- TYPE X520name }
-
-givenName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-givenName
- TYPE X520name }
-
-initials ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-initials
- TYPE X520name }
-
-generationQualifier ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-generationQualifier
- TYPE X520name }
-
-commonName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-commonName
- TYPE X520CommonName }
-
-localityName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-localityName
- TYPE X520LocalityName }
-
-stateOrProvinceName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-stateOrProvinceName
- TYPE X520StateOrProvinceName }
-
-organizationName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-organizationName
- TYPE X520OrganizationName }
-
-organizationalUnitName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-organizationalUnitName
- TYPE X520OrganizationalUnitName }
-
-title ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-title
- TYPE X520Title }
-
-dnQualifier ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-dnQualifier
- TYPE X520dnQualifier }
-
-countryName ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-countryName
- TYPE X520countryName }
-
-serialNumber ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-serialNumber
- TYPE X520SerialNumber }
-
-pseudonym ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-at-pseudonym
- TYPE X520Pseudonym }
-
-domainComponent ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-domainComponent
- TYPE DomainComponent }
-
-emailAddress ATTRIBUTE-TYPE-AND-VALUE-CLASS ::= {
- ID id-emailAddress
- TYPE EmailAddress }
-
---
--- Signature and Public Key Algorithms
---
-
-SSLSubjectPublicKeyInfo ::= SEQUENCE {
- algorithm SEQUENCE {
- algo PUBLIC-KEY-ALGORITHM-CLASS.&id
- ({SupportedPublicKeyAlgorithms}),
- parameters PUBLIC-KEY-ALGORITHM-CLASS.&Type
- ({SupportedPublicKeyAlgorithms}{@.algo})
- OPTIONAL
- },
- subjectPublicKey PUBLIC-KEY-ALGORITHM-CLASS.&PublicKeyType
- ({SupportedPublicKeyAlgorithms}{@algorithm.algo}) }
-
--- The following is needed for conversion of SubjectPublicKeyInfo.
-
-SSLSubjectPublicKeyInfo-Any ::= SEQUENCE {
- algorithm PublicKeyAlgorithm,
- subjectPublicKey ANY }
-
-
-SIGNATURE-ALGORITHM-CLASS ::= CLASS {
- &id OBJECT IDENTIFIER UNIQUE,
- &Type OPTIONAL }
- WITH SYNTAX {
- ID &id
- [TYPE &Type] }
-
-PUBLIC-KEY-ALGORITHM-CLASS ::= CLASS {
- &id OBJECT IDENTIFIER UNIQUE,
- &Type OPTIONAL,
- &PublicKeyType OPTIONAL }
- WITH SYNTAX {
- ID &id
- [TYPE &Type]
- [PUBLIC-KEY-TYPE &PublicKeyType] }
-
-SignatureAlgorithm ::= SEQUENCE {
- algorithm SIGNATURE-ALGORITHM-CLASS.&id
- ({SupportedSignatureAlgorithms}),
- parameters SIGNATURE-ALGORITHM-CLASS.&Type
- ({SupportedSignatureAlgorithms}{@algorithm})
- OPTIONAL }
-
-SignatureAlgorithm-Any ::= SEQUENCE {
- algorithm OBJECT IDENTIFIER,
- parameters ANY OPTIONAL }
-
-PublicKeyAlgorithm ::= SEQUENCE {
- algorithm PUBLIC-KEY-ALGORITHM-CLASS.&id
- ({SupportedPublicKeyAlgorithms}),
- parameters PUBLIC-KEY-ALGORITHM-CLASS.&Type
- ({SupportedPublicKeyAlgorithms}{@algorithm})
- OPTIONAL }
-
-SupportedSignatureAlgorithms SIGNATURE-ALGORITHM-CLASS ::= {
- dsa-with-sha1 | md2-with-rsa-encryption |
- md5-with-rsa-encryption | sha1-with-rsa-encryption |
- ecdsa-with-sha1 }
-
-SupportedPublicKeyAlgorithms PUBLIC-KEY-ALGORITHM-CLASS ::= {
- dsa | rsa-encryption | dh | kea | ec-public-key }
-
- -- DSA Keys and Signatures
-
- -- SubjectPublicKeyInfo:
-
- dsa PUBLIC-KEY-ALGORITHM-CLASS ::= {
- ID id-dsa
- TYPE Dss-Parms -- XXX Must be OPTIONAL
- PUBLIC-KEY-TYPE DSAPublicKey }
-
- -- Certificate.signatureAlgorithm
-
- dsa-with-sha1 SIGNATURE-ALGORITHM-CLASS ::= {
- ID id-dsa-with-sha1
- TYPE NULL } -- XXX Must be empty and not NULL
-
- --
- -- RSA Keys and Signatures
- --
-
- -- Certificate.signatureAlgorithm
-
- md2-with-rsa-encryption SIGNATURE-ALGORITHM-CLASS ::= {
- ID md2WithRSAEncryption
- TYPE NULL }
-
- md5-with-rsa-encryption SIGNATURE-ALGORITHM-CLASS ::= {
- ID md5WithRSAEncryption
- TYPE NULL }
-
- sha1-with-rsa-encryption SIGNATURE-ALGORITHM-CLASS ::= {
- ID sha1WithRSAEncryption
- TYPE NULL }
-
- -- Certificate.signature
- -- See PKCS #1 (RFC 2313). XXX
-
- -- SubjectPublicKeyInfo:
-
- rsa-encryption PUBLIC-KEY-ALGORITHM-CLASS ::= {
- ID rsaEncryption
- TYPE NULL
- PUBLIC-KEY-TYPE RSAPublicKey }
-
- --
- -- Diffie-Hellman Keys
- --
-
- -- SubjectPublicKeyInfo:
-
- dh PUBLIC-KEY-ALGORITHM-CLASS ::= {
- ID dhpublicnumber
- TYPE DomainParameters
- PUBLIC-KEY-TYPE DHPublicKey }
-
- -- There are no Diffie-Hellman signature algorithms
-
- --
- -- KEA Keys
- --
-
- -- SubjectPublicKeyInfo:
-
- KEA-PublicKey ::= INTEGER
-
- kea PUBLIC-KEY-ALGORITHM-CLASS ::= {
- ID id-keyExchangeAlgorithm
- TYPE KEA-Parms-Id
- PUBLIC-KEY-TYPE KEA-PublicKey }
-
- -- There are no KEA signature algorithms
-
- --
- -- Elliptic Curve Keys, Signatures, and Curves
- --
-
- -- Certificate.signatureAlgorithm
-
- ecdsa-with-sha1 SIGNATURE-ALGORITHM-CLASS ::= {
- ID ecdsa-with-SHA1
- TYPE NULL } -- XXX Must be empty and not NULL
-
- FIELD-ID-CLASS ::= CLASS {
- &id OBJECT IDENTIFIER UNIQUE,
- &Type }
- WITH SYNTAX {
- ID &id
- TYPE &Type }
-
- SSLFieldID ::= SEQUENCE { -- Finite field
- fieldType FIELD-ID-CLASS.&id({SupportedFieldIds}),
- parameters FIELD-ID-CLASS.&Type({SupportedFieldIds}{@fieldType}) }
-
- SupportedFieldIds FIELD-ID-CLASS ::= {
- field-prime-field | field-characteristic-two }
-
- field-prime-field FIELD-ID-CLASS ::= {
- ID prime-field
- TYPE Prime-p }
-
- CHARACTERISTIC-TWO-CLASS ::= CLASS {
- &id OBJECT IDENTIFIER UNIQUE,
- &Type }
- WITH SYNTAX {
- ID &id
- TYPE &Type }
-
- SSLCharacteristic-two ::= SEQUENCE { -- Finite field
- m INTEGER, -- Field size 2^m
- basis CHARACTERISTIC-TWO-CLASS.&id({SupportedCharacteristicTwos}),
- parameters CHARACTERISTIC-TWO-CLASS.&Type
- ({SupportedCharacteristicTwos}{@basis}) }
-
- SupportedCharacteristicTwos CHARACTERISTIC-TWO-CLASS ::= {
- gn-basis | tp-basis | pp-basis }
-
- field-characteristic-two FIELD-ID-CLASS ::= {
- ID characteristic-two-field
- TYPE Characteristic-two }
-
- gn-basis CHARACTERISTIC-TWO-CLASS ::= {
- ID gnBasis
- TYPE NULL }
-
- tp-basis CHARACTERISTIC-TWO-CLASS ::= {
- ID tpBasis
- TYPE Trinomial }
-
- pp-basis CHARACTERISTIC-TWO-CLASS ::= {
- ID ppBasis
- TYPE Pentanomial }
-
- -- SubjectPublicKeyInfo.algorithm
-
- ec-public-key PUBLIC-KEY-ALGORITHM-CLASS ::= {
- ID id-ecPublicKey
- TYPE EcpkParameters
- PUBLIC-KEY-TYPE ECPoint }
-
---
--- Extension Attributes
---
-
-EXTENSION-ATTRIBUTE-CLASS ::= CLASS {
- &id INTEGER UNIQUE,
- &Type }
- WITH SYNTAX {
- ID &id
- TYPE &Type }
-
-SSLExtensionAttributes ::= SET SIZE (1..MAX) OF ExtensionAttribute
-
--- XXX Below we should have extension-attribute-type and extension-
--- attribute-value but Erlang ASN1 does not like it.
-SSLExtensionAttribute ::= SEQUENCE {
- extensionAttributeType [0] IMPLICIT EXTENSION-ATTRIBUTE-CLASS.&id
- ({SupportedExtensionAttributes}),
- extensionAttributeValue [1] EXTENSION-ATTRIBUTE-CLASS.&Type
- ({SupportedExtensionAttributes}{@extensionAttributeType}) }
-
-SupportedExtensionAttributes EXTENSION-ATTRIBUTE-CLASS ::= {
- x400-common-name |
- x400-teletex-common-name |
- x400-teletex-personal-name |
- x400-pds-name |
- x400-physical-delivery-country-name |
- x400-postal-code |
- x400-physical-delivery-office-name |
- x400-physical-delivery-office-number |
- x400-extension-OR-address-components |
- x400-physical-delivery-personal-name |
- x400-physical-delivery-organization-name |
- x400-extension-physical-delivery-address-components |
- x400-unformatted-postal-address |
- x400-street-address |
- x400-post-office-box-address |
- x400-poste-restante-address |
- x400-unique-postal-name |
- x400-local-postal-attributes |
- x400-extended-network-address |
- x400-terminal-type |
- x400-teletex-domain-defined-attributes }
-
--- Extension types and attribute values
-
-x400-common-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID common-name
- TYPE CommonName }
-
-x400-teletex-common-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID teletex-common-name
- TYPE TeletexCommonName }
-
-x400-teletex-personal-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID teletex-personal-name
- TYPE TeletexPersonalName }
-
-x400-pds-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID pds-name
- TYPE PDSName }
-
-x400-physical-delivery-country-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID physical-delivery-country-name
- TYPE PhysicalDeliveryCountryName }
-
-x400-postal-code EXTENSION-ATTRIBUTE-CLASS ::= {
- ID postal-code
- TYPE PostalCode }
-
-x400-physical-delivery-office-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID physical-delivery-office-name
- TYPE PhysicalDeliveryOfficeName }
-
-x400-physical-delivery-office-number EXTENSION-ATTRIBUTE-CLASS ::= {
- ID physical-delivery-office-number
- TYPE PhysicalDeliveryOfficeNumber }
-
-x400-extension-OR-address-components EXTENSION-ATTRIBUTE-CLASS ::= {
- ID extension-OR-address-components
- TYPE ExtensionORAddressComponents }
-
-x400-physical-delivery-personal-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID physical-delivery-personal-name
- TYPE PhysicalDeliveryPersonalName }
-
-x400-physical-delivery-organization-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID physical-delivery-organization-name
- TYPE PhysicalDeliveryOrganizationName }
-
-x400-extension-physical-delivery-address-components
- EXTENSION-ATTRIBUTE-CLASS ::= {
- ID extension-physical-delivery-address-components
- TYPE ExtensionPhysicalDeliveryAddressComponents }
-
-x400-unformatted-postal-address EXTENSION-ATTRIBUTE-CLASS ::= {
- ID unformatted-postal-address
- TYPE UnformattedPostalAddress }
-
-x400-street-address EXTENSION-ATTRIBUTE-CLASS ::= {
- ID street-address
- TYPE StreetAddress }
-
-x400-post-office-box-address EXTENSION-ATTRIBUTE-CLASS ::= {
- ID post-office-box-address
- TYPE PostOfficeBoxAddress }
-
-x400-poste-restante-address EXTENSION-ATTRIBUTE-CLASS ::= {
- ID poste-restante-address
- TYPE PosteRestanteAddress }
-
-x400-unique-postal-name EXTENSION-ATTRIBUTE-CLASS ::= {
- ID unique-postal-name
- TYPE UniquePostalName }
-
-x400-local-postal-attributes EXTENSION-ATTRIBUTE-CLASS ::= {
- ID local-postal-attributes
- TYPE LocalPostalAttributes }
-
-x400-extended-network-address EXTENSION-ATTRIBUTE-CLASS ::= {
- ID extended-network-address
- TYPE ExtendedNetworkAddress }
-
-x400-terminal-type EXTENSION-ATTRIBUTE-CLASS ::= {
- ID terminal-type
- TYPE TerminalType }
-
-x400-teletex-domain-defined-attributes EXTENSION-ATTRIBUTE-CLASS ::= {
- ID teletex-domain-defined-attributes
- TYPE TeletexDomainDefinedAttributes }
-
--- Extensions
-
-SSLExtensions ::= SEQUENCE SIZE (1..MAX) OF Extension
-
-EXTENSION-CLASS ::= CLASS {
- &id OBJECT IDENTIFIER UNIQUE,
- &Type OPTIONAL}
- WITH SYNTAX {
- ID &id
- [TYPE &Type] }
-
-SSLExtension ::= SEQUENCE {
- extnID EXTENSION-CLASS.&id({SupportedExtensions}),
- critical BOOLEAN DEFAULT FALSE,
- extnValue EXTENSION-CLASS.&Type({SupportedExtensions}{@extnID}) }
-
--- The following is needed for conversion between Extension and Extension-Cd
-
-ObjId ::= OBJECT IDENTIFIER
-Boolean ::= BOOLEAN
-Any ::= ANY
-
-Extension-Any ::= SEQUENCE {
- extnID OBJECT IDENTIFIER,
- critical BOOLEAN DEFAULT FALSE,
- extnValue ANY }
-
-SupportedExtensions EXTENSION-CLASS ::= { authorityKeyIdentifier |
- subjectKeyIdentifier | keyUsage | privateKeyUsagePeriod |
- certificatePolicies | policyMappings | subjectAltName |
- issuerAltName | subjectDirectoryAttributes | basicConstraints |
- nameConstraints | policyConstraints | cRLDistributionPoints |
- extKeyUsage | inhibitAnyPolicy | freshestCRL | authorityInfoAccess |
- subjectInfoAccess | cRLNumber | issuingDistributionPoint |
- deltaCRLIndicator | cRLReasons | certificateIssuer |
- holdInstructionCode | invalidityDate }
-
-authorityKeyIdentifier EXTENSION-CLASS ::= {
- ID id-ce-authorityKeyIdentifier
- TYPE AuthorityKeyIdentifier }
-
-subjectKeyIdentifier EXTENSION-CLASS ::= {
- ID id-ce-subjectKeyIdentifier
- TYPE SubjectKeyIdentifier }
-
-keyUsage EXTENSION-CLASS ::= {
- ID id-ce-keyUsage
- TYPE KeyUsage }
-
-privateKeyUsagePeriod EXTENSION-CLASS ::= {
- ID id-ce-privateKeyUsagePeriod
- TYPE PrivateKeyUsagePeriod }
-
-certificatePolicies EXTENSION-CLASS ::= {
- ID id-ce-certificatePolicies
- TYPE CertificatePolicies }
-
-policyMappings EXTENSION-CLASS ::= {
- ID id-ce-policyMappings
- TYPE PolicyMappings }
-
-subjectAltName EXTENSION-CLASS ::= {
- ID id-ce-subjectAltName
- TYPE SubjectAltName }
-
-issuerAltName EXTENSION-CLASS ::= {
- ID id-ce-issuerAltName
- TYPE IssuerAltName }
-
-subjectDirectoryAttributes EXTENSION-CLASS ::= {
- ID id-ce-subjectDirectoryAttributes
- TYPE SubjectDirectoryAttributes }
-
-basicConstraints EXTENSION-CLASS ::= {
- ID id-ce-basicConstraints
- TYPE BasicConstraints }
-
-nameConstraints EXTENSION-CLASS ::= {
- ID id-ce-nameConstraints
- TYPE NameConstraints }
-
-policyConstraints EXTENSION-CLASS ::= {
- ID id-ce-policyConstraints
- TYPE PolicyConstraints }
-
-cRLDistributionPoints EXTENSION-CLASS ::= {
- ID id-ce-cRLDistributionPoints
- TYPE CRLDistributionPoints }
-
-extKeyUsage EXTENSION-CLASS ::= {
- ID id-ce-extKeyUsage
- TYPE ExtKeyUsageSyntax }
-
-inhibitAnyPolicy EXTENSION-CLASS ::= {
- ID id-ce-inhibitAnyPolicy
- TYPE InhibitAnyPolicy }
-
-freshestCRL EXTENSION-CLASS ::= {
- ID id-ce-freshestCRL
- TYPE FreshestCRL }
-
-authorityInfoAccess EXTENSION-CLASS ::= {
- ID id-pe-authorityInfoAccess
- TYPE AuthorityInfoAccessSyntax }
-
-subjectInfoAccess EXTENSION-CLASS ::= {
- ID id-pe-subjectInfoAccess
- TYPE SubjectInfoAccessSyntax }
-
-cRLNumber EXTENSION-CLASS ::= {
- ID id-ce-cRLNumber
- TYPE CRLNumber }
-
-issuingDistributionPoint EXTENSION-CLASS ::= {
- ID id-ce-issuingDistributionPoint
- TYPE IssuingDistributionPoint }
-
-deltaCRLIndicator EXTENSION-CLASS ::= {
- ID id-ce-deltaCRLIndicator
- TYPE BaseCRLNumber }
-
-cRLReasons EXTENSION-CLASS ::= {
- ID id-ce-cRLReasons
- TYPE CRLReason }
-
-certificateIssuer EXTENSION-CLASS ::= {
- ID id-ce-certificateIssuer
- TYPE CertificateIssuer }
-
-holdInstructionCode EXTENSION-CLASS ::= {
- ID id-ce-holdInstructionCode
- TYPE HoldInstructionCode }
-
-invalidityDate EXTENSION-CLASS ::= {
- ID id-ce-invalidityDate
- TYPE InvalidityDate }
-
-END
diff --git a/lib/ssl/pkix/mk_ssl_pkix_oid.erl b/lib/ssl/pkix/mk_ssl_pkix_oid.erl
deleted file mode 100644
index 06edc5113a..0000000000
--- a/lib/ssl/pkix/mk_ssl_pkix_oid.erl
+++ /dev/null
@@ -1,94 +0,0 @@
--module(mk_ssl_pkix_oid).
-
--export([make/0]).
-
--define(PKIX_MODULES, ['OTP-PKIX']).
-
-make() ->
- {ok, Fd} = file:open("ssl_pkix_oid.erl", [write]),
- io:fwrite(Fd, "%%% File: ssl_pkix_oid.erl\n"
- "%%% NB This file has been automatically generated by "
- "mk_ssl_pkix_oid.\n"
- "%%% Do not edit it.\n\n", []),
- io:fwrite(Fd, "-module(ssl_pkix_oid).\n", []),
- io:fwrite(Fd, "-export([id2atom/1, atom2id/1, all_atoms/0, "
- "all_ids/0]).\n\n", []),
-
-
- AIds0 = get_atom_ids(?PKIX_MODULES),
-
- AIds1 = modify_atoms(AIds0),
- gen_id2atom(Fd, AIds1),
- gen_atom2id(Fd, AIds1),
- gen_all(Fd, AIds1),
- file:close(Fd).
-
-get_atom_ids(Ms) ->
- get_atom_ids(Ms, []).
-
-get_atom_ids([], AIdss) ->
- lists:flatten(AIdss);
-get_atom_ids([M| Ms], AIdss) ->
- {value, {exports, Exports}} =
- lists:keysearch(exports, 1, M:module_info()),
- As = lists:zf(
- fun ({info, 0}) -> false;
- ({module_info, 0}) -> false;
- ({encoding_rule, 0}) -> false;
- ({F, 0}) ->
- case atom_to_list(F) of
- %% Remove upper-bound (ub-) functions
- "ub-" ++ _Rest ->
- false;
- _ ->
- {true, F}
- end;
- (_) -> false
- end, Exports),
- AIds = lists:map(fun(F) -> {F, M:F()} end, As),
- get_atom_ids(Ms, [AIds| AIdss]).
-
-modify_atoms(AIds) ->
- F = fun({A, I}) ->
- NAS = case atom_to_list(A) of
- "id-" ++ Rest ->
- Rest;
- Any ->
- Any
- end,
- {list_to_atom(NAS), I} end,
- lists:map(F, AIds).
-
-gen_id2atom(Fd, AIds0) ->
- AIds1 = lists:keysort(2, AIds0),
- Txt = join(";\n",
- lists:map(
- fun({Atom, Id}) ->
- io_lib:fwrite("id2atom(~p) ->\n ~p", [Id, Atom])
- end, AIds1)),
- io:fwrite(Fd, "~s;\nid2atom(Any)->\n Any.\n\n", [Txt]).
-
-gen_atom2id(Fd, AIds0) ->
- AIds1 = lists:keysort(1, AIds0),
- Txt = join(";\n",
- lists:map(
- fun({Atom, Id}) ->
- io_lib:fwrite("atom2id(~p) ->\n ~p", [Atom, Id])
- end, AIds1)),
- io:fwrite(Fd, "~s;\natom2id(Any)->\n Any.\n\n", [Txt]).
-
-gen_all(Fd, AIds) ->
- Atoms = lists:sort([A || {A, _} <- AIds]),
- Ids = lists:sort([I || {_, I} <- AIds]),
- F = fun(X) -> io_lib:fwrite(" ~w", [X]) end,
- ATxt = "all_atoms() ->\n" ++ join(",\n", lists:map(F, Atoms)),
- io:fwrite(Fd, "~s.\n\n", [ATxt]),
- ITxt = "all_ids() ->\n" ++ join(",\n", lists:map(F, Ids)),
- io:fwrite(Fd, "~s.\n\n", [ITxt]).
-
-join(Sep, [H1, H2| T]) ->
- [H1, Sep| join(Sep, [H2| T])];
-join(_Sep, [H1]) ->
- H1;
-join(_, []) ->
- [].
diff --git a/lib/ssl/pkix/prebuild.skip b/lib/ssl/pkix/prebuild.skip
deleted file mode 100644
index ffe82be68b..0000000000
--- a/lib/ssl/pkix/prebuild.skip
+++ /dev/null
@@ -1,5 +0,0 @@
-PKIX1Algorithms88.asn1db
-PKIXAttributeCertificate.asn1db
-PKIX1Explicit88.asn1db
-SSL-PKIX.asn1db
-PKIX1Implicit88.asn1db
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index fabf8a4e0d..7514ad2aa2 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1999-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1999-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%
#
@@ -46,9 +46,6 @@ MODULES= \
ssl_server \
ssl_sup \
ssl_prim \
- ssl_pkix \
- ssl_pem \
- ssl_base64 \
inet_ssl_dist \
ssl_certificate\
ssl_certificate_db\
@@ -71,8 +68,6 @@ INTERNAL_HRL_FILES = \
ssl_alert.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_internal.hrl \
ssl_record.hrl
-PUBLIC_HRL_FILES = ssl_pkix.hrl
-
ERL_FILES= $(MODULES:%=%.erl)
TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
@@ -85,15 +80,12 @@ APP_TARGET= $(EBIN)/$(APP_FILE)
APPUP_SRC= $(APPUP_FILE).src
APPUP_TARGET= $(EBIN)/$(APPUP_FILE)
-INCLUDE = ../include
-
# ----------------------------------------------------
# FLAGS
# ----------------------------------------------------
EXTRA_ERLC_FLAGS = +warn_unused_vars
ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
-pz $(ERL_TOP)/lib/public_key/ebin \
- -I$(INCLUDE) \
$(EXTRA_ERLC_FLAGS) -DVSN=\"$(VSN)\"
@@ -101,7 +93,7 @@ ERL_COMPILE_FLAGS += -I$(ERL_TOP)/lib/kernel/src \
# Targets
# ----------------------------------------------------
-debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(PUBLIC_HRL_FILES)
+debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
clean:
rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET)
@@ -113,9 +105,6 @@ $(APP_TARGET): $(APP_SRC) ../vsn.mk
$(APPUP_TARGET): $(APPUP_SRC) ../vsn.mk
sed -e 's;%VSN%;$(VSN);' $< > $@
-$(PUBLIC_HRL_FILES):
- cp -f $(PUBLIC_HRL_FILES) $(INCLUDE)
-
docs:
# ----------------------------------------------------
@@ -126,8 +115,6 @@ include $(ERL_TOP)/make/otp_release_targets.mk
release_spec: opt
$(INSTALL_DIR) $(RELSYSDIR)/src
$(INSTALL_DATA) $(ERL_FILES) $(INTERNAL_HRL_FILES) $(RELSYSDIR)/src
- $(INSTALL_DIR) $(RELSYSDIR)/include
- $(INSTALL_DATA) $(PUBLIC_HRL_FILES) $(RELSYSDIR)/include
$(INSTALL_DIR) $(RELSYSDIR)/ebin
$(INSTALL_DATA) $(TARGET_FILES) $(APP_TARGET) \
$(APPUP_TARGET) $(RELSYSDIR)/ebin
diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src
index 2a7d451341..b9716786e6 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -7,10 +7,6 @@
ssl_server,
ssl_broker,
ssl_broker_sup,
- ssl_base64,
- ssl_pem,
- ssl_pkix,
- ssl_pkix_oid,
ssl_prim,
inet_ssl_dist,
ssl_tls1,
@@ -28,11 +24,10 @@
ssl_cipher,
ssl_certificate_db,
ssl_certificate,
- ssl_alert,
- 'OTP-PKIX'
+ ssl_alert
]},
{registered, [ssl_sup, ssl_server, ssl_broker_sup]},
- {applications, [kernel, stdlib]},
+ {applications, [crypto, public_key, kernel, stdlib]},
{env, []},
{mod, {ssl_app, []}}]}.
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src
index fdda65021d..65f23e2f74 100644
--- a/lib/ssl/src/ssl.appup.src
+++ b/lib/ssl/src/ssl.appup.src
@@ -1,6 +1,8 @@
%% -*- erlang -*-
{"%VSN%",
[
+ {"3.11.1", [{restart_application, ssl}]},
+ {"3.11", [{restart_application, ssl}]},
{"3.10", [{restart_application, ssl}]},
{"3.10.1", [{restart_application, ssl}]},
{"3.10.2", [{restart_application, ssl}]},
@@ -9,9 +11,12 @@
{"3.10.5", [{restart_application, ssl}]},
{"3.10.6", [{restart_application, ssl}]},
{"3.10.7", [{restart_application, ssl}]},
- {"3.10.8", [{restart_application, ssl}]}
+ {"3.10.8", [{restart_application, ssl}]},
+ {"3.10.9", [{restart_application, ssl}]}
],
[
+ {"3.11.1", [{restart_application, ssl}]},
+ {"3.11", [{restart_application, ssl}]},
{"3.10", [{restart_application, ssl}]},
{"3.10.1", [{restart_application, ssl}]},
{"3.10.2", [{restart_application, ssl}]},
@@ -19,6 +24,7 @@
{"3.10.4", [{restart_application, ssl}]},
{"3.10.5", [{restart_application, ssl}]},
{"3.10.6", [{restart_application, ssl}]},
- {"3.10.8", [{restart_application, ssl}]}
+ {"3.10.8", [{restart_application, ssl}]},
+ {"3.10.9", [{restart_application, ssl}]}
]}.
diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl
index da5f750762..df4cd7c84d 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -34,10 +34,14 @@
%% Should be deprecated as soon as old ssl is removed
%%-deprecated({pid, 1, next_major_release}).
+-deprecated({peercert, 2, next_major_release}).
-include("ssl_int.hrl").
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
+-include("ssl_cipher.hrl").
+
+-include_lib("public_key/include/public_key.hrl").
-record(config, {ssl, %% SSL parameters
inet_user, %% User set inet options
@@ -47,22 +51,25 @@
}).
%%--------------------------------------------------------------------
-%% Function: start([, Type]) -> ok
-%%
-%% Type = permanent | transient | temporary
-%% Vsns = [Vsn]
-%% Vsn = ssl3 | tlsv1 | 'tlsv1.1'
+-spec start() -> ok.
+-spec start(permanent | transient | temporary) -> ok.
%%
-%% Description: Starts the ssl application. Default type
+%% Description: Utility function that starts the ssl,
+%% crypto and public_key applications. Default type
%% is temporary. see application(3)
%%--------------------------------------------------------------------
start() ->
+ application:start(crypto),
+ application:start(public_key),
application:start(ssl).
+
start(Type) ->
+ application:start(crypto, Type),
+ application:start(public_key, Type),
application:start(ssl, Type).
%%--------------------------------------------------------------------
-%% Function: stop() -> ok
+-spec stop() -> ok.
%%
%% Description: Stops the ssl application.
%%--------------------------------------------------------------------
@@ -70,7 +77,8 @@ stop() ->
application:stop(ssl).
%%--------------------------------------------------------------------
-%% Function: connect(Address, Port, Options[, Timeout]) -> {ok, Socket}
+-spec connect(host() | port(), port_num(), list()) -> {ok, #sslsocket{}}.
+-spec connect(host() | port(), port_num(), list(), timeout()) -> {ok, #sslsocket{}}.
%%
%% Description: Connect to a ssl server.
%%--------------------------------------------------------------------
@@ -96,13 +104,13 @@ connect(Socket, SslOptions0, Timeout) when is_port(Socket) ->
{error, Reason}
end;
-connect(Address, Port, Options) ->
- connect(Address, Port, Options, infinity).
+connect(Host, Port, Options) ->
+ connect(Host, Port, Options, infinity).
-connect(Address, Port, Options0, Timeout) ->
- case proplists:get_value(ssl_imp, Options0, old) of
+connect(Host, Port, Options0, Timeout) ->
+ case proplists:get_value(ssl_imp, Options0, new) of
new ->
- new_connect(Address, Port, Options0, Timeout);
+ new_connect(Host, Port, Options0, Timeout);
old ->
%% Allow the option reuseaddr to be present
%% so that new and old ssl can be run by the same
@@ -110,20 +118,21 @@ connect(Address, Port, Options0, Timeout) ->
%% that hardcodes reuseaddr to true in its portprogram.
Options1 = proplists:delete(reuseaddr, Options0),
Options = proplists:delete(ssl_imp, Options1),
- old_connect(Address, Port, Options, Timeout);
+ old_connect(Host, Port, Options, Timeout);
Value ->
{error, {eoptions, {ssl_imp, Value}}}
end.
%%--------------------------------------------------------------------
-%% Function: listen(Port, Options) -> {ok, ListenSock} | {error, Reason}
+-spec listen(port_num(), list()) ->{ok, #sslsocket{}} | {error, reason()}.
+
%%
%% Description: Creates a ssl listen socket.
%%--------------------------------------------------------------------
listen(_Port, []) ->
{error, enooptions};
listen(Port, Options0) ->
- case proplists:get_value(ssl_imp, Options0, old) of
+ case proplists:get_value(ssl_imp, Options0, new) of
new ->
new_listen(Port, Options0);
old ->
@@ -139,7 +148,8 @@ listen(Port, Options0) ->
end.
%%--------------------------------------------------------------------
-%% Function: transport_accept(ListenSocket[, Timeout]) -> {ok, Socket}.
+-spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}}.
+-spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}}.
%%
%% Description: Performs transport accept on a ssl listen socket
%%--------------------------------------------------------------------
@@ -147,23 +157,23 @@ transport_accept(ListenSocket) ->
transport_accept(ListenSocket, infinity).
transport_accept(#sslsocket{pid = {ListenSocket, #config{cb=CbInfo, ssl=SslOpts}},
- fd = new_ssl} = SslSocket, Timeout) ->
+ fd = new_ssl}, Timeout) ->
%% The setopt could have been invoked on the listen socket
%% and options should be inherited.
EmOptions = emulated_options(),
{ok, InetValues} = inet:getopts(ListenSocket, EmOptions),
- {CbModule,_,_} = CbInfo,
+ ok = inet:setopts(ListenSocket, internal_inet_values()),
+ {CbModule,_,_, _} = CbInfo,
case CbModule:accept(ListenSocket, Timeout) of
{ok, Socket} ->
- inet:setopts(Socket, internal_inet_values()),
+ ok = inet:setopts(ListenSocket, InetValues),
{ok, Port} = inet:port(Socket),
ConnArgs = [server, "localhost", Port, Socket,
{SslOpts, socket_options(InetValues)}, self(), CbInfo],
case ssl_connection_sup:start_child(ConnArgs) of
{ok, Pid} ->
- CbModule:controlling_process(Socket, Pid),
- {ok, SslSocket#sslsocket{pid = Pid}};
+ ssl_connection:socket_control(Socket, Pid, CbModule);
{error, Reason} ->
{error, Reason}
end;
@@ -177,8 +187,8 @@ transport_accept(#sslsocket{} = ListenSocket, Timeout) ->
ssl_broker:transport_accept(Pid, ListenSocket, Timeout).
%%--------------------------------------------------------------------
-%% Function: ssl_accept(ListenSocket[, Timeout]) -> {ok, Socket} |
-%% {error, Reason}
+-spec ssl_accept(#sslsocket{}) -> {ok, #sslsocket{}} | {error, reason()}.
+-spec ssl_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | {error, reason()}.
%%
%% Description: Performs accept on a ssl listen socket. e.i. performs
%% ssl handshake.
@@ -186,22 +196,9 @@ transport_accept(#sslsocket{} = ListenSocket, Timeout) ->
ssl_accept(ListenSocket) ->
ssl_accept(ListenSocket, infinity).
-ssl_accept(#sslsocket{pid = Pid, fd = new_ssl}, Timeout) ->
- gen_fsm:send_event(Pid, socket_control),
- try gen_fsm:sync_send_all_state_event(Pid, started, Timeout) of
- connected ->
- ok;
- {error, _} = Error ->
- Error
- catch
- exit:{noproc, _} ->
- {error, closed};
- exit:{timeout, _} ->
- {error, timeout};
- exit:{normal, _} ->
- {error, closed}
- end;
-
+ssl_accept(#sslsocket{fd = new_ssl} = Socket, Timeout) ->
+ ssl_connection:handshake(Socket, Timeout);
+
ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) ->
ssl_accept(ListenSocket, SslOptions, infinity);
@@ -217,7 +214,7 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
try handle_options(SslOptions ++ InetValues, server) of
{ok, #config{cb=CbInfo,ssl=SslOpts, emulated=EmOpts}} ->
{ok, Port} = inet:port(Socket),
- ssl_connection:accept(Port, Socket,
+ ssl_connection:ssl_accept(Port, Socket,
{SslOpts, EmOpts},
self(), CbInfo, Timeout)
catch
@@ -225,11 +222,11 @@ ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) ->
end.
%%--------------------------------------------------------------------
-%% Function: close() -> ok
+-spec close(#sslsocket{}) -> term().
%%
%% Description: Close a ssl connection
%%--------------------------------------------------------------------
-close(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _}}}, fd = new_ssl}) ->
+close(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _, _}}}, fd = new_ssl}) ->
CbMod:close(ListenSocket);
close(#sslsocket{pid = Pid, fd = new_ssl}) ->
ssl_connection:close(Pid);
@@ -238,7 +235,7 @@ close(Socket = #sslsocket{}) ->
ssl_broker:close(Socket).
%%--------------------------------------------------------------------
-%% Function: send(Socket, Data) -> ok
+-spec send(#sslsocket{}, iolist()) -> ok | {error, reason()}.
%%
%% Description: Sends data over the ssl connection
%%--------------------------------------------------------------------
@@ -250,7 +247,8 @@ send(#sslsocket{} = Socket, Data) ->
ssl_broker:send(Socket, Data).
%%--------------------------------------------------------------------
-%% Function: recv(Socket, Length [,Timeout]) -> {ok, Data} | {error, reason}
+-spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}.
+-spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}.
%%
%% Description: Receives data when active = false
%%--------------------------------------------------------------------
@@ -264,8 +262,8 @@ recv(Socket = #sslsocket{}, Length, Timeout) ->
ssl_broker:recv(Socket, Length, Timeout).
%%--------------------------------------------------------------------
-%% Function: controlling_process(Socket, NewOwner) -> ok | {error, Reason}
-%%
+-spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}.
+%%
%% Description: Changes process that receives the messages when active = true
%% or once.
%%--------------------------------------------------------------------
@@ -278,11 +276,8 @@ controlling_process(Socket, NewOwner) when is_pid(NewOwner) ->
ssl_broker:controlling_process(Socket, NewOwner).
%%--------------------------------------------------------------------
-%% Function: connection_info(Socket) -> {ok, {Protocol, CipherSuite}} |
-%% {error, Reason}
-%% Protocol = sslv3 | tlsv1 | tlsv1.1
-%% CipherSuite = {KeyExchange, Chipher, Hash, Exportable}
-%%
+-spec connection_info(#sslsocket{}) -> {ok, {tls_atom_version(), erl_cipher_suite()}} |
+ {error, reason()}.
%%
%% Description: Returns ssl protocol and cipher used for the connection
%%--------------------------------------------------------------------
@@ -294,9 +289,9 @@ connection_info(#sslsocket{} = Socket) ->
ssl_broker:connection_info(Socket).
%%--------------------------------------------------------------------
-%% Function: peercert(Socket[, Opts]) -> {ok, Cert} | {error, Reason}
+-spec peercert(#sslsocket{}) ->{ok, der_cert()} | {error, reason()}.
%%
-%% Description:
+%% Description: Returns the peercert.
%%--------------------------------------------------------------------
peercert(Socket) ->
peercert(Socket, []).
@@ -306,14 +301,7 @@ peercert(#sslsocket{pid = Pid, fd = new_ssl}, Opts) ->
{ok, undefined} ->
{error, no_peercert};
{ok, BinCert} ->
- PKOpts = [case Opt of ssl -> otp; pkix -> plain end ||
- Opt <- Opts, Opt =:= ssl orelse Opt =:= pkix],
- case PKOpts of
- [Opt] ->
- public_key:pkix_decode_cert(BinCert, Opt);
- [] ->
- {ok, BinCert}
- end;
+ decode_peercert(BinCert, Opts);
{error, Reason} ->
{error, Reason}
end;
@@ -322,15 +310,44 @@ peercert(#sslsocket{} = Socket, Opts) ->
ensure_old_ssl_started(),
case ssl_broker:peercert(Socket) of
{ok, Bin} ->
- ssl_pkix:decode_cert(Bin, Opts);
+ decode_peercert(Bin, Opts);
{error, Reason} ->
{error, Reason}
end.
+
+decode_peercert(BinCert, Opts) ->
+ PKOpts = [case Opt of ssl -> otp; pkix -> plain end ||
+ Opt <- Opts, Opt =:= ssl orelse Opt =:= pkix],
+ case PKOpts of
+ [Opt] ->
+ select_part(Opt, public_key:pkix_decode_cert(BinCert, Opt), Opts);
+ [] ->
+ {ok, BinCert}
+ end.
+
+select_part(otp, {ok, Cert}, Opts) ->
+ case lists:member(subject, Opts) of
+ true ->
+ TBS = Cert#'OTPCertificate'.tbsCertificate,
+ {ok, TBS#'OTPTBSCertificate'.subject};
+ false ->
+ {ok, Cert}
+ end;
+
+select_part(plain, {ok, Cert}, Opts) ->
+ case lists:member(subject, Opts) of
+ true ->
+ TBS = Cert#'Certificate'.tbsCertificate,
+ {ok, TBS#'TBSCertificate'.subject};
+ false ->
+ {ok, Cert}
+ end.
+
%%--------------------------------------------------------------------
-%% Function: peername(Socket) -> {ok, {Address, Port}} | {error, Reason}
+-spec peername(#sslsocket{}) -> {ok, {tuple(), port_num()}} | {error, reason()}.
%%
-%% Description:
+%% Description: same as inet:peername/1.
%%--------------------------------------------------------------------
peername(#sslsocket{fd = new_ssl, pid = Pid}) ->
ssl_connection:peername(Pid);
@@ -340,9 +357,10 @@ peername(#sslsocket{} = Socket) ->
ssl_broker:peername(Socket).
%%--------------------------------------------------------------------
-%% Function: cipher_suites() ->
-%%
-%% Description:
+-spec cipher_suites() -> [erl_cipher_suite()].
+-spec cipher_suites(erlang | openssl) -> [erl_cipher_suite()] | [string()].
+
+%% Description: Returns all supported cipher suites.
%%--------------------------------------------------------------------
cipher_suites() ->
cipher_suites(erlang).
@@ -356,7 +374,7 @@ cipher_suites(openssl) ->
[ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)].
%%--------------------------------------------------------------------
-%% Function: getopts(Socket, OptTags) -> {ok, Options} | {error, Reason}
+-spec getopts(#sslsocket{}, [atom()]) -> {ok, [{atom(), term()}]}| {error, reason()}.
%%
%% Description:
%%--------------------------------------------------------------------
@@ -369,7 +387,7 @@ getopts(#sslsocket{} = Socket, Options) ->
ssl_broker:getopts(Socket, Options).
%%--------------------------------------------------------------------
-%% Function: setopts(Socket, Options) -> ok | {error, Reason}
+-spec setopts(#sslsocket{}, [{atom(), term()}]) -> ok | {error, reason()}.
%%
%% Description:
%%--------------------------------------------------------------------
@@ -384,18 +402,18 @@ setopts(#sslsocket{} = Socket, Options) ->
ssl_broker:setopts(Socket, Options).
%%---------------------------------------------------------------
-%% Function: shutdown(Socket, How) -> ok | {error, Reason}
-%%
+-spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}.
+%%
%% Description: Same as gen_tcp:shutdown/2
%%--------------------------------------------------------------------
-shutdown(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _}}}, fd = new_ssl}, How) ->
+shutdown(#sslsocket{pid = {ListenSocket, #config{cb={CbMod,_, _, _}}}, fd = new_ssl}, How) ->
CbMod:shutdown(ListenSocket, How);
shutdown(#sslsocket{pid = Pid, fd = new_ssl}, How) ->
ssl_connection:shutdown(Pid, How).
%%--------------------------------------------------------------------
-%% Function: sockname(Socket) -> {ok, {Address, Port}} | {error, Reason}
-%%
+-spec sockname(#sslsocket{}) -> {ok, {tuple(), port_num()}} | {error, reason()}.
+%%
%% Description: Same as inet:sockname/1
%%--------------------------------------------------------------------
sockname(#sslsocket{fd = new_ssl, pid = {ListenSocket, _}}) ->
@@ -409,9 +427,9 @@ sockname(#sslsocket{} = Socket) ->
ssl_broker:sockname(Socket).
%%---------------------------------------------------------------
-%% Function: seed(Data) -> ok | {error, edata}
+-spec seed(term()) ->term().
%%
-%% Description:
+%% Description: Only used by old ssl.
%%--------------------------------------------------------------------
%% TODO: crypto:seed ?
seed(Data) ->
@@ -419,20 +437,17 @@ seed(Data) ->
ssl_server:seed(Data).
%%---------------------------------------------------------------
-%% Function: session_id(Socket) -> {ok, PropList} | {error, Reason}
+-spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}.
%%
-%% Description:
+%% Description: Returns list of session info currently [{session_id, session_id(),
+%% {cipher_suite, cipher_suite()}]
%%--------------------------------------------------------------------
session_info(#sslsocket{pid = Pid, fd = new_ssl}) ->
ssl_connection:session_info(Pid).
%%---------------------------------------------------------------
-%% Function: versions() -> [{SslAppVer, SupportedSslVer, AvailableSslVsn}]
-%%
-%% SslAppVer = string() - t.ex: ssl-4.0
-%% SupportedSslVer = [SslVer]
-%% AvailableSslVsn = [SSLVer]
-%% SSLVer = sslv3 | tlsv1 | 'tlsv1.1'
+-spec versions() -> [{{ssl_app, string()}, {supported, [tls_version()]},
+ {available, [tls_version()]}}].
%%
%% Description: Returns a list of relevant versions.
%%--------------------------------------------------------------------
@@ -442,7 +457,11 @@ versions() ->
AvailableVsns = ?DEFAULT_SUPPORTED_VERSIONS,
[{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}].
-
+%%---------------------------------------------------------------
+-spec renegotiate(#sslsocket{}) -> ok | {error, reason()}.
+%%
+%% Description:
+%%--------------------------------------------------------------------
renegotiate(#sslsocket{pid = Pid, fd = new_ssl}) ->
ssl_connection:renegotiation(Pid).
@@ -462,7 +481,7 @@ do_new_connect(Address, Port,
#config{cb=CbInfo, inet_user=UserOpts, ssl=SslOpts,
emulated=EmOpts,inet_ssl=SocketOpts},
Timeout) ->
- {CbModule, _, _} = CbInfo,
+ {CbModule, _, _, _} = CbInfo,
try CbModule:connect(Address, Port, SocketOpts, Timeout) of
{ok, Socket} ->
ssl_connection:connect(Address, Port, Socket, {SslOpts,EmOpts},
@@ -484,7 +503,7 @@ old_connect(Address, Port, Options, Timeout) ->
new_listen(Port, Options0) ->
try
{ok, Config} = handle_options(Options0, server),
- #config{cb={CbModule, _, _},inet_user=Options} = Config,
+ #config{cb={CbModule, _, _, _},inet_user=Options} = Config,
case CbModule:listen(Port, Options) of
{ok, ListenSocket} ->
{ok, #sslsocket{pid = {ListenSocket, Config}, fd = new_ssl}};
@@ -519,6 +538,9 @@ handle_options(Opts0, Role) ->
end
end,
+ UserFailIfNoPeerCert = validate_option(fail_if_no_peer_cert,
+ proplists:get_value(fail_if_no_peer_cert, Opts, false)),
+
{Verify, FailIfNoPeerCert, CaCertDefault} =
%% Handle 0, 1, 2 for backwards compatibility
case proplists:get_value(verify, Opts, verify_none) of
@@ -531,9 +553,7 @@ handle_options(Opts0, Role) ->
verify_none ->
{verify_none, false, ca_cert_default(verify_none, Role)};
verify_peer ->
- {verify_peer, proplists:get_value(fail_if_no_peer_cert,
- Opts, false),
- ca_cert_default(verify_peer, Role)};
+ {verify_peer, UserFailIfNoPeerCert, ca_cert_default(verify_peer, Role)};
Value ->
throw({error, {eoptions, {verify, Value}}})
end,
@@ -544,9 +564,9 @@ handle_options(Opts0, Role) ->
versions = handle_option(versions, Opts, []),
verify = validate_option(verify, Verify),
verify_fun = handle_option(verify_fun, Opts, VerifyFun),
- fail_if_no_peer_cert = validate_option(fail_if_no_peer_cert,
- FailIfNoPeerCert),
+ fail_if_no_peer_cert = FailIfNoPeerCert,
verify_client_once = handle_option(verify_client_once, Opts, false),
+ validate_extensions_fun = handle_option(validate_extensions_fun, Opts, undefined),
depth = handle_option(depth, Opts, 1),
certfile = CertFile,
keyfile = handle_option(keyfile, Opts, CertFile),
@@ -558,17 +578,18 @@ handle_options(Opts0, Role) ->
%% Server side option
reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun),
reuse_sessions = handle_option(reuse_sessions, Opts, true),
+ secure_renegotiate = handle_option(secure_renegotiate, Opts, false),
renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT),
debug = handle_option(debug, Opts, [])
},
- CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed}),
- SslOptions = [versions, verify, verify_fun,
+ CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}),
+ SslOptions = [versions, verify, verify_fun, validate_extensions_fun,
fail_if_no_peer_cert, verify_client_once,
depth, certfile, keyfile,
key, password, cacertfile, dhfile, ciphers,
debug, reuse_session, reuse_sessions, ssl_imp,
- cb_info, renegotiate_at],
+ cb_info, renegotiate_at, secure_renegotiate],
SockOpts = lists:foldl(fun(Key, PropList) ->
proplists:delete(Key, PropList)
@@ -598,6 +619,9 @@ validate_option(fail_if_no_peer_cert, Value)
validate_option(verify_client_once, Value)
when Value == true; Value == false ->
Value;
+
+validate_option(validate_extensions_fun, Value) when Value == undefined; is_function(Value) ->
+ Value;
validate_option(depth, Value) when is_integer(Value),
Value >= 0, Value =< 255->
Value;
@@ -627,6 +651,8 @@ validate_option(ciphers, Value) when is_list(Value) ->
try cipher_suites(Version, Value)
catch
exit:_ ->
+ throw({error, {eoptions, {ciphers, Value}}});
+ error:_->
throw({error, {eoptions, {ciphers, Value}}})
end;
validate_option(reuse_session, Value) when is_function(Value) ->
@@ -634,8 +660,12 @@ validate_option(reuse_session, Value) when is_function(Value) ->
validate_option(reuse_sessions, Value) when Value == true;
Value == false ->
Value;
+
+validate_option(secure_renegotiate, Value) when Value == true;
+ Value == false ->
+ Value;
validate_option(renegotiate_at, Value) when is_integer(Value) ->
- min(Value, ?DEFAULT_RENEGOTIATE_AT);
+ erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT);
validate_option(debug, Value) when is_list(Value); Value == true ->
Value;
@@ -648,7 +678,7 @@ validate_versions([Version | Rest], Versions) when Version == 'tlsv1.1';
Version == tlsv1;
Version == sslv3 ->
validate_versions(Rest, Versions);
-validate_versions(Ver, Versions) ->
+validate_versions([Ver| _], Versions) ->
throw({error, {eoptions, {Ver, {versions, Versions}}}}).
validate_inet_option(mode, Value)
@@ -720,7 +750,10 @@ emulated_options([], Inet,Emulated) ->
cipher_suites(Version, []) ->
ssl_cipher:suites(Version);
-cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) ->
+cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility
+ Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0],
+ cipher_suites(Version, Ciphers);
+cipher_suites(Version, [{_,_,_}| _] = Ciphers0) ->
Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0],
cipher_suites(Version, Ciphers);
cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) ->
@@ -742,24 +775,34 @@ cipher_suites(Version, Ciphers0) ->
format_error({error, Reason}) ->
format_error(Reason);
+format_error(Reason) when is_list(Reason) ->
+ Reason;
format_error(closed) ->
- "Connection closed for the operation in question.";
+ "The connection is closed";
+format_error(ecacertfile) ->
+ "Own CA certificate file is invalid.";
+format_error(ecertfile) ->
+ "Own certificate file is invalid.";
+format_error(ekeyfile) ->
+ "Own private key file is invalid.";
+format_error(esslaccept) ->
+ "Server SSL handshake procedure between client and server failed.";
+format_error(esslconnect) ->
+ "Client SSL handshake procedure between client and server failed.";
+format_error({eoptions, Options}) ->
+ lists:flatten(io_lib:format("Error in options list: ~p~n", [Options]));
+
+%%%%%%%%%%%% START OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
format_error(ebadsocket) ->
"Connection not found (internal error).";
format_error(ebadstate) ->
"Connection not in connect state (internal error).";
format_error(ebrokertype) ->
"Wrong broker type (internal error).";
-format_error(ecacertfile) ->
- "Own CA certificate file is invalid.";
-format_error(ecertfile) ->
- "Own certificate file is invalid.";
format_error(echaintoolong) ->
"The chain of certificates provided by peer is too long.";
format_error(ecipher) ->
"Own list of specified ciphers is invalid.";
-format_error(ekeyfile) ->
- "Own private key file is invalid.";
format_error(ekeymismatch) ->
"Own private key does not match own certificate.";
format_error(enoissuercert) ->
@@ -785,10 +828,6 @@ format_error(epeercertinvalid) ->
"Certificate provided by peer is invalid.";
format_error(eselfsignedcert) ->
"Certificate provided by peer is self signed.";
-format_error(esslaccept) ->
- "Server SSL handshake procedure between client and server failed.";
-format_error(esslconnect) ->
- "Client SSL handshake procedure between client and server failed.";
format_error(esslerrssl) ->
"SSL protocol failure. Typically because of a fatal alert from peer.";
format_error(ewantconnect) ->
@@ -807,6 +846,9 @@ format_error({badcast, _Cast}) ->
format_error({badinfo, _Info}) ->
"Call not recognized for current mode (active or passive) and state "
"of socket.";
+
+%%%%%%%%%%%%%%%%%% END OLD SSL format_error %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
format_error(Error) ->
case (catch inet:format_error(Error)) of
"unkknown POSIX" ++ _ ->
@@ -818,7 +860,7 @@ format_error(Error) ->
end.
no_format(Error) ->
- io_lib:format("No format string for error: \"~p\" available.", [Error]).
+ lists:flatten(io_lib:format("No format string for error: \"~p\" available.", [Error])).
%% Start old ssl port program if needed.
ensure_old_ssl_started() ->
@@ -853,10 +895,6 @@ version() ->
end,
{ok, {SSLVsn, CompVsn, LibVsn}}.
-min(N,M) when N < M ->
- N;
-min(_, M) ->
- M.
%% Only used to remove exit messages from old ssl
%% First is a nonsense clause to provide some
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index d3f9c833f1..eb1228afa4 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
@@ -32,76 +32,87 @@
-export([alert_txt/1, reason_code/2]).
+%%====================================================================
+%% Internal application API
+%%====================================================================
+%%--------------------------------------------------------------------
+-spec reason_code(#alert{}, client | server) -> closed | esslconnect |
+ esslaccept | string().
+%%
+%% Description: Returns the error reason that will be returned to the
+%% user.
+%%--------------------------------------------------------------------
+
reason_code(#alert{description = ?CLOSE_NOTIFY}, _) ->
closed;
reason_code(#alert{description = ?HANDSHAKE_FAILURE}, client) ->
esslconnect;
reason_code(#alert{description = ?HANDSHAKE_FAILURE}, server) ->
esslaccept;
-reason_code(#alert{description = ?CERTIFICATE_EXPIRED}, _) ->
- epeercertexpired;
-reason_code(#alert{level = ?FATAL}, _) ->
- esslerrssl;
reason_code(#alert{description = Description}, _) ->
description_txt(Description).
+%%--------------------------------------------------------------------
+-spec alert_txt(#alert{}) -> string().
+%%
+%% Description: Returns the error string for given alert.
+%%--------------------------------------------------------------------
+
alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}}) ->
Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++
level_txt(Level) ++" "++ description_txt(Description).
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
level_txt(?WARNING) ->
"Warning:";
level_txt(?FATAL) ->
"Fatal error:".
description_txt(?CLOSE_NOTIFY) ->
- "close_notify";
+ "close notify";
description_txt(?UNEXPECTED_MESSAGE) ->
- "unexpected_message";
+ "unexpected message";
description_txt(?BAD_RECORD_MAC) ->
- "bad_record_mac";
+ "bad record mac";
description_txt(?DECRYPTION_FAILED) ->
- "decryption_failed";
+ "decryption failed";
description_txt(?RECORD_OVERFLOW) ->
- "record_overflow";
+ "record overflow";
description_txt(?DECOMPRESSION_FAILURE) ->
- "decompression_failure";
+ "decompression failure";
description_txt(?HANDSHAKE_FAILURE) ->
- "handshake_failure";
+ "handshake failure";
description_txt(?BAD_CERTIFICATE) ->
- "bad_certificate";
+ "bad certificate";
description_txt(?UNSUPPORTED_CERTIFICATE) ->
- "unsupported_certificate";
+ "unsupported certificate";
description_txt(?CERTIFICATE_REVOKED) ->
- "certificate_revoked";
+ "certificate revoked";
description_txt(?CERTIFICATE_EXPIRED) ->
- "certificate_expired";
+ "certificate expired";
description_txt(?CERTIFICATE_UNKNOWN) ->
- "certificate_unknown";
+ "certificate unknown";
description_txt(?ILLEGAL_PARAMETER) ->
- "illegal_parameter";
+ "illegal parameter";
description_txt(?UNKNOWN_CA) ->
- "unknown_ca";
+ "unknown ca";
description_txt(?ACCESS_DENIED) ->
- "access_denied";
+ "access denied";
description_txt(?DECODE_ERROR) ->
- "decode_error";
+ "decode error";
description_txt(?DECRYPT_ERROR) ->
- "decrypt_error";
+ "decrypt error";
description_txt(?EXPORT_RESTRICTION) ->
- "export_restriction";
+ "export restriction";
description_txt(?PROTOCOL_VERSION) ->
- "protocol_version";
+ "protocol version";
description_txt(?INSUFFICIENT_SECURITY) ->
- "insufficient_security";
+ "insufficient security";
description_txt(?INTERNAL_ERROR) ->
- "internal_error";
+ "internal error";
description_txt(?USER_CANCELED) ->
- "user_canceled";
+ "user canceled";
description_txt(?NO_RENEGOTIATION) ->
- "no_renegotiation".
-
-
-
-
-
+ "no renegotiation".
diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl
index 6ca1c42631..d9a354086d 100644
--- a/lib/ssl/src/ssl_app.erl
+++ b/lib/ssl/src/ssl_app.erl
@@ -27,14 +27,16 @@
-export([start/2, stop/1]).
-%% start/2(Type, StartArgs) -> {ok, Pid} | {ok, Pid, State} |
-%% {error, Reason}
-%%
+%%--------------------------------------------------------------------
+-spec start(normal | {takeover, node()} | {failover, node()}, list()) ->
+ {ok, pid()} | {ok, pid(), term()} | {error, term()}.
+%%--------------------------------------------------------------------
start(_Type, _StartArgs) ->
ssl_sup:start_link().
-%% stop(State) -> void()
-%%
+%--------------------------------------------------------------------
+-spec stop(term())-> ok.
+%%--------------------------------------------------------------------
stop(_State) ->
ok.
diff --git a/lib/ssl/src/ssl_base64.erl b/lib/ssl/src/ssl_base64.erl
deleted file mode 100644
index cfc42407e8..0000000000
--- a/lib/ssl/src/ssl_base64.erl
+++ /dev/null
@@ -1,129 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-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%
-%%
-
-%%
-
-%%% Purpose : Base 64 encoding and decoding.
-
--module(ssl_base64).
-
--export([encode/1, encode_split/1, decode/1, join_decode/1]).
-
--define(st(X,A), ((X-A+256) div 256)).
--define(CHARS, 64).
-
-%% A PEM encoding consists of characters A-Z, a-z, 0-9, +, / and
-%% =. Each character encodes a 6 bits value from 0 to 63 (A = 0, / =
-%% 63); = is a padding character.
-%%
-
-%%
-%% encode(Bytes|Binary) -> Chars
-%%
-%% Take 3 bytes a time (3 x 8 = 24 bits), and make 4 characters out of
-%% them (4 x 6 = 24 bits).
-%%
-encode(Bs) when is_list(Bs) ->
- encode(list_to_binary(Bs));
-encode(<<B:3/binary, Bs/binary>>) ->
- <<C1:6, C2:6, C3:6, C4:6>> = B,
- [enc(C1), enc(C2), enc(C3), enc(C4)| encode(Bs)];
-encode(<<B:2/binary>>) ->
- <<C1:6, C2:6, C3:6, _:6>> = <<B/binary, 0>>,
- [enc(C1), enc(C2), enc(C3), $=];
-encode(<<B:1/binary>>) ->
- <<C1:6, C2:6, _:12>> = <<B/binary, 0, 0>>,
- [enc(C1), enc(C2), $=, $=];
-encode(<<>>) ->
- [].
-
-%%
-%% encode_split(Bytes|Binary) -> Lines
-%%
-%% The encoding is divided into lines separated by <NL>, and each line
-%% is precisely 64 characters long (excluding the <NL> characters,
-%% except the last line which 64 characters long or shorter. <NL> may
-%% follow the last line.
-%%
-encode_split(Bs) ->
- split(encode(Bs)).
-
-%%
-%% decode(Chars) -> Binary
-%%
-decode(Cs) ->
- list_to_binary(decode1(Cs)).
-
-decode1([C1, C2, $=, $=]) ->
- <<B1, _:16>> = <<(dec(C1)):6, (dec(C2)):6, 0:12>>,
- [B1];
-decode1([C1, C2, C3, $=]) ->
- <<B1, B2, _:8>> = <<(dec(C1)):6, (dec(C2)):6, (dec(C3)):6, (dec(0)):6>>,
- [B1, B2];
-decode1([C1, C2, C3, C4| Cs]) ->
- Bin = <<(dec(C1)):6, (dec(C2)):6, (dec(C3)):6, (dec(C4)):6>>,
- [Bin| decode1(Cs)];
-decode1([]) ->
- [].
-
-%%
-%% join_decode(Lines) -> Binary
-%%
-%% Remove <NL> before decoding.
-%%
-join_decode(Cs) ->
- decode(join(Cs)).
-
-%%
-%% Locals
-%%
-
-%% enc/1 and dec/1
-%%
-%% Mapping: 0-25 -> A-Z, 26-51 -> a-z, 52-61 -> 0-9, 62 -> +, 63 -> /
-%%
-enc(C) ->
- 65 + C + 6*?st(C,26) - 75*?st(C,52) -15*?st(C,62) + 3*?st(C,63).
-
-dec(C) ->
- 62*?st(C,43) + ?st(C,47) + (C-59)*?st(C,48) - 69*?st(C,65) - 6*?st(C,97).
-
-%% split encoding into lines
-%%
-split(Cs) ->
- split(Cs, ?CHARS).
-
-split([], _N) ->
- [$\n];
-split(Cs, 0) ->
- [$\n| split(Cs, ?CHARS)];
-split([C| Cs], N) ->
- [C| split(Cs, N-1)].
-
-%% join lines of encodings
-%%
-join([$\r, $\n| Cs]) ->
- join(Cs);
-join([$\n| Cs]) ->
- join(Cs);
-join([C| Cs]) ->
- [C| join(Cs)];
-join([]) ->
- [].
-
diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl
index d97b61a5ce..8a79f75725 100644
--- a/lib/ssl/src/ssl_certificate.erl
+++ b/lib/ssl/src/ssl_certificate.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
@@ -29,15 +29,31 @@
-include("ssl_alert.hrl").
-include("ssl_internal.hrl").
-include("ssl_debug.hrl").
+-include_lib("public_key/include/public_key.hrl").
-export([trusted_cert_and_path/3,
certificate_chain/2,
- file_to_certificats/1]).
+ file_to_certificats/1,
+ validate_extensions/6,
+ is_valid_extkey_usage/2,
+ is_valid_key_usage/2,
+ select_extension/2,
+ extensions_list/1,
+ signature_type/1
+ ]).
%%====================================================================
%% Internal application API
%%====================================================================
+%%--------------------------------------------------------------------
+-spec trusted_cert_and_path([der_cert()], certdb_ref(), boolean()) ->
+ {der_cert(), [der_cert()], list()}.
+%%
+%% Description: Extracts the root cert (if not presents tries to
+%% look it up, if not found {bad_cert, unknown_ca} will be added verification
+%% errors. Returns {RootCert, Path, VerifyErrors}
+%%--------------------------------------------------------------------
trusted_cert_and_path(CertChain, CertDbRef, Verify) ->
[Cert | RestPath] = lists:reverse(CertChain),
{ok, OtpCert} = public_key:pkix_decode_cert(Cert, otp),
@@ -65,7 +81,7 @@ trusted_cert_and_path(CertChain, CertDbRef, Verify) ->
%% The root CA was not sent and can not be found, we fail if verify = true
not_valid(?ALERT_REC(?FATAL, ?UNKNOWN_CA), Verify, {Cert, RestPath});
{{SerialNr, Issuer}, Path} ->
- case ssl_certificate_db:lookup_trusted_cert(CertDbRef,
+ case ssl_manager:lookup_trusted_cert(CertDbRef,
SerialNr, Issuer) of
{ok, {BinCert,_}} ->
{BinCert, Path, []};
@@ -76,16 +92,94 @@ trusted_cert_and_path(CertChain, CertDbRef, Verify) ->
end
end.
-
+%%--------------------------------------------------------------------
+-spec certificate_chain(undefined | binary(), certdb_ref()) ->
+ {error, no_cert} | [der_cert()].
+%%
+%% Description: Return the certificate chain to send to peer.
+%%--------------------------------------------------------------------
certificate_chain(undefined, _CertsDbRef) ->
{error, no_cert};
certificate_chain(OwnCert, CertsDbRef) ->
{ok, ErlCert} = public_key:pkix_decode_cert(OwnCert, otp),
certificate_chain(ErlCert, OwnCert, CertsDbRef, [OwnCert]).
-
-file_to_certificats(File) ->
+%%--------------------------------------------------------------------
+-spec file_to_certificats(string()) -> [der_cert()].
+%%
+%% Description: Return list of DER encoded certificates.
+%%--------------------------------------------------------------------
+file_to_certificats(File) ->
{ok, List} = ssl_manager:cache_pem_file(File),
[Bin || {cert, Bin, not_encrypted} <- List].
+%%--------------------------------------------------------------------
+-spec validate_extensions([#'Extension'{}], term(), [#'Extension'{}],
+ boolean(), list(), client | server) -> {[#'Extension'{}], term(), list()}.
+%%
+%% Description: Validates ssl/tls specific extensions
+%%--------------------------------------------------------------------
+validate_extensions([], ValidationState, UnknownExtensions, _, AccErr, _) ->
+ {UnknownExtensions, ValidationState, AccErr};
+
+validate_extensions([#'Extension'{extnID = ?'id-ce-extKeyUsage',
+ extnValue = KeyUse,
+ critical = true} | Rest],
+ ValidationState, UnknownExtensions, Verify, AccErr0, Role) ->
+ case is_valid_extkey_usage(KeyUse, Role) of
+ true ->
+ validate_extensions(Rest, ValidationState, UnknownExtensions,
+ Verify, AccErr0, Role);
+ false ->
+ AccErr =
+ not_valid_extension({bad_cert, invalid_ext_key_usage}, Verify, AccErr0),
+ validate_extensions(Rest, ValidationState, UnknownExtensions, Verify, AccErr, Role)
+ end;
+
+validate_extensions([Extension | Rest], ValidationState, UnknownExtensions,
+ Verify, AccErr, Role) ->
+ validate_extensions(Rest, ValidationState, [Extension | UnknownExtensions],
+ Verify, AccErr, Role).
+
+%%--------------------------------------------------------------------
+-spec is_valid_key_usage(list(), term()) -> boolean().
+%%
+%% Description: Checks if Use is a valid key usage.
+%%--------------------------------------------------------------------
+is_valid_key_usage(KeyUse, Use) ->
+ lists:member(Use, KeyUse).
+
+%%--------------------------------------------------------------------
+-spec select_extension(term(), list()) -> undefined | #'Extension'{}.
+%%
+%% Description: Selects the extension identified by Id if present in
+%% a list of extensions.
+%%--------------------------------------------------------------------
+select_extension(_, []) ->
+ undefined;
+select_extension(Id, [#'Extension'{extnID = Id} = Extension | _]) ->
+ Extension;
+select_extension(Id, [_ | Extensions]) ->
+ select_extension(Id, Extensions).
+
+%%--------------------------------------------------------------------
+-spec extensions_list(asn1_NOVALUE | list()) -> list().
+%%
+%% Description: Handles that
+%%--------------------------------------------------------------------
+extensions_list(asn1_NOVALUE) ->
+ [];
+extensions_list(Extensions) ->
+ Extensions.
+
+%%--------------------------------------------------------------------
+-spec signature_type(term()) -> rsa | dsa .
+%%
+%% Description:
+%%--------------------------------------------------------------------
+signature_type(RSA) when RSA == ?sha1WithRSAEncryption;
+ RSA == ?md5WithRSAEncryption ->
+ rsa;
+signature_type(?'id-dsa-with-sha1') ->
+ dsa.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -122,7 +216,7 @@ certificate_chain(_CertsDbRef, Chain, _SerialNr, _Issuer, true) ->
{ok, lists:reverse(Chain)};
certificate_chain(CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) ->
- case ssl_certificate_db:lookup_trusted_cert(CertsDbRef,
+ case ssl_manager:lookup_trusted_cert(CertsDbRef,
SerialNr, Issuer) of
{ok, {IssuerCert, ErlCert}} ->
{ok, ErlCert} = public_key:pkix_decode_cert(IssuerCert, otp),
@@ -138,7 +232,7 @@ certificate_chain(CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) ->
end.
find_issuer(OtpCert, PrevCandidateKey) ->
- case ssl_certificate_db:issuer_candidate(PrevCandidateKey) of
+ case ssl_manager:issuer_candidate(PrevCandidateKey) of
no_more_candidates ->
{error, issuer_not_found};
{Key, {_Cert, ErlCertCandidate}} ->
@@ -154,3 +248,15 @@ not_valid(Alert, true, _) ->
throw(Alert);
not_valid(_, false, {ErlCert, Path}) ->
{ErlCert, Path, [{bad_cert, unknown_ca}]}.
+
+is_valid_extkey_usage(KeyUse, client) ->
+ %% Client wants to verify server
+ is_valid_key_usage(KeyUse,?'id-kp-serverAuth');
+is_valid_extkey_usage(KeyUse, server) ->
+ %% Server wants to verify client
+ is_valid_key_usage(KeyUse, ?'id-kp-clientAuth').
+
+not_valid_extension(Error, true, _) ->
+ throw(Error);
+not_valid_extension(Error, false, AccErrors) ->
+ [Error | AccErrors].
diff --git a/lib/ssl/src/ssl_certificate_db.erl b/lib/ssl/src/ssl_certificate_db.erl
index b8c3c6f6b7..e953821057 100644
--- a/lib/ssl/src/ssl_certificate_db.erl
+++ b/lib/ssl/src/ssl_certificate_db.erl
@@ -22,7 +22,7 @@
%%----------------------------------------------------------------------
-module(ssl_certificate_db).
-
+-include("ssl_internal.hrl").
-include_lib("public_key/include/public_key.hrl").
-export([create/0, remove/1, add_trusted_certs/3,
@@ -34,8 +34,7 @@
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: create() -> Db
-%% Db = term() - Reference to the crated database
+-spec create() -> certdb_ref().
%%
%% Description: Creates a new certificate db.
%% Note: lookup_trusted_cert/3 may be called from any process but only
@@ -47,8 +46,7 @@ create() ->
ets:new(ssl_pid_to_file, [bag, private])].
%%--------------------------------------------------------------------
-%% Function: delete(Db) -> _
-%% Db = Database refererence as returned by create/0
+-spec remove(certdb_ref()) -> term().
%%
%% Description: Removes database db
%%--------------------------------------------------------------------
@@ -56,11 +54,10 @@ remove(Dbs) ->
lists:foreach(fun(Db) -> true = ets:delete(Db) end, Dbs).
%%--------------------------------------------------------------------
-%% Function: lookup_trusted_cert(Ref, SerialNumber, Issuer) -> {BinCert,DecodedCert}
-%% Ref = ref()
+-spec lookup_trusted_cert(reference(), serialnumber(), issuer()) -> {der_cert(), #'OTPCertificate'{}}.
+
%% SerialNumber = integer()
%% Issuer = {rdnSequence, IssuerAttrs}
-%% BinCert = binary()
%%
%% Description: Retrives the trusted certificate identified by
%% <SerialNumber, Issuer>. Ref is used as it is specified
@@ -78,11 +75,7 @@ lookup_cached_certs(File) ->
ets:lookup(certificate_db_name(), {file, File}).
%%--------------------------------------------------------------------
-%% Function: add_trusted_certs(Pid, File, Db) -> {ok, Ref}
-%% Pid = pid()
-%% File = string()
-%% Db = Database refererence as returned by create/0
-%% Ref = ref()
+-spec add_trusted_certs(pid(), string(), certdb_ref()) -> {ok, certdb_ref()}.
%%
%% Description: Adds the trusted certificates from file <File> to the
%% runtime database. Returns Ref that should be handed to lookup_trusted_cert
@@ -103,7 +96,7 @@ add_trusted_certs(Pid, File, [CertsDb, FileToRefDb, PidToFileDb]) ->
{ok, Ref}.
%%--------------------------------------------------------------------
-%% Function: cache_pem_file(Pid, File, Db) -> FileContent
+-spec cache_pem_file(pid(), string(), certdb_ref()) -> term().
%%
%% Description: Cache file as binary in DB
%%--------------------------------------------------------------------
@@ -114,7 +107,8 @@ cache_pem_file(Pid, File, [CertsDb, _FileToRefDb, PidToFileDb]) ->
Res.
%%--------------------------------------------------------------------
-%% Function: remove_trusted_certs(Pid, Db) -> _
+-spec remove_trusted_certs(pid(), certdb_ref()) -> term().
+
%%
%% Description: Removes trusted certs originating from
%% the file associated to Pid from the runtime database.
@@ -144,11 +138,9 @@ remove_trusted_certs(Pid, [CertsDb, FileToRefDb, PidToFileDb]) ->
end.
%%--------------------------------------------------------------------
-%% Function: issuer_candidate() -> {Key, Candidate} | no_more_candidates
+-spec issuer_candidate(no_candidate | cert_key()) ->
+ {cert_key(), der_cert()} | no_more_candidates.
%%
-%% Candidate
-%%
-%%
%% Description: If a certificat does not define its issuer through
%% the extension 'ce-authorityKeyIdentifier' we can
%% try to find the issuer in the database over known
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 3d3d11b7f3..a6e80047c2 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
@@ -28,27 +28,26 @@
-include("ssl_internal.hrl").
-include("ssl_record.hrl").
-include("ssl_cipher.hrl").
+-include("ssl_alert.hrl").
-include("ssl_debug.hrl").
+-include_lib("public_key/include/public_key.hrl").
-export([security_parameters/2, suite_definition/1,
- decipher/4, cipher/4,
+ decipher/5, cipher/4,
suite/1, suites/1,
- openssl_suite/1, openssl_suite_name/1]).
+ openssl_suite/1, openssl_suite_name/1, filter/2]).
-compile(inline).
%%--------------------------------------------------------------------
-%% Function: security_parameters(CipherSuite, SecParams) ->
-%% #security_parameters{}
-%%
-%% CipherSuite - as defined in ssl_cipher.hrl
-%% SecParams - #security_parameters{}
+-spec security_parameters(erl_cipher_suite(), #security_parameters{}) ->
+ #security_parameters{}.
%%
%% Description: Returns a security parameters record where the
%% cipher values has been updated according to <CipherSuite>
%%-------------------------------------------------------------------
security_parameters(CipherSuite, SecParams) ->
- { _, Cipher, Hash, Exportable} = suite_definition(CipherSuite),
+ { _, Cipher, Hash} = suite_definition(CipherSuite),
SecParams#security_parameters{
cipher_suite = CipherSuite,
bulk_cipher_algorithm = bulk_cipher_algorithm(Cipher),
@@ -58,19 +57,14 @@ security_parameters(CipherSuite, SecParams) ->
key_material_length = key_material(Cipher),
iv_size = iv_size(Cipher),
mac_algorithm = mac_algorithm(Hash),
- hash_size = hash_size(Hash),
- exportable = Exportable}.
+ hash_size = hash_size(Hash)}.
%%--------------------------------------------------------------------
-%% Function: cipher(Method, CipherState, Mac, Data) ->
-%% {Encrypted, UpdateCipherState}
+-spec cipher(cipher_enum(), #cipher_state{}, binary(), binary()) ->
+ {binary(), #cipher_state{}}.
%%
-%% Method - integer() (as defined in ssl_cipher.hrl)
-%% CipherState, UpdatedCipherState - #cipher_state{}
-%% Data, Encrypted - binary()
-%%
-%% Description: Encrypts the data and the mac using method, updating
-%% the cipher state
+%% Description: Encrypts the data and the MAC using chipher described
+%% by cipher_enum() and updating the cipher state
%%-------------------------------------------------------------------
cipher(?NULL, CipherState, <<>>, Fragment) ->
GenStreamCipherList = [Fragment, <<>>],
@@ -91,10 +85,10 @@ cipher(?DES, CipherState, Mac, Fragment) ->
block_cipher(fun(Key, IV, T) ->
crypto:des_cbc_encrypt(Key, IV, T)
end, block_size(des_cbc), CipherState, Mac, Fragment);
-cipher(?DES40, CipherState, Mac, Fragment) ->
- block_cipher(fun(Key, IV, T) ->
- crypto:des_cbc_encrypt(Key, IV, T)
- end, block_size(des_cbc), CipherState, Mac, Fragment);
+%% cipher(?DES40, CipherState, Mac, Fragment) ->
+%% block_cipher(fun(Key, IV, T) ->
+%% crypto:des_cbc_encrypt(Key, IV, T)
+%% end, block_size(des_cbc), CipherState, Mac, Fragment);
cipher(?'3DES', CipherState, Mac, Fragment) ->
block_cipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
crypto:des3_cbc_encrypt(K1, K2, K3, IV, T)
@@ -104,15 +98,11 @@ cipher(?AES, CipherState, Mac, Fragment) ->
crypto:aes_cbc_128_encrypt(Key, IV, T);
(Key, IV, T) when byte_size(Key) =:= 32 ->
crypto:aes_cbc_256_encrypt(Key, IV, T)
- end, block_size(aes_128_cbc), CipherState, Mac, Fragment);
+ end, block_size(aes_128_cbc), CipherState, Mac, Fragment).
%% cipher(?IDEA, CipherState, Mac, Fragment) ->
%% block_cipher(fun(Key, IV, T) ->
%% crypto:idea_cbc_encrypt(Key, IV, T)
%% end, block_size(idea_cbc), CipherState, Mac, Fragment);
-cipher(?RC2, CipherState, Mac, Fragment) ->
- block_cipher(fun(Key, IV, T) ->
- crypto:rc2_40_cbc_encrypt(Key, IV, T)
- end, block_size(rc2_cbc_40), CipherState, Mac, Fragment).
block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
Mac, Fragment) ->
@@ -128,19 +118,15 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0,
{T, CS0#cipher_state{iv=NextIV}}.
%%--------------------------------------------------------------------
-%% Function: decipher(Method, CipherState, Mac, Data) ->
-%% {Decrypted, UpdateCipherState}
+-spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(), tls_version()) ->
+ {binary(), #cipher_state{}}.
%%
-%% Method - integer() (as defined in ssl_cipher.hrl)
-%% CipherState, UpdatedCipherState - #cipher_state{}
-%% Data, Encrypted - binary()
-%%
-%% Description: Decrypts the data and the mac using method, updating
-%% the cipher state
+%% Description: Decrypts the data and the MAC using cipher described
+%% by cipher_enum() and updating the cipher state.
%%-------------------------------------------------------------------
-decipher(?NULL, _HashSz, CipherState, Fragment) ->
+decipher(?NULL, _HashSz, CipherState, Fragment, _) ->
{Fragment, <<>>, CipherState};
-decipher(?RC4, HashSz, CipherState, Fragment) ->
+decipher(?RC4, HashSz, CipherState, Fragment, _) ->
?DBG_TERM(CipherState#cipher_state.key),
State0 = case CipherState#cipher_state.state of
undefined -> crypto:rc4_set_key(CipherState#cipher_state.key);
@@ -153,52 +139,49 @@ decipher(?RC4, HashSz, CipherState, Fragment) ->
GSC = generic_stream_cipher_from_bin(T, HashSz),
#generic_stream_cipher{content=Content, mac=Mac} = GSC,
{Content, Mac, CipherState#cipher_state{state=State1}};
-decipher(?DES, HashSz, CipherState, Fragment) ->
- block_decipher(fun(Key, IV, T) ->
- crypto:des_cbc_decrypt(Key, IV, T)
- end, CipherState, HashSz, Fragment);
-decipher(?DES40, HashSz, CipherState, Fragment) ->
+decipher(?DES, HashSz, CipherState, Fragment, Version) ->
block_decipher(fun(Key, IV, T) ->
crypto:des_cbc_decrypt(Key, IV, T)
- end, CipherState, HashSz, Fragment);
-decipher(?'3DES', HashSz, CipherState, Fragment) ->
+ end, CipherState, HashSz, Fragment, Version);
+%% decipher(?DES40, HashSz, CipherState, Fragment, Version) ->
+%% block_decipher(fun(Key, IV, T) ->
+%% crypto:des_cbc_decrypt(Key, IV, T)
+%% end, CipherState, HashSz, Fragment, Version);
+decipher(?'3DES', HashSz, CipherState, Fragment, Version) ->
block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) ->
crypto:des3_cbc_decrypt(K1, K2, K3, IV, T)
- end, CipherState, HashSz, Fragment);
-decipher(?AES, HashSz, CipherState, Fragment) ->
+ end, CipherState, HashSz, Fragment, Version);
+decipher(?AES, HashSz, CipherState, Fragment, Version) ->
block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 ->
crypto:aes_cbc_128_decrypt(Key, IV, T);
(Key, IV, T) when byte_size(Key) =:= 32 ->
crypto:aes_cbc_256_decrypt(Key, IV, T)
- end, CipherState, HashSz, Fragment);
-%% decipher(?IDEA, HashSz, CipherState, Fragment) ->
+ end, CipherState, HashSz, Fragment, Version).
+%% decipher(?IDEA, HashSz, CipherState, Fragment, Version) ->
%% block_decipher(fun(Key, IV, T) ->
%% crypto:idea_cbc_decrypt(Key, IV, T)
-%% end, CipherState, HashSz, Fragment);
-decipher(?RC2, HashSz, CipherState, Fragment) ->
- block_decipher(fun(Key, IV, T) ->
- crypto:rc2_40_cbc_decrypt(Key, IV, T)
- end, CipherState, HashSz, Fragment).
+%% end, CipherState, HashSz, Fragment, Version);
block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
- HashSz, Fragment) ->
+ HashSz, Fragment, Version) ->
?DBG_HEX(Key),
?DBG_HEX(IV),
?DBG_HEX(Fragment),
T = Fun(Key, IV, Fragment),
?DBG_HEX(T),
GBC = generic_block_cipher_from_bin(T, HashSz),
- ok = check_padding(GBC), %% TODO kolla ocks�...
- Content = GBC#generic_block_cipher.content,
- Mac = GBC#generic_block_cipher.mac,
- CipherState1 = CipherState0#cipher_state{iv=next_iv(Fragment, IV)},
- {Content, Mac, CipherState1}.
-
+ case is_correct_padding(GBC, Version) of
+ true ->
+ Content = GBC#generic_block_cipher.content,
+ Mac = GBC#generic_block_cipher.mac,
+ CipherState1 = CipherState0#cipher_state{iv=next_iv(Fragment, IV)},
+ {Content, Mac, CipherState1};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end.
+
%%--------------------------------------------------------------------
-%% Function: suites(Version) -> [Suite]
-%%
-%% Version = version()
-%% Suite = binary() from ssl_cipher.hrl
+-spec suites(tls_version()) -> [cipher_suite()].
%%
%% Description: Returns a list of supported cipher suites.
%%--------------------------------------------------------------------
@@ -208,294 +191,112 @@ suites({3, N}) when N == 1; N == 2 ->
ssl_tls1:suites().
%%--------------------------------------------------------------------
-%% Function: suite_definition(CipherSuite) ->
-%% {KeyExchange, Cipher, Hash, Exportable}
-%%
-%%
-%% CipherSuite - as defined in ssl_cipher.hrl
-%% KeyExchange - rsa | dh_dss | dh_rsa | dh_anon | dhe_dss | dhe_rsa
-%% krb5 | *_export (old ssl)
-%% Cipher - null | rc4_128 | idea_cbc | des_cbc | '3des_ede_cbc'
-%% des40_cbc | dh_dss | aes_128_cbc | aes_256_cbc |
-%% rc2_cbc_40 | rc4_40
-%% Hash - null | md5 | sha
-%% Exportable - export | no_export | ignore(?)
+-spec suite_definition(cipher_suite()) -> erl_cipher_suite().
%%
-%% Description: Returns a security parameters record where the
-%% cipher values has been updated according to <CipherSuite>
-%% Note: since idea is unsupported on the openssl version used by
-%% crypto (as of OTP R12B), we've commented away the idea stuff
+%% Description: Return erlang cipher suite definition.
+%% Note: Currently not supported suites are commented away.
+%% They should be supported or removed in the future.
%%-------------------------------------------------------------------
%% TLS v1.1 suites
suite_definition(?TLS_NULL_WITH_NULL_NULL) ->
- {null, null, null, ignore};
-suite_definition(?TLS_RSA_WITH_NULL_MD5) ->
- {rsa, null, md5, ignore};
-suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
- {rsa, null, sha, ignore};
-suite_definition(?TLS_RSA_WITH_RC4_128_MD5) -> % ok
- {rsa, rc4_128, md5, no_export};
-suite_definition(?TLS_RSA_WITH_RC4_128_SHA) -> % ok
- {rsa, rc4_128, sha, no_export};
-%% suite_definition(?TLS_RSA_WITH_IDEA_CBC_SHA) -> % unsupported
-%% {rsa, idea_cbc, sha, no_export};
-suite_definition(?TLS_RSA_WITH_DES_CBC_SHA) -> % ok
- {rsa, des_cbc, sha, no_export};
+ {null, null, null};
+%% suite_definition(?TLS_RSA_WITH_NULL_MD5) ->
+%% {rsa, null, md5};
+%% suite_definition(?TLS_RSA_WITH_NULL_SHA) ->
+%% {rsa, null, sha};
+suite_definition(?TLS_RSA_WITH_RC4_128_MD5) ->
+ {rsa, rc4_128, md5};
+suite_definition(?TLS_RSA_WITH_RC4_128_SHA) ->
+ {rsa, rc4_128, sha};
+%% suite_definition(?TLS_RSA_WITH_IDEA_CBC_SHA) ->
+%% {rsa, idea_cbc, sha};
+suite_definition(?TLS_RSA_WITH_DES_CBC_SHA) ->
+ {rsa, des_cbc, sha};
suite_definition(?TLS_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {rsa, '3des_ede_cbc', sha, no_export};
-suite_definition(?TLS_DH_DSS_WITH_DES_CBC_SHA) ->
- {dh_dss, des_cbc, sha, no_export};
-suite_definition(?TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA) ->
- {dh_dss, '3des_ede_cbc', sha, no_export};
-suite_definition(?TLS_DH_RSA_WITH_DES_CBC_SHA) ->
- {dh_rsa, des_cbc, sha, no_export};
-suite_definition(?TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {dh_rsa, '3des_ede_cbc', sha, no_export};
+ {rsa, '3des_ede_cbc', sha};
suite_definition(?TLS_DHE_DSS_WITH_DES_CBC_SHA) ->
- {dhe_dss, des_cbc, sha, no_export};
+ {dhe_dss, des_cbc, sha};
suite_definition(?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_dss, '3des_ede_cbc', sha, no_export};
+ {dhe_dss, '3des_ede_cbc', sha};
suite_definition(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
- {dhe_rsa, des_cbc, sha, no_export};
+ {dhe_rsa, des_cbc, sha};
suite_definition(?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA) ->
- {dhe_rsa, '3des_ede_cbc', sha, no_export};
-suite_definition(?TLS_DH_anon_WITH_RC4_128_MD5) ->
- {dh_anon, rc4_128, md5, no_export};
-suite_definition(?TLS_DH_anon_WITH_DES_CBC_SHA) ->
- {dh_anon, des40_cbc, sha, no_export};
-suite_definition(?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA) ->
- {dh_anon, '3des_ede_cbc', sha, no_export};
+ {dhe_rsa, '3des_ede_cbc', sha};
%%% TSL V1.1 AES suites
-suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA) -> % ok
- {rsa, aes_128_cbc, sha, ignore};
-suite_definition(?TLS_DH_DSS_WITH_AES_128_CBC_SHA) ->
- {dh_dss, aes_128_cbc, sha, ignore};
-suite_definition(?TLS_DH_RSA_WITH_AES_128_CBC_SHA) ->
- {dh_rsa, aes_128_cbc, sha, ignore};
+suite_definition(?TLS_RSA_WITH_AES_128_CBC_SHA) ->
+ {rsa, aes_128_cbc, sha};
suite_definition(?TLS_DHE_DSS_WITH_AES_128_CBC_SHA) ->
- {dhe_dss, aes_128_cbc, sha, ignore};
+ {dhe_dss, aes_128_cbc, sha};
suite_definition(?TLS_DHE_RSA_WITH_AES_128_CBC_SHA) ->
- {dhe_rsa, aes_128_cbc, sha, ignore};
-suite_definition(?TLS_DH_anon_WITH_AES_128_CBC_SHA) ->
- {dh_anon, aes_128_cbc, sha, ignore};
-suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA) -> % ok
- {rsa, aes_256_cbc, sha, ignore};
-suite_definition(?TLS_DH_DSS_WITH_AES_256_CBC_SHA) ->
- {dh_dss, aes_256_cbc, sha, ignore};
-suite_definition(?TLS_DH_RSA_WITH_AES_256_CBC_SHA) ->
- {dh_rsa, aes_256_cbc, sha, ignore};
+ {dhe_rsa, aes_128_cbc, sha};
+suite_definition(?TLS_RSA_WITH_AES_256_CBC_SHA) ->
+ {rsa, aes_256_cbc, sha};
suite_definition(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
- {dhe_dss, aes_256_cbc, sha, ignore};
+ {dhe_dss, aes_256_cbc, sha};
suite_definition(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
- {dhe_rsa, aes_256_cbc, sha, ignore};
-suite_definition(?TLS_DH_anon_WITH_AES_256_CBC_SHA) ->
- {dh_anon, aes_256_cbc, sha, ignore};
-
-%% TSL V1.1 KRB SUITES
-suite_definition(?TLS_KRB5_WITH_DES_CBC_SHA) ->
- {krb5, des_cbc, sha, ignore};
-suite_definition(?TLS_KRB5_WITH_3DES_EDE_CBC_SHA) ->
- {krb5, '3des_ede_cbc', sha, ignore};
-suite_definition(?TLS_KRB5_WITH_RC4_128_SHA) ->
- {krb5, rc4_128, sha, ignore};
-%% suite_definition(?TLS_KRB5_WITH_IDEA_CBC_SHA) ->
-%% {krb5, idea_cbc, sha, ignore};
-suite_definition(?TLS_KRB5_WITH_DES_CBC_MD5) ->
- {krb5, des_cbc, md5, ignore};
-suite_definition(?TLS_KRB5_WITH_3DES_EDE_CBC_MD5) ->
- {krb5, '3des_ede_cbc', md5, ignore};
-suite_definition(?TLS_KRB5_WITH_RC4_128_MD5) ->
- {krb5, rc4_128, md5, ignore};
-%% suite_definition(?TLS_KRB5_WITH_IDEA_CBC_MD5) ->
-%% {krb5, idea_cbc, md5, ignore};
-
-suite_definition(?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5) ->
- {rsa, rc4_56, md5, export};
-suite_definition(?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5) ->
- {rsa, rc2_cbc_56, md5, export};
-suite_definition(?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA) ->
- {rsa, des_cbc, sha, export};
-suite_definition(?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA) ->
- {dhe_dss, des_cbc, sha, export};
-suite_definition(?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA) ->
- {rsa, rc4_56, sha, export};
-suite_definition(?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA) ->
- {dhe_dss, rc4_56, sha, export};
-suite_definition(?TLS_DHE_DSS_WITH_RC4_128_SHA) ->
- {dhe_dss, rc4_128, sha, export};
-
-%% Export suites TLS 1.0 OR SSLv3-only servers.
-suite_definition(?TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA) ->
- {krb5_export, des40_cbc, sha, export};
-suite_definition(?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA) ->
- {krb5_export, rc2_cbc_40, sha, export};
-suite_definition(?TLS_KRB5_EXPORT_WITH_RC4_40_SHA) ->
- {krb5_export, des40_cbc, sha, export};
-suite_definition(?TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5) ->
- {krb5_export, des40_cbc, md5, export};
-suite_definition(?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5) ->
- {krb5_export, rc2_cbc_40, md5, export};
-suite_definition(?TLS_KRB5_EXPORT_WITH_RC4_40_MD5) ->
- {krb5_export, rc2_cbc_40, md5, export};
-suite_definition(?TLS_RSA_EXPORT_WITH_RC4_40_MD5) -> % ok
- {rsa, rc4_40, md5, export};
-suite_definition(?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5) -> % ok
- {rsa, rc2_cbc_40, md5, export};
-suite_definition(?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
- {rsa, des40_cbc, sha, export};
-suite_definition(?TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA) ->
- {dh_dss, des40_cbc, sha, export};
-suite_definition(?TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
- {dh_rsa, des40_cbc, sha, export};
-suite_definition(?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA) ->
- {dhe_dss, des40_cbc, sha, export};
-suite_definition(?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
- {dhe_rsa, des40_cbc, sha, export};
-suite_definition(?TLS_DH_anon_EXPORT_WITH_RC4_40_MD5) ->
- {dh_anon, rc4_40, md5, export};
-suite_definition(?TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA) ->
- {dh_anon, des40_cbc, sha, export}.
+ {dhe_rsa, aes_256_cbc, sha}.
+
+%%--------------------------------------------------------------------
+-spec suite(erl_cipher_suite()) -> cipher_suite().
+%%
+%% Description: Return TLS cipher suite definition.
+%%--------------------------------------------------------------------
%% TLS v1.1 suites
-suite({rsa, null, md5, ignore}) ->
- ?TLS_RSA_WITH_NULL_MD5;
-suite({rsa, null, sha, ignore}) ->
- ?TLS_RSA_WITH_NULL_SHA;
-suite({rsa, rc4_128, md5, no_export}) ->
+%%suite({rsa, null, md5}) ->
+%% ?TLS_RSA_WITH_NULL_MD5;
+%%suite({rsa, null, sha}) ->
+%% ?TLS_RSA_WITH_NULL_SHA;
+suite({rsa, rc4_128, md5}) ->
?TLS_RSA_WITH_RC4_128_MD5;
-suite({rsa, rc4_128, sha, no_export}) ->
+suite({rsa, rc4_128, sha}) ->
?TLS_RSA_WITH_RC4_128_SHA;
-%% suite({rsa, idea_cbc, sha, no_export}) ->
+%% suite({rsa, idea_cbc, sha}) ->
%% ?TLS_RSA_WITH_IDEA_CBC_SHA;
-suite({rsa, des_cbc, sha, no_export}) ->
+suite({rsa, des_cbc, sha}) ->
?TLS_RSA_WITH_DES_CBC_SHA;
-suite({rsa, '3des_ede_cbc', sha, no_export}) ->
+suite({rsa, '3des_ede_cbc', sha}) ->
?TLS_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({dh_dss, des_cbc, sha, no_export}) ->
- ?TLS_DH_DSS_WITH_DES_CBC_SHA;
-suite({dh_dss, '3des_ede_cbc', sha, no_export}) ->
- ?TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA;
-suite({dh_rsa, des_cbc, sha, no_export}) ->
- ?TLS_DH_RSA_WITH_DES_CBC_SHA;
-suite({dh_rsa, '3des_ede_cbc', sha, no_export}) ->
- ?TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_dss, des_cbc, sha, no_export}) ->
+suite({dhe_dss, des_cbc, sha}) ->
?TLS_DHE_DSS_WITH_DES_CBC_SHA;
-suite({dhe_dss, '3des_ede_cbc', sha, no_export}) ->
+suite({dhe_dss, '3des_ede_cbc', sha}) ->
?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA;
-suite({dhe_rsa, des_cbc, sha, no_export}) ->
+suite({dhe_rsa, des_cbc, sha}) ->
?TLS_DHE_RSA_WITH_DES_CBC_SHA;
-suite({dhe_rsa, '3des_ede_cbc', sha, no_export}) ->
+suite({dhe_rsa, '3des_ede_cbc', sha}) ->
?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA;
-suite({dh_anon, rc4_128, md5, no_export}) ->
- ?TLS_DH_anon_WITH_RC4_128_MD5;
-suite({dh_anon, des40_cbc, sha, no_export}) ->
- ?TLS_DH_anon_WITH_DES_CBC_SHA;
-suite({dh_anon, '3des_ede_cbc', sha, no_export}) ->
- ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
+%% suite({dh_anon, rc4_128, md5}) ->
+%% ?TLS_DH_anon_WITH_RC4_128_MD5;
+%% suite({dh_anon, des40_cbc, sha}) ->
+%% ?TLS_DH_anon_WITH_DES_CBC_SHA;
+%% suite({dh_anon, '3des_ede_cbc', sha}) ->
+%% ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA;
%%% TSL V1.1 AES suites
-suite({rsa, aes_128_cbc, sha, ignore}) ->
+suite({rsa, aes_128_cbc, sha}) ->
?TLS_RSA_WITH_AES_128_CBC_SHA;
-suite({dh_dss, aes_128_cbc, sha, ignore}) ->
- ?TLS_DH_DSS_WITH_AES_128_CBC_SHA;
-suite({dh_rsa, aes_128_cbc, sha, ignore}) ->
- ?TLS_DH_RSA_WITH_AES_128_CBC_SHA;
-suite({dhe_dss, aes_128_cbc, sha, ignore}) ->
+suite({dhe_dss, aes_128_cbc, sha}) ->
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
-suite({dhe_rsa, aes_128_cbc, sha, ignore}) ->
+suite({dhe_rsa, aes_128_cbc, sha}) ->
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA;
-suite({dh_anon, aes_128_cbc, sha, ignore}) ->
- ?TLS_DH_anon_WITH_AES_128_CBC_SHA;
-suite({rsa, aes_256_cbc, sha, ignore}) ->
+%% suite({dh_anon, aes_128_cbc, sha}) ->
+%% ?TLS_DH_anon_WITH_AES_128_CBC_SHA;
+suite({rsa, aes_256_cbc, sha}) ->
?TLS_RSA_WITH_AES_256_CBC_SHA;
-suite({dh_dss, aes_256_cbc, sha, ignore}) ->
- ?TLS_DH_DSS_WITH_AES_256_CBC_SHA;
-suite({dh_rsa, aes_256_cbc, sha, ignore}) ->
- ?TLS_DH_RSA_WITH_AES_256_CBC_SHA;
-suite({dhe_dss, aes_256_cbc, sha, ignore}) ->
+suite({dhe_dss, aes_256_cbc, sha}) ->
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA;
-suite({dhe_rsa, aes_256_cbc, sha, ignore}) ->
- ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
-suite({dh_anon, aes_256_cbc, sha, ignore}) ->
- ?TLS_DH_anon_WITH_AES_256_CBC_SHA;
-
-%% TSL V1.1 KRB SUITES
-suite({krb5, des_cbc, sha, ignore}) ->
- ?TLS_KRB5_WITH_DES_CBC_SHA;
-suite({krb5_cbc, '3des_ede_cbc', sha, ignore}) ->
- ?TLS_KRB5_WITH_3DES_EDE_CBC_SHA;
-suite({krb5, rc4_128, sha, ignore}) ->
- ?TLS_KRB5_WITH_RC4_128_SHA;
-%% suite({krb5_cbc, idea_cbc, sha, ignore}) ->
-%% ?TLS_KRB5_WITH_IDEA_CBC_SHA;
-suite({krb5_cbc, md5, ignore}) ->
- ?TLS_KRB5_WITH_DES_CBC_MD5;
-suite({krb5_ede_cbc, des_cbc, md5, ignore}) ->
- ?TLS_KRB5_WITH_3DES_EDE_CBC_MD5;
-suite({krb5_128, rc4_128, md5, ignore}) ->
- ?TLS_KRB5_WITH_RC4_128_MD5;
-%% suite({krb5, idea_cbc, md5, ignore}) ->
-%% ?TLS_KRB5_WITH_IDEA_CBC_MD5;
-
-%% Export suites TLS 1.0 OR SSLv3-only servers.
-suite({rsa, rc4_40, md5, export}) ->
- ?TLS_RSA_EXPORT_WITH_RC4_40_MD5;
-suite({rsa, rc2_cbc_40, md5, export}) ->
- ?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5;
-suite({rsa, des40_cbc, sha, export}) ->
- ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA;
-suite({rsa, rc4_56, md5, export}) ->
- ?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5;
-suite({rsa, rc2_cbc_56, md5, export}) ->
- ?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5;
-suite({rsa, des_cbc, sha, export}) ->
- ?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA;
-suite({dhe_dss, des_cbc, sha, export}) ->
- ?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA;
-suite({rsa, rc4_56, sha, export}) ->
- ?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA;
-suite({dhe_dss, rc4_56, sha, export}) ->
- ?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA;
-suite({dhe_dss, rc4_128, sha, export}) ->
- ?TLS_DHE_DSS_WITH_RC4_128_SHA;
-suite({krb5_export, des40_cbc, sha, export}) ->
- ?TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA;
-suite({krb5_export, rc2_cbc_40, sha, export}) ->
- ?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA;
-suite({krb5_export, rc4_cbc_40, sha, export}) ->
- ?TLS_KRB5_EXPORT_WITH_RC4_40_SHA;
-suite({krb5_export, des40_cbc, md5, export}) ->
- ?TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5;
-suite({krb5_export, rc2_cbc_40, md5, export}) ->
- ?TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5;
-suite({krb5_export, rc4_cbc_40, md5, export}) ->
- ?TLS_KRB5_EXPORT_WITH_RC4_40_MD5;
-suite({rsa_export, rc4_cbc_40, md5, export}) ->
- ?TLS_RSA_EXPORT_WITH_RC4_40_MD5;
-suite({rsa_export, rc2_cbc_40, md5, export}) ->
- ?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5;
-suite({rsa_export, des40_cbc, sha, export}) ->
- ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA;
-suite({dh_dss_export, des40_cbc, sha, export}) ->
- ?TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA;
-suite({dh_rsa_export, des40_cbc, sha, export}) ->
- ?TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA;
-suite({dhe_dss_export, des40_cbc, sha, export}) ->
- ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA;
-suite({dhe_rsa_export, des40_cbc, sha, export}) ->
- ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA;
-suite({dh_anon_export, rc4_40, md5, export}) ->
- ?TLS_DH_anon_EXPORT_WITH_RC4_40_MD5;
-suite({dh_anon_export, des40_cbc, sha, export}) ->
- ?TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA.
-
+suite({dhe_rsa, aes_256_cbc, sha}) ->
+ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA.
+%% suite({dh_anon, aes_256_cbc, sha}) ->
+%% ?TLS_DH_anon_WITH_AES_256_CBC_SHA.
+%%--------------------------------------------------------------------
+-spec openssl_suite(openssl_cipher_suite()) -> cipher_suite().
+%%
+%% Description: Return TLS cipher suite definition.
+%%--------------------------------------------------------------------
%% translate constants <-> openssl-strings
-%% TODO: Is there a pattern in the nameing
-%% that is useable to make a nicer function defention?
-
openssl_suite("DHE-RSA-AES256-SHA") ->
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA;
openssl_suite("DHE-DSS-AES256-SHA") ->
@@ -514,46 +315,21 @@ openssl_suite("DHE-DSS-AES128-SHA") ->
?TLS_DHE_DSS_WITH_AES_128_CBC_SHA;
openssl_suite("AES128-SHA") ->
?TLS_RSA_WITH_AES_128_CBC_SHA;
-%% TODO: Do we want to support this?
-%% openssl_suite("DHE-DSS-RC4-SHA") ->
-%% ?TLS_DHE_DSS_WITH_RC4_128_SHA;
%%openssl_suite("IDEA-CBC-SHA") ->
%% ?TLS_RSA_WITH_IDEA_CBC_SHA;
openssl_suite("RC4-SHA") ->
?TLS_RSA_WITH_RC4_128_SHA;
openssl_suite("RC4-MD5") ->
?TLS_RSA_WITH_RC4_128_MD5;
-%% TODO: Do we want to support this?
-openssl_suite("EXP1024-RC4-MD5") ->
- ?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5;
-openssl_suite("EXP1024-RC2-CBC-MD5") ->
- ?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5;
-openssl_suite("EXP1024-DES-CBC-SHA") ->
- ?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA;
-openssl_suite("EXP1024-DHE-DSS-DES-CBC-SHA") ->
- ?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA;
-openssl_suite("EXP1024-RC4-SHA") ->
- ?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA;
-openssl_suite("EXP1024-DHE-DSS-RC4-SHA") ->
- ?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA;
-openssl_suite("DHE-DSS-RC4-SHA") ->
- ?TLS_DHE_DSS_WITH_RC4_128_SHA;
-
openssl_suite("EDH-RSA-DES-CBC-SHA") ->
?TLS_DHE_RSA_WITH_DES_CBC_SHA;
openssl_suite("DES-CBC-SHA") ->
- ?TLS_RSA_WITH_DES_CBC_SHA;
-openssl_suite("EXP-EDH-RSA-DES-CBC-SHA") ->
- ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA;
-openssl_suite("EXP-EDH-DSS-DES-CBC-SHA") ->
- ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA;
-openssl_suite("EXP-DES-CBC-SHA") ->
- ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA;
-openssl_suite("EXP-RC2-CBC-MD5") ->
- ?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5;
-openssl_suite("EXP-RC4-MD5") ->
- ?TLS_RSA_EXPORT_WITH_RC4_40_MD5.
-
+ ?TLS_RSA_WITH_DES_CBC_SHA.
+%%--------------------------------------------------------------------
+-spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite().
+%%
+%% Description: Return openssl cipher suite name.
+%%-------------------------------------------------------------------
openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_CBC_SHA) ->
"DHE-RSA-AES256-SHA";
openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_CBC_SHA) ->
@@ -582,37 +358,28 @@ openssl_suite_name(?TLS_DHE_RSA_WITH_DES_CBC_SHA) ->
"EDH-RSA-DES-CBC-SHA";
openssl_suite_name(?TLS_RSA_WITH_DES_CBC_SHA) ->
"DES-CBC-SHA";
-openssl_suite_name(?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
- "EXP-EDH-RSA-DES-CBC-SHA";
-openssl_suite_name(?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA) ->
- "EXP-EDH-DSS-DES-CBC-SHA";
-openssl_suite_name(?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA) ->
- "EXP-DES-CBC-SHA";
-openssl_suite_name(?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5) ->
- "EXP-RC2-CBC-MD5";
-openssl_suite_name(?TLS_RSA_EXPORT_WITH_RC4_40_MD5) ->
- "EXP-RC4-MD5";
-
-openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5) ->
- "EXP1024-RC4-MD5";
-openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5) ->
- "EXP1024-RC2-CBC-MD5";
-openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA) ->
- "EXP1024-DES-CBC-SHA";
-openssl_suite_name(?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA) ->
- "EXP1024-DHE-DSS-DES-CBC-SHA";
-openssl_suite_name(?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA) ->
- "EXP1024-RC4-SHA";
-openssl_suite_name(?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA) ->
- "EXP1024-DHE-DSS-RC4-SHA";
-openssl_suite_name(?TLS_DHE_DSS_WITH_RC4_128_SHA) ->
- "DHE-DSS-RC4-SHA";
-
%% No oppenssl name
openssl_suite_name(Cipher) ->
suite_definition(Cipher).
%%--------------------------------------------------------------------
+-spec filter(undefined | binary(), [cipher_suite()]) -> [cipher_suite()].
+%%
+%% Description: .
+%%-------------------------------------------------------------------
+filter(undefined, Ciphers) ->
+ Ciphers;
+filter(DerCert, Ciphers) ->
+ {ok, OtpCert} = public_key:pkix_decode_cert(DerCert, otp),
+ SigAlg = OtpCert#'OTPCertificate'.signatureAlgorithm,
+ case ssl_certificate:signature_type(SigAlg#'SignatureAlgorithm'.algorithm) of
+ rsa ->
+ filter_rsa(OtpCert, Ciphers -- dsa_signed_suites());
+ dsa ->
+ Ciphers -- rsa_signed_suites()
+ end.
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
@@ -621,15 +388,10 @@ bulk_cipher_algorithm(null) ->
%% Not supported yet
%% bulk_cipher_algorithm(idea_cbc) ->
%% ?IDEA;
-bulk_cipher_algorithm(Cipher) when Cipher == rc2_cbc_40;
- Cipher == rc2_cbc_56 ->
- ?RC2;
-bulk_cipher_algorithm(Cipher) when Cipher == rc4_40;
- Cipher == rc4_56;
- Cipher == rc4_128 ->
+bulk_cipher_algorithm(rc4_128) ->
?RC4;
-bulk_cipher_algorithm(des40_cbc) ->
- ?DES40;
+%% bulk_cipher_algorithm(des40_cbc) ->
+%% ?DES40;
bulk_cipher_algorithm(des_cbc) ->
?DES;
bulk_cipher_algorithm('3des_ede_cbc') ->
@@ -639,14 +401,10 @@ bulk_cipher_algorithm(Cipher) when Cipher == aes_128_cbc;
?AES.
type(Cipher) when Cipher == null;
- Cipher == rc4_40;
- Cipher == rc4_56;
Cipher == rc4_128 ->
?STREAM;
type(Cipher) when Cipher == idea_cbc;
- Cipher == rc2_cbc_40;
- Cipher == rc2_cbc_56;
Cipher == des40_cbc;
Cipher == des_cbc;
Cipher == '3des_ede_cbc';
@@ -659,13 +417,8 @@ key_material(null) ->
key_material(Cipher) when Cipher == idea_cbc;
Cipher == rc4_128 ->
16;
-key_material(Cipher) when Cipher == rc2_cbc_56;
- Cipher == rc4_56 ->
- 7;
-key_material(Cipher) when Cipher == rc2_cbc_40;
- Cipher == rc4_40;
- Cipher == des40_cbc ->
- 5;
+%%key_material(des40_cbc) ->
+%% 5;
key_material(des_cbc) ->
8;
key_material('3des_ede_cbc') ->
@@ -678,10 +431,6 @@ key_material(aes_256_cbc) ->
expanded_key_material(null) ->
0;
expanded_key_material(Cipher) when Cipher == idea_cbc;
- Cipher == rc2_cbc_40;
- Cipher == rc2_cbc_56;
- Cipher == rc4_40;
- Cipher == rc4_56;
Cipher == rc4_128 ->
16;
expanded_key_material(Cipher) when Cipher == des_cbc;
@@ -696,13 +445,9 @@ expanded_key_material(Cipher) when Cipher == aes_128_cbc;
effective_key_bits(null) ->
0;
-effective_key_bits(Cipher) when Cipher == rc2_cbc_40;
- Cipher == rc4_40;
- Cipher == des40_cbc ->
- 40;
-effective_key_bits(Cipher) when Cipher == rc2_cbc_56;
- Cipher == rc4_56;
- Cipher == des_cbc ->
+%%effective_key_bits(des40_cbc) ->
+%% 40;
+effective_key_bits(des_cbc) ->
56;
effective_key_bits(Cipher) when Cipher == idea_cbc;
Cipher == rc4_128;
@@ -714,16 +459,12 @@ effective_key_bits(aes_256_cbc) ->
256.
iv_size(Cipher) when Cipher == null;
- Cipher == rc4_40;
- Cipher == rc4_56;
Cipher == rc4_128 ->
0;
iv_size(Cipher) ->
block_size(Cipher).
block_size(Cipher) when Cipher == idea_cbc;
- Cipher == rc2_cbc_40;
- Cipher == rc2_cbc_56;
Cipher == des40_cbc;
Cipher == des_cbc;
Cipher == '3des_ede_cbc' ->
@@ -763,9 +504,12 @@ generic_stream_cipher_from_bin(T, HashSz) ->
#generic_stream_cipher{content=Content,
mac=Mac}.
-check_padding(_GBC) ->
- ok.
+is_correct_padding(_, {3, 0}) ->
+ true;
+is_correct_padding(#generic_block_cipher{padding_length = Len, padding = Padding}, _) ->
+ list_to_binary(lists:duplicate(Len, Len)) == Padding.
+
get_padding(Length, BlockSize) ->
get_padding_aux(BlockSize, Length rem BlockSize).
@@ -782,3 +526,53 @@ next_iv(Bin, IV) ->
<<_:FirstPart/binary, NextIV:IVSz/binary>> = Bin,
NextIV.
+rsa_signed_suites() ->
+ dhe_rsa_suites() ++ rsa_suites().
+
+dhe_rsa_suites() ->
+ [?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_RSA_WITH_DES_CBC_SHA].
+
+rsa_suites() ->
+ [?TLS_RSA_WITH_AES_256_CBC_SHA,
+ ?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_RSA_WITH_AES_128_CBC_SHA,
+ %%?TLS_RSA_WITH_IDEA_CBC_SHA,
+ ?TLS_RSA_WITH_RC4_128_SHA,
+ ?TLS_RSA_WITH_RC4_128_MD5,
+ ?TLS_RSA_WITH_DES_CBC_SHA].
+
+dsa_signed_suites() ->
+ dhe_dss_suites().
+
+dhe_dss_suites() ->
+ [?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA].
+
+filter_rsa(OtpCert, RsaCiphers) ->
+ TBSCert = OtpCert#'OTPCertificate'.tbsCertificate,
+ TBSExtensions = TBSCert#'OTPTBSCertificate'.extensions,
+ Extensions = ssl_certificate:extensions_list(TBSExtensions),
+ case ssl_certificate:select_extension(?'id-ce-keyUsage', Extensions) of
+ undefined ->
+ RsaCiphers;
+ #'Extension'{extnValue = KeyUse} ->
+ Result = filter_rsa_suites(keyEncipherment,
+ KeyUse, RsaCiphers, rsa_suites()),
+ filter_rsa_suites(digitalSignature,
+ KeyUse, Result, dhe_rsa_suites())
+ end.
+
+filter_rsa_suites(Use, KeyUse, CipherSuits, RsaSuites) ->
+ case ssl_certificate:is_valid_key_usage(KeyUse, Use) of
+ true ->
+ CipherSuits;
+ false ->
+ CipherSuits -- RsaSuites
+ end.
+
+
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index 4304c501b7..19de709d9c 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
@@ -26,6 +26,14 @@
-ifndef(ssl_cipher).
-define(ssl_cipher, true).
+-type cipher() :: null |rc4_128 | idea_cbc | des40_cbc | des_cbc | '3des_ede_cbc'
+ | aes_128_cbc | aes_256_cbc.
+-type hash() :: sha | md5.
+-type erl_cipher_suite() :: {key_algo(), cipher(), hash()}.
+-type cipher_suite() :: binary().
+-type cipher_enum() :: integer().
+-type openssl_cipher_suite() :: string().
+
%%% SSL cipher protocol %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-define(CHANGE_CIPHER_SPEC_PROTO, 1). % _PROTO to not clash with
% SSL record protocol
@@ -57,7 +65,7 @@
%% TLS_NULL_WITH_NULL_NULL = { 0x00,0x00 };
-define(TLS_NULL_WITH_NULL_NULL, <<?BYTE(16#00), ?BYTE(16#00)>>).
-%%% The following CipherSuite definitions require that the server
+%%% The following cipher suite definitions require that the server
%%% provide an RSA certificate that can be used for key exchange. The
%%% server may request either an RSA or a DSS signature-capable
%%% certificate in the certificate request message.
@@ -68,24 +76,15 @@
%% TLS_RSA_WITH_NULL_SHA = { 0x00,0x02 };
-define(TLS_RSA_WITH_NULL_SHA, <<?BYTE(16#00), ?BYTE(16#02)>>).
-%% TLS_RSA_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x03 };
--define(TLS_RSA_EXPORT_WITH_RC4_40_MD5, <<?BYTE(16#00), ?BYTE(16#03)>>).
-
%% TLS_RSA_WITH_RC4_128_MD5 = { 0x00,0x04 };
-define(TLS_RSA_WITH_RC4_128_MD5, <<?BYTE(16#00), ?BYTE(16#04)>>).
%% TLS_RSA_WITH_RC4_128_SHA = { 0x00,0x05 };
-define(TLS_RSA_WITH_RC4_128_SHA, <<?BYTE(16#00), ?BYTE(16#05)>>).
-%% TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x06 };
--define(TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5, <<?BYTE(16#00), ?BYTE(16#06)>>).
-
%% TLS_RSA_WITH_IDEA_CBC_SHA = { 0x00,0x07 };
-define(TLS_RSA_WITH_IDEA_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#07)>>).
-%% TLS_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x08 };
--define(TLS_RSA_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#08)>>).
-
%% TLS_RSA_WITH_DES_CBC_SHA = { 0x00,0x09 };
-define(TLS_RSA_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#09)>>).
@@ -106,51 +105,33 @@
%%% provided by the client must use the parameters (group and
%%% generator) described by the server.
-%% TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0B };
--define(TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0B)>>).
-
%% TLS_DH_DSS_WITH_DES_CBC_SHA = { 0x00,0x0C };
-define(TLS_DH_DSS_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0C)>>).
%% TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x0D };
-define(TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0D)>>).
-%% TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x0E };
--define(TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0E)>>).
-
%% TLS_DH_RSA_WITH_DES_CBC_SHA = { 0x00,0x0F };
-define(TLS_DH_RSA_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#0F)>>).
%% TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x10 };
-define(TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#10)>>).
-%% TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x11 };
--define(TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#11)>>).
-
%% TLS_DHE_DSS_WITH_DES_CBC_SHA = { 0x00,0x12 };
-define(TLS_DHE_DSS_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#12)>>).
%% TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = { 0x00,0x13 };
-define(TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#13)>>).
-%% TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x14 };
--define(TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#14)>>).
-
%% TLS_DHE_RSA_WITH_DES_CBC_SHA = { 0x00,0x15 };
-define(TLS_DHE_RSA_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#15)>>).
%% TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = { 0x00,0x16 };
-define(TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#16)>>).
-%% TLS_DH_anon_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x17 };
--define(TLS_DH_anon_EXPORT_WITH_RC4_40_MD5, <<?BYTE(16#00), ?BYTE(16#17)>>).
-
%% TLS_DH_anon_WITH_RC4_128_MD5 = { 0x00,0x18 };
-define(TLS_DH_anon_WITH_RC4_128_MD5, <<?BYTE(16#00),?BYTE(16#18)>>).
-%% TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA = { 0x00,0x19 };
--define(TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#19)>>).
-
%% TLS_DH_anon_WITH_DES_CBC_SHA = { 0x00,0x1A };
-define(TLS_DH_anon_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#1A)>>).
@@ -222,32 +203,9 @@
%% TLS_KRB5_WITH_IDEA_CBC_MD5 = { 0x00,0x25 };
-define(TLS_KRB5_WITH_IDEA_CBC_MD5, <<?BYTE(16#00), ?BYTE(16#25)>>).
-%% TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA = { 0x00,0x26 };
--define(TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA, <<?BYTE(16#00), ?BYTE(16#26)>>).
-
-%% TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA = { 0x00,0x27 };
--define(TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA, <<?BYTE(16#00), ?BYTE(16#27)>>).
-
-%% TLS_KRB5_EXPORT_WITH_RC4_40_SHA = { 0x00,0x28 };
--define(TLS_KRB5_EXPORT_WITH_RC4_40_SHA, <<?BYTE(16#00), ?BYTE(16#28)>>).
-
-%% TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5 = { 0x00,0x29 };
--define(TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5, <<?BYTE(16#00), ?BYTE(16#29)>>).
-
-%% TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5 = { 0x00,0x2A };
--define(TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5, <<?BYTE(16#00), ?BYTE(16#2A)>>).
-
-%% TLS_KRB5_EXPORT_WITH_RC4_40_MD5 = { 0x00,0x2B };
--define(TLS_KRB5_EXPORT_WITH_RC4_40_MD5, <<?BYTE(16#00), ?BYTE(16#2B)>>).
-
-%% Additional TLS ciphersuites from draft-ietf-tls-56-bit-ciphersuites-00.txt
-
--define(TLS_RSA_EXPORT1024_WITH_RC4_56_MD5, <<?BYTE(16#00), ?BYTE(16#60)>>).
--define(TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5, <<?BYTE(16#00), ?BYTE(16#61)>>).
--define(TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#62)>>).
--define(TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA, <<?BYTE(16#00), ?BYTE(16#63)>>).
--define(TLS_RSA_EXPORT1024_WITH_RC4_56_SHA, <<?BYTE(16#00), ?BYTE(16#64)>>).
--define(TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA, <<?BYTE(16#00), ?BYTE(16#65)>>).
--define(TLS_DHE_DSS_WITH_RC4_128_SHA, <<?BYTE(16#00), ?BYTE(16#66)>>).
+%% RFC 5746 - Not a real cipher suite used to signal empty "renegotiation_info" extension
+%% to avoid handshake failure from old servers that do not ignore
+%% hello extension data as they should.
+-define(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, <<?BYTE(16#00), ?BYTE(16#FF)>>).
-endif. % -ifdef(ssl_cipher).
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index e04e2dea42..5b4b129e30 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -39,7 +39,8 @@
-include_lib("public_key/include/public_key.hrl").
%% Internal application API
--export([send/2, send/3, recv/3, connect/7, accept/6, close/1, shutdown/2,
+-export([send/2, recv/3, connect/7, ssl_accept/6, handshake/2,
+ socket_control/3, close/1, shutdown/2,
new_user/2, get_opts/2, set_opts/2, info/1, session_info/1,
peer_certificate/1, sockname/1, peername/1, renegotiation/1]).
@@ -57,19 +58,21 @@
transport_cb, % atom() - callback module
data_tag, % atom() - ex tcp.
close_tag, % atom() - ex tcp_closed
+ error_tag, % atom() - ex tcp_error
host, % string() | ipadress()
port, % integer()
socket, % socket()
ssl_options, % #ssl_options{}
socket_options, % #socket_options{}
connection_states, % #connection_states{} from ssl_record.hrl
+ tls_packets = [], % Not yet handled decode ssl/tls packets.
tls_record_buffer, % binary() buffer of incomplete records
tls_handshake_buffer, % binary() buffer of incomplete handshakes
%% {{md5_hash, sha_hash}, {prev_md5, prev_sha}} (binary())
tls_handshake_hashes, % see above
tls_cipher_texts, % list() received but not deciphered yet
own_cert, % binary()
- session, % #session{} from ssl_handshake.erl
+ session, % #session{} from ssl_handshake.hrl
session_cache, %
session_cache_cb, %
negotiated_version, % #protocol_version{}
@@ -85,9 +88,8 @@
from, % term(), where to reply
bytes_to_read, % integer(), # bytes to read in passive mode
user_data_buffer, % binary()
-%% tls_buffer, % Keeps a lookahead one packet if available
log_alert, % boolean()
- renegotiation, % boolean()
+ renegotiation, % {boolean(), From | internal | peer}
recv_during_renegotiation, %boolean()
send_queue % queue()
}).
@@ -96,46 +98,90 @@
#'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME,
base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}).
+-type state_name() :: hello | abbreviated | certify | cipher | connection.
+-type gen_fsm_state_return() :: {next_state, state_name(), #state{}} |
+ {next_state, state_name(), #state{}, timeout()} |
+ {stop, term(), #state{}}.
+
%%====================================================================
%% Internal application API
%%====================================================================
%%--------------------------------------------------------------------
-%% Function:
+-spec send(pid(), iolist()) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Sends data over the ssl connection
%%--------------------------------------------------------------------
send(Pid, Data) ->
- sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, infinity).
-send(Pid, Data, Timeout) ->
- sync_send_all_state_event(Pid, {application_data, erlang:iolist_to_binary(Data)}, Timeout).
+ sync_send_all_state_event(Pid, {application_data,
+ erlang:iolist_to_binary(Data)}, infinity).
+
%%--------------------------------------------------------------------
-%% Function:
+-spec recv(pid(), integer(), timeout()) ->
+ {ok, binary() | list()} | {error, reason()}.
%%
-%% Description:
+%% Description: Receives data when active = false
%%--------------------------------------------------------------------
recv(Pid, Length, Timeout) ->
sync_send_all_state_event(Pid, {recv, Length}, Timeout).
%%--------------------------------------------------------------------
-%% Function:
+-spec connect(host(), port_num(), port(), list(), pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
%%
-%% Description:
+%% Description: Connect to a ssl server.
%%--------------------------------------------------------------------
connect(Host, Port, Socket, Options, User, CbInfo, Timeout) ->
- start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
- Timeout).
+ try start_fsm(client, Host, Port, Socket, Options, User, CbInfo,
+ Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+%%--------------------------------------------------------------------
+-spec ssl_accept(port_num(), port(), list(), pid(), tuple(), timeout()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Performs accept on a ssl listen socket. e.i. performs
+%% ssl handshake.
+%%--------------------------------------------------------------------
+ssl_accept(Port, Socket, Opts, User, CbInfo, Timeout) ->
+ try start_fsm(server, "localhost", Port, Socket, Opts, User,
+ CbInfo, Timeout)
+ catch
+ exit:{noproc, _} ->
+ {error, ssl_not_started}
+ end.
+
%%--------------------------------------------------------------------
-%% Function:
+-spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Starts ssl handshake.
%%--------------------------------------------------------------------
-accept(Port, Socket, Opts, User, CbInfo, Timeout) ->
- start_fsm(server, "localhost", Port, Socket, Opts, User,
- CbInfo, Timeout).
+handshake(#sslsocket{pid = Pid}, Timeout) ->
+ case sync_send_all_state_event(Pid, start, Timeout) of
+ connected ->
+ ok;
+ Error ->
+ Error
+ end.
+%--------------------------------------------------------------------
+-spec socket_control(port(), pid(), atom()) ->
+ {ok, #sslsocket{}} | {error, reason()}.
+%%
+%% Description: Set the ssl process to own the accept socket
+%%--------------------------------------------------------------------
+socket_control(Socket, Pid, CbModule) ->
+ case CbModule:controlling_process(Socket, Pid) of
+ ok ->
+ {ok, sslsocket(Pid)};
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
%%--------------------------------------------------------------------
-%% Function:
+-spec close(pid()) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Close a ssl connection
%%--------------------------------------------------------------------
close(ConnectionPid) ->
case sync_send_all_state_event(ConnectionPid, close) of
@@ -146,90 +192,89 @@ close(ConnectionPid) ->
end.
%%--------------------------------------------------------------------
-%% Function:
+-spec shutdown(pid(), atom()) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Same as gen_tcp:shutdown/2
%%--------------------------------------------------------------------
shutdown(ConnectionPid, How) ->
sync_send_all_state_event(ConnectionPid, {shutdown, How}).
-
%%--------------------------------------------------------------------
-%% Function:
+-spec new_user(pid(), pid()) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Changes process that receives the messages when active = true
+%% or once.
%%--------------------------------------------------------------------
new_user(ConnectionPid, User) ->
sync_send_all_state_event(ConnectionPid, {new_user, User}).
%%--------------------------------------------------------------------
-%% Function:
+-spec sockname(pid()) -> {ok, {tuple(), port_num()}} | {error, reason()}.
%%
-%% Description:
+%% Description: Same as inet:sockname/1
%%--------------------------------------------------------------------
sockname(ConnectionPid) ->
sync_send_all_state_event(ConnectionPid, sockname).
%%--------------------------------------------------------------------
-%% Function:
+-spec peername(pid()) -> {ok, {tuple(), port_num()}} | {error, reason()}.
%%
-%% Description:
+%% Description: Same as inet:peername/1
%%--------------------------------------------------------------------
peername(ConnectionPid) ->
sync_send_all_state_event(ConnectionPid, peername).
%%--------------------------------------------------------------------
-%% Function:
+-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}.
%%
-%% Description:
+%% Description: Same as inet:getopts/2
%%--------------------------------------------------------------------
-get_opts({ListenSocket, {_SslOpts, SockOpts}, _}, OptTags) ->
- get_socket_opts(ListenSocket, OptTags, SockOpts, []);
get_opts(ConnectionPid, OptTags) ->
sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}).
%%--------------------------------------------------------------------
-%% Function:
+-spec set_opts(pid(), list()) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Same as inet:setopts/2
%%--------------------------------------------------------------------
set_opts(ConnectionPid, Options) ->
sync_send_all_state_event(ConnectionPid, {set_opts, Options}).
%%--------------------------------------------------------------------
-%% Function:
+-spec info(pid()) -> {ok, {atom(), tuple()}} | {error, reason()}.
%%
-%% Description:
+%% Description: Returns ssl protocol and cipher used for the connection
%%--------------------------------------------------------------------
info(ConnectionPid) ->
sync_send_all_state_event(ConnectionPid, info).
%%--------------------------------------------------------------------
-%% Function:
+-spec session_info(pid()) -> {ok, list()} | {error, reason()}.
%%
-%% Description:
+%% Description: Returns info about the ssl session
%%--------------------------------------------------------------------
session_info(ConnectionPid) ->
sync_send_all_state_event(ConnectionPid, session_info).
%%--------------------------------------------------------------------
-%% Function:
+-spec peer_certificate(pid()) -> {ok, binary()} | {error, reason()}.
%%
-%% Description:
+%% Description: Returns the peer cert
%%--------------------------------------------------------------------
peer_certificate(ConnectionPid) ->
sync_send_all_state_event(ConnectionPid, peer_certificate).
%%--------------------------------------------------------------------
-%% Function:
+-spec renegotiation(pid()) -> ok | {error, reason()}.
%%
-%% Description:
+%% Description: Starts a renegotiation of the ssl session.
%%--------------------------------------------------------------------
renegotiation(ConnectionPid) ->
- send_all_state_event(ConnectionPid, renegotiate).
+ sync_send_all_state_event(ConnectionPid, renegotiate).
%%====================================================================
%% ssl_connection_sup API
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+-spec start_link(atom(), host(), port_num(), port(), list(), pid(), tuple()) ->
+ {ok, pid()} | ignore | {error, reason()}.
%%
%% Description: Creates a gen_fsm process which calls Module:init/1 to
%% initialize. To ensure a synchronized start-up procedure, this function
@@ -243,20 +288,19 @@ start_link(Role, Host, Port, Socket, Options, User, CbInfo) ->
%% gen_fsm callbacks
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, StateName, State} |
-%% {ok, StateName, State, Timeout} |
-%% ignore |
-%% {stop, StopReason}
+-spec init(list()) -> {ok, state_name(), #state{}}
+ | {ok, state_name(), #state{}, timeout()} |
+ ignore | {stop, term()}.
%% Description:Whenever a gen_fsm is started using gen_fsm:start/[3,4] or
%% gen_fsm:start_link/3,4, this function is called by the new process to
%% initialize.
%%--------------------------------------------------------------------
-init([Role, Host, Port, Socket, {SSLOpts, _} = Options,
+init([Role, Host, Port, Socket, {SSLOpts0, _} = Options,
User, CbInfo]) ->
State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo),
Hashes0 = ssl_handshake:init_hashes(),
- try ssl_init(SSLOpts, Role) of
+ try ssl_init(SSLOpts0, Role) of
{ok, Ref, CacheRef, OwnCert, Key, DHParams} ->
State = State0#state{tls_handshake_hashes = Hashes0,
own_cert = OwnCert,
@@ -269,101 +313,98 @@ init([Role, Host, Port, Socket, {SSLOpts, _} = Options,
throw:Error ->
{stop, Error}
end.
-
+
%%--------------------------------------------------------------------
-%% Function:
-%% state_name(Event, State) -> {next_state, NextStateName, NextState}|
-%% {next_state, NextStateName,
-%% NextState, Timeout} |
-%% {stop, Reason, NewState}
+%% -spec state_name(event(), #state{}) -> gen_fsm_state_return()
%%
%% Description:There should be one instance of this function for each
%% possible state name. Whenever a gen_fsm receives an event sent
%% using gen_fsm:send_event/2, the instance of this function with the
%% same name as the current state name StateName is called to handle
%% the event. It is also called if a timeout occurs.
+%%
+%%--------------------------------------------------------------------
+-spec hello(start | #hello_request{} | #client_hello{} | #server_hello{} | term(),
+ #state{}) -> gen_fsm_state_return().
%%--------------------------------------------------------------------
-hello(socket_control, #state{host = Host, port = Port, role = client,
- ssl_options = SslOpts,
- transport_cb = Transport, socket = Socket,
- connection_states = ConnectionStates}
+hello(start, #state{host = Host, port = Port, role = client,
+ ssl_options = SslOpts,
+ transport_cb = Transport, socket = Socket,
+ connection_states = ConnectionStates,
+ own_cert = Cert,
+ renegotiation = {Renegotiation, _}}
= State0) ->
+
Hello = ssl_handshake:client_hello(Host, Port,
- ConnectionStates, SslOpts),
+ ConnectionStates,
+ SslOpts, Cert,
+ Renegotiation),
+
Version = Hello#client_hello.client_version,
Hashes0 = ssl_handshake:init_hashes(),
{BinMsg, CS2, Hashes1} =
encode_handshake(Hello, Version, ConnectionStates, Hashes0),
Transport:send(Socket, BinMsg),
- State = State0#state{connection_states = CS2,
+ State1 = State0#state{connection_states = CS2,
negotiated_version = Version, %% Requested version
session =
#session{session_id = Hello#client_hello.session_id,
is_resumable = false},
tls_handshake_hashes = Hashes1},
- {next_state, hello, next_record(State)};
+ {Record, State} = next_record(State1),
+ next_state(hello, Record, State);
-hello(socket_control, #state{role = server} = State) ->
- {next_state, hello, next_record(State)};
+hello(start, #state{role = server} = State0) ->
+ {Record, State} = next_record(State0),
+ next_state(hello, Record, State);
-hello(#hello_request{}, #state{role = client} = State) ->
- {next_state, hello, State};
+hello(#hello_request{}, #state{role = client} = State0) ->
+ {Record, State} = next_record(State0),
+ next_state(hello, Record, State);
hello(#server_hello{cipher_suite = CipherSuite,
compression_method = Compression} = Hello,
- #state{session = Session0 = #session{session_id = OldId},
+ #state{session = #session{session_id = OldId},
connection_states = ConnectionStates0,
role = client,
negotiated_version = ReqVersion,
- host = Host, port = Port,
- session_cache = Cache,
- session_cache_cb = CacheCb} = State0) ->
+ renegotiation = {Renegotiation, _},
+ ssl_options = SslOptions} = State0) ->
- {Version, NewId, ConnectionStates1} =
- ssl_handshake:hello(Hello, ConnectionStates0),
-
- {KeyAlgorithm, _, _, _} =
- ssl_cipher:suite_definition(CipherSuite),
-
- PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
-
- State = State0#state{key_algorithm = KeyAlgorithm,
- negotiated_version = Version,
- connection_states = ConnectionStates1,
- premaster_secret = PremasterSecret},
-
- case ssl_session:is_new(OldId, NewId) of
- true ->
- Session = Session0#session{session_id = NewId,
- cipher_suite = CipherSuite,
- compression_method = Compression},
- {next_state, certify,
- next_record(State#state{session = Session})};
- false ->
- Session = CacheCb:lookup(Cache, {{Host, Port}, NewId}),
- case ssl_handshake:master_secret(Version, Session,
- ConnectionStates1, client) of
- {_, ConnectionStates2} ->
- {next_state, abbreviated,
- next_record(State#state{
- connection_states = ConnectionStates2,
- session = Session})};
- #alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State),
- {stop, normal, State}
- end
+ case ssl_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of
+ {Version, NewId, ConnectionStates} ->
+ {KeyAlgorithm, _, _} =
+ ssl_cipher:suite_definition(CipherSuite),
+
+ PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm),
+
+ State = State0#state{key_algorithm = KeyAlgorithm,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
+ premaster_secret = PremasterSecret},
+
+ case ssl_session:is_new(OldId, NewId) of
+ true ->
+ handle_new_session(NewId, CipherSuite, Compression, State);
+ false ->
+ handle_resumed_session(NewId, State#state{connection_states = ConnectionStates})
+ end;
+ #alert{} = Alert ->
+ handle_own_alert(Alert, ReqVersion, hello, State0),
+ {stop, normal, State0}
end;
hello(Hello = #client_hello{client_version = ClientVersion},
State = #state{connection_states = ConnectionStates0,
port = Port, session = Session0,
- session_cache = Cache,
+ renegotiation = {Renegotiation, _},
+ session_cache = Cache,
session_cache_cb = CacheCb,
- ssl_options = SslOpts}) ->
+ ssl_options = SslOpts,
+ own_cert = Cert}) ->
- case ssl_handshake:hello(Hello, {Port, SslOpts,
- Session0, Cache, CacheCb,
- ConnectionStates0}) of
+ case ssl_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
+ ConnectionStates0, Cert}, Renegotiation) of
{Version, {Type, Session}, ConnectionStates} ->
do_server_hello(Type, State#state{connection_states =
ConnectionStates,
@@ -372,49 +413,67 @@ hello(Hello = #client_hello{client_version = ClientVersion},
#alert{} = Alert ->
handle_own_alert(Alert, ClientVersion, hello, State),
{stop, normal, State}
- end.
+ end;
-abbreviated(socket_control, #state{role = server} = State) ->
- {next_state, abbreviated, State};
-abbreviated(#hello_request{}, State) ->
- {next_state, certify, State};
+hello(Msg, State) ->
+ handle_unexpected_message(Msg, hello, State).
+%%--------------------------------------------------------------------
+-spec abbreviated(#hello_request{} | #finished{} | term(),
+ #state{}) -> gen_fsm_state_return().
+%%--------------------------------------------------------------------
+abbreviated(#hello_request{}, State0) ->
+ {Record, State} = next_record(State0),
+ next_state(hello, Record, State);
-abbreviated(Finished = #finished{},
+abbreviated(#finished{verify_data = Data} = Finished,
#state{role = server,
negotiated_version = Version,
tls_handshake_hashes = Hashes,
- session = #session{master_secret = MasterSecret}} = State) ->
+ session = #session{master_secret = MasterSecret},
+ connection_states = ConnectionStates0} =
+ State) ->
case ssl_handshake:verify_connection(Version, Finished, client,
MasterSecret, Hashes) of
- verified ->
- ack_connection(State),
- next_state_connection(State);
+ verified ->
+ ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
+ next_state_connection(abbreviated,
+ ack_connection(State#state{connection_states = ConnectionStates}));
#alert{} = Alert ->
handle_own_alert(Alert, Version, abbreviated, State),
{stop, normal, State}
end;
-abbreviated(Finished = #finished{},
+abbreviated(#finished{verify_data = Data} = Finished,
#state{role = client, tls_handshake_hashes = Hashes0,
session = #session{master_secret = MasterSecret},
- negotiated_version = Version} = State) ->
+ negotiated_version = Version,
+ connection_states = ConnectionStates0} = State) ->
case ssl_handshake:verify_connection(Version, Finished, server,
MasterSecret, Hashes0) of
verified ->
- {ConnectionStates, Hashes} = finalize_client_handshake(State),
- ack_connection(State),
- next_state_connection(State#state{tls_handshake_hashes = Hashes,
- connection_states =
- ConnectionStates});
+ ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
+ {ConnectionStates, Hashes} =
+ finalize_handshake(State#state{connection_states = ConnectionStates1}, abbreviated),
+ next_state_connection(abbreviated,
+ ack_connection(State#state{tls_handshake_hashes = Hashes,
+ connection_states =
+ ConnectionStates}));
#alert{} = Alert ->
handle_own_alert(Alert, Version, abbreviated, State),
{stop, normal, State}
- end.
+ end;
-certify(socket_control, #state{role = server} = State) ->
- {next_state, certify, State};
-certify(#hello_request{}, State) ->
- {next_state, certify, State};
+abbreviated(Msg, State) ->
+ handle_unexpected_message(Msg, abbreviated, State).
+
+%%--------------------------------------------------------------------
+-spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} |
+ #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
+ #state{}) -> gen_fsm_state_return().
+%%--------------------------------------------------------------------
+certify(#hello_request{}, State0) ->
+ {Record, State} = next_record(State0),
+ next_state(hello, Record, State);
certify(#certificate{asn1_certificates = []},
#state{role = server, negotiated_version = Version,
@@ -429,17 +488,19 @@ certify(#certificate{asn1_certificates = []},
#state{role = server,
ssl_options = #ssl_options{verify = verify_peer,
fail_if_no_peer_cert = false}} =
- State) ->
- {next_state, certify,
- next_record(State#state{client_certificate_requested = false})};
+ State0) ->
+ {Record, State} = next_record(State0#state{client_certificate_requested = false}),
+ next_state(certify, Record, State);
certify(#certificate{} = Cert,
#state{negotiated_version = Version,
+ role = Role,
cert_db_ref = CertDbRef,
ssl_options = Opts} = State) ->
case ssl_handshake:certify(Cert, CertDbRef, Opts#ssl_options.depth,
Opts#ssl_options.verify,
- Opts#ssl_options.verify_fun) of
+ Opts#ssl_options.verify_fun,
+ Opts#ssl_options.validate_extensions_fun, Role) of
{PeerCert, PublicKeyInfo} ->
handle_peer_cert(PeerCert, PublicKeyInfo,
State#state{client_certificate_requested = false});
@@ -451,28 +512,24 @@ certify(#certificate{} = Cert,
certify(#server_key_exchange{} = KeyExchangeMsg,
#state{role = client, negotiated_version = Version,
key_algorithm = Alg} = State0)
- when Alg == dhe_dss; Alg == dhe_rsa ->%%Not imp:Alg == dh_anon;Alg == krb5 ->
+ when Alg == dhe_dss; Alg == dhe_rsa ->
case handle_server_key(KeyExchangeMsg, State0) of
- #state{} = State ->
- {next_state, certify, next_record(State)};
+ #state{} = State1 ->
+ {Record, State} = next_record(State1),
+ next_state(certify, Record, State);
#alert{} = Alert ->
handle_own_alert(Alert, Version, certify_server_keyexchange,
State0),
{stop, normal, State0}
end;
-certify(#server_key_exchange{},
- State = #state{role = client, negotiated_version = Version,
- key_algorithm = Alg})
- when Alg == rsa; Alg == dh_dss; Alg == dh_rsa ->
- Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE),
- handle_own_alert(Alert, Version, certify_server_key_exchange, State),
- {stop, normal, State};
-
-certify(#certificate_request{}, State) ->
- NewState = State#state{client_certificate_requested = true},
- {next_state, certify, next_record(NewState)};
+certify(#server_key_exchange{} = Msg,
+ #state{role = client, key_algorithm = rsa} = State) ->
+ handle_unexpected_message(Msg, certify_server_keyexchange, State);
+certify(#certificate_request{}, State0) ->
+ {Record, State} = next_record(State0#state{client_certificate_requested = true}),
+ next_state(certify, Record, State);
%% Master secret was determined with help of server-key exchange msg
certify(#server_hello_done{},
@@ -512,17 +569,12 @@ certify(#server_hello_done{},
{stop, normal, State0}
end;
-certify(#client_key_exchange{},
- State = #state{role = server,
- client_certificate_requested = true,
- ssl_options = #ssl_options{fail_if_no_peer_cert = true},
- negotiated_version = Version}) ->
+certify(#client_key_exchange{} = Msg,
+ #state{role = server,
+ client_certificate_requested = true,
+ ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State) ->
%% We expect a certificate here
- Alert = ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE),
- handle_own_alert(Alert, Version,
- certify_server_waiting_certificate, State),
- {stop, normal, State};
-
+ handle_unexpected_message(Msg, certify_client_key_exchange, State);
certify(#client_key_exchange{exchange_keys
= #encrypted_premaster_secret{premaster_secret
@@ -537,9 +589,10 @@ certify(#client_key_exchange{exchange_keys
ConnectionStates0, server) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
- State = State0#state{connection_states = ConnectionStates,
+ State1 = State0#state{connection_states = ConnectionStates,
session = Session},
- {next_state, cipher, next_record(State)};
+ {Record, State} = next_record(State1),
+ next_state(cipher, Record, State);
#alert{} = Alert ->
handle_own_alert(Alert, Version,
certify_client_key_exchange, State0),
@@ -571,21 +624,29 @@ certify(#client_key_exchange{exchange_keys = #client_diffie_hellman_public{
case ssl_handshake:master_secret(Version, PremasterSecret,
ConnectionStates0, Role) of
{MasterSecret, ConnectionStates} ->
- State = State0#state{session =
+ State1 = State0#state{session =
Session#session{master_secret
= MasterSecret},
connection_states = ConnectionStates},
- {next_state, cipher, next_record(State)};
+
+ {Record, State} = next_record(State1),
+ next_state(cipher, Record, State);
#alert{} = Alert ->
handle_own_alert(Alert, Version,
certify_client_key_exchange, State0),
{stop, normal, State0}
- end.
+ end;
+
+certify(Msg, State) ->
+ handle_unexpected_message(Msg, certify, State).
-cipher(socket_control, #state{role = server} = State) ->
- {next_state, cipher, State};
-cipher(#hello_request{}, State) ->
- {next_state, cipher, State};
+%%--------------------------------------------------------------------
+-spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(),
+ #state{}) -> gen_fsm_state_return().
+%%--------------------------------------------------------------------
+cipher(#hello_request{}, State0) ->
+ {Record, State} = next_record(State0),
+ next_state(hello, Record, State);
cipher(#certificate_verify{signature = Signature},
#state{role = server,
@@ -594,193 +655,87 @@ cipher(#certificate_verify{signature = Signature},
session = #session{master_secret = MasterSecret},
key_algorithm = Algorithm,
tls_handshake_hashes = Hashes
- } = State) ->
+ } = State0) ->
case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
Version, MasterSecret,
Algorithm, Hashes) of
valid ->
- {next_state, cipher, next_record(State)};
+ {Record, State} = next_record(State0),
+ next_state(cipher, Record, State);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, cipher, State),
- {stop, normal, State}
+ handle_own_alert(Alert, Version, cipher, State0),
+ {stop, normal, State0}
end;
-cipher(#finished{} = Finished,
- State = #state{negotiated_version = Version,
- host = Host,
- port = Port,
- role = Role,
- session = #session{master_secret = MasterSecret}
- = Session0,
- tls_handshake_hashes = Hashes}) ->
-
+cipher(#finished{verify_data = Data} = Finished,
+ #state{negotiated_version = Version,
+ host = Host,
+ port = Port,
+ role = Role,
+ session = #session{master_secret = MasterSecret}
+ = Session0,
+ tls_handshake_hashes = Hashes0} = State) ->
+
case ssl_handshake:verify_connection(Version, Finished,
opposite_role(Role),
- MasterSecret, Hashes) of
+ MasterSecret, Hashes0) of
verified ->
- ack_connection(State),
Session = register_session(Role, Host, Port, Session0),
- case Role of
- client ->
- next_state_connection(State#state{session = Session});
- server ->
- {NewConnectionStates, NewHashes} =
- finalize_server_handshake(State#state{
- session = Session}),
- NewState =
- State#state{connection_states = NewConnectionStates,
- session = Session,
- tls_handshake_hashes = NewHashes},
- next_state_connection(NewState)
- end;
+ cipher_role(Role, Data, Session, State);
#alert{} = Alert ->
handle_own_alert(Alert, Version, cipher, State),
{stop, normal, State}
- end.
+ end;
-connection(socket_control, #state{role = server} = State) ->
- {next_state, connection, State};
-connection(#hello_request{}, State = #state{host = Host, port = Port,
- socket = Socket,
- ssl_options = SslOpts,
- negotiated_version = Version,
- transport_cb = Transport,
- connection_states = ConnectionStates0,
- tls_handshake_hashes = Hashes0}) ->
+cipher(Msg, State) ->
+ handle_unexpected_message(Msg, cipher, State).
+%%--------------------------------------------------------------------
+-spec connection(#hello_request{} | #client_hello{} | term(),
+ #state{}) -> gen_fsm_state_return().
+%%--------------------------------------------------------------------
+connection(#hello_request{}, #state{host = Host, port = Port,
+ socket = Socket,
+ ssl_options = SslOpts,
+ negotiated_version = Version,
+ own_cert = Cert,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0,
+ renegotiation = {Renegotiation, _},
+ tls_handshake_hashes = Hashes0} = State0) ->
+
Hello = ssl_handshake:client_hello(Host, Port,
- ConnectionStates0, SslOpts),
+ ConnectionStates0, SslOpts, Cert, Renegotiation),
+
{BinMsg, ConnectionStates1, Hashes1} =
encode_handshake(Hello, Version, ConnectionStates0, Hashes0),
Transport:send(Socket, BinMsg),
- {next_state, hello, next_record(State#state{connection_states =
- ConnectionStates1,
- tls_handshake_hashes = Hashes1,
- renegotiation = true})};
+ {Record, State} = next_record(State0#state{connection_states =
+ ConnectionStates1,
+ tls_handshake_hashes = Hashes1}),
+ next_state(hello, Record, State);
connection(#client_hello{} = Hello, #state{role = server} = State) ->
- hello(Hello, State).
+ hello(Hello, State);
+connection(Msg, State) ->
+ handle_unexpected_message(Msg, connection, State).
%%--------------------------------------------------------------------
-%% Function:
-%% handle_event(Event, StateName, State) -> {next_state, NextStateName,
-%% NextState} |
-%% {next_state, NextStateName,
-%% NextState, Timeout} |
-%% {stop, Reason, NewState}
+-spec handle_event(term(), state_name(), #state{}) -> gen_fsm_state_return().
+%%
%% Description: Whenever a gen_fsm receives an event sent using
%% gen_fsm:send_all_state_event/2, this function is called to handle
-%% the event.
+%% the event. Not currently used!
%%--------------------------------------------------------------------
-handle_event(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
- StateName,
- State0 = #state{key_algorithm = KeyAlg,
- tls_handshake_buffer = Buf0,
- negotiated_version = Version}) ->
- Handle =
- fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) ->
- %% This message should not be included in handshake
- %% message hashes. Starts new handshake (renegotiation)
- Hs0 = ssl_handshake:init_hashes(),
- ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0});
- ({#hello_request{} = Packet, _}, {next_state, SName, State}) ->
- %% This message should not be included in handshake
- %% message hashes. If allready in negotiation it will be ignored!
- ?MODULE:SName(Packet, State);
- ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) ->
- Hs0 = ssl_handshake:init_hashes(),
- Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
- ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1,
- renegotiation = true});
- ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_hashes=Hs0}}) ->
- Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
- ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1});
- (_, StopState) -> StopState
- end,
- try
- {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version),
- Start = {next_state, StateName, State0#state{tls_handshake_buffer = Buf}},
- lists:foldl(Handle, Start, Packets)
- catch throw:#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State0),
- {stop, normal, State0}
- end;
-
-handle_event(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data},
- StateName, State0) ->
- case application_data(Data, State0) of
- Stop = {stop,_,_} ->
- Stop;
- State ->
- {next_state, StateName, State}
- end;
-
-handle_event(#ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} =
- _ChangeCipher,
- StateName,
- State = #state{connection_states = ConnectionStates0}) ->
- ?DBG_TERM(_ChangeCipher),
- ConnectionStates1 =
- ssl_record:activate_pending_connection_state(ConnectionStates0, read),
- {next_state, StateName,
- next_record(State#state{connection_states = ConnectionStates1})};
-
-handle_event(#ssl_tls{type = ?ALERT, fragment = Data}, StateName, State) ->
- Alerts = decode_alerts(Data),
- ?DBG_TERM(Alerts),
- [alert_event(A) || A <- Alerts],
- {next_state, StateName, State};
-
-handle_event(#alert{level = ?FATAL} = Alert, connection,
- #state{from = From, user_application = {_Mon, Pid},
- log_alert = Log,
- host = Host, port = Port, session = Session,
- role = Role, socket_options = Opts} = State) ->
- invalidate_session(Role, Host, Port, Session),
- log_alert(Log, connection, Alert),
- alert_user(Opts#socket_options.active, Pid, From, Alert, Role),
- {stop, normal, State};
-handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- connection, #state{from = From,
- role = Role,
- user_application = {_Mon, Pid},
- socket_options = Opts} = State) ->
- alert_user(Opts#socket_options.active, Pid, From, Alert, Role),
- {stop, normal, State};
-
-handle_event(#alert{level = ?FATAL} = Alert, StateName,
- #state{from = From, host = Host, port = Port, session = Session,
- log_alert = Log, role = Role} = State) ->
- invalidate_session(Role, Host, Port, Session),
- log_alert(Log, StateName, Alert),
- alert_user(From, Alert, Role),
- {stop, normal, State};
-handle_event(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
- _, #state{from = From, role = Role} = State) ->
- alert_user(From, Alert, Role),
- {stop, normal, State};
-handle_event(#alert{level = ?WARNING} = Alert, StateName,
- #state{log_alert = Log} = State) ->
- log_alert(Log, StateName, Alert),
-%%TODO: Could be user_canceled or no_negotiation should the latter be
- %% treated as fatal?!
- {next_state, StateName, next_record(State)};
-
-handle_event(renegotiate, connection, State) ->
- renegotiate(State);
-handle_event(renegotiate, StateName, State) ->
- %% Already in renegotiate ignore
+handle_event(_Event, StateName, State) ->
{next_state, StateName, State}.
+
%%--------------------------------------------------------------------
-%% Function:
-%% handle_sync_event(Event, From, StateName,
-%% State) -> {next_state, NextStateName, NextState} |
-%% {next_state, NextStateName, NextState,
-%% Timeout} |
-%% {reply, Reply, NextStateName, NextState}|
-%% {reply, Reply, NextStateName, NextState,
-%% Timeout} |
-%% {stop, Reason, NewState} |
-%% {stop, Reason, Reply, NewState}
+-spec handle_sync_event(term(), from(), state_name(), #state{}) ->
+ gen_fsm_state_return() |
+ {reply, reply(), state_name(), #state{}} |
+ {reply, reply(), state_name(), #state{}, timeout()} |
+ {stop, reason(), reply(), #state{}}.
+%%
%% Description: Whenever a gen_fsm receives an event sent using
%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle
%% the event.
@@ -812,7 +767,8 @@ handle_sync_event({application_data, Data0}, From, connection,
ok
end,
renegotiate(State#state{connection_states = ConnectionStates,
- send_queue = queue:in_r({From, RestData}, SendQueue)})
+ send_queue = queue:in_r({From, RestData}, SendQueue),
+ renegotiation = {true, internal}})
end
catch throw:Error ->
{reply, Error, connection, State}
@@ -821,27 +777,53 @@ handle_sync_event({application_data, Data}, From, StateName,
#state{send_queue = Queue} = State) ->
%% In renegotiation priorities handshake, send data when handshake is finished
{next_state, StateName, State#state{send_queue = queue:in({From, Data}, Queue)}};
-handle_sync_event(started, From, StateName, State) ->
+
+handle_sync_event(start, From, hello, State) ->
+ hello(start, State#state{from = From});
+
+%% The two clauses below could happen if a server upgrades a socket in
+%% active mode. Note that in this case we are lucky that
+%% controlling_process has been evalueated before receiving handshake
+%% messages from client. The server should put the socket in passive
+%% mode before telling the client that it is willing to upgrade
+%% and before calling ssl:ssl_accept/2. These clauses are
+%% here to make sure it is the users problem and not owers if
+%% they upgrade a active socket.
+handle_sync_event(start, _, connection, State) ->
+ {reply, connected, connection, State};
+handle_sync_event(start, From, StateName, State) ->
{next_state, StateName, State#state{from = From}};
-handle_sync_event(close, From, _StateName, State) ->
- {stop, normal, ok, State#state{from = From}};
+handle_sync_event(close, _, _StateName, State) ->
+ {stop, normal, ok, State};
-handle_sync_event({shutdown, How}, From, StateName,
- #state{transport_cb = CbModule,
+handle_sync_event({shutdown, How0}, _, StateName,
+ #state{transport_cb = Transport,
+ negotiated_version = Version,
+ connection_states = ConnectionStates,
socket = Socket} = State) ->
- case CbModule:shutdown(Socket, How) of
+ case How0 of
+ How when How == write; How == both ->
+ Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY),
+ {BinMsg, _} =
+ encode_alert(Alert, Version, ConnectionStates),
+ Transport:send(Socket, BinMsg);
+ _ ->
+ ok
+ end,
+
+ case Transport:shutdown(Socket, How0) of
ok ->
{reply, ok, StateName, State};
Error ->
- {stop, normal, Error, State#state{from = From}}
+ {stop, normal, Error, State}
end;
handle_sync_event({recv, N}, From, connection = StateName, State0) ->
passive_receive(State0#state{bytes_to_read = N, from = From}, StateName);
%% Doing renegotiate wait with handling request until renegotiate is
-%% finished. Will be handled by next_state_connection/1.
+%% finished. Will be handled by next_state_connection/2.
handle_sync_event({recv, N}, From, StateName, State) ->
{next_state, StateName, State#state{bytes_to_read = N, from = From,
recv_during_renegotiation = true}};
@@ -879,7 +861,13 @@ handle_sync_event({set_opts, Opts0}, _From, StateName,
{reply, ok, StateName, State1};
Buffer =:= <<>>, Opts1#socket_options.active =:= false ->
%% Need data, set active once
- {reply, ok, StateName, next_record_if_active(State1)};
+ {Record, State2} = next_record_if_active(State1),
+ case next_state(StateName, Record, State2) of
+ {next_state, StateName, State} ->
+ {reply, ok, StateName, State};
+ {stop, Reason, State} ->
+ {stop, Reason, State}
+ end;
Buffer =:= <<>> ->
%% Active once already set
{reply, ok, StateName, State1};
@@ -887,10 +875,21 @@ handle_sync_event({set_opts, Opts0}, _From, StateName,
case application_data(<<>>, State1) of
Stop = {stop,_,_} ->
Stop;
- State ->
- {reply, ok, StateName, State}
+ {Record, State2} ->
+ case next_state(StateName, Record, State2) of
+ {next_state, StateName, State} ->
+ {reply, ok, StateName, State};
+ {stop, Reason, State} ->
+ {stop, Reason, State}
+ end
end
- end;
+ end;
+
+handle_sync_event(renegotiate, From, connection, State) ->
+ renegotiate(State#state{renegotiation = {true, From}});
+
+handle_sync_event(renegotiate, _, StateName, State) ->
+ {reply, {error, already_renegotiating}, StateName, State};
handle_sync_event(info, _, StateName,
#state{negotiated_version = Version,
@@ -913,61 +912,28 @@ handle_sync_event(peer_certificate, _, StateName,
{reply, {ok, Cert}, StateName, State}.
%%--------------------------------------------------------------------
-%% Function:
-%% handle_info(Info,StateName,State)-> {next_state, NextStateName, NextState}|
-%% {next_state, NextStateName, NextState,
-%% Timeout} |
-%% {stop, Reason, NewState}
+-spec handle_info(msg(),state_name(), #state{}) ->
+ {next_state, state_name(), #state{}}|
+ {next_state, state_name(), #state{}, timeout()} |
+ {stop, reason(), #state{}}.
+%%
%% Description: This function is called by a gen_fsm when it receives any
%% other message than a synchronous or asynchronous event
%% (or a system message).
%%--------------------------------------------------------------------
%% raw data from TCP, unpack records
-handle_info({Protocol, _, Data}, StateName, State =
+handle_info({Protocol, _, Data}, StateName,
#state{data_tag = Protocol,
- negotiated_version = Version,
- tls_record_buffer = Buf0,
- tls_cipher_texts = CT0}) ->
- case ssl_record:get_tls_records(Data, Buf0) of
- {Records, Buf1} ->
- CT1 = CT0 ++ Records,
- {next_state, StateName,
- next_record(State#state{tls_record_buffer = Buf1,
- tls_cipher_texts = CT1})};
+ negotiated_version = Version} = State0) ->
+ case next_tls_record(Data, State0) of
+ {Record, State} ->
+ next_state(StateName, Record, State);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, StateName, State),
- {stop, normal, State}
+ handle_own_alert(Alert, Version, StateName, State0),
+ {stop, normal, State0}
end;
-%% %% This is the code for {packet,ssl} removed because it was slower
-%% %% than handling it in erlang.
-%% handle_info(Data = #ssl_tls{}, StateName,
-%% State = #state{tls_buffer = Buffer,
-%% socket = Socket,
-%% connection_states = ConnectionStates0}) ->
-%% case Buffer of
-%% buffer ->
-%% {next_state, StateName, State#state{tls_buffer = [Data]}};
-%% continue ->
-%% inet:setopts(Socket, [{active,once}]),
-%% {Plain, ConnectionStates} =
-%% ssl_record:decode_cipher_text(Data, ConnectionStates0),
-%% gen_fsm:send_all_state_event(self(), Plain),
-%% {next_state, StateName,
-%% State#state{tls_buffer = buffer,
-%% connection_states = ConnectionStates}};
-%% List when is_list(List) ->
-%% {next_state, StateName,
-%% State#state{tls_buffer = Buffer ++ [Data]}}
-%% end;
-
-%% handle_info(CloseMsg = {_, Socket}, StateName0,
-%% #state{socket = Socket,tls_buffer = [Msg]} = State0) ->
-%% %% Hmm we have a ssl_tls msg buffered, handle that first
-%% %% and it proberbly is a close alert
-%% {next_state, StateName0, State0#state{tls_buffer=[Msg,{ssl_close,CloseMsg}]}};
-
handle_info({CloseTag, Socket}, _StateName,
#state{socket = Socket, close_tag = CloseTag,
negotiated_version = Version, host = Host,
@@ -986,36 +952,60 @@ handle_info({CloseTag, Socket}, _StateName,
?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), Role),
{stop, normal, State};
+handle_info({ErrorTag, Socket, econnaborted}, StateName,
+ #state{socket = Socket, from = User, role = Role,
+ error_tag = ErrorTag} = State) when StateName =/= connection ->
+ alert_user(User, ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Role),
+ {stop, normal, State};
+
+handle_info({ErrorTag, Socket, Reason}, _,
+ #state{socket = Socket, from = User,
+ role = Role, error_tag = ErrorTag} = State) ->
+ Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]),
+ error_logger:info_report(Report),
+ alert_user(User, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role),
+ {stop, normal, State};
+
handle_info({'DOWN', MonitorRef, _, _, _}, _,
State = #state{user_application={MonitorRef,_Pid}}) ->
{stop, normal, State};
-handle_info(A, StateName, State) ->
- io:format("SSL: Bad info (state ~w): ~w\n", [StateName, A]),
- {stop, bad_info, State}.
+handle_info(Msg, StateName, State) ->
+ Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [Msg]),
+ error_logger:info_report(Report),
+ {next_state, StateName, State}.
%%--------------------------------------------------------------------
-%% Function: terminate(Reason, StateName, State) -> void()
+-spec terminate(reason(), state_name(), #state{}) -> term().
+%%
%% Description:This function is called by a gen_fsm when it is about
%% to terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_fsm terminates with
%% Reason. The return value is ignored.
%%--------------------------------------------------------------------
-terminate(_Reason, connection, _S=#state{negotiated_version = Version,
+terminate(_Reason, connection, #state{negotiated_version = Version,
connection_states = ConnectionStates,
transport_cb = Transport,
- socket = Socket}) ->
+ socket = Socket, send_queue = SendQueue,
+ renegotiation = Renegotiate}) ->
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
{BinAlert, _} = encode_alert(?ALERT_REC(?WARNING,?CLOSE_NOTIFY),
Version, ConnectionStates),
Transport:send(Socket, BinAlert),
+ workaround_transport_delivery_problems(Socket, Transport),
Transport:close(Socket);
-terminate(_Reason, _StateName, _S=#state{transport_cb = Transport,
- socket = Socket}) ->
- Transport:close(Socket),
- ok.
+terminate(_Reason, _StateName, #state{transport_cb = Transport,
+ socket = Socket, send_queue = SendQueue,
+ renegotiation = Renegotiate}) ->
+ notify_senders(SendQueue),
+ notify_renegotiater(Renegotiate),
+ workaround_transport_delivery_problems(Socket, Transport),
+ Transport:close(Socket).
%%--------------------------------------------------------------------
-%% Function:
+-spec code_change(term(), state_name(), #state{}, list()) -> {ok, state_name(), #state{}}.
+%%
%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
@@ -1025,23 +1015,19 @@ code_change(_OldVsn, StateName, State, _Extra) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_} = CbInfo,
+start_fsm(Role, Host, Port, Socket, Opts, User, {CbModule, _,_, _} = CbInfo,
Timeout) ->
- case ssl_connection_sup:start_child([Role, Host, Port, Socket,
- Opts, User, CbInfo]) of
- {ok, Pid} ->
- CbModule:controlling_process(Socket, Pid),
- send_event(Pid, socket_control),
- case sync_send_all_state_event(Pid, started, Timeout) of
- connected ->
- {ok, sslsocket(Pid)};
- {error, Reason} ->
- {error, Reason}
- end;
- {error, Reason} ->
- {error, Reason}
+ try
+ {ok, Pid} = ssl_connection_sup:start_child([Role, Host, Port, Socket,
+ Opts, User, CbInfo]),
+ {ok, SslSocket} = socket_control(Socket, Pid, CbModule),
+ ok = handshake(SslSocket, Timeout),
+ {ok, SslSocket}
+ catch
+ error:{badmatch, {error, _} = Error} ->
+ Error
end.
-
+
ssl_init(SslOpts, Role) ->
{ok, CertDbRef, CacheRef, OwnCert} = init_certificates(SslOpts, Role),
PrivateKey =
@@ -1056,16 +1042,9 @@ init_certificates(#ssl_options{cacertfile = CACertFile,
case ssl_manager:connection_init(CACertFile, Role) of
{ok, CertDbRef, CacheRef} ->
init_certificates(CertDbRef, CacheRef, CertFile, Role);
- {error, {badmatch, _Error}} ->
- Report = io_lib:format("SSL: Error ~p Initializing: ~p ~n",
- [_Error, CACertFile]),
- error_logger:error_report(Report),
- throw(ecacertfile);
- {error, _Error} ->
- Report = io_lib:format("SSL: Error ~p Initializing: ~p ~n",
- [_Error, CACertFile]),
- error_logger:error_report(Report),
- throw(ecacertfile)
+ {error, Reason} ->
+ handle_file_error(?LINE, error, Reason, CACertFile, ecacertfile,
+ erlang:get_stacktrace())
end.
init_certificates(CertDbRef, CacheRef, CertFile, client) ->
@@ -1081,70 +1060,60 @@ init_certificates(CertDbRef, CacheRef, CertFile, server) ->
[OwnCert] = ssl_certificate:file_to_certificats(CertFile),
{ok, CertDbRef, CacheRef, OwnCert}
catch
- _E:{badmatch, _R={error,_}} ->
- Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n",
- [?LINE, _E,_R, CertFile,
- erlang:get_stacktrace()]),
- error_logger:error_report(Report),
- throw(ecertfile);
- _E:_R ->
- Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n",
- [?LINE, _E,_R, CertFile,
- erlang:get_stacktrace()]),
- error_logger:error_report(Report),
- throw(ecertfile)
+ Error:Reason ->
+ handle_file_error(?LINE, Error, Reason, CertFile, ecertfile,
+ erlang:get_stacktrace())
end.
init_private_key(undefined, "", _Password, client) ->
undefined;
init_private_key(undefined, KeyFile, Password, _) ->
- try
- {ok, List} = ssl_manager:cache_pem_file(KeyFile),
- [Der] = [Der || Der = {PKey, _ , _} <- List,
- PKey =:= rsa_private_key orelse
- PKey =:= dsa_private_key],
- {ok, Decoded} = public_key:decode_private_key(Der,Password),
- Decoded
- catch
- _E:{badmatch, _R={error,_}} ->
- Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n",
- [?LINE, _E,_R, KeyFile,
- erlang:get_stacktrace()]),
- error_logger:error_report(Report),
- throw(ekeyfile);
- _E:_R ->
- Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n",
- [?LINE, _E,_R, KeyFile,
- erlang:get_stacktrace()]),
- error_logger:error_report(Report),
- throw(ekeyfile)
+ case ssl_manager:cache_pem_file(KeyFile) of
+ {ok, List} ->
+ [Der] = [Der || Der = {PKey, _ , _} <- List,
+ PKey =:= rsa_private_key orelse
+ PKey =:= dsa_private_key],
+ {ok, Decoded} = public_key:decode_private_key(Der,Password),
+ Decoded;
+ {error, Reason} ->
+ handle_file_error(?LINE, error, Reason, KeyFile, ekeyfile,
+ erlang:get_stacktrace())
end;
+
init_private_key(PrivateKey, _, _,_) ->
PrivateKey.
+handle_file_error(Line, Error, {badmatch, Reason}, File, Throw, Stack) ->
+ file_error(Line, Error, Reason, File, Throw, Stack);
+handle_file_error(Line, Error, Reason, File, Throw, Stack) ->
+ file_error(Line, Error, Reason, File, Throw, Stack).
+
+file_error(Line, Error, Reason, File, Throw, Stack) ->
+ Report = io_lib:format("SSL: ~p: ~p:~p ~s~n ~p~n",
+ [Line, Error, Reason, File, Stack]),
+ error_logger:error_report(Report),
+ throw(Throw).
+
init_diffie_hellman(_, client) ->
undefined;
init_diffie_hellman(undefined, _) ->
?DEFAULT_DIFFIE_HELLMAN_PARAMS;
init_diffie_hellman(DHParamFile, server) ->
- {ok, List} = ssl_manager:cache_pem_file(DHParamFile),
- case [Der || Der = {dh_params, _ , _} <- List] of
- [Der] ->
- {ok, Decoded} = public_key:decode_dhparams(Der),
- Decoded;
- [] ->
- ?DEFAULT_DIFFIE_HELLMAN_PARAMS
+ case ssl_manager:cache_pem_file(DHParamFile) of
+ {ok, List} ->
+ case [Der || Der = {dh_params, _ , _} <- List] of
+ [Der] ->
+ {ok, Decoded} = public_key:decode_dhparams(Der),
+ Decoded;
+ [] ->
+ ?DEFAULT_DIFFIE_HELLMAN_PARAMS
+ end;
+ {error, Reason} ->
+ handle_file_error(?LINE, error, Reason, DHParamFile, edhfile, erlang:get_stacktrace())
end.
-send_event(FsmPid, Event) ->
- gen_fsm:send_event(FsmPid, Event).
-
-
-send_all_state_event(FsmPid, Event) ->
- gen_fsm:send_all_state_event(FsmPid, Event).
-
sync_send_all_state_event(FsmPid, Event) ->
- sync_send_all_state_event(FsmPid, Event, ?DEFAULT_TIMEOUT).
+ sync_send_all_state_event(FsmPid, Event, infinity).
sync_send_all_state_event(FsmPid, Event, Timeout) ->
try gen_fsm:sync_send_all_state_event(FsmPid, Event, Timeout)
@@ -1154,50 +1123,21 @@ sync_send_all_state_event(FsmPid, Event, Timeout) ->
exit:{timeout, _} ->
{error, timeout};
exit:{normal, _} ->
+ {error, closed};
+ exit:{shutdown, _} ->
{error, closed}
end.
-%% Events: #alert{}
-alert_event(Alert) ->
- send_all_state_event(self(), Alert).
-
-
-%% TODO: This clause is not yet supported. Do we need
-%% to support it, not supported by openssl!
-handle_peer_cert(PeerCert, {Algorithm, _, _} = PublicKeyInfo,
- #state{negotiated_version = Version,
- session = Session} =
- State0) when Algorithm == dh_rsa;
- Algorithm == dh_dss ->
- case public_key:pkix_is_fixed_dh_cert(PeerCert) of
- true ->
- %% TODO: extract DH-params from cert and save
- %% Keys and SharedSecret in state.
- %% {P, G, ServerPublicDhKey}= extract .....
- %% Keys = {ClientDhPublicKey, _} =
- %% public_key:gen_key(#'DHParameter'{prime = P, base = G}),
- %%SharedSecret = dh_shared_secret(ServerPublicDhKey,
- %% ClientDhPublicKey,
- %% [P, G]),
- %% ssl_handshake:master_secret ...
- State = State0#state{session =
- Session#session{peer_certificate
- = PeerCert},
- public_key_info = PublicKeyInfo},
- {next_state, certify, next_record(State)};
-
- false ->
- Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE),
- handle_own_alert(Alert, Version, certify_certificate, State0),
- {stop, normal, State0}
- end;
-
+%% We do currently not support cipher suites that use fixed DH.
+%% If we want to implement that we should add a code
+%% here to extract DH parameters form cert.
handle_peer_cert(PeerCert, PublicKeyInfo,
#state{session = Session} = State0) ->
- State = State0#state{session =
+ State1 = State0#state{session =
Session#session{peer_certificate = PeerCert},
public_key_info = PublicKeyInfo},
- {next_state, certify, next_record(State)}.
+ {Record, State} = next_record(State1),
+ next_state(certify, Record, State).
certify_client(#state{client_certificate_requested = true, role = client,
connection_states = ConnectionStates0,
@@ -1229,46 +1169,52 @@ verify_client_cert(#state{client_certificate_requested = true, role = client,
case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
Version, KeyAlg,
PrivateKey, Hashes0) of
- ignore -> %% No key or cert or fixed_diffie_hellman
- State;
- Verified ->
+ #certificate_verify{} = Verified ->
{BinVerified, ConnectionStates1, Hashes1} =
encode_handshake(Verified, KeyAlg, Version,
ConnectionStates0, Hashes0),
Transport:send(Socket, BinVerified),
State#state{connection_states = ConnectionStates1,
- tls_handshake_hashes = Hashes1}
+ tls_handshake_hashes = Hashes1};
+ ignore ->
+ State;
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, certify, State)
+
end;
verify_client_cert(#state{client_certificate_requested = false} = State) ->
State.
do_server_hello(Type, #state{negotiated_version = Version,
session = Session,
- connection_states = ConnectionStates0}
+ connection_states = ConnectionStates0,
+ renegotiation = {Renegotiation, _}}
= State0) when is_atom(Type) ->
ServerHello =
ssl_handshake:server_hello(Session#session.session_id, Version,
- ConnectionStates0),
- State = server_hello(ServerHello, State0),
+ ConnectionStates0, Renegotiation),
+ State1 = server_hello(ServerHello, State0),
case Type of
new ->
- do_server_hello(ServerHello, State);
+ do_server_hello(ServerHello, State1);
resumed ->
+ ConnectionStates1 = State1#state.connection_states,
case ssl_handshake:master_secret(Version, Session,
- ConnectionStates0, server) of
- {_, ConnectionStates1} ->
- State1 = State#state{connection_states=ConnectionStates1,
- session = Session},
+ ConnectionStates1, server) of
+ {_, ConnectionStates2} ->
+ State2 = State1#state{connection_states=ConnectionStates2,
+ session = Session},
{ConnectionStates, Hashes} =
- finalize_server_handshake(State1),
- Resumed = State1#state{connection_states =
- ConnectionStates,
- tls_handshake_hashes = Hashes},
- {next_state, abbreviated, next_record(Resumed)};
+ finalize_handshake(State2, abbreviated),
+ State3 = State2#state{connection_states =
+ ConnectionStates,
+ tls_handshake_hashes = Hashes},
+ {Record, State} = next_record(State3),
+ next_state(abbreviated, Record, State);
#alert{} = Alert ->
- handle_own_alert(Alert, Version, hello, State),
- {stop, normal, State}
+ handle_own_alert(Alert, Version, hello, State1),
+ {stop, normal, State1}
end
end;
@@ -1279,32 +1225,60 @@ do_server_hello(#server_hello{cipher_suite = CipherSuite,
negotiated_version = Version} = State0) ->
try server_certify_and_key_exchange(State0) of
#state{} = State1 ->
- State = server_hello_done(State1),
+ State2 = server_hello_done(State1),
Session =
Session0#session{session_id = SessionId,
cipher_suite = CipherSuite,
compression_method = Compression},
- {next_state, certify, State#state{session = Session}}
+ {Record, State} = next_record(State2#state{session = Session}),
+ next_state(certify, Record, State)
catch
#alert{} = Alert ->
handle_own_alert(Alert, Version, hello, State0),
{stop, normal, State0}
end.
+handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0} = State0) ->
+ Session = Session0#session{session_id = NewId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression},
+ {Record, State} = next_record(State0#state{session = Session}),
+ next_state(certify, Record, State).
+
+handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
+ negotiated_version = Version,
+ host = Host, port = Port,
+ session_cache = Cache,
+ session_cache_cb = CacheCb} = State0) ->
+ Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
+ case ssl_handshake:master_secret(Version, Session,
+ ConnectionStates0, client) of
+ {_, ConnectionStates1} ->
+ {Record, State} =
+ next_record(State0#state{
+ connection_states = ConnectionStates1,
+ session = Session}),
+ next_state(abbreviated, Record, State);
+ #alert{} = Alert ->
+ handle_own_alert(Alert, Version, hello, State0),
+ {stop, normal, State0}
+ end.
+
+
client_certify_and_key_exchange(#state{negotiated_version = Version} =
State0) ->
try do_client_certify_and_key_exchange(State0) of
State1 = #state{} ->
- {ConnectionStates, Hashes} = finalize_client_handshake(State1),
- State = State1#state{connection_states = ConnectionStates,
+ {ConnectionStates, Hashes} = finalize_handshake(State1, certify),
+ State2 = State1#state{connection_states = ConnectionStates,
%% Reinitialize
client_certificate_requested = false,
tls_handshake_hashes = Hashes},
- {next_state, cipher, next_record(State)}
-
+ {Record, State} = next_record(State2),
+ next_state(cipher, Record, State)
catch
#alert{} = Alert ->
- handle_own_alert(Alert, Version, certify_foo, State0),
+ handle_own_alert(Alert, Version, client_certify_and_key_exchange, State0),
{stop, normal, State0}
end.
@@ -1324,8 +1298,7 @@ server_hello(ServerHello, #state{transport_cb = Transport,
connection_states = ConnectionStates0,
tls_handshake_hashes = Hashes0} = State) ->
CipherSuite = ServerHello#server_hello.cipher_suite,
- {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
- %% Version = ServerHello#server_hello.server_version, TODO ska kontrolleras
+ {KeyAlgorithm, _, _} = ssl_cipher:suite_definition(CipherSuite),
{BinMsg, ConnectionStates1, Hashes1} =
encode_handshake(ServerHello, Version, ConnectionStates0, Hashes0),
Transport:send(Socket, BinMsg),
@@ -1337,17 +1310,16 @@ server_hello_done(#state{transport_cb = Transport,
socket = Socket,
negotiated_version = Version,
connection_states = ConnectionStates,
- tls_handshake_hashes = Hashes} = State0) ->
+ tls_handshake_hashes = Hashes} = State) ->
HelloDone = ssl_handshake:server_hello_done(),
-
+
{BinHelloDone, NewConnectionStates, NewHashes} =
encode_handshake(HelloDone, Version, ConnectionStates, Hashes),
Transport:send(Socket, BinHelloDone),
- State = State0#state{connection_states = NewConnectionStates,
- tls_handshake_hashes = NewHashes},
- next_record(State).
-
+ State#state{connection_states = NewConnectionStates,
+ tls_handshake_hashes = NewHashes}.
+
certify_server(#state{transport_cb = Transport,
socket = Socket,
negotiated_version = Version,
@@ -1368,18 +1340,8 @@ certify_server(#state{transport_cb = Transport,
throw(Alert)
end.
-key_exchange(#state{role = server, key_algorithm = Algo} = State)
- when Algo == rsa;
- Algo == dh_dss;
- Algo == dh_rsa ->
- State;
-
-key_exchange(#state{role = server, key_algorithm = rsa_export} = State) ->
- %% TODO when the public key in the server certificate is
- %% less than or equal to 512 bits in length dont send key_exchange
- %% but do it otherwise
+key_exchange(#state{role = server, key_algorithm = rsa} = State) ->
State;
-
key_exchange(#state{role = server, key_algorithm = Algo,
diffie_hellman_params = Params,
private_key = PrivateKey,
@@ -1390,9 +1352,7 @@ key_exchange(#state{role = server, key_algorithm = Algo,
transport_cb = Transport
} = State)
when Algo == dhe_dss;
- Algo == dhe_dss_export;
- Algo == dhe_rsa;
- Algo == dhe_rsa_export ->
+ Algo == dhe_rsa ->
Keys = public_key:gen_key(Params),
ConnectionState =
@@ -1411,20 +1371,10 @@ key_exchange(#state{role = server, key_algorithm = Algo,
diffie_hellman_keys = Keys,
tls_handshake_hashes = Hashes1};
-%% TODO: Not yet supported should be by default disabled when supported.
-%% key_exchange(#state{role = server, key_algorithm = dh_anon,
-%% connection_states = ConnectionStates0,
-%% negotiated_version = Version,
-%% tls_handshake_hashes = Hashes0,
-%% socket = Socket,
-%% transport_cb = Transport
-%% } = State) ->
-%% Msg = ssl_handshake:key_exchange(server, anonymous),
-%% {BinMsg, ConnectionStates1, Hashes1} =
-%% encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
-%% Transport:send(Socket, BinMsg),
-%% State#state{connection_states = ConnectionStates1,
-%% tls_handshake_hashes = Hashes1};
+
+%% key_algorithm = dh_anon is not supported. Should be by default disabled
+%% if support is implemented and then we need a key_exchange clause for it
+%% here.
key_exchange(#state{role = client,
connection_states = ConnectionStates0,
@@ -1440,7 +1390,6 @@ key_exchange(#state{role = client,
Transport:send(Socket, BinMsg),
State#state{connection_states = ConnectionStates1,
tls_handshake_hashes = Hashes1};
-
key_exchange(#state{role = client,
connection_states = ConnectionStates0,
key_algorithm = Algorithm,
@@ -1449,32 +1398,12 @@ key_exchange(#state{role = client,
socket = Socket, transport_cb = Transport,
tls_handshake_hashes = Hashes0} = State)
when Algorithm == dhe_dss;
- Algorithm == dhe_dss_export;
- Algorithm == dhe_rsa;
- Algorithm == dhe_rsa_export ->
+ Algorithm == dhe_rsa ->
Msg = ssl_handshake:key_exchange(client, {dh, DhPubKey}),
{BinMsg, ConnectionStates1, Hashes1} =
encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
Transport:send(Socket, BinMsg),
State#state{connection_states = ConnectionStates1,
- tls_handshake_hashes = Hashes1};
-
-key_exchange(#state{role = client,
- connection_states = ConnectionStates0,
- key_algorithm = Algorithm,
- negotiated_version = Version,
- client_certificate_requested = ClientCertReq,
- own_cert = OwnCert,
- diffie_hellman_keys = DhKeys,
- socket = Socket, transport_cb = Transport,
- tls_handshake_hashes = Hashes0} = State)
- when Algorithm == dh_dss;
- Algorithm == dh_rsa ->
- Msg = dh_key_exchange(OwnCert, DhKeys, ClientCertReq),
- {BinMsg, ConnectionStates1, Hashes1} =
- encode_handshake(Msg, Version, ConnectionStates0, Hashes0),
- Transport:send(Socket, BinMsg),
- State#state{connection_states = ConnectionStates1,
tls_handshake_hashes = Hashes1}.
rsa_key_exchange(PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
@@ -1488,17 +1417,6 @@ rsa_key_exchange(PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
rsa_key_exchange(_, _) ->
throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)).
-dh_key_exchange(OwnCert, DhKeys, true) ->
- case public_key:pkix_is_fixed_dh_cert(OwnCert) of
- true ->
- ssl_handshake:key_exchange(client, fixed_diffie_hellman);
- false ->
- {DhPubKey, _} = DhKeys,
- ssl_handshake:key_exchange(client, {dh, DhPubKey})
- end;
-dh_key_exchange(_, {DhPubKey, _}, false) ->
- ssl_handshake:key_exchange(client, {dh, DhPubKey}).
-
request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
connection_states = ConnectionStates0,
cert_db_ref = CertDbRef,
@@ -1517,45 +1435,44 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} =
State) ->
State.
-finalize_client_handshake(#state{connection_states = ConnectionStates0}
- = State) ->
- ConnectionStates1 =
- cipher_protocol(State#state{connection_states =
- ConnectionStates0}),
- ConnectionStates2 =
- ssl_record:activate_pending_connection_state(ConnectionStates1,
+finalize_handshake(State, StateName) ->
+ ConnectionStates0 = cipher_protocol(State),
+ ConnectionStates =
+ ssl_record:activate_pending_connection_state(ConnectionStates0,
write),
- finished(State#state{connection_states = ConnectionStates2}).
+ finished(State#state{connection_states = ConnectionStates}, StateName).
-
-finalize_server_handshake(State) ->
- ConnectionStates0 = cipher_protocol(State),
- ConnectionStates =
- ssl_record:activate_pending_connection_state(ConnectionStates0,
- write),
- finished(State#state{connection_states = ConnectionStates}).
-
-cipher_protocol(#state{connection_states = ConnectionStates,
+cipher_protocol(#state{connection_states = ConnectionStates0,
socket = Socket,
negotiated_version = Version,
transport_cb = Transport}) ->
- {BinChangeCipher, NewConnectionStates} =
+ {BinChangeCipher, ConnectionStates} =
encode_change_cipher(#change_cipher_spec{},
- Version, ConnectionStates),
+ Version, ConnectionStates0),
Transport:send(Socket, BinChangeCipher),
- NewConnectionStates.
+ ConnectionStates.
finished(#state{role = Role, socket = Socket, negotiated_version = Version,
transport_cb = Transport,
session = Session,
- connection_states = ConnectionStates,
- tls_handshake_hashes = Hashes}) ->
+ connection_states = ConnectionStates0,
+ tls_handshake_hashes = Hashes0}, StateName) ->
MasterSecret = Session#session.master_secret,
- Finished = ssl_handshake:finished(Version, Role, MasterSecret, Hashes),
- {BinFinished, NewConnectionStates, NewHashes} =
- encode_handshake(Finished, Version, ConnectionStates, Hashes),
+ Finished = ssl_handshake:finished(Version, Role, MasterSecret, Hashes0),
+ ConnectionStates1 = save_verify_data(Role, Finished, ConnectionStates0, StateName),
+ {BinFinished, ConnectionStates, Hashes} =
+ encode_handshake(Finished, Version, ConnectionStates1, Hashes0),
Transport:send(Socket, BinFinished),
- {NewConnectionStates, NewHashes}.
+ {ConnectionStates, Hashes}.
+
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
+ ssl_record:set_client_verify_data(current_write, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) ->
+ ssl_record:set_server_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_client_verify_data(current_both, Data, ConnectionStates);
+save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) ->
+ ssl_record:set_server_verify_data(current_write, Data, ConnectionStates).
handle_server_key(
#server_key_exchange{params =
@@ -1609,18 +1526,34 @@ handle_server_key(
?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)
end.
-%%handle_clinet_key(_KeyExchangeMsg, State) ->
-%% State.
-verify_dh_params(Signed, Hash, {?rsaEncryption, PubKey, _PubKeyparams}) ->
+verify_dh_params(Signed, Hashes, {?rsaEncryption, PubKey, _PubKeyParams}) ->
case public_key:decrypt_public(Signed, PubKey,
[{rsa_pad, rsa_pkcs1_padding}]) of
- Hash ->
+ Hashes ->
true;
_ ->
false
- end.
+ end;
+verify_dh_params(Signed, Hash, {?'id-dsa', PublicKey, PublicKeyParams}) ->
+ public_key:verify_signature(Hash, none, Signed, PublicKey, PublicKeyParams).
+
+cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) ->
+ ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, ConnectionStates0),
+ next_state_connection(cipher, ack_connection(State#state{session = Session,
+ connection_states = ConnectionStates}));
+
+cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State) ->
+ ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0),
+ {ConnectionStates, Hashes} =
+ finalize_handshake(State#state{connection_states = ConnectionStates1,
+ session = Session}, cipher),
+ next_state_connection(cipher, ack_connection(State#state{connection_states =
+ ConnectionStates,
+ session = Session,
+ tls_handshake_hashes =
+ Hashes})).
encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
?DBG_TERM(Alert),
ssl_record:encode_alert_record(Alert, Version, ConnectionStates).
@@ -1671,14 +1604,14 @@ decode_alerts(<<>>, Acc) ->
passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) ->
case Buffer of
<<>> ->
- State = next_record(State0),
- {next_state, StateName, State};
+ {Record, State} = next_record(State0),
+ next_state(StateName, Record, State);
_ ->
case application_data(<<>>, State0) of
Stop = {stop, _, _} ->
Stop;
- State ->
- {next_state, StateName, State}
+ {Record, State} ->
+ next_state(StateName, Record, State)
end
end.
@@ -1703,12 +1636,12 @@ application_data(Data, #state{user_application = {_Mon, Pid},
socket_options = SocketOpt
},
if
- SocketOpt#socket_options.active =:= false ->
- State; %% Passive mode, wait for active once or recv
- Buffer =:= <<>> -> %% Active and empty, get more data
- next_record(State);
- true -> %% We have more data
- application_data(<<>>, State)
+ SocketOpt#socket_options.active =:= false; Buffer =:= <<>> ->
+ %% Passive mode, wait for active once or recv
+ %% Active and empty, get more data
+ next_record_if_active(State);
+ true -> %% We have more data
+ application_data(<<>>, State)
end;
{error,_Reason} -> %% Invalid packet in packet mode
deliver_packet_error(SOpts, Buffer1, Pid, From),
@@ -1776,33 +1709,40 @@ deliver_app_data(SOpts = #socket_options{active=Active, packet=Type},
SO
end.
-format_reply(#socket_options{active=false, mode=Mode, header=Header}, Data) ->
- {ok, format_reply(Mode, Header, Data)};
-format_reply(#socket_options{active=_, mode=Mode, header=Header}, Data) ->
- {ssl, sslsocket(), format_reply(Mode, Header, Data)}.
+format_reply(#socket_options{active = false, mode = Mode, packet = Packet,
+ header = Header}, Data) ->
+ {ok, format_reply(Mode, Packet, Header, Data)};
+format_reply(#socket_options{active = _, mode = Mode, packet = Packet,
+ header = Header}, Data) ->
+ {ssl, sslsocket(), format_reply(Mode, Packet, Header, Data)}.
-deliver_packet_error(SO= #socket_options{active=Active}, Data, Pid, From) ->
+deliver_packet_error(SO= #socket_options{active = Active}, Data, Pid, From) ->
send_or_reply(Active, Pid, From, format_packet_error(SO, Data)).
-format_packet_error(#socket_options{active=false, mode=Mode}, Data) ->
- {error, {invalid_packet, format_reply(Mode, raw, Data)}};
-format_packet_error(#socket_options{active=_, mode=Mode}, Data) ->
- {ssl_error, sslsocket(), {invalid_packet, format_reply(Mode, raw, Data)}}.
-
-format_reply(list, _, Data) -> binary_to_list(Data);
-format_reply(binary, 0, Data) -> Data;
-format_reply(binary, raw, Data) -> Data;
-format_reply(binary, N, Data) -> % Header mode
- <<Header:N/binary, Rest/binary>> = Data,
- [binary_to_list(Header), Rest].
-
-%% tcp_closed
-send_or_reply(false, _Pid, undefined, _Data) ->
- Report = io_lib:format("SSL(debug): Unexpected Data ~p ~n",[_Data]),
- error_logger:error_report(Report),
- erlang:error({badarg, _Pid, undefined, _Data}),
- ok;
-send_or_reply(false, _Pid, From, Data) ->
+format_packet_error(#socket_options{active = false, mode = Mode}, Data) ->
+ {error, {invalid_packet, format_reply(Mode, raw, 0, Data)}};
+format_packet_error(#socket_options{active = _, mode = Mode}, Data) ->
+ {ssl_error, sslsocket(), {invalid_packet, format_reply(Mode, raw, 0, Data)}}.
+
+format_reply(binary, _, N, Data) when N > 0 -> % Header mode
+ header(N, Data);
+format_reply(binary, _, _, Data) -> Data;
+format_reply(list, Packet, _, Data) when is_integer(Packet); Packet == raw ->
+ binary_to_list(Data);
+format_reply(list, _,_, Data) ->
+ Data.
+
+header(0, <<>>) ->
+ <<>>;
+header(_, <<>>) ->
+ [];
+header(0, Binary) ->
+ Binary;
+header(N, Binary) ->
+ <<?BYTE(ByteN), NewBinary/binary>> = Binary,
+ [ByteN | header(N-1, NewBinary)].
+
+send_or_reply(false, _Pid, From, Data) when From =/= undefined ->
gen_fsm:reply(From, Data);
send_or_reply(_, Pid, _From, Data) ->
send_user(Pid, Data).
@@ -1815,68 +1755,134 @@ opposite_role(server) ->
send_user(Pid, Msg) ->
Pid ! Msg.
-%% %% This is the code for {packet,ssl} removed because it was slower
-%% %% than handling it in erlang.
-%% next_record(#state{socket = Socket,
-%% tls_buffer = [Msg|Rest],
-%% connection_states = ConnectionStates0} = State) ->
-%% Buffer =
-%% case Rest of
-%% [] ->
-%% inet:setopts(Socket, [{active,once}]),
-%% buffer;
-%% _ -> Rest
-%% end,
-%% case Msg of
-%% #ssl_tls{} ->
-%% {Plain, ConnectionStates} =
-%% ssl_record:decode_cipher_text(Msg, ConnectionStates0),
-%% gen_fsm:send_all_state_event(self(), Plain),
-%% State#state{tls_buffer=Buffer, connection_states = ConnectionStates};
-%% {ssl_close, Msg} ->
-%% self() ! Msg,
-%% State#state{tls_buffer=Buffer}
-%% end;
-%% next_record(#state{socket = Socket, tls_buffer = undefined} = State) ->
-%% inet:setopts(Socket, [{active,once}]),
-%% State#state{tls_buffer=continue};
-%% next_record(State) ->
-%% State#state{tls_buffer=continue}.
-
-next_record(#state{tls_cipher_texts = [], socket = Socket} = State) ->
+handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet]} = State) ->
+ FsmReturn = {next_state, StateName, State#state{tls_packets = []}},
+ Handle(Packet, FsmReturn);
+
+handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet | Packets]} = State0) ->
+ FsmReturn = {next_state, StateName, State0#state{tls_packets = Packets}},
+ case Handle(Packet, FsmReturn) of
+ {next_state, NextStateName, State} ->
+ handle_tls_handshake(Handle, NextStateName, State);
+ {stop, _,_} = Stop ->
+ Stop
+ end.
+
+next_state(_, #alert{} = Alert, #state{negotiated_version = Version} = State) ->
+ handle_own_alert(Alert, Version, decipher_error, State),
+ {stop, normal, State};
+
+next_state(Next, no_record, State) ->
+ {next_state, Next, State};
+
+next_state(Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) ->
+ Alerts = decode_alerts(EncAlerts),
+ handle_alerts(Alerts, {next_state, Next, State});
+
+next_state(StateName, #ssl_tls{type = ?HANDSHAKE, fragment = Data},
+ State0 = #state{key_algorithm = KeyAlg,
+ tls_handshake_buffer = Buf0,
+ negotiated_version = Version}) ->
+ Handle =
+ fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) ->
+ %% This message should not be included in handshake
+ %% message hashes. Starts new handshake (renegotiation)
+ Hs0 = ssl_handshake:init_hashes(),
+ ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs0,
+ renegotiation = {true, peer}});
+ ({#hello_request{} = Packet, _}, {next_state, SName, State}) ->
+ %% This message should not be included in handshake
+ %% message hashes. Already in negotiation so it will be ignored!
+ ?MODULE:SName(Packet, State);
+ ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) ->
+ Hs0 = ssl_handshake:init_hashes(),
+ Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
+ ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1,
+ renegotiation = {true, peer}});
+ ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_hashes=Hs0}}) ->
+ Hs1 = ssl_handshake:update_hashes(Hs0, Raw),
+ ?MODULE:SName(Packet, State#state{tls_handshake_hashes=Hs1});
+ (_, StopState) -> StopState
+ end,
+ try
+ {Packets, Buf} = ssl_handshake:get_tls_handshake(Data,Buf0, KeyAlg,Version),
+ State = State0#state{tls_packets = Packets, tls_handshake_buffer = Buf},
+ handle_tls_handshake(Handle, StateName, State)
+ catch throw:#alert{} = Alert ->
+ handle_own_alert(Alert, Version, StateName, State0),
+ {stop, normal, State0}
+ end;
+
+next_state(StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) ->
+ case application_data(Data, State0) of
+ Stop = {stop,_,_} ->
+ Stop;
+ {Record, State} ->
+ next_state(StateName, Record, State)
+ end;
+next_state(StateName, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} =
+ _ChangeCipher,
+ #state{connection_states = ConnectionStates0} = State0) ->
+ ?DBG_TERM(_ChangeCipher),
+ ConnectionStates1 =
+ ssl_record:activate_pending_connection_state(ConnectionStates0, read),
+ {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}),
+ next_state(StateName, Record, State);
+next_state(StateName, #ssl_tls{type = _Unknown}, State0) ->
+ %% Ignore unknown type
+ {Record, State} = next_record(State0),
+ next_state(StateName, Record, State).
+
+next_tls_record(Data, #state{tls_record_buffer = Buf0,
+ tls_cipher_texts = CT0} = State0) ->
+ case ssl_record:get_tls_records(Data, Buf0) of
+ {Records, Buf1} ->
+ CT1 = CT0 ++ Records,
+ next_record(State0#state{tls_record_buffer = Buf1,
+ tls_cipher_texts = CT1});
+ #alert{} = Alert ->
+ Alert
+ end.
+
+next_record(#state{tls_packets = [], tls_cipher_texts = [], socket = Socket} = State) ->
inet:setopts(Socket, [{active,once}]),
- State;
-next_record(#state{tls_cipher_texts = [CT | Rest],
+ {no_record, State};
+next_record(#state{tls_packets = [], tls_cipher_texts = [CT | Rest],
connection_states = ConnStates0} = State) ->
- {Plain, ConnStates} = ssl_record:decode_cipher_text(CT, ConnStates0),
- gen_fsm:send_all_state_event(self(), Plain),
- State#state{tls_cipher_texts = Rest, connection_states = ConnStates}.
-
+ case ssl_record:decode_cipher_text(CT, ConnStates0) of
+ {Plain, ConnStates} ->
+ {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}};
+ #alert{} = Alert ->
+ {Alert, State}
+ end;
+next_record(State) ->
+ {no_record, State}.
next_record_if_active(State =
#state{socket_options =
#socket_options{active = false}}) ->
- State;
+ {no_record ,State};
next_record_if_active(State) ->
next_record(State).
-next_state_connection(#state{send_queue = Queue0,
- negotiated_version = Version,
- socket = Socket,
- transport_cb = Transport,
- connection_states = ConnectionStates0,
- ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}
- } = State) ->
+next_state_connection(StateName, #state{send_queue = Queue0,
+ negotiated_version = Version,
+ socket = Socket,
+ transport_cb = Transport,
+ connection_states = ConnectionStates0,
+ ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}
+ } = State) ->
%% Send queued up data
case queue:out(Queue0) of
{{value, {From, Data}}, Queue} ->
case encode_data(Data, Version, ConnectionStates0, RenegotiateAt) of
{Msgs, [], ConnectionStates} ->
Result = Transport:send(Socket, Msgs),
- gen_fsm:reply(From, Result),
- next_state_connection(State#state{connection_states = ConnectionStates,
- send_queue = Queue});
+ gen_fsm:reply(From, Result),
+ next_state_connection(StateName,
+ State#state{connection_states = ConnectionStates,
+ send_queue = Queue});
%% This is unlikely to happen. User configuration of the
%% undocumented test option renegotiation_at can make it more likely.
{Msgs, RestData, ConnectionStates} ->
@@ -1887,7 +1893,8 @@ next_state_connection(#state{send_queue = Queue0,
ok
end,
renegotiate(State#state{connection_states = ConnectionStates,
- send_queue = queue:in_r({From, RestData}, Queue)})
+ send_queue = queue:in_r({From, RestData}, Queue),
+ renegotiation = {true, internal}})
end;
{empty, Queue0} ->
next_state_is_connection(State)
@@ -1898,9 +1905,9 @@ next_state_is_connection(State =
#socket_options{active = false}}) ->
passive_receive(State#state{recv_during_renegotiation = false}, connection);
-next_state_is_connection(State) ->
- {next_state, connection, next_record_if_active(State)}.
-
+next_state_is_connection(State0) ->
+ {Record, State} = next_record_if_active(State0),
+ next_state(connection, Record, State).
register_session(_, _, _, #session{is_resumable = true} = Session) ->
Session; %% Already registered
@@ -1919,7 +1926,7 @@ invalidate_session(server, _, Port, Session) ->
ssl_manager:invalidate_session(Port, Session).
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
- {CbModule, DataTag, CloseTag}) ->
+ {CbModule, DataTag, CloseTag, ErrorTag}) ->
ConnectionStates = ssl_record:init_connection_states(Role),
SessionCacheCb = case application:get_env(ssl, session_cb) of
@@ -1939,6 +1946,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
transport_cb = CbModule,
data_tag = DataTag,
close_tag = CloseTag,
+ error_tag = ErrorTag,
role = Role,
host = Host,
port = Port,
@@ -1952,7 +1960,7 @@ initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
user_data_buffer = <<>>,
log_alert = true,
session_cache_cb = SessionCacheCb,
- renegotiation = false,
+ renegotiation = {false, first},
recv_during_renegotiation = false,
send_queue = queue:new()
}.
@@ -2010,10 +2018,61 @@ set_socket_opts(Socket, [{active, Active}| Opts], SockOpts, Other) ->
set_socket_opts(Socket, [Opt | Opts], SockOpts, Other) ->
set_socket_opts(Socket, Opts, SockOpts, [Opt | Other]).
+handle_alerts([], Result) ->
+ Result;
+handle_alerts(_, {stop, _, _} = Stop) ->
+ %% If it is a fatal alert immediately close
+ Stop;
+handle_alerts([Alert | Alerts], {next_state, StateName, State}) ->
+ handle_alerts(Alerts, handle_alert(Alert, StateName, State)).
+
+handle_alert(#alert{level = ?FATAL} = Alert, StateName,
+ #state{from = From, host = Host, port = Port, session = Session,
+ user_application = {_Mon, Pid},
+ log_alert = Log, role = Role, socket_options = Opts} = State) ->
+ invalidate_session(Role, Host, Port, Session),
+ log_alert(Log, StateName, Alert),
+ alert_user(StateName, Opts, Pid, From, Alert, Role),
+ {stop, normal, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
+ StateName, #state{from = From, role = Role,
+ user_application = {_Mon, Pid}, socket_options = Opts} = State) ->
+ alert_user(StateName, Opts, Pid, From, Alert, Role),
+ {stop, normal, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+ #state{log_alert = Log, renegotiation = {true, internal}, from = From,
+ role = Role} = State) ->
+ log_alert(Log, StateName, Alert),
+ alert_user(From, Alert, Role),
+ {stop, normal, State};
+
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+ #state{log_alert = Log, renegotiation = {true, From}} = State0) ->
+ log_alert(Log, StateName, Alert),
+ gen_fsm:reply(From, {error, renegotiation_rejected}),
+ {Record, State} = next_record(State0),
+ next_state(connection, Record, State);
+
+handle_alert(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName,
+ #state{log_alert = Log} = State0) ->
+ log_alert(Log, StateName, Alert),
+ {Record, State} = next_record(State0),
+ next_state(StateName, Record, State).
+
+alert_user(connection, Opts, Pid, From, Alert, Role) ->
+ alert_user(Opts#socket_options.active, Pid, From, Alert, Role);
+alert_user(_, _, _, From, Alert, Role) ->
+ alert_user(From, Alert, Role).
+
alert_user(From, Alert, Role) ->
alert_user(false, no_pid, From, Alert, Role).
alert_user(false = Active, Pid, From, Alert, Role) ->
+ %% If there is an outstanding ssl_accept | recv
+ %% From will be defined and send_or_reply will
+ %% send the appropriate error message.
ReasonCode = ssl_alert:reason_code(Alert, Role),
send_or_reply(Active, Pid, From, {error, ReasonCode});
alert_user(Active, Pid, From, Alert, Role) ->
@@ -2026,13 +2085,13 @@ alert_user(Active, Pid, From, Alert, Role) ->
{ssl_error, sslsocket(), ReasonCode})
end.
-log_alert(true, StateName, Alert) ->
+log_alert(true, Info, Alert) ->
Txt = ssl_alert:alert_txt(Alert),
- error_logger:format("SSL: ~p: ~s\n", [StateName, Txt]);
+ error_logger:format("SSL: ~p: ~s\n", [Info, Txt]);
log_alert(false, _, _) ->
ok.
-handle_own_alert(Alert, Version, StateName,
+handle_own_alert(Alert, Version, Info,
#state{transport_cb = Transport,
socket = Socket,
from = User,
@@ -2041,20 +2100,25 @@ handle_own_alert(Alert, Version, StateName,
log_alert = Log}) ->
try %% Try to tell the other side
{BinMsg, _} =
- encode_alert(Alert, Version, ConnectionStates),
+ encode_alert(Alert, Version, ConnectionStates),
+ linux_workaround_transport_delivery_problems(Alert, Socket),
Transport:send(Socket, BinMsg)
catch _:_ -> %% Can crash if we are in a uninitialized state
ignore
end,
try %% Try to tell the local user
- log_alert(Log, StateName, Alert),
+ log_alert(Log, Info, Alert),
alert_user(User, Alert, Role)
catch _:_ ->
ok
end.
-make_premaster_secret({MajVer, MinVer}, Alg) when Alg == rsa;
- Alg == dh_dss;
- Alg == dh_rsa ->
+
+handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) ->
+ Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE),
+ handle_own_alert(Alert, Version, {Info, Msg}, State),
+ {stop, normal, State}.
+
+make_premaster_secret({MajVer, MinVer}, rsa) ->
Rand = crypto:rand_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2),
<<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>;
make_premaster_secret(_, _) ->
@@ -2065,11 +2129,19 @@ mpint_binary(Binary) ->
<<?UINT32(Size), Binary/binary>>.
-ack_connection(#state{renegotiation = true}) ->
- ok;
-ack_connection(#state{renegotiation = false, from = From}) ->
- gen_fsm:reply(From, connected).
-
+ack_connection(#state{renegotiation = {true, Initiater}} = State)
+ when Initiater == internal;
+ Initiater == peer ->
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {true, From}} = State) ->
+ gen_fsm:reply(From, ok),
+ State#state{renegotiation = undefined};
+ack_connection(#state{renegotiation = {false, first},
+ from = From} = State) when From =/= undefined ->
+ gen_fsm:reply(From, connected),
+ State#state{renegotiation = undefined};
+ack_connection(State) ->
+ State.
renegotiate(#state{role = client} = State) ->
%% Handle same way as if server requested
@@ -2080,14 +2152,42 @@ renegotiate(#state{role = server,
socket = Socket,
transport_cb = Transport,
negotiated_version = Version,
- connection_states = ConnectionStates0} = State) ->
+ connection_states = ConnectionStates0} = State0) ->
HelloRequest = ssl_handshake:hello_request(),
Frag = ssl_handshake:encode_handshake(HelloRequest, Version, undefined),
Hs0 = ssl_handshake:init_hashes(),
{BinMsg, ConnectionStates} =
ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
Transport:send(Socket, BinMsg),
- {next_state, hello, next_record(State#state{connection_states =
- ConnectionStates,
- tls_handshake_hashes = Hs0,
- renegotiation = true})}.
+ {Record, State} = next_record(State0#state{connection_states =
+ ConnectionStates,
+ tls_handshake_hashes = Hs0}),
+ next_state(hello, Record, State).
+
+notify_senders(SendQueue) ->
+ lists:foreach(fun({From, _}) ->
+ gen_fsm:reply(From, {error, closed})
+ end, queue:to_list(SendQueue)).
+
+notify_renegotiater({true, From}) when not is_atom(From) ->
+ gen_fsm:reply(From, {error, closed});
+notify_renegotiater(_) ->
+ ok.
+
+workaround_transport_delivery_problems(Socket, Transport) ->
+ %% Standard trick to try to make sure all
+ %% data sent to to tcp port is really sent
+ %% before tcp port is closed.
+ inet:setopts(Socket, [{active, false}]),
+ Transport:shutdown(Socket, write),
+ Transport:recv(Socket, 0).
+
+linux_workaround_transport_delivery_problems(#alert{level = ?FATAL}, Socket) ->
+ case os:type() of
+ {unix, linux} ->
+ inet:setopts(Socket, [{nodelay, true}]);
+ _ ->
+ ok
+ end;
+linux_workaround_transport_delivery_problems(_, _) ->
+ ok.
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index ff78375b9f..3811906d77 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -31,8 +31,8 @@
-include("ssl_debug.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([master_secret/4, client_hello/4, server_hello/3, hello/2,
- hello_request/0, certify/5, certificate/3,
+-export([master_secret/4, client_hello/6, server_hello/4, hello/4,
+ hello_request/0, certify/7, certificate/3,
client_certificate_verify/6,
certificate_verify/6, certificate_request/2,
key_exchange/2, server_key_exchange_hash/2, finished/4,
@@ -42,22 +42,21 @@
encode_handshake/3, init_hashes/0,
update_hashes/2, decrypt_premaster_secret/2]).
+-type tls_handshake() :: #client_hello{} | #server_hello{} | #server_hello_done{} |
+#certificate{} | #client_key_exchange{} | #finished{} | #certificate_verify{}.
+
%%====================================================================
%% Internal application API
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: client_hello(Host, Port, ConnectionStates, SslOpts) ->
-%% #client_hello{}
-%% Host
-%% Port
-%% ConnectionStates = #connection_states{}
-%% SslOpts = #ssl_options{}
+-spec client_hello(host(), port_num(), #connection_states{},
+ #ssl_options{}, binary(), boolean()) -> #client_hello{}.
%%
%% Description: Creates a client hello message.
%%--------------------------------------------------------------------
client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions,
- ciphers = Ciphers}
- = SslOpts) ->
+ ciphers = UserSuites}
+ = SslOpts, Cert, Renegotiation) ->
Fun = fun(Version) ->
ssl_record:protocol_version(Version)
@@ -65,27 +64,26 @@ client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions,
Version = ssl_record:highest_protocol_version(lists:map(Fun, Versions)),
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = Pending#connection_state.security_parameters,
-
+ Ciphers = available_suites(Cert, UserSuites, Version),
+
Id = ssl_manager:client_session_id(Host, Port, SslOpts),
#client_hello{session_id = Id,
client_version = Version,
- cipher_suites = Ciphers,
+ cipher_suites = cipher_suites(Ciphers, Renegotiation),
compression_methods = ssl_record:compressions(),
- random = SecParams#security_parameters.client_random
+ random = SecParams#security_parameters.client_random,
+ renegotiation_info =
+ renegotiation_info(client, ConnectionStates, Renegotiation)
}.
%%--------------------------------------------------------------------
-%% Function: server_hello(Host, Port, SessionId,
-%% Version, ConnectionStates) -> #server_hello{}
-%% SessionId
-%% Version
-%% ConnectionStates
-%%
+-spec server_hello(session_id(), tls_version(), #connection_states{},
+ boolean()) -> #server_hello{}.
%%
%% Description: Creates a server hello message.
%%--------------------------------------------------------------------
-server_hello(SessionId, Version, ConnectionStates) ->
+server_hello(SessionId, Version, ConnectionStates, Renegotiation) ->
Pending = ssl_record:pending_connection_state(ConnectionStates, read),
SecParams = Pending#connection_state.security_parameters,
#server_hello{server_version = Version,
@@ -93,11 +91,13 @@ server_hello(SessionId, Version, ConnectionStates) ->
compression_method =
SecParams#security_parameters.compression_algorithm,
random = SecParams#security_parameters.server_random,
- session_id = SessionId
+ session_id = SessionId,
+ renegotiation_info =
+ renegotiation_info(server, ConnectionStates, Renegotiation)
}.
%%--------------------------------------------------------------------
-%% Function: hello_request() -> #hello_request{}
+-spec hello_request() -> #hello_request{}.
%%
%% Description: Creates a hello request message sent by server to
%% trigger renegotiation.
@@ -106,65 +106,100 @@ hello_request() ->
#hello_request{}.
%%--------------------------------------------------------------------
-%% Function: hello(Hello, Info) ->
-%% {Version, Id, NewConnectionStates} |
-%% #alert{}
-%%
-%% Hello = #client_hello{} | #server_hello{}
-%% Info = ConnectionStates | {Port, Session, ConnectionStates}
-%% ConnectionStates = #connection_states{}
+-spec hello(#server_hello{} | #client_hello{}, #ssl_options{},
+ #connection_states{} | {port_num(), #session{}, cache_ref(),
+ atom(), #connection_states{}, binary()},
+ boolean()) -> {tls_version(), session_id(), #connection_states{}}|
+ {tls_version(), {resumed | new, session_id()},
+ #connection_states{}} | #alert{}.
%%
%% Description: Handles a recieved hello message
%%--------------------------------------------------------------------
hello(#server_hello{cipher_suite = CipherSuite, server_version = Version,
compression_method = Compression, random = Random,
- session_id = SessionId}, ConnectionStates) ->
- NewConnectionStates =
- hello_pending_connection_states(client, CipherSuite, Random,
- Compression, ConnectionStates),
- {Version, SessionId, NewConnectionStates};
-
-hello(#client_hello{client_version = ClientVersion, random = Random} = Hello,
- {Port, #ssl_options{versions = Versions} = SslOpts,
- Session0, Cache, CacheCb, ConnectionStates0}) ->
+ session_id = SessionId, renegotiation_info = Info},
+ #ssl_options{secure_renegotiate = SecureRenegotation},
+ ConnectionStates0, Renegotiation) ->
+
+ case ssl_record:is_acceptable_version(Version) of
+ true ->
+ case handle_renegotiation_info(client, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation, []) of
+ {ok, ConnectionStates1} ->
+ ConnectionStates =
+ hello_pending_connection_states(client, CipherSuite, Random,
+ Compression, ConnectionStates1),
+ {Version, SessionId, ConnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end;
+ false ->
+ ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
+ end;
+
+hello(#client_hello{client_version = ClientVersion, random = Random,
+ cipher_suites = CipherSuites,
+ renegotiation_info = Info} = Hello,
+ #ssl_options{versions = Versions,
+ secure_renegotiate = SecureRenegotation} = SslOpts,
+ {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) ->
Version = select_version(ClientVersion, Versions),
case ssl_record:is_acceptable_version(Version) of
true ->
{Type, #session{cipher_suite = CipherSuite,
compression_method = Compression} = Session}
= select_session(Hello, Port, Session0, Version,
- SslOpts, Cache, CacheCb),
+ SslOpts, Cache, CacheCb, Cert),
case CipherSuite of
no_suite ->
?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY);
_ ->
- ConnectionStates =
- hello_pending_connection_states(server,
- CipherSuite,
- Random,
- Compression,
- ConnectionStates0),
- {Version, {Type, Session}, ConnectionStates}
+ case handle_renegotiation_info(server, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation,
+ CipherSuites) of
+ {ok, ConnectionStates1} ->
+ ConnectionStates =
+ hello_pending_connection_states(server,
+ CipherSuite,
+ Random,
+ Compression,
+ ConnectionStates1),
+ {Version, {Type, Session}, ConnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end
end;
false ->
?ALERT_REC(?FATAL, ?PROTOCOL_VERSION)
end.
%%--------------------------------------------------------------------
-%% Function: certify(Certs, CertDbRef, MaxPathLen) ->
-%% {PeerCert, PublicKeyInfo} | #alert{}
-%%
-%% Certs = #certificate{}
-%% CertDbRef = reference()
-%% MaxPathLen = integer() | nolimit
+-spec certify(#certificate{}, term(), integer() | nolimit,
+ verify_peer | verify_none, fun(), fun(),
+ client | server) -> {der_cert(), public_key_info()} | #alert{}.
%%
%% Description: Handles a certificate handshake message
%%--------------------------------------------------------------------
certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
- MaxPathLen, Verify, VerifyFun) ->
+ MaxPathLen, Verify, VerifyFun, ValidateFun, Role) ->
[PeerCert | _] = ASN1Certs,
VerifyBool = verify_bool(Verify),
-
+
+ ValidateExtensionFun =
+ case ValidateFun of
+ undefined ->
+ fun(Extensions, ValidationState, Verify0, AccError) ->
+ ssl_certificate:validate_extensions(Extensions, ValidationState,
+ [], Verify0, AccError, Role)
+ end;
+ Fun ->
+ fun(Extensions, ValidationState, Verify0, AccError) ->
+ {NewExtensions, NewValidationState, NewAccError}
+ = ssl_certificate:validate_extensions(Extensions, ValidationState,
+ [], Verify0, AccError, Role),
+ Fun(NewExtensions, NewValidationState, Verify0, NewAccError)
+ end
+ end,
try
%% Allow missing root_cert and check that with VerifyFun
ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbRef, false) of
@@ -174,6 +209,8 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
[{max_path_length,
MaxPathLen},
{verify, VerifyBool},
+ {validate_extensions_fun,
+ ValidateExtensionFun},
{acc_errors,
VerifyErrors}]),
case Result of
@@ -195,10 +232,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbRef,
end.
%%--------------------------------------------------------------------
-%% Function: certificate(OwnCert, CertDbRef, Role) -> #certificate{}
-%%
-%% OwnCert = binary()
-%% CertDbRef = term() as returned by ssl_certificate_db:create()
+-spec certificate(der_cert(), term(), client | server) -> #certificate{}.
%%
%% Description: Creates a certificate message.
%%--------------------------------------------------------------------
@@ -224,10 +258,10 @@ certificate(OwnCert, CertDbRef, server) ->
end.
%%--------------------------------------------------------------------
-%% Function: client_certificate_verify(Cert, ConnectionStates) ->
-%% #certificate_verify{} | ignore
-%% Cert = #'OTPcertificate'{}
-%% ConnectionStates = #connection_states{}
+-spec client_certificate_verify(undefined | der_cert(), binary(),
+ tls_version(), key_algo(), private_key(),
+ {binary(), binary()}) ->
+ #certificate_verify{} | ignore.
%%
%% Description: Creates a certificate_verify message, called by the client.
%%--------------------------------------------------------------------
@@ -239,7 +273,7 @@ client_certificate_verify(OwnCert, MasterSecret, Version, Algorithm,
PrivateKey, {Hashes0, _}) ->
case public_key:pkix_is_fixed_dh_cert(OwnCert) of
true ->
- ignore;
+ ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE);
false ->
Hashes =
calc_certificate_verify(Version, MasterSecret,
@@ -249,17 +283,15 @@ client_certificate_verify(OwnCert, MasterSecret, Version, Algorithm,
end.
%%--------------------------------------------------------------------
-%% Function: certificate_verify(Signature, PublicKeyInfo) -> valid | #alert{}
-%%
-%% Signature = binary()
-%% PublicKeyInfo = {Algorithm, PublicKey, PublicKeyParams}
+-spec certificate_verify(binary(), public_key_info(), tls_version(),
+ binary(), key_algo(),
+ {binary(), binary()}) -> valid | #alert{}.
%%
%% Description: Checks that the certificate_verify message is valid.
%%--------------------------------------------------------------------
certificate_verify(Signature, {_, PublicKey, _}, Version,
MasterSecret, Algorithm, {_, Hashes0})
when Algorithm == rsa;
- Algorithm == dh_rsa;
Algorithm == dhe_rsa ->
Hashes = calc_certificate_verify(Version, MasterSecret,
Algorithm, Hashes0),
@@ -269,12 +301,16 @@ certificate_verify(Signature, {_, PublicKey, _}, Version,
valid;
_ ->
?ALERT_REC(?FATAL, ?BAD_CERTIFICATE)
- end.
-%% TODO dsa clause
+ end;
+certificate_verify(Signature, {_, PublicKey, PublicKeyParams}, Version,
+ MasterSecret, dhe_dss = Algorithm, {_, Hashes0}) ->
+ Hashes = calc_certificate_verify(Version, MasterSecret,
+ Algorithm, Hashes0),
+ public_key:verify_signature(Hashes, sha, Signature, PublicKey, PublicKeyParams).
%%--------------------------------------------------------------------
-%% Function: certificate_request(ConnectionStates, CertDbRef) ->
-%% #certificate_request{}
+-spec certificate_request(#connection_states{}, certdb_ref()) ->
+ #certificate_request{}.
%%
%% Description: Creates a certificate_request message, called by the server.
%%--------------------------------------------------------------------
@@ -290,11 +326,12 @@ certificate_request(ConnectionStates, CertDbRef) ->
}.
%%--------------------------------------------------------------------
-%% Function: key_exchange(Role, Secret, Params) ->
-%% #client_key_exchange{} | #server_key_exchange{}
-%%
-%% Secret -
-%% Params -
+-spec key_exchange(client | server,
+ {premaster_secret, binary(), public_key_info()} |
+ {dh, binary()} |
+ {dh, binary(), #'DHParameter'{}, key_algo(),
+ binary(), binary(), private_key()}) ->
+ #client_key_exchange{} | #server_key_exchange{}.
%%
%% Description: Creates a keyexchange message.
%%--------------------------------------------------------------------
@@ -302,18 +339,14 @@ key_exchange(client, {premaster_secret, Secret, {_, PublicKey, _}}) ->
EncPremasterSecret =
encrypted_premaster_secret(Secret, PublicKey),
#client_key_exchange{exchange_keys = EncPremasterSecret};
-key_exchange(client, fixed_diffie_hellman) ->
- #client_key_exchange{exchange_keys =
- #client_diffie_hellman_public{
- dh_public = <<>>
- }};
+
key_exchange(client, {dh, <<?UINT32(Len), PublicKey:Len/binary>>}) ->
#client_key_exchange{
exchange_keys = #client_diffie_hellman_public{
dh_public = PublicKey}
};
-key_exchange(server, {dh, {<<?UINT32(_), PublicKey/binary>>, _},
+key_exchange(server, {dh, {<<?UINT32(Len), PublicKey:Len/binary>>, _},
#'DHParameter'{prime = P, base = G},
KeyAlgo, ClientRandom, ServerRandom, PrivateKey}) ->
<<?UINT32(_), PBin/binary>> = crypto:mpint(P),
@@ -322,31 +355,21 @@ key_exchange(server, {dh, {<<?UINT32(_), PublicKey/binary>>, _},
GLen = byte_size(GBin),
YLen = byte_size(PublicKey),
ServerDHParams = #server_dh_params{dh_p = PBin,
- dh_g = GBin, dh_y = PublicKey},
-
+ dh_g = GBin, dh_y = PublicKey},
Hash =
server_key_exchange_hash(KeyAlgo, <<ClientRandom/binary,
- ServerRandom/binary,
- ?UINT16(PLen), PBin/binary,
- ?UINT16(GLen), GBin/binary,
- ?UINT16(YLen), PublicKey/binary>>),
+ ServerRandom/binary,
+ ?UINT16(PLen), PBin/binary,
+ ?UINT16(GLen), GBin/binary,
+ ?UINT16(YLen), PublicKey/binary>>),
Signed = digitally_signed(Hash, PrivateKey),
#server_key_exchange{params = ServerDHParams,
- signed_params = Signed};
-key_exchange(_, _) ->
- %%TODO : Real imp
- #server_key_exchange{}.
+ signed_params = Signed}.
%%--------------------------------------------------------------------
-%% Function: master_secret(Version, Session/PremasterSecret,
-%% ConnectionStates, Role) ->
-%% {MasterSecret, NewConnectionStates} | #alert{}
-%% Version = #protocol_version{}
-%% Session = #session{} (session contains master secret)
-%% PremasterSecret = binary()
-%% ConnectionStates = #connection_states{}
-%% Role = client | server
-%%
+-spec master_secret(tls_version(), #session{} | binary(), #connection_states{},
+ client | server) -> {binary(), #connection_states{}} | #alert{}.
+%%
%% Description: Sets or calculates the master secret and calculate keys,
%% updating the pending connection states. The Mastersecret and the update
%% connection states are returned or an alert if the calculation fails.
@@ -383,9 +406,8 @@ master_secret(Version, PremasterSecret, ConnectionStates, Role) ->
end.
%%--------------------------------------------------------------------
-%% Function: finished(Version, Role, MacSecret, Hashes) -> #finished{}
-%%
-%% ConnectionStates = #connection_states{}
+-spec finished(tls_version(), client | server, binary(), {binary(), binary()}) ->
+ #finished{}.
%%
%% Description: Creates a handshake finished message
%%-------------------------------------------------------------------
@@ -394,15 +416,8 @@ finished(Version, Role, MasterSecret, {Hashes, _}) -> % use the current hashes
calc_finished(Version, Role, MasterSecret, Hashes)}.
%%--------------------------------------------------------------------
-%% Function: verify_connection(Finished, Role,
-%% MasterSecret, Hashes) -> verified | #alert{}
-%%
-%% Finished = #finished{}
-%% Role = client | server - the role of the process that sent the finished
-%% message.
-%% MasterSecret = binary()
-%% Hashes = binary() - {md5_hash, sha_hash}
-%%
+-spec verify_connection(tls_version(), #finished{}, client | server, binary(),
+ {binary(), binary()}) -> verified | #alert{}.
%%
%% Description: Checks the ssl handshake finished message to verify
%% the connection.
@@ -418,17 +433,18 @@ verify_connection(Version, #finished{verify_data = Data},
_E ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
end.
-
+%%--------------------------------------------------------------------
+-spec server_hello_done() -> #server_hello_done{}.
+%%
+%% Description: Creates a server hello done message.
+%%--------------------------------------------------------------------
server_hello_done() ->
#server_hello_done{}.
%%--------------------------------------------------------------------
-%% Function: encode_handshake(HandshakeRec) -> BinHandshake
-%% HandshakeRec = #client_hello | #server_hello{} | server_hello_done |
-%% #certificate{} | #client_key_exchange{} | #finished{} |
-%% #client_certify_request{}
+-spec encode_handshake(tls_handshake(), tls_version(), key_algo()) -> binary().
%%
-%% encode a handshake packet to binary
+%% Description: Encode a handshake packet to binary
%%--------------------------------------------------------------------
encode_handshake(Package, Version, KeyAlg) ->
SigAlg = sig_alg(KeyAlg),
@@ -437,12 +453,11 @@ encode_handshake(Package, Version, KeyAlg) ->
[MsgType, ?uint24(Len), Bin].
%%--------------------------------------------------------------------
-%% Function: get_tls_handshake(Data, Buffer) -> Result
-%% Result = {[#handshake{}], [Raw], NewBuffer}
-%% Data = Buffer = NewBuffer = Raw = binary()
+-spec get_tls_handshake(binary(), binary(), key_algo(), tls_version()) ->
+ {[tls_handshake()], [binary()], binary()}.
%%
%% Description: Given buffered and new data from ssl_record, collects
-%% and returns it as a list of #handshake, also returns leftover
+%% and returns it as a list of handshake messages, also returns leftover
%% data.
%%--------------------------------------------------------------------
get_tls_handshake(Data, <<>>, KeyAlg, Version) ->
@@ -451,18 +466,18 @@ get_tls_handshake(Data, Buffer, KeyAlg, Version) ->
get_tls_handshake_aux(list_to_binary([Buffer, Data]),
KeyAlg, Version, []).
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
get_tls_handshake_aux(<<?BYTE(Type), ?UINT24(Length),
Body:Length/binary,Rest/binary>>, KeyAlg,
Version, Acc) ->
Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>,
- H = dec_hs(Type, Body, key_excahange_alg(KeyAlg), Version),
+ H = dec_hs(Type, Body, key_exchange_alg(KeyAlg), Version),
get_tls_handshake_aux(Rest, KeyAlg, Version, [{H,Raw} | Acc]);
get_tls_handshake_aux(Data, _KeyAlg, _Version, Acc) ->
{lists:reverse(Acc), Data}.
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
verify_bool(verify_peer) ->
true;
verify_bool(verify_none) ->
@@ -484,18 +499,12 @@ path_validation_alert(_, _) ->
?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE).
select_session(Hello, Port, Session, Version,
- #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb) ->
+ #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) ->
SuggestedSessionId = Hello#client_hello.session_id,
SessionId = ssl_manager:server_session_id(Port, SuggestedSessionId,
SslOpts),
- Suites = case UserSuites of
- [] ->
- ssl_cipher:suites(Version);
- _ ->
- UserSuites
- end,
-
+ Suites = available_suites(Cert, UserSuites, Version),
case ssl_session:is_new(SuggestedSessionId, SessionId) of
true ->
CipherSuite =
@@ -508,7 +517,116 @@ select_session(Hello, Port, Session, Version,
false ->
{resumed, CacheCb:lookup(Cache, {Port, SessionId})}
end.
-
+
+available_suites(Cert, UserSuites, Version) ->
+ case UserSuites of
+ [] ->
+ ssl_cipher:filter(Cert, ssl_cipher:suites(Version));
+ _ ->
+ ssl_cipher:filter(Cert, UserSuites)
+ end.
+
+cipher_suites(Suites, false) ->
+ [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites];
+cipher_suites(Suites, true) ->
+ Suites.
+
+renegotiation_info(client, _, false) ->
+ #renegotiation_info{renegotiated_connection = undefined};
+renegotiation_info(server, ConnectionStates, false) ->
+ CS = ssl_record:current_connection_state(ConnectionStates, read),
+ case CS#connection_state.secure_renegotiation of
+ true ->
+ #renegotiation_info{renegotiated_connection = ?byte(0)};
+ false ->
+ #renegotiation_info{renegotiated_connection = undefined}
+ end;
+renegotiation_info(client, ConnectionStates, true) ->
+ CS = ssl_record:current_connection_state(ConnectionStates, read),
+ case CS#connection_state.secure_renegotiation of
+ true ->
+ Data = CS#connection_state.client_verify_data,
+ #renegotiation_info{renegotiated_connection = Data};
+ false ->
+ #renegotiation_info{renegotiated_connection = undefined}
+ end;
+
+renegotiation_info(server, ConnectionStates, true) ->
+ CS = ssl_record:current_connection_state(ConnectionStates, read),
+ case CS#connection_state.secure_renegotiation of
+ true ->
+ CData = CS#connection_state.client_verify_data,
+ SData =CS#connection_state.server_verify_data,
+ #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>};
+ false ->
+ #renegotiation_info{renegotiated_connection = undefined}
+ end.
+
+handle_renegotiation_info(_, #renegotiation_info{renegotiated_connection = ?byte(0)},
+ ConnectionStates, false, _, _) ->
+ {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
+
+handle_renegotiation_info(server, undefined, ConnectionStates, _, _, CipherSuites) ->
+ case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
+ true ->
+ {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)};
+ false ->
+ {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}
+ end;
+
+handle_renegotiation_info(_, undefined, ConnectionStates, false, _, _) ->
+ {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)};
+
+handle_renegotiation_info(client, #renegotiation_info{renegotiated_connection = ClientServerVerify},
+ ConnectionStates, true, _, _) ->
+ CS = ssl_record:current_connection_state(ConnectionStates, read),
+ CData = CS#connection_state.client_verify_data,
+ SData = CS#connection_state.server_verify_data,
+ case <<CData/binary, SData/binary>> == ClientServerVerify of
+ true ->
+ {ok, ConnectionStates};
+ false ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end;
+handle_renegotiation_info(server, #renegotiation_info{renegotiated_connection = ClientVerify},
+ ConnectionStates, true, _, CipherSuites) ->
+
+ case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
+ true ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ false ->
+ CS = ssl_record:current_connection_state(ConnectionStates, read),
+ Data = CS#connection_state.client_verify_data,
+ case Data == ClientVerify of
+ true ->
+ {ok, ConnectionStates};
+ false ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)
+ end
+ end;
+
+handle_renegotiation_info(client, undefined, ConnectionStates, true, SecureRenegotation, _) ->
+ handle_renegotiation_info(ConnectionStates, SecureRenegotation);
+
+handle_renegotiation_info(server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) ->
+ case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of
+ true ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ false ->
+ handle_renegotiation_info(ConnectionStates, SecureRenegotation)
+ end.
+
+handle_renegotiation_info(ConnectionStates, SecureRenegotation) ->
+ CS = ssl_record:current_connection_state(ConnectionStates, read),
+ case {SecureRenegotation, CS#connection_state.secure_renegotiation} of
+ {_, true} ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ {true, false} ->
+ ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION);
+ {false, false} ->
+ {ok, ConnectionStates}
+ end.
+
%% Update pending connection states with parameters exchanged via
%% hello messages
%% NOTE : Role is the role of the receiver of the hello message
@@ -580,12 +698,11 @@ master_secret(Version, MasterSecret, #security_parameters{
hash_size = HashSize,
key_material_length = KML,
expanded_key_material_length = EKML,
- iv_size = IVS,
- exportable = Exportable},
+ iv_size = IVS},
ConnectionStates, Role) ->
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV} =
- setup_keys(Version, Exportable, MasterSecret, ServerRandom,
+ setup_keys(Version, MasterSecret, ServerRandom,
ClientRandom, HashSize, KML, EKML, IVS),
?DBG_HEX(ClientWriteKey),
?DBG_HEX(ClientIV),
@@ -619,44 +736,59 @@ dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor),
random = ssl_ssl2:client_random(ChallengeData, CDLength),
session_id = 0,
cipher_suites = from_3bytes(CipherSuites),
- compression_methods = [?NULL]
+ compression_methods = [?NULL],
+ renegotiation_info = undefined
};
dec_hs(?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
?UINT16(Cs_length), CipherSuites:Cs_length/binary,
?BYTE(Cm_length), Comp_methods:Cm_length/binary,
- _FutureCompatData/binary>>,
+ Extensions/binary>>,
_, _) ->
+
+ RenegotiationInfo = proplists:get_value(renegotiation_info, dec_hello_extensions(Extensions),
+ undefined),
#client_hello{
client_version = {Major,Minor},
random = Random,
session_id = Session_ID,
cipher_suites = from_2bytes(CipherSuites),
- compression_methods = Comp_methods
+ compression_methods = Comp_methods,
+ renegotiation_info = RenegotiationInfo
};
+
dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID:SID_length/binary,
- Cipher_suite:2/binary, ?BYTE(Comp_method)>>, _, _) ->
+ Cipher_suite:2/binary, ?BYTE(Comp_method)>>, _, _) ->
#server_hello{
server_version = {Major,Minor},
random = Random,
session_id = Session_ID,
cipher_suite = Cipher_suite,
- compression_method = Comp_method
- };
+ compression_method = Comp_method,
+ renegotiation_info = undefined};
+
+dec_hs(?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SID_length), Session_ID:SID_length/binary,
+ Cipher_suite:2/binary, ?BYTE(Comp_method),
+ ?UINT16(ExtLen), Extensions:ExtLen/binary>>, _, _) ->
+
+ RenegotiationInfo = proplists:get_value(renegotiation_info, dec_hello_extensions(Extensions, []),
+ undefined),
+ #server_hello{
+ server_version = {Major,Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method,
+ renegotiation_info = RenegotiationInfo};
dec_hs(?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>, _, _) ->
#certificate{asn1_certificates = certs_to_list(ASN1Certs)};
-dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen), Mod:ModLen/binary,
- ?UINT16(ExpLen), Exp:ExpLen/binary,
- ?UINT16(_), Sig/binary>>,
- ?KEY_EXCHANGE_RSA, _) ->
- #server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod,
- rsa_exponent = Exp},
- signed_params = Sig};
+
dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary,
?UINT16(GLen), G:GLen/binary,
?UINT16(YLen), Y:YLen/binary,
- ?UINT16(_), Sig/binary>>,
+ ?UINT16(Len), Sig:Len/binary>>,
?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
#server_key_exchange{params = #server_dh_params{dh_p = P,dh_g = G,
dh_y = Y},
@@ -664,7 +796,6 @@ dec_hs(?SERVER_KEY_EXCHANGE, <<?UINT16(PLen), P:PLen/binary,
dec_hs(?CERTIFICATE_REQUEST,
<<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary,
?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>, _, _) ->
- %% TODO: maybe we should chop up CertAuths into a list?
#certificate_request{certificate_types = CertTypes,
certificate_authorities = CertAuths};
dec_hs(?SERVER_HELLO_DONE, <<>>, _, _) ->
@@ -679,8 +810,7 @@ dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(_), PKEPMS/binary>>,
PreSecret = #encrypted_premaster_secret{premaster_secret = PKEPMS},
#client_key_exchange{exchange_keys = PreSecret};
dec_hs(?CLIENT_KEY_EXCHANGE, <<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
- %% TODO: Should check whether the cert already contains a suitable DH-key (7.4.7.2)
- throw(?ALERT_REC(?FATAL, implicit_public_value_encoding));
+ throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE));
dec_hs(?CLIENT_KEY_EXCHANGE, <<?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>,
?KEY_EXCHANGE_DIFFIE_HELLMAN, _) ->
#client_key_exchange{exchange_keys =
@@ -690,6 +820,32 @@ dec_hs(?FINISHED, VerifyData, _, _) ->
dec_hs(_, _, _, _) ->
throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+dec_hello_extensions(<<>>) ->
+ [];
+dec_hello_extensions(<<?UINT16(ExtLen), Extensions:ExtLen/binary>>) ->
+ dec_hello_extensions(Extensions, []);
+dec_hello_extensions(_) ->
+ [].
+
+dec_hello_extensions(<<>>, Acc) ->
+ Acc;
+dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) ->
+ RenegotiateInfo = case Len of
+ 1 -> % Initial handshake
+ Info; % should be <<0>> will be matched in handle_renegotiation_info
+ _ ->
+ VerifyLen = Len - 1,
+ <<?BYTE(VerifyLen), VerifyInfo/binary>> = Info,
+ VerifyInfo
+ end,
+ dec_hello_extensions(Rest, [{renegotiation_info,
+ #renegotiation_info{renegotiated_connection = RenegotiateInfo}} | Acc]);
+dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len, Rest/binary>>, Acc) ->
+ dec_hello_extensions(Rest, Acc);
+%% Need this clause?
+dec_hello_extensions(_, Acc) ->
+ Acc.
+
encrypted_premaster_secret(Secret, RSAPublicKey) ->
try
PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey,
@@ -726,45 +882,40 @@ certs_from_list(ACList) ->
enc_hs(#hello_request{}, _Version, _) ->
{?HELLO_REQUEST, <<>>};
-enc_hs(#client_hello{
- client_version = {Major, Minor},
- random = Random,
- session_id = SessionID,
- cipher_suites = CipherSuites,
- compression_methods = CompMethods}, _Version, _) ->
+enc_hs(#client_hello{client_version = {Major, Minor},
+ random = Random,
+ session_id = SessionID,
+ cipher_suites = CipherSuites,
+ compression_methods = CompMethods,
+ renegotiation_info = RenegotiationInfo}, _Version, _) ->
SIDLength = byte_size(SessionID),
BinCompMethods = list_to_binary(CompMethods),
CmLength = byte_size(BinCompMethods),
BinCipherSuites = list_to_binary(CipherSuites),
CsLength = byte_size(BinCipherSuites),
+ Extensions = hello_extensions(RenegotiationInfo),
+ ExtensionsBin = enc_hello_extensions(Extensions),
{?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SIDLength), SessionID/binary,
?UINT16(CsLength), BinCipherSuites/binary,
- ?BYTE(CmLength), BinCompMethods/binary>>};
-enc_hs(#server_hello{
- server_version = {Major, Minor},
- random = Random,
- session_id = Session_ID,
- cipher_suite = Cipher_suite,
- compression_method = Comp_method}, _Version, _) ->
+ ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
+
+enc_hs(#server_hello{server_version = {Major, Minor},
+ random = Random,
+ session_id = Session_ID,
+ cipher_suite = Cipher_suite,
+ compression_method = Comp_method,
+ renegotiation_info = RenegotiationInfo}, _Version, _) ->
SID_length = byte_size(Session_ID),
+ Extensions = hello_extensions(RenegotiationInfo),
+ ExtensionsBin = enc_hello_extensions(Extensions),
{?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
?BYTE(SID_length), Session_ID/binary,
- Cipher_suite/binary, ?BYTE(Comp_method)>>};
+ Cipher_suite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>};
enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version, _) ->
ASN1Certs = certs_from_list(ASN1CertList),
ACLen = erlang:iolist_size(ASN1Certs),
{?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>};
-enc_hs(#server_key_exchange{params = #server_rsa_params{rsa_modulus = Mod,
- rsa_exponent = Exp},
- signed_params = SignedParams}, _Version, _) ->
- ModLen = byte_size(Mod),
- ExpLen = byte_size(Exp),
- SignedLen = byte_size(SignedParams),
- {?SERVER_KEY_EXCHANGE, <<?UINT16(ModLen),Mod/binary,
- ?UINT16(ExpLen), Exp/binary,
- ?UINT16(SignedLen), SignedParams/binary>>
- };
enc_hs(#server_key_exchange{params = #server_dh_params{
dh_p = P, dh_g = G, dh_y = Y},
signed_params = SignedParams}, _Version, _) ->
@@ -809,6 +960,29 @@ enc_bin_sig(BinSig) ->
Size = byte_size(BinSig),
<<?UINT16(Size), BinSig/binary>>.
+%% Renegotiation info, only current extension
+hello_extensions(#renegotiation_info{renegotiated_connection = undefined}) ->
+ [];
+hello_extensions(#renegotiation_info{} = Info) ->
+ [Info].
+
+enc_hello_extensions(Extensions) ->
+ enc_hello_extensions(Extensions, <<>>).
+enc_hello_extensions([], <<>>) ->
+ <<>>;
+enc_hello_extensions([], Acc) ->
+ Size = byte_size(Acc),
+ <<?UINT16(Size), Acc/binary>>;
+
+enc_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) ->
+ Len = byte_size(Info),
+ enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>);
+
+enc_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) ->
+ InfoLen = byte_size(Info),
+ Len = InfoLen +1,
+ enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen), Info/binary, Acc/binary>>).
+
init_hashes() ->
T = {crypto:md5_init(), crypto:sha_init()},
{T, T}.
@@ -851,16 +1025,11 @@ from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) ->
certificate_types({KeyExchange, _, _, _})
when KeyExchange == rsa;
- KeyExchange == dh_dss;
- KeyExchange == dh_rsa;
KeyExchange == dhe_dss;
KeyExchange == dhe_rsa ->
<<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>;
certificate_types(_) ->
- %%TODO: Is this a good default,
- %% is there a case where we like to request
- %% a RSA_FIXED_DH or DSS_FIXED_DH
<<?BYTE(?RSA_SIGN)>>.
certificate_authorities(CertDbRef) ->
@@ -879,7 +1048,7 @@ certificate_authorities_from_db(CertDbRef) ->
certificate_authorities_from_db(CertDbRef, no_candidate, []).
certificate_authorities_from_db(CertDbRef, PrevKey, Acc) ->
- case ssl_certificate_db:issuer_candidate(PrevKey) of
+ case ssl_manager:issuer_candidate(PrevKey) of
no_more_candidates ->
lists:reverse(Acc);
{{CertDbRef, _, _} = Key, Cert} ->
@@ -889,13 +1058,12 @@ certificate_authorities_from_db(CertDbRef, PrevKey, Acc) ->
certificate_authorities_from_db(CertDbRef, Key, Acc)
end.
-digitally_signed(Hashes, #'RSAPrivateKey'{} = Key) ->
- public_key:encrypt_private(Hashes, Key,
+digitally_signed(Hash, #'RSAPrivateKey'{} = Key) ->
+ public_key:encrypt_private(Hash, Key,
[{rsa_pad, rsa_pkcs1_padding}]);
-digitally_signed(Hashes, #'DSAPrivateKey'{} = Key) ->
- public_key:sign(Hashes, Key).
-
-
+digitally_signed(Hash, #'DSAPrivateKey'{} = Key) ->
+ public_key:sign(none, Hash, Key).
+
calc_master_secret({3,0}, PremasterSecret, ClientRandom, ServerRandom) ->
ssl_ssl3:master_secret(PremasterSecret, ClientRandom, ServerRandom);
@@ -903,20 +1071,15 @@ calc_master_secret({3,N},PremasterSecret, ClientRandom, ServerRandom)
when N == 1; N == 2 ->
ssl_tls1:master_secret(PremasterSecret, ClientRandom, ServerRandom).
-setup_keys({3,0}, Exportable, MasterSecret,
+setup_keys({3,0}, MasterSecret,
ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) ->
- ssl_ssl3:setup_keys(Exportable, MasterSecret, ServerRandom,
+ ssl_ssl3:setup_keys(MasterSecret, ServerRandom,
ClientRandom, HashSize, KML, EKML, IVS);
-setup_keys({3,1}, _Exportable, MasterSecret,
+setup_keys({3,1}, MasterSecret,
ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) ->
ssl_tls1:setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize,
- KML, IVS);
-
-setup_keys({3,2}, _Exportable, MasterSecret,
- ServerRandom, ClientRandom, HashSize, KML, _EKML, _IVS) ->
- ssl_tls1:setup_keys(MasterSecret, ServerRandom,
- ClientRandom, HashSize, KML).
+ KML, IVS).
calc_finished({3, 0}, Role, MasterSecret, Hashes) ->
ssl_ssl3:finished(Role, MasterSecret, Hashes);
@@ -931,39 +1094,27 @@ calc_certificate_verify({3, N}, _, Algorithm, Hashes)
ssl_tls1:certificate_verify(Algorithm, Hashes).
server_key_exchange_hash(Algorithm, Value) when Algorithm == rsa;
- Algorithm == dh_rsa;
Algorithm == dhe_rsa ->
- MD5Context = crypto:md5_init(),
- NewMD5Context = crypto:md5_update(MD5Context, Value),
- MD5 = crypto:md5_final(NewMD5Context),
-
- SHAContext = crypto:sha_init(),
- NewSHAContext = crypto:sha_update(SHAContext, Value),
- SHA = crypto:sha_final(NewSHAContext),
-
+ MD5 = crypto:md5(Value),
+ SHA = crypto:sha(Value),
<<MD5/binary, SHA/binary>>;
-server_key_exchange_hash(Algorithm, Value) when Algorithm == dh_dss;
- Algorithm == dhe_dss ->
-
- SHAContext = crypto:sha_init(),
- NewSHAContext = crypto:sha_update(SHAContext, Value),
- crypto:sha_final(NewSHAContext).
-
+server_key_exchange_hash(dhe_dss, Value) ->
+ crypto:sha(Value).
sig_alg(dh_anon) ->
?SIGNATURE_ANONYMOUS;
-sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa; Alg == dh_rsa ->
+sig_alg(Alg) when Alg == dhe_rsa; Alg == rsa ->
?SIGNATURE_RSA;
-sig_alg(Alg) when Alg == dh_dss; Alg == dhe_dss ->
+sig_alg(dhe_dss) ->
?SIGNATURE_DSA;
sig_alg(_) ->
?NULL.
-key_excahange_alg(rsa) ->
+key_exchange_alg(rsa) ->
?KEY_EXCHANGE_RSA;
-key_excahange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss;
+key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss;
Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon ->
?KEY_EXCHANGE_DIFFIE_HELLMAN;
-key_excahange_alg(_) ->
+key_exchange_alg(_) ->
?NULL.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index 889d39f2af..74fba3786c 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -81,7 +81,8 @@
random,
session_id, % opaque SessionID<0..32>
cipher_suites, % cipher_suites<2..2^16-1>
- compression_methods % compression_methods<1..2^8-1>
+ compression_methods, % compression_methods<1..2^8-1>,
+ renegotiation_info
}).
-record(server_hello, {
@@ -89,7 +90,8 @@
random,
session_id, % opaque SessionID<0..32>
cipher_suite, % cipher_suites
- compression_method % compression_method
+ compression_method, % compression_method
+ renegotiation_info
}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -195,6 +197,15 @@
verify_data %opaque verify_data[12]
}).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Renegotiation info RFC 5746 section 3.2
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+-define(RENEGOTIATION_EXT, 16#ff01).
+
+-record(renegotiation_info,{
+ renegotiated_connection
+ }).
+
-endif. % -ifdef(ssl_handshake).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index ab24c28b2f..ddace02dea 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -23,6 +23,8 @@
-ifndef(ssl_internal).
-define(ssl_internal, true).
+-include_lib("public_key/include/public_key.hrl").
+
%% basic binary constructors
-define(BOOLEAN(X), X:8/unsigned-big-integer).
-define(BYTE(X), X:8/unsigned-big-integer).
@@ -57,6 +59,8 @@
verify_fun, % fun(CertVerifyErrors) -> boolean()
fail_if_no_peer_cert, % boolean()
verify_client_once, % boolean()
+ %% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError}
+ validate_extensions_fun,
depth, % integer()
certfile, % file()
keyfile, % file()
@@ -73,6 +77,7 @@
%% will be reused if possible.
reuse_sessions, % boolean()
renegotiate_at,
+ secure_renegotiate,
debug %
}).
@@ -85,6 +90,28 @@
active = true
}).
+-type reason() :: term().
+-type reply() :: term().
+-type msg() :: term().
+-type from() :: term().
+-type host() :: string() | tuple().
+-type port_num() :: integer().
+-type session_id() :: binary().
+-type tls_version() :: {integer(), integer()}.
+-type tls_atom_version() :: sslv3 | tlsv1.
+-type cache_ref() :: term().
+-type certdb_ref() :: term().
+-type key_algo() :: rsa | dhe_rsa | dhe_dss.
+-type enum_algo() :: integer().
+-type public_key() :: #'RSAPublicKey'{} | integer().
+-type public_key_params() :: #'Dss-Parms'{} | term().
+-type public_key_info() :: {enum_algo(), public_key(), public_key_params()}.
+-type der_cert() :: binary().
+-type private_key() :: #'RSAPrivateKey'{} | #'DSAPrivateKey'{}.
+-type issuer() :: tuple().
+-type serialnumber() :: integer().
+-type cert_key() :: {reference(), integer(), issuer()}.
+
-endif. % -ifdef(ssl_internal).
diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl
index 0151426d43..af30f78dbf 100644
--- a/lib/ssl/src/ssl_manager.erl
+++ b/lib/ssl/src/ssl_manager.erl
@@ -24,10 +24,12 @@
-module(ssl_manager).
-behaviour(gen_server).
+-include("ssl_internal.hrl").
+
%% Internal application API
--export([start_link/0, start_link/1,
+-export([start_link/1,
connection_init/2, cache_pem_file/1,
- lookup_trusted_cert/3, client_session_id/3, server_session_id/3,
+ lookup_trusted_cert/3, issuer_candidate/1, client_session_id/3, server_session_id/3,
register_session/2, register_session/3, invalidate_session/2,
invalidate_session/3]).
@@ -58,21 +60,25 @@
%% API
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
+-spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}.
+%%
%% Description: Starts the server
%%--------------------------------------------------------------------
-start_link() ->
- gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
start_link(Opts) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Opts], []).
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec connection_init(string(), client | server) -> {ok, reference(), cache_ref()}.
+%%
+%% Description: Do necessary initializations for a new connection.
%%--------------------------------------------------------------------
connection_init(TrustedcertsFile, Role) ->
call({connection_init, TrustedcertsFile, Role}).
-
+%%--------------------------------------------------------------------
+-spec cache_pem_file(string()) -> {ok, term()}.
+%%
+%% Description: Cach a pem file and
+%%--------------------------------------------------------------------
cache_pem_file(File) ->
case ssl_certificate_db:lookup_cached_certs(File) of
[{_,Content}] ->
@@ -80,41 +86,51 @@ cache_pem_file(File) ->
[] ->
call({cache_pem, File})
end.
-
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec lookup_trusted_cert(reference(), serialnumber(), issuer()) ->
+ {der_cert(), #'OTPCertificate'{}}.
+%%
+%% Description: Lookup the trusted cert with Key = {reference(), serialnumber(), issuer()}.
%%--------------------------------------------------------------------
-lookup_trusted_cert(SerialNumber, Issuer, Ref) ->
+lookup_trusted_cert(Ref, SerialNumber, Issuer) ->
ssl_certificate_db:lookup_trusted_cert(Ref, SerialNumber, Issuer).
-
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec issuer_candidate(cert_key()) -> {cert_key(), der_cert()} | no_more_candidates.
+%%
+%% Description: Return next issuer candidate.
+%%--------------------------------------------------------------------
+issuer_candidate(PrevCandidateKey) ->
+ ssl_certificate_db:issuer_candidate(PrevCandidateKey).
+%%--------------------------------------------------------------------
+-spec client_session_id(host(), port_num(), #ssl_options{}) -> session_id().
+%%
+%% Description: Select a session id for the client.
%%--------------------------------------------------------------------
client_session_id(Host, Port, SslOpts) ->
call({client_session_id, Host, Port, SslOpts}).
-
+
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec server_session_id(host(), port_num(), #ssl_options{}) -> session_id().
+%%
+%% Description: Select a session id for the server.
%%--------------------------------------------------------------------
server_session_id(Port, SuggestedSessionId, SslOpts) ->
call({server_session_id, Port, SuggestedSessionId, SslOpts}).
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec register_session(host(), port_num(), #session{}) -> ok.
+%%
+%% Description: Make the session available for reuse.
%%--------------------------------------------------------------------
register_session(Host, Port, Session) ->
cast({register_session, Host, Port, Session}).
register_session(Port, Session) ->
cast({register_session, Port, Session}).
-
%%--------------------------------------------------------------------
-%% Function:
-%% Description:
+-spec invalidate_session(host(), port_num(), #session{}) -> ok.
+%%
+%% Description: Make the session unavilable for reuse.
%%--------------------------------------------------------------------
invalidate_session(Host, Port, Session) ->
cast({invalidate_session, Host, Port, Session}).
@@ -127,34 +143,34 @@ invalidate_session(Port, Session) ->
%%====================================================================
%%--------------------------------------------------------------------
-%% Function: init(Args) -> {ok, State} |
-%% {ok, State, Timeout} |
-%% ignore |
-%% {stop, Reason}
+-spec init(list()) -> {ok, #state{}} | {ok, #state{}, timeout()} |
+ ignore | {stop, term()}.
+%%
%% Description: Initiates the server
%%--------------------------------------------------------------------
-init(Opts) ->
+init([Opts]) ->
process_flag(trap_exit, true),
- CacheCb = proplists:get_value(session_cache, Opts, ssl_session_cache),
+ CacheCb = proplists:get_value(session_cb, Opts, ssl_session_cache),
SessionLifeTime =
proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'),
CertDb = ssl_certificate_db:create(),
- SessionCache = CacheCb:init(),
+ SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])),
Timer = erlang:send_after(SessionLifeTime * 1000,
self(), validate_sessions),
{ok, #state{certificate_db = CertDb,
session_cache = SessionCache,
session_cache_cb = CacheCb,
- session_lifetime = SessionLifeTime ,
+ session_lifetime = SessionLifeTime,
session_validation_timer = Timer}}.
%%--------------------------------------------------------------------
-%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
-%% {reply, Reply, State, Timeout} |
-%% {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, Reply, State} |
-%% {stop, Reason, State}
+-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}} |
+ {reply, reply(), #state{}, timeout()} |
+ {noreply, #state{}} |
+ {noreply, #state{}, timeout()} |
+ {stop, reason(), reply(), #state{}} |
+ {stop, reason(), #state{}}.
+%%
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({{connection_init, "", _Role}, Pid}, _From,
@@ -172,10 +188,8 @@ handle_call({{connection_init, TrustedcertsFile, _Role}, Pid}, _From,
{ok, Ref} = ssl_certificate_db:add_trusted_certs(Pid, TrustedcertsFile, Db),
{ok, Ref, Cache}
catch
- _:{badmatch, Error} ->
- {error, Error};
- _E:_R ->
- {error, {_R,erlang:get_stacktrace()}}
+ _:Reason ->
+ {error, Reason}
end,
{reply, Result, State};
@@ -197,18 +211,15 @@ handle_call({{cache_pem, File},Pid}, _, State = #state{certificate_db = Db}) ->
try ssl_certificate_db:cache_pem_file(Pid,File,Db) of
Result ->
{reply, Result, State}
- catch _:{badmatch, Reason} ->
- {reply, Reason, State};
- _:Reason ->
+ catch
+ _:Reason ->
{reply, {error, Reason}, State}
- end;
-
-handle_call(_,_, State) ->
- {reply, ok, State}.
+ end.
%%--------------------------------------------------------------------
-%% Function: handle_cast(Msg, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
+-spec handle_cast(msg(), #state{}) -> {noreply, #state{}} |
+ {noreply, #state{}, timeout()} |
+ {stop, reason(), #state{}}.
+%%
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast({register_session, Host, Port, Session},
@@ -242,9 +253,10 @@ handle_cast({invalidate_session, Port, #session{session_id = ID}},
{noreply, State}.
%%--------------------------------------------------------------------
-%% Function: handle_info(Info, State) -> {noreply, State} |
-%% {noreply, State, Timeout} |
-%% {stop, Reason, State}
+-spec handle_info(msg(), #state{}) -> {noreply, #state{}} |
+ {noreply, #state{}, timeout()} |
+ {stop, reason(), #state{}}.
+%%
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------
handle_info(validate_sessions, #state{session_cache_cb = CacheCb,
@@ -277,7 +289,8 @@ handle_info(_Info, State) ->
{noreply, State}.
%%--------------------------------------------------------------------
-%% Function: terminate(Reason, State) -> void()
+-spec terminate(reason(), #state{}) -> term().
+%%
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
@@ -293,7 +306,8 @@ terminate(_Reason, #state{certificate_db = Db,
ok.
%%--------------------------------------------------------------------
-%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
+-spec code_change(term(), #state{}, list()) -> {ok, #state{}}.
+%%
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
@@ -332,10 +346,9 @@ init_session_validator([Cache, CacheCb, LifeTime]) ->
CacheCb:foldl(fun session_validation/2,
LifeTime, Cache).
-session_validation({{Host, Port, _}, Session}, LifeTime) ->
+session_validation({{{Host, Port}, _}, Session}, LifeTime) ->
validate_session(Host, Port, Session, LifeTime),
LifeTime;
session_validation({{Port, _}, Session}, LifeTime) ->
validate_session(Port, Session, LifeTime),
LifeTime.
-
diff --git a/lib/ssl/src/ssl_pem.erl b/lib/ssl/src/ssl_pem.erl
deleted file mode 100644
index 0a1bf0f32a..0000000000
--- a/lib/ssl/src/ssl_pem.erl
+++ /dev/null
@@ -1,147 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-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(ssl_pem).
-
-%%% Purpose: Reading and writing of PEM type encoded files for SSL.
-
-%% NB write_file/2 is only preliminary.
-
-%% PEM encoded files have the following structure:
-%%
-%% <text>
-%% -----BEGIN SOMETHING-----<CR><LF>
-%% <Base64 encoding line><CR><LF>
-%% <Base64 encoding line><CR><LF>
-%% ...
-%% -----END SOMETHING-----<CR><LF>
-%% <text>
-%%
-%% A file can contain several BEGIN/END blocks. Text lines between
-%% blocks are ignored.
-
--export([read_file/1, read_file/2, write_file/2]).
-
-%% Read a PEM file and return each decoding as a binary.
-
-read_file(File) ->
- read_file(File, no_passwd).
-
-read_file(File, Passwd) ->
- {ok, Fd} = file:open(File, [read]),
- Result = decode_file(Fd, Passwd),
- file:close(Fd),
- Result.
-
-decode_file(Fd, Passwd) ->
- decode_file(Fd, [], [], notag, [Passwd]).
-
-decode_file(Fd, _RLs, Ens, notag, Info) ->
- case io:get_line(Fd, "") of
- "-----BEGIN CERTIFICATE REQUEST-----" ++ _ ->
- decode_file(Fd, [], Ens, cert_req, Info);
- "-----BEGIN CERTIFICATE-----" ++ _ ->
- decode_file(Fd, [], Ens, cert, Info);
- "-----BEGIN RSA PRIVATE KEY-----" ++ _ ->
- decode_file(Fd, [], Ens, rsa_private_key, Info);
- eof ->
- {ok, lists:reverse(Ens)};
- _ ->
- decode_file(Fd, [], Ens, notag, Info)
- end;
-decode_file(Fd, RLs, Ens, Tag, Info0) ->
- case io:get_line(Fd, "") of
- "Proc-Type: 4,ENCRYPTED"++_ ->
- Info = dek_info(Fd, Info0),
- decode_file(Fd, RLs, Ens, Tag, Info);
- "-----END" ++ _ -> % XXX sloppy
- Cs = lists:flatten(lists:reverse(RLs)),
- Bin = ssl_base64:join_decode(Cs),
- case Info0 of
- [Password, Cipher, SaltHex | Info1] ->
- Decoded = decode_key(Bin, Password, Cipher, unhex(SaltHex)),
- decode_file(Fd, [], [{Tag, Decoded}| Ens], notag, Info1);
- _ ->
- decode_file(Fd, [], [{Tag, Bin}| Ens], notag, Info0)
- end;
- eof ->
- {ok, lists:reverse(Ens)};
- L ->
- decode_file(Fd, [L|RLs], Ens, Tag, Info0)
- end.
-
-dek_info(Fd, Info) ->
- Line = io:get_line(Fd, ""),
- [_, DekInfo0] = string:tokens(Line, ": "),
- DekInfo1 = string:tokens(DekInfo0, ",\n"),
- Info ++ DekInfo1.
-
-unhex(S) ->
- unhex(S, []).
-
-unhex("", Acc) ->
- lists:reverse(Acc);
-unhex([D1, D2 | Rest], Acc) ->
- unhex(Rest, [erlang:list_to_integer([D1, D2], 16) | Acc]).
-
-decode_key(Data, Password, "DES-CBC", Salt) ->
- Key = password_to_key(Password, Salt, 8),
- IV = Salt,
- crypto:des_cbc_decrypt(Key, IV, Data);
-decode_key(Data, Password, "DES-EDE3-CBC", Salt) ->
- Key = password_to_key(Password, Salt, 24),
- IV = Salt,
- <<Key1:8/binary, Key2:8/binary, Key3:8/binary>> = Key,
- crypto:des_ede3_cbc_decrypt(Key1, Key2, Key3, IV, Data).
-
-write_file(File, Ds) ->
- file:write_file(File, encode_file(Ds)).
-
-encode_file(Ds) ->
- [encode_file_1(D) || D <- Ds].
-
-encode_file_1({cert, Bin}) ->
- %% PKIX (X.509)
- ["-----BEGIN CERTIFICATE-----\n",
- ssl_base64:encode_split(Bin),
- "-----END CERTIFICATE-----\n\n"];
-encode_file_1({cert_req, Bin}) ->
- %% PKCS#10
- ["-----BEGIN CERTIFICATE REQUEST-----\n",
- ssl_base64:encode_split(Bin),
- "-----END CERTIFICATE REQUEST-----\n\n"];
-encode_file_1({rsa_private_key, Bin}) ->
- %% PKCS#?
- ["XXX Following key assumed not encrypted\n",
- "-----BEGIN RSA PRIVATE KEY-----\n",
- ssl_base64:encode_split(Bin),
- "-----END RSA PRIVATE KEY-----\n\n"].
-
-password_to_key(Data, Salt, KeyLen) ->
- <<Key:KeyLen/binary, _/binary>> =
- password_to_key(<<>>, Data, Salt, KeyLen, <<>>),
- Key.
-
-password_to_key(_, _, _, Len, Acc) when Len =< 0 ->
- Acc;
-password_to_key(Prev, Data, Salt, Len, Acc) ->
- M = crypto:md5([Prev, Data, Salt]),
- password_to_key(M, Data, Salt, Len - byte_size(M), <<Acc/binary, M/binary>>).
diff --git a/lib/ssl/src/ssl_pkix.erl b/lib/ssl/src/ssl_pkix.erl
deleted file mode 100644
index 8f540f74ad..0000000000
--- a/lib/ssl/src/ssl_pkix.erl
+++ /dev/null
@@ -1,307 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-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%
-%%
-
-%%% Purpose : API module for decoding of certificates.
-
--module(ssl_pkix).
-
--include("ssl_pkix.hrl").
-
--export([decode_cert_file/1, decode_cert_file/2,
- decode_cert/1, decode_cert/2, encode_cert/1, encoded_tbs_cert/1,
- signature_digest/1, decode_rsa_keyfile/2]).
-
-%% The public API is dprecated by public_key and
-%% the internal application API is no longer used ssl.
-%% So this file can be compleatly removed in R14.
--deprecated({decode_cert_file, 1, next_major_release}).
--deprecated({decode_cert_file, 2, next_major_release}).
--deprecated({decode_cert, 1, next_major_release}).
--deprecated({decode_cert, 2, next_major_release}).
-
-%%====================================================================
-%% API
-%%====================================================================
-
-%%--------------------------------------------------------------------
-%% Function: decode_cert_file(File, <Opts>) -> {ok, Cert} | {ok, [Cert]}
-%%
-%% File = string()
-%% Opts = [Opt]
-%% Opt = pem | ssl | pkix - ssl and pkix are mutual exclusive
-%% Cert = term()
-%%
-%% Description: Decodes certificats found in file <File>.
-%% If the options list is empty the certificate is
-%% returned as a DER encoded binary, i.e. {ok, Bin} is returned, where
-%% Bin> is the provided input. The options pkix and ssl imply that the
-%% certificate is returned as a parsed ASN.1 structure in the form of
-%% an Erlang term. The ssl option gives a more elaborate return
-%% structure, with more explicit information. In particular object
-%% identifiers are replaced by atoms. The option subject implies that
-%% only the subject's distinguished name part of the certificate is
-%% returned. It can only be used together with the option pkix or the
-%% option ssl.
-%%--------------------------------------------------------------------
-decode_cert_file(File) ->
- decode_cert_file(File, []).
-
-decode_cert_file(File, Opts) ->
- case lists:member(pem, Opts) of
- true ->
- {ok, List} = ssl_pem:read_file(File),
- Certs = [Bin || {cert, Bin} <- List],
- NewOpts = lists:delete(pem, Opts),
- Fun = fun(Cert) ->
- {ok, Decoded} = decode_cert(Cert, NewOpts),
- Decoded
- end,
- case lists:map(Fun, Certs) of
- [DecodedCert] ->
- {ok, DecodedCert};
- DecodedCerts ->
- {ok, DecodedCerts}
- end;
- false ->
- {ok, Bin} = file:read_file(File),
- decode_cert(Bin, Opts)
- end.
-%%--------------------------------------------------------------------
-%% Function: decode_cert(Bin, <Opts>) -> {ok, Cert}
-%% Bin - binary()
-%% Opts = [Opt]
-%% Opt = ssl | pkix | subject - ssl and pkix are mutual exclusive
-%% Cert = term()
-%%
-%% Description: If the options list is empty the certificate is
-%% returned as a DER encoded binary, i.e. {ok, Bin} is returned, where
-%% Bin> is the provided input. The options pkix and ssl imply that the
-%% certificate is returned as a parsed ASN.1 structure in the form of
-%% an Erlang term. The ssl option gives a more elaborate return
-%% structure, with more explicit information. In particular object
-%% identifiers are replaced by atoms. The option subject implies that
-%% only the subject's distinguished name part of the certificate is
-%% returned. It can only be used together with the option pkix or the
-%% option ssl.
-%%--------------------------------------------------------------------
-decode_cert(Bin) ->
- decode_cert(Bin, []).
-
-decode_cert(Bin, []) when is_binary(Bin) ->
- {ok, Bin};
-decode_cert(Bin, Opts) when is_binary(Bin) ->
-
- {ok, Cert} = 'OTP-PKIX':decode('Certificate', Bin),
-
- case {lists:member(ssl, Opts), lists:member(pkix, Opts)} of
- {true, false} ->
- cert_return(transform(Cert, ssl), Opts);
- {false, true} ->
- cert_return(transform(Cert, pkix), Opts);
- _ ->
- {error, eoptions}
- end.
-
-encode_cert(#'Certificate'{} = Cert) ->
- {ok, List} = 'OTP-PKIX':encode('Certificate', Cert),
- list_to_binary(List).
-
-decode_rsa_keyfile(KeyFile, Password) ->
- {ok, List} = ssl_pem:read_file(KeyFile, Password),
- [PrivatKey] = [Bin || {rsa_private_key, Bin} <- List],
- 'OTP-PKIX':decode('RSAPrivateKey', PrivatKey).
-
-%%====================================================================
-%% Application internal API
-%%====================================================================
-
-%%--------------------------------------------------------------------
-%% Function: encoded_tbs_cert(Cert) -> PKXCert
-%%
-%% Cert = binary() - Der encoded
-%% PKXCert = binary() - Der encoded
-%%
-%% Description: Extracts the binary TBSCert from the binary Certificate.
-%%--------------------------------------------------------------------
-encoded_tbs_cert(Cert) ->
- {ok, PKIXCert} =
- 'OTP-PKIX':decode_TBSCert_exclusive(Cert),
- {'Certificate',
- {'Certificate_tbsCertificate', EncodedTBSCert}, _, _} = PKIXCert,
- EncodedTBSCert.
-
-%%--------------------------------------------------------------------
-%%% Internal functions
-%%--------------------------------------------------------------------
-
-cert_return(Cert, Opts) ->
- case lists:member(subject, Opts) of
- true ->
- {ok, get_subj(Cert)};
- false ->
- {ok, Cert}
- end.
-
-
-%% Transfrom from PKIX1-Explicit88 to SSL-PKIX.
-
-transform(#'Certificate'{signature = Signature,
- signatureAlgorithm = SignatureAlgorithm,
- tbsCertificate = TbsCertificate} = Cert, Type) ->
- Cert#'Certificate'{tbsCertificate = transform(TbsCertificate, Type),
- signatureAlgorithm = transform(SignatureAlgorithm, Type),
- signature = transform(Signature, Type)};
-
-%% -record('TBSCertificate',{
-%% version = asn1_DEFAULT, serialNumber, signature, issuer, validity, subject,
-%% subjectPublicKeyInfo, issuerUniqueID = asn1_NOVALUE,
-%% subjectUniqueID = asn1_NOVALUE, extensions = asn1_NOVALUE}).
-
-transform(#'TBSCertificate'{signature = Signature, issuer = Issuer,
- subject = Subject, extensions = Extensions,
- subjectPublicKeyInfo = SPKInfo} = TBSCert, Type) ->
- TBSCert#'TBSCertificate'{signature = transform(Signature, Type),
- issuer = transform(Issuer, Type),
- subject = transform(Subject, Type),
- subjectPublicKeyInfo = transform(SPKInfo, Type),
- extensions = transform_extensions(Extensions, Type)
- };
-
-transform(#'AlgorithmIdentifier'{algorithm = Algorithm,
- parameters = Params}, ssl) ->
- SignAlgAny =
- #'SignatureAlgorithm-Any'{algorithm = Algorithm, parameters = Params},
- {ok, AnyEnc} = 'OTP-PKIX':encode('SignatureAlgorithm-Any', SignAlgAny),
- {ok, SignAlgCd} = 'OTP-PKIX':decode('SignatureAlgorithm',
- list_to_binary(AnyEnc)),
- NAlgo = ssl_pkix_oid:id2atom(SignAlgCd#'SignatureAlgorithm'.algorithm),
- SignAlgCd#'SignatureAlgorithm'{algorithm = NAlgo};
-
-transform({rdnSequence, Lss}, Type) when is_list(Lss) ->
- {rdnSequence, [[transform(L, Type) || L <- Ls] || Ls <- Lss]};
-transform({rdnSequence, Lss}, _) ->
- {rdnSequence, Lss};
-
-transform(#'AttributeTypeAndValue'{} = ATAV, ssl) ->
- {ok, ATAVEnc} =
- 'OTP-PKIX':encode('AttributeTypeAndValue', ATAV),
- {ok, ATAVDec} = 'OTP-PKIX':decode('SSLAttributeTypeAndValue',
- list_to_binary(ATAVEnc)),
- AttrType = ATAVDec#'SSLAttributeTypeAndValue'.type,
- #'AttributeTypeAndValue'{type = ssl_pkix_oid:id2atom(AttrType),
- value =
- ATAVDec#'SSLAttributeTypeAndValue'.value};
-
-transform(#'AttributeTypeAndValue'{} = Att, pkix) ->
- Att;
-
-%% -record('SubjectPublicKeyInfo',{
-%% algorithm, subjectPublicKey}).
-%%
-%% -record('SubjectPublicKeyInfo_algorithm',{
-%% algo, parameters = asn1_NOVALUE}).
-%%
-%% -record('SubjectPublicKeyInfo-Any',{
-%% algorithm, subjectPublicKey}).
-%%
-%% -record('PublicKeyAlgorithm',{
-%% algorithm, parameters = asn1_NOVALUE}).
-
-transform(#'SubjectPublicKeyInfo'{subjectPublicKey = SubjectPublicKey,
- algorithm = Algorithm}, ssl) ->
- %% Transform from SubjectPublicKeyInfo (PKIX1Explicit88)
- %% to SubjectPublicKeyInfo-Any (SSL-PKIX).
- Algo = Algorithm#'AlgorithmIdentifier'.algorithm,
- Parameters = Algorithm#'AlgorithmIdentifier'.parameters,
- AlgorithmAny = #'PublicKeyAlgorithm'{algorithm = Algo,
- parameters = Parameters},
- {0, Bin} = SubjectPublicKey,
- SInfoAny = #'SSLSubjectPublicKeyInfo-Any'{algorithm = AlgorithmAny,
- subjectPublicKey = Bin},
-
- %% Encode according to SubjectPublicKeyInfo-Any, and decode according
- %% to SubjectPublicKeyInfo.
- {ok, AnyEnc} =
- 'OTP-PKIX':encode('SSLSubjectPublicKeyInfo-Any', SInfoAny),
- {ok, SInfoCd} = 'OTP-PKIX':decode('SSLSubjectPublicKeyInfo',
- list_to_binary(AnyEnc)),
- %% Replace object identifier by atom
- AlgorithmCd = SInfoCd#'SSLSubjectPublicKeyInfo'.algorithm,
- AlgoCd = AlgorithmCd#'SSLSubjectPublicKeyInfo_algorithm'.algo,
- Params = AlgorithmCd#'SSLSubjectPublicKeyInfo_algorithm'.parameters,
- Key = SInfoCd#'SSLSubjectPublicKeyInfo'.subjectPublicKey,
- NAlgoCd = ssl_pkix_oid:id2atom(AlgoCd),
- NAlgorithmCd =
- #'SubjectPublicKeyInfo_algorithm'{algorithm = NAlgoCd,
- parameters = Params},
- #'SubjectPublicKeyInfo'{algorithm = NAlgorithmCd,
- subjectPublicKey = Key
- };
-transform(#'SubjectPublicKeyInfo'{} = SInfo, pkix) ->
- SInfo;
-
-transform(#'Extension'{extnID = ExtnID} = Ext, ssl) ->
- NewExtID = ssl_pkix_oid:id2atom(ExtnID),
- ExtAny = setelement(1, Ext, 'Extension-Any'),
- {ok, AnyEnc} = 'OTP-PKIX':encode('Extension-Any', ExtAny),
- {ok, ExtCd} = 'OTP-PKIX':decode('SSLExtension', list_to_binary(AnyEnc)),
-
- ExtValue = transform_extension_value(NewExtID,
- ExtCd#'SSLExtension'.extnValue,
- ssl),
- #'Extension'{extnID = NewExtID,
- critical = ExtCd#'SSLExtension'.critical,
- extnValue = ExtValue};
-
-transform(#'Extension'{extnID = ExtnID, extnValue = ExtnValue} = Ext, pkix) ->
- NewExtID = ssl_pkix_oid:id2atom(ExtnID),
- ExtValue = transform_extension_value(NewExtID, ExtnValue, pkix),
- Ext#'Extension'{extnValue = ExtValue};
-
-transform(#'AuthorityKeyIdentifier'{authorityCertIssuer = CertIssuer} = Ext,
- Type) ->
- Ext#'AuthorityKeyIdentifier'{authorityCertIssuer =
- transform(CertIssuer, Type)};
-
-transform([{directoryName, Value}], Type) ->
- [{directoryName, transform(Value, Type)}];
-
-transform(X, _) ->
- X.
-
-transform_extension_value('ce-authorityKeyIdentifier', Value, Type) ->
- transform(Value, Type);
-transform_extension_value(_, Value, _) ->
- Value.
-
-transform_extensions(Exts, Type) when is_list(Exts) ->
- [transform(Ext, Type) || Ext <- Exts];
-transform_extensions(Exts, _) ->
- Exts.
-
-get_subj(Cert) ->
- (Cert#'Certificate'.tbsCertificate)#'TBSCertificate'.subject.
-
-signature_digest(BinSignature) ->
- case (catch 'OTP-PKIX':decode('DigestInfo', BinSignature)) of
- {ok, DigestInfo} ->
- list_to_binary(DigestInfo#'DigestInfo'.digest);
- _ ->
- {error, decode_error}
- end.
diff --git a/lib/ssl/src/ssl_pkix.hrl b/lib/ssl/src/ssl_pkix.hrl
deleted file mode 100644
index a8463369f6..0000000000
--- a/lib/ssl/src/ssl_pkix.hrl
+++ /dev/null
@@ -1,81 +0,0 @@
-%%
-%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-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%
-%%
-
-%%
-
--ifndef(ssl_pkix).
--define(ssl_pkix, true).
-
--include("OTP-PKIX.hrl").
-
-%% The following commented out records are currently defined in OTP-PKIX.hrl
-%% and are considered a public interface through ssl_pkix.hrl.
-%% NOTE do not include OTP-PKIX.hrl it is an generated file
-%% and may change but the following records will still be
-%% availanble from this file.
-
-% -record('Certificate', {
-% tbsCertificate,
-% signatureAlgorithm,
-% signature}).
-
-% -record('TBSCertificate', {
-% version = asn1_DEFAULT,
-% serialNumber,
-% signature,
-% issuer,
-% validity,
-% subject,
-% subjectPublicKeyInfo,
-% issuerUniqueID = asn1_NOVALUE,
-% subjectUniqueID = asn1_NOVALUE,
-% extensions = asn1_NOVALUE}).
-
-% -record('AttributeTypeAndValue', {
-% type,
-% value}).
-
-% -record('SubjectPublicKeyInfo', {
-% algorithm,
-% subjectPublicKey}).
-
--record('SubjectPublicKeyInfo_algorithm', {
- algorithm,
- parameters = asn1_NOVALUE}).
-
-% -record('FieldID', {
-% fieldType,
-% parameters}).
-
-% -record('Characteristic-two', {
-% m,
-% basis,
-% parameters}).
-
-% -record('ExtensionAttribute', {
-% extensionAttributeType,
-% extensionAttributeValue}).
-
-% -record('Extension', {
-% extnID,
-% critical = asn1_DEFAULT,
-% extnValue}).
-
--endif. % -ifdef(ssl_pkix).
-
diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl
index da48f049f6..90615c22a1 100644
--- a/lib/ssl/src/ssl_record.erl
+++ b/lib/ssl/src/ssl_record.erl
@@ -29,6 +29,7 @@
-include("ssl_internal.hrl").
-include("ssl_alert.hrl").
-include("ssl_handshake.hrl").
+-include("ssl_cipher.hrl").
-include("ssl_debug.hrl").
%% Connection state handling
@@ -38,7 +39,10 @@
set_mac_secret/4,
set_master_secret/2,
activate_pending_connection_state/2,
- set_pending_cipher_state/4]).
+ set_pending_cipher_state/4,
+ set_renegotiation_flag/2,
+ set_client_verify_data/3,
+ set_server_verify_data/3]).
%% Handling of incoming data
-export([get_tls_records/2]).
@@ -62,10 +66,9 @@
%%====================================================================
%% Internal application API
%%====================================================================
+
%%--------------------------------------------------------------------
-%% Function: init_connection_states(Role) -> #connection_states{}
-%% Role = client | server
-%% Random = binary()
+-spec init_connection_states(client | server) -> #connection_states{}.
%%
%% Description: Creates a connection_states record with appropriate
%% values for the initial SSL connection setup.
@@ -81,9 +84,8 @@ init_connection_states(Role) ->
}.
%%--------------------------------------------------------------------
-%% Function: current_connection_state(States, Type) -> #connection_state{}
-%% States = #connection_states{}
-%% Type = read | write
+-spec current_connection_state(#connection_states{}, read | write) ->
+ #connection_state{}.
%%
%% Description: Returns the instance of the connection_state record
%% that is currently defined as the current conection state.
@@ -96,9 +98,8 @@ current_connection_state(#connection_states{current_write = Current},
Current.
%%--------------------------------------------------------------------
-%% Function: pending_connection_state(States, Type) -> #connection_state{}
-%% States = #connection_states{}
-%% Type = read | write
+-spec pending_connection_state(#connection_states{}, read | write) ->
+ #connection_state{}.
%%
%% Description: Returns the instance of the connection_state record
%% that is currently defined as the pending conection state.
@@ -111,14 +112,11 @@ pending_connection_state(#connection_states{pending_write = Pending},
Pending.
%%--------------------------------------------------------------------
-%% Function: update_security_params(Params, States) ->
-%% #connection_states{}
-%% Params = #security_parameters{}
-%% States = #connection_states{}
+-spec update_security_params(#security_parameters{}, #security_parameters{},
+ #connection_states{}) -> #connection_states{}.
%%
%% Description: Creates a new instance of the connection_states record
-%% where the pending states gets its security parameters
-%% updated to <Params>.
+%% where the pending states gets its security parameters updated.
%%--------------------------------------------------------------------
update_security_params(ReadParams, WriteParams, States =
#connection_states{pending_read = Read,
@@ -131,14 +129,10 @@ update_security_params(ReadParams, WriteParams, States =
WriteParams}
}.
%%--------------------------------------------------------------------
-%% Function: set_mac_secret(ClientWriteMacSecret,
-%% ServerWriteMacSecret, Role, States) ->
-%% #connection_states{}
-%% MacSecret = binary()
-%% States = #connection_states{}
-%% Role = server | client
+-spec set_mac_secret(binary(), binary(), client | server,
+ #connection_states{}) -> #connection_states{}.
%%
-%% update the mac_secret field in pending connection states
+%% Description: update the mac_secret field in pending connection states
%%--------------------------------------------------------------------
set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, client, States) ->
set_mac_secret(ServerWriteMacSecret, ClientWriteMacSecret, States);
@@ -155,12 +149,9 @@ set_mac_secret(ReadMacSecret, WriteMacSecret,
%%--------------------------------------------------------------------
-%% Function: set_master_secret(MasterSecret, States) ->
-%% #connection_states{}
-%% MacSecret =
-%% States = #connection_states{}
+-spec set_master_secret(binary(), #connection_state{}) -> #connection_states{}.
%%
-%% Set master_secret in pending connection states
+%% Description: Set master_secret in pending connection states
%%--------------------------------------------------------------------
set_master_secret(MasterSecret,
States = #connection_states{pending_read = Read,
@@ -175,12 +166,94 @@ set_master_secret(MasterSecret,
master_secret = MasterSecret}},
States#connection_states{pending_read = Read1, pending_write = Write1}.
+%%--------------------------------------------------------------------
+-spec set_renegotiation_flag(boolean(), #connection_states{}) -> #connection_states{}.
+%%
+%% Description: Set secure_renegotiation in pending connection states
+%%--------------------------------------------------------------------
+set_renegotiation_flag(Flag, #connection_states{
+ current_read = CurrentRead0,
+ current_write = CurrentWrite0,
+ pending_read = PendingRead0,
+ pending_write = PendingWrite0}
+ = ConnectionStates) ->
+ CurrentRead = CurrentRead0#connection_state{secure_renegotiation = Flag},
+ CurrentWrite = CurrentWrite0#connection_state{secure_renegotiation = Flag},
+ PendingRead = PendingRead0#connection_state{secure_renegotiation = Flag},
+ PendingWrite = PendingWrite0#connection_state{secure_renegotiation = Flag},
+ ConnectionStates#connection_states{current_read = CurrentRead,
+ current_write = CurrentWrite,
+ pending_read = PendingRead,
+ pending_write = PendingWrite}.
%%--------------------------------------------------------------------
-%% Function: activate_pending_connection_state(States, Type) ->
-%% #connection_states{}
-%% States = #connection_states{}
-%% Type = read | write
+-spec set_client_verify_data(current_read | current_write | current_both,
+ binary(), #connection_states{})->
+ #connection_states{}.
+%%
+%% Description: Set verify data in connection states.
+%%--------------------------------------------------------------------
+set_client_verify_data(current_read, Data,
+ #connection_states{current_read = CurrentRead0,
+ pending_write = PendingWrite0}
+ = ConnectionStates) ->
+ CurrentRead = CurrentRead0#connection_state{client_verify_data = Data},
+ PendingWrite = PendingWrite0#connection_state{client_verify_data = Data},
+ ConnectionStates#connection_states{current_read = CurrentRead,
+ pending_write = PendingWrite};
+set_client_verify_data(current_write, Data,
+ #connection_states{pending_read = PendingRead0,
+ current_write = CurrentWrite0}
+ = ConnectionStates) ->
+ PendingRead = PendingRead0#connection_state{client_verify_data = Data},
+ CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data},
+ ConnectionStates#connection_states{pending_read = PendingRead,
+ current_write = CurrentWrite};
+set_client_verify_data(current_both, Data,
+ #connection_states{current_read = CurrentRead0,
+ current_write = CurrentWrite0}
+ = ConnectionStates) ->
+ CurrentRead = CurrentRead0#connection_state{client_verify_data = Data},
+ CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data},
+ ConnectionStates#connection_states{current_read = CurrentRead,
+ current_write = CurrentWrite}.
+%%--------------------------------------------------------------------
+-spec set_server_verify_data(current_read | current_write | current_both,
+ binary(), #connection_states{})->
+ #connection_states{}.
+%%
+%% Description: Set verify data in pending connection states.
+%%--------------------------------------------------------------------
+set_server_verify_data(current_write, Data,
+ #connection_states{pending_read = PendingRead0,
+ current_write = CurrentWrite0}
+ = ConnectionStates) ->
+ PendingRead = PendingRead0#connection_state{server_verify_data = Data},
+ CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data},
+ ConnectionStates#connection_states{pending_read = PendingRead,
+ current_write = CurrentWrite};
+
+set_server_verify_data(current_read, Data,
+ #connection_states{current_read = CurrentRead0,
+ pending_write = PendingWrite0}
+ = ConnectionStates) ->
+ CurrentRead = CurrentRead0#connection_state{server_verify_data = Data},
+ PendingWrite = PendingWrite0#connection_state{server_verify_data = Data},
+ ConnectionStates#connection_states{current_read = CurrentRead,
+ pending_write = PendingWrite};
+
+set_server_verify_data(current_both, Data,
+ #connection_states{current_read = CurrentRead0,
+ current_write = CurrentWrite0}
+ = ConnectionStates) ->
+ CurrentRead = CurrentRead0#connection_state{server_verify_data = Data},
+ CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data},
+ ConnectionStates#connection_states{current_read = CurrentRead,
+ current_write = CurrentWrite}.
+
+%%--------------------------------------------------------------------
+-spec activate_pending_connection_state(#connection_states{}, read | write) ->
+ #connection_states{}.
%%
%% Description: Creates a new instance of the connection_states record
%% where the pending state of <Type> has been activated.
@@ -191,7 +264,9 @@ activate_pending_connection_state(States =
NewCurrent = Pending#connection_state{sequence_number = 0},
SecParams = Pending#connection_state.security_parameters,
ConnectionEnd = SecParams#security_parameters.connection_end,
- NewPending = empty_connection_state(ConnectionEnd),
+ EmptyPending = empty_connection_state(ConnectionEnd),
+ SecureRenegotation = NewCurrent#connection_state.secure_renegotiation,
+ NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation},
States#connection_states{current_read = NewCurrent,
pending_read = NewPending
};
@@ -202,17 +277,17 @@ activate_pending_connection_state(States =
NewCurrent = Pending#connection_state{sequence_number = 0},
SecParams = Pending#connection_state.security_parameters,
ConnectionEnd = SecParams#security_parameters.connection_end,
- NewPending = empty_connection_state(ConnectionEnd),
+ EmptyPending = empty_connection_state(ConnectionEnd),
+ SecureRenegotation = NewCurrent#connection_state.secure_renegotiation,
+ NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation},
States#connection_states{current_write = NewCurrent,
pending_write = NewPending
}.
%%--------------------------------------------------------------------
-%% Function: set_pending_cipher_state(States, ClientState,
-%% ServerState, Role) ->
-%% #connection_states{}
-%% ClientState = ServerState = #cipher_state{}
-%% States = #connection_states{}
+-spec set_pending_cipher_state(#connection_states{}, #cipher_state{},
+ #cipher_state{}, client | server) ->
+ #connection_states{}.
%%
%% Description: Set the cipher state in the specified pending connection state.
%%--------------------------------------------------------------------
@@ -231,12 +306,10 @@ set_pending_cipher_state(#connection_states{pending_read = Read,
pending_write = Write#connection_state{cipher_state = ClientState}}.
%%--------------------------------------------------------------------
-%% Function: get_tls_record(Data, Buffer) -> Result
-%% Result = {[#tls_compressed{}], NewBuffer}
-%% Data = Buffer = NewBuffer = binary()
-%%
-%% Description: given old buffer and new data from TCP, packs up a records
-%% and returns it as a list of #tls_compressed, also returns leftover
+-spec get_tls_records(binary(), binary()) -> {[binary()], binary()}.
+%%
+%% Description: Given old buffer and new data from TCP, packs up a records
+%% and returns it as a list of tls_compressed binaries also returns leftover
%% data
%%--------------------------------------------------------------------
get_tls_records(Data, <<>>) ->
@@ -299,8 +372,7 @@ get_tls_records_aux(Data, Acc) ->
{lists:reverse(Acc), Data}.
%%--------------------------------------------------------------------
-%% Function: protocol_version(Version) -> #protocol_version{}
-%% Version = atom()
+-spec protocol_version(tls_atom_version()) -> tls_version().
%%
%% Description: Creates a protocol version record from a version atom
%% or vice versa.
@@ -311,19 +383,16 @@ protocol_version(tlsv1) ->
{3, 1};
protocol_version(sslv3) ->
{3, 0};
-protocol_version(sslv2) ->
+protocol_version(sslv2) -> %% Backwards compatibility
{2, 0};
protocol_version({3, 2}) ->
'tlsv1.1';
protocol_version({3, 1}) ->
tlsv1;
protocol_version({3, 0}) ->
- sslv3;
-protocol_version({2, 0}) ->
- sslv2.
+ sslv3.
%%--------------------------------------------------------------------
-%% Function: protocol_version(Version1, Version2) -> #protocol_version{}
-%% Version1 = Version2 = #protocol_version{}
+-spec lowest_protocol_version(tls_version(), tls_version()) -> tls_version().
%%
%% Description: Lowes protocol version of two given versions
%%--------------------------------------------------------------------
@@ -338,8 +407,7 @@ lowest_protocol_version(Version = {M,_},
lowest_protocol_version(_,Version) ->
Version.
%%--------------------------------------------------------------------
-%% Function: protocol_version(Versions) -> #protocol_version{}
-%% Versions = [#protocol_version{}]
+-spec highest_protocol_version([tls_version()]) -> tls_version().
%%
%% Description: Highest protocol version present in a list
%%--------------------------------------------------------------------
@@ -361,14 +429,13 @@ highest_protocol_version(_, [Version | Rest]) ->
highest_protocol_version(Version, Rest).
%%--------------------------------------------------------------------
-%% Function: supported_protocol_versions() -> Versions
-%% Versions = [#protocol_version{}]
-%%
+-spec supported_protocol_versions() -> [tls_version()].
+%%
%% Description: Protocol versions supported
%%--------------------------------------------------------------------
supported_protocol_versions() ->
Fun = fun(Version) ->
- protocol_version(Version)
+ protocol_version(Version)
end,
case application:get_env(ssl, protocol_version) of
undefined ->
@@ -376,14 +443,20 @@ supported_protocol_versions() ->
{ok, []} ->
lists:map(Fun, ?DEFAULT_SUPPORTED_VERSIONS);
{ok, Vsns} when is_list(Vsns) ->
- lists:map(Fun, Vsns);
+ Versions = lists:filter(fun is_acceptable_version/1, lists:map(Fun, Vsns)),
+ supported_protocol_versions(Versions);
{ok, Vsn} ->
- [Fun(Vsn)]
+ Versions = lists:filter(fun is_acceptable_version/1, [Fun(Vsn)]),
+ supported_protocol_versions(Versions)
end.
+supported_protocol_versions([]) ->
+ ?DEFAULT_SUPPORTED_VERSIONS;
+supported_protocol_versions([_|_] = Vsns) ->
+ Vsns.
+
%%--------------------------------------------------------------------
-%% Function: is_acceptable_version(Version) -> true | false
-%% Version = #protocol_version{}
+-spec is_acceptable_version(tls_version()) -> boolean().
%%
%% Description: ssl version 2 is not acceptable security risks are too big.
%%--------------------------------------------------------------------
@@ -394,7 +467,7 @@ is_acceptable_version(_) ->
false.
%%--------------------------------------------------------------------
-%% Function: compressions() -> binary()
+-spec compressions() -> binary().
%%
%% Description: return a list of compressions supported (currently none)
%%--------------------------------------------------------------------
@@ -402,8 +475,8 @@ compressions() ->
[?byte(?NULL)].
%%--------------------------------------------------------------------
-%% Function: decode_cipher_text(CipherText, ConnectionStates0) ->
-%% {Plain, ConnectionStates}
+-spec decode_cipher_text(#ssl_tls{}, #connection_states{}) ->
+ {#ssl_tls{}, #connection_states{}}.
%%
%% Description: Decode cipher text
%%--------------------------------------------------------------------
@@ -412,13 +485,17 @@ decode_cipher_text(CipherText, ConnnectionStates0) ->
#connection_state{compression_state = CompressionS0,
security_parameters = SecParams} = ReadState0,
CompressAlg = SecParams#security_parameters.compression_algorithm,
- {Compressed, ReadState1} = decipher(CipherText, ReadState0),
- {Plain, CompressionS1} = uncompress(CompressAlg,
- Compressed, CompressionS0),
- ConnnectionStates = ConnnectionStates0#connection_states{
- current_read = ReadState1#connection_state{
- compression_state = CompressionS1}},
- {Plain, ConnnectionStates}.
+ case decipher(CipherText, ReadState0) of
+ {Compressed, ReadState1} ->
+ {Plain, CompressionS1} = uncompress(CompressAlg,
+ Compressed, CompressionS0),
+ ConnnectionStates = ConnnectionStates0#connection_states{
+ current_read = ReadState1#connection_state{
+ compression_state = CompressionS1}},
+ {Plain, ConnnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end.
%%--------------------------------------------------------------------
%%% Internal functions
@@ -433,12 +510,10 @@ initial_connection_state(ConnectionEnd) ->
}.
initial_security_params(ConnectionEnd) ->
- #security_parameters{connection_end = ConnectionEnd,
- bulk_cipher_algorithm = ?NULL,
- mac_algorithm = ?NULL,
- compression_algorithm = ?NULL,
- cipher_type = ?NULL
- }.
+ SecParams = #security_parameters{connection_end = ConnectionEnd,
+ compression_algorithm = ?NULL},
+ ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL,
+ SecParams).
empty_connection_state(ConnectionEnd) ->
SecParams = empty_security_params(ConnectionEnd),
@@ -544,29 +619,37 @@ encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) ->
cipher(Type, Version, Fragment, CS0) ->
Length = erlang:iolist_size(Fragment),
- {Hash, CS1=#connection_state{cipher_state = CipherS0,
+ {MacHash, CS1=#connection_state{cipher_state = CipherS0,
security_parameters=
#security_parameters{bulk_cipher_algorithm =
BCA}
}} =
hash_and_bump_seqno(CS0, Type, Version, Length, Fragment),
?DBG_HEX(Fragment),
- {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, Hash, Fragment),
+ {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment),
?DBG_HEX(Ciphered),
CS2 = CS1#connection_state{cipher_state=CipherS1},
{Ciphered, CS2}.
decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) ->
SP = CS0#connection_state.security_parameters,
- BCA = SP#security_parameters.bulk_cipher_algorithm, % eller Cipher?
+ BCA = SP#security_parameters.bulk_cipher_algorithm,
HashSz = SP#security_parameters.hash_size,
CipherS0 = CS0#connection_state.cipher_state,
- {T, Mac, CipherS1} = ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment),
- CS1 = CS0#connection_state{cipher_state = CipherS1},
- TLength = size(T),
- {Hash, CS2} = hash_and_bump_seqno(CS1, Type, Version, TLength, Fragment),
- ok = check_hash(Hash, Mac),
- {TLS#ssl_tls{fragment = T}, CS2}.
+ case ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment, Version) of
+ {T, Mac, CipherS1} ->
+ CS1 = CS0#connection_state{cipher_state = CipherS1},
+ TLength = size(T),
+ {MacHash, CS2} = hash_and_bump_seqno(CS1, Type, Version, TLength, T),
+ case is_correct_mac(Mac, MacHash) of
+ true ->
+ {TLS#ssl_tls{fragment = T}, CS2};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+ #alert{} = Alert ->
+ Alert
+ end.
uncompress(?NULL, Data = #ssl_tls{type = _Type,
version = _Version,
@@ -587,10 +670,12 @@ hash_and_bump_seqno(#connection_state{sequence_number = SeqNo,
Length, Fragment),
{Hash, CS0#connection_state{sequence_number = SeqNo+1}}.
-check_hash(_, _) ->
- ok. %% TODO check this
+is_correct_mac(Mac, Mac) ->
+ true;
+is_correct_mac(_M,_H) ->
+ false.
-mac_hash(?NULL, {_,_}, _MacSecret, _SeqNo, _Type,
+mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type,
_Length, _Fragment) ->
<<>>;
mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl
index 362b7039d4..5fb0070b91 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -60,7 +60,11 @@
compression_state,
cipher_state,
mac_secret,
- sequence_number
+ sequence_number,
+ %% RFC 5746
+ secure_renegotiation,
+ client_verify_data,
+ server_verify_data
}).
-define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19
diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl
index bcb10daf69..e9755cb0e1 100644
--- a/lib/ssl/src/ssl_session.erl
+++ b/lib/ssl/src/ssl_session.erl
@@ -32,11 +32,10 @@
-define(GEN_UNIQUE_ID_MAX_TRIES, 10).
+-type seconds() :: integer().
+
%%--------------------------------------------------------------------
-%% Function: is_new(ClientSuggestedId, ServerDecidedId) -> true | false
-%%
-%% ClientSuggestedId = binary()
-%% ServerDecidedId = binary()
+-spec is_new(binary(), binary()) -> boolean().
%%
%% Description: Checks if the session id decided by the server is a
%% new or resumed sesion id.
@@ -45,17 +44,11 @@ is_new(<<>>, _) ->
true;
is_new(SessionId, SessionId) ->
false;
-is_new(_, _) ->
+is_new(_ClientSuggestion, _ServerDecision) ->
true.
%%--------------------------------------------------------------------
-%% Function: id(ClientInfo, Cache, CacheCb) -> SessionId
-%%
-%% ClientInfo = {HostIP, Port, SslOpts}
-%% HostIP = ipadress()
-%% Port = integer()
-%% CacheCb = atom()
-%% SessionId = binary()
+-spec id({host(), port_num(), #ssl_options{}}, cache_ref(), atom()) -> binary().
%%
%% Description: Should be called by the client side to get an id
%% for the client hello message.
@@ -69,14 +62,8 @@ id(ClientInfo, Cache, CacheCb) ->
end.
%%--------------------------------------------------------------------
-%% Function: id(Port, SuggestedSessionId, ReuseFun, CacheCb,
-%% SecondLifeTime) -> SessionId
-%%
-%% Port = integer()
-%% SuggestedSessionId = SessionId = binary()
-%% ReuseFun = fun(SessionId, PeerCert, Compression, CipherSuite) ->
-%% true | false
-%% CacheCb = atom()
+-spec id(port_num(), binary(), #ssl_options{}, cache_ref(),
+ atom(), seconds()) -> binary().
%%
%% Description: Should be called by the server side to get an id
%% for the server hello message.
@@ -95,10 +82,7 @@ id(Port, SuggestedSessionId, #ssl_options{reuse_sessions = ReuseEnabled,
new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb)
end.
%%--------------------------------------------------------------------
-%% Function: valid_session(Session, LifeTime) -> true | false
-%%
-%% Session = #session{}
-%% LifeTime = integer() - seconds
+-spec valid_session(#session{}, seconds()) -> boolean().
%%
%% Description: Check that the session has not expired
%%--------------------------------------------------------------------
diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl
index 4a60892235..823bf7acfa 100644
--- a/lib/ssl/src/ssl_session_cache.erl
+++ b/lib/ssl/src/ssl_session_cache.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -22,23 +22,24 @@
-behaviour(ssl_session_cache_api).
--export([init/0, terminate/1, lookup/2, update/3, delete/2, foldl/3,
- select_session/2]).
+-include("ssl_handshake.hrl").
+-include("ssl_internal.hrl").
+
+-export([init/1, terminate/1, lookup/2, update/3, delete/2, foldl/3,
+ select_session/2]).
+
+-type key() :: {{host(), port_num()}, session_id()} | {port_num(), session_id()}.
%%--------------------------------------------------------------------
-%% Function: init() -> Cache
-%%
-%% Cache - Reference to the cash (opaque)
+-spec init(list()) -> cache_ref(). %% Returns reference to the cache (opaque)
%%
%% Description: Return table reference. Called by ssl_manager process.
%%--------------------------------------------------------------------
-init() ->
+init(_) ->
ets:new(cache_name(), [set, protected]).
%%--------------------------------------------------------------------
-%% Function: terminate(Cache) ->
-%%
-%% Cache - as returned by create/0
+-spec terminate(cache_ref()) -> any(). %%
%%
%% Description: Handles cache table at termination of ssl manager.
%%--------------------------------------------------------------------
@@ -46,9 +47,7 @@ terminate(Cache) ->
ets:delete(Cache).
%%--------------------------------------------------------------------
-%% Function: lookup(Cache, Key) -> Session | undefined
-%% Cache - as returned by create/0
-%% Session = #session{}
+-spec lookup(cache_ref(), key()) -> #session{} | undefined.
%%
%% Description: Looks up a cach entry. Should be callable from any
%% process.
@@ -62,9 +61,7 @@ lookup(Cache, Key) ->
end.
%%--------------------------------------------------------------------
-%% Function: update(Cache, Key, Session) -> _
-%% Cache - as returned by create/0
-%% Session = #session{}
+-spec update(cache_ref(), key(), #session{}) -> any().
%%
%% Description: Caches a new session or updates a already cached one.
%% Will only be called from the ssl_manager process.
@@ -73,11 +70,7 @@ update(Cache, Key, Session) ->
ets:insert(Cache, {Key, Session}).
%%--------------------------------------------------------------------
-%% Function: delete(Cache, HostIP, Port, Id) -> _
-%% Cache - as returned by create/0
-%% HostIP = Host = string() | ipadress()
-%% Port = integer()
-%% Id =
+-spec delete(cache_ref(), key()) -> any().
%%
%% Description: Delets a cache entry.
%% Will only be called from the ssl_manager process.
@@ -86,28 +79,19 @@ delete(Cache, Key) ->
ets:delete(Cache, Key).
%%--------------------------------------------------------------------
-%% Function: foldl(Fun, Acc0, Cache) -> Acc
-%%
-%% Fun - fun()
-%% Acc0 - term()
-%% Cache - cache_ref()
-%%
+-spec foldl(fun(), term(), cache_ref()) -> term().
%%
%% Description: Calls Fun(Elem, AccIn) on successive elements of the
%% cache, starting with AccIn == Acc0. Fun/2 must return a new
%% accumulator which is passed to the next call. The function returns
-%% the final value of the accumulator. Acc0 is returned if the cache is
-%% empty.
-%% Should be callable from any process
+%% the final value of the accumulator. Acc0 is returned if the cache
+%% is empty.Should be callable from any process
%%--------------------------------------------------------------------
foldl(Fun, Acc0, Cache) ->
ets:foldl(Fun, Acc0, Cache).
%%--------------------------------------------------------------------
-%% Function: select_session(Cache, PartialKey) -> [Sessions]
-%%
-%% Cache - as returned by create/0
-%% PartialKey - opaque Key = {PartialKey, SessionId}
+-spec select_session(cache_ref(), {host(), port_num()} | port_num()) -> [#session{}].
%%
%% Description: Selects a session that could be reused. Should be callable
%% from any process.
@@ -119,6 +103,5 @@ select_session(Cache, PartialKey) ->
%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
-
cache_name() ->
ssl_otp_session_cache.
diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl
index d2e846e9fd..f8416bf327 100644
--- a/lib/ssl/src/ssl_session_cache_api.erl
+++ b/lib/ssl/src/ssl_session_cache_api.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -25,7 +25,7 @@
behaviour_info(callbacks) ->
[
- {init, 0},
+ {init, 1},
{terminate, 1},
{lookup, 2},
{update, 3},
diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_ssl3.erl
index df809ce275..375adf263a 100644
--- a/lib/ssl/src/ssl_ssl3.erl
+++ b/lib/ssl/src/ssl_ssl3.erl
@@ -30,7 +30,7 @@
-include("ssl_record.hrl"). % MD5 and SHA
-export([master_secret/3, finished/3, certificate_verify/3,
- mac_hash/6, setup_keys/8,
+ mac_hash/6, setup_keys/7,
suites/0]).
-compile(inline).
@@ -38,6 +38,8 @@
%% Internal application API
%%====================================================================
+-spec master_secret(binary(), binary(), binary()) -> binary().
+
master_secret(PremasterSecret, ClientRandom, ServerRandom) ->
?DBG_HEX(PremasterSecret),
?DBG_HEX(ClientRandom),
@@ -57,6 +59,8 @@ master_secret(PremasterSecret, ClientRandom, ServerRandom) ->
?DBG_HEX(B),
B.
+-spec finished(client | server, binary(), {binary(), binary()}) -> binary().
+
finished(Role, MasterSecret, {MD5Hash, SHAHash}) ->
%% draft-ietf-tls-ssl-version3-00 - 5.6.9 Finished
%% struct {
@@ -75,8 +79,10 @@ finished(Role, MasterSecret, {MD5Hash, SHAHash}) ->
SHA = handshake_hash(?SHA, MasterSecret, Sender, SHAHash),
<<MD5/binary, SHA/binary>>.
+-spec certificate_verify(key_algo(), binary(), {binary(), binary()}) -> binary().
+
certificate_verify(Algorithm, MasterSecret, {MD5Hash, SHAHash})
- when Algorithm == rsa; Algorithm == dh_rsa; Algorithm == dhe_rsa ->
+ when Algorithm == rsa; Algorithm == dhe_rsa ->
%% md5_hash
%% MD5(master_secret + pad_2 +
%% MD5(handshake_messages + master_secret + pad_1));
@@ -88,13 +94,14 @@ certificate_verify(Algorithm, MasterSecret, {MD5Hash, SHAHash})
SHA = handshake_hash(?SHA, MasterSecret, undefined, SHAHash),
<<MD5/binary, SHA/binary>>;
-certificate_verify(Algorithm, MasterSecret, {_, SHAHash})
- when Algorithm == dh_dss; Algorithm == dhe_dss ->
+certificate_verify(dhe_dss, MasterSecret, {_, SHAHash}) ->
%% sha_hash
%% SHA(master_secret + pad_2 +
%% SHA(handshake_messages + master_secret + pad_1));
handshake_hash(?SHA, MasterSecret, undefined, SHAHash).
+-spec mac_hash(integer(), binary(), integer(), integer(), integer(), binary()) -> binary().
+
mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) ->
%% draft-ietf-tls-ssl-version3-00 - 5.2.3.1
%% hash(MAC_write_secret + pad_2 +
@@ -114,9 +121,11 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) ->
?DBG_HEX(Mac),
Mac.
-setup_keys(Exportable, MasterSecret, ServerRandom, ClientRandom,
- HS, KML, _EKML, IVS)
- when Exportable == no_export; Exportable == ignore ->
+-spec setup_keys(binary(), binary(), binary(), binary(),
+ integer(), integer(), binary()) -> {binary(), binary(), binary(),
+ binary(), binary(), binary()}.
+
+setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) ->
KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom,
2*(HS+KML+IVS)),
%% draft-ietf-tls-ssl-version3-00 - 6.2.2
@@ -137,79 +146,25 @@ setup_keys(Exportable, MasterSecret, ServerRandom, ClientRandom,
?DBG_HEX(ClientIV),
?DBG_HEX(ServerIV),
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
- ServerWriteKey, ClientIV, ServerIV};
-
-setup_keys(export, MasterSecret, ServerRandom, ClientRandom,
- HS, KML, EKML, IVS) ->
- KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom,
- 2*(HS+KML)),
- %% draft-ietf-tls-ssl-version3-00 - 6.2.2
- %% Exportable encryption algorithms (for which
- %% CipherSpec.is_exportable is true) require additional processing as
- %% follows to derive their final write keys:
+ ServerWriteKey, ClientIV, ServerIV}.
- %% final_client_write_key = MD5(client_write_key +
- %% ClientHello.random +
- %% ServerHello.random);
- %% final_server_write_key = MD5(server_write_key +
- %% ServerHello.random +
- %% ClientHello.random);
-
- %% Exportable encryption algorithms derive their IVs from the random
- %% messages:
- %% client_write_IV = MD5(ClientHello.random + ServerHello.random);
- %% server_write_IV = MD5(ServerHello.random + ClientHello.random);
-
- <<ClientWriteMacSecret:HS/binary, ServerWriteMacSecret:HS/binary,
- ClientWriteKey:KML/binary, ServerWriteKey:KML/binary>> = KeyBlock,
- <<ClientIV:IVS/binary, _/binary>> =
- hash(?MD5, [ClientRandom, ServerRandom]),
- <<ServerIV:IVS/binary, _/binary>> =
- hash(?MD5, [ServerRandom, ClientRandom]),
- <<FinalClientWriteKey:EKML/binary, _/binary>> =
- hash(?MD5, [ClientWriteKey, ClientRandom, ServerRandom]),
- <<FinalServerWriteKey:EKML/binary, _/binary>> =
- hash(?MD5, [ServerWriteKey, ServerRandom, ClientRandom]),
- ?DBG_HEX(ClientWriteMacSecret),
- ?DBG_HEX(ServerWriteMacSecret),
- ?DBG_HEX(FinalClientWriteKey),
- ?DBG_HEX(FinalServerWriteKey),
- ?DBG_HEX(ClientIV),
- ?DBG_HEX(ServerIV),
- {ClientWriteMacSecret, ServerWriteMacSecret, FinalClientWriteKey,
- FinalServerWriteKey, ClientIV, ServerIV}.
+-spec suites() -> [cipher_suite()].
suites() ->
[
- %% TODO: uncomment when supported
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- %% ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
?TLS_RSA_WITH_AES_256_CBC_SHA,
?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
- %% ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- %% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_AES_128_CBC_SHA,
- %%?TLS_DHE_DSS_WITH_RC4_128_SHA, TODO: Support this?
- %% ?TLS_RSA_WITH_IDEA_CBC_SHA, Not supported: in later openssl version than OTP requires
-
+ %%?TLS_RSA_WITH_IDEA_CBC_SHA,
?TLS_RSA_WITH_RC4_128_SHA,
?TLS_RSA_WITH_RC4_128_MD5,
- %%?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,
- %%?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5,
- %%?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
- %%?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA,
- %%?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
- %%?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA,
- %%?TLS_DHE_DSS_WITH_RC4_128_SHA,
-
?TLS_RSA_WITH_DES_CBC_SHA
- %% ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
- %% ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
- %% ?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
- %%?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
- %%?TLS_RSA_EXPORT_WITH_RC4_40_MD5
].
%%--------------------------------------------------------------------
@@ -269,8 +224,7 @@ handshake_hash(Method, MasterSecret, Sender, HandshakeHash) ->
hash(Method, [MasterSecret, pad_2(Method), InnerHash]).
get_sender(client) -> "CLNT";
-get_sender(server) -> "SRVR";
-get_sender(none) -> "".
+get_sender(server) -> "SRVR".
generate_keyblock(MasterSecret, ServerRandom, ClientRandom, WantedLength) ->
gen(MasterSecret, [MasterSecret, ServerRandom, ClientRandom],
diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl
index bd5a02417a..b7cb5c3ab3 100644
--- a/lib/ssl/src/ssl_sup.erl
+++ b/lib/ssl/src/ssl_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-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%
%%
@@ -40,8 +40,7 @@ start_link() ->
%%%=========================================================================
%% init([]) -> {ok, {SupFlags, [ChildSpec]}}
%%
-init([]) ->
-
+init([]) ->
%% OLD ssl - moved start to ssl.erl only if old
%% ssl is acctualy run!
%%Child1 = {ssl_server, {ssl_server, start_link, []},
@@ -67,7 +66,7 @@ init([]) ->
session_and_cert_manager_child_spec() ->
Opts = manager_opts(),
Name = ssl_manager,
- StartFunc = {ssl_manager, start_link, Opts},
+ StartFunc = {ssl_manager, start_link, [Opts]},
Restart = permanent,
Shutdown = 4000,
Modules = [ssl_manager],
@@ -86,11 +85,12 @@ connection_manager_child_spec() ->
manager_opts() ->
CbOpts = case application:get_env(ssl, session_cb) of
- {ok, Cb} when is_atom(Cb) ->
- [{session_cb, Cb}];
- _ ->
- []
- end,
+ {ok, Cb} when is_atom(Cb) ->
+ InitArgs = session_cb_init_args(),
+ [{session_cb, Cb}, {session_cb_init_args, InitArgs}];
+ _ ->
+ []
+ end,
case application:get_env(ssl, session_lifetime) of
{ok, Time} when is_integer(Time) ->
[{session_lifetime, Time}| CbOpts];
@@ -98,3 +98,10 @@ manager_opts() ->
CbOpts
end.
+session_cb_init_args() ->
+ case application:get_env(ssl, session_cb_init_args) of
+ {ok, Args} when is_list(Args) ->
+ Args;
+ _ ->
+ []
+ end.
diff --git a/lib/ssl/src/ssl_tls1.erl b/lib/ssl/src/ssl_tls1.erl
index ce9a135168..d1bc0730ba 100644
--- a/lib/ssl/src/ssl_tls1.erl
+++ b/lib/ssl/src/ssl_tls1.erl
@@ -30,12 +30,14 @@
-include("ssl_debug.hrl").
-export([master_secret/3, finished/3, certificate_verify/2, mac_hash/7,
- setup_keys/5, setup_keys/6, suites/0]).
+ setup_keys/6, suites/0]).
%%====================================================================
%% Internal application API
%%====================================================================
+-spec master_secret(binary(), binary(), binary()) -> binary().
+
master_secret(PreMasterSecret, ClientRandom, ServerRandom) ->
%% RFC 2246 & 4346 - 8.1 %% master_secret = PRF(pre_master_secret,
%% "master secret", ClientHello.random +
@@ -43,6 +45,8 @@ master_secret(PreMasterSecret, ClientRandom, ServerRandom) ->
prf(PreMasterSecret, <<"master secret">>,
[ClientRandom, ServerRandom], 48).
+-spec finished(client | server, binary(), {binary(), binary()}) -> binary().
+
finished(Role, MasterSecret, {MD5Hash, SHAHash}) ->
%% RFC 2246 & 4346 - 7.4.9. Finished
%% struct {
@@ -56,18 +60,21 @@ finished(Role, MasterSecret, {MD5Hash, SHAHash}) ->
SHA = hash_final(?SHA, SHAHash),
prf(MasterSecret, finished_label(Role), [MD5, SHA], 12).
+-spec certificate_verify(key_algo(), {binary(), binary()}) -> binary().
certificate_verify(Algorithm, {MD5Hash, SHAHash}) when Algorithm == rsa;
- Algorithm == dh_rsa;
Algorithm == dhe_rsa ->
MD5 = hash_final(?MD5, MD5Hash),
SHA = hash_final(?SHA, SHAHash),
<<MD5/binary, SHA/binary>>;
-certificate_verify(Algorithm, {_, SHAHash}) when Algorithm == dh_dss;
- Algorithm == dhe_dss ->
+certificate_verify(dhe_dss, {_, SHAHash}) ->
hash_final(?SHA, SHAHash).
-
+
+-spec setup_keys(binary(), binary(), binary(), integer(),
+ integer(), integer()) -> {binary(), binary(), binary(),
+ binary(), binary(), binary()}.
+
setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize,
KeyMatLen, IVSize) ->
%% RFC 2246 - 6.3. Key calculation
@@ -92,26 +99,30 @@ setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize,
{ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
ServerWriteKey, ClientIV, ServerIV}.
-setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize, KeyMatLen) ->
- %% RFC 4346 - 6.3. Key calculation
- %% key_block = PRF(SecurityParameters.master_secret,
- %% "key expansion",
- %% SecurityParameters.server_random +
- %% SecurityParameters.client_random);
- %% Then the key_block is partitioned as follows:
- %% client_write_MAC_secret[SecurityParameters.hash_size]
- %% server_write_MAC_secret[SecurityParameters.hash_size]
- %% client_write_key[SecurityParameters.key_material_length]
- %% server_write_key[SecurityParameters.key_material_length]
- WantedLength = 2 * (HashSize + KeyMatLen),
- KeyBlock = prf(MasterSecret, "key expansion",
- [ServerRandom, ClientRandom], WantedLength),
- <<ClientWriteMacSecret:HashSize/binary,
- ServerWriteMacSecret:HashSize/binary,
- ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary>>
- = KeyBlock,
- {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
- ServerWriteKey, undefined, undefined}.
+%% TLS v1.1 uncomment when supported.
+%% setup_keys(MasterSecret, ServerRandom, ClientRandom, HashSize, KeyMatLen) ->
+%% %% RFC 4346 - 6.3. Key calculation
+%% %% key_block = PRF(SecurityParameters.master_secret,
+%% %% "key expansion",
+%% %% SecurityParameters.server_random +
+%% %% SecurityParameters.client_random);
+%% %% Then the key_block is partitioned as follows:
+%% %% client_write_MAC_secret[SecurityParameters.hash_size]
+%% %% server_write_MAC_secret[SecurityParameters.hash_size]
+%% %% client_write_key[SecurityParameters.key_material_length]
+%% %% server_write_key[SecurityParameters.key_material_length]
+%% WantedLength = 2 * (HashSize + KeyMatLen),
+%% KeyBlock = prf(MasterSecret, "key expansion",
+%% [ServerRandom, ClientRandom], WantedLength),
+%% <<ClientWriteMacSecret:HashSize/binary,
+%% ServerWriteMacSecret:HashSize/binary,
+%% ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary>>
+%% = KeyBlock,
+%% {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey,
+%% ServerWriteKey, undefined, undefined}.
+
+-spec mac_hash(integer(), binary(), integer(), integer(), tls_version(),
+ integer(), binary()) -> binary().
mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
Length, Fragment) ->
@@ -133,37 +144,24 @@ mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor},
?DBG_HEX(Mac),
Mac.
+-spec suites() -> [cipher_suite()].
+
suites() ->
[
- %% TODO: uncomment when supported
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA,
- %%?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA,
?TLS_RSA_WITH_AES_256_CBC_SHA,
?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA,
- %% ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA,
?TLS_RSA_WITH_3DES_EDE_CBC_SHA,
?TLS_DHE_RSA_WITH_AES_128_CBC_SHA,
- %% ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
+ ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA,
?TLS_RSA_WITH_AES_128_CBC_SHA,
- %%?TLS_DHE_DSS_WITH_RC4_128_SHA, TODO: Support this?
- %% ?TLS_RSA_WITH_IDEA_CBC_SHA,
+ %%?TLS_RSA_WITH_IDEA_CBC_SHA,
?TLS_RSA_WITH_RC4_128_SHA,
?TLS_RSA_WITH_RC4_128_MD5,
- %%?TLS_RSA_EXPORT1024_WITH_RC4_56_MD5,
- %%?TLS_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5,
- %%?TLS_RSA_EXPORT1024_WITH_DES_CBC_SHA,
- %%?TLS_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA,
- %%?TLS_RSA_EXPORT1024_WITH_RC4_56_SHA,
- %%?TLS_DHE_DSS_EXPORT1024_WITH_RC4_56_SHA,
- %%?TLS_DHE_DSS_WITH_RC4_128_SHA,
- %%?TLS_DHE_RSA_WITH_DES_CBC_SHA,
- %% EDH-DSS-DES-CBC-SHA TODO: ??
+ ?TLS_DHE_RSA_WITH_DES_CBC_SHA,
?TLS_RSA_WITH_DES_CBC_SHA
- %% ?TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA,
- %% ?TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA,
- %%?TLS_RSA_EXPORT_WITH_DES40_CBC_SHA,
- %%?TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
- %%?TLS_RSA_EXPORT_WITH_RC4_40_MD5
].
%%--------------------------------------------------------------------
@@ -245,7 +243,3 @@ hash_final(?MD5, Conntext) ->
crypto:md5_final(Conntext);
hash_final(?SHA, Conntext) ->
crypto:sha_final(Conntext).
-
-
-
-
diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile
index bd86120c98..9e4aecac45 100644
--- a/lib/ssl/test/Makefile
+++ b/lib/ssl/test/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1999-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1999-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%
#
@@ -50,7 +50,8 @@ MODULES = \
old_ssl_protocol_SUITE \
old_transport_accept_SUITE \
old_ssl_dist_SUITE \
- make_certs
+ make_certs\
+ erl_make_certs
ERL_FILES = $(MODULES:%=%.erl)
@@ -58,12 +59,10 @@ ERL_FILES = $(MODULES:%=%.erl)
HRL_FILES = ssl_test_MACHINE.hrl
HRL_FILES_SRC = \
- ssl_pkix.hrl \
ssl_alert.hrl \
ssl_handshake.hrl
-HRL_FILES_INC = \
- OTP-PKIX.hrl
+HRL_FILES_INC =
HRL_FILES_NEEDED_IN_TEST = \
$(HRL_FILES_SRC:%=../src/%) \
diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl
new file mode 100644
index 0000000000..1d2cea6c72
--- /dev/null
+++ b/lib/ssl/test/erl_make_certs.erl
@@ -0,0 +1,412 @@
+%%
+%% %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%
+%%
+
+%% Create test certificates
+
+-module(erl_make_certs).
+-include_lib("public_key/include/public_key.hrl").
+
+-export([make_cert/1, gen_rsa/1, verify_signature/3, write_pem/3]).
+-compile(export_all).
+
+%%--------------------------------------------------------------------
+%% @doc Create and return a der encoded certificate
+%% Option Default
+%% -------------------------------------------------------
+%% digest sha1
+%% validity {date(), date() + week()}
+%% version 3
+%% subject [] list of the following content
+%% {name, Name}
+%% {email, Email}
+%% {city, City}
+%% {state, State}
+%% {org, Org}
+%% {org_unit, OrgUnit}
+%% {country, Country}
+%% {serial, Serial}
+%% {title, Title}
+%% {dnQualifer, DnQ}
+%% issuer = {Issuer, IssuerKey} true (i.e. a ca cert is created)
+%% (obs IssuerKey migth be {Key, Password}
+%% key = KeyFile|KeyBin|rsa|dsa Subject PublicKey rsa or dsa generates key
+%%
+%%
+%% (OBS: The generated keys are for testing only)
+%% @spec ([{::atom(), ::term()}]) -> {Cert::binary(), Key::binary()}
+%% @end
+%%--------------------------------------------------------------------
+
+make_cert(Opts) ->
+ SubjectPrivateKey = get_key(Opts),
+ {TBSCert, IssuerKey} = make_tbs(SubjectPrivateKey, Opts),
+ Cert = public_key:sign(TBSCert, IssuerKey),
+ true = verify_signature(Cert, IssuerKey, undef), %% verify that the keys where ok
+ {Cert, encode_key(SubjectPrivateKey)}.
+
+%%--------------------------------------------------------------------
+%% @doc Writes pem files in Dir with FileName ++ ".pem" and FileName ++ "_key.pem"
+%% @spec (::string(), ::string(), {Cert,Key}) -> ok
+%% @end
+%%--------------------------------------------------------------------
+write_pem(Dir, FileName, {Cert, Key = {_,_,not_encrypted}}) when is_binary(Cert) ->
+ ok = public_key:der_to_pem(filename:join(Dir, FileName ++ ".pem"), [{cert, Cert, not_encrypted}]),
+ ok = public_key:der_to_pem(filename:join(Dir, FileName ++ "_key.pem"), [Key]).
+
+%%--------------------------------------------------------------------
+%% @doc Creates a rsa key (OBS: for testing only)
+%% the size are in bytes
+%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()}
+%% @end
+%%--------------------------------------------------------------------
+gen_rsa(Size) when is_integer(Size) ->
+ Key = gen_rsa2(Size),
+ {Key, encode_key(Key)}.
+
+%%--------------------------------------------------------------------
+%% @doc Creates a dsa key (OBS: for testing only)
+%% the sizes are in bytes
+%% @spec (::integer()) -> {::atom(), ::binary(), ::opaque()}
+%% @end
+%%--------------------------------------------------------------------
+gen_dsa(LSize,NSize) when is_integer(LSize), is_integer(NSize) ->
+ Key = gen_dsa2(LSize, NSize),
+ {Key, encode_key(Key)}.
+
+%%--------------------------------------------------------------------
+%% @doc Verifies cert signatures
+%% @spec (::binary(), ::tuple()) -> ::boolean()
+%% @end
+%%--------------------------------------------------------------------
+verify_signature(DerEncodedCert, DerKey, KeyParams) ->
+ Key = decode_key(DerKey),
+ case Key of
+ #'RSAPrivateKey'{modulus=Mod, publicExponent=Exp} ->
+ public_key:verify_signature(DerEncodedCert,
+ #'RSAPublicKey'{modulus=Mod, publicExponent=Exp},
+ 'NULL');
+ #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} ->
+ public_key:verify_signature(DerEncodedCert, Y, #'Dss-Parms'{p=P, q=Q, g=G});
+
+ _ ->
+ public_key:verify_signature(DerEncodedCert, Key, KeyParams)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%% Implementation %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_key(Opts) ->
+ case proplists:get_value(key, Opts) of
+ undefined -> make_key(rsa, Opts);
+ rsa -> make_key(rsa, Opts);
+ dsa -> make_key(dsa, Opts);
+ Key ->
+ Password = proplists:get_value(password, Opts, no_passwd),
+ decode_key(Key, Password)
+ end.
+
+decode_key({Key, Pw}) ->
+ decode_key(Key, Pw);
+decode_key(Key) ->
+ decode_key(Key, no_passwd).
+
+
+decode_key(#'RSAPublicKey'{} = Key,_) ->
+ Key;
+decode_key(#'RSAPrivateKey'{} = Key,_) ->
+ Key;
+decode_key(#'DSAPrivateKey'{} = Key,_) ->
+ Key;
+decode_key(Der = {_,_,_}, Pw) ->
+ {ok, Key} = public_key:decode_private_key(Der, Pw),
+ Key;
+decode_key(FileOrDer, Pw) ->
+ {ok, [KeyInfo]} = public_key:pem_to_der(FileOrDer),
+ decode_key(KeyInfo, Pw).
+
+encode_key(Key = #'RSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('RSAPrivateKey', Key),
+ {rsa_private_key, list_to_binary(Der), not_encrypted};
+encode_key(Key = #'DSAPrivateKey'{}) ->
+ {ok, Der} = 'OTP-PUB-KEY':encode('DSAPrivateKey', Key),
+ {dsa_private_key, list_to_binary(Der), not_encrypted}.
+
+make_tbs(SubjectKey, Opts) ->
+ Version = list_to_atom("v"++integer_to_list(proplists:get_value(version, Opts, 3))),
+ {Issuer, IssuerKey} = issuer(Opts, SubjectKey),
+
+ {Algo, Parameters} = sign_algorithm(IssuerKey, Opts),
+
+ SignAlgo = #'SignatureAlgorithm'{algorithm = Algo,
+ parameters = Parameters},
+
+ {#'OTPTBSCertificate'{serialNumber = trunc(random:uniform()*100000000)*10000 + 1,
+ signature = SignAlgo,
+ issuer = Issuer,
+ validity = validity(Opts),
+ subject = subject(proplists:get_value(subject, Opts),false),
+ subjectPublicKeyInfo = publickey(SubjectKey),
+ version = Version,
+ extensions = extensions(Opts)
+ }, IssuerKey}.
+
+issuer(Opts, SubjectKey) ->
+ IssuerProp = proplists:get_value(issuer, Opts, true),
+ case IssuerProp of
+ true -> %% Self signed
+ {subject(proplists:get_value(subject, Opts), true), SubjectKey};
+ {Issuer, IssuerKey} when is_binary(Issuer) ->
+ {issuer_der(Issuer), decode_key(IssuerKey)};
+ {File, IssuerKey} when is_list(File) ->
+ {ok, [{cert, Cert, _}|_]} = public_key:pem_to_der(File),
+ {issuer_der(Cert), decode_key(IssuerKey)}
+ end.
+
+issuer_der(Issuer) ->
+ {ok, Decoded} = public_key:pkix_decode_cert(Issuer, otp),
+ #'OTPCertificate'{tbsCertificate=Tbs} = Decoded,
+ #'OTPTBSCertificate'{subject=Subject} = Tbs,
+ Subject.
+
+subject(undefined, IsCA) ->
+ User = if IsCA -> "CA"; true -> os:getenv("USER") end,
+ Opts = [{email, User ++ "@erlang.org"},
+ {name, User},
+ {city, "Stockholm"},
+ {country, "SE"},
+ {org, "erlang"},
+ {org_unit, "testing dep"}],
+ subject(Opts);
+subject(Opts, _) ->
+ subject(Opts).
+
+subject(SubjectOpts) when is_list(SubjectOpts) ->
+ Encode = fun(Opt) ->
+ {Type,Value} = subject_enc(Opt),
+ [#'AttributeTypeAndValue'{type=Type, value=Value}]
+ end,
+ {rdnSequence, [Encode(Opt) || Opt <- SubjectOpts]}.
+
+%% Fill in the blanks
+subject_enc({name, Name}) -> {?'id-at-commonName', {printableString, Name}};
+subject_enc({email, Email}) -> {?'id-emailAddress', Email};
+subject_enc({city, City}) -> {?'id-at-localityName', {printableString, City}};
+subject_enc({state, State}) -> {?'id-at-stateOrProvinceName', {printableString, State}};
+subject_enc({org, Org}) -> {?'id-at-organizationName', {printableString, Org}};
+subject_enc({org_unit, OrgUnit}) -> {?'id-at-organizationalUnitName', {printableString, OrgUnit}};
+subject_enc({country, Country}) -> {?'id-at-countryName', Country};
+subject_enc({serial, Serial}) -> {?'id-at-serialNumber', Serial};
+subject_enc({title, Title}) -> {?'id-at-title', {printableString, Title}};
+subject_enc({dnQualifer, DnQ}) -> {?'id-at-dnQualifier', DnQ};
+subject_enc(Other) -> Other.
+
+
+extensions(Opts) ->
+ case proplists:get_value(extensions, Opts, []) of
+ false ->
+ asn1_NOVALUE;
+ Exts ->
+ lists:flatten([extension(Ext) || Ext <- default_extensions(Exts)])
+ end.
+
+default_extensions(Exts) ->
+ Def = [{key_usage,undefined},
+ {subject_altname, undefined},
+ {issuer_altname, undefined},
+ {basic_constraints, default},
+ {name_constraints, undefined},
+ {policy_constraints, undefined},
+ {ext_key_usage, undefined},
+ {inhibit_any, undefined},
+ {auth_key_id, undefined},
+ {subject_key_id, undefined},
+ {policy_mapping, undefined}],
+ Filter = fun({Key, _}, D) -> lists:keydelete(Key, 1, D) end,
+ Exts ++ lists:foldl(Filter, Def, Exts).
+
+extension({_, undefined}) -> [];
+extension({basic_constraints, Data}) ->
+ case Data of
+ default ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = #'BasicConstraints'{cA=true},
+ critical=true};
+ false ->
+ [];
+ Len when is_integer(Len) ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = #'BasicConstraints'{cA=true, pathLenConstraint=Len},
+ critical=true};
+ _ ->
+ #'Extension'{extnID = ?'id-ce-basicConstraints',
+ extnValue = Data}
+ end;
+extension({Id, Data, Critical}) ->
+ #'Extension'{extnID = Id, extnValue = Data, critical = Critical}.
+
+
+publickey(#'RSAPrivateKey'{modulus=N, publicExponent=E}) ->
+ Public = #'RSAPublicKey'{modulus=N, publicExponent=E},
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?rsaEncryption, parameters='NULL'},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo,
+ subjectPublicKey = Public};
+publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) ->
+ Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-dsa',
+ parameters=#'Dss-Parms'{p=P, q=Q, g=G}},
+ #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = Y}.
+
+validity(Opts) ->
+ DefFrom0 = date(),
+ DefTo0 = calendar:gregorian_days_to_date(calendar:date_to_gregorian_days(date())+7),
+ {DefFrom, DefTo} = proplists:get_value(validity, Opts, {DefFrom0, DefTo0}),
+ Format = fun({Y,M,D}) -> lists:flatten(io_lib:format("~w~2..0w~2..0w000000Z",[Y,M,D])) end,
+ #'Validity'{notBefore={generalTime, Format(DefFrom)},
+ notAfter ={generalTime, Format(DefTo)}}.
+
+sign_algorithm(#'RSAPrivateKey'{}, Opts) ->
+ Type = case proplists:get_value(digest, Opts, sha1) of
+ sha1 -> ?'sha1WithRSAEncryption';
+ sha512 -> ?'sha512WithRSAEncryption';
+ sha384 -> ?'sha384WithRSAEncryption';
+ sha256 -> ?'sha256WithRSAEncryption';
+ md5 -> ?'md5WithRSAEncryption';
+ md2 -> ?'md2WithRSAEncryption'
+ end,
+ {Type, 'NULL'};
+sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) ->
+ {?'id-dsa-with-sha1', #'Dss-Parms'{p=P, q=Q, g=G}}.
+
+make_key(rsa, _Opts) ->
+ %% (OBS: for testing only)
+ gen_rsa2(64);
+make_key(dsa, _Opts) ->
+ gen_dsa2(128, 20). %% Bytes i.e. {1024, 160}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% RSA key generation (OBS: for testing only)
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-define(SMALL_PRIMES, [65537,97,89,83,79,73,71,67,61,59,53,
+ 47,43,41,37,31,29,23,19,17,13,11,7,5,3]).
+
+gen_rsa2(Size) ->
+ P = prime(Size),
+ Q = prime(Size),
+ N = P*Q,
+ Tot = (P - 1) * (Q - 1),
+ [E|_] = lists:dropwhile(fun(Candidate) -> (Tot rem Candidate) == 0 end, ?SMALL_PRIMES),
+ {D1,D2} = extended_gcd(E, Tot),
+ D = erlang:max(D1,D2),
+ case D < E of
+ true ->
+ gen_rsa2(Size);
+ false ->
+ {Co1,Co2} = extended_gcd(Q, P),
+ Co = erlang:max(Co1,Co2),
+ #'RSAPrivateKey'{version = 'two-prime',
+ modulus = N,
+ publicExponent = E,
+ privateExponent = D,
+ prime1 = P,
+ prime2 = Q,
+ exponent1 = D rem (P-1),
+ exponent2 = D rem (Q-1),
+ coefficient = Co
+ }
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% DSA key generation (OBS: for testing only)
+%% See http://en.wikipedia.org/wiki/Digital_Signature_Algorithm
+%% and the fips_186-3.pdf
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+gen_dsa2(LSize, NSize) ->
+ Q = prime(NSize), %% Choose N-bit prime Q
+ X0 = prime(LSize),
+ P0 = prime((LSize div 2) +1),
+
+ %% Choose L-bit prime modulus P such that p–1 is a multiple of q.
+ case dsa_search(X0 div (2*Q*P0), P0, Q, 1000) of
+ error ->
+ gen_dsa2(LSize, NSize);
+ P ->
+ G = crypto:mod_exp(2, (P-1) div Q, P), % Choose G a number whose multiplicative order modulo p is q.
+ %% such that This may be done by setting g = h^(p–1)/q mod p, commonly h=2 is used.
+
+ X = prime(20), %% Choose x by some random method, where 0 < x < q.
+ Y = crypto:mod_exp(G, X, P), %% Calculate y = g^x mod p.
+
+ #'DSAPrivateKey'{version=0, p=P, q=Q, g=G, y=Y, x=X}
+ end.
+
+%% See fips_186-3.pdf
+dsa_search(T, P0, Q, Iter) when Iter > 0 ->
+ P = 2*T*Q*P0 + 1,
+ case is_prime(crypto:mpint(P), 50) of
+ true -> P;
+ false -> dsa_search(T+1, P0, Q, Iter-1)
+ end;
+dsa_search(_,_,_,_) ->
+ error.
+
+
+%%%%%%% Crypto Math %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+prime(ByteSize) ->
+ Rand = odd_rand(ByteSize),
+ crypto:erlint(prime_odd(Rand, 0)).
+
+prime_odd(Rand, N) ->
+ case is_prime(Rand, 50) of
+ true ->
+ Rand;
+ false ->
+ NotPrime = crypto:erlint(Rand),
+ prime_odd(crypto:mpint(NotPrime+2), N+1)
+ end.
+
+%% see http://en.wikipedia.org/wiki/Fermat_primality_test
+is_prime(_, 0) -> true;
+is_prime(Candidate, Test) ->
+ CoPrime = odd_rand(<<0,0,0,4, 10000:32>>, Candidate),
+ case crypto:mod_exp(CoPrime, Candidate, Candidate) of
+ CoPrime -> is_prime(Candidate, Test-1);
+ _ -> false
+ end.
+
+odd_rand(Size) ->
+ Min = 1 bsl (Size*8-1),
+ Max = (1 bsl (Size*8))-1,
+ odd_rand(crypto:mpint(Min), crypto:mpint(Max)).
+
+odd_rand(Min,Max) ->
+ Rand = <<Sz:32, _/binary>> = crypto:rand_uniform(Min,Max),
+ BitSkip = (Sz+4)*8-1,
+ case Rand of
+ Odd = <<_:BitSkip, 1:1>> -> Odd;
+ Even = <<_:BitSkip, 0:1>> ->
+ crypto:mpint(crypto:erlint(Even)+1)
+ end.
+
+extended_gcd(A, B) ->
+ case A rem B of
+ 0 ->
+ {0, 1};
+ N ->
+ {X, Y} = extended_gcd(B, N),
+ {Y, X-Y*(A div B)}
+ end.
diff --git a/lib/ssl/test/old_ssl_active_SUITE.erl b/lib/ssl/test/old_ssl_active_SUITE.erl
index 010596f351..d1cec26827 100644
--- a/lib/ssl/test/old_ssl_active_SUITE.erl
+++ b/lib/ssl/test/old_ssl_active_SUITE.erl
@@ -87,6 +87,8 @@ config(Config) ->
%% operating system, version of OTP, Erts, kernel and stdlib.
%% Check if SSL exists. If this case fails, all other cases are skipped
+ crypto:start(),
+ application:start(public_key),
case ssl:start() of
ok -> ssl:stop();
{error, {already_started, _}} -> ssl:stop();
diff --git a/lib/ssl/test/old_ssl_active_once_SUITE.erl b/lib/ssl/test/old_ssl_active_once_SUITE.erl
index 6224b17aa7..63eaa730e9 100644
--- a/lib/ssl/test/old_ssl_active_once_SUITE.erl
+++ b/lib/ssl/test/old_ssl_active_once_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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%
%%
@@ -79,6 +79,8 @@ config(Config) ->
io:format("Config: ~p~n", [Config]),
%% Check if SSL exists. If this case fails, all other cases are skipped
+ crypto:start(),
+ application:start(public_key),
case ssl:start() of
ok -> ssl:stop();
{error, {already_started, _}} -> ssl:stop();
diff --git a/lib/ssl/test/old_ssl_dist_SUITE.erl b/lib/ssl/test/old_ssl_dist_SUITE.erl
index 56209c3530..97090c1409 100644
--- a/lib/ssl/test/old_ssl_dist_SUITE.erl
+++ b/lib/ssl/test/old_ssl_dist_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
@@ -254,7 +254,8 @@ mk_node_cmdline(ListenPort, Name, Args) ->
Prog ++ " "
++ Static ++ " "
++ NameSw ++ " " ++ Name ++ " "
- ++ "-pa " ++ Pa ++ " "
+ ++ "-pa " ++ Pa ++ " "
+ ++ "-run application start crypto -run application start public_key "
++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr "
++ host_name() ++ " "
++ integer_to_list(ListenPort) ++ " "
@@ -524,23 +525,10 @@ add_ssl_opts_config(Config) ->
KrnlDir = filename:join([LibDir, "kernel-" ++ KRNL_VSN]),
{ok, _} = file:read_file_info(StdlDir),
{ok, _} = file:read_file_info(KrnlDir),
- SSL_VSN = case lists:keysearch(ssl, 1, Apps) of
- {value, {ssl, _, VSN}} ->
- VSN;
- _ ->
- application:start(ssl),
- try
- {value,
- {ssl,
- _,
- VSN}} = lists:keysearch(ssl,
- 1,
- application:which_applications()),
- VSN
- after
- application:stop(ssl)
- end
- end,
+ SSL_VSN = vsn(ssl),
+ VSN_CRYPTO = vsn(crypto),
+ VSN_PKEY = vsn(public_key),
+
SslDir = filename:join([LibDir, "ssl-" ++ SSL_VSN]),
{ok, _} = file:read_file_info(SslDir),
%% We are using an installed otp system, create the boot script.
@@ -552,6 +540,8 @@ add_ssl_opts_config(Config) ->
" {erts, \"~s\"},~n"
" [{kernel, \"~s\"},~n"
" {stdlib, \"~s\"},~n"
+ " {crypto, \"~s\"},~n"
+ " {public_key, \"~s\"},~n"
" {ssl, \"~s\"}]}.~n",
[case catch erlang:system_info(otp_release) of
{'EXIT', _} -> "R11B";
@@ -560,6 +550,8 @@ add_ssl_opts_config(Config) ->
erlang:system_info(version),
KRNL_VSN,
STDL_VSN,
+ VSN_CRYPTO,
+ VSN_PKEY,
SSL_VSN]),
ok = file:close(RelFile),
ok = systools:make_script(Script, []),
@@ -593,3 +585,17 @@ success(Config) ->
{value, {comment, _} = Res} -> Res;
_ -> ok
end.
+
+vsn(App) ->
+ application:start(App),
+ try
+ {value,
+ {ssl,
+ _,
+ VSN}} = lists:keysearch(App,
+ 1,
+ application:which_applications()),
+ VSN
+ after
+ application:stop(ssl)
+ end.
diff --git a/lib/ssl/test/old_ssl_misc_SUITE.erl b/lib/ssl/test/old_ssl_misc_SUITE.erl
index 55d1b71025..2767123a12 100644
--- a/lib/ssl/test/old_ssl_misc_SUITE.erl
+++ b/lib/ssl/test/old_ssl_misc_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -61,6 +61,8 @@ config(Config) ->
io:format("Config: ~p~n", [Config]),
%% Check if SSL exists. If this case fails, all other cases are skipped
+ crypto:start(),
+ application:start(public_key),
case ssl:start() of
ok -> ssl:stop();
{error, {already_started, _}} -> ssl:stop();
diff --git a/lib/ssl/test/old_ssl_passive_SUITE.erl b/lib/ssl/test/old_ssl_passive_SUITE.erl
index 4cb8c1f0cd..96a7938583 100644
--- a/lib/ssl/test/old_ssl_passive_SUITE.erl
+++ b/lib/ssl/test/old_ssl_passive_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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%
%%
@@ -78,6 +78,8 @@ config(Config) ->
io:format("Config: ~p~n", [Config]),
%% Check if SSL exists. If this case fails, all other cases are skipped
+ crypto:start(),
+ application:start(public_key),
case ssl:start() of
ok -> ssl:stop();
{error, {already_started, _}} -> ssl:stop();
diff --git a/lib/ssl/test/old_ssl_peer_cert_SUITE.erl b/lib/ssl/test/old_ssl_peer_cert_SUITE.erl
index f0b8db2607..e5b3975d41 100644
--- a/lib/ssl/test/old_ssl_peer_cert_SUITE.erl
+++ b/lib/ssl/test/old_ssl_peer_cert_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -62,6 +62,8 @@ config(Config) ->
io:format("Config: ~p~n", [Config]),
%% Check if SSL exists. If this case fails, all other cases are skipped
+ crypto:start(),
+ application:start(public_key),
case ssl:start() of
ok -> ssl:stop();
{error, {already_started, _}} -> ssl:stop();
diff --git a/lib/ssl/test/old_ssl_protocol_SUITE.erl b/lib/ssl/test/old_ssl_protocol_SUITE.erl
index 7bde5d6749..efdbf45a3d 100644
--- a/lib/ssl/test/old_ssl_protocol_SUITE.erl
+++ b/lib/ssl/test/old_ssl_protocol_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2005-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%
%%
@@ -55,6 +55,8 @@ config(Config) ->
io:format("Config: ~p~n", [Config]),
%% Check if SSL exists. If this case fails, all other cases are skipped
+ crypto:start(),
+ application:start(public_key),
case ssl:start() of
ok -> ssl:stop();
{error, {already_started, _}} -> ssl:stop();
diff --git a/lib/ssl/test/old_ssl_verify_SUITE.erl b/lib/ssl/test/old_ssl_verify_SUITE.erl
index 5db964526f..7a8cd1578a 100644
--- a/lib/ssl/test/old_ssl_verify_SUITE.erl
+++ b/lib/ssl/test/old_ssl_verify_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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%
%%
@@ -60,6 +60,8 @@ config(Config) ->
io:format("Config: ~p~n", [Config]),
%% Check if SSL exists. If this case fails, all other cases are skipped
+ crypto:start(),
+ application:start(public_key),
case ssl:start() of
ok -> ssl:stop();
{error, {already_started, _}} -> ssl:stop();
diff --git a/lib/ssl/test/old_transport_accept_SUITE.erl b/lib/ssl/test/old_transport_accept_SUITE.erl
index 4bb09cee19..71c1d9e181 100644
--- a/lib/ssl/test/old_transport_accept_SUITE.erl
+++ b/lib/ssl/test/old_transport_accept_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-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%
%%
@@ -224,12 +224,9 @@ tolerant_server_loop(Client, LSock, Msg, N) ->
tolerant_server_loop(Client, LSock, Msg, N-1).
app() ->
- case application:get_application(ssl) of
- undefined ->
- application:start(ssl);
- _ ->
- ok
- end.
+ crypto:start(),
+ application:start(public_key),
+ ssl:start().
start_node(Kind, Params) ->
S = atom_to_list(?MODULE)++"_" ++ atom_to_list(Kind),
diff --git a/lib/ssl/test/ssl.cover b/lib/ssl/test/ssl.cover
index 138bf96b9d..e8daa363c5 100644
--- a/lib/ssl/test/ssl.cover
+++ b/lib/ssl/test/ssl.cover
@@ -3,5 +3,17 @@
'PKIX1Explicit88',
'PKIX1Implicit88',
'PKIXAttributeCertificate',
- 'SSL-PKIX']}.
+ 'SSL-PKIX',
+ ssl_pem,
+ ssl_pkix,
+ ssl_base64,
+ ssl_broker,
+ ssl_broker_int,
+ ssl_broker_sup,
+ ssl_debug,
+ ssl_server,
+ ssl_prim,
+ inet_ssl_dist,
+ 'OTP-PKIX'
+ ]}.
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 68970b6693..8a1b90ed98 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -26,6 +26,8 @@
-include("test_server.hrl").
-include("test_server_line.hrl").
+-include_lib("public_key/include/public_key.hrl").
+-include("ssl_alert.hrl").
-define('24H_in_sec', 86400).
-define(TIMEOUT, 60000).
@@ -35,7 +37,7 @@
-behaviour(ssl_session_cache_api).
%% For the session cache tests
--export([init/0, terminate/1, lookup/2, update/3,
+-export([init/1, terminate/1, lookup/2, update/3,
delete/2, foldl/3, select_session/2]).
%% Test server callback functions
@@ -48,14 +50,21 @@
%% Note: This function is free to add any key/value pairs to the Config
%% variable, but should NOT alter/remove any existing entries.
%%--------------------------------------------------------------------
-init_per_suite(Config) ->
+init_per_suite(Config0) ->
+ Dog = ssl_test_lib:timetrap(?TIMEOUT *2),
crypto:start(),
+ application:start(public_key),
ssl:start(),
+
+ %% make rsa certs using oppenssl
Result =
- (catch make_certs:all(?config(data_dir, Config),
- ?config(priv_dir, Config))),
+ (catch make_certs:all(?config(data_dir, Config0),
+ ?config(priv_dir, Config0))),
test_server:format("Make certs ~p~n", [Result]),
- ssl_test_lib:cert_options(Config).
+
+ Config1 = ssl_test_lib:make_dsa_cert(Config0),
+ Config = ssl_test_lib:cert_options(Config1),
+ [{watchdog, Dog} | Config].
%%--------------------------------------------------------------------
%% Function: end_per_suite(Config) -> _
@@ -81,11 +90,11 @@ end_per_suite(_Config) ->
%% Description: Initialization before each test case
%%--------------------------------------------------------------------
init_per_testcase(session_cache_process_list, Config) ->
- init_customized_session_cache(Config);
+ init_customized_session_cache(list, Config);
init_per_testcase(session_cache_process_mnesia, Config) ->
mnesia:start(),
- init_customized_session_cache(Config);
+ init_customized_session_cache(mnesia, Config);
init_per_testcase(reuse_session_expired, Config0) ->
Config = lists:keydelete(watchdog, 1, Config0),
@@ -96,17 +105,50 @@ init_per_testcase(reuse_session_expired, Config0) ->
ssl:start(),
[{watchdog, Dog} | Config];
+init_per_testcase(no_authority_key_identifier, Config) ->
+ %% Clear cach so that root cert will not
+ %% be found.
+ ssl:stop(),
+ ssl:start(),
+ Config;
+
+init_per_testcase(TestCase, Config) when TestCase == ciphers_rsa_signed_certs_ssl3;
+ TestCase == ciphers_rsa_signed_certs_openssl_names_ssl3;
+ TestCase == ciphers_dsa_signed_certs_ssl3;
+ TestCase == ciphers_dsa_signed_certs_openssl_names_ssl3 ->
+ ssl:stop(),
+ application:load(ssl),
+ application:set_env(ssl, protocol_version, sslv3),
+ ssl:start(),
+ Config;
+
+init_per_testcase(protocol_versions, Config) ->
+ ssl:stop(),
+ application:load(ssl),
+ %% For backwards compatibility sslv2 should be filtered out.
+ application:set_env(ssl, protocol_version, [sslv2, sslv3, tlsv1]),
+ ssl:start(),
+ Config;
+
+init_per_testcase(empty_protocol_versions, Config) ->
+ ssl:stop(),
+ application:load(ssl),
+ application:set_env(ssl, protocol_version, []),
+ ssl:start(),
+ Config;
+
init_per_testcase(_TestCase, Config0) ->
Config = lists:keydelete(watchdog, 1, Config0),
Dog = test_server:timetrap(?TIMEOUT),
- [{watchdog, Dog} | Config].
+ [{watchdog, Dog} | Config].
-init_customized_session_cache(Config0) ->
+init_customized_session_cache(Type, Config0) ->
Config = lists:keydelete(watchdog, 1, Config0),
Dog = test_server:timetrap(?TIMEOUT),
ssl:stop(),
application:load(ssl),
application:set_env(ssl, session_cb, ?MODULE),
+ application:set_env(ssl, session_cb_init_args, [Type]),
ssl:start(),
[{watchdog, Dog} | Config].
@@ -123,11 +165,22 @@ end_per_testcase(session_cache_process_list, Config) ->
end_per_testcase(default_action, Config);
end_per_testcase(session_cache_process_mnesia, Config) ->
application:unset_env(ssl, session_cb),
+ application:unset_env(ssl, session_cb_init_args),
mnesia:stop(),
+ ssl:stop(),
+ ssl:start(),
end_per_testcase(default_action, Config);
end_per_testcase(reuse_session_expired, Config) ->
application:unset_env(ssl, session_lifetime),
end_per_testcase(default_action, Config);
+end_per_testcase(TestCase, Config) when TestCase == ciphers_rsa_signed_certs_ssl3;
+ TestCase == ciphers_rsa_signed_certs_openssl_names_ssl3;
+ TestCase == ciphers_dsa_signed_certs_ssl3;
+ TestCase == ciphers_dsa_signed_certs_openssl_names_ssl3;
+ TestCase == protocol_versions;
+ TestCase == empty_protocol_versions->
+ application:unset_env(ssl, protocol_version),
+ end_per_testcase(default_action, Config);
end_per_testcase(_TestCase, Config) ->
Dog = ?config(watchdog, Config),
case Dog of
@@ -149,24 +202,38 @@ all(doc) ->
["Test the basic ssl functionality"];
all(suite) ->
- [app, connection_info, controlling_process, controller_dies,
- peercert, connect_dist,
- peername, sockname, socket_options, valid_ssl_options, versions, cipher_suites,
- upgrade, upgrade_with_timeout, tcp_connect,
- ipv6, ekeyfile, ecertfile, ecacertfile, eoptions, shutdown,
- shutdown_write, shutdown_both, shutdown_error, ciphers,
- send_close, close_transport_accept, dh_params,
- server_verify_peer_passive,
+ [app, alerts, connection_info, protocol_versions,
+ empty_protocol_versions, controlling_process, controller_dies,
+ client_closes_socket, peercert, connect_dist, peername, sockname,
+ socket_options, misc_ssl_options, versions, cipher_suites,
+ upgrade, upgrade_with_timeout, tcp_connect, ipv6, ekeyfile,
+ ecertfile, ecacertfile, eoptions, shutdown, shutdown_write,
+ shutdown_both, shutdown_error,
+ ciphers_rsa_signed_certs, ciphers_rsa_signed_certs_ssl3,
+ ciphers_rsa_signed_certs_openssl_names,
+ ciphers_rsa_signed_certs_openssl_names_ssl3,
+ ciphers_dsa_signed_certs,
+ ciphers_dsa_signed_certs_ssl3,
+ ciphers_dsa_signed_certs_openssl_names,
+ ciphers_dsa_signed_certs_openssl_names_ssl3,
+ send_close,
+ close_transport_accept, dh_params, server_verify_peer_passive,
server_verify_peer_active, server_verify_peer_active_once,
- server_verify_none_passive, server_verify_none_active,
+ server_verify_none_passive, server_verify_none_active,
server_verify_none_active_once, server_verify_no_cacerts,
server_require_peer_cert_ok, server_require_peer_cert_fail,
- client_verify_none_passive,
- client_verify_none_active, client_verify_none_active_once
- %%, session_cache_process_list, session_cache_process_mnesia
- ,reuse_session, reuse_session_expired, server_does_not_want_to_reuse_session,
- client_renegotiate, server_renegotiate,
- client_no_wrap_sequence_number, server_no_wrap_sequence_number
+ server_verify_client_once_passive,
+ server_verify_client_once_active,
+ server_verify_client_once_active_once, client_verify_none_passive,
+ client_verify_none_active, client_verify_none_active_once,
+ session_cache_process_list, session_cache_process_mnesia,
+ reuse_session, reuse_session_expired,
+ server_does_not_want_to_reuse_session, client_renegotiate,
+ server_renegotiate, client_renegotiate_reused_session,
+ server_renegotiate_reused_session, client_no_wrap_sequence_number,
+ server_no_wrap_sequence_number, extended_key_usage,
+ validate_extensions_fun, no_authority_key_identifier,
+ invalid_signature_client, invalid_signature_server, cert_expired
].
%% Test cases starts here.
@@ -177,7 +244,31 @@ app(suite) ->
[];
app(Config) when is_list(Config) ->
ok = test_server:app_test(ssl).
-
+%%--------------------------------------------------------------------
+alerts(doc) ->
+ "Test ssl_alert:alert_txt/1";
+alerts(suite) ->
+ [];
+alerts(Config) when is_list(Config) ->
+ Descriptions = [?CLOSE_NOTIFY, ?UNEXPECTED_MESSAGE, ?BAD_RECORD_MAC,
+ ?DECRYPTION_FAILED, ?RECORD_OVERFLOW, ?DECOMPRESSION_FAILURE,
+ ?HANDSHAKE_FAILURE, ?BAD_CERTIFICATE, ?UNSUPPORTED_CERTIFICATE,
+ ?CERTIFICATE_REVOKED,?CERTIFICATE_EXPIRED, ?CERTIFICATE_UNKNOWN,
+ ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR,
+ ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION,
+ ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED,
+ ?NO_RENEGOTIATION],
+ Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) |
+ [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]],
+ lists:foreach(fun(Alert) ->
+ case ssl_alert:alert_txt(Alert) of
+ Txt when is_list(Txt) ->
+ ok;
+ Other ->
+ test_server:fail({unexpected, Other})
+ end
+ end, Alerts).
+%%--------------------------------------------------------------------
connection_info(doc) ->
["Test the API function ssl:connection_info/1"];
connection_info(suite) ->
@@ -206,7 +297,7 @@ connection_info(Config) when is_list(Config) ->
Version =
ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
- ServerMsg = ClientMsg = {ok, {Version, {rsa,rc4_128,sha,no_export}}},
+ ServerMsg = ClientMsg = {ok, {Version, {rsa,rc4_128,sha}}},
ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg),
@@ -218,6 +309,49 @@ connection_info_result(Socket) ->
%%--------------------------------------------------------------------
+protocol_versions(doc) ->
+ ["Test to set a list of protocol versions in app environment."];
+
+protocol_versions(suite) ->
+ [];
+
+protocol_versions(Config) when is_list(Config) ->
+ basic_test(Config).
+
+empty_protocol_versions(doc) ->
+ ["Test to set an empty list of protocol versions in app environment."];
+
+empty_protocol_versions(suite) ->
+ [];
+
+empty_protocol_versions(Config) when is_list(Config) ->
+ basic_test(Config).
+
+
+basic_test(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+
controlling_process(doc) ->
["Test API function controlling_process/2"];
@@ -275,7 +409,7 @@ controlling_process_result(Socket, Pid, Msg) ->
ssl:send(Socket, Msg),
no_result_msg.
-
+%%--------------------------------------------------------------------
controller_dies(doc) ->
["Test that the socket is closed after controlling process dies"];
controller_dies(suite) -> [];
@@ -311,11 +445,15 @@ controller_dies(Config) when is_list(Config) ->
get_close(Client, ?LINE),
%% Test that clients die when process disappear
- Server ! listen, test_server:sleep(?SLEEP),
+ Server ! listen,
Tester = self(),
Connect = fun(Pid) ->
{ok, Socket} = ssl:connect(Hostname, Port,
[{reuseaddr,true},{ssl_imp,new}]),
+ %% Make sure server finishes and verification
+ %% and is in coonection state before
+ %% killing client
+ test_server:sleep(?SLEEP),
Pid ! {self(), connected, Socket},
receive die_nice -> normal end
end,
@@ -325,7 +463,7 @@ controller_dies(Config) when is_list(Config) ->
get_close(Client2, ?LINE),
%% Test that clients die when the controlling process have changed
- Server ! listen, test_server:sleep(?SLEEP),
+ Server ! listen,
Client3 = spawn_link(fun() -> Connect(Tester) end),
Controller = spawn_link(fun() -> receive die_nice -> normal end end),
@@ -349,7 +487,7 @@ controller_dies(Config) when is_list(Config) ->
get_close(Controller, ?LINE),
%% Test that servers die
- Server ! listen, test_server:sleep(?SLEEP),
+ Server ! listen,
LastClient = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -387,6 +525,36 @@ get_close(Pid, Where) ->
end.
%%--------------------------------------------------------------------
+client_closes_socket(doc) ->
+ ["Test what happens when client closes socket before handshake is compleated"];
+client_closes_socket(suite) -> [];
+client_closes_socket(Config) when is_list(Config) ->
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ TcpOpts = [binary, {reuseaddr, true}],
+
+ Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {tcp_options, TcpOpts},
+ {ssl_options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Connect = fun() ->
+ {ok, _Socket} = rpc:call(ClientNode, gen_tcp, connect,
+ [Hostname, Port, TcpOpts]),
+ %% Make sure that ssl_accept is called before
+ %% client process ends and closes socket.
+ test_server:sleep(?SLEEP)
+ end,
+
+ _Client = spawn_link(Connect),
+
+ ssl_test_lib:check_result(Server, {error,closed}),
+
+ ssl_test_lib:close(Server).
+
+%%--------------------------------------------------------------------
+
peercert(doc) ->
[""];
@@ -488,9 +656,9 @@ peername(Config) when is_list(Config) ->
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
- {from, self()},
- {mfa, {?MODULE, peername_result, []}},
- {options, [{port, 0} | ClientOpts]}]),
+ {from, self()},
+ {mfa, {?MODULE, peername_result, []}},
+ {options, [{port, 0} | ClientOpts]}]),
ClientPort = ssl_test_lib:inet_port(Client),
ServerIp = ssl_test_lib:node_to_hostip(ServerNode),
@@ -530,6 +698,7 @@ sockname(Config) when is_list(Config) ->
{from, self()},
{mfa, {?MODULE, sockname_result, []}},
{options, [{port, 0} | ClientOpts]}]),
+
ClientPort = ssl_test_lib:inet_port(Client),
ServerIp = ssl_test_lib:node_to_hostip(ServerNode),
ClientIp = ssl_test_lib:node_to_hostip(ClientNode),
@@ -555,9 +724,12 @@ cipher_suites(suite) ->
[];
cipher_suites(Config) when is_list(Config) ->
- MandatoryCipherSuite = {rsa,'3des_ede_cbc',sha,no_export},
+ MandatoryCipherSuite = {rsa,'3des_ede_cbc',sha},
[_|_] = Suites = ssl:cipher_suites(),
- true = lists:member(MandatoryCipherSuite, Suites).
+ true = lists:member(MandatoryCipherSuite, Suites),
+ Suites = ssl:cipher_suites(erlang),
+ [_|_] =ssl:cipher_suites(openssl).
+
%%--------------------------------------------------------------------
socket_options(doc) ->
["Test API function getopts/2 and setopts/2"];
@@ -592,9 +764,16 @@ socket_options(Config) when is_list(Config) ->
{options, ClientOpts}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
-
+
ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
+ ssl_test_lib:close(Client),
+
+ {ok, Listen} = ssl:listen(0, ServerOpts),
+ {ok,[{mode,list}]} = ssl:getopts(Listen, [mode]),
+ ok = ssl:setopts(Listen, [{mode, binary}]),
+ {ok,[{mode, binary}]} = ssl:getopts(Listen, [mode]),
+ {ok,[{recbuf, _}]} = ssl:getopts(Listen, [recbuf]),
+ ssl:close(Listen).
socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) ->
%% Test get/set emulated opts
@@ -603,60 +782,49 @@ socket_options_result(Socket, Options, DefaultValues, NewOptions, NewValues) ->
{ok, NewValues} = ssl:getopts(Socket, NewOptions),
%% Test get/set inet opts
{ok,[{nodelay,false}]} = ssl:getopts(Socket, [nodelay]),
+ ssl:setopts(Socket, [{nodelay, true}]),
+ {ok,[{nodelay, true}]} = ssl:getopts(Socket, [nodelay]),
ok.
%%--------------------------------------------------------------------
-valid_ssl_options(doc) ->
+misc_ssl_options(doc) ->
["Test what happens when we give valid options"];
-valid_ssl_options(suite) ->
+misc_ssl_options(suite) ->
[];
-valid_ssl_options(Config) when is_list(Config) ->
- ClientOpts = [{reuseaddr, true} | ?config(client_opts, Config)],
- ServerOpts = [{reuseaddr, true} | ?config(server_opts, Config)],
+misc_ssl_options(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Port = ssl_test_lib:inet_port(ServerNode),
-
- StartOk =
- fun(Peer, Pid, TestOpt) ->
- receive
- {Pid, ok} when Peer =:= server ->
- ok;
- {Pid, {ok, _}} when Peer =:= client ->
- ok;
- {Pid, Error} ->
- test_server:fail({Peer,
- {option_being_tested, TestOpt},
- {got, Error}})
- end
- end,
+
+ %% Chek that ssl options not tested elsewhere are filtered away e.i. not passed to inet.
+ TestOpts = [{depth, 1},
+ {key, undefined},
+ {password, []},
+ {reuse_session, fun(_,_,_,_) -> true end},
+ {debug, []},
+ {cb_info, {gen_tcp, tcp, tcp_closed, tcp_error}}],
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, TestOpts ++ ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, TestOpts ++ ClientOpts}]),
- %% The following contains both documented and undocumented options as
- %% listed in ssl:handle_options/2. It excludes file options which are
- %% tested elsewhere (cacertfile, certfile, keyfile).
- TestOpts = [{versions, []}, {verify, verify_none}, {verify_fun, fun(_) -> false end},
- {fail_if_no_peer_cert, false}, {verify_client_once, false},
- {depth, 1}, {key, undefined}, {password, "secret"}, {ciphers, []},
- {reuse_sessions, true}, {reuse_session, fun(_,_,_,_) -> true end},
- {renegotiate_at, 1000000000}, {debug, []},
- {cb_info, {gen_tcp, tcp, tcp_closed}}],
- [begin
- Server =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [TestOpt | ServerOpts]}]),
- Client =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname}, {from, self()},
- {options, [TestOpt | ClientOpts]}]),
- StartOk(server, Server, TestOpt),
- StartOk(client, Client, TestOpt),
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client),
- ok
- end || TestOpt <- TestOpts],
- ok.
+ test_server:format("Testcase ~p, Client ~p Server ~p ~n",
+ [self(), Client, Server]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
versions(doc) ->
@@ -802,11 +970,12 @@ upgrade(Config) when is_list(Config) ->
TcpOpts = [binary, {reuseaddr, true}],
Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE,
- upgrade_result, []}},
- {tcp_options, TcpOpts},
- {ssl_options, ServerOpts}]),
+ {from, self()},
+ {mfa, {?MODULE,
+ upgrade_result, []}},
+ {tcp_options,
+ [{active, false} | TcpOpts]},
+ {ssl_options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_upgrade_client([{node, ClientNode},
{port, Port},
@@ -825,6 +994,7 @@ upgrade(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
upgrade_result(Socket) ->
+ ssl:setopts(Socket, [{active, true}]),
ok = ssl:send(Socket, "Hello world"),
%% Make sure binary is inherited from tcp socket and that we do
%% not get the list default!
@@ -851,7 +1021,8 @@ upgrade_with_timeout(Config) when is_list(Config) ->
{timeout, 5000},
{mfa, {?MODULE,
upgrade_result, []}},
- {tcp_options, TcpOpts},
+ {tcp_options,
+ [{active, false} | TcpOpts]},
{ssl_options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_upgrade_client([{node, ClientNode},
@@ -878,9 +1049,8 @@ tcp_connect(suite) ->
[];
tcp_connect(Config) when is_list(Config) ->
- ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
TcpOpts = [binary, {reuseaddr, true}],
Server = ssl_test_lib:start_upgrade_server([{node, ServerNode}, {port, 0},
@@ -905,7 +1075,7 @@ tcp_connect(Config) when is_list(Config) ->
ssl_test_lib:close(Server).
-dummy(Socket) ->
+dummy(_Socket) ->
%% Should not happen as the ssl connection will not be established
%% due to fatal handshake failiure
exit(kill).
@@ -960,12 +1130,14 @@ ekeyfile(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
BadOpts = ?config(server_bad_key, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Port = ssl_test_lib:inet_port(ServerNode),
-
+
Server =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
+ ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
{options, BadOpts}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
Client =
ssl_test_lib:start_client_error([{node, ClientNode},
{port, Port}, {host, Hostname},
@@ -986,19 +1158,21 @@ ecertfile(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
ServerBadOpts = ?config(server_bad_cert, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Port = ssl_test_lib:inet_port(ServerNode),
- Server0 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
+ Server =
+ ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
{options, ServerBadOpts}]),
- Client0 =
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client =
ssl_test_lib:start_client_error([{node, ClientNode},
{port, Port}, {host, Hostname},
{from, self()},
{options, ClientOpts}]),
- ssl_test_lib:check_result(Server0, {error, ecertfile}, Client0,
+ ssl_test_lib:check_result(Server, {error, ecertfile}, Client,
{error, closed}).
@@ -1013,15 +1187,18 @@ ecacertfile(Config) when is_list(Config) ->
ClientOpts = [{reuseaddr, true}|?config(client_opts, Config)],
ServerBadOpts = [{reuseaddr, true}|?config(server_bad_ca, Config)],
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Port = ssl_test_lib:inet_port(ServerNode),
Server0 =
ssl_test_lib:start_server_error([{node, ServerNode},
- {port, Port}, {from, self()},
+ {port, 0}, {from, self()},
{options, ServerBadOpts}]),
+
+ Port0 = ssl_test_lib:inet_port(Server0),
+
+
Client0 =
ssl_test_lib:start_client_error([{node, ClientNode},
- {port, Port}, {host, Hostname},
+ {port, Port0}, {host, Hostname},
{from, self()},
{options, ClientOpts}]),
@@ -1034,11 +1211,14 @@ ecacertfile(Config) when is_list(Config) ->
Server1 =
ssl_test_lib:start_server_error([{node, ServerNode},
- {port, Port}, {from, self()},
+ {port, 0}, {from, self()},
{options, ServerBadOpts1}]),
+
+ Port1 = ssl_test_lib:inet_port(Server1),
+
Client1 =
ssl_test_lib:start_client_error([{node, ClientNode},
- {port, Port}, {host, Hostname},
+ {port, Port1}, {host, Hostname},
{from, self()},
{options, ClientOpts}]),
@@ -1059,168 +1239,58 @@ eoptions(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Port = ssl_test_lib:inet_port(ServerNode),
-
- %% Emulated opts
- Server0 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{active, trice} | ServerOpts]}]),
- Client0 =
- ssl_test_lib:start_client_error([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {from, self()},
- {options, [{active, trice} | ClientOpts]}]),
- ssl_test_lib:check_result(Server0, {error, {eoptions, {active,trice}}},
- Client0, {error, {eoptions, {active,trice}}}),
-
- Server1 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{header, a} | ServerOpts]}]),
- Client1 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{header, a} | ClientOpts]}]),
- ssl_test_lib:check_result(Server1, {error, {eoptions, {header, a}}},
- Client1, {error, {eoptions, {header, a}}}),
-
- Server2 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{mode, a} | ServerOpts]}]),
- Client2 =
- ssl_test_lib:start_client_error([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {from, self()},
- {options, [{mode, a} | ClientOpts]}]),
- ssl_test_lib:check_result(Server2, {error, {eoptions, {mode, a}}},
- Client2, {error, {eoptions, {mode, a}}}),
+ Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) ->
+ ssl_test_lib:check_result(Server,
+ {error, {eoptions, {sslv2, Option}}},
+ Client,
+ {error, {eoptions, {sslv2, Option}}});
+ (Client, Server, Option) ->
+ ssl_test_lib:check_result(Server,
+ {error, {eoptions, Option}},
+ Client,
+ {error, {eoptions, Option}})
+ end,
- Server3 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{packet, 8.0} | ServerOpts]}]),
- Client3 =
- ssl_test_lib:start_client_error([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {from, self()},
- {options, [{packet, 8.0} | ClientOpts]}]),
- ssl_test_lib:check_result(Server3, {error, {eoptions, {packet, 8.0}}},
- Client3, {error, {eoptions, {packet, 8.0}}}),
-
- %% ssl
- Server4 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{verify, 4} | ServerOpts]}]),
- Client4 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{verify, 4} | ClientOpts]}]),
- ssl_test_lib:check_result(Server4, {error, {eoptions, {verify, 4}}},
- Client4, {error, {eoptions, {verify, 4}}}),
-
- Server5 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{depth, four} | ServerOpts]}]),
- Client5 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{depth, four} | ClientOpts]}]),
- ssl_test_lib:check_result(Server5, {error, {eoptions, {depth, four}}},
- Client5, {error, {eoptions, {depth, four}}}),
-
- Server6 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{cacertfile, ""} | ServerOpts]}]),
- Client6 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{cacertfile, ""} | ClientOpts]}]),
- ssl_test_lib:check_result(Server6, {error, {eoptions, {cacertfile, ""}}},
- Client6, {error, {eoptions, {cacertfile, ""}}}),
-
- Server7 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{certfile, 'cert.pem'} | ServerOpts]}]),
- Client7 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{certfile, 'cert.pem'} | ClientOpts]}]),
- ssl_test_lib:check_result(Server7,
- {error, {eoptions, {certfile, 'cert.pem'}}},
- Client7, {error, {eoptions, {certfile, 'cert.pem'}}}),
-
- Server8 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{keyfile,'key.pem' } | ServerOpts]}]),
- Client8 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()}, {options, [{keyfile, 'key.pem'}
- | ClientOpts]}]),
- ssl_test_lib:check_result(Server8,
- {error, {eoptions, {keyfile, 'key.pem'}}},
- Client8, {error, {eoptions, {keyfile, 'key.pem'}}}),
-
- Server9 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{key, 'key.pem' } | ServerOpts]}]),
- Client9 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()}, {options, [{key, 'key.pem'}
- | ClientOpts]}]),
- ssl_test_lib:check_result(Server9, {error, {eoptions, {key, 'key.pem'}}},
- Client9, {error, {eoptions, {key, 'key.pem'}}}),
-
- Server10 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{password, foo} | ServerOpts]}]),
- Client10 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{password, foo} | ClientOpts]}]),
- ssl_test_lib:check_result(Server10, {error, {eoptions, {password, foo}}},
- Client10, {error, {eoptions, {password, foo}}}),
- %% Misc
- Server11 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{ssl_imp, cool} | ServerOpts]}]),
- Client11 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{ssl_imp, cool} | ClientOpts]}]),
- ssl_test_lib:check_result(Server11, {error, {eoptions, {ssl_imp, cool}}},
- Client11, {error, {eoptions, {ssl_imp, cool}}}),
-
- Server12 =
- ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
- {from, self()},
- {options, [{debug, cool} | ServerOpts]}]),
- Client12 =
- ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {options, [{debug, cool} | ClientOpts]}]),
- ssl_test_lib:check_result(Server12, {error, {eoptions, {debug, cool}}},
- Client12, {error, {eoptions, {debug, cool}}}).
+ TestOpts = [{versions, [sslv2, sslv3]},
+ {ssl_imp, cool},
+ {verify, 4},
+ {verify_fun, function},
+ {fail_if_no_peer_cert, 0},
+ {verify_client_once, 1},
+ {validate_extensions_fun, function},
+ {depth, four},
+ {certfile, 'cert.pem'},
+ {keyfile,'key.pem' },
+ {password, foo},
+ {cacertfile, ""},
+ {dhfile,'dh.pem' },
+ {ciphers, [{foo, bar, sha, ignore}]},
+ {reuse_session, foo},
+ {reuse_sessions, 0},
+ {renegotiate_at, "10"},
+ {debug, 1},
+ {mode, depech},
+ {packet, 8.0},
+ {packet_size, "2"},
+ {header, a},
+ {active, trice},
+ {key, 'key.pem' }],
+
+ [begin
+ Server =
+ ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, [TestOpt | ServerOpts]}]),
+ %% Will never reach a point where port is used.
+ Client =
+ ssl_test_lib:start_client_error([{node, ClientNode}, {port, 0},
+ {host, Hostname}, {from, self()},
+ {options, [TestOpt | ClientOpts]}]),
+ Check(Client, Server, TestOpt),
+ ok
+ end || TestOpt <- TestOpts],
+ ok.
%%--------------------------------------------------------------------
shutdown(doc) ->
@@ -1340,20 +1410,130 @@ shutdown_error(Config) when is_list(Config) ->
ok = ssl:close(Listen),
{error, closed} = ssl:shutdown(Listen, read_write).
-%%--------------------------------------------------------------------
-ciphers(doc) ->
- [""];
+%%-------------------------------------------------------------------
+ciphers_rsa_signed_certs(doc) ->
+ ["Test all rsa ssl cipher suites in highest support ssl/tls version"];
+
+ciphers_rsa_signed_certs(suite) ->
+ [];
+
+ciphers_rsa_signed_certs(Config) when is_list(Config) ->
+ Version =
+ ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+
+ Ciphers = ssl_test_lib:rsa_suites(),
+ test_server:format("tls1 erlang cipher suites ~p~n", [Ciphers]),
+ run_suites(Ciphers, Version, Config, rsa).
+
+ciphers_rsa_signed_certs_ssl3(doc) ->
+ ["Test all rsa ssl cipher suites in ssl3"];
+
+ciphers_rsa_signed_certs_ssl3(suite) ->
+ [];
+
+ciphers_rsa_signed_certs_ssl3(Config) when is_list(Config) ->
+ Version =
+ ssl_record:protocol_version({3,0}),
+
+ Ciphers = ssl_test_lib:rsa_suites(),
+ test_server:format("ssl3 erlang cipher suites ~p~n", [Ciphers]),
+ run_suites(Ciphers, Version, Config, rsa).
+
+ciphers_rsa_signed_certs_openssl_names(doc) ->
+ ["Test all rsa ssl cipher suites in highest support ssl/tls version"];
+
+ciphers_rsa_signed_certs_openssl_names(suite) ->
+ [];
+
+ciphers_rsa_signed_certs_openssl_names(Config) when is_list(Config) ->
+ Version =
+ ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+ Ciphers = ssl_test_lib:openssl_rsa_suites(),
+ test_server:format("tls1 openssl cipher suites ~p~n", [Ciphers]),
+ run_suites(Ciphers, Version, Config, rsa).
+
+
+ciphers_rsa_signed_certs_openssl_names_ssl3(doc) ->
+ ["Test all dsa ssl cipher suites in ssl3"];
+
+ciphers_rsa_signed_certs_openssl_names_ssl3(suite) ->
+ [];
+
+ciphers_rsa_signed_certs_openssl_names_ssl3(Config) when is_list(Config) ->
+ Version = ssl_record:protocol_version({3,0}),
+ Ciphers = ssl_test_lib:openssl_rsa_suites(),
+ run_suites(Ciphers, Version, Config, rsa).
+
+
+ciphers_dsa_signed_certs(doc) ->
+ ["Test all dsa ssl cipher suites in highest support ssl/tls version"];
+
+ciphers_dsa_signed_certs(suite) ->
+ [];
+
+ciphers_dsa_signed_certs(Config) when is_list(Config) ->
+ Version =
+ ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+
+ Ciphers = ssl_test_lib:dsa_suites(),
+ test_server:format("tls1 erlang cipher suites ~p~n", [Ciphers]),
+ run_suites(Ciphers, Version, Config, dsa).
+
+ciphers_dsa_signed_certs_ssl3(doc) ->
+ ["Test all dsa ssl cipher suites in ssl3"];
+
+ciphers_dsa_signed_certs_ssl3(suite) ->
+ [];
+
+ciphers_dsa_signed_certs_ssl3(Config) when is_list(Config) ->
+ Version =
+ ssl_record:protocol_version({3,0}),
+
+ Ciphers = ssl_test_lib:dsa_suites(),
+ test_server:format("ssl3 erlang cipher suites ~p~n", [Ciphers]),
+ run_suites(Ciphers, Version, Config, dsa).
+
+
+ciphers_dsa_signed_certs_openssl_names(doc) ->
+ ["Test all dsa ssl cipher suites in highest support ssl/tls version"];
-ciphers(suite) ->
+ciphers_dsa_signed_certs_openssl_names(suite) ->
[];
-ciphers(Config) when is_list(Config) ->
+ciphers_dsa_signed_certs_openssl_names(Config) when is_list(Config) ->
Version =
ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
- Ciphers = ssl:cipher_suites(),
+ Ciphers = ssl_test_lib:openssl_dsa_suites(),
+ test_server:format("tls1 openssl cipher suites ~p~n", [Ciphers]),
+ run_suites(Ciphers, Version, Config, dsa).
+
+
+ciphers_dsa_signed_certs_openssl_names_ssl3(doc) ->
+ ["Test all dsa ssl cipher suites in ssl3"];
+
+ciphers_dsa_signed_certs_openssl_names_ssl3(suite) ->
+ [];
+
+ciphers_dsa_signed_certs_openssl_names_ssl3(Config) when is_list(Config) ->
+ Version = ssl_record:protocol_version({3,0}),
+ Ciphers = ssl_test_lib:openssl_dsa_suites(),
+ run_suites(Ciphers, Version, Config, dsa).
+
+
+run_suites(Ciphers, Version, Config, Type) ->
+ {ClientOpts, ServerOpts} =
+ case Type of
+ rsa ->
+ {?config(client_opts, Config),
+ ?config(server_opts, Config)};
+ dsa ->
+ {?config(client_opts, Config),
+ ?config(server_dsa_opts, Config)}
+ end,
+
Result = lists:map(fun(Cipher) ->
- cipher(Cipher, Version, Config) end,
+ cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end,
Ciphers),
case lists:flatten(Result) of
[] ->
@@ -1362,12 +1542,15 @@ ciphers(Config) when is_list(Config) ->
test_server:format("Cipher suite errors: ~p~n", [Error]),
test_server:fail(cipher_suite_failed_see_test_case_log)
end.
-
-cipher(CipherSuite, Version, Config) ->
+
+erlang_cipher_suite(Suite) when is_list(Suite)->
+ ssl_cipher:suite_definition(ssl_cipher:openssl_suite(Suite));
+erlang_cipher_suite(Suite) ->
+ Suite.
+
+cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) ->
process_flag(trap_exit, true),
test_server:format("Testing CipherSuite ~p~n", [CipherSuite]),
- ClientOpts = ?config(client_opts, Config),
- ServerOpts = ?config(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
{from, self()},
@@ -1382,7 +1565,9 @@ cipher(CipherSuite, Version, Config) ->
[{ciphers,[CipherSuite]} |
ClientOpts]}]),
- ServerMsg = ClientMsg = {ok, {Version, CipherSuite}},
+ ErlangCipherSuite = erlang_cipher_suite(CipherSuite),
+
+ ServerMsg = ClientMsg = {ok, {Version, ErlangCipherSuite}},
Result = ssl_test_lib:wait_for_result(Server, ServerMsg,
Client, ClientMsg),
@@ -1401,7 +1586,7 @@ cipher(CipherSuite, Version, Config) ->
ok ->
[];
Error ->
- [{CipherSuite, Error}]
+ [{ErlangCipherSuite, Error}]
end.
%%--------------------------------------------------------------------
@@ -1812,10 +1997,124 @@ server_verify_none_active_once(Config) when is_list(Config) ->
ssl_test_lib:check_result(Server, ok, Client, ok),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+%%--------------------------------------------------------------------
+server_verify_client_once_passive(doc) ->
+ ["Test server option verify_client_once"];
+
+server_verify_client_once_passive(suite) ->
+ [];
+
+server_verify_client_once_passive(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result, []}},
+ {options, [{active, false}, {verify, verify_peer},
+ {verify_client_once, true}
+ | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result, []}},
+ {options, [{active, false} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client0, ok),
+ ssl_test_lib:close(Client0),
+ Server ! listen,
+ Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, result_ok, []}},
+ {options, [{active, false} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Client1, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client1).
%%--------------------------------------------------------------------
+server_verify_client_once_active(doc) ->
+ ["Test server option verify_client_once"];
+
+server_verify_client_once_active(suite) ->
+ [];
+
+server_verify_client_once_active(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, [{active, once}, {verify, verify_peer},
+ {verify_client_once, true}
+ | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, [{active, true} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client0, ok),
+ ssl_test_lib:close(Client0),
+ Server ! listen,
+ Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, result_ok, []}},
+ {options, [{active, true} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Client1, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client1).
+
+
+%%--------------------------------------------------------------------
+
+server_verify_client_once_active_once(doc) ->
+ ["Test server option verify_client_once"];
+
+server_verify_client_once_active_once(suite) ->
+ [];
+
+server_verify_client_once_active_once(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active_once, []}},
+ {options, [{active, once}, {verify, verify_peer},
+ {verify_client_once, true}
+ | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active_once, []}},
+ {options, [{active, once} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client0, ok),
+ ssl_test_lib:close(Client0),
+ Server ! listen,
+
+ Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, result_ok, []}},
+ {options, [{active, once} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Client1, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client1).
+
+%%--------------------------------------------------------------------
+
server_verify_no_cacerts(doc) ->
["Test server must have cacerts if it wants to verify client"];
@@ -1874,22 +2173,20 @@ server_require_peer_cert_fail(Config) when is_list(Config) ->
| ?config(server_verification_opts, Config)],
BadClientOpts = ?config(client_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
- Port = ssl_test_lib:inet_port(ServerNode),
-
- Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, Port},
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, send_recv_result, []}},
{options, [{active, false} | ServerOpts]}]),
- Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
- {host, Hostname},
- {from, self()},
- {mfa, {?MODULE, send_recv_result, []}},
- {options, [{active, false} | BadClientOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, [{active, false} | BadClientOpts]}]),
ssl_test_lib:check_result(Server, {error, esslaccept},
- Client, {error, esslconnect}),
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client).
+ Client, {error, esslconnect}).
%%--------------------------------------------------------------------
@@ -2019,8 +2316,6 @@ client_renegotiate(Config) when is_list(Config) ->
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -2057,8 +2352,6 @@ server_renegotiate(Config) when is_list(Config) ->
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -2071,6 +2364,76 @@ server_renegotiate(Config) when is_list(Config) ->
ok.
%%--------------------------------------------------------------------
+client_renegotiate_reused_session(doc) ->
+ ["Test ssl:renegotiate/1 on client when the ssl session will be reused."];
+
+client_renegotiate_reused_session(suite) ->
+ [];
+
+client_renegotiate_reused_session(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ?config(server_opts, Config),
+ ClientOpts = ?config(client_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = "From erlang to erlang",
+
+ Server =
+ ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, erlang_ssl_receive, [Data]}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ renegotiate_reuse_session, [Data]}},
+ {options, [{reuse_sessions, true} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Client, ok, Server, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ process_flag(trap_exit, false),
+ ok.
+%%--------------------------------------------------------------------
+server_renegotiate_reused_session(doc) ->
+ ["Test ssl:renegotiate/1 on server when the ssl session will be reused."];
+
+server_renegotiate_reused_session(suite) ->
+ [];
+
+server_renegotiate_reused_session(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ?config(server_opts, Config),
+ ClientOpts = ?config(client_opts, Config),
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = "From erlang to erlang",
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE,
+ renegotiate_reuse_session, [Data]}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, erlang_ssl_receive, [Data]}},
+ {options, [{reuse_sessions, true} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client),
+ ok.
+
+%%--------------------------------------------------------------------
client_no_wrap_sequence_number(doc) ->
["Test that erlang client will renegotiate session when",
"max sequence number celing is about to be reached. Although"
@@ -2097,8 +2460,6 @@ client_no_wrap_sequence_number(Config) when is_list(Config) ->
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -2140,8 +2501,6 @@ server_no_wrap_sequence_number(Config) when is_list(Config) ->
{options, [{renegotiate_at, N} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -2154,17 +2513,351 @@ server_no_wrap_sequence_number(Config) when is_list(Config) ->
ok.
%%--------------------------------------------------------------------
+extended_key_usage(doc) ->
+ ["Test cert that has a critical extended_key_usage extension"];
+
+extended_key_usage(suite) ->
+ [];
+
+extended_key_usage(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_verification_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"),
+ {ok, [KeyInfo]} = public_key:pem_to_der(KeyFile),
+ {ok, Key} = public_key:decode_private_key(KeyInfo),
+
+ ServerCertFile = proplists:get_value(certfile, ServerOpts),
+ NewServerCertFile = filename:join(PrivDir, "server/new_cert.pem"),
+ {ok, [{cert, ServerDerCert, _}]} = public_key:pem_to_der(ServerCertFile),
+ {ok, ServerOTPCert} = public_key:pkix_decode_cert(ServerDerCert, otp),
+ ServerExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-serverAuth']},
+ ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate,
+ ServerExtensions = ServerOTPTbsCert#'OTPTBSCertificate'.extensions,
+ NewServerOTPTbsCert = ServerOTPTbsCert#'OTPTBSCertificate'{extensions =
+ [ServerExtKeyUsageExt |
+ ServerExtensions]},
+ NewServerDerCert = public_key:sign(NewServerOTPTbsCert, Key),
+ public_key:der_to_pem(NewServerCertFile, [{cert, NewServerDerCert, not_encrypted}]),
+ NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)],
+
+ ClientCertFile = proplists:get_value(certfile, ClientOpts),
+ NewClientCertFile = filename:join(PrivDir, "client/new_cert.pem"),
+ {ok, [{cert, ClientDerCert, _}]} = public_key:pem_to_der(ClientCertFile),
+ {ok, ClientOTPCert} = public_key:pkix_decode_cert(ClientDerCert, otp),
+ ClientExtKeyUsageExt = {'Extension', ?'id-ce-extKeyUsage', true, [?'id-kp-clientAuth']},
+ ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate,
+ ClientExtensions = ClientOTPTbsCert#'OTPTBSCertificate'.extensions,
+ NewClientOTPTbsCert = ClientOTPTbsCert#'OTPTBSCertificate'{extensions =
+ [ClientExtKeyUsageExt |
+ ClientExtensions]},
+ NewClientDerCert = public_key:sign(NewClientOTPTbsCert, Key),
+ public_key:der_to_pem(NewClientCertFile, [{cert, NewClientDerCert, not_encrypted}]),
+ NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)],
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, [{verify, verify_peer} | NewServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, [{verify, verify_peer} | NewClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+validate_extensions_fun(doc) ->
+ ["Test that it is possible to specify a validate_extensions_fun"];
+
+validate_extensions_fun(suite) ->
+ [];
+
+validate_extensions_fun(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_verification_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+
+ Fun = fun(Extensions, State, _, AccError) ->
+ {Extensions, State, AccError}
+ end,
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, [{validate_extensions_fun, Fun},
+ {verify, verify_peer} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options,[{validate_extensions_fun, Fun} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+no_authority_key_identifier(doc) ->
+ ["Test cert that does not have authorityKeyIdentifier extension"];
+
+no_authority_key_identifier(suite) ->
+ [];
+no_authority_key_identifier(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"),
+ {ok, [KeyInfo]} = public_key:pem_to_der(KeyFile),
+ {ok, Key} = public_key:decode_private_key(KeyInfo),
+
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ NewCertFile = filename:join(PrivDir, "server/new_cert.pem"),
+ {ok, [{cert, DerCert, _}]} = public_key:pem_to_der(CertFile),
+ {ok, OTPCert} = public_key:pkix_decode_cert(DerCert, otp),
+ OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate,
+ Extensions = OTPTbsCert#'OTPTBSCertificate'.extensions,
+ NewExtensions = delete_authority_key_extension(Extensions, []),
+ NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{extensions = NewExtensions},
+
+ test_server:format("Extensions ~p~n, NewExtensions: ~p~n", [Extensions, NewExtensions]),
+
+ NewDerCert = public_key:sign(NewOTPTbsCert, Key),
+ public_key:der_to_pem(NewCertFile, [{cert, NewDerCert, not_encrypted}]),
+ NewServerOpts = [{certfile, NewCertFile} | proplists:delete(certfile, ServerOpts)],
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, NewServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, send_recv_result_active, []}},
+ {options, [{verify, verify_peer} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+delete_authority_key_extension([], Acc) ->
+ lists:reverse(Acc);
+delete_authority_key_extension([#'Extension'{extnID = ?'id-ce-authorityKeyIdentifier'} | Rest],
+ Acc) ->
+ delete_authority_key_extension(Rest, Acc);
+delete_authority_key_extension([Head | Rest], Acc) ->
+ delete_authority_key_extension(Rest, [Head | Acc]).
+
+%%--------------------------------------------------------------------
+
+invalid_signature_server(doc) ->
+ ["Test server with invalid signature"];
+
+invalid_signature_server(suite) ->
+ [];
+
+invalid_signature_server(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ KeyFile = filename:join(PrivDir, "server/key.pem"),
+ {ok, [KeyInfo]} = public_key:pem_to_der(KeyFile),
+ {ok, Key} = public_key:decode_private_key(KeyInfo),
+
+ ServerCertFile = proplists:get_value(certfile, ServerOpts),
+ NewServerCertFile = filename:join(PrivDir, "server/invalid_cert.pem"),
+ {ok, [{cert, ServerDerCert, _}]} = public_key:pem_to_der(ServerCertFile),
+ {ok, ServerOTPCert} = public_key:pkix_decode_cert(ServerDerCert, otp),
+ ServerOTPTbsCert = ServerOTPCert#'OTPCertificate'.tbsCertificate,
+ NewServerDerCert = public_key:sign(ServerOTPTbsCert, Key),
+ public_key:der_to_pem(NewServerCertFile, [{cert, NewServerDerCert, not_encrypted}]),
+ NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)],
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, NewServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, {error, "bad certificate"},
+ Client, {error,"bad certificate"}).
+
+%%--------------------------------------------------------------------
+
+invalid_signature_client(doc) ->
+ ["Test server with invalid signature"];
+
+invalid_signature_client(suite) ->
+ [];
+
+invalid_signature_client(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_verification_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ KeyFile = filename:join(PrivDir, "client/key.pem"),
+ {ok, [KeyInfo]} = public_key:pem_to_der(KeyFile),
+ {ok, Key} = public_key:decode_private_key(KeyInfo),
+
+ ClientCertFile = proplists:get_value(certfile, ClientOpts),
+ NewClientCertFile = filename:join(PrivDir, "client/invalid_cert.pem"),
+ {ok, [{cert, ClientDerCert, _}]} = public_key:pem_to_der(ClientCertFile),
+ {ok, ClientOTPCert} = public_key:pkix_decode_cert(ClientDerCert, otp),
+ ClientOTPTbsCert = ClientOTPCert#'OTPCertificate'.tbsCertificate,
+ NewClientDerCert = public_key:sign(ClientOTPTbsCert, Key),
+ public_key:der_to_pem(NewClientCertFile, [{cert, NewClientDerCert, not_encrypted}]),
+ NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)],
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, [{verify, verify_peer} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, NewClientOpts}]),
+
+ tcp_delivery_workaround(Server, {error, "bad certificate"},
+ Client, {error,"bad certificate"}).
+
+tcp_delivery_workaround(Server, ServMsg, Client, ClientMsg) ->
+ receive
+ {Server, ServerMsg} ->
+ receive
+ {Client, ClientMsg} ->
+ ok;
+ {Client, {error,closed}} ->
+ test_server:format("client got close");
+ Unexpected ->
+ test_server:fail(Unexpected)
+ end;
+ {Client, ClientMsg} ->
+ receive
+ {Server, ServerMsg} ->
+ ok;
+ Unexpected ->
+ test_server:fail(Unexpected)
+ end;
+ {Client, {error,closed}} ->
+ receive
+ {Server, ServerMsg} ->
+ ok;
+ Unexpected ->
+ test_server:fail(Unexpected)
+ end;
+ {Server, {error,closed}} ->
+ receive
+ {Client, ClientMsg} ->
+ ok;
+ {Client, {error,closed}} ->
+ test_server:format("client got close"),
+ ok;
+ Unexpected ->
+ test_server:fail(Unexpected)
+ end;
+ Unexpected ->
+ test_server:fail(Unexpected)
+ end.
+%%--------------------------------------------------------------------
+cert_expired(doc) ->
+ ["Test server with invalid signature"];
+
+cert_expired(suite) ->
+ [];
+
+cert_expired(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_verification_opts, Config),
+ PrivDir = ?config(priv_dir, Config),
+
+ KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"),
+ {ok, [KeyInfo]} = public_key:pem_to_der(KeyFile),
+ {ok, Key} = public_key:decode_private_key(KeyInfo),
+
+ ServerCertFile = proplists:get_value(certfile, ServerOpts),
+ NewServerCertFile = filename:join(PrivDir, "server/expired_cert.pem"),
+ {ok, [{cert, DerCert, _}]} = public_key:pem_to_der(ServerCertFile),
+ {ok, OTPCert} = public_key:pkix_decode_cert(DerCert, otp),
+ OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate,
+
+ {Year, Month, Day} = date(),
+ {Hours, Min, Sec} = time(),
+ NotBeforeStr = lists:flatten(io_lib:format("~p~s~s~s~s~sZ",[Year-2,
+ two_digits_str(Month),
+ two_digits_str(Day),
+ two_digits_str(Hours),
+ two_digits_str(Min),
+ two_digits_str(Sec)])),
+ NotAfterStr = lists:flatten(io_lib:format("~p~s~s~s~s~sZ",[Year-1,
+ two_digits_str(Month),
+ two_digits_str(Day),
+ two_digits_str(Hours),
+ two_digits_str(Min),
+ two_digits_str(Sec)])),
+ NewValidity = {'Validity', {generalTime, NotBeforeStr}, {generalTime, NotAfterStr}},
+
+ test_server:format("Validity: ~p ~n NewValidity: ~p ~n",
+ [OTPTbsCert#'OTPTBSCertificate'.validity, NewValidity]),
+
+ NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{validity = NewValidity},
+ NewServerDerCert = public_key:sign(NewOTPTbsCert, Key),
+ public_key:der_to_pem(NewServerCertFile, [{cert, NewServerDerCert, not_encrypted}]),
+ NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)],
+
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, NewServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {options, [{verify, verify_peer} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, {error, "certificate expired"},
+ Client, {error, "certificate expired"}).
+
+two_digits_str(N) when N < 10 ->
+ lists:flatten(io_lib:format("0~p", [N]));
+two_digits_str(N) ->
+ lists:flatten(io_lib:format("~p", [N])).
+
+%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------
send_recv_result(Socket) ->
ssl:send(Socket, "Hello world"),
- test_server:sleep(?SLEEP),
{ok,"Hello world"} = ssl:recv(Socket, 11),
ok.
send_recv_result_active(Socket) ->
ssl:send(Socket, "Hello world"),
- test_server:sleep(?SLEEP),
receive
{ssl, Socket, "Hello world"} ->
ok
@@ -2172,26 +2865,31 @@ send_recv_result_active(Socket) ->
send_recv_result_active_once(Socket) ->
ssl:send(Socket, "Hello world"),
- test_server:sleep(?SLEEP),
receive
{ssl, Socket, "Hello world"} ->
ok
end.
+result_ok(_Socket) ->
+ ok.
renegotiate(Socket, Data) ->
- [{session_id, Id} | _ ] = ssl:session_info(Socket),
- ssl:renegotiate(Socket),
+ test_server:format("Renegotiating ~n", []),
+ Result = ssl:renegotiate(Socket),
+ test_server:format("Result ~p~n", [Result]),
ssl:send(Socket, Data),
- test_server:sleep(1000),
- case ssl:session_info(Socket) of
- [{session_id, Id} | _ ] ->
- fail_session_not_renegotiated;
- _ ->
- ok
+ case Result of
+ ok ->
+ ok;
+ Other ->
+ Other
end.
-
+renegotiate_reuse_session(Socket, Data) ->
+ %% Make sure session is registerd
+ test_server:sleep(?SLEEP),
+ renegotiate(Socket, Data).
+
session_cache_process_list(doc) ->
["Test reuse of sessions (short handshake)"];
@@ -2209,128 +2907,34 @@ session_cache_process_mnesia(Config) when is_list(Config) ->
session_cache_process(mnesia,Config).
session_cache_process(Type,Config) when is_list(Config) ->
- process_flag(trap_exit, true),
- setup_session_cb(Type),
-
- ClientOpts = ?config(client_opts, Config),
- ServerOpts = ?config(server_opts, Config),
- {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
-
- Server =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, session_info_result, []}},
- {options,
- [{session_cache_cb, ?MODULE}|
- ServerOpts]}]),
- Port = ssl_test_lib:inet_port(Server),
- Client0 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {ssl_test_lib, no_result, []}},
- {from, self()}, {options, ClientOpts}]),
- SessionInfo =
- receive
- {Server, Info} ->
- Info
- end,
-
- Server ! listen,
-
- %% Make sure session is registered
- test_server:sleep(?SLEEP),
+ reuse_session(Config).
- Client1 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port}, {host, Hostname},
- {mfa, {?MODULE, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
- receive
- {Client1, SessionInfo} ->
- ok;
- {Client1, Other} ->
- test_server:format("Expected: ~p, Unexpected: ~p~n",
- [SessionInfo, Other]),
- test_server:fail(session_not_reused)
- end,
-
- ssl_test_lib:close(Server),
- ssl_test_lib:close(Client0),
- ssl_test_lib:close(Client1),
-
- Server1 =
- ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
- {from, self()},
- {mfa, {?MODULE, session_info_result, []}},
- {options,
- [{reuse_sessions, false} | ServerOpts]}]),
- Port1 = ssl_test_lib:inet_port(Server1),
-
- Client3 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {?MODULE, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- SessionInfo1 =
- receive
- {Server1, Info1} ->
- Info1
- end,
-
- Server1 ! listen,
-
- %% Make sure session is registered
- test_server:sleep(?SLEEP),
-
- Client4 =
- ssl_test_lib:start_client([{node, ClientNode},
- {port, Port1}, {host, Hostname},
- {mfa, {?MODULE, session_info_result, []}},
- {from, self()}, {options, ClientOpts}]),
-
- receive
- {Client4, SessionInfo1} ->
- test_server:fail(
- session_reused_when_session_reuse_disabled_by_server);
- {Client4, _Other} ->
- ok
- end,
-
- ssl_test_lib:close(Server1),
- ssl_test_lib:close(Client3),
- ssl_test_lib:close(Client4),
- process_flag(trap_exit, false).
-
-setup_session_cb(Type) ->
- ssl_test = ets:new(ssl_test,[named_table, set,public]),
- ets:insert(ssl_test, {type,Type}).
-
-session_cb() ->
- [{type,Type}] = ets:lookup(ssl_test, type),
- Type.
-
-init() ->
- io:format("~p~n",[?LINE]),
- case session_cb() of
+init([Type]) ->
+ ets:new(ssl_test, [named_table, public, set]),
+ ets:insert(ssl_test, {type, Type}),
+ case Type of
list ->
spawn(fun() -> session_loop([]) end);
mnesia ->
mnesia:start(),
- {atomic,ok} = mnesia:create_table(sess_cache, [])
+ {atomic,ok} = mnesia:create_table(sess_cache, []),
+ sess_cache
end.
+session_cb() ->
+ [{type, Type}] = ets:lookup(ssl_test, type),
+ Type.
+
terminate(Cache) ->
- io:format("~p~n",[?LINE]),
case session_cb() of
list ->
Cache ! terminate;
mnesia ->
- {atomic,ok} = mnesia:delete_table(sess_cache, [])
+ catch {atomic,ok} =
+ mnesia:delete_table(sess_cache)
end.
-lookup(Cache, Key) ->
- io:format("~p~n",[?LINE]),
+lookup(Cache, Key) ->
case session_cb() of
list ->
Cache ! {self(), lookup, Key},
@@ -2340,13 +2944,14 @@ lookup(Cache, Key) ->
mnesia:read(sess_cache,
Key, read)
end) of
- {atomic, [Session]} -> Session;
- _ -> undefined
+ {atomic, [{sess_cache, Key, Value}]} ->
+ Value;
+ _ ->
+ undefined
end
- end.
+ end.
update(Cache, Key, Value) ->
- io:format("~p~n",[?LINE]),
case session_cb() of
list ->
Cache ! {update, Key, Value};
@@ -2354,12 +2959,11 @@ update(Cache, Key, Value) ->
{atomic, ok} =
mnesia:transaction(fun() ->
mnesia:write(sess_cache,
- Key, Value)
+ {sess_cache, Key, Value}, write)
end)
end.
delete(Cache, Key) ->
- io:format("~p~n",[?LINE]),
case session_cb() of
list ->
Cache ! {delete, Key};
@@ -2371,7 +2975,6 @@ delete(Cache, Key) ->
end.
foldl(Fun, Acc, Cache) ->
- io:format("~p~n",[?LINE]),
case session_cb() of
list ->
Cache ! {self(),foldl,Fun,Acc},
@@ -2385,15 +2988,17 @@ foldl(Fun, Acc, Cache) ->
end.
select_session(Cache, PartialKey) ->
- io:format("~p~n",[?LINE]),
case session_cb() of
list ->
Cache ! {self(),select_session, PartialKey},
- receive {Cache, Res} -> Res end;
+ receive
+ {Cache, Res} ->
+ Res
+ end;
mnesia ->
Sel = fun() ->
mnesia:select(Cache,
- [{{{PartialKey,'$1'}, '$2'},
+ [{{sess_cache,{PartialKey,'$1'}, '$2'},
[],['$$']}])
end,
{atomic, Res} = mnesia:transaction(Sel),
@@ -2413,7 +3018,8 @@ session_loop(Sess) ->
end,
session_loop(Sess);
{update, Key, Value} ->
- session_loop([{Key,Value}|Sess]);
+ NewSess = [{Key,Value}| lists:keydelete(Key,1,Sess)],
+ session_loop(NewSess);
{delete, Key} ->
session_loop(lists:keydelete(Key,1,Sess));
{Pid,foldl,Fun,Acc} ->
@@ -2421,15 +3027,17 @@ session_loop(Sess) ->
Pid ! {self(), Res},
session_loop(Sess);
{Pid,select_session,PKey} ->
- Sel = fun({{Head, _},Session}, Acc) when Head =:= PKey ->
- [Session|Acc];
+ Sel = fun({{PKey0, Id},Session}, Acc) when PKey == PKey0 ->
+ [[Id, Session]|Acc];
(_,Acc) ->
Acc
- end,
- Pid ! {self(), lists:foldl(Sel, [], Sess)},
+ end,
+ Sessions = lists:foldl(Sel, [], Sess),
+ Pid ! {self(), Sessions},
session_loop(Sess)
end.
+
erlang_ssl_receive(Socket, Data) ->
receive
{ssl, Socket, Data} ->
@@ -2437,7 +3045,6 @@ erlang_ssl_receive(Socket, Data) ->
ok;
Other ->
test_server:fail({unexpected_message, Other})
- after 4000 ->
+ after ?SLEEP * 3 ->
test_server:fail({did_not_get, Data})
end.
-
diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl
index 1bcb9a657b..1b8754afe9 100644
--- a/lib/ssl/test/ssl_packet_SUITE.erl
+++ b/lib/ssl/test/ssl_packet_SUITE.erl
@@ -42,7 +42,6 @@
-define(MANY, 1000).
-define(SOME, 50).
-
%% Test server callback functions
%%--------------------------------------------------------------------
%% Function: init_per_suite(Config) -> Config
@@ -55,6 +54,7 @@
%%--------------------------------------------------------------------
init_per_suite(Config) ->
crypto:start(),
+ application:start(public_key),
ssl:start(),
Result =
(catch make_certs:all(?config(data_dir, Config),
@@ -144,9 +144,20 @@ all(suite) ->
packet_wait_passive, packet_wait_active,
packet_baddata_passive, packet_baddata_active,
packet_size_passive, packet_size_active,
- packet_erl_decode,
+ packet_cdr_decode,
packet_http_decode,
- packet_http_bin_decode_multi
+ packet_http_decode_list,
+ packet_http_bin_decode_multi,
+ packet_line_decode,
+ packet_asn1_decode,
+ packet_tpkt_decode,
+ %packet_fcgi_decode,
+ packet_sunrm_decode,
+ header_decode_one_byte,
+ header_decode_two_bytes,
+ header_decode_two_bytes_one_sent,
+ header_decode_two_bytes_two_sent
+
].
%% Test cases starts here.
@@ -503,7 +514,8 @@ packet_raw_active_once_many_small(Config) when is_list(Config) ->
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, active_once_raw, [Data, ?MANY]}},
+ {mfa, {?MODULE, active_once_raw,
+ [Data, ?MANY]}},
{options, [{active, once},
{packet, raw} |
ClientOpts]}]),
@@ -535,7 +547,8 @@ packet_raw_active_once_some_big(Config) when is_list(Config) ->
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, active_once_raw, [Data, ?SOME]}},
+ {mfa, {?MODULE, active_once_raw,
+ [Data, ?SOME]}},
{options, [{active, once},
{packet, raw} |
ClientOpts]}]),
@@ -1191,7 +1204,8 @@ packet_send_to_large(Config) when is_list(Config) ->
{mfa, {?MODULE, active_packet, [Data, 1]}},
{options, [{active, true} | ClientOpts]}]),
- ssl_test_lib:check_result(Server, {error, {badarg, {packet_to_large, 300, 255}}}),
+ ssl_test_lib:check_result(Server, {error, {badarg,
+ {packet_to_large, 300, 255}}}),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
@@ -1216,7 +1230,8 @@ packet_wait_active(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, send_incomplete ,[Data, ?SOME]}},
+ {mfa, {?MODULE, send_incomplete,
+ [Data, ?SOME]}},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
@@ -1251,7 +1266,8 @@ packet_wait_passive(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, send_incomplete ,[Data, ?SOME]}},
+ {mfa, {?MODULE, send_incomplete,
+ [Data, ?SOME]}},
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
@@ -1293,7 +1309,8 @@ packet_baddata_active(Config) when is_list(Config) ->
{packet, cdr} |
ClientOpts]}]),
receive
- {Client, {other, {ssl_error, _Socket, {invalid_packet, _}},{error,closed},1}} -> ok;
+ {Client, {other, {ssl_error, _Socket,
+ {invalid_packet, _}},{error,closed},1}} -> ok;
Unexpected ->
test_server:fail({unexpected, Unexpected})
end,
@@ -1338,8 +1355,11 @@ packet_baddata_passive(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+
packet_size_active(doc) ->
- ["Test that if a packet of size larger than packet_size arrives error msg is sent and socket is closed"];
+ ["Test that if a packet of size larger than
+ packet_size arrives error msg is sent and socket is closed"];
+
packet_size_active(suite) ->
[];
@@ -1363,7 +1383,8 @@ packet_size_active(Config) when is_list(Config) ->
{packet, 4}, {packet_size, 10} |
ClientOpts]}]),
receive
- {Client, {other, {ssl_error, _Socket, {invalid_packet, _}},{error,closed},1}} -> ok;
+ {Client, {other, {ssl_error, _Socket,
+ {invalid_packet, _}},{error,closed},1}} -> ok;
Unexpected ->
test_server:fail({unexpected, Unexpected})
end,
@@ -1371,10 +1392,11 @@ packet_size_active(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
+
packet_size_passive(doc) ->
- ["Test that if a packet of size larger than packet_size arrives error msg is sent and socket is closed"];
-packet_size_passive(suite) ->
- [];
+ ["Test that if a packet of size larger
+ than packet_size arrives error msg is sent and socket is closed"];
+packet_size_passive(suite) -> [];
packet_size_passive(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
@@ -1391,7 +1413,8 @@ packet_size_passive(Config) when is_list(Config) ->
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, passive_recv_packet, [Data, 1]}},
+ {mfa, {?MODULE, passive_recv_packet,
+ [Data, 1]}},
{options, [{active, false},
{packet, 4}, {packet_size, 30} |
ClientOpts]}]),
@@ -1405,14 +1428,11 @@ packet_size_passive(Config) when is_list(Config) ->
ssl_test_lib:close(Client).
%%--------------------------------------------------------------------
-packet_erl_decode(doc) ->
- ["Test that packets of sent to erlang:decode_packet works, i.e. currently"
- "asn1 | cdr | sunrm | fcgi | tpkt | line | http | http_bin"
- ];
-packet_erl_decode(suite) ->
+packet_cdr_decode(doc) ->
+ ["Test setting the packet option {packet, cdr}"];
+packet_cdr_decode(suite) ->
[];
-
-packet_erl_decode(Config) when is_list(Config) ->
+packet_cdr_decode(Config) when is_list(Config) ->
ClientOpts = ?config(client_opts, Config),
ServerOpts = ?config(server_opts, Config),
{ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
@@ -1423,54 +1443,28 @@ packet_erl_decode(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, server_packet_decode ,[Data]}},
- {options, [{active, true}, binary, {packet, cdr}|ServerOpts]}]),
+ {mfa, {?MODULE, server_packet_decode,
+ [Data]}},
+ {options, [{active, true}, binary,
+ {packet, cdr}|ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, client_packet_decode, [Data]}},
- {options, [{active, true}, binary | ClientOpts]}]),
+ {mfa, {?MODULE, client_packet_decode,
+ [Data]}},
+ {options, [{active, true}, {packet, cdr},
+ binary | ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
-
-server_packet_decode(Socket, CDR) ->
- receive
- {ssl, Socket, CDR} -> ok;
- Other1 -> exit({?LINE, Other1})
- end,
- ok = ssl:send(Socket, CDR),
- receive
- {ssl, Socket, CDR} -> ok;
- Other2 -> exit({?LINE, Other2})
- end,
- ok = ssl:send(Socket, CDR),
- ok.
-
-client_packet_decode(Socket, CDR) ->
- <<P1:10/binary, P2/binary>> = CDR,
- ok = ssl:send(Socket, P1),
- ok = ssl:send(Socket, P2),
- receive
- {ssl, Socket, CDR} -> ok;
- Other1 -> exit({?LINE, Other1})
- end,
- ssl:setopts(Socket, [{packet, cdr}]),
- ok = ssl:send(Socket, CDR),
- receive
- {ssl, Socket, CDR} -> ok;
- Other2 -> exit({?LINE, Other2})
- end,
- ok.
-
%%--------------------------------------------------------------------
packet_http_decode(doc) ->
- ["Test setting the packet option {packet, http}"];
+ ["Test setting the packet option {packet, http} {mode, binary}"];
packet_http_decode(suite) ->
[];
@@ -1489,16 +1483,19 @@ packet_http_decode(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, server_http_decode, [Response]}},
- {options, [{active, true}, binary, {packet, http} |
- ServerOpts]}]),
+ {mfa, {?MODULE, server_http_decode,
+ [Response]}},
+ {options, [{active, true}, binary,
+ {packet, http} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, client_http_decode, [Request]}},
- {options, [{active, true}, binary, {packet, http} |
+ {mfa, {?MODULE, client_http_decode,
+ [Request]}},
+ {options, [{active, true}, binary,
+ {packet, http} |
ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -1550,6 +1547,65 @@ client_http_decode(Socket, HttpRequest) ->
ok.
%%--------------------------------------------------------------------
+packet_http_decode_list(doc) ->
+ ["Test setting the packet option {packet, http}, {mode, list}"];
+packet_http_decode_list(suite) ->
+ [];
+packet_http_decode_list(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Request = "GET / HTTP/1.1\r\n"
+ "host: www.example.com\r\n"
+ "user-agent: HttpTester\r\n"
+ "\r\n",
+ Response = "HTTP/1.1 200 OK\r\n"
+ "\r\n"
+ "Hello!",
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_http_decode,
+ [Response]}},
+ {options, [{active, true}, binary,
+ {packet, http} |
+ ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_http_decode_list,
+ [Request]}},
+ {options, [{active, true}, list,
+ {packet, http} |
+ ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
+client_http_decode_list(Socket, HttpRequest) ->
+ ok = ssl:send(Socket, HttpRequest),
+ receive
+ {ssl, Socket, {http_response, {1,1}, 200, "OK"}} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ receive
+ {ssl, Socket, http_eoh} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ ok = ssl:setopts(Socket, [{packet, 0}]),
+ receive
+ {ssl, Socket, "Hello!"} -> ok;
+ Other3 -> exit({?LINE, Other3})
+ end,
+ ok.
+
+%%--------------------------------------------------------------------
packet_http_bin_decode_multi(doc) ->
["Test setting the packet option {packet, http_bin} with multiple requests"];
packet_http_bin_decode_multi(suite) ->
@@ -1571,16 +1627,20 @@ packet_http_bin_decode_multi(Config) when is_list(Config) ->
Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
{from, self()},
- {mfa, {?MODULE, server_http_bin_decode, [Response, NumMsgs]}},
- {options, [{active, true}, binary, {packet, http_bin} |
+ {mfa, {?MODULE, server_http_bin_decode,
+ [Response, NumMsgs]}},
+ {options, [{active, true}, binary,
+ {packet, http_bin} |
ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
{host, Hostname},
{from, self()},
- {mfa, {?MODULE, client_http_bin_decode, [Request, NumMsgs]}},
- {options, [{active, true}, binary, {packet, http_bin} |
+ {mfa, {?MODULE, client_http_bin_decode,
+ [Request, NumMsgs]}},
+ {options, [{active, true}, binary,
+ {packet, http_bin} |
ClientOpts]}]),
ssl_test_lib:check_result(Server, ok, Client, ok),
@@ -1635,6 +1695,344 @@ client_http_bin_decode(Socket, HttpRequest, Count) when Count > 0 ->
client_http_bin_decode(Socket, HttpRequest, Count - 1);
client_http_bin_decode(_, _, _) ->
ok.
+%%--------------------------------------------------------------------
+packet_line_decode(doc) ->
+ ["Test setting the packet option {packet, line}"];
+packet_line_decode(suite) ->
+ [];
+packet_line_decode(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = list_to_binary(lists:flatten(io_lib:format("Line ends here.~n"
+ "Now it is a new line.~n",
+ []))),
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_line_packet_decode,
+ [Data]}},
+ {options, [{active, true}, binary,
+ {packet, line}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_line_packet_decode,
+ [Data]}},
+ {options, [{active, true},
+ {packet, line},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
+server_line_packet_decode(Socket, Lines) ->
+ receive
+ {ssl, Socket, <<"Line ends here.\n">>} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ receive
+ {ssl, Socket, <<"Now it is a new line.\n">>} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ ok = ssl:send(Socket, Lines).
+
+client_line_packet_decode(Socket, Lines) ->
+ <<P1:10/binary, P2/binary>> = Lines,
+ ok = ssl:send(Socket, P1),
+ ok = ssl:send(Socket, P2),
+ receive
+ {ssl, Socket, <<"Line ends here.\n">>} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ receive
+ {ssl, Socket, <<"Now it is a new line.\n">>} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end.
+
+%%--------------------------------------------------------------------
+
+packet_asn1_decode(doc) ->
+ ["Test setting the packet option {packet, asn1}"];
+packet_asn1_decode(suite) ->
+ [];
+packet_asn1_decode(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ File = proplists:get_value(certfile, ServerOpts),
+
+ %% A valid asn1 BER packet (DER is stricter BER)
+ {ok,[{cert, Data, _}]} = public_key:pem_to_der(File),
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_packet_decode,
+ [Data]}},
+ {options, [{active, true}, binary,
+ {packet, asn1}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_packet_decode,
+ [Data]}},
+ {options, [{active, true}, {packet, asn1},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+packet_tpkt_decode(doc) ->
+ ["Test setting the packet option {packet, tpkt}"];
+packet_tpkt_decode(suite) ->
+ [];
+packet_tpkt_decode(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = list_to_binary(add_tpkt_header("TPKT data")),
+
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_packet_decode,
+ [Data]}},
+ {options, [{active, true}, binary,
+ {packet, tpkt}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_packet_decode,
+ [Data]}},
+ {options, [{active, true}, {packet, tpkt},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+
+%% packet_fcgi_decode(doc) ->
+%% ["Test setting the packet option {packet, fcgi}"];
+%% packet_fcgi_decode(suite) ->
+%% [];
+%% packet_fcgi_decode(Config) when is_list(Config) ->
+%% ClientOpts = ?config(client_opts, Config),
+%% ServerOpts = ?config(server_opts, Config),
+%% {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+%% Data = ...
+
+%% Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+%% {from, self()},
+%% {mfa, {?MODULE, server_packet_decode,
+%% [Data0, Data1]}},
+%% {options, [{active, true}, binary,
+%% {packet, fcgi}|ServerOpts]}]),
+
+%% Port = ssl_test_lib:inet_port(Server),
+%% Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+%% {host, Hostname},
+%% {from, self()},
+%% {mfa, {?MODULE, client_packet_decode,
+%% [Data0, Data1]}},
+%% {options, [{active, true}, {packet, fcgi},
+%% binary | ClientOpts]}]),
+
+%% ssl_test_lib:check_result(Server, ok, Client, ok),
+
+%% ssl_test_lib:close(Server),
+%% ssl_test_lib:close(Client).
+
+
+%%--------------------------------------------------------------------
+
+packet_sunrm_decode(doc) ->
+ ["Test setting the packet option {packet, sunrm}"];
+packet_sunrm_decode(suite) ->
+ [];
+packet_sunrm_decode(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = <<11:32, "Hello world">>,
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_packet_decode,
+ [Data]}},
+ {options, [{active, true}, binary,
+ {packet, sunrm}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_packet_decode,
+ [Data]}},
+ {options, [{active, true}, {packet, sunrm},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+%%--------------------------------------------------------------------
+
+header_decode_one_byte(doc) ->
+ ["Test setting the packet option {header, 1}"];
+header_decode_one_byte(suite) ->
+ [];
+header_decode_one_byte(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = <<11:8, "Hello world">>,
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_header_decode,
+ [Data, [11 | <<"Hello world">>]]}},
+ {options, [{active, true}, binary,
+ {header,1}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_header_decode,
+ [Data, [11 | <<"Hello world">> ]]}},
+ {options, [{active, true}, {header, 1},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+
+header_decode_two_bytes(doc) ->
+ ["Test setting the packet option {header, 2}"];
+header_decode_two_bytes(suite) ->
+ [];
+header_decode_two_bytes(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = <<11:8, "Hello world">>,
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_header_decode,
+ [Data, [11, $H | <<"ello world">> ]]}},
+ {options, [{active, true}, binary,
+ {header,2}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_header_decode,
+ [Data, [11, $H | <<"ello world">> ]]}},
+ {options, [{active, true}, {header, 2},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
+%%--------------------------------------------------------------------
+
+header_decode_two_bytes_two_sent(doc) ->
+ ["Test setting the packet option {header, 2} and sending on byte"];
+header_decode_two_bytes_two_sent(suite) ->
+ [];
+header_decode_two_bytes_two_sent(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = <<"He">>,
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_header_decode,
+ [Data, [$H, $e | <<>> ]]}},
+ {options, [{active, true}, binary,
+ {header,2}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_header_decode,
+ [Data, [$H, $e | <<>> ]]}},
+ {options, [{active, true}, {header, 2},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
+%%--------------------------------------------------------------------
+
+header_decode_two_bytes_one_sent(doc) ->
+ ["Test setting the packet option {header, 2} and sending on byte"];
+header_decode_two_bytes_one_sent(suite) ->
+ [];
+header_decode_two_bytes_one_sent(Config) when is_list(Config) ->
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = <<"H">>,
+
+ Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, server_header_decode,
+ [Data, "H"]}},
+ {options, [{active, true}, binary,
+ {header,2}|ServerOpts]}]),
+
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ServerNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, client_header_decode,
+ [Data, "H"]}},
+ {options, [{active, true}, {header, 2},
+ binary | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
%%--------------------------------------------------------------------
%% Internal functions
@@ -1744,3 +2142,61 @@ active_packet(Socket, Data, N) ->
assert_packet_opt(Socket, Type) ->
{ok, [{packet, Type}]} = ssl:getopts(Socket, [packet]).
+
+server_packet_decode(Socket, Packet) ->
+ receive
+ {ssl, Socket, Packet} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ ok = ssl:send(Socket, Packet),
+ receive
+ {ssl, Socket, Packet} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ ok = ssl:send(Socket, Packet).
+
+client_packet_decode(Socket, Packet) ->
+ <<P1:10/binary, P2/binary>> = Packet,
+ ok = ssl:send(Socket, P1),
+ ok = ssl:send(Socket, P2),
+ receive
+ {ssl, Socket, Packet} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ ok = ssl:send(Socket, Packet),
+ receive
+ {ssl, Socket, Packet} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end.
+
+server_header_decode(Socket, Packet, Result) ->
+ receive
+ {ssl, Socket, Result} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ ok = ssl:send(Socket, Packet),
+ receive
+ {ssl, Socket, Result} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end,
+ ok = ssl:send(Socket, Packet).
+
+client_header_decode(Socket, Packet, Result) ->
+ ok = ssl:send(Socket, Packet),
+ receive
+ {ssl, Socket, Result} -> ok;
+ Other1 -> exit({?LINE, Other1})
+ end,
+ ok = ssl:send(Socket, Packet),
+ receive
+ {ssl, Socket, Result} -> ok;
+ Other2 -> exit({?LINE, Other2})
+ end.
+
+add_tpkt_header(Data) when is_binary(Data) ->
+ L = size(Data) + 4,
+ [3, 0, ((L) bsr 8) band 16#ff, (L) band 16#ff ,Data];
+add_tpkt_header(IOList) when is_list(IOList) ->
+ Binary = list_to_binary(IOList),
+ L = size(Binary) + 4,
+ [3, 0, ((L) bsr 8) band 16#ff, (L) band 16#ff , Binary].
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index a0aa92bdf2..d80df0bfbd 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%
@@ -38,6 +38,7 @@
%%--------------------------------------------------------------------
init_per_suite(Config) ->
crypto:start(),
+ application:start(public_key),
ssl:start(),
make_certs:all(?config(data_dir, Config), ?config(priv_dir, Config)),
ssl_test_lib:cert_options(Config).
diff --git a/lib/ssl/test/ssl_test_MACHINE.erl b/lib/ssl/test/ssl_test_MACHINE.erl
index e75f7079ed..e0ffa15d80 100644
--- a/lib/ssl/test/ssl_test_MACHINE.erl
+++ b/lib/ssl/test/ssl_test_MACHINE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2003-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2003-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%
%%
@@ -60,10 +60,12 @@ many_conns_1() ->
%%
mk_ssl_cert_opts(_Config) ->
Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]),
- COpts = [{cacertfile, filename:join([Dir, "client", "cacerts.pem"])},
+ COpts = [{ssl_imp, old},
+ {cacertfile, filename:join([Dir, "client", "cacerts.pem"])},
{certfile, filename:join([Dir, "client", "cert.pem"])},
{keyfile, filename:join([Dir, "client", "key.pem"])}],
- SOpts = [{cacertfile, filename:join([Dir, "server", "cacerts.pem"])},
+ SOpts = [{ssl_imp, old},
+ {cacertfile, filename:join([Dir, "server", "cacerts.pem"])},
{certfile, filename:join([Dir, "server", "cert.pem"])},
{keyfile, filename:join([Dir, "server", "key.pem"])}],
{ok, {COpts, SOpts}}.
@@ -225,11 +227,13 @@ start_ssl(Nodes, Config) ->
ok.
do_start(Env) ->
+ application:start(crypto),
+ application:start(public_key),
application:load(ssl),
lists:foreach(
fun({Par, Val}) -> application:set_env(ssl, Par, Val) end, Env),
- application:start(ssl),
- application:start(crypto).
+ application:start(ssl).
+
%%
%% start_node(Name) -> {ok, Node}
@@ -542,7 +546,7 @@ get_active(St) ->
listen(St, LPort) ->
case St#st.protomod of
ssl ->
- ssl:listen(LPort, St#st.sockopts ++ St#st.sslopts);
+ ssl:listen(LPort, [{ssl_imp, old} | St#st.sockopts ++ St#st.sslopts]);
gen_tcp ->
gen_tcp:listen(LPort, St#st.sockopts)
end.
@@ -584,7 +588,8 @@ connect(St, Host, Port) ->
case St#st.protomod of
ssl ->
- case ssl:connect(Host, Port, St#st.sockopts ++ St#st.sslopts,
+ case ssl:connect(Host, Port,
+ [{ssl_imp, old} | St#st.sockopts ++ St#st.sslopts],
St#st.timeout) of
{ok, Sock} ->
{ok, LPort} = ssl:sockname(Sock),
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 466e987e3e..40715dbf30 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -26,6 +26,7 @@
%% Note: This directive should only be used in test suites.
-compile(export_all).
+-record(sslsocket, { fd = nil, pid = nil}).
timetrap(Time) ->
Mul = try
@@ -66,13 +67,7 @@ run_server(Opts) ->
test_server:format("ssl:listen(~p, ~p)~n", [Port, Options]),
{ok, ListenSocket} = rpc:call(Node, ssl, listen, [Port, Options]),
Pid ! {listen, up},
- case Port of
- 0 ->
- {ok, {_, NewPort}} = ssl:sockname(ListenSocket),
- Pid ! {self(), {port, NewPort}};
- _ ->
- ok
- end,
+ send_selected_port(Pid, Port, ListenSocket),
run_server(ListenSocket, Opts).
run_server(ListenSocket, Opts) ->
@@ -129,7 +124,11 @@ remove_close_msg(ReconnectTimes) ->
start_client(Args) ->
- spawn_link(?MODULE, run_client, [Args]).
+ Result = spawn_link(?MODULE, run_client, [Args]),
+ receive
+ connected ->
+ Result
+ end.
run_client(Opts) ->
Node = proplists:get_value(node, Opts),
@@ -140,14 +139,11 @@ run_client(Opts) ->
test_server:format("ssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]),
case rpc:call(Node, ssl, connect, [Host, Port, Options]) of
{ok, Socket} ->
+ Pid ! connected,
test_server:format("Client: connected~n", []),
- case proplists:get_value(port, Options) of
- 0 ->
- {ok, {_, NewPort}} = ssl:sockname(Socket),
- Pid ! {self(), {port, NewPort}};
- _ ->
- ok
- end,
+ %% In specail cases we want to know the client port, it will
+ %% be indicated by sending {port, 0} in options list!
+ send_selected_port(Pid, proplists:get_value(port, Options), Socket),
{Module, Function, Args} = proplists:get_value(mfa, Opts),
test_server:format("Client: apply(~p,~p,~p)~n",
[Module, Function, [Socket | Args]]),
@@ -212,6 +208,26 @@ check_result(Pid, Msg) ->
test_server:fail(Reason)
end.
+check_result_ignore_renegotiation_reject(Pid, Msg) ->
+ receive
+ {Pid, fail_session_fatal_alert_during_renegotiation} ->
+ test_server:comment("Server rejected old renegotiation"),
+ ok;
+ {ssl_error, _, esslconnect} ->
+ test_server:comment("Server rejected old renegotiation"),
+ ok;
+ {Pid, Msg} ->
+ ok;
+ {Port, {data,Debug}} when is_port(Port) ->
+ io:format("openssl ~s~n",[Debug]),
+ check_result(Pid,Msg);
+ Unexpected ->
+ Reason = {{expected, {Pid, Msg}},
+ {got, Unexpected}},
+ test_server:fail(Reason)
+ end.
+
+
wait_for_result(Server, ServerMsg, Client, ClientMsg) ->
receive
{Server, ServerMsg} ->
@@ -271,7 +287,7 @@ cert_options(Config) ->
"badcert.pem"]),
BadKeyFile = filename:join([?config(priv_dir, Config),
"badkey.pem"]),
- [{client_opts, [{ssl_imp, new}]},
+ [{client_opts, [{ssl_imp, new},{reuseaddr, true}]},
{client_verification_opts, [{cacertfile, ClientCaCertFile},
{certfile, ClientCertFile},
{keyfile, ClientKeyFile},
@@ -302,6 +318,35 @@ cert_options(Config) ->
| Config].
+make_dsa_cert(Config) ->
+
+ {ServerCaCertFile, ServerCertFile, ServerKeyFile} = make_dsa_cert_files("server", Config),
+ {ClientCaCertFile, ClientCertFile, ClientKeyFile} = make_dsa_cert_files("client", Config),
+ [{server_dsa_opts, [{ssl_imp, new},{reuseaddr, true},
+ {cacertfile, ServerCaCertFile},
+ {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]},
+ {client_dsa_opts, [{ssl_imp, new},{reuseaddr, true},
+ {cacertfile, ClientCaCertFile},
+ {certfile, ClientCertFile}, {keyfile, ClientKeyFile}]}
+ | Config].
+
+
+
+make_dsa_cert_files(RoleStr, Config) ->
+ CaInfo = {CaCert, _} = erl_make_certs:make_cert([{key, dsa}]),
+ {Cert, CertKey} = erl_make_certs:make_cert([{key, dsa}, {issuer, CaInfo}]),
+ CaCertFile = filename:join([?config(priv_dir, Config),
+ RoleStr, "dsa_cacerts.pem"]),
+ CertFile = filename:join([?config(priv_dir, Config),
+ RoleStr, "dsa_cert.pem"]),
+ KeyFile = filename:join([?config(priv_dir, Config),
+ RoleStr, "dsa_key.pem"]),
+
+ public_key:der_to_pem(CaCertFile, [{cert, CaCert, not_encrypted}]),
+ public_key:der_to_pem(CertFile, [{cert, Cert, not_encrypted}]),
+ public_key:der_to_pem(KeyFile, [CertKey]),
+ {CaCertFile, CertFile, KeyFile}.
+
start_upgrade_server(Args) ->
Result = spawn_link(?MODULE, run_upgrade_server, [Args]),
receive
@@ -320,15 +365,7 @@ run_upgrade_server(Opts) ->
test_server:format("gen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
{ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]),
Pid ! {listen, up},
-
- case Port of
- 0 ->
- {ok, {_, NewPort}} = inet:sockname(ListenSocket),
- Pid ! {self(), {port, NewPort}};
- _ ->
- ok
- end,
-
+ send_selected_port(Pid, Port, ListenSocket),
test_server:format("gen_tcp:accept(~p)~n", [ListenSocket]),
{ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]),
@@ -370,14 +407,8 @@ run_upgrade_client(Opts) ->
test_server:format("gen_tcp:connect(~p, ~p, ~p)~n",
[Host, Port, TcpOptions]),
{ok, Socket} = rpc:call(Node, gen_tcp, connect, [Host, Port, TcpOptions]),
-
- case proplists:get_value(port, Opts) of
- 0 ->
- {ok, {_, NewPort}} = inet:sockname(Socket),
- Pid ! {self(), {port, NewPort}};
- _ ->
- ok
- end,
+
+ send_selected_port(Pid, Port, Socket),
test_server:format("ssl:connect(~p, ~p)~n", [Socket, SslOptions]),
{ok, SslSocket} = rpc:call(Node, ssl, connect, [Socket, SslOptions]),
@@ -392,6 +423,41 @@ run_upgrade_client(Opts) ->
ok = rpc:call(Node, ssl, close, [SslSocket])
end.
+start_upgrade_server_error(Args) ->
+ Result = spawn_link(?MODULE, run_upgrade_server_error, [Args]),
+ receive
+ {listen, up} ->
+ Result
+ end.
+
+run_upgrade_server_error(Opts) ->
+ Node = proplists:get_value(node, Opts),
+ Port = proplists:get_value(port, Opts),
+ TimeOut = proplists:get_value(timeout, Opts, infinity),
+ TcpOptions = proplists:get_value(tcp_options, Opts),
+ SslOptions = proplists:get_value(ssl_options, Opts),
+ Pid = proplists:get_value(from, Opts),
+
+ test_server:format("gen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]),
+ {ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]),
+ Pid ! {listen, up},
+ send_selected_port(Pid, Port, ListenSocket),
+ test_server:format("gen_tcp:accept(~p)~n", [ListenSocket]),
+ {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]),
+ Error = case TimeOut of
+ infinity ->
+ test_server:format("ssl:ssl_accept(~p, ~p)~n",
+ [AcceptSocket, SslOptions]),
+ rpc:call(Node, ssl, ssl_accept,
+ [AcceptSocket, SslOptions]);
+ _ ->
+ test_server:format("ssl:ssl_accept(~p, ~p, ~p)~n",
+ [AcceptSocket, SslOptions, TimeOut]),
+ rpc:call(Node, ssl, ssl_accept,
+ [AcceptSocket, SslOptions, TimeOut])
+ end,
+ Pid ! {self(), Error}.
+
start_server_error(Args) ->
Result = spawn_link(?MODULE, run_server_error, [Args]),
receive
@@ -410,6 +476,7 @@ run_server_error(Opts) ->
%% To make sure error_client will
%% get {error, closed} and not {error, connection_refused}
Pid ! {listen, up},
+ send_selected_port(Pid, Port, ListenSocket),
test_server:format("ssl:transport_accept(~p)~n", [ListenSocket]),
case rpc:call(Node, ssl, transport_accept, [ListenSocket]) of
{error, _} = Error ->
@@ -447,8 +514,8 @@ inet_port(Pid) when is_pid(Pid)->
inet_port(Node) ->
{Port, Socket} = do_inet_port(Node),
- rpc:call(Node, gen_tcp, close, [Socket]),
- Port.
+ rpc:call(Node, gen_tcp, close, [Socket]),
+ Port.
do_inet_port(Node) ->
{ok, Socket} = rpc:call(Node, gen_tcp, listen, [0, [{reuseaddr, true}]]),
@@ -466,9 +533,6 @@ trigger_renegotiate(Socket, _, 0, Id) ->
test_server:sleep(1000),
case ssl:session_info(Socket) of
[{session_id, Id} | _ ] ->
- %% If a warning alert is received
- %% from openssl this may not be
- %% an error!
fail_session_not_renegotiated;
%% Tests that uses this function will not reuse
%% sessions so if we get a new session id the
@@ -485,3 +549,51 @@ trigger_renegotiate(Socket, ErlData, N, Id) ->
ssl:send(Socket, ErlData),
trigger_renegotiate(Socket, ErlData, N-1, Id).
+
+send_selected_port(Pid, 0, #sslsocket{} = Socket) ->
+ {ok, {_, NewPort}} = ssl:sockname(Socket),
+ Pid ! {self(), {port, NewPort}};
+send_selected_port(Pid, 0, Socket) ->
+ {ok, {_, NewPort}} = inet:sockname(Socket),
+ Pid ! {self(), {port, NewPort}};
+send_selected_port(_,_,_) ->
+ ok.
+
+rsa_suites() ->
+ lists:filter(fun({dhe_dss, _, _}) ->
+ false;
+ (_) ->
+ true
+ end,
+ ssl:cipher_suites()).
+
+dsa_suites() ->
+ lists:filter(fun({dhe_dss, _, _}) ->
+ true;
+ (_) ->
+ false
+ end,
+ ssl:cipher_suites()).
+
+
+openssl_rsa_suites() ->
+ Ciphers = ssl:cipher_suites(openssl),
+ lists:filter(fun(Str) ->
+ case re:run(Str,"DSS",[]) of
+ nomatch ->
+ true;
+ _ ->
+ false
+ end
+ end, Ciphers).
+
+openssl_dsa_suites() ->
+ Ciphers = ssl:cipher_suites(openssl),
+ lists:filter(fun(Str) ->
+ case re:run(Str,"DSS",[]) of
+ nomatch ->
+ false;
+ _ ->
+ true
+ end
+ end, Ciphers).
diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl
index 624404b556..d2a4ca8db5 100644
--- a/lib/ssl/test/ssl_to_openssl_SUITE.erl
+++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl
@@ -25,13 +25,13 @@
-compile(export_all).
-include("test_server.hrl").
--include("test_server_line.hrl").
--include("ssl_pkix.hrl").
-define(TIMEOUT, 120000).
-define(SLEEP, 1000).
-define(OPENSSL_RENEGOTIATE, "r\n").
-define(OPENSSL_QUIT, "Q\n").
+-define(OPENSSL_GARBAGE, "P\n").
+-define(EXPIRE, 10).
%% Test server callback functions
%%--------------------------------------------------------------------
@@ -43,18 +43,22 @@
%% Note: This function is free to add any key/value pairs to the Config
%% variable, but should NOT alter/remove any existing entries.
%%--------------------------------------------------------------------
-init_per_suite(Config) ->
+init_per_suite(Config0) ->
+ Dog = ssl_test_lib:timetrap(?TIMEOUT *2),
case os:find_executable("openssl") of
false ->
{skip, "Openssl not found"};
_ ->
crypto:start(),
+ application:start(public_key),
ssl:start(),
Result =
- (catch make_certs:all(?config(data_dir, Config),
- ?config(priv_dir, Config))),
+ (catch make_certs:all(?config(data_dir, Config0),
+ ?config(priv_dir, Config0))),
test_server:format("Make certs ~p~n", [Result]),
- ssl_test_lib:cert_options(Config)
+ Config1 = ssl_test_lib:make_dsa_cert(Config0),
+ Config = ssl_test_lib:cert_options(Config1),
+ [{watchdog, Dog} | Config]
end.
%%--------------------------------------------------------------------
@@ -80,11 +84,29 @@ end_per_suite(_Config) ->
%% variable, but should NOT alter/remove any existing entries.
%% Description: Initialization before each test case
%%--------------------------------------------------------------------
-init_per_testcase(_TestCase, Config0) ->
+init_per_testcase(expired_session, Config0) ->
+ Config = lists:keydelete(watchdog, 1, Config0),
+ Dog = ssl_test_lib:timetrap(?EXPIRE * 1000 * 5),
+ ssl:stop(),
+ application:load(ssl),
+ application:set_env(ssl, session_lifetime, ?EXPIRE),
+ ssl:start(),
+ [{watchdog, Dog} | Config];
+
+init_per_testcase(TestCase, Config0) ->
Config = lists:keydelete(watchdog, 1, Config0),
Dog = ssl_test_lib:timetrap(?TIMEOUT),
- [{watchdog, Dog} | Config].
+ special_init(TestCase, [{watchdog, Dog} | Config]).
+special_init(TestCase, Config)
+ when TestCase == erlang_client_openssl_server_renegotiate;
+ TestCase == erlang_client_openssl_server_no_wrap_sequence_number;
+ TestCase == erlang_server_openssl_client_no_wrap_sequence_number ->
+ check_sane_openssl_renegotaite(Config);
+
+special_init(_, Config) ->
+ Config.
+
%%--------------------------------------------------------------------
%% Function: end_per_testcase(TestCase, Config) -> _
%% Case - atom()
@@ -93,14 +115,20 @@ init_per_testcase(_TestCase, Config0) ->
%% A list of key/value pairs, holding the test case configuration.
%% Description: Cleanup after each test case
%%--------------------------------------------------------------------
-end_per_testcase(_TestCase, Config) ->
+end_per_testcase(reuse_session_expired, Config) ->
+ application:unset_env(ssl, session_lifetime),
+ end_per_testcase(default_action, Config);
+
+end_per_testcase(default_action, Config) ->
Dog = ?config(watchdog, Config),
case Dog of
undefined ->
ok;
_ ->
test_server:timetrap_cancel(Dog)
- end.
+ end;
+end_per_testcase(_, Config) ->
+ end_per_testcase(default_action, Config).
%%--------------------------------------------------------------------
%% Function: all(Clause) -> TestCases
@@ -116,6 +144,10 @@ all(doc) ->
all(suite) ->
[erlang_client_openssl_server,
erlang_server_openssl_client,
+ tls1_erlang_client_openssl_server_dsa_cert,
+ tls1_erlang_server_openssl_client_dsa_cert,
+ ssl3_erlang_client_openssl_server_dsa_cert,
+ ssl3_erlang_server_openssl_client_dsa_cert,
erlang_server_openssl_client_reuse_session,
erlang_client_openssl_server_renegotiate,
erlang_client_openssl_server_no_wrap_sequence_number,
@@ -131,7 +163,11 @@ all(suite) ->
tls1_erlang_client_openssl_server_client_cert,
tls1_erlang_server_openssl_client_client_cert,
tls1_erlang_server_erlang_client_client_cert,
- ciphers
+ ciphers_rsa_signed_certs,
+ ciphers_dsa_signed_certs,
+ erlang_client_bad_openssl_server,
+ expired_session,
+ ssl2_erlang_server_openssl_client
].
%% Test cases starts here.
@@ -161,7 +197,7 @@ erlang_client_openssl_server(Config) when is_list(Config) ->
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -200,8 +236,6 @@ erlang_server_openssl_client(Config) when is_list(Config) ->
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
" -host localhost",
@@ -220,6 +254,185 @@ erlang_server_openssl_client(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
+tls1_erlang_client_openssl_server_dsa_cert(doc) ->
+ ["Test erlang server with openssl client"];
+tls1_erlang_client_openssl_server_dsa_cert(suite) ->
+ [];
+tls1_erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = ?config(client_dsa_opts, Config),
+ ServerOpts = ?config(server_dsa_opts, Config),
+
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = "From openssl to erlang",
+
+ Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++
+ " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile
+ ++ " -key " ++ KeyFile ++ " -Verify 2 -tls1 -msg",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ wait_for_openssl_server(),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ erlang_ssl_receive, [Data]}},
+ {options, ClientOpts}]),
+
+ port_command(OpensslPort, Data),
+
+ ssl_test_lib:check_result(Client, ok),
+
+ %% Clean close down! Server needs to be closed first !!
+ close_port(OpensslPort),
+
+ ssl_test_lib:close(Client),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+
+tls1_erlang_server_openssl_client_dsa_cert(doc) ->
+ ["Test erlang server with openssl client"];
+tls1_erlang_server_openssl_client_dsa_cert(suite) ->
+ [];
+tls1_erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = ?config(client_dsa_opts, Config),
+ ServerOpts = ?config(server_dsa_opts, Config),
+
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+
+ Data = "From openssl to erlang",
+ CaCertFile = proplists:get_value(cacertfile, ClientOpts),
+ CertFile = proplists:get_value(certfile, ClientOpts),
+ KeyFile = proplists:get_value(keyfile, ClientOpts),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, erlang_ssl_receive, [Data]}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
+ " -host localhost " ++ " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile
+ ++ " -key " ++ KeyFile ++ " -tls1 -msg",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+ port_command(OpenSslPort, Data),
+
+ ssl_test_lib:check_result(Server, ok),
+
+ ssl_test_lib:close(Server),
+
+ close_port(OpenSslPort),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+
+ssl3_erlang_client_openssl_server_dsa_cert(doc) ->
+ ["Test erlang server with openssl client"];
+ssl3_erlang_client_openssl_server_dsa_cert(suite) ->
+ [];
+ssl3_erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = ?config(client_dsa_opts, Config),
+ ServerOpts = ?config(server_dsa_opts, Config),
+
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Data = "From openssl to erlang",
+
+ Port = ssl_test_lib:inet_port(node()),
+ CaCertFile = proplists:get_value(cacertfile, ServerOpts),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++
+ " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile
+ ++ " -key " ++ KeyFile ++ " -Verify 2 -ssl3 -msg",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ wait_for_openssl_server(),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE,
+ erlang_ssl_receive, [Data]}},
+ {options, ClientOpts}]),
+
+ port_command(OpensslPort, Data),
+
+ ssl_test_lib:check_result(Client, ok),
+
+ %% Clean close down! Server needs to be closed first !!
+ close_port(OpensslPort),
+
+ ssl_test_lib:close(Client),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+
+ssl3_erlang_server_openssl_client_dsa_cert(doc) ->
+ ["Test erlang server with openssl client"];
+ssl3_erlang_server_openssl_client_dsa_cert(suite) ->
+ [];
+ssl3_erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = ?config(client_dsa_opts, Config),
+ ServerOpts = ?config(server_dsa_opts, Config),
+
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+
+ Data = "From openssl to erlang",
+ CaCertFile = proplists:get_value(cacertfile, ClientOpts),
+ CertFile = proplists:get_value(certfile, ClientOpts),
+ KeyFile = proplists:get_value(keyfile, ClientOpts),
+
+ Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, erlang_ssl_receive, [Data]}},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
+ " -host localhost " ++ " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile
+ ++ " -key " ++ KeyFile ++ " -ssl3 -msg",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+ port_command(OpenSslPort, Data),
+
+ ssl_test_lib:check_result(Server, ok),
+
+ ssl_test_lib:close(Server),
+
+ close_port(OpenSslPort),
+ process_flag(trap_exit, false),
+ ok.
+
+
+%%--------------------------------------------------------------------
+
erlang_server_openssl_client_reuse_session(doc) ->
["Test erlang server with openssl client that reconnects with the"
"same session id, to test reusing of sessions."];
@@ -240,8 +453,6 @@ erlang_server_openssl_client_reuse_session(Config) when is_list(Config) ->
{options, ServerOpts}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
" -host localhost -reconnect",
@@ -286,7 +497,7 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -294,16 +505,13 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) ->
{mfa, {?MODULE,
delayed_send, [[ErlData, OpenSslData]]}},
{options, ClientOpts}]),
- test_server:sleep(?SLEEP),
port_command(OpensslPort, ?OPENSSL_RENEGOTIATE),
-
test_server:sleep(?SLEEP),
-
port_command(OpensslPort, OpenSslData),
ssl_test_lib:check_result(Client, ok),
-
+
%% Clean close down! Server needs to be closed first !!
close_port(OpensslPort),
@@ -341,7 +549,7 @@ erlang_client_openssl_server_no_wrap_sequence_number(Config) when is_list(Config
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -385,8 +593,6 @@ erlang_server_openssl_client_no_wrap_sequence_number(Config) when is_list(Config
{options, [{renegotiate_at, N} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
" -host localhost -msg",
@@ -431,7 +637,7 @@ erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) ->
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -473,7 +679,7 @@ ssl3_erlang_client_openssl_server(Config) when is_list(Config) ->
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -508,8 +714,6 @@ ssl3_erlang_server_openssl_client(Config) when is_list(Config) ->
{options,
[{versions, [sslv3]} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
-
- test_server:sleep(?SLEEP),
Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
" -host localhost -ssl3",
@@ -552,7 +756,7 @@ ssl3_erlang_client_openssl_server_client_cert(Config) when is_list(Config) ->
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -594,8 +798,6 @@ ssl3_erlang_server_openssl_client_client_cert(Config) when is_list(Config) ->
| ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
CaCertFile = proplists:get_value(cacertfile, ClientOpts),
CertFile = proplists:get_value(certfile, ClientOpts),
KeyFile = proplists:get_value(keyfile, ClientOpts),
@@ -642,8 +844,6 @@ ssl3_erlang_server_erlang_client_client_cert(Config) when is_list(Config) ->
| ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -685,7 +885,7 @@ tls1_erlang_client_openssl_server(Config) when is_list(Config) ->
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -723,8 +923,6 @@ tls1_erlang_server_openssl_client(Config) when is_list(Config) ->
[{versions, [tlsv1]} | ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
" -host localhost -tls1",
@@ -768,7 +966,7 @@ tls1_erlang_client_openssl_server_client_cert(Config) when is_list(Config) ->
OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -810,8 +1008,6 @@ tls1_erlang_server_openssl_client_client_cert(Config) when is_list(Config) ->
| ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
CaCertFile = proplists:get_value(cacertfile, ClientOpts),
CertFile = proplists:get_value(certfile, ClientOpts),
KeyFile = proplists:get_value(keyfile, ClientOpts),
@@ -856,8 +1052,6 @@ tls1_erlang_server_erlang_client_client_cert(Config) when is_list(Config) ->
| ServerOpts]}]),
Port = ssl_test_lib:inet_port(Server),
- test_server:sleep(?SLEEP),
-
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
{from, self()},
@@ -873,19 +1067,46 @@ tls1_erlang_server_erlang_client_client_cert(Config) when is_list(Config) ->
ok.
%%--------------------------------------------------------------------
-ciphers(doc) ->
- [""];
+ciphers_rsa_signed_certs(doc) ->
+ ["Test cipher suites that uses rsa certs"];
+
+ciphers_rsa_signed_certs(suite) ->
+ [];
+
+ciphers_rsa_signed_certs(Config) when is_list(Config) ->
+ Version =
+ ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
+
+ Ciphers = ssl_test_lib:rsa_suites(),
+ run_suites(Ciphers, Version, Config, rsa).
+
+
+ciphers_dsa_signed_certs(doc) ->
+ ["Test cipher suites that uses dsa certs"];
-ciphers(suite) ->
+ciphers_dsa_signed_certs(suite) ->
[];
-ciphers(Config) when is_list(Config) ->
+ciphers_dsa_signed_certs(Config) when is_list(Config) ->
Version =
ssl_record:protocol_version(ssl_record:highest_protocol_version([])),
- Ciphers = ssl:cipher_suites(),
+ Ciphers = ssl_test_lib:dsa_suites(),
+ run_suites(Ciphers, Version, Config, dsa).
+
+run_suites(Ciphers, Version, Config, Type) ->
+ {ClientOpts, ServerOpts} =
+ case Type of
+ rsa ->
+ {?config(client_opts, Config),
+ ?config(server_opts, Config)};
+ dsa ->
+ {?config(client_opts, Config),
+ ?config(server_dsa_opts, Config)}
+ end,
+
Result = lists:map(fun(Cipher) ->
- cipher(Cipher, Version, Config) end,
+ cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end,
Ciphers),
case lists:flatten(Result) of
[] ->
@@ -894,12 +1115,12 @@ ciphers(Config) when is_list(Config) ->
test_server:format("Cipher suite errors: ~p~n", [Error]),
test_server:fail(cipher_suite_failed_see_test_case_log)
end.
-
-cipher(CipherSuite, Version, Config) ->
+
+
+
+cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) ->
process_flag(trap_exit, true),
test_server:format("Testing CipherSuite ~p~n", [CipherSuite]),
- ClientOpts = ?config(client_opts, Config),
- ServerOpts = ?config(server_opts, Config),
{ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config),
Port = ssl_test_lib:inet_port(node()),
@@ -913,7 +1134,7 @@ cipher(CipherSuite, Version, Config) ->
OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
- test_server:sleep(?SLEEP),
+ wait_for_openssl_server(),
Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
{host, Hostname},
@@ -945,6 +1166,157 @@ cipher(CipherSuite, Version, Config) ->
Return.
%%--------------------------------------------------------------------
+erlang_client_bad_openssl_server(doc) ->
+ [""];
+erlang_client_bad_openssl_server(suite) ->
+ [];
+erlang_client_bad_openssl_server(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ?config(server_verification_opts, Config),
+ ClientOpts = ?config(client_verification_opts, Config),
+
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++
+ " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ wait_for_openssl_server(),
+
+ Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, server_sent_garbage, []}},
+ {options,
+ [{versions, [tlsv1]} | ClientOpts]}]),
+
+ %% Send garbage
+ port_command(OpensslPort, ?OPENSSL_GARBAGE),
+
+ test_server:sleep(?SLEEP),
+
+ Client0 ! server_sent_garbage,
+
+ ssl_test_lib:check_result(Client0, true),
+
+ ssl_test_lib:close(Client0),
+
+ %% Make sure openssl does not hang and leave zombie process
+ Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, no_result_msg, []}},
+ {options,
+ [{versions, [tlsv1]} | ClientOpts]}]),
+
+ ssl_test_lib:close(Client1),
+
+ %% Clean close down!
+ close_port(OpensslPort),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
+
+expired_session(doc) ->
+ ["Test our ssl client handling of expired sessions. Will make"
+ "better code coverage of the ssl_manager module"];
+
+expired_session(suite) ->
+ [];
+
+expired_session(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ClientOpts = ?config(client_opts, Config),
+ ServerOpts = ?config(server_opts, Config),
+ {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config),
+
+ Port = ssl_test_lib:inet_port(node()),
+ CertFile = proplists:get_value(certfile, ServerOpts),
+ KeyFile = proplists:get_value(keyfile, ServerOpts),
+
+ Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++
+ " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+
+ wait_for_openssl_server(),
+
+ Client0 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ ssl_test_lib:close(Client0),
+
+ %% Make sure session is registered
+ test_server:sleep(?SLEEP),
+
+ Client1 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ ssl_test_lib:close(Client1),
+ %% Make sure session is unregistered due to expiration
+ test_server:sleep((?EXPIRE+1) * 1000),
+
+ Client2 =
+ ssl_test_lib:start_client([{node, ClientNode},
+ {port, Port}, {host, Hostname},
+ {mfa, {ssl_test_lib, no_result, []}},
+ {from, self()}, {options, ClientOpts}]),
+
+ close_port(OpensslPort),
+ ssl_test_lib:close(Client2),
+ process_flag(trap_exit, false).
+
+%%--------------------------------------------------------------------
+ssl2_erlang_server_openssl_client(doc) ->
+ ["Test that ssl v2 clients are rejected"];
+ssl2_erlang_server_openssl_client(suite) ->
+ [];
+ssl2_erlang_server_openssl_client(Config) when is_list(Config) ->
+ process_flag(trap_exit, true),
+ ServerOpts = ?config(server_opts, Config),
+
+ {_, ServerNode, _} = ssl_test_lib:run_where(Config),
+
+ Data = "From openssl to erlang",
+
+ Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0},
+ {from, self()},
+ {options, ServerOpts}]),
+ Port = ssl_test_lib:inet_port(Server),
+
+ Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++
+ " -host localhost -ssl2 -msg",
+
+ test_server:format("openssl cmd: ~p~n", [Cmd]),
+
+ OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]),
+ port_command(OpenSslPort, Data),
+
+ ssl_test_lib:check_result(Server, {error,"protocol version"}),
+
+ ssl_test_lib:close(Server),
+
+ close_port(OpenSslPort),
+ process_flag(trap_exit, false),
+ ok.
+
+%%--------------------------------------------------------------------
erlang_ssl_receive(Socket, Data) ->
test_server:format("Connection info: ~p~n",
@@ -983,8 +1355,7 @@ delayed_send(Socket, [ErlData, OpenSslData]) ->
erlang_ssl_receive(Socket, OpenSslData).
close_port(Port) ->
- port_command(Port, ?OPENSSL_QUIT),
- %%catch port_command(Port, "quit\n"),
+ catch port_command(Port, ?OPENSSL_QUIT),
close_loop(Port, 500, false).
close_loop(Port, Time, SentClose) ->
@@ -1014,3 +1385,32 @@ close_loop(Port, Time, SentClose) ->
io:format("Timeout~n",[])
end
end.
+
+
+server_sent_garbage(Socket) ->
+ receive
+ server_sent_garbage ->
+ {error, closed} == ssl:send(Socket, "data")
+
+ end.
+
+wait_for_openssl_server() ->
+ receive
+ {Port, {data, Debug}} when is_port(Port) ->
+ io:format("openssl ~s~n",[Debug]),
+ %% openssl has started make sure
+ %% it will be in accept. Parsing
+ %% output is too error prone. (Even
+ %% more so than sleep!)
+ test_server:sleep(?SLEEP)
+ end.
+
+check_sane_openssl_renegotaite(Config) ->
+ case os:cmd("openssl version") of
+ "OpenSSL 0.9.8" ++ _ ->
+ {skip, "Known renegotiation bug in OppenSSL"};
+ "OpenSSL 0.9.7" ++ _ ->
+ {skip, "Known renegotiation bug in OppenSSL"};
+ _ ->
+ Config
+ end.
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index f259e15d68..74b1cf4c78 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -17,12 +17,23 @@
# %CopyrightEnd%
#
-SSL_VSN = 3.11
-
-TICKETS = OTP-8517 \
- OTP-7046 \
- OTP-8557 \
- OTP-8560
+SSL_VSN = 4.0
+
+TICKETS = OTP-8587\
+ OTP-8695
+
+#TICKETS = OTP-8679 \
+# OTP-7047 \
+# OTP-7049 \
+# OTP-8568 \
+# OTP-8588
+
+#TICKETS_3.11 = OTP-8517 \
+# OTP-7046 \
+# OTP-8557 \
+# OTP-8560 \
+# OTP-8545 \
+# OTP-8554
#TICKETS_3.10.9 = OTP-8510
diff --git a/lib/stdlib/doc/src/Makefile b/lib/stdlib/doc/src/Makefile
index 13b9b2ff18..b558697d63 100644
--- a/lib/stdlib/doc/src/Makefile
+++ b/lib/stdlib/doc/src/Makefile
@@ -1,19 +1,19 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1997-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
@@ -40,6 +40,7 @@ XML_REF3_FILES = \
array.xml \
base64.xml \
beam_lib.xml \
+ binary.xml \
c.xml \
calendar.xml \
dets.xml \
diff --git a/lib/stdlib/doc/src/beam_lib.xml b/lib/stdlib/doc/src/beam_lib.xml
index b9286f1402..27308e02f3 100644
--- a/lib/stdlib/doc/src/beam_lib.xml
+++ b/lib/stdlib/doc/src/beam_lib.xml
@@ -347,9 +347,10 @@ chunkref() = chunkname() | chunkid()</code>
</type>
<desc>
<p>Compares the contents of two BEAM files. If the module names
- are the same, and the chunks with the identifiers
- <c>"Code"</c>, <c>"ExpT"</c>, <c>"ImpT"</c>, <c>"StrT"</c>,
- and <c>"Atom"</c> have the same contents in both files,
+ are the same, and all chunks except for the <c>"CInf"</c> chunk
+ (the chunk containing the compilation information which is
+ returned by <c>Module:module_info(compile)</c>)
+ have the same contents in both files,
<c>ok</c> is returned. Otherwise an error message is returned.</p>
</desc>
</func>
diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml
new file mode 100644
index 0000000000..c5eb81a86a
--- /dev/null
+++ b/lib/stdlib/doc/src/binary.xml
@@ -0,0 +1,729 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2010</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 on line 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 AB.
+ </legalnotice>
+
+ <title>binary</title>
+ <prepared>Patrik Nyblom</prepared>
+ <responsible>Kenneth Lundin</responsible>
+ <docno>1</docno>
+ <approved></approved>
+ <checked></checked>
+ <date>2010-05-05</date>
+ <rev>A</rev>
+ <file>binary.xml</file>
+ </header>
+ <module>binary</module>
+ <modulesummary>Library for handling binary data</modulesummary>
+ <description>
+
+ <p>This module contains functions for manipulating byte-oriented
+ binaries. Although the majority of functions could be implemented
+ using bit-syntax, the functions in this library are highly
+ optimized and are expected to either execute faster or consume
+ less memory (or both) than a counterpart written in pure Erlang.</p>
+
+ <p>The module is implemented according to the EEP (Erlang Enhancement Proposal) 31.</p>
+
+ <note>
+ <p>
+ The library handles byte-oriented data. Bitstrings that are not
+ binaries (does not contain whole octets of bits) will result in a <c>badarg</c>
+ exception being thrown from any of the functions in this
+ module.
+ </p>
+ </note>
+
+
+ </description>
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none">
+ cp()
+ - Opaque data-type representing a compiled search-pattern. Guaranteed to be a tuple()
+ to allow programs to distinguish it from non precompiled search patterns.
+ </code>
+ <code type="none">
+ part() = {Start,Length}
+ Start = int()
+ Length = int()
+ - A representaion of a part (or range) in a binary. Start is a
+ zero-based offset into a binary() and Length is the length of
+ that part. As input to functions in this module, a reverse
+ part specification is allowed, constructed with a negative
+ Length, so that the part of the binary begins at Start +
+ Length and is -Length long. This is useful for referencing the
+ last N bytes of a binary as {size(Binary), -N}. The functions
+ in this module always return part()'s with positive Length.
+ </code>
+ </section>
+ <funcs>
+ <func>
+ <name>at(Subject, Pos) -> int()</name>
+ <fsummary>Returns the byte at a specific position in a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pos = int() >= 0</v>
+ </type>
+ <desc>
+
+ <p>Returns the byte at position <c>Pos</c> (zero-based) in the binary
+ <c>Subject</c> as an integer. If <c>Pos</c> &gt;= <c>byte_size(Subject)</c>,
+ a <c>badarg</c>
+ exception is raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>bin_to_list(Subject) -> list()</name>
+ <fsummary>Convert a binary to a list of integers</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ </type>
+ <desc>
+ <p>The same as <c>bin_to_list(Subject,{0,byte_size(Subject)})</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>bin_to_list(Subject, PosLen) -> list()</name>
+ <fsummary>Convert a binary to a list of integers</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>PosLen = part()</v>
+ </type>
+ <desc>
+
+ <p>Converts <c>Subject</c> to a list of <c>int()</c>s, each representing
+ the value of one byte. The <c>part()</c> denotes which part of the
+ <c>binary()</c> to convert. Example:</p>
+
+<code>
+1> binary:bin_to_list(&lt;&lt;"erlang"&gt;&gt;,{1,3}).
+"rla"
+%% or [114,108,97] in list notation.
+</code>
+ <p>If <c>PosLen</c> in any way references outside the binary, a <c>badarg</c> exception is raised.</p>
+ </desc>
+ </func>
+ <func>
+ <name>bin_to_list(Subject, Pos, Len) -> list()</name>
+ <fsummary>Convert a binary to a list of integers</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pos = int()</v>
+ <v>Len = int()</v>
+ </type>
+ <desc>
+ <p>The same as<c> bin_to_list(Subject,{Pos,Len})</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>compile_pattern(Pattern) -> cp()</name>
+ <fsummary>Pre-compiles a binary search pattern</fsummary>
+ <type>
+ <v>Pattern = binary() | [ binary() ]</v>
+ </type>
+ <desc>
+
+ <p>Builds an internal structure representing a compilation of a
+ search-pattern, later to be used in the <seealso marker="#match-3">match/3</seealso>,
+ <seealso marker="#matches-3">matches/3</seealso>,
+ <seealso marker="#split-3">split/3</seealso> or
+ <seealso marker="#replace-4">replace/4</seealso>
+ functions. The <c>cp()</c> returned is guaranteed to be a
+ <c>tuple()</c> to allow programs to distinguish it from non
+ pre-compiled search patterns</p>
+
+ <p>When a list of binaries is given, it denotes a set of
+ alternative binaries to search for. I.e if
+ <c>[&lt;&lt;"functional"&gt;&gt;,&lt;&lt;"programming"&gt;&gt;]</c>
+ is given as <c>Pattern</c>, this
+ means "either <c>&lt;&lt;"functional"&gt;&gt;</c> or
+ <c>&lt;&lt;"programming"&gt;&gt;</c>". The pattern is a set of
+ alternatives; when only a single binary is given, the set has
+ only one element. The order of alternatives in a pattern is not significant.</p>
+
+ <p>The list of binaries used for search alternatives shall be flat and proper.</p>
+
+ <p>If <c>Pattern</c> is not a binary or a flat proper list of binaries with length &gt; 0,
+ a <c>badarg</c> exception will be raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>copy(Subject) -> binary()</name>
+ <fsummary>Creates a duplicate of a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ </type>
+ <desc>
+ <p>The same as <c>copy(Subject, 1)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>copy(Subject,N) -> binary()</name>
+ <fsummary>Duplicates a binary N times and creates a new</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>N = int() >= 0</v>
+ </type>
+ <desc>
+ <p>Creates a binary with the content of <c>Subject</c> duplicated <c>N</c> times.</p>
+
+ <p>This function will always create a new binary, even if <c>N =
+ 1</c>. By using <c>copy/1</c> on a binary referencing a larger binary, one
+ might free up the larger binary for garbage collection.</p>
+
+ <note>
+ <p>By deliberately copying a single binary to avoid referencing
+ a larger binary, one might, instead of freeing up the larger
+ binary for later garbage collection, create much more binary
+ data than needed. Sharing binary data is usually good. Only in
+ special cases, when small parts reference large binaries and the
+ large binaries are no longer used in any process, deliberate
+ copying might be a good idea.</p> </note>
+
+ <p>If <c>N</c> &lt; <c>0</c>, a <c>badarg</c> exception is raised.</p>
+ </desc>
+ </func>
+ <func>
+ <name>decode_unsigned(Subject) -> Unsigned</name>
+ <fsummary>Decode a whole binary into an integer of arbitrary size</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Unsigned = int() >= 0</v>
+ </type>
+ <desc>
+ <p>The same as <c>decode_unsigned(Subject,big)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>decode_unsigned(Subject, Endianess) -> Unsigned</name>
+ <fsummary>Decode a whole binary into an integer of arbitrary size</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Endianess = big | little</v>
+ <v>Unsigned = int() >= 0</v>
+ </type>
+ <desc>
+
+ <p>Converts the binary digit representation, in big or little
+ endian, of a positive integer in <c>Subject</c> to an Erlang <c>int()</c>.</p>
+
+ <p>Example:</p>
+
+ <code>
+1> binary:decode_unsigned(&lt;&lt;169,138,199&gt;&gt;,big).
+11111111
+ </code>
+ </desc>
+ </func>
+ <func>
+ <name>encode_unsigned(Unsigned) -> binary()</name>
+ <fsummary>Encodes an unsigned integer into the minimal binary</fsummary>
+ <type>
+ <v>Unsigned = int() >= 0</v>
+ </type>
+ <desc>
+ <p>The same as <c>encode_unsigned(Unsigned,big)</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>encode_unsigned(Unsigned,Endianess) -> binary()</name>
+ <fsummary>Encodes an unsigned integer into the minimal binary</fsummary>
+ <type>
+ <v>Unsigned = int() >= 0</v>
+ <v>Endianess = big | little</v>
+ </type>
+ <desc>
+
+ <p>Converts a positive integer to the smallest possible
+ representation in a binary digit representation, either big
+ or little endian.</p>
+
+ <p>Example:</p>
+
+ <code>
+1> binary:encode_unsigned(11111111,big).
+&lt;&lt;169,138,199&gt;&gt;
+ </code>
+ </desc>
+ </func>
+ <func>
+ <name>first(Subject) -> int()</name>
+ <fsummary>Returns the first byte of a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ </type>
+ <desc>
+
+ <p>Returns the first byte of the binary <c>Subject</c> as an integer. If the
+ size of <c>Subject</c> is zero, a <c>badarg</c> exception is raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>last(Subject) -> int()</name>
+ <fsummary>Returns the last byte of a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ </type>
+ <desc>
+
+ <p>Returns the last byte of the binary <c>Subject</c> as an integer. If the
+ size of <c>Subject</c> is zero, a <c>badarg</c> exception is raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>list_to_bin(ByteList) -> binary()</name>
+ <fsummary>Convert a list of integers and binaries to a binary</fsummary>
+ <type>
+ <v>ByteList = iodata() (see module erlang)</v>
+ </type>
+ <desc>
+ <p>Works exactly as <c>erlang:list_to_binary/1</c>, added for completeness.</p>
+ </desc>
+ </func>
+ <func>
+ <name>longest_common_prefix(Binaries) -> int()</name>
+ <fsummary>Returns length of longest common prefix for a set of binaries</fsummary>
+ <type>
+ <v>Binaries = [ binary() ]</v>
+ </type>
+ <desc>
+
+ <p>Returns the length of the longest common prefix of the
+ binaries in the list <c>Binaries</c>. Example:</p>
+
+<code>
+1> binary:longest_common_prefix([&lt;&lt;"erlang"&gt;&gt;,&lt;&lt;"ergonomy"&gt;&gt;]).
+2
+2> binary:longest_common_prefix([&lt;&lt;"erlang"&gt;&gt;,&lt;&lt;"perl"&gt;&gt;]).
+0
+</code>
+
+ <p>If <c>Binaries</c> is not a flat list of binaries, a <c>badarg</c> exception is raised.</p>
+ </desc>
+ </func>
+ <func>
+ <name>longest_common_suffix(Binaries) -> int()</name>
+ <fsummary>Returns length of longest common suffix for a set of binaries</fsummary>
+ <type>
+ <v>Binaries = [ binary() ]</v>
+ </type>
+ <desc>
+
+ <p>Returns the length of the longest common suffix of the
+ binaries in the list <c>Binaries</c>. Example:</p>
+
+<code>
+1> binary:longest_common_suffix([&lt;&lt;"erlang"&gt;&gt;,&lt;&lt;"fang"&gt;&gt;]).
+3
+2> binary:longest_common_suffix([&lt;&lt;"erlang"&gt;&gt;,&lt;&lt;"perl"&gt;&gt;]).
+0
+</code>
+
+ <p>If <c>Binaries</c> is not a flat list of binaries, a <c>badarg</c> exception is raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>match(Subject, Pattern) -> Found | <c>nomatch</c></name>
+ <fsummary>Searches for the first match of a pattern in a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Found = part()</v>
+ </type>
+ <desc>
+ <p>The same as <c>match(Subject, Pattern, [])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>match(Subject,Pattern,Options) -> Found | <c>nomatch</c></name>
+ <fsummary>Searches for the first match of a pattern in a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Found = part()</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = {scope, part()}</v>
+ </type>
+ <desc>
+
+ <p>Searches for the first occurrence of <c>Pattern</c> in <c>Subject</c> and
+ returns the position and length.</p>
+
+ <p>The function will return <c>{Pos,Length}</c> for the binary
+ in <c>Pattern</c> starting at the lowest position in
+ <c>Subject</c>, Example:</p>
+
+<code>
+1> binary:match(&lt;&lt;"abcde"&gt;&gt;, [&lt;&lt;"bcde"&gt;&gt;,&lt;&lt;"cd"&gt;&gt;],[]).
+{1,4}
+</code>
+
+ <p>Even though <c>&lt;&lt;"cd"&gt;&gt;</c> ends before
+ <c>&lt;&lt;"bcde"&gt;&gt;</c>, <c>&lt;&lt;"bcde"&gt;&gt;</c>
+ begins first and is therefore the first match. If two
+ overlapping matches begin at the same position, the longest is
+ returned.</p>
+
+ <p>Summary of the options:</p>
+
+ <taglist>
+ <tag>{scope, {Start, Length}}</tag>
+ <item><p>Only the given part is searched. Return values still have
+ offsets from the beginning of <c>Subject</c>. A negative <c>Length</c> is
+ allowed as described in the <c>TYPES</c> section of this manual.</p></item>
+ </taglist>
+
+ <p>If none of the strings in
+ <c>Pattern</c> is found, the atom <c>nomatch</c> is returned.</p>
+
+ <p>For a description of <c>Pattern</c>, see
+ <seealso marker="#compile_pattern-1">compile_pattern/1</seealso>.</p>
+
+ <p>If <c>{scope, {Start,Length}}</c> is given in the options
+ such that <c>Start</c> is larger than the size of
+ <c>Subject</c>, <c>Start + Length</c> is less than zero or
+ <c>Start + Length</c> is larger than the size of
+ <c>Subject</c>, a <c>badarg</c> exception is raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>matches(Subject, Pattern) -> Found</name>
+ <fsummary>Searches for all matches of a pattern in a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Found = [ part() ] | []</v>
+ </type>
+ <desc>
+ <p>The same as <c>matches(Subject, Pattern, [])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>matches(Subject,Pattern,Options) -> Found</name>
+ <fsummary>Searches for all matches of a pattern in a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Found = [ part() ] | []</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = {scope, part()}</v>
+ </type>
+ <desc>
+
+ <p>Works like match, but the <c>Subject</c> is searched until
+ exhausted and a list of all non-overlapping parts matching
+ <c>Pattern</c> is returned (in order). </p>
+
+ <p>The first and longest match is preferred to a shorter,
+ which is illustrated by the following example:</p>
+
+<code>
+1> binary:matches(&lt;&lt;"abcde"&gt;&gt;,
+ [&lt;&lt;"bcde"&gt;&gt;,&lt;&lt;"bc"&gt;&gt;>,&lt;&lt;"de"&gt;&gt;],[]).
+[{1,4}]
+</code>
+
+ <p>The result shows that &lt;&lt;bcde"&gt;&gt; is selected instead of the
+ shorter match &lt;&lt;"bc"&gt;&gt; (which would have given raise to one
+ more match,&lt;&lt;"de"&gt;&gt;). This corresponds to the behavior of posix
+ regular expressions (and programs like awk), but is not
+ consistent with alternative matches in re (and Perl), where
+ instead lexical ordering in the search pattern selects which
+ string matches.</p>
+
+ <p>If none of the strings in pattern is found, an empty list is returned.</p>
+
+ <p>For a description of <c>Pattern</c>, see <seealso marker="#compile_pattern-1">compile_pattern/1</seealso> and for a
+ description of available options, see <seealso marker="#match-3">match/3</seealso>.</p>
+
+ <p>If <c>{scope, {Start,Length}}</c> is given in the options such that
+ <c>Start</c> is larger than the size of <c>Subject</c>, <c>Start + Length</c> is
+ less than zero or <c>Start + Length</c> is larger than the size of
+ <c>Subject</c>, a <c>badarg</c> exception is raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>part(Subject, PosLen) -> binary()</name>
+ <fsummary>Extracts a part of a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>PosLen = part()</v>
+ </type>
+ <desc>
+
+ <p>Extracts the part of the binary <c>Subject</c> described by <c>PosLen</c>.</p>
+
+ <p>Negative length can be used to extract bytes at the end of a binary:</p>
+
+<code>
+1> Bin = &lt;&lt;1,2,3,4,5,6,7,8,9,10&gt;&gt;.
+2> binary:part(Bin,{byte_size(Bin), -5)).
+&lt;&lt;6,7,8,9,10&gt;&gt;
+</code>
+
+ <note>
+ <p><seealso marker="#part-2">part/2</seealso>and <seealso
+ marker="#part-3">part/3</seealso> are also available in the
+ <c>erlang</c> module under the names <c>binary_part/2</c> and
+ <c>binary_part/3</c>. Those BIFs are allowed in guard tests.</p>
+ </note>
+
+ <p>If <c>PosLen</c> in any way references outside the binary, a <c>badarg</c> exception
+ is raised.</p>
+
+ </desc>
+ </func>
+ <func>
+ <name>part(Subject, Pos, Len) -> binary()</name>
+ <fsummary>Extracts a part of a binary</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pos = int()</v>
+ <v>Len = int()</v>
+ </type>
+ <desc>
+ <p>The same as <c>part(Subject, {Pos, Len})</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>referenced_byte_size(binary()) -> int()</name>
+ <fsummary>Determines the size of the actual binary pointed out by a sub-binary</fsummary>
+ <desc>
+
+ <p>If a binary references a larger binary (often described as
+ being a sub-binary), it can be useful to get the size of the
+ actual referenced binary. This function can be used in a program
+ to trigger the use of <c>copy/1</c>. By copying a binary, one might
+ dereference the original, possibly large, binary which a smaller
+ binary is a reference to.</p>
+
+ <p>Example:</p>
+
+ <code>
+store(Binary, GBSet) ->
+ NewBin =
+ case binary:referenced_byte_size(Binary) of
+ Large when Large > 2 * byte_size(Binary) ->
+ binary:copy(Binary);
+ _ ->
+ Binary
+ end,
+ gb_sets:insert(NewBin,GBSet).
+ </code>
+
+ <p>In this example, we chose to copy the binary content before
+ inserting it in the <c>gb_set()</c> if it references a binary more than
+ twice the size of the data we're going to keep. Of course
+ different rules for when copying will apply to different
+ programs.</p>
+
+ <p>Binary sharing will occur whenever binaries are taken apart,
+ this is the fundamental reason why binaries are fast,
+ decomposition can always be done with O(1) complexity. In rare
+ circumstances this data sharing is however undesirable, why this
+ function together with <c>copy/1</c> might be useful when optimizing
+ for memory use.</p>
+
+ <p>Example of binary sharing:</p>
+
+ <code>
+1> A = binary:copy(&lt;&lt;1&gt;&gt;,100).
+&lt;&lt;1,1,1,1,1 ...
+2> byte_size(A).
+100
+3> binary:referenced_byte_size(A)
+100
+4> &lt;&lt;_:10/binary,B:10/binary,_/binary&gt;&gt; = A.
+&lt;&lt;1,1,1,1,1 ...
+5> byte_size(B).
+10
+6> binary:referenced_byte_size(B)
+100
+ </code>
+
+ <note>
+ <p>Binary data is shared among processes. If another process
+ still references the larger binary, copying the part this
+ process uses only consumes more memory and will not free up the
+ larger binary for garbage collection. Use this kind of intrusive
+ functions with extreme care, and only if a real problem is
+ detected.</p>
+ </note>
+
+ </desc>
+ </func>
+ <func>
+ <name>replace(Subject,Pattern,Replacement) -> Result</name>
+ <fsummary>Replaces bytes in a binary according to a pattern</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Replacement = binary()</v>
+ <v>Result = binary()</v>
+ </type>
+ <desc>
+ <p>The same as <c>replace(Subject,Pattern,Replacement,[])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>replace(Subject,Pattern,Replacement,Options) -> Result</name>
+ <fsummary>Replaces bytes in a binary according to a pattern</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Replacement = binary()</v>
+ <v>Result = binary()</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = global | {scope, part()} | {insert_replaced, InsPos}</v>
+ <v>InsPos = OnePos | [ OnePos ]</v>
+ <v>OnePos = int() =&lt; byte_size(Replacement)</v>
+ </type>
+ <desc>
+
+ <p>Constructs a new binary by replacing the parts in
+ <c>Subject</c> matching <c>Pattern</c> with the content of
+ <c>Replacement</c>.</p>
+
+ <p>If the matching sub-part of <c>Subject</c> giving raise to the
+ replacement is to be inserted in the result, the option
+ <c>{insert_replaced, InsPos}</c> will insert the matching part into
+ <c>Replacement</c> at the given position (or positions) before actually
+ inserting <c>Replacement</c> into the <c>Subject</c>. Example:</p>
+
+<code>
+1> binary:replace(&lt;&lt;"abcde"&gt;&gt;,&lt;&lt;"b"&gt;&gt;,&lt;&lt;"[]"&gt;&gt;,[{insert_replaced,1}]).
+&lt;&lt;"a[b]cde"&gt;&gt;
+2> binary:replace(&lt;&lt;"abcde"&gt;&gt;,[&lt;&lt;"b"&gt;&gt;,&lt;&lt;"d"&gt;&gt;],&lt;&lt;"[]"&gt;&gt;,
+ [global,{insert_replaced,1}]).
+&lt;&lt;"a[b]c[d]e"&gt;&gt;
+3> binary:replace(&lt;&lt;"abcde"&gt;&gt;,[&lt;&lt;"b"&gt;&gt;,&lt;&lt;"d"&gt;&gt;],&lt;&lt;"[]"&gt;&gt;,
+ [global,{insert_replaced,[1,1]}]).
+&lt;&lt;"a[bb]c[dd]e"&gt;&gt;
+4> binary:replace(&lt;&lt;"abcde"&gt;&gt;,[&lt;&lt;"b"&gt;&gt;,&lt;&lt;"d"&gt;&gt;],&lt;&lt;"[-]"&gt;&gt;,
+ [global,{insert_replaced,[1,2]}]).
+&lt;&lt;"a[b-b]c[d-d]e"&gt;&gt;
+</code>
+
+ <p>If any position given in <c>InsPos</c> is greater than the size of the replacement binary, a <c>badarg</c> exception is raised.</p>
+
+ <p>The options <c>global</c> and <c>{scope, part()}</c> work as for <seealso marker="#split-3">split/3</seealso>. The return type is always a <c>binary()</c>.</p>
+
+ <p>For a description of <c>Pattern</c>, see <seealso marker="#compile_pattern-1">compile_pattern/1</seealso>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>split(Subject,Pattern) -> Parts</name>
+ <fsummary>Splits a binary according to a pattern</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Parts = [ binary() ]</v>
+ </type>
+ <desc>
+ <p>The same as <c>split(Subject, Pattern, [])</c>.</p>
+ </desc>
+ </func>
+ <func>
+ <name>split(Subject,Pattern,Options) -> Parts</name>
+ <fsummary>Splits a binary according to a pattern</fsummary>
+ <type>
+ <v>Subject = binary()</v>
+ <v>Pattern = binary() | [ binary() ] | cp()</v>
+ <v>Parts = [ binary() ]</v>
+ <v>Options = [ Option ]</v>
+ <v>Option = {scope, part()} | trim | global</v>
+ </type>
+ <desc>
+
+ <p>Splits Binary into a list of binaries based on Pattern. If
+ the option global is not given, only the first occurrence of
+ Pattern in Subject will give rise to a split.</p>
+
+ <p>The parts of Pattern actually found in Subject are not included in the result.</p>
+
+ <p>Example:</p>
+
+<code>
+1> binary:split(&lt;&lt;1,255,4,0,0,0,2,3&gt;&gt;, [&lt;&lt;0,0,0&gt;&gt;,&lt;&lt;2&gt;&gt;],[]).
+[&lt;&lt;1,255,4&gt;&gt;, &lt;&lt;2,3&gt;&gt;]
+2> binary:split(&lt;&lt;0,1,0,0,4,255,255,9&gt;&gt;, [&lt;&lt;0,0&gt;&gt;, &lt;&lt;255,255&gt;&gt;],[global]).
+[&lt;&lt;0,1&gt;&gt;,&lt;&lt;4&gt;&gt;,&lt;&lt;9&gt;&gt;]
+</code>
+
+ <p>Summary of options:</p>
+ <taglist>
+
+ <tag>{scope, part()}</tag>
+
+ <item><p>Works as in <seealso marker="#match-3">match/3</seealso> and
+ <seealso marker="#matches-3">matches/3</seealso>. Note that
+ this only defines the scope of the search for matching strings,
+ it does not cut the binary before splitting. The bytes before
+ and after the scope will be kept in the result. See example
+ below.</p></item>
+
+ <tag>trim</tag>
+
+ <item><p>Removes trailing empty parts of the result (as does trim in <c>re:split/3</c>)</p></item>
+
+ <tag>global</tag>
+
+ <item><p>Repeats the split until the <c>Subject</c> is
+ exhausted. Conceptually the global option makes split work on
+ the positions returned by <seealso marker="#matches-3">matches/3</seealso>,
+ while it normally
+ works on the position returned by
+ <seealso marker="#match-3">match/3</seealso>.</p></item>
+
+ </taglist>
+
+ <p>Example of the difference between a scope and taking the
+ binary apart before splitting:</p>
+
+<code>
+1> binary:split(&lt;&lt;"banana"&gt;&gt;,[&lt;&lt;"a"&gt;&gt;],[{scope,{2,3}}]).
+[&lt;&lt;"ban"&gt;&gt;,&lt;&lt;"na"&gt;&gt;]
+2> binary:split(binary:part(&lt;&lt;"banana"&gt;&gt;,{2,3}),[&lt;&lt;"a"&gt;&gt;],[]).
+[&lt;&lt;"n"&gt;&gt;,&lt;&lt;"n"&gt;&gt;]
+</code>
+
+ <p>The return type is always a list of binaries that are all
+ referencing <c>Subject</c>. This means that the data in <c>Subject</c> is not
+ actually copied to new binaries and that <c>Subject</c> cannot be
+ garbage collected until the results of the split are no longer
+ referenced.</p>
+
+ <p>For a description of <c>Pattern</c>, see <seealso marker="#compile_pattern-1">compile_pattern/1</seealso>.</p>
+
+ </desc>
+ </func>
+ </funcs>
+</erlref>
diff --git a/lib/stdlib/doc/src/erl_scan.xml b/lib/stdlib/doc/src/erl_scan.xml
index 4175146c3c..1199c34f0f 100644
--- a/lib/stdlib/doc/src/erl_scan.xml
+++ b/lib/stdlib/doc/src/erl_scan.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>erl_scan</title>
@@ -103,7 +103,7 @@ attributes() = line() | list() | tuple()</code>
Info, atom()}</c>,
<c>{char, Info, integer()}</c>, <c>{comment, Info,
string()}</c>, <c>{float, Info, float()}</c>, <c>{integer,
- Info, integer()}</c>, <c>{var, Info, atom()}</c>,
+ Info, integer()}</c>, <c>{var, Info, atom()}</c>,
and <c>{white_space, Info, string()}</c>.</p>
<p>The valid options are:</p>
<taglist>
@@ -149,7 +149,8 @@ attributes() = line() | list() | tuple()</code>
<v>StartLocation = EndLocation = location()</v>
<v>Options = Option | [Option]</v>
<v>Option = {reserved_word_fun,reserved_word_fun()}
- | return_comments | return_white_spaces | return</v>
+ | return_comments | return_white_spaces | return
+ | text</v>
</type>
<desc>
<p>This is the re-entrant scanner which scans characters until
@@ -173,7 +174,7 @@ attributes() = line() | list() | tuple()</code>
<tag><c>{error, ErrorInfo, EndLocation}</c></tag>
<item>
<p>An error occurred. <c>LeftOverChars</c> is the remaining
- characters of the input data,
+ characters of the input data,
starting from <c>EndLocation</c>.</p>
</item>
</taglist>
@@ -278,7 +279,7 @@ attributes() = line() | list() | tuple()</code>
<item><p>The token's symbol.</p>
</item>
<tag><c>{text, string()}</c></tag>
- <item><p>The token's text..</p>
+ <item><p>The token's text.</p>
</item>
</taglist>
</desc>
@@ -315,7 +316,7 @@ attributes() = line() | list() | tuple()</code>
<type>
<v>Attributes = attributes()</v>
<v>AttributeItemSpec = AttributeItem | [AttributeItem]</v>
- <v>AttributesInfo = AttributeInfoTuple | undefined
+ <v>AttributesInfo = AttributeInfoTuple | undefined
| [AttributeInfoTuple]</v>
<v>AttributeInfoTuple = {AttributeItem, Info}</v>
<v>AttributeItem = atom()</v>
@@ -352,7 +353,7 @@ attributes() = line() | list() | tuple()</code>
just the line if the column unknown.</p>
</item>
<tag><c>{text, string()}</c></tag>
- <item><p>The token's text..</p>
+ <item><p>The token's text.</p>
</item>
</taglist>
</desc>
diff --git a/lib/stdlib/doc/src/ets.xml b/lib/stdlib/doc/src/ets.xml
index 7b9f0e7772..ee1befc882 100644
--- a/lib/stdlib/doc/src/ets.xml
+++ b/lib/stdlib/doc/src/ets.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>ets</title>
@@ -1686,7 +1686,7 @@ true</pre>
</desc>
</func>
<func>
- <name>to_dets(Tab, DetsTab) -> Tab</name>
+ <name>to_dets(Tab, DetsTab) -> DetsTab</name>
<fsummary>Fill a Dets table with objects from an ETS table.</fsummary>
<type>
<v>Tab = tid() | atom()</v>
diff --git a/lib/stdlib/doc/src/gen_event.xml b/lib/stdlib/doc/src/gen_event.xml
index df09294de6..2234a62ac3 100644
--- a/lib/stdlib/doc/src/gen_event.xml
+++ b/lib/stdlib/doc/src/gen_event.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>gen_event</title>
@@ -630,12 +630,66 @@ gen_event:stop -----> Module:terminate/2
<p>The function should return the updated internal state.</p>
</desc>
</func>
+ <func>
+ <name>Module:format_status(Opt, [PDict, State]) -> Status</name>
+ <fsummary>Optional function for providing a term describing the
+ current event handler state.</fsummary>
+ <type>
+ <v>Opt = normal | terminate</v>
+ <v>PDict = [{Key, Value}]</v>
+ <v>State = term()</v>
+ <v>Status = term()</v>
+ </type>
+ <desc>
+ <note>
+ <p>This callback is optional, so event handler modules need
+ not export it. If a handler does not export this function,
+ the gen_event module uses the handler state directly for
+ the purposes described below.</p>
+ </note>
+ <p>This function is called by a gen_event process when:</p>
+ <list typed="bulleted">
+ <item>One
+ of <seealso marker="sys#get_status/1">sys:get_status/1,2</seealso>
+ is invoked to get the gen_event status. <c>Opt</c> is set
+ to the atom <c>normal</c> for this case.</item>
+ <item>The event handler terminates abnormally and gen_event
+ logs an error. <c>Opt</c> is set to the
+ atom <c>terminate</c> for this case.</item>
+ </list>
+ <p>This function is useful for customising the form and
+ appearance of the event handler state for these cases. An
+ event handler callback module wishing to customise
+ the <c>sys:get_status/1,2</c> return value as well as how
+ its state appears in termination error logs exports an
+ instance of <c>format_status/2</c> that returns a term
+ describing the current state of the event handler.</p>
+ <p><c>PDict</c> is the current value of the gen_event's
+ process dictionary.</p>
+ <p><c>State</c> is the internal state of the event
+ handler.</p>
+ <p>The function should return <c>Status</c>, a term that
+ customises the details of the current state of the event
+ handler. Any term is allowed for <c>Status</c>. The
+ gen_event module uses <c>Status</c> as follows:</p>
+ <list typed="bulleted">
+ <item>When <c>sys:get_status/1,2</c> is called, gen_event
+ ensures that its return value contains <c>Status</c> in
+ place of the event handler's actual state term.</item>
+ <item>When an event handler terminates abnormally, gen_event
+ logs <c>Status</c> in place of the event handler's actual
+ state term.</item>
+ </list>
+ <p>One use for this function is to return compact alternative
+ state representations to avoid having large state terms
+ printed in logfiles.</p>
+ </desc>
+ </func>
</funcs>
<section>
<title>SEE ALSO</title>
- <p><seealso marker="supervisor">supervisor(3)</seealso>,
+ <p><seealso marker="supervisor">supervisor(3)</seealso>,
<seealso marker="sys">sys(3)</seealso></p>
</section>
</erlref>
-
diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml
index 739cd0bffd..d15383c621 100644
--- a/lib/stdlib/doc/src/gen_fsm.xml
+++ b/lib/stdlib/doc/src/gen_fsm.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>gen_fsm</title>
@@ -730,33 +730,58 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4
</desc>
</func>
<func>
- <name>Module:format_status(normal, [PDict, StateData]) -> Status</name>
+ <name>Module:format_status(Opt, [PDict, StateData]) -> Status</name>
<fsummary>Optional function for providing a term describing the
current gen_fsm status.</fsummary>
<type>
+ <v>Opt = normal | terminate</v>
<v>PDict = [{Key, Value}]</v>
<v>StateData = term()</v>
- <v>Status = [term()]</v>
+ <v>Status = term()</v>
</type>
<desc>
- <p><em>This callback is optional, so callback modules need not
- export it. The gen_fsm module provides a default
- implementation of this function that returns the callback
- module state data.</em></p>
- <p>This function is called by a gen_fsm process when one
- of <seealso marker="sys#get_status/1">sys:get_status/1,2</seealso>
- is invoked to get the gen_fsm status. A callback module
- wishing to customise the <c>sys:get_status/1,2</c> return
- value exports an instance of <c>format_status/2</c> that
- returns a term describing the current status of the
- gen_fsm.</p>
+ <note>
+ <p>This callback is optional, so callback modules need not
+ export it. The gen_fsm module provides a default
+ implementation of this function that returns the callback
+ module state data.</p>
+ </note>
+ <p>This function is called by a gen_fsm process when:</p>
+ <list typed="bulleted">
+ <item>One
+ of <seealso marker="sys#get_status/1">sys:get_status/1,2</seealso>
+ is invoked to get the gen_fsm status. <c>Opt</c> is set to
+ the atom <c>normal</c> for this case.</item>
+ <item>The gen_fsm terminates abnormally and logs an
+ error. <c>Opt</c> is set to the atom <c>terminate</c> for
+ this case.</item>
+ </list>
+ <p>This function is useful for customising the form and
+ appearance of the gen_fsm status for these cases. A callback
+ module wishing to customise the <c>sys:get_status/1,2</c>
+ return value as well as how its status appears in
+ termination error logs exports an instance
+ of <c>format_status/2</c> that returns a term describing the
+ current status of the gen_fsm.</p>
<p><c>PDict</c> is the current value of the gen_fsm's
process dictionary.</p>
<p><c>StateData</c> is the internal state data of the
gen_fsm.</p>
- <p>The function should return <c>Status</c>, a list of one or
- more terms that customise the details of the current state
- and status of the gen_fsm.</p>
+ <p>The function should return <c>Status</c>, a term that
+ customises the details of the current state and status of
+ the gen_fsm. There are no restrictions on the
+ form <c>Status</c> can take, but for
+ the <c>sys:get_status/1,2</c> case (when <c>Opt</c>
+ is <c>normal</c>), the recommended form for
+ the <c>Status</c> value is <c>[{data, [{"StateData",
+ Term}]}]</c> where <c>Term</c> provides relevant details of
+ the gen_fsm state data. Following this recommendation isn't
+ required, but doing so will make the callback module status
+ consistent with the rest of the <c>sys:get_status/1,2</c>
+ return value.</p>
+ <p>One use for this function is to return compact alternative
+ state data representations to avoid having large state terms
+ printed in logfiles.</p>
</desc>
</func>
</funcs>
@@ -770,4 +795,3 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4
<seealso marker="sys">sys(3)</seealso></p>
</section>
</erlref>
-
diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml
index 30c04d1d52..1045766e01 100644
--- a/lib/stdlib/doc/src/gen_server.xml
+++ b/lib/stdlib/doc/src/gen_server.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>gen_server</title>
@@ -599,32 +599,57 @@ gen_server:abcast -----> Module:handle_cast/2
</desc>
</func>
<func>
- <name>Module:format_status(normal, [PDict, State]) -> Status</name>
+ <name>Module:format_status(Opt, [PDict, State]) -> Status</name>
<fsummary>Optional function for providing a term describing the
current gen_server status.</fsummary>
<type>
+ <v>Opt = normal | terminate</v>
<v>PDict = [{Key, Value}]</v>
<v>State = term()</v>
- <v>Status = [term()]</v>
+ <v>Status = term()</v>
</type>
<desc>
- <p><em>This callback is optional, so callback modules need not
- export it. The gen_server module provides a default
- implementation of this function that returns the callback
- module state.</em></p>
- <p>This function is called by a gen_server process when one
+ <note>
+ <p>This callback is optional, so callback modules need not
+ export it. The gen_server module provides a default
+ implementation of this function that returns the callback
+ module state.</p>
+ </note>
+ <p>This function is called by a gen_server process when:</p>
+ <list typed="bulleted">
+ <item>One
of <seealso marker="sys#get_status/1">sys:get_status/1,2</seealso>
- is invoked to get the gen_server status. A callback module
- wishing to customise the <c>sys:get_status/1,2</c> return
- value exports an instance of <c>format_status/2</c> that
- returns a term describing the current status of the
- gen_server.</p>
+ is invoked to get the gen_server status. <c>Opt</c> is set
+ to the atom <c>normal</c> for this case.</item>
+ <item>The gen_server terminates abnormally and logs an
+ error. <c>Opt</c> is set to the atom <c>terminate</c> for this
+ case.</item>
+ </list>
+ <p>This function is useful for customising the form and
+ appearance of the gen_server status for these cases. A
+ callback module wishing to customise
+ the <c>sys:get_status/1,2</c> return value as well as how
+ its status appears in termination error logs exports an
+ instance of <c>format_status/2</c> that returns a term
+ describing the current status of the gen_server.</p>
<p><c>PDict</c> is the current value of the gen_server's
process dictionary.</p>
<p><c>State</c> is the internal state of the gen_server.</p>
- <p>The function should return <c>Status</c>, a list of one or
- more terms that customise the details of the current state
- and status of the gen_server.</p>
+ <p>The function should return <c>Status</c>, a term that
+ customises the details of the current state and status of
+ the gen_server. There are no restrictions on the
+ form <c>Status</c> can take, but for
+ the <c>sys:get_status/1,2</c> case (when <c>Opt</c>
+ is <c>normal</c>), the recommended form for
+ the <c>Status</c> value is <c>[{data, [{"State",
+ Term}]}]</c> where <c>Term</c> provides relevant details of
+ the gen_server state. Following this recommendation isn't
+ required, but doing so will make the callback module status
+ consistent with the rest of the <c>sys:get_status/1,2</c>
+ return value.</p>
+ <p>One use for this function is to return compact alternative
+ state representations to avoid having large state terms
+ printed in logfiles.</p>
</desc>
</func>
</funcs>
diff --git a/lib/stdlib/doc/src/lists.xml b/lib/stdlib/doc/src/lists.xml
index 855a7e0244..a273a2301f 100644
--- a/lib/stdlib/doc/src/lists.xml
+++ b/lib/stdlib/doc/src/lists.xml
@@ -443,7 +443,7 @@ flatmap(Fun, List1) ->
<desc>
<p>Returns a list containing the sorted elements of the list
<c>TupleList1</c>. Sorting is performed on the <c>N</c>th
- element of the tuples.</p>
+ element of the tuples. The sort is stable.</p>
</desc>
</func>
<func>
@@ -466,7 +466,7 @@ flatmap(Fun, List1) ->
</desc>
</func>
<func>
- <name>keytake(Key, N, TupleList1) -> {value, Tuple, TupleList2}
+ <name>keytake(Key, N, TupleList1) -> {value, Tuple, TupleList2}
| false</name>
<fsummary>Extract an element from a list of tuples</fsummary>
<type>
@@ -840,7 +840,7 @@ length(lists:seq(From, To, Incr)) == (To-From+Incr) div Incr</code>
<c>Pred</c>. <c>splitwith/2</c> behaves as if it is defined
as follows:</p>
<code type="none">
-splitwith(Pred, List) ->
+splitwith(Pred, List) ->
{takewhile(Pred, List), dropwhile(Pred, List)}.</code>
<p>Examples:</p>
<pre>
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 28a3719d38..23d1e8b7de 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -30,6 +30,283 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 1.17</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The Erlang code preprocessor (<c>epp</c>) sent extra
+ messages on the form <c>{eof,Location}</c> to the client
+ when parsing the <c>file</c> attribute. This bug,
+ introduced in R11B, has been fixed.</p>
+ <p>
+ Own Id: OTP-8470</p>
+ </item>
+ <item>
+ <p>The abstract type 'fun' could not be printed by the
+ Erlang pretty printer (<c>erl_pp</c>). This bug has been
+ fixed.</p>
+ <p>
+ Own Id: OTP-8473</p>
+ </item>
+ <item>
+ <p>The function <c>erl_scan:reserved_word/1</c> no longer
+ returns <c>true</c> when given the word <c>spec</c>. This
+ bug was introduced in STDLIB-1.15.3 (R12B-3).</p>
+ <p>
+ Own Id: OTP-8567</p>
+ </item>
+ <item>
+ <p>The documentation of <c>lists:keysort/2</c> states
+ that the sort is stable.</p>
+ <p>
+ Own Id: OTP-8628 Aux Id: seq11576 </p>
+ </item>
+ <item>
+ <p>
+ The shell's line editing has been improved to more
+ resemble the behaviour of readline and other shells.
+ (Thanks to Dave Peticolas)</p>
+ <p>
+ Own Id: OTP-8635</p>
+ </item>
+ <item>
+ <p>The Erlang code preprocessor (<c>epp</c>) did not
+ correctly handle premature end-of-input when defining
+ macros. This bug, introduced in STDLIB 1.16, has been
+ fixed.</p>
+ <p>
+ Own Id: OTP-8665 Aux Id: OTP-7810 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The module binary from EEP31 (and EEP9) is implemented.</p>
+ <p>
+ Own Id: OTP-8217</p>
+ </item>
+ <item>
+ <p>The erlang pretty printer (<c>erl_pp</c>) no longer
+ quotes atoms in types.</p>
+ <p>
+ Own Id: OTP-8501</p>
+ </item>
+ <item>
+ <p>The Erlang code preprocessor (<c>epp</c>) now
+ considers records with no fields as typed.</p>
+ <p>
+ Own Id: OTP-8503</p>
+ </item>
+ <item>
+ <p>
+ Added function <c>zip:foldl/3</c> to iterate over zip
+ archives.</p>
+ <p>
+ Added functions to create and extract escripts. See
+ <c>escript:create/2</c> and <c>escript:extract/2</c>.</p>
+ <p>
+ The undocumented function <c>escript:foldl/3</c> has been
+ removed. The same functionality can be achieved with the
+ more flexible functions <c>escript:extract/2</c> and
+ <c>zip:foldl/3</c>.</p>
+ <p>
+ Record fields has been annotated with type info. Source
+ files as been adapted to fit within 80 chars and trailing
+ whitespace has been removed.</p>
+ <p>
+ Own Id: OTP-8521</p>
+ </item>
+ <item>
+ <p>The Erlang parser no longer duplicates the singleton
+ type <c>undefined</c> in the type of record fields
+ without initial value.</p>
+ <p>
+ Own Id: OTP-8522</p>
+ </item>
+ <item>
+ <p>A regular expression with many levels of parenthesis
+ could cause a buffer overflow. That has been corrected.
+ (Thanks to Michael Santos.)</p>
+ <p>
+ Own Id: OTP-8539</p>
+ </item>
+ <item>
+ <p>When defining macros the closing right parenthesis
+ before the dot is now mandatory.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8562</p>
+ </item>
+ <item>
+ <p>
+ Some properties of a compiled re pattern are defined to
+ allow for guard tests.</p>
+ <p>
+ Own Id: OTP-8577</p>
+ </item>
+ <item>
+ <p>Local and imported functions now override the
+ auto-imported BIFs when the names clash. The pre R14
+ behaviour was that auto-imported BIFs would override
+ local functions. To avoid that old programs change
+ behaviour, the following will generate an error:</p>
+ <list><item><p>Doing a call without explicit module name
+ to a local function having a name clashing with the name
+ of an auto-imported BIF that was present (and
+ auto-imported) before OTP R14A</p></item>
+ <item><p>Explicitly importing a function having a name
+ clashing with the name of an autoimported BIF that was
+ present (and autoimported) before OTP R14A</p></item>
+ <item><p>Using any form of the old compiler directive
+ <c>nowarn_bif_clash</c></p></item> </list> <p>If the BIF
+ was added or auto-imported in OTP R14A or later,
+ overriding it with an import or a local function will
+ only result in a warning,</p> <p>To resolve clashes, you
+ can either use the explicit module name <c>erlang</c> to
+ call the BIF, or you can remove the auto-import of that
+ specific BIF by using the new compiler directive
+ <c>-compile({no_auto_import,[F/A]}).</c>, which makes all
+ calls to the local or imported function without explicit
+ module name pass without warnings or errors.</p> <p>The
+ change makes it possible to add auto-imported BIFs
+ without breaking or silently changing old code in the
+ future. However some current code ingeniously utilizing
+ the old behaviour or the <c>nowarn_bif_clash</c> compiler
+ directive, might need changing to be accepted by the
+ compiler.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8579</p>
+ </item>
+ <item>
+ <p>The undocumented, unsupport, and deprecated function
+ <c>lists:flat_length/1</c> has been removed.</p>
+ <p>
+ Own Id: OTP-8584</p>
+ </item>
+ <item>
+ <p>
+ A bug in re that could cause certain regular expression
+ matches never to terminate is corrected. (Thanks to
+ Michael Santos and Gordon Guthrie.)</p>
+ <p>
+ Own Id: OTP-8589</p>
+ </item>
+ <item>
+ <p>Nested records can now be accessed without
+ parenthesis. See the Reference Manual for examples.
+ (Thanks to YAMASHINA Hio and Tuncer Ayaz.)</p>
+ <p>
+ Own Id: OTP-8597</p>
+ </item>
+ <item>
+ <p><c>receive</c> statements that can only read out a
+ newly created reference are now specially optimized so
+ that it will execute in constant time regardless of the
+ number of messages in the receive queue for the process.
+ That optimization will benefit calls to
+ <c>gen_server:call()</c>. (See <c>gen:do_call/4</c> for
+ an example of a receive statement that will be
+ optimized.)</p>
+ <p>
+ Own Id: OTP-8623</p>
+ </item>
+ <item>
+ <p>The beam_lib:cmp/2 function now compares BEAM files in
+ stricter way. The BEAM files will be considered different
+ if there are any changes except in the compilation
+ information ("CInf") chunk. beam_lib:cmp/2 used to ignore
+ differences in the debug information (significant for
+ Dialyzer) and other chunks that did not directly change
+ the run-time behavior.</p>
+ <p>
+ Own Id: OTP-8625</p>
+ </item>
+ <item>
+ <p>
+ When a gen_server, gen_fsm process, or gen_event
+ terminates abnormally, sometimes the text representation
+ of the process state can occupy many lines of the error
+ log, depending on the definition of the state term. A
+ mechanism to trim out parts of the state from the log has
+ been added (using a format_status/2 callback). See the
+ documentation.</p>
+ <p>
+ Own Id: OTP-8630</p>
+ </item>
+ <item>
+ <p>
+ Calling <c>sys:get_status()</c> for processes that have
+ globally registered names that were not atoms would cause
+ a crash. Corrected. (Thanks to Steve Vinoski.)</p>
+ <p>
+ Own Id: OTP-8656</p>
+ </item>
+ <item>
+ <p>The Erlang scanner has been augmented with two new
+ tokens: <c>..</c> and <c>...</c>.</p>
+ <p>
+ Own Id: OTP-8657</p>
+ </item>
+ <item>
+ <p>Expressions evaluating to integers can now be used in
+ types and function specifications where hitherto only
+ integers were allowed ("Erlang_Integer").</p>
+ <p>
+ Own Id: OTP-8664</p>
+ </item>
+ <item>
+ <p>The compiler optimizes record operations better.</p>
+ <p>
+ Own Id: OTP-8668</p>
+ </item>
+ <item>
+ <p>
+ The recently added BIFs erlang:min/2, erlang:max/2 and
+ erlang:port_command/3 are now auto-imported (as they were
+ originally intended to be). Due to the recent compiler
+ change (OTP-8579), the only impact on old code defining
+ it's own min/2, max/2 or port_command/3 functions will be
+ a warning, the local functions will still be used. The
+ warning can be removed by using
+ -compile({no_auto_import,[min/2,max/2,port_command/3]}).
+ in the source file.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8669 Aux Id: OTP-8579 </p>
+ </item>
+ <item>
+ <p>
+ Now, binary_to_term/2 is auto-imported. This will cause a
+ compile warning if and only if a module has got a local
+ function with that name.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8671</p>
+ </item>
+ <item>
+ <p>
+ The predefined builtin type tid() has been removed.
+ Instead, ets:tid() should be used.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8687</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 1.16.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/doc/src/re.xml b/lib/stdlib/doc/src/re.xml
index 4d2a0e0995..80adc3e347 100644
--- a/lib/stdlib/doc/src/re.xml
+++ b/lib/stdlib/doc/src/re.xml
@@ -80,7 +80,11 @@
- a unicode_binary is allowed as the tail of the list</code>
<code type="none">
- mp() = Opaque datatype containing a compiled regular expression.</code>
+ mp() = Opaque datatype containing a compiled regular expression.
+ - The mp() is guaranteed to be a tuple() having the atom
+ 're_pattern' as it's first element, to allow for matching in
+ guards. The arity of the tuple() or the content of the other fields
+ is however not to be trusted.</code>
</section>
<funcs>
<func>
diff --git a/lib/stdlib/doc/src/ref_man.xml b/lib/stdlib/doc/src/ref_man.xml
index f6ae368e92..85aae6151d 100644
--- a/lib/stdlib/doc/src/ref_man.xml
+++ b/lib/stdlib/doc/src/ref_man.xml
@@ -4,7 +4,7 @@
<application xmlns:xi="http://www.w3.org/2001/XInclude">
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>STDLIB Reference Manual</title>
@@ -37,6 +37,7 @@
<xi:include href="array.xml"/>
<xi:include href="base64.xml"/>
<xi:include href="beam_lib.xml"/>
+ <xi:include href="binary.xml"/>
<xi:include href="c.xml"/>
<xi:include href="calendar.xml"/>
<xi:include href="dets.xml"/>
diff --git a/lib/stdlib/doc/src/timer.xml b/lib/stdlib/doc/src/timer.xml
index 3d54047e35..1b34e71490 100644
--- a/lib/stdlib/doc/src/timer.xml
+++ b/lib/stdlib/doc/src/timer.xml
@@ -218,7 +218,8 @@
<tag><c>tc/3</c></tag>
<item>
<p>Evaluates <c>apply(Module, Function, Arguments)</c> and measures
- the elapsed real time. Returns <c>{Time, Value}</c>, where
+ the elapsed real time as reported by <c>now/0</c>.
+ Returns <c>{Time, Value}</c>, where
<c>Time</c> is the elapsed real time in <em>microseconds</em>,
and <c>Value</c> is what is returned from the apply.</p>
</item>
diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile
index 237818c08b..600303d7e1 100644
--- a/lib/stdlib/src/Makefile
+++ b/lib/stdlib/src/Makefile
@@ -43,6 +43,7 @@ MODULES= \
array \
base64 \
beam_lib \
+ binary \
c \
calendar \
dets \
diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl
index 820afd3739..91ff2438c6 100644
--- a/lib/stdlib/src/beam_lib.erl
+++ b/lib/stdlib/src/beam_lib.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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(beam_lib).
@@ -41,12 +41,11 @@
terminate/2,code_change/3]).
-export([make_crypto_key/2, get_crypto_key/1]). %Utilities used by compiler
+-export_type([attrib_entry/0, compinfo_entry/0, labeled_entry/0]).
+
-import(lists, [append/1, delete/2, foreach/2, keysort/2,
member/2, reverse/1, sort/1, splitwith/2]).
--include_lib("kernel/include/file.hrl").
--include("erl_compile.hrl").
-
%%-------------------------------------------------------------------------
-type beam() :: module() | file:filename() | binary().
@@ -331,13 +330,11 @@ beam_files(Dir) ->
%% -> ok | throw(Error)
cmp_files(File1, File2) ->
- {ok, {M1, L1}} = read_significant_chunks(File1),
- {ok, {M2, L2}} = read_significant_chunks(File2),
+ {ok, {M1, L1}} = read_all_but_useless_chunks(File1),
+ {ok, {M2, L2}} = read_all_but_useless_chunks(File2),
if
M1 =:= M2 ->
- List1 = filter_funtab(L1),
- List2 = filter_funtab(L2),
- cmp_lists(List1, List2);
+ cmp_lists(L1, L2);
true ->
error({modules_different, M1, M2})
end.
@@ -408,6 +405,20 @@ pad(Size) ->
end.
%% -> {ok, {Module, Chunks}} | throw(Error)
+read_all_but_useless_chunks(File0) when is_atom(File0);
+ is_list(File0);
+ is_binary(File0) ->
+ File = beam_filename(File0),
+ {ok, Module, ChunkIds0} = scan_beam(File, info),
+ ChunkIds = [Name || {Name,_,_} <- ChunkIds0,
+ not is_useless_chunk(Name)],
+ {ok, Module, Chunks} = scan_beam(File, ChunkIds),
+ {ok, {Module, lists:reverse(Chunks)}}.
+
+is_useless_chunk("CInf") -> true;
+is_useless_chunk(_) -> false.
+
+%% -> {ok, {Module, Chunks}} | throw(Error)
read_significant_chunks(File) ->
case read_chunk_data(File, significant_chunks(), [allow_missing_chunks]) of
{ok, {Module, Chunks0}} ->
diff --git a/lib/stdlib/src/binary.erl b/lib/stdlib/src/binary.erl
new file mode 100644
index 0000000000..f6489788b2
--- /dev/null
+++ b/lib/stdlib/src/binary.erl
@@ -0,0 +1,177 @@
+%%
+%% %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(binary).
+%%
+%% The following functions implemented as BIF's
+%% binary:compile_pattern/1
+%% binary:match/{2,3}
+%% binary:matches/{2,3}
+%% binary:longest_common_prefix/1
+%% binary:longest_common_suffix/1
+%% binary:first/1
+%% binary:last/1
+%% binary:at/2
+%% binary:part/{2,3}
+%% binary:bin_to_list/{1,2,3}
+%% binary:list_to_bin/1
+%% binary:copy/{1,2}
+%% binary:referenced_byte_size/1
+%% binary:decode_unsigned/{1,2}
+%% - Not yet:
+%%
+%% Implemented in this module:
+-export([split/2,split/3,replace/3,replace/4]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% split
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+split(H,N) ->
+ split(H,N,[]).
+split(Haystack,Needles,Options) ->
+ try
+ {Part,Global,Trim} = get_opts_split(Options,{no,false,false}),
+ Moptlist = case Part of
+ no ->
+ [];
+ {A,B} ->
+ [{scope,{A,B}}]
+ end,
+ MList = if
+ Global ->
+ binary:matches(Haystack,Needles,Moptlist);
+ true ->
+ case binary:match(Haystack,Needles,Moptlist) of
+ nomatch -> [];
+ Match -> [Match]
+ end
+ end,
+ do_split(Haystack,MList,0,Trim)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+do_split(H,[],N,true) when N >= byte_size(H) ->
+ [];
+do_split(H,[],N,_) ->
+ [binary:part(H,{N,byte_size(H)-N})];
+do_split(H,[{A,B}|T],N,Trim) ->
+ case binary:part(H,{N,A-N}) of
+ <<>> ->
+ Rest = do_split(H,T,A+B,Trim),
+ case {Trim, Rest} of
+ {true,[]} ->
+ [];
+ _ ->
+ [<<>> | Rest]
+ end;
+ Oth ->
+ [Oth | do_split(H,T,A+B,Trim)]
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% replace
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+replace(H,N,R) ->
+ replace(H,N,R,[]).
+replace(Haystack,Needles,Replacement,Options) ->
+ try
+ true = is_binary(Replacement), % Make badarg instead of function clause
+ {Part,Global,Insert} = get_opts_replace(Options,{no,false,[]}),
+ Moptlist = case Part of
+ no ->
+ [];
+ {A,B} ->
+ [{scope,{A,B}}]
+ end,
+ MList = if
+ Global ->
+ binary:matches(Haystack,Needles,Moptlist);
+ true ->
+ case binary:match(Haystack,Needles,Moptlist) of
+ nomatch -> [];
+ Match -> [Match]
+ end
+ end,
+ ReplList = case Insert of
+ [] ->
+ Replacement;
+ Y when is_integer(Y) ->
+ splitat(Replacement,0,[Y]);
+ Li when is_list(Li) ->
+ splitat(Replacement,0,lists:sort(Li))
+ end,
+ erlang:iolist_to_binary(do_replace(Haystack,MList,ReplList,0))
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+
+do_replace(H,[],_,N) ->
+ [binary:part(H,{N,byte_size(H)-N})];
+do_replace(H,[{A,B}|T],Replacement,N) ->
+ [binary:part(H,{N,A-N}),
+ if
+ is_list(Replacement) ->
+ do_insert(Replacement, binary:part(H,{A,B}));
+ true ->
+ Replacement
+ end
+ | do_replace(H,T,Replacement,A+B)].
+
+do_insert([X],_) ->
+ [X];
+do_insert([H|T],R) ->
+ [H,R|do_insert(T,R)].
+
+splitat(H,N,[]) ->
+ [binary:part(H,{N,byte_size(H)-N})];
+splitat(H,N,[I|T]) ->
+ [binary:part(H,{N,I-N})|splitat(H,I,T)].
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Simple helper functions
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_opts_split([],{Part,Global,Trim}) ->
+ {Part,Global,Trim};
+get_opts_split([{scope,{A,B}} | T],{_Part,Global,Trim}) ->
+ get_opts_split(T,{{A,B},Global,Trim});
+get_opts_split([global | T],{Part,_Global,Trim}) ->
+ get_opts_split(T,{Part,true,Trim});
+get_opts_split([trim | T],{Part,Global,_Trim}) ->
+ get_opts_split(T,{Part,Global,true});
+get_opts_split(_,_) ->
+ throw(badopt).
+
+get_opts_replace([],{Part,Global,Insert}) ->
+ {Part,Global,Insert};
+get_opts_replace([{scope,{A,B}} | T],{_Part,Global,Insert}) ->
+ get_opts_replace(T,{{A,B},Global,Insert});
+get_opts_replace([global | T],{Part,_Global,Insert}) ->
+ get_opts_replace(T,{Part,true,Insert});
+get_opts_replace([{insert_replaced,N} | T],{Part,Global,_Insert}) ->
+ get_opts_replace(T,{Part,Global,N});
+get_opts_replace(_,_) ->
+ throw(badopt).
+
diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl
index 433833e233..e05a1c787f 100644
--- a/lib/stdlib/src/c.erl
+++ b/lib/stdlib/src/c.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(c).
@@ -31,10 +31,14 @@
-export([display_info/1]).
-export([appcall/4]).
--import(lists, [reverse/1,flatten/1,sublist/3,sort/1,keysearch/3,keysort/2,
+-import(lists, [reverse/1,flatten/1,sublist/3,sort/1,keysort/2,
concat/1,max/1,min/1,foreach/2,foldl/3,flatmap/2]).
-import(io, [format/1, format/2]).
+%%-----------------------------------------------------------------------
+
+-spec help() -> 'ok'.
+
help() ->
format("bt(Pid) -- stack backtrace for a process\n"
"c(File) -- compile and load code in <File>\n"
@@ -65,8 +69,12 @@ help() ->
%% c(FileName)
%% Compile a file/module.
+-spec c(file:name()) -> {'ok', module()} | 'error'.
+
c(File) -> c(File, []).
+-spec c(file:name(), [compile:option()]) -> {'ok', module()} | 'error'.
+
c(File, Opts0) when is_list(Opts0) ->
Opts = [report_errors,report_warnings|Opts0],
case compile:file(File, Opts) of
@@ -82,6 +90,8 @@ c(File, Opt) ->
%%% Obtain the 'outdir' option from the argument. Return "." if no
%%% such option was given.
+-spec outdir([compile:option()]) -> file:filename().
+
outdir([]) ->
".";
outdir([Opt|Rest]) ->
@@ -118,8 +128,8 @@ machine_load(Mod, File, Opts) ->
%%% loaded from some other place than current directory.
%%% Now, loading from other than current directory is supposed to work.
%%% so this function does nothing special.
-check_load({error, R}, _) -> {error, R};
-check_load(_, X) -> {ok, X}.
+check_load({error, _R} = Error, _) -> Error;
+check_load(_, Mod) -> {ok, Mod}.
%% Compile a list of modules
%% enables the nice unix shell cmd
@@ -128,6 +138,8 @@ check_load(_, X) -> {ok, X}.
%% with constant c2 defined, c1=v1 (v1 must be a term!), include dir
%% IDir, outdir ODir.
+-spec lc([erl_compile:cmd_line_arg()]) -> 'ok' | 'error'.
+
lc(Args) ->
case catch split(Args, [], []) of
error -> error;
@@ -145,7 +157,7 @@ lc_batch() ->
io:format("Error: no files to compile~n"),
halt(1).
--spec lc_batch([_]) -> no_return().
+-spec lc_batch([erl_compile:cmd_line_arg()]) -> no_return().
lc_batch(Args) ->
try split(Args, [], []) of
@@ -191,8 +203,13 @@ make_term(Str) ->
throw(error)
end.
+-spec nc(file:name()) -> {'ok', module()} | 'error'.
+
nc(File) -> nc(File, []).
+-spec nc(file:name(), [compile:option()] | compile:option()) ->
+ {'ok', module} | 'error'.
+
nc(File, Opts0) when is_list(Opts0) ->
Opts = Opts0 ++ [report_errors, report_warnings],
case compile:file(File, Opts) of
@@ -215,26 +232,37 @@ nc(File, Opt) when is_atom(Opt) ->
%% l(Mod)
%% Reload module Mod from file of same name
+-spec l(module()) -> code:load_ret().
l(Mod) ->
code:purge(Mod),
code:load_file(Mod).
%% Network version of l/1
+%%-spec nl(module()) ->
nl(Mod) ->
case code:get_object_code(Mod) of
{_Module, Bin, Fname} ->
- rpc:eval_everywhere(code,load_binary,[Mod,Fname,Bin]);
+ rpc:eval_everywhere(code, load_binary, [Mod, Fname, Bin]);
Other ->
Other
end.
+-spec i() -> 'ok'.
+
i() -> i(processes()).
+
+-spec ni() -> 'ok'.
+
ni() -> i(all_procs()).
+-spec i([pid()]) -> 'ok'.
+
i(Ps) ->
i(Ps, length(Ps)).
+-spec i([pid()], non_neg_integer()) -> 'ok'.
+
i(Ps, N) when N =< 100 ->
iformat("Pid", "Initial Call", "Heap", "Reds",
"Msgs"),
@@ -275,7 +303,6 @@ paged_i(Ps, Acc, N, Page) ->
paged_i([], NewAcc, 0, Page)
end.
-
choice(F) ->
case get_line('(c)ontinue (q)uit -->', "c\n") of
"c\n" ->
@@ -285,7 +312,6 @@ choice(F) ->
_ ->
choice(F)
end.
-
get_line(P, Default) ->
case io:get_line(P) of
@@ -305,7 +331,6 @@ mfa_string({M,F,A}) ->
mfa_string(X) ->
w(X).
-
display_info(Pid) ->
case pinfo(Pid) of
undefined -> {0,0,0,0};
@@ -317,7 +342,7 @@ display_info(Pid) ->
Other ->
Other
end,
- Reds = fetch(reductions, Info),
+ Reds = fetch(reductions, Info),
LM = length(fetch(messages, Info)),
HS = fetch(heap_size, Info),
SS = fetch(stack_size, Info),
@@ -364,21 +389,30 @@ pinfo(Pid) ->
end.
fetch(Key, Info) ->
- case keysearch(Key, 1, Info) of
- {value, {_, Val}} -> Val;
+ case lists:keyfind(Key, 1, Info) of
+ {_, Val} -> Val;
false -> 0
end.
-pid(X,Y,Z) ->
+-spec pid(non_neg_integer(), non_neg_integer(), non_neg_integer()) -> pid().
+
+pid(X, Y, Z) ->
list_to_pid("<" ++ integer_to_list(X) ++ "." ++
integer_to_list(Y) ++ "." ++
integer_to_list(Z) ++ ">").
-i(X,Y,Z) -> pinfo(pid(X,Y,Z)).
+-spec i(non_neg_integer(), non_neg_integer(), non_neg_integer()) ->
+ [{atom(), term()}].
+
+i(X, Y, Z) -> pinfo(pid(X, Y, Z)).
+
+-spec q() -> no_return().
q() ->
init:stop().
+-spec bt(pid()) -> 'ok' | 'undefined'.
+
bt(Pid) ->
case catch erlang:process_display(Pid, backtrace) of
{'EXIT', _} ->
@@ -387,6 +421,8 @@ bt(Pid) ->
ok
end.
+-spec m() -> 'ok'.
+
m() ->
mformat("Module", "File"),
foreach(fun ({Mod,File}) -> mformat(Mod, File) end, sort(code:all_loaded())).
@@ -414,8 +450,8 @@ error(Fmt, Args) ->
f_p_e(P, F) ->
case file:path_eval(P, F) of
- {error, enoent} ->
- {error, enoent};
+ {error, enoent} = Enoent ->
+ Enoent;
{error, E={Line, _Mod, _Term}} ->
error("file:path_eval(~p,~p): error on line ~p: ~s~n",
[P, F, Line, file:format_error(E)]),
@@ -438,10 +474,11 @@ bi(I) ->
%%
%% Short and nice form of module info
%%
+-spec m(module()) -> 'ok'.
m(M) ->
L = M:module_info(),
- {value,{exports,E}} = keysearch(exports, 1, L),
+ {exports,E} = lists:keyfind(exports, 1, L),
Time = get_compile_time(L),
COpts = get_compile_options(L),
format("Module ~w compiled: ",[M]), print_time(Time),
@@ -470,10 +507,10 @@ get_compile_options(L) ->
end.
get_compile_info(L, Tag) ->
- case keysearch(compile, 1, L) of
- {value, {compile, I}} ->
- case keysearch(Tag, 1, I) of
- {value, {Tag, Val}} -> {ok,Val};
+ case lists:keyfind(compile, 1, L) of
+ {compile, I} ->
+ case lists:keyfind(Tag, 1, I) of
+ {Tag, Val} -> {ok,Val};
false -> error
end;
false -> error
@@ -523,6 +560,8 @@ month(11) -> "November";
month(12) -> "December".
%% Just because we can't eval receive statements...
+-spec flush() -> 'ok'.
+
flush() ->
receive
X ->
@@ -533,9 +572,13 @@ flush() ->
end.
%% Print formatted info about all registered names in the system
+-spec nregs() -> 'ok'.
+
nregs() ->
foreach(fun (N) -> print_node_regs(N) end, all_regs()).
+-spec regs() -> 'ok'.
+
regs() ->
print_node_regs({node(),registered()}).
@@ -609,6 +652,8 @@ portformat(Name, Id, Cmd) ->
%% cd(Directory)
%% These are just wrappers around the file:get/set_cwd functions.
+-spec pwd() -> 'ok'.
+
pwd() ->
case file:get_cwd() of
{ok, Str} ->
@@ -617,6 +662,8 @@ pwd() ->
ok = io:format("Cannot determine current directory\n")
end.
+-spec cd(file:name()) -> 'ok'.
+
cd(Dir) ->
file:set_cwd(Dir),
pwd().
@@ -625,9 +672,13 @@ cd(Dir) ->
%% ls(Directory)
%% The strategy is to print in fixed width files.
+-spec ls() -> 'ok'.
+
ls() ->
ls(".").
+-spec ls(file:name()) -> 'ok'.
+
ls(Dir) ->
case file:list_dir(Dir) of
{ok, Entries} ->
@@ -660,24 +711,31 @@ w(X) ->
%% memory/[0,1]
%%
-memory() -> erlang:memory().
+-spec memory() -> [{atom(), non_neg_integer()}].
+
+memory() -> erlang:memory().
+
+-spec memory(atom()) -> non_neg_integer()
+ ; ([atom()]) -> [{atom(), non_neg_integer()}].
+
memory(TypeSpec) -> erlang:memory(TypeSpec).
%%
%% Cross Reference Check
%%
-
+%%-spec xm(module() | file:filename()) -> xref:m/1 return
xm(M) ->
appcall(tools, xref, m, [M]).
%%
%% Call yecc
%%
-
+%%-spec y(file:name()) -> yecc:file/2 return
y(File) -> y(File, []).
+%%-spec y(file:name(), [yecc:option()]) -> yecc:file/2 return
y(File, Opts) ->
- appcall(parsetools, yecc, file, [File,Opts]).
+ appcall(parsetools, yecc, file, [File, Opts]).
%%
@@ -699,4 +757,3 @@ appcall(App, M, F, Args) ->
erlang:raise(error, undef, Stk)
end
end.
-
diff --git a/lib/stdlib/src/dets.erl b/lib/stdlib/src/dets.erl
index 7f1c13770b..4584b8184f 100644
--- a/lib/stdlib/src/dets.erl
+++ b/lib/stdlib/src/dets.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(dets).
@@ -88,6 +88,7 @@
%% Not documented, or not ready for publication.
-export([lookup_keys/2]).
+-export_type([tab_name/0]).
-compile({inline, [{einval,2},{badarg,2},{undefined,1},
{badarg_exit,2},{lookup_reply,2}]}).
diff --git a/lib/stdlib/src/dets_sup.erl b/lib/stdlib/src/dets_sup.erl
index 5c6caa787d..8ea2ba9b3f 100644
--- a/lib/stdlib/src/dets_sup.erl
+++ b/lib/stdlib/src/dets_sup.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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(dets_sup).
@@ -22,9 +22,16 @@
-export([start_link/0, init/1]).
+-spec start_link() -> {'ok', pid()} | 'ignore' | {'error', term()}.
+
start_link() ->
supervisor:start_link({local, dets_sup}, dets_sup, []).
+-spec init([]) ->
+ {'ok', {{'simple_one_for_one', 4, 3600},
+ [{'dets', {'dets', 'istart_link', []},
+ 'temporary', 30000, 'worker', ['dets']}]}}.
+
init([]) ->
SupFlags = {simple_one_for_one, 4, 3600},
Child = {dets, {dets, istart_link, []}, temporary, 30000, worker, [dets]},
diff --git a/lib/stdlib/src/digraph.erl b/lib/stdlib/src/digraph.erl
index 9bdea671a9..b5f52da921 100644
--- a/lib/stdlib/src/digraph.erl
+++ b/lib/stdlib/src/digraph.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(digraph).
@@ -36,6 +36,8 @@
-export([get_short_path/3, get_short_cycle/2]).
+-export_type([d_type/0, vertex/0]).
+
-record(digraph, {vtab = notable :: ets:tab(),
etab = notable :: ets:tab(),
ntab = notable :: ets:tab(),
diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl
index 6cb441dbed..026bd9038f 100644
--- a/lib/stdlib/src/edlin.erl
+++ b/lib/stdlib/src/edlin.erl
@@ -24,6 +24,7 @@
-export([init/0,start/1,edit_line/2,prefix_arg/1]).
-export([erase_line/1,erase_inp/1,redraw_line/1]).
-export([length_before/1,length_after/1,prompt/1]).
+-export([current_line/1]).
%%-export([expand/1]).
-export([edit_line1/2]).
@@ -421,6 +422,7 @@ over_paren_auto([], _, _, _) ->
%% length_before(Line)
%% length_after(Line)
%% prompt(Line)
+%% current_line(Line)
%% Various functions for accessing bits of a line.
erase_line({line,Pbs,{Bef,Aft},_}) ->
@@ -447,6 +449,9 @@ length_after({line,_,{_Bef,Aft},_}) ->
prompt({line,Pbs,_,_}) ->
Pbs.
+current_line({line,_,{Bef, Aft},_}) ->
+ reverse(Bef, Aft ++ "\n").
+
%% %% expand(CurrentBefore) ->
%% %% {yes,Expansion} | no
%% %% Try to expand the word before as either a module name or a function
diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl
index f144cbb938..81b2431f40 100644
--- a/lib/stdlib/src/epp.erl
+++ b/lib/stdlib/src/epp.erl
@@ -111,6 +111,8 @@ format_error({bad,W}) ->
io_lib:format("badly formed '~s'", [W]);
format_error(missing_parenthesis) ->
io_lib:format("badly formed define: missing closing right parenthesis",[]);
+format_error(premature_end) ->
+ "premature end";
format_error({call,What}) ->
io_lib:format("illegal macro call '~s'",[What]);
format_error({undefined,M,none}) ->
@@ -163,7 +165,7 @@ parse_file(Epp) ->
case normalize_typed_record_fields(Fields) of
{typed, NewFields} ->
[{attribute, La, record, {Record, NewFields}},
- {attribute, La, type,
+ {attribute, La, type,
{{record, Record}, Fields, []}}
|parse_file(Epp)];
not_typed ->
@@ -188,7 +190,7 @@ normalize_typed_record_fields([], NewFields, Typed) ->
true -> {typed, lists:reverse(NewFields)};
false -> not_typed
end;
-normalize_typed_record_fields([{typed_record_field,Field,_}|Rest],
+normalize_typed_record_fields([{typed_record_field,Field,_}|Rest],
NewFields, _Typed) ->
normalize_typed_record_fields(Rest, [Field|NewFields], true);
normalize_typed_record_fields([Field|Rest], NewFields, Typed) ->
@@ -324,7 +326,7 @@ wait_req_scan(St) ->
wait_req_skip(St, Sis) ->
From = wait_request(St),
skip_toks(From, St, Sis).
-
+
%% enter_file(Path, FileName, IncludeToken, From, EppState)
%% leave_file(From, EppState)
%% Handle entering and leaving included files. Notify caller when the
@@ -380,16 +382,16 @@ file_name(N) when is_atom(N) ->
leave_file(From, St) ->
case St#epp.istk of
- [I|Cis] ->
+ [I|Cis] ->
epp_reply(From,
- {error,{St#epp.location,epp,
+ {error,{St#epp.location,epp,
{illegal,"unterminated",I}}}),
leave_file(wait_request(St),St#epp{istk=Cis});
[] ->
case St#epp.sstk of
[OldSt|Sts] ->
close_file(St),
- enter_file_reply(From, OldSt#epp.name,
+ enter_file_reply(From, OldSt#epp.name,
OldSt#epp.location, OldSt#epp.location),
Ms = dict:store({atom,'FILE'},
{none,
@@ -491,9 +493,9 @@ scan_extends(_Ts, _As, Ms) -> Ms.
%% scan_define(Tokens, DefineToken, From, EppState)
-scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{',',_Lc}|Toks], _Def, From, St)
+scan_define([{'(',_Lp},{Type,_Lm,M}=Mac,{',',Lc}|Toks], _Def, From, St)
when Type =:= atom; Type =:= var ->
- case catch macro_expansion(Toks) of
+ case catch macro_expansion(Toks, Lc) of
Expansion when is_list(Expansion) ->
case dict:find({atom,M}, St#epp.macs) of
{ok, Defs} when is_list(Defs) ->
@@ -608,7 +610,7 @@ scan_undef(_Toks, Undef, From, St) ->
%% scan_include(Tokens, IncludeToken, From, St)
-scan_include([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}], Inc,
+scan_include([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}], Inc,
From, St) ->
NewName = expand_var(NewName0),
enter_file(St#epp.path, NewName, Inc, From, St);
@@ -644,7 +646,7 @@ scan_include_lib([{'(',_Llp},{string,_Lf,NewName0},{')',_Lrp},{dot,_Ld}],
case file:open(LibName, [read]) of
{ok,NewF} ->
ExtraPath = [filename:dirname(LibName)],
- wait_req_scan(enter_file2(NewF, LibName, From,
+ wait_req_scan(enter_file2(NewF, LibName, From,
St, Loc, ExtraPath));
{error,_E2} ->
epp_reply(From,
@@ -773,7 +775,7 @@ scan_file(_Toks, Tf, From, St) ->
new_location(Ln, Le, Lf) when is_integer(Lf) ->
Ln+(Le-Lf);
-new_location(Ln, {Le,_}, {Lf,_}) ->
+new_location(Ln, {Le,_}, {Lf,_}) ->
{Ln+(Le-Lf),1}.
%% skip_toks(From, EppState, SkipIstack)
@@ -814,22 +816,23 @@ skip_else(_Else, From, St, Sis) ->
skip_toks(From, St, Sis).
%% macro_pars(Tokens, ArgStack)
-%% macro_expansion(Tokens)
+%% macro_expansion(Tokens, Line)
%% Extract the macro parameters and the expansion from a macro definition.
-macro_pars([{')',_Lp}, {',',_Ld}|Ex], Args) ->
- {ok, {lists:reverse(Args), macro_expansion(Ex)}};
-macro_pars([{var,_,Name}, {')',_Lp}, {',',_Ld}|Ex], Args) ->
+macro_pars([{')',_Lp}, {',',Ld}|Ex], Args) ->
+ {ok, {lists:reverse(Args), macro_expansion(Ex, Ld)}};
+macro_pars([{var,_,Name}, {')',_Lp}, {',',Ld}|Ex], Args) ->
false = lists:member(Name, Args), %Prolog is nice
- {ok, {lists:reverse([Name|Args]), macro_expansion(Ex)}};
+ {ok, {lists:reverse([Name|Args]), macro_expansion(Ex, Ld)}};
macro_pars([{var,_L,Name}, {',',_}|Ts], Args) ->
- false = lists:member(Name, Args),
+ false = lists:member(Name, Args),
macro_pars(Ts, [Name|Args]).
-macro_expansion([{')',_Lp},{dot,_Ld}]) -> [];
-macro_expansion([{dot,Ld}]) -> throw({error,Ld,missing_parenthesis});
-macro_expansion([T|Ts]) ->
- [T|macro_expansion(Ts)].
+macro_expansion([{')',_Lp},{dot,_Ld}], _L0) -> [];
+macro_expansion([{dot,Ld}], _L0) -> throw({error,Ld,missing_parenthesis});
+macro_expansion([T|Ts], _L0) ->
+ [T|macro_expansion(Ts, element(2, T))];
+macro_expansion([], L0) -> throw({error,L0,premature_end}).
%% expand_macros(Tokens, Macros)
%% expand_macro(Tokens, MacroToken, RestTokens)
@@ -1084,11 +1087,11 @@ epp_reply(From, Rep) ->
wait_epp_reply(Epp, Mref) ->
receive
- {epp_reply,Epp,Rep} ->
+ {epp_reply,Epp,Rep} ->
erlang:demonitor(Mref),
receive {'DOWN',Mref,_,_,_} -> ok after 0 -> ok end,
Rep;
- {'DOWN',Mref,_,_,E} ->
+ {'DOWN',Mref,_,_,E} ->
receive {epp_reply,Epp,Rep} -> Rep
after 0 -> exit(E)
end
@@ -1145,7 +1148,7 @@ get_line({Line,_Column}) ->
%% mainly aimed at yecc, the parser generator, which uses the -file
%% attribute to get correct lines in messages referring to code
%% supplied by the user (actions etc in .yrl files).
-%%
+%%
%% In a perfect world (read: perfectly implemented applications such
%% as Xref, Cover, Debugger, etc.) it would not be necessary to
%% distinguish -file attributes from epp and the input file. The
@@ -1165,7 +1168,7 @@ get_line({Line,_Column}) ->
%% have been output by epp (corresponding to -include and
%% -include_lib) are kept, but the user's -file attributes are
%% removed. This seems sufficient for now.
-%%
+%%
%% It turns out to be difficult to distinguish -file attributes in the
%% input file from the ones added by epp unless some action is taken.
%% The (less than perfect) solution employed is to let epp assign
@@ -1177,7 +1180,7 @@ get_line({Line,_Column}) ->
interpret_file_attribute(Forms) ->
interpret_file_attr(Forms, 0, []).
-interpret_file_attr([{attribute,Loc,file,{File,Line}}=Form | Forms],
+interpret_file_attr([{attribute,Loc,file,{File,Line}}=Form | Forms],
Delta, Fs) ->
{line, L} = erl_scan:attributes_info(Loc, line),
if
diff --git a/lib/stdlib/src/erl_compile.erl b/lib/stdlib/src/erl_compile.erl
index d9d15e05f8..abff37e4bc 100644
--- a/lib/stdlib/src/erl_compile.erl
+++ b/lib/stdlib/src/erl_compile.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(erl_compile).
@@ -23,6 +23,8 @@
-export([compile_cmdline/1]).
+-export_type([cmd_line_arg/0]).
+
%% Mapping from extension to {M,F} to run the correct compiler.
compiler(".erl") -> {compile, compile};
diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl
index a38b7639d8..61ce41f714 100644
--- a/lib/stdlib/src/erl_expand_records.erl
+++ b/lib/stdlib/src/erl_expand_records.erl
@@ -95,8 +95,9 @@ forms([F | Fs0], St0) ->
forms([], St) -> {[],St}.
clauses([{clause,Line,H0,G0,B0} | Cs0], St0) ->
- {H,St1} = head(H0, St0),
- {G,St2} = guard(G0, St1),
+ {H1,St1} = head(H0, St0),
+ {G1,St2} = guard(G0, St1),
+ {H,G} = optimize_is_record(H1, G1, St2),
{B,St3} = exprs(B0, St2),
{Cs,St4} = clauses(Cs0, St3),
{[{clause,Line,H,G,B} | Cs],St4};
@@ -800,5 +801,137 @@ imported(F, A, St) ->
error -> no
end.
+%%%
+%%% Replace is_record/3 in guards with matching if possible.
+%%%
+
+optimize_is_record(H0, G0, #exprec{compile=Opts}) ->
+ case opt_rec_vars(G0) of
+ [] ->
+ {H0,G0};
+ Rs0 ->
+ case lists:member(no_is_record_optimization, Opts) of
+ true ->
+ {H0,G0};
+ false ->
+ {H,Rs} = opt_pattern_list(H0, Rs0),
+ G = opt_remove(G0, Rs),
+ {H,G}
+ end
+ end.
+
+
+%% opt_rec_vars(Guards) -> Vars.
+%% Search through the guard expression, looking for
+%% variables referenced in those is_record/3 calls that
+%% will fail the entire guard if they evaluate to 'false'
+%%
+%% In the following code
+%%
+%% f(X, Y, Z) when is_record(X, r1) andalso
+%% (is_record(Y, r2) orelse is_record(Z, r3))
+%%
+%% the entire guard will be false if the record test for
+%% X fails, and the clause can be rewritten to:
+%%
+%% f({r1,...}=X, Y, Z) when true andalso
+%% (is_record(Y, r2) or is_record(Z, r3))
+%%
+opt_rec_vars([G|Gs]) ->
+ Rs = opt_rec_vars_1(G, orddict:new()),
+ opt_rec_vars(Gs, Rs);
+opt_rec_vars([]) -> orddict:new().
+
+opt_rec_vars([G|Gs], Rs0) ->
+ Rs1 = opt_rec_vars_1(G, orddict:new()),
+ Rs = ordsets:intersection(Rs0, Rs1),
+ opt_rec_vars(Gs, Rs);
+opt_rec_vars([], Rs) -> Rs.
+
+opt_rec_vars_1([T|Ts], Rs0) ->
+ Rs = opt_rec_vars_2(T, Rs0),
+ opt_rec_vars_1(Ts, Rs);
+opt_rec_vars_1([], Rs) -> Rs.
+
+opt_rec_vars_2({op,_,'and',A1,A2}, Rs) ->
+ opt_rec_vars_1([A1,A2], Rs);
+opt_rec_vars_2({op,_,'andalso',A1,A2}, Rs) ->
+ opt_rec_vars_1([A1,A2], Rs);
+opt_rec_vars_2({op,_,'orelse',Arg,{atom,_,fail}}, Rs) ->
+ %% Since the second argument guarantees failure,
+ %% it is safe to inspect the first argument.
+ opt_rec_vars_2(Arg, Rs);
+opt_rec_vars_2({call,_,{remote,_,{atom,_,erlang},{atom,_,is_record}},
+ [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}, Rs) ->
+ orddict:store(V, {Tag,Sz}, Rs);
+opt_rec_vars_2({call,_,{atom,_,is_record},
+ [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}, Rs) ->
+ orddict:store(V, {Tag,Sz}, Rs);
+opt_rec_vars_2(_, Rs) -> Rs.
+
+opt_pattern_list(Ps, Rs) ->
+ opt_pattern_list(Ps, Rs, []).
+
+opt_pattern_list([P0|Ps], Rs0, Acc) ->
+ {P,Rs} = opt_pattern(P0, Rs0),
+ opt_pattern_list(Ps, Rs, [P|Acc]);
+opt_pattern_list([], Rs, Acc) ->
+ {reverse(Acc),Rs}.
+
+opt_pattern({var,_,V}=Var, Rs0) ->
+ case orddict:find(V, Rs0) of
+ {ok,{Tag,Sz}} ->
+ Rs = orddict:store(V, {remove,Tag,Sz}, Rs0),
+ {opt_var(Var, Tag, Sz),Rs};
+ _ ->
+ {Var,Rs0}
+ end;
+opt_pattern({cons,Line,H0,T0}, Rs0) ->
+ {H,Rs1} = opt_pattern(H0, Rs0),
+ {T,Rs} = opt_pattern(T0, Rs1),
+ {{cons,Line,H,T},Rs};
+opt_pattern({tuple,Line,Es0}, Rs0) ->
+ {Es,Rs} = opt_pattern_list(Es0, Rs0),
+ {{tuple,Line,Es},Rs};
+opt_pattern({match,Line,Pa0,Pb0}, Rs0) ->
+ {Pa,Rs1} = opt_pattern(Pa0, Rs0),
+ {Pb,Rs} = opt_pattern(Pb0, Rs1),
+ {{match,Line,Pa,Pb},Rs};
+opt_pattern(P, Rs) -> {P,Rs}.
+
+opt_var({var,Line,_}=Var, Tag, Sz) ->
+ Rp = record_pattern(2, -1, ignore, Sz, Line, [{atom,Line,Tag}]),
+ {match,Line,{tuple,Line,Rp},Var}.
+
+opt_remove(Gs, Rs) ->
+ [opt_remove_1(G, Rs) || G <- Gs].
+
+opt_remove_1(Ts, Rs) ->
+ [opt_remove_2(T, Rs) || T <- Ts].
+
+opt_remove_2({op,L,'and'=Op,A1,A2}, Rs) ->
+ {op,L,Op,opt_remove_2(A1, Rs),opt_remove_2(A2, Rs)};
+opt_remove_2({op,L,'andalso'=Op,A1,A2}, Rs) ->
+ {op,L,Op,opt_remove_2(A1, Rs),opt_remove_2(A2, Rs)};
+opt_remove_2({op,L,'orelse',A1,A2}, Rs) ->
+ {op,L,'orelse',opt_remove_2(A1, Rs),A2};
+opt_remove_2({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}},
+ [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}=A, Rs) ->
+ case orddict:find(V, Rs) of
+ {ok,{remove,Tag,Sz}} ->
+ {atom,Line,true};
+ _ ->
+ A
+ end;
+opt_remove_2({call,Line,{atom,_,is_record},
+ [{var,_,V},{atom,_,Tag},{integer,_,Sz}]}=A, Rs) ->
+ case orddict:find(V, Rs) of
+ {ok,{remove,Tag,Sz}} ->
+ {atom,Line,true};
+ _ ->
+ A
+ end;
+opt_remove_2(A, _) -> A.
+
neg_line(L) ->
erl_parse:set_line(L, fun(Line) -> -abs(Line) end).
diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl
index 16173d8210..bf6e5bc5ca 100644
--- a/lib/stdlib/src/erl_internal.erl
+++ b/lib/stdlib/src/erl_internal.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-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(erl_internal).
@@ -48,7 +48,7 @@
%%
-export([bif/2,bif/3,guard_bif/2,
- type_test/2,new_type_test/2,old_type_test/2]).
+ type_test/2,new_type_test/2,old_type_test/2,old_bif/2]).
-export([arith_op/2,bool_op/2,comp_op/2,list_op/2,send_op/2,op_type/2]).
%%---------------------------------------------------------------------------
@@ -87,6 +87,8 @@ guard_bif(is_reference, 1) -> true;
guard_bif(is_tuple, 1) -> true;
guard_bif(is_record, 2) -> true;
guard_bif(is_record, 3) -> true;
+guard_bif(binary_part, 2) -> true;
+guard_bif(binary_part, 3) -> true;
guard_bif(Name, A) when is_atom(Name), is_integer(A) -> false.
%% Erlang type tests.
@@ -229,11 +231,14 @@ bif(apply, 2) -> true;
bif(apply, 3) -> true;
bif(atom_to_binary, 2) -> true;
bif(atom_to_list, 1) -> true;
+bif(binary_part, 2) -> true;
+bif(binary_part, 3) -> true;
bif(binary_to_atom, 2) -> true;
bif(binary_to_existing_atom, 2) -> true;
bif(binary_to_list, 1) -> true;
bif(binary_to_list, 3) -> true;
bif(binary_to_term, 1) -> true;
+bif(binary_to_term, 2) -> true;
bif(bitsize, 1) -> true;
bif(bit_size, 1) -> true;
bif(bitstring_to_list, 1) -> true;
@@ -294,6 +299,8 @@ bif(list_to_pid, 1) -> true;
bif(list_to_tuple, 1) -> true;
bif(load_module, 2) -> true;
bif(make_ref, 0) -> true;
+bif(max,2) -> true;
+bif(min,2) -> true;
bif(module_loaded, 1) -> true;
bif(monitor_node, 2) -> true;
bif(node, 0) -> true;
@@ -305,6 +312,7 @@ bif(open_port, 2) -> true;
bif(pid_to_list, 1) -> true;
bif(port_close, 1) -> true;
bif(port_command, 2) -> true;
+bif(port_command, 3) -> true;
bif(port_connect, 2) -> true;
bif(port_control, 3) -> true;
bif(pre_loaded, 0) -> true;
@@ -349,3 +357,134 @@ bif(unlink, 1) -> true;
bif(unregister, 1) -> true;
bif(whereis, 1) -> true;
bif(Name, A) when is_atom(Name), is_integer(A) -> false.
+
+-spec old_bif(Name::atom(), Arity::arity()) -> boolean().
+%% Returns true if erlang:Name/Arity is an old (pre R14) auto-imported BIF, false otherwise.
+%% Use erlang:is_bultin(Mod, Name, Arity) to find whether a function is a BIF
+%% (meaning implemented in C) or not.
+
+old_bif(abs, 1) -> true;
+old_bif(apply, 2) -> true;
+old_bif(apply, 3) -> true;
+old_bif(atom_to_binary, 2) -> true;
+old_bif(atom_to_list, 1) -> true;
+old_bif(binary_to_atom, 2) -> true;
+old_bif(binary_to_existing_atom, 2) -> true;
+old_bif(binary_to_list, 1) -> true;
+old_bif(binary_to_list, 3) -> true;
+old_bif(binary_to_term, 1) -> true;
+old_bif(bitsize, 1) -> true;
+old_bif(bit_size, 1) -> true;
+old_bif(bitstring_to_list, 1) -> true;
+old_bif(byte_size, 1) -> true;
+old_bif(check_process_code, 2) -> true;
+old_bif(concat_binary, 1) -> true;
+old_bif(date, 0) -> true;
+old_bif(delete_module, 1) -> true;
+old_bif(disconnect_node, 1) -> true;
+old_bif(element, 2) -> true;
+old_bif(erase, 0) -> true;
+old_bif(erase, 1) -> true;
+old_bif(exit, 1) -> true;
+old_bif(exit, 2) -> true;
+old_bif(float, 1) -> true;
+old_bif(float_to_list, 1) -> true;
+old_bif(garbage_collect, 0) -> true;
+old_bif(garbage_collect, 1) -> true;
+old_bif(get, 0) -> true;
+old_bif(get, 1) -> true;
+old_bif(get_keys, 1) -> true;
+old_bif(group_leader, 0) -> true;
+old_bif(group_leader, 2) -> true;
+old_bif(halt, 0) -> true;
+old_bif(halt, 1) -> true;
+old_bif(hd, 1) -> true;
+old_bif(integer_to_list, 1) -> true;
+old_bif(iolist_size, 1) -> true;
+old_bif(iolist_to_binary, 1) -> true;
+old_bif(is_alive, 0) -> true;
+old_bif(is_process_alive, 1) -> true;
+old_bif(is_atom, 1) -> true;
+old_bif(is_boolean, 1) -> true;
+old_bif(is_binary, 1) -> true;
+old_bif(is_bitstr, 1) -> true;
+old_bif(is_bitstring, 1) -> true;
+old_bif(is_float, 1) -> true;
+old_bif(is_function, 1) -> true;
+old_bif(is_function, 2) -> true;
+old_bif(is_integer, 1) -> true;
+old_bif(is_list, 1) -> true;
+old_bif(is_number, 1) -> true;
+old_bif(is_pid, 1) -> true;
+old_bif(is_port, 1) -> true;
+old_bif(is_reference, 1) -> true;
+old_bif(is_tuple, 1) -> true;
+old_bif(is_record, 2) -> true;
+old_bif(is_record, 3) -> true;
+old_bif(length, 1) -> true;
+old_bif(link, 1) -> true;
+old_bif(list_to_atom, 1) -> true;
+old_bif(list_to_binary, 1) -> true;
+old_bif(list_to_bitstring, 1) -> true;
+old_bif(list_to_existing_atom, 1) -> true;
+old_bif(list_to_float, 1) -> true;
+old_bif(list_to_integer, 1) -> true;
+old_bif(list_to_pid, 1) -> true;
+old_bif(list_to_tuple, 1) -> true;
+old_bif(load_module, 2) -> true;
+old_bif(make_ref, 0) -> true;
+old_bif(module_loaded, 1) -> true;
+old_bif(monitor_node, 2) -> true;
+old_bif(node, 0) -> true;
+old_bif(node, 1) -> true;
+old_bif(nodes, 0) -> true;
+old_bif(nodes, 1) -> true;
+old_bif(now, 0) -> true;
+old_bif(open_port, 2) -> true;
+old_bif(pid_to_list, 1) -> true;
+old_bif(port_close, 1) -> true;
+old_bif(port_command, 2) -> true;
+old_bif(port_connect, 2) -> true;
+old_bif(port_control, 3) -> true;
+old_bif(pre_loaded, 0) -> true;
+old_bif(process_flag, 2) -> true;
+old_bif(process_flag, 3) -> true;
+old_bif(process_info, 1) -> true;
+old_bif(process_info, 2) -> true;
+old_bif(processes, 0) -> true;
+old_bif(purge_module, 1) -> true;
+old_bif(put, 2) -> true;
+old_bif(register, 2) -> true;
+old_bif(registered, 0) -> true;
+old_bif(round, 1) -> true;
+old_bif(self, 0) -> true;
+old_bif(setelement, 3) -> true;
+old_bif(size, 1) -> true;
+old_bif(spawn, 1) -> true;
+old_bif(spawn, 2) -> true;
+old_bif(spawn, 3) -> true;
+old_bif(spawn, 4) -> true;
+old_bif(spawn_link, 1) -> true;
+old_bif(spawn_link, 2) -> true;
+old_bif(spawn_link, 3) -> true;
+old_bif(spawn_link, 4) -> true;
+old_bif(spawn_monitor, 1) -> true;
+old_bif(spawn_monitor, 3) -> true;
+old_bif(spawn_opt, 2) -> true;
+old_bif(spawn_opt, 3) -> true;
+old_bif(spawn_opt, 4) -> true;
+old_bif(spawn_opt, 5) -> true;
+old_bif(split_binary, 2) -> true;
+old_bif(statistics, 1) -> true;
+old_bif(term_to_binary, 1) -> true;
+old_bif(term_to_binary, 2) -> true;
+old_bif(throw, 1) -> true;
+old_bif(time, 0) -> true;
+old_bif(tl, 1) -> true;
+old_bif(trunc, 1) -> true;
+old_bif(tuple_size, 1) -> true;
+old_bif(tuple_to_list, 1) -> true;
+old_bif(unlink, 1) -> true;
+old_bif(unregister, 1) -> true;
+old_bif(whereis, 1) -> true;
+old_bif(Name, A) when is_atom(Name), is_integer(A) -> false.
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index 94ad560549..077621ac91 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -40,7 +40,7 @@
%% Value.
%% The option handling functions.
--spec bool_option(atom(), atom(), boolean(), [_]) -> boolean().
+-spec bool_option(atom(), atom(), boolean(), [compile:option()]) -> boolean().
bool_option(On, Off, Default, Opts) ->
foldl(fun (Opt, _Def) when Opt =:= On -> true;
@@ -72,6 +72,10 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
%%-define(DEBUGF(X,Y), io:format(X, Y)).
-define(DEBUGF(X,Y), void).
+-type line() :: erl_scan:line(). % a convenient alias
+-type fa() :: {atom(), arity()}. % function+arity
+-type ta() :: {atom(), arity()}. % type+arity
+
%% Usage of records, functions, and imports. The variable table, which
%% is passed on as an argument, holds the usage of variables.
-record(usage, {
@@ -94,9 +98,11 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
mod_imports=dict:new() :: dict(), %Module Imports
compile=[], %Compile flags
records=dict:new() :: dict(), %Record definitions
+ locals=gb_sets:empty() :: gb_set(), %All defined functions (prescanned)
+ no_auto=gb_sets:empty() :: gb_set(), %Functions explicitly not autoimported
defined=gb_sets:empty() :: gb_set(), %Defined fuctions
- on_load=[] :: [{atom(),integer()}], %On-load function
- on_load_line=0 :: integer(), %Line for on_load
+ on_load=[] :: [fa()], %On-load function
+ on_load_line=0 :: line(), %Line for on_load
clashes=[], %Exported functions named as BIFs
not_deprecated=[], %Not considered deprecated
func=[], %Current function
@@ -110,10 +116,11 @@ value_option(Flag, Default, On, OnVal, Off, OffVal, Opts) ->
%outside any fun or lc
xqlc= false :: boolean(), %true if qlc.hrl included
new = false :: boolean(), %Has user-defined 'new/N'
- called= [], %Called functions
+ called= [] :: [{fa(),line()}], %Called functions
usage = #usage{} :: #usage{},
specs = dict:new() :: dict(), %Type specifications
- types = dict:new() :: dict() %Type definitions
+ types = dict:new() :: dict(), %Type definitions
+ exp_types=gb_sets:empty():: gb_set() %Exported types
}).
-type lint_state() :: #lint{}.
@@ -161,6 +168,9 @@ format_error({bad_nowarn_unused_function,{F,A}}) ->
io_lib:format("function ~w/~w undefined", [F,A]);
format_error({bad_nowarn_bif_clash,{F,A}}) ->
io_lib:format("function ~w/~w undefined", [F,A]);
+format_error(disallowed_nowarn_bif_clash) ->
+ io_lib:format("compile directive nowarn_bif_clash is no longer allowed,~n"
+ " - use explicit module names or -compile({no_auto_import, [F/A]})", []);
format_error({bad_nowarn_deprecated_function,{M,F,A}}) ->
io_lib:format("~w:~w/~w is not a deprecated function", [M,F,A]);
format_error({bad_on_load,Term}) ->
@@ -186,13 +196,21 @@ format_error({define_import,{F,A}}) ->
io_lib:format("defining imported function ~w/~w", [F,A]);
format_error({unused_function,{F,A}}) ->
io_lib:format("function ~w/~w is unused", [F,A]);
-format_error({redefine_bif,{F,A}}) ->
- io_lib:format("defining BIF ~w/~w", [F,A]);
format_error({call_to_redefined_bif,{F,A}}) ->
- io_lib:format("call to ~w/~w will call erlang:~w/~w; "
- "not ~w/~w in this module \n"
- " (add an explicit module name to the call to avoid this error)",
- [F,A,F,A,F,A]);
+ io_lib:format("ambiguous call of overridden auto-imported BIF ~w/~w~n"
+ " - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" "
+ "to resolve name clash", [F,A,F,A,F,A]);
+format_error({call_to_redefined_old_bif,{F,A}}) ->
+ io_lib:format("ambiguous call of overridden pre R14 auto-imported BIF ~w/~w~n"
+ " - use erlang:~w/~w or \"-compile({no_auto_import,[~w/~w]}).\" "
+ "to resolve name clash", [F,A,F,A,F,A]);
+format_error({redefine_old_bif_import,{F,A}}) ->
+ io_lib:format("import directive overrides pre R14 auto-imported BIF ~w/~w~n"
+ " - use \"-compile({no_auto_import,[~w/~w]}).\" "
+ "to resolve name clash", [F,A,F,A]);
+format_error({redefine_bif_import,{F,A}}) ->
+ io_lib:format("import directive overrides auto-imported BIF ~w/~w~n"
+ " - use \"-compile({no_auto_import,[~w/~w]}).\" to resolve name clash", [F,A,F,A]);
format_error({deprecated, MFA, ReplacementMFA, Rel}) ->
io_lib:format("~s is deprecated and will be removed in ~s; use ~s",
@@ -213,6 +231,9 @@ format_error(illegal_pattern) -> "illegal pattern";
format_error(illegal_bin_pattern) ->
"binary patterns cannot be matched in parallel using '='";
format_error(illegal_expr) -> "illegal expression";
+format_error({illegal_guard_local_call, {F,A}}) ->
+ io_lib:format("call to local/imported function ~w/~w is illegal in guard",
+ [F,A]);
format_error(illegal_guard_expr) -> "illegal guard expression";
%% --- exports ---
format_error({explicit_export,F,A}) ->
@@ -242,10 +263,10 @@ format_error({untyped_record,T}) ->
format_error({unbound_var,V}) ->
io_lib:format("variable ~w is unbound", [V]);
format_error({unsafe_var,V,{What,Where}}) ->
- io_lib:format("variable ~w unsafe in ~w ~s",
+ io_lib:format("variable ~w unsafe in ~w ~s",
[V,What,format_where(Where)]);
format_error({exported_var,V,{What,Where}}) ->
- io_lib:format("variable ~w exported from ~w ~s",
+ io_lib:format("variable ~w exported from ~w ~s",
[V,What,format_where(Where)]);
format_error({shadowed_var,V,In}) ->
io_lib:format("variable ~w shadowed in ~w", [V,In]);
@@ -290,22 +311,24 @@ format_error({ill_defined_behaviour_callbacks,Behaviour}) ->
%% --- types and specs ---
format_error({singleton_typevar, Name}) ->
io_lib:format("type variable ~w is only used once (is unbound)", [Name]);
+format_error({duplicated_export_type, {T, A}}) ->
+ io_lib:format("type ~w/~w already exported", [T, A]);
format_error({undefined_type, {TypeName, Arity}}) ->
io_lib:format("type ~w~s undefined", [TypeName, gen_type_paren(Arity)]);
format_error({unused_type, {TypeName, Arity}}) ->
io_lib:format("type ~w~s is unused", [TypeName, gen_type_paren(Arity)]);
format_error({new_builtin_type, {TypeName, Arity}}) ->
io_lib:format("type ~w~s is a new builtin type; "
- "its (re)definition is allowed only until the next release",
+ "its (re)definition is allowed only until the next release",
[TypeName, gen_type_paren(Arity)]);
format_error({builtin_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s is a builtin type; it cannot be redefined",
+ io_lib:format("type ~w~s is a builtin type; it cannot be redefined",
[TypeName, gen_type_paren(Arity)]);
format_error({renamed_type, OldName, NewName}) ->
io_lib:format("type ~w() is now called ~w(); "
"please use the new name instead", [OldName, NewName]);
format_error({redefine_type, {TypeName, Arity}}) ->
- io_lib:format("type ~w~s already defined",
+ io_lib:format("type ~w~s already defined",
[TypeName, gen_type_paren(Arity)]);
format_error({type_syntax, Constr}) ->
io_lib:format("bad ~w type", [Constr]);
@@ -354,7 +377,7 @@ pseudolocals() ->
%%
%% Used by erl_eval.erl to check commands.
-%%
+%%
exprs(Exprs, BindingsList) ->
exprs_opt(Exprs, BindingsList, []).
@@ -362,7 +385,7 @@ exprs_opt(Exprs, BindingsList, Opts) ->
{St0,Vs} = foldl(fun({{record,_SequenceNumber,_Name},Attr0}, {St1,Vs1}) ->
Attr = zip_file_and_line(Attr0, "none"),
{attribute_state(Attr, St1),Vs1};
- ({V,_}, {St1,Vs1}) ->
+ ({V,_}, {St1,Vs1}) ->
{St1,[{V,{bound,unused,[]}} | Vs1]}
end, {start("nofile",Opts),[]}, BindingsList),
Vt = orddict:from_list(Vs),
@@ -391,7 +414,7 @@ module(Forms) ->
Opts = compiler_options(Forms),
St = forms(Forms, start("nofile", Opts)),
return_status(St).
-
+
module(Forms, FileName) ->
Opts = compiler_options(Forms),
St = forms(Forms, start(FileName, Opts)),
@@ -506,7 +529,7 @@ pack_errors(Es) ->
%% Sort on line number.
pack_warnings(Ws) ->
- [{File,lists:sort([W || {F,W} <- Ws, F =:= File])} ||
+ [{File,lists:sort([W || {F,W} <- Ws, F =:= File])} ||
File <- lists:usort([F || {F,_} <- Ws])].
%% add_error(ErrorDescriptor, State) -> State'
@@ -516,13 +539,13 @@ pack_warnings(Ws) ->
add_error(E, St) -> St#lint{errors=[{St#lint.file,E}|St#lint.errors]}.
-add_error(FileLine, E, St) ->
+add_error(FileLine, E, St) ->
{File,Location} = loc(FileLine),
add_error({Location,erl_lint,E}, St#lint{file = File}).
add_warning(W, St) -> St#lint{warnings=[{St#lint.file,W}|St#lint.warnings]}.
-add_warning(FileLine, W, St) ->
+add_warning(FileLine, W, St) ->
{File,Location} = loc(FileLine),
add_warning({Location,erl_lint,W}, St#lint{file = File}).
@@ -538,8 +561,12 @@ loc(L) ->
forms(Forms0, St0) ->
Forms = eval_file_attribute(Forms0, St0),
+ Locals = local_functions(Forms),
+ AutoImportSuppressed = auto_import_suppressed(St0#lint.compile),
+ StDeprecated = disallowed_compile_flags(Forms,St0),
%% Line numbers are from now on pairs {File,Line}.
- St1 = includes_qlc_hrl(Forms, St0),
+ St1 = includes_qlc_hrl(Forms, StDeprecated#lint{locals = Locals,
+ no_auto = AutoImportSuppressed}),
St2 = bif_clashes(Forms, St1),
St3 = not_deprecated(Forms, St2),
St4 = foldl(fun form/2, pre_scan(Forms, St3), Forms),
@@ -561,7 +588,7 @@ pre_scan([_ | Fs], St) ->
pre_scan(Fs, St);
pre_scan([], St) ->
St.
-
+
includes_qlc_hrl(Forms, St) ->
%% QLC calls erl_lint several times, sometimes with the compile
%% attribute removed. The file attribute, however, is left as is.
@@ -667,6 +694,8 @@ attribute_state({attribute,L,extends,_M}, St) ->
add_error(L, invalid_extends, St);
attribute_state({attribute,L,export,Es}, St) ->
export(L, Es, St);
+attribute_state({attribute,L,export_type,Es}, St) ->
+ export_type(L, Es, St);
attribute_state({attribute,L,import,Is}, St) ->
import(L, Is, St);
attribute_state({attribute,L,record,{Name,Fields}}, St) ->
@@ -724,27 +753,38 @@ bif_clashes(Forms, St) ->
Clashes = ordsets:subtract(ordsets:from_list(Clashes0), Nowarn),
St#lint{clashes=Clashes}.
--spec is_bif_clash(atom(), byte(), lint_state()) -> boolean().
-
-is_bif_clash(_Name, _Arity, #lint{clashes=[]}) ->
- false;
-is_bif_clash(Name, Arity, #lint{clashes=Clashes}) ->
- ordsets:is_element({Name,Arity}, Clashes).
-
%% not_deprecated(Forms, State0) -> State
not_deprecated(Forms, St0) ->
%% There are no line numbers in St0#lint.compile.
- MFAsL = [{MFA,L} ||
+ MFAsL = [{MFA,L} ||
{attribute, L, compile, Args} <- Forms,
{nowarn_deprecated_function, MFAs0} <- lists:flatten([Args]),
MFA <- lists:flatten([MFAs0])],
Nowarn = [MFA || {MFA,_L} <- MFAsL],
- Bad = [MFAL || {{M,F,A},_L}=MFAL <- MFAsL,
+ Bad = [MFAL || {{M,F,A},_L}=MFAL <- MFAsL,
otp_internal:obsolete(M, F, A) =:= no],
St1 = func_line_warning(bad_nowarn_deprecated_function, Bad, St0),
St1#lint{not_deprecated = ordsets:from_list(Nowarn)}.
+%% The nowarn_bif_clash directive is not only deprecated, it's actually an error from R14A
+disallowed_compile_flags(Forms, St0) ->
+ %% There are (still) no line numbers in St0#lint.compile.
+ Errors0 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} ||
+ {attribute,[{line,{_,L}}],compile,nowarn_bif_clash} <- Forms ],
+ Errors1 = [ {St0#lint.file,{L,erl_lint,disallowed_nowarn_bif_clash}} ||
+ {attribute,[{line,{_,L}}],compile,{nowarn_bif_clash, {_,_}}} <- Forms ],
+ Disabled = (not is_warn_enabled(bif_clash, St0)),
+ Errors = if
+ Disabled andalso Errors0 =:= [] ->
+ [{St0#lint.file,{erl_lint,disallowed_nowarn_bif_clash}} | St0#lint.errors];
+ Disabled ->
+ Errors0 ++ Errors1 ++ St0#lint.errors;
+ true ->
+ Errors1 ++ St0#lint.errors
+ end,
+ St0#lint{errors=Errors}.
+
%% post_traversal_check(Forms, State0) -> State.
%% Do some further checking after the forms have been traversed and
%% data about calls etc. have been collected.
@@ -862,7 +902,7 @@ check_deprecated(Forms, St0) ->
Bad = [{E,L} || {attribute, L, deprecated, Depr} <- Forms,
D <- lists:flatten([Depr]),
E <- depr_cat(D, X, Mod)],
- foldl(fun ({E,L}, St1) ->
+ foldl(fun ({E,L}, St1) ->
add_error(L, E, St1)
end, St0, Bad).
@@ -912,7 +952,7 @@ check_imports(Forms, St0) ->
true ->
Usage = St0#lint.usage,
Unused = ordsets:subtract(St0#lint.imports, Usage#usage.imported),
- Imports = [{{FA,list_to_atom(package_to_string(Mod))},L}
+ Imports = [{{FA,list_to_atom(package_to_string(Mod))},L}
|| {attribute,L,import,{Mod,Fs}} <- Forms,
FA <- lists:usort(Fs)],
Bad = [{FM,L} || FM <- Unused, {FM2,L} <- Imports, FM =:= FM2],
@@ -932,7 +972,7 @@ check_unused_functions(Forms, St0) ->
Opts = St1#lint.compile,
case member(export_all, Opts) orelse
not is_warn_enabled(unused_function, St1) of
- true ->
+ true ->
St1;
false ->
Nowarn = nowarn_function(nowarn_unused_function, Opts),
@@ -1003,12 +1043,13 @@ check_option_functions(Forms, Tag0, Type, St0) ->
{Tag, FAs0} <- lists:flatten([Args]),
Tag0 =:= Tag,
FA <- lists:flatten([FAs0])],
- DefFunctions = gb_sets:to_list(St0#lint.defined) -- pseudolocals(),
+ DefFunctions = (gb_sets:to_list(St0#lint.defined) -- pseudolocals()) ++
+ [{F,A} || {{F,A},_} <- orddict:to_list(St0#lint.imports)],
Bad = [{FA,L} || {FA,L} <- FAsL, not member(FA, DefFunctions)],
func_line_error(Type, Bad, St0).
nowarn_function(Tag, Opts) ->
- ordsets:from_list([FA || {Tag1,FAs} <- Opts,
+ ordsets:from_list([FA || {Tag1,FAs} <- Opts,
Tag1 =:= Tag,
FA <- lists:flatten([FAs])]).
@@ -1048,10 +1089,10 @@ check_unused_records(Forms, St0) ->
%% functions count.
Usage = St0#lint.usage,
UsedRecords = sets:to_list(Usage#usage.used_records),
- URecs = foldl(fun (Used, Recs) ->
- dict:erase(Used, Recs)
+ URecs = foldl(fun (Used, Recs) ->
+ dict:erase(Used, Recs)
end, St0#lint.records, UsedRecords),
- Unused = [{Name,FileLine} ||
+ Unused = [{Name,FileLine} ||
{Name,{FileLine,_Fields}} <- dict:to_list(URecs),
element(1, loc(FileLine)) =:= FirstFile],
foldl(fun ({N,L}, St) ->
@@ -1061,18 +1102,19 @@ check_unused_records(Forms, St0) ->
St0
end.
-%% For storing the import list we use the orddict module.
+%% For storing the import list we use the orddict module.
%% We know an empty set is [].
-%% export(Line, Exports, State) -> State.
+-spec export(line(), [fa()], lint_state()) -> lint_state().
%% Mark functions as exported, also as called from the export line.
export(Line, Es, #lint{exports = Es0, called = Called} = St0) ->
- {Es1,C1,St1} =
+ {Es1,C1,St1} =
foldl(fun (NA, {E,C,St2}) ->
St = case gb_sets:is_element(NA, E) of
true ->
- add_warning(Line, {duplicated_export, NA}, St2);
+ Warn = {duplicated_export,NA},
+ add_warning(Line, Warn, St2);
false ->
St2
end,
@@ -1081,8 +1123,27 @@ export(Line, Es, #lint{exports = Es0, called = Called} = St0) ->
{Es0,Called,St0}, Es),
St1#lint{exports = Es1, called = C1}.
-%% import(Line, Imports, State) -> State.
-%% imported(Name, Arity, State) -> {yes,Module} | no.
+-spec export_type(line(), [ta()], lint_state()) -> lint_state().
+%% Mark types as exported; also mark them as used from the export line.
+
+export_type(Line, ETs, #lint{usage = Usage, exp_types = ETs0} = St0) ->
+ UTs0 = Usage#usage.used_types,
+ {ETs1,UTs1,St1} =
+ foldl(fun (TA, {E,U,St2}) ->
+ St = case gb_sets:is_element(TA, E) of
+ true ->
+ Warn = {duplicated_export_type,TA},
+ add_warning(Line, Warn, St2);
+ false ->
+ St2
+ end,
+ {gb_sets:add_element(TA, E), dict:store(TA, Line, U), St}
+ end,
+ {ETs0,UTs0,St0}, ETs),
+ St1#lint{usage = Usage#usage{used_types = UTs1}, exp_types = ETs1}.
+
+-type import() :: {module(), [fa()]} | module().
+-spec import(line(), import(), lint_state()) -> lint_state().
import(Line, {Mod,Fs}, St) ->
Mod1 = package_to_string(Mod),
@@ -1094,11 +1155,41 @@ import(Line, {Mod,Fs}, St) ->
St#lint{imports=add_imports(list_to_atom(Mod1), Mfs,
St#lint.imports)};
Efs ->
- foldl(fun (Ef, St0) ->
- add_error(Line, {redefine_import,Ef},
- St0)
+ {Err, St1} =
+ foldl(fun ({bif,{F,A},_}, {Err,St0}) ->
+ %% BifClash - import directive
+ Warn = is_warn_enabled(bif_clash, St0)
+ and (not bif_clash_specifically_disabled(St0,{F,A})),
+ AutoImpSup = is_autoimport_suppressed(St0#lint.no_auto,{F,A}),
+ OldBif = erl_internal:old_bif(F,A),
+ {Err,if
+ Warn and (not AutoImpSup) and OldBif ->
+ add_error
+ (Line,
+ {redefine_old_bif_import, {F,A}},
+ St0);
+ Warn and (not AutoImpSup) ->
+ add_warning
+ (Line,
+ {redefine_bif_import, {F,A}},
+ St0);
+ true ->
+ St0
+ end};
+ (Ef, {_Err,St0}) ->
+ {true,add_error(Line,
+ {redefine_import,Ef},
+ St0)}
end,
- St, Efs)
+ {false,St}, Efs),
+ if
+ not Err ->
+ St1#lint{imports=
+ add_imports(list_to_atom(Mod1), Mfs,
+ St#lint.imports)};
+ true ->
+ St1
+ end
end;
false ->
add_error(Line, {bad_module_name, Mod1}, St)
@@ -1141,13 +1232,15 @@ check_imports(_Line, Fs, Is) ->
add_imports(Mod, Fs, Is) ->
foldl(fun (F, Is0) -> orddict:store(F, Mod, Is0) end, Is, Fs).
+-spec imported(atom(), arity(), lint_state()) -> {'yes',module()} | 'no'.
+
imported(F, A, St) ->
case orddict:find({F,A}, St#lint.imports) of
{ok,Mod} -> {yes,Mod};
error -> no
end.
-%% on_load(Line, Val, State) -> State.
+-spec on_load(line(), fa(), lint_state()) -> lint_state().
%% Check an on_load directive and remember it.
on_load(Line, {Name,Arity}=Fa, #lint{on_load=OnLoad0}=St0)
@@ -1179,7 +1272,7 @@ check_on_load(#lint{defined=Defined,on_load=[{_,0}=Fa],
end;
check_on_load(St) -> St.
-%% call_function(Line, Name, Arity, State) -> State.
+-spec call_function(line(), atom(), arity(), lint_state()) -> lint_state().
%% Add to both called and calls.
call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func}=St) ->
@@ -1191,12 +1284,6 @@ call_function(Line, F, A, #lint{usage=Usage0,called=Cd,func=Func}=St) ->
end,
St#lint{called=[{NA,Line}|Cd], usage=Usage}.
-%% is_function_exported(Name, Arity, State) -> false|true.
-
-is_function_exported(Name, Arity, #lint{exports=Exports,compile=Compile}) ->
- gb_sets:is_element({Name,Arity}, Exports) orelse
- member(export_all, Compile).
-
%% function(Line, Name, Arity, Clauses, State) -> State.
function(Line, instance, _Arity, _Cs, St) when St#lint.global_vt =/= [] ->
@@ -1205,7 +1292,7 @@ function(Line, Name, Arity, Cs, St0) ->
St1 = define_function(Line, Name, Arity, St0#lint{func={Name,Arity}}),
clauses(Cs, St1#lint.global_vt, St1).
-%% define_function(Line, Name, Arity, State) -> State.
+-spec define_function(line(), atom(), arity(), lint_state()) -> lint_state().
define_function(Line, Name, Arity, St0) ->
St1 = keyword_warning(Line, Name, St0),
@@ -1215,14 +1302,9 @@ define_function(Line, Name, Arity, St0) ->
add_error(Line, {redefine_function,NA}, St1);
false ->
St2 = St1#lint{defined=gb_sets:add_element(NA, St1#lint.defined)},
- St = case erl_internal:bif(Name, Arity) andalso
- not is_function_exported(Name, Arity, St2) of
- true -> add_warning(Line, {redefine_bif,NA}, St2);
- false -> St2
- end,
- case imported(Name, Arity, St) of
- {yes,_M} -> add_error(Line, {define_import,NA}, St);
- no -> St
+ case imported(Name, Arity, St2) of
+ {yes,_M} -> add_error(Line, {define_import,NA}, St2);
+ no -> St2
end
end.
@@ -1258,7 +1340,7 @@ head([P|Ps], Vt, Old, St0) ->
{vtmerge_pat(Pvt, Psvt),vtmerge_pat(Bvt1,Bvt2),St2};
head([], _Vt, _Env, St) -> {[],[],St}.
-%% pattern(Pattern, VarTable, Old, BinVarTable, State) ->
+%% pattern(Pattern, VarTable, Old, BinVarTable, State) ->
%% {UpdVarTable,BinVarTable,State}.
%% Check pattern return variables. Old is the set of variables used for
%% deciding whether an occurrence is a binding occurrence or a use, and
@@ -1276,7 +1358,7 @@ pattern(P, Vt, St) ->
pattern({var,_Line,'_'}, _Vt, _Old, _Bvt, St) ->
{[],[],St}; %Ignore anonymous variable
-pattern({var,Line,V}, _Vt, Old, Bvt, St) ->
+pattern({var,Line,V}, _Vt, Old, Bvt, St) ->
pat_var(V, Line, Old, Bvt, St);
pattern({char,_Line,_C}, _Vt, _Old, _Bvt, St) -> {[],[],St};
pattern({integer,_Line,_I}, _Vt, _Old, _Bvt, St) -> {[],[],St};
@@ -1294,7 +1376,7 @@ pattern({tuple,_Line,Ps}, Vt, Old, Bvt, St) ->
%%pattern({struct,_Line,_Tag,Ps}, Vt, Old, Bvt, St) ->
%% pattern_list(Ps, Vt, Old, Bvt, St);
pattern({record_index,Line,Name,Field}, _Vt, _Old, _Bvt, St) ->
- {Vt1,St1} =
+ {Vt1,St1} =
check_record(Line, Name, St,
fun (Dfs, St1) ->
pattern_field(Field, Name, Dfs, St1)
@@ -1309,7 +1391,7 @@ pattern({record_field,Line,_,_}=M, _Vt, _Old, _Bvt, St0) ->
end;
pattern({record,Line,Name,Pfs}, Vt, Old, Bvt, St) ->
case dict:find(Name, St#lint.records) of
- {ok,{_Line,Fields}} ->
+ {ok,{_Line,Fields}} ->
St1 = used_record(Name, St),
pattern_fields(Pfs, Name, Fields, Vt, Old, Bvt, St1);
error -> {[],[],add_error(Line, {undefined_record,Name}, St)}
@@ -1369,7 +1451,7 @@ reject_bin_alias({cons,_,H1,T1}, {cons,_,H2,T2}, St0) ->
reject_bin_alias(T1, T2, St);
reject_bin_alias({tuple,_,Es1}, {tuple,_,Es2}, St) ->
reject_bin_alias_list(Es1, Es2, St);
-reject_bin_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2},
+reject_bin_alias({record,_,Name1,Pfs1}, {record,_,Name2,Pfs2},
#lint{records=Recs}=St) ->
case {dict:find(Name1, Recs),dict:find(Name2, Recs)} of
{{ok,{_Line1,Fields1}},{ok,{_Line2,Fields2}}} ->
@@ -1451,7 +1533,7 @@ is_pattern_expr_1({op,_Line,Op,A1,A2}) ->
erl_internal:arith_op(Op, 2) andalso all(fun is_pattern_expr/1, [A1,A2]);
is_pattern_expr_1(_Other) -> false.
-%% pattern_bin([Element], VarTable, Old, BinVarTable, State) ->
+%% pattern_bin([Element], VarTable, Old, BinVarTable, State) ->
%% {UpdVarTable,UpdBinVarTable,State}.
%% Check a pattern group. BinVarTable are used binsize variables.
@@ -1498,7 +1580,7 @@ good_string_size_type(default, Ts) ->
end, Ts);
good_string_size_type(_, _) -> false.
-%% pat_bit_expr(Pattern, OldVarTable, BinVarTable,State) ->
+%% pat_bit_expr(Pattern, OldVarTable, BinVarTable,State) ->
%% {UpdVarTable,UpdBinVarTable,State}.
%% Check pattern bit expression, only allow really valid patterns!
@@ -1513,7 +1595,7 @@ pat_bit_expr(P, _Old, _Bvt, St) ->
false -> {[],[],add_error(element(2, P), illegal_pattern, St)}
end.
-%% pat_bit_size(Size, VarTable, BinVarTable, State) ->
+%% pat_bit_size(Size, VarTable, BinVarTable, State) ->
%% {Value,UpdVarTable,UpdBinVarTable,State}.
%% Check pattern size expression, only allow really valid sizes!
@@ -1596,7 +1678,7 @@ bit_size_check(Line, Size, #bittype{type=Type,unit=Unit}, St) ->
Sz = Unit * Size, %Total number of bits!
St2 = elemtype_check(Line, Type, Sz, St),
{Sz,St2}.
-
+
elemtype_check(_Line, float, 32, St) -> St;
elemtype_check(_Line, float, 64, St) -> St;
elemtype_check(Line, float, _Size, St) ->
@@ -1678,8 +1760,6 @@ gexpr({cons,_Line,H,T}, Vt, St) ->
gexpr_list([H,T], Vt, St);
gexpr({tuple,_Line,Es}, Vt, St) ->
gexpr_list(Es, Vt, St);
-%%gexpr({struct,_Line,_Tag,Es}, Vt, St) ->
-%% gexpr_list(Es, Vt, St);
gexpr({record_index,Line,Name,Field}, _Vt, St) ->
check_record(Line, Name, St,
fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end );
@@ -1710,7 +1790,7 @@ gexpr({call,_Line,{atom,_Lr,is_record},[E,{atom,Ln,Name}]}, Vt, St0) ->
gexpr({call,Line,{atom,_Lr,is_record},[E,R]}, Vt, St0) ->
{Asvt,St1} = gexpr_list([E,R], Vt, St0),
{Asvt,add_error(Line, illegal_guard_expr, St1)};
-gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]},
+gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]},
Vt, St0) ->
gexpr({call,Line,{atom,Lf,is_record},[E,A]}, Vt, St0);
gexpr({call,_Line,{atom,_Lr,is_record},[E,{atom,_,_Name},{integer,_,_}]},
@@ -1725,14 +1805,22 @@ gexpr({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}=Isr},[_,_,_]=Args}
gexpr({call,Line,{atom,_La,F},As}, Vt, St0) ->
{Asvt,St1} = gexpr_list(As, Vt, St0),
A = length(As),
- case erl_internal:guard_bif(F, A) of
+ %% BifClash - Function called in guard
+ case erl_internal:guard_bif(F, A) andalso no_guard_bif_clash(St1,{F,A}) of
true ->
%% Also check that it is auto-imported.
case erl_internal:bif(F, A) of
true -> {Asvt,St1};
false -> {Asvt,add_error(Line, {explicit_export,F,A}, St1)}
end;
- false -> {Asvt,add_error(Line, illegal_guard_expr, St1)}
+ false ->
+ case is_local_function(St1#lint.locals,{F,A}) orelse
+ is_imported_function(St1#lint.imports,{F,A}) of
+ true ->
+ {Asvt,add_error(Line, {illegal_guard_local_call,{F,A}}, St1)};
+ _ ->
+ {Asvt,add_error(Line, illegal_guard_expr, St1)}
+ end
end;
gexpr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, Vt, St0) ->
{Asvt,St1} = gexpr_list(As, Vt, St0),
@@ -1777,7 +1865,7 @@ is_guard_test(E) ->
%% is_guard_test(Expression, Forms) -> boolean().
is_guard_test(Expression, Forms) ->
RecordAttributes = [A || A = {attribute, _, record, _D} <- Forms],
- St0 = foldl(fun(Attr0, St1) ->
+ St0 = foldl(fun(Attr0, St1) ->
Attr = zip_file_and_line(Attr0, "none"),
attribute_state(Attr, St1)
end, start(), RecordAttributes),
@@ -1798,7 +1886,7 @@ is_guard_test2(G, RDs) ->
%% is_guard_expr(Expression) -> boolean().
%% Test if an expression is a guard expression.
-is_guard_expr(E) -> is_gexpr(E, []).
+is_guard_expr(E) -> is_gexpr(E, []).
is_gexpr({var,_L,_V}, _RDs) -> true;
is_gexpr({char,_L,_C}, _RDs) -> true;
@@ -1820,7 +1908,7 @@ is_gexpr({record_field,_L,Rec,_Name,Field}, RDs) ->
is_gexpr({record,L,Name,Inits}, RDs) ->
is_gexpr_fields(Inits, L, Name, RDs);
is_gexpr({bin,_L,Fs}, RDs) ->
- all(fun ({bin_element,_Line,E,Sz,_Ts}) ->
+ all(fun ({bin_element,_Line,E,Sz,_Ts}) ->
is_gexpr(E, RDs) and (Sz =:= default orelse is_gexpr(Sz, RDs))
end, Fs);
is_gexpr({call,_L,{atom,_Lf,F},As}, RDs) ->
@@ -1895,15 +1983,13 @@ expr({bc,_Line,E,Qs}, Vt0, St0) ->
{vtold(Vt,Vt0),St}; %Don't export local variables
expr({tuple,_Line,Es}, Vt, St) ->
expr_list(Es, Vt, St);
-%%expr({struct,Line,Tag,Es}, Vt, St) ->
-%% expr_list(Es, Vt, St);
expr({record_index,Line,Name,Field}, _Vt, St) ->
check_record(Line, Name, St,
fun (Dfs, St1) -> record_field(Field, Name, Dfs, St1) end);
expr({record,Line,Name,Inits}, Vt, St) ->
check_record(Line, Name, St,
- fun (Dfs, St1) ->
- init_fields(Inits, Line, Name, Dfs, Vt, St1)
+ fun (Dfs, St1) ->
+ init_fields(Inits, Line, Name, Dfs, Vt, St1)
end);
expr({record_field,Line,_,_}=M, _Vt, St0) ->
case expand_package(M, St0) of
@@ -1958,8 +2044,11 @@ expr({'fun',Line,Body}, Vt, St) ->
{Bvt, St1} = fun_clauses(Cs, Vt, St),
{vtupdate(Bvt, Vt), St1};
{function,F,A} ->
+ %% BifClash - Fun expression
%% N.B. Only allows BIFs here as well, NO IMPORTS!!
- case erl_internal:bif(F, A) of
+ case ((not is_local_function(St#lint.locals,{F,A})) andalso
+ (erl_internal:bif(F, A) andalso
+ (not is_autoimport_suppressed(St#lint.no_auto,{F,A})))) of
true -> {[],St};
false -> {[],call_function(Line, F, A, St)}
end;
@@ -1969,7 +2058,7 @@ expr({'fun',Line,Body}, Vt, St) ->
expr({call,_Line,{atom,_Lr,is_record},[E,{atom,Ln,Name}]}, Vt, St0) ->
{Rvt,St1} = expr(E, Vt, St0),
{Rvt,exist_record(Ln, Name, St1)};
-expr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]},
+expr({call,Line,{remote,_Lr,{atom,_Lm,erlang},{atom,Lf,is_record}},[E,A]},
Vt, St0) ->
expr({call,Line,{atom,Lf,is_record},[E,A]}, Vt, St0);
expr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,is_record}]},As}, Vt, St) ->
@@ -1992,29 +2081,54 @@ expr({call,Line,{atom,La,F},As}, Vt, St0) ->
St1 = keyword_warning(La, F, St0),
{Asvt,St2} = expr_list(As, Vt, St1),
A = length(As),
- case erl_internal:bif(F, A) of
+ IsLocal = is_local_function(St2#lint.locals,{F,A}),
+ IsAutoBif = erl_internal:bif(F, A),
+ AutoSuppressed = is_autoimport_suppressed(St2#lint.no_auto,{F,A}),
+ Warn = is_warn_enabled(bif_clash, St2) and (not bif_clash_specifically_disabled(St2,{F,A})),
+ Imported = imported(F, A, St2),
+ case ((not IsLocal) andalso (Imported =:= no) andalso
+ IsAutoBif andalso (not AutoSuppressed)) of
true ->
St3 = deprecated_function(Line, erlang, F, As, St2),
- {Asvt,case is_warn_enabled(bif_clash, St3) andalso
- is_bif_clash(F, A, St3) of
- false ->
- St3;
- true ->
- add_error(Line, {call_to_redefined_bif,{F,A}}, St3)
- end};
+ {Asvt,St3};
false ->
- {Asvt,case imported(F, A, St2) of
+ {Asvt,case Imported of
{yes,M} ->
St3 = check_remote_function(Line, M, F, As, St2),
U0 = St3#lint.usage,
Imp = ordsets:add_element({{F,A},M},U0#usage.imported),
St3#lint{usage=U0#usage{imported = Imp}};
no ->
- case {F,A} of
- {record_info,2} ->
+ case {F,A} of
+ {record_info,2} ->
check_record_info_call(Line,La,As,St2);
- N when N =:= St2#lint.func -> St2;
- _ -> call_function(Line, F, A, St2)
+ N ->
+ %% BifClash - function call
+ %% Issue these warnings/errors even if it's a recursive call
+ St3 = if
+ (not AutoSuppressed) andalso IsAutoBif andalso Warn ->
+ case erl_internal:old_bif(F,A) of
+ true ->
+ add_error
+ (Line,
+ {call_to_redefined_old_bif, {F,A}},
+ St2);
+ false ->
+ add_warning
+ (Line,
+ {call_to_redefined_bif, {F,A}},
+ St2)
+ end;
+ true ->
+ St2
+ end,
+ %% ...but don't lint recursive calls
+ if
+ N =:= St3#lint.func ->
+ St3;
+ true ->
+ call_function(Line, F, A, St3)
+ end
end
end}
end;
@@ -2155,7 +2269,7 @@ def_fields(Fs0, Name, St0) ->
foldl(fun ({record_field,Lf,{atom,La,F},V}, {Fs,St}) ->
case exist_field(F, Fs) of
true -> {Fs,add_error(Lf, {redefine_field,Name,F}, St)};
- false ->
+ false ->
St1 = St#lint{recdef_top = true},
{_,St2} = expr(V, [], St1),
%% Warnings and errors found are kept, but
@@ -2306,7 +2420,7 @@ init_fields(Ifs, Line, Name, Dfs, Vt0, St0) ->
Defs = init_fields(Ifs, Line, Dfs),
{_,St2} = check_fields(Defs, Name, Dfs, Vt1, St1, fun expr/3),
{Vt1,St1#lint{usage = St2#lint.usage}}.
-
+
ginit_fields(Ifs, Line, Name, Dfs, Vt0, St0) ->
{Vt1,St1} = check_fields(Ifs, Name, Dfs, Vt0, St0, fun gexpr/3),
Defs = init_fields(Ifs, Line, Dfs),
@@ -2316,7 +2430,7 @@ ginit_fields(Ifs, Line, Name, Dfs, Vt0, St0) ->
IllErrs = [E || {_File,{_Line,erl_lint,illegal_guard_expr}}=E <- Errors],
St4 = St1#lint{usage = Usage, errors = IllErrs ++ St1#lint.errors},
{Vt1,St4}.
-
+
%% Default initializations to be carried out
init_fields(Ifs, Line, Dfs) ->
[ {record_field,Lf,{atom,La,F},copy_expr(Di, Line)} ||
@@ -2394,7 +2508,7 @@ check_type({ann_type, _L, [_Var, Type]}, SeenVars, St) ->
check_type(Type, SeenVars, St);
check_type({paren_type, _L, [Type]}, SeenVars, St) ->
check_type(Type, SeenVars, St);
-check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]},
+check_type({remote_type, L, [{atom, _, Mod}, {atom, _, Name}, Args]},
SeenVars, #lint{module=CurrentMod} = St) ->
St1 =
case (dict:is_key({Name, length(Args)}, default_types())
@@ -2432,7 +2546,7 @@ check_type({type, L, 'fun', [Dom, Range]}, SeenVars, St) ->
check_type({type, -1, product, [Dom, Range]}, SeenVars, St1);
check_type({type, L, range, [From, To]}, SeenVars, St) ->
St1 =
- case {From, To} of
+ case {erl_eval:partial_eval(From), erl_eval:partial_eval(To)} of
{{integer, _, X}, {integer, _, Y}} when X < Y -> St;
_ -> add_error(L, {type_syntax, range}, St)
end,
@@ -2441,8 +2555,8 @@ check_type({type, _L, tuple, any}, SeenVars, St) -> {SeenVars, St};
check_type({type, _L, any}, SeenVars, St) -> {SeenVars, St};
check_type({type, L, binary, [Base, Unit]}, SeenVars, St) ->
St1 =
- case {Base, Unit} of
- {{integer, _, BaseVal},
+ case {erl_eval:partial_eval(Base), erl_eval:partial_eval(Unit)} of
+ {{integer, _, BaseVal},
{integer, _, UnitVal}} when BaseVal >= 0, UnitVal >= 0 -> St;
_ -> add_error(L, {type_syntax, binary}, St)
end,
@@ -2467,7 +2581,13 @@ check_type({type, La, TypeName, Args}, SeenVars, #lint{usage=Usage} = St) ->
UsedTypes = dict:store({TypeName, Arity}, La, OldUsed),
St#lint{usage=Usage#usage{used_types=UsedTypes}}
end,
- check_type({type, -1, product, Args}, SeenVars, St1).
+ check_type({type, -1, product, Args}, SeenVars, St1);
+check_type(I, SeenVars, St) ->
+ case erl_eval:partial_eval(I) of
+ {integer,_ILn,_Integer} -> {SeenVars, St};
+ _Other ->
+ {SeenVars, add_error(element(2, I), {type_syntax, integer}, St)}
+ end.
check_record_types(Line, Name, Fields, SeenVars, St) ->
case dict:find(Name, St#lint.records) of
@@ -2475,12 +2595,12 @@ check_record_types(Line, Name, Fields, SeenVars, St) ->
case lists:all(fun({type, _, field_type, _}) -> true;
(_) -> false
end, Fields) of
- true ->
+ true ->
check_record_types(Fields, Name, DefFields, SeenVars, St, []);
false ->
{SeenVars, add_error(Line, {type_syntax, record}, St)}
end;
- error ->
+ error ->
{SeenVars, add_error(Line, {undefined_record, Name}, St)}
end.
@@ -2563,7 +2683,6 @@ default_types() ->
{set, 0},
{string, 0},
{term, 0},
- {tid, 0},
{timeout, 0},
{var, 1}],
dict:from_list([{T, -1} || T <- DefTypes]).
@@ -2585,7 +2704,6 @@ is_newly_introduced_builtin_type({gb_tree, 0}) -> true; % opaque
is_newly_introduced_builtin_type({iodata, 0}) -> true;
is_newly_introduced_builtin_type({queue, 0}) -> true; % opaque
is_newly_introduced_builtin_type({set, 0}) -> true; % opaque
-is_newly_introduced_builtin_type({tid, 0}) -> true; % opaque
%% R13B01
is_newly_introduced_builtin_type({boolean, 0}) -> true;
is_newly_introduced_builtin_type({Name, _}) when is_atom(Name) -> false.
@@ -2606,7 +2724,7 @@ spec_decl(Line, MFA0, TypeSpecs, St0 = #lint{specs = Specs, module = Mod}) ->
check_specs([FunType|Left], Arity, St0) ->
{FunType1, CTypes} =
case FunType of
- {type, _, bounded_fun, [FT = {type, _, 'fun', _}, Cs]} ->
+ {type, _, bounded_fun, [FT = {type, _, 'fun', _}, Cs]} ->
Types0 = [T || {type, _, constraint, [_, T]} <- Cs],
{FT, lists:append(Types0)};
{type, _, 'fun', _} = FT -> {FT, []}
@@ -2666,10 +2784,12 @@ add_missing_spec_warnings(Forms, St0, Type) ->
add_warning(L, {missing_spec,FA}, St)
end, St0, Warns).
-check_unused_types(Forms, St = #lint{usage=Usage, types=Types}) ->
+check_unused_types(Forms, #lint{usage=Usage, types=Ts, exp_types=ExpTs}=St) ->
case [File || {attribute,_L,file,{File,_Line}} <- Forms] of
[FirstFile|_] ->
- UsedTypes = Usage#usage.used_types,
+ D = Usage#usage.used_types,
+ L = gb_sets:to_list(ExpTs) ++ dict:fetch_keys(D),
+ UsedTypes = gb_sets:from_list(L),
FoldFun =
fun(_Type, -1, AccSt) ->
%% Default type
@@ -2677,19 +2797,18 @@ check_unused_types(Forms, St = #lint{usage=Usage, types=Types}) ->
(Type, FileLine, AccSt) ->
case loc(FileLine) of
{FirstFile, _} ->
- case dict:is_key(Type, UsedTypes) of
+ case gb_sets:is_member(Type, UsedTypes) of
true -> AccSt;
- false ->
- add_warning(FileLine,
- {unused_type, Type},
- AccSt)
+ false ->
+ Warn = {unused_type,Type},
+ add_warning(FileLine, Warn, AccSt)
end;
_ ->
- %% Don't warn about unused types in include file
+ %% No warns about unused types in include files
AccSt
end
end,
- dict:fold(FoldFun, St, Types);
+ dict:fold(FoldFun, St, Ts);
[] ->
St
end.
@@ -2834,7 +2953,7 @@ fun_clause({clause,_Line,H,G,B}, Vt0, St0) ->
%%
%% used variable has been used
%% unused variable has been bound but not used
-%%
+%%
%% Lines is a list of line numbers where the variable was bound.
%%
%% Report variable errors/warnings as soon as possible and then change
@@ -2864,9 +2983,9 @@ pat_var(V, Line, Vt, Bvt, St) ->
case orddict:find(V, Bvt) of
{ok, {bound,_Usage,Ls}} ->
{[],[{V,{bound,used,Ls}}],St};
- error ->
+ error ->
case orddict:find(V, Vt) of
- {ok,{bound,_Usage,Ls}} ->
+ {ok,{bound,_Usage,Ls}} ->
{[{V,{bound,used,Ls}}],[],St};
{ok,{{unsafe,In},_Usage,Ls}} ->
{[{V,{bound,used,Ls}}],[],
@@ -2919,7 +3038,7 @@ pat_binsize_var(V, Line, Vt, Bvt, St) ->
expr_var(V, Line, Vt, St0) ->
case orddict:find(V, Vt) of
- {ok,{bound,_Usage,Ls}} ->
+ {ok,{bound,_Usage,Ls}} ->
{[{V,{bound,used,Ls}}],St0};
{ok,{{unsafe,In},_Usage,Ls}} ->
{[{V,{bound,used,Ls}}],
@@ -2957,7 +3076,7 @@ check_old_unused_vars(Vt, Vt0, St0) ->
warn_unused_vars(U, Vt, St0).
unused_vars(Vt, Vt0, _St0) ->
- U0 = orddict:filter(fun (V, {_State,unused,_Ls}) ->
+ U0 = orddict:filter(fun (V, {_State,unused,_Ls}) ->
case atom_to_list(V) of
"_"++_ -> false;
_ -> true
@@ -2973,7 +3092,7 @@ warn_unused_vars(U, Vt, St0) ->
false -> St0;
true ->
foldl(fun ({V,{_,unused,Ls}}, St) ->
- foldl(fun (L, St2) ->
+ foldl(fun (L, St2) ->
add_warning(L, {unused_var,V},
St2)
end, St, Ls)
@@ -3073,7 +3192,7 @@ vt_no_unsafe(Vt) -> [V || {_,{S,_U,_L}}=V <- Vt,
-ifdef(NOTUSED).
vunion(Vs1, Vs2) -> ordsets:union(vtnames(Vs1), vtnames(Vs2)).
-vunion(Vss) -> foldl(fun (Vs, Uvs) ->
+vunion(Vss) -> foldl(fun (Vs, Uvs) ->
ordsets:union(vtnames(Vs), Uvs)
end, [], Vss).
@@ -3103,7 +3222,7 @@ modify_line(T, F0) ->
%% Forms.
modify_line1({function,F,A}, _Mf) -> {function,F,A};
modify_line1({function,M,F,A}, _Mf) -> {function,M,F,A};
-modify_line1({attribute,L,record,{Name,Fields}}, Mf) ->
+modify_line1({attribute,L,record,{Name,Fields}}, Mf) ->
{attribute,Mf(L),record,{Name,modify_line1(Fields, Mf)}};
modify_line1({attribute,L,spec,{Fun,Types}}, Mf) ->
{attribute,Mf(L),spec,{Fun,modify_line1(Types, Mf)}};
@@ -3118,7 +3237,7 @@ modify_line1({warning,W}, _Mf) -> {warning,W};
modify_line1({error,W}, _Mf) -> {error,W};
%% Expressions.
modify_line1({clauses,Cs}, Mf) -> {clauses,modify_line1(Cs, Mf)};
-modify_line1({typed_record_field,Field,Type}, Mf) ->
+modify_line1({typed_record_field,Field,Type}, Mf) ->
{typed_record_field,modify_line1(Field, Mf),modify_line1(Type, Mf)};
modify_line1({Tag,L}, Mf) -> {Tag,Mf(L)};
modify_line1({Tag,L,E1}, Mf) ->
@@ -3154,7 +3273,7 @@ check_record_info_call(Line,_La,_As,St) ->
has_wildcard_field([{record_field,_Lf,{var,_La,'_'},_Val}|_Fs]) -> true;
has_wildcard_field([_|Fs]) -> has_wildcard_field(Fs);
has_wildcard_field([]) -> false.
-
+
%% check_remote_function(Line, ModuleName, FuncName, [Arg], State) -> State.
%% Perform checks on known remote calls.
@@ -3170,7 +3289,7 @@ check_remote_function(Line, M, F, As, St0) ->
check_qlc_hrl(Line, M, F, As, St) ->
Arity = length(As),
case As of
- [{lc,_L,_E,_Qs}|_] when M =:= qlc, F =:= q,
+ [{lc,_L,_E,_Qs}|_] when M =:= qlc, F =:= q,
Arity < 3, not St#lint.xqlc ->
add_warning(Line, {missing_qlc_hrl, Arity}, St);
_ ->
@@ -3355,11 +3474,11 @@ extract_sequence(3, [$.,_|Fmt], Need) ->
extract_sequence(4, Fmt, Need);
extract_sequence(3, Fmt, Need) ->
extract_sequence(4, Fmt, Need);
-extract_sequence(4, [$t, $c | Fmt], Need) ->
- extract_sequence(5, [$c|Fmt], Need);
-extract_sequence(4, [$t, $s | Fmt], Need) ->
- extract_sequence(5, [$s|Fmt], Need);
-extract_sequence(4, [$t, C | _Fmt], _Need) ->
+extract_sequence(4, [$t, $c | Fmt], Need) ->
+ extract_sequence(5, [$c|Fmt], Need);
+extract_sequence(4, [$t, $s | Fmt], Need) ->
+ extract_sequence(5, [$s|Fmt], Need);
+extract_sequence(4, [$t, C | _Fmt], _Need) ->
{error,"invalid control ~t" ++ [C]};
extract_sequence(4, Fmt, Need) ->
extract_sequence(5, Fmt, Need);
@@ -3437,3 +3556,56 @@ expand_package(M, St0) ->
{error, St1}
end
end.
+
+
+%% Prebuild set of local functions (to override auto-import)
+local_functions(Forms) ->
+ gb_sets:from_list([ {Func,Arity} || {function,_,Func,Arity,_} <- Forms ]).
+%% Predicate to find out if the function is locally defined
+is_local_function(LocalSet,{Func,Arity}) ->
+ gb_sets:is_element({Func,Arity},LocalSet).
+%% Predicate to see if a function is explicitly imported
+is_imported_function(ImportSet,{Func,Arity}) ->
+ case orddict:find({Func,Arity}, ImportSet) of
+ {ok,_Mod} -> true;
+ error -> false
+ end.
+%% Predicate to see if a function is explicitly imported from the erlang module
+is_imported_from_erlang(ImportSet,{Func,Arity}) ->
+ case orddict:find({Func,Arity}, ImportSet) of
+ {ok,erlang} -> true;
+ _ -> false
+ end.
+%% Build set of functions where auto-import is explicitly supressed
+auto_import_suppressed(CompileFlags) ->
+ L0 = [ X || {no_auto_import,X} <- CompileFlags ],
+ L1 = [ {Y,Z} || {Y,Z} <- lists:flatten(L0), is_atom(Y), is_integer(Z) ],
+ gb_sets:from_list(L1).
+%% Predicate to find out if autoimport is explicitly supressed for a function
+is_autoimport_suppressed(NoAutoSet,{Func,Arity}) ->
+ gb_sets:is_element({Func,Arity},NoAutoSet).
+%% Predicate to find out if a function specific bif-clash supression (old deprecated) is present
+bif_clash_specifically_disabled(St,{F,A}) ->
+ Nowarn = nowarn_function(nowarn_bif_clash, St#lint.compile),
+ lists:member({F,A},Nowarn).
+
+%% Predicate to find out if an autoimported guard_bif is not overriden in some way
+%% Guard Bif without module name is disallowed if
+%% * It is overridden by local function
+%% * It is overridden by -import and that import is not of itself (i.e. from module erlang)
+%% * The autoimport is suppressed or it's not reimported by -import directive
+%% Otherwise it's OK (given that it's actually a guard bif and actually is autoimported)
+no_guard_bif_clash(St,{F,A}) ->
+ (
+ (not is_local_function(St#lint.locals,{F,A}))
+ andalso
+ (
+ (not is_imported_function(St#lint.imports,{F,A})) orelse
+ is_imported_from_erlang(St#lint.imports,{F,A})
+ )
+ andalso
+ (
+ (not is_autoimport_suppressed(St#lint.no_auto, {F,A})) orelse
+ is_imported_from_erlang(St#lint.imports,{F,A})
+ )
+ ).
diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl
index 808e1a8926..bb4b18cf9b 100644
--- a/lib/stdlib/src/erl_parse.yrl
+++ b/lib/stdlib/src/erl_parse.yrl
@@ -30,9 +30,8 @@ expr_600 expr_700 expr_800 expr_900
expr_max
list tail
list_comprehension lc_expr lc_exprs
-binary_comprehension
+binary_comprehension
tuple
-atom1
%struct
record_expr record_tuple record_field record_fields
if_expr if_clause if_clauses case_expr cr_clause cr_clauses receive_expr
@@ -48,21 +47,22 @@ opt_bit_size_expr bit_size_expr opt_bit_type_list bit_type_list bit_type
top_type top_type_100 top_types type typed_expr typed_attr_val
type_sig type_sigs type_guard type_guards fun_type fun_type_100 binary_type
type_spec spec_fun typed_exprs typed_record_fields field_types field_type
-bin_base_type bin_unit_type int_type.
+bin_base_type bin_unit_type type_200 type_300 type_400 type_500.
Terminals
char integer float atom string var
'(' ')' ',' '->' ':-' '{' '}' '[' ']' '|' '||' '<-' ';' ':' '#' '.'
'after' 'begin' 'case' 'try' 'catch' 'end' 'fun' 'if' 'of' 'receive' 'when'
-'andalso' 'orelse' 'query' 'spec'
+'andalso' 'orelse' 'query'
'bnot' 'not'
'*' '/' 'div' 'rem' 'band' 'and'
'+' '-' 'bor' 'bxor' 'bsl' 'bsr' 'or' 'xor'
'++' '--'
'==' '/=' '=<' '<' '>=' '>' '=:=' '=/=' '<='
'<<' '>>'
-'!' '=' '::'
+'!' '=' '::' '..' '...'
+'spec' % helper
dot.
Expect 2.
@@ -77,19 +77,16 @@ attribute -> '-' atom attr_val : build_attribute('$2', '$3').
attribute -> '-' atom typed_attr_val : build_typed_attribute('$2','$3').
attribute -> '-' atom '(' typed_attr_val ')' : build_typed_attribute('$2','$4').
attribute -> '-' 'spec' type_spec : build_type_spec('$2', '$3').
-
-atom1 -> 'spec' : {atom, ?line('$1'), 'spec'}.
-atom1 -> atom : '$1'.
type_spec -> spec_fun type_sigs : {'$1', '$2'}.
type_spec -> '(' spec_fun type_sigs ')' : {'$2', '$3'}.
-spec_fun -> atom1 : '$1'.
-spec_fun -> atom1 ':' atom1 : {'$1', '$3'}.
+spec_fun -> atom : '$1'.
+spec_fun -> atom ':' atom : {'$1', '$3'}.
%% The following two are retained only for backwards compatibility;
%% they are not part of the EEP syntax and should be removed.
-spec_fun -> atom1 '/' integer '::' : {'$1', '$3'}.
-spec_fun -> atom1 ':' atom1 '/' integer '::' : {'$1', '$3', '$5'}.
+spec_fun -> atom '/' integer '::' : {'$1', '$3'}.
+spec_fun -> atom ':' atom '/' integer '::' : {'$1', '$3', '$5'}.
typed_attr_val -> expr ',' typed_record_fields : {typed_record, '$1', '$3'}.
typed_attr_val -> expr '::' top_type : {type_def, '$1', '$3'}.
@@ -107,14 +104,15 @@ type_sigs -> type_sig : ['$1'].
type_sigs -> type_sig ';' type_sigs : ['$1'|'$3'].
type_sig -> fun_type : '$1'.
-type_sig -> fun_type 'when' type_guards : {type, ?line('$1'), bounded_fun,
+type_sig -> fun_type 'when' type_guards : {type, ?line('$1'), bounded_fun,
['$1','$3']}.
type_guards -> type_guard : ['$1'].
type_guards -> type_guard ',' type_guards : ['$1'|'$3'].
-type_guard -> atom1 '(' top_types ')' : {type, ?line('$1'), constraint,
+type_guard -> atom '(' top_types ')' : {type, ?line('$1'), constraint,
['$1', '$3']}.
+type_guard -> var '::' top_type : build_def('$1', '$3').
top_types -> top_type : ['$1'].
top_types -> top_type ',' top_types : ['$1'|'$3'].
@@ -122,58 +120,68 @@ top_types -> top_type ',' top_types : ['$1'|'$3'].
top_type -> var '::' top_type_100 : {ann_type, ?line('$1'), ['$1','$3']}.
top_type -> top_type_100 : '$1'.
-top_type_100 -> type : '$1'.
-top_type_100 -> type '|' top_type_100 : lift_unions('$1','$3').
+top_type_100 -> type_200 : '$1'.
+top_type_100 -> type_200 '|' top_type_100 : lift_unions('$1','$3').
+
+type_200 -> type_300 '..' type_300 : {type, ?line('$1'), range,
+ [skip_paren('$1'),
+ skip_paren('$3')]}.
+type_200 -> type_300 : '$1'.
+
+type_300 -> type_300 add_op type_400 : ?mkop2(skip_paren('$1'),
+ '$2', skip_paren('$3')).
+type_300 -> type_400 : '$1'.
+
+type_400 -> type_400 mult_op type_500 : ?mkop2(skip_paren('$1'),
+ '$2', skip_paren('$3')).
+type_400 -> type_500 : '$1'.
+
+type_500 -> prefix_op type : ?mkop1('$1', skip_paren('$2')).
+type_500 -> type : '$1'.
type -> '(' top_type ')' : {paren_type, ?line('$2'), ['$2']}.
type -> var : '$1'.
-type -> atom1 : '$1'.
-type -> atom1 '(' ')' : build_gen_type('$1').
-type -> atom1 '(' top_types ')' : {type, ?line('$1'),
+type -> atom : '$1'.
+type -> atom '(' ')' : build_gen_type('$1').
+type -> atom '(' top_types ')' : {type, ?line('$1'),
normalise('$1'), '$3'}.
-type -> atom1 ':' atom1 '(' ')' : {remote_type, ?line('$1'),
+type -> atom ':' atom '(' ')' : {remote_type, ?line('$1'),
['$1', '$3', []]}.
-type -> atom1 ':' atom1 '(' top_types ')' : {remote_type, ?line('$1'),
+type -> atom ':' atom '(' top_types ')' : {remote_type, ?line('$1'),
['$1', '$3', '$5']}.
type -> '[' ']' : {type, ?line('$1'), nil, []}.
type -> '[' top_type ']' : {type, ?line('$1'), list, ['$2']}.
-type -> '[' top_type ',' '.' '.' '.' ']' : {type, ?line('$1'),
+type -> '[' top_type ',' '...' ']' : {type, ?line('$1'),
nonempty_list, ['$2']}.
type -> '{' '}' : {type, ?line('$1'), tuple, []}.
type -> '{' top_types '}' : {type, ?line('$1'), tuple, '$2'}.
-type -> '#' atom1 '{' '}' : {type, ?line('$1'), record, ['$2']}.
-type -> '#' atom1 '{' field_types '}' : {type, ?line('$1'),
+type -> '#' atom '{' '}' : {type, ?line('$1'), record, ['$2']}.
+type -> '#' atom '{' field_types '}' : {type, ?line('$1'),
record, ['$2'|'$4']}.
type -> binary_type : '$1'.
-type -> int_type : '$1'.
-type -> int_type '.' '.' int_type : {type, ?line('$1'), range,
- ['$1', '$4']}.
+type -> integer : '$1'.
type -> 'fun' '(' ')' : {type, ?line('$1'), 'fun', []}.
type -> 'fun' '(' fun_type_100 ')' : '$3'.
-int_type -> integer : '$1'.
-int_type -> '-' integer : abstract(-normalise('$2'),
- ?line('$2')).
-
-fun_type_100 -> '(' '.' '.' '.' ')' '->' top_type
+fun_type_100 -> '(' '...' ')' '->' top_type
: {type, ?line('$1'), 'fun',
- [{type, ?line('$1'), any}, '$7']}.
+ [{type, ?line('$1'), any}, '$5']}.
fun_type_100 -> fun_type : '$1'.
fun_type -> '(' ')' '->' top_type : {type, ?line('$1'), 'fun',
[{type, ?line('$1'), product, []}, '$4']}.
-fun_type -> '(' top_types ')' '->' top_type
+fun_type -> '(' top_types ')' '->' top_type
: {type, ?line('$1'), 'fun',
[{type, ?line('$1'), product, '$2'},'$5']}.
field_types -> field_type : ['$1'].
field_types -> field_type ',' field_types : ['$1'|'$3'].
-field_type -> atom1 '::' top_type : {type, ?line('$1'), field_type,
+field_type -> atom '::' top_type : {type, ?line('$1'), field_type,
['$1', '$3']}.
-binary_type -> '<<' '>>' : {type, ?line('$1'),binary,
- [abstract(0, ?line('$1')),
+binary_type -> '<<' '>>' : {type, ?line('$1'),binary,
+ [abstract(0, ?line('$1')),
abstract(0, ?line('$1'))]}.
binary_type -> '<<' bin_base_type '>>' : {type, ?line('$1'),binary,
['$2', abstract(0, ?line('$1'))]}.
@@ -182,9 +190,9 @@ binary_type -> '<<' bin_unit_type '>>' : {type, ?line('$1'),binary,
binary_type -> '<<' bin_base_type ',' bin_unit_type '>>'
: {type, ?line('$1'), binary, ['$2', '$4']}.
-bin_base_type -> var ':' integer : build_bin_type(['$1'], '$3').
+bin_base_type -> var ':' type : build_bin_type(['$1'], '$3').
-bin_unit_type -> var ':' var '*' integer : build_bin_type(['$1', '$3'], '$5').
+bin_unit_type -> var ':' var '*' type : build_bin_type(['$1', '$3'], '$5').
attr_val -> expr : ['$1'].
attr_val -> expr ',' exprs : ['$1' | '$3'].
@@ -195,7 +203,7 @@ function -> function_clauses : build_function('$1').
function_clauses -> function_clause : ['$1'].
function_clauses -> function_clause ';' function_clauses : ['$1'|'$3'].
-function_clause -> atom1 clause_args clause_guard clause_body :
+function_clause -> atom clause_args clause_guard clause_body :
{clause,?line('$1'),element(3, '$1'),'$2','$3','$4'}.
@@ -248,9 +256,9 @@ expr_800 -> expr_900 ':' expr_max :
{remote,?line('$2'),'$1','$3'}.
expr_800 -> expr_900 : '$1'.
-expr_900 -> '.' atom1 :
+expr_900 -> '.' atom :
{record_field,?line('$1'),{atom,?line('$1'),''},'$2'}.
-expr_900 -> expr_900 '.' atom1 :
+expr_900 -> expr_900 '.' atom :
{record_field,?line('$2'),'$1','$3'}.
expr_900 -> expr_max : '$1'.
@@ -301,8 +309,8 @@ opt_bit_type_list -> '$empty' : default.
bit_type_list -> bit_type '-' bit_type_list : ['$1' | '$3'].
bit_type_list -> bit_type : ['$1'].
-bit_type -> atom1 : element(3,'$1').
-bit_type -> atom1 ':' integer : { element(3,'$1'), element(3,'$3') }.
+bit_type -> atom : element(3,'$1').
+bit_type -> atom ':' integer : { element(3,'$1'), element(3,'$3') }.
bit_size_expr -> expr_max : '$1'.
@@ -322,7 +330,7 @@ tuple -> '{' '}' : {tuple,?line('$1'),[]}.
tuple -> '{' exprs '}' : {tuple,?line('$1'),'$2'}.
-%%struct -> atom1 tuple :
+%%struct -> atom tuple :
%% {struct,?line('$1'),element(3, '$1'),element(3, '$2')}.
@@ -330,13 +338,17 @@ tuple -> '{' exprs '}' : {tuple,?line('$1'),'$2'}.
%% N.B. Field names are returned as the complete object, even if they are
%% always atoms for the moment, this might change in the future.
-record_expr -> '#' atom1 '.' atom1 :
+record_expr -> '#' atom '.' atom :
{record_index,?line('$1'),element(3, '$2'),'$4'}.
-record_expr -> '#' atom1 record_tuple :
+record_expr -> '#' atom record_tuple :
{record,?line('$1'),element(3, '$2'),'$3'}.
-record_expr -> expr_max '#' atom1 '.' atom1 :
+record_expr -> expr_max '#' atom '.' atom :
{record_field,?line('$2'),'$1',element(3, '$3'),'$5'}.
-record_expr -> expr_max '#' atom1 record_tuple :
+record_expr -> expr_max '#' atom record_tuple :
+ {record,?line('$2'),'$1',element(3, '$3'),'$4'}.
+record_expr -> record_expr '#' atom '.' atom :
+ {record_field,?line('$2'),'$1',element(3, '$3'),'$5'}.
+record_expr -> record_expr '#' atom record_tuple :
{record,?line('$2'),'$1',element(3, '$3'),'$4'}.
record_tuple -> '{' '}' : [].
@@ -346,7 +358,7 @@ record_fields -> record_field : ['$1'].
record_fields -> record_field ',' record_fields : ['$1' | '$3'].
record_field -> var '=' expr : {record_field,?line('$1'),'$1','$3'}.
-record_field -> atom1 '=' expr : {record_field,?line('$1'),'$1','$3'}.
+record_field -> atom '=' expr : {record_field,?line('$1'),'$1','$3'}.
%% N.B. This is called from expr_700.
@@ -380,9 +392,9 @@ receive_expr -> 'receive' cr_clauses 'after' expr clause_body 'end' :
{'receive',?line('$1'),'$2','$4','$5'}.
-fun_expr -> 'fun' atom1 '/' integer :
+fun_expr -> 'fun' atom '/' integer :
{'fun',?line('$1'),{function,element(3, '$2'),element(3, '$4')}}.
-fun_expr -> 'fun' atom1 ':' atom1 '/' integer :
+fun_expr -> 'fun' atom ':' atom '/' integer :
{'fun',?line('$1'),{function,element(3, '$2'),element(3, '$4'),element(3,'$6')}}.
fun_expr -> 'fun' fun_clauses 'end' :
build_fun(?line('$1'), '$2').
@@ -412,7 +424,7 @@ try_clauses -> try_clause ';' try_clauses : ['$1' | '$3'].
try_clause -> expr clause_guard clause_body :
L = ?line('$1'),
{clause,L,[{tuple,L,[{atom,L,throw},'$1',{var,L,'_'}]}],'$2','$3'}.
-try_clause -> atom1 ':' expr clause_guard clause_body :
+try_clause -> atom ':' expr clause_guard clause_body :
L = ?line('$1'),
{clause,L,[{tuple,L,['$1','$3',{var,L,'_'}]}],'$4','$5'}.
try_clause -> var ':' expr clause_guard clause_body :
@@ -436,7 +448,7 @@ guard -> exprs ';' guard : ['$1'|'$3'].
atomic -> char : '$1'.
atomic -> integer : '$1'.
atomic -> float : '$1'.
-atomic -> atom1 : '$1'.
+atomic -> atom : '$1'.
atomic -> strings : '$1'.
strings -> string : '$1'.
@@ -481,7 +493,7 @@ rule -> rule_clauses : build_rule('$1').
rule_clauses -> rule_clause : ['$1'].
rule_clauses -> rule_clause ';' rule_clauses : ['$1'|'$3'].
-rule_clause -> atom1 clause_args clause_guard rule_body :
+rule_clause -> atom clause_args clause_guard rule_body :
{clause,?line('$1'),element(3, '$1'),'$2','$3','$4'}.
rule_body -> ':-' lc_exprs: '$2'.
@@ -503,8 +515,8 @@ Erlang code.
%% mkop(Op, Arg) -> {op,Line,Op,Arg}.
%% mkop(Left, Op, Right) -> {op,Line,Op,Left,Right}.
--define(mkop2(L, OpPos, R),
- begin
+-define(mkop2(L, OpPos, R),
+ begin
{Op,Pos} = OpPos,
{op,Pos,Op,L,R}
end).
@@ -522,6 +534,8 @@ Erlang code.
%% These really suck and are only here until Calle gets multiple
%% entry points working.
+parse_form([{'-',L1},{atom,L2,spec}|Tokens]) ->
+ parse([{'-',L1},{'spec',L2}|Tokens]);
parse_form(Tokens) ->
parse(Tokens).
@@ -548,7 +562,7 @@ parse_term(Tokens) ->
-type attributes() :: 'export' | 'file' | 'import' | 'module'
| 'opaque' | 'record' | 'type'.
-build_typed_attribute({atom,La,record},
+build_typed_attribute({atom,La,record},
{typed_record, {atom,_Ln,RecordName}, RecTuple}) ->
{attribute,La,record,{RecordName,record_tuple(RecTuple)}};
build_typed_attribute({atom,La,Attr},
@@ -571,7 +585,7 @@ build_typed_attribute({atom,La,Attr},_) ->
build_type_spec({spec,La}, {SpecFun, TypeSpecs}) ->
NewSpecFun =
case SpecFun of
- {atom, _, Fun} ->
+ {atom, _, Fun} ->
{Fun, find_arity_from_specs(TypeSpecs)};
{{atom,_, Mod}, {atom,_, Fun}} ->
{Mod,Fun,find_arity_from_specs(TypeSpecs)};
@@ -594,11 +608,20 @@ find_arity_from_specs([Spec|_]) ->
{type, _, 'fun', [{type, _, product, Args},_]} = Fun,
length(Args).
+build_def(LHS, Types) ->
+ IsSubType = {atom, ?line(LHS), is_subtype},
+ {type, ?line(LHS), constraint, [IsSubType, [LHS, Types]]}.
+
lift_unions(T1, {type, _La, union, List}) ->
{type, ?line(T1), union, [T1|List]};
lift_unions(T1, T2) ->
{type, ?line(T1), union, [T1, T2]}.
+skip_paren({paren_type,_L,[Type]}) ->
+ skip_paren(Type);
+skip_paren(Type) ->
+ Type.
+
build_gen_type({atom, La, tuple}) ->
{type, La, tuple, any};
build_gen_type({atom, La, Name}) ->
@@ -607,7 +630,7 @@ build_gen_type({atom, La, Name}) ->
build_bin_type([{var, _, '_'}|Left], Int) ->
build_bin_type(Left, Int);
build_bin_type([], Int) ->
- Int;
+ skip_paren(Int);
build_bin_type([{var, La, _}|_], _) ->
ret_err(La, "Bad binary type").
@@ -705,7 +728,7 @@ attribute_farity(Other) -> Other.
attribute_farity_list(Args) ->
[attribute_farity(A) || A <- Args].
-
+
-spec error_bad_decl(integer(), attributes()) -> no_return().
error_bad_decl(L, S) ->
@@ -728,17 +751,17 @@ record_fields([{match,_Lm,{atom,La,A},Expr}|Fields]) ->
[{record_field,La,{atom,La,A},Expr}|record_fields(Fields)];
record_fields([{typed,Expr,TypeInfo}|Fields]) ->
[Field] = record_fields([Expr]),
- TypeInfo1 =
+ TypeInfo1 =
case Expr of
{match, _, _, _} -> TypeInfo; %% If we have an initializer.
- {atom, La, _} ->
+ {atom, La, _} ->
case has_undefined(TypeInfo) of
false ->
lift_unions(abstract(undefined, La), TypeInfo);
true ->
TypeInfo
end
- end,
+ end,
[{typed_record_field,Field,TypeInfo1}|record_fields(Fields)];
record_fields([Other|_Fields]) ->
ret_err(?line(Other), "bad record field");
@@ -994,7 +1017,7 @@ inop_prec('#') -> {800,700,800};
inop_prec(':') -> {900,800,900};
inop_prec('.') -> {900,900,1000}.
--type pre_op() :: 'catch' | '+' | '-' | 'bnot' | '#'.
+-type pre_op() :: 'catch' | '+' | '-' | 'bnot' | 'not' | '#'.
-spec preop_prec(pre_op()) -> {0 | 600 | 700, 100 | 700 | 800}.
diff --git a/lib/stdlib/src/erl_pp.erl b/lib/stdlib/src/erl_pp.erl
index 0859bf0466..df4a20b833 100644
--- a/lib/stdlib/src/erl_pp.erl
+++ b/lib/stdlib/src/erl_pp.erl
@@ -115,7 +115,7 @@ lattribute({attribute,_Line,Name,Arg}, Hook) ->
lattribute(module, {M,Vs}, _Hook) ->
attr("module",[{var,0,pname(M)},
- foldr(fun(V, C) -> {cons,0,{var,0,V},C}
+ foldr(fun(V, C) -> {cons,0,{var,0,V},C}
end, {nil,0}, Vs)]);
lattribute(module, M, _Hook) ->
attr("module", [{var,0,pname(M)}]);
@@ -140,7 +140,7 @@ typeattr(Tag, {TypeName,Type,Args}, _Hook) ->
ltype({ann_type,_Line,[V,T]}) ->
typed(lexpr(V, none), T);
ltype({paren_type,_Line,[T]}) ->
- [$(,ltype(T),$)];
+ [$(,ltype(T),$)];
ltype({type,_Line,union,Ts}) ->
{seq,[],[],[' |'],ltypes(Ts)};
ltype({type,_Line,list,[T]}) ->
@@ -153,7 +153,7 @@ ltype({type,Line,tuple,any}) ->
simple_type({atom,Line,tuple}, []);
ltype({type,_Line,tuple,Ts}) ->
tuple_type(Ts, fun ltype/1);
-ltype({type,_Line,record,[N|Fs]}) ->
+ltype({type,_Line,record,[{atom,_,N}|Fs]}) ->
record_type(N, Fs);
ltype({type,_Line,range,[_I1,_I2]=Es}) ->
expr_list(Es, '..', fun lexpr/2, none);
@@ -174,12 +174,15 @@ ltype({atom,_,T}) ->
ltype(E) ->
lexpr(E, 0, none).
-binary_type({integer,_,Int1}=I1, {integer,_,Int2}=I2) ->
- E1 = [[leaf("_:"),lexpr(I1, 0, none)] || Int1 =/= 0],
- E2 = [[leaf("_:_*"),lexpr(I2, 0, none)] || Int2 =/= 0],
+binary_type(I1, I2) ->
+ B = [[] || {integer,_,0} <- [I1]] =:= [],
+ U = [[] || {integer,_,0} <- [I2]] =:= [],
+ P = max_prec(),
+ E1 = [[leaf("_:"),lexpr(I1, P, none)] || B],
+ E2 = [[leaf("_:_*"),lexpr(I2, P, none)] || U],
{seq,'<<','>>',[$,],E1++E2}.
-record_type({atom,_,Name}, Fields) ->
+record_type(Name, Fields) ->
{first,[record_name(Name)],field_types(Fields)}.
field_types(Fs) ->
@@ -443,7 +446,7 @@ lexpr({op,_,Op,Arg}, Prec, Hook) ->
Ol = leaf(format("~s ", [Op])),
El = [Ol,lexpr(Arg, R, Hook)],
maybe_paren(P, Prec, El);
-lexpr({op,_,Op,Larg,Rarg}, Prec, Hook) when Op =:= 'orelse';
+lexpr({op,_,Op,Larg,Rarg}, Prec, Hook) when Op =:= 'orelse';
Op =:= 'andalso' ->
%% Breaks lines since R12B.
{L,P,R} = inop_prec(Op),
@@ -727,15 +730,15 @@ frmt(Item, I) ->
%%% and indentation are inserted between IPs.
%%% - {first,I,IP2}: IP2 follows after I, and is output with an indentation
%%% updated with the width of I.
-%%% - {seq,Before,After,Separator,IPs}: a sequence of Is separated by
-%%% Separator. Before is output before IPs, and the indentation of IPs
+%%% - {seq,Before,After,Separator,IPs}: a sequence of Is separated by
+%%% Separator. Before is output before IPs, and the indentation of IPs
%%% is updated with the width of Before. After follows after IPs.
%%% - {force_nl,ExtraInfo,I}: fun-info (a comment) forces linebreak before I.
%%% - {prefer_nl,Sep,IPs}: forces linebreak between Is unlesss negative
%%% indentation.
%%% - {string,S}: a string.
%%% - {hook,...}, {ehook,...}: hook expressions.
-%%%
+%%%
%%% list, first, seq, force_nl, and prefer_nl all accept IPs, where each
%%% element is either an item or a tuple {step|cstep,I1,I2}. step means
%%% that I2 is output after linebreak and an incremented indentation.
@@ -761,7 +764,7 @@ f({seq,Before,After,Sep,LItems}, I0, ST, WT) ->
{CharsL,SizeL} = unz(CharsSizeL),
{BCharsL,BSizeL} = unz1([BCharsSize]),
Sizes = BSizeL ++ SizeL,
- NSepChars = if
+ NSepChars = if
is_list(Sep), Sep =/= [] ->
erlang:max(0, length(CharsL)-1);
true ->
@@ -876,7 +879,7 @@ nl_indent(I, T) when I > 0 ->
[$\n|spaces(I, T)].
same_line(I0, SizeL, NSepChars) ->
- try
+ try
Size = lists:sum(SizeL) + NSepChars,
true = incr(I0, Size) =< ?MAXLINE,
{yes,Size}
@@ -956,9 +959,9 @@ write_a_string(S, N, Len) ->
-define(N_SPACES, 30).
spacetab() ->
- {[_|L],_} = mapfoldl(fun(_, A) -> {A,[$\s|A]}
+ {[_|L],_} = mapfoldl(fun(_, A) -> {A,[$\s|A]}
end, [], lists:seq(0, ?N_SPACES)),
- list_to_tuple(L).
+ list_to_tuple(L).
spaces(N, T) when N =< ?N_SPACES ->
element(N, T);
@@ -966,7 +969,7 @@ spaces(N, T) ->
[element(?N_SPACES, T)|spaces(N-?N_SPACES, T)].
wordtable() ->
- L = [begin {leaf,Sz,S} = leaf(W), {S,Sz} end ||
+ L = [begin {leaf,Sz,S} = leaf(W), {S,Sz} end ||
W <- [" ->"," =","<<",">>","[]","after","begin","case","catch",
"end","fun","if","of","receive","try","when"," ::","..",
" |"]],
diff --git a/lib/stdlib/src/erl_scan.erl b/lib/stdlib/src/erl_scan.erl
index 52ec81a78b..18f64c46d0 100644
--- a/lib/stdlib/src/erl_scan.erl
+++ b/lib/stdlib/src/erl_scan.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
@@ -48,25 +48,20 @@
-module(erl_scan).
-%%% External exports
+%%% External exports
-export([string/1,string/2,string/3,tokens/3,tokens/4,
format_error/1,reserved_word/1,
token_info/1,token_info/2,
attributes_info/1,attributes_info/2,set_attribute/3]).
-%%% Local record.
--record(erl_scan,
- {resword_fun=fun reserved_word/1,
- ws=false,
- comment=false,
- text=false}).
+-export_type([error_info/0, line/0, tokens_result/0]).
%%%
-%%% Exported functions
+%%% Defines and type definitions
%%%
--define(COLUMN(C), is_integer(C), C >= 1).
+-define(COLUMN(C), (is_integer(C) andalso C >= 1)).
%% Line numbers less than zero have always been allowed:
-define(ALINE(L), is_integer(L)).
-define(STRING(S), is_list(S)).
@@ -95,44 +90,53 @@
-type error_description() :: term().
-type error_info() :: {location(), module(), error_description()}.
+%%% Local record.
+-record(erl_scan,
+ {resword_fun = fun reserved_word/1 :: resword_fun(),
+ ws = false :: boolean(),
+ comment = false :: boolean(),
+ text = false :: boolean()}).
+
+%%----------------------------------------------------------------------------
+
-spec format_error(Error :: term()) -> string().
format_error({string,Quote,Head}) ->
lists:flatten(["unterminated " ++ string_thing(Quote) ++
- " starting with " ++
+ " starting with " ++
io_lib:write_unicode_string(Head, Quote)]);
-format_error({illegal,Type}) ->
+format_error({illegal,Type}) ->
lists:flatten(io_lib:fwrite("illegal ~w", [Type]));
format_error(char) -> "unterminated character";
-format_error({base,Base}) ->
+format_error({base,Base}) ->
lists:flatten(io_lib:fwrite("illegal base '~w'", [Base]));
-format_error(Other) ->
+format_error(Other) ->
lists:flatten(io_lib:write(Other)).
--type string_return() :: {'ok', tokens(), location()}
+-type string_return() :: {'ok', tokens(), location()}
| {'error', error_info(), location()}.
-spec string(String :: string()) -> string_return().
string(String) ->
string(String, 1, []).
--spec string(String :: string(), StartLocation :: location()) ->
+-spec string(String :: string(), StartLocation :: location()) ->
string_return().
string(String, StartLocation) ->
string(String, StartLocation, []).
--spec string(String :: string(), StartLocation :: location(),
+-spec string(String :: string(), StartLocation :: location(),
Options :: options()) -> string_return().
string(String, Line, Options) when ?STRING(String), ?ALINE(Line) ->
string1(String, options(Options), Line, no_col, []);
string(String, {Line,Column}, Options) when ?STRING(String),
- ?ALINE(Line),
+ ?ALINE(Line),
?COLUMN(Column) ->
string1(String, options(Options), Line, Column, []).
-type char_spec() :: string() | 'eof'.
-type cont_fun() :: fun((char_spec(), #erl_scan{}, line(), column(),
tokens(), any()) -> any()).
--opaque return_cont() :: {string(), column(), tokens(), line(),
+-opaque return_cont() :: {string(), column(), tokens(), line(),
#erl_scan{}, cont_fun(), any()}.
-type cont() :: return_cont() | [].
-type tokens_result() :: {'ok', tokens(), location()}
@@ -141,13 +145,13 @@ string(String, {Line,Column}, Options) when ?STRING(String),
-type tokens_return() :: {'done', tokens_result(), char_spec()}
| {'more', return_cont()}.
--spec tokens(Cont :: cont(), CharSpec :: char_spec(),
+-spec tokens(Cont :: cont(), CharSpec :: char_spec(),
StartLocation :: location()) -> tokens_return().
tokens(Cont, CharSpec, StartLocation) ->
tokens(Cont, CharSpec, StartLocation, []).
--spec tokens(Cont :: cont(), CharSpec :: char_spec(),
- StartLocation :: location(), Options :: options()) ->
+-spec tokens(Cont :: cont(), CharSpec :: char_spec(),
+ StartLocation :: location(), Options :: options()) ->
tokens_return().
tokens([], CharSpec, Line, Options) when ?ALINE(Line) ->
tokens1(CharSpec, options(Options), Line, no_col, [], fun scan/6, []);
@@ -157,15 +161,15 @@ tokens([], CharSpec, {Line,Column}, Options) when ?ALINE(Line),
tokens({Cs,Col,Toks,Line,St,Any,Fun}, CharSpec, _Loc, _Opts) ->
tokens1(Cs++CharSpec, St, Line, Col, Toks, Fun, Any).
--type attribute_item() :: 'column' | 'length' | 'line'
+-type attribute_item() :: 'column' | 'length' | 'line'
| 'location' | 'text'.
-type info_location() :: location() | term().
--type attribute_info() :: {'column', column()}| {'length', pos_integer()}
- | {'line', info_line()}
+-type attribute_info() :: {'column', column()}| {'length', pos_integer()}
+ | {'line', info_line()}
| {'location', info_location()}
| {'text', string()}.
-type token_item() :: 'category' | 'symbol' | attribute_item().
--type token_info() :: {'category', category()} | {'symbol', symbol()}
+-type token_info() :: {'category', category()} | {'symbol', symbol()}
| attribute_info().
-spec token_info(token()) -> [token_info()].
@@ -214,7 +218,7 @@ attributes_info(Attrs, [A|As]) when is_atom(A) ->
AttributeInfo when is_tuple(AttributeInfo) ->
[AttributeInfo|attributes_info(Attrs, As)]
end;
-attributes_info({Line,Column}, column=Item) when ?ALINE(Line),
+attributes_info({Line,Column}, column=Item) when ?ALINE(Line),
?COLUMN(Column) ->
{Item,Column};
attributes_info(Line, column) when ?ALINE(Line) ->
@@ -230,12 +234,12 @@ attributes_info(Attrs, length=Item) ->
end;
attributes_info(Line, line=Item) when ?ALINE(Line) ->
{Item,Line};
-attributes_info({Line,Column}, line=Item) when ?ALINE(Line),
+attributes_info({Line,Column}, line=Item) when ?ALINE(Line),
?COLUMN(Column) ->
{Item,Line};
attributes_info(Attrs, line=Item) ->
attr_info(Attrs, Item);
-attributes_info({Line,Column}=Location, location=Item) when ?ALINE(Line),
+attributes_info({Line,Column}=Location, location=Item) when ?ALINE(Line),
?COLUMN(Column) ->
{Item,Location};
attributes_info(Line, location=Item) when ?ALINE(Line) ->
@@ -289,11 +293,11 @@ string_thing(_) -> "string".
options(Opts0) when is_list(Opts0) ->
Opts = lists:foldr(fun expand_opt/2, [], Opts0),
- [RW_fun] =
+ [RW_fun] =
case opts(Opts, [reserved_word_fun], []) of
badarg ->
erlang:error(badarg, [Opts0]);
- R ->
+ R ->
R
end,
Comment = proplists:get_bool(return_comments, Opts),
@@ -307,10 +311,10 @@ options(Opt) ->
options([Opt]).
opts(Options, [Key|Keys], L) ->
- V = case lists:keysearch(Key, 1, Options) of
- {value,{reserved_word_fun,F}} when ?RESWORDFUN(F) ->
+ V = case lists:keyfind(Key, 1, Options) of
+ {reserved_word_fun,F} when ?RESWORDFUN(F) ->
{ok,F};
- {value,{Key,_}} ->
+ {Key,_} ->
badarg;
false ->
{ok,default_option(Key)}
@@ -333,12 +337,13 @@ expand_opt(O, Os) ->
[O|Os].
attr_info(Attrs, Item) ->
- case catch lists:keysearch(Item, 1, Attrs) of
- {value,{Item,Value}} ->
- {Item,Value};
- false ->
- undefined;
- _ ->
+ try lists:keyfind(Item, 1, Attrs) of
+ {_Item, _Value} = T ->
+ T;
+ false ->
+ undefined
+ catch
+ _:_ ->
erlang:error(badarg, [Attrs, Item])
end.
@@ -442,6 +447,14 @@ scan1([$\%=C|Cs], St, Line, Col, Toks) ->
scan_comment(Cs, St, Line, Col, Toks, [C]);
scan1([C|Cs], St, Line, Col, Toks) when ?DIGIT(C) ->
scan_number(Cs, St, Line, Col, Toks, [C]);
+scan1("..."++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "...", '...', 3);
+scan1(".."=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
+scan1(".."++Cs, St, Line, Col, Toks) ->
+ tok2(Cs, St, Line, Col, Toks, "..", '..', 2);
+scan1("."=Cs, _St, Line, Col, Toks) ->
+ {more,{Cs,Col,Toks,Line,[],fun scan/6}};
scan1([$.=C|Cs], St, Line, Col, Toks) ->
scan_dot(Cs, St, Line, Col, Toks, [C]);
scan1([$"|Cs], St, Line, Col, Toks) -> %" Emacs
@@ -591,12 +604,12 @@ scan_atom(Cs0, St, Line, Col, Toks, Ncs0) ->
case catch list_to_atom(Wcs) of
Name when is_atom(Name) ->
case (St#erl_scan.resword_fun)(Name) of
- true ->
+ true ->
tok2(Cs, St, Line, Col, Toks, Wcs, Name);
- false ->
+ false ->
tok3(Cs, St, Line, Col, Toks, atom, Wcs, Name)
end;
- _Error ->
+ _Error ->
Ncol = incr_column(Col, length(Wcs)),
scan_error({illegal,atom}, Line, Col, Line, Ncol, Cs)
end
@@ -610,7 +623,7 @@ scan_variable(Cs0, St, Line, Col, Toks, Ncs0) ->
case catch list_to_atom(Wcs) of
Name when is_atom(Name) ->
tok3(Cs, St, Line, Col, Toks, var, Wcs, Name);
- _Error ->
+ _Error ->
Ncol = incr_column(Col, length(Wcs)),
scan_error({illegal,var}, Line, Col, Line, Ncol, Cs)
end
@@ -644,8 +657,6 @@ scan_dot([$\n=C|Cs], St, Line, Col, Toks, Ncs) ->
scan_dot([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
Attrs = attributes(Line, Col, St, Ncs++[C]),
{ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 2)};
-scan_dot([]=Cs, _St, Line, Col, Toks, Ncs) ->
- {more,{Cs,Col,Toks,Line,Ncs,fun scan_dot/6}};
scan_dot(eof=Cs, St, Line, Col, Toks, Ncs) ->
Attrs = attributes(Line, Col, St, Ncs),
{ok,[{dot,Attrs}|Toks],Cs,Line,incr_column(Col, 1)};
@@ -690,7 +701,7 @@ scan_nl_spcs([]=Cs, _St, Line, Col, Toks, N) ->
{more,{Cs,Col,Toks,Line,N,fun scan_nl_spcs/6}};
scan_nl_spcs(Cs, St, Line, Col, Toks, N) ->
newline_end(Cs, St, Line, Col, Toks, N, nl_spcs(N)).
-
+
scan_nl_tabs([$\t|Cs], St, Line, Col, Toks, N) when N < 11 ->
scan_nl_tabs(Cs, St, Line, Col, Toks, N+1);
scan_nl_tabs([]=Cs, _St, Line, Col, Toks, N) ->
@@ -701,7 +712,7 @@ scan_nl_tabs(Cs, St, Line, Col, Toks, N) ->
%% Note: returning {more,Cont} is meaningless here; one could just as
%% well return several tokens. But since tokens() scans up to a full
%% stop anyway, nothing is gained by not collecting all white spaces.
-scan_nl_white_space([$\n|Cs], #erl_scan{text = false}=St, Line, no_col=Col,
+scan_nl_white_space([$\n|Cs], #erl_scan{text = false}=St, Line, no_col=Col,
Toks0, Ncs) ->
Toks = [{white_space,Line,lists:reverse(Ncs)}|Toks0],
scan_newline(Cs, St, Line+1, Col, Toks);
@@ -714,7 +725,7 @@ scan_nl_white_space([C|Cs], St, Line, Col, Toks, Ncs) when ?WHITE_SPACE(C) ->
scan_nl_white_space(Cs, St, Line, Col, Toks, [C|Ncs]);
scan_nl_white_space([]=Cs, _St, Line, Col, Toks, Ncs) ->
{more,{Cs,Col,Toks,Line,Ncs,fun scan_nl_white_space/6}};
-scan_nl_white_space(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
+scan_nl_white_space(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
Toks, Ncs) ->
scan1(Cs, St, Line+1, Col, [{white_space,Line,lists:reverse(Ncs)}|Toks]);
scan_nl_white_space(Cs, St, Line, Col, Toks, Ncs0) ->
@@ -723,7 +734,7 @@ scan_nl_white_space(Cs, St, Line, Col, Toks, Ncs0) ->
Token = {white_space,Attrs,Ncs},
scan1(Cs, St, Line+1, new_column(Col, length(Ncs)), [Token|Toks]).
-newline_end(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
+newline_end(Cs, #erl_scan{text = false}=St, Line, no_col=Col,
Toks, _N, Ncs) ->
scan1(Cs, St, Line+1, Col, [{white_space,Line,Ncs}|Toks]);
newline_end(Cs, St, Line, Col, Toks, N, Ncs) ->
@@ -789,7 +800,7 @@ scan_char([$\\|Cs]=Cs0, St, Line, Col, Toks) ->
Ntoks = [{char,Attrs,Val}|Toks],
scan1(Ncs, St, Line, Ncol, Ntoks)
end;
-scan_char([$\n=C|Cs], St, Line, Col, Toks) ->
+scan_char([$\n=C|Cs], St, Line, Col, Toks) ->
Attrs = attributes(Line, Col, St, [$$,C]),
scan1(Cs, St, Line+1, new_column(Col, 1), [{char,Attrs,C}|Toks]);
scan_char([C|Cs], St, Line, Col, Toks) when ?CHAR(C) ->
@@ -896,7 +907,7 @@ scan_string_no_col([Q|Cs], Line, Col, Q, Wcs, Uni) ->
{Cs,Line,Col,_DontCare=[],lists:reverse(Wcs),Uni};
scan_string_no_col([$\n=C|Cs], Line, Col, Q, Wcs, Uni) ->
scan_string_no_col(Cs, Line+1, Col, Q, [C|Wcs], Uni);
-scan_string_no_col([C|Cs], Line, Col, Q, Wcs, Uni) when C =/= $\\,
+scan_string_no_col([C|Cs], Line, Col, Q, Wcs, Uni) when C =/= $\\,
?CHAR(C), ?UNI255(C) ->
scan_string_no_col(Cs, Line, Col, Q, [C|Wcs], Uni);
scan_string_no_col(Cs, Line, Col, Q, Wcs, Uni) ->
@@ -909,7 +920,7 @@ scan_string_col([Q|Cs], Line, Col, Q, Wcs0, Uni) ->
{Cs,Line,Col+1,Str,Wcs,Uni};
scan_string_col([$\n=C|Cs], Line, _xCol, Q, Wcs, Uni) ->
scan_string_col(Cs, Line+1, 1, Q, [C|Wcs], Uni);
-scan_string_col([C|Cs], Line, Col, Q, Wcs, Uni) when C =/= $\\,
+scan_string_col([C|Cs], Line, Col, Q, Wcs, Uni) when C =/= $\\,
?CHAR(C), ?UNI255(C) ->
scan_string_col(Cs, Line, Col+1, Q, [C|Wcs], Uni);
scan_string_col(Cs, Line, Col, Q, Wcs, Uni) ->
@@ -970,8 +981,8 @@ scan_string1(eof, Line, Col, _Q, _Str, Wcs, _Uni) ->
{error,Line,Col,lists:reverse(Wcs),eof}.
-define(OCT(C), C >= $0, C =< $7).
--define(HEX(C), C >= $0 andalso C =< $9 orelse
- C >= $A andalso C =< $F orelse
+-define(HEX(C), C >= $0 andalso C =< $9 orelse
+ C >= $A andalso C =< $F orelse
C >= $a andalso C =< $f).
%% \<1-3> octal digits
@@ -1086,7 +1097,7 @@ scan_number(Cs, St, Line, Col, Toks, Ncs0) ->
Ncol = incr_column(Col, length(Ncs)),
scan_error({illegal,integer}, Line, Col, Line, Ncol, Cs)
end.
-
+
scan_based_int([C|Cs], St, Line, Col, Toks, {B,Ncs,Bcs})
when ?DIGIT(C), C < $0+B ->
scan_based_int(Cs, St, Line, Col, Toks, {B,[C|Ncs],Bcs});
@@ -1262,7 +1273,7 @@ nl_tabs(8) -> "\n\t\t\t\t\t\t\t";
nl_tabs(9) -> "\n\t\t\t\t\t\t\t\t";
nl_tabs(10) -> "\n\t\t\t\t\t\t\t\t\t";
nl_tabs(11) -> "\n\t\t\t\t\t\t\t\t\t\t".
-
+
tabs(1) -> "\t";
tabs(2) -> "\t\t";
tabs(3) -> "\t\t\t";
@@ -1303,5 +1314,4 @@ reserved_word('bsl') -> true;
reserved_word('bsr') -> true;
reserved_word('or') -> true;
reserved_word('xor') -> true;
-reserved_word('spec') -> true;
reserved_word(_) -> false.
diff --git a/lib/stdlib/src/escript.erl b/lib/stdlib/src/escript.erl
index d26443f277..99e454f593 100644
--- a/lib/stdlib/src/escript.erl
+++ b/lib/stdlib/src/escript.erl
@@ -24,35 +24,41 @@
%% Internal API.
-export([start/0, start/1]).
--include_lib("kernel/include/file.hrl").
+%%-----------------------------------------------------------------------
-define(SHEBANG, "/usr/bin/env escript").
-define(COMMENT, "This is an -*- erlang -*- file").
--record(state, {file,
- module,
+%%-----------------------------------------------------------------------
+
+-type mode() :: 'compile' | 'debug' | 'interpret' | 'run'.
+-type source() :: 'archive' | 'beam' | 'text'.
+
+-record(state, {file :: file:filename(),
+ module :: module(),
forms_or_bin,
- source,
- n_errors,
- mode,
- exports_main,
- has_records}).
--record(sections, {type,
- shebang,
- comment,
- emu_args,
- body}).
--record(extract_options, {compile_source}).
+ source :: source(),
+ n_errors :: non_neg_integer(),
+ mode :: mode(),
+ exports_main :: boolean(),
+ has_records :: boolean()}).
-type shebang() :: string().
-type comment() :: string().
-type emu_args() :: string().
--type escript_filename() :: string().
--type filename() :: string().
+
+-record(sections, {type,
+ shebang :: shebang(),
+ comment :: comment(),
+ emu_args :: emu_args(),
+ body}).
+
+-record(extract_options, {compile_source}).
+
-type zip_file() ::
- filename()
- | {filename(), binary()}
- | {filename(), binary(), #file_info{}}.
+ file:filename()
+ | {file:filename(), binary()}
+ | {file:filename(), binary(), file:file_info()}.
-type zip_create_option() :: term().
-type section() ::
shebang
@@ -60,13 +66,15 @@
| comment
| {comment, comment()}
| {emu_args, emu_args()}
- | {source, filename() | binary()}
- | {beam, filename() | binary()}
- | {archive, filename() | binary()}
+ | {source, file:filename() | binary()}
+ | {beam, file:filename() | binary()}
+ | {archive, file:filename() | binary()}
| {archive, [zip_file()], [zip_create_option()]}.
+%%-----------------------------------------------------------------------
+
%% Create a complete escript file with both header and body
--spec create(escript_filename() | binary, [section()]) ->
+-spec create(file:filename() | binary, [section()]) ->
ok | {ok, binary()} | {error, term()}.
create(File, Options) when is_list(Options) ->
@@ -149,7 +157,9 @@ prepare(BadOptions, _) ->
-type section_name() :: shebang | comment | emu_args | body .
-type extract_option() :: compile_source | {section, [section_name()]}.
--spec extract(filename(), [extract_option()]) -> {ok, [section()]} | {error, term()}.
+-spec extract(file:filename(), [extract_option()]) ->
+ {ok, [section()]} | {error, term()}.
+
extract(File, Options) when is_list(File), is_list(Options) ->
try
EO = parse_extract_options(Options,
@@ -239,6 +249,7 @@ normalize_section(Name, Chars) ->
{Name, Chars}.
-spec script_name() -> string().
+
script_name() ->
[ScriptName|_] = init:get_plain_arguments(),
ScriptName.
@@ -248,10 +259,12 @@ script_name() ->
%%
-spec start() -> no_return().
+
start() ->
start([]).
-spec start([string()]) -> no_return().
+
start(EscriptOptions) ->
try
%% Commands run using -run or -s are run in a process
@@ -484,18 +497,12 @@ find_first_body_line(Fd, HeaderSz0, LineNo, KeepFirst, Sections) ->
classify_line(Line) ->
case Line of
- [$\#, $\! | _] ->
- shebang;
- [$P, $K | _] ->
- archive;
- [$F, $O, $R, $1 | _] ->
- beam;
- [$%, $%, $\! | _] ->
- emu_args;
- [$% | _] ->
- comment;
- _ ->
- undefined
+ "#!" ++ _ -> shebang;
+ "PK" ++ _ -> archive;
+ "FOR1" ++ _ -> beam;
+ "%%!" ++ _ -> emu_args;
+ "%" ++ _ -> comment;
+ _ -> undefined
end.
guess_type(Line) ->
@@ -531,7 +538,6 @@ parse_archive(S, File, HeaderSz) ->
end,
list_to_atom(lists:reverse(RevBase2))
end,
-
S#state{source = archive,
mode = run,
module = Mod,
@@ -587,8 +593,8 @@ parse_source(S, File, Fd, StartLine, HeaderSz, CheckOnly) ->
epp_parse_file2(Epp, S2, [ModForm, FileForm], OptModRes);
{error, _} ->
epp_parse_file2(Epp, S2, [FileForm], OptModRes);
- {eof,LastLine} ->
- S#state{forms_or_bin = [FileForm, {eof,LastLine}]}
+ {eof, _LastLine} = Eof ->
+ S#state{forms_or_bin = [FileForm, Eof]}
end,
ok = epp:close(Epp),
ok = file:close(Fd),
@@ -683,8 +689,8 @@ epp_parse_file2(Epp, S, Forms, Parsed) ->
io:format("~s:~w: ~s\n",
[S#state.file,Ln,Mod:format_error(Args)]),
epp_parse_file(Epp, S#state{n_errors = S#state.n_errors + 1}, [Form | Forms]);
- {eof,LastLine} ->
- S#state{forms_or_bin = lists:reverse([{eof, LastLine} | Forms])}
+ {eof, _LastLine} = Eof ->
+ S#state{forms_or_bin = lists:reverse([Eof | Forms])}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
diff --git a/lib/stdlib/src/ets.erl b/lib/stdlib/src/ets.erl
index 9f84e3639f..1d033f6f7b 100644
--- a/lib/stdlib/src/ets.erl
+++ b/lib/stdlib/src/ets.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(ets).
@@ -42,10 +42,15 @@
-export([i/0, i/1, i/2, i/3]).
-%%------------------------------------------------------------------------------
+-export_type([tab/0, tid/0]).
+
+%%-----------------------------------------------------------------------------
-type tab() :: atom() | tid().
+%% a similar definition is also in erl_types
+-opaque tid() :: integer().
+
-type ext_info() :: 'md5sum' | 'object_count'.
-type protection() :: 'private' | 'protected' | 'public'.
-type type() :: 'bag' | 'duplicate_bag' | 'ordered_set' | 'set'.
@@ -63,7 +68,7 @@
-type match_pattern() :: atom() | tuple().
-type match_specs() :: [{match_pattern(), [_], [_]}].
-%%------------------------------------------------------------------------------
+%%-----------------------------------------------------------------------------
%% The following functions used to be found in this module, but
%% are now BIFs (i.e. implemented in C).
@@ -230,7 +235,7 @@ from_dets(EtsTable, DetsTable) ->
erlang:error(Unexpected,[EtsTable,DetsTable])
end.
--spec to_dets(tab(), dets:tab_name()) -> tab().
+-spec to_dets(tab(), dets:tab_name()) -> dets:tab_name().
to_dets(EtsTable, DetsTable) ->
case (catch dets:from_ets(DetsTable, EtsTable)) of
@@ -622,14 +627,14 @@ do_read_and_verify(ReadFun,InitState,Tab,FtOptions,HeadCount,Verify) ->
end,
{ok,Tab};
{ok,{FinalMD5State,FinalCount,['$end_of_table',LastInfo],_}} ->
- ECount = case lists:keysearch(count,1,LastInfo) of
- {value,{count,N}} ->
+ ECount = case lists:keyfind(count,1,LastInfo) of
+ {count,N} ->
N;
_ ->
false
end,
- EMD5 = case lists:keysearch(md5,1,LastInfo) of
- {value,{md5,M}} ->
+ EMD5 = case lists:keyfind(md5,1,LastInfo) of
+ {md5,M} ->
M;
_ ->
false
@@ -742,22 +747,21 @@ get_header_data(Name,true) ->
false ->
throw(badfile);
true ->
- Major = case lists:keysearch(major,1,L) of
- {value,{major,Maj}} ->
+ Major = case lists:keyfind(major,1,L) of
+ {major,Maj} ->
Maj;
_ ->
0
end,
- Minor = case lists:keysearch(minor,1,L) of
- {value,{minor,Min}} ->
+ Minor = case lists:keyfind(minor,1,L) of
+ {minor,Min} ->
Min;
_ ->
0
end,
FtOptions =
- case lists:keysearch(extended_info,1,L) of
- {value,{extended_info,I}}
- when is_list(I) ->
+ case lists:keyfind(extended_info,1,L) of
+ {extended_info,I} when is_list(I) ->
#filetab_options
{
object_count =
@@ -786,29 +790,28 @@ get_header_data(Name,true) ->
end;
get_header_data(Name, false) ->
- case wrap_chunk(Name,start,1,false) of
+ case wrap_chunk(Name, start, 1, false) of
{C,[Tup]} when is_tuple(Tup) ->
L = tuple_to_list(Tup),
case verify_header_mandatory(L) of
false ->
throw(badfile);
true ->
- Major = case lists:keysearch(major_version,1,L) of
- {value,{major_version,Maj}} ->
+ Major = case lists:keyfind(major_version, 1, L) of
+ {major_version, Maj} ->
Maj;
_ ->
0
end,
- Minor = case lists:keysearch(minor_version,1,L) of
- {value,{minor_version,Min}} ->
+ Minor = case lists:keyfind(minor_version, 1, L) of
+ {minor_version, Min} ->
Min;
_ ->
0
end,
FtOptions =
- case lists:keysearch(extended_info,1,L) of
- {value,{extended_info,I}}
- when is_list(I) ->
+ case lists:keyfind(extended_info, 1, L) of
+ {extended_info, I} when is_list(I) ->
#filetab_options
{
object_count =
@@ -825,25 +828,26 @@ get_header_data(Name, false) ->
throw(badfile)
end.
-md5_and_convert([],MD5State,Count) ->
+md5_and_convert([], MD5State, Count) ->
{[],MD5State,Count,[]};
-md5_and_convert([H|T],MD5State,Count) when is_binary(H) ->
+md5_and_convert([H|T], MD5State, Count) when is_binary(H) ->
case (catch binary_to_term(H)) of
{'EXIT', _} ->
md5_and_convert(T,MD5State,Count);
- ['$end_of_table',Dat] ->
- {[],MD5State,Count,['$end_of_table',Dat]};
+ ['$end_of_table',_Dat] = L ->
+ {[],MD5State,Count,L};
Term ->
- X = erlang:md5_update(MD5State,H),
- {Rest,NewMD5,NewCount,NewLast} = md5_and_convert(T,X,Count+1),
+ X = erlang:md5_update(MD5State, H),
+ {Rest,NewMD5,NewCount,NewLast} = md5_and_convert(T, X, Count+1),
{[Term | Rest],NewMD5,NewCount,NewLast}
end.
-scan_for_endinfo([],Count) ->
+
+scan_for_endinfo([], Count) ->
{[],Count,[]};
-scan_for_endinfo([['$end_of_table',Dat]],Count) ->
+scan_for_endinfo([['$end_of_table',Dat]], Count) ->
{['$end_of_table',Dat],Count,[]};
-scan_for_endinfo([Term|T],Count) ->
- {NewLast,NCount,Rest} = scan_for_endinfo(T,Count+1),
+scan_for_endinfo([Term|T], Count) ->
+ {NewLast,NCount,Rest} = scan_for_endinfo(T, Count+1),
{NewLast,NCount,[Term | Rest]}.
load_table(ReadFun, State, Tab) ->
@@ -852,19 +856,19 @@ load_table(ReadFun, State, Tab) ->
[] ->
{ok,NewState};
List ->
- ets:insert(Tab,List),
- load_table(ReadFun,NewState,Tab)
+ ets:insert(Tab, List),
+ load_table(ReadFun, NewState, Tab)
end.
create_tab(I) ->
- {value, {name, Name}} = lists:keysearch(name, 1, I),
- {value, {type, Type}} = lists:keysearch(type, 1, I),
- {value, {protection, P}} = lists:keysearch(protection, 1, I),
- {value, {named_table, Val}} = lists:keysearch(named_table, 1, I),
- {value, {keypos, Kp}} = lists:keysearch(keypos, 1, I),
- {value, {size, Sz}} = lists:keysearch(size, 1, I),
+ {name, Name} = lists:keyfind(name, 1, I),
+ {type, Type} = lists:keyfind(type, 1, I),
+ {protection, P} = lists:keyfind(protection, 1, I),
+ {named_table, Val} = lists:keyfind(named_table, 1, I),
+ {keypos, _Kp} = Keypos = lists:keyfind(keypos, 1, I),
+ {size, Sz} = lists:keyfind(size, 1, I),
try
- Tab = ets:new(Name, [Type, P, {keypos, Kp} | named_table(Val)]),
+ Tab = ets:new(Name, [Type, P, Keypos | named_table(Val)]),
{ok, Tab, Sz}
catch
_:_ ->
@@ -905,9 +909,9 @@ tabfile_info(File) when is_list(File) ; is_atom(File) ->
{value, Val} = lists:keysearch(named_table, 1, FullHeader),
{value, Kp} = lists:keysearch(keypos, 1, FullHeader),
{value, Sz} = lists:keysearch(size, 1, FullHeader),
- Ei = case lists:keysearch(extended_info, 1, FullHeader) of
- {value, Ei0} -> Ei0;
- _ -> {extended_info, []}
+ Ei = case lists:keyfind(extended_info, 1, FullHeader) of
+ false -> {extended_info, []};
+ Ei0 -> Ei0
end,
{ok, [N,Type,P,Val,Kp,Sz,Ei,{version,{Major,Minor}}]}
catch
@@ -1021,21 +1025,20 @@ options(Option, Keys) ->
options([Option], Keys, []).
options(Options, [Key | Keys], L) when is_list(Options) ->
- V = case lists:keysearch(Key, 1, Options) of
- {value, {n_objects, default}} ->
+ V = case lists:keyfind(Key, 1, Options) of
+ {n_objects, default} ->
{ok, default_option(Key)};
- {value, {n_objects, NObjs}} when is_integer(NObjs),
- NObjs >= 1 ->
+ {n_objects, NObjs} when is_integer(NObjs), NObjs >= 1 ->
{ok, NObjs};
- {value, {traverse, select}} ->
+ {traverse, select} ->
{ok, select};
- {value, {traverse, {select, MS}}} ->
- {ok, {select, MS}};
- {value, {traverse, first_next}} ->
+ {traverse, {select, _MS} = Select} ->
+ {ok, Select};
+ {traverse, first_next} ->
{ok, first_next};
- {value, {traverse, last_prev}} ->
+ {traverse, last_prev} ->
{ok, last_prev};
- {value, {Key, _}} ->
+ {Key, _} ->
badarg;
false ->
Default = default_option(Key),
diff --git a/lib/stdlib/src/file_sorter.erl b/lib/stdlib/src/file_sorter.erl
index e21a0c88f3..3875eca39d 100644
--- a/lib/stdlib/src/file_sorter.erl
+++ b/lib/stdlib/src/file_sorter.erl
@@ -191,7 +191,7 @@ options([{format, Format} | L], Opts) when Format =:= binary;
options([{format, binary_term} | L], Opts) ->
options(L, Opts#opts{format = binary_term_fun()});
options([{size, Size} | L], Opts) when is_integer(Size), Size >= 0 ->
- options(L, Opts#opts{size = max(Size, 1)});
+ options(L, Opts#opts{size = erlang:max(Size, 1)});
options([{no_files, NoFiles} | L], Opts) when is_integer(NoFiles),
NoFiles > 1 ->
options(L, Opts#opts{no_files = NoFiles});
@@ -997,10 +997,10 @@ close_read_fun(Fd, FileName, fsort) ->
file:delete(FileName).
read_objs(Fd, FileName, I, L, Bin0, Size0, LSz, W) ->
- Max = max(Size0, ?CHUNKSIZE),
+ Max = erlang:max(Size0, ?CHUNKSIZE),
BSz0 = byte_size(Bin0),
Min = Size0 - BSz0 + W#w.hdlen, % Min > 0
- NoBytes = max(Min, Max),
+ NoBytes = erlang:max(Min, Max),
case read(Fd, FileName, NoBytes, W) of
{ok, Bin} ->
BSz = byte_size(Bin),
@@ -1180,9 +1180,6 @@ make_key2([Kp], T) ->
make_key2([Kp | Kps], T) ->
[element(Kp, T) | make_key2(Kps, T)].
-max(A, B) when A < B -> B;
-max(A, _) -> A.
-
infun(W) ->
W1 = W#w{in = undefined},
try (W#w.in)(read) of
diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl
index 5aab547644..43df6f621d 100644
--- a/lib/stdlib/src/gen.erl
+++ b/lib/stdlib/src/gen.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(gen).
@@ -212,7 +212,22 @@ do_call(Process, Label, Request, Timeout) ->
catch erlang:send(Process, {Label, {self(), Mref}, Request},
[noconnect]),
- wait_resp_mon(Node, Mref, Timeout)
+ receive
+ {Mref, Reply} ->
+ erlang:demonitor(Mref, [flush]),
+ {ok, Reply};
+ {'DOWN', Mref, _, _, noconnection} ->
+ exit({nodedown, Node});
+ {'DOWN', Mref, _, _, Reason} ->
+ exit(Reason)
+ after Timeout ->
+ erlang:demonitor(Mref),
+ receive
+ {'DOWN', Mref, _, _, _} -> true
+ after 0 -> true
+ end,
+ exit(timeout)
+ end
catch
error:_ ->
%% Node (C/Java?) is not supporting the monitor.
@@ -233,24 +248,6 @@ do_call(Process, Label, Request, Timeout) ->
end
end.
-wait_resp_mon(Node, Mref, Timeout) ->
- receive
- {Mref, Reply} ->
- erlang:demonitor(Mref, [flush]),
- {ok, Reply};
- {'DOWN', Mref, _, _, noconnection} ->
- exit({nodedown, Node});
- {'DOWN', Mref, _, _, Reason} ->
- exit(Reason)
- after Timeout ->
- erlang:demonitor(Mref),
- receive
- {'DOWN', Mref, _, _, _} -> true
- after 0 -> true
- end,
- exit(timeout)
- end.
-
wait_resp(Node, Tag, Timeout) ->
receive
{Tag, Reply} ->
diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl
index 1b30aaf5eb..b1e9e3a02f 100644
--- a/lib/stdlib/src/gen_event.erl
+++ b/lib/stdlib/src/gen_event.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(gen_event).
@@ -42,7 +42,6 @@
system_continue/3,
system_terminate/4,
system_code_change/4,
- print_event/3,
format_status/2]).
-import(error_logger, [error_msg/2]).
@@ -239,7 +238,7 @@ fetch_msg(Parent, ServerName, MSL, Debug, Hib) ->
Msg when Debug =:= [] ->
handle_msg(Msg, Parent, ServerName, MSL, []);
Msg ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
ServerName, {in, Msg}),
handle_msg(Msg, Parent, ServerName, MSL, Debug1)
end.
@@ -678,12 +677,23 @@ report_error(Handler, Reason, State, LastIn, SName) ->
_ ->
Reason
end,
+ Mod = Handler#handler.module,
+ FmtState = case erlang:function_exported(Mod, format_status, 2) of
+ true ->
+ Args = [get(), State],
+ case catch Mod:format_status(terminate, Args) of
+ {'EXIT', _} -> State;
+ Else -> Else
+ end;
+ _ ->
+ State
+ end,
error_msg("** gen_event handler ~p crashed.~n"
"** Was installed in ~p~n"
"** Last event was: ~p~n"
"** When handler state == ~p~n"
"** Reason == ~p~n",
- [handler(Handler),SName,LastIn,State,Reason1]).
+ [handler(Handler),SName,LastIn,FmtState,Reason1]).
handler(Handler) when not Handler#handler.id ->
Handler#handler.module;
@@ -712,10 +722,20 @@ get_modules(MSL) ->
%%-----------------------------------------------------------------
%% Status information
%%-----------------------------------------------------------------
-format_status(_Opt, StatusData) ->
- [_PDict, SysState, Parent, _Debug, [ServerName, MSL, _Hib]] = StatusData,
+format_status(Opt, StatusData) ->
+ [PDict, SysState, Parent, _Debug, [ServerName, MSL, _Hib]] = StatusData,
Header = lists:concat(["Status for event handler ", ServerName]),
+ FmtMSL = [case erlang:function_exported(Mod, format_status, 2) of
+ true ->
+ Args = [PDict, State],
+ case catch Mod:format_status(Opt, Args) of
+ {'EXIT', _} -> MSL;
+ Else -> MS#handler{state = Else}
+ end;
+ _ ->
+ MS
+ end || #handler{module = Mod, state = State} = MS <- MSL],
[{header, Header},
{data, [{"Status", SysState},
{"Parent", Parent}]},
- {items, {"Installed handlers", MSL}}].
+ {items, {"Installed handlers", FmtMSL}}].
diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl
index ba0275ae2b..7d9960b912 100644
--- a/lib/stdlib/src/gen_fsm.erl
+++ b/lib/stdlib/src/gen_fsm.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(gen_fsm).
@@ -116,7 +116,7 @@
-export([behaviour_info/1]).
%% Internal exports
--export([init_it/6, print_event/3,
+-export([init_it/6,
system_continue/3,
system_terminate/4,
system_code_change/4,
@@ -376,7 +376,7 @@ decode_msg(Msg,Parent, Name, StateName, StateData, Mod, Time, Debug, Hib) ->
_Msg when Debug =:= [] ->
handle_msg(Msg, Parent, Name, StateName, StateData, Mod, Time);
_Msg ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
{Name, StateName}, {in, Msg}),
handle_msg(Msg, Parent, Name, StateName, StateData,
Mod, Time, Debug1)
@@ -466,11 +466,11 @@ handle_msg(Msg, Parent, Name, StateName, StateData, Mod, _Time, Debug) ->
From = from(Msg),
case catch dispatch(Msg, Mod, StateName, StateData) of
{next_state, NStateName, NStateData} ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
{Name, NStateName}, return),
loop(Parent, Name, NStateName, NStateData, Mod, infinity, Debug1);
{next_state, NStateName, NStateData, Time1} ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
{Name, NStateName}, return),
loop(Parent, Name, NStateName, NStateData, Mod, Time1, Debug1);
{reply, Reply, NStateName, NStateData} when From =/= undefined ->
@@ -519,7 +519,7 @@ reply({To, Tag}, Reply) ->
reply(Name, {To, Tag}, Reply, Debug, StateName) ->
reply({To, Tag}, Reply),
- sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ sys:handle_debug(Debug, fun print_event/3, Name,
{out, Reply, To, StateName}).
%%% ---------------------------------------------------
@@ -542,7 +542,18 @@ terminate(Reason, Name, Msg, Mod, StateName, StateData, Debug) ->
{shutdown,_}=Shutdown ->
exit(Shutdown);
_ ->
- error_info(Reason, Name, Msg, StateName, StateData, Debug),
+ FmtStateData =
+ case erlang:function_exported(Mod, format_status, 2) of
+ true ->
+ Args = [get(), StateData],
+ case catch Mod:format_status(terminate, Args) of
+ {'EXIT', _} -> StateData;
+ Else -> Else
+ end;
+ _ ->
+ StateData
+ end,
+ error_info(Reason,Name,Msg,StateName,FmtStateData,Debug),
exit(Reason)
end
end.
@@ -603,22 +614,27 @@ get_msg(Msg) -> Msg.
format_status(Opt, StatusData) ->
[PDict, SysState, Parent, Debug, [Name, StateName, StateData, Mod, _Time]] =
StatusData,
- NameTag = if is_pid(Name) ->
- pid_to_list(Name);
- is_atom(Name) ->
- Name
- end,
- Header = lists:concat(["Status for state machine ", NameTag]),
+ StatusHdr = "Status for state machine",
+ Header = if
+ is_pid(Name) ->
+ lists:concat([StatusHdr, " ", pid_to_list(Name)]);
+ is_atom(Name); is_list(Name) ->
+ lists:concat([StatusHdr, " ", Name]);
+ true ->
+ {StatusHdr, Name}
+ end,
Log = sys:get_debug(log, Debug, []),
- Specfic =
+ DefaultStatus = [{data, [{"StateData", StateData}]}],
+ Specfic =
case erlang:function_exported(Mod, format_status, 2) of
true ->
case catch Mod:format_status(Opt,[PDict,StateData]) of
- {'EXIT', _} -> [{data, [{"StateData", StateData}]}];
- Else -> Else
+ {'EXIT', _} -> DefaultStatus;
+ StatusList when is_list(StatusList) -> StatusList;
+ Else -> [Else]
end;
_ ->
- [{data, [{"StateData", StateData}]}]
+ DefaultStatus
end,
[{header, Header},
{data, [{"Status", SysState},
diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl
index f1a9a31c63..ac81df9cab 100644
--- a/lib/stdlib/src/gen_server.erl
+++ b/lib/stdlib/src/gen_server.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(gen_server).
@@ -103,7 +103,7 @@
format_status/2]).
%% Internal exports
--export([init_it/6, print_event/3]).
+-export([init_it/6]).
-import(error_logger, [format/2]).
@@ -353,7 +353,7 @@ decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
_Msg when Debug =:= [] ->
handle_msg(Msg, Parent, Name, State, Mod);
_Msg ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event},
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
Name, {in, Msg}),
handle_msg(Msg, Parent, Name, State, Mod, Debug1)
end.
@@ -589,11 +589,11 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) ->
Debug1 = reply(Name, From, Reply, NState, Debug),
loop(Parent, Name, NState, Mod, Time1, Debug1);
{noreply, NState} ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ Debug1 = sys:handle_debug(Debug, fun print_event/3, Name,
{noreply, NState}),
loop(Parent, Name, NState, Mod, infinity, Debug1);
{noreply, NState, Time1} ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ Debug1 = sys:handle_debug(Debug, fun print_event/3, Name,
{noreply, NState}),
loop(Parent, Name, NState, Mod, Time1, Debug1);
{stop, Reason, Reply, NState} ->
@@ -625,11 +625,11 @@ handle_common_reply(Reply, Parent, Name, Msg, Mod, State) ->
handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) ->
case Reply of
{noreply, NState} ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ Debug1 = sys:handle_debug(Debug, fun print_event/3, Name,
{noreply, NState}),
loop(Parent, Name, NState, Mod, infinity, Debug1);
{noreply, NState, Time1} ->
- Debug1 = sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ Debug1 = sys:handle_debug(Debug, fun print_event/3, Name,
{noreply, NState}),
loop(Parent, Name, NState, Mod, Time1, Debug1);
{stop, Reason, NState} ->
@@ -642,7 +642,7 @@ handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) ->
reply(Name, {To, Tag}, Reply, State, Debug) ->
reply({To, Tag}, Reply),
- sys:handle_debug(Debug, {?MODULE, print_event}, Name,
+ sys:handle_debug(Debug, fun print_event/3, Name,
{out, Reply, To, State} ).
@@ -705,7 +705,18 @@ terminate(Reason, Name, Msg, Mod, State, Debug) ->
{shutdown,_}=Shutdown ->
exit(Shutdown);
_ ->
- error_info(Reason, Name, Msg, State, Debug),
+ FmtState =
+ case erlang:function_exported(Mod, format_status, 2) of
+ true ->
+ Args = [get(), State],
+ case catch Mod:format_status(terminate, Args) of
+ {'EXIT', _} -> State;
+ Else -> Else
+ end;
+ _ ->
+ State
+ end,
+ error_info(Reason, Name, Msg, FmtState, Debug),
exit(Reason)
end
end.
@@ -829,22 +840,27 @@ name_to_pid(Name) ->
%%-----------------------------------------------------------------
format_status(Opt, StatusData) ->
[PDict, SysState, Parent, Debug, [Name, State, Mod, _Time]] = StatusData,
- NameTag = if is_pid(Name) ->
- pid_to_list(Name);
- is_atom(Name) ->
- Name
- end,
- Header = lists:concat(["Status for generic server ", NameTag]),
+ StatusHdr = "Status for generic server",
+ Header = if
+ is_pid(Name) ->
+ lists:concat([StatusHdr, " ", pid_to_list(Name)]);
+ is_atom(Name); is_list(Name) ->
+ lists:concat([StatusHdr, " ", Name]);
+ true ->
+ {StatusHdr, Name}
+ end,
Log = sys:get_debug(log, Debug, []),
- Specfic =
+ DefaultStatus = [{data, [{"State", State}]}],
+ Specfic =
case erlang:function_exported(Mod, format_status, 2) of
true ->
case catch Mod:format_status(Opt, [PDict, State]) of
- {'EXIT', _} -> [{data, [{"State", State}]}];
- Else -> Else
+ {'EXIT', _} -> DefaultStatus;
+ StatusList when is_list(StatusList) -> StatusList;
+ Else -> [Else]
end;
_ ->
- [{data, [{"State", State}]}]
+ DefaultStatus
end,
[{header, Header},
{data, [{"Status", SysState},
diff --git a/lib/stdlib/src/io.erl b/lib/stdlib/src/io.erl
index 1f8076e864..1d0f9374bc 100644
--- a/lib/stdlib/src/io.erl
+++ b/lib/stdlib/src/io.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
-module(io).
@@ -32,6 +32,7 @@
parse_erl_form/1,parse_erl_form/2,parse_erl_form/3]).
-export([request/1,request/2,requests/1,requests/2]).
+-export_type([device/0, format/0]).
%%-------------------------------------------------------------------------
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 26f6ec8931..4ca9d079b7 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -75,6 +75,8 @@
collect_line/2, collect_line/3, collect_line/4,
get_until/3, get_until/4]).
+-export_type([chars/0]).
+
%%----------------------------------------------------------------------
%% XXX: overapproximates a deep list of (unicode) characters
diff --git a/lib/stdlib/src/io_lib_fread.erl b/lib/stdlib/src/io_lib_fread.erl
index 74316dc730..33553692bc 100644
--- a/lib/stdlib/src/io_lib_fread.erl
+++ b/lib/stdlib/src/io_lib_fread.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
-%%
+%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
-%%
+%%
%% %CopyrightEnd%
%%
-module(io_lib_fread).
@@ -22,6 +22,8 @@
-export([fread/2,fread/3]).
+-export_type([continuation/0, fread_2_ret/0, fread_3_ret/0]).
+
-import(lists, [reverse/1,reverse/2]).
%%-----------------------------------------------------------------------
diff --git a/lib/stdlib/src/lists.erl b/lib/stdlib/src/lists.erl
index e1f8d1c200..08ee595f4d 100644
--- a/lib/stdlib/src/lists.erl
+++ b/lib/stdlib/src/lists.erl
@@ -1,23 +1,26 @@
%%
%% %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
%% 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(lists).
+-compile({no_auto_import,[max/2]}).
+-compile({no_auto_import,[min/2]}).
+
-export([append/2, append/1, subtract/2, reverse/1,
nth/2, nthtail/2, prefix/2, suffix/2, last/1,
seq/2, seq/3, sum/1, duplicate/2, min/1, max/1, sublist/2, sublist/3,
@@ -25,7 +28,7 @@
unzip/1, unzip3/1, zip/2, zip3/3, zipwith/3, zipwith3/4,
sort/1, merge/1, merge/2, rmerge/2, merge3/3, rmerge3/3,
usort/1, umerge/1, umerge3/3, umerge/2, rumerge3/3, rumerge/2,
- concat/1, flatten/1, flatten/2, flat_length/1, flatlength/1,
+ concat/1, flatten/1, flatten/2, flatlength/1,
keydelete/3, keyreplace/4, keytake/3, keystore/4,
keysort/2, keymerge/3, rkeymerge/3, rukeymerge/3,
ukeysort/2, ukeymerge/3, keymap/3]).
@@ -40,8 +43,6 @@
mapfoldl/3,mapfoldr/3,foreach/2,takewhile/2,dropwhile/2,splitwith/2,
split/2]).
--deprecated([flat_length/1]).
-
%% member(X, L) -> (true | false)
%% test if X is a member of the list L
%% Now a BIF!
@@ -436,13 +437,6 @@ do_flatten([H|T], Tail) ->
do_flatten([], Tail) ->
Tail.
-%% flat_length(List) (undocumented can be removed later)
-%% Calculate the length of a list of lists.
-
--spec flat_length([_]) -> non_neg_integer().
-
-flat_length(List) -> flatlength(List).
-
%% flatlength(List)
%% Calculate the length of a list of lists.
diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl
index 7ea7de8d58..1514414e48 100644
--- a/lib/stdlib/src/otp_internal.erl
+++ b/lib/stdlib/src/otp_internal.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1999-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(otp_internal).
@@ -236,109 +236,131 @@ obsolete_1(erlang, fault, 2) ->
obsolete_1(file, rawopen, 2) ->
{removed, "deprecated (will be removed in R13B); use file:open/2 with the raw option"};
-obsolete_1(httpd, start, 0) -> {deprecated,{inets,start,[2,3]},"R14B"};
-obsolete_1(httpd, start, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
-obsolete_1(httpd, start_link, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
-obsolete_1(httpd, start_child, 0) -> {deprecated,{inets,start,[2,3]},"R14B"};
-obsolete_1(httpd, start_child, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
-obsolete_1(httpd, stop, 0) -> {deprecated,{inets,stop,2},"R14B"};
-obsolete_1(httpd, stop, 1) -> {deprecated,{inets,stop,2},"R14B"};
-obsolete_1(httpd, stop, 2) -> {deprecated,{inets,stop,2},"R14B"};
-obsolete_1(httpd, stop_child, 0) -> {deprecated,{inets,stop,2},"R14B"};
-obsolete_1(httpd, stop_child, 1) -> {deprecated,{inets,stop,2},"R14B"};
-obsolete_1(httpd, stop_child, 2) -> {deprecated,{inets,stop,2},"R14B"};
-obsolete_1(httpd, restart, 0) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, restart, 1) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, restart, 2) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, block, 0) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, block, 1) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, block, 2) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, block, 3) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, block, 4) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, unblock, 0) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, unblock, 1) -> {deprecated,{httpd,reload_config,2},"R14B"};
-obsolete_1(httpd, unblock, 2) -> {deprecated,{httpd,reload_config,2},"R14B"};
+obsolete_1(http, request, 1) -> {deprecated,{httpc,request,1},"R15B"};
+obsolete_1(http, request, 2) -> {deprecated,{httpc,request,2},"R15B"};
+obsolete_1(http, request, 4) -> {deprecated,{httpc,request,4},"R15B"};
+obsolete_1(http, request, 5) -> {deprecated,{httpc,request,5},"R15B"};
+obsolete_1(http, cancel_request, 1) -> {deprecated,{httpc,cancel_request,1},"R15B"};
+obsolete_1(http, cancel_request, 2) -> {deprecated,{httpc,cancel_request,2},"R15B"};
+obsolete_1(http, set_option, 2) -> {deprecated,{httpc,set_option,2},"R15B"};
+obsolete_1(http, set_option, 3) -> {deprecated,{httpc,set_option,3},"R15B"};
+obsolete_1(http, set_options, 1) -> {deprecated,{httpc,set_options,1},"R15B"};
+obsolete_1(http, set_options, 2) -> {deprecated,{httpc,set_options,2},"R15B"};
+obsolete_1(http, verify_cookies, 2) -> {deprecated,{httpc,verify_cookies,2},"R15B"};
+obsolete_1(http, verify_cookies, 3) -> {deprecated,{httpc,verify_cookies,3},"R15B"};
+obsolete_1(http, cookie_header, 1) -> {deprecated,{httpc,cookie_header,1},"R15B"};
+obsolete_1(http, cookie_header, 2) -> {deprecated,{httpc,cookie_header,2},"R15B"};
+obsolete_1(http, stream_next, 1) -> {deprecated,{httpc,stream_next,1},"R15B"};
+obsolete_1(http, default_profile, 0) -> {deprecated,{httpc,default_profile,0},"R15B"};
+
+obsolete_1(httpd, start, 0) -> {removed,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start, 1) -> {removed,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start_link, 0) -> {removed,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start_link, 1) -> {removed,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start_child, 0) -> {removed,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, start_child, 1) -> {removed,{inets,start,[2,3]},"R14B"};
+obsolete_1(httpd, stop, 0) -> {removed,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop, 1) -> {removed,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop, 2) -> {removed,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop_child, 0) -> {removed,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop_child, 1) -> {removed,{inets,stop,2},"R14B"};
+obsolete_1(httpd, stop_child, 2) -> {removed,{inets,stop,2},"R14B"};
+obsolete_1(httpd, restart, 0) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, restart, 1) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, restart, 2) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 0) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 1) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 2) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 3) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, block, 4) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, unblock, 0) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, unblock, 1) -> {removed,{httpd,reload_config,2},"R14B"};
+obsolete_1(httpd, unblock, 2) -> {removed,{httpd,reload_config,2},"R14B"};
obsolete_1(httpd_util, key1search, 2) -> {removed,{proplists,get_value,2},"R13B"};
obsolete_1(httpd_util, key1search, 3) -> {removed,{proplists,get_value,3},"R13B"};
-obsolete_1(ftp, open, 3) -> {deprecated,{inets,start,[2,3]},"R14B"};
-obsolete_1(ftp, force_active, 1) -> {deprecated,{inets,start,[2,3]},"R14B"};
+obsolete_1(ftp, open, 3) -> {removed,{inets,start,[2,3]},"R14B"};
+obsolete_1(ftp, force_active, 1) -> {removed,{inets,start,[2,3]},"R14B"};
%% Added in R12B-4.
obsolete_1(ssh_cm, connect, A) when 1 =< A, A =< 3 ->
- {deprecated,{ssh,connect,A},"R14B"};
+ {removed,{ssh,connect,A},"R14B"};
obsolete_1(ssh_cm, listen, A) when 2 =< A, A =< 4 ->
- {deprecated,{ssh,daemon,A},"R14B"};
+ {removed,{ssh,daemon,A},"R14B"};
obsolete_1(ssh_cm, stop_listener, 1) ->
- {deprecated,{ssh,stop_listener,[1,2]},"R14B"};
+ {removed,{ssh,stop_listener,[1,2]},"R14B"};
obsolete_1(ssh_cm, session_open, A) when A =:= 2; A =:= 4 ->
- {deprecated,{ssh_connection,session_channel,A},"R14B"};
+ {removed,{ssh_connection,session_channel,A},"R14B"};
obsolete_1(ssh_cm, direct_tcpip, A) when A =:= 6; A =:= 8 ->
- {deprecated,{ssh_connection,direct_tcpip,A}};
+ {removed,{ssh_connection,direct_tcpip,A}};
obsolete_1(ssh_cm, tcpip_forward, 3) ->
- {deprecated,{ssh_connection,tcpip_forward,3},"R14B"};
+ {removed,{ssh_connection,tcpip_forward,3},"R14B"};
obsolete_1(ssh_cm, cancel_tcpip_forward, 3) ->
- {deprecated,{ssh_connection,cancel_tcpip_forward,3},"R14B"};
+ {removed,{ssh_connection,cancel_tcpip_forward,3},"R14B"};
obsolete_1(ssh_cm, open_pty, A) when A =:= 3; A =:= 7; A =:= 9 ->
- {deprecated,{ssh_connection,open_pty,A},"R14"};
+ {removed,{ssh_connection,open_pty,A},"R14"};
obsolete_1(ssh_cm, setenv, 5) ->
- {deprecated,{ssh_connection,setenv,5},"R14B"};
+ {removed,{ssh_connection,setenv,5},"R14B"};
obsolete_1(ssh_cm, shell, 2) ->
- {deprecated,{ssh_connection,shell,2},"R14B"};
+ {removed,{ssh_connection,shell,2},"R14B"};
obsolete_1(ssh_cm, exec, 4) ->
- {deprecated,{ssh_connection,exec,4},"R14B"};
+ {removed,{ssh_connection,exec,4},"R14B"};
obsolete_1(ssh_cm, subsystem, 4) ->
- {deprecated,{ssh_connection,subsystem,4},"R14B"};
+ {removed,{ssh_connection,subsystem,4},"R14B"};
obsolete_1(ssh_cm, winch, A) when A =:= 4; A =:= 6 ->
- {deprecated,{ssh_connection,window_change,A},"R14B"};
+ {removed,{ssh_connection,window_change,A},"R14B"};
obsolete_1(ssh_cm, signal, 3) ->
- {deprecated,{ssh_connection,signal,3},"R14B"};
+ {removed,{ssh_connection,signal,3},"R14B"};
obsolete_1(ssh_cm, attach, A) when A =:= 2; A =:= 3 ->
- {deprecated,{ssh,attach,A}};
+ {removed,{ssh,attach,A}};
obsolete_1(ssh_cm, detach, 2) ->
- {deprecated,"no longer useful; will be removed in R14B"};
+ {removed,"no longer useful; will be removed in R14B"};
obsolete_1(ssh_cm, set_user_ack, 4) ->
- {deprecated,"no longer useful; will be removed in R14B"};
+ {removed,"no longer useful; will be removed in R14B"};
obsolete_1(ssh_cm, adjust_window, 3) ->
- {deprecated,{ssh_connection,adjust_window,3},"R14B"};
+ {removed,{ssh_connection,adjust_window,3},"R14B"};
obsolete_1(ssh_cm, close, 2) ->
- {deprecated,{ssh_connection,close,2},"R14B"};
+ {removed,{ssh_connection,close,2},"R14B"};
obsolete_1(ssh_cm, stop, 1) ->
- {deprecated,{ssh,close,1},"R14B"};
+ {removed,{ssh,close,1},"R14B"};
obsolete_1(ssh_cm, send_eof, 2) ->
- {deprecated,{ssh_connection,send_eof,2},"R14B"};
+ {removed,{ssh_connection,send_eof,2},"R14B"};
obsolete_1(ssh_cm, send, A) when A =:= 3; A =:= 4 ->
- {deprecated,{ssh_connection,send,A},"R14B"};
+ {removed,{ssh_connection,send,A},"R14B"};
obsolete_1(ssh_cm, send_ack, A) when 3 =< A, A =< 5 ->
- {deprecated,{ssh_connection,send,[3,4]},"R14B"};
+ {removed,{ssh_connection,send,[3,4]},"R14B"};
obsolete_1(ssh_ssh, connect, A) when 1 =< A, A =< 3 ->
- {deprecated,{ssh,shell,A},"R14B"};
+ {removed,{ssh,shell,A},"R14B"};
obsolete_1(ssh_sshd, listen, A) when 0 =< A, A =< 3 ->
- {deprecated,{ssh,daemon,[1,2,3]},"R14"};
+ {removed,{ssh,daemon,[1,2,3]},"R14"};
obsolete_1(ssh_sshd, stop, 1) ->
- {deprecated,{ssh,stop_listener,1}};
+ {removed,{ssh,stop_listener,1}};
%% Added in R13A.
obsolete_1(regexp, _, _) ->
{deprecated, "the regexp module is deprecated (will be removed in R15A); use the re module instead"};
obsolete_1(lists, flat_length, 1) ->
- {deprecated,{lists,flatlength,1},"R14"};
+ {removed,{lists,flatlength,1},"R14"};
obsolete_1(ssh_sftp, connect, A) when 1 =< A, A =< 3 ->
- {deprecated,{ssh_sftp,start_channel,A},"R14B"};
+ {removed,{ssh_sftp,start_channel,A},"R14B"};
obsolete_1(ssh_sftp, stop, 1) ->
- {deprecated,{ssh_sftp,stop_channel,1},"R14B"};
+ {removed,{ssh_sftp,stop_channel,1},"R14B"};
%% Added in R13B01.
obsolete_1(ssl_pkix, decode_cert_file, A) when A =:= 1; A =:= 2 ->
- {deprecated,"deprecated (will be removed in R14B); use public_key:pem_to_der/1 and public_key:pkix_decode_cert/2 instead"};
+ {removed,"removed in R14A; use public_key:pem_to_der/1 and public_key:pkix_decode_cert/2 instead"};
obsolete_1(ssl_pkix, decode_cert, A) when A =:= 1; A =:= 2 ->
- {deprecated,{public_key,pkix_decode_cert,2},"R14B"};
+ {removed,{public_key,pkix_decode_cert,2},"R14A"};
%% Added in R13B04.
obsolete_1(erlang, concat_binary, 1) ->
- {deprecated,{erlang,list_to_binary,1},"R14B"};
-
+ {deprecated,{erlang,list_to_binary,1},"R15B"};
+
+%% Added in R14A.
+obsolete_1(ssl, peercert, 2) ->
+ {deprecated,"deprecated (will be removed in R15A); use ssl:peercert/1 and public_key:pkix_decode_cert/2 instead"};
+
obsolete_1(_, _, _) ->
no.
diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl
index 9aa5e0a71e..4fb64a3353 100644
--- a/lib/stdlib/src/proc_lib.erl
+++ b/lib/stdlib/src/proc_lib.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(proc_lib).
@@ -34,6 +34,8 @@
%% Internal exports.
-export([wake_up/3]).
+-export_type([spawn_option/0]).
+
%%-----------------------------------------------------------------------------
-type priority_level() :: 'high' | 'low' | 'max' | 'normal'.
diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl
index 35d14891f1..6a45e0f868 100644
--- a/lib/stdlib/src/proplists.erl
+++ b/lib/stdlib/src/proplists.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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%
%%
%% =====================================================================
@@ -49,6 +49,8 @@
%% ---------------------------------------------------------------------
+-export_type([property/0]).
+
-type property() :: atom() | tuple().
-type aliases() :: [{any(), any()}].
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index 3e52c48e42..9d15f01683 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -1,20 +1,20 @@
%% This is an -*- erlang -*- file.
%%
%% %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
%% 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%
%%
{application, stdlib,
@@ -23,6 +23,7 @@
{modules, [array,
base64,
beam_lib,
+ binary,
c,
calendar,
dets,
diff --git a/lib/stdlib/src/supervisor.erl b/lib/stdlib/src/supervisor.erl
index 22269a8d1b..f5d5441184 100644
--- a/lib/stdlib/src/supervisor.erl
+++ b/lib/stdlib/src/supervisor.erl
@@ -21,7 +21,7 @@
-behaviour(gen_server).
%% External exports
--export([start_link/2,start_link/3,
+-export([start_link/2, start_link/3,
start_child/2, restart_child/2,
delete_child/2, terminate_child/2,
which_children/1, count_children/1,
@@ -33,25 +33,47 @@
-export([init/1, handle_call/3, handle_info/2, terminate/2, code_change/3]).
-export([handle_cast/2]).
+-export_type([child_spec/0, strategy/0]).
+
+%%--------------------------------------------------------------------------
+
+-type child_id() :: pid() | 'undefined'.
+-type mfargs() :: {module(), atom(), [term()]}.
+-type modules() :: [module()] | 'dynamic'.
+-type restart() :: 'permanent' | 'transient' | 'temporary'.
+-type shutdown() :: 'brutal_kill' | timeout().
+-type worker() :: 'worker' | 'supervisor'.
+-type sup_name() :: {'local', atom()} | {'global', atom()}.
+-type sup_ref() :: atom() | {atom(), atom()} | {'global', atom()} | pid().
+-type child_spec() :: {term(),mfargs(),restart(),shutdown(),worker(),modules()}.
+
+-type strategy() :: 'one_for_all' | 'one_for_one'
+ | 'rest_for_one' | 'simple_one_for_one'.
+
+%%--------------------------------------------------------------------------
+
+-record(child, {% pid is undefined when child is not running
+ pid = undefined :: child_id(),
+ name,
+ mfargs :: mfargs(),
+ restart_type :: restart(),
+ shutdown :: shutdown(),
+ child_type :: worker(),
+ modules = [] :: modules()}).
+-type child() :: #child{}.
+
-define(DICT, dict).
-record(state, {name,
- strategy,
- children = [],
- dynamics = ?DICT:new(),
- intensity,
- period,
+ strategy :: strategy(),
+ children = [] :: [child()],
+ dynamics = ?DICT:new() :: ?DICT(),
+ intensity :: non_neg_integer(),
+ period :: pos_integer(),
restarts = [],
module,
args}).
-
--record(child, {pid = undefined, % pid is undefined when child is not running
- name,
- mfa,
- restart_type,
- shutdown,
- child_type,
- modules = []}).
+-type state() :: #state{}.
-define(is_simple(State), State#state.strategy =:= simple_one_for_one).
@@ -65,21 +87,40 @@ behaviour_info(_Other) ->
%%% Servers/processes should/could also be built using gen_server.erl.
%%% SupName = {local, atom()} | {global, atom()}.
%%% ---------------------------------------------------
+
+-type startlink_err() :: {'already_started', pid()} | 'shutdown' | term().
+-type startlink_ret() :: {'ok', pid()} | 'ignore' | {'error', startlink_err()}.
+
+-spec start_link(module(), term()) -> startlink_ret().
start_link(Mod, Args) ->
gen_server:start_link(supervisor, {self, Mod, Args}, []).
+-spec start_link(sup_name(), module(), term()) -> startlink_ret().
start_link(SupName, Mod, Args) ->
gen_server:start_link(SupName, supervisor, {SupName, Mod, Args}, []).
%%% ---------------------------------------------------
%%% Interface functions.
%%% ---------------------------------------------------
+
+-type info() :: term().
+-type startchild_err() :: 'already_present'
+ | {'already_started', child_id()} | term().
+-type startchild_ret() :: {'ok', child_id()} | {'ok', child_id(), info()}
+ | {'error', startchild_err()}.
+
+-spec start_child(sup_ref(), child_spec() | [term()]) -> startchild_ret().
start_child(Supervisor, ChildSpec) ->
call(Supervisor, {start_child, ChildSpec}).
+-type restart_err() :: 'running' | 'not_found' | 'simple_one_for_one' | term().
+-spec restart_child(sup_ref(), term()) ->
+ {'ok', child_id()} | {'ok', child_id(), info()} | {'error', restart_err()}.
restart_child(Supervisor, Name) ->
call(Supervisor, {restart_child, Name}).
+-type del_err() :: 'running' | 'not_found' | 'simple_one_for_one'.
+-spec delete_child(sup_ref(), term()) -> 'ok' | {'error', del_err()}.
delete_child(Supervisor, Name) ->
call(Supervisor, {delete_child, Name}).
@@ -89,9 +130,13 @@ delete_child(Supervisor, Name) ->
%% Note that the child is *always* terminated in some
%% way (maybe killed).
%%-----------------------------------------------------------------
+
+-type term_err() :: 'not_found' | 'simple_one_for_one'.
+-spec terminate_child(sup_ref(), term()) -> 'ok' | {'error', term_err()}.
terminate_child(Supervisor, Name) ->
call(Supervisor, {terminate_child, Name}).
+-spec which_children(sup_ref()) -> [{term(), child_id(), worker(), modules()}].
which_children(Supervisor) ->
call(Supervisor, which_children).
@@ -101,6 +146,7 @@ count_children(Supervisor) ->
call(Supervisor, Req) ->
gen_server:call(Supervisor, Req, infinity).
+-spec check_childspecs([child_spec()]) -> 'ok' | {'error', term()}.
check_childspecs(ChildSpecs) when is_list(ChildSpecs) ->
case check_startspec(ChildSpecs) of
{ok, _} -> ok;
@@ -113,6 +159,14 @@ check_childspecs(X) -> {error, {badarg, X}}.
%%% Initialize the supervisor.
%%%
%%% ---------------------------------------------------
+
+-type stop_rsn() :: 'shutdown' | {'bad_return', {module(),'init', term()}}
+ | {'bad_start_spec', term()} | {'start_spec', term()}
+ | {'supervisor_data', term()}.
+
+-spec init({sup_name(), module(), [term()]}) ->
+ {'ok', state()} | 'ignore' | {'stop', stop_rsn()}.
+
init({SupName, Mod, Args}) ->
process_flag(trap_exit, true),
case Mod:init(Args) of
@@ -158,12 +212,12 @@ init_dynamic(_State, StartSpec) ->
%%-----------------------------------------------------------------
%% Func: start_children/2
-%% Args: Children = [#child] in start order
-%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod}
+%% Args: Children = [child()] in start order
+%% SupName = {local, atom()} | {global, atom()} | {pid(), Mod}
%% Purpose: Start all children. The new list contains #child's
%% with pids.
%% Returns: {ok, NChildren} | {error, NChildren}
-%% NChildren = [#child] in termination order (reversed
+%% NChildren = [child()] in termination order (reversed
%% start order)
%%-----------------------------------------------------------------
start_children(Children, SupName) -> start_children(Children, [], SupName).
@@ -182,8 +236,8 @@ start_children([], NChildren, _SupName) ->
{ok, NChildren}.
do_start_child(SupName, Child) ->
- #child{mfa = {M, F, A}} = Child,
- case catch apply(M, F, A) of
+ #child{mfargs = {M, F, Args}} = Child,
+ case catch apply(M, F, Args) of
{ok, Pid} when is_pid(Pid) ->
NChild = Child#child{pid = Pid},
report_progress(NChild, SupName),
@@ -192,7 +246,7 @@ do_start_child(SupName, Child) ->
NChild = Child#child{pid = Pid},
report_progress(NChild, SupName),
{ok, Pid, Extra};
- ignore ->
+ ignore ->
{ok, undefined};
{error, What} -> {error, What};
What -> {error, What}
@@ -211,15 +265,17 @@ do_start_child_i(M, F, A) ->
What ->
{error, What}
end.
-
%%% ---------------------------------------------------
%%%
%%% Callback functions.
%%%
%%% ---------------------------------------------------
+-type call() :: 'which_children' | 'count_children' | {_, _}. % XXX: refine
+-spec handle_call(call(), term(), state()) -> {'reply', term(), state()}.
+
handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) ->
- #child{mfa = {M, F, A}} = hd(State#state.children),
+ #child{mfargs = {M, F, A}} = hd(State#state.children),
Args = A ++ EArgs,
case do_start_child_i(M, F, Args) of
{ok, Pid} ->
@@ -235,7 +291,7 @@ handle_call({start_child, EArgs}, _From, State) when ?is_simple(State) ->
end;
%%% The requests terminate_child, delete_child and restart_child are
-%%% invalid for simple_one_for_one supervisors.
+%%% invalid for simple_one_for_one supervisors.
handle_call({_Req, _Data}, _From, State) when ?is_simple(State) ->
{reply, {error, simple_one_for_one}, State};
@@ -297,7 +353,7 @@ handle_call(which_children, _From, State) ->
Resp =
lists:map(fun(#child{pid = Pid, name = Name,
child_type = ChildType, modules = Mods}) ->
- {Name, Pid, ChildType, Mods}
+ {Name, Pid, ChildType, Mods}
end,
State#state.children),
{reply, Resp, State};
@@ -318,7 +374,6 @@ handle_call(count_children, _From, State) when ?is_simple(State) ->
{reply, Reply, State};
handle_call(count_children, _From, State) ->
-
%% Specs and children are together on the children list...
{Specs, Active, Supers, Workers} =
lists:foldl(fun(Child, Counts) ->
@@ -347,15 +402,19 @@ count_child(#child{pid = Pid, child_type = supervisor},
%%% Hopefully cause a function-clause as there is no API function
%%% that utilizes cast.
+-spec handle_cast('null', state()) -> {'noreply', state()}.
+
handle_cast(null, State) ->
error_logger:error_msg("ERROR: Supervisor received cast-message 'null'~n",
[]),
-
{noreply, State}.
%%
%% Take care of terminated children.
%%
+-spec handle_info(term(), state()) ->
+ {'noreply', state()} | {'stop', 'shutdown', state()}.
+
handle_info({'EXIT', Pid, Reason}, State) ->
case restart_child(Pid, Reason, State) of
{ok, State1} ->
@@ -368,9 +427,12 @@ handle_info(Msg, State) ->
error_logger:error_msg("Supervisor received unexpected message: ~p~n",
[Msg]),
{noreply, State}.
+
%%
%% Terminate this server.
%%
+-spec terminate(term(), state()) -> 'ok'.
+
terminate(_Reason, State) ->
terminate_children(State#state.children, State#state.name),
ok.
@@ -384,6 +446,9 @@ terminate(_Reason, State) ->
%% NOTE: This requires that the init function of the call-back module
%% does not have any side effects.
%%
+-spec code_change(term(), state(), term()) ->
+ {'ok', state()} | {'error', term()}.
+
code_change(_, State, _) ->
case (State#state.module):init(State#state.args) of
{ok, {SupFlags, StartSpec}} ->
@@ -411,7 +476,7 @@ check_flags({Strategy, MaxIntensity, Period}) ->
check_flags(What) ->
{bad_flags, What}.
-update_childspec(State, StartSpec) when ?is_simple(State) ->
+update_childspec(State, StartSpec) when ?is_simple(State) ->
case check_startspec(StartSpec) of
{ok, [Child]} ->
{ok, State#state{children = [Child]}};
@@ -437,7 +502,7 @@ update_childspec1([Child|OldC], Children, KeepOld) ->
update_childspec1(OldC, Children, [Child|KeepOld])
end;
update_childspec1([], Children, KeepOld) ->
- % Return them in (keeped) reverse start order.
+ %% Return them in (kept) reverse start order.
lists:reverse(Children ++ KeepOld).
update_chsp(OldCh, Children) ->
@@ -482,7 +547,7 @@ handle_start_child(Child, State) ->
%%% ---------------------------------------------------
%%% Restart. A process has terminated.
-%%% Returns: {ok, #state} | {shutdown, #state}
+%%% Returns: {ok, state()} | {shutdown, state()}
%%% ---------------------------------------------------
restart_child(Pid, Reason, State) when ?is_simple(State) ->
@@ -490,19 +555,19 @@ restart_child(Pid, Reason, State) when ?is_simple(State) ->
{ok, Args} ->
[Child] = State#state.children,
RestartType = Child#child.restart_type,
- {M, F, _} = Child#child.mfa,
- NChild = Child#child{pid = Pid, mfa = {M, F, Args}},
+ {M, F, _} = Child#child.mfargs,
+ NChild = Child#child{pid = Pid, mfargs = {M, F, Args}},
do_restart(RestartType, Reason, NChild, State);
error ->
{ok, State}
end;
restart_child(Pid, Reason, State) ->
Children = State#state.children,
- case lists:keysearch(Pid, #child.pid, Children) of
- {value, Child} ->
+ case lists:keyfind(Pid, #child.pid, Children) of
+ #child{} = Child ->
RestartType = Child#child.restart_type,
do_restart(RestartType, Reason, Child, State);
- _ ->
+ false ->
{ok, State}
end.
@@ -534,7 +599,7 @@ restart(Child, State) ->
end.
restart(simple_one_for_one, Child, State) ->
- #child{mfa = {M, F, A}} = Child,
+ #child{mfargs = {M, F, A}} = Child,
Dynamics = ?DICT:erase(Child#child.pid, State#state.dynamics),
case do_start_child_i(M, F, A) of
{ok, Pid} ->
@@ -580,9 +645,9 @@ restart(one_for_all, Child, State) ->
%%-----------------------------------------------------------------
%% Func: terminate_children/2
-%% Args: Children = [#child] in termination order
+%% Args: Children = [child()] in termination order
%% SupName = {local, atom()} | {global, atom()} | {pid(),Mod}
-%% Returns: NChildren = [#child] in
+%% Returns: NChildren = [child()] in
%% startup order (reversed termination order)
%%-----------------------------------------------------------------
terminate_children(Children, SupName) ->
@@ -617,7 +682,6 @@ do_terminate(Child, _SupName) ->
%% Returns: ok | {error, OtherReason} (this should be reported)
%%-----------------------------------------------------------------
shutdown(Pid, brutal_kill) ->
-
case monitor_child(Pid) of
ok ->
exit(Pid, kill),
@@ -630,9 +694,7 @@ shutdown(Pid, brutal_kill) ->
{error, Reason} ->
{error, Reason}
end;
-
shutdown(Pid, Time) ->
-
case monitor_child(Pid) of
ok ->
exit(Pid, shutdown), %% Try to shutdown gracefully
@@ -738,9 +800,9 @@ remove_child(Child, State) ->
%% MaxIntensity = integer()
%% Period = integer()
%% Mod :== atom()
-%% Arsg :== term()
+%% Args :== term()
%% Purpose: Check that Type is of correct type (!)
-%% Returns: {ok, #state} | Error
+%% Returns: {ok, state()} | Error
%%-----------------------------------------------------------------
init_state(SupName, Type, Mod, Args) ->
case catch init_state1(SupName, Type, Mod, Args) of
@@ -755,11 +817,11 @@ init_state1(SupName, {Strategy, MaxIntensity, Period}, Mod, Args) ->
validIntensity(MaxIntensity),
validPeriod(Period),
{ok, #state{name = supname(SupName,Mod),
- strategy = Strategy,
- intensity = MaxIntensity,
- period = Period,
- module = Mod,
- args = Args}};
+ strategy = Strategy,
+ intensity = MaxIntensity,
+ period = Period,
+ module = Mod,
+ args = Args}};
init_state1(_SupName, Type, _, _) ->
{invalid_type, Type}.
@@ -771,26 +833,26 @@ validStrategy(What) -> throw({invalid_strategy, What}).
validIntensity(Max) when is_integer(Max),
Max >= 0 -> true;
-validIntensity(What) -> throw({invalid_intensity, What}).
+validIntensity(What) -> throw({invalid_intensity, What}).
validPeriod(Period) when is_integer(Period),
Period > 0 -> true;
validPeriod(What) -> throw({invalid_period, What}).
-supname(self,Mod) -> {self(),Mod};
-supname(N,_) -> N.
+supname(self, Mod) -> {self(), Mod};
+supname(N, _) -> N.
%%% ------------------------------------------------------
%%% Check that the children start specification is valid.
%%% Shall be a six (6) tuple
%%% {Name, Func, RestartType, Shutdown, ChildType, Modules}
%%% where Name is an atom
-%%% Func is {Mod, Fun, Args} == {atom, atom, list}
+%%% Func is {Mod, Fun, Args} == {atom(), atom(), list()}
%%% RestartType is permanent | temporary | transient
%%% Shutdown = integer() | infinity | brutal_kill
%%% ChildType = supervisor | worker
%%% Modules = [atom()] | dynamic
-%%% Returns: {ok, [#child]} | Error
+%%% Returns: {ok, [child()]} | Error
%%% ------------------------------------------------------
check_startspec(Children) -> check_startspec(Children, []).
@@ -818,14 +880,14 @@ check_childspec(Name, Func, RestartType, Shutdown, ChildType, Mods) ->
validChildType(ChildType),
validShutdown(Shutdown, ChildType),
validMods(Mods),
- {ok, #child{name = Name, mfa = Func, restart_type = RestartType,
+ {ok, #child{name = Name, mfargs = Func, restart_type = RestartType,
shutdown = Shutdown, child_type = ChildType, modules = Mods}}.
validChildType(supervisor) -> true;
validChildType(worker) -> true;
validChildType(What) -> throw({invalid_child_type, What}).
-validName(_Name) -> true.
+validName(_Name) -> true.
validFunc({M, F, A}) when is_atom(M),
is_atom(F),
@@ -923,7 +985,7 @@ report_error(Error, Reason, Child, SupName) ->
extract_child(Child) ->
[{pid, Child#child.pid},
{name, Child#child.name},
- {mfa, Child#child.mfa},
+ {mfargs, Child#child.mfargs},
{restart_type, Child#child.restart_type},
{shutdown, Child#child.shutdown},
{child_type, Child#child.child_type}].
diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl
index 12bbd45703..24e14caa69 100644
--- a/lib/stdlib/src/timer.erl
+++ b/lib/stdlib/src/timer.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(timer).
@@ -41,54 +41,54 @@
%%
%% Time is in milliseconds.
%%
--opaque tref() :: any().
+-opaque tref() :: {integer(), reference()}.
-type time() :: non_neg_integer().
-type timestamp() :: {non_neg_integer(), non_neg_integer(), non_neg_integer()}.
%%
%% Interface functions
%%
--spec apply_after(time(), atom(), atom(), [_]) -> {'ok', tref()} | {'error', _}.
+-spec apply_after(time(), atom(), atom(), [term()]) -> {'ok', tref()} | {'error', term()}.
apply_after(Time, M, F, A) ->
req(apply_after, {Time, {M, F, A}}).
--spec send_after(time(), pid() | atom(), term()) -> {'ok', tref()} | {'error', _}.
+-spec send_after(time(), pid() | atom(), term()) -> {'ok', tref()} | {'error', term()}.
send_after(Time, Pid, Message) ->
req(apply_after, {Time, {?MODULE, send, [Pid, Message]}}).
--spec send_after(time(), _) -> {'ok', tref()} | {'error', _}.
+-spec send_after(time(), term()) -> {'ok', tref()} | {'error', term()}.
send_after(Time, Message) ->
send_after(Time, self(), Message).
--spec exit_after(time(), pid() | atom(), _) -> {'ok', tref()} | {'error', _}.
+-spec exit_after(time(), pid() | atom(), term()) -> {'ok', tref()} | {'error', term()}.
exit_after(Time, Pid, Reason) ->
req(apply_after, {Time, {erlang, exit, [Pid, Reason]}}).
--spec exit_after(time(), term()) -> {'ok', tref()} | {'error', _}.
+-spec exit_after(time(), term()) -> {'ok', tref()} | {'error', term()}.
exit_after(Time, Reason) ->
exit_after(Time, self(), Reason).
--spec kill_after(time(), pid() | atom()) -> {'ok', tref()} | {'error', _}.
+-spec kill_after(time(), pid() | atom()) -> {'ok', tref()} | {'error', term()}.
kill_after(Time, Pid) ->
exit_after(Time, Pid, kill).
--spec kill_after(time()) -> {'ok', tref()} | {'error', _}.
+-spec kill_after(time()) -> {'ok', tref()} | {'error', term()}.
kill_after(Time) ->
exit_after(Time, self(), kill).
--spec apply_interval(time(), atom(), atom(), [_]) -> {'ok', tref()} | {'error', _}.
+-spec apply_interval(time(), atom(), atom(), [term()]) -> {'ok', tref()} | {'error', term()}.
apply_interval(Time, M, F, A) ->
req(apply_interval, {Time, self(), {M, F, A}}).
--spec send_interval(time(), pid() | atom(), term()) -> {'ok', tref()} | {'error', _}.
+-spec send_interval(time(), pid() | atom(), term()) -> {'ok', tref()} | {'error', term()}.
send_interval(Time, Pid, Message) ->
req(apply_interval, {Time, Pid, {?MODULE, send, [Pid, Message]}}).
--spec send_interval(time(), term()) -> {'ok', tref()} | {'error', _}.
+-spec send_interval(time(), term()) -> {'ok', tref()} | {'error', term()}.
send_interval(Time, Message) ->
send_interval(Time, self(), Message).
--spec cancel(tref()) -> {'ok', 'cancel'} | {'error', _}.
+-spec cancel(tref()) -> {'ok', 'cancel'} | {'error', term()}.
cancel(BRef) ->
req(cancel, BRef).
@@ -112,7 +112,7 @@ tc(F, A) ->
%%
%% Measure the execution time (in microseconds) for an MFA.
%%
--spec tc(atom(), atom(), [_]) -> {time(), term()}.
+-spec tc(atom(), atom(), [term()]) -> {time(), term()}.
tc(M, F, A) ->
Before = erlang:now(),
Val = (catch apply(M, F, A)),
@@ -152,7 +152,7 @@ hms(H, M, S) ->
start() ->
ensure_started().
--spec start_link() -> {'ok', pid()} | {'error', _}.
+-spec start_link() -> {'ok', pid()} | {'error', term()}.
start_link() ->
gen_server:start_link({local, timer_server}, ?MODULE, [], []).
@@ -163,6 +163,7 @@ init([]) ->
?INTERVAL_TAB = ets:new(?INTERVAL_TAB, [named_table,protected]),
{ok, [], infinity}.
+-spec ensure_started() -> 'ok'.
ensure_started() ->
case whereis(timer_server) of
undefined ->
@@ -186,6 +187,10 @@ req(Req, Arg) ->
%%
%% Time and Timeout is in milliseconds. Started is in microseconds.
%%
+-type timers() :: term(). % XXX: refine?
+
+-spec handle_call(term(), term(), timers()) ->
+ {'reply', term(), timers(), timeout()} | {'noreply', timers(), timeout()}.
handle_call({apply_after, {Time, Op}, Started}, _From, _Ts)
when is_integer(Time), Time >= 0 ->
BRef = {Started + 1000*Time, make_ref()},
@@ -205,7 +210,7 @@ handle_call({apply_interval, {Time, To, MFA}, Started}, _From, _Ts)
Interval = Time*1000,
BRef2 = {Started + Interval, Ref},
Timer = {BRef2, {repeat, Interval, Pid}, MFA},
- ets:insert(?INTERVAL_TAB,{BRef1,BRef2,Pid}),
+ ets:insert(?INTERVAL_TAB, {BRef1,BRef2,Pid}),
ets:insert(?TIMER_TAB, Timer),
Timeout = timer_timeout(SysTime),
{reply, {ok, BRef1}, [], Timeout};
@@ -213,7 +218,7 @@ handle_call({apply_interval, {Time, To, MFA}, Started}, _From, _Ts)
{reply, {error, badarg}, [], next_timeout()}
end;
handle_call({cancel, BRef = {_Time, Ref}, _}, _From, Ts)
- when is_reference(Ref) ->
+ when is_reference(Ref) ->
delete_ref(BRef),
{reply, {ok, cancel}, Ts, next_timeout()};
handle_call({cancel, _BRef, _}, _From, Ts) ->
@@ -225,6 +230,7 @@ handle_call({apply_interval, _, _}, _From, Ts) ->
handle_call(_Else, _From, Ts) -> % Catch anything else
{noreply, Ts, next_timeout()}.
+-spec handle_info(term(), timers()) -> {'noreply', timers(), timeout()}.
handle_info(timeout, Ts) -> % Handle timeouts
Timeout = timer_timeout(system_time()),
{noreply, Ts, Timeout};
@@ -234,19 +240,21 @@ handle_info({'EXIT', Pid, _Reason}, Ts) -> % Oops, someone died
handle_info(_OtherMsg, Ts) -> % Other Msg's
{noreply, Ts, next_timeout()}.
+-spec handle_cast(term(), timers()) -> {'noreply', timers(), timeout()}.
handle_cast(_Req, Ts) -> % Not predicted but handled
{noreply, Ts, next_timeout()}.
--spec terminate(_, _) -> 'ok'.
+-spec terminate(term(), _State) -> 'ok'.
terminate(_Reason, _State) ->
ok.
+-spec code_change(term(), State, term()) -> {'ok', State}.
code_change(_OldVsn, State, _Extra) ->
%% According to the man for gen server no timer can be set here.
{ok, State}.
%%
-%% timer_timeout(Timers, SysTime)
+%% timer_timeout(SysTime)
%%
%% Apply and remove already timed-out timers. A timer is a tuple
%% {Time, BRef, Op, MFA}, where Time is in microseconds.
@@ -290,12 +298,13 @@ delete_ref(BRef = {interval, _}) ->
ok
end;
delete_ref(BRef) ->
- ets:delete(?TIMER_TAB,BRef).
+ ets:delete(?TIMER_TAB, BRef).
%%
%% pid_delete
%%
+-spec pid_delete(pid()) -> 'ok'.
pid_delete(Pid) ->
IntervalTimerList =
ets:select(?INTERVAL_TAB,
@@ -303,13 +312,14 @@ pid_delete(Pid) ->
[{'==','$1',Pid}],
['$_']}]),
lists:foreach(fun({IntKey, TimerKey, _ }) ->
- ets:delete(?INTERVAL_TAB,IntKey),
- ets:delete(?TIMER_TAB,TimerKey)
+ ets:delete(?INTERVAL_TAB, IntKey),
+ ets:delete(?TIMER_TAB, TimerKey)
end, IntervalTimerList).
%% Calculate time to the next timeout. Returned timeout must fit in a
%% small int.
+-spec next_timeout() -> timeout().
next_timeout() ->
case ets:first(?TIMER_TAB) of
'$end_of_table' ->
@@ -369,7 +379,7 @@ get_pid(_) ->
get_status() ->
Info1 = ets:info(?TIMER_TAB),
- {value,{size,TotalNumTimers}} = lists:keysearch(size, 1, Info1),
+ {size,TotalNumTimers} = lists:keyfind(size, 1, Info1),
Info2 = ets:info(?INTERVAL_TAB),
- {value,{size,NumIntervalTimers}} = lists:keysearch(size, 1, Info2),
+ {size,NumIntervalTimers} = lists:keyfind(size, 1, Info2),
{{?TIMER_TAB,TotalNumTimers},{?INTERVAL_TAB,NumIntervalTimers}}.
diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile
index 9beac93eb8..3bbd9ce318 100644
--- a/lib/stdlib/test/Makefile
+++ b/lib/stdlib/test/Makefile
@@ -9,6 +9,8 @@ MODULES= \
array_SUITE \
base64_SUITE \
beam_lib_SUITE \
+ binary_module_SUITE \
+ binref \
c_SUITE \
calendar_SUITE \
dets_SUITE \
diff --git a/lib/stdlib/test/binary_module_SUITE.erl b/lib/stdlib/test/binary_module_SUITE.erl
new file mode 100644
index 0000000000..16ed9a2c26
--- /dev/null
+++ b/lib/stdlib/test/binary_module_SUITE.erl
@@ -0,0 +1,1323 @@
+-module(binary_module_SUITE).
+
+-export([all/1, interesting/1,random_ref_comp/1,random_ref_sr_comp/1,
+ random_ref_fla_comp/1,parts/1, bin_to_list/1, list_to_bin/1,
+ copy/1, referenced/1,guard/1,encode_decode/1,badargs/1,longest_common_trap/1]).
+
+-export([random_number/1, make_unaligned/1]).
+
+
+
+%%-define(STANDALONE,1).
+
+-ifdef(STANDALONE).
+
+-define(line,erlang:display({?MODULE,?LINE}),).
+
+-else.
+
+-include("test_server.hrl").
+-export([init_per_testcase/2, fin_per_testcase/2]).
+% Default timetrap timeout (set in init_per_testcase).
+% Some of these testcases are really heavy...
+-define(default_timeout, ?t:minutes(20)).
+
+-endif.
+
+
+
+-ifdef(STANDALONE).
+-export([run/0]).
+
+run() ->
+ [ apply(?MODULE,X,[[]]) || X <- all(suite) ].
+
+-else.
+
+init_per_testcase(_Case, Config) ->
+ ?line Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
+
+fin_per_testcase(_Case, Config) ->
+ ?line Dog = ?config(watchdog, Config),
+ ?line test_server:timetrap_cancel(Dog),
+ ok.
+-endif.
+
+all(suite) -> [interesting,random_ref_fla_comp,random_ref_sr_comp,
+ random_ref_comp,parts,bin_to_list, list_to_bin, copy,
+ referenced,guard,encode_decode,badargs,longest_common_trap].
+
+-define(MASK_ERROR(EXPR),mask_error((catch (EXPR)))).
+
+
+badargs(doc) ->
+ ["Tests various badarg exceptions in the module"];
+badargs(Config) when is_list(Config) ->
+ ?line badarg = ?MASK_ERROR(binary:compile_pattern([<<1,2,3:3>>])),
+ ?line badarg = ?MASK_ERROR(binary:compile_pattern([<<1,2,3>>|<<1,2>>])),
+ ?line badarg = ?MASK_ERROR(binary:compile_pattern(<<1,2,3:3>>)),
+ ?line badarg = ?MASK_ERROR(binary:compile_pattern(<<>>)),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3:3>>,<<1>>)),
+ ?line badarg = ?MASK_ERROR(binary:matches(<<1,2,3:3>>,<<1>>)),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,
+ [{scope,{0,1},1}])),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,
+ [{scape,{0,1}}])),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,
+ [{scope,{0,1,1}}])),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,[{scope,0,1}])),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,[{scope,[0,1]}])),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,
+ [{scope,{0.1,1}}])),
+ ?line badarg = ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,
+ [{scope,{1,1.1}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:match(<<1,2,3>>,<<1>>,
+ [{scope,{16#FF,
+ 16#FFFFFFFFFFFFFFFF}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:match(<<1,2,3>>,<<1>>,
+ [{scope,{16#FFFFFFFFFFFFFFFF,
+ -16#7FFFFFFFFFFFFFFF-1}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:match(<<1,2,3>>,<<1>>,
+ [{scope,{16#FFFFFFFFFFFFFFFF,
+ 16#7FFFFFFFFFFFFFFF}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:part(<<1,2,3>>,{16#FF,
+ 16#FFFFFFFFFFFFFFFF})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:part(<<1,2,3>>,{16#FFFFFFFFFFFFFFFF,
+ -16#7FFFFFFFFFFFFFFF-1})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:part(<<1,2,3>>,{16#FFFFFFFFFFFFFFFF,
+ 16#7FFFFFFFFFFFFFFF})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:part(make_unaligned(<<1,2,3>>),{1,1,1})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary_part(make_unaligned(<<1,2,3>>),{1,1,1})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary_part(make_unaligned(<<1,2,3>>),{16#FFFFFFFFFFFFFFFF,
+ -16#7FFFFFFFFFFFFFFF-1})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary_part(make_unaligned(<<1,2,3>>),{16#FF,
+ 16#FFFFFFFFFFFFFFFF})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary_part(make_unaligned(<<1,2,3>>),{16#FFFFFFFFFFFFFFFF,
+ 16#7FFFFFFFFFFFFFFF})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary_part(make_unaligned(<<1,2,3>>),{16#FFFFFFFFFFFFFFFFFF,
+ -16#7FFF})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary_part(make_unaligned(<<1,2,3>>),{16#FF,
+ -16#7FFF})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>,{16#FF,
+ 16#FFFFFFFFFFFFFFFF})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>,{16#FFFFFFFFFFFFFFFF,
+ -16#7FFFFFFFFFFFFFFF-1})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>,{16#FFFFFFFFFFFFFFFF,
+ 16#7FFFFFFFFFFFFFFF})),
+ ?line [1,2,3] =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>,[])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>,{1,2,3})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>,{1.0,1})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3>>,{1,1.0})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3:3>>,{1,1})),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list(<<1,2,3:3>>)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:bin_to_list([1,2,3])),
+
+ ?line nomatch =
+ ?MASK_ERROR(binary:match(<<1,2,3>>,<<1>>,[{scope,{0,0}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:match(<<1,2,3>>,{bm,<<>>},[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:match(<<1,2,3>>,[],[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:match(<<1,2,3>>,{ac,<<>>},[{scope,{0,1}}])),
+ ?line {bm,BMMagic} = binary:compile_pattern([<<1,2,3>>]),
+ ?line {ac,ACMagic} = binary:compile_pattern([<<1,2,3>>,<<4,5>>]),
+ ?line badarg =
+ ?MASK_ERROR(binary:match(<<1,2,3>>,{bm,ACMagic},[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:match(<<1,2,3>>,{ac,BMMagic},[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:match(<<1,2,3>>,
+ {bm,ets:match_spec_compile([{'_',[],['$_']}])},
+ [{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:match(<<1,2,3>>,
+ {ac,ets:match_spec_compile([{'_',[],['$_']}])},
+ [{scope,{0,1}}])),
+ ?line nomatch =
+ ?MASK_ERROR(binary:matches(<<1,2,3>>,<<1>>,[{scope,{0,0}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:matches(<<1,2,3>>,{bm,<<>>},[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:matches(<<1,2,3>>,[],[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:matches(<<1,2,3>>,{ac,<<>>},[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:matches(<<1,2,3>>,{bm,ACMagic},[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:matches(<<1,2,3>>,{ac,BMMagic},[{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:matches(<<1,2,3>>,
+ {bm,ets:match_spec_compile([{'_',[],['$_']}])},
+ [{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:matches(<<1,2,3>>,
+ {ac,ets:match_spec_compile([{'_',[],['$_']}])},
+ [{scope,{0,1}}])),
+ ?line badarg =
+ ?MASK_ERROR(binary:longest_common_prefix(
+ [<<0:10000,1,2,4,1:3>>,
+ <<0:10000,1,2,3>>])),
+ ?line badarg =
+ ?MASK_ERROR(binary:longest_common_suffix(
+ [<<0:10000,1,2,4,1:3>>,
+ <<0:10000,1,2,3>>])),
+ ?line badarg =
+ ?MASK_ERROR(binary:encode_unsigned(-1)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:encode_unsigned(-16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:first(<<1,2,4,1:3>>)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:first([1,2,4])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:last(<<1,2,4,1:3>>)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:last([1,2,4])),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:at(<<1,2,4,1:3>>,2)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:at(<<>>,2)),
+ ?line badarg =
+ ?MASK_ERROR(
+ binary:at([1,2,4],2)),
+ ok.
+
+longest_common_trap(doc) ->
+ ["Whitebox test to force special trap conditions in longest_common_{prefix,suffix}"];
+longest_common_trap(Config) when is_list(Config) ->
+ ?line erts_debug:set_internal_state(available_internal_state,true),
+ ?line io:format("oldlimit: ~p~n",
+ [erts_debug:set_internal_state(binary_loop_limit,10)]),
+ erlang:bump_reductions(10000000),
+ ?line _ = binary:longest_common_prefix(
+ [<<0:10000,1,2,4>>,
+ <<0:10000,1,2,3>>,
+ <<0:10000,1,3,3>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,3>>,
+ <<0:10000,1,3,3>>,
+ <<0:10000,1,2,3>>,
+ <<0:10000,1,3,3>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,3>>,
+ <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0:10000,1,3,3>>,
+ <<0:10000,1,2,4>>]),
+ ?line _ = binary:longest_common_prefix(
+ [<<0:10000,1,2,4>>,
+ <<0:10000,1,2,3>>,
+ <<0:10000,1,3,3>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,3>>,
+ <<0:10000,1,3,3>>,
+ <<0:10000,1,2,3>>,
+ <<0:10000,1,3,3>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,4>>,
+ <<0:10000,1,2,3>>,
+ <<0,0,0,0,0,0,0,0,0,0,0,0,0,0>>,
+ <<0:10000,1,2,4>>]),
+ erlang:bump_reductions(10000000),
+ ?line _ = binary:longest_common_suffix(
+ [<<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,3,3,0:10000,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>,
+ <<1,2,4,0:10000>>]),
+ ?line _ = binary:longest_common_suffix(
+ [<<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<1,2,4,0:10000>>,
+ <<0,0,0,0,0,0,0,0,0,0,0,0,0,0>>,
+ <<1,2,4,0:10000>>]),
+ Subj = subj(),
+ Len = byte_size(Subj),
+ ?line Len = binary:longest_common_suffix(
+ [Subj,Subj,Subj]),
+ ?line io:format("limit was: ~p~n",
+ [erts_debug:set_internal_state(binary_loop_limit,
+ default)]),
+ ?line erts_debug:set_internal_state(available_internal_state,false),
+ ok.
+
+subj() ->
+ Me = self(),
+ spawn(fun() ->
+ X0 = iolist_to_binary([
+ "1234567890",
+ %lists:seq(16#21, 16#7e),
+ lists:duplicate(100, $x)
+ ]),
+ Me ! X0,
+ receive X -> X end
+ end),
+ X0 = receive A -> A end,
+ <<X1:32/binary,_/binary>> = X0,
+ Subject= <<X1/binary>>,
+ Subject.
+
+
+interesting(doc) ->
+ ["Try some interesting patterns"];
+interesting(Config) when is_list(Config) ->
+ X = do_interesting(binary),
+ X = do_interesting(binref).
+
+do_interesting(Module) ->
+ ?line {0,4} = Module:match(<<"123456">>,
+ Module:compile_pattern([<<"12">>,<<"1234">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>,<<"6">>])),
+ ?line [{0,4},{5,1}] = Module:matches(<<"123456">>,
+ Module:compile_pattern([<<"12">>,<<"1234">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>,<<"6">>])),
+ ?line [{0,4}] = Module:matches(<<"123456">>,
+ Module:compile_pattern([<<"12">>,<<"1234">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>])),
+ ?line [{0,2},{2,2}] = Module:matches(<<"123456">>,
+ Module:compile_pattern([<<"12">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>])),
+ ?line {1,4} = Module:match(<<"123456">>,
+ Module:compile_pattern([<<"34">>,<<"34">>,
+ <<"12347">>,<<"2345">>])),
+ ?line [{1,4}] = Module:matches(<<"123456">>,
+ Module:compile_pattern([<<"34">>,<<"34">>,
+ <<"12347">>,<<"2345">>])),
+ ?line [{2,2}] = Module:matches(<<"123456">>,
+ Module:compile_pattern([<<"34">>,<<"34">>,
+ <<"12347">>,<<"2346">>])),
+
+ ?line {0,4} = Module:match(<<"123456">>,
+ [<<"12">>,<<"1234">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>,<<"6">>]),
+ ?line [{0,4},{5,1}] = Module:matches(<<"123456">>,
+ [<<"12">>,<<"1234">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>,<<"6">>]),
+ ?line [{0,4}] = Module:matches(<<"123456">>,
+ [<<"12">>,<<"1234">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>]),
+ ?line [{0,2},{2,2}] = Module:matches(<<"123456">>,
+ [<<"12">>,
+ <<"23">>,<<"3">>,
+ <<"34">>,<<"456">>,
+ <<"45">>]),
+ ?line {1,4} = Module:match(<<"123456">>,
+ [<<"34">>,<<"34">>,
+ <<"12347">>,<<"2345">>]),
+ ?line [{1,4}] = Module:matches(<<"123456">>,
+ [<<"34">>,<<"34">>,
+ <<"12347">>,<<"2345">>]),
+ ?line [{2,2}] = Module:matches(<<"123456">>,
+ [<<"34">>,<<"34">>,
+ <<"12347">>,<<"2346">>]),
+ ?line nomatch = Module:match(<<1,2,3,4>>,<<2>>,[{scope,{0,1}}]),
+ ?line {1,1} = Module:match(<<1,2,3,4>>,<<2>>,[{scope,{0,2}}]),
+ ?line nomatch = Module:match(<<1,2,3,4>>,<<2,3>>,[{scope,{0,2}}]),
+ ?line {1,2} = Module:match(<<1,2,3,4>>,<<2,3>>,[{scope,{0,3}}]),
+ ?line {1,2} = Module:match(<<1,2,3,4>>,<<2,3>>,[{scope,{0,4}}]),
+ ?line badarg = ?MASK_ERROR(Module:match(<<1,2,3,4>>,<<2,3>>,
+ [{scope,{0,5}}])),
+ ?line {1,2} = Module:match(<<1,2,3,4>>,<<2,3>>,[{scope,{4,-4}}]),
+ ?line {0,3} = Module:match(<<1,2,3,4>>,<<1,2,3>>,[{scope,{4,-4}}]),
+ ?line {0,4} = Module:match(<<1,2,3,4>>,<<1,2,3,4>>,[{scope,{4,-4}}]),
+ ?line badarg = ?MASK_ERROR(Module:match(<<1,2,3,4>>,<<1,2,3,4>>,
+ [{scope,{3,-4}}])),
+ ?line [] = Module:matches(<<1,2,3,4>>,<<2>>,[{scope,{0,1}}]),
+ ?line [{1,1}] = Module:matches(<<1,2,3,4>>,[<<2>>,<<3>>],[{scope,{0,2}}]),
+ ?line [] = Module:matches(<<1,2,3,4>>,<<2,3>>,[{scope,{0,2}}]),
+ ?line [{1,2}] = Module:matches(<<1,2,3,4>>,<<2,3>>,[{scope,{0,3}}]),
+ ?line [{1,2}] = Module:matches(<<1,2,3,4>>,<<2,3>>,[{scope,{0,4}}]),
+ ?line [{1,2}] = Module:matches(<<1,2,3,4>>,[<<2,3>>,<<4>>],
+ [{scope,{0,3}}]),
+ ?line [{1,2},{3,1}] = Module:matches(<<1,2,3,4>>,[<<2,3>>,<<4>>],
+ [{scope,{0,4}}]),
+ ?line badarg = ?MASK_ERROR(Module:matches(<<1,2,3,4>>,<<2,3>>,
+ [{scope,{0,5}}])),
+ ?line [{1,2}] = Module:matches(<<1,2,3,4>>,<<2,3>>,[{scope,{4,-4}}]),
+ ?line [{1,2},{3,1}] = Module:matches(<<1,2,3,4>>,[<<2,3>>,<<4>>],
+ [{scope,{4,-4}}]),
+ ?line [{0,3}] = Module:matches(<<1,2,3,4>>,<<1,2,3>>,[{scope,{4,-4}}]),
+ ?line [{0,4}] = Module:matches(<<1,2,3,4>>,<<1,2,3,4>>,[{scope,{4,-4}}]),
+ ?line badarg = ?MASK_ERROR(Module:matches(<<1,2,3,4>>,<<1,2,3,4>>,
+ [{scope,{3,-4}}])),
+ ?line badarg = ?MASK_ERROR(Module:matches(<<1,2,3,4>>,[<<1,2,3,4>>],
+ [{scope,{3,-4}}])),
+ ?line [<<1,2,3>>,<<6,7,8>>] = Module:split(<<1,2,3,4,5,6,7,8>>,<<4,5>>),
+ ?line [<<1,2,3>>,<<6,7,8>>] = Module:split(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>]),
+ ?line [<<1,2,3>>,<<6>>,<<8>>] = Module:split(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>],[global]),
+ ?line [<<1,2,3>>,<<6>>,<<>>,<<>>] = Module:split(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],
+ [global]),
+ ?line [<<1,2,3>>,<<6>>] = Module:split(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],
+ [global,trim]),
+ ?line [<<1,2,3,4,5,6,7,8>>] = Module:split(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],
+ [global,trim,{scope,{0,4}}]),
+ ?line [<<1,2,3>>,<<6,7,8>>] = Module:split(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],
+ [global,trim,{scope,{0,5}}]),
+ ?line badarg = ?MASK_ERROR(
+ Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,
+ [global,trim,{scope,{0,5}}])),
+ ?line <<1,2,3,99,6,7,8>> = Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,[]),
+ ?line <<1,2,3,99,6,99,99>> = Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,
+ [global]),
+ ?line <<1,2,3,99,6,7,8>> = Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,
+ [global,{scope,{0,5}}]),
+ ?line <<1,2,3,99,6,7,8>> = Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,
+ [global,{scope,{0,5}}]),
+ ?line <<1,2,3,99,6,7,8>> = Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,
+ [global,{scope,{0,5}}]),
+ ?line badarg = ?MASK_ERROR(Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,
+ [global,{scope,{0,5}},
+ {insert,1}])),
+ ?line <<1,2,3,99,4,5,6,7,8>> = Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<99>>,
+ [global,{scope,{0,5}},
+ {insert_replaced,1}]),
+ ?line <<1,2,3,9,4,5,9,6,7,8>> = Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],
+ <<9,9>>,
+ [global,{scope,{0,5}},
+ {insert_replaced,1}]),
+ ?line badarg = ?MASK_ERROR(Module:replace(<<1,2,3,4,5,6,7,8>>,
+ [<<4,5>>,<<7>>,<<8>>],<<>>,
+ [global,{scope,{0,5}},
+ {insert_replaced,1}])),
+ ?line 2 = Module:longest_common_prefix([<<1,2,4>>,<<1,2,3>>]),
+ ?line 2 = Module:longest_common_prefix([<<1,2,4>>,<<1,2>>]),
+ ?line 1 = Module:longest_common_prefix([<<1,2,4>>,<<1>>]),
+ ?line 0 = Module:longest_common_prefix([<<1,2,4>>,<<>>]),
+ ?line 1 = Module:longest_common_prefix([<<1,2,4>>,<<1,2,3>>,<<1,3,3>>]),
+ ?line 1 = Module:longest_common_prefix([<<1,2,4>>,<<1,2,3>>,<<1,3,3>>,<<1,2,4>>]),
+ ?line 1251 = Module:longest_common_prefix([<<0:10000,1,2,4>>,
+ <<0:10000,1,2,3>>,
+ <<0:10000,1,3,3>>,
+ <<0:10000,1,2,4>>]),
+ ?line 12501 = Module:longest_common_prefix([<<0:100000,1,2,4>>,
+ <<0:100000,1,2,3>>,
+ <<0:100000,1,3,3>>,
+ <<0:100000,1,2,4>>]),
+ ?line 1251 = Module:longest_common_prefix(
+ [make_unaligned(<<0:10000,1,2,4>>),
+ <<0:10000,1,2,3>>,
+ make_unaligned(<<0:10000,1,3,3>>),
+ <<0:10000,1,2,4>>]),
+ ?line 12501 = Module:longest_common_prefix(
+ [<<0:100000,1,2,4>>,
+ make_unaligned(<<0:100000,1,2,3>>),
+ <<0:100000,1,3,3>>,
+ make_unaligned(<<0:100000,1,2,4>>)]),
+ ?line 1250001 = Module:longest_common_prefix([<<0:10000000,1,2,4>>,
+ <<0:10000000,1,2,3>>,
+ <<0:10000000,1,3,3>>,
+ <<0:10000000,1,2,4>>]),
+ if % Too cruel for the reference implementation
+ Module =:= binary ->
+ ?line erts_debug:set_internal_state(available_internal_state,true),
+ ?line io:format("oldlimit: ~p~n",
+ [erts_debug:set_internal_state(
+ binary_loop_limit,100)]),
+ ?line 1250001 = Module:longest_common_prefix(
+ [<<0:10000000,1,2,4>>,
+ <<0:10000000,1,2,3>>,
+ <<0:10000000,1,3,3>>,
+ <<0:10000000,1,2,4>>]),
+ ?line io:format("limit was: ~p~n",
+ [erts_debug:set_internal_state(binary_loop_limit,
+ default)]),
+ ?line erts_debug:set_internal_state(available_internal_state,
+ false);
+ true ->
+ ok
+ end,
+ ?line 1 = Module:longest_common_suffix([<<0:100000000,1,2,4,5>>,
+ <<0:100000000,1,2,3,5>>,
+ <<0:100000000,1,3,3,5>>,
+ <<0:100000000,1,2,4,5>>]),
+ ?line 1 = Module:longest_common_suffix([<<1,2,4,5>>,
+ <<0:100000000,1,2,3,5>>,
+ <<0:100000000,1,3,3,5>>,
+ <<0:100000000,1,2,4,5>>]),
+ ?line 1 = Module:longest_common_suffix([<<1,2,4,5,5>>,<<5,5>>,
+ <<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4,5>>]),
+ ?line 0 = Module:longest_common_suffix([<<1,2,4,5,5>>,<<5,5>>,
+ <<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4>>]),
+ ?line 2 = Module:longest_common_suffix([<<1,2,4,5,5>>,<<5,5>>,
+ <<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4,5,5>>]),
+ ?line 1 = Module:longest_common_suffix([<<1,2,4,5,5>>,<<5>>,
+ <<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4,5,5>>]),
+ ?line 0 = Module:longest_common_suffix([<<1,2,4,5,5>>,<<>>,
+ <<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4,5,5>>]),
+ ?line 0 = Module:longest_common_suffix([<<>>,<<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4,5,5>>]),
+ ?line 0 = Module:longest_common_suffix([<<>>,<<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4,5,5>>]),
+ ?line 2 = Module:longest_common_suffix([<<5,5>>,<<0:100000000,1,3,3,5,5>>,
+ <<0:100000000,1,2,4,5,5>>]),
+ ?line 2 = Module:longest_common_suffix([<<5,5>>,<<5,5>>,<<4,5,5>>]),
+ ?line 2 = Module:longest_common_suffix([<<5,5>>,<<5,5>>,<<5,5>>]),
+ ?line 3 = Module:longest_common_suffix([<<4,5,5>>,<<4,5,5>>,<<4,5,5>>]),
+ ?line 0 = Module:longest_common_suffix([<<>>]),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_suffix([])),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_suffix([apa])),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_suffix([[<<>>]])),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_suffix([[<<0>>,
+ <<1:9>>]])),
+ ?line 0 = Module:longest_common_prefix([<<>>]),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_prefix([])),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_prefix([apa])),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_prefix([[<<>>]])),
+ ?line badarg = ?MASK_ERROR(Module:longest_common_prefix([[<<0>>,
+ <<1:9>>]])),
+
+ ?line <<1:6,Bin:3/binary,_:2>> = <<1:6,1,2,3,1:2>>,
+ ?line <<1,2,3>> = Bin,
+ ?line 1 = Module:first(Bin),
+ ?line 1 = Module:first(<<1>>),
+ ?line 1 = Module:first(<<1,2,3>>),
+ ?line badarg = ?MASK_ERROR(Module:first(<<>>)),
+ ?line badarg = ?MASK_ERROR(Module:first(apa)),
+ ?line 3 = Module:last(Bin),
+ ?line 1 = Module:last(<<1>>),
+ ?line 3 = Module:last(<<1,2,3>>),
+ ?line badarg = ?MASK_ERROR(Module:last(<<>>)),
+ ?line badarg = ?MASK_ERROR(Module:last(apa)),
+ ?line 1 = Module:at(Bin,0),
+ ?line 1 = Module:at(<<1>>,0),
+ ?line 1 = Module:at(<<1,2,3>>,0),
+ ?line 2 = Module:at(<<1,2,3>>,1),
+ ?line 3 = Module:at(<<1,2,3>>,2),
+ ?line badarg = ?MASK_ERROR(Module:at(<<1,2,3>>,3)),
+ ?line badarg = ?MASK_ERROR(Module:at(<<1,2,3>>,-1)),
+ ?line badarg = ?MASK_ERROR(Module:at(<<1,2,3>>,apa)),
+ ?line "hejsan" = [ Module:at(<<"hejsan">>,I) || I <- lists:seq(0,5) ],
+
+ ?line badarg = ?MASK_ERROR(Module:bin_to_list(<<1,2,3>>,3,-4)),
+ ?line [1,2,3] = ?MASK_ERROR(Module:bin_to_list(<<1,2,3>>,3,-3)),
+
+ ?line badarg = ?MASK_ERROR(Module:decode_unsigned(<<1,2,1:2>>,big)),
+ ?line badarg = ?MASK_ERROR(Module:decode_unsigned(<<1,2,1:2>>,little)),
+ ?line badarg = ?MASK_ERROR(Module:decode_unsigned(apa)),
+ ?line badarg = ?MASK_ERROR(Module:decode_unsigned(125,little)),
+ ?line 0 = ?MASK_ERROR(Module:decode_unsigned(<<>>,little)),
+ ?line 0 = ?MASK_ERROR(Module:decode_unsigned(<<>>,big)),
+ ?line 0 = ?MASK_ERROR(Module:decode_unsigned(<<0>>,little)),
+ ?line 0 = ?MASK_ERROR(Module:decode_unsigned(<<0>>,big)),
+ ?line 0 = ?MASK_ERROR(Module:decode_unsigned(make_unaligned(<<0>>),
+ little)),
+ ?line 0 = ?MASK_ERROR(Module:decode_unsigned(make_unaligned(<<0>>),big)),
+ ?line badarg = ?MASK_ERROR(Module:encode_unsigned(apa)),
+ ?line badarg = ?MASK_ERROR(Module:encode_unsigned(125.3,little)),
+ ?line badarg = ?MASK_ERROR(Module:encode_unsigned({1},little)),
+ ?line badarg = ?MASK_ERROR(Module:encode_unsigned([1],little)),
+ ?line <<0>> = ?MASK_ERROR(Module:encode_unsigned(0,little)),
+ ?line <<0>> = ?MASK_ERROR(Module:encode_unsigned(0,big)),
+ ok.
+
+encode_decode(doc) ->
+ ["test binary:encode_unsigned/1,2 and binary:decode_unsigned/1,2"];
+encode_decode(Config) when is_list(Config) ->
+ ?line random:seed({1271,769940,559934}),
+ ?line ok = encode_decode_loop({1,200},1000), % Need to be long enough
+ % to create offheap binaries
+ ok.
+
+encode_decode_loop(_Range,0) ->
+ ok;
+encode_decode_loop(Range, X) ->
+ ?line N = random_number(Range),
+ ?line A = binary:encode_unsigned(N),
+ ?line B = binary:encode_unsigned(N,big),
+ ?line C = binref:encode_unsigned(N),
+ ?line D = binref:encode_unsigned(N,big),
+ ?line E = binary:encode_unsigned(N,little),
+ ?line F = binref:encode_unsigned(N,little),
+ ?line G = binary:decode_unsigned(A),
+ ?line H = binary:decode_unsigned(A,big),
+ ?line I = binref:decode_unsigned(A),
+ ?line J = binary:decode_unsigned(E,little),
+ ?line K = binref:decode_unsigned(E,little),
+ ?line L = binary:decode_unsigned(make_unaligned(A)),
+ ?line M = binary:decode_unsigned(make_unaligned(E),little),
+ ?line PaddedBig = <<0:48,A/binary>>,
+ ?line PaddedLittle = <<E/binary,0:48>>,
+ ?line O = binary:decode_unsigned(PaddedBig),
+ ?line P = binary:decode_unsigned(make_unaligned(PaddedBig)),
+ ?line Q = binary:decode_unsigned(PaddedLittle,little),
+ ?line R = binary:decode_unsigned(make_unaligned(PaddedLittle),little),
+ ?line S = binref:decode_unsigned(PaddedLittle,little),
+ ?line T = binref:decode_unsigned(PaddedBig),
+ case (((A =:= B) and (B =:= C) and (C =:= D)) and
+ ((E =:= F)) and
+ ((N =:= G) and (G =:= H) and (H =:= I) and
+ (I =:= J) and (J =:= K) and (K =:= L) and (L =:= M)) and
+ ((M =:= O) and (O =:= P) and (P =:= Q) and (Q =:= R) and
+ (R =:= S) and (S =:= T)))of
+ true ->
+ encode_decode_loop(Range,X-1);
+ _ ->
+ io:format("Failed to encode/decode ~w~n(Results ~p)~n",
+ [N,[A,B,C,D,E,F,G,H,I,J,K,L,M,x,O,P,Q,R,S,T]]),
+ exit(mismatch)
+ end.
+
+guard(doc) ->
+ ["Smoke test of the guard BIFs binary_part/2,3"];
+guard(Config) when is_list(Config) ->
+ {comment, "Guard tests are run in emulator test suite"}.
+
+referenced(doc) ->
+ ["Test refernced_byte_size/1 bif."];
+referenced(Config) when is_list(Config) ->
+ ?line badarg = ?MASK_ERROR(binary:referenced_byte_size([])),
+ ?line badarg = ?MASK_ERROR(binary:referenced_byte_size(apa)),
+ ?line badarg = ?MASK_ERROR(binary:referenced_byte_size({})),
+ ?line badarg = ?MASK_ERROR(binary:referenced_byte_size(1)),
+ ?line A = <<1,2,3>>,
+ ?line B = binary:copy(A,1000),
+ ?line 3 = binary:referenced_byte_size(A),
+ ?line 3000 = binary:referenced_byte_size(B),
+ ?line <<_:8,C:2/binary>> = A,
+ ?line 3 = binary:referenced_byte_size(C),
+ ?line 2 = binary:referenced_byte_size(binary:copy(C)),
+ ?line <<_:7,D:2/binary,_:1>> = A,
+ ?line 2 = binary:referenced_byte_size(binary:copy(D)),
+ ?line 3 = binary:referenced_byte_size(D),
+ ?line <<_:8,E:2/binary,_/binary>> = B,
+ ?line 3000 = binary:referenced_byte_size(E),
+ ?line 2 = binary:referenced_byte_size(binary:copy(E)),
+ ?line <<_:7,F:2/binary,_:1,_/binary>> = B,
+ ?line 2 = binary:referenced_byte_size(binary:copy(F)),
+ ?line 3000 = binary:referenced_byte_size(F),
+ ok.
+
+
+
+list_to_bin(doc) ->
+ ["Test list_to_bin/1 bif"];
+list_to_bin(Config) when is_list(Config) ->
+ %% Just some smoke_tests first, then go nuts with random cases
+ ?line badarg = ?MASK_ERROR(binary:list_to_bin({})),
+ ?line badarg = ?MASK_ERROR(binary:list_to_bin(apa)),
+ ?line badarg = ?MASK_ERROR(binary:list_to_bin(<<"apa">>)),
+ F1 = fun(L) ->
+ ?MASK_ERROR(binref:list_to_bin(L))
+ end,
+ F2 = fun(L) ->
+ ?MASK_ERROR(binary:list_to_bin(L))
+ end,
+ ?line random_iolist:run(1000,F1,F2),
+ ok.
+
+copy(doc) ->
+ ["Test copy/1,2 bif's"];
+copy(Config) when is_list(Config) ->
+ ?line <<1,2,3>> = binary:copy(<<1,2,3>>),
+ ?line RS = random_string({1,10000}),
+ ?line RS = RS2 = binary:copy(RS),
+ ?line false = erts_debug:same(RS,RS2),
+ ?line <<>> = ?MASK_ERROR(binary:copy(<<1,2,3>>,0)),
+ ?line badarg = ?MASK_ERROR(binary:copy(<<1,2,3:3>>,2)),
+ ?line badarg = ?MASK_ERROR(binary:copy([],0)),
+ ?line <<>> = ?MASK_ERROR(binary:copy(<<>>,0)),
+ ?line badarg = ?MASK_ERROR(binary:copy(<<1,2,3>>,1.0)),
+ ?line badarg = ?MASK_ERROR(binary:copy(<<1,2,3>>,
+ 16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)),
+ ?line <<>> = binary:copy(<<>>,10000),
+ ?line random:seed({1271,769940,559934}),
+ ?line ok = random_copy(3000),
+ ?line erts_debug:set_internal_state(available_internal_state,true),
+ ?line io:format("oldlimit: ~p~n",
+ [erts_debug:set_internal_state(binary_loop_limit,10)]),
+ ?line Subj = subj(),
+ ?line XX = binary:copy(Subj,1000),
+ ?line XX = binref:copy(Subj,1000),
+ ?line ok = random_copy(1000),
+ ?line kill_copy_loop(1000),
+ ?line io:format("limit was: ~p~n",
+ [erts_debug:set_internal_state(binary_loop_limit,
+ default)]),
+ ?line erts_debug:set_internal_state(available_internal_state,false),
+ ok.
+
+kill_copy_loop(0) ->
+ ok;
+kill_copy_loop(N) ->
+ {Pid,Ref} = spawn_monitor(fun() ->
+ ok = random_copy(1000)
+ end),
+ receive
+ after 10 ->
+ ok
+ end,
+ exit(Pid,kill),
+ receive
+ {'DOWN',Ref,process,Pid,_} ->
+ kill_copy_loop(N-1)
+ after 1000 ->
+ exit(did_not_die)
+ end.
+
+random_copy(0) ->
+ ok;
+random_copy(N) ->
+ Str = random_string({0,N}),
+ Num = random:uniform(N div 10+1),
+ A = ?MASK_ERROR(binary:copy(Str,Num)),
+ B = ?MASK_ERROR(binref:copy(Str,Num)),
+ C = ?MASK_ERROR(binary:copy(make_unaligned(Str),Num)),
+ case {(A =:= B), (B =:= C)} of
+ {true,true} ->
+ random_copy(N-1);
+ _ ->
+ io:format("Failed to pick copy ~s ~p times~n",
+ [Str,Num]),
+ io:format("A:~p,~nB:~p,~n,C:~p.~n",
+ [A,B,C]),
+ exit(mismatch)
+ end.
+
+bin_to_list(doc) ->
+ ["Test bin_to_list/1,2,3 bif's"];
+bin_to_list(Config) when is_list(Config) ->
+ %% Just some smoke_tests first, then go nuts with random cases
+ ?line X = <<1,2,3,4,0:1000000,5>>,
+ ?line Y = make_unaligned(X),
+ ?line LX = binary:bin_to_list(X),
+ ?line LX = binary:bin_to_list(X,0,byte_size(X)),
+ ?line LX = binary:bin_to_list(X,byte_size(X),-byte_size(X)),
+ ?line LX = binary:bin_to_list(X,{0,byte_size(X)}),
+ ?line LX = binary:bin_to_list(X,{byte_size(X),-byte_size(X)}),
+ ?line LY = binary:bin_to_list(Y),
+ ?line LY = binary:bin_to_list(Y,0,byte_size(Y)),
+ ?line LY = binary:bin_to_list(Y,byte_size(Y),-byte_size(Y)),
+ ?line LY = binary:bin_to_list(Y,{0,byte_size(Y)}),
+ ?line LY = binary:bin_to_list(Y,{byte_size(Y),-byte_size(Y)}),
+ ?line 1 = hd(LX),
+ ?line 5 = lists:last(LX),
+ ?line 1 = hd(LY),
+ ?line 5 = lists:last(LY),
+ ?line X = list_to_binary(LY),
+ ?line Y = list_to_binary(LY),
+ ?line X = list_to_binary(LY),
+ ?line [5] = lists:nthtail(byte_size(X)-1,LX),
+ ?line [0,5] = lists:nthtail(byte_size(X)-2,LX),
+ ?line [0,5] = lists:nthtail(byte_size(Y)-2,LY),
+ ?line random:seed({1271,769940,559934}),
+ ?line ok = random_bin_to_list(5000),
+ ok.
+
+random_bin_to_list(0) ->
+ ok;
+random_bin_to_list(N) ->
+ Str = random_string({1,N}),
+ Parts0 = random_parts(10,N),
+ Parts1 = Parts0 ++ [ {X+Y,-Y} || {X,Y} <- Parts0 ],
+ [ begin
+ try
+ true = ?MASK_ERROR(binary:bin_to_list(Str,Z)) =:=
+ ?MASK_ERROR(binref:bin_to_list(Str,Z)),
+ true = ?MASK_ERROR(binary:bin_to_list(Str,Z)) =:=
+ ?MASK_ERROR(binary:bin_to_list(make_unaligned(Str),Z))
+ catch
+ _:_ ->
+ io:format("Error, Str = <<\"~s\">>.~nZ = ~p.~n",
+ [Str,Z]),
+ exit(badresult)
+ end
+ end || Z <- Parts1 ],
+ [ begin
+ try
+ true = ?MASK_ERROR(binary:bin_to_list(Str,A,B)) =:=
+ ?MASK_ERROR(binref:bin_to_list(Str,A,B)),
+ true = ?MASK_ERROR(binary:bin_to_list(Str,A,B)) =:=
+ ?MASK_ERROR(binary:bin_to_list(make_unaligned(Str),A,B))
+ catch
+ _:_ ->
+ io:format("Error, Str = <<\"~s\">>.~nA = ~p.~nB = ~p.~n",
+ [Str,A,B]),
+ exit(badresult)
+ end
+ end || {A,B} <- Parts1 ],
+ random_bin_to_list(N-1).
+
+parts(doc) ->
+ ["Test the part/2,3 bif's"];
+parts(Config) when is_list(Config) ->
+ %% Some simple smoke tests to begin with
+ ?line Simple = <<1,2,3,4,5,6,7,8>>,
+ ?line <<1,2>> = binary:part(Simple,0,2),
+ ?line <<1,2>> = binary:part(Simple,{0,2}),
+ ?line Simple = binary:part(Simple,0,8),
+ ?line Simple = binary:part(Simple,{0,8}),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,0,9)),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{0,9})),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,1,8)),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{1,8})),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{3,-4})),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{3.0,1})),
+ ?line badarg = ?MASK_ERROR(
+ binary:part(Simple,{16#FFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ ,1})),
+ ?line <<2,3,4,5,6,7,8>> = binary:part(Simple,{1,7}),
+ ?line <<2,3,4,5,6,7,8>> = binary:part(Simple,{8,-7}),
+ ?line Simple = binary:part(Simple,{8,-8}),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{1,-8})),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{8,-9})),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{0,-1})),
+ ?line <<>> = binary:part(Simple,{8,0}),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{9,0})),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{-1,0})),
+ ?line badarg = ?MASK_ERROR(binary:part(Simple,{7,2})),
+ ?line <<8>> = binary:part(Simple,{7,1}),
+ ?line random:seed({1271,769940,559934}),
+ ?line random_parts(5000),
+ ok.
+
+
+random_parts(0) ->
+ ok;
+random_parts(N) ->
+ Str = random_string({1,N}),
+ Parts0 = random_parts(10,N),
+ Parts1 = Parts0 ++ [ {X+Y,-Y} || {X,Y} <- Parts0 ],
+ [ begin
+ true = ?MASK_ERROR(binary:part(Str,Z)) =:=
+ ?MASK_ERROR(binref:part(Str,Z)),
+ true = ?MASK_ERROR(binary:part(Str,Z)) =:=
+ ?MASK_ERROR(erlang:binary_part(Str,Z)),
+ true = ?MASK_ERROR(binary:part(Str,Z)) =:=
+ ?MASK_ERROR(binary:part(make_unaligned(Str),Z))
+ end || Z <- Parts1 ],
+ random_parts(N-1).
+
+random_parts(0,_) ->
+ [];
+random_parts(X,N) ->
+ Pos = random:uniform(N),
+ Len = random:uniform((Pos * 12) div 10),
+ [{Pos,Len} | random_parts(X-1,N)].
+
+random_ref_comp(doc) ->
+ ["Test pseudorandomly generated cases against reference imlementation"];
+random_ref_comp(Config) when is_list(Config) ->
+ ?line put(success_counter,0),
+ ?line random:seed({1271,769940,559934}),
+ ?line do_random_match_comp(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_match_comp2(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_match_comp3(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_match_comp4(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_matches_comp(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_matches_comp2(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_matches_comp3(5,{1,40},{30,1000}),
+ ?line erts_debug:set_internal_state(available_internal_state,true),
+ ?line io:format("oldlimit: ~p~n",[ erts_debug:set_internal_state(binary_loop_limit,100)]),
+ ?line do_random_match_comp(5000,{1,40},{30,1000}),
+ ?line do_random_matches_comp3(5,{1,40},{30,1000}),
+ ?line io:format("limit was: ~p~n",[ erts_debug:set_internal_state(binary_loop_limit,default)]),
+ ?line erts_debug:set_internal_state(available_internal_state,false),
+ ok.
+
+random_ref_sr_comp(doc) ->
+ ["Test pseudorandomly generated cases against reference imlementation of split and replace"];
+random_ref_sr_comp(Config) when is_list(Config) ->
+ ?line put(success_counter,0),
+ ?line random:seed({1271,769940,559934}),
+ ?line do_random_split_comp(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_replace_comp(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_split_comp2(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ?line do_random_replace_comp2(5000,{1,40},{30,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ok.
+random_ref_fla_comp(doc) ->
+ ["Test pseudorandomly generated cases against reference imlementation of split and replace"];
+random_ref_fla_comp(Config) when is_list(Config) ->
+ ?line put(success_counter,0),
+ ?line random:seed({1271,769940,559934}),
+ ?line do_random_first_comp(5000,{1,1000}),
+ ?line do_random_last_comp(5000,{1,1000}),
+ ?line do_random_at_comp(5000,{1,1000}),
+ io:format("Number of successes: ~p~n",[get(success_counter)]),
+ ok.
+
+do_random_first_comp(0,_) ->
+ ok;
+do_random_first_comp(N,Range) ->
+ S = random_string(Range),
+ A = ?MASK_ERROR(binref:first(S)),
+ B = ?MASK_ERROR(binary:first(S)),
+ C = ?MASK_ERROR(binary:first(make_unaligned(S))),
+ case {(A =:= B), (B =:= C)} of
+ {true,true} ->
+ do_random_first_comp(N-1,Range);
+ _ ->
+ io:format("Failed to pick first of ~s~n",
+ [S]),
+ io:format("A:~p,~nB:~p,~n,C:~p.~n",
+ [A,B,C]),
+ exit(mismatch)
+ end.
+
+do_random_last_comp(0,_) ->
+ ok;
+do_random_last_comp(N,Range) ->
+ S = random_string(Range),
+ A = ?MASK_ERROR(binref:last(S)),
+ B = ?MASK_ERROR(binary:last(S)),
+ C = ?MASK_ERROR(binary:last(make_unaligned(S))),
+ case {(A =:= B), (B =:= C)} of
+ {true,true} ->
+ do_random_last_comp(N-1,Range);
+ _ ->
+ io:format("Failed to pick last of ~s~n",
+ [S]),
+ io:format("A:~p,~nB:~p,~n,C:~p.~n",
+ [A,B,C]),
+ exit(mismatch)
+ end.
+do_random_at_comp(0,_) ->
+ ok;
+do_random_at_comp(N,{Min,Max}=Range) ->
+ S = random_string(Range),
+ XMax = Min + ((Max - Min) * 3) div 4,
+ Pos = random_length({Min,XMax}), %% some out of range
+ A = ?MASK_ERROR(binref:at(S,Pos)),
+ B = ?MASK_ERROR(binary:at(S,Pos)),
+ C = ?MASK_ERROR(binary:at(make_unaligned(S),Pos)),
+ if
+ A =/= badarg ->
+ put(success_counter,get(success_counter)+1);
+ true ->
+ ok
+ end,
+ case {(A =:= B), (B =:= C)} of
+ {true,true} ->
+ do_random_at_comp(N-1,Range);
+ _ ->
+ io:format("Failed to pick last of ~s~n",
+ [S]),
+ io:format("A:~p,~nB:~p,~n,C:~p.~n",
+ [A,B,C]),
+ exit(mismatch)
+ end.
+
+do_random_matches_comp(0,_,_) ->
+ ok;
+do_random_matches_comp(N,NeedleRange,HaystackRange) ->
+ NumNeedles = element(2,HaystackRange) div element(2,NeedleRange),
+ Needles = [random_string(NeedleRange) ||
+ _ <- lists:duplicate(NumNeedles,a)],
+ Haystack = random_string(HaystackRange),
+ true = do_matches_comp(Needles,Haystack),
+ do_random_matches_comp(N-1,NeedleRange,HaystackRange).
+
+do_random_matches_comp2(0,_,_) ->
+ ok;
+do_random_matches_comp2(N,NeedleRange,HaystackRange) ->
+ NumNeedles = element(2,HaystackRange) div element(2,NeedleRange),
+ Haystack = random_string(HaystackRange),
+ Needles = [random_substring(NeedleRange,Haystack) ||
+ _ <- lists:duplicate(NumNeedles,a)],
+ true = do_matches_comp(Needles,Haystack),
+ do_random_matches_comp2(N-1,NeedleRange,HaystackRange).
+
+do_random_matches_comp3(0,_,_) ->
+ ok;
+do_random_matches_comp3(N,NeedleRange,HaystackRange) ->
+ NumNeedles = element(2,HaystackRange) div element(2,NeedleRange),
+ Haystack = random_string(HaystackRange),
+ Needles = [random_substring(NeedleRange,Haystack) ||
+ _ <- lists:duplicate(NumNeedles,a)],
+ RefRes = binref:matches(Haystack,Needles),
+ true = do_matches_comp_loop(10000,Needles,Haystack, RefRes),
+ do_random_matches_comp3(N-1,NeedleRange,HaystackRange).
+
+do_matches_comp_loop(0,_,_,_) ->
+ true;
+do_matches_comp_loop(N, Needles, Haystack0,RR) ->
+ DummySize=N*8,
+ Haystack1 = <<0:DummySize,Haystack0/binary>>,
+ RR1=[{X+N,Y} || {X,Y} <- RR],
+ true = do_matches_comp2(Needles,Haystack1,RR1),
+ Haystack2 = <<Haystack0/binary,Haystack1/binary>>,
+ RR2 = RR ++ [{X2+N+byte_size(Haystack0),Y2} || {X2,Y2} <- RR],
+ true = do_matches_comp2(Needles,Haystack2,RR2),
+ do_matches_comp_loop(N-1, Needles, Haystack0,RR).
+
+
+do_matches_comp2(N,H,A) ->
+ C = ?MASK_ERROR(binary:matches(H,N)),
+ case (A =:= C) of
+ true ->
+ true;
+ _ ->
+ io:format("Failed to match ~p (needle) against ~s (haystack)~n",
+ [N,H]),
+ io:format("A:~p,~n,C:~p.~n",
+ [A,C]),
+ exit(mismatch)
+ end.
+do_matches_comp(N,H) ->
+ A = ?MASK_ERROR(binref:matches(H,N)),
+ B = ?MASK_ERROR(binref:matches(H,binref:compile_pattern(N))),
+ C = ?MASK_ERROR(binary:matches(H,N)),
+ D = ?MASK_ERROR(binary:matches(make_unaligned(H),
+ binary:compile_pattern([make_unaligned2(X) || X <- N]))),
+ if
+ A =/= nomatch ->
+ put(success_counter,get(success_counter)+1);
+ true ->
+ ok
+ end,
+ case {(A =:= B), (B =:= C),(C =:= D)} of
+ {true,true,true} ->
+ true;
+ _ ->
+ io:format("Failed to match ~p (needle) against ~s (haystack)~n",
+ [N,H]),
+ io:format("A:~p,~nB:~p,~n,C:~p,~n,D:~p.~n",
+ [A,B,C,D]),
+ exit(mismatch)
+ end.
+
+do_random_match_comp(0,_,_) ->
+ ok;
+do_random_match_comp(N,NeedleRange,HaystackRange) ->
+ Needle = random_string(NeedleRange),
+ Haystack = random_string(HaystackRange),
+ true = do_match_comp(Needle,Haystack),
+ do_random_match_comp(N-1,NeedleRange,HaystackRange).
+
+do_random_match_comp2(0,_,_) ->
+ ok;
+do_random_match_comp2(N,NeedleRange,HaystackRange) ->
+ Haystack = random_string(HaystackRange),
+ Needle = random_substring(NeedleRange,Haystack),
+ true = do_match_comp(Needle,Haystack),
+ do_random_match_comp2(N-1,NeedleRange,HaystackRange).
+
+do_random_match_comp3(0,_,_) ->
+ ok;
+do_random_match_comp3(N,NeedleRange,HaystackRange) ->
+ NumNeedles = element(2,HaystackRange) div element(2,NeedleRange),
+ Haystack = random_string(HaystackRange),
+ Needles = [random_substring(NeedleRange,Haystack) ||
+ _ <- lists:duplicate(NumNeedles,a)],
+ true = do_match_comp3(Needles,Haystack),
+ do_random_match_comp3(N-1,NeedleRange,HaystackRange).
+
+do_random_match_comp4(0,_,_) ->
+ ok;
+do_random_match_comp4(N,NeedleRange,HaystackRange) ->
+ NumNeedles = element(2,HaystackRange) div element(2,NeedleRange),
+ Haystack = random_string(HaystackRange),
+ Needles = [random_string(NeedleRange) ||
+ _ <- lists:duplicate(NumNeedles,a)],
+ true = do_match_comp3(Needles,Haystack),
+ do_random_match_comp4(N-1,NeedleRange,HaystackRange).
+
+do_match_comp(N,H) ->
+ A = ?MASK_ERROR(binref:match(H,N)),
+ B = ?MASK_ERROR(binref:match(H,binref:compile_pattern([N]))),
+ C = ?MASK_ERROR(binary:match(make_unaligned(H),N)),
+ D = ?MASK_ERROR(binary:match(H,binary:compile_pattern([N]))),
+ E = ?MASK_ERROR(binary:match(H,binary:compile_pattern(make_unaligned(N)))),
+ if
+ A =/= nomatch ->
+ put(success_counter,get(success_counter)+1);
+ true ->
+ ok
+ end,
+ case {(A =:= B), (B =:= C),(C =:= D),(D =:= E)} of
+ {true,true,true,true} ->
+ true;
+ _ ->
+ io:format("Failed to match ~s (needle) against ~s (haystack)~n",
+ [N,H]),
+ io:format("A:~p,~nB:~p,~n,C:~p,~n,D:~p,E:~p.~n",
+ [A,B,C,D,E]),
+ exit(mismatch)
+ end.
+
+do_match_comp3(N,H) ->
+ A = ?MASK_ERROR(binref:match(H,N)),
+ B = ?MASK_ERROR(binref:match(H,binref:compile_pattern(N))),
+ C = ?MASK_ERROR(binary:match(H,N)),
+ D = ?MASK_ERROR(binary:match(H,binary:compile_pattern(N))),
+ if
+ A =/= nomatch ->
+ put(success_counter,get(success_counter)+1);
+ true ->
+ ok
+ end,
+ case {(A =:= B), (B =:= C),(C =:= D)} of
+ {true,true,true} ->
+ true;
+ _ ->
+ io:format("Failed to match ~s (needle) against ~s (haystack)~n",
+ [N,H]),
+ io:format("A:~p,~nB:~p,~n,C:~p,~n,D:~p.~n",
+ [A,B,C,D]),
+ exit(mismatch)
+ end.
+
+do_random_split_comp(0,_,_) ->
+ ok;
+do_random_split_comp(N,NeedleRange,HaystackRange) ->
+ Haystack = random_string(HaystackRange),
+ Needle = random_substring(NeedleRange,Haystack),
+ true = do_split_comp(Needle,Haystack,[]),
+ true = do_split_comp(Needle,Haystack,[global]),
+ true = do_split_comp(Needle,Haystack,[global,trim]),
+ do_random_split_comp(N-1,NeedleRange,HaystackRange).
+do_random_split_comp2(0,_,_) ->
+ ok;
+do_random_split_comp2(N,NeedleRange,HaystackRange) ->
+ NumNeedles = element(2,HaystackRange) div element(2,NeedleRange),
+ Haystack = random_string(HaystackRange),
+ Needles = [random_substring(NeedleRange,Haystack) ||
+ _ <- lists:duplicate(NumNeedles,a)],
+ true = do_split_comp(Needles,Haystack,[]),
+ true = do_split_comp(Needles,Haystack,[global]),
+ do_random_split_comp2(N-1,NeedleRange,HaystackRange).
+
+do_split_comp(N,H,Opts) ->
+ A = ?MASK_ERROR(binref:split(H,N,Opts)),
+ D = ?MASK_ERROR(binary:split(H,binary:compile_pattern(N),Opts)),
+ if
+ (A =/= [N]) and is_list(A) ->
+ put(success_counter,get(success_counter)+1);
+ true ->
+ ok
+ end,
+ case (A =:= D) of
+ true ->
+ true;
+ _ ->
+ io:format("Failed to split ~n~p ~n(haystack) with ~n~p ~n(needle) "
+ "~nand options ~p~n",
+ [H,N,Opts]),
+ io:format("A:~p,D:~p.~n",
+ [A,D]),
+ exit(mismatch)
+ end.
+
+do_random_replace_comp(0,_,_) ->
+ ok;
+do_random_replace_comp(N,NeedleRange,HaystackRange) ->
+ Haystack = random_string(HaystackRange),
+ Needle = random_substring(NeedleRange,Haystack),
+ Repl = random_string(NeedleRange),
+ Insertat = random_length(NeedleRange), %Sometimes larger than Repl
+ true = do_replace_comp(Needle,Haystack,Repl,[]),
+ true = do_replace_comp(Needle,Haystack,Repl,[global]),
+ true = do_replace_comp(Needle,Haystack,Repl,
+ [global,{insert_replaced,Insertat}]),
+ do_random_replace_comp(N-1,NeedleRange,HaystackRange).
+do_random_replace_comp2(0,_,_) ->
+ ok;
+do_random_replace_comp2(N,NeedleRange,HaystackRange) ->
+ NumNeedles = element(2,HaystackRange) div element(2,NeedleRange),
+ Haystack = random_string(HaystackRange),
+ Needles = [random_substring(NeedleRange,Haystack) ||
+ _ <- lists:duplicate(NumNeedles,a)],
+ Repl = random_string(NeedleRange),
+ Insertat = random_length(NeedleRange), %Sometimes larger than Repl
+ true = do_replace_comp(Needles,Haystack,Repl,[]),
+ true = do_replace_comp(Needles,Haystack,Repl,[global]),
+ true = do_replace_comp(Needles,Haystack,Repl,
+ [global,{insert_replaced,Insertat}]),
+ do_random_replace_comp2(N-1,NeedleRange,HaystackRange).
+
+do_replace_comp(N,H,R,Opts) ->
+ A = ?MASK_ERROR(binref:replace(H,N,R,Opts)),
+ D = ?MASK_ERROR(binary:replace(H,binary:compile_pattern(N),R,Opts)),
+ if
+ (A =/= N) and is_binary(A) ->
+ put(success_counter,get(success_counter)+1);
+ true ->
+ ok
+ end,
+ case (A =:= D) of
+ true ->
+ true;
+ _ ->
+ io:format("Failed to replace ~s (haystack) by ~s (needle) "
+ "inserting ~s (replacement) and options ~p~n",
+ [H,N,R,Opts]),
+ io:format("A:~p,D:~p.~n",
+ [A,D]),
+ exit(mismatch)
+ end.
+
+one_random_number(N) ->
+ M = ((N - 1) rem 10) + 1,
+ element(M,{$0,$1,$2,$3,$4,$5,$6,$7,$8,$9}).
+
+one_random(N) ->
+ M = ((N - 1) rem 68) + 1,
+ element(M,{$a,$b,$c,$d,$e,$f,$g,$h,$i,$j,$k,$l,$m,$n,$o,$p,$q,$r,$s,$t,
+ $u,$v,$w,$x,$y,$z,$�,$�,$�,$A,$B,$C,$D,$E,$F,$G,$H,
+ $I,$J,$K,$L,$M,$N,$O,$P,$Q,$R,$S,$T,$U,$V,$W,$X,$Y,$Z,$�,
+ $�,$�,$0,$1,$2,$3,$4,$5,$6,$7,$8,$9}).
+
+random_number({Min,Max}) -> % Min and Max are *length* of number in
+ % decimal positions
+ X = random:uniform(Max - Min + 1) + Min - 1,
+ list_to_integer([one_random_number(random:uniform(10)) || _ <- lists:seq(1,X)]).
+
+
+random_length({Min,Max}) ->
+ random:uniform(Max - Min + 1) + Min - 1.
+random_string({Min,Max}) ->
+ X = random:uniform(Max - Min + 1) + Min - 1,
+ list_to_binary([one_random(random:uniform(68)) || _ <- lists:seq(1,X)]).
+random_substring({Min,Max},Hay) ->
+ X = random:uniform(Max - Min + 1) + Min - 1,
+ Y = byte_size(Hay),
+ Z = if
+ X > Y -> Y;
+ true -> X
+ end,
+ PMax = Y - Z,
+ Pos = random:uniform(PMax + 1) - 1,
+ <<_:Pos/binary,Res:Z/binary,_/binary>> = Hay,
+ Res.
+
+mask_error({'EXIT',{Err,_}}) ->
+ Err;
+mask_error(Else) ->
+ Else.
+
+make_unaligned(Bin0) when is_binary(Bin0) ->
+ Bin1 = <<0:3,Bin0/binary,31:5>>,
+ Sz = byte_size(Bin0),
+ <<0:3,Bin:Sz/binary,31:5>> = id(Bin1),
+ Bin.
+make_unaligned2(Bin0) when is_binary(Bin0) ->
+ Bin1 = <<31:5,Bin0/binary,0:3>>,
+ Sz = byte_size(Bin0),
+ <<31:5,Bin:Sz/binary,0:3>> = id(Bin1),
+ Bin.
+
+id(I) -> I.
diff --git a/lib/stdlib/test/binref.erl b/lib/stdlib/test/binref.erl
new file mode 100644
index 0000000000..6d96736ef3
--- /dev/null
+++ b/lib/stdlib/test/binref.erl
@@ -0,0 +1,588 @@
+-module(binref).
+
+-export([compile_pattern/1,match/2,match/3,matches/2,matches/3,
+ split/2,split/3,replace/3,replace/4,first/1,last/1,at/2,
+ part/2,part/3,copy/1,copy/2,encode_unsigned/1,encode_unsigned/2,
+ decode_unsigned/1,decode_unsigned/2,referenced_byte_size/1,
+ longest_common_prefix/1,longest_common_suffix/1,bin_to_list/1,
+ bin_to_list/2,bin_to_list/3,list_to_bin/1]).
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% compile_pattern, a dummy
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+compile_pattern(Pattern) when is_binary(Pattern) ->
+ {[Pattern]};
+compile_pattern(Pattern) ->
+ try
+ [ true = is_binary(P) || P <- Pattern ],
+ {Pattern}
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% match and matches
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+match(H,N) ->
+ match(H,N,[]).
+match(Haystack,Needle,Options) when is_binary(Needle) ->
+ match(Haystack,[Needle],Options);
+match(Haystack,{Needles},Options) ->
+ match(Haystack,Needles,Options);
+match(Haystack,Needles,Options) ->
+ try
+ true = is_binary(Haystack) and is_list(Needles), % badarg, not function_clause
+ case get_opts_match(Options,nomatch) of
+ nomatch ->
+ mloop(Haystack,Needles);
+ {A,B} when B > 0 ->
+ <<_:A/binary,SubStack:B/binary,_/binary>> = Haystack,
+ mloop(SubStack,Needles,A,B+A);
+ {A,B} when B < 0 ->
+ Start = A + B,
+ Len = -B,
+ <<_:Start/binary,SubStack:Len/binary,_/binary>> = Haystack,
+ mloop(SubStack,Needles,Start,Len+Start);
+ _ ->
+ nomatch
+ end
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+matches(H,N) ->
+ matches(H,N,[]).
+matches(Haystack,Needle,Options) when is_binary(Needle) ->
+ matches(Haystack,[Needle],Options);
+matches(Haystack,{Needles},Options) ->
+ matches(Haystack,Needles,Options);
+matches(Haystack,Needles,Options) ->
+ try
+ true = is_binary(Haystack) and is_list(Needles), % badarg, not function_clause
+ case get_opts_match(Options,nomatch) of
+ nomatch ->
+ msloop(Haystack,Needles);
+ {A,B} when B > 0 ->
+ <<_:A/binary,SubStack:B/binary,_/binary>> = Haystack,
+ msloop(SubStack,Needles,A,B+A);
+ {A,B} when B < 0 ->
+ Start = A + B,
+ Len = -B,
+ <<_:Start/binary,SubStack:Len/binary,_/binary>> = Haystack,
+ msloop(SubStack,Needles,Start,Len+Start);
+ _ ->
+ []
+ end
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+mloop(Haystack,Needles) ->
+ mloop(Haystack,Needles,0,byte_size(Haystack)).
+
+mloop(_Haystack,_Needles,N,M) when N >= M ->
+ nomatch;
+mloop(Haystack,Needles,N,M) ->
+ case mloop2(Haystack,Needles,N,nomatch) of
+ nomatch ->
+ % Not found
+ <<_:8,NewStack/binary>> = Haystack,
+ mloop(NewStack,Needles,N+1,M);
+ {N,Len} ->
+ {N,Len}
+ end.
+
+msloop(Haystack,Needles) ->
+ msloop(Haystack,Needles,0,byte_size(Haystack)).
+
+msloop(_Haystack,_Needles,N,M) when N >= M ->
+ [];
+msloop(Haystack,Needles,N,M) ->
+ case mloop2(Haystack,Needles,N,nomatch) of
+ nomatch ->
+ % Not found
+ <<_:8,NewStack/binary>> = Haystack,
+ msloop(NewStack,Needles,N+1,M);
+ {N,Len} ->
+ NewN = N+Len,
+ if
+ NewN >= M ->
+ [{N,Len}];
+ true ->
+ <<_:Len/binary,NewStack/binary>> = Haystack,
+ [{N,Len} | msloop(NewStack,Needles,NewN,M)]
+ end
+ end.
+
+mloop2(_Haystack,[],_N,Res) ->
+ Res;
+mloop2(Haystack,[Needle|Tail],N,Candidate) ->
+ NS = byte_size(Needle),
+ case Haystack of
+ <<Needle:NS/binary,_/binary>> ->
+ NewCandidate = case Candidate of
+ nomatch ->
+ {N,NS};
+ {N,ONS} when ONS < NS ->
+ {N,NS};
+ Better ->
+ Better
+ end,
+ mloop2(Haystack,Tail,N,NewCandidate);
+ _ ->
+ mloop2(Haystack,Tail,N,Candidate)
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% split
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+split(H,N) ->
+ split(H,N,[]).
+split(Haystack,{Needles},Options) ->
+ split(Haystack, Needles, Options);
+split(Haystack,Needles0,Options) ->
+ try
+ Needles = if
+ is_list(Needles0) ->
+ Needles0;
+ is_binary(Needles0) ->
+ [Needles0];
+ true ->
+ exit(badtype)
+ end,
+ {Part,Global,Trim} = get_opts_split(Options,{nomatch,false,false}),
+ {Start,End,NewStack} =
+ case Part of
+ nomatch ->
+ {0,byte_size(Haystack),Haystack};
+ {A,B} when B >= 0 ->
+ <<_:A/binary,SubStack:B/binary,_/binary>> = Haystack,
+ {A,A+B,SubStack};
+ {A,B} when B < 0 ->
+ S = A + B,
+ L = -B,
+ <<_:S/binary,SubStack:L/binary,_/binary>> = Haystack,
+ {S,S+L,SubStack}
+ end,
+ MList = if
+ Global ->
+ msloop(NewStack,Needles,Start,End);
+ true ->
+ case mloop(NewStack,Needles,Start,End) of
+ nomatch ->
+ [];
+ X ->
+ [X]
+ end
+ end,
+ do_split(Haystack,MList,0,Trim)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+do_split(H,[],N,true) when N >= byte_size(H) ->
+ [];
+do_split(H,[],N,_) ->
+ [part(H,{N,byte_size(H)-N})];
+do_split(H,[{A,B}|T],N,Trim) ->
+ case part(H,{N,A-N}) of
+ <<>> ->
+ Rest = do_split(H,T,A+B,Trim),
+ case {Trim, Rest} of
+ {true,[]} ->
+ [];
+ _ ->
+ [<<>> | Rest]
+ end;
+ Oth ->
+ [Oth | do_split(H,T,A+B,Trim)]
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% replace
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+replace(H,N,R) ->
+ replace(H,N,R,[]).
+replace(Haystack,{Needles},Replacement,Options) ->
+ replace(Haystack,Needles,Replacement,Options);
+
+replace(Haystack,Needles0,Replacement,Options) ->
+ try
+ Needles = if
+ is_list(Needles0) ->
+ Needles0;
+ is_binary(Needles0) ->
+ [Needles0];
+ true ->
+ exit(badtype)
+ end,
+ true = is_binary(Replacement), % Make badarg instead of function clause
+ {Part,Global,Insert} = get_opts_replace(Options,{nomatch,false,[]}),
+ {Start,End,NewStack} =
+ case Part of
+ nomatch ->
+ {0,byte_size(Haystack),Haystack};
+ {A,B} when B >= 0 ->
+ <<_:A/binary,SubStack:B/binary,_/binary>> = Haystack,
+ {A,A+B,SubStack};
+ {A,B} when B < 0 ->
+ S = A + B,
+ L = -B,
+ <<_:S/binary,SubStack:L/binary,_/binary>> = Haystack,
+ {S,S+L,SubStack}
+ end,
+ MList = if
+ Global ->
+ msloop(NewStack,Needles,Start,End);
+ true ->
+ case mloop(NewStack,Needles,Start,End) of
+ nomatch ->
+ [];
+ X ->
+ [X]
+ end
+ end,
+ ReplList = case Insert of
+ [] ->
+ Replacement;
+ Y when is_integer(Y) ->
+ splitat(Replacement,0,[Y]);
+ Li when is_list(Li) ->
+ splitat(Replacement,0,lists:sort(Li))
+ end,
+ erlang:iolist_to_binary(do_replace(Haystack,MList,ReplList,0))
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+
+do_replace(H,[],_,N) ->
+ [part(H,{N,byte_size(H)-N})];
+do_replace(H,[{A,B}|T],Replacement,N) ->
+ [part(H,{N,A-N}),
+ if
+ is_list(Replacement) ->
+ do_insert(Replacement, part(H,{A,B}));
+ true ->
+ Replacement
+ end
+ | do_replace(H,T,Replacement,A+B)].
+
+do_insert([X],_) ->
+ [X];
+do_insert([H|T],R) ->
+ [H,R|do_insert(T,R)].
+
+splitat(H,N,[]) ->
+ [part(H,{N,byte_size(H)-N})];
+splitat(H,N,[I|T]) ->
+ [part(H,{N,I-N})|splitat(H,I,T)].
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% first, last and at
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+first(Subject) ->
+ try
+ <<A:8,_/binary>> = Subject,
+ A
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+last(Subject) ->
+ try
+ N = byte_size(Subject) - 1,
+ <<_:N/binary,A:8>> = Subject,
+ A
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+at(Subject,X) ->
+ try
+ <<_:X/binary,A:8,_/binary>> = Subject,
+ A
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% bin_to_list
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+bin_to_list(Subject) ->
+ try
+ binary_to_list(Subject)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+bin_to_list(Subject,T) ->
+ try
+ {A0,B0} = T,
+ {A,B} = if
+ B0 < 0 ->
+ {A0+B0,-B0};
+ true ->
+ {A0,B0}
+ end,
+ binary_to_list(Subject,A+1,A+B)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+bin_to_list(Subject,A,B) ->
+ try
+ bin_to_list(Subject,{A,B})
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+% list_to_bin
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+list_to_bin(List) ->
+ try
+ erlang:list_to_binary(List)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% longest_common_prefix
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+longest_common_prefix(LB) ->
+ try
+ true = is_list(LB) and (length(LB) > 0), % Make badarg instead of function clause
+ do_longest_common_prefix(LB,0)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+do_longest_common_prefix(LB,X) ->
+ case do_lcp(LB,X,no) of
+ true ->
+ do_longest_common_prefix(LB,X+1);
+ false ->
+ X
+ end.
+do_lcp([],_,_) ->
+ true;
+do_lcp([Bin|_],X,_) when byte_size(Bin) =< X ->
+ false;
+do_lcp([Bin|T],X,no) ->
+ Ch = at(Bin,X),
+ do_lcp(T,X,Ch);
+do_lcp([Bin|T],X,Ch) ->
+ Ch2 = at(Bin,X),
+ if
+ Ch =:= Ch2 ->
+ do_lcp(T,X,Ch);
+ true ->
+ false
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% longest_common_suffix
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+longest_common_suffix(LB) ->
+ try
+ true = is_list(LB) and (length(LB) > 0), % Make badarg instead of function clause
+ do_longest_common_suffix(LB,0)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+do_longest_common_suffix(LB,X) ->
+ case do_lcs(LB,X,no) of
+ true ->
+ do_longest_common_suffix(LB,X+1);
+ false ->
+ X
+ end.
+do_lcs([],_,_) ->
+ true;
+do_lcs([Bin|_],X,_) when byte_size(Bin) =< X ->
+ false;
+do_lcs([Bin|T],X,no) ->
+ Ch = at(Bin,byte_size(Bin) - 1 - X),
+ do_lcs(T,X,Ch);
+do_lcs([Bin|T],X,Ch) ->
+ Ch2 = at(Bin,byte_size(Bin) - 1 - X),
+ if
+ Ch =:= Ch2 ->
+ do_lcs(T,X,Ch);
+ true ->
+ false
+ end.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% part
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+part(Subject,Part) ->
+ try
+ do_part(Subject,Part)
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+part(Subject,Pos,Len) ->
+ part(Subject,{Pos,Len}).
+
+do_part(Bin,{A,B}) when B >= 0 ->
+ <<_:A/binary,Sub:B/binary,_/binary>> = Bin,
+ Sub;
+do_part(Bin,{A,B}) when B < 0 ->
+ S = A + B,
+ L = -B,
+ <<_:S/binary,Sub:L/binary,_/binary>> = Bin,
+ Sub.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% copy
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+copy(Subject) ->
+ copy(Subject,1).
+copy(Subject,N) ->
+ try
+ true = is_integer(N) and (N >= 0) and is_binary(Subject), % Badarg, not function clause
+ erlang:list_to_binary(lists:duplicate(N,Subject))
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% encode_unsigned
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+encode_unsigned(Unsigned) ->
+ encode_unsigned(Unsigned,big).
+encode_unsigned(Unsigned,Endian) ->
+ try
+ true = is_integer(Unsigned) and (Unsigned >= 0),
+ if
+ Unsigned =:= 0 ->
+ <<0>>;
+ true ->
+ case Endian of
+ big ->
+ list_to_binary(do_encode(Unsigned,[]));
+ little ->
+ list_to_binary(do_encode_r(Unsigned))
+ end
+ end
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+do_encode(0,L) ->
+ L;
+do_encode(N,L) ->
+ Byte = N band 255,
+ NewN = N bsr 8,
+ do_encode(NewN,[Byte|L]).
+
+do_encode_r(0) ->
+ [];
+do_encode_r(N) ->
+ Byte = N band 255,
+ NewN = N bsr 8,
+ [Byte|do_encode_r(NewN)].
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% decode_unsigned
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+decode_unsigned(Subject) ->
+ decode_unsigned(Subject,big).
+
+decode_unsigned(Subject,Endian) ->
+ try
+ true = is_binary(Subject),
+ case Endian of
+ big ->
+ do_decode(Subject,0);
+ little ->
+ do_decode_r(Subject,0)
+ end
+ catch
+ _:_ ->
+ erlang:error(badarg)
+ end.
+
+do_decode(<<>>,N) ->
+ N;
+do_decode(<<X:8,Bin/binary>>,N) ->
+ do_decode(Bin,(N bsl 8) bor X).
+
+do_decode_r(<<>>,N) ->
+ N;
+do_decode_r(Bin,N) ->
+ Sz = byte_size(Bin) - 1,
+ <<NewBin:Sz/binary,X>> = Bin,
+ do_decode_r(NewBin, (N bsl 8) bor X).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% referenced_byte_size cannot
+%% be implemented in pure
+%% erlang
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+referenced_byte_size(Bin) when is_binary(Bin) ->
+ erlang:error(not_implemented);
+referenced_byte_size(_) ->
+ erlang:error(badarg).
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Simple helper functions
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Option "parsing"
+get_opts_match([],Part) ->
+ Part;
+get_opts_match([{scope,{A,B}} | T],_Part) ->
+ get_opts_match(T,{A,B});
+get_opts_match(_,_) ->
+ throw(badopt).
+
+get_opts_split([],{Part,Global,Trim}) ->
+ {Part,Global,Trim};
+get_opts_split([{scope,{A,B}} | T],{_Part,Global,Trim}) ->
+ get_opts_split(T,{{A,B},Global,Trim});
+get_opts_split([global | T],{Part,_Global,Trim}) ->
+ get_opts_split(T,{Part,true,Trim});
+get_opts_split([trim | T],{Part,Global,_Trim}) ->
+ get_opts_split(T,{Part,Global,true});
+get_opts_split(_,_) ->
+ throw(badopt).
+
+get_opts_replace([],{Part,Global,Insert}) ->
+ {Part,Global,Insert};
+get_opts_replace([{scope,{A,B}} | T],{_Part,Global,Insert}) ->
+ get_opts_replace(T,{{A,B},Global,Insert});
+get_opts_replace([global | T],{Part,_Global,Insert}) ->
+ get_opts_replace(T,{Part,true,Insert});
+get_opts_replace([{insert_replaced,N} | T],{Part,Global,_Insert}) ->
+ get_opts_replace(T,{Part,Global,N});
+get_opts_replace(_,_) ->
+ throw(badopt).
diff --git a/lib/stdlib/test/dummy1_h.erl b/lib/stdlib/test/dummy1_h.erl
index 4377d774a3..5b503d5984 100644
--- a/lib/stdlib/test/dummy1_h.erl
+++ b/lib/stdlib/test/dummy1_h.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(dummy1_h).
@@ -21,7 +21,7 @@
%% Test event handler for gen_event_SUITE.erl
-export([init/1, handle_event/2, handle_call/2, handle_info/2,
- terminate/2]).
+ terminate/2, format_status/2]).
init(make_error) ->
{error, my_error};
@@ -67,4 +67,5 @@ terminate(remove_handler, Parent) ->
terminate(_Reason, _State) ->
ok.
-
+format_status(_Opt, [_PDict, _State]) ->
+ "dummy1_h handler state".
diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl
index 4806b5d361..e31dfdd764 100644
--- a/lib/stdlib/test/epp_SUITE.erl
+++ b/lib/stdlib/test/epp_SUITE.erl
@@ -19,12 +19,12 @@
-module(epp_SUITE).
-export([all/1]).
--export([rec_1/1, predef_mac/1,
+-export([rec_1/1, predef_mac/1,
upcase_mac/1, upcase_mac_1/1, upcase_mac_2/1,
variable/1, variable_1/1, otp_4870/1, otp_4871/1, otp_5362/1,
pmod/1, not_circular/1, skip_header/1, otp_6277/1, otp_7702/1,
otp_8130/1, overload_mac/1, otp_8388/1, otp_8470/1, otp_8503/1,
- otp_8562/1]).
+ otp_8562/1, otp_8665/1]).
-export([epp_parse_erl_form/2]).
@@ -39,7 +39,7 @@
-define(config(A,B),config(A,B)).
%% -define(t, test_server).
-define(t, io).
-config(priv_dir, _) ->
+config(priv_dir, _) ->
filename:absname("./epp_SUITE_priv");
config(data_dir, _) ->
filename:absname("./epp_SUITE_data").
@@ -64,7 +64,7 @@ all(doc) ->
all(suite) ->
[rec_1, upcase_mac, predef_mac, variable, otp_4870, otp_4871, otp_5362,
pmod, not_circular, skip_header, otp_6277, otp_7702, otp_8130,
- overload_mac, otp_8388, otp_8470, otp_8503, otp_8562].
+ overload_mac, otp_8388, otp_8470, otp_8503, otp_8562, otp_8665].
rec_1(doc) ->
["Recursive macros hang or crash epp (OTP-1398)."];
@@ -192,7 +192,7 @@ variable_1(Config) when is_list(Config) ->
%% variable_1.erl includes variable_1_include.hrl and
%% variable_1_include_dir.hrl.
?line {ok, List} = epp:parse_file(File, [], []),
- ?line {value, {attribute,_,a,{value1,value2}}} =
+ ?line {value, {attribute,_,a,{value1,value2}}} =
lists:keysearch(a,3,List),
ok.
@@ -219,13 +219,13 @@ otp_4871(Config) when is_list(Config) ->
%% Testing crash in erl_scan. Unfortunately there currently is
%% no known way to crash erl_scan so it is emulated by killing the
%% file io server. This assumes lots of things about how
- %% the processes are started and how monitors are set up,
+ %% the processes are started and how monitors are set up,
%% so there are some sanity checks before killing.
?line {ok,Epp} = epp:open(File, []),
timer:sleep(1),
?line {current_function,{epp,_,_}} = process_info(Epp, current_function),
?line {monitored_by,[Io]} = process_info(Epp, monitored_by),
- ?line {current_function,{file_io_server,_,_}} =
+ ?line {current_function,{file_io_server,_,_}} =
process_info(Io, current_function),
?line exit(Io, emulate_crash),
timer:sleep(1),
@@ -302,7 +302,7 @@ otp_5362(Config) when is_list(Config) ->
Back_hrl = [<<"
-file(\"">>,File_Back,<<"\", 2).
">>],
-
+
?line ok = file:write_file(File_Back, Back),
?line ok = file:write_file(File_Back_hrl, list_to_binary(Back_hrl)),
@@ -333,7 +333,7 @@ otp_5362(Config) when is_list(Config) ->
?line ok = file:write_file(File_Change, list_to_binary(Change)),
- ?line {ok, change_5362, ChangeWarnings} =
+ ?line {ok, change_5362, ChangeWarnings} =
compile:file(File_Change, Copts),
?line true = message_compare(
[{File_Change,[{{1002,21},erl_lint,{unused_var,'B'}}]},
@@ -441,9 +441,9 @@ skip_header(Config) when is_list(Config) ->
that should be skipped
-module(epp_test_skip_header).
-export([main/1]).
-
+
main(_) -> ?MODULE.
-
+
">>),
?line {ok, Fd} = file:open(File, [read]),
?line io:get_line(Fd, ''),
@@ -494,9 +494,9 @@ otp_7702(Config) when is_list(Config) ->
t() ->
?RECEIVE(foo, bar).">>,
?line ok = file:write_file(File, Contents),
- ?line {ok, file_7702, []} =
+ ?line {ok, file_7702, []} =
compile:file(File, [debug_info,return,{outdir,Dir}]),
-
+
BeamFile = filename:join(Dir, "file_7702.beam"),
{ok, AC} = beam_lib:chunks(BeamFile, [abstract_code]),
@@ -506,7 +506,7 @@ otp_7702(Config) when is_list(Config) ->
L
end,
Forms2 = [erl_lint:modify_line(Form, Fun) || Form <- Forms],
- ?line
+ ?line
[{attribute,1,file,_},
_,
_,
@@ -637,7 +637,7 @@ otp_8130(Config) when is_list(Config) ->
],
?line [] = run(Config, Ts),
-
+
Cs = [{otp_8130_c1,
<<"-define(M1(A), if\n"
"A =:= 1 -> B;\n"
@@ -681,7 +681,7 @@ otp_8130(Config) when is_list(Config) ->
<<"\n-include_lib(\"$apa/foo.hrl\").\n">>,
{errors,[{{2,2},epp,{include,lib,"$apa/foo.hrl"}}],[]}},
-
+
{otp_8130_c9,
<<"-define(S, ?S).\n"
"t() -> ?S.\n">>,
@@ -775,7 +775,7 @@ otp_8130(Config) when is_list(Config) ->
?line Dir = ?config(priv_dir, Config),
?line File = filename:join(Dir, "otp_8130.erl"),
- ?line ok = file:write_file(File,
+ ?line ok = file:write_file(File,
"-module(otp_8130).\n"
"-define(a, 3.14).\n"
"t() -> ?a.\n"),
@@ -788,7 +788,7 @@ otp_8130(Config) when is_list(Config) ->
?line {eof,_} = epp:scan_erl_form(Epp),
?line ['BASE_MODULE','BASE_MODULE_STRING','BEAM','FILE','LINE',
'MACHINE','MODULE','MODULE_STRING',a] = macs(Epp),
- ?line epp:close(Epp),
+ ?line epp:close(Epp),
%% escript
ModuleStr = "any_name",
@@ -815,7 +815,7 @@ otp_8130(Config) when is_list(Config) ->
PreDefMacros = [{a,1},a],
?line {error,{redefine,a}} = epp:open(File, [], PreDefMacros)
end(),
-
+
?line {error,enoent} = epp:open("no such file", []),
?line {error,enoent} = epp:parse_file("no such file", [], []),
@@ -941,7 +941,7 @@ ifdef(Config) ->
<<"\n-if.\n"
"-endif.\n">>,
{errors,[{{2,2},epp,{'NYI','if'}}],[]}},
-
+
{define_c7,
<<"-ifndef(a).\n"
"-elif.\n"
@@ -1197,6 +1197,18 @@ otp_8562(Config) when is_list(Config) ->
?line [] = compile(Config, Cs),
ok.
+otp_8665(doc) ->
+ ["OTP-8665. Bugfix premature end."];
+otp_8665(suite) ->
+ [];
+otp_8665(Config) when is_list(Config) ->
+ Cs = [{otp_8562,
+ <<"-define(A, a)\n">>,
+ {errors,[{{1,54},epp,premature_end}],[]}}
+ ],
+ ?line [] = compile(Config, Cs),
+ ok.
+
check(Config, Tests) ->
eval_tests(Config, fun check_test/2, Tests).
@@ -1213,7 +1225,7 @@ eval_tests(Config, Fun, Tests) ->
case message_compare(E, Return) of
true ->
BadL;
- false ->
+ false ->
?t:format("~nTest ~p failed. Expected~n ~p~n"
"but got~n ~p~n", [N, E, Return]),
fail()
@@ -1228,9 +1240,9 @@ check_test(Config, Test) ->
?line File = filename:join(PrivDir, Filename),
?line ok = file:write_file(File, Test),
?line case epp:parse_file(File, [PrivDir], []) of
- {ok,Forms} ->
+ {ok,Forms} ->
[E || E={error,_} <- Forms];
- {error,Error} ->
+ {error,Error} ->
Error
end.
@@ -1245,7 +1257,7 @@ compile_test(Config, Test0) ->
{ok, Ws} -> warnings(File, Ws);
Else -> Else
end.
-
+
warnings(File, Ws) ->
case lists:append([W || {F, W} <- Ws, F =:= File]) of
[] -> [];
@@ -1289,7 +1301,7 @@ message_compare(T, T) ->
message_compare(T1, T2) ->
ln(T1) =:= T2.
-%% Replaces locations like {Line,Column} with Line.
+%% Replaces locations like {Line,Column} with Line.
ln({warnings,L}) ->
{warnings,ln0(L)};
ln({errors,EL,WL}) ->
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index 8581b496aa..d0c0d68b4a 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -1784,6 +1784,9 @@ otp_5362(Config) when is_list(Config) ->
{15,erl_lint,{undefined_field,ok,nix}},
{16,erl_lint,{field_name_is_variable,ok,'Var'}}]}},
+ %% Nowarn_bif_clash has changed behaviour as local functions
+ %% nowdays supersede auto-imported BIFs, why nowarn_bif_clash in itself generates an error
+ %% (OTP-8579) /PaN
{otp_5362_4,
<<"-compile(nowarn_deprecated_function).
-compile(nowarn_bif_clash).
@@ -1795,9 +1798,8 @@ otp_5362(Config) when is_list(Config) ->
warn_deprecated_function,
warn_bif_clash]},
{error,
- [{5,erl_lint,{call_to_redefined_bif,{spawn,1}}}],
- [{3,erl_lint,{redefine_bif,{spawn,1}}},
- {4,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2},
+ [{5,erl_lint,{call_to_redefined_old_bif,{spawn,1}}}],
+ [{4,erl_lint,{deprecated,{erlang,hash,2},{erlang,phash2,2},
"in a future release"}}]}},
{otp_5362_5,
@@ -1808,8 +1810,8 @@ otp_5362(Config) when is_list(Config) ->
spawn(A).
">>,
{[nowarn_unused_function]},
- {warnings,
- [{3,erl_lint,{redefine_bif,{spawn,1}}}]}},
+ {errors,
+ [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}},
%% The special nowarn_X are not affected by general warn_X.
{otp_5362_6,
@@ -1822,8 +1824,8 @@ otp_5362(Config) when is_list(Config) ->
{[nowarn_unused_function,
warn_deprecated_function,
warn_bif_clash]},
- {warnings,
- [{3,erl_lint,{redefine_bif,{spawn,1}}}]}},
+ {errors,
+ [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}},
{otp_5362_7,
<<"-export([spawn/1]).
@@ -1838,7 +1840,9 @@ otp_5362(Config) when is_list(Config) ->
spawn(A).
">>,
{[nowarn_unused_function]},
- {error,[{4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}],
+ {error,[{3,erl_lint,disallowed_nowarn_bif_clash},
+ {4,erl_lint,disallowed_nowarn_bif_clash},
+ {4,erl_lint,{bad_nowarn_bif_clash,{spawn,2}}}],
[{5,erl_lint,{bad_nowarn_deprecated_function,{3,hash,-1}}},
{5,erl_lint,{bad_nowarn_deprecated_function,{erlang,hash,-1}}},
{5,erl_lint,{bad_nowarn_deprecated_function,{{a,b,c},hash,-1}}}]}
@@ -1865,7 +1869,21 @@ otp_5362(Config) when is_list(Config) ->
t() -> #a{}.
">>,
{[]},
- []}
+ []},
+
+ {otp_5362_10,
+ <<"-compile({nowarn_deprecated_function,{erlang,hash,2}}).
+ -compile({nowarn_bif_clash,{spawn,1}}).
+ -import(x,[spawn/1]).
+ spin(A) ->
+ erlang:hash(A, 3000),
+ spawn(A).
+ ">>,
+ {[nowarn_unused_function,
+ warn_deprecated_function,
+ warn_bif_clash]},
+ {errors,
+ [{2,erl_lint,disallowed_nowarn_bif_clash}],[]}}
],
@@ -2234,7 +2252,7 @@ otp_5878(Config) when is_list(Config) ->
{15,erl_lint,{undefined_field,r3,q}},
{17,erl_lint,{undefined_field,r,q}},
{21,erl_lint,illegal_guard_expr},
- {23,erl_lint,illegal_guard_expr}],
+ {23,erl_lint,{illegal_guard_local_call,{l,0}}}],
[]} =
run_test2(Config, Ill1, [warn_unused_record]),
@@ -2389,9 +2407,9 @@ bif_clash(Config) when is_list(Config) ->
N.
">>,
[],
- {errors,[{2,erl_lint,{call_to_redefined_bif,{size,1}}}],[]}},
+ {errors,[{2,erl_lint,{call_to_redefined_old_bif,{size,1}}}],[]}},
- %% Verify that (some) warnings can be turned off.
+ %% Verify that warnings can not be turned off in the old way.
{clash2,
<<"-export([t/1,size/1]).
t(X) ->
@@ -2400,17 +2418,198 @@ bif_clash(Config) when is_list(Config) ->
size({N,_}) ->
N.
- %% My own abs/1 function works on lists too.
- %% Unfortunately, it is not exported, so there will
- %% be a warning that can't be turned off.
+ %% My own abs/1 function works on lists too. From R14 this really works.
abs([H|T]) when $a =< H, H =< $z -> [H-($a-$A)|abs(T)];
abs([H|T]) -> [H|abs(T)];
abs([]) -> [];
abs(X) -> erlang:abs(X).
">>,
- {[nowarn_bif_clash]},
- {warnings,[{11,erl_lint,{redefine_bif,{abs,1}}},
- {11,erl_lint,{unused_function,{abs,1}}}]}}],
+ {[nowarn_unused_function,nowarn_bif_clash]},
+ {errors,[{erl_lint,disallowed_nowarn_bif_clash}],[]}},
+ %% As long as noone calls an overridden BIF, it's totally OK
+ {clash3,
+ <<"-export([size/1]).
+ size({N,_}) ->
+ N;
+ size(X) ->
+ erlang:size(X).
+ ">>,
+ [],
+ []},
+ %% But this is totally wrong - meaning of the program changed in R14, so this is an error
+ {clash4,
+ <<"-export([size/1]).
+ size({N,_}) ->
+ N;
+ size(X) ->
+ size(X).
+ ">>,
+ [],
+ {errors,[{5,erl_lint,{call_to_redefined_old_bif,{size,1}}}],[]}},
+ %% For a post R14 bif, its only a warning
+ {clash5,
+ <<"-export([binary_part/2]).
+ binary_part({B,_},{X,Y}) ->
+ binary_part(B,{X,Y});
+ binary_part(B,{X,Y}) ->
+ binary:part(B,X,Y).
+ ">>,
+ [],
+ {warnings,[{3,erl_lint,{call_to_redefined_bif,{binary_part,2}}}]}},
+ %% If you really mean to call yourself here, you can "unimport" size/1
+ {clash6,
+ <<"-export([size/1]).
+ -compile({no_auto_import,[size/1]}).
+ size([]) ->
+ 0;
+ size({N,_}) ->
+ N;
+ size([_|T]) ->
+ 1+size(T).
+ ">>,
+ [],
+ []},
+ %% Same for the post R14 autoimport warning
+ {clash7,
+ <<"-export([binary_part/2]).
+ -compile({no_auto_import,[binary_part/2]}).
+ binary_part({B,_},{X,Y}) ->
+ binary_part(B,{X,Y});
+ binary_part(B,{X,Y}) ->
+ binary:part(B,X,Y).
+ ">>,
+ [],
+ []},
+ %% but this doesn't mean the local function is allowed in a guard...
+ {clash8,
+ <<"-export([x/1]).
+ -compile({no_auto_import,[binary_part/2]}).
+ x(X) when binary_part(X,{1,2}) =:= <<1,2>> ->
+ hej.
+ binary_part({B,_},{X,Y}) ->
+ binary_part(B,{X,Y});
+ binary_part(B,{X,Y}) ->
+ binary:part(B,X,Y).
+ ">>,
+ [],
+ {errors,[{3,erl_lint,{illegal_guard_local_call,{binary_part,2}}}],[]}},
+ %% no_auto_import is not like nowarn_bif_clash, it actually removes the autoimport
+ {clash9,
+ <<"-export([x/1]).
+ -compile({no_auto_import,[binary_part/2]}).
+ x(X) ->
+ binary_part(X,{1,2}) =:= <<1,2>>.
+ ">>,
+ [],
+ {errors,[{4,erl_lint,{undefined_function,{binary_part,2}}}],[]}},
+ %% but we could import it again...
+ {clash10,
+ <<"-export([x/1]).
+ -compile({no_auto_import,[binary_part/2]}).
+ -import(erlang,[binary_part/2]).
+ x(X) ->
+ binary_part(X,{1,2}) =:= <<1,2>>.
+ ">>,
+ [],
+ []},
+ %% and actually use it in a guard...
+ {clash11,
+ <<"-export([x/1]).
+ -compile({no_auto_import,[binary_part/2]}).
+ -import(erlang,[binary_part/2]).
+ x(X) when binary_part(X,{0,1}) =:= <<0>> ->
+ binary_part(X,{1,2}) =:= <<1,2>>.
+ ">>,
+ [],
+ []},
+ %% but for non-obvious historical reasons, imported functions cannot be used in
+ %% fun construction without the module name...
+ {clash12,
+ <<"-export([x/1]).
+ -compile({no_auto_import,[binary_part/2]}).
+ -import(erlang,[binary_part/2]).
+ x(X) when binary_part(X,{0,1}) =:= <<0>> ->
+ binary_part(X,{1,2}) =:= fun binary_part/2.
+ ">>,
+ [],
+ {errors,[{5,erl_lint,{undefined_function,{binary_part,2}}}],[]}},
+ %% Not from erlang and not from anywhere else
+ {clash13,
+ <<"-export([x/1]).
+ -compile({no_auto_import,[binary_part/2]}).
+ -import(x,[binary_part/2]).
+ x(X) ->
+ binary_part(X,{1,2}) =:= fun binary_part/2.
+ ">>,
+ [],
+ {errors,[{5,erl_lint,{undefined_function,{binary_part,2}}}],[]}},
+ %% ...while real auto-import is OK.
+ {clash14,
+ <<"-export([x/1]).
+ x(X) when binary_part(X,{0,1}) =:= <<0>> ->
+ binary_part(X,{1,2}) =:= fun binary_part/2.
+ ">>,
+ [],
+ []},
+ %% Import directive clashing with old bif is an error, regardless of if it's called or not
+ {clash15,
+ <<"-export([x/1]).
+ -import(x,[abs/1]).
+ x(X) ->
+ binary_part(X,{1,2}).
+ ">>,
+ [],
+ {errors,[{2,erl_lint,{redefine_old_bif_import,{abs,1}}}],[]}},
+ %% For a new BIF, it's only a warning
+ {clash16,
+ <<"-export([x/1]).
+ -import(x,[binary_part/3]).
+ x(X) ->
+ abs(X).
+ ">>,
+ [],
+ {warnings,[{2,erl_lint,{redefine_bif_import,{binary_part,3}}}]}},
+ %% And, you cannot redefine already imported things that aren't auto-imported
+ {clash17,
+ <<"-export([x/1]).
+ -import(x,[binary_port/3]).
+ -import(y,[binary_port/3]).
+ x(X) ->
+ abs(X).
+ ">>,
+ [],
+ {errors,[{3,erl_lint,{redefine_import,{{binary_port,3},x}}}],[]}},
+ %% Not with local functions either
+ {clash18,
+ <<"-export([x/1]).
+ -import(x,[binary_port/3]).
+ binary_port(A,B,C) ->
+ binary_part(A,B,C).
+ x(X) ->
+ abs(X).
+ ">>,
+ [],
+ {errors,[{3,erl_lint,{define_import,{binary_port,3}}}],[]}},
+ %% Like clash8: Dont accept a guard if it's explicitly module-name called either
+ {clash19,
+ <<"-export([binary_port/3]).
+ -compile({no_auto_import,[binary_part/3]}).
+ -import(x,[binary_part/3]).
+ binary_port(A,B,C) when x:binary_part(A,B,C) ->
+ binary_part(A,B,C+1).
+ ">>,
+ [],
+ {errors,[{4,erl_lint,illegal_guard_expr}],[]}},
+ %% Not with local functions either
+ {clash20,
+ <<"-export([binary_port/3]).
+ -import(x,[binary_part/3]).
+ binary_port(A,B,C) ->
+ binary_part(A,B,C).
+ ">>,
+ [warn_unused_import],
+ {warnings,[{2,erl_lint,{redefine_bif_import,{binary_part,3}}}]}}
+ ],
?line [] = run(Config, Ts),
ok.
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index 92a54108aa..c57541fba9 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -46,7 +46,7 @@
neg_indent/1,
tickets/1,
otp_6321/1, otp_6911/1, otp_6914/1, otp_8150/1, otp_8238/1,
- otp_8473/1, otp_8522/1]).
+ otp_8473/1, otp_8522/1, otp_8567/1, otp_8664/1]).
%% Internal export.
-export([ehook/6]).
@@ -150,15 +150,15 @@ recs(Config) when is_list(Config) ->
(A#r1.a)#r.a > 3 -> 3
end(#r1{a = #r{a = 4}}),
7 = fun(A) when record(A#r3.a, r1) -> 7 end(#r3{}),
- [#r1{a = 2,b = 1}] =
+ [#r1{a = 2,b = 1}] =
fun() ->
- [A || A <- [#r1{a = 1, b = 3},
- #r2{a = 2,b = 1},
+ [A || A <- [#r1{a = 1, b = 3},
+ #r2{a = 2,b = 1},
#r1{a = 2, b = 1}],
- A#r1.a >
+ A#r1.a >
A#r1.b]
end(),
- {[_],b} =
+ {[_],b} =
fun(L) ->
%% A is checked only once:
R1 = [{A,B} || A <- L, A#r1.a, B <- L, A#r1.b],
@@ -177,7 +177,7 @@ recs(Config) when is_list(Config) ->
end(#r1{a = 2}),
%% The test done twice (an effect of doing the test as soon as possible).
- 3 = fun(A) when A#r1.a > 3,
+ 3 = fun(A) when A#r1.a > 3,
record(A, r1) -> 3
end(#r1{a = 5}),
@@ -251,18 +251,18 @@ recs(Config) when is_list(Config) ->
ok
end(),
- both = fun(A) when A#r.a, A#r.b -> both
+ both = fun(A) when A#r.a, A#r.b -> both
end(#r{a = true, b = true}),
ok = fun() ->
- F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
+ F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
or (B#r2.b) or (A#r1.b) ->
true;
(_, _) -> false
end,
- true = F(#r1{a = false, b = false},
+ true = F(#r1{a = false, b = false},
#r2{a = false, b = true}),
- false = F(#r1{a = true, b = true},
+ false = F(#r1{a = true, b = true},
#r1{a = false, b = true}),
ok
end(),
@@ -273,7 +273,7 @@ recs(Config) when is_list(Config) ->
<<"-record(r1, {a, b = foo:bar(kljlfjsdlf, kjlksdjf)}).
-record(r2, {c = #r1{}, d = #r1{a = bar:foo(kljklsjdf)}}).
- t() ->
+ t() ->
R = #r2{},
R#r2{c = R, d = #r1{}}.">>}
],
@@ -304,10 +304,10 @@ try_catch(Config) when is_list(Config) ->
{try_6,
<<"t() -> try 1=2 catch throw:{badmatch,2} -> 3 end.">>},
{try_7,
- <<"t() -> try 1=2 of 3 -> 4
+ <<"t() -> try 1=2 of 3 -> 4
catch error:{badmatch,2} -> 5 end.">>},
{try_8,
- <<"t() -> try 1=2
+ <<"t() -> try 1=2
catch error:{badmatch,2} -> 3
after put(try_catch, 4) end.">>},
{try_9,
@@ -371,7 +371,7 @@ receive_after(Config) when is_list(Config) ->
{X,Y};
Z ->
Z
- after
+ after
foo:bar() ->
{3,4}
end.">>}
@@ -429,7 +429,7 @@ head_tail(Config) when is_list(Config) ->
{list_4,
<<"t() -> [a].">>},
{list_5,
- <<"t() ->
+ <<"t() ->
[foo:bar(lkjljlskdfj, klsdajflds, sdafkljsdlfkjdas, kjlsdadjl),
bar:foo(kljlkjsdf, lkjsdlfj, [kljsfj, sdfdsfsad])].">>}
],
@@ -462,7 +462,7 @@ cond1(Config) when is_list(Config) ->
" true ->\n"
" {x,y}\n"
"end" = CChars,
-% ?line ok = pp_expr(<<"cond
+% ?line ok = pp_expr(<<"cond
% {foo,bar} ->
% [a,b];
% true ->
@@ -544,7 +544,7 @@ old_mnemosyne_syntax(Config) when is_list(Config) ->
" X <- table(tab),\n"
" X.foo = bar\n"
" ]\n"
- "end" =
+ "end" =
lists:flatten(erl_pp:expr(Q)),
R = {rule,12,sales,2,
@@ -559,7 +559,7 @@ old_mnemosyne_syntax(Config) when is_list(Config) ->
{atom,14,sales}}]}]},
?line "sales(E, employee) :-\n"
" E <- table(employee),\n"
- " E.salary = sales.\n" =
+ " E.salary = sales.\n" =
lists:flatten(erl_pp:form(R)),
ok.
@@ -660,7 +660,7 @@ hook(Config) when is_list(Config) ->
?line "INVALID-FORM:{foo,bar}:" = lists:flatten(erl_pp:expr({foo,bar})),
- %% A list (as before R6), not a list of lists.
+ %% A list (as before R6), not a list of lists.
G = [{op,1,'>',{atom,1,a},{foo,{atom,1,b}}}], % not a proper guard
GChars = lists:flatten(erl_pp:guard(G, H)),
G2 = [{op,1,'>',{atom,1,a},
@@ -677,23 +677,23 @@ hook(Config) when is_list(Config) ->
%% Note: no leading spaces before "begin".
Block = {block,0,[{match,0,{var,0,'A'},{integer,0,3}},
{atom,0,true}]},
- ?line "begin\n A =" ++ _ =
+ ?line "begin\n A =" ++ _ =
lists:flatten(erl_pp:expr(Block, 17, none)),
%% Special...
- ?line true =
+ ?line true =
"{some,value}" =:= lists:flatten(erl_pp:expr({value,0,{some,value}})),
%% Silly...
?line true =
- "if true -> 0 end" =:=
+ "if true -> 0 end" =:=
flat_expr({'if',0,[{clause,0,[],[],[{atom,0,0}]}]}),
%% More compatibility: before R6
OldIf = {'if',0,[{clause,0,[],[{atom,0,true}],[{atom,0,b}]}]},
NewIf = {'if',0,[{clause,0,[],[[{atom,0,true}]],[{atom,0,b}]}]},
OldIfChars = lists:flatten(erl_pp:expr(OldIf)),
- NewIfChars = lists:flatten(erl_pp:expr(NewIf)),
+ NewIfChars = lists:flatten(erl_pp:expr(NewIf)),
?line true = OldIfChars =:= NewIfChars,
ok.
@@ -706,7 +706,7 @@ remove_indentation(S) ->
ehook(HE, I, P, H, foo, bar) ->
hook(HE, I, P, H).
-hook({foo,E}, I, P, H) ->
+hook({foo,E}, I, P, H) ->
erl_pp:expr({call,0,{atom,0,foo},[E]}, I, P, H).
neg_indent(suite) ->
@@ -722,14 +722,14 @@ neg_indent(Config) when is_list(Config) ->
end">>),
?line ok = pp_expr(
<<"fun() ->
- F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
+ F = fun(A, B) when ((A#r1.a) orelse (B#r2.a))
or (B#r2.b) or (A#r1.b) ->
true;
(_, _) -> false
end,
- true = F(#r1{a = false, b = false},
+ true = F(#r1{a = false, b = false},
#r2{a = false, b = true}),
- false = F(#r1{a = true, b = true},
+ false = F(#r1{a = true, b = true},
#r1{a = false, b = true}),
ok
end()">>),
@@ -764,7 +764,8 @@ neg_indent(Config) when is_list(Config) ->
ok.
tickets(suite) ->
- [otp_6321, otp_6911, otp_6914, otp_8150, otp_8238, otp_8473, otp_8522].
+ [otp_6321, otp_6911, otp_6914, otp_8150, otp_8238, otp_8473, otp_8522,
+ otp_8567, otp_8664].
otp_6321(doc) ->
"OTP_6321. Bug fix of exprs().";
@@ -813,7 +814,7 @@ otp_8150(doc) ->
"OTP_8150. Types.";
otp_8150(suite) -> [];
otp_8150(Config) when is_list(Config) ->
- ?line _ = [{N,ok} = {N,pp_forms(B)} ||
+ ?line _ = [{N,ok} = {N,pp_forms(B)} ||
{N,B} <- type_examples()
],
ok.
@@ -918,7 +919,7 @@ otp_8473(suite) -> [];
otp_8473(Config) when is_list(Config) ->
Ex = [{ex1,<<"-type 'fun'(A) :: A.\n"
"-type funkar() :: 'fun'(fun((integer()) -> atom())).\n">>}],
- ?line _ = [{N,ok} = {N,pp_forms(B)} ||
+ ?line _ = [{N,ok} = {N,pp_forms(B)} ||
{N,B} <- Ex],
ok.
@@ -949,12 +950,89 @@ count_atom(L, A) when is_list(L) ->
count_atom(_, _) ->
0.
+otp_8567(doc) ->
+ "OTP_8567. Avoid duplicated 'undefined' in record field types.";
+otp_8567(suite) -> [];
+otp_8567(Config) when is_list(Config) ->
+ FileName = filename('otp_8567.erl', Config),
+ C = <<"-module otp_8567.\n"
+ "-compile export_all.\n"
+ "-spec(a).\n"
+ "-record r, {a}.\n"
+ "-record s, {a :: integer()}.\n"
+ "-type t() :: {#r{},#s{}}.\n">>,
+ ?line ok = file:write_file(FileName, C),
+ ?line {error,[{_,[{3,erl_parse,["syntax error before: ","')'"]}]}],_} =
+ compile:file(FileName, [return]),
+
+ F = <<"-module(otp_8567).\n"
+ "-compile(export_all).\n"
+ "-record(t, {a}).\n"
+ "-record(u, {a :: integer()}).\n"
+ "-opaque ot() :: {#t{}, #u{}}.\n"
+ "-opaque(ot1() :: atom()).\n"
+ "-type a() :: integer().\n"
+ "-spec t() -> a().\n"
+ "t() ->\n"
+ " 3.\n"
+ "\n"
+ "-spec(t1/1 :: (ot()) -> ot1()).\n"
+ "t1(A) ->\n"
+ " A.\n"
+ "\n"
+ "-spec(t2 (ot()) -> ot1()).\n"
+ "t2(A) ->\n"
+ " A.\n"
+ "\n"
+ "-spec(otp_8567:t3/1 :: (ot()) -> ot1()).\n"
+ "t3(A) ->\n"
+ " A.\n"
+ "\n"
+ "-spec(otp_8567:t4 (ot()) -> ot1()).\n"
+ "t4(A) ->\n"
+ " A.\n">>,
+ ?line ok = pp_forms(F),
+
+ ok.
+
+otp_8664(doc) ->
+ "OTP_8664. Types with integer expressions.";
+otp_8664(suite) -> [];
+otp_8664(Config) when is_list(Config) ->
+ FileName = filename('otp_8664.erl', Config),
+ C1 = <<"-module(otp_8664).\n"
+ "-export([t/0]).\n"
+ "-define(A, -3).\n"
+ "-define(B, (?A*(-1 band (((2)))))).\n"
+ "-type t1() :: ?B | ?A.\n"
+ "-type t2() :: ?B-1 .. -?B.\n"
+ "-type t3() :: 9 band (8 - 3) | 1+2 | 5 band 3.\n"
+ "-type b1() :: <<_:_*(3-(-1))>>\n"
+ " | <<_:(-(?B))>>\n"
+ " | <<_:4>>.\n"
+ "-type u() :: 1 .. 2 | 3.. 4 | (8-3) ..6 | 5+0..6.\n"
+ "-type t() :: t1() | t2() | t3() | b1() | u().\n"
+ "-spec t() -> t().\n"
+ "t() -> 3.\n">>,
+ ?line ok = file:write_file(FileName, C1),
+ ?line {ok, _, []} = compile:file(FileName, [return]),
+
+ C2 = <<"-module(otp_8664).\n"
+ "-export([t/0]).\n"
+ "-spec t() -> 9 and 4.\n"
+ "t() -> 0.\n">>,
+ ?line ok = file:write_file(FileName, C2),
+ ?line {error,[{_,[{3,erl_lint,{type_syntax,integer}}]}],_} =
+ compile:file(FileName, [return]),
+
+ ok.
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
compile(Config, Tests) ->
F = fun({N,P}, BadL) ->
case catch compile_file(Config, P) of
- ok ->
+ ok ->
case pp_forms(P) of
ok ->
BadL;
@@ -962,8 +1040,8 @@ compile(Config, Tests) ->
?t:format("~nTest ~p failed.~n", [N]),
fail()
end;
- Bad ->
- ?t:format("~nTest ~p failed. got~n ~p~n",
+ Bad ->
+ ?t:format("~nTest ~p failed. got~n ~p~n",
[N, Bad]),
fail()
end
@@ -993,7 +1071,7 @@ compile_file(Config, Test0) ->
Error ->
Error
end.
-
+
compile_file(Config, Test0, Opts0) ->
FileName = filename('erl_pp_test.erl', Config),
Test = list_to_binary(["-module(erl_pp_test). "
@@ -1002,7 +1080,7 @@ compile_file(Config, Test0, Opts0) ->
Opts = [export_all,return,nowarn_unused_record,{outdir,?privdir} | Opts0],
ok = file:write_file(FileName, Test),
case compile:file(FileName, Opts) of
- {ok, _M, _Ws} ->
+ {ok, _M, _Ws} ->
{ok, filename:rootname(FileName)};
Error -> Error
end.
@@ -1029,7 +1107,7 @@ pp_forms(Bin, Hook) ->
end.
parse_and_pp_forms(String, Hook) ->
- lists:append(lists:map(fun(AF) -> erl_pp:form(AF, Hook)
+ lists:append(lists:map(fun(AF) -> erl_pp:form(AF, Hook)
end, parse_forms(String))).
parse_forms(Chars) ->
@@ -1037,13 +1115,13 @@ parse_forms(Chars) ->
parse_forms2(String, [], 1, []).
parse_forms2([], _Cont, _Line, Forms) ->
- lists:reverse(Forms);
+ lists:reverse(Forms);
parse_forms2(String, Cont0, Line, Forms) ->
case erl_scan:tokens(Cont0, String, Line) of
{done, {ok, Tokens, EndLine}, Chars} ->
{ok, Form} = erl_parse:parse_form(Tokens),
parse_forms2(Chars, [], EndLine, [Form | Forms]);
- {more, Cont} when element(3, Cont) =:= [] ->
+ {more, Cont} when element(3, Cont) =:= [] ->
%% extra spaces after forms...
parse_forms2([], Cont, Line, Forms);
{more, Cont} ->
@@ -1061,10 +1139,10 @@ pp_expr(Bin, Hook) ->
PP2 = (catch parse_and_pp_expr(PPneg, 0, Hook)),
if
PP1 =:= PP2 -> % same line numbers
- case
+ case
(test_max_line(PP1) =:= ok) and (test_new_line(PPneg) =:= ok)
of
- true ->
+ true ->
ok;
false ->
not_ok
@@ -1097,7 +1175,7 @@ test_max_line(String) ->
end.
max_line(String) ->
- lists:max([0 | [length(Sub) ||
+ lists:max([0 | [length(Sub) ||
Sub <- string:tokens(String, "\n"),
string:substr(Sub, 1, 5) =/= "-file"]]).
diff --git a/lib/stdlib/test/erl_scan_SUITE.erl b/lib/stdlib/test/erl_scan_SUITE.erl
index 32a06d15c7..32eb97bc92 100644
--- a/lib/stdlib/test/erl_scan_SUITE.erl
+++ b/lib/stdlib/test/erl_scan_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-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(erl_scan_SUITE).
@@ -34,7 +34,7 @@
-define(line, put(line, ?LINE), ).
-define(config(A,B),config(A,B)).
-define(t, test_server).
-%% config(priv_dir, _) ->
+%% config(priv_dir, _) ->
%% ".";
%% config(data_dir, _) ->
%% ".".
@@ -45,7 +45,7 @@
init_per_testcase(_Case, Config) when is_list(Config) ->
?line Dog=test_server:timetrap(test_server:seconds(1200)),
[{watchdog, Dog}|Config].
-
+
fin_per_testcase(_Case, Config) ->
Dog=?config(watchdog, Config),
test_server:timetrap_cancel(Dog),
@@ -97,7 +97,7 @@ assert_type(N, integer) when is_integer(N) ->
ok;
assert_type(N, atom) when is_atom(N) ->
ok.
-
+
check(String) ->
Error = erl_scan:string(String),
check_error(Error, erl_scan).
@@ -146,7 +146,7 @@ iso88591(Config) when is_list(Config) ->
ok -> ok %Aok
end.
-otp_7810(doc) ->
+otp_7810(doc) ->
["OTP-7810. White spaces, comments, and more.."];
otp_7810(suite) ->
[];
@@ -185,7 +185,7 @@ reserved_words() ->
'andalso', 'orelse', 'end', 'fun', 'if', 'let', 'of',
'query', 'receive', 'when', 'bnot', 'not', 'div',
'rem', 'band', 'and', 'bor', 'bxor', 'bsl', 'bsr',
- 'or', 'xor', 'spec'] , % 'spec' shouldn't be there...
+ 'or', 'xor'],
[begin
?line {RW, true} = {RW, erl_scan:reserved_word(RW)},
S = atom_to_list(RW),
@@ -203,7 +203,7 @@ atoms() ->
?line test_string("a@2", [{atom,1,a@2}]),
?line test_string([39,65,200,39], [{atom,1,'A�'}]),
?line test_string("�rlig �sten", [{atom,1,�rlig},{atom,1,�sten}]),
- ?line {ok,[{atom,_,'$a'}],{1,6}} =
+ ?line {ok,[{atom,_,'$a'}],{1,6}} =
erl_scan:string("'$\\a'", {1,1}),
?line test("'$\\a'"),
ok.
@@ -221,8 +221,8 @@ punctuations() ->
Three = ["/=:=", "<=:=", "==:=", ">=:="], % three tokens...
No = Three ++ L,
SL0 = [{S1++S2,{-length(S1),S1,S2}} ||
- S1 <- L,
- S2 <- L,
+ S1 <- L,
+ S2 <- L,
not lists:member(S1++S2, No)],
SL = family_list(SL0),
%% Two tokens. When there are several answers, the one with
@@ -244,21 +244,24 @@ punctuations() ->
{'\\',1},{'^',1},{'`',1},{'~',1}],
?line test_string("#&*+/:<>?@\\^`~", PTs2),
+ ?line test_string(".. ", [{'..',1}]),
+ ?line test("1 .. 2"),
+ ?line test_string("...", [{'...',1}]),
ok.
comments() ->
?line test("a %%\n b"),
?line {ok,[],1} = erl_scan:string("%"),
?line test("a %%\n b"),
- ?line {ok,[{atom,_,a},{atom,_,b}],{2,3}} =
+ ?line {ok,[{atom,_,a},{atom,_,b}],{2,3}} =
erl_scan:string("a %%\n b",{1,1}),
- ?line {ok,[{atom,_,a},{comment,_,"%%"},{atom,_,b}],{2,3}} =
+ ?line {ok,[{atom,_,a},{comment,_,"%%"},{atom,_,b}],{2,3}} =
erl_scan:string("a %%\n b",{1,1}, [return_comments]),
?line {ok,[{atom,_,a},
{white_space,_," "},
{white_space,_,"\n "},
{atom,_,b}],
- {2,3}} =
+ {2,3}} =
erl_scan:string("a %%\n b",{1,1},[return_white_spaces]),
?line {ok,[{atom,_,a},
{white_space,_," "},
@@ -275,14 +278,14 @@ errors() ->
?line {error,{1,erl_scan,char},1} = erl_scan:string("$"),
?line test_string([34,65,200,34], [{string,1,"A�"}]),
?line test_string("\\", [{'\\',1}]),
- ?line {'EXIT',_} =
+ ?line {'EXIT',_} =
(catch {foo, erl_scan:string('$\\a', {1,1})}), % type error
- ?line {'EXIT',_} =
+ ?line {'EXIT',_} =
(catch {foo, erl_scan:tokens([], '$\\a', {1,1})}), % type error
?line "{a,tuple}" = erl_scan:format_error({a,tuple}),
ok.
-
+
integers() ->
[begin
I = list_to_integer(S),
@@ -299,14 +302,14 @@ base_integers() ->
?line test_string(BS++"#"++S, Ts)
end || {BS,S} <- [{"2","11"}, {"5","23234"}, {"12","05a"},
{"16","abcdef"}, {"16","ABCDEF"}] ],
-
+
?line {error,{1,erl_scan,{base,1}},1} = erl_scan:string("1#000"),
-
+
?line test_string("12#bc", [{integer,1,11},{atom,1,c}]),
-
+
[begin
Str = BS ++ "#" ++ S,
- ?line {error,{1,erl_scan,{illegal,integer}},1} =
+ ?line {error,{1,erl_scan,{illegal,integer}},1} =
erl_scan:string(Str)
end || {BS,S} <- [{"3","3"},{"15","f"}, {"12","c"}] ],
@@ -323,8 +326,8 @@ floats() ->
end || FS <- ["1.0","001.17","3.31200","1.0e0","1.0E17",
"34.21E-18", "17.0E+14"]],
?line test_string("1.e2", [{integer,1,1},{'.',1},{atom,1,e2}]),
-
- ?line {error,{1,erl_scan,{illegal,float}},1} =
+
+ ?line {error,{1,erl_scan,{illegal,float}},1} =
erl_scan:string("1.0e400"),
[begin
?line {error,{1,erl_scan,{illegal,float}},1} = erl_scan:string(S)
@@ -345,16 +348,16 @@ dots() ->
{".a", {ok,[{'.',1},{atom,1,a}],1}}
],
?line [R = erl_scan:string(S) || {S, R} <- Dot],
-
+
?line {ok,[{dot,_}=T1],{1,2}} = erl_scan:string(".", {1,1}, text),
- ?line [{column,1},{length,1},{line,1},{text,"."}] =
+ ?line [{column,1},{length,1},{line,1},{text,"."}] =
erl_scan:token_info(T1, [column, length, line, text]),
?line {ok,[{dot,_}=T2],{1,3}} = erl_scan:string(".%", {1,1}, text),
- ?line [{column,1},{length,1},{line,1},{text,"."}] =
+ ?line [{column,1},{length,1},{line,1},{text,"."}] =
erl_scan:token_info(T2, [column, length, line, text]),
?line {ok,[{dot,_}=T3],{1,6}} =
erl_scan:string(".% �h", {1,1}, text),
- ?line [{column,1},{length,1},{line,1},{text,"."}] =
+ ?line [{column,1},{length,1},{line,1},{text,"."}] =
erl_scan:token_info(T3, [column, length, line, text]),
?line {error,{{1,2},erl_scan,char},{1,3}} =
erl_scan:string(".$", {1,1}),
@@ -376,10 +379,10 @@ dots() ->
?line {done,{ok,[{comment,_,"%. "},
{white_space,_,"\n"},
{dot,_}],
- {2,3}}, ""} =
+ {2,3}}, ""} =
erl_scan:tokens(C, "\n. ", {1,1}, return), % any loc, any options
- ?line [test_string(S, R) ||
+ ?line [test_string(S, R) ||
{S, R} <- [{".$\n", [{'.',1},{char,1,$\n}]},
{"$\\\n", [{char,1,$\n}]},
{"'\\\n'", [{atom,1,'\n'}]},
@@ -392,7 +395,7 @@ chars() ->
Ts = [{char,1,C}],
?line test_string(L, Ts)
end || C <- lists:seq(0, 255)],
-
+
%% Leading zeroes...
[begin
L = lists:flatten(io_lib:format("$\\~3.8.0b", [C])),
@@ -406,13 +409,13 @@ chars() ->
Ts = [{char,1,C band 2#11111}],
?line test_string(L, Ts)
end || C <- lists:seq(0, 255)],
-
+
[begin
L = "$\\" ++ [C],
Ts = [{char,1,V}],
?line test_string(L, Ts)
- end || {C,V} <- [{$n,$\n}, {$r,$\r}, {$t,$\t}, {$v,$\v},
- {$b,$\b}, {$f,$\f}, {$e,$\e}, {$s,$\s},
+ end || {C,V} <- [{$n,$\n}, {$r,$\r}, {$t,$\t}, {$v,$\v},
+ {$b,$\b}, {$f,$\f}, {$e,$\e}, {$s,$\s},
{$d,$\d}]],
EC = [$\n,$\r,$\t,$\v,$\b,$\f,$\e,$\s,$\d],
@@ -445,7 +448,7 @@ chars() ->
end || C <- lists:seq(0, 255) -- (No ++ [$\\])],
?line test_string("$\n", [{char,1,$\n}]),
- ?line {error,{{1,1},erl_scan,char},{1,4}} =
+ ?line {error,{{1,1},erl_scan,char},{1,4}} =
erl_scan:string("$\\^",{1,1}),
?line test_string("$\\\n", [{char,1,$\n}]),
%% Robert's scanner returns line 1:
@@ -453,7 +456,7 @@ chars() ->
?line test_string("$\n\n", [{char,1,$\n}]),
?line test("$\n\n"),
ok.
-
+
variables() ->
?line test_string(" \237_Aou�eiy��", [{var,1,'_Aou�eiy��'}]),
@@ -469,8 +472,8 @@ eof() ->
?line {done,{eof,2},eof} = erl_scan:tokens(C1, eof, 1),
{more, C2} = erl_scan:tokens([], "abra", 1),
%% An error before R13A.
- %% ?line {done,Err={error,{1,erl_scan,scan},1},eof} =
- ?line {done,{ok,[{atom,1,abra}],1},eof} =
+ %% ?line {done,Err={error,{1,erl_scan,scan},1},eof} =
+ ?line {done,{ok,[{atom,1,abra}],1},eof} =
erl_scan:tokens(C2, eof, 1),
%% With column.
@@ -478,7 +481,7 @@ eof() ->
?line {done,{eof,{2,1}},eof} = erl_scan:tokens(C3, eof, 1),
{more, C4} = erl_scan:tokens([], "abra", {1,1}),
%% An error before R13A.
- %% ?line {done,{error,{{1,1},erl_scan,scan},{1,5}},eof} =
+ %% ?line {done,{error,{{1,1},erl_scan,scan},{1,5}},eof} =
?line {done,{ok,[{atom,_,abra}],{1,5}},eof} =
erl_scan:tokens(C4, eof, 1),
@@ -486,7 +489,7 @@ eof() ->
%% the R12B scanner returns eof as LeftoverChars: (eof is correct)
?line {more, C5} = erl_scan:tokens([], "a", 1),
%% An error before R13A.
- %% ?line {done,{error,{1,erl_scan,scan},1},eof} =
+ %% ?line {done,{error,{1,erl_scan,scan},1},eof} =
?line {done,{ok,[{atom,1,a}],1},eof} =
erl_scan:tokens(C5,eof,1),
@@ -528,7 +531,7 @@ illegal() ->
erl_scan:tokens([], "foo "++Atom++". ", {1,1}),
?line {error,{{1,1},erl_scan,{illegal,atom}},{1,1003}} =
erl_scan:string(QAtom, {1,1}),
- ?line {done,{error,{{1,5},erl_scan,{illegal,atom}},{1,1007}},". "} =
+ ?line {done,{error,{{1,5},erl_scan,{illegal,atom}},{1,1007}},". "} =
erl_scan:tokens([], "foo "++QAtom++". ", {1,1}),
?line {error,{{1,1},erl_scan,{illegal,var}},{1,1001}} =
erl_scan:string(Var, {1,1}),
@@ -553,7 +556,7 @@ crashes() ->
?line {'EXIT',_} = (catch {foo, erl_scan:string("\"\\v"++[-1,$"])}), %$"
?line {'EXIT',_} = (catch {foo, erl_scan:string([$",-1,$"])}),
?line {'EXIT',_} = (catch {foo, erl_scan:string("% foo"++[-1])}),
- ?line {'EXIT',_} =
+ ?line {'EXIT',_} =
(catch {foo, erl_scan:string("% foo"++[-1],{1,1})}),
?line {'EXIT',_} = (catch {foo, erl_scan:string([a])}), % type error
@@ -564,7 +567,7 @@ crashes() ->
?line {'EXIT',_} = (catch {foo, erl_scan:string("\"\\v"++[a,$"])}), %$"
?line {'EXIT',_} = (catch {foo, erl_scan:string([$",a,$"])}),
?line {'EXIT',_} = (catch {foo, erl_scan:string("% foo"++[a])}),
- ?line {'EXIT',_} =
+ ?line {'EXIT',_} =
(catch {foo, erl_scan:string("% foo"++[a],{1,1})}),
?line {'EXIT',_} = (catch {foo, erl_scan:string([3.0])}), % type error
@@ -573,26 +576,26 @@ crashes() ->
options() ->
%% line and column are not options, but tested here
- ?line {ok,[{atom,1,foo},{white_space,1," "},{comment,1,"% bar"}], 1} =
+ ?line {ok,[{atom,1,foo},{white_space,1," "},{comment,1,"% bar"}], 1} =
erl_scan:string("foo % bar", 1, return),
- ?line {ok,[{atom,1,foo},{white_space,1," "}],1} =
+ ?line {ok,[{atom,1,foo},{white_space,1," "}],1} =
erl_scan:string("foo % bar", 1, return_white_spaces),
- ?line {ok,[{atom,1,foo},{comment,1,"% bar"}],1} =
+ ?line {ok,[{atom,1,foo},{comment,1,"% bar"}],1} =
erl_scan:string("foo % bar", 1, return_comments),
- ?line {ok,[{atom,17,foo}],17} =
+ ?line {ok,[{atom,17,foo}],17} =
erl_scan:string("foo % bar", 17),
- ?line {'EXIT',{function_clause,_}} =
- (catch {foo,
+ ?line {'EXIT',{function_clause,_}} =
+ (catch {foo,
erl_scan:string("foo % bar", {a,1}, [])}), % type error
- ?line {ok,[{atom,_,foo}],{17,18}} =
+ ?line {ok,[{atom,_,foo}],{17,18}} =
erl_scan:string("foo % bar", {17,9}, []),
- ?line {'EXIT',{function_clause,_}} =
+ ?line {'EXIT',{function_clause,_}} =
(catch {foo,
erl_scan:string("foo % bar", {1,0}, [])}), % type error
- ?line {ok,[{foo,1}],1} =
+ ?line {ok,[{foo,1}],1} =
erl_scan:string("foo % bar",1, [{reserved_word_fun,
fun(W) -> W =:= foo end}]),
- ?line {'EXIT',{badarg,_}} =
+ ?line {'EXIT',{badarg,_}} =
(catch {foo,
erl_scan:string("foo % bar",1, % type error
[{reserved_word_fun,
@@ -618,14 +621,14 @@ more_options() ->
token_info() ->
?line {ok,[T1],_} = erl_scan:string("foo", {1,18}, [text]),
- {'EXIT',{badarg,_}} =
+ {'EXIT',{badarg,_}} =
(catch {foo, erl_scan:token_info(T1, foo)}), % type error
?line {line,1} = erl_scan:token_info(T1, line),
?line {column,18} = erl_scan:token_info(T1, column),
?line {length,3} = erl_scan:token_info(T1, length),
?line {text,"foo"} = erl_scan:token_info(T1, text),
?line [{category,atom},{column,18},{length,3},{line,1},
- {symbol,foo},{text,"foo"}] =
+ {symbol,foo},{text,"foo"}] =
erl_scan:token_info(T1),
?line [{length,3},{column,18}] =
erl_scan:token_info(T1, [length, column]),
@@ -648,9 +651,9 @@ token_info() ->
?line {category,'='} = erl_scan:token_info(T3, category),
?line [{symbol,'='}] = erl_scan:token_info(T3, [symbol]),
ok.
-
+
attributes_info() ->
- ?line {'EXIT',_} =
+ ?line {'EXIT',_} =
(catch {foo,erl_scan:attributes_info(foo)}), % type error
?line [{line,18}] = erl_scan:attributes_info(18),
?line {location,19} = erl_scan:attributes_info(19, location),
@@ -717,9 +720,9 @@ set_attribute() ->
?line [{line,{17,11}},{text,"foo"}] =
erl_scan:attributes_info(A7, [line,column,text]),
- ?line {'EXIT',_} =
+ ?line {'EXIT',_} =
(catch {foo, erl_scan:set_attribute(line, [], F2)}), % type error
- ?line {'EXIT',{badarg,_}} =
+ ?line {'EXIT',{badarg,_}} =
(catch {foo, erl_scan:set_attribute(column, [], F2)}), % type error
ok.
@@ -790,14 +793,14 @@ unicode() ->
?line {ok,[{char,1,83},{integer,1,45}],1} =
erl_scan:string("$\\12345"), % not unicode
- ?line {error,{1,erl_scan,{illegal,character}},1} =
+ ?line {error,{1,erl_scan,{illegal,character}},1} =
erl_scan:string([1089]),
- ?line {error,{{1,1},erl_scan,{illegal,character}},{1,2}} =
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,2}} =
erl_scan:string([1089], {1,1}),
?line {error,{1,erl_scan,{illegal,character}},1} =
- %% ?line {error,{1,erl_scan,{illegal,atom}},1} =
+ %% ?line {error,{1,erl_scan,{illegal,atom}},1} =
erl_scan:string("'a"++[1089]++"b'"),
- ?line {error,{{1,3},erl_scan,{illegal,character}},{1,4}} =
+ ?line {error,{{1,3},erl_scan,{illegal,character}},{1,4}} =
erl_scan:string("'a"++[1089]++"b'", {1,1}),
?line test("\"a"++[1089]++"b\""),
?line {ok,[{char,1,1}],1} = erl_scan:string([$$,$\\,$^,1089]),
@@ -822,7 +825,7 @@ unicode() ->
?line {ok,[{integer,1,16#aaa}],1} = erl_scan:string(Qs),
?line {ok,[Q2],{1,9}} = erl_scan:string("$\\x{aaa}", {1,1}, text),
?line [{category,integer},{column,1},{length,8},
- {line,1},{symbol,16#aaa},{text,Qs}] =
+ {line,1},{symbol,16#aaa},{text,Qs}] =
erl_scan:token_info(Q2),
U1 = "\"\\x{aaa}\"",
@@ -830,11 +833,11 @@ unicode() ->
?line [{category,'['},{column,1},{length,1},{line,1},
{symbol,'['},{text,"\""}] = erl_scan:token_info(T1, Tags),
?line [{category,integer},{column,2},{length,7},
- {line,1},{symbol,16#aaa},{text,"\\x{aaa}"}] =
+ {line,1},{symbol,16#aaa},{text,"\\x{aaa}"}] =
erl_scan:token_info(T2, Tags),
?line [{category,']'},{column,9},{length,1},{line,1},
{symbol,']'},{text,"\""}] = erl_scan:token_info(T3, Tags),
- ?line {ok,[{'[',1},{integer,1,16#aaa},{']',1}],1} =
+ ?line {ok,[{'[',1},{integer,1,16#aaa},{']',1}],1} =
erl_scan:string(U1, 1),
U2 = "\"\\x41\\x{fff}\\x42\"",
@@ -844,7 +847,7 @@ unicode() ->
U3 = "\"a\n\\x{fff}\n\"",
?line {ok,[{'[',1},{char,1,$a},{',',1},{char,1,$\n},
{',',2},{integer,2,16#fff},{',',2},{char,2,$\n},
- {']',3}],3} =
+ {']',3}],3} =
erl_scan:string(U3, 1),
U4 = "\"\\^\n\\x{aaa}\\^\n\"",
@@ -867,10 +870,10 @@ unicode() ->
{char,_,$d},{']',_}],{1,8}} = erl_scan:string(Str1, {1,1}),
?line test(Str1),
Comment = "%% "++[1089],
- ?line {ok,[{comment,1,[$%,$%,$\s,1089]}],1} =
+ ?line {ok,[{comment,1,[$%,$%,$\s,1089]}],1} =
erl_scan:string(Comment, 1, return),
- ?line {ok,[{comment,_,[$%,$%,$\s,1089]}],{1,5}} =
- erl_scan:string(Comment, {1,1}, return),
+ ?line {ok,[{comment,_,[$%,$%,$\s,1089]}],{1,5}} =
+ erl_scan:string(Comment, {1,1}, return),
ok.
more_chars() ->
@@ -885,7 +888,7 @@ more_chars() ->
erl_scan:tokens(C1, eof, 1),
?line {ok,[{char,1,123},{atom,1,a},{'}',1}],1} =
erl_scan:string("$\\{a}"),
-
+
?line {error,{{1,1},erl_scan,char},{1,4}} =
erl_scan:string("$\\x", {1,1}),
?line {error,{{1,1},erl_scan,char},{1,5}} =
@@ -893,12 +896,12 @@ more_chars() ->
?line {more, C3} = erl_scan:tokens([], "$\\x", {1,1}),
?line {done,{error,{{1,1},erl_scan,char},{1,4}},eof} =
erl_scan:tokens(C3, eof, 1),
- ?line {error,{{1,1},erl_scan,char},{1,5}} =
+ ?line {error,{{1,1},erl_scan,char},{1,5}} =
erl_scan:string("$\\x{",{1,1}),
?line {more, C2} = erl_scan:tokens([], "$\\x{", {1,1}),
- ?line {done,{error,{{1,1},erl_scan,char},{1,5}},eof} =
+ ?line {done,{error,{{1,1},erl_scan,char},{1,5}},eof} =
erl_scan:tokens(C2, eof, 1),
- ?line {error,{1,erl_scan,{illegal,character}},1} =
+ ?line {error,{1,erl_scan,{illegal,character}},1} =
erl_scan:string("$\\x{g}"),
?line {error,{{1,1},erl_scan,{illegal,character}},{1,5}} =
erl_scan:string("$\\x{g}", {1,1}),
@@ -924,12 +927,12 @@ more_chars() ->
?line test("$\\x{10FFFF}"),
?line test("$\\x{10ffff}"),
?line test("\"$\n \\{1}\""),
- ?line {error,{1,erl_scan,{illegal,character}},1} =
+ ?line {error,{1,erl_scan,{illegal,character}},1} =
erl_scan:string("$\\x{110000}"),
- ?line {error,{{1,1},erl_scan,{illegal,character}},{1,12}} =
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,12}} =
erl_scan:string("$\\x{110000}", {1,1}),
- ?line {error,{{1,1},erl_scan,{illegal,character}},{1,4}} =
+ ?line {error,{{1,1},erl_scan,{illegal,character}},{1,4}} =
erl_scan:string("$\\xfg", {1,1}),
?line test("$\\xffg"),
@@ -953,11 +956,11 @@ test(String) ->
{Wtokens, Wend},
{Ctokens, Cend},
{CWtokens, CWend},
- {CWtokens2, _}] =
+ {CWtokens2, _}] =
[scan_string_with_column(String, X) ||
- X <- [[],
- [return_white_spaces],
- [return_comments],
+ X <- [[],
+ [return_white_spaces],
+ [return_comments],
[return],
[return]]], % for white space compaction test
@@ -969,7 +972,7 @@ test(String) ->
{none,Tokens} = {none, filter_tokens(CWtokens, [white_space,comment])},
{comments,Ctokens} =
{comments,filter_tokens(CWtokens, [white_space])},
- {white_spaces,Wtokens} =
+ {white_spaces,Wtokens} =
{white_spaces,filter_tokens(CWtokens, [comment])},
%% Use token attributes to extract parts from the original string,
@@ -991,9 +994,9 @@ test(String) ->
%% Line attribute only:
[Simple,Wsimple,Csimple,WCsimple] = Simples =
[element(2, erl_scan:string(String, 1, Opts)) ||
- Opts <- [[],
- [return_white_spaces],
- [return_comments],
+ Opts <- [[],
+ [return_white_spaces],
+ [return_comments],
[return]]],
{consistent,true} = {consistent,consistent_attributes(Simples)},
{simple_wc,WCsimple} = {simple_wc,simplify(CWtokens)},
@@ -1004,19 +1007,19 @@ test(String) ->
%% Line attribute only, with text:
[SimpleTxt,WsimpleTxt,CsimpleTxt,WCsimpleTxt] = SimplesTxt =
[element(2, erl_scan:string(String, 1, [text|Opts])) ||
- Opts <- [[],
- [return_white_spaces],
- [return_comments],
+ Opts <- [[],
+ [return_white_spaces],
+ [return_comments],
[return]]],
TextTxt = get_text(WCsimpleTxt),
{text_txt,TextTxt,String} = {text_txt,String,TextTxt},
- {consistent_txt,true} =
+ {consistent_txt,true} =
{consistent_txt,consistent_attributes(SimplesTxt)},
- {simple_txt,SimpleTxt} =
+ {simple_txt,SimpleTxt} =
{simple_txt,filter_tokens(WCsimpleTxt, [white_space,comment])},
- {simple_c_txt,CsimpleTxt} =
+ {simple_c_txt,CsimpleTxt} =
{simple_c_txt,filter_tokens(WCsimpleTxt, [white_space])},
- {simple_w_txt,WsimpleTxt} =
+ {simple_w_txt,WsimpleTxt} =
{simple_w_txt,filter_tokens(WCsimpleTxt, [comment])},
ok.
@@ -1024,18 +1027,18 @@ test(String) ->
test_white_space_compaction(Tokens, Tokens2) when Tokens =:= Tokens2 ->
[WS, WS2] = [select_tokens(Ts, [white_space]) || Ts <- [Tokens, Tokens2]],
test_wsc(WS, WS2).
-
+
test_wsc([], []) ->
ok;
test_wsc([Token|Tokens], [Token2|Tokens2]) ->
- [Text, Text2] = [Text ||
- {text, Text} <-
+ [Text, Text2] = [Text ||
+ {text, Text} <-
[erl_scan:token_info(T, text) ||
T <- [Token, Token2]]],
Sz = erts_debug:size(Text),
Sz2 = erts_debug:size({Text, Text2}),
IsCompacted = Sz2 < 2*Sz+erts_debug:size({a,a}),
- ToBeCompacted = is_compacted(Text),
+ ToBeCompacted = is_compacted(Text),
if
IsCompacted =:= ToBeCompacted ->
test_wsc(Tokens, Tokens2);
@@ -1050,14 +1053,14 @@ is_compacted("\n\r") ->
is_compacted("\n\f") ->
true;
is_compacted([$\n|String]) ->
- all_spaces(String)
+ all_spaces(String)
orelse
all_tabs(String);
is_compacted(String) ->
all_spaces(String)
orelse
all_tabs(String).
-
+
all_spaces(L) ->
all_same(L, $\s).
@@ -1078,7 +1081,7 @@ newlines_first([Token|Tokens]) ->
_ ->
Nnls =:= 0
end,
- if
+ if
OK -> newlines_first(Tokens);
true -> OK
end.
@@ -1097,7 +1100,7 @@ simplify([]) ->
get_text(Tokens) ->
lists:flatten(
- [T ||
+ [T ||
Token <- Tokens,
({text,T} = erl_scan:token_info(Token, text)) =/= []]).
@@ -1108,7 +1111,7 @@ test_decorated_tokens(String, Tokens) ->
token_attrs(Tokens) ->
[{L,C,Len,T} ||
Token <- Tokens,
- ([{line,L},{column,C},{length,Len},{text,T}] =
+ ([{line,L},{column,C},{length,Len},{text,T}] =
erl_scan:token_info(Token, [line,column,length,text])) =/= []].
test_strings([], _S, Line, Column) ->
@@ -1150,7 +1153,7 @@ scan_string_with_column(String, Options0) ->
{ok, Ts1, End1} = erl_scan:string(String, StartLoc, Options),
TString = String ++ ". ",
{ok,Ts2,End2} = scan_tokens(TString, Options, [], StartLoc),
- {ok, Ts3, End3} =
+ {ok, Ts3, End3} =
scan_tokens_1({more, []}, TString, Options, [], StartLoc),
{end_2,End2,End3} = {end_2,End3,End2},
{EndLine1,EndColumn1} = End1,
@@ -1190,8 +1193,8 @@ consistent_attributes([Ts | TsL]) ->
L = [T || T <- Ts, is_integer(element(2, T))],
case L of
[] ->
- TagsL = [[Tag || {Tag,_} <-
- erl_scan:attributes_info(element(2, T))] ||
+ TagsL = [[Tag || {Tag,_} <-
+ erl_scan:attributes_info(element(2, T))] ||
T <- Ts],
case lists:usort(TagsL) of
[_] ->
diff --git a/lib/stdlib/test/gen_event_SUITE.erl b/lib/stdlib/test/gen_event_SUITE.erl
index 8cbffaca56..4f7de451e3 100644
--- a/lib/stdlib/test/gen_event_SUITE.erl
+++ b/lib/stdlib/test/gen_event_SUITE.erl
@@ -23,9 +23,11 @@
-export([all/1]).
-export([start/1, test_all/1, add_handler/1, add_sup_handler/1,
delete_handler/1, swap_handler/1, swap_sup_handler/1,
- notify/1, sync_notify/1, call/1, info/1, hibernate/1]).
+ notify/1, sync_notify/1, call/1, info/1, hibernate/1,
+ call_format_status/1, error_format_status/1]).
-all(suite) -> {req, [stdlib], [start, test_all, hibernate]}.
+all(suite) -> {req, [stdlib], [start, test_all, hibernate,
+ call_format_status, error_format_status]}.
%% --------------------------------------
%% Start an event manager.
@@ -844,3 +846,56 @@ info(Config) when is_list(Config) ->
?line ok = gen_event:stop(my_dummy_handler),
ok.
+
+call_format_status(suite) ->
+ [];
+call_format_status(doc) ->
+ ["Test that sys:get_status/1,2 calls format_status/2"];
+call_format_status(Config) when is_list(Config) ->
+ ?line {ok, Pid} = gen_event:start({local, my_dummy_handler}),
+ %% State here intentionally differs from what we expect from format_status
+ State = self(),
+ FmtState = "dummy1_h handler state",
+ ?line ok = gen_event:add_handler(my_dummy_handler, dummy1_h, [State]),
+ ?line Status1 = sys:get_status(Pid),
+ ?line Status2 = sys:get_status(Pid, 5000),
+ ?line ok = gen_event:stop(Pid),
+ ?line {status, Pid, _, [_, _, Pid, [], Data1]} = Status1,
+ ?line HandlerInfo1 = proplists:get_value(items, Data1),
+ ?line {"Installed handlers", [{_,dummy1_h,_,FmtState,_}]} = HandlerInfo1,
+ ?line {status, Pid, _, [_, _, Pid, [], Data2]} = Status2,
+ ?line HandlerInfo2 = proplists:get_value(items, Data2),
+ ?line {"Installed handlers", [{_,dummy1_h,_,FmtState,_}]} = HandlerInfo2,
+ ok.
+
+error_format_status(suite) ->
+ [];
+error_format_status(doc) ->
+ ["Test that a handler error calls format_status/2"];
+error_format_status(Config) when is_list(Config) ->
+ ?line error_logger_forwarder:register(),
+ OldFl = process_flag(trap_exit, true),
+ State = self(),
+ ?line {ok, Pid} = gen_event:start({local, my_dummy_handler}),
+ ?line ok = gen_event:add_sup_handler(my_dummy_handler, dummy1_h, [State]),
+ ?line ok = gen_event:notify(my_dummy_handler, do_crash),
+ ?line receive
+ {gen_event_EXIT,dummy1_h,{'EXIT',_}} -> ok
+ after 5000 ->
+ ?t:fail(exit_gen_event)
+ end,
+ FmtState = "dummy1_h handler state",
+ receive
+ {error,_GroupLeader, {Pid,
+ "** gen_event handler"++_,
+ [dummy1_h,my_dummy_handler,do_crash,
+ FmtState, _]}} ->
+ ok;
+ Other ->
+ ?line io:format("Unexpected: ~p", [Other]),
+ ?line ?t:fail()
+ end,
+ ?t:messages_get(),
+ ?line ok = gen_event:stop(Pid),
+ process_flag(trap_exit, OldFl),
+ ok.
diff --git a/lib/stdlib/test/gen_fsm_SUITE.erl b/lib/stdlib/test/gen_fsm_SUITE.erl
index 23c1d9a193..dd120f8c05 100644
--- a/lib/stdlib/test/gen_fsm_SUITE.erl
+++ b/lib/stdlib/test/gen_fsm_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(gen_fsm_SUITE).
@@ -30,7 +30,7 @@
-export([shutdown/1]).
--export([sys/1, sys1/1, call_format_status/1]).
+-export([sys/1, sys1/1, call_format_status/1, error_format_status/1]).
-export([hibernate/1,hiber_idle/3,hiber_wakeup/3,hiber_idle/2,hiber_wakeup/2]).
@@ -305,7 +305,7 @@ shutdown(Config) when is_list(Config) ->
ok.
-sys(suite) -> [sys1, call_format_status].
+sys(suite) -> [sys1, call_format_status, error_format_status].
sys1(Config) when is_list(Config) ->
?line {ok, Pid} =
@@ -320,10 +320,53 @@ sys1(Config) when is_list(Config) ->
call_format_status(Config) when is_list(Config) ->
?line {ok, Pid} = gen_fsm:start(gen_fsm_SUITE, [], []),
?line Status = sys:get_status(Pid),
- ?line {status, Pid, _Mod, [_PDict, running, _Parent, _, Data]} = Status,
+ ?line {status, Pid, _Mod, [_PDict, running, _, _, Data]} = Status,
?line [format_status_called | _] = lists:reverse(Data),
- ?line stop_it(Pid).
+ ?line stop_it(Pid),
+
+ %% check that format_status can handle a name being an atom (pid is
+ %% already checked by the previous test)
+ ?line {ok, Pid2} = gen_fsm:start({local, gfsm}, gen_fsm_SUITE, [], []),
+ ?line Status2 = sys:get_status(gfsm),
+ ?line {status, Pid2, _Mod, [_PDict2, running, _, _, Data2]} = Status2,
+ ?line [format_status_called | _] = lists:reverse(Data2),
+ ?line stop_it(Pid2),
+ %% check that format_status can handle a name being a term other than a
+ %% pid or atom
+ GlobalName1 = {global, "CallFormatStatus"},
+ ?line {ok, Pid3} = gen_fsm:start(GlobalName1, gen_fsm_SUITE, [], []),
+ ?line Status3 = sys:get_status(GlobalName1),
+ ?line {status, Pid3, _Mod, [_PDict3, running, _, _, Data3]} = Status3,
+ ?line [format_status_called | _] = lists:reverse(Data3),
+ ?line stop_it(Pid3),
+ GlobalName2 = {global, {name, "term"}},
+ ?line {ok, Pid4} = gen_fsm:start(GlobalName2, gen_fsm_SUITE, [], []),
+ ?line Status4 = sys:get_status(GlobalName2),
+ ?line {status, Pid4, _Mod, [_PDict4, running, _, _, Data4]} = Status4,
+ ?line [format_status_called | _] = lists:reverse(Data4),
+ ?line stop_it(Pid4).
+
+error_format_status(Config) when is_list(Config) ->
+ ?line error_logger_forwarder:register(),
+ OldFl = process_flag(trap_exit, true),
+ StateData = "called format_status",
+ ?line {ok, Pid} = gen_fsm:start(gen_fsm_SUITE, {state_data, StateData}, []),
+ %% bad return value in the gen_fsm loop
+ ?line {'EXIT',{{bad_return_value, badreturn},_}} =
+ (catch gen_fsm:sync_send_event(Pid, badreturn)),
+ receive
+ {error,_GroupLeader,{Pid,
+ "** State machine"++_,
+ [Pid,{_,_,badreturn},idle,StateData,_]}} ->
+ ok;
+ Other ->
+ ?line io:format("Unexpected: ~p", [Other]),
+ ?line ?t:fail()
+ end,
+ ?t:messages_get(),
+ process_flag(trap_exit, OldFl),
+ ok.
%% Hibernation
hibernate(suite) -> [];
@@ -704,6 +747,8 @@ init(hiber) ->
{ok, hiber_idle, []};
init(hiber_now) ->
{ok, hiber_idle, [], hibernate};
+init({state_data, StateData}) ->
+ {ok, idle, StateData};
init(_) ->
{ok, idle, state_data}.
@@ -844,5 +889,7 @@ handle_sync_event(stop_shutdown_reason, _From, _State, Data) ->
handle_sync_event({get, _Pid}, _From, State, Data) ->
{reply, {state, State, Data}, State, Data}.
-format_status(_Opt, [_Pdict, _StateData]) ->
+format_status(terminate, [_Pdict, StateData]) ->
+ StateData;
+format_status(normal, [_Pdict, _StateData]) ->
[format_status_called].
diff --git a/lib/stdlib/test/gen_server_SUITE.erl b/lib/stdlib/test/gen_server_SUITE.erl
index 6efdce78a1..99388ba2e3 100644
--- a/lib/stdlib/test/gen_server_SUITE.erl
+++ b/lib/stdlib/test/gen_server_SUITE.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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(gen_server_SUITE).
@@ -30,7 +30,8 @@
call_remote_n1/1, call_remote_n2/1, call_remote_n3/1, spec_init/1,
spec_init_local_registered_parent/1,
spec_init_global_registered_parent/1,
- otp_5854/1, hibernate/1, otp_7669/1, call_format_status/1
+ otp_5854/1, hibernate/1, otp_7669/1, call_format_status/1,
+ error_format_status/1, call_with_huge_message_queue/1
]).
% spawn export
@@ -51,7 +52,9 @@ all(suite) ->
call_remote_n2, call_remote_n3, spec_init,
spec_init_local_registered_parent,
spec_init_global_registered_parent,
- otp_5854, hibernate, otp_7669, call_format_status].
+ otp_5854, hibernate, otp_7669,
+ call_format_status, error_format_status,
+ call_with_huge_message_queue].
-define(default_timeout, ?t:minutes(1)).
@@ -895,15 +898,105 @@ call_format_status(doc) ->
["Test that sys:get_status/1,2 calls format_status/2"];
call_format_status(Config) when is_list(Config) ->
?line {ok, Pid} = gen_server:start_link({local, call_format_status},
- gen_server_SUITE, [], []),
+ ?MODULE, [], []),
?line Status1 = sys:get_status(call_format_status),
?line {status, Pid, _Mod, [_PDict, running, _Parent, _, Data1]} = Status1,
?line [format_status_called | _] = lists:reverse(Data1),
?line Status2 = sys:get_status(call_format_status, 5000),
?line {status, Pid, _Mod, [_PDict, running, _Parent, _, Data2]} = Status2,
?line [format_status_called | _] = lists:reverse(Data2),
+
+ %% check that format_status can handle a name being a pid (atom is
+ %% already checked by the previous test)
+ ?line {ok, Pid3} = gen_server:start_link(gen_server_SUITE, [], []),
+ ?line Status3 = sys:get_status(Pid3),
+ ?line {status, Pid3, _Mod, [_PDict3, running, _Parent, _, Data3]} = Status3,
+ ?line [format_status_called | _] = lists:reverse(Data3),
+
+ %% check that format_status can handle a name being a term other than a
+ %% pid or atom
+ GlobalName1 = {global, "CallFormatStatus"},
+ ?line {ok, Pid4} = gen_server:start_link(GlobalName1,
+ gen_server_SUITE, [], []),
+ ?line Status4 = sys:get_status(Pid4),
+ ?line {status, Pid4, _Mod, [_PDict4, running, _Parent, _, Data4]} = Status4,
+ ?line [format_status_called | _] = lists:reverse(Data4),
+ GlobalName2 = {global, {name, "term"}},
+ ?line {ok, Pid5} = gen_server:start_link(GlobalName2,
+ gen_server_SUITE, [], []),
+ ?line Status5 = sys:get_status(GlobalName2),
+ ?line {status, Pid5, _Mod, [_PDict5, running, _Parent, _, Data5]} = Status5,
+ ?line [format_status_called | _] = lists:reverse(Data5),
+ ok.
+
+%% Verify that error termination correctly calls our format_status/2 fun
+%%
+error_format_status(suite) ->
+ [];
+error_format_status(doc) ->
+ ["Test that an error termination calls format_status/2"];
+error_format_status(Config) when is_list(Config) ->
+ ?line error_logger_forwarder:register(),
+ OldFl = process_flag(trap_exit, true),
+ State = "called format_status",
+ ?line {ok, Pid} = gen_server:start_link(?MODULE, {state, State}, []),
+ ?line {'EXIT',{crashed,_}} = (catch gen_server:call(Pid, crash)),
+ receive
+ {'EXIT', Pid, crashed} ->
+ ok
+ end,
+ receive
+ {error,_GroupLeader,{Pid,
+ "** Generic server"++_,
+ [Pid,crash,State,crashed]}} ->
+ ok;
+ Other ->
+ ?line io:format("Unexpected: ~p", [Other]),
+ ?line ?t:fail()
+ end,
+ ?t:messages_get(),
+ process_flag(trap_exit, OldFl),
+ ok.
+
+%% Test that the time for a huge message queue is not
+%% significantly slower than with an empty message queue.
+call_with_huge_message_queue(Config) when is_list(Config) ->
+ ?line Pid = spawn_link(fun echo_loop/0),
+
+ ?line {Time,ok} = tc(fun() -> calls(10, Pid) end),
+
+ ?line [self() ! {msg,N} || N <- lists:seq(1, 500000)],
+ erlang:garbage_collect(),
+ ?line {NewTime,ok} = tc(fun() -> calls(10, Pid) end),
+ io:format("Time for empty message queue: ~p", [Time]),
+ io:format("Time for huge message queue: ~p", [NewTime]),
+
+ case (NewTime+1) / (Time+1) of
+ Q when Q < 10 ->
+ ok;
+ Q ->
+ io:format("Q = ~p", [Q]),
+ ?line ?t:fail()
+ end,
ok.
+calls(0, _) -> ok;
+calls(N, Pid) ->
+ {ultimate_answer,42} = call(Pid, {ultimate_answer,42}),
+ calls(N-1, Pid).
+
+call(Pid, Msg) ->
+ gen_server:call(Pid, Msg, infinity).
+
+tc(Fun) ->
+ timer:tc(erlang, apply, [Fun,[]]).
+
+echo_loop() ->
+ receive
+ {'$gen_call',{Pid,Ref},Msg} ->
+ Pid ! {Ref,Msg},
+ echo_loop()
+ end.
%%--------------------------------------------------------------
%% Help functions to spec_init_*
@@ -1064,5 +1157,7 @@ terminate({From, stopped_info}, _State) ->
terminate(_Reason, _State) ->
ok.
-format_status(_Opt, [_PDict, _State]) ->
- [format_status_called].
+format_status(terminate, [_PDict, State]) ->
+ State;
+format_status(normal, [_PDict, _State]) ->
+ format_status_called.
diff --git a/lib/stdlib/test/io_proto_SUITE.erl b/lib/stdlib/test/io_proto_SUITE.erl
index 93159fbd5b..d9672a8c7b 100644
--- a/lib/stdlib/test/io_proto_SUITE.erl
+++ b/lib/stdlib/test/io_proto_SUITE.erl
@@ -17,6 +17,7 @@
%% %CopyrightEnd%
%%
-module(io_proto_SUITE).
+-compile(r12).
-export([all/1]).
diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl
index ff11ebc6bf..e21de8770a 100644
--- a/lib/stdlib/test/qlc_SUITE.erl
+++ b/lib/stdlib/test/qlc_SUITE.erl
@@ -1,25 +1,26 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2004-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2004-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%
%%
%%%----------------------------------------------------------------
%%% Purpose:Test Suite for the 'qlc' module.
%%%-----------------------------------------------------------------
-module(qlc_SUITE).
+-compile(r12).
-define(QLC, qlc).
-define(QLCs, "qlc").
@@ -3183,7 +3184,9 @@ lookup2(Config) when is_list(Config) ->
[] = qlc:e(Q),
false = lookup_keys(Q)
end, [{1,b},{2,3}])">>,
- {warnings,[{{3,48},qlc,nomatch_filter}]}},
+ {warnings,[{2,sys_core_fold,nomatch_guard},
+ {3,qlc,nomatch_filter},
+ {3,sys_core_fold,{eval_failure,badarg}}]}},
<<"etsc(fun(E) ->
Q = qlc:q([X || {X} <- ets:table(E), element(1,{X}) =:= 1]),
diff --git a/lib/stdlib/test/re_SUITE.erl b/lib/stdlib/test/re_SUITE.erl
index 02683f9f1a..46a84d4e24 100644
--- a/lib/stdlib/test/re_SUITE.erl
+++ b/lib/stdlib/test/re_SUITE.erl
@@ -18,12 +18,12 @@
%%
-module(re_SUITE).
--export([all/1, pcre/1,compile_options/1,run_options/1,combined_options/1,replace_autogen/1,global_capture/1,replace_input_types/1,replace_return/1,split_autogen/1,split_options/1,split_specials/1,error_handling/1,pcre_cve_2008_2371/1]).
+-export([all/1, pcre/1,compile_options/1,run_options/1,combined_options/1,replace_autogen/1,global_capture/1,replace_input_types/1,replace_return/1,split_autogen/1,split_options/1,split_specials/1,error_handling/1,pcre_cve_2008_2371/1,pcre_compile_workspace_overflow/1,re_infinite_loop/1]).
-include("test_server.hrl").
-include_lib("kernel/include/file.hrl").
-all(suite) -> [pcre,compile_options,run_options,combined_options,replace_autogen,global_capture,replace_input_types,replace_return,split_autogen,split_options,split_specials,error_handling,pcre_cve_2008_2371].
+all(suite) -> [pcre,compile_options,run_options,combined_options,replace_autogen,global_capture,replace_input_types,replace_return,split_autogen,split_options,split_specials,error_handling,pcre_cve_2008_2371,pcre_compile_workspace_overflow,re_infinite_loop].
pcre(doc) ->
["Run all applicable tests from the PCRE testsuites."];
@@ -544,3 +544,25 @@ pcre_cve_2008_2371(Config) when is_list(Config) ->
%% Make sure it doesn't crash the emulator.
re:compile(<<"(?i)[\xc3\xa9\xc3\xbd]|[\xc3\xa9\xc3\xbdA]">>, [unicode]),
ok.
+
+pcre_compile_workspace_overflow(doc) ->
+ "Patch from http://vcs.pcre.org/viewvc/code/trunk/pcre_compile.c?r1=504&r2=505&view=patch";
+pcre_compile_workspace_overflow(Config) when is_list(Config) ->
+ N = 819,
+ ?line {error,{"internal error: overran compiling workspace",799}} =
+ re:compile([lists:duplicate(N, $(), lists:duplicate(N, $))]),
+ ok.
+re_infinite_loop(doc) ->
+ "Make sure matches that really loop infinitely actually fail";
+re_infinite_loop(Config) when is_list(Config) ->
+ Dog = ?t:timetrap(?t:minutes(1)),
+ ?line Str =
+ "http:/www.flickr.com/slideShow/index.gne?group_id=&user_id=69845378@N0",
+ ?line EMail_regex = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+"
+ ++ "(\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*"
+ ++ "@.*([a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+"
+ ++ "([a-zA-Z]{2}|com|org|net|gov|mil"
+ ++ "|biz|info|mobi|name|aero|jobs|museum)",
+ ?line nomatch = re:run(Str, EMail_regex),
+ ?t:timetrap_cancel(Dog),
+ ok.
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index f081bd60e2..02ea6d9d68 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -17,5 +17,4 @@
# %CopyrightEnd%
#
-STDLIB_VSN = 1.17
-
+STDLIB_VSN = 1.17.1
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index a3b842b50b..fca93a27d9 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -31,6 +31,21 @@
<p>This document describes the changes made to the Syntax_Tools
application.</p>
+<section><title>Syntax_Tools 1.6.6</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Minor changes and clean-ups.</p>
+ <p>
+ Own Id: OTP-8709</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 1.6.5</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl
index 09ce21a428..108ab3bffd 100644
--- a/lib/syntax_tools/src/erl_comment_scan.erl
+++ b/lib/syntax_tools/src/erl_comment_scan.erl
@@ -26,6 +26,7 @@
-export([file/1, join_lines/1, scan_lines/1, string/1]).
+-export_type([comment/0]).
%% =====================================================================
@@ -273,12 +274,8 @@ join_lines([], Txt, L, Col, Ind) ->
filename([C|T]) when is_integer(C), C > 0, C =< 255 ->
[C | filename(T)];
-filename([H|T]) ->
- filename(H) ++ filename(T);
filename([]) ->
[];
-filename(N) when is_atom(N) ->
- atom_to_list(N);
filename(N) ->
report_error("bad filename: `~P'.", [N, 25]),
exit(error).
diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl
index 606441bcf1..c2c72d1ed2 100644
--- a/lib/syntax_tools/src/erl_prettypr.erl
+++ b/lib/syntax_tools/src/erl_prettypr.erl
@@ -384,7 +384,7 @@ lay_postcomments(Cs, D) ->
beside(D, floating(break(stack_comments(Cs, true)), 1, 0)).
%% Format (including padding, if `Pad' is `true', otherwise not)
-%% and stack the listed comments above each other,
+%% and stack the listed comments above each other.
stack_comments([C | Cs], Pad) ->
D = stack_comment_lines(erl_syntax:comment_text(C)),
@@ -405,9 +405,7 @@ stack_comments([C | Cs], Pad) ->
D1; % done
_ ->
above(D1, stack_comments(Cs, Pad))
- end;
-stack_comments([], _) ->
- empty().
+ end.
%% Stack lines of text above each other and prefix each string in
%% the list with a single `%' character.
diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl
index 145bbc6f37..94e760dad7 100644
--- a/lib/syntax_tools/src/erl_recomment.erl
+++ b/lib/syntax_tools/src/erl_recomment.erl
@@ -486,7 +486,7 @@ build_tree(Node) ->
%% Include L, while preserving Min =< Max.
tree_node(minpos(L, Min),
- max(L, Max),
+ erlang:max(L, Max),
erl_syntax:type(Node),
erl_syntax:get_attrs(Node),
Subtrees)
@@ -507,7 +507,7 @@ build_list(Ts) ->
build_list([T | Ts], Min, Max, Ack) ->
Node = build_tree(T),
Min1 = minpos(node_min(Node), Min),
- Max1 = max(node_max(Node), Max),
+ Max1 = erlang:max(node_max(Node), Max),
build_list(Ts, Min1, Max1, [Node | Ack]);
build_list([], Min, Max, Ack) ->
list_node(Min, Max, lists:reverse(Ack)).
@@ -518,7 +518,7 @@ build_list_list(Ls) ->
build_list_list([L | Ls], Min, Max, Ack) ->
Node = build_list(L),
Min1 = minpos(node_min(Node), Min),
- Max1 = max(node_max(Node), Max),
+ Max1 = erlang:max(node_max(Node), Max),
build_list_list(Ls, Min1, Max1, [Node | Ack]);
build_list_list([], Min, Max, Ack) ->
{lists:reverse(Ack), Min, Max}.
@@ -723,9 +723,6 @@ tree_node_attrs(#tree{attrs = Attrs}) ->
%% Just the generic "maximum" function
-max(X, Y) when X > Y -> X;
-max(_, Y) -> Y.
-
%% Return the least positive integer of X and Y, or zero if none of them
%% are positive. (This is necessary for computing minimum source line
%% numbers, since zero (or negative) numbers may occur, but they
diff --git a/lib/syntax_tools/src/erl_syntax.erl b/lib/syntax_tools/src/erl_syntax.erl
index 9a2967d550..a40bf83c5a 100644
--- a/lib/syntax_tools/src/erl_syntax.erl
+++ b/lib/syntax_tools/src/erl_syntax.erl
@@ -309,6 +309,7 @@
data/1,
is_tree/1]).
+-export_type([forms/0, syntaxTree/0, syntaxTreeAttributes/0]).
%% =====================================================================
%% IMPLEMENTATION NOTES:
diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl
index 5c4e074488..4808971a59 100644
--- a/lib/syntax_tools/src/erl_syntax_lib.erl
+++ b/lib/syntax_tools/src/erl_syntax_lib.erl
@@ -46,6 +46,8 @@
new_variable_names/2, new_variable_names/3, strip_comments/1,
to_comment/1, to_comment/2, to_comment/3, variables/1]).
+-export_type([info_pair/0]).
+
%% =====================================================================
-type ordset(X) :: [X]. % XXX: TAKE ME OUT
@@ -400,10 +402,7 @@ new_variable_name(N, R, _T, F, S) ->
%% implementation of `sets'.
start_range(S) ->
- max(sets:size(S) * ?START_RANGE_FACTOR, ?MINIMUM_RANGE).
-
-max(X, Y) when X > Y -> X;
-max(_, Y) -> Y.
+ erlang:max(sets:size(S) * ?START_RANGE_FACTOR, ?MINIMUM_RANGE).
%% The previous number might or might not be used to compute the
%% next number to be tried. It is currently not used.
diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl
index e92e9593b6..702b399615 100644
--- a/lib/syntax_tools/src/igor.erl
+++ b/lib/syntax_tools/src/igor.erl
@@ -699,7 +699,7 @@ merge_files(Name, Trees, Files, Opts) ->
options :: [option()]
}).
--spec merge_sources(atom(), erl_syntax:forms(), [option()]) ->
+-spec merge_sources(atom(), [erl_syntax:forms()], [option()]) ->
{erl_syntax:syntaxTree(), [stubDescriptor()]}.
merge_sources(Name, Sources, Opts) ->
@@ -782,12 +782,12 @@ merge_sources_1(Name, Modules, Trees, Opts) ->
%% however not "safe" by default. If no modules are explicitly
%% specified as static, it is assumed that *all* are static.
Static0 = ordsets:from_list(proplists:append_values(static, Opts)),
- case proplists:is_defined(static, Opts) of
- false ->
- Static = All;
- true ->
- Static = ordsets:add_element(Name, Static0)
- end,
+ Static = case proplists:is_defined(static, Opts) of
+ false ->
+ All;
+ true ->
+ ordsets:add_element(Name, Static0)
+ end,
check_module_names(Static, All, "declared 'static'"),
verbose("static modules: ~p.", [Static], Opts),
@@ -806,8 +806,8 @@ merge_sources_1(Name, Modules, Trees, Opts) ->
verbose("safe modules: ~p.", [Safe], Opts),
Preserved = (ordsets:is_element(Name, Sources)
- and ordsets:is_element(Name, Export))
- or proplists:get_bool(no_banner, Opts),
+ andalso ordsets:is_element(Name, Export))
+ orelse proplists:get_bool(no_banner, Opts),
NoHeaders = proplists:get_bool(no_headers, Opts),
Notes = proplists:get_value(notes, Opts, always),
Rs = proplists:append_values(redirect, Opts),
@@ -2924,9 +2924,7 @@ make_attribute({Name, Term}) ->
[erl_syntax:abstract(Term)]).
is_auto_import({F, A}) ->
- erl_internal:bif(F, A);
-is_auto_import(_) ->
- false.
+ erl_internal:bif(F, A).
timestamp() ->
{{Yr, Mth, Dy}, {Hr, Mt, Sc}} = erlang:localtime(),
diff --git a/lib/syntax_tools/src/prettypr.erl b/lib/syntax_tools/src/prettypr.erl
index 1868f63e54..c13fa30998 100644
--- a/lib/syntax_tools/src/prettypr.erl
+++ b/lib/syntax_tools/src/prettypr.erl
@@ -48,6 +48,8 @@
nest/2, par/1, par/2, sep/1, text/1, null_text/1, text_par/1,
text_par/2]).
+-export_type([document/0]).
+
%% ---------------------------------------------------------------------
-type deep_string() :: [char() | deep_string()].
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 2ba5eac582..6051fb8e39 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 1.6.5
+SYNTAX_TOOLS_VSN = 1.6.6
diff --git a/lib/test_server/doc/src/notes.xml b/lib/test_server/doc/src/notes.xml
index b6e0a6cefa..dae071311c 100644
--- a/lib/test_server/doc/src/notes.xml
+++ b/lib/test_server/doc/src/notes.xml
@@ -32,6 +32,70 @@
<file>notes.xml</file>
</header>
+<section><title>Test_Server 3.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Returning {fail,Reason} from the framework end_tc
+ function was not handled properly by Test Server for all
+ test suite functions.</p>
+ <p>
+ Own Id: OTP-8492 Aux Id: seq11502 </p>
+ </item>
+ <item>
+ <p>
+ If the framework end_tc function would hang and get
+ aborted by Test Server, there was no indication of
+ failure in the logs. This has been fixed.</p>
+ <p>
+ Own Id: OTP-8682 Aux Id: seq11504 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ It is now possible for the Test Server framework end_tc
+ function to change the status of the test case from ok or
+ auto-skipped to failed by returning {fail,Reason}.</p>
+ <p>
+ Own Id: OTP-8495 Aux Id: seq11502 </p>
+ </item>
+ <item>
+ <p>
+ Test Server will now call the end_per_testcase/2 function
+ even if the test case has been terminated explicitly
+ (with abort_current_testcase/1), or after a timetrap
+ timeout. Under these circumstances the return value of
+ end_per_testcase is completely ignored. Therefore the
+ function will not be able to change the reason for test
+ case termination by returning {fail,Reason}, nor will it
+ be able to save data with {save_config,Data}.</p>
+ <p>
+ Own Id: OTP-8500 Aux Id: seq11521 </p>
+ </item>
+ <item>
+ <p>
+ Previously, a repeat property of a test case group
+ specified the number of times the group should be
+ repeated after the main test run. I.e. {repeat,N} would
+ case the group to execute 1+N times. To be consistent
+ with the behaviour of the run_test repeat option, this
+ has been changed. N now specifies the absolute number of
+ executions instead.</p>
+ <p>
+ Own Id: OTP-8689 Aux Id: seq11502 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Test_Server 3.3.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/test_server/doc/src/test_server.xml b/lib/test_server/doc/src/test_server.xml
index 6e75425862..0cae75d692 100644
--- a/lib/test_server/doc/src/test_server.xml
+++ b/lib/test_server/doc/src/test_server.xml
@@ -167,6 +167,22 @@
</desc>
</func>
<func>
+ <name>adjusted_sleep(MSecs) -> ok</name>
+ <fsummary>Suspens the calling task for a specified time.</fsummary>
+ <type>
+ <v>MSecs = integer() | float() | infinity</v>
+ <d>The default number of milliseconds to sleep</d>
+ </type>
+ <desc>
+ <p>This function suspends the calling process for at least the
+ supplied number of milliseconds. The function behaves the same
+ way as <c>test_server:sleep/1</c>, only <c>MSecs</c>
+ will be multiplied by the 'multiply_timetraps' value, if set,
+ and also automatically scaled up if 'scale_timetraps' is set
+ to true (which it is by default).</p>
+ </desc>
+ </func>
+ <func>
<name>hours(N) -> MSecs</name>
<name>minutes(N) -> MSecs</name>
<name>seconds(N) -> MSecs</name>
diff --git a/lib/test_server/doc/src/test_server_ctrl.xml b/lib/test_server/doc/src/test_server_ctrl.xml
index 4a778bcaf7..2368c4bacc 100644
--- a/lib/test_server/doc/src/test_server_ctrl.xml
+++ b/lib/test_server/doc/src/test_server_ctrl.xml
@@ -376,6 +376,31 @@ Optional, if not given the test server controller node
</desc>
</func>
<func>
+ <name>scale_timetraps(Bool) -> ok</name>
+ <fsummary>.</fsummary>
+ <type>
+ <v>Bool = true | false</v>
+ </type>
+ <desc>
+ <p>This function should be called before a test is started.
+ The parameter specifies if test_server should attempt
+ to automatically scale the timetrap value in order to compensate
+ for delays caused by e.g. the cover tool.</p>
+ </desc>
+ </func>
+ <func>
+ <name>get_timetrap_parameters() -> {N,Bool} </name>
+ <fsummary>Read the parameter values that affect timetraps.</fsummary>
+ <type>
+ <v>N = integer() | infinity</v>
+ <v>Bool = true | false</v>
+ </type>
+ <desc>
+ <p>This function may be called to read the values set by
+ <c>multiply_timetraps/1</c> and <c>scale_timetraps/1</c>.</p>
+ </desc>
+ </func>
+ <func>
<name>cover(Application,Analyse) -> ok</name>
<name>cover(CoverFile,Analyse) -> ok</name>
<name>cover(App,CoverFile,Analyse) -> ok</name>
@@ -538,9 +563,6 @@ Optional, if not given the test server controller node
test server controller node. The log must be formatted using
<c>ttb:format/1/2</c>.
</p>
- <p>This is valid for all targets except the OSE/Delta target
- for which all nodes will be logged and automatically formatted
- in one single text file called <c>allnodes-test_server</c>.</p>
</desc>
</func>
<func>
diff --git a/lib/test_server/doc/src/ts.xml b/lib/test_server/doc/src/ts.xml
index 0f91d3eea2..f60c79aadd 100644
--- a/lib/test_server/doc/src/ts.xml
+++ b/lib/test_server/doc/src/ts.xml
@@ -250,7 +250,7 @@
running test suites. If a remote host is to be used, the
<c>TargetSystem</c> argument must be given so that "cross
installation" can be done. This should be used for testing on
- VxWorks or OSE/Delta. Installation is required for any of the
+ VxWorks. Installation is required for any of the
functions in <c>ts</c> to work.
</p>
<p>Opts may be one or more of
@@ -275,7 +275,7 @@
strings.
</item>
<item><c>{slavetargets, SlaveTarges}</c><br></br>
- For VxWorks and OSE/Delta only. This is a list of
+ For VxWorks only. This is a list of
available hosts where slave nodes can be started. This is
necessary because only one node can run per host in the
VxWorks environment. This is not the same as
diff --git a/lib/test_server/src/test_server.erl b/lib/test_server/src/test_server.erl
index f918f47415..acc9dbaab8 100644
--- a/lib/test_server/src/test_server.erl
+++ b/lib/test_server/src/test_server.erl
@@ -35,7 +35,7 @@
-export([fail/0,fail/1,format/1,format/2,format/3]).
-export([capture_start/0,capture_stop/0,capture_get/0]).
-export([messages_get/0]).
--export([hours/1,minutes/1,seconds/1,sleep/1,timecall/3]).
+-export([hours/1,minutes/1,seconds/1,sleep/1,adjusted_sleep/1,timecall/3]).
-export([timetrap_scale_factor/0,timetrap/1,timetrap_cancel/1]).
-export([m_out_of_n/3,do_times/4,do_times/2]).
-export([call_crash/3,call_crash/4,call_crash/5]).
@@ -89,14 +89,14 @@ init(Host,Port,Starter) ->
global:register_name(?MODULE,self()),
process_flag(trap_exit,true),
test_server_sup:cleanup_crash_dumps(),
- case gen_tcp:connect(Host,Port, [binary,
- {reuseaddr,true},
+ case gen_tcp:connect(Host,Port, [binary,
+ {reuseaddr,true},
{packet,2}]) of
- {ok,MainSock} ->
+ {ok,MainSock} ->
Starter ! {self(),started},
request(MainSock,{target_info,init_target_info()}),
loop(#state{controller={Host,MainSock}});
- Error ->
+ Error ->
Starter ! {self(),{error,
{could_not_contact_controller,Error}}}
end.
@@ -127,7 +127,7 @@ loop(#state{controller={_,MainSock}} = State) ->
halt();
{'EXIT',Pid,Reason} ->
case lists:keysearch(Pid,1,State#state.jobs) of
- {value,{Pid,Name}} ->
+ {value,{Pid,Name}} ->
case Reason of
normal -> ignore;
_other -> request(MainSock,{job_proc_killed,Name,Reason})
@@ -157,14 +157,14 @@ init_purify() ->
job(Host,Port,Starter) ->
process_flag(trap_exit,true),
init_purify(),
- case gen_tcp:connect(Host,Port, [binary,
- {reuseaddr,true},
+ case gen_tcp:connect(Host,Port, [binary,
+ {reuseaddr,true},
{packet,4},
{active,false}]) of
{ok,JobSock} ->
Starter ! {self(),started},
job(JobSock);
- Error ->
+ Error ->
Starter ! {self(),{error,
{could_not_contact_controller,Error}}}
end.
@@ -192,7 +192,7 @@ get_jobdir() ->
true ->
{ok,Cwd} = file:get_cwd(),
Cwd ++ "/" ++ Basename;
- false ->
+ false ->
filename:absname(Basename)
end.
@@ -216,7 +216,7 @@ send_privdir(JobDir,JobSock) ->
del_dir(Dir) ->
case file:read_file_info(Dir) of
- {ok,#file_info{type=directory}} ->
+ {ok,#file_info{type=directory}} ->
{ok,Cont} = file:list_dir(Dir),
lists:foreach(fun(F) -> del_dir(filename:join(Dir,F)) end, Cont),
ok = file:del_dir(Dir);
@@ -227,7 +227,7 @@ del_dir(Dir) ->
catch file:delete(Dir),
ok
end.
-
+
%%
%% Receive and decode request on job socket
%%
@@ -237,7 +237,7 @@ job_loop(JobSock) ->
ok -> job_loop(JobSock);
{stop,R} -> R
end.
-
+
decode_job({{beam,Mod,Which},Beam}) ->
% FIXME, shared directory structure on host and target required,
% "Library beams" are not loaded from HOST... /Patrik
@@ -254,7 +254,7 @@ decode_job({{datadir,Tarfile0},Archive}) ->
ok = erl_tar:extract(Tarfile,[compressed,{cwd,JobDir}]),
ok = file:delete(Tarfile),
ok;
-decode_job({test_case,Case}) ->
+decode_job({test_case,Case}) ->
Result = run_test_case_apply(Case),
JobSock = get(test_server_job_sock),
request(JobSock,{test_case_result,Result}),
@@ -266,11 +266,11 @@ decode_job({test_case,Case}) ->
request(JobSock,{{crash_dumps,filename:basename(TarFile)},TarBin})
end,
ok;
-decode_job({sync_apply,{M,F,A}}) ->
+decode_job({sync_apply,{M,F,A}}) ->
R = apply(M,F,A),
request(get(test_server_job_sock),{sync_result,R}),
ok;
-decode_job(job_done) ->
+decode_job(job_done) ->
{stop,stopped}.
%%
@@ -282,9 +282,9 @@ decode_job(job_done) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% cover_compile({App,Include,Exclude,Cross}) ->
+%% cover_compile({App,Include,Exclude,Cross}) ->
%% {ok,AnalyseModules} | {error,Reason}
-%%
+%%
%% App = atom() , name of application to be compiled
%% Exclude = [atom()], list of modules to exclude
%% Include = [atom()], list of modules outside of App that should be included
@@ -293,7 +293,7 @@ decode_job(job_done) ->
%% in the cover compilation, but that shall not be part of
%% the cover analysis for this application.
%%
-%% Cover compile the given application. Return {ok,AnalyseMods} if application
+%% Cover compile the given application. Return {ok,AnalyseMods} if application
%% is found, else {error,application_not_found}.
cover_compile({none,_Exclude,Include,Cross}) ->
@@ -330,7 +330,7 @@ cover_compile({App,all,Include,Cross}) ->
end;
cover_compile({App,Exclude,Include,Cross}) ->
case code:lib_dir(App) of
- {error,bad_name} ->
+ {error,bad_name} ->
case Include++Cross of
[] ->
io:format("\nWARNING: Can't find lib_dir for \'~w\'\n"
@@ -366,7 +366,7 @@ cover_compile({App,Exclude,Include,Cross}) ->
{ok,AnalyseMods}
end
end.
-
+
module_names(Beams) ->
[list_to_atom(filename:basename(filename:rootname(Beam))) || Beam <- Beams].
@@ -380,11 +380,11 @@ do_cover_compile1([Dont|Rest]) when Dont=:=cover;
Dont=:=test_server_ctrl ->
do_cover_compile1(Rest);
do_cover_compile1([M|Rest]) ->
- case {code:is_sticky(M),code:is_loaded(M)} of
+ case {code:is_sticky(M),code:is_loaded(M)} of
{true,_} ->
code:unstick_mod(M),
case cover:compile_beam(M) of
- {ok,_} ->
+ {ok,_} ->
ok;
Error ->
io:fwrite("\nWARNING: Could not cover compile ~w: ~p\n",
@@ -402,7 +402,7 @@ do_cover_compile1([M|Rest]) ->
end;
{false,_} ->
case cover:compile_beam(M) of
- {ok,_} ->
+ {ok,_} ->
ok;
Error ->
io:fwrite("\nWARNING: Could not cover compile ~w: ~p\n",
@@ -415,14 +415,14 @@ do_cover_compile1([]) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% cover_analyse(Analyse,Modules) -> [{M,{Cov,NotCov,Details}}]
-%%
+%%
%% Analyse = {details,Dir} | details | {overview,void()} | overview
%% Modules = [atom()], the modules to analyse
%%
%% Cover analysis. If this is a remote target, analyse_to_file can not be used.
%% In that case the analyse level 'line' is used instead if Analyse==details.
%%
-%% If this is a local target, the test directory is given
+%% If this is a local target, the test directory is given
%% (Analyse=={details,Dir}) and analyse_to_file can be used directly.
%%
%% If Analyse==overview | {overview,Dir} analyse_to_file is not used, only
@@ -432,12 +432,12 @@ do_cover_compile1([]) ->
%% all.coverdata in that directory.
cover_analyse(Analyse,Modules) ->
io:fwrite("Cover analysing...\n",[]),
- DetailsFun =
+ DetailsFun =
case Analyse of
{details,Dir} ->
case cover:export(filename:join(Dir,"all.coverdata")) of
ok ->
- fun(M) ->
+ fun(M) ->
OutFile = filename:join(Dir,
atom_to_list(M) ++
".COVER.html"),
@@ -451,7 +451,7 @@ cover_analyse(Analyse,Modules) ->
Error ->
fun(_) -> Error end
end;
- details ->
+ details ->
fun(M) ->
case cover:analyse(M,line) of
{ok,Lines} ->
@@ -489,7 +489,7 @@ cover_analyse(Analyse,Modules) ->
unstick_all_sticky(Node) ->
lists:filter(
- fun(M) ->
+ fun(M) ->
case code:is_sticky(M) of
true ->
rpc:call(Node,code,unstick_mod,[M]),
@@ -502,24 +502,24 @@ unstick_all_sticky(Node) ->
stick_all_sticky(Node,Sticky) ->
lists:foreach(
- fun(M) ->
+ fun(M) ->
rpc:call(Node,code,stick_mod,[M])
end,
Sticky).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% run_test_case_apply(Mod,Func,Args,Name,RunInit,MultiplyTimetrap) ->
+%% run_test_case_apply(Mod,Func,Args,Name,RunInit,TimetrapData) ->
%% {Time,Value,Loc,Opts,Comment} | {died,Reason,unknown,Comment}
-%%
+%%
%% Time = float() (seconds)
%% Value = term()
%% Loc = term()
%% Comment = string()
%% Reason = term()
%%
-%% Spawns off a process (case process) that actually runs the test suite.
-%% The case process will have the job process as group leader, which makes
+%% Spawns off a process (case process) that actually runs the test suite.
+%% The case process will have the job process as group leader, which makes
%% it possible to capture all it's output from io:format/2, etc.
%%
%% The job process then sits down and waits for news from the case process.
@@ -535,40 +535,43 @@ stick_all_sticky(Node,Sticky) ->
%% called or the comment given by the return value {comment,Comment} from
%% a test case.
%%
-%% {died,Reason,unknown,Comment} is returned if the test case was killed
+%% {died,Reason,unknown,Comment} is returned if the test case was killed
%% by some other process. Reason is the kill reason provided.
%%
-%% MultiplyTimetrap indicates a possible extension of all timetraps
-%% Timetraps will be multiplied by this integer. If it is infinity, no
-%% timetraps will be started at all.
+%% TimetrapData = {MultiplyTimetrap,ScaleTimetrap}, which indicates a
+%% possible extension of all timetraps. Timetraps will be multiplied by
+%% MultiplyTimetrap. If it is infinity, no timetraps will be started at all.
+%% ScaleTimetrap indicates if test_server should attemp to automatically
+%% compensate timetraps for runtime delays introduced by e.g. tools like
+%% cover.
-run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,MultiplyTimetrap}) ->
+run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,TimetrapData}) ->
purify_format("Test case #~w ~w:~w/1", [CaseNum, Mod, Func]),
case os:getenv("TS_RUN_VALGRIND") of
- false ->
+ false ->
ok;
_ ->
os:putenv("VALGRIND_LOGFILE_INFIX",atom_to_list(Mod)++"."++
atom_to_list(Func)++"-")
end,
test_server_h:testcase({Mod,Func,1}),
- ProcBef = erlang:system_info(process_count),
- Result = run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap),
+ ProcBef = erlang:system_info(process_count),
+ Result = run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData),
ProcAft = erlang:system_info(process_count),
purify_new_leaks(),
DetFail = get(test_server_detected_fail),
{Result,DetFail,ProcBef,ProcAft}.
-
-run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) ->
+
+run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
case get(test_server_job_dir) of
undefined ->
%% i'm a local target
- do_run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap);
+ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData);
JobDir ->
%% i'm a remote target
case Args of
[Config] when is_list(Config) ->
- {value,{data_dir,HostDataDir}} =
+ {value,{data_dir,HostDataDir}} =
lists:keysearch(data_dir, 1, Config),
DataBase = filename:basename(HostDataDir),
TargetDataDir = filename:join(JobDir, DataBase),
@@ -578,18 +581,18 @@ run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) ->
Config2 = lists:keyreplace(priv_dir, 1, Config1,
{priv_dir,TargetPrivDir}),
do_run_test_case_apply(Mod, Func, [Config2], Name, RunInit,
- MultiplyTimetrap);
+ TimetrapData);
_other ->
do_run_test_case_apply(Mod, Func, Args, Name, RunInit,
- MultiplyTimetrap)
+ TimetrapData)
end
end.
-do_run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) ->
+do_run_test_case_apply(Mod, Func, Args, Name, RunInit, TimetrapData) ->
{ok,Cwd} = file:get_cwd(),
Args2Print = case Args of
- [Args1] when is_list(Args1) ->
+ [Args1] when is_list(Args1) ->
lists:keydelete(tc_group_result, 1, Args1);
- _ ->
+ _ ->
Args
end,
print(minor, "Test case started with:\n~s:~s(~p)\n", [Mod,Func,Args2Print]),
@@ -600,16 +603,16 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) ->
OldGLeader = group_leader(),
%% Set ourself to group leader for the spawned process
group_leader(self(),self()),
- Pid =
+ Pid =
spawn_link(
- fun() ->
- run_test_case_eval(Mod, Func, Args, Name, Ref,
- RunInit, MultiplyTimetrap,
+ fun() ->
+ run_test_case_eval(Mod, Func, Args, Name, Ref,
+ RunInit, TimetrapData,
TCCallback)
end),
group_leader(OldGLeader, self()),
put(test_server_detected_fail, []),
- run_test_case_msgloop(Ref, Pid, false, false, "").
+ run_test_case_msgloop(Ref, Pid, false, false, "", undefined).
%% Ugly bug (pre R5A):
%% If this process (group leader of the test case) terminates before
@@ -620,7 +623,7 @@ do_run_test_case_apply(Mod, Func, Args, Name, RunInit, MultiplyTimetrap) ->
%% A test case is known to have failed if it returns {'EXIT', _} tuple,
%% or sends a message {failed, File, Line} to it's group_leader
%%
-run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) ->
+run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment, CurrConf) ->
%% NOTE: Keep job_proxy_msgloop/0 up to date when changes
%% are made in this function!
{Timeout,ReturnValue} =
@@ -641,13 +644,13 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) ->
receive
{'DOWN', Mon, process, Pid, _} ->
Comment
- after 10000 ->
+ after 10000 ->
%% Pid is probably trapping exits, hit it harder...
exit(Pid, kill),
%% here's the only place we know Reason, so we save
%% it as a comment, potentially replacing user data
Error = lists:flatten(io_lib:format("Aborted: ~p",[Reason])),
- Error1 = lists:flatten([string:strip(S,left) ||
+ Error1 = lists:flatten([string:strip(S,left) ||
S <- string:tokens(Error,[$\n])]),
if length(Error1) > 63 ->
string:substr(Error1,1,60) ++ "...";
@@ -655,149 +658,224 @@ run_test_case_msgloop(Ref, Pid, CaptureStdout, Terminate, Comment) ->
Error1
end
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,NewComment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,NewComment,CurrConf);
{io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}}
when is_list(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,io_lib,Func,[Format,Args]}}
when is_atom(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,Bytes}} ->
run_test_case_msgloop_io(
ReplyAs,CaptureStdout,Bytes,From,put_chars),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}}
when is_list(Format) ->
Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)),
run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}}
when is_list(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,unicode,io_lib,Func,[Format,Args]}}
when is_atom(Format) ->
Msg = unicode_to_latin1(catch io_lib:Func(Format,Args)),
run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,latin1,io_lib,Func,[Format,Args]}}
when is_atom(Format) ->
Msg = (catch io_lib:Func(Format,Args)),
run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,unicode,Bytes}} ->
run_test_case_msgloop_io(
ReplyAs,CaptureStdout,unicode_to_latin1(Bytes),From,put_chars),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{io_request,From,ReplyAs,{put_chars,latin1,Bytes}} ->
run_test_case_msgloop_io(
ReplyAs,CaptureStdout,Bytes,From,put_chars),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
IoReq when element(1, IoReq) == io_request ->
%% something else, just pass it on
group_leader() ! IoReq,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{structured_io,ClientPid,Msg} ->
output(Msg, ClientPid),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{capture,NewCapture} ->
- run_test_case_msgloop(Ref,Pid,NewCapture,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,NewCapture,Terminate,Comment,CurrConf);
{sync_apply,From,MFA} ->
sync_local_or_remote_apply(false,From,MFA),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{sync_apply_proxy,Proxy,From,MFA} ->
sync_local_or_remote_apply(Proxy,From,MFA),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{printout,Detail,Format,Args} ->
print(Detail,Format,Args),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{comment,NewComment} ->
Terminate1 =
case Terminate of
- {true,{Time,Value,Loc,Opts,_OldComment}} ->
+ {true,{Time,Value,Loc,Opts,_OldComment}} ->
{true,{Time,Value,mod_loc(Loc),Opts,NewComment}};
Other ->
Other
end,
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate1,NewComment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate1,NewComment,CurrConf);
+ {set_curr_conf,NewCurrConf} ->
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,NewCurrConf);
{'EXIT',Pid,{Ref,Time,Value,Loc,Opts}} ->
RetVal = {Time/1000000,Value,mod_loc(Loc),Opts,Comment},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment,undefined);
{'EXIT',Pid,Reason} ->
case Reason of
{timetrap_timeout,TVal,Loc} ->
%% convert Loc to form that can be formatted
- Loc1 = mod_loc(Loc),
- {Mod,Func} = get_mf(Loc1),
- %% The framework functions mustn't execute on this
- %% group leader process or io will cause deadlock,
- %% so we spawn a dedicated process for the operation
- %% and let the group leader go back to handle io.
- spawn_fw_call(Mod,Func,Pid,{timetrap_timeout,TVal},
- Loc1,self(),Comment),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ case mod_loc(Loc) of
+ {FwMod,FwFunc,framework} ->
+ %% timout during framework call
+ spawn_fw_call(FwMod,FwFunc,Pid,
+ {framework_error,{timetrap,TVal}},
+ unknown,self(),Comment),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ Comment,undefined);
+ Loc1 ->
+ {Mod,Func} = get_mf(Loc1),
+ %% call end_per_testcase on a separate process,
+ %% only so that the user has a chance to clean up
+ %% after init_per_testcase, even after a timetrap timeout
+ NewCurrConf =
+ case CurrConf of
+ {{Mod,Func},Conf} ->
+ EndConfPid =
+ call_end_conf(Mod,Func,Pid,
+ {timetrap_timeout,TVal},
+ Loc1,[{tc_status,
+ {failed,
+ timetrap_timeout}}|Conf],
+ TVal),
+ {EndConfPid,{Mod,Func},Conf};
+ _ ->
+ %% The framework functions mustn't execute on this
+ %% group leader process or io will cause deadlock,
+ %% so we spawn a dedicated process for the operation
+ %% and let the group leader go back to handle io.
+ spawn_fw_call(Mod,Func,Pid,{timetrap_timeout,TVal},
+ Loc1,self(),Comment),
+ undefined
+ end,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ Comment,NewCurrConf)
+ end;
{timetrap_timeout,TVal,Loc,InitOrEnd} ->
- Loc1 = mod_loc(Loc),
- {Mod,_Func} = get_mf(Loc1),
- spawn_fw_call(Mod,InitOrEnd,Pid,{timetrap_timeout,TVal},
- Loc1,self(),Comment),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
- {testcase_aborted,Reason,Loc} ->
- Loc1 = mod_loc(Loc),
- {Mod,Func} = get_mf(Loc1),
- spawn_fw_call(Mod,Func,Pid,{testcase_aborted,Reason},
- Loc1,self(),Comment),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
- killed ->
+ case mod_loc(Loc) of
+ {FwMod,FwFunc,framework} ->
+ %% timout during framework call
+ spawn_fw_call(FwMod,FwFunc,Pid,
+ {framework_error,{timetrap,TVal}},
+ unknown,self(),Comment);
+ Loc1 ->
+ {Mod,_Func} = get_mf(Loc1),
+ spawn_fw_call(Mod,InitOrEnd,Pid,{timetrap_timeout,TVal},
+ Loc1,self(),Comment)
+ end,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
+ {testcase_aborted,AbortReason,AbortLoc} ->
+ ErrorMsg = {testcase_aborted,AbortReason},
+ case mod_loc(AbortLoc) of
+ {FwMod,FwFunc,framework} ->
+ %% abort during framework call
+ spawn_fw_call(FwMod,FwFunc,Pid,
+ {framework_error,ErrorMsg},
+ unknown,self(),Comment),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ Comment,undefined);
+ Loc1 ->
+ {Mod,Func} = get_mf(Loc1),
+ %% call end_per_testcase on a separate process, only so
+ %% that the user has a chance to clean up after init_per_testcase,
+ %% even after abortion
+ NewCurrConf =
+ case CurrConf of
+ {{Mod,Func},Conf} ->
+ TVal = case lists:keysearch(default_timeout,1,Conf) of
+ {value,{default_timeout,Tmo}} -> Tmo;
+ _ -> ?DEFAULT_TIMETRAP_SECS*1000
+ end,
+ EndConfPid =
+ call_end_conf(Mod,Func,Pid,ErrorMsg,
+ Loc1,
+ [{tc_status,{failed,ErrorMsg}}|Conf],
+ TVal),
+ {EndConfPid,{Mod,Func},Conf};
+ _ ->
+ spawn_fw_call(Mod,Func,Pid,ErrorMsg,
+ Loc1,self(),Comment),
+ undefined
+ end,
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,
+ Comment,NewCurrConf)
+ end;
+ killed ->
%% result of an exit(TestCase,kill) call, which is the
- %% only way to abort a testcase process that traps exits
+ %% only way to abort a testcase process that traps exits
%% (see abort_current_testcase)
spawn_fw_call(undefined,undefined,Pid,testcase_aborted_or_killed,
unknown,self(),Comment),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
{fw_error,{FwMod,FwFunc,FwError}} ->
spawn_fw_call(FwMod,FwFunc,Pid,{framework_error,FwError},
unknown,self(),Comment),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
- _ ->
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
+ _Other ->
%% the testcase has terminated because of Reason (e.g. an exit
%% because a linked process failed)
spawn_fw_call(undefined,undefined,Pid,Reason,
unknown,self(),Comment),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment)
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf)
+ end;
+ {EndConfPid,{call_end_conf,Data,_Result}} ->
+ case CurrConf of
+ {EndConfPid,{Mod,Func},_Conf} ->
+ {_Mod,_Func,TCPid,TCExitReason,Loc} = Data,
+ spawn_fw_call(Mod,Func,TCPid,TCExitReason,Loc,self(),Comment),
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,undefined);
+ _ ->
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf)
end;
{_FwCallPid,fw_notify_done,RetVal} ->
%% the framework has been notified, we're finished
- run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment,undefined);
{'EXIT',_FwCallPid,{fw_notify_done,Func,Error}} ->
%% a framework function failed
CB = os:getenv("TEST_SERVER_FRAMEWORK"),
Loc = case CB of
- false ->
+ false ->
{test_server,Func};
- _ ->
+ _ ->
{list_to_atom(CB),Func}
end,
RetVal = {died,{framework_error,Loc,Error},Loc,"Framework error"},
- run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,{true,RetVal},Comment,undefined);
{failed,File,Line} ->
- put(test_server_detected_fail,
+ put(test_server_detected_fail,
[{File, Line}| get(test_server_detected_fail)]),
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
_Other when not is_tuple(_Other) ->
%% ignore anything not generated by test server
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment);
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf);
_Other when element(1, _Other) /= 'EXIT',
element(1, _Other) /= started,
element(1, _Other) /= finished,
element(1, _Other) /= print ->
%% ignore anything not generated by test server
- run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment)
+ run_test_case_msgloop(Ref,Pid,CaptureStdout,Terminate,Comment,CurrConf)
after Timeout ->
ReturnValue
end.
@@ -819,12 +897,43 @@ run_test_case_msgloop_io(ReplyAs,CaptureStdout,Msg,From,Func) ->
output(Msg,Sender) ->
local_or_remote_apply({test_server_ctrl,output,[Msg,Sender]}).
+call_end_conf(Mod,Func,TCPid,TCExitReason,Loc,Conf,TVal) ->
+ Starter = self(),
+ Data = {Mod,Func,TCPid,TCExitReason,Loc},
+ EndConfProc =
+ fun() ->
+ Supervisor = self(),
+ EndConfApply =
+ fun() ->
+ case catch apply(Mod,end_per_testcase,[Func,Conf]) of
+ {'EXIT',Why} ->
+ group_leader() ! {printout,12,
+ "ERROR! ~p:end_per_testcase(~p, ~p)"
+ " crashed!\n\tReason: ~p\n",
+ [Mod,Func,Conf,Why]};
+ _ ->
+ ok
+ end,
+ Supervisor ! {self(),end_conf}
+ end,
+ Pid = spawn_link(EndConfApply),
+ receive
+ {Pid,end_conf} ->
+ Starter ! {self(),{call_end_conf,Data,ok}};
+ {'EXIT',Pid,Reason} ->
+ Starter ! {self(),{call_end_conf,Data,{error,Reason}}}
+ after TVal ->
+ Starter ! {self(),{call_end_conf,Data,{error,timeout}}}
+ end
+ end,
+ spawn_link(EndConfProc).
+
spawn_fw_call(Mod,{init_per_testcase,Func},Pid,{timetrap_timeout,TVal}=Why,
Loc,SendTo,Comment) ->
FwCall =
fun() ->
Skip = {skip,{failed,{Mod,init_per_testcase,Why}}},
- %% if init_per_testcase fails, the test case
+ %% if init_per_testcase fails, the test case
%% should be skipped
case catch test_server_sup:framework_call(
end_tc,[?pl2a(Mod),Func,{Pid,Skip,[[]]}]) of
@@ -838,6 +947,7 @@ spawn_fw_call(Mod,{init_per_testcase,Func},Pid,{timetrap_timeout,TVal}=Why,
{TVal/1000,Skip,Loc,[],Comment}}
end,
spawn_link(FwCall);
+
spawn_fw_call(Mod,{end_per_testcase,Func},Pid,{timetrap_timeout,TVal}=Why,
Loc,SendTo,_Comment) ->
FwCall =
@@ -869,7 +979,7 @@ spawn_fw_call(FwMod,FwFunc,_Pid,{framework_error,FwError},_,SendTo,_Comment) ->
fun() ->
test_server_sup:framework_call(report, [framework_error,
{{FwMod,FwFunc},FwError}]),
- Comment =
+ Comment =
lists:flatten(
io_lib:format("<font color=\"red\">"
"WARNING! ~w:~w failed!</font>", [FwMod,FwFunc])),
@@ -953,9 +1063,10 @@ job_proxy_msgloop() ->
%% A test case is known to have failed if it returns {'EXIT', _} tuple,
%% or sends a message {failed, File, Line} to it's group_leader
-run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit,
- MultiplyTimetrap, TCCallback) ->
- put(test_server_multiply_timetraps,MultiplyTimetrap),
+run_test_case_eval(Mod, Func, Args0, Name, Ref, RunInit,
+ TimetrapData, TCCallback) ->
+ put(test_server_multiply_timetraps,TimetrapData),
+
{{Time,Value},Loc,Opts} =
case test_server_sup:framework_call(init_tc,[?pl2a(Mod),Func,Args0],
{ok,Args0}) of
@@ -1004,6 +1115,8 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
put(test_server_init_or_end_conf,undefined),
%% call user callback function if defined
NewConf1 = user_callback(TCCallback, Mod, Func, init, NewConf),
+ %% save current state in controller loop
+ group_leader() ! {set_curr_conf,{{Mod,Func},NewConf1}},
put(test_server_loc, {Mod,Func}),
%% execute the test case
{{T,Return},Loc} = {ts_tc(Mod, Func, [NewConf1]),get_loc()},
@@ -1025,6 +1138,8 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
_ ->
{[{tc_status,ok}|NewConf1],Return,ok}
end,
+ %% clear current state in controller loop
+ group_leader() ! {set_curr_conf,undefined},
%% call user callback function if defined
EndConf1 = user_callback(TCCallback, Mod, Func, 'end', EndConf),
{FWReturn1,TSReturn1,EndConf2} =
@@ -1036,9 +1151,10 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
{{error,ReasonToFail},{failed,ReasonToFail},EndConf1};
{failed,{_,end_per_testcase,_}} = Failure -> % unexpected termination
{Failure,TSReturn,EndConf1};
- _ ->
+ _ ->
{FWReturn,TSReturn,EndConf1}
end,
+ put(test_server_init_or_end_conf,undefined),
case test_server_sup:framework_call(end_tc, [?pl2a(Mod), Func,
{FWReturn1,[EndConf2]}]) of
{fail,Reason} ->
@@ -1067,7 +1183,7 @@ run_test_case_eval1(Mod, Func, Args, Name, RunInit, TCCallback) ->
{{T,Return2},Loc,Opts}
end.
-%% the return value is a list and we have to check if it contains
+%% the return value is a list and we have to check if it contains
%% the result of an end conf case or if it's a Config list
process_return_val([Return], M,F,A, Loc, Final) when is_list(Return) ->
ReturnTags = [skip,skip_and_save,save_config,comment,return_group_result],
@@ -1090,16 +1206,20 @@ process_return_val([Return], M,F,A, Loc, Final) when is_list(Return) ->
process_return_val(Return, M,F,A, Loc, Final) ->
process_return_val1(Return, M,F,A, Loc, Final, []).
-process_return_val1([Failed={E,TCError}|_], M,F,A=[Args], Loc, _, SaveOpts) when E=='EXIT';
+process_return_val1([Failed={E,TCError}|_], M,F,A=[Args], Loc, _, SaveOpts) when E=='EXIT';
E==failed ->
fw_error_notify(M,F,A, TCError, mod_loc(Loc)),
- test_server_sup:framework_call(end_tc,
- [?pl2a(M),F,{{error,TCError},
- [[{tc_status,{failed,TCError}}|Args]]}]),
- {Failed,SaveOpts};
-process_return_val1([SaveCfg={save_config,_}|Opts], M,F,[Args], Loc, Final, SaveOpts) ->
+ case test_server_sup:framework_call(end_tc,
+ [?pl2a(M),F,{{error,TCError},
+ [[{tc_status,{failed,TCError}}|Args]]}]) of
+ {fail,FWReason} ->
+ {{failed,FWReason},SaveOpts};
+ _ ->
+ {Failed,SaveOpts}
+ end;
+process_return_val1([SaveCfg={save_config,_}|Opts], M,F,[Args], Loc, Final, SaveOpts) ->
process_return_val1(Opts, M,F,[[SaveCfg|Args]], Loc, Final, SaveOpts);
-process_return_val1([{skip_and_save,Why,SaveCfg}|Opts], M,F,[Args], Loc, _, SaveOpts) ->
+process_return_val1([{skip_and_save,Why,SaveCfg}|Opts], M,F,[Args], Loc, _, SaveOpts) ->
process_return_val1(Opts, M,F,[[{save_config,SaveCfg}|Args]], Loc, {skip,Why}, SaveOpts);
process_return_val1([GR={return_group_result,_}|Opts], M,F,A, Loc, Final, SaveOpts) ->
process_return_val1(Opts, M,F,A, Loc, Final, [GR|SaveOpts]);
@@ -1109,8 +1229,12 @@ process_return_val1([RetVal={Tag,_}|Opts], M,F,A, Loc, _, SaveOpts) when Tag==sk
process_return_val1([_|Opts], M,F,A, Loc, Final, SaveOpts) ->
process_return_val1(Opts, M,F,A, Loc, Final, SaveOpts);
process_return_val1([], M,F,A, _Loc, Final, SaveOpts) ->
- test_server_sup:framework_call(end_tc, [?pl2a(M),F,{Final,A}]),
- {Final,lists:reverse(SaveOpts)}.
+ case test_server_sup:framework_call(end_tc, [?pl2a(M),F,{Final,A}]) of
+ {fail,FWReason} ->
+ {{failed,FWReason},SaveOpts};
+ _ ->
+ {Final,lists:reverse(SaveOpts)}
+ end.
user_callback(undefined, _, _, _, Args) ->
Args;
@@ -1138,7 +1262,7 @@ init_per_testcase(Mod, Func, Args) ->
case erlang:function_exported(Mod,init_per_testcase,2) of
true ->
case catch my_apply(Mod, init_per_testcase, [Func|Args]) of
- {'$test_server_ok',{Skip,Reason}} when Skip==skip;
+ {'$test_server_ok',{Skip,Reason}} when Skip==skip;
Skip==skipped ->
{skip,Reason};
{'$test_server_ok',Res={skip_and_save,_,_}} ->
@@ -1149,31 +1273,31 @@ init_per_testcase(Mod, Func, Args) ->
[] ->
{ok,NewConf};
Bad ->
- group_leader() ! {printout,12,
+ group_leader() ! {printout,12,
"ERROR! init_per_testcase has returned "
- "bad elements in Config: ~p\n",[Bad]},
+ "bad elements in Config: ~p\n",[Bad]},
{skip,{failed,{Mod,init_per_testcase,bad_return}}}
end;
{'$test_server_ok',_Other} ->
- group_leader() ! {printout,12,
+ group_leader() ! {printout,12,
"ERROR! init_per_testcase did not return "
- "a Config list.\n",[]},
+ "a Config list.\n",[]},
{skip,{failed,{Mod,init_per_testcase,bad_return}}};
{'EXIT',Reason} ->
Line = get_loc(),
FormattedLoc = test_server_sup:format_loc(mod_loc(Line)),
- group_leader() ! {printout,12,
+ group_leader() ! {printout,12,
"ERROR! init_per_testcase crashed!\n"
"\tLocation: ~s\n\tReason: ~p\n",
- [FormattedLoc,Reason]},
+ [FormattedLoc,Reason]},
{skip,{failed,{Mod,init_per_testcase,Reason}}};
Other ->
Line = get_loc(),
FormattedLoc = test_server_sup:format_loc(mod_loc(Line)),
- group_leader() ! {printout,12,
+ group_leader() ! {printout,12,
"ERROR! init_per_testcase thrown!\n"
"\tLocation: ~s\n\tReason: ~p\n",
- [FormattedLoc, Other]},
+ [FormattedLoc, Other]},
{skip,{failed,{Mod,init_per_testcase,Other}}}
end;
false ->
@@ -1182,7 +1306,7 @@ init_per_testcase(Mod, Func, Args) ->
[Config] = Args,
{ok, Config}
end.
-
+
end_per_testcase(Mod, Func, Conf) ->
case erlang:function_exported(Mod,end_per_testcase,2) of
true ->
@@ -1211,11 +1335,11 @@ do_end_per_testcase(Mod,EndFunc,Func,Conf) ->
comment(io_lib:format("<font color=\"red\">"
"WARNING: ~w crashed!"
"</font>\n",[EndFunc])),
- group_leader() ! {printout,12,
+ group_leader() ! {printout,12,
"WARNING: ~w crashed!\n"
"Reason: ~p\n"
"Line: ~s\n",
- [EndFunc, Reason,
+ [EndFunc, Reason,
test_server_sup:format_loc(
mod_loc(get_loc()))]},
{failed,{Mod,end_per_testcase,Why}};
@@ -1223,13 +1347,13 @@ do_end_per_testcase(Mod,EndFunc,Func,Conf) ->
comment(io_lib:format("<font color=\"red\">"
"WARNING: ~w thrown!"
"</font>\n",[EndFunc])),
- group_leader() ! {printout,12,
+ group_leader() ! {printout,12,
"WARNING: ~w thrown!\n"
"Reason: ~p\n"
"Line: ~s\n",
- [EndFunc, Other,
+ [EndFunc, Other,
test_server_sup:format_loc(
- mod_loc(get_loc()))]},
+ mod_loc(get_loc()))]},
{failed,{Mod,end_per_testcase,Other}}
end.
@@ -1254,7 +1378,7 @@ get_mf(_) -> {undefined,undefined}.
mod_loc(Loc) ->
%% handle diff line num versions
- case Loc of
+ case Loc of
[{{_M,_F},_L}|_] ->
[{?pl2a(M),F,L} || {{M,F},L} <- Loc];
[{_M,_F}|_] ->
@@ -1286,7 +1410,7 @@ fw_error_notify(Mod, Func, Args, Error, Loc) ->
%% Args = [term()]
%%
%% Just like io:format, except that depending on the Detail value, the output
-%% is directed to console, major and/or minor log files.
+%% is directed to console, major and/or minor log files.
print(Detail,Format,Args) ->
local_or_remote_apply({test_server_ctrl,print,[Detail,Format,Args]}).
@@ -1296,11 +1420,11 @@ print(Detail,Format,Args) ->
%%
%% Prints Leader followed by a time stamp (date and time). Depending on
%% the Detail value, the output is directed to console, major and/or minor
-%% log files.
+%% log files.
print_timestamp(Detail,Leader) ->
local_or_remote_apply({test_server_ctrl,print_timestamp,[Detail,Leader]}).
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% lookup_config(Key,Config) -> {value,{Key,Value}} | undefined
@@ -1326,11 +1450,11 @@ ts_tc(M, F, A) ->
Val = (catch my_apply(M, F, A)),
After = erlang:now(),
Result = case Val of
- {'$test_server_ok', R} ->
+ {'$test_server_ok', R} ->
R; % test case ok
- {'EXIT',_Reason} = R ->
+ {'EXIT',_Reason} = R ->
R; % test case crashed
- Other ->
+ Other ->
{failed, {thrown,Other}} % test case was thrown
end,
Elapsed =
@@ -1352,7 +1476,7 @@ my_apply(M, F, A) ->
%% in an attempt to keep this modules small (yeah, right!) %%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
unicode_to_latin1(Chars) when is_list(Chars); is_binary(Chars) ->
- lists:flatten(
+ lists:flatten(
[ case X of
High when High > 255 ->
io_lib:format("\\{~.8B}",[X]);
@@ -1460,6 +1584,44 @@ sleep(MSecs) ->
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% adjusted_sleep(Time) -> ok
+%% Time = integer() | float() | infinity
+%%
+%% Sleeps the specified number of milliseconds, multiplied by the
+%% 'multiply_timetraps' value (if set) and possibly also automatically scaled
+%% up if 'scale_timetraps' is set to true (which is default).
+%% This function also accepts floating point numbers (which are truncated) and
+%% the atom 'infinity'.
+adjusted_sleep(infinity) ->
+ receive
+ after infinity ->
+ ok
+ end;
+adjusted_sleep(MSecs) ->
+ {Multiplier,ScaleFactor} =
+ case test_server_ctrl:get_timetrap_parameters() of
+ {undefined,undefined} ->
+ {1,1};
+ {undefined,false} ->
+ {1,1};
+ {undefined,true} ->
+ {1,timetrap_scale_factor()};
+ {infinity,_} ->
+ {infinity,1};
+ {Mult,undefined} ->
+ {Mult,1};
+ {Mult,false} ->
+ {Mult,1};
+ {Mult,true} ->
+ {Mult,timetrap_scale_factor()}
+ end,
+ receive
+ after trunc(MSecs*Multiplier*ScaleFactor) ->
+ ok
+ end,
+ ok.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% fail(Reason) -> exit({suite_failed,Reason})
%%
%% Immediately calls exit. Included because test suites are easier
@@ -1509,9 +1671,9 @@ break(Comment) ->
receive continue -> ok end.
spawn_break_process(Pid) ->
- spawn(fun() ->
+ spawn(fun() ->
register(test_server_break_process,self()),
- receive
+ receive
continue -> continue(Pid);
cancel -> ok
end
@@ -1561,20 +1723,21 @@ timetrap_scale_factor() ->
%% timetrap(Timeout) -> Handle
%% Handle = term()
%%
-%% Creates a time trap, that will kill the calling process if the
+%% Creates a time trap, that will kill the calling process if the
%% trap is not cancelled with timetrap_cancel/1, within Timeout milliseconds.
-
timetrap(Timeout0) ->
Timeout = time_ms(Timeout0),
cancel_default_timetrap(),
case get(test_server_multiply_timetraps) of
- undefined -> timetrap1(Timeout);
- infinity -> infinity;
- Int -> timetrap1(Timeout*Int)
+ undefined -> timetrap1(Timeout, true);
+ {undefined,false} -> timetrap1(Timeout, false);
+ {undefined,_} -> timetrap1(Timeout, true);
+ {infinity,_} -> infinity;
+ {Int,Scale} -> timetrap1(Timeout*Int, Scale)
end.
-timetrap1(Timeout) ->
- Ref = spawn_link(test_server_sup,timetrap,[Timeout,self()]),
+timetrap1(Timeout, Scale) ->
+ Ref = spawn_link(test_server_sup,timetrap,[Timeout,Scale,self()]),
case get(test_server_timetraps) of
undefined -> put(test_server_timetraps,[Ref]);
List -> put(test_server_timetraps,[Ref|List])
@@ -1582,7 +1745,6 @@ timetrap1(Timeout) ->
Ref.
ensure_timetrap(Config) ->
- %format("ensure_timetrap:~p~n",[Config]),
case get(test_server_timetraps) of
[_|_] ->
ok;
@@ -1623,7 +1785,7 @@ cancel_default_timetrap() ->
time_ms({hours,N}) -> hours(N);
time_ms({minutes,N}) -> minutes(N);
time_ms({seconds,N}) -> seconds(N);
-time_ms({Other,_N}) ->
+time_ms({Other,_N}) ->
format("=== ERROR: Invalid time specification: ~p. "
"Should be seconds, minutes, or hours.~n", [Other]),
exit({invalid_time_spec,Other});
@@ -1763,21 +1925,21 @@ call_crash(Time,Crash,M,F,A) ->
%% Slave and Peer:
%% {remote, true} - Start the node on a remote host. If not specified,
%% the node will be started on the local host (with
-%% some exceptions, as for the case of VxWorks and OSE,
+%% some exceptions, for instance VxWorks,
%% where all nodes are started on a remote host).
%% {args, Arguments} - Arguments passed directly to the node.
%% {cleanup, false} - Nodes started with this option will not be killed
%% by the test server after completion of the test case
%% Therefore it is IMPORTANT that the USER terminates
%% the node!!
-%% {erl, ReleaseList} - Use an Erlang emulator determined by ReleaseList
-%% when starting nodes, instead of the same emulator
+%% {erl, ReleaseList} - Use an Erlang emulator determined by ReleaseList
+%% when starting nodes, instead of the same emulator
%% as the test server is running. ReleaseList is a list
-%% of specifiers, where a specifier is either
-%% {release, Rel}, {prog, Prog}, or 'this'. Rel is
-%% either the name of a release, e.g., "r7a" or
-%% 'latest'. 'this' means using the same emulator as
-%% the test server. Prog is the name of an emulator
+%% of specifiers, where a specifier is either
+%% {release, Rel}, {prog, Prog}, or 'this'. Rel is
+%% either the name of a release, e.g., "r7a" or
+%% 'latest'. 'this' means using the same emulator as
+%% the test server. Prog is the name of an emulator
%% executable. If the list has more than one element,
%% one of them is picked randomly. (Only
%% works on Solaris and Linux, and the test
@@ -1792,13 +1954,13 @@ call_crash(Time,Crash,M,F,A) ->
%% peer nodes.
%% Note that slave nodes always act as if they had
%% fail_on_error==false.
-%%
+%%
start_node(Name, Type, Options) ->
lists:foreach(
- fun(N) ->
+ fun(N) ->
case firstname(N) of
- Name ->
+ Name ->
format("=== WARNING: Trying to start node \'~w\' when node"
" with same first name exists: ~w", [Name, N]);
_other -> ok
@@ -1817,19 +1979,19 @@ start_node(Name, Type, Options) ->
%% Cannot run cover on shielded node or on a node started
%% by a shielded node.
Cover = case is_cover() of
- true ->
+ true ->
not is_shielded(Name) andalso same_version(Node);
- false ->
+ false ->
false
end,
net_adm:ping(Node),
case Cover of
- true ->
+ true ->
Sticky = unstick_all_sticky(Node),
cover:start(Node),
stick_all_sticky(Node,Sticky);
- _ ->
+ _ ->
ok
end,
{ok,Node};
@@ -1857,7 +2019,7 @@ wait_for_node(Slave) ->
self(),
{test_server_ctrl,wait_for_node,[Slave]}},
receive {sync_result,R} -> R end.
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% stop_node(Name) -> true|false
@@ -1867,7 +2029,7 @@ wait_for_node(Slave) ->
stop_node(Slave) ->
Nocover = is_shielded(Slave) orelse not same_version(Slave),
case is_cover() of
- true when not Nocover ->
+ true when not Nocover ->
Sticky = unstick_all_sticky(Slave),
cover:stop(Slave),
stick_all_sticky(Slave,Sticky);
@@ -1895,10 +2057,10 @@ stop_node(Slave) ->
%% with the {cleanup,false} option, or it was started
%% in some other way than test_server:start_node/3
format("=== WARNING: Attempt to stop a nonexisting slavenode (~p)~n"
- "=== Trying to kill it anyway!!!",
+ "=== Trying to kill it anyway!!!",
[Slave]),
case net_adm:ping(Slave)of
- pong ->
+ pong ->
slave:stop(Slave),
true;
pang ->
@@ -1918,7 +2080,7 @@ is_release_available(Release) ->
self(),
{test_server_ctrl,is_release_available,[Release]}},
receive {sync_result,R} -> R end.
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% run_on_shielded_node(Fun, CArgs) -> term()
@@ -1937,7 +2099,7 @@ is_release_available(Release) ->
%%
%% Fun - Function to execute
%% CArg - Extra command line arguments to use when starting
-%% the shielded node.
+%% the shielded node.
%%
%% If Fun is successfully executed, the result is returned.
%%
@@ -2014,14 +2176,8 @@ temp_name(Stem) ->
app_test(App) ->
app_test(App, pedantic).
app_test(App, Mode) ->
- case os:type() of
- {ose,_} ->
- Comment = "Skipping app_test on OSE",
- comment(Comment), % in case user ignores the return value
- {skip,Comment};
- _other ->
- test_server_sup:app_test(App, Mode)
- end.
+ test_server_sup:app_test(App, Mode).
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -2043,8 +2199,8 @@ is_native(Mod) ->
%% The given String will occur in the comment field
%% of the table on the test suite result page. If
%% called several times, only the last comment is
-%% printed.
-%% comment/1 is also overwritten by the return value
+%% printed.
+%% comment/1 is also overwritten by the return value
%% {comment,Comment} or fail/1 (which prints Reason
%% as a comment).
comment(String) ->
@@ -2160,7 +2316,7 @@ purify_new_fds_inuse() ->
{'EXIT', _} -> false;
Inuse when is_integer(Inuse) -> Inuse
end.
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% purify_format(Format, Args) -> ok
%% Format = string()
@@ -2208,9 +2364,9 @@ local_or_remote_apply({M,F,A} = MFA) ->
request(Sock,Request) ->
gen_tcp:send(Sock,<<1,(term_to_binary(Request))/binary>>).
-%%
+%%
%% Generic receive function for communication with host
-%%
+%%
recv(Sock) ->
case gen_tcp:recv(Sock,0) of
{error,closed} ->
diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl
index 4cb5863955..1245c10a01 100644
--- a/lib/test_server/src/test_server_ctrl.erl
+++ b/lib/test_server/src/test_server_ctrl.erl
@@ -27,7 +27,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% MODULE DEPENDENCIES:
-%% HARD TO REMOVE: erlang, lists, io_lib, gen_server, file, io, string,
+%% HARD TO REMOVE: erlang, lists, io_lib, gen_server, file, io, string,
%% code, ets, rpc, gen_tcp, inet, erl_tar, sets,
%% test_server, test_server_sup, test_server_node
%% EASIER TO REMOVE: filename, filelib, lib, re
@@ -36,7 +36,7 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% ARCHITECTURE
-%%
+%%
%% The Erlang Test Server can be run on the target machine (local target)
%% or towards a remote target. The execution flow is mainly the same in
%% both cases, but with a remote target the test cases are (obviously)
@@ -44,11 +44,11 @@
%% socket connections because the host should not be introduced as an
%% additional node in the distributed erlang system in which the test
%% cases are run.
-%%
-%%
+%%
+%%
%% Local Target:
%% =============
-%%
+%%
%% -----
%% | | test_server_ctrl ({global,test_server})
%% ----- (test_server_ctrl.erl)
@@ -62,33 +62,33 @@
%% -----
%% | | CaseProc
%% ----- (test_server.erl)
-%%
-%%
-%%
+%%
+%%
+%%
%% test_server_ctrl is the main process in the system. It is a registered
%% process, and it will always be alive when testing is ongoing.
%% test_server_ctrl initiates testing and monitors JobProc(s).
-%%
-%% When target is local, and Test Server is *not* being used by a framework
-%% application (where it might cause duplicate name problems in a distributed
-%% test environment), the process is globally registered as 'test_server'
+%%
+%% When target is local, and Test Server is *not* being used by a framework
+%% application (where it might cause duplicate name problems in a distributed
+%% test environment), the process is globally registered as 'test_server'
%% to be able to simulate the {global,test_server} process on a remote target.
-%%
-%% JobProc is spawned for each 'job' added to the test_server_ctrl.
+%%
+%% JobProc is spawned for each 'job' added to the test_server_ctrl.
%% A job can mean one test case, one test suite or one spec.
%% JobProc creates and writes logs and presents results from testing.
%% JobProc is the group leader for CaseProc.
-%%
+%%
%% CaseProc is spawned for each test case. It runs the test case and
%% sends results and any other information to its group leader - JobProc.
-%%
-%%
-%%
+%%
+%%
+%%
%% Remote Target:
%% ==============
-%%
+%%
%% HOST TARGET
-%%
+%%
%% ----- MainSock -----
%% test_server_ctrl | |- - - - - - -| | {global,test_server}
%% (test_server_ctrl.erl) ----- ----- (test_server.erl)
@@ -102,36 +102,36 @@
%% -----
%% | | CaseProc
%% ----- (test_server.erl)
-%%
-%%
-%%
-%%
+%%
+%%
+%%
+%%
%% A separate test_server process only exists when target is remote. It
%% is then the main process on target. It is started when test_server_ctrl
%% is started, and a socket connection is established between
%% test_server_ctrl and test_server. The following information can be sent
%% over MainSock:
-%%
+%%
%% HOST TARGET
%% -> {target_info, TargetInfo} (during initiation)
%% <- {job_proc_killed,Name,Reason} (if a JobProcT dies unexpectedly)
%% -> {job,Port,Name} (to start a new JobProcT)
-%%
-%%
+%%
+%%
%% When target is remote, JobProc is split into to processes: JobProcH
%% executing on Host and JobProcT executing on Target. (The two processes
%% execute the same code as JobProc does when target is local.) JobProcH
%% and JobProcT communicates over a socket connection. The following
%% information can be sent over JobSock:
-%%
+%%
%% HOST TARGET
%% -> {test_case, Case} To start a new test case
%% -> {beam,Mod} .beam file as binary to be loaded
%% on target, e.g. a test suite
%% -> {datadir,Tarfile} Content of the datadir for a test suite
%% <- {apply,MFA} MFA to be applied on host, ignore return;
-%% (apply is used for printing information in
-%% log or console)
+%% (apply is used for printing information in
+%% log or console)
%% <- {sync_apply,MFA} MFA to be applied on host, wait for return
%% (used for starting and stopping slave nodes)
%% -> {sync_apply,MFA} MFA to be applied on target, wait for return
@@ -141,7 +141,7 @@
%% <- {crash_dumps,Tarfile} When a test case is finished
%% -> job_done When a job is finished
%% <- {privdir,Privdir} When a job is finished
-%%
+%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -161,7 +161,7 @@
abort_current_testcase/1, abort/0]).
-export([start_get_totals/1, stop_get_totals/0]).
-export([get_levels/0, set_levels/3]).
--export([multiply_timetraps/1]).
+-export([multiply_timetraps/1, scale_timetraps/1, get_timetrap_parameters/0]).
-export([cover/2, cover/3, cover/7,
cross_cover_analyse/1, cross_cover_analyse/2, trc/1, stop_trace/0]).
-export([testcase_callback/1]).
@@ -207,13 +207,15 @@
-define(pl2a(M), test_server_sup:package_atom(M)).
-define(void_fun, fun() -> ok end).
--define(mod_result(X), if X == skip -> skipped;
- X == auto_skip -> skipped;
+-define(mod_result(X), if X == skip -> skipped;
+ X == auto_skip -> skipped;
true -> X end).
--record(state,{jobs=[],levels={1,19,10},multiply_timetraps=1,finish=false,
+-record(state,{jobs=[],levels={1,19,10},
+ multiply_timetraps=1,scale_timetraps=true,
+ finish=false,
target_info, trc=false, cover=false, wait_for_node=[],
- testcase_callback=undefined, idle_notify=[],
+ testcase_callback=undefined, idle_notify=[],
get_totals=false, random_seed=undefined}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -222,14 +224,14 @@
add_dir(Name, Job=[Dir|_Dirs]) when is_list(Dir) ->
add_job(cast_to_list(Name),
lists:map(fun(D)-> {dir,cast_to_list(D)} end, Job));
-add_dir(Name, Dir) ->
+add_dir(Name, Dir) ->
add_job(cast_to_list(Name), {dir,cast_to_list(Dir)}).
add_dir(Name, Job=[Dir|_Dirs], Pattern) when is_list(Dir) ->
add_job(cast_to_list(Name),
lists:map(fun(D)-> {dir,cast_to_list(D),
cast_to_list(Pattern)} end, Job));
-add_dir(Name, Dir, Pattern) ->
+add_dir(Name, Dir, Pattern) ->
add_job(cast_to_list(Name), {dir,cast_to_list(Dir),cast_to_list(Pattern)}).
add_module(Mod) when is_atom(Mod) ->
@@ -256,14 +258,14 @@ add_spec(Spec) ->
false -> {error,nofile}
end.
-%% This version of the interface is to be used if there are
+%% This version of the interface is to be used if there are
%% suites or cases that should be skipped.
add_dir_with_skip(Name, Job=[Dir|_Dirs], Skip) when is_list(Dir) ->
add_job(cast_to_list(Name),
lists:map(fun(D)-> {dir,cast_to_list(D)} end, Job),
Skip);
-add_dir_with_skip(Name, Dir, Skip) ->
+add_dir_with_skip(Name, Dir, Skip) ->
add_job(cast_to_list(Name), {dir,cast_to_list(Dir)}, Skip).
add_dir_with_skip(Name, Job=[Dir|_Dirs], Pattern, Skip) when is_list(Dir) ->
@@ -271,7 +273,7 @@ add_dir_with_skip(Name, Job=[Dir|_Dirs], Pattern, Skip) when is_list(Dir) ->
lists:map(fun(D)-> {dir,cast_to_list(D),
cast_to_list(Pattern)} end, Job),
Skip);
-add_dir_with_skip(Name, Dir, Pattern, Skip) ->
+add_dir_with_skip(Name, Dir, Pattern, Skip) ->
add_job(cast_to_list(Name),
{dir,cast_to_list(Dir),cast_to_list(Pattern)}, Skip).
@@ -295,15 +297,14 @@ add_cases_with_skip(Name, Mod, Cases, Skip) when is_atom(Mod), is_list(Cases) ->
add_tests_with_skip(LogDir, Tests, Skip) ->
add_job(LogDir,
- lists:map(fun({Dir,all,all}) ->
+ lists:map(fun({Dir,all,all}) ->
{Dir,{dir,Dir}};
- ({Dir,Mods,all}) ->
+ ({Dir,Mods,all}) ->
{Dir,lists:map(fun(M) -> {M,all} end, Mods)};
({Dir,Mod,Cases}) ->
{Dir,{Mod,Cases}}
end, Tests),
Skip).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% COMMAND LINE INTERFACE
@@ -315,7 +316,7 @@ parse_cmd_line(['SPEC',Spec|Cmds], SpecList, Names, Param, Trc, Cov, TCCB) ->
case file:consult(Spec) of
{ok, TermList} ->
Name = filename:rootname(Spec),
- parse_cmd_line(Cmds, TermList++SpecList, [Name|Names], Param,
+ parse_cmd_line(Cmds, TermList++SpecList, [Name|Names], Param,
Trc, Cov, TCCB);
{error,Reason} ->
io:format("Can't open ~s: ~p\n",
@@ -406,7 +407,7 @@ run_test(CommandLine) ->
end,
testcase_callback(TCCB),
add_job(Name, {command_line,SpecList}),
-
+
%% adding of jobs involves file i/o which may take long time
%% when running a nfs mounted file system (VxWorks).
case controller_call(get_target_info) of
@@ -479,6 +480,12 @@ set_levels(Show, Major, Minor) ->
multiply_timetraps(N) ->
controller_call({multiply_timetraps,N}).
+scale_timetraps(Bool) ->
+ controller_call({scale_timetraps,Bool}).
+
+get_timetrap_parameters() ->
+ controller_call(get_timetrap_parameters).
+
trc(TraceFile) ->
controller_call({trace,TraceFile}, 2*?ACCEPT_TIMEOUT).
@@ -551,7 +558,7 @@ controller_call(Arg, Timeout) ->
Other ->
Other
end.
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -617,7 +624,7 @@ contact_main_target(local) ->
case os:getenv("TEST_SERVER_FRAMEWORK") of
false ->
%% Local target! The global test_server process implemented by
- %% test_server.erl will not be started, so we simulate it by
+ %% test_server.erl will not be started, so we simulate it by
%% globally registering this process instead.
global:sync(),
case global:whereis_name(test_server) of
@@ -681,9 +688,9 @@ read_parameters([], Par) when Par#par.type==undefined ->
read_parameters([], Par) when Par#par.target==undefined ->
{error, {missing_mandatory_parameter,target}};
read_parameters([], Par0) ->
- Par =
+ Par =
case {Par0#par.type, Par0#par.master} of
- {ose, undefined} ->
+ {ose, undefined} ->
%% Use this node as master and bootserver for target
%% and slave nodes
Par0#par{master = atom_to_list(node()),
@@ -691,10 +698,10 @@ read_parameters([], Par0) ->
{ose, _Master} ->
%% Master for target and slave nodes was defined in parameterfile
Par0;
- _ ->
+ _ ->
%% Use target as master for slave nodes,
%% (No master is used for target)
- Par0#par{master="test_server@" ++ Par0#par.target}
+ Par0#par{master="test_server@" ++ Par0#par.target}
end,
{ok,Par}.
@@ -708,7 +715,7 @@ naming() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% handle_call(kill_slavenodes, From, State) -> ok
%%
-%% Kill all slave nodes that remain after a test case
+%% Kill all slave nodes that remain after a test case
%% is completed.
%%
handle_call(kill_slavenodes, _From, State) ->
@@ -736,7 +743,7 @@ handle_call(get_hosts, _From, State) ->
{reply, Hosts, State};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% handle_call({add_job,Dir,Name,TopCase,Skip}, _, State) ->
+%% handle_call({add_job,Dir,Name,TopCase,Skip}, _, State) ->
%% ok | {error,Reason}
%%
%% Dir = string()
@@ -760,7 +767,7 @@ handle_call(get_hosts, _From, State) ->
handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) ->
LogDir = Dir ++ ?logdir_ext,
- ExtraTools =
+ ExtraTools =
case State#state.cover of
false -> [];
{App,Analyse} -> [{cover,App,Analyse}]
@@ -776,19 +783,21 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) ->
{spec,SpecName} ->
Pid = spawn_tester(
?MODULE, do_spec,
- [SpecName,State#state.multiply_timetraps],
- LogDir, Name, State#state.levels,
+ [SpecName,{State#state.multiply_timetraps,
+ State#state.scale_timetraps}],
+ LogDir, Name, State#state.levels,
State#state.testcase_callback, ExtraTools1),
NewJobs = [{Name,Pid}|State#state.jobs],
- {reply, ok, State#state{jobs=NewJobs}};
+ {reply, ok, State#state{jobs=NewJobs}};
{command_line,SpecList} ->
Pid = spawn_tester(
?MODULE, do_spec_list,
- [SpecList,State#state.multiply_timetraps],
- LogDir, Name, State#state.levels,
+ [SpecList,{State#state.multiply_timetraps,
+ State#state.scale_timetraps}],
+ LogDir, Name, State#state.levels,
State#state.testcase_callback, ExtraTools1),
NewJobs = [{Name,Pid}|State#state.jobs],
- {reply, ok, State#state{jobs=NewJobs}};
+ {reply, ok, State#state{jobs=NewJobs}};
TopCase ->
case State#state.get_totals of
{CliPid,Fun} ->
@@ -798,10 +807,11 @@ handle_call({add_job,Dir,Name,TopCase,Skip}, _From, State) ->
_ ->
Cfg = make_config([]),
Pid = spawn_tester(
- ?MODULE, do_test_cases,
+ ?MODULE, do_test_cases,
[TopCase,Skip,Cfg,
- State#state.multiply_timetraps],
- LogDir, Name, State#state.levels,
+ {State#state.multiply_timetraps,
+ State#state.scale_timetraps}],
+ LogDir, Name, State#state.levels,
State#state.testcase_callback, ExtraTools1),
NewJobs = [{Name,Pid}|State#state.jobs],
{reply, ok, State#state{jobs=NewJobs}}
@@ -827,7 +837,7 @@ handle_call(jobs, _From, State) ->
%% handle_call({abort_current_testcase,Reason}, _, State) -> Result
%% Reason = term()
%% Result = ok | {error,no_testcase_running}
-%%
+%%
%% Attempts to abort the test case that's currently running.
handle_call({abort_current_testcase,Reason}, _From, State) ->
@@ -855,7 +865,7 @@ handle_call({abort_current_testcase,Reason}, _From, State) ->
handle_call({finish,Fini}, _From, State) ->
case State#state.jobs of
[] ->
- lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end,
+ lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end,
State#state.idle_notify),
State2 = State#state{finish=false},
{stop,shutdown,{ok,self()}, State2};
@@ -878,7 +888,7 @@ handle_call({idle_notify,Fun}, {Cli,_Ref}, State) ->
{reply, {ok,self()}, State};
_ ->
Subscribed = State#state.idle_notify,
- {reply, {ok,self()},
+ {reply, {ok,self()},
State#state{idle_notify=[{Cli,Fun}|Subscribed]}}
end;
@@ -891,7 +901,7 @@ handle_call({idle_notify,Fun}, {Cli,_Ref}, State) ->
handle_call({start_get_totals,Fun}, {Cli,_Ref}, State) ->
{reply, {ok,self()}, State#state{get_totals={Cli,Fun}}};
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% handle_call(stop_get_totals, From, State) -> ok
%%
@@ -942,11 +952,31 @@ handle_call({multiply_timetraps,N}, _From, State) ->
{reply,ok,State#state{multiply_timetraps=N}};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% handle_call({scale_timetraps,Bool}, _, State) -> ok
+%% Bool = true | false
+%%
+%% Specifies if test_server should scale the timetrap value
+%% automatically if e.g. cover is running.
+
+handle_call({scale_timetraps,Bool}, _From, State) ->
+ {reply,ok,State#state{scale_timetraps=Bool}};
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% handle_call(get_timetrap_parameters, _, State) -> {Multiplier,Scale}
+%% Multiplier = integer() | infinity
+%% Scale = true | false
+%%
+%% Returns the parameter values that affect timetraps.
+
+handle_call(get_timetrap_parameters, _From, State) ->
+ {reply,{State#state.multiply_timetraps,State#state.scale_timetraps},State};
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% handle_call({trace,TraceFile}, _, State) -> ok | {error,Reason}
%%
-%% Starts a separate node (trace control node) which
+%% Starts a separate node (trace control node) which
%% starts tracing on target and all slave nodes
-%%
+%%
%% TraceFile is a text file with elements of type
%% {Trace,Mod,TracePattern}.
%% {Trace,Mod,Func,TracePattern}.
@@ -955,10 +985,10 @@ handle_call({multiply_timetraps,N}, _From, State) ->
%% Trace = tp | tpl; local or global call trace
%% Mod,Func = atom(), Arity=integer(); defines what to trace
%% TracePattern = [] | match_spec()
-%%
+%%
%% The 'call' trace flag is set on all processes, and then
%% the given trace patterns are set.
-
+
handle_call({trace,TraceFile}, _From, State=#state{trc=false}) ->
TI = State#state.target_info,
case test_server_node:start_tracer_node(TraceFile, TI) of
@@ -993,7 +1023,7 @@ handle_call({cover,App,Analyse}, _From, State) ->
%% handle_call({testcase_callback,{Mod,Func}}, _, State) -> ok | {error,Reason}
%%
%% Add a callback function that will be called before and after every
-%% test case (on the test case process):
+%% test case (on the test case process):
%%
%% Mod:Func(Suite,TestCase,InitOrEnd,Config)
%%
@@ -1001,9 +1031,9 @@ handle_call({cover,App,Analyse}, _From, State) ->
handle_call({testcase_callback,ModFunc}, _From, State) ->
case ModFunc of
- {Mod,Func} ->
+ {Mod,Func} ->
case code:is_loaded(Mod) of
- {file,_} ->
+ {file,_} ->
ok;
false ->
code:load_file(Mod)
@@ -1065,15 +1095,15 @@ handle_call({start_node, Name, Type, Options}, From, State) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% handle_call({wait_for_node,Node}, _, State) -> ok
%%
-%% Waits for a new node to take contact. Used if
+%% Waits for a new node to take contact. Used if
%% node is started with option {wait,false}
handle_call({wait_for_node, Node}, From, State) ->
- NewWaitList =
+ NewWaitList =
case ets:lookup(slave_tab,Node) of
- [] ->
+ [] ->
[{Node,From}|State#state.wait_for_node];
- _ ->
+ _ ->
gen_server:reply(From,ok),
State#state.wait_for_node
end,
@@ -1086,7 +1116,7 @@ handle_call({wait_for_node, Node}, From, State) ->
%% - the node is really stopped by test_server when this returns.
handle_call({stop_node, Name}, _From, State) ->
- R = test_server_node:stop_node(Name, State#state.target_info),
+ R = test_server_node:stop_node(Name, State#state.target_info),
{reply, R, State};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1112,7 +1142,7 @@ handle_cast({node_started,Node}, State) ->
false -> ok;
Trc -> test_server_node:trace_nodes(Trc, [Node])
end,
- NewWaitList =
+ NewWaitList =
case lists:keysearch(Node,1,State#state.wait_for_node) of
{value,{Node,From}} ->
gen_server:reply(From, ok),
@@ -1128,10 +1158,10 @@ handle_cast({node_started,Node}, State) ->
%% Reason = term()
%%
%% Handles exit messages from linked processes. Only test suites and
-%% possibly a target client are expected to be linked.
+%% possibly a target client are expected to be linked.
%% When a test suite terminates, it is removed from the job queue.
%% If a target client terminates it means that we lost contact with
-%% target. The test_server_ctrl process is terminated, and teminate/2
+%% target. The test_server_ctrl process is terminated, and teminate/2
%% will do the cleanup
handle_info({'EXIT',Pid,Reason}, State) ->
@@ -1139,7 +1169,7 @@ handle_info({'EXIT',Pid,Reason}, State) ->
false ->
TI = State#state.target_info,
case TI#target_info.target_client of
- Pid ->
+ Pid ->
%% The target client died - lost contact with target
{stop,{lost_contact_with_target,Reason},State};
_other ->
@@ -1160,13 +1190,13 @@ handle_info({'EXIT',Pid,Reason}, State) ->
State2 = State#state{jobs=NewJobs},
case NewJobs of
[] ->
- lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end,
+ lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end,
State2#state.idle_notify),
case State2#state.finish of
false ->
{noreply,State2#state{idle_notify=[]}};
_ -> % true | abort
- %% test_server:finish() has been called and
+ %% test_server:finish() has been called and
%% there are no jobs in the job queue =>
%% stop the test_server_ctrl
{stop,shutdown,State2#state{finish=false}}
@@ -1174,7 +1204,7 @@ handle_info({'EXIT',Pid,Reason}, State) ->
_ -> % pending jobs
case State2#state.finish of
abort -> % abort test now!
- lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end,
+ lists:foreach(fun({Cli,Fun}) -> Fun(Cli) end,
State2#state.idle_notify),
{stop,shutdown,State2#state{finish=false}};
_ -> % true | false
@@ -1194,9 +1224,9 @@ handle_info({tcp,_MainSock,<<1,Request/binary>>}, State) ->
case binary_to_term(Request) of
{job_proc_killed,Name,Reason} ->
%% The only purpose of this is to inform the user about what
- %% happened on target.
+ %% happened on target.
%% The local job proc will soon be killed by the closed socket or
- %% because the job is finished. Then the above clause ('EXIT') will
+ %% because the job is finished. Then the above clause ('EXIT') will
%% handle the problem.
io:format("Suite ~s was killed on remote target with reason"
" ~p\n", [Name,Reason]);
@@ -1204,13 +1234,13 @@ handle_info({tcp,_MainSock,<<1,Request/binary>>}, State) ->
ignore
end,
{noreply,State};
-
+
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% handle_info({tcp_closed,Sock}, State)
%%
%% A Socket was closed. This indicates that a node died.
-%% This can be
+%% This can be
%% *Target node (if remote)
%% *Slave or peer node started by a test suite
%% *Trace controll node
@@ -1221,10 +1251,10 @@ handle_info({tcp_closed,Sock}, State=#state{trc=Sock}) ->
{noreply,State#state{trc=false}};
handle_info({tcp_closed,Sock}, State) ->
case test_server_node:nodedown(Sock,State#state.target_info) of
- target_died ->
+ target_died ->
%% terminate/2 will do the cleanup
{stop,target_died,State};
- _ ->
+ _ ->
{noreply,State}
end;
@@ -1260,7 +1290,7 @@ kill_all_jobs([]) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% spawn_tester(Mod, Func, Args, Dir, Name, Levels,
+%% spawn_tester(Mod, Func, Args, Dir, Name, Levels,
%% TestCaseCallback, ExtraTools) -> Pid
%% Mod = atom()
%% Func = atom()
@@ -1268,23 +1298,23 @@ kill_all_jobs([]) ->
%% Dir = string()
%% Name = string()
%% Levels = {integer(),integer(),integer()}
-%% TestCaseCallback = {CBMod,CBFunc} | undefined
+%% TestCaseCallback = {CBMod,CBFunc} | undefined
%% ExtraTools = [ExtraTool,...]
%% ExtraTool = CoverInfo | TraceInfo | RandomSeed
%%
%% Spawns a test suite execute-process, just an ordinary spawn, except
%% that it will set a lot of dictionary information before starting the
%% named function. Also, the execution is timed and protected by a catch.
-%% When the named function is done executing, a summary of the results
+%% When the named function is done executing, a summary of the results
%% is printed to the log files.
spawn_tester(Mod, Func, Args, Dir, Name, Levels, TCCallback, ExtraTools) ->
spawn_link(
- fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels,
- TCCallback, ExtraTools)
+ fun() -> init_tester(Mod, Func, Args, Dir, Name, Levels,
+ TCCallback, ExtraTools)
end).
-init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev},
+init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev},
TCCallback, ExtraTools) ->
process_flag(trap_exit, true),
put(test_server_name, Name),
@@ -1324,7 +1354,7 @@ init_tester(Mod, Func, Args, Dir, Name, {SumLev,MajLev,MinLev},
{Skipped,_} -> {Skipped,io_lib:format(", ~p Skipped", [Skipped])}
end,
OkN = get(test_server_ok),
- FailedN = get(test_server_failed),
+ FailedN = get(test_server_failed),
print(html,"<tr><td></td><td><b>TOTAL</b></td><td></td><td></td>"
"<td>~.3fs</td><td><b>~s</b></td><td>~p Ok, ~p Failed~s of ~p</td></tr>\n",
[Time,SuccessStr,OkN,FailedN,SkipStr,OkN+FailedN+SkippedN]).
@@ -1338,9 +1368,9 @@ ts_tc(M, F, A) ->
{Elapsed,Val}.
elapsed_time(Before, After) ->
- (element(1,After)*1000000000000 +
+ (element(1,After)*1000000000000 +
element(2,After)*1000000 + element(3,After)) -
- (element(1,Before)*1000000000000 +
+ (element(1,Before)*1000000000000 +
element(2,Before)*1000000 + element(3,Before)).
start_extra_tools(ExtraTools) ->
@@ -1378,28 +1408,32 @@ stop_extra_tools([], _) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% do_spec(SpecName, MultiplyTimetrap) -> {error,Reason} | exit(Result)
+%% do_spec(SpecName, TimetrapSpec) -> {error,Reason} | exit(Result)
%% SpecName = string()
+%% TimetrapSpec = MultiplyTimetrap | {MultiplyTimetrap,ScaleTimetrap}
%% MultiplyTimetrap = integer() | infinity
+%% ScaleTimetrap = bool()
%%
%% Reads the named test suite specification file, and executes it.
%%
-%% This function is meant to be called by a process created by
+%% This function is meant to be called by a process created by
%% spawn_tester/7, which sets up some necessary dictionary values.
-do_spec(SpecName, MultiplyTimetrap) when is_list(SpecName) ->
+do_spec(SpecName, TimetrapSpec) when is_list(SpecName) ->
case file:consult(SpecName) of
{ok,TermList} ->
- do_spec_list(TermList,MultiplyTimetrap);
+ do_spec_list(TermList,TimetrapSpec);
{error,Reason} ->
io:format("Can't open ~s: ~p\n", [SpecName,Reason]),
{error,{cant_open_spec,Reason}}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% do_spec_list(TermList) -> exit(Result)
+%% do_spec_list(TermList, TimetrapSpec) -> exit(Result)
%% TermList = [term()|...]
+%% TimetrapSpec = MultiplyTimetrap | {MultiplyTimetrap,ScaleTimetrap}
%% MultiplyTimetrap = integer() | infinity
+%% ScaleTimetrap = bool()
%%
%% Executes a list of test suite specification commands. The following
%% commands are available, and may occur zero or more times (if several,
@@ -1422,21 +1456,21 @@ do_spec(SpecName, MultiplyTimetrap) when is_list(SpecName) ->
%% nodenames will be generated from the local host.
%%
%% {hosts, Hosts} Specifies a list of available hosts on which to start
-%% slave nodes. It is used when the {remote, true} option is given to the
+%% slave nodes. It is used when the {remote, true} option is given to the
%% test_server:start_node/3 function. Also, if {require_nodenames, Num} is
-%% contained in the TermList, the generated nodenames will be spread over
+%% contained in the TermList, the generated nodenames will be spread over
%% all hosts given in this Hosts list. The hostnames are given as atoms or
%% strings.
-%%
+%%
%% {diskless, true}</c></tag> is kept for backwards compatiblilty and
%% should not be used. Use a configuration test case instead.
-%%
-%% This function is meant to be called by a process created by
+%%
+%% This function is meant to be called by a process created by
%% spawn_tester/7, which sets up some necessary dictionary values.
-do_spec_list(TermList0, MultiplyTimetrap) ->
+do_spec_list(TermList0, TimetrapSpec) ->
Nodes = [],
- TermList =
+ TermList =
case lists:keysearch(hosts, 1, TermList0) of
{value, {hosts, Hosts0}} ->
Hosts = lists:map(fun(H) -> cast_to_list(H) end, Hosts0),
@@ -1447,7 +1481,7 @@ do_spec_list(TermList0, MultiplyTimetrap) ->
end,
DefaultConfig = make_config([{nodes,Nodes}]),
{TopCases,SkipList,Config} = do_spec_terms(TermList, [], [], DefaultConfig),
- do_test_cases(TopCases, SkipList, Config, MultiplyTimetrap).
+ do_test_cases(TopCases, SkipList, Config, TimetrapSpec).
do_spec_terms([], TopCases, SkipList, Config) ->
{TopCases,SkipList,Config};
@@ -1470,21 +1504,21 @@ do_spec_terms([{default_timeout,Tmo}|Terms], TopCases, SkipList, Config) ->
do_spec_terms([{require_nodenames,NumNames}|Terms], TopCases, SkipList, Config) ->
NodeNames0=generate_nodenames(NumNames),
NodeNames=lists:delete([], NodeNames0),
- do_spec_terms(Terms, TopCases, SkipList,
+ do_spec_terms(Terms, TopCases, SkipList,
update_config(Config, {nodenames,NodeNames}));
do_spec_terms([Other|Terms], TopCases, SkipList, Config) ->
io:format("** WARNING: Spec file contains unknown directive ~p\n",
[Other]),
do_spec_terms(Terms, TopCases, SkipList, Config).
-
+
generate_nodenames(Num) ->
Hosts = case controller_call(get_hosts) of
- [] ->
+ [] ->
TI = controller_call(get_target_info),
[TI#target_info.host];
- List ->
+ List ->
List
end,
generate_nodenames2(Num, Hosts, []).
@@ -1511,7 +1545,7 @@ temp_nodename([Chr|Base], Acc) ->
%% NoOfCases = integer() | unknown
%%
%% Counts the test cases that are about to run and returns that number.
-%% If there's a conf group in TestSpec with a repeat property, the total number
+%% If there's a conf group in TestSpec with a repeat property, the total number
%% of cases can not be calculated and NoOfCases = unknown.
count_test_cases(TopCases, SkipCases) when is_list(TopCases) ->
case collect_all_cases(TopCases, SkipCases) of
@@ -1522,14 +1556,14 @@ count_test_cases(TopCases, SkipCases) when is_list(TopCases) ->
case remove_conf(TestSpec) of
{repeats,_} ->
unknown;
- TestSpec1 ->
+ TestSpec1 ->
length(TestSpec1)
end}
end;
count_test_cases(TopCase, SkipCases) ->
count_test_cases([TopCase], SkipCases).
-
+
remove_conf(Cases) ->
remove_conf(Cases, [], false).
@@ -1538,13 +1572,15 @@ remove_conf([{conf, _Ref, Props, _MF}|Cases], NoConf, Repeats) ->
case get_repeat(Props) of
undefined ->
remove_conf(Cases, NoConf, Repeats);
+ {_RepType,1} ->
+ remove_conf(Cases, NoConf, Repeats);
_ ->
remove_conf(Cases, NoConf, true)
end;
remove_conf([{make,_Ref,_MF}|Cases], NoConf, Repeats) ->
remove_conf(Cases, NoConf, Repeats);
-remove_conf([{skip_case,{Type,_Ref,_MF,_Cmt}}|Cases],
- NoConf, Repeats) when Type==conf;
+remove_conf([{skip_case,{Type,_Ref,_MF,_Cmt}}|Cases],
+ NoConf, Repeats) when Type==conf;
Type==make ->
remove_conf(Cases, NoConf, Repeats);
remove_conf([C|Cases], NoConf, Repeats) ->
@@ -1582,22 +1618,30 @@ add_mod(Mod, Mods) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) ->
+%% do_test_cases(TopCases, SkipCases, Config, TimetrapSpec) ->
%% exit(Result)
%%
%% TopCases = term() (See collect_cases/3)
%% SkipCases = term() (See collect_cases/3)
%% Config = term() (See collect_cases/3)
+%% TimetrapSpec = MultiplyTimetrap | {MultiplyTimetrap,ScaleTimetrap}
%% MultiplyTimetrap = integer() | infinity
+%% ScaleTimetrap = bool()
%%
%% Initializes and starts the test run, for "ordinary" test suites.
%% Creates log directories and log files, inserts initial timestamps and
%% configuration information into the log files.
%%
-%% This function is meant to be called by a process created by
+%% This function is meant to be called by a process created by
%% spawn_tester/7, which sets up some necessary dictionary values.
-
-do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCases) ->
+do_test_cases(TopCases, SkipCases,
+ Config, MultiplyTimetrap) when is_integer(MultiplyTimetrap);
+ MultiplyTimetrap == infinity ->
+ do_test_cases(TopCases, SkipCases, Config, {MultiplyTimetrap,true});
+
+do_test_cases(TopCases, SkipCases,
+ Config, TimetrapData) when is_list(TopCases),
+ is_tuple(TimetrapData) ->
start_log_file(),
case collect_all_cases(TopCases, SkipCases) of
{error,Why} ->
@@ -1607,10 +1651,10 @@ do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCas
N = case remove_conf(TestSpec0) of
{repeats,_} -> unknown;
TS -> length(TS)
- end,
+ end,
put(test_server_cases, N),
put(test_server_case_num, 0),
- TestSpec =
+ TestSpec =
add_init_and_end_per_suite(TestSpec0, undefined, undefined),
TI = get_target_info(),
print(1, "Starting test~s", [print_if_known(N, {", ~w test cases",[N]},
@@ -1643,7 +1687,7 @@ do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCas
[TI#target_info.version, TI#target_info.root_dir]);
_ ->
case test_server_sup:framework_call(target_info, []) of
- TargetInfo when is_list(TargetInfo),
+ TargetInfo when is_list(TargetInfo),
length(TargetInfo) > 0 ->
print(html, "<p>Target:<br>\n"),
print(html, "~s\n", [TargetInfo]);
@@ -1681,12 +1725,12 @@ do_test_cases(TopCases, SkipCases, Config, MultiplyTimetrap) when is_list(TopCas
print(major, "=otp_release ~s", [TI#target_info.otp_release]),
print(major, "=started ~s",
[lists:flatten(timestamp_get(""))]),
- run_test_cases(TestSpec, Config, MultiplyTimetrap)
+ run_test_cases(TestSpec, Config, TimetrapData)
end;
-do_test_cases(TopCase, SkipCases, Config, MultiplyTimetrap) ->
+do_test_cases(TopCase, SkipCases, Config, TimetrapSpec) ->
%% when not list(TopCase)
- do_test_cases([TopCase], SkipCases, Config, MultiplyTimetrap).
+ do_test_cases([TopCase], SkipCases, Config, TimetrapSpec).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -1741,8 +1785,8 @@ start_log_file() ->
ok.
make_html_link(LinkName, Target, Explanation) ->
- %% if possible use a relative reference�to�Target.
- TargetL = filename:split(Target),
+ %% if possible use a relative reference to Target.
+ TargetL = filename:split(Target),
PwdL = filename:split(filename:dirname(LinkName)),
Href = case lists:prefix(PwdL, TargetL) of
true ->
@@ -1782,7 +1826,7 @@ start_minor_log_file(Mod, Func) ->
start_minor_log_file1(Mod, Func, LogDir, AbsName);
{ok,_} -> %% special case, duplicate names
{_,S,Us} = now(),
- Name1_0 =
+ Name1_0 =
lists:flatten(io_lib:format("~s.~s.~w.~w~s", [Mod,Func,S,
trunc(Us/1000),
?html_ext])),
@@ -1853,7 +1897,7 @@ html_convert_modules(TestSpec, _Config) ->
%% Retrieve a list of modules out of the test spec.
html_isolate_modules(List) -> html_isolate_modules(List, sets:new()).
-
+
html_isolate_modules([], Set) -> sets:to_list(Set);
html_isolate_modules([{skip_case,_}|Cases], Set) ->
html_isolate_modules(Cases, Set);
@@ -1919,36 +1963,36 @@ add_init_and_end_per_suite([{make,_,_}=Case|Cases], LastMod, LastRef) ->
[Case|add_init_and_end_per_suite(Cases, LastMod, LastRef)];
add_init_and_end_per_suite([{skip_case,{{Mod,all},_}}=Case|Cases], LastMod, LastRef)
when Mod =/= LastMod ->
- {PreCases, NextMod, NextRef} =
+ {PreCases, NextMod, NextRef} =
do_add_end_per_suite_and_skip(LastMod, LastRef, Mod),
PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)];
add_init_and_end_per_suite([{skip_case,{{Mod,_},_}}=Case|Cases], LastMod, LastRef)
when Mod =/= LastMod ->
- {PreCases, NextMod, NextRef} =
+ {PreCases, NextMod, NextRef} =
do_add_init_and_end_per_suite(LastMod, LastRef, Mod),
PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)];
add_init_and_end_per_suite([{skip_case,{conf,_,{Mod,_},_}}=Case|Cases], LastMod, LastRef)
when Mod =/= LastMod ->
- {PreCases, NextMod, NextRef} =
+ {PreCases, NextMod, NextRef} =
do_add_init_and_end_per_suite(LastMod, LastRef, Mod),
PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)];
add_init_and_end_per_suite([{skip_case,_}=Case|Cases], LastMod, LastRef) ->
[Case|add_init_and_end_per_suite(Cases, LastMod, LastRef)];
-add_init_and_end_per_suite([{conf,_,_,{Mod,_}}=Case|Cases], LastMod, LastRef)
+add_init_and_end_per_suite([{conf,_,_,{Mod,_}}=Case|Cases], LastMod, LastRef)
when Mod =/= LastMod ->
- {PreCases, NextMod, NextRef} =
+ {PreCases, NextMod, NextRef} =
do_add_init_and_end_per_suite(LastMod, LastRef, Mod),
PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)];
add_init_and_end_per_suite([{conf,_,_,_}=Case|Cases], LastMod, LastRef) ->
[Case|add_init_and_end_per_suite(Cases, LastMod, LastRef)];
-add_init_and_end_per_suite([{Mod,_}=Case|Cases], LastMod, LastRef)
+add_init_and_end_per_suite([{Mod,_}=Case|Cases], LastMod, LastRef)
when Mod =/= LastMod ->
- {PreCases, NextMod, NextRef} =
+ {PreCases, NextMod, NextRef} =
do_add_init_and_end_per_suite(LastMod, LastRef, Mod),
PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)];
add_init_and_end_per_suite([{Mod,_,_}=Case|Cases], LastMod, LastRef)
when Mod =/= LastMod ->
- {PreCases, NextMod, NextRef} =
+ {PreCases, NextMod, NextRef} =
do_add_init_and_end_per_suite(LastMod, LastRef, Mod),
PreCases ++ [Case|add_init_and_end_per_suite(Cases, NextMod, NextRef)];
add_init_and_end_per_suite([Case|Cases], LastMod, LastRef)->
@@ -1965,7 +2009,7 @@ do_add_init_and_end_per_suite(LastMod, LastRef, Mod) ->
false -> code:load_file(Mod);
_ -> ok
end,
- {Init,NextMod,NextRef} =
+ {Init,NextMod,NextRef} =
case erlang:function_exported(Mod, init_per_suite, 1) of
true ->
Ref = make_ref(),
@@ -1973,15 +2017,15 @@ do_add_init_and_end_per_suite(LastMod, LastRef, Mod) ->
false ->
{[],Mod,undefined}
end,
- Cases =
+ Cases =
if LastRef==undefined ->
Init;
LastRef==skipped_suite ->
Init;
true ->
- %% Adding end_per_suite here without checking if the
+ %% Adding end_per_suite here without checking if the
%% function is actually exported. This is because a
- %% conf case must have an end case - so if it doesn't
+ %% conf case must have an end case - so if it doesn't
%% exist, it will only fail...
[{conf,LastRef,[],{LastMod,end_per_suite}}|Init]
end,
@@ -1997,12 +2041,12 @@ do_add_end_per_suite_and_skip(LastMod, LastRef, Mod) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% run_test_cases(TestSpec, Config, MultiplyTimetrap) -> exit(Result)
+%% run_test_cases(TestSpec, Config, TimetrapData) -> exit(Result)
%%
%% If remote target, a socket connection is established.
%% Runs the specified tests, then displays/logs the summary.
-run_test_cases(TestSpec, Config, MultiplyTimetrap) ->
+run_test_cases(TestSpec, Config, TimetrapData) ->
maybe_open_job_sock(),
@@ -2010,10 +2054,10 @@ run_test_cases(TestSpec, Config, MultiplyTimetrap) ->
%%! For readable tracing...
%%! Config1 = [{data_dir,""},{priv_dir,""},{nodes,[]}],
- %%! run_test_cases_loop(TestSpec, [[]], MultiplyTimetrap, [], []),
+ %%! run_test_cases_loop(TestSpec, [[]], TimetrapData, [], []),
+
+ run_test_cases_loop(TestSpec, [Config], TimetrapData, [], []),
- run_test_cases_loop(TestSpec, [Config], MultiplyTimetrap, [], []),
-
maybe_get_privdir(),
{AllSkippedN,UserSkipN,AutoSkipN,SkipStr} =
@@ -2060,10 +2104,10 @@ maybe_open_job_sock() ->
%% tar packet containing the privdir created by the test case.
maybe_get_privdir() ->
case get(test_server_ctrl_job_sock) of
- undefined ->
+ undefined ->
%% local target
ok;
- Sock ->
+ Sock ->
%% remote target
request(Sock, job_done),
gen_tcp:close(Sock)
@@ -2071,37 +2115,39 @@ maybe_get_privdir() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% run_test_cases_loop(TestCases, Config, MultiplyTimetrap, Mode, Status) -> ok
+%% run_test_cases_loop(TestCases, Config, TimetrapData, Mode, Status) -> ok
%% TestCases = [Test,...]
%% Config = [[{Key,Val},...],...]
+%% TimetrapData = {MultiplyTimetrap,ScaleTimetrap}
%% MultiplyTimetrap = integer() | infinity
+%% ScaleTimetrap = bool()
%% Mode = [{Ref,[Prop,..],StartTime}]
%% Ref = reference()
-%% Prop = {name,Name} | sequence | parallel |
+%% Prop = {name,Name} | sequence | parallel |
%% shuffle | {shuffle,Seed} |
-%% repeat | {repeat,N} |
+%% repeat | {repeat,N} |
%% repeat_until_all_ok | {repeat_until_all_ok,N} |
-%% repeat_until_any_ok | {repeat_until_any_ok,N} |
-%% repeat_until_any_fail | {repeat_until_any_fail,N} |
-%% repeat_until_all_fail | {repeat_until_all_fail,N}
+%% repeat_until_any_ok | {repeat_until_any_ok,N} |
+%% repeat_until_any_fail | {repeat_until_any_fail,N} |
+%% repeat_until_all_fail | {repeat_until_all_fail,N}
%% Status = [{Ref,{{Ok,Skipped,Failed},CopiedCases}}]
%% Ok = Skipped = Failed = [Case,...]
%%
%% Execute the TestCases under configuration Config. Config is a list
%% of lists, where hd(Config) holds the config tuples for the current
-%% conf case and tl(Config) is the data for the higher level conf cases.
-%% Config data is "inherited" from top to nested conf cases, but
+%% conf case and tl(Config) is the data for the higher level conf cases.
+%% Config data is "inherited" from top to nested conf cases, but
%% never the other way around. if length(Config) == 1, Config contains
%% only the initial config data for the suite.
%%
%% Test may be one of the following:
%%
-%% {conf,Ref,Props,{Mod,Func}} Mod:Func is a configuration modification
+%% {conf,Ref,Props,{Mod,Func}} Mod:Func is a configuration modification
%% function, call it with the current configuration as argument. It will
%% return a new configuration.
%%
-%% {make,Ref,{Mod,Func,Args}} Mod:Func is a make function, and it is called
-%% with the given arguments. This function will *always* be called on the host
+%% {make,Ref,{Mod,Func,Args}} Mod:Func is a make function, and it is called
+%% with the given arguments. This function will *always* be called on the host
%% - not on target.
%%
%% {Mod,Case} This is a normal test case. Determine the correct
@@ -2114,16 +2160,16 @@ maybe_get_privdir() ->
%% {skip_case,{conf,Ref,Case,Comment}} An init conf case gets skipped
%% by the user. This will also cause the end conf case to be skipped.
%% Note that it is not possible to skip an end conf case directly (it
-%% can only be skipped indirectly by a skipped init conf case). The
-%% comment (which gets printed in the log files) describes why the case
+%% can only be skipped indirectly by a skipped init conf case). The
+%% comment (which gets printed in the log files) describes why the case
%% was skipped.
%%
-%% {skip_case,{Case,Comment}} A normal test case skipped by the user.
-%% The comment (which gets printed in the log files) describes why the
+%% {skip_case,{Case,Comment}} A normal test case skipped by the user.
+%% The comment (which gets printed in the log files) describes why the
%% case was skipped.
%%
%% {auto_skip_case,{conf,Ref,Case,Comment},Mode} This is the result of
-%% an end conf case being automatically skipped due to a failing init
+%% an end conf case being automatically skipped due to a failing init
%% conf case. It could also be a nested conf case that gets skipped
%% because of a failed or skipped top level conf.
%%
@@ -2151,25 +2197,25 @@ maybe_get_privdir() ->
%% messages to the main process instead of writing the data to file
%% (only true for printouts to common log files).
%%
-%% If a conf group nested under a parallel group in the test
+%% If a conf group nested under a parallel group in the test
%% specification should be started, the 'test_server_common_io_handler'
%% value gets set also on the main process. This causes all printouts
-%% to common files - both from parallel test cases and from cases
+%% to common files - both from parallel test cases and from cases
%% executed by the main process - to all end up as messages in the
-%% inbox of the main process.
+%% inbox of the main process.
%%
%% During execution of a parallel group (or of a group nested under a
-%% parallel group), *any* new test case being started gets registered
+%% parallel group), *any* new test case being started gets registered
%% in a list saved in the dictionary with 'test_server_queued_io' as key.
%% When the top level parallel group is finished (only then can we be
%% sure all parallel test cases have finished and "reported in"), the
-%% list of test cases is traversed in order and printout messages from
-%% each process - including the main process - are handled in turn. See
+%% list of test cases is traversed in order and printout messages from
+%% each process - including the main process - are handled in turn. See
%% handle_test_case_io_and_status/0 for details.
%%
%% To be able to handle nested conf groups with different properties,
%% the Mode argument specifies a list of {Ref,Properties} tuples.
-%% The head of the Mode list at any given time identifies the group
+%% The head of the Mode list at any given time identifies the group
%% currently being processed. The tail of the list identifies groups
%% on higher level.
%%
@@ -2179,13 +2225,13 @@ maybe_get_privdir() ->
%%
%% A group nested under a parallel group will start executing in
%% parallel with previous (parallel) test cases (no matter what
-%% properties the nested group has). Test cases are however never
+%% properties the nested group has). Test cases are however never
%% executed in parallel with the start or end conf case of the same
%% group! Because of this, the test_server_ctrl loop waits at
%% the end conf of a group for all parallel cases to finish
%% before the end conf case actually executes. This has the effect
%% that it's only after a nested group has finished that any
-%% remaining parallel cases in the previous group get spawned (*).
+%% remaining parallel cases in the previous group get spawned (*).
%% Example (all parallel cases):
%%
%% group1_init |---->
@@ -2201,8 +2247,8 @@ maybe_get_privdir() ->
%%
run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases],
- Config, MultiplyTimetrap, Mode, Status) when Type==conf;
- Type==make ->
+ Config, TimetrapData, Mode, Status) when Type==conf;
+ Type==make ->
file:set_cwd(filename:dirname(get(test_server_dir))),
CurrIOHandler = get(test_server_common_io_handler),
@@ -2217,24 +2263,24 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases],
set_io_buffering(undefined),
{Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, false, SkipMode),
test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode),
+ run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode),
delete_status(Ref, Status));
_ ->
- %% this is a skipped end conf for a parallel group nested under a
+ %% this is a skipped end conf for a parallel group nested under a
%% parallel group (io buffering is active)
wait_for_cases(Ref),
{Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, true, SkipMode),
test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]),
case CurrIOHandler of
- {Ref,_} ->
+ {Ref,_} ->
%% current_io_handler was set by start conf of this
- %% group, so we can unset it now (no more io from main
+ %% group, so we can unset it now (no more io from main
%% process needs to be buffered)
set_io_buffering(undefined);
- _ ->
+ _ ->
ok
end,
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode),
+ run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode),
delete_status(Ref, Status))
end;
{Ref,false} ->
@@ -2242,7 +2288,7 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases],
%% nested under a parallel group
{Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, false, SkipMode),
test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode),
+ run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode),
delete_status(Ref, Status));
{Ref,_} ->
%% this is a skipped end conf for a non-parallel group nested under
@@ -2250,22 +2296,22 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases],
{Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, true, SkipMode),
test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]),
case CurrIOHandler of
- {Ref,_} ->
+ {Ref,_} ->
%% current_io_handler was set by start conf of this
- %% group, so we can unset it now (no more io from main
+ %% group, so we can unset it now (no more io from main
%% process needs to be buffered)
set_io_buffering(undefined);
- _ ->
+ _ ->
ok
end,
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, tl(Mode),
+ run_test_cases_loop(Cases, Config, TimetrapData, tl(Mode),
delete_status(Ref, Status));
{_,false} ->
%% this is a skipped start conf for a group which is not nested
%% under a parallel group
{Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, false, SkipMode),
test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, [conf(Ref,[])|Mode], Status);
+ run_test_cases_loop(Cases, Config, TimetrapData, [conf(Ref,[])|Mode], Status);
{_,Ref0} when is_reference(Ref0) ->
%% this is a skipped start conf for a group nested under a parallel group
%% and if this is the first nested group, io buffering must be activated
@@ -2276,19 +2322,19 @@ run_test_cases_loop([{auto_skip_case,{Type,Ref,Case,Comment},SkipMode}|Cases],
end,
{Mod,Func} = skip_case(auto, Ref, 0, Case, Comment, true, SkipMode),
test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, [conf(Ref,[])|Mode], Status)
- end;
+ run_test_cases_loop(Cases, Config, TimetrapData, [conf(Ref,[])|Mode], Status)
+ end;
run_test_cases_loop([{auto_skip_case,{Case,Comment},SkipMode}|Cases],
- Config, MultiplyTimetrap, Mode, Status) ->
+ Config, TimetrapData, Mode, Status) ->
{Mod,Func} = skip_case(auto, undefined, get(test_server_case_num)+1, Case, Comment,
(undefined /= get(test_server_common_io_handler)), SkipMode),
test_server_sup:framework_call(report, [tc_auto_skip,{?pl2a(Mod),Func,Comment}]),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode,
+ run_test_cases_loop(Cases, Config, TimetrapData, Mode,
update_status(skipped, Mod, Func, Status));
run_test_cases_loop([{skip_case,{conf,Ref,Case,Comment}}|Cases0],
- Config, MultiplyTimetrap, Mode, Status) ->
+ Config, TimetrapData, Mode, Status) ->
{Mod,Func} = skip_case(user, Ref, 0, Case, Comment,
(undefined /= get(test_server_common_io_handler))),
{Cases,Config1} =
@@ -2301,24 +2347,24 @@ run_test_cases_loop([{skip_case,{conf,Ref,Case,Comment}}|Cases0],
{skip_cases_upto(Ref, Cases0, Comment, conf, Mode),Config}
end,
test_server_sup:framework_call(report, [tc_user_skip,{?pl2a(Mod),Func,Comment}]),
- run_test_cases_loop(Cases, Config1, MultiplyTimetrap, Mode,
+ run_test_cases_loop(Cases, Config1, TimetrapData, Mode,
update_status(skipped, Mod, Func, Status));
run_test_cases_loop([{skip_case,{Case,Comment}}|Cases],
- Config, MultiplyTimetrap, Mode, Status) ->
+ Config, TimetrapData, Mode, Status) ->
{Mod,Func} = skip_case(user, undefined, get(test_server_case_num)+1, Case, Comment,
(undefined /= get(test_server_common_io_handler))),
test_server_sup:framework_call(report, [tc_user_skip,{?pl2a(Mod),Func,Comment}]),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode,
+ run_test_cases_loop(Cases, Config, TimetrapData, Mode,
update_status(skipped, Mod, Func, Status));
%% a start *or* end conf case, wrapping test cases or other conf cases
-run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
- Config, MultiplyTimetrap, Mode0, Status) ->
-
+run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
+ Config, TimetrapData, Mode0, Status) ->
+
CurrIOHandler = get(test_server_common_io_handler),
%% check and update the mode for test case execution and io msg handling
- {StartConf,Mode,IOHandler,ConfTime,Status1} =
+ {StartConf,Mode,IOHandler,ConfTime,Status1} =
case {curr_ref(Mode0),check_props(parallel, Mode0)} of
{Ref,Ref} ->
case check_props(parallel, tl(Mode0)) of
@@ -2334,19 +2380,19 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
{false,tl(Mode0),undefined,Elapsed,
update_status(Ref, OkSkipFail, Status)};
_ ->
- %% this is an end conf for a parallel group nested under a
+ %% this is an end conf for a parallel group nested under a
%% parallel group (io buffering is active)
OkSkipFail = wait_for_cases(Ref),
queue_test_case_io(Ref, self(), 0, Mod, Func),
Elapsed = elapsed_time(conf_start(Ref, Mode0),?now)/1000000,
case CurrIOHandler of
- {Ref,_} ->
+ {Ref,_} ->
%% current_io_handler was set by start conf of this
%% group, so we can unset it after this case (no
%% more io from main process needs to be buffered)
{false,tl(Mode0),undefined,Elapsed,
update_status(Ref, OkSkipFail, Status)};
- _ ->
+ _ ->
{false,tl(Mode0),CurrIOHandler,Elapsed,
update_status(Ref, OkSkipFail, Status)}
end
@@ -2362,16 +2408,16 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
queue_test_case_io(Ref, self(), 0, Mod, Func),
Elapsed = elapsed_time(conf_start(Ref, Mode0),?now)/1000000,
case CurrIOHandler of
- {Ref,_} ->
+ {Ref,_} ->
%% current_io_handler was set by start conf of this
%% group, so we can unset it after this case (no
%% more io from main process needs to be buffered)
{false,tl(Mode0),undefined,Elapsed,Status};
- _ ->
+ _ ->
{false,tl(Mode0),CurrIOHandler,Elapsed,Status}
end;
{_,false} ->
- %% this is a start conf for a group which is not nested under a
+ %% this is a start conf for a group which is not nested under a
%% parallel group, check if this case starts a new parallel group
case lists:member(parallel, Props) of
true ->
@@ -2424,9 +2470,9 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
end;
NumStr ->
%% Ex: "123 456 789" or "123,456,789" -> {123,456,789}
- list_to_tuple([list_to_integer(NS) ||
+ list_to_tuple([list_to_integer(NS) ||
NS <- string:tokens(NumStr, [$ ,$:,$,])])
- end,
+ end,
{shuffle_cases(Ref, Cs0, UseSeed),{shuffle,UseSeed}}
end;
not StartConf ->
@@ -2440,17 +2486,19 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
if StartConf ->
case get_repeat(Props) of
undefined ->
- %% we *must* have a status entry for every conf since we
+ %% we *must* have a status entry for every conf since we
%% will continously update status with test case results
%% without knowing the Ref (but update hd(Status))
{false,new_status(Ref, Status1),Cases1,?void_fun};
- _ ->
+ {_RepType,N} when N =< 1 ->
+ {false,new_status(Ref, Status1),Cases1,?void_fun};
+ _ ->
{Copied,_} = copy_cases(Ref, make_ref(), Cs1),
{true,new_status(Ref, Copied, Status1),Cases1,?void_fun}
end;
not StartConf ->
RepVal = get_repeat(get_props(Mode0)),
- ReportStop =
+ ReportStop =
fun() ->
print(minor, "~n*** Stopping repeat operation ~w", [RepVal]),
print(1, "Stopping repeat operation ~w", [RepVal])
@@ -2461,21 +2509,23 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
case RepVal of
undefined ->
{false,EndStatus,Cases1,?void_fun};
+ {_RepType,N} when N =< 1 ->
+ {false,EndStatus,Cases1,?void_fun};
{repeat,_} ->
{true,EndStatus,CopiedCases++Cases1,?void_fun};
{repeat_until_all_ok,_} ->
{RestCs,Fun} = case get_tc_results(Status1) of
- {_,_,[]} ->
+ {_,_,[]} ->
{Cases1,ReportStop};
- _ ->
+ _ ->
{CopiedCases++Cases1,?void_fun}
end,
{true,EndStatus,RestCs,Fun};
{repeat_until_any_ok,_} ->
{RestCs,Fun} = case get_tc_results(Status1) of
- {Ok,_,_} when length(Ok) > 0 ->
+ {Ok,_,_} when length(Ok) > 0 ->
{Cases1,ReportStop};
- _ ->
+ _ ->
{CopiedCases++Cases1,?void_fun}
end,
{true,EndStatus,RestCs,Fun};
@@ -2483,15 +2533,15 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
{RestCs,Fun} = case get_tc_results(Status1) of
{_,_,Fails} when length(Fails) > 0 ->
{Cases1,ReportStop};
- _ ->
+ _ ->
{CopiedCases++Cases1,?void_fun}
end,
{true,EndStatus,RestCs,Fun};
{repeat_until_all_fail,_} ->
{RestCs,Fun} = case get_tc_results(Status1) of
- {[],_,_} ->
+ {[],_,_} ->
{Cases1,ReportStop};
- _ ->
+ _ ->
{CopiedCases++Cases1,?void_fun}
end,
{true,EndStatus,RestCs,Fun}
@@ -2517,13 +2567,13 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
[{tc_group_properties,get_props(Mode0)},
{tc_group_result,[{ok,TcOk},{skipped,TcSkip},{failed,TcFail}]}]
end,
- ActualCfg =
+ ActualCfg =
update_config(hd(Config), [{priv_dir,get(test_server_priv_dir)},
{data_dir,get_data_dir(Mod)}] ++ CfgProps),
CurrMode = curr_mode(Ref, Mode0, Mode),
- ConfCaseResult = run_test_case(Ref, 0, Mod, Func, [ActualCfg], skip_init, target,
- MultiplyTimetrap, CurrMode),
+ ConfCaseResult = run_test_case(Ref, 0, Mod, Func, [ActualCfg], skip_init, target,
+ TimetrapData, CurrMode),
case ConfCaseResult of
{_,NewCfg,_} when Func == init_per_suite, is_list(NewCfg) ->
@@ -2533,8 +2583,8 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
[] ->
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(Cases, [NewCfg|Config],
- MultiplyTimetrap, Mode, Status2);
+ run_test_cases_loop(Cases, [NewCfg|Config],
+ TimetrapData, Mode, Status2);
Bad ->
print(minor, "~n*** ~p returned bad elements in Config: ~p.~n",
[Func,Bad]),
@@ -2542,22 +2592,22 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
Cases2 = skip_cases_upto(Ref, Cases, Reason, conf, CurrMode),
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode,
+ run_test_cases_loop(Cases2, Config, TimetrapData, Mode,
delete_status(Ref, Status2))
- end;
+ end;
{_,NewCfg,_} when StartConf, is_list(NewCfg) ->
print_conf_time(ConfTime),
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(Cases, [NewCfg|Config], MultiplyTimetrap, Mode, Status2);
+ run_test_cases_loop(Cases, [NewCfg|Config], TimetrapData, Mode, Status2);
{_,{framework_error,{FwMod,FwFunc},Reason},_} ->
print(minor, "~n*** ~p failed in ~p. Reason: ~p~n", [FwMod,FwFunc,Reason]),
print(1, "~p failed in ~p. Reason: ~p~n", [FwMod,FwFunc,Reason]),
exit(framework_error);
- {_,Fail,_} when element(1,Fail) == 'EXIT';
+ {_,Fail,_} when element(1,Fail) == 'EXIT';
element(1,Fail) == timetrap_timeout;
element(1,Fail) == failed ->
- {Cases2,Config1} =
+ {Cases2,Config1} =
if StartConf ->
ReportAbortRepeat(failed),
print(minor, "~n*** ~p failed.~n"
@@ -2571,7 +2621,7 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
end,
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(Cases2, Config1, MultiplyTimetrap, Mode,
+ run_test_cases_loop(Cases2, Config1, TimetrapData, Mode,
delete_status(Ref, Status2));
{died,Why,_} when Func == init_per_suite ->
print(minor, "~n*** Unexpected exit during init_per_suite.~n", []),
@@ -2579,16 +2629,16 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
Cases2 = skip_cases_upto(Ref, Cases, Reason, conf, CurrMode),
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode,
- delete_status(Ref, Status2));
+ run_test_cases_loop(Cases2, Config, TimetrapData, Mode,
+ delete_status(Ref, Status2));
{_,{Skip,Reason},_} when StartConf and ((Skip==skip) or (Skip==skipped)) ->
ReportAbortRepeat(skipped),
print(minor, "~n*** ~p skipped.~n"
" Skipping all cases.", [Func]),
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode),
- Config, MultiplyTimetrap, Mode,
+ run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode),
+ Config, TimetrapData, Mode,
delete_status(Ref, Status2));
{_,{skip_and_save,Reason,_SavedConfig},_} when StartConf ->
ReportAbortRepeat(skipped),
@@ -2596,8 +2646,8 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
" Skipping all cases.", [Func]),
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode),
- Config, MultiplyTimetrap, Mode,
+ run_test_cases_loop(skip_cases_upto(Ref, Cases, Reason, conf, CurrMode),
+ Config, TimetrapData, Mode,
delete_status(Ref, Status2));
{_,_Other,_} when Func == init_per_suite ->
print(minor, "~n*** init_per_suite failed to return a Config list.~n", []),
@@ -2605,16 +2655,16 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
Cases2 = skip_cases_upto(Ref, Cases, Reason, conf, CurrMode),
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode,
+ run_test_cases_loop(Cases2, Config, TimetrapData, Mode,
delete_status(Ref, Status2));
{_,_Other,_} when StartConf ->
print_conf_time(ConfTime),
set_io_buffering(IOHandler),
ReportRepeatStop(),
stop_minor_log_file(),
- run_test_cases_loop(Cases, [hd(Config)|Config], MultiplyTimetrap,
+ run_test_cases_loop(Cases, [hd(Config)|Config], TimetrapData,
Mode, Status2);
-
+
{_,_EndConfRetVal,Opts} ->
%% check if return_group_result is set (ok, skipped or failed) and
%% if so return the value to the group "above" so that result may be
@@ -2631,35 +2681,35 @@ run_test_cases_loop([{conf,Ref,Props,{Mod,Func}}|_Cases]=Cs0,
ReportRepeatStop(),
set_io_buffering(IOHandler),
stop_minor_log_file(),
- run_test_cases_loop(Cases, tl(Config), MultiplyTimetrap, Mode, Status3)
+ run_test_cases_loop(Cases, tl(Config), TimetrapData, Mode, Status3)
end;
-run_test_cases_loop([{make,Ref,{Mod,Func,Args}}|Cases0], Config, MultiplyTimetrap, Mode, Status) ->
- case run_test_case(Ref, 0, Mod, Func, Args, skip_init, host, MultiplyTimetrap) of
+run_test_cases_loop([{make,Ref,{Mod,Func,Args}}|Cases0], Config, TimetrapData, Mode, Status) ->
+ case run_test_case(Ref, 0, Mod, Func, Args, skip_init, host, TimetrapData) of
{_,Why={'EXIT',_},_} ->
print(minor, "~n*** ~p failed.~n"
" Skipping all cases.", [Func]),
Reason = {failed,{Mod,Func,Why}},
Cases = skip_cases_upto(Ref, Cases0, Reason, conf, Mode),
stop_minor_log_file(),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status);
+ run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status);
{_,_Whatever,_} ->
stop_minor_log_file(),
- run_test_cases_loop(Cases0, Config, MultiplyTimetrap, Mode, Status)
+ run_test_cases_loop(Cases0, Config, TimetrapData, Mode, Status)
end;
-run_test_cases_loop([{conf,_Ref,_Props,_X}=Conf|_Cases0],
- Config, _MultiplyTimetrap, _Mode, _Status) ->
+run_test_cases_loop([{conf,_Ref,_Props,_X}=Conf|_Cases0],
+ Config, _TimetrapData, _Mode, _Status) ->
erlang:error(badarg, [Conf,Config]);
-run_test_cases_loop([{Mod,Case}|Cases], Config, MultiplyTimetrap, Mode, Status) ->
- ActualCfg =
+run_test_cases_loop([{Mod,Case}|Cases], Config, TimetrapData, Mode, Status) ->
+ ActualCfg =
update_config(hd(Config), [{priv_dir,get(test_server_priv_dir)},
{data_dir,get_data_dir(Mod)}]),
run_test_cases_loop([{Mod,Case,[ActualCfg]}|Cases], Config,
- MultiplyTimetrap, Mode, Status);
+ TimetrapData, Mode, Status);
-run_test_cases_loop([{Mod,Func,Args}|Cases], Config, MultiplyTimetrap, Mode, Status) ->
+run_test_cases_loop([{Mod,Func,Args}|Cases], Config, TimetrapData, Mode, Status) ->
Num = put(test_server_case_num, get(test_server_case_num)+1),
%% check the current execution mode and save info about the case if
%% detected that printouts to common log files is handled later
@@ -2669,15 +2719,15 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, MultiplyTimetrap, Mode, Sta
undefined ->
%% io printouts are written to straight to file
ok;
- _ ->
+ _ ->
%% io messages are buffered, put test case in queue
queue_test_case_io(undefined, self(), Num+1, Mod, Func)
end;
_ ->
ok
end,
- case run_test_case(undefined, Num+1, Mod, Func, Args,
- run_init, target, MultiplyTimetrap, Mode) of
+ case run_test_case(undefined, Num+1, Mod, Func, Args,
+ run_init, target, TimetrapData, Mode) of
%% callback to framework module failed, exit immediately
{_,{framework_error,{FwMod,FwFunc},Reason},_} ->
print(minor, "~n*** ~p failed in ~p. Reason: ~p~n", [FwMod,FwFunc,Reason]),
@@ -2688,50 +2738,50 @@ run_test_cases_loop([{Mod,Func,Args}|Cases], Config, MultiplyTimetrap, Mode, Sta
{Time,RetVal,_} ->
{Failed,Status1} =
case Time of
- died ->
+ died ->
{true,update_status(failed, Mod, Func, Status)};
_ when is_tuple(RetVal) ->
case element(1, RetVal) of
- R when R=='EXIT'; R==failed ->
+ R when R=='EXIT'; R==failed ->
{true,update_status(failed, Mod, Func, Status)};
- R when R==skip; R==skipped ->
+ R when R==skip; R==skipped ->
{false,update_status(skipped, Mod, Func, Status)};
- _ ->
+ _ ->
{false,update_status(ok, Mod, Func, Status)}
end;
- _ ->
+ _ ->
{false,update_status(ok, Mod, Func, Status)}
- end,
+ end,
case check_prop(sequence, Mode) of
false ->
stop_minor_log_file(),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status1);
- Ref ->
- %% the case is in a sequence; we must check the result and
+ run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1);
+ Ref ->
+ %% the case is in a sequence; we must check the result and
%% determine if the following cases should run or be skipped
if not Failed -> % proceed with next case
stop_minor_log_file(),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status1);
+ run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status1);
true -> % skip rest of cases in sequence
print(minor, "~n*** ~p failed.~n"
" Skipping all other cases in sequence.", [Func]),
Reason = {failed,{Mod,Func}},
Cases2 = skip_cases_upto(Ref, Cases, Reason, tc, Mode),
stop_minor_log_file(),
- run_test_cases_loop(Cases2, Config, MultiplyTimetrap, Mode, Status1)
+ run_test_cases_loop(Cases2, Config, TimetrapData, Mode, Status1)
end
end;
%% the test case is being executed in parallel with the main process (and
%% other test cases) and Pid is the dedicated process executing the case
Pid ->
- %% io from Pid will be buffered in the main process inbox and handled
+ %% io from Pid will be buffered in the main process inbox and handled
%% later, so we have to save info about the case
queue_test_case_io(undefined, Pid, Num+1, Mod, Func),
- run_test_cases_loop(Cases, Config, MultiplyTimetrap, Mode, Status)
+ run_test_cases_loop(Cases, Config, TimetrapData, Mode, Status)
end;
%% TestSpec processing finished
-run_test_cases_loop([], _Config, _MultiplyTimetrap, _, _) ->
+run_test_cases_loop([], _Config, _TimetrapData, _, _) ->
ok.
%%--------------------------------------------------------------------
@@ -2798,12 +2848,12 @@ check_props(Attrib, Mode) ->
case [R || {R,Ps,_} <- Mode, lists:member(Attrib, Ps)] of
[] -> false;
[Ref|_] -> Ref
- end.
+ end.
get_name([{_Ref,Props,_}|_]) ->
proplists:get_value(name, Props);
get_name([]) ->
- undefined.
+ undefined.
conf_start(Ref, Mode) ->
case lists:keysearch(Ref, 1, Mode) of
@@ -2826,10 +2876,10 @@ print_conf_time(0) ->
print_conf_time(ConfTime) ->
print(major, "=group_time ~.3fs", [ConfTime]),
print(minor, "~n=== Total execution time of group: ~.3fs~n", [ConfTime]).
-
-print_props(_, []) ->
+
+print_props(_, []) ->
ok;
-print_props(true, Props) ->
+print_props(true, Props) ->
print(major, "=group_props ~p", [Props]),
print(minor, "Group properties: ~p~n", [Props]);
print_props(_, _) ->
@@ -2853,12 +2903,12 @@ update_repeat(Props) ->
Props1 =
if N == forever ->
[{RepType,N}|lists:keydelete(RepType, 1, Props)];
- N < 2 ->
+ N < 3 ->
lists:keydelete(RepType, 1, Props);
- N >= 2 ->
+ N >= 3 ->
[{RepType,N-1}|lists:keydelete(RepType, 1, Props)]
end,
- %% if shuffle is used in combination with repeat, a new
+ %% if shuffle is used in combination with repeat, a new
%% seed shouldn't be set every new turn
case get_shuffle(Props1) of
undefined ->
@@ -2874,13 +2924,13 @@ get_shuffle(Props) ->
delete_shuffle(Props) ->
delete_prop([shuffle], Props).
-%% Return {Item,Value} if found, else if Item alone
+%% Return {Item,Value} if found, else if Item alone
%% is found, return {Item,Default}
get_prop([Item|Items], Default, Props) ->
case lists:keysearch(Item, 1, Props) of
- {value,R} ->
+ {value,R} ->
R;
- false ->
+ false ->
case lists:member(Item, Props) of
true ->
{Item,Default};
@@ -2940,8 +2990,8 @@ random_order(1, {_Pos,Seed}, [{_Ix,CaseOrGroup}], Shuffled) ->
put(test_server_curr_random_seed, Seed),
Shuffled++CaseOrGroup;
random_order(N, {Pos,NewSeed}, IxCases, Shuffled) ->
- {First,[{_Ix,CaseOrGroup}|Rest]} = lists:split(Pos-1, IxCases),
- random_order(N-1, random:uniform_s(N-1, NewSeed),
+ {First,[{_Ix,CaseOrGroup}|Rest]} = lists:split(Pos-1, IxCases),
+ random_order(N-1, random:uniform_s(N-1, NewSeed),
First++Rest, Shuffled++CaseOrGroup).
@@ -2949,7 +2999,7 @@ random_order(N, {Pos,NewSeed}, IxCases, Shuffled) ->
%% skip_case(Type, Ref, CaseNum, Case, Comment, SendSync) -> {Mod,Func}
%%
%% Prints info about a skipped case in the major and html log files.
-%% SendSync determines if start and finished messages must be sent so
+%% SendSync determines if start and finished messages must be sent so
%% that the printouts can be buffered and handled in order with io from
%% parallel processes.
@@ -2969,13 +3019,13 @@ skip_case(Type, Ref, CaseNum, Case, Comment, SendSync, Mode) ->
not SendSync ->
skip_case1(Type, CaseNum, Mod, Func, Comment, Mode)
end,
- MF.
+ MF.
skip_case1(Type, CaseNum, Mod, Func, Comment, Mode) ->
{{Col0,Col1},_} = get_font_style((CaseNum > 0), Mode),
ResultCol = if Type == auto -> "#ffcc99";
Type == user -> "#ff9933"
- end,
+ end,
Comment1 = reason_to_string(Comment),
@@ -3084,7 +3134,7 @@ modify_cases_upto1(Ref, ModOp, [{skip_case,{_F,_Cmt}}=MF|T], Orig, Alt) ->
%% next is a normal case (possibly in a sequence), mark as skipped, or copy, and proceed
modify_cases_upto1(Ref, {skip,Reason,_,Mode}=Op, [{_M,_F}=MF|T], Orig, Alt) ->
- modify_cases_upto1(Ref, Op, T, Orig, [{auto_skip_case,{MF,Reason},Mode}|Alt]);
+ modify_cases_upto1(Ref, Op, T, Orig, [{auto_skip_case,{MF,Reason},Mode}|Alt]);
modify_cases_upto1(Ref, CopyOp, [{_M,_F}=MF|T], Orig, Alt) ->
modify_cases_upto1(Ref, CopyOp, T, [MF|Orig], [MF|Alt]);
@@ -3110,7 +3160,7 @@ set_io_buffering(IOHandler) ->
%% queue_test_case_io(Pid, Num, Mod, Func) -> ok
%%
%% Save info about test case that gets its io buffered. This can
-%% be a parallel test case or it can be a test case (conf or normal)
+%% be a parallel test case or it can be a test case (conf or normal)
%% that belongs to a group nested under a parallel group. The queue
%% is processed after io buffering is disabled. See run_test_cases_loop/4
%% and handle_test_case_io_and_status/0 for more info.
@@ -3124,10 +3174,10 @@ queue_test_case_io(Ref, Pid, Num, Mod, Func) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% wait_for_cases(Ref) -> {Ok,Skipped,Failed}
%%
-%% At the end of a nested parallel group, we have to wait for the test
+%% At the end of a nested parallel group, we have to wait for the test
%% cases to terminate before we can go on (since test cases never execute
-%% in parallel with the end conf case of the group). When a top level
-%% parallel group is finished, buffered io messages must be handled and
+%% in parallel with the end conf case of the group). When a top level
+%% parallel group is finished, buffered io messages must be handled and
%% this is taken care of by handle_test_case_io_and_status/0.
wait_for_cases(Ref) ->
@@ -3135,15 +3185,15 @@ wait_for_cases(Ref) ->
[] ->
{[],[],[]};
Cases ->
- [_Start|TCs] =
+ [_Start|TCs] =
lists:dropwhile(fun({R,_,_,_,_}) when R == Ref -> false;
(_) -> true
end, Cases),
wait_and_resend(Ref, TCs, [],[],[])
end.
-wait_and_resend(Ref, [{OtherRef,_,0,_,_}|Ps],
- Ok,Skip,Fail) when is_reference(OtherRef),
+wait_and_resend(Ref, [{OtherRef,_,0,_,_}|Ps],
+ Ok,Skip,Fail) when is_reference(OtherRef),
OtherRef /= Ref ->
%% ignore cases that belong to nested group
Ps1 = rm_cases_upto(OtherRef, Ps),
@@ -3152,7 +3202,7 @@ wait_and_resend(Ref, [{OtherRef,_,0,_,_}|Ps],
wait_and_resend(Ref, [{_,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) ->
receive
{finished,_Ref,CurrPid,CaseNum,Mod,Func,Result,_RetVal} = Msg ->
- %% resend message to main process so that it can be used
+ %% resend message to main process so that it can be used
%% to handle buffered io messages later
self() ! Msg,
MF = {Mod,Func},
@@ -3163,7 +3213,7 @@ wait_and_resend(Ref, [{_,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) ->
failed -> {Ok,Skip,[MF|Fail]}
end,
wait_and_resend(Ref, Ps, Ok1,Skip1,Fail1);
- {'EXIT',CurrPid,Reason} when Reason /= normal ->
+ {'EXIT',CurrPid,Reason} when Reason /= normal ->
%% unexpected termination of test case process
{value,{_,_,CaseNum,Mod,Func}} = lists:keysearch(CurrPid, 2, Cases),
print(1, "Error! Process for test case #~p (~p:~p) died! Reason: ~p",
@@ -3186,17 +3236,17 @@ rm_cases_upto(Ref, [_|Ps]) ->
%% execution. The common log files (major, html etc) must however be
%% written to sequentially. The test case processes send print requests
%% to the main (starting) process (the same process executing
-%% run_test_cases_loop/4), which handles these requests in the same
+%% run_test_cases_loop/4), which handles these requests in the same
%% order that the test case processes were started.
%%
%% An io session is always started with a {started,Ref,Pid,Num,Mod,Func}
%% message and terminated with {finished,Ref,Pid,Num,Mod,Func,Result,RetVal}.
%% The result shipped with the finished message from a parallel process
-%% is used to update status data of the current test run. An 'EXIT'
-%% message from each parallel test case process (after finishing and
+%% is used to update status data of the current test run. An 'EXIT'
+%% message from each parallel test case process (after finishing and
%% terminating) is also received and handled here.
%%
-%% During execution of a parallel group, any cases (conf or normal)
+%% During execution of a parallel group, any cases (conf or normal)
%% belonging to a nested group will also get its io printouts buffered.
%% This is necessary to get the major and html log files written in
%% correct sequence. This function handles also the print messages
@@ -3207,7 +3257,7 @@ rm_cases_upto(Ref, [_|Ps]) ->
%% See the header comment for run_test_cases_loop/4 for more
%% info about IO handling.
%%
-%% Note: It is important that the type of messages handled here
+%% Note: It is important that the type of messages handled here
%% do not get consumated by test_server:run_test_case_msgloop/5
%% during the test case execution (e.g. in the catch clause of
%% the receive)!
@@ -3231,7 +3281,7 @@ handle_test_case_io_and_status() ->
ok
end, Cases),
Result
- end.
+ end.
%% Handle cases (without Ref) that belong to the top parallel group (i.e. when Refs = [])
handle_io_and_exit_loop([], [{undefined,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) ->
@@ -3249,7 +3299,7 @@ handle_io_and_exit_loop([], [{undefined,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, O
1000 ->
exit({testcase_failed_to_start,Mod,Func})
end;
-
+
%% Handle cases that belong to groups nested under top parallel group
handle_io_and_exit_loop(Refs, [{Ref,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Skip,Fail) ->
receive
@@ -3269,7 +3319,7 @@ handle_io_and_exit_loop(Refs, [{Ref,CurrPid,CaseNum,Mod,Func}|Ps] = Cases, Ok,Sk
1000 ->
exit({testcase_failed_to_start,Mod,Func})
end;
-
+
handle_io_and_exit_loop(_, [], Ok,Skip,Fail) ->
{lists:reverse(Ok),lists:reverse(Skip),lists:reverse(Fail)}.
@@ -3286,7 +3336,7 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) ->
failed ->
put(test_server_failed, get(test_server_failed)+1);
skipped ->
- SkipCounters =
+ SkipCounters =
update_skip_counters(RetVal, get(test_server_skipped)),
put(test_server_skipped, SkipCounters)
end,
@@ -3298,7 +3348,7 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) ->
handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases);
%% unexpected termination of test case process
- {'EXIT',TCPid,Reason} when Reason /= normal ->
+ {'EXIT',TCPid,Reason} when Reason /= normal ->
{value,{_,_,Num,M,F}} = lists:keysearch(TCPid, 2, Cases),
print(1, "Error! Process for test case #~p (~p:~p) died! Reason: ~p",
[Num, M, F, Reason]),
@@ -3307,65 +3357,65 @@ handle_io_and_exits(Main, CurrPid, CaseNum, Mod, Func, Cases) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% run_test_case(Ref, Num, Mod, Func, Args, RunInit,
-%% Where, MultiplyTimetrap, Mode) -> RetVal
+%% run_test_case(Ref, Num, Mod, Func, Args, RunInit,
+%% Where, TimetrapData, Mode) -> RetVal
%%
%% Creates the minor log file and inserts some test case specific headers
-%% and footers into the log files. If a remote target is used, the test
+%% and footers into the log files. If a remote target is used, the test
%% suite (binary) and the content of data_dir is sent. Then the test case
-%% is executed and the result is printed to the log files (also info
+%% is executed and the result is printed to the log files (also info
%% about lingering processes & slave nodes in the system is presented).
-%%
+%%
%% RunInit decides if the per test case init is to be run (true for all
%% but conf cases).
%%
-%% Where specifies if the test case should run on target or on the host.
+%% Where specifies if the test case should run on target or on the host.
%% (Note that 'make' test cases always run on host).
-%%
+%%
%% Mode specifies if the test case should be executed by a dedicated,
%% parallel, process rather than sequentially by the main process. If
%% the former, the new process is spawned and the dictionary of the main
%% process is copied to the test case process.
-%%
-%% RetVal is the result of executing the test case. It contains info
+%%
+%% RetVal is the result of executing the test case. It contains info
%% about the execution time and the return value of the test case function.
-run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, MultiplyTimetrap) ->
+run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, TimetrapData) ->
file:set_cwd(filename:dirname(get(test_server_dir))),
- run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
- MultiplyTimetrap, [], [], self()).
+ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
+ TimetrapData, [], [], self()).
-run_test_case(Ref, Num, Mod, Func, Args, skip_init, Where, MultiplyTimetrap, Mode) ->
+run_test_case(Ref, Num, Mod, Func, Args, skip_init, Where, TimetrapData, Mode) ->
%% a conf case is always executed by the main process
- run_test_case1(Ref, Num, Mod, Func, Args, skip_init, Where,
- MultiplyTimetrap, [], Mode, self());
+ run_test_case1(Ref, Num, Mod, Func, Args, skip_init, Where,
+ TimetrapData, [], Mode, self());
-run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, MultiplyTimetrap, Mode) ->
+run_test_case(Ref, Num, Mod, Func, Args, RunInit, Where, TimetrapData, Mode) ->
file:set_cwd(filename:dirname(get(test_server_dir))),
case check_prop(parallel, Mode) of
false ->
%% this is a sequential test case
- run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
- MultiplyTimetrap, [], Mode, self());
+ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
+ TimetrapData, [], Mode, self());
_Ref ->
%% this a parallel test case, spawn the new process
Main = self(),
- {dictionary,State} = process_info(self(), dictionary),
+ {dictionary,State} = process_info(self(), dictionary),
spawn_link(fun() ->
- run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
- MultiplyTimetrap, State, Mode, Main)
- end)
+ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
+ TimetrapData, State, Mode, Main)
+ end)
end.
-run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
- MultiplyTimetrap, State, Mode, Main) ->
- %% if this runs on a parallel test case process,
+run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
+ TimetrapData, State, Mode, Main) ->
+ %% if this runs on a parallel test case process,
%% copy the dictionary from the main process
do_if_parallel(Main, fun() -> process_flag(trap_exit, true) end, ok),
CopyDict = fun() -> lists:foreach(fun({Key,Val}) -> put(Key, Val) end, State) end,
do_if_parallel(Main, CopyDict, ok),
do_if_parallel(Main, fun() -> put(test_server_common_io_handler, {tc,Main}) end, ok),
- %% if io is being buffered, send start io session message
+ %% if io is being buffered, send start io session message
%% (no matter if case runs on parallel or main process)
case get(test_server_common_io_handler) of
undefined -> ok;
@@ -3373,7 +3423,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
end,
TSDir = get(test_server_dir),
case Where of
- target ->
+ target ->
maybe_send_beam_and_datadir(Mod);
host ->
ok
@@ -3396,8 +3446,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
do_if_parallel(Main, ok, fun erlang:yield/0),
%% run the test case
{Result,DetectedFail,ProcsBefore,ProcsAfter} =
- run_test_case_apply(Num, Mod, Func, Args, get_name(Mode),
- RunInit, Where, MultiplyTimetrap),
+ run_test_case_apply(Num, Mod, Func, Args, get_name(Mode),
+ RunInit, Where, TimetrapData),
{Time,RetVal,Loc,Opts,Comment} =
case Result of
Normal={_Time,_RetVal,_Loc,_Opts,_Comment} -> Normal;
@@ -3409,7 +3459,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
print(major, "=ended ~s", [lists:flatten(timestamp_get(""))]),
do_if_parallel(Main, ok, fun() -> file:set_cwd(filename:dirname(TSDir)) end),
-
+
%% call the appropriate progress function clause to print the results to log
Status =
case {Time,RetVal} of
@@ -3423,16 +3473,16 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
progress(skip, Num, Mod, Func, Loc, Reason,
Time, Comment, Style);
{_,{'EXIT',_Pid,{Skip,Reason}}} when Skip==skip; Skip==skipped ->
- progress(skip, Num, Mod, Func, Loc, Reason,
+ progress(skip, Num, Mod, Func, Loc, Reason,
Time, Comment, Style);
{_,{'EXIT',_Pid,Reason}} ->
- progress(failed, Num, Mod, Func, Loc, Reason,
+ progress(failed, Num, Mod, Func, Loc, Reason,
Time, Comment, Style);
{_,{'EXIT',Reason}} ->
- progress(failed, Num, Mod, Func, Loc, Reason,
+ progress(failed, Num, Mod, Func, Loc, Reason,
Time, Comment, Style);
{_, {failed, Reason}} ->
- progress(failed, Num, Mod, Func, Loc, Reason,
+ progress(failed, Num, Mod, Func, Loc, Reason,
Time, Comment, Style);
{_, {Skip, Reason}} when Skip==skip; Skip==skipped ->
progress(skip, Num, Mod, Func, Loc, Reason,
@@ -3442,7 +3492,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
[] ->
progress(ok, Num, Mod, Func, Loc, RetVal,
Time, Comment, Style);
-
+
Reason ->
progress(failed, Num, Mod, Func, Loc, Reason,
Time, Comment, Style)
@@ -3465,18 +3515,18 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
{US,AS} = get(test_server_skipped),
put(test_server_skipped, {US,AS+1})
end,
- %% only if test case execution is sequential do we care about the
+ %% only if test case execution is sequential do we care about the
%% remaining processes and slave nodes count
case self() of
Main ->
case test_server_sup:framework_call(warn, [processes], true) of
true ->
if ProcsBefore < ProcsAfter ->
- print(minor,
+ print(minor,
"WARNING: ~w more processes in system after test case",
[ProcsAfter-ProcsBefore]);
ProcsBefore > ProcsAfter ->
- print(minor,
+ print(minor,
"WARNING: ~w less processes in system after test case",
[ProcsBefore-ProcsAfter]);
true -> ok
@@ -3493,7 +3543,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
" system. I tried to kill them, but I failed: ~p\n",
[Exit]);
[] -> ok;
- List ->
+ List ->
print(minor, "WARNING: ~w slave nodes in system after test"++
"case. Tried to killed them.~n"++
" Names:~p",
@@ -3505,8 +3555,8 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
_ ->
ok
end,
- %% if the test case was executed sequentially, this updates the execution
- %% time count on the main process (adding execution time of parallel test
+ %% if the test case was executed sequentially, this updates the execution
+ %% time count on the main process (adding execution time of parallel test
%% case groups is done in run_test_cases_loop/4)
if is_number(Time) ->
put(test_server_total_time, get(test_server_total_time)+Time);
@@ -3515,7 +3565,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
end,
check_new_crash_dumps(Where),
- %% if io is being buffered, send finished message
+ %% if io is being buffered, send finished message
%% (no matter if case runs on parallel or main process)
case get(test_server_common_io_handler) of
undefined -> ok;
@@ -3528,7 +3578,7 @@ run_test_case1(Ref, Num, Mod, Func, Args, RunInit, Where,
%%--------------------------------------------------------------------
%% various help functions
-%% Call If() if we're on parallel process, or
+%% Call If() if we're on parallel process, or
%% call Else() if we're on main process
do_if_parallel(Pid, If, Else) ->
case self() of
@@ -3536,7 +3586,7 @@ do_if_parallel(Pid, If, Else) ->
if is_function(Else) -> Else();
true -> Else
end;
- _ ->
+ _ ->
if is_function(If) -> If();
true -> If
end
@@ -3549,13 +3599,13 @@ num2str(N) -> integer_to_list(N).
%% and the content of datadir til target.
maybe_send_beam_and_datadir(Mod) ->
case get(test_server_ctrl_job_sock) of
- undefined ->
+ undefined ->
%% local target
ok;
JobSock ->
%% remote target
case get(test_server_downloaded_suites) of
- undefined ->
+ undefined ->
send_beam_and_datadir(Mod, JobSock),
put(test_server_downloaded_suites, [Mod]);
Suites ->
@@ -3571,10 +3621,10 @@ maybe_send_beam_and_datadir(Mod) ->
send_beam_and_datadir(Mod, JobSock) ->
case code:which(Mod) of
- non_existing ->
+ non_existing ->
io:format("** WARNING: Suite ~w could not be found on host\n",
[Mod]);
- BeamFile ->
+ BeamFile ->
send_beam(JobSock, Mod, BeamFile)
end,
DataDir = get_data_dir(Mod),
@@ -3589,7 +3639,7 @@ send_beam_and_datadir(Mod, JobSock) ->
ModsInDatadir = filelib:wildcard(Wc),
SendBeamFun = fun(X) -> send_beam(JobSock, X) end,
lists:foreach(SendBeamFun, ModsInDatadir),
- %% No need to send C code or makefiles since
+ %% No need to send C code or makefiles since
%% no compilation can be done on target anyway.
%% Compiled C code must exist on target.
%% Beam files are already sent as binaries.
@@ -3597,7 +3647,7 @@ send_beam_and_datadir(Mod, JobSock) ->
%% is to compile it.
Filter = fun("Makefile") -> false;
("Makefile.src") -> false;
- (Y) ->
+ (Y) ->
case filename:extension(Y) of
".c" -> false;
ObjExt -> false;
@@ -3611,7 +3661,7 @@ send_beam_and_datadir(Mod, JobSock) ->
Tarfile = "data_dir.tar.gz",
{ok,Tar} = erl_tar:open(Tarfile, [write,compressed]),
ShortDataDir = filename:basename(DataDir),
- AddTarFun =
+ AddTarFun =
fun(File) ->
Long = filename:join(DataDir, File),
Short = filename:join(ShortDataDir, File),
@@ -3628,11 +3678,11 @@ send_beam_and_datadir(Mod, JobSock) ->
send_beam(JobSock, BeamFile) ->
Mod=filename:rootname(filename:basename(BeamFile), code:objfile_extension()),
- send_beam(JobSock, list_to_atom(Mod), BeamFile).
+ send_beam(JobSock, list_to_atom(Mod), BeamFile).
send_beam(JobSock, Mod, BeamFile) ->
{ok,BeamBin} = file:read_file(BeamFile),
request(JobSock, {{beam,Mod,BeamFile}, BeamBin}).
-
+
check_new_crash_dumps(Where) ->
case Where of
target ->
@@ -3649,25 +3699,25 @@ check_new_crash_dumps(Where) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% progress(Result, CaseNum, Mod, Func, Location, Reason, Time,
+%% progress(Result, CaseNum, Mod, Func, Location, Reason, Time,
%% Comment, TimeFormat) -> Result
%%
%% Prints the result of the test case to log file.
%% Note: Strings that are to be written to the minor log must
%% be prefixed with "=== " here, or the indentation will be wrong.
-progress(skip, CaseNum, Mod, Func, Loc, Reason, Time,
+progress(skip, CaseNum, Mod, Func, Loc, Reason, Time,
Comment, {St0,St1}) ->
- {Reason1,{Color,Ret}} = if_auto_skip(Reason,
+ {Reason1,{Color,Ret}} = if_auto_skip(Reason,
fun() -> {"#ffcc99",auto_skip} end,
fun() -> {"#ff9933",skip} end),
print(major, "=result skipped", []),
- print(1, "*** SKIPPED *** ~s",
+ print(1, "*** SKIPPED *** ~s",
[get_info_str(Func, CaseNum, get(test_server_cases))]),
test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func,
{skipped,Reason1}}]),
ReasonStr = reason_to_string(Reason1),
- ReasonStr1 = lists:flatten([string:strip(S,left) ||
+ ReasonStr1 = lists:flatten([string:strip(S,left) ||
S <- string:tokens(ReasonStr,[$\n])]),
ReasonStr2 =
if length(ReasonStr1) > 80 ->
@@ -3686,10 +3736,10 @@ progress(skip, CaseNum, Mod, Func, Loc, Reason, Time,
[Time,Color,ReasonStr2,Comment1]),
FormatLoc = test_server_sup:format_loc(Loc),
print(minor, "=== location ~s", [FormatLoc]),
- print(minor, "=== reason = ~s", [ReasonStr1]),
+ print(minor, "=== reason = ~s", [ReasonStr1]),
Ret;
-
-progress(failed, CaseNum, Mod, Func, Loc, timetrap_timeout, T,
+
+progress(failed, CaseNum, Mod, Func, Loc, timetrap_timeout, T,
Comment0, {St0,St1}) ->
print(major, "=result failed: timeout, ~p", [Loc]),
print(1, "*** FAILED *** ~s",
@@ -3699,23 +3749,23 @@ progress(failed, CaseNum, Mod, Func, Loc, timetrap_timeout, T,
{failed,timetrap_timeout}}]),
FormatLastLoc = test_server_sup:format_loc(get_last_loc(Loc)),
ErrorReason = io_lib:format("{timetrap_timeout,~s}", [FormatLastLoc]),
- Comment =
+ Comment =
case Comment0 of
"" -> "<font color=\"red\">" ++ ErrorReason ++ "</font>";
- _ -> "<font color=\"red\">" ++ ErrorReason ++ "</font><br>" ++
+ _ -> "<font color=\"red\">" ++ ErrorReason ++ "</font><br>" ++
to_string(Comment0)
end,
- print(html,
+ print(html,
"<td>" ++ St0 ++ "~.3fs" ++ St1 ++ "</td>"
"<td><font color=\"red\">FAILED</font></td>"
- "<td>~s</td></tr>\n",
+ "<td>~s</td></tr>\n",
[T/1000,Comment]),
FormatLoc = test_server_sup:format_loc(Loc),
print(minor, "=== location ~s", [FormatLoc]),
print(minor, "=== reason = timetrap timeout", []),
failed;
-progress(failed, CaseNum, Mod, Func, Loc, {testcase_aborted,Reason}, _T,
+progress(failed, CaseNum, Mod, Func, Loc, {testcase_aborted,Reason}, _T,
Comment0, {St0,St1}) ->
print(major, "=result failed: testcase_aborted, ~p", [Loc]),
print(1, "*** FAILED *** ~s",
@@ -3725,23 +3775,23 @@ progress(failed, CaseNum, Mod, Func, Loc, {testcase_aborted,Reason}, _T,
{failed,testcase_aborted}}]),
FormatLastLoc = test_server_sup:format_loc(get_last_loc(Loc)),
ErrorReason = io_lib:format("{testcase_aborted,~s}", [FormatLastLoc]),
- Comment =
+ Comment =
case Comment0 of
"" -> "<font color=\"red\">" ++ ErrorReason ++ "</font>";
- _ -> "<font color=\"red\">" ++ ErrorReason ++ "</font><br>" ++
+ _ -> "<font color=\"red\">" ++ ErrorReason ++ "</font><br>" ++
to_string(Comment0)
end,
- print(html,
+ print(html,
"<td>" ++ St0 ++ "died" ++ St1 ++ "</td>"
"<td><font color=\"red\">FAILED</font></td>"
- "<td>~s</td></tr>\n",
+ "<td>~s</td></tr>\n",
[Comment]),
FormatLoc = test_server_sup:format_loc(Loc),
print(minor, "=== location ~s", [FormatLoc]),
print(minor, "=== reason = {testcase_aborted,~p}", [Reason]),
failed;
-progress(failed, CaseNum, Mod, Func, unknown, Reason, Time,
+progress(failed, CaseNum, Mod, Func, unknown, Reason, Time,
Comment0, {St0,St1}) ->
print(major, "=result failed: ~p, ~p", [Reason,unknown]),
print(1, "*** FAILED *** ~s",
@@ -3749,10 +3799,10 @@ progress(failed, CaseNum, Mod, Func, unknown, Reason, Time,
test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func,
{failed,Reason}}]),
TimeStr = io_lib:format(if is_float(Time) -> "~.3fs";
- true -> "~w"
+ true -> "~w"
end, [Time]),
ErrorReason = lists:flatten(io_lib:format("~p", [Reason])),
- ErrorReason1 = lists:flatten([string:strip(S,left) ||
+ ErrorReason1 = lists:flatten([string:strip(S,left) ||
S <- string:tokens(ErrorReason,[$\n])]),
ErrorReason2 =
if length(ErrorReason1) > 63 ->
@@ -3760,13 +3810,13 @@ progress(failed, CaseNum, Mod, Func, unknown, Reason, Time,
true ->
ErrorReason1
end,
- Comment =
+ Comment =
case Comment0 of
"" -> "<font color=\"red\">" ++ ErrorReason2 ++ "</font>";
- _ -> "<font color=\"red\">" ++ ErrorReason2 ++ "</font><br>" ++
+ _ -> "<font color=\"red\">" ++ ErrorReason2 ++ "</font><br>" ++
to_string(Comment0)
end,
- print(html,
+ print(html,
"<td>" ++ St0 ++ "~s" ++ St1 ++ "</td>"
"<td><font color=\"red\">FAILED</font></td>"
"<td>~s</td></tr>\n",
@@ -3776,7 +3826,7 @@ progress(failed, CaseNum, Mod, Func, unknown, Reason, Time,
print(minor, "=== reason = "++FStr, [FormattedReason]),
failed;
-progress(failed, CaseNum, Mod, Func, Loc, Reason, Time,
+progress(failed, CaseNum, Mod, Func, Loc, Reason, Time,
Comment0, {St0,St1}) ->
print(major, "=result failed: ~p, ~p", [Reason,Loc]),
print(1, "*** FAILED *** ~s",
@@ -3784,18 +3834,18 @@ progress(failed, CaseNum, Mod, Func, Loc, Reason, Time,
test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func,
{failed,Reason}}]),
TimeStr = io_lib:format(if is_float(Time) -> "~.3fs";
- true -> "~w"
+ true -> "~w"
end, [Time]),
- Comment =
+ Comment =
case Comment0 of
"" -> "";
_ -> "<br>" ++ to_string(Comment0)
end,
FormatLastLoc = test_server_sup:format_loc(get_last_loc(Loc)),
- print(html,
+ print(html,
"<td>" ++ St0 ++ "~s" ++ St1 ++ "</td>"
"<td><font color=\"red\">FAILED</font></td>"
- "<td><font color=\"red\">~s</font>~s</td></tr>\n",
+ "<td><font color=\"red\">~s</font>~s</td></tr>\n",
[TimeStr,FormatLastLoc,Comment]),
FormatLoc = test_server_sup:format_loc(Loc),
print(minor, "=== location ~s", [FormatLoc]),
@@ -3803,7 +3853,7 @@ progress(failed, CaseNum, Mod, Func, Loc, Reason, Time,
print(minor, "=== reason = "++FStr, [FormattedReason]),
failed;
-progress(ok, _CaseNum, Mod, Func, _Loc, RetVal, Time,
+progress(ok, _CaseNum, Mod, Func, _Loc, RetVal, Time,
Comment0, {St0,St1}) ->
print(minor, "successfully completed test case", []),
test_server_sup:framework_call(report, [tc_done,{?pl2a(Mod),Func,ok}]),
@@ -3852,9 +3902,9 @@ get_info_str(Func, 0, _Cases) ->
get_info_str(_Func, CaseNum, unknown) ->
"test case " ++ integer_to_list(CaseNum);
get_info_str(_Func, CaseNum, Cases) ->
- "test case " ++ integer_to_list(CaseNum) ++
+ "test case " ++ integer_to_list(CaseNum) ++
" of " ++ integer_to_list(Cases).
-
+
print_if_known(Known, {SK,AK}, {SU,AU}) ->
{S,A} = if Known == unknown -> {SU,AU};
true -> {SK,AK}
@@ -3880,7 +3930,7 @@ reason_to_string({failed,{_,FailFunc,bad_return}}) ->
atom_to_list(FailFunc) ++ " bad return value";
reason_to_string({failed,{_,FailFunc,{timetrap_timeout,_}}}) ->
atom_to_list(FailFunc) ++ " timed out";
-reason_to_string(FWInitFail = {failed,{_CB,init_tc,_Reason}}) ->
+reason_to_string(FWInitFail = {failed,{_CB,init_tc,_Reason}}) ->
to_string(FWInitFail);
reason_to_string({failed,{_,FailFunc,_}}) ->
atom_to_list(FailFunc) ++ " failed";
@@ -3889,29 +3939,29 @@ reason_to_string(Other) ->
%get_font_style(Prop) ->
% {Col,St0,St1} = get_font_style1(Prop),
-% {{"<font color="++Col++">","</font>"},
+% {{"<font color="++Col++">","</font>"},
% {"<font color="++Col++">"++St0,St1++"</font>"}}.
-
+
get_font_style(NormalCase, Mode) ->
- Prop = if not NormalCase ->
+ Prop = if not NormalCase ->
default;
true ->
case check_prop(parallel, Mode) of
- false ->
+ false ->
case check_prop(sequence, Mode) of
- false ->
+ false ->
default;
- _ ->
+ _ ->
sequence
end;
- _ ->
+ _ ->
parallel
end
end,
{Col,St0,St1} = get_font_style1(Prop),
- {{"<font color="++Col++">","</font>"},
+ {{"<font color="++Col++">","</font>"},
{"<font color="++Col++">"++St0,St1++"</font>"}}.
-
+
get_font_style1(parallel) ->
{"\"darkslategray\"","<i>","</i>"};
get_font_style1(sequence) ->
@@ -3931,7 +3981,7 @@ get_font_style1(default) ->
%% The framework application can switch this feature off by setting
%% *its* application environment variable 'format_exception' to false.
%% It is also possible to switch formatting off by starting the
-%% test_server node with init argument 'test_server_format_exception'
+%% test_server node with init argument 'test_server_format_exception'
%% set to false.
format_exception(Reason={_Error,Stack}) when is_list(Stack) ->
@@ -3950,17 +4000,17 @@ format_exception(Reason={_Error,Stack}) when is_list(Stack) ->
_ ->
do_format_exception(Reason)
end
- end;
+ end;
format_exception(Error) ->
format_exception({Error,[]}).
do_format_exception(Reason={Error,Stack}) ->
StackFun = fun(_, _, _) -> false end,
- PF = fun(Term, I) ->
- io_lib:format("~." ++ integer_to_list(I) ++ "p", [Term])
+ PF = fun(Term, I) ->
+ io_lib:format("~." ++ integer_to_list(I) ++ "p", [Term])
end,
case catch lib:format_exception(1, error, Error, Stack, StackFun, PF) of
- {'EXIT',_} ->
+ {'EXIT',_} ->
{"~p",Reason};
Formatted ->
Formatted1 = re:replace(Formatted, "exception error: ", "", [{return,list}]),
@@ -3969,8 +4019,8 @@ do_format_exception(Reason={Error,Stack}) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit,
-%% Where, MultiplyTimetrap) ->
+%% run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit,
+%% Where, TimetrapData) ->
%% {{Time,RetVal,Loc,Opts,Comment},DetectedFail,ProcessesBefore,ProcessesAfter} |
%% {{died,Reason,unknown,Comment},DetectedFail,ProcessesBefore,ProcessesAfter}
%% Name = atom()
@@ -3984,24 +4034,24 @@ do_format_exception(Reason={Error,Stack}) ->
%% ProcessesBefore = ProcessesAfter = integer()
%%
%% Where indicates if the test should run on target or always on the host.
-%%
-%% If test is to be run on target, and target is remote the request is
+%%
+%% If test is to be run on target, and target is remote the request is
%% sent over socket to target, and test_server runs the case and sends the
%% result back over the socket. Else test_server runs the case directly on host.
-run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, host, MultiplyTimetrap) ->
+run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, host, TimetrapData) ->
test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,
- MultiplyTimetrap});
-run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, MultiplyTimetrap) ->
+ TimetrapData});
+run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, TimetrapData) ->
case get(test_server_ctrl_job_sock) of
undefined ->
%% local target
test_server:run_test_case_apply({CaseNum,Mod,Func,Args,Name,RunInit,
- MultiplyTimetrap});
+ TimetrapData});
JobSock ->
%% remote target
request(JobSock, {test_case,{CaseNum,Mod,Func,Args,Name,RunInit,
- MultiplyTimetrap}}),
+ TimetrapData}}),
read_job_sock_loop(JobSock)
end.
@@ -4012,15 +4062,15 @@ run_test_case_apply(CaseNum, Mod, Func, Args, Name, RunInit, target, MultiplyTim
%% Args = [term()]
%%
%% Just like io:format, except that depending on the Detail value, the output
-%% is directed to console, major and/or minor log files.
+%% is directed to console, major and/or minor log files.
%%
%% To handle printouts to common (not minor) log files from parallel test
%% case processes, the test_server_common_io_handler value is checked. If
%% set, the data is sent to the main controlling process. Note that test
%% cases that belong to a conf group nested under a parallel group will also
%% get its io data sent to main rather than immediately printed out, even
-%% if the test cases are executed by the same, main, process (ie the main
-%% process sends messages to itself then).
+%% if the test cases are executed by the same, main, process (ie the main
+%% process sends messages to itself then).
%%
%% Buffered io is handled by the handle_test_case_io_and_status/0 function.
@@ -4040,21 +4090,21 @@ print_or_buffer(Detail, Msg, Printer) ->
output({Detail,Msg}, Printer);
MinLevel when is_number(Detail), Detail >= MinLevel ->
output({Detail,Msg}, Printer);
- _ -> % Detail < Minor | major | html
+ _ -> % Detail < Minor | major | html
case get(test_server_common_io_handler) of
- undefined ->
+ undefined ->
output({Detail,Msg}, Printer);
{_,MainPid} ->
MainPid ! {print,self(),Detail,Msg}
end
- end.
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% print_timestamp(Detail, Leader) -> ok
%%
%% Prints Leader followed by a time stamp (date and time). Depending on
%% the Detail value, the output is directed to console, major and/or minor
-%% log files.
+%% log files.
print_timestamp(Detail, Leader) ->
print(Detail, timestamp_get(Leader), []).
@@ -4288,7 +4338,7 @@ update_config(Config, []) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% collect_cases(CurMod, TopCase, SkipList) ->
+%% collect_cases(CurMod, TopCase, SkipList) ->
%% BasicCaseList | {error,Reason}
%%
%% CurMod = atom()
@@ -4319,18 +4369,18 @@ update_config(Config, []) ->
%% are listed, and each Module:all(suite) is called
%% {dir,Dir,Pattern} All modules <Pattern>_SUITE in the named dir
%% are listed, and each Module:all(suite) is called
-%% {conf,InitMF,Cases,FinMF}
-%% {conf,Props,InitMF,Cases,FinMF}
+%% {conf,InitMF,Cases,FinMF}
+%% {conf,Props,InitMF,Cases,FinMF}
%% InitMF is placed in the BasicCaseList, then
%% Cases is treated according to this table, then
%% FinMF is placed in the BasicCaseList. InitMF
%% and FinMF are configuration manipulation
%% functions. See below.
-%% {make,InitMFA,Cases,FinMFA}
+%% {make,InitMFA,Cases,FinMFA}
%% InitMFA is placed in the BasicCaseList, then
%% Cases is treated according to this table, then
%% FinMFA is placed in the BasicCaseList. InitMFA
-%% and FinMFA are make/unmake functions. If InitMFA
+%% and FinMFA are make/unmake functions. If InitMFA
%% fails, Cases are not run. InitMFA and FinMFA are
%% always run on the host - not on target.
%%
@@ -4339,7 +4389,7 @@ update_config(Config, []) ->
%%
%% [] Leaf case
%% {req,ReqList} Kept for backwards compatibility - same as []
-%% {req,ReqList,Cases} Kept for backwards compatibility -
+%% {req,ReqList,Cases} Kept for backwards compatibility -
%% Cases parsed recursively with collect_cases/3
%% Cases (list) Recursively parsed with collect_cases/3
%%
@@ -4351,7 +4401,7 @@ update_config(Config, []) ->
%% Configuration manipulation functions are called with the current
%% configuration list as only argument, and are expected to return a new
%% configuration list. Such a pair of function may, for example, start a
-%% server and stop it after a serie of test cases.
+%% server and stop it after a serie of test cases.
%%
%% SkipCases is expected to be in the format:
%%
@@ -4364,7 +4414,7 @@ update_config(Config, []) ->
skip}). % skip list
collect_all_cases(Top, Skip) when is_list(Skip) ->
- Result =
+ Result =
case collect_cases(Top, #cc{mod=[],skip=Skip}) of
{ok,Cases,_St} -> Cases;
Other -> Other
@@ -4384,7 +4434,7 @@ collect_cases([Case|Cs0], St0) ->
{error,_Reason}=Error -> Error
end;
-
+
collect_cases({module,Case}, St) when is_atom(Case), is_atom(St#cc.mod) ->
collect_case({St#cc.mod,Case}, St);
collect_cases({module,Mod,Case}, St) ->
@@ -4404,24 +4454,41 @@ collect_cases({conf,InitMF,CaseList,FinF}, St) when is_atom(FinF) ->
collect_cases({conf,InitMF,CaseList,FinMF}, St0) ->
collect_cases({conf,[],InitMF,CaseList,FinMF}, St0);
collect_cases({conf,Props,InitF,CaseList,FinMF}, St) when is_atom(InitF) ->
- collect_cases({conf,Props,{St#cc.mod,InitF},CaseList,FinMF}, St);
+ case init_props(Props) of
+ {error,_} ->
+ {ok,[],St};
+ Props1 ->
+ collect_cases({conf,Props1,{St#cc.mod,InitF},CaseList,FinMF}, St)
+ end;
collect_cases({conf,Props,InitMF,CaseList,FinF}, St) when is_atom(FinF) ->
- collect_cases({conf,Props,InitMF,CaseList,{St#cc.mod,FinF}}, St);
+ case init_props(Props) of
+ {error,_} ->
+ {ok,[],St};
+ Props1 ->
+ collect_cases({conf,Props1,InitMF,CaseList,{St#cc.mod,FinF}}, St)
+ end;
collect_cases({conf,Props,InitMF,CaseList,FinMF}, St0) ->
case collect_cases(CaseList, St0) of
- {ok,[],_St}=Empty ->
+ {ok,[],_St}=Empty ->
Empty;
{ok,FlatCases,St} ->
Ref = make_ref(),
case in_skip_list(InitMF, St#cc.skip) of
- {true,Comment} ->
- {ok,[{skip_case,{conf,Ref,InitMF,Comment}} |
+ {true,Comment} ->
+ {ok,[{skip_case,{conf,Ref,InitMF,Comment}} |
FlatCases ++ [{conf,Ref,[],FinMF}]],St};
false ->
- {ok,[{conf,Ref,Props,InitMF} |
- FlatCases ++ [{conf,Ref,keep_name(Props),FinMF}]],St}
+ case init_props(Props) of
+ {error,_} ->
+ {ok,[],St};
+ Props1 ->
+ {ok,[{conf,Ref,Props1,InitMF} |
+ FlatCases ++ [{conf,Ref,
+ keep_name(Props1),
+ FinMF}]],St}
+ end
end;
- {error,_Reason}=Error ->
+ {error,_Reason}=Error ->
Error
end;
@@ -4430,12 +4497,12 @@ collect_cases({make,InitMFA,CaseList,FinMFA}, St0) ->
{ok,[],_St}=Empty -> Empty;
{ok,FlatCases,St} ->
Ref = make_ref(),
- {ok,[{make,Ref,InitMFA}|FlatCases ++
+ {ok,[{make,Ref,InitMFA}|FlatCases ++
[{make,Ref,FinMFA}]],St};
{error,_Reason}=Error -> Error
end;
-collect_cases({Module, Cases}, St) when is_list(Cases) ->
+collect_cases({Module, Cases}, St) when is_list(Cases) ->
case (catch collect_case(Cases, St#cc{mod=Module}, [])) of
{ok, NewCases, NewSt} ->
{ok, NewCases, NewSt};
@@ -4475,9 +4542,9 @@ collect_case_invoke(Mod, Case, MFA, St) ->
case os:getenv("TEST_SERVER_FRAMEWORK") of
false ->
case catch apply(Mod, Case, [suite]) of
- {'EXIT',_} ->
+ {'EXIT',_} ->
{ok,[MFA],St};
- Suite ->
+ Suite ->
collect_subcases(Mod, Case, MFA, St, Suite)
end;
_ ->
@@ -4485,7 +4552,7 @@ collect_case_invoke(Mod, Case, MFA, St) ->
collect_subcases(Mod, Case, MFA, St, Suite)
end.
-collect_subcases(Mod, Case, MFA, St, Suite) ->
+collect_subcases(Mod, Case, MFA, St, Suite) ->
case Suite of
[] when Case == all -> {ok,[],St};
[] -> {ok,[MFA],St};
@@ -4536,7 +4603,7 @@ collect_case_deny(Mod, Case, MFA, ReqList, SubCases, St) ->
{granted,SubCases} ->
collect_case_subcases(Mod, Case, SubCases, St)
end.
-
+
check_deny([Req|Reqs], DenyList) ->
case check_deny_req(Req, DenyList) of
{denied,_Comment}=Denied -> Denied;
@@ -4560,7 +4627,7 @@ check_deny_req(Req, DenyList) ->
end.
in_skip_list({Mod,Func,_Args}, SkipList) ->
- in_skip_list({Mod,Func}, SkipList);
+ in_skip_list({Mod,Func}, SkipList);
in_skip_list({Mod,Func}, [{Mod,Funcs,Comment}|SkipList]) when is_list(Funcs) ->
case lists:member(Func, Funcs) of
true ->
@@ -4577,9 +4644,21 @@ in_skip_list({Mod,Func}, [_|SkipList]) ->
in_skip_list(_, []) ->
false.
+%% remove unnecessary properties
+init_props(Props) ->
+ case get_repeat(Props) of
+ Repeat = {_RepType,N} when N < 2 ->
+ if N == 0 ->
+ {error,{invalid_property,Repeat}};
+ true ->
+ lists:delete(Repeat, Props)
+ end;
+ _ ->
+ Props
+ end.
+
keep_name(Props) ->
lists:filter(fun({name,_}) -> true; (_) -> false end, Props).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Target node handling functions %%
@@ -4615,13 +4694,13 @@ start_node(Name, Type, Options) ->
end,
case Warning of
[] -> ok;
- _ ->
+ _ ->
format(1, Warning),
format(minor, Warning)
end,
{ok, Nodename};
{fail,{Ret, Host, Cmd}} ->
- format(minor,
+ format(minor,
"Failed to start node ~p on ~p with command: ~p~n"
"Reason: ~p",
[Name, Host, Cmd, Ret]),
@@ -4630,7 +4709,7 @@ start_node(Name, Type, Options) ->
format(minor, "Failed to start node ~p: ~p", [Name,Ret]),
Ret;
{Ret, Host, Cmd} ->
- format(minor,
+ format(minor,
"Failed to start node ~p on ~p with command: ~p~n"
"Reason: ~p",
[Name, Host, Cmd, Ret]),
@@ -4685,7 +4764,7 @@ read_job_sock_loop(Sock) ->
exit({controller,connection_lost,Reason});
{ok,<<1,Request/binary>>} ->
case decode(binary_to_term(Request)) of
- ok ->
+ ok ->
read_job_sock_loop(Sock);
{stop,Result} ->
Result
@@ -4695,14 +4774,14 @@ read_job_sock_loop(Sock) ->
decode({apply,{M,F,A}}) ->
apply(M,F,A),
ok;
-decode({sync_apply,{M,F,A}}) ->
+decode({sync_apply,{M,F,A}}) ->
R = apply(M,F,A),
request(get(test_server_ctrl_job_sock),{sync_result,R}),
ok;
decode({sync_result,Result}) ->
{stop,Result};
decode({test_case_result,Result}) ->
- {stop,Result};
+ {stop,Result};
decode({privdir,empty_priv_dir}) ->
{stop,ok};
decode({{privdir,PrivDirTar},TarBin}) ->
@@ -4742,7 +4821,7 @@ p({A,B,C}) ->
p(X) ->
pinfo(X).
-t() ->
+t() ->
t(wall_clock).
t(X) ->
element(1, statistics(X)).
@@ -4781,7 +4860,7 @@ display_info([Pid|T], R, M) ->
Other ->
Other
end,
- Reds = fetch(reductions, Info),
+ Reds = fetch(reductions, Info),
LM = length(fetch(messages, Info)),
pformat(io_lib:format("~w", [Pid]),
io_lib:format("~w", [Call]),
@@ -4822,12 +4901,12 @@ pinfo(P) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% A module is included in the cover analysis if
-%% - it belongs to the tested application and is not listed in the
+%% - it belongs to the tested application and is not listed in the
%% {exclude,List} part of the App.cover file
%% - it does not belong to the application, but is listed in the
%% {include,List} part of the App.cover file
-%% - it does not belong to the application, but is listed in the
-%% cross.cover file (in the test_server application) under 'all'
+%% - it does not belong to the application, but is listed in the
+%% cross.cover file (in the test_server application) under 'all'
%% or under the tested application.
%%
%% The modules listed in the cross.cover file are modules that are
@@ -4893,7 +4972,7 @@ read_cover_file(CoverFile) ->
io:fwrite("Faulty format of CoverFile ~p\n", [CoverFile]),
{[],[]}
end;
- {error,Reason} ->
+ {error,Reason} ->
io:fwrite("Can't read CoverFile ~p\nReason: ~p\n",
[CoverFile,Reason]),
{[],[]}
@@ -4958,7 +5037,7 @@ cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, TestDir) ->
end,
io:fwrite(CoverLog, "<p>Excluded module(s): <code>~p</code>\n", [Excluded]),
-
+
Coverage = cover_analyse(Analyse, AnalyseMods),
case lists:filter(fun({_M,{_,_,_}}) -> false;
@@ -4968,7 +5047,7 @@ cover_analyse({App,CoverInfo}, Analyse, AnalyseMods, TestDir) ->
ok;
Bad ->
io:fwrite(CoverLog, "<p>Analysis failed for ~w module(s): "
- "<code>~w</code>\n",
+ "<code>~w</code>\n",
[length(Bad),[BadM || {BadM,{_,_Why}} <- Bad]])
end,
@@ -5002,10 +5081,10 @@ cross_cover_analyse(Analyse, CrossModules) ->
CoverdataFiles = get_coverdata_files(),
lists:foreach(fun(CDF) -> cover:import(CDF) end, CoverdataFiles),
io:fwrite("Cover analysing... ", []),
- DetailsFun =
+ DetailsFun =
case Analyse of
details ->
- fun(Dir,M) ->
+ fun(Dir,M) ->
OutFile = filename:join(Dir,
atom_to_list(M) ++
".CROSS_COVER.html"),
@@ -5018,7 +5097,7 @@ cross_cover_analyse(Analyse, CrossModules) ->
SortedModules =
case CrossModules of
undefined ->
- sort_modules([Mod || Mod <- get_all_cross_modules(),
+ sort_modules([Mod || Mod <- get_all_cross_modules(),
lists:member(Mod, cover:imported_modules())], []);
_ ->
sort_modules(CrossModules, [])
@@ -5031,7 +5110,7 @@ cross_cover_analyse(Analyse, CrossModules) ->
%% cross.cover, write a cross cover log (cross_cover.html).
write_cross_cover_logs([{App,Coverage}|T]) ->
case last_test_for_app(App) of
- false ->
+ false ->
ok;
Dir ->
CoverLogName = filename:join(Dir,?cross_coverlog_name),
@@ -5045,13 +5124,13 @@ write_cross_cover_logs([{App,Coverage}|T]) ->
end,
write_cross_cover_logs(T);
write_cross_cover_logs([]) ->
- io:fwrite("done\n", []).
+ io:fwrite("done\n", []).
%% Find all exported coverdata files. First find all the latest
%% run.<timestamp> directories, and the check if there is a file named
%% all.coverdata.
get_coverdata_files() ->
- PossibleFiles = [last_coverdata_file(Dir) ||
+ PossibleFiles = [last_coverdata_file(Dir) ||
Dir <- filelib:wildcard([$*|?logdir_ext]),
filelib:is_dir(Dir)],
[File || File <- PossibleFiles, filelib:is_file(File)].
@@ -5074,12 +5153,12 @@ last_test([_|Rest], Latest) ->
last_test(Rest, Latest);
last_test([], Latest) ->
Latest.
-
+
%% Sort modules according to the application they belong to.
%% Return [{App,LastTestDir,ModuleList}]
sort_modules([M|Modules], Acc) ->
App = get_app(M),
- Acc1 =
+ Acc1 =
case lists:keysearch(App, 1, Acc) of
{value,{App,LastTest,List}} ->
lists:keyreplace(App, 1, Acc, {App,LastTest,[M|List]});
@@ -5120,9 +5199,9 @@ get_all_cross_modules() ->
get_cross_modules(all).
get_cross_modules(App) ->
case file:consult(?cross_cover_file) of
- {ok,List} ->
+ {ok,List} ->
get_cross_modules(App, List, []);
- _X ->
+ _X ->
[]
end.
@@ -5134,11 +5213,11 @@ get_cross_modules(App, [_H|T], Acc) ->
get_cross_modules(App, T, Acc);
get_cross_modules(_App, [], Acc) ->
Acc.
-
+
%% Support functions for writing the cover logs (both cross and normal)
write_coverlog_header(CoverLog) ->
- case catch
+ case catch
io:fwrite(CoverLog,
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2 Final//EN\">\n"
"<!-- autogenerated by '~w'. -->\n"
@@ -5162,13 +5241,13 @@ format_analyse(M,Cov,NotCov,undefined) ->
io_lib:fwrite("<tr><td>~w</td>"
"<td align=right>~w %</td>"
"<td align=right>~w</td>"
- "<td align=right>~w</td></tr>\n",
+ "<td align=right>~w</td></tr>\n",
[M,pc(Cov,NotCov),Cov,NotCov]);
format_analyse(M,Cov,NotCov,{file,File}) ->
io_lib:fwrite("<tr><td><a href=\"~s\">~w</a></td>"
"<td align=right>~w %</td>"
"<td align=right>~w</td>"
- "<td align=right>~w</td></tr>\n",
+ "<td align=right>~w</td></tr>\n",
[filename:basename(File),M,pc(Cov,NotCov),Cov,NotCov]);
format_analyse(M,Cov,NotCov,{lines,Lines}) ->
CoverOutName = atom_to_list(M)++".COVER.html",
@@ -5177,15 +5256,15 @@ format_analyse(M,Cov,NotCov,{lines,Lines}) ->
io_lib:fwrite("<tr><td><a href=\"~s\">~w</a></td>"
"<td align=right>~w %</td>"
"<td align=right>~w</td>"
- "<td align=right>~w</td></tr>\n",
+ "<td align=right>~w</td></tr>\n",
[CoverOutName,M,pc(Cov,NotCov),Cov,NotCov]);
format_analyse(M,Cov,NotCov,{error,_}) ->
io_lib:fwrite("<tr><td>~w</td>"
"<td align=right>~w %</td>"
"<td align=right>~w</td>"
- "<td align=right>~w</td></tr>\n",
+ "<td align=right>~w</td></tr>\n",
[M,pc(Cov,NotCov),Cov,NotCov]).
-
+
pc(0,0) ->
0;
@@ -5200,9 +5279,9 @@ write_not_covered(CoverOut,M,Lines) ->
"<table border=3 cellpadding=5>\n"
"<th>Line Number</th>\n",
[M]),
- lists:foreach(fun({{_M,Line},{0,1}}) ->
+ lists:foreach(fun({{_M,Line},{0,1}}) ->
io:fwrite(CoverOut,"<tr><td>~w</td></tr>\n", [Line]);
- (_) ->
+ (_) ->
ok
end,
Lines),
@@ -5216,7 +5295,7 @@ write_default_coverlog(TestDir) ->
file:close(CoverLog).
write_default_cross_coverlog(TestDir) ->
- {ok,CrossCoverLog} =
+ {ok,CrossCoverLog} =
file:open(filename:join(TestDir,?cross_coverlog_name), [write]),
write_coverlog_header(CrossCoverLog),
io:fwrite(CrossCoverLog,
@@ -5232,7 +5311,7 @@ write_cover_result_table(CoverLog,Coverage) ->
"<th>Not covered (Lines)</th>\n",
[]),
{TotCov,TotNotCov} =
- lists:foldl(fun({M,{Cov,NotCov,Details}},{AccCov,AccNotCov}) ->
+ lists:foldl(fun({M,{Cov,NotCov,Details}},{AccCov,AccNotCov}) ->
Str = format_analyse(M,Cov,NotCov,Details),
io:fwrite(CoverLog,"~s", [Str]),
{AccCov+Cov,AccNotCov+NotCov};
diff --git a/lib/test_server/src/test_server_internal.hrl b/lib/test_server/src/test_server_internal.hrl
index 6fa5ef75b1..c9c52854e3 100644
--- a/lib/test_server/src/test_server_internal.hrl
+++ b/lib/test_server/src/test_server_internal.hrl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2002-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2002-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%
%%
@@ -37,7 +37,7 @@
username, % string()
cookie, % string(); Cookie for target node
naming, % string(); "-name" | "-sname"
- master, % string(); For OSE this is the master
+ master, % string(); Was used for OSE's master
% node for main target and slave nodes.
% For other platforms the target node
% itself is master for slave nodes
diff --git a/lib/test_server/src/test_server_node.erl b/lib/test_server/src/test_server_node.erl
index 32886b6765..49025b1a3d 100644
--- a/lib/test_server/src/test_server_node.erl
+++ b/lib/test_server/src/test_server_node.erl
@@ -17,6 +17,7 @@
%% %CopyrightEnd%
%%
-module(test_server_node).
+-compile(r12).
%%%
%%% The same compiled code for this module must be possible to load
diff --git a/lib/test_server/src/test_server_sup.erl b/lib/test_server/src/test_server_sup.erl
index 89edb0f881..625724fbb5 100644
--- a/lib/test_server/src/test_server_sup.erl
+++ b/lib/test_server/src/test_server_sup.erl
@@ -21,7 +21,7 @@
%%% Purpose: Test server support functions.
%%%-------------------------------------------------------------------
-module(test_server_sup).
--export([timetrap/2, timetrap_cancel/1, capture_get/1, messages_get/1,
+-export([timetrap/2, timetrap/3, timetrap_cancel/1, capture_get/1, messages_get/1,
timecall/3, call_crash/5, app_test/2, check_new_crash_dumps/0,
cleanup_crash_dumps/0, crash_dump_dir/0, tar_crash_dumps/0,
get_username/0, get_os_family/0,
@@ -34,16 +34,23 @@
-define(src_listing_ext, ".src.html").
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% timetrap(Timeout,Pid) -> Handle
+%% timetrap(Timeout,Scale,Pid) -> Handle
%% Handle = term()
%%
%% Creates a time trap, that will kill the given process if the
%% trap is not cancelled with timetrap_cancel/1, within Timeout
%% milliseconds.
+%% Scale says if the time should be scaled up to compensate for
+%% delays during the test (e.g. if cover is running).
timetrap(Timeout0, Pid) ->
+ timetrap(Timeout0, true, Pid).
+
+timetrap(Timeout0, Scale, Pid) ->
process_flag(priority, max),
- Timeout = test_server:timetrap_scale_factor() * Timeout0,
+ Timeout = if not Scale -> Timeout0;
+ true -> test_server:timetrap_scale_factor() * Timeout0
+ end,
receive
after trunc(Timeout) ->
Line = test_server:get_loc(Pid),
@@ -497,6 +504,7 @@ framework_call(Callback,Func,Args,DefaultReturn) ->
end,
case erlang:function_exported(Mod,Func,length(Args)) of
true ->
+ put(test_server_loc, {Mod,Func,framework}),
EH = fun(Reason) -> exit({fw_error,{Mod,Func,Reason}}) end,
try apply(Mod,Func,Args) of
Result ->
diff --git a/lib/test_server/src/ts.erl b/lib/test_server/src/ts.erl
index 1b750c3858..e23b891392 100644
--- a/lib/test_server/src/ts.erl
+++ b/lib/test_server/src/ts.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
@@ -112,7 +112,7 @@
" Mandatory for remote targets\n"
" {master, {MasterHost, MasterCookie}}\n"
" - Master host and cookie for targets which are\n"
- " started as slave nodes (i.e. OSE/Delta targets\n"
+ " started as slave nodes.\n"
" erl_boot_server must be started on master before\n"
" test is run.\n"
" Optional, default is controller host and then\n"
diff --git a/lib/test_server/src/ts_erl_config.erl b/lib/test_server/src/ts_erl_config.erl
index 4fc46fc5d6..14c36a2c35 100644
--- a/lib/test_server/src/ts_erl_config.erl
+++ b/lib/test_server/src/ts_erl_config.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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%
%%
@@ -70,18 +70,18 @@ dl_vars(Vars, _) ->
ShlibRules = ts_lib:subst(ShlibRules0, Vars),
[{'SHLIB_RULES', ShlibRules}|Vars].
-erts_lib_name(multi_threaded, win32) ->
+erts_lib_name(multi_threaded, {win32, V}) ->
link_library("erts_MD" ++ case is_debug_build() of
true -> "d";
false -> ""
end,
- win32);
-erts_lib_name(single_threaded, win32) ->
+ {win32, V});
+erts_lib_name(single_threaded, {win32, V}) ->
link_library("erts_ML" ++ case is_debug_build() of
true -> "d";
false -> ""
end,
- win32);
+ {win32, V});
erts_lib_name(multi_threaded, OsType) ->
link_library("erts_r", OsType);
erts_lib_name(single_threaded, OsType) ->
@@ -161,7 +161,6 @@ system_include(Root, Vars) ->
case ts_lib:var(os, Vars) of
"Windows" ++ _T -> "sys/win32";
"VxWorks" -> "sys.vxworks";
- "OSE" -> "sys/ose";
_ -> "sys/unix"
end,
" -I" ++ filename:nativename(filename:join([Root, "erts", "emulator", SysDir])).
@@ -219,7 +218,7 @@ erl_interface(Vars,OsType) ->
{unix,_} ->
"-lpthread";
_ ->
- "" % VxWorks or OSE
+ "" % VxWorks
end,
CrossCompile = case OsType of
vxworks -> "true";
@@ -350,10 +349,7 @@ sock_libraries({unix, _}) ->
sock_libraries(vxworks) ->
"";
sock_libraries(ose) ->
- "";
-sock_libraries(_Other) ->
- exit({sock_libraries, not_supported}).
-
+ "".
link_library(LibName,{win32, _}) ->
LibName ++ ".lib";
diff --git a/lib/test_server/src/ts_run.erl b/lib/test_server/src/ts_run.erl
index 1d611f501c..888ac98973 100644
--- a/lib/test_server/src/ts_run.erl
+++ b/lib/test_server/src/ts_run.erl
@@ -198,8 +198,6 @@ get_spec_filename_1(Vars, TestDir, File) ->
case ts_lib:var(os, Vars) of
"VxWorks" ->
check_spec_filename(TestDir, File, ".spec.vxworks");
- "OSE" ->
- check_spec_filename(TestDir, File, ".spec.ose");
"Windows"++_ ->
check_spec_filename(TestDir, File, ".spec.win");
_Other ->
@@ -306,53 +304,36 @@ make_make(Vars, Spec, State) ->
add_make_testcase(Vars, Spec, St) ->
Makefile = St#state.makefile,
Dir = filename:dirname(Makefile),
- case ts_lib:var(os, Vars) of
- "OSE" ->
- %% For OSE, C code in datadir must be linked in the image file,
- %% and erlang code is sent as binaries from test_server_ctrl
- %% Making erlang code here because the Makefile.src probably won't
- %% work.
- Erl_flags=[{i, "../../test_server"}|ts_lib:var(erl_flags,Vars)],
- {ok, Cwd} = file:get_cwd(),
- ok = file:set_cwd(Dir),
- Result = (catch make:all(Erl_flags)),
- ok = file:set_cwd(Cwd),
- case Result of
- up_to_date -> {ok, Vars, Spec, St};
- _error -> {error, {erlang_make_failed,Dir}}
- end;
+ Shortname = filename:basename(Makefile),
+ Suite = filename:basename(Dir, "_data"),
+ Config = [{data_dir,Dir},{makefile,Shortname}],
+ MakeModule = Suite ++ "_make",
+ MakeModuleSrc = filename:join(filename:dirname(Dir),
+ MakeModule ++ ".erl"),
+ MakeMod = list_to_atom(MakeModule),
+ case filelib:is_file(MakeModuleSrc) of
+ true -> ok;
+ false -> generate_make_module(ts_lib:var(make_command, Vars),
+ MakeModuleSrc,
+ MakeModule)
+ end,
+ case Suite of
+ "all_SUITE" ->
+ {ok,Vars,Spec,St#state{all={MakeMod,Config}}};
_ ->
- Shortname = filename:basename(Makefile),
- Suite = filename:basename(Dir, "_data"),
- Config = [{data_dir,Dir},{makefile,Shortname}],
- MakeModule = Suite ++ "_make",
- MakeModuleSrc = filename:join(filename:dirname(Dir),
- MakeModule ++ ".erl"),
- MakeMod = list_to_atom(MakeModule),
- case filelib:is_file(MakeModuleSrc) of
- true -> ok;
- false -> generate_make_module(ts_lib:var(make_command, Vars),
- MakeModuleSrc,
- MakeModule)
- end,
- case Suite of
- "all_SUITE" ->
- {ok,Vars,Spec,St#state{all={MakeMod,Config}}};
- _ ->
- %% Avoid duplicates of testcases. There is no longer
- %% a check for this in test_server_ctrl.
- TestCase = {list_to_atom(Suite),all},
- TopCase0 = case St#state.topcase of
- List when is_list(List) ->
- List -- [TestCase];
- Top ->
- [Top] -- [TestCase]
- end,
- TopCase = [{make,{MakeMod,make,[Config]},
- TestCase,
- {MakeMod,unmake,[Config]}}|TopCase0],
- {ok,Vars,Spec,St#state{topcase=TopCase}}
- end
+ %% Avoid duplicates of testcases. There is no longer
+ %% a check for this in test_server_ctrl.
+ TestCase = {list_to_atom(Suite),all},
+ TopCase0 = case St#state.topcase of
+ List when is_list(List) ->
+ List -- [TestCase];
+ Top ->
+ [Top] -- [TestCase]
+ end,
+ TopCase = [{make,{MakeMod,make,[Config]},
+ TestCase,
+ {MakeMod,unmake,[Config]}}|TopCase0],
+ {ok,Vars,Spec,St#state{topcase=TopCase}}
end.
generate_make_module(MakeCmd, Name, ModuleString) ->
@@ -629,9 +610,6 @@ make_test_server_args(Args0,Options,Vars) ->
"VxWorks" ->
F = write_parameterfile(vxworks,Vars),
" PARAMETERS " ++ F;
- "OSE" ->
- F = write_parameterfile(ose,Vars),
- " PARAMETERS " ++ F;
_ ->
""
end,
diff --git a/lib/test_server/test/test_server_SUITE.erl b/lib/test_server/test/test_server_SUITE.erl
index dfe1028d3a..0563e1104f 100644
--- a/lib/test_server/test/test_server_SUITE.erl
+++ b/lib/test_server/test/test_server_SUITE.erl
@@ -183,7 +183,7 @@ multiply_timetrap(suite) -> [];
multiply_timetrap(doc) -> ["Test multiply timetrap"];
multiply_timetrap(Config) when is_list(Config) ->
%% This simulates the call to test_server_ctrl:multiply_timetraps/1:
- put(test_server_multiply_timetraps,2),
+ put(test_server_multiply_timetraps,{2,true}),
Dog = ?t:timetrap(500),
timer:sleep(800),
diff --git a/lib/test_server/vsn.mk b/lib/test_server/vsn.mk
index e3aac682ec..ee3c957d59 100644
--- a/lib/test_server/vsn.mk
+++ b/lib/test_server/vsn.mk
@@ -1,2 +1,2 @@
-TEST_SERVER_VSN = 3.3.6
+TEST_SERVER_VSN = 3.4
diff --git a/lib/tools/doc/src/eprof.xml b/lib/tools/doc/src/eprof.xml
index ae1033f2d0..6d68c90768 100644
--- a/lib/tools/doc/src/eprof.xml
+++ b/lib/tools/doc/src/eprof.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>1996</year><year>2009</year>
+ <year>1996</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>eprof</title>
@@ -35,8 +35,7 @@
used. The profiling is done using the Erlang trace BIFs. Tracing of
local function calls for a specified set of processes is enabled when
profiling is begun, and disabled when profiling is stopped.</p>
- <p>When using Eprof, expect a significant slowdown in program execution,
- in most cases at least 100 percent.</p>
+ <p>When using Eprof expect a slowdown in program execution.</p>
</description>
<funcs>
<func>
@@ -47,15 +46,19 @@
<v>Reason = {already_started,Pid}</v>
</type>
<desc>
- <p>Starts the Eprof server which owns the Eprof internal database.</p>
+ <p>Starts the Eprof server which holds the internal state of the collected data.</p>
</desc>
</func>
<func>
- <name>start_profiling(Rootset) -> profiling | error</name>
- <name>profile(Rootset) -> profiling | error</name>
+ <name>start_profiling(Rootset) -> profiling | {error, Reason}</name>
+ <name>start_profiling(Rootset,Pattern) -> profiling | {error, Reason}</name>
<fsummary>Start profiling.</fsummary>
<type>
<v>Rootset = [atom() | pid()]</v>
+ <v>Pattern = {Module, Function, Arity}</v>
+ <v>Module = Function = atom()</v>
+ <v>Arity = integer()</v>
+ <v>Reason = term()</v>
</type>
<desc>
<p>Starts profiling for the processes in <c>Rootset</c> (and any new
@@ -64,6 +67,9 @@
<p><c>Rootset</c> is a list of pids and registered names.</p>
<p>The function returns <c>profiling</c> if tracing could be enabled
for all processes in <c>Rootset</c>, or <c>error</c> otherwise.</p>
+ <p>A pattern can be selected to narrow the profiling. For instance ca a specific
+ module be selected and only the code processes executes in that module will be
+ profiled.</p>
</desc>
</func>
<func>
@@ -75,14 +81,20 @@
</desc>
</func>
<func>
- <name>profile(Rootset,Fun) -> {ok,Value} | {error,Reason} | error</name>
- <name>profile(Rootset,Module,Function,Args) -> {ok,Value} | {error,Reason} | error</name>
+ <name>profile(Fun) -> profiling | {error, Reason}</name>
+ <name>profile(Rootset) -> profiling | {error, Reason}</name>
+ <name>profile(Rootset,Fun) -> {ok, Value} | {error,Reason}</name>
+ <name>profile(Rootset,Fun,Pattern) -> {ok, Value} | {error, Reason}</name>
+ <name>profile(Rootset,Module,Function,Args) -> {ok, Value} | {error, Reason}</name>
+ <name>profile(Rootset,Module,Function,Args,Pattern) -> {ok, Value} | {error, Reason}</name>
<fsummary>Start profiling.</fsummary>
<type>
<v>Rootset = [atom() | pid()]</v>
<v>Fun = fun() -> term()</v>
+ <v>Pattern = {Module, Function, Arity}</v>
<v>Module = Function = atom()</v>
<v>Args = [term()]</v>
+ <v>Arity = integer()</v>
<v>Value = Reason = term()</v>
</type>
<desc>
@@ -96,7 +108,7 @@
<c>Rootset</c>, the function returns <c>{ok,Value}</c> when
<c>Fun()</c>/<c>apply</c> returns with the value <c>Value</c>, or
<c>{error,Reason}</c> if <c>Fun()</c>/<c>apply</c> fails with
- exit reason <c>Reason</c>. Otherwise it returns <c>error</c>
+ exit reason <c>Reason</c>. Otherwise it returns <c>{error, Reason}</c>
immediately.</p>
<p>The programmer must ensure that the function given as argument
is truly synchronous and that no work continues after
@@ -104,7 +116,15 @@
</desc>
</func>
<func>
- <name>analyse()</name>
+ <name>analyze() -> ok</name>
+ <name>analyze(Type) -> ok</name>
+ <name>analyze(Type,Options) -> ok</name>
+ <type>
+ <v>Type = procs | total</v>
+ <v>Options = [{filter, Filter} | {sort, Sort}</v>
+ <v>Filter = [{calls, integer()} | {time, float()}]</v>
+ <v>Sort = time | calls | mfa</v>
+ </type>
<fsummary>Display profiling results per process.</fsummary>
<desc>
<p>Call this function when profiling has been stopped to display
@@ -113,17 +133,10 @@
<item>how much time has been used by each process, and</item>
<item>in which function calls this time has been spent.</item>
</list>
- <p>Time is shown as percentage of total time, not as absolute time.</p>
- </desc>
- </func>
- <func>
- <name>total_analyse()</name>
- <fsummary>Display profiling results per function call.</fsummary>
- <desc>
- <p>Call this function when profiling has been stopped to display
+ <p>Call <c>analyze</c> with <c>total</c> option when profiling has been stopped to display
the results per function call, that is in which function calls
the time has been spent.</p>
- <p>Time is shown as percentage of total time, not as absolute time.</p>
+ <p>Time is shown as percentage of total time and as absolute time.</p>
</desc>
</func>
<func>
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index e7d1ae150c..b9e1ef2959 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -30,6 +30,76 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 2.6.6</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>A race condition affecting Cover has been removed.</p>
+ <p>
+ Own Id: OTP-8469</p>
+ </item>
+ <item>
+ <p>
+ Emacs improvements:</p>
+ <p>
+ Fixed emacs-mode installation problems.</p>
+ <p>
+ Fixed a couple of -spec and -type indentation and
+ font-lock problems.</p>
+ <p>
+ Fixed error messages on emacs-21.</p>
+ <p>
+ Magnus Henoch fixed several issues.</p>
+ <p>
+ Ralf Doering, Klas Johansson and Chris Bernard
+ contributed various emacs-eunit improvements.</p>
+ <p>
+ Klas Johansson and Dave Peticolas added emacs-flymake
+ support.</p>
+ <p>
+ Own Id: OTP-8530</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Xref has been updated to use the <c>re</c> module
+ instead of the deprecated <c>regexp</c> module.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8472</p>
+ </item>
+ <item>
+ <p>When given the option <c>{builtins,true}</c> Xref now
+ adds calls to operators.</p>
+ <p>
+ Own Id: OTP-8647</p>
+ </item>
+ <item>
+ <p><c>eprof</c> has been reimplemented with support in
+ the Erlang virtual machine and is now both faster (i.e.
+ slows down the code being measured less) and scales much
+ better. In measurements we saw speed-ups compared to the
+ old eprof ranging from 6 times (for sequential code that
+ only uses one scheduler/core) up to 84 times (for
+ parallel code that uses 8 cores).</p>
+ <p>Note: The API for the <c>eprof</c> has been cleaned up
+ and extended. See the documentation.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-8706</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 2.6.5.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/tools/emacs/Makefile b/lib/tools/emacs/Makefile
index 0028df247c..8533488463 100644
--- a/lib/tools/emacs/Makefile
+++ b/lib/tools/emacs/Makefile
@@ -42,6 +42,7 @@ EMACS_FILES= \
erlang_appwiz \
erlang-start \
erlang-eunit \
+ erlang-flymake \
erlang
README_FILES= README
diff --git a/lib/tools/emacs/README b/lib/tools/emacs/README
index ca068d04c4..cc107dcd41 100644
--- a/lib/tools/emacs/README
+++ b/lib/tools/emacs/README
@@ -42,7 +42,14 @@ Files\erl-<Ver>:
(setq erlang-root-dir "C:/Program Files/erl<Ver>")
(setq exec-path (cons "C:/Program Files/erl<Ver>/bin" exec-path))
(require 'erlang-start)
-
+Miscellaneous addons
+--------------------
+
+In order to check erlang source code on the fly, add the following
+line to your .emacs file (after erlang-start, see above). See
+erlang-flymake.el for more information on how to customize the syntax
+check.
+ (require 'erlang-flymake)
diff --git a/lib/tools/emacs/erlang-eunit.el b/lib/tools/emacs/erlang-eunit.el
index 05528aee6d..f2c0db67dd 100644
--- a/lib/tools/emacs/erlang-eunit.el
+++ b/lib/tools/emacs/erlang-eunit.el
@@ -1,27 +1,44 @@
;;
;; %CopyrightBegin%
-;;
-;; Copyright Ericsson AB 2009. All Rights Reserved.
-;;
+;;
+;; Copyright Ericsson AB 2009-2010. All Rights Reserved.
+;;
;; The contents of this file are subject to the Erlang Public License,
;; Version 1.1, (the "License"); you may not use this file except in
;; compliance with the License. You should have received a copy of the
;; Erlang Public License along with this software. If not, it can be
;; retrieved online at http://www.erlang.org/.
-;;
+;;
;; Software distributed under the License is distributed on an "AS IS"
;; basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
;; the License for the specific language governing rights and limitations
;; under the License.
-;;
+;;
;; %CopyrightEnd%
;;;
;;; Purpose: Provide EUnit utilities.
;;;
;;; Author: Klas Johansson
-(defvar erlang-eunit-separate-src-and-test-directories t
- "*Whether or not to keep source and EUnit test files in separate directories")
+(eval-when-compile
+ (require 'cl))
+
+(defvar erlang-eunit-src-candidate-dirs '("../src" ".")
+ "*Name of directories which to search for source files matching
+an EUnit test file. The first directory in the list will be used,
+if there is no match.")
+
+(defvar erlang-eunit-test-candidate-dirs '("../test" ".")
+ "*Name of directories which to search for EUnit test files matching
+a source file. The first directory in the list will be used,
+if there is no match.")
+
+(defvar erlang-eunit-autosave nil
+ "*Set to non-nil to automtically save unsaved buffers before running tests.
+This is useful, reducing the save-compile-load-test cycle to one keychord.")
+
+(defvar erlang-eunit-recent-info '((mode . nil) (module . nil) (test . nil) (cover . nil))
+ "Info about the most recent running of an EUnit test representation.")
;;;
;;; Switch between src/EUnit test buffers
@@ -41,7 +58,6 @@ buffer and vice versa"
"Open the EUnit test file which corresponds to a src file"
(find-file-other-window (erlang-eunit-test-filename src-file-path)))
-
;;;
;;; Open the src file which corresponds to the an EUnit test file
;;;
@@ -52,37 +68,55 @@ buffer and vice versa"
;;; Return the name and path of the EUnit test file
;;, (input may be either the source filename itself or the EUnit test filename)
(defun erlang-eunit-test-filename (file-path)
- (erlang-eunit-rewrite-filename file-path "test" "_tests"))
+ (if (erlang-eunit-test-file-p file-path)
+ file-path
+ (erlang-eunit-rewrite-filename file-path erlang-eunit-test-candidate-dirs)))
;;; Return the name and path of the source file
;;, (input may be either the source filename itself or the EUnit test filename)
(defun erlang-eunit-src-filename (file-path)
- (erlang-eunit-rewrite-filename file-path "src" ""))
+ (if (erlang-eunit-src-file-p file-path)
+ file-path
+ (erlang-eunit-rewrite-filename file-path erlang-eunit-src-candidate-dirs)))
;;; Rewrite a filename from the src or test filename to the other
-(defun erlang-eunit-rewrite-filename (orig-file-path dest-dirname dest-suffix)
- (let* ((root-dir-name (erlang-eunit-file-root-dir-name orig-file-path))
- (src-module-name (erlang-eunit-source-module-name orig-file-path))
- (dest-base-name (concat src-module-name dest-suffix ".erl"))
- (dest-dir-name-1 (file-name-directory orig-file-path))
- (dest-dir-name-2 (filename-join root-dir-name dest-dirname))
- (dest-file-name-1 (filename-join dest-dir-name-1 dest-base-name))
- (dest-file-name-2 (filename-join dest-dir-name-2 dest-base-name)))
- ;; This function tries to be a bit intelligent:
- ;; * if there already is a test (or source) file in the same
- ;; directory as a source (or test) file, it'll be picked
- ;; * if there already is a test (or source) file in a separate
- ;; test (or src) directory, it'll be picked
- ;; * otherwise it'll resort to whatever alternative (same or
- ;; separate directories) that the user has chosen
- (cond ((file-readable-p dest-file-name-1)
- dest-file-name-1)
- ((file-readable-p dest-file-name-2)
- dest-file-name-2)
- (erlang-eunit-separate-src-and-test-directories
- dest-file-name-2)
- (t
- dest-file-name-1))))
+(defun erlang-eunit-rewrite-filename (orig-file-path candidate-dirs)
+ (or (erlang-eunit-locate-buddy orig-file-path candidate-dirs)
+ (erlang-eunit-buddy-file-path orig-file-path (car candidate-dirs))))
+
+;;; Search for a file's buddy file (a source file's EUnit test file,
+;;; or an EUnit test file's source file) in a list of candidate
+;;; directories.
+(defun erlang-eunit-locate-buddy (orig-file-path candidate-dirs)
+ (when candidate-dirs
+ (let ((buddy-file-path (erlang-eunit-buddy-file-path
+ orig-file-path
+ (car candidate-dirs))))
+ (if (file-readable-p buddy-file-path)
+ buddy-file-path
+ (erlang-eunit-locate-buddy orig-file-path (cdr candidate-dirs))))))
+
+(defun erlang-eunit-buddy-file-path (orig-file-path buddy-dir-name)
+ (let* ((orig-dir-name (file-name-directory orig-file-path))
+ (buddy-dir-name (file-truename
+ (filename-join orig-dir-name buddy-dir-name)))
+ (buddy-base-name (erlang-eunit-buddy-basename orig-file-path)))
+ (filename-join buddy-dir-name buddy-base-name)))
+
+;;; Return the basename of the buddy file:
+;;; /tmp/foo/src/x.erl --> x_tests.erl
+;;; /tmp/foo/test/x_tests.erl --> x.erl
+(defun erlang-eunit-buddy-basename (file-path)
+ (let ((src-module-name (erlang-eunit-source-module-name file-path)))
+ (cond
+ ((erlang-eunit-src-file-p file-path)
+ (concat src-module-name "_tests.erl"))
+ ((erlang-eunit-test-file-p file-path)
+ (concat src-module-name ".erl")))))
+
+;;; Checks whether a file is a source file or not
+(defun erlang-eunit-src-file-p (file-path)
+ (not (erlang-eunit-test-file-p file-path)))
;;; Checks whether a file is a EUnit test file or not
(defun erlang-eunit-test-file-p (file-path)
@@ -93,23 +127,17 @@ buffer and vice versa"
;;; /tmp/foo/test/x_tests.erl --> x
(defun erlang-eunit-source-module-name (file-path)
(interactive)
- (let* ((file-name (file-name-nondirectory file-path))
- (base-name (file-name-sans-extension file-name)))
- (if (string-match "^\\(.+\\)_tests$" base-name)
- (substring base-name (match-beginning 1) (match-end 1))
- base-name)))
-
-;;; Return the directory name which is common to both src and test
-;;; /tmp/foo/src/x.erl --> /tmp/foo
-;;; /tmp/foo/test/x_tests.erl --> /tmp/foo
-(defun erlang-eunit-file-root-dir-name (file-path)
- (erlang-eunit-dir-parent-dirname (file-name-directory file-path)))
-
-;;; Return the parent directory name of a directory
-;;; /tmp/foo/ --> /tmp
-;;; /tmp/foo --> /tmp
-(defun erlang-eunit-dir-parent-dirname (dir-name)
- (file-name-directory (directory-file-name dir-name)))
+ (let ((module-name (erlang-eunit-module-name file-path)))
+ (if (string-match "^\\(.+\\)_tests$" module-name)
+ (substring module-name (match-beginning 1) (match-end 1))
+ module-name)))
+
+;;; Return the module name of the file
+;;; /tmp/foo/src/x.erl --> x
+;;; /tmp/foo/test/x_tests.erl --> x_tests
+(defun erlang-eunit-module-name (file-path)
+ (interactive)
+ (file-name-sans-extension (file-name-nondirectory file-path)))
;;; Older emacsen don't have string-match-p.
(defun erlang-eunit-string-match-p (regexp string &optional start)
@@ -125,25 +153,158 @@ buffer and vice versa"
(concat dir file)
(concat dir "/" file)))
+;;; Get info about the most recent running of EUnit
+(defun erlang-eunit-recent (key)
+ (cdr (assq key erlang-eunit-recent-info)))
+
+;;; Record info about the most recent running of EUnit
+;;; Known modes are 'module-mode and 'test-mode
+(defun erlang-eunit-record-recent (mode module test)
+ (setcdr (assq 'mode erlang-eunit-recent-info) mode)
+ (setcdr (assq 'module erlang-eunit-recent-info) module)
+ (setcdr (assq 'test erlang-eunit-recent-info) test))
+
+;;; Record whether the most recent running of EUnit included cover
+;;; compilation
+(defun erlang-eunit-record-recent-compile (under-cover)
+ (setcdr (assq 'cover erlang-eunit-recent-info) under-cover))
+
+;;; Determine options for EUnit.
+(defun erlang-eunit-opts ()
+ (if current-prefix-arg ", [verbose]" ""))
+
+;;; Determine current test function
+(defun erlang-eunit-current-test ()
+ (save-excursion
+ (erlang-end-of-function 1)
+ (erlang-beginning-of-function 1)
+ (erlang-name-of-function)))
+
+(defun erlang-eunit-simple-test-p (test-name)
+ (if (erlang-eunit-string-match-p "^\\(.+\\)_test$" test-name) t nil))
+
+(defun erlang-eunit-test-generator-p (test-name)
+ (if (erlang-eunit-string-match-p "^\\(.+\\)_test_$" test-name) t nil))
+
+;;; Run one EUnit test
+(defun erlang-eunit-run-test (module-name test-name)
+ (let ((command
+ (cond ((erlang-eunit-simple-test-p test-name)
+ (format "eunit:test({%s, %s}%s)."
+ module-name test-name (erlang-eunit-opts)))
+ ((erlang-eunit-test-generator-p test-name)
+ (format "eunit:test({generator, %s, %s}%s)."
+ module-name test-name (erlang-eunit-opts)))
+ (t (format "%% WARNING: '%s' is not a test function" test-name)))))
+ (erlang-eunit-record-recent 'test-mode module-name test-name)
+ (erlang-eunit-inferior-erlang-send-command command)))
+
;;; Run EUnit tests for the current module
-(defun erlang-eunit-run-tests ()
- "Run the EUnit test suite for the current module.
+(defun erlang-eunit-run-module-tests (module-name)
+ (let ((command (format "eunit:test(%s%s)." module-name (erlang-eunit-opts))))
+ (erlang-eunit-record-recent 'module-mode module-name nil)
+ (erlang-eunit-inferior-erlang-send-command command)))
-With prefix arg, runs tests with the verbose flag set."
+(defun erlang-eunit-compile-and-run-recent ()
+ "Compile the source and test files and repeat the most recent EUnit test run.
+
+With prefix arg, compiles for debug and runs tests with the verbose flag set."
(interactive)
- (let* ((module-name (erlang-add-quotes-if-needed
- (erlang-eunit-source-module-name buffer-file-name)))
- (opts (if current-prefix-arg ", [verbose]" ""))
- (command (format "eunit:test(%s%s)." module-name opts)))
- (erlang-eunit-inferior-erlang-send-command command)))
+ (case (erlang-eunit-recent 'mode)
+ ('test-mode
+ (erlang-eunit-compile-and-test
+ 'erlang-eunit-run-test (list (erlang-eunit-recent 'module)
+ (erlang-eunit-recent 'test))))
+ ('module-mode
+ (erlang-eunit-compile-and-test
+ 'erlang-eunit-run-module-tests (list (erlang-eunit-recent 'module))
+ (erlang-eunit-recent 'cover)))
+ (t (error "EUnit has not yet been run. Please run a test first."))))
+
+(defun erlang-eunit-cover-compile ()
+ "Cover compile current module."
+ (interactive)
+ (let* ((erlang-compile-extra-opts
+ (append (list 'debug_info) erlang-compile-extra-opts))
+ (module-name
+ (erlang-add-quotes-if-needed
+ (erlang-eunit-module-name buffer-file-name)))
+ (compile-command
+ (format "cover:compile_beam(%s)." module-name)))
+ (erlang-compile)
+ (if (erlang-eunit-last-compilation-successful-p)
+ (erlang-eunit-inferior-erlang-send-command compile-command))))
+
+(defun erlang-eunit-analyze-coverage ()
+ "Analyze the data collected by cover tool for the module in the
+current buffer.
+
+Assumes that the module has been cover compiled prior to this
+call. This function will do two things: print the number of
+covered and uncovered functions in the erlang shell and display a
+new buffer called *<module name> coverage* which shows the source
+code along with the coverage analysis results."
+ (interactive)
+ (let* ((module-name (erlang-add-quotes-if-needed
+ (erlang-eunit-module-name buffer-file-name)))
+ (tmp-filename (make-temp-file "cover"))
+ (analyze-command (format "cover:analyze_to_file(%s, \"%s\"). "
+ module-name tmp-filename))
+ (buf-name (format "*%s coverage*" module-name)))
+ (erlang-eunit-inferior-erlang-send-command analyze-command)
+ ;; The purpose of the following snippet is to get the result of the
+ ;; analysis from a file into a new buffer (or an old, if one with
+ ;; the specified name already exists). Also we want the erlang-mode
+ ;; *and* view-mode to be enabled.
+ (save-excursion
+ (let ((buf (get-buffer-create (format "*%s coverage*" module-name))))
+ (set-buffer buf)
+ (setq buffer-read-only nil)
+ (insert-file-contents tmp-filename nil nil nil t)
+ (if (= (buffer-size) 0)
+ (kill-buffer buf)
+ ;; FIXME: this would be a good place to enable (emacs-mode)
+ ;; to get some nice syntax highlighting in the
+ ;; coverage report, but it doesn't play well with
+ ;; flymake. Leave it off for now.
+ (view-buffer buf))))
+ (delete-file tmp-filename)))
+
+(defun erlang-eunit-compile-and-run-current-test ()
+ "Compile the source and test files and run the current EUnit test.
-;;; Compile source and EUnit test file and finally run EUnit tests for
-;;; the current module
-(defun erlang-eunit-compile-and-run-tests ()
- "Compile the source and test files and run the EUnit test suite.
+With prefix arg, compiles for debug and runs tests with the verbose flag set."
+ (interactive)
+ (let ((module-name (erlang-add-quotes-if-needed
+ (erlang-eunit-module-name buffer-file-name)))
+ (test-name (erlang-eunit-current-test)))
+ (erlang-eunit-compile-and-test
+ 'erlang-eunit-run-test (list module-name test-name))))
+
+(defun erlang-eunit-compile-and-run-module-tests ()
+ "Compile the source and test files and run all EUnit tests in the module.
With prefix arg, compiles for debug and runs tests with the verbose flag set."
(interactive)
+ (let ((module-name (erlang-add-quotes-if-needed
+ (erlang-eunit-source-module-name buffer-file-name))))
+ (erlang-eunit-compile-and-test
+ 'erlang-eunit-run-module-tests (list module-name))))
+
+;;; Compile source and EUnit test file and finally run EUnit tests for
+;;; the current module
+(defun erlang-eunit-compile-and-test (test-fun test-args &optional under-cover)
+ "Compile the source and test files and run the EUnit test suite.
+
+If under-cover is set to t, the module under test is compile for
+code coverage analysis. If under-cover is left out or not set,
+coverage analysis is disabled. The result of the code coverage
+is both printed to the erlang shell (the number of covered vs
+uncovered functions in a module) and written to a buffer called
+*<module> coverage* (which shows the source code for the module
+and the number of times each line is covered).
+With prefix arg, compiles for debug and runs tests with the verbose flag set."
+ (erlang-eunit-record-recent-compile under-cover)
(let ((src-filename (erlang-eunit-src-filename buffer-file-name))
(test-filename (erlang-eunit-test-filename buffer-file-name)))
@@ -151,7 +312,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set."
;; below, is to ask the question about saving buffers only once,
;; instead of possibly several: one for each file to compile,
;; for instance for both x.erl and x_tests.erl.
- (save-some-buffers)
+ (save-some-buffers erlang-eunit-autosave)
(flet ((save-some-buffers (&optional any) nil))
;; Compilation of the source file is mandatory (the file must
@@ -159,23 +320,56 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set."
;; test file on the other hand, is optional, since eunit tests may
;; be placed in the source file instead. Any compilation error
;; will prevent the subsequent steps to be run (hence the `and')
- (and (erlang-eunit-compile-file src-filename)
+ (and (erlang-eunit-compile-file src-filename under-cover)
(if (file-readable-p test-filename)
(erlang-eunit-compile-file test-filename)
t)
- (erlang-eunit-run-tests)))))
+ (apply test-fun test-args)
+ (if under-cover
+ (save-excursion
+ (set-buffer (find-file-noselect src-filename))
+ (erlang-eunit-analyze-coverage)))))))
+
+(defun erlang-eunit-compile-and-run-module-tests-under-cover ()
+ "Compile the source and test files and run the EUnit test suite and measure
+code coverage.
-(defun erlang-eunit-compile-file (file-path)
+With prefix arg, compiles for debug and runs tests with the verbose flag set."
+ (interactive)
+ (let ((module-name (erlang-add-quotes-if-needed
+ (erlang-eunit-source-module-name buffer-file-name))))
+ (erlang-eunit-compile-and-test
+ 'erlang-eunit-run-module-tests (list module-name) t)))
+
+(defun erlang-eunit-compile-file (file-path &optional under-cover)
(if (file-readable-p file-path)
(save-excursion
- (set-buffer (find-file-noselect file-path))
- (erlang-compile)
- (erlang-eunit-last-compilation-successful-p))
+ (set-buffer (find-file-noselect file-path))
+ ;; In order to run a code coverage analysis on a
+ ;; module, we have two options:
+ ;;
+ ;; * either compile the module with cover:compile instead of the
+ ;; regular compiler
+ ;;
+ ;; * or first compile the module with the regular compiler (but
+ ;; *with* debug_info) and then compile it for coverage
+ ;; analysis using cover:compile_beam.
+ ;;
+ ;; We could accomplish the first by changing the
+ ;; erlang-compile-erlang-function to cover:compile, but there's
+ ;; a risk that that's used for other purposes. Therefore, a
+ ;; safer alternative (although with more steps) is to add
+ ;; debug_info to the list of compiler options and go for the
+ ;; second alternative.
+ (if under-cover
+ (erlang-eunit-cover-compile)
+ (erlang-compile))
+ (erlang-eunit-last-compilation-successful-p))
(let ((msg (format "Could not read %s" file-path)))
- (erlang-eunit-inferior-erlang-send-command
+ (erlang-eunit-inferior-erlang-send-command
(format "%% WARNING: %s" msg))
(error msg))))
-
+
(defun erlang-eunit-last-compilation-successful-p ()
(save-excursion
(set-buffer inferior-erlang-buffer)
@@ -184,7 +378,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set."
(lambda (re) (let ((continue t)
(result t))
(while continue ; ignore warnings, stop at errors
- (if (re-search-forward re (point-max) t)
+ (if (re-search-forward re (point-max) t)
(if (erlang-eunit-is-compilation-warning)
t
(setq result nil)
@@ -195,7 +389,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set."
(mapcar (lambda (e) (car e)) erlang-error-regexp-alist))))
(defun erlang-eunit-is-compilation-warning ()
- (erlang-eunit-string-match-p
+ (erlang-eunit-string-match-p
"[0-9]+: Warning:"
(buffer-substring (line-beginning-position) (line-end-position))))
@@ -221,22 +415,22 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set."
;;; Key bindings
;;;====================================================================
-(defvar erlang-eunit-toggle-src-and-test-file-other-window-key "\C-c\C-et"
- "*Key to which the `erlang-eunit-toggle-src-and-test-file-other-window'
-function will be bound.")
-(defvar erlang-eunit-compile-and-run-tests-key "\C-c\C-ek"
- "*Key to which the `erlang-eunit-compile-and-run-tests'
-function will be bound.")
+(defconst erlang-eunit-key-bindings
+ '(("\C-c\C-et" erlang-eunit-toggle-src-and-test-file-other-window)
+ ("\C-c\C-ek" erlang-eunit-compile-and-run-module-tests)
+ ("\C-c\C-ej" erlang-eunit-compile-and-run-current-test)
+ ("\C-c\C-el" erlang-eunit-compile-and-run-recent)
+ ("\C-c\C-ec" erlang-eunit-compile-and-run-module-tests-under-cover)
+ ("\C-c\C-ev" erlang-eunit-cover-compile)
+ ("\C-c\C-ea" erlang-eunit-analyze-coverage)))
(defun erlang-eunit-add-key-bindings ()
- (erlang-eunit-ensure-keymap-for-key
- erlang-eunit-toggle-src-and-test-file-other-window-key)
- (local-set-key erlang-eunit-toggle-src-and-test-file-other-window-key
- 'erlang-eunit-toggle-src-and-test-file-other-window)
- (erlang-eunit-ensure-keymap-for-key
- erlang-eunit-compile-and-run-tests-key)
- (local-set-key erlang-eunit-compile-and-run-tests-key
- 'erlang-eunit-compile-and-run-tests))
+ (dolist (binding erlang-eunit-key-bindings)
+ (erlang-eunit-bind-key (car binding) (cadr binding))))
+
+(defun erlang-eunit-bind-key (key function)
+ (erlang-eunit-ensure-keymap-for-key key)
+ (local-set-key key function))
(defun erlang-eunit-ensure-keymap-for-key (key-seq)
(let ((prefix-keys (butlast (append key-seq nil)))
diff --git a/lib/tools/emacs/erlang-flymake.el b/lib/tools/emacs/erlang-flymake.el
new file mode 100644
index 0000000000..bc368e9454
--- /dev/null
+++ b/lib/tools/emacs/erlang-flymake.el
@@ -0,0 +1,102 @@
+;; erlang-flymake.el
+;;
+;; Syntax check erlang source code on the fly (integrates with flymake).
+;;
+;; Start using flymake with erlang by putting the following somewhere
+;; in your .emacs file:
+;;
+;; (require 'erlang-flymake)
+;;
+;; Flymake is rather eager and does its syntax checks frequently by
+;; default and if you are bothered by this, you might want to put the
+;; following in your .emacs as well:
+;;
+;; (erlang-flymake-only-on-save)
+;;
+;; There are a couple of variables which control the compilation options:
+;; * erlang-flymake-get-code-path-dirs-function
+;; * erlang-flymake-get-include-dirs-function
+;; * erlang-flymake-extra-opts
+;;
+;; This code is inspired by http://www.emacswiki.org/emacs/FlymakeErlang.
+
+(require 'flymake)
+(eval-when-compile
+ (require 'cl))
+
+(defvar erlang-flymake-command
+ "erlc"
+ "The command that will be used to perform the syntax check")
+
+(defvar erlang-flymake-get-code-path-dirs-function
+ 'erlang-flymake-get-code-path-dirs
+ "Return a list of ebin directories to add to the code path.")
+
+(defvar erlang-flymake-get-include-dirs-function
+ 'erlang-flymake-get-include-dirs
+ "Return a list of include directories to add to the compiler options.")
+
+(defvar erlang-flymake-extra-opts
+ (list "+warn_obsolete_guard"
+ "+warn_unused_import"
+ "+warn_shadow_vars"
+ "+warn_export_vars"
+ "+strong_validation"
+ "+report")
+ "A list of options that will be passed to the compiler")
+
+(defun erlang-flymake-only-on-save ()
+ "Trigger flymake only when the buffer is saved (disables syntax
+check on newline and when there are no changes)."
+ (interactive)
+ ;; There doesn't seem to be a way of disabling this; set to the
+ ;; largest int available as a workaround (most-positive-fixnum
+ ;; equates to 8.5 years on my machine, so it ought to be enough ;-) )
+ (setq flymake-no-changes-timeout most-positive-fixnum)
+ (setq flymake-start-syntax-check-on-newline nil))
+
+
+(defun erlang-flymake-get-code-path-dirs ()
+ (list (concat (erlang-flymake-get-app-dir) "ebin")))
+
+(defun erlang-flymake-get-include-dirs ()
+ (list (concat (erlang-flymake-get-app-dir) "include")))
+
+(defun erlang-flymake-get-app-dir ()
+ (let ((src-path (file-name-directory (buffer-file-name))))
+ (file-name-directory (directory-file-name src-path))))
+
+(defun erlang-flymake-init ()
+ (let* ((temp-file
+ (flet ((flymake-get-temp-dir () (erlang-flymake-temp-dir)))
+ (flymake-init-create-temp-buffer-copy
+ 'flymake-create-temp-with-folder-structure)))
+ (code-dir-opts
+ (erlang-flymake-flatten
+ (mapcar (lambda (dir) (list "-pa" dir))
+ (funcall erlang-flymake-get-code-path-dirs-function))))
+ (inc-dir-opts
+ (erlang-flymake-flatten
+ (mapcar (lambda (dir) (list "-I" dir))
+ (funcall erlang-flymake-get-include-dirs-function))))
+ (compile-opts
+ (append inc-dir-opts
+ code-dir-opts
+ erlang-flymake-extra-opts)))
+ (list erlang-flymake-command (append compile-opts (list temp-file)))))
+
+(defun erlang-flymake-temp-dir ()
+ ;; Squeeze the user's name in there in order to make sure that files
+ ;; for two users who are working on the same computer (like a linux
+ ;; box) don't collide
+ (format "%s/flymake-%s" temporary-file-directory user-login-name))
+
+(defun erlang-flymake-flatten (list)
+ (apply #'append list))
+
+(add-to-list 'flymake-allowed-file-name-masks
+ '("\\.erl\\'" erlang-flymake-init))
+(add-hook 'erlang-mode-hook 'flymake-mode)
+
+(provide 'erlang-flymake)
+;; erlang-flymake ends here
diff --git a/lib/tools/emacs/erlang-start.el b/lib/tools/emacs/erlang-start.el
index 542e81f24c..bbcea3e46a 100644
--- a/lib/tools/emacs/erlang-start.el
+++ b/lib/tools/emacs/erlang-start.el
@@ -90,6 +90,11 @@
(or (assoc (car b) auto-mode-alist)
(setq auto-mode-alist (cons b auto-mode-alist))))
+;;
+;; Associate files using interpreter "escript" with Erlang mode.
+;;
+
+(add-to-list 'interpreter-mode-alist (cons "escript" 'erlang-mode))
;;
;; Ignore files ending in ".jam", ".vee", and ".beam" when performing
diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el
index a84f40244d..91acfdf2b6 100644
--- a/lib/tools/emacs/erlang.el
+++ b/lib/tools/emacs/erlang.el
@@ -659,24 +659,30 @@ resulting regexp is surrounded by \\_< and \\_>."
(eval-and-compile
(defconst erlang-guards-regexp (erlang-regexp-opt erlang-guards 'symbols)))
-
(eval-and-compile
(defvar erlang-predefined-types
'("any"
"arity"
+ "boolean"
"byte"
"char"
"cons"
"deep_string"
+ "iolist"
"maybe_improper_list"
+ "module"
"mfa"
"nil"
+ "neg_integer"
"none"
"non_neg_integer"
"nonempty_list"
"nonempty_improper_list"
"nonempty_maybe_improper_list"
+ "no_return"
+ "pos_integer"
"string"
+ "term"
"timeout")
"Erlang type specs types"))
@@ -885,15 +891,54 @@ files written in other languages than Erlang.")
If nil, the inferior shell replaces the window. This is the traditional
behaviour.")
-(defvar erlang-mode-map nil
+(defconst inferior-erlang-use-cmm (boundp 'minor-mode-overriding-map-alist)
+ "Non-nil means use `compilation-minor-mode' in Erlang shell.")
+
+(defvar erlang-mode-map
+ (let ((map (make-sparse-keymap)))
+ (unless (boundp 'indent-line-function)
+ (define-key map "\t" 'erlang-indent-command))
+ (define-key map ";" 'erlang-electric-semicolon)
+ (define-key map "," 'erlang-electric-comma)
+ (define-key map "<" 'erlang-electric-lt)
+ (define-key map ">" 'erlang-electric-gt)
+ (define-key map "\C-m" 'erlang-electric-newline)
+ (if (not (boundp 'delete-key-deletes-forward))
+ (define-key map "\177" 'backward-delete-char-untabify)
+ (define-key map [(backspace)] 'backward-delete-char-untabify))
+ ;;(unless (boundp 'fill-paragraph-function)
+ (define-key map "\M-q" 'erlang-fill-paragraph)
+ (unless (boundp 'beginning-of-defun-function)
+ (define-key map "\M-\C-a" 'erlang-beginning-of-function)
+ (define-key map "\M-\C-e" 'erlang-end-of-function)
+ (define-key map '(meta control h) 'erlang-mark-function)) ; Xemacs
+ (define-key map "\M-\t" 'erlang-complete-tag)
+ (define-key map "\C-c\M-\t" 'tempo-complete-tag)
+ (define-key map "\M-+" 'erlang-find-next-tag)
+ (define-key map "\C-c\M-a" 'erlang-beginning-of-clause)
+ (define-key map "\C-c\M-b" 'tempo-backward-mark)
+ (define-key map "\C-c\M-e" 'erlang-end-of-clause)
+ (define-key map "\C-c\M-f" 'tempo-forward-mark)
+ (define-key map "\C-c\M-h" 'erlang-mark-clause)
+ (define-key map "\C-c\C-c" 'comment-region)
+ (define-key map "\C-c\C-j" 'erlang-generate-new-clause)
+ (define-key map "\C-c\C-k" 'erlang-compile)
+ (define-key map "\C-c\C-l" 'erlang-compile-display)
+ (define-key map "\C-c\C-s" 'erlang-show-syntactic-information)
+ (define-key map "\C-c\C-q" 'erlang-indent-function)
+ (define-key map "\C-c\C-u" 'erlang-uncomment-region)
+ (define-key map "\C-c\C-y" 'erlang-clone-arguments)
+ (define-key map "\C-c\C-a" 'erlang-align-arrows)
+ (define-key map "\C-c\C-z" 'erlang-shell-display)
+ (unless inferior-erlang-use-cmm
+ (define-key map "\C-x`" 'erlang-next-error))
+ map)
"*Keymap used in Erlang mode.")
(defvar erlang-mode-abbrev-table nil
"Abbrev table in use in Erlang-mode buffers.")
(defvar erlang-mode-syntax-table nil
"Syntax table in use in Erlang-mode buffers.")
-(defconst inferior-erlang-use-cmm (boundp 'minor-mode-overriding-map-alist)
- "Non-nil means use `compilation-minor-mode' in Erlang shell.")
(defvar erlang-skel-file "erlang-skels"
@@ -988,7 +1033,7 @@ behaviour.")
(list (concat "^\\(-" erlang-atom-regexp "\\)\\(\\s-\\|\\.\\|(\\)")
1 (if (boundp 'font-lock-preprocessor-face)
'font-lock-preprocessor-face
- 'font-lock-function-name-face)))
+ 'font-lock-constant-face)))
"Font lock keyword highlighting attributes.")
(defvar erlang-font-lock-keywords-quotes
@@ -1019,10 +1064,12 @@ are highlighted by syntactic analysis.")
(list
(list (concat "?\\s-*\\(" erlang-atom-regexp
"\\|" erlang-variable-regexp "\\)")
- 1 'font-lock-type-face)
+ 1 'font-lock-constant-face)
(list (concat "^\\(-\\(?:define\\|ifn?def\\)\\)\\s-*(\\s-*\\(" erlang-atom-regexp
"\\|" erlang-variable-regexp "\\)")
- (list 1 'font-lock-preprocessor-face t)
+ (if (boundp 'font-lock-preprocessor-face)
+ (list 1 'font-lock-preprocessor-face t)
+ (list 1 'font-lock-constant-face t))
(list 3 'font-lock-type-face t t))
(list "^-e\\(lse\\|ndif\\)\\>" 0 'font-lock-preprocessor-face t))
"Font lock keyword highlighting macros.
@@ -1245,7 +1292,7 @@ Other commands:
(setq major-mode 'erlang-mode)
(setq mode-name "Erlang")
(erlang-syntax-table-init)
- (erlang-keymap-init)
+ (use-local-map erlang-mode-map)
(erlang-electric-init)
(erlang-menu-init)
(erlang-mode-variables)
@@ -1300,53 +1347,6 @@ Other commands:
(set-syntax-table erlang-mode-syntax-table))
-(defun erlang-keymap-init ()
- (if erlang-mode-map
- nil
- (setq erlang-mode-map (make-sparse-keymap))
- (erlang-mode-commands erlang-mode-map))
- (use-local-map erlang-mode-map))
-
-
-(defun erlang-mode-commands (map)
- (unless (boundp 'indent-line-function)
- (define-key map "\t" 'erlang-indent-command))
- (define-key map ";" 'erlang-electric-semicolon)
- (define-key map "," 'erlang-electric-comma)
- (define-key map "<" 'erlang-electric-lt)
- (define-key map ">" 'erlang-electric-gt)
- (define-key map "\C-m" 'erlang-electric-newline)
- (if (not (boundp 'delete-key-deletes-forward))
- (define-key map "\177" 'backward-delete-char-untabify)
- (define-key map [(backspace)] 'backward-delete-char-untabify))
- ;;(unless (boundp 'fill-paragraph-function)
- (define-key map "\M-q" 'erlang-fill-paragraph)
- (unless (boundp 'beginning-of-defun-function)
- (define-key map "\M-\C-a" 'erlang-beginning-of-function)
- (define-key map "\M-\C-e" 'erlang-end-of-function)
- (define-key map '(meta control h) 'erlang-mark-function)) ; Xemacs
- (define-key map "\M-\t" 'erlang-complete-tag)
- (define-key map "\C-c\M-\t" 'tempo-complete-tag)
- (define-key map "\M-+" 'erlang-find-next-tag)
- (define-key map "\C-c\M-a" 'erlang-beginning-of-clause)
- (define-key map "\C-c\M-b" 'tempo-backward-mark)
- (define-key map "\C-c\M-e" 'erlang-end-of-clause)
- (define-key map "\C-c\M-f" 'tempo-forward-mark)
- (define-key map "\C-c\M-h" 'erlang-mark-clause)
- (define-key map "\C-c\C-c" 'comment-region)
- (define-key map "\C-c\C-j" 'erlang-generate-new-clause)
- (define-key map "\C-c\C-k" 'erlang-compile)
- (define-key map "\C-c\C-l" 'erlang-compile-display)
- (define-key map "\C-c\C-s" 'erlang-show-syntactic-information)
- (define-key map "\C-c\C-q" 'erlang-indent-function)
- (define-key map "\C-c\C-u" 'erlang-uncomment-region)
- (define-key map "\C-c\C-y" 'erlang-clone-arguments)
- (define-key map "\C-c\C-a" 'erlang-align-arrows)
- (define-key map "\C-c\C-z" 'erlang-shell-display)
- (unless inferior-erlang-use-cmm
- (define-key map "\C-x`" 'erlang-next-error)))
-
-
(defun erlang-electric-init ()
;; Set up electric character functions to work with
;; delsel/pending-del mode. Also, set up text properties for bit
@@ -1400,7 +1400,7 @@ Other commands:
(set (make-local-variable 'imenu-prev-index-position-function)
'erlang-beginning-of-function)
(set (make-local-variable 'imenu-extract-index-name-function)
- 'erlang-get-function-name)
+ 'erlang-get-function-name-and-arity)
(set (make-local-variable 'tempo-match-finder)
"[^-a-zA-Z0-9_]\\([-a-zA-Z0-9_]*\\)\\=")
(set (make-local-variable 'beginning-of-defun-function)
@@ -2490,9 +2490,10 @@ Value is list (stack token-start token-type in-what)."
((looking-at "\\(of\\)[^_a-zA-Z0-9]")
;; Must handle separately, try X of -> catch
(if (and stack (eq (car (car stack)) 'try))
- (let ((try-column (nth 2 (car stack))))
+ (let ((try-column (nth 2 (car stack)))
+ (try-pos (nth 1 (car stack))))
(erlang-pop stack)
- (erlang-push (list 'icr token try-column) stack))))
+ (erlang-push (list 'icr try-pos try-column) stack))))
((looking-at "\\(fun\\)[^_a-zA-Z0-9]")
;; Push a new layer if we are defining a `fun'
@@ -2753,7 +2754,7 @@ Return nil if inside string, t if in a comment."
;;
;; `after' should be indented to the same level as the
;; corresponding receive.
- (cond ((looking-at "\\(after\\|catch\\|of\\)\\($\\|[^_a-zA-Z0-9]\\)")
+ (cond ((looking-at "\\(after\\|of\\)\\($\\|[^_a-zA-Z0-9]\\)")
(nth 2 stack-top))
((looking-at "when[^_a-zA-Z0-9]")
;; Handling one when part
@@ -2772,7 +2773,7 @@ Return nil if inside string, t if in a comment."
((and (eq (car stack-top) '||) (looking-at "\\(]\\|>>\\)[^_a-zA-Z0-9]"))
(nth 2 (car (cdr stack))))
;; Real indentation, where operators create extra indentation etc.
- ((memq (car stack-top) '(-> || begin try))
+ ((memq (car stack-top) '(-> || try begin))
(if (looking-at "\\(of\\)[^_a-zA-Z0-9]")
(nth 2 stack-top)
(goto-char (nth 1 stack-top))
@@ -2801,19 +2802,24 @@ Return nil if inside string, t if in a comment."
(erlang-caddr (car stack))
0))
((looking-at "catch\\($\\|[^_a-zA-Z0-9]\\)")
- (if (or (eq (car stack-top) 'try)
- (eq (car (car (cdr stack))) 'icr))
- (progn
- (if (eq (car stack-top) '->)
- (erlang-pop stack))
- (if stack
- (erlang-caddr (car stack))
- 0))
- base)) ;; old catch
+ ;; Are we in a try
+ (let ((start (if (eq (car stack-top) '->)
+ (car (cdr stack))
+ stack-top)))
+ (if (null start) nil
+ (goto-char (nth 1 start)))
+ (cond ((looking-at "try\\($\\|[^_a-zA-Z0-9]\\)")
+ (progn
+ (if (eq (car stack-top) '->)
+ (erlang-pop stack))
+ (if stack
+ (erlang-caddr (car stack))
+ 0)))
+ (t (erlang-indent-standard indent-point token base 'nil))))) ;; old catch
(t
(erlang-indent-standard indent-point token base 'nil)
))))
- ))
+ ))
((eq (car stack-top) 'when)
(goto-char (nth 1 stack-top))
(if (looking-at "when\\s *\\($\\|%\\)")
@@ -2839,27 +2845,32 @@ Return nil if inside string, t if in a comment."
(current-column)))
;; Type and Spec indentation
((eq (car stack-top) '::)
- (cond ((null erlang-argument-indent)
- ;; indent to next column.
- (+ 2 (nth 2 stack-top)))
- ((looking-at "::[^_a-zA-Z0-9]")
- (nth 2 stack-top))
- (t
- (let ((start-alternativ (if (looking-at "|") 2 0)))
- (goto-char (nth 1 stack-top))
- (- (cond ((looking-at "::\\s *\\($\\|%\\)")
- ;; Line ends with ::
- (if (eq (car (car (last stack))) 'spec)
+ (if (looking-at "}")
+ ;; Closing record definition with types
+ ;; pop stack and recurse
+ (erlang-calculate-stack-indent indent-point
+ (cons (erlang-pop stack) (cdr state)))
+ (cond ((null erlang-argument-indent)
+ ;; indent to next column.
+ (+ 2 (nth 2 stack-top)))
+ ((looking-at "::[^_a-zA-Z0-9]")
+ (nth 2 stack-top))
+ (t
+ (let ((start-alternativ (if (looking-at "|") 2 0)))
+ (goto-char (nth 1 stack-top))
+ (- (cond ((looking-at "::\\s *\\($\\|%\\)")
+ ;; Line ends with ::
+ (if (eq (car (car (last stack))) 'spec)
(+ (erlang-indent-find-preceding-expr 1)
erlang-argument-indent)
- (+ (erlang-indent-find-preceding-expr 2)
- erlang-argument-indent)))
- (t
- ;; Indent to the same column as the first
- ;; argument.
- (goto-char (+ 2 (nth 1 stack-top)))
- (skip-chars-forward " \t")
- (current-column))) start-alternativ)))))
+ (+ (erlang-indent-find-preceding-expr 2)
+ erlang-argument-indent)))
+ (t
+ ;; Indent to the same column as the first
+ ;; argument.
+ (goto-char (+ 2 (nth 1 stack-top)))
+ (skip-chars-forward " \t")
+ (current-column))) start-alternativ))))))
)))
(defun erlang-indent-standard (indent-point token base inside-parenthesis)
@@ -2931,10 +2942,16 @@ This assumes that the preceding expression is either simple
(skip-chars-backward " \t")
;; Needed to match the colon in "'foo':'bar'".
(if (not (memq (preceding-char) '(?# ?:)))
- col
- (backward-char 1)
- (forward-sexp -1)
- (current-column)))))
+ col
+ ;; Special hack to handle: (note line break)
+ ;; [#myrecord{
+ ;; foo = foo}]
+ (or
+ (ignore-errors
+ (backward-char 1)
+ (forward-sexp -1)
+ (current-column))
+ col)))))
(defun erlang-indent-parenthesis (stack-position)
(let ((previous (erlang-indent-find-preceding-expr)))
@@ -3503,6 +3520,13 @@ Normally used in conjunction with `erlang-beginning-of-clause', e.g.:
res)
(error nil)))))
+(defun erlang-get-function-name-and-arity ()
+ "Return the name and arity of the function at point, or nil.
+The return value is a string of the form \"foo/1\"."
+ (let ((name (erlang-get-function-name))
+ (arity (erlang-get-function-arity)))
+ (and name arity (format "%s/%d" name arity))))
+
(defun erlang-get-function-arguments ()
"Return arguments of current function, or nil."
(if (not (looking-at (eval-when-compile
@@ -4899,9 +4923,14 @@ a prompt. When nil, we will wait forever, or until \\[keyboard-quit].")
(defvar inferior-erlang-buffer nil
"Buffer of last invoked inferior Erlang, or nil.")
+;; Enable uniquifying Erlang shell buffers based on directory name.
+(eval-after-load "uniquify"
+ '(add-to-list 'uniquify-list-buffers-directory-modes 'erlang-shell-mode))
+
;;;###autoload
-(defun inferior-erlang ()
+(defun inferior-erlang (&optional command)
"Run an inferior Erlang.
+With prefix command, prompt for command to start Erlang with.
This is just like running Erlang in a normal shell, except that
an Emacs buffer is used for input and output.
@@ -4915,17 +4944,37 @@ Entry to this mode calls the functions in the variables
The following commands imitate the usual Unix interrupt and
editing control characters:
\\{erlang-shell-mode-map}"
- (interactive)
+ (interactive
+ (when current-prefix-arg
+ (list (if (fboundp 'read-shell-command)
+ ;; `read-shell-command' is a new function in Emacs 23.
+ (read-shell-command "Erlang command: ")
+ (read-string "Erlang command: ")))))
(require 'comint)
- (let ((opts inferior-erlang-machine-options))
- (cond ((eq inferior-erlang-shell-type 'oldshell)
- (setq opts (cons "-oldshell" opts)))
- ((eq inferior-erlang-shell-type 'newshell)
- (setq opts (append '("-newshell" "-env" "TERM" "vt100") opts))))
- (setq inferior-erlang-buffer
- (apply 'make-comint
- inferior-erlang-process-name inferior-erlang-machine
- nil opts)))
+ (let (cmd opts)
+ (if command
+ (setq cmd "sh"
+ opts (list "-c" command))
+ (setq cmd inferior-erlang-machine
+ opts inferior-erlang-machine-options)
+ (cond ((eq inferior-erlang-shell-type 'oldshell)
+ (setq opts (cons "-oldshell" opts)))
+ ((eq inferior-erlang-shell-type 'newshell)
+ (setq opts (append '("-newshell" "-env" "TERM" "vt100") opts)))))
+
+ ;; Using create-file-buffer and list-buffers-directory in this way
+ ;; makes uniquify give each buffer a unique name based on the
+ ;; directory.
+ (let ((fake-file-name (expand-file-name inferior-erlang-buffer-name default-directory)))
+ (setq inferior-erlang-buffer (create-file-buffer fake-file-name))
+ (apply 'make-comint-in-buffer
+ inferior-erlang-process-name
+ inferior-erlang-buffer
+ cmd
+ nil opts)
+ (with-current-buffer inferior-erlang-buffer
+ (setq list-buffers-directory fake-file-name))))
+
(setq inferior-erlang-process
(get-buffer-process inferior-erlang-buffer))
(if (> 21 erlang-emacs-major-version) ; funcalls to avoid compiler warnings
@@ -4938,10 +4987,6 @@ editing control characters:
(if (and (not (eq system-type 'windows-nt))
(eq inferior-erlang-shell-type 'newshell))
(setq comint-process-echoes t))
- ;; `rename-buffer' takes only one argument in Emacs 18.
- (condition-case nil
- (rename-buffer inferior-erlang-buffer-name t)
- (error (rename-buffer inferior-erlang-buffer-name)))
(erlang-shell-mode))
diff --git a/lib/tools/emacs/test.erl.indented b/lib/tools/emacs/test.erl.indented
index d0ea4c29cf..2948ccf1b5 100644
--- a/lib/tools/emacs/test.erl.indented
+++ b/lib/tools/emacs/test.erl.indented
@@ -93,11 +93,27 @@
-type t13() :: maybe_improper_list(integer(), t11()).
-type t14() :: [erl_scan:foo() |
%% Should be highlighted
- non_neg_integer() | nonempty_list() |
+ term() |
+ bool() |
+ byte() |
+ char() |
+ non_neg_integer() | nonempty_list() |
+ pos_integer() |
+ neg_integer() |
+ number() |
+ list() |
nonempty_improper_list() | nonempty_maybe_improper_list() |
+ maybe_improper_list() | string() | iolist() | byte() |
+ module() |
+ mfa() |
+ node() |
+ timeout() |
+ no_return() |
%% Should not be highlighted
nonempty_() | nonlist() |
- erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)].
+ erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)].
+
+
-type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>,
<<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>|
<<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>|
@@ -172,6 +188,9 @@
f19 = 3 :: integer()|undefined,
f5 = 3 :: undefined|integer()}).
+-record(state, {
+ sequence_number = 1 :: integer()
+ }).
highlighting(X) % Function definitions should be highlighted
@@ -493,7 +512,9 @@ indent_try_catch() ->
file:close(Xfile)
end;
indent_try_catch() ->
- try foo(bar) of
+ try
+ foo(bar)
+ of
X when true andalso
kalle ->
io:format(stdout, "Parsing file ~s, ",
@@ -551,14 +572,57 @@ indent_catch() ->
C = catch B +
float(43.1),
- case catch (X) of
+ case catch foo(X) of
+ A ->
+ B
+ end,
+
+ case
+ catch foo(X)
+ of
A ->
B
end,
+
+ case
+ foo(X)
+ of
+ A ->
+ catch B,
+ X
+ end,
+
try sune of
_ -> foo
catch _:_ -> baf
- end.
+ end,
+
+ try
+ sune
+ of
+ _ ->
+ X = 5,
+ (catch foo(X)),
+ X + 10
+ catch _:_ -> baf
+ end,
+
+ try
+ (catch sune)
+ of
+ _ ->
+ catch foo() %% BUGBUG can't handle catch inside try without parentheses
+ catch _:_ ->
+ baf
+ end,
+
+ try
+ (catch exit())
+ catch
+ _ ->
+ catch baf()
+ end,
+ ok.
indent_binary() ->
X = lists:foldr(fun(M) ->
@@ -588,3 +652,8 @@ indent_comprehensions() ->
true = (X rem 2)
>>,
ok.
+
+%% This causes an error in earlier erlang-mode versions.
+foo() ->
+ [#foo{
+ foo = foo}].
diff --git a/lib/tools/emacs/test.erl.orig b/lib/tools/emacs/test.erl.orig
index 70e97a2e91..1221c5655e 100644
--- a/lib/tools/emacs/test.erl.orig
+++ b/lib/tools/emacs/test.erl.orig
@@ -93,11 +93,27 @@
-type t13() :: maybe_improper_list(integer(), t11()).
-type t14() :: [erl_scan:foo() |
%% Should be highlighted
- non_neg_integer() | nonempty_list() |
+ term() |
+ bool() |
+ byte() |
+ char() |
+ non_neg_integer() | nonempty_list() |
+ pos_integer() |
+ neg_integer() |
+ number() |
+ list() |
nonempty_improper_list() | nonempty_maybe_improper_list() |
+ maybe_improper_list() | string() | iolist() | byte() |
+ module() |
+ mfa() |
+ node() |
+ timeout() |
+ no_return() |
%% Should not be highlighted
nonempty_() | nonlist() |
-erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)].
+ erl_scan:bar(34, 92) | t13() | m:f(integer() | <<_:_*16>>)].
+
+
-type t15() :: {binary(),<<>>,<<_:34>>,<<_:_*42>>,
<<_:3,_:_*14>>,<<>>} | [<<>>|<<_:34>>|<<_:16>>|
<<_:3,_:_*1472>>|<<_:19,_:_*14>>| <<_:34>>|
@@ -172,6 +188,9 @@ f18 :: 1 | 2 | 'undefined',
f19 = 3 :: integer()|undefined,
f5 = 3 :: undefined|integer()}).
+-record(state, {
+ sequence_number = 1 :: integer()
+ }).
highlighting(X) % Function definitions should be highlighted
@@ -493,7 +512,9 @@ indent_try_catch() ->
file:close(Xfile)
end;
indent_try_catch() ->
- try foo(bar) of
+ try
+ foo(bar)
+ of
X when true andalso
kalle ->
io:format(stdout, "Parsing file ~s, ",
@@ -551,14 +572,57 @@ indent_catch() ->
C = catch B +
float(43.1),
- case catch (X) of
+ case catch foo(X) of
A ->
B
end,
+
+ case
+ catch foo(X)
+ of
+ A ->
+ B
+ end,
+
+ case
+ foo(X)
+ of
+ A ->
+ catch B,
+ X
+ end,
+
try sune of
- _ -> foo
- catch _:_ -> baf
- end.
+ _ -> foo
+ catch _:_ -> baf
+ end,
+
+ try
+sune
+ of
+ _ ->
+ X = 5,
+ (catch foo(X)),
+ X + 10
+ catch _:_ -> baf
+ end,
+
+ try
+ (catch sune)
+ of
+ _ ->
+ catch foo() %% BUGBUG can't handle catch inside try without parentheses
+ catch _:_ ->
+ baf
+ end,
+
+ try
+(catch exit())
+ catch
+_ ->
+ catch baf()
+ end,
+ ok.
indent_binary() ->
X = lists:foldr(fun(M) ->
@@ -588,3 +652,8 @@ Binary2 = << <<X:8>> || <<X:32,_:32>> <= <<0:512>>,
true = (X rem 2)
>>,
ok.
+
+%% This causes an error in earlier erlang-mode versions.
+foo() ->
+[#foo{
+foo = foo}].
diff --git a/lib/tools/src/eprof.erl b/lib/tools/src/eprof.erl
index b4313d6888..f7c1b76364 100644
--- a/lib/tools/src/eprof.erl
+++ b/lib/tools/src/eprof.erl
@@ -1,19 +1,19 @@
%%
%% %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
%% 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%
%%
%% Purpose: Profile a system in order to figure out where the
@@ -23,456 +23,464 @@
-module(eprof).
-behaviour(gen_server).
--export([start/0, stop/0, dump/0, total_analyse/0,
- start_profiling/1, profile/2, profile/4, profile/1,
- stop_profiling/0, analyse/0, log/1]).
+-export([start/0,
+ stop/0,
+ dump/0,
+ start_profiling/1, start_profiling/2,
+ profile/1, profile/2, profile/3, profile/4, profile/5,
+ stop_profiling/0,
+ analyze/0, analyze/1, analyze/2,
+ log/1]).
%% Internal exports
-export([init/1,
- call/4,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3]).
+-record(bpd, {
+ n = 0, % number of total calls
+ us = 0, % sum of uS for all calls
+ p = gb_trees:empty(), % tree of {Pid, {Mfa, {Count, Us}}}
+ mfa = [] % list of {Mfa, {Count, Us}}
+ }).
+
+-record(state, {
+ profiling = false,
+ pattern = {'_','_','_'},
+ rootset = [],
+ fd = undefined,
+ start_ts = undefined,
+ reply = undefined,
+ bpd = #bpd{}
+ }).
+
+
+
+%% -------------------------------------------------------------------- %%
+%%
+%% API
+%%
+%% -------------------------------------------------------------------- %%
--include_lib("stdlib/include/qlc.hrl").
-
--import(lists, [flatten/1,reverse/1,keysort/2]).
-
-
--record(state, {table = notable,
- proc = noproc,
- profiling = false,
- pfunc = undefined,
- pop = running,
- ptime = 0,
- overhead = 0,
- rootset = []}).
-
-%%%%%%%%%%%%%%
-
-start() -> gen_server:start({local, eprof}, eprof, [], []).
-stop() -> gen_server:call(eprof, stop, infinity).
+start() -> gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+stop() -> gen_server:call(?MODULE, stop, infinity).
+profile(Fun) when is_function(Fun) ->
+ profile([], Fun);
+profile(Rs) when is_list(Rs) ->
+ start_profiling(Rs).
profile(Pids, Fun) ->
- start(),
- gen_server:call(eprof, {profile,Pids,erlang,apply,[Fun,[]]},infinity).
+ profile(Pids, Fun, {'_','_','_'}).
+
+profile(Pids, Fun, Pattern) ->
+ profile(Pids, erlang, apply, [Fun,[]], Pattern).
profile(Pids, M, F, A) ->
+ profile(Pids, M, F, A, {'_','_','_'}).
+
+profile(Pids, M, F, A, Pattern) ->
start(),
- gen_server:call(eprof, {profile,Pids,M,F,A},infinity).
+ gen_server:call(?MODULE, {profile,Pids,Pattern,M,F,A},infinity).
dump() ->
- gen_server:call(eprof, dump, infinity).
+ gen_server:call(?MODULE, dump, infinity).
-analyse() ->
- gen_server:call(eprof, analyse, infinity).
+analyze() ->
+ analyze(procs).
-log(File) ->
- gen_server:call(eprof, {logfile, File}, infinity).
+analyze(Type) when is_atom(Type) ->
+ analyze(Type, []);
+analyze(Opts) when is_list(Opts) ->
+ analyze(procs, Opts).
+analyze(Type, Opts) when is_list(Opts) ->
+ gen_server:call(?MODULE, {analyze, Type, Opts}, infinity).
-total_analyse() ->
- gen_server:call(eprof, total_analyse, infinity).
+log(File) ->
+ gen_server:call(?MODULE, {logfile, File}, infinity).
start_profiling(Rootset) ->
+ start_profiling(Rootset, {'_','_','_'}).
+start_profiling(Rootset, Pattern) ->
start(),
- gen_server:call(eprof, {profile, Rootset}, infinity).
+ gen_server:call(?MODULE, {profile, Rootset, Pattern}, infinity).
stop_profiling() ->
- gen_server:call(eprof, stop_profiling, infinity).
+ gen_server:call(?MODULE, stop_profiling, infinity).
-profile(Rs) ->
- start_profiling(Rs).
-%%%%%%%%%%%%%%%%
+%% -------------------------------------------------------------------- %%
+%%
+%% init
+%%
+%% -------------------------------------------------------------------- %%
-init(_) ->
+init([]) ->
process_flag(trap_exit, true),
- process_flag(priority, max),
- put(three_one, {3,1}), %To avoid building garbage.
{ok, #state{}}.
-subtr({X1,Y1,Z1}, {X1,Y1,Z2}) ->
- Z1 - Z2;
-subtr({X1,Y1,Z1}, {X2,Y2,Z2}) ->
- (((X1-X2) * 1000000) + Y1 - Y2) * 1000000 + Z1 - Z2.
+%% -------------------------------------------------------------------- %%
+%%
+%% handle_call
+%%
+%% -------------------------------------------------------------------- %%
-update_call_statistics(Tab, Key, Time) ->
- try ets:update_counter(Tab, Key, Time) of
- NewTime when is_integer(NewTime) ->
- ets:update_counter(Tab, Key, get(three_one))
- catch
- error:badarg ->
- ets:insert(Tab, {Key,Time,1})
- end.
+%% analyze
-update_other_statistics(Tab, Key, Time) ->
- try
- ets:update_counter(Tab, Key, Time)
- catch
- error:badarg ->
- ets:insert(Tab, {Key,Time,0})
- end.
+handle_call({analyze, _, _}, _, #state{ bpd = #bpd{ p = {0,nil}, us = 0, n = 0} = Bpd } = S) when is_record(Bpd, bpd) ->
+ {reply, nothing_to_analyze, S};
-do_messages({trace_ts,From,Op,Mfa,Time}, Tab, undefined,_PrevOp0,_PrevTime0) ->
- PrevFunc = [From|Mfa],
- receive
- {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time)
- after 0 ->
- {PrevFunc,Op,Time}
- end;
-do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, call, PrevTime0) ->
- update_call_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)),
- PrevFunc = case Op of
- exit -> undefined;
- out -> undefined;
- _ -> [From|Mfa]
- end,
- receive
- {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time)
- after 0 ->
- {PrevFunc,Op,Time}
- end;
-do_messages({trace_ts,From,Op,Mfa,Time}, Tab, PrevFunc0, _PrevOp0, PrevTime0) ->
- update_other_statistics(Tab, PrevFunc0, subtr(Time, PrevTime0)),
- PrevFunc = case Op of
- exit -> undefined;
- out -> undefined;
- _ -> [From|Mfa]
- end,
- receive
- {trace_ts,_,_,_,_}=Ts -> do_messages(Ts, Tab, PrevFunc, Op, Time)
- after 0 ->
- {PrevFunc,Op,Time}
- end.
+handle_call({analyze, procs, Opts}, _, #state{ bpd = #bpd{ p = Ps, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) ->
+ lists:foreach(fun
+ ({Pid, Mfas}) ->
+ {Pn, Pus} = sum_bp_total_n_us(Mfas),
+ format(Fd, "~n****** Process ~w -- ~s % of profiled time *** ~n", [Pid, s("~.2f", [100.0*(Pus/Tus)])]),
+ print_bp_mfa(Mfas, {Pn,Pus}, Fd, Opts),
+ ok
+ end, gb_trees:to_list(Ps)),
+ {reply, ok, S};
-%%%%%%%%%%%%%%%%%%
+handle_call({analyze, total, Opts}, _, #state{ bpd = #bpd{ mfa = Mfas, n = Tn, us = Tus} = Bpd, fd = Fd} = S) when is_record(Bpd, bpd) ->
+ print_bp_mfa(Mfas, {Tn, Tus}, Fd, Opts),
+ {reply, ok, S};
-handle_cast(_Req, S) -> {noreply, S}.
+handle_call({analyze, Type, _Opts}, _, S) ->
+ {reply, {error, {undefined, Type}}, S};
-terminate(_Reason,_S) ->
- call_trace_for_all(false),
- normal.
+%% profile
-%%%%%%%%%%%%%%%%%%
+handle_call({profile, _Rootset, _Pattern, _M,_F,_A}, _From, #state{ profiling = true } = S) ->
+ {reply, {error, already_profiling}, S};
-handle_call({logfile, F}, _FromTag, Status) ->
- case file:open(F, [write]) of
- {ok, Fd} ->
- case get(fd) of
- undefined -> ok;
- FdOld -> file:close(FdOld)
- end,
- put(fd, Fd),
- {reply, ok, Status};
- {error, _} ->
- {reply, error, Status}
- end;
+handle_call({profile, Rootset, Pattern, M,F,A}, From, #state{fd = Fd } = S) ->
-handle_call({profile, Rootset}, {From, _Tag}, S) ->
- link(From),
- maybe_delete(S#state.table),
- io:format("eprof: Starting profiling ..... ~n",[]),
- ptrac(S#state.rootset, false, all()),
- flush_receive(),
- Tab = ets:new(eprof, [set, public]),
- case ptrac(Rootset, true, all()) of
- false ->
- {reply, error, #state{}};
+ set_pattern_trace(false, S#state.pattern),
+ set_process_trace(false, S#state.rootset),
+
+ Pid = setup_profiling(M,F,A),
+ case set_process_trace(true, [Pid|Rootset]) of
true ->
- uni_schedule(),
- call_trace_for_all(true),
- erase(replyto),
- {reply, profiling, #state{table = Tab,
- proc = From,
- profiling = true,
- rootset = Rootset}}
+ set_pattern_trace(true, Pattern),
+ T0 = now(),
+ execute_profiling(Pid),
+ {noreply, #state{
+ profiling = true,
+ rootset = [Pid|Rootset],
+ start_ts = T0,
+ reply = From,
+ fd = Fd,
+ pattern = Pattern
+ }};
+ false ->
+ exit(Pid, eprof_kill),
+ {reply, error, #state{ fd = Fd}}
end;
-handle_call(stop_profiling, _FromTag, S) when S#state.profiling ->
- ptrac(S#state.rootset, false, all()),
- call_trace_for_all(false),
- multi_schedule(),
- io:format("eprof: Stop profiling~n",[]),
- ets:delete(S#state.table, nofunc),
- {reply, profiling_stopped, S#state{profiling = false}};
+handle_call({profile, _Rootset, _Pattern}, _From, #state{ profiling = true } = S) ->
+ {reply, {error, already_profiling}, S};
-handle_call(stop_profiling, _FromTag, S) ->
- {reply, profiling_already_stopped, S};
+handle_call({profile, Rootset, Pattern}, From, #state{ fd = Fd } = S) ->
+
+ set_pattern_trace(false, S#state.pattern),
+ set_process_trace(false, S#state.rootset),
-handle_call({profile, Rootset, M, F, A}, FromTag, S) ->
- io:format("eprof: Starting profiling..... ~n", []),
- maybe_delete(S#state.table),
- ptrac(S#state.rootset, false, all()),
- flush_receive(),
- put(replyto, FromTag),
- Tab = ets:new(eprof, [set, public]),
- P = spawn_link(eprof, call, [self(), M, F, A]),
- case ptrac([P|Rootset], true, all()) of
+ case set_process_trace(true, Rootset) of
true ->
- uni_schedule(),
- call_trace_for_all(true),
- P ! {self(),go},
- {noreply, #state{table = Tab,
- profiling = true,
- rootset = [P|Rootset]}};
+ T0 = now(),
+ set_pattern_trace(true, Pattern),
+ {reply, profiling, #state{
+ profiling = true,
+ rootset = Rootset,
+ start_ts = T0,
+ reply = From,
+ fd = Fd,
+ pattern = Pattern
+ }};
false ->
- exit(P, kill),
- erase(replyto),
- {reply, error, #state{}}
+ {reply, error, #state{ fd = Fd }}
end;
-handle_call(dump, _FromTag, S) ->
- {reply, dump(S#state.table), S};
-
-handle_call(analyse, _FromTag, S) ->
- {reply, analyse(S), S};
+handle_call(stop_profiling, _From, #state{ profiling = false } = S) ->
+ {reply, profiling_already_stopped, S};
-handle_call(total_analyse, _FromTag, S) ->
- {reply, total_analyse(S), S};
+handle_call(stop_profiling, _From, #state{ profiling = true } = S) ->
-handle_call(stop, _FromTag, S) ->
- multi_schedule(),
- {stop, normal, stopped, S}.
+ set_pattern_trace(pause, S#state.pattern),
-%%%%%%%%%%%%%%%%%%%
+ Bpd = collect_bpd(),
-handle_info({trace_ts,_From,_Op,_Func,_Time}=M, S0) when S0#state.profiling ->
- Start = erlang:now(),
- #state{table=Tab,pop=PrevOp0,ptime=PrevTime0,pfunc=PrevFunc0,
- overhead=Overhead0} = S0,
- {PrevFunc,PrevOp,PrevTime} = do_messages(M, Tab, PrevFunc0, PrevOp0, PrevTime0),
- Overhead = Overhead0 + subtr(erlang:now(), Start),
- S = S0#state{overhead=Overhead,pfunc=PrevFunc,pop=PrevOp,ptime=PrevTime},
- {noreply,S};
+ set_process_trace(false, S#state.rootset),
+ set_pattern_trace(false, S#state.pattern),
-handle_info({trace_ts, From, _, _, _}, S) when not S#state.profiling ->
- ptrac([From], false, all()),
- {noreply, S};
+ {reply, profiling_stopped, S#state{
+ profiling = false,
+ rootset = [],
+ pattern = {'_','_','_'},
+ bpd = Bpd
+ }};
-handle_info({_P, {answer, A}}, S) ->
- ptrac(S#state.rootset, false, all()),
- io:format("eprof: Stop profiling~n",[]),
- {From,_Tag} = get(replyto),
- catch unlink(From),
- ets:delete(S#state.table, nofunc),
- gen_server:reply(erase(replyto), {ok, A}),
- multi_schedule(),
- {noreply, S#state{profiling = false,
- rootset = []}};
-
-handle_info({'EXIT', P, Reason},
- #state{profiling=true,proc=P,table=T,rootset=RootSet}) ->
- maybe_delete(T),
- ptrac(RootSet, false, all()),
- multi_schedule(),
- io:format("eprof: Profiling failed\n",[]),
- case erase(replyto) of
- undefined ->
- {noreply, #state{}};
- FromTag ->
- gen_server:reply(FromTag, {error, Reason}),
- {noreply, #state{}}
+%% logfile
+handle_call({logfile, File}, _From, #state{ fd = OldFd } = S) ->
+ case file:open(File, [write]) of
+ {ok, Fd} ->
+ case OldFd of
+ undefined -> ok;
+ OldFd -> file:close(OldFd)
+ end,
+ {reply, ok, S#state{ fd = Fd}};
+ Error ->
+ {reply, Error, S}
end;
-handle_info({'EXIT',_P,_Reason}, S) ->
- {noreply, S}.
+handle_call(dump, _From, #state{ bpd = Bpd } = S) when is_record(Bpd, bpd) ->
+ {reply, gb_trees:to_list(Bpd#bpd.p), S};
-uni_schedule() ->
- erlang:system_flag(multi_scheduling, block).
+handle_call(stop, _FromTag, S) ->
+ {stop, normal, stopped, S}.
-multi_schedule() ->
- erlang:system_flag(multi_scheduling, unblock).
+%% -------------------------------------------------------------------- %%
+%%
+%% handle_cast
+%%
+%% -------------------------------------------------------------------- %%
-%%%%%%%%%%%%%%%%%%
+handle_cast(_Msg, State) ->
+ {noreply, State}.
-call(Top, M, F, A) ->
- receive
- {Top,go} ->
- Top ! {self(), {answer, apply(M,F,A)}}
- end.
+%% -------------------------------------------------------------------- %%
+%%
+%% handle_info
+%%
+%% -------------------------------------------------------------------- %%
-call_trace_for_all(Flag) ->
- erlang:trace_pattern(on_load, Flag, [local]),
- erlang:trace_pattern({'_','_','_'}, Flag, [local]).
+handle_info({'EXIT', _, normal}, S) ->
+ {noreply, S};
+handle_info({'EXIT', _, eprof_kill}, S) ->
+ {noreply, S};
+handle_info({'EXIT', _, Reason}, #state{ reply = FromTag } = S) ->
-ptrac([P|T], How, Flags) when is_pid(P) ->
- case dotrace(P, How, Flags) of
- true ->
- ptrac(T, How, Flags);
- false when How ->
- false;
- false ->
- ptrac(T, How, Flags)
- end;
+ set_process_trace(false, S#state.rootset),
+ set_pattern_trace(false, S#state.pattern),
-ptrac([P|T], How, Flags) when is_atom(P) ->
- case whereis(P) of
- undefined when How ->
- false;
- undefined when not How ->
- ptrac(T, How, Flags);
- Pid ->
- ptrac([Pid|T], How, Flags)
- end;
+ gen_server:reply(FromTag, {error, Reason}),
+ {noreply, S#state{
+ profiling = false,
+ rootset = [],
+ pattern = {'_','_','_'}
+ }};
-ptrac([H|_],_How,_Flags) ->
- io:format("** eprof bad process ~w~n",[H]),
- false;
+% check if Pid is spawned process?
+handle_info({_Pid, {answer, Result}}, #state{ reply = {From,_} = FromTag} = S) ->
-ptrac([],_,_) -> true.
+ set_pattern_trace(pause, S#state.pattern),
-dotrace(P, How, What) ->
- case (catch erlang:trace(P, How, What)) of
- 1 ->
- true;
- _Other when not How ->
- true;
- _Other ->
- io:format("** eprof: bad process: ~p,~p,~p~n", [P,How,What]),
- false
- end.
+ Bpd = collect_bpd(),
-all() -> [call,arity,return_to,running,timestamp,set_on_spawn].
-
-total_analyse(#state{table=notable}) ->
- nothing_to_analyse;
-total_analyse(S) ->
- #state{table = T, overhead = Overhead} = S,
- QH = qlc:q([{{From,Mfa},Time,Count} ||
- {[From|Mfa],Time,Count} <- ets:table(T)]),
- Pcalls = reverse(keysort(2, replicas(qlc:eval(QH)))),
- Time = collect_times(Pcalls),
- format("FUNCTION~44s TIME ~n", ["CALLS"]),
- printit(Pcalls, Time),
- format("\nTotal time: ~.2f\n", [Time / 1000000]),
- format("Measurement overhead: ~.2f\n", [Overhead / 1000000]).
-
-analyse(#state{table=notable}) ->
- nothing_to_analyse;
-analyse(S) ->
- #state{table = T, overhead = Overhead} = S,
- Pids = ordsets:from_list(flatten(ets:match(T, {['$1'|'_'],'_', '_'}))),
- Times = sum(ets:match(T, {'_','$1', '_'})),
- format("FUNCTION~44s TIME ~n", ["CALLS"]),
- do_pids(Pids, T, 0, Times),
- format("\nTotal time: ~.2f\n", [Times / 1000000]),
- format("Measurement overhead: ~.2f\n", [Overhead / 1000000]).
-
-do_pids([Pid|Tail], T, AckTime, Total) ->
- Pcalls =
- reverse(keysort(2, to_tups(ets:match(T, {[Pid|'$1'], '$2','$3'})))),
- Time = collect_times(Pcalls),
- PercentTotal = 100 * (divide(Time, Total)),
- format("~n****** Process ~w -- ~s % of profiled time *** ~n",
- [Pid, fpf(PercentTotal)]),
- printit(Pcalls, Time),
- do_pids(Tail, T, AckTime + Time, Total);
-do_pids([], _, _, _) ->
- ok.
+ set_process_trace(false, S#state.rootset),
+ set_pattern_trace(false, S#state.pattern),
-printit([],_) -> ok;
-printit([{{Mod,Fun,Arity}, Time, Calls} |Tail], ProcTime) ->
- format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls),
- fpf(100*(divide(Time,ProcTime)))]),
- printit(Tail, ProcTime);
-printit([{{_,{Mod,Fun,Arity}}, Time, Calls} |Tail], ProcTime) ->
- format("~s ~s ~s % ~n", [ff(Mod,Fun,Arity), fint(Calls),
- fpf(100*(divide(Time,ProcTime)))]),
- printit(Tail, ProcTime);
-printit([_|T], Time) ->
- printit(T, Time).
-
-ff(Mod,Fun,Arity) ->
- pad(flatten(io_lib:format("~w:~w/~w", [Mod,Fun, Arity])),45).
-
-pad(Str, Len) ->
- Strlen = length(Str),
- if
- Strlen > Len -> strip_tail(Str, 45);
- true -> lists:append(Str, mklist(Len-Strlen))
- end.
+ catch unlink(From),
+ gen_server:reply(FromTag, {ok, Result}),
+ {noreply, S#state{
+ profiling = false,
+ rootset = [],
+ pattern = {'_','_','_'},
+ bpd = Bpd
+ }}.
+
+%% -------------------------------------------------------------------- %%
+%%
+%% termination
+%%
+%% -------------------------------------------------------------------- %%
+
+terminate(_Reason, #state{ fd = undefined }) ->
+ set_pattern_trace(false, {'_','_','_'}),
+ ok;
+terminate(_Reason, #state{ fd = Fd }) ->
+ file:close(Fd),
+ set_pattern_trace(false, {'_','_','_'}),
+ ok.
-strip_tail([_|_], 0) ->[];
-strip_tail([H|T], I) -> [H|strip_tail(T, I-1)];
-strip_tail([],_I) -> [].
+%% -------------------------------------------------------------------- %%
+%%
+%% code_change
+%%
+%% -------------------------------------------------------------------- %%
-fpf(F) -> strip_tail(flatten(io_lib:format("~w", [round(F)])), 5).
-fint(Int) -> pad(flatten(io_lib:format("~w",[Int])), 10).
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
-mklist(0) -> [];
-mklist(I) -> [$ |mklist(I-1)].
-to_tups(L) -> lists:map(fun(List) -> erlang:list_to_tuple(List) end, L).
+%% -------------------------------------------------------------------- %%
+%%
+%% AUX Functions
+%%
+%% -------------------------------------------------------------------- %%
-divide(X,Y) -> X / Y.
+setup_profiling(M,F,A) ->
+ spawn_link(fun() -> spin_profile(M,F,A) end).
-collect_times([]) -> 0;
-collect_times([Tup|Tail]) -> element(2, Tup) + collect_times(Tail).
+spin_profile(M, F, A) ->
+ receive
+ {Pid, execute} ->
+ Pid ! {self(), {answer, erlang:apply(M,F,A)}}
+ end.
-dump(T) ->
- L = ets:tab2list(T),
- format(L).
+execute_profiling(Pid) ->
+ Pid ! {self(), execute}.
-format([H|T]) ->
- format("~p~n", [H]), format(T);
-format([]) -> ok.
+set_pattern_trace(Flag, Pattern) ->
+ erlang:system_flag(multi_scheduling, block),
+ erlang:trace_pattern(on_load, Flag, [call_time]),
+ erlang:trace_pattern(Pattern, Flag, [call_time]),
+ erlang:system_flag(multi_scheduling, unblock),
+ ok.
-format(F, A) ->
- io:format(F,A),
- case get(fd) of
- undefined -> ok;
- Fd -> io:format(Fd, F,A)
+set_process_trace(Flag, Pids) ->
+ % do we need procs for meta info?
+ % could be useful
+ set_process_trace(Flag, Pids, [call, set_on_spawn]).
+set_process_trace(_, [], _) -> true;
+set_process_trace(Flag, [Pid|Pids], Options) when is_pid(Pid) ->
+ try
+ erlang:trace(Pid, Flag, Options),
+ set_process_trace(Flag, Pids, Options)
+ catch
+ _:_ ->
+ false
+ end;
+set_process_trace(Flag, [Name|Pids], Options) when is_atom(Name) ->
+ case whereis(Name) of
+ undefined ->
+ set_process_trace(Flag, Pids, Options);
+ Pid ->
+ set_process_trace(Flag, [Pid|Pids], Options)
end.
-maybe_delete(T) ->
- catch ets:delete(T).
-
-sum([[H]|T]) -> H + sum(T);
-sum([]) -> 0.
+collect_bpd() ->
+ collect_bpd([M || M <- [element(1, Mi) || Mi <- code:all_loaded()], M =/= ?MODULE]).
+
+collect_bpd(Ms) when is_list(Ms) ->
+ collect_bpdf(collect_mfas(Ms) ++ erlang:system_info(snifs)).
+
+collect_mfas(Ms) ->
+ lists:foldl(fun
+ (M, Mfas) ->
+ Mfas ++ [{M, F, A} || {F, A} <- M:module_info(functions)]
+ end, [], Ms).
+
+collect_bpdf(Mfas) ->
+ collect_bpdf(Mfas, #bpd{}).
+collect_bpdf([], Bpd) ->
+ Bpd;
+collect_bpdf([Mfa|Mfas], #bpd{n = N, us = Us, p = Tree, mfa = Code } = Bpd) ->
+ case erlang:trace_info(Mfa, call_time) of
+ {call_time, []} ->
+ collect_bpdf(Mfas, Bpd);
+ {call_time, Data} when is_list(Data) ->
+ {CTn, CTus, CTree} = collect_bpdfp(Mfa, Tree, Data),
+ collect_bpdf(Mfas, Bpd#bpd{
+ n = CTn + N,
+ us = CTus + Us,
+ p = CTree,
+ mfa = [{Mfa, {CTn, CTus}}|Code]
+ });
+ {call_time, false} ->
+ collect_bpdf(Mfas, Bpd);
+ {call_time, _Other} ->
+ collect_bpdf(Mfas, Bpd)
+ end.
-replicas(L) ->
- replicas(L, []).
+collect_bpdfp(Mfa, Tree, Data) ->
+ lists:foldl(fun
+ ({Pid, Ni, Si, Usi}, {PTno, PTuso, To}) ->
+ Time = Si * 1000000 + Usi,
+ Ti1 = case gb_trees:lookup(Pid, To) of
+ none ->
+ gb_trees:enter(Pid, [{Mfa, {Ni, Time}}], To);
+ {value, Pmfas} ->
+ gb_trees:enter(Pid, [{Mfa, {Ni, Time}}|Pmfas], To)
+ end,
+ {PTno + Ni, PTuso + Time, Ti1}
+ end, {0,0, Tree}, Data).
+
+%% manipulators
+sort_mfa(Bpfs, mfa) when is_list(Bpfs) ->
+ lists:sort(fun
+ ({A,_}, {B,_}) when A < B -> true;
+ (_, _) -> false
+ end, Bpfs);
+sort_mfa(Bpfs, time) when is_list(Bpfs) ->
+ lists:sort(fun
+ ({_,{A,_}}, {_,{B,_}}) when A < B -> true;
+ (_, _) -> false
+ end, Bpfs);
+sort_mfa(Bpfs, calls) when is_list(Bpfs) ->
+ lists:sort(fun
+ ({_,{_,A}}, {_,{_,B}}) when A < B -> true;
+ (_, _) -> false
+ end, Bpfs);
+sort_mfa(Bpfs, _) when is_list(Bpfs) -> sort_mfa(Bpfs, calls).
+
+filter_mfa(Bpfs, Ts) when is_list(Ts) ->
+ filter_mfa(Bpfs, [], proplists:get_value(calls, Ts, 0), proplists:get_value(time, Ts, 0));
+filter_mfa(Bpfs, _) -> Bpfs.
+filter_mfa([], Out, _, _) -> lists:reverse(Out);
+filter_mfa([{_, {C, T}}=Bpf|Bpfs], Out, Ct, Tt) when C >= Ct, T >= Tt -> filter_mfa(Bpfs, [Bpf|Out], Ct, Tt);
+filter_mfa([_|Bpfs], Out, Ct, Tt) -> filter_mfa(Bpfs, Out, Ct, Tt).
+
+sum_bp_total_n_us(Mfas) ->
+ lists:foldl(fun ({_, {Ci,Usi}}, {Co, Uso}) -> {Co + Ci, Uso + Usi} end, {0,0}, Mfas).
+
+%% strings and format
+
+string_bp_mfa(Mfas, Tus) -> string_bp_mfa(Mfas, Tus, {0,0,0,0,0}, []).
+string_bp_mfa([], _, Ws, Strings) -> {Ws, lists:reverse(Strings)};
+string_bp_mfa([{Mfa, {Count, Time}}|Mfas], Tus, {MfaW, CountW, PercW, TimeW, TpCW}, Strings) ->
+ Smfa = s(Mfa),
+ Scount = s(Count),
+ Stime = s(Time),
+ Sperc = s("~.2f", [100*(Time/Tus)]),
+ Stpc = s("~.2f", [Time/Count]),
+
+ string_bp_mfa(Mfas, Tus, {
+ erlang:max(MfaW, length(Smfa)),
+ erlang:max(CountW,length(Scount)),
+ erlang:max(PercW, length(Sperc)),
+ erlang:max(TimeW, length(Stime)),
+ erlang:max(TpCW, length(Stpc))
+ }, [[Smfa, Scount, Sperc, Stime, Stpc] | Strings]).
+
+print_bp_mfa(Mfas, {_Tn, Tus}, Fd, Opts) ->
+ Fmfas = filter_mfa(sort_mfa(Mfas, proplists:get_value(sort, Opts)), proplists:get_value(filter, Opts)),
+ {{MfaW, CountW, PercW, TimeW, TpCW}, Strs} = string_bp_mfa(Fmfas, Tus),
+ Ws = {
+ erlang:max(length("FUNCTION"), MfaW),
+ erlang:max(length("CALLS"), CountW),
+ erlang:max(length(" %"), PercW),
+ erlang:max(length("TIME"), TimeW),
+ erlang:max(length("uS / CALLS"), TpCW)
+ },
+ format(Fd, Ws, ["FUNCTION", "CALLS", " %", "TIME", "uS / CALLS"]),
+ format(Fd, Ws, ["--------", "-----", "---", "----", "----------"]),
+
+ lists:foreach(fun (String) -> format(Fd, Ws, String) end, Strs),
+ ok.
-replicas([{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Tail], Result) ->
- case search({Mod,Fun,Arity},Result) of
- false ->
- replicas(Tail, [{{Pid, {Mod,Fun,Arity}}, Ack,Calls} |Result]);
- {Ack2, Calls2} ->
- Result2 = del({Mod,Fun,Arity}, Result),
- replicas(Tail, [{{Pid, {Mod,Fun,Arity}},
- Ack+Ack2,Calls+Calls2} |Result2])
- end;
+s({M,F,A}) -> s("~w:~w/~w",[M,F,A]);
+s(Term) -> s("~p", [Term]).
+s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)).
-replicas([_|T], Ack) -> %% Whimpy
- replicas(T, Ack);
-
-replicas([], Res) -> Res.
-
-search(Key, [{{_,Key}, Ack, Calls}|_]) ->
- {Ack, Calls};
-search(Key, [_|T]) ->
- search(Key, T);
-search(_Key,[]) -> false.
-
-del(Key, [{{_,Key},_Ack,_Calls}|T]) ->
- T;
-del(Key, [H | Tail]) ->
- [H|del(Key, Tail)];
-del(_Key,[]) -> [].
-
-flush_receive() ->
- receive
- {trace_ts, From, _, _, _} when is_pid(From) ->
- ptrac([From], false, all()),
- flush_receive();
- _ ->
- flush_receive()
- after 0 ->
- ok
- end.
-code_change(_OldVsn, State, _Extra) ->
- {ok,State}.
+format(Fd, {MfaW, CountW, PercW, TimeW, TpCW}, Strings) ->
+ format(Fd, s("~~.~ps ~~~ps ~~~ps ~~~ps [~~~ps]~~n", [MfaW, CountW, PercW, TimeW, TpCW]), Strings);
+format(undefined, Format, Strings) ->
+ io:format(Format, Strings),
+ ok;
+format(Fd, Format, Strings) ->
+ io:format(Fd, Format, Strings),
+ io:format(Format, Strings),
+ ok.
diff --git a/lib/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl
index d0dbf4a2b4..1656899e8f 100644
--- a/lib/tools/src/xref_base.erl
+++ b/lib/tools/src/xref_base.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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%
%%
@@ -29,7 +29,7 @@
add_release/2, add_release/3,
get_library_path/1, set_library_path/2, set_library_path/3,
set_up/1, set_up/2,
- q/2, q/3, info/1, info/2, info/3, update/1, update/2,
+ q/2, q/3, info/1, info/2, info/3, update/1, update/2,
forget/1, forget/2, variables/1, variables/2,
analyze/2, analyze/3, analysis/1,
get_default/2, set_default/3,
@@ -38,14 +38,14 @@
-export([format_error/1]).
%% The following functions are exported for testing purposes only:
--export([do_add_module/4, do_add_application/2, do_add_release/2,
+-export([do_add_module/4, do_add_application/2, do_add_release/2,
do_remove_module/2]).
--import(lists,
- [filter/2, flatten/1, foldl/3, keysearch/3, map/2, mapfoldl/3,
- member/2, reverse/1, sort/1, usort/1]).
+-import(lists,
+ [filter/2, flatten/1, foldl/3, foreach/2, keysearch/3, map/2,
+ mapfoldl/3, member/2, reverse/1, sort/1, usort/1]).
--import(sofs,
+-import(sofs,
[constant_function/2, converse/1, difference/2, domain/1,
empty_set/0, family/1, family_difference/2, intersection/2,
family_projection/2, family_to_relation/1, family_union/1,
@@ -103,12 +103,12 @@ delete(State) ->
ok
end
end,
- map(Fun, dict:to_list(State#xref.variables)),
+ foreach(Fun, dict:to_list(State#xref.variables)),
ok.
add_directory(State, Dir) ->
add_directory(State, Dir, []).
-
+
%% -> {ok, Modules, NewState} | Error
add_directory(State, Dir, Options) ->
ValOptions = option_values([builtins, recurse, verbose, warnings], State),
@@ -277,7 +277,7 @@ q(S, Q, Options) when is_atom(Q) ->
q(S, atom_to_list(Q), Options);
q(S, Q, Options) ->
case xref_utils:is_string(Q, 1) of
- true ->
+ true ->
case set_up(S, Options) of
{ok, S1} ->
case xref_compiler:compile(Q, S1#xref.variables) of
@@ -336,7 +336,7 @@ forget(State, Variable) when is_atom(Variable) ->
forget(State, Variables) ->
Vars = State#xref.variables,
do_forget(Variables, Vars, Variables, State).
-
+
variables(State) ->
variables(State, [user]).
@@ -350,9 +350,9 @@ variables(State, Options) ->
{ok, NewState} ->
{U, P} = do_variables(NewState),
R1 = if User -> [{user, U}]; true -> [] end,
- R = if
- Predef -> [{predefined,P} | R1];
- true -> R1
+ R = if
+ Predef -> [{predefined,P} | R1];
+ true -> R1
end,
{{ok, R}, NewState};
Error ->
@@ -368,7 +368,7 @@ analyze(State, Analysis) ->
%% -> {{ok, Answer}, NewState} | {Error, NewState}
analyze(State, Analysis, Options) ->
case analysis(Analysis, State#xref.mode) of
- P when is_list(P) ->
+ P when is_list(P) ->
q(State, P, Options);
error ->
R = case analysis(Analysis, functions) of
@@ -461,7 +461,7 @@ get_default(State, Option) ->
%% -> [{Option, Value}]
get_default(State) ->
- Fun = fun(O) -> V = current_default(State, O), {O, V} end,
+ Fun = fun(O) -> V = current_default(State, O), {O, V} end,
map(Fun, [builtins, recurse, verbose, warnings]).
%% -> {ok, NewState} -> Error
@@ -478,7 +478,7 @@ set_default(State, Options) ->
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({invalid_options, Options}) ->
- io_lib:format("Unknown option(s) or invalid option value(s): ~p~n",
+ io_lib:format("Unknown option(s) or invalid option value(s): ~p~n",
[Options]);
format_error({invalid_filename, Term}) ->
io_lib:format("A file name (a string) was expected: ~p~n", [Term]);
@@ -540,7 +540,7 @@ updated_modules(State) ->
case xref_utils:file_info(File) of
{ok, {_, file, readable, MTime}} when MTime =/= RTime ->
[{M,File} | L];
- _Else ->
+ _Else ->
L
end
end,
@@ -591,7 +591,7 @@ do_add_release(Dir, RelName, OB, OV, OW, State) ->
case xref_utils:release_directory(Dir, true, "ebin") of
{ok, ReleaseDirName, ApplDir, Dirs} ->
ApplDirs = xref_utils:select_last_application_version(Dirs),
- Release = case RelName of
+ Release = case RelName of
[[]] -> ReleaseDirName;
[Name] -> Name
end,
@@ -615,7 +615,7 @@ do_add_release(S, XRel) ->
end.
add_rel_appls([ApplDir | ApplDirs], Release, OB, OV, OW, State) ->
- {ok, _AppName, NewState} =
+ {ok, _AppName, NewState} =
add_appldir(ApplDir, Release, [[]], OB, OV, OW, State),
add_rel_appls(ApplDirs, Release, OB, OV, OW, NewState);
add_rel_appls([], [Release], _OB, _OV, _OW, NewState) ->
@@ -637,10 +637,10 @@ add_appldir(ApplDir, Release, Name, OB, OV, OW, OldState) ->
[[]] -> AppName0;
[N] -> N
end,
- AppInfo = #xref_app{name = AppName, rel_name = Release,
+ AppInfo = #xref_app{name = AppName, rel_name = Release,
vsn = Vsn, dir = Dir},
State1 = do_add_application(OldState, AppInfo),
- {ok, _Modules, NewState} =
+ {ok, _Modules, NewState} =
do_add_directory(Dir, [AppName], OB, false, OV, OW, State1),
{ok, AppName, NewState}.
@@ -662,7 +662,7 @@ do_add_directory(Dir, AppName, Bui, Rec, Ver, War, State) ->
ok = is_filename(Dir),
{FileNames, Errors, Jams, Unreadable} =
xref_utils:scan_directory(Dir, Rec, [?Suffix], [".jam"]),
- warnings(War, jam, Jams),
+ warnings(War, jam, Jams),
warnings(War, unreadable, Unreadable),
case Errors of
[] ->
@@ -683,7 +683,7 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
false ->
throw_error({invalid_filename, File});
Splitname ->
- do_add_module(Splitname, AppName, Builtins, Verbose,
+ do_add_module(Splitname, AppName, Builtins, Verbose,
Warnings, State)
end.
@@ -691,7 +691,7 @@ do_add_a_module(File, AppName, Builtins, Verbose, Warnings, State) ->
%% Options: verbose, warnings, builtins
do_add_module({Dir, Basename}, AppName, Builtins, Verbose, Warnings, State) ->
File = filename:join(Dir, Basename),
- {ok, M, Bad, NewState} =
+ {ok, M, Bad, NewState} =
do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State),
filter(fun({Tag,B}) -> warnings(Warnings, Tag, [[File,B]]) end, Bad),
{ok, M, NewState}.
@@ -723,7 +723,7 @@ do_add_module1(Dir, File, AppName, Builtins, Verbose, Warnings, State) ->
{ok, {_, _, _, Time}} -> Time;
Error -> throw(Error)
end,
- XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
+ XMod = #xref_mod{name = M, app_name = AppName, dir = Dir,
mtime = T, builtins = Builtins,
no_unresolved = NoUnresCalls},
do_add_module(State, XMod, UnresCalls, Data);
@@ -736,13 +736,13 @@ abst(File, Builtins, Mode) when Mode =:= functions ->
case beam_lib:chunks(File, [abstract_code, exports, attributes]) of
{ok, {M,[{abstract_code,NoA},_X,_A]}} when NoA =:= no_abstract_code ->
{ok, M, NoA};
- {ok, {M, [{abstract_code, {abstract_v1, Forms}},
+ {ok, {M, [{abstract_code, {abstract_v1, Forms}},
{exports,X0}, {attributes,A}]}} ->
%% R7.
X = xref_utils:fa_to_mfa(X0, M),
D = deprecated(A, X, M),
xref_reader:module(M, Forms, Builtins, X, D);
- {ok, {M, [{abstract_code, {abstract_v2, Forms}},
+ {ok, {M, [{abstract_code, {abstract_v2, Forms}},
{exports,X0}, {attributes,A}]}} ->
%% R8-R9B.
X = xref_utils:fa_to_mfa(X0, M),
@@ -769,8 +769,8 @@ abst(File, Builtins, Mode) when Mode =:= modules ->
true ->
I0;
false ->
- Fun = fun({M,F,A}) ->
- not xref_utils:is_builtin(M, F, A)
+ Fun = fun({M,F,A}) ->
+ not xref_utils:is_builtin(M, F, A)
end,
filter(Fun, I0)
end,
@@ -790,7 +790,7 @@ mfa_exports(X0, Attributes, M) ->
xref_utils:fa_to_mfa(X1, M).
adjust_arity(F, A) ->
- case xref_utils:is_static_function(F, A) of
+ case xref_utils:is_static_function(F, A) of
true -> A;
false -> A - 1
end.
@@ -885,7 +885,7 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
Unres = domain(UnresCalls),
DefinedFuns = domain(DefAt),
- {AXC, ALC, Bad1, LPreCAt2, XPreCAt2} =
+ {AXC, ALC, Bad1, LPreCAt2, XPreCAt2} =
extra_edges(AXC1, ALC1, Bad0, DefinedFuns),
Bad = map(fun(B) -> {xref_attr, B} end, Bad1),
LPreCAt = union(LPreCAt1, LPreCAt2),
@@ -904,8 +904,8 @@ do_add_module(S, M, XMod, Unres0, Data) when S#xref.mode =:= functions ->
%% {EE, ECallAt} = inter_graph(X, L, LC, XC, LCallAt, XCallAt),
Self = self(),
- Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
- {EE, ECallAt} =
+ Fun = fun() -> inter_graph(Self, X, L, LC, XC, CallAt) end,
+ {EE, ECallAt} =
xref_utils:subprocess(Fun, [link, {min_heap_size,100000}]),
[DefAt2,L2,X2,LCallAt2,XCallAt2,CallAt2,LC2,XC2,EE2,ECallAt2,
@@ -977,13 +977,13 @@ extra_edges(CAX, CAL, Bad0, F) ->
ALC = restriction(2, restriction(ALC0, F), F),
LPreCAt2 = restriction(CAL, ALC),
XPreCAt2 = restriction(CAX, AXC),
- Bad = Bad0 ++ to_external(difference(AXC0, AXC))
+ Bad = Bad0 ++ to_external(difference(AXC0, AXC))
++ to_external(difference(ALC0, ALC)),
{AXC, ALC, Bad, LPreCAt2, XPreCAt2}.
no_info(X, L, LC, XC, EE, Unres, NoCalls, NoUnresCalls) ->
NoUnres = no_elements(Unres),
- [{no_calls, {NoCalls-NoUnresCalls, NoUnresCalls}},
+ [{no_calls, {NoCalls-NoUnresCalls, NoUnresCalls}},
{no_function_calls, {no_elements(LC), no_elements(XC)-NoUnres, NoUnres}},
{no_functions, {no_elements(L), no_elements(X)}},
%% Note: this is overwritten in do_set_up():
@@ -1011,10 +1011,10 @@ inter_graph(X, L, LC, XC, CallAt) ->
Es = union(LEs, XEs),
E1 = to_external(restriction(difference(LC, LEs), XL)),
- R0 = xref_utils:xset(reachable(E1, G, []),
+ R0 = xref_utils:xset(reachable(E1, G, []),
[{tspec(func), tspec(fun_edge)}]),
true = digraph:delete(G),
-
+
% RL is a set of indirect local calls to exports.
RL = restriction(R0, XL),
% RX is a set of indirect external calls to exports.
@@ -1033,7 +1033,7 @@ inter_graph(X, L, LC, XC, CallAt) ->
?FORMAT("XL=~p~nXEs=~p~nLEs=~p~nE1=~p~nR0=~p~nRL=~p~nRX=~p~nR=~p~n"
"EE=~p~nECallAt1=~p~nECallAt2=~p~nECallAt=~p~n~n",
- [XL, XEs, LEs, E1, R0, RL, RX, R, EE,
+ [XL, XEs, LEs, E1, R0, RL, RX, R, EE,
ECallAt1, ECallAt2, ECallAt]),
{EE, ECallAt}.
@@ -1121,7 +1121,7 @@ remove_erase([], D) ->
do_add_libraries(Path, Verbose, State) ->
message(Verbose, lib_search, []),
- {C, E} = xref_utils:list_path(Path, [?Suffix]),
+ {C, E} = xref_utils:list_path(Path, [?Suffix]),
message(Verbose, done, []),
MDs = to_external(relation_to_family(relation(C))),
%% message(Verbose, lib_check, []),
@@ -1160,23 +1160,23 @@ do_set_up(S, VerboseOpt) ->
Reply.
%% If data has been supplied using add_module/9 (and that is the only
-%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC,
-%% and LU are guaranteed to be functions (with all supplied
-%% modules as domain (disregarding unknown modules, that is, modules
+%% sanctioned way), then DefAt, L, X, LCallAt, XCallAt, CallAt, XC, LC,
+%% and LU are guaranteed to be functions (with all supplied
+%% modules as domain (disregarding unknown modules, that is, modules
%% not supplied but hosting unknown functions)).
%% As a consequence, V and E are also functions. V is defined for unknown
%% modules also.
%% UU is also a function (thanks to sofs:family_difference/2...).
-%% XU on the other hand can be a partial function (that is, not defined
+%% XU on the other hand can be a partial function (that is, not defined
%% for all modules). U is derived from XU, so U is also partial.
%% The inverse variables - LC_1, XC_1, E_1 and EE_1 - are all partial.
%% B is also partial.
do_set_up(S) when S#xref.mode =:= functions ->
ModDictList = dict:to_list(S#xref.modules),
- [DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU,
+ [DefAt0, L, X0, LCallAt, XCallAt, CallAt, LC, XC, LU,
EE0, ECallAt, UC, LPredefined,
Mod_DF,Mod_DF_1,Mod_DF_2,Mod_DF_3] = make_families(ModDictList, 18),
-
+
{XC_1, XU, XPredefined} = do_set_up_1(XC),
LC_1 = user_family(union_of_family(LC)),
E_1 = family_union(XC_1, LC_1),
@@ -1206,7 +1206,7 @@ do_set_up(S) when S#xref.mode =:= functions ->
AM = domain(F1),
%% Undef is the union of U0 and Lib:
- {Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
+ {Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
make_libs(XU, F1, AM, S#xref.library_path, S#xref.libraries),
{B, U} = make_builtins(U0),
X1_B = family_union(X1, B),
@@ -1228,22 +1228,22 @@ do_set_up(S) when S#xref.mode =:= functions ->
%% way to discard calls to local functions in other modules.
EE_conv = converse(union_of_family(EE0)),
EE_exported = restriction(EE_conv, union_of_family(X)),
- EE_local =
+ EE_local =
specification({external, fun({{M1,_,_},{M2,_,_}}) -> M1 =:= M2 end},
EE_conv),
EE_0 = converse(union(EE_local, EE_exported)),
EE_1 = user_family(EE_0),
- EE1 = partition_family({external, fun({{M1,_,_}, _MFA2}) -> M1 end},
+ EE1 = partition_family({external, fun({{M1,_,_}, _MFA2}) -> M1 end},
EE_0),
%% Make sure EE is defined for all modules:
EE = family_union(family_difference(EE0, EE0), EE1),
- IFun =
- fun({Mod,EE_M}, XMods) ->
- IMFun =
+ IFun =
+ fun({Mod,EE_M}, XMods) ->
+ IMFun =
fun(XrefMod) ->
- [NoCalls, NoFunctionCalls,
+ [NoCalls, NoFunctionCalls,
NoFunctions, _NoInter] = XrefMod#xref_mod.info,
- NewInfo = [NoCalls, NoFunctionCalls, NoFunctions,
+ NewInfo = [NoCalls, NoFunctionCalls, NoFunctions,
{no_inter_function_calls,length(EE_M)}],
XrefMod#xref_mod{info = NewInfo}
end,
@@ -1274,11 +1274,11 @@ do_set_up(S) when S#xref.mode =:= functions ->
finish_set_up(S1, Vs);
do_set_up(S) when S#xref.mode =:= modules ->
ModDictList = dict:to_list(S#xref.modules),
- [X0, I0, Mod_DF, Mod_DF_1, Mod_DF_2, Mod_DF_3] =
+ [X0, I0, Mod_DF, Mod_DF_1, Mod_DF_2, Mod_DF_3] =
make_families(ModDictList, 7),
I = union_of_family(I0),
AM = domain(X0),
-
+
{XU, Predefined} = make_predefined(I, AM),
%% Add "hidden" functions to the exports.
X1 = family_union(X0, Predefined),
@@ -1288,8 +1288,8 @@ do_set_up(S) when S#xref.mode =:= modules ->
M2A = make_M2A(ModDictList),
{A2R,A} = make_A2R(S#xref.applications),
R = set(dict:fetch_keys(S#xref.releases)),
-
- ME = projection({external, fun({M1,{M2,_F2,_A2}}) -> {M1,M2} end},
+
+ ME = projection({external, fun({M1,{M2,_F2,_A2}}) -> {M1,M2} end},
family_to_relation(I0)),
ME2AE = multiple_relative_product({M2A, M2A}, ME),
@@ -1298,7 +1298,7 @@ do_set_up(S) when S#xref.mode =:= modules ->
RE = range(AE2RE),
%% Undef is the union of U0 and Lib:
- {_Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
+ {_Undef, U0, Lib, Lib_DF, Lib_DF_1, Lib_DF_2, Lib_DF_3} =
make_libs(XU, X1, AM, S#xref.library_path, S#xref.libraries),
{B, U} = make_builtins(U0),
X1_B = family_union(X1, B),
@@ -1312,7 +1312,7 @@ do_set_up(S) when S#xref.mode =:= modules ->
X = family_union(X1, Lib),
Empty = empty_set(),
- Vs = [{'X',X},{'U',U},{'B',B},{'XU',XU},{v,V},
+ Vs = [{'X',X},{'U',U},{'B',B},{'XU',XU},{v,V},
{e,{Empty,Empty}},
{'M',M},{'A',A},{'R',R},
{'AM',AM},{'UM',UM},{'LM',LM},
@@ -1328,10 +1328,10 @@ finish_set_up(S, Vs) ->
S1 = S#xref{variables = T},
%% io:format("~p <= state <= ~p~n", [pack:lsize(S), pack:usize(S)]),
{ok, S1}.
-
+
do_finish_set_up([{Key, Value} | Vs], T) ->
{Type, OType} = var_type(Key),
- Val = #xref_var{name = Key, value = Value, vtype = predef,
+ Val = #xref_var{name = Key, value = Value, vtype = predef,
otype = OType, type = Type},
T1 = dict:store(Key, Val, T),
do_finish_set_up(Vs, T1);
@@ -1362,15 +1362,15 @@ var_type('EE') -> {function, edge};
var_type('LC') -> {function, edge};
var_type('UC') -> {function, edge};
var_type('XC') -> {function, edge};
-var_type('AE') -> {application, edge};
-var_type('ME') -> {module, edge};
+var_type('AE') -> {application, edge};
+var_type('ME') -> {module, edge};
var_type('RE') -> {release, edge};
var_type(_) -> {foo, bar}.
make_families(ModDictList, N) ->
Fun1 = fun({_,XMod}) -> XMod#xref_mod.data end,
Ss = from_sets(map(Fun1, ModDictList)),
- %% io:format("~n~p <= module data <= ~p~n",
+ %% io:format("~n~p <= module data <= ~p~n",
%% [pack:lsize(Ss), pack:usize(Ss)]),
make_fams(N, Ss, []).
@@ -1389,7 +1389,7 @@ make_M2A(ModDictList) ->
make_A2R(ApplDict) ->
AppDict = dict:to_list(ApplDict),
Fun = fun({A,XApp}) -> {A, XApp#xref_app.rel_name} end,
- Appl0 = family(map(Fun, AppDict)),
+ Appl0 = family(map(Fun, AppDict)),
AllApps = domain(Appl0),
Appl = family_to_relation(Appl0),
{Appl, AllApps}.
@@ -1445,13 +1445,13 @@ make_libs(XU, F, AM, LibPath, LibDict) ->
false ->
Libraries = dict:to_list(LibDict),
Lb = restriction(a_function(Libraries), UM),
- MFun = fun({M,XLib}) ->
+ MFun = fun({M,XLib}) ->
#xref_lib{dir = Dir} = XLib,
xref_utils:module_filename(Dir, M)
end,
map(MFun, to_external(Lb))
end,
- Fun = fun(FileName, Deprs) ->
+ Fun = fun(FileName, Deprs) ->
case beam_lib:chunks(FileName, [exports, attributes]) of
{ok, {M, [{exports,X}, {attributes,A}]}} ->
Exports = mfa_exports(X, A, M),
@@ -1496,14 +1496,14 @@ user_family(R) ->
partition_family({external, fun({_MFA1, {M2,_,_}}) -> M2 end}, R).
do_variables(State) ->
- Fun = fun({Name, #xref_var{vtype = user}}, {P,U}) ->
+ Fun = fun({Name, #xref_var{vtype = user}}, {P,U}) ->
{P,[Name | U]};
- ({Name, #xref_var{vtype = predef}}, A={P,U}) ->
+ ({Name, #xref_var{vtype = predef}}, A={P,U}) ->
case atom_to_list(Name) of
[H|_] when H>= $a, H=<$z -> A;
_Else -> {[Name | P], U}
end;
- ({{tmp, V}, _}, A) ->
+ ({{tmp, V}, _}, A) ->
io:format("Bug in ~p: temporary ~p~n", [?MODULE, V]), A;
(_V, A) -> A
end,
@@ -1565,7 +1565,7 @@ do_info(S, libraries) ->
map(fun({_L,XLib}) -> lib_info(XLib) end, D);
do_info(_S, I) ->
error({no_such_info, I}).
-
+
do_info(S, Type, E) when is_atom(E) ->
do_info(S, Type, [E]);
do_info(S, modules, Modules0) when is_list(Modules0) ->
@@ -1598,7 +1598,7 @@ find_info([E | Es], Dict, Error) ->
{ok, X} ->
[X | find_info(Es, Dict, Error)]
end;
-find_info([], _Dict, _Error) ->
+find_info([], _Dict, _Error) ->
[].
%% -> {[{AppName, RelName}], [{RelName, XApp}]}
@@ -1618,7 +1618,7 @@ rel_apps(S) ->
rel_apps_sums(AR, RRA0, S) ->
AppMods = app_mods(S), % [{AppName, XMod}]
RRA1 = relation_to_family(relation(RRA0)),
- RRA = inverse(substitution(1, RRA1)),
+ RRA = inverse(substitution(1, RRA1)),
%% RRA is [{RelName,{RelName,[XApp]}}]
RelMods = relative_product1(relation(AR), relation(AppMods)),
RelAppsMods = relative_product1(RRA, RelMods),
@@ -1630,7 +1630,7 @@ rel_apps_sums(AR, RRA0, S) ->
%% -> [{AppName, XMod}]
app_mods(S) ->
D = sort(dict:to_list(S#xref.modules)),
- Fun = fun({_M,XMod}, Acc) ->
+ Fun = fun({_M,XMod}, Acc) ->
case XMod#xref_mod.app_name of
[] -> Acc;
[AppName] -> [{AppName, XMod} | Acc]
@@ -1639,7 +1639,7 @@ app_mods(S) ->
foldl(Fun, [], D).
mod_info(XMod) ->
- #xref_mod{name = M, app_name = AppName, builtins = BuiltIns,
+ #xref_mod{name = M, app_name = AppName, builtins = BuiltIns,
dir = Dir, info = Info} = XMod,
App = sup_info(AppName),
{M, [{application, App}, {builtins, BuiltIns}, {directory, Dir} | Info]}.
@@ -1649,7 +1649,7 @@ app_info({AppName, ModSums}, S) ->
#xref_app{rel_name = RelName, vsn = Vsn, dir = Dir} = XApp,
Release = sup_info(RelName),
{AppName, [{directory,Dir}, {release, Release}, {version,Vsn} | ModSums]}.
-
+
rel_info({{RelName, XApps}, ModSums}, S) ->
NoApps = length(XApps),
XRel = dict:fetch(RelName, S#xref.releases),
@@ -1678,16 +1678,16 @@ no_sum(S, L) when S#xref.mode =:= modules ->
[{no_analyzed_modules, length(L)}].
no_sum([XMod | D], C0, UC0, LC0, XC0, UFC0, L0, X0, EV0, NoM) ->
- [{no_calls, {C,UC}},
+ [{no_calls, {C,UC}},
{no_function_calls, {LC,XC,UFC}},
{no_functions, {L,X}},
{no_inter_function_calls, EV}] = XMod#xref_mod.info,
no_sum(D, C0+C, UC0+UC, LC0+LC, XC0+XC, UFC0+UFC, L0+L, X0+X, EV0+EV, NoM);
no_sum([], C, UC, LC, XC, UFC, L, X, EV, NoM) ->
[{no_analyzed_modules, NoM},
- {no_calls, {C,UC}},
+ {no_calls, {C,UC}},
{no_function_calls, {LC,XC,UFC}},
- {no_functions, {L,X}},
+ {no_functions, {L,X}},
{no_inter_function_calls, EV}].
%% -> ok | throw(Error)
@@ -1712,20 +1712,20 @@ warnings(Flag, Message, [F | Fs]) ->
%% pack(term()) -> term()
%%
%% The identify function. The returned term does not use more heap
-%% than the given term. Tuples that are equal (=:=/2) are made
+%% than the given term. Tuples that are equal (=:=/2) are made
%% "the same".
%%
%% The process dictionary is used because it seems to be faster than
%% anything else right now...
%%
%pack(T) -> T;
-pack(T) ->
+pack(T) ->
PD = erase(),
NT = pack1(T),
%% true = T =:= NT,
%% io:format("erasing ~p elements...~n", [length(erase())]),
erase(), % wasting heap (and time)...
- map(fun({K,V}) -> put(K, V) end, PD),
+ foreach(fun({K,V}) -> put(K, V) end, PD),
NT.
pack1(C) when not is_tuple(C), not is_list(C) ->
diff --git a/lib/tools/src/xref_compiler.erl b/lib/tools/src/xref_compiler.erl
index 67ac8c617d..c80eb0e669 100644
--- a/lib/tools/src/xref_compiler.erl
+++ b/lib/tools/src/xref_compiler.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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%
%%
@@ -37,15 +37,15 @@
-export([format_error/1]).
--import(lists,
+-import(lists,
[concat/1, foldl/3, nthtail/2, reverse/1, sort/1, sublist/2]).
-import(sofs,
[composite/2, difference/2, empty_set/0, from_term/1,
intersection/2, is_empty_set/1, multiple_relative_product/2,
projection/2, relation/1, relation_to_family/1,
- restriction/2, substitution/2, to_external/1, union/2,
- union_of_family/1]).
+ restriction/2, specification/2, substitution/2,
+ to_external/1, union/2, union_of_family/1]).
%%
%% Exported functions
@@ -75,7 +75,7 @@ compile(Chars, Table) ->
{error, Info, Line} ->
error({parse_error, Line, Info})
end.
-
+
format_error({error, Module, Error}) ->
Module:format_error(Error);
format_error({parse_error, Line, Error}) ->
@@ -115,7 +115,7 @@ statements([Stmt={assign, VarType, Name, E} | Stmts0], Table, L, UV) ->
throw_error({variable_reassigned, xref_parser:t2s(Stmt)});
error ->
{Type, OType, NewE} = t_expr(E, Table),
- Val = #xref_var{name = Name, vtype = VarType,
+ Val = #xref_var{name = Name, vtype = VarType,
otype = OType, type = Type},
NewTable = dict:store(Name, Val, Table),
Stmts = if Stmts0 =:= [] -> [{variable, Name}]; true -> Stmts0 end,
@@ -128,9 +128,9 @@ statements([Expr], Table, L, UV) ->
E1 = un_familiarize(Type, OType, NewE),
NE = case {Type, OType} of
%% Edges with empty sets of line numbers are removed.
- {{line, _}, edge} ->
+ {{line, _}, edge} ->
{relation_to_family, E1};
- {_Type, edge_closure} ->
+ {_Type, edge_closure} ->
%% Fake a closure usage, just to make sure it is destroyed.
E2 = {fun graph_access/2, E1, E1},
{fun(_E) -> 'closure()' end, E2};
@@ -163,7 +163,7 @@ t_expr(E, Table) ->
%%% Constant = atom() | {atom(), atom()} | MFA | {MFA, MFA}
%%% Call = atom() % function in the sofs module
%%% | fun()
-%%% Type = {line, LineType} | function | module | application | release
+%%% Type = {line, LineType} | function | module | application | release
%%% | number
%%% LineType = line | local_call | external_call | export_call | all_line_call
%%% VarType = predef | user | tmp
@@ -182,7 +182,7 @@ check_expr({variable, Name}, Table) ->
case dict:find(Name, Table) of
{ok, #xref_var{vtype = VarType, otype = OType, type = Type}} ->
V0 = {variable, {VarType, Name}},
- V = case {VarType, Type, OType} of
+ V = case {VarType, Type, OType} of
{predef, release, _} -> V0;
{predef, application, _} -> V0;
{predef, module, _} -> V0;
@@ -212,7 +212,7 @@ check_expr(Expr={set, SOp, E}, Table) ->
{edge_set, domain} -> vertex_set;
{edge_set, weak} -> edge_set;
{edge_set, strict} -> edge_set;
- _ ->
+ _ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end,
Op = set_op(SOp),
@@ -223,10 +223,10 @@ check_expr(Expr={graph, Op, E}, Table) ->
case Type of
{line, _LineType} ->
throw_error({type_error, xref_parser:t2s(Expr)});
- _Else ->
+ _Else ->
ok
end,
- OType =
+ OType =
case {NOType, Op} of
{edge, components} -> vertex_set;
{edge, condensation} -> edge_set;
@@ -237,7 +237,7 @@ check_expr(Expr={graph, Op, E}, Table) ->
%% Neither need nor want these ones:
%% {edge_set, closure} -> edge_set_closure;
%% {edge_set, components} -> vertex_set_set;
- _ ->
+ _ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end,
E2 = {convert, NOType, edge_closure, E1},
@@ -271,10 +271,10 @@ check_expr(Expr={set, SOp, E1, E2}, Table) ->
number ->
{expr, number, number, {call, ari_op(SOp), NE1, NE2}};
_Else -> % set
- {Type, NewE1, NewE2} =
+ {Type, NewE1, NewE2} =
case {type_ord(Type1), type_ord(Type2)} of
{T1, T2} when T1 =:= T2 ->
- %% Example: if Type1 = {line, line} and
+ %% Example: if Type1 = {line, line} and
%% Type2 = {line, export_line}, then this is not
%% correct, but works:
{Type1, NE1, NE2};
@@ -296,7 +296,7 @@ check_expr(Expr={restr, ROp, E1, E2}, Table) ->
throw_error({type_error, xref_parser:t2s(Expr)});
{_Type1, {line, _LineType2}} ->
throw_error({type_error, xref_parser:t2s(Expr)});
- _ ->
+ _ ->
ok
end,
case {OType1, OType2} of
@@ -307,14 +307,14 @@ check_expr(Expr={restr, ROp, E1, E2}, Table) ->
{edge, vertex} ->
restriction(ROp, E1, Type1, NE1, Type2, NE2);
{edge_closure, vertex} when ROp =:= '|||' ->
- {expr, _, _, R1} =
+ {expr, _, _, R1} =
closure_restriction('|', Type1, Type2, OType2, NE1, NE2),
- {expr, _, _, R2} =
+ {expr, _, _, R2} =
closure_restriction('||', Type1, Type2, OType2, NE1, NE2),
{expr, Type1, edge, {call, intersection, R1, R2}};
- {edge_closure, vertex} ->
+ {edge_closure, vertex} ->
closure_restriction(ROp, Type1, Type2, OType2, NE1, NE2);
- _ ->
+ _ ->
throw_error({type_error, xref_parser:t2s(Expr)})
end;
check_expr(Expr={path, E1, E2}, Table) ->
@@ -330,7 +330,7 @@ check_expr(Expr={path, E1, E2}, Table) ->
end,
E2b = {convert, OType2, Type2, Type1, E2a},
{OType1, NE1} = path_arg(OType1a, E1a),
- NE2 = case {OType1, OType2} of
+ NE2 = case {OType1, OType2} of
{path, edge} -> {convert, OType2, edge_closure, E2b};
{path, edge_closure} when Type1 =:= Type2 -> E2b;
_ -> throw_error({type_error, xref_parser:t2s(Expr)})
@@ -347,7 +347,7 @@ check_expr({regexpr, RExpr, Type0}, _Table) ->
release -> 'R'
end,
Var = {variable, {predef, V}},
- Call = {call, fun(E, V2) -> xref_utils:regexpr(E, V2) end,
+ Call = {call, fun(E, V2) -> xref_utils:regexpr(E, V2) end,
{constants, RExpr}, Var},
{expr, Type, vertex, Call};
check_expr(C={constant, _Type, _OType, _C}, Table) ->
@@ -368,15 +368,15 @@ check_conversion(OType, Type1, Type2, Expr) ->
end.
%% Allowed conversions.
-conversions(_OType, {line, LineType}, {line, LineType}) -> ok;
+conversions(_OType, {line, LineType}, {line, LineType}) -> ok;
conversions(edge, {line, _}, {line, all_line_call}) -> ok;
-conversions(edge, From, {line, Line})
+conversions(edge, From, {line, Line})
when is_atom(From), Line =/= all_line_call -> ok;
conversions(vertex, From, {line, line}) when is_atom(From) -> ok;
conversions(vertex, From, To) when is_atom(From), is_atom(To) -> ok;
conversions(edge, From, To) when is_atom(From), is_atom(To) -> ok;
%% "Extra":
-conversions(edge, {line, Line}, To)
+conversions(edge, {line, Line}, To)
when is_atom(To), Line =/= all_line_call -> ok;
conversions(vertex, {line, line}, To) when is_atom(To) -> ok;
conversions(_OType, _From, _To) -> not_ok.
@@ -399,7 +399,7 @@ ari_op(difference) -> fun(X, Y) -> X - Y end.
restriction(ROp, E1, Type1, NE1, Type2, NE2) ->
{Column, _} = restr_op(ROp),
- case NE1 of
+ case NE1 of
{call, union_of_family, _E} when ROp =:= '|' ->
restriction(Column, Type1, E1, Type2, NE2);
{call, union_of_family, _E} when ROp =:= '||' ->
@@ -455,8 +455,8 @@ check_constants(Cs=[C={constant, Type0, OType, _Con} | Cs1], Table) ->
E = function_vertices_to_family(Type, OType, {constants, S}),
{expr, Type, OType, E};
[{Type1, [C1|_]}, {Type2, [C2|_]} | _] ->
- throw_error({type_mismatch,
- make_vertex(Type1, C1),
+ throw_error({type_mismatch,
+ make_vertex(Type1, C1),
make_vertex(Type2, C2)})
end.
@@ -467,7 +467,7 @@ check_mix([C={constant, Type, OType, _Con} | Cs], Type0, OType, _C0)
check_mix(Cs, Type, OType, C);
check_mix([C | _], _Type0, _OType0, C0) ->
throw_error({type_mismatch, xref_parser:t2s(C0), xref_parser:t2s(C)});
-check_mix([], _Type0, _OType0, _C0) ->
+check_mix([], _Type0, _OType0, _C0) ->
ok.
split(Types, Cs, Table) ->
@@ -478,11 +478,11 @@ split([Type | Types], Vs, AllSoFar, _Type, Table, L) ->
S0 = known_vertices(Type, Vs, Table),
S = difference(S0, AllSoFar),
case is_empty_set(S) of
- true ->
+ true ->
split(Types, Vs, AllSoFar, Type, Table, L);
- false ->
+ false ->
All = union(AllSoFar, S0),
- split(Types, Vs, All, Type, Table,
+ split(Types, Vs, All, Type, Table,
[{Type, to_external(S)} | L])
end;
split([], Vs, All, Type, _Table, L) ->
@@ -491,7 +491,7 @@ split([], Vs, All, Type, _Table, L) ->
[C|_] -> throw_error({unknown_constant, make_vertex(Type, C)})
end.
-make_vertex(Type, C) ->
+make_vertex(Type, C) ->
xref_parser:t2s({constant, Type, vertex, C}).
constant_vertices([{constant, _Type, edge, {A,B}} | Cs], L) ->
@@ -504,7 +504,7 @@ constant_vertices([], L) ->
known_vertices('Fun', Cs, T) ->
M = projection(1, Cs),
F = union_of_family(restriction(fetch_value(v, T), M)),
- intersection(Cs, F);
+ union(bifs(Cs), intersection(Cs, F));
known_vertices('Mod', Cs, T) ->
intersection(Cs, fetch_value('M', T));
known_vertices('App', Cs, T) ->
@@ -512,6 +512,11 @@ known_vertices('App', Cs, T) ->
known_vertices('Rel', Cs, T) ->
intersection(Cs, fetch_value('R', T)).
+bifs(Cs) ->
+ specification({external,
+ fun({M,F,A}) -> xref_utils:is_builtin(M, F, A) end},
+ Cs).
+
function_vertices_to_family(function, vertex, E) ->
{call, partition_family, 1, E};
function_vertices_to_family(_Type, _OType, E) ->
@@ -567,11 +572,11 @@ convert(E, OType, FromType, ToType) ->
general(_ObjectType, FromType, ToType, X) when FromType =:= ToType ->
X;
-general(edge, {line, _LineType}, ToType, LEs) ->
+general(edge, {line, _LineType}, ToType, LEs) ->
VEs = {projection, ?Q({external, fun({V1V2,_Ls}) -> V1V2 end}), LEs},
general(edge, function, ToType, VEs);
general(edge, function, ToType, VEs) ->
- MEs = {projection,
+ MEs = {projection,
?Q({external, fun({{M1,_,_},{M2,_,_}}) -> {M1,M2} end}),
VEs},
general(edge, module, ToType, MEs);
@@ -580,7 +585,7 @@ general(edge, module, ToType, MEs) ->
general(edge, application, ToType, AEs);
general(edge, application, release, AEs) ->
{image, {get, ae}, AEs};
-general(vertex, {line, _LineType}, ToType, L) ->
+general(vertex, {line, _LineType}, ToType, L) ->
V = {partition_family, ?Q(1), {domain, L}},
general(vertex, function, ToType, V);
general(vertex, function, ToType, V) ->
@@ -595,18 +600,18 @@ general(vertex, application, release, A) ->
special(_ObjectType, FromType, ToType, X) when FromType =:= ToType ->
X;
special(edge, {line, _LineType}, {line, all_line_call}, Calls) ->
- {put, ?T(mods),
- {projection,
- ?Q({external, fun({{{M1,_,_},{M2,_,_}},_}) -> {M1,M2} end}),
+ {put, ?T(mods),
+ {projection,
+ ?Q({external, fun({{{M1,_,_},{M2,_,_}},_}) -> {M1,M2} end}),
Calls},
- {put, ?T(def_at),
+ {put, ?T(def_at),
{union, {image, {get, def_at},
- {union, {domain, {get, ?T(mods)}},
+ {union, {domain, {get, ?T(mods)}},
{range, {get, ?T(mods)}}}}},
{fun funs_to_lines/2,
{get, ?T(def_at)}, Calls}}};
special(edge, function, {line, LineType}, VEs) ->
- Var = if
+ Var = if
LineType =:= line -> call_at;
LineType =:= export_call -> e_call_at;
LineType =:= local_call -> l_call_at;
@@ -615,9 +620,9 @@ special(edge, function, {line, LineType}, VEs) ->
line_edges(VEs, Var);
special(edge, module, ToType, MEs) ->
VEs = {image,
- {projection,
+ {projection,
?Q({external, fun(FE={{M1,_,_},{M2,_,_}}) -> {{M1,M2},FE} end}),
- {union,
+ {union,
{image, {get, e},
{projection, ?Q({external, fun({M1,_M2}) -> M1 end}), MEs}}}},
MEs},
@@ -629,7 +634,7 @@ special(edge, release, ToType, REs) ->
AEs = {inverse_image, {get, ae}, REs},
special(edge, application, ToType, AEs);
special(vertex, function, {line, _LineType}, V) ->
- {restriction,
+ {restriction,
{union_of_family, {restriction, {get, def_at}, {domain, V}}},
{union_of_family, V}};
special(vertex, module, ToType, M) ->
@@ -643,15 +648,15 @@ special(vertex, release, ToType, R) ->
special(vertex, application, ToType, A).
line_edges(VEs, CallAt) ->
- {put, ?T(ves), VEs,
- {put, ?T(m1),
- {projection, ?Q({external, fun({{M1,_,_},_}) -> M1 end}),
+ {put, ?T(ves), VEs,
+ {put, ?T(m1),
+ {projection, ?Q({external, fun({{M1,_,_},_}) -> M1 end}),
{get, ?T(ves)}},
{image, {projection, ?Q({external, fun(C={VV,_L}) -> {VV,C} end}),
{union, {image, {get, CallAt}, {get, ?T(m1)}}}},
{get, ?T(ves)}}}}.
-%% {(((v1,l1),(v2,l2)),l) :
+%% {(((v1,l1),(v2,l2)),l) :
%% (v1,l1) in DefAt and (v2,l2) in DefAt and ((v1,v2),L) in CallAt}
funs_to_lines(DefAt, CallAt) ->
T1 = multiple_relative_product({DefAt, DefAt}, projection(1, CallAt)),
@@ -765,7 +770,7 @@ save_vars([], _D, Vs, UVs, L) ->
%% Traverses the expression again, this time using more or less the
%% inverse of the table created by find_nodes. The first time a node
-%% is visited, its children are traversed, the following times a
+%% is visited, its children are traversed, the following times a
%% get instructions are inserted (using the saved value).
make_instructions(N, UserVars, D) ->
{D1, Is0} = make_instrs(N, D, []),
@@ -777,9 +782,9 @@ make_instructions(N, UserVars, D) ->
make_more_instrs([UV | UVs], D, Is) ->
case dict:find(UV, D) of
- error ->
+ error ->
make_more_instrs(UVs, D, Is);
- _Else ->
+ _Else ->
{ND, NIs} = make_instrs(UV, D, Is),
make_more_instrs(UVs, ND, [pop | NIs])
end;
@@ -844,17 +849,17 @@ evaluate([{quote, Val} | P], T, S) ->
evaluate(P, T, [Val | S]);
evaluate([{get, Var} | P], T, S) when is_atom(Var) -> % predefined
Value = fetch_value(Var, T),
- Val = case Value of
+ Val = case Value of
{R, _} -> R; % relation
_ -> Value % simple set
end,
- evaluate(P, T, [Val | S]);
+ evaluate(P, T, [Val | S]);
evaluate([{get, {inverse, Var}} | P], T, S) -> % predefined, inverse
{_, R} = fetch_value(Var, T),
- evaluate(P, T, [R | S]);
+ evaluate(P, T, [R | S]);
evaluate([{get, {user, Var}} | P], T, S) ->
Val = fetch_value(Var, T),
- evaluate(P, T, [Val | S]);
+ evaluate(P, T, [Val | S]);
evaluate([{get, Var} | P], T, S) -> % tmp
evaluate(P, T, [dict:fetch(Var, T) | S]);
evaluate([{save, Var={tmp, _}} | P], T, S=[Val | _]) ->
@@ -862,7 +867,7 @@ evaluate([{save, Var={tmp, _}} | P], T, S=[Val | _]) ->
evaluate(P, dict:store(Var, Val, T1), S);
evaluate([{save, {user, Name}} | P], T, S=[Val | _]) ->
#xref_var{vtype = user, otype = OType, type = Type} = dict:fetch(Name, T),
- NewVar = #xref_var{name = Name, value = Val,
+ NewVar = #xref_var{name = Name, value = Val,
vtype = user, otype = OType, type = Type},
T1 = update_graph_counter(Val, +1, T),
NT = dict:store(Name, NewVar, T1),
@@ -889,7 +894,7 @@ update_graph_counter(Value, Inc, T) ->
error when Inc =:= 1 ->
dict:store(Value, 1, T)
end;
- _EXIT ->
+ _EXIT ->
T
end.
diff --git a/lib/tools/src/xref_reader.erl b/lib/tools/src/xref_reader.erl
index db755c31d8..d22f0df164 100644
--- a/lib/tools/src/xref_reader.erl
+++ b/lib/tools/src/xref_reader.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2000-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2000-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(xref_reader).
@@ -22,7 +22,7 @@
-import(lists, [keysearch/3, member/2, reverse/1]).
--record(xrefr,
+-record(xrefr,
{module=[],
function=[],
def_at=[],
@@ -59,15 +59,15 @@
module(Module, Forms, CollectBuiltins, X, DF) ->
Attrs = [{Attr,V} || {attribute,_Line,Attr,V} <- Forms],
IsAbstract = xref_utils:is_abstract_module(Attrs),
- S = #xrefr{module = Module, builtins_too = CollectBuiltins,
+ S = #xrefr{module = Module, builtins_too = CollectBuiltins,
is_abstr = IsAbstract, x = X, df = DF},
forms(Forms, S).
forms([F | Fs], S) ->
S1 = form(F, S),
forms(Fs, S1);
-forms([], S) ->
- #xrefr{module = M, def_at = DefAt,
+forms([], S) ->
+ #xrefr{module = M, def_at = DefAt,
l_call_at = LCallAt, x_call_at = XCallAt,
el = LC, ex = XC, x = X, df = Depr,
lattrs = AL, xattrs = AX, battrs = B, unresolved = U} = S,
@@ -75,7 +75,7 @@ forms([], S) ->
{ok, M, {DefAt, LCallAt, XCallAt, LC, XC, X, Attrs, Depr}, U}.
form({attribute, Line, xref, Calls}, S) -> % experimental
- #xrefr{module = M, function = Fun,
+ #xrefr{module = M, function = Fun,
lattrs = L, xattrs = X, battrs = B} = S,
attr(Calls, Line, M, Fun, L, X, B, S);
form({attribute, _Line, _Attr, _Val}, S) ->
@@ -110,12 +110,12 @@ clauses([{clause, _Line, _H, G, B} | Cs], FunVars, Matches, S) ->
S2 = expr(B, S1),
S3 = S2#xrefr{funvars = FunVars, matches = Matches},
clauses(Cs, S3);
-clauses([], _FunVars, _Matches, S) ->
+clauses([], _FunVars, _Matches, S) ->
S.
attr([E={From, To} | As], Ln, M, Fun, AL, AX, B, S) ->
case mfa(From, M) of
- {_, _, MFA} when MFA =:= Fun; [] =:= Fun ->
+ {_, _, MFA} when MFA =:= Fun; [] =:= Fun ->
attr(From, To, Ln, M, Fun, AL, AX, B, S, As, E);
{_, _, _} ->
attr(As, Ln, M, Fun, AL, AX, [E | B], S);
@@ -164,7 +164,7 @@ expr({call, Line,
%% Added in R10B-6. M:F/A.
expr({'fun', Line, {function, Mod, Fun, Arity}}, S);
expr({'fun', Line, {function, Mod, Name, Arity}}, S) ->
- %% Added in R10B-6. M:F/A.
+ %% Added in R10B-6. M:F/A.
As = lists:duplicate(Arity, {atom, Line, foo}),
external_call(Mod, Name, As, Line, false, S);
expr({'fun', Line, {function, Name, Arity}, _Extra}, S) ->
@@ -183,7 +183,7 @@ expr({call, Line, {remote, _Line, Mod, Name}, As}, S) ->
expr({call, Line, F, As}, S) ->
external_call(erlang, apply, [F, list2term(As)], Line, true, S);
expr({match, _Line, {var,_,Var}, {'fun', _, {clauses, Cs}, _Extra}}, S) ->
- %% This is what is needed in R7 to avoid warnings for the functions
+ %% This is what is needed in R7 to avoid warnings for the functions
%% that are passed around by the "expansion" of list comprehension.
S1 = S#xrefr{funvars = [Var | S#xrefr.funvars]},
clauses(Cs, S1);
@@ -192,6 +192,14 @@ expr({match, _Line, {var,_,Var}, E}, S) ->
%% Args = [A,B], apply(m, f, Args)
S1 = S#xrefr{matches = [{Var, E} | S#xrefr.matches]},
expr(E, S1);
+expr({op, _Line, 'orelse', Op1, Op2}, S) ->
+ expr([Op1, Op2], S);
+expr({op, _Line, 'andalso', Op1, Op2}, S) ->
+ expr([Op1, Op2], S);
+expr({op, Line, Op, Operand1, Operand2}, S) ->
+ external_call(erlang, Op, [Operand1, Operand2], Line, false, S);
+expr({op, Line, Op, Operand}, S) ->
+ external_call(erlang, Op, [Operand], Line, false, S);
expr(T, S) when is_tuple(T) ->
expr(tuple_to_list(T), S);
expr([E | Es], S) ->
@@ -241,13 +249,13 @@ external_call(Mod, Fun, ArgsList, Line, X, S) ->
_Else -> % apply2, 1 or 2
check_funarg(W, ArgsList, Line, S1)
end.
-
+
eval_args(Mod, Fun, ArgsTerm, Line, S, ArgsList, Extra) ->
{IsSimpleCall, M, F} = mod_fun(Mod, Fun),
case term2list(ArgsTerm, [], S) of
undefined ->
S1 = unresolved(M, F, -1, Line, S),
- expr(ArgsList, S1);
+ expr(ArgsList, S1);
ArgsList2 when not IsSimpleCall ->
S1 = unresolved(M, F, length(ArgsList2), Line, S),
expr(ArgsList, S1);
@@ -288,14 +296,14 @@ fun_args(apply2, [FunArg, Args]) -> {FunArg, Args};
fun_args(1, [FunArg | Args]) -> {FunArg, Args};
fun_args(2, [_Node, FunArg | Args]) -> {FunArg, Args}.
-list2term([A | As]) ->
+list2term([A | As]) ->
{cons, 0, A, list2term(As)};
-list2term([]) ->
+list2term([]) ->
{nil, 0}.
term2list({cons, _Line, H, T}, L, S) ->
term2list(T, [H | L], S);
-term2list({nil, _Line}, L, _S) ->
+term2list({nil, _Line}, L, _S) ->
reverse(L);
term2list({var, _, Var}, L, S) ->
case keysearch(Var, 1, S#xrefr.matches) of
@@ -332,11 +340,11 @@ handle_call(Locality, To0, Line, S, IsUnres) ->
true ->
S
end,
- case Locality of
- local ->
+ case Locality of
+ local ->
S1#xrefr{el = [Call | S1#xrefr.el],
l_call_at = [CallAt | S1#xrefr.l_call_at]};
- external ->
+ external ->
S1#xrefr{ex = [Call | S1#xrefr.ex],
x_call_at = [CallAt | S1#xrefr.x_call_at]}
end.
diff --git a/lib/tools/test/eprof_SUITE.erl b/lib/tools/test/eprof_SUITE.erl
index 028fea8fe1..67607c6cf2 100644
--- a/lib/tools/test/eprof_SUITE.erl
+++ b/lib/tools/test/eprof_SUITE.erl
@@ -20,10 +20,89 @@
-include("test_server.hrl").
--export([all/1,tiny/1,eed/1]).
+-export([all/1,tiny/1,eed/1,basic/1]).
-all(suite) -> [tiny,eed].
+all(suite) -> [basic,tiny,eed].
+basic(suite) -> [];
+basic(Config) when is_list(Config) ->
+
+ %% load eprof_test and change directory
+
+ ?line {ok, OldCurDir} = file:get_cwd(),
+ Datadir = ?config(data_dir, Config),
+ Privdir = ?config(priv_dir, Config),
+ ?line {ok,eprof_test} = compile:file(filename:join(Datadir, "eprof_test"),
+ [trace,{outdir, Privdir}]),
+ ?line ok = file:set_cwd(Privdir),
+ ?line code:purge(eprof_test),
+ ?line {module,eprof_test} = code:load_file(eprof_test),
+
+ %% rootset profiling
+
+ ?line ensure_eprof_stopped(),
+ ?line profiling = eprof:profile([self()]),
+ ?line {error, already_profiling} = eprof:profile([self()]),
+ ?line profiling_stopped = eprof:stop_profiling(),
+ ?line profiling_already_stopped = eprof:stop_profiling(),
+ ?line profiling = eprof:start_profiling([self(),self(),self()]),
+ ?line profiling_stopped = eprof:stop_profiling(),
+
+ %% with patterns
+
+ ?line profiling = eprof:start_profiling([self()], {?MODULE, '_', '_'}),
+ ?line {error, already_profiling} = eprof:start_profiling([self()], {?MODULE, '_', '_'}),
+ ?line profiling_stopped = eprof:stop_profiling(),
+ ?line profiling = eprof:start_profiling([self()], {?MODULE, start_stop, '_'}),
+ ?line profiling_stopped = eprof:stop_profiling(),
+ ?line profiling = eprof:start_profiling([self()], {?MODULE, start_stop, 1}),
+ ?line profiling_stopped = eprof:stop_profiling(),
+
+ %% with fun
+
+ ?line {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end),
+ ?line profiling = eprof:profile([self()]),
+ ?line {error, already_profiling} = eprof:profile(fun() -> eprof_test:go(10) end),
+ ?line profiling_stopped = eprof:stop_profiling(),
+ ?line {ok, _} = eprof:profile(fun() -> eprof_test:go(10) end),
+ ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end),
+ ?line Pid = whereis(eprof),
+ ?line {ok, _} = eprof:profile(erlang:processes() -- [Pid], fun() -> eprof_test:go(10) end),
+ ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}),
+ ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, '_'}),
+ ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, go, 1}),
+ ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, dec, 1}),
+
+ %% error case
+
+ ?line error = eprof:profile([Pid], fun() -> eprof_test:go(10) end),
+ ?line Pid = whereis(eprof),
+ ?line error = eprof:profile([Pid], fun() -> eprof_test:go(10) end),
+ ?line A = spawn(fun() -> receive _ -> ok end end),
+ ?line profiling = eprof:profile([A]),
+ ?line true = exit(A, kill_it),
+ ?line profiling_stopped = eprof:stop_profiling(),
+ ?line {error,_} = eprof:profile(fun() -> a = b end),
+
+ %% with mfa
+
+ ?line {ok, _} = eprof:profile([], eprof_test, go, [10]),
+ ?line {ok, _} = eprof:profile([], eprof_test, go, [10], {eprof_test, dec, 1}),
+
+ %% dump
+
+ ?line {ok, _} = eprof:profile([], fun() -> eprof_test:go(10) end, {eprof_test, '_', '_'}),
+ ?line [{_, Mfas}] = eprof:dump(),
+ ?line Dec_mfa = {eprof_test, dec, 1},
+ ?line Go_mfa = {eprof_test, go, 1},
+ ?line {value, {Go_mfa, { 1, _Time1}}} = lists:keysearch(Go_mfa, 1, Mfas),
+ ?line {value, {Dec_mfa, {11, _Time2}}} = lists:keysearch(Dec_mfa, 1, Mfas),
+
+ %% change current working directory
+
+ ?line ok = file:set_cwd(OldCurDir),
+ ?line stopped = eprof:stop(),
+ ok.
tiny(suite) -> [];
tiny(Config) when is_list(Config) ->
@@ -40,11 +119,11 @@ tiny(Config) when is_list(Config) ->
?line code:purge(eprof_suite_test),
?line {module,eprof_suite_test} = code:load_file(eprof_suite_test),
?line {ok,_Pid} = eprof:start(),
- ?line nothing_to_analyse = eprof:analyse(),
- ?line nothing_to_analyse = eprof:total_analyse(),
+ ?line nothing_to_analyze = eprof:analyze(),
+ ?line nothing_to_analyze = eprof:analyze(total),
?line eprof:profile([], eprof_suite_test, test, [Config]),
- ?line ok = eprof:analyse(),
- ?line ok = eprof:total_analyse(),
+ ?line ok = eprof:analyze(),
+ ?line ok = eprof:analyze(total),
?line ok = eprof:log("eprof_SUITE_logfile"),
?line stopped = eprof:stop(),
?line ?t:timetrap_cancel(TTrap),
@@ -79,8 +158,8 @@ eed(Config) when is_list(Config) ->
?line {ok,ok} = eprof:profile([], eed, file, [Script]),
?line {T3,_} = statistics(runtime),
?line profiling_already_stopped = eprof:stop_profiling(),
- ?line ok = eprof:analyse(),
- ?line ok = eprof:total_analyse(),
+ ?line ok = eprof:analyze(),
+ ?line ok = eprof:analyze(total),
?line ok = eprof:log("eprof_SUITE_logfile"),
?line stopped = eprof:stop(),
?line ?t:timetrap_cancel(TTrap),
diff --git a/lib/tools/test/eprof_SUITE_data/eprof_test.erl b/lib/tools/test/eprof_SUITE_data/eprof_test.erl
new file mode 100644
index 0000000000..33c428e893
--- /dev/null
+++ b/lib/tools/test/eprof_SUITE_data/eprof_test.erl
@@ -0,0 +1,9 @@
+-module(eprof_test).
+-export([go/1]).
+
+go(N) ->
+ 0 = dec(N),
+ ok.
+
+dec(0) -> 0;
+dec(N) -> dec(N - 1).
diff --git a/lib/tools/test/fprof_SUITE.erl b/lib/tools/test/fprof_SUITE.erl
index e437007e76..1cd9ac7824 100644
--- a/lib/tools/test/fprof_SUITE.erl
+++ b/lib/tools/test/fprof_SUITE.erl
@@ -356,7 +356,7 @@ imm_tail_seq(Config) when is_list(Config) ->
?line profiling_stopped = eprof:stop_profiling(),
?line R2 = R0,
%%
- ?line eprof:analyse(),
+ ?line eprof:analyze(),
?line stopped = eprof:stop(),
%%
?line {ok, Tracer} = fprof:profile(start),
@@ -471,7 +471,7 @@ imm_compile(Config) when is_list(Config) ->
?line TS3 = erlang:now(),
?line profiling_stopped = eprof:stop_profiling(),
%%
- ?line eprof:analyse(),
+ ?line eprof:analyze(),
?line stopped = eprof:stop(),
%%
?line {ok, Tracer} = fprof:profile(start),
diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl
index a7855b0bb9..f9d062ef85 100644
--- a/lib/tools/test/xref_SUITE.erl
+++ b/lib/tools/test/xref_SUITE.erl
@@ -39,11 +39,11 @@
-export([all/1, init/1, fini/1]).
-export([xref/1,
- addrem/1, convert/1, intergraph/1, lines/1, loops/1,
+ addrem/1, convert/1, intergraph/1, lines/1, loops/1,
no_data/1, modules/1]).
-export([files/1,
- add/1, default/1, info/1, lib/1, read/1, read2/1, remove/1,
+ add/1, default/1, info/1, lib/1, read/1, read2/1, remove/1,
replace/1, update/1, deprecated/1, trycatch/1,
abstract_modules/1, fun_mfa/1, qlc/1]).
@@ -82,7 +82,7 @@ init(Conf) when is_list(Conf) ->
?line ok = erl_tar:extract(TarFile, [compressed]),
?line ok = file:delete(TarFile),
[{copy_dir, CopyDir} | Conf].
-
+
fini(Conf) when is_list(Conf) ->
%% Nothing.
Conf.
@@ -120,7 +120,7 @@ addrem(Conf) when is_list(Conf) ->
LCallAt_m1 = [],
XCallAt_m1 = [{E1,13}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -132,7 +132,7 @@ addrem(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E2,96}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
?line S5 = set_up(S2),
@@ -142,7 +142,7 @@ addrem(Conf) when is_list(Conf) ->
?line {ok, XMod2, S6a} = remove_module(S6, m2),
?line [a2] = XMod2#xref_mod.app_name,
?line S7 = set_up(S6a),
-
+
?line AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
?line S9 = add_application(S7, AppInfo1),
?line S10 = set_up(S9),
@@ -186,7 +186,7 @@ convert(Conf) when is_list(Conf) ->
LCallAt_m1 = [],
XCallAt_m1 = [{E1,13},{E2,17},{E4,7}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -200,7 +200,7 @@ convert(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
D4 = {F4,6},
@@ -213,7 +213,7 @@ convert(Conf) when is_list(Conf) ->
LCallAt_m3 = [{E5,19}],
XCallAt_m3 = [{UE2,22}],
Info3 = #xref_mod{name = m3, app_name = [a3]},
- ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
+ ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
XC_m3, LC_m3),
Info4 = #xref_mod{name = m4, app_name = [a2]},
@@ -303,7 +303,7 @@ convert(Conf) when is_list(Conf) ->
?line {ok, _} = eval("(XXL) (Lin) (Fun) E", AllCallAt, S),
?line {ok, _} = eval("(XXL) (XXL) (Lin) (Fun) E", AllCallAt, S),
- ?line {ok, _} = eval(f("(XXL) (Lin) ~p", [[E1, E6]]),
+ ?line {ok, _} = eval(f("(XXL) (Lin) ~p", [[E1, E6]]),
[{{D1,D3},[13]}, {{D7,D4},[12]}], S),
?line {ok, _} = eval(f("(Fun) ~p", [AllMs]), AllE, S),
?line {ok, _} = eval("(Fun) [m1->m2,m2->m3]", [E1,E2,E6], S),
@@ -323,7 +323,7 @@ intergraph(Conf) when is_list(Conf) ->
F3 = {m1,f3,3},
F4 = {m1,f4,4},
F5 = {m1,f5,5},
-
+
F6 = {m2,f1,6}, % X
F7 = {m2,f1,7},
F8 = {m2,f1,8},
@@ -339,7 +339,7 @@ intergraph(Conf) when is_list(Conf) ->
E5 = {F4,F2},
E6 = {F5,F4},
E7 = {F4,F5},
-
+
E8 = {F6,F7},
E9 = {F7,F8},
E10 = {F8,F1}, % X
@@ -363,9 +363,9 @@ intergraph(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E1,1},{E2,2},{E3,3},{E5,5},{E6,6},{E7,7}],
XCallAt_m1 = [{E1,4}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
-
+
D6 = {F6,6},
D7 = {F7,7},
D8 = {F8,8},
@@ -380,7 +380,7 @@ intergraph(Conf) when is_list(Conf) ->
LCallAt_m2 = [{E8,8},{E9,9},{E11,11},{E12,12},{E13,13},{E14,14}],
XCallAt_m2 = [{E10,10},{E15,15}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
@@ -397,13 +397,13 @@ intergraph(Conf) when is_list(Conf) ->
?line {ok, _} = eval("EE | m2", [{F6,F1}], S),
?line {ok, _} = eval("EE | m2 + EE | m2", [{F6,F1}], S),
- ?line {ok, _} = eval("(Fun)(Lin)(E | m1)",
+ ?line {ok, _} = eval("(Fun)(Lin)(E | m1)",
to_external(union(set(XC_m1), set(LC_m1))), S),
- ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)",
- [{{D2,D1},[1,2,4]},{{D4,D2},[5]},{{D5,D4},[6]},{{D4,D5},[7]}],
+ ?line {ok, _} = eval("(XXL)(ELin) (EE | m1)",
+ [{{D2,D1},[1,2,4]},{{D4,D2},[5]},{{D5,D4},[6]},{{D4,D5},[7]}],
S),
?line {ok, _} = eval("(XXL)(ELin)(EE | m2)", [{{D6,D1},[8,11,12]}], S),
- ?line {ok, _} = eval("(XXL)(ELin)(ELin)(EE | m2)",
+ ?line {ok, _} = eval("(XXL)(ELin)(ELin)(EE | m2)",
[{{D6,D1},[8,11,12]}], S),
%% Combining graphs (equal or different):
@@ -420,15 +420,15 @@ intergraph(Conf) when is_list(Conf) ->
?line {ok, _} = eval("EE | m1 + E | m1", LC_m1, S),
?line {ok, _} = eval(f("EE | ~p + E | ~p", [F2, F2]), [E1,E2], S),
%% [1,4] from 'calls' is a subset of [1,2,4] from Inter Call Graph:
- ?line {ok, _} = eval(f("(XXL)(Lin) (E | ~p)", [F2]),
+ ?line {ok, _} = eval(f("(XXL)(Lin) (E | ~p)", [F2]),
[{{D2,D1},[1,4]},{{D2,D3},[2]}], S),
- ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F2]),
+ ?line {ok, _} = eval(f("(XXL)(ELin) (EE | ~p)", [F2]),
[{{D2,D1},[1,2,4]}], S),
?line {ok, _} = eval(f("(XXL)((ELin)(EE | ~p) + (Lin)(E | ~p))", [F2, F2]),
[{{D2,D1},[1,2,4]},{{D2,D3},[2]}], S),
- ?line {ok, _} =
- eval(f("(XXL)((ELin) ~p + (Lin) ~p)", [{F2, F1}, {F2, F1}]),
+ ?line {ok, _} =
+ eval(f("(XXL)((ELin) ~p + (Lin) ~p)", [{F2, F1}, {F2, F1}]),
[{{D2,D1},[1,2,4]}], S),
?line {ok, _} = eval(f("(Fun)(Lin) ~p", [{F2, F1}]), [E1], S),
%% The external call E4 is included in the reply:
@@ -438,7 +438,7 @@ intergraph(Conf) when is_list(Conf) ->
%% The local call E1 is included in the reply:
?line {ok, _} = eval("(XXL)(Lin)(XC | m1)", [{{D2,D1},[1,4]}], S),
- ?line {ok, _} = eval(f("(LLin) (E | ~p || ~p) + (XLin) (E | ~p || ~p)",
+ ?line {ok, _} = eval(f("(LLin) (E | ~p || ~p) + (XLin) (E | ~p || ~p)",
[F2, F1, F2, F1]), [{E4,[1,4]}], S),
?line {ok, _} = eval("# (ELin) E", 6, S),
@@ -449,7 +449,7 @@ lines(suite) -> [];
lines(doc) -> ["More test of Inter Call Graph, and regular expressions"];
lines(Conf) when is_list(Conf) ->
S0 = new(),
-
+
F1 = {m1,f1,1}, % X
F2 = {m1,f2,2},
F3 = {m1,f3,3},
@@ -464,14 +464,14 @@ lines(Conf) when is_list(Conf) ->
E5 = {F2,F4}, % X
E6 = {F5,F6},
E7 = {F6,F4}, % X
-
+
D1 = {F1,1},
D2 = {F2,2},
D3 = {F3,3},
D4 = {F4,4},
D5 = {F5,5},
D6 = {F6,6},
-
+
DefAt_m1 = [D1,D2,D3,D5,D6],
X_m1 = [F1,F5],
% L_m1 = [F2,F3,F6],
@@ -480,7 +480,7 @@ lines(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E1,1},{E3,3},{E6,6}],
XCallAt_m1 = [{E2,2},{E4,4},{E5,5},{E7,7}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
DefAt_m2 = [D4],
@@ -491,9 +491,9 @@ lines(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
-
+
AppInfo1 = #xref_app{name = a1, rel_name = [r1]},
?line S5 = add_application(S2, AppInfo1),
AppInfo2 = #xref_app{name = a2, rel_name = [r1]},
@@ -509,10 +509,10 @@ lines(Conf) when is_list(Conf) ->
{{D5,D4},[6]}], S),
?line {ok, _} = eval("(XXL)(Lin) (E | m1)",
[{{D1,D2},[1]},{{D1,D4},[4]},{{D2,D1},[2]},
- {{D2,D4},[5]},{{D3,D2},[3]},{{D5,D6},[6]},{{D6,D4},[7]}],
+ {{D2,D4},[5]},{{D3,D2},[3]},{{D5,D6},[6]},{{D6,D4},[7]}],
S),
?line {ok, _} = eval("(E | m1) + (EE | m1)",
- [E1,E2,E3,E4,E5,E6,E7,{F1,F1},{F3,F1},{F3,F4},{F5,F4}],
+ [E1,E2,E3,E4,E5,E6,E7,{F1,F1},{F3,F1},{F3,F4},{F5,F4}],
S),
?line {ok, _} = eval("(Lin)(E | m1)",
[{E4,[4]},{E1,[1]},{E2,[2]},{E5,[5]},
@@ -567,7 +567,7 @@ lines(Conf) when is_list(Conf) ->
loops(suite) -> [];
loops(doc) -> ["More Inter Call Graph, loops and \"unusual\" cases"];
loops(Conf) when is_list(Conf) ->
- S0 = new(),
+ S0 = new(),
F1 = {m1,f1,1}, % X
F2 = {m1,f2,2},
@@ -582,7 +582,7 @@ loops(Conf) when is_list(Conf) ->
E3 = {F3,F4},
E4 = {F4,F5},
E5 = {F5,F3}, % X
-
+
D1 = {F1,1},
D2 = {F2,2},
D3 = {F3,3},
@@ -598,7 +598,7 @@ loops(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E2,2},{E3,3},{E4,4}],
XCallAt_m1 = [{E1,1},{E5,5}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
?line S = set_up(S1),
@@ -659,16 +659,16 @@ modules(Conf) when is_list(Conf) ->
?line {ok, y} = compile:file(Y, [debug_info, {outdir,EB1_1}]),
?line {ok, S0} = xref_base:new([{xref_mode, modules}]),
- ?line {ok, release2, S1} =
+ ?line {ok, release2, S1} =
xref_base:add_release(S0, Dir, [{name,release2}]),
?line S = set_up(S1),
?line {{error, _, {unavailable_analysis, undefined_function_calls}}, _} =
xref_base:analyze(S, undefined_function_calls),
- ?line {{error, _, {unavailable_analysis, locals_not_used}}, _} =
+ ?line {{error, _, {unavailable_analysis, locals_not_used}}, _} =
xref_base:analyze(S, locals_not_used),
- ?line {{error, _, {unavailable_analysis, {call, foo}}}, _} =
+ ?line {{error, _, {unavailable_analysis, {call, foo}}}, _} =
xref_base:analyze(S, {call, foo}),
- ?line {{error, _, {unavailable_analysis, {use, foo}}}, _} =
+ ?line {{error, _, {unavailable_analysis, {use, foo}}}, _} =
xref_base:analyze(S, {use, foo}),
?line analyze(undefined_functions, [{x,undef,0}], S),
?line 5 = length(xref_base:info(S)),
@@ -681,7 +681,7 @@ modules(Conf) when is_list(Conf) ->
ok.
files(suite) ->
- [add, default, info, lib, read, read2, remove, replace, update,
+ [add, default, info, lib, read, read2, remove, replace, update,
deprecated, trycatch, abstract_modules, fun_mfa, qlc].
add(suite) -> [];
@@ -708,7 +708,7 @@ add(Conf) when is_list(Conf) ->
{unix, _} ->
?line make_udir(UDir),
?line make_ufile(UFile);
- _ ->
+ _ ->
true
end,
@@ -743,20 +743,20 @@ add(Conf) when is_list(Conf) ->
xref_base:add_release(S, foo, [{builtins,not_a_value}]),
?line {error, _, {invalid_filename,{foo,bar}}} =
xref_base:add_release(S, {foo,bar}, []),
- ?line {ok, S1} =
+ ?line {ok, S1} =
xref_base:set_default(S, [{verbose,false}, {warnings, false}]),
?line case os:type() of
{unix, _} ->
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref_base:add_release(S, UDir);
_ ->
true
end,
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref_base:add_release(S, fname(["/a/b/c/d/e/f","__foo"])),
- ?line {ok, release2, S2} =
+ ?line {ok, release2, S2} =
xref_base:add_release(S1, Dir, [{name,release2}]),
- ?line {error, _, {module_clash, {x, _, _}}} =
+ ?line {error, _, {module_clash, {x, _, _}}} =
xref_base:add_module(S2, Xbeam),
?line {ok, S3} = xref_base:remove_release(S2, release2),
?line {ok, rel2, S4} = xref_base:add_release(S3, Dir),
@@ -764,11 +764,11 @@ add(Conf) when is_list(Conf) ->
xref_base:add_release(S4, Dir),
?line {ok, S5} = xref_base:remove_release(S4, rel2),
%% One unreadable file and one JAM file found (no verification here):
- ?line {ok, [], S6} = xref_base:add_directory(S5, fname(CopyDir,"dir"),
+ ?line {ok, [], S6} = xref_base:add_directory(S5, fname(CopyDir,"dir"),
[{recurse,true}, {warnings,true}]),
?line case os:type() of
{unix, _} ->
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref_base:add_directory(S6, UDir);
_ ->
true
@@ -803,7 +803,7 @@ default(Conf) when is_list(Conf) ->
xref_base:set_default(S, [not_an_option]),
?line D = xref_base:get_default(S),
- ?line [{builtins,false},{recurse,false},{verbose,false},{warnings,true}] =
+ ?line [{builtins,false},{recurse,false},{verbose,false},{warnings,true}] =
D,
?line ok = xref_base:delete(S),
@@ -831,7 +831,7 @@ info(Conf) when is_list(Conf) ->
?line {error, _, {no_such_info, release}} = xref:info(s, release),
?line {error, _, {no_such_info, release}} = xref:info(s, release, rel),
?line {error, _, {no_such_module, mod}} = xref:info(s, modules, mod),
- ?line {error, _, {no_such_application, app}} =
+ ?line {error, _, {no_such_application, app}} =
xref:info(s, applications, app),
?line {error, _, {no_such_release, rel}} = xref:info(s, releases, rel),
?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
@@ -845,9 +845,9 @@ info(Conf) when is_list(Conf) ->
?line [{rel2,_}] = xref:info(s, releases, rel2),
?line {error, _, {no_such_library, foo}} = xref:info(s, libraries, [foo]),
- ?line {ok, lib1} =
+ ?line {ok, lib1} =
compile:file(fname(LDir,lib1),[debug_info,{outdir,LDir}]),
- ?line {ok, lib2} =
+ ?line {ok, lib2} =
compile:file(fname(LDir,lib2),[debug_info,{outdir,LDir}]),
?line ok = xref:set_library_path(s, [LDir], [{verbose,false}]),
?line [{lib1,_}, {lib2, _}] = xref:info(s, libraries),
@@ -883,13 +883,13 @@ lib(Conf) when is_list(Conf) ->
xref:set_library_path(s, ["foo"], [not_an_option]),
?line {error, _, {invalid_path,otp}} = xref:set_library_path(s,otp),
?line {error, _, {invalid_path,[""]}} = xref:set_library_path(s,[""]),
- ?line {error, _, {invalid_path,[[$a | $b]]}} =
+ ?line {error, _, {invalid_path,[[$a | $b]]}} =
xref:set_library_path(s,[[$a | $b]]),
?line {error, _, {invalid_path,[otp]}} = xref:set_library_path(s,[otp]),
?line {ok, []} = xref:get_library_path(s),
?line ok = xref:set_library_path(s, [Dir], [{verbose,false}]),
?line {ok, UnknownFunctions} = xref:q(s, "U"),
- ?line [{lib1,unknown,0}, {lib2,local,0},
+ ?line [{lib1,unknown,0}, {lib2,local,0},
{lib2,unknown,0}, {unknown,unknown,0}]
= UnknownFunctions,
?line {ok, [{lib2,f,0},{lib3,f,0}]} = xref:q(s, "DF"),
@@ -934,7 +934,7 @@ lib(Conf) when is_list(Conf) ->
?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
?line {ok, cp} = xref:add_module(s, fname(Dir,"cp.beam")),
?line {ok, [{lists, sort, 1}]} = xref:q(s, "U"),
- ?line ok = xref:set_library_path(s, code_path),
+ ?line ok = xref:set_library_path(s, code_path),
?line {ok, []} = xref:q(s, "U"),
?line check_state(s),
?line xref:stop(s),
@@ -1010,18 +1010,18 @@ do_read(File, Version) ->
?line {ok, CallsB} = xref:q(s, "(Lin) (E - UC) "),
?line ok = check_state(s),
?line {ok, XU} = xref:q(s, "XU"),
- ?line Erl = set([{erlang,length,1},{erlang,integer,1},
+ ?line Erl = set([{erlang,length,1},{erlang,integer,1},
{erlang,binary_to_term,1}]),
- ?line [{erlang,binary_to_term,1},{erlang,length,1}] =
+ ?line [{erlang,binary_to_term,1},{erlang,length,1}] =
to_external(intersection(set(XU), Erl)),
- ?line xref:stop(s).
+ ?line xref:stop(s).
%% What is expected when xref_SUITE_data/read/read.erl is added:
read_expected(Version) ->
%% Line positions in xref_SUITE_data/read/read.erl:
- POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10,
- POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8,
- POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10,
+ POS1 = 28, POS2 = POS1+10, POS3 = POS2+6, POS4 = POS3+6, POS5 = POS4+10,
+ POS6 = POS5+5, POS7 = POS6+6, POS8 = POS7+6, POS9 = POS8+8,
+ POS10 = POS9+10, POS11 = POS10+7, POS12 = POS11+8, POS13 = POS12+10,
POS14 = POS13+18, % POS15 = POS14+23,
FF = {read,funfuns,0},
@@ -1162,7 +1162,7 @@ read_expected(Version) ->
{POS14+17,{{read,bi,0},{read,bi,0}}}],
OK = case Version of
- abstract_v1 ->
+ abstract_v1 ->
[{POS8+3, {FF,{erlang,apply,3}}},
{POS10+1, {FF,{erlang,apply,3}}},
{POS10+6, {FF,{erlang,apply,3}}}]
@@ -1170,7 +1170,7 @@ read_expected(Version) ->
[{0,{FF,{read,'$F_EXPR',178}}},
{0,{FF,{modul,'$F_EXPR',179}}}]
++ O1;
- _ ->
+ _ ->
% [{POS15+2,{{read,bi,0},{foo,t,0}}},
% {POS15+3,{{read,bi,0},{bar,t,0}}},
% {POS15+6,{{read,bi,0},{read,local,0}}},
@@ -1183,18 +1183,34 @@ read_expected(Version) ->
end,
%% When builtins =:= true:
- OKB = [{POS13+1,{FF,{erts_debug,apply,4}}},
- {POS13+2,{FF,{erts_debug,apply,4}}},
- {POS13+3,{FF,{erts_debug,apply,4}}},
- {POS1+3, {FF,{erlang,binary_to_term,1}}},
- {POS3+1, {FF,{erlang,spawn,3}}},
- {POS3+2, {FF,{erlang,spawn,3}}},
- {POS3+3, {FF,{erlang,spawn_link,3}}},
- {POS3+4, {FF,{erlang,spawn_link,3}}},
- {POS6+4, {FF,{erlang,spawn,3}}},
- {POS13+5, {{read,bi,0},{erlang,length,1}}},
- {POS14+3, {{read,bi,0},{erlang,length,1}}}]
- ++ OK,
+ OKB1 = [{POS13+1,{FF,{erts_debug,apply,4}}},
+ {POS13+2,{FF,{erts_debug,apply,4}}},
+ {POS13+3,{FF,{erts_debug,apply,4}}},
+ {POS1+3, {FF,{erlang,binary_to_term,1}}},
+ {POS3+1, {FF,{erlang,spawn,3}}},
+ {POS3+2, {FF,{erlang,spawn,3}}},
+ {POS3+3, {FF,{erlang,spawn_link,3}}},
+ {POS3+4, {FF,{erlang,spawn_link,3}}},
+ {POS6+4, {FF,{erlang,spawn,3}}},
+ {POS13+5, {{read,bi,0},{erlang,length,1}}},
+ {POS14+3, {{read,bi,0},{erlang,length,1}}}],
+
+ %% Operators (OTP-8647):
+ OKB = case Version of
+ abstract_v1 ->
+ [];
+ _ ->
+ [{POS13+16, {{read,bi,0},{erlang,'!',2}}},
+ {POS13+16, {{read,bi,0},{erlang,'-',1}}},
+ {POS13+16, {{read,bi,0},{erlang,self,0}}}]
+ end
+ ++ [{POS14+19, {{read,bi,0},{erlang,'+',2}}},
+ {POS14+21, {{read,bi,0},{erlang,'+',2}}},
+ {POS13+16, {{read,bi,0},{erlang,'==',2}}},
+ {POS14+15, {{read,bi,0},{erlang,'==',2}}},
+ {POS13+5, {{read,bi,0},{erlang,'>',2}}},
+ {POS14+3, {{read,bi,0},{erlang,'>',2}}}]
+ ++ OKB1 ++ OK,
{U, OK, OKB}.
@@ -1217,9 +1233,9 @@ read2(Conf) when is_list(Conf) ->
spawn_opt(fun() -> foo end, [link]),
spawn_opt(f(),
{read2,f}, [{min_heap_size,1000}]),
- spawn_opt(f(),
+ spawn_opt(f(),
fun() -> f() end, [flopp]),
- spawn_opt(f(),
+ spawn_opt(f(),
read2, f, [], []);
f() ->
%% Duplicated unresolved calls are ignored:
@@ -1237,7 +1253,7 @@ read2(Conf) when is_list(Conf) ->
?line {ok, U2} = xref:q(s, "(Lin) UC"),
?line {ok, OK2} = xref:q(s, "(Lin) (E - UC)"),
?line true = U =:= U2,
- ?line true = OK =:= OK2,
+ ?line true = OK =:= OK2,
?line ok = check_state(s),
?line xref:stop(s),
@@ -1304,7 +1320,7 @@ replace(Conf) when is_list(Conf) ->
?line {ok, true} = xref:set_default(s, warnings, false),
?line {ok, rel2} = xref:add_release(s, Dir, []),
?line {error, _, _} = xref:replace_application(s, app1, "no_data"),
- ?line {error, _, {no_such_application, app12}} =
+ ?line {error, _, {no_such_application, app12}} =
xref:replace_application(s, app12, A1_0, []),
?line {error, _, {invalid_filename,{foo,bar}}} =
xref:replace_application(s, app1, {foo,bar}, []),
@@ -1312,7 +1328,7 @@ replace(Conf) when is_list(Conf) ->
xref:replace_application(s, foo, bar, [not_an_option]),
?line {error, _, {invalid_options,[{builtins,not_a_value}]}} =
xref:replace_application(s, foo, bar, [{builtins,not_a_value}]),
- ?line {ok, app1} =
+ ?line {ok, app1} =
xref:replace_application(s, app1, A1_0),
?line [{_, AppInfo}] = xref:info(s, applications, app1),
?line {value, {release, [rel2]}} = keysearch(release, 1, AppInfo),
@@ -1332,14 +1348,14 @@ replace(Conf) when is_list(Conf) ->
?line {ok, x} = compile:file(X, [no_debug_info, {outdir,EB1_1}]),
?line {error, _, {no_debug_info, _}} = xref:replace_module(s, x, Xbeam),
- ?line {error, _, {module_mismatch, x,y}} =
+ ?line {error, _, {module_mismatch, x,y}} =
xref:replace_module(s, x, Ybeam),
?line case os:type() of
{unix, _} ->
?line hide_file(Ybeam),
- ?line {error, _, {file_error, _, _}} =
+ ?line {error, _, {file_error, _, _}} =
xref:replace_module(s, x, Ybeam);
- _ ->
+ _ ->
true
end,
?line ok = xref:remove_module(s, x),
@@ -1362,16 +1378,16 @@ update(Conf) when is_list(Conf) ->
Source = fname(Dir, "x.erl"),
Beam = fname(Dir, "x.beam"),
?line copy_file(fname(Dir, "x.erl.1"), Source),
- ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
-
+ ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
+
?line {ok, _} = start(s),
- ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
?line {ok, [x]} = xref:add_directory(s, Dir, [{builtins,true}]),
?line {error, _, {invalid_options,[not_an_option]}} =
xref:update(s, [not_an_option]),
?line {ok, []} = xref:update(s),
?line {ok, [{erlang,atom_to_list,1}]} = xref:q(s, "XU"),
-
+
?line [{x, ModInfo}] = xref:info(s, modules, x),
?line case keysearch(directory, 1, ModInfo) of
{value, {directory, Dir}} -> ok
@@ -1379,7 +1395,7 @@ update(Conf) when is_list(Conf) ->
timer:sleep(2000), % make sure modification time has changed
?line copy_file(fname(Dir, "x.erl.2"), Source),
- ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
+ ?line {ok, x} = compile:file(Source, [debug_info, {outdir,Dir}]),
?line {ok, [x]} = xref:update(s, []),
?line {ok, [{erlang,list_to_atom,1}]} = xref:q(s, "XU"),
@@ -1454,11 +1470,11 @@ deprecated(Conf) when is_list(Conf) ->
DF = usort(DF_3++[{{M9,t,0},{M9,f,1}}]),
?line {ok,DF} = xref:analyze(s, deprecated_function_calls),
- ?line {ok,DF_1} =
+ ?line {ok,DF_1} =
xref:analyze(s, {deprecated_function_calls,next_version}),
- ?line {ok,DF_2} =
+ ?line {ok,DF_2} =
xref:analyze(s, {deprecated_function_calls,next_major_release}),
- ?line {ok,DF_3} =
+ ?line {ok,DF_3} =
xref:analyze(s, {deprecated_function_calls,eventually}),
D = to_external(range(from_term(DF))),
@@ -1467,11 +1483,11 @@ deprecated(Conf) when is_list(Conf) ->
D_3 = to_external(range(from_term(DF_3))),
?line {ok,D} = xref:analyze(s, deprecated_functions),
- ?line {ok,D_1} =
+ ?line {ok,D_1} =
xref:analyze(s, {deprecated_functions,next_version}),
- ?line {ok,D_2} =
+ ?line {ok,D_2} =
xref:analyze(s, {deprecated_functions,next_major_release}),
- ?line {ok,D_3} =
+ ?line {ok,D_3} =
xref:analyze(s, {deprecated_functions,eventually}),
?line ok = check_state(s),
@@ -1516,11 +1532,11 @@ deprecated(Conf) when is_list(Conf) ->
DFa = DFa_3,
?line {ok,DFa} = xref:analyze(s, deprecated_function_calls),
- ?line {ok,DFa_1} =
+ ?line {ok,DFa_1} =
xref:analyze(s, {deprecated_function_calls,next_version}),
- ?line {ok,DFa_2} =
+ ?line {ok,DFa_2} =
xref:analyze(s, {deprecated_function_calls,next_major_release}),
- ?line {ok,DFa_3} =
+ ?line {ok,DFa_3} =
xref:analyze(s, {deprecated_function_calls,eventually}),
?line ok = check_state(s),
@@ -1564,11 +1580,11 @@ deprecated(Conf) when is_list(Conf) ->
DFb = usort(DFb_2++[{{M,bar,2},{M,t,0}},{{M,g,3},{M,bar,2}}]),
?line {ok,DFb} = xref:analyze(s, deprecated_function_calls),
- ?line {ok,DFb_1} =
+ ?line {ok,DFb_1} =
xref:analyze(s, {deprecated_function_calls,next_version}),
- ?line {ok,DFb_2} =
+ ?line {ok,DFb_2} =
xref:analyze(s, {deprecated_function_calls,next_major_release}),
- ?line {ok,DFb_3} =
+ ?line {ok,DFb_3} =
xref:analyze(s, {deprecated_function_calls,eventually}),
?line ok = check_state(s),
@@ -1599,7 +1615,7 @@ trycatch(Conf) when is_list(Conf) ->
catch
error:a -> err:e1();
error:b -> err:e2()
- after
+ after
fini:shed()
end.
">>,
@@ -1616,7 +1632,7 @@ trycatch(Conf) when is_list(Conf) ->
{{{A,A,0},{err,e2,0}},[13]},
{{{A,A,0},{fini,shed,0}},[15]},
{{{A,A,0},{foo,bar,0}},[7]},
- {{{A,A,0},{foo,foo,0}},[9]}]} =
+ {{{A,A,0},{foo,foo,0}},[9]}]} =
xref:q(s, "(Lin) (E | trycatch:trycatch/0)"),
?line ok = check_state(s),
@@ -1662,7 +1678,7 @@ abstract_modules(Conf) when is_list(Conf) ->
{{{A,args,1},{A,local,1}},[6]},
{{{A,args,1},{A,new,2}},[8]},
{{{A,local,1},{A,module_info,1}},[12]},
- {{{param,new,2},{param,instance,2}},[0]}]} =
+ {{{param,new,2},{param,instance,2}},[0]}]} =
xref:q(s, "(Lin) E"),
?line {ok,[{param,args,1},
{param,instance,2},
@@ -1747,10 +1763,10 @@ qlc(Conf) when is_list(Conf) ->
t() ->
dets:open_file(t, []),
dets:insert(t, [{1,a},{2,b},{3,c},{4,d}]),
- MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y}
+ MS = ets:fun2ms(fun({X,Y}) when (X > 1) or (X < 5) -> {Y}
end),
QH1 = dets:table(t, [{traverse, {select, MS}}]),
- QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t),
+ QH2 = qlc:q([{Y} || {X,Y} <- dets:table(t),
(X > 1) or (X < 5)]),
true = qlc:info(QH1) =:= qlc:info(QH2),
dets:close(t),
@@ -1783,7 +1799,7 @@ analyze(Conf) when is_list(Conf) ->
xref_base:analyze(S0, undefined_function_calls, [not_an_option]),
?line {{error, _, {invalid_query,{q}}}, _} = xref_base:q(S0,{q}),
?line {{error, _, {unknown_analysis,foo}}, _} = xref_base:analyze(S0, foo),
- ?line {{error, _, {unknown_constant,"foo:bar/-1"}}, _} =
+ ?line {{error, _, {unknown_constant,"foo:bar/-1"}}, _} =
xref_base:analyze(S0, {use,{foo,bar,-1}}),
CopyDir = ?copydir,
@@ -1803,30 +1819,30 @@ analyze(Conf) when is_list(Conf) ->
?line {ok, rel2, S1} = xref_base:add_release(S0, Dir, [{verbose,false}]),
?line S = set_up(S1),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze(undefined_function_calls, [{{x,xx,0},{x,undef,0}}], S),
?line {ok, _} = analyze(undefined_functions, [{x,undef,0}], S),
?line {ok, _} = analyze(locals_not_used, [{x,l,0},{x,l1,0}], S),
?line {ok, _} = analyze(exports_not_used, [{x,xx,0},{y,t,0}], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze(deprecated_function_calls, [{{y,t,0},{x,t,0}}], S),
?line {ok, _} = analyze({deprecated_function_calls,next_version}, [], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({deprecated_function_calls,next_major_release}, [], S),
- ?line {ok, _} = analyze({deprecated_function_calls,eventually},
+ ?line {ok, _} = analyze({deprecated_function_calls,eventually},
[{{y,t,0},{x,t,0}}], S),
?line {ok, _} = analyze(deprecated_functions, [{x,t,0}], S),
?line {ok, _} = analyze({deprecated_functions,next_version}, [], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({deprecated_functions,next_major_release}, [], S),
?line {ok, _} = analyze({deprecated_functions,eventually}, [{x,t,0}], S),
?line {ok, _} = analyze({call, {x,xx,0}}, [{x,undef,0}], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({call, [{x,xx,0},{x,l,0}]}, [{x,l1,0},{x,undef,0}], S),
?line {ok, _} = analyze({use, {x,l,0}}, [{x,l1,0}], S),
- ?line {ok, _} =
+ ?line {ok, _} =
analyze({use, [{x,l,0},{x,l1,0}]}, [{x,l,0},{x,l1,0}], S),
?line {ok, _} = analyze({module_call, x}, [x], S),
@@ -1881,7 +1897,7 @@ basic(Conf) when is_list(Conf) ->
LCallAt_m1 = [{E7,12}],
XCallAt_m1 = [{E1,13},{E2,17},{E4,7}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -1895,7 +1911,7 @@ basic(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E3,96},{E6,12},{UE1,77}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
D4 = {F4,6},
@@ -1908,7 +1924,7 @@ basic(Conf) when is_list(Conf) ->
LCallAt_m3 = [{E5,19}],
XCallAt_m3 = [{UE2,22}],
Info3 = #xref_mod{name = m3, app_name = [a3]},
- ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
+ ?line S3 = add_module(S2, Info3, DefAt_m3, X_m3, LCallAt_m3, XCallAt_m3,
XC_m3, LC_m3),
Info4 = #xref_mod{name = m4, app_name = [a2]},
@@ -1955,7 +1971,7 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval(f("(Mod) ~p", [[F1,F6,F5]]), [m1,m3], S),
?line {ok, _} = eval("(Lin) M - (Lin) m1",
[{F2,7},{F3,9},{F7,19},{F4,6},{F5,97},{UF2,0}], S),
- ?line {ok, _} = eval(f("(Lin) M * (Lin) ~p", [[F1,F6]]),
+ ?line {ok, _} = eval(f("(Lin) M * (Lin) ~p", [[F1,F6]]),
[{F1,12},{F6,3}], S),
?line {ok, _} = eval(f("X * ~p", [[F1, F2, F3, F4, F5]]), [F3, F4], S),
@@ -1976,7 +1992,7 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
[{{D1,D3},[13]},{{D1,D4},[7]}],S),
?line {ok, _} = eval(f("XC | (~p + ~p)", [F1, F2]), [E1,E4,E3,UE1], S),
- ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
+ ?line {ok, _} = eval(f("(XXL) (Lin) (XC | ~p)", [F1]),
[{{D1,D3},[13]},{{D1,D4},[7]}], S),
?line {ok, _} = eval("LC | m3", [E5], S),
?line {ok, _} = eval(f("LC | ~p", [F1]), [E7], S),
@@ -1984,7 +2000,7 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval("E | m1", [E1,E2,E4,E7], S),
?line {ok, _} = eval(f("E | ~p", [F1]), [E1,E7,E4], S),
?line {ok, _} = eval(f("E | (~p + ~p)", [F1, F2]), [E1,E7,E4,E3,UE1], S),
-
+
?line {ok, _} = eval("XC || m1", [E3,UE2], S),
?line {ok, _} = eval(f("XC || ~p", [F6]), [E3], S),
?line {ok, _} = eval(f("XC || (~p + ~p)", [F4, UF2]), [UE1,E4,E6], S),
@@ -2012,18 +2028,18 @@ basic(Conf) when is_list(Conf) ->
?line {ok, _} = eval("components V", type_error, S),
?line {ok, _} = eval("components E + components E", type_error, S),
- ?line {ok, _} = eval(f("range (closure E | ~p)", [[F1,F2]]),
+ ?line {ok, _} = eval(f("range (closure E | ~p)", [[F1,F2]]),
[F6,F3,F7,F4,F5,UF1,UF2], S),
- ?line {ok, _} =
+ ?line {ok, _} =
eval(f("domain (closure E || ~p)", [[UF2,F7]]), [F1,F2,F6], S),
?line {ok, _} = eval("components E", [], S),
?line {ok, _} = eval("components (Mod) E", [[m1,m2,m3]], S),
?line {ok, _} = eval("components closure (Mod) E", [[m1,m2,m3]], S),
- ?line {ok, _} = eval("condensation (Mod) E",
+ ?line {ok, _} = eval("condensation (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
- ?line {ok, _} = eval("condensation closure (Mod) E",
+ ?line {ok, _} = eval("condensation closure (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
- ?line {ok, _} = eval("condensation closure closure closure (Mod) E",
+ ?line {ok, _} = eval("condensation closure closure closure (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]}], S),
?line {ok, _} = eval("weak condensation (Mod) E",
[{[m1,m2,m3],[m1,m2,m3]},{[m1,m2,m3],[m17]},{[m17],[m17]}], S),
@@ -2035,11 +2051,11 @@ basic(Conf) when is_list(Conf) ->
[[m1,m2,m3]], S),
%% |, ||, |||
- ?line {ok, _} = eval("(Lin) E || V", type_error, S),
- ?line {ok, _} = eval("E ||| (Lin) V", type_error, S),
+ ?line {ok, _} = eval("(Lin) E || V", type_error, S),
+ ?line {ok, _} = eval("E ||| (Lin) V", type_error, S),
?line {ok, _} = eval("E ||| m1", [E7], S),
?line {ok, _} = eval("closure E ||| m1", [E7,{F1,UF1},{F6,UF1}], S),
- ?line {ok, _} = eval("closure E ||| [m1,m2]",
+ ?line {ok, _} = eval("closure E ||| [m1,m2]",
[{F1,UF1},{F2,F7},{F1,F7},{F6,UF1},{F2,UF1},{F7,UF1},E7,E1,E2,E3], S),
?line {ok, _} = eval("AE | a1", [{a1,a1},{a1,a2},{a1,a3}], S),
@@ -2095,7 +2111,7 @@ md(Conf) when is_list(Conf) ->
Y = fname(Dir, "y__y.erl"),
Xbeam = fname(Dir, "x__x.beam"),
Ybeam = fname(Dir, "y__y.beam"),
-
+
?line {error, _, {invalid_filename,{foo,bar}}} = xref:m({foo,bar}),
?line {error, _, {invalid_filename,{foo,bar}}} = xref:d({foo,bar}),
@@ -2171,7 +2187,7 @@ variables(Conf) when is_list(Conf) ->
LCallAt_m1 = [],
XCallAt_m1 = [{E1,13},{E3,17}],
Info1 = #xref_mod{name = m1, app_name = [a1]},
- ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
+ ?line S1 = add_module(S0, Info1, DefAt_m1, X_m1, LCallAt_m1, XCallAt_m1,
XC_m1, LC_m1),
D2 = {F2,7},
@@ -2183,11 +2199,11 @@ variables(Conf) when is_list(Conf) ->
LCallAt_m2 = [],
XCallAt_m2 = [{E2,96}],
Info2 = #xref_mod{name = m2, app_name = [a2]},
- ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
+ ?line S2 = add_module(S1, Info2, DefAt_m2, X_m2, LCallAt_m2, XCallAt_m2,
XC_m2, LC_m2),
?line S = set_up(S2),
-
+
?line eval("T1=E, T2=E*T1, T3 = T2*T2, T4=range T3, T5=T3|T4, T5",
[E1,E2,E3], S),
?line eval("((E*E)*(E*E)) | (range ((E*E)*(E*E)))",
@@ -2202,16 +2218,16 @@ variables(Conf) when is_list(Conf) ->
?line {ok, S102} = eval("T2 := E | m2", [E2], S101),
?line {{ok, [{user, ['T0', 'T1', 'T2']}]}, _} = xref_base:variables(S102),
?line {ok, S103} = xref_base:forget(S102, 'T0'),
- ?line {{ok, [{user, ['T1', 'T2']}]}, S104} =
+ ?line {{ok, [{user, ['T1', 'T2']}]}, S104} =
xref_base:variables(S103, [user]),
?line {ok, S105} = xref_base:forget(S104),
?line {{ok, [{user, []}]}, S106} = xref_base:variables(S105),
- ?line {{ok, [{predefined,_}]}, S107_0} =
+ ?line {{ok, [{predefined,_}]}, S107_0} =
xref_base:variables(S106, [predefined]),
- ?line {ok, S107_1} =
+ ?line {ok, S107_1} =
eval("TT := E, TT2 := V, TT1 := TT * TT", [E1,E2,E3], S107_0),
- ?line {{ok, [{user, ['TT', 'TT1', 'TT2']}]}, _} =
+ ?line {{ok, [{user, ['TT', 'TT1', 'TT2']}]}, _} =
xref_base:variables(S107_1),
?line {ok, S107} = xref_base:forget(S107_1),
@@ -2220,14 +2236,14 @@ variables(Conf) when is_list(Conf) ->
Beam = fname(Dir, "lib1.beam"),
?line copy_file(fname(Dir, "lib1.erl"), Beam),
- ?line {ok, S108} =
+ ?line {ok, S108} =
xref_base:set_library_path(S107, [Dir], [{verbose,false}]),
?line {{error, _, _}, _} = xref_base:variables(S108, [{verbose,false}]),
?line {ok, S109} = xref_base:set_library_path(S108, [], [{verbose,false}]),
?line Tabs = length(ets:all()),
- ?line {ok, S110} = eval("Eplus := closure E, TT := Eplus",
+ ?line {ok, S110} = eval("Eplus := closure E, TT := Eplus",
'closure()', S109),
?line {{ok, [{user, ['Eplus','TT']}]}, S111} = xref_base:variables(S110),
?line {ok, S112} = xref_base:forget(S111, ['TT','Eplus']),
@@ -2289,7 +2305,7 @@ unused_locals(Conf) when is_list(Conf) ->
?line {ok, []} = xref:analyse(s, locals_not_used),
?line ok = check_state(s),
?line xref:stop(s),
-
+
?line ok = file:delete(File1),
?line ok = file:delete(Beam1),
?line ok = file:delete(File2),
@@ -2303,7 +2319,7 @@ format_error(suite) -> [];
format_error(doc) -> ["Format error messages"];
format_error(Conf) when is_list(Conf) ->
?line {ok, _Pid} = start(s),
- ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
+ ?line ok = xref:set_default(s, [{verbose,false}, {warnings, false}]),
%% Parse error messages.
?line "Invalid regular expression \"add(\"" ++ _ =
@@ -2332,7 +2348,7 @@ format_error(Conf) when is_list(Conf) ->
%% Other messages
?line 'Variable \'QQ\' used before set\n' =
fatom(xref:q(s,"QQ")),
- ?line 'Unknown constant a\n' =
+ ?line 'Unknown constant a\n' =
fatom(xref:q(s,"{a} of E")),
%% Testing xref_parser:t2s/1.
@@ -2341,12 +2357,12 @@ format_error(Conf) when is_list(Conf) ->
?line 'Variable assigned more than once: E = E + E\n' =
fatom(xref:q(s,"E=E + E")),
?line "Operator applied to argument(s) of different or invalid type(s): "
- "E + V * V\n" =
+ "E + V * V\n" =
flatten(xref:format_error(xref:q(s,"E + (V * V)"))),
?line {error,xref_compiler,{type_error,"(V + V) * E"}} =
xref:q(s,"(V + V) * E"),
?line "Type does not match structure of constant: [m:f/3 -> g:h/17] : "
- "App\n" =
+ "App\n" =
flatten(xref:format_error(xref:q(s,"[{{m,f,3},{g,h,17}}] : App"))),
?line 'Type does not match structure of constant: [m -> f, g -> h] : Fun\n'
= fatom(xref:q(s,"[{m,f},g->h] : Fun")),
@@ -2360,11 +2376,11 @@ format_error(Conf) when is_list(Conf) ->
xref:q(s,"condensation (# E + # V)"),
?line {error,xref_compiler,{type_error,"range (# E + # E)"}} =
xref:q(s,"range (#E + #E)"),
- ?line {error,xref_compiler,{type_error,"range (# E)"}} =
+ ?line {error,xref_compiler,{type_error,"range (# E)"}} =
xref:q(s,"range #E"), % Hm...
?line {error,xref_compiler,{type_error,"E + # E"}} =
xref:q(s,"E + #E + #E"), % Hm...
- ?line {error,xref_compiler,{type_error,"V * E || V | V"}} =
+ ?line {error,xref_compiler,{type_error,"V * E || V | V"}} =
xref:q(s,"V * (E || V) | V"),
?line {error,xref_compiler,{type_error,"E || (E | V)"}} =
xref:q(s,"V * E || (E | V)"),
@@ -2421,7 +2437,7 @@ eval(Query, E, S) ->
?format("------------------------------~n", []),
?format("Evaluating ~p~n", [Query]),
?line {Answer, NewState} = xref_base:q(S, Query, [{verbose, false}]),
- {Reply, Expected} =
+ {Reply, Expected} =
case Answer of
{ok, R} when is_list(E) ->
{unsetify(R), sort(E)};
@@ -2430,7 +2446,7 @@ eval(Query, E, S) ->
{error, _Module, Reason} ->
{element(1, Reason), E}
end,
- if
+ if
Reply =:= Expected ->
?format("As expected, got ~n~p~n", [Expected]),
{ok, NewState};
@@ -2442,7 +2458,7 @@ eval(Query, E, S) ->
analyze(Query, E, S) ->
?format("------------------------------~n", []),
?format("Evaluating ~p~n", [Query]),
- ?line {{ok, L}, NewState} =
+ ?line {{ok, L}, NewState} =
xref_base:analyze(S, Query, [{verbose, false}]),
case {unsetify(L), sort(E)} of
{X,X} ->
@@ -2461,7 +2477,7 @@ unsetify(S) ->
%% Note: assumes S has been set up; the new state is not returned
eval(Query, S) ->
- ?line {{ok, Answer}, _NewState} =
+ ?line {{ok, Answer}, _NewState} =
xref_base:q(S, Query, [{verbose, false}]),
unsetify(Answer).
@@ -2514,7 +2530,7 @@ check_state(S) ->
functions_mode_check(S, Info)
end.
-%% The manual mentions some facts that should always hold.
+%% The manual mentions some facts that should always hold.
%% Here they are again.
functions_mode_check(S, Info) ->
%% F = L + X,
@@ -2526,7 +2542,7 @@ functions_mode_check(S, Info) ->
?line {ok, V} = xref:q(S, "X + L + B + U"),
%% X, L, B and U are disjoint.
- ?line {ok, []} =
+ ?line {ok, []} =
xref:q(S, "X * L + X * B + X * U + L * B + L * U + B * U"),
%% V = UU + XU + LU,
@@ -2577,11 +2593,11 @@ functions_mode_check(S, Info) ->
?line {Local, Exported} = info(Info, no_functions),
?line LX = Local+Exported,
- ?line {ok, LXs} = xref:q(S, 'Extra = _:module_info/"(0|1)" + LM,
+ ?line {ok, LXs} = xref:q(S, 'Extra = _:module_info/"(0|1)" + LM,
# (F - Extra)'),
?line true = LX =:= LXs,
- ?line {LocalCalls, ExternalCalls, UnresCalls} =
+ ?line {LocalCalls, ExternalCalls, UnresCalls} =
info(Info, no_function_calls),
?line LEU = LocalCalls + ExternalCalls + UnresCalls,
?line {ok, LEU} = xref:q(S, "# LC + # XC"),
@@ -2635,7 +2651,7 @@ check_count(S) ->
%% {ok, A} = xref:q(S, 'A'),
{ok, M} = xref:q(S, 'AM'),
- {ok, _} = xref:q(S,
+ {ok, _} = xref:q(S,
"Extra := _:module_info/\"(0|1)\" + LM"),
%% info/1:
@@ -2670,7 +2686,7 @@ check_count(S) ->
ok.
info_module([M | Ms], S) ->
- {ok, NoCalls} = per_module("T = (E | ~p : Mod), # (XLin) T + # (LLin) T",
+ {ok, NoCalls} = per_module("T = (E | ~p : Mod), # (XLin) T + # (LLin) T",
M, S),
{ok, NoFunCalls} = per_module("# (E | ~p : Mod)", M, S),
{ok, NoXCalls} = per_module("# (XC | ~p : Mod)", M, S),
@@ -2719,14 +2735,14 @@ start(Server) ->
end.
add_erts_code_path(KernelPath) ->
- VersionDirs =
+ VersionDirs =
filelib:is_dir(
filename:join(
[code:lib_dir(),
lists:flatten(
["kernel-",
- [X ||
- {kernel,_,X} <-
+ [X ||
+ {kernel,_,X} <-
application_controller:which_applications()]])])),
case VersionDirs of
true ->
@@ -2746,5 +2762,5 @@ add_erts_code_path(KernelPath) ->
[KernelPath]
end
end.
-
-
+
+
diff --git a/lib/tools/test/xref_SUITE_data/read/read.erl b/lib/tools/test/xref_SUITE_data/read/read.erl
index 4a0cc280c3..19694c9e25 100644
--- a/lib/tools/test/xref_SUITE_data/read/read.erl
+++ b/lib/tools/test/xref_SUITE_data/read/read.erl
@@ -106,13 +106,13 @@ funfuns() ->
apply(m,f,a), % {m,f,-1}
3(a), % {'$M_EXPR','$F_EXPR',1}
apply(3,[a]), % {'$M_EXPR','$F_EXPR',1}
-
+
%% POS12=POS11+8
apply(A, A), % number of arguments is not known, {'$M_EXPR','$F_EXPR',-1}
Args0 = [list],
Args = [a | Args0], % number of arguments is known
apply(A, Args), % {'$M_EXPR','$F_EXPR',2}
- apply(m3, f3, Args), %
+ apply(m3, f3, Args), %
NotArgs = [is_not, a | list], % number of arguments is not known
apply(A, NotArgs), % {'$M_EXPR','$F_EXPR',-1}
apply(m4, f4, NotArgs), % {m4,f4,-1}
@@ -125,7 +125,7 @@ funfuns() ->
bi() when length([]) > 17 ->
foo:module_info(),
module_info(),
- A = tjo,
+ A = true andalso tjo ,
t:foo(A),
case true of
true when integer(1) ->
@@ -133,7 +133,7 @@ bi() when length([]) > 17 ->
false ->
X = flopp
end,
- X == A;
+ self() ! X == -A orelse false;
bi() ->
%% POS14=POS13+18
Z = fun(Y) -> Y end,
@@ -159,7 +159,7 @@ bi() ->
D + E + F.
%bi() ->
% %% POS15=POS14+13
-% try
+% try
% foo:t(),
% bar:t()
% of
@@ -169,7 +169,7 @@ bi() ->
% foo:t()
% catch
% {'EXIT',_} -> bar:t()
-% end.
+% end.
local() ->
true.
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index 13cf5af9f5..abe9a804f0 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1,19 +1,19 @@
# This is an -*-makefile-*- file.
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1997-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%
-TOOLS_VSN = 2.6.5.1
+TOOLS_VSN = 2.6.6
diff --git a/lib/tv/doc/src/notes.xml b/lib/tv/doc/src/notes.xml
index 62fab2f0f1..43b650dd29 100644
--- a/lib/tv/doc/src/notes.xml
+++ b/lib/tv/doc/src/notes.xml
@@ -4,7 +4,7 @@
<chapter>
<header>
<copyright>
- <year>2004</year><year>2009</year>
+ <year>2004</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>TV Release Notes</title>
@@ -30,6 +30,21 @@
</header>
<p>This document describes the changes made to the TV application.</p>
+<section><title>TV 2.1.4.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Warnings due to new autoimported BIFs removed</p>
+ <p>
+ Own Id: OTP-8674 Aux Id: OTP-8579 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>TV 2.1.4.4</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/tv/src/tv_io_lib_format.erl b/lib/tv/src/tv_io_lib_format.erl
index 5042fd3f9d..e043d9296e 100644
--- a/lib/tv/src/tv_io_lib_format.erl
+++ b/lib/tv/src/tv_io_lib_format.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1998-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1998-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(tv_io_lib_format).
@@ -188,7 +188,7 @@ indentation([], I) -> I.
term(T, none, _Adj, none, _Pad) -> T;
term(T, none, Adj, P, Pad) -> term(T, P, Adj, P, Pad);
-term(T, F, Adj, none, Pad) -> term(T, F, Adj, min(flat_length(T), F), Pad);
+term(T, F, Adj, none, Pad) -> term(T, F, Adj, erlang:min(flat_length(T), F), Pad);
term(T, F, Adj, P, Pad) when F >= P ->
adjust_error(T, F, Adj, P, Pad).
@@ -316,7 +316,7 @@ fwrite_g(Fl, F, Adj, P, Pad) ->
string(S, none, _Adj, none, _Pad) -> S;
string(S, F, Adj, none, Pad) ->
- string(S, F, Adj, min(flat_length(S), F), Pad);
+ string(S, F, Adj, erlang:min(flat_length(S), F), Pad);
string(S, none, _Adj, P, Pad) ->
string:left(flatten(S), P, Pad);
string(S, F, Adj, P, Pad) when F >= P ->
@@ -362,9 +362,6 @@ reverse([H|T], Stack) ->
reverse(T, [H|Stack]);
reverse([], Stack) -> Stack.
-min(L, R) when L < R -> L;
-min(_, R) -> R.
-
%% flatten(List)
%% Flatten a list.
diff --git a/lib/tv/src/tv_pb.erl b/lib/tv/src/tv_pb.erl
index 34db8d0772..78a27185dc 100644
--- a/lib/tv/src/tv_pb.erl
+++ b/lib/tv/src/tv_pb.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(tv_pb).
@@ -522,7 +522,7 @@ handle_col_resizing(RbtnId, RealCol, VirtualCol, Xpos, ProcVars) ->
get_xdiff(Id, Btn, LastXdiff, LineId, LineXpos, MinAllowedXdiff) ->
receive
{gs, Id, motion, {resbtn, _RealCol, _VirtCol, _OldXpos}, [NewXdiff | _T]} ->
- UsedXdiff = max(MinAllowedXdiff, NewXdiff),
+ UsedXdiff = erlang:max(MinAllowedXdiff, NewXdiff),
gs:config(LineId, [{x, LineXpos + UsedXdiff}]),
get_xdiff(Id, Btn, UsedXdiff, LineId, LineXpos, MinAllowedXdiff);
{gs, Id, buttonrelease, _Data, [Btn | _T]} ->
@@ -658,28 +658,3 @@ update_vbtns(Msg, ProcVars) ->
update_keys(Msg, ProcVars) ->
#pb_key_info{list_of_keys = KeyList} = Msg,
tv_pb_funcs:update_keys(KeyList, ProcVars).
-
-
-
-
-
-
-
-
-%%======================================================================
-%% Function:
-%%
-%% Return Value:
-%%
-%% Description:
-%%
-%% Parameters:
-%%======================================================================
-
-
-max(A, B) when A >= B ->
- A;
-max(_, B) ->
- B.
-
-
diff --git a/lib/tv/src/tv_pg_gridfcns.erl b/lib/tv/src/tv_pg_gridfcns.erl
index 809403fd96..ab88e2864f 100644
--- a/lib/tv/src/tv_pg_gridfcns.erl
+++ b/lib/tv/src/tv_pg_gridfcns.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 1997-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 1997-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(tv_pg_gridfcns).
@@ -98,7 +98,7 @@ init_grid(GridParentId, GridWidth,
nof_rows_shown = NofRowsShown
},
- NewNofCols = max(length(ColsShown), NofCols),
+ NewNofCols = erlang:max(length(ColsShown), NofCols),
% The GridColWidths list shall contain the current width of each frame.
NewColWidths = update_col_widths(ColsShown, ColWidths, FirstColShown,
@@ -270,7 +270,7 @@ resize_grid_column(RealCol, VirtualCol, Xdiff, ProcVars) ->
lists_as_strings = ListAsStr} = GridP,
% Get new width!
- Width = min(MaxColWidth, max((lists:nth(VirtualCol, ColWidths) + Xdiff),
+ Width = erlang:min(MaxColWidth, erlang:max((lists:nth(VirtualCol, ColWidths) + Xdiff),
MinColWidth)),
% Resize the column.
@@ -1336,7 +1336,7 @@ resize_all_grid_columns(RealCol, [ColWidth | Tail], ColFrameIds, MaxColWidth, Mi
resize_one_column(RealCol, Width, ColFrameIds, MaxW, MinW) ->
- NewWidthOfCol = min(MaxW, max(Width, MinW)),
+ NewWidthOfCol = erlang:min(MaxW, erlang:max(Width, MinW)),
case length(ColFrameIds) of
RealCol ->
done;
@@ -1894,46 +1894,3 @@ extract_ids_for_one_row(N, [ColIds | Tail]) ->
%%%---------------------------------------------------------------------
%%% END of functions used to create the grid.
%%%---------------------------------------------------------------------
-
-
-
-
-
-%%======================================================================
-%% Function:
-%%
-%% Return Value:
-%%
-%% Description:
-%%
-%% Parameters:
-%%======================================================================
-
-
-max(A, B) when A > B ->
- A;
-max(_, B) ->
- B.
-
-
-
-
-
-
-
-%%======================================================================
-%% Function:
-%%
-%% Return Value:
-%%
-%% Description:
-%%
-%% Parameters:
-%%======================================================================
-
-
-min(A, B) when A < B ->
- A;
-min(_, B) ->
- B.
-
diff --git a/lib/tv/vsn.mk b/lib/tv/vsn.mk
index be1981b755..93973489bc 100644
--- a/lib/tv/vsn.mk
+++ b/lib/tv/vsn.mk
@@ -1,20 +1,20 @@
#
# %CopyrightBegin%
-#
-# Copyright Ericsson AB 1997-2009. All Rights Reserved.
-#
+#
+# Copyright Ericsson AB 1997-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%
#
-TV_VSN = 2.1.4.4
+TV_VSN = 2.1.4.5
diff --git a/lib/webtool/doc/src/notes.xml b/lib/webtool/doc/src/notes.xml
index dc325b5b61..5179f37db2 100644
--- a/lib/webtool/doc/src/notes.xml
+++ b/lib/webtool/doc/src/notes.xml
@@ -31,6 +31,23 @@
<p>This document describes the changes made to the Webtool
application.</p>
+<section><title>WebTool 0.8.7</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Up until now Netscape has been the default web browser on
+ Unix/Linux. Webtool has now been updated to start Firefox
+ as default browser instead.</p>
+ <p>
+ Own Id: OTP-8651 Aux Id: OTP-8650 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>WebTool 0.8.6</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/webtool/doc/src/start_webtool.xml b/lib/webtool/doc/src/start_webtool.xml
index 184285c631..b525b38845 100644
--- a/lib/webtool/doc/src/start_webtool.xml
+++ b/lib/webtool/doc/src/start_webtool.xml
@@ -4,7 +4,7 @@
<comref>
<header>
<copyright>
- <year>2003</year><year>2009</year>
+ <year>2003</year><year>2010</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -13,12 +13,12 @@
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>start_webtool</title>
@@ -56,7 +56,7 @@ Or http://127.0.0.1:8888/
Usage: start_webtool application [ browser ]
Available applications are: [orber,appmon,crashdump_viewer,webcover]
-Default browser is 'iexplore' (Internet Explorer) on Windows or else 'netscape' </pre>
+Default browser is 'iexplore' (Internet Explorer) on Windows or else 'firefox' </pre>
<p>To start any of the listed applications, give the
application name as the first argument, e.g.</p>
<pre>
@@ -68,7 +68,7 @@ Starting webcover...
Sending URL to netscape...done </pre>
<p>The WebTool application WebCover is then started and the
default browser is used. The default browser is Internet
- Explorer on Windows or else Netscape.
+ Explorer on Windows or else Firefox.
</p>
<p>To use another browser, give the browser's start command
as the second argument, e.g.</p>
diff --git a/lib/webtool/src/webtool.erl b/lib/webtool/src/webtool.erl
index 51d821751c..1be903b827 100644
--- a/lib/webtool/src/webtool.erl
+++ b/lib/webtool/src/webtool.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2001-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2001-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(webtool).
@@ -139,7 +139,7 @@ script_start([App]) ->
DefaultBrowser =
case os:type() of
{win32,_} -> iexplore;
- _ -> netscape
+ _ -> firefox
end,
script_start([App,DefaultBrowser]);
script_start([App,Browser]) ->
@@ -159,6 +159,8 @@ script_start([App,Browser]) ->
"http://localhost:" ++ PortStr ++ "/" ++ StartPage
end,
case Browser of
+ none ->
+ ok;
iexplore when OSType == win32->
io:format("Starting internet explorer...\n"),
{ok,R} = win32reg:open(""),
@@ -170,7 +172,7 @@ script_start([App,Browser]) ->
_ when OSType == win32 ->
io:format("Starting ~w...\n",[Browser]),
os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url);
- B when B==netscape; B==mozilla ->
+ B when B==firefox; B==mozilla ->
io:format("Sending URL to ~w...",[Browser]),
BStr = atom_to_list(Browser),
SendCmd = BStr ++ " -raise -remote \'openUrl(" ++
@@ -209,7 +211,7 @@ usage() ->
"\nUsage: start_webtool application [ browser ]\n"
"\nAvailable applications are: ~p\n"
"Default browser is \'iexplore\' (Internet Explorer) on Windows "
- "or else \'netscape\'\n",
+ "or else \'firefox\'\n",
[Apps]),
stop().
diff --git a/lib/webtool/vsn.mk b/lib/webtool/vsn.mk
index 712e3abbaf..6b76883330 100644
--- a/lib/webtool/vsn.mk
+++ b/lib/webtool/vsn.mk
@@ -1 +1 @@
-WEBTOOL_VSN=0.8.6
+WEBTOOL_VSN=0.8.7
diff --git a/lib/wx/c_src/Makefile.in b/lib/wx/c_src/Makefile.in
index 5a0b4ce8ef..8710641b57 100644
--- a/lib/wx/c_src/Makefile.in
+++ b/lib/wx/c_src/Makefile.in
@@ -167,7 +167,7 @@ release_spec: opt
$(INSTALL_DIR) $(RELSYSDIR)/priv/$(SYS_TYPE)
$(INSTALL_DATA) ../priv/erlang-logo32.png $(RELSYSDIR)/priv/
$(INSTALL_DATA) ../priv/erlang-logo64.png $(RELSYSDIR)/priv/
- $(INSTALL_DATA) $(TARGET_DIR)/$(TARGET_API)$(SO_EXT) $(RELSYSDIR)/priv/$(SYS_TYPE)
+ $(INSTALL_PROGRAM) $(TARGET_DIR)/$(TARGET_API)$(SO_EXT) $(RELSYSDIR)/priv/$(SYS_TYPE)
release_docs_spec:
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index 92933c348b..34c56091aa 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -31,6 +31,23 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 0.98.6</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Calling <c>sys:get_status()</c> for processes that have
+ globally registered names that were not atoms would cause
+ a crash. Corrected. (Thanks to Steve Vinoski.)</p>
+ <p>
+ Own Id: OTP-8656</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 0.98.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl
index 1f0b7922a0..bfd38960dd 100644
--- a/lib/wx/src/wx_object.erl
+++ b/lib/wx/src/wx_object.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2008-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2008-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%
%%%-------------------------------------------------------------------
%%% File : wx_object.erl
@@ -321,7 +321,8 @@ loop(Parent, Name, State, Mod, Time, Debug) ->
_Msg when Debug =:= [] ->
handle_msg(Msg, Parent, Name, State, Mod);
_Msg ->
- Debug1 = sys:handle_debug(Debug, {gen_server, print_event}, Name, {in, Msg}),
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
+ Name, {in, Msg}),
handle_msg(Msg, Parent, Name, State, Mod, Debug1)
end.
@@ -410,12 +411,12 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) ->
Debug1 = reply(Name, From, Reply, NState, Debug),
loop(Parent, Name, NState, Mod, Time1, Debug1);
{noreply, NState} ->
- Debug1 = sys:handle_debug(Debug, {gen_server, print_event}, Name,
- {noreply, NState}),
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
+ Name, {noreply, NState}),
loop(Parent, Name, NState, Mod, infinity, Debug1);
{noreply, NState, Time1} ->
- Debug1 = sys:handle_debug(Debug, {gen_server, print_event}, Name,
- {noreply, NState}),
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
+ Name, {noreply, NState}),
loop(Parent, Name, NState, Mod, Time1, Debug1);
{stop, Reason, Reply, NState} ->
{'EXIT', R} =
@@ -437,12 +438,12 @@ handle_no_reply({noreply, NState}, Parent, Name, _Msg, Mod, _State, []) ->
handle_no_reply({noreply, NState, Time1}, Parent, Name, _Msg, Mod, _State, []) ->
loop(Parent, Name, NState, Mod, Time1, []);
handle_no_reply({noreply, NState}, Parent, Name, _Msg, Mod, _State, Debug) ->
- Debug1 = sys:handle_debug(Debug, {gen_server, print_event}, Name,
- {noreply, NState}),
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
+ Name, {noreply, NState}),
loop(Parent, Name, NState, Mod, infinity, Debug1);
handle_no_reply({noreply, NState, Time1}, Parent, Name, _Msg, Mod, _State, Debug) ->
- Debug1 = sys:handle_debug(Debug, {gen_server, print_event}, Name,
- {noreply, NState}),
+ Debug1 = sys:handle_debug(Debug, fun print_event/3,
+ Name, {noreply, NState}),
loop(Parent, Name, NState, Mod, Time1, Debug1);
handle_no_reply(Reply, _Parent, Name, Msg, Mod, State, Debug) ->
handle_common_reply(Reply, Name, Msg, Mod, State,Debug).
@@ -462,8 +463,8 @@ handle_common_reply(Reply, Name, Msg, Mod, State, Debug) ->
%% @hidden
reply(Name, {To, Tag}, Reply, State, Debug) ->
reply({To, Tag}, Reply),
- sys:handle_debug(Debug, {gen_server, print_event}, Name,
- {out, Reply, To, State} ).
+ sys:handle_debug(Debug, fun print_event/3,
+ Name, {out, Reply, To, State}).
%%-----------------------------------------------------------------
@@ -485,6 +486,29 @@ system_code_change([Name, State, Mod, Time], _Module, OldVsn, Extra) ->
Else -> Else
end.
+%%-----------------------------------------------------------------
+%% Format debug messages. Print them as the call-back module sees
+%% them, not as the real erlang messages. Use trace for that.
+%%-----------------------------------------------------------------
+print_event(Dev, {in, Msg}, Name) ->
+ case Msg of
+ {'$gen_call', {From, _Tag}, Call} ->
+ io:format(Dev, "*DBG* ~p got call ~p from ~w~n",
+ [Name, Call, From]);
+ {'$gen_cast', Cast} ->
+ io:format(Dev, "*DBG* ~p got cast ~p~n",
+ [Name, Cast]);
+ _ ->
+ io:format(Dev, "*DBG* ~p got ~p~n", [Name, Msg])
+ end;
+print_event(Dev, {out, Msg, To, State}, Name) ->
+ io:format(Dev, "*DBG* ~p sent ~p to ~w, new state ~w~n",
+ [Name, Msg, To, State]);
+print_event(Dev, {noreply, State}, Name) ->
+ io:format(Dev, "*DBG* ~p new state ~w~n", [Name, State]);
+print_event(Dev, Event, Name) ->
+ io:format(Dev, "*DBG* ~p dbg ~p~n", [Name, Event]).
+
%%% ---------------------------------------------------
%%% Terminate the server.
%%% ---------------------------------------------------
@@ -581,12 +605,15 @@ dbg_opts(Name, Opts) ->
%%-----------------------------------------------------------------
format_status(Opt, StatusData) ->
[PDict, SysState, Parent, Debug, [Name, State, Mod, _Time]] = StatusData,
- NameTag = if is_pid(Name) ->
- pid_to_list(Name);
- is_atom(Name) ->
- Name
- end,
- Header = lists:concat(["Status for generic server ", NameTag]),
+ StatusHdr = "Status for wx object ",
+ Header = if
+ is_pid(Name) ->
+ lists:concat([StatusHdr, pid_to_list(Name)]);
+ is_atom(Name); is_list(Name) ->
+ lists:concat([StatusHdr, Name]);
+ true ->
+ {StatusHdr, Name}
+ end,
Log = sys:get_debug(log, Debug, []),
Specfic =
case erlang:function_exported(Mod, format_status, 2) of
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index 54ab92cad2..4ed22d2256 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1,6 +1,8 @@
-WX_VSN = 0.98.5
+WX_VSN = 0.98.6
-TICKETS = OTP-8330 OTP-8461 OTP-8408 OTP-8455 OTP-8462
+TICKETS = OTP-8656
+
+TICKETS_0.98.5 = OTP-8330 OTP-8461 OTP-8408 OTP-8455 OTP-8462
TICKETS_0.98.4 = OTP-8243 OTP-8250 OTP-8292
TICKETS_0.98.3 = OTP-8138 OTP-8126 OTP-8083
TICKETS_0.98.2 = OTP-7943
diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml
index 207f6fdf16..0403fbca27 100644
--- a/lib/xmerl/doc/src/notes.xml
+++ b/lib/xmerl/doc/src/notes.xml
@@ -50,6 +50,14 @@
Own Id: OTP-8537
</p>
</item>
+ <item>
+ <p>
+ An empty element declared as a simpleContent was not properly validated.
+ </p>
+ <p>
+ Own Id: OTP-8599
+ </p>
+ </item>
</list>
</section>
diff --git a/lib/xmerl/src/xmerl_xsd.erl b/lib/xmerl/src/xmerl_xsd.erl
index c7bca86205..1aedc9e270 100644
--- a/lib/xmerl/src/xmerl_xsd.erl
+++ b/lib/xmerl/src/xmerl_xsd.erl
@@ -1,19 +1,19 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2006-2009. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2006-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%
%%
@@ -2687,13 +2687,16 @@ check_element_type(XML=[E=#xmlElement{name=Name}|Rest],
_ ->
{error,{error_path(E,Name),?MODULE,{element_bad_match,E,Any,Env}}}
end;
-check_element_type([],CM,_Env,_Block,_S,Checked) ->
+check_element_type([],CM,_Env,_Block,S,Checked) ->
%% #schema_complex_type, any, #schema_group, anyType and lists are
%% catched above.
case CM of
+ #schema_simple_type{} ->
+ {NewVal,S2} = check_type(CM,[],unapplied,S),
+ {NewVal,[],S2};
{simpleType,_} ->
- {error,{error_path(Checked,undefined),?MODULE,
- {empty_content_not_allowed,CM}}};
+ {NewVal,S2} = check_type(CM,[],unapplied,S),
+ {NewVal,[],S2};
_ ->
{error,{error_path(Checked,undefined),?MODULE,
{empty_content_not_allowed,CM}}}
diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk
index 83b9d4826f..aee7546c3c 100644
--- a/lib/xmerl/vsn.mk
+++ b/lib/xmerl/vsn.mk
@@ -21,7 +21,8 @@ XMERL_VSN = 1.2.5
TICKETS = \
- OTP-8537
+ OTP-8537 \
+ OTP-8599
TICKETS_1.2.5 = \
OTP-8537