| 1 | % Heinrich Heine Universitaet Duesseldorf | |
| 2 | % (c) 2025-2025 Lehrstuhl fuer Softwaretechnik und Programmiersprachen, | |
| 3 | % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html) | |
| 4 | ||
| 5 | :- module(rule_validation,[setup_rules_extras/0, reset_rules_extras/0, | |
| 6 | generate_report/1, generate_report/2, generate_report/3, | |
| 7 | generate_dependency_graph/1]). | |
| 8 | ||
| 9 | :- use_module(probsrc(module_information),[module_info/2]). | |
| 10 | :- module_info(group,rules_dsl). | |
| 11 | :- module_info(description,'This module provides functionality for the B Rules DSL.'). | |
| 12 | ||
| 13 | :- use_module(library(lists)). | |
| 14 | :- use_module(library(aggregate),[forall/2]). | |
| 15 | :- use_module(probsrc(bmachine),[bmachine_is_precompiled/0, get_machine_identifiers/2, | |
| 16 | b_get_main_filename/1, b_get_definition_string_from_machine/2, | |
| 17 | get_operation_info/2]). | |
| 18 | :- use_module(probsrc(debug)). | |
| 19 | :- use_module(probsrc(error_manager)). | |
| 20 | :- use_module(probsrc(preferences),[get_preference/2]). | |
| 21 | :- use_module(probsrc(specfile),[animation_minor_mode/1]). | |
| 22 | :- use_module(probsrc(tools),[read_string_from_file/2, ajoin/2, get_tail_filename/2, split_filename/3]). | |
| 23 | :- use_module(probsrc(tools_io),[safe_open_file/4, with_open_stream_to_codes/4]). | |
| 24 | ||
| 25 | :- dynamic computation/1, rule/1, dependencies/2, tags/2, classification/2, rule_id/2. | |
| 26 | :- dynamic rule_report_template_file_codes/2. | |
| 27 | ||
| 28 | assert_from_template(Filename) :- | |
| 29 | absolute_file_name(rulesdslsrc(Filename), Absolute, []), | |
| 30 | read_string_from_file(Absolute,String), | |
| 31 | assertz(rule_report_template_file_codes(Filename,String)). | |
| 32 | ||
| 33 | :- assert_from_template('validation_report_header.html'). | |
| 34 | :- assert_from_template('validation_report_footer.html'). | |
| 35 | ||
| 36 | write_rule_report_template(HtmlFile,Stream) :- | |
| 37 | rule_report_template_file_codes(HtmlFile,Codes), | |
| 38 | format(Stream,'~s~n',[Codes]). | |
| 39 | ||
| 40 | setup_rules_extras :- | |
| 41 | reset_rules_extras, | |
| 42 | (animation_minor_mode(rules_dsl) -> true | |
| 43 | ; b_get_main_filename(BFile), | |
| 44 | add_error(rule_validation,'Cannot set up rules DSL mode, current machine is not a rules machine (.rmch): ',BFile), | |
| 45 | fail), | |
| 46 | (bmachine_is_precompiled -> true ; add_error(setup_rules_extras,'bmachine not precompiled'), fail), | |
| 47 | get_machine_identifiers(operations,Ops), | |
| 48 | get_machine_identifiers(variables,Vars), | |
| 49 | forall(member(Op,Ops), | |
| 50 | (extract_rule_operations(Op,Vars), | |
| 51 | extract_dependencies(Op), | |
| 52 | extract_operation_attributes(Op))), % tags, classification, rule_id | |
| 53 | debug_println(19,'% setup for rules DSL complete.'). | |
| 54 | ||
| 55 | reset_rules_extras :- | |
| 56 | retractall(computation(_)), | |
| 57 | retractall(rule(_)), | |
| 58 | retractall(dependencies(_,_)), | |
| 59 | retractall(tags(_,_)), | |
| 60 | retractall(classification(_,_)), | |
| 61 | retractall(rule_id(_,_)). | |
| 62 | ||
| 63 | extract_rule_operations(Id,Vars) :- | |
| 64 | counterexample_name_from_id(Id,CId), % we could double check with successful | |
| 65 | (member(CId,Vars) -> assertz(rule(Id)) ; assertz(computation(Id))). | |
| 66 | ||
| 67 | counterexample_name_from_id(Id,CId) :- ajoin([Id,'_Counterexamples'],CId). | |
| 68 | successful_name_from_id(Id,CId) :- ajoin([Id,'_Successful'],CId). | |
| 69 | unchecked_name_from_id(Id,CId) :- ajoin([Id,'_Unchecked'],CId). | |
| 70 | ||
| 71 | :- use_module(probsrc(b_operation_guards),[get_operation_propositional_guards/3]). | |
| 72 | extract_dependencies(OpName) :- | |
| 73 | get_operation_propositional_guards(OpName,Guards,_RestBody), | |
| 74 | findall(Id, (member(Guard, Guards), is_rule_comp_dependency(Guard,Id)), Deps), | |
| 75 | assertz(dependencies(OpName,Deps)). | |
| 76 | ||
| 77 | is_rule_comp_dependency(b(equal(b(identifier(Id),string,_),b(string('SUCCESS'),string,_)),pred,_), Id) :- rule(Id). | |
| 78 | is_rule_comp_dependency(b(equal(b(identifier(Id),string,_),b(string('EXECUTED'),string,_)),pred,_), Id) :- computation(Id). | |
| 79 | ||
| 80 | extract_operation_attributes(OpName) :- | |
| 81 | get_operation_info(OpName,Infos), | |
| 82 | (member(rules_info(RInfos),Infos) -> true ; RInfos = []), | |
| 83 | (member(tags(Tags),RInfos) -> assertz(tags(OpName,Tags)) ; true), | |
| 84 | (member(classification(Classification),RInfos) | |
| 85 | -> (classification(Classification,OpList) | |
| 86 | -> retractall(classification(Classification,_)), | |
| 87 | assertz(classification(Classification,[OpName|OpList])) | |
| 88 | ; assertz(classification(Classification,[OpName]))) | |
| 89 | ; true), | |
| 90 | (member(rule_id(RId),RInfos) -> assertz(rule_id(OpName,RId)) ; true). | |
| 91 | ||
| 92 | get_computations(Comps) :- findall(X, computation(X), Comps). | |
| 93 | get_rules(Rules) :- findall(X, rule(X), Rules). | |
| 94 | ||
| 95 | get_simple_rule_status(BState,RuleName,Status) :- | |
| 96 | rule(RuleName), | |
| 97 | member(bind(RuleName,string(Status)),BState). | |
| 98 | get_rule_status(BState,RuleName,Status,CounterEx,SuccessMsg,UncheckedMsg,FailDep,NotExecDeps) :- | |
| 99 | rule(RuleName), | |
| 100 | dependencies(RuleName,Deps), | |
| 101 | member(bind(RuleName,string(Status)),BState), % rule status: NOT_CHECKED, DISABLED, FAIL, SUCCESS | |
| 102 | include(get_failed_dependencies(BState),Deps,FailDep), | |
| 103 | include(get_not_executed_dependencies(BState),Deps,NotExecDeps), | |
| 104 | get_rule_counterexamples(RuleName,BState,CounterEx), | |
| 105 | get_rule_success_messages(RuleName,BState,SuccessMsg), | |
| 106 | get_rule_unchecked_messages(RuleName,BState,UncheckedMsg). | |
| 107 | ||
| 108 | get_simple_computation_status(BState,CompName,Status) :- | |
| 109 | computation(CompName), | |
| 110 | member(bind(CompName,string(Status)),BState). | |
| 111 | ||
| 112 | get_failed_dependencies(State,Dep) :- | |
| 113 | rule(Dep), | |
| 114 | member(bind(Dep,string('FAIL')),State). | |
| 115 | ||
| 116 | get_not_executed_dependencies(State,Dep) :- | |
| 117 | member(bind(Dep,string(DepStatus)),State), | |
| 118 | (DepStatus = 'DISABLED' ; | |
| 119 | (rule(Dep) | |
| 120 | -> DepStatus = 'NOT_CHECKED' | |
| 121 | ; DepStatus = 'NOT_EXECUTED')). | |
| 122 | ||
| 123 | get_rule_counterexamples(RuleName,State,CounterEx) :- | |
| 124 | counterexample_name_from_id(RuleName,CId), | |
| 125 | evaluate_rule_results(CId,State,CounterEx). | |
| 126 | ||
| 127 | get_rule_success_messages(RuleName,State,SuccessMsg) :- | |
| 128 | successful_name_from_id(RuleName,SId), | |
| 129 | evaluate_rule_results(SId,State,SuccessMsg). | |
| 130 | ||
| 131 | get_rule_unchecked_messages(RuleName,State,UncheckedMsg) :- | |
| 132 | unchecked_name_from_id(RuleName,UId), | |
| 133 | evaluate_rule_results(UId,State,UncheckedMsg). | |
| 134 | ||
| 135 | evaluate_rule_results(Id,State,RuleRes) :- | |
| 136 | member(bind(Id,RR0),State), | |
| 137 | custom_explicit_sets:expand_custom_set_to_list(RR0,RR1), % TODO: enumeration warning (infinitely many messages) | |
| 138 | maplist(extract_rule_result,RR1,RuleRes). | |
| 139 | ||
| 140 | extract_rule_result((int(ErrorCode),string(ResultStr)),rule_result(ErrorCode,ResultStr)). | |
| 141 | ||
| 142 | :- use_module(probsrc(state_space),[current_state_id/1,visited_expression/2]). | |
| 143 | :- use_module(probsrc(specfile),[state_corresponds_to_initialised_b_machine/1,extract_variables_from_state/2]). | |
| 144 | get_variables_for_export(StateID,VarState) :- | |
| 145 | visited_expression(StateID,State), | |
| 146 | state_corresponds_to_initialised_b_machine(State),!, | |
| 147 | extract_variables_from_state(State,VarState). | |
| 148 | get_variables_for_export(_,_) :- | |
| 149 | add_error(rule_validation,'Cannot export rule validation results for uninitialised state'), fail. | |
| 150 | ||
| 151 | %%%%%%%%% BEGIN REPORT %%%%%%%%%% | |
| 152 | generate_report(File) :- generate_report(File,[]). % /1 default (for current state) | |
| 153 | generate_report(File,Options) :- % /2 current state with options | |
| 154 | current_state_id(ID), | |
| 155 | generate_report(ID,File,Options). | |
| 156 | generate_report(StateID,File,Options) :- % /3 any state with options TODO: language option | |
| 157 | (animation_minor_mode(rules_dsl) -> true | |
| 158 | ; b_get_main_filename(BFile), | |
| 159 | add_error(rule_validation,'Cannot create rule validation report, is not a rules machine (.rmch): ',BFile), | |
| 160 | fail), | |
| 161 | get_variables_for_export(StateID,VarState), | |
| 162 | (get_preference(prob_profiling_on,true) | |
| 163 | -> tcltk_get_operations_profile_info(list(ProfileList)), | |
| 164 | get_total_walltime(ProfileList,TotalWT) | |
| 165 | ; ProfileList = [], | |
| 166 | (member(checktime(TotalWT),Options) -> true ; TotalWT = -1) % option to provide checktime from ProB2 if profiling is off | |
| 167 | ), | |
| 168 | safe_open_file(File,write,Stream,[encoding(utf8)]), | |
| 169 | split_filename(File,_Base,Ext), % infer report mode from file extension | |
| 170 | (Ext=html -> generate_html_to_stream(Stream,VarState,Options,TotalWT,ProfileList) ; | |
| 171 | Ext=xml -> generate_xml_to_stream(Stream,VarState,Options,TotalWT,ProfileList) ; | |
| 172 | add_error(rule_validation,'unsupported output type for rule validation report: ',Ext)), | |
| 173 | close(Stream), | |
| 174 | formatsilent('% created rule validation report at ~w.~n',[File]). | |
| 175 | ||
| 176 | %%%%%%%%% BEGIN HTML REPORT %%%%%%%%%% | |
| 177 | :- use_module(probsrc(runtime_profiler),[tcltk_get_operations_profile_info/1]). | |
| 178 | :- use_module(library(system),[datime/1]). | |
| 179 | :- use_module(probsrc(tools_strings),[number_codes_min_length/3]). | |
| 180 | :- use_module(probsrc(version),[version_str/1,revision/1]). | |
| 181 | generate_html_to_stream(Stream,State,_Options,TotalWT,ProfileList) :- | |
| 182 | write_rule_report_template('validation_report_header.html',Stream), | |
| 183 | get_rules(Rules), | |
| 184 | gen_report_infos(Stream,Rules,State), | |
| 185 | forall(classification(CName,OpList),gen_classifications(Stream,CName,OpList,State,ProfileList)), | |
| 186 | format(Stream,' <div class="rule-list normal">~n',[]), | |
| 187 | forall((member(Rule,Rules), \+ (classification(_,OpList), member(Rule,OpList))), % all rules w/o classification | |
| 188 | gen_rule_result(Stream,Rule,State,ProfileList)), | |
| 189 | format(Stream,' </div>~n',[]), | |
| 190 | (TotalWT > 0 -> convert_ms_s(TotalWT,WTPrint), format(Stream,' Check finished after: ~w~n',[WTPrint]) ; true), | |
| 191 | version_str(VStr), revision(VRev), | |
| 192 | format(Stream,' <br>ProB Version: ~w (~w)~n',[VStr,VRev]), | |
| 193 | datime(datime(Yr,Mon,Day,Hr,Min,_Sec)), | |
| 194 | number_codes_min_length(Min,2,MC), | |
| 195 | format(Stream,' <br>Generated on ~w/~w/~w at ~w:~s~n',[Day,Mon,Yr,Hr,MC]), | |
| 196 | write_rule_report_template('validation_report_footer.html',Stream). | |
| 197 | ||
| 198 | gen_report_infos(Stream,Rules,BState) :- | |
| 199 | length(Rules,NrRules), | |
| 200 | precollect_statuses(Rules,BState,NrC,NrS,NrF,NrD), | |
| 201 | b_get_main_filename(Filename), | |
| 202 | get_tail_filename(Filename,MName), | |
| 203 | format(Stream,' Checked machine: <b>~w</b>~n',[MName]), | |
| 204 | (b_get_definition_string_from_machine('RULE_REPORT_CONTEXT',RepCtx) | |
| 205 | -> format(Stream,' <br>With context: <b>~w</b>~n',[RepCtx]) | |
| 206 | ; true), | |
| 207 | format(Stream,' <div style="line-height:25px;">~n',[]), | |
| 208 | format(Stream,' <br>Total number of rules: <b>~w</b>~n',[NrRules]), | |
| 209 | format(Stream,' <br>Number of checked rules: <b>~w</b>~n',[NrC]), | |
| 210 | format(Stream,' <br>Number of successful rules: <b>~w</b>~n',[NrS]), | |
| 211 | format(Stream,' <br>Number of failed rules: <b>~w</b>~n',[NrF]), | |
| 212 | format(Stream,' <br>Number of disabled rules: <b>~w</b>~n',[NrD]), | |
| 213 | format(Stream,' </div>~n',[]), | |
| 214 | format(Stream,' <br>~n',[]). | |
| 215 | ||
| 216 | get_total_walltime([_|T],WT) :- get_total_walltime1(T,WT). % ignore table legend | |
| 217 | get_total_walltime1([],0). | |
| 218 | get_total_walltime1([list([_,_,WT0,_,_,_,_])|T],WT) :- | |
| 219 | get_total_walltime1(T,WT1), | |
| 220 | WT is WT0+WT1. | |
| 221 | ||
| 222 | precollect_statuses(Rules,BState,NrC,NrS,NrF,NrD) :- | |
| 223 | maplist(get_simple_rule_status(BState), Rules, Statuses), | |
| 224 | count_statuses(Statuses,NrS,NrF,NrD), | |
| 225 | NrC is NrS+NrF. | |
| 226 | ||
| 227 | count_statuses([],0,0,0). | |
| 228 | count_statuses([Status|T],NrS,NrF,NrD) :- | |
| 229 | count_statuses(T,S,F,D), | |
| 230 | (Status = 'SUCCESS' -> NrS is S+1 ; NrS = S), | |
| 231 | (Status = 'FAIL' -> NrF is F+1 ; NrF = F), | |
| 232 | (Status = 'DISABLED' -> NrD is D+1 ; NrD = D). | |
| 233 | ||
| 234 | gen_classifications(Stream,CName,OpList,BState,ProfileList) :- | |
| 235 | format(Stream,' <div class="classification-list">~n',[]), | |
| 236 | format(Stream,' <div class="classification-header" onclick="openDetails(\'classification_~w\')">~n',[CName]), | |
| 237 | length(OpList,NrOps), | |
| 238 | format(Stream,' <b>~w</b> (~w)~n',[CName,NrOps]), | |
| 239 | format(Stream,' </div>~n',[]), | |
| 240 | format(Stream,' <div class="rule-list classification" id="classification_~w">~n',[CName]), | |
| 241 | forall(member(Rule,OpList), gen_rule_result(Stream,Rule,BState,ProfileList)), | |
| 242 | format(Stream,' </div>~n',[]), | |
| 243 | format(Stream,' </div>~n',[]), | |
| 244 | format(Stream,' </div>~n',[]). | |
| 245 | ||
| 246 | gen_rule_result(Stream,Rule,BState,ProfileList) :- | |
| 247 | get_rule_status(BState,Rule,Status,CounterEx,SuccessMsg,UncheckedMsg,FailDep,NotExecDep), | |
| 248 | (Status = 'SUCCESS' | |
| 249 | -> ((SuccessMsg \= [] ; UncheckedMsg \= []) | |
| 250 | -> format(Stream,' <div class="rule-header pointer green" onclick="openDetails(\'~w\')">~n',[Rule]) | |
| 251 | ; format(Stream,' <div class="rule-header green">~n',[])) | |
| 252 | ; Status = 'FAIL' | |
| 253 | -> format(Stream,' <div class="rule-header pointer red" onclick="openDetails(\'~w\')">~n',[Rule]), | |
| 254 | format(Stream,' <span class="icon">⚠</span>~n',[]) | |
| 255 | ; Status = 'DISABLED' -> format(Stream,' <div class="rule-header grey">~n',[]) | |
| 256 | ; Status = 'NOT_CHECKED' | |
| 257 | -> ((FailDep \= [] ; NotExecDep \= []) | |
| 258 | -> (FailDep \= [] -> C = 'orange' ; C = 'lightgrey'), % FAIL dependencies: orange, else grey | |
| 259 | P = 'pointer' | |
| 260 | ; C = 'white', P = '' % no conflicts with dependencies | |
| 261 | ), | |
| 262 | format(Stream,' <div class="rule-header ~w ~w" onclick="openDetails(\'~w\')">~n',[P,C,Rule]), | |
| 263 | (C = 'orange' -> format(Stream,' <span class="icon">⚠</span>~n',[]) ; true) | |
| 264 | ; format(Stream,' <div class="rule-header">~n',[]) | |
| 265 | ), | |
| 266 | format(Stream,' ~w',[Rule]), | |
| 267 | (rule_id(Rule,RuleId) | |
| 268 | -> format(Stream,' [~w]~n',[RuleId]) | |
| 269 | ; format(Stream,'~n',[])), | |
| 270 | (tags(Rule,Tags) | |
| 271 | -> format(Stream,' ~n',[]), | |
| 272 | forall(member(Tag,Tags), format(Stream,' <div class="tag">~w</div>~n',[Tag])), | |
| 273 | format(Stream,' ~n',[]) | |
| 274 | ; true), | |
| 275 | format(Stream,' <b>~w</b>~n',[Status]), | |
| 276 | (member(list([Rule,_,WT,_,_,_,_]),ProfileList) | |
| 277 | -> convert_ms_s(WT,WTPrint), | |
| 278 | format(Stream,' <span style="float: right;"><em>(~w)</em></span>~n',[WTPrint]) | |
| 279 | ; true), % ignore if not available | |
| 280 | format(Stream,' </div>~n',[]), | |
| 281 | gen_rule_content_list(Stream,Rule,CounterEx,SuccessMsg,UncheckedMsg,FailDep,NotExecDep), | |
| 282 | fail. | |
| 283 | gen_rule_result(_Stream,_,_,_). | |
| 284 | ||
| 285 | convert_ms_s(Time,CTime) :- Time >= 1000 -> TimeSec is Time/1000, ajoin([TimeSec,' s'],CTime) ; ajoin([Time,' ms'],CTime). | |
| 286 | ||
| 287 | gen_rule_content_list(Stream,Rule,CounterEx,SuccessMsg,UncheckedMsg,FailDep,NotExecDep) :- | |
| 288 | length(CounterEx,CELen), | |
| 289 | length(SuccessMsg,SMLen), | |
| 290 | length(UncheckedMsg,UCLen), | |
| 291 | length(FailDep,FDLen), | |
| 292 | length(NotExecDep,NEDLen), | |
| 293 | ((CELen > 0 ; SMLen > 0 ; UCLen > 0 ; FDLen > 0 ; NEDLen > 0) % create tag only if at least one child exists | |
| 294 | -> format(Stream,' <div class="rule-content" id="~w">~n',[Rule]), | |
| 295 | (CELen > 0 | |
| 296 | -> format(Stream,' <b>~w</b> Violations found:~n',[CELen]), | |
| 297 | format(Stream,' <ul>~n',[]), | |
| 298 | forall(member(CE,CounterEx),gen_rule_content_list_aux(Stream,CE)), | |
| 299 | format(Stream,' </ul>~n',[]) | |
| 300 | ; true | |
| 301 | ), | |
| 302 | (SMLen > 0 | |
| 303 | -> format(Stream,' <b>~w</b> successful checks:~n',[SMLen]), | |
| 304 | format(Stream,' <ul>~n',[]), | |
| 305 | forall(member(SM,SuccessMsg),gen_rule_content_list_aux(Stream,SM)), | |
| 306 | format(Stream,' </ul>~n',[]) | |
| 307 | ; true | |
| 308 | ), | |
| 309 | (UCLen > 0 | |
| 310 | -> format(Stream,' <b>~w</b> unchecked items:~n',[UCLen]), | |
| 311 | format(Stream,' <ul>~n',[]), | |
| 312 | forall(member(UC,UncheckedMsg),gen_rule_content_list_aux(Stream,UC)), | |
| 313 | format(Stream,' </ul>~n',[]) | |
| 314 | ; true | |
| 315 | ), | |
| 316 | (FDLen > 0 | |
| 317 | -> format(Stream,' Rule could not be checked due to ~w failed dependencies:~n',[FDLen]), | |
| 318 | format(Stream,' <ul>~n',[]), | |
| 319 | forall(member(FD,FailDep),gen_rule_content_list_aux(Stream,FD)), | |
| 320 | format(Stream,' </ul>~n',[]) | |
| 321 | ; true | |
| 322 | ), | |
| 323 | (NEDLen > 0 | |
| 324 | -> format(Stream,' Rule could not be checked due to ~w unchecked dependencies:~n',[NEDLen]), | |
| 325 | format(Stream,' <ul>~n',[]), | |
| 326 | forall(member(NED,NotExecDep),gen_rule_content_list_aux(Stream,NED)), | |
| 327 | format(Stream,' </ul>~n',[]) | |
| 328 | ; true | |
| 329 | ), | |
| 330 | format(Stream,' </div>~n',[]) | |
| 331 | ; true), | |
| 332 | fail. | |
| 333 | gen_rule_content_list(_,_,_,_,_,_,_). | |
| 334 | ||
| 335 | gen_rule_content_list_aux(Stream,rule_result(ErrorCode,Message)) :- !, | |
| 336 | format(Stream,' <li>(~w) ~w</li>~n',[ErrorCode,Message]). | |
| 337 | gen_rule_content_list_aux(Stream,Dependency) :- | |
| 338 | format(Stream,' <li>~w</li>~n',[Dependency]). | |
| 339 | ||
| 340 | % TODO: | |
| 341 | % Violations cannot be enumerated (infinitely many). | |
| 342 | % Successful checks cannot be enumerated (infinitely many). | |
| 343 | %%%%%%%%% END HTML REPORT %%%%%%%%%% | |
| 344 | ||
| 345 | %%%%%%%%% BEGIN XML REPORT %%%%%%%%% | |
| 346 | generate_xml_to_stream(Stream,State,_Options,TotalWT,ProfileList) :- | |
| 347 | get_rules(Rules), | |
| 348 | format(Stream,'<ruleValidationReport>~n',[]), | |
| 349 | gen_xml_report_statistics(Stream,Rules,State,TotalWT), | |
| 350 | forall(classification(CName,OpList),gen_xml_classifications(Stream,CName,OpList,State,ProfileList)), | |
| 351 | forall((member(Rule,Rules), \+ (classification(_,OpList), member(Rule,OpList))), % all rules w/o classification | |
| 352 | gen_xml_rule_result(Stream,Rule,State,ProfileList)), | |
| 353 | format(Stream,'</ruleValidationReport>~n',[]). | |
| 354 | ||
| 355 | gen_xml_report_statistics(Stream,Rules,BState,TotalWT) :- | |
| 356 | length(Rules,NrRules), | |
| 357 | precollect_statuses(Rules,BState,NrC,NrS,NrF,NrD), | |
| 358 | format(Stream,' <statistics>~n',[]), | |
| 359 | b_get_main_filename(Filename), | |
| 360 | get_tail_filename(Filename,MName), | |
| 361 | format(Stream,' <checkedMachine>~w</checkedMachine>~n',[MName]), | |
| 362 | (b_get_definition_string_from_machine('RULE_REPORT_CONTEXT',RepCtx) | |
| 363 | -> format(Stream,' <context>~w</context>~n',[RepCtx]) | |
| 364 | ; true), | |
| 365 | format(Stream,' <totalNumber>~w</totalNumber>~n',[NrRules]), | |
| 366 | format(Stream,' <checked>~w</checked>~n',[NrC]), | |
| 367 | format(Stream,' <succeeded>~w</succeeded>~n',[NrS]), | |
| 368 | format(Stream,' <failed>~w</failed>~n',[NrF]), | |
| 369 | format(Stream,' <disabled>~w</disabled>~n',[NrD]), | |
| 370 | format(Stream,' <checkTime>~w</checkTime>~n',[TotalWT]), | |
| 371 | version_str(VStr), revision(VRev), | |
| 372 | format(Stream,' <proBVersion>~w (~w)</proBVersion>~n',[VStr,VRev]), | |
| 373 | datime(datime(Yr,Mon,Day,Hr,Min,Sec)), | |
| 374 | number_codes_min_length(Mon,2,MonC), | |
| 375 | number_codes_min_length(Day,2,DC), | |
| 376 | number_codes_min_length(Hr,2,HC), | |
| 377 | number_codes_min_length(Min,2,MC), | |
| 378 | number_codes_min_length(Sec,2,SC), | |
| 379 | format(Stream,' <date>~w-~s-~sT~s:~s:~s</date>~n',[Yr,MonC,DC,HC,MC,SC]), | |
| 380 | format(Stream,' </statistics>~n',[]). | |
| 381 | ||
| 382 | gen_xml_classifications(Stream,CName,OpList,BState,ProfileList) :- | |
| 383 | length(OpList,NrOps), | |
| 384 | format(Stream,' <classification name="~w" nrRules="~w">~n',[CName,NrOps]), | |
| 385 | forall(member(Rule,OpList), gen_xml_rule_result(Stream,Rule,BState,ProfileList)), | |
| 386 | format(Stream,' </classification>~n',[]). | |
| 387 | ||
| 388 | gen_xml_rule_result(Stream,Rule,BState,ProfileList) :- | |
| 389 | get_rule_status(BState,Rule,Status,CounterEx,SuccessMsg,UncheckedMsg,FailDep,NotExecDep), | |
| 390 | (rule_id(Rule,RID) | |
| 391 | -> format(Stream,' <rule name="~w" status="~w" id="~w">~n',[Rule,Status,RID]) | |
| 392 | ; format(Stream,' <rule name="~w" status="~w">~n',[Rule,Status])), | |
| 393 | (tags(Rule,Tags) | |
| 394 | -> forall(member(Tag,Tags), format(Stream,' <tag>~w</tag>~n',[Tag])) | |
| 395 | ; true), | |
| 396 | forall(member(rule_result(ErrId,Msg),CounterEx), format(Stream,' <counterExample errorType="~w">~w</counterExample>~n',[ErrId,Msg])), | |
| 397 | forall(member(rule_result(BodyId,Msg),SuccessMsg), format(Stream,' <successMessage ruleBody="~w">~w</successMessage>~n',[BodyId,Msg])), | |
| 398 | forall(member(rule_result(BodyId,Msg),UncheckedMsg), format(Stream,' <uncheckedMessage ruleBody="~w">~w</uncheckedMessage>~n',[BodyId,Msg])), | |
| 399 | forall(member(FD,FailDep), format(Stream,' <failedDependency>~w</failedDependency>~n',[FD])), | |
| 400 | forall(member(NED,NotExecDep), format(Stream,' <notCheckedDependency>~w</notCheckedDependency>~n',[NED])), | |
| 401 | (member(list([Rule,_,WT,_,_,_,_]),ProfileList) | |
| 402 | -> format(Stream,' <walltime>~w</walltime>~n',[WT]) | |
| 403 | ; true), % ignore if not available | |
| 404 | format(Stream,' </rule>~n',[]), | |
| 405 | fail. | |
| 406 | gen_xml_rule_result(_Stream,_,_,_). | |
| 407 | %%%%%%%%% END XML REPORT %%%%%%%%%% | |
| 408 | %%%%%%%%% END REPORT %%%%%%%%%%%%%% | |
| 409 | ||
| 410 | %%%%%%%%% BEGIN DEPENDENCY GRAPH %%%%%%%%% | |
| 411 | % TODO: tooltip with counterexample? | |
| 412 | :- use_module(probsrc(state_space),[current_expression/2]). | |
| 413 | :- use_module(dotsrc(dot_graph_generator), [gen_dot_graph/3,use_new_dot_attr_pred/7]). | |
| 414 | generate_dependency_graph(File) :- | |
| 415 | (animation_minor_mode(rules_dsl) -> true | |
| 416 | ; b_get_main_filename(BFile), | |
| 417 | add_error(rule_validation,'Cannot create rule dependency graph, is not a rules machine (.rmch): ',BFile), | |
| 418 | fail), | |
| 419 | current_state_id(CurID), | |
| 420 | get_variables_for_export(CurID,VarState), | |
| 421 | gen_dot_graph(File, | |
| 422 | use_new_dot_attr_pred(rule_validation:rule_graph_node_predicate(VarState)), | |
| 423 | use_new_dot_attr_pred(rule_validation:rule_graph_trans_predicate(VarState))). | |
| 424 | ||
| 425 | :- public rule_graph_node_predicate/4. | |
| 426 | rule_graph_node_predicate(State,OpName,none,[style/'filled',label/OpName,tooltip/OpName|Attr]) :- | |
| 427 | dependencies(OpName,_), % all relevant operations | |
| 428 | get_node_attributes(State,OpName,Attr). | |
| 429 | ||
| 430 | get_node_attributes(State,OpName,[shape/RShape,fillcolor/StatusColor,tooltip/Tooltip]) :- | |
| 431 | get_simple_rule_status(State,OpName,Status), !, | |
| 432 | get_preference(rule_graph_shape,RShape), | |
| 433 | (Status = 'FAIL' -> get_preference(failed_rule_colour,StatusColor) ; | |
| 434 | Status = 'SUCCESS' -> get_preference(successful_rule_colour,StatusColor) ; | |
| 435 | Status = 'DISABLED' -> get_preference(disabled_rule_colour,StatusColor) ; | |
| 436 | get_preference(not_checked_rule_colour,StatusColor)), % NOT_CHECKED, TODO: maybe UNCHECKED | |
| 437 | ajoin(['Rule: ',OpName,', Status: ',Status],Tooltip). | |
| 438 | get_node_attributes(State,OpName,[shape/CompShape,fillcolor/StatusColor,tooltip/Tooltip]) :- | |
| 439 | get_simple_computation_status(State,OpName,Status), !, | |
| 440 | get_preference(computation_graph_shape,CompShape), | |
| 441 | (Status = 'EXECUTED' -> get_preference(executed_computation_colour,StatusColor) ; | |
| 442 | Status = 'COMPUTATION_DISABLED' -> get_preference(disabled_computation_colour,StatusColor) ; | |
| 443 | get_preference(not_executed_computation_colour,StatusColor)), % NOT_EXECUTED | |
| 444 | ajoin(['Computation: ',OpName,', Status: ',Status],Tooltip). | |
| 445 | ||
| 446 | :- public rule_graph_trans_predicate/4. | |
| 447 | rule_graph_trans_predicate(State,NodeID,SuccID,[label/''|Attr]) :- | |
| 448 | dependencies(NodeID,Deps), | |
| 449 | member(SuccID,Deps), | |
| 450 | get_trans_attributes(State,SuccID,Attr). | |
| 451 | ||
| 452 | get_trans_attributes(State,DepName,[color/StatusColor,tooltip/Tooltip]) :- | |
| 453 | get_simple_rule_status(State,DepName,Status), !, | |
| 454 | ((Status = 'FAIL' ; Status = 'DISABLED') | |
| 455 | -> get_preference(failed_dependency_colour,StatusColor), TTStatus = ' is not available.' ; | |
| 456 | Status = 'SUCCESS' -> get_preference(succeeded_dependency_colour,StatusColor), TTStatus = ' is available.' ; | |
| 457 | get_preference(normal_dependency_colour,StatusColor), TTStatus = ' is available.'), % NOT_CHECKED | |
| 458 | ajoin(['Required rule ',DepName,' with status ',Status,TTStatus],Tooltip). | |
| 459 | get_trans_attributes(State,DepName,[color/StatusColor,tooltip/Tooltip]) :- | |
| 460 | get_simple_computation_status(State,DepName,Status), !, | |
| 461 | (Status = 'COMPUTATION_DISABLED' -> get_preference(failed_dependency_colour,StatusColor), TTStatus = ' is not available.' ; | |
| 462 | get_preference(normal_dependency_colour,StatusColor), TTStatus = ' is available.'), | |
| 463 | ajoin(['Required computation ',DepName,' with status ',Status,TTStatus],Tooltip). | |
| 464 | %%%%%%%%% END DEPENDENCY GRAPH %%%%%%%%% |