212 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
		
		
			
		
	
	
			212 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Perl
		
	
	
	
	
	
| 
								 | 
							
								:- module(test_wizard,
							 | 
						||
| 
								 | 
							
									  [ make_tests/3,		% +Module, +File, +Out
							 | 
						||
| 
								 | 
							
									    make_test/3			% +Callable, -Module, -Test
							 | 
						||
| 
								 | 
							
									  ]).
							 | 
						||
| 
								 | 
							
								:- use_module(library(time)).
							 | 
						||
| 
								 | 
							
								:- use_module(library(lists)).
							 | 
						||
| 
								 | 
							
								:- use_module(library(listing)).
							 | 
						||
| 
								 | 
							
								:- use_module(library(readutil)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/** <module> Test Generation Wizard
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Tasks
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									* Accumulate user queries
							 | 
						||
| 
								 | 
							
									* Suggest tests from user queries
							 | 
						||
| 
								 | 
							
								*/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								setting(max_time(5)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										 /*******************************
							 | 
						||
| 
								 | 
							
										 *	 UNIT GENERATION	*
							 | 
						||
| 
								 | 
							
										 *******************************/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	make_tests(+Module, +File, +Out) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Create tests from queries stored in File and write the tests for
							 | 
						||
| 
								 | 
							
								%	Module to the stream Out.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								make_tests(Module, File, Out) :-
							 | 
						||
| 
								 | 
							
									read_file_to_terms(File, Queries, []),
							 | 
						||
| 
								 | 
							
									findall(Test, (   member(Q, Queries),
							 | 
						||
| 
								 | 
							
											  make_test(Q, Module, Test)), Tests),
							 | 
						||
| 
								 | 
							
									(   Tests == []
							 | 
						||
| 
								 | 
							
									->  true
							 | 
						||
| 
								 | 
							
									;   format(Out, ':- begin_tests(~q).~n~n', [Module]),
							 | 
						||
| 
								 | 
							
									    maplist(portray_clause(Out), Tests),
							 | 
						||
| 
								 | 
							
									    format(Out, '~n:- end_tests(~q).~n', [Module])
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										 /*******************************
							 | 
						||
| 
								 | 
							
										 *	 TEST GENERATION	*
							 | 
						||
| 
								 | 
							
										 *******************************/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	make_test(+Query:callable, -Module, -Test:term) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Generate a test from a query. Test   is  returned as a clause of
							 | 
						||
| 
								 | 
							
								%	test/1  or  test/2  to  be   inserted  between  begin_tests  and
							 | 
						||
| 
								 | 
							
								%	end_tests.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								make_test(Query0, Module, (test(Name, Options) :- Query)) :-
							 | 
						||
| 
								 | 
							
									find_test_module(Query0, Module, Query),
							 | 
						||
| 
								 | 
							
									pred_name(Query, Name),
							 | 
						||
| 
								 | 
							
									setting(max_time(Max)),
							 | 
						||
| 
								 | 
							
									test_result(Module:Query, Max, Options).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	find_test_module(+QuerySpec, ?Module, -Query).
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Find module to test from a query. Note that it is very common
							 | 
						||
| 
								 | 
							
								%	for toplevel usage to rely on SWI-Prolog's DWIM.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	@tbd	What if multiple modules match?  We can select the
							 | 
						||
| 
								 | 
							
								%		local one or ask the user.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								find_test_module(Var, _, _) :-
							 | 
						||
| 
								 | 
							
									var(Var), !, fail.
							 | 
						||
| 
								 | 
							
								find_test_module(M:Query, M0, Query) :- !,
							 | 
						||
| 
								 | 
							
									M0 = M.
							 | 
						||
| 
								 | 
							
								find_test_module(Query, M, Query) :-
							 | 
						||
| 
								 | 
							
									current_predicate(_, M:Query),
							 | 
						||
| 
								 | 
							
									\+ predicate_property(M:Query, imported_from(_M2)).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	pred_name(+Callable, -Name) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Suggest a name for the test. In   the  plunit framework the name
							 | 
						||
| 
								 | 
							
								%	needs not be unique, so we simply take the predicate name.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								pred_name(Callable, Name) :-
							 | 
						||
| 
								 | 
							
									strip_module(Callable, _, Term),
							 | 
						||
| 
								 | 
							
									functor(Term, Name, _Arity).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	test_result(+Callable, +Maxtime, -Result) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Try running goal and get meaningful results.  Results are:
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%		* true(Templ == Var)
							 | 
						||
| 
								 | 
							
								%		* fail
							 | 
						||
| 
								 | 
							
								%		* all(Templ == Bindings)
							 | 
						||
| 
								 | 
							
								%		* throws(Error)
							 | 
						||
| 
								 | 
							
								%		* timeout
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								test_result(Callable, Maxtime, Result) :-
							 | 
						||
| 
								 | 
							
									term_variables(Callable, Vars),
							 | 
						||
| 
								 | 
							
									make_template(Vars, Templ),
							 | 
						||
| 
								 | 
							
									catch(call_with_time_limit(Maxtime,
							 | 
						||
| 
								 | 
							
												   findall(Templ-Det,
							 | 
						||
| 
								 | 
							
													   call_test(Callable, Det),
							 | 
						||
| 
								 | 
							
													   Bindings)),
							 | 
						||
| 
								 | 
							
									      E, true),
							 | 
						||
| 
								 | 
							
									(   var(E)
							 | 
						||
| 
								 | 
							
									->  success(Bindings, Templ, Result)
							 | 
						||
| 
								 | 
							
									;   error(E, Result)
							 | 
						||
| 
								 | 
							
									).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	success(+Bindings, +Templ, -Result) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Create test-results from non-error cases.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								success([], _, [fail]) :- !.
							 | 
						||
| 
								 | 
							
								success([[]-true],  _, []) :- !.
							 | 
						||
| 
								 | 
							
								success([S1-true],  Templ, [ true(Templ == S1) ]) :- !.
							 | 
						||
| 
								 | 
							
								success([[]-false], _, [ nondet ]) :- !.
							 | 
						||
| 
								 | 
							
								success([S1-false], Templ, [ true(Templ == S1), nondet ]) :- !.
							 | 
						||
| 
								 | 
							
								success(ListDet, Templ, [all(Templ == List)]) :-
							 | 
						||
| 
								 | 
							
									strip_det(ListDet, List).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								strip_det([], []).
							 | 
						||
| 
								 | 
							
								strip_det([H-_|T0], [H|T]) :-
							 | 
						||
| 
								 | 
							
									strip_det(T0, T).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	error(+ErrorTerm, -Result)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								error(Error0, [throws(Error)]) :-
							 | 
						||
| 
								 | 
							
									generalise_error(Error0, Error).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								generalise_error(error(Formal, _), error(Formal, _)) :- !.
							 | 
						||
| 
								 | 
							
								generalise_error(Term, Term).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	make_template(+Vars, -Template) is det.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	Make a nice looking template
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								make_template([], []) :- !.
							 | 
						||
| 
								 | 
							
								make_template([One], One) :- !.
							 | 
						||
| 
								 | 
							
								make_template([One, Two], One-Two) :- !.
							 | 
						||
| 
								 | 
							
								make_template(List, Vars) :-
							 | 
						||
| 
								 | 
							
									Vars =.. [v|List].
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								%%	call_test(:Goal, -Det) is nondet.
							 | 
						||
| 
								 | 
							
								%
							 | 
						||
| 
								 | 
							
								%	True if Goal succeeded.  Det is unified to =true= if Goal left
							 | 
						||
| 
								 | 
							
								%	no choicepoints and =false= otherwise.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								call_test(Goal, Det) :-
							 | 
						||
| 
								 | 
							
									Goal,
							 | 
						||
| 
								 | 
							
									deterministic(Det).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										 /*******************************
							 | 
						||
| 
								 | 
							
										 *	     COLLECT		*
							 | 
						||
| 
								 | 
							
										 *******************************/
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
							 | 
						||
| 
								 | 
							
								Collect toplevel queries if the Prolog flag log_query_file points to the
							 | 
						||
| 
								 | 
							
								name of a writeable  file.  The  file   is  opened  in  append-mode  for
							 | 
						||
| 
								 | 
							
								exclusive write to allow for concurrent   operation from multiple Prolog
							 | 
						||
| 
								 | 
							
								systems using the same logfile.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								The file is written in  UTF-8   encoding  and  using ignore_ops(true) to
							 | 
						||
| 
								 | 
							
								ensure it can be read.
							 | 
						||
| 
								 | 
							
								- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								:- multifile
							 | 
						||
| 
								 | 
							
									user:message_hook/3.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								user:message_hook(toplevel_goal(Goal0, Bindings), _Level, _Lines) :-
							 | 
						||
| 
								 | 
							
									open_query_log(Out),
							 | 
						||
| 
								 | 
							
									bind_vars(Bindings),
							 | 
						||
| 
								 | 
							
									clean_goal(Goal0, Goal),
							 | 
						||
| 
								 | 
							
									call_cleanup(format(Out, '~W.~n', [Goal, [ numbervars(true),
							 | 
						||
| 
								 | 
							
														   quoted(true),
							 | 
						||
| 
								 | 
							
														   ignore_ops(true)
							 | 
						||
| 
								 | 
							
														 ]]), close(Out)),
							 | 
						||
| 
								 | 
							
									fail.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								clean_goal(Var, _) :-
							 | 
						||
| 
								 | 
							
									var(Var), !, fail.
							 | 
						||
| 
								 | 
							
								clean_goal(user:Goal, Goal) :- !.
							 | 
						||
| 
								 | 
							
								clean_goal(Goal, Goal).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								bind_vars([]).
							 | 
						||
| 
								 | 
							
								bind_vars([Name=Var|T]) :-
							 | 
						||
| 
								 | 
							
									Var = '$VAR'(Name),
							 | 
						||
| 
								 | 
							
									bind_vars(T).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								open_query_log(Out) :-
							 | 
						||
| 
								 | 
							
									current_prolog_flag(log_query_file, File),
							 | 
						||
| 
								 | 
							
									exists_file(File), !,
							 | 
						||
| 
								 | 
							
									open(File, append, Out,
							 | 
						||
| 
								 | 
							
									     [ encoding(utf8),
							 | 
						||
| 
								 | 
							
									       lock(write)
							 | 
						||
| 
								 | 
							
									     ]).
							 | 
						||
| 
								 | 
							
								open_query_log(Out) :-
							 | 
						||
| 
								 | 
							
									current_prolog_flag(log_query_file, File),
							 | 
						||
| 
								 | 
							
									access_file(File, write), !,
							 | 
						||
| 
								 | 
							
									open(File, write, Out,
							 | 
						||
| 
								 | 
							
									     [ encoding(utf8),
							 | 
						||
| 
								 | 
							
									       lock(write),
							 | 
						||
| 
								 | 
							
									       bom(true)
							 | 
						||
| 
								 | 
							
									     ]),
							 | 
						||
| 
								 | 
							
									format(Out,
							 | 
						||
| 
								 | 
							
									       '/* SWI-Prolog query log.  This file contains all syntactically\n   \
							 | 
						||
| 
								 | 
							
									           correct queries issued in this directory.  It is used by the\n   \
							 | 
						||
| 
								 | 
							
										   test wizard to generate unit tests.\n\
							 | 
						||
| 
								 | 
							
										*/~n~n', []).
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 |