aboutsummaryrefslogblamecommitdiffstats
path: root/lib/observer/test/crashdump_viewer_SUITE.erl
blob: 79ece7edf57b00efe485efdfcf6e572900a0f707 (plain) (tree)
1
2
3
4


                   
                                                        

















                                                                         

                                                                  



                                                            
                                           



















                                                                 
                                         
 
         

                                                         




                                     
           

                                    
           
 





                                                                            
                                             
                                       
                                                                            








































                                                                              
                                   




































































































































                                                                           
                              






                                                
                                                                             




                                                 
                                                                     
























































































































































                                                                              










                                                                







































































                                                                              








                                                      



                    
                                           



                       
                                                                                          
                      
                                                                                          

























                                                                    
                                                 

                                
                                                 
























































































































                                                                            
                             



















































                                                                 

                               









                        
                        

                     
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2003-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(crashdump_viewer_SUITE).

%% Test functions
-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2,
	 translate/1,start/1,fini/1,load_file/1,
	 non_existing/1,not_a_crashdump/1,old_crashdump/1]).
-export([init_per_suite/1, end_per_suite/1]).
-export([init_per_testcase/2, end_per_testcase/2]).

-include_lib("common_test/include/ct.hrl").
-include("test_server_line.hrl").
-include_lib("kernel/include/file.hrl").

-include_lib("stdlib/include/ms_transform.hrl").

-define(default_timeout, ?t:minutes(30)).
-define(sl_alloc_vsns,[r9b]).

init_per_testcase(_Case, Config) ->
    DataDir = ?config(data_dir,Config),
    Fs = filelib:wildcard(filename:join(DataDir,"*translated*")),
    lists:foreach(fun(F) -> file:delete(F) end,Fs),
    catch crashdump_viewer:stop(),
    Dog = ?t:timetrap(?default_timeout),
    [{watchdog, Dog}|Config].
end_per_testcase(_Case, Config) ->
    Dog=?config(watchdog, Config),
    ?t:timetrap_cancel(Dog),
    ok.

suite() -> [{ct_hooks,[ts_install_cth]}].

all() -> 
    [translate, load_file, non_existing, not_a_crashdump,
     old_crashdump].

groups() -> 
    [].

init_per_group(_GroupName, Config) ->
    Config.

end_per_group(_GroupName, Config) ->
    Config.


init_per_suite(doc) ->
    ["Create a lot of crashdumps which can be used in the testcases below"];
init_per_suite(Config) when is_list(Config) ->
    Dog = ?t:timetrap(?default_timeout),
    application:start(inets), % will be using the http client later
    httpc:set_options([{ipfamily,inet6fb4}]),
    DataDir = ?config(data_dir,Config),
    Rels = [R || R <- [r13b,r14b], ?t:is_release_available(R)] ++ [current],
    io:format("Creating crash dumps for the following releases: ~p", [Rels]),
    AllDumps = create_dumps(DataDir,Rels),
    ?t:timetrap_cancel(Dog),
    [{dumps,AllDumps}|Config].

translate(suite) ->
    [];
translate(doc) ->
    ["Test that crash dumps from OTP R9B can be translated"];
translate(Config) when is_list(Config) ->
    DataDir = ?config(data_dir,Config),
    OutFile = filename:join(DataDir,"translated"),
    
    R9BFiles = filelib:wildcard(filename:join(DataDir,"r9b_dump.*")),
    AllFiles = R9BFiles,
    lists:foreach(
      fun(File) ->
	      io:format("Translating file: ~s~n",[File]),
	      ok = crashdump_translate:old2new(File,OutFile),
	      check_result(File,OutFile)
      end,
      AllFiles),
    ok.

start(suite) ->
    [];
start(doc) ->
    ["Test start and stop of the Crashdump Viewer"];
start(Config) when is_list(Config) ->
    %% Set a much shorter timeout here... We don't have all the time in world.
    AngryDog = ?t:timetrap(?t:seconds(30)),
    Port = start_cdv(),
    true = is_pid(whereis(crashdump_viewer_server)),
    true = is_pid(whereis(web_tool)),
    Html = contents(Port,"start_page"),
    "Welcome to the Web BasedErlang Crash Dump Analyser" = strip(Html),
    ok = crashdump_viewer:stop(),
    timer:sleep(10), % give some time to stop
    undefined = whereis(crashdump_viewer_server),
    undefined = whereis(web_tool),
    Url = cdv_url(Port,"start_page"),
    {error,_} = httpc:request(Url),
%    exit(whereis(httpc_manager),kill),
    ?t:timetrap_cancel(AngryDog),
    ok.

fini(Config) when is_list(Config) ->
    ok.

load_file(suite) ->
    [];
load_file(doc) ->
    ["Load files into the tool and view all pages"];
load_file(Config) when is_list(Config) ->
    case ?t:is_debug() of
	true ->
	    {skip,"Debug-compiled emulator -- far too slow"};
	false ->
	    load_file_1(Config)
    end.


load_file_1(Config) ->
    DataDir = ?config(data_dir,Config),
    Port = start_cdv(),

    AllFiles = filelib:wildcard(filename:join(DataDir,"r*_dump.*")),
    lists:foreach(
      fun(File) ->
	      browse_file(Port,File),
	      special(Port,File)
      end,
      AllFiles),
    ok = crashdump_viewer:stop().

non_existing(suite) ->
    [];
non_existing(doc) ->
    ["Try to load nonexisting file"];
non_existing(Config) when is_list(Config) ->
    Port = start_cdv(),
    Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file",
    Html = request_sync(post,{Url,[],[],"path=nonexistingfile"}),
    "Please wait..." = title(Html),
    "An error occured:nonexistingfile is not an Erlang crash dump" = 
	strip(wait(10,Port,"redirect")),
    ok = crashdump_viewer:stop().

old_crashdump(doc) ->
    ["Try to load nonexisting file"];
old_crashdump(Config) when is_list(Config) ->
    Port = start_cdv(),
    DataDir = ?config(data_dir, Config),
    OldCrashDump = filename:join(DataDir, "old_format.dump"),
    Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file",
    Html = request_sync(post,{Url,[],[],"path="++OldCrashDump}),
    "Please wait..." = title(Html),
    Str = "An error occured:The crashdump "++OldCrashDump++
	" is in the pre-R10B format, which is no longer supported.",
    Str = strip(wait(10,Port,"redirect")),
    ok = crashdump_viewer:stop().


not_a_crashdump(suite) ->
    [];
not_a_crashdump(doc) ->
    ["Try to load a file which is not an erlang crashdump"];
not_a_crashdump(Config) when is_list(Config) ->
    Port = start_cdv(),
    NoCrashdump = code:which(?MODULE),
    Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file",
    Html = request_sync(post,{Url,[],[],"path="++NoCrashdump}),
    "Please wait..." = title(Html),
    Str = "An error occured:"++NoCrashdump++" is not an Erlang crash dump",
    Str = strip(wait(10,Port,"redirect")),
    ok = crashdump_viewer:stop(),
%    exit(whereis(httpc_manager),kill),
    ok.
    


end_per_suite(doc) ->
    ["Remove generated crashdumps"];
end_per_suite(Config) when is_list(Config) ->
    Dumps = ?config(dumps,Config),
    lists:foreach(fun(CD) -> ok = file:delete(CD) end,Dumps),
    lists:keydelete(dumps,1,Config).


%%%-----------------------------------------------------------------
%%% Internal
start_cdv() ->
    ?t:capture_start(),
    ok = crashdump_viewer:start(),
    "WebTool is available at http://localhost:" ++ Where = 
	lists:flatten(?t:capture_get()),
    ?t:capture_stop(),
    [Port|_] = string:tokens(Where,"/"),
    Port.


check_result(File,OutFile) ->
    {ok,#file_info{size=FS}} = file:read_file_info(File),
    {ok,#file_info{size=OFS}} = file:read_file_info(OutFile),
    Rel = 
	if OFS > 0 -> FS/OFS;
	   true -> 1.25
	end,
    if Rel>0.75, Rel<1.25 -> ok;
       true -> ?t:fail({unreasonable_size,File,FS,OFS})
    end,
    {ok,Fd} = file:open(OutFile,[read]),
    "=erl_crash_dump:0.0\n" = io:get_line(Fd,''),
    case is_truncated(File) of
	true ->
	    ok;
	false ->
	    {ok,_} = file:position(Fd,{eof,-5}),
	    case io:get_line(Fd,'') of
		"=end\n"  -> ok;
		Other -> ?t:fail({truncated,File,Other})
	    end
    end,
    ok = file:close(Fd).


%% Read a page and check that the page title matches Title
contents(Port,Link) ->
    Url = cdv_url(Port,Link),
    request_sync(get,{Url,[]}).

cdv_url(Port,Link) ->
    "http://localhost:" ++ Port ++ "/cdv_erl/crashdump_viewer/" ++ Link.

request_sync(Method,HTTPReqCont) ->
    case httpc:request(Method,
		      HTTPReqCont,
		      [{timeout,30000}],
		      [{full_result, false}]) of
	{ok,{200,Html}} ->
	    Html;
	{ok,{Code,Html}} ->
	    io:format("~s\n", [Html]),
	    io:format("Received ~w from httpc:request(...) with\nMethod=~w\n"
		      "HTTPReqCont=~p\n",
		      [Code,Method,HTTPReqCont]),
	    ?t:fail();
	Other ->
	    io:format(
	      "Received ~w from httpc:request(...) with\nMethod=~w\n"
	      "HTTPReqCont=~p\n",
	      [Other,Method,HTTPReqCont]),
	    ?t:fail()
    end.




strip([$<|Html]) ->
    strip(drop_tag(Html));
strip([$\n|Html]) -> 
    strip(Html);
strip([X|Html]) ->
    [X|strip(Html)];
strip([]) ->
    [].
drop_tag([$>|Html]) ->
    Html;
drop_tag([_|Html]) ->
    drop_tag(Html).

title(Port,Link,Title) ->
    Html = contents(Port,Link),
    Title = title(Html).

wait(0,_Port,Link) ->
    ?t:fail({wait,Link,timeout});
wait(Time,Port,Link) ->
    Html = contents(Port,Link),
    case title(Html) of
	"Please wait..." ->
	    timer:sleep(1000),
	    wait(Time-1,Port,Link);
	_Title ->
	    Html
    end.

title([$<,$T,$I,$T,$L,$E,$>|Html]) ->
    title_end(Html);
title([_|Html]) ->
    title(Html);
title([]) ->
    [].

title_end([$<,$/,$T,$I,$T,$L,$E,$>|_]) ->
    [];
title_end([X|Html]) ->
    [X|title_end(Html)].


%%%-----------------------------------------------------------------
%%% General check of what is displayed for a dump
browse_file(Port,File) ->
    io:format("Browsing file: ~s~n",[File]),

    %% The page where a filename can be entered
    title(Port,"read_file_frame","Read File"),
    
    %% Load a file
    Url = "http://localhost:"++Port++"/cdv_erl/crashdump_viewer/read_file",
    Html = request_sync(post,{Url,[],[],"path="++File}),
    "Please wait..." = title(Html),
    "Crashdump Viewer Start Page" = title(wait(10,Port,"start_page")),
    
    %% The frame with the initial information for a dump
    title(Port,"initial_info_frame","General Information"),
    
    %% Topmost frame of the page
    FilenameFrame = contents(Port,"filename_frame"),
    Match = "FilenameCrashdump currently viewed:" ++ File,
    true = lists:prefix(Match,strip(FilenameFrame)),
    
    %% Toggle a menu item and check that it explodes/collapses
    title(Port,"menu_frame","Menu"),
    exploded = toggle_menu(Port),
    collapsed = toggle_menu(Port),
    
    %% Open each page in menu and check that correct title is shown
    title(Port,"general_info","General Information"),
    title(Port,"processes","Process Information"),
    title(Port,"sort_procs?sort=state","Process Information"),
    title(Port,"sort_procs?sort=state","Process Information"),
    title(Port,"sort_procs?sort=pid","Process Information"),
    title(Port,"sort_procs?sort=pid","Process Information"),
    title(Port,"sort_procs?sort=msg_q_len","Process Information"),
    title(Port,"sort_procs?sort=msg_q_len","Process Information"),
    title(Port,"sort_procs?sort=reds","Process Information"),
    title(Port,"sort_procs?sort=reds","Process Information"),
    title(Port,"sort_procs?sort=mem","Process Information"),
    title(Port,"sort_procs?sort=mem","Process Information"),
    title(Port,"sort_procs?sort=name","Process Information"),
    title(Port,"sort_procs?sort=name","Process Information"),
    title(Port,"sort_procs?sort=init_func","Process Information"),
    title(Port,"sort_procs?sort=init_func","Process Information"),
    title(Port,"ports","Port Information"),
    title(Port,"ets_tables","ETS Table Information"),
    title(Port,"timers","Timer Information"),
    title(Port,"fun_table","Fun Information"),
    title(Port,"atoms","Atoms"),
    title(Port,"dist_info","Distribution Information"),
    title(Port,"loaded_modules","Loaded Modules Information"),
    title(Port,"hash_tables","Hash Table Information"),
    title(Port,"index_tables","Index Table Information"),
    title(Port,"memory","Memory Information"),
    title(Port,"allocated_areas","Information about allocated areas"),
    title(Port,"allocator_info","Allocator Information"),
    
    case is_truncated(File) of
	true ->
	    ok;
	_ ->
	    proc_details(Port),
	    port_details(Port),
	    title(Port,"loaded_mod_details?mod=kernel","kernel")
    end,
    
    ok.


special(Port,File) ->
    case filename:extension(File) of
	".full_dist" ->
	    contents(Port,"processes"),
	    AllProcs = contents(Port,"sort_procs?sort=name"),

	    %% I registered a process as aaaaaaaa in the full_dist dumps 
	    %% to make sure it will be the first in the list when sorted
	    %% on names. There are some special data here, s� I'll thoroughly
	    %% read the process details for this process. Other processes
	    %% are just briefly traversed.
	    {Pid,Rest1} = get_first_process(AllProcs),
	    
	    ProcDetails = contents(Port,"proc_details?pid=" ++ Pid),
	    ProcTitle = "Process " ++ Pid,
	    ProcTitle = title(ProcDetails),
	    title(Port,"ets_tables?pid="++Pid,"ETS Tables for Process "++Pid),
	    title(Port,"timers?pid="++Pid,"Timers for Process "++Pid),

	    case filename:basename(File) of
		"r10b_dump.full_dist" ->
		    [MsgQueueLink,DictLink,StackDumpLink] = 
			expand_memory_links(ProcDetails),
		    MsgQueue = contents(Port,MsgQueueLink),
		    "MsgQueue" = title(MsgQueue),
		    title(Port,DictLink,"Dictionary"),
		    title(Port,StackDumpLink,"StackDump"),
		    
		    ExpandBinaryLink = expand_binary_link(MsgQueue),
		    title(Port,ExpandBinaryLink,"Expanded binary"),
		    lookat_all_pids(Port,Rest1);
		_ ->
		    ok
	    end;
	%%! No longer needed - all atoms are shown on one page!!
	%% ".250atoms" ->
	%%     Html1 = contents(Port,"atoms"),
	%%     NextLink1 = next_link(Html1),
	%%     "Atoms" = title(Html1),
	%%     Html2 = contents(Port,NextLink1),
	%%     NextLink2 = next_link(Html2),
	%%     "Atoms" = title(Html2),
	%%     Html3 = contents(Port,NextLink2),
	%%     "" = next_link(Html3),
	%%     "Atoms" = title(Html3);
	_ ->
	    ok
    end,
    case filename:basename(File) of
	"r10b_dump." ++ _ ->
	    lookat_all_pids(Port,contents(Port,"processes"));
	"r11b_dump." ++ _ ->
	    lookat_all_pids(Port,contents(Port,"processes"));
	_ ->
	    ok
    end,
    ok.


lookat_all_pids(Port,Pids) ->
    case get_first_process(Pids) of
	{Pid,Rest} ->
	    ProcDetails = contents(Port,"proc_details?pid=" ++ Pid),
	    ProcTitle = "Process " ++ Pid,
	    ProcTitle = title(ProcDetails),
	    title(Port,"ets_tables?pid="++Pid,"ETS Tables for Process "++Pid),
	    title(Port,"timers?pid="++Pid,"Timers for Process "++Pid),
	    
	    MemoryLinks = expand_memory_links(ProcDetails),
	    lists:foreach(
	      fun(Link) ->
		      Cont = contents(Port,Link),
		      true = lists:member(title(Cont),
					  ["MsgQueue",
					   "Dictionary",
					   "StackDump"])
	      end,
	      MemoryLinks),
	    lookat_all_pids(Port,Rest);
	false ->
	    ok
    end.


get_first_process([]) ->
    false;
get_first_process(Html) ->
    case Html of
	"<TD><A HREF=\"./proc_details?pid=" ++ Rest ->
	    {string:sub_word(Rest,1,$"),Rest};
	[_H|T] ->
	    get_first_process(T)
    end.
	
expand_memory_links(Html) ->
    case Html of
	"<B>MsgQueue</B></TD><TD COLSPAN=3><A HREF=\"./" ++ Rest ->
	    [string:sub_word(Rest,1,$")|expand_memory_links(Rest)];
	"<B>Dictionary</B></TD><TD COLSPAN=3><A HREF=\"./" ++ Rest ->
	    [string:sub_word(Rest,1,$")|expand_memory_links(Rest)];
	"<B>StackDump</B></TD><TD COLSPAN=3><A HREF=\"./" ++ Rest ->
	    [string:sub_word(Rest,1,$")];
	[_H|T] ->
	    expand_memory_links(T);
	[] ->
	    []
    end.

expand_binary_link(Html) ->
    case Html of
	"<A HREF=\"./expand_binary?pos=" ++ Rest ->
	    "expand_binary?pos=" ++ string:sub_word(Rest,1,$");
	[_H|T] ->
	    expand_binary_link(T)
    end.


%% next_link(Html) ->
%%     case Html of
%% 	"<A HREF=\"./next?pos=" ++ Rest ->
%% 	    "next?pos=" ++ string:sub_word(Rest,1,$");
%% 	[_H|T] ->
%% 	    next_link(T);
%% 	[] ->
%% 	    []
%%     end.



toggle_menu(Port) ->
    Html = contents(Port,"toggle?index=4"),
    check_toggle(Html).

check_toggle(Html) ->
    case Html of
	"<A HREF=\"./toggle?index=4\"><IMG SRC=\"/crashdump_viewer/collapsd.gif\"" ++ _ ->
	    collapsed;
	"<A HREF=\"./toggle?index=4\"><IMG SRC=\"/crashdump_viewer/exploded.gif\"" ++ _ ->
	    exploded;
	[_H|T] ->
	    check_toggle(T)
    end.
	    

proc_details(Port) ->
    ProcDetails = contents(Port,"proc_details?pid=<0.0.0>"),
    "Process <0.0.0>" = title(ProcDetails),

    ExpandLink = expand_link(ProcDetails),
    title(Port,ExpandLink,"StackDump"),

    Unknown = contents(Port,"proc_details?pid=<0.9999.0>"),
    "Could not find process: <0.9999.0>" = title(Unknown).

expand_link(Html) ->
    case Html of
	"<B>StackDump</B></TD><TD COLSPAN=3><A HREF=\"./" ++ Rest ->
	    string:sub_word(Rest,1,$");
	[_H|T] ->
	    expand_link(T)
    end.


port_details(Port) ->
    Port1 = contents(Port,"port?port=Port<0.1>"),
    "#Port<0.1>" = title(Port1),
    
    Port0 = contents(Port,"port?port=Port<0.0>"),
    "Could not find port: #Port<0.0>" = title(Port0).

is_truncated(File) ->
    case filename:extension(filename:rootname(File)) of
	".trunc" -> true;
	_ -> false
    end.


%%%-----------------------------------------------------------------
%%% 
create_dumps(DataDir,Rels) ->
    create_dumps(DataDir,Rels,[]).
create_dumps(DataDir,[Rel|Rels],Acc) ->
    Fun = fun() -> do_create_dumps(DataDir,Rel) end,
    Pa = filename:dirname(code:which(?MODULE)),
    {SlAllocDumps,Dumps,DosDump} = 
	?t:run_on_shielded_node(Fun, compat_rel(Rel) ++ "-pa " ++ Pa),
    create_dumps(DataDir,Rels,SlAllocDumps ++ Dumps ++ Acc ++ DosDump);
create_dumps(_DataDir,[],Acc) ->
    Acc.

do_create_dumps(DataDir,Rel) ->
    SlAllocDumps = 
	case lists:member(Rel,?sl_alloc_vsns) of
	    true ->
		[dump_with_args(DataDir,Rel,"no_sl_alloc","+Se false"),
		 dump_with_args(DataDir,Rel,"sl_alloc_1","+Se true +Sr 1"),
		 dump_with_args(DataDir,Rel,"sl_alloc_2","+Se true +Sr 2")];
	    false ->
		[]
	end,
    CD1 = full_dist_dump(DataDir,Rel),
    CD2 = dump_with_args(DataDir,Rel,"port_is_unix_fd","-oldshell"),
    DosDump = 
	case os:type() of
	    {unix,sunos} -> dos_dump(DataDir,Rel,CD1);
	    _ -> []
	end,
    case Rel of
	current ->
	    CD3 = dump_with_args(DataDir,Rel,"instr","+Mim true"),
	    {SlAllocDumps, [CD1,CD2,CD3], DosDump};
	_ ->
	    {SlAllocDumps, [CD1,CD2], DosDump}
    end.


%% Create a dump which has two visible nodes, one hidden and one
%% not connected node, and with monitors and links between nodes.
full_dist_dump(DataDir,Rel) ->
    Opt = rel_opt(Rel),
    Pz = "-pz " ++ filename:dirname(code:which(?MODULE)),
    PzOpt = [{args,Pz}],
    {ok,N1} = ?t:start_node(n1,peer,Opt ++ PzOpt),
    {ok,N2} = ?t:start_node(n2,peer,Opt ++ PzOpt),
    {ok,N3} = ?t:start_node(n3,peer,Opt ++ PzOpt),
    {ok,N4} = ?t:start_node(n4,peer,Opt ++ [{args,"-hidden " ++ Pz}]),
    Creator = self(),

    HelperMod = crashdump_helper,
    
    P1 = rpc:call(N1,HelperMod,n1_proc,[N2,Creator]),
    P2 = rpc:call(N2,HelperMod,remote_proc,[P1,Creator]),
    P3 = rpc:call(N3,HelperMod,remote_proc,[P1,Creator]),
    P4 = rpc:call(N4,HelperMod,remote_proc,[P1,Creator]),
    
    get_response(P2),
    get_response(P3),
    get_response(P4),
    get_response(P1),

    L = lists:seq(0,255),
    BigMsg = {message,list_to_binary(L),L},    
    Port = hd(erlang:ports()),
    {aaaaaaaa,N1} ! {short,message,1,2.5,"hello world",Port,{}},
    {aaaaaaaa,N1} ! BigMsg,
    
    ?t:stop_node(N3),
    DumpName = "full_dist",
    CD = dump(N1,DataDir,Rel,DumpName),

    ?t:stop_node(N2),
    ?t:stop_node(N4),
    CD.

get_response(P) ->
    receive {P,done} -> ok
    after 3000 -> ?t:fail({get_response_timeout,P,node(P)})
    end.


dump_with_args(DataDir,Rel,DumpName,Args) ->
    RelOpt = rel_opt(Rel),
    Opt = RelOpt ++ [{args,Args}],
    {ok,N1} = ?t:start_node(n1,peer,Opt),
    CD = dump(N1,DataDir,Rel,DumpName),
    ?t:stop_node(n1),
    CD.



dump(Node,DataDir,Rel,DumpName) ->
    rpc:call(Node,erlang,halt,[DumpName]),
    Crashdump0 = filename:join(filename:dirname(code:which(?t)),
			       "erl_crash_dump.n1"),
    Crashdump1 = filename:join(DataDir, dump_prefix(Rel)++DumpName),
    ok = rename(Crashdump0,Crashdump1),
    Crashdump1.

rename(From,To) ->
    ok = check_complete(From),
    case file:rename(From,To) of
	{error,exdev} ->
	    {ok,_} = file:copy(From,To),
	    ok = file:delete(From);
	ok ->
	    ok
    end.

check_complete(File) ->
    check_complete1(File,10).

check_complete1(_File,0) ->
    {error,enoent};
check_complete1(File,N) ->
    case file:read_file_info(File) of
	{error,enoent} ->
	    timer:sleep(500),
	    check_complete1(File,N-1);
	{ok,#file_info{size=Size}} ->
	    check_complete2(File,Size)
    end.

check_complete2(File,Size) ->
    timer:sleep(500),
    case file:read_file_info(File) of
	{ok,#file_info{size=Size}} ->
	    ok;
	{ok,#file_info{size=OtherSize}} ->
	    check_complete2(File,OtherSize)
    end.

dos_dump(DataDir,Rel,Dump) ->
    DosDumpName = filename:join(DataDir,dump_prefix(Rel)++"dos"),
    Cmd = "unix2dos " ++ Dump ++ " > " ++ DosDumpName,
    Port = open_port({spawn,Cmd},[exit_status]),
    receive
	{Port,{exit_status,0}} -> 
	    [DosDumpName];
	{Port,{exit_status,_Error}} ->
	    ?t:comment("Couldn't run \'unix2dos\'"),
	    []
    end.

rel_opt(Rel) ->
    case Rel of
	r9b -> [{erl,[{release,"r9b_patched"}]}];
	r9c -> [{erl,[{release,"r9c_patched"}]}];
	r10b -> [{erl,[{release,"r10b_patched"}]}];
	r11b -> [{erl,[{release,"r11b_patched"}]}];
	r12b -> [{erl,[{release,"r12b_patched"}]}];
	r13b -> [{erl,[{release,"r13b_patched"}]}];
	current -> []
    end.

dump_prefix(Rel) ->
    case Rel of
	r9b -> "r9b_dump.";
	r9c -> "r9c_dump.";
	r10b -> "r10b_dump.";
	r11b -> "r11b_dump.";
	r12b -> "r12b_dump.";
	r13b -> "r13b_dump.";
	r14b -> "r14b_dump.";
	current -> "r15b_dump."
    end.

compat_rel(Rel) ->
    case Rel of
	r9b -> "+R9 ";
	r9c -> "+R9 ";
	r10b -> "+R10 ";
	r11b -> "+R11 ";
	r12b -> "+R12 ";
	r13b -> "+R13 ";
	r14b -> "+R13 ";
	current -> ""
    end.