diff options
Diffstat (limited to 'lib/inets/test')
-rw-r--r-- | lib/inets/test/ftp_suite_lib.erl | 35 | ||||
-rw-r--r-- | lib/inets/test/httpc_SUITE.erl | 560 | ||||
-rw-r--r-- | lib/inets/test/httpd_SUITE.erl | 304 | ||||
-rw-r--r-- | lib/inets/test/httpd_test_lib.erl | 91 | ||||
-rw-r--r-- | lib/inets/test/inets_test_lib.erl | 151 |
5 files changed, 818 insertions, 323 deletions
diff --git a/lib/inets/test/ftp_suite_lib.erl b/lib/inets/test/ftp_suite_lib.erl index d0d07a8358..3ebd02229e 100644 --- a/lib/inets/test/ftp_suite_lib.erl +++ b/lib/inets/test/ftp_suite_lib.erl @@ -1129,10 +1129,16 @@ ticket_6035(Config) -> LogFile = filename:join([PrivDir,"ticket_6035.log"]), try begin + p("ticket_6035 -> select ftpd host"), Host = dirty_select_ftpd_host(Config), + p("ticket_6035 -> ftpd host selected (~p) => now spawn ftp owner", [Host]), Pid = spawn(?MODULE, open_wait_6035, [Host, self()]), + p("ticket_6035 -> waiter spawned: ~p => now open error logfile (~p)", + [Pid, LogFile]), error_logger:logfile({open, LogFile}), - ok = kill_ftp_proc_6035(Pid,LogFile), + p("ticket_6035 -> error logfile open => now kill waiter process"), + true = kill_ftp_proc_6035(Pid, LogFile), + p("ticket_6035 -> waiter process killed => now close error logfile"), error_logger:logfile(close), p("ticket_6035 -> done", []), ok @@ -1146,7 +1152,7 @@ kill_ftp_proc_6035(Pid, LogFile) -> p("kill_ftp_proc_6035 -> entry"), receive open -> - p("kill_ftp_proc_6035 -> received open: send shutdown"), + p("kill_ftp_proc_6035 -> received open => now issue shutdown"), exit(Pid, shutdown), kill_ftp_proc_6035(Pid, LogFile); {open_failed, Reason} -> @@ -1159,11 +1165,11 @@ kill_ftp_proc_6035(Pid, LogFile) -> is_error_report_6035(LogFile) end. -open_wait_6035(FtpServer, From) -> - p("open_wait_6035 -> try connect to ~s", [FtpServer]), +open_wait_6035({Tag, FtpServer}, From) -> + p("open_wait_6035 -> try connect to [~p] ~s for ~p", [Tag, FtpServer, From]), case ftp:open(FtpServer, [{timeout, timer:seconds(15)}]) of {ok, Pid} -> - p("open_wait_6035 -> connected, now login"), + p("open_wait_6035 -> connected (~p), now login", [Pid]), LoginResult = ftp:user(Pid,"anonymous","kldjf"), p("open_wait_6035 -> login result: ~p", [LoginResult]), From ! open, @@ -1191,22 +1197,27 @@ is_error_report_6035(LogFile) -> Res = case file:read_file(LogFile) of {ok, Bin} -> - p("is_error_report_6035 -> logfile read"), - read_log_6035(binary_to_list(Bin)); + Txt = binary_to_list(Bin), + p("is_error_report_6035 -> logfile read: ~n~p", [Txt]), + read_log_6035(Txt); _ -> - ok + false end, p("is_error_report_6035 -> logfile read result: " "~n ~p", [Res]), - file:delete(LogFile), + %% file:delete(LogFile), Res. read_log_6035("=ERROR REPORT===="++_Rest) -> - error_report; -read_log_6035([_H|T]) -> + p("read_log_6035 -> ERROR REPORT detected"), + true; +read_log_6035([H|T]) -> + p("read_log_6035 -> OTHER: " + "~p", [H]), read_log_6035(T); read_log_6035([]) -> - ok. + p("read_log_6035 -> done"), + false. %%-------------------------------------------------------------------- diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 1998bd3950..6edd5371af 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -64,16 +64,6 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [ - proxy_options, - proxy_head, - proxy_get, - proxy_trace, - proxy_post, - proxy_put, - proxy_delete, - proxy_auth, - proxy_headers, - proxy_emulate_lower_versions, http_options, http_head, http_get, @@ -88,15 +78,6 @@ all() -> http_headers, http_headers_dummy, http_bad_response, - ssl_head, - ossl_head, - essl_head, - ssl_get, - ossl_get, - essl_get, - ssl_trace, - ossl_trace, - essl_trace, http_redirect, http_redirect_loop, http_internal_server_error, @@ -106,21 +87,44 @@ all() -> http_emulate_lower_versions, http_relaxed, page_does_not_exist, - proxy_page_does_not_exist, - proxy_https_not_supported, - http_stream, - http_stream_once, - proxy_stream, parse_url, options, - ipv6, headers_as_is, + {group, proxy}, + {group, ssl}, + {group, stream}, + {group, ipv6}, {group, tickets}, initial_server_connect ]. groups() -> - [{tickets, [], [hexed_query_otp_6191, + [ + {proxy, [], [proxy_options, + proxy_head, + proxy_get, + proxy_trace, + proxy_post, + proxy_put, + proxy_delete, + proxy_auth, + proxy_headers, + proxy_emulate_lower_versions, + proxy_page_does_not_exist, + proxy_https_not_supported]}, + {ssl, [], [ssl_head, + ossl_head, + essl_head, + ssl_get, + ossl_get, + essl_get, + ssl_trace, + ossl_trace, + essl_trace]}, + {stream, [], [http_stream, + http_stream_once, + proxy_stream]}, + {tickets, [], [hexed_query_otp_6191, empty_body_otp_6243, empty_response_header_otp_6830, transfer_encoding_otp_6807, @@ -139,7 +143,10 @@ groups() -> {otp_8154, [], [otp_8154_1]}, {otp_8106, [], [otp_8106_pid, otp_8106_fun, - otp_8106_mfa]}]. + otp_8106_mfa]}, + {ipv6, [], [ipv6_ipcomm, ipv6_essl]} + ]. + init_per_group(_GroupName, Config) -> @@ -213,36 +220,38 @@ end_per_suite(Config) -> %% Note: This function is free to add any key/value pairs to the Config %% variable, but should NOT alter/remove any existing entries. %%-------------------------------------------------------------------- + init_per_testcase(otp_8154_1 = Case, Config) -> init_per_testcase(Case, 5, Config); -init_per_testcase(initial_server_connect, Config) -> +init_per_testcase(initial_server_connect = Case, Config) -> %% Try to check if crypto actually exist or not, %% this test case does not work unless it does - case (catch crypto:start()) of - ok -> - application:start(public_key), - application:start(ssl), + try + begin + ensure_started(crypto), + ensure_started(public_key), + ensure_started(ssl), inets:start(), - Config; - _ -> - {skip,"Could not start crypto"} + Config + end + catch + throw:{error, {failed_starting, App, ActualError}} -> + tsp("init_per_testcase(~w) -> failed starting ~w: " + "~n ~p", [Case, App, ActualError]), + SkipString = + "Could not start " ++ atom_to_list(App), + {skip, SkipString}; + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + {skip, SkipString} end; init_per_testcase(Case, Config) -> init_per_testcase(Case, 2, Config). -init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) -> - tsp("init_per_testcase_ssl -> stop ssl"), - application:stop(ssl), - Config2 = lists:keydelete(local_ssl_server, 1, Config), - %% Will start inets - tsp("init_per_testcase_ssl -> try start http server (including inets)"), - Server = inets_test_lib:start_http_server( - filename:join(PrivDir, SslConfFile), Tag), - tsp("init_per_testcase -> Server: ~p", [Server]), - [{local_ssl_server, Server} | Config2]. - init_per_testcase(Case, Timeout, Config) -> io:format(user, "~n~n*** INIT ~w:~w[~w] ***~n~n", [?MODULE, Case, Timeout]), @@ -261,13 +270,16 @@ init_per_testcase(Case, Timeout, Config) -> NewConfig = case atom_to_list(Case) of [$s, $s, $l | _] -> - init_per_testcase_ssl(ssl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + init_per_testcase_ssl(ssl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); [$o, $s, $s, $l | _] -> - init_per_testcase_ssl(ossl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + init_per_testcase_ssl(ossl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); [$e, $s, $s, $l | _] -> - init_per_testcase_ssl(essl, PrivDir, SslConfFile, [{watchdog, Dog} | TmpConfig]); + init_per_testcase_ssl(essl, PrivDir, SslConfFile, + [{watchdog, Dog} | TmpConfig]); "proxy_" ++ Rest -> io:format("init_per_testcase -> Rest: ~p~n", [Rest]), @@ -275,16 +287,23 @@ init_per_testcase(Case, Timeout, Config) -> "https_not_supported" -> tsp("init_per_testcase -> [proxy case] start inets"), inets:start(), - tsp("init_per_testcase -> [proxy case] start ssl"), - application:start(crypto), - application:start(public_key), - case (catch application:start(ssl)) of + tsp("init_per_testcase -> " + "[proxy case] start crypto, public_key and ssl"), + try ensure_started([crypto, public_key, ssl]) of ok -> - [{watchdog, Dog} | TmpConfig]; - _ -> - [{skip, "SSL does not seem to be supported"} - | TmpConfig] + [{watchdog, Dog} | TmpConfig] + catch + throw:{error, {failed_starting, App, _}} -> + SkipString = + "Could not start " ++ atom_to_list(App), + {skip, SkipString}; + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + {skip, SkipString} end; + _ -> %% We use erlang.org for the proxy tests %% and after the switch to erlang-web, many @@ -321,6 +340,33 @@ init_per_testcase(Case, Timeout, Config) -> [{skip, "proxy not responding"} | TmpConfig] end end; + + "ipv6_" ++ _Rest -> + %% Ensure needed apps (crypto, public_key and ssl) started + try ensure_started([crypto, public_key, ssl]) of + ok -> + Profile = ipv6, + %% A stand-alone profile is represented by a pid() + {ok, ProfilePid} = + inets:start(httpc, + [{profile, Profile}, + {data_dir, PrivDir}], stand_alone), + httpc:set_options([{ipfamily, inet6}], ProfilePid), + tsp("httpc profile pid: ~p", [ProfilePid]), + [{watchdog, Dog}, {profile, ProfilePid}| TmpConfig] + catch + throw:{error, {failed_starting, App, ActualError}} -> + tsp("init_per_testcase(~w) -> failed starting ~w: " + "~n ~p", [Case, App, ActualError]), + SkipString = + "Could not start " ++ atom_to_list(App), + {skip, SkipString}; + _:X -> + SkipString = + lists:flatten( + io_lib:format("Failed starting apps: ~p", [X])), + {skip, SkipString} + end; _ -> TmpConfig2 = lists:keydelete(local_server, 1, TmpConfig), Server = @@ -330,9 +376,7 @@ init_per_testcase(Case, Timeout, Config) -> [{watchdog, Dog}, {local_server, Server} | TmpConfig2] end, - %% httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, - %% ["localhost", ?IPV6_LOCAL_HOST]}}]), - + %% This will fail for the ipv6_ - cases (but that is ok) httpc:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, ["localhost", ?IPV6_LOCAL_HOST]}}, {ipfamily, inet6fb4}]), @@ -341,6 +385,19 @@ init_per_testcase(Case, Timeout, Config) -> NewConfig. +init_per_testcase_ssl(Tag, PrivDir, SslConfFile, Config) -> + tsp("init_per_testcase_ssl(~w) -> stop ssl", [Tag]), + application:stop(ssl), + Config2 = lists:keydelete(local_ssl_server, 1, Config), + %% Will start inets + tsp("init_per_testcase_ssl(~w) -> try start http server (including inets)", + [Tag]), + Server = inets_test_lib:start_http_server( + filename:join(PrivDir, SslConfFile), Tag), + tsp("init_per_testcase(~w) -> Server: ~p", [Tag, Server]), + [{local_ssl_server, Server} | Config2]. + + %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ %% Case - atom() @@ -349,13 +406,36 @@ init_per_testcase(Case, Timeout, Config) -> %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- -end_per_testcase(http_save_to_file, Config) -> - PrivDir = ?config(priv_dir, Config), +end_per_testcase(http_save_to_file = Case, Config) -> + io:format(user, "~n~n*** END ~w:~w ***~n~n", + [?MODULE, Case]), + PrivDir = ?config(priv_dir, Config), FullPath = filename:join(PrivDir, "dummy.html"), file:delete(FullPath), finish(Config); -end_per_testcase(_, Config) -> +end_per_testcase(Case, Config) -> + io:format(user, "~n~n*** END ~w:~w ***~n~n", + [?MODULE, Case]), + case atom_to_list(Case) of + "ipv6_" ++ _Rest -> + tsp("end_per_testcase(~w) -> stop ssl", [Case]), + application:stop(ssl), + tsp("end_per_testcase(~w) -> stop public_key", [Case]), + application:stop(public_key), + tsp("end_per_testcase(~w) -> stop crypto", [Case]), + application:stop(crypto), + ProfilePid = ?config(profile, Config), + tsp("end_per_testcase(~w) -> stop httpc profile (~p)", + [Case, ProfilePid]), + unlink(ProfilePid), + inets:stop(stand_alone, ProfilePid), + tsp("end_per_testcase(~w) -> httpc profile (~p) stopped", + [Case, ProfilePid]), + ok; + _ -> + ok + end, finish(Config). finish(Config) -> @@ -364,6 +444,7 @@ finish(Config) -> undefined -> ok; _ -> + tsp("finish -> stop watchdog (~p)", [Dog]), test_server:timetrap_cancel(Dog) end. @@ -565,7 +646,7 @@ http_relaxed(suite) -> http_relaxed(Config) when is_list(Config) -> ok = httpc:set_options([{ipv6, disabled}]), % also test the old option %% ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_reason_phrase.html", @@ -591,7 +672,7 @@ http_dummy_pipe(suite) -> []; http_dummy_pipe(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/foobar.html", @@ -905,7 +986,7 @@ http_headers_dummy(suite) -> []; http_headers_dummy(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy_headers.html", @@ -970,7 +1051,7 @@ http_bad_response(suite) -> []; http_bad_response(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_crlf.html", @@ -1089,9 +1170,9 @@ ssl_get(SslTag, Config) when is_list(Config) -> httpc:request(get, {URL, []}, [{ssl, SSLConfig}], []), inets_test_lib:check_body(Body); {ok, _} -> - {skip, "Failed to start local http-server"}; + {skip, "local http-server not started"}; _ -> - {skip, "Failed to start SSL"} + {skip, "SSL not started"} end. @@ -1149,9 +1230,9 @@ ssl_trace(SslTag, Config) when is_list(Config) -> tsf({failed, Error}) end; {ok, _} -> - {skip, "Failed to start local http-server"}; + {skip, "local http-server not started"}; _ -> - {skip, "Failed to start SSL"} + {skip, "SSL not started"} end. @@ -1170,7 +1251,7 @@ http_redirect(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), tsp("http_redirect -> start dummy server inet"), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), tsp("http_redirect -> server port = ~p", [Port]), URL300 = ?URL_START ++ integer_to_list(Port) ++ "/300.html", @@ -1282,7 +1363,7 @@ http_redirect_loop(suite) -> []; http_redirect_loop(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/redirectloop.html", @@ -1299,7 +1380,7 @@ http_internal_server_error(suite) -> []; http_internal_server_error(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL500 = ?URL_START ++ integer_to_list(Port) ++ "/500.html", @@ -1335,7 +1416,7 @@ http_userinfo(suite) -> http_userinfo(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URLAuth = "http://alladin:sesame@localhost:" ++ integer_to_list(Port) ++ "/userinfo.html", @@ -1361,7 +1442,7 @@ http_cookie(suite) -> []; http_cookie(Config) when is_list(Config) -> ok = httpc:set_options([{cookies, enabled}, {ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URLStart = ?URL_START ++ integer_to_list(Port), @@ -1735,7 +1816,7 @@ http_stream_once(Config) when is_list(Config) -> p("http_stream_once -> set ipfamily to inet", []), ok = httpc:set_options([{ipfamily, inet}]), p("http_stream_once -> start dummy server", []), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), PortStr = integer_to_list(Port), p("http_stream_once -> once", []), @@ -1871,28 +1952,79 @@ parse_url(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -ipv6() -> - [{require,ipv6_hosts}]. -ipv6(doc) -> - ["Test ipv6."]; -ipv6(suite) -> - []; -ipv6(Config) when is_list(Config) -> - {ok, Hostname} = inet:gethostname(), - - case lists:member(list_to_atom(Hostname), - ct:get_config(ipv6_hosts)) of - true -> - {DummyServerPid, Port} = dummy_server(self(), ipv6), - - URL = "http://[" ++ ?IPV6_LOCAL_HOST ++ "]:" ++ + +ipv6_ipcomm() -> + [{require, ipv6_hosts}]. +ipv6_ipcomm(doc) -> + ["Test ip_comm ipv6."]; +ipv6_ipcomm(suite) -> + []; +ipv6_ipcomm(Config) when is_list(Config) -> + HTTPOptions = [], + SocketType = ip_comm, + Scheme = "http", + Extra = [], + ipv6(SocketType, Scheme, HTTPOptions, Extra, Config). + + +%%------------------------------------------------------------------------- + +ipv6_essl() -> + [{require, ipv6_hosts}]. +ipv6_essl(doc) -> + ["Test essl ipv6."]; +ipv6_essl(suite) -> + []; +ipv6_essl(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + CertFile = filename:join(DataDir, "ssl_client_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + SSLConfig = {essl, SSLOptions}, + tsp("ssl_ipv6 -> make request using: " + "~n SSLOptions: ~p", [SSLOptions]), + HTTPOptions = [{ssl, SSLConfig}], + SocketType = essl, + Scheme = "https", + Extra = SSLOptions, + ipv6(SocketType, Scheme, HTTPOptions, Extra, Config). + + +%%------------------------------------------------------------------------- + +ipv6(SocketType, Scheme, HTTPOptions, Extra, Config) -> + %% Check if we are a IPv6 host + tsp("ipv6 -> verify ipv6 support", []), + case inets_test_lib:has_ipv6_support(Config) of + {ok, Addr} -> + tsp("ipv6 -> ipv6 supported: ~p", [Addr]), + {DummyServerPid, Port} = dummy_server(SocketType, ipv6, Extra), + Profile = ?config(profile, Config), + URL = + Scheme ++ + "://[" ++ http_transport:ipv6_name(Addr) ++ "]:" ++ integer_to_list(Port) ++ "/foobar.html", - {ok, {{_,200,_}, [_ | _], [_|_]}} = - httpc:request(get, {URL, []}, [], []), - - DummyServerPid ! stop, + tsp("ipv6 -> issue request with: " + "~n URL: ~p" + "~n HTTPOptions: ~p", [URL, HTTPOptions]), + case httpc:request(get, {URL, []}, HTTPOptions, [], Profile) of + {ok, {{_,200,_}, [_ | _], [_|_]}} -> + tsp("ipv6 -> expected result"), + DummyServerPid ! stop, + ok; + {ok, Unexpected} -> + tsp("ipv6 -> unexpected result: " + "~n ~p", [Unexpected]), + DummyServerPid ! stop, + tsf({unexpected_result, Unexpected}); + {error, Reason} -> + tsp("ipv6 -> error: " + "~n Reason: ~p", [Reason]), + DummyServerPid ! stop, + tsf(Reason) + end, ok; - false -> + _ -> + tsp("ipv6 -> ipv6 not supported", []), {skip, "Host does not support IPv6"} end. @@ -1945,7 +2077,7 @@ http_invalid_http(suite) -> []; http_invalid_http(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/invalid_http.html", @@ -2002,7 +2134,7 @@ transfer_encoding_otp_6807(suite) -> []; transfer_encoding_otp_6807(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/capital_transfer_encoding.html", @@ -2035,7 +2167,7 @@ empty_response_header_otp_6830(suite) -> []; empty_response_header_otp_6830(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/no_headers.html", {ok, {{_,200,_}, [], [_ | _]}} = httpc:request(URL), @@ -2052,7 +2184,7 @@ no_content_204_otp_6982(suite) -> []; no_content_204_otp_6982(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/no_content.html", {ok, {{_,204,_}, [], []}} = httpc:request(URL), @@ -2070,7 +2202,7 @@ missing_CR_otp_7304(suite) -> []; missing_CR_otp_7304(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/missing_CR.html", {ok, {{_,200,_}, _, [_ | _]}} = httpc:request(URL), @@ -2089,7 +2221,7 @@ otp_7883_1(suite) -> otp_7883_1(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html", {error, socket_closed_remotely} = httpc:request(URL), @@ -2105,7 +2237,7 @@ otp_7883_2(suite) -> otp_7883_2(Config) when is_list(Config) -> ok = httpc:set_options([{ipfamily, inet}]), - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/just_close.html", Method = get, @@ -2626,7 +2758,7 @@ otp_8371(suite) -> []; otp_8371(Config) when is_list(Config) -> ok = httpc:set_options([{ipv6, disabled}]), % also test the old option - {DummyServerPid, Port} = dummy_server(self(), ipv4), + {DummyServerPid, Port} = dummy_server(ipv4), URL = ?URL_START ++ integer_to_list(Port) ++ "/ensure_host_header_with_port.html", @@ -2864,75 +2996,179 @@ receive_streamed_body(RequestId, Body, Pid) -> test_server:fail(Msg) end. +%% Perform a synchronous stop +dummy_server_stop(Pid) -> + Pid ! {stop, self()}, + receive + {stopped, Pid} -> + ok + end. + +dummy_server(IpV) -> + dummy_server(self(), ip_comm, IpV, []). +dummy_server(SocketType, IpV, Extra) -> + dummy_server(self(), SocketType, IpV, Extra). -dummy_server(Caller, IpV) -> - Pid = spawn(httpc_SUITE, dummy_server_init, [Caller, IpV]), +dummy_server(Caller, SocketType, IpV, Extra) -> + Args = [Caller, SocketType, IpV, Extra], + Pid = spawn(httpc_SUITE, dummy_server_init, Args), receive {port, Port} -> {Pid, Port} end. -dummy_server_init(Caller, IpV) -> +dummy_server_init(Caller, ip_comm, IpV, _) -> + BaseOpts = [binary, {packet, 0}, {reuseaddr,true}, {active, false}], {ok, ListenSocket} = case IpV of ipv4 -> - gen_tcp:listen(0, [binary, inet, {packet, 0}, - {reuseaddr,true}, - {active, false}]); + tsp("ip_comm ipv4 listen", []), + gen_tcp:listen(0, [inet | BaseOpts]); ipv6 -> - gen_tcp:listen(0, [binary, inet6, {packet, 0}, - {reuseaddr,true}, - {active, false}]) + tsp("ip_comm ipv6 listen", []), + gen_tcp:listen(0, [inet6 | BaseOpts]) end, {ok, Port} = inet:port(ListenSocket), - tsp("dummy_server_init -> Port: ~p", [Port]), + tsp("dummy_server_init(ip_comm) -> Port: ~p", [Port]), Caller ! {port, Port}, - dummy_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, - [], ListenSocket). + dummy_ipcomm_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + [], ListenSocket); +dummy_server_init(Caller, essl, IpV, SSLOptions) -> + BaseOpts = [{ssl_imp, new}, + {backlog, 128}, binary, {reuseaddr,true}, {active, false} | + SSLOptions], + dummy_ssl_server_init(Caller, BaseOpts, IpV); +dummy_server_init(Caller, ossl, IpV, SSLOptions) -> + BaseOpts = [{ssl_imp, old}, + {backlog, 128}, binary, {active, false} | SSLOptions], + dummy_ssl_server_init(Caller, BaseOpts, IpV). + +dummy_ssl_server_init(Caller, BaseOpts, IpV) -> + {ok, ListenSocket} = + case IpV of + ipv4 -> + tsp("dummy_ssl_server_init -> ssl ipv4 listen", []), + ssl:listen(0, [inet | BaseOpts]); + ipv6 -> + tsp("dummy_ssl_server_init -> ssl ipv6 listen", []), + ssl:listen(0, [inet6 | BaseOpts]) + end, + tsp("dummy_ssl_server_init -> ListenSocket: ~p", [ListenSocket]), + {ok, {_, Port}} = ssl:sockname(ListenSocket), + tsp("dummy_ssl_server_init -> Port: ~p", [Port]), + Caller ! {port, Port}, + dummy_ssl_server_loop({httpd_request, parse, [?HTTP_MAX_HEADER_SIZE]}, + [], ListenSocket). -dummy_server_loop(MFA, Handlers, ListenSocket) -> +dummy_ipcomm_server_loop(MFA, Handlers, ListenSocket) -> receive stop -> - lists:foreach(fun(Handler) -> Handler ! stop end, Handlers) + tsp("dummy_ipcomm_server_loop -> stop handlers", []), + lists:foreach(fun(Handler) -> Handler ! stop end, Handlers); + {stop, From} -> + tsp("dummy_ipcomm_server_loop -> " + "stop command from ~p for handlers (~p)", [From, Handlers]), + Stopper = fun(Handler) -> Handler ! stop end, + lists:foreach(Stopper, Handlers), + From ! {stopped, self()} after 0 -> + tsp("dummy_ipcomm_server_loop -> await accept", []), {ok, Socket} = gen_tcp:accept(ListenSocket), + tsp("dummy_ipcomm_server_loop -> accepted: ~p", [Socket]), HandlerPid = dummy_request_handler(MFA, Socket), + tsp("dummy_icomm_server_loop -> handler created: ~p", [HandlerPid]), gen_tcp:controlling_process(Socket, HandlerPid), - HandlerPid ! controller, - dummy_server_loop(MFA, [HandlerPid | Handlers], + tsp("dummy_ipcomm_server_loop -> " + "control transfered to handler", []), + HandlerPid ! ipcomm_controller, + tsp("dummy_ipcomm_server_loop -> " + "handler informed about control transfer", []), + dummy_ipcomm_server_loop(MFA, [HandlerPid | Handlers], ListenSocket) end. +dummy_ssl_server_loop(MFA, Handlers, ListenSocket) -> + receive + stop -> + tsp("dummy_ssl_server_loop -> stop handlers", []), + lists:foreach(fun(Handler) -> Handler ! stop end, Handlers); + {stop, From} -> + tsp("dummy_ssl_server_loop -> " + "stop command from ~p for handlers (~p)", [From, Handlers]), + Stopper = fun(Handler) -> Handler ! stop end, + lists:foreach(Stopper, Handlers), + From ! {stopped, self()} + after 0 -> + tsp("dummy_ssl_server_loop -> await accept", []), + {ok, Socket} = ssl:transport_accept(ListenSocket), + tsp("dummy_ssl_server_loop -> accepted: ~p", [Socket]), + HandlerPid = dummy_request_handler(MFA, Socket), + tsp("dummy_ssl_server_loop -> handler created: ~p", [HandlerPid]), + ssl:controlling_process(Socket, HandlerPid), + tsp("dummy_ssl_server_loop -> control transfered to handler", []), + HandlerPid ! ssl_controller, + tsp("dummy_ssl_server_loop -> " + "handler informed about control transfer", []), + dummy_ssl_server_loop(MFA, [HandlerPid | Handlers], + ListenSocket) + end. + dummy_request_handler(MFA, Socket) -> + tsp("spawn request handler", []), spawn(httpc_SUITE, dummy_request_handler_init, [MFA, Socket]). dummy_request_handler_init(MFA, Socket) -> - receive - controller -> - inet:setopts(Socket, [{active, true}]) - end, - dummy_request_handler_loop(MFA, Socket). + SockType = + receive + ipcomm_controller -> + tsp("dummy_request_handler_init -> " + "received ip_comm controller - activate", []), + inet:setopts(Socket, [{active, true}]), + ip_comm; + ssl_controller -> + tsp("dummy_request_handler_init -> " + "received ssl controller - activate", []), + ssl:setopts(Socket, [{active, true}]), + ssl + end, + dummy_request_handler_loop(MFA, SockType, Socket). -dummy_request_handler_loop({Module, Function, Args}, Socket) -> +dummy_request_handler_loop({Module, Function, Args}, SockType, Socket) -> tsp("dummy_request_handler_loop -> entry with" "~n Module: ~p" "~n Function: ~p" "~n Args: ~p", [Module, Function, Args]), receive - {tcp, _, Data} -> - tsp("dummy_request_handler_loop -> Data ~p", [Data]), - case handle_request(Module, Function, [Data | Args], Socket) of - stop -> + {Proto, _, Data} when (Proto =:= tcp) orelse (Proto =:= ssl) -> + tsp("dummy_request_handler_loop -> [~w] Data ~p", [Proto, Data]), + case handle_request(Module, Function, [Data | Args], Socket, Proto) of + stop when Proto =:= tcp -> gen_tcp:close(Socket); + stop when Proto =:= ssl -> + ssl:close(Socket); NewMFA -> - dummy_request_handler_loop(NewMFA, Socket) + dummy_request_handler_loop(NewMFA, SockType, Socket) end; - stop -> - gen_tcp:close(Socket) + stop when SockType =:= ip_comm -> + gen_tcp:close(Socket); + stop when SockType =:= ssl -> + ssl:close(Socket) end. -handle_request(Module, Function, Args, Socket) -> + +mk_close(tcp) -> fun(Sock) -> gen_tcp:close(Sock) end; +mk_close(ssl) -> fun(Sock) -> ssl:close(Sock) end. + +mk_send(tcp) -> fun(Sock, Data) -> gen_tcp:send(Sock, Data) end; +mk_send(ssl) -> fun(Sock, Data) -> ssl:send(Sock, Data) end. + +handle_request(Module, Function, Args, Socket, Proto) -> + Close = mk_close(Proto), + Send = mk_send(Proto), + handle_request(Module, Function, Args, Socket, Close, Send). + +handle_request(Module, Function, Args, Socket, Close, Send) -> tsp("handle_request -> entry with" "~n Module: ~p" "~n Function: ~p" @@ -2941,7 +3177,7 @@ handle_request(Module, Function, Args, Socket) -> {ok, Result} -> tsp("handle_request -> ok" "~n Result: ~p", [Result]), - case (catch handle_http_msg(Result, Socket)) of + case (catch handle_http_msg(Result, Socket, Close, Send)) of stop -> stop; <<>> -> @@ -2949,7 +3185,8 @@ handle_request(Module, Function, Args, Socket) -> {httpd_request, parse, [[<<>>, ?HTTP_MAX_HEADER_SIZE]]}; Data -> handle_request(httpd_request, parse, - [Data |[?HTTP_MAX_HEADER_SIZE]], Socket) + [Data |[?HTTP_MAX_HEADER_SIZE]], Socket, + Close, Send) end; NewMFA -> tsp("handle_request -> " @@ -2957,7 +3194,7 @@ handle_request(Module, Function, Args, Socket) -> NewMFA end. -handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> +handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket, Close, Send) -> tsp("handle_http_msg -> entry with: " "~n RelUri: ~p" "~n Headers: ~p" @@ -3114,16 +3351,16 @@ handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> "Expires:Sat, 29 Oct 1994 19:43:31 GMT\r\n" ++ "Proxy-Authenticate:#1Basic" ++ "\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")), - gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")), + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); "/capital_transfer_encoding.html" -> Head = "HTTP/1.1 200 ok\r\n" ++ "Transfer-Encoding:Chunked\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")), - gen_tcp:send(Socket, http_chunk:encode("obar</BODY></HTML>")), + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); "/cookie.html" -> "HTTP/1.1 200 ok\r\n" ++ @@ -3142,20 +3379,20 @@ handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> "/once_chunked.html" -> Head = "HTTP/1.1 200 ok\r\n" ++ "Transfer-Encoding:Chunked\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, http_chunk:encode("<HTML><BODY>fo")), - gen_tcp:send(Socket, + Send(Socket, Head), + Send(Socket, http_chunk:encode("<HTML><BODY>fo")), + Send(Socket, http_chunk:encode("obar</BODY></HTML>")), http_chunk:encode_last(); "/once.html" -> Head = "HTTP/1.1 200 ok\r\n" ++ "Content-Length:32\r\n\r\n", - gen_tcp:send(Socket, Head), - gen_tcp:send(Socket, "<HTML><BODY>fo"), + Send(Socket, Head), + Send(Socket, "<HTML><BODY>fo"), test_server:sleep(1000), - gen_tcp:send(Socket, "ob"), + Send(Socket, "ob"), test_server:sleep(1000), - gen_tcp:send(Socket, "ar</BODY></HTML>"); + Send(Socket, "ar</BODY></HTML>"); "/invalid_http.html" -> "HTTP/1.1 301\r\nDate:Sun, 09 Dec 2007 13:04:18 GMT\r\n" ++ "Transfer-Encoding:chunked\r\n\r\n"; @@ -3178,9 +3415,9 @@ handle_http_msg({_, RelUri, _, {_, Headers}, Body}, Socket) -> ok; close -> %% Nothing to send, just close - gen_tcp:close(Socket); + Close(Socket); _ when is_list(Msg) orelse is_binary(Msg) -> - gen_tcp:send(Socket, Msg) + Send(Socket, Msg) end, tsp("handle_http_msg -> done"), NextRequest. @@ -3316,3 +3553,20 @@ dummy_ssl_server_hang_loop(_) -> stop -> ok end. + + +ensure_started([]) -> + ok; +ensure_started([App|Apps]) -> + ensure_started(App), + ensure_started(Apps); +ensure_started(App) when is_atom(App) -> + case (catch application:start(App)) of + ok -> + ok; + {error, {already_started, _}} -> + ok; + Error -> + throw({error, {failed_starting, App, Error}}) + end. + diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index fde5178879..c4d4bf969b 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -207,8 +207,11 @@ -export([ticket_5775/1,ticket_5865/1,ticket_5913/1,ticket_6003/1, ticket_7304/1]). -%%% Misc --export([ipv6_hostname/1, ipv6_address/1]). +%%% IPv6 tests +-export([ipv6_hostname_ipcomm/0, ipv6_hostname_ipcomm/1, + ipv6_address_ipcomm/0, ipv6_address_ipcomm/1, + ipv6_hostname_essl/0, ipv6_hostname_essl/1, + ipv6_address_essl/0, ipv6_address_essl/1]). %% Help functions -export([cleanup_mnesia/0, setup_mnesia/0, setup_mnesia/1]). @@ -241,9 +244,15 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [{group, ip}, {group, ssl}, {group, http_1_1_ip}, - {group, http_1_0_ip}, {group, http_0_9_ip}, - {group, tickets}]. + [ + {group, ip}, + {group, ssl}, + {group, http_1_1_ip}, + {group, http_1_0_ip}, + {group, http_0_9_ip}, + {group, ipv6}, + {group, tickets} + ]. groups() -> [{ip, [], @@ -329,7 +338,8 @@ groups() -> {http_1_0_ip, [], [ip_head_1_0, ip_get_1_0, ip_post_1_0]}, {http_0_9_ip, [], [ip_get_0_9]}, - {ipv6, [], [ipv6_hostname, ipv6_address]}, + {ipv6, [], [ipv6_hostname_ipcomm, ipv6_address_ipcomm, + ipv6_hostname_essl, ipv6_address_essl]}, {tickets, [], [ticket_5775, ticket_5865, ticket_5913, ticket_6003, ticket_7304]}]. @@ -408,10 +418,10 @@ init_per_testcase2(Case, Config) -> "~n Config: ~p" "~n", [?MODULE, Case, Config]), - IpNormal = integer_to_list(?IP_PORT) ++ ".conf", - IpHtacess = integer_to_list(?IP_PORT) ++ "htacess.conf", - SslNormal = integer_to_list(?SSL_PORT) ++ ".conf", - SslHtacess = integer_to_list(?SSL_PORT) ++ "htacess.conf", + IpNormal = integer_to_list(?IP_PORT) ++ ".conf", + IpHtaccess = integer_to_list(?IP_PORT) ++ "htaccess.conf", + SslNormal = integer_to_list(?SSL_PORT) ++ ".conf", + SslHtaccess = integer_to_list(?SSL_PORT) ++ "htaccess.conf", DataDir = ?config(data_dir, Config), SuiteTopDir = ?config(suite_top_dir, Config), @@ -471,9 +481,9 @@ init_per_testcase2(Case, Config) -> io:format(user, "~w:init_per_testcase2(~w) -> ip testcase setups~n", [?MODULE, Case]), create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig], - normal_acess, IpNormal), + normal_access, IpNormal), create_config([{port, ?IP_PORT}, {sock_type, ip_comm} | NewConfig], - mod_htaccess, IpHtacess), + mod_htaccess, IpHtaccess), %% To be used by SSL test cases io:format(user, "~w:init_per_testcase2(~w) -> ssl testcase setups~n", @@ -491,9 +501,9 @@ init_per_testcase2(Case, Config) -> end, create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig], - normal_acess, SslNormal), + normal_access, SslNormal), create_config([{port, ?SSL_PORT}, {sock_type, SocketType} | NewConfig], - mod_htaccess, SslHtacess), + mod_htaccess, SslHtaccess), %% To be used by IPv6 test cases. Case-clause is so that %% you can do ts:run(inets, httpd_SUITE, <test case>) @@ -501,22 +511,52 @@ init_per_testcase2(Case, Config) -> %% on 'test_host_ipv6_only' that will only be present %% when you run the whole test suite due to shortcomings %% of the test server. - %% case (catch ?config(test_host_ipv6_only, Config)) of - %% {_,IPv6Host,IPv6Adress,_,_} -> - %% create_ipv6_config([{port, ?IP_PORT}, - %% {sock_type, ip_comm} | NewConfig], - %% "ipv6_hostname.conf", IPv6Host), - %% create_ipv6_config([{port, ?IP_PORT}, - %% {sock_type, ip_comm} | NewConfig], - %% "ipv6_address.conf", IPv6Adress); - %% _ -> - %% ok - %% end, - + + io:format(user, "~w:init_per_testcase2(~w) -> " + "maybe generate IPv6 config file(s)", [?MODULE, Case]), + NewConfig2 = + case atom_to_list(Case) of + "ipv6_" ++ _ -> + case (catch inets_test_lib:has_ipv6_support(NewConfig)) of + {ok, IPv6Address0} -> + {ok, Hostname} = inet:gethostname(), + IPv6Address = http_transport:ipv6_name(IPv6Address0), + create_ipv6_config([{port, ?IP_PORT}, + {sock_type, ip_comm}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_hostname_ipcomm.conf", + Hostname), + create_ipv6_config([{port, ?IP_PORT}, + {sock_type, ip_comm}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_address_ipcomm.conf", + IPv6Address), + create_ipv6_config([{port, ?SSL_PORT}, + {sock_type, essl}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_hostname_essl.conf", + Hostname), + create_ipv6_config([{port, ?SSL_PORT}, + {sock_type, essl}, + {ipv6_host, IPv6Address} | + NewConfig], + "ipv6_address_essl.conf", + IPv6Address), + [{ipv6_host, IPv6Address} | NewConfig]; + _ -> + NewConfig + end; + _ -> + NewConfig + end, + io:format(user, "~w:init_per_testcase2(~w) -> done~n", [?MODULE, Case]), - NewConfig. + NewConfig2. init_per_testcase3(Case, Config) -> @@ -547,10 +587,10 @@ init_per_testcase3(Case, Config) -> [?MODULE, Case]), inets:disable_trace(); _ -> - %% TraceLevel = max, io:format(user, "~w:init_per_testcase3(~w) -> enabling trace", [?MODULE, Case]), - TraceLevel = 70, + %% TraceLevel = 70, + TraceLevel = max, TraceDest = io, inets:enable_trace(TraceLevel, TraceDest, httpd) end, @@ -569,7 +609,7 @@ init_per_testcase3(Case, Config) -> inets_test_lib:start_http_server( filename:join(TcTopDir, integer_to_list(?IP_PORT) ++ - "htacess.conf")), + "htaccess.conf")), "mod_htaccess"; "ip_" ++ Rest -> inets_test_lib:start_http_server( @@ -602,7 +642,7 @@ init_per_testcase3(Case, Config) -> case inets_test_lib:start_http_server_ssl( filename:join(TcTopDir, integer_to_list(?SSL_PORT) ++ - "htacess.conf"), SslTag) of + "htaccess.conf"), SslTag) of ok -> "mod_htaccess"; Other -> @@ -627,16 +667,13 @@ init_per_testcase3(Case, Config) -> {skip, "SSL does not seem to be supported"} end; "ipv6_" ++ _ = TestCaseStr -> - {ok, Hostname} = inet:gethostname(), - - case lists:member(list_to_atom(Hostname), - ?config(ipv6_hosts, Config)) of - true -> + case inets_test_lib:has_ipv6_support() of + {ok, _} -> inets_test_lib:start_http_server( filename:join(TcTopDir, TestCaseStr ++ ".conf")); - false -> + _ -> {skip, "Host does not support IPv6"} end end, @@ -650,8 +687,8 @@ init_per_testcase3(Case, Config) -> "mod_htaccess" -> ServerRoot = ?config(server_root, Config), Path = filename:join([ServerRoot, "htdocs"]), - catch remove_htacess(Path), - create_htacess_data(Path, ?config(address, Config)), + catch remove_htaccess(Path), + create_htaccess_data(Path, ?config(address, Config)), [{watchdog, Dog} | NewConfig]; "range" -> ServerRoot = ?config(server_root, Config), @@ -2409,30 +2446,76 @@ ip_mod_cgi_chunked_encoding_test(Config) when is_list(Config) -> ok. %------------------------------------------------------------------------- -ipv6_hostname(doc) -> + +ipv6_hostname_ipcomm() -> + [{require, ipv6_hosts}]. +ipv6_hostname_ipcomm(X) -> + SocketType = ip_comm, + Port = ?IP_PORT, + ipv6_hostname(SocketType, Port, X). + +ipv6_hostname_essl() -> + [{require, ipv6_hosts}]. +ipv6_hostname_essl(X) -> + SocketType = essl, + Port = ?SSL_PORT, + ipv6_hostname(SocketType, Port, X). + +ipv6_hostname(_SocketType, _Port, doc) -> ["Test standard ipv6 address"]; -ipv6_hostname(suite)-> +ipv6_hostname(_SocketType, _Port, suite)-> []; -ipv6_hostname(Config) when is_list(Config) -> +ipv6_hostname(SocketType, Port, Config) when is_list(Config) -> + tsp("ipv6_hostname -> entry with" + "~n SocketType: ~p" + "~n Port: ~p" + "~n Config: ~p", [SocketType, Port, Config]), Host = ?config(host, Config), - httpd_test_lib:verify_request(ip_comm, Host, ?IP_PORT, node(), - "GET / HTTP/1.1\r\n\r\n", - [{statuscode, 200}, - {version, "HTTP/1.1"}]), + URI = "GET HTTP://" ++ + Host ++ ":" ++ integer_to_list(Port) ++ "/ HTTP/1.1\r\n\r\n", + tsp("ipv6_hostname -> Host: ~p", [Host]), + httpd_test_lib:verify_request(SocketType, Host, Port, [inet6], + node(), + URI, + [{statuscode, 200}, {version, "HTTP/1.1"}]), ok. %%------------------------------------------------------------------------- -ipv6_address(doc) -> + +ipv6_address_ipcomm() -> + [{require, ipv6_hosts}]. +ipv6_address_ipcomm(X) -> + SocketType = ip_comm, + Port = ?IP_PORT, + ipv6_address(SocketType, Port, X). + +ipv6_address_essl() -> + [{require, ipv6_hosts}]. +ipv6_address_essl(X) -> + SocketType = essl, + Port = ?SSL_PORT, + ipv6_address(SocketType, Port, X). + +ipv6_address(_SocketType, _Port, doc) -> ["Test standard ipv6 address"]; -ipv6_address(suite)-> +ipv6_address(_SocketType, _Port, suite)-> []; -ipv6_address(Config) when is_list(Config) -> - httpd_test_lib:verify_request(ip_comm, ?IPV6_LOCAL_HOST, ?IP_PORT, - node(), "GET / HTTP/1.1\r\n\r\n", - [{statuscode, 200}, - {version, "HTTP/1.1"}]), +ipv6_address(SocketType, Port, Config) when is_list(Config) -> + tsp("ipv6_address -> entry with" + "~n SocketType: ~p" + "~n Port: ~p" + "~n Config: ~p", [SocketType, Port, Config]), + Host = ?config(host, Config), + tsp("ipv6_address -> Host: ~p", [Host]), + URI = "GET HTTP://" ++ + Host ++ ":" ++ integer_to_list(Port) ++ "/ HTTP/1.1\r\n\r\n", + httpd_test_lib:verify_request(SocketType, Host, Port, [inet6], + node(), + URI, + [{statuscode, 200}, {version, "HTTP/1.1"}]), ok. + %%-------------------------------------------------------------------- ticket_5775(doc) -> ["Tests that content-length is correct"]; @@ -2805,22 +2888,22 @@ cleanup_mnesia() -> mnesia:delete_schema([node()]), ok. -create_htacess_data(Path, IpAddress)-> - create_htacess_dirs(Path), +create_htaccess_data(Path, IpAddress)-> + create_htaccess_dirs(Path), create_html_file(filename:join([Path,"ht/open/dummy.html"])), create_html_file(filename:join([Path,"ht/blocknet/dummy.html"])), create_html_file(filename:join([Path,"ht/secret/dummy.html"])), create_html_file(filename:join([Path,"ht/secret/top_secret/dummy.html"])), - create_htacess_file(filename:join([Path,"ht/open/.htaccess"]), + create_htaccess_file(filename:join([Path,"ht/open/.htaccess"]), Path, "user one Aladdin"), - create_htacess_file(filename:join([Path,"ht/secret/.htaccess"]), + create_htaccess_file(filename:join([Path,"ht/secret/.htaccess"]), Path, "group group1 group2"), - create_htacess_file(filename:join([Path, + create_htaccess_file(filename:join([Path, "ht/secret/top_secret/.htaccess"]), Path, "user four"), - create_htacess_file(filename:join([Path,"ht/blocknet/.htaccess"]), + create_htaccess_file(filename:join([Path,"ht/blocknet/.htaccess"]), Path, nouser, IpAddress), create_user_group_file(filename:join([Path,"ht","users.file"]), @@ -2835,7 +2918,7 @@ create_html_file(PathAndFileName)-> "<html><head><title>test</title></head> <body>testar</body></html>")). -create_htacess_file(PathAndFileName, BaseDir, RequireData)-> +create_htaccess_file(PathAndFileName, BaseDir, RequireData)-> file:write_file(PathAndFileName, list_to_binary( "AuthUserFile "++ BaseDir ++ @@ -2844,7 +2927,7 @@ create_htacess_file(PathAndFileName, BaseDir, RequireData)-> " Basic\n<Limit>\nrequire " ++ RequireData ++ "\n</Limit>")). -create_htacess_file(PathAndFileName, BaseDir, nouser, IpAddress)-> +create_htaccess_file(PathAndFileName, BaseDir, nouser, IpAddress)-> file:write_file(PathAndFileName,list_to_binary( "AuthUserFile "++ BaseDir ++ "/ht/users.file\nAuthGroupFile " ++ @@ -2858,14 +2941,14 @@ create_htacess_file(PathAndFileName, BaseDir, nouser, IpAddress)-> create_user_group_file(PathAndFileName, Data)-> file:write_file(PathAndFileName, list_to_binary(Data)). -create_htacess_dirs(Path)-> +create_htaccess_dirs(Path)-> ok = file:make_dir(filename:join([Path,"ht"])), ok = file:make_dir(filename:join([Path,"ht/open"])), ok = file:make_dir(filename:join([Path,"ht/blocknet"])), ok = file:make_dir(filename:join([Path,"ht/secret"])), ok = file:make_dir(filename:join([Path,"ht/secret/top_secret"])). -remove_htacess_dirs(Path)-> +remove_htaccess_dirs(Path)-> file:del_dir(filename:join([Path,"ht/secret/top_secret"])), file:del_dir(filename:join([Path,"ht/secret"])), file:del_dir(filename:join([Path,"ht/blocknet"])), @@ -2888,7 +2971,7 @@ format_ip(IpAddress,Pos)when Pos > 0-> format_ip(IpAddress, _Pos)-> "1" ++ IpAddress. -remove_htacess(Path)-> +remove_htaccess(Path)-> file:delete(filename:join([Path,"ht/open/dummy.html"])), file:delete(filename:join([Path,"ht/secret/dummy.html"])), file:delete(filename:join([Path,"ht/secret/top_secret/dummy.html"])), @@ -2899,7 +2982,7 @@ remove_htacess(Path)-> file:delete(filename:join([Path,"ht/secret/top_secret/.htaccess"])), file:delete(filename:join([Path,"ht","users.file"])), file:delete(filename:join([Path,"ht","groups.file"])), - remove_htacess_dirs(Path). + remove_htaccess_dirs(Path). dos_hostname_poll(Type, Host, Port, Node, Hosts) -> @@ -2939,35 +3022,66 @@ create_range_data(Path) -> "12345678901234567890", "12345678901234567890"])). -%% create_ipv6_config(Config, FileName, Ipv6Address) -> -%% ServerRoot = ?config(server_root, Config), -%% TcTopDir = ?config(tc_top_dir, Config), -%% Port = ?config(port, Config), -%% SockType = ?config(sock_type, Config), -%% -%% MaxHdrSz = io_lib:format("~p", [256]), -%% MaxHdrAct = io_lib:format("~p", [close]), -%% -%% Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi" -%% " mod_include mod_dir mod_get mod_head" -%% " mod_log mod_disk_log mod_trace", -%% -%% HttpConfig = [cline(["BindAddress ", "[" ++ Ipv6Address ++"]|inet6"]), -%% cline(["Port ", integer_to_list(Port)]), -%% cline(["ServerName ", "httpc_test"]), -%% cline(["SocketType ", atom_to_list(SockType)]), -%% cline([Mod_order]), -%% cline(["ServerRoot ", ServerRoot]), -%% cline(["DocumentRoot ", -%% filename:join(ServerRoot, "htdocs")]), -%% cline(["MaxHeaderSize ",MaxHdrSz]), -%% cline(["MaxHeaderAction ",MaxHdrAct]), -%% cline(["DirectoryIndex ", "index.html "]), -%% cline(["DefaultType ", "text/plain"])], -%% ConfigFile = filename:join([TcTopDir,FileName]), -%% {ok, Fd} = file:open(ConfigFile, [write]), -%% ok = file:write(Fd, lists:flatten(HttpConfig)), -%% ok = file:close(Fd). +create_ipv6_config(Config, FileName, Ipv6Address) -> + ServerRoot = ?config(server_root, Config), + TcTopDir = ?config(tc_top_dir, Config), + Port = ?config(port, Config), + SockType = ?config(sock_type, Config), + Mods = io_lib:format("~p", [httpd_mod]), + Funcs = io_lib:format("~p", [ssl_password_cb]), + Host = ?config(ipv6_host, Config), + + MaxHdrSz = io_lib:format("~p", [256]), + MaxHdrAct = io_lib:format("~p", [close]), + + Mod_order = "Modules mod_alias mod_auth mod_esi mod_actions mod_cgi" + " mod_include mod_dir mod_get mod_head" + " mod_log mod_disk_log mod_trace", + + SSL = + if + (SockType =:= ssl) orelse + (SockType =:= ossl) orelse + (SockType =:= essl) -> + [cline(["SSLCertificateFile ", + filename:join(ServerRoot, "ssl/ssl_server.pem")]), + cline(["SSLCertificateKeyFile ", + filename:join(ServerRoot, "ssl/ssl_server.pem")]), + cline(["SSLCACertificateFile ", + filename:join(ServerRoot, "ssl/ssl_server.pem")]), + cline(["SSLPasswordCallbackModule ", Mods]), + cline(["SSLPasswordCallbackFunction ", Funcs]), + cline(["SSLVerifyClient 0"]), + cline(["SSLVerifyDepth 1"])]; + true -> + [] + end, + + BindAddress = "[" ++ Ipv6Address ++"]|inet6", + + HttpConfig = + [cline(["BindAddress ", BindAddress]), + cline(["Port ", integer_to_list(Port)]), + cline(["ServerName ", Host]), + cline(["SocketType ", atom_to_list(SockType)]), + cline([Mod_order]), + cline(["ServerRoot ", ServerRoot]), + cline(["DocumentRoot ", filename:join(ServerRoot, "htdocs")]), + cline(["MaxHeaderSize ",MaxHdrSz]), + cline(["MaxHeaderAction ",MaxHdrAct]), + cline(["DirectoryIndex ", "index.html "]), + cline(["DefaultType ", "text/plain"]), + SSL], + ConfigFile = filename:join([TcTopDir,FileName]), + {ok, Fd} = file:open(ConfigFile, [write]), + ok = file:write(Fd, lists:flatten(HttpConfig)), + ok = file:close(Fd). + + +%% tsp(F) -> +%% inets_test_lib:tsp(F). +tsp(F, A) -> + inets_test_lib:tsp(F, A). tsf(Reason) -> - test_server:fail(Reason). + inets_test_lib:tsf(Reason). diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl index 3189a758a5..2903aaafa5 100644 --- a/lib/inets/test/httpd_test_lib.erl +++ b/lib/inets/test/httpd_test_lib.erl @@ -22,7 +22,7 @@ -include("inets_test_lib.hrl"). %% Poll functions --export([verify_request/6, verify_request/7, is_expect/1]). +-export([verify_request/6, verify_request/7, verify_request/8, is_expect/1]). -record(state, {request, % string() socket, % socket() @@ -81,33 +81,57 @@ %%------------------------------------------------------------------ verify_request(SocketType, Host, Port, Node, RequestStr, Options) -> verify_request(SocketType, Host, Port, Node, RequestStr, Options, 30000). -verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) -> - {ok, Socket} = inets_test_lib:connect_bin(SocketType, Host, Port), +verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options) + when is_list(TranspOpts) -> + verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, 30000); +verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) + when (is_integer(TimeOut) orelse (TimeOut =:= infinity)) -> + verify_request(SocketType, Host, Port, [], Node, RequestStr, Options, TimeOut). +verify_request(SocketType, Host, Port, TranspOpts, Node, RequestStr, Options, TimeOut) -> + tsp("verify_request -> entry with" + "~n SocketType: ~p" + "~n Host: ~p" + "~n Port: ~p" + "~n TranspOpts: ~p" + "~n Node: ~p" + "~n Options: ~p" + "~n TimeOut: ~p", + [SocketType, Host, Port, TranspOpts, Node, Options, TimeOut]), + case (catch inets_test_lib:connect_bin(SocketType, Host, Port, TranspOpts)) of + {ok, Socket} -> + tsp("verify_request -> connected - now send message"), + SendRes = inets_test_lib:send(SocketType, Socket, RequestStr), + tsp("verify_request -> send result: " + "~n ~p", [SendRes]), + State = case inets_regexp:match(RequestStr, "printenv") of + nomatch -> + #state{}; + _ -> + #state{print = true} + end, + + case request(State#state{request = RequestStr, + socket = Socket}, TimeOut) of + {error, Reason} -> + tsp("request failed: " + "~n Reason: ~p", [Reason]), + {error, Reason}; + NewState -> + tsp("validate reply: " + "~n NewState: ~p", [NewState]), + ValidateResult = + validate(RequestStr, NewState, Options, Node, Port), + tsp("validation result: " + "~n ~p", [ValidateResult]), + inets_test_lib:close(SocketType, Socket), + ValidateResult + end; - _SendRes = inets_test_lib:send(SocketType, Socket, RequestStr), - - State = case inets_regexp:match(RequestStr, "printenv") of - nomatch -> - #state{}; - _ -> - #state{print = true} - end, - - case request(State#state{request = RequestStr, - socket = Socket}, TimeOut) of - {error, Reason} -> - tsp("request failed: " - "~n Reason: ~p", [Reason]), - {error, Reason}; - NewState -> - tsp("validate reply: " - "~n NewState: ~p", [NewState]), - ValidateResult = validate(RequestStr, NewState, Options, - Node, Port), - tsp("validation result: " - "~n ~p", [ValidateResult]), - inets_test_lib:close(SocketType, Socket), - ValidateResult + ConnectError -> + tsp("verify_request -> connect failed: " + "~n ~p" + "~n", [ConnectError]), + tsf({connect_failure, ConnectError}) end. request(#state{mfa = {Module, Function, Args}, @@ -214,7 +238,10 @@ validate(RequestStr, #state{status_line = {Version, StatusCode, _}, headers = Headers, body = Body}, Options, N, P) -> - %io:format("Status~p: H:~p B:~p~n", [StatusCode, Headers, Body]), + %% tsp("validate -> entry with" + %% "~n StatusCode: ~p" + %% "~n Headers: ~p" + %% "~n Body: ~p", [StatusCode, Headers, Body]), check_version(Version, Options), case lists:keysearch(statuscode, 1, Options) of {value, _} -> @@ -342,8 +369,10 @@ print(_, _, #state{print = false}) -> ok. -%% tsp(F) -> -%% tsp(F, []). +tsp(F) -> + inets_test_lib:tsp(F). tsp(F, A) -> - test_server:format("~p ~p:" ++ F ++ "~n", [self(), ?MODULE | A]). + inets_test_lib:tsp(F, A). +tsf(Reason) -> + inets_test_lib:tsf(Reason). diff --git a/lib/inets/test/inets_test_lib.erl b/lib/inets/test/inets_test_lib.erl index 6cedaf9638..2e19c41f16 100644 --- a/lib/inets/test/inets_test_lib.erl +++ b/lib/inets/test/inets_test_lib.erl @@ -26,18 +26,64 @@ -export([start_http_server/1, start_http_server/2]). -export([start_http_server_ssl/1, start_http_server_ssl/2]). -export([hostname/0]). --export([connect_bin/3, connect_byte/3, send/3, close/2]). +-export([connect_bin/3, connect_bin/4, + connect_byte/3, connect_byte/4, + send/3, close/2]). -export([copy_file/3, copy_files/2, copy_dirs/2, del_dirs/1]). -export([info/4, log/4, debug/4, print/4]). +-export([tsp/1, tsp/2, tsf/1]). -export([check_body/1]). -export([millis/0, millis_diff/2, hours/1, minutes/1, seconds/1, sleep/1]). --export([oscmd/1]). +-export([oscmd/1, has_ipv6_support/1]). -export([non_pc_tc_maybe_skip/4, os_based_skip/1, skip/3, fail/3]). -export([flush/0]). -export([start_node/1, stop_node/1]). %% -- Misc os command and stuff +has_ipv6_support(Config) -> + case lists:keysearch(ipv6_hosts, 1, Config) of + false -> + %% Do a basic check to se if + %% our own host has a working IPv6 address... + tsp("has_ipv6_support -> no ipv6_hosts config"), + {ok, Hostname} = inet:gethostname(), + case inet:getaddrs(Hostname, inet6) of + {ok, [Addr|_]} when is_tuple(Addr) andalso + (element(1, Addr) =/= 0) -> + %% We actually need to test that the addr can be used, + %% this is done by attempting to create a (tcp) + %% listen socket + tsp("has_ipv6_support -> check Addr: ~p", [Addr]), + case (catch gen_tcp:listen(0, [inet6, {ip, Addr}])) of + {ok, LSock} -> + tsp("has_ipv6_support -> we are ipv6 host"), + gen_tcp:close(LSock), + {ok, Addr}; + _ -> + undefined + end; + _ -> + undefined + end; + {value, {_, Hosts}} when is_list(Hosts) -> + %% Check if our host is in the list of *known* IPv6 hosts + tsp("has_ipv6_support -> Hosts: ~p", [Hosts]), + {ok, Hostname} = inet:gethostname(), + case lists:member(list_to_atom(Hostname), Hosts) of + true -> + tsp("has_ipv6_support -> we are known ipv6 host"), + {ok, [Addr|_]} = inet:getaddrs(Hostname, inet6), + {ok, Addr}; + false -> + undefined + end; + + _ -> + undefined + + end. + oscmd(Cmd) -> string:strip(os:cmd(Cmd), right, $\n). @@ -87,31 +133,34 @@ start_http_server(Conf) -> start_http_server(Conf, ?HTTP_DEFAULT_SSL_KIND). start_http_server(Conf, essl = _SslTag) -> + tsp("start_http_server(essl) -> entry - try start crypto and public_key"), application:start(crypto), + application:start(public_key), do_start_http_server(Conf); -start_http_server(Conf, _SslTag) -> +start_http_server(Conf, SslTag) -> + tsp("start_http_server(~w) -> entry", [SslTag]), do_start_http_server(Conf). do_start_http_server(Conf) -> - tsp("start http server with " + tsp("do_start_http_server -> entry with" "~n Conf: ~p" "~n", [Conf]), application:load(inets), case application:set_env(inets, services, [{httpd, Conf}]) of ok -> + tsp("start_http_server -> httpd conf stored in inets app env"), case application:start(inets) of ok -> + tsp("start_http_server -> inets started"), ok; Error1 -> - test_server:format("<ERROR> Failed starting application: " - "~n Error: ~p" - "~n", [Error1]), + tsp("<ERROR> Failed starting application: " + "~n Error1: ~p", [Error1]), Error1 end; Error2 -> - test_server:format("<ERROR> Failed set application env: " - "~n Error: ~p" - "~n", [Error2]), + tsp("<ERROR> Failed set application env: " + "~n Error: ~p", [Error2]), Error2 end. @@ -285,29 +334,45 @@ os_based_skip(_) -> %% Host -> atom() | string() | {A, B, C, D} %% Port -> integer() -connect_bin(ssl, Host, Port) -> - connect(ssl, Host, Port, [binary, {packet,0}]); -connect_bin(ossl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, old}, binary, {packet,0}]); -connect_bin(essl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, new}, binary, {packet,0}, {reuseaddr, true}]); -connect_bin(ip_comm, Host, Port) -> - Opts = [inet6, binary, {packet,0}], +connect_bin(SockType, Host, Port) -> + connect_bin(SockType, Host, Port, []). + +connect_bin(ssl, Host, Port, Opts0) -> + Opts = [binary, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(ossl, Host, Port, Opts0) -> + Opts = [{ssl_imp, old}, binary, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(essl, Host, Port, Opts0) -> + Opts = [{ssl_imp, new}, binary, {packet,0}, {reuseaddr, true} | Opts0], + connect(ssl, Host, Port, Opts); +connect_bin(ip_comm, Host, Port, Opts0) -> + Opts = [binary, {packet, 0} | Opts0], connect(ip_comm, Host, Port, Opts). + +connect_byte(SockType, Host, Port) -> + connect_byte(SockType, Host, Port, []). -connect_byte(ssl, Host, Port) -> - connect(ssl, Host, Port, [{packet,0}]); -connect_byte(ossl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, old}, {packet,0}]); -connect_byte(essl, Host, Port) -> - connect(ssl, Host, Port, [{ssl_imp, new}, {packet,0}]); -connect_byte(ip_comm, Host, Port) -> - Opts = [inet6, {packet,0}], +connect_byte(ssl, Host, Port, Opts0) -> + Opts = [{packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(ossl, Host, Port, Opts0) -> + Opts = [{ssl_imp, old}, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(essl, Host, Port, Opts0) -> + Opts = [{ssl_imp, new}, {packet,0} | Opts0], + connect(ssl, Host, Port, Opts); +connect_byte(ip_comm, Host, Port, Opts0) -> + Opts = [{packet,0} | Opts0], connect(ip_comm, Host, Port, Opts). connect(ssl, Host, Port, Opts) -> + tsp("connect(ssl) -> entry with" + "~n Host: ~p" + "~n Port: ~p" + "~n Opts: ~p", [Host, Port, Opts]), ssl:start(), %% Does not support ipv6 in old ssl case ssl:connect(Host, Port, Opts) of @@ -319,21 +384,28 @@ connect(ssl, Host, Port, Opts) -> Error end; connect(ip_comm, Host, Port, Opts) -> + tsp("connect(ip_comm) -> entry with" + "~n Host: ~p" + "~n Port: ~p" + "~n Opts: ~p", [Host, Port, Opts]), case gen_tcp:connect(Host,Port, Opts) of {ok, Socket} -> - %% tsp("connect success"), + tsp("connect success"), {ok, Socket}; {error, nxdomain} -> - tsp("nxdomain opts: ~p", [Opts]), + tsp("connect error nxdomain when opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, eafnosupport} -> - tsp("eafnosupport opts: ~p", [Opts]), + tsp("connect error eafnosupport when opts: ~p", [Opts]), + connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); + {error, econnreset} -> + tsp("connect error econnreset when opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, enetunreach} -> - tsp("eafnosupport opts: ~p", [Opts]), + tsp("connect error eafnosupport when opts: ~p", [Opts]), connect(ip_comm, Host, Port, lists:delete(inet6, Opts)); {error, {enfile,_}} -> - tsp("Error enfile"), + tsp("connect error enfile when opts: ~p", [Opts]), {error, enfile}; Error -> tsp("Unexpected error: " @@ -414,7 +486,22 @@ flush() -> tsp(F) -> tsp(F, []). tsp(F, A) -> - test_server:format("~p ~p ~p:" ++ F ++ "~n", [node(), self(), ?MODULE | A]). + Timestamp = formated_timestamp(), + test_server:format("*** ~s ~p ~p ~w:" ++ F ++ "~n", + [Timestamp, node(), self(), ?MODULE | A]). tsf(Reason) -> test_server:fail(Reason). + +formated_timestamp() -> + format_timestamp( os:timestamp() ). + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). + |