Dave Cross: Still Munging Data With Perl: Online event - Mar 17 Learn more

use v5.14;
use Attean;
use Carp;
use Encode qw(encode encode_utf8 decode_utf8);
use Regexp::Common qw /URI/;
use Scalar::Util qw(blessed reftype);
use List::Util qw(all);
use Types::Standard qw(Str Bool ArrayRef HashRef InstanceOf ConsumerOf);
require XML::Simple;
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;
$self->default_graph(iri('http://graph/'));
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 );
$base = "file://${base}";
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 );
$base = "file://${base}";
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));
}
# TODO: set up remote endpoint mock
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 );
$base = "file://${base}";
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 );
$base = "file://${base}";
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);
# warn "comparing results...";
$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 ];
# XXX @@ heuristics that won't always work.
# XXX @@ expected to work on the test suite, though
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/) {
# CSV is a lossy format, so strip the languages and datatypes off of literals in the actual results (so that they'll match up with the (lossy) expected results
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;