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.