summaryrefslogtreecommitdiff
path: root/test/rebar_xref_SUITE.erl
diff options
context:
space:
mode:
authorKelly McLaughlin <kelly@kelly-mclaughlin.com>2015-02-04 10:05:19 -0700
committerKelly McLaughlin <kelly@kelly-mclaughlin.com>2015-02-05 17:01:19 -0700
commit2abb55170ea5408496c79b15a1c714263c66c0ed (patch)
treeafc420c1c783757ab82bafc361cbc6a295f1416a /test/rebar_xref_SUITE.erl
parentd2cfe6a073d4d5d3092143c3925dacaab76ed472 (diff)
Add xref provider for cross reference analysis
* Add a provider for the xref tool for running cross reference analysis on a project. Most of the code has been ported directly from the rebar2 rebar_xref module with some modification and cleanup to support testing. * Port over the eunit test suite from rebar2, but convert it to common_test. The testing is the same, but now the erlang term output is examined to determine if the test run is successful instead of scanning the console output for the expected strings.
Diffstat (limited to 'test/rebar_xref_SUITE.erl')
-rw-r--r--test/rebar_xref_SUITE.erl190
1 files changed, 190 insertions, 0 deletions
diff --git a/test/rebar_xref_SUITE.erl b/test/rebar_xref_SUITE.erl
new file mode 100644
index 0000000..fde8c8f
--- /dev/null
+++ b/test/rebar_xref_SUITE.erl
@@ -0,0 +1,190 @@
+%% -*- erlang-indent-level: 4;indent-tabs-mode: nil -*-
+%% ex: ts=4 sw=4 et
+-module(rebar_xref_SUITE).
+
+-export([suite/0,
+ init_per_suite/1,
+ end_per_suite/1,
+ init_per_testcase/2,
+ end_per_testcase/2,
+ all/0,
+ xref_test/1,
+ xref_ignore_test/1]).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("kernel/include/file.hrl").
+
+%% ===================================================================
+%% common_test callbacks
+%% ===================================================================
+
+suite() ->
+ [].
+
+init_per_suite(Config) ->
+ Config.
+
+end_per_suite(_Config) ->
+ ok.
+
+init_per_testcase(Case, Config) ->
+ UpdConfig = rebar_test_utils:init_rebar_state(Config),
+ AppDir = ?config(apps, UpdConfig),
+ {ok, OrigDir} = file:get_cwd(),
+ file:set_cwd(AppDir),
+ Name = rebar_test_utils:create_random_name("xrefapp_"),
+ Vsn = rebar_test_utils:create_random_vsn(),
+ rebar_test_utils:create_empty_app(AppDir, Name, Vsn, [kernel, stdlib]),
+ AppModules = [behaviour1, behaviour2, mymod, othermod],
+ [write_src_file(AppDir, Name, Module, ignore_xref(Case)) || Module <- AppModules],
+ RebarConfig = [{erl_opts, [debug_info]},
+ {xref_checks, [deprecated_function_calls,deprecated_functions,
+ undefined_function_calls,undefined_functions,
+ exports_not_used,locals_not_used]}],
+ [{app_name, Name},
+ {rebar_config, RebarConfig},
+ {orig_dir, OrigDir} | UpdConfig].
+
+end_per_testcase(_, Config) ->
+ ?debugMsg("End test case cleanup"),
+ AppDir = ?config(apps, Config),
+ OrigDir = ?config(orig_dir, Config),
+ %% Code path cleanup because we set the CWD to the `AppDir' prior
+ %% to launching rebar and these paths make it into the code path
+ %% before the xref module executes so they don't get cleaned up
+ %% automatically after the xref run. Only have to do this because
+ %% we are about to remove the directory and there may be
+ %% subsequent test cases that error out when the code path tries
+ %% to include one of these soon-to-be nonexistent directories.
+ true = code:del_path(AppDir ++ "/."),
+ true = code:del_path(rebar_dir:ebin_dir()),
+ file:set_cwd(OrigDir),
+ ec_file:remove(AppDir, [recursive]),
+ ok.
+
+all() ->
+ [xref_test, xref_ignore_test].
+
+%% ===================================================================
+%% Test cases
+%% ===================================================================
+
+xref_test(Config) ->
+ AppDir = ?config(apps, Config),
+ State = ?config(state, Config),
+ Name = ?config(app_name, Config),
+ RebarConfig = ?config(rebar_config, Config),
+ Result = rebar3:run(rebar_state:new(State, RebarConfig, AppDir), ["xref"]),
+ verify_results(xref_test, Name, Result).
+
+xref_ignore_test(Config) ->
+ AppDir = ?config(apps, Config),
+ State = ?config(state, Config),
+ Name = ?config(app_name, Config),
+ RebarConfig = ?config(rebar_config, Config),
+ Result = rebar3:run(rebar_state:new(State, RebarConfig, AppDir), ["xref"]),
+ verify_results(xref_ignore_test, Name, Result).
+
+%% ===================================================================
+%% Helper functions
+%% ===================================================================
+
+ignore_xref(xref_ignore_test) ->
+ true;
+ignore_xref(_) ->
+ false.
+
+verify_results(TestCase, AppName, Results) ->
+ {error, {rebar_prv_xref,
+ {xref_issues, XrefResults, QueryResults}}} = Results,
+ verify_test_results(TestCase, AppName, XrefResults, QueryResults).
+
+verify_test_results(xref_test, AppName, XrefResults, _QueryResults) ->
+ AppModules = ["behaviour1", "behaviour2", "mymod", "othermod", "somemod"],
+ [Behaviour1Mod, Behaviour2Mod, MyMod, OtherMod, SomeMod] =
+ [list_to_atom(AppName ++ "_" ++ Mod) || Mod <- AppModules],
+ UndefFuns = proplists:get_value(undefined_functions, XrefResults),
+ UndefFunCalls = proplists:get_value(undefined_function_calls, XrefResults),
+ LocalsNotUsed = proplists:get_value(locals_not_used, XrefResults),
+ ExportsNotUsed = proplists:get_value(exports_not_used, XrefResults),
+ DeprecatedFuns = proplists:get_value(deprecated_functions, XrefResults),
+ DeprecatedFunCalls = proplists:get_value(deprecated_function_calls, XrefResults),
+ ?assert(lists:member({SomeMod, notavailable, 1}, UndefFuns)),
+ ?assert(lists:member({{OtherMod, somefunc, 0}, {SomeMod, notavailable, 1}},
+ UndefFunCalls)),
+ ?assert(lists:member({MyMod, fdeprecated, 0}, DeprecatedFuns)),
+ ?assert(lists:member({{OtherMod, somefunc, 0}, {MyMod, fdeprecated, 0}},
+ DeprecatedFunCalls)),
+ ?assert(lists:member({MyMod, localfunc2, 0}, LocalsNotUsed)),
+ ?assert(lists:member({Behaviour1Mod, behaviour_info, 1}, ExportsNotUsed)),
+ ?assert(lists:member({Behaviour2Mod, behaviour_info, 1}, ExportsNotUsed)),
+ ?assert(lists:member({MyMod, other2, 1}, ExportsNotUsed)),
+ ?assert(lists:member({OtherMod, somefunc, 0}, ExportsNotUsed)),
+ ?assertNot(lists:member({MyMod, bh1_a, 1}, ExportsNotUsed)),
+ ?assertNot(lists:member({MyMod, bh1_b, 1}, ExportsNotUsed)),
+ ?assertNot(lists:member({MyMod, bh2_a, 1}, ExportsNotUsed)),
+ ?assertNot(lists:member({MyMod, bh2_b, 1}, ExportsNotUsed)),
+ ok;
+verify_test_results(xref_ignore_test, AppName, XrefResults, _QueryResults) ->
+ AppModules = ["behaviour1", "behaviour2", "mymod", "othermod", "somemod"],
+ [Behaviour1Mod, Behaviour2Mod, MyMod, OtherMod, SomeMod] =
+ [list_to_atom(AppName ++ "_" ++ Mod) || Mod <- AppModules],
+ UndefFuns = proplists:get_value(undefined_functions, XrefResults),
+ ?assertNot(lists:keymember(undefined_function_calls, 1, XrefResults)),
+ ?assertNot(lists:keymember(locals_not_used, 1, XrefResults)),
+ ?assertNot(lists:keymember(exports_not_used, 1, XrefResults)),
+ ?assertNot(lists:keymember(deprecated_functions, 1, XrefResults)),
+ ?assertNot(lists:keymember(deprecated_function_calls, 1, XrefResults)),
+ ?assert(lists:member({SomeMod, notavailable, 1}, UndefFuns)),
+ ok.
+
+write_src_file(Dir, AppName, Module, IgnoreXref) ->
+ Erl = filename:join([Dir, "src", module_name(AppName, Module)]),
+ ok = filelib:ensure_dir(Erl),
+ ok = ec_file:write(Erl, get_module_body(Module, AppName, IgnoreXref)).
+
+module_name(AppName, Module) ->
+ lists:flatten([AppName, "_", atom_to_list(Module), ".erl"]).
+
+get_module_body(behaviour1, AppName, IgnoreXref) ->
+ ["-module(", AppName, "_behaviour1).\n",
+ "-export([behaviour_info/1]).\n",
+ ["-ignore_xref({behaviour_info,1}).\n" || X <- [IgnoreXref], X =:= true],
+ "behaviour_info(callbacks) -> [{bh1_a,1},{bh1_b,1}];\n",
+ "behaviour_info(_Other) -> undefined.\n"];
+get_module_body(behaviour2, AppName, IgnoreXref) ->
+ ["-module(", AppName, "_behaviour2).\n",
+ "-export([behaviour_info/1]).\n",
+ ["-ignore_xref({behaviour_info,1}).\n" || X <- [IgnoreXref], X =:= true],
+ "behaviour_info(callbacks) -> [{bh2_a,1},{bh2_b,1}];\n",
+ "behaviour_info(_Other) -> undefined.\n"];
+get_module_body(mymod, AppName, IgnoreXref) ->
+ ["-module(", AppName, "_mymod).\n",
+ "-export([bh1_a/1,bh1_b/1,bh2_a/1,bh2_b/1,"
+ "other1/1,other2/1,fdeprecated/0]).\n",
+ ["-ignore_xref([{other2,1},{localfunc2,0},{fdeprecated,0}]).\n"
+ || X <- [IgnoreXref], X =:= true],
+ "-behaviour(", AppName, "_behaviour1).\n", % 2 behaviours
+ "-behaviour(", AppName, "_behaviour2).\n",
+ "-deprecated({fdeprecated,0}).\n", % deprecated function
+ "bh1_a(A) -> localfunc1(bh1_a, A).\n", % behaviour functions
+ "bh1_b(A) -> localfunc1(bh1_b, A).\n",
+ "bh2_a(A) -> localfunc1(bh2_a, A).\n",
+ "bh2_b(A) -> localfunc1(bh2_b, A).\n",
+ "other1(A) -> localfunc1(other1, A).\n", % regular exported functions
+ "other2(A) -> localfunc1(other2, A).\n",
+ "localfunc1(A, B) -> {A, B}.\n", % used local
+ "localfunc2() -> ok.\n", % unused local
+ "fdeprecated() -> ok.\n" % deprecated function
+ ];
+get_module_body(othermod, AppName, IgnoreXref) ->
+ ["-module(", AppName, "_othermod).\n",
+ "-export([somefunc/0]).\n",
+ [["-ignore_xref([{", AppName, "_somemod,notavailable,1},{somefunc,0}]).\n",
+ "-ignore_xref({", AppName, "_mymod,fdeprecated,0}).\n"]
+ || X <- [IgnoreXref], X =:= true],
+ "somefunc() ->\n",
+ " ", AppName, "_mymod:other1(arg),\n",
+ " ", AppName, "_somemod:notavailable(arg),\n",
+ " ", AppName, "_mymod:fdeprecated().\n"].