t 000755 000765 000024 0 13425063133 13345 5 ustar 00david staff 000000 000000 App-Sqitch-0.9999 x.t 100644 000765 000024 4424 13425063133 14145 0 ustar 00david staff 000000 000000 App-Sqitch-0.9999/t #!/usr/bin/perl -w use strict; use Test::More; use Test::Exception; use Try::Tiny; use Path::Class; use lib 't/lib'; use TestConfig; my $CLASS; BEGIN { $CLASS = 'App::Sqitch::X'; require_ok $CLASS or die; $CLASS->import(':all'); } isa_ok my $x = $CLASS->new(ident => 'test', message => 'Die'), $CLASS, 'X object'; for my $role(qw( Throwable StackTrace::Auto )) { ok $x->does($role), "X object does $role"; } # Make sure default ident works. ok $x = $CLASS->new(message => 'whatever'), 'Create X without ident'; is $x->ident, 'DEV', 'Default ident should be "DEV"'; throws_ok { hurl basic => 'OMFG!' } $CLASS; isa_ok $x = $@, $CLASS, 'Thrown object'; is $x->ident, 'basic', 'Ident should be "basic"'; is $x->message, 'OMFG!', 'The message should have been passed'; ok $x->stack_trace->frames, 'It should have a stack trace'; is $x->exitval, 2, 'Exit val should be 2'; is +($x->stack_trace->frames)[0]->filename, file(qw(t x.t)), 'The trace should start in this file'; # NB: Don't use `local $@`, as it does not work on Perls < 5.14. throws_ok { $@ = 'Yo dawg'; hurl 'OMFG!' } $CLASS; isa_ok $x = $@, $CLASS, 'Thrown object'; is $x->ident, 'DEV', 'Ident should be "DEV"'; is $x->message, 'OMFG!', 'The message should have been passed'; is $x->exitval, 2, 'Exit val should again be 2'; is $x->previous_exception, 'Yo dawg', 'Previous exception should have been passed'; throws_ok { hurl {ident => 'blah', message => 'OMFG!', exitval => 1} } $CLASS; isa_ok $x = $@, $CLASS, 'Thrown object'; is $x->message, 'OMFG!', 'The params should have been passed'; is $x->exitval, 1, 'Exit val should be 1'; is $x->as_string, join("\n", grep { defined } $x->message, $x->previous_exception, $x->stack_trace ), 'Stringification should work'; is $x->as_string, "$x", 'Stringification should work'; # Do some actual exception handling. try { hurl io => 'Cannot open file'; } catch { return fail "Not a Sqitch::X: $_" unless eval { $_->isa('App::Sqitch::X') }; is $_->ident, 'io', 'Should be an "io" exception'; }; # Make sure we can goto hurl. try { @_ = (io => 'Cannot open file'); goto &hurl; } catch { return fail "Not a Sqitch::X: $_" unless eval { $_->isa('App::Sqitch::X') }; is $_->ident, 'io', 'Should catch error called via &goto'; }; done_testing; pg.t 100644 000765 000024 25351 13425063133 14326 0 ustar 00david staff 000000 000000 App-Sqitch-0.9999/t #!/usr/bin/perl -w use strict; use warnings; use 5.010; use Test::More 0.94; use Test::MockModule; use Test::Exception; use Locale::TextDomain qw(App-Sqitch); use Capture::Tiny 0.12 qw(:all); use Try::Tiny; use App::Sqitch; use App::Sqitch::Target; use App::Sqitch::Plan; use lib 't/lib'; use DBIEngineTest; use TestConfig; my $CLASS; BEGIN { $CLASS = 'App::Sqitch::Engine::pg'; require_ok $CLASS or die; delete $ENV{PGPASSWORD}; } is_deeply [$CLASS->config_vars], [ target => 'any', registry => 'any', client => 'any', ], 'config_vars should return three vars'; my $uri = URI::db->new('db:pg:'); my $config = TestConfig->new('core.engine' => 'pg'); my $sqitch = App::Sqitch->new(config => $config); my $target = App::Sqitch::Target->new( sqitch => $sqitch, uri => $uri, ); isa_ok my $pg = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; is $pg->key, 'pg', 'Key should be "pg"'; is $pg->name, 'PostgreSQL', 'Name should be "PostgreSQL"'; my $client = 'psql' . (App::Sqitch::ISWIN ? '.exe' : ''); is $pg->client, $client, 'client should default to psqle'; is $pg->registry, 'sqitch', 'registry default should be "sqitch"'; is $pg->uri, $uri, 'DB URI should be "db:pg:"'; my $dest_uri = $uri->clone; $dest_uri->dbname($ENV{PGDATABASE} || $ENV{PGUSER} || $sqitch->sysuser); is $pg->destination, $dest_uri->as_string, 'Destination should fall back on environment variables'; is $pg->registry_destination, $pg->destination, 'Registry destination should be the same as destination'; my @std_opts = ( '--quiet', '--no-psqlrc', '--no-align', '--tuples-only', '--set' => 'ON_ERROR_STOP=1', '--set' => 'registry=sqitch', ); my $sysuser = $sqitch->sysuser; is_deeply [$pg->psql], [$client, @std_opts], 'psql command should be conninfo, and std opts-only'; isa_ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; ok $pg->set_variables(foo => 'baz', whu => 'hi there', yo => 'stellar'), 'Set some variables'; is_deeply [$pg->psql], [ $client, '--set' => 'foo=baz', '--set' => 'whu=hi there', '--set' => 'yo=stellar', @std_opts, ], 'Variables should be passed to psql via --set'; ############################################################################## # Test other configs for the target. ENV: { # Make sure we override system-set vars. local $ENV{PGDATABASE}; for my $env (qw(PGDATABASE PGUSER PGPASSWORD)) { my $pg = $CLASS->new(sqitch => $sqitch, target => $target); local $ENV{$env} = "\$ENV=whatever"; is $pg->target->uri, "db:pg:", "Target should not read \$$env"; is $pg->registry_destination, $pg->destination, 'Registry target should be the same as destination'; } my $mocker = Test::MockModule->new('App::Sqitch'); $mocker->mock(sysuser => 'sysuser=whatever'); my $pg = $CLASS->new(sqitch => $sqitch, target => $target); is $pg->target->uri, 'db:pg:', 'Target should not fall back on sysuser'; is $pg->registry_destination, $pg->destination, 'Registry target should be the same as destination'; $ENV{PGDATABASE} = 'mydb'; $pg = $CLASS->new(sqitch => $sqitch, username => 'hi', target => $target); is $pg->target->uri, 'db:pg:', 'Target should be the default'; is $pg->registry_destination, $pg->destination, 'Registry target should be the same as destination'; } ############################################################################## # Make sure config settings override defaults. $config->update( 'engine.pg.client' => '/path/to/psql', 'engine.pg.target' => 'db:pg://localhost/try?sslmode=disable&connect_timeout=5', 'engine.pg.registry' => 'meta', ); $std_opts[-1] = 'registry=meta'; $target = App::Sqitch::Target->new( sqitch => $sqitch ); ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), 'Create another pg'; is $pg->client, '/path/to/psql', 'client should be as configured'; is $pg->uri->as_string, 'db:pg://localhost/try?sslmode=disable&connect_timeout=5', 'uri should be as configured'; is $pg->registry, 'meta', 'registry should be as configured'; is_deeply [$pg->psql], [ '/path/to/psql', '--dbname', "dbname=try host=localhost connect_timeout=5 sslmode=disable", @std_opts], 'psql command should be configured from URI config'; ############################################################################## # Test _run(), _capture(), and _spool(). can_ok $pg, qw(_run _capture _spool); my $mock_sqitch = Test::MockModule->new('App::Sqitch'); my (@run, $exp_pass); $mock_sqitch->mock(run => sub { local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @run = @_; if (defined $exp_pass) { is $ENV{PGPASSWORD}, $exp_pass, qq{PGPASSWORD should be "$exp_pass"}; } else { ok !exists $ENV{PGPASSWORD}, 'PGPASSWORD should not exist'; } }); my @capture; $mock_sqitch->mock(capture => sub { local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @capture = @_; if (defined $exp_pass) { is $ENV{PGPASSWORD}, $exp_pass, qq{PGPASSWORD should be "$exp_pass"}; } else { ok !exists $ENV{PGPASSWORD}, 'PGPASSWORD should not exist'; } }); my @spool; $mock_sqitch->mock(spool => sub { local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @spool = @_; if (defined $exp_pass) { is $ENV{PGPASSWORD}, $exp_pass, qq{PGPASSWORD should be "$exp_pass"}; } else { ok !exists $ENV{PGPASSWORD}, 'PGPASSWORD should not exist'; } }); $target->uri->password('s3cr3t'); $exp_pass = 's3cr3t'; ok $pg->_run(qw(foo bar baz)), 'Call _run'; is_deeply \@run, [$pg->psql, qw(foo bar baz)], 'Command should be passed to run()'; ok $pg->_spool('FH'), 'Call _spool'; is_deeply \@spool, ['FH', $pg->psql], 'Command should be passed to spool()'; ok $pg->_capture(qw(foo bar baz)), 'Call _capture'; is_deeply \@capture, [$pg->psql, qw(foo bar baz)], 'Command should be passed to capture()'; # Without password. $target = App::Sqitch::Target->new( sqitch => $sqitch ); ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a pg with sqitch with no pw'; $exp_pass = undef; ok $pg->_run(qw(foo bar baz)), 'Call _run again'; is_deeply \@run, [$pg->psql, qw(foo bar baz)], 'Command should be passed to run() again'; ok $pg->_spool('FH'), 'Call _spool again'; is_deeply \@spool, ['FH', $pg->psql], 'Command should be passed to spool() again'; ok $pg->_capture(qw(foo bar baz)), 'Call _capture again'; is_deeply \@capture, [$pg->psql, qw(foo bar baz)], 'Command should be passed to capture() again'; ############################################################################## # Test file and handle running. ok $pg->run_file('foo/bar.sql'), 'Run foo/bar.sql'; is_deeply \@run, [$pg->psql, '--file', 'foo/bar.sql'], 'File should be passed to run()'; ok $pg->run_handle('FH'), 'Spool a "file handle"'; is_deeply \@spool, ['FH', $pg->psql], 'Handle should be passed to spool()'; # Verify should go to capture unless verosity is > 1. ok $pg->run_verify('foo/bar.sql'), 'Verify foo/bar.sql'; is_deeply \@capture, [$pg->psql, '--file', 'foo/bar.sql'], 'Verify file should be passed to capture()'; $mock_sqitch->mock(verbosity => 2); ok $pg->run_verify('foo/bar.sql'), 'Verify foo/bar.sql again'; is_deeply \@run, [$pg->psql, '--file', 'foo/bar.sql'], 'Verifile file should be passed to run() for high verbosity'; $mock_sqitch->unmock_all; ############################################################################## # Test DateTime formatting stuff. ok my $ts2char = $CLASS->can('_ts2char_format'), "$CLASS->can('_ts2char_format')"; is sprintf($ts2char->(), 'foo'), q{to_char(foo AT TIME ZONE 'UTC', '"year":YYYY:"month":MM:"day":DD:"hour":HH24:"minute":MI:"second":SS:"time_zone":"UTC"')}, '_ts2char_format should work'; ok my $dtfunc = $CLASS->can('_dt'), "$CLASS->can('_dt')"; isa_ok my $dt = $dtfunc->( 'year:2012:month:07:day:05:hour:15:minute:07:second:01:time_zone:UTC' ), 'App::Sqitch::DateTime', 'Return value of _dt()'; is $dt->year, 2012, 'DateTime year should be set'; is $dt->month, 7, 'DateTime month should be set'; is $dt->day, 5, 'DateTime day should be set'; is $dt->hour, 15, 'DateTime hour should be set'; is $dt->minute, 7, 'DateTime minute should be set'; is $dt->second, 1, 'DateTime second should be set'; is $dt->time_zone->name, 'UTC', 'DateTime TZ should be set'; ############################################################################## # Test _psql_major_version. for my $spec ( ['11beta3', 11], ['11.3', 11], ['10', 10], ['9.6.3', 9], ['8.4.2', 8], ['9.0.19', 9], ) { $mock_sqitch->mock(probe => "psql (PostgreSQL) $spec->[0]"); is $pg->_psql_major_version, $spec->[1], "Should find major version $spec->[1] in $spec->[0]"; } $mock_sqitch->unmock('probe'); ############################################################################## # Can we do live tests? $config->replace('core.engine' => 'pg'); $sqitch = App::Sqitch->new(config => $config); $target = App::Sqitch::Target->new( sqitch => $sqitch ); $pg = $CLASS->new(sqitch => $sqitch, target => $target); my $dbh; my $db = '__sqitchtest__' . $$; END { return unless $dbh; $dbh->{Driver}->visit_child_handles(sub { my $h = shift; $h->disconnect if $h->{Type} eq 'db' && $h->{Active} && $h ne $dbh; }); $dbh->do("DROP DATABASE $db") if $dbh->{Active}; } my $pguser = $ENV{PGUSER} || 'postgres'; my $err = try { $pg->_capture('--version'); $pg->use_driver; $dbh = DBI->connect('dbi:Pg:dbname=template1', $pguser, '', { PrintError => 0, RaiseError => 1, AutoCommit => 1, }); $dbh->do($_) for ( "CREATE DATABASE $db", "ALTER DATABASE $db SET lc_messages = 'C'", ); undef; } catch { eval { $_->message } || $_; }; DBIEngineTest->run( class => $CLASS, version_query => 'SELECT version()', target_params => [ uri => URI::db->new("db:pg://$pguser\@/$db"), ], alt_target_params => [ registry => '__sqitchtest', uri => URI::db->new("db:pg://$pguser\@/$db"), ], skip_unless => sub { my $self = shift; die $err if $err; # Make sure we have psql and can connect to the database. $self->sqitch->probe( $self->client, '--version' ); $self->_capture('--command' => 'SELECT version()'); }, engine_err_regex => qr/^ERROR: /, init_error => __x( 'Sqitch schema "{schema}" already exists', schema => '__sqitchtest', ), 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], '__sqitchtest', 'The Sqitch schema should be the current schema'; }, ); done_testing; App-Sqitch-0.9999 000755 000765 000024 0 13425063133 13161 5 ustar 00david staff 000000 000000 README 100644 000765 000024 450 13425063133 14101 0 ustar 00david staff 000000 000000 App-Sqitch-0.9999 This archive contains the distribution App-Sqitch, version 0.9999: Sane database change management This software is Copyright (c) 2019 by "iovation Inc.". This is free software, licensed under: The MIT (X11) License This README file was generated by Dist::Zilla::Plugin::Readme v6.012. Changes 100644 000765 000024 330643 13425063133 14606 0 ustar 00david staff 000000 000000 App-Sqitch-0.9999 Revision history for Perl extension App::Sqitch 0.9999 2019-02-01T15:29:40Z [Bug Fixes] - Fixed a test failure with the MySQL max limit value, mostly exhibited on BSD platforms. - Removed fallback in the PostgreSQL engine on the `$PGUSER` and `$PGPASSWORD` environnement variables, as well as the system username, since libpq does all that automatically, and collects data from other sources that we did not (e.g., the password and connection service files). Thanks to Tom Bloor for the report (issue #410). - Changed dependency validation to prevent an error when a change required from a different project has been reworked. Previously, when a when requiring a change such as `foo:greeble`, Sqitch would raise an error if `foo:greeble` was reworked, suggesting that the dependency be tag-qualified to eliminate ambiguity. Now reworked dependencies may be required without tag-qualification, though tag-qualification should still be specified if functionality as of a particular tag is required. - Added a workaround for the shell quoting issue on Windows. Applies to IPC::System::Simple 1.29 and lower. See [pjf/ipc-system-simple#29](https://github.com/pjf/ipc-system-simple/pull/29) for details (#413). - Fixed an issue with the MariaDB client where a deploy, revert, or verify failure was not properly propagated to Sqitch. Sqitch now passes `--abort-source-on-error` to the Maria `mysql` client to ensure that SQL errors cause the client to abort with an error so that Sqitch can properly handle it. Thanks to @mvgrimes for the original report and, years later, the fix (#209). - Fixed an issue with command argument parsing so that it truly never returns a target without an engine specified, as documented. - Removed documentation for methods that don't exist. - Fixed test failures due to a change in Encode v2.99 that's stricter about `undef` arguments that should be defined. [Improvements] - The Snowflake engine now consults the `connections.warehousename`, `connections.dbname`, and `connections.rolename` variables in the SnowSQL configuration file (`~/.snowsql/config`) before falling back on the hard-coded warehouse name "sqitch" and using the system username as the database name and no default for the role. - Switched to using a constant internally to optimize windows-specific code paths at compile time. - When `deploy` detects undeployed dependencies, it now eliminates duplicates before listing them in the error message. - Now requiring IO::Pager v0.34 or later for its more consistent interface. - Added notes about creating databases to the tutorials. Thanks to Dave Rolsky for the prompt (#315). - Added a status message to tell the user when the registry is being updated, rather than just show each individual update. Thanks to Ben Hutton for the suggestion (#276). - Added support for a `$SQITCH_TARGET` environment variable, which takes precedence over all other target specifications except for command-line options and arguments. Thanks to @mvgrimes for the suggestion (#203). - Fixed target/engine/change argument parsing so it won't automatically fail when `core.engine` isn't set unless no targets are found. This lets engines be determined strictly from command-line arguments -- derived from targets, or just listed on their own -- whether or not `core.engine` is set. This change eliminates the need for the `no_default` parameter to the `parse_args()` method of App::Sqitch Command. It also greatly reduces the need for the core `--engine` option, which was previously required to work around this issue (see below for its removal). - Refactored config handling in tests to use a custom subclass of App::Sqitch::Config instead of various mocks, temporary files, and the like. - Added advice to use the PL/pgSQL `ASSERT()` function for verify scripts to the Postgres tutorial. Thanks to Sergii Tkachenko for the PR (#425). [Target Variables] - The `verify` command now reads `deploy.variables`, and individual `verify.variables override `deploy.variables`, on the assumption that the verify variables in general ought to be the same as the deploy variables. This makes `verify` variable configuration consistent with `revert` variable configuration. - Variables set via the `--set-deploy` option on the `rebase` and `checkout` commands no longer apply to both reverts and deploys, but only deploys. Use the `--set` option to apply a variable to both reverts and deploys. - Added support for core, engine, and target variable configuration. The simplest way to use them is via the `--set` option on the `init`, `engine`, and `target` commands. These commands allow the configuration of database client variables for specific engines and targets, as well as defaults that apply to all change execution commands (`deploy`, `revert`, `verify`, `rebase`, and `checkout`). The commands merge the variables from each level in this priority order: * `--set-deploy` and `--set-revert` options on `rebase` and `checkout` * `--set` option * `target.$target.variables` * `engine.$engine.variables` * `deploy.variables`, `revert.variables`, and `verify.variables` * `core.variables` See `sqitch-configuration` for general documentation of of the hierarchy for merging variables and the documentation for each command for specifics. [Options Unification] - Added the `--chdir`/`--cd`/`-C` option to specify a directory to change to before executing any Sqitch commands. Thanks to Thomas Sibley for the suggestion (#411). - Added the `--no-pager` option to disable the pager (#414). - Changed command-line parsing to allow core and command options to appear anywhere on the line. Previously, core options had to come before the command name, and command options after. No more. The caveat is that command options that take arguments should either appear after the command or use the `--opt=val` syntax instead of `--opt val`, so that Sqitch doesn't think `val` is the command. Even in that case, it will search the rest of the arguments to find a valid command. However, to minimize this challenge, the documentation now suggests and demonstrates putting all options after the command, like so: `sqitch [command] [options]`. - Simplified and clarified the distinction between core and command options by removing all options from the core except those that affect output and runtime context. The core options are: * -C --chdir --cd