diff options
Diffstat (limited to 'lib/tools/test/cover_SUITE.erl')
-rw-r--r-- | lib/tools/test/cover_SUITE.erl | 1198 |
1 files changed, 1198 insertions, 0 deletions
diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl new file mode 100644 index 0000000000..b9ccd62d0b --- /dev/null +++ b/lib/tools/test/cover_SUITE.erl @@ -0,0 +1,1198 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% +-module(cover_SUITE). + +-export([all/1]). +-export([start/1, compile/1, analyse/1, misc/1, stop/1, + distribution/1, export_import/1, + otp_5031/1, eif/1, otp_5305/1, otp_5418/1, otp_6115/1, otp_7095/1, + otp_8188/1, otp_8270/1, otp_8273/1, otp_8340/1]). + +-include("test_server.hrl"). + +%%---------------------------------------------------------------------- +%% The following directory structure is assumed: +%% cwd __________________________________________ +%% | \ \ \ \ \ \ \ +%% a b cc d f d1 compile_beam_____ otp_6115 +%% | \ \ \ \ \ \ \ +%% e crypt v w x d f1 f2 +%% | +%% y +%%---------------------------------------------------------------------- + +all(suite) -> + case whereis(cover_server) of + undefined -> + [start, compile, analyse, misc, stop, distribution, + export_import, + otp_5031, eif, otp_5305, otp_5418, otp_6115, otp_7095, + otp_8188, otp_8270, otp_8273, otp_8340]; + _pid -> + {skip,"It looks like the test server is running cover. " + "Can't run cover test."} + end. + +start(suite) -> []; +start(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(data_dir, Config)), + + ?line Files = lsfiles(), + ?line remove(files(Files, ".out")), + + ?line {ok, Pid} = cover:start(), + ?line {error, {already_started, Pid}} = cover:start(). + +compile(suite) -> []; +compile(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(data_dir, Config)), + + ?line Result1 = cover:compile_directory(), + ?line SortedResult = lists:sort(Result1), + ?line {ok, CWD} = file:get_cwd(), + ?line Result2 = cover:compile_directory(CWD), + ?line SortedResult = lists:sort(Result2), + ?line [{error,_DFile},{ok,a},{ok,b},{ok,cc},{ok,f}] = SortedResult, + ?line [{ok,e}] = cover:compile_directory("d1"), + ?line {error,enoent} = cover:compile_directory("d2"), + + ?line {ok,a} = cover:compile(a), + ?line {ok,b} = compile:file(b), + ?line code:purge(b), + ?line {module,b} = code:load_file(b), + ?line {ok,d} = cover:compile("d.erl", [{d,'AGE',42}]), + ?line {error,_BBFile} = cover:compile(bb), + + ?line StdlibDir = code:lib_dir(stdlib), + ?line Lists = filename:join([StdlibDir, "src", "lists.erl"]), + ?line {error, Lists} = cover:compile(Lists), + + %% For compiling beam: using dummy files v,w,x,y and z + ?line file:set_cwd("compile_beam"), + ?line {ok,_} = compile:file(v,[debug_info,report]), + ?line {ok,_} = compile:file(w,[debug_info,report]), + ?line {ok,_} = compile:file(x), + ?line {ok,_} = compile:file("d/y",[debug_info,{outdir,"d"},report]), + ?line Key = "A Krypto Key", + ?line {ok,_} = compile:file(crypt, [debug_info,{debug_info_key,Key},report]), + ?line {ok,v} = cover:compile_beam(v), + ?line {ok,w} = cover:compile_beam("w.beam"), + ?line {error,{encrypted_abstract_code,_}} = + cover:compile_beam("crypt.beam"), + ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun(Key)), + ?line {ok,crypt} = cover:compile_beam("crypt.beam"), + ?line {error,{no_abstract_code,"./x.beam"}} = cover:compile_beam(x), + ?line {error,{already_cover_compiled,no_beam_found,a}}=cover:compile_beam(a), + ?line {error,non_existing} = cover:compile_beam(z), + ?line [{ok,y}] = cover:compile_beam_directory("d"), + ?line Result3 = lists:sort(cover:compile_beam_directory()), + ?line [{error,{no_abstract_code,_XBeam}},{ok,crypt},{ok,v},{ok,w}] = Result3, + ?line {error,enoent} = cover:compile_beam_directory("d2"), + ?line decompile([v,w,y]), + ?line Files = lsfiles(), + ?line remove(files(Files, ".beam")). + +simple_crypto_fun(Key) -> + fun(init) -> ok; + ({debug_info, des3_cbc, crypt, _}) -> Key + end. + +analyse(suite) -> []; +analyse(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(data_dir, Config)), + + ?line done = a:start(5), + + ?line {ok, {a,{17,2}}} = cover:analyse(a, coverage, module), + ?line {ok, [{{a,start,1},{6,0}}, + {{a,stop,1},{0,1}}, + {{a,pong,1},{1,0}}, + {{a,loop,3},{5,1}}, + {{a,trycatch,1},{4,0}}, + {{a,exit_kalle,0},{1,0}}]} = cover:analyse(a, coverage, function), + ?line {ok, [{{a,start,1,1},{6,0}}, + {{a,stop,1,1},{0,1}}, + {{a,pong,1,1},{1,0}}, + {{a,loop,3,1},{3,1}}, + {{a,loop,3,2},{2,0}}, + {{a,trycatch,1,1},{4,0}}, + {{a,exit_kalle,0,1},{1,0}}]} = cover:analyse(a, coverage, clause), + ?line {ok, [{{a,9},{1,0}}, + {{a,10},{1,0}}, + {{a,11},{1,0}}, + {{a,13},{1,0}}, + {{a,14},{1,0}}, + {{a,15},{1,0}}, + {{a,21},{0,1}}, + {{a,26},{1,0}}, + {{a,31},{1,0}}, + {{a,32},{1,0}}, + {{a,34},{1,0}}, + {{a,36},{0,1}}, + {{a,39},{1,0}}, + {{a,40},{1,0}}, + {{a,44},{1,0}}, + {{a,47},{1,0}}, + {{a,49},{1,0}}, + {{a,51},{1,0}}, + {{a,55},{1,0}}]} = cover:analyse(a, coverage, line), + + ?line {ok, {a,15}} = cover:analyse(a, calls, module), + ?line {ok, [{{a,start,1},1}, + {{a,stop,1},0}, + {{a,pong,1},5}, + {{a,loop,3},6}, + {{a,trycatch,1},2}, + {{a,exit_kalle,0},1}]} = cover:analyse(a, calls, function), + ?line {ok, [{{a,start,1,1},1}, + {{a,stop,1,1},0}, + {{a,pong,1,1},5}, + {{a,loop,3,1},5}, + {{a,loop,3,2},1}, + {{a,trycatch,1,1},2}, + {{a,exit_kalle,0,1},1}]} = cover:analyse(a, calls, clause), + ?line {ok, [{{a,9},1}, + {{a,10},1}, + {{a,11},1}, + {{a,13},1}, + {{a,14},1}, + {{a,15},1}, + {{a,21},0}, + {{a,26},5}, + {{a,31},5}, + {{a,32},5}, + {{a,34},5}, + {{a,36},0}, + {{a,39},1}, + {{a,40},1}, + {{a,44},2}, + {{a,47},1}, + {{a,49},1}, + {{a,51},2}, + {{a,55},1}]} = cover:analyse(a, calls, line), + + ?line {ok, [{{a,start,1},{6,0}}, + {{a,stop,1},{0,1}}, + {{a,pong,1},{1,0}}, + {{a,loop,3},{5,1}}, + {{a,trycatch,1},{4,0}}, + {{a,exit_kalle,0},{1,0}}]} = cover:analyse(a), + ?line {ok, {a,{17,2}}} = cover:analyse(a, module), + ?line {ok, [{{a,start,1},1}, + {{a,stop,1},0}, + {{a,pong,1},5}, + {{a,loop,3},6}, + {{a,trycatch,1},2}, + {{a,exit_kalle,0},1}]} = cover:analyse(a, calls), + + ?line {ok, "a.COVER.out"} = cover:analyse_to_file(a), + ?line {ok, "e.COVER.out"} = cover:analyse_to_file(e), + ?line {ok, "a.COVER.html"} = cover:analyse_to_file(a,[html]), + ?line {ok, "e.COVER.html"} = cover:analyse_to_file(e,[html]), + + %% analyse_to_file of file which is compiled from beam + ?line {ok,f} = compile:file(f,[debug_info]), + ?line code:purge(f), + ?line {module,f} = code:load_file(f), + ?line {ok,f} = cover:compile_beam(f), + ?line f:f2(), + ?line {ok, "f.COVER.out"} = cover:analyse_to_file(f), + + %% Source code cannot be found by analyse_to_file + ?line {ok,v} = compile:file("compile_beam/v",[debug_info]), + ?line code:purge(v), + ?line {module,v} = code:load_file(v), + ?line {ok,v} = cover:compile_beam(v), + ?line {error,no_source_code_found} = cover:analyse_to_file(v), + + ?line {error,{not_cover_compiled,b}} = cover:analyse(b), + ?line {error,{not_cover_compiled,g}} = cover:analyse(g), + ?line {error,{not_cover_compiled,b}} = cover:analyse_to_file(b), + ?line {error,{not_cover_compiled,g}} = cover:analyse_to_file(g). + +misc(suite) -> []; +misc(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(data_dir, Config)), + + ?line [a,cc,crypt,d,e,f,v] = lists:sort(cover:modules()), + + ?line {ok,cc} = compile:file(cc), + ?line code:purge(cc), + ?line {module,cc} = code:load_file(cc), + ?line [a,crypt,d,e,f,v] = lists:sort(cover:modules()), + + ?line {file, _File} = cover:is_compiled(a), + ?line false = cover:is_compiled(b), + ?line false = cover:is_compiled(g), + + ?line ok = cover:reset(a), + ?line {ok, {a,{0,19}}} = cover:analyse(a, module), + ?line ok = cover:reset(). + +stop(suite) -> []; +stop(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(data_dir, Config)), + + ?line cover_compiled = code:which(a), + ?line {ok,d} = compile:file(d, [{d,'AGE',42}]), + ?line code:purge(d), + ?line {module,d} = code:load_file(d), + ?line ok = cover:stop(), + ?line Beam = code:which(a), + ?line true = is_unloaded(Beam), + + ?line Files = lsfiles(), + ?line remove(files(Files, ".out")), + ?line remove(files(Files, ".html")), + ?line remove(files(Files, ".beam")). + +distribution(suite) -> []; +distribution(Config) when is_list(Config) -> + ?line DataDir = ?config(data_dir, Config), + ?line ok = file:set_cwd(DataDir), + + ?line {ok,N1} = ?t:start_node(cover_SUITE_distribution1,slave,[]), + ?line {ok,N2} = ?t:start_node(cover_SUITE_distribution2,slave,[]), + ?line {ok,N3} = ?t:start_node(cover_SUITE_distribution3,slave,[]), + + %% Check that an already compiled module is loaded on new nodes + ?line {ok,f} = cover:compile(f), + ?line {ok,[_,_,_]} = cover:start(nodes()), + ?line cover_compiled = code:which(f), + ?line cover_compiled = rpc:call(N1,code,which,[f]), + ?line cover_compiled = rpc:call(N2,code,which,[f]), + ?line cover_compiled = rpc:call(N3,code,which,[f]), + + %% Check that a node cannot be started twice + ?line {ok,[]} = cover:start(N2), + + %% Check that the current node (i.e. the main node) is not started with + %% start/1 and not stopped with stop/1 + ?line {ok,[]} = cover:start(node()), + ?line ok = cover:stop(node()), + ?line true = is_pid(whereis(cover_server)), + + %% Check that a new compiled module is loaded on all existing nodes + ?line compile:file("compile_beam/v",[debug_info]), + ?line {ok,v} = cover:compile_beam(v), + ?line cover_compiled = code:which(v), + ?line cover_compiled = rpc:call(N1,code,which,[v]), + ?line cover_compiled = rpc:call(N2,code,which,[v]), + ?line cover_compiled = rpc:call(N3,code,which,[v]), + + %% this is lost when the node is killed + ?line rpc:call(N3,f,f2,[]), + ?line rpc:call(N3,erlang,halt,[]), + + %% this should be visible in analyse + ?line rpc:call(N1,f,f1,[]), + + %% Check that data is collected from remote node when stopped + ?line ok = cover:stop(N1), + ?line N1Beam = rpc:call(N1,code,which,[f]), + ?line true = is_unloaded(N1Beam), + ?line check_f_calls(1,0), + + %% Call f:f1() again on another node and check that number of calls is + %% accumulated. + ?line f:f1(), + ?line check_f_calls(2,0), + + %% Check that reset works on all nodes + ?line f:f1(), + ?line rpc:call(N2,f,f1,[]), + ?line ok = cover:reset(f), + ?line check_f_calls(0,0), + + %% Check that data is collected from all nodes + ?line rpc:call(N2,f,f1,[]), + ?line f:f2(), + ?line check_f_calls(1,1), + + %% Check that same data is not fetched again (i.e. that analyse does + %% reset on the remote node(s)) + ?line check_f_calls(1,1), + + %% Check that stop() unloads on all nodes + ?line ok = cover:stop(), + ?line LocalBeam = code:which(f), + ?line N2Beam = rpc:call(N2,code,which,[f]), + ?line true = is_unloaded(LocalBeam), + ?line true = is_unloaded(N2Beam), + + %% Check that cover_server on remote node dies if main node dies + ?line {ok,[N1]} = cover:start(N1), + ?line true = is_pid(rpc:call(N1,erlang,whereis,[cover_server])), + ?line exit(whereis(cover_server),kill), + ?line timer:sleep(10), + ?line undefined = rpc:call(N1,erlang,whereis,[cover_server]), + + %% Cleanup + ?line Files = lsfiles(), + ?line remove(files(Files, ".beam")), + ?line ?t:stop_node(N1), + ?line ?t:stop_node(N2). + + +export_import(suite) -> []; +export_import(Config) when is_list(Config) -> + ?line DataDir = ?config(data_dir, Config), + ?line ok = file:set_cwd(DataDir), + + %% Export one module + ?line {ok,f} = cover:compile(f), + ?line f:f1(), + %% check that no info is written about where data comes from when no + %% files are imported + ?line ?t:capture_start(), + ?line check_f_calls(1,0), + ?line [] = ?t:capture_get(), + ?line ?t:capture_stop(), + ?line ok = cover:export("f_exported",f), + ?line check_f_calls(1,0), + ?line ok = cover:stop(), + + %% Check that same data exists after import and that info is written about + %% data comming from imported file + ?line ok = cover:import("f_exported"), + ?line ?t:capture_start(), + ?line check_f_calls(1,0), + ?line [Text1] = ?t:capture_get(), + ?line "Analysis includes data from imported files"++_ = lists:flatten(Text1), + ?line ?t:capture_stop(), + + %% Export all modules + ?line {ok,a} = cover:compile(a), + ?line ?t:capture_start(), + ?line ok = cover:export("all_exported"), + ?line [Text2] = ?t:capture_get(), + ?line "Export includes data from imported files"++_ = lists:flatten(Text2), + ?line ?t:capture_stop(), + ?line ok = cover:stop(), + ?line ok = cover:import("all_exported"), + ?line check_f_calls(1,0), + + %% Check that data is reset when module is compiled again, and that + %% warning is written when data is deleted for imported module. + ?line ?t:capture_start(), + ?line {ok,f} = cover:compile(f), + ?line timer:sleep(10), % capture needs some time + ?line [Text3] = ?t:capture_get(), + ?line "WARNING: Deleting data for module f imported from" ++ _ = + lists:flatten(Text3), + ?line ?t:capture_stop(), + ?line check_f_calls(0,0), + + %% Check that data is summed up when first compiled and then imported + %% The module which has been compiled (f) is loaded from the file + %% all_exported again (since it has been reset during cover compiling), + %% but the other module (a) is not loaded since it is already loaded + ?line f:f1(), + ?line f:f2(), + ?line ok = cover:import("f_exported"), + ?line ?t:capture_start(), + ?line ok = cover:import("all_exported"), + ?line [Text4] = ?t:capture_get(), % a is not loaded again + ?line "WARNING: Module a already imported from " ++ _ = lists:flatten(Text4), + ?line ?t:capture_stop(), + ?line check_f_calls(3,1), + + %% Check that warning is written when same file is imported twice, + %% and that data is not imported again + ?line ?t:capture_start(), + ?line ok = cover:import("all_exported"), + ?line [Text5,Text6] = ?t:capture_get(), + ?line "WARNING: Module f already imported from " ++ _ = lists:flatten(Text5), + ?line "WARNING: Module a already imported from " ++ _ = lists:flatten(Text6), + ?line ?t:capture_stop(), + ?line check_f_calls(3,1), + + %% Check that reset removes all data and that the file which has been + %% reset can be imported again with no warning + ?line cover:reset(f), + ?line check_f_calls(0,0), + ?line ?t:capture_start(), + ?line ok = cover:import("all_exported"), + ?line [Text7] = ?t:capture_get(), % warning only on mod a + ?line "WARNING: Module a already imported from " ++ _ = lists:flatten(Text7), + ?line ?t:capture_stop(), + ?line check_f_calls(1,0), + + %% same as above - only reset all + ?line cover:reset(), + ?line check_f_calls(0,0), + ?line ?t:capture_start(), + ?line ok = cover:import("all_exported"), + ?line [] = ?t:capture_get(), % no warnings + ?line ?t:capture_stop(), + ?line check_f_calls(1,0), + + %% Cleanup + ?line ok = cover:stop(), + ?line Files = lsfiles(), + ?line remove(["f_exported","all_exported"|files(Files, ".beam")]). + + +otp_5031(suite) -> []; +otp_5031(Config) when is_list(Config) -> + + Dog = ?t:timetrap(?t:seconds(10)), + + ?line {ok,N1} = ?t:start_node(cover_SUITE_distribution1,slave,[]), + ?line {ok,[N1]} = cover:start(N1), + ?line {error,not_main_node} = rpc:call(N1,cover,modules,[]), + ?line cover:stop(), + + ?t:timetrap_cancel(Dog), + ok. + +eif(doc) -> + ["Test the \'Exclude Included Functions\' functionality"]; +eif(suite) -> + []; +eif(Config) when is_list(Config) -> + ?line ok = file:set_cwd(filename:join(?config(data_dir, Config), + "included_functions")), + ?line {ok, cover_inc} = compile:file(cover_inc,[debug_info]), + ?line {ok, cover_inc} = cover:compile_beam(cover_inc), + + %% This function will cause an included function to be executed. + %% The analysis should only show the lines that actually exist + %% in cover_inc.beam - not the ones from the included file. + ?line cover_inc:func(), + ?line {ok, [_, _]} = cover:analyse(cover_inc, line), + ?line cover:stop(), + ok. + +otp_5305(suite) -> []; +otp_5305(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(priv_dir, Config)), + + File = "t.erl", + Test = <<"-module(t). + -export([t/0]). + -include_lib(\"stdlib/include/ms_transform.hrl\"). + t() -> + ets:fun2ms(fun(X) -> X end). + ">>, + ?line ok = file:write_file(File, Test), + ?line {ok, t} = cover:compile(File), + ?line cover:stop(), + ?line ok = file:delete(File), + + ok. + +otp_5418(suite) -> []; +otp_5418(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(priv_dir, Config)), + + File = "t.erl", + Test = <<"-module(t). + ">>, + ?line ok = file:write_file(File, Test), + ?line {ok, t} = cover:compile(File), + ?line {ok,{t,{0,0}}} = cover:analyse(t, module), + ?line cover:stop(), + ?line ok = file:delete(File), + + ok. + +otp_6115(suite) -> []; +otp_6115(Config) when is_list(Config) -> + case erlang:system_info(heap_type) of + hybrid -> {skip,"Hybrid-heap emulator doesn't keep track of funs"}; + _ -> otp_6115_1(Config) + end. + +otp_6115_1(Config) -> + ?line {ok, CWD} = file:get_cwd(), + ?line Dir = filename:join(?config(data_dir, Config), otp_6115), + ?line ok = file:set_cwd(Dir), + ?line {ok, f1} = compile:file(f1, [debug_info]), + ?line {ok, f2} = compile:file(f2, [debug_info]), + + %% Cover compile f1, but not f2 + ?line {ok, f1} = cover:compile(f1), + + %% If f1 is cover compiled, a process P is started with a + %% reference to the fun created in start_fail/0, and cover:stop() is + %% called, then P should be killed. + %% This is because (the fun held by P) references the cover + %% compiled code which should be *unloaded* when cover:stop() is + %% called -- running cover compiled code when there is no cover + %% server and thus no ets tables to bump counters in, makes no + %% sense. + ?line Pid1 = f1:start_fail(), + + %% If f1 is cover compiled, a process P is started with a + %% reference to the fun created in start_ok/0, and + %% cover:stop() is called, then P should survive. + %% This is because (the fun held by) P always references the current + %% version of the module, and is thus not affected by the cover + %% compiled version being unloaded. + ?line Pid2 = f1:start_ok(), + + %% Now stop cover + ?line cover:stop(), + + %% Ensure that f1 is loaded (and not cover compiled), that Pid1 + %% is dead and Pid2 is alive, but with no reference to old code + case code:which(f1) of + Beam when is_list(Beam) -> + ok; + Other -> + ?line ?t:fail({"f1 is not reloaded", Other}) + end, + case process_info(Pid1) of + undefined -> + ok; + _PI1 -> + RefToOldP = erlang:check_process_code(Pid1, f1), + ?line ?t:fail({"Pid1 still alive", RefToOldP}) + end, + case process_info(Pid2) of + PI2 when is_list(PI2) -> + case erlang:check_process_code(Pid2, f2) of + false -> + ok; + true -> + ?line ?t:fail("Pid2 has ref to old code") + end; + undefined -> + ?line ?t:fail("Pid2 has died") + end, + + ?line file:set_cwd(CWD), + ok. + +otp_7095(doc) -> + ["andalso/orelse"]; +otp_7095(suite) -> []; +otp_7095(Config) when is_list(Config) -> + ?line ok = file:set_cwd(?config(priv_dir, Config)), + + File = "t.erl", + Test = <<"-module(t). + -export([t/0]). + t() -> + t1(), + t2(), + t3(), + t4(), + t5(), + put(t6, 0), + 0 = t6(), + 1 = erase(t6), + t7(), + put(t8, 0), + {'EXIT',{{badarg,0},_}} = (catch t8()), + 1 = erase(t8), + t9(), + ok. + + t1() -> + false % 20 + andalso + true. % 22 + + t2() -> + true % 25 + andalso + true. % 27 + + t3() -> + false % 30 + orelse + true. % 32 + + t4() -> + true % 35 + orelse + true. % 37 + + t5() -> + true % 40 + andalso + true % 42 + andalso + false. % 44 + + t6() -> + true andalso % 47 + add_one(t6). % 48 + + t7() -> + true % 51 + andalso + false % 53 + andalso + not_ok. % 55 + + t8() -> + true % 58 + andalso + true % 60 + andalso + add_one(t8) % 62 + andalso + false. % 64 + + t9() -> + if % 67 + true -> + true % 69 + andalso + false % 71 + end + orelse + case ok of % 74 + true -> + a; % 76 + _ -> + true % 78 + end. + + add_one(T) -> + put(T, get(T) + 1). % 82 + ">>, + ?line ok = file:write_file(File, Test), + ?line {ok, t} = cover:compile(File), + ?line ok = t:t(), + ?line {ok,[{{t,4},1},{{t,5},1},{{t,6},1},{{t,7},1},{{t,8},1},{{t,9},1}, + {{t,10},1},{{t,11},1},{{t,12},1},{{t,13},1},{{t,14},1}, + {{t,15},1},{{t,16},1},{{t,17},1}, + {{t,20},1},{{t,22},0}, + {{t,25},1},{{t,27},1}, + {{t,30},1},{{t,32},1}, + {{t,35},1},{{t,37},0}, + {{t,40},1},{{t,42},1},{{t,44},1}, + {{t,47},1},{{t,48},1}, + {{t,51},1},{{t,53},1},{{t,55},0}, + {{t,58},1},{{t,60},1},{{t,62},1},{{t,64},0}, + {{t,67},1},{{t,69},1},{{t,71},1},{{t,74},1}, + {{t,76},0},{{t,78},1}, + {{t,82},2}]} = cover:analyse(t, calls, line), + ?line cover:stop(), + ?line ok = file:delete(File), + + ok. + +otp_8270(doc) -> + ["OTP-8270. Bug."]; +otp_8270(suite) -> []; +otp_8270(Config) when is_list(Config) -> + ?line DataDir = ?config(data_dir, Config), + ?line ok = file:set_cwd(DataDir), + + ?line PrivDir = ?config(priv_dir, Config), + + As = [{args," -pa " ++ PrivDir}], + ?line {ok,N1} = ?t:start_node(cover_n1,slave,As), + ?line {ok,N2} = ?t:start_node(cover_n2,slave,As), + ?line {ok,N3} = ?t:start_node(cover_n3,slave,As), + + timer:sleep(500), + cover:start(nodes()), + + Test = << + "-module(m).\n" + "-compile(export_all).\n" + "t() -> t(0).\n" + "l() ->\n" + " catch ets:tab2list(cover_internal_data_table).\n" + "t(Sz) ->\n" + " case ets:info(cover_internal_data_table, size) of\n" + " Sz ->\n" + " m:t(Sz); % Not a local call! Newly loaded code is entered.\n" + " NSz ->\n" + " % error_logger:info_msg(\"~p: ~p ~p change~n L1 ~p~n\", \n" + " % [node(), Sz, NSz, l()]),\n" + " m:t(NSz)\n" + " end.\n">>, + ?line _File = c_mod(m, Test, Config), + Fun = fun m:t/0, + ?line Pid1 = spawn(Fun), + ?line Pid2 = spawn(N1, Fun), + ?line Pid3 = spawn(N2, Fun), + ?line Pid4 = spawn(N3, Fun), + + ?line {ok, m} = cover:compile_beam(m), + + timer:sleep(1000), + + ?line Info = erlang:process_info(Pid1), + ?line N1_info = rpc:call(N1, erlang, process_info, [Pid2]), + ?line N2_info = rpc:call(N2, erlang, process_info, [Pid3]), + ?line N3_info = rpc:call(N3, erlang, process_info, [Pid4]), + + ?line true = is_list(Info), + ?line {N1,true} = {N1,is_list(N1_info)}, + ?line {N2,true} = {N2,is_list(N2_info)}, + ?line {N3,true} = {N3,is_list(N3_info)}, + + ?line ?t:stop_node(N1), + ?line ?t:stop_node(N2), + ?line ?t:stop_node(N3), + ok. + +otp_8273(doc) -> + ["OTP-8270. Bug."]; +otp_8273(suite) -> []; +otp_8273(Config) when is_list(Config) -> + Test = <<"-module(t). + -export([t/0]). + t() -> + foo = true andalso foo, + bar = false orelse bar, + ok. + ">>, + ?line File = cc_mod(t, Test, Config), + ?line ok = t:t(), + ?line cover:stop(), + ?line ok = file:delete(File), + + ok. + +otp_8340(doc) -> + ["OTP-8340. Bug."]; +otp_8340(suite) -> []; +otp_8340(Config) when is_list(Config) -> + ?line [{{t,1},1},{{t,2},1},{{t,4},1}] = + analyse_expr(<<"<< \n" + " <<3:2, \n" + " SeqId:62>> \n" + " || SeqId <- [64] >>">>, Config), + + ok. + +otp_8188(doc) -> + ["Clauses on the same line."]; +otp_8188(suite) -> []; +otp_8188(Config) when is_list(Config) -> + %% This example covers the bug report: + Test = <<"-module(t). + -export([test/1]). + + -define(FOOBAR(X), + case X of + ok -> true; + _ -> false + end). + + test(X)-> + _Res = + ?FOOBAR(X). + ">>, + ?line File = cc_mod(t, Test, Config), + ?line false = t:test(nok), + ?line {ok,[{{t,11},1},{{t,12},1}]} = cover:analyse(t, calls, line), + ?line cover:stop(), + ?line ok = file:delete(File), + + %% Bit string comprehensions are now traversed; + %% the handling of list comprehensions has been improved: + comprehension_8188(Config), + + %% Variants of the reported bug: + bug_8188(Config), + ok. + +bug_8188(Cf) -> + ?line [{{t,1},1},{{t,2},1},{{t,3},1}] = + analyse_expr(<<"A = 3,\n" % 1 + " case A of\n" % 1 + " 2 -> two; 3 -> three end, A + 2">>, % 1 + Cf), + + ?line [{{t,1},1}, + {{t,2},0}, + {{t,3},1}, + {{t,4},1}, + {{t,5},1}, + {{t,6},0}, + {{t,7},1}, + {{t,9},2}] = + analyse_expr(<<"case two() of\n" % 1 + " 1 -> 2;\n" % 0 + " _ -> begin 3 end\n" % 1 + " +\n" % 1 + " begin 4 end end, case two() of\n" % 1 + " 1 -> a;\n" % 0 + " 2 -> b; 3 -> c\n" % 1 + " end.\n" + "two() -> 2">>, Cf), % 2 + + ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, + {{t,4},1}, {{t,5},1}, {{t,6},0}] = + analyse_expr(<<" self() ! 1,\n" + " receive \n" + " X=1 -> a;\n" + " X=2 -> b end, case X of \n" + " 1 -> a;\n" + " 2 -> b\n" + " end">>, Cf), + + T0 = <<"t1(X) ->\n " + "case X of\n" + " 1 -> A=a,B=A,B; % bump Li\n" + " 2 -> b; 3 -> case X of % 2 -> b shall bump Li\n" + " 3 -> a; % bump Li\n" + " 2 -> b end; 4 -> d end, case X of % Li\n" + " 1 -> a;\n" + " 2 -> b; 3 -> c;\n" + " 4 -> d\n" + " end">>, + + T1 = [<<"a = t1(1). ">>,T0], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, {{t,4},0}, + {{t,5},0}, {{t,6},1}, {{t,7},1}, {{t,8},0}, {{t,9},0}] = + analyse_expr(T1, Cf), + + T2 = [<<"b = t1(2). ">>,T0], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1}, + {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}, {{t,9},0}] = + analyse_expr(T2, Cf), + + T3 = [<<"c = t1(3). ">>,T0], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1}, + {{t,5},1}, {{t,6},1}, {{t,7},0}, {{t,8},1}, {{t,9},0}] = + analyse_expr(T3, Cf), + + T4 = [<<"d = t1(4). ">>,T0], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},0}, + {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},0}, {{t,9},1}] = + analyse_expr(T4, Cf), + + ?line [{{t,1},1},{{t,2},1},{{t,3},1},{{t,4},1},{{t,5},1}] = + analyse_expr( + <<"2 = x3(1). " + "x3(X) ->\n" + " case X of \n" + " 1 -> case X of\n" + " 1 -> a, Y = 2;\n" + " 2 -> b, Y = 3 end, Y; 2 -> Y = 4 end, Y">>, Cf), + + ?line [{{t,1},1},{{t,2},1},{{t,3},1},{{t,4},1}] = + analyse_expr( + <<"1 = x4(1). " + "x4(X) ->\n" + " case X of\n" + " 1 -> case X of\n" + " 1 -> Y = 1 end, case X of 1 -> Y = 1 end, Y end">>, + Cf), + + T10 = <<"t1(X) ->\n" + "if\n" + " X =:= 1 -> a;\n" + " X =:= 2 -> b; X =:= 3 -> c end, case X of \n" + " 1 -> a;\n" + " 2 -> b; 3 -> c end, case X of\n" + " 1 -> a;\n" + " 2 -> b; 3 -> c\n" + " end">>, + T11 = [<<"a = t1(1). ">>,T10], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, {{t,4},1}, + {{t,5},1}, {{t,6},1}, {{t,7},1}, {{t,8},0}] = + analyse_expr(T11, Cf), + + T12 = [<<"b = t1(2). ">>,T10], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1}, + {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] = + analyse_expr(T12, Cf), + + T13 = [<<"c = t1(3). ">>,T10], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1}, + {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] = + analyse_expr(T13, Cf), + + T20 = <<"t1(X) ->\n" + "case X of\n" + " 1 -> a;\n" + " 2 -> b; 3 -> case X of\n" + " 1 -> a;\n" + " 2 -> b; 3 -> c end end, case X of\n" + " 1 -> a;\n" + " 2 -> b; 3 -> c\n" + " end">>, + + T21 = [<<"a = t1(1). ">>,T20], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, {{t,4},0}, + {{t,5},0}, {{t,6},1}, {{t,7},1}, {{t,8},0}] = + analyse_expr(T21, Cf), + + T22 = [<<"b = t1(2). ">>,T20], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1}, + {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] = + analyse_expr(T22, Cf), + + T23 = [<<"c = t1(3). ">>,T20], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, {{t,4},1}, + {{t,5},0}, {{t,6},1}, {{t,7},0}, {{t,8},1}] = + analyse_expr(T23, Cf), + + T30 = << + "t1(X) ->\n" + "case X of\n" + " 1 -> a;\n" + " 2 -> b; 3 -> case X of 1 -> a; 2 -> b; 3 -> c end end, case X of\n" + " 1 -> a;\n" + " 2 -> b; 3 -> c\n" + " end\n">>, + + T31 = [<<"a = t1(1). ">>,T30], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},1}, + {{t,4},1}, {{t,5},1}, {{t,6},0}] = + analyse_expr(T31, Cf), + + T32 = [<<"b = t1(2). ">>,T30], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, + {{t,4},1}, {{t,5},0}, {{t,6},1}] = + analyse_expr(T32, Cf), + + T33 = [<<"c = t1(3). ">>,T30], + ?line [{{t,1},1}, {{t,2},1}, {{t,3},0}, + {{t,4},1}, {{t,5},0}, {{t,6},1}] = + analyse_expr(T33, Cf), + + %% 'try' now traverses the body as a body... + ?line [{{t,1},1},{{t,2},1},{{t,3},1},{{t,4},0},{{t,6},1}] = + analyse_expr(<<"try \n" + " B = 2, \n" + " C = erlang:error(foo), \n" + " {B,C} \n" + "catch _:_ -> \n" + " foo \n" + "end">>, Cf), + + %% receive after: + ?line [{{t,1},1},{{t,2},0},{{t,3},1}] = + analyse_expr(<<"receive \n" + " X=1 -> a; \n" + " X=2 -> b after begin 10 end -> X=3 end">>, Cf), + ?line [{{t,1},1},{{t,2},0},{{t,3},1}] = + analyse_expr(<<"receive \n" + " X=1 -> a; \n" + " X=2 -> b after 10 -> begin X=3 end end">>, Cf), + ok. + +comprehension_8188(Cf) -> + ?line [{{t,1},1}] = + analyse_expr(<<"[begin X end || X <- [1,2,3], X > 1]">>, Cf), + ?line [{{t,1},1},{{t,2},1}] = + analyse_expr(<<"[begin X end || \n" + " X <- [1,2,3], X > 1]">>, Cf), + ?line [{{t,1},1},{{t,2},1},{{t,3},3}] = + analyse_expr(<<"[begin X end || \n " + " X <- [1,2,3], \n " + " X > 1]">>, Cf), + ?line [{{t,1},1},{{t,3},1},{{t,4},3}] = + analyse_expr(<<"[begin X end || \n " + " X <- \n " + " [1,2,3], \n " + " X > 1]">>, Cf), + ?line [{{t,1},1},{{t,2},2}] = + analyse_expr(<<"[ \n " + " X || X <- [1,2,3], X > 1]">>, Cf), + ?line [{{t,1},1},{{t,2},2},{{t,3},3}] = + analyse_expr(<<"[ \n" + " X || X <- [1,2,3], \n" + " X > 1]">>, Cf), + ?line [{{t,1},1},{{t,2},1},{{t,3},2}] = + analyse_expr(<<"[ \n " + " X || X <- [1,2,3], X > 1, \n" + " X > 2]">>, Cf), + + ?line [{{t,1},1}, + {{t,3},2}, + {{t,5},1}, + {{t,7},1}, + {{t,8},0}, + {{t,12},3}, + {{t,15},2}, + {{t,17},2}, + {{t,18},1}] = + analyse_expr(<<"[ \n" % 1 + " begin\n" + " X * 2\n" % 2 + " end ||\n" + " X <- [1,\n" % 1 + " case two() of\n" + " 2 -> 2;\n" % 1 + " _ -> two\n" % 0 + " end,\n" + " 3],\n" + " begin\n" + " math:sqrt(X) > 1.0\n" % 3 + " end,\n" + " begin\n" + " true\n" % 2 + " end,\n" + " true]. \n" % 2 + " two() -> 2">>, Cf), % 1 + + ?line [{{t,1},1}, + {{t,2},2}, + {{t,3},1}, + {{t,5},1}, + {{t,6},0}, + {{t,9},3}, + {{t,10},2}, + {{t,11},2}, + {{t,12},1}] = + analyse_expr(<<"[ \n" + " X * 2 || \n" % 2 + " X <- [1,\n" % 1 + " case two() of\n" + " 2 -> 2;\n" % 1 + " _ -> two\n" % 0 + " end,\n" + " 3],\n" + " math:sqrt(X) > 1.0,\n" % 3 + " true,\n" % 2 + " true]. \n" % 2 + " two() -> 2">>, Cf), % 1 + + ?line [{{t,1},1}, + {{t,2},2}, + {{t,3},1}, + {{t,4},1}, + {{t,5},0}, + {{t,8},1}, + {{t,9},0}, + {{t,12},3}, + {{t,13},2}, + {{t,14},2}] = + analyse_expr(<<"<< \n" % 1 + " << (X*2) >> || \n" % 2 + " <<X>> <= << (case two() of\n" + " 2 -> 1;\n" % 1 + " _ -> 2\n" % 0 + " end)/integer,\n" + " (case two() of \n" + " 2 -> 2;\n" % 1 + " _ -> two\n" % 0 + " end)/integer,\n" + " 3 >>, \n" + " math:sqrt(X) > 1.0,\n" % 3 + " true >>.\n" % 2 + "two() -> 2">>, Cf), + + ?line [{{t,1},1}, + {{t,2},4}, + {{t,4},1}, + {{t,6},1}, + {{t,7},0}, + {{t,10},3}, + {{t,11},2}, + {{t,12},4}, + {{t,13},1}] = + analyse_expr(<<"<< \n" % 1 + " << (2)\n" % 4 + " :(8) >> || \n" + " <<X>> <= << 1,\n" % 1 + " (case two() of \n" + " 2 -> 2;\n" % 1 + " _ -> two\n" % 0 + " end)/integer,\n" + " 3 >>, \n" + " math:sqrt(X) > 1.0,\n" % 3 + " <<_>> <= << 1, 2 >>,\n" % 2 + " true >>.\n" % 4 + "two() -> 2">>, Cf), % 1 + + ok. + +%%--Auxiliary------------------------------------------------------------ + +analyse_expr(Expr, Config) -> + Binary = [<<"-module(t). " + "-export([t/0]). " + "t() -> ">>, Expr, <<".\n">>], + File = cc_mod(t, Binary, Config), + t:t(), + {ok, Result} = cover:analyse(t, calls, line), + ok = file:delete(File), + Result. + +cc_mod(M, Binary, Config) -> + {ok, Dir} = file:get_cwd(), + PrivDir = ?config(priv_dir, Config), + ok = file:set_cwd(PrivDir), + File = atom_to_list(M) ++ ".erl", + try + ok = file:write_file(File, Binary), + {ok, M} = cover:compile(File), + filename:join(PrivDir, File) + after file:set_cwd(Dir) + end. + +c_mod(M, Binary, Config) -> + {ok, Dir} = file:get_cwd(), + PrivDir = ?config(priv_dir, Config), + ok = file:set_cwd(PrivDir), + File = atom_to_list(M) ++ ".erl", + try + ok = file:write_file(File, Binary), + {ok, M} = compile:file(File, [debug_info]), + code:purge(M), + AbsFile = filename:rootname(File, ".erl"), + code:load_abs(AbsFile, M), + filename:join(PrivDir, File) + after file:set_cwd(Dir) + end. + +lsfiles() -> + {ok, CWD} = file:get_cwd(), + lsfiles(CWD). + +lsfiles(Dir) -> + {ok, Files} = file:list_dir(Dir), + Files. + +files(Files, Ext) -> + lists:filter(fun(File) -> + case filename:extension(File) of + Ext -> true; + _ -> false + end + end, + Files). + +remove([File|Files]) -> + ok = file:delete(File), + remove(Files); +remove([]) -> + ok. + +decompile([Mod|Mods]) -> + code:purge(Mod), + code:delete(Mod), + decompile(Mods); +decompile([]) -> + ok. + +is_unloaded(What) -> + if + is_list(What) -> true; + What==non_existing -> true; + true -> false + end. + +check_f_calls(F1,F2) -> + {ok,[{{f,f1,0},F1},{{f,f2,0},F2}]} = cover:analyse(f,calls,function). |