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