1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
%%% @doc Common Test Framework functions handling test specifications.
%%%
%%% <p>This module creates a junit report of the test run if plugged in
%%% as a suite_callback.</p>
-module(cth_surefire).
%% Suite Callbacks
-export([id/1, init/2]).
-export([pre_init_per_suite/3]).
-export([post_init_per_suite/4]).
-export([pre_end_per_suite/3]).
-export([post_end_per_suite/4]).
-export([pre_init_per_group/3]).
-export([post_init_per_group/4]).
-export([pre_end_per_group/3]).
-export([post_end_per_group/4]).
-export([pre_init_per_testcase/3]).
-export([post_end_per_testcase/4]).
-export([on_tc_fail/3]).
-export([on_tc_skip/3]).
-export([terminate/1]).
-record(state, { filepath, axis, properties, package, hostname,
curr_suite, curr_suite_ts, curr_group = [], curr_tc,
curr_log_dir, timer, tc_log,
test_cases = [],
test_suites = [] }).
-record(testcase, { log, group, classname, name, time, failure, timestamp }).
-record(testsuite, { errors, failures, hostname, name, tests,
time, timestamp, id, package,
properties, testcases }).
id(Opts) ->
filename:absname(proplists:get_value(path, Opts, "junit_report.xml")).
init(Path, Opts) ->
{ok, Host} = inet:gethostname(),
#state{ filepath = Path,
hostname = proplists:get_value(hostname,Opts,Host),
package = proplists:get_value(package,Opts),
axis = proplists:get_value(axis,Opts,[]),
properties = proplists:get_value(properties,Opts,[]),
timer = now() }.
pre_init_per_suite(Suite,Config,State) ->
{Config, init_tc(State#state{ curr_suite = Suite, curr_suite_ts = now() },
Config) }.
post_init_per_suite(_Suite,Config, Result, State) ->
{Result, end_tc(init_per_suite,Config,Result,State)}.
pre_end_per_suite(_Suite,Config,State) -> {Config, init_tc(State, Config)}.
post_end_per_suite(_Suite,Config,Result,State) ->
NewState = end_tc(end_per_suite,Config,Result,State),
TCs = NewState#state.test_cases,
Suite = get_suite(NewState, TCs),
{Result, State#state{ test_cases = [],
test_suites = [Suite | State#state.test_suites]}}.
pre_init_per_group(Group,Config,State) ->
{Config, init_tc(State#state{ curr_group = [Group|State#state.curr_group]},
Config)}.
post_init_per_group(_Group,Config,Result,State) ->
{Result, end_tc(init_per_group,Config,Result,State)}.
pre_end_per_group(_Group,Config,State) -> {Config, init_tc(State, Config)}.
post_end_per_group(_Group,Config,Result,State) ->
NewState = end_tc(end_per_group, Config, Result, State),
{Result, NewState#state{ curr_group = tl(NewState#state.curr_group)}}.
pre_init_per_testcase(_TC,Config,State) -> {Config, init_tc(State, Config)}.
post_end_per_testcase(TC,Config,Result,State) ->
{Result, end_tc(TC,Config, Result,State)}.
on_tc_fail(_TC, _Res, State = #state{test_cases = []}) ->
State;
on_tc_fail(_TC, Res, State) ->
TCs = State#state.test_cases,
TC = hd(TCs),
NewTC = TC#testcase{
failure =
{fail,lists:flatten(io_lib:format("~p",[Res]))} },
State#state{ test_cases = [NewTC | tl(TCs)]}.
on_tc_skip(_Tc, _Res, State = #state{test_cases = []}) ->
State;
on_tc_skip(_Tc, Res, State) ->
TCs = State#state.test_cases,
TC = hd(TCs),
NewTC = TC#testcase{
failure =
{skipped,lists:flatten(io_lib:format("~p",[Res]))} },
State#state{ test_cases = [NewTC | tl(TCs)]}.
init_tc(State, Config) ->
State#state{ timer = now(),
tc_log = proplists:get_value(tc_logfile, Config)}.
end_tc(Func, Config, Res, State) when is_atom(Func) ->
end_tc(atom_to_list(Func), Config, Res, State);
end_tc(Name, _Config, _Res, State = #state{ curr_suite = Suite,
curr_group = Groups,
timer = TS, tc_log = Log } ) ->
ClassName = atom_to_list(Suite),
PGroup = string:join([ atom_to_list(Group)||
Group <- lists:reverse(Groups)],"."),
TimeTakes = io_lib:format("~f",[timer:now_diff(now(),TS) / 1000000]),
State#state{ test_cases = [#testcase{ log = Log,
timestamp = now_to_string(TS),
classname = ClassName,
group = PGroup,
name = Name,
time = TimeTakes,
failure = passed }| State#state.test_cases]}.
get_suite(State, TCs) ->
Total = length(TCs),
Succ = length(lists:filter(fun(#testcase{ failure = F }) ->
F == passed
end,TCs)),
Fail = Total - Succ,
TimeTaken = timer:now_diff(now(),State#state.curr_suite_ts) / 1000000,
#testsuite{ name = atom_to_list(State#state.curr_suite),
package = State#state.package,
time = io_lib:format("~f",[TimeTaken]),
timestamp = now_to_string(State#state.curr_suite_ts),
errors = Fail, tests = Total, testcases = lists:reverse(TCs) }.
terminate(State) ->
{ok,D} = file:open(State#state.filepath,[write]),
io:format(D, "<?xml version=\"1.0\" encoding= \"UTF-8\" ?>", []),
io:format(D, to_xml(State), []),
catch file:sync(D),
catch file:close(D).
to_xml(#testcase{ group = Group, classname = CL, log = L, name = N, time = T, timestamp = TS, failure = F}) ->
["<testcase ",
[["group=\"",Group,"\""]||Group /= ""]," "
"name=\"",N,"\" "
"time=\"",T,"\" "
"timestamp=\"",TS,"\" "
"log=\"",L,"\">",
case F of
passed ->
[];
{skipped,Reason} ->
["<skipped type=\"skip\" message=\"Test ",N," in ",CL,
" skipped!\">", sanitize(Reason),"</skipped>"];
{fail,Reason} ->
["<failure message=\"Test ",N," in ",CL," failed!\" type=\"crash\">",
sanitize(Reason),"</failure>"]
end,"</testcase>"];
to_xml(#testsuite{ package = P, hostname = H, errors = E, time = Time,
timestamp = TS, tests = T, name = N, testcases = Cases }) ->
["<testsuite ",
[["package=\"",P,"\" "]||P /= undefined],
[["hostname=\"",P,"\" "]||H /= undefined],
[["name=\"",N,"\" "]||N /= undefined],
[["time=\"",Time,"\" "]||Time /= undefined],
[["timestamp=\"",TS,"\" "]||TS /= undefined],
"errors=\"",integer_to_list(E),"\" "
"tests=\"",integer_to_list(T),"\">",
[to_xml(Case) || Case <- Cases],
"</testsuite>"];
to_xml(#state{ test_suites = TestSuites, axis = Axis, properties = Props }) ->
["<testsuites>",properties_to_xml(Axis,Props),
[to_xml(TestSuite) || TestSuite <- TestSuites],"</testsuites>"].
properties_to_xml(Axis,Props) ->
["<properties>",
[["<property name=\"",Name,"\" axis=\"yes\" value=\"",Value,"\" />"] || {Name,Value} <- Axis],
[["<property name=\"",Name,"\" value=\"",Value,"\" />"] || {Name,Value} <- Props],
"</properties>"
].
sanitize([$>|T]) ->
">" ++ sanitize(T);
sanitize([$<|T]) ->
"<" ++ sanitize(T);
sanitize([$"|T]) ->
""" ++ sanitize(T);
sanitize([$'|T]) ->
"'" ++ sanitize(T);
sanitize([$&|T]) ->
"&" ++ sanitize(T);
sanitize([H|T]) ->
[H|sanitize(T)];
sanitize([]) ->
[].
now_to_string(Now) ->
{{YY,MM,DD},{HH,Mi,SS}} = calendar:now_to_local_time(Now),
io_lib:format("~p-~2..0B-~2..0BT~2..0B:~2..0B:~2..0B",[YY,MM,DD,HH,Mi,SS]).
|