:- asserta(user:file_search_path(library, 'prolog')).
:- use_module(library(quasi_quotations)).
:- use_module(library(xsd)).
:- use_module(library(xsd/flatten)).
:- use_module(library(xsd/validate)).
:- use_module(library(xsd/xpath)).
% operator for xpath expression unit tests
:- op(800, xfx, user:(==>)).
% suppress all warnings for ungrouped clause definitions
:- style_check(-discontiguous).
/* --- Term Expansion --- */
% all consulted tests are temporarily stored as a test_definition/3 during term expansion
:- dynamic test_definition/3.
/**
* {|xsd|| ... |} quasi quotation support for embedding xml instances in pl files.
* The embedded instance is flattened and the file id is returned.
*/
:- quasi_quotation_syntax(xml).
xml(Content, _Args, _Variables, Result) :-
with_quasi_quotation_input(
Content,
Stream,
flatten:xml_flatten(stream(Stream), File_ID)
),
Result = File_ID.
/**
* Asserts the test_definition that represents the read schema validation test definition.
*
* e.g.:
* 'xs:float valid':
* {|xml||
* NaN
* |}.
* or
* 'xs:float invalid'(fail):
* {|xml||
* NAN
* |}.
*/
term_expansion((Testname : Test_File_ID), (test_definition(Testname, Test_File_ID, success))) :-
atom(Testname).
term_expansion((Test : Test_File_ID), (test_definition(Testname, Test_File_ID, fail))) :-
Test =.. [Testname, fail],
atom(Testname).
/**
* Asserts the test_definition that represents the read xpath unit test definition.
*
* e.g.:
* float('NaN') ==> data('float', [nan])
* or
* float('NAN') ==> false
*/
term_expansion((Input ==> Output), (test_definition(Input, (Input ==> Output), success))) :-
Output \= false,
% automatically asserting the 2nd argument of term_expansion does not work
assertz(test_definition(Input, (Input ==> Output), success)).
term_expansion((Input ==> Output), (test_definition(Input, (Input ==> Output), fail))) :-
Output = false,
% automatically asserting the 2nd argument of term_expansion does not work
assertz(test_definition(Input, (Input ==> Output), fail)).
/**
* Executes this test runner by replacing the run(tests) fact with the test definitions,
* registering the tests with tap and finally executing them.
*/
term_expansion(run(tests), TermReplacement) :-
get_test_paths(Schema_Path, Validation_Path, XPath_Path),
load_tests(Schema_Path, Validation_Path, Schema_Validation_Tests),
load_tests(XPath_Path, XPath_Tests),
append(XPath_Tests, Schema_Validation_Tests, UnsortedTests),
sort(UnsortedTests, SortedTests),
execute_tests(SortedTests, TermReplacement).
/* --- Test Loading and Execution --- */
/**
* Get the paths to the schema, validation and xpath directory.
*/
get_test_paths(Schema_Path, Validation_Path, XPath_Path) :-
source_file_property(Main_Module_File_Location, module(xsd)),
file_directory_name(Main_Module_File_Location, Main_Module_Path),
absolute_file_name('../test', Path, [relative_to(Main_Module_Path), file_type(directory)]),
absolute_file_name('./schema', Schema_Path, [relative_to(Path), file_type(directory)]),
absolute_file_name('./validation', Validation_Path, [relative_to(Path), file_type(directory)]),
absolute_file_name('./xpath', XPath_Path, [relative_to(Path), file_type(directory)]).
/**
* Translates the provided identifiers between local and global mode.
*
* e.g.: in the directory is identified as ,
* whereas in the directory is identified as .
*/
path_translation(Directory, LocalIdentifier, GlobalIdentifier) :-
atomic_list_concat([Directory, LocalIdentifier], ':', GlobalIdentifier).
/**
* Returns the directory name of the provided directory path.
*/
directory_name(DirectoryPath, DirectoryName) :-
atomic_list_concat(DirectoryPathParts, '/', DirectoryPath),
reverse(DirectoryPathParts, [DirectoryName|_]).
/**
* Loads the test definitions of files inside the provided path(s).
*
* If the tests require no xml schema and instance, only the primary path,
* that contains test definitions inside .pl-files, must be provided.
*
* If the tests are based on an xml schema and instance, then the primary path
* references the schema directory, that contains .xsd-files, and the secondary
* path references the directory, that contains test definitions inside .pl-files.
*/
load_tests(PrimaryPath, Tests) :-
load_tests(PrimaryPath, null, Tests).
load_tests(PrimaryPath, SecondaryPath, Tests) :-
directory_files(PrimaryPath, Filenames),
load_test_file(Filenames, PrimaryPath, SecondaryPath, Tests).
load_test_file([], _, _, []).
load_test_file([Filename|Filenames], PrimaryPath, SecondaryPath, AllTests) :-
load_test_file(Filenames, PrimaryPath, SecondaryPath, OtherTests),
(
\+member(Filename, [., ..]),
file_name_extension(LocalIdentifier, Extension, Filename),
absolute_file_name(LocalIdentifier, Absolute_Filename, [relative_to(PrimaryPath), extensions([Extension])]),
directory_name(PrimaryPath, Directory),
(
Extension = pl,
ensure_loaded(Absolute_Filename) % term expansions are triggered here for schema/instance-independent tests
;
Extension = xsd,
flatten:xml_flatten(Absolute_Filename, LocalIdentifier),
absolute_file_name(LocalIdentifier, Instance_Absolute_Filename, [relative_to(SecondaryPath), extensions([pl])]),
ensure_loaded(Instance_Absolute_Filename) % term expansions are triggered here for schema/instance-dependent tests
),
findall(
test_definition(GlobalIdentifier, Test_Name, Test_Definition, Mode),
(
test_definition(Test_Name, Test_Definition, Mode),
path_translation(Directory, LocalIdentifier, GlobalIdentifier)
),
LoadedTests
),
append(OtherTests, LoadedTests, AllTests),
retractall(test_definition(_, _, _))
;
AllTests = OtherTests
).
/**
* Executes the provided test definitions by generating and returning the test rules as well as registering them with tap.
*/
execute_tests([], []).
execute_tests([test_definition(GlobalIdentifier, Test_Name, Test_Definition, Mode)|Tests], [(Tap_Test_Name :- Test)|TermReplacement]) :-
format(atom(Tap_Test_Name), '[~w] ~w', [GlobalIdentifier, Test_Name]),
(
compound(Test_Definition), % schema/instance independent tests
Test_Definition = (IN ==> OUT),
(
Mode = success,
Test_Run = (xpath:xpath_expr(_, IN, OUT))
;
Mode = fail,
Test_Run = (\+xpath:xpath_expr(_, IN, _))
)
;
\+compound(Test_Definition), % schema/instance dependent tests
path_translation(_, LocalIdentifier, GlobalIdentifier),
(
Mode = success,
Test_Run = (validate:validate(LocalIdentifier, Test_Definition))
;
Mode = fail,
Test_Run = (\+validate:validate(LocalIdentifier, Test_Definition))
)
),
Test = (
% suppress output during test run
with_output_to(
codes(_Output),
Test_Run
)
),
tap:register_test(Tap_Test_Name),
execute_tests(Tests, TermReplacement).
/* --- Testrunner entry point --- */
:- use_module(library(tap)).
run(tests).