diff --git a/Makefile b/Makefile index 8485ff54..bd4ed1b0 100644 --- a/Makefile +++ b/Makefile @@ -149,6 +149,9 @@ eunit: compile-tests erl $(ERL_OPTS) -noinput -pa ebin -pa test -eval \ '$(erl_run_eunit), halt().' +ct: + @rebar3 ct --label "git: $$(git describe --tags --always) $$(git diff --no-ext-diff --quiet --exit-code || echo '(modified)')" + cli-tests: bin/gradualizer test/arg.beam # CLI test cases # 1. When checking a dir with erl files, erl file names are printed diff --git a/rebar.config b/rebar.config index 155bd7cc..0de6c2cb 100644 --- a/rebar.config +++ b/rebar.config @@ -4,10 +4,7 @@ {deps, [ {proper, {git, "https://github.com/proper-testing/proper.git", {branch, "master"}}} - ]}, - %% see the maybe expression fail; - %% the VM also needs to be configured to load the module - {erl_opts, [{feature,maybe_expr,enable}]} + ]} ]} ]}. diff --git a/src/gradualizer_cache.erl b/src/gradualizer_cache.erl index b57f5e88..4a2cbb9a 100644 --- a/src/gradualizer_cache.erl +++ b/src/gradualizer_cache.erl @@ -9,6 +9,7 @@ %% API -export([start_link/1, + clear/1, get/2, store/3]). @@ -66,6 +67,13 @@ store_(Cache, Key, Value) -> ok end. +clear(glb) -> clear_(?GLB_CACHE); +clear(subtype) -> clear_(?SUB_CACHE). + +clear_(Cache) -> + ets:delete_all_objects(Cache), + ok. + %%=================================================================== %% gen_server callbacks %%=================================================================== diff --git a/src/typechecker.erl b/src/typechecker.erl index 9e9337c6..efa7d00f 100644 --- a/src/typechecker.erl +++ b/src/typechecker.erl @@ -361,11 +361,23 @@ compat_ty({type, _, 'fun', [{type, _, product, Args1}, Res1]}, {Aps, constraints:combine(Cs, Css, Env)}; %% Unions -compat_ty({type, _, union, Tys1}, {type, _, union, Tys2}, Seen, Env) -> - lists:foldl(fun (Ty1, {Seen1, C1}) -> - {Seen2, C2} = any_type(Ty1, Tys2, Seen1, Env), - {Seen2, constraints:combine(C1, C2, Env)} - end, {Seen, constraints:empty()}, Tys1); +compat_ty({type, _, union, Tys1} = U1, {type, _, union, Tys2} = U2, Seen, Env) -> + IsAny = fun + (?type(any)) -> true; + (_) -> false + end, + case lists:any(IsAny, Tys1) of + true -> ret(Seen); + false -> + case lists:any(IsAny, Tys2) of + true -> ret(Seen); + false -> + case type_diff(U1, U2, Env) of + ?type(none) -> ret(Seen); + _ -> throw(nomatch) + end + end + end; compat_ty(Ty1, {type, _, union, Tys2}, Seen, Env) -> any_type(Ty1, Tys2, Seen, Env); compat_ty({type, _, union, Tys1}, Ty2, Seen, Env) -> @@ -5746,8 +5758,10 @@ type_check_forms(Forms, Opts) -> %% a Gradualizer (NOT the checked program!) error. -spec type_check_form_with_timeout(expr(), [any()], boolean(), env(), [any()]) -> [any()]. type_check_form_with_timeout(Function, Errors, StopOnFirstError, Env, Opts) -> - %% TODO: make FormCheckTimeOut configurable - FormCheckTimeOut = ?form_check_timeout_ms, + FormCheckTimeOut = case lists:keyfind(form_check_timeout_ms, 1, Opts) of + false -> ?form_check_timeout_ms; + {form_check_timeout_ms, MS} -> MS + end, ?verbose(Env, "Spawning async task...~n", []), Self = self(), Task = fun () -> diff --git a/test/gradualizer_dynamic_suite.erl b/test/gradualizer_dynamic_suite.erl new file mode 100644 index 00000000..b002d613 --- /dev/null +++ b/test/gradualizer_dynamic_suite.erl @@ -0,0 +1,50 @@ +-module(gradualizer_dynamic_suite). + +-export([reload/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("stdlib/include/assert.hrl"). + +reload(Config) -> + Module = ?config(dynamic_suite_module, Config), + Path = ?config(dynamic_suite_test_path, Config), + ?assert(Module /= undefined), + ?assert(Path /= undefined), + Forms = get_forms(Module), + FilesForms = map_erl_files(fun (File) -> + make_test_form(Forms, File, Config) + end, Path), + {TestFiles, TestForms} = lists:unzip(FilesForms), + TestNames = [ list_to_atom(filename:basename(File, ".erl")) || File <- TestFiles ], + ct:pal("All tests found under ~s:\n~p\n", [Path, TestNames]), + NewForms = Forms ++ TestForms ++ [{eof, 0}], + {ok, _} = merl:compile_and_load(NewForms), + {ok, TestNames}. + +map_erl_files(Fun, Dir) -> + Files = filelib:wildcard(filename:join(Dir, "*.erl")), + [{filename:basename(File), Fun(File)} || File <- Files]. + +make_test_form(Forms, File, Config) -> + TestTemplateName = ?config(dynamic_test_template, Config), + ?assert(TestTemplateName /= undefined), + TestTemplate = merl:quote("'@Name'(_) -> _@Body."), + {function, _Anno, _Name, 1, Clauses} = lists:keyfind(TestTemplateName, 3, Forms), + [{clause, _, _Args, _Guards, ClauseBodyTemplate}] = Clauses, + TestName = filename:basename(File, ".erl"), + ClauseBody = merl:subst(ClauseBodyTemplate, [{'File', erl_syntax:string(File)}]), + TestEnv = [ + {'Name', erl_syntax:atom(TestName)}, + {'Body', ClauseBody} + ], + erl_syntax:revert(merl:subst(TestTemplate, TestEnv)). + +get_forms(Module) -> + ModPath = code:which(Module), + {ok, {Module, [Abst]}} = beam_lib:chunks(ModPath, [abstract_code]), + {abstract_code, {raw_abstract_v1, Forms}} = Abst, + StripEnd = fun + ({eof, _}) -> false; + (_) -> true + end, + lists:filter(StripEnd, Forms). diff --git a/test/known_problems/should_fail/poly_should_fail.erl b/test/known_problems/should_fail/poly_should_fail.erl index 781f22b0..d28be89f 100644 --- a/test/known_problems/should_fail/poly_should_fail.erl +++ b/test/known_problems/should_fail/poly_should_fail.erl @@ -8,12 +8,10 @@ id_id_atom_is_int/1, id_fun_id_atom_is_int/1, use_flatten/1, - use_maps_get/3, use_generic_hd/1, use_generic_hd_var/1, inference1/1, - inference2/1, - invariant_tyvar/2 + inference2/1 ]). -spec id(A) -> A. @@ -56,11 +54,6 @@ pass_id_to_takes_int_to_bool_fun() -> use_flatten(ListOfListsOfAtoms) -> lists:flatten(ListOfListsOfAtoms). -%% Type variables in maps usually result in any(). --spec use_maps_get(atom(), #{atom() => binary()}, not_found) -> float() | not_found. -use_maps_get(Key, Map, NotFound) -> - maps:get(Key, Map, NotFound). - %% We do not support polymorphic intersection functions yet. %% When calling intersection functions, type variables are replaced with any(). @@ -88,20 +81,3 @@ inference1(L) -> -spec inference2([integer()]) -> [atom()]. inference2(L) -> lists:map(fun (I) -> I * 2 end, L). - -%% The type variable `A` in `id_fun_arg/2` is invariant in its result type. -%% Thus, if there are multiple possible substitutions, none of them is minimal. -%% In this case we choose `A = the_lower_bound_of_A | any()' which is a bit -%% lenient in some cases, as shown in invariant_tyvar/2. Hopefully, invariant -%% type variables are very rare. - --spec id_fun_arg(fun ((A) -> B), A) -> {fun ((A) -> B), A}. -id_fun_arg(Fun, Arg) -> {Fun, Arg}. - --spec positive(number()) -> boolean(). -positive(N) -> N > 0. - --spec invariant_tyvar(integer(), boolean()) -> any(). -invariant_tyvar(Int, Bool) -> - {Fun, _Arg} = id_fun_arg(fun positive/1, Int), - Fun(Bool). diff --git a/test/known_problems_should_fail_SUITE.erl b/test/known_problems_should_fail_SUITE.erl new file mode 100644 index 00000000..c65211b9 --- /dev/null +++ b/test/known_problems_should_fail_SUITE.erl @@ -0,0 +1,90 @@ +-module(known_problems_should_fail_SUITE). + +-compile([export_all, nowarn_export_all]). + +%% EUnit has some handy macros, so let's use it, too +-include_lib("eunit/include/eunit.hrl"). + +%% Test server callbacks +-export([suite/0, + all/0, + groups/0, + init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2]). + +suite() -> + [{timetrap, {minutes, 10}}]. + +init_per_suite(Config0) -> + AppBase = code:lib_dir(gradualizer), + Config = [ + {dynamic_suite_module, ?MODULE}, + {dynamic_suite_test_path, filename:join(AppBase, "test/known_problems/should_fail")}, + {dynamic_test_template, known_problems_should_fail_template} + ] ++ Config0, + {ok, _} = application:ensure_all_started(gradualizer), + ok = load_prerequisites(AppBase), + {ok, TestNames} = gradualizer_dynamic_suite:reload(Config), + case all_tests() of + TestNames -> ok; + _ -> ct:fail("Please update all_tests/0 to list all tests") + end, + Config. + +load_prerequisites(AppBase) -> + %% exhaustive_user_type.erl is referenced by exhaustive_remote_user_type.erl + gradualizer_db:import_erl_files([filename:join(AppBase, "test/should_fail/exhaustive_user_type.erl")]), + ok. + +end_per_suite(_Config) -> + ok = application:stop(gradualizer), + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +all() -> + [{group, all_tests}]. + +groups() -> + [{all_tests, [parallel], all_tests()}]. + +all_tests() -> + [arith_op,binary_comprehension,case_pattern_should_fail, + exhaustive_argumentwise,exhaustive_expr,exhaustive_map_variants, + exhaustive_remote_map_variants,guard_should_fail,infer_any_pattern, + intersection_with_any_should_fail,intersection_with_unreachable, + lambda_wrong_args,map_refinement_fancy,poly_lists_map_should_fail, + poly_should_fail,recursive_types_should_fail,refine_ty_vars,sample]. + +known_problems_should_fail_template(_@File) -> + Result = safe_type_check_file(_@File, [return_errors]), + case Result of + crash -> + ok; + Errors -> + ErrorsExceptTimeouts = lists:filter( + fun ({_File, {form_check_timeout, _}}) -> false; (_) -> true end, + Errors), + ?assertEqual(0, length(ErrorsExceptTimeouts)) + end. + +safe_type_check_file(File) -> + safe_type_check_file(File, []). + +safe_type_check_file(File, Opts) -> + try + gradualizer:type_check_file(File, Opts) + catch + _:_ -> crash + end. diff --git a/test/known_problems_should_pass_SUITE.erl b/test/known_problems_should_pass_SUITE.erl new file mode 100644 index 00000000..41ec7515 --- /dev/null +++ b/test/known_problems_should_pass_SUITE.erl @@ -0,0 +1,85 @@ +-module(known_problems_should_pass_SUITE). + +-compile([export_all, nowarn_export_all]). + +%% EUnit has some handy macros, so let's use it, too +-include_lib("eunit/include/eunit.hrl"). + +%% Test server callbacks +-export([suite/0, + all/0, + groups/0, + init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2]). + +suite() -> + [{timetrap, {minutes, 10}}]. + +init_per_suite(Config0) -> + AppBase = code:lib_dir(gradualizer), + Config = [ + {dynamic_suite_module, ?MODULE}, + {dynamic_suite_test_path, filename:join(AppBase, "test/known_problems/should_pass")}, + {dynamic_test_template, known_problems_should_pass_template} + ] ++ Config0, + {ok, _} = application:ensure_all_started(gradualizer), + ok = load_prerequisites(AppBase), + {ok, TestNames} = gradualizer_dynamic_suite:reload(Config), + case all_tests() of + TestNames -> ok; + _ -> ct:fail("Please update all_tests/0 to list all tests") + end, + Config. + +load_prerequisites(_AppBase) -> + ok. + +end_per_suite(_Config) -> + ok = application:stop(gradualizer), + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +all() -> + [{group, all_tests}]. + +groups() -> + [{all_tests, [parallel], all_tests()}]. + +all_tests() -> + [arith_op_arg_types,binary_exhaustiveness_checking_should_pass, + call_intersection_function_with_union_arg_should_pass,elixir_list_first, + error_in_guard,fun_subtyping,generator_var_shadow, + inner_union_subtype_of_root_union,intersection_should_pass, + intersection_with_any,list_concat_op_should_pass,list_tail, + map_pattern_duplicate_key,maybe_expr,poly_should_pass,poly_type_vars, + recursive_types,refine_bound_var_on_mismatch, + refine_bound_var_with_guard_should_pass,refine_comparison_should_pass, + refine_list_tail,union_fun]. + +known_problems_should_pass_template(_@File) -> + {ok, Forms} = gradualizer_file_utils:get_forms_from_erl(_@File, []), + ExpectedErrors = typechecker:number_of_exported_functions(Forms), + ReturnedErrors = length(safe_type_check_file(_@File, [return_errors])), + ?assertEqual(ExpectedErrors, ReturnedErrors). + +safe_type_check_file(File) -> + safe_type_check_file(File, []). + +safe_type_check_file(File, Opts) -> + try + gradualizer:type_check_file(File, Opts) + catch + _:_ -> crash + end. diff --git a/test/should_fail/poly_fail.erl b/test/should_fail/poly_fail.erl index b21b5143..24937efa 100644 --- a/test/should_fail/poly_fail.erl +++ b/test/should_fail/poly_fail.erl @@ -13,6 +13,7 @@ use_app_var/1, use_id/1, use_id_var/1, + use_maps_get/3, filter_positive_ints/1, filter_positive_concrete_numbers/1, append_floats_to_ints/2, @@ -34,7 +35,8 @@ use_enum_map1/1, use_enum_map1_var/1, use_enum_map2/1, - use_enum_map3/1 + use_enum_map3/1, + invariant_tyvar2/2 ]). -gradualizer([solve_constraints]). @@ -110,6 +112,11 @@ use_id_var(Atom) -> X = id(Atom), X. +%% Type variables in maps usually result in any(). +-spec use_maps_get(atom(), #{atom() => binary()}, not_found) -> float() | not_found. +use_maps_get(Key, Map, NotFound) -> + maps:get(Key, Map, NotFound). + -spec positive(number()) -> boolean(). positive(N) -> N > 0. @@ -239,3 +246,14 @@ use_enum_map2(Atoms) -> -spec use_enum_map3(#{'__struct__' := some_struct}) -> [float()]. use_enum_map3(SomeStruct) -> enum_map(SomeStruct, fun positive/1). + +%% The type variable `A` in `id_fun_arg/2` is invariant in its result type. +%% Thus, if there are multiple possible substitutions, none of them is minimal. +%% In this case we choose `A = the_lower_bound_of_A | any()' which is a bit +%% lenient in some cases, as shown in invariant_tyvar2/2. Hopefully, invariant +%% type variables are very rare. + +-spec invariant_tyvar2(integer(), boolean()) -> any(). +invariant_tyvar2(Int, Bool) -> + {Fun, _Arg} = id_fun_arg(fun positive/1, Int), + Fun(Bool). diff --git a/test/should_fail/union_with_any.erl b/test/should_fail/union_with_any.erl index d9517587..6ddf23b2 100644 --- a/test/should_fail/union_with_any.erl +++ b/test/should_fail/union_with_any.erl @@ -3,17 +3,15 @@ %% T | any() means %% "at least values of type T are possible; maybe also other values" --export([f1/1, f2/0, f3/1]). +-export([f2/0, f3/1]). -%% With spec --spec f1(atom() | any()) -> any(). -f1(X) -> inc(X). +-gradualizer([infer]). %% Without spec f2() -> AtomOrAny = receive - 1 -> get_atom(); - Any -> Any + 1 -> get_atom(); + an_atom -> an_atom end, inc(AtomOrAny). %% Fails because atom() is possible diff --git a/test/should_fail_SUITE.erl b/test/should_fail_SUITE.erl new file mode 100644 index 00000000..95011f1b --- /dev/null +++ b/test/should_fail_SUITE.erl @@ -0,0 +1,111 @@ +-module(should_fail_SUITE). + +-compile([export_all, nowarn_export_all]). + +%% EUnit has some handy macros, so let's use it, too +-include_lib("eunit/include/eunit.hrl"). + +%% Test server callbacks +-export([suite/0, + all/0, + groups/0, + init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2]). + +suite() -> + [{timetrap, {minutes, 10}}]. + +init_per_suite(Config0) -> + AppBase = code:lib_dir(gradualizer), + Config = [ + {dynamic_suite_module, ?MODULE}, + {dynamic_suite_test_path, filename:join(AppBase, "test/should_fail")}, + {dynamic_test_template, should_fail_template} + ] ++ Config0, + {ok, _} = application:ensure_all_started(gradualizer), + ok = load_prerequisites(AppBase), + {ok, TestNames} = gradualizer_dynamic_suite:reload(Config), + case all_tests() of + TestNames -> ok; + _ -> ct:fail("Please update all_tests/0 to list all tests") + end, + Config. + +load_prerequisites(AppBase) -> + %% user_types.erl is referenced by opaque_fail.erl. + %% It is not in the sourcemap of the DB so let's import it manually + gradualizer_db:import_erl_files([filename:join(AppBase, "test/should_pass/user_types.erl")]), + %% exhaustive_user_type.erl is referenced by exhaustive_remote_user_type.erl + gradualizer_db:import_erl_files([filename:join(AppBase, "test/should_fail/exhaustive_user_type.erl")]), + ok. + +end_per_suite(_Config) -> + ok = application:stop(gradualizer), + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +all() -> + [{group, all_tests}]. + +groups() -> + [{all_tests, [parallel], all_tests()}]. + +all_tests() -> + [annotated_types_fail,arg,arith_op_fail,arity_mismatch, + bc_fail,bin_expression,bin_type_error,branch,branch2,call, + call_intersection_function_with_union_arg_fail,case_pattern, + case_pattern2,catch_expr_fail,cons,covariant_map_keys_fail, + cyclic_type_vars,depth,exhaustive,exhaustive_float, + exhaustive_list_variants,exhaustive_refinable_map_variants, + exhaustive_remote_user_type,exhaustive_string_variants, + exhaustive_type,exhaustive_user_type, + exhaustiveness_check_toggling,generator,guard_fail, + imported_undef,infer_enabled,intersection_check, + intersection_fail,intersection_infer, + intersection_with_any_fail,iodata_fail,lambda_not_fun, + lc_generator_not_none_fail,lc_not_list,list_infer_fail, + list_op,list_op_should_fail,list_union_fail, + lists_map_nonempty_fail,literal_char,literal_patterns, + logic_op,map_entry,map_fail,map_failing_expr, + map_failing_subtyping,map_field_invalid_update,map_literal, + map_pattern_fail,map_refinement_fail,map_type_error,match, + messaging_fail,module_info_fail,named_fun_fail, + named_fun_infer_fail,nil,no_idempotent_xor, + non_neg_plus_pos_is_pos_fail, + nonempty_list_match_in_head_nonexhaustive, + nonempty_string_fail,opaque_fail,operator_pattern_fail, + pattern,pattern_record_fail,poly_fail,poly_lists_map_fail, + poly_union_lower_bound_fail,pp_intersection,record, + record_exhaustive,record_field,record_index, + record_info_fail,record_refinement_fail,record_update, + record_wildcard_fail,recursive_type_fail, + recursive_types_failing,rel_op,return_fun_fail, + rigid_type_variables_fail,send_fail,shortcut_ops_fail, + spec_and_fun_clause_intersection_fail,string_literal, + tuple_union_arg_fail,tuple_union_fail,tuple_union_pattern, + tuple_union_refinement,type_refinement_fail,unary_op, + unary_plus_fail,union_with_any,unreachable_after_refinement]. + +should_fail_template(_@File) -> + Errors = gradualizer:type_check_file(_@File, [return_errors]), + Timeouts = [ E || {_File, {form_check_timeout, _}} = E <- Errors], + ?assertEqual(0, length(Timeouts)), + %% Test that error formatting doesn't crash + Opts = [{fmt_location, brief}, + {fmt_expr_fun, fun erl_prettypr:format/1}], + lists:foreach(fun({_, Error}) -> gradualizer_fmt:handle_type_error(Error, Opts) end, Errors), + {ok, Forms} = gradualizer_file_utils:get_forms_from_erl(_@File, []), + ExpectedErrors = typechecker:number_of_exported_functions(Forms), + ?assertEqual(ExpectedErrors, length(Errors)). diff --git a/test/known_problems/should_pass/different_normalization_levels.erl b/test/should_pass/different_normalization_levels.erl similarity index 64% rename from test/known_problems/should_pass/different_normalization_levels.erl rename to test/should_pass/different_normalization_levels.erl index 1b44231f..7266dc60 100644 --- a/test/known_problems/should_pass/different_normalization_levels.erl +++ b/test/should_pass/different_normalization_levels.erl @@ -5,13 +5,13 @@ -type t() :: a | b | c. %% The problem is that the argument type stays the same (as top-level normalization -%% does not expand it) but the result type gets normalized to `a | b | c', -%% and `t() | a' is not a subtype of `a | b | c' because it currently checks whether -%% `t()' is a subtype of one of `a' or `b', or `c' (which it isn't). +%% does not expand it) but the result type gets normalized to `a | b | c'. +%% `t() | a' used not to be a subtype of `a | b | c' because it used to check whether +%% `t()' was a subtype of one of `a' or `b', or `c' (which it wasn't). %% It surfaced because, for instance, %% type() | any() -%% is not a subtype of +%% was not a subtype of %% type() -spec f(t() | a) -> t(). diff --git a/test/should_pass/module_info.erl b/test/should_pass/module_info_pass.erl similarity index 92% rename from test/should_pass/module_info.erl rename to test/should_pass/module_info_pass.erl index d4d1c028..8e781741 100644 --- a/test/should_pass/module_info.erl +++ b/test/should_pass/module_info_pass.erl @@ -1,4 +1,4 @@ --module(module_info). +-module(module_info_pass). -compile([export_all, nowarn_export_all]). @@ -18,4 +18,4 @@ unary_direct() -> -spec unary_var() -> atom(). unary_var() -> I = erlang:module_info(module), - I. \ No newline at end of file + I. diff --git a/test/should_pass/union_with_any_pass.erl b/test/should_pass/union_with_any_pass.erl new file mode 100644 index 00000000..0592db69 --- /dev/null +++ b/test/should_pass/union_with_any_pass.erl @@ -0,0 +1,21 @@ +-module(union_with_any_pass). + +-export([f1/1, f2/0]). + +%% With spec +-spec f1(atom() | any()) -> any(). +f1(X) -> inc(X). + +%% Without spec +f2() -> + AtomOrAny = receive + 1 -> get_atom(); + Any -> Any + end, + inc(AtomOrAny). %% Fails because atom() is possible + +-spec get_atom() -> atom(). +get_atom() -> banana. + +-spec inc(number()) -> number(). +inc(N) -> N + 1. diff --git a/test/should_pass_SUITE.erl b/test/should_pass_SUITE.erl new file mode 100644 index 00000000..b208df5c --- /dev/null +++ b/test/should_pass_SUITE.erl @@ -0,0 +1,108 @@ +-module(should_pass_SUITE). + +-compile([export_all, nowarn_export_all]). + +%% EUnit has some handy macros, so let's use it, too +-include_lib("eunit/include/eunit.hrl"). + +%% Test server callbacks +-export([suite/0, + all/0, + groups/0, + init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2, + init_per_testcase/2, end_per_testcase/2]). + +suite() -> + [{timetrap, {minutes, 10}}]. + +init_per_suite(Config0) -> + AppBase = code:lib_dir(gradualizer), + Config = [ + {dynamic_suite_module, ?MODULE}, + {dynamic_suite_test_path, filename:join(AppBase, "test/should_pass")}, + {dynamic_test_template, should_pass_template} + ] ++ Config0, + {ok, _} = application:ensure_all_started(gradualizer), + ok = load_prerequisites(AppBase), + {ok, TestNames} = gradualizer_dynamic_suite:reload(Config), + case all_tests() of + TestNames -> ok; + _ -> ct:fail("Please update all_tests/0 to list all tests") + end, + Config. + +load_prerequisites(AppBase) -> + %% user_types.erl is referenced by remote_types.erl and opaque.erl. + %% It is not in the sourcemap of the DB so let's import it manually + gradualizer_db:import_erl_files([filename:join(AppBase, "test/should_pass/user_types.erl")]), + gradualizer_db:import_erl_files([filename:join(AppBase, "test/should_pass/other_module.erl")]), + %% imported.erl references any.erl + gradualizer_db:import_erl_files([filename:join(AppBase, "test/should_pass/any.erl")]), + ok. + +end_per_suite(_Config) -> + ok = application:stop(gradualizer), + ok. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, _Config) -> + ok. + +init_per_testcase(_TestCase, Config) -> + Config. + +end_per_testcase(_TestCase, _Config) -> + ok. + +all() -> + [{group, all_tests}]. + +groups() -> + [{all_tests, [parallel], all_tests()}]. + +all_tests() -> + [alias_in_pattern,andalso_any,ann_types,annotated_types,any, + any_doesnt_have_type_none_pass,any_pattern,bc_pass, + binary_exhaustiveness_checking,binary_in_union,binary_literal_pattern, + bitstring,block_scope,bool,bounded_funs, + call_intersection_function_with_union_arg_pass,'case', + case_of_record_with_user_defined,catch_expr_pass,covariant_map_keys_pass, + cyclic_otp_specs,different_normalization_levels,erlang_error_args_none_pass, + exhaustiveness_union_types,factorial,float,flow,fun_capture,fun_spec,guard, + guard_sequences_pass,if_expr,imported,int,intersection_pass, + intersection_with_any_pass,iodata,issue131,lc,lc_generator_not_none, + lc_var_binds_in_filters,list,list_concat_op_pass, + list_exhaustiveness_checking_regressions, + list_exhaustiveness_checking_regressions2, + list_exhaustiveness_checking_unreachable_clause_regression,list_infer_pass, + list_op_pass,listsspecs,map,map_as_argument_update,map_creation, + map_field_valid_update,map_infer_pass,map_passing_expr,map_passing_subtyping, + map_pattern,map_refinement,map_update,map_update_with_record_field, + messaging_pass,minimised_gradualizer_fmt,minus,module_info_higher_arity, + module_info_pass,named_fun_infer_pass,named_fun_pass,negate_none, + nested_pattern_match,non_neg_plus_pos_is_pos_pass,nonempty_cons, + nonempty_list_match_in_head_exhaustive,nonempty_string, + nonexhaustive_record_pattern,opaque,operator_pattern_pass,operator_subtypes, + other_module,pattern_bind_reuse,pattern_record,pattern_with_ty_vars, + poly_lists_map_constraints_pass,poly_lists_map_pass,poly_map_pattern, + poly_pass,poly_pass_infer,poly_pass_no_solve_constraints, + poly_union_lower_bound_pass,preludes,qlc_test,record_info,record_refinement, + record_union_pass,record_union_with_any_should_pass,record_var, + record_wildcard_pass,record_with_user_defined,records, + recursive_call_with_remote_union_return_type_pass,recursive_types_passing, + refine_comparison,refine_mismatch_using_guard_bifs,remote_types, + remote_types_pass,return_fun,rigid_type_variables,rigid_type_variables_pass, + scope,send_pass,sets_set,shortcut_ops_pass, + spec_and_fun_clause_intersection_pass,stuff_as_top,'try',try_expr,tuple, + tuple_union_pass,tuple_union_pat,tuple_union_pattern_pass,type_decl, + type_pattern,type_refinement_pass,type_variable,type_vars_term, + typed_record_field_access,unary_negate_union_with_user_type_pass,unary_plus, + underscore,union_with_any_pass,user_type_in_pattern_body,user_types,var, + var_fun,varbind_in_block,varbind_in_case,varbind_in_function_head, + varbind_in_lc,variable_binding,variable_binding_leaks]. + +should_pass_template(_@File) -> + ?assertEqual(ok, gradualizer:type_check_file(_@File, [{form_check_timeout_ms, 2000}])).