use
Encode
qw(encode encode_utf8 decode_utf8)
;
use
Scalar::Util
qw(blessed reftype)
;
requires
'test_model'
;
requires
'manifest_paths'
;
has
run_update_tests
=> (
is
=>
'rw'
,
isa
=> Bool,
default
=> 1);
has
run_query_tests
=> (
is
=>
'rw'
,
isa
=> Bool,
default
=> 1);
has
debug
=> (
is
=>
'rw'
,
isa
=> Bool,
default
=> 0);
has
results
=> (
is
=>
'rw'
,
isa
=> Bool,
default
=> 0);
has
strict_approval
=> (
is
=>
'rw'
,
isa
=> Bool,
default
=> 0);
has
use_idp_planner
=> (
is
=>
'rw'
,
isa
=> Bool,
default
=> 1);
has
pattern
=> (
is
=>
'rw'
,
isa
=> Str,
default
=>
''
);
has
tests_dir
=> (
is
=>
'rw'
,
required
=> 1,
default
=>
sub
{
$ENV
{ATTEAN_SPARQL_TESTS_DIR} });
has
model
=> (
is
=>
'rw'
,
isa
=> ConsumerOf[
'Attean::API::Model'
],
init_arg
=>
undef
);
has
manifests
=> (
is
=>
'rw'
,
isa
=> ArrayRef,
init_arg
=>
undef
);
has
default_graph
=> (
is
=>
'rw'
);
has
failures
=> (
is
=>
'rw'
,
isa
=> HashRef,
default
=>
sub
{ +{} });
sub
BUILD {
my
$self
=
shift
;
if
(
$self
->pattern) {
$self
->results(1);
}
}
sub
memory_model {
my
$self
=
shift
;
my
$store
= Attean->get_store(
'Memory'
)->new();
my
$model
= Attean::MutableQuadModel->new(
store
=>
$store
);
return
$model
;
}
sub
setup {
my
$self
=
shift
;
warn
'setting up sparql test harness'
if
(
$self
->debug);
my
@manifests_iris
=
$self
->manifest_paths();
unless
(
scalar
(
@manifests_iris
)) {
my
$dir
=
$self
->tests_dir;
plan
skip_all
=>
"No manifest files found in $dir"
;
exit
(0);
}
my
$model
=
$self
->memory_model();
my
$class
= Attean->get_parser(
"turtle"
) ||
die
"Failed to load parser for 'turtle'"
;
my
%loaded
;
my
@manifests
;
my
@load
=
map
{ iri(
"file://"
. File::Spec->rel2abs(
$_
)) }
@manifests_iris
;
while
(
scalar
(
@load
)) {
foreach
my
$iri
(
@load
) {
warn
"Loading "
.
$iri
->value .
"\n"
if
(
$self
->debug);
$loaded
{
$iri
->value }++;
}
$model
->load_urls_into_graph(
$self
->default_graph,
@load
);
@load
= ();
warn
"done parsing manifests"
if
$self
->debug;
$self
->model(
$model
);
my
$subjects
=
$model
->subjects( iri(
"${RDF}type"
), iri(
"${MF}Manifest"
) );
my
@manifest_matches
=
$subjects
->elements;
push
(
@manifests
,
@manifest_matches
);
foreach
my
$m
(
@manifest_matches
) {
my
@list_heads
=
$model
->objects(
$m
, iri(
"${MF}include"
) )->elements;
my
@elements
=
map
{
$model
->get_list(
undef
,
$_
)->elements() }
@list_heads
;
push
(
@load
,
grep
{ not
exists
(
$loaded
{
$_
->value}) }
@elements
);
}
}
$self
->manifests(\
@manifests
);
}
sub
syntax_test {
my
$self
=
shift
;
my
$test_type
=
shift
;
my
$model
=
shift
;
my
$test
=
shift
;
my
$count
=
shift
// 1;
my
(
$queryd
) =
$model
->objects(
$test
, iri(
"${MF}action"
) )->elements;
my
(
$approved
) =
$model
->objects(
$test
, iri(
"${DAWGT}approval"
) )->elements;
my
(
$name
) =
$model
->objects(
$test
,
$mfname
)->elements;
my
$namevalue
=
$name
->value;
if
(
$self
->strict_approval) {
unless
(
$approved
) {
warn
"- skipping test because it isn't approved\n"
if
(
$self
->debug);
return
;
}
if
(
$approved
->equal(
"${DAWGT}NotClassified"
)) {
warn
"- skipping test because its approval is dawgt:NotClassified\n"
if
(
$self
->debug);
return
;
}
}
my
$is_pos_query
=
$model
->count_quads(
$test
,
$type
, iri(
"${MF}PositiveSyntaxTest11"
));
my
$is_pos_update
=
$model
->count_quads(
$test
,
$type
, iri(
"${MF}PositiveUpdateSyntaxTest11"
));
my
$is_neg_query
=
$model
->count_quads(
$test
,
$type
, iri(
"${MF}NegativeSyntaxTest"
)) +
$model
->count_quads(
$test
,
$type
, iri(
"${MF}NegativeSyntaxTest11"
));
my
$is_neg_update
=
$model
->count_quads(
$test
,
$type
, iri(
"${MF}NegativeUpdateSyntaxTest"
)) +
$model
->count_quads(
$test
,
$type
, iri(
"${MF}NegativeUpdateSyntaxTest11"
));
my
$uri
= URI->new(
$queryd
->value );
my
$filename
=
$uri
->file;
my
(
undef
,
$base
,
undef
) = File::Spec->splitpath(
$filename
);
warn
"Loading SPARQL query from file $filename"
if
(
$self
->debug);
my
$sparql
=
do
{
local
($/) =
undef
;
open
(
my
$fh
,
'<:utf8'
,
$filename
) or
do
{
warn
(
"$!: $filename; "
.
$test
->as_string);
return
}; <
$fh
> };
my
$bytes
= encode_utf8(
$sparql
);
if
(
$self
->debug) {
my
$q
=
$sparql
;
$q
=~ s/\s+/ /g;
warn
"### test : "
.
$test
->as_string .
"\n"
;
warn
"# file : $filename\n"
;
warn
"# sparql : $q\n"
;
}
my
$pclass
= Attean->get_parser(
'SPARQL'
);
my
$parser
=
$pclass
->new();
if
(
$test_type
eq
'update'
) {
$parser
->update(1);
}
if
(
$is_pos_query
or
$is_pos_update
) {
my
(
$query
) =
eval
{
$parser
->parse_list_from_bytes(
$bytes
) };
my
$ok
= blessed(
$query
);
$self
->record_result(
'syntax'
,
$ok
,
$test
->as_string);
if
(
$ok
) {
pass(
"syntax $namevalue: $filename"
);
}
else
{
fail(
"syntax $namevalue; $filename: $@"
);
}
}
elsif
(
$is_neg_query
or
$is_neg_update
) {
my
(
$query
) =
eval
{
$parser
->parse_list_from_bytes(
$bytes
) };
my
$ok
= $@ ? 1 : 0;
$self
->record_result(
'syntax'
,
$ok
,
$test
->as_string);
if
(
$ok
) {
pass(
"syntax $namevalue: $filename"
);
}
else
{
if
(
$self
->debug) {
warn
$query
->as_string;
}
fail(
"syntax $namevalue; $filename (unexpected successful parse)"
);
}
}
}
sub
update_eval_test {
my
$self
=
shift
;
my
$model
=
shift
;
my
$test
=
shift
;
my
$count
=
shift
// 1;
my
(
$action
) =
$model
->objects(
$test
, iri(
"${MF}action"
) )->elements;
my
(
$result
) =
$model
->objects(
$test
, iri(
"${MF}result"
) )->elements;
my
(
$req
) =
$model
->objects(
$test
, iri(
"${MF}requires"
) )->elements;
my
(
$approved
) =
$model
->objects(
$test
, iri(
"${DAWGT}approval"
) )->elements;
my
(
$queryd
) =
$model
->objects(
$action
, iri(
"${UT}request"
) )->elements;
my
@data
=
$model
->objects(
$action
, iri(
"${UT}data"
) )->elements;
my
@gdata
=
$model
->objects(
$action
, iri(
"${UT}graphData"
) )->elements;
if
(
$self
->strict_approval) {
unless
(
$approved
) {
warn
"- skipping test because it isn't approved\n"
if
(
$self
->debug);
return
;
}
if
(
$approved
->equal(iri(
"${DAWGT}NotClassified"
))) {
warn
"- skipping test because its approval is dawgt:NotClassified\n"
if
(
$self
->debug);
return
;
}
}
my
$uri
= URI->new(
$queryd
->value );
my
$filename
=
$uri
->file;
my
(
undef
,
$base
,
undef
) = File::Spec->splitpath(
$filename
);
warn
"Loading SPARQL query from file $filename"
if
(
$self
->debug);
my
$sparql
=
do
{
local
($/) =
undef
;
open
(
my
$fh
,
'<'
,
$filename
) or
do
{ fail(
"$!: $filename; "
.
$test
->as_string);
return
};
binmode
(
$fh
,
':utf8'
); <
$fh
> };
my
$q
=
$sparql
;
$q
=~ s/\s+/ /g;
if
(
$self
->debug) {
warn
"### test : "
.
$test
->value .
"\n"
;
warn
"# sparql : $q\n"
;
foreach
my
$data
(
@data
) {
warn
"# data : "
.
$data
->value .
"\n"
if
(blessed(
$data
));
}
warn
"# graph data : "
.
$_
->value .
"\n"
for
(
@gdata
);
warn
"# result : "
.
$result
->value .
"\n"
;
warn
"# requires : "
.
$req
->value .
"\n"
if
(blessed(
$req
));
}
warn
"constructing model...\n"
if
(
$self
->debug);
my
$test_model
=
$self
->test_model();
foreach
my
$data
(
@data
) {
eval
{
if
(blessed(
$data
)) {
$test_model
->load_urls_into_graph(
$self
->default_graph,
$data
);
}
};
if
($@) {
fail(
$test
->value);
print
"# died: "
.
$test
->value .
": $@\n"
;
return
;
}
}
foreach
my
$gdata
(
@gdata
) {
my
(
$data
) = (
$model
->objects(
$gdata
, iri(
"${UT}data"
) )->elements)[0] || (
$model
->objects(
$gdata
, iri(
"${UT}graph"
) )->elements)[0];
my
(
$graph
) =
$model
->objects(
$gdata
, iri(
"${RDFS}label"
) )->elements;
my
$uri
=
$graph
->value;
eval
{
$test_model
->load_urls_into_graph(iri(
$uri
),
$data
);
};
if
($@) {
fail(
$test
->as_string);
print
"# died: "
.
$test
->value .
": $@\n"
;
return
;
};
}
my
(
$result_status
) =
$model
->objects(
$result
, iri(
"${UT}result"
) )->elements;
my
@resgdata
=
$model
->objects(
$result
, iri(
"${UT}graphData"
) )->elements;
my
(
$resdata
) =
$model
->objects(
$result
, iri(
"${UT}data"
) )->elements;
my
$expected_model
= memory_model;
eval
{
if
(blessed(
$resdata
)) {
$expected_model
->load_urls_into_graph(
$self
->default_graph,
$resdata
);
}
};
if
($@) {
fail(
$test
->as_string);
print
"# died: "
.
$test
->value .
": $@\n"
;
return
;
};
foreach
my
$gdata
(
@resgdata
) {
my
(
$data
) = (
$model
->objects(
$gdata
, iri(
"${UT}data"
) )->elements)[0] || (
$model
->objects(
$gdata
, iri(
"${UT}graph"
) )->elements)[0];
my
(
$graph
) =
$model
->objects(
$gdata
, iri(
"${RDFS}label"
) )->elements;
my
$uri
=
$graph
->value;
my
$return
= 0;
if
(
$data
) {
eval
{
$expected_model
->load_urls_into_graph(iri(
$uri
),
$data
);
};
if
($@) {
fail(
$test
->as_string);
print
"# died: "
.
$test
->value .
": $@\n"
;
$return
= 1;
};
return
if
(
$return
);
}
}
if
(
$self
->debug) {
warn
"Dataset before update operation:\n"
;
warn
$self
->model_as_string(
$test_model
);
}
my
$ok
= 0;
eval
{
my
$algebra
=
eval
{ Attean->get_parser(
'SPARQL'
)->parse_update(
$sparql
) };
if
($@) {
warn
"Failed to parse query $filename: $@"
;
die
$@;
}
unless
(
$algebra
) {
warn
"No algebra generated for update\n"
;
fail(
$test
->value);
return
;
}
if
(
$self
->debug) {
warn
"# Algebra:\n"
.
$algebra
->as_string .
"\n"
;
}
my
$default_graphs
= [
$self
->default_graph];
my
$planner
= Attean::IDPQueryPlanner->new();
my
$plan
=
$planner
->plan_for_algebra(
$algebra
,
$test_model
,
$default_graphs
);
if
(
$self
->debug) {
warn
"# Plan:\n"
.
$plan
->as_string .
"\n"
;
}
if
(
$self
->debug) {
warn
"Running update...\n"
;
}
my
$iter
=
$plan
->evaluate(
$test_model
);
$iter
->elements;
if
(
$self
->debug) {
warn
"done.\n"
;
}
if
(
$self
->debug) {
warn
"Comparing results...\n"
;
}
my
$eqtest
= Attean::BindingEqualityTest->new();
my
$eq
=
$eqtest
->equals(
$test_model
,
$expected_model
);
if
(
$self
->debug) {
warn
"done.\n"
;
}
$ok
= is(
$eq
, 1,
$test
->value );
unless
(
$ok
) {
warn
$eqtest
->error;
warn
"Got model:\n"
.
$self
->model_as_string(
$test_model
);
warn
"Expected model:\n"
.
$self
->model_as_string(
$expected_model
);
}
};
if
($@) {
warn
"Failed to execute update: $@"
;
fail(
$test
->value);
}
if
(not(
$ok
)) {
print
"# failed: "
.
$test
->value .
"\n"
;
}
warn
"ok\n"
if
(
$self
->debug);
}
sub
construct_data {
my
$self
=
shift
;
my
$model
=
shift
;
my
$action
=
shift
;
my
(
$queryd
) =
$model
->objects(
$action
, iri(
"${RQ}query"
) )->elements;
my
@data
=
$model
->objects(
$action
, iri(
"${RQ}data"
) )->elements;
my
@gdata
=
$model
->objects(
$action
, iri(
"${RQ}graphData"
) )->elements;
my
@sdata
=
$model
->objects(
$action
, iri(
"${RQ}serviceData"
) )->elements;
my
@cdata
=
$model
->objects(
$action
, iri(
"${RQ}constructDataFile"
) )->elements;
my
(
$fnode
) =
$model
->objects(
$action
, iri(
"${RQ}format"
) )->elements;
my
$format
= blessed(
$fnode
) ?
$fnode
->value :
'text/turtle'
;
my
$uri
= URI->new(
$queryd
->value );
my
$filename
=
$uri
->file;
my
(
undef
,
$base
,
undef
) = File::Spec->splitpath(
$filename
);
warn
"Loading SPARQL query from file $filename"
if
(
$self
->debug);
my
$sparql
=
do
{
local
($/) =
undef
;
open
(
my
$fh
,
'<'
,
$filename
) or
do
{
warn
(
"$!: $filename"
);
return
};
binmode
(
$fh
,
':utf8'
); <
$fh
> };
my
$test_model
=
$self
->test_model();
foreach
my
$data
(
@data
) {
if
(blessed(
$data
)) {
$test_model
->load_urls_into_graph(
$self
->default_graph,
$data
);
}
}
foreach
my
$g
(
@gdata
) {
my
$start
=
$test_model
->size;
$test_model
->load_urls_into_graph(
$g
,
$g
);
my
$end
=
$test_model
->size;
unless
(
$start
<
$end
) {
warn
"*** Loading file did not result in any new quads: "
.
$g
;
}
}
foreach
my
$n
(
@cdata
) {
my
(
$bytes
,
$format
) =
$self
->construct_data(
$model
,
$n
);
my
$p
= Attean->get_parser(
media_type
=>
$format
)->new();
my
$iter
=
$p
->parse_iter_from_bytes(
$bytes
);
$test_model
->add_iter(
$iter
->as_quads(
$self
->default_graph));
}
print
STDERR
"ok\n"
if
(
$self
->debug);
if
(
$self
->debug) {
my
$q
=
$sparql
;
$q
=~ s/([\x{256}-\x{1000}])/
'\x{'
.
sprintf
(
'%x'
,
ord
($1)) .
'}'
/eg;
warn
$q
;
}
my
(
$iter
,
$type
) =
$self
->get_actual_results(
$filename
,
$test_model
,
$sparql
,
$base
,
$model
, \
@sdata
);
my
$sclass
= Attean->get_serializer(
media_type
=>
$format
);
my
$s
=
$sclass
->new();
my
$bytes
=
$s
->serialize_iter_to_bytes(
$iter
);
return
(
$bytes
,
$format
);
}
sub
query_eval_test {
my
$self
=
shift
;
my
$model
=
shift
;
my
$test
=
shift
;
my
$count
=
shift
// 1;
my
(
$action
) =
$model
->objects(
$test
, iri(
"${MF}action"
) )->elements;
my
(
$result
) =
$model
->objects(
$test
, iri(
"${MF}result"
) )->elements;
my
(
$req
) =
$model
->objects(
$test
, iri(
"${MF}requires"
) )->elements;
my
(
$approved
) =
$model
->objects(
$test
, iri(
"${DAWGT}approval"
) )->elements;
my
(
$queryd
) =
$model
->objects(
$action
, iri(
"${RQ}query"
) )->elements;
my
@data
=
$model
->objects(
$action
, iri(
"${RQ}data"
) )->elements;
my
@gdata
=
$model
->objects(
$action
, iri(
"${RQ}graphData"
) )->elements;
my
@sdata
=
$model
->objects(
$action
, iri(
"${RQ}serviceData"
) )->elements;
my
@cdata
=
$model
->objects(
$action
, iri(
"${RQ}constructDataFile"
) )->elements;
if
(
$self
->strict_approval) {
unless
(
$approved
) {
warn
"- skipping test because it isn't approved\n"
if
(
$self
->debug);
return
;
}
if
(
$approved
->equal(
"${DAWGT}NotClassified"
)) {
warn
"- skipping test because its approval is dawgt:NotClassified\n"
if
(
$self
->debug);
return
;
}
}
my
$uri
= URI->new(
$queryd
->value );
my
$filename
=
$uri
->file;
my
(
undef
,
$base
,
undef
) = File::Spec->splitpath(
$filename
);
warn
"Loading SPARQL query from file $filename"
if
(
$self
->debug);
my
$sparql
=
do
{
local
($/) =
undef
;
open
(
my
$fh
,
'<'
,
$filename
) or
do
{
warn
(
"$!: $filename; "
.
$test
->value);
return
};
binmode
(
$fh
,
':utf8'
); <
$fh
> };
my
$q
=
$sparql
;
$q
=~ s/\s+/ /g;
if
(
$self
->debug) {
warn
"### test : "
.
$test
->value .
"\n"
;
warn
"# sparql : $q\n"
;
foreach
my
$data
(
@data
) {
warn
"# data : "
. (
$data
->value =~ s#file://##r) .
"\n"
if
(blessed(
$data
));
}
warn
"# graph data : "
. (
$_
->value =~ s#file://##r) .
"\n"
for
(
@gdata
);
warn
"# constructed data : "
. (
$_
->value =~ s#file://##r) .
"\n"
for
(
@cdata
);
warn
"# result : "
. (
$result
->value =~ s#file://##r) .
"\n"
;
warn
"# requires : "
. (
$req
->value =~ s#file://##r) .
"\n"
if
(blessed(
$req
));
}
STRESS:
foreach
(1 ..
$count
) {
print
STDERR
"constructing model... "
if
(
$self
->debug);
my
$test_model
=
$self
->test_model();
my
$next_stress
= 0;
try
{
foreach
my
$data
(
@data
) {
if
(blessed(
$data
)) {
$test_model
->load_urls_into_graph(
$self
->default_graph,
$data
);
}
}
foreach
my
$g
(
@gdata
) {
my
$start
=
$test_model
->size;
$test_model
->load_urls_into_graph(
$g
,
$g
);
my
$end
=
$test_model
->size;
unless
(
$start
<
$end
) {
warn
"*** Loading file did not result in any new quads: "
.
$g
;
}
}
foreach
my
$n
(
@cdata
) {
my
(
$bytes
,
$format
) =
$self
->construct_data(
$model
,
$n
);
my
$p
= Attean->get_parser(
media_type
=>
$format
)->new();
my
$iter
=
$p
->parse_iter_from_bytes(
$bytes
);
$test_model
->add_iter(
$iter
->as_quads(
$self
->default_graph));
}
}
catch
{
fail(
$test
->value);
$self
->record_result(
'evaluation'
, 0,
$test
->value);
print
"# died: "
.
$test
->value .
": $_\n"
;
$next_stress
++;
};
next
STRESS
if
$next_stress
;
print
STDERR
"ok\n"
if
(
$self
->debug);
my
$resuri
= URI->new(
$result
->value );
my
$resfilename
=
$resuri
->file;
TODO: {
local
(
$TODO
) = (blessed(
$req
)) ?
"requires "
.
$req
->value :
''
;
my
$comment
;
eval
{
if
(
$self
->debug) {
my
$q
=
$sparql
;
$q
=~ s/([\x{256}-\x{1000}])/
'\x{'
.
sprintf
(
'%x'
,
ord
($1)) .
'}'
/eg;
warn
$q
;
}
my
(
$actual
,
$type
);
{
local
($::DEBUG) = 1;
print
STDERR
"getting actual results... "
if
(
$self
->debug);
(
$actual
,
$type
) =
$self
->get_actual_results(
$filename
,
$test_model
,
$sparql
,
$base
,
$model
, \
@sdata
);
print
STDERR
"ok\n"
if
(
$self
->debug);
}
print
STDERR
"getting expected results... "
if
(
$self
->debug);
my
$expected
=
$self
->get_expected_results(
$resfilename
,
$type
);
print
STDERR
"ok\n"
if
(
$self
->debug);
$self
->compare_results(
$expected
,
$actual
,
$test
->value, \
$comment
);
};
my
$ok
= not($@);
unless
(
$ok
) {
warn
$@;
fail(
$test
->value);
$self
->record_result(
'evaluation'
, 0,
$test
->value);
};
if
(
$ok
) {
}
else
{
print
"# failed: "
.
$test
->value .
"\n"
;
}
}
}
}
sub
mock_endpoints {
my
$self
=
shift
;
my
$mock
=
shift
;
my
$model
=
shift
;
my
$sdata
=
shift
;
foreach
my
$sd
(
@$sdata
) {
my
(
$e
) =
$model
->objects(
$sd
, iri(
"${RQ}endpoint"
) )->elements;
my
@data
=
$model
->objects(
$sd
, iri(
"${RQ}data"
) )->elements;
my
@gdata
=
$model
->objects(
$sd
, iri(
"${RQ}graphData"
) )->elements;
my
$endpoint
=
$e
->value;
my
$test_model
=
$self
->test_model();
foreach
my
$data
(
@data
) {
if
(blessed(
$data
)) {
$test_model
->load_urls_into_graph(
$self
->default_graph,
$data
);
}
}
foreach
my
$g
(
@gdata
) {
my
$start
=
$test_model
->size;
$test_model
->load_urls_into_graph(
$g
,
$g
);
my
$end
=
$test_model
->size;
unless
(
$start
<
$end
) {
warn
"*** Loading file did not result in any new quads: "
.
$g
;
}
}
$mock
->register_test_endpoint(
$endpoint
, [
$test_model
,
$self
->default_graph]);
}
}
sub
get_actual_results {
my
$self
=
shift
;
my
$filename
=
shift
;
my
$model
=
shift
;
my
$sparql
=
shift
;
my
$base
=
shift
;
my
$manifest_model
=
shift
;
my
$sdata
=
shift
;
my
$bytes
= encode_utf8(
$sparql
);
my
$s
= AtteanX::Parser::SPARQL->new(
base
=>
$base
);
my
$algebra
;
eval
{
(
$algebra
) =
$s
->parse_list_from_bytes(
$bytes
);
};
if
($@) {
warn
"Failed to parse query $filename: $@"
;
die
$@;
}
if
(
$self
->debug) {
warn
"Walking algebra:\n"
;
warn
$algebra
->as_string;
}
if
(
$self
->debug) {
my
$iter
=
$model
->get_quads;
warn
"Dataset:\n-------------\n"
;
while
(
my
$q
=
$iter
->
next
) {
say
$q
->as_string;
}
warn
"-------------\n"
;
}
my
$rmodel
= memory_model();
my
$results
;
if
(
$self
->use_idp_planner) {
my
$default_graphs
= [
$self
->default_graph];
my
$planner
= Test::Attean::TestIDPQueryPlanner->new();
$self
->mock_endpoints(
$planner
,
$manifest_model
,
$sdata
);
my
$plan
=
$planner
->plan_for_algebra(
$algebra
,
$model
,
$default_graphs
);
if
(
$self
->debug) {
warn
"Walking plan:\n"
;
warn
$plan
->as_string;
}
$results
=
eval
{
$plan
->evaluate(
$model
) };
warn
$@
if
$@;
}
else
{
my
$e
= Test::Attean::TestSimpleQueryEvaluator->new(
model
=>
$model
,
default_graph
=>
$self
->default_graph );
$self
->mock_endpoints(
$e
,
$manifest_model
,
$sdata
);
$results
=
eval
{
$e
->evaluate(
$algebra
,
$self
->default_graph) };
warn
$@
if
$@;
}
my
$count
= 1;
$results
=
$results
->materialize;
my
$item
=
$results
->peek;
my
$type
=
'bindings'
;
if
(
$item
) {
if
(
$item
->does(
'Attean::API::Triple'
)) {
$type
=
'graph'
;
}
elsif
(
$item
->does(
'Attean::API::Term'
)) {
$type
=
'boolean'
;
}
}
$self
->print_results(
"Actual results"
, \
$results
)
if
(
$self
->results);
return
(
$results
,
$type
);
if
(
$results
->is_bindings) {
return
(
$results
,
'bindings'
);
}
elsif
(
$results
->is_boolean) {
$rmodel
->add_statement( triple( iri(
"${testns}result"
), iri(
"${testns}boolean"
), literal((
$results
->get_boolean ?
'true'
:
'false'
),
undef
,
"${XSD}boolean"
) ) );
return
(
$rmodel
->get_statements,
'boolean'
);
}
elsif
(
$results
->is_graph) {
return
(
$results
,
'graph'
);
}
else
{
warn
"unknown result type: "
. Dumper(
$results
);
}
}
sub
print_results {
my
$self
=
shift
;
my
$name
=
shift
;
my
$results
=
shift
;
$$results
=
$$results
->materialize;
print
"$name:\n"
;
my
$count
= 1;
while
(
my
$r
=
$$results
->
next
) {
printf
(
"%3d %s\n"
,
$count
++,
$r
->as_string);
}
$$results
->
reset
;
}
sub
get_expected_results {
my
$self
=
shift
;
my
$file
=
shift
;
my
$type
=
shift
;
if
(
$type
eq
'graph'
) {
my
$model
= memory_model();
$model
->load_urls_into_graph(
$self
->default_graph, iri(
"file://$file"
));
my
$results
=
$model
->get_quads->
map
(
sub
{
shift
->as_triple },
'Attean::API::Triple'
);
$self
->print_results(
"Expected results"
, \
$results
)
if
(
$self
->results);
return
$results
;
}
elsif
(
$file
=~ /[.](srj|json)/) {
my
$model
= memory_model();
open
(
my
$fh
,
'<'
,
$file
) or
die
$!;
my
$parser
= Attean->get_parser(
'SPARQLJSON'
)->new();
my
$results
=
$parser
->parse_iter_from_io(
$fh
)->materialize;
my
$item
=
$results
->peek;
if
(blessed(
$item
) and
$item
->does(
'Attean::API::Term'
)) {
if
(
$self
->results) {
warn
"Expected result: "
.
$item
->as_string .
"\n"
;
}
return
$results
;
}
else
{
$self
->print_results(
"Expected results"
, \
$results
)
if
(
$self
->results);
return
$results
;
}
}
elsif
(
$file
=~ /[.]srx/) {
my
$model
= memory_model();
my
$parser
= Attean->get_parser(
'sparqlxml'
)->new();
open
(
my
$fh
,
'<'
,
$file
);
my
$results
=
$parser
->parse_iter_from_io(
$fh
);
$self
->print_results(
"Expected results"
, \
$results
)
if
(
$self
->results);
return
$results
;
}
elsif
(
$file
=~ /[.]csv/) {
my
$csv
= Text::CSV->new({
binary
=> 1});
open
(
my
$fh
,
"<:encoding(utf8)"
,
$file
) or
die
$!;
my
$header
=
$csv
->getline(
$fh
);
my
@vars
=
@$header
;
my
@data
;
while
(
my
$row
=
$csv
->getline(
$fh
)) {
my
%result
;
foreach
my
$i
(0 ..
$#vars
) {
my
$var
=
$vars
[
$i
];
my
$value
=
$row
->[
$i
];
if
(
$value
=~ /^_:(\w+)$/) {
$value
= blank($1);
}
elsif
(
$value
=~ /
$RE
{URI}/) {
$value
= iri(
$value
);
}
elsif
(
defined
(
$value
) and
length
(
$value
)) {
$value
= literal(
$value
);
}
if
(
ref
(
$value
)) {
$result
{
$var
} =
$value
;
}
}
push
(
@data
, Attean::Result->new(
bindings
=> \
%result
));
}
my
$results
= Attean::ListIterator->new(
values
=> \
@data
,
item_type
=>
'Attean::API::Result'
,
variables
=> \
@vars
);
$self
->print_results(
"Expected results"
, \
$results
)
if
(
$self
->results);
return
$results
;
}
elsif
(
$file
=~ /[.]tsv/) {
my
$parser
= Attean->get_parser(
'SPARQLTSV'
)->new();
open
(
my
$fh
,
"<:encoding(utf8)"
,
$file
) or
die
$!;
my
$iter
=
$parser
->parse_iter_from_io(
$fh
);
return
$iter
;
}
elsif
(
$file
=~ /[.](ttl|rdf|nt)/) {
my
$model
= memory_model();
$model
->load_urls_into_graph(
$self
->default_graph, iri(
"file://$file"
));
my
(
$res
) =
$model
->subjects( iri(
"${RDF}type"
), iri(
"${RS}ResultSet"
) )->elements;
if
(
my
(
$b
) =
$model
->objects(
$res
, iri(
"${RS}boolean"
) )->elements) {
my
$bool
=
$b
->value;
my
$term
= literal(
value
=>
$bool
,
datatype
=>
"${XSD}boolean"
);
if
(
$self
->results) {
warn
"Expected result: "
.
$term
->as_string .
"\n"
;
}
return
Attean::ListIterator->new(
values
=> [
$term
],
item_type
=>
'Attean::API::Term'
);
}
else
{
my
@vars
=
$model
->objects(
$res
, iri(
"${RS}resultVariable"
) )->elements;
my
@sols
=
$model
->objects(
$res
, iri(
"${RS}solution"
) )->elements;
my
@names
=
map
{
$_
->value }
@vars
;
my
@bindings
;
my
%vars
;
foreach
my
$r
(
@sols
) {
my
%data
;
my
@b
=
$model
->objects(
$r
, iri(
"${RS}binding"
) )->elements;
foreach
my
$b
(
@b
) {
my
(
$value
) =
$model
->objects(
$b
, iri(
"${RS}value"
) )->elements;
my
(
$var
) =
$model
->objects(
$b
, iri(
"${RS}variable"
) )->elements;
$data
{
$var
->value } =
$value
;
$vars
{
$var
->value }++;
}
push
(
@bindings
, Attean::Result->new(
bindings
=> \
%data
));
}
my
$results
= Attean::ListIterator->new(
values
=> \
@bindings
,
item_type
=>
'Attean::API::Result'
,
variables
=> [
keys
%vars
]);
$self
->print_results(
"Expected results"
, \
$results
)
if
(
$self
->results);
return
$results
;
}
}
else
{
die
"Unrecognized type of expected results: $file"
;
}
}
sub
compare_results {
my
$self
=
shift
;
my
$expected
=
shift
->canonicalize->materialize;
my
$actual
=
shift
->canonicalize->materialize;
my
$test
=
shift
;
my
$comment
=
shift
||
do
{
my
$foo
; \
$foo
};
my
$TODO
=
shift
;
if
(
$actual
->does(
'Attean::API::ResultIterator'
) or
$actual
->does(
'Attean::API::TripleIterator'
)) {
my
$eqtest
= Attean::BindingEqualityTest->new();
if
(
$test
=~ /csv0/) {
my
$mapper
= Attean::TermMap->new(
mapper
=>
sub
{
my
$term
=
shift
;
if
(
$term
->does(
'Attean::API::Literal'
)) {
return
Attean::Literal->new(
value
=>
$term
->value);
}
return
$term
;
});
$actual
=
$actual
->
map
(
$mapper
->binding_mapper);
}
my
$ok
=
eval
{ ok(
$eqtest
->equals(
$actual
,
$expected
),
$test
) or diag(
$eqtest
->error) };
if
($@) {
diag($@);
}
$self
->record_result(
'evaluation'
,
$ok
,
$test
);
return
$ok
;
}
elsif
(
$actual
->does(
'Attean::API::TermIterator'
)) {
my
$a
=
$actual
->
next
;
my
$e
=
$expected
->
next
;
my
$name
=
$self
->debug ?
sprintf
(
"$test: %s == %s"
,
$a
->as_string,
$e
->as_string) :
$test
;
my
$ok
= ok(
$a
->equals(
$e
),
$name
);
$self
->record_result(
'evaluation'
,
$ok
,
$test
);
return
$ok
;
}
else
{
die
"Unexpected result type $actual"
;
}
}
sub
record_result {
my
$self
=
shift
;
my
$type
=
shift
;
my
$ok
=
shift
;
my
$name
=
shift
;
unless
(
$ok
) {
push
(@{
$self
->failures->{
$type
} },
$name
);
}
}
sub
model_as_string {
my
$self
=
shift
;
my
$model
=
shift
;
my
$ser
= Attean->get_serializer(
'nquads'
);
my
$sep
= (
'####'
x 25) .
"\n"
;
my
$s
=
sprintf
(
"Model with %d quads:\n"
,
$model
->size);
$s
.=
$ser
->serialize_iter_to_bytes(
$model
->get_quads);
return
$sep
.
$s
.
$sep
;
}
sub
DESTROY {
my
$self
=
shift
;
my
$count
= 0;
while
(
my
(
$type
,
$failures
) =
each
(%{
$self
->failures })) {
$count
+=
scalar
(
@$failures
);
}
if
(
$self
->run_query_tests and
$count
) {
my
$d
= Data::Dumper->new([
$self
->failures], [
qw(failures)
]);
$d
->Sortkeys(1)->Indent(2);
my
$msg
=
"Failing tests: "
.
$d
->Dump;
warn
$msg
;
unless
(
$self
->pattern) {
open
(
my
$fh
,
'>'
,
sprintf
(
'.sparql-test-suite-%d'
,
scalar
(
time
)));
while
(
my
(
$type
,
$failures
) =
each
(%{
$self
->failures })) {
say
$fh
$type
;
say
$fh
join
(
"\n"
,
sort
@$failures
);
}
}
}
}
1;