diff options
Diffstat (limited to 'server/_build/default/plugins/coveralls/src')
3 files changed, 730 insertions, 0 deletions
diff --git a/server/_build/default/plugins/coveralls/src/coveralls.app.src b/server/_build/default/plugins/coveralls/src/coveralls.app.src new file mode 100644 index 0000000..85a0d8e --- /dev/null +++ b/server/_build/default/plugins/coveralls/src/coveralls.app.src @@ -0,0 +1,11 @@ +{application,coveralls, + [{description,"Coveralls for Erlang"}, + {vsn,"2.2.0"}, + {licenses,["BSD"]}, + {modules,[]}, + {registred,[]}, + {applications,[kernel,stdlib]}, + {env,[{providers,[rebar3_coveralls]}]}, + {maintainers,["Markus Ekholm"]}, + {links,[{"Github", + "https://github.com/markusn/coveralls-erl"}]}]}. diff --git a/server/_build/default/plugins/coveralls/src/coveralls.erl b/server/_build/default/plugins/coveralls/src/coveralls.erl new file mode 100644 index 0000000..90954c6 --- /dev/null +++ b/server/_build/default/plugins/coveralls/src/coveralls.erl @@ -0,0 +1,499 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Copyright (c) 2013-2016, Markus Ekholm +%%% All rights reserved. +%%% Redistribution and use in source and binary forms, with or without +%%% modification, are permitted provided that the following conditions are met: +%%% * Redistributions of source code must retain the above copyright +%%% notice, this list of conditions and the following disclaimer. +%%% * Redistributions in binary form must reproduce the above copyright +%%% notice, this list of conditions and the following disclaimer in the +%%% documentation and/or other materials provided with the distribution. +%%% * Neither the name of the <organization> nor the +%%% names of its contributors may be used to endorse or promote products +%%% derived from this software without specific prior written permission. +%%% +%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +%%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +%%% ARE DISCLAIMED. IN NO EVENT SHALL MARKUS EKHOLM BE LIABLE FOR ANY +%%% DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +%%% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +%%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +%%% ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +%%% THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +%%% +%%% @copyright 2013-2016 (c) Markus Ekholm <markus@botten.org> +%%% @author Markus Ekholm <markus@botten.org> +%%% @doc coveralls +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%============================================================================= +%% Module declaration + +-module(coveralls). + +%%============================================================================= +%% Exports + +-export([ convert_file/2 + , convert_and_send_file/2 + ]). + +%%============================================================================= +%% Records + +-record(s, { importer = fun cover:import/1 + , module_lister = fun cover:imported_modules/0 + , mod_info = fun module_info_compile/1 + , file_reader = fun file:read_file/1 + , wildcard_reader = fun filelib:wildcard/1 + , analyser = fun cover:analyse/3 + , poster = fun httpc:request/4 + , poster_init = start_wrapper([fun ssl:start/0, fun inets:start/0]) + }). + +%%============================================================================= +%% Defines + +-define(COVERALLS_URL, "https://coveralls.io/api/v1/jobs"). +%%-define(COVERALLS_URL, "http://127.0.0.1:8080"). + +-ifdef(random_only). +-define(random, random). +-else. +-define(random, rand). +-endif. + +%%============================================================================= +%% API functions + +%% @doc Import and convert cover file(s) `Filenames' to a json string +%% representation suitable to post to coveralls. +%% +%% Note that this function will crash if the modules mentioned in +%% any of the `Filenames' are not availabe on the node. +%% @end +-spec convert_file(string() | [string()], map()) -> + string(). +convert_file(Filenames, Report) -> + convert_file(Filenames, Report, #s{}). + +%% @doc Import and convert cover files `Filenames' to a json string and send the +%% json to coveralls. +%% @end +-spec convert_and_send_file(string() | [string()], map()) -> ok. +convert_and_send_file(Filenames, Report) -> + convert_and_send_file(Filenames, Report, #s{}). + +%%============================================================================= +%% Internal functions + +convert_file([L|_]=Filename, Report, S) when is_integer(L) -> + %% single file or wildcard was specified + WildcardReader = S#s.wildcard_reader, + Filenames = WildcardReader(Filename), + convert_file(Filenames, Report, S); +convert_file([[_|_]|_]=Filenames, Report, S) -> + ok = lists:foreach( + fun(Filename) -> ok = import(S, Filename) end, + Filenames), + ConvertedModules = convert_modules(S), + jsx:encode(Report#{source_files => ConvertedModules}, []). + +convert_and_send_file(Filenames, Report, S) -> + send(convert_file(Filenames, Report, S), S). + +send(Json, #s{poster=Poster, poster_init=Init}) -> + ok = Init(), + Boundary = ["----------", integer_to_list(?random:uniform(1000))], + Type = "multipart/form-data; boundary=" ++ Boundary, + Body = to_body(Json, Boundary), + R = Poster(post, {?COVERALLS_URL, [], Type, Body}, [], []), + {ok, {{_, ReturnCode, _}, _, Message}} = R, + case ReturnCode of + 200 -> ok; + ErrCode -> throw({error, {ErrCode, Message}}) + end. + +%%----------------------------------------------------------------------------- +%% HTTP helpers + +to_body(Json, Boundary) -> + iolist_to_binary(["--", Boundary, "\r\n", + "Content-Disposition: form-data; name=\"json_file\"; " + "filename=\"json_file.json\" \r\n" + "Content-Type: application/json\r\n\r\n", + Json, "\r\n", "--", Boundary, "--", "\r\n"]). + +%%----------------------------------------------------------------------------- +%% Callback mockery + +import(#s{importer=F}, File) -> F(File). + +imported_modules(#s{module_lister=F}) -> F(). + +analyze(#s{analyser=F}, Mod) -> F(Mod, calls, line). + +compile_info(#s{mod_info=F}, Mod) -> F(Mod). + +-ifdef(TEST). +module_info_compile(Mod) -> Mod:module_info(compile). +-else. +module_info_compile(Mod) -> + code:load_file(Mod), + case code:is_loaded(Mod) of + {file, _} -> Mod:module_info(compile); + _ -> [] + end. +-endif. + +read_file(#s{file_reader=_F}, "") -> {ok, <<"">>}; +read_file(#s{file_reader=F}, SrcFile) -> F(SrcFile). + +start_wrapper(Funs) -> + fun() -> + lists:foreach(fun(F) -> ok = wrap_start(F) end, Funs) + end. + +wrap_start(StartFun) -> + case StartFun() of + {error,{already_started,_}} -> ok; + ok -> ok + end. + +digit(I) when I < 10 -> <<($0 + I):8>>; +digit(I) -> <<($a -10 + I):8>>. + +hex(<<>>) -> + <<>>; +hex(<<I:4, R/bitstring>>) -> + <<(digit(I))/binary, (hex(R))/binary>>. + +%%----------------------------------------------------------------------------- +%% Converting modules + +convert_modules(S) -> + F = fun(Mod, L) -> convert_module(Mod, S, L) end, + lists:foldr(F, [], imported_modules(S)). + +convert_module(Mod, S, L) -> + {ok, CoveredLines0} = analyze(S, Mod), + %% Remove strange 0 indexed line + FilterF = fun({{_, X}, _}) -> X =/= 0 end, + CoveredLines = lists:filter(FilterF, CoveredLines0), + case proplists:get_value(source, compile_info(S, Mod), "") of + "" -> L; + SrcFile -> + {ok, SrcBin} = read_file(S, SrcFile), + Src0 = lists:flatten(io_lib:format("~s", [SrcBin])), + SrcDigest = erlang:md5(SrcBin), + LinesCount = count_lines(Src0), + Cov = create_cov(CoveredLines, LinesCount), + [#{name => unicode:characters_to_binary(relative_to_cwd(SrcFile), utf8, utf8), + source_digest => hex(SrcDigest), + coverage => Cov} + | L] + end. + +expand(Path) -> expand(filename:split(Path), []). + +expand([], Acc) -> filename:join(lists:reverse(Acc)); +expand(["."|Tail], Acc) -> expand(Tail, Acc); +expand([".."|Tail], []) -> expand(Tail, []); +expand([".."|Tail], [_|Acc]) -> expand(Tail, Acc); +expand([Segment|Tail], Acc) -> expand(Tail, [Segment|Acc]). + +realpath(Path) -> realpath(filename:split(Path), "./"). + +realpath([], Acc) -> filename:absname(expand(Acc)); +realpath([Head | Tail], Acc) -> + NewAcc0 = filename:join([Acc, Head]), + NewAcc = case file:read_link(NewAcc0) of + {ok, Link} -> + case filename:pathtype(Link) of + absolute -> realpath(Link); + relative -> filename:join([Acc, Link]) + end; + _ -> NewAcc0 + end, + realpath(Tail, NewAcc). + +relative_to_cwd(Path) -> + case file:get_cwd() of + {ok, Base} -> relative_to(Path, Base); + _ -> Path + end. + +relative_to(Path, From) -> + Path1 = realpath(Path), + relative_to(filename:split(Path1), filename:split(From), Path). + +relative_to([H|T1], [H|T2], Original) -> relative_to(T1, T2, Original); +relative_to([_|_] = L1, [], _Original) -> filename:join(L1); +relative_to(_, _, Original) -> Original. + +create_cov(_CoveredLines, []) -> + []; +create_cov(CoveredLines, LinesCount) when is_integer(LinesCount) -> + create_cov(CoveredLines, lists:seq(1, LinesCount)); +create_cov([{{_,LineNo},Count}|CoveredLines], [LineNo|LineNos]) -> + [Count | create_cov(CoveredLines, LineNos)]; +create_cov(CoveredLines, [_|LineNos]) -> + [null | create_cov(CoveredLines, LineNos)]. + +%%----------------------------------------------------------------------------- +%% Generic helpers + +count_lines("") -> 1; +count_lines("\n") -> 1; +count_lines([$\n|S]) -> 1 + count_lines(S); +count_lines([_|S]) -> count_lines(S). + +%%============================================================================= +%% Tests + +-ifdef(TEST). +-define(DEBUG, true). +-include_lib("eunit/include/eunit.hrl"). + +normalize_json_str(Str) when is_binary(Str) -> + jsx:encode(jsx:decode(Str, [return_maps, {labels, existing_atom}])); +normalize_json_str(Str) when is_list(Str) -> + normalize_json_str(iolist_to_binary(Str)). + +convert_file_test() -> + Expected = + jsx:decode( + <<"{\"service_job_id\": \"1234567890\"," + " \"service_name\": \"travis-ci\"," + " \"source_files\": [" + " {\"name\": \"example.rb\"," + " \"source_digest\": \"3feb892deff06e7accbe2457eec4cd8b\"," + " \"coverage\": [null,1,null]" + " }," + " {\"name\": \"two.rb\"," + " \"source_digest\": \"fce46ee19702bd262b2e4907a005aff4\"," + " \"coverage\": [null,1,0,null]" + " }" + " ]" + "}">>, [return_maps, {labels, existing_atom}]), + Report = #{service_job_id => <<"1234567890">>, + service_name => <<"travis-ci">>}, + Got = jsx:decode( + convert_file("example.rb", Report, mock_s()), + [return_maps, {labels, existing_atom}]), + ?assertEqual(Expected, Got). + +convert_and_send_file_test() -> + Expected = + normalize_json_str( + "{\"service_job_id\": \"1234567890\"," + " \"service_name\": \"travis-ci\"," + " \"source_files\": [" + " {\"name\": \"example.rb\"," + " \"source_digest\": \"3feb892deff06e7accbe2457eec4cd8b\"," + " \"coverage\": [null,1,null]" + " }," + " {\"name\": \"two.rb\"," + " \"source_digest\": \"fce46ee19702bd262b2e4907a005aff4\"," + " \"coverage\": [null,1,0,null]" + " }" + " ]" + "}"), + Report = #{service_job_id => <<"1234567890">>, + service_name => <<"travis-ci">>}, + ?assertEqual(ok, convert_and_send_file("example.rb", Report, mock_s(Expected))). + +send_test_() -> + Expected = + normalize_json_str( + "{\"service_job_id\": \"1234567890\",\n" + " \"service_name\": \"travis-ci\",\n" + " \"source_files\": [\n" + " {\"name\": \"example.rb\",\n" + " \"source_digest\": \"\tdef four\\n 4\\nend\",\n" + " \"coverage\": [null,1,null]\n" + " }" + " ]" + "}"), + [ ?_assertEqual(ok, send(Expected, mock_s(Expected))) + , ?_assertThrow({error, {_,_}}, send("foo", mock_s(<<"bar">>))) + ]. + +%%----------------------------------------------------------------------------- +%% Generic helpers tests + +count_lines_test_() -> + [ ?_assertEqual(1, count_lines("")) + , ?_assertEqual(1, count_lines("foo")) + , ?_assertEqual(1, count_lines("bar\n")) + , ?_assertEqual(2, count_lines("foo\nbar")) + , ?_assertEqual(3, count_lines("foo\n\nbar")) + , ?_assertEqual(2, count_lines("foo\nbar\n")) + ]. + +expand_test_() -> + [ ?_assertEqual("/a/b", expand(["/", "a", "b"], [])) + , ?_assertEqual("a/c" , expand(["a", "b", "..", ".", "c"], [])) + , ?_assertEqual("/" , expand(["..", ".", "/"], [])) + ]. + +realpath_and_relative_test_() -> + {setup, + fun() -> %% setup + {ok, Cwd} = file:get_cwd(), + Root = string:strip( + os:cmd("mktemp -d -t coveralls_tests.XXX"), right, $\n), + ok = file:set_cwd(Root), + {Cwd, Root} + end, + fun({Cwd, _Root}) -> %% teardown + ok = file:set_cwd(Cwd) + end, + fun({_Cwd, Root}) -> %% tests + Filename = "file", + Dir1 = filename:join([Root, "_test_src", "dir1"]), + Dir2 = filename:join([Root, "_test_src", "dir2"]), + File1 = filename:join([Dir1, Filename]), + File2 = filename:join([Dir2, Filename]), + Link1 = filename:join([ Root + , "_test_build" + , "default" + , "lib" + , "mylib" + , "src" + , "dir1" + ]), + Link2 = filename:join([ Root + , "_test_build" + , "default" + , "lib" + , "mylib" + , "src" + , "dir2" + ]), + [ ?_assertEqual(ok, + filelib:ensure_dir(filename:join([Dir1, "dummy"]))) + , ?_assertEqual(ok, + filelib:ensure_dir(filename:join([Dir2, "dummy"]))) + , ?_assertEqual(ok, + file:write_file(File1, "data")) + , ?_assertEqual(ok, + file:write_file(File2, "data")) + , ?_assertEqual(ok, + filelib:ensure_dir(Link1)) + , ?_assertEqual(ok, + filelib:ensure_dir(Link2)) + , ?_assertEqual(ok, + file:make_symlink(Dir1, Link1)) + , ?_assertEqual(ok, + file:make_symlink(filename:join([ ".." + , ".." + , ".." + , ".." + , ".." + , "_test_src" + , "dir2" + ]) + , Link2)) + , ?_assertEqual(realpath(File1), + realpath(filename:join([Link1, Filename]))) + , ?_assertEqual(realpath(File2), + realpath(filename:join([Link2, Filename]))) + , ?_assertEqual(realpath(File1), + filename:absname( + relative_to_cwd( + filename:join([Link1, Filename])))) + , ?_assertEqual(realpath(File2), + filename:absname( + relative_to_cwd( + filename:join([Link2, Filename])))) + ] + end}. + +%%----------------------------------------------------------------------------- +%% Callback mockery tests +module_info_compile_test() -> + ?assert(is_tuple(lists:keyfind(source, 1, module_info_compile(?MODULE)))). + +start_wrapper_test_() -> + F = fun() -> ok end, + StartedF = fun() -> {error,{already_started,mod}} end, + ErrorF = fun() -> {error, {error, mod}} end, + [ ?_assertEqual(ok, (start_wrapper([F, StartedF]))()) + , ?_assertError(_, (start_wrapper([F, StartedF, ErrorF]))()) + ]. + +%%----------------------------------------------------------------------------- +%% Converting modules tests + +create_cov_test() -> + ?assertEqual([null, 3, null, 4, null], + create_cov([{{foo, 2}, 3}, {{foo, 4}, 4}], 5)). + +convert_module_test() -> + Expected = + [#{name => <<"example.rb">>, + source_digest => <<"3feb892deff06e7accbe2457eec4cd8b">>, + coverage => [null,1,null]}], + ?assertEqual(Expected, convert_module('example.rb', mock_s(), [])). + +convert_modules_test() -> + Expected = + [#{name => <<"example.rb">>, + source_digest => <<"3feb892deff06e7accbe2457eec4cd8b">>, + coverage => [null,1,null] + }, + #{name => <<"two.rb">>, + source_digest => <<"fce46ee19702bd262b2e4907a005aff4">>, + coverage => [null,1,0,null] + }], + ?assertEqual(Expected, + convert_modules(mock_s())). + +%%----------------------------------------------------------------------------- +%% Setup helpers + +mock_s() -> mock_s(""). + +mock_s(Json) -> + #s{ importer = + fun(_) -> ok end + , module_lister = + fun() -> ['example.rb', 'two.rb'] end + , mod_info = + fun('example.rb') -> [{source,"example.rb"}]; + ('two.rb') -> [{source,"two.rb"}] + end + , file_reader = + fun("example.rb") -> + {ok, <<"def four\n 4\nend">>}; + ("two.rb") -> + {ok, <<"def seven\n eight\n nine\nend">>} + end + , wildcard_reader = fun(AnyFile) -> [AnyFile] end + , analyser = + fun('example.rb' , calls, line) -> {ok, [ {{'example.rb', 2}, 1} ]}; + ('two.rb' , calls, line) -> {ok, [ {{'two.rb', 2}, 1} + , {{'two.rb', 3}, 0} + ] + } + end + , poster_init = + fun() -> ok end + , poster = + fun(post, {_, _, _, Body}, _, _) -> + case binary:match(Body, Json) =/= nomatch of + true -> {ok, {{"", 200, ""}, "", ""}}; + false -> {ok, {{"", 666, ""}, "", "Not expected"}} + end + end + }. + +-endif. + +%%% Local Variables: +%%% allout-layout: t +%%% erlang-indent-level: 2 +%%% End: diff --git a/server/_build/default/plugins/coveralls/src/rebar3_coveralls.erl b/server/_build/default/plugins/coveralls/src/rebar3_coveralls.erl new file mode 100644 index 0000000..01084ee --- /dev/null +++ b/server/_build/default/plugins/coveralls/src/rebar3_coveralls.erl @@ -0,0 +1,220 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Copyright (c) 2013-2016, Markus Ekholm +%%% All rights reserved. +%%% Redistribution and use in source and binary forms, with or without +%%% modification, are permitted provided that the following conditions are met: +%%% * Redistributions of source code must retain the above copyright +%%% notice, this list of conditions and the following disclaimer. +%%% * Redistributions in binary form must reproduce the above copyright +%%% notice, this list of conditions and the following disclaimer in the +%%% documentation and/or other materials provided with the distribution. +%%% * Neither the name of the <organization> nor the +%%% names of its contributors may be used to endorse or promote products +%%% derived from this software without specific prior written permission. +%%% +%%% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +%%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +%%% ARE DISCLAIMED. IN NO EVENT SHALL MARKUS EKHOLM BE LIABLE FOR ANY +%%% DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +%%% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +%%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +%%% ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +%%% THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +%%% +%%% @copyright 2013-2016 (c) Yury Gargay <yury.gargay@gmail.com>, +%%% Markus Ekholm <markus@botten.org> +%%% @end +%%% @author Yury Gargay <yury.gargay@gmail.com> +%%% @author Markus Ekholm <markus@botten.org> +%%% @doc coveralls plugin for rebar3 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-module(rebar3_coveralls). +-behaviour(provider). + +-export([ init/1 + , do/1 + , format_error/1 + ]). + +-define(PROVIDER, send). +-define(DEPS, [{default, app_discovery}]). + +%% =================================================================== +%% Public API +%% =================================================================== +-spec init(rebar_state:t()) -> {ok, rebar_state:t()}. +init(State) -> + Provider = providers:create([ {name, ?PROVIDER} + , {module, ?MODULE} + , {namespace, coveralls} + , {bare, true} + , {deps, ?DEPS} + , {example, "rebar3 coveralls send"} + , {short_desc, "Send coverdata to coveralls."} + , {desc, "Send coveralls to coveralls."} + , {opts, []} + ]), + {ok, rebar_state:add_provider(State, Provider)}. + +-spec do(rebar_state:t()) -> {ok, rebar_state:t()} | {error, string()}. +do(State) -> + rebar_api:info("Running coveralls...", []), + ConvertAndSend = fun coveralls:convert_and_send_file/2, + Get = fun(Key, Def) -> rebar_state:get(State, Key, Def) end, + GetLocal = fun(Key, Def) -> rebar_state:get(State, Key, Def) end, + MaybeSkip = fun() -> ok end, + ok = cover_paths(State), + try + do_coveralls(ConvertAndSend, + Get, + GetLocal, + MaybeSkip, + 'send-coveralls'), + {ok, State} + catch throw:{error, {ErrCode, Msg}} -> + io:format("Failed sending coverdata to coveralls, ~p: ~p", + [ErrCode, Msg]), + {error, rebar_abort} + end. + +-spec format_error(any()) -> iolist(). +format_error(Reason) -> + io_lib:format("~p", [Reason]). + +cover_paths(State) -> + lists:foreach(fun(App) -> + AppDir = rebar_app_info:out_dir(App), + true = code:add_patha(filename:join([AppDir, "ebin"])), + _ = code:add_patha(filename:join([AppDir, "test"])) + end, + rebar_state:project_apps(State)), + _ = code:add_patha(filename:join([rebar_dir:base_dir(State), "test"])), + ok. + +%%============================================================================= +%% Internal functions + +to_binary(List) when is_list(List) -> + unicode:characters_to_binary(List, utf8, utf8); +to_binary(Atom) when is_atom(Atom) -> + atom_to_binary(Atom, utf8); +to_binary(Bin) when is_binary(Bin) -> + Bin. +to_boolean(true) -> true; +to_boolean(1) -> true; +to_boolean(_) -> false. + +do_coveralls(ConvertAndSend, Get, GetLocal, MaybeSkip, Task) -> + File = GetLocal(coveralls_coverdata, undef), + ServiceName = to_binary(GetLocal(coveralls_service_name, undef)), + ServiceJobId = to_binary(GetLocal(coveralls_service_job_id, undef)), + F = fun(X) -> X =:= undef orelse X =:= false end, + CoverExport = Get(cover_export_enabled, false), + case lists:any(F, [File, ServiceName, ServiceJobId, CoverExport]) of + true -> + throw({error, + "need to specify coveralls_* and cover_export_enabled " + "in rebar.config"}); + false -> + ok + end, + + Report0 = + #{service_job_id => ServiceJobId, + service_name => ServiceName}, + Opts = [{coveralls_repo_token, repo_token, string}, + {coveralls_service_pull_request, service_pull_request, string}, + {coveralls_commit_sha, commit_sha, string}, + {coveralls_service_number, service_number, string}, + {coveralls_parallel, parallel, boolean}], + Report = + lists:foldl(fun({Cfg, Key, Conv}, R) -> + case GetLocal(Cfg, undef) of + undef -> R; + Value when Conv =:= string -> maps:put(Key, to_binary(Value), R); + Value when Conv =:= boolean -> maps:put(Key, to_boolean(Value), R); + Value -> maps:put(Key, Value, R) + end + end, Report0, Opts), + + DoCoveralls = (GetLocal(do_coveralls_after_ct, true) andalso Task == ct) + orelse (GetLocal(do_coveralls_after_eunit, true) andalso Task == eunit) + orelse Task == 'send-coveralls', + case DoCoveralls of + true -> + io:format("rebar_coveralls:" + "Exporting cover data " + "from ~s using service ~s and jobid ~s~n", + [File, ServiceName, ServiceJobId]), + ok = ConvertAndSend(File, Report); + _ -> MaybeSkip() + end. + + +%%============================================================================= +%% Tests + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +task_test_() -> + File = "foo", + ServiceJobId = "123", + ServiceName = "bar", + ConvertAndSend = fun("foo", #{service_job_id := <<"123">>, + service_name := <<"bar">>}) -> ok end, + ConvertWithOpts = fun("foo", #{service_job_id := <<"123">>, + service_name := <<"bar">>, + service_pull_request := <<"PR#1">>, + parallel := true}) -> ok + end, + Get = fun(cover_export_enabled, _) -> true end, + GetLocal = fun(coveralls_coverdata, _) -> File; + (coveralls_service_name, _) -> ServiceName; + (coveralls_service_job_id, _) -> ServiceJobId; + (do_coveralls_after_eunit, _) -> true; + (do_coveralls_after_ct, _) -> true; + (coveralls_repo_token, _) -> []; + (_, Default) -> Default + end, + GetLocalAllOpt = fun(coveralls_coverdata, _) -> File; + (coveralls_service_name, _) -> ServiceName; + (coveralls_service_job_id, _) -> ServiceJobId; + (coveralls_service_pull_request, _) -> "PR#1"; + (coveralls_parallel, _) -> true; + (do_coveralls_after_eunit, _) -> true; + (do_coveralls_after_ct, _) -> true; + (coveralls_repo_token, _) -> []; + (_, Default) -> Default + end, + GetLocalWithCoverallsTask + = fun(coveralls_coverdata, _) -> File; + (coveralls_service_name, _) -> ServiceName; + (coveralls_service_job_id, _) -> ServiceJobId; + (do_coveralls_after_eunit, _) -> false; + (do_coveralls_after_ct, _) -> false; + (coveralls_repo_token, _) -> []; + (_, Default) -> Default + end, + GetBroken = fun(cover_export_enabled, _) -> false end, + MaybeSkip = fun() -> skip end, + [ ?_assertEqual(ok, do_coveralls(ConvertAndSend, Get, GetLocal, MaybeSkip, eunit)) + , ?_assertEqual(ok, do_coveralls(ConvertAndSend, Get, GetLocal, MaybeSkip, ct)) + , ?_assertThrow({error, _}, do_coveralls(ConvertAndSend, GetBroken, GetLocal, MaybeSkip, eunit)) + , ?_assertThrow({error, _}, do_coveralls(ConvertAndSend, GetBroken, GetLocal, MaybeSkip, ct)) + , ?_assertEqual(skip, do_coveralls(ConvertAndSend, Get, GetLocalWithCoverallsTask, MaybeSkip, eunit)) + , ?_assertEqual(skip, do_coveralls(ConvertAndSend, Get, GetLocalWithCoverallsTask, MaybeSkip, ct)) + , ?_assertEqual(ok, do_coveralls(ConvertAndSend, Get, GetLocalWithCoverallsTask, MaybeSkip, 'send-coveralls')) + , ?_assertEqual(ok, do_coveralls(ConvertWithOpts, Get, GetLocalAllOpt, MaybeSkip, eunit)) + , ?_assertEqual(ok, do_coveralls(ConvertWithOpts, Get, GetLocalAllOpt, MaybeSkip, ct)) + ]. + +-endif. + +%%% Local Variables: +%%% allout-layout: t +%%% erlang-indent-level: 2 +%%% End: |