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