| 1 | % (c) 2009-2024 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 | :- module(sap,[%reset_sap/0, | |
| 6 | explore_and_generate_testcases/7, | |
| 7 | cbc_gen_test_cases/5, cbc_gen_test_cases_from_string/5, | |
| 8 | cbc_get_uncovered_events/1, cbc_get_nr_uncovered_events/1, | |
| 9 | test_generation_by_xml_description/1, | |
| 10 | tcl_create_local_testsuite/5, | |
| 11 | tcl_get_events_preselected/2, tcl_convert_event_numbers_to_names/2, | |
| 12 | tcl_generate_cb_testcases/5, % not used? | |
| 13 | tcl_generate_testcases/7, | |
| 14 | tcl_get_stored_test_cases/1, tcl_execute_stored_test_case/1, | |
| 15 | stored_test_case_op_trace/2, | |
| 16 | cb_timeout_path/2, cb_path/3, cb_path_testcase_only/3, % provide access to cbc-tests tree | |
| 17 | write_all_deadlocking_paths_to_xml/1, | |
| 18 | cbc_gen_test_cases_task_step/7, cbc_gen_test_cases_finish_tasks/0, | |
| 19 | get_counter_info/4 | |
| 20 | ]). | |
| 21 | ||
| 22 | :- use_module(library(lists)). | |
| 23 | :- use_module(probsrc(xml_prob),[xml_parse/2]). | |
| 24 | :- use_module(library(timeout)). | |
| 25 | ||
| 26 | :- use_module(probsrc(error_manager)). | |
| 27 | :- use_module(probsrc(state_space)). | |
| 28 | :- use_module(probsrc(bmachine)). | |
| 29 | :- use_module(probsrc(b_state_model_check),[set_up_transition/7]). | |
| 30 | :- use_module(probsrc(preferences),[get_preference/2]). | |
| 31 | :- use_module(probsrc(specfile),[expand_const_and_vars_to_full_store/2,expand_to_constants_and_variables/3]). | |
| 32 | :- use_module(probsrc(translate),[translate_bvalue/2]). | |
| 33 | :- use_module(probsrc(prob2_interface),[set_eclipse_preference/2]). | |
| 34 | :- use_module(probsrc(kernel_objects),[not_equal_object/2]). | |
| 35 | ||
| 36 | :- use_module(probsrc(bsyntaxtree)). | |
| 37 | :- use_module(probsrc(tools)). | |
| 38 | :- use_module(probsrc(debug),[debug_mode/1,debug_println/2, printsilent/1,nls/0,silent_mode/1]). | |
| 39 | :- use_module(probsrc(store),[empty_state/1]). | |
| 40 | ||
| 41 | ||
| 42 | :- use_module(probsrc(module_information),[module_info/2]). | |
| 43 | :- module_info(group,test_generation). | |
| 44 | :- module_info(description,'This module contains code to generate test cases based on Event-B or Classical B models'). | |
| 45 | ||
| 46 | ||
| 47 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 48 | % some code for information in the GUI | |
| 49 | ||
| 50 | % get_events_preselected(-Events,-Preselect): | |
| 51 | % Events: a list of all events | |
| 52 | % Preselect: a list of numbers, each an index (starting with 0) of which event in Events should | |
| 53 | % be preselected because it is already covered in the state space | |
| 54 | % Both lists are wrapped by list(...) to use it from Tcl | |
| 55 | tcl_get_events_preselected(list(Events),list(Preselect)) :- | |
| 56 | get_all_events(Events), | |
| 57 | select_uncovered_operations(Events,0,Preselect). | |
| 58 | select_uncovered_operations([],_,[]). | |
| 59 | select_uncovered_operations([Event|Erest],N,Preselect) :- | |
| 60 | ( operation_already_covered_or_infeasible(Event) -> | |
| 61 | Preselect = PSrest | |
| 62 | ; | |
| 63 | Preselect = [N|PSrest]), | |
| 64 | N2 is N+1, | |
| 65 | select_uncovered_operations(Erest,N2,PSrest). | |
| 66 | ||
| 67 | :- use_module(cbcsrc(enabling_analysis),[infeasible_operation_cache/1]). | |
| 68 | operation_already_covered_or_infeasible(Event) :- | |
| 69 | \+ operation_name_not_yet_covered(Event). | |
| 70 | operation_already_covered_or_infeasible(Event) :- | |
| 71 | infeasible_operation_cache(Event). % check if determined to be infeasible; but do not trigger computation | |
| 72 | ||
| 73 | % returns a list of all events (their namese) in the model | |
| 74 | get_all_events(Events) :- | |
| 75 | findall(E,b_top_level_operation(E),Events). | |
| 76 | ||
| 77 | tcl_convert_event_numbers_to_names(list(EventNumbers),list(EventNames)) :- | |
| 78 | get_all_events(Events), | |
| 79 | maplist(get_event_name(Events),EventNumbers,EventNames). | |
| 80 | get_event_name(List,Number,Res) :- | |
| 81 | ((number(Number),nth0(Number,List,Res)) -> true | |
| 82 | ; add_error(get_event_name,'Illegal event number: ',Number),fail). | |
| 83 | ||
| 84 | % tcl_generate_testcases(+EventSelection,+TargetString,+MaxDepth,+MaxNewNodes,+Testfile,-NumberOfTestcases,-NrCovEvents): | |
| 85 | % EventSelection: a list of numbers containing all indeces of events that should be covered | |
| 86 | % TargetString: the predicate that identifies target nodes as string (atom) | |
| 87 | % MaxDepth: the maximum depth of the search | |
| 88 | % MaxNewNodes: the maximum number of newly explored nodes | |
| 89 | % Testfile: the file where we store the found test cases | |
| 90 | % NumberOfTestcases: The number of test cases that where found | |
| 91 | tcl_generate_testcases(EventSelection,TargetString,MaxDepth,MaxNewNodes,Testfile,NumberOfTestcases,NrCoveredEvents) :- | |
| 92 | % convert the list of event indeces into a list of events | |
| 93 | get_selected_events(EventSelection,AllEvents,Events), | |
| 94 | b_parse_optional_machine_predicate(TargetString,Target), | |
| 95 | set_max_nr_of_new_impl_trans_nodes(MaxNewNodes), | |
| 96 | safe_get_inits(Inits), | |
| 97 | ( explore_covered_statespace(Inits, Events, Target, MaxDepth, _ExploredDepth) -> | |
| 98 | true | |
| 99 | ; | |
| 100 | show_uncovered_events_as_errors), | |
| 101 | generate_test_cases(Inits,Testcases), | |
| 102 | length(Testcases,NumberOfTestcases), | |
| 103 | maplist(get_coverage_count,Testcases,Nrs), | |
| 104 | sumlist(Nrs,NrCoveredEvents), | |
| 105 | format('Generated ~w testcases covering ~w events~n',[NumberOfTestcases,NrCoveredEvents]), | |
| 106 | testcase_output(Testfile,AllEvents,Events,Testcases). | |
| 107 | ||
| 108 | ||
| 109 | get_coverage_count(global_test_case_for_events(Events,_),Len) :- !, length(Events,Len). | |
| 110 | get_coverage_count(global_test_case(_,_),Len) :- !, Len=1. | |
| 111 | get_coverage_count(_,0). | |
| 112 | ||
| 113 | ||
| 114 | % get_selected_events(+TkNumbers,-ListOfAllEvents,-SelectedEvents) | |
| 115 | % Input: TkNumbers: numbers starting at 0; comes from Tk Selection Box | |
| 116 | % Output: ListOfAllEvents | |
| 117 | % Output: SelectedEvents: Name of Selected Events | |
| 118 | get_selected_events(Selection,AllEvents,Events) :- | |
| 119 | get_all_events(AllEvents), | |
| 120 | (Selection==all -> Events=AllEvents | |
| 121 | ; get_selected_events2(Selection,AllEvents,Events)). | |
| 122 | get_selected_events2([],_AllEvents,[]). | |
| 123 | get_selected_events2([Selection|Srest],AllEvents,[Event|Erest]) :- | |
| 124 | nth0(Selection,AllEvents,Event),!, | |
| 125 | get_selected_events2(Srest,AllEvents,Erest). | |
| 126 | ||
| 127 | ||
| 128 | :- use_module(probsrc(state_space),[find_initialised_states/1]). | |
| 129 | ||
| 130 | safe_get_inits(Inits) :- | |
| 131 | find_initialised_states(Inits), | |
| 132 | (Inits = [] -> add_error(sap,'no initial states found'),fail ; true). | |
| 133 | ||
| 134 | % this predicate is called if the breadth-first search fails | |
| 135 | % it shows an error for every event, for which we did not find | |
| 136 | % an appropiate path | |
| 137 | show_uncovered_events_as_errors :- | |
| 138 | target_needed(Event), | |
| 139 | (infeasible_operation_cache(Event) | |
| 140 | -> ajoin(['No test path exists for infeasible (!) event ',Event],Msg) | |
| 141 | ; ajoin(['No test path found for event ',Event],Msg) | |
| 142 | ), | |
| 143 | add_error(sap,Msg), | |
| 144 | fail. | |
| 145 | show_uncovered_events_as_errors. | |
| 146 | ||
| 147 | get_uncovered_events(Uncovered) :- | |
| 148 | findall(Event, target_needed(Event), Uncovered). | |
| 149 | ||
| 150 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 151 | ||
| 152 | % model checking based test-case generation mcm | |
| 153 | ||
| 154 | explore_and_generate_testcases(InEvents,Target,MaxDepth,MaxNewNodes, | |
| 155 | Testfile,NumberOfTestcases,UncoveredEvents) :- | |
| 156 | % If no event is specified to be covered, we assume that all events | |
| 157 | % should be covered | |
| 158 | ( InEvents = [] -> | |
| 159 | printsilent('No events to cover specified, assuming all'),nls, | |
| 160 | get_all_events(Events) | |
| 161 | ; InEvents = [_|_] -> InEvents = Events | |
| 162 | ; add_internal_error('Illegal events: ',InEvents),fail | |
| 163 | ), | |
| 164 | statistics(walltime, [Start|_]), | |
| 165 | set_max_nr_of_new_impl_trans_nodes(MaxNewNodes), | |
| 166 | safe_get_inits(Inits), | |
| 167 | ( explore_covered_statespace(Inits, Events, Target, MaxDepth, _ExploredDepth) -> | |
| 168 | UncoveredEvents = [] | |
| 169 | ; | |
| 170 | get_uncovered_events(UncoveredEvents)), | |
| 171 | generate_test_cases(Inits, Testcases), | |
| 172 | length(Testcases, NumberOfTestcases), | |
| 173 | get_all_events(AllEvents), | |
| 174 | testcase_output(Testfile,AllEvents,Events,Testcases), | |
| 175 | statistics(walltime, [End|_]), | |
| 176 | Runtime is End - Start, | |
| 177 | format_msg('Runtime for test case generation: ~w ms~n',[Runtime]). | |
| 178 | ||
| 179 | ||
| 180 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 181 | % generate test cases from an explored state-space | |
| 182 | ||
| 183 | ||
| 184 | % generate_test_cases(+Inits,-TestCases): | |
| 185 | % Inits: List of initial states | |
| 186 | % TestCases: list of test cases, each of the form global_test_case_for_event(Event,Trace), | |
| 187 | % where Trace is a list of events | |
| 188 | generate_test_cases(Inits,TestCases) :- | |
| 189 | init_wanted_event_to_cover, | |
| 190 | findall(T, generate_test_case(Inits,T), Ts), | |
| 191 | sort(Ts,TestCases). | |
| 192 | % TO DO: if generate_minimal_nr_of_testcases is true we could still have redundant tests; eliminate them. | |
| 193 | ||
| 194 | :- dynamic wanted_event_to_cover/1. | |
| 195 | init_wanted_event_to_cover :- retractall(wanted_event_to_cover(_)), | |
| 196 | wanted_event(E), assertz(wanted_event_to_cover(E)),fail. | |
| 197 | init_wanted_event_to_cover. | |
| 198 | assert_covered(E) :- retractall(wanted_event_to_cover(E)). | |
| 199 | % find a single test case by first choosing one event and one initial state | |
| 200 | generate_test_case(Inits,global_test_case_for_events(WantedEventsCovered,Trace)) :- | |
| 201 | %fail, % SAP wants all test cases, not just for each event the shortest, | |
| 202 | !, | |
| 203 | % generate a single test_case per event | |
| 204 | wanted_event_to_cover(Event), | |
| 205 | (minimum_path_length(Event,MaxSteps) -> true ; debug_println(20,cannot_cover(Event)),fail), | |
| 206 | % this code could be improved: maybe a previous test-case has already covered this event ! | |
| 207 | debug_println(20,generating_testcase(Event,MaxSteps)), | |
| 208 | % now find a path that can serve as a test case | |
| 209 | (find_test_case_with_init(Event,Inits,MaxSteps,Trace) | |
| 210 | -> trace_covers_wanted_events(Trace,WantedEventsCovered), | |
| 211 | debug_println(44,found_trace(WantedEventsCovered,Trace)), | |
| 212 | (get_preference(generate_minimal_nr_of_testcases,true) | |
| 213 | -> maplist(assert_covered,WantedEventsCovered) | |
| 214 | ; true) | |
| 215 | ). | |
| 216 | generate_test_case(Inits,global_test_case_for_events(WantedEventsCovered,Trace)) :- | |
| 217 | % generate all paths up to certain depth; was requested by SAP, but can blow up | |
| 218 | find_global_maximum(MaxSteps), | |
| 219 | member(Init,Inits), | |
| 220 | find_way_to_target(Init,MaxSteps,Trace), | |
| 221 | trace_covers_wanted_events(Trace,WantedEventsCovered). | |
| 222 | ||
| 223 | trace_covers_wanted_events(Trace,WantedEventsCovered) :- | |
| 224 | findall(TestEvent,contains_an_event_to_test(TestEvent,Trace),Covered), | |
| 225 | Covered\=[], | |
| 226 | sort(Covered,WantedEventsCovered). | |
| 227 | contains_an_event_to_test(Event,Trace) :- | |
| 228 | member('$full_event'(Event,_Args),Trace),wanted_event_to_cover(Event). | |
| 229 | ||
| 230 | ||
| 231 | % find variables or constants which are non-deterministically assigned | |
| 232 | % TO DO: check max_reached of predecessor ?! | |
| 233 | non_deterministic_values(_,[],NonDetState) :- !, NonDetState=[]. | |
| 234 | non_deterministic_values(Init,OtherInits,NonDetState) :- | |
| 235 | findall(bind(ID,Value),non_deterministic_value(Init,OtherInits,ID,Value),NonDetState). | |
| 236 | ||
| 237 | non_deterministic_value(Init,OtherInits,ID,Value) :- | |
| 238 | expand_initalised_state(Init,State0), | |
| 239 | maplist(expand_initalised_state,OtherInits,OtherStates), | |
| 240 | member(bind(ID,Value),State0), | |
| 241 | (other_value_exists(ID,Value,OtherStates) -> true). | |
| 242 | ||
| 243 | other_value_exists(ID,Value,OtherStates) :- | |
| 244 | member(OtherState,OtherStates), | |
| 245 | member(bind(ID,OtherValue),OtherState), | |
| 246 | kernel_objects:not_equal_object(Value,OtherValue). | |
| 247 | ||
| 248 | % find the maximum of the minimum mapth length | |
| 249 | find_global_maximum(MaxSteps) :- | |
| 250 | findall(M, minimum_path_length(_Event,M), [First|Maxis]), | |
| 251 | find_global_maximum2(Maxis,First,MaxSteps). | |
| 252 | find_global_maximum2([],Max,Max). | |
| 253 | find_global_maximum2([H|T],A,Max) :- B is max(H,A), find_global_maximum2(T,B,Max). | |
| 254 | ||
| 255 | ||
| 256 | % find a single test case for a single event and a set of initial states | |
| 257 | find_test_case_with_init(Event,Inits,MaxSteps,ResTrace) :- | |
| 258 | select(Init,Inits,OtherInits), | |
| 259 | find_test_case(Event,Init,MaxSteps,TraceWithoutInit), | |
| 260 | non_deterministic_values(Init,OtherInits,NonDetState), | |
| 261 | (NonDetState=[] -> ResTrace = TraceWithoutInit | |
| 262 | ; ResTrace = ['$non_det_init'(NonDetState)|TraceWithoutInit]). | |
| 263 | ||
| 264 | % find a single test case for a single event and an initial state | |
| 265 | find_test_case(Event,Init,MaxSteps,Trace) :- | |
| 266 | % first find a path where Event occurs | |
| 267 | find_way_to_event(Init,MaxSteps,Event,Trace,TraceB,State,RestSteps), | |
| 268 | % then find a path from that state to a target state | |
| 269 | find_way_to_target(State,RestSteps,TraceB). | |
| 270 | ||
| 271 | % find_way_to_event(+A,+Max,+Event,-Trace,TraceR,-B,-RestSteps): | |
| 272 | % A: node where we start the search | |
| 273 | % Max: maximum number of steps in the path | |
| 274 | % Event: the event we are searching for | |
| 275 | % Trace: a trace to the state, the last event is Event, the list | |
| 276 | % has the variable TraceR as tail | |
| 277 | % B: the state after Event occurs | |
| 278 | % RestSteps: the number of remaining steps (i.e. RestSteps=Max-length(Trace)) | |
| 279 | find_way_to_event(A,Max,Event,['$full_event'(Operation,Args)|Trace],TraceR,B,RestSteps) :- | |
| 280 | dec(Max,Max2), | |
| 281 | do_full_event(A,Operation,Args,C), % TO DO: extract operation arguments | |
| 282 | ( Event = Operation -> | |
| 283 | Trace = TraceR, | |
| 284 | B = C, | |
| 285 | RestSteps = Max2 | |
| 286 | ; | |
| 287 | find_way_to_event(C,Max2,Event,Trace,TraceR,B,RestSteps)). | |
| 288 | ||
| 289 | find_way_to_target(A,_Max,[]) :- | |
| 290 | target_node(A). % removed the cut means that if target found straight away, no paths were generated : ,!. | |
| 291 | find_way_to_target(A,Max,['$full_event'(Operation,Args)|Trace]) :- | |
| 292 | dec(Max,Max2), | |
| 293 | do_full_event(A,Operation,Args,B), | |
| 294 | find_way_to_target(B,Max2,Trace). | |
| 295 | ||
| 296 | % decrease N, but never become negative | |
| 297 | dec(N,N2) :- N>0, N2 is N-1. | |
| 298 | ||
| 299 | :- use_module(probsrc(specfile),[get_operation_arguments/2, get_operation_name/2]). | |
| 300 | do_event(A,Event,B) :- | |
| 301 | transition(A,FullOperation,B), | |
| 302 | get_operation_name(FullOperation,Event). | |
| 303 | do_full_event(A,Event,Args,B) :- | |
| 304 | transition(A,FullOperation,B), | |
| 305 | get_operation_name(FullOperation,Event), | |
| 306 | get_operation_arguments(FullOperation,Args). | |
| 307 | ||
| 308 | :- use_module(library(ordsets)). | |
| 309 | % :- use_module(probsrc(sap)), sap:path_to_deadlock_or_loop(root,[],P), print(P),nl,fail. | |
| 310 | % compute all paths until a deadlock or non-interesting node is reached | |
| 311 | % assumes no loop !! otherwise an infinite number of solutions will be returned | |
| 312 | path_to_deadlock_or_loop(A,History,Path) :- ord_member(A,History), | |
| 313 | !, % loop | |
| 314 | Path = [loop(A)]. | |
| 315 | path_to_deadlock_or_loop(A,History,Path) :- ord_add_element(History,A,NH), | |
| 316 | if(%do_event(A,Event,B), | |
| 317 | %(transition(A,FullOperation,B), translate:translate_event(FullOperation,Event)), | |
| 318 | my_event(A,Event,B), | |
| 319 | (Path = [Event|P2], path_to_deadlock_or_loop(B,NH,P2)), | |
| 320 | Path = [] %[deadlock(A)] | |
| 321 | ). | |
| 322 | ||
| 323 | my_event(Src,(TransId,Op,Src,Dst),Dst) :- | |
| 324 | transition(Src,Op,TransId,Dst). | |
| 325 | ||
| 326 | % :- use_module(probsrc(sap)), sap:write_all_deadlocking_paths_to_xml('~/Desktop/all.xml'). | |
| 327 | write_all_deadlocking_paths_to_xml(Filename) :- | |
| 328 | retractall(path_nr(_)), assertz(path_nr(0)), | |
| 329 | open_xml_testcase(Filename), | |
| 330 | call_cleanup(write_path_to_deadlock_or_loop, close_xml_testcase), | |
| 331 | get_path_nr(Nr), | |
| 332 | format_msg('Wrote ~w tests to ~w.~n',[Nr,Filename]). | |
| 333 | ||
| 334 | write_path_to_deadlock_or_loop :- path_to_deadlock_or_loop(root,[],Trace), | |
| 335 | get_path_nr(Id), | |
| 336 | (debug_mode(on) -> format_msg('Trace ~w = ~w~n',[Id,Trace]) ; true), | |
| 337 | %write_testcase(global_test_case_for_event(unknown,Trace),Id), | |
| 338 | get_initialisation(Trace,RestTrace,State), | |
| 339 | cb_write_test_case(RestTrace,State), | |
| 340 | (exceeded_limit(Id) -> format_warn('Exceeded limit of number of traces: ~w~n',[Id]) | |
| 341 | ; Id>0, Id mod 500 =:= 0, format_msg('Wrote ~w tests ...~n',[Id]), | |
| 342 | fail). % continue backtracking | |
| 343 | write_path_to_deadlock_or_loop. | |
| 344 | ||
| 345 | exceeded_limit(100000). % do not write more than 100,000 states to avoid filling up disk | |
| 346 | % TO DO: add loop detection and/or user settable upper bound | |
| 347 | ||
| 348 | :- use_module(probsrc(specfile),[state_corresponds_to_initialised_b_machine/2]). | |
| 349 | :- use_module(probsrc(state_space),[visited_expression/2]). | |
| 350 | get_initialisation([(_,_,_,Dst)|T],Trace,InitBState) :- | |
| 351 | expand_initalised_state(Dst,InitBState),!, Trace=T. | |
| 352 | get_initialisation([_|T],Trace,State) :- get_initialisation(T,Trace,State). | |
| 353 | ||
| 354 | expand_initalised_state(ID,InitBState) :- | |
| 355 | state_space:visited_expression(ID,State), | |
| 356 | state_corresponds_to_initialised_b_machine(State,InitBState). | |
| 357 | ||
| 358 | :- dynamic path_nr/1. | |
| 359 | path_nr(0). | |
| 360 | get_path_nr(N) :- retract(path_nr(N)), N1 is N+1, assertz(path_nr(N1)). | |
| 361 | ||
| 362 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 363 | % explore the state-space so that it is usable for test-case generation | |
| 364 | ||
| 365 | % bfs_node(NodeId,Depth): open nodes in the state space, for breadth first search, | |
| 366 | % the order is relevant, Depth is the distance from the initialisation nodes | |
| 367 | % (later we call this the breadt-first FIFO queue) | |
| 368 | % predecessor(NodeId,PreNodeId): store predecessors of a node | |
| 369 | % target_reachable(NodeId,TargetDist): A target state is reachable from this state | |
| 370 | % event_dest(NodeId,Event): One of the events we want to cover leads to this node | |
| 371 | % node_covered(NodeId,Depth): This node was encountered before by the search at depth Depth. | |
| 372 | % target_needed(Event): We need to find a final from a state that is the destination of the event, | |
| 373 | % in the beginning, each given event is stored here | |
| 374 | % wanted_event(Event): Event if one of the events we are interested in | |
| 375 | % in the beginning, each given event is stored here | |
| 376 | % target_node(NodeId): The node is a target node | |
| 377 | ||
| 378 | :- volatile bfs_node/2, predecessor/2, target_reachable/2, event_dest/2. | |
| 379 | :- volatile node_covered/2, target_needed/1, wanted_event/1. | |
| 380 | :- volatile target_node/1, minimum_path_length/2. | |
| 381 | :- volatile maximum_depth_reached/1. | |
| 382 | :- dynamic bfs_node/2, predecessor/2, target_reachable/2, event_dest/2. | |
| 383 | :- dynamic node_covered/2, target_needed/1, wanted_event/1. | |
| 384 | :- dynamic target_node/1, minimum_path_length/2. | |
| 385 | :- dynamic maximum_depth_reached/1. | |
| 386 | ||
| 387 | % explore_covered_statespace(+Inits,+Events,+Target,-ExploredDepth): | |
| 388 | % Inits: a list of initialisation states | |
| 389 | % Events: the list of events that should be covered | |
| 390 | % Target: the predicate that is used to identify a target state | |
| 391 | % MaxDepth: the maximum depth that will be explored | |
| 392 | % ExploredDepth: the depth until the state-space was explored | |
| 393 | % This predicate explores a part of the state-space by breadt-first search | |
| 394 | % that contains for each event a path where the event occurs and that ends | |
| 395 | % in a target state. | |
| 396 | % Additionally, the state-space is explored to a fixed depth. | |
| 397 | % (i.e. if we needed a state that is reachable by 4 steps, than all states | |
| 398 | % that are reachable by 4 steps are explored with their transitions and | |
| 399 | % successor states) | |
| 400 | explore_covered_statespace(Inits,Events,Target,MaxDepth,ExploredDepth) :- | |
| 401 | % initialise the data structures | |
| 402 | reset_bfs(Events), | |
| 403 | ||
| 404 | % put initial states into the breadt-first FIFO queue | |
| 405 | assert_bfs_nodes(Inits,0,_,_), | |
| 406 | % check if the initialised states are target states | |
| 407 | mark_target_nodes(Inits,Target), | |
| 408 | ||
| 409 | % do the breadt-first search | |
| 410 | explore(Target,MaxDepth,-1,ExploredDepth). | |
| 411 | ||
| 412 | % explore(+Target,+PrevExploredDepth,-ExploredDepth): | |
| 413 | % Target: the predicate that is used to identify target nodes | |
| 414 | % MaxDepth: the maximum depth that will be explored | |
| 415 | % PrevExploredDepth: the deepest node that is explored before calling this predicate | |
| 416 | % ExploredDepth: the deepest node that was explored by the search | |
| 417 | explore(Target,_MaxDepth,ExploredDepth,ExploredDepth) :- | |
| 418 | % we do not have to find any more targets, we're finished | |
| 419 | \+ target_needed(_),!, | |
| 420 | % we did a breadth-first search until a certain depth, now we | |
| 421 | % complete the exploration of the state space to that depth | |
| 422 | complete_depth(Target,ExploredDepth). | |
| 423 | explore(Target,MaxDepth,_,ExploredDepth) :- | |
| 424 | % get a node from the breadth-first FIFO queue | |
| 425 | retract( bfs_node(Node,Depth) ),!, | |
| 426 | % and explore it | |
| 427 | explore_node(Node, Target, Depth), | |
| 428 | !, % just to be safe that no back-tracking occurs | |
| 429 | Depth =< MaxDepth, | |
| 430 | update_depth(Depth,MaxDepth), | |
| 431 | explore(Target,MaxDepth,Depth,ExploredDepth). | |
| 432 | ||
| 433 | :- use_module(probsrc(state_space),[get_state_space_stats/3]). | |
| 434 | update_depth(Depth,MaxDepth) :- maximum_depth_reached(MD), Depth>MD, | |
| 435 | !, | |
| 436 | get_state_space_stats(NrNodes,NrTransitions,ProcessedNodes), | |
| 437 | format_msg('Depth reached: ~w (max.: ~w), States: ~w (~w processed), Transitions: ~w~n',[Depth,MaxDepth,NrNodes,ProcessedNodes,NrTransitions]), | |
| 438 | retractall(maximum_depth_reached(_)), | |
| 439 | assertz(maximum_depth_reached(Depth)). | |
| 440 | update_depth(_,_). | |
| 441 | ||
| 442 | % explore_node(+Node,+Target,+Depth): | |
| 443 | % Node: the node that should be explored | |
| 444 | % Target: the predicate that is used to identify target nodes | |
| 445 | % Depth: depth of the given node | |
| 446 | explore_node(Node,Target,Depth) :- | |
| 447 | % newly encountered nodes are one level deeper | |
| 448 | NewDepth is Depth+1, | |
| 449 | % get all transitions from this node to successor nodes | |
| 450 | impl_trans_term_all(Node,Transitions), | |
| 451 | % check if there are events we are interested in and | |
| 452 | % get the successor nodes | |
| 453 | analyse_transitions(Transitions,Successors), | |
| 454 | % find out wich of the successor nodes are new and | |
| 455 | % store those in the breadt-first FIFO queue | |
| 456 | assert_bfs_nodes(Successors,NewDepth,NewNodes,OldNodes), | |
| 457 | % store that this node is a predecessor node of | |
| 458 | % all the successor nodes encountered | |
| 459 | assert_predecessors(Successors, Node), | |
| 460 | % find out which of the new nodes are target nodes | |
| 461 | % and in such a case propagate the information that a target | |
| 462 | % note is reachable back into the network. | |
| 463 | mark_target_nodes(NewNodes,Target), | |
| 464 | mark_target_nodes_by_old(OldNodes,Node). | |
| 465 | ||
| 466 | mark_target_nodes_by_old([],_Node). | |
| 467 | mark_target_nodes_by_old([PotentialTargetNode|Rest],Node) :- | |
| 468 | ( target_reachable(PotentialTargetNode,DistToTarget) -> | |
| 469 | NewDist is DistToTarget + 1, | |
| 470 | update_minimal_path_for_events(Node,PotentialTargetNode,DistToTarget), | |
| 471 | assert_target_reachable(Node,NewDist) | |
| 472 | ; true), | |
| 473 | mark_target_nodes_by_old(Rest,Node). | |
| 474 | ||
| 475 | ||
| 476 | % mark_target_nodes(+Nodes,+Target). | |
| 477 | % Nodes: a list of node IDs | |
| 478 | % Target: the predicate to check whether the node is a target node | |
| 479 | % PreDepth: the depth of the node that led to this node, | |
| 480 | % checks for each node in the given list if it is a target | |
| 481 | % node, and if yes then propagate the information that the | |
| 482 | % target is reachable to all predecessor nodes. | |
| 483 | :- use_module(probsrc(eclipse_interface),[test_boolean_expression_in_node/3]). | |
| 484 | mark_target_nodes([],_Target). | |
| 485 | mark_target_nodes([Node|NRest],Target) :- | |
| 486 | ( test_boolean_expression_in_node(Node,Target,'testcase target') -> | |
| 487 | assertz( target_node(Node) ), | |
| 488 | assert_target_reachable(Node,1) | |
| 489 | ; true), | |
| 490 | mark_target_nodes(NRest,Target). | |
| 491 | ||
| 492 | % for all transitions, check if the resp. event is interesting | |
| 493 | % and if yes, mark the successor node. | |
| 494 | % and return the list of successor nodes. | |
| 495 | analyse_transitions([],[]). | |
| 496 | analyse_transitions([op(_Id,Operation,Succ)|Orest],[Succ|Srest]) :- | |
| 497 | % the arguments of the event are not interesting to us | |
| 498 | specfile:get_operation_name(Operation,Event), %functor(Operation,Event,_), | |
| 499 | % if Event is one of the events we monitor, we mark | |
| 500 | % the destination node as a node from where we look for | |
| 501 | % a path to a target node. | |
| 502 | (wanted_event(Event) -> assert_event_dest(Succ,Event) ; true), | |
| 503 | analyse_transitions(Orest,Srest). | |
| 504 | ||
| 505 | % store the information, that the Node is reached by Event. | |
| 506 | % We need this, because we need such a node fro | |
| 507 | assert_event_dest(Node,Event) :- | |
| 508 | ( event_dest(Node,Event) -> true % information was already present, ignore | |
| 509 | ; | |
| 510 | assertz( event_dest(Node,Event) ), | |
| 511 | % if a target node can be reached from Node, we have a path in the explored | |
| 512 | % state-space that includes the Event and ends in a target node. | |
| 513 | ( target_reachable(Node,_) -> | |
| 514 | ( retract( target_needed(Event) ) -> true; true) | |
| 515 | ; true)). | |
| 516 | ||
| 517 | % reset all data structures needed for the breadth-first search | |
| 518 | reset_bfs(Events) :- | |
| 519 | retractall(bfs_node(_,_)), | |
| 520 | retractall(predecessor(_,_)), | |
| 521 | retractall(target_reachable(_,_)), | |
| 522 | retractall(node_covered(_,_)), | |
| 523 | retractall(event_dest(_,_)), | |
| 524 | retractall(target_needed(_)), | |
| 525 | retractall(wanted_event(_)), | |
| 526 | retractall(target_node(_)), | |
| 527 | retractall(minimum_path_length(_,_)), | |
| 528 | retractall(maximum_depth_reached(_)), | |
| 529 | assertz( maximum_depth_reached(0)), | |
| 530 | reset_bfs2(Events). | |
| 531 | reset_bfs2([]). | |
| 532 | reset_bfs2([Event|Rest]) :- | |
| 533 | (b_top_level_operation(Event) -> true | |
| 534 | ; add_warning(sap,'Unknown event: ',Event)), | |
| 535 | assertz( target_needed(Event) ), | |
| 536 | assertz( wanted_event(Event) ), | |
| 537 | reset_bfs2(Rest). | |
| 538 | ||
| 539 | % assert_bfs_nodes(+Nodes,+Depth,-NewNodes): | |
| 540 | % Nodes: a list of nodes | |
| 541 | % Depth: the current depth of the search, this is stored for finding | |
| 542 | % out later until which depth the search has been done | |
| 543 | % NewNodes: a sublist of Nodes, that includes all nodes that have not been | |
| 544 | % encountered before | |
| 545 | % This predicate checks if the given nodes are already encountered and if | |
| 546 | % not, it stores them into the breadt-first FIFO queue | |
| 547 | assert_bfs_nodes([],_,[],[]). | |
| 548 | assert_bfs_nodes([Node|Rest],Depth,NewNodes,OldNodes) :- | |
| 549 | ( node_covered(Node,_NodeDepth) -> % we already encountered Node, ignore | |
| 550 | NewNodes = NewRest, | |
| 551 | OldNodes = [Node|OldRest] | |
| 552 | ; % this is a new node | |
| 553 | NewNodes = [Node|NewRest], | |
| 554 | OldNodes = OldRest, | |
| 555 | assertz( node_covered(Node,Depth) ), % mark as covered | |
| 556 | assertz( bfs_node(Node,Depth) )), % put it into the FIFO queue for the breadth-first search | |
| 557 | assert_bfs_nodes(Rest,Depth,NewRest,OldRest). | |
| 558 | ||
| 559 | % assert_predecessors(+Nodes,+Predecessor): | |
| 560 | % Nodes: a list of nodes | |
| 561 | % Predecessor: the predecessor node, a node from where a transition | |
| 562 | % leads to each of the nodes in Nodes | |
| 563 | % This predicate stores for each node in Nodes that Predecessor is a | |
| 564 | % predecessor of the node. | |
| 565 | % This information is needed to be able to propagate information of | |
| 566 | % a reachable target state back into the graph. | |
| 567 | assert_predecessors([],_). | |
| 568 | assert_predecessors([Node|Rest],Pre) :- | |
| 569 | ( predecessor(Node,Pre) -> true % information already there, ignore | |
| 570 | ; assertz(predecessor(Node,Pre))), | |
| 571 | assert_predecessors(Rest,Pre). | |
| 572 | ||
| 573 | % store the information that a target node is reachable from a node | |
| 574 | % propagate this information to predecessor nodes if necessary | |
| 575 | assert_target_reachable(Node,DistToTarget) :- | |
| 576 | ( target_reachable(Node,OldDistToTarget) -> | |
| 577 | ( OldDistToTarget =< DistToTarget -> | |
| 578 | true % information already there, ignore | |
| 579 | ; | |
| 580 | retract( target_reachable(Node,_) ), | |
| 581 | assert_target_reachable2(Node,DistToTarget) | |
| 582 | ) | |
| 583 | ; | |
| 584 | assert_target_reachable2(Node,DistToTarget) | |
| 585 | ). | |
| 586 | assert_target_reachable2(Node,DistToTarget) :- | |
| 587 | assertz( target_reachable(Node,DistToTarget) ), fail. | |
| 588 | assert_target_reachable2(Node,_DistToTarget) :- | |
| 589 | % the node can be reached by Event, so we know | |
| 590 | % that we have a path in the explored state-space that contains | |
| 591 | % Event a ends in a target node. | |
| 592 | event_dest(Node,Event), | |
| 593 | retract( target_needed(Event) ), | |
| 594 | % (this is a fail-loop) | |
| 595 | fail. | |
| 596 | assert_target_reachable2(Node,DistToTarget) :- | |
| 597 | % propagate the information to the predecessors | |
| 598 | % (if we can reach a target node from this node, we also can | |
| 599 | % reach the target node from each predecessor node.) | |
| 600 | predecessor(Node,Pre), | |
| 601 | update_minimal_path_for_events(Pre,Node,DistToTarget), | |
| 602 | NewDistToTarget is DistToTarget + 1, | |
| 603 | assert_target_reachable(Pre,NewDistToTarget), | |
| 604 | % (this is a fail-loop) | |
| 605 | fail. | |
| 606 | assert_target_reachable2(_,_). | |
| 607 | ||
| 608 | % Pre: node where the event starts | |
| 609 | % Post: node where the events leads to | |
| 610 | % DistToTarget: number of steps to reach the target when starting in the | |
| 611 | % pre node. | |
| 612 | % For each event that should be covered, we store the minimal length of the | |
| 613 | % path needed to cover that event. | |
| 614 | % We store and compute that information while going back from the target node | |
| 615 | % to set the target_reachable flag for states. | |
| 616 | % To do that, we add the distance to the target to the depth of the node. The | |
| 617 | % encountered minimum for each event is stored in minimum_path_length/2. | |
| 618 | update_minimal_path_for_events(Pre,Post,DistToTarget) :- | |
| 619 | do_event(Pre,Event,Post), | |
| 620 | wanted_event(Event), | |
| 621 | node_covered(Pre,NodeDepth), | |
| 622 | MinLength is DistToTarget + NodeDepth, | |
| 623 | ( minimum_path_length(Event,Old) -> | |
| 624 | Old > MinLength, | |
| 625 | retractall( minimum_path_length(Event,_) ), | |
| 626 | assertz( minimum_path_length(Event,MinLength) ) | |
| 627 | ; | |
| 628 | format_ok('Covered: ~w (length: ~w)~n',[Event,MinLength]), | |
| 629 | assertz( minimum_path_length(Event,MinLength) )), | |
| 630 | fail. | |
| 631 | update_minimal_path_for_events(_Pre,_Post,_DistToTarget). | |
| 632 | ||
| 633 | % explore the state-space to the given depth | |
| 634 | complete_depth(Target,Depth) :- | |
| 635 | retract( bfs_node(Node,NodeDepth) ),!, | |
| 636 | ( NodeDepth =< Depth -> | |
| 637 | explore_node(Node,Target,Depth), | |
| 638 | complete_depth(Target,Depth) | |
| 639 | ; | |
| 640 | % the next nodes are deeper, just revert the previous retract | |
| 641 | % (this might not be necessary, because the data may not be used | |
| 642 | % anymore after this, but just to keep it cleaner) | |
| 643 | asserta( bfs_node(Node,NodeDepth) )). | |
| 644 | complete_depth(_Target,_Depth). | |
| 645 | ||
| 646 | ||
| 647 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 648 | % XML output | |
| 649 | ||
| 650 | testcase_output(Filename,_AllEvents,_SelectedEvents,Testcases) :- | |
| 651 | open_xml_testcase(Filename), | |
| 652 | % write_selected_events(AllEvents,SelectedEvents), | |
| 653 | call_cleanup((write_testcases(Testcases,1) -> true ; add_internal_error('Generating testcases failed: ',Filename)), | |
| 654 | close_xml_testcase). | |
| 655 | ||
| 656 | :- use_module(probsrc(tools_io)). | |
| 657 | :- dynamic no_xml_file/0. | |
| 658 | ||
| 659 | open_xml_testcase('') :- assertz(no_xml_file),!. | |
| 660 | open_xml_testcase(Filename) :- | |
| 661 | safe_open_file(Filename,write,_Stream,[alias(xml_testcase),encoding(utf8)]), | |
| 662 | absolute_file_name(Filename,AF), format_msg('% Writing testcases to ~w~n',[AF]), | |
| 663 | xml_write('<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<extended_test_suite>\n'). | |
| 664 | ||
| 665 | close_xml_testcase :- retract(no_xml_file),!. | |
| 666 | close_xml_testcase :- | |
| 667 | xml_write('</extended_test_suite>\n'), | |
| 668 | close(xml_testcase). | |
| 669 | ||
| 670 | xml_write(Atom) :- no_xml_file,!, (debug_mode(on) -> write(Atom) ; true). | |
| 671 | xml_write(Atom) :- write(xml_testcase,Atom). | |
| 672 | ||
| 673 | :- use_module(probsrc(tools_files),[put_codes/2]). | |
| 674 | put_codes_xml(Codes) :- no_xml_file,!, (debug_mode(on) -> put_codes(Codes,user_output) ; true). | |
| 675 | put_codes_xml(Codes) :- put_codes(Codes,xml_testcase). | |
| 676 | ||
| 677 | flush_output_xml :- no_xml_file,!. | |
| 678 | flush_output_xml :- flush_output(xml_testcase). | |
| 679 | ||
| 680 | /* | |
| 681 | write_selected_events(AllEvents,Events) :- | |
| 682 | xml_write(' <global_events>\n'), | |
| 683 | write_selected_events2(AllEvents,Events), | |
| 684 | xml_write(' </global_events>\n'). | |
| 685 | write_selected_events2([],_Selected). | |
| 686 | write_selected_events2([Event|Rest],Selected) :- | |
| 687 | (member(Event,Selected) -> Sel = true ; Sel = false), | |
| 688 | xml_write(' <event name=\"'),xml_write(Event), | |
| 689 | xml_write('\" covered=\"'),xml_write(Sel), | |
| 690 | xml_write('\"/>\n'), | |
| 691 | write_selected_events2(Rest,Selected). | |
| 692 | */ | |
| 693 | ||
| 694 | ||
| 695 | write_testcases([],_). | |
| 696 | write_testcases([Testcase|TCrest],N) :- | |
| 697 | write_testcase(Testcase,N), | |
| 698 | N2 is N+1, | |
| 699 | write_testcases(TCrest,N2). | |
| 700 | ||
| 701 | write_testcase(global_test_case_for_events(Events,Trace),N) :- | |
| 702 | number_codes(N,Id), | |
| 703 | write_testcase2(Trace,[],Id,Events). | |
| 704 | write_testcase(global_test_case(Id,Trace),_N) :- | |
| 705 | write_testcase2(Trace,[],Id,unknown). | |
| 706 | write_testcase(local_test_case(Id,Global,Local),_N) :- | |
| 707 | write_testcase2(Global,[Local],Id,unknown). | |
| 708 | ||
| 709 | write_testcase2(GlobalTrace,LocalTraces,Id,Events) :- | |
| 710 | xml_write(' <test_case id=\"'),put_codes_xml(Id), | |
| 711 | (Events=unknown -> true | |
| 712 | ; xml_write('\" targets=\"'), xml_write(Events)), | |
| 713 | xml_write('\">\n'), | |
| 714 | write_trace(global,GlobalTrace), | |
| 715 | write_traces(LocalTraces,local), | |
| 716 | xml_write(' </test_case>\n'). | |
| 717 | ||
| 718 | write_traces([],_Type). | |
| 719 | write_traces([Trace|Trest],Type) :- | |
| 720 | write_trace(Type,Trace), | |
| 721 | write_traces(Trest,Type). | |
| 722 | write_trace(Type,Trace) :- | |
| 723 | xml_write(' <'),xml_write(Type),xml_write('>\n'), | |
| 724 | write_trace2(Trace,1), | |
| 725 | xml_write(' </'),xml_write(Type),xml_write('>\n'). | |
| 726 | write_trace2([],_). | |
| 727 | write_trace2([Step|Trace],N) :- | |
| 728 | write_step(Step,N), | |
| 729 | N2 is N+1, | |
| 730 | write_trace2(Trace,N2). | |
| 731 | ||
| 732 | write_step('$non_det_init'(NonDetState),N) :- !, | |
| 733 | write_step_tag(N,'INITIALISATION'), | |
| 734 | maplist(xml_write_binding(value),NonDetState), | |
| 735 | xml_write(' </step>\n'). | |
| 736 | write_step('$full_event'(Event,[]),N) :- !,write_step(Event,N). | |
| 737 | write_step('$full_event'(Event,Args),N) :- | |
| 738 | write_step_tag(N,Event), | |
| 739 | b_get_machine_operation(Event,_,Parameters,_), | |
| 740 | xml_write_parameters(Event,Parameters,Args), | |
| 741 | xml_write(' </step>\n'). | |
| 742 | write_step(Event,N) :- | |
| 743 | %xml_write(' <step id=\"'), xml_write(N), xml_write('\">'), xml_write(Step), xml_write(' </step>\n'). | |
| 744 | xml_write(' <step id=\"'), | |
| 745 | xml_write(N), | |
| 746 | xml_write('\" name=\"'), | |
| 747 | xml_write(Event), | |
| 748 | xml_write('\" />\n'). | |
| 749 | ||
| 750 | write_step_tag(N,Event) :- | |
| 751 | xml_write(' <step id=\"'), | |
| 752 | xml_write(N), | |
| 753 | xml_write('\" name=\"'), | |
| 754 | xml_write(Event), | |
| 755 | xml_write('\">\n'). | |
| 756 | ||
| 757 | ||
| 758 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 759 | % local test case generation | |
| 760 | ||
| 761 | :- volatile is_local_event/1, is_global_event/2. | |
| 762 | :- dynamic is_local_event/1, is_global_event/2. | |
| 763 | ||
| 764 | tcl_create_local_testsuite(GlobalFile,LocalFile,MaxNewNodes,NumSat,NumUnsat) :- | |
| 765 | read_xml_testcases(GlobalFile,Global), | |
| 766 | set_max_nr_of_new_impl_trans_nodes(MaxNewNodes), | |
| 767 | process_testsuite(Global,Local,Unsatisfied), | |
| 768 | length(Local,NumSat),length(Unsatisfied,NumUnsat), | |
| 769 | append(Local,Unsatisfied,Testcases), | |
| 770 | testcase_output(LocalFile,[],[],Testcases). | |
| 771 | ||
| 772 | process_testsuite(GlobalTestsuite,LocalTestsuite,Unsatisfied) :- | |
| 773 | reset_local_search, | |
| 774 | safe_get_inits(Inits), | |
| 775 | process_testsuite2(GlobalTestsuite,Inits,LocalTestsuite,Unsatisfied). | |
| 776 | process_testsuite2([],_Inits,[],[]). | |
| 777 | process_testsuite2([Global|Grest],Inits,LocalTestsuite,Unsatisfied) :- | |
| 778 | get_testcase_id(Global,Id), | |
| 779 | ( find_local_testcase(Global,Inits,Local) -> | |
| 780 | write('++ found local testcase: '),print(Id),nl, | |
| 781 | LocalTestsuite = [Local|Lnext], | |
| 782 | Unsatisfied = Unext | |
| 783 | ; | |
| 784 | write('-- no local testcase: '),print(Id),nl, | |
| 785 | LocalTestsuite = Lnext, | |
| 786 | Unsatisfied = [Global|Unext]), | |
| 787 | process_testsuite2(Grest,Inits,Lnext,Unext). | |
| 788 | ||
| 789 | get_testcase_id(global_test_case(Id,_),I) :- atom_codes(I,Id). | |
| 790 | ||
| 791 | % find_local_testcase(+Global,+Inits,-Local): | |
| 792 | % Global: a global test case | |
| 793 | % Inits: list of initial states | |
| 794 | % Local: a local test case | |
| 795 | % search for a local trace of a global test case in the current model. | |
| 796 | % A local trace consists of refined events of events of the global model, | |
| 797 | % interspered by local events (i.e. events that refine skip) | |
| 798 | find_local_testcase(global_test_case(Id,Global),Inits,local_test_case(Id,Global,Local)) :- | |
| 799 | member(Init,Inits), | |
| 800 | find_local_trace(Global,Init,Local). | |
| 801 | ||
| 802 | find_local_trace([],_State,[]). | |
| 803 | find_local_trace([GlobalEvent|Grest],State,LocalTrace) :- | |
| 804 | % do a number of local steps (without loop) until the global event | |
| 805 | find_local_path_to_global_event(State,GlobalEvent,[State],LocalTrace,RTrace,Dest), | |
| 806 | % continue with the next global event | |
| 807 | find_local_trace(Grest,Dest,RTrace). | |
| 808 | ||
| 809 | % find_local_path_to_global_event(+State,+GlobalEvent,+History,-Trace,?RTrace,-End): | |
| 810 | % State: the state where the search starts | |
| 811 | % GlobalEvent: the global event that we are searching for, until reaching it, only | |
| 812 | % local events are taken | |
| 813 | % History: the states that we encountered before in this search, to prevent loops | |
| 814 | % Trace: the resulting trace, the last element is the global event, the tail of the | |
| 815 | % list is uninstantiated | |
| 816 | % RTrace: the tail of Trace | |
| 817 | % End: the last state in the search, the global event leads into this state | |
| 818 | :- use_module(probsrc(state_space),[compute_transitions_if_necessary/1]). | |
| 819 | find_local_path_to_global_event(State,GlobalEvent,History,[Event|Trace],RTrace,End) :- | |
| 820 | % make sure this node is explored | |
| 821 | compute_transitions_if_necessary(State), % this may use a ltllimit (max_nr_of_new_nodes) | |
| 822 | % do an (non-deterministic) step | |
| 823 | do_event(State,Event,Dest), | |
| 824 | ( is_local_event(Event) -> | |
| 825 | % local events can be taken as long we do not loop | |
| 826 | \+ member(Dest,History), | |
| 827 | % continue the search with the new state | |
| 828 | find_local_path_to_global_event(Dest,GlobalEvent,[Dest|History],Trace,RTrace,End) | |
| 829 | ; is_global_event(Event,GlobalEvent) -> | |
| 830 | % if we encounter the global event we search for, we can stop | |
| 831 | Trace = RTrace, | |
| 832 | Dest = End). | |
| 833 | ||
| 834 | % initial the data structures for the search | |
| 835 | reset_local_search :- | |
| 836 | get_all_events(Events), | |
| 837 | retractall(is_local_event(_)), | |
| 838 | retractall(is_global_event(_,_)), | |
| 839 | assert_local_events(Events). | |
| 840 | ||
| 841 | % for each event, we store whether it's local or global, | |
| 842 | % and if it's global, we store the refined event | |
| 843 | assert_local_events([]). | |
| 844 | assert_local_events([Event|Erest]) :- | |
| 845 | get_operation_info(Event,Info), | |
| 846 | ( member(refines(Ref),Info) -> | |
| 847 | assert_abstract_events(Event,Ref) | |
| 848 | ; assertz(is_local_event(Event))), | |
| 849 | assert_local_events(Erest). | |
| 850 | assert_abstract_events(Event,Refines) :- | |
| 851 | member(Abstract,Refines), assertz( is_global_event(Event,Abstract) ), | |
| 852 | % (fail-loop) | |
| 853 | fail. | |
| 854 | assert_abstract_events(_,_). | |
| 855 | ||
| 856 | ||
| 857 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 858 | % read XML testcases | |
| 859 | read_xml_testcases(Filename,TestCases) :- | |
| 860 | safe_read_string_from_file(Filename,utf8,Codes), | |
| 861 | xml_parse(Codes,xml(_Atts,Content)), | |
| 862 | member(element(extended_test_suite,_,XmlTestSuite),Content), | |
| 863 | extract_testcases(XmlTestSuite,TestCases). | |
| 864 | extract_testcases(Content,TestCases) :- | |
| 865 | findall(TC, extract_testcase(Content,TC), TestCases). | |
| 866 | extract_testcase(Content,global_test_case(Id,GlobalTrace)) :- | |
| 867 | member(element(test_case,Atts,EContent),Content), | |
| 868 | member(id=Id, Atts), | |
| 869 | member(element(global,_,GContent),EContent), | |
| 870 | findall(Step, | |
| 871 | ( member(element(step,_,[pcdata(P)]),GContent),atom_codes(Step,P) ), | |
| 872 | GlobalTrace). | |
| 873 | ||
| 874 | ||
| 875 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 876 | % dot debugger | |
| 877 | ||
| 878 | :- use_module(library(process),[process_create/3,process_wait/2]). | |
| 879 | :- public sapdebug/0. | |
| 880 | sapdebug :- | |
| 881 | process_create(path(dot), | |
| 882 | ['-T','pdf','-o','sap_debug.pdf'], | |
| 883 | [stdin(pipe(S)),process(P)]), | |
| 884 | write(S,'digraph sap_debug {\n'), | |
| 885 | sapwrite_nodes(S), | |
| 886 | write(S,'\n'), | |
| 887 | sapwrite_edges(S), | |
| 888 | write(S,'}\n'), | |
| 889 | close(S), | |
| 890 | process_wait(P,_). | |
| 891 | ||
| 892 | sapwrite_nodes(S) :- | |
| 893 | node_covered(Node,_), | |
| 894 | ( bfs_node(Node,_) -> Shape = triangle; Shape = box ), | |
| 895 | ( target_reachable(Node,_) -> Style = ',filled'; Style = ''), | |
| 896 | write_l(S,[' ',Node,' [shape=',Shape,', style=\"rounded',Style,'\"];\n']), | |
| 897 | fail. | |
| 898 | sapwrite_nodes(_). | |
| 899 | ||
| 900 | sapwrite_edges(S) :- | |
| 901 | predecessor(Post,Pre), | |
| 902 | write_l(S,[' ',Pre,' -> ',Post,';']), | |
| 903 | fail. | |
| 904 | sapwrite_edges(_). | |
| 905 | ||
| 906 | write_l(_S,[]) :- !. | |
| 907 | write_l(S,[H|T]) :- | |
| 908 | write(S,H),write_l(S,T). | |
| 909 | ||
| 910 | ||
| 911 | ||
| 912 | ||
| 913 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% | |
| 914 | % constraint based test case generation | |
| 915 | ||
| 916 | tcl_generate_cb_testcases(EventSelection,TargetString,MaxDepth,Filename,list(Uncovered)) :- | |
| 917 | % first translate Tk EventSelection Number list into Events | |
| 918 | get_selected_events(EventSelection,_AllEvents,Events), | |
| 919 | cbc_gen_test_cases_from_string(Events,TargetString,MaxDepth,Filename,Uncovered). | |
| 920 | ||
| 921 | % TargetString can be #not_invariant for BMC - bounded model checking | |
| 922 | cbc_gen_test_cases_from_string(Events,TargetString,MaxDepth,Filename,Uncovered) :- | |
| 923 | % parse target predicate for test-case sequences | |
| 924 | parse_cbc_target_predicate(TargetString,Target), | |
| 925 | cbc_gen_test_cases(Events,Target,MaxDepth,Filename,Uncovered). | |
| 926 | ||
| 927 | % cb_uncovered_event/1: event is not covered in any test-case; but could by covered in a path | |
| 928 | :- volatile cb_path/3, cb_path_testcase_only/3, cb_uncovered_event/1, cb_target_event/1, cb_final_event/1. | |
| 929 | :- volatile cb_try_state_caching/0. | |
| 930 | :- dynamic cb_path/3, cb_path_testcase_only/3, cb_uncovered_event/1, cb_target_event/1, cb_final_event/1. | |
| 931 | :- dynamic cb_try_state_caching/0. | |
| 932 | ||
| 933 | print_cb_uncovered_events :- %print_prefs | |
| 934 | findall(E,cb_uncovered_event_in_path(E),L1), | |
| 935 | findall(E,(cb_uncovered_event(E), \+cb_uncovered_event_in_path(E)),L2), | |
| 936 | % cb_feasible_uncovered_event \+ infeasible_operation_cache(E) | |
| 937 | length(L1,Len1), length(L2,Len2), | |
| 938 | counter(total_events,Tot), | |
| 939 | test_case_name(TCN), | |
| 940 | ((debug_mode(on); Len1+Len2<160) | |
| 941 | -> format_silent_msg('Completely uncovered events: ~w/~w : ~w~n',[Len1,Tot,L1]), | |
| 942 | format_silent_msg('Covered but no ~w yet: ~w/~w : ~w~n',[TCN,Len2,Tot,L2]) | |
| 943 | ; | |
| 944 | format_silent_msg('Completely uncovered events: ~w/~w, Covered but no ~w: ~w~n',[Len1,Tot,TCN,Len2]) | |
| 945 | ). | |
| 946 | ||
| 947 | test_case_name('counter-example') :- bounded_model_checking,!. | |
| 948 | test_case_name('test-case'). | |
| 949 | ||
| 950 | %print_prefs :- | |
| 951 | % get_preference(use_smt_mode,SMT), get_preference(use_chr_solver,CHR), | |
| 952 | % get_preference(use_clpfd_solver,CLPFD), get_preference(use_po,PO), | |
| 953 | % format('Preferences: SMT = ~w, CHR=~w, CLPFD=~w, PROOF_INFO=~w~n',[SMT,CHR,CLPFD,PO]). | |
| 954 | % constraint-based test case generation: one step predicate | |
| 955 | :- use_module(probsrc(solver_interface), [call_with_smt_mode_enabled/1]). | |
| 956 | cbc_gen_test_cases(EventsToCover,TargetPredicate,MaxLength,Filename,UncoveredEvents) :- | |
| 957 | cbc_reset_gen_test_cases(EventsToCover), | |
| 958 | open_xml_testcase(Filename), | |
| 959 | get_preference(time_out,Timeout), | |
| 960 | call_with_smt_mode_enabled( | |
| 961 | call_cleanup(cb_timed_testcases(TargetPredicate, MaxLength, Timeout), | |
| 962 | close_xml_testcase)), | |
| 963 | cbc_get_uncovered_events(UncoveredEvents). | |
| 964 | ||
| 965 | cbc_reset_gen_test_cases(EventsToCover) :- | |
| 966 | reset_test_cases, | |
| 967 | cb_test_cases_init_search(EventsToCover). | |
| 968 | ||
| 969 | cbc_get_uncovered_events(UncoveredEvents) :- | |
| 970 | findall(E,cb_uncovered_event(E),UncoveredEvents). | |
| 971 | cbc_get_nr_uncovered_events(Len) :- | |
| 972 | cbc_get_uncovered_events(UncoveredEvents), | |
| 973 | length(UncoveredEvents,Len). | |
| 974 | ||
| 975 | % constraint-based test case generation: multi step predicate; for use by prologTasks module | |
| 976 | :- use_module(library(between),[between/3]). | |
| 977 | cbc_gen_test_cases_task_step(EventSelection,TargetString,MaxLength,Filename, Final, Len, Task) :- | |
| 978 | % first translate Tk EventSelection Number list into Events | |
| 979 | get_selected_events(EventSelection,_AllEvents,EventsToCover), | |
| 980 | % parse target predicate for test-case sequences | |
| 981 | parse_cbc_target_predicate(TargetString,TargetPredicate), | |
| 982 | ||
| 983 | (Final=1 -> cbc_reset_gen_test_cases(final(EventsToCover)) | |
| 984 | ; cbc_reset_gen_test_cases(EventsToCover)), | |
| 985 | get_preference(time_out,Timeout), | |
| 986 | ||
| 987 | open_xml_testcase(Filename), | |
| 988 | Task = sap: cb_test_cases_step(Len,TargetPredicate,MaxLength,Timeout,_), | |
| 989 | between(1,MaxLength,Len). | |
| 990 | ||
| 991 | cbc_gen_test_cases_finish_tasks :- | |
| 992 | close_xml_testcase. | |
| 993 | ||
| 994 | cb_timed_testcases(TargetPred,MaxLengthOfTestCase,Timeout) :- | |
| 995 | statistics(walltime, [Start|_]), | |
| 996 | InitialLength = 1, | |
| 997 | cb_test_cases_loop(InitialLength,TargetPred,MaxLengthOfTestCase,Timeout), | |
| 998 | statistics(walltime, [End|_]), | |
| 999 | Runtime is End - Start, | |
| 1000 | write_results(Runtime). | |
| 1001 | ||
| 1002 | cb_test_cases_loop(CurLength,TargetPred,MaxLengthOfTestCase,Timeout) :- | |
| 1003 | cb_test_cases_step(CurLength,TargetPred,MaxLengthOfTestCase,Timeout,Result), | |
| 1004 | (Result=paths_found | |
| 1005 | -> L1 is CurLength+1, | |
| 1006 | cb_test_cases_loop(L1,TargetPred,MaxLengthOfTestCase,Timeout) | |
| 1007 | ; Result=limit_reached -> check_progress_possible | |
| 1008 | ; Result=no_test_found -> check_progress_possible | |
| 1009 | ; true | |
| 1010 | ). | |
| 1011 | ||
| 1012 | :- use_module(probsrc(solver_interface), [call_with_smt_mode_enabled/1]). | |
| 1013 | :- use_module(probsrc(tools_printing),[format_with_colour/4]). | |
| 1014 | ||
| 1015 | :- use_module(cbcsrc(cbc_path_solver),[create_testcase_path/5]). | |
| 1016 | ||
| 1017 | % try and find test-cases of a particular depth: CurLengthOfTestCase | |
| 1018 | cb_test_cases_step(1,TargetPred,_MaxLengthOfTestCase,Timeout,Res) :- bounded_model_checking, | |
| 1019 | % check if INITIALISATION can violate INVARIANT or Deadlock | |
| 1020 | Events = [], Length=0, | |
| 1021 | count_path(Length), | |
| 1022 | call_with_smt_mode_enabled(create_testcase_path(init,Events,TargetPred,Timeout,PathResult)), | |
| 1023 | debug_println(19,init_bmc_check(PathResult)), | |
| 1024 | PathResult = [_|_], % Result contains e.g., (0,$initialise_machine(int(-1)),root,0) and possibly set_up_constants | |
| 1025 | !, | |
| 1026 | Res =paths_found, | |
| 1027 | format_err('~nBMC counterexample found, Length=~w~n',[Length]), | |
| 1028 | assertz(bounded_model_checking_complete), count_found(0,testcase), | |
| 1029 | store_test_case(['$initialise_machine'],PathResult). | |
| 1030 | cb_test_cases_step(CurLengthOfTestCase,_TargetPred,_MaxLengthOfTestCase,_Timeout,success) :- | |
| 1031 | cb_test_case_algorithm_finished, | |
| 1032 | !, | |
| 1033 | L1 is CurLengthOfTestCase-1, | |
| 1034 | format_msg('~nCBC Algorithm finished (~w)~n',[L1]), | |
| 1035 | (debug_mode(on), specfile:currently_opened_file(F) -> format_msg(' File: ~w~n',[F]) ; true). | |
| 1036 | cb_test_cases_step(CurLengthOfTestCase,_TargetPred,MaxLengthOfTestCase,_Timeout,limit_reached) :- | |
| 1037 | CurLengthOfTestCase > MaxLengthOfTestCase,!, | |
| 1038 | format_warn('~nLimit Reached: ~w~n',[MaxLengthOfTestCase]). | |
| 1039 | cb_test_cases_step(CurLengthOfTestCase,TargetPred,MaxLengthOfTestCase,Timeout,paths_found) :- | |
| 1040 | % now try to find a path of length CurLengthOfTestCase | |
| 1041 | call_with_smt_mode_enabled(cb_find_paths(CurLengthOfTestCase,TargetPred,MaxLengthOfTestCase,Timeout)), | |
| 1042 | !, | |
| 1043 | (bounded_model_checking_complete -> format_err('~nBMC counterexample found, Length=~w~n',[CurLengthOfTestCase]) | |
| 1044 | ; \+ cb_has_uncovered_events -> format_ok('~nEverything covered, Length=~w~n',[CurLengthOfTestCase]) | |
| 1045 | ; \+ cb_has_uncovered_feasible_events -> format_ok('~nEverything feasible covered, Length=~w~n',[CurLengthOfTestCase]) | |
| 1046 | ; true). | |
| 1047 | cb_test_cases_step(CurLengthOfTestCase,_TargetPred,MaxLengthOfTestCase,_Timeout,Res) :- | |
| 1048 | % either everything covered or MaxLength reached or no way to extend current paths | |
| 1049 | (CurLengthOfTestCase = MaxLengthOfTestCase | |
| 1050 | -> format_warn('~nCould not generate a test case of length ~w~n',[CurLengthOfTestCase]), | |
| 1051 | Res = no_test_found | |
| 1052 | ; format_warn('~nCould not generate a path of length ~w~n',[CurLengthOfTestCase]), | |
| 1053 | Res = no_path_found | |
| 1054 | ). | |
| 1055 | ||
| 1056 | % utilities to print colored messages | |
| 1057 | format_msg(FormatStr,Args) :- format_with_colour(user_output,[blue],FormatStr,Args). | |
| 1058 | format_err(FormatStr,Args) :- format_with_colour(user_output,[red,bold],FormatStr,Args). | |
| 1059 | format_warn(FormatStr,Args) :- format_with_colour(user_output,[cyan,bold],FormatStr,Args). | |
| 1060 | format_ok(FormatStr,Args) :- format_with_colour(user_output,[green,bold],FormatStr,Args). | |
| 1061 | ||
| 1062 | format_silent_msg(_,_) :- silent_mode(on),!. | |
| 1063 | format_silent_msg(FormatStr,Args) :- format_msg(FormatStr,Args). | |
| 1064 | ||
| 1065 | format_silent_ok(_,_) :- silent_mode(on),!. | |
| 1066 | format_silent_ok(FormatStr,Args) :- format_with_colour(user_output,[green],FormatStr,Args). | |
| 1067 | ||
| 1068 | ||
| 1069 | cb_test_case_algorithm_finished :- | |
| 1070 | (cb_has_uncovered_feasible_events -> bounded_model_checking_complete ; true). | |
| 1071 | ||
| 1072 | :- dynamic cb_uncovered_event_in_path/1. % uncovered in any path (and thus also test-case) | |
| 1073 | % reset datastructures for CBC test case generation | |
| 1074 | cb_test_cases_init_search(Events) :- | |
| 1075 | reset_counters, | |
| 1076 | retractall(cb_path(_,_,_)), % retract all found paths | |
| 1077 | retractall(cb_path_testcase_only(_,_,_)), | |
| 1078 | retractall(cb_timeout_path(_,_)), | |
| 1079 | assertz(cb_path(0,[],no_state)), % generate an intial path; Note: we assume the initialisation is feasible | |
| 1080 | % set up all coverage targets: | |
| 1081 | retractall(cb_uncovered_event(_)), | |
| 1082 | retractall(cb_uncovered_event_in_path(_)), | |
| 1083 | retractall(cb_target_event(_)), | |
| 1084 | retractall(cb_final_event(_)), | |
| 1085 | cb_test_cases_init_uncovered_in_path, | |
| 1086 | add_target_events(Events). | |
| 1087 | cb_test_cases_init_uncovered_in_path :- | |
| 1088 | b_top_level_operation(Event), | |
| 1089 | assertz(cb_uncovered_event_in_path(Event)), | |
| 1090 | count_total_events, | |
| 1091 | fail. | |
| 1092 | cb_test_cases_init_uncovered_in_path. | |
| 1093 | ||
| 1094 | add_target_events(Desc) :- add_events_aux(Desc),fail. | |
| 1095 | add_target_events(_). | |
| 1096 | add_events_aux(all) :- b_top_level_operation(Event), assert_cb_uncovered_event(Event). | |
| 1097 | add_events_aux([H|T]) :- member(Event,[H|T]), | |
| 1098 | match_event(Event,MatchedEvent), | |
| 1099 | assert_cb_uncovered_event(MatchedEvent). | |
| 1100 | add_events_aux(final(List)) :- member(Event,List), | |
| 1101 | match_event(Event,MatchedEvent), | |
| 1102 | assert_cb_uncovered_event(MatchedEvent), assertz(cb_final_event(MatchedEvent)). | |
| 1103 | ||
| 1104 | match_event(match_event(E),Match) :- !, % try and find operation/event with E occuring inside string | |
| 1105 | atom_codes(E,EC), append(EC,_,EC2), | |
| 1106 | if((b_top_level_operation(Match), | |
| 1107 | atom_codes(Match,MC), | |
| 1108 | append(_,EC2,MC)), | |
| 1109 | debug_println(19,match_event(E,Match)), | |
| 1110 | (add_error(cbc_cover_match,'No matching event for: ',E),fail)). | |
| 1111 | match_event(E,E). | |
| 1112 | ||
| 1113 | assert_cb_uncovered_event(E) :- \+ b_top_level_operation(E),!, | |
| 1114 | add_error(cbc_cover,'Unknown event: ',E). | |
| 1115 | assert_cb_uncovered_event(E) :- assertz(cb_uncovered_event(E)), assertz(cb_target_event(E)). | |
| 1116 | ||
| 1117 | get_feasible_event_name(Event) :- | |
| 1118 | b_top_level_feasible_operation(Event), | |
| 1119 | \+ infeasible_operation_cache(Event). | |
| 1120 | cb_feasible_uncovered_event(E) :- cb_uncovered_event(E), \+ infeasible_operation_cache(E). | |
| 1121 | ||
| 1122 | useless_prefix(PrefixPath) :- | |
| 1123 | last(PrefixPath,LastPrefix), | |
| 1124 | \+ cb_uncovered_event(LastPrefix), % we still need to try and navigate into a valid end state | |
| 1125 | bmachine:b_operation_cannot_modify_state(LastPrefix). % no use in trying such prefixes | |
| 1126 | ||
| 1127 | ||
| 1128 | :- dynamic stat_last_print_time/1. | |
| 1129 | print_cb_stats(Level,StartWTime) :- | |
| 1130 | statistics(walltime,[CurWTime,_]), | |
| 1131 | ToTime is CurWTime-StartWTime, | |
| 1132 | (stat_last_print_time(Last) | |
| 1133 | -> (CurWTime - Last > 60000 | |
| 1134 | -> retract(stat_last_print_time(_)), | |
| 1135 | assertz(stat_last_print_time(CurWTime)), | |
| 1136 | format_msg('~nTotal runtime for level ~w so far: ~w ms~n',[Level,ToTime]), | |
| 1137 | print_cb_uncovered_events, | |
| 1138 | write_results,nls | |
| 1139 | ; true) | |
| 1140 | ; assertz(stat_last_print_time(CurWTime))). | |
| 1141 | ||
| 1142 | cb_find_paths(Length,TargetPredicate,MaxLength,Timeout) :- | |
| 1143 | (all_uncovered_events_are_final | |
| 1144 | -> (CoveredStatus = uncovered ; CoveredStatus = covered), % force using uncovered operations first in b_ordered_get_operation | |
| 1145 | % this only works if once an uncovered operation becomes covered it no longer needs to be enumerated; otherwise we will miss paths | |
| 1146 | format_silent_msg('~nCBC test search: starting length ~w (~w events)~n',[Length,CoveredStatus]) | |
| 1147 | ; format_silent_msg('~nCBC test search: starting length ~w~n',[Length])), | |
| 1148 | print_cb_uncovered_events, | |
| 1149 | statistics(walltime,[StartWTime,_]), | |
| 1150 | % set up template for Path = PrefixPath ^ [Event] | |
| 1151 | cb_prefix_trace(Length,PreLength,PrefixPath,Event,Path), | |
| 1152 | % choose any existing prefix of length PreLength for which a CBC solution was found: | |
| 1153 | cb_path(PreLength,PrefixPath,LastStateId), | |
| 1154 | % TO DO: maybe also try cb_timeout_path(PreLength,PrefixPath), LastStateId = no_state | |
| 1155 | \+ useless_prefix(PrefixPath), % avoid using operations that do not change the state | |
| 1156 | statistics(walltime,[WT,_]), Delta is WT-StartWTime, | |
| 1157 | (Delta>1000 -> nls ; true), % too long lines on terminal slow things down | |
| 1158 | (debug_mode(on) -> format_msg('~nTrying to extend path of length ~w: ~w~n',[Length,PrefixPath]) | |
| 1159 | ; printsilent('*'),printsilent(Length)),flush_output, | |
| 1160 | % print(prefix(PrefixPath)),nl, | |
| 1161 | % check if full coverage not yet achieved: | |
| 1162 | \+ cb_test_case_algorithm_finished, | |
| 1163 | % now choose any event; uncovered ones first: | |
| 1164 | b_ordered_get_operation(Event,CoveredStatus), | |
| 1165 | \+ bounded_model_checking_complete, % check again ; we may have found a solution with the previous event | |
| 1166 | possible_trace(PrefixPath,Event,Length), % check if trace is in principle possible | |
| 1167 | printsilent('.'),flush_output, | |
| 1168 | % statistics information: store that we attempt another path of length Length: | |
| 1169 | count_path(Length), | |
| 1170 | (Length>=MaxLength -> Result=testcase, % no need to find partial solutions now | |
| 1171 | cb_feasible_uncovered_event(Event) % only set up paths for feasible Events we want to cover | |
| 1172 | ; true | |
| 1173 | ), | |
| 1174 | print_cb_stats(Length,StartWTime), | |
| 1175 | % and try to set up that path PrefixPath ^ [Event] | |
| 1176 | debug_println(9,setting_up_path(Path,Event)), | |
| 1177 | cb_set_up_path(Path,Event,TargetPredicate,LastStateId,Timeout,Result), | |
| 1178 | % now store info that we have found a solution for length Length with Result | |
| 1179 | debug_println(19,found_solution(Length,Result,Path)), | |
| 1180 | count_found(Length,Result), | |
| 1181 | % fail-loop | |
| 1182 | fail. | |
| 1183 | cb_find_paths(Length,_TargetPredicate,_MaxLengthOfTestCase,_Timeout) :- | |
| 1184 | % success if a path or testcase of this length was generated, fail otherwise | |
| 1185 | (cb_path(Length,_,_) -> true ; counter(testcase_count(Length),TC), TC>0). | |
| 1186 | ||
| 1187 | % enumerate events with uncovered first: | |
| 1188 | b_ordered_get_operation(Event,CoveredStatus) :- | |
| 1189 | findall(Event,get_feasible_event_name(Event),AllEvents), | |
| 1190 | include(cb_uncovered_event,AllEvents,Uncovered), | |
| 1191 | exclude(cb_uncovered_event,AllEvents,Covered), | |
| 1192 | % Note: we do a findall and exclude/include first to avoid side-effects from modifying cb_uncovered_event | |
| 1193 | % and possibly forgetting an event | |
| 1194 | %print(uncovered(Uncovered)),nl, | |
| 1195 | ( CoveredStatus=uncovered, | |
| 1196 | member(Event,Uncovered) | |
| 1197 | ; | |
| 1198 | CoveredStatus=covered, | |
| 1199 | member(Event,Covered), | |
| 1200 | \+ bmachine:b_operation_cannot_modify_state(Event), | |
| 1201 | \+ cb_final_event(Event) % it is already covered; no need to cover it again | |
| 1202 | ). % no use in using a query/skip operation at the end | |
| 1203 | ||
| 1204 | all_uncovered_events_are_final :- cb_uncovered_event(Ev), \+ cb_final_event(Ev),!,fail. | |
| 1205 | all_uncovered_events_are_final. | |
| 1206 | ||
| 1207 | cb_prefix_trace(Length,PreLength,PrefixPath,Event,Path) :- | |
| 1208 | PreLength is Length-1, | |
| 1209 | length(PrefixPath,PreLength),append(PrefixPath,[Event],Path). | |
| 1210 | ||
| 1211 | cb_has_uncovered_events :- | |
| 1212 | cb_uncovered_event(_),!. %% | |
| 1213 | cb_has_uncovered_feasible_events :- | |
| 1214 | cb_feasible_uncovered_event(_),!. %% | |
| 1215 | ||
| 1216 | :- use_module(cbcsrc(cbc_path_solver),[testcase_set_up_events/9]). | |
| 1217 | cb_set_up_events(Trace,InitialState,ConstantsState,FinalState,DetailedTrace,WF,WF2) :- | |
| 1218 | DetailedTrace = dt1(States,Operations,OpTransInfos), | |
| 1219 | testcase_set_up_events(Trace,InitialState,ConstantsState,Operations,States,FinalState,OpTransInfos,WF,WF2). | |
| 1220 | ||
| 1221 | combine_detail_infos(ConstantsState,InitialState,Initialisation,InitInfo,DT1,DT) :- | |
| 1222 | DT1 = dt1(States,Operations,OpTransInfos), | |
| 1223 | DT = dt(ConstantsState, | |
| 1224 | [InitialState|States], | |
| 1225 | [Initialisation|Operations], | |
| 1226 | [InitInfo|OpTransInfos]). | |
| 1227 | ||
| 1228 | ||
| 1229 | ||
| 1230 | % check if trace : Trace ^ [Event] is in principle possible using static/cbc enabling analysis results | |
| 1231 | possible_trace(Trace,Event,Length) :- get_preference(use_po,true), | |
| 1232 | last(Trace,PreviousEvent), | |
| 1233 | !, | |
| 1234 | %format('Try "~w" after "~w"~n',[Event,PreviousEvent]),nl, | |
| 1235 | check_operation_sequence_possible(PreviousEvent,Event,Length), | |
| 1236 | % TO DO: we could delay computing this until after cb_set_up_path has failed; if we managed to find a solution from the intialisation no need to do this check which starts from Invariant | |
| 1237 | true. | |
| 1238 | possible_trace(_,_,_). | |
| 1239 | ||
| 1240 | ||
| 1241 | :- use_module(probsrc(eventhandling),[register_event_listener/3,announce_event/1]). | |
| 1242 | :- register_event_listener(specification_initialised,reset_sap, | |
| 1243 | 'Initialise module sap.'). | |
| 1244 | ||
| 1245 | :- use_module(cbcsrc(enabling_analysis),[operation_sequence_possible/3]). | |
| 1246 | :- dynamic operation_sequence_possible_cache/3. | |
| 1247 | :- dynamic operation_can_be_enabled_by_cache/3. | |
| 1248 | reset_sap :- | |
| 1249 | retractall(operation_sequence_possible_cache(_,_,_)), | |
| 1250 | retractall(operation_can_be_enabled_by_cache(_,_,_)), | |
| 1251 | announce_event(end_solving). | |
| 1252 | ||
| 1253 | check_operation_sequence_possible(PreviousEvent,Event,Length) :- | |
| 1254 | (operation_sequence_possible_cache(PreviousEvent,Event,R) | |
| 1255 | -> chk_possible(R,PreviousEvent,Event,Length) | |
| 1256 | ; operation_sequence_possible(PreviousEvent,Event,R) -> | |
| 1257 | assertz(operation_sequence_possible_cache(PreviousEvent,Event,R)), | |
| 1258 | chk_possible(R,PreviousEvent,Event,Length) | |
| 1259 | ; assertz(operation_sequence_possible_cache(PreviousEvent,Event,failure)) | |
| 1260 | % we assume it is possible to be on the safe side | |
| 1261 | ). | |
| 1262 | ||
| 1263 | %chk_possible(R,PreviousEvent,Event) :- print(chk(R,PreviousEvent,Event)),nl,fail. | |
| 1264 | chk_possible(impossible,PrevEv,Event,_) :- !, | |
| 1265 | (debug_mode(on) -> format_warn('Impossible event: ~w after ~w~n',[Event,PrevEv]) ; printsilent('-')), fail. | |
| 1266 | chk_possible(activation_independent,Prev,Event,_) :- cb_uncovered_event_in_path(Event), !, | |
| 1267 | % PreviousEvent cannot influence the variables of the guards of Event; | |
| 1268 | % as Event was previously not covered it cannot possible be enabled after PreviousEvent | |
| 1269 | (debug_mode(on) -> format_msg('Activation independent: ~w (previous ~w)~n',[Event,Prev]) ; printsilent('||')), | |
| 1270 | fail. | |
| 1271 | % this is probably not very often useful ; to do: enable only if we repeatedly fail to satisfy this combination? | |
| 1272 | % or do this only at deeper levels ? | |
| 1273 | chk_possible(_,PreviousEvent,Event,Length) :- | |
| 1274 | Length>3, % only start doing this check later, when hopefully many events are already covered | |
| 1275 | cb_uncovered_event_in_path(Event),!, | |
| 1276 | % we know Event must be disabled before PreviousEvent | |
| 1277 | (check_operation_can_be_enabled_by(PreviousEvent,Event) -> true | |
| 1278 | ; (debug_mode(on) -> format_msg('Event ~w cannot be enabled by ~w~n',[Event,PreviousEvent]) ; printsilent('|^|')), | |
| 1279 | fail | |
| 1280 | ). | |
| 1281 | chk_possible(_,_,_,_). | |
| 1282 | ||
| 1283 | :- use_module(cbcsrc(enabling_analysis),[operation_can_be_enabled_by/4]). | |
| 1284 | ||
| 1285 | check_operation_can_be_enabled_by(PreviousEvent,Event) :- | |
| 1286 | TIMEOUT = 500, % TO DO : pass timeout from cb_find_paths and divide by 5? (default in cb_find_paths is 2500) | |
| 1287 | (operation_can_be_enabled_by_cache(PreviousEvent,Event,R) -> R \= failure | |
| 1288 | ; operation_can_be_enabled_by(PreviousEvent,Event,TIMEOUT,R) -> | |
| 1289 | assertz(operation_can_be_enabled_by_cache(PreviousEvent,Event,R)), | |
| 1290 | R \= failure | |
| 1291 | ; assertz(operation_can_be_enabled_by_cache(PreviousEvent,Event,failure)), | |
| 1292 | fail | |
| 1293 | ). | |
| 1294 | ||
| 1295 | % TO DO: use this check at regular intervals ?!? and use it to abort cbc_tests if no progress possible and or prune the events that can be reached | |
| 1296 | % check if there exists a covered event that can enable one of the uncovered ones: | |
| 1297 | check_progress_possible :- | |
| 1298 | bounded_model_checking,!. % progress always possible | |
| 1299 | check_progress_possible :- | |
| 1300 | % to do: check only valid if target pred = truth | |
| 1301 | (progress_possible(PreviousEvent,Event) | |
| 1302 | -> format_msg('Progress possible, e.g., covered ~w could enable uncovered ~w~nTry increasing depth bound for cbc_tests.~n',[PreviousEvent,Event]) | |
| 1303 | ; cbc_get_uncovered_events(Uncov), | |
| 1304 | format_warn('Impossible to activate any of the uncovered events ~w.~nNo further progress possible!~n',[Uncov])). | |
| 1305 | ||
| 1306 | progress_possible(PreviousEvent,Event) :- cb_feasible_uncovered_event(Event), | |
| 1307 | cb_covered_event(PreviousEvent), | |
| 1308 | check_operation_can_be_enabled_by(PreviousEvent,Event). | |
| 1309 | ||
| 1310 | cb_covered_event(Event) :- % TO DO: this may give false positives; if an event was not marked to be covered initially and has not yet occured | |
| 1311 | b_top_level_operation(Event), \+ cb_uncovered_event(Event). | |
| 1312 | ||
| 1313 | :- use_module(probsrc(kernel_waitflags),[init_wait_flags/2]). | |
| 1314 | :- use_module(cbcsrc(cbc_path_solver),[testcase_initialise/5]). | |
| 1315 | ||
| 1316 | %cb_set_up_path([Event],Event,TargetPredicate,no_state,Timeout,Result) :- | |
| 1317 | % cb_find_constants_and_initial_vars_in_statespace(State,InitID), | |
| 1318 | % do_event(InitID,Event,SuccId), | |
| 1319 | % visited_expression(SuccId,OutState), expand ... | |
| 1320 | % cb_finalize_path_with_timeout(TargetPredicate,OutState,Trace,Event,WF,LResult,Timeout,TO_Success). | |
| 1321 | %cb_set_up_path(Trace,Event,TargetPredicate,StateId,Timeout,Result) :- | |
| 1322 | % print(cb_set_up_path(Trace,Event,TargetPredicate,StateId,Timeout,Result)),nl,fail. | |
| 1323 | cb_set_up_path(Trace,Event,TargetPredicate,StateId,Timeout,Result) :- | |
| 1324 | % StateID is the last state of the previously found path Trace (without Event) | |
| 1325 | cb_try_state_caching, % if true: try to use model checking paths found; currently disabled | |
| 1326 | StateId \== no_state, | |
| 1327 | lookup_state(StateId,ConstantState,InState), | |
| 1328 | init_wait_flags(WF,[cb_set_up_path]), | |
| 1329 | set_up_transition(Event,Operation,ConstantState,InState,OutState,TransInfo,WF), | |
| 1330 | cb_finalize_path_with_timeout(TargetPredicate,OutState,Trace,Event,WF,LResult,Timeout,TO_Success), | |
| 1331 | ( TO_Success = time_out -> | |
| 1332 | cb_write_timeout(Timeout,Trace),fail | |
| 1333 | ; | |
| 1334 | cb_store_single_step_in_statespace(ConstantState,OutState,StateId,Operation,TransInfo,LastStateId,Sequence), | |
| 1335 | cb_save_new_step(Trace,Event,LResult,LastStateId,Sequence), | |
| 1336 | % if we did not find a test case, just a normal step, we need to fall back | |
| 1337 | % to constraint solving on the full path | |
| 1338 | !, | |
| 1339 | ( LResult==testcase -> Result=testcase | |
| 1340 | ; cb_set_up_path(Trace,Event,TargetPredicate,no_state,Timeout,testcase) -> Result=testcase | |
| 1341 | ; Result = path)). | |
| 1342 | %cb_set_up_path(Trace,Event,TargetPredicate,StateId,Timeout,Result) :- %print(try(Trace,Event,TargetPredicate,StateId)),nl, % see if an outgoing transition event exists for StateId already explored by MC | |
| 1343 | % is_truth(TargetPredicate), do_event(StateId,Event,LastStateId),!, print('MC'),Sequence=[], % TO DO: compute sequence | |
| 1344 | % Result=testcase,cb_save_new_step(Trace,Event,Result,LastStateId,Sequence). | |
| 1345 | cb_set_up_path(Trace,Event,TargetPredicate,_StateId,Timeout,Result) :- | |
| 1346 | init_wait_flags(WF,[cb_set_up_path]), | |
| 1347 | % set up constants and initialisation: | |
| 1348 | testcase_initialise(ConstantsState,InitialState,Initialisation,TransInfo,WF), | |
| 1349 | combine_detail_infos(ConstantsState,InitialState,Initialisation,TransInfo,DT1,DT), | |
| 1350 | cb_set_up_events(Trace,InitialState,ConstantsState,FinalState,DT1,WF,WF2), | |
| 1351 | % either find a real testcase where TargetPredicate holds at end | |
| 1352 | % or find a path where TargetPredicate does not hold at end: | |
| 1353 | cb_finalize_path_with_timeout(TargetPredicate,FinalState,Trace,Event,WF2,Result,Timeout,TO_Success), | |
| 1354 | !, | |
| 1355 | ( TO_Success = time_out -> | |
| 1356 | cb_write_timeout(Timeout,Trace), | |
| 1357 | fail | |
| 1358 | ; (Result=testcase, bounded_model_checking -> format_err('Found BMC counter example (~w)~n',[Trace]) | |
| 1359 | ; debug_mode(on) -> format_ok('Found ~w path for ~w (~w)~n',[Result,Event,Trace]) | |
| 1360 | ; Result=testcase -> printsilent('!') %,print(Trace),nl | |
| 1361 | ; printsilent('+') | |
| 1362 | ), | |
| 1363 | cb_store_path_in_statespace(DT,LastStateId,Sequence), % only do if path required or new testcase generated | |
| 1364 | cb_save_new_step(Trace,Event,Result,LastStateId,Sequence) | |
| 1365 | ). | |
| 1366 | cb_save_new_step(Trace,Event,Result,LastStateId,Sequence) :- | |
| 1367 | (retract(cb_uncovered_event_in_path(Event)) | |
| 1368 | -> (debug_mode(on) -> format_ok('Covered event ~w~n',[Event]) ; printsilent('#')) ; true), | |
| 1369 | ( Result == testcase | |
| 1370 | -> (cb_final_event(Event) -> cb_save_path_testcase_only(Trace,LastStateId) ; cb_save_path(Trace,LastStateId)), | |
| 1371 | cb_save_found_test_case(Trace,Sequence) | |
| 1372 | ; cb_save_path(Trace,LastStateId)). | |
| 1373 | ||
| 1374 | lookup_state(StateId,ConstStore,VarStore) :- | |
| 1375 | visited_expression(StateId,Store), | |
| 1376 | lookup_state2(Store,ConstStore,VarStore). | |
| 1377 | lookup_state2(const_and_vars(ConstId,VarStore),ConstStore,VarStore) :- | |
| 1378 | !,visited_expression(ConstId,concrete_constants(ConstStore)). | |
| 1379 | lookup_state2(Store,Empty,Store) :- empty_state(Empty). | |
| 1380 | ||
| 1381 | ||
| 1382 | ||
| 1383 | :- use_module(probsrc(tools_meta),[call_residue/2]). | |
| 1384 | ||
| 1385 | cb_finalize_path_with_timeout(TargetPredicate,FinalState,Trace,Event,WF,Result,Timeout,TO_Success) :- | |
| 1386 | call_residue( | |
| 1387 | cb_catch_finalize(TargetPredicate,FinalState,Trace,Event,WF,Result,Timeout,TO_Success2),Residue), | |
| 1388 | (TO_Success2=time_out -> TO_Success=TO_Success2 % when time-out occurs there can be residues | |
| 1389 | ; Residue = [] -> TO_Success=TO_Success2 | |
| 1390 | ; add_internal_error('Call had residue: ',trace_residue(Trace,Residue)/cb_finalize_path_with_timeout), | |
| 1391 | %flush_output, print('GOAL:'),nl,tools_printing:print_goal(Residue),nl, | |
| 1392 | TO_Success=time_out % as we have pending co-routines: assume time-out | |
| 1393 | ). | |
| 1394 | ||
| 1395 | :- use_module(probsrc(clpfd_interface),[catch_clpfd_overflow_call3/3]). | |
| 1396 | cb_catch_finalize(TargetPredicate,FinalState,Trace,Event,WF,Result,Timeout,TO_Success) :- | |
| 1397 | catch_clpfd_overflow_call3( | |
| 1398 | cb_finalize_path_aux(TargetPredicate,FinalState,Trace,Event,WF,Result,Timeout,TO_Success), | |
| 1399 | warning, | |
| 1400 | (printsilent('@OVERFLOW@'), Result=path, TO_Success = time_out)). | |
| 1401 | ||
| 1402 | ||
| 1403 | % finalize the path by checking whether TargetPredicate holds in final state; if so: we have a testcase otherwise just a path | |
| 1404 | cb_finalize_path_aux(TargetPredicate,_FinalState,Trace,Event,WF,Result,Timeout,TO_Success) :- | |
| 1405 | % if the target predicate is just true, we can store every found path | |
| 1406 | % directly as a possible test trace, too. | |
| 1407 | is_truth(TargetPredicate),!, % avoid ground_wait_flags twice if path infeasible | |
| 1408 | % now check if the path makes sense as either testcase or path; if not: no use in enumerating | |
| 1409 | (is_potential_new_test_case(Trace) -> Result = testcase % replace by is_potential_test_case if you want more tests in the test xml files | |
| 1410 | ; is_potential_path_to_be_stored(Trace,Event) -> Result = path), % paths are not written to xml file | |
| 1411 | ground_wait_flags_with_timeout(WF,Timeout,TO_Success), | |
| 1412 | !. | |
| 1413 | cb_finalize_path_aux(TargetPredicate,FinalState,Trace,Event,WF,RESULT,Timeout,TO_Success) :- | |
| 1414 | is_potential_new_test_case(Trace), | |
| 1415 | % Note: if the target predicate is fulfilled in the last state, we | |
| 1416 | % can store the solution as a test trace. | |
| 1417 | (bounded_model_checking_invariant, | |
| 1418 | b_specialized_invariant_for_op(Event,SpecINV) % only computed if get_preference(use_po,true) | |
| 1419 | -> bsyntaxtree:create_negation(SpecINV,NegSpecINV), | |
| 1420 | eval_predicate(NegSpecINV,FinalState,WF), | |
| 1421 | (debug_mode(on) -> print('Specialized Invariant Target: '),translate:print_bexpr(SpecINV),nl ; true) | |
| 1422 | ; eval_predicate(TargetPredicate,FinalState,WF), | |
| 1423 | (debug_mode(on) -> print('Target: '),translate:print_bexpr(TargetPredicate),nl ; true) | |
| 1424 | ), | |
| 1425 | % print(grounding_with_predicate(Event,Trace)),nl, | |
| 1426 | ground_wait_flags_with_timeout(WF,Timeout,TO_Success), | |
| 1427 | (RESULT == testcase -> true % we are only looking for testcases not paths where we do not reach the target predicate | |
| 1428 | ; TO_Success = time_out -> printsilent('@Timeout@'), %nl,print(Trace),nl, | |
| 1429 | fail % try and find a solution without target predicate below | |
| 1430 | ; true), | |
| 1431 | !, | |
| 1432 | RESULT=testcase. | |
| 1433 | cb_finalize_path_aux(_TargetPredicate,_FinalState,Trace,Event,WF,path,Timeout,TO_Success) :- | |
| 1434 | % if the path is not feasible with the target predicate applied to the | |
| 1435 | % last state, it still might be feasible as a starting point for other traces. | |
| 1436 | is_potential_path_to_be_stored(Trace,Event), | |
| 1437 | % print(grounding_without_predicate(_Trace)),nl, | |
| 1438 | ground_wait_flags_with_timeout(WF,Timeout,TO_Success),!. | |
| 1439 | ||
| 1440 | :- use_module(probsrc(b_interpreter), [b_test_boolean_expression/4]). | |
| 1441 | eval_predicate(Predicate,State,WF) :- | |
| 1442 | empty_state(EmptyState), | |
| 1443 | b_test_boolean_expression(Predicate,EmptyState,State,WF). | |
| 1444 | ||
| 1445 | % check if a sequence of events is a potential new test case | |
| 1446 | is_potential_new_test_case(_Trace) :- bounded_model_checking, | |
| 1447 | !. % any trace can potentially lead to an invariant violation/deadlock | |
| 1448 | % TO DO: check if last event is not a proven event | |
| 1449 | is_potential_new_test_case(Trace) :- | |
| 1450 | ((member(Ev,Trace), cb_uncovered_event(Ev)) -> true). %print(uncov(Ev)),nl ; fail) | |
| 1451 | % we could add a requirement that only certain events are admissible as last events (a bit like cb_final_event + requiring that if there is one cb_final_event then one must be used) | |
| 1452 | ||
| 1453 | :- public is_potential_test_case/1. | |
| 1454 | % check if a sequence of events is a potential test case | |
| 1455 | is_potential_test_case(_Trace) :- bounded_model_checking, | |
| 1456 | !. % any trace can potentially lead to an invariant violation/deadlock | |
| 1457 | % TO DO: check if last event is not a proven event | |
| 1458 | is_potential_test_case(Trace) :- ((member(Ev,Trace), cb_target_event(Ev)) -> true). %print(uncov(Ev)),nl ; fail) | |
| 1459 | ||
| 1460 | % check if it is worth storing this trace, i.e., could it later be extended to a test-case | |
| 1461 | is_potential_path_to_be_stored(_Trace,Event) :- | |
| 1462 | (cb_final_event(Event) -> printsilent(f),fail ; true). % this event is only allowed at the end: we cannot extend the trace | |
| 1463 | ||
| 1464 | :- use_module(probsrc(kernel_waitflags),[ground_wait_flags/1]). | |
| 1465 | ground_wait_flags_with_timeout(WF,Timeout,TO_Success) :- | |
| 1466 | catch( | |
| 1467 | time_out(ground_wait_flags(WF),Timeout,TO_Success), | |
| 1468 | enumeration_warning(_,_,_,_,_), | |
| 1469 | TO_Success = time_out). | |
| 1470 | ||
| 1471 | % cb_save_found_test_case(TraceOfOperationNames,Sequence of 4-tuples (Id,Op,Src,Dest)) | |
| 1472 | cb_save_found_test_case(Trace,Sequence) :- % print(found(Trace)),nl, | |
| 1473 | % mark all events in Trace as covered | |
| 1474 | cb_mark_all_covered(Trace,NewCover), | |
| 1475 | (bounded_model_checking -> assertz(bounded_model_checking_complete) ; true), | |
| 1476 | cb_get_initialised_state(Sequence,InitState), | |
| 1477 | % Sequence is a list of (_Id,Op,Src,Dst) | |
| 1478 | (NewCover=[] -> true | |
| 1479 | ; store_test_case(NewCover,Sequence) | |
| 1480 | ), | |
| 1481 | cb_write_test_case(Sequence,InitState). | |
| 1482 | ||
| 1483 | % store main test cases for later replaying in GUI / Tcl/Tk | |
| 1484 | :- dynamic stored_test_case/3, test_case_nr/1. | |
| 1485 | test_case_nr(0). | |
| 1486 | ||
| 1487 | reset_test_cases :- retractall(stored_test_case(_,_,_)), retract(test_case_nr(_)), | |
| 1488 | assertz(test_case_nr(0)). | |
| 1489 | print_op_name((_Id,Op,_Src,_Dst)) :- specfile:get_operation_name(Op,Name), !,write(Name),write(' '). | |
| 1490 | print_op_names(Sequence) :- maplist(print_op_name,Sequence),!,nl. | |
| 1491 | print_op_names(L) :- add_internal_error('Illegal BMC counterexample:',L). | |
| 1492 | ||
| 1493 | store_test_case(NewCover,Sequence) :- retract(test_case_nr(Nr)), N1 is Nr+1, | |
| 1494 | assertz(test_case_nr(N1)), | |
| 1495 | %print(stored_test_case(N1,NewCover,Sequence)),nl, | |
| 1496 | assertz(stored_test_case(N1,NewCover,Sequence)), | |
| 1497 | (bounded_model_checking | |
| 1498 | -> write('Replaying BMC counterexample: '), print_op_names(Sequence), | |
| 1499 | tcl_execute_stored_test_case(N1) ; true). | |
| 1500 | ||
| 1501 | % a function to get a textual description of a stored test case | |
| 1502 | stored_test_case_description(N1,Descr) :- | |
| 1503 | stored_test_case(N1,NewCover,Sequence), get_op(Sequence,Ops), | |
| 1504 | add_sep(NewCover,NCS),ajoin(NCS,NCA), | |
| 1505 | ajoin(['Test ', N1, '. Covering ',NCA,'. Trace = '|Ops],Descr). | |
| 1506 | % function to get the list of operation names of a stored test case | |
| 1507 | stored_test_case_op_trace(N1,OpTrace) :- | |
| 1508 | stored_test_case(N1,_NewCover,Sequence), | |
| 1509 | maplist(get_op_name,Sequence,OpTrace). | |
| 1510 | ||
| 1511 | add_sep([],[]). | |
| 1512 | add_sep([A],R) :- !, R=[A]. | |
| 1513 | add_sep([A|T],[A,' '|AT]) :- add_sep(T,AT). | |
| 1514 | get_op([],[]). | |
| 1515 | get_op([Tuple|Rest],[Name,' '|T]) :- | |
| 1516 | get_op_name(Tuple,Name), | |
| 1517 | get_op(Rest,T). | |
| 1518 | get_op_name((_Id,Op,_Src,_Dst),Name) :- | |
| 1519 | specfile:get_operation_name(Op,Name). % deal with '-->' | |
| 1520 | ||
| 1521 | % a function to execute a stored test case in the animator; should probably move to tcltk_interface.pl | |
| 1522 | tcl_execute_stored_test_case(Nr) :- | |
| 1523 | stored_test_case(Nr,NewCover,Sequence), | |
| 1524 | debug_println(19,executing_stored_test_case(Nr,NewCover,Sequence)), | |
| 1525 | Sequence = [(_,_,Src,_)|_], | |
| 1526 | tcltk_interface:tcltk_execute_trace_to_node(Src), % first find a trace to the starting initialisation | |
| 1527 | last(Sequence,(_,_,_,Dest)), | |
| 1528 | maplist(get_op_id,Sequence,OpIds), | |
| 1529 | maplist(get_dest,Sequence,Ids), | |
| 1530 | execute_id_trace_from_current(Dest,OpIds,Ids). | |
| 1531 | ||
| 1532 | get_op_id((OpID,_,_,_),OpID). | |
| 1533 | get_dest((_,_,_,Dest),Dest). | |
| 1534 | ||
| 1535 | tcl_get_stored_test_cases(list(L)) :- | |
| 1536 | findall(Descr,stored_test_case_description(_,Descr),L). | |
| 1537 | ||
| 1538 | cb_get_initialised_state([(_Id,_Op,Src,_Dst)|_],InitState) :- | |
| 1539 | visited_expression(Src,Store), | |
| 1540 | expand_const_and_vars_to_full_store(Store,InitState). | |
| 1541 | ||
| 1542 | cb_mark_all_covered([],[]). | |
| 1543 | cb_mark_all_covered([Event|Rest],NewCover) :- | |
| 1544 | ( retract(cb_uncovered_event(Event)) -> NewCover=[Event|RestCover], | |
| 1545 | format_silent_ok('~nCBC test search: covered event ~w~n',[Event]) | |
| 1546 | ; NewCover=RestCover), | |
| 1547 | cb_mark_all_covered(Rest,RestCover). | |
| 1548 | ||
| 1549 | :- use_module(probsrc(store),[lookup_value/3]). | |
| 1550 | ||
| 1551 | cb_write_test_case(Sequence,InitState) :- | |
| 1552 | xml_write(' <test_case>\n'), | |
| 1553 | if(cb_xml_initialisation(InitState),true,add_internal_error('Call failed: ',cb_xml_initialisation(InitState))), | |
| 1554 | if(cb_xml_sequence(Sequence),true,add_internal_error('Call failed: ',cb_xml_sequence(Sequence))), | |
| 1555 | xml_write(' </test_case>\n'), | |
| 1556 | flush_output_xml. | |
| 1557 | cb_xml_initialisation(InitState) :- | |
| 1558 | xml_write(' <initialisation>\n'), | |
| 1559 | b_get_machine_constants(Constants),cb_xml_init2(InitState,Constants,constant), | |
| 1560 | b_get_machine_variables(Variables),cb_xml_init2(InitState,Variables,variable), | |
| 1561 | xml_write(' </initialisation>\n'). | |
| 1562 | cb_xml_init2(Store,Identifiers,Type) :- | |
| 1563 | get_texpr_id(TId,Id), | |
| 1564 | member(TId,Identifiers), | |
| 1565 | lookup_value(Id,Store,BValue), | |
| 1566 | cb_xml_value(Id,Type,BValue), | |
| 1567 | fail. | |
| 1568 | cb_xml_init2(_Store,_Identifiers,_Type). | |
| 1569 | cb_xml_value(Id,Type,BValue) :- | |
| 1570 | translate_bvalue(BValue,SValue), | |
| 1571 | xml_write(' <value type=\"'), | |
| 1572 | xml_write(Type), | |
| 1573 | xml_write('\" name=\"'),xml_write(Id),xml_write('\">'), | |
| 1574 | xml_write(SValue),xml_write('</value>\n'). | |
| 1575 | cb_xml_sequence([]). | |
| 1576 | cb_xml_sequence([(_Id,Op,Src,Dst)|Rest]) :- !, | |
| 1577 | specfile:get_operation_name(Op,Name), | |
| 1578 | cb_xml_step(Name,Op,Src,Dst), % TO DO: also provide operation parameters | |
| 1579 | cb_xml_sequence(Rest). | |
| 1580 | cb_xml_sequence([loop(_ID)]). | |
| 1581 | cb_xml_step(Event,Op,SrcId,DstId) :- | |
| 1582 | b_get_machine_operation(Event,_,Parameters,_), | |
| 1583 | Parameters = [_|_],!, | |
| 1584 | xml_write(' <step name=\"'),xml_write(Event),xml_write('\">\n'), | |
| 1585 | specfile:get_operation_arguments(Op,Args), % TO DO: maybe also write out return arguments ?! | |
| 1586 | xml_write_parameters(Event,Parameters,Args), | |
| 1587 | expand_state(SrcId,Src), | |
| 1588 | expand_state(DstId,Dst), | |
| 1589 | maplist(cb_xml_modified(Src),Dst), | |
| 1590 | xml_write(' </step>\n'). | |
| 1591 | cb_xml_step(Event,_,_Src,_Dst) :- | |
| 1592 | xml_write(' <step name=\"'),xml_write(Event),xml_write('\" />\n'). | |
| 1593 | ||
| 1594 | xml_write_parameters(Event,Parameters,Args) :- | |
| 1595 | get_preference(prefix_internal_operation_arguments,Prefix), | |
| 1596 | maplist(cb_xml_parameter(Event,Prefix),Parameters,Args). | |
| 1597 | ||
| 1598 | % write operation parameters | |
| 1599 | cb_xml_parameter(_Event,Prefix,Parameter,BValue) :- | |
| 1600 | get_texpr_id(Parameter,Id),!, | |
| 1601 | (is_internal_parameter(Prefix,Id) -> true % do not print it | |
| 1602 | ; cb_xml_value_element(value,Id,BValue)). | |
| 1603 | cb_xml_parameter(Event,Prefix,Parameter,BValue) :- | |
| 1604 | add_internal_error('Call failed: ',cb_xml_parameter(Event,Prefix,Parameter,BValue)). | |
| 1605 | ||
| 1606 | is_internal_parameter(Prefix,Parameter) :- atom_concat(Prefix,_,Parameter). | |
| 1607 | ||
| 1608 | % write modified variable values | |
| 1609 | cb_xml_modified(Src,bind(Name,NewValue)) :- | |
| 1610 | cb_var_is_modified(Name,NewValue,Src),!, | |
| 1611 | cb_xml_value_element(modified,Name,NewValue). | |
| 1612 | cb_xml_modified(_,_). | |
| 1613 | ||
| 1614 | cb_var_is_modified(Name,NewValue,Src) :- | |
| 1615 | memberchk(bind(Name,OldValue),Src),!, | |
| 1616 | not_equal_object(OldValue,NewValue). | |
| 1617 | cb_var_is_modified(Name,_NewValue,Src) :- | |
| 1618 | nonmember(bind(Name,_OldValue),Src). | |
| 1619 | ||
| 1620 | xml_write_binding(Element,bind(Name,Value)) :- cb_xml_value_element(Element,Name,Value). | |
| 1621 | ||
| 1622 | cb_xml_value_element(Element,Name,Value) :- | |
| 1623 | xml_write(' <'),xml_write(Element), | |
| 1624 | xml_write(' name=\"'),xml_write(Name),xml_write('\">'), | |
| 1625 | xml_write_b_value(Value), | |
| 1626 | !, | |
| 1627 | xml_write('</'),xml_write(Element),xml_write('>\n'). | |
| 1628 | cb_xml_value_element(Element,Name,Value) :- | |
| 1629 | add_internal_error('Call failed: ',cb_xml_value_element(Element,Name,Value)). | |
| 1630 | ||
| 1631 | xml_write_b_value((Fst,Snd)) :- !, | |
| 1632 | xml_write('<pair><fst>'), | |
| 1633 | xml_write_b_value(Fst), | |
| 1634 | xml_write('</fst><snd>'), | |
| 1635 | xml_write_b_value(Snd), | |
| 1636 | xml_write('</snd></pair> '). | |
| 1637 | xml_write_b_value(avl_set(A)) :- !, custom_explicit_sets:expand_custom_set_to_list(avl_set(A),Elements), | |
| 1638 | xml_write('<set>'), | |
| 1639 | maplist(xml_write_b_value,Elements), | |
| 1640 | xml_write('</set> '). | |
| 1641 | xml_write_b_value([H|T]) :- !, | |
| 1642 | xml_write('<set>'), | |
| 1643 | maplist(xml_write_b_value,[H|T]), | |
| 1644 | xml_write('</set> '). | |
| 1645 | xml_write_b_value(rec(Fields)) :- !, | |
| 1646 | xml_write('<record>'), | |
| 1647 | maplist(xml_write_b_field_value,Fields), | |
| 1648 | xml_write('</record> '). | |
| 1649 | xml_write_b_value(string(S)) :- !, | |
| 1650 | xml_write('<string>'), | |
| 1651 | xml_write_b_value(S), % TO DO: encode S ! | |
| 1652 | xml_write('</string> '). | |
| 1653 | xml_write_b_value(Value) :- translate_bvalue(Value,SValue), xml_write(SValue). | |
| 1654 | % TO DO: support other types: other set representations, booleans pred_false, pred_true | |
| 1655 | % [] -> empty_set ? | |
| 1656 | % interval closures ? freeval(ID,Case,Value) | |
| 1657 | ||
| 1658 | xml_write_b_field_value(field(Name,Val)) :- | |
| 1659 | xml_write('<field name=\"'), xml_write(Name), xml_write('\">'), | |
| 1660 | xml_write_b_value(Val), xml_write('</field>'). | |
| 1661 | ||
| 1662 | expand_state(Id,VariableBindings) :- | |
| 1663 | visited_expression(Id,State), | |
| 1664 | expand_to_constants_and_variables(State,_ConstantBindings,VariableBindings), | |
| 1665 | !. | |
| 1666 | expand_state(Id,VariableBindings) :- | |
| 1667 | add_internal_error('Call failed: ',expand_state(Id,VariableBindings)), | |
| 1668 | VariableBindings=[]. | |
| 1669 | ||
| 1670 | :- volatile cb_timeout_path/2. | |
| 1671 | :- dynamic cb_timeout_path/2. | |
| 1672 | cb_write_timeout(Timeout,Events) :- | |
| 1673 | length(Events,Length), | |
| 1674 | count_timeout(Length), | |
| 1675 | printsilent(timeout),flush_output, | |
| 1676 | assertz(cb_timeout_path(Length,Events)), | |
| 1677 | xml_write(' <!-- timeout ('),xml_write(Timeout),xml_write(') ms) for events '), | |
| 1678 | cb_write_timeouts2(Events), | |
| 1679 | xml_write(' --!>\n'),flush_output_xml. | |
| 1680 | cb_write_timeouts2([Event]) :- | |
| 1681 | !,xml_write(Event). | |
| 1682 | cb_write_timeouts2([Event|Erest]) :- | |
| 1683 | xml_write(Event),xml_write(','),cb_write_timeouts2(Erest). | |
| 1684 | ||
| 1685 | cb_save_path(Trace,LastStateId) :- | |
| 1686 | length(Trace,CurDepth), | |
| 1687 | assertz(cb_path(CurDepth,Trace,LastStateId)). | |
| 1688 | cb_save_path_testcase_only(Trace,LastStateId) :- % save mainly for Tcl/Tk tree viewer | |
| 1689 | length(Trace,CurDepth), | |
| 1690 | assertz(cb_path_testcase_only(CurDepth,Trace,LastStateId)). | |
| 1691 | ||
| 1692 | :- use_module(probsrc(store),[normalise_state/2, normalise_states/2]). | |
| 1693 | :- use_module(cbcsrc(cbc_path_solver),[add_constants_to_state_space/5, | |
| 1694 | add_operations_to_state_space/5, remove_constants_from_state/4]). | |
| 1695 | ||
| 1696 | cb_store_path_in_statespace(dt(ConstantsState1,States1,Operations,TransInfos),LastStateId,RealOperations) :- | |
| 1697 | normalise_states([ConstantsState1|States1],[ConstantsState|States]), | |
| 1698 | add_constants_to_state_space(ConstantsState,States,ConstStates,_SetupSequence,StartStateId), | |
| 1699 | add_operations_to_state_space(Operations,StartStateId,ConstStates,TransInfos,OpSequence), | |
| 1700 | OpSequence = [_InitOp|RealOperations], | |
| 1701 | last(OpSequence,(_,_,_,LastStateId)). | |
| 1702 | ||
| 1703 | cb_store_single_step_in_statespace(ConstantsState,State1,PrevStateId,Operation,TransInfo,NewStateId,[Tuple]) :- | |
| 1704 | normalise_state(State1,State2), | |
| 1705 | ( cb_find_constants_state_in_statespace(ConstantsState,ConstId) -> | |
| 1706 | remove_constants_from_state([State2],ConstantsState,ConstId,[State]) | |
| 1707 | ; | |
| 1708 | State = State2), | |
| 1709 | add_operations_to_state_space([Operation],PrevStateId,[State],[TransInfo],[Tuple]), | |
| 1710 | Tuple = (_,_,_,NewStateId). | |
| 1711 | ||
| 1712 | % find a state where the constants have been valued | |
| 1713 | cb_find_constants_state_in_statespace(ConstantsState,_ConstId) :- | |
| 1714 | empty_state(ConstantsState),!,fail. | |
| 1715 | cb_find_constants_state_in_statespace(ConstantsState,ConstId) :- | |
| 1716 | transition(root,_,ConstId), | |
| 1717 | visited_expression(ConstId,concrete_constants(ConstantsState)),!. | |
| 1718 | ||
| 1719 | % %try and find an initial state in the state space | |
| 1720 | %cb_find_constants_and_initial_vars_in_statespace(InitialState,InitId) :- | |
| 1721 | % transition(root,_,Id1), | |
| 1722 | % (visited_expression(Id1,concrete_constants(_)) | |
| 1723 | % -> transition(Id1,_,InitId) | |
| 1724 | % ; InitId = Id1), | |
| 1725 | % visited_expression(InitId,State), | |
| 1726 | % expand_const_and_vars_to_full_store(State,InitialState). | |
| 1727 | ||
| 1728 | test_generation_by_xml_description(Filename) :- | |
| 1729 | read_test_generation_description(Filename,Description), | |
| 1730 | execute_test_generation(Description). | |
| 1731 | execute_test_generation(cb_test_description(Events,TargetString,MaxDepth,Preferences,Filename,StateCaching)) :- | |
| 1732 | parse_cbc_target_predicate(TargetString,Target), | |
| 1733 | maplist(set_ec_preference,Preferences), | |
| 1734 | retractall(cb_try_state_caching), | |
| 1735 | ( StateCaching = true | |
| 1736 | -> assertz(cb_try_state_caching), printsilent('State caching (states from model checker) enabled'),nls | |
| 1737 | ; true), | |
| 1738 | cbc_gen_test_cases(Events,Target,MaxDepth,Filename,_Uncovered). | |
| 1739 | set_ec_preference(pref(Name,Value)) :- | |
| 1740 | set_eclipse_preference(Name,Value). | |
| 1741 | ||
| 1742 | read_test_generation_description(Filename,Description) :- | |
| 1743 | safe_read_string_from_file(Filename,utf8,Codes), | |
| 1744 | xml_parse(Codes,xml(_Atts,Content)), | |
| 1745 | ( read_test_generation_description2(Content,Description) -> | |
| 1746 | true | |
| 1747 | ; | |
| 1748 | add_error(sap,'error while reading test description file'),fail). | |
| 1749 | read_test_generation_description2(Xml,cb_test_description(Events,Target,MaxDepth,Preferences,File,StateCaching)) :- | |
| 1750 | expect_xml_element('test-generation-description',Xml,Content),!, | |
| 1751 | expect_xml_element('output-file',Content,[pcdata(FileCodes)]),atom_codes(File,FileCodes), | |
| 1752 | get_xml_events(Content,Events), | |
| 1753 | expect_xml_element(parameters,Content,XmlParameters), | |
| 1754 | % maximum depth in parameters section | |
| 1755 | expect_xml_element('maximum-depth',XmlParameters,[pcdata(MaxDepthCodes)]),number_codes(MaxDepth,MaxDepthCodes), | |
| 1756 | % optional target predicate | |
| 1757 | (memberchk(element(target,_,[pcdata(Tcodes)]),Content) -> atom_codes(Target,Tcodes) ; Target = ''), | |
| 1758 | % set preference in parameters section | |
| 1759 | findall(PL,member(element(preference,PL,_),XmlParameters),PrefAttLists), | |
| 1760 | maplist(convert_xml_preference,PrefAttLists,Preferences), | |
| 1761 | % check if state caching should be used | |
| 1762 | (memberchk(element('state-caching',_,_),Content) -> StateCaching = true ; StateCaching = false). | |
| 1763 | convert_xml_preference(PAL,pref(Name,Value)) :- | |
| 1764 | (memberchk(name=CName,PAL) -> atom_codes(Name,CName) ; add_error(sap,'Missing preference name'),fail), | |
| 1765 | (memberchk(value=CValue,PAL) -> atom_codes(Value,CValue) ; add_error(sap,'Missing preference value'),fail). | |
| 1766 | get_xml_events(Content,Events) :- | |
| 1767 | memberchk(element('event-coverage',_,EventElements),Content),!, | |
| 1768 | findall(E,(member(element(event,_,[pcdata(Ecodes)]),EventElements),atom_codes(E,Ecodes)),Events). | |
| 1769 | get_xml_events(_Content,Events) :- | |
| 1770 | get_all_events(Events). | |
| 1771 | ||
| 1772 | ||
| 1773 | expect_xml_element(ElemName,Xml,Elements) :- | |
| 1774 | ( memberchk(element(ElemName,_,Es),Xml) -> Elements=Es | |
| 1775 | ; | |
| 1776 | atom_concat('did not find xml element ',ElemName,Msg), | |
| 1777 | add_error(sap,Msg),fail). | |
| 1778 | ||
| 1779 | :- volatile counter/2. | |
| 1780 | :- dynamic counter/2. | |
| 1781 | ||
| 1782 | reset_counters :- | |
| 1783 | retractall(counter(_,_)). | |
| 1784 | ||
| 1785 | count(Counter) :- | |
| 1786 | (retract(counter(Counter,Count)) -> true ; Count = 0), | |
| 1787 | NCount is Count+1, | |
| 1788 | assertz(counter(Counter,NCount)). | |
| 1789 | ||
| 1790 | count_total_events :- count(total_events). | |
| 1791 | count_timeout(Length) :- count(timeouts(Length)). | |
| 1792 | count_path(Length) :- count(path_count(Length)). | |
| 1793 | count_found(Length,Result) :- | |
| 1794 | (Result==testcase -> count(testcase_count(Length)) ; true), | |
| 1795 | count(solution_count(Length)). | |
| 1796 | ||
| 1797 | write_result_item(Desc,Db) :- | |
| 1798 | format('~w: ',[Desc]), | |
| 1799 | C =..[Db,Length], | |
| 1800 | findall( count(Length,Num), counter(C,Num), UResults), | |
| 1801 | sort(UResults,Results), | |
| 1802 | maplist(write_result_item2,Results), | |
| 1803 | maplist(extract_num,Results,Nums),sumlist(Nums,Total), | |
| 1804 | format('total: ~w~n',[Total]). | |
| 1805 | write_result_item2(count(Length,Num)) :- | |
| 1806 | format('~w:~w, ',[Length,Num]). | |
| 1807 | extract_num(count(_,Num),Num). | |
| 1808 | ||
| 1809 | %total_cbc_testcases(T) :- get_totals(testcase_count,T). | |
| 1810 | %get_totals(CounterName,Total) :- | |
| 1811 | % C =..[CounterName,_Len], | |
| 1812 | % findall( Num, counter(C,Num), Ns), | |
| 1813 | % sumlist(Ns,Total). | |
| 1814 | ||
| 1815 | write_results(_) :- silent_mode(on),!. | |
| 1816 | write_results(Runtime) :- | |
| 1817 | write_results, | |
| 1818 | format('Runtime (wall): ~w ms~n',[Runtime]). | |
| 1819 | write_results :- | |
| 1820 | write_result_item('Timeouts',timeouts), | |
| 1821 | write_result_item('Checked paths (Depth:Paths)',path_count), | |
| 1822 | write_result_item('Found paths',solution_count), | |
| 1823 | write_result_item('Found test cases', testcase_count). | |
| 1824 | ||
| 1825 | get_counter_info(CBC_Depth,Paths,Tests,Timeouts) :- | |
| 1826 | get_counter(solution_count(CBC_Depth),Paths), | |
| 1827 | get_counter(testcase_count(CBC_Depth),Tests), | |
| 1828 | get_counter(timeouts(CBC_Depth),Timeouts). | |
| 1829 | ||
| 1830 | get_counter(C,Num) :- counter(C,R) -> Num=R ; Num=0. | |
| 1831 | ||
| 1832 | :- use_module(probsrc(b_state_model_check),[get_unsorted_all_guards_false_pred/1]). | |
| 1833 | :- dynamic bounded_model_checking/0, bounded_model_checking_invariant/0, bounded_model_checking_complete/0. | |
| 1834 | parse_cbc_target_predicate(_,_) :- | |
| 1835 | retractall(bounded_model_checking), | |
| 1836 | retractall(bounded_model_checking_invariant), | |
| 1837 | retractall(bounded_model_checking_complete), | |
| 1838 | fail. | |
| 1839 | parse_cbc_target_predicate('#invariant',Invariant) :- !, | |
| 1840 | b_get_invariant_from_machine(Invariant). | |
| 1841 | parse_cbc_target_predicate('#deadlock',DeadlockPO) :- !, | |
| 1842 | assertz(bounded_model_checking), % stop at first counter example found | |
| 1843 | get_unsorted_all_guards_false_pred(DeadlockPO). | |
| 1844 | parse_cbc_target_predicate('#not_invariant',TargetPredicate) :- !, | |
| 1845 | debug_println(19,bounded_model_checking), | |
| 1846 | b_get_invariant_from_machine(Invariant), | |
| 1847 | assertz(bounded_model_checking), % stop at first counter example found | |
| 1848 | assertz(bounded_model_checking_invariant), % for bounded model check we will try to use proven invariant for op ! | |
| 1849 | bsyntaxtree:create_negation(Invariant,TargetPredicate). | |
| 1850 | parse_cbc_target_predicate(TargetString,TargetPredicate) :- debug_println(15,parse_target_string(TargetString)), | |
| 1851 | b_parse_optional_machine_predicate(TargetString,TargetPredicate). |