Files
gsc/test/ts_utils.erl
T
Peter Harpending 3f73cd4d85 some stuff
2026-06-08 16:35:27 -07:00

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).