diff options
author | olgeni <olgeni@FreeBSD.org> | 2015-05-08 04:34:30 +0800 |
---|---|---|
committer | olgeni <olgeni@FreeBSD.org> | 2015-05-08 04:34:30 +0800 |
commit | 1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5 (patch) | |
tree | b8e420f7abb9857550d224bf4693cfa78b4b8c82 | |
parent | cf8587700191e43cd36d3c7c59f35c592ebd998f (diff) | |
download | freebsd-ports-gnome-1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5.tar.gz freebsd-ports-gnome-1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5.tar.zst freebsd-ports-gnome-1884a385818ca10fdf5a279ffe2b9c5ea46ed5f5.zip |
Upgrade all Erlang 17 ports to 17.5.3.
-rw-r--r-- | lang/erlang-java/Makefile | 5 | ||||
-rw-r--r-- | lang/erlang-runtime17/Makefile | 23 | ||||
-rw-r--r-- | lang/erlang-runtime17/files/patch-otp-17.5.3 | 3738 | ||||
-rw-r--r-- | lang/erlang-wx/Makefile | 5 | ||||
-rw-r--r-- | lang/erlang/Makefile | 23 | ||||
-rw-r--r-- | lang/erlang/files/patch-otp-17.5.3 | 3738 |
6 files changed, 7522 insertions, 10 deletions
diff --git a/lang/erlang-java/Makefile b/lang/erlang-java/Makefile index dac934c4c27f..a3591087ed47 100644 --- a/lang/erlang-java/Makefile +++ b/lang/erlang-java/Makefile @@ -1,7 +1,7 @@ # $FreeBSD$ PORTNAME= erlang -PORTVERSION= 17.5.2 +PORTVERSION= 17.5.3 CATEGORIES= lang parallel java MASTER_SITES= http://www.erlang.org/download/:erlangorg \ http://erlang.stacken.kth.se/download/:erlangorg \ @@ -25,8 +25,7 @@ OPTIONS_DEFINE= DOCS ERL_RELEASE= 17.5 -USES= gmake -USE_AUTOTOOLS= autoconf:env +USES= autoreconf gmake GNU_CONFIGURE= yes LDFLAGS+= -L${LOCALBASE}/lib diff --git a/lang/erlang-runtime17/Makefile b/lang/erlang-runtime17/Makefile index 2d81c390859c..c568752830ae 100644 --- a/lang/erlang-runtime17/Makefile +++ b/lang/erlang-runtime17/Makefile @@ -2,8 +2,7 @@ # $FreeBSD$ PORTNAME= erlang -PORTVERSION= 17.5.2 -PORTREVISION= 2 +PORTVERSION= 17.5.3 CATEGORIES= lang parallel java MASTER_SITES= http://www.erlang.org/download/:erlangorg \ http://erlang.stacken.kth.se/download/:erlangorg \ @@ -186,6 +185,10 @@ post-install: ${TAR} --unlink -xzpf ${DISTDIR}/${DIST_SUBDIR}/${ERLANG_DOCS} \ -C ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB} + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4 + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6/* \ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.7 ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6 @@ -194,6 +197,22 @@ post-install: ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2.2 ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2 + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10 + + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9 + + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.2 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1 + + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8 + ${INSTALL_DATA} ${WRKSRC}/lib/dialyzer/doc/*.txt \ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/dialyzer-*/doc/ .endif diff --git a/lang/erlang-runtime17/files/patch-otp-17.5.3 b/lang/erlang-runtime17/files/patch-otp-17.5.3 new file mode 100644 index 000000000000..f9ed64007fd2 --- /dev/null +++ b/lang/erlang-runtime17/files/patch-otp-17.5.3 @@ -0,0 +1,3738 @@ +diff --git OTP_VERSION OTP_VERSION +index 808ab16..f32d20d 100644 +--- OTP_VERSION ++++ OTP_VERSION +@@ -1 +1 @@ +-17.5.2 ++17.5.3 +diff --git erts/doc/src/notes.xml erts/doc/src/notes.xml +index a2b4ae4..35e6e55 100644 +--- erts/doc/src/notes.xml ++++ erts/doc/src/notes.xml +@@ -30,6 +30,22 @@ + </header> + <p>This document describes the changes made to the ERTS application.</p> + ++<section><title>Erts 6.4.1</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ The VTS mode in Common Test has been modified to use a ++ private version of the Webtool application (ct_webtool).</p> ++ <p> ++ Own Id: OTP-12704 Aux Id: OTP-10922 </p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>Erts 6.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git erts/etc/common/ct_run.c erts/etc/common/ct_run.c +index bb59b93..9e67b94 100644 +--- erts/etc/common/ct_run.c ++++ erts/etc/common/ct_run.c +@@ -239,7 +239,7 @@ int main(int argc, char** argv) + */ + + if (ct_mode == VTS_MODE) { +- PUSH4("-s", "webtool", "script_start", "vts"); ++ PUSH4("-s", "ct_webtool", "script_start", "vts"); + if (browser[0] != '\0') PUSH(browser); + PUSH3("-s", "ct_run", "script_start"); + } +diff --git erts/vsn.mk erts/vsn.mk +index abc9c0b..9e5aa99 100644 +--- erts/vsn.mk ++++ erts/vsn.mk +@@ -17,7 +17,7 @@ + # %CopyrightEnd% + # + +-VSN = 6.4 ++VSN = 6.4.1 + + # Port number 4365 in 4.2 + # Port number 4366 in 4.3 +diff --git lib/common_test/doc/src/notes.xml lib/common_test/doc/src/notes.xml +index 822ebf1..472e3b7 100644 +--- lib/common_test/doc/src/notes.xml ++++ lib/common_test/doc/src/notes.xml +@@ -32,6 +32,66 @@ + <file>notes.xml</file> + </header> + ++<section><title>Common_Test 1.10.1</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ A fault in the Common Test logger process, that caused ++ the application to crash when running on a long name ++ node, has been corrected.</p> ++ <p> ++ Own Id: OTP-12643</p> ++ </item> ++ <item> ++ <p> ++ A 'wait_for_prompt' option in ct_telnet:expect/3 has been ++ introduced which forces the function to not return until ++ a prompt string has been received, even if other expect ++ patterns have already been found.</p> ++ <p> ++ Own Id: OTP-12688 Aux Id: seq12818 </p> ++ </item> ++ <item> ++ <p> ++ If the last expression in a test case causes a timetrap ++ timeout, the stack trace is ignored and not printed to ++ the test case log file. This happens because the ++ {Suite,TestCase,Line} info is not available in the stack ++ trace in this scenario, due to tail call elimination. ++ Common Test has been modified to handle this situation by ++ inserting a {Suite,TestCase,last_expr} tuple in the ++ correct place and printing the stack trace as expected.</p> ++ <p> ++ Own Id: OTP-12697 Aux Id: seq12848 </p> ++ </item> ++ <item> ++ <p> ++ Fixed a buffer problem in ct_netconfc which could cause ++ that some messages where buffered forever.</p> ++ <p> ++ Own Id: OTP-12698 Aux Id: seq12844 </p> ++ </item> ++ <item> ++ <p> ++ The VTS mode in Common Test has been modified to use a ++ private version of the Webtool application (ct_webtool).</p> ++ <p> ++ Own Id: OTP-12704 Aux Id: OTP-10922 </p> ++ </item> ++ <item> ++ <p> ++ Add possibility to add user capabilities in ++ <c>ct_netconfc:hello/3</c>.</p> ++ <p> ++ Own Id: OTP-12707 Aux Id: seq12846 </p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>Common_Test 1.10</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git lib/common_test/src/Makefile lib/common_test/src/Makefile +index 8d74546..449cba6 100644 +--- lib/common_test/src/Makefile ++++ lib/common_test/src/Makefile +@@ -62,6 +62,8 @@ MODULES= \ + ct_telnet_client \ + ct_make \ + vts \ ++ ct_webtool \ ++ ct_webtool_sup \ + unix_telnet \ + ct_config \ + ct_config_plain \ +diff --git lib/common_test/src/ct_logs.erl lib/common_test/src/ct_logs.erl +index dc118ed..fa55a97 100644 +--- lib/common_test/src/ct_logs.erl ++++ lib/common_test/src/ct_logs.erl +@@ -1908,13 +1908,14 @@ sort_all_runs(Dirs) -> + sort_ct_runs(Dirs) -> + %% Directory naming: <Prefix>.NodeName.Date_Time[/...] + %% Sort on Date_Time string: "YYYY-MM-DD_HH.MM.SS" +- lists:sort(fun(Dir1,Dir2) -> +- [_Prefix,_Node1,DateHH1,MM1,SS1] = +- string:tokens(filename:dirname(Dir1),[$.]), +- [_Prefix,_Node2,DateHH2,MM2,SS2] = +- string:tokens(filename:dirname(Dir2),[$.]), +- {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2} +- end, Dirs). ++ lists:sort( ++ fun(Dir1,Dir2) -> ++ [SS1,MM1,DateHH1 | _] = ++ lists:reverse(string:tokens(filename:dirname(Dir1),[$.])), ++ [SS2,MM2,DateHH2 | _] = ++ lists:reverse(string:tokens(filename:dirname(Dir2),[$.])), ++ {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2} ++ end, Dirs). + + dir_diff_all_runs(Dirs, LogCache) -> + case LogCache#log_cache.all_runs of +diff --git lib/common_test/src/ct_netconfc.erl lib/common_test/src/ct_netconfc.erl +index 85fb1ea..80ffb51 100644 +--- lib/common_test/src/ct_netconfc.erl ++++ lib/common_test/src/ct_netconfc.erl +@@ -172,6 +172,7 @@ + only_open/2, + hello/1, + hello/2, ++ hello/3, + close_session/1, + close_session/2, + kill_session/2, +@@ -456,23 +457,35 @@ only_open(KeyOrName, ExtraOpts) -> + + %%---------------------------------------------------------------------- + %% @spec hello(Client) -> Result +-%% @equiv hello(Client, infinity) ++%% @equiv hello(Client, [], infinity) + hello(Client) -> +- hello(Client,?DEFAULT_TIMEOUT). ++ hello(Client,[],?DEFAULT_TIMEOUT). + + %%---------------------------------------------------------------------- + -spec hello(Client,Timeout) -> Result when + Client :: handle(), + Timeout :: timeout(), + Result :: ok | {error,error_reason()}. +-%% @doc Exchange `hello' messages with the server. +-%% +-%% Sends a `hello' message to the server and waits for the return. +-%% +-%% @end +-%%---------------------------------------------------------------------- ++%% @spec hello(Client, Timeout) -> Result ++%% @equiv hello(Client, [], Timeout) + hello(Client,Timeout) -> +- call(Client, {hello, Timeout}). ++ hello(Client,[],Timeout). ++ ++%%---------------------------------------------------------------------- ++-spec hello(Client,Options,Timeout) -> Result when ++ Client :: handle(), ++ Options :: [{capability, [string()]}], ++ Timeout :: timeout(), ++ Result :: ok | {error,error_reason()}. ++%% @doc Exchange `hello' messages with the server. ++%% ++%% Adds optional capabilities and sends a `hello' message to the ++%% server and waits for the return. ++%% @end ++%%---------------------------------------------------------------------- ++hello(Client,Options,Timeout) -> ++ call(Client, {hello, Options, Timeout}). ++ + + %%---------------------------------------------------------------------- + %% @spec get_session_id(Client) -> Result +@@ -1040,9 +1053,9 @@ terminate(_, #state{connection=Connection}) -> + ok. + + %% @private +-handle_msg({hello,Timeout}, From, ++handle_msg({hello, Options, Timeout}, From, + #state{connection=Connection,hello_status=HelloStatus} = State) -> +- case do_send(Connection, client_hello()) of ++ case do_send(Connection, client_hello(Options)) of + ok -> + case HelloStatus of + undefined -> +@@ -1118,7 +1131,9 @@ handle_msg({Ref,timeout},#state{pending=Pending} = State) -> + close_session -> stop; + _ -> noreply + end, +- {R,State#state{pending=Pending1}}. ++ %% Halfhearted try to get in correct state, this matches ++ %% the implementation before this patch ++ {R,State#state{pending=Pending1, buff= <<>>}}. + + %% @private + %% Called by ct_util_server to close registered connections before terminate. +@@ -1222,10 +1237,14 @@ set_request_timer(T) -> + + + %%%----------------------------------------------------------------- +-client_hello() -> ++client_hello(Options) when is_list(Options) -> ++ UserCaps = [{capability, UserCap} || ++ {capability, UserCap} <- Options, ++ is_list(hd(UserCap))], + {hello, ?NETCONF_NAMESPACE_ATTR, + [{capabilities, +- [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}]}]}. ++ [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}| ++ UserCaps]}]}. + + %%%----------------------------------------------------------------- + +@@ -1308,72 +1327,54 @@ to_xml_doc(Simple) -> + + %%%----------------------------------------------------------------- + %%% Parse and handle received XML data +-handle_data(NewData,#state{connection=Connection,buff=Buff} = State) -> ++handle_data(NewData,#state{connection=Connection,buff=Buff0} = State0) -> + log(Connection,recv,NewData), +- Data = <<Buff/binary,NewData/binary>>, +- case xmerl_sax_parser:stream(<<>>, +- [{continuation_fun,fun sax_cont/1}, +- {continuation_state,{Data,Connection,false}}, +- {event_fun,fun sax_event/3}, +- {event_state,[]}]) of +- {ok, Simple, Rest} -> +- decode(Simple,State#state{buff=Rest}); +- {fatal_error,_Loc,Reason,_EndTags,_EventState} -> +- ?error(Connection#connection.name,[{parse_error,Reason}, +- {buffer,Buff}, +- {new_data,NewData}]), +- case Reason of +- {could_not_fetch_data,Msg} -> +- handle_msg(Msg,State#state{buff = <<>>}); +- _Other -> +- Pending1 = +- case State#state.pending of +- [] -> +- []; +- Pending -> +- %% Assuming the first request gets the +- %% first answer +- P=#pending{tref=TRef,caller=Caller} = +- lists:last(Pending), +- _ = timer:cancel(TRef), +- Reason1 = {failed_to_parse_received_data,Reason}, +- ct_gen_conn:return(Caller,{error,Reason1}), +- lists:delete(P,Pending) +- end, +- {noreply,State#state{pending=Pending1,buff = <<>>}} +- end +- end. +- +-%%%----------------------------------------------------------------- +-%%% Parsing of XML data +-%% Contiuation function for the sax parser +-sax_cont(done) -> +- {<<>>,done}; +-sax_cont({Data,Connection,false}) -> ++ Data = append_wo_initial_nl(Buff0,NewData), + case binary:split(Data,[?END_TAG],[]) of +- [All] -> +- %% No end tag found. Remove what could be a part +- %% of an end tag from the data and save for next +- %% iteration +- SafeSize = size(All)-5, +- <<New:SafeSize/binary,Save:5/binary>> = All, +- {New,{Save,Connection,true}}; +- [_Msg,_Rest]=Msgs -> +- %% We have at least one full message. Any excess data will +- %% be returned from xmerl_sax_parser:stream/2 in the Rest +- %% parameter. +- {list_to_binary(Msgs),done} +- end; +-sax_cont({Data,Connection,true}) -> +- case ssh_receive_data() of +- {ok,Bin} -> +- log(Connection,recv,Bin), +- sax_cont({<<Data/binary,Bin/binary>>,Connection,false}); +- {error,Reason} -> +- throw({could_not_fetch_data,Reason}) ++ [_NoEndTagFound] -> ++ {noreply, State0#state{buff=Data}}; ++ [FirstMsg,Buff1] -> ++ SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}], ++ case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of ++ {ok, Simple, _Thrash} -> ++ case decode(Simple, State0#state{buff=Buff1}) of ++ {noreply, #state{buff=Buff} = State} when Buff =/= <<>> -> ++ %% Recurse if we have more data in buffer ++ handle_data(<<>>, State); ++ Other -> ++ Other ++ end; ++ {fatal_error,_Loc,Reason,_EndTags,_EventState} -> ++ ?error(Connection#connection.name, ++ [{parse_error,Reason}, ++ {buffer, Buff0}, ++ {new_data,NewData}]), ++ handle_error(Reason, State0#state{buff= <<>>}) ++ end + end. + ++%% xml does not accept a leading nl and some netconf server add a nl after ++%% each ?END_TAG, ignore them ++append_wo_initial_nl(<<>>,NewData) -> NewData; ++append_wo_initial_nl(<<"\n", Data/binary>>, NewData) -> ++ append_wo_initial_nl(Data, NewData); ++append_wo_initial_nl(Data, NewData) -> ++ <<Data/binary, NewData/binary>>. + ++handle_error(Reason, State) -> ++ Pending1 = case State#state.pending of ++ [] -> []; ++ Pending -> ++ %% Assuming the first request gets the ++ %% first answer ++ P=#pending{tref=TRef,caller=Caller} = ++ lists:last(Pending), ++ _ = timer:cancel(TRef), ++ Reason1 = {failed_to_parse_received_data,Reason}, ++ ct_gen_conn:return(Caller,{error,Reason1}), ++ lists:delete(P,Pending) ++ end, ++ {noreply, State#state{pending=Pending1}}. + + %% Event function for the sax parser. It builds a simple XML structure. + %% Care is taken to keep namespace attributes and prefixes as in the original XML. +@@ -1836,16 +1837,6 @@ get_tag([]) -> + + %%%----------------------------------------------------------------- + %%% SSH stuff +-ssh_receive_data() -> +- receive +- {ssh_cm, CM, {data, Ch, _Type, Data}} -> +- ssh_connection:adjust_window(CM,Ch,size(Data)), +- {ok, Data}; +- {ssh_cm, _CM, {Closed, _Ch}} = X when Closed == closed; Closed == eof -> +- {error,X}; +- {_Ref,timeout} = X -> +- {error,X} +- end. + + ssh_open(#options{host=Host,timeout=Timeout,port=Port,ssh=SshOpts,name=Name}) -> + case ssh:connect(Host, Port, +diff --git lib/common_test/src/ct_run.erl lib/common_test/src/ct_run.erl +index 4a12481..be547b4 100644 +--- lib/common_test/src/ct_run.erl ++++ lib/common_test/src/ct_run.erl +@@ -225,18 +225,24 @@ finish(Tracing, ExitStatus, Args) -> + if ExitStatus == interactive_mode -> + interactive_mode; + true -> +- %% it's possible to tell CT to finish execution with a call +- %% to a different function than the normal halt/1 BIF +- %% (meant to be used mainly for reading the CT exit status) +- case get_start_opt(halt_with, +- fun([HaltMod,HaltFunc]) -> +- {list_to_atom(HaltMod), +- list_to_atom(HaltFunc)} end, +- Args) of +- undefined -> +- halt(ExitStatus); +- {M,F} -> +- apply(M, F, [ExitStatus]) ++ case get_start_opt(vts, true, Args) of ++ true -> ++ %% VTS mode, don't halt the node ++ ok; ++ _ -> ++ %% it's possible to tell CT to finish execution with a call ++ %% to a different function than the normal halt/1 BIF ++ %% (meant to be used mainly for reading the CT exit status) ++ case get_start_opt(halt_with, ++ fun([HaltMod,HaltFunc]) -> ++ {list_to_atom(HaltMod), ++ list_to_atom(HaltFunc)} end, ++ Args) of ++ undefined -> ++ halt(ExitStatus); ++ {M,F} -> ++ apply(M, F, [ExitStatus]) ++ end + end + end. + +@@ -244,7 +250,7 @@ script_start1(Parent, Args) -> + %% read general start flags + Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args), + Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args), +- Vts = get_start_opt(vts, true, Args), ++ Vts = get_start_opt(vts, true, undefined, Args), + Shell = get_start_opt(shell, true, Args), + Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args), + CoverStop = get_start_opt(cover_stop, +@@ -330,8 +336,8 @@ script_start1(Parent, Args) -> + Stylesheet = get_start_opt(stylesheet, + fun([SS]) -> ?abs(SS) end, Args), + %% basic_html - used by ct_logs +- BasicHtml = case proplists:get_value(basic_html, Args) of +- undefined -> ++ BasicHtml = case {Vts,proplists:get_value(basic_html, Args)} of ++ {undefined,undefined} -> + application:set_env(common_test, basic_html, false), + undefined; + _ -> +@@ -364,9 +370,10 @@ script_start1(Parent, Args) -> + scale_timetraps = ScaleTT, + create_priv_dir = CreatePrivDir, + starter = script}, +- ++ + %% check if log files should be refreshed or go on to run tests... + Result = run_or_refresh(Opts, Args), ++ + %% send final results to starting process waiting in script_start/0 + Parent ! {self(), Result}. + +@@ -757,21 +764,6 @@ script_start4(Opts = #opts{tests = Tests}, Args) -> + %%% @doc Print usage information for <code>ct_run</code>. + script_usage() -> + io:format("\n\nUsage:\n\n"), +- io:format("Run tests in web based GUI:\n\n" +- "\tct_run -vts [-browser Browser]" +- "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" +- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" +- "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |" +- "\n\t[-suite Suite [-case Case]]" +- "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]" +- "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" +- "\n\t[-include InclDir1 InclDir2 .. InclDirN]" +- "\n\t[-no_auto_compile]" +- "\n\t[-abort_if_missing_suites]" +- "\n\t[-multiply_timetraps N]" +- "\n\t[-scale_timetraps]" +- "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" +- "\n\t[-basic_html]\n\n"), + io:format("Run tests from command line:\n\n" + "\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |" + "\n\t[[-dir TestDir] -suite Suite1 Suite2 .. SuiteN" +@@ -831,7 +823,22 @@ script_usage() -> + io:format("Run CT in interactive mode:\n\n" + "\tct_run -shell" + "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" +- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"). ++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"), ++ io:format("Run tests in web based GUI:\n\n" ++ "\tct_run -vts [-browser Browser]" ++ "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" ++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" ++ "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |" ++ "\n\t[-suite Suite [-case Case]]" ++ "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]" ++ "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" ++ "\n\t[-include InclDir1 InclDir2 .. InclDirN]" ++ "\n\t[-no_auto_compile]" ++ "\n\t[-abort_if_missing_suites]" ++ "\n\t[-multiply_timetraps N]" ++ "\n\t[-scale_timetraps]" ++ "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" ++ "\n\t[-basic_html]\n\n"). + + %%%----------------------------------------------------------------- + %%% @hidden +diff --git lib/common_test/src/ct_telnet.erl lib/common_test/src/ct_telnet.erl +index d906a26..844f537 100644 +--- lib/common_test/src/ct_telnet.erl ++++ lib/common_test/src/ct_telnet.erl +@@ -486,7 +486,8 @@ expect(Connection,Patterns) -> + %%% Opts = [Opt] + %%% Opt = {idle_timeout,IdleTimeout} | {total_timeout,TotalTimeout} | + %%% repeat | {repeat,N} | sequence | {halt,HaltPatterns} | +-%%% ignore_prompt | no_prompt_check ++%%% ignore_prompt | no_prompt_check | wait_for_prompt | ++%%% {wait_for_prompt,Prompt} + %%% IdleTimeout = infinity | integer() + %%% TotalTimeout = infinity | integer() + %%% N = integer() +@@ -499,9 +500,9 @@ expect(Connection,Patterns) -> + %%% + %%% @doc Get data from telnet and wait for the expected pattern. + %%% +-%%% <p><code>Pattern</code> can be a POSIX regular expression. If more +-%%% than one pattern is given, the function returns when the first +-%%% match is found.</p> ++%%% <p><code>Pattern</code> can be a POSIX regular expression. The function ++%%% returns as soon as a pattern has been successfully matched (at least one, ++%%% in the case of multiple patterns).</p> + %%% + %%% <p><code>RxMatch</code> is a list of matched strings. It looks + %%% like this: <code>[FullMatch, SubMatch1, SubMatch2, ...]</code> +@@ -524,10 +525,13 @@ expect(Connection,Patterns) -> + %%% milliseconds, <code>{error,timeout}</code> is returned. The default + %%% value is <code>infinity</code> (i.e. no time limit).</p> + %%% +-%%% <p>The function will always return when a prompt is found, unless +-%%% any of the <code>ignore_prompt</code> or +-%%% <code>no_prompt_check</code> options are used, in which case it +-%%% will return when a match is found or after a timeout.</p> ++%%% <p>The function will return when a prompt is received, even if no ++%%% pattern has yet been matched. In this event, ++%%% <code>{error,{prompt,Prompt}}</code> is returned. ++%%% However, this behaviour may be modified with the ++%%% <code>ignore_prompt</code> or <code>no_prompt_check</code> option, which ++%%% tells <code>expect</code> to return only when a match is found or after a ++%%% timeout.</p> + %%% + %%% <p>If the <code>ignore_prompt</code> option is used, + %%% <code>ct_telnet</code> will ignore any prompt found. This option +@@ -541,6 +545,13 @@ expect(Connection,Patterns) -> + %%% is useful if, for instance, the <code>Pattern</code> itself + %%% matches the prompt.</p> + %%% ++%%% <p>The <code>wait_for_prompt</code> option forces <code>ct_telnet</code> ++%%% to wait until the prompt string has been received before returning ++%%% (even if a pattern has already been matched). This is equal to calling: ++%%% <code>expect(Conn, Patterns++[{prompt,Prompt}], [sequence|Opts])</code>. ++%%% Note that <code>idle_timeout</code> and <code>total_timeout</code> ++%%% may abort the operation of waiting for prompt.</p> ++%%% + %%% <p>The <code>repeat</code> option indicates that the pattern(s) + %%% shall be matched multiple times. If <code>N</code> is given, the + %%% pattern(s) will be matched <code>N</code> times, and the function +@@ -653,18 +664,21 @@ handle_msg({cmd,Cmd,Opts},State) -> + start_gen_log(heading(cmd,State#state.name)), + log(State,cmd,"Cmd: ~p",[Cmd]), + ++ %% whatever is in the buffer from previous operations ++ %% will be ignored as we go ahead with this telnet cmd ++ + debug_cont_gen_log("Throwing Buffer:",[]), + debug_log_lines(State#state.buffer), + + case {State#state.type,State#state.prompt} of +- {ts,_} -> ++ {ts,_} -> + silent_teln_expect(State#state.name, + State#state.teln_pid, + State#state.buffer, + prompt, + State#state.prx, + [{idle_timeout,2000}]); +- {ip,false} -> ++ {ip,false} -> + silent_teln_expect(State#state.name, + State#state.teln_pid, + State#state.buffer, +@@ -1029,10 +1043,12 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) -> + end, + + PromptCheck = get_prompt_check(Opts), +- Seq = get_seq(Opts), +- Pattern = convert_pattern(Pattern0,Seq), + +- {IdleTimeout,TotalTimeout} = get_timeouts(Opts), ++ {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts), ++ ++ Seq = get_seq(Opts1), ++ Pattern2 = convert_pattern(Pattern1,Seq), ++ {IdleTimeout,TotalTimeout} = get_timeouts(Opts1), + + EO = #eo{teln_pid=Pid, + prx=Prx, +@@ -1042,9 +1058,16 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) -> + haltpatterns=HaltPatterns, + prompt_check=PromptCheck}, + +- case get_repeat(Opts) of ++ case get_repeat(Opts1) of + false -> +- case teln_expect1(Name,Pid,Data,Pattern,[],EO) of ++ case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of ++ {ok,Matched,Rest} when WaitForPrompt -> ++ case lists:reverse(Matched) of ++ [{prompt,_},Matched1] -> ++ {ok,Matched1,Rest}; ++ [{prompt,_}|Matched1] -> ++ {ok,lists:reverse(Matched1),Rest} ++ end; + {ok,Matched,Rest} -> + {ok,Matched,Rest}; + {halt,Why,Rest} -> +@@ -1054,7 +1077,7 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) -> + end; + N -> + EO1 = EO#eo{repeat=N}, +- repeat_expect(Name,Pid,Data,Pattern,[],EO1) ++ repeat_expect(Name,Pid,Data,Pattern2,[],EO1) + end. + + convert_pattern(Pattern,Seq) +@@ -1118,6 +1141,40 @@ get_ignore_prompt(Opts) -> + get_prompt_check(Opts) -> + not lists:member(no_prompt_check,Opts). + ++wait_for_prompt(Pattern, Opts) -> ++ case lists:member(wait_for_prompt, Opts) of ++ true -> ++ wait_for_prompt1(prompt, Pattern, ++ lists:delete(wait_for_prompt,Opts)); ++ false -> ++ case proplists:get_value(wait_for_prompt, Opts) of ++ undefined -> ++ {false,Pattern,Opts}; ++ PromptStr -> ++ wait_for_prompt1({prompt,PromptStr}, Pattern, ++ proplists:delete(wait_for_prompt,Opts)) ++ end ++ end. ++ ++wait_for_prompt1(Prompt, [Ch|_] = Pattern, Opts) when is_integer(Ch) -> ++ wait_for_prompt2(Prompt, [Pattern], Opts); ++wait_for_prompt1(Prompt, Pattern, Opts) when is_list(Pattern) -> ++ wait_for_prompt2(Prompt, Pattern, Opts); ++wait_for_prompt1(Prompt, Pattern, Opts) -> ++ wait_for_prompt2(Prompt, [Pattern], Opts). ++ ++wait_for_prompt2(Prompt, Pattern, Opts) -> ++ Pattern1 = case lists:reverse(Pattern) of ++ [prompt|_] -> Pattern; ++ [{prompt,_}|_] -> Pattern; ++ _ -> Pattern ++ [Prompt] ++ end, ++ Opts1 = case lists:member(sequence, Opts) of ++ true -> Opts; ++ false -> [sequence|Opts] ++ end, ++ {true,Pattern1,Opts1}. ++ + %% Repeat either single or sequence. All match results are accumulated + %% and returned when a halt condition is fulllfilled. + repeat_expect(_Name,_Pid,Rest,_Pattern,Acc,#eo{repeat=0}) -> +@@ -1210,7 +1267,7 @@ get_data1(Pid) -> + %% 1) Single expect. + %% First the whole data chunk is searched for a prompt (to avoid doing + %% a regexp match for the prompt at each line). +-%% If we are searching for anyting else, the datachunk is split into ++%% If we are searching for anything else, the datachunk is split into + %% lines and each line is matched against each pattern. + + %% one_expect: split data chunk at prompts +@@ -1227,7 +1284,7 @@ one_expect(Name,Pid,Data,Pattern,EO) -> + log(name_or_pid(Name,Pid),"PROMPT: ~ts",[PromptType]), + {match,{prompt,PromptType},Rest}; + [{prompt,_OtherPromptType}] -> +- %% Only searching for one specific prompt, not thisone ++ %% Only searching for one specific prompt, not this one + log_lines(Name,Pid,UptoPrompt), + {nomatch,Rest}; + _ -> +diff --git lib/common_test/src/ct_webtool.erl lib/common_test/src/ct_webtool.erl +new file mode 100644 +index 0000000..b67a7c2 +--- /dev/null ++++ lib/common_test/src/ct_webtool.erl +@@ -0,0 +1,1207 @@ ++%% ++%% %CopyrightBegin% ++%% ++%% 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(ct_webtool). ++-behaviour(gen_server). ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% The general idea is: %% ++%% %% ++%% %% ++%% 1. Scan through the path for *.tool files and find all the web %% ++%% based tools. Query each tool for configuration data. %% ++%% 2. Add Alias for Erlscript and html for each tool to %% ++%% the webserver configuration data. %% ++%% 3. Start the webserver. %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%% API functions ++-export([start/0, start/2, stop/0]). ++ ++%% Starting Webtool from a shell script ++-export([script_start/0, script_start/1]). ++ ++%% Web api ++-export([started_tools/2, toolbar/2, start_tools/2, stop_tools/2]). ++ ++%% API against other tools ++-export([is_localhost/0]). ++ ++%% Debug export s ++-export([get_tools1/1]). ++-export([debug/1, stop_debug/0, debug_app/1]). ++ ++%% gen_server callbacks ++-export([init/1, handle_call/3, handle_cast/2, handle_info/2, ++ terminate/2, code_change/3]). ++ ++-include_lib("kernel/include/file.hrl"). ++-include_lib("stdlib/include/ms_transform.hrl"). ++ ++-record(state,{priv_dir,app_data,supvis,web_data,started=[]}). ++ ++-define(MAX_NUMBER_OF_WEBTOOLS,256). ++-define(DEFAULT_PORT,8888).% must be >1024 or the user must be root on unix ++-define(DEFAULT_ADDR,{127,0,0,1}). ++ ++-define(WEBTOOL_ALIAS,{ct_webtool,[{alias,{erl_alias,"/ct_webtool",[ct_webtool]}}]}). ++-define(HEADER,"Pragma:no-cache\r\n Content-type: text/html\r\n\r\n"). ++-define(HTML_HEADER,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool</TITLE>\r\n</HEAD>\r\n<BODY BGCOLOR=\"#FFFFFF\">\r\n"). ++-define(HTML_HEADER_RELOAD,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool ++ </TITLE>\r\n</HEAD>\r\n ++ <BODY BGCOLOR=\"#FFFFFF\" onLoad=reloadCompiledList()>\r\n"). ++ ++-define(HTML_END,"</BODY></HTML>"). ++ ++-define(SEND_URL_TIMEOUT,5000). ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% For debugging only. %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% Start tracing with ++%% debug(Functions). ++%% Functions = local | global | FunctionList ++%% FunctionList = [Function] ++%% Function = {FunctionName,Arity} | FunctionName | ++%% {Module, FunctionName, Arity} | {Module,FunctionName} ++debug(F) -> ++ ttb:tracer(all,[{file,"webtool.trc"}]), % tracing all nodes ++ ttb:p(all,[call,timestamp]), ++ MS = [{'_',[],[{return_trace},{message,{caller}}]}], ++ tp(F,MS), ++ ttb:ctp(?MODULE,stop_debug), % don't want tracing of the stop_debug func ++ ok. ++tp(local,MS) -> % all functions ++ ttb:tpl(?MODULE,MS); ++tp(global,MS) -> % all exported functions ++ ttb:tp(?MODULE,MS); ++tp([{M,F,A}|T],MS) -> % Other module ++ ttb:tpl(M,F,A,MS), ++ tp(T,MS); ++tp([{M,F}|T],MS) when is_atom(F) -> % Other module ++ ttb:tpl(M,F,MS), ++ tp(T,MS); ++tp([{F,A}|T],MS) -> % function/arity ++ ttb:tpl(?MODULE,F,A,MS), ++ tp(T,MS); ++tp([F|T],MS) -> % function ++ ttb:tpl(?MODULE,F,MS), ++ tp(T,MS); ++tp([],_MS) -> ++ ok. ++stop_debug() -> ++ ttb:stop([format]). ++ ++debug_app(Mod) -> ++ ttb:tracer(all,[{file,"webtool_app.trc"},{handler,{fun out/4,true}}]), ++ ttb:p(all,[call,timestamp]), ++ MS = [{'_',[],[{return_trace},{message,{caller}}]}], ++ ttb:tp(Mod,MS), ++ ok. ++ ++out(_,{trace_ts,Pid,call,MFA={M,F,A},{W,_,_},TS},_,S) ++ when W==webtool;W==mod_esi-> ++ io:format("~w: (~p)~ncall ~s~n", [TS,Pid,ffunc(MFA)]), ++ [{M,F,length(A)}|S]; ++out(_,{trace_ts,Pid,return_from,MFA,R,TS},_,[MFA|S]) -> ++ io:format("~w: (~p)~nreturned from ~s -> ~p~n", [TS,Pid,ffunc(MFA),R]), ++ S; ++out(_,_,_,_) -> ++ ok. ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Functions called via script. %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++script_start() -> ++ usage(), ++ halt(). ++script_start([App]) -> ++ DefaultBrowser = ++ case os:type() of ++ {win32,_} -> iexplore; ++ _ -> firefox ++ end, ++ script_start([App,DefaultBrowser]); ++script_start([App,Browser]) -> ++ io:format("Starting webtool...\n"), ++ start(), ++ AvailableApps = get_applications(), ++ {OSType,_} = os:type(), ++ case lists:keysearch(App,1,AvailableApps) of ++ {value,{App,StartPage}} -> ++ io:format("Starting ~w...\n",[App]), ++ start_tools([],"app=" ++ atom_to_list(App)), ++ PortStr = integer_to_list(get_port()), ++ Url = case StartPage of ++ "/" ++ Page -> ++ "http://localhost:" ++ PortStr ++ "/" ++ Page; ++ _ -> ++ "http://localhost:" ++ PortStr ++ "/" ++ StartPage ++ end, ++ case Browser of ++ none -> ++ ok; ++ iexplore when OSType == win32-> ++ io:format("Starting internet explorer...\n"), ++ {ok,R} = win32reg:open(""), ++ Key="\\local_machine\\SOFTWARE\\Microsoft\\IE Setup\\Setup", ++ win32reg:change_key(R,Key), ++ {ok,Val} = win32reg:value(R,"Path"), ++ IExplore=filename:join(win32reg:expand(Val),"iexplore.exe"), ++ os:cmd("\"" ++ IExplore ++ "\" " ++ Url); ++ _ when OSType == win32 -> ++ io:format("Starting ~w...\n",[Browser]), ++ os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url); ++ B when B==firefox; B==mozilla -> ++ io:format("Sending URL to ~w...",[Browser]), ++ BStr = atom_to_list(Browser), ++ SendCmd = BStr ++ " -raise -remote \'openUrl(" ++ ++ Url ++ ")\'", ++ Port = open_port({spawn,SendCmd},[exit_status]), ++ receive ++ {Port,{exit_status,0}} -> ++ io:format("done\n"), ++ ok; ++ {Port,{exit_status,_Error}} -> ++ io:format(" not running, starting ~w...\n", ++ [Browser]), ++ os:cmd(BStr ++ " " ++ Url), ++ ok ++ after ?SEND_URL_TIMEOUT -> ++ io:format(" failed, starting ~w...\n",[Browser]), ++ erlang:port_close(Port), ++ os:cmd(BStr ++ " " ++ Url) ++ end; ++ _ -> ++ io:format("Starting ~w...\n",[Browser]), ++ os:cmd(atom_to_list(Browser) ++ " " ++ Url) ++ end, ++ ok; ++ false -> ++ stop(), ++ io:format("\n{error,{unknown_app,~p}}\n",[App]), ++ halt() ++ end. ++ ++usage() -> ++ io:format("Starting webtool...\n"), ++ start(), ++ Apps = lists:map(fun({A,_}) -> A end,get_applications()), ++ io:format( ++ "\nUsage: start_webtool application [ browser ]\n" ++ "\nAvailable applications are: ~p\n" ++ "Default browser is \'iexplore\' (Internet Explorer) on Windows " ++ "or else \'firefox\'\n", ++ [Apps]), ++ stop(). ++ ++ ++get_applications() -> ++ gen_server:call(ct_web_tool,get_applications). ++ ++get_port() -> ++ gen_server:call(ct_web_tool,get_port). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Api functions to the genserver. %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------------------------------------- ++% ++%---------------------------------------------------------------------- ++ ++start()-> ++ start(standard_path,standard_data). ++ ++start(Path,standard_data)-> ++ case get_standard_data() of ++ {error,Reason} -> ++ {error,Reason}; ++ Data -> ++ start(Path,Data) ++ end; ++ ++start(standard_path,Data)-> ++ Path=get_path(), ++ start(Path,Data); ++ ++start(Path,Port) when is_integer(Port)-> ++ Data = get_standard_data(Port), ++ start(Path,Data); ++ ++start(Path,Data0)-> ++ Data = Data0 ++ rest_of_standard_data(), ++ gen_server:start({local,ct_web_tool},ct_webtool,{Path,Data},[]). ++ ++stop()-> ++ gen_server:call(ct_web_tool,stoppit). ++ ++%---------------------------------------------------------------------- ++%Web Api functions called by the web ++%---------------------------------------------------------------------- ++started_tools(Env,Input)-> ++ gen_server:call(ct_web_tool,{started_tools,Env,Input}). ++ ++toolbar(Env,Input)-> ++ gen_server:call(ct_web_tool,{toolbar,Env,Input}). ++ ++start_tools(Env,Input)-> ++ gen_server:call(ct_web_tool,{start_tools,Env,Input}). ++ ++stop_tools(Env,Input)-> ++ gen_server:call(ct_web_tool,{stop_tools,Env,Input}). ++%---------------------------------------------------------------------- ++%Support API for other tools ++%---------------------------------------------------------------------- ++ ++is_localhost()-> ++ gen_server:call(ct_web_tool,is_localhost). ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%%The gen_server callback functions that builds the webbpages %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++handle_call(get_applications,_,State)-> ++ MS = ets:fun2ms(fun({Tool,{web_data,{_,Start}}}) -> {Tool,Start} end), ++ Tools = ets:select(State#state.app_data,MS), ++ {reply,Tools,State}; ++ ++handle_call(get_port,_,State)-> ++ {value,{port,Port}}=lists:keysearch(port,1,State#state.web_data), ++ {reply,Port,State}; ++ ++handle_call({started_tools,_Env,_Input},_,State)-> ++ {reply,started_tools_page(State),State}; ++ ++handle_call({toolbar,_Env,_Input},_,State)-> ++ {reply,toolbar(),State}; ++ ++handle_call({start_tools,Env,Input},_,State)-> ++ {NewState,Page}=start_tools_page(Env,Input,State), ++ {reply,Page,NewState}; ++ ++handle_call({stop_tools,Env,Input},_,State)-> ++ {NewState,Page}=stop_tools_page(Env,Input,State), ++ {reply,Page,NewState}; ++ ++handle_call(stoppit,_From,Data)-> ++ {stop,normal,ok,Data}; ++ ++handle_call(is_localhost,_From,Data)-> ++ Result=case proplists:get_value(bind_address, Data#state.web_data) of ++ ?DEFAULT_ADDR -> ++ true; ++ _IpNumber -> ++ false ++ end, ++ {reply,Result,Data}. ++ ++ ++handle_info(_Message,State)-> ++ {noreply,State}. ++ ++handle_cast(_Request,State)-> ++ {noreply,State}. ++ ++code_change(_,State,_)-> ++ {ok,State}. ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++% ++% The other functions needed by the gen_server behaviour ++% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------------------------------------- ++% Start the gen_server ++%---------------------------------------------------------------------- ++init({Path,Config})-> ++ case filelib:is_dir(Path) of ++ true -> ++ {ok, Table} = get_tool_files_data(), ++ insert_app(?WEBTOOL_ALIAS, Table), ++ case ct_webtool_sup:start_link() of ++ {ok, Pid} -> ++ case start_webserver(Table, Path, Config) of ++ {ok, _} -> ++ print_url(Config), ++ {ok,#state{priv_dir=Path, ++ app_data=Table, ++ supvis=Pid, ++ web_data=Config}}; ++ {error, Error} -> ++ {stop, {error, Error}} ++ end; ++ Error -> ++ {stop,Error} ++ end; ++ false -> ++ {stop, {error, error_dir}} ++ end. ++ ++terminate(_Reason,Data)-> ++ %%shut down the webbserver ++ shutdown_server(Data), ++ %%Shutdown the different tools that are started with application:start ++ shutdown_apps(Data), ++ %%Shutdown the supervisor and its children will die ++ shutdown_supervisor(Data), ++ ok. ++ ++print_url(ConfigData)-> ++ Server=proplists:get_value(server_name,ConfigData,"undefined"), ++ Port=proplists:get_value(port,ConfigData,"undefined"), ++ {A,B,C,D}=proplists:get_value(bind_address,ConfigData,"undefined"), ++ io:format("WebTool is available at http://~s:~w/~n",[Server,Port]), ++ io:format("Or http://~w.~w.~w.~w:~w/~n",[A,B,C,D,Port]). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++% ++% begin build the pages ++% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++%The page that shows the started tools ++%---------------------------------------------------------------------- ++started_tools_page(State)-> ++ [?HEADER,?HTML_HEADER,started_tools(State),?HTML_END]. ++ ++toolbar()-> ++ [?HEADER,?HTML_HEADER,toolbar_page(),?HTML_END]. ++ ++ ++start_tools_page(_Env,Input,State)-> ++ %%io:format("~n======= ~n ~p ~n============~n",[Input]), ++ case get_tools(Input) of ++ {tools,Tools}-> ++ %%io:format("~n======= ~n ~p ~n============~n",[Tools]), ++ {ok,NewState}=handle_apps(Tools,State,start), ++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(), ++ show_unstarted_apps(NewState),?HTML_END]}; ++ _ -> ++ {State,[?HEADER,?HTML_HEADER,show_unstarted_apps(State),?HTML_END]} ++ end. ++ ++stop_tools_page(_Env,Input,State)-> ++ case get_tools(Input) of ++ {tools,Tools}-> ++ {ok,NewState}=handle_apps(Tools,State,stop), ++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(), ++ show_started_apps(NewState),?HTML_END]}; ++ _ -> ++ {State,[?HEADER,?HTML_HEADER,show_started_apps(State),?HTML_END]} ++ end. ++ ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%% Functions that start and config the webserver ++%% 1. Collect the config data ++%% 2. Start webserver ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++% Start the webserver ++%---------------------------------------------------------------------- ++start_webserver(Data,Path,Config)-> ++ case get_conf_data(Data,Path,Config) of ++ {ok,Conf_data}-> ++ %%io:format("Conf_data: ~p~n",[Conf_data]), ++ start_server(Conf_data); ++ {error,Error} -> ++ {error,{error_server_conf_file,Error}} ++ end. ++ ++start_server(Conf_data)-> ++ case inets:start(httpd, Conf_data, stand_alone) of ++ {ok,Pid}-> ++ {ok,Pid}; ++ Error-> ++ {error,{server_error,Error}} ++ end. ++ ++%---------------------------------------------------------------------- ++% Create config data for the webserver ++%---------------------------------------------------------------------- ++get_conf_data(Data,Path,Config)-> ++ Aliases=get_aliases(Data), ++ ServerRoot = filename:join([Path,"root"]), ++ MimeTypesFile = filename:join([ServerRoot,"conf","mime.types"]), ++ case httpd_conf:load_mime_types(MimeTypesFile) of ++ {ok,MimeTypes} -> ++ Config1 = Config ++ Aliases, ++ Config2 = [{server_root,ServerRoot}, ++ {document_root,filename:join([Path,"root/doc"])}, ++ {mime_types,MimeTypes} | ++ Config1], ++ {ok,Config2}; ++ Error -> ++ Error ++ end. ++ ++%---------------------------------------------------------------------- ++% Control the path for *.tools files ++%---------------------------------------------------------------------- ++get_tool_files_data()-> ++ Tools=get_tools1(code:get_path()), ++ %%io:format("Data : ~p ~n",[Tools]), ++ get_file_content(Tools). ++ ++%---------------------------------------------------------------------- ++%Control that the data in the file really is erlang terms ++%---------------------------------------------------------------------- ++get_file_content(Tools)-> ++ Get_data=fun({tool,ToolData}) -> ++ %%io:format("Data : ~p ~n",[ToolData]), ++ case proplists:get_value(config_func,ToolData) of ++ {M,F,A}-> ++ case catch apply(M,F,A) of ++ {'EXIT',_} -> ++ bad_data; ++ Data when is_tuple(Data) -> ++ Data; ++ _-> ++ bad_data ++ end; ++ _ -> ++ bad_data ++ end ++ end, ++ insert_file_content([X ||X<-lists:map(Get_data,Tools),X/=bad_data]). ++ ++%---------------------------------------------------------------------- ++%Insert the data from the file in to the ets:table ++%---------------------------------------------------------------------- ++insert_file_content(Content)-> ++ Table=ets:new(app_data,[bag]), ++ lists:foreach(fun(X)-> ++ insert_app(X,Table) ++ end,Content), ++ {ok,Table}. ++ ++%---------------------------------------------------------------------- ++%Control that we got a a tuple of a atom and a list if so add the ++%elements in the list to the ets:table ++%---------------------------------------------------------------------- ++insert_app({Name,Key_val_list},Table) when is_list(Key_val_list),is_atom(Name)-> ++ %%io:format("ToolData: ~p: ~p~n",[Name,Key_val_list]), ++ lists:foreach( ++ fun({alias,{erl_alias,Alias,Mods}}) -> ++ Key_val = {erl_script_alias,{Alias,Mods}}, ++ %%io:format("Insert: ~p~n",[Key_val]), ++ ets:insert(Table,{Name,Key_val}); ++ (Key_val_pair)-> ++ %%io:format("Insert: ~p~n",[Key_val_pair]), ++ ets:insert(Table,{Name,Key_val_pair}) ++ end, ++ Key_val_list); ++ ++insert_app(_,_)-> ++ ok. ++ ++%---------------------------------------------------------------------- ++% Select all the alias in the database ++%---------------------------------------------------------------------- ++get_aliases(Data)-> ++ MS = ets:fun2ms(fun({_,{erl_script_alias,Alias}}) -> ++ {erl_script_alias,Alias}; ++ ({_,{alias,Alias}}) -> ++ {alias,Alias} ++ end), ++ ets:select(Data,MS). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Helper functions %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++get_standard_data(Port)-> ++ [ ++ {port,Port}, ++ {bind_address,?DEFAULT_ADDR}, ++ {server_name,"localhost"} ++ ]. ++ ++get_standard_data()-> ++ case get_free_port(?DEFAULT_PORT,?MAX_NUMBER_OF_WEBTOOLS) of ++ {error,Reason} -> {error,Reason}; ++ Port -> ++ [ ++ {port,Port}, ++ {bind_address,?DEFAULT_ADDR}, ++ {server_name,"localhost"} ++ ] ++ end. ++ ++get_free_port(_Port,0) -> ++ {error,no_free_port_found}; ++get_free_port(Port,N) -> ++ case gen_tcp:connect("localhost",Port,[]) of ++ {error, _Reason} -> ++ Port; ++ {ok,Sock} -> ++ gen_tcp:close(Sock), ++ get_free_port(Port+1,N-1) ++ end. ++ ++rest_of_standard_data() -> ++ [ ++ %% Do not allow the server to be crashed by malformed http-request ++ {max_header_siz,1024}, ++ {max_header_action,reply414}, ++ %% Go on a straight ip-socket ++ {com_type,ip_comm}, ++ %% Do not change the order of these module names!! ++ {modules,[mod_alias, ++ mod_auth, ++ mod_esi, ++ mod_actions, ++ mod_cgi, ++ mod_include, ++ mod_dir, ++ mod_get, ++ mod_head, ++ mod_log, ++ mod_disk_log]}, ++ {directory_index,["index.html"]}, ++ {default_type,"text/plain"} ++ ]. ++ ++ ++get_path()-> ++ code:priv_dir(webtool). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++% These functions is used to shutdown the webserver ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++% Shut down the webbserver ++%---------------------------------------------------------------------- ++shutdown_server(State)-> ++ {Addr,Port} = get_addr_and_port(State#state.web_data), ++ inets:stop(httpd,{Addr,Port}). ++ ++get_addr_and_port(Config) -> ++ Addr = proplists:get_value(bind_address,Config,?DEFAULT_ADDR), ++ Port = proplists:get_value(port,Config,?DEFAULT_PORT), ++ {Addr,Port}. ++ ++%---------------------------------------------------------------------- ++% Select all apps in the table and close them ++%---------------------------------------------------------------------- ++shutdown_apps(State)-> ++ Data=State#state.app_data, ++ MS = ets:fun2ms(fun({_,{start,HowToStart}}) -> HowToStart end), ++ lists:foreach(fun(Start_app)-> ++ stop_app(Start_app) ++ end, ++ ets:select(Data,MS)). ++ ++%---------------------------------------------------------------------- ++%Shuts down the supervisor that supervises tools that is not ++%Designed as applications ++%---------------------------------------------------------------------- ++shutdown_supervisor(State)-> ++ %io:format("~n==================~n"), ++ ct_webtool_sup:stop(State#state.supvis). ++ %io:format("~n==================~n"). ++ ++%---------------------------------------------------------------------- ++%close the individual apps. ++%---------------------------------------------------------------------- ++stop_app({child,_Real_name})-> ++ ok; ++ ++stop_app({app,Real_name})-> ++ application:stop(Real_name); ++ ++stop_app({func,_Start,Stop})-> ++ case Stop of ++ {M,F,A} -> ++ catch apply(M,F,A); ++ _NoStop -> ++ ok ++ end. ++ ++ ++ ++ ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%% These functions creates the webpage where the user can select if ++%% to start apps or to stop apps ++%% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++toolbar_page()-> ++ "<TABLE> ++ <TR> ++ <TD> ++ <B>Select Action</B> ++ </TD> ++ </TR> ++ <TR> ++ <TD> ++ <A HREF=\"./start_tools\" TARGET=right> Start Tools</A> ++ </TD> ++ </TR> ++ <TR> ++ <TD> ++ <A HREF=\"./stop_tools\" TARGET=right> Stop Tools</A> ++ </TD> ++ </TR> ++ </TABLE>". ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%% These functions creates the webbpage that shows the started apps ++%% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++% started_tools(State)->String (html table) ++% State is a record of type state ++%---------------------------------------------------------------------- ++started_tools(State)-> ++ Names=get_started_apps(State#state.app_data,State#state.started), ++ "<TABLE BORDER=1 WIDTH=100%> ++ "++ make_rows(Names,[],0) ++" ++ </TABLE>". ++%---------------------------------------------------------------------- ++%get_started_apps(Data,Started)-> [{web_name,link}] ++%selects the started apps from the ets table of apps. ++%---------------------------------------------------------------------- ++ ++get_started_apps(Data,Started)-> ++ SelectData=fun({Name,Link}) -> ++ {Name,Link} ++ end, ++ MS = lists:map(fun(A) -> {{A,{web_data,'$1'}},[],['$1']} end,Started), ++ ++ [{"WebTool","/tool_management.html"} | ++ [SelectData(X) || X <- ets:select(Data,MS)]]. ++ ++%---------------------------------------------------------------------- ++% make_rows(List,Result,Fields)-> String (The rows of a htmltable ++% List a list of tupler discibed above ++% Result an accumulator for the result ++% Field, counter that counts the number of cols in each row. ++%---------------------------------------------------------------------- ++make_rows([],Result,Fields)-> ++ Result ++ fill_out(Fields); ++make_rows([Data|Paths],Result,Field)when Field==0-> ++ make_rows(Paths,Result ++ "<TR>" ++ make_field(Data),Field+1); ++ ++make_rows([Path|Paths],Result,Field)when Field==4-> ++ make_rows(Paths,Result ++ make_field(Path) ++ "</TR>",0); ++ ++make_rows([Path|Paths],Result,Field)-> ++ make_rows(Paths,Result ++ make_field(Path),Field+1). ++ ++%---------------------------------------------------------------------- ++% make_fields(Path)-> String that is a field i a html table ++% Path is a name url tuple {Name,url} ++%---------------------------------------------------------------------- ++make_field(Path)-> ++ "<TD WIDTH=20%>" ++ get_name(Path) ++ "</TD>". ++ ++ ++%---------------------------------------------------------------------- ++%get_name({Nae,Url})->String that represents a <A> tag in html. ++%---------------------------------------------------------------------- ++get_name({Name,Url})-> ++ "<A HREF=\"" ++ Url ++ "\" TARGET=app_frame>" ++ Name ++ "</A>". ++ ++ ++%---------------------------------------------------------------------- ++% fill_out(Nr)-> String, that represent Nr fields in a html-table. ++%---------------------------------------------------------------------- ++fill_out(Nr)when Nr==0-> ++ []; ++fill_out(Nr)when Nr==4-> ++ "<TD WIDTH=\"20%\" > </TD></TR>"; ++ ++fill_out(Nr)-> ++ "<TD WIDTH=\"20%\"> </TD>" ++ fill_out(Nr+1). ++ ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%%These functions starts applicatons and builds the page showing tools ++%%to start ++%% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------------------------------------- ++%Controls whether the user selected a tool to start ++%---------------------------------------------------------------------- ++get_tools(Input)-> ++ case httpd:parse_query(Input) of ++ []-> ++ no_tools; ++ Tools-> ++ FormatData=fun({_Name,Data}) -> list_to_atom(Data) end, ++ SelectData= ++ fun({Name,_Data}) -> string:equal(Name,"app") end, ++ {tools,[FormatData(X)||X<-Tools,SelectData(X)]} ++ end. ++ ++%---------------------------------------------------------------------- ++% Selects the data to start the applications the user has ordered ++% starting of ++%---------------------------------------------------------------------- ++handle_apps([],State,_Cmd)-> ++ {ok,State}; ++ ++handle_apps([Tool|Tools],State,Cmd)-> ++ case ets:match_object(State#state.app_data,{Tool,{start,'_'}}) of ++ []-> ++ Started = case Cmd of ++ start -> ++ [Tool|State#state.started]; ++ stop -> ++ lists:delete(Tool,State#state.started) ++ end, ++ {ok,#state{priv_dir=State#state.priv_dir, ++ app_data=State#state.app_data, ++ supvis=State#state.supvis, ++ web_data=State#state.web_data, ++ started=Started}}; ++ ToStart -> ++ case handle_apps2(ToStart,State,Cmd) of ++ {ok,NewState}-> ++ handle_apps(Tools,NewState,Cmd); ++ _-> ++ handle_apps(Tools,State,Cmd) ++ end ++ end. ++ ++%---------------------------------------------------------------------- ++%execute every start or stop data about a tool. ++%---------------------------------------------------------------------- ++handle_apps2([{Name,Start_data}],State,Cmd)-> ++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd) of ++ ok-> ++ Started = case Cmd of ++ start -> ++ [Name|State#state.started]; ++ stop -> ++ ++ lists:delete(Name,State#state.started) ++ end, ++ {ok,#state{priv_dir=State#state.priv_dir, ++ app_data=State#state.app_data, ++ supvis=State#state.supvis, ++ web_data=State#state.web_data, ++ started=Started}}; ++ _-> ++ error ++ end; ++ ++handle_apps2([{Name,Start_data}|Rest],State,Cmd)-> ++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd)of ++ ok-> ++ handle_apps2(Rest,State,Cmd); ++ _-> ++ error ++ end. ++ ++ ++%---------------------------------------------------------------------- ++% Handle start and stop of applications ++%---------------------------------------------------------------------- ++ ++handle_app({Name,{start,{func,Start,Stop}}},Data,_Pid,Cmd)-> ++ Action = case Cmd of ++ start -> ++ Start; ++ _ -> ++ Stop ++ end, ++ case Action of ++ {M,F,A} -> ++ case catch apply(M,F,A) of ++ {'EXIT',_} = Exit-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "~w:~w(~s) ->\n" ++ "~p\n\n", ++ [?LINE,Name,M,F,format_args(A),Exit]), ++ ets:delete(Data,Name); ++ _OK-> ++ ok ++ end; ++ _NoStart -> ++ ok ++ end; ++ ++ ++handle_app({Name,{start,{child,ChildSpec}}},Data,Pid,Cmd)-> ++ case Cmd of ++ start -> ++ case catch supervisor:start_child(Pid,ChildSpec) of ++ {ok,_}-> ++ ok; ++ {ok,_,_}-> ++ ok; ++ {error,Reason}-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "supervisor:start_child(~p,~p) ->\n" ++ "~p\n\n", ++ [?LINE,Name,Pid,ChildSpec,{error,Reason}]), ++ ets:delete(Data,Name); ++ Error -> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "supervisor:start_child(~p,~p) ->\n" ++ "~p\n\n", ++ [?LINE,Name,Pid,ChildSpec,Error]), ++ ets:delete(Data,Name) ++ end; ++ stop -> ++ case catch supervisor:terminate_child(websup,element(1,ChildSpec)) of ++ ok -> ++ supervisor:delete_child(websup,element(1,ChildSpec)); ++ _ -> ++ error ++ end ++ end; ++ ++ ++ ++handle_app({Name,{start,{app,Real_name}}},Data,_Pid,Cmd)-> ++ case Cmd of ++ start -> ++ case application:start(Real_name,temporary) of ++ ok-> ++ io:write(Name), ++ ok; ++ {error,{already_started,_}}-> ++ %% Remove it from the database so we dont start ++ %% anything already started ++ ets:match_delete(Data,{Name,{start,{app,Real_name}}}), ++ ok; ++ {error,_Reason}=Error-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "application:start(~p,~p) ->\n" ++ "~p\n\n", ++ [?LINE,Name,Real_name,temporary,Error]), ++ ets:delete(Data,Name) ++ end; ++ ++ stop -> ++ application:stop(Real_name) ++ end; ++ ++%---------------------------------------------------------------------- ++% If the data is incorrect delete the app ++%---------------------------------------------------------------------- ++handle_app({Name,Incorrect},Data,_Pid,Cmd)-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not ~w application \'~p\'\n\n" ++ "Incorrect data: ~p\n\n", ++ [?LINE,Cmd,Name,Incorrect]), ++ ets:delete(Data,Name). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% this functions creates the page that shows the unstarted tools %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++reload_started_apps()-> ++ "<script> ++ function reloadCompiledList() ++ { ++ parent.parent.top1.document.location.href=\"/webtool/webtool/started_tools\"; ++ } ++ </script>". ++ ++show_unstarted_apps(State)-> ++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0> ++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\"> ++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/start_tools\" > ++ <TABLE BORDER=1 WIDTH=60%> ++ <TR BGCOLOR=\"#8899AA\"> ++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Available Tools<FONT></TD> ++ </TR> ++ <TR> ++ <TD WIDTH=50%> ++ <TABLE BORDER=0> ++ "++ list_available_apps(State)++" ++ <TR><TD COLSPAN=2> </TD></TR> ++ <TR> ++ <TD COLSPAN=2 ALIGN=\"center\"> ++ <INPUT TYPE=submit VALUE=\"Start\"> ++ </TD> ++ </TR> ++ </TABLE> ++ </TD> ++ <TD> ++ To Start a Tool: ++ <UL> ++ <LI>Select the ++ checkbox for each tool to ++ start.</LI> ++ <LI>Click on the ++ button marked <EM>Start</EM>.</LI></UL> ++ </TD> ++ </TR> ++ </TABLE> ++ </FORM> ++ </TD></TR> ++ <TR><TD> </TD></TR> ++ </TABLE>". ++ ++ ++ ++list_available_apps(State)-> ++ MS = ets:fun2ms(fun({Tool,{web_data,{Name,_}}}) -> {Tool,Name} end), ++ Unstarted_apps= ++ lists:filter( ++ fun({Tool,_})-> ++ false==lists:member(Tool,State#state.started) ++ end, ++ ets:select(State#state.app_data,MS)), ++ case Unstarted_apps of ++ []-> ++ "<TR><TD>All tools are started</TD></TR>"; ++ _-> ++ list_apps(Unstarted_apps) ++ end. ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% these functions creates the page that shows the started apps %% ++%% the user can select to shutdown %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++show_started_apps(State)-> ++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0> ++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\"> ++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/stop_tools\" > ++ <TABLE BORDER=1 WIDTH=60%> ++ <TR BGCOLOR=\"#8899AA\"> ++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Started Tools<FONT></TD> ++ </TR> ++ <TR> ++ <TD WIDTH=50%> ++ <TABLE BORDER=0> ++ "++ list_started_apps(State)++" ++ <TR><TD COLSPAN=2> </TD></TR> ++ <TR> ++ <TD COLSPAN=2 ALIGN=\"center\"> ++ <INPUT TYPE=submit VALUE=\"Stop\"> ++ </TD> ++ </TR> ++ </TABLE> ++ </TD> ++ <TD> ++ Stop a Tool: ++ <UL> ++ <LI>Select the ++ checkbox for each tool to ++ stop.</LI> ++ <LI>Click on the ++ button marked <EM>Stop</EM>.</LI></UL> ++ </TD> ++ </TR> ++ </TABLE> ++ </FORM> ++ </TD></TR> ++ <TR><TD> </TD></TR> ++ </TABLE>". ++ ++list_started_apps(State)-> ++ MS = lists:map(fun(A) -> {{A,{web_data,{'$1','_'}}},[],[{{A,'$1'}}]} end, ++ State#state.started), ++ Started_apps= ets:select(State#state.app_data,MS), ++ case Started_apps of ++ []-> ++ "<TR><TD>No tool is started yet.</TD></TR>"; ++ _-> ++ list_apps(Started_apps) ++ end. ++ ++ ++list_apps(Apps) -> ++ lists:map(fun({Tool,Name})-> ++ "<TR><TD> ++ <INPUT TYPE=\"checkbox\" NAME=\"app\" VALUE=\"" ++ ++ atom_to_list(Tool) ++ "\"> ++ " ++ Name ++ " ++ </TD></TR>" ++ end, ++ Apps). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Collecting the data from the *.tool files %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------- ++% get_tools(Dirs) => [{M,F,A},{M,F,A}...{M,F,A}] ++% Dirs - [string()] Directory names ++% Calls get_tools2/2 recursively for a number of directories ++% to retireve the configuration data for the web based tools. ++%---------------------------------------- ++get_tools1(Dirs)-> ++ get_tools1(Dirs,[]). ++ ++get_tools1([Dir|Rest],Data) when is_list(Dir) -> ++ Tools=case filename:basename(Dir) of ++ %% Dir is an 'ebin' directory, check in '../priv' as well ++ "ebin" -> ++ [get_tools2(filename:join(filename:dirname(Dir),"priv")) | ++ get_tools2(Dir)]; ++ _ -> ++ get_tools2(Dir) ++ end, ++ get_tools1(Rest,[Tools|Data]); ++ ++get_tools1([],Data) -> ++ lists:flatten(Data). ++ ++%---------------------------------------- ++% get_tools2(Directory) => DataList ++% DataList : [WebTuple]|[] ++% WebTuple: {tool,[{web,M,F,A}]} ++% ++%---------------------------------------- ++get_tools2(Dir)-> ++ get_tools2(tool_files(Dir),[]). ++ ++get_tools2([ToolFile|Rest],Data) -> ++ case get_tools3(ToolFile) of ++ {tool,WebData} -> ++ get_tools2(Rest,[{tool,WebData}|Data]); ++ {error,_Reason} -> ++ get_tools2(Rest,Data); ++ nodata -> ++ get_tools2(Rest,Data) ++ end; ++ ++get_tools2([],Data) -> ++ Data. ++ ++%---------------------------------------- ++% get_tools3(ToolFile) => {ok,Tool}|{error,Reason}|nodata ++% Tool: {tool,[KeyValTuple]} ++% ToolFile - string() A .tool file ++% Now we have the file get the data and sort it out ++%---------------------------------------- ++get_tools3(ToolFile) -> ++ case file:consult(ToolFile) of ++ {error,open} -> ++ {error,nofile}; ++ {error,read} -> ++ {error,format}; ++ {ok,[{version,"1.2"},ToolInfo]} when is_list(ToolInfo)-> ++ webdata(ToolInfo); ++ {ok,[{version,_Vsn},_Info]} -> ++ {error,old_version}; ++ {ok,_Other} -> ++ {error,format} ++ end. ++ ++ ++%---------------------------------------------------------------------- ++% webdata(TupleList)-> ToolTuple| nodata ++% ToolTuple: {tool,[{config_func,{M,F,A}}]} ++% ++% There are a little unneccesary work in this format but it is extendable ++%---------------------------------------------------------------------- ++webdata(TupleList)-> ++ case proplists:get_value(config_func,TupleList,nodata) of ++ {M,F,A} -> ++ {tool,[{config_func,{M,F,A}}]}; ++ _ -> ++ nodata ++ end. ++ ++ ++%============================================================================= ++% Functions for getting *.tool configuration files ++%============================================================================= ++ ++%---------------------------------------- ++% tool_files(Dir) => ToolFiles ++% Dir - string() Directory name ++% ToolFiles - [string()] ++% Return the list of all files in Dir ending with .tool (appended to Dir) ++%---------------------------------------- ++tool_files(Dir) -> ++ case file:list_dir(Dir) of ++ {ok,Files} -> ++ filter_tool_files(Dir,Files); ++ {error,_Reason} -> ++ [] ++ end. ++ ++%---------------------------------------- ++% filter_tool_files(Dir,Files) => ToolFiles ++% Dir - string() Directory name ++% Files, ToolFiles - [string()] File names ++% Filters out the files in Files ending with .tool and append them to Dir ++%---------------------------------------- ++filter_tool_files(_Dir,[]) -> ++ []; ++filter_tool_files(Dir,[File|Rest]) -> ++ case filename:extension(File) of ++ ".tool" -> ++ [filename:join(Dir,File)|filter_tool_files(Dir,Rest)]; ++ _ -> ++ filter_tool_files(Dir,Rest) ++ end. ++ ++ ++%%%----------------------------------------------------------------- ++%%% format functions ++ffunc({M,F,A}) when is_list(A) -> ++ io_lib:format("~w:~w(~s)\n",[M,F,format_args(A)]); ++ffunc({M,F,A}) when is_integer(A) -> ++ io_lib:format("~w:~w/~w\n",[M,F,A]). ++ ++format_args([]) -> ++ ""; ++format_args(Args) -> ++ Str = lists:append(["~p"|lists:duplicate(length(Args)-1,",~p")]), ++ io_lib:format(Str,Args). +diff --git lib/common_test/src/ct_webtool_sup.erl lib/common_test/src/ct_webtool_sup.erl +new file mode 100644 +index 0000000..1d612a2 +--- /dev/null ++++ lib/common_test/src/ct_webtool_sup.erl +@@ -0,0 +1,74 @@ ++%% ++%% %CopyrightBegin% ++%% ++%% Copyright Ericsson AB 2001-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(ct_webtool_sup). ++ ++-behaviour(supervisor). ++ ++%% External exports ++-export([start_link/0,stop/1]). ++ ++%% supervisor callbacks ++-export([init/1]). ++ ++%%%---------------------------------------------------------------------- ++%%% API ++%%%---------------------------------------------------------------------- ++start_link() -> ++ supervisor:start_link({local,ct_websup},ct_webtool_sup, []). ++ ++stop(Pid)-> ++ exit(Pid,normal). ++%%%---------------------------------------------------------------------- ++%%% Callback functions from supervisor ++%%%---------------------------------------------------------------------- ++ ++%%---------------------------------------------------------------------- ++%% Func: init/1 ++%% Returns: {ok, {SupFlags, [ChildSpec]}} | ++%% ignore | ++%% {error, Reason} ++%%---------------------------------------------------------------------- ++init(_StartArgs) -> ++ %%Child1 = ++ %%Child2 ={webcover_backend,{webcover_backend,start_link,[]},permanent,2000,worker,[webcover_backend]}, ++ %%{ok,{{simple_one_for_one,5,10},[Child1]}}. ++ {ok,{{one_for_one,100,10},[]}}. ++ ++%%%---------------------------------------------------------------------- ++%%% Internal functions ++%%%---------------------------------------------------------------------- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git lib/common_test/src/vts.erl lib/common_test/src/vts.erl +index b340c6f..ab13e7d 100644 +--- lib/common_test/src/vts.erl ++++ lib/common_test/src/vts.erl +@@ -63,21 +63,21 @@ + %%%----------------------------------------------------------------- + %%% User API + start() -> +- webtool:start(), +- webtool:start_tools([],"app=vts"). ++ ct_webtool:start(), ++ ct_webtool:start_tools([],"app=vts"). + + init_data(ConfigFiles,EvHandlers,LogDir,LogOpts,Tests) -> + call({init_data,ConfigFiles,EvHandlers,LogDir,LogOpts,Tests}). + + stop() -> +- webtool:stop_tools([],"app=vts"), +- webtool:stop(). ++ ct_webtool:stop_tools([],"app=vts"), ++ ct_webtool:stop(). + + report(What,Data) -> + call({report,What,Data}). + + %%%----------------------------------------------------------------- +-%%% Return config data used by webtool ++%%% Return config data used by ct_webtool + config_data() -> + {ok,LogDir} = + case lists:keysearch(logdir,1,init:get_arguments()) of +diff --git lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl +index a145d85..d01211b 100644 +--- lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl ++++ lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl +@@ -164,7 +164,7 @@ hello_from_server_first(Config) -> + {ok,Client} = ct_netconfc:only_open(?DEFAULT_SSH_OPTS(DataDir)), + ct:sleep(500), + ?NS:expect(hello), +- ?ok = ct_netconfc:hello(Client), ++ ?ok = ct_netconfc:hello(Client, [{capability, ["urn:com:ericsson:ebase:1.1.0"]}], infinity), + ?NS:expect_do_reply('close-session',close,ok), + ?ok = ct_netconfc:close_session(Client), + ok. +@@ -490,13 +490,16 @@ action(Config) -> + Data = [{myactionreturn,[{xmlns,"myns"}],["value"]}], + %% test either to receive {data,Data} or {ok,Data}, + %% both need to be handled +- {Reply,RetVal} = case element(3, now()) rem 2 of +- 0 -> {{data,Data},{ok,Data}}; +- 1 -> {{ok,Data},ok} +- end, +- ct:log("Client will receive {~w,Data}", [element(1,Reply)]), +- ?NS:expect_reply(action,Reply), +- RetVal = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}), ++ ct:log("Client will receive {~w,~p}", [data,Data]), ++ ct:log("Expecting ~p", [{ok, Data}]), ++ ?NS:expect_reply(action,{data, Data}), ++ {ok, Data} = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}), ++ ++ ct:log("Client will receive {~w,~p}", [ok,Data]), ++ ct:log("Expecting ~p", [ok]), ++ ?NS:expect_reply(action,{ok, Data}), ++ ok = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}), ++ + ?NS:expect_do_reply('close-session',close,ok), + ?ok = ct_netconfc:close_session(Client), + ok. +diff --git lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl +index 1d3f591..9dc9095 100644 +--- lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl ++++ lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl +@@ -40,6 +40,7 @@ all() -> + expect, + expect_repeat, + expect_sequence, ++ expect_wait_until_prompt, + expect_error_prompt, + expect_error_timeout1, + expect_error_timeout2, +@@ -81,6 +82,8 @@ end_per_group(_GroupName, Config) -> + expect(_) -> + {ok, Handle} = ct_telnet:open(telnet_server_conn1), + ok = ct_telnet:send(Handle, "echo ayt"), ++ {ok,["ayt"]} = ct_telnet:expect(Handle, "ayt"), ++ ok = ct_telnet:send(Handle, "echo ayt"), + {ok,["ayt"]} = ct_telnet:expect(Handle, ["ayt"]), + ok = ct_telnet:close(Handle), + ok. +@@ -103,6 +106,21 @@ expect_sequence(_) -> + ok = ct_telnet:close(Handle), + ok. + ++%% Check that expect can wait for delayed prompt ++expect_wait_until_prompt(_) -> ++ {ok, Handle} = ct_telnet:open(telnet_server_conn1), ++ Timeouts = [{idle_timeout,5000},{total_timeout,7000}], ++ ++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 xxx"), ++ {ok,["xxx"]} = ++ ct_telnet:expect(Handle, "xxx", ++ [wait_for_prompt|Timeouts]), ++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 yyy zzz"), ++ {ok,[["yyy"],["zzz"]]} = ++ ct_telnet:expect(Handle, ["yyy","zzz"], ++ [{wait_for_prompt,"> "}|Timeouts]), ++ ok. ++ + %% Check that expect returns when a prompt is found, even if pattern + %% is not matched. + expect_error_prompt(_) -> +diff --git lib/common_test/test/telnet_server.erl lib/common_test/test/telnet_server.erl +index 11959c3..2db5a9b 100644 +--- lib/common_test/test/telnet_server.erl ++++ lib/common_test/test/telnet_server.erl +@@ -242,6 +242,12 @@ do_handle_data("echo_loop " ++ Data,State) -> + ReturnData = string:join(Lines,"\n"), + send_loop(list_to_integer(TStr),ReturnData,State), + {ok,State}; ++do_handle_data("echo_delayed_prompt "++Data,State) -> ++ [MsStr|EchoData] = string:tokens(Data, " "), ++ send(string:join(EchoData,"\n"),State), ++ ct:sleep(list_to_integer(MsStr)), ++ send("\r\n> ",State), ++ {ok,State}; + do_handle_data("disconnect_after " ++WaitStr,State) -> + Wait = list_to_integer(string:strip(WaitStr,right,$\n)), + dbg("Server will close connection in ~w ms...", [Wait]), +diff --git lib/common_test/vsn.mk lib/common_test/vsn.mk +index d654a8a..e2d9217 100644 +--- lib/common_test/vsn.mk ++++ lib/common_test/vsn.mk +@@ -1 +1 @@ +-COMMON_TEST_VSN = 1.10 ++COMMON_TEST_VSN = 1.10.1 +diff --git lib/diameter/doc/src/diameter.xml lib/diameter/doc/src/diameter.xml +index 6e41b01..ea175a5 100644 +--- lib/diameter/doc/src/diameter.xml ++++ lib/diameter/doc/src/diameter.xml +@@ -1820,7 +1820,8 @@ The information presented here is as in the <c>connect</c> case except + that the client connections are grouped under an <c>accept</c> tuple.</p> + + <p> +-Whether or not the &transport_opt; <c>pool_size</c> affects the format ++Whether or not the &transport_opt; <c>pool_size</c> has been ++configured affects the format + of the listing in the case of a connecting transport, since a value + greater than 1 implies multiple transport processes for the same + <c>&transport_ref;</c>, as in the listening case. +diff --git lib/diameter/doc/src/notes.xml lib/diameter/doc/src/notes.xml +index 479fab2..6931788 100644 +--- lib/diameter/doc/src/notes.xml ++++ lib/diameter/doc/src/notes.xml +@@ -42,6 +42,47 @@ first.</p> + + <!-- ===================================================================== --> + ++<section><title>diameter 1.9.1</title> ++ ++ <section><title>Known Bugs and Problems</title> ++ <list> ++ <item> ++ <p> ++ Don't leave extra bit in decoded AVP data.</p> ++ <p> ++ OTP-12074 in OTP 17.3 missed one case: a length error on ++ a trailing AVP unknown to the dictionary in question.</p> ++ <p> ++ Own Id: OTP-12642</p> ++ </item> ++ <item> ++ <p> ++ Don't confuse Result-Code and Experimental-Result</p> ++ <p> ++ The errors field of a decoded diameter_packet record was ++ populated with a Result-Code AVP when an ++ Experimental-Result containing a 3xxx Result-Code was ++ received in an answer not setting the E-bit. The correct ++ AVP is now extracted from the incoming message.</p> ++ <p> ++ Own Id: OTP-12654 Aux Id: seq12851 </p> ++ </item> ++ <item> ++ <p> ++ Don't count on unknown Application Id.</p> ++ <p> ++ OTP-11721 in OTP 17.1 missed the case of an Application ++ Id not agreeing with that of the dictionary in question, ++ causing counters to be accumulated on keys containing the ++ unknown id.</p> ++ <p> ++ Own Id: OTP-12701</p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>diameter 1.9</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git lib/diameter/include/diameter_gen.hrl lib/diameter/include/diameter_gen.hrl +index 0eef218..e8ffe7f 100644 +--- lib/diameter/include/diameter_gen.hrl ++++ lib/diameter/include/diameter_gen.hrl +@@ -445,7 +445,7 @@ reset(_, _) -> + %% undecoded. Note that the type field is 'undefined' in this case. + + decode_AVP(Name, Avp, {Avps, Acc}) -> +- {[Avp | Avps], pack_AVP(Name, Avp, Acc)}. ++ {[trim(Avp) | Avps], pack_AVP(Name, Avp, Acc)}. + + %% rc/1 + +diff --git lib/diameter/src/base/diameter_codec.erl lib/diameter/src/base/diameter_codec.erl +index 15a4c5e..bf2fe8e 100644 +--- lib/diameter/src/base/diameter_codec.erl ++++ lib/diameter/src/base/diameter_codec.erl +@@ -640,8 +640,12 @@ split_data(Bin, Len) -> + %% payload if this is a request. Do this (in cases that we + %% know the type) by inducing a decode failure and letting + %% the dictionary's decode (in diameter_gen) deal with it. +- %% Here we don't know type. If the type isn't known, then +- %% the decode just strips the extra bit. ++ %% ++ %% Note that the extra bit can only occur in the trailing ++ %% AVP of a message or Grouped AVP, since a faulty AVP ++ %% Length is otherwise indistinguishable from a correct ++ %% one here, since we don't know the types of the AVPs ++ %% being extracted. + {<<0:1, Bin/binary>>, <<>>} + end. + +@@ -690,8 +694,8 @@ pack_avp(#diameter_avp{code = undefined, data = B}) + Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>), + <<H/binary, Len:24, T/binary>>; + +-%% ... from a dictionary compiled against old code in diameter_gen ... + %% ... when ignoring errors in Failed-AVP ... ++%% ... during a relay encode ... + pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) -> + pack_avp(A#diameter_avp{data = B}); + +diff --git lib/diameter/src/base/diameter_traffic.erl lib/diameter/src/base/diameter_traffic.erl +index 538ebee..ffd2c0a 100644 +--- lib/diameter/src/base/diameter_traffic.erl ++++ lib/diameter/src/base/diameter_traffic.erl +@@ -980,8 +980,8 @@ answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps, + session_id(Code, Vid, Dict0, Avps) + when is_list(Avps) -> + try +- {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps), +- [{'Session-Id', [Dict0:avp(decode, D, 'Session-Id')]}] ++ #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps), ++ [{'Session-Id', [Dict0:avp(decode, Bin, 'Session-Id')]}] + catch + error: _ -> + [] +@@ -998,26 +998,17 @@ failed_avp(_, [] = No) -> + + %% find_avp/3 + +-find_avp(Code, Vid, Avps) +- when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) -> +- find(fun(A) -> is_avp(Code, Vid, A) end, Avps). ++%% Grouped ... ++find_avp(Code, VId, [[#diameter_avp{code = Code, vendor_id = VId} | _] = As ++ | _]) -> ++ As; + +-%% The final argument here could be a list of AVP's, depending on the case, +-%% but we're only searching at the top level. +-is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) -> +- true; +-is_avp(_, _, _) -> +- false. ++%% ... or not. ++find_avp(Code, VId, [#diameter_avp{code = Code, vendor_id = VId} = A | _]) -> ++ A; + +-find(_, []) -> +- false; +-find(Pred, [H|T]) -> +- case Pred(H) of +- true -> +- {value, H}; +- false -> +- find(Pred, T) +- end. ++find_avp(Code, VId, [_ | Avps]) -> ++ find_avp(Code, VId, Avps). + + %% 7. Error Handling + %% +@@ -1086,7 +1077,6 @@ incr_result(_, #diameter_packet{msg = undefined = No}, _, _) -> + incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) -> + #diameter_packet{header = #diameter_header{is_error = E} + = Hdr, +- msg = Msg, + errors = Es} + = Pkt, + +@@ -1096,13 +1086,13 @@ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) -> + recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict), + + %% Exit on a missing result code. +- T = rc_counter(Dict, Msg), ++ T = rc_counter(Dict, Dir, Pkt), + T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}), +- {Ctr, RC} = T, ++ {Ctr, RC, Avp} = T, + + %% Or on an inappropriate value. + is_result(RC, E, Dict0) +- orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}), ++ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, Avp}), + + incr(TPid, {Id, Dir, Ctr}), + Ctr. +@@ -1116,19 +1106,15 @@ msg_id(#diameter_packet{header = H}, Dict) -> + %% there are 2^32 (application ids) * 2^24 (command codes) = 2^56 + %% pairs for an attacker to choose from. + msg_id(Hdr, Dict) -> +- {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr), +- case Dict:msg_name(Code, 0 == R) of +- '' -> +- unknown(Dict:id(), R); +- _ -> +- Id ++ {Aid, Code, R} = Id = diameter_codec:msg_id(Hdr), ++ if Aid == ?APP_ID_RELAY -> ++ {relay, R}; ++ true -> ++ choose(Aid /= Dict:id() orelse '' == Dict:msg_name(Code, 0 == R), ++ unknown, ++ Id) + end. + +-unknown(?APP_ID_RELAY, R) -> +- {relay, R}; +-unknown(_, _) -> +- unknown. +- + %% No E-bit: can't be 3xxx. + is_result(RC, false, _Dict0) -> + RC < 3000 orelse 4000 =< RC; +@@ -1148,7 +1134,7 @@ is_result(RC, true, _) -> + incr(TPid, Counter) -> + diameter_stats:incr(Counter, TPid, 1). + +-%% rc_counter/2 ++%% rc_counter/3 + + %% RFC 3588, 7.6: + %% +@@ -1156,39 +1142,45 @@ incr(TPid, Counter) -> + %% applications MUST include either one Result-Code AVP or one + %% Experimental-Result AVP. + ++rc_counter(Dict, recv, #diameter_packet{header = H, avps = As}) -> ++ rc_counter(Dict, [H|As]); ++ ++rc_counter(Dict, _, #diameter_packet{msg = Msg}) -> ++ rc_counter(Dict, Msg). ++ + rc_counter(Dict, Msg) -> +- rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))). ++ rcc(get_result(Dict, Msg)). + +-rcc(Dict, Msg, undefined) -> +- rcc(get_avp_value(Dict, 'Experimental-Result', Msg)); +- +-rcc(_, _, N) ++rcc(#diameter_avp{name = 'Result-Code' = Name, value = N} = A) + when is_integer(N) -> +- {{'Result-Code', N}, N}. ++ {{Name, N}, N, A}; + +-%% Outgoing answers may be in any of the forms messages can be sent +-%% in. Incoming messages will be records. We're assuming here that the +-%% arity of the result code AVP's is 0 or 1. ++rcc(#diameter_avp{name = 'Result-Code' = Name, value = [N|_]} = A) ++ when is_integer(N) -> ++ {{Name, N}, N, A}; + +-rcc([{_,_,N} = T | _]) ++rcc(#diameter_avp{name = 'Experimental-Result', value = {_,_,N} = T} = A) + when is_integer(N) -> +- {T,N}; +-rcc({_,_,N} = T) ++ {T, N, A}; ++ ++rcc(#diameter_avp{name = 'Experimental-Result', value = [{_,_,N} = T|_]} = A) + when is_integer(N) -> +- {T,N}; ++ {T, N, A}; ++ + rcc(_) -> + false. + +-%% Extract the first good looking integer. There's no guarantee +-%% that what we're looking for has arity 1. +-int([N|_]) +- when is_integer(N) -> +- N; +-int(N) +- when is_integer(N) -> +- N; +-int(_) -> +- undefined. ++%% get_result/2 ++ ++get_result(Dict, Msg) -> ++ try ++ [throw(A) || N <- ['Result-Code', 'Experimental-Result'], ++ #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]] ++ of ++ [] -> false ++ catch ++ #diameter_avp{} = A -> A ++ end. + + x(T) -> + exit(T). +@@ -1528,10 +1520,10 @@ handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) -> + %% a missing AVP. If both are optional in the dictionary + %% then this isn't a decode error: just continue on. + answer(Pkt, SvcName, App, Req); +- exit: {invalid_error_bit, {_, _, _, RC}} -> ++ exit: {invalid_error_bit, {_, _, _, Avp}} -> + #diameter_packet{errors = Es} + = Pkt, +- E = {5004, #diameter_avp{name = 'Result-Code', value = RC}}, ++ E = {5004, Avp}, + answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req) + end. + +@@ -1868,7 +1860,7 @@ str([]) -> + str(T) -> + T. + +-%% get_avp_value/3 ++%% get_avp/3 + %% + %% Find an AVP in a message of one of three forms: + %% +@@ -1885,47 +1877,71 @@ str(T) -> + %% look for are in the common dictionary. This is required since the + %% relay dictionary doesn't inherit the common dictionary (which maybe + %% it should). +-get_avp_value(?RELAY, Name, Msg) -> +- get_avp_value(?BASE, Name, Msg); ++get_avp(?RELAY, Name, Msg) -> ++ get_avp(?BASE, Name, Msg); + +-%% Message sent as a header/avps list, probably a relay case but not +-%% necessarily. +-get_avp_value(Dict, Name, [#diameter_header{} | Avps]) -> ++%% Message as a header/avps list. ++get_avp(Dict, Name, [#diameter_header{} | Avps]) -> + try + {Code, _, VId} = Dict:avp_header(Name), +- [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) -> +- C /= Code orelse V /= VId +- end, +- Avps), +- avp_decode(Dict, Name, A) ++ find_avp(Code, VId, Avps) ++ of ++ A -> ++ avp_decode(Dict, Name, ungroup(A)) + catch + error: _ -> + undefined + end; + + %% Outgoing message as a name/values list. +-get_avp_value(_, Name, [_MsgName | Avps]) -> ++get_avp(_, Name, [_MsgName | Avps]) -> + case lists:keyfind(Name, 1, Avps) of + {_, V} -> +- V; ++ #diameter_avp{name = Name, value = V}; + _ -> + undefined + end; + + %% Message is typically a record but not necessarily. +-get_avp_value(Dict, Name, Rec) -> ++get_avp(Dict, Name, Rec) -> + try +- Dict:'#get-'(Name, Rec) ++ #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)} + catch + error:_ -> + undefined + end. + ++%% get_avp_value/3 ++ ++get_avp_value(Dict, Name, Msg) -> ++ case get_avp(Dict, Name, Msg) of ++ #diameter_avp{value = V} -> ++ V; ++ undefined = No -> ++ No ++ end. ++ ++%% ungroup/1 ++ ++ungroup([Avp|_]) -> ++ Avp; ++ungroup(Avp) -> ++ Avp. ++ ++%% avp_decode/3 ++ + avp_decode(Dict, Name, #diameter_avp{value = undefined, +- data = Bin}) -> +- Dict:avp(decode, Bin, Name); +-avp_decode(_, _, #diameter_avp{value = V}) -> +- V. ++ data = Bin} ++ = Avp) -> ++ try Dict:avp(decode, Bin, Name) of ++ V -> ++ Avp#diameter_avp{value = V} ++ catch ++ error:_ -> ++ Avp ++ end; ++avp_decode(_, _, #diameter_avp{} = Avp) -> ++ Avp. + + cb(#diameter_app{module = [_|_] = M}, F, A) -> + eval(M, F, A); +diff --git lib/diameter/src/diameter.appup.src lib/diameter/src/diameter.appup.src +index a54eb24..0ef0fd3 100644 +--- lib/diameter/src/diameter.appup.src ++++ lib/diameter/src/diameter.appup.src +@@ -35,32 +35,10 @@ + {"1.4.3", [{restart_application, diameter}]}, %% R16B02 + {"1.4.4", [{restart_application, diameter}]}, + {"1.5", [{restart_application, diameter}]}, %% R16B03 +- {"1.6", [{load_module, diameter_lib}, %% 17.0 +- {load_module, diameter_traffic}, +- {load_module, diameter_watchdog}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_service}, +- {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_gen_acct_rfc6733}, +- {load_module, diameter_gen_base_rfc3588}, +- {load_module, diameter_gen_base_accounting}, +- {load_module, diameter_gen_relay}, +- {load_module, diameter_codec}, +- {load_module, diameter_sctp}]}, +- {"1.7", [{load_module, diameter_service}, %% 17.1 +- {load_module, diameter_codec}, +- {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_gen_acct_rfc6733}, +- {load_module, diameter_gen_base_rfc3588}, +- {load_module, diameter_gen_base_accounting}, +- {load_module, diameter_gen_relay}, +- {load_module, diameter_traffic}, +- {load_module, diameter_peer_fsm}]}, +- {"1.7.1", [{load_module, diameter_traffic}, %% 17.3 +- {load_module, diameter_watchdog}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_service}]}, +- {"1.8", [{load_module, diameter_lib}, %% 17.4 ++ {"1.6", [{restart_application, diameter}]}, %% 17.0 ++ {"1.7", [{restart_application, diameter}]}, %% 17.[12] ++ {<<"^1\\.(7\\.1|8)$">>, %% 17.[34] ++ [{load_module, diameter_lib}, + {load_module, diameter_peer}, + {load_module, diameter_reg}, + {load_module, diameter_session}, +@@ -84,7 +62,14 @@ + {load_module, diameter_gen_relay}, + {update, diameter_transport_sup, supervisor}, + {update, diameter_service_sup, supervisor}, +- {update, diameter_sup, supervisor}]} ++ {update, diameter_sup, supervisor}]}, ++ {"1.9", [{load_module, diameter_codec}, %% 17.5 ++ {load_module, diameter_traffic}, ++ {load_module, diameter_gen_base_rfc6733}, ++ {load_module, diameter_gen_acct_rfc6733}, ++ {load_module, diameter_gen_base_rfc3588}, ++ {load_module, diameter_gen_base_accounting}, ++ {load_module, diameter_gen_relay}]} + ], + [ + {"0.9", [{restart_application, diameter}]}, +@@ -102,55 +87,40 @@ + {"1.4.3", [{restart_application, diameter}]}, + {"1.4.4", [{restart_application, diameter}]}, + {"1.5", [{restart_application, diameter}]}, +- {"1.6", [{load_module, diameter_sctp}, +- {load_module, diameter_codec}, ++ {"1.6", [{restart_application, diameter}]}, ++ {"1.7", [{restart_application, diameter}]}, ++ {<<"^1\\.(7\\.1|8)$">>, ++ [{update, diameter_sup, supervisor}, ++ {update, diameter_service_sup, supervisor}, ++ {update, diameter_transport_sup, supervisor}, + {load_module, diameter_gen_relay}, + {load_module, diameter_gen_base_accounting}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_service}, +- {load_module, diameter_peer_fsm}, ++ {load_module, diameter}, ++ {load_module, diameter_config}, ++ {load_module, diameter_sctp}, ++ {load_module, diameter_tcp}, + {load_module, diameter_watchdog}, ++ {load_module, diameter_peer_fsm}, ++ {load_module, diameter_service}, + {load_module, diameter_traffic}, ++ {load_module, diameter_types}, ++ {load_module, diameter_codec}, ++ {load_module, diameter_capx}, ++ {load_module, diameter_sync}, ++ {load_module, diameter_stats}, ++ {load_module, diameter_session}, ++ {load_module, diameter_reg}, ++ {load_module, diameter_peer}, + {load_module, diameter_lib}]}, +- {"1.7", [{load_module, diameter_peer_fsm}, +- {load_module, diameter_traffic}, +- {load_module, diameter_gen_relay}, +- {load_module, diameter_gen_base_accounting}, +- {load_module, diameter_gen_base_rfc3588}, +- {load_module, diameter_gen_acct_rfc6733}, +- {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_codec}, +- {load_module, diameter_service}]}, +- {"1.7.1", [{load_module, diameter_service}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_watchdog}, +- {load_module, diameter_traffic}]}, +- {"1.8", [{update, diameter_sup, supervisor}, +- {update, diameter_service_sup, supervisor}, +- {update, diameter_transport_sup, supervisor}, +- {load_module, diameter_gen_relay}, ++ {"1.9", [{load_module, diameter_gen_relay}, + {load_module, diameter_gen_base_accounting}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter}, +- {load_module, diameter_config}, +- {load_module, diameter_sctp}, +- {load_module, diameter_tcp}, +- {load_module, diameter_watchdog}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_service}, + {load_module, diameter_traffic}, +- {load_module, diameter_types}, +- {load_module, diameter_codec}, +- {load_module, diameter_capx}, +- {load_module, diameter_sync}, +- {load_module, diameter_stats}, +- {load_module, diameter_session}, +- {load_module, diameter_reg}, +- {load_module, diameter_peer}, +- {load_module, diameter_lib}]} ++ {load_module, diameter_codec}]} + ] + }. +diff --git lib/diameter/test/diameter_3xxx_SUITE.erl lib/diameter/test/diameter_3xxx_SUITE.erl +index 071b1a1..44fc3a6 100644 +--- lib/diameter/test/diameter_3xxx_SUITE.erl ++++ lib/diameter/test/diameter_3xxx_SUITE.erl +@@ -47,6 +47,7 @@ + send_double_error/1, + send_3xxx/1, + send_5xxx/1, ++ counters/1, + stop/1]). + + %% diameter callbacks +@@ -111,7 +112,7 @@ all() -> + + groups() -> + Tc = tc(), +- [{?util:name([E,D]), [], [start] ++ Tc ++ [stop]} ++ [{?util:name([E,D]), [], [start] ++ Tc ++ [counters, stop]} + || E <- ?ERRORS, D <- ?RFCS]. + + init_per_suite(Config) -> +@@ -169,6 +170,203 @@ stop(_Config) -> + ok = diameter:stop_service(?SERVER), + ok = diameter:stop_service(?CLIENT). + ++%% counters/1 ++%% ++%% Check that counters are as expected. ++ ++counters(Config) -> ++ Group = proplists:get_value(group, Config), ++ [_Errors, _Rfc] = G = ?util:name(Group), ++ [] = ?util:run([[fun counters/3, K, S, G] ++ || K <- [statistics, transport, connections], ++ S <- [?CLIENT, ?SERVER]]). ++ ++counters(Key, Svc, Group) -> ++ counters(Key, Svc, Group, [_|_] = diameter:service_info(Svc, Key)). ++ ++counters(statistics, Svc, [Errors, Rfc], L) -> ++ [{P, Stats}] = L, ++ true = is_pid(P), ++ stats(Svc, Errors, Rfc, lists:sort(Stats)); ++ ++counters(_, _, _, _) -> ++ todo. ++ ++stats(?CLIENT, E, rfc3588, L) ++ when E == answer; ++ E == answer_3xxx -> ++ [{{unknown,recv},2}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},6}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3001}},1}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',3008}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},1}] ++ = L; ++ ++stats(?SERVER, E, rfc3588, L) ++ when E == answer; ++ E == answer_3xxx -> ++ [{{unknown,recv},1}, ++ {{unknown,send},2}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},6}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3001}},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',3008}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, answer, rfc6733, L) -> ++ [{{unknown,recv},2}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},8}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3001}},1}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',3008}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},3}, ++ {{{0,275,0},recv,{'Result-Code',5999}},1}] ++ = L; ++ ++stats(?SERVER, answer, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},2}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},8}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3001}},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',3008}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},3}, ++ {{{0,275,0},send,{'Result-Code',5999}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, answer_3xxx, rfc6733, L) -> ++ [{{unknown,recv},2}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},8}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3001}},1}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',3008}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},2}, ++ {{{0,275,0},recv,{'Result-Code',5999}},1}] ++ = L; ++ ++stats(?SERVER, answer_3xxx, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},2}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},8}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3001}},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',3008}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},2}, ++ {{{0,275,0},send,{'Result-Code',5999}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, callback, rfc3588, L) -> ++ [{{unknown,recv},1}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},6}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},2}] ++ = L; ++ ++stats(?SERVER, callback, rfc3588, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},1}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},6}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},2}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, callback, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},8}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},3}, ++ {{{0,275,0},recv,{'Result-Code',5999}},1}] ++ = L; ++ ++stats(?SERVER, callback, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},1}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},8}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},3}, ++ {{{0,275,0},send,{'Result-Code',5999}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L. ++ + %% send_unknown_application/1 + %% + %% Send an unknown application that a callback (which shouldn't take +diff --git lib/diameter/test/diameter_app_SUITE.erl lib/diameter/test/diameter_app_SUITE.erl +index 6975e83..84f8a66 100644 +--- lib/diameter/test/diameter_app_SUITE.erl ++++ lib/diameter/test/diameter_app_SUITE.erl +@@ -249,11 +249,10 @@ release() -> + end. + + unversion(App) -> +- T = lists:dropwhile(fun is_vsn_ch/1, lists:reverse(App)), +- lists:reverse(case T of [$-|TT] -> TT; _ -> T end). +- +-is_vsn_ch(C) -> +- $0 =< C andalso C =< $9 orelse $. == C. ++ {Name, [$-|Vsn]} = lists:splitwith(fun(C) -> C /= $- end, App), ++ true = is_app(Name), %% assert ++ Vsn = vsn_str(Vsn), %% ++ Name. + + app('$M_EXPR') -> %% could be anything but assume it's ok + "erts"; +@@ -322,11 +321,11 @@ acc_rel(Dir, Rel, {Vsn, _}, Acc) -> + + %% Write a rel file and return its name. + write_rel(Dir, [Erts | Apps], Vsn) -> +- true = is_vsn(Vsn), +- Name = "diameter_test_" ++ Vsn, ++ VS = vsn_str(Vsn), ++ Name = "diameter_test_" ++ VS, + ok = write_file(filename:join([Dir, Name ++ ".rel"]), + {release, +- {"diameter " ++ Vsn ++ " test release", Vsn}, ++ {"diameter " ++ VS ++ " test release", VS}, + Erts, + Apps}), + Name. +@@ -341,10 +340,34 @@ fetch(Key, List) -> + write_file(Path, T) -> + file:write_file(Path, io_lib:format("~p.", [T])). + +-%% Is a version string of the expected form? Return the argument +-%% itself for 'false' for a useful badmatch. ++%% Is a version string of the expected form? + is_vsn(V) -> +- is_list(V) +- andalso length(V) == string:span(V, "0123456789.") +- andalso V == string:join(string:tokens(V, [$.]), ".") %% no ".." +- orelse {error, V}. ++ V = vsn_str(V), ++ true. ++ ++%% Turn a from/to version in appup to a version string after ensuring ++%% that it's valid version number of regexp. In the regexp case, the ++%% regexp itself becomes the version string since there's no ++%% requirement that a version in appup be anything but a string. The ++%% restrictions placed on string-valued version numbers (that they be ++%% '.'-separated integers) are our own. ++ ++vsn_str(S) ++ when is_list(S) -> ++ {_, match} = {S, match(S, "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*$")}, ++ {_, nomatch} = {S, match(S, "\\.0\\.0$")}, ++ S; ++ ++vsn_str(B) ++ when is_binary(B) -> ++ {ok, _} = re:compile(B), ++ binary_to_list(B). ++ ++match(S, RE) -> ++ re:run(S, RE, [{capture, none}]). ++ ++%% Is an application name of the expected form? ++is_app(S) ++ when is_list(S) -> ++ {_, match} = {S, match(S, "^([a-z]([a-z_]*|[a-zA-Z]*))$")}, ++ true. +diff --git lib/diameter/test/diameter_traffic_SUITE.erl lib/diameter/test/diameter_traffic_SUITE.erl +index 7dd9f39..7ff6ba7 100644 +--- lib/diameter/test/diameter_traffic_SUITE.erl ++++ lib/diameter/test/diameter_traffic_SUITE.erl +@@ -41,6 +41,7 @@ + send_eval/1, + send_bad_answer/1, + send_protocol_error/1, ++ send_experimental_result/1, + send_arbitrary/1, + send_unknown/1, + send_unknown_short/1, +@@ -301,6 +302,7 @@ tc() -> + send_eval, + send_bad_answer, + send_protocol_error, ++ send_experimental_result, + send_arbitrary, + send_unknown, + send_unknown_short, +@@ -443,7 +445,7 @@ send_ok(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 1}], + +- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req). + + %% Send an accounting ACR that the server answers badly to. +@@ -459,7 +461,7 @@ send_eval(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 3}], + +- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req). + + %% Send an accounting ACR that the server tries to answer with an +@@ -480,12 +482,20 @@ send_protocol_error(Config) -> + ?answer_message(?TOO_BUSY) + = call(Config, Req). + ++%% Send a 3xxx Experimental-Result in an answer not setting the E-bit ++%% and missing a Result-Code. ++send_experimental_result(Config) -> ++ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, ++ {'Accounting-Record-Number', 5}], ++ ['ACA', {'Session-Id', _} | _] ++ = call(Config, Req). ++ + %% Send an ASR with an arbitrary non-mandatory AVP and expect success + %% and the same AVP in the reply. + send_arbitrary(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name', + value = "XXX"}]}], +- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps] + = call(Config, Req), + {'AVP', [#diameter_avp{name = 'Product-Name', + value = V}]} +@@ -497,7 +507,7 @@ send_unknown(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 999, + is_mandatory = false, + data = <<17>>}]}], +- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps] + = call(Config, Req), + {'AVP', [#diameter_avp{code = 999, + is_mandatory = false, +@@ -513,7 +523,7 @@ send_unknown_short(Config, M, RC) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 999, + is_mandatory = M, + data = <<17>>}]}], +- ['ASA', _SessionId, {'Result-Code', RC} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', RC} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), +@@ -527,7 +537,7 @@ send_unknown_mandatory(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 999, + is_mandatory = true, + data = <<17>>}]}], +- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), +@@ -547,7 +557,7 @@ send_unexpected_mandatory_decode(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout + is_mandatory = true, + data = <<12:32>>}]}], +- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), +@@ -583,7 +593,7 @@ send_error_bit(Config) -> + %% Send a bad version and check that we get 5011. + send_unsupported_version(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], +- ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?UNSUPPORTED_VERSION} | _] + = call(Config, Req). + + %% Send a request containing an AVP length > data size. +@@ -603,14 +613,14 @@ send_zero_avp_length(Config) -> + send_invalid_avp_length(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + +- ['STA', _SessionId, ++ ['STA', {'Session-Id', _}, + {'Result-Code', ?INVALID_AVP_LENGTH}, +- _OriginHost, +- _OriginRealm, +- _UserName, +- _Class, +- _ErrorMessage, +- _ErrorReportingHost, ++ {'Origin-Host', _}, ++ {'Origin-Realm', _}, ++ {'User-Name', _}, ++ {'Class', _}, ++ {'Error-Message', _}, ++ {'Error-Reporting-Host', _}, + {'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]} + | _] + = call(Config, Req). +@@ -628,14 +638,14 @@ send_invalid_reject(Config) -> + send_unexpected_mandatory(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + +- ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | _] + = call(Config, Req). + + %% Send something long that will be fragmented by TCP. + send_long(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}, + {'User-Name', [lists:duplicate(1 bsl 20, $X)]}], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req). + + %% Send something longer than the configure incoming_maxlen. +@@ -677,7 +687,7 @@ send_any_2(Config) -> + send_all_1(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM), +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req, [{filter, {all, [{host, any}, + {realm, Realm}]}}]). + send_all_2(Config) -> +@@ -697,9 +707,8 @@ send_timeout(Config) -> + %% received the Session-Id. + send_error(Config) -> + Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}], +- ?answer_message(SId, ?TOO_BUSY) +- = call(Config, Req), +- true = undefined /= SId. ++ ?answer_message([_], ?TOO_BUSY) ++ = call(Config, Req). + + %% Send a request with the detached option and receive it as a message + %% from handle_answer instead. +@@ -708,7 +717,7 @@ send_detach(Config) -> + Ref = make_ref(), + ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]), + Ans = receive {Ref, T} -> T end, +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = Ans. + + %% Send a request which can't be encoded and expect {error, encode}. +@@ -721,11 +730,11 @@ send_destination_1(Config) -> + = group(Config), + Req = ['STR', {'Termination-Cause', ?LOGOUT}, + {'Destination-Host', [?HOST(SN, ?REALM)]}], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req, [{filter, {all, [host, realm]}}]). + send_destination_2(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req, [{filter, {all, [host, realm]}}]). + + %% Send with filtering on and expect failure when specifying an +@@ -789,7 +798,7 @@ send_bad_filter(Config, F) -> + %% Specify multiple filter options and expect them be conjunctive. + send_multiple_filters_1(Config) -> + Fun = fun(#diameter_caps{}) -> true end, +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = send_multiple_filters(Config, [host, {eval, Fun}]). + send_multiple_filters_2(Config) -> + E = {erlang, is_tuple, []}, +@@ -800,7 +809,7 @@ send_multiple_filters_3(Config) -> + E2 = {erlang, is_tuple, []}, + E3 = {erlang, is_record, [diameter_caps]}, + E4 = [{erlang, is_record, []}, diameter_caps], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]). + + send_multiple_filters(Config, Fs) -> +@@ -811,7 +820,7 @@ send_multiple_filters(Config, Fs) -> + %% only the return value from the prepare_request callback being + %% significant. + send_anything(Config) -> +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, anything). + + %% =========================================================================== +@@ -1144,6 +1153,13 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) -> + [R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)), + [Dict:rec2msg(R) | Vs]. + ++%% Missing Result-Codec and inapproriate Experimental-Result-Code. ++answer(Rec, Es, send_experimental_result) -> ++ [{5004, #diameter_avp{name = 'Experimental-Result'}}, ++ {5005, #diameter_avp{name = 'Result-Code'}}] ++ = Es, ++ Rec; ++ + %% An inappropriate E-bit results in a decode error ... + answer(Rec, Es, send_bad_answer) -> + [{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es, +@@ -1175,7 +1191,9 @@ handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) -> + %% Note that diameter will set Result-Code and Failed-AVPs if + %% #diameter_packet.errors is non-null. + +-handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) -> ++handle_request(#diameter_packet{header = H, msg = M, avps = As}, ++ _, ++ {_Ref, Caps}) -> + #diameter_header{end_to_end_id = EI, + hop_by_hop_id = HI} + = H, +@@ -1183,10 +1201,12 @@ handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) -> + V = EI bsr B, %% assert + V = HI bsr B, %% + #diameter_caps{origin_state_id = {_,[Id]}} = Caps, +- answer(origin(Id), request(M, Caps)). ++ answer(origin(Id), request(M, [H|As], Caps)). + + answer(T, {Tag, Action, Post}) -> + {Tag, answer(T, Action), Post}; ++answer(_, {reply, [#diameter_header{} | _]} = T) -> ++ T; + answer({A,C}, {reply, Ans}) -> + answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)}); + answer(pkt, {reply, Ans}) +@@ -1195,6 +1215,41 @@ answer(pkt, {reply, Ans}) + answer(_, T) -> + T. + ++%% request/3 ++ ++%% send_experimental_result ++request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 5}, ++ [Hdr | Avps], ++ #diameter_caps{origin_host = {OH, _}, ++ origin_realm = {OR, _}}) -> ++ [H,R|T] = [A || N <- ['Origin-Host', ++ 'Origin-Realm', ++ 'Session-Id', ++ 'Accounting-Record-Type', ++ 'Accounting-Record-Number'], ++ #diameter_avp{} = A ++ <- [lists:keyfind(N, #diameter_avp.name, Avps)]], ++ Ans = [Hdr#diameter_header{is_request = false}, ++ H#diameter_avp{data = OH}, ++ R#diameter_avp{data = OR}, ++ #diameter_avp{name = 'Experimental-Result', ++ code = 297, ++ need_encryption = false, ++ data = [#diameter_avp{data = {?DIAMETER_DICT_COMMON, ++ 'Vendor-Id', ++ 123}}, ++ #diameter_avp{data ++ = {?DIAMETER_DICT_COMMON, ++ 'Experimental-Result-Code', ++ 3987}}]} ++ | T], ++ {reply, Ans}; ++ ++request(Msg, _Avps, Caps) -> ++ request(Msg, Caps). ++ ++%% request/2 ++ + %% send_nok + request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0}, + _) -> +diff --git lib/diameter/vsn.mk lib/diameter/vsn.mk +index c00bac2..db7f72c 100644 +--- lib/diameter/vsn.mk ++++ lib/diameter/vsn.mk +@@ -16,5 +16,5 @@ + # %CopyrightEnd% + + APPLICATION = diameter +-DIAMETER_VSN = 1.9 ++DIAMETER_VSN = 1.9.1 + APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) +diff --git lib/snmp/doc/src/notes.xml lib/snmp/doc/src/notes.xml +index fd307ef..52022f5 100644 +--- lib/snmp/doc/src/notes.xml ++++ lib/snmp/doc/src/notes.xml +@@ -33,7 +33,40 @@ + </header> + + +- <section> ++ <section><title>SNMP 5.1.2</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ A bug in the SNMP Agent has been corrected; when opening ++ a port using the command line argument -snmpa_fd the Port ++ should be 0 when calling gen_udp:open.</p> ++ <p> ++ A bug in the SNMP manager has been corrected; it should ++ not look at the -snmp_fd command line argument, but ++ instead at -snmpm_fd.</p> ++ <p> ++ Own Id: OTP-12669 Aux Id: seq12841 </p> ++ </item> ++ </list> ++ </section> ++ ++ ++ <section><title>Improvements and New Features</title> ++ <list> ++ <item> ++ <p> ++ Improved cryptocraphic capability.</p> ++ <p> ++ Own Id: OTP-12452</p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ ++<section> + <title>SNMP Development Toolkit 5.1.1</title> + <p>Version 5.1.1 supports code replacement in runtime from/to + version 5.1. </p> +diff --git lib/snmp/src/agent/snmpa_net_if.erl lib/snmp/src/agent/snmpa_net_if.erl +index 840d56d..57d63ba 100644 +--- lib/snmp/src/agent/snmpa_net_if.erl ++++ lib/snmp/src/agent/snmpa_net_if.erl +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 2004-2014. All Rights Reserved. ++%% Copyright Ericsson AB 2004-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -297,14 +297,14 @@ socket_open(snmpUDPDomain = Domain, [IpPort | Opts]) -> + Fd = list_to_integer(FdStr), + ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p", + [Domain, IpPort, Opts, Fd]), +- gen_udp_open(IpPort, [{fd, Fd} | Opts]); ++ gen_udp_open(0, [{fd, Fd} | Opts]); + error -> + case init:get_argument(snmpa_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p", + [Domain, IpPort, Opts, Fd]), +- gen_udp_open(IpPort, [{fd, Fd} | Opts]); ++ gen_udp_open(0, [{fd, Fd} | Opts]); + error -> + ?vdebug("socket_open(~p, [~p | ~p])", + [Domain, IpPort, Opts]), +diff --git lib/snmp/src/app/snmp.appup.src lib/snmp/src/app/snmp.appup.src +index e7e54f5..081163b 100644 +--- lib/snmp/src/app/snmp.appup.src ++++ lib/snmp/src/app/snmp.appup.src +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 1999-2014. All Rights Reserved. ++%% Copyright Ericsson AB 1999-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -28,6 +28,7 @@ + %% {update, snmpa_local_db, soft, soft_purge, soft_purge, []} + %% {add_module, snmpm_net_if_mt} + [ ++ {"5.1.1", [{restart_application, snmp}]}, + {"5.1", [ % Only compiler changes + ]}, + {"5.0", [{restart_application, snmp}]}, +@@ -46,6 +47,7 @@ + %% {remove, {snmpm_net_if_mt, soft_purge, soft_purge}} + + [ ++ {"5.1.1", [{restart_application, snmp}]}, + {"5.1", [ % Only compiler changes + ]}, + {"5.0", [{restart_application, snmp}]}, +diff --git lib/snmp/src/manager/snmpm_net_if.erl lib/snmp/src/manager/snmpm_net_if.erl +index b4cc165..0e1c51c 100644 +--- lib/snmp/src/manager/snmpm_net_if.erl ++++ lib/snmp/src/manager/snmpm_net_if.erl +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 2004-2014. All Rights Reserved. ++%% Copyright Ericsson AB 2004-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -330,7 +330,7 @@ socket_params(Domain, {IpAddr, IpPort} = Addr, BindTo, CommonSocketOpts) -> + end, + case Family of + inet -> +- case init:get_argument(snmp_fd) of ++ case init:get_argument(snmpm_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + case BindTo of +diff --git lib/snmp/src/manager/snmpm_server.erl lib/snmp/src/manager/snmpm_server.erl +index a75122d..8fc3359 100644 +--- lib/snmp/src/manager/snmpm_server.erl ++++ lib/snmp/src/manager/snmpm_server.erl +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 2004-2014. All Rights Reserved. ++%% Copyright Ericsson AB 2004-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -2116,7 +2116,8 @@ do_handle_agent(DefUserId, DefMod, + ok; + + InvalidResult -> +- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData], ++ CallbackArgs = ++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData], + handle_invalid_result(handle_agent, CallbackArgs, InvalidResult) + + catch +@@ -2212,7 +2213,8 @@ do_handle_agent(DefUserId, DefMod, + end; + + T:E -> +- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData], ++ CallbackArgs = ++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData], + handle_invalid_result(handle_agent, CallbackArgs, T, E) + + end. +diff --git lib/snmp/vsn.mk lib/snmp/vsn.mk +index 345cc79..67adf0a 100644 +--- lib/snmp/vsn.mk ++++ lib/snmp/vsn.mk +@@ -2,7 +2,7 @@ + + # %CopyrightBegin% + # +-# Copyright Ericsson AB 1997-2014. All Rights Reserved. ++# Copyright Ericsson AB 1997-2015. All Rights Reserved. + # + # The contents of this file are subject to the Erlang Public License, + # Version 1.1, (the "License"); you may not use this file except in +@@ -18,6 +18,6 @@ + # %CopyrightEnd% + + APPLICATION = snmp +-SNMP_VSN = 5.1.1 ++SNMP_VSN = 5.1.2 + PRE_VSN = + APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)" +diff --git lib/test_server/doc/src/notes.xml lib/test_server/doc/src/notes.xml +index f21c32a..e996d2b 100644 +--- lib/test_server/doc/src/notes.xml ++++ lib/test_server/doc/src/notes.xml +@@ -32,6 +32,28 @@ + <file>notes.xml</file> + </header> + ++<section><title>Test_Server 3.8.1</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ If the last expression in a test case causes a timetrap ++ timeout, the stack trace is ignored and not printed to ++ the test case log file. This happens because the ++ {Suite,TestCase,Line} info is not available in the stack ++ trace in this scenario, due to tail call elimination. ++ Common Test has been modified to handle this situation by ++ inserting a {Suite,TestCase,last_expr} tuple in the ++ correct place and printing the stack trace as expected.</p> ++ <p> ++ Own Id: OTP-12697 Aux Id: seq12848 </p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>Test_Server 3.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git lib/test_server/src/erl2html2.erl lib/test_server/src/erl2html2.erl +index 7cfaa2c..50dbbb8 100644 +--- lib/test_server/src/erl2html2.erl ++++ lib/test_server/src/erl2html2.erl +@@ -117,9 +117,10 @@ parse_preprocessed_file(Epp,File,InCorrectFile) -> + parse_preprocessed_file(Epp,File,true); + {attribute,_,file,{_OtherFile,_}} -> + parse_preprocessed_file(Epp,File,false); +- {function,L,F,A,[_|C]} when InCorrectFile -> +- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C], +- [{atom_to_list(F),A,L} | Clauses] ++ ++ {function,L,F,A,Cs} when InCorrectFile -> ++ {CLs,LastCL} = find_clause_lines(Cs, []), ++ %% tl(CLs) cause we know the start line already ++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++ + parse_preprocessed_file(Epp,File,true); + _ -> + parse_preprocessed_file(Epp,File,InCorrectFile) +@@ -146,9 +147,10 @@ parse_non_preprocessed_file(Epp, File, Location) -> + case epp_dodger:parse_form(Epp, Location) of + {ok,Tree,Location1} -> + try erl_syntax:revert(Tree) of +- {function,L,F,A,[_|C]} -> +- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C], +- [{atom_to_list(F),A,L} | Clauses] ++ ++ {function,L,F,A,Cs} -> ++ {CLs,LastCL} = find_clause_lines(Cs, []), ++ %% tl(CLs) cause we know the start line already ++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++ + parse_non_preprocessed_file(Epp, File, Location1); + _ -> + parse_non_preprocessed_file(Epp, File, Location1) +@@ -162,22 +164,48 @@ parse_non_preprocessed_file(Epp, File, Location) -> + end. + + %%%----------------------------------------------------------------- ++%%% Find the line number of the last expression in the function ++find_clause_lines([{clause,CL,_Params,_Op,Exprs}], CLs) -> % last clause ++ try tuple_to_list(lists:last(Exprs)) of ++ [_Type,ExprLine | _] -> ++ {lists:reverse([{clause,CL}|CLs]), ExprLine}; ++ _ -> ++ {lists:reverse([{clause,CL}|CLs]), CL} ++ catch ++ _:_ -> ++ {lists:reverse([{clause,CL}|CLs]), CL} ++ end; ++ ++find_clause_lines([{clause,CL,_Params,_Op,_Exprs} | Cs], CLs) -> ++ find_clause_lines(Cs, [{clause,CL}|CLs]). ++ ++%%%----------------------------------------------------------------- + %%% Add a link target for each line and one for each function definition. +-build_html(SFd,DFd,Encoding,Functions) -> +- build_html(SFd,DFd,Encoding,file:read_line(SFd),1,Functions,false). ++build_html(SFd,DFd,Encoding,FuncsAndCs) -> ++ build_html(SFd,DFd,Encoding,file:read_line(SFd),1,FuncsAndCs, ++ false,undefined). + +-build_html(SFd,DFd,Encoding,{ok,Str},L,[{F,A,L}|Functions],_IsFuncDef) -> ++%% function start line found ++build_html(SFd,DFd,Enc,{ok,Str},L0,[{F,A,L0,LastL}|FuncsAndCs], ++ _IsFuncDef,_FAndLastL) -> + FALink = test_server_ctrl:uri_encode(F++"-"++integer_to_list(A),utf8), +- file:write(DFd,["<a name=\"",to_raw_list(FALink,Encoding),"\"/>"]), +- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true); +-build_html(SFd,DFd,Encoding,{ok,Str},L,[{clause,L}|Functions],_IsFuncDef) -> +- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true); +-build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,IsFuncDef) -> ++ file:write(DFd,["<a name=\"",to_raw_list(FALink,Enc),"\"/>"]), ++ build_html(SFd,DFd,Enc,{ok,Str},L0,FuncsAndCs,true,{F,LastL}); ++%% line of last expression in function found ++build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,_IsFuncDef,{F,LastL}) -> ++ LastLineLink = test_server_ctrl:uri_encode(F++"-last_expr",utf8), ++ file:write(DFd,["<a name=\"", ++ to_raw_list(LastLineLink,Enc),"\"/>"]), ++ build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,true,undefined); ++build_html(SFd,DFd,Enc,{ok,Str},L,[{clause,L}|FuncsAndCs], ++ _IsFuncDef,FAndLastL) -> ++ build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,true,FAndLastL); ++build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,IsFuncDef,FAndLastL) -> + LStr = line_number(L), + Str1 = line(Str,IsFuncDef), + file:write(DFd,[LStr,Str1]), +- build_html(SFd,DFd,Encoding,file:read_line(SFd),L+1,Functions,false); +-build_html(_SFd,_DFd,_Encoding,eof,L,_Functions,_IsFuncDef) -> ++ build_html(SFd,DFd,Enc,file:read_line(SFd),L+1,FuncsAndCs,false,FAndLastL); ++build_html(_SFd,_DFd,_Enc,eof,L,_FuncsAndCs,_IsFuncDef,_FAndLastL) -> + L. + + line_number(L) -> +diff --git lib/test_server/src/test_server.erl lib/test_server/src/test_server.erl +index 8d91778..1c33525 100644 +--- lib/test_server/src/test_server.erl ++++ lib/test_server/src/test_server.erl +@@ -1355,12 +1355,30 @@ get_loc(Pid) -> + Stk = [rewrite_loc_item(Loc) || Loc <- Stk0], + case get(test_server_loc) of + [{Suite,Case}] -> +- %% location info unknown, check if {Suite,Case,Line} +- %% is available in stacktrace. and if so, use stacktrace +- %% instead of current test_server_loc ++ %% Location info unknown, check if {Suite,Case,Line} ++ %% is available in stacktrace and if so, use stacktrace ++ %% instead of current test_server_loc. ++ %% If location is the last expression in a test case ++ %% function, the info is not available due to tail call ++ %% elimination. We need to check if the test case has been ++ %% called by ts_tc/3 and, if so, insert the test case info ++ %% at that position. + case [match || {S,C,_L} <- Stk, S == Suite, C == Case] of +- [match|_] -> put(test_server_loc, Stk); +- _ -> ok ++ [match|_] -> ++ put(test_server_loc, Stk); ++ _ -> ++ {PreTC,PostTC} = ++ lists:splitwith(fun({test_server,ts_tc,_}) -> ++ false; ++ (_) -> ++ true ++ end, Stk), ++ if PostTC == [] -> ++ ok; ++ true -> ++ put(test_server_loc, ++ PreTC++[{Suite,Case,last_expr} | PostTC]) ++ end + end; + _ -> + put(test_server_loc, Stk) +@@ -1422,7 +1440,10 @@ lookup_config(Key,Config) -> + undefined + end. + +-%% timer:tc/3 ++%% ++%% IMPORTANT: get_loc/1 uses the name of this function when analysing ++%% stack traces. If the name changes, get_loc/1 must be updated! ++%% + ts_tc(M, F, A) -> + Before = erlang:now(), + Result = try +diff --git lib/test_server/src/test_server_sup.erl lib/test_server/src/test_server_sup.erl +index 96e369a..15a6fdd 100644 +--- lib/test_server/src/test_server_sup.erl ++++ lib/test_server/src/test_server_sup.erl +@@ -61,33 +61,37 @@ timetrap(Timeout0, ReportTVal, Scale, Pid) -> + TruncTO = trunc(Timeout), + receive + after TruncTO -> +- case is_process_alive(Pid) of +- true -> +- TimeToReport = if Timeout0 == ReportTVal -> TruncTO; +- true -> ReportTVal end, +- MFLs = test_server:get_loc(Pid), +- Mon = erlang:monitor(process, Pid), +- Trap = {timetrap_timeout,TimeToReport,MFLs}, +- exit(Pid, Trap), +- receive +- {'DOWN', Mon, process, Pid, _} -> +- ok +- after 10000 -> +- %% Pid is probably trapping exits, hit it harder... +- catch error_logger:warning_msg( +- "Testcase process ~w not " +- "responding to timetrap " +- "timeout:~n" +- " ~p.~n" +- "Killing testcase...~n", +- [Pid, Trap]), +- exit(Pid, kill) +- end; +- false -> ++ kill_the_process(Pid, Timeout0, TruncTO, ReportTVal) ++ end. ++ ++kill_the_process(Pid, Timeout0, TruncTO, ReportTVal) -> ++ case is_process_alive(Pid) of ++ true -> ++ TimeToReport = if Timeout0 == ReportTVal -> TruncTO; ++ true -> ReportTVal end, ++ MFLs = test_server:get_loc(Pid), ++ Mon = erlang:monitor(process, Pid), ++ Trap = {timetrap_timeout,TimeToReport,MFLs}, ++ exit(Pid, Trap), ++ receive ++ {'DOWN', Mon, process, Pid, _} -> + ok +- end ++ after 10000 -> ++ %% Pid is probably trapping exits, hit it harder... ++ catch error_logger:warning_msg( ++ "Testcase process ~w not " ++ "responding to timetrap " ++ "timeout:~n" ++ " ~p.~n" ++ "Killing testcase...~n", ++ [Pid, Trap]), ++ exit(Pid, kill) ++ end; ++ false -> ++ ok + end. + ++ + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% timetrap_cancel(Handle) -> ok + %% Handle = term() +@@ -812,10 +816,19 @@ format_loc1({Mod,Func,Line}) -> + case {lists:member(no_src, get(test_server_logopts)), + lists:reverse(ModStr)} of + {false,[$E,$T,$I,$U,$S,$_|_]} -> +- io_lib:format("{~w,~w,<a href=\"~ts~ts#~w\">~w</a>}", ++ Link = if is_integer(Line) -> ++ integer_to_list(Line); ++ Line == last_expr -> ++ list_to_atom(atom_to_list(Func)++"-last_expr"); ++ is_atom(Line) -> ++ atom_to_list(Line); ++ true -> ++ Line ++ end, ++ io_lib:format("{~w,~w,<a href=\"~ts~ts#~s\">~w</a>}", + [Mod,Func, + test_server_ctrl:uri_encode(downcase(ModStr)), +- ?src_listing_ext,Line,Line]); ++ ?src_listing_ext,Link,Line]); + _ -> + io_lib:format("{~w,~w,~w}",[Mod,Func,Line]) + end. +diff --git lib/test_server/vsn.mk lib/test_server/vsn.mk +index 77225b4..2a2ed2b 100644 +--- lib/test_server/vsn.mk ++++ lib/test_server/vsn.mk +@@ -1 +1 @@ +-TEST_SERVER_VSN = 3.8 ++TEST_SERVER_VSN = 3.8.1 +diff --git otp_versions.table otp_versions.table +index 4bf6cb9..12790c8 100644 +--- otp_versions.table ++++ otp_versions.table +@@ -1,3 +1,4 @@ ++OTP-17.5.3 : common_test-1.10.1 diameter-1.9.1 erts-6.4.1 snmp-5.1.2 test_server-3.8.1 # asn1-3.0.4 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.7 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 ssh-3.2.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : + OTP-17.5.2 : inets-5.10.7 ssh-3.2.2 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : + OTP-17.5.1 : ssh-3.2.1 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : + OTP-17.5 : asn1-3.0.4 common_test-1.10 compiler-5.0.4 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 eldap-1.1.1 erts-6.4 hipe-3.11.3 inets-5.10.6 kernel-3.2 mnesia-4.12.5 observer-2.0.4 os_mon-2.3.1 public_key-0.23 runtime_tools-1.8.16 ssh-3.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 wx-1.3.3 # cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 edoc-0.7.16 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 ic-4.3.6 jinterface-1.5.12 megaco-3.17.3 odbc-2.10.22 orber-3.7.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 reltool-0.6.6 sasl-2.4.1 snmp-5.1.1 typer-0.9.8 webtool-0.8.10 xmerl-1.3.7 : diff --git a/lang/erlang-wx/Makefile b/lang/erlang-wx/Makefile index 5938abdbeb78..d1612bae34b3 100644 --- a/lang/erlang-wx/Makefile +++ b/lang/erlang-wx/Makefile @@ -1,7 +1,7 @@ # $FreeBSD$ PORTNAME= erlang -PORTVERSION= 17.5.2 +PORTVERSION= 17.5.3 CATEGORIES= lang parallel MASTER_SITES= http://www.erlang.org/download/:erlangorg \ http://erlang.stacken.kth.se/download/:erlangorg \ @@ -26,8 +26,7 @@ OPTIONS_DEFINE= DOCS ERL_RELEASE= 17.5 -USES= gmake -USE_AUTOTOOLS= autoconf:env +USES= autoreconf gmake GNU_CONFIGURE= yes LDFLAGS+= -L${LOCALBASE}/lib diff --git a/lang/erlang/Makefile b/lang/erlang/Makefile index 42df0d01fb6a..97869da4c6e6 100644 --- a/lang/erlang/Makefile +++ b/lang/erlang/Makefile @@ -2,8 +2,7 @@ # $FreeBSD$ PORTNAME= erlang -PORTVERSION= 17.5.2 -PORTREVISION= 1 +PORTVERSION= 17.5.3 PORTEPOCH= 3 CATEGORIES= lang parallel java MASTER_SITES= http://www.erlang.org/download/:erlangorg \ @@ -207,6 +206,10 @@ post-install: ${TAR} --unlink -xzpf ${DISTDIR}/${DIST_SUBDIR}/${ERLANG_DOCS} \ -C ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB} + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/erts-6.4 + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6/* \ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.7 ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/inets-5.10.6 @@ -215,6 +218,22 @@ post-install: ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2.2 ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/ssh-3.2 + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/common_test-1.10 + + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/diameter-1.9 + + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.2 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/snmp-5.1.1 + + ${MV} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8/* \ + ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8.1 + ${RMDIR} ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/test_server-3.8 + ${INSTALL_DATA} ${WRKSRC}/lib/dialyzer/doc/*.txt \ ${STAGEDIR}${PREFIX}/lib/${ERLANG_LIB}/lib/dialyzer-*/doc/ @${MKDIR} ${STAGEDIR}${DOCSDIR} diff --git a/lang/erlang/files/patch-otp-17.5.3 b/lang/erlang/files/patch-otp-17.5.3 new file mode 100644 index 000000000000..f9ed64007fd2 --- /dev/null +++ b/lang/erlang/files/patch-otp-17.5.3 @@ -0,0 +1,3738 @@ +diff --git OTP_VERSION OTP_VERSION +index 808ab16..f32d20d 100644 +--- OTP_VERSION ++++ OTP_VERSION +@@ -1 +1 @@ +-17.5.2 ++17.5.3 +diff --git erts/doc/src/notes.xml erts/doc/src/notes.xml +index a2b4ae4..35e6e55 100644 +--- erts/doc/src/notes.xml ++++ erts/doc/src/notes.xml +@@ -30,6 +30,22 @@ + </header> + <p>This document describes the changes made to the ERTS application.</p> + ++<section><title>Erts 6.4.1</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ The VTS mode in Common Test has been modified to use a ++ private version of the Webtool application (ct_webtool).</p> ++ <p> ++ Own Id: OTP-12704 Aux Id: OTP-10922 </p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>Erts 6.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git erts/etc/common/ct_run.c erts/etc/common/ct_run.c +index bb59b93..9e67b94 100644 +--- erts/etc/common/ct_run.c ++++ erts/etc/common/ct_run.c +@@ -239,7 +239,7 @@ int main(int argc, char** argv) + */ + + if (ct_mode == VTS_MODE) { +- PUSH4("-s", "webtool", "script_start", "vts"); ++ PUSH4("-s", "ct_webtool", "script_start", "vts"); + if (browser[0] != '\0') PUSH(browser); + PUSH3("-s", "ct_run", "script_start"); + } +diff --git erts/vsn.mk erts/vsn.mk +index abc9c0b..9e5aa99 100644 +--- erts/vsn.mk ++++ erts/vsn.mk +@@ -17,7 +17,7 @@ + # %CopyrightEnd% + # + +-VSN = 6.4 ++VSN = 6.4.1 + + # Port number 4365 in 4.2 + # Port number 4366 in 4.3 +diff --git lib/common_test/doc/src/notes.xml lib/common_test/doc/src/notes.xml +index 822ebf1..472e3b7 100644 +--- lib/common_test/doc/src/notes.xml ++++ lib/common_test/doc/src/notes.xml +@@ -32,6 +32,66 @@ + <file>notes.xml</file> + </header> + ++<section><title>Common_Test 1.10.1</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ A fault in the Common Test logger process, that caused ++ the application to crash when running on a long name ++ node, has been corrected.</p> ++ <p> ++ Own Id: OTP-12643</p> ++ </item> ++ <item> ++ <p> ++ A 'wait_for_prompt' option in ct_telnet:expect/3 has been ++ introduced which forces the function to not return until ++ a prompt string has been received, even if other expect ++ patterns have already been found.</p> ++ <p> ++ Own Id: OTP-12688 Aux Id: seq12818 </p> ++ </item> ++ <item> ++ <p> ++ If the last expression in a test case causes a timetrap ++ timeout, the stack trace is ignored and not printed to ++ the test case log file. This happens because the ++ {Suite,TestCase,Line} info is not available in the stack ++ trace in this scenario, due to tail call elimination. ++ Common Test has been modified to handle this situation by ++ inserting a {Suite,TestCase,last_expr} tuple in the ++ correct place and printing the stack trace as expected.</p> ++ <p> ++ Own Id: OTP-12697 Aux Id: seq12848 </p> ++ </item> ++ <item> ++ <p> ++ Fixed a buffer problem in ct_netconfc which could cause ++ that some messages where buffered forever.</p> ++ <p> ++ Own Id: OTP-12698 Aux Id: seq12844 </p> ++ </item> ++ <item> ++ <p> ++ The VTS mode in Common Test has been modified to use a ++ private version of the Webtool application (ct_webtool).</p> ++ <p> ++ Own Id: OTP-12704 Aux Id: OTP-10922 </p> ++ </item> ++ <item> ++ <p> ++ Add possibility to add user capabilities in ++ <c>ct_netconfc:hello/3</c>.</p> ++ <p> ++ Own Id: OTP-12707 Aux Id: seq12846 </p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>Common_Test 1.10</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git lib/common_test/src/Makefile lib/common_test/src/Makefile +index 8d74546..449cba6 100644 +--- lib/common_test/src/Makefile ++++ lib/common_test/src/Makefile +@@ -62,6 +62,8 @@ MODULES= \ + ct_telnet_client \ + ct_make \ + vts \ ++ ct_webtool \ ++ ct_webtool_sup \ + unix_telnet \ + ct_config \ + ct_config_plain \ +diff --git lib/common_test/src/ct_logs.erl lib/common_test/src/ct_logs.erl +index dc118ed..fa55a97 100644 +--- lib/common_test/src/ct_logs.erl ++++ lib/common_test/src/ct_logs.erl +@@ -1908,13 +1908,14 @@ sort_all_runs(Dirs) -> + sort_ct_runs(Dirs) -> + %% Directory naming: <Prefix>.NodeName.Date_Time[/...] + %% Sort on Date_Time string: "YYYY-MM-DD_HH.MM.SS" +- lists:sort(fun(Dir1,Dir2) -> +- [_Prefix,_Node1,DateHH1,MM1,SS1] = +- string:tokens(filename:dirname(Dir1),[$.]), +- [_Prefix,_Node2,DateHH2,MM2,SS2] = +- string:tokens(filename:dirname(Dir2),[$.]), +- {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2} +- end, Dirs). ++ lists:sort( ++ fun(Dir1,Dir2) -> ++ [SS1,MM1,DateHH1 | _] = ++ lists:reverse(string:tokens(filename:dirname(Dir1),[$.])), ++ [SS2,MM2,DateHH2 | _] = ++ lists:reverse(string:tokens(filename:dirname(Dir2),[$.])), ++ {DateHH1,MM1,SS1} =< {DateHH2,MM2,SS2} ++ end, Dirs). + + dir_diff_all_runs(Dirs, LogCache) -> + case LogCache#log_cache.all_runs of +diff --git lib/common_test/src/ct_netconfc.erl lib/common_test/src/ct_netconfc.erl +index 85fb1ea..80ffb51 100644 +--- lib/common_test/src/ct_netconfc.erl ++++ lib/common_test/src/ct_netconfc.erl +@@ -172,6 +172,7 @@ + only_open/2, + hello/1, + hello/2, ++ hello/3, + close_session/1, + close_session/2, + kill_session/2, +@@ -456,23 +457,35 @@ only_open(KeyOrName, ExtraOpts) -> + + %%---------------------------------------------------------------------- + %% @spec hello(Client) -> Result +-%% @equiv hello(Client, infinity) ++%% @equiv hello(Client, [], infinity) + hello(Client) -> +- hello(Client,?DEFAULT_TIMEOUT). ++ hello(Client,[],?DEFAULT_TIMEOUT). + + %%---------------------------------------------------------------------- + -spec hello(Client,Timeout) -> Result when + Client :: handle(), + Timeout :: timeout(), + Result :: ok | {error,error_reason()}. +-%% @doc Exchange `hello' messages with the server. +-%% +-%% Sends a `hello' message to the server and waits for the return. +-%% +-%% @end +-%%---------------------------------------------------------------------- ++%% @spec hello(Client, Timeout) -> Result ++%% @equiv hello(Client, [], Timeout) + hello(Client,Timeout) -> +- call(Client, {hello, Timeout}). ++ hello(Client,[],Timeout). ++ ++%%---------------------------------------------------------------------- ++-spec hello(Client,Options,Timeout) -> Result when ++ Client :: handle(), ++ Options :: [{capability, [string()]}], ++ Timeout :: timeout(), ++ Result :: ok | {error,error_reason()}. ++%% @doc Exchange `hello' messages with the server. ++%% ++%% Adds optional capabilities and sends a `hello' message to the ++%% server and waits for the return. ++%% @end ++%%---------------------------------------------------------------------- ++hello(Client,Options,Timeout) -> ++ call(Client, {hello, Options, Timeout}). ++ + + %%---------------------------------------------------------------------- + %% @spec get_session_id(Client) -> Result +@@ -1040,9 +1053,9 @@ terminate(_, #state{connection=Connection}) -> + ok. + + %% @private +-handle_msg({hello,Timeout}, From, ++handle_msg({hello, Options, Timeout}, From, + #state{connection=Connection,hello_status=HelloStatus} = State) -> +- case do_send(Connection, client_hello()) of ++ case do_send(Connection, client_hello(Options)) of + ok -> + case HelloStatus of + undefined -> +@@ -1118,7 +1131,9 @@ handle_msg({Ref,timeout},#state{pending=Pending} = State) -> + close_session -> stop; + _ -> noreply + end, +- {R,State#state{pending=Pending1}}. ++ %% Halfhearted try to get in correct state, this matches ++ %% the implementation before this patch ++ {R,State#state{pending=Pending1, buff= <<>>}}. + + %% @private + %% Called by ct_util_server to close registered connections before terminate. +@@ -1222,10 +1237,14 @@ set_request_timer(T) -> + + + %%%----------------------------------------------------------------- +-client_hello() -> ++client_hello(Options) when is_list(Options) -> ++ UserCaps = [{capability, UserCap} || ++ {capability, UserCap} <- Options, ++ is_list(hd(UserCap))], + {hello, ?NETCONF_NAMESPACE_ATTR, + [{capabilities, +- [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}]}]}. ++ [{capability,[?NETCONF_BASE_CAP++?NETCONF_BASE_CAP_VSN]}| ++ UserCaps]}]}. + + %%%----------------------------------------------------------------- + +@@ -1308,72 +1327,54 @@ to_xml_doc(Simple) -> + + %%%----------------------------------------------------------------- + %%% Parse and handle received XML data +-handle_data(NewData,#state{connection=Connection,buff=Buff} = State) -> ++handle_data(NewData,#state{connection=Connection,buff=Buff0} = State0) -> + log(Connection,recv,NewData), +- Data = <<Buff/binary,NewData/binary>>, +- case xmerl_sax_parser:stream(<<>>, +- [{continuation_fun,fun sax_cont/1}, +- {continuation_state,{Data,Connection,false}}, +- {event_fun,fun sax_event/3}, +- {event_state,[]}]) of +- {ok, Simple, Rest} -> +- decode(Simple,State#state{buff=Rest}); +- {fatal_error,_Loc,Reason,_EndTags,_EventState} -> +- ?error(Connection#connection.name,[{parse_error,Reason}, +- {buffer,Buff}, +- {new_data,NewData}]), +- case Reason of +- {could_not_fetch_data,Msg} -> +- handle_msg(Msg,State#state{buff = <<>>}); +- _Other -> +- Pending1 = +- case State#state.pending of +- [] -> +- []; +- Pending -> +- %% Assuming the first request gets the +- %% first answer +- P=#pending{tref=TRef,caller=Caller} = +- lists:last(Pending), +- _ = timer:cancel(TRef), +- Reason1 = {failed_to_parse_received_data,Reason}, +- ct_gen_conn:return(Caller,{error,Reason1}), +- lists:delete(P,Pending) +- end, +- {noreply,State#state{pending=Pending1,buff = <<>>}} +- end +- end. +- +-%%%----------------------------------------------------------------- +-%%% Parsing of XML data +-%% Contiuation function for the sax parser +-sax_cont(done) -> +- {<<>>,done}; +-sax_cont({Data,Connection,false}) -> ++ Data = append_wo_initial_nl(Buff0,NewData), + case binary:split(Data,[?END_TAG],[]) of +- [All] -> +- %% No end tag found. Remove what could be a part +- %% of an end tag from the data and save for next +- %% iteration +- SafeSize = size(All)-5, +- <<New:SafeSize/binary,Save:5/binary>> = All, +- {New,{Save,Connection,true}}; +- [_Msg,_Rest]=Msgs -> +- %% We have at least one full message. Any excess data will +- %% be returned from xmerl_sax_parser:stream/2 in the Rest +- %% parameter. +- {list_to_binary(Msgs),done} +- end; +-sax_cont({Data,Connection,true}) -> +- case ssh_receive_data() of +- {ok,Bin} -> +- log(Connection,recv,Bin), +- sax_cont({<<Data/binary,Bin/binary>>,Connection,false}); +- {error,Reason} -> +- throw({could_not_fetch_data,Reason}) ++ [_NoEndTagFound] -> ++ {noreply, State0#state{buff=Data}}; ++ [FirstMsg,Buff1] -> ++ SaxArgs = [{event_fun,fun sax_event/3}, {event_state,[]}], ++ case xmerl_sax_parser:stream(FirstMsg, SaxArgs) of ++ {ok, Simple, _Thrash} -> ++ case decode(Simple, State0#state{buff=Buff1}) of ++ {noreply, #state{buff=Buff} = State} when Buff =/= <<>> -> ++ %% Recurse if we have more data in buffer ++ handle_data(<<>>, State); ++ Other -> ++ Other ++ end; ++ {fatal_error,_Loc,Reason,_EndTags,_EventState} -> ++ ?error(Connection#connection.name, ++ [{parse_error,Reason}, ++ {buffer, Buff0}, ++ {new_data,NewData}]), ++ handle_error(Reason, State0#state{buff= <<>>}) ++ end + end. + ++%% xml does not accept a leading nl and some netconf server add a nl after ++%% each ?END_TAG, ignore them ++append_wo_initial_nl(<<>>,NewData) -> NewData; ++append_wo_initial_nl(<<"\n", Data/binary>>, NewData) -> ++ append_wo_initial_nl(Data, NewData); ++append_wo_initial_nl(Data, NewData) -> ++ <<Data/binary, NewData/binary>>. + ++handle_error(Reason, State) -> ++ Pending1 = case State#state.pending of ++ [] -> []; ++ Pending -> ++ %% Assuming the first request gets the ++ %% first answer ++ P=#pending{tref=TRef,caller=Caller} = ++ lists:last(Pending), ++ _ = timer:cancel(TRef), ++ Reason1 = {failed_to_parse_received_data,Reason}, ++ ct_gen_conn:return(Caller,{error,Reason1}), ++ lists:delete(P,Pending) ++ end, ++ {noreply, State#state{pending=Pending1}}. + + %% Event function for the sax parser. It builds a simple XML structure. + %% Care is taken to keep namespace attributes and prefixes as in the original XML. +@@ -1836,16 +1837,6 @@ get_tag([]) -> + + %%%----------------------------------------------------------------- + %%% SSH stuff +-ssh_receive_data() -> +- receive +- {ssh_cm, CM, {data, Ch, _Type, Data}} -> +- ssh_connection:adjust_window(CM,Ch,size(Data)), +- {ok, Data}; +- {ssh_cm, _CM, {Closed, _Ch}} = X when Closed == closed; Closed == eof -> +- {error,X}; +- {_Ref,timeout} = X -> +- {error,X} +- end. + + ssh_open(#options{host=Host,timeout=Timeout,port=Port,ssh=SshOpts,name=Name}) -> + case ssh:connect(Host, Port, +diff --git lib/common_test/src/ct_run.erl lib/common_test/src/ct_run.erl +index 4a12481..be547b4 100644 +--- lib/common_test/src/ct_run.erl ++++ lib/common_test/src/ct_run.erl +@@ -225,18 +225,24 @@ finish(Tracing, ExitStatus, Args) -> + if ExitStatus == interactive_mode -> + interactive_mode; + true -> +- %% it's possible to tell CT to finish execution with a call +- %% to a different function than the normal halt/1 BIF +- %% (meant to be used mainly for reading the CT exit status) +- case get_start_opt(halt_with, +- fun([HaltMod,HaltFunc]) -> +- {list_to_atom(HaltMod), +- list_to_atom(HaltFunc)} end, +- Args) of +- undefined -> +- halt(ExitStatus); +- {M,F} -> +- apply(M, F, [ExitStatus]) ++ case get_start_opt(vts, true, Args) of ++ true -> ++ %% VTS mode, don't halt the node ++ ok; ++ _ -> ++ %% it's possible to tell CT to finish execution with a call ++ %% to a different function than the normal halt/1 BIF ++ %% (meant to be used mainly for reading the CT exit status) ++ case get_start_opt(halt_with, ++ fun([HaltMod,HaltFunc]) -> ++ {list_to_atom(HaltMod), ++ list_to_atom(HaltFunc)} end, ++ Args) of ++ undefined -> ++ halt(ExitStatus); ++ {M,F} -> ++ apply(M, F, [ExitStatus]) ++ end + end + end. + +@@ -244,7 +250,7 @@ script_start1(Parent, Args) -> + %% read general start flags + Label = get_start_opt(label, fun([Lbl]) -> Lbl end, Args), + Profile = get_start_opt(profile, fun([Prof]) -> Prof end, Args), +- Vts = get_start_opt(vts, true, Args), ++ Vts = get_start_opt(vts, true, undefined, Args), + Shell = get_start_opt(shell, true, Args), + Cover = get_start_opt(cover, fun([CoverFile]) -> ?abs(CoverFile) end, Args), + CoverStop = get_start_opt(cover_stop, +@@ -330,8 +336,8 @@ script_start1(Parent, Args) -> + Stylesheet = get_start_opt(stylesheet, + fun([SS]) -> ?abs(SS) end, Args), + %% basic_html - used by ct_logs +- BasicHtml = case proplists:get_value(basic_html, Args) of +- undefined -> ++ BasicHtml = case {Vts,proplists:get_value(basic_html, Args)} of ++ {undefined,undefined} -> + application:set_env(common_test, basic_html, false), + undefined; + _ -> +@@ -364,9 +370,10 @@ script_start1(Parent, Args) -> + scale_timetraps = ScaleTT, + create_priv_dir = CreatePrivDir, + starter = script}, +- ++ + %% check if log files should be refreshed or go on to run tests... + Result = run_or_refresh(Opts, Args), ++ + %% send final results to starting process waiting in script_start/0 + Parent ! {self(), Result}. + +@@ -757,21 +764,6 @@ script_start4(Opts = #opts{tests = Tests}, Args) -> + %%% @doc Print usage information for <code>ct_run</code>. + script_usage() -> + io:format("\n\nUsage:\n\n"), +- io:format("Run tests in web based GUI:\n\n" +- "\tct_run -vts [-browser Browser]" +- "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" +- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" +- "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |" +- "\n\t[-suite Suite [-case Case]]" +- "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]" +- "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" +- "\n\t[-include InclDir1 InclDir2 .. InclDirN]" +- "\n\t[-no_auto_compile]" +- "\n\t[-abort_if_missing_suites]" +- "\n\t[-multiply_timetraps N]" +- "\n\t[-scale_timetraps]" +- "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" +- "\n\t[-basic_html]\n\n"), + io:format("Run tests from command line:\n\n" + "\tct_run [-dir TestDir1 TestDir2 .. TestDirN] |" + "\n\t[[-dir TestDir] -suite Suite1 Suite2 .. SuiteN" +@@ -831,7 +823,22 @@ script_usage() -> + io:format("Run CT in interactive mode:\n\n" + "\tct_run -shell" + "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" +- "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"). ++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]\n\n"), ++ io:format("Run tests in web based GUI:\n\n" ++ "\tct_run -vts [-browser Browser]" ++ "\n\t[-config ConfigFile1 ConfigFile2 .. ConfigFileN]" ++ "\n\t[-decrypt_key Key] | [-decrypt_file KeyFile]" ++ "\n\t[-dir TestDir1 TestDir2 .. TestDirN] |" ++ "\n\t[-suite Suite [-case Case]]" ++ "\n\t[-logopts LogOpt1 LogOpt2 .. LogOptN]" ++ "\n\t[-verbosity GenVLvl | [CategoryVLvl1 .. CategoryVLvlN]]" ++ "\n\t[-include InclDir1 InclDir2 .. InclDirN]" ++ "\n\t[-no_auto_compile]" ++ "\n\t[-abort_if_missing_suites]" ++ "\n\t[-multiply_timetraps N]" ++ "\n\t[-scale_timetraps]" ++ "\n\t[-create_priv_dir auto_per_run | auto_per_tc | manual_per_tc]" ++ "\n\t[-basic_html]\n\n"). + + %%%----------------------------------------------------------------- + %%% @hidden +diff --git lib/common_test/src/ct_telnet.erl lib/common_test/src/ct_telnet.erl +index d906a26..844f537 100644 +--- lib/common_test/src/ct_telnet.erl ++++ lib/common_test/src/ct_telnet.erl +@@ -486,7 +486,8 @@ expect(Connection,Patterns) -> + %%% Opts = [Opt] + %%% Opt = {idle_timeout,IdleTimeout} | {total_timeout,TotalTimeout} | + %%% repeat | {repeat,N} | sequence | {halt,HaltPatterns} | +-%%% ignore_prompt | no_prompt_check ++%%% ignore_prompt | no_prompt_check | wait_for_prompt | ++%%% {wait_for_prompt,Prompt} + %%% IdleTimeout = infinity | integer() + %%% TotalTimeout = infinity | integer() + %%% N = integer() +@@ -499,9 +500,9 @@ expect(Connection,Patterns) -> + %%% + %%% @doc Get data from telnet and wait for the expected pattern. + %%% +-%%% <p><code>Pattern</code> can be a POSIX regular expression. If more +-%%% than one pattern is given, the function returns when the first +-%%% match is found.</p> ++%%% <p><code>Pattern</code> can be a POSIX regular expression. The function ++%%% returns as soon as a pattern has been successfully matched (at least one, ++%%% in the case of multiple patterns).</p> + %%% + %%% <p><code>RxMatch</code> is a list of matched strings. It looks + %%% like this: <code>[FullMatch, SubMatch1, SubMatch2, ...]</code> +@@ -524,10 +525,13 @@ expect(Connection,Patterns) -> + %%% milliseconds, <code>{error,timeout}</code> is returned. The default + %%% value is <code>infinity</code> (i.e. no time limit).</p> + %%% +-%%% <p>The function will always return when a prompt is found, unless +-%%% any of the <code>ignore_prompt</code> or +-%%% <code>no_prompt_check</code> options are used, in which case it +-%%% will return when a match is found or after a timeout.</p> ++%%% <p>The function will return when a prompt is received, even if no ++%%% pattern has yet been matched. In this event, ++%%% <code>{error,{prompt,Prompt}}</code> is returned. ++%%% However, this behaviour may be modified with the ++%%% <code>ignore_prompt</code> or <code>no_prompt_check</code> option, which ++%%% tells <code>expect</code> to return only when a match is found or after a ++%%% timeout.</p> + %%% + %%% <p>If the <code>ignore_prompt</code> option is used, + %%% <code>ct_telnet</code> will ignore any prompt found. This option +@@ -541,6 +545,13 @@ expect(Connection,Patterns) -> + %%% is useful if, for instance, the <code>Pattern</code> itself + %%% matches the prompt.</p> + %%% ++%%% <p>The <code>wait_for_prompt</code> option forces <code>ct_telnet</code> ++%%% to wait until the prompt string has been received before returning ++%%% (even if a pattern has already been matched). This is equal to calling: ++%%% <code>expect(Conn, Patterns++[{prompt,Prompt}], [sequence|Opts])</code>. ++%%% Note that <code>idle_timeout</code> and <code>total_timeout</code> ++%%% may abort the operation of waiting for prompt.</p> ++%%% + %%% <p>The <code>repeat</code> option indicates that the pattern(s) + %%% shall be matched multiple times. If <code>N</code> is given, the + %%% pattern(s) will be matched <code>N</code> times, and the function +@@ -653,18 +664,21 @@ handle_msg({cmd,Cmd,Opts},State) -> + start_gen_log(heading(cmd,State#state.name)), + log(State,cmd,"Cmd: ~p",[Cmd]), + ++ %% whatever is in the buffer from previous operations ++ %% will be ignored as we go ahead with this telnet cmd ++ + debug_cont_gen_log("Throwing Buffer:",[]), + debug_log_lines(State#state.buffer), + + case {State#state.type,State#state.prompt} of +- {ts,_} -> ++ {ts,_} -> + silent_teln_expect(State#state.name, + State#state.teln_pid, + State#state.buffer, + prompt, + State#state.prx, + [{idle_timeout,2000}]); +- {ip,false} -> ++ {ip,false} -> + silent_teln_expect(State#state.name, + State#state.teln_pid, + State#state.buffer, +@@ -1029,10 +1043,12 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) -> + end, + + PromptCheck = get_prompt_check(Opts), +- Seq = get_seq(Opts), +- Pattern = convert_pattern(Pattern0,Seq), + +- {IdleTimeout,TotalTimeout} = get_timeouts(Opts), ++ {WaitForPrompt,Pattern1,Opts1} = wait_for_prompt(Pattern0,Opts), ++ ++ Seq = get_seq(Opts1), ++ Pattern2 = convert_pattern(Pattern1,Seq), ++ {IdleTimeout,TotalTimeout} = get_timeouts(Opts1), + + EO = #eo{teln_pid=Pid, + prx=Prx, +@@ -1042,9 +1058,16 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) -> + haltpatterns=HaltPatterns, + prompt_check=PromptCheck}, + +- case get_repeat(Opts) of ++ case get_repeat(Opts1) of + false -> +- case teln_expect1(Name,Pid,Data,Pattern,[],EO) of ++ case teln_expect1(Name,Pid,Data,Pattern2,[],EO) of ++ {ok,Matched,Rest} when WaitForPrompt -> ++ case lists:reverse(Matched) of ++ [{prompt,_},Matched1] -> ++ {ok,Matched1,Rest}; ++ [{prompt,_}|Matched1] -> ++ {ok,lists:reverse(Matched1),Rest} ++ end; + {ok,Matched,Rest} -> + {ok,Matched,Rest}; + {halt,Why,Rest} -> +@@ -1054,7 +1077,7 @@ teln_expect(Name,Pid,Data,Pattern0,Prx,Opts) -> + end; + N -> + EO1 = EO#eo{repeat=N}, +- repeat_expect(Name,Pid,Data,Pattern,[],EO1) ++ repeat_expect(Name,Pid,Data,Pattern2,[],EO1) + end. + + convert_pattern(Pattern,Seq) +@@ -1118,6 +1141,40 @@ get_ignore_prompt(Opts) -> + get_prompt_check(Opts) -> + not lists:member(no_prompt_check,Opts). + ++wait_for_prompt(Pattern, Opts) -> ++ case lists:member(wait_for_prompt, Opts) of ++ true -> ++ wait_for_prompt1(prompt, Pattern, ++ lists:delete(wait_for_prompt,Opts)); ++ false -> ++ case proplists:get_value(wait_for_prompt, Opts) of ++ undefined -> ++ {false,Pattern,Opts}; ++ PromptStr -> ++ wait_for_prompt1({prompt,PromptStr}, Pattern, ++ proplists:delete(wait_for_prompt,Opts)) ++ end ++ end. ++ ++wait_for_prompt1(Prompt, [Ch|_] = Pattern, Opts) when is_integer(Ch) -> ++ wait_for_prompt2(Prompt, [Pattern], Opts); ++wait_for_prompt1(Prompt, Pattern, Opts) when is_list(Pattern) -> ++ wait_for_prompt2(Prompt, Pattern, Opts); ++wait_for_prompt1(Prompt, Pattern, Opts) -> ++ wait_for_prompt2(Prompt, [Pattern], Opts). ++ ++wait_for_prompt2(Prompt, Pattern, Opts) -> ++ Pattern1 = case lists:reverse(Pattern) of ++ [prompt|_] -> Pattern; ++ [{prompt,_}|_] -> Pattern; ++ _ -> Pattern ++ [Prompt] ++ end, ++ Opts1 = case lists:member(sequence, Opts) of ++ true -> Opts; ++ false -> [sequence|Opts] ++ end, ++ {true,Pattern1,Opts1}. ++ + %% Repeat either single or sequence. All match results are accumulated + %% and returned when a halt condition is fulllfilled. + repeat_expect(_Name,_Pid,Rest,_Pattern,Acc,#eo{repeat=0}) -> +@@ -1210,7 +1267,7 @@ get_data1(Pid) -> + %% 1) Single expect. + %% First the whole data chunk is searched for a prompt (to avoid doing + %% a regexp match for the prompt at each line). +-%% If we are searching for anyting else, the datachunk is split into ++%% If we are searching for anything else, the datachunk is split into + %% lines and each line is matched against each pattern. + + %% one_expect: split data chunk at prompts +@@ -1227,7 +1284,7 @@ one_expect(Name,Pid,Data,Pattern,EO) -> + log(name_or_pid(Name,Pid),"PROMPT: ~ts",[PromptType]), + {match,{prompt,PromptType},Rest}; + [{prompt,_OtherPromptType}] -> +- %% Only searching for one specific prompt, not thisone ++ %% Only searching for one specific prompt, not this one + log_lines(Name,Pid,UptoPrompt), + {nomatch,Rest}; + _ -> +diff --git lib/common_test/src/ct_webtool.erl lib/common_test/src/ct_webtool.erl +new file mode 100644 +index 0000000..b67a7c2 +--- /dev/null ++++ lib/common_test/src/ct_webtool.erl +@@ -0,0 +1,1207 @@ ++%% ++%% %CopyrightBegin% ++%% ++%% 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(ct_webtool). ++-behaviour(gen_server). ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% The general idea is: %% ++%% %% ++%% %% ++%% 1. Scan through the path for *.tool files and find all the web %% ++%% based tools. Query each tool for configuration data. %% ++%% 2. Add Alias for Erlscript and html for each tool to %% ++%% the webserver configuration data. %% ++%% 3. Start the webserver. %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%% API functions ++-export([start/0, start/2, stop/0]). ++ ++%% Starting Webtool from a shell script ++-export([script_start/0, script_start/1]). ++ ++%% Web api ++-export([started_tools/2, toolbar/2, start_tools/2, stop_tools/2]). ++ ++%% API against other tools ++-export([is_localhost/0]). ++ ++%% Debug export s ++-export([get_tools1/1]). ++-export([debug/1, stop_debug/0, debug_app/1]). ++ ++%% gen_server callbacks ++-export([init/1, handle_call/3, handle_cast/2, handle_info/2, ++ terminate/2, code_change/3]). ++ ++-include_lib("kernel/include/file.hrl"). ++-include_lib("stdlib/include/ms_transform.hrl"). ++ ++-record(state,{priv_dir,app_data,supvis,web_data,started=[]}). ++ ++-define(MAX_NUMBER_OF_WEBTOOLS,256). ++-define(DEFAULT_PORT,8888).% must be >1024 or the user must be root on unix ++-define(DEFAULT_ADDR,{127,0,0,1}). ++ ++-define(WEBTOOL_ALIAS,{ct_webtool,[{alias,{erl_alias,"/ct_webtool",[ct_webtool]}}]}). ++-define(HEADER,"Pragma:no-cache\r\n Content-type: text/html\r\n\r\n"). ++-define(HTML_HEADER,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool</TITLE>\r\n</HEAD>\r\n<BODY BGCOLOR=\"#FFFFFF\">\r\n"). ++-define(HTML_HEADER_RELOAD,"<HTML>\r\n<HEAD>\r\n<TITLE>WebTool ++ </TITLE>\r\n</HEAD>\r\n ++ <BODY BGCOLOR=\"#FFFFFF\" onLoad=reloadCompiledList()>\r\n"). ++ ++-define(HTML_END,"</BODY></HTML>"). ++ ++-define(SEND_URL_TIMEOUT,5000). ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% For debugging only. %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% Start tracing with ++%% debug(Functions). ++%% Functions = local | global | FunctionList ++%% FunctionList = [Function] ++%% Function = {FunctionName,Arity} | FunctionName | ++%% {Module, FunctionName, Arity} | {Module,FunctionName} ++debug(F) -> ++ ttb:tracer(all,[{file,"webtool.trc"}]), % tracing all nodes ++ ttb:p(all,[call,timestamp]), ++ MS = [{'_',[],[{return_trace},{message,{caller}}]}], ++ tp(F,MS), ++ ttb:ctp(?MODULE,stop_debug), % don't want tracing of the stop_debug func ++ ok. ++tp(local,MS) -> % all functions ++ ttb:tpl(?MODULE,MS); ++tp(global,MS) -> % all exported functions ++ ttb:tp(?MODULE,MS); ++tp([{M,F,A}|T],MS) -> % Other module ++ ttb:tpl(M,F,A,MS), ++ tp(T,MS); ++tp([{M,F}|T],MS) when is_atom(F) -> % Other module ++ ttb:tpl(M,F,MS), ++ tp(T,MS); ++tp([{F,A}|T],MS) -> % function/arity ++ ttb:tpl(?MODULE,F,A,MS), ++ tp(T,MS); ++tp([F|T],MS) -> % function ++ ttb:tpl(?MODULE,F,MS), ++ tp(T,MS); ++tp([],_MS) -> ++ ok. ++stop_debug() -> ++ ttb:stop([format]). ++ ++debug_app(Mod) -> ++ ttb:tracer(all,[{file,"webtool_app.trc"},{handler,{fun out/4,true}}]), ++ ttb:p(all,[call,timestamp]), ++ MS = [{'_',[],[{return_trace},{message,{caller}}]}], ++ ttb:tp(Mod,MS), ++ ok. ++ ++out(_,{trace_ts,Pid,call,MFA={M,F,A},{W,_,_},TS},_,S) ++ when W==webtool;W==mod_esi-> ++ io:format("~w: (~p)~ncall ~s~n", [TS,Pid,ffunc(MFA)]), ++ [{M,F,length(A)}|S]; ++out(_,{trace_ts,Pid,return_from,MFA,R,TS},_,[MFA|S]) -> ++ io:format("~w: (~p)~nreturned from ~s -> ~p~n", [TS,Pid,ffunc(MFA),R]), ++ S; ++out(_,_,_,_) -> ++ ok. ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Functions called via script. %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++script_start() -> ++ usage(), ++ halt(). ++script_start([App]) -> ++ DefaultBrowser = ++ case os:type() of ++ {win32,_} -> iexplore; ++ _ -> firefox ++ end, ++ script_start([App,DefaultBrowser]); ++script_start([App,Browser]) -> ++ io:format("Starting webtool...\n"), ++ start(), ++ AvailableApps = get_applications(), ++ {OSType,_} = os:type(), ++ case lists:keysearch(App,1,AvailableApps) of ++ {value,{App,StartPage}} -> ++ io:format("Starting ~w...\n",[App]), ++ start_tools([],"app=" ++ atom_to_list(App)), ++ PortStr = integer_to_list(get_port()), ++ Url = case StartPage of ++ "/" ++ Page -> ++ "http://localhost:" ++ PortStr ++ "/" ++ Page; ++ _ -> ++ "http://localhost:" ++ PortStr ++ "/" ++ StartPage ++ end, ++ case Browser of ++ none -> ++ ok; ++ iexplore when OSType == win32-> ++ io:format("Starting internet explorer...\n"), ++ {ok,R} = win32reg:open(""), ++ Key="\\local_machine\\SOFTWARE\\Microsoft\\IE Setup\\Setup", ++ win32reg:change_key(R,Key), ++ {ok,Val} = win32reg:value(R,"Path"), ++ IExplore=filename:join(win32reg:expand(Val),"iexplore.exe"), ++ os:cmd("\"" ++ IExplore ++ "\" " ++ Url); ++ _ when OSType == win32 -> ++ io:format("Starting ~w...\n",[Browser]), ++ os:cmd("\"" ++ atom_to_list(Browser) ++ "\" " ++ Url); ++ B when B==firefox; B==mozilla -> ++ io:format("Sending URL to ~w...",[Browser]), ++ BStr = atom_to_list(Browser), ++ SendCmd = BStr ++ " -raise -remote \'openUrl(" ++ ++ Url ++ ")\'", ++ Port = open_port({spawn,SendCmd},[exit_status]), ++ receive ++ {Port,{exit_status,0}} -> ++ io:format("done\n"), ++ ok; ++ {Port,{exit_status,_Error}} -> ++ io:format(" not running, starting ~w...\n", ++ [Browser]), ++ os:cmd(BStr ++ " " ++ Url), ++ ok ++ after ?SEND_URL_TIMEOUT -> ++ io:format(" failed, starting ~w...\n",[Browser]), ++ erlang:port_close(Port), ++ os:cmd(BStr ++ " " ++ Url) ++ end; ++ _ -> ++ io:format("Starting ~w...\n",[Browser]), ++ os:cmd(atom_to_list(Browser) ++ " " ++ Url) ++ end, ++ ok; ++ false -> ++ stop(), ++ io:format("\n{error,{unknown_app,~p}}\n",[App]), ++ halt() ++ end. ++ ++usage() -> ++ io:format("Starting webtool...\n"), ++ start(), ++ Apps = lists:map(fun({A,_}) -> A end,get_applications()), ++ io:format( ++ "\nUsage: start_webtool application [ browser ]\n" ++ "\nAvailable applications are: ~p\n" ++ "Default browser is \'iexplore\' (Internet Explorer) on Windows " ++ "or else \'firefox\'\n", ++ [Apps]), ++ stop(). ++ ++ ++get_applications() -> ++ gen_server:call(ct_web_tool,get_applications). ++ ++get_port() -> ++ gen_server:call(ct_web_tool,get_port). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Api functions to the genserver. %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------------------------------------- ++% ++%---------------------------------------------------------------------- ++ ++start()-> ++ start(standard_path,standard_data). ++ ++start(Path,standard_data)-> ++ case get_standard_data() of ++ {error,Reason} -> ++ {error,Reason}; ++ Data -> ++ start(Path,Data) ++ end; ++ ++start(standard_path,Data)-> ++ Path=get_path(), ++ start(Path,Data); ++ ++start(Path,Port) when is_integer(Port)-> ++ Data = get_standard_data(Port), ++ start(Path,Data); ++ ++start(Path,Data0)-> ++ Data = Data0 ++ rest_of_standard_data(), ++ gen_server:start({local,ct_web_tool},ct_webtool,{Path,Data},[]). ++ ++stop()-> ++ gen_server:call(ct_web_tool,stoppit). ++ ++%---------------------------------------------------------------------- ++%Web Api functions called by the web ++%---------------------------------------------------------------------- ++started_tools(Env,Input)-> ++ gen_server:call(ct_web_tool,{started_tools,Env,Input}). ++ ++toolbar(Env,Input)-> ++ gen_server:call(ct_web_tool,{toolbar,Env,Input}). ++ ++start_tools(Env,Input)-> ++ gen_server:call(ct_web_tool,{start_tools,Env,Input}). ++ ++stop_tools(Env,Input)-> ++ gen_server:call(ct_web_tool,{stop_tools,Env,Input}). ++%---------------------------------------------------------------------- ++%Support API for other tools ++%---------------------------------------------------------------------- ++ ++is_localhost()-> ++ gen_server:call(ct_web_tool,is_localhost). ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%%The gen_server callback functions that builds the webbpages %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++handle_call(get_applications,_,State)-> ++ MS = ets:fun2ms(fun({Tool,{web_data,{_,Start}}}) -> {Tool,Start} end), ++ Tools = ets:select(State#state.app_data,MS), ++ {reply,Tools,State}; ++ ++handle_call(get_port,_,State)-> ++ {value,{port,Port}}=lists:keysearch(port,1,State#state.web_data), ++ {reply,Port,State}; ++ ++handle_call({started_tools,_Env,_Input},_,State)-> ++ {reply,started_tools_page(State),State}; ++ ++handle_call({toolbar,_Env,_Input},_,State)-> ++ {reply,toolbar(),State}; ++ ++handle_call({start_tools,Env,Input},_,State)-> ++ {NewState,Page}=start_tools_page(Env,Input,State), ++ {reply,Page,NewState}; ++ ++handle_call({stop_tools,Env,Input},_,State)-> ++ {NewState,Page}=stop_tools_page(Env,Input,State), ++ {reply,Page,NewState}; ++ ++handle_call(stoppit,_From,Data)-> ++ {stop,normal,ok,Data}; ++ ++handle_call(is_localhost,_From,Data)-> ++ Result=case proplists:get_value(bind_address, Data#state.web_data) of ++ ?DEFAULT_ADDR -> ++ true; ++ _IpNumber -> ++ false ++ end, ++ {reply,Result,Data}. ++ ++ ++handle_info(_Message,State)-> ++ {noreply,State}. ++ ++handle_cast(_Request,State)-> ++ {noreply,State}. ++ ++code_change(_,State,_)-> ++ {ok,State}. ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++% ++% The other functions needed by the gen_server behaviour ++% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------------------------------------- ++% Start the gen_server ++%---------------------------------------------------------------------- ++init({Path,Config})-> ++ case filelib:is_dir(Path) of ++ true -> ++ {ok, Table} = get_tool_files_data(), ++ insert_app(?WEBTOOL_ALIAS, Table), ++ case ct_webtool_sup:start_link() of ++ {ok, Pid} -> ++ case start_webserver(Table, Path, Config) of ++ {ok, _} -> ++ print_url(Config), ++ {ok,#state{priv_dir=Path, ++ app_data=Table, ++ supvis=Pid, ++ web_data=Config}}; ++ {error, Error} -> ++ {stop, {error, Error}} ++ end; ++ Error -> ++ {stop,Error} ++ end; ++ false -> ++ {stop, {error, error_dir}} ++ end. ++ ++terminate(_Reason,Data)-> ++ %%shut down the webbserver ++ shutdown_server(Data), ++ %%Shutdown the different tools that are started with application:start ++ shutdown_apps(Data), ++ %%Shutdown the supervisor and its children will die ++ shutdown_supervisor(Data), ++ ok. ++ ++print_url(ConfigData)-> ++ Server=proplists:get_value(server_name,ConfigData,"undefined"), ++ Port=proplists:get_value(port,ConfigData,"undefined"), ++ {A,B,C,D}=proplists:get_value(bind_address,ConfigData,"undefined"), ++ io:format("WebTool is available at http://~s:~w/~n",[Server,Port]), ++ io:format("Or http://~w.~w.~w.~w:~w/~n",[A,B,C,D,Port]). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++% ++% begin build the pages ++% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++%The page that shows the started tools ++%---------------------------------------------------------------------- ++started_tools_page(State)-> ++ [?HEADER,?HTML_HEADER,started_tools(State),?HTML_END]. ++ ++toolbar()-> ++ [?HEADER,?HTML_HEADER,toolbar_page(),?HTML_END]. ++ ++ ++start_tools_page(_Env,Input,State)-> ++ %%io:format("~n======= ~n ~p ~n============~n",[Input]), ++ case get_tools(Input) of ++ {tools,Tools}-> ++ %%io:format("~n======= ~n ~p ~n============~n",[Tools]), ++ {ok,NewState}=handle_apps(Tools,State,start), ++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(), ++ show_unstarted_apps(NewState),?HTML_END]}; ++ _ -> ++ {State,[?HEADER,?HTML_HEADER,show_unstarted_apps(State),?HTML_END]} ++ end. ++ ++stop_tools_page(_Env,Input,State)-> ++ case get_tools(Input) of ++ {tools,Tools}-> ++ {ok,NewState}=handle_apps(Tools,State,stop), ++ {NewState,[?HEADER,?HTML_HEADER_RELOAD,reload_started_apps(), ++ show_started_apps(NewState),?HTML_END]}; ++ _ -> ++ {State,[?HEADER,?HTML_HEADER,show_started_apps(State),?HTML_END]} ++ end. ++ ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%% Functions that start and config the webserver ++%% 1. Collect the config data ++%% 2. Start webserver ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++% Start the webserver ++%---------------------------------------------------------------------- ++start_webserver(Data,Path,Config)-> ++ case get_conf_data(Data,Path,Config) of ++ {ok,Conf_data}-> ++ %%io:format("Conf_data: ~p~n",[Conf_data]), ++ start_server(Conf_data); ++ {error,Error} -> ++ {error,{error_server_conf_file,Error}} ++ end. ++ ++start_server(Conf_data)-> ++ case inets:start(httpd, Conf_data, stand_alone) of ++ {ok,Pid}-> ++ {ok,Pid}; ++ Error-> ++ {error,{server_error,Error}} ++ end. ++ ++%---------------------------------------------------------------------- ++% Create config data for the webserver ++%---------------------------------------------------------------------- ++get_conf_data(Data,Path,Config)-> ++ Aliases=get_aliases(Data), ++ ServerRoot = filename:join([Path,"root"]), ++ MimeTypesFile = filename:join([ServerRoot,"conf","mime.types"]), ++ case httpd_conf:load_mime_types(MimeTypesFile) of ++ {ok,MimeTypes} -> ++ Config1 = Config ++ Aliases, ++ Config2 = [{server_root,ServerRoot}, ++ {document_root,filename:join([Path,"root/doc"])}, ++ {mime_types,MimeTypes} | ++ Config1], ++ {ok,Config2}; ++ Error -> ++ Error ++ end. ++ ++%---------------------------------------------------------------------- ++% Control the path for *.tools files ++%---------------------------------------------------------------------- ++get_tool_files_data()-> ++ Tools=get_tools1(code:get_path()), ++ %%io:format("Data : ~p ~n",[Tools]), ++ get_file_content(Tools). ++ ++%---------------------------------------------------------------------- ++%Control that the data in the file really is erlang terms ++%---------------------------------------------------------------------- ++get_file_content(Tools)-> ++ Get_data=fun({tool,ToolData}) -> ++ %%io:format("Data : ~p ~n",[ToolData]), ++ case proplists:get_value(config_func,ToolData) of ++ {M,F,A}-> ++ case catch apply(M,F,A) of ++ {'EXIT',_} -> ++ bad_data; ++ Data when is_tuple(Data) -> ++ Data; ++ _-> ++ bad_data ++ end; ++ _ -> ++ bad_data ++ end ++ end, ++ insert_file_content([X ||X<-lists:map(Get_data,Tools),X/=bad_data]). ++ ++%---------------------------------------------------------------------- ++%Insert the data from the file in to the ets:table ++%---------------------------------------------------------------------- ++insert_file_content(Content)-> ++ Table=ets:new(app_data,[bag]), ++ lists:foreach(fun(X)-> ++ insert_app(X,Table) ++ end,Content), ++ {ok,Table}. ++ ++%---------------------------------------------------------------------- ++%Control that we got a a tuple of a atom and a list if so add the ++%elements in the list to the ets:table ++%---------------------------------------------------------------------- ++insert_app({Name,Key_val_list},Table) when is_list(Key_val_list),is_atom(Name)-> ++ %%io:format("ToolData: ~p: ~p~n",[Name,Key_val_list]), ++ lists:foreach( ++ fun({alias,{erl_alias,Alias,Mods}}) -> ++ Key_val = {erl_script_alias,{Alias,Mods}}, ++ %%io:format("Insert: ~p~n",[Key_val]), ++ ets:insert(Table,{Name,Key_val}); ++ (Key_val_pair)-> ++ %%io:format("Insert: ~p~n",[Key_val_pair]), ++ ets:insert(Table,{Name,Key_val_pair}) ++ end, ++ Key_val_list); ++ ++insert_app(_,_)-> ++ ok. ++ ++%---------------------------------------------------------------------- ++% Select all the alias in the database ++%---------------------------------------------------------------------- ++get_aliases(Data)-> ++ MS = ets:fun2ms(fun({_,{erl_script_alias,Alias}}) -> ++ {erl_script_alias,Alias}; ++ ({_,{alias,Alias}}) -> ++ {alias,Alias} ++ end), ++ ets:select(Data,MS). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Helper functions %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++get_standard_data(Port)-> ++ [ ++ {port,Port}, ++ {bind_address,?DEFAULT_ADDR}, ++ {server_name,"localhost"} ++ ]. ++ ++get_standard_data()-> ++ case get_free_port(?DEFAULT_PORT,?MAX_NUMBER_OF_WEBTOOLS) of ++ {error,Reason} -> {error,Reason}; ++ Port -> ++ [ ++ {port,Port}, ++ {bind_address,?DEFAULT_ADDR}, ++ {server_name,"localhost"} ++ ] ++ end. ++ ++get_free_port(_Port,0) -> ++ {error,no_free_port_found}; ++get_free_port(Port,N) -> ++ case gen_tcp:connect("localhost",Port,[]) of ++ {error, _Reason} -> ++ Port; ++ {ok,Sock} -> ++ gen_tcp:close(Sock), ++ get_free_port(Port+1,N-1) ++ end. ++ ++rest_of_standard_data() -> ++ [ ++ %% Do not allow the server to be crashed by malformed http-request ++ {max_header_siz,1024}, ++ {max_header_action,reply414}, ++ %% Go on a straight ip-socket ++ {com_type,ip_comm}, ++ %% Do not change the order of these module names!! ++ {modules,[mod_alias, ++ mod_auth, ++ mod_esi, ++ mod_actions, ++ mod_cgi, ++ mod_include, ++ mod_dir, ++ mod_get, ++ mod_head, ++ mod_log, ++ mod_disk_log]}, ++ {directory_index,["index.html"]}, ++ {default_type,"text/plain"} ++ ]. ++ ++ ++get_path()-> ++ code:priv_dir(webtool). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++% These functions is used to shutdown the webserver ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++% Shut down the webbserver ++%---------------------------------------------------------------------- ++shutdown_server(State)-> ++ {Addr,Port} = get_addr_and_port(State#state.web_data), ++ inets:stop(httpd,{Addr,Port}). ++ ++get_addr_and_port(Config) -> ++ Addr = proplists:get_value(bind_address,Config,?DEFAULT_ADDR), ++ Port = proplists:get_value(port,Config,?DEFAULT_PORT), ++ {Addr,Port}. ++ ++%---------------------------------------------------------------------- ++% Select all apps in the table and close them ++%---------------------------------------------------------------------- ++shutdown_apps(State)-> ++ Data=State#state.app_data, ++ MS = ets:fun2ms(fun({_,{start,HowToStart}}) -> HowToStart end), ++ lists:foreach(fun(Start_app)-> ++ stop_app(Start_app) ++ end, ++ ets:select(Data,MS)). ++ ++%---------------------------------------------------------------------- ++%Shuts down the supervisor that supervises tools that is not ++%Designed as applications ++%---------------------------------------------------------------------- ++shutdown_supervisor(State)-> ++ %io:format("~n==================~n"), ++ ct_webtool_sup:stop(State#state.supvis). ++ %io:format("~n==================~n"). ++ ++%---------------------------------------------------------------------- ++%close the individual apps. ++%---------------------------------------------------------------------- ++stop_app({child,_Real_name})-> ++ ok; ++ ++stop_app({app,Real_name})-> ++ application:stop(Real_name); ++ ++stop_app({func,_Start,Stop})-> ++ case Stop of ++ {M,F,A} -> ++ catch apply(M,F,A); ++ _NoStop -> ++ ok ++ end. ++ ++ ++ ++ ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%% These functions creates the webpage where the user can select if ++%% to start apps or to stop apps ++%% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++toolbar_page()-> ++ "<TABLE> ++ <TR> ++ <TD> ++ <B>Select Action</B> ++ </TD> ++ </TR> ++ <TR> ++ <TD> ++ <A HREF=\"./start_tools\" TARGET=right> Start Tools</A> ++ </TD> ++ </TR> ++ <TR> ++ <TD> ++ <A HREF=\"./stop_tools\" TARGET=right> Stop Tools</A> ++ </TD> ++ </TR> ++ </TABLE>". ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%% These functions creates the webbpage that shows the started apps ++%% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++%---------------------------------------------------------------------- ++% started_tools(State)->String (html table) ++% State is a record of type state ++%---------------------------------------------------------------------- ++started_tools(State)-> ++ Names=get_started_apps(State#state.app_data,State#state.started), ++ "<TABLE BORDER=1 WIDTH=100%> ++ "++ make_rows(Names,[],0) ++" ++ </TABLE>". ++%---------------------------------------------------------------------- ++%get_started_apps(Data,Started)-> [{web_name,link}] ++%selects the started apps from the ets table of apps. ++%---------------------------------------------------------------------- ++ ++get_started_apps(Data,Started)-> ++ SelectData=fun({Name,Link}) -> ++ {Name,Link} ++ end, ++ MS = lists:map(fun(A) -> {{A,{web_data,'$1'}},[],['$1']} end,Started), ++ ++ [{"WebTool","/tool_management.html"} | ++ [SelectData(X) || X <- ets:select(Data,MS)]]. ++ ++%---------------------------------------------------------------------- ++% make_rows(List,Result,Fields)-> String (The rows of a htmltable ++% List a list of tupler discibed above ++% Result an accumulator for the result ++% Field, counter that counts the number of cols in each row. ++%---------------------------------------------------------------------- ++make_rows([],Result,Fields)-> ++ Result ++ fill_out(Fields); ++make_rows([Data|Paths],Result,Field)when Field==0-> ++ make_rows(Paths,Result ++ "<TR>" ++ make_field(Data),Field+1); ++ ++make_rows([Path|Paths],Result,Field)when Field==4-> ++ make_rows(Paths,Result ++ make_field(Path) ++ "</TR>",0); ++ ++make_rows([Path|Paths],Result,Field)-> ++ make_rows(Paths,Result ++ make_field(Path),Field+1). ++ ++%---------------------------------------------------------------------- ++% make_fields(Path)-> String that is a field i a html table ++% Path is a name url tuple {Name,url} ++%---------------------------------------------------------------------- ++make_field(Path)-> ++ "<TD WIDTH=20%>" ++ get_name(Path) ++ "</TD>". ++ ++ ++%---------------------------------------------------------------------- ++%get_name({Nae,Url})->String that represents a <A> tag in html. ++%---------------------------------------------------------------------- ++get_name({Name,Url})-> ++ "<A HREF=\"" ++ Url ++ "\" TARGET=app_frame>" ++ Name ++ "</A>". ++ ++ ++%---------------------------------------------------------------------- ++% fill_out(Nr)-> String, that represent Nr fields in a html-table. ++%---------------------------------------------------------------------- ++fill_out(Nr)when Nr==0-> ++ []; ++fill_out(Nr)when Nr==4-> ++ "<TD WIDTH=\"20%\" > </TD></TR>"; ++ ++fill_out(Nr)-> ++ "<TD WIDTH=\"20%\"> </TD>" ++ fill_out(Nr+1). ++ ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% ++%%These functions starts applicatons and builds the page showing tools ++%%to start ++%% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------------------------------------- ++%Controls whether the user selected a tool to start ++%---------------------------------------------------------------------- ++get_tools(Input)-> ++ case httpd:parse_query(Input) of ++ []-> ++ no_tools; ++ Tools-> ++ FormatData=fun({_Name,Data}) -> list_to_atom(Data) end, ++ SelectData= ++ fun({Name,_Data}) -> string:equal(Name,"app") end, ++ {tools,[FormatData(X)||X<-Tools,SelectData(X)]} ++ end. ++ ++%---------------------------------------------------------------------- ++% Selects the data to start the applications the user has ordered ++% starting of ++%---------------------------------------------------------------------- ++handle_apps([],State,_Cmd)-> ++ {ok,State}; ++ ++handle_apps([Tool|Tools],State,Cmd)-> ++ case ets:match_object(State#state.app_data,{Tool,{start,'_'}}) of ++ []-> ++ Started = case Cmd of ++ start -> ++ [Tool|State#state.started]; ++ stop -> ++ lists:delete(Tool,State#state.started) ++ end, ++ {ok,#state{priv_dir=State#state.priv_dir, ++ app_data=State#state.app_data, ++ supvis=State#state.supvis, ++ web_data=State#state.web_data, ++ started=Started}}; ++ ToStart -> ++ case handle_apps2(ToStart,State,Cmd) of ++ {ok,NewState}-> ++ handle_apps(Tools,NewState,Cmd); ++ _-> ++ handle_apps(Tools,State,Cmd) ++ end ++ end. ++ ++%---------------------------------------------------------------------- ++%execute every start or stop data about a tool. ++%---------------------------------------------------------------------- ++handle_apps2([{Name,Start_data}],State,Cmd)-> ++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd) of ++ ok-> ++ Started = case Cmd of ++ start -> ++ [Name|State#state.started]; ++ stop -> ++ ++ lists:delete(Name,State#state.started) ++ end, ++ {ok,#state{priv_dir=State#state.priv_dir, ++ app_data=State#state.app_data, ++ supvis=State#state.supvis, ++ web_data=State#state.web_data, ++ started=Started}}; ++ _-> ++ error ++ end; ++ ++handle_apps2([{Name,Start_data}|Rest],State,Cmd)-> ++ case handle_app({Name,Start_data},State#state.app_data,State#state.supvis,Cmd)of ++ ok-> ++ handle_apps2(Rest,State,Cmd); ++ _-> ++ error ++ end. ++ ++ ++%---------------------------------------------------------------------- ++% Handle start and stop of applications ++%---------------------------------------------------------------------- ++ ++handle_app({Name,{start,{func,Start,Stop}}},Data,_Pid,Cmd)-> ++ Action = case Cmd of ++ start -> ++ Start; ++ _ -> ++ Stop ++ end, ++ case Action of ++ {M,F,A} -> ++ case catch apply(M,F,A) of ++ {'EXIT',_} = Exit-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "~w:~w(~s) ->\n" ++ "~p\n\n", ++ [?LINE,Name,M,F,format_args(A),Exit]), ++ ets:delete(Data,Name); ++ _OK-> ++ ok ++ end; ++ _NoStart -> ++ ok ++ end; ++ ++ ++handle_app({Name,{start,{child,ChildSpec}}},Data,Pid,Cmd)-> ++ case Cmd of ++ start -> ++ case catch supervisor:start_child(Pid,ChildSpec) of ++ {ok,_}-> ++ ok; ++ {ok,_,_}-> ++ ok; ++ {error,Reason}-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "supervisor:start_child(~p,~p) ->\n" ++ "~p\n\n", ++ [?LINE,Name,Pid,ChildSpec,{error,Reason}]), ++ ets:delete(Data,Name); ++ Error -> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "supervisor:start_child(~p,~p) ->\n" ++ "~p\n\n", ++ [?LINE,Name,Pid,ChildSpec,Error]), ++ ets:delete(Data,Name) ++ end; ++ stop -> ++ case catch supervisor:terminate_child(websup,element(1,ChildSpec)) of ++ ok -> ++ supervisor:delete_child(websup,element(1,ChildSpec)); ++ _ -> ++ error ++ end ++ end; ++ ++ ++ ++handle_app({Name,{start,{app,Real_name}}},Data,_Pid,Cmd)-> ++ case Cmd of ++ start -> ++ case application:start(Real_name,temporary) of ++ ok-> ++ io:write(Name), ++ ok; ++ {error,{already_started,_}}-> ++ %% Remove it from the database so we dont start ++ %% anything already started ++ ets:match_delete(Data,{Name,{start,{app,Real_name}}}), ++ ok; ++ {error,_Reason}=Error-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not start application \'~p\'\n\n" ++ "application:start(~p,~p) ->\n" ++ "~p\n\n", ++ [?LINE,Name,Real_name,temporary,Error]), ++ ets:delete(Data,Name) ++ end; ++ ++ stop -> ++ application:stop(Real_name) ++ end; ++ ++%---------------------------------------------------------------------- ++% If the data is incorrect delete the app ++%---------------------------------------------------------------------- ++handle_app({Name,Incorrect},Data,_Pid,Cmd)-> ++ %%! Here the tool disappears from the webtool interface!! ++ io:format("\n=======ERROR (webtool, line ~w) =======\n" ++ "Could not ~w application \'~p\'\n\n" ++ "Incorrect data: ~p\n\n", ++ [?LINE,Cmd,Name,Incorrect]), ++ ets:delete(Data,Name). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% this functions creates the page that shows the unstarted tools %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++ ++reload_started_apps()-> ++ "<script> ++ function reloadCompiledList() ++ { ++ parent.parent.top1.document.location.href=\"/webtool/webtool/started_tools\"; ++ } ++ </script>". ++ ++show_unstarted_apps(State)-> ++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0> ++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\"> ++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/start_tools\" > ++ <TABLE BORDER=1 WIDTH=60%> ++ <TR BGCOLOR=\"#8899AA\"> ++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Available Tools<FONT></TD> ++ </TR> ++ <TR> ++ <TD WIDTH=50%> ++ <TABLE BORDER=0> ++ "++ list_available_apps(State)++" ++ <TR><TD COLSPAN=2> </TD></TR> ++ <TR> ++ <TD COLSPAN=2 ALIGN=\"center\"> ++ <INPUT TYPE=submit VALUE=\"Start\"> ++ </TD> ++ </TR> ++ </TABLE> ++ </TD> ++ <TD> ++ To Start a Tool: ++ <UL> ++ <LI>Select the ++ checkbox for each tool to ++ start.</LI> ++ <LI>Click on the ++ button marked <EM>Start</EM>.</LI></UL> ++ </TD> ++ </TR> ++ </TABLE> ++ </FORM> ++ </TD></TR> ++ <TR><TD> </TD></TR> ++ </TABLE>". ++ ++ ++ ++list_available_apps(State)-> ++ MS = ets:fun2ms(fun({Tool,{web_data,{Name,_}}}) -> {Tool,Name} end), ++ Unstarted_apps= ++ lists:filter( ++ fun({Tool,_})-> ++ false==lists:member(Tool,State#state.started) ++ end, ++ ets:select(State#state.app_data,MS)), ++ case Unstarted_apps of ++ []-> ++ "<TR><TD>All tools are started</TD></TR>"; ++ _-> ++ list_apps(Unstarted_apps) ++ end. ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% these functions creates the page that shows the started apps %% ++%% the user can select to shutdown %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++show_started_apps(State)-> ++ "<TABLE HEIGHT=100% WIDTH=100% BORDER=0> ++ <TR HEIGHT=80%><TD ALIGN=\"center\" VALIGN=\"middle\"> ++ <FORM NAME=\"stop_apps\" ACTION=\"/webtool/webtool/stop_tools\" > ++ <TABLE BORDER=1 WIDTH=60%> ++ <TR BGCOLOR=\"#8899AA\"> ++ <TD ALIGN=CENTER COLSPAN=2><FONT SIZE=4>Started Tools<FONT></TD> ++ </TR> ++ <TR> ++ <TD WIDTH=50%> ++ <TABLE BORDER=0> ++ "++ list_started_apps(State)++" ++ <TR><TD COLSPAN=2> </TD></TR> ++ <TR> ++ <TD COLSPAN=2 ALIGN=\"center\"> ++ <INPUT TYPE=submit VALUE=\"Stop\"> ++ </TD> ++ </TR> ++ </TABLE> ++ </TD> ++ <TD> ++ Stop a Tool: ++ <UL> ++ <LI>Select the ++ checkbox for each tool to ++ stop.</LI> ++ <LI>Click on the ++ button marked <EM>Stop</EM>.</LI></UL> ++ </TD> ++ </TR> ++ </TABLE> ++ </FORM> ++ </TD></TR> ++ <TR><TD> </TD></TR> ++ </TABLE>". ++ ++list_started_apps(State)-> ++ MS = lists:map(fun(A) -> {{A,{web_data,{'$1','_'}}},[],[{{A,'$1'}}]} end, ++ State#state.started), ++ Started_apps= ets:select(State#state.app_data,MS), ++ case Started_apps of ++ []-> ++ "<TR><TD>No tool is started yet.</TD></TR>"; ++ _-> ++ list_apps(Started_apps) ++ end. ++ ++ ++list_apps(Apps) -> ++ lists:map(fun({Tool,Name})-> ++ "<TR><TD> ++ <INPUT TYPE=\"checkbox\" NAME=\"app\" VALUE=\"" ++ ++ atom_to_list(Tool) ++ "\"> ++ " ++ Name ++ " ++ </TD></TR>" ++ end, ++ Apps). ++ ++ ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%% %% ++%% Collecting the data from the *.tool files %% ++%% %% ++%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ++%---------------------------------------- ++% get_tools(Dirs) => [{M,F,A},{M,F,A}...{M,F,A}] ++% Dirs - [string()] Directory names ++% Calls get_tools2/2 recursively for a number of directories ++% to retireve the configuration data for the web based tools. ++%---------------------------------------- ++get_tools1(Dirs)-> ++ get_tools1(Dirs,[]). ++ ++get_tools1([Dir|Rest],Data) when is_list(Dir) -> ++ Tools=case filename:basename(Dir) of ++ %% Dir is an 'ebin' directory, check in '../priv' as well ++ "ebin" -> ++ [get_tools2(filename:join(filename:dirname(Dir),"priv")) | ++ get_tools2(Dir)]; ++ _ -> ++ get_tools2(Dir) ++ end, ++ get_tools1(Rest,[Tools|Data]); ++ ++get_tools1([],Data) -> ++ lists:flatten(Data). ++ ++%---------------------------------------- ++% get_tools2(Directory) => DataList ++% DataList : [WebTuple]|[] ++% WebTuple: {tool,[{web,M,F,A}]} ++% ++%---------------------------------------- ++get_tools2(Dir)-> ++ get_tools2(tool_files(Dir),[]). ++ ++get_tools2([ToolFile|Rest],Data) -> ++ case get_tools3(ToolFile) of ++ {tool,WebData} -> ++ get_tools2(Rest,[{tool,WebData}|Data]); ++ {error,_Reason} -> ++ get_tools2(Rest,Data); ++ nodata -> ++ get_tools2(Rest,Data) ++ end; ++ ++get_tools2([],Data) -> ++ Data. ++ ++%---------------------------------------- ++% get_tools3(ToolFile) => {ok,Tool}|{error,Reason}|nodata ++% Tool: {tool,[KeyValTuple]} ++% ToolFile - string() A .tool file ++% Now we have the file get the data and sort it out ++%---------------------------------------- ++get_tools3(ToolFile) -> ++ case file:consult(ToolFile) of ++ {error,open} -> ++ {error,nofile}; ++ {error,read} -> ++ {error,format}; ++ {ok,[{version,"1.2"},ToolInfo]} when is_list(ToolInfo)-> ++ webdata(ToolInfo); ++ {ok,[{version,_Vsn},_Info]} -> ++ {error,old_version}; ++ {ok,_Other} -> ++ {error,format} ++ end. ++ ++ ++%---------------------------------------------------------------------- ++% webdata(TupleList)-> ToolTuple| nodata ++% ToolTuple: {tool,[{config_func,{M,F,A}}]} ++% ++% There are a little unneccesary work in this format but it is extendable ++%---------------------------------------------------------------------- ++webdata(TupleList)-> ++ case proplists:get_value(config_func,TupleList,nodata) of ++ {M,F,A} -> ++ {tool,[{config_func,{M,F,A}}]}; ++ _ -> ++ nodata ++ end. ++ ++ ++%============================================================================= ++% Functions for getting *.tool configuration files ++%============================================================================= ++ ++%---------------------------------------- ++% tool_files(Dir) => ToolFiles ++% Dir - string() Directory name ++% ToolFiles - [string()] ++% Return the list of all files in Dir ending with .tool (appended to Dir) ++%---------------------------------------- ++tool_files(Dir) -> ++ case file:list_dir(Dir) of ++ {ok,Files} -> ++ filter_tool_files(Dir,Files); ++ {error,_Reason} -> ++ [] ++ end. ++ ++%---------------------------------------- ++% filter_tool_files(Dir,Files) => ToolFiles ++% Dir - string() Directory name ++% Files, ToolFiles - [string()] File names ++% Filters out the files in Files ending with .tool and append them to Dir ++%---------------------------------------- ++filter_tool_files(_Dir,[]) -> ++ []; ++filter_tool_files(Dir,[File|Rest]) -> ++ case filename:extension(File) of ++ ".tool" -> ++ [filename:join(Dir,File)|filter_tool_files(Dir,Rest)]; ++ _ -> ++ filter_tool_files(Dir,Rest) ++ end. ++ ++ ++%%%----------------------------------------------------------------- ++%%% format functions ++ffunc({M,F,A}) when is_list(A) -> ++ io_lib:format("~w:~w(~s)\n",[M,F,format_args(A)]); ++ffunc({M,F,A}) when is_integer(A) -> ++ io_lib:format("~w:~w/~w\n",[M,F,A]). ++ ++format_args([]) -> ++ ""; ++format_args(Args) -> ++ Str = lists:append(["~p"|lists:duplicate(length(Args)-1,",~p")]), ++ io_lib:format(Str,Args). +diff --git lib/common_test/src/ct_webtool_sup.erl lib/common_test/src/ct_webtool_sup.erl +new file mode 100644 +index 0000000..1d612a2 +--- /dev/null ++++ lib/common_test/src/ct_webtool_sup.erl +@@ -0,0 +1,74 @@ ++%% ++%% %CopyrightBegin% ++%% ++%% Copyright Ericsson AB 2001-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(ct_webtool_sup). ++ ++-behaviour(supervisor). ++ ++%% External exports ++-export([start_link/0,stop/1]). ++ ++%% supervisor callbacks ++-export([init/1]). ++ ++%%%---------------------------------------------------------------------- ++%%% API ++%%%---------------------------------------------------------------------- ++start_link() -> ++ supervisor:start_link({local,ct_websup},ct_webtool_sup, []). ++ ++stop(Pid)-> ++ exit(Pid,normal). ++%%%---------------------------------------------------------------------- ++%%% Callback functions from supervisor ++%%%---------------------------------------------------------------------- ++ ++%%---------------------------------------------------------------------- ++%% Func: init/1 ++%% Returns: {ok, {SupFlags, [ChildSpec]}} | ++%% ignore | ++%% {error, Reason} ++%%---------------------------------------------------------------------- ++init(_StartArgs) -> ++ %%Child1 = ++ %%Child2 ={webcover_backend,{webcover_backend,start_link,[]},permanent,2000,worker,[webcover_backend]}, ++ %%{ok,{{simple_one_for_one,5,10},[Child1]}}. ++ {ok,{{one_for_one,100,10},[]}}. ++ ++%%%---------------------------------------------------------------------- ++%%% Internal functions ++%%%---------------------------------------------------------------------- ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git lib/common_test/src/vts.erl lib/common_test/src/vts.erl +index b340c6f..ab13e7d 100644 +--- lib/common_test/src/vts.erl ++++ lib/common_test/src/vts.erl +@@ -63,21 +63,21 @@ + %%%----------------------------------------------------------------- + %%% User API + start() -> +- webtool:start(), +- webtool:start_tools([],"app=vts"). ++ ct_webtool:start(), ++ ct_webtool:start_tools([],"app=vts"). + + init_data(ConfigFiles,EvHandlers,LogDir,LogOpts,Tests) -> + call({init_data,ConfigFiles,EvHandlers,LogDir,LogOpts,Tests}). + + stop() -> +- webtool:stop_tools([],"app=vts"), +- webtool:stop(). ++ ct_webtool:stop_tools([],"app=vts"), ++ ct_webtool:stop(). + + report(What,Data) -> + call({report,What,Data}). + + %%%----------------------------------------------------------------- +-%%% Return config data used by webtool ++%%% Return config data used by ct_webtool + config_data() -> + {ok,LogDir} = + case lists:keysearch(logdir,1,init:get_arguments()) of +diff --git lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl +index a145d85..d01211b 100644 +--- lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl ++++ lib/common_test/test/ct_netconfc_SUITE_data/netconfc1_SUITE.erl +@@ -164,7 +164,7 @@ hello_from_server_first(Config) -> + {ok,Client} = ct_netconfc:only_open(?DEFAULT_SSH_OPTS(DataDir)), + ct:sleep(500), + ?NS:expect(hello), +- ?ok = ct_netconfc:hello(Client), ++ ?ok = ct_netconfc:hello(Client, [{capability, ["urn:com:ericsson:ebase:1.1.0"]}], infinity), + ?NS:expect_do_reply('close-session',close,ok), + ?ok = ct_netconfc:close_session(Client), + ok. +@@ -490,13 +490,16 @@ action(Config) -> + Data = [{myactionreturn,[{xmlns,"myns"}],["value"]}], + %% test either to receive {data,Data} or {ok,Data}, + %% both need to be handled +- {Reply,RetVal} = case element(3, now()) rem 2 of +- 0 -> {{data,Data},{ok,Data}}; +- 1 -> {{ok,Data},ok} +- end, +- ct:log("Client will receive {~w,Data}", [element(1,Reply)]), +- ?NS:expect_reply(action,Reply), +- RetVal = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}), ++ ct:log("Client will receive {~w,~p}", [data,Data]), ++ ct:log("Expecting ~p", [{ok, Data}]), ++ ?NS:expect_reply(action,{data, Data}), ++ {ok, Data} = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}), ++ ++ ct:log("Client will receive {~w,~p}", [ok,Data]), ++ ct:log("Expecting ~p", [ok]), ++ ?NS:expect_reply(action,{ok, Data}), ++ ok = ct_netconfc:action(Client,{myaction,[{xmlns,"myns"}],[]}), ++ + ?NS:expect_do_reply('close-session',close,ok), + ?ok = ct_netconfc:close_session(Client), + ok. +diff --git lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl +index 1d3f591..9dc9095 100644 +--- lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl ++++ lib/common_test/test/ct_telnet_SUITE_data/ct_telnet_own_server_SUITE.erl +@@ -40,6 +40,7 @@ all() -> + expect, + expect_repeat, + expect_sequence, ++ expect_wait_until_prompt, + expect_error_prompt, + expect_error_timeout1, + expect_error_timeout2, +@@ -81,6 +82,8 @@ end_per_group(_GroupName, Config) -> + expect(_) -> + {ok, Handle} = ct_telnet:open(telnet_server_conn1), + ok = ct_telnet:send(Handle, "echo ayt"), ++ {ok,["ayt"]} = ct_telnet:expect(Handle, "ayt"), ++ ok = ct_telnet:send(Handle, "echo ayt"), + {ok,["ayt"]} = ct_telnet:expect(Handle, ["ayt"]), + ok = ct_telnet:close(Handle), + ok. +@@ -103,6 +106,21 @@ expect_sequence(_) -> + ok = ct_telnet:close(Handle), + ok. + ++%% Check that expect can wait for delayed prompt ++expect_wait_until_prompt(_) -> ++ {ok, Handle} = ct_telnet:open(telnet_server_conn1), ++ Timeouts = [{idle_timeout,5000},{total_timeout,7000}], ++ ++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 xxx"), ++ {ok,["xxx"]} = ++ ct_telnet:expect(Handle, "xxx", ++ [wait_for_prompt|Timeouts]), ++ ok = ct_telnet:send(Handle, "echo_delayed_prompt 3000 yyy zzz"), ++ {ok,[["yyy"],["zzz"]]} = ++ ct_telnet:expect(Handle, ["yyy","zzz"], ++ [{wait_for_prompt,"> "}|Timeouts]), ++ ok. ++ + %% Check that expect returns when a prompt is found, even if pattern + %% is not matched. + expect_error_prompt(_) -> +diff --git lib/common_test/test/telnet_server.erl lib/common_test/test/telnet_server.erl +index 11959c3..2db5a9b 100644 +--- lib/common_test/test/telnet_server.erl ++++ lib/common_test/test/telnet_server.erl +@@ -242,6 +242,12 @@ do_handle_data("echo_loop " ++ Data,State) -> + ReturnData = string:join(Lines,"\n"), + send_loop(list_to_integer(TStr),ReturnData,State), + {ok,State}; ++do_handle_data("echo_delayed_prompt "++Data,State) -> ++ [MsStr|EchoData] = string:tokens(Data, " "), ++ send(string:join(EchoData,"\n"),State), ++ ct:sleep(list_to_integer(MsStr)), ++ send("\r\n> ",State), ++ {ok,State}; + do_handle_data("disconnect_after " ++WaitStr,State) -> + Wait = list_to_integer(string:strip(WaitStr,right,$\n)), + dbg("Server will close connection in ~w ms...", [Wait]), +diff --git lib/common_test/vsn.mk lib/common_test/vsn.mk +index d654a8a..e2d9217 100644 +--- lib/common_test/vsn.mk ++++ lib/common_test/vsn.mk +@@ -1 +1 @@ +-COMMON_TEST_VSN = 1.10 ++COMMON_TEST_VSN = 1.10.1 +diff --git lib/diameter/doc/src/diameter.xml lib/diameter/doc/src/diameter.xml +index 6e41b01..ea175a5 100644 +--- lib/diameter/doc/src/diameter.xml ++++ lib/diameter/doc/src/diameter.xml +@@ -1820,7 +1820,8 @@ The information presented here is as in the <c>connect</c> case except + that the client connections are grouped under an <c>accept</c> tuple.</p> + + <p> +-Whether or not the &transport_opt; <c>pool_size</c> affects the format ++Whether or not the &transport_opt; <c>pool_size</c> has been ++configured affects the format + of the listing in the case of a connecting transport, since a value + greater than 1 implies multiple transport processes for the same + <c>&transport_ref;</c>, as in the listening case. +diff --git lib/diameter/doc/src/notes.xml lib/diameter/doc/src/notes.xml +index 479fab2..6931788 100644 +--- lib/diameter/doc/src/notes.xml ++++ lib/diameter/doc/src/notes.xml +@@ -42,6 +42,47 @@ first.</p> + + <!-- ===================================================================== --> + ++<section><title>diameter 1.9.1</title> ++ ++ <section><title>Known Bugs and Problems</title> ++ <list> ++ <item> ++ <p> ++ Don't leave extra bit in decoded AVP data.</p> ++ <p> ++ OTP-12074 in OTP 17.3 missed one case: a length error on ++ a trailing AVP unknown to the dictionary in question.</p> ++ <p> ++ Own Id: OTP-12642</p> ++ </item> ++ <item> ++ <p> ++ Don't confuse Result-Code and Experimental-Result</p> ++ <p> ++ The errors field of a decoded diameter_packet record was ++ populated with a Result-Code AVP when an ++ Experimental-Result containing a 3xxx Result-Code was ++ received in an answer not setting the E-bit. The correct ++ AVP is now extracted from the incoming message.</p> ++ <p> ++ Own Id: OTP-12654 Aux Id: seq12851 </p> ++ </item> ++ <item> ++ <p> ++ Don't count on unknown Application Id.</p> ++ <p> ++ OTP-11721 in OTP 17.1 missed the case of an Application ++ Id not agreeing with that of the dictionary in question, ++ causing counters to be accumulated on keys containing the ++ unknown id.</p> ++ <p> ++ Own Id: OTP-12701</p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>diameter 1.9</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git lib/diameter/include/diameter_gen.hrl lib/diameter/include/diameter_gen.hrl +index 0eef218..e8ffe7f 100644 +--- lib/diameter/include/diameter_gen.hrl ++++ lib/diameter/include/diameter_gen.hrl +@@ -445,7 +445,7 @@ reset(_, _) -> + %% undecoded. Note that the type field is 'undefined' in this case. + + decode_AVP(Name, Avp, {Avps, Acc}) -> +- {[Avp | Avps], pack_AVP(Name, Avp, Acc)}. ++ {[trim(Avp) | Avps], pack_AVP(Name, Avp, Acc)}. + + %% rc/1 + +diff --git lib/diameter/src/base/diameter_codec.erl lib/diameter/src/base/diameter_codec.erl +index 15a4c5e..bf2fe8e 100644 +--- lib/diameter/src/base/diameter_codec.erl ++++ lib/diameter/src/base/diameter_codec.erl +@@ -640,8 +640,12 @@ split_data(Bin, Len) -> + %% payload if this is a request. Do this (in cases that we + %% know the type) by inducing a decode failure and letting + %% the dictionary's decode (in diameter_gen) deal with it. +- %% Here we don't know type. If the type isn't known, then +- %% the decode just strips the extra bit. ++ %% ++ %% Note that the extra bit can only occur in the trailing ++ %% AVP of a message or Grouped AVP, since a faulty AVP ++ %% Length is otherwise indistinguishable from a correct ++ %% one here, since we don't know the types of the AVPs ++ %% being extracted. + {<<0:1, Bin/binary>>, <<>>} + end. + +@@ -690,8 +694,8 @@ pack_avp(#diameter_avp{code = undefined, data = B}) + Len = size(<<H:5/binary, _:24, T/binary>> = <<B/binary, 0:Pad>>), + <<H/binary, Len:24, T/binary>>; + +-%% ... from a dictionary compiled against old code in diameter_gen ... + %% ... when ignoring errors in Failed-AVP ... ++%% ... during a relay encode ... + pack_avp(#diameter_avp{data = <<0:1, B/binary>>} = A) -> + pack_avp(A#diameter_avp{data = B}); + +diff --git lib/diameter/src/base/diameter_traffic.erl lib/diameter/src/base/diameter_traffic.erl +index 538ebee..ffd2c0a 100644 +--- lib/diameter/src/base/diameter_traffic.erl ++++ lib/diameter/src/base/diameter_traffic.erl +@@ -980,8 +980,8 @@ answer_message(OH, OR, RC, Dict0, #diameter_packet{avps = Avps, + session_id(Code, Vid, Dict0, Avps) + when is_list(Avps) -> + try +- {value, #diameter_avp{data = D}} = find_avp(Code, Vid, Avps), +- [{'Session-Id', [Dict0:avp(decode, D, 'Session-Id')]}] ++ #diameter_avp{data = Bin} = find_avp(Code, Vid, Avps), ++ [{'Session-Id', [Dict0:avp(decode, Bin, 'Session-Id')]}] + catch + error: _ -> + [] +@@ -998,26 +998,17 @@ failed_avp(_, [] = No) -> + + %% find_avp/3 + +-find_avp(Code, Vid, Avps) +- when is_integer(Code), (undefined == Vid orelse is_integer(Vid)) -> +- find(fun(A) -> is_avp(Code, Vid, A) end, Avps). ++%% Grouped ... ++find_avp(Code, VId, [[#diameter_avp{code = Code, vendor_id = VId} | _] = As ++ | _]) -> ++ As; + +-%% The final argument here could be a list of AVP's, depending on the case, +-%% but we're only searching at the top level. +-is_avp(Code, Vid, #diameter_avp{code = Code, vendor_id = Vid}) -> +- true; +-is_avp(_, _, _) -> +- false. ++%% ... or not. ++find_avp(Code, VId, [#diameter_avp{code = Code, vendor_id = VId} = A | _]) -> ++ A; + +-find(_, []) -> +- false; +-find(Pred, [H|T]) -> +- case Pred(H) of +- true -> +- {value, H}; +- false -> +- find(Pred, T) +- end. ++find_avp(Code, VId, [_ | Avps]) -> ++ find_avp(Code, VId, Avps). + + %% 7. Error Handling + %% +@@ -1086,7 +1077,6 @@ incr_result(_, #diameter_packet{msg = undefined = No}, _, _) -> + incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) -> + #diameter_packet{header = #diameter_header{is_error = E} + = Hdr, +- msg = Msg, + errors = Es} + = Pkt, + +@@ -1096,13 +1086,13 @@ incr_result(Dir, Pkt, TPid, {Dict, AppDict, Dict0}) -> + recv /= Dir orelse [] == Es orelse incr_error(Dir, Id, TPid, AppDict), + + %% Exit on a missing result code. +- T = rc_counter(Dict, Msg), ++ T = rc_counter(Dict, Dir, Pkt), + T == false andalso ?LOGX(no_result_code, {Dict, Dir, Hdr}), +- {Ctr, RC} = T, ++ {Ctr, RC, Avp} = T, + + %% Or on an inappropriate value. + is_result(RC, E, Dict0) +- orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, RC}), ++ orelse ?LOGX(invalid_error_bit, {Dict, Dir, Hdr, Avp}), + + incr(TPid, {Id, Dir, Ctr}), + Ctr. +@@ -1116,19 +1106,15 @@ msg_id(#diameter_packet{header = H}, Dict) -> + %% there are 2^32 (application ids) * 2^24 (command codes) = 2^56 + %% pairs for an attacker to choose from. + msg_id(Hdr, Dict) -> +- {_ApplId, Code, R} = Id = diameter_codec:msg_id(Hdr), +- case Dict:msg_name(Code, 0 == R) of +- '' -> +- unknown(Dict:id(), R); +- _ -> +- Id ++ {Aid, Code, R} = Id = diameter_codec:msg_id(Hdr), ++ if Aid == ?APP_ID_RELAY -> ++ {relay, R}; ++ true -> ++ choose(Aid /= Dict:id() orelse '' == Dict:msg_name(Code, 0 == R), ++ unknown, ++ Id) + end. + +-unknown(?APP_ID_RELAY, R) -> +- {relay, R}; +-unknown(_, _) -> +- unknown. +- + %% No E-bit: can't be 3xxx. + is_result(RC, false, _Dict0) -> + RC < 3000 orelse 4000 =< RC; +@@ -1148,7 +1134,7 @@ is_result(RC, true, _) -> + incr(TPid, Counter) -> + diameter_stats:incr(Counter, TPid, 1). + +-%% rc_counter/2 ++%% rc_counter/3 + + %% RFC 3588, 7.6: + %% +@@ -1156,39 +1142,45 @@ incr(TPid, Counter) -> + %% applications MUST include either one Result-Code AVP or one + %% Experimental-Result AVP. + ++rc_counter(Dict, recv, #diameter_packet{header = H, avps = As}) -> ++ rc_counter(Dict, [H|As]); ++ ++rc_counter(Dict, _, #diameter_packet{msg = Msg}) -> ++ rc_counter(Dict, Msg). ++ + rc_counter(Dict, Msg) -> +- rcc(Dict, Msg, int(get_avp_value(Dict, 'Result-Code', Msg))). ++ rcc(get_result(Dict, Msg)). + +-rcc(Dict, Msg, undefined) -> +- rcc(get_avp_value(Dict, 'Experimental-Result', Msg)); +- +-rcc(_, _, N) ++rcc(#diameter_avp{name = 'Result-Code' = Name, value = N} = A) + when is_integer(N) -> +- {{'Result-Code', N}, N}. ++ {{Name, N}, N, A}; + +-%% Outgoing answers may be in any of the forms messages can be sent +-%% in. Incoming messages will be records. We're assuming here that the +-%% arity of the result code AVP's is 0 or 1. ++rcc(#diameter_avp{name = 'Result-Code' = Name, value = [N|_]} = A) ++ when is_integer(N) -> ++ {{Name, N}, N, A}; + +-rcc([{_,_,N} = T | _]) ++rcc(#diameter_avp{name = 'Experimental-Result', value = {_,_,N} = T} = A) + when is_integer(N) -> +- {T,N}; +-rcc({_,_,N} = T) ++ {T, N, A}; ++ ++rcc(#diameter_avp{name = 'Experimental-Result', value = [{_,_,N} = T|_]} = A) + when is_integer(N) -> +- {T,N}; ++ {T, N, A}; ++ + rcc(_) -> + false. + +-%% Extract the first good looking integer. There's no guarantee +-%% that what we're looking for has arity 1. +-int([N|_]) +- when is_integer(N) -> +- N; +-int(N) +- when is_integer(N) -> +- N; +-int(_) -> +- undefined. ++%% get_result/2 ++ ++get_result(Dict, Msg) -> ++ try ++ [throw(A) || N <- ['Result-Code', 'Experimental-Result'], ++ #diameter_avp{} = A <- [get_avp(Dict, N, Msg)]] ++ of ++ [] -> false ++ catch ++ #diameter_avp{} = A -> A ++ end. + + x(T) -> + exit(T). +@@ -1528,10 +1520,10 @@ handle_A(Pkt, SvcName, Dict, Dict0, App, #request{transport = TPid} = Req) -> + %% a missing AVP. If both are optional in the dictionary + %% then this isn't a decode error: just continue on. + answer(Pkt, SvcName, App, Req); +- exit: {invalid_error_bit, {_, _, _, RC}} -> ++ exit: {invalid_error_bit, {_, _, _, Avp}} -> + #diameter_packet{errors = Es} + = Pkt, +- E = {5004, #diameter_avp{name = 'Result-Code', value = RC}}, ++ E = {5004, Avp}, + answer(Pkt#diameter_packet{errors = [E|Es]}, SvcName, App, Req) + end. + +@@ -1868,7 +1860,7 @@ str([]) -> + str(T) -> + T. + +-%% get_avp_value/3 ++%% get_avp/3 + %% + %% Find an AVP in a message of one of three forms: + %% +@@ -1885,47 +1877,71 @@ str(T) -> + %% look for are in the common dictionary. This is required since the + %% relay dictionary doesn't inherit the common dictionary (which maybe + %% it should). +-get_avp_value(?RELAY, Name, Msg) -> +- get_avp_value(?BASE, Name, Msg); ++get_avp(?RELAY, Name, Msg) -> ++ get_avp(?BASE, Name, Msg); + +-%% Message sent as a header/avps list, probably a relay case but not +-%% necessarily. +-get_avp_value(Dict, Name, [#diameter_header{} | Avps]) -> ++%% Message as a header/avps list. ++get_avp(Dict, Name, [#diameter_header{} | Avps]) -> + try + {Code, _, VId} = Dict:avp_header(Name), +- [A|_] = lists:dropwhile(fun(#diameter_avp{code = C, vendor_id = V}) -> +- C /= Code orelse V /= VId +- end, +- Avps), +- avp_decode(Dict, Name, A) ++ find_avp(Code, VId, Avps) ++ of ++ A -> ++ avp_decode(Dict, Name, ungroup(A)) + catch + error: _ -> + undefined + end; + + %% Outgoing message as a name/values list. +-get_avp_value(_, Name, [_MsgName | Avps]) -> ++get_avp(_, Name, [_MsgName | Avps]) -> + case lists:keyfind(Name, 1, Avps) of + {_, V} -> +- V; ++ #diameter_avp{name = Name, value = V}; + _ -> + undefined + end; + + %% Message is typically a record but not necessarily. +-get_avp_value(Dict, Name, Rec) -> ++get_avp(Dict, Name, Rec) -> + try +- Dict:'#get-'(Name, Rec) ++ #diameter_avp{name = Name, value = Dict:'#get-'(Name, Rec)} + catch + error:_ -> + undefined + end. + ++%% get_avp_value/3 ++ ++get_avp_value(Dict, Name, Msg) -> ++ case get_avp(Dict, Name, Msg) of ++ #diameter_avp{value = V} -> ++ V; ++ undefined = No -> ++ No ++ end. ++ ++%% ungroup/1 ++ ++ungroup([Avp|_]) -> ++ Avp; ++ungroup(Avp) -> ++ Avp. ++ ++%% avp_decode/3 ++ + avp_decode(Dict, Name, #diameter_avp{value = undefined, +- data = Bin}) -> +- Dict:avp(decode, Bin, Name); +-avp_decode(_, _, #diameter_avp{value = V}) -> +- V. ++ data = Bin} ++ = Avp) -> ++ try Dict:avp(decode, Bin, Name) of ++ V -> ++ Avp#diameter_avp{value = V} ++ catch ++ error:_ -> ++ Avp ++ end; ++avp_decode(_, _, #diameter_avp{} = Avp) -> ++ Avp. + + cb(#diameter_app{module = [_|_] = M}, F, A) -> + eval(M, F, A); +diff --git lib/diameter/src/diameter.appup.src lib/diameter/src/diameter.appup.src +index a54eb24..0ef0fd3 100644 +--- lib/diameter/src/diameter.appup.src ++++ lib/diameter/src/diameter.appup.src +@@ -35,32 +35,10 @@ + {"1.4.3", [{restart_application, diameter}]}, %% R16B02 + {"1.4.4", [{restart_application, diameter}]}, + {"1.5", [{restart_application, diameter}]}, %% R16B03 +- {"1.6", [{load_module, diameter_lib}, %% 17.0 +- {load_module, diameter_traffic}, +- {load_module, diameter_watchdog}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_service}, +- {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_gen_acct_rfc6733}, +- {load_module, diameter_gen_base_rfc3588}, +- {load_module, diameter_gen_base_accounting}, +- {load_module, diameter_gen_relay}, +- {load_module, diameter_codec}, +- {load_module, diameter_sctp}]}, +- {"1.7", [{load_module, diameter_service}, %% 17.1 +- {load_module, diameter_codec}, +- {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_gen_acct_rfc6733}, +- {load_module, diameter_gen_base_rfc3588}, +- {load_module, diameter_gen_base_accounting}, +- {load_module, diameter_gen_relay}, +- {load_module, diameter_traffic}, +- {load_module, diameter_peer_fsm}]}, +- {"1.7.1", [{load_module, diameter_traffic}, %% 17.3 +- {load_module, diameter_watchdog}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_service}]}, +- {"1.8", [{load_module, diameter_lib}, %% 17.4 ++ {"1.6", [{restart_application, diameter}]}, %% 17.0 ++ {"1.7", [{restart_application, diameter}]}, %% 17.[12] ++ {<<"^1\\.(7\\.1|8)$">>, %% 17.[34] ++ [{load_module, diameter_lib}, + {load_module, diameter_peer}, + {load_module, diameter_reg}, + {load_module, diameter_session}, +@@ -84,7 +62,14 @@ + {load_module, diameter_gen_relay}, + {update, diameter_transport_sup, supervisor}, + {update, diameter_service_sup, supervisor}, +- {update, diameter_sup, supervisor}]} ++ {update, diameter_sup, supervisor}]}, ++ {"1.9", [{load_module, diameter_codec}, %% 17.5 ++ {load_module, diameter_traffic}, ++ {load_module, diameter_gen_base_rfc6733}, ++ {load_module, diameter_gen_acct_rfc6733}, ++ {load_module, diameter_gen_base_rfc3588}, ++ {load_module, diameter_gen_base_accounting}, ++ {load_module, diameter_gen_relay}]} + ], + [ + {"0.9", [{restart_application, diameter}]}, +@@ -102,55 +87,40 @@ + {"1.4.3", [{restart_application, diameter}]}, + {"1.4.4", [{restart_application, diameter}]}, + {"1.5", [{restart_application, diameter}]}, +- {"1.6", [{load_module, diameter_sctp}, +- {load_module, diameter_codec}, ++ {"1.6", [{restart_application, diameter}]}, ++ {"1.7", [{restart_application, diameter}]}, ++ {<<"^1\\.(7\\.1|8)$">>, ++ [{update, diameter_sup, supervisor}, ++ {update, diameter_service_sup, supervisor}, ++ {update, diameter_transport_sup, supervisor}, + {load_module, diameter_gen_relay}, + {load_module, diameter_gen_base_accounting}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_service}, +- {load_module, diameter_peer_fsm}, ++ {load_module, diameter}, ++ {load_module, diameter_config}, ++ {load_module, diameter_sctp}, ++ {load_module, diameter_tcp}, + {load_module, diameter_watchdog}, ++ {load_module, diameter_peer_fsm}, ++ {load_module, diameter_service}, + {load_module, diameter_traffic}, ++ {load_module, diameter_types}, ++ {load_module, diameter_codec}, ++ {load_module, diameter_capx}, ++ {load_module, diameter_sync}, ++ {load_module, diameter_stats}, ++ {load_module, diameter_session}, ++ {load_module, diameter_reg}, ++ {load_module, diameter_peer}, + {load_module, diameter_lib}]}, +- {"1.7", [{load_module, diameter_peer_fsm}, +- {load_module, diameter_traffic}, +- {load_module, diameter_gen_relay}, +- {load_module, diameter_gen_base_accounting}, +- {load_module, diameter_gen_base_rfc3588}, +- {load_module, diameter_gen_acct_rfc6733}, +- {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter_codec}, +- {load_module, diameter_service}]}, +- {"1.7.1", [{load_module, diameter_service}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_watchdog}, +- {load_module, diameter_traffic}]}, +- {"1.8", [{update, diameter_sup, supervisor}, +- {update, diameter_service_sup, supervisor}, +- {update, diameter_transport_sup, supervisor}, +- {load_module, diameter_gen_relay}, ++ {"1.9", [{load_module, diameter_gen_relay}, + {load_module, diameter_gen_base_accounting}, + {load_module, diameter_gen_base_rfc3588}, + {load_module, diameter_gen_acct_rfc6733}, + {load_module, diameter_gen_base_rfc6733}, +- {load_module, diameter}, +- {load_module, diameter_config}, +- {load_module, diameter_sctp}, +- {load_module, diameter_tcp}, +- {load_module, diameter_watchdog}, +- {load_module, diameter_peer_fsm}, +- {load_module, diameter_service}, + {load_module, diameter_traffic}, +- {load_module, diameter_types}, +- {load_module, diameter_codec}, +- {load_module, diameter_capx}, +- {load_module, diameter_sync}, +- {load_module, diameter_stats}, +- {load_module, diameter_session}, +- {load_module, diameter_reg}, +- {load_module, diameter_peer}, +- {load_module, diameter_lib}]} ++ {load_module, diameter_codec}]} + ] + }. +diff --git lib/diameter/test/diameter_3xxx_SUITE.erl lib/diameter/test/diameter_3xxx_SUITE.erl +index 071b1a1..44fc3a6 100644 +--- lib/diameter/test/diameter_3xxx_SUITE.erl ++++ lib/diameter/test/diameter_3xxx_SUITE.erl +@@ -47,6 +47,7 @@ + send_double_error/1, + send_3xxx/1, + send_5xxx/1, ++ counters/1, + stop/1]). + + %% diameter callbacks +@@ -111,7 +112,7 @@ all() -> + + groups() -> + Tc = tc(), +- [{?util:name([E,D]), [], [start] ++ Tc ++ [stop]} ++ [{?util:name([E,D]), [], [start] ++ Tc ++ [counters, stop]} + || E <- ?ERRORS, D <- ?RFCS]. + + init_per_suite(Config) -> +@@ -169,6 +170,203 @@ stop(_Config) -> + ok = diameter:stop_service(?SERVER), + ok = diameter:stop_service(?CLIENT). + ++%% counters/1 ++%% ++%% Check that counters are as expected. ++ ++counters(Config) -> ++ Group = proplists:get_value(group, Config), ++ [_Errors, _Rfc] = G = ?util:name(Group), ++ [] = ?util:run([[fun counters/3, K, S, G] ++ || K <- [statistics, transport, connections], ++ S <- [?CLIENT, ?SERVER]]). ++ ++counters(Key, Svc, Group) -> ++ counters(Key, Svc, Group, [_|_] = diameter:service_info(Svc, Key)). ++ ++counters(statistics, Svc, [Errors, Rfc], L) -> ++ [{P, Stats}] = L, ++ true = is_pid(P), ++ stats(Svc, Errors, Rfc, lists:sort(Stats)); ++ ++counters(_, _, _, _) -> ++ todo. ++ ++stats(?CLIENT, E, rfc3588, L) ++ when E == answer; ++ E == answer_3xxx -> ++ [{{unknown,recv},2}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},6}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3001}},1}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',3008}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},1}] ++ = L; ++ ++stats(?SERVER, E, rfc3588, L) ++ when E == answer; ++ E == answer_3xxx -> ++ [{{unknown,recv},1}, ++ {{unknown,send},2}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},6}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3001}},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',3008}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, answer, rfc6733, L) -> ++ [{{unknown,recv},2}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},8}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3001}},1}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',3008}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},3}, ++ {{{0,275,0},recv,{'Result-Code',5999}},1}] ++ = L; ++ ++stats(?SERVER, answer, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},2}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},8}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3001}},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',3008}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},3}, ++ {{{0,275,0},send,{'Result-Code',5999}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, answer_3xxx, rfc6733, L) -> ++ [{{unknown,recv},2}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},8}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3001}},1}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',3008}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},2}, ++ {{{0,275,0},recv,{'Result-Code',5999}},1}] ++ = L; ++ ++stats(?SERVER, answer_3xxx, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},2}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},8}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3001}},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',3008}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},2}, ++ {{{0,275,0},send,{'Result-Code',5999}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, callback, rfc3588, L) -> ++ [{{unknown,recv},1}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},6}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},2}] ++ = L; ++ ++stats(?SERVER, callback, rfc3588, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},1}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},6}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},2}, ++ {{{0,275,1},recv,error},5}] ++ = L; ++ ++stats(?CLIENT, callback, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{{0,257,0},recv},1}, ++ {{{0,257,1},send},1}, ++ {{{0,275,0},recv},8}, ++ {{{0,275,1},send},10}, ++ {{unknown,recv,{'Result-Code',3007}},1}, ++ {{{0,257,0},recv,{'Result-Code',2001}},1}, ++ {{{0,275,0},recv,{'Result-Code',2001}},2}, ++ {{{0,275,0},recv,{'Result-Code',3999}},1}, ++ {{{0,275,0},recv,{'Result-Code',5002}},1}, ++ {{{0,275,0},recv,{'Result-Code',5005}},3}, ++ {{{0,275,0},recv,{'Result-Code',5999}},1}] ++ = L; ++ ++stats(?SERVER, callback, rfc6733, L) -> ++ [{{unknown,recv},1}, ++ {{unknown,send},1}, ++ {{{0,257,0},send},1}, ++ {{{0,257,1},recv},1}, ++ {{{0,275,0},send},8}, ++ {{{0,275,1},recv},8}, ++ {{unknown,recv,error},1}, ++ {{unknown,send,{'Result-Code',3007}},1}, ++ {{{0,257,0},send,{'Result-Code',2001}},1}, ++ {{{0,275,0},send,{'Result-Code',2001}},2}, ++ {{{0,275,0},send,{'Result-Code',3999}},1}, ++ {{{0,275,0},send,{'Result-Code',5002}},1}, ++ {{{0,275,0},send,{'Result-Code',5005}},3}, ++ {{{0,275,0},send,{'Result-Code',5999}},1}, ++ {{{0,275,1},recv,error},5}] ++ = L. ++ + %% send_unknown_application/1 + %% + %% Send an unknown application that a callback (which shouldn't take +diff --git lib/diameter/test/diameter_app_SUITE.erl lib/diameter/test/diameter_app_SUITE.erl +index 6975e83..84f8a66 100644 +--- lib/diameter/test/diameter_app_SUITE.erl ++++ lib/diameter/test/diameter_app_SUITE.erl +@@ -249,11 +249,10 @@ release() -> + end. + + unversion(App) -> +- T = lists:dropwhile(fun is_vsn_ch/1, lists:reverse(App)), +- lists:reverse(case T of [$-|TT] -> TT; _ -> T end). +- +-is_vsn_ch(C) -> +- $0 =< C andalso C =< $9 orelse $. == C. ++ {Name, [$-|Vsn]} = lists:splitwith(fun(C) -> C /= $- end, App), ++ true = is_app(Name), %% assert ++ Vsn = vsn_str(Vsn), %% ++ Name. + + app('$M_EXPR') -> %% could be anything but assume it's ok + "erts"; +@@ -322,11 +321,11 @@ acc_rel(Dir, Rel, {Vsn, _}, Acc) -> + + %% Write a rel file and return its name. + write_rel(Dir, [Erts | Apps], Vsn) -> +- true = is_vsn(Vsn), +- Name = "diameter_test_" ++ Vsn, ++ VS = vsn_str(Vsn), ++ Name = "diameter_test_" ++ VS, + ok = write_file(filename:join([Dir, Name ++ ".rel"]), + {release, +- {"diameter " ++ Vsn ++ " test release", Vsn}, ++ {"diameter " ++ VS ++ " test release", VS}, + Erts, + Apps}), + Name. +@@ -341,10 +340,34 @@ fetch(Key, List) -> + write_file(Path, T) -> + file:write_file(Path, io_lib:format("~p.", [T])). + +-%% Is a version string of the expected form? Return the argument +-%% itself for 'false' for a useful badmatch. ++%% Is a version string of the expected form? + is_vsn(V) -> +- is_list(V) +- andalso length(V) == string:span(V, "0123456789.") +- andalso V == string:join(string:tokens(V, [$.]), ".") %% no ".." +- orelse {error, V}. ++ V = vsn_str(V), ++ true. ++ ++%% Turn a from/to version in appup to a version string after ensuring ++%% that it's valid version number of regexp. In the regexp case, the ++%% regexp itself becomes the version string since there's no ++%% requirement that a version in appup be anything but a string. The ++%% restrictions placed on string-valued version numbers (that they be ++%% '.'-separated integers) are our own. ++ ++vsn_str(S) ++ when is_list(S) -> ++ {_, match} = {S, match(S, "^(0|[1-9][0-9]*)(\\.(0|[1-9][0-9]*))*$")}, ++ {_, nomatch} = {S, match(S, "\\.0\\.0$")}, ++ S; ++ ++vsn_str(B) ++ when is_binary(B) -> ++ {ok, _} = re:compile(B), ++ binary_to_list(B). ++ ++match(S, RE) -> ++ re:run(S, RE, [{capture, none}]). ++ ++%% Is an application name of the expected form? ++is_app(S) ++ when is_list(S) -> ++ {_, match} = {S, match(S, "^([a-z]([a-z_]*|[a-zA-Z]*))$")}, ++ true. +diff --git lib/diameter/test/diameter_traffic_SUITE.erl lib/diameter/test/diameter_traffic_SUITE.erl +index 7dd9f39..7ff6ba7 100644 +--- lib/diameter/test/diameter_traffic_SUITE.erl ++++ lib/diameter/test/diameter_traffic_SUITE.erl +@@ -41,6 +41,7 @@ + send_eval/1, + send_bad_answer/1, + send_protocol_error/1, ++ send_experimental_result/1, + send_arbitrary/1, + send_unknown/1, + send_unknown_short/1, +@@ -301,6 +302,7 @@ tc() -> + send_eval, + send_bad_answer, + send_protocol_error, ++ send_experimental_result, + send_arbitrary, + send_unknown, + send_unknown_short, +@@ -443,7 +445,7 @@ send_ok(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 1}], + +- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req). + + %% Send an accounting ACR that the server answers badly to. +@@ -459,7 +461,7 @@ send_eval(Config) -> + Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, + {'Accounting-Record-Number', 3}], + +- ['ACA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['ACA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req). + + %% Send an accounting ACR that the server tries to answer with an +@@ -480,12 +482,20 @@ send_protocol_error(Config) -> + ?answer_message(?TOO_BUSY) + = call(Config, Req). + ++%% Send a 3xxx Experimental-Result in an answer not setting the E-bit ++%% and missing a Result-Code. ++send_experimental_result(Config) -> ++ Req = ['ACR', {'Accounting-Record-Type', ?EVENT_RECORD}, ++ {'Accounting-Record-Number', 5}], ++ ['ACA', {'Session-Id', _} | _] ++ = call(Config, Req). ++ + %% Send an ASR with an arbitrary non-mandatory AVP and expect success + %% and the same AVP in the reply. + send_arbitrary(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{name = 'Product-Name', + value = "XXX"}]}], +- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps] + = call(Config, Req), + {'AVP', [#diameter_avp{name = 'Product-Name', + value = V}]} +@@ -497,7 +507,7 @@ send_unknown(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 999, + is_mandatory = false, + data = <<17>>}]}], +- ['ASA', _SessionId, {'Result-Code', ?SUCCESS} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | Avps] + = call(Config, Req), + {'AVP', [#diameter_avp{code = 999, + is_mandatory = false, +@@ -513,7 +523,7 @@ send_unknown_short(Config, M, RC) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 999, + is_mandatory = M, + data = <<17>>}]}], +- ['ASA', _SessionId, {'Result-Code', RC} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', RC} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), +@@ -527,7 +537,7 @@ send_unknown_mandatory(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 999, + is_mandatory = true, + data = <<17>>}]}], +- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), +@@ -547,7 +557,7 @@ send_unexpected_mandatory_decode(Config) -> + Req = ['ASR', {'AVP', [#diameter_avp{code = 27, %% Session-Timeout + is_mandatory = true, + data = <<12:32>>}]}], +- ['ASA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] ++ ['ASA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | Avps] + = call(Config, Req), + [#'diameter_base_Failed-AVP'{'AVP' = As}] + = proplists:get_value('Failed-AVP', Avps), +@@ -583,7 +593,7 @@ send_error_bit(Config) -> + %% Send a bad version and check that we get 5011. + send_unsupported_version(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], +- ['STA', _SessionId, {'Result-Code', ?UNSUPPORTED_VERSION} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?UNSUPPORTED_VERSION} | _] + = call(Config, Req). + + %% Send a request containing an AVP length > data size. +@@ -603,14 +613,14 @@ send_zero_avp_length(Config) -> + send_invalid_avp_length(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + +- ['STA', _SessionId, ++ ['STA', {'Session-Id', _}, + {'Result-Code', ?INVALID_AVP_LENGTH}, +- _OriginHost, +- _OriginRealm, +- _UserName, +- _Class, +- _ErrorMessage, +- _ErrorReportingHost, ++ {'Origin-Host', _}, ++ {'Origin-Realm', _}, ++ {'User-Name', _}, ++ {'Class', _}, ++ {'Error-Message', _}, ++ {'Error-Reporting-Host', _}, + {'Failed-AVP', [#'diameter_base_Failed-AVP'{'AVP' = [_]}]} + | _] + = call(Config, Req). +@@ -628,14 +638,14 @@ send_invalid_reject(Config) -> + send_unexpected_mandatory(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + +- ['STA', _SessionId, {'Result-Code', ?AVP_UNSUPPORTED} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?AVP_UNSUPPORTED} | _] + = call(Config, Req). + + %% Send something long that will be fragmented by TCP. + send_long(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}, + {'User-Name', [lists:duplicate(1 bsl 20, $X)]}], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req). + + %% Send something longer than the configure incoming_maxlen. +@@ -677,7 +687,7 @@ send_any_2(Config) -> + send_all_1(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], + Realm = lists:foldr(fun(C,A) -> [C,A] end, [], ?REALM), +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req, [{filter, {all, [{host, any}, + {realm, Realm}]}}]). + send_all_2(Config) -> +@@ -697,9 +707,8 @@ send_timeout(Config) -> + %% received the Session-Id. + send_error(Config) -> + Req = ['RAR', {'Re-Auth-Request-Type', ?AUTHORIZE_AUTHENTICATE}], +- ?answer_message(SId, ?TOO_BUSY) +- = call(Config, Req), +- true = undefined /= SId. ++ ?answer_message([_], ?TOO_BUSY) ++ = call(Config, Req). + + %% Send a request with the detached option and receive it as a message + %% from handle_answer instead. +@@ -708,7 +717,7 @@ send_detach(Config) -> + Ref = make_ref(), + ok = call(Config, Req, [{extra, [{self(), Ref}]}, detach]), + Ans = receive {Ref, T} -> T end, +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = Ans. + + %% Send a request which can't be encoded and expect {error, encode}. +@@ -721,11 +730,11 @@ send_destination_1(Config) -> + = group(Config), + Req = ['STR', {'Termination-Cause', ?LOGOUT}, + {'Destination-Host', [?HOST(SN, ?REALM)]}], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req, [{filter, {all, [host, realm]}}]). + send_destination_2(Config) -> + Req = ['STR', {'Termination-Cause', ?LOGOUT}], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, Req, [{filter, {all, [host, realm]}}]). + + %% Send with filtering on and expect failure when specifying an +@@ -789,7 +798,7 @@ send_bad_filter(Config, F) -> + %% Specify multiple filter options and expect them be conjunctive. + send_multiple_filters_1(Config) -> + Fun = fun(#diameter_caps{}) -> true end, +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = send_multiple_filters(Config, [host, {eval, Fun}]). + send_multiple_filters_2(Config) -> + E = {erlang, is_tuple, []}, +@@ -800,7 +809,7 @@ send_multiple_filters_3(Config) -> + E2 = {erlang, is_tuple, []}, + E3 = {erlang, is_record, [diameter_caps]}, + E4 = [{erlang, is_record, []}, diameter_caps], +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = send_multiple_filters(Config, [{eval, E} || E <- [E1,E2,E3,E4]]). + + send_multiple_filters(Config, Fs) -> +@@ -811,7 +820,7 @@ send_multiple_filters(Config, Fs) -> + %% only the return value from the prepare_request callback being + %% significant. + send_anything(Config) -> +- ['STA', _SessionId, {'Result-Code', ?SUCCESS} | _] ++ ['STA', {'Session-Id', _}, {'Result-Code', ?SUCCESS} | _] + = call(Config, anything). + + %% =========================================================================== +@@ -1144,6 +1153,13 @@ answer(Pkt, Req, _Peer, Name, #group{client_dict0 = Dict0}) -> + [R | Vs] = Dict:'#get-'(answer(Ans, Es, Name)), + [Dict:rec2msg(R) | Vs]. + ++%% Missing Result-Codec and inapproriate Experimental-Result-Code. ++answer(Rec, Es, send_experimental_result) -> ++ [{5004, #diameter_avp{name = 'Experimental-Result'}}, ++ {5005, #diameter_avp{name = 'Result-Code'}}] ++ = Es, ++ Rec; ++ + %% An inappropriate E-bit results in a decode error ... + answer(Rec, Es, send_bad_answer) -> + [{5004, #diameter_avp{name = 'Result-Code'}} | _] = Es, +@@ -1175,7 +1191,9 @@ handle_error(Reason, _Req, [$C|_], _Peer, _, _Time) -> + %% Note that diameter will set Result-Code and Failed-AVPs if + %% #diameter_packet.errors is non-null. + +-handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) -> ++handle_request(#diameter_packet{header = H, msg = M, avps = As}, ++ _, ++ {_Ref, Caps}) -> + #diameter_header{end_to_end_id = EI, + hop_by_hop_id = HI} + = H, +@@ -1183,10 +1201,12 @@ handle_request(#diameter_packet{header = H, msg = M}, _, {_Ref, Caps}) -> + V = EI bsr B, %% assert + V = HI bsr B, %% + #diameter_caps{origin_state_id = {_,[Id]}} = Caps, +- answer(origin(Id), request(M, Caps)). ++ answer(origin(Id), request(M, [H|As], Caps)). + + answer(T, {Tag, Action, Post}) -> + {Tag, answer(T, Action), Post}; ++answer(_, {reply, [#diameter_header{} | _]} = T) -> ++ T; + answer({A,C}, {reply, Ans}) -> + answer(C, {reply, msg(Ans, A, diameter_gen_base_rfc3588)}); + answer(pkt, {reply, Ans}) +@@ -1195,6 +1215,41 @@ answer(pkt, {reply, Ans}) + answer(_, T) -> + T. + ++%% request/3 ++ ++%% send_experimental_result ++request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 5}, ++ [Hdr | Avps], ++ #diameter_caps{origin_host = {OH, _}, ++ origin_realm = {OR, _}}) -> ++ [H,R|T] = [A || N <- ['Origin-Host', ++ 'Origin-Realm', ++ 'Session-Id', ++ 'Accounting-Record-Type', ++ 'Accounting-Record-Number'], ++ #diameter_avp{} = A ++ <- [lists:keyfind(N, #diameter_avp.name, Avps)]], ++ Ans = [Hdr#diameter_header{is_request = false}, ++ H#diameter_avp{data = OH}, ++ R#diameter_avp{data = OR}, ++ #diameter_avp{name = 'Experimental-Result', ++ code = 297, ++ need_encryption = false, ++ data = [#diameter_avp{data = {?DIAMETER_DICT_COMMON, ++ 'Vendor-Id', ++ 123}}, ++ #diameter_avp{data ++ = {?DIAMETER_DICT_COMMON, ++ 'Experimental-Result-Code', ++ 3987}}]} ++ | T], ++ {reply, Ans}; ++ ++request(Msg, _Avps, Caps) -> ++ request(Msg, Caps). ++ ++%% request/2 ++ + %% send_nok + request(#diameter_base_accounting_ACR{'Accounting-Record-Number' = 0}, + _) -> +diff --git lib/diameter/vsn.mk lib/diameter/vsn.mk +index c00bac2..db7f72c 100644 +--- lib/diameter/vsn.mk ++++ lib/diameter/vsn.mk +@@ -16,5 +16,5 @@ + # %CopyrightEnd% + + APPLICATION = diameter +-DIAMETER_VSN = 1.9 ++DIAMETER_VSN = 1.9.1 + APP_VSN = $(APPLICATION)-$(DIAMETER_VSN)$(PRE_VSN) +diff --git lib/snmp/doc/src/notes.xml lib/snmp/doc/src/notes.xml +index fd307ef..52022f5 100644 +--- lib/snmp/doc/src/notes.xml ++++ lib/snmp/doc/src/notes.xml +@@ -33,7 +33,40 @@ + </header> + + +- <section> ++ <section><title>SNMP 5.1.2</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ A bug in the SNMP Agent has been corrected; when opening ++ a port using the command line argument -snmpa_fd the Port ++ should be 0 when calling gen_udp:open.</p> ++ <p> ++ A bug in the SNMP manager has been corrected; it should ++ not look at the -snmp_fd command line argument, but ++ instead at -snmpm_fd.</p> ++ <p> ++ Own Id: OTP-12669 Aux Id: seq12841 </p> ++ </item> ++ </list> ++ </section> ++ ++ ++ <section><title>Improvements and New Features</title> ++ <list> ++ <item> ++ <p> ++ Improved cryptocraphic capability.</p> ++ <p> ++ Own Id: OTP-12452</p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ ++<section> + <title>SNMP Development Toolkit 5.1.1</title> + <p>Version 5.1.1 supports code replacement in runtime from/to + version 5.1. </p> +diff --git lib/snmp/src/agent/snmpa_net_if.erl lib/snmp/src/agent/snmpa_net_if.erl +index 840d56d..57d63ba 100644 +--- lib/snmp/src/agent/snmpa_net_if.erl ++++ lib/snmp/src/agent/snmpa_net_if.erl +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 2004-2014. All Rights Reserved. ++%% Copyright Ericsson AB 2004-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -297,14 +297,14 @@ socket_open(snmpUDPDomain = Domain, [IpPort | Opts]) -> + Fd = list_to_integer(FdStr), + ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p", + [Domain, IpPort, Opts, Fd]), +- gen_udp_open(IpPort, [{fd, Fd} | Opts]); ++ gen_udp_open(0, [{fd, Fd} | Opts]); + error -> + case init:get_argument(snmpa_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + ?vdebug("socket_open(~p, [~p | ~p]) Fd: ~p", + [Domain, IpPort, Opts, Fd]), +- gen_udp_open(IpPort, [{fd, Fd} | Opts]); ++ gen_udp_open(0, [{fd, Fd} | Opts]); + error -> + ?vdebug("socket_open(~p, [~p | ~p])", + [Domain, IpPort, Opts]), +diff --git lib/snmp/src/app/snmp.appup.src lib/snmp/src/app/snmp.appup.src +index e7e54f5..081163b 100644 +--- lib/snmp/src/app/snmp.appup.src ++++ lib/snmp/src/app/snmp.appup.src +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 1999-2014. All Rights Reserved. ++%% Copyright Ericsson AB 1999-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -28,6 +28,7 @@ + %% {update, snmpa_local_db, soft, soft_purge, soft_purge, []} + %% {add_module, snmpm_net_if_mt} + [ ++ {"5.1.1", [{restart_application, snmp}]}, + {"5.1", [ % Only compiler changes + ]}, + {"5.0", [{restart_application, snmp}]}, +@@ -46,6 +47,7 @@ + %% {remove, {snmpm_net_if_mt, soft_purge, soft_purge}} + + [ ++ {"5.1.1", [{restart_application, snmp}]}, + {"5.1", [ % Only compiler changes + ]}, + {"5.0", [{restart_application, snmp}]}, +diff --git lib/snmp/src/manager/snmpm_net_if.erl lib/snmp/src/manager/snmpm_net_if.erl +index b4cc165..0e1c51c 100644 +--- lib/snmp/src/manager/snmpm_net_if.erl ++++ lib/snmp/src/manager/snmpm_net_if.erl +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 2004-2014. All Rights Reserved. ++%% Copyright Ericsson AB 2004-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -330,7 +330,7 @@ socket_params(Domain, {IpAddr, IpPort} = Addr, BindTo, CommonSocketOpts) -> + end, + case Family of + inet -> +- case init:get_argument(snmp_fd) of ++ case init:get_argument(snmpm_fd) of + {ok, [[FdStr]]} -> + Fd = list_to_integer(FdStr), + case BindTo of +diff --git lib/snmp/src/manager/snmpm_server.erl lib/snmp/src/manager/snmpm_server.erl +index a75122d..8fc3359 100644 +--- lib/snmp/src/manager/snmpm_server.erl ++++ lib/snmp/src/manager/snmpm_server.erl +@@ -1,7 +1,7 @@ + %% + %% %CopyrightBegin% + %% +-%% Copyright Ericsson AB 2004-2014. All Rights Reserved. ++%% Copyright Ericsson AB 2004-2015. All Rights Reserved. + %% + %% The contents of this file are subject to the Erlang Public License, + %% Version 1.1, (the "License"); you may not use this file except in +@@ -2116,7 +2116,8 @@ do_handle_agent(DefUserId, DefMod, + ok; + + InvalidResult -> +- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData], ++ CallbackArgs = ++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData], + handle_invalid_result(handle_agent, CallbackArgs, InvalidResult) + + catch +@@ -2212,7 +2213,8 @@ do_handle_agent(DefUserId, DefMod, + end; + + T:E -> +- CallbackArgs = [Domain, Addr, Type, SnmpInfo, DefData], ++ CallbackArgs = ++ [Domain_or_Ip, Addr_or_Port, Type, SnmpInfo, DefData], + handle_invalid_result(handle_agent, CallbackArgs, T, E) + + end. +diff --git lib/snmp/vsn.mk lib/snmp/vsn.mk +index 345cc79..67adf0a 100644 +--- lib/snmp/vsn.mk ++++ lib/snmp/vsn.mk +@@ -2,7 +2,7 @@ + + # %CopyrightBegin% + # +-# Copyright Ericsson AB 1997-2014. All Rights Reserved. ++# Copyright Ericsson AB 1997-2015. All Rights Reserved. + # + # The contents of this file are subject to the Erlang Public License, + # Version 1.1, (the "License"); you may not use this file except in +@@ -18,6 +18,6 @@ + # %CopyrightEnd% + + APPLICATION = snmp +-SNMP_VSN = 5.1.1 ++SNMP_VSN = 5.1.2 + PRE_VSN = + APP_VSN = "$(APPLICATION)-$(SNMP_VSN)$(PRE_VSN)" +diff --git lib/test_server/doc/src/notes.xml lib/test_server/doc/src/notes.xml +index f21c32a..e996d2b 100644 +--- lib/test_server/doc/src/notes.xml ++++ lib/test_server/doc/src/notes.xml +@@ -32,6 +32,28 @@ + <file>notes.xml</file> + </header> + ++<section><title>Test_Server 3.8.1</title> ++ ++ <section><title>Fixed Bugs and Malfunctions</title> ++ <list> ++ <item> ++ <p> ++ If the last expression in a test case causes a timetrap ++ timeout, the stack trace is ignored and not printed to ++ the test case log file. This happens because the ++ {Suite,TestCase,Line} info is not available in the stack ++ trace in this scenario, due to tail call elimination. ++ Common Test has been modified to handle this situation by ++ inserting a {Suite,TestCase,last_expr} tuple in the ++ correct place and printing the stack trace as expected.</p> ++ <p> ++ Own Id: OTP-12697 Aux Id: seq12848 </p> ++ </item> ++ </list> ++ </section> ++ ++</section> ++ + <section><title>Test_Server 3.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> +diff --git lib/test_server/src/erl2html2.erl lib/test_server/src/erl2html2.erl +index 7cfaa2c..50dbbb8 100644 +--- lib/test_server/src/erl2html2.erl ++++ lib/test_server/src/erl2html2.erl +@@ -117,9 +117,10 @@ parse_preprocessed_file(Epp,File,InCorrectFile) -> + parse_preprocessed_file(Epp,File,true); + {attribute,_,file,{_OtherFile,_}} -> + parse_preprocessed_file(Epp,File,false); +- {function,L,F,A,[_|C]} when InCorrectFile -> +- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C], +- [{atom_to_list(F),A,L} | Clauses] ++ ++ {function,L,F,A,Cs} when InCorrectFile -> ++ {CLs,LastCL} = find_clause_lines(Cs, []), ++ %% tl(CLs) cause we know the start line already ++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++ + parse_preprocessed_file(Epp,File,true); + _ -> + parse_preprocessed_file(Epp,File,InCorrectFile) +@@ -146,9 +147,10 @@ parse_non_preprocessed_file(Epp, File, Location) -> + case epp_dodger:parse_form(Epp, Location) of + {ok,Tree,Location1} -> + try erl_syntax:revert(Tree) of +- {function,L,F,A,[_|C]} -> +- Clauses = [{clause,CL} || {clause,CL,_,_,_} <- C], +- [{atom_to_list(F),A,L} | Clauses] ++ ++ {function,L,F,A,Cs} -> ++ {CLs,LastCL} = find_clause_lines(Cs, []), ++ %% tl(CLs) cause we know the start line already ++ [{atom_to_list(F),A,L,LastCL} | tl(CLs)] ++ + parse_non_preprocessed_file(Epp, File, Location1); + _ -> + parse_non_preprocessed_file(Epp, File, Location1) +@@ -162,22 +164,48 @@ parse_non_preprocessed_file(Epp, File, Location) -> + end. + + %%%----------------------------------------------------------------- ++%%% Find the line number of the last expression in the function ++find_clause_lines([{clause,CL,_Params,_Op,Exprs}], CLs) -> % last clause ++ try tuple_to_list(lists:last(Exprs)) of ++ [_Type,ExprLine | _] -> ++ {lists:reverse([{clause,CL}|CLs]), ExprLine}; ++ _ -> ++ {lists:reverse([{clause,CL}|CLs]), CL} ++ catch ++ _:_ -> ++ {lists:reverse([{clause,CL}|CLs]), CL} ++ end; ++ ++find_clause_lines([{clause,CL,_Params,_Op,_Exprs} | Cs], CLs) -> ++ find_clause_lines(Cs, [{clause,CL}|CLs]). ++ ++%%%----------------------------------------------------------------- + %%% Add a link target for each line and one for each function definition. +-build_html(SFd,DFd,Encoding,Functions) -> +- build_html(SFd,DFd,Encoding,file:read_line(SFd),1,Functions,false). ++build_html(SFd,DFd,Encoding,FuncsAndCs) -> ++ build_html(SFd,DFd,Encoding,file:read_line(SFd),1,FuncsAndCs, ++ false,undefined). + +-build_html(SFd,DFd,Encoding,{ok,Str},L,[{F,A,L}|Functions],_IsFuncDef) -> ++%% function start line found ++build_html(SFd,DFd,Enc,{ok,Str},L0,[{F,A,L0,LastL}|FuncsAndCs], ++ _IsFuncDef,_FAndLastL) -> + FALink = test_server_ctrl:uri_encode(F++"-"++integer_to_list(A),utf8), +- file:write(DFd,["<a name=\"",to_raw_list(FALink,Encoding),"\"/>"]), +- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true); +-build_html(SFd,DFd,Encoding,{ok,Str},L,[{clause,L}|Functions],_IsFuncDef) -> +- build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,true); +-build_html(SFd,DFd,Encoding,{ok,Str},L,Functions,IsFuncDef) -> ++ file:write(DFd,["<a name=\"",to_raw_list(FALink,Enc),"\"/>"]), ++ build_html(SFd,DFd,Enc,{ok,Str},L0,FuncsAndCs,true,{F,LastL}); ++%% line of last expression in function found ++build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,_IsFuncDef,{F,LastL}) -> ++ LastLineLink = test_server_ctrl:uri_encode(F++"-last_expr",utf8), ++ file:write(DFd,["<a name=\"", ++ to_raw_list(LastLineLink,Enc),"\"/>"]), ++ build_html(SFd,DFd,Enc,{ok,Str},LastL,FuncsAndCs,true,undefined); ++build_html(SFd,DFd,Enc,{ok,Str},L,[{clause,L}|FuncsAndCs], ++ _IsFuncDef,FAndLastL) -> ++ build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,true,FAndLastL); ++build_html(SFd,DFd,Enc,{ok,Str},L,FuncsAndCs,IsFuncDef,FAndLastL) -> + LStr = line_number(L), + Str1 = line(Str,IsFuncDef), + file:write(DFd,[LStr,Str1]), +- build_html(SFd,DFd,Encoding,file:read_line(SFd),L+1,Functions,false); +-build_html(_SFd,_DFd,_Encoding,eof,L,_Functions,_IsFuncDef) -> ++ build_html(SFd,DFd,Enc,file:read_line(SFd),L+1,FuncsAndCs,false,FAndLastL); ++build_html(_SFd,_DFd,_Enc,eof,L,_FuncsAndCs,_IsFuncDef,_FAndLastL) -> + L. + + line_number(L) -> +diff --git lib/test_server/src/test_server.erl lib/test_server/src/test_server.erl +index 8d91778..1c33525 100644 +--- lib/test_server/src/test_server.erl ++++ lib/test_server/src/test_server.erl +@@ -1355,12 +1355,30 @@ get_loc(Pid) -> + Stk = [rewrite_loc_item(Loc) || Loc <- Stk0], + case get(test_server_loc) of + [{Suite,Case}] -> +- %% location info unknown, check if {Suite,Case,Line} +- %% is available in stacktrace. and if so, use stacktrace +- %% instead of current test_server_loc ++ %% Location info unknown, check if {Suite,Case,Line} ++ %% is available in stacktrace and if so, use stacktrace ++ %% instead of current test_server_loc. ++ %% If location is the last expression in a test case ++ %% function, the info is not available due to tail call ++ %% elimination. We need to check if the test case has been ++ %% called by ts_tc/3 and, if so, insert the test case info ++ %% at that position. + case [match || {S,C,_L} <- Stk, S == Suite, C == Case] of +- [match|_] -> put(test_server_loc, Stk); +- _ -> ok ++ [match|_] -> ++ put(test_server_loc, Stk); ++ _ -> ++ {PreTC,PostTC} = ++ lists:splitwith(fun({test_server,ts_tc,_}) -> ++ false; ++ (_) -> ++ true ++ end, Stk), ++ if PostTC == [] -> ++ ok; ++ true -> ++ put(test_server_loc, ++ PreTC++[{Suite,Case,last_expr} | PostTC]) ++ end + end; + _ -> + put(test_server_loc, Stk) +@@ -1422,7 +1440,10 @@ lookup_config(Key,Config) -> + undefined + end. + +-%% timer:tc/3 ++%% ++%% IMPORTANT: get_loc/1 uses the name of this function when analysing ++%% stack traces. If the name changes, get_loc/1 must be updated! ++%% + ts_tc(M, F, A) -> + Before = erlang:now(), + Result = try +diff --git lib/test_server/src/test_server_sup.erl lib/test_server/src/test_server_sup.erl +index 96e369a..15a6fdd 100644 +--- lib/test_server/src/test_server_sup.erl ++++ lib/test_server/src/test_server_sup.erl +@@ -61,33 +61,37 @@ timetrap(Timeout0, ReportTVal, Scale, Pid) -> + TruncTO = trunc(Timeout), + receive + after TruncTO -> +- case is_process_alive(Pid) of +- true -> +- TimeToReport = if Timeout0 == ReportTVal -> TruncTO; +- true -> ReportTVal end, +- MFLs = test_server:get_loc(Pid), +- Mon = erlang:monitor(process, Pid), +- Trap = {timetrap_timeout,TimeToReport,MFLs}, +- exit(Pid, Trap), +- receive +- {'DOWN', Mon, process, Pid, _} -> +- ok +- after 10000 -> +- %% Pid is probably trapping exits, hit it harder... +- catch error_logger:warning_msg( +- "Testcase process ~w not " +- "responding to timetrap " +- "timeout:~n" +- " ~p.~n" +- "Killing testcase...~n", +- [Pid, Trap]), +- exit(Pid, kill) +- end; +- false -> ++ kill_the_process(Pid, Timeout0, TruncTO, ReportTVal) ++ end. ++ ++kill_the_process(Pid, Timeout0, TruncTO, ReportTVal) -> ++ case is_process_alive(Pid) of ++ true -> ++ TimeToReport = if Timeout0 == ReportTVal -> TruncTO; ++ true -> ReportTVal end, ++ MFLs = test_server:get_loc(Pid), ++ Mon = erlang:monitor(process, Pid), ++ Trap = {timetrap_timeout,TimeToReport,MFLs}, ++ exit(Pid, Trap), ++ receive ++ {'DOWN', Mon, process, Pid, _} -> + ok +- end ++ after 10000 -> ++ %% Pid is probably trapping exits, hit it harder... ++ catch error_logger:warning_msg( ++ "Testcase process ~w not " ++ "responding to timetrap " ++ "timeout:~n" ++ " ~p.~n" ++ "Killing testcase...~n", ++ [Pid, Trap]), ++ exit(Pid, kill) ++ end; ++ false -> ++ ok + end. + ++ + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %% timetrap_cancel(Handle) -> ok + %% Handle = term() +@@ -812,10 +816,19 @@ format_loc1({Mod,Func,Line}) -> + case {lists:member(no_src, get(test_server_logopts)), + lists:reverse(ModStr)} of + {false,[$E,$T,$I,$U,$S,$_|_]} -> +- io_lib:format("{~w,~w,<a href=\"~ts~ts#~w\">~w</a>}", ++ Link = if is_integer(Line) -> ++ integer_to_list(Line); ++ Line == last_expr -> ++ list_to_atom(atom_to_list(Func)++"-last_expr"); ++ is_atom(Line) -> ++ atom_to_list(Line); ++ true -> ++ Line ++ end, ++ io_lib:format("{~w,~w,<a href=\"~ts~ts#~s\">~w</a>}", + [Mod,Func, + test_server_ctrl:uri_encode(downcase(ModStr)), +- ?src_listing_ext,Line,Line]); ++ ?src_listing_ext,Link,Line]); + _ -> + io_lib:format("{~w,~w,~w}",[Mod,Func,Line]) + end. +diff --git lib/test_server/vsn.mk lib/test_server/vsn.mk +index 77225b4..2a2ed2b 100644 +--- lib/test_server/vsn.mk ++++ lib/test_server/vsn.mk +@@ -1 +1 @@ +-TEST_SERVER_VSN = 3.8 ++TEST_SERVER_VSN = 3.8.1 +diff --git otp_versions.table otp_versions.table +index 4bf6cb9..12790c8 100644 +--- otp_versions.table ++++ otp_versions.table +@@ -1,3 +1,4 @@ ++OTP-17.5.3 : common_test-1.10.1 diameter-1.9.1 erts-6.4.1 snmp-5.1.2 test_server-3.8.1 # asn1-3.0.4 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.7 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 ssh-3.2.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : + OTP-17.5.2 : inets-5.10.7 ssh-3.2.2 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : + OTP-17.5.1 : ssh-3.2.1 # asn1-3.0.4 common_test-1.10 compiler-5.0.4 cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 edoc-0.7.16 eldap-1.1.1 erl_docgen-0.3.7 erl_interface-3.7.20 erts-6.4 et-1.5 eunit-2.2.9 gs-1.5.16 hipe-3.11.3 ic-4.3.6 inets-5.10.6 jinterface-1.5.12 kernel-3.2 megaco-3.17.3 mnesia-4.12.5 observer-2.0.4 odbc-2.10.22 orber-3.7.1 os_mon-2.3.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 public_key-0.23 reltool-0.6.6 runtime_tools-1.8.16 sasl-2.4.1 snmp-5.1.1 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 typer-0.9.8 webtool-0.8.10 wx-1.3.3 xmerl-1.3.7 : + OTP-17.5 : asn1-3.0.4 common_test-1.10 compiler-5.0.4 crypto-3.5 debugger-4.0.3 dialyzer-2.7.4 diameter-1.9 eldap-1.1.1 erts-6.4 hipe-3.11.3 inets-5.10.6 kernel-3.2 mnesia-4.12.5 observer-2.0.4 os_mon-2.3.1 public_key-0.23 runtime_tools-1.8.16 ssh-3.2 ssl-6.0 stdlib-2.4 syntax_tools-1.6.18 test_server-3.8 tools-2.7.2 wx-1.3.3 # cosEvent-2.1.15 cosEventDomain-1.1.14 cosFileTransfer-1.1.16 cosNotification-1.1.21 cosProperty-1.1.17 cosTime-1.1.14 cosTransactions-1.2.14 edoc-0.7.16 erl_docgen-0.3.7 erl_interface-3.7.20 et-1.5 eunit-2.2.9 gs-1.5.16 ic-4.3.6 jinterface-1.5.12 megaco-3.17.3 odbc-2.10.22 orber-3.7.1 ose-1.0.2 otp_mibs-1.0.10 parsetools-2.0.12 percept-0.8.10 reltool-0.6.6 sasl-2.4.1 snmp-5.1.1 typer-0.9.8 webtool-0.8.10 xmerl-1.3.7 : |