%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2005-2011. All Rights Reserved. %% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. %% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. %% %% %CopyrightEnd% %% -module(emem_SUITE). %%-define(line_trace, 1). -export([init_per_suite/1, end_per_suite/1, receive_and_save_trace/2, send_trace/2]). -export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2, init_per_testcase/2, end_per_testcase/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("test_server/include/test_server.hrl"). -define(DEFAULT_TIMEOUT, ?t:minutes(5)). -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]}]. all() -> case is_debug_compiled() of true -> {skip, "Not run when debug compiled"}; false -> test_cases() end. groups() -> []. init_per_group(_GroupName, Config) -> Config. end_per_group(_GroupName, Config) -> Config. 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 -> Dog = ?t:timetrap(?DEFAULT_TIMEOUT), %% 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, [{watchdog, Dog}, {testcase, Case} | Config]) end. end_per_testcase(_Case, Config) when is_list(Config) -> ignore_cores:restore(Config), Dog = ?config(watchdog, Config), ?t:timetrap_cancel(Dog), ok. maybe_skip(Config) -> DataDir = ?config(data_dir, Config), case filelib:is_dir(DataDir) of false -> {skip, "No data directory"}; true -> case ?config(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(doc) -> []; live_node(suite) -> []; live_node(Config) when is_list(Config) -> ?line {ok, EmuFlag, Port} = start_emem(Config), ?line Nodename = mk_nodename(Config), ?line {ok, Node} = start_node(Nodename, EmuFlag), ?line 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), ?line MRef = erlang:monitor(process, NP), NP ! go, ?line receive {'DOWN', MRef, process, NP, Reason} -> ?line spawn(Node, fun () -> halt(17) end), ?line normal = Reason end, ?line Res = get_emem_result(Port), ?line {ok, Hostname} = inet:gethostname(), ?line ShortHostname = short_hostname(Hostname), ?line {true, _} = has_prefix(Nodename, Res#emem_res.nodename), ?line ShortHostname = short_hostname(Res#emem_res.hostname), ?line Bits = case erlang:system_info(wordsize) of 4 -> ?line "32 bits"; 8 -> ?line "64 bits" end, ?line Bits = Res#emem_res.word_size, ?line "17" = Res#emem_res.exit_code, ?line emem_comment(Config). 'sparc_sunos5.8_32b_emt2.0'(doc) -> []; 'sparc_sunos5.8_32b_emt2.0'(suite) -> []; 'sparc_sunos5.8_32b_emt2.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "test_server" = Res#emem_res.nodename, ?line "gorbag" = Res#emem_res.hostname, ?line "17074" = Res#emem_res.pid, ?line "2005-01-14 17:28:37.881980" = Res#emem_res.start_time, ?line "2.0" = Res#emem_res.trace_version, ?line "32 bits" = Res#emem_res.word_size, ?line ["15", "2665739", "8992", "548986", "16131", "539994", "4334192", "1", "99", "15", "98", "0", "0", "49", "0", "49"] = Res#emem_res.last_values, ?line ["5972061", "9662", "7987824", "5", "2375680", "3"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config). 'pc_win2000_32b_emt2.0'(doc) -> []; 'pc_win2000_32b_emt2.0'(suite) -> []; 'pc_win2000_32b_emt2.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "test_server" = Res#emem_res.nodename, ?line "E-788FCF5191B54" = Res#emem_res.hostname, ?line "504" = Res#emem_res.pid, ?line "2005-01-24 17:27:28.224000" = Res#emem_res.start_time, ?line "2.0" = Res#emem_res.trace_version, ?line "32 bits" = Res#emem_res.word_size, ?line ["11", "2932575", "8615", "641087", "68924", "632472"] = Res#emem_res.last_values, ?line ["5434206", "9285"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config). 'pc.smp_linux2.2.19pre17_32b_emt2.0'(doc) -> []; 'pc.smp_linux2.2.19pre17_32b_emt2.0'(suite) -> []; 'pc.smp_linux2.2.19pre17_32b_emt2.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "test_server" = Res#emem_res.nodename, ?line "four-roses" = Res#emem_res.hostname, ?line "20689" = Res#emem_res.pid, ?line "2005-01-20 13:11:26.143077" = Res#emem_res.start_time, ?line "2.0" = Res#emem_res.trace_version, ?line "32 bits" = Res#emem_res.word_size, ?line ["49", "2901817", "9011", "521610", "10875", "512599", "5392096", "2", "120", "10", "118", "0", "0", "59", "0", "59"] = Res#emem_res.last_values, ?line ["6182918", "9681", "9062112", "6", "2322432", "3"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config). 'powerpc_darwin7.7.0_32b_emt2.0'(doc) -> []; 'powerpc_darwin7.7.0_32b_emt2.0'(suite) -> []; 'powerpc_darwin7.7.0_32b_emt2.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "test_server" = Res#emem_res.nodename, ?line "grima" = Res#emem_res.hostname, ?line "13021" = Res#emem_res.pid, ?line "2005-01-20 15:08:17.568668" = Res#emem_res.start_time, ?line "2.0" = Res#emem_res.trace_version, ?line "32 bits" = Res#emem_res.word_size, ?line ["9", "2784323", "8641", "531105", "15893", "522464"] = Res#emem_res.last_values, ?line ["6150376", "9311"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config). 'alpha_osf1v5.1_64b_emt2.0'(doc) -> []; 'alpha_osf1v5.1_64b_emt2.0'(suite) -> []; 'alpha_osf1v5.1_64b_emt2.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "test_server" = Res#emem_res.nodename, ?line "thorin" = Res#emem_res.hostname, ?line "224630" = Res#emem_res.pid, ?line "2005-01-20 22:38:01.299632" = Res#emem_res.start_time, ?line "2.0" = Res#emem_res.trace_version, ?line "64 bits" = Res#emem_res.word_size, ?line case Res#emem_res.max_word_size of "32 bits" -> ?line emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ?line ["22", "6591992", "8625", "516785", "14805", "508160", "11429184", "5", "127", "254", "122", "0", "0", "61", "0", "61"] = Res#emem_res.last_values, ?line ["7041775", "9295", "11593024", "7", "2097152", "3"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config) end. 'sparc_sunos5.8_64b_emt2.0'(doc) -> []; 'sparc_sunos5.8_64b_emt2.0'(suite) -> []; 'sparc_sunos5.8_64b_emt2.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "test_server" = Res#emem_res.nodename, ?line "gorbag" = Res#emem_res.hostname, ?line "10907" = Res#emem_res.pid, ?line "2005-01-20 13:48:34.677068" = Res#emem_res.start_time, ?line "2.0" = Res#emem_res.trace_version, ?line "64 bits" = Res#emem_res.word_size, ?line case Res#emem_res.max_word_size of "32 bits" -> ?line emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ?line ["16", "5032887", "8657", "530635", "14316", "521978", "8627140", "5", "139", "19", "134", "0", "0", "67", "0", "67"] = Res#emem_res.last_values, ?line ["11695070", "9324", "16360388", "10", "4136960", "3"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config) end. 'sparc_sunos5.8_32b_emt1.0'(doc) -> []; 'sparc_sunos5.8_32b_emt1.0'(suite) -> []; 'sparc_sunos5.8_32b_emt1.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "" = Res#emem_res.nodename, ?line "" = Res#emem_res.hostname, ?line "" = Res#emem_res.pid, ?line "" = Res#emem_res.start_time, ?line "1.0" = Res#emem_res.trace_version, ?line "32 bits" = Res#emem_res.word_size, ?line ["11", "2558261", "8643", "560610", "15325", "551967"] = Res#emem_res.last_values, ?line ["2791121", "9317"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config). 'pc_win2000_32b_emt1.0'(doc) -> []; 'pc_win2000_32b_emt1.0'(suite) -> []; 'pc_win2000_32b_emt1.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "" = Res#emem_res.nodename, ?line "" = Res#emem_res.hostname, ?line "" = Res#emem_res.pid, ?line "" = Res#emem_res.start_time, ?line "1.0" = Res#emem_res.trace_version, ?line "32 bits" = Res#emem_res.word_size, ?line ["6", "2965248", "8614", "640897", "68903", "632283"] = Res#emem_res.last_values, ?line ["3147090", "9283"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config). 'powerpc_darwin7.7.0_32b_emt1.0'(doc) -> []; 'powerpc_darwin7.7.0_32b_emt1.0'(suite) -> []; 'powerpc_darwin7.7.0_32b_emt1.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "" = Res#emem_res.nodename, ?line "" = Res#emem_res.hostname, ?line "" = Res#emem_res.pid, ?line "" = Res#emem_res.start_time, ?line "1.0" = Res#emem_res.trace_version, ?line "32 bits" = Res#emem_res.word_size, ?line ["8", "2852991", "8608", "529662", "15875", "521054"] = Res#emem_res.last_values, ?line ["3173335", "9278"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config). 'alpha_osf1v5.1_64b_emt1.0'(doc) -> []; 'alpha_osf1v5.1_64b_emt1.0'(suite) -> []; 'alpha_osf1v5.1_64b_emt1.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "" = Res#emem_res.nodename, ?line "" = Res#emem_res.hostname, ?line "" = Res#emem_res.pid, ?line "" = Res#emem_res.start_time, ?line "1.0" = Res#emem_res.trace_version, ?line "64 bits" = Res#emem_res.word_size, ?line case Res#emem_res.max_word_size of "32 bits" -> ?line emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ?line ["22", "6820094", "8612", "515518", "14812", "506906"] = Res#emem_res.last_values, ?line ["7292413", "9282"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line emem_comment(Config) end. 'sparc_sunos5.8_64b_emt1.0'(doc) -> []; 'sparc_sunos5.8_64b_emt1.0'(suite) -> []; 'sparc_sunos5.8_64b_emt1.0'(Config) when is_list(Config) -> ?line Res = run_emem_on_casefile(Config), ?line "" = Res#emem_res.nodename, ?line "" = Res#emem_res.hostname, ?line "" = Res#emem_res.pid, ?line "" = Res#emem_res.start_time, ?line "1.0" = Res#emem_res.trace_version, ?line "64 bits" = Res#emem_res.word_size, ?line case Res#emem_res.max_word_size of "32 bits" -> ?line emem_comment(Config, ?EMEM_64_32_COMMENT); "64 bits" -> ?line ["15", "4965746", "8234", "543940", "14443", "535706"] = Res#emem_res.last_values, ?line ["11697645", "8908"] = Res#emem_res.maximum, ?line "0" = Res#emem_res.exit_code, ?line 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) -> ?line {ok, F} = file:open(FileName, [read, compressed]), ?line {ok, S} = gen_tcp:connect(Host, PortNumber, [inet,{packet, 0}]), ?line send_loop(S, F); send_trace(EmuFlag, FileName) when is_list(EmuFlag), is_list(FileName) -> ?line ["+Mit", IpAddrStr, PortNoStr] = string:tokens(EmuFlag, " :"), ?line send_trace({IpAddrStr, list_to_integer(PortNoStr)}, FileName). send_loop(Socket, File) -> ?line case file:read(File, 128) of {ok, Data} -> ?line case gen_tcp:send(Socket, Data) of ok -> ?line send_loop(Socket, File); Error -> ?line gen_tcp:close(Socket), ?line file:close(File), Error end; eof -> ?line gen_tcp:close(Socket), ?line file:close(File), ?line ok; Error -> ?line gen_tcp:close(Socket), ?line file:close(File), ?line Error end. check_emem(Dir, Type) when is_atom(Type) -> ExeSuffix = case ?t: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 -> ?line FileName; _ -> ?line 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 {?config(emem_comment, Config), ExtraComment} of {"", ""} -> ?line ok; {"", XC} -> ?line {comment, XC}; {EmemC, ""} -> ?line {comment, EmemC}; {EmemC, XC} -> ?line {comment, EmemC ++ " " ++ XC} end. run_emem_on_casefile(Config) -> CaseName = atom_to_list(?config(testcase, Config)), ?line File = filename:join([?config(data_dir, Config), CaseName ++ ".gz"]), ?line case check_file(File) of not_found -> ?line ?t:fail({error, {filenotfound, File}}); _ -> ?line ok end, ?line {ok, EmuFlag, Port} = start_emem(Config), ?line Parent = self(), ?line Ref = make_ref(), ?line spawn_link(fun () -> SRes = send_trace(EmuFlag, File), Parent ! {Ref, SRes} end), ?line Res = get_emem_result(Port), ?line receive {Ref, ok} -> ?line ok; {Ref, SendError} -> ?line ?t:format("Send result: ~p~n", [SendError]) end, ?line Res. get_emem_result(Port) -> ?line {Res, LV} = get_emem_result(Port, {#emem_res{}, []}), ?line Res#emem_res{last_values = string:tokens(LV, " ")}. get_emem_result(Port, {_EmemRes, _LastValues} = Res) -> ?line case get_emem_line(Port) of eof -> ?line Res; Line -> ?line get_emem_result(Port, parse_emem_line(Line, Res)) end. parse_emem_main_header_footer_line(Line, {ER, LV} = Res) -> %% Header ?line case has_prefix("> Nodename:", Line) of {true, NN} -> ?line throw({ER#emem_res{nodename = strip(NN)}, LV}); false -> ?line ok end, ?line case has_prefix("> Hostname:", Line) of {true, HN} -> ?line throw({ER#emem_res{hostname = strip(HN)}, LV}); false -> ?line ok end, ?line case has_prefix("> Pid:", Line) of {true, P} -> ?line throw({ER#emem_res{pid = strip(P)}, LV}); false -> ?line ok end, ?line case has_prefix("> Start time (UTC):", Line) of {true, ST} -> ?line throw({ER#emem_res{start_time = strip(ST)}, LV}); false -> ?line ok end, ?line case has_prefix("> Actual trace version:", Line) of {true, TV} -> ?line throw({ER#emem_res{trace_version = strip(TV)}, LV}); false -> ?line ok end, ?line case has_prefix("> Maximum trace word size:", Line) of {true, MWS} -> ?line throw({ER#emem_res{max_word_size = strip(MWS)}, LV}); false -> ?line ok end, ?line case has_prefix("> Actual trace word size:", Line) of {true, WS} -> ?line throw({ER#emem_res{word_size = strip(WS)}, LV}); false -> ?line ok end, %% Footer ?line case has_prefix("> Maximum:", Line) of {true, M} -> ?line throw({ER#emem_res{maximum = string:tokens(M," ")}, LV}); false -> ?line ok end, ?line case has_prefix("> Emulator exited with code:", Line) of {true, EC} -> ?line throw({ER#emem_res{exit_code = strip(EC)}, LV}); false -> ?line ok end, ?line Res. parse_emem_header_line(_Line, {_ER, _LV} = Res) -> ?line Res. parse_emem_value_line(Line, {EmemRes, _OldLastValues}) -> ?line {EmemRes, Line}. parse_emem_line("", Res) -> ?line Res; parse_emem_line(Line, Res) -> ?line [Prefix | _] = Line, case Prefix of $> -> ?line catch parse_emem_main_header_footer_line(Line, Res); $| -> ?line catch parse_emem_header_line(Line, Res); _ -> ?line catch parse_emem_value_line(Line, Res) end. start_emem(Config) when is_list(Config) -> ?line Emem = ?config(emem, Config), ?line Cd = case ignore_cores:dir(Config) of false -> []; Dir -> [{cd, Dir}] end, ?line case open_port({spawn, Emem ++ " -t -n -o -i 1"}, Cd ++ [{line, 1024}, eof]) of Port when is_port(Port) -> ?line {ok, read_emu_flag(Port), Port}; Error -> ?line ?t:fail(Error) end. read_emu_flag(Port) -> ?line Line = case get_emem_line(Port) of eof -> ?line ?t:fail(unexpected_end_of_file); L -> ?line L end, ?line case has_prefix("> Emulator command line argument:", Line) of {true, EmuFlag} -> EmuFlag; false -> ?line read_emu_flag(Port) end. get_emem_line(Port, Acc) -> ?line receive {Port, {data, {eol, Data}}} -> ?line Res = case Acc of [] -> ?line Data; _ -> ?line lists:flatten([Acc|Data]) end, ?line ?t:format("~s", [Res]), ?line Res; {Port, {data, {noeol, Data}}} -> ?line get_emem_line(Port, [Acc|Data]); {Port, eof} -> ?line port_close(Port), ?line eof end. get_emem_line(Port) -> ?line 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) -> {A, B, C} = now(), atom_to_list(?MODULE) ++ "-" ++ atom_to_list(?config(testcase, Config)) ++ "-" ++ integer_to_list(A*1000000000000 + B*1000000 + C). start_node(Name, Args) -> ?line Pa = filename:dirname(code:which(?MODULE)), ?line ?t:start_node(Name, peer, [{args, Args ++ " -pa " ++ Pa}]). % stop_node(Node) -> % ?t:stop_node(Node). is_debug_compiled() -> is_debug_compiled(erlang:system_info(system_version)). is_debug_compiled([$d,$e,$b,$u,$g | _]) -> true; is_debug_compiled([ _, _, _, _]) -> false; is_debug_compiled([]) -> false; is_debug_compiled([_|Rest]) -> is_debug_compiled(Rest).