#!/usr/bin/perl -w #----------------------------------------------------------------- # service_unit_tester.pl # Author: Edward Kawas , # For copyright and disclaimer see below. # # $Id: service_unit_tester.pl,v 1.2 2009/02/12 19:29:47 kawas Exp $ # # BETA # # This script goes ahead an asks a registry what services exist # and then goes ahead and extracts their signature urls. # # The signature urls are then parsed into services and any service # with a valid unit test is tested and the data stored in an XML file. # # Configurable options: # TIMEOUT - the timeout in seconds to wait for each service # CATEGORIES - the moby service categories to test # URL - the registry endpoint # URI - the registry namespace # DIRECTORY - the place to store details between jobs # # IMPORTANT NOTES: # This script doesn't fork yet, so as more and more services provide # unit tests, this script will take longer and longer to complete. # # This script works on windows/unix/linux. Other machines have not been # tested, but they may well work just fine since we don't do any black # magic. #----------------------------------------------------------------- use strict; use warnings; use MOBY::Config; use MOBY::Client::Central; use MOBY::RDF::Parsers::ServiceParser; use SOAP::Lite; use XML::LibXML; use HTTP::Request::Common qw(POST); use LWP::UserAgent; use MOBY::Async::Service; use Data::Dumper; ######-------USER CONFIGURABLE PARAMETERS-------###### # how long in seconds to wait for a service to respond my $TIMEOUT = 20; # the categories of services to unit test my @CATEGORIES = qw / moby cgi moby-async /; ######-------------------------------------------###### # the registry to query my $URL = $ENV{MOBY_SERVER} || 'http://moby.ucalgary.ca/moby/MOBY-Central.pl'; my $URI = $ENV{MOBY_URI} || 'http://moby.ucalgary.ca/MOBY/Central'; # The directory to store the job details my $CONF = MOBY::Config->new; my $DIRECTORY = $CONF->{mobycentral}->{service_tester_path} || '/tmp/'; # hashes whose key is the service provider and the value is an array of service names my %RESULTS = (); my $FILENAME = 'unitTestStats.xml'; print "saving results to $DIRECTORY/$FILENAME (if possible)\n"; # a hash of signature urls that we have found and will process my %signature_urls = (); # this is just to test if I'm going to have permissions # right now, rather than an hour from now... Arghghg! open( OUT, ">>$DIRECTORY/$FILENAME" ) || die("Cannot Open File '$DIRECTORY/$FILENAME' $!"); close OUT; # create the central client and get all service providers once my $central = MOBY::Client::Central->new( Registries => { mobycentral => { URL => $URL, URI => $URI } } ); my @providers = $central->retrieveServiceProviders(); # get the signature urls for all services that we are interested in foreach my $cat (@CATEGORIES) { foreach my $authURI (@providers) { my ( $second, $minute, $hour, @whatever ) = localtime(); $hour = "0$hour" if $hour <= 9; $second = "0$second" if $second <= 9; $minute = "0$minute" if $minute <= 9; print "Retrieving signature urls for services registered by '$authURI' as '$cat' @ $hour:$minute:$second\n"; my ( $services, $reg ) = $central->findService( Registry => "mobycentral", category => $cat, authURI => $authURI ); ( $second, $minute, $hour, @whatever ) = localtime(); $hour = "0$hour" if $hour <= 9; $second = "0$second" if $second <= 9; $minute = "0$minute" if $minute <= 9; print "Services found " . scalar @$services . "... processing @ $hour:$minute:$second \n"; my $count = 0; print "\tservice count: " . scalar(@$services) . "\n"; foreach (@$services) { # no sig url, then nothing to test next unless $_->signatureURL; # ignore test services or sig urls we have already processed next if $_->authority eq 'samples.jmoby.net' or $signature_urls{ $_->signatureURL }; # add the sig url to the hash $signature_urls{ $_->signatureURL } = 1; } ( $second, $minute, $hour, @whatever ) = localtime(); $hour = "0$hour" if $hour <= 9; $second = "0$second" if $second <= 9; $minute = "0$minute" if $minute <= 9; print "Retrieved signature urls for '$cat' services from '$authURI' ... completed @ $hour:$minute:$second \n"; } } # now get the services from the signature URL and test them my $parser = MOBY::RDF::Parsers::ServiceParser->new(); foreach my $sigurl ( keys %signature_urls ) { # get the services from the URL my $service_arrayref = $parser->getServices($sigurl); # call their unit tests foreach my $serv ( @{$service_arrayref} ) { my $tests = $serv->unitTests; next unless $tests; foreach my $test (@$tests) { if ( $test->xpath or $test->regex or $test->example_input ) { my $name = $serv->name; my $auth = $serv->authority; my $url = $serv->URL; my $cat = $serv->category; my $out = undef; my $input = $test->example_input || _empty_input(); print "\tTesting the service $name from $auth ($cat)...\n"; # add the authority if not yet in the hash $RESULTS{$auth} = () if not exists $RESULTS{$auth}; do { my $soap = SOAP::Lite->uri("http://biomoby.org/") ->proxy( $url, timeout => $TIMEOUT )->on_fault( sub { my $soap = shift; my $res = shift; # record that the service failed the test ... push @{ $RESULTS{$auth}{$name} }, { name => $name, xpath => 'false', regex => 'false', output => 'false' }; exit(0); } ); $out = $soap->$name( SOAP::Data->type( 'string' => "$input" ) ) ->result; } if $cat eq 'moby'; # test cgi services do { my $ua = LWP::UserAgent->new; $ua->timeout($TIMEOUT); my $req = POST $url, [ data => $input ]; $req = $ua->request($req); $out = $req->content if $req->is_success; do { # record that the service failed the test ... push @{ $RESULTS{$auth}{$name} }, { name => $name, xpath => 'false', regex => 'false', output => 'false' }; } unless $req->is_success; } if $cat eq 'cgi'; # test async services do { my $WSDL = $central->retrieveService($serv); my $async = MOBY::Async::Service->new(service => $WSDL); eval {$out = $async->raw_execute($input);}; # record that we have an error if we failed the test push @{ $RESULTS{$auth}{$name} }, { name => $name, xpath => 'false', regex => 'false', output => 'false' } if $@; } if $cat eq 'moby-async'; # nothing to test unless we have some output next unless $out; # perform the unit tests my %unit; $unit{name} = $name; # test regex if ( $test->regex ) { $unit{regex} = $test->test_regex($out) ? 'true' : 'false'; } # test xpath if ( $test->xpath ) { $unit{xpath} = $test->test_xpath($out) ? 'true' : 'false'; } # semantically compare XML if ( $test->expected_output ) { $unit{output} = $test->test_output_xml($out) ? 'true' : 'false'; } push @{ $RESULTS{$auth}{$name} }, \%unit; } } } } # create the XML file that contains our information my $doc = XML::LibXML::Document->new( "1.0", "UTF-8" ); my $root = $doc->createElement('Services'); $doc->setDocumentElement($root); # authorities for my $auth ( sort keys %RESULTS ) { my $element = $doc->createElement('authority'); $element->setAttribute( 'id', $auth ); my %services = %{ $RESULTS{$auth} }; next unless %services; # service names foreach my $service (sort keys %services) { my @tests = @{ $services{$service} }; next unless @tests; my $child = $doc->createElement('service'); $child->setAttribute( 'id', $service ); my $unitTests = $doc->createElement('UnitTests'); #array of unit tests foreach my $s (@tests) { next unless $s; my $test = $doc->createElement('unitTest'); # sets values to true/false $test->setAttribute( 'xpath', $s->{xpath} ) if $s->{xpath}; $test->setAttribute( 'regex', $s->{regex} ) if $s->{regex}; $test->setAttribute( 'output', $s->{output} ) if $s->{output}; # add the test to unittests $unitTests->appendChild($test); } # add the unittests to $child $child->appendChild($unitTests); # append this child $element->appendChild($child); } $root->appendChild($element); } # save our XML file open( OUT, ">$DIRECTORY/$FILENAME" ) || die("Cannot Open File $DIRECTORY/$FILENAME $!"); print OUT $doc->toString(1); close OUT; # empty input for those services that dont consume anything sub _empty_input { return <<'END_OF_XML'; END_OF_XML }