357 lines
7.5 KiB
Erlang
357 lines
7.5 KiB
Erlang
% test suite utilities
|
|
-module(ts_utils).
|
|
|
|
-export([
|
|
tokenizers_agree/1,
|
|
absify/1,
|
|
so_tokens/1,
|
|
load_test_deps/0,
|
|
test_deps/0,
|
|
load_dep/1,
|
|
clean_after/1, tidily/1,
|
|
delete_beams/0, tidy/0,
|
|
run_test_by_name/1,
|
|
rmm/1, run_mod_main/1,
|
|
runnable_test_names/0,
|
|
runnable_test_mods/0,
|
|
load_test_erls/0,
|
|
abspath_to_name/1,
|
|
ls_test_erls/0,
|
|
ls_test_beams/0,
|
|
is_erl/1,
|
|
is_beam/1,
|
|
ls_test/0,
|
|
test_dir/0,
|
|
ct_dir/0,
|
|
ct_file/1, ct_file_abspath/1, ct_abspath/1
|
|
]).
|
|
|
|
tokenizers_agree(Relpath) ->
|
|
FilePath = absify(Relpath),
|
|
% extracting data to be tested
|
|
% i hate this so much but lazy and this is test code so who really cares.
|
|
SoTokens = so_tokens(FilePath),
|
|
SfTokens = gsc:gso_tokens_from_file(FilePath),
|
|
case {SoTokens, SfTokens} of
|
|
{{ok, So}, {ok, Sf}} -> So =:= Sf;
|
|
{{error, _}, {error, _}} -> true;
|
|
{{ok, _}, {error, _}} -> false;
|
|
{{error, _}, {ok, _}} -> false
|
|
end.
|
|
|
|
|
|
absify(RelPath) ->
|
|
filename:absname(RelPath).
|
|
|
|
|
|
so_tokens(FilePath) ->
|
|
{ok, FileBytes} = file:read_file(FilePath),
|
|
FileStr = binary_to_list(FileBytes),
|
|
so_scan:scan(FileStr).
|
|
|
|
|
|
load_test_deps() ->
|
|
lists:foreach(fun load_dep/1, test_deps()).
|
|
|
|
test_deps() ->
|
|
[{"otpr", "sophia", {9, 0, 0}}].
|
|
|
|
|
|
load_dep(D) ->
|
|
{ok, Cwd} = file:get_cwd(),
|
|
% apparently zx changes the working dir when doing
|
|
% all this stuff so beam files get dropped in
|
|
% random dep dir
|
|
ok =
|
|
case zx_lib:installed(D) of
|
|
false ->
|
|
Id = zx_daemon:fetch(D),
|
|
ok = zx_daemon:wait_result(Id),
|
|
ok;
|
|
true ->
|
|
ok
|
|
end,
|
|
Result = zx_daemon:build(D),
|
|
ok = file:set_cwd(Cwd),
|
|
Result.
|
|
|
|
|
|
-spec clean_after(Fun) -> Result when
|
|
Fun :: fun(() -> Result),
|
|
Result :: any().
|
|
|
|
% @doc
|
|
% run Fun(), delete gsc/test/*.beam afterward even if
|
|
% Fun() errors
|
|
% @end
|
|
clean_after(Fun) ->
|
|
try
|
|
load_test_deps(),
|
|
Fun()
|
|
after
|
|
delete_beams()
|
|
end.
|
|
|
|
|
|
% @doc alias for `clean_after/1'
|
|
tidily(Fun) ->
|
|
clean_after(Fun).
|
|
|
|
|
|
|
|
-spec delete_beams() -> ok.
|
|
|
|
delete_beams() ->
|
|
Beams = ls_test_beams(),
|
|
%io:format("Deleting: ~tp~n", [Beams]),
|
|
lists:foreach(fun file:delete/1, Beams).
|
|
|
|
tidy() ->
|
|
delete_beams().
|
|
|
|
|
|
-spec run_test_by_name(Name) -> Result when
|
|
Name :: string(),
|
|
Result :: ok.
|
|
|
|
run_test_by_name(Name) when is_list(Name) ->
|
|
case find_test_by_name(Name) of
|
|
{good, Mod} ->
|
|
rmm(Mod);
|
|
{bad, Mod} ->
|
|
io:format("FATAL: Module ~tp didn't compile~n", [Mod]),
|
|
ok;
|
|
not_found ->
|
|
io:format("FATAL: test not found: ~p~n", [Name]),
|
|
ok
|
|
end.
|
|
|
|
|
|
run_mod_main(Mod) ->
|
|
rmm(Mod).
|
|
|
|
rmm(Mod) ->
|
|
try
|
|
io:format("=================================================~n"),
|
|
io:format("~p:main()~n", [Mod]),
|
|
io:format("=================================================~n"),
|
|
Mod:main()
|
|
catch
|
|
Cat:Err:Tr ->
|
|
io:format("~tp:main(): ERROR~n", [Mod]),
|
|
io:format("~tp: ~tp~n", [Cat, Err]),
|
|
io:format("Trace: ~tp~n", [Tr]),
|
|
ok
|
|
end.
|
|
|
|
|
|
|
|
find_test_by_name(Name) ->
|
|
C1 = list_to_atom(Name),
|
|
C2 = list_to_atom("gsc_test_" ++ Name),
|
|
{Gd, Bd} = runnable_test_mods(),
|
|
C1Gd = lists:member(C1, Gd),
|
|
C2Gd = lists:member(C2, Gd),
|
|
C1Bd = lists:member(C1, Gd),
|
|
C2Bd = lists:member(C2, Bd),
|
|
if
|
|
C1Gd -> {good, C1};
|
|
C2Gd -> {good, C2};
|
|
C1Bd -> {bad, C1};
|
|
C2Bd -> {bad, C2};
|
|
true -> not_found
|
|
end.
|
|
|
|
|
|
-spec runnable_test_names() -> Result when
|
|
Result :: [{string(), atom()}].
|
|
|
|
runnable_test_names() ->
|
|
{Gd, Bd} = runnable_test_mods(),
|
|
rtns([], lists:sort(Gd ++ Bd)).
|
|
|
|
rtns(Acc, []) ->
|
|
lists:reverse(Acc);
|
|
rtns(Acc, [TestMod | Rest]) ->
|
|
TestName = test_mod_name(TestMod),
|
|
rtns([{TestName, TestMod} | Acc], Rest).
|
|
|
|
test_mod_name(TestModAtom) ->
|
|
"gsc_test_" ++ Name = atom_to_list(TestModAtom),
|
|
Name.
|
|
|
|
|
|
|
|
-spec runnable_test_mods() -> Result when
|
|
Result :: {Good, Bad},
|
|
Good :: Mods,
|
|
Bad :: Mods,
|
|
Mods :: [atom()].
|
|
|
|
runnable_test_mods() ->
|
|
{Ld, Bds} = load_test_erls(),
|
|
Gd = lists:filter(fun is_runnable/1, Ld),
|
|
Bd = lists:filter(fun is_runnable/1, Bds),
|
|
{Gd, Bd}.
|
|
|
|
|
|
|
|
is_runnable(ModAtom) ->
|
|
case atom_to_list(ModAtom) of
|
|
"gsc_test_" ++ _ -> true;
|
|
_ -> false
|
|
end.
|
|
|
|
|
|
|
|
-spec load_test_erls() -> {Loaded, Errs} when
|
|
Loaded :: [atom()],
|
|
Errs :: [atom()].
|
|
|
|
load_test_erls() ->
|
|
ltes([], [], ls_test_erls()).
|
|
|
|
|
|
ltes(Ld, Errs, []) ->
|
|
{lists:reverse(Ld), lists:reverse(Errs)};
|
|
ltes(Ld, Errs, [FP | Rest]) ->
|
|
FN = abspath_to_name(FP),
|
|
ModAtom = fp_to_mod_atom(FP),
|
|
case compile:file(FP) of
|
|
{ok, Mod} ->
|
|
ltes([Mod | Ld], Errs, Rest);
|
|
Err ->
|
|
io:format("ERROR ~tp: ~tp~n", [FN, Err]),
|
|
ltes(Ld, [ModAtom | Errs], Rest)
|
|
end.
|
|
|
|
|
|
fp_to_mod_atom(FP) ->
|
|
FN = abspath_to_name(FP),
|
|
[ModStr, "erl"] = string:split(FN, ".", trailing),
|
|
list_to_atom(ModStr).
|
|
|
|
|
|
|
|
-spec abspath_to_name(FilePath) -> FileName when
|
|
FilePath :: string(),
|
|
FileName :: string().
|
|
% @doc "/path/to/foo.bar" -> "foo.bar"
|
|
|
|
abspath_to_name(FP) ->
|
|
lists:last(string:tokens(FP, "/")).
|
|
|
|
|
|
|
|
-spec ls_test_erls() -> AbsPaths when
|
|
AbsPaths :: [string()].
|
|
% @doc ["/path/to/gsc/test/foo.erl",
|
|
% "/path/to/gsc/test/bar.erl",
|
|
% "/path/to/gsc/test/baz.erl"]
|
|
|
|
ls_test_erls() ->
|
|
lists:filter(fun is_erl/1, ls_test()).
|
|
|
|
|
|
|
|
-spec ls_test_beams() -> AbsPaths when
|
|
AbsPaths :: [string()].
|
|
|
|
% important: beams get dropped in working dir
|
|
ls_test_beams() ->
|
|
lists:filter(fun is_beam/1, ls_pwd()).
|
|
|
|
|
|
|
|
-spec is_beam(AbsPath) -> IsBeam when
|
|
AbsPath :: string(),
|
|
IsBeam :: boolean().
|
|
|
|
% @private
|
|
% "foo.beam" ~> true
|
|
% _ ~> false
|
|
is_beam(Filename) ->
|
|
case filename:extension(Filename) of
|
|
".beam" -> true;
|
|
_ -> false
|
|
end.
|
|
|
|
|
|
|
|
-spec is_erl(AbsPath) -> IsErl when
|
|
AbsPath :: string(),
|
|
IsErl :: boolean().
|
|
% @private
|
|
% "foo.erl" ~> true
|
|
% _ ~> false
|
|
|
|
is_erl(Filename) ->
|
|
case filename:extension(Filename) of
|
|
".erl" -> true;
|
|
_ -> false
|
|
end.
|
|
|
|
|
|
|
|
-spec ls_test() -> Abspaths when
|
|
Abspaths :: [string()].
|
|
% @doc
|
|
% Includes junk/irrelevant files:
|
|
%
|
|
% ["/path/to/gsc/test/foo.erl",
|
|
% "/path/to/gsc/test/.foo.erl.swp",
|
|
% "/path/to/gsc/test/bar.erl"]
|
|
|
|
ls_test() ->
|
|
TD = test_dir(),
|
|
{ok, Names} = file:list_dir(TD),
|
|
lists:sort([TD ++ "/" ++ Name || Name <- Names]).
|
|
|
|
|
|
ls_pwd() ->
|
|
{ok, D} = file:get_cwd(),
|
|
{ok, Ns} = file:list_dir(D),
|
|
lists:sort([D ++ "/" ++ N || N <- Ns]).
|
|
|
|
|
|
-spec test_dir() -> AbsPath when
|
|
AbsPath :: string().
|
|
% @doc "/path/to/gsc/test"
|
|
|
|
test_dir() ->
|
|
zx_daemon:get_home() ++ "/test".
|
|
|
|
|
|
|
|
-spec ct_dir() -> AbsPath when
|
|
AbsPath :: string().
|
|
|
|
% @doc "/path/to/gsc/test/ct"
|
|
%
|
|
% directory containing the tests for the tokenizer
|
|
ct_dir() ->
|
|
test_dir() ++ "/ct".
|
|
|
|
|
|
|
|
|
|
-spec ct_file(Name) -> AbsPath when
|
|
Name :: string(),
|
|
AbsPath :: string().
|
|
% @doc
|
|
% "foo.aes" -> "/path/to/ct/foo.aes"
|
|
|
|
ct_file(Name) ->
|
|
ct_dir() ++ "/" ++ Name.
|
|
|
|
|
|
% @doc alias for `ct_file/1'
|
|
%
|
|
% "foo.aes" -> "/path/to/ct/foo.aes"
|
|
ct_file_abspath(Name) -> ct_file(Name).
|
|
|
|
% @doc alias for `ct_file/1'
|
|
%
|
|
% "foo.aes" -> "/path/to/ct/foo.aes"
|
|
ct_abspath(Name) -> ct_file(Name).
|