File Coverage

blib/lib/Labyrinth/Test/Harness.pm
Criterion Covered Total %
statement 31 33 93.9
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 42 44 95.4


line stmt bran cond sub pod time code
1             package Labyrinth::Test::Harness;
2              
3 7     7   85928 use warnings;
  7         13  
  7         222  
4 7     7   24 use strict;
  7         10  
  7         360  
5             $|++;
6              
7             our $VERSION = '1.10';
8              
9             #----------------------------------------------------------------------------
10              
11             =head1 NAME
12              
13             Labyrinth::Test::Harness - Test Harness for Labyrinth Plugin modules
14              
15             =head1 SYNOPSIS
16              
17             my $harness = Labyrinth::Test::Harness->new( %options );
18              
19             my $res = $harness->prep('file1.sql','file2.sql');
20              
21             $res = $harness->labyrinth(@plugins);
22              
23             $res = $harness->action('Base::Admin');
24              
25             $harness->refresh( \@plugins );
26             $harness->refresh(
27             \@plugins,
28             { test1 => 1 },
29             { test2 => 2 } );
30              
31             $harness->cleanup;
32              
33             $harness->clear();
34             my $vars = $harness->vars;
35             my $params = $harness->params;
36             my $settings = $harness->settings;
37             $harness->set_vars( name => 'Test', test => 1 );
38             $harness->set_params( name => 'Test', test => 1 );
39             $harness->set_settings( name => 'Test', test => 1 );
40              
41             my $error = $harness->error;
42              
43             my $config = $harness->config;
44             my $directory = $harness->directory;
45             $harness->copy_files($source,$target);
46              
47             =head1 DESCRIPTION
48              
49             Contains all the harness code around Labyrinth, to enable plugin testing.
50              
51             =cut
52              
53             #----------------------------------------------------------------------------
54             # Libraries
55              
56 7     7   29 use base qw(Class::Accessor::Fast);
  7         13  
  7         3463  
57              
58 7     7   22288 use Config::IniFiles;
  7         249936  
  7         239  
59 7     7   56 use File::Basename;
  7         7  
  7         442  
60 7     7   3779 use File::Copy;
  7         12684  
  7         370  
61 7     7   36 use File::Path;
  7         6  
  7         346  
62 7     7   3009 use IO::File;
  7         4746  
  7         887  
63 7     7   3199 use Module::Pluggable search_path => ['Labyrinth::Plugin'];
  7         52798  
  7         40  
64 7     7   3775 use Test::Database;
  7         1576619  
  7         270  
65              
66             # Required Core
67 7     7   6273 use Labyrinth;
  0            
  0            
68             use Labyrinth::Audit;
69             use Labyrinth::DTUtils;
70             use Labyrinth::Globals qw(:all);
71             use Labyrinth::Mailer;
72             use Labyrinth::Plugins;
73             use Labyrinth::Request;
74             use Labyrinth::Session;
75             use Labyrinth::Support;
76             use Labyrinth::Writer;
77             use Labyrinth::Variables;
78              
79             #----------------------------------------------------------------------------
80             # Default Test Variables
81              
82             my $CONFIG = 't/_DBDIR/test-config.ini';
83             my $DIRECTORY = 't/_DBDIR';
84              
85             #----------------------------------------------------------------------------
86             # Class Methods
87              
88             sub new {
89             my ($class, %hash) = @_;
90             my $self = {};
91             bless $self, $class;
92              
93             $self->keep( $hash{keep} || 0 );
94             $self->config( $hash{config} || $CONFIG );
95             $self->directory( $hash{directory} || $DIRECTORY );
96              
97             return $self;
98             }
99              
100             sub DESTROY {
101             my $self = shift;
102             $self->cleanup unless($self->keep);
103             }
104              
105             #----------------------------------------------------------------------------
106             # Object Methods
107              
108             __PACKAGE__->mk_accessors(qw( config directory keep ));
109              
110             sub prep {
111             my ($self,%hash) = @_;
112             $self->{error} = '';
113              
114             my $directory = $self->directory;
115              
116             # prep test directories
117             rmtree($directory);
118             mkpath($directory) or return $self->_set_error( "cannot create test directory" );
119              
120             for my $dir ('html','cgi-bin') {
121             next unless(-d "vhost/$dir");
122             return $self->_set_error( "cannot create test files: " . $self->{error} )
123             unless ($self->copy_files("vhost/$dir","$directory/$dir"));
124             }
125              
126             mkpath("$directory/html/cache") or return $self->_set_error( "cannot create cache directory" );
127              
128             # copy additional files
129             if($hash{files}) {
130             for my $source (keys %{$hash{files}}) {
131             my $target = "$directory/$hash{files}{$source}";
132             return $self->_set_error( "cannot create test files: " . $self->{error} )
133             unless ($self->copy_file($source,$target));
134             }
135             }
136              
137             # prep database
138             my $td1 = Test::Database->handle( 'mysql' );
139             return $self->_set_error( "Unable to load a test database instance." ) unless($td1);
140              
141             return 0 unless $self->create_mysql_databases($td1,$hash{sql});
142              
143             my %opts;
144             ($opts{dsn}, $opts{dbuser}, $opts{dbpass}) = $td1->connection_info();
145             ($opts{driver}) = $opts{dsn} =~ /dbi:([^;:]+)/;
146             ($opts{database}) = $opts{dsn} =~ /database=([^;]+)/;
147             ($opts{database}) = $opts{dsn} =~ /dbname=([^;]+)/ unless($opts{database});
148             ($opts{dbhost}) = $opts{dsn} =~ /host=([^;]+)/;
149             ($opts{dbport}) = $opts{dsn} =~ /port=([^;]+)/;
150             my %db_config = map {my $v = $opts{$_}; defined($v) ? ($_ => $v) : () }
151             qw(driver database dbfile dbhost dbport dbuser dbpass);
152              
153             # prep config files
154             return $self->_set_error( "Failed to create config file" )
155             unless( $self->create_config(\%db_config,$hash{config}) );
156              
157             # prep environment variables
158              
159             my %env = (
160             SERVER_PROTOCOL => 'http',
161             SERVER_PORT => 80,
162             HTTP_HOST => 'example.com',
163             REQUEST_URI => '/',
164             PATH_INFO => undef
165             );
166              
167             for my $key (keys %env) {
168             $ENV{$key} = $hash{ENV}{$key} || $env{$key};
169             }
170              
171             return 1;
172             }
173              
174             sub cleanup {
175             my ($self) = @_;
176              
177             my $directory = $self->directory;
178              
179             # remove test directories
180             rmtree($directory);
181              
182             # remove test database
183             eval "use Test::Database";
184             return if($@);
185              
186             my $td1 = Test::Database->handle( 'mysql' );
187             return unless($td1);
188              
189             $td1->{driver}->drop_database($td1->name);
190             }
191              
192             sub labyrinth {
193             my ($self,@plugins) = @_;
194             $self->{error} = '';
195              
196             my $config = $self->config;
197              
198             eval {
199             # configure labyrinth instance
200             $self->{labyrinth} = Labyrinth->new;
201              
202             Labyrinth::Variables::init(); # initial standard variable values
203              
204             UnPublish(); # Start a fresh slate
205             LoadSettings($config); # Load All Global Settings
206              
207             DBConnect();
208              
209             load_plugins( @plugins );
210             };
211              
212             return 1 unless($@);
213             return $self->_set_error( "Failed to load Labyrinth: $@" );
214             }
215              
216             sub action {
217             my ($self,$action) = @_;
218             $self->{error} = '';
219              
220             eval {
221             # run plugin action
222             $self->{labyrinth}->action($action);
223             };
224              
225             return 1 unless($@);
226             return $self->_set_error( "Failed to run action: $action: $@" );
227             }
228              
229             sub refresh {
230             my ($self,$plugins,$vars,$params,$settings) = @_;
231              
232             $self->labyrinth(@$plugins);
233             $self->set_vars( %$vars ) if($vars);
234             $self->set_params( %$params ) if($params);
235             $self->set_settings( %$settings ) if($settings);
236             }
237              
238             sub login {
239             my ($self,$id) = @_;
240             return unless($dbi && $id);
241             my @user = $dbi->GetQuery('hash','GetUserByID',$id);
242             Labyrinth::Session::InternalLogin($user[0]) if(@user);
243             }
244              
245             sub clear {
246             my ($self) = @_;
247             %tvars = ();
248             %cgiparams = ();
249             }
250              
251             sub vars {
252             my ($self) = @_;
253             return \%tvars;
254             }
255              
256             sub set_vars {
257             my ($self,%hash) = @_;
258             for my $name (keys %hash) {
259             $tvars{$name} = $hash{$name}
260             }
261             }
262              
263             sub params {
264             my ($self) = @_;
265             return \%cgiparams;
266             }
267              
268             sub set_params {
269             my ($self,%hash) = @_;
270             for my $name (keys %hash) {
271             $cgiparams{$name} = $hash{$name}
272             }
273             }
274              
275             sub settings {
276             my ($self) = @_;
277             return \%settings;
278             }
279              
280             sub set_settings {
281             my ($self,%hash) = @_;
282             for my $name (keys %hash) {
283             $settings{$name} = $hash{$name}
284             }
285             }
286              
287             sub error {
288             my ($self) = @_;
289             return $self->{error};
290             }
291              
292             #----------------------------------------------------------------------------
293             # Internal Methods
294              
295             sub copy_files {
296             my ($self,$source_dir,$target_dir) = @_;
297              
298             return $self->_set_error( "no source directory given" ) unless($source_dir);
299             return $self->_set_error( "no target directory given" ) unless($target_dir);
300             return $self->_set_error( "failed to find source directory/file: $source_dir" ) unless(-f $source_dir || -d $source_dir);
301              
302             my @dirs = ($source_dir);
303             while(@dirs) {
304             my $dir = shift @dirs;
305              
306             my @files = glob("$dir/*");
307              
308             for my $filename (@files) {
309             my $source = $filename;
310             if(-f $source) {
311             my $target = $filename;
312             $target =~ s/^$source_dir/$target_dir/;
313             next if(-f $target);
314              
315             mkpath( dirname($target) );
316             return $self->_set_error( "failed to created directory: " . dirname($target) )
317             unless(-d dirname($target));
318              
319             copy( $source, $target );
320              
321             } elsif(-d $source) {
322             push @dirs, $source;
323              
324             } else {
325             return $self->_set_error( "failed to to find source: $source" );
326             }
327             }
328             }
329              
330             return 1;
331             }
332              
333             sub copy_file {
334             my ($self,$source,$target) = @_;
335              
336             return $self->_set_error( "no source file given" ) unless($source);
337             return $self->_set_error( "no target file given" ) unless($target);
338             return $self->_set_error( "failed to find source file: $source" ) unless(-f $source);
339              
340             my $dir = dirname($target);
341             mkpath($dir) unless(-d $dir);
342             return $self->_set_error( "failed to created directory: $dir" ) unless(-d $dir);
343              
344             copy( $source, $target );
345             return 1;
346             }
347              
348             sub create_config {
349             my ($self,$db_config,$user_config) = @_;
350              
351             my $config = $self->config;
352             my $directory = $self->directory;
353              
354             # main config
355             unlink $config if -f $config;
356              
357             my %CONFIG = (
358             PROJECT => {
359             icode => 'testsite',
360             iname => 'Test Site',
361             administrator => 'admin@example.com',
362             mailhost => '',
363             cookiename => 'session',
364             timeout => 3600,
365             autoguest => 1,
366             copyright => '2013-2014 Me',
367             lastpagereturn => 0,
368             minpasslen => 6,
369             maxpasslen => 20,
370             evalperl => 1
371             },
372             INTERNAL => {
373             phrasebook => "$directory/cgi-bin/config/phrasebook.ini",
374             logfile => "$directory/html/cache/audit.log",
375             loglevel => 4,
376             logclear => 1
377             },
378             HTTP => {
379             webpath => '',
380             cgipath => '/cgi-bin',
381             realm => 'public',
382             basedir => "$directory",
383             webdir => "$directory/html",
384             cgidir => "$directory/cgi-bin",
385             requests => "$directory/cgi-bin/config/requests"
386             },
387             CMS => {
388             htmltags => '+img',
389             maxpicwidth => 500,
390             randpicwidth => 400,
391             blank => 'images/blank.png',
392             testing => 0
393             }
394             );
395              
396             if($user_config) {
397             for my $section (keys %$user_config) {
398             for my $key (keys %{$user_config->{$section}}) {
399             $CONFIG{$section}{$key} = $user_config->{$section}{$key};
400             }
401             }
402             }
403              
404             # just in case, do this last to avoid being overwritten.
405             $CONFIG{DATABASE} = $db_config;
406              
407             my $fh = IO::File->new($config,'w+') or return 0;
408             for my $section (keys %CONFIG) {
409             print $fh "[$section]\n";
410             for my $key (keys %{$CONFIG{$section}}) {
411             print $fh "$key=$CONFIG{$section}{$key}\n";
412             }
413             print $fh "\n";
414             }
415              
416             $fh->close;
417             return 1;
418             }
419              
420             # this is primitive, but works :)
421              
422             sub create_mysql_databases {
423             my ($self,$db1,$files) = @_;
424              
425             return $self->_set_error( "no SQL files provided" ) unless($files && @$files > 0);
426              
427             my (@statements);
428             my $sql = '';
429              
430             for my $file (@$files) {
431             #print STDERR "# file=$file\n";
432             return $self->_set_error( "file '$file' cannot be read" ) unless($file && -r $file);
433              
434             my $fh = IO::File->new($file,'r') or return $self->_set_error( "file '$file' cannot be opened: $!" );
435             while(<$fh>) {
436             next if(/^--/); # ignore comment lines
437             s/;\s+--.*/;/; # remove end of line comments
438             s/\s+$//; # remove trailing spaces
439             next unless($_);
440              
441             #print STDERR "# line=$_\n";
442             $sql .= ' ' . $_;
443             #print STDERR "# sql=$sql\n";
444             #exit;
445             if($sql =~ /;$/) {
446             $sql =~ s/;$//;
447             push @statements, $sql;
448             $sql = '';
449             }
450             }
451             $fh->close;
452             }
453              
454             #print STDERR "# statements=".join("\n# ",@statements)."\n";
455             dosql($db1,\@statements);
456              
457             return 1;
458             }
459              
460             sub dosql {
461             my ($db,$sql) = @_;
462              
463             for(@$sql) {
464             #print STDERR "#SQL: [$db] $_\n";
465             eval { $db->dbh->do($_); };
466             if($@) {
467             diag $@;
468             return 1;
469             }
470             }
471              
472             return 0;
473             }
474              
475             #----------------------------------------------------------------------------
476             # Private(ish) Methods
477              
478             sub _set_error {
479             my $self = shift;
480             $self->{error} = join(' ',@_);
481             return 0; # always fail
482             }
483              
484             1;
485              
486             __END__