1 % (c) 2009-2019 Lehrstuhl fuer Softwaretechnik und Programmiersprachen,
2 % Heinrich Heine Universitaet Duesseldorf
3 % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html)
4
5
6 :- module(junit_tests,[set_junit_dir/1,print_junit/2,create_junit_result/6,junit_mode/1,
7 create_and_print_junit_result/5]).
8
9 :- use_module(library(xml)).
10 :- use_module(library(lists)).
11 :- use_module(library(codesio)).
12 :- use_module(library(file_systems)).
13 :- use_module(library(process)).
14 :- use_module(library(sets)).
15 :- use_module(library(system)).
16
17 :- use_module(module_information,[module_info/2]).
18 :- module_info(group,infrastructure).
19 :- module_info(description,'This module provides functionality to output test case results in a format compatible to junit.').
20
21
22 :- dynamic junit_mode/1.
23
24 set_junit_dir(Directory) :- assert(junit_mode(Directory)).
25
26 iso8601_datetime(ISODatime) :- datime(datime(Year,Month,Day,Hour,Min,Sec)),
27 format_to_codes('~`0t~d~2|-~`0t~d~3+-~`0t~d~3+T~`0t~d~3+:~`0t~d~3+:~`0t~d~3+',
28 [Year,Month,Day,Hour,Min,Sec], ISODatime).
29
30
31
32 % <testcase name="Call" classname="Selfcheck.Module" time="2"><error type="error"></testcase>
33
34 create_junit_result(Name,Section, Module, Time, Verdict, result(Name,Section,Module,TR,Verdict)) :-
35 ? junit_mode(_),!,
36 convert_to_junit_time(Time,TR).
37
38 create_junit_result(_Call, _Section, _Module, _Time, _Verdict, none).
39
40 convert_to_junit_time(T,R) :- number(T),!, R is T / 1000.
41
42 create_and_print_junit_result(Name,Section, Module, Time, Verdict) :-
43 create_junit_result(Name,Section, Module, Time, Verdict,Result),
44 print_junit([Result],Section).
45
46
47 print_junit(Results,Module) :-
48 ? junit_mode(Dir),!,print_junit2(Dir,Results,Module).
49 print_junit(_Results,_Module).
50
51 :- use_module(tools_strings,[ajoin/2]).
52 :- use_module(tools_files,[put_codes/2]).
53 :- use_module(error_manager).
54 print_junit2(Dir,Results,Module) :-
55 ? prepare_xml(Results,Codes),
56 open_file(0,Dir,Module,Stream),
57 put_codes(Codes,Stream),
58 close(Stream),!.
59 print_junit2(Dir,_Results,Module) :- add_error(junit_tests,'print_junit failed',Dir:Module).
60
61 :- use_module(tools_meta,[safe_on_exception/3]).
62 open_file(C,Dir,Module,Stream) :- process_id(PID),
63 number_codes(C,CC), atom_codes(Num,CC),
64 ajoin([Dir,'/',Num,'_',PID,'_',Module,'.xml'], File),
65 open_file2(C,File,Dir,Module,Stream).
66 open_file2(C,F,D,M,Stream) :- file_exists(F),!, C1 is C+1, open_file(C1,D,M,Stream).
67 open_file2(_C,File,_D,_M,Stream) :-
68 safe_on_exception(E,open(File, write,Stream),
69 (print('### Exception while opening Junit File: '), print(File),nl,
70 add_error(junit_tests,'Junit File Opening Exception: ',File:E), fail)).
71
72 prepare_xml(Results,Codes) :-
73 maplist(prepare_testcase_xml, Results,X),
74 % count tests
75 length(Results,TestsNr),
76 number_codes(TestsNr, Tests),
77 % count tests marked as error
78 count_results(error(_), Results, ErrorsNr),
79 number_codes(ErrorsNr, Errors),
80 % count skipped tests
81 count_results(skip, Results, SkippedNr),
82 number_codes(SkippedNr, Skipped),
83 % sum all test runtimes
84 maplist(extract_times, Results,Times),
85 sumlist(Times, TimeNr),
86 format_to_codes('~6F', [TimeNr], Time),
87 append([element(properties, [], [])|X],
88 [element('system-out',[], []), element('system-err', [], [])], Children),
89 maplist(extract_testcase_section, Results, SectionsL),
90 list_to_set(SectionsL, Sections),
91 print('Sections '), print(Sections),nl,
92 [Section|_] = Sections,
93 atom_codes(Section,SC),
94 iso8601_datetime(DT),
95 xml_parse(Codes,
96 xml([version="1.0",encoding="UTF-8"],
97 element(testsuite,
98 [name=SC,hostname="Test Runner",tests=Tests,
99 skipped=Skipped,failures="0",
100 errors=Errors,time=Time,timestamp=DT],
101 Children))).
102 prepare_xml(Results,_Codes) :- add_error_fail(junit_tests,'prepare_xml failed',Results).
103
104 extract_testcase_section(Result,_) :- var(Result),!,
105 add_error_fail(junit_tests,'extract_testcase_section called on non-ground result',Result).
106 extract_testcase_section(result(_,Section,_,_,_),Section).
107
108 prepare_testcase_xml(Result,_) :- var(Result),!,
109 add_error_fail(junit_tests,'prepare_testcase_xml called on non-ground result',Result).
110 prepare_testcase_xml(result(Call,Section,Module,Time,Verdict),element(testcase,R,Error)) :-
111 R = [name=Name, classname=Classname, time=T],
112 write_to_codes(Call,Name),
113 atom_codes(Module,MC),
114 atom_codes(Section,SC),
115 ( Verdict=pass -> Error=[]
116 ; Verdict=error(E) -> (createError(E,Err), Error=[element(error,['='(type,"Error")],Err)])
117 ; Verdict=skip -> Error=[element(skipped,[],[])]),
118 append([SC,".",MC],Classname),
119 format_to_codes('~6F', [Time], T).
120
121 createError([],[]).
122 createError([E|T],[pcdata(Error)|R]) :- write_to_codes(E,Codes), append(["\n",Codes,"\n"],Error), createError(T,R).
123
124 extract_times(Result,_) :- var(Result),!,
125 add_error_fail(junit_tests,'extract_times called on non-ground result',Result).
126 extract_times(result(_,_,_,Time,_),Time).
127
128 count_results(_,Results,_) :- var(Results),!,
129 add_error_fail(junit_tests,'count_results called on non-ground list of results',Results).
130 count_results(Type,Results,Sum) :- count_results2(Type, Results, Sum).
131
132 count_results2(_, [], 0).
133 count_results2(Type,[result(_,_,_,_,Type)|Tail],Result) :- !, count_results2(Type,Tail,Temp), Result is 1 + Temp.
134 count_results2(Type,[result(_,_,_,_,_)|Tail],Result) :- !, count_results2(Type,Tail,Result).