| 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). |