-module(dialyzer_test). -export([dialyzer_test/6]). -include("test_server.hrl"). -define(test_case_dir, "src"). -define(results_dir,"results"). -define(plt_filename,".dialyzer_plt"). -define(required_modules, "kernel stdlib compiler erts"). dialyzer_test(Options, TestCase, Kind, Dir, OutDir, Dog) -> PltFilename = filename:join(OutDir, ?plt_filename), case file:read_file_info(PltFilename) of {ok, _} -> ok; {error, _ } -> create_plt(OutDir, Dog) end, SrcDir = filename:join(Dir, ?test_case_dir), ResDir = filename:join(Dir, ?results_dir), TestCaseString = atom_to_list(TestCase), Filename = filename:join(SrcDir, TestCaseString), CorrectOptions = convert_relative_paths(Options, Dir), FilesOption = case Kind of file -> {files, [Filename ++ ".erl"]}; dir -> {files_rec, [Filename]} end, ResFile = TestCaseString, NewResFile = filename:join(OutDir, ResFile), OldResFile = filename:join(ResDir, ResFile), RawWarns = dialyzer:run([FilesOption, {init_plt, PltFilename}, {from, src_code}, {check_plt, false} | CorrectOptions]), Warns = lists:sort([dialyzer:format_warning(W) || W <- RawWarns]), case Warns of [] -> ok; _ -> case file:open(NewResFile,['write']) of {ok, OutFile} -> io:format(OutFile,"\n~s",[Warns]), file:close(OutFile); Other -> erlang:error(Other) end end, case diff(NewResFile, OldResFile) of 'same' -> file:delete(NewResFile), 'same'; Any -> Any end. create_plt(OutDir, Dog) -> PltFilename = filename:join(OutDir, ?plt_filename), ?t:timetrap_cancel(Dog), ?t:format("Generating plt..."), HomeDir = os:getenv("HOME"), HomePlt = filename:join(HomeDir, ?plt_filename), file:copy(HomePlt, PltFilename), try AddCommand = "dialyzer --add_to_plt --output_plt " ++ PltFilename ++ " --apps " ++ ?required_modules, ?t:format(AddCommand ++ "\n"), ?t:format(os:cmd(AddCommand)), dialyzer:run([{analysis_type, plt_check}, {init_plt, PltFilename}]) of [] -> ok catch _:_ -> BuildCommand = "dialyzer --build_plt --output_plt " ++ PltFilename ++ " --apps " ++ ?required_modules, ?t:format(BuildCommand ++ "\n"), ?t:format(os:cmd(BuildCommand)) end. convert_relative_paths(Options, Dir) -> convert_relative_paths(Options, Dir, []). convert_relative_paths([], _Dir, Acc) -> Acc; convert_relative_paths([{include_dirs, Paths}|Rest], Dir, Acc) -> AbsolutePaths = convert_relative_paths_1(Paths, Dir, []), convert_relative_paths(Rest, Dir, [{include_dirs, AbsolutePaths}|Acc]); convert_relative_paths([Option|Rest], Dir, Acc) -> convert_relative_paths(Rest, Dir, [Option|Acc]). convert_relative_paths_1([], _Dir, Acc) -> Acc; convert_relative_paths_1([Path|Rest], Dir, Acc) -> convert_relative_paths_1(Rest, Dir, [filename:join(Dir, Path)|Acc]). diff(Filename1, Filename2) -> File1 = case file:open(Filename1, [read]) of {ok, F1} -> {file, F1}; _ -> empty end, File2 = case file:open(Filename2, [read]) of {ok, F2} -> {file, F2}; _ -> empty end, case diff1(File1, File2) of {error, {N, Error}} -> case N of 1 -> {error, {Filename1, Error}}; 2 -> {error, {Filename2, Error}} end; [] -> 'same'; DiffList -> {'differ', DiffList} end. diff1(File1, File2) -> case file_to_lines(File1) of {error, Error} -> {error, {1, Error}}; Lines1 -> case file_to_lines(File2) of {error, Error} -> {error, {2, Error}}; Lines2 -> Common = lcs_fast(Lines1, Lines2), diff2(Lines1, 1, Lines2, 1, Common, []) end end. diff2([], _, [], _, [], Acc) -> lists:keysort(2,Acc); diff2([H1|T1], N1, [], N2, [], Acc) -> diff2(T1, N1+1, [], N2, [], [{new, N1, H1}|Acc]); diff2([], N1, [H2|T2], N2, [], Acc) -> diff2([], N1, T2, N2+1, [], [{old, N2, H2}|Acc]); diff2([H1|T1], N1, [H2|T2], N2, [], Acc) -> diff2(T1, N1+1, T2, N2+1, [], [{new, N1, H1}, {old, N2, H2}|Acc]); diff2([H1|T1]=L1, N1, [H2|T2]=L2, N2, [HC|TC]=LC, Acc) -> case H1 =:= H2 of true -> diff2(T1, N1+1, T2, N2+1, TC, Acc); false -> case H1 =:= HC of true -> diff2(L1, N1, T2, N2+1, LC, [{old, N2, H2}|Acc]); false -> diff2(T1, N1+1, L2, N2, LC, [{new, N1, H1}|Acc]) end end. -spec lcs_fast([string()], [string()]) -> [string()]. lcs_fast(S1, S2) -> M = length(S1), N = length(S2), Acc = array:new(M*N, {default, 0}), {L, _} = lcs_fast(S1, S2, 1, 1, N, Acc), L. -spec lcs_fast([string()], [string()], pos_integer(), pos_integer(), non_neg_integer(), array()) -> {[string()], array()}. lcs_fast([], _, _, _, _, Acc) -> {[], Acc}; lcs_fast(_, [], _, _, _, Acc) -> {[], Acc}; lcs_fast([H1|T1] = S1, [H2|T2] = S2, N1, N2, N, Acc) -> I = (N1-1) * N + N2 - 1, case array:get(I, Acc) of 0 -> case string:equal(H1, H2) of true -> {T, NAcc} = lcs_fast(T1, T2, N1+1, N2+1, N, Acc), L = [H1|T], {L, array:set(I, L, NAcc)}; false -> {L1, NAcc1} = lcs_fast(S1, T2, N1, N2+1, N, Acc), {L2, NAcc2} = lcs_fast(T1, S2, N1+1, N2, N, NAcc1), L = longest(L1, L2), {L, array:set(I, L, NAcc2)} end; L -> {L, Acc} end. -spec longest([string()], [string()]) -> [string()]. longest(S1, S2) -> case length(S1) > length(S2) of true -> S1; false -> S2 end. file_to_lines(empty) -> []; file_to_lines({file, File}) -> case file_to_lines(File, []) of {error, _} = Error -> Error; Lines -> lists:reverse(Lines) end. file_to_lines(File, Acc) -> case io:get_line(File, "") of {error, _}=Error -> Error; eof -> Acc; A -> file_to_lines(File, [A|Acc]) end.