| 1 | package Wiki::Toolkit::TestLib; |
|---|
| 2 | |
|---|
| 3 | use strict; |
|---|
| 4 | use Carp "croak"; |
|---|
| 5 | use Wiki::Toolkit; |
|---|
| 6 | use Wiki::Toolkit::TestConfig; |
|---|
| 7 | |
|---|
| 8 | use vars qw( $VERSION @wiki_info ); |
|---|
| 9 | $VERSION = '0.03'; |
|---|
| 10 | |
|---|
| 11 | =head1 NAME |
|---|
| 12 | |
|---|
| 13 | Wiki::Toolkit::TestLib - Utilities for writing Wiki::Toolkit tests. |
|---|
| 14 | |
|---|
| 15 | =head1 DESCRIPTION |
|---|
| 16 | |
|---|
| 17 | When 'perl Makefile.PL' is run on a Wiki::Toolkit distribution, |
|---|
| 18 | information will be gathered about test databases etc that can be used |
|---|
| 19 | for running tests. Wiki::Toolkit::TestLib gives convenient access to this |
|---|
| 20 | information. |
|---|
| 21 | |
|---|
| 22 | =head1 SYNOPSIS |
|---|
| 23 | |
|---|
| 24 | use strict; |
|---|
| 25 | use Wiki::Toolkit::TestLib; |
|---|
| 26 | use Test::More; |
|---|
| 27 | |
|---|
| 28 | my $iterator = Wiki::Toolkit::TestLib->new_wiki_maker; |
|---|
| 29 | plan tests => ( $iterator->number * 6 ); |
|---|
| 30 | |
|---|
| 31 | while ( my $wiki = $iterator->new_wiki ) { |
|---|
| 32 | # put some test data in |
|---|
| 33 | # run six tests |
|---|
| 34 | } |
|---|
| 35 | |
|---|
| 36 | Each time you call C<< ->next >> on your iterator, you will get a |
|---|
| 37 | fresh blank wiki object. The iterator will iterate over all configured |
|---|
| 38 | search and storage backends. |
|---|
| 39 | |
|---|
| 40 | =cut |
|---|
| 41 | |
|---|
| 42 | my %configured = %Wiki::Toolkit::TestConfig::config; |
|---|
| 43 | |
|---|
| 44 | my %datastore_info; |
|---|
| 45 | foreach my $dbtype (qw( MySQL Pg SQLite )) { |
|---|
| 46 | if ( $configured{$dbtype}{dbname} ) { |
|---|
| 47 | my %config = %{ $configured{$dbtype} }; |
|---|
| 48 | my $store_class = "Wiki::Toolkit::Store::$dbtype"; |
|---|
| 49 | my $setup_class = "Wiki::Toolkit::Setup::$dbtype"; |
|---|
| 50 | $datastore_info{$dbtype} = { |
|---|
| 51 | class => $store_class, |
|---|
| 52 | setup_class => $setup_class, |
|---|
| 53 | params => { |
|---|
| 54 | dbname => $config{dbname}, |
|---|
| 55 | dbuser => $config{dbuser}, |
|---|
| 56 | dbpass => $config{dbpass}, |
|---|
| 57 | dbhost => $config{dbhost}, |
|---|
| 58 | }, |
|---|
| 59 | }; |
|---|
| 60 | } |
|---|
| 61 | } |
|---|
| 62 | |
|---|
| 63 | my %dbixfts_info; |
|---|
| 64 | # DBIxFTS only works with MySQL. |
|---|
| 65 | if ( $configured{dbixfts} && $configured{MySQL}{dbname} ) { |
|---|
| 66 | my %config = %{ $configured{MySQL} }; |
|---|
| 67 | $dbixfts_info{MySQL} = { |
|---|
| 68 | db_params => { |
|---|
| 69 | dbname => $config{dbname}, |
|---|
| 70 | dbuser => $config{dbuser}, |
|---|
| 71 | dbpass => $config{dbpass}, |
|---|
| 72 | dbhost => $config{dbhost}, |
|---|
| 73 | }, |
|---|
| 74 | }; |
|---|
| 75 | } |
|---|
| 76 | |
|---|
| 77 | my %sii_info; |
|---|
| 78 | # Test the MySQL SII backend, if we can. |
|---|
| 79 | if ( $configured{search_invertedindex} && $configured{MySQL}{dbname} ) { |
|---|
| 80 | my %config = %{ $configured{MySQL} }; |
|---|
| 81 | $sii_info{MySQL} = { |
|---|
| 82 | db_class => "Search::InvertedIndex::DB::Mysql", |
|---|
| 83 | db_params => { |
|---|
| 84 | -db_name => $config{dbname}, |
|---|
| 85 | -username => $config{dbuser}, |
|---|
| 86 | -password => $config{dbpass}, |
|---|
| 87 | -hostname => $config{dbhost} || "", |
|---|
| 88 | -table_name => 'siindex', |
|---|
| 89 | -lock_mode => 'EX', |
|---|
| 90 | }, |
|---|
| 91 | }; |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | # Test the Pg SII backend, if we can. It's not in the main S::II package. |
|---|
| 95 | eval { require Search::InvertedIndex::DB::Pg; }; |
|---|
| 96 | my $sii_pg = $@ ? 0 : 1; |
|---|
| 97 | if ( $configured{search_invertedindex} |
|---|
| 98 | && $configured{Pg}{dbname} |
|---|
| 99 | && $sii_pg |
|---|
| 100 | ) { |
|---|
| 101 | my %config = %{ $configured{Pg} }; |
|---|
| 102 | $sii_info{Pg} = { |
|---|
| 103 | db_class => "Search::InvertedIndex::DB::Pg", |
|---|
| 104 | db_params => { |
|---|
| 105 | -db_name => $config{dbname}, |
|---|
| 106 | -username => $config{dbuser}, |
|---|
| 107 | -password => $config{dbpass}, |
|---|
| 108 | -hostname => $config{dbhost}, |
|---|
| 109 | -table_name => 'siindex', |
|---|
| 110 | -lock_mode => 'EX', |
|---|
| 111 | }, |
|---|
| 112 | }; |
|---|
| 113 | } |
|---|
| 114 | |
|---|
| 115 | # Also test the default DB_File backend, if we have S::II installed at all. |
|---|
| 116 | if ( $configured{search_invertedindex} ) { |
|---|
| 117 | $sii_info{DB_File} = { |
|---|
| 118 | db_class => "Search::InvertedIndex::DB::DB_File_SplitHash", |
|---|
| 119 | db_params => { |
|---|
| 120 | -map_name => 't/sii-db-file-test.db', |
|---|
| 121 | -lock_mode => 'EX', |
|---|
| 122 | }, |
|---|
| 123 | }; |
|---|
| 124 | } |
|---|
| 125 | |
|---|
| 126 | my $plucene_path; |
|---|
| 127 | # Test with Plucene if possible. |
|---|
| 128 | if ( $configured{plucene} ) { |
|---|
| 129 | $plucene_path = "t/plucene"; |
|---|
| 130 | } |
|---|
| 131 | |
|---|
| 132 | # @wiki_info describes which searches work with which stores. |
|---|
| 133 | |
|---|
| 134 | # Database-specific searchers. |
|---|
| 135 | push @wiki_info, { datastore_info => $datastore_info{MySQL}, |
|---|
| 136 | dbixfts_info => $dbixfts_info{MySQL} } |
|---|
| 137 | if ( $datastore_info{MySQL} and $dbixfts_info{MySQL} ); |
|---|
| 138 | push @wiki_info, { datastore_info => $datastore_info{MySQL}, |
|---|
| 139 | sii_info => $sii_info{MySQL} } |
|---|
| 140 | if ( $datastore_info{MySQL} and $sii_info{MySQL} ); |
|---|
| 141 | push @wiki_info, { datastore_info => $datastore_info{Pg}, |
|---|
| 142 | sii_info => $sii_info{Pg} } |
|---|
| 143 | if ( $datastore_info{Pg} and $sii_info{Pg} ); |
|---|
| 144 | |
|---|
| 145 | # All stores are compatible with the default S::II search, and with Plucene, |
|---|
| 146 | # and with no search. |
|---|
| 147 | foreach my $dbtype ( qw( MySQL Pg SQLite ) ) { |
|---|
| 148 | push @wiki_info, { datastore_info => $datastore_info{$dbtype}, |
|---|
| 149 | sii_info => $sii_info{DB_File} } |
|---|
| 150 | if ( $datastore_info{$dbtype} and $sii_info{DB_File} ); |
|---|
| 151 | push @wiki_info, { datastore_info => $datastore_info{$dbtype}, |
|---|
| 152 | plucene_path => $plucene_path } |
|---|
| 153 | if ( $datastore_info{$dbtype} and $plucene_path ); |
|---|
| 154 | push @wiki_info, { datastore_info => $datastore_info{$dbtype} } |
|---|
| 155 | if $datastore_info{$dbtype}; |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | =head1 METHODS |
|---|
| 159 | |
|---|
| 160 | =over 4 |
|---|
| 161 | |
|---|
| 162 | =item B<new_wiki_maker> |
|---|
| 163 | |
|---|
| 164 | my $iterator = Wiki::Toolkit::TestLib->new_wiki_maker; |
|---|
| 165 | |
|---|
| 166 | =cut |
|---|
| 167 | |
|---|
| 168 | sub new_wiki_maker { |
|---|
| 169 | my $class = shift; |
|---|
| 170 | my $count = 0; |
|---|
| 171 | my $iterator = \$count; |
|---|
| 172 | bless $iterator, $class; |
|---|
| 173 | return $iterator; |
|---|
| 174 | } |
|---|
| 175 | |
|---|
| 176 | =item B<number> |
|---|
| 177 | |
|---|
| 178 | use Test::More; |
|---|
| 179 | my $iterator = Wiki::Toolkit::TestLib->new_wiki_maker; |
|---|
| 180 | plan tests => ( $iterator->number * 6 ); |
|---|
| 181 | |
|---|
| 182 | Returns the number of new wikis that your iterator will be able to give you. |
|---|
| 183 | |
|---|
| 184 | =cut |
|---|
| 185 | |
|---|
| 186 | sub number { |
|---|
| 187 | return scalar @wiki_info; |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | =item B<new_wiki> |
|---|
| 191 | |
|---|
| 192 | my $wiki = $iterator->new_wiki; |
|---|
| 193 | |
|---|
| 194 | Returns a fresh blank wiki object, or false if you've used up all the |
|---|
| 195 | configured search and storage backends. |
|---|
| 196 | |
|---|
| 197 | =cut |
|---|
| 198 | |
|---|
| 199 | sub new_wiki { |
|---|
| 200 | my $self = shift; |
|---|
| 201 | return undef if $$self > $#wiki_info; |
|---|
| 202 | my $details = $wiki_info[$$self]; |
|---|
| 203 | my %wiki_config; |
|---|
| 204 | |
|---|
| 205 | # Set up and clear datastore. |
|---|
| 206 | my %datastore_info = %{ $details->{datastore_info } }; |
|---|
| 207 | my $setup_class = $datastore_info{setup_class}; |
|---|
| 208 | eval "require $setup_class"; |
|---|
| 209 | { |
|---|
| 210 | no strict "refs"; |
|---|
| 211 | &{"$setup_class\:\:cleardb"}( $datastore_info{params} ); |
|---|
| 212 | &{"$setup_class\:\:setup"}( $datastore_info{params} ); |
|---|
| 213 | } |
|---|
| 214 | my $class = $datastore_info{class}; |
|---|
| 215 | eval "require $class"; |
|---|
| 216 | $wiki_config{store} = $class->new( %{ $datastore_info{params} } ); |
|---|
| 217 | |
|---|
| 218 | # Set up and clear search object (if required). |
|---|
| 219 | if ( $details->{dbixfts_info} ) { |
|---|
| 220 | my %fts_info = %{ $details->{dbixfts_info} }; |
|---|
| 221 | require Wiki::Toolkit::Store::MySQL; |
|---|
| 222 | my %dbconfig = %{ $fts_info{db_params} }; |
|---|
| 223 | my $dsn = Wiki::Toolkit::Store::MySQL->_dsn( $dbconfig{dbname}, |
|---|
| 224 | $dbconfig{dbhost} ); |
|---|
| 225 | my $dbh = DBI->connect( $dsn, $dbconfig{dbuser}, $dbconfig{dbpass}, |
|---|
| 226 | { PrintError => 0, RaiseError => 1, AutoCommit => 1 } ) |
|---|
| 227 | or croak "Can't connect to $dbconfig{dbname} using $dsn: " . DBI->errstr; |
|---|
| 228 | require Wiki::Toolkit::Setup::DBIxFTSMySQL; |
|---|
| 229 | Wiki::Toolkit::Setup::DBIxFTSMySQL::setup( |
|---|
| 230 | @dbconfig{ qw( dbname dbuser dbpass dbhost ) } |
|---|
| 231 | ); |
|---|
| 232 | require Wiki::Toolkit::Search::DBIxFTS; |
|---|
| 233 | $wiki_config{search} = Wiki::Toolkit::Search::DBIxFTS->new( dbh => $dbh ); |
|---|
| 234 | } elsif ( $details->{sii_info} ) { |
|---|
| 235 | my %sii_info = %{ $details->{sii_info} }; |
|---|
| 236 | my $db_class = $sii_info{db_class}; |
|---|
| 237 | eval "use $db_class"; |
|---|
| 238 | my %db_params = %{ $sii_info{db_params} }; |
|---|
| 239 | my $indexdb = $db_class->new( %db_params ); |
|---|
| 240 | require Wiki::Toolkit::Setup::SII; |
|---|
| 241 | Wiki::Toolkit::Setup::SII::setup( indexdb => $indexdb ); |
|---|
| 242 | $wiki_config{search} = Wiki::Toolkit::Search::SII->new(indexdb =>$indexdb); |
|---|
| 243 | } elsif ( $details->{plucene_path} ) { |
|---|
| 244 | require Wiki::Toolkit::Search::Plucene; |
|---|
| 245 | my $dir = $details->{plucene_path}; |
|---|
| 246 | unlink <$dir/*>; # don't die if false since there may be no files |
|---|
| 247 | if ( -d $dir ) { |
|---|
| 248 | rmdir $dir or die $!; |
|---|
| 249 | } |
|---|
| 250 | mkdir $dir or die $!; |
|---|
| 251 | $wiki_config{search} = Wiki::Toolkit::Search::Plucene->new( path => $dir ); |
|---|
| 252 | } |
|---|
| 253 | |
|---|
| 254 | # Make a wiki. |
|---|
| 255 | my $wiki = Wiki::Toolkit->new( %wiki_config ); |
|---|
| 256 | $$self++; |
|---|
| 257 | return $wiki; |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | =back |
|---|
| 261 | |
|---|
| 262 | =head1 SEE ALSO |
|---|
| 263 | |
|---|
| 264 | L<Wiki::Toolkit> |
|---|
| 265 | |
|---|
| 266 | =head1 AUTHOR |
|---|
| 267 | |
|---|
| 268 | Kake Pugh (kake@earth.li). |
|---|
| 269 | |
|---|
| 270 | =head1 COPYRIGHT |
|---|
| 271 | |
|---|
| 272 | Copyright (C) 2003-2004 Kake Pugh. All Rights Reserved. |
|---|
| 273 | |
|---|
| 274 | This module is free software; you can redistribute it and/or modify it |
|---|
| 275 | under the same terms as Perl itself. |
|---|
| 276 | |
|---|
| 277 | =head1 CAVEATS |
|---|
| 278 | |
|---|
| 279 | If you have the L<Search::InvertedIndex> backend configured (see |
|---|
| 280 | L<Wiki::Toolkit::Search::SII>) then your tests will raise warnings like |
|---|
| 281 | |
|---|
| 282 | (in cleanup) Search::InvertedIndex::DB::Mysql::lock() - |
|---|
| 283 | testdb is not open. Can't lock. |
|---|
| 284 | at /usr/local/share/perl/5.6.1/Search/InvertedIndex.pm line 1348 |
|---|
| 285 | |
|---|
| 286 | or |
|---|
| 287 | |
|---|
| 288 | (in cleanup) Can't call method "sync" on an undefined value |
|---|
| 289 | at /usr/local/share/perl/5.6.1/Tie/DB_File/SplitHash.pm line 331 |
|---|
| 290 | during global destruction. |
|---|
| 291 | |
|---|
| 292 | in unexpected places. I don't know whether this is a bug in me or in |
|---|
| 293 | L<Search::InvertedIndex>. |
|---|
| 294 | |
|---|
| 295 | =cut |
|---|
| 296 | |
|---|
| 297 | 1; |
|---|