-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit of code, tests and test files.
- Loading branch information
Showing
25 changed files
with
1,896 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
############################################################################### | ||
# | ||
# This file copyright (c) 2011 by Randy J. Ray, all rights reserved. | ||
# | ||
# See LICENSE AND COPYRIGHT in the documentation for redistribution terms. | ||
# | ||
############################################################################### | ||
# | ||
# Description: This is an umbrella of sorts, that allows users to make | ||
# use of multiple Test::AgainstSchema::* implementation | ||
# classes at once, while also still getting all the benefits | ||
# of being a subclass of Test::Builder::Module. | ||
# | ||
# Functions: import | ||
# | ||
# Libraries: Test::Builder::Module | ||
# | ||
# Global Consts: $VERSION | ||
# | ||
############################################################################### | ||
|
||
package Test::AgainstSchema; | ||
|
||
use 5.008; | ||
use strict; | ||
use warnings; | ||
use vars qw($VERSION); | ||
use subs qw(import); | ||
use base 'Test::Builder::Module'; | ||
|
||
use Carp 'croak'; | ||
|
||
$VERSION = '0.100'; | ||
$VERSION = eval $VERSION; ## no critic(ProhibitStringyEval) | ||
|
||
############################################################################### | ||
# | ||
# Sub Name: import | ||
# | ||
# Description: Facilitate the loading of any specialization classes into | ||
# the namespace of our caller. | ||
# | ||
# Arguments: NAME IN/OUT TYPE DESCRIPTION | ||
# $class in scalar Class we're called from | ||
# @rest in array Rest of the args-list | ||
# | ||
# Returns: threads through to SUPER::import() | ||
# | ||
############################################################################### | ||
sub import | ||
{ | ||
my ($class, @rest) = @_; | ||
|
||
# Yes, this means we will be force-exporting other modules' export lists | ||
# into the namespace of whoever called us. | ||
my $caller = caller 0; | ||
|
||
# This is tricky. Anything that might be an import argument to | ||
# Test::Builder::Module has to stay. Things that look like they are | ||
# specializations of Test::AgainstSchema need to be handled here. For now, | ||
# at least until the first bugs appear on RT, assume any string that leads | ||
# with an upper-case letter is meant for us. | ||
my @pass = (); | ||
for my $opt (@rest) | ||
{ | ||
# I explicitly want the [A-Z] range here, not [[:upper:]]. This is | ||
# referring to a class that will be eventually loaded, so it isn't | ||
# covered by the full Unicode range. | ||
## no critic(ProhibitEnumeratedClasses) | ||
if (ref($opt) || ($opt !~ /^[A-Z]/)) | ||
{ | ||
# Assume that this option is intended for the superclass | ||
push @pass, $opt; | ||
next; | ||
} | ||
|
||
# If the name doesn't begin with "Test::", then it is assumed to be | ||
# relative to this class. Prepend $class to it in that case. This | ||
# lets format-testing modules that aren't directly under | ||
# Test::AgainstSchema::* be covered by our umbrella. | ||
if ($opt !~ /^Test::/) | ||
{ | ||
$opt = "${class}::$opt"; | ||
} | ||
|
||
# Attempt to load it, importing what it offers into the namespace that | ||
# called us. | ||
## no critic(ProhibitStringyEval) | ||
my $ret = eval "package $caller; use $opt; 1;"; | ||
if (! $ret) | ||
{ | ||
croak "$class: Error loading format-tester $opt: $@"; | ||
} | ||
} | ||
|
||
return $class->SUPER::import(@pass); | ||
} | ||
|
||
1; | ||
|
||
__END__ | ||
=head1 NAME | ||
Test::AgainstSchema - Umbrella for test classes that target schema-based data | ||
=head1 SYNOPSIS | ||
use Test::AgainstSchema XML => tests => 5; | ||
our $schema = "structure.xsd"; | ||
for (qw(file1.xml file2.xml file3.xml reference.xml minimal.xml)) | ||
{ | ||
is_valid_against_xmlschema($schema, $_, "$_ validation"); | ||
} | ||
=head1 DESCRIPTION | ||
The B<Test::AgainstSchema> module provides unit tests of the B<Test::More> | ||
variety, that work with any TAP-driven test-harness system. The tests are | ||
oriented towards testing textual data that follows defined formats, such as XML | ||
or YAML. Rather than using regular expressions or string-equality comparisons, | ||
the classes provided with this distribution use existing validators. For | ||
example, the XML tests (see | ||
L<Test::AgainstSchema::XML|Test::AgainstSchema::XML>) use the B<XML::LibXML> | ||
module from CPAN which provides validation for XML Schema, RelaxNG and DTDs | ||
(SGML or XML style DTDs). | ||
The tests accessible through B<Test::AgainstSchema> are broken into groups | ||
called I<specializations>, each represented by a class that inherits from the | ||
B<Test::Builder::Module> class. Each of these can operate as a stand-alone test | ||
module, as their inheritance from B<Test::Builder::Module> also provides access | ||
to the basic testing functionality (C<plan>, C<skip>, etc.). This module, | ||
B<Test::AgainstSchema>, acts as an umbrella that makes it easier to load | ||
several of these groups at once. | ||
This class does not actually provide any functionality of its own, except for | ||
an C<import> method that removes arguments that identify specializations, loads | ||
them into the namespace of the caller, and passes the remaining arguments on to | ||
B<Test::Builder::Module> which registers them with the current test-session | ||
being set up. | ||
=head2 Defining Specialization Classes | ||
A specialization class can stand alone, and does not need to be loaded through | ||
the B<Test::AgainstSchema> container. It should be a subclass of | ||
B<Test::Builder::Module>, or if it isn't it should provide the full | ||
functionality of Test::Builder through its own means. | ||
If a specialization class is loaded through the B<Test::AgainstSchema> | ||
umbrella, it will not receive any arguments in the C<use> command that loads | ||
it. Any test-suite control arguments are passed on to the | ||
superclass. Therefore, the specialization class should not override the | ||
C<import> method it inherits from Test::Builder::Module unless the local | ||
version ends with a call to C<SUPER::import(@_)> (passing C<@_> ensures that | ||
the class does work when loaded directly, by passing any arguments it received | ||
to the parent class). The specialization class should instead rely only on the | ||
C<@EXPORT> list to define the functions it provides. | ||
See L<Test::AgainstSchema::XML|Test::AgainstSchema::XML> for an example of a | ||
specialization class. | ||
=head2 Loading Specializations from Test::AgainstSchema | ||
One or more specialization classes can be loaded through a single call to | ||
B<Test::AgainstSchema> by passing their names in the import-list to the load of | ||
this class. As an example: | ||
use Test::AgainstSchema XML => tests => 5; | ||
This invocation loads the B<Test::AgainstSchema::XML> specialization class, | ||
then passes the arguments C<tests> and C<5> on to the construction of the test | ||
suite. | ||
Such classes do not need to be named B<Test::AgainstSchema::I<Something>>. The | ||
B<Test::AgainstSchema> C<import> method scans the list of arguments for any | ||
value that is not a reference and whose first character is an upper-case | ||
letter. Anything else is presumed to be intended for B<Test::Builder>. If the | ||
argument string does not start with C<Test::>, then the value of C<__PACKAGE__> | ||
is prepended to it. Thus, B<Test::AgainstSchema> can itself be used as a base | ||
class in which the derived class' name is used in creating the full class | ||
name. If the argument's first six characters I<are> C<Test::>, then it is used | ||
without modification. The final name, modified or not, is then used in an | ||
C<eval> block that tries to load the module via C<use>, in the namespace that | ||
initally loaded B<Test::AgainstSchema> (or the class derived from it). | ||
To illustrate the modification of arguments to class names, consider this | ||
table: | ||
Main class name Argument text Name of class that gets loaded | ||
=============== ============= ============================== | ||
Test::AgainstSchema XML Test::AgainstSchema::XML | ||
Test::AgainstSchema XML::Simple Test::AgainstSchema::XML::Simple | ||
Test::AgainstSchema Test::Hooks Test::Hooks | ||
My::Test::AgainstSchema MyFormat My::Test::AgainstSchema::MyFormat | ||
Note that in the third case, no change is made to the argument. And in the | ||
fourth case (assuming that C<My::Test::AgainstSchema> is a subclass of | ||
B<Test::AgainstSchema>) the modified argument has the derived class prepended | ||
to it, not C<Test::AgainstSchema>. | ||
=head1 SUBROUTINES/METHODS | ||
B<Test::AgainstSchema> does not export any subroutines of its own. It only | ||
facilitates the loading of specialization classes and the export of their | ||
functionality into the namespace of the package that uses | ||
B<Test::AgainstSchema>. However, since it is a sub-class of | ||
B<Test::Builder::Module>, it is fully usable as a test framework (provided at | ||
least one specialization is loaded) and provides all the functionality | ||
described in B<Test::More> (in addition to any functions provided by | ||
specialization classes). See L<Test::More|Test::More> for documentation on | ||
those functions provided. | ||
=head1 DIAGNOSTICS | ||
See L<Test::More|Test::More> for a description of the diagnostics produced by | ||
the functions provided by it. See the specialization classes for details of | ||
their diagnostics. | ||
=head1 BUGS | ||
Please report any bugs or feature requests to | ||
C<bug-test-againstschema at rt.cpan.org>, or through the web interface at | ||
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-AgainstSchema>. I will be | ||
notified, and then you'll automatically be notified of progress on | ||
your bug as I make changes. | ||
=head1 SUPPORT | ||
=over 4 | ||
=item * RT: CPAN's request tracker | ||
L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Test-AgainstSchema> | ||
=item * AnnoCPAN: Annotated CPAN documentation | ||
L<http://annocpan.org/dist/Test-AgainstSchema> | ||
=item * CPAN Ratings | ||
L<http://cpanratings.perl.org/d/Test-AgainstSchema> | ||
=item * MetaCPAN | ||
L<https://metacpan.org/release/Test-AgainstSchema> | ||
=item * Search CPAN | ||
L<http://search.cpan.org/dist/Test-AgainstSchema> | ||
=item * Source code on GitHub | ||
L<https://github.com/rjray/test-againstschema> | ||
=back | ||
=head1 ACKNOWLEDGMENTS | ||
The original idea for this stemmed from a blog post on L<http://use.perl.org> | ||
by Curtis "Ovid" Poe (C<< <ovid at cpan.org> >>. He proferred some sample code | ||
based on recent work he'd done, that validated against a RelaxNG schema. I | ||
generalized it for all the validation types that B<XML::LibXML> offers, and | ||
expanded the idea to cover more general cases of structured, formatted text. | ||
=head1 LICENSE AND COPYRIGHT | ||
Copyright (c) 2011 Randy J. Ray, all rights reserved. | ||
This module and the code within are released under the terms of the Artistic | ||
License 2.0 | ||
(L<http://www.opensource.org/licenses/artistic-license-2.0.php>). This code | ||
may be redistributed under either the Artistic License or the GNU Lesser | ||
General Public License (LGPL) version 2.1 | ||
(L<http://www.opensource.org/licenses/lgpl-license.php>). | ||
=head1 SEE ALSO | ||
L<Test::AgainstSchema::XML|Test::AgainstSchema::XML>, L<Test::XML|Test::XML> | ||
=head1 AUTHOR | ||
Randy J. Ray, C<< <rjray at blackperl.com> >> |
Oops, something went wrong.