%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2005-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% -module(emem_SUITE). -export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2, init_per_suite/1, end_per_suite/1, receive_and_save_trace/2, send_trace/2]). -export([live_node/1, 'sparc_sunos5.8_32b_emt2.0'/1, 'pc_win2000_32b_emt2.0'/1, 'pc.smp_linux2.2.19pre17_32b_emt2.0'/1, 'powerpc_darwin7.7.0_32b_emt2.0'/1, 'alpha_osf1v5.1_64b_emt2.0'/1, 'sparc_sunos5.8_64b_emt2.0'/1, 'sparc_sunos5.8_32b_emt1.0'/1, 'pc_win2000_32b_emt1.0'/1, 'powerpc_darwin7.7.0_32b_emt1.0'/1, 'alpha_osf1v5.1_64b_emt1.0'/1, 'sparc_sunos5.8_64b_emt1.0'/1]). -include_lib("kernel/include/file.hrl"). -include_lib("common_test/include/ct.hrl"). -define(EMEM_64_32_COMMENT, "64 bit trace; this build of emem can only handle 32 bit traces"). -record(emem_res, {nodename, hostname, pid, start_time, trace_version, max_word_size, word_size, last_values, maximum, exit_code}). %% %% Exported suite functions %% suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,5}}]. all() -> case test_server:is_debug() of true -> {skip, "Not run when debug compiled"}; false -> test_cases() end. test_cases() -> [live_node, 'sparc_sunos5.8_32b_emt2.0', 'pc_win2000_32b_emt2.0', 'pc.smp_linux2.2.19pre17_32b_emt2.0', 'powerpc_darwin7.7.0_32b_emt2.0', 'alpha_osf1v5.1_64b_emt2.0', 'sparc_sunos5.8_64b_emt2.0', 'sparc_sunos5.8_32b_emt1.0', 'pc_win2000_32b_emt1.0', 'powerpc_darwin7.7.0_32b_emt1.0', 'alpha_osf1v5.1_64b_emt1.0', 'sparc_sunos5.8_64b_emt1.0']. init_per_testcase(Case, Config) when is_list(Config) -> case maybe_skip(Config) of {skip, _}=Skip -> Skip; ok -> %% Until emem is completely stable we run these tests in a working %% directory with an ignore_core_files file which will make the %% search for core files ignore cores generated by this suite. ignore_cores:setup(?MODULE, Case, [{testcase, Case}|Config]) end. end_per_testcase(_Case, Config) when is_list(Config) -> ignore_cores:restore(Config), ok. maybe_skip(Config) -> DataDir = proplists:get_value(data_dir, Config), case filelib:is_dir(DataDir) of false -> {skip, "No data directory"}; true -> case proplists:get_value(emem, Config) of undefined -> {skip, "emem not found"}; _ -> ok end end. init_per_suite(Config) when is_list(Config) -> BinDir = filename:join([code:lib_dir(tools), "bin"]), Target = erlang:system_info(system_architecture), Res = (catch begin case check_dir(filename:join([BinDir, Target])) of not_found -> ok; TDir -> check_emem(TDir, purecov), check_emem(TDir, purify), check_emem(TDir, debug), check_emem(TDir, opt) end, check_emem(BinDir, opt), "" end), Res ++ ignore_cores:init(Config). end_per_suite(Config) when is_list(Config) -> Config1 = lists:keydelete(emem, 1, Config), Config2 = lists:keydelete(emem_comment, 1, Config1), ignore_cores:fini(Config2). %% %% %% Test cases %% %% live_node(Config) when is_list(Config) -> {ok, EmuFlag, Port} = start_emem(Config), Nodename = mk_nodename(Config), {ok, Node} = start_node(Nodename, EmuFlag), NP = spawn(Node, fun () -> receive go -> ok end, I = spawn(fun () -> ignorer end), GC = fun () -> GCP = fun (P) -> garbage_collect(P) end, lists:foreach(GCP, processes()) end, Seq = fun () -> I ! lists:seq(1, 1000000) end, spawn_link(Seq), B1 = <<0:10000000>>, spawn_link(Seq), B2 = <<0:10000000>>, spawn_link(Seq), B3 = <<0:10000000>>, I ! {B1, B2, B3}, GC(), GC(), GC() end), MRef = erlang:monitor(process, NP), NP ! go, receive {'DOWN', MRef, process, NP, Reason} -> spawn(Node, fun () -> halt(17) end), normal = Reason end, Res = get_emem_result(Port), {ok, Hostname} = inet:gethostname(), ShortHostname = short_hostname(Hostname), {true, _} = has_prefix(Nodename, Res#emem_res.nodename), ShortHostname = short_hostname(Res#emem_res.hostname), Bits = case erlang:system_info(wordsize) of 4 -> "32 bits"; 8 -> "64 bits" end, Bits = Res#emem_res.word_size, "17" = Res#emem_res.exit_code, emem_comment(Config). 'sparc_sunos5.8_32b_emt2.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "test_server" = Res#emem_res.nodename, "gorbag" = Res#emem_res.hostname, "17074" = Res#emem_res.pid, "2005-01-14 17:28:37.881980" = Res#emem_res.start_time, "2.0" = Res#emem_res.trace_version, "32 bits" = Res#emem_res.word_size, ["15", "2665739", "8992", "548986", "16131", "539994", "4334192", "1", "99", "15", "98", "0", "0", "49", "0", "49"] = Res#emem_res.last_values, ["5972061", "9662", "7987824", "5", "2375680", "3"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config). 'pc_win2000_32b_emt2.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "test_server" = Res#emem_res.nodename, "E-788FCF5191B54" = Res#emem_res.hostname, "504" = Res#emem_res.pid, "2005-01-24 17:27:28.224000" = Res#emem_res.start_time, "2.0" = Res#emem_res.trace_version, "32 bits" = Res#emem_res.word_size, ["11", "2932575", "8615", "641087", "68924", "632472"] = Res#emem_res.last_values, ["5434206", "9285"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config). 'pc.smp_linux2.2.19pre17_32b_emt2.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "test_server" = Res#emem_res.nodename, "four-roses" = Res#emem_res.hostname, "20689" = Res#emem_res.pid, "2005-01-20 13:11:26.143077" = Res#emem_res.start_time, "2.0" = Res#emem_res.trace_version, "32 bits" = Res#emem_res.word_size, ["49", "2901817", "9011", "521610", "10875", "512599", "5392096", "2", "120", "10", "118", "0", "0", "59", "0", "59"] = Res#emem_res.last_values, ["6182918", "9681", "9062112", "6", "2322432", "3"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config). 'powerpc_darwin7.7.0_32b_emt2.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "test_server" = Res#emem_res.nodename, "grima" = Res#emem_res.hostname, "13021" = Res#emem_res.pid, "2005-01-20 15:08:17.568668" = Res#emem_res.start_time, "2.0" = Res#emem_res.trace_version, "32 bits" = Res#emem_res.word_size, ["9", "2784323", "8641", "531105", "15893", "522464"] = Res#emem_res.last_values, ["6150376", "9311"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config). 'alpha_osf1v5.1_64b_emt2.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "test_server" = Res#emem_res.nodename, "thorin" = Res#emem_res.hostname, "224630" = Res#emem_res.pid, "2005-01-20 22:38:01.299632" = Res#emem_res.start_time, "2.0" = Res#emem_res.trace_version, "64 bits" = Res#emem_res.word_size, case Res#emem_res.max_word_size of "32 bits" -> emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ["22", "6591992", "8625", "516785", "14805", "508160", "11429184", "5", "127", "254", "122", "0", "0", "61", "0", "61"] = Res#emem_res.last_values, ["7041775", "9295", "11593024", "7", "2097152", "3"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config) end. 'sparc_sunos5.8_64b_emt2.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "test_server" = Res#emem_res.nodename, "gorbag" = Res#emem_res.hostname, "10907" = Res#emem_res.pid, "2005-01-20 13:48:34.677068" = Res#emem_res.start_time, "2.0" = Res#emem_res.trace_version, "64 bits" = Res#emem_res.word_size, case Res#emem_res.max_word_size of "32 bits" -> emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ["16", "5032887", "8657", "530635", "14316", "521978", "8627140", "5", "139", "19", "134", "0", "0", "67", "0", "67"] = Res#emem_res.last_values, ["11695070", "9324", "16360388", "10", "4136960", "3"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config) end. 'sparc_sunos5.8_32b_emt1.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "" = Res#emem_res.nodename, "" = Res#emem_res.hostname, "" = Res#emem_res.pid, "" = Res#emem_res.start_time, "1.0" = Res#emem_res.trace_version, "32 bits" = Res#emem_res.word_size, ["11", "2558261", "8643", "560610", "15325", "551967"] = Res#emem_res.last_values, ["2791121", "9317"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config). 'pc_win2000_32b_emt1.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "" = Res#emem_res.nodename, "" = Res#emem_res.hostname, "" = Res#emem_res.pid, "" = Res#emem_res.start_time, "1.0" = Res#emem_res.trace_version, "32 bits" = Res#emem_res.word_size, ["6", "2965248", "8614", "640897", "68903", "632283"] = Res#emem_res.last_values, ["3147090", "9283"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config). 'powerpc_darwin7.7.0_32b_emt1.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "" = Res#emem_res.nodename, "" = Res#emem_res.hostname, "" = Res#emem_res.pid, "" = Res#emem_res.start_time, "1.0" = Res#emem_res.trace_version, "32 bits" = Res#emem_res.word_size, ["8", "2852991", "8608", "529662", "15875", "521054"] = Res#emem_res.last_values, ["3173335", "9278"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config). 'alpha_osf1v5.1_64b_emt1.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "" = Res#emem_res.nodename, "" = Res#emem_res.hostname, "" = Res#emem_res.pid, "" = Res#emem_res.start_time, "1.0" = Res#emem_res.trace_version, "64 bits" = Res#emem_res.word_size, case Res#emem_res.max_word_size of "32 bits" -> emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ["22", "6820094", "8612", "515518", "14812", "506906"] = Res#emem_res.last_values, ["7292413", "9282"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config) end. 'sparc_sunos5.8_64b_emt1.0'(Config) when is_list(Config) -> Res = run_emem_on_casefile(Config), "" = Res#emem_res.nodename, "" = Res#emem_res.hostname, "" = Res#emem_res.pid, "" = Res#emem_res.start_time, "1.0" = Res#emem_res.trace_version, "64 bits" = Res#emem_res.word_size, case Res#emem_res.max_word_size of "32 bits" -> emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ["15", "4965746", "8234", "543940", "14443", "535706"] = Res#emem_res.last_values, ["11697645", "8908"] = Res#emem_res.maximum, "0" = Res#emem_res.exit_code, emem_comment(Config) end. %% %% %% Auxiliary functions %% %% receive_and_save_trace(PortNumber, FileName) when is_integer(PortNumber), is_list(FileName) -> {ok, F} = file:open(FileName, [write, compressed]), {ok, LS} = gen_tcp:listen(PortNumber, [inet, {reuseaddr,true}, binary]), {ok, S} = gen_tcp:accept(LS), gen_tcp:close(LS), receive_loop(S,F). receive_loop(Socket, File) -> receive {tcp, Socket, Data} -> ok = file:write(File, Data), receive_loop(Socket, File); {tcp_closed, Socket} -> file:close(File), ok; {tcp_error, Socket, Reason} -> file:close(File), {error, Reason} end. send_trace({Host, PortNumber}, FileName) when is_list(Host), is_integer(PortNumber), is_list(FileName) -> {ok, F} = file:open(FileName, [read, compressed]), {ok, S} = gen_tcp:connect(Host, PortNumber, [inet,{packet, 0}]), send_loop(S, F); send_trace(EmuFlag, FileName) when is_list(EmuFlag), is_list(FileName) -> ["+Mit", IpAddrStr, PortNoStr] = string:tokens(EmuFlag, " :"), send_trace({IpAddrStr, list_to_integer(PortNoStr)}, FileName). send_loop(Socket, File) -> case file:read(File, 128) of {ok, Data} -> case gen_tcp:send(Socket, Data) of ok -> send_loop(Socket, File); Error -> gen_tcp:close(Socket), file:close(File), Error end; eof -> gen_tcp:close(Socket), file:close(File), ok; Error -> gen_tcp:close(Socket), file:close(File), Error end. check_emem(Dir, Type) when is_atom(Type) -> ExeSuffix = case os:type() of {win32, _} -> ".exe"; _ -> "" end, TypeSuffix = case Type of opt -> ""; _ -> "." ++ atom_to_list(Type) end, Emem = "emem" ++ TypeSuffix ++ ExeSuffix, case check_file(filename:join([Dir, Emem])) of not_found -> ok; File -> Comment = case Type of opt -> ""; _ -> "[emem " ++ atom_to_list(Type) ++ " compiled]" end, throw([{emem, File}, {emem_comment, Comment}]) end. check_dir(DirName) -> case file:read_file_info(DirName) of {ok, #file_info {type = directory, access = A}} when A == read; A == read_write -> DirName; _ -> not_found end. check_file(FileName) -> case file:read_file_info(FileName) of {ok, #file_info {type = regular, access = A}} when A == read; A == read_write -> FileName; _ -> not_found end. emem_comment(Config) when is_list(Config) -> emem_comment(Config, ""). emem_comment(Config, ExtraComment) when is_list(Config), is_list(ExtraComment) -> case {proplists:get_value(emem_comment, Config), ExtraComment} of {"", ""} -> ok; {"", XC} -> {comment, XC}; {EmemC, ""} -> {comment, EmemC}; {EmemC, XC} -> {comment, EmemC ++ " " ++ XC} end. run_emem_on_casefile(Config) -> CaseName = atom_to_list(proplists:get_value(testcase, Config)), File = filename:join([proplists:get_value(data_dir, Config), CaseName ++ ".gz"]), case check_file(File) of not_found -> ct:fail({error, {filenotfound, File}}); _ -> ok end, {ok, EmuFlag, Port} = start_emem(Config), Parent = self(), Ref = make_ref(), spawn_link(fun () -> SRes = send_trace(EmuFlag, File), Parent ! {Ref, SRes} end), Res = get_emem_result(Port), receive {Ref, ok} -> ok; {Ref, SendError} -> io:format("Send result: ~p~n", [SendError]) end, Res. get_emem_result(Port) -> {Res, LV} = get_emem_result(Port, {#emem_res{}, []}), Res#emem_res{last_values = string:tokens(LV, " ")}. get_emem_result(Port, {_EmemRes, _LastValues} = Res) -> case get_emem_line(Port) of eof -> Res; Line -> get_emem_result(Port, parse_emem_line(Line, Res)) end. parse_emem_main_header_footer_line(Line, {ER, LV} = Res) -> %% Header case has_prefix("> Nodename:", Line) of {true, NN} -> throw({ER#emem_res{nodename = strip(NN)}, LV}); false -> ok end, case has_prefix("> Hostname:", Line) of {true, HN} -> throw({ER#emem_res{hostname = strip(HN)}, LV}); false -> ok end, case has_prefix("> Pid:", Line) of {true, P} -> throw({ER#emem_res{pid = strip(P)}, LV}); false -> ok end, case has_prefix("> Start time (UTC):", Line) of {true, ST} -> throw({ER#emem_res{start_time = strip(ST)}, LV}); false -> ok end, case has_prefix("> Actual trace version:", Line) of {true, TV} -> throw({ER#emem_res{trace_version = strip(TV)}, LV}); false -> ok end, case has_prefix("> Maximum trace word size:", Line) of {true, MWS} -> throw({ER#emem_res{max_word_size = strip(MWS)}, LV}); false -> ok end, case has_prefix("> Actual trace word size:", Line) of {true, WS} -> throw({ER#emem_res{word_size = strip(WS)}, LV}); false -> ok end, %% Footer case has_prefix("> Maximum:", Line) of {true, M} -> throw({ER#emem_res{maximum = string:tokens(M," ")}, LV}); false -> ok end, case has_prefix("> Emulator exited with code:", Line) of {true, EC} -> throw({ER#emem_res{exit_code = strip(EC)}, LV}); false -> ok end, Res. parse_emem_header_line(_Line, {_ER, _LV} = Res) -> Res. parse_emem_value_line(Line, {EmemRes, _OldLastValues}) -> {EmemRes, Line}. parse_emem_line("", Res) -> Res; parse_emem_line(Line, Res) -> [Prefix | _] = Line, case Prefix of $> -> catch parse_emem_main_header_footer_line(Line, Res); $| -> catch parse_emem_header_line(Line, Res); _ -> catch parse_emem_value_line(Line, Res) end. start_emem(Config) when is_list(Config) -> Emem = proplists:get_value(emem, Config), Cd = case ignore_cores:dir(Config) of false -> []; Dir -> [{cd, Dir}] end, case open_port({spawn, Emem ++ " -t -n -o -i 1"}, Cd ++ [{line, 1024}, eof]) of Port when is_port(Port) -> {ok, read_emu_flag(Port), Port}; Error -> ct:fail(Error) end. read_emu_flag(Port) -> Line = case get_emem_line(Port) of eof -> ct:fail(unexpected_end_of_file); L -> L end, case has_prefix("> Emulator command line argument:", Line) of {true, EmuFlag} -> EmuFlag; false -> read_emu_flag(Port) end. get_emem_line(Port, Acc) -> receive {Port, {data, {eol, Data}}} -> Res = case Acc of [] -> Data; _ -> lists:flatten([Acc|Data]) end, io:format("~s", [Res]), Res; {Port, {data, {noeol, Data}}} -> get_emem_line(Port, [Acc|Data]); {Port, eof} -> port_close(Port), eof end. get_emem_line(Port) -> get_emem_line(Port, []). short_hostname([]) -> []; short_hostname([$.|_]) -> []; short_hostname([C|Cs]) -> [C | short_hostname(Cs)]. has_prefix([], List) when is_list(List) -> {true, List}; has_prefix([P|Xs], [P|Ys]) -> has_prefix(Xs, Ys); has_prefix(_, _) -> false. strip(Str) -> string:strip(Str). mk_nodename(Config) -> Us = erlang:monotonic_time(), atom_to_list(?MODULE) ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config)) ++ integer_to_list(Us). start_node(Name, Args) -> Pa = filename:dirname(code:which(?MODULE)), test_server:start_node(Name, peer, [{args, Args ++ " -pa " ++ Pa}]).