#!/usr/bin/perl -w # # Run a test # use strict; use DBI; use Getopt::Long qw(:config permute); # allow mixed args. # Options variables my $debug = 0; my $dsn = "davical"; my $dbuser = ""; my $dbpass = ""; my $webhost = 'mycaldav'; my $althost = 'myempty'; my $testdef; my $suite; my $case; my $helpmeplease = 0; my $dbadir = $0; $dbadir =~ s{/[^/]*$}{}; my $patchdir = $dbadir . "/patches"; GetOptions ('debug!' => \$debug, 'dsn=s' => \$dsn, 'dbuser=s' => \$dbuser, 'dbpass=s' => \$dbpass, 'webhost=s' => \$webhost, 'althost=s' => \$althost, 'test=s' => \$testdef, 'suite=s' => \$suite, 'case=s' => \$case, 'help' => \$helpmeplease ); usage() if ( $helpmeplease || ((!defined($suite) || !defined($case)) && !defined($testdef))); my $dbh; my @arguments = ( "--basic", "--proxy", "", "--insecure", "--raw" ); push @arguments, "--silent" unless ( $debug ); push @arguments, "--verbose" if ( $debug ); my $url; my $script; my @scripts = ( ); my $is_head_request = 0; my @auth = ( "--user", "user1:user1" ); if ( !defined($testdef) ) { $testdef = "tests/$suite/$case.test"; } my $datafile = $testdef; $datafile =~ s{\.test$}{}; push @arguments, "--header", 'X-DAViCal-Testcase: '.$datafile; $datafile .= '.data'; my $state = ""; my $data_binary; my $sql_variable = ""; my $sql_statement = ""; my $sql_values = {}; my $queries = (); my $replacements = (); open( TEST, '<', $testdef ) or die "Can't open '$testdef'"; while( ) { my $line = $_; # Do any variable replcements we have so far foreach my $variable ( keys %{$sql_values} ) { my $value = $sql_values->{$variable}; $line =~ s/##$variable##/$value/g; } if ( $state ne "" ) { if ( /^END$state$/ ) { if ( $state eq "SQL" ) { get_sql_value( $sql_variable, $sql_values, $sql_statement ); } elsif ( $state eq "DOSQL" ) { do_sql( $sql_statement ); } elsif ( $state eq "QUERY" ) { push @$queries, $sql_statement; } $state = ""; } elsif ( $state eq "DATA" ) { $data_binary .= $line; } elsif ( $state eq "SQL" || $state eq "QUERY" || $state eq "DOSQL" ) { $sql_statement .= $line; } next; } /^\s*(#|$)/ && next; $line =~ /^\s*HEAD\s*(#|$|=)/ && do { push @arguments, "--include"; }; $line =~ /^\s*VERBOSE\s*(#|$|=)/ && do { push @arguments, "--verbose"; }; $line =~ /^\s*NOAUTH\s*(#|$|=)/ && do { @auth = (); }; $line =~ /^\s*DIGEST\s*(#|$|=)/ && do { push @arguments, "--digest"; @auth = ( "--user", $1 ); }; $line =~ /^\s*AUTH\s*=\s*(\S.*)$/ && do { @auth = ( "--user", $1 ); }; $line =~ /^\s*DATA\s*=\s*(\S.*)$/ && do { my $basename = $1; if ( defined($suite) ) { if ( -e "tests/$suite/$basename.data" ) { $datafile="tests/$suite/$basename.data"; } elsif ( -e "tests/$suite/$basename" ) { $datafile="tests/$suite/$basename"; } } elsif ( -e "$basename.data" ) { $datafile="$basename.data"; } elsif ( -e $basename ) { $datafile=$basename; } else { die "Can't find DATA file $basename or $basename.data"; } }; $line =~ /^BEGINDATA\s*$/ && do { $data_binary = ""; $state = "DATA"; }; $line =~ /^GETSQL\s*=\s*(\S.*)$/ && do { $sql_variable = $1; $sql_statement = ""; $state = "SQL"; }; $line =~ /^DOSQL\s*$/ && do { $sql_statement = ""; $state = "DOSQL"; }; $line =~ /^REPLACE\s*=\s*(\S)(.*)$/ && do { my $separator = $1; $2 =~ /^([^$separator]*)$separator([^$separator]*)$separator$/ && do { push @$replacements, { 'pattern' => $1, 'replacement' => $2 }; }; }; $line =~ /^QUERY\s*$/ && do { $sql_statement = ""; $state = "QUERY"; }; $line =~ /^\s*TYPE\s*=\s*(\S.*)$/ && do { if ( $1 eq "HEAD" ) { $is_head_request = 1; } else { push @arguments, "--request", $1; } }; $line =~ /^\s*HEADER\s*=\s*(\S.*)$/ && do { my $arg = $1; $arg =~ s{regression.host}{$webhost}; $arg =~ s{alternate.host}{$althost}; push @arguments, "--header", $arg; }; $line =~ /^\s*URL\s*=\s*(\S.*)$/ && do { $url=$1; $url =~ s{regression.host}{$webhost}; $url =~ s{alternate.host}{$althost}; }; $line =~ /^\s*SCRIPT\s*=\s*(\S.*)$/ && do { $script=$1; $script =~ s{regression.host}{$webhost}; $script =~ s{alternate.host}{$althost}; push @scripts, $script; }; } if ( !defined($url) && !defined($script) ) { print < ) { my $line = $_; foreach my $replacement ( @$replacements ) { $line =~ s/$replacement->{'pattern'}/$replacement->{'replacement'}/; } print $line; } } if ( defined($script) ) { foreach $script ( @scripts ) { open RESULTS, "-|", $script; while( ) { my $line = $_; foreach my $replacement ( @$replacements ) { $line =~ s/$replacement->{'pattern'}/$replacement->{'replacement'}/; } print $line; } } } if ( defined($queries) && @{$queries} ) { opendb() unless defined($dbh); print "\n"; print STDERR "Processing special queries\n" if ( $debug ); foreach $sql_statement ( @$queries ) { # run SQL statement and dump results, into array of hashes my $results = $dbh->selectall_arrayref($sql_statement, { Slice => {} } ); if ( $dbh->err ) { print $dbh->errstr, "\n"; next; } foreach my $row ( @$results ) { print "Query result ================================================\n" if ( $debug ); my $sep = ""; foreach my $name ( sort keys %$row ) { my $value = $row->{$name}; $value = 'NULL' unless ( defined($value) ); printf("%17.17s: >%s<\n", $name, $value ); } print "\n"; } } } exit(0); =item do_sql( $sql_statement ) Queries the database using the specified statement and ignores the result. =cut sub do_sql { my $sql = shift; opendb() unless defined($dbh); $dbh->do($sql); if ( $dbh->err ) { print $dbh->errstr, "\n"; return; } print "SQL executed successfully.\n"; print $sql, "\n"; } =item get_sql_value( $sql_variable, $sql_values, $sql_statement ) Queries the database using the specified statement and puts the first column of the first row returned into the hash referenced $sql_values->{$sql_variable} for replacement later in the parsing process. =cut sub get_sql_value { my $varname = shift; my $values = shift; my $sql = shift; opendb() unless defined($dbh); my $results = $dbh->selectall_arrayref($sql); if ( $dbh->err ) { print $dbh->errstr, "\n"; return; } print STDERR "RESULT for $varname is ", $results->[0][0], "\n" if ( $debug ); $values->{$varname} = (defined($results->[0][0]) ? $results->[0][0] : ""); } =item opendb() Opens the database connection to the global $dbh handle. Note that the standard PostgreSQL environment variables will also work with DBD::Pg. =cut sub opendb { $dsn = "dbi:Pg:dbname=$dsn"; $dbh = DBI->connect($dsn, $dbuser, $dbpass, { AutoCommit => 1 } ) or die "Can't connect to database $dsn"; $dbh->do("SET TIMEZONE TO 'Pacific/Auckland'"); } sub usage { print < --case ] | [--test ] This program will read the file 'tests//.test and follow the instructions there. The following options are available for controlling the database, for those test cases which might require it: --dsn [;port=NNNN][;host=example.com] --dbuser --dbpass The test instructions will include lines defining the test like: ================================================= # This is an example URL=http://mycaldav/caldav.php/andrew/ HEADER=Depth: 0 HEADER=Content-type: text/xml TYPE=PROPFIND HEAD DATA=OTHERTEST # This will let you use ##somename## for this value after this GETSQL=somename SELECT column FROM table WHERE criteria ENDSQL # The data can be included in line BEGINDATA ... data content ... ENDDATA # The result could be some SQL output QUERY SELECT something, or, other FROM table ... ENDQUERY REPLACE=/pattern/replacement/options ================================================= URL The URL to request from. HEADER An additional header for the request TYPE The type of request (e.g. GET/PUT/POST/REPORT/...) HEAD Whether to include the headers in the recorded response VERBOSE Whether to provide the full request / response headers. DATA The name of a different test in this suite to use data from. REPLACE A perl regex replacement to post-process the result through. Additionally, if a file 'tests//.data' exists the contents of that file will be sent in the body of the request. EOERROR exit(1); }