use strict; use warnings; use Test::More; use lib 'lib'; # test lib/Qpsmtpd (vs site_perl) use lib 't'; use Test::Qpsmtpd; use_ok('Qpsmtpd::DB::File::DBM'); my $db = Qpsmtpd::DB::File::DBM->new( name => 'testing', dir => 't/tmp' ); __new(); __get(); __mget(); __set(); __delete(); __get_keys(); __size(); __flush(); __qphome(); __candidate_dirs(); __validate_dir(); __dir(); __untie_gotcha(); done_testing(); sub __new { is( ref $db, 'Qpsmtpd::DB::File::DBM', 'Qpsmtpd::DB::File::DBM object created' ); } sub __get { $db->lock; $db->flush; $db->set( moo => 'oooo' ); is( $db->get('moo'), 'oooo', 'get() retrieves key' ); $db->unlock; } sub __mget { $db->lock; $db->flush; $db->set( key1 => 'val1' ); $db->set( key2 => 'val2' ); is( join('|',$db->mget(qw( key2 key1 ))), 'val2|val1', 'mget() retrieves multiple keys' ); $db->unlock; } sub __set { $db->lock; $db->flush; $db->set( mee => 'ow' ); is( $db->get('mee'), 'ow', 'set() stores key' ); $db->unlock; } sub __delete { $db->lock; $db->flush; $db->set( oink => 1 ); $db->set( quack => 1 ); $db->set( woof => 1 ); $db->set( moo => 1 ); is( $db->delete('quack'), 1, 'delete() return value when removing a single key' ); is( join( '|', sort $db->get_keys ), 'moo|oink|woof', 'delete() removes a single key' ); is( $db->delete(qw( moo oink )), 2, 'delete() return value when removing a single key' ); is( join( '|', sort $db->get_keys ), 'woof', 'delete() removes two keys' ); is( $db->delete('noop'), 0, 'delete() return value when removing a non-existent key' ); $db->unlock; } sub __get_keys { $db->lock; $db->flush; $db->set( asdf => 1 ); $db->set( qwerty => 1 ); is( join( '|', sort $db->get_keys ), 'asdf|qwerty', 'get_keys() lists all keys in the db' ); $db->unlock; } sub __size { $db->lock; $db->flush; $db->set( baz => 1 ); $db->set( blah => 1 ); is( $db->size, 2, 'size() shows key count for db' ); $db->unlock; } sub __flush { $db->lock; $db->flush; $db->set( bogus => 1 ); is( join( '|', $db->get_keys ), 'bogus', 'DBM db populated' ); $db->flush; is( join( '|', $db->get_keys ), '', 'flush() empties db' ); $db->unlock; } sub __qphome { is( $db->qphome, 't', 'qphome()' ); } sub __candidate_dirs { is( join('|', $db->candidate_dirs), 't/var/db|t/config', 'candidate_dirs() default ' ); is( join('|', $db->candidate_dirs('foo')), 'foo|t/var/db|t/config', 'candidate_dirs() passed args plus defaults' ); is( join('|', $db->candidate_dirs), 'foo|t/var/db|t/config', 'candidate_dirs() cached values' ); } sub __validate_dir { eval { $db->validate_dir(); }; is( $@, "Empty DB directory supplied\n", 'validate_dir(): die on no input' ); eval { $db->validate_dir(undef); }; is( $@, "Empty DB directory supplied\n", 'validate_dir(): die on undef' ); eval { $db->validate_dir(''); }; is( $@, "Empty DB directory supplied\n", 'validate_dir(): die on empty string' ); eval { $db->validate_dir('invalid'); }; is( $@, "DB directory 'invalid' does not exist\n", 'validate_dir(): die on non-existent directory' ); is( $db->validate_dir('t/tmp'), 1, 'validate_dir(): true for real directory' ); mkdir 't/tmp/wtest', 0555; eval { $db->validate_dir('t/tmp/wtest') }; is( $@, "DB directory 't/tmp/wtest' is not writeable\n", 'validate_dir(): die on non-writeable directory' ); chmod 0777, 't/tmp/wtest'; is( $db->validate_dir('t/tmp/wtest'), 1, 'validate_dir(): true for writeable directory' ); rmdir 't/tmp/wtest'; } sub __dir { my $db2 = Qpsmtpd::DB::File::DBM->new( name => 'dirtest' ); { local $SIG{__WARN__} = sub { warn @_ if $_[0] !~ /selecting database directory/; }; is( $db2->dir(), 't/config', 'default directory' ); delete $db2->{dir}; $db2->candidate_dirs('_invalid','t/Test'); is( $db2->dir, 't/Test', 'skip invalid candidate dirs' ); $db2->{dir} = '_cached'; is( $db2->dir(), '_cached', 'cached directory' ); is( $db2->dir('t/Test'), 't/Test', 'passing candidate dirs resets cache' ); delete $db2->{dir}; $db2->candidate_dirs('_invalid'); is( $db2->dir, 't/config', 'invalid candidate dirs reverts to default' ); eval { $db2->dir('_invalid'); }; is( $@, "DB directory '_invalid' does not exist\n", 'die on invalid dir' ); } { delete $db2->{dir}; my $warned; local $SIG{__WARN__} = sub { warn @_ if $_[0] !~ /selecting database directory/; $warned .= join '', @_; }; $db2->candidate_dirs('_invalid2','t/Test'); is( $db2->dir(), 't/Test', 'default directory' ); my $expected_warning = "Encountered errors while selecting database directory: DB directory '_invalid2' does not exist Selected database directory: t/Test. Data is now stored in: t/Test/dirtest.dbm It is recommended to manually specify a useable database directory and move any important data into this directory.\n"; is( $warned, $expected_warning, 'Emit warning on bad directories' ); delete $db2->{dir}; $db2->{candidate_dirs} = ['/___invalid___']; my $expected_err = "Unable to find a useable database directory! DB directory '/___invalid___' does not exist\n"; eval { $db2->dir() }; is( $@, $expected_err, 'Die on no valid directories' ); } } sub __untie_gotcha { # Regression test for 'gotcha' with untying hash that never goes away $db->lock; $db->flush; $db->set( cut => 'itout' ); $db->unlock; my $db2 = Qpsmtpd::DB::File::DBM->new( name => 'testing', dir => 't/tmp' ); $db2->lock; is( $db2->get('cut'), 'itout', 'get() in second db handle reads key set in first handle' ); # Get rid of test data $db2->flush; $db2->unlock; $db->lock; $db->flush; $db->unlock; }