App-Sqitch
view release on metacpan or search on metacpan
t/lib/DBIEngineTest.pm view on Meta::CPAN
=item C<version_query>
version_query => 'SELECT version()',
Optional SQL query that should return a single row with a single column
containing the engine server version. Used to display a diagnostic in the
test output.
=item C<init_error>
init_error => __x(
'Sqitch schema "{schema}" already exists',
schema => $reg2,
),
Localized string representing the error raised when database initialization
fails because the registry database already exists. Required.
=item C<engine_err_regex>
engine_err_regex => qr/^ERROR: /,
Regular expression that matches an error from the database engine. Required.
=item C<test_dbh>
test_dbh => sub {
my $dbh = shift;
# Make sure the sqitch schema is the first in the search path.
is $dbh->selectcol_arrayref('SELECT current_schema')->[0],
$reg2, 'The Sqitch schema should be the current schema';
},
Optional subroutine that tests a database connection once it has been
established. Use to test that connection parameters were properly set or
executed.
=item C<add_second_format>
add_second_format => 'dateadd(second, 1, %s)',
Optional C<sprintf> format that adds one second to the named timestamp
column, to be filled in for C<%s>. Use for engines without sub-second
timestamp precision.
=item C<no_unique>
no_unique => 1,
Indicates that the engine being tested does not support unique constraints.
Required for such engines.
=item C<lock_sql>
Anonymous subroutine that returns a hash reference with SQL queries to test
for various lock states. Required only for engines that support locking on
a per-deploy basis. The keys in the returned hash reference must be:
=over
=item C<is_locked>
An SQL query that returns true if a lock is in place. Required.
=item C<try_lock>
An SQL query that returns true when it creates a lock and false when it fails
to create a lock. Required.
=item C<free_lock>
An SQL query that frees a lock. Required.
=item C<wait_time>
Time to pass to C<lock_timeout> to wait for a lock to time out. Defaults to
C<0.005>.
=item C<async_free>
Boolean indicating whether the freeing of a lock is performed asynchronously.
Required only for engines that don't free locks synchronously.
=back
=back
=cut
sub run {
my ( $self, %p ) = @_;
my $class = $p{class};
my @sqitch_params = @{ $p{sqitch_params} || [] };
my $user1_name = 'Marge Simpson';
my $user1_email = 'marge@example.com';
my $mock_sqitch = Test::MockModule->new('App::Sqitch');
# Mock script hashes using lines from the README.
my $mock_change = Test::MockModule->new('App::Sqitch::Plan::Change');
my @lines = grep { $_ } file('README.md')->slurp(
chomp => 1,
iomode => '<:raw'
);
# Each change should retain its own hash.
my $orig_deploy_hash;
$mock_change->mock(_deploy_hash => sub {
my $self = shift;
$self->$orig_deploy_hash || sha1_hex shift @lines;
});
$orig_deploy_hash = $mock_change->original('_deploy_hash');
can_ok $class, qw(
initialized
initialize
run_file
run_handle
log_deploy_change
log_fail_change
log_revert_change
t/lib/DBIEngineTest.pm view on Meta::CPAN
# Restore the reworked script.
$tmp_dir->file( $deploy_file->basename )->copy_to($deploy_file);
};
# make sure that change_id_for is still good with things.
for my $spec (
[
'beta instance of change',
{ change => $name, tag => 'beta' },
$change2->id,
],
[
'HEAD instance of change',
{ change => $name, tag => 'HEAD' },
$rev_change2->id,
],
[
'second instance of change by tag',
{ change => $name, tag => 'theta' },
$rev_change->id,
],
) {
my ( $desc, $params, $exp_id ) = @{ $spec };
is $engine->change_id_for(%{ $params }), $exp_id, "Should find id for $desc";
}
# Unmock everything and call it a day.
$mock_dbh->unmock_all;
$mock_sqitch->unmock_all;
######################################################################
# Let's make sure script_hash upgrades work.
$engine->dbh->do('UPDATE changes SET script_hash = change_id WHERE 1=1');
ok $engine->_update_script_hashes, 'Update script hashes';
# Make sure they were updated properly.
my $sth = $engine->dbh->prepare(
'SELECT change_id, script_hash FROM changes WHERE project = ?',
);
$sth->execute($plan->project);
while (my $row = $sth->fetch) {
my $change = $plan->get($row->[0]);
is $row->[1], $change->script_hash,
'Should have updated script hash for ' . $change->name;
}
# Make sure no other projects were updated.
$sth = $engine->dbh->prepare(
'SELECT change_id, script_hash FROM changes WHERE project <> ?',
);
$sth->execute($plan->project);
while (my $row = $sth->fetch) {
is $row->[1], $row->[0],
'Change ID and script hash should be ' . substr $row->[0], 0, 6;
}
######################################################################
# Test try_lock() and wait_lock().
if (my $sql = ($p{lock_sql} || sub {})->($engine)) {
ok !$engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should not be locked';
ok $engine->try_lock, 'Try lock';
ok $engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should be locked';
ok $engine->wait_lock, 'Should not have to wait for lock';
# Make a second connection to the database.
my $dbh = DBI->connect($engine->_dsn, $engine->username, $engine->password, {
PrintError => 0,
RaiseError => 0,
AutoCommit => 1,
HandleError => $engine->error_handler,
});
ok !$dbh->selectcol_arrayref($sql->{try_lock})->[0],
'Should fail to get same lock in second connection';
lives_ok { $engine->dbh->do($sql->{free_lock}) } 'Free the lock';
# Wait for the free to complete if frees are async.
if (my $wait = $sql->{async_free}) {
while ($wait) {
$wait = $engine->dbh->selectcol_arrayref($sql->{free_lock})->[0];
}
}
ok !$engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should not be locked';
ok $dbh->selectcol_arrayref($sql->{try_lock})->[0],
'Should now get the lock in second connection';
ok $engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should be locked';
ok !$engine->try_lock, 'Try lock should now return false';
# Make sure that wait_lock waits.
my $secs = $sql->{wait_time} || 0.005;
$engine->lock_timeout($secs);
my $time = [gettimeofday];
ok !$engine->wait_lock, 'Should wait and fail to get the lock';
cmp_ok tv_interval($time), '>=', $secs, 'Should have waited for the lock';
lives_ok { $dbh->do($sql->{free_lock}) } 'Free the second lock';
# Wait for the free to complete if frees are async.
if (my $wait = $sql->{async_free}) {
while ($wait) {
$wait = $engine->dbh->selectcol_arrayref($sql->{free_lock})->[0];
}
}
# Now wait lock should acquire the lock.
ok $engine->wait_lock, 'Should no longer wait for lock';
ok $engine->dbh->selectcol_arrayref($sql->{is_locked})->[0],
'Should be locked';
lives_ok { $dbh->do($sql->{free_lock}) } 'Free the lock one last time';
}
######################################################################
# All done.
done_testing;
};
}
sub dt_for_change {
my $engine = shift;
my $col = sprintf $engine->_ts2char_format, 'committed_at';
my $dtfunc = $engine->can('_dt');
$dtfunc->($engine->dbh->selectcol_arrayref(
"SELECT $col FROM changes WHERE change_id = ?",
undef, shift
)->[0]);
}
sub dt_for_tag {
my $engine = shift;
my $col = sprintf $engine->_ts2char_format, 'committed_at';
my $dtfunc = $engine->can('_dt');
$dtfunc->($engine->dbh->selectcol_arrayref(
"SELECT $col FROM tags WHERE tag_id = ?",
undef, shift
)->[0]);
}
sub all {
my $iter = shift;
my @res;
while (my $row = $iter->()) {
push @res => $row;
}
return \@res;
}
sub dt_for_event {
my ($engine, $offset) = @_;
my $col = sprintf $engine->_ts2char_format, 'committed_at';
my $dtfunc = $engine->can('_dt');
my $dbh = $engine->dbh;
return $dtfunc->($engine->dbh->selectcol_arrayref(qq{
SELECT ts FROM (
SELECT ts, rownum AS rnum FROM (
SELECT $col AS ts
FROM events
ORDER BY committed_at ASC
)
) WHERE rnum = ?
}, undef, $offset + 1)->[0]) if $dbh->{Driver}->{Name} eq 'Oracle';
return $dtfunc->($engine->dbh->selectcol_arrayref(
"SELECT FIRST 1 SKIP $offset $col FROM events ORDER BY committed_at ASC",
)->[0]) if $dbh->{Driver}->{Name} eq 'Firebird';
return $dtfunc->($engine->dbh->selectcol_arrayref(
"SELECT $col FROM events ORDER BY committed_at ASC LIMIT 1 OFFSET $offset",
)->[0]);
}
( run in 0.518 second using v1.01-cache-2.11-cpan-63c85eba8c4 )