erlydtl_functional_tests.erl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. %%%-------------------------------------------------------------------
  2. %%% File: erlydtl_tests.erl
  3. %%% @author Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
  4. %%% @author Evan Miller <emmiller@gmail.com>
  5. %%% @copyright 2008 Roberto Saccon, Evan Miller
  6. %%% @doc ErlyDTL test suite
  7. %%% @end
  8. %%%
  9. %%% The MIT License
  10. %%%
  11. %%% Copyright (c) 2007 Roberto Saccon
  12. %%%
  13. %%% Permission is hereby granted, free of charge, to any person obtaining a copy
  14. %%% of this software and associated documentation files (the "Software"), to deal
  15. %%% in the Software without restriction, including without limitation the rights
  16. %%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  17. %%% copies of the Software, and to permit persons to whom the Software is
  18. %%% furnished to do so, subject to the following conditions:
  19. %%%
  20. %%% The above copyright notice and this permission notice shall be included in
  21. %%% all copies or substantial portions of the Software.
  22. %%%
  23. %%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24. %%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25. %%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26. %%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27. %%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  28. %%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  29. %%% THE SOFTWARE.
  30. %%%
  31. %%% @since 2008-02-11 by Roberto Saccon
  32. %%%-------------------------------------------------------------------
  33. -module(erlydtl_functional_tests).
  34. -author('rsaccon@gmail.com').
  35. -author('emmiller@gmail.com').
  36. %% API
  37. -export([run_tests/0, run_test/1]).
  38. test_list() ->
  39. %% order is important.
  40. ["autoescape", "comment", "extends", "filters", "for", "for_list",
  41. "for_tuple", "for_list_preset", "for_preset", "for_records",
  42. "for_records_preset", "include", "if", "if_preset", "ifequal",
  43. "ifequal_preset", "ifnotequal", "ifnotequal_preset", "now",
  44. "var", "var_preset", "cycle", "custom_tag", "custom_tag1",
  45. "custom_tag2", "custom_tag3", "custom_tag4", "custom_call",
  46. "include_template", "include_path", "ssi", "extends_path",
  47. "extends_path2", "trans", "extends2", "extends3",
  48. "recursive_block", "extend_recursive_block"
  49. ].
  50. setup_compile("for_list_preset") ->
  51. CompileVars = [{fruit_list, [["apple", "apples"], ["banana", "bananas"], ["coconut", "coconuts"]]}],
  52. {ok, CompileVars};
  53. setup_compile("for_preset") ->
  54. CompileVars = [{fruit_list, ["preset-apple", "preset-banana", "preset-coconut"]}],
  55. {ok, CompileVars};
  56. setup_compile("for_records_preset") ->
  57. Link1a = [{name, "Amazon (preset)"}, {url, "http://amazon.com"}],
  58. Link2a = [{name, "Google (preset)"}, {url, "http://google.com"}],
  59. Link3a = [{name, "Microsoft (preset)"}, {url, "http://microsoft.com"}],
  60. CompileVars = [{software_links, [Link1a, Link2a, Link3a]}],
  61. {ok, CompileVars};
  62. setup_compile("if_preset") ->
  63. CompileVars = [{var1, "something"}],
  64. {ok, CompileVars};
  65. setup_compile("ifequal_preset") ->
  66. CompileVars = [{var1, "foo"}, {var2, "foo"}],
  67. {ok, CompileVars};
  68. setup_compile("ifnotequal_preset") ->
  69. CompileVars = [{var1, "foo"}, {var2, "foo"}],
  70. {ok, CompileVars};
  71. setup_compile("var_preset") ->
  72. CompileVars = [{preset_var1, "preset-var1"}, {preset_var2, "preset-var2"}],
  73. {ok, CompileVars};
  74. setup_compile("extends2") ->
  75. File = templates_dir("input/extends2"),
  76. Error = {none, erlydtl_compiler, unexpected_extends_tag},
  77. {{error, [{File, [Error]}], []}, []};
  78. setup_compile("extends3") ->
  79. File = templates_dir("input/extends3"),
  80. Include = templates_dir("input/imaginary"),
  81. Error = {none, erlydtl_compiler, {read_file, Include, enoent}},
  82. {{error, [{File, [Error]}], []}, []};
  83. setup_compile(_) ->
  84. {ok, []}.
  85. %% @spec (Name::string()) -> {CompileStatus::atom(), PresetVars::list(),
  86. %% RenderStatus::atom(), RenderVars::list()} | skip
  87. %% @doc
  88. %% @end
  89. %%--------------------------------------------------------------------
  90. setup("autoescape") ->
  91. RenderVars = [{var1, "<b>bold</b>"}],
  92. {ok, RenderVars};
  93. setup("extends") ->
  94. RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
  95. {ok, RenderVars};
  96. setup("filters") ->
  97. RenderVars = [
  98. {date_var1, {1975,7,24}},
  99. {datetime_var1, {{1975,7,24}, {7,13,1}}},
  100. {'list', ["eins", "zwei", "drei"]}
  101. ],
  102. {ok, RenderVars};
  103. setup("for") ->
  104. RenderVars = [{fruit_list, ["apple", "banana", "coconut"]}],
  105. {ok, RenderVars};
  106. setup("for_list") ->
  107. RenderVars = [{fruit_list, [["apple", "apples", "$1"], ["banana", "bananas", "$2"], ["coconut", "coconuts", "$500"]]}],
  108. {ok, RenderVars};
  109. setup("for_tuple") ->
  110. RenderVars = [{fruit_list, [{"apple", "apples"}, {"banana", "bananas"}, {"coconut", "coconuts"}]}],
  111. {ok, RenderVars};
  112. setup("for_records") ->
  113. Link1 = [{name, "Amazon"}, {url, "http://amazon.com"}],
  114. Link2 = [{name, "Google"}, {url, "http://google.com"}],
  115. Link3 = [{name, "Microsoft"}, {url, "http://microsoft.com"}],
  116. RenderVars = [{link_list, [Link1, Link2, Link3]}],
  117. {ok, RenderVars};
  118. setup("for_records_preset") ->
  119. Link1b = [{name, "Canon"}, {url, "http://canon.com"}],
  120. Link2b = [{name, "Leica"}, {url, "http://leica.com"}],
  121. Link3b = [{name, "Nikon"}, {url, "http://nikon.com"}],
  122. RenderVars = [{photo_links, [Link1b, Link2b, Link3b]}],
  123. {ok, RenderVars};
  124. setup("include") ->
  125. RenderVars = [{var1, "foostring1"}, {var2, "foostring2"}],
  126. {ok, RenderVars};
  127. setup("if") ->
  128. RenderVars = [{var1, "something"}],
  129. {ok, RenderVars};
  130. setup("ifequal") ->
  131. RenderVars = [{var1, "foo"}, {var2, "foo"}, {var3, "bar"}],
  132. {ok, RenderVars};
  133. setup("ifequal_preset") ->
  134. RenderVars = [{var3, "bar"}],
  135. {ok, RenderVars};
  136. setup("ifnotequal") ->
  137. RenderVars = [{var1, "foo"}, {var2, "foo"}, {var3, "bar"}],
  138. {ok, RenderVars};
  139. setup("now") ->
  140. {ok, [], [], skip_check};
  141. setup("var") ->
  142. RenderVars = [{var1, "foostring1"}, {var2, "foostring2"}, {var_not_used, "foostring3"}],
  143. {ok, RenderVars};
  144. setup("var_preset") ->
  145. RenderVars = [{var1, "foostring1"}, {var2, "foostring2"}],
  146. {ok, RenderVars};
  147. setup("cycle") ->
  148. RenderVars = [{test, [integer_to_list(X) || X <- lists:seq(1, 20)]},
  149. {a, "Apple"}, {b, "Banana"}, {c, "Cherry"}],
  150. {ok, RenderVars};
  151. setup("include_template") ->
  152. RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
  153. {ok, RenderVars};
  154. setup("include_path") ->
  155. RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
  156. {ok, RenderVars};
  157. setup("extends_path") ->
  158. RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
  159. {ok, RenderVars};
  160. setup("extends_path2") ->
  161. RenderVars = [{base_var, "base-barstring"}, {test_var, "test-barstring"}],
  162. {ok, RenderVars};
  163. setup("trans") ->
  164. RenderVars = [{locale, "reverse"}],
  165. {ok, RenderVars};
  166. setup("locale") ->
  167. {ok, _RenderVars = [{locale, "ru"}]};
  168. setup("custom_tag1") ->
  169. {ok, [{a, <<"a1">>}], [{locale, ru}], <<"b1\n">>};
  170. setup("custom_tag2") ->
  171. {ok, [{a, <<"a1">>}], [{locale, ru}, {foo, bar}], <<"b2\n">>};
  172. setup("custom_tag3") ->
  173. {ok, [{a, <<"a1">>}], [{locale, ru}], <<"b3\n">>};
  174. setup("custom_tag4") ->
  175. {ok, [], [], <<"a\n">>};
  176. setup("ssi") ->
  177. RenderVars = [{path, filename:absname(filename:join(["tests", "input", "ssi_include.html"]))}],
  178. {ok, RenderVars};
  179. %%--------------------------------------------------------------------
  180. %% Custom tags
  181. %%--------------------------------------------------------------------
  182. setup("custom_call") ->
  183. RenderVars = [{var1, "something"}],
  184. {ok, RenderVars};
  185. setup(_) ->
  186. {ok, []}.
  187. run_tests() ->
  188. io:format("Running functional tests...~n"),
  189. case [filelib:ensure_dir(
  190. filename:join([templates_dir(Dir), "foo"]))
  191. || Dir <- ["output", "expect"]] -- [ok,ok]
  192. of
  193. [] ->
  194. case fold_tests() of
  195. {N, []}->
  196. Msg = lists:concat(["All ", N, " functional tests passed~n~n"]),
  197. io:format(Msg),
  198. {ok, Msg};
  199. {N, Errs} ->
  200. io:format(
  201. "~b / ~b functional tests failed.~nErrors: ~n",
  202. [length(Errs), N]),
  203. [io:format(" ~s [~s] ~s~n", [Name, Error, Reason])
  204. || {Name, Error, Reason} <- Errs],
  205. throw(failed)
  206. end;
  207. Err ->
  208. [io:format("Ensure dir failed: ~p~n~n", [Reason]) || {error, Reason} <- Err],
  209. throw(failed)
  210. end.
  211. run_test(Name) ->
  212. test_compile_render(Name).
  213. %%====================================================================
  214. %% Internal functions
  215. %%====================================================================
  216. fold_tests() ->
  217. lists:foldl(fun(Name, {AccCount, AccErrs}) ->
  218. Res = case catch test_compile_render(Name) of
  219. ok -> {AccCount + 1, AccErrs};
  220. {'EXIT', Reason} ->
  221. {AccCount + 1, [{Name, crash,
  222. io_lib:format("~p", [Reason])}
  223. | AccErrs]};
  224. {Error, Reason} ->
  225. {AccCount + 1, [{Name, Error, Reason}
  226. | AccErrs]}
  227. end,
  228. io:format("~n"), Res
  229. end, {0, []}, test_list()).
  230. test_compile_render(Name) ->
  231. File = filename:join([templates_docroot(), Name]),
  232. Module = "functional_test_" ++ Name,
  233. io:format(" Template: ~p, ... ", [Name]),
  234. case setup_compile(Name) of
  235. {CompileStatus, CompileVars} ->
  236. Options = [
  237. {vars, CompileVars},
  238. force_recompile,
  239. return_errors,
  240. return_warnings,
  241. %% debug_compiler,
  242. {custom_tags_modules, [erlydtl_custom_tags]}],
  243. io:format("compiling ... "),
  244. case erlydtl:compile(File, Module, Options) of
  245. {ok, Mod, [{File, [{none,erlydtl_compiler,no_out_dir}]}]} ->
  246. if CompileStatus =:= ok -> test_render(Name, Mod);
  247. true ->
  248. io:format("missing error"),
  249. {error, "compiling should have failed :" ++ File}
  250. end;
  251. {error, _, _}=Err ->
  252. if CompileStatus =:= Err -> io:format("ok");
  253. true ->
  254. io:format("failed"),
  255. {compile_error, io_lib:format("~p", [Err])}
  256. end
  257. end;
  258. skip -> io:format("skipped")
  259. end.
  260. test_render(Name, Module) ->
  261. File = filename:join([templates_docroot(), Name]),
  262. {RenderStatus, Vars, Opts, RenderResult} =
  263. case setup(Name) of
  264. {RS, V} -> {RS, V, [], get_expected_result(Name)};
  265. {RS, V, O} -> {RS, V, O, get_expected_result(Name)};
  266. {RS, V, O, R} -> {RS, V, O, R}
  267. end,
  268. io:format("rendering ... "),
  269. case catch Module:render(Vars, Opts) of
  270. {ok, Output} ->
  271. Data = iolist_to_binary(Output),
  272. if RenderStatus =:= ok ->
  273. if RenderResult =:= undefined ->
  274. Devs = [begin
  275. FileName = filename:join([templates_dir(Dir), Name]),
  276. {ok, IoDev} = file:open(FileName, [write]),
  277. IoDev
  278. end || Dir <- ["output", "expect"]],
  279. try
  280. [file:write(IoDev, Data) || IoDev <- Devs],
  281. io:format("~n #### NOTE: created new expected output file: \"tests/expect/~s\"."
  282. "~n Please verify contents.", [Name])
  283. after
  284. [file:close(IoDev) || IoDev <- Devs]
  285. end;
  286. RenderResult =:= Data ->
  287. io:format("ok");
  288. RenderResult =:= skip_check ->
  289. io:format("ok (not checked for regression)");
  290. true ->
  291. io:format("failed"),
  292. {error, io_lib:format(
  293. "Expected output does not match rendered output~n"
  294. "==Expected==~n~p~n--Actual--~n~p~n==End==~n",
  295. [RenderResult, Data])}
  296. end;
  297. true ->
  298. io:format("missing error"),
  299. {missing_error, "rendering should have failed :" ++ File}
  300. end;
  301. {'EXIT', Reason} ->
  302. io:format("failed"),
  303. {render_error, io_lib:format("failed invoking render method of ~p ~p", [Module, Reason])};
  304. Err ->
  305. if RenderStatus =:= error -> io:format("ok");
  306. true -> io:format("failed"),
  307. {render_error, io_lib:format("~p", [Err])}
  308. end
  309. end.
  310. get_expected_result(Name) ->
  311. FileName = filename:join([templates_dir("expect"), Name]),
  312. case filelib:is_regular(FileName) of
  313. true -> {ok, Data} = file:read_file(FileName), Data;
  314. false -> undefined
  315. end.
  316. templates_docroot() -> templates_dir("input").
  317. templates_dir(Name) -> filename:join([erlydtl_deps:get_base_dir(), "tests", Name]).