1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 2012. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: -module(erl2html2_SUITE).
   20: 
   21: -compile(export_all).
   22: 
   23: -include_lib("common_test/include/ct.hrl").
   24: 
   25: 
   26: -define(HEADER,
   27: 	["<!DOCTYPE HTML PUBLIC",
   28: 	 "\"-//W3C//DTD HTML 3.2 Final//EN\">\n",
   29: 	 "<!-- autogenerated by 'erl2html2' -->\n",
   30: 	 "<html>\n",
   31: 	 "<head><title>Module ", Src, "</title>\n",
   32: 	 "<meta http-equiv=\"cache-control\" ",
   33: 	 "content=\"no-cache\">\n",
   34: 	 "</head>\n",
   35: 	 "<body bgcolor=\"white\" text=\"black\" ",
   36: 	 "link=\"blue\" vlink=\"purple\" alink=\"red\">\n"]).
   37: 
   38: %%--------------------------------------------------------------------
   39: %% @spec suite() -> Info
   40: %% Info = [tuple()]
   41: %% @end
   42: %%--------------------------------------------------------------------
   43: suite() ->
   44:     [{timetrap,{seconds,30}},
   45:      {ct_hooks,[ts_install_cth,test_server_test_lib]}].
   46: 
   47: %%--------------------------------------------------------------------
   48: %% @spec init_per_suite(Config0) ->
   49: %%     Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
   50: %% Config0 = Config1 = [tuple()]
   51: %% Reason = term()
   52: %% @end
   53: %%--------------------------------------------------------------------
   54: init_per_suite(Config) ->
   55:     Config.
   56: 
   57: %%--------------------------------------------------------------------
   58: %% @spec end_per_suite(Config0) -> void() | {save_config,Config1}
   59: %% Config0 = Config1 = [tuple()]
   60: %% @end
   61: %%--------------------------------------------------------------------
   62: end_per_suite(_Config) ->
   63:     ok.
   64: 
   65: %%--------------------------------------------------------------------
   66: %% @spec init_per_group(GroupName, Config0) ->
   67: %%               Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
   68: %% GroupName = atom()
   69: %% Config0 = Config1 = [tuple()]
   70: %% Reason = term()
   71: %% @end
   72: %%--------------------------------------------------------------------
   73: init_per_group(_GroupName, Config) ->
   74:     Config.
   75: 
   76: %%--------------------------------------------------------------------
   77: %% @spec end_per_group(GroupName, Config0) ->
   78: %%               void() | {save_config,Config1}
   79: %% GroupName = atom()
   80: %% Config0 = Config1 = [tuple()]
   81: %% @end
   82: %%--------------------------------------------------------------------
   83: end_per_group(_GroupName, _Config) ->
   84:     ok.
   85: 
   86: %%--------------------------------------------------------------------
   87: %% @spec init_per_testcase(TestCase, Config0) ->
   88: %%               Config1 | {skip,Reason} | {skip_and_save,Reason,Config1}
   89: %% TestCase = atom()
   90: %% Config0 = Config1 = [tuple()]
   91: %% Reason = term()
   92: %% @end
   93: %%--------------------------------------------------------------------
   94: init_per_testcase(_TestCase, Config) ->
   95:     Config.
   96: 
   97: %%--------------------------------------------------------------------
   98: %% @spec end_per_testcase(TestCase, Config0) ->
   99: %%               void() | {save_config,Config1} | {fail,Reason}
  100: %% TestCase = atom()
  101: %% Config0 = Config1 = [tuple()]
  102: %% Reason = term()
  103: %% @end
  104: %%--------------------------------------------------------------------
  105: end_per_testcase(_TestCase, _Config) ->
  106:     ok.
  107: 
  108: %%--------------------------------------------------------------------
  109: %% @spec groups() -> [Group]
  110: %% Group = {GroupName,Properties,GroupsAndTestCases}
  111: %% GroupName = atom()
  112: %% Properties = [parallel | sequence | Shuffle | {RepeatType,N}]
  113: %% GroupsAndTestCases = [Group | {group,GroupName} | TestCase]
  114: %% TestCase = atom()
  115: %% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}}
  116: %% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail |
  117: %%              repeat_until_any_ok | repeat_until_any_fail
  118: %% N = integer() | forever
  119: %% @end
  120: %%--------------------------------------------------------------------
  121: groups() ->
  122:     [].
  123: 
  124: %%--------------------------------------------------------------------
  125: %% @spec all() -> GroupsAndTestCases | {skip,Reason}
  126: %% GroupsAndTestCases = [{group,GroupName} | TestCase]
  127: %% GroupName = atom()
  128: %% TestCase = atom()
  129: %% Reason = term()
  130: %% @end
  131: %%--------------------------------------------------------------------
  132: all() ->
  133:     [m1].
  134: 
  135: %%--------------------------------------------------------------------
  136: %% @spec TestCase() -> Info
  137: %% Info = [tuple()]
  138: %% @end
  139: %%--------------------------------------------------------------------
  140: m1() ->
  141:     [].
  142: 
  143: %%--------------------------------------------------------------------
  144: %% @spec TestCase(Config0) ->
  145: %%               ok | exit() | {skip,Reason} | {comment,Comment} |
  146: %%               {save_config,Config1} | {skip_and_save,Reason,Config1}
  147: %% Config0 = Config1 = [tuple()]
  148: %% Reason = term()
  149: %% Comment = term()
  150: %% @end
  151: %%--------------------------------------------------------------------
  152: m1(Config) ->
  153:     {Src,Dst} = convert_module("m1",Config),
  154:     {true,L} = check_line_numbers(Src,Dst),
  155:     ok = check_link_targets(Src,Dst,L,[{baz,0}]),
  156:     ok.
  157: 
  158: convert_module(Mod,Config) ->
  159:     DataDir = ?config(data_dir,Config),
  160:     PrivDir = ?config(priv_dir,Config),
  161:     Src = filename:join(DataDir,Mod++".erl"),
  162:     Dst = filename:join(PrivDir,Mod++".erl.html"),
  163:     io:format("<a href=\"~s\">~s</a>\n",[Src,filename:basename(Src)]),
  164:     ok = erl2html2:convert(Src, Dst, "<html><body>"),
  165:     io:format("<a href=\"~s\">~s</a>\n",[Dst,filename:basename(Dst)]),
  166:     {Src,Dst}.
  167: 
  168: %% Check that there are the same number of lines in each file, and
  169: %% that all line numbers are displayed in the dst file.
  170: check_line_numbers(Src,Dst) ->
  171:     {ok,SFd} = file:open(Src,[read]),
  172:     {ok,DFd} = file:open(Dst,[read]),
  173:     {ok,SN} = count_src_lines(SFd,0),
  174:     ok = file:close(SFd),
  175:     {ok,DN} = read_dst_line_numbers(DFd),
  176:     ok = file:close(DFd),
  177:     {SN == DN,SN}.
  178: 
  179: count_src_lines(Fd,N) ->
  180:     case io:get_line(Fd,"") of
  181: 	eof ->
  182: 	    {ok,N};
  183: 	{error,Reason} ->
  184: 	    {error,Reason,N};
  185: 	_Line ->
  186: 	    count_src_lines(Fd,N+1)
  187:     end.
  188: 
  189: read_dst_line_numbers(Fd) ->
  190:     "<html><body><pre>\n" = io:get_line(Fd,""),
  191:     read_dst_line_numbers(Fd,0).
  192: read_dst_line_numbers(Fd,Last) when is_integer(Last) ->
  193:     case io:get_line(Fd,"") of
  194: 	eof ->
  195: 	    {ok,Last};
  196: 	{error,Reason} ->
  197: 	    {error,Reason,Last};
  198: 	"</pre>"++_ ->
  199: 	    {ok,Last};
  200: 	"</body>"++_ ->
  201: 	    {ok,Last};
  202: 	Line ->
  203: 	    %% erlang:display(Line),
  204: 	    Num = check_line_number(Last,Line,Line),
  205: 	    read_dst_line_numbers(Fd,Num)
  206:     end.
  207: 
  208: check_line_number(Last,Line,OrigLine) ->
  209:     case Line of
  210: 	"<a name="++_ ->
  211: 	    [$>|Rest] = lists:dropwhile(fun($>) -> false; (_) -> true end,Line),
  212: 	    check_line_number(Last,Rest,OrigLine);
  213: 	_ ->
  214: 	    [N |_] = string:tokens(Line,":"),
  215: %	    erlang:display(N),
  216: 	    Num =
  217: 		try list_to_integer(string:strip(N))
  218: 		catch _:_ -> ct:fail({no_line_number_after,Last,OrigLine})
  219: 		end,
  220: 	    if Num == Last+1 ->
  221: 		    Num;
  222: 	       true ->
  223: 		    ct:fail({unexpected_integer,Num,Last})
  224: 	    end
  225:     end.
  226: 
  227: 
  228: %% Check that there is one link target for each line and one for each
  229: %% function.
  230: %% The test module has -compile(export_all), so all functions are
  231: %% found by listing the exported ones.
  232: check_link_targets(Src,Dst,L,RmFncs) ->
  233:     Mod = list_to_atom(filename:basename(filename:rootname(Src))),
  234:     Exports = Mod:module_info(exports)--[{module_info,0},{module_info,1}|RmFncs],
  235:     {ok,{[],L},_} = xmerl_sax_parser:file(Dst,
  236: 				     [{event_fun,fun sax_event/3},
  237: 				      {event_state,{Exports,0}}]),
  238:     ok.
  239: 
  240: sax_event(Event,_Loc,State) ->
  241:     sax_event(Event,State).
  242: 
  243: sax_event({startElement,_Uri,"a",_QN,Attrs},{Exports,PrevLine}) ->
  244:     {_,_,"name",Name} = lists:keyfind("name",3,Attrs),
  245:     case catch list_to_integer(Name) of
  246: 	Line when is_integer(Line) ->
  247: 	    case PrevLine + 1 of
  248: 		Line ->
  249: %		    erlang:display({found_line,Line}),
  250: 		    {Exports,Line};
  251: 		Other ->
  252: 		    ct:fail({unexpected_line_number_target,Other})
  253: 	    end;
  254: 	{'EXIT',_} ->
  255: 	    {match,[FStr,AStr]} =
  256: 		 re:run(Name,"^(.*)-([0-9]+)$",[{capture,all_but_first,list}]),
  257: 	    F = list_to_atom(http_uri:decode(FStr)),
  258: 	    A = list_to_integer(AStr),
  259: %	    erlang:display({found_fnc,F,A}),
  260: 	    A = proplists:get_value(F,Exports),
  261: 	    {lists:delete({F,A},Exports),PrevLine}
  262:     end;
  263: sax_event(_,State) ->
  264:     State.