Skip to content

Commit

Permalink
Convert Gradualizer into an OTP application with supervision tree
Browse files Browse the repository at this point in the history
  • Loading branch information
gomoripeti authored and josefs committed Mar 8, 2019
1 parent b03bc74 commit 27ceb1d
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 74 deletions.
1 change: 1 addition & 0 deletions src/gradualizer.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
[kernel,
stdlib
]},
{mod, {gradualizer_app, []}},
{env,[
{providers, [rebar_prv_gradualizer]}
]},
Expand Down
21 changes: 21 additions & 0 deletions src/gradualizer_app.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
%%%-------------------------------------------------------------------
%%% @doc Gradualizer application
%%%-------------------------------------------------------------------
-module(gradualizer_app).

-behaviour(application).

%% Application callbacks
-export([start/2,
stop/1
]).

%%%===================================================================
%%% Application callbacks
%%%===================================================================

start(_StartType, _StartArgs) ->
gradualizer_sup:start_link().

stop(_State) ->
ok.
18 changes: 2 additions & 16 deletions src/gradualizer_cache.erl
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@

%% API
-export([start_link/0,
ensure_started/0,
stop/0,
get_glb/3,
store_glb/4
]).
Expand All @@ -38,18 +36,6 @@
start_link() ->
gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

-spec ensure_started() -> ok | {error, Error :: any()}.
ensure_started() ->
case start_link() of
{ok, _} -> ok;
{error, {already_started, _}} -> ok;
Error -> Error
end.

-spec stop() -> ok.
stop() ->
gen_server:call(?SERVER, stop).

%%
%% GLB Cache
%%
Expand Down Expand Up @@ -84,8 +70,8 @@ init([]) ->
ets:new(?GLB_CACHE, [set, public, named_table]),
{ok, #state{}}.

handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
handle_call(_Request, _From, State) ->
{reply, unknown_request, State}.

handle_cast(_Request, State) ->
{noreply, State}.
Expand Down
6 changes: 1 addition & 5 deletions src/gradualizer_db.erl
Original file line number Diff line number Diff line change
Expand Up @@ -275,11 +275,7 @@ call(Request) ->
call(Request, 5000).

call(Request, Timeout) ->
try gen_server:call(?name, Request, Timeout)
catch exit:{noproc, _} ->
{ok, _} = start_link(),
gen_server:call(?name, Request, Timeout)
end.
gen_server:call(?name, Request, Timeout).

%% helper for handle_call for get_type, get_exported_type, get_opaque_type.
-spec handle_get_type(module(), Name :: atom(), Params :: [type()],
Expand Down
57 changes: 57 additions & 0 deletions src/gradualizer_sup.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
%%%-------------------------------------------------------------------
%%% @doc Main Gradualizer supervisor
%%%-------------------------------------------------------------------
-module(gradualizer_sup).

-behaviour(supervisor).

%% API
-export([start_link/0]).

%% Supervisor callbacks
-export([init/1]).

-define(SERVER, ?MODULE).

%%===================================================================
%% API functions
%%===================================================================

%% @doc Start the supervisor
-spec start_link() -> {ok, Pid :: pid()} |
{error, {already_started, Pid :: pid()}} |
{error, {shutdown, term()}} |
{error, term()} |
ignore.
start_link() ->
supervisor:start_link({local, ?SERVER}, ?MODULE, []).

%%===================================================================
%% Supervisor callbacks
%%===================================================================

-spec init(Args :: term()) ->
{ok, {SupFlags :: supervisor:sup_flags(),
[ChildSpec :: supervisor:child_spec()]}} |
ignore.
init([]) ->
SupFlags = #{strategy => one_for_one,
intensity => 1,
period => 5},
Children = [child(gradualizer_db),
child(gradualizer_cache)],
{ok, {SupFlags, Children}}.

%%===================================================================
%% Internal functions
%%===================================================================

child(Module) ->
#{id => Module,
start => {Module, start_link, []},
restart => permanent,
shutdown => 5000,
type => worker,
modules => [Module]}.


6 changes: 1 addition & 5 deletions src/typechecker.erl
Original file line number Diff line number Diff line change
Expand Up @@ -3828,11 +3828,7 @@ type_check_forms(Forms, Opts) ->
File = proplists:get_value(print_file, Opts),
NoReportErrors = proplists:get_bool(return_errors, Opts),

case gradualizer_db:start_link() of
{ok, _Pid} -> ok;
{error, {already_started, _}} -> ok
end,
ok = gradualizer_cache:ensure_started(),
{ok, _} = application:ensure_all_started(gradualizer),

ParseData =
collect_specs_types_opaques_and_functions(Forms),
Expand Down
22 changes: 19 additions & 3 deletions test/gradualizer_db_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

-include_lib("eunit/include/eunit.hrl").

import_module_test() ->
?assertMatch(not_found, gradualizer_db:import_module(hello)),
?assertMatch(ok, gradualizer_db:import_module(lists)).
import_module_test_() ->
{setup,
fun setup_app/0,
fun cleanup_app/1,
[?_assertMatch(not_found, gradualizer_db:import_module(hello)),
?_assertMatch(ok, gradualizer_db:import_module(lists))
]}.

%%
%% Helper functions
%%

setup_app() ->
{ok, Apps} = application:ensure_all_started(gradualizer),
Apps.

cleanup_app(Apps) ->
[ok = application:stop(App) || App <- Apps],
ok.
107 changes: 67 additions & 40 deletions test/test.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,79 @@

-include_lib("eunit/include/eunit.hrl").

should_pass_test_() ->
%% user_types.erl references remote_types.erl
%% it is not in the sourcemap of the DB so let's import it manually
gradualizer_db:import_erl_files(["test/should_pass/user_types.erl"]),
%% imported.erl references any.erl
gradualizer_db:import_erl_files(["test/should_pass/any.erl"]),
map_erl_files(fun(File) ->
{filename:basename(File), [?_assertMatch({ok, _}, {gradualizer:type_check_file(File), File})]}
end, "test/should_pass").

should_fail_test_() ->
map_erl_files(fun(File) ->
Errors = gradualizer:type_check_file(File, [return_errors]),
%% Test that error formatting doesn't crash
lists:foreach(fun({_, Error}) -> typechecker:handle_type_error(Error) end, Errors),
{ok, Forms} = gradualizer_file_utils:get_forms_from_erl(File),
NumberOfExportedFunctions = typechecker:number_of_exported_functions(Forms),
{filename:basename(File),
[?_assertMatch({NumberOfExportedFunctions, _, _},
{length(Errors), filename:basename(File), NumberOfExportedFunctions})]}
end, "test/should_fail").
should_test_() ->
{setup,
fun setup_app/0,
fun cleanup_app/1,
[{generator, fun gen_should_pass/0},
{generator, fun gen_should_fail/0},
{generator, fun gen_known_problem_should_pass/0},
{generator, fun gen_known_problem_should_fail/0}
]}.

gen_should_pass() ->
{setup,
fun() ->
%% user_types.erl references remote_types.erl
%% it is not in the sourcemap of the DB so let's import it manually
gradualizer_db:import_erl_files(["test/should_pass/user_types.erl"]),
%% imported.erl references any.erl
gradualizer_db:import_erl_files(["test/should_pass/any.erl"])
end,
map_erl_files(
fun(File) ->
?_assertEqual(ok, gradualizer:type_check_file(File))
end, "test/should_pass")
}.

gen_should_fail() ->
map_erl_files(
fun(File) ->
fun() ->
Errors = gradualizer:type_check_file(File, [return_errors]),
%% Test that error formatting doesn't crash
lists:foreach(fun({_, Error}) -> typechecker:handle_type_error(Error) end, Errors),
{ok, Forms} = gradualizer_file_utils:get_forms_from_erl(File),
ExpectedErrors = typechecker:number_of_exported_functions(Forms),
?_assertEqual(ExpectedErrors, length(Errors))
end
end, "test/should_fail").

% Test succeeds if Gradualizer crashes or if it doesn't type check.
% Doing so makes the test suite notify us whenever a known problem is resolved.
known_problem_should_pass_test_() ->
map_erl_files(fun(File) ->
Result =
try gradualizer:type_check_file(File) of
V -> V
catch _:_ -> nok
end,
{filename:basename(File), [?_assertMatch({nok, _}, {Result, File})]}
end, "test/known_problems/should_pass").
gen_known_problem_should_pass() ->
map_erl_files(
fun(File) ->
?_assertNotEqual(ok, safe_type_check_file(File))
end, "test/known_problems/should_pass").

% Test succeeds if Gradualizer crashes or if it does type check.
% Doing so makes the test suite notify us whenever a known problem is resolved.
known_problem_should_fail_test_() ->
map_erl_files(fun(File) ->
Result =
try gradualizer:type_check_file(File) of
V -> V
catch _:_ -> ok
end,
{filename:basename(File), ?_assertMatch({ok, _}, {Result, File})}
end, "test/known_problems/should_fail").
gen_known_problem_should_fail() ->
map_erl_files(
fun(File) ->
?_assertNotEqual(nok, safe_type_check_file(File))
end, "test/known_problems/should_fail").

%%
%% Helper functions
%%

setup_app() ->
{ok, Apps} = application:ensure_all_started(gradualizer),
Apps.

cleanup_app(Apps) ->
[ok = application:stop(App) || App <- Apps],
ok.

map_erl_files(Fun, Dir) ->
Files = filelib:wildcard(filename:join(Dir, "*.erl")),
lists:map(Fun, Files).
[{filename:basename(File), Fun(File)} || File <- Files].

safe_type_check_file(File) ->
try
gradualizer:type_check_file(File)
catch
_:_ -> crash
end.
28 changes: 23 additions & 5 deletions test/typechecker_tests.erl
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ subtype_test_() ->
?_assert(subtype(?t( any() ), ?t( 1..10 ))),
?_assert(subtype(?t( 1..10 ), ?t( any() ))),

%% Term and none
?_assert(subtype(?t( sets:set() ), ?t( term() ))),
?_assert(subtype(?t( none() ), ?t( sets:set() ))),
%% remote types need gradualizer_db to be started
{setup,
fun setup_app/0,
fun cleanup_app/1,
[
%% Term and none
?_assert(subtype(?t( sets:set() ), ?t( term() ))),
?_assert(subtype(?t( none() ), ?t( sets:set() )))
]},

%% Integer
?_assert(subtype(?t( 1 ), ?t( 1 ))),
Expand Down Expand Up @@ -229,8 +235,8 @@ glb_test_() ->
%% Call to glb never returns
glb_issue_test_() ->
{setup,
fun gradualizer_cache:ensure_started/0,
fun(_) -> gradualizer_cache:stop() end,
fun setup_app/0,
fun cleanup_app/1,
[?_assertMatch(Result when is_tuple(Result),
glb( ?t([erl_parse:abstract_type()]),
{type,0,list,[{type,0,union,[{ann_type,0,[{var,891,'Name'},{user_type,[{file,"erl_parse.erl"},{location,0}],af_atom,[]}]},
Expand Down Expand Up @@ -534,6 +540,18 @@ undefined_records_test_() ->
"f() -> #r{f2 = 1}."]))
].

%%
%% Helper functions
%%

setup_app() ->
{ok, Apps} = application:ensure_all_started(gradualizer),
Apps.

cleanup_app(Apps) ->
[ok = application:stop(App) || App <- Apps],
ok.

subtype(T1, T2) ->
case typechecker:subtype(T1, T2, typechecker:create_tenv(?MODULE, [], [])) of
{true, _} ->
Expand Down

0 comments on commit 27ceb1d

Please sign in to comment.