File Coverage

blib/lib/Test/RDF.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package Test::RDF;
2              
3 1     1   15229 use warnings;
  1         1  
  1         29  
4 1     1   3 use strict;
  1         2  
  1         25  
5              
6 1     1   4 use Carp qw(confess);
  1         5  
  1         54  
7 1     1   193 use RDF::Trine;
  0            
  0            
8             use RDF::Trine::Parser;
9             use RDF::Trine::Model;
10             use RDF::Trine::Graph;
11             use Scalar::Util qw/blessed/;
12              
13             use base 'Test::Builder::Module';
14             our @EXPORT = qw/are_subgraphs is_rdf is_valid_rdf isomorph_graphs has_subject has_predicate has_object_uri has_uri hasnt_uri has_literal hasnt_literal pattern_target pattern_ok pattern_fail/;
15              
16              
17             =head1 NAME
18              
19             Test::RDF - Test RDF data for content, validity and equality, etc.
20              
21             =head1 VERSION
22              
23             Version 1.20
24              
25             =cut
26              
27             our $VERSION = '1.20';
28              
29              
30             =head1 SYNOPSIS
31              
32             use Test::RDF;
33              
34             is_valid_rdf($rdf_string, $syntax, 'RDF string is valid according to selected syntax');
35             is_rdf($rdf_string, $syntax1, $expected_rdf_string, $syntax2, 'The two strings have the same triples');
36             isomorph_graphs($model, $expected_model, 'The two models have the same triples');
37             are_subgraphs($model1, $model2, 'Model 1 is a subgraph of model 2' );
38             has_uri($uri_string, $model, 'Has correct URI');
39             hasnt_uri($uri_string, $model, "Hasn't correct URI");
40             has_subject($uri_string, $model, 'Subject URI is found');
41             has_predicate($uri_string, $model, 'Predicate URI is found');
42             has_object_uri($uri_string, $model, 'Object URI is found');
43             has_literal($string, $language, $datatype, $model, 'Literal is found');
44             hasnt_literal($string, $language, $datatype, $model, 'Literal is not found');
45             pattern_target($model);
46             pattern_ok($pattern, '$pattern found in $model');
47             pattern_fail($pattern, '$pattern not found in $model');
48              
49             =head1 DESCRIPTION
50              
51             This Perl module, Test::RDF, provides tools for testing code which
52             deals with RDF. It can test RDF for validity, check if two RDF graphs
53             are the same, or subgraphs of each other, if a URI is or is not in a
54             dataset, if it has certain subjects, predicates, objects or
55             literals. It can also test to see if a full pattern is present or
56             absent.
57              
58              
59             =head1 EXPORT
60              
61             =head2 is_valid_rdf
62              
63             Use to check if the input RDF string is valid in the chosen syntax
64              
65             =cut
66              
67             sub is_valid_rdf {
68             my ($rdf, $syntax, $name) = @_;
69             my $test = __PACKAGE__->builder;
70             unless ($rdf) {
71             $test->ok( 0, $name );
72             $test->diag("No input was given.");
73             return;
74             }
75             my $parser = RDF::Trine::Parser->new($syntax);
76             eval {
77             $parser->parse('http://example.org/', $rdf, sub {});
78             };
79             if ( my $error = $@ ) {
80             $test->ok( 0, $name );
81             $test->diag("Input was not valid RDF:\n\n\t$error");
82             return;
83             }
84             else {
85             $test->ok( 1, $name );
86             return 1;
87             }
88             }
89              
90              
91             =head2 is_rdf
92              
93             Use to check if the input RDF strings are isomorphic (i.e. the same).
94              
95             =cut
96              
97              
98             sub is_rdf {
99             my ($rdf1, $syntax1, $rdf2, $syntax2, $name) = @_;
100             my $test = __PACKAGE__->builder;
101             unless ($rdf1) {
102             $test->ok( 0, $name );
103             $test->diag("No input was given.");
104             return;
105             }
106             my $parser1 = RDF::Trine::Parser->new($syntax1);
107             local $Test::Builder::Level = $Test::Builder::Level + 1;
108              
109             # First, test if the input RDF is OK
110             my $model1 = RDF::Trine::Model->temporary_model;
111             eval {
112             $parser1->parse_into_model('http://example.org/', $rdf1, $model1);
113             };
114             if ( my $error = $@ ) {
115             $test->ok( 0, $name );
116             $test->diag("Input was not valid RDF:\n\n\t$error");
117             return;
118             }
119              
120             # If the expected RDF is non-valid, don't catch the exception
121             my $parser2 = RDF::Trine::Parser->new($syntax2);
122             my $model2 = RDF::Trine::Model->temporary_model;
123             $parser2->parse_into_model('http://example.org/', $rdf2, $model2);
124             return isomorph_graphs($model1, $model2, $name);
125             }
126              
127              
128             =head2 isomorph_graphs
129              
130             Use to check if the input RDF::Trine::Models have isomorphic graphs.
131              
132             =cut
133              
134              
135             sub isomorph_graphs {
136             my ($model1, $model2, $name) = @_;
137             confess 'No valid models given in test' unless ((blessed($model1) && $model1->isa('RDF::Trine::Model'))
138             && (blessed($model2) && $model2->isa('RDF::Trine::Model')));
139             my $g1 = RDF::Trine::Graph->new( $model1 );
140             my $g2 = RDF::Trine::Graph->new( $model2 );
141             my $test = __PACKAGE__->builder;
142              
143             if ($g1->equals($g2)) {
144             $test->ok( 1, $name );
145             return 1;
146             } else {
147             $test->ok( 0, $name );
148             $test->diag('Graphs differ:');
149             $test->diag($g1->error);
150             return;
151             }
152             }
153              
154             =head2 are_subgraphs
155              
156             Use to check if the first RDF::Trine::Models is a subgraph of the second.
157              
158             =cut
159              
160             sub are_subgraphs {
161             my ($model1, $model2, $name) = @_;
162             confess 'No valid models given in test' unless ((blessed($model1) && $model1->isa('RDF::Trine::Model'))
163             && (blessed($model2) && $model2->isa('RDF::Trine::Model')));
164             my $g1 = RDF::Trine::Graph->new( $model1 );
165             my $g2 = RDF::Trine::Graph->new( $model2 );
166             my $test = __PACKAGE__->builder;
167              
168             if ($g1->is_subgraph_of($g2)) {
169             $test->ok( 1, $name );
170             return 1;
171             } else {
172             $test->ok( 0, $name );
173             $test->diag('Graph not subgraph: ' . $g1->error) if defined($g1->error);
174             $test->diag('Hint: There are ' . $model1->size . ' statement(s) in model1 and ' . $model2->size . ' statement(s) in model2');
175             return;
176             }
177             }
178              
179             =head2 has_subject
180              
181             Check if the string URI passed as first argument is a subject in any
182             of the statements given in the model given as second argument.
183              
184             =cut
185              
186             sub has_subject {
187             my ($uri, $model, $name) = @_;
188             confess 'No valid model given in test' unless (blessed($model) && $model->isa('RDF::Trine::Model'));
189             my $resource = _resource_uri_checked($uri, $name);
190             return $resource unless ($resource);
191             my $count = $model->count_statements($resource, undef, undef);
192             return _single_uri_tests($count, $name);
193             }
194              
195              
196             =head2 has_predicate
197              
198             Check if the string URI passed as first argument is a predicate in any
199             of the statements given in the model given as second argument.
200              
201             =cut
202              
203             sub has_predicate {
204             my ($uri, $model, $name) = @_;
205             confess 'No valid model given in test' unless (blessed($model) && $model->isa('RDF::Trine::Model'));
206             my $resource = _resource_uri_checked($uri, $name);
207             return $resource unless ($resource);
208             my $count = $model->count_statements(undef, $resource, undef);
209             return _single_uri_tests($count, $name);
210             }
211              
212             =head2 has_object_uri
213              
214             Check if the string URI passed as first argument is a object in any
215             of the statements given in the model given as second argument.
216              
217             =cut
218              
219             sub has_object_uri {
220             my ($uri, $model, $name) = @_;
221             confess 'No valid model given in test' unless (blessed($model) && $model->isa('RDF::Trine::Model'));
222             my $resource = _resource_uri_checked($uri, $name);
223             return $resource unless ($resource);
224             my $count = $model->count_statements(undef, undef, $resource);
225             return _single_uri_tests($count, $name);
226             }
227              
228             =head2 has_literal
229              
230             Check if the string passed as first argument, with corresponding
231             optional language and datatype as second and third respectively, is a
232             literal in any of the statements given in the model given as fourth
233             argument.
234              
235             language and datatype may not occur in the same statement, so the test
236             fails if they are both set. If none are used, use C<undef>, like e.g.
237              
238             has_literal('A test', undef, undef, $model, 'Simple literal');
239              
240             A test for a typed literal may be done like
241              
242             has_literal('42', undef, 'http://www.w3.org/2001/XMLSchema#integer', $model, 'Just an integer');
243              
244             and a language literal like
245              
246             has_literal('This is a Another test', 'en', undef, $model, 'Language literal');
247              
248              
249             =cut
250              
251             sub has_literal {
252             my ($string, $lang, $datatype, $model, $name) = @_;
253             confess 'No valid model given in test' unless (blessed($model) && $model->isa('RDF::Trine::Model'));
254             my $literal;
255             my $test = __PACKAGE__->builder;
256             eval {
257             $literal = RDF::Trine::Node::Literal->new($string, $lang, $datatype);
258             };
259             if ( my $error = $@ ) {
260             $test->ok( 0, $name );
261             $test->diag("Invalid literal:\n\n\t$error");
262             return;
263             }
264              
265             if ($model->count_statements(undef, undef, $literal) > 0) {
266             $test->ok( 1, $name );
267             return 1;
268             } else {
269             $test->ok( 0, $name );
270             $test->diag('No matching literals found in model');
271             return 0;
272             }
273             }
274              
275              
276             =head2 hasnt_literal
277              
278             This is like the above, only the opposite: It checks if a literal
279             doesn't exist. Like the above, the test will fail if the literal is
280             invalid, however.
281              
282             =cut
283              
284             sub hasnt_literal {
285             my ($string, $lang, $datatype, $model, $name) = @_;
286             confess 'No valid model given in test' unless (blessed($model) && $model->isa('RDF::Trine::Model'));
287             my $literal;
288             my $test = __PACKAGE__->builder;
289             eval {
290             $literal = RDF::Trine::Node::Literal->new($string, $lang, $datatype);
291             };
292              
293             if ( my $error = $@ ) {
294             $test->ok( 0, $name );
295             $test->diag("Invalid literal:\n\n\t$error");
296             return;
297             }
298              
299             if ($model->count_statements(undef, undef, $literal) > 0) {
300             $test->ok( 0, $name );
301             $test->diag('Matching literals found in model');
302             return 0;
303             } else {
304             $test->ok( 1, $name );
305             return 1;
306             }
307             }
308              
309              
310              
311             =head2 has_uri
312              
313             Check if the string URI passed as first argument is present in any of
314             the statements given in the model given as second argument.
315              
316             =cut
317              
318             sub has_uri {
319             my ($uri, $model, $name) = @_;
320             confess 'No valid model given in test' unless (blessed($model) && $model->isa('RDF::Trine::Model'));
321             my $test = __PACKAGE__->builder;
322             my $resource = _resource_uri_checked($uri, $name);
323             return $resource unless ($resource);
324             if ($model->count_statements(undef, undef, $resource) > 0
325             || $model->count_statements(undef, $resource, undef) > 0
326             || $model->count_statements($resource, undef, undef) > 0) {
327             $test->ok( 1, $name );
328             return 1;
329             } else {
330             $test->ok( 0, $name );
331             $test->diag('No matching URIs found in model');
332             return 0;
333             }
334             }
335              
336              
337             =head2 hasnt_uri
338              
339             Check if the string URI passed as first argument is not present in any
340             of the statements given in the model given as second argument.
341              
342             =cut
343              
344             sub hasnt_uri {
345             my ($uri, $model, $name) = @_;
346             confess 'No valid model given in test' unless (blessed($model) && $model->isa('RDF::Trine::Model'));
347             my $test = __PACKAGE__->builder;
348             my $resource;
349             eval {
350             $resource = RDF::Trine::Node::Resource->new($uri);
351             };
352             if (($resource) && ($model->count_statements(undef, undef, $resource) > 0
353             || $model->count_statements(undef, $resource, undef) > 0
354             || $model->count_statements($resource, undef, undef)) > 0) {
355             $test->ok( 0, $name );
356             $test->diag('Matching URIs found in model');
357             return 0;
358             } else {
359             $test->ok( 1, $name );
360             return 1;
361             }
362             }
363              
364              
365             sub _single_uri_tests {
366             my ($count, $name) = @_;
367             my $test = __PACKAGE__->builder;
368             local $Test::Builder::Level = $Test::Builder::Level + 1;
369             if ($count > 0) {
370             $test->ok( 1, $name );
371             return 1;
372             } else {
373             $test->ok( 0, $name );
374             $test->diag('No matching URIs found in model');
375             return 0;
376             }
377             }
378              
379             sub _resource_uri_checked {
380             my ($uri, $name) = @_;
381             my $resource;
382             eval {
383             $resource = RDF::Trine::Node::Resource->new($uri);
384             };
385             if ( my $error = $@ ) {
386             my $test = __PACKAGE__->builder;
387             local $Test::Builder::Level = $Test::Builder::Level + 1;
388             $test->ok( 0, $name );
389             $test->diag("No matching URIs found in model");
390             return 0;
391             }
392             return $resource;
393             }
394              
395            
396              
397              
398              
399             =head2 pattern_target
400              
401             Tests that the object passed as its parameter is an RDF::Trine::Model or
402             RDF::Trine::Store. That is, tests that it is a valid thing to match basic
403             graph patterns against.
404              
405             Additionally, this test establishes the target for future C<pattern_ok> tests.
406              
407             =head2 pattern_ok
408              
409             Tests that the pattern passed matches against the target established by
410             C<pattern_target>. The pattern may be passed as an RDF::Trine::Pattern, or
411             a list of RDF::Trine::Statement objects.
412              
413             use Test::RDF;
414             use RDF::Trine qw[iri literal blank variable statement];
415             use My::Module;
416              
417             my $foaf = RDF::Trine::Namespace->new('http://xmlns.com/foaf/0.1/');
418             pattern_target(My::Module->get_model); # check isa RDF::Trine::Model
419             pattern_ok(
420             statement(
421             variable('who'),
422             $foaf->name,
423             literal('Kjetil Kjernsmo')
424             ),
425             statement(
426             variable('who'),
427             $foaf->page,
428             iri('http://search.cpan.org/~kjetilk/')
429             ),
430             "Data contains Kjetil's details."
431             );
432              
433             B<Note:> C<pattern_target> must have been tested before any C<pattern_ok> tests.
434              
435             =head2 pattern_fail
436              
437             The same as above, but tests if the pattern returns no results instead.
438              
439             =cut
440              
441             { # scope for $target
442             my $target;
443             sub pattern_target {
444             my $t = shift;
445             my $test = __PACKAGE__->builder;
446             if (blessed($t) && $t->isa('RDF::Trine::Model')) {
447             $target = $t;
448             $test->ok(1, 'Data is an RDF::Trine::Model.');
449             return 1;
450             }
451             elsif (blessed($t) && $t->isa('RDF::Trine::Store')) {
452             $target = $t;
453             $test->ok(1, 'Data is an RDF::Trine::Store.');
454             return 1;
455             }
456             else {
457             $test->ok(0, 'Data is not an RDF::Trine::Model or RDF::Trine::Store.');
458             return 0;
459             }
460             }
461              
462             sub pattern_ok {
463             my $message = undef;
464             $message = pop @_ if !ref $_[-1];
465             unless (defined $message and length $message) {
466             $message = "Pattern match";
467             }
468             my $test = __PACKAGE__->builder;
469             unless (blessed($target)) {
470             $test->ok(0, $message);
471             $test->diag("No target defined for pattern match. Call pattern_target test first.");
472             return 0;
473             }
474             my $pattern = (blessed($_[0]) and $_[0]->isa('RDF::Trine::Pattern'))
475             ? $_[0]
476             : RDF::Trine::Pattern->new(@_);
477             my $s = RDF::Trine::Serializer::Turtle->new();
478              
479             my $iter = $target->get_pattern($pattern);
480             if ($iter->materialize->length > 0) {
481             $test->ok(1, $message);
482             return 1;
483             }
484             # The test result is now known, return diagnostics
485             my $noreturns;
486             foreach my $triple ($pattern->triples) {
487             my @triple;
488             foreach my $node ($triple->nodes) {
489             if ($node->is_variable) {
490             push(@triple, undef);
491             } else {
492             push(@triple, $node);
493             }
494             }
495             next if ($target->count_statements(@triple));
496             $noreturns .= $triple->as_string . "\n";
497             }
498             $test->ok(0, $message);
499             if ($noreturns) {
500             $test->diag("Triples that had no results:\n$noreturns");
501             } else {
502             $test->diag('Pattern as a whole did not match');
503             }
504             return 0;
505             }
506              
507             sub pattern_fail {
508             my $message = undef;
509             $message = pop @_ if !ref $_[-1];
510             unless (defined $message and length $message) {
511             $message = "Pattern doesn't match";
512             }
513             my $test = __PACKAGE__->builder;
514             unless (blessed($target)) {
515             $test->ok(0, $message);
516             $test->diag("No target defined for pattern match. Call pattern_target test first.");
517             return 0;
518             }
519             my $pattern = (blessed($_[0]) and $_[0]->isa('RDF::Trine::Pattern'))
520             ? $_[0]
521             : RDF::Trine::Pattern->new(@_);
522             my $iter = $target->get_pattern($pattern)->materialize;
523              
524             if ($iter->length == 0) {
525             $test->ok(1, $message);
526             return 1;
527             }
528             # The test result is now known, return diagnostics
529             $test->ok(0, $message);
530             $test->diag("These triples had results:\n" . $iter->as_string);
531             return 0;
532             }
533             } # /scope for $target
534              
535              
536             =head1 NOTE
537              
538             Graph isomorphism is a complex problem, so do not attempt to run the
539             isomorphism tests on large datasets. For more information see
540             L<http://en.wikipedia.org/wiki/Graph_isomorphism_problem>.
541              
542              
543             =head1 AUTHOR
544              
545             Kjetil Kjernsmo, C<< <kjetilk at cpan.org> >>
546              
547             =head1 BUGS
548              
549             Please report any bugs using L<github|https://github.com/kjetilk/Test-RDF/issues>
550              
551              
552             =head1 SUPPORT
553              
554             You can find documentation for this module with the perldoc command.
555              
556             perldoc Test::RDF
557              
558             You may find the Perl and RDF community L<website|http://www.perlrdf.org/> useful.
559              
560             You can also look for information at:
561              
562             =over 4
563              
564             =item * AnnoCPAN: Annotated CPAN documentation
565              
566             L<http://annocpan.org/dist/Test-RDF>
567              
568             =item * CPAN Ratings
569              
570             L<http://cpanratings.perl.org/d/Test-RDF>
571              
572             =item * Search CPAN
573              
574             L<http://search.cpan.org/dist/Test-RDF/>
575              
576             =item * MetaCPAN
577              
578             L<https://metacpan.org/module/Test::RDF>
579              
580             =back
581              
582              
583             =head1 ACKNOWLEDGEMENTS
584              
585             Michael Hendricks wrote the first Test::RDF. The present module is a
586             complete rewrite from scratch using Gregory Todd William's
587             L<RDF::Trine::Graph> to do the heavy lifting.
588              
589             Toby Inkster has submitted the pattern_* functions.
590              
591             =head1 LICENSE AND COPYRIGHT
592              
593             Copyright 2010 ABC Startsiden AS.
594             Copyright 2010, 2011, 2012, 2013, 2014 Kjetil Kjernsmo.
595              
596             This program is free software; you can redistribute it and/or modify it
597             under the terms of either: the GNU General Public License as published
598             by the Free Software Foundation; or the Artistic License.
599              
600             See http://dev.perl.org/licenses/ for more information.
601              
602              
603             =cut
604              
605             1; # End of Test::RDF