From 01c57e2d8fd39a2a502eb0d0de32f23b1a90c25e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 15 Oct 2014 09:17:17 -0700 Subject: [PATCH 01/59] Start work on Sqitch::Target. Most of the attributes currently in App::Sqitch and App::Sqitch::Engine will be moved here, to follow the config path options, target, engine, core, default. --- lib/App/Sqitch.pm | 2 +- lib/App/Sqitch/Target.pm | 247 +++++++++++++++++++++++++++++++++++++++ lib/App/Sqitch/Types.pm | 6 + 3 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 lib/App/Sqitch/Target.pm diff --git a/lib/App/Sqitch.pm b/lib/App/Sqitch.pm index b4622dda5..1f24d57b9 100644 --- a/lib/App/Sqitch.pm +++ b/lib/App/Sqitch.pm @@ -31,7 +31,7 @@ BEGIN { bind_textdomain_filter 'App-Sqitch' => \&Encode::decode_utf8; } -# Okay to load Sqitch classes now that typess are created. +# Okay to load Sqitch classes now that types are created. use App::Sqitch::Config; use App::Sqitch::Command; use App::Sqitch::Plan; diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm new file mode 100644 index 000000000..a7cdf49c2 --- /dev/null +++ b/lib/App/Sqitch/Target.pm @@ -0,0 +1,247 @@ +package App::Sqitch::Target; + +use 5.010; +use Moo; +use strict; +use warnings; +use App::Sqitch::Types qw(Maybe URIDB Str Dir Engine); +use Path::Class qw(dir); +use namespace::autoclean; + +has name => ( + is => 'ro', + isa => Str, + required => 1, +); + +has uri => ( + is => 'ro', + isa => URIDB, + required => 1, +); + +has sqitch => ( + is => 'ro', + isa => Sqitch, + required => 1, +); + +has engine => ( + is => 'ro', + isa => Engine, + lazy => 1, + default => sub { + my $self = shift; + my $sqitch = $self->sqitch; + require App::Sqitch::Engine; + App::Sqitch::Engine->load({ + sqitch => $sqitch, + engine => $sqitch->_engine || $self->uri->canonical_engine, + }); + }, +); + +has registry => ( + is => 'ro', + isa => Str, + lazy => 1, + default => sub { + my $self = shift; + my $engine = $self->engine; + my $ekey = $engine->key; + return $self->sqitch->config->get( + key => "core.$ekey.registry" + ) || $engine->default_registry; + }, +); + +has client => ( + is => 'ro', + isa => Str, + lazy => 1, + default => sub { + my $self = shift; + my $engine = $self->engine; + my $ekey = $engine->key; + return $self->sqitch->config->get( + key => "core.$ekey.registry" + ) or do { + my $client = $self->default_client; + return $client if $^O ne 'MSWin32'; + return $client if $client =~ /[.](?:exe|bat)$/; + return $client . '.exe'; + }; + }, +); + +sub _fetch { + my ($self, $key) = @_; + my $sqitch = $self->sqitch; + if (my $val = $sqitch->options->{$key}) { + return $val; + } + + my $config = $sqitch->config; + return $config->get( key => "target." . $self->name . ".$key" ) + || $config->get( key => "engine." . $self->engine->key . ".$key") + || $config->get( key => "core.$key"); +} + +has plan_file => ( + is => 'ro', + isa => File, + lazy => 1, + default => sub { + my $self = shift; + if (my $f = shift->_fetch('plan_file') { + return file $f; + } + return $self->top_dir->file('sqitch.plan')->cleanup; + }, +); + +has plan => ( + is => 'ro', + isa => Plan, + lazy => 1, + default => sub { + # XXX Modify to use target. + App::Sqitch::Plan->new( sqitch => shift ); + }, +); + +has top_dir => ( + is => 'ro', + isa => Dir, + lazy => 1, + default => sub { + dir shift->_fetch('top_dir') || (); + }, +); + +has deploy_dir => ( + is => 'ro', + isa => Dir, + lazy => 1, + default => sub { + my $self = shift; + if ( my $dir = $self->_fetch('deploy_dir'); + return dir $dir; + } + $self->top_dir->subdir('deploy')->cleanup; + }, +); + +has revert_dir => ( + is => 'ro', + isa => Dir, + lazy => 1, + default => sub { + my $self = shift; + if ( my $dir = $self->_fetch('revert_dir'); + return dir $dir; + } + $self->top_dir->subdir('deploy')->cleanup; + }, +); + +has verify_dir => ( + is => 'ro', + isa => Dir, + lazy => 1, + default => sub { + my $self = shift; + if ( my $dir = $self->_fetch('verify_dir'); + return dir $dir; + } + $self->top_dir->subdir('verify')->cleanup; + }, +); + +has extension => ( + is => 'ro', + isa => Str, + lazy => 1, + default => sub { + shift->_fetch('extension') || 'sql'; + }, +); + +# If no name: +# a. use URI for name; or +# b. Look for core.$engine.target. +# If no URI: +# a. Use name if it exists and contains a colon; or +# b. If name exists: look for target.$name.uri or die; or +# c. Default to "db:$engine:" +# If still no name, use URI. + +# Need to move command-line options into a hash, remove accessors in App::Sqitch. +# Remove attributes here from App::Sqitch and Engine. + +sub BUILDARGS { + my $p = @_ == 1 && ref $_[0] ? { %{ +shift } } : { @_ }; + my $sqitch = $p->{sqitch} or return $p; + + # The name defaults to the URI, if we have one. + if (my $uri = $p->{uri}) { + $p->{name} ||= "$uri"; + return $p; + } + + # If no name, try to find the default. + my $uri; + my $ekey = $sqitch->_engine; + my $name = $p->{name} ||= $sqitch->config->get( + key => "core.$engine.target" + ); + + # If no URI, we have to find one. + if (!$name) { + # Fall back on the default. + $p->{name} = $uri = "db:$ekey"; + } elsif ($name =~ /:/) { + # The name is a URI. + $uri = URI::db->new($name); + } else { + # Well then, we have a whole config to load up. + my $config = $sqitch->config->get_section( + section => "target.$t" + ) or hurl target => __x( + 'Cannot find target "{target}"', + target => $name + ); + + # There had best be a URI. + $uri = $config->{uri} or hurl target => __( + 'No URI associated with target "{target}"', + target => $name, + ); + } + + # Instantiate the URI and override parts with command-line options. + # TODO: Deprecate these. + $uri = $p->{uri} = URI::db->new( $uri ); + + # Override parts with command-line options (deprecate?) + if (my $host = $sqitch->db_host) { + $uri->host($host); + } + + if (my $port = $sqitch->db_port) { + $uri->port($port); + } + + if (my $user = $sqitch->db_username) { + $uri->user($user); + } + + if (my $db = $sqitch->db_name) { + $uri->dbname($db); + } + + return $p; +} + +1; +__END__ diff --git a/lib/App/Sqitch/Types.pm b/lib/App/Sqitch/Types.pm index 7eb0080c4..649c39339 100644 --- a/lib/App/Sqitch/Types.pm +++ b/lib/App/Sqitch/Types.pm @@ -6,6 +6,7 @@ use warnings; use utf8; use Type::Library 0.040 -base, -declare => qw( Sqitch + Engine UserName UserEmail ConfigBool @@ -35,6 +36,7 @@ use List::Util qw(first); BEGIN { extends "Types::Standard" }; class_type Sqitch, { class => 'App::Sqitch' }; +class_type Engine, { class => 'App::Sqitch::Engine' }; class_type Plan, { class => 'App::Sqitch::Plan' }; class_type Change, { class => 'App::Sqitch::Plan::Change' }; class_type ChangeList, { class => 'App::Sqitch::Plan::ChangeList' }; @@ -95,6 +97,10 @@ are: An L object. +=item C + +An L object. + =item C A Sqitch user name. From 0b565af0d1f590aba817b417a2e2c1ac365d2858 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 15 Oct 2014 12:27:41 -0700 Subject: [PATCH 02/59] Add options attribute to App::Sqitch. --- lib/App/Sqitch.pm | 15 +++++++++++++++ lib/App/Sqitch/Target.pm | 15 ++++++++------- t/base.t | 6 ++++-- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/App/Sqitch.pm b/lib/App/Sqitch.pm index 1f24d57b9..b02bde338 100644 --- a/lib/App/Sqitch.pm +++ b/lib/App/Sqitch.pm @@ -61,6 +61,12 @@ has plan => ( }, ); +has options => ( + is => 'ro', + isa => HashRef, + default => sub { {} }, +); + has _engine => ( is => 'ro', isa => Maybe[Str], @@ -336,6 +342,7 @@ sub go { my $config = App::Sqitch::Config->new; # 5. Instantiate Sqitch. + $opts->{options} = { %{ $opts }}; $opts->{_engine} = delete $opts->{engine} if $opts->{engine}; $opts->{config} = $config; my $sqitch = $class->new($opts); @@ -776,6 +783,8 @@ Constructs and returns a new Sqitch object. The supported parameters include: =over +=item C + =item C =item C @@ -838,6 +847,12 @@ Constructs and returns a new Sqitch object. The supported parameters include: =head3 C +=head3 C + + my $options = $sqitch->options; + +Returns a hashref of the core command-line options. + =head3 C my $config = $sqitch->config; diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index a7cdf49c2..d2623224c 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -219,24 +219,25 @@ sub BUILDARGS { ); } - # Instantiate the URI and override parts with command-line options. - # TODO: Deprecate these. + # Instantiate the URI. $uri = $p->{uri} = URI::db->new( $uri ); - # Override parts with command-line options (deprecate?) - if (my $host = $sqitch->db_host) { + # Override parts with command-line options. + # TODO: Deprecate these. + my $opts = $sqitch->options; + if (my $host = $opts->{db_host}) { $uri->host($host); } - if (my $port = $sqitch->db_port) { + if (my $port = $opts->{db_port}) { $uri->port($port); } - if (my $user = $sqitch->db_username) { + if (my $user = $opts->{db_username}) { $uri->user($user); } - if (my $db = $sqitch->db_name) { + if (my $db = $opts->{db_name}) { $uri->dbname($db); } diff --git a/t/base.t b/t/base.t index ea9fac0de..43afcd7bb 100644 --- a/t/base.t +++ b/t/base.t @@ -2,8 +2,8 @@ use strict; use warnings; -use Test::More tests => 242; -#use Test::More 'no_plan'; +#use Test::More tests => 242; +use Test::More 'no_plan'; use Test::MockModule; use Path::Class; use Test::Exception; @@ -25,6 +25,7 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; can_ok $CLASS, qw( go new + options plan_file plan engine @@ -295,6 +296,7 @@ GO: { 'Should have read user name from configuration'; is $sqitch->user_email, 'michael@example.com', 'Should have read user email from configuration'; + is_deeply $sqitch->options, { engine => 'sqlite' }, 'Should have options'; # Now make it die. sub puke { App::Sqitch::X->new(@_) } # Ensures we have trace frames. From f8a8c1b64389ce9b09845ad3b14b4608cffc98e9 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 15 Oct 2014 12:28:51 -0700 Subject: [PATCH 03/59] Rename t/target.t to t/target_cmd.t. --- t/{target.t => target_cmd.t} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename t/{target.t => target_cmd.t} (100%) diff --git a/t/target.t b/t/target_cmd.t similarity index 100% rename from t/target.t rename to t/target_cmd.t From 2c7ba8213a21f251c0993b425cd1f68b6133417c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 15 Oct 2014 12:49:29 -0700 Subject: [PATCH 04/59] Get Target to compile. --- lib/App/Sqitch/Target.pm | 31 ++++++++++++++++++++----------- t/target.t | 24 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 11 deletions(-) create mode 100644 t/target.t diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index d2623224c..326a66b81 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -4,8 +4,10 @@ use 5.010; use Moo; use strict; use warnings; -use App::Sqitch::Types qw(Maybe URIDB Str Dir Engine); -use Path::Class qw(dir); +use App::Sqitch::Types qw(Maybe URIDB Str Dir Engine Sqitch File Plan); +use App::Sqitch::X qw(hurl); +use Locale::TextDomain 1.20 qw(App-Sqitch); +use Path::Class qw(dir file); use namespace::autoclean; has name => ( @@ -65,7 +67,7 @@ has client => ( my $ekey = $engine->key; return $self->sqitch->config->get( key => "core.$ekey.registry" - ) or do { + ) || do { my $client = $self->default_client; return $client if $^O ne 'MSWin32'; return $client if $client =~ /[.](?:exe|bat)$/; @@ -93,7 +95,7 @@ has plan_file => ( lazy => 1, default => sub { my $self = shift; - if (my $f = shift->_fetch('plan_file') { + if (my $f = shift->_fetch('plan_file') ) { return file $f; } return $self->top_dir->file('sqitch.plan')->cleanup; @@ -125,7 +127,7 @@ has deploy_dir => ( lazy => 1, default => sub { my $self = shift; - if ( my $dir = $self->_fetch('deploy_dir'); + if ( my $dir = $self->_fetch('deploy_dir') ) { return dir $dir; } $self->top_dir->subdir('deploy')->cleanup; @@ -138,7 +140,7 @@ has revert_dir => ( lazy => 1, default => sub { my $self = shift; - if ( my $dir = $self->_fetch('revert_dir'); + if ( my $dir = $self->_fetch('revert_dir') ) { return dir $dir; } $self->top_dir->subdir('deploy')->cleanup; @@ -151,7 +153,7 @@ has verify_dir => ( lazy => 1, default => sub { my $self = shift; - if ( my $dir = $self->_fetch('verify_dir'); + if ( my $dir = $self->_fetch('verify_dir') ) { return dir $dir; } $self->top_dir->subdir('verify')->cleanup; @@ -180,6 +182,7 @@ has extension => ( # Remove attributes here from App::Sqitch and Engine. sub BUILDARGS { + my $class = shift; my $p = @_ == 1 && ref $_[0] ? { %{ +shift } } : { @_ }; my $sqitch = $p->{sqitch} or return $p; @@ -191,9 +194,14 @@ sub BUILDARGS { # If no name, try to find the default. my $uri; - my $ekey = $sqitch->_engine; + my $ekey = $sqitch->options->{engine} || $sqitch->config->get( + key => 'core.engine' + ) or hurl target => __( + 'No engine specified; use --engine or set core.engine' + ); + my $name = $p->{name} ||= $sqitch->config->get( - key => "core.$engine.target" + key => "core.$ekey.target" ); # If no URI, we have to find one. @@ -206,20 +214,21 @@ sub BUILDARGS { } else { # Well then, we have a whole config to load up. my $config = $sqitch->config->get_section( - section => "target.$t" + section => "target.$name" ) or hurl target => __x( 'Cannot find target "{target}"', target => $name ); # There had best be a URI. - $uri = $config->{uri} or hurl target => __( + $uri = $config->{uri} or hurl target => __x( 'No URI associated with target "{target}"', target => $name, ); } # Instantiate the URI. + require URI::db; $uri = $p->{uri} = URI::db->new( $uri ); # Override parts with command-line options. diff --git a/t/target.t b/t/target.t new file mode 100644 index 000000000..3558e0563 --- /dev/null +++ b/t/target.t @@ -0,0 +1,24 @@ +#!/usr/bin/perl -w + +use strict; +use warnings; +use utf8; +#use Test::More tests => 142; +use Test::More 'no_plan'; +use App::Sqitch; + +$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; +$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; +$ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; + +my $CLASS; +BEGIN { + $CLASS = 'App::Sqitch::Target'; + use_ok $CLASS or die; +} + +############################################################################## +# Load a target and test the basics. +ok my $sqitch = App::Sqitch->new(options => { engine => 'sqlite'}), + 'Load a sqitch sqitch object'; +isa_ok my $x = $CLASS->new(sqitch => $sqitch), $CLASS; From c626769dc5b0deb65f35a2c38478e2331b86b96d Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 15 Oct 2014 13:03:00 -0700 Subject: [PATCH 05/59] Test Target basics, defaults. --- lib/App/Sqitch/Engine.pm | 2 +- lib/App/Sqitch/Target.pm | 15 +++++++-------- t/target.t | 40 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/lib/App/Sqitch/Engine.pm b/lib/App/Sqitch/Engine.pm index f2c2bbe85..660c1f5ff 100644 --- a/lib/App/Sqitch/Engine.pm +++ b/lib/App/Sqitch/Engine.pm @@ -254,7 +254,7 @@ sub load { # We should have a URI or an engine param. my $engine = delete $p->{engine} - or hurl 'Missing "uri" or "engine" parameter to load()'; + or hurl 'Missing "engine" parameter to load()'; # Load the engine class. my $pkg = __PACKAGE__ . "::$engine"; diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 326a66b81..1839ef63c 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -34,11 +34,10 @@ has engine => ( lazy => 1, default => sub { my $self = shift; - my $sqitch = $self->sqitch; require App::Sqitch::Engine; App::Sqitch::Engine->load({ - sqitch => $sqitch, - engine => $sqitch->_engine || $self->uri->canonical_engine, + sqitch => $self->sqitch, + engine => $self->uri->canonical_engine, }); }, ); @@ -68,7 +67,7 @@ has client => ( return $self->sqitch->config->get( key => "core.$ekey.registry" ) || do { - my $client = $self->default_client; + my $client = $engine->default_client; return $client if $^O ne 'MSWin32'; return $client if $client =~ /[.](?:exe|bat)$/; return $client . '.exe'; @@ -95,7 +94,7 @@ has plan_file => ( lazy => 1, default => sub { my $self = shift; - if (my $f = shift->_fetch('plan_file') ) { + if (my $f = $self->_fetch('plan_file') ) { return file $f; } return $self->top_dir->file('sqitch.plan')->cleanup; @@ -108,7 +107,7 @@ has plan => ( lazy => 1, default => sub { # XXX Modify to use target. - App::Sqitch::Plan->new( sqitch => shift ); + App::Sqitch::Plan->new( sqitch => shift->sqitch ); }, ); @@ -143,7 +142,7 @@ has revert_dir => ( if ( my $dir = $self->_fetch('revert_dir') ) { return dir $dir; } - $self->top_dir->subdir('deploy')->cleanup; + $self->top_dir->subdir('revert')->cleanup; }, ); @@ -207,7 +206,7 @@ sub BUILDARGS { # If no URI, we have to find one. if (!$name) { # Fall back on the default. - $p->{name} = $uri = "db:$ekey"; + $p->{name} = $uri = "db:$ekey:"; } elsif ($name =~ /:/) { # The name is a URI. $uri = URI::db->new($name); diff --git a/t/target.t b/t/target.t index 3558e0563..46b65f5d1 100644 --- a/t/target.t +++ b/t/target.t @@ -6,6 +6,7 @@ use utf8; #use Test::More tests => 142; use Test::More 'no_plan'; use App::Sqitch; +use Path::Class qw(dir); $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; $ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; @@ -21,4 +22,41 @@ BEGIN { # Load a target and test the basics. ok my $sqitch = App::Sqitch->new(options => { engine => 'sqlite'}), 'Load a sqitch sqitch object'; -isa_ok my $x = $CLASS->new(sqitch => $sqitch), $CLASS; +isa_ok my $target = $CLASS->new(sqitch => $sqitch), $CLASS; +can_ok $target, qw( + new + name + uri + sqitch + engine + registry + client + plan_file + plan + top_dir + deploy_dir + revert_dir + verify_dir + extension +); + +# Look at default values. +is $target->name, 'db:sqlite:', 'Name should be "db:sqlite"'; +is $target->uri, URI::db->new('db:sqlite:'), 'URI should be "db:sqlite"'; +is $target->sqitch, $sqitch, 'Sqitch should be as passed'; +isa_ok $target->engine, 'App::Sqitch::Engine::sqlite', 'Engine'; +is $target->registry, $target->engine->default_registry, + 'Should have default registry'; +is $target->client, $target->engine->default_client, + 'Should have default client'; +is $target->top_dir, dir, 'Should have default top_dir'; +is $target->deploy_dir, $target->top_dir->subdir('deploy'), + 'Should have default deploy_dir'; +is $target->revert_dir, $target->top_dir->subdir('revert'), + 'Should have default revert_dir'; +is $target->verify_dir, $target->top_dir->subdir('verify'), + 'Should have default verify_dir'; +is $target->extension, 'sql', 'Should have default extension'; +is $target->plan_file, $target->top_dir->file('sqitch.plan')->cleanup, + 'Should have default plan file'; +isa_ok $target->plan, 'App::Sqitch::Plan', 'Should get plan'; From ccc8d85ab66a31317b15fb70ed5176f851889f3e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 15 Oct 2014 13:11:59 -0700 Subject: [PATCH 06/59] Add `file` attribute to Plan. --- lib/App/Sqitch/Plan.pm | 15 ++++++++++++--- lib/App/Sqitch/Plan/Depend.pm | 2 +- t/plan.t | 20 ++++++++++++++------ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/App/Sqitch/Plan.pm b/lib/App/Sqitch/Plan.pm index 9256c915c..e06143503 100644 --- a/lib/App/Sqitch/Plan.pm +++ b/lib/App/Sqitch/Plan.pm @@ -15,7 +15,7 @@ use App::Sqitch::X qw(hurl); use List::MoreUtils qw(uniq any); use namespace::autoclean; use Moo; -use App::Sqitch::Types qw(Str Int HashRef ChangeList LineList Maybe Sqitch URI); +use App::Sqitch::Types qw(Str Int HashRef ChangeList LineList Maybe Sqitch URI File); use constant SYNTAX_VERSION => '1.0.0-b2'; our $VERSION = '0.997'; @@ -47,6 +47,15 @@ has sqitch => ( weak_ref => 1, ); +has file => ( + is => 'ro', + isa => File, + lazy => 1, + default => sub { + shift->sqitch->plan_file + }, +); + has _plan => ( is => 'rw', isa => HashRef, @@ -109,7 +118,7 @@ sub parse { sub load { my $self = shift; - my $file = $self->sqitch->plan_file; + my $file = $self->file; my $fh = shift || do { hurl plan => __x('Plan file {file} does not exist', file => $file) unless -e $file; @@ -548,7 +557,7 @@ sub check_changes { $max_delta, change => $change, num => $max_delta, - plan => $self->sqitch->plan_file, + plan => $self->file, ); } } diff --git a/lib/App/Sqitch/Plan/Depend.pm b/lib/App/Sqitch/Plan/Depend.pm index c0c7d1df7..8cd1a322c 100644 --- a/lib/App/Sqitch/Plan/Depend.pm +++ b/lib/App/Sqitch/Plan/Depend.pm @@ -77,7 +77,7 @@ has id => ( my $change = $plan->find( $self->key_name ) // hurl plan => __x( 'Unable to find change "{change}" in plan {file}', change => $self->key_name, - file => $plan->sqitch->plan_file, + file => $plan->file, ); return $change->id; } diff --git a/t/plan.t b/t/plan.t index ad5335226..e0774801c 100644 --- a/t/plan.t +++ b/t/plan.t @@ -32,6 +32,7 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; can_ok $CLASS, qw( sqitch + file changes position load @@ -45,6 +46,7 @@ can_ok $CLASS, qw( my $sqitch = App::Sqitch->new; isa_ok my $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS; +is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; # Set up some some utility functions for creating changes. sub blank { @@ -696,6 +698,7 @@ $file = file qw(t plans dependencies.plan); $sqitch = App::Sqitch->new(plan_file => $file); isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, 'Plan with sqitch with plan file with dependencies'; +is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; ok $parsed = $plan->load, 'Load plan with dependencies file'; is_deeply $parsed->{changes}, [ clear, @@ -727,6 +730,7 @@ $file = file qw(t plans project_deps.plan); $sqitch = App::Sqitch->new(plan_file => $file); isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, 'Plan with sqitch with plan file with project deps'; +is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; ok $parsed = $plan->load, 'Load plan with project deps file'; is_deeply $parsed->{changes}, [ clear, @@ -760,6 +764,7 @@ $fh = IO::File->new(\"%project=tagdep\n\nfoo $tsnp\n\@bar [:foo] $tsnp", '<:utf8 $sqitch = App::Sqitch->new(plan_file => $file); isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, 'Plan with sqitch with plan with tag dependencies'; +is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; throws_ok { $plan->_parse($file, $fh) } 'App::Sqitch::X', 'Should get an exception for tag with dependencies'; is $@->ident, 'parse', 'The tag dependencies error ident should be "plan"'; @@ -775,6 +780,7 @@ $file = file qw(t plans multi.plan); $sqitch = App::Sqitch->new(plan_file => $file); isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, 'Plan with sqitch with plan file'; +is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; cmp_deeply [$plan->lines], [ clear, version, @@ -1544,6 +1550,7 @@ $file = file qw(t plans dupe-change-diff-tag.plan); $sqitch = App::Sqitch->new(plan_file => $file); isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, 'Plan shoud work plan with dupe change across tags'; +is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; is $plan->project, 'dupe_change_diff_tag', 'Project name should be set'; cmp_deeply [ $plan->lines ], [ clear, @@ -1701,7 +1708,7 @@ is $@->message, $deperr->(__nx( 2, change => 'this', num => 2, - plan => $plan->sqitch->plan_file, + plan => $plan->file, )), 'And the unordered dependency error message should be correct'; # Have this require other and that. @@ -1731,7 +1738,7 @@ is $@->message, $deperr->( 2, change => 'this', num => 2, - plan => $plan->sqitch->plan_file, + plan => $plan->file, ), ), 'And the multiple dependency error message should be correct'; @@ -1776,7 +1783,7 @@ is $@->message, $deperr->( 1, change => 'this', num => 1, - plan => $plan->sqitch->plan_file, + plan => $plan->file, ), ), 'The cycle error message should be correct'; @@ -1803,7 +1810,7 @@ is $@->message, $deperr->( 1, change => 'this', num => 1, - plan => $plan->sqitch->plan_file, + plan => $plan->file, ), __nx( 'Change "{change}" planned {num} change before required change "{required}"', 'Change "{change}" planned {num} changes before required change "{required}"', @@ -1817,7 +1824,7 @@ is $@->message, $deperr->( 1, change => 'that', num => 1, - plan => $plan->sqitch->plan_file, + plan => $plan->file, ), ), 'The two-hop cycle error message should be correct'; @@ -1853,7 +1860,7 @@ is $@->message, $deperr->( 2, change => 'this', num => 2, - plan => $plan->sqitch->plan_file, + plan => $plan->file, ), ), 'And the misordered and seen error message should be correct'; @@ -1964,6 +1971,7 @@ $file = file qw(t plans pragmas.plan); $sqitch = App::Sqitch->new(plan_file => $file); isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, 'Plan with sqitch with plan file with dependencies'; +is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; is $plan->syntax_version, App::Sqitch::Plan::SYNTAX_VERSION, 'syntax_version should be set'; is $plan->project, 'pragmata', 'Project should be set'; From f497ef59b1a98bef336bb72606303117ef738fee Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 15 Oct 2014 15:46:48 -0700 Subject: [PATCH 07/59] Fix test. --- t/engine.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/engine.t b/t/engine.t index bb592fd05..58a6e143c 100644 --- a/t/engine.t +++ b/t/engine.t @@ -137,7 +137,7 @@ NOENGINE: { throws_ok { $CLASS->load({ engine => '', sqitch => $sqitch }) } 'App::Sqitch::X', 'No engine should die'; - is $@->message, 'Missing "uri" or "engine" parameter to load()', + is $@->message, 'Missing "engine" parameter to load()', 'It should be the expected message'; } From da36c518a6025ba918e92f19006cf372c2c2d162 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 16 Oct 2014 15:26:15 -0700 Subject: [PATCH 08/59] Fully test target contstruction. Should properly handle all combinations of target names and URIs, command-line options, engine specs, and configuration. The --db-* options are also deprecated, and the `uri` new methods delegate to the URI attribute, to make the Target interface the main one one would use. --- lib/App/Sqitch/Engine.pm | 2 +- lib/App/Sqitch/Target.pm | 83 +++++++++++++++++----- t/target.t | 149 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 213 insertions(+), 21 deletions(-) diff --git a/lib/App/Sqitch/Engine.pm b/lib/App/Sqitch/Engine.pm index 660c1f5ff..3948283ca 100644 --- a/lib/App/Sqitch/Engine.pm +++ b/lib/App/Sqitch/Engine.pm @@ -252,7 +252,7 @@ sub _merge_options_into { sub load { my ( $class, $p ) = @_; - # We should have a URI or an engine param. + # We should have an engine param. my $engine = delete $p->{engine} or hurl 'Missing "engine" parameter to load()'; diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 1839ef63c..520da4852 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -6,7 +6,7 @@ use strict; use warnings; use App::Sqitch::Types qw(Maybe URIDB Str Dir Engine Sqitch File Plan); use App::Sqitch::X qw(hurl); -use Locale::TextDomain 1.20 qw(App-Sqitch); +use Locale::TextDomain qw(App-Sqitch); use Path::Class qw(dir file); use namespace::autoclean; @@ -20,6 +20,12 @@ has uri => ( is => 'ro', isa => URIDB, required => 1, + handles => { + engine_key => 'canonical_engine', + dsn => 'dbi_dsn', + username => 'user', + password => 'password', + }, ); has sqitch => ( @@ -106,8 +112,12 @@ has plan => ( isa => Plan, lazy => 1, default => sub { - # XXX Modify to use target. - App::Sqitch::Plan->new( sqitch => shift->sqitch ); + my $self = shift; + # XXX Update to reference target. + App::Sqitch::Plan->new( + sqitch => $self->sqitch, + file => $self->plan_file, + ); }, ); @@ -187,7 +197,15 @@ sub BUILDARGS { # The name defaults to the URI, if we have one. if (my $uri = $p->{uri}) { - $p->{name} ||= "$uri"; + if (!$p->{name}) { + # Set the URI as the name, sans password. + if ($uri->password) { + $uri = $uri->clone; + $uri->password(undef); + } + $p->{name} = $uri->as_string; + + } return $p; } @@ -206,24 +224,26 @@ sub BUILDARGS { # If no URI, we have to find one. if (!$name) { # Fall back on the default. - $p->{name} = $uri = "db:$ekey:"; + $uri = "db:$ekey:"; } elsif ($name =~ /:/) { # The name is a URI. $uri = URI::db->new($name); + $name = undef; } else { - # Well then, we have a whole config to load up. - my $config = $sqitch->config->get_section( - section => "target.$name" - ) or hurl target => __x( - 'Cannot find target "{target}"', - target => $name - ); - - # There had best be a URI. - $uri = $config->{uri} or hurl target => __x( - 'No URI associated with target "{target}"', - target => $name, - ); + # Well then, there had better be a config with a URI. + $uri = $sqitch->config->get( key => "target.$name.uri" ) or do { + # Die on no section or no URI. + hurl target => __x( + 'Cannot find target "{target}"', + target => $name + ) unless %{ $sqitch->config->get_section( + section => "target.$name" + ) }; + hurl target => __x( + 'No URI associated with target "{target}"', + target => $name, + ); + }; } # Instantiate the URI. @@ -233,22 +253,49 @@ sub BUILDARGS { # Override parts with command-line options. # TODO: Deprecate these. my $opts = $sqitch->options; + my @deprecated; if (my $host = $opts->{db_host}) { + push @deprecated => '--db-host'; $uri->host($host); } if (my $port = $opts->{db_port}) { + push @deprecated => '--db-port'; $uri->port($port); } if (my $user = $opts->{db_username}) { + push @deprecated => '--db-username'; $uri->user($user); } if (my $db = $opts->{db_name}) { + push @deprecated => '--db-name'; $uri->dbname($db); } + if (@deprecated) { + $sqitch->warn(__nx( + 'Option {options} deprecated and will be removed in 1.0; use URI {uri} instead', + 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', + scalar @deprecated, + options => join(', ', @deprecated), + uri => $uri->as_string, + )); + } + + unless ($p->{name}) { + # Set the name. + if ($uri->password) { + # Remove the password from the name. + my $tmp = $uri->clone; + $tmp->password(undef); + $p->{name} = $tmp->as_string; + } else { + $p->{name} = $uri->as_string; + } + } + return $p; } diff --git a/t/target.t b/t/target.t index 46b65f5d1..6145c54ec 100644 --- a/t/target.t +++ b/t/target.t @@ -7,6 +7,11 @@ use utf8; use Test::More 'no_plan'; use App::Sqitch; use Path::Class qw(dir); +use Test::Exception; +use Test::MockModule; +use Locale::TextDomain qw(App-Sqitch); +use lib 't/lib'; +use MockOutput; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; $ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; @@ -41,9 +46,10 @@ can_ok $target, qw( ); # Look at default values. -is $target->name, 'db:sqlite:', 'Name should be "db:sqlite"'; -is $target->uri, URI::db->new('db:sqlite:'), 'URI should be "db:sqlite"'; +is $target->name, 'db:sqlite:', 'Name should be "db:sqlite:"'; +is $target->uri, URI::db->new('db:sqlite:'), 'URI should be "db:sqlite:"'; is $target->sqitch, $sqitch, 'Sqitch should be as passed'; +is $target->engine_key, 'sqlite', 'Engine key should be "sqlite"'; isa_ok $target->engine, 'App::Sqitch::Engine::sqlite', 'Engine'; is $target->registry, $target->engine->default_registry, 'Should have default registry'; @@ -60,3 +66,142 @@ is $target->extension, 'sql', 'Should have default extension'; is $target->plan_file, $target->top_dir->file('sqitch.plan')->cleanup, 'Should have default plan file'; isa_ok $target->plan, 'App::Sqitch::Plan', 'Should get plan'; +is $target->plan->file, $target->plan_file, + 'Plan file should be copied from Target'; +my $uri = $target->uri; +is $target->dsn, $uri->dbi_dsn, 'DSN should be from URI'; +is $target->username, $uri->user, 'Username should be from URI'; +is $target->password, $uri->password, 'Password should be from URI'; + +############################################################################## +# Let's look at how the object is created based on the params to new(). +# First try no params. +throws_ok { $CLASS->new } qr/^Missing required arguments:/, + 'Should get error for missing params'; + +# Pass both name and URI. +$uri = URI::db->new('db:pg://hi:@there@localhost/blah'), +isa_ok $target = $CLASS->new( + sqitch => $sqitch, + name => 'foo', + uri => $uri, +), $CLASS, 'Target with name and URI'; + +is $target->name, 'foo', 'Name should be "foo"'; +is $target->uri, $uri, 'URI should be set as passed'; +is $target->sqitch, $sqitch, 'Sqitch should be as passed'; +is $target->engine_key, 'pg', 'Engine key should be "pg"'; +isa_ok $target->engine, 'App::Sqitch::Engine::pg', 'Engine'; +is $target->dsn, $uri->dbi_dsn, 'DSN should be from URI'; +is $target->username, $uri->user, 'Username should be from URI'; +is $target->password, $uri->password, 'Password should be from URI'; + +# Pass a URI but no name. +isa_ok $target = $CLASS->new( + sqitch => $sqitch, + uri => $uri, +), $CLASS, 'Target with URI'; +like $target->name, qr{db:pg://hi:?\@localhost/blah}, + 'Name should be URI without password'; +is $target->engine_key, 'pg', 'Engine key should be "pg"'; +isa_ok $target->engine, 'App::Sqitch::Engine::pg', 'Engine'; +is $target->dsn, $uri->dbi_dsn, 'DSN should be from URI'; +is $target->username, $uri->user, 'Username should be from URI'; +is $target->password, $uri->password, 'Password should be from URI'; + +# Set up a config. +CONFIG: { + my $mock = Test::MockModule->new('App::Sqitch::Config'); + my @get_params; + my @get_ret; + $mock->mock(get => sub { shift; push @get_params => \@_; shift @get_ret; }); + + # Pass neither, but rely on the engine in the Sqitch object. + isa_ok my $target = $CLASS->new(sqitch => $sqitch), $CLASS, 'Default target'; + is $target->name, 'db:sqlite:', 'Name should be "db:sqlite:"'; + is $target->uri, URI::db->new('db:sqlite:'), 'URI should be "db:sqlite:"'; + is_deeply \@get_params, [[key => 'core.sqlite.target']], + 'Should have tried to get engine target'; + + # Try with no engine option. + @get_params = (); + delete $sqitch->options->{engine}; + push @get_ret => 'mysql'; + isa_ok $target = $CLASS->new(sqitch => $sqitch), $CLASS, 'Default target'; + is $target->name, 'db:mysql:', 'Name should be "db:mysql:"'; + is $target->uri, URI::db->new('db:mysql:'), 'URI should be "db:mysql"'; + is_deeply \@get_params, [[key => 'core.engine'], [key => 'core.mysql.target']], + 'Should have tried to get core engine and its target'; + + # Try it with no configured core engine or target. + throws_ok { $CLASS->new(sqitch => $sqitch) } 'App::Sqitch::X', + 'Should have error for no engine or target'; + is $@->ident, 'target', 'Should have target ident'; + is $@->message, __( + 'No engine specified; use --engine or set core.engine' + ), 'Should have message about no specified engine'; + + # Mock get_section. + my @sect_params; + my @sect_ret = ({}); + $mock->mock(get_section => sub { shift; push @sect_params => \@_; shift @sect_ret; }); + + # Try it with a name. + $sqitch->options->{engine} = 'sqlite'; + @get_params = (); + throws_ok { $CLASS->new(sqitch => $sqitch, name => 'foo') } 'App::Sqitch::X', + 'Should have exception for unknown named target'; + is $@->ident, 'target', 'Unknown target error ident should be "target"'; + is $@->message, __x( + 'Cannot find target "{target}"', + target => 'foo', + ), 'Unknown target error message should be correct'; + is_deeply \@get_params, [[key => 'target.foo.uri']], + 'Should have requested target URI from config'; + is_deeply \@sect_params, [[section => 'target.foo']], + 'Should have requested target.foo section'; + + # Let the name section exist, but without a URI. + @get_params = @sect_params = (); + @sect_ret = ({ foo => 1}); + throws_ok { $CLASS->new(sqitch => $sqitch, name => 'foo') } 'App::Sqitch::X', + 'Should have exception for URL-less named target'; + is $@->ident, 'target', 'URL-less target error ident should be "target"'; + is $@->message, __x( + 'No URI associated with target "{target}"', + target => 'foo', + ), 'URL-less target error message should be correct'; + is_deeply \@get_params, [[key => 'target.foo.uri']], + 'Should have requested target URI from config'; + is_deeply \@sect_params, [[section => 'target.foo']], + 'Should have requested target.foo section'; + + # Now give it a URI. + @get_params = @sect_params = (); + @get_ret = ('db:pg:foo'); + isa_ok $target = $CLASS->new(sqitch => $sqitch, name => 'foo'), $CLASS, + 'Named target'; + is $target->name, 'foo', 'Name should be "foo"'; + is $target->uri, URI::db->new('db:pg:foo'), 'URI should be "db:pg:foo"'; + is_deeply \@get_params, [[key => 'target.foo.uri']], + 'Should have requested target URI from config'; + is_deeply \@sect_params, [], 'Should have requested no section'; + + # Make sure deprecated --db-* options work. + $uri = URI::db->new('db:pg://fred@foo.com:12245/widget'); + $sqitch->options->{engine} = 'pg'; + $sqitch->options->{db_host} = 'foo.com'; + $sqitch->options->{db_port} = 12245; + $sqitch->options->{db_username} = 'fred'; + $sqitch->options->{db_name} = 'widget'; + isa_ok $target = $CLASS->new(sqitch => $sqitch), $CLASS, 'SQLite target'; + is $target->name, $uri->as_string, 'Name should be stringified URI'; + is $target->uri, $uri, 'URI should be tweaked by --db-* options'; + is_deeply +MockOutput->get_warn, [[__x( + 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', + options => '--db-host, --db-port, --db-username, --db-name', + uri => $uri->as_string, + )]], 'Should have warned on deprecated options'; +} + + From c9953c0ac5b4a46e7656f45c1e0db494ebc61340 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 16 Oct 2014 16:51:22 -0700 Subject: [PATCH 09/59] Test and fix attribute config hierarchy. These attributes get their values from: 1. command-line option 2. target config 3. engine config 4. core config 5. reasonable default Need to add --registry option to make it complete. --- lib/App/Sqitch/Target.pm | 44 +++++------- t/target.t | 147 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 162 insertions(+), 29 deletions(-) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 520da4852..0aad2488f 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -48,17 +48,26 @@ has engine => ( }, ); +sub _fetch { + my ($self, $key) = @_; + my $sqitch = $self->sqitch; + if (my $val = $sqitch->options->{$key}) { + return $val; + } + + my $config = $sqitch->config; + return $config->get( key => "target." . $self->name . ".$key" ) + || $config->get( key => "core." . $self->engine->key . ".$key") + || $config->get( key => "core.$key"); +} + has registry => ( is => 'ro', isa => Str, lazy => 1, default => sub { - my $self = shift; - my $engine = $self->engine; - my $ekey = $engine->key; - return $self->sqitch->config->get( - key => "core.$ekey.registry" - ) || $engine->default_registry; + my $self = shift; + $self->_fetch('registry') || $self->engine->default_registry; }, ); @@ -67,13 +76,9 @@ has client => ( isa => Str, lazy => 1, default => sub { - my $self = shift; - my $engine = $self->engine; - my $ekey = $engine->key; - return $self->sqitch->config->get( - key => "core.$ekey.registry" - ) || do { - my $client = $engine->default_client; + my $self = shift; + $self->_fetch('client') || do { + my $client = $self->engine->default_client; return $client if $^O ne 'MSWin32'; return $client if $client =~ /[.](?:exe|bat)$/; return $client . '.exe'; @@ -81,19 +86,6 @@ has client => ( }, ); -sub _fetch { - my ($self, $key) = @_; - my $sqitch = $self->sqitch; - if (my $val = $sqitch->options->{$key}) { - return $val; - } - - my $config = $sqitch->config; - return $config->get( key => "target." . $self->name . ".$key" ) - || $config->get( key => "engine." . $self->engine->key . ".$key") - || $config->get( key => "core.$key"); -} - has plan_file => ( is => 'ro', isa => File, diff --git a/t/target.t b/t/target.t index 6145c54ec..5a4ff8a2a 100644 --- a/t/target.t +++ b/t/target.t @@ -3,8 +3,7 @@ use strict; use warnings; use utf8; -#use Test::More tests => 142; -use Test::More 'no_plan'; +use Test::More; use App::Sqitch; use Path::Class qw(dir); use Test::Exception; @@ -110,7 +109,7 @@ is $target->username, $uri->user, 'Username should be from URI'; is $target->password, $uri->password, 'Password should be from URI'; # Set up a config. -CONFIG: { +CONSTRUCTOR: { my $mock = Test::MockModule->new('App::Sqitch::Config'); my @get_params; my @get_ret; @@ -204,4 +203,146 @@ CONFIG: { )]], 'Should have warned on deprecated options'; } +CONFIG: { + # Look at how attributes are populated from options, config. + my $opts = { engine => 'pg' }; + my $sqitch = App::Sqitch->new(options => $opts ); + + # Mock config. + my $mock = Test::MockModule->new('App::Sqitch::Config'); + my %config; + $mock->mock(get => sub { $config{$_[2]} }); + + # Start with core config. + %config = ( + 'core.registry' => 'myreg', + 'core.client' => 'pgsql', + 'core.plan_file' => 'my.plan', + 'core.top_dir' => 'top', + 'core.deploy_dir' => 'dep', + 'core.revert_dir' => 'rev', + 'core.verify_dir' => 'ver', + 'core.extension' => 'ddl', + ); + my $target = $CLASS->new( + sqitch => $sqitch, + name => 'foo', + uri => URI::db->new('db:pg:foo'), + ); + + is $target->registry, 'myreg', 'Registry should be "myreg"'; + is $target->client, 'pgsql', 'Client should be "pgsql"'; + is $target->plan_file, 'my.plan', 'Plan file should be "my.plan"'; + isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; + isa_ok my $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; + is $plan->file, $target->plan_file, 'Plan should use target plan file'; + is $target->top_dir, 'top', 'Top dir should be "top"'; + isa_ok $target->top_dir, 'Path::Class::Dir', 'Top dir'; + is $target->deploy_dir, 'dep', 'Deploy dir should be "dep"'; + isa_ok $target->deploy_dir, 'Path::Class::Dir', 'Deploy dir'; + is $target->revert_dir, 'rev', 'Revert dir should be "rev"'; + isa_ok $target->revert_dir, 'Path::Class::Dir', 'Revert dir'; + is $target->verify_dir, 'ver', 'Verify dir should be "ver"'; + isa_ok $target->verify_dir, 'Path::Class::Dir', 'Verify dir'; + is $target->extension, 'ddl', 'Extension should be "ddl"'; + + # Add engine config. + $config{'core.pg.registry'} = 'yoreg'; + $config{'core.pg.client'} = 'mycli'; + $config{'core.pg.plan_file'} = 'pg.plan'; + $config{'core.pg.top_dir'} = 'pg'; + $config{'core.pg.deploy_dir'} = 'pgdep'; + $config{'core.pg.revert_dir'} = 'pgrev'; + $config{'core.pg.verify_dir'} = 'pgver'; + $config{'core.pg.extension'} = 'pgddl'; + $target = $CLASS->new( + sqitch => $sqitch, + name => 'foo', + uri => URI::db->new('db:pg:foo'), + ); + + is $target->registry, 'yoreg', 'Registry should be "yoreg"'; + is $target->client, 'mycli', 'Client should be "mycli"'; + is $target->plan_file, 'pg.plan', 'Plan file should be "pg.plan"'; + isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; + isa_ok my $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; + is $plan->file, $target->plan_file, 'Plan should use target plan file'; + is $target->top_dir, 'pg', 'Top dir should be "pg"'; + isa_ok $target->top_dir, 'Path::Class::Dir', 'Top dir'; + is $target->deploy_dir, 'pgdep', 'Deploy dir should be "pgdep"'; + isa_ok $target->deploy_dir, 'Path::Class::Dir', 'Deploy dir'; + is $target->revert_dir, 'pgrev', 'Revert dir should be "pgrev"'; + isa_ok $target->revert_dir, 'Path::Class::Dir', 'Revert dir'; + is $target->verify_dir, 'pgver', 'Verify dir should be "pgver"'; + isa_ok $target->verify_dir, 'Path::Class::Dir', 'Verify dir'; + is $target->extension, 'pgddl', 'Extension should be "pgddl"'; + + # Add target config. + $config{'target.foo.registry'} = 'fooreg'; + $config{'target.foo.client'} = 'foocli'; + $config{'target.foo.plan_file'} = 'foo.plan'; + $config{'target.foo.top_dir'} = 'foo'; + $config{'target.foo.deploy_dir'} = 'foodep'; + $config{'target.foo.revert_dir'} = 'foorev'; + $config{'target.foo.verify_dir'} = 'foover'; + $config{'target.foo.extension'} = 'fooddl'; + $target = $CLASS->new( + sqitch => $sqitch, + name => 'foo', + uri => URI::db->new('db:pg:foo'), + ); + + is $target->registry, 'fooreg', 'Registry should be "fooreg"'; + is $target->client, 'foocli', 'Client should be "foocli"'; + is $target->plan_file, 'foo.plan', 'Plan file should be "foo.plan"'; + isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; + isa_ok my $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; + is $plan->file, $target->plan_file, 'Plan should use target plan file'; + is $target->top_dir, 'foo', 'Top dir should be "foo"'; + isa_ok $target->top_dir, 'Path::Class::Dir', 'Top dir'; + is $target->deploy_dir, 'foodep', 'Deploy dir should be "foodep"'; + isa_ok $target->deploy_dir, 'Path::Class::Dir', 'Deploy dir'; + is $target->revert_dir, 'foorev', 'Revert dir should be "foorev"'; + isa_ok $target->revert_dir, 'Path::Class::Dir', 'Revert dir'; + is $target->verify_dir, 'foover', 'Verify dir should be "foover"'; + isa_ok $target->verify_dir, 'Path::Class::Dir', 'Verify dir'; + is $target->extension, 'fooddl', 'Extension should be "fooddl"'; + + # Add command-line options. + $opts->{registry} = 'optreg'; + $opts->{client} = 'optcli'; + $opts->{plan_file} = 'opt.plan'; + $opts->{top_dir} = 'top.dir'; + $opts->{deploy_dir} = 'dep.dir'; + $opts->{revert_dir} = 'rev.dir'; + $opts->{verify_dir} = 'ver.dir'; + $opts->{extension} = 'opt'; + $target = $CLASS->new( + sqitch => $sqitch, + name => 'foo', + uri => URI::db->new('db:pg:foo'), + ); + + TODO: { + local $TODO = 'NO --registry option yet'; + is $target->registry, 'optreg', 'Registry should be "optreg"'; + } + + is $target->client, 'optcli', 'Client should be "optcli"'; + is $target->plan_file, 'opt.plan', 'Plan file should be "opt.plan"'; + isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; + isa_ok my $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; + is $plan->file, $target->plan_file, 'Plan should use target plan file'; + is $target->top_dir, 'top.dir', 'Top dir should be "top.dir"'; + isa_ok $target->top_dir, 'Path::Class::Dir', 'Top dir'; + is $target->deploy_dir, 'dep.dir', 'Deploy dir should be "dep.dir"'; + isa_ok $target->deploy_dir, 'Path::Class::Dir', 'Deploy dir'; + is $target->revert_dir, 'rev.dir', 'Revert dir should be "rev.dir"'; + isa_ok $target->revert_dir, 'Path::Class::Dir', 'Revert dir'; + is $target->verify_dir, 'ver.dir', 'Verify dir should be "ver.dir"'; + isa_ok $target->verify_dir, 'Path::Class::Dir', 'Verify dir'; + is $target->extension, 'opt', 'Extension should be "opt"'; +} + +done_testing; From 6ae579d4554fd5ca2c756bb9b777ddd52fb09fe0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 16 Oct 2014 17:11:29 -0700 Subject: [PATCH 10/59] Add --registry option. --- lib/App/Sqitch.pm | 1 + lib/sqitch.pod | 44 +++++++++++++++++++++++++----------------- lib/sqitchcommands.pod | 4 ++-- lib/sqitchusage.pod | 37 ++++++++++++++++++----------------- t/options.t | 2 ++ t/target.t | 6 +++--- 6 files changed, 53 insertions(+), 41 deletions(-) diff --git a/lib/App/Sqitch.pm b/lib/App/Sqitch.pm index b02bde338..ff6810b26 100644 --- a/lib/App/Sqitch.pm +++ b/lib/App/Sqitch.pm @@ -384,6 +384,7 @@ sub _core_opts { return qw( plan-file|f=s engine=s + registry=s db-client=s db-name|d=s db-username|db-user|u=s diff --git a/lib/sqitch.pod b/lib/sqitch.pod index e058f1981..e66292140 100644 --- a/lib/sqitch.pod +++ b/lib/sqitch.pod @@ -200,24 +200,25 @@ User who adds a change to the plan. =head1 Options - -f --plan-file FILE Path to a deployment plan file. - --engine ENGINE Database engine. - --db-client PATH Path to the engine command-line client. - -d --db-name NAME Database name. - -u --db-user USER Database user name. - -h --db-host HOST Database server host name. - -p --db-port PORT Database server port number. - --top-dir DIR Path to directory with plan and scripts. - --deploy-dir DIR Path to directory with deployment scripts. - --revert-dir DIR Path to directory with reversion scripts. - --verify-dir DIR Path to directory with verify scripts. - --extension EXT SQL script file name extension. - --etc-path Print the path to the etc directory and exit. - --quiet Quiet mode with non-error output suppressed. - -v --verbose Increment verbosity. - --version Print the version number and exit. - --help Show a list of commands and exit. - --man Print the introductory documentation and exit. + -f --plan-file FILE Path to a deployment plan file. + --engine ENGINE Database engine. + --registry REGISTRY Registry schema or database. + --db-client PATH Path to the engine command-line client. + -d --db-name NAME Database name. + -u --db-user USER Database user name. + -h --db-host HOST Database server host name. + -p --db-port PORT Database server port number. + --top-dir DIR Path to directory with plan and scripts. + --deploy-dir DIR Path to directory with deployment scripts. + --revert-dir DIR Path to directory with reversion scripts. + --verify-dir DIR Path to directory with verify scripts. + --extension EXT SQL script file name extension. + --etc-path Print the path to the etc directory and exit. + --quiet Quiet mode with non-error output suppressed. + -v --verbose Increment verbosity. + --version Print the version number and exit. + --help Show a list of commands and exit. + --man Print the introductory documentation and exit. =head1 Options Details @@ -253,6 +254,13 @@ The database engine to use. Supported engines include: =back +=item C<--registry> + + sqitch --registry registry + +The name of the Sqitch registry schema or database. Sqitch will store its own +data here. + =item C<--db-client> sqitch --db-client /usr/local/pgsql/bin/psql diff --git a/lib/sqitchcommands.pod b/lib/sqitchcommands.pod index 0d2a13ca0..de279bad4 100644 --- a/lib/sqitchcommands.pod +++ b/lib/sqitchcommands.pod @@ -12,14 +12,14 @@ sqitchcommands - List of common sqitch commands =head1 Usage sqitch [--plan-file ] [--engine ] [--top-dir ] - [--extension ] [--etc-path] [--quiet] [--verbose] [--version] + [--top-dir ] [--extension ] [--registry ] + [--etc-path] [--quiet] [--verbose] [--version] [] [] =head1 Common Commands The most commonly used sqitch commands are: - add Add a new change to the plan bundle Bundle a Sqitch project for distribution checkout Revert, checkout another VCS branch, and re-deploy changes diff --git a/lib/sqitchusage.pod b/lib/sqitchusage.pod index 81c3daaf9..3fa8958d2 100644 --- a/lib/sqitchusage.pod +++ b/lib/sqitchusage.pod @@ -15,21 +15,22 @@ sqitchusage - Sqitch usage statement =head1 Options - -f --plan-file FILE Path to a deployment plan file. - --engine ENGINE Database engine. - --db-client PATH Path to the engine command-line client. - -d --db-name NAME Database name. - -u --db-user USER Database user name. - -h --db-host HOST Database server host name. - -p --db-port PORT Database server port number. - --top-dir DIR Path to directory with plan and scripts. - --deploy-dir DIR Path to directory with deployment scripts. - --revert-dir DIR Path to directory with reversion scripts. - --verify-dir DIR Path to directory with verify scripts. - --extension EXT SQL script file name extension. - --etc-path Print the path to the etc directory and exit. - --quiet Quiet mode with non-error output suppressed. - -v --verbose Increment verbosity. - --version Print the version number and exit. - --help Show a list of commands and exit. - --man Print the introductory documentation and exit. + -f --plan-file FILE Path to a deployment plan file. + --engine ENGINE Database engine. + --registry REGISTRY Registry schema or database. + --db-client PATH Path to the engine command-line client. + -d --db-name NAME Database name. + -u --db-user USER Database user name. + -h --db-host HOST Database server host name. + -p --db-port PORT Database server port number. + --top-dir DIR Path to directory with plan and scripts. + --deploy-dir DIR Path to directory with deployment scripts. + --revert-dir DIR Path to directory with reversion scripts. + --verify-dir DIR Path to directory with verify scripts. + --extension EXT SQL script file name extension. + --etc-path Print the path to the etc directory and exit. + --quiet Quiet mode with non-error output suppressed. + -v --verbose Increment verbosity. + --version Print the version number and exit. + --help Show a list of commands and exit. + --man Print the introductory documentation and exit. diff --git a/t/options.t b/t/options.t index 6c0cff021..17ef0e132 100644 --- a/t/options.t +++ b/t/options.t @@ -95,6 +95,7 @@ HELP: { my $opts = $CLASS->_parse_core_opts([ '--plan-file' => 'plan.txt', '--engine' => 'pg', + '--registry' => 'reg', '--db-client' => 'psql', '--db-name' => 'try', '--db-user' => 'bob', @@ -111,6 +112,7 @@ my $opts = $CLASS->_parse_core_opts([ is_deeply $opts, { 'plan_file' => 'plan.txt', 'engine' => 'pg', + 'registry' => 'reg', 'db_client' => 'psql', 'db_name' => 'try', 'db_username' => 'bob', diff --git a/t/target.t b/t/target.t index 5a4ff8a2a..d6f315af0 100644 --- a/t/target.t +++ b/t/target.t @@ -265,7 +265,7 @@ CONFIG: { is $target->client, 'mycli', 'Client should be "mycli"'; is $target->plan_file, 'pg.plan', 'Plan file should be "pg.plan"'; isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; - isa_ok my $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; + isa_ok $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; is $plan->file, $target->plan_file, 'Plan should use target plan file'; is $target->top_dir, 'pg', 'Top dir should be "pg"'; isa_ok $target->top_dir, 'Path::Class::Dir', 'Top dir'; @@ -296,7 +296,7 @@ CONFIG: { is $target->client, 'foocli', 'Client should be "foocli"'; is $target->plan_file, 'foo.plan', 'Plan file should be "foo.plan"'; isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; - isa_ok my $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; + isa_ok $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; is $plan->file, $target->plan_file, 'Plan should use target plan file'; is $target->top_dir, 'foo', 'Top dir should be "foo"'; isa_ok $target->top_dir, 'Path::Class::Dir', 'Top dir'; @@ -331,7 +331,7 @@ CONFIG: { is $target->client, 'optcli', 'Client should be "optcli"'; is $target->plan_file, 'opt.plan', 'Plan file should be "opt.plan"'; isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; - isa_ok my $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; + isa_ok $plan = $target->plan, 'App::Sqitch::Plan', 'Plan'; is $plan->file, $target->plan_file, 'Plan should use target plan file'; is $target->top_dir, 'top.dir', 'Top dir should be "top.dir"'; isa_ok $target->top_dir, 'Path::Class::Dir', 'Top dir'; From f072968f08eef7524d235072b080ee33cbbb2a2c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 16 Oct 2014 17:12:02 -0700 Subject: [PATCH 11/59] No longer a todo test. --- t/target.t | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/t/target.t b/t/target.t index d6f315af0..8e1c24c92 100644 --- a/t/target.t +++ b/t/target.t @@ -323,11 +323,7 @@ CONFIG: { uri => URI::db->new('db:pg:foo'), ); - TODO: { - local $TODO = 'NO --registry option yet'; - is $target->registry, 'optreg', 'Registry should be "optreg"'; - } - + is $target->registry, 'optreg', 'Registry should be "optreg"'; is $target->client, 'optcli', 'Client should be "optcli"'; is $target->plan_file, 'opt.plan', 'Plan file should be "opt.plan"'; isa_ok $target->plan_file, 'Path::Class::File', 'Plan file'; From 11e78b8ad483fd8dd8b0a6db637a3666dce6d31e Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 17 Oct 2014 17:06:27 -0700 Subject: [PATCH 12/59] Replace engine attributs with target attributes. Core engine and SQLite tests now passing. The requirement that a target object be passed to the engine means things need to be fixed in a lot of places, so expect failures for the next few days as I work though it all. Also: * Change `--db-client` to `--client`. * Make Sqitch a weak ref in Engine. * Fix a typo in the localized duplicate project URI error message. --- lib/App/Sqitch.pm | 4 +- lib/App/Sqitch/Engine.pm | 186 +++--------------------------- lib/App/Sqitch/Engine/firebird.pm | 12 -- lib/App/Sqitch/Engine/sqlite.pm | 8 +- lib/App/Sqitch/Role/DBIEngine.pm | 2 +- lib/App/Sqitch/Target.pm | 2 +- lib/App/Sqitch/Types.pm | 6 + lib/sqitch-init.pod | 16 +-- lib/sqitch.pod | 4 +- lib/sqitchusage.pod | 2 +- t/base.t | 2 - t/engine.t | 153 ++++++++++++++---------- t/init.t | 4 +- t/lib/DBIEngineTest.pm | 23 +++- t/options.t | 4 +- t/sqlite.t | 141 +++++++++++----------- t/target.t | 2 +- 17 files changed, 224 insertions(+), 347 deletions(-) diff --git a/lib/App/Sqitch.pm b/lib/App/Sqitch.pm index ff6810b26..31774888d 100644 --- a/lib/App/Sqitch.pm +++ b/lib/App/Sqitch.pm @@ -143,7 +143,7 @@ sub engine_for_target { } # Attributes useful to engines; no defaults. -has db_client => ( is => 'ro', isa => Str ); +has client => ( is => 'ro', isa => Str ); has db_name => ( is => 'ro', isa => Str ); has db_username => ( is => 'ro', isa => Str ); has db_host => ( is => 'ro', isa => Str ); @@ -385,7 +385,7 @@ sub _core_opts { plan-file|f=s engine=s registry=s - db-client=s + client|db-client=s db-name|d=s db-username|db-user|u=s db-host|h=s diff --git a/lib/App/Sqitch/Engine.pm b/lib/App/Sqitch/Engine.pm index 3948283ca..ef66eca1f 100644 --- a/lib/App/Sqitch/Engine.pm +++ b/lib/App/Sqitch/Engine.pm @@ -9,7 +9,7 @@ use Locale::TextDomain qw(App-Sqitch); use App::Sqitch::X qw(hurl); use List::Util qw(first max); use URI::db 0.15; -use App::Sqitch::Types qw(Str Int Sqitch Plan Bool HashRef URI Maybe); +use App::Sqitch::Types qw(Str Int Sqitch Plan Bool HashRef URI Maybe Target); use namespace::autoclean; our $VERSION = '0.997'; @@ -18,81 +18,23 @@ has sqitch => ( is => 'ro', isa => Sqitch, required => 1, + weak_ref => 1, ); -has client => ( - is => 'ro', - isa => Str, - lazy => 1, - default => sub { - my $self = shift; - my $sqitch = $self->sqitch; - my $engine = $self->key; - my $config = $self->sqitch->config; - - # Command-line option takes precedence. - if (my $client = $sqitch->db_client) { - return $client; - } - - # Next look for it in the target config. - if (my $target = $self->target) { - if (my $cli = $config->get( key => "target.$target.client" )) { - return $cli; - } - } - - # Next look for it in the engine config. - if ( my $client = $config->get( key => "core.$engine.client" ) ) { - return $client; - } - - # Otherwise, go with the default. - my $client = $self->default_client; - return $client if $^O ne 'MSWin32'; - return $client if $client =~ /[.](?:exe|bat)$/; - return $client . '.exe'; - }, -); - -has _target_set => (is => 'rw'); - has target => ( - is => 'ro', - isa => Str, - lazy => 1, - trigger => sub { shift->_target_set(1) }, # Excludes default and built values. - default => sub { - my $self = shift; - my $engine = $self->key; - return $self->sqitch->config->get( key => "core.$engine.target") - || $self->uri->as_string; - } -); - -has destination => ( - is => 'ro', - isa => Str, - lazy => 1, - default => sub { - my $self = shift; - - # Just use the target unless it looks like a URI. - my $target = $self->target; - return $target if $target !~ /:/; - - # Use the URI sans password. - my $uri = $self->uri; - if ($uri->password) { - $uri = $uri->clone; - $uri->password(undef); - } - return $uri->as_string; + is => 'ro', + isa => Target, + required => 1, + weak_ref => 1, + handles => { + uri => 'uri', + client => 'client', + registry => 'registry', + destination => 'name', + registry_destination => 'name', } ); -sub registry_destination { shift->destination } - has start_at => ( is => 'rw', isa => Str @@ -139,7 +81,7 @@ has plan => ( is => 'rw', isa => Plan, lazy => 1, - default => sub { shift->sqitch->plan } + default => sub { shift->target->plan } ); has _variables => ( @@ -152,112 +94,16 @@ sub variables { %{ shift->_variables } } sub set_variables { shift->_variables({ @_ }) } sub clear_variables { %{ shift->_variables } = () } -# * If not passed -# a. Look for core.$engine.target; or -# b. Construct from config.$engine.@parts (deprecated); or -# c. Default to "db:$engine:" -# * If command-line-options, override parts in the URI. - -sub BUILD { - my ($self, $args) = @_; - if (my $uri = $args->{uri}) { - $self->_merge_options_into($uri); - } -} - -has uri => ( is => 'ro', isa => URI, lazy => 1, default => sub { - my $self = shift; - my $sqitch = $self->sqitch; - my $config = $sqitch->config; - my $engine = $self->key; - my $uri; - - # Get the target, but only if it has been passed, not the default, - # because the default may call back into uri for an infinite loop! - my $target = $self->_target_set - ? $self->target : $config->get( key => "core.$engine.target" ); - - if ($target) { - $uri = $sqitch->config_for_target_strict($target)->{uri}; - } elsif ( my $config_uri = $config->get( key => "core.$engine.uri" ) ) { - $uri = URI::db->new($config_uri); - } else { - $uri = URI::db->new("db:$engine:"); - - # XXX Deprecated use of other config variables. - for my $spec ( - [ username => 'user' ], - [ password => 'password' ], - [ db_name => 'dbname' ], - [ host => 'host' ], - [ port => 'port' ], - ) { - my ($key, $meth) = @{ $spec }; - my $val = $config->get( key => "core.$engine.$key" ) or next; - $uri->$meth($val); - } - } - - return $self->_merge_options_into($uri); -}); - -has registry => ( - is => 'ro', - isa => Maybe[Str], # May be undef in a subclass. - lazy => 1, - default => sub { - my $self = shift; - my $engine = $self->key; - my $config = $self->sqitch->config; - - if (my $target = $self->target) { - if (my $reg = $config->get( key => "target.$target.registry" )) { - return $reg; - } - } - - return $config->get( key => "core.$engine.registry" ) - || $config->get( key => "core.$engine.sqitch_schema" ) # deprecated - || $config->get( key => "core.$engine.sqitch_db" ) # deprecated - || $self->default_registry; - }, -); - sub default_registry { 'sqitch' } -sub _merge_options_into { - my ($self, $uri) = @_; - my $sqitch = $self->sqitch; - - # Override parts with command-line options (deprecate?) - if (my $host = $sqitch->db_host) { - $uri->host($host); - } - - if (my $port = $sqitch->db_port) { - $uri->port($port); - } - - if (my $user = $sqitch->db_username) { - $uri->user($user); - } - - if (my $name = $sqitch->db_name) { - $uri->dbname($name); - } - - return $uri; -} - sub load { my ( $class, $p ) = @_; # We should have an engine param. - my $engine = delete $p->{engine} - or hurl 'Missing "engine" parameter to load()'; + my $target = $p->{target} or hurl 'Missing "target" parameter to load()'; # Load the engine class. - my $pkg = __PACKAGE__ . "::$engine"; + my $pkg = __PACKAGE__ . '::' . $target->engine_key; eval "require $pkg" or hurl "Unable to load $pkg"; return $pkg->new( $p ); } @@ -896,7 +742,7 @@ sub _rollback { $tagged = $tagged ? $tagged->format_name_with_tags : $self->start_at; $sqitch->vent( $tagged ? __x('Reverting to {change}', change => $tagged) - : __ 'Reverting all changes' + : __ 'Reverting all changes' ); try { diff --git a/lib/App/Sqitch/Engine/firebird.pm b/lib/App/Sqitch/Engine/firebird.pm index c12ba0576..3a3cb1a44 100644 --- a/lib/App/Sqitch/Engine/firebird.pm +++ b/lib/App/Sqitch/Engine/firebird.pm @@ -895,18 +895,6 @@ App::Sqitch::Engine::firebird provides the Firebird storage engine for Sqitch. =head1 Interface -=head2 Accessors - -=head3 C - -Returns the path to the Firebird client. If C<--db-client> was passed to -C, that's what will be returned. Otherwise, it uses the -C configuration value, or else defaults to C, -C, or C, whichever appears first in the path and appears to be -Firebird interactive SQL utility. The value will end in C<.exe> on Windows. An -exception will be thrown if none of these can be found in the path, or if none -look like the Firebird interactive SQL utility. - =head2 Instance Methods =head3 C diff --git a/lib/App/Sqitch/Engine/sqlite.pm b/lib/App/Sqitch/Engine/sqlite.pm index 792b76466..8411ee3dc 100644 --- a/lib/App/Sqitch/Engine/sqlite.pm +++ b/lib/App/Sqitch/Engine/sqlite.pm @@ -269,10 +269,10 @@ App::Sqitch::Engine::sqlite provides the SQLite storage engine for Sqitch. =head3 C -Returns the path to the SQLite client. If C<--db-client> was passed to -C, that's what will be returned. Otherwise, it uses the -C configuration value, or else defaults to C (or -C on Windows), which should work if it's in your path. +Returns the path to the SQLite client. If C<--client> was passed to C, +that's what will be returned. Otherwise, it uses the C +configuration value, or else defaults to C (or C on +Windows), which should work if it's in your path. =head2 Instance Methods diff --git a/lib/App/Sqitch/Role/DBIEngine.pm b/lib/App/Sqitch/Role/DBIEngine.pm index ad8983432..58dd242cb 100644 --- a/lib/App/Sqitch/Role/DBIEngine.pm +++ b/lib/App/Sqitch/Role/DBIEngine.pm @@ -316,7 +316,7 @@ sub register_project { ); hurl engine => __x( - 'Cannot register "{project}" with URI {uri}: project "{reg_prog}" already using that URI', + 'Cannot register "{project}" with URI {uri}: project "{reg_proj}" already using that URI', project => $proj, uri => $uri, reg_proj => $res->[0], diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 0aad2488f..49c4909ba 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -43,7 +43,7 @@ has engine => ( require App::Sqitch::Engine; App::Sqitch::Engine->load({ sqitch => $self->sqitch, - engine => $self->uri->canonical_engine, + target => $self, }); }, ); diff --git a/lib/App/Sqitch/Types.pm b/lib/App/Sqitch/Types.pm index 649c39339..017aa71d1 100644 --- a/lib/App/Sqitch/Types.pm +++ b/lib/App/Sqitch/Types.pm @@ -7,6 +7,7 @@ use utf8; use Type::Library 0.040 -base, -declare => qw( Sqitch Engine + Target UserName UserEmail ConfigBool @@ -37,6 +38,7 @@ BEGIN { extends "Types::Standard" }; class_type Sqitch, { class => 'App::Sqitch' }; class_type Engine, { class => 'App::Sqitch::Engine' }; +class_type Target, { class => 'App::Sqitch::Target' }; class_type Plan, { class => 'App::Sqitch::Plan' }; class_type Change, { class => 'App::Sqitch::Plan::Change' }; class_type ChangeList, { class => 'App::Sqitch::Plan::ChangeList' }; @@ -101,6 +103,10 @@ An L object. An L object. +=item C + +An L object. + =item C A Sqitch user name. diff --git a/lib/sqitch-init.pod b/lib/sqitch-init.pod index 109b92848..c4877fcd8 100644 --- a/lib/sqitch-init.pod +++ b/lib/sqitch-init.pod @@ -79,11 +79,11 @@ their default values. If no defaults are specified, they will still be written, commented out, with a bar C<=> and no value. This allows one to know what sorts of things are available to edit. -Relatedly, one engine-specific L option, C<--db-client> will also be -used to write the engine-specific variable C. Again, if -this option is not specified, the engine-specific option will still be written -out, but as a comment with the user or system configuration value, or a -default value. +Relatedly, one engine-specific L option, C<--client> will also be used +to write the engine-specific variable C. Again, if this +option is not specified, the engine-specific option will still be written out, +but as a comment with the user or system configuration value, or a default +value. =head1 Examples @@ -97,9 +97,9 @@ database name "widgets", the default user name "postgres", and a version-specific client: sqitch --engine pg \ - --db-name widgets \ - --db-user postgres \ - --db-client /opt/pgsql-9.1/bin/psql init + --db-name widgets \ + --db-user postgres \ + --client /opt/pgsql-9.1/bin/psql init =head1 Sqitch diff --git a/lib/sqitch.pod b/lib/sqitch.pod index e66292140..ebb6b83ce 100644 --- a/lib/sqitch.pod +++ b/lib/sqitch.pod @@ -261,9 +261,11 @@ The database engine to use. Supported engines include: The name of the Sqitch registry schema or database. Sqitch will store its own data here. +=item C<--client> + =item C<--db-client> - sqitch --db-client /usr/local/pgsql/bin/psql + sqitch --client /usr/local/pgsql/bin/psql Path to the command-line client for the database engine. Defaults to a client in the current path named appropriately for the specified engine. diff --git a/lib/sqitchusage.pod b/lib/sqitchusage.pod index 3fa8958d2..f1d118ea2 100644 --- a/lib/sqitchusage.pod +++ b/lib/sqitchusage.pod @@ -18,7 +18,7 @@ sqitchusage - Sqitch usage statement -f --plan-file FILE Path to a deployment plan file. --engine ENGINE Database engine. --registry REGISTRY Registry schema or database. - --db-client PATH Path to the engine command-line client. + --client PATH Path to the engine command-line client. -d --db-name NAME Database name. -u --db-user USER Database user name. -h --db-host HOST Database server host name. diff --git a/t/base.t b/t/base.t index 43afcd7bb..1c6115bcb 100644 --- a/t/base.t +++ b/t/base.t @@ -32,7 +32,6 @@ can_ok $CLASS, qw( _engine user_name user_email - db_client db_name db_username db_host @@ -52,7 +51,6 @@ can_ok $CLASS, qw( isa_ok my $sqitch = $CLASS->new, $CLASS, 'A new object'; for my $attr (qw( - db_client db_username db_name db_host diff --git a/t/engine.t b/t/engine.t index 58a6e143c..6ba7eba5a 100644 --- a/t/engine.t +++ b/t/engine.t @@ -4,10 +4,11 @@ use strict; use warnings; use 5.010; use utf8; -use Test::More tests => 598; +use Test::More tests => 595; #use Test::More 'no_plan'; use App::Sqitch; use App::Sqitch::Plan; +use App::Sqitch::Target; use Path::Class; use Test::Exception; use Test::NoWarnings; @@ -91,59 +92,85 @@ ENGINE: { sub name_for_change_id { return 'bugaboo' } } +# XXX Remove attributes in favor of options. ok my $sqitch = App::Sqitch->new( _engine => 'sqlite', db_name => 'mydb', top_dir => dir( qw(t sql) ), - plan_file => file qw(t plans multi.plan) + plan_file => file( qw(t plans multi.plan) ), + options => { + engine => 'sqlite', + db_name => 'mydb', + top_dir => dir(qw(t sql))->stringify, + plan_file => file(qw(t plans multi.plan))->stringify, + } ), 'Load a sqitch sqitch object'; my $mock_engine = Test::MockModule->new($CLASS); ############################################################################## # Test new(). -throws_ok { $CLASS->new } +my $target = App::Sqitch::Target->new( sqitch => $sqitch ); +throws_ok { $CLASS->new( sqitch => $sqitch ) } + qr/\QMissing required arguments: target/, + 'Should get an exception for missing sqitch param'; +throws_ok { $CLASS->new( target => $target ) } qr/\QMissing required arguments: sqitch/, 'Should get an exception for missing sqitch param'; my $array = []; -throws_ok { $CLASS->new({ sqitch => $array }) } +throws_ok { $CLASS->new({ sqitch => $array, target => $target }) } qr/\QReference [] did not pass type constraint "Sqitch"/, 'Should get an exception for array sqitch param'; -throws_ok { $CLASS->new({ sqitch => 'foo' }) } +throws_ok { $CLASS->new({ sqitch => $sqitch, target => $array }) } + qr/\QReference [] did not pass type constraint "Target"/, + 'Should get an exception for array target param'; +throws_ok { $CLASS->new({ sqitch => 'foo', target => $target }) } qr/\QValue "foo" did not pass type constraint "Sqitch"/, 'Should get an exception for string sqitch param'; +throws_ok { $CLASS->new({ sqitch => $sqitch, target => 'foo' }) } + qr/\QValue "foo" did not pass type constraint "Target"/, + 'Should get an exception for string target param'; -isa_ok $CLASS->new({sqitch => $sqitch}), $CLASS; +isa_ok $CLASS->new({sqitch => $sqitch, target => $target}), $CLASS, 'Engine'; ############################################################################## # Test load(). +$sqitch->options->{engine} = 'whu'; +$target = App::Sqitch::Target->new( sqitch => $sqitch ); ok my $engine = $CLASS->load({ sqitch => $sqitch, - engine => 'whu', -}), 'Load a "whu" engine'; + target => $target, +}), 'Load an engine'; isa_ok $engine, 'App::Sqitch::Engine::whu'; is $engine->sqitch, $sqitch, 'The sqitch attribute should be set'; # Test handling of an invalid engine. -throws_ok { $CLASS->load({ engine => 'nonexistent', sqitch => $sqitch }) } - 'App::Sqitch::X', 'Should die on invalid engine'; +my $unknown_target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI::db->new('db:nonexistent:') +); +throws_ok { $CLASS->load({ sqitch => $sqitch, target => $unknown_target }) } + 'App::Sqitch::X', 'Should die on unknown target'; is $@->message, 'Unable to load App::Sqitch::Engine::nonexistent', 'Should get load error message'; like $@->previous_exception, qr/\QCan't locate/, 'Should have relevant previoius exception'; NOENGINE: { - # Test handling of no engine. - throws_ok { $CLASS->load({ engine => '', sqitch => $sqitch }) } - 'App::Sqitch::X', - 'No engine should die'; - is $@->message, 'Missing "engine" parameter to load()', + # Test handling of no target. + throws_ok { $CLASS->load({ sqitch => $sqitch }) } 'App::Sqitch::X', + 'No target should die'; + is $@->message, 'Missing "target" parameter to load()', 'It should be the expected message'; } # Test handling a bad engine implementation. use lib 't/lib'; -throws_ok { $CLASS->load({ engine => 'bad', sqitch => $sqitch }) } +my $bad_target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI::db->new('db:bad:') +); +throws_ok { $CLASS->load({ sqitch => $sqitch, target => $bad_target }) } 'App::Sqitch::X', 'Should die on bad engine module'; is $@->message, 'Unable to load App::Sqitch::Engine::bad', 'Should get another load error message'; @@ -154,14 +181,15 @@ like $@->previous_exception, qr/^LOL BADZ/, ############################################################################## # Test name. can_ok $CLASS, 'name'; -ok $engine = $CLASS->new({ sqitch => $sqitch }), "Create a $CLASS object"; +ok $engine = $CLASS->new({ sqitch => $sqitch, target => $target }), + "Create a $CLASS object"; throws_ok { $engine->name } 'App::Sqitch::X', 'Should get error from base engine name'; is $@->ident, 'engine', 'Name error ident should be "engine"'; is $@->message, __('No engine specified; use --engine or set core.engine'), 'Name error message should be correct'; -ok $engine = App::Sqitch::Engine::whu->new({sqitch => $sqitch}), +ok $engine = App::Sqitch::Engine::whu->new({sqitch => $sqitch, target => $target}), 'Create a subclass name object'; is $engine->name, 'whu', 'Subclass oject name should be "whu"'; is +App::Sqitch::Engine::whu->name, 'whu', 'Subclass class name should be "whu"'; @@ -192,67 +220,48 @@ is_deeply [$engine->variables], [], 'Should again have no variables'; # Test target. ok $engine = $CLASS->load({ sqitch => $sqitch, - engine => 'whu', - target => 'foo', -}), 'Load engine'; -is $engine->target, 'foo', 'Target should be as passed'; - -ok $engine = $CLASS->load({ - sqitch => $sqitch, - engine => 'whu', + target => $target, }), 'Load engine'; -is $engine->target, 'db:whu:mydb', 'Target should be URI string'; +is $engine->target, $target, 'Target should be as passed'; # Make sure password is removed from the target. ok $engine = $CLASS->load({ sqitch => $sqitch, - engine => 'whu', + target => $target, uri => URI->new('db:whu://foo:bar@localhost/blah'), }), 'Load engine with URI with password'; -is $engine->target, $engine->uri->as_string, - 'Target should be the URI stringified'; - -# Try a target in the configuration. -MOCKCONFIG: { - local $ENV{SQITCH_CONFIG} = file qw(t local.conf); - ok my $engine = $CLASS->load({ - sqitch => App::Sqitch->new( _engine => 'sqlite' ), - engine => 'sqlite', - }), 'Load engine'; - is $engine->target, 'devdb', 'Target should be read from config'; - - ok $engine = $CLASS->load({ - sqitch => App::Sqitch->new( _engine => 'sqlite' ), - engine => 'sqlite', - uri => URI->new('db:sqlite:/var/db/widgets.db'), - }), 'Load engine with URI'; - is $engine->target, 'devdb', 'Target should still be "devdb"'; -} +isa_ok $engine->target, 'App::Sqitch::Target', 'target attribute'; ############################################################################## # Test destination. ok $engine = $CLASS->load({ sqitch => $sqitch, - engine => 'whu', + target => $target, }), 'Load engine'; is $engine->destination, 'db:whu:mydb', 'Destination should be URI string'; is $engine->registry_destination, $engine->destination, 'Rgistry destination should be the same as destination'; # Make sure password is removed from the destination. +my $long_target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI->new('db:whu://foo:bar@localhost/blah'), +); ok $engine = $CLASS->load({ sqitch => $sqitch, - engine => 'whu', - uri => URI->new('db:whu://foo:bar@localhost/blah'), + target => $long_target, }), 'Load engine with URI with password'; -like $engine->destination, qr{^db:whu://foo:?\@localhost/mydb$}, +like $engine->destination, qr{^db:whu://foo:?\@localhost/blah$}, 'Destination should not include password'; is $engine->registry_destination, $engine->destination, 'Meta destination should again be the same as destination'; ############################################################################## # Test abstract methods. -ok $engine = $CLASS->new({ sqitch => $sqitch }), "Create a $CLASS object again"; +ok $engine = $CLASS->new({ + sqitch => $sqitch, + target => $target, +}), "Create a $CLASS object again"; for my $abs (qw( initialized initialize @@ -289,7 +298,7 @@ for my $abs (qw( # Test _load_changes(). can_ok $engine, '_load_changes'; my $now = App::Sqitch::DateTime->now; -my $plan = $sqitch->plan; +my $plan = $target->plan; # Mock App::Sqitch::DateTime so that dbchange tags all have the same # timestamps. @@ -585,11 +594,11 @@ for my $spec ( ############################################################################## # Test deploy_change and revert_change. -ok $engine = App::Sqitch::Engine::whu->new( sqitch => $sqitch ), +ok $engine = App::Sqitch::Engine::whu->new( sqitch => $sqitch, target => $target ), 'Create a subclass name object again'; can_ok $engine, 'deploy_change', 'revert_change'; -my $change = App::Sqitch::Plan::Change->new( name => 'users', plan => $sqitch->plan ); +my $change = App::Sqitch::Plan::Change->new( name => 'users', plan => $target->plan ); $engine->max_name_length(length $change->format_name_with_tags); ok $engine->deploy_change($change), 'Deploy a change'; @@ -767,10 +776,21 @@ $record_work = 0; chdir 't'; my $plan_file = file qw(sql sqitch.plan); my $sqitch_old = $sqitch; # Hang on to this because $change does not retain it. -$sqitch = App::Sqitch->new( _engine => 'sqlite', plan_file => $plan_file, top_dir => dir 'sql' ); -ok $engine = App::Sqitch::Engine::whu->new( sqitch => $sqitch ), +# XXX Remove attributes in favor of options. +$sqitch = App::Sqitch->new( + _engine => 'sqlite', + plan_file => $plan_file, + top_dir => dir('sql'), + options => { + engine => 'sqlite', + plan_file => $plan_file->stringify, + top_dir => 'sql', + }, +); +$target = App::Sqitch::Target->new( sqitch => $sqitch ); +ok $engine = App::Sqitch::Engine::whu->new( sqitch => $sqitch, target => $target ), 'Engine with sqitch with plan file'; -$plan = $sqitch->plan; +$plan = $target->plan; my @changes = $plan->changes; $latest_change_id = $changes[0]->id; @@ -1059,9 +1079,19 @@ NOSTEPS: { say $fh '%project=empty'; $fh->close or die "Error closing $plan_file: $!"; END { $plan_file->remove } - my $sqitch = App::Sqitch->new( _engine => 'sqlite', plan_file => $plan_file ); - ok $engine = App::Sqitch::Engine::whu->new( sqitch => $sqitch ), - 'Engine with sqitch with no file'; + my $sqitch = App::Sqitch->new( + _engine => 'sqlite', + plan_file => $plan_file, + options => { + engine => 'sqlite', + plan_file => $plan_file->stringify, + } + ); + my $target = App::Sqitch::Target->new(sqitch => $sqitch ); + ok my $engine = App::Sqitch::Engine::whu->new( + sqitch => $sqitch, + target => $target, + ), 'Engine with sqitch with no file'; $engine->max_name_length(10); throws_ok { $engine->deploy } 'App::Sqitch::X', 'Should die with no changes'; is $@->message, __"Nothing to deploy (empty plan)", @@ -1073,6 +1103,7 @@ NOSTEPS: { ############################################################################## # Test _deploy_by_change() +$engine = App::Sqitch::Engine::whu->new(sqitch => $sqitch, target => $target); $plan->reset; $mock_engine->unmock('_deploy_by_change'); $engine->max_name_length( diff --git a/t/init.t b/t/init.t index 11bc31c68..7a88de1a0 100644 --- a/t/init.t +++ b/t/init.t @@ -275,7 +275,7 @@ is_deeply read_config $conf_file, { unlink $conf_file; $sqitch = App::Sqitch->new( _engine => 'sqlite', - db_client => '/to/sqlite3', + client => '/to/sqlite3', db_name => 'my.db', ); @@ -349,7 +349,7 @@ USERCONF: { unlink $conf_file; $sqitch = App::Sqitch->new( _engine => 'pg', - db_client => '/to/psql', + client => '/to/psql', db_name => 'thingies', db_username => 'anna', db_host => 'banana', diff --git a/t/lib/DBIEngineTest.pm b/t/lib/DBIEngineTest.pm index 905222af2..2b08a4d74 100644 --- a/t/lib/DBIEngineTest.pm +++ b/t/lib/DBIEngineTest.pm @@ -46,8 +46,15 @@ sub run { user_name => $user1_name, user_email => $user1_email, ); - - my $engine = $class->new(sqitch => $sqitch, @{ $p{engine_params} || [] }); + my $target = App::Sqitch::Target->new( + sqitch => $sqitch, + @{ $p{target_params} || [] }, + ); + my $engine = $class->new( + sqitch => $sqitch, + target => $target, + @{ $p{engine_params} || [] }, + ); if (my $code = $p{skip_unless}) { try { $code->( $engine ) || die 'NO'; @@ -67,8 +74,13 @@ sub run { ok $engine->initialized, 'Database should now be initialized'; # Try it with a different Sqitch DB. + $target = App::Sqitch::Target->new( + sqitch => $sqitch, + @{ $p{alt_target_params} || [] }, + ); ok $engine = $class->new( sqitch => $sqitch, + target => $target, @{ $p{alt_engine_params} || [] }, ), 'Create engine with alternate params'; @@ -206,7 +218,7 @@ sub run { 'Should get error for an project with the URI'; is $@->ident, 'engine', 'Existing URI error ident should be "engine"'; is $@->message, __x( - 'Cannot register "{project}" with URI {uri}: project "{reg_prog}" already using that URI', + 'Cannot register "{project}" with URI {uri}: project "{reg_proj}" already using that URI', project => $plan_proj, uri => $plan_uri, reg_proj => 'groovy', @@ -215,7 +227,7 @@ sub run { ###################################################################### # Test log_deploy_change(). - my $plan = $sqitch->plan; + my $plan = $target->plan; my $change = $plan->change_at(0); my ($tag) = $change->tags; is $change->name, 'users', 'Should have "users" change'; @@ -1024,7 +1036,8 @@ sub run { # Add an external project event. ok my $ext_plan = App::Sqitch::Plan->new( - sqitch => $sqitch, + sqitch => $sqitch, + file => $target->plan_file, project => 'groovy', ), 'Create external plan'; ok my $ext_change = $ext_plan->add( diff --git a/t/options.t b/t/options.t index 17ef0e132..ca3950be2 100644 --- a/t/options.t +++ b/t/options.t @@ -96,7 +96,7 @@ my $opts = $CLASS->_parse_core_opts([ '--plan-file' => 'plan.txt', '--engine' => 'pg', '--registry' => 'reg', - '--db-client' => 'psql', + '--client' => 'psql', '--db-name' => 'try', '--db-user' => 'bob', '--db-host' => 'local', @@ -113,7 +113,7 @@ is_deeply $opts, { 'plan_file' => 'plan.txt', 'engine' => 'pg', 'registry' => 'reg', - 'db_client' => 'psql', + 'client' => 'psql', 'db_name' => 'try', 'db_username' => 'bob', 'db_host' => 'local', diff --git a/t/sqlite.t b/t/sqlite.t index 698b01b7e..5077bcb8e 100644 --- a/t/sqlite.t +++ b/t/sqlite.t @@ -5,6 +5,7 @@ use warnings; use 5.010; use Test::More; use App::Sqitch; +use App::Sqitch::Target; use Test::MockModule; use Path::Class; use Try::Tiny; @@ -31,30 +32,21 @@ is_deeply [$CLASS->config_vars], [ ], 'config_vars should return three vars'; my $sqitch = App::Sqitch->new( _engine => 'sqlite', db_name => 'foo.db' ); -isa_ok my $sqlite = $CLASS->new(sqitch => $sqitch), $CLASS; +my $target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI->new('db:sqlite:foo.db'), +); +isa_ok my $sqlite = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; is $sqlite->client, 'sqlite3' . ($^O eq 'MSWin32' ? '.exe' : ''), 'client should default to sqlite3'; is $sqlite->uri->dbname, file('foo.db'), 'dbname should be filled in'; -is $sqlite->target, $sqlite->uri->as_string, - 'Target should be uri stringified'; +is $sqlite->target, $target, 'Target attribute should be specified target'; is $sqlite->destination, $sqlite->uri->as_string, 'Destination should be uri stringified'; is $sqlite->registry_destination, $sqlite->registry_uri->as_string, 'Meta target should be registry_uri stringified'; -# Make sure the password is suppressed in the destination, should anyone be -# silly enough to include on in an SQLite URI. -isa_ok $sqlite = $CLASS->new( - sqitch => $sqitch, - uri => URI->new('db:sqlite://foo:bar@localhost/my.db'), -), $CLASS; -is $sqlite->target, $sqlite->uri->as_string, 'Target should be the URI stringified'; -like $sqlite->destination, qr{^db:sqlite://foo:?\@localhost/foo.db$}, - 'Destination should exclude password'; -like $sqlite->registry_destination, qr{^db:sqlite://foo:?\@localhost/sqitch\.db$}, - 'Registry destination should also exclude password'; - # Pretend for now that we always have a valid SQLite. my $mock_sqitch = Test::MockModule->new(ref $sqitch); my $sqlite_version = '3.7.12 2012-04-03 19:43:07 86b8481be7e76cccc92d14ce762d21bfb69504af'; @@ -70,21 +62,11 @@ my @std_opts = ( is_deeply [$sqlite->sqlite3], [$sqlite->client, @std_opts, $sqlite->uri->dbname], 'sqlite3 command should have the proper opts'; -############################################################################## -# Make sure we get an error for no database name. -throws_ok { - $CLASS->new(sqitch => App::Sqitch->new( _engine => 'sqlite' ))->sqlite3 -} 'App::Sqitch::X', 'Should get an error for no db name'; -is $@->ident, 'sqlite', 'Missing db name error ident should be "sqlite"'; -is $@->message, - __x('Database name missing in URI {uri}', uri => 'db:sqlite:'), - 'Missing db name error message should be correct'; - ############################################################################## # Make sure we get an error for no database name. my $tmp_dir = Path::Class::dir( tempdir CLEANUP => 1 ); my $have_sqlite = try { $sqlite->use_driver }; -$sqitch = App::Sqitch->new( _engine => 'sqlite' ); +$sqitch = App::Sqitch->new( _engine => 'sqlite', options => {engine => 'sqlite'} ); if ($have_sqlite) { # We have DBD::SQLite. # Find out if it's built with SQLite >= 3.7.11. @@ -95,7 +77,7 @@ if ($have_sqlite) { # We have DBD::SQLite, but it is too old. Make sure we complain about that. isa_ok $sqlite = $CLASS->new( sqitch => $sqitch, - registry => '_tmp', + target => $target, ), $CLASS; throws_ok { $sqlite->dbh } 'App::Sqitch::X', 'Should get an error for old SQLite'; is $@->ident, 'sqlite', 'Unsupported SQLite error ident should be "sqlite"'; @@ -120,21 +102,22 @@ if ($have_sqlite) { # Make sure config settings override defaults. my %config = ( 'core.sqlite.client' => '/path/to/sqlite3', - 'core.sqlite.db_name' => '/path/to/sqlite.db', + 'core.sqlite.target' => 'test', 'core.sqlite.registry' => 'meta', + 'target.test.uri' => 'db:sqlite:/path/to/sqlite.db', ); my $mock_config = Test::MockModule->new('App::Sqitch::Config'); $mock_config->mock(get => sub { $config{ $_[2] } }); -ok $sqlite = $CLASS->new(sqitch => $sqitch), +$target = ref($target)->new( sqitch => $sqitch ); +ok $sqlite = $CLASS->new(sqitch => $sqitch, target => $target), 'Create another sqlite'; is $sqlite->client, '/path/to/sqlite3', 'client should fall back on config'; is $sqlite->uri->as_string, 'db:sqlite:/path/to/sqlite.db', 'dbname should fall back on config'; -is $sqlite->target, $sqlite->uri->as_string, - 'Target should be configured uri stringified'; -is $sqlite->destination, $sqlite->uri->as_string, - 'Destination should be configured uri stringified'; +is $sqlite->target, $target, 'Target should be as specified'; +is $sqlite->destination, 'test', + 'Destination should be configured target name'; is $sqlite->registry_uri->as_string, 'db:sqlite:/path/to/meta.db', 'registry_uri should fall back on config'; is $sqlite->registry_destination, $sqlite->registry_uri->as_string, @@ -142,17 +125,18 @@ is $sqlite->registry_destination, $sqlite->registry_uri->as_string, # Try a registry with an extension and a dbname without. %config = ( - 'core.sqlite.db_name' => '/path/to/sqitch', 'core.sqlite.registry' => 'meta.db', + 'core.sqlite.target' => 'test', + 'target.test.uri' => 'db:sqlite:/path/to/sqitch', ); -ok $sqlite = $CLASS->new(sqitch => $sqitch), +$target = ref($target)->new( sqitch => $sqitch ); +ok $sqlite = $CLASS->new(sqitch => $sqitch, target => $target), 'Create another sqlite'; is $sqlite->uri->as_string, 'db:sqlite:/path/to/sqitch', 'dbname should fall back on config with no extension'; -is $sqlite->target, $sqlite->uri->as_string, - 'Target should be configured uri stringified'; -is $sqlite->destination, $sqlite->uri->as_string, - 'Destination should be configured uri stringified'; +is $sqlite->target, $target, 'Target should be as specified'; +is $sqlite->destination, 'test', + 'Destination should be configured target name'; is $sqlite->registry_uri->as_string, 'db:sqlite:/path/to/meta.db', 'registry_uri should fall back on config wth extension'; is $sqlite->registry_destination, $sqlite->registry_uri->as_string, @@ -160,17 +144,18 @@ is $sqlite->registry_destination, $sqlite->registry_uri->as_string, # Also try a registry with no extension and a dbname with. %config = ( - 'core.sqlite.db_name' => '/path/to/sqitch.db', 'core.sqlite.registry' => 'registry', + 'core.sqlite.target' => 'noext', + 'target.noext.uri' => 'db:sqlite:/path/to/sqitch.db', ); -ok $sqlite = $CLASS->new(sqitch => $sqitch), +$target = ref($target)->new( sqitch => $sqitch ); +ok $sqlite = $CLASS->new(sqitch => $sqitch, target => $target), 'Create another sqlite'; is $sqlite->uri->as_string, 'db:sqlite:/path/to/sqitch.db', 'dbname should fall back on config with no extension'; -is $sqlite->target, $sqlite->uri->as_string, - 'Target should be configured uri stringified'; -is $sqlite->destination, $sqlite->uri->as_string, - 'Destination should be configured uri stringified'; +is $sqlite->target, $target, 'Target should be as specified'; +is $sqlite->destination, 'noext', + 'Destination should be configured target name'; is $sqlite->registry_uri->as_string, 'db:sqlite:/path/to/registry.db', 'registry_uri should fall back on config wth extension'; is $sqlite->registry_destination, $sqlite->registry_uri->as_string, @@ -178,17 +163,18 @@ is $sqlite->registry_destination, $sqlite->registry_uri->as_string, # Try a registry with an absolute path. %config = ( - 'core.sqlite.db_name' => '/path/to/sqitch.db', 'core.sqlite.registry' => '/some/other/path.db', + 'core.sqlite.target' => 'abs', + 'target.abs.uri' => 'db:sqlite:/path/to/sqitch.db', ); -ok $sqlite = $CLASS->new(sqitch => $sqitch), +$target = ref($target)->new( sqitch => $sqitch ); +ok $sqlite = $CLASS->new(sqitch => $sqitch, target => $target), 'Create another sqlite'; is $sqlite->uri->as_string, 'db:sqlite:/path/to/sqitch.db', 'dbname should fall back on config with no extension'; -is $sqlite->target, $sqlite->uri->as_string, - 'Target should be configured uri stringified'; -is $sqlite->destination, $sqlite->uri->as_string, - 'Destination should be configured uri stringified'; +is $sqlite->target, $target, 'Target should be as specified'; +is $sqlite->destination, 'abs', + 'Destination should be configured target name'; is $sqlite->registry_uri->as_string, 'db:sqlite:/some/other/path.db', 'registry_uri should fall back on config wth extension'; is $sqlite->registry_destination, $sqlite->registry_uri->as_string, @@ -196,16 +182,16 @@ is $sqlite->registry_destination, $sqlite->registry_uri->as_string, ############################################################################## # Now make sure that Sqitch options override configurations. -$sqitch = App::Sqitch->new(_engine => 'sqlite', db_client => 'foo/bar', db_name => 'my.db'); -ok $sqlite = $CLASS->new(sqitch => $sqitch), - 'Create sqlite with sqitch with --client and --db-name'; -is $sqlite->client, 'foo/bar', 'The client should be grabbed from sqitch'; -is $sqlite->uri->as_string, 'db:sqlite:' . file('my.db'), - 'The uri should be grabbed from sqitch'; -is $sqlite->target, $sqlite->uri->as_string, - 'Target should be optioned uri stringified'; -is $sqlite->destination, $sqlite->uri->as_string, - 'Destination should be optioned uri stringified'; +$sqitch = App::Sqitch->new( options => { + engine => 'sqlite', + client => 'foo/bar', + registry => 'reg', +}); +$target = ref($target)->new( sqitch => $sqitch ); +ok $sqlite = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create sqlite with sqitch with --client and --target'; +is $sqlite->client, 'foo/bar', 'The client should be grabbed from --client'; +is $sqlite->registry, 'reg', 'The registry should be grabbed from --registry'; is_deeply [$sqlite->sqlite3], [$sqlite->client, @std_opts, $sqlite->uri->dbname], 'sqlite3 command should have option values'; @@ -216,7 +202,11 @@ $mock_config->unmock_all; # Test _read(). my $db_name = $tmp_dir->file('sqitch.db'); $sqitch = App::Sqitch->new(_engine => 'sqlite'); -ok $sqlite = $CLASS->new(sqitch => $sqitch, uri => URI->new("db:sqlite:$db_name")), +$target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI->new("db:sqlite:$db_name") +); +ok $sqlite = $CLASS->new(sqitch => $sqitch, target => $target ), 'Instantiate with a temporary database file'; can_ok $sqlite, qw(_read); my $quote = $^O eq 'MSWin32' ? sub { $sqitch->quote_shell(shift) } : sub { shift }; @@ -311,7 +301,7 @@ for my $v (qw( $sqlite_version = "$v 2012-04-03 19:43:07 86b8481be7e76cccc92d14ce762d21bfb69504af"; ok my $sqlite = $CLASS->new( sqitch => $sqitch, - uri => URI->new('db:sqlite:foo.db') + target => $target, ), "Create command for v$v"; ok $sqlite->sqlite3, "Should be okay with sqlite v$v"; } @@ -327,7 +317,10 @@ for my $v (qw( 1.0.0 )) { $sqlite_version = "$v 2012-04-03 19:43:07 86b8481be7e76cccc92d14ce762d21bfb69504af"; - ok my $sqlite = $CLASS->new(sqitch => $sqitch), "Create command for v$v"; + ok my $sqlite = $CLASS->new( + sqitch => $sqitch, + target => $target, + ), "Create command for v$v"; throws_ok { $sqlite->sqlite3 } 'App::Sqitch::X', "Should not be okay with v$v"; is $@->ident, 'sqlite', qq{Should get ident "sqlite" for v$v}; is $@->message, __x( @@ -354,17 +347,17 @@ END { DBIEngineTest->run( class => $CLASS, - sqitch_params => [ - top_dir => Path::Class::dir(qw(t engine)), - plan_file => Path::Class::file(qw(t engine sqitch.plan)), - _engine => 'sqlite', - ], - engine_params => [ uri => URI->new("db:sqlite:$db_name") ], - alt_engine_params => [ - uri => URI->new("db:sqlite:$db_name"), - registry => 'sqitchtest', + sqitch_params => [options => { + top_dir => Path::Class::dir(qw(t engine))->stringify, + plan_file => Path::Class::file(qw(t engine sqitch.plan))->stringify, + engine => 'sqlite', + }], + target_params => [ uri => URI->new("db:sqlite:$db_name") ], + alt_target_params => [ + registry => 'sqitchtest', + uri => URI->new("db:sqlite:$db_name"), ], - skip_unless => sub { + skip_unless => sub { my $self = shift; # Should have the database handle and client. diff --git a/t/target.t b/t/target.t index 8e1c24c92..8866bf2e8 100644 --- a/t/target.t +++ b/t/target.t @@ -206,7 +206,7 @@ CONSTRUCTOR: { CONFIG: { # Look at how attributes are populated from options, config. my $opts = { engine => 'pg' }; - my $sqitch = App::Sqitch->new(options => $opts ); + my $sqitch = App::Sqitch->new(options => $opts); # Mock config. my $mock = Test::MockModule->new('App::Sqitch::Config'); From e0e737698934c152dfa3bd2b84c3dd00e0b03dd3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 20 Oct 2014 16:47:09 -0700 Subject: [PATCH 13/59] Get PostgreSQL engine working with Target. Note that the deprecated username, password, host, port, and db_name keys in the core.pg config have been removed, since Target does not support them. Related changes: * `registry_destination` is now a true alias for `destination`, rather than `uri->name`. * Un-deprecated the `--db-*` options. I think they are just too useful and ingrained. I might change my mind again, though. * Fixed Target where it was checking the wrong variable for the presence of a name. --- lib/App/Sqitch/Engine.pm | 11 +++-- lib/App/Sqitch/Engine/pg.pm | 37 +++++++------- lib/App/Sqitch/Target.pm | 18 +------ t/pg.t | 99 +++++++++++++++++-------------------- t/target.t | 8 +-- 5 files changed, 70 insertions(+), 103 deletions(-) diff --git a/lib/App/Sqitch/Engine.pm b/lib/App/Sqitch/Engine.pm index ef66eca1f..6cb4737a2 100644 --- a/lib/App/Sqitch/Engine.pm +++ b/lib/App/Sqitch/Engine.pm @@ -27,14 +27,15 @@ has target => ( required => 1, weak_ref => 1, handles => { - uri => 'uri', - client => 'client', - registry => 'registry', - destination => 'name', - registry_destination => 'name', + uri => 'uri', + client => 'client', + registry => 'registry', + destination => 'name', } ); +sub registry_destination { shift->destination } + has start_at => ( is => 'rw', isa => Str diff --git a/lib/App/Sqitch/Engine/pg.pm b/lib/App/Sqitch/Engine/pg.pm index e024be270..e31052cae 100644 --- a/lib/App/Sqitch/Engine/pg.pm +++ b/lib/App/Sqitch/Engine/pg.pm @@ -17,26 +17,25 @@ extends 'App::Sqitch::Engine'; our $VERSION = '0.997'; -has '+destination' => ( - default => sub { - my $self = shift; +sub destination { + my $self = shift; - # Just use the target unless it looks like a URI. - my $target = $self->target; - return $target if $target !~ /:/; - - # Use the URI sans password, and with the database name added. - my $uri = $self->uri->clone; - $uri->password(undef) if $uri->password; - $uri->dbname( - $ENV{PGDATABASE} - || $uri->user - || $ENV{PGUSER} - || $self->sqitch->sysuser - ) unless $uri->dbname; - return $uri->as_string; - }, -); + # Just use the target name if it doesn't look like a URI or if the URI + # includes the database name. + return $self->target->name if $self->target->name !~ /:/ + || $self->target->uri->dbname; + + # Use the URI sans password, and with the database name added. + my $uri = $self->target->uri->clone; + $uri->password(undef) if $uri->password; + $uri->dbname( + $ENV{PGDATABASE} + || $uri->user + || $ENV{PGUSER} + || $self->sqitch->sysuser + ); + return $uri->as_string; +} has _psql => ( is => 'ro', diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 49c4909ba..32de7deaa 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -243,40 +243,24 @@ sub BUILDARGS { $uri = $p->{uri} = URI::db->new( $uri ); # Override parts with command-line options. - # TODO: Deprecate these. my $opts = $sqitch->options; - my @deprecated; if (my $host = $opts->{db_host}) { - push @deprecated => '--db-host'; $uri->host($host); } if (my $port = $opts->{db_port}) { - push @deprecated => '--db-port'; $uri->port($port); } if (my $user = $opts->{db_username}) { - push @deprecated => '--db-username'; $uri->user($user); } if (my $db = $opts->{db_name}) { - push @deprecated => '--db-name'; $uri->dbname($db); } - if (@deprecated) { - $sqitch->warn(__nx( - 'Option {options} deprecated and will be removed in 1.0; use URI {uri} instead', - 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', - scalar @deprecated, - options => join(', ', @deprecated), - uri => $uri->as_string, - )); - } - - unless ($p->{name}) { + unless ($name) { # Set the name. if ($uri->password) { # Remove the password from the name. diff --git a/t/pg.t b/t/pg.t index 4cbe9490e..23141f04c 100644 --- a/t/pg.t +++ b/t/pg.t @@ -10,6 +10,7 @@ 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; @@ -32,10 +33,14 @@ is_deeply [$CLASS->config_vars], [ client => 'any', ], 'config_vars should return three vars'; -my $sqitch = App::Sqitch->new(_engine => 'pg'); -isa_ok my $pg = $CLASS->new(sqitch => $sqitch), $CLASS; - my $uri = URI::db->new('db:pg:'); +my $sqitch = App::Sqitch->new(options => { engine => 'pg' }); +my $target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => $uri, +); +isa_ok my $pg = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; + my $client = 'psql' . ($^O eq 'MSWin32' ? '.exe' : ''); is $pg->client, $client, 'client should default to psql'; is $pg->registry, 'sqitch', 'registry default should be "sqitch"'; @@ -59,7 +64,7 @@ my @std_opts = ( is_deeply [$pg->psql], [$client, @std_opts], 'psql command should be std opts-only'; -isa_ok $pg = $CLASS->new(sqitch => $sqitch), $CLASS; +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], [ @@ -77,23 +82,23 @@ ENV: { local $ENV{PGDATABASE}; local $ENV{PGUSER}; for my $env (qw(PGDATABASE PGUSER)) { - my $pg = $CLASS->new(sqitch => $sqitch); + my $pg = $CLASS->new(sqitch => $sqitch, target => $target); local $ENV{$env} = "\$ENV=whatever"; - is $pg->target, "db:pg:", "Target should not read \$$env"; + is $pg->target->uri, "db:pg:", "Target should not read \$$env"; is $pg->registry_destination, $pg->destination, 'Meta 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); - is $pg->target, 'db:pg:', 'Target should not fall back on sysuser'; + 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, 'Meta target should be the same as destination'; $ENV{PGDATABASE} = 'mydb'; - $pg = $CLASS->new(sqitch => $sqitch, username => 'hi'); - is $pg->target, 'db:pg:', 'Target should be the default'; + $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, 'Meta target should be the same as destination'; } @@ -103,11 +108,6 @@ ENV: { my %config = ( 'core.pg.client' => '/path/to/psql', 'core.pg.target' => 'db:pg://localhost/try', - 'core.pg.username' => 'freddy', - 'core.pg.password' => 's3cr3t', - 'core.pg.db_name' => 'widgets', - 'core.pg.host' => 'db.example.com', - 'core.pg.port' => 1234, 'core.pg.registry' => 'meta', ); $std_opts[-3] = 'registry=meta'; @@ -115,7 +115,8 @@ $std_opts[-1] = 'sqitch_schema=meta'; my $mock_config = Test::MockModule->new('App::Sqitch::Config'); $mock_config->mock(get => sub { $config{ $_[2] } }); -ok $pg = $CLASS->new(sqitch => $sqitch), 'Create another pg'; +$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', 'uri should be as configured'; @@ -126,43 +127,27 @@ is_deeply [$pg->psql], [qw( --host localhost ), @std_opts], 'psql command should be configured from URI config'; -############################################################################## -# Try deprecated config. -%config = ( - 'core.pg.client' => '/path/to/psql', - 'core.pg.username' => 'freddy', - 'core.pg.password' => 's3cr3t', - 'core.pg.db_name' => 'widgets', - 'core.pg.host' => 'db.example.com', - 'core.pg.port' => 1234, - 'core.pg.sqitch_schema' => 'meta', -); -ok $pg = $CLASS->new(sqitch => $sqitch), 'Create yet another pg'; -is $pg->uri->as_string, 'db:pg://freddy:s3cr3t@db.example.com:1234/widgets', - 'DB URI should be derived from deprecated config vars'; -is $pg->target, $pg->uri->as_string, 'target should be the URI'; -like $pg->destination, qr{^db:pg://freddy:?\@db\.example\.com:1234/widgets$}, - 'destination should be the URI without the password'; -is $pg->registry_destination, $pg->destination, - 'registry_destination should default be the URI'; - ############################################################################## # Now make sure that (deprecated?) Sqitch options override configurations. $sqitch = App::Sqitch->new( - _engine => 'pg', - db_client => '/some/other/psql', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, -); + options => { + engine => 'pg', + client => '/some/other/psql', + db_username => 'anna', + db_name => 'widgets_dev', + db_host => 'foo.com', + db_port => 98760, +}); -ok $pg = $CLASS->new(sqitch => $sqitch), 'Create a pg with sqitch with options'; +$target = App::Sqitch::Target->new( sqitch => $sqitch ); +ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create a pg with sqitch with options'; is $pg->client, '/some/other/psql', 'client should be as optioned'; -is $pg->uri->as_string, 'db:pg://anna:s3cr3t@foo.com:98760/widgets_dev', +is $pg->uri->as_string, 'db:pg://anna@foo.com:98760/widgets_dev', 'uri should be as configured'; -is $pg->target, $pg->uri->as_string, 'target should be the URI stringified'; +is $pg->target->name, $pg->uri->as_string, + 'target name should be the URI stringified'; like $pg->destination, qr{^db:pg://anna:?\@foo\.com:98760/widgets_dev$}, 'destination should be the URI without the password'; is $pg->registry_destination, $pg->destination, @@ -182,6 +167,7 @@ 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) { @@ -193,6 +179,7 @@ $mock_sqitch->mock(run => sub { my @capture; $mock_sqitch->mock(capture => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @capture = @_; if (defined $exp_pass) { @@ -204,6 +191,7 @@ $mock_sqitch->mock(capture => sub { my @spool; $mock_sqitch->mock(spool => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @spool = @_; if (defined $exp_pass) { @@ -213,6 +201,7 @@ $mock_sqitch->mock(spool => sub { } }); +$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)], @@ -227,8 +216,9 @@ is_deeply \@capture, [$pg->psql, qw(foo bar baz)], 'Command should be passed to capture()'; # Remove the password. -delete $config{'core.pg.password'}; -ok $pg = $CLASS->new(sqitch => $sqitch), 'Create a pg with sqitch with no pw'; +$target->uri->password(undef); +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)], @@ -315,15 +305,14 @@ my $err = try { DBIEngineTest->run( class => $CLASS, - sqitch_params => [ - _engine => 'pg', + sqitch_params => [options => { + engine => 'pg', db_username => 'postgres', db_name => '__sqitchtest__', - top_dir => Path::Class::dir(qw(t engine)), - plan_file => Path::Class::file(qw(t engine sqitch.plan)), - ], - engine_params => [], - alt_engine_params => [ registry => '__sqitchtest' ], + top_dir => Path::Class::dir(qw(t engine))->stringify, + plan_file => Path::Class::file(qw(t engine sqitch.plan))->stringify, + }], + alt_target_params => [ registry => '__sqitchtest' ], skip_unless => sub { my $self = shift; die $err if $err; diff --git a/t/target.t b/t/target.t index 8866bf2e8..284d23d62 100644 --- a/t/target.t +++ b/t/target.t @@ -10,7 +10,6 @@ use Test::Exception; use Test::MockModule; use Locale::TextDomain qw(App-Sqitch); use lib 't/lib'; -use MockOutput; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; $ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; @@ -186,7 +185,7 @@ CONSTRUCTOR: { 'Should have requested target URI from config'; is_deeply \@sect_params, [], 'Should have requested no section'; - # Make sure deprecated --db-* options work. + # Make sure --db-* options work. $uri = URI::db->new('db:pg://fred@foo.com:12245/widget'); $sqitch->options->{engine} = 'pg'; $sqitch->options->{db_host} = 'foo.com'; @@ -196,11 +195,6 @@ CONSTRUCTOR: { isa_ok $target = $CLASS->new(sqitch => $sqitch), $CLASS, 'SQLite target'; is $target->name, $uri->as_string, 'Name should be stringified URI'; is $target->uri, $uri, 'URI should be tweaked by --db-* options'; - is_deeply +MockOutput->get_warn, [[__x( - 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', - options => '--db-host, --db-port, --db-username, --db-name', - uri => $uri->as_string, - )]], 'Should have warned on deprecated options'; } CONFIG: { From 02bd873b008bcf18c676f14b8e418c05bf0e0280 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Mon, 20 Oct 2014 17:44:20 -0700 Subject: [PATCH 14/59] Get mysql tests passing with Target. --- t/mysql.t | 100 ++++++++++++++++++++++-------------------------------- 1 file changed, 41 insertions(+), 59 deletions(-) diff --git a/t/mysql.t b/t/mysql.t index 8ebe5c3e5..3e5b073ca 100755 --- a/t/mysql.t +++ b/t/mysql.t @@ -5,6 +5,7 @@ use warnings; use 5.010; use Test::More; use App::Sqitch; +use App::Sqitch::Target; use Test::MockModule; use Path::Class; use Try::Tiny; @@ -31,8 +32,9 @@ is_deeply [$CLASS->config_vars], [ client => 'any', ], 'config_vars should return three vars'; -my $sqitch = App::Sqitch->new(_engine => 'mysql'); -isa_ok my $mysql = $CLASS->new(sqitch => $sqitch), $CLASS; +my $sqitch = App::Sqitch->new( options => { engine => 'mysql'} ); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +isa_ok my $mysql = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; my $client = 'mysql' . ($^O eq 'MSWin32' ? '.exe' : ''); my $uri = URI::db->new('db:mysql:'); @@ -62,9 +64,13 @@ is_deeply $warning, [__x ], 'Should have emitted a warning for no database name'; $mock_sqitch->unmock_all; -isa_ok $mysql = $CLASS->new( +$target = App::Sqitch::Target->new( sqitch => $sqitch, uri => URI::db->new('db:mysql:foo'), +); +isa_ok $mysql = $CLASS->new( + sqitch => $sqitch, + target => $target, ), $CLASS; ok $mysql->set_variables(foo => 'baz', whu => 'hi there', yo => 'stellar'), 'Set some variables'; @@ -87,11 +93,13 @@ my %config = ( my $mock_config = Test::MockModule->new('App::Sqitch::Config'); $mock_config->mock(get => sub { $config{ $_[2] } }); -ok $mysql = $CLASS->new(sqitch => $sqitch), 'Create another mysql'; +$target = App::Sqitch::Target->new(sqitch => $sqitch); +ok $mysql = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create another mysql'; is $mysql->client, '/path/to/mysql', 'client should be as configured'; is $mysql->uri->as_string, 'db:mysql://foo.com/widgets', 'URI should be as configured'; -is $mysql->target, $mysql->uri->as_string, 'target should be the URI'; +is $mysql->target->name, $mysql->uri->as_string, 'target name should be the URI'; is $mysql->destination, $mysql->uri->as_string, 'destination should be the URI'; is $mysql->registry, 'meta', 'registry should be as configured'; is $mysql->registry_uri->as_string, 'db:mysql://foo.com/meta', @@ -104,61 +112,31 @@ is_deeply [$mysql->mysql], [qw( --host foo.com ), @std_opts], 'mysql command should be configured'; -############################################################################## -# Make sure the deprecated configs are also respected. -%config = ( - 'core.mysql.client' => '/path/to/mysql', - 'core.mysql.username' => 'freddy', - 'core.mysql.password' => 's3cr3t', - 'core.mysql.db_name' => 'widgets', - 'core.mysql.host' => 'db.example.com', - 'core.mysql.port' => 1234, - 'core.mysql.registry' => 'meta', -); - -ok $mysql = $CLASS->new(sqitch => $sqitch), 'Create yet another mysql'; -is $mysql->client, '/path/to/mysql', 'client should be as configured'; -is $mysql->uri->as_string, 'db:mysql://freddy:s3cr3t@db.example.com:1234/widgets', - 'URI should be as configured'; -is $mysql->target, $mysql->uri->as_string, 'target should be the URI string'; -like $mysql->destination, qr{^db:mysql://freddy"?:\@db\.example\.com:1234/widgets$}, - 'destination should be the URI minus the password'; -is $mysql->registry, 'meta', 'registry should be as configured'; -is $mysql->registry_uri->as_string, 'db:mysql://freddy:s3cr3t@db.example.com:1234/meta', - 'Sqitch DB URI should be the same as uri but with DB name "meta"'; -like $mysql->registry_destination, qr{^db:mysql://freddy:?\@db\.example\.com:1234/meta$}, - 'registry_destination should be the sqitch DB URL sans password'; -is_deeply [$mysql->mysql], [qw( - /path/to/mysql - --user freddy - --database widgets - --host db.example.com - --port 1234 - --password=s3cr3t -), @std_opts], 'mysql command should be configured'; - ############################################################################## # Now make sure that Sqitch options override configurations. $sqitch = App::Sqitch->new( - _engine => 'mysql', - db_client => '/some/other/mysql', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, -); + options => { + engine => 'mysql', + client => '/some/other/mysql', + db_username => 'anna', + db_name => 'widgets_dev', + db_host => 'foo.com', + db_port => 98760, +}); -ok $mysql = $CLASS->new(sqitch => $sqitch), +$target = App::Sqitch::Target->new(sqitch => $sqitch); +ok $mysql = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a mysql with sqitch with options'; is $mysql->client, '/some/other/mysql', 'client should be as optioned'; -is $mysql->uri->as_string, 'db:mysql://anna:s3cr3t@foo.com:98760/widgets_dev', +is $mysql->uri->as_string, 'db:mysql://anna@foo.com:98760/widgets_dev', 'The DB URI should be as optioned'; -is $mysql->target, $mysql->uri->as_string, 'target should be the URI stringified'; +is $mysql->target->name, $mysql->uri->as_string, + 'target name should be the URI stringified'; like $mysql->destination, qr{^db:mysql://anna:?\@foo\.com:98760/widgets_dev$}, 'destination should be the URI minus the password'; is $mysql->registry, 'meta', 'registry should be as configured'; -is $mysql->registry_uri->as_string, 'db:mysql://anna:s3cr3t@foo.com:98760/meta', +is $mysql->registry_uri->as_string, 'db:mysql://anna@foo.com:98760/meta', 'Sqitch DB URI should be the same as uri but with DB name "meta"'; like $mysql->registry_destination, qr{^db:mysql://anna:?\@foo\.com:98760/meta$}, 'registry_destination should be the sqitch DB URL sans password'; @@ -169,7 +147,6 @@ is_deeply [$mysql->mysql], [qw( --database widgets_dev --host foo.com --port 98760 - --password=s3cr3t ), @std_opts], 'mysql command should be as optioned'; ############################################################################## @@ -177,6 +154,7 @@ is_deeply [$mysql->mysql], [qw( can_ok $mysql, qw(_run _capture _spool); my (@run, $exp_pass); $mock_sqitch->mock(run => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @run = @_; if (defined $exp_pass) { @@ -188,6 +166,7 @@ $mock_sqitch->mock(run => sub { my @capture; $mock_sqitch->mock(capture => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @capture = @_; if (defined $exp_pass) { @@ -199,6 +178,7 @@ $mock_sqitch->mock(capture => sub { my @spool; $mock_sqitch->mock(spool => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @spool = @_; if (defined $exp_pass) { @@ -209,6 +189,7 @@ $mock_sqitch->mock(spool => sub { }); $exp_pass = 's3cr3t'; +$target->uri->password($exp_pass); ok $mysql->_run(qw(foo bar baz)), 'Call _run'; is_deeply \@run, [$mysql->mysql, qw(foo bar baz)], 'Command should be passed to run()'; @@ -222,8 +203,9 @@ is_deeply \@capture, [$mysql->mysql, qw(foo bar baz)], 'Command should be passed to capture()'; # Remove the password. -delete $config{'core.mysql.password'}; -ok $mysql = $CLASS->new(sqitch => $sqitch), 'Create a mysql with sqitch with no pw'; +$target->uri->password(undef); +ok $mysql = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create a mysql with sqitch with no pw'; $exp_pass = undef; ok $mysql->_run(qw(foo bar baz)), 'Call _run again'; is_deeply \@run, [$mysql->mysql, qw(foo bar baz)], @@ -323,15 +305,15 @@ my $err = try { DBIEngineTest->run( class => $CLASS, - sqitch_params => [ - _engine => 'mysql', + sqitch_params => [options => { + engine => 'mysql', db_username => 'root', db_name => '__sqitchtest__', - top_dir => Path::Class::dir(qw(t engine)), - plan_file => Path::Class::file(qw(t engine sqitch.plan)), - ], - engine_params => [ registry => '__metasqitch' ], - alt_engine_params => [ registry => '__sqitchtest' ], + top_dir => Path::Class::dir(qw(t engine))->stringify, + plan_file => Path::Class::file(qw(t engine sqitch.plan))->stringify, + }], + target_params => [ registry => '__metasqitch' ], + alt_target_params => [ registry => '__sqitchtest' ], skip_unless => sub { my $self = shift; die $err if $err; From 8948460fe4bd5a8be01f39cf7364f0dac9b8beca Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 09:24:26 -0700 Subject: [PATCH 15/59] Update vertica engine to work with Target. --- lib/App/Sqitch/Engine/vertica.pm | 38 ++++++------ t/pg.t | 2 +- t/vertica.t | 103 +++++++++++++++---------------- 3 files changed, 70 insertions(+), 73 deletions(-) diff --git a/lib/App/Sqitch/Engine/vertica.pm b/lib/App/Sqitch/Engine/vertica.pm index 6eea7aebc..33fc9356e 100644 --- a/lib/App/Sqitch/Engine/vertica.pm +++ b/lib/App/Sqitch/Engine/vertica.pm @@ -19,27 +19,25 @@ sub name { 'Vertica' } sub driver { 'DBD::ODBC 1.43' } sub default_client { 'vsql' } -has '+destination' => ( - default => sub { - my $self = shift; - - # Just use the target unless it looks like a URI. - my $target = $self->target; - return $target if $target !~ /:/; - - # Use the URI sans password, and with the database name added. - my $uri = $self->uri->clone; - $uri->password(undef) if $uri->password; - $uri->dbname( - $ENV{VSQL_DATABASE} - || $uri->user - || $ENV{VSQL_USER} - || $self->sqitch->sysuser - ) unless $uri->dbname; - return $uri->as_string; - }, -); +sub destination { + my $self = shift; + # Just use the target name if it doesn't look like a URI or if the URI + # includes the database name. + return $self->target->name if $self->target->name !~ /:/ + || $self->target->uri->dbname; + + # Use the URI sans password, and with the database name added. + my $uri = $self->target->uri->clone; + $uri->password(undef) if $uri->password; + $uri->dbname( + $ENV{VSQL_DATABASE} + || $uri->user + || $ENV{VSQL_USER} + || $self->sqitch->sysuser + ); + return $uri->as_string; +} has _vsql => ( is => 'ro', isa => ArrayRef, diff --git a/t/pg.t b/t/pg.t index 23141f04c..d1441938c 100644 --- a/t/pg.t +++ b/t/pg.t @@ -37,7 +37,7 @@ my $uri = URI::db->new('db:pg:'); my $sqitch = App::Sqitch->new(options => { engine => 'pg' }); my $target = App::Sqitch::Target->new( sqitch => $sqitch, - uri => $uri, + uri => $uri, ); isa_ok my $pg = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; diff --git a/t/vertica.t b/t/vertica.t index dffc2b196..0ea0dcf47 100644 --- a/t/vertica.t +++ b/t/vertica.t @@ -18,6 +18,7 @@ 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; @@ -40,10 +41,17 @@ is_deeply [$CLASS->config_vars], [ client => 'any', ], 'config_vars should return three vars'; -my $sqitch = App::Sqitch->new(_engine => 'vertica'); -isa_ok my $vta = $CLASS->new(sqitch => $sqitch), $CLASS; - my $uri = URI::db->new('db:vertica:'); +my $sqitch = App::Sqitch->new(options => { engine => 'vertica' }); +my $target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => $uri, +); +isa_ok my $vta = $CLASS->new( + sqitch => $sqitch, + target => $target, +), $CLASS; + my $client = 'vsql' . ($^O eq 'MSWin32' ? '.exe' : ''); is $vta->client, $client, 'client should default to vsql'; is $vta->registry, 'sqitch', 'registry default should be "sqitch"'; @@ -66,7 +74,10 @@ my @std_opts = ( is_deeply [$vta->vsql], [$client, @std_opts], 'vsql command should be std opts-only'; -isa_ok $vta = $CLASS->new(sqitch => $sqitch), $CLASS; +isa_ok $vta = $CLASS->new( + sqitch => $sqitch, + target => $target, +), $CLASS; ok $vta->set_variables(foo => 'baz', whu => 'hi there', yo => 'stellar'), 'Set some variables'; is_deeply [$vta->vsql], [ @@ -84,23 +95,24 @@ ENV: { local $ENV{VERTICADATABASE}; local $ENV{VERTICAUSER}; for my $env (qw(VERTICADATABASE VERTICAUSER)) { - my $vta = $CLASS->new(sqitch => $sqitch); + my $vta = $CLASS->new(sqitch => $sqitch, target => $target); local $ENV{$env} = "\$ENV=whatever"; - is $vta->target, "db:vertica:", "Target should not read \$$env"; + is $vta->target->name, "db:vertica:", "Target name should not read \$$env"; is $vta->registry_destination, $vta->destination, 'Meta target should be the same as destination'; } my $mocker = Test::MockModule->new('App::Sqitch'); $mocker->mock(sysuser => 'sysuser=whatever'); - my $vta = $CLASS->new(sqitch => $sqitch); - is $vta->target, 'db:vertica:', 'Target should not fall back on sysuser'; + my $vta = $CLASS->new(sqitch => $sqitch, target => $target); + is $vta->target->name, 'db:vertica:', + 'Target name should not fall back on sysuser'; is $vta->registry_destination, $vta->destination, 'Meta target should be the same as destination'; $ENV{VERTICADATABASE} = 'mydb'; - $vta = $CLASS->new(sqitch => $sqitch, username => 'hi'); - is $vta->target, 'db:vertica:', 'Target should be the default'; + $vta = $CLASS->new(sqitch => $sqitch, username => 'hi', target => $target); + is $vta->target->name, 'db:vertica:', 'Target name should be the default'; is $vta->registry_destination, $vta->destination, 'Meta target should be the same as destination'; } @@ -110,18 +122,15 @@ ENV: { my %config = ( 'core.vertica.client' => '/path/to/vsql', 'core.vertica.target' => 'db:vertica://localhost/try', - 'core.vertica.username' => 'freddy', - 'core.vertica.password' => 's3cr3t', - 'core.vertica.db_name' => 'widgets', - 'core.vertica.host' => 'db.example.com', - 'core.vertica.port' => 1234, 'core.vertica.registry' => 'meta', ); $std_opts[-1] = 'registry=meta'; my $mock_config = Test::MockModule->new('App::Sqitch::Config'); $mock_config->mock(get => sub { $config{ $_[2] } }); -ok $vta = $CLASS->new(sqitch => $sqitch), 'Create another vertica'; +$target = App::Sqitch::Target->new( sqitch => $sqitch ); +ok $vta = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create another vertica'; is $vta->client, '/path/to/vsql', 'client should be as configured'; is $vta->uri->as_string, 'db:vertica://localhost/try', 'uri should be as configured'; @@ -132,43 +141,28 @@ is_deeply [$vta->vsql], [qw( --host localhost ), @std_opts], 'vsql command should be configured from URI config'; -############################################################################## -# Try deprecated config. -%config = ( - 'core.vertica.client' => '/path/to/vsql', - 'core.vertica.username' => 'freddy', - 'core.vertica.password' => 's3cr3t', - 'core.vertica.db_name' => 'widgets', - 'core.vertica.host' => 'db.example.com', - 'core.vertica.port' => 1234, - 'core.vertica.sqitch_schema' => 'meta', -); -ok $vta = $CLASS->new(sqitch => $sqitch), 'Create yet another vertica'; -is $vta->uri->as_string, 'db:vertica://freddy:s3cr3t@db.example.com:1234/widgets', - 'DB URI should be derived from deprecated config vars'; -is $vta->target, $vta->uri->as_string, 'target should be the URI'; -like $vta->destination, qr{^db:vertica://freddy:?\@db\.example\.com:1234/widgets$}, - 'destination should be the URI without the password'; -is $vta->registry_destination, $vta->destination, - 'registry_destination should default be the URI'; - ############################################################################## # Now make sure that (deprecated?) Sqitch options override configurations. $sqitch = App::Sqitch->new( - _engine => 'vertica', - db_client => '/some/other/vsql', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, + options => { + engine => 'vertica', + client => '/some/other/vsql', + db_username => 'anna', + db_name => 'widgets_dev', + db_host => 'foo.com', + db_port => 98760, + }, ); -ok $vta = $CLASS->new(sqitch => $sqitch), 'Create a vertica with sqitch with options'; +$target = App::Sqitch::Target->new( sqitch => $sqitch ); +ok $vta = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create a vertica with sqitch with options'; is $vta->client, '/some/other/vsql', 'client should be as optioned'; -is $vta->uri->as_string, 'db:vertica://anna:s3cr3t@foo.com:98760/widgets_dev', +is $vta->uri->as_string, 'db:vertica://anna@foo.com:98760/widgets_dev', 'uri should be as configured'; -is $vta->target, $vta->uri->as_string, 'target should be the URI stringified'; +is $vta->target->name, $vta->uri->as_string, + 'Target name should be the URI stringified'; like $vta->destination, qr{^db:vertica://anna:?\@foo\.com:98760/widgets_dev$}, 'destination should be the URI without the password'; is $vta->registry_destination, $vta->destination, @@ -188,6 +182,7 @@ can_ok $vta, 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) { @@ -199,6 +194,7 @@ $mock_sqitch->mock(run => sub { my @capture; $mock_sqitch->mock(capture => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @capture = @_; if (defined $exp_pass) { @@ -210,6 +206,7 @@ $mock_sqitch->mock(capture => sub { my @spool; $mock_sqitch->mock(spool => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @spool = @_; if (defined $exp_pass) { @@ -220,6 +217,7 @@ $mock_sqitch->mock(spool => sub { }); $exp_pass = 's3cr3t'; +$target->uri->password($exp_pass); ok $vta->_run(qw(foo bar baz)), 'Call _run'; is_deeply \@run, [$vta->vsql, qw(foo bar baz)], 'Command should be passed to run()'; @@ -233,8 +231,9 @@ is_deeply \@capture, [$vta->vsql, qw(foo bar baz)], 'Command should be passed to capture()'; # Remove the password. -delete $config{'core.vertica.password'}; -ok $vta = $CLASS->new(sqitch => $sqitch), 'Create a vertica with sqitch with no pw'; +$target->uri->password(undef); +ok $vta = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create a vertica with sqitch with no pw'; $exp_pass = undef; ok $vta->_run(qw(foo bar baz)), 'Call _run again'; is_deeply \@run, [$vta->vsql, qw(foo bar baz)], @@ -323,13 +322,13 @@ my $err = try { DBIEngineTest->run( class => $CLASS, - sqitch_params => [ - _engine => 'vertica', + sqitch_params => [options => { + engine => 'vertica', top_dir => Path::Class::dir(qw(t engine)), plan_file => Path::Class::file(qw(t engine sqitch.plan)), - ], - engine_params => [ uri => $uri ], - alt_engine_params => [ uri => $uri, registry => '__sqitchtest' ], + }], + target_params => [ uri => $uri ], + alt_target_params => [ uri => $uri, registry => '__sqitchtest' ], skip_unless => sub { my $self = shift; die $err if $err; From b53f0559ec8b14c3d4188af2354d50e2af488dfb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 10:42:56 -0700 Subject: [PATCH 16/59] Update Oracle engine for Target. --- lib/App/Sqitch/Engine/oracle.pm | 41 ++++++------ t/oracle.t | 108 ++++++++++++++++++-------------- 2 files changed, 81 insertions(+), 68 deletions(-) diff --git a/lib/App/Sqitch/Engine/oracle.pm b/lib/App/Sqitch/Engine/oracle.pm index e9b07b56f..6ce17561d 100644 --- a/lib/App/Sqitch/Engine/oracle.pm +++ b/lib/App/Sqitch/Engine/oracle.pm @@ -26,27 +26,26 @@ BEGIN { $ENV{SQLPATH} = ''; } -has '+destination' => ( - default => sub { - my $self = shift; +sub destination { + my $self = shift; - # Just use the target unless it looks like a URI. - my $target = $self->target; - return $target if $target !~ /:/; - - # Use the URI sans password, and with the database name added. - my $uri = $self->uri->clone; - $uri->password(undef) if $uri->password; - $uri->dbname( - $ENV{TWO_TASK} - || ( $^O eq 'MSWin32' ? $ENV{LOCAL} : undef ) - || $ENV{ORACLE_SID} - || $uri->user - || $self->sqitch->sysuser - ) unless $uri->dbname; - return $uri->as_string; - }, -); + # Just use the target name if it doesn't look like a URI or if the URI + # includes the database name. + return $self->target->name if $self->target->name !~ /:/ + || $self->target->uri->dbname; + + # Use the URI sans password, and with the database name added. + my $uri = $self->target->uri->clone; + $uri->password(undef) if $uri->password; + $uri->dbname( + $ENV{TWO_TASK} + || ( $^O eq 'MSWin32' ? $ENV{LOCAL} : undef ) + || $ENV{ORACLE_SID} + || $uri->user + || $self->sqitch->sysuser + ); + return $uri->as_string; +} has _sqlplus => ( is => 'ro', @@ -73,7 +72,7 @@ has tmpdir => ( sub key { 'oracle' } sub name { 'Oracle' } sub driver { 'DBD::Oracle 1.23' } -sub default_registry { undef } +sub default_registry { '' } sub default_client { file( ($ENV{ORACLE_HOME} || ()), 'sqlplus' )->stringify diff --git a/t/oracle.t b/t/oracle.t index dea350471..263b7ad76 100644 --- a/t/oracle.t +++ b/t/oracle.t @@ -25,7 +25,6 @@ # # sqlplus sys/oracle@localhost/ORCL as sysdba # -# # If this fails with either of these errors: # # ORA-01017: invalid username/password; logon denied @@ -35,9 +34,9 @@ # /etc/hosts (http://sourceforge.net/p/tora/discussion/52737/thread/f68b89ad/): # # > hostname -# dwhee-ma-2944 +# stickywicket # > grep 127 /etc/hosts -# 127.0.0.1 localhost dwhee-ma-2944 +# 127.0.0.1 localhost stickywicket # # Once connected, execute this SQL to create the user and give it access: # @@ -58,6 +57,7 @@ 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; @@ -79,19 +79,21 @@ is_deeply [$CLASS->config_vars], [ client => 'any', ], 'config_vars should return three vars'; -my $sqitch = App::Sqitch->new(_engine => 'oracle'); -isa_ok my $ora = $CLASS->new(sqitch => $sqitch), $CLASS; +my $sqitch = App::Sqitch->new(options => { engine => 'oracle' }); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +isa_ok my $ora = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; my $client = 'sqlplus' . ($^O eq 'MSWin32' ? '.exe' : ''); is $ora->client, $client, 'client should default to sqlplus'; ORACLE_HOME: { local $ENV{ORACLE_HOME} = '/foo/bar'; - isa_ok my $ora = $CLASS->new(sqitch => $sqitch), $CLASS; + my $target = App::Sqitch::Target->new(sqitch => $sqitch); + isa_ok my $ora = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; is $ora->client, Path::Class::file('/foo/bar', $client)->stringify, 'client should use $ORACLE_HOME'; } -is $ora->registry, undef, 'registry default should be undefined'; +is $ora->registry, '', 'registry default should be empty'; is $ora->uri, 'db:oracle:', 'Default URI should be "db:oracle"'; my $dest_uri = $ora->uri->clone; @@ -101,7 +103,7 @@ $dest_uri->dbname( || $ENV{ORACLE_SID} || $sqitch->sysuser ); -is $ora->target, $ora->uri, 'Target should be the uri stringified'; +is $ora->target->name, $ora->uri, 'Target name should be the uri stringified'; is $ora->destination, $dest_uri->as_string, 'Destination should fall back on environment variables'; is $ora->registry_destination, $ora->destination, @@ -119,9 +121,13 @@ is $ora->_script, join( "\n" => ( ) ), '_script should work'; # Set up username, password, and db_name. +$target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI::db->new('db:oracle://fred:derf@/blah') +); isa_ok $ora = $CLASS->new( sqitch => $sqitch, - uri => URI::db->new('db:oracle://fred:derf@/blah') + target => $target, ), $CLASS; is $ora->_script, join( "\n" => ( @@ -132,9 +138,13 @@ is $ora->_script, join( "\n" => ( ) ), '_script should assemble connection string'; # Add a host name. +$target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI::db->new('db:oracle://fred:derf@there/blah') +); isa_ok $ora = $CLASS->new( sqitch => $sqitch, - uri => URI::db->new('db:oracle://fred:derf@there/blah') + target => $target, ), $CLASS; is $ora->_script('@foo'), join( "\n" => ( @@ -146,11 +156,15 @@ is $ora->_script('@foo'), join( "\n" => ( ) ), '_script should assemble connection string with host'; # Add a port and varibles. -isa_ok $ora = $CLASS->new( +$target = App::Sqitch::Target->new( sqitch => $sqitch, - uri => URI::db->new( + uri => URI::db->new( 'db:oracle://fred:derf%20%22derf%22@there:1345/blah%20%22blah%22' ), +); +isa_ok $ora = $CLASS->new( + sqitch => $sqitch, + target => $target, ), $CLASS; ok $ora->set_variables(foo => 'baz', whu => 'hi there', yo => q{"stellar"}), 'Set some variables'; @@ -167,14 +181,15 @@ is $ora->_script, join( "\n" => ( ############################################################################## # Test other configs for the destination. +$target = App::Sqitch::Target->new(sqitch => $sqitch); ENV: { # Make sure we override system-set vars. local $ENV{TWO_TASK}; local $ENV{ORACLE_SID}; for my $env (qw(TWO_TASK ORACLE_SID)) { - my $ora = $CLASS->new(sqitch => $sqitch); + my $ora = $CLASS->new(sqitch => $sqitch, target => $target); local $ENV{$env} = '$ENV=whatever'; - is $ora->target, "db:oracle:", "Target should not read \$$env"; + is $ora->target->name, "db:oracle:", "Target name should not read \$$env"; is $ora->destination, "db:oracle:\$ENV=whatever", "Destination should read \$$env"; is $ora->registry_destination, $ora->destination, 'Registry destination should be the same as destination'; @@ -182,16 +197,17 @@ ENV: { my $mocker = Test::MockModule->new('App::Sqitch'); $mocker->mock(sysuser => 'sysuser=whatever'); - my $ora = $CLASS->new(sqitch => $sqitch); - is $ora->target, 'db:oracle:', 'Target should not fall back on sysuser'; + my $ora = $CLASS->new(sqitch => $sqitch, target => $target); + is $ora->target->name, 'db:oracle:', + 'Target name should not fall back on sysuser'; is $ora->destination, 'db:oracle:sysuser=whatever', 'Destination should fall back on sysuser'; is $ora->registry_destination, $ora->destination, 'Registry destination should be the same as destination'; $ENV{TWO_TASK} = 'mydb'; - $ora = $CLASS->new(sqitch => $sqitch, username => 'hi'); - is $ora->target, 'db:oracle:', 'Target should be the default'; + $ora = $CLASS->new(sqitch => $sqitch, username => 'hi', target => $target); + is $ora->target->name, 'db:oracle:', 'Target should be the default'; is $ora->destination, 'db:oracle:mydb', 'Destination should prefer $TWO_TASK to username'; is $ora->registry_destination, $ora->destination, @@ -207,12 +223,15 @@ my %config = ( ); my $mock_config = Test::MockModule->new('App::Sqitch::Config'); $mock_config->mock(get => sub { $config{ $_[2] } }); -ok $ora = $CLASS->new(sqitch => $sqitch), 'Create another ora'; +$target = App::Sqitch::Target->new(sqitch => $sqitch); +ok $ora = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create another ora'; is $ora->client, '/path/to/sqlplus', 'client should be as configured'; is $ora->uri->as_string, 'db:oracle://bob:hi@db.net:12/howdy', 'DB URI should be as configured'; -is $ora->target, $ora->uri->as_string, 'Target should be the URI stringified'; +like $ora->target->name, qr{^db:oracle://bob:?\@db\.net:12/howdy$}, + 'Target name should be the passwordless URI stringified'; like $ora->destination, qr{^db:oracle://bob:?\@db\.net:12/howdy$}, 'Destination should be the URI without the password'; is $ora->registry_destination, $ora->destination, @@ -223,23 +242,13 @@ is_deeply [$ora->sqlplus], ['/path/to/sqlplus', @std_opts], %config = ( 'core.oracle.client' => '/path/to/sqlplus', - 'core.oracle.username' => 'freddy', - 'core.oracle.password' => 's3cr3t', - 'core.oracle.db_name' => 'widgets', - 'core.oracle.host' => 'db.example.com', - 'core.oracle.port' => 1234, 'core.oracle.registry' => 'meta', ); -ok $ora = $CLASS->new(sqitch => $sqitch), 'Create yet another ora'; +$target = App::Sqitch::Target->new(sqitch => $sqitch); +ok $ora = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create yet another ora'; is $ora->client, '/path/to/sqlplus', 'client should be as configured'; -is $ora->uri->as_string, 'db:oracle://freddy:s3cr3t@db.example.com:1234/widgets', - 'DB URI should be constructed from old config variables'; -is $ora->target, $ora->uri->as_string, 'Target should be the URI stringified'; -like $ora->destination, qr{^db:oracle://freddy:?\@db\.example\.com:1234/widgets$}, - 'Destination should be the URI without the password'; -is $ora->registry_destination, $ora->destination, - 'registry_destination should be the same URI'; is $ora->registry, 'meta', 'registry should be as configured'; is_deeply [$ora->sqlplus], ['/path/to/sqlplus', @std_opts], 'sqlplus command should be configured'; @@ -247,20 +256,25 @@ is_deeply [$ora->sqlplus], ['/path/to/sqlplus', @std_opts], ############################################################################## # Now make sure that Sqitch options override configurations. $sqitch = App::Sqitch->new( - _engine => 'oracle', - db_client => '/some/other/sqlplus', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, + options => { + engine => 'oracle', + client => '/some/other/sqlplus', + db_username => 'anna', + db_name => 'widgets_dev', + db_host => 'foo.com', + db_port => 98760, + }, ); -ok $ora = $CLASS->new(sqitch => $sqitch), 'Create a ora with sqitch with options'; +$target = App::Sqitch::Target->new(sqitch => $sqitch); +ok $ora = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create a ora with sqitch with options'; is $ora->client, '/some/other/sqlplus', 'client should be as optioned'; -is $ora->uri->as_string, 'db:oracle://anna:s3cr3t@foo.com:98760/widgets_dev', +is $ora->uri->as_string, 'db:oracle://anna@foo.com:98760/widgets_dev', 'DB URI should have attributes overridden by options'; -is $ora->target, $ora->uri->as_string, 'Target should be the URI stringified'; +is $ora->target->name, $ora->uri->as_string, + 'Target name should be the URI stringified'; like $ora->destination, qr{^db:oracle://anna:?\@foo\.com:98760/widgets_dev$}, 'Destination should be the URI without the password'; is $ora->registry_destination, $ora->destination, @@ -451,13 +465,13 @@ $uri->password($pass); # $uri->dbname( $ENV{TWO_TASK} || $ENV{LOCAL} || $ENV{ORACLE_SID} ); DBIEngineTest->run( class => $CLASS, - sqitch_params => [ - _engine => 'oracle', + sqitch_params => [options => { + engine => 'oracle', top_dir => Path::Class::dir(qw(t engine)), plan_file => Path::Class::file(qw(t engine sqitch.plan)), - ], - engine_params => [ uri => $uri ], - alt_engine_params => [ uri => $uri, registry => 'oe' ], + }], + target_params => [ uri => $uri ], + alt_target_params => [ uri => $uri, registry => 'oe' ], skip_unless => sub { my $self = shift; die $err if $err; From 703f5e11b16fcca5b241bd8d8b56ee4257b40d95 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 10:51:56 -0700 Subject: [PATCH 17/59] Restore option deprecations. --- lib/App/Sqitch/Target.pm | 18 +++++++++++++++++- t/target.t | 8 +++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 32de7deaa..49c4909ba 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -243,24 +243,40 @@ sub BUILDARGS { $uri = $p->{uri} = URI::db->new( $uri ); # Override parts with command-line options. + # TODO: Deprecate these. my $opts = $sqitch->options; + my @deprecated; if (my $host = $opts->{db_host}) { + push @deprecated => '--db-host'; $uri->host($host); } if (my $port = $opts->{db_port}) { + push @deprecated => '--db-port'; $uri->port($port); } if (my $user = $opts->{db_username}) { + push @deprecated => '--db-username'; $uri->user($user); } if (my $db = $opts->{db_name}) { + push @deprecated => '--db-name'; $uri->dbname($db); } - unless ($name) { + if (@deprecated) { + $sqitch->warn(__nx( + 'Option {options} deprecated and will be removed in 1.0; use URI {uri} instead', + 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', + scalar @deprecated, + options => join(', ', @deprecated), + uri => $uri->as_string, + )); + } + + unless ($p->{name}) { # Set the name. if ($uri->password) { # Remove the password from the name. diff --git a/t/target.t b/t/target.t index 284d23d62..8866bf2e8 100644 --- a/t/target.t +++ b/t/target.t @@ -10,6 +10,7 @@ use Test::Exception; use Test::MockModule; use Locale::TextDomain qw(App-Sqitch); use lib 't/lib'; +use MockOutput; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; $ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; @@ -185,7 +186,7 @@ CONSTRUCTOR: { 'Should have requested target URI from config'; is_deeply \@sect_params, [], 'Should have requested no section'; - # Make sure --db-* options work. + # Make sure deprecated --db-* options work. $uri = URI::db->new('db:pg://fred@foo.com:12245/widget'); $sqitch->options->{engine} = 'pg'; $sqitch->options->{db_host} = 'foo.com'; @@ -195,6 +196,11 @@ CONSTRUCTOR: { isa_ok $target = $CLASS->new(sqitch => $sqitch), $CLASS, 'SQLite target'; is $target->name, $uri->as_string, 'Name should be stringified URI'; is $target->uri, $uri, 'URI should be tweaked by --db-* options'; + is_deeply +MockOutput->get_warn, [[__x( + 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', + options => '--db-host, --db-port, --db-username, --db-name', + uri => $uri->as_string, + )]], 'Should have warned on deprecated options'; } CONFIG: { From cdea697cac91c27a005dbd94a872faad1c0efe44 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 10:56:11 -0700 Subject: [PATCH 18/59] Remove deprecation warnings from Postgres tests. And restore the checking of the proper name variable in Target. --- lib/App/Sqitch/Target.pm | 4 ++-- t/pg.t | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 49c4909ba..bdf2e1f00 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -220,7 +220,7 @@ sub BUILDARGS { } elsif ($name =~ /:/) { # The name is a URI. $uri = URI::db->new($name); - $name = undef; + $name = $p->{name} = undef; } else { # Well then, there had better be a config with a URI. $uri = $sqitch->config->get( key => "target.$name.uri" ) or do { @@ -276,7 +276,7 @@ sub BUILDARGS { )); } - unless ($p->{name}) { + unless ($name) { # Set the name. if ($uri->password) { # Remove the password from the name. diff --git a/t/pg.t b/t/pg.t index d1441938c..886eb4e6f 100644 --- a/t/pg.t +++ b/t/pg.t @@ -307,12 +307,16 @@ DBIEngineTest->run( class => $CLASS, sqitch_params => [options => { engine => 'pg', - db_username => 'postgres', - db_name => '__sqitchtest__', top_dir => Path::Class::dir(qw(t engine))->stringify, plan_file => Path::Class::file(qw(t engine sqitch.plan))->stringify, }], - alt_target_params => [ registry => '__sqitchtest' ], + target_params => [ + uri => URI::db->new('db:pg://postgres@/__sqitchtest__'), + ], + alt_target_params => [ + registry => '__sqitchtest', + uri => URI::db->new('db:pg://postgres@/__sqitchtest__'), + ], skip_unless => sub { my $self = shift; die $err if $err; From 616f8675ae52ea6581122bc6f8e433ae37792733 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 10:59:52 -0700 Subject: [PATCH 19/59] Eliminate deprecation warning sin mysql test output. --- t/mysql.t | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/t/mysql.t b/t/mysql.t index 3e5b073ca..900b1a373 100755 --- a/t/mysql.t +++ b/t/mysql.t @@ -307,13 +307,17 @@ DBIEngineTest->run( class => $CLASS, sqitch_params => [options => { engine => 'mysql', - db_username => 'root', - db_name => '__sqitchtest__', top_dir => Path::Class::dir(qw(t engine))->stringify, plan_file => Path::Class::file(qw(t engine sqitch.plan))->stringify, }], - target_params => [ registry => '__metasqitch' ], - alt_target_params => [ registry => '__sqitchtest' ], + target_params => [ + registry => '__metasqitch', + uri => URI::db->new('db:mysql://root@/__sqitchtest__'), + ], + alt_target_params => [ + registry => '__sqitchtest', + uri => URI::db->new('db:mysql://root@/__sqitchtest__'), + ], skip_unless => sub { my $self = shift; die $err if $err; From 4f5139b97b86e90bf68654ffdc97768d11ac90c7 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 11:56:24 -0700 Subject: [PATCH 20/59] Support and warn on deprecated engine configs. --- lib/App/Sqitch/Target.pm | 24 +++++++++++++++++++--- t/target.t | 44 +++++++++++++++++++++++++++++++++++----- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index bdf2e1f00..0bda23edd 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -242,28 +242,46 @@ sub BUILDARGS { require URI::db; $uri = $p->{uri} = URI::db->new( $uri ); - # Override parts with command-line options. - # TODO: Deprecate these. - my $opts = $sqitch->options; + # Override parts with deprecated command-line options and config. + my $opts = $sqitch->options; + my $config = $sqitch->config->get_section(section => "core.$ekey") || {}; + my @deprecated; if (my $host = $opts->{db_host}) { push @deprecated => '--db-host'; $uri->host($host); + } elsif ($host = $config->{host}) { + push @deprecated => "core.$ekey.host"; + $uri->host($host); } if (my $port = $opts->{db_port}) { push @deprecated => '--db-port'; $uri->port($port); + } elsif ($port = $config->{port}) { + push @deprecated => "core.$ekey.port"; + $uri->port($port); } if (my $user = $opts->{db_username}) { push @deprecated => '--db-username'; $uri->user($user); + } elsif ($user = $config->{username}) { + push @deprecated => "core.$ekey.username"; + $uri->user($user); + } + + if (my $pass = $config->{password}) { + push @deprecated => "core.$ekey.password"; + $uri->password($pass); } if (my $db = $opts->{db_name}) { push @deprecated => '--db-name'; $uri->dbname($db); + } elsif ($db = $config->{db_name}) { + push @deprecated => "core.$ekey.db_name"; + $uri->dbname($db); } if (@deprecated) { diff --git a/t/target.t b/t/target.t index 8866bf2e8..0966da188 100644 --- a/t/target.t +++ b/t/target.t @@ -184,21 +184,55 @@ CONSTRUCTOR: { is $target->uri, URI::db->new('db:pg:foo'), 'URI should be "db:pg:foo"'; is_deeply \@get_params, [[key => 'target.foo.uri']], 'Should have requested target URI from config'; - is_deeply \@sect_params, [], 'Should have requested no section'; + is_deeply \@sect_params, [ [section => 'core.sqlite' ]], + 'Should have requested sqlite section'; - # Make sure deprecated --db-* options work. - $uri = URI::db->new('db:pg://fred@foo.com:12245/widget'); + # Make sure deprecated config options work. + @sect_ret = ({ + host => 'hi.com', + port => 5432, + username => 'bob', + password => 'ouch', + db_name => 'sharks', + }); $sqitch->options->{engine} = 'pg'; + @get_params = @sect_params = (); + $uri = URI::db->new('db:pg://bob:ouch@hi.com:5432/sharks'); + isa_ok $target = $CLASS->new(sqitch => $sqitch), $CLASS, 'Pg target'; + is_deeply \@sect_params, [ [section => 'core.pg' ]], + 'Should have requested pg section'; + like $target->name, qr{db:pg://bob:?\@hi.com:5432/sharks}, + 'Name should be passwordless stringified URI'; + is $target->uri, $uri, 'URI should be tweaked by config* options'; + is_deeply +MockOutput->get_warn, [[__x( + 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', + options => 'core.pg.host, core.pg.port, core.pg.username, core.pg.password, core.pg.db_name', + uri => $uri->as_string, + )]], 'Should have warned on deprecated config options'; + + # Make sure deprecated --db-* options work. + @sect_ret = ({ + host => 'hi.com', + port => 5432, + username => 'bob', + password => 'ouch', + db_name => 'sharks', + }); + @get_params = @sect_params = (); + $uri = URI::db->new('db:pg://fred:ouch@foo.com:12245/widget'); $sqitch->options->{db_host} = 'foo.com'; $sqitch->options->{db_port} = 12245; $sqitch->options->{db_username} = 'fred'; $sqitch->options->{db_name} = 'widget'; isa_ok $target = $CLASS->new(sqitch => $sqitch), $CLASS, 'SQLite target'; - is $target->name, $uri->as_string, 'Name should be stringified URI'; + is_deeply \@sect_params, [ [section => 'core.pg' ]], + 'Should have requested sqlite section'; + like $target->name, qr{db:pg://fred:?\@foo.com:12245/widget}, + 'Name should be passwordless stringified URI'; is $target->uri, $uri, 'URI should be tweaked by --db-* options'; is_deeply +MockOutput->get_warn, [[__x( 'Options {options} deprecated and will be removed in 1.0; use URI {uri} instead', - options => '--db-host, --db-port, --db-username, --db-name', + options => '--db-host, --db-port, --db-username, core.pg.password, --db-name', uri => $uri->as_string, )]], 'Should have warned on deprecated options'; } From 1d862959c0b28b4a41c68b70a2b5ac6e470fd1a4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 13:25:06 -0700 Subject: [PATCH 21/59] Add target attribute to Plan. --- lib/App/Sqitch/Plan.pm | 11 +++++++-- t/plan.t | 56 ++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/lib/App/Sqitch/Plan.pm b/lib/App/Sqitch/Plan.pm index e06143503..ab76e1917 100644 --- a/lib/App/Sqitch/Plan.pm +++ b/lib/App/Sqitch/Plan.pm @@ -15,7 +15,7 @@ use App::Sqitch::X qw(hurl); use List::MoreUtils qw(uniq any); use namespace::autoclean; use Moo; -use App::Sqitch::Types qw(Str Int HashRef ChangeList LineList Maybe Sqitch URI File); +use App::Sqitch::Types qw(Str Int HashRef ChangeList LineList Maybe Sqitch URI File Target); use constant SYNTAX_VERSION => '1.0.0-b2'; our $VERSION = '0.997'; @@ -47,12 +47,19 @@ has sqitch => ( weak_ref => 1, ); +has target => ( + is => 'ro', + isa => Target, + required => 1, + weak_ref => 1, +); + has file => ( is => 'ro', isa => File, lazy => 1, default => sub { - shift->sqitch->plan_file + shift->target->plan_file }, ); diff --git a/t/plan.t b/t/plan.t index e0774801c..db091cc9d 100644 --- a/t/plan.t +++ b/t/plan.t @@ -6,6 +6,7 @@ use 5.010; use utf8; use Test::More; use App::Sqitch; +use App::Sqitch::Target; use Locale::TextDomain qw(App-Sqitch); use Path::Class; use Test::Exception; @@ -32,6 +33,7 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; can_ok $CLASS, qw( sqitch + target file changes position @@ -44,9 +46,11 @@ can_ok $CLASS, qw( open_script ); -my $sqitch = App::Sqitch->new; -isa_ok my $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS; -is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; +my $sqitch = App::Sqitch->new( options => { engine => 'sqlite' }); +my $target = App::Sqitch::Target->new( sqitch => $sqitch ); +isa_ok my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target), + $CLASS; +is $plan->file, $target->plan_file, 'File should be coopied from Target'; # Set up some some utility functions for creating changes. sub blank { @@ -684,8 +688,8 @@ cmp_deeply $parsed, { # Try a non-existent plan file with load(). $file = file qw(t hi nonexistent.plan); -$sqitch = App::Sqitch->new(plan_file => $file); -throws_ok { App::Sqitch::Plan->new(sqitch => $sqitch)->load } 'App::Sqitch::X', +$target = App::Sqitch::Target->new(sqitch => $sqitch, plan_file => $file); +throws_ok { App::Sqitch::Plan->new(sqitch => $sqitch, target => $target)->load } 'App::Sqitch::X', 'Should get exception for nonexistent plan file'; is $@->ident, 'plan', 'Nonexistent plan file ident should be "plan"'; is $@->message, __x( @@ -695,10 +699,10 @@ is $@->message, __x( # Try a plan with dependencies. $file = file qw(t plans dependencies.plan); -$sqitch = App::Sqitch->new(plan_file => $file); -isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, +$target = App::Sqitch::Target->new(sqitch => $sqitch, plan_file => $file); +isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target), $CLASS, 'Plan with sqitch with plan file with dependencies'; -is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; +is $plan->file, $target->plan_file, 'File should be coopied from Sqitch'; ok $parsed = $plan->load, 'Load plan with dependencies file'; is_deeply $parsed->{changes}, [ clear, @@ -727,10 +731,10 @@ is sorted, 2, 'Should have sorted changes twice'; # Try a plan with cross-project dependencies. $file = file qw(t plans project_deps.plan); -$sqitch = App::Sqitch->new(plan_file => $file); -isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, +$target = App::Sqitch::Target->new(sqitch => $sqitch, plan_file => $file); +isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target), $CLASS, 'Plan with sqitch with plan file with project deps'; -is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; +is $plan->file, $target->plan_file, 'File should be coopied from Sqitch'; ok $parsed = $plan->load, 'Load plan with project deps file'; is_deeply $parsed->{changes}, [ clear, @@ -760,11 +764,11 @@ is sorted, 2, 'Should have sorted changes twice'; # Should fail with dependencies on tags. $file = file qw(t plans tag_dependencies.plan); +$target = App::Sqitch::Target->new(sqitch => $sqitch, plan_file => $file); $fh = IO::File->new(\"%project=tagdep\n\nfoo $tsnp\n\@bar [:foo] $tsnp", '<:utf8_strict'); -$sqitch = App::Sqitch->new(plan_file => $file); -isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, - 'Plan with sqitch with plan with tag dependencies'; -is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; +isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target), + $CLASS, 'Plan with sqitch with plan with tag dependencies'; +is $plan->file, $target->plan_file, 'File should be coopied from Sqitch'; throws_ok { $plan->_parse($file, $fh) } 'App::Sqitch::X', 'Should get an exception for tag with dependencies'; is $@->ident, 'parse', 'The tag dependencies error ident should be "plan"'; @@ -777,10 +781,10 @@ is $@->message, __x( # Make sure that lines() loads the plan. $file = file qw(t plans multi.plan); -$sqitch = App::Sqitch->new(plan_file => $file); -isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, +$target = App::Sqitch::Target->new(sqitch => $sqitch, plan_file => $file); +isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target), $CLASS, 'Plan with sqitch with plan file'; -is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; +is $plan->file, $target->plan_file, 'File should be coopied from Sqitch'; cmp_deeply [$plan->lines], [ clear, version, @@ -1547,10 +1551,10 @@ is $@->message, __x( ############################################################################## # Try a plan with a duplicate change in different tag sections. $file = file qw(t plans dupe-change-diff-tag.plan); -$sqitch = App::Sqitch->new(plan_file => $file); -isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, - 'Plan shoud work plan with dupe change across tags'; -is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; +$target = App::Sqitch::Target->new(sqitch => $sqitch, plan_file => $file); +isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target), + $CLASS, 'Plan shoud work plan with dupe change across tags'; +is $plan->file, $target->plan_file, 'File should be coopied from Sqitch'; is $plan->project, 'dupe_change_diff_tag', 'Project name should be set'; cmp_deeply [ $plan->lines ], [ clear, @@ -1968,10 +1972,10 @@ for my $req (qw(wanker @blah greets@foo)) { # Test pragma accessors. is $plan->uri, undef, 'Should have undef URI when no pragma'; $file = file qw(t plans pragmas.plan); -$sqitch = App::Sqitch->new(plan_file => $file); -isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch), $CLASS, - 'Plan with sqitch with plan file with dependencies'; -is $plan->file, $sqitch->plan_file, 'File should be coopied from Sqitch'; +$target = App::Sqitch::Target->new(sqitch => $sqitch, plan_file => $file); +isa_ok $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target), + $CLASS, 'Plan with sqitch with plan file with dependencies'; +is $plan->file, $target->plan_file, 'File should be coopied from Sqitch'; is $plan->syntax_version, App::Sqitch::Plan::SYNTAX_VERSION, 'syntax_version should be set'; is $plan->project, 'pragmata', 'Project should be set'; From 2ed7d4419d0afb9d700da1d5d795520fd725d3b1 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 13:55:03 -0700 Subject: [PATCH 22/59] Update plan-related classes to use target. --- lib/App/Sqitch/Plan/Change.pm | 12 ++++----- lib/App/Sqitch/Plan/ChangeList.pm | 2 +- lib/App/Sqitch/Plan/Line.pm | 2 +- lib/App/Sqitch/Target.pm | 2 +- t/blank.t | 6 +++-- t/change.t | 41 ++++++++++++++++++------------- t/changelist.t | 9 +++++-- t/depend.t | 13 ++++++---- t/linelist.t | 4 ++- t/pragma.t | 6 +++-- t/tag.t | 9 +++++-- 11 files changed, 66 insertions(+), 40 deletions(-) diff --git a/lib/App/Sqitch/Plan/Change.pm b/lib/App/Sqitch/Plan/Change.pm index 7dd48b0d1..b6dea2783 100644 --- a/lib/App/Sqitch/Plan/Change.pm +++ b/lib/App/Sqitch/Plan/Change.pm @@ -83,10 +83,10 @@ has _path_segments => ( default => sub { my $self = shift; my @path = split m{/} => $self->name; - my $ext = '.' . $self->sqitch->extension; + my $ext = '.' . $self->target->extension; if (my @rework_tags = $self->rework_tags) { # Determine suffix based on the first one found in the deploy dir. - my $dir = $self->sqitch->deploy_dir; + my $dir = $self->target->deploy_dir; my $bn = pop @path; my $first; for my $tag (@rework_tags) { @@ -197,17 +197,17 @@ sub dependencies { sub deploy_file { my $self = shift; - $self->sqitch->deploy_dir->file( $self->path_segments ); + $self->target->deploy_dir->file( $self->path_segments ); } sub revert_file { my $self = shift; - $self->sqitch->revert_dir->file( $self->path_segments ); + $self->target->revert_dir->file( $self->path_segments ); } sub verify_file { my $self = shift; - $self->sqitch->verify_dir->file( $self->path_segments ); + $self->target->verify_dir->file( $self->path_segments ); } sub script_file { @@ -215,7 +215,7 @@ sub script_file { if ( my $meth = $self->can("$name\_file") ) { return $self->$meth; } - return $self->sqitch->top_dir->subdir($name)->cleanup->file( + return $self->target->top_dir->subdir($name)->cleanup->file( $self->path_segments ); } diff --git a/lib/App/Sqitch/Plan/ChangeList.pm b/lib/App/Sqitch/Plan/ChangeList.pm index 8d272b3b0..6d3c8b12b 100644 --- a/lib/App/Sqitch/Plan/ChangeList.pm +++ b/lib/App/Sqitch/Plan/ChangeList.pm @@ -54,7 +54,7 @@ sub _lookup { my $symtag = _dbsymtag $key or return $self->{lookup}{$key}; # XXX The rest of this only applies to the deprecated @FIRST & @LAST tags. my $change = $self->{list}[0] || return undef; - my $engine = $change->plan->sqitch->engine; + my $engine = $change->plan->target->engine; my $offset = _offset $key; $key = do { if ($symtag eq 'LAST') { diff --git a/lib/App/Sqitch/Plan/Line.pm b/lib/App/Sqitch/Plan/Line.pm index f91460063..6f2395708 100644 --- a/lib/App/Sqitch/Plan/Line.pm +++ b/lib/App/Sqitch/Plan/Line.pm @@ -57,7 +57,7 @@ has plan => ( isa => Plan, weak_ref => 1, required => 1, - handles => [qw(sqitch project uri)], + handles => [qw(sqitch project uri target)], ); my %escape = ( diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 0bda23edd..70e78bc80 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -108,7 +108,7 @@ has plan => ( # XXX Update to reference target. App::Sqitch::Plan->new( sqitch => $self->sqitch, - file => $self->plan_file, + target => $self, ); }, ); diff --git a/t/blank.t b/t/blank.t index 81a9b875f..2a2e20765 100644 --- a/t/blank.t +++ b/t/blank.t @@ -10,6 +10,7 @@ use Locale::TextDomain qw(App-Sqitch); use Test::NoWarnings; use Test::Exception; use App::Sqitch; +use App::Sqitch::Target; use App::Sqitch::Plan; use Test::MockModule; use Test::File; @@ -36,8 +37,9 @@ can_ok $CLASS, qw( note_prompt ); -my $sqitch = App::Sqitch->new; -my $plan = App::Sqitch::Plan->new(sqitch => $sqitch); +my $sqitch = App::Sqitch->new(options => { engine => 'sqlite'}); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target); isa_ok my $blank = $CLASS->new( name => 'foo', plan => $plan, diff --git a/t/change.t b/t/change.t index ca16e9e9a..0fb189f68 100644 --- a/t/change.t +++ b/t/change.t @@ -8,6 +8,7 @@ use Test::More tests => 85; #use Test::More 'no_plan'; use Test::NoWarnings; use App::Sqitch; +use App::Sqitch::Target; use App::Sqitch::Plan; use App::Sqitch::Plan::Tag; use Encode qw(encode_utf8); @@ -64,11 +65,15 @@ can_ok $CLASS, qw( note_prompt ); -my $sqitch = App::Sqitch->new( top_dir => dir('test-change') ); -my $plan = App::Sqitch::Plan->new(sqitch => $sqitch); +my $sqitch = App::Sqitch->new( options => { + engine => 'sqlite', + top_dir => dir('test-change')->stringify, +}); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target); make_path 'test-change'; END { remove_tree 'test-change' }; -my $fn = $sqitch->plan_file; +my $fn = $target->plan_file; open my $fh, '>', $fn or die "Cannot open $fn: $!"; say $fh "%project=change\n\n"; close $fh or die "Error closing $fn: $!"; @@ -92,11 +97,11 @@ my $tag = App::Sqitch::Plan::Tag->new( is_deeply [ $change->path_segments ], ['foo.sql'], 'path_segments should have the file name'; -is $change->deploy_file, $sqitch->deploy_dir->file('foo.sql'), +is $change->deploy_file, $target->deploy_dir->file('foo.sql'), 'The deploy file should be correct'; -is $change->revert_file, $sqitch->revert_dir->file('foo.sql'), +is $change->revert_file, $target->revert_dir->file('foo.sql'), 'The revert file should be correct'; -is $change->verify_file, $sqitch->verify_dir->file('foo.sql'), +is $change->verify_file, $target->verify_dir->file('foo.sql'), 'The verify file should be correct'; ok !$change->is_reworked, 'The change should not be reworked'; is_deeply [ $change->path_segments ], ['foo.sql'], @@ -106,8 +111,8 @@ is_deeply [ $change->path_segments ], ['foo.sql'], ok $change->add_rework_tags($tag), 'Add a rework tag'; is_deeply [$change->rework_tags], [$tag], 'Reworked tag should be stored'; ok $change->is_reworked, 'The change should be reworked'; -$sqitch->deploy_dir->mkpath; -$sqitch->deploy_dir->file('foo@alpha.sql')->touch; +$target->deploy_dir->mkpath; +$target->deploy_dir->file('foo@alpha.sql')->touch; is_deeply [ $change->path_segments ], ['foo@alpha.sql'], 'path_segments should now include suffix'; @@ -191,7 +196,7 @@ my $date = App::Sqitch::DateTime->new( sub dep($) { App::Sqitch::Plan::Depend->new( %{ App::Sqitch::Plan::Depend->parse(shift) }, - plan => $sqitch->plan, + plan => $target->plan, project => 'change', ) } @@ -283,11 +288,11 @@ my @fn = ('yo', 'howdy@beta.sql'); $change2->add_rework_tags($tag2); is_deeply [ $change2->path_segments ], \@fn, 'path_segments should include directories'; -is $change2->deploy_file, $sqitch->deploy_dir->file(@fn), +is $change2->deploy_file, $target->deploy_dir->file(@fn), 'The deploy file should include the suffix'; -is $change2->revert_file, $sqitch->revert_dir->file(@fn), +is $change2->revert_file, $target->revert_dir->file(@fn), 'The revert file should include the suffix'; -is $change2->verify_file, $sqitch->verify_dir->file(@fn), +is $change2->verify_file, $target->verify_dir->file(@fn), 'The verify file should include the suffix'; ############################################################################## @@ -332,11 +337,13 @@ is $fh->getline, "-- verify it, baby\n", 'It should be the verify file'; ############################################################################## # Test the requires/conflicts params. my $file = file qw(t plans multi.plan); -my $sqitch2 = App::Sqitch->new( - top_dir => dir('test-change'), - plan_file => $file, -); -my $plan2 = $sqitch2->plan; +my $sqitch2 = App::Sqitch->new(options => { + engine => 'sqlite', + top_dir => dir('test-change')->stringify, + plan_file => $file->stringify, +}); +my $target2 = App::Sqitch::Target->new(sqitch => $sqitch2); +my $plan2 = $target2->plan; ok $change2 = $CLASS->new( name => 'whatever', plan => $plan2, diff --git a/t/changelist.t b/t/changelist.t index 87656575b..074c4e98f 100644 --- a/t/changelist.t +++ b/t/changelist.t @@ -10,6 +10,7 @@ use Test::NoWarnings; use Test::Exception; use Path::Class; use App::Sqitch; +use App::Sqitch::Target; use App::Sqitch::Plan; use Locale::TextDomain qw(App-Sqitch); use Test::MockModule; @@ -20,8 +21,12 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; BEGIN { require_ok 'App::Sqitch::Plan::ChangeList' or die } -my $sqitch = App::Sqitch->new( _engine => 'sqlite', top_dir => dir qw(t sql) ); -my $plan = App::Sqitch::Plan->new(sqitch => $sqitch); +my $sqitch = App::Sqitch->new(options => { + engine => 'sqlite', + top_dir => dir(qw(t sql))->stringify, +}); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target); my $foo = App::Sqitch::Plan::Change->new(plan => $plan, name => 'foo'); my $bar = App::Sqitch::Plan::Change->new(plan => $plan, name => 'bar', parent => $foo); diff --git a/t/depend.t b/t/depend.t index ef99b9344..154104722 100644 --- a/t/depend.t +++ b/t/depend.t @@ -9,6 +9,7 @@ use Test::More tests => 326; use Test::Exception; #use Test::NoWarnings; use App::Sqitch; +use App::Sqitch::Target; use App::Sqitch::Plan; use Locale::TextDomain qw(App-Sqitch); @@ -23,10 +24,12 @@ BEGIN { require_ok $CLASS or die; } -ok my $sqitch = App::Sqitch->new( - top_dir => Path::Class::Dir->new(qw(t sql)), -), 'Load a sqitch sqitch object'; -my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, project => 'depend'); +ok my $sqitch = App::Sqitch->new(options => { + engine => 'sqlite', + top_dir => Path::Class::Dir->new(qw(t sql))->stringify, +}), 'Load a sqitch sqitch object'; +my $target = App::Sqitch::Target->new( sqitch => $sqitch ); +my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, project => 'depend', target => $target); can_ok $CLASS, qw( conflicts @@ -206,7 +209,7 @@ is $@->ident, 'plan', 'Nonexistent change error ident should be "plan"'; is $@->message, __x( 'Unable to find change "{change}" in plan {file}', change => 'nonexistent', - file => $plan->sqitch->plan_file, + file => $target->plan_file, ), 'Nonexistent change error message should be correct'; ############################################################################## diff --git a/t/linelist.t b/t/linelist.t index d8a7ef33d..96dce558a 100644 --- a/t/linelist.t +++ b/t/linelist.t @@ -9,6 +9,7 @@ use Test::More tests => 28; use Test::NoWarnings; use Test::Exception; use App::Sqitch; +use App::Sqitch::Target; use App::Sqitch::Plan; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; @@ -18,7 +19,8 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; BEGIN { require_ok 'App::Sqitch::Plan::LineList' or die } my $sqitch = App::Sqitch->new; -my $plan = App::Sqitch::Plan->new(sqitch => $sqitch); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target); my $foo = App::Sqitch::Plan::Change->new(plan => $plan, name => 'foo'); my $bar = App::Sqitch::Plan::Change->new(plan => $plan, name => 'bar'); diff --git a/t/pragma.t b/t/pragma.t index a3711d2cf..bcf5b6cd6 100644 --- a/t/pragma.t +++ b/t/pragma.t @@ -8,6 +8,7 @@ use Test::More tests => 10; #use Test::More 'no_plan'; use Test::NoWarnings; use App::Sqitch; +use App::Sqitch::Target; use App::Sqitch::Plan; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; @@ -33,8 +34,9 @@ can_ok $CLASS, qw( value ); -my $sqitch = App::Sqitch->new; -my $plan = App::Sqitch::Plan->new(sqitch => $sqitch); +my $sqitch = App::Sqitch->new(options => { engine => 'sqlite' }); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target); isa_ok my $dir = $CLASS->new( name => 'foo', plan => $plan, diff --git a/t/tag.t b/t/tag.t index d2d86b60a..b58fc13af 100644 --- a/t/tag.t +++ b/t/tag.t @@ -9,6 +9,7 @@ use Test::More tests => 27; use Test::NoWarnings; use Path::Class; use App::Sqitch; +use App::Sqitch::Target; use App::Sqitch::Plan; use Test::MockModule; use Digest::SHA; @@ -41,8 +42,12 @@ can_ok $CLASS, qw( format_planner ); -my $sqitch = App::Sqitch->new( top_dir => dir qw(t sql) ); -my $plan = App::Sqitch::Plan->new(sqitch => $sqitch); +my $sqitch = App::Sqitch->new(options => { + engine => 'sqlite', + top_dir => dir(qw(t sql))->stringify, +}); +my $target = App::Sqitch::Target->new(sqitch => $sqitch); +my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target); my $change = App::Sqitch::Plan::Change->new( plan => $plan, name => 'roles' ); isa_ok my $tag = $CLASS->new( From cb521f57c3720c74280aafa4ff1634b7d05b390f Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 14:11:31 -0700 Subject: [PATCH 23/59] Teach Target to pass itself to Plan. --- t/engine.t | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/t/engine.t b/t/engine.t index 6ba7eba5a..814ad3e8e 100644 --- a/t/engine.t +++ b/t/engine.t @@ -723,7 +723,7 @@ is_deeply +MockOutput->get_vent, [['WTF!']], # Try a change with no verify file. $engine->log_only(0); $mock_engine->unmock( 'verify_change' ); -$change = App::Sqitch::Plan::Change->new( name => 'foo', plan => $sqitch->plan ); +$change = App::Sqitch::Plan::Change->new( name => 'foo', plan => $target->plan ); ok $engine->deploy_change($change), 'Deploy a change with no verify script'; is_deeply $engine->seen, [ ['begin_work'], @@ -788,6 +788,7 @@ $sqitch = App::Sqitch->new( }, ); $target = App::Sqitch::Target->new( sqitch => $sqitch ); +$change = App::Sqitch::Plan::Change->new( name => 'foo', plan => $target->plan ); ok $engine = App::Sqitch::Engine::whu->new( sqitch => $sqitch, target => $target ), 'Engine with sqitch with plan file'; $plan = $target->plan; @@ -1569,9 +1570,9 @@ $mock_whu->unmock_all; ############################################################################## # Test is_deployed(). my $tag = App::Sqitch::Plan::Tag->new( - name => 'foo', + name => 'foo', change => $change, - plan => $sqitch->plan, + plan => $target->plan, ); $is_deployed_tag = $is_deployed_change = 1; ok $engine->is_deployed($tag), 'Test is_deployed(tag)'; @@ -1622,7 +1623,7 @@ DEPLOYDIE: { my @conflicts = $make_deps->( 1, qw(dr_evil) ); my $change = App::Sqitch::Plan::Change->new( name => 'foo', - plan => $sqitch->plan, + plan => $target->plan, requires => \@requires, conflicts => \@conflicts, ); @@ -2004,7 +2005,7 @@ is $@->ident, 'plan', 'Should get ident "plan" from change_id_for_depend'; is $@->message, __x( 'Unable to find change "{change}" in plan {file}', change => $dep->key_name, - file => $sqitch->plan_file, + file => $target->plan_file, ), 'Should have proper message from change_id_for_depend error'; PLANOK: { @@ -2074,7 +2075,7 @@ is_deeply $engine->seen, [ ############################################################################## # Test verify_change(). can_ok $CLASS, 'verify_change'; -$change = App::Sqitch::Plan::Change->new( name => 'users', plan => $sqitch->plan ); +$change = App::Sqitch::Plan::Change->new( name => 'users', plan => $target->plan ); ok $engine->verify_change($change), 'Verify a change'; is_deeply $engine->seen, [ [run_file => $change->verify_file ], @@ -2082,7 +2083,7 @@ is_deeply $engine->seen, [ is_deeply +MockOutput->get_info, [], 'Should have no info output'; # Try a change with no verify script. -$change = App::Sqitch::Plan::Change->new( name => 'roles', plan => $sqitch->plan ); +$change = App::Sqitch::Plan::Change->new( name => 'roles', plan => $target->plan ); ok $engine->verify_change($change), 'Verify a change with no verify script.'; is_deeply $engine->seen, [], 'No abstract methods should be called'; is_deeply +MockOutput->get_info, [], 'Should have no info output'; @@ -2134,7 +2135,7 @@ CHECK_DEPLOY_DEPEND: { my @conflicts = $make_deps->( 1, qw(foo bar) ); $change = App::Sqitch::Plan::Change->new( name => 'foo', - plan => $sqitch->plan, + plan => $target->plan, conflicts => \@conflicts, ); $plan->_changes->append($change); @@ -2213,7 +2214,7 @@ CHECK_DEPLOY_DEPEND: { my @requires = $make_deps->( 0, qw(foo bar) ); $change = App::Sqitch::Plan::Change->new( name => 'blah', - plan => $sqitch->plan, + plan => $target->plan, requires => \@requires, ); $plan->_changes->append($change); From 262163c6f85144013ae1211c489610e24240cba3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 14:13:11 -0700 Subject: [PATCH 24/59] Fix linelist test. --- t/linelist.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/linelist.t b/t/linelist.t index 96dce558a..f4b2aff07 100644 --- a/t/linelist.t +++ b/t/linelist.t @@ -18,7 +18,7 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; BEGIN { require_ok 'App::Sqitch::Plan::LineList' or die } -my $sqitch = App::Sqitch->new; +my $sqitch = App::Sqitch->new(options => { engine => 'sqlite' }); my $target = App::Sqitch::Target->new(sqitch => $sqitch); my $plan = App::Sqitch::Plan->new(sqitch => $sqitch, target => $target); From b68c45a9b65783205c0e1e8f89da75404196f767 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 14:17:02 -0700 Subject: [PATCH 25/59] Remove t/dburi.t. Now tested as part of the Target class. --- t/dburi.t | 126 ------------------------------------------------------ 1 file changed, 126 deletions(-) delete mode 100644 t/dburi.t diff --git a/t/dburi.t b/t/dburi.t deleted file mode 100644 index cf59f0dc7..000000000 --- a/t/dburi.t +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/perl -w - -use strict; -use warnings; -use 5.010; -use Test::More; -use Path::Class qw(dir file); -use App::Sqitch; -use Test::Exception; -use Test::MockModule; -use Locale::TextDomain qw(App-Sqitch); -use App::Sqitch::Engine; - -$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; -$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; -$ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; - -can_ok 'App::Sqitch::Engine', 'uri'; -my @sqitch_params = ( - plan_file => file(qw(t sql sqitch.plan)), - top_dir => dir(qw(t sql)), -); - -############################################################################## -# Test with no engine. -my $sqitch = App::Sqitch->new(@sqitch_params); - -isa_ok my $engine = App::Sqitch::Engine->new({ sqitch => $sqitch }), - 'App::Sqitch::Engine', 'Engine'; -throws_ok { $engine->uri } 'App::Sqitch::X', - 'Should get an exception when no engine'; -is $@->ident, 'engine', 'No _engine error ident should be "core"'; -is $@->message, __ 'No engine specified; use --engine or set core.engine', - 'No _engine error message should be correct'; - -############################################################################## -# Test with an engine. -$sqitch = App::Sqitch->new(@sqitch_params, _engine => 'sqlite'); -isa_ok $engine = $sqitch->engine, 'App::Sqitch::Engine::sqlite', 'SQLite Engine'; -isa_ok my $uri = $engine->uri, 'URI::db', 'SQLite URI'; -is $uri->as_string, 'db:sqlite:sql.db', 'SQLite URI should be correct'; - -# Different engine. -$sqitch = App::Sqitch->new(@sqitch_params, _engine => 'pg'); -isa_ok $engine = $sqitch->engine, 'App::Sqitch::Engine', 'Engine with Pg engine'; -isa_ok $uri = $engine->uri, 'URI::db', 'Pg URI'; -is $uri->as_string, 'db:pg:', 'Pg URI should be correct'; - -############################################################################## -# Test with configuration keys. -CONFIG: { - my $mock_config = Test::MockModule->new('App::Sqitch::Config'); - my @config_params; - my @config_ret = ('db:sqlite:hi'); - $mock_config->mock(get => sub { shift; @config_params = @_; shift @config_ret }); - my $e = $sqitch->engine; - is $e->uri, URI->new('db:sqlite:hi'), - 'URI should be the default for the engine'; - is_deeply \@config_params, [key => 'core.pg.target'], - 'Should have asked for the Pg default target'; - - # Test with key that contains another key. - my $mock_sqitch = Test::MockModule->new('App::Sqitch'); - my @sqitch_params; - my $sqitch_ret = { uri => URI::db->new('db:pg:yo'), target => 'db:pg:yo' }; - $mock_sqitch->mock(config_for_target_strict => sub { shift; @sqitch_params = @_; $sqitch_ret }); - @config_ret = ('yo'); - - $e = $sqitch->engine; - is $e->uri, $sqitch_ret->{uri}, 'URI should be from the target lookup'; - is_deeply \@config_params, [key => 'core.pg.target'], - 'Should have asked for the Pg default target again'; - is_deeply \@sqitch_params, ['yo'], 'Should have looked up the "yo" database'; - - # Test with uri configuration key. - @config_ret = (undef, 'db:pg:'); - $e = $sqitch->engine; - is $e->uri, URI->new('db:pg:'), - 'URI should get the engine-specific config key'; - is_deeply \@config_params, [key => 'core.pg.uri'], - 'Should have asked for the Pg default uri'; -} - -############################################################################## -# Add some other attributes. -push @sqitch_params, _engine => 'pg'; -for my $spec ( - [ 'host only', [ db_host => 'localhost' ], 'db:pg://localhost' ], - [ 'host and port', [ db_host => 'foo', db_port => 3333 ], 'db:pg://foo:3333' ], - [ 'username', [ db_username => 'fred' ], 'db:pg://fred@' ], - [ 'db name', [ db_name => 'try' ], 'db:pg:try' ], - [ - 'host and db name', - [ db_host => 'foo.com', db_name => '/try.db' ], - 'db:pg://foo.com//try.db', - ], - [ - 'all parts', - [ db_host => 'foo.us', db_port => 2, db_username => 'al', db_name => 'blah' ], - 'db:pg://al@foo.us:2/blah', - ] -) { - my ($desc, $params, $uri) = @{ $spec }; - my $sqitch = App::Sqitch->new(@sqitch_params, @{ $params }); - isa_ok my $engine = $sqitch->engine, 'App::Sqitch::Engine', "Engine with $desc"; - is $engine->uri->as_string, $uri, "Default URI with $desc should be correct"; -} - -############################################################################## -# Make sure URIs passed to the construtor get merged. -$sqitch = App::Sqitch->new(@sqitch_params, db_name => 'foo'); -isa_ok $engine = App::Sqitch::Engine->new({ - sqitch => $sqitch, - uri => URI->new('db:pg:blah'), -}), 'App::Sqitch::Engine', 'Engine with URI'; -is $engine->uri->as_string, 'db:pg:foo', 'DB name should be merged into URI'; - -$sqitch = App::Sqitch->new(@sqitch_params, db_name => 'foo', db_host => 'foo.com'); -isa_ok $engine = App::Sqitch::Engine->new({ - sqitch => $sqitch, - uri => URI->new('db:pg://localhost:1234/blah'), -}), 'App::Sqitch::Engine', 'Engine with full URI'; -is $engine->uri->as_string, 'db:pg://foo.com:1234/foo', - 'DB host and name should be merged into URI'; - -done_testing; From be8ab3922b16d03a99505005c6aa15390ac67c39 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 14:21:43 -0700 Subject: [PATCH 26/59] Always use target plan for db tests. --- t/lib/DBIEngineTest.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/t/lib/DBIEngineTest.pm b/t/lib/DBIEngineTest.pm index 2b08a4d74..9dcd1c619 100644 --- a/t/lib/DBIEngineTest.pm +++ b/t/lib/DBIEngineTest.pm @@ -146,7 +146,7 @@ sub run { # Register a different project name. MOCKPROJECT: { - my $plan_mocker = Test::MockModule->new(ref $sqitch->plan ); + my $plan_mocker = Test::MockModule->new(ref $target->plan ); $plan_mocker->mock(project => 'groovy'); $plan_mocker->mock(uri => 'http://example.com/'); ok $engine->register_project, 'Register a second project'; @@ -163,7 +163,7 @@ sub run { # Try to register with a different URI. MOCKURI: { - my $plan_mocker = Test::MockModule->new(ref $sqitch->plan ); + my $plan_mocker = Test::MockModule->new(ref $target->plan ); my $plan_proj = 'engine'; my $plan_uri = 'http://example.net/'; $plan_mocker->mock(project => sub { $plan_proj }); @@ -1037,7 +1037,7 @@ sub run { # Add an external project event. ok my $ext_plan = App::Sqitch::Plan->new( sqitch => $sqitch, - file => $target->plan_file, + target => $target, project => 'groovy', ), 'Create external plan'; ok my $ext_change = $ext_plan->add( From 6fbfe96ca917082d7ab4f871f5eaa671d9a9930b Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 14:36:53 -0700 Subject: [PATCH 27/59] Get Firebird tests passing with Target. --- t/firebird.t | 73 +++++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/t/firebird.t b/t/firebird.t index 91dbe62f3..a3321ebf0 100644 --- a/t/firebird.t +++ b/t/firebird.t @@ -7,6 +7,7 @@ use warnings; use 5.010; use Test::More; use App::Sqitch; +use App::Sqitch::Target; use Test::MockModule; use Path::Class; use Try::Tiny; @@ -48,8 +49,12 @@ is_deeply [$CLASS->config_vars], [ client => 'any', ], 'config_vars should return three vars'; -my $sqitch = App::Sqitch->new(_engine => 'firebird', db_name => 'foo.fdb'); -isa_ok my $fb = $CLASS->new(sqitch => $sqitch), $CLASS; +my $sqitch = App::Sqitch->new(options => { engine => 'firebird' }); +my $target = App::Sqitch::Target->new( + sqitch => $sqitch, + uri => URI->new('db:firebird:foo.fdb'), +); +isa_ok my $fb = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; my $have_fb_client; if ($have_fb_driver && (my $client = try { $fb->client })) { @@ -77,7 +82,7 @@ my $dbname = $fb->connection_string($fb->uri); is_deeply([$fb->isql], [$fb->client, @std_opts, $dbname], 'isql command should be std opts-only') if $have_fb_client; -isa_ok $fb = $CLASS->new(sqitch => $sqitch, db_name => 'foo'), $CLASS; +isa_ok $fb = $CLASS->new(sqitch => $sqitch, target => $target), $CLASS; ok $fb->set_variables(foo => 'baz', whu => 'hi there', yo => 'stellar'), 'Set some variables'; @@ -88,13 +93,14 @@ is_deeply([$fb->isql], [$fb->client, @std_opts, $dbname], # Make sure config settings override defaults. my %config = ( 'core.firebird.client' => '/path/to/isql', - 'core.firebird.uri' => 'db:firebird://freddy:s3cr3t@db.example.com:1234/widgets', + 'core.firebird.target' => 'db:firebird://freddy:s3cr3t@db.example.com:1234/widgets', 'core.firebird.registry' => 'meta', ); my $mock_config = Test::MockModule->new('App::Sqitch::Config'); $mock_config->mock(get => sub { $config{ $_[2] } }); -$sqitch = App::Sqitch->new( _engine => 'firebird' ); -ok $fb = $CLASS->new(sqitch => $sqitch), 'Create another firebird'; +$sqitch = App::Sqitch->new(options => { engine => 'firebird' }); +$target = App::Sqitch::Target->new(sqitch => $sqitch); +ok $fb = $CLASS->new(sqitch => $sqitch, target => $target), 'Create another firebird'; is $fb->client, '/path/to/isql', 'client should be as configured'; is $fb->uri, URI::db->new('db:firebird://freddy:s3cr3t@db.example.com:1234/widgets'), @@ -110,33 +116,26 @@ is_deeply [$fb->isql], [( ), @std_opts, 'db.example.com/1234:widgets'], 'firebird command should be configured'; ############################################################################## -# Now make sure that (deprecated?) Sqitch options override configurations. -$sqitch = App::Sqitch->new( - _engine => 'firebird', - db_client => '/some/other/isql', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, -); +# Now make sure that Sqitch options override configurations. +$sqitch = App::Sqitch->new(options => { + engine => 'firebird', + client => '/some/other/isql', + registry => 'meta', +}); +$target = App::Sqitch::Target->new(sqitch => $sqitch); -ok $fb = $CLASS->new(sqitch => $sqitch), +ok $fb = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a firebird with sqitch with options'; is $fb->client, '/some/other/isql', 'client should be as optioned'; -is $fb->uri, URI::db->new('db:firebird://anna:s3cr3t@foo.com:98760/widgets_dev'), - 'URI should include option values.'; -like $fb->destination, qr{db:firebird://anna:?\@foo.com:98760/widgets_dev}, - 'destination should be URI without password_name'; -is $fb->registry_uri, URI::db->new('db:firebird://anna:s3cr3t@foo.com:98760/meta'), - 'Registry URI should include option values.'; -like $fb->registry_destination, qr{db:firebird://anna:?\@foo.com:98760/meta}, - 'meta_destination should be correct'; +is $fb->registry_uri, + URI::db->new('db:firebird://freddy:s3cr3t@db.example.com:1234/meta'), + 'Registry URI should include --registry value.'; is_deeply [$fb->isql], [( '/some/other/isql', - '-user', 'anna', + '-user', 'freddy', '-password', 's3cr3t', -), @std_opts, 'foo.com/98760:widgets_dev'], 'isql command should be as optioned'; +), @std_opts, 'db.example.com/1234:widgets'], 'isql command should be as optioned'; ############################################################################## # Test connection_string. @@ -172,6 +171,7 @@ can_ok $fb, 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) { @@ -183,6 +183,7 @@ $mock_sqitch->mock(run => sub { my @capture; $mock_sqitch->mock(capture => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @capture = @_; if (defined $exp_pass) { @@ -194,6 +195,7 @@ $mock_sqitch->mock(capture => sub { my @spool; $mock_sqitch->mock(spool => sub { + local $Test::Builder::Level = $Test::Builder::Level + 2; shift; @spool = @_; if (defined $exp_pass) { @@ -204,6 +206,7 @@ $mock_sqitch->mock(spool => sub { }); $exp_pass = 's3cr3t'; +$target->uri->password($exp_pass); ok $fb->_run(qw(foo bar baz)), 'Call _run'; is_deeply \@run, [$fb->isql, qw(foo bar baz)], 'Command should be passed to run()'; @@ -217,9 +220,9 @@ is_deeply \@capture, [$fb->isql, qw(foo bar baz)], 'Command should be passed to capture()'; # Remove the password from the URI. -$config{'core.firebird.uri'} - = 'db:firebird://freddy@db.example.com:1234/widgets'; -ok $fb = $CLASS->new(sqitch => $sqitch), 'Create a firebird with sqitch with no pw'; +$target->uri->password(undef); +ok $fb = $CLASS->new(sqitch => $sqitch, target => $target), + 'Create a firebird with sqitch with no pw'; $exp_pass = undef; ok $fb->_run(qw(foo bar baz)), 'Call _run again'; is_deeply \@run, [$fb->isql, qw(foo bar baz)], @@ -344,13 +347,13 @@ my $err = try { my $uri = URI::db->new("db:firebird://$user:$pass\@localhost/$dbpath"); DBIEngineTest->run( class => $CLASS, - sqitch_params => [ + sqitch_params => [options => { _engine => 'firebird', - top_dir => Path::Class::dir(qw(t engine)), - plan_file => Path::Class::file(qw(t engine sqitch.plan)), - ], - engine_params => [ uri => $uri, registry => catfile($tmpdir, '__metasqitch') ], - alt_engine_params => [ uri => $uri, registry => catfile($tmpdir, '__sqitchtest') ], + top_dir => Path::Class::dir(qw(t engine))->stringify, + plan_file => Path::Class::file(qw(t engine sqitch.plan))->stringify, + }], + target_params => [ uri => $uri, registry => catfile($tmpdir, '__metasqitch') ], + alt_target_params => [ uri => $uri, registry => catfile($tmpdir, '__sqitchtest') ], skip_unless => sub { my $self = shift; From 64197cbd940396844931949bb0b565d332a97a23 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 15:51:29 -0700 Subject: [PATCH 28/59] Get command working with target. Mainly need to let parse_args work. It now finds changes in the plan for the last target passed. --- lib/App/Sqitch/Command.pm | 23 +++++++++++++++-------- t/command.t | 20 ++++++++++++++------ t/local.conf | 1 + 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/lib/App/Sqitch/Command.pm b/lib/App/Sqitch/Command.pm index e594dbd5c..e063e0efe 100644 --- a/lib/App/Sqitch/Command.pm +++ b/lib/App/Sqitch/Command.pm @@ -18,7 +18,6 @@ has sqitch => ( isa => Sqitch, required => 1, handles => [qw( - plan engine config_for_target config_for_target_strict @@ -179,9 +178,11 @@ sub usage { sub parse_args { my $self = shift; - my $plan = $self->plan; - my $config = $self->sqitch->config; + my $sqitch = $self->sqitch; + my $config = $sqitch->config; + require App::Sqitch::Target; require URI; + my $target = try { App::Sqitch::Target->new(sqitch => $self->sqitch) }; my %ret = ( changes => [], @@ -189,11 +190,17 @@ sub parse_args { unknown => [], ); for my $arg (@_) { - my $ref = $plan->contains($arg) ? $ret{changes} - : URI->new($arg)->isa('URI::db') ? $ret{targets} - : $config->get( key => "target.$arg.uri") ? $ret{targets} - : $ret{unknown}; - push @{ $ref } => $arg; + if ( $target && $target->plan->contains($arg) ) { + # It's a change. + push @{ $ret{changes} } => $arg; + } elsif ($config->get( key => "target.$arg.uri") || URI->new($arg)->isa('URI::db')) { + # It's a target; load the plan to search for other change params. + push @{ $ret{targets} } => $arg; + $target = App::Sqitch::Target->new( sqitch => $sqitch, name => $arg ); + } else { + # Who knows? + push @{ $ret{unknown} } => $arg; + } } return %ret; diff --git a/t/command.t b/t/command.t index 189bb1b3b..64073661a 100644 --- a/t/command.t +++ b/t/command.t @@ -4,7 +4,7 @@ use strict; use warnings; use 5.010; use utf8; -use Test::More tests => 114; +use Test::More tests => 117; #use Test::More 'no_plan'; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; @@ -253,11 +253,11 @@ PARSEOPTSERR: { # Test argument passing. ARGS: { local $ENV{SQITCH_CONFIG} = file qw(t local.conf); - ok $sqitch = App::Sqitch->new( - _engine => 'sqlite', - plan_file => file(qw(t plans multi.plan)), - top_dir => dir qw(t sql) - ), 'Load Sqitch with config and plan'; + ok $sqitch = App::Sqitch->new(options => { + engine => 'sqlite', + plan_file => file(qw(t plans multi.plan))->stringify, + top_dir => dir(qw(t sql))->stringify + }), 'Load Sqitch with config and plan'; ok my $cmd = $CLASS->new({ sqitch => $sqitch }), 'Load cmd with config and plan'; is_deeply { $cmd->parse_args }, { changes => [], targets => [], unknown => [] }, 'Parsing now args should return no results'; @@ -286,6 +286,14 @@ ARGS: { { changes => ['hey', 'hey-there'], targets => ['devdb'], unknown => ['foo'] }, 'Multiple changes, target, and unknown should be recognized'; + ok $sqitch = App::Sqitch->new(options => { + engine => 'sqlite', + top_dir => dir(qw(t sql))->stringify + }), 'Load Sqitch with config'; + ok $cmd = $CLASS->new({ sqitch => $sqitch }), 'Load cmd with config'; + is_deeply { $cmd->parse_args('devdb', 'you', 'add_user') }, + { changes => ['add_user'], targets => ['devdb'], unknown => ['you'] }, + 'Change following target should be recognized from target plan'; } ############################################################################## diff --git a/t/local.conf b/t/local.conf index f03d5817e..d3d057e74 100644 --- a/t/local.conf +++ b/t/local.conf @@ -9,3 +9,4 @@ [target "devdb"] uri = db:sqlite: + plan_file = t/plans/dependencies.plan From 977b8e20f26ef133f8baec1a4677bcebc7911b92 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Tue, 21 Oct 2014 18:03:57 -0700 Subject: [PATCH 29/59] Pass target to parse_args(). If it's there, always return it. Otherwise always reurn the default target if no target is recognized in the arguments, or if a change is recognized from the default target. --- lib/App/Sqitch/Command.pm | 34 +++++++++++++++++------- t/command.t | 55 +++++++++++++++++++++++++++++---------- t/local.conf | 3 +++ 3 files changed, 68 insertions(+), 24 deletions(-) mode change 100644 => 100755 t/command.t diff --git a/lib/App/Sqitch/Command.pm b/lib/App/Sqitch/Command.pm index e063e0efe..9fbd63641 100644 --- a/lib/App/Sqitch/Command.pm +++ b/lib/App/Sqitch/Command.pm @@ -8,6 +8,7 @@ use Try::Tiny; use Locale::TextDomain qw(App-Sqitch); use App::Sqitch::X qw(hurl); use Hash::Merge 'merge'; +use List::Util qw(first); use Moo; use App::Sqitch::Types qw(Sqitch); @@ -177,32 +178,37 @@ sub usage { } sub parse_args { - my $self = shift; + my ($self, %p) = @_; my $sqitch = $self->sqitch; my $config = $sqitch->config; require App::Sqitch::Target; - require URI; - my $target = try { App::Sqitch::Target->new(sqitch => $self->sqitch) }; + my $target = App::Sqitch::Target->new( sqitch => $sqitch, name => $p{target} ); - my %ret = ( + my %ret = ( changes => [], - targets => [], + targets => [$p{target} ? $target : ()], unknown => [], ); - for my $arg (@_) { + for my $arg (@{ $p{args} }) { if ( $target && $target->plan->contains($arg) ) { - # It's a change. + # A change. Keep the target if it's the default. + push @{ $ret{targets} } => $target unless @{ $ret{targets} }; push @{ $ret{changes} } => $arg; } elsif ($config->get( key => "target.$arg.uri") || URI->new($arg)->isa('URI::db')) { - # It's a target; load the plan to search for other change params. - push @{ $ret{targets} } => $arg; + # A target. Instantiate and keep for subsequente change searches. $target = App::Sqitch::Target->new( sqitch => $sqitch, name => $arg ); + push @{ $ret{targets} } => $target unless first { + $target->name eq $_->name + } @{ $ret{targets} }; } else { # Who knows? push @{ $ret{unknown} } => $arg; } } + # Make sure we have the default target + push @{ $ret{targets} } => $target if $target && !@{ $ret{targets} }; + return %ret; } @@ -347,7 +353,7 @@ use. =head3 C - my @parsed_args = $cmd->parse_args(@args); + my %parsed_args = $cmd->parse_args(target => $target_name, args => \@args); Examines each argument to determine whether it's a known change spec or target. Returns a list of two-value array references, one for each argument @@ -356,6 +362,14 @@ passed. For each array reference, the first item is the argument type, either Useful for commands that take a number of parameters where the order may be mixed. +If a target param is passed, it is the default target and will always be +returned instantiated, and arguments recognized as changes in that target will +be returned as changes. If a target name is specified in the arguments, it +will be instantiated and returned under the targets key and any subsequent +changes must be recognized from I plan. If no target is passed or appears +in the arguments, the default target will be used and any changes must be +recognized from it. + =head3 C $cmd->run('echo hello'); diff --git a/t/command.t b/t/command.t old mode 100644 new mode 100755 index 64073661a..5d9bf8e8f --- a/t/command.t +++ b/t/command.t @@ -4,7 +4,7 @@ use strict; use warnings; use 5.010; use utf8; -use Test::More tests => 117; +use Test::More tests => 122; #use Test::More 'no_plan'; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; @@ -22,6 +22,7 @@ BEGIN { } use App::Sqitch; +use App::Sqitch::Target; use Test::Exception; use Test::NoWarnings; use Test::MockModule; @@ -258,42 +259,68 @@ ARGS: { plan_file => file(qw(t plans multi.plan))->stringify, top_dir => dir(qw(t sql))->stringify }), 'Load Sqitch with config and plan'; + ok my $cmd = $CLASS->new({ sqitch => $sqitch }), 'Load cmd with config and plan'; - is_deeply { $cmd->parse_args }, { changes => [], targets => [], unknown => [] }, + my $parsem = sub { + my %ret = $cmd->parse_args(@_); + $ret{targets} = [ map { $_->name } @{ $ret{targets} } ]; + return \%ret; + }; + + is_deeply $parsem->(), + { changes => [], targets => ['devdb'], unknown => [] }, 'Parsing now args should return no results'; - is_deeply { $cmd->parse_args('foo') }, - { changes => [], targets => [], unknown => ['foo'] }, + is_deeply $parsem->( args => ['foo'] ), + { changes => [], targets => ['devdb'], unknown => ['foo'] }, 'Single unknown arg should be returned unknown'; - is_deeply { $cmd->parse_args('hey') }, - { changes => ['hey'], targets => [], unknown => [] }, + is_deeply $parsem->( args => ['hey'] ), + { changes => ['hey'], targets => ['devdb'], unknown => [] }, 'Single change should be recognized as change'; - is_deeply { $cmd->parse_args('devdb') }, + is_deeply $parsem->( args => ['devdb'] ), { changes => [], targets => ['devdb'], unknown => [] }, 'Single target should be recognized as target'; - is_deeply { $cmd->parse_args('db:pg:') }, + is_deeply $parsem->(args => ['db:pg:']), { changes => [], targets => ['db:pg:'], unknown => [] }, 'URI target should be recognized as target, too'; - is_deeply { $cmd->parse_args('devdb', 'hey') }, + is_deeply $parsem->(args => ['devdb', 'hey']), { changes => ['hey'], targets => ['devdb'], unknown => [] }, 'Target and change should be recognized'; - is_deeply { $cmd->parse_args('hey', 'devdb') }, + is_deeply $parsem->(args => ['hey', 'devdb']), { changes => ['hey'], targets => ['devdb'], unknown => [] }, 'Change and target should be recognized'; - is_deeply { $cmd->parse_args('hey', 'devdb', 'foo') }, + is_deeply $parsem->(args => ['hey', 'devdb', 'foo']), { changes => ['hey'], targets => ['devdb'], unknown => ['foo'] }, 'Change, target, and unknown should be recognized'; - is_deeply { $cmd->parse_args('hey', 'devdb', 'foo', 'hey-there') }, + is_deeply $parsem->(args => ['hey', 'devdb', 'foo', 'hey-there']), { changes => ['hey', 'hey-there'], targets => ['devdb'], unknown => ['foo'] }, 'Multiple changes, target, and unknown should be recognized'; + # Make sure changes are found in previously-passed target. ok $sqitch = App::Sqitch->new(options => { engine => 'sqlite', top_dir => dir(qw(t sql))->stringify }), 'Load Sqitch with config'; ok $cmd = $CLASS->new({ sqitch => $sqitch }), 'Load cmd with config'; - is_deeply { $cmd->parse_args('devdb', 'you', 'add_user') }, - { changes => ['add_user'], targets => ['devdb'], unknown => ['you'] }, + is_deeply $parsem->(args => ['mydb', 'you', 'add_user']), + { changes => ['add_user'], targets => ['mydb'], unknown => ['you'] }, 'Change following target should be recognized from target plan'; + + # Now pass a target. + is_deeply $parsem->(target => 'devdb'), + { changes => [], targets => ['devdb'], unknown => [] }, + 'Passed target should always be returned'; + is_deeply $parsem->(target => 'devdb', args => ['mydb']), + { changes => [], targets => ['devdb', 'mydb'], unknown => [] }, + 'Passed and specified targets should always be returned'; + is_deeply $parsem->(target => 'devdb', args => ['hey']), + { changes => [], targets => ['devdb'], unknown => ['hey'] }, + 'Change unknown to passed target should be returned as unknown'; + is_deeply $parsem->(args => ['widgets', 'foo', '@beta']), + { changes => ['widgets', '@beta'], targets => ['devdb'], unknown => ['foo'] }, + 'Should get known changes from default target (t/sql/sqitch.plan)'; + is_deeply $parsem->(args => ['widgets', 'mydb', 'foo', '@beta']), + { changes => ['widgets'], targets => ['devdb', 'mydb'], unknown => ['foo', '@beta'] }, + 'Change seen after target should be unknown if not in that target'; } ############################################################################## diff --git a/t/local.conf b/t/local.conf index d3d057e74..5b30bf555 100644 --- a/t/local.conf +++ b/t/local.conf @@ -9,4 +9,7 @@ [target "devdb"] uri = db:sqlite: + +[target "mydb"] + uri = db:pg:mydb plan_file = t/plans/dependencies.plan From 7c2785f62ca98ac45276468e2b7b916911278968 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 13:19:53 -0700 Subject: [PATCH 30/59] Update deploy command to use Target. --- lib/App/Sqitch/Command/deploy.pm | 8 ++--- t/deploy.t | 54 ++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/lib/App/Sqitch/Command/deploy.pm b/lib/App/Sqitch/Command/deploy.pm index 5d14d1a98..118307d92 100644 --- a/lib/App/Sqitch/Command/deploy.pm +++ b/lib/App/Sqitch/Command/deploy.pm @@ -100,7 +100,7 @@ sub configure { sub execute { my $self = shift; - my %args = $self->parse_args(@_); + my %args = $self->parse_args(target => $self->target, args => \@_); # Die on unknowns. if (my @unknown = @{ $args{unknown}} ) { @@ -113,10 +113,10 @@ sub execute { } # Warn on multiple targets. - my $target = $self->target // shift @{ $args{targets} }; + my $target = shift @{ $args{targets} }; $self->warn(__x( 'Too many targets specified; connecting to {target}', - target => $target, + target => $target->name, )) if @{ $args{targets} }; # Warn on too many changes. @@ -127,7 +127,7 @@ sub execute { )) if @{ $args{changes} }; # Now get to work. - my $engine = $self->engine_for_target($target); + my $engine = $target->engine; $engine->with_verify( $self->verify ); $engine->log_only( $self->log_only ); if (my %v = %{ $self->variables }) { $engine->set_variables(%v) } diff --git a/t/deploy.t b/t/deploy.t index 4a538e191..3614bd8c4 100644 --- a/t/deploy.t +++ b/t/deploy.t @@ -43,9 +43,11 @@ is_deeply [$CLASS->options], [qw( )], 'Options should be correct'; my $sqitch = App::Sqitch->new( - plan_file => file(qw(t sql sqitch.plan)), - top_dir => dir(qw(t sql)), - _engine => 'sqlite', + options => { + engine => 'sqlite', + plan_file => file(qw(t sql sqitch.plan))->stringify, + top_dir => dir(qw(t sql))->stringify, + }, ); my $config = $sqitch->config; @@ -118,15 +120,25 @@ isa_ok my $deploy = $CLASS->new( ), $CLASS, 'new deploy with target'; is $deploy->target, 'foo', 'Should have target "foo"'; - isa_ok $deploy = $CLASS->new(sqitch => $sqitch), $CLASS; is $deploy->target, undef, 'Should have undef default target'; is $deploy->to_change, undef, 'to_change should be undef'; is $deploy->mode, 'all', 'mode should be "all"'; +# Mock parse_args() so that we can grab the target it returns. +my $mock_cmd = Test::MockModule->new($CLASS); +my $parser; +my $target; +$mock_cmd->mock(parse_args => sub { + my %ret = $parser->(@_); + $target = $ret{targets}[0]; + return %ret; +}); +$parser = $mock_cmd->original('parse_args'); + # Mock the engine interface. -my $mock_engine = Test::MockModule->new('App::Sqitch::Engine::sqlite'); +my $mock_engine = Test::MockModule->new('App::Sqitch::Engine'); my @args; $mock_engine->mock(deploy => sub { shift; @args = @_ }); my @vars; @@ -135,7 +147,8 @@ $mock_engine->mock(set_variables => sub { shift; @vars = @_ }); ok $deploy->execute('@alpha'), 'Execute to "@alpha"'; is_deeply \@args, ['@alpha', 'all'], '"@alpha" "all", and 0 should be passed to the engine'; -ok !$sqitch->engine->log_only, 'The engine should not be set log_only'; +ok $target, 'Should have a target'; +ok !$target->engine->log_only, 'The engine should not be set log_only'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; @args = (); @@ -151,29 +164,24 @@ is_deeply \@args, ['widgets', 'all'], is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Try passing the target. -my $mock_sqitch = Test::MockModule->new(ref $sqitch); -my ($engine, $orig_emethod); -$mock_sqitch->mock(engine => sub { $engine = shift->$orig_emethod(@_) }); -$orig_emethod = $mock_sqitch->original('engine'); - ok $deploy->execute('db:pg:foo'), 'Execute with target'; is_deeply \@args, [undef, 'all'], 'undef and "all" should be passed to the engine'; -is $engine->target, 'db:pg:foo', 'The engine should know the target'; +is $target->name, 'db:pg:foo', 'The target should be as specified'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Pass both! -ok $deploy->execute('widgets', 'db:pg:blah'), 'Execute with change and target'; +ok $deploy->execute('db:pg:blah', 'widgets'), 'Execute with change and target'; is_deeply \@args, ['widgets', 'all'], '"widgets" and "all" should be passed to the engine'; -is $engine->target, 'db:pg:blah', 'The engine should know the target'; +is $target->name, 'db:pg:blah', 'The target should be as specified'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Reverse them! ok $deploy->execute('db:pg:blah', 'widgets'), 'Execute with target and change'; is_deeply \@args, ['widgets', 'all'], '"widgets" and "all" should be passed to the engine'; -is $engine->target, 'db:pg:blah', 'The engine should know the target'; +is $target->name, 'db:pg:blah', 'The target should be as specified'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Now pass a bunch of options. @@ -189,19 +197,19 @@ isa_ok $deploy = $CLASS->new( @args = (); ok $deploy->execute, 'Execute again'; -ok $engine->with_verify, 'Engine should verify'; -ok $engine->log_only, 'The engine should be set log_only'; +ok $target->engine->with_verify, 'Engine should verify'; +ok $target->engine->log_only, 'The engine should be set log_only'; is_deeply \@args, ['foo', 'tag'], '"foo", "tag", and 1 should be passed to the engine'; is_deeply {@vars}, { foo => 'bar', one => 1 }, 'Vars should have been passed through to the engine'; -is $engine->target, 'db:pg:hi', 'The engine should have the target option'; +is $target->name, 'db:pg:hi', 'The target name should be from the target option'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Try passing the change. ok $deploy->execute('widgets'), 'Execute with change'; -ok $engine->with_verify, 'Engine should verify'; -ok $engine->log_only, 'The engine should be set log_only'; +ok $target->engine->with_verify, 'Engine should verify'; +ok $target->engine->log_only, 'The engine should be set log_only'; is_deeply \@args, ['foo', 'tag'], '"foo", "tag", and 1 should be passed to the engine'; is_deeply {@vars}, { foo => 'bar', one => 1 }, @@ -213,13 +221,13 @@ is_deeply +MockOutput->get_warn, [[__x( # Pass the target. ok $deploy->execute('db:pg:bye'), 'Execute with target again'; -ok $engine->with_verify, 'Engine should verify'; -ok $engine->log_only, 'The engine should be set log_only'; +ok $target->engine->with_verify, 'Engine should verify'; +ok $target->engine->log_only, 'The engine should be set log_only'; is_deeply \@args, ['foo', 'tag'], '"foo", "tag", and 1 should be passed to the engine'; is_deeply {@vars}, { foo => 'bar', one => 1 }, 'Vars should have been passed through to the engine'; -is $engine->target, 'db:pg:hi', 'The engine should have the target option'; +is $target->name, 'db:pg:hi', 'The target should be from the target option'; is_deeply +MockOutput->get_warn, [[__x( 'Too many targets specified; connecting to {target}', target => 'db:pg:hi', From 1b08eb9b4aea569c6be63a947f5172f6eb63d8ce Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 15:39:27 -0700 Subject: [PATCH 31/59] Make sure target works with no engine key. --- lib/App/Sqitch/Engine.pm | 4 ++++ lib/App/Sqitch/Target.pm | 2 +- t/target.t | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/lib/App/Sqitch/Engine.pm b/lib/App/Sqitch/Engine.pm index 6cb4737a2..2afd1fcea 100644 --- a/lib/App/Sqitch/Engine.pm +++ b/lib/App/Sqitch/Engine.pm @@ -104,6 +104,10 @@ sub load { my $target = $p->{target} or hurl 'Missing "target" parameter to load()'; # Load the engine class. + my $ekey = $target->engine_key or hurl engine => __( + 'No engine specified; use --engine or set core.engine' + ); + my $pkg = __PACKAGE__ . '::' . $target->engine_key; eval "require $pkg" or hurl "Unable to load $pkg"; return $pkg->new( $p ); diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 70e78bc80..d2c4bb3df 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -57,7 +57,7 @@ sub _fetch { my $config = $sqitch->config; return $config->get( key => "target." . $self->name . ".$key" ) - || $config->get( key => "core." . $self->engine->key . ".$key") + || $config->get( key => "core." .($self->engine_key || '') . ".$key") || $config->get( key => "core.$key"); } diff --git a/t/target.t b/t/target.t index 0966da188..fd9f5dcd8 100644 --- a/t/target.t +++ b/t/target.t @@ -140,6 +140,42 @@ CONSTRUCTOR: { 'No engine specified; use --engine or set core.engine' ), 'Should have message about no specified engine'; + # Try with engine-less URI. + @get_params = (); + isa_ok my $target = $CLASS->new( + sqitch => $sqitch, + uri => URI::db->new('db:'), + ), $CLASS, 'Engineless target'; + is $target->name, 'db:', 'Name should be "db:"'; + is $target->uri, URI::db->new('db:'), 'URI should be "db:"'; + is_deeply \@get_params, [], 'Should not have tried to get engine target'; + + is $target->sqitch, $sqitch, 'Sqitch should be as passed'; + is $target->engine_key, undef, 'Engine key should be undef'; + throws_ok { $target->engine } 'App::Sqitch::X', + 'Should get exception for no engine'; + is $@->ident, 'engine', 'Should have engine ident'; + is $@->message, __( + 'No engine specified; use --engine or set core.engine' + ), 'Should have message about no engine'; + + is $target->top_dir, dir, 'Should have default top_dir'; + is $target->deploy_dir, $target->top_dir->subdir('deploy'), + 'Should have default deploy_dir'; + is $target->revert_dir, $target->top_dir->subdir('revert'), + 'Should have default revert_dir'; + is $target->verify_dir, $target->top_dir->subdir('verify'), + 'Should have default verify_dir'; + is $target->extension, 'sql', 'Should have default extension'; + is $target->plan_file, $target->top_dir->file('sqitch.plan')->cleanup, + 'Should have default plan file'; + isa_ok $target->plan, 'App::Sqitch::Plan', 'Should get plan'; + is $target->plan->file, $target->plan_file, + 'Plan file should be copied from Target'; + is $target->dsn, '', 'DSN should be empty'; + is $target->username, undef, 'Username should be undef'; + is $target->password, undef, 'Password should be undef'; + # Mock get_section. my @sect_params; my @sect_ret = ({}); From 34298cc2398b024ca4a5253c67279615be391602 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 16:43:04 -0700 Subject: [PATCH 32/59] Add the `default_target` attribute to Command. To be used by commands that don't require access to an engine, but do need to know about the plan, or file locations. Examples include `init` and `add`. Also expand the documentation for `parse_args()` to better record how it creates targets, especially the default target, which in this case *must* have an associated engine. --- lib/App/Sqitch/Command.pm | 59 +++++++++++++++++++++++++++++++----- t/command.t | 63 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 111 insertions(+), 11 deletions(-) diff --git a/lib/App/Sqitch/Command.pm b/lib/App/Sqitch/Command.pm index 9fbd63641..096abb58b 100644 --- a/lib/App/Sqitch/Command.pm +++ b/lib/App/Sqitch/Command.pm @@ -10,7 +10,7 @@ use App::Sqitch::X qw(hurl); use Hash::Merge 'merge'; use List::Util qw(first); use Moo; -use App::Sqitch::Types qw(Sqitch); +use App::Sqitch::Types qw(Sqitch Target); our $VERSION = '0.997'; @@ -50,6 +50,26 @@ has sqitch => ( )], ); +has default_target => ( + is => 'ro', + isa => Target, + lazy => 1, + default => sub { + my $sqitch = shift->sqitch; + my @params = (sqitch => $sqitch); + unless ( + $sqitch->options->{engine} + || $sqitch->config->get(key => 'core.engine') + ) { + # No specified engine, so specify an engineless URI. + require URI::db; + push @params, uri => URI::db->new('db:'); + } + require App::Sqitch::Target; + return App::Sqitch::Target->new(@params); + }, +); + sub command { my $class = ref $_[0] || shift; return '' if $class eq __PACKAGE__; @@ -351,6 +371,21 @@ uses to find the command class. These methods are mainly provided as utilities for the command subclasses to use. +=head3 C + + my $target = $cmd->default_target; + +This method returns the default target. It should only be used by commands +that don't use a C to find and load a target. + +This method should always return a target option, never C. If the +C<--engine> option or C configuration option has been set, then +the target will support that engine. In the latter case, if +C is set, that value will be used. Otherwise, the +returned target will have a URI of C and no associated engine; the +C method will throw an exception. This behavior sould be fine for +commands that don't need to load the engine. + =head3 C my %parsed_args = $cmd->parse_args(target => $target_name, args => \@args); @@ -362,13 +397,21 @@ passed. For each array reference, the first item is the argument type, either Useful for commands that take a number of parameters where the order may be mixed. -If a target param is passed, it is the default target and will always be -returned instantiated, and arguments recognized as changes in that target will -be returned as changes. If a target name is specified in the arguments, it -will be instantiated and returned under the targets key and any subsequent -changes must be recognized from I plan. If no target is passed or appears -in the arguments, the default target will be used and any changes must be -recognized from it. +If a target parameter is passed, it will always be instantiated and returned +under the "target" key, and arguments recognized as changes in the plan +associated with that target will be returned as changes. + +If a target name is specified in the arguments, it will be instantiated and +returned under the "target" key and any subsequent changes must be recognized +from I plan. + +If no target is passed or appears in the arguments, a default target will be +intantiated based on the comnad-line options and configuration. Unlike the +target returned by C, however, it B have an associated +engine specified by the C<--engine> option or configuration. This is on the +assumption that it will be used by commands that require an engine to do their +work. Of course, any changes must be recognized from the plan associated with +this target. =head3 C diff --git a/t/command.t b/t/command.t index 5d9bf8e8f..603e7552b 100755 --- a/t/command.t +++ b/t/command.t @@ -4,8 +4,8 @@ use strict; use warnings; use 5.010; use utf8; -use Test::More tests => 122; -#use Test::More 'no_plan'; +#use Test::More tests => 135; +use Test::More 'no_plan'; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; $ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; @@ -38,7 +38,17 @@ BEGIN { use_ok $CLASS or die; } -can_ok $CLASS, qw(load new options configure command prompt ask_y_n parse_args); +can_ok $CLASS, qw( + load + new + options + configure + command + prompt + ask_y_n + parse_args + default_target +); COMMAND: { # Stub out a couple of commands. @@ -178,6 +188,53 @@ ok $cmd = $CLASS->load({ isa_ok $cmd, "$CLASS\::wah_hoo", 'It'; is $cmd->command, 'wah-hoo', 'command() should return hyphenated name'; +############################################################################## +# Test default_target. +ok $cmd = $CLASS->new({ sqitch => $sqitch }), "Create a $CLASS object"; +isa_ok my $target = $cmd->default_target, 'App::Sqitch::Target', + 'default target'; +is $target->name, 'db:', 'Default target name should be "db:"'; +is $target->uri, URI->new('db:'), 'Default target URI should be "db:"'; + +# Make sure the core.engine config option gets used. +my @get_ret; +my @get_expect; +$cmock->mock(get => sub { + my $self = shift; + my $exp = shift @get_expect; + is_deeply \@_, [key => $exp], "Should try to fetch $exp"; + return shift @get_ret; +}); +@get_ret = ('sqlite', 'sqlite'); +@get_expect = ('core.engine', 'core.engine', 'core.sqlite.target'); +ok $cmd = $CLASS->new({ sqitch => $sqitch }), "Create a $CLASS object"; +isa_ok $target = $cmd->default_target, 'App::Sqitch::Target', + 'default target'; +is $target->name, 'db:sqlite:', 'Default target name should be "db:sqlite:"'; +is $target->uri, URI->new('db:sqlite:'), 'Default target URI should be "db:sqlite:"'; + +# Make sure --engine is higher precedence. +$sqitch->options->{engine} = 'pg'; +@get_expect = ('core.pg.target'); +ok $cmd = $CLASS->new({ sqitch => $sqitch }), "Create a $CLASS object"; +isa_ok $target = $cmd->default_target, 'App::Sqitch::Target', + 'default target'; +is $target->name, 'db:pg:', 'Default target name should be "db:pg:"'; +is $target->uri, URI->new('db:pg:'), 'Default target URI should be "db:pg:"'; + +# We should get stuff from the engine section of the config. +@get_expect = ('core.pg.target'); +@get_ret = ('db:pg:foo'); +ok $cmd = $CLASS->new({ sqitch => $sqitch }), "Create a $CLASS object"; +isa_ok $target = $cmd->default_target, 'App::Sqitch::Target', + 'default target'; +is $target->name, 'db:pg:foo', 'Default target name should be "db:pg:foo"'; +is $target->uri, URI->new('db:pg:foo'), 'Default target URI should be "db:pg:foo"'; + +# Cleanup. +delete $sqitch->options->{engine}; +$cmock->unmock('get'); + ############################################################################## # Test command and execute. can_ok $CLASS, 'execute'; From 434d4a3babd2f0a3af4c64adb9020dfb37356a10 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 17:21:47 -0700 Subject: [PATCH 33/59] Add `target` as alias for `name`. Make iniitalization simpler. --- lib/App/Sqitch/Target.pm | 1 + t/target.t | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index d2c4bb3df..8bc7e2a49 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -15,6 +15,7 @@ has name => ( isa => Str, required => 1, ); +sub target { shift->name } has uri => ( is => 'ro', diff --git a/t/target.t b/t/target.t index fd9f5dcd8..25e6d405e 100644 --- a/t/target.t +++ b/t/target.t @@ -30,6 +30,7 @@ isa_ok my $target = $CLASS->new(sqitch => $sqitch), $CLASS; can_ok $target, qw( new name + target uri sqitch engine @@ -46,6 +47,7 @@ can_ok $target, qw( # Look at default values. is $target->name, 'db:sqlite:', 'Name should be "db:sqlite:"'; +is $target->target, $target->name, 'Target should be alias for name'; is $target->uri, URI::db->new('db:sqlite:'), 'URI should be "db:sqlite:"'; is $target->sqitch, $sqitch, 'Sqitch should be as passed'; is $target->engine_key, 'sqlite', 'Engine key should be "sqlite"'; @@ -87,6 +89,7 @@ isa_ok $target = $CLASS->new( ), $CLASS, 'Target with name and URI'; is $target->name, 'foo', 'Name should be "foo"'; +is $target->target, $target->name, 'Target should be alias for name'; is $target->uri, $uri, 'URI should be set as passed'; is $target->sqitch, $sqitch, 'Sqitch should be as passed'; is $target->engine_key, 'pg', 'Engine key should be "pg"'; @@ -102,6 +105,7 @@ isa_ok $target = $CLASS->new( ), $CLASS, 'Target with URI'; like $target->name, qr{db:pg://hi:?\@localhost/blah}, 'Name should be URI without password'; +is $target->target, $target->name, 'Target should be alias for name'; is $target->engine_key, 'pg', 'Engine key should be "pg"'; isa_ok $target->engine, 'App::Sqitch::Engine::pg', 'Engine'; is $target->dsn, $uri->dbi_dsn, 'DSN should be from URI'; From f5c090789e3c8e5525f69884584580cda8a94f79 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 17:23:27 -0700 Subject: [PATCH 34/59] Update init to use default_target. --- lib/App/Sqitch/Command/init.pm | 78 +++++++++-------------- t/init.t | 109 ++++++++++++++++++--------------- t/sqitch.conf | 1 - t/user.conf | 5 +- 4 files changed, 89 insertions(+), 104 deletions(-) diff --git a/lib/App/Sqitch/Command/init.pm b/lib/App/Sqitch/Command/init.pm index d2cd92886..8ca74e998 100644 --- a/lib/App/Sqitch/Command/init.pm +++ b/lib/App/Sqitch/Command/init.pm @@ -64,9 +64,9 @@ sub configure { sub make_directories { my $self = shift; - my $sqitch = $self->sqitch; + my $target = $self->default_target; for my $attr (qw(deploy_dir revert_dir verify_dir)) { - $self->_mkdir( $sqitch->$attr ); + $self->_mkdir( $target->$attr ); } return $self; } @@ -91,8 +91,8 @@ sub _mkdir { sub write_plan { my ( $self, $project ) = @_; - my $sqitch = $self->sqitch; - my $file = $sqitch->plan_file; + my $target = $self->default_target; + my $file = $target->plan_file; return $self if -f $file; $self->_mkdir( $file->dir ) unless -d $file->dir; @@ -120,8 +120,9 @@ sub write_plan { sub write_config { my $self = shift; my $sqitch = $self->sqitch; - my $was_set = $sqitch->_was_set; my $config = $sqitch->config; + my $options = $sqitch->options; + my $target = $self->default_target; my $file = $config->local_file; if ( -f $file ) { @@ -131,8 +132,9 @@ sub write_config { my ( @vars, @comments ); - # Write the engine. - if (my $ekey = eval { $sqitch->engine_key }) { + # Write the engine from --engine or core.engine. + my $ekey = $target->engine_key; + if ($ekey) { push @vars => { key => "core.engine", value => $ekey, @@ -154,12 +156,10 @@ sub write_config { # Set core attributes that are not their default values and not # already in user or system config. - my $val = $sqitch->$name; + my $val = $options->{$name}; my $var = $config->get( key => "core.$name" ); - no warnings 'uninitialized'; - if ( $was_set->{$name} && $val ne $var ) { - + if ( $val && $val ne ($var // '') ) { # It was specified on the command-line, so grab it to write out. push @vars => { key => "core.$name", @@ -167,7 +167,7 @@ sub write_config { }; } else { - $var //= $val // ''; + $var //= $target->$name // ''; push @comments => "\t$name = $var"; } } @@ -187,51 +187,31 @@ sub write_config { comment => join "\n" => @comments, ) if @comments; - if ( my $engine = try { $sqitch->engine } ) { - + if ($ekey) { # Write out the core.$engine section. - my $ekey = 'core.' . $engine->key; - my @config_vars = $engine->config_vars; + my $config_key = "core.$ekey"; @comments = @vars = (); - my $iter = natatime 2, @config_vars; - while ( my ( $key, $type ) = $iter->() ) { + for my $key (qw(target registry client)) { # Was it passed as an option? - my $core_key = $key =~ /^db_/ ? $key : "db_$key"; - if ( my $acc = $sqitch->can($core_key) ) { - if ( my $val = $sqitch->$acc ) { - - # It was passed as an option, so record that. - my $multiple = $type =~ s/[+]$//; - $type = undef if $type eq 'any'; - push @vars => { - key => "$ekey.$key", - value => $val, - as => $type, - multiple => $multiple, - }; - - # We're good on this one. - next; - } - } + if ( my $val = $options->{$key} ) { - # No value, but add it as a comment. - if ( my $acc = $engine->can($key) ) { + # It was passed as an option, so record that. + push @vars => { + key => "$config_key.$key", + value => $val, + }; - # Add it as a comment, possibly with a default. - my $def = $engine->$acc - // $config->get( key => "$ekey.$key" ) - // ''; - push @comments => "\t$key = $def"; + # We're good on this one. + next; } - else { - # Add it as a comment, with the config, if possible. - my $val = $config->get( key => "$ekey.$key" ) // ''; - push @comments => "\t$key = $val"; - } + # No value, but add it as a comment, possibly with a default. + my $def = $target->$key + // $config->get( key => "$config_key.$key" ) + // ''; + push @comments => "\t$key = $def"; } if (@vars) { @@ -242,7 +222,7 @@ sub write_config { else { # Still want the section, emit it as a comment. - unshift @comments => '[core "' . $engine->key . '"]'; + unshift @comments => qq{[core "$ekey"]}; } # Emit the comments. diff --git a/t/init.t b/t/init.t index 7a88de1a0..3ecd5421f 100644 --- a/t/init.t +++ b/t/init.t @@ -4,8 +4,8 @@ use strict; use warnings; use 5.010; use utf8; -use Test::More tests => 157; -#use Test::More 'no_plan'; +#use Test::More tests => 157; +use Test::More 'no_plan'; use App::Sqitch; use Locale::TextDomain qw(App-Sqitch); use Path::Class; @@ -37,13 +37,15 @@ sub read_config($) { $conf->data; } -$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; -$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; +$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; +$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; ############################################################################## # Test options and configuration. -my $sqitch = App::Sqitch->new(top_dir => dir 'init.mkdir'); +my $sqitch = App::Sqitch->new( + options => { top_dir => dir('init.mkdir')->stringify }, +); isa_ok my $init = $CLASS->new( sqitch => $sqitch ), $CLASS, 'New init object'; can_ok $init, qw(uri options configure); @@ -57,24 +59,25 @@ is_deeply $CLASS->configure({}, { uri => 'http://example.com' }), 'Should accept a URI in options'; isa_ok $CLASS->configure({}, { uri => 'http://example.com' })->{uri}, 'URI', 'processed uri option'; +isa_ok my $target = $init->default_target, 'App::Sqitch::Target', 'default target'; ############################################################################## # Test make_directories. can_ok $init, 'make_directories'; for my $attr (map { "$_\_dir"} qw(top deploy revert verify)) { - dir_not_exists_ok $sqitch->$attr; + dir_not_exists_ok $target->$attr; } -my $top_dir_string = $sqitch->top_dir->stringify; +my $top_dir_string = $target->top_dir->stringify; END { remove_tree $top_dir_string } ok $init->make_directories, 'Make the directories'; for my $attr (map { "$_\_dir"} qw(top deploy revert verify)) { - dir_exists_ok $sqitch->$attr; + dir_exists_ok $target->$attr; } my $sep = dir('')->stringify; is_deeply +MockOutput->get_info, [ - map { [__x "Created {file}", file => $sqitch->$_ . $sep] } + map { [__x "Created {file}", file => $target->$_ . $sep] } map { "$_\_dir" } qw(deploy revert verify) ], 'Each should have been sent to info'; @@ -83,11 +86,11 @@ ok $init->make_directories, 'Make the directories again'; is_deeply +MockOutput->get_info, [], 'Nothing should have been sent to info'; # Delete one of them. -remove_tree $sqitch->revert_dir->stringify; +remove_tree $target->revert_dir->stringify; ok $init->make_directories, 'Make the directories once more'; -dir_exists_ok $sqitch->revert_dir, 'revert dir exists again'; +dir_exists_ok $target->revert_dir, 'revert dir exists again'; is_deeply +MockOutput->get_info, [ - [__x 'Created {file}', file => $sqitch->revert_dir . $sep], + [__x 'Created {file}', file => $target->revert_dir . $sep], ], 'Should have noted creation of revert dir'; remove_tree $top_dir_string; @@ -107,7 +110,7 @@ FSERR: { is $@->ident, 'init', 'Permission error should have ident "init"'; is $@->message, __x( 'Error creating {path}: {error}', - path => $sqitch->deploy_dir, + path => $target->deploy_dir, error => 'Permission denied yo', ), 'The permission error should be formatted properly'; } @@ -130,6 +133,7 @@ ok $init = $CLASS->new( sqitch => $sqitch, ), 'Another init object'; file_not_exists_ok $conf_file; +$target = $init->default_target; # Write empty config. ok $init->write_config, 'Write the config'; @@ -143,7 +147,7 @@ my $top_dir = File::Spec->curdir; my $deploy_dir = File::Spec->catdir(qw(deploy)); my $revert_dir = File::Spec->catdir(qw(revert)); my $verify_dir = File::Spec->catdir(qw(verify)); -my $plan_file = $sqitch->top_dir->file('sqitch.plan')->cleanup->stringify; +my $plan_file = $target->top_dir->file('sqitch.plan')->cleanup->stringify; file_contents_like $conf_file, qr{\Q[core] # engine = # plan_file = $plan_file @@ -156,8 +160,9 @@ file_contents_like $conf_file, qr{\Q[core] unlink $conf_file; # Set two options. -$sqitch = App::Sqitch->new( extension => 'foo' ); +$sqitch = App::Sqitch->new(options => { extension => 'foo' }); ok $init = $CLASS->new( sqitch => $sqitch ), 'Another init object'; +$target = $init->default_target; ok $init->write_config, 'Write the config'; file_exists_ok $conf_file; is_deeply read_config $conf_file, { @@ -188,7 +193,7 @@ USERCONF: { # Delete the file and write with a user config loaded. unlink $conf_file; local $ENV{SQITCH_USER_CONFIG} = file +File::Spec->updir, 'user.conf'; - my $sqitch = App::Sqitch->new(extension => 'foo'); + my $sqitch = App::Sqitch->new(options => { extension => 'foo' }); ok my $init = $CLASS->new( sqitch => $sqitch), 'Make an init object with user config'; file_not_exists_ok $conf_file; @@ -214,9 +219,10 @@ SYSTEMCONF: { # Delete the file and write with a system config loaded. unlink $conf_file; local $ENV{SQITCH_SYSTEM_CONFIG} = file +File::Spec->updir, 'sqitch.conf'; - my $sqitch = App::Sqitch->new(extension => 'foo'); + my $sqitch = App::Sqitch->new(options => { extension => 'foo' }); ok my $init = $CLASS->new( sqitch => $sqitch), 'Make an init object with system config'; + ok $target = $init->default_target, 'Get target'; file_not_exists_ok $conf_file; ok $init->write_config, 'Write the config with a system conf'; file_exists_ok $conf_file; @@ -231,7 +237,7 @@ SYSTEMCONF: { my $deploy_dir = File::Spec->catdir(qw(migrations deploy)); my $revert_dir = File::Spec->catdir(qw(migrations revert)); my $verify_dir = File::Spec->catdir(qw(migrations verify)); - my $plan_file = $sqitch->top_dir->file('sqitch.plan')->stringify; + my $plan_file = $target->top_dir->file('sqitch.plan')->stringify; file_contents_like $conf_file, qr{\Q # plan_file = $plan_file @@ -246,12 +252,14 @@ SYSTEMCONF: { # Now get it to write a bunch of other stuff. unlink $conf_file; $sqitch = App::Sqitch->new( - plan_file => 'my.plan', - deploy_dir => dir('dep'), - revert_dir => dir('rev'), - verify_dir => dir('tst'), - extension => 'ddl', - _engine => 'sqlite', + options => { + plan_file => 'my.plan', + deploy_dir => dir('dep')->stringify, + revert_dir => dir('rev')->stringify, + verify_dir => dir('tst')->stringify, + extension => 'ddl', + engine => 'sqlite', + }, ); ok $init = $CLASS->new( sqitch => $sqitch ), @@ -274,9 +282,10 @@ is_deeply read_config $conf_file, { # Now get it to write core.sqlite stuff. unlink $conf_file; $sqitch = App::Sqitch->new( - _engine => 'sqlite', - client => '/to/sqlite3', - db_name => 'my.db', + options => { + engine => 'sqlite', + client => '/to/sqlite3', + }, ); ok $init = $CLASS->new( sqitch => $sqitch ), @@ -291,14 +300,14 @@ is_deeply read_config $conf_file, { 'core.sqlite.client' => '/to/sqlite3', }, 'The configuration should have been written with sqlite values'; -file_contents_like $conf_file, qr/^\t# target = \Qdb:sqlite:my.db\E\n/m, - 'target should be included in a comment'; +file_contents_like $conf_file, qr{^\tclient = /to/sqlite3\n}m, + 'Client should be included'; file_contents_like $conf_file, qr/^\t# registry = sqitch\n/m, 'registry_uri should be included in a comment'; # Try it with no options. unlink $conf_file; -$sqitch = App::Sqitch->new(_engine => 'sqlite'); +$sqitch = App::Sqitch->new(options => { engine => 'sqlite' }); ok $init = $CLASS->new( sqitch => $sqitch ), 'Create new init with sqitch with default engine attributes'; ok $init->write_config, 'Write the config with engine attrs'; @@ -320,10 +329,7 @@ USERCONF: { # Delete the file and write with a user config loaded. unlink $conf_file; local $ENV{SQITCH_USER_CONFIG} = file +File::Spec->updir, 'user.conf'; - my $sqitch = App::Sqitch->new( - _engine => 'sqlite', - db_name => 'my.db', - ); + my $sqitch = App::Sqitch->new(options => { engine => 'sqlite' }); ok my $init = $CLASS->new( sqitch => $sqitch ), 'Make an init with sqlite and user config'; file_not_exists_ok $conf_file; @@ -348,12 +354,14 @@ USERCONF: { # Now get it to write core.pg stuff. unlink $conf_file; $sqitch = App::Sqitch->new( - _engine => 'pg', - client => '/to/psql', - db_name => 'thingies', - db_username => 'anna', - db_host => 'banana', - db_port => 93453, + options => { + engine => 'pg', + client => '/to/psql', + # db_name => 'thingies', + # db_username => 'anna', + # db_host => 'banana', + # db_port => 93453, + }, ); ok $init = $CLASS->new( sqitch => $sqitch ), @@ -373,7 +381,7 @@ file_contents_like $conf_file, qr/^\t# registry = sqitch\n/m, # Try it with no config or options. unlink $conf_file; -$sqitch = App::Sqitch->new(_engine => 'pg'); +$sqitch = App::Sqitch->new(options => { engine => 'pg' }); ok $init = $CLASS->new( sqitch => $sqitch ), 'Create new init with sqitch with default engine attributes'; ok $init->write_config, 'Write the config with engine attrs'; @@ -394,10 +402,7 @@ USERCONF: { # Delete the file and write with a user config loaded. unlink $conf_file; local $ENV{SQITCH_USER_CONFIG} = file +File::Spec->updir, 'user.conf'; - my $sqitch = App::Sqitch->new( - _engine => 'pg', - db_name => 'thingies', - ); + my $sqitch = App::Sqitch->new(options => { engine => 'pg' }); ok my $init = $CLASS->new( sqitch => $sqitch ), 'Make an init with pg and user config'; file_not_exists_ok $conf_file; @@ -420,7 +425,8 @@ USERCONF: { ############################################################################## # Test write_plan(). can_ok $init, 'write_plan'; -$plan_file = $sqitch->plan_file; +$target = $init->default_target; +$plan_file = $target->plan_file; file_not_exists_ok $plan_file, 'Plan file should not yet exist'; ok $init->write_plan( 'nada' ), 'Write the plan file'; is_deeply +MockOutput->get_info, [ @@ -444,13 +450,14 @@ file_contents_like $plan_file, qr/testing 1, 2, 3/, # Make sure a URI gets written, if present. $plan_file->remove; -$sqitch = App::Sqitch->new(top_dir => dir 'plan.dir'); -END { remove_tree dir('plan.dir')->stringify } -$plan_file = $sqitch->plan_file; +$sqitch = App::Sqitch->new(options => { top_dir => dir('plan.dir')->stringify }); +END { remove_tree dir('plan.dir')->stringify }; ok $init = $CLASS->new( sqitch => $sqitch, uri => $uri, ), 'Create new init with sqitch with project and URI'; +$target = $init->default_target; +$plan_file = $target->plan_file; ok $init->write_plan( 'howdy' ), 'Write the plan file again'; is_deeply +MockOutput->get_info, [ [__x 'Created {file}', file => $plan_file->dir . $sep], @@ -510,7 +517,7 @@ ok $init->execute('foofoo'), 'Execute!'; # Should have directories. for my $attr (map { "$_\_dir"} qw(top deploy revert verify)) { - dir_exists_ok $sqitch->$attr; + dir_exists_ok $target->$attr; } # Should have config and plan. @@ -519,7 +526,7 @@ file_exists_ok $plan_file; # Should have the output. my @dir_messages = map { - [__x 'Created {file}', file => $sqitch->$_ . $sep] + [__x 'Created {file}', file => $target->$_ . $sep] } map { "$_\_dir" } qw(deploy revert verify); is_deeply +MockOutput->get_info, [ [__x 'Created {file}', file => $conf_file], diff --git a/t/sqitch.conf b/t/sqitch.conf index e7032daab..ebbec5b05 100644 --- a/t/sqitch.conf +++ b/t/sqitch.conf @@ -6,7 +6,6 @@ [core.pg] client = /usr/local/pgsql/bin/psql - username = theory [revert] to = gamma diff --git a/t/user.conf b/t/user.conf index 46cdb8c4a..d2a128da0 100644 --- a/t/user.conf +++ b/t/user.conf @@ -4,18 +4,17 @@ [core.pg] client = /opt/local/pgsql/bin/psql - username = postgres - host = localhost + target = db:pg://postgres@localhost/thingies registry = meta [core.mysql] client = /opt/local/mysql/bin/mysql - username = root registry = meta [core.sqlite] client = /opt/local/bin/sqlite3 registry = meta + target = db:sqlite:my.db [core.firebird] client = /opt/firebird/bin/isql From 2db4c928821b005abc8317ee3a3ea36833f3efcc Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 17:40:07 -0700 Subject: [PATCH 35/59] Update add command to use default_target. --- lib/App/Sqitch/Command/add.pm | 11 +++++---- t/add.t | 46 ++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/lib/App/Sqitch/Command/add.pm b/lib/App/Sqitch/Command/add.pm index 9d92d1f87..9a90cff06 100644 --- a/lib/App/Sqitch/Command/add.pm +++ b/lib/App/Sqitch/Command/add.pm @@ -54,7 +54,7 @@ has template_name => ( is => 'ro', isa => Str, lazy => 1, - default => sub { shift->sqitch->engine_key }, + default => sub { shift->default_target->engine_key }, ); has with_scripts => ( @@ -275,8 +275,8 @@ sub configure { sub execute { my ( $self, $name ) = @_; $self->usage unless defined $name; - my $sqitch = $self->sqitch; - my $plan = $sqitch->plan; + my $target = $self->default_target; + my $plan = $target->plan; my $with = $self->with_scripts; my $tmpl = $self->all_templates; my $change = $plan->add( @@ -302,15 +302,16 @@ sub execute { $self->_add( $name, $files[$i++], $tmpl->{$_} ) for @scripts; # We good, write the plan file back out. - $plan->write_to( $sqitch->plan_file ); + $plan->write_to( $target->plan_file ); $self->info(__x( 'Added "{change}" to {file}', change => $change->format_op_name_dependencies, - file => $sqitch->plan_file, + file => $target->plan_file, )); # Let 'em at it. if ($self->open_editor) { + my $sqitch = $self->sqitch; $sqitch->shell( $sqitch->editor . ' ' . $sqitch->quote_shell(@files) ); } diff --git a/t/add.t b/t/add.t index 9e3a25f85..33b702087 100644 --- a/t/add.t +++ b/t/add.t @@ -6,6 +6,7 @@ use utf8; use Test::More tests => 163; #use Test::More 'no_plan'; use App::Sqitch; +use App::Sqitch::Target; use Locale::TextDomain qw(App-Sqitch); use Path::Class; use Test::Exception; @@ -20,8 +21,8 @@ use MockOutput; my $CLASS = 'App::Sqitch::Command::add'; -$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; -$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; +$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; +$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; my $config_mock = Test::MockModule->new('App::Sqitch::Config'); @@ -31,27 +32,30 @@ $config_mock->mock(system_dir => sub { $sysdir }); $config_mock->mock(user_dir => sub { $usrdir }); ok my $sqitch = App::Sqitch->new( - top_dir => dir('test-add'), - _engine => 'pg', + options => { + top_dir => dir('test-add')->stringify, + engine => 'pg', + } ), 'Load a sqitch sqitch object'; my $config = $sqitch->config; +isa_ok my $add = App::Sqitch::Command->load({ + sqitch => $sqitch, + command => 'add', + config => $config, +}), $CLASS, 'add command'; +my $target = $add->default_target; + sub dep($$) { my $dep = App::Sqitch::Plan::Depend->new( %{ App::Sqitch::Plan::Depend->parse( $_[1] ) }, - plan => $sqitch->plan, + plan => $add->default_target->plan, conflicts => $_[0], ); $dep->project; return $dep; } -isa_ok my $add = App::Sqitch::Command->load({ - sqitch => $sqitch, - command => 'add', - config => $config, -}), $CLASS, 'add command'; - can_ok $CLASS, qw( options requires @@ -213,7 +217,7 @@ is_deeply $add->conflicts, [], 'Conflicts should be an arrayref'; is_deeply $add->note, [], 'Notes should be an arrayref'; is_deeply $add->variables, {}, 'Varibles should be a hashref'; is $add->template_directory, undef, 'Default dir should be undef'; -is $add->template_name, $sqitch->_engine, 'Default temlate_name should be engine'; +is $add->template_name, 'pg', 'Default temlate_name should be engine'; is_deeply $add->with_scripts, {}, 'Default with_scripts should be empty'; is_deeply $add->templates, {}, 'Default templates should be empty'; @@ -244,7 +248,7 @@ is $@->message, __x( READCONFIG: { local $ENV{SQITCH_CONFIG} = file('t/templates.conf')->stringify; ok my $sqitch = App::Sqitch->new( - top_dir => dir('test-add'), + options => { top_dir => dir('test-add')->stringify }, ), 'Load another sqitch sqitch object'; my $config = $sqitch->config; ok $add = $CLASS->new(sqitch => $sqitch), @@ -356,7 +360,7 @@ is $ { $add->_slurp($tmpl)}, contents_of $tmpl, my $test_add = sub { my $engine = shift; make_path 'test-add'; - my $fn = $sqitch->plan_file; + my $fn = $target->plan_file; open my $fh, '>', $fn or die "Cannot open $fn: $!"; say $fh "%project=add\n\n"; close $fh or die "Error closing $fn: $!"; @@ -462,7 +466,7 @@ my $deploy_file = file qw(test-add deploy widgets_table.sql); my $revert_file = file qw(test-add revert widgets_table.sql); my $verify_file = file qw(test-add verify widgets_table.sql); -my $plan = $sqitch->plan; +my $plan = $add->default_target->plan; is $plan->get('widgets_table'), undef, 'Should not have "widgets_table" in plan'; dir_not_exists_ok +File::Spec->catdir('test-add', $_) for qw(deploy revert verify); ok $add->execute('widgets_table'), 'Add change "widgets_table"'; @@ -489,7 +493,7 @@ is_deeply +MockOutput->get_info, [ [__x 'Created {file}', file => $verify_file], [__x 'Added "{change}" to {file}', change => 'widgets_table', - file => $sqitch->plan_file, + file => $target->plan_file, ], ], 'Info should have reported file creation'; @@ -519,6 +523,7 @@ is $plan->get('foo_table'), undef, 'Should not have "foo_table" in plan'; ok $add->execute('foo_table'), 'Add change "foo_table"'; file_exists_ok $_ for ($deploy_file, $revert_file); file_not_exists_ok $verify_file; +$plan = $add->default_target->plan; isa_ok $change = $plan->get('foo_table'), 'App::Sqitch::Plan::Change', '"foo_table" change'; is_deeply \%request_params, { @@ -536,7 +541,7 @@ is_deeply +MockOutput->get_info, [ [__x 'Created {file}', file => $revert_file], [__x 'Added "{change}" to {file}', change => 'foo_table [widgets_table !dr_evil !joker]', - file => $sqitch->plan_file, + file => $target->plan_file, ], ], 'Info should report skipping file and include dependencies'; @@ -558,7 +563,7 @@ MOCKSHELL: { my $revert_file = file qw(test-add revert open_editor.sql); my $verify_file = file qw(test-add verify open_editor.sql); - my $plan = $sqitch->plan; + my $plan = $add->default_target->plan; is $plan->get('open_editor'), undef, 'Should not have "open_editor" in plan'; ok $add->execute('open_editor'), 'Add change "open_editor"'; isa_ok my $change = $plan->get('open_editor'), 'App::Sqitch::Plan::Change', @@ -580,7 +585,7 @@ MOCKSHELL: { [__x 'Created {file}', file => $verify_file], [__x 'Added "{change}" to {file}', change => 'open_editor', - file => $sqitch->plan_file, + file => $target->plan_file, ], ], 'Info should have reported file creation'; }; @@ -601,6 +606,7 @@ EXTRAS: { my $whatev_file = file qw(test-add whatev custom_script.sql); ok $add->execute('custom_script'), 'Add change "custom_script"'; + my $plan = $add->default_target->plan; isa_ok my $change = $plan->get('custom_script'), 'App::Sqitch::Plan::Change', 'Added change'; is $change->name, 'custom_script', 'Change name should be set'; @@ -627,7 +633,7 @@ EXTRAS: { [__x 'Created {file}', file => $whatev_file], [__x 'Added "{change}" to {file}', change => 'custom_script', - file => $sqitch->plan_file, + file => $target->plan_file, ], ], 'Info should have reported file creation'; From a9bcb9542693705205a862d00c1183f054c865e0 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 18:01:15 -0700 Subject: [PATCH 36/59] Update checkout command to use Target. --- lib/App/Sqitch/Command/checkout.pm | 22 ++++++++++------ t/checkout.t | 41 +++++++++++++++++------------- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/lib/App/Sqitch/Command/checkout.pm b/lib/App/Sqitch/Command/checkout.pm index 1388ab51d..f8c3d8547 100644 --- a/lib/App/Sqitch/Command/checkout.pm +++ b/lib/App/Sqitch/Command/checkout.pm @@ -33,7 +33,7 @@ sub configure { {} } sub execute { my $self = shift; - my %args = $self->parse_args(@_); + my %args = $self->parse_args(target => $self->target, args => \@_); # The branch arg will be the one parse_args does not recognize. my $branch = shift @{ $args{unknown} } // $self->usage; @@ -49,17 +49,17 @@ sub execute { } # Warn on multiple targets. - my $target = $self->target // shift @{ $args{targets} }; + my $target = shift @{ $args{targets} }; $self->warn(__x( 'Too many targets specified; connecting to {target}', - target => $target, + target => $target->name, )) if @{ $args{targets} }; # Now get to work. my $sqitch = $self->sqitch; my $git = $self->client; - my $engine = $self->engine_for_target($target); + my $engine = $target->engine; $engine->with_verify( $self->verify ); $engine->no_prompt( $self->no_prompt ); $engine->prompt_accept( $self->prompt_accept ); @@ -73,13 +73,19 @@ sub execute { exitval => 1, } if $current_branch eq $branch; - # Instantitate a plan without calling $sqitch->plan. - my $from_plan = App::Sqitch::Plan->new( sqitch => $sqitch ); + # Instantitate a plan without calling $target->plan. + my $from_plan = App::Sqitch::Plan->new( + sqitch => $sqitch, + target => $target, + ); # Load the branch plan from Git, assuming the same path. - my $to_plan = App::Sqitch::Plan->new( sqitch => $sqitch )->parse( + my $to_plan = App::Sqitch::Plan->new( + sqitch => $sqitch, + target => $target, + )->parse( # XXX Handle missing file/no contents. - scalar $sqitch->capture( $git, 'show', "$branch:" . $sqitch->plan_file) + scalar $sqitch->capture( $git, 'show', "$branch:" . $target->plan_file) ); # Find the last change the plans have in common. diff --git a/t/checkout.t b/t/checkout.t index 749969c4a..6bbbbd8d3 100644 --- a/t/checkout.t +++ b/t/checkout.t @@ -17,8 +17,8 @@ use MockOutput; my $CLASS = 'App::Sqitch::Command::checkout'; require_ok $CLASS or die; -$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; -$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; +$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; +$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; isa_ok $CLASS, 'App::Sqitch::Command'; @@ -44,9 +44,11 @@ is_deeply [$CLASS->options], [qw( )], 'Options should be correct'; ok my $sqitch = App::Sqitch->new( - plan_file => file(qw(t sql sqitch.plan)), - top_dir => dir(qw(t sql)), - _engine => 'sqlite', + options => { + plan_file => file(qw(t sql sqitch.plan))->stringify, + top_dir => dir(qw(t sql))->stringify, + engine => 'sqlite', + }, ), 'Load a sqitch object'; my $config = $sqitch->config; @@ -252,10 +254,15 @@ CONFIG: { # Mock the execution interface. my $mock_sqitch = Test::MockModule->new(ref $sqitch); -my (@probe_args, $probed, $engine, $orig_emethod); +my (@probe_args, $probed, $target, $orig_method); $mock_sqitch->mock(probe => sub { shift; @probe_args = @_; $probed }); -$mock_sqitch->mock(engine => sub { $engine = shift->$orig_emethod(@_) }); -$orig_emethod = $mock_sqitch->original('engine'); +my $mock_cmd = Test::MockModule->new($CLASS); +$mock_cmd->mock(parse_args => sub { + my %ret = shift->$orig_method(@_); + $target = $ret{targets}[0]; + %ret; +}); +$orig_method = $mock_cmd->original('parse_args'); my @run_args; $mock_sqitch->mock(run => sub { shift; @run_args = @_ }); @@ -333,7 +340,7 @@ isa_ok $checkout = $CLASS->new( ok $checkout->execute('master'), 'Checkout master'; is_deeply \@probe_args, [$client, qw(rev-parse --abbrev-ref HEAD)], 'The proper args should again have been passed to rev-parse'; -is_deeply \@capture_args, [$client, 'show', 'master:' . $sqitch->plan_file ], +is_deeply \@capture_args, [$client, 'show', 'master:' . $checkout->default_target->plan_file ], 'Should have requested the plan file contents as of master'; is_deeply \@run_args, [$client, qw(checkout master)], 'Should have checked out other branch'; @@ -345,7 +352,7 @@ is_deeply +MockOutput->get_info, [[__x( )]], 'Should have emitted info identifying the last common change'; # Did it revert? -is_deeply \@rev_args, [$sqitch->plan->get('users')->id], +is_deeply \@rev_args, [$checkout->default_target->plan->get('users')->id], '"users" ID and 1 should be passed to the engine revert'; is_deeply \@rev_changes, [qw(roles users widgets)], 'Should have had the current changes for revision'; @@ -356,8 +363,8 @@ is_deeply \@dep_args, [undef, 'tag'], is_deeply \@dep_changes, [qw(roles users thingíes)], 'Should have had the other branch changes (decoded) for deploy'; -ok $engine->with_verify, 'Engine should verify'; -ok $engine->log_only, 'The engine should be set to log_only'; +ok $target->engine->with_verify, 'Engine should verify'; +ok $target->engine->log_only, 'The engine should be set to log_only'; is @vars, 2, 'Variables should have been passed to the engine twice'; is_deeply { @{ $vars[0] } }, { hey => 'there' }, 'The revert vars should have been passed first'; @@ -366,7 +373,7 @@ is_deeply { @{ $vars[1] } }, { foo => 'bar', one => 1 }, # Try passing a target. ok $checkout->execute('master', 'db:sqlite:foo'), 'Checkout master with target'; -is $engine->target, 'db:sqlite:foo', 'Target should be passed to engine'; +is $target->name, 'db:sqlite:foo', 'Target should be passed to engine'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # If nothing is deployed, or we are already at the revert target, the revert @@ -384,12 +391,12 @@ isa_ok $checkout = $CLASS->new( $mock_engine->mock(revert => sub { hurl { ident => 'revert', message => 'foo', exitval => 1 } }); @dep_args = @rev_args = @vars = (); ok $checkout->execute('master'), 'Checkout master again'; -is $engine->target, 'db:sqlite:hello', 'Target should be passed to engine'; +is $target->name, 'db:sqlite:hello', 'Target should be passed to engine'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Did it deploy? -ok !$sqitch->engine->log_only, 'The engine should not be set to log_only'; -ok !$sqitch->engine->with_verify, 'The engine should not be set with_verfy'; +ok !$target->engine->log_only, 'The engine should not be set to log_only'; +ok !$target->engine->with_verify, 'The engine should not be set with_verfy'; is_deeply \@dep_args, [undef, 'tag'], 'undef, "tag", and 1 should be passed to the engine deploy again'; is_deeply \@dep_changes, [qw(roles users thingíes)], @@ -402,7 +409,7 @@ is_deeply { @{ $vars[1] } }, { foo => 'bar', one => 1 }, # Should get a warning for two targets. ok $checkout->execute('master', 'db:sqlite:'), 'Checkout master again with target'; -is $engine->target, 'db:sqlite:hello', 'Target should be passed to engine'; +is $target->name, 'db:sqlite:hello', 'Target should be passed to engine'; is_deeply +MockOutput->get_warn, [[__x( 'Too many targets specified; connecting to {target}', target => 'db:sqlite:hello', From dafc0fafebb620130781c2b601b1684b4ee1e321 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Wed, 22 Oct 2014 18:36:37 -0700 Subject: [PATCH 37/59] Fix config tests. --- t/config.t | 49 +++++++++++++++++++++++-------------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/t/config.t b/t/config.t index 8d7efc305..a575fdbfd 100644 --- a/t/config.t +++ b/t/config.t @@ -223,8 +223,8 @@ ok $cmd->execute('core.engine'), 'Get core.engine'; is_deeply \@emit, [['pg']], 'Should have emitted the merged core.engine'; @emit = (); -ok $cmd->execute('core.pg.host'), 'Get core.pg.host'; -is_deeply \@emit, [['localhost']], 'Should have emitted the merged core.pg.host'; +ok $cmd->execute('core.pg.registry'), 'Get core.pg.registry'; +is_deeply \@emit, [['meta']], 'Should have emitted the merged core.pg.registry'; @emit = (); ok $cmd->execute('core.pg.client'), 'Get core.pg.client'; @@ -358,8 +358,8 @@ CONTEXT: { }), 'Create user config get command'; @emit = (); - ok $cmd->execute('core.pg.host'), 'Get user core.pg.host'; - is_deeply \@emit, [['localhost']], 'Should have emitted the user core.pg.host'; + ok $cmd->execute('core.pg.registry'), 'Get user core.pg.registry'; + is_deeply \@emit, [['meta']], 'Should have emitted the user core.pg.registry'; @emit = (); ok $cmd->execute('core.pg.client'), 'Get user core.pg.client'; @@ -440,7 +440,7 @@ ok $cmd = App::Sqitch::Command::config->new({ }), 'Create config list command'; ok $cmd->execute, 'Execute the list action'; is_deeply \@emit, [[ - "bundle.dest_dir=_build/sql + 'bundle.dest_dir=_build/sql bundle.from=gamma bundle.tags_only=true core.engine=pg @@ -449,12 +449,10 @@ core.firebird.client=/opt/firebird/bin/isql core.firebird.registry=meta core.mysql.client=/opt/local/mysql/bin/mysql core.mysql.registry=meta -core.mysql.username=root core.pg.client=/opt/local/pgsql/bin/psql core.pg.db_name=widgets -core.pg.host=localhost core.pg.registry=meta -core.pg.username=postgres +core.pg.target=db:pg://postgres@localhost/thingies core.sqlite.client=/opt/local/bin/sqlite3 core.sqlite.registry=meta core.sqlite.target=devdb @@ -464,9 +462,11 @@ revert.count=2 revert.revision=1.1 revert.to=gamma target.devdb.uri=db:sqlite: -user.email=michael\@example.com +target.mydb.plan_file=t/plans/dependencies.plan +target.mydb.uri=db:pg:mydb +user.email=michael@example.com user.name=Michael Stonebraker -" +' ]], 'Should have emitted the merged config'; @emit = (); @@ -482,19 +482,18 @@ CONTEXT: { }), 'Create system config list command'; ok $cmd->execute, 'List the system config'; is_deeply \@emit, [[ - "bundle.dest_dir=_build/sql + 'bundle.dest_dir=_build/sql bundle.from=gamma bundle.tags_only=true core.engine=pg core.extension=ddl core.pg.client=/usr/local/pgsql/bin/psql -core.pg.username=theory core.top_dir=migrations core.uri=https://github.com/theory/sqitch/ revert.count=2 revert.revision=1.1 revert.to=gamma -" +' ]], 'Should have emitted the system config list'; @emit = (); @@ -507,20 +506,19 @@ revert.to=gamma }), 'Create user config list command'; ok $cmd->execute, 'List the user config'; is_deeply \@emit, [[ - "core.firebird.client=/opt/firebird/bin/isql + 'core.firebird.client=/opt/firebird/bin/isql core.firebird.registry=meta core.mysql.client=/opt/local/mysql/bin/mysql core.mysql.registry=meta -core.mysql.username=root core.pg.client=/opt/local/pgsql/bin/psql -core.pg.host=localhost core.pg.registry=meta -core.pg.username=postgres +core.pg.target=db:pg://postgres@localhost/thingies core.sqlite.client=/opt/local/bin/sqlite3 core.sqlite.registry=meta -user.email=michael\@example.com +core.sqlite.target=db:sqlite:my.db +user.email=michael@example.com user.name=Michael Stonebraker -" +' ]], 'Should only have emitted the user config list'; @emit = (); @@ -533,11 +531,13 @@ user.name=Michael Stonebraker }), 'Create local config list command'; ok $cmd->execute, 'List the local config'; is_deeply \@emit, [[ - "core.engine=pg + 'core.engine=pg core.pg.db_name=widgets core.sqlite.target=devdb target.devdb.uri=db:sqlite: -" +target.mydb.plan_file=t/plans/dependencies.plan +target.mydb.uri=db:pg:mydb +' ]], 'Should only have emitted the local config list'; @emit = (); } @@ -790,7 +790,6 @@ core.extension=ddl core.foo=[bar, baz] core.pg.client=/usr/local/pgsql/bin/psql core.pg.user=theory -core.pg.username=theory core.top_dir=migrations core.uri=https://github.com/theory/sqitch/} ]], 'Should match all core options'; @@ -798,15 +797,13 @@ core.uri=https://github.com/theory/sqitch/} ok $cmd->execute('core\\.pg\\..+'), 'Call get_regex on core\\.pg\\..+'; is_deeply \@emit, [[q{core.pg.client=/usr/local/pgsql/bin/psql -core.pg.user=theory -core.pg.username=theory} +core.pg.user=theory} ]], 'Should match all core.pg options'; @emit = (); ok $cmd->execute('core\\.pg\\..+', 'theory$'), 'Call get_regex on core\\.pg\\..+ and value regex'; -is_deeply \@emit, [[q{core.pg.user=theory -core.pg.username=theory} +is_deeply \@emit, [[q{core.pg.user=theory} ]], 'Should match all core.pg options that match'; @emit = (); From 6400a586e460d21419068ed9c92cb06f72edd879 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 12:38:39 -0700 Subject: [PATCH 38/59] Update log command to use Target. --- lib/App/Sqitch/Command/log.pm | 18 ++++++++----- t/log.t | 51 ++++++++++++++++++++--------------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/lib/App/Sqitch/Command/log.pm b/lib/App/Sqitch/Command/log.pm index d4bb9b1f5..1ad0f61b6 100644 --- a/lib/App/Sqitch/Command/log.pm +++ b/lib/App/Sqitch/Command/log.pm @@ -193,13 +193,17 @@ sub configure { sub execute { my ( $self, $target ) = @_; - # Warn on multiple targets. - $self->warn(__x( - 'Both the --target option and the target argument passed; using {option}', - option => $self->target, - )) if $target && $self->target; - - my $engine = $self->engine_for_target($self->target // $target); + if (my $t = $self->target // $target) { + $self->warn(__x( + 'Both the --target option and the target argument passed; using {option}', + option => $self->target, + )) if $target && $self->target; + require App::Sqitch::Target; + $target = App::Sqitch::Target->new(sqitch => $self->sqitch, name => $t); + } else { + $target = $self->default_target; + } + my $engine = $target->engine; # Exit with status 1 on uninitialized database, probably not expected. hurl { diff --git a/t/log.t b/t/log.t index 22e23abe6..325c2445c 100644 --- a/t/log.t +++ b/t/log.t @@ -24,8 +24,11 @@ my $CLASS = 'App::Sqitch::Command::log'; require_ok $CLASS; ok my $sqitch = App::Sqitch->new( - top_dir => Path::Class::Dir->new('test-log'), - _engine => 'sqlite', + options => { + engine => 'sqlite', + top_dir => Path::Class::Dir->new('test-log')->stringify, + plan_file => Path::Class::File->new('t/sql/sqitch.plan')->stringify, + }, ), 'Load a sqitch sqitch object'; my $config = $sqitch->config; isa_ok my $log = App::Sqitch::Command->load({ @@ -588,14 +591,16 @@ is $@->message, __x( my $emock = Test::MockModule->new('App::Sqitch::Engine::sqlite'); $emock->mock(destination => 'flipr'); -my $mock_log = Test::MockModule->new(ref $log); -my ($db_arg, $orig_meth); -$db_arg = '_blah'; -$mock_log->mock(engine_for_target => sub { - $db_arg = $_[1]; - shift->$orig_meth(@_); +my $mock_target = Test::MockModule->new('App::Sqitch::Target'); +my ($target_name_arg, $orig_meth); +$target_name_arg = '_blah'; +$mock_target->mock(new => sub { + my $self = shift; + my %p = @_; + $target_name_arg = $p{name}; + $self->$orig_meth(@_); }); -$orig_meth = $mock_log->original('engine_for_target'); +$orig_meth = $mock_target->original('new'); # First test for uninitialized DB. my $init = 0; @@ -608,11 +613,11 @@ is $@->message, __x( 'Database {db} has not been initialized for Sqitch', db => 'db:sqlite:', ), 'Uninit db error message should be correct'; -is $db_arg, undef, 'Should have passed undef to engine_for_target'; +is $target_name_arg, undef, 'Should have passed undef to Target'; # Next, test for no events. $init = 1; -$db_arg = '_blah'; +$target_name_arg = '_blah'; my @events; my $iter = sub { shift @events }; my $search_args; @@ -621,6 +626,7 @@ $emock->mock(search_events => sub { $search_args = [@_]; return $iter; }); +$log = $CLASS->new(sqitch => $sqitch); throws_ok { $log->execute } 'App::Sqitch::X', 'Should get error for empty event table'; is $@->ident, 'log', 'no events error ident should be "log"'; @@ -631,13 +637,14 @@ is $@->message, __x( ), 'no events error message should be correct'; is_deeply $search_args, [limit => 1], 'Search should have been limited to one row'; -is $db_arg, undef, 'Should have passed undef to engine_for_target again'; +is $target_name_arg, undef, 'Should have passed undef to Target again'; # Okay, let's add some events. push @events => {}, $event; -$db_arg = '_blah'; +$target_name_arg = '_blah'; +$log = $CLASS->new(sqitch => $sqitch); ok $log->execute, 'Execute log'; -is $db_arg, undef, 'Should have passed undef to engine_for_target once more'; +is $target_name_arg, undef, 'Should have passed undef to Target once more'; is_deeply $search_args, [ event => undef, change => undef, @@ -655,10 +662,10 @@ is_deeply +MockOutput->get_page, [ # Make sure a passed target is processed. push @events => {}, $event; -$db_arg = '_blah'; +$target_name_arg = '_blah'; ok $log->execute('db:sqlite:whatever.db'), 'Execute with target arg'; -is $db_arg, 'db:sqlite:whatever.db', - 'target arg should have been passed to engine_for_target'; +is $target_name_arg, 'db:sqlite:whatever.db', + 'Target name should have been passed to Target'; is_deeply $search_args, [ event => undef, change => undef, @@ -698,9 +705,9 @@ isa_ok $log = $CLASS->new( reverse => 1, ), $CLASS, 'log with attributes'; -$db_arg = '_blah'; +$target_name_arg = '_blah'; ok $log->execute, 'Execute log with attributes'; -is $db_arg, $log->target, 'Should have passed target to engine_for_target'; +is $target_name_arg, $log->target, 'Should have passed target name to Target'; is_deeply $search_args, [ event => [qw(revert fail)], change => '.+', @@ -720,7 +727,7 @@ is_deeply +MockOutput->get_page, [ # Make sure we get a warning when both the option and the arg are specified. push @events => {}, $event; ok $log->execute('foo'), 'Execute log with attributes'; -is $db_arg, $log->target, 'Should have passed target to engine_for_target'; +is $target_name_arg, $log->target, 'Should have passed target name to Target'; is_deeply +MockOutput->get_warn, [[__x( 'Both the --target option and the target argument passed; using {option}', option => $log->target, @@ -733,7 +740,7 @@ isa_ok $log = $CLASS->new( ), $CLASS, 'log with bad format'; push @events, {}, $event; -$db_arg = '_blah'; +$target_name_arg = '_blah'; throws_ok { $log->execute } 'App::Sqitch::X', 'Should get an exception for a bad format code'; is $@->ident, 'format', @@ -741,4 +748,4 @@ is $@->ident, 'format', is $@->message, __x( 'Unknown format code "{code}"', code => 'Z', ), 'bad format code format error message should be correct'; -is $db_arg, $log->target, 'Should have passed target to engine_for_target again'; +is $target_name_arg, $log->target, 'Should have passed target name to Target'; From 7defa7fa10ecb7731f544356c0624e045d6116e4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 14:00:26 -0700 Subject: [PATCH 39/59] Teach the plan command to use default_target. --- lib/App/Sqitch/Command/plan.pm | 6 +++--- t/plan_command.t | 23 +++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/App/Sqitch/Command/plan.pm b/lib/App/Sqitch/Command/plan.pm index 0b8db0b61..116f3ca13 100644 --- a/lib/App/Sqitch/Command/plan.pm +++ b/lib/App/Sqitch/Command/plan.pm @@ -175,7 +175,7 @@ sub configure { sub execute { my $self = shift; - my $plan = $self->plan; + my $plan = $self->default_target->plan; # Exit with status 1 on no changes, probably not expected. hurl { @@ -183,7 +183,7 @@ sub execute { exitval => 1, message => __x( 'No changes in {file}', - file => $self->sqitch->plan_file, + file => $plan->file, ), } unless $plan->count; @@ -201,7 +201,7 @@ sub execute { my $formatter = $self->formatter; my $format = $self->format; $self->page( '# ', __x 'Project: {project}', project => $plan->project ); - $self->page( '# ', __x 'File: {file}', file => $self->sqitch->plan_file ); + $self->page( '# ', __x 'File: {file}', file => $plan->file ); $self->page(''); while ( my $change = $iter->() ) { $self->page( $formatter->format( $format, { diff --git a/t/plan_command.t b/t/plan_command.t index b6fbeb007..d4985cbfc 100644 --- a/t/plan_command.t +++ b/t/plan_command.t @@ -24,9 +24,11 @@ my $CLASS = 'App::Sqitch::Command::plan'; require_ok $CLASS; ok my $sqitch = App::Sqitch->new( - top_dir => Path::Class::Dir->new('test-plan_command'), - _engine => 'sqlite', - plan_file => file(qw(t sql sqitch.plan)), + options => { + engine => 'sqlite', + top_dir => Path::Class::Dir->new('test-plan_command')->stringify, + plan_file => file(qw(t sql sqitch.plan))->stringify, + }, ), 'Load a sqitch sqitch object'; my $config = $sqitch->config; isa_ok my $cmd = App::Sqitch::Command->load({ @@ -543,13 +545,14 @@ my $pmock = Test::MockModule->new('App::Sqitch::Plan'); # First, test for no changes. $pmock->mock(count => 0); +my $plan = $cmd->default_target->plan; throws_ok { $cmd->execute } 'App::Sqitch::X', 'Should get error for no changes'; is $@->ident, 'plan', 'no changes error ident should be "plan"'; is $@->exitval, 1, 'no changes exit val should be 1'; is $@->message, __x( 'No changes in {file}', - file => $sqitch->plan_file + file => $plan->file, ), 'no changes error message should be correct'; $pmock->unmock('count'); @@ -563,7 +566,7 @@ $pmock->mock(search_changes => sub { return $iter; }); -$change = $sqitch->plan->change_at(0); +$change = $plan->change_at(0); push @changes => $change; ok $cmd->execute, 'Execute plan'; is_deeply $search_args, [ @@ -589,14 +592,14 @@ my $fmt_params = { planner_email => $change->planner_email, }; is_deeply +MockOutput->get_page, [ - ['# ', __x 'Project: {project}', project => $sqitch->plan->project ], - ['# ', __x 'File: {file}', file => $sqitch->plan_file ], + ['# ', __x 'Project: {project}', project => $plan->project ], + ['# ', __x 'File: {file}', file => $plan->file ], [''], [ $cmd->formatter->format( $cmd->format, $fmt_params ) ], ], 'The event should have been paged'; # Set attributes and add more events. -my $change2 = $sqitch->plan->change_at(1); +my $change2 = $plan->change_at(1); push @changes => $change, $change2; isa_ok $cmd = $CLASS->new( sqitch => $sqitch, @@ -634,8 +637,8 @@ my $fmt_params2 = { }; is_deeply +MockOutput->get_page, [ - ['# ', __x 'Project: {project}', project => $sqitch->plan->project ], - ['# ', __x 'File: {file}', file => $sqitch->plan_file ], + ['# ', __x 'Project: {project}', project => $plan->project ], + ['# ', __x 'File: {file}', file => $plan->file ], [''], [ $cmd->formatter->format( $cmd->format, $fmt_params ) ], [ $cmd->formatter->format( $cmd->format, $fmt_params2 ) ], From 4d8a3bf35fb9c7ede928236e7599ae7f9906e45c Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 14:08:56 -0700 Subject: [PATCH 40/59] Teach rebase to use Target. --- lib/App/Sqitch/Command/rebase.pm | 8 ++-- t/rebase.t | 64 +++++++++++++++++--------------- 2 files changed, 39 insertions(+), 33 deletions(-) diff --git a/lib/App/Sqitch/Command/rebase.pm b/lib/App/Sqitch/Command/rebase.pm index c1ea0ce9d..9cf0685b7 100644 --- a/lib/App/Sqitch/Command/rebase.pm +++ b/lib/App/Sqitch/Command/rebase.pm @@ -61,7 +61,7 @@ sub configure { sub execute { my $self = shift; - my %args = $self->parse_args(@_); + my %args = $self->parse_args(target => $self->target, args => \@_); # Die on unknowns. if (my @unknown = @{ $args{unknown}} ) { @@ -74,10 +74,10 @@ sub execute { } # Warn on multiple targets. - my $target = $self->target // shift @{ $args{targets} }; + my $target = shift @{ $args{targets} }; $self->warn(__x( 'Too many targets specified; connecting to {target}', - target => $target, + target => $target->name, )) if @{ $args{targets} }; # Warn on too many changes. @@ -91,7 +91,7 @@ sub execute { # Now get to work. - my $engine = $self->engine_for_target($target); + my $engine = $target->engine; $engine->with_verify( $self->verify ); $engine->no_prompt( $self->no_prompt ); $engine->prompt_accept( $self->prompt_accept ); diff --git a/t/rebase.t b/t/rebase.t index 85aed50e2..ea8173dcd 100644 --- a/t/rebase.t +++ b/t/rebase.t @@ -16,8 +16,8 @@ use MockOutput; my $CLASS = 'App::Sqitch::Command::rebase'; require_ok $CLASS or die; -$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; -$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; +$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; +$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; isa_ok $CLASS, 'App::Sqitch::Command'; @@ -50,9 +50,11 @@ is_deeply [$CLASS->options], [qw( )], 'Options should be correct'; my $sqitch = App::Sqitch->new( - plan_file => file(qw(t sql sqitch.plan)), - top_dir => dir(qw(t sql)), - _engine => 'sqlite', + options => { + engine => 'sqlite', + plan_file => file(qw(t sql sqitch.plan))->stringify, + top_dir => dir(qw(t sql))->stringify, + } ); my $config = $sqitch->config; @@ -282,18 +284,22 @@ $mock_engine->mock(revert => sub { shift; @rev_args = @_ }); my @vars; $mock_engine->mock(set_variables => sub { shift; push @vars => [@_] }); -my $mock_sqitch = Test::MockModule->new(ref $sqitch); -my ($engine, $orig_emethod); -$mock_sqitch->mock(engine => sub { $engine = shift->$orig_emethod(@_) }); -$orig_emethod = $mock_sqitch->original('engine'); +my $mock_cmd = Test::MockModule->new($CLASS); +my ($target, $orig_method); +$mock_cmd->mock(parse_args => sub { + my %ret = shift->$orig_method(@_); + $target = $ret{targets}[0]; + %ret; +}); +$orig_method = $mock_cmd->original('parse_args'); ok $rebase->execute('@alpha'), 'Execute to "@alpha"'; is_deeply \@dep_args, [undef, 'all'], 'undef, and "all" should be passed to the engine deploy'; is_deeply \@rev_args, ['@alpha'], '"@alpha" should be passed to the engine revert'; -ok !$sqitch->engine->no_prompt, 'Engine should prompt'; -ok !$sqitch->engine->log_only, 'Engine should no be log only'; +ok !$target->engine->no_prompt, 'Engine should prompt'; +ok !$target->engine->log_only, 'Engine should no be log only'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Pass a target. @@ -302,20 +308,20 @@ is_deeply \@dep_args, [undef, 'all'], 'undef, and "all" should be passed to the engine deploy'; is_deeply \@rev_args, [undef], 'undef should be passed to the engine revert'; -ok !$engine->no_prompt, 'Engine should prompt'; -ok !$engine->log_only, 'Engine should no be log only'; -is $engine->target, 'db:sqlite:yow', 'The engine should know the target'; +ok !$target->engine->no_prompt, 'Engine should prompt'; +ok !$target->engine->log_only, 'Engine should no be log only'; +is $target->name, 'db:sqlite:yow', 'The target name should be as passed'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Pass both. -ok $rebase->execute('widgets', 'db:sqlite:yow'), 'Execute with onto and target'; +ok $rebase->execute('db:sqlite:yow', 'widgets'), 'Execute with onto and target'; is_deeply \@dep_args, [undef, 'all'], 'undef, and "all" should be passed to the engine deploy'; is_deeply \@rev_args, ['widgets'], '"widgets" should be passed to the engine revert'; -ok !$engine->no_prompt, 'Engine should prompt'; -ok !$engine->log_only, 'Engine should no be log only'; -is $engine->target, 'db:sqlite:yow', 'The engine should know the target'; +ok !$target->engine->no_prompt, 'Engine should prompt'; +ok !$target->engine->log_only, 'Engine should no be log only'; +is $target->name, 'db:sqlite:yow', 'The target name should be as passed'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Pass all three! @@ -325,9 +331,9 @@ is_deeply \@dep_args, ['widgets', 'all'], '"widgets", and "all" should be passed to the engine deploy'; is_deeply \@rev_args, ['roles'], '"roles" should be passed to the engine revert'; -ok !$engine->no_prompt, 'Engine should prompt'; -ok !$engine->log_only, 'Engine should no be log only'; -is $engine->target, 'db:sqlite:yow', 'The engine should know the target'; +ok !$target->engine->no_prompt, 'Engine should prompt'; +ok !$target->engine->log_only, 'Engine should no be log only'; +is $target->name, 'db:sqlite:yow', 'The target name should be as passed'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Pass no args. @@ -357,10 +363,10 @@ isa_ok $rebase = $CLASS->new( @dep_args = @rev_args = (); ok $rebase->execute, 'Execute again'; -is $engine->target, 'db:sqlite:lolwut', 'ENgine should have target option'; -ok $engine->no_prompt, 'Engine should be no_prompt'; -ok $engine->log_only, 'Engine should be log_only'; -ok $engine->with_verify, 'Engine should verify'; +is $target->name, 'db:sqlite:lolwut', 'Target name should be from option'; +ok $target->engine->no_prompt, 'Engine should be no_prompt'; +ok $target->engine->log_only, 'Engine should be log_only'; +ok $target->engine->with_verify, 'Engine should verify'; is_deeply \@dep_args, ['bar', 'tag'], '"bar", "tag", and 1 should be passed to the engine deploy'; is_deeply \@rev_args, ['foo'], '"foo" and 1 should be passed to the engine revert'; @@ -375,10 +381,10 @@ is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; @dep_args = @rev_args, @vars = (); ok $rebase->execute('db:sqlite:yow', 'roles', 'widgets'), 'Execute with three args'; -is $engine->target, 'db:sqlite:lolwut', 'ENgine should have target option'; -ok $engine->no_prompt, 'Engine should be no_prompt'; -ok $engine->log_only, 'Engine should be log_only'; -ok $engine->with_verify, 'Engine should verify'; +is $target->name, 'db:sqlite:lolwut', 'Target name should be from option'; +ok $target->engine->no_prompt, 'Engine should be no_prompt'; +ok $target->engine->log_only, 'Engine should be log_only'; +ok $target->engine->with_verify, 'Engine should verify'; is_deeply \@dep_args, ['bar', 'tag'], '"bar", "tag", and 1 should be passed to the engine deploy'; is_deeply \@rev_args, ['foo'], '"foo" and 1 should be passed to the engine revert'; From 346e57b8c338db32538f6bec312f8f02443a5cea Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 14:17:05 -0700 Subject: [PATCH 41/59] Teach revert command to use Target. --- lib/App/Sqitch/Command/revert.pm | 8 ++--- t/revert.t | 56 ++++++++++++++++++-------------- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/lib/App/Sqitch/Command/revert.pm b/lib/App/Sqitch/Command/revert.pm index b3308f34d..09cf512a6 100644 --- a/lib/App/Sqitch/Command/revert.pm +++ b/lib/App/Sqitch/Command/revert.pm @@ -106,7 +106,7 @@ sub configure { sub execute { my $self = shift; - my %args = $self->parse_args(@_); + my %args = $self->parse_args(target => $self->target, args => \@_); # Die on unknowns. if (my @unknown = @{ $args{unknown}} ) { @@ -119,10 +119,10 @@ sub execute { } # Warn on multiple targets. - my $target = $self->target // shift @{ $args{targets} }; + my $target = shift @{ $args{targets} }; $self->warn(__x( 'Too many targets specified; connecting to {target}', - target => $target, + target => $target->name, )) if @{ $args{targets} }; # Warn on too many changes. @@ -133,7 +133,7 @@ sub execute { )) if @{ $args{changes} }; # Now get to work. - my $engine = $self->engine_for_target($target); + my $engine = $target->engine; $engine->no_prompt( $self->no_prompt ); $engine->prompt_accept( $self->prompt_accept ); $engine->log_only( $self->log_only ); diff --git a/t/revert.t b/t/revert.t index ce54208f1..41e0f5f55 100644 --- a/t/revert.t +++ b/t/revert.t @@ -41,9 +41,11 @@ is_deeply [$CLASS->options], [qw( )], 'Options should be correct'; my $sqitch = App::Sqitch->new( - plan_file => file(qw(t sql sqitch.plan)), - top_dir => dir(qw(t sql)), - _engine => 'sqlite', + options => { + engine => 'sqlite', + plan_file => file(qw(t sql sqitch.plan))->stringify, + top_dir => dir(qw(t sql))->stringify, + }, ); my $config = $sqitch->config; @@ -144,15 +146,19 @@ $mock_engine->mock(revert => sub { shift; @args = @_ }); my @vars; $mock_engine->mock(set_variables => sub { shift; @vars = @_ }); -my $mock_sqitch = Test::MockModule->new(ref $sqitch); -my ($engine, $orig_emethod); -$mock_sqitch->mock(engine => sub { $engine = shift->$orig_emethod(@_) }); -$orig_emethod = $mock_sqitch->original('engine'); +my $mock_cmd = Test::MockModule->new($CLASS); +my ($target, $orig_method); +$mock_cmd->mock(parse_args => sub { + my %ret = shift->$orig_method(@_); + $target = $ret{targets}[0]; + %ret; +}); +$orig_method = $mock_cmd->original('parse_args'); # Pass the change. ok $revert->execute('@alpha'), 'Execute to "@alpha"'; -ok $engine->no_prompt, 'Engine should be no_prompt'; -ok !$engine->log_only, 'Engine should not be log_only'; +ok $target->engine->no_prompt, 'Engine should be no_prompt'; +ok !$target->engine->log_only, 'Engine should not be log_only'; is_deeply \@args, ['@alpha'], '"@alpha" should be passed to the engine'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; @@ -168,29 +174,29 @@ is_deeply +MockOutput->get_warn, [], 'Should still have no warnings'; # Pass the target. ok $revert->execute('db:sqlite:hi'), 'Execute to target'; -ok $engine->no_prompt, 'Engine should be no_prompt'; -ok !$engine->log_only, 'Engine should not be log_only'; +ok $target->engine->no_prompt, 'Engine should be no_prompt'; +ok !$target->engine->log_only, 'Engine should not be log_only'; is_deeply \@args, [undef], 'undef" should be passed to the engine'; -is $engine->target, 'db:sqlite:hi', 'Enging should have passed target'; +is $target->name, 'db:sqlite:hi', 'Target name should be as passed'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Pass them both! -ok $revert->execute('widgets', 'db:sqlite:lol'), 'Execute with change and target'; -ok $engine->no_prompt, 'Engine should be no_prompt'; -ok !$engine->log_only, 'Engine should not be log_only'; +ok $revert->execute('db:sqlite:lol', 'widgets'), 'Execute with change and target'; +ok $target->engine->no_prompt, 'Engine should be no_prompt'; +ok !$target->engine->log_only, 'Engine should not be log_only'; is_deeply \@args, ['widgets'], '"widgets" should be passed to the engine'; -is $engine->target, 'db:sqlite:lol', 'Enging should have passed target'; +is $target->name, 'db:sqlite:lol', 'Target name should be as passed'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # And reverse them. ok $revert->execute('db:sqlite:lol', 'widgets'), 'Execute with target and change'; -ok $engine->no_prompt, 'Engine should be no_prompt'; -ok !$engine->log_only, 'Engine should not be log_only'; +ok $target->engine->no_prompt, 'Engine should be no_prompt'; +ok !$target->engine->log_only, 'Engine should not be log_only'; is_deeply \@args, ['widgets'], '"widgets" should be passed to the engine'; -is $engine->target, 'db:sqlite:lol', 'Enging should have passed target'; +is $target->name, 'db:sqlite:lol', 'Target name should be as passed'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Now specify options. @@ -204,24 +210,24 @@ isa_ok $revert = $CLASS->new( @args = (); ok $revert->execute, 'Execute again'; -ok !$engine->no_prompt, 'Engine should not be no_prompt'; -ok $engine->log_only, 'Engine should be log_only'; +ok !$target->engine->no_prompt, 'Engine should not be no_prompt'; +ok $target->engine->log_only, 'Engine should be log_only'; is_deeply \@args, ['foo'], '"foo" and 1 should be passed to the engine'; is_deeply {@vars}, { foo => 'bar', one => 1 }, 'Vars should have been passed through to the engine'; -is $engine->target, 'db:sqlite:welp', 'Enging should have target option'; +is $target->name, 'db:sqlite:welp', 'Target name should be from option'; is_deeply +MockOutput->get_warn, [], 'Should have no warnings'; # Try also passing the target and change. ok $revert->execute('db:sqlite:lol', '@alpha'), 'Execute with options and args'; -ok !$engine->no_prompt, 'Engine should not be no_prompt'; -ok $engine->log_only, 'Engine should be log_only'; +ok !$target->engine->no_prompt, 'Engine should not be no_prompt'; +ok $target->engine->log_only, 'Engine should be log_only'; is_deeply \@args, ['foo'], '"foo" and 1 should be passed to the engine'; is_deeply {@vars}, { foo => 'bar', one => 1 }, 'Vars should have been passed through to the engine'; -is $engine->target, 'db:sqlite:welp', 'Enging should have target option'; +is $target->name, 'db:sqlite:welp', 'Target name should be from option'; is_deeply +MockOutput->get_warn, [[__x( 'Too many targets specified; connecting to {target}', target => 'db:sqlite:welp', From 8d7276c4cf7132d9fbacccda872e6b7acb41c022 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 14:30:45 -0700 Subject: [PATCH 42/59] Teach rework to use default_target. --- lib/App/Sqitch/Command/rework.pm | 9 ++++---- t/rework.t | 36 +++++++++++++++++++------------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/App/Sqitch/Command/rework.pm b/lib/App/Sqitch/Command/rework.pm index 9af55e11a..b3100a1c7 100644 --- a/lib/App/Sqitch/Command/rework.pm +++ b/lib/App/Sqitch/Command/rework.pm @@ -62,8 +62,8 @@ sub options { sub execute { my ( $self, $name ) = @_; $self->usage unless defined $name; - my $sqitch = $self->sqitch; - my $plan = $sqitch->plan; + my $target = $self->default_target; + my $plan = $target->plan; # Rework it. my $reworked = $plan->rework( @@ -116,13 +116,13 @@ sub execute { ); # We good, write the plan file back out. - $plan->write_to( $sqitch->plan_file ); + $plan->write_to( $target->plan_file ); # Let the user knnow what to do. $self->info(__x( 'Added "{change}" to {file}.', change => $reworked->format_op_name_dependencies, - file => $sqitch->plan_file, + file => $target->plan_file, )); $self->info(__n( 'Modify this file as appropriate:', @@ -133,6 +133,7 @@ sub execute { # Let 'em at it. if ($self->open_editor) { + my $sqitch = $self->sqitch; $sqitch->shell( $sqitch->editor . ' ' . $sqitch->quote_shell(@files) ); } diff --git a/t/rework.t b/t/rework.t index 33dc16087..3a1e0b10d 100644 --- a/t/rework.t +++ b/t/rework.t @@ -24,26 +24,30 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; my $CLASS = 'App::Sqitch::Command::rework'; ok my $sqitch = App::Sqitch->new( - top_dir => Path::Class::Dir->new('test-rework'), - _engine => 'pg', + options => { + engine => 'pg', + top_dir => Path::Class::Dir->new('test-rework')->stringify, + }, ), 'Load a sqitch sqitch object'; +my $config = $sqitch->config; +isa_ok my $rework = App::Sqitch::Command->load({ + sqitch => $sqitch, + command => 'rework', + config => $config, +}), $CLASS, 'rework command'; +my $target = $rework->default_target; + sub dep($) { my $dep = App::Sqitch::Plan::Depend->new( conflicts => 0, %{ App::Sqitch::Plan::Depend->parse(shift) }, - plan => $sqitch->plan, + plan => $rework->default_target->plan, ); $dep->project; return $dep; } -my $config = $sqitch->config; -isa_ok my $rework = App::Sqitch::Command->load({ - sqitch => $sqitch, - command => 'rework', - config => $config, -}), $CLASS, 'rework command'; can_ok $CLASS, qw( requires @@ -99,12 +103,12 @@ is_deeply $rework->note, [], 'Note should be an arrayref'; # Test execute(). make_path 'test-rework'; END { remove_tree 'test-rework' }; -my $plan_file = $sqitch->plan_file; +my $plan_file = $target->plan_file; my $fh = $plan_file->open('>') or die "Cannot open $plan_file: $!"; say $fh "%project=empty\n\n"; $fh->close or die "Error closing $plan_file: $!"; -my $plan = $sqitch->plan; +my $plan = $target->plan; throws_ok { $rework->execute('foo') } 'App::Sqitch::X', 'Should get an example for nonexistent change'; @@ -127,6 +131,10 @@ $change_mocker->mock(request_note => sub { %request_params = @_; }); +# Use the same plan. +my $mock_plan = Test::MockModule->new(ref $target); +$mock_plan->mock(plan => $plan); + ok my $add = App::Sqitch::Command::add->new( sqitch => $sqitch, template_directory => Path::Class::dir(qw(etc templates)) @@ -191,7 +199,7 @@ is_deeply +MockOutput->get_info, [ [__x( 'Added "{change}" to {file}.', change => 'foo [foo@alpha]', - file => $sqitch->plan_file, + file => $target->plan_file, )], [__n( 'Modify this file as appropriate:', @@ -288,7 +296,7 @@ is_deeply +MockOutput->get_info, [ [__x( 'Added "{change}" to {file}.', change => 'bar [bar@beta foo !dr_evil]', - file => $sqitch->plan_file, + file => $target->plan_file, )], [__n( 'Modify this file as appropriate:', @@ -358,7 +366,7 @@ MOCKSHELL: { [__x( 'Added "{change}" to {file}.', change => 'bar [bar@gamma]', - file => $sqitch->plan_file, + file => $target->plan_file, )], [__n( 'Modify this file as appropriate:', From 282ca9de3fc435a9e3db395d53f916ac8d23ef14 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 14:33:17 -0700 Subject: [PATCH 43/59] Teach show command to use default_target. --- lib/App/Sqitch/Command/show.pm | 5 +++-- t/show.t | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/App/Sqitch/Command/show.pm b/lib/App/Sqitch/Command/show.pm index d85ce2a53..f229cbbc9 100644 --- a/lib/App/Sqitch/Command/show.pm +++ b/lib/App/Sqitch/Command/show.pm @@ -36,11 +36,12 @@ sub configure { sub execute { my ( $self, $type, $key ) = @_; $self->usage unless $type && $key; + my $plan = $self->default_target->plan; # Handle tags first. if ( $type eq 'tag' ) { my $is_id = $key =~ /^[0-9a-f]{40}/; - my $change = $self->plan->get( + my $change = $plan->get( $is_id ? $key : ($key =~ /^@/ ? '' : '@') . $key ); @@ -69,7 +70,7 @@ sub execute { ) unless first { $type eq $_ } qw(change deploy revert verify); # Make sure we have a change object. - my $change = $self->plan->get($key) or do { + my $change = $plan->get($key) or do { return if $self->exists_only; hurl show => __x( 'Unknown change "{change}"', diff --git a/t/show.t b/t/show.t index 6edc63e10..62571e374 100644 --- a/t/show.t +++ b/t/show.t @@ -27,9 +27,11 @@ is_deeply [$CLASS->options], [qw( )], 'Options should be correct'; my $sqitch = App::Sqitch->new( - plan_file => file(qw(t engine sqitch.plan)), - top_dir => dir(qw(t engine)), - _engine => 'pg', + options => { + plan_file => file(qw(t engine sqitch.plan))->stringify, + top_dir => dir(qw(t engine))->stringify, + engine => 'pg', + }, ); isa_ok my $show = $CLASS->new(sqitch => $sqitch), $CLASS; @@ -50,7 +52,7 @@ is_deeply $CLASS->configure($config, {exists => 1}), { exists_only => 1 }, ############################################################################## # Start with the change. -ok my $change = $sqitch->plan->get('users'), 'Get a change'; +ok my $change = $show->default_target->plan->get('users'), 'Get a change'; ok $show->execute( change => $change->id ), 'Find change by id'; is_deeply +MockOutput->get_emit, [[ $change->info ]], From 818fd6e644f73dd9f28bfc313726422fa08f4494 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 16:00:11 -0700 Subject: [PATCH 44/59] Teach the status command to use Target. --- lib/App/Sqitch/Command/status.pm | 38 ++++++++---- t/status.t | 100 +++++++++++++++++++------------ 2 files changed, 87 insertions(+), 51 deletions(-) diff --git a/lib/App/Sqitch/Command/status.pm b/lib/App/Sqitch/Command/status.pm index 74084e884..1c6eca0db 100644 --- a/lib/App/Sqitch/Command/status.pm +++ b/lib/App/Sqitch/Command/status.pm @@ -7,7 +7,7 @@ use utf8; use Locale::TextDomain qw(App-Sqitch); use App::Sqitch::X qw(hurl); use Moo; -use Types::Standard qw(Str Bool); +use App::Sqitch::Types qw(Str Bool Target); use List::Util qw(max); use Try::Tiny; use namespace::autoclean; @@ -15,11 +15,17 @@ extends 'App::Sqitch::Command'; our $VERSION = '0.997'; -has target => ( +has target_name => ( is => 'ro', isa => Str, ); +has target => ( + is => 'rw', + isa => Target, + handles => [qw(engine plan plan_file)], +); + has show_changes => ( is => 'ro', isa => Bool, @@ -91,13 +97,19 @@ sub options { sub execute { my ( $self, $target ) = @_; - # Warn on multiple targets. - $self->warn(__x( - 'Both the --target option and the target argument passed; using {option}', - option => $self->target, - )) if $target && $self->target; - - my $engine = $self->engine_for_target($self->target // $target); + # Need to set up the target before we do anything else. + if (my $t = $self->target_name // $target) { + $self->warn(__x( + 'Both the --target option and the target argument passed; using {option}', + option => $self->target_name, + )) if $target && $self->target_name; + require App::Sqitch::Target; + $target = App::Sqitch::Target->new(sqitch => $self->sqitch, name => $t); + } else { + $target = $self->default_target; + } + $self->target($target); + my $engine = $target->engine; # Where are we? $self->comment( __x 'On database {db}', db => $engine->destination ); @@ -130,7 +142,7 @@ sub execute { $self->emit_changes; $self->emit_tags; - my $plan_proj = try { $self->plan->project }; + my $plan_proj = try { $target->plan->project }; if (defined $plan_proj && $self->project eq $plan_proj ) { $self->emit_status($state); } else { @@ -156,7 +168,9 @@ sub configure { App::Sqitch::DateTime->validate_as_string_format($format); } - return $class->SUPER::configure( $config, $opt ); + my $ret = $class->SUPER::configure( $config, $opt ); + $ret->{target_name} = delete $ret->{target} if exists $ret->{target}; + return $ret; } sub emit_state { @@ -272,7 +286,7 @@ sub emit_status { my $idx = $plan->index_of( $state->{change_id} ) // do { $self->vent(__x( 'Cannot find this change in {file}', - file => $self->sqitch->plan_file + file => $self->plan_file )); hurl status => __ 'Make sure you are connected to the proper ' . 'database for this project.'; diff --git a/t/status.t b/t/status.t index 3d9a50f88..e4102656d 100644 --- a/t/status.t +++ b/t/status.t @@ -3,7 +3,7 @@ use strict; use warnings; use utf8; -use Test::More tests => 112; +use Test::More tests => 113; #use Test::More 'no_plan'; use App::Sqitch; use Locale::TextDomain qw(App-Sqitch); @@ -17,13 +17,15 @@ use MockOutput; my $CLASS = 'App::Sqitch::Command::status'; require_ok $CLASS; -$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; -$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; +$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; +$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; ok my $sqitch = App::Sqitch->new( - top_dir => Path::Class::Dir->new('test-status'), - _engine => 'sqlite', + options => { + engine => 'sqlite', + top_dir => Path::Class::Dir->new('test-status'), + }, ), 'Load a sqitch object'; my $config = $sqitch->config; isa_ok my $status = App::Sqitch::Command->load({ @@ -63,11 +65,17 @@ $engine_mocker->mock( initialized => sub { $initialized; } ); +my $mock_target = Test::MockModule->new('App::Sqitch::Target'); +my ($target, $orig_new); +$mock_target->mock(new => sub { $target = shift->$orig_new(@_); }); +$orig_new = $mock_target->original('new'); + # Start with uninitialized database. $initialized = 0; ############################################################################## # Test project. +$status->target($status->default_target); throws_ok { $status->project } 'App::Sqitch::X', 'Should have error for uninitialized database'; is $@->ident, 'status', 'Uninitialized database error ident should be "status"'; @@ -84,11 +92,14 @@ is $status->project, 'foo', 'Should have project "foo"'; # Look up the project in the database. ok $sqitch = App::Sqitch->new( - _engine => 'sqlite', - top_dir => Path::Class::Dir->new('test-status'), + options => { + engine => 'sqlite', + top_dir => Path::Class::Dir->new('test-status')->stringify, + }, ), 'Load a sqitch object with SQLite'; -ok $status = $CLASS->new(sqitch => $sqitch), 'Create another status command'; +ok $status = $CLASS->new(sqitch => $sqitch), 'Create another status command'; +$status->target($status->default_target); throws_ok { $status->project } 'App::Sqitch::X', 'Should get an error for uninitialized db'; is $@->ident, 'status', 'Uninitialized db error ident should be "status"'; @@ -120,21 +131,22 @@ $engine_mocker->unmock_all; # Fall back on plan project name. ok $sqitch = App::Sqitch->new( - top_dir => Path::Class::Dir->new(qw(t sql)), + options => { top_dir => Path::Class::Dir->new(qw(t sql))->stringify }, ), 'Load another sqitch object'; isa_ok $status = $CLASS->new( sqitch => $sqitch ), $CLASS, 'another status command'; -is $status->project, $sqitch->plan->project, 'Should have plan project'; +$status->target($status->default_target); +is $status->project, $target->plan->project, 'Should have plan project'; ############################################################################## # Test database. -is $status->target, undef, 'Default target should be undef'; +is $status->target_name, undef, 'Default target should be undef'; isa_ok $status = $CLASS->new( - sqitch => $sqitch, - target => 'foo', + sqitch => $sqitch, + target_name => 'foo', ), $CLASS, 'new status with target'; -is $status->target, 'foo', 'Should have target "foo"'; +is $status->target_name, 'foo', 'Should have target "foo"'; ############################################################################## # Test configure(). @@ -241,7 +253,7 @@ $engine_mocker->mock(current_changes => sub { planner_email => 'anna@example.com', planned_at => $dt->clone->subtract( hours => 4 ), }); -$sqitch = App::Sqitch->new(_engine => 'sqlite'); +$sqitch = App::Sqitch->new(options => { engine => 'sqlite' }); ok $status = App::Sqitch::Command->load({ sqitch => $sqitch, command => 'status', @@ -257,6 +269,7 @@ ok $status = App::Sqitch::Command::status->new( show_changes => 1, project => 'foo', ), 'Create change-showing status command'; +$status->target($status->default_target); ok $status->emit_changes, 'Emit changes again'; is $project, 'foo', 'Project "foo" should have been passed to current_changes'; @@ -327,6 +340,7 @@ ok $status = App::Sqitch::Command::status->new( show_tags => 1, project => 'bar', ), 'Create tag-showing status command'; +$status->target($status->default_target); # Try with no tags. ok $status->emit_tags, 'Try to emit tags again'; @@ -402,13 +416,16 @@ is_deeply +MockOutput->get_comment, [ ############################################################################## # Test emit_status(). my $file = file qw(t plans multi.plan); -$sqitch = App::Sqitch->new(plan_file => $file, _engine => 'sqlite'); -my @changes = $sqitch->plan->changes; +$sqitch = App::Sqitch->new(options => { + plan_file => $file->stringify, + engine => 'sqlite', +}); ok $status = App::Sqitch::Command->load({ sqitch => $sqitch, command => 'status', - config => $config, -}), 'Create status command with actual plan command'; + config => $config,}), 'Create status command with actual plan command'; +$status->target($target = $status->default_target); +my @changes = $target->plan->changes; # Start with an up-to-date state. $state->{change_id} = $changes[-1]->id; @@ -449,18 +466,25 @@ is_deeply +MockOutput->get_vent, [ ############################################################################## # Test execute(). -my $mock_status = Test::MockModule->new(ref $status); -my ($db_arg, $orig_meth); -$mock_status->mock(engine_for_target => sub { - $db_arg = $_[1]; - shift->$orig_meth(@_); +my ($target_name_arg, $orig_meth); +$target_name_arg = '_blah'; +$mock_target->mock(new => sub { + my $self = shift; + my %p = @_; + $target_name_arg = $p{name}; + $self->$orig_meth(@_); }); -$orig_meth = $mock_status->original('engine_for_target'); -my $dest = $sqitch->engine->destination; +$orig_meth = $mock_target->original('new'); + +ok $status = App::Sqitch::Command::status->new( + sqitch => $sqitch, + config => $config, +), 'Recreate status command'; my $check_output = sub { + local $Test::Builder::Level = $Test::Builder::Level + 1; is_deeply +MockOutput->get_comment, [ - [__x 'On database {db}', db => $dest ], + [__x 'On database {db}', db => $target->engine->destination ], [__x 'Project: {project}', project => 'mystatus'], [__x 'Change: {change_id}', change_id => $state->{change_id}], [__x 'Name: {change}', change => 'widgets_table'], @@ -476,17 +500,17 @@ my $check_output = sub { ], 'Should emit list of undeployed changes'; }; + $state->{change_id} = $changes[1]->id; $engine_mocker->mock( current_state => $state ); ok $status->execute, 'Execute'; $check_output->(); -is $db_arg, undef, 'No DB arg should have been passed to engine_for_db'; +is $target_name_arg, undef, 'No target name should have been passed to Target'; # Test with a database argument. ok $status->execute('db:sqlite:'), 'Execute with target arg'; -$dest = 'db:sqlite:multi.db'; $check_output->(); -is $db_arg, 'db:sqlite:', 'DB arg "db:sqlite:" should have been passed to engine_for_db'; +is $target_name_arg, 'db:sqlite:', 'Name "db:sqlite:" should have been passed to Target'; # Pass the database in an option. ok $status = App::Sqitch::Command->load({ @@ -496,23 +520,21 @@ ok $status = App::Sqitch::Command->load({ args => ['--target', 'db:sqlite:'], }), 'Create status command with a target option'; ok $status->execute, 'Execute with target attribute'; -$dest = 'db:sqlite:multi.db'; $check_output->(); -is $db_arg, 'db:sqlite:', 'DB arg "db:sqlite:" should have been passed to engine_for_db'; +is $target_name_arg, 'db:sqlite:', 'Name "db:sqlite:" should have been passed to Target'; # Test with two targets. ok $status->execute('whatever'), 'Execute with target attribute and arg'; -$dest = 'db:sqlite:multi.db'; $check_output->(); -is $db_arg, 'db:sqlite:', 'DB arg "db:sqlite:" should have been passed to engine_for_db'; +is $target_name_arg, 'db:sqlite:', 'Name "db:sqlite:" should have been passed to Target'; is_deeply +MockOutput->get_warn, [[__x( 'Both the --target option and the target argument passed; using {option}', - option => $status->target, + option => $status->target_name, )]], 'Should have got warning for two targets'; # Test with unknown plan. for my $spec ( - [ 'specified', App::Sqitch->new( _engine => 'sqlite', db_name => 'whatever.db') ], + [ 'specified', App::Sqitch->new( options => { engine => 'sqlite' }) ], [ 'external', $sqitch ], ) { my ( $desc, $sqitch ) = @{ $spec }; @@ -523,7 +545,7 @@ for my $spec ( ok $status->execute, "Execute for $desc project"; is_deeply +MockOutput->get_comment, [ - [__x 'On database {db}', db => $sqitch->engine->destination ], + [__x 'On database {db}', db => $target->engine->destination ], [__x 'Project: {project}', project => 'mystatus'], [__x 'Change: {change_id}', change_id => $state->{change_id}], [__x 'Name: {change}', change => 'widgets_table'], @@ -545,7 +567,7 @@ is $@->ident, 'status', 'No state error ident should be "status"'; is $@->message, __ 'No changes deployed', 'No state error message should be correct'; is_deeply +MockOutput->get_comment, [ - [__x 'On database {db}', db => $sqitch->engine->destination ], + [__x 'On database {db}', db => $target->engine->destination ], ], 'The "On database" comment should have been emitted'; # Test with no initilization. @@ -557,5 +579,5 @@ throws_ok { $status->execute } 'App::Sqitch::X', is $@->ident, 'status', 'Uninitialized db error ident should be "status"'; is $@->message, __x( 'Database {db} has not been initialized for Sqitch', - db => 'db:sqlite:sqitch.db', + db => $status->engine->destination, ), 'Uninitialized db error message should be correct'; From f6d56d9ca946a58d4bfb09c7b1b23a9e5db7e943 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 16:03:29 -0700 Subject: [PATCH 45/59] Teach the tag command to use default_target. --- lib/App/Sqitch/Command/tag.pm | 6 +++--- t/tag_cmd.t | 10 +++++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/App/Sqitch/Command/tag.pm b/lib/App/Sqitch/Command/tag.pm index 5d4ace030..c92e27ea7 100644 --- a/lib/App/Sqitch/Command/tag.pm +++ b/lib/App/Sqitch/Command/tag.pm @@ -27,8 +27,8 @@ sub options { sub execute { my ( $self, $name, $change ) = @_; - my $sqitch = $self->sqitch; - my $plan = $sqitch->plan; + my $target = $self->default_target; + my $plan = $target->plan; if (defined $name) { my $tag = $plan->tag( @@ -41,7 +41,7 @@ sub execute { $tag->request_note( for => __ 'tag'); # We good, write the plan file back out. - $plan->write_to( $sqitch->plan_file ); + $plan->write_to( $target->plan_file ); $self->info(__x( 'Tagged "{change}" with {tag}', change => $tag->change->format_name, diff --git a/t/tag_cmd.t b/t/tag_cmd.t index b8c89aff8..f2f3d7d8f 100644 --- a/t/tag_cmd.t +++ b/t/tag_cmd.t @@ -19,7 +19,9 @@ $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; my $CLASS = 'App::Sqitch::Command::tag'; ok my $sqitch = App::Sqitch->new( - top_dir => Path::Class::Dir->new('test-tag_cmd'), + options => { + top_dir => Path::Class::Dir->new('test-tag_cmd')->stringify, + }, ), 'Load a sqitch sqitch object'; my $config = $sqitch->config; isa_ok my $tag = App::Sqitch::Command->load({ @@ -41,7 +43,7 @@ is_deeply [$CLASS->options], [qw( make_path 'test-tag_cmd'; END { remove_tree 'test-tag_cmd' }; -my $plan_file = $sqitch->plan_file; +my $plan_file = $tag->default_target->plan_file; my $fh = $plan_file->open('>') or die "Cannot open $plan_file: $!"; say $fh "%project=empty\n\n"; $fh->close or die "Error closing $plan_file: $!"; @@ -54,7 +56,7 @@ $tag_mocker->mock(request_note => sub { %request_params = @_; }); -my $plan = $sqitch->plan; +my $plan = $tag->default_target->plan; ok $plan->add( name => 'foo' ), 'Add change "foo"'; ok $tag->execute('alpha'), 'Tag @alpha'; @@ -93,6 +95,7 @@ isa_ok $tag = App::Sqitch::Command::tag->new({ sqitch => $sqitch, note => [qw(hello there)], }), $CLASS, 'tag command with note'; +$plan = $tag->default_target->plan; ok $tag->execute( 'gamma' ), 'Tag @gamma'; is $plan->get('@gamma')->name, 'foo', 'Gamma tag should be on change "foo"'; @@ -115,6 +118,7 @@ isa_ok $tag = App::Sqitch::Command::tag->new({ sqitch => $sqitch, note => ['here we go'], }), $CLASS, 'tag command with note'; +$plan = $tag->default_target->plan; ok $plan->add( name => 'bar' ), 'Add change "bar"'; ok $plan->add( name => 'baz' ), 'Add change "baz"'; From 99349f41c6c2e424ed85fc35649a080939304d92 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 16:12:17 -0700 Subject: [PATCH 46/59] Teach target command to use Target. --- lib/App/Sqitch/Command/target.pm | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/App/Sqitch/Command/target.pm b/lib/App/Sqitch/Command/target.pm index 7fad167db..92d4c0e8b 100644 --- a/lib/App/Sqitch/Command/target.pm +++ b/lib/App/Sqitch/Command/target.pm @@ -186,7 +186,8 @@ sub _rename { sub show { my ($self, @names) = @_; return $self->list unless @names; - my $config = $self->sqitch->config; + my $sqitch = $self->sqitch; + my $config = $sqitch->config; # Set up labels. my $len = max map { length } ( @@ -201,12 +202,16 @@ sub show { client => __('Client') . ': ' . ' ' x ($len - length __ 'Client'), ); + require App::Sqitch::Target; for my $name (@names) { - my $engine = $self->engine_for_target($name); + my $target = App::Sqitch::Target->new( + sqitch => $sqitch, + name => $name, + ); $self->emit("* $name"); - $self->emit(' ', $label_for{uri}, $engine->uri->as_string); - $self->emit(' ', $label_for{registry}, $engine->registry); - $self->emit(' ', $label_for{client}, $engine->client); + $self->emit(' ', $label_for{uri}, $target->uri->as_string); + $self->emit(' ', $label_for{registry}, $target->registry); + $self->emit(' ', $label_for{client}, $target->client); } return $self; From 61452f42ae97bc356451a7fb04b8fd29f29325e5 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 16:38:29 -0700 Subject: [PATCH 47/59] Teach the verify command to use Target. --- lib/App/Sqitch/Command/verify.pm | 8 +++---- t/verify.t | 37 +++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/lib/App/Sqitch/Command/verify.pm b/lib/App/Sqitch/Command/verify.pm index e11bd3abf..a7244a38e 100644 --- a/lib/App/Sqitch/Command/verify.pm +++ b/lib/App/Sqitch/Command/verify.pm @@ -83,7 +83,7 @@ sub configure { sub execute { my $self = shift; - my %args = $self->parse_args(@_); + my %args = $self->parse_args(target => $self->target, args => \@_); # Die on unknowns. if (my @unknown = @{ $args{unknown}} ) { @@ -96,10 +96,10 @@ sub execute { } # Warn on multiple targets. - my $target = $self->target // shift @{ $args{targets} }; + my $target = shift @{ $args{targets} }; $self->warn(__x( 'Too many targets specified; connecting to {target}', - target => $target, + target => $target->name, )) if @{ $args{targets} }; # Warn on too many changes. @@ -112,7 +112,7 @@ sub execute { )) if @{ $args{changes} }; # Now get to work. - my $engine = $self->engine_for_target($target); + my $engine = $target->engine; if (my %v = %{ $self->variables }) { $engine->set_variables(%v) } $engine->verify($from, $to); return $self; diff --git a/t/verify.t b/t/verify.t index 12e5c402f..84d1abcce 100644 --- a/t/verify.t +++ b/t/verify.t @@ -15,8 +15,8 @@ use MockOutput; my $CLASS = 'App::Sqitch::Command::verify'; require_ok $CLASS or die; -$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; -$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; +$ENV{SQITCH_CONFIG} = 'nonexistent.conf'; +$ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; $ENV{SQITCH_SYSTEM_CONFIG} = 'nonexistent.sys'; isa_ok $CLASS, 'App::Sqitch::Command'; @@ -40,9 +40,11 @@ is_deeply [$CLASS->options], [qw( )], 'Options should be correct'; my $sqitch = App::Sqitch->new( - plan_file => file(qw(t sql sqitch.plan)), - top_dir => dir(qw(t sql)), - _engine => 'sqlite', + options => { + engine => 'sqlite', + plan_file => file(qw(t sql sqitch.plan))->stringify, + top_dir => dir(qw(t sql))->stringify, + }, ); my $config = $sqitch->config; @@ -154,12 +156,21 @@ is_deeply +MockOutput->get_warn, [[__x( )]], 'Should have warning about which roles are used'; # Pass a target. -my $mock_sqitch = Test::MockModule->new('App::Sqitch'); -my $earg; my $target = 'db:pg:'; -$mock_sqitch->mock(engine_for_target => sub { $earg = $_[1]; shift->engine }); +my $mock_cmd = Test::MockModule->new(ref $verify); +my ($target_name_arg, $orig_meth); +$mock_cmd->mock(parse_args => sub { + my $self = shift; + my %p = @_; + my %ret = $self->$orig_meth(@_); + $target_name_arg = $ret{targets}->[0]->name; + $ret{targets}->[0] = $self->default_target; + return %ret; +}); +$orig_meth = $mock_cmd->original('parse_args'); + ok $verify->execute($target), 'Execute with target arg'; -is $earg, $target, 'The target should have been passed to the engine'; +is $target_name_arg, $target, 'The target should have been passed to the engine'; is_deeply \@args, ['foo', 'bar'], '"foo" and "bar" should be passed to the engine'; is_deeply {@vars}, { foo => 'bar', one => 1 }, @@ -171,10 +182,10 @@ isa_ok $verify = $CLASS->new( sqitch => $sqitch, target => $target, ), $CLASS, 'Object with target'; -$earg = undef; +$target_name_arg = undef; @vars = (); ok $verify->execute, 'Execute with no args'; -is $earg, $target, 'The target option should have been passed to the engine'; +is $target_name_arg, $target, 'The target option should have been passed to the engine'; is_deeply \@args, [undef, undef], 'Undefs should be passed to the engine'; is_deeply {@vars}, {}, 'No vars should have been passed through to the engine'; is_deeply +MockOutput->get_warn, [], 'Should once again have no warnings'; @@ -182,13 +193,13 @@ is_deeply +MockOutput->get_warn, [], 'Should once again have no warnings'; # Pass a target, get a warning. ok $verify->execute('db:sqlite:', 'roles', 'widgets'), 'Execute with two targegs and two changes'; -is $earg, $target, 'The target option should have been passed to the engine'; +is $target_name_arg, $target, 'The target option should have been passed to the engine'; is_deeply \@args, ['roles', 'widgets'], 'The two changes should be passed to the engine'; is_deeply {@vars}, {}, 'No vars should have been passed through to the engine'; is_deeply +MockOutput->get_warn, [[__x( 'Too many targets specified; connecting to {target}', - target => $target, + target => $verify->default_target->name, )]], 'Should have warning about too many targets'; # Make sure we get an exception for unknown args. From 60360911526b99a49bc9d06ae25f197af3276973 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 16:47:59 -0700 Subject: [PATCH 48/59] Remove target methods from App::Sqitch. They are now in App::Sqitch::Target. --- lib/App/Sqitch.pm | 206 --------------------------------- lib/App/Sqitch/Command.pm | 4 - t/base.t | 231 +------------------------------------- t/plan.t | 2 +- 4 files changed, 5 insertions(+), 438 deletions(-) diff --git a/lib/App/Sqitch.pm b/lib/App/Sqitch.pm index 31774888d..cb7b3c640 100644 --- a/lib/App/Sqitch.pm +++ b/lib/App/Sqitch.pm @@ -36,31 +36,6 @@ use App::Sqitch::Config; use App::Sqitch::Command; use App::Sqitch::Plan; -has _was_set => (is => 'ro', type => HashRef, default => sub {{}}); - -has plan_file => ( - is => 'ro', - # XXX isa Path::Class::File? - lazy => 1, - trigger => sub { shift->_was_set->{plan_file} = 1 }, - default => sub { - my $self = shift; - if ( my $fn = $self->config->get( key => 'core.plan_file') ) { - return file $fn; - } - return $self->top_dir->file('sqitch.plan')->cleanup; - } -); - -has plan => ( - is => 'ro', - isa => Plan, - lazy => 1, - default => sub { - App::Sqitch::Plan->new( sqitch => shift ); - }, -); - has options => ( is => 'ro', isa => HashRef, @@ -72,143 +47,6 @@ has _engine => ( isa => Maybe[Str], ); -sub engine_key { - my ($self, $uri) = @_; - - # Figure out what engine to use. Precedence --engine, target, config. - my $key = $self->_engine || do { - if ($uri) { - hurl core => __x( - 'URI "{uri}" is not a database URI', - uri => $uri - ) unless $uri->isa('URI::db'); - $uri->canonical_engine; - } elsif ( my $key = $self->config->get( key => 'core.engine' ) ) { - $key =~ s/\s+\z//; - lc $key; - } else { - hurl core => __( - 'No engine specified; use --engine or set core.engine' - ); - } - }; - hurl core => __x('Unknown engine "{engine}"', engine => $key) - unless first { $key eq $_ } qw(pg sqlite mysql oracle firebird vertica); -} - -sub engine { - my $self = shift; - my %p = ref $_[0] ? %{ $_[0] } : @_; - - require App::Sqitch::Engine; - App::Sqitch::Engine->load({ - sqitch => $self, - engine => $self->engine_key($p{uri}), - %p, - }); -} - -sub config_for_target { - my ($self, $target) = @_; - return unless $target; - require URI::db; - return { - target => $target, - uri => URI::db->new($target) - } if $target =~ /:/; - my $config = $self->config->get_section( section => "target.$target" ) - or return; - $config->{target} = $target; - $config->{uri} = URI::db->new( $config->{uri} ) if $config->{uri}; - return $config; -} - -sub config_for_target_strict { - my ($self, $target) = @_; - my $config = shift->config_for_target($target) or hurl core => __x( - 'Cannot find target "{target}"', - target => $target - ); - hurl core => __x( - 'No URI associated with target "{target}"', - target => $target - ) unless $config->{uri}; - return $config; -} - -sub engine_for_target { - my ($self, $target) = @_; - return $self->engine unless $target; - return $self->engine( $self->config_for_target_strict($target) ); -} - -# Attributes useful to engines; no defaults. -has client => ( is => 'ro', isa => Str ); -has db_name => ( is => 'ro', isa => Str ); -has db_username => ( is => 'ro', isa => Str ); -has db_host => ( is => 'ro', isa => Str ); -has db_port => ( is => 'ro', isa => Int ); - -has top_dir => ( - is => 'ro', - isa => Maybe[Dir], - lazy => 1, - trigger => sub { shift->_was_set->{top_dir} = 1 }, - default => sub { dir shift->config->get( key => 'core.top_dir' ) || () }, -); - -has deploy_dir => ( - is => 'ro', - isa => Dir, - lazy => 1, - trigger => sub { shift->_was_set->{deploy_dir} = 1 }, - default => sub { - my $self = shift; - if ( my $dir = $self->config->get( key => 'core.deploy_dir' ) ) { - return dir $dir; - } - $self->top_dir->subdir('deploy')->cleanup; - }, -); - -has revert_dir => ( - is => 'ro', - isa => Dir, - lazy => 1, - trigger => sub { shift->_was_set->{revert_dir} = 1 }, - default => sub { - my $self = shift; - if ( my $dir = $self->config->get( key => 'core.revert_dir' ) ) { - return dir $dir; - } - $self->top_dir->subdir('revert')->cleanup; - }, -); - -has verify_dir => ( - is => 'ro', - isa => Dir, - lazy => 1, - trigger => sub { shift->_was_set->{verify_dir} = 1 }, - default => sub { - my $self = shift; - if ( my $dir = $self->config->get( key => 'core.verify_dir' ) ) { - return dir $dir; - } - $self->top_dir->subdir('verify')->cleanup; - }, -); - -has extension => ( - is => 'ro', - isa => Str, - lazy => 1, - trigger => sub { shift->_was_set->{extension} = 1 }, - default => sub { - shift->config->get( key => 'core.extension' ) || 'sql'; - } -); - has verbosity => ( is => 'ro', lazy => 1, @@ -786,32 +624,10 @@ Constructs and returns a new Sqitch object. The supported parameters include: =item C -=item C - -=item C - -=item C - -=item C - =item C =item C -=item C - -=item C - -=item C - -=item C - -=item C - -=item C - -=item C - =item C =item C @@ -820,32 +636,10 @@ Constructs and returns a new Sqitch object. The supported parameters include: =head2 Accessors -=head3 C - -=head3 C - -=head3 C - -=head3 C - =head3 C =head3 C -=head3 C - -=head3 C - -=head3 C - -=head3 C - -=head3 C - -=head3 C - -=head3 C - =head3 C =head3 C diff --git a/lib/App/Sqitch/Command.pm b/lib/App/Sqitch/Command.pm index 096abb58b..2acd7cbde 100644 --- a/lib/App/Sqitch/Command.pm +++ b/lib/App/Sqitch/Command.pm @@ -19,10 +19,6 @@ has sqitch => ( isa => Sqitch, required => 1, handles => [qw( - engine - config_for_target - config_for_target_strict - engine_for_target run shell quote_shell diff --git a/t/base.t b/t/base.t index 1c6115bcb..248e510c0 100644 --- a/t/base.t +++ b/t/base.t @@ -2,8 +2,8 @@ use strict; use warnings; -#use Test::More tests => 242; -use Test::More 'no_plan'; +use Test::More tests => 134; +#use Test::More 'no_plan'; use Test::MockModule; use Path::Class; use Test::Exception; @@ -26,21 +26,8 @@ can_ok $CLASS, qw( go new options - plan_file - plan - engine - _engine user_name user_email - db_name - db_username - db_host - db_port - top_dir - deploy_dir - revert_dir - verify_dir - extension verbosity prompt ask_y_n @@ -50,220 +37,13 @@ can_ok $CLASS, qw( # Defaults. isa_ok my $sqitch = $CLASS->new, $CLASS, 'A new object'; -for my $attr (qw( - db_username - db_name - db_host - db_port -)) { - is $sqitch->$attr, undef, "$attr should be undef"; -} - -is $sqitch->plan_file, $sqitch->top_dir->file('sqitch.plan')->cleanup, - 'Default plan file should be $top_dir/sqitch.plan'; is $sqitch->verbosity, 1, 'verbosity should be 1'; -is $sqitch->extension, 'sql', 'Default extension should be sql'; -is $sqitch->top_dir, dir(), 'Default top_dir should be .'; -is $sqitch->deploy_dir, dir(qw(deploy)), 'Default deploy_dir should be ./sql/deploy'; -is $sqitch->revert_dir, dir(qw(revert)), 'Default revert_dir should be ./sql/revert'; -is $sqitch->verify_dir, dir(qw(verify)), 'Default verify_dir should be ./sql/verify'; -isa_ok $sqitch->plan, 'App::Sqitch::Plan'; ok $sqitch->user_name, 'Default user_name should be set from system'; is $sqitch->user_email, do { require Sys::Hostname; $sqitch->sysuser . '@' . Sys::Hostname::hostname(); }, 'Default user_email should be set from system'; -# Test engine_key. -throws_ok { $sqitch->engine_key } 'App::Sqitch::X', - 'Should get exception for no engine_key'; -is $@->ident, 'core', 'No engine_key error ident should be "core"'; -is $@->message, __ 'No engine specified; use --engine or set core.engine', - 'No engine_key error message should be correct'; -throws_ok { $sqitch->engine } 'App::Sqitch::X', - 'Should get exception for no engine'; -is $@->ident, 'core', 'No engine error ident should be "core"'; -is $@->message, __ 'No engine specified; use --engine or set core.engine', - 'No engine error message should be correct'; - -# Try an unknown engine. -throws_ok { $CLASS->new(_engine => 'nonexistent')->engine_key } 'App::Sqitch::X', - 'Should get error for unknown engine'; -is $@->ident, 'core', 'Unknown engine error ident should be "core"'; -is $@->message, __x('Unknown engine "{engine}"', engine => 'nonexistent'), - 'Unknown engine error message should be correct'; - -# Try engine key from URI. -is $sqitch->engine_key(URI->new('db:sqlite:foo')), 'sqlite', - 'Should derive sqlite engine key from URI'; - -# Try a URI with a driver that is not lowercase. -is $sqitch->engine_key(URI->new('db:pg:foo')), 'pg', - 'Should derive pg engine key from URI'; - -throws_ok { $sqitch->engine_key(URI->new('db:nonexistent:')) } 'App::Sqitch::X', - 'Should get error for nonexistent engine'; -is $@->ident, 'core', 'Nonexistent engine error ident should be "core"'; -is $@->message, __x('Unknown engine "{engine}"', engine => 'nonexistent'), - 'Nonexistent engine error message should be correct'; - -throws_ok { $sqitch->engine_key(URI->new('file:foo:')) } 'App::Sqitch::X', - 'Should get error for non-db URI'; -is $@->ident, 'core', 'Non-db URI error ident should be "core"'; -is $@->message, __x( - 'URI "{uri}" is not a database URI', - uri => 'file:foo:' -), 'Non-DB URI error message should be correct'; - -# Valid engines and the engine constructors. -for my $eng (qw(pg sqlite mysql oracle firebird vertica)) { - ok my $sqitch = $CLASS->new(engine_key => $eng), - qq{Engine "$eng" should be valid}; - - my $uri = URI->new("db:$eng:foo"); - isa_ok my $engine = $sqitch->engine( uri => $uri ), - "App::Sqitch::Engine::$eng", "$eng engine"; - is $engine->uri, $uri, "URI $uri should have been passed through"; - - ok $engine = $sqitch->engine({ uri => $uri }), - "Create another App::Sqitch::Engine::$eng with hash params"; - is $engine->uri, $uri, "URI $uri should have been passed through again"; -} - -############################################################################## -# Test config_for_target. -is $sqitch->config_for_target, undef, 'Should get no string for no DB param'; -is $sqitch->config_for_target(undef), undef, 'Should get no string for undef DB param'; -is $sqitch->config_for_target(''), undef, 'Should get no string for empty DB param'; -is $sqitch->config_for_target(0), undef, 'Should get no string for DB param 0'; - -# Pass a URI. -is_deeply $sqitch->config_for_target('db:pg:'), { - target => 'db:pg:', - uri => URI->new('db:pg:'), -}, 'Should get target back from config_for_target()'; - -# Pass a key. -CONFIG: { - my $mock = Test::MockModule->new('App::Sqitch::Config'); - my @params; - my $ret = { uri => URI->new('db:sqlite:hi') }; - $mock->mock(get_section => sub { shift; @params = @_; $ret }); - is_deeply $sqitch->config_for_target('grokker'), $ret, - 'Should get target for URI key'; - is_deeply \@params, [section => 'target.grokker'], - 'The URI should have been fetched from the config'; - $ret = undef; - is $sqitch->config_for_target('whatever'), undef, - 'Should get back undef when no URI for key'; - is_deeply \@params, [section => 'target.whatever'], - 'The URI should have been sought in the config'; -} - -############################################################################## -# Test config_for_target_strict. -is_deeply $sqitch->config_for_target_strict('db:pg:foo'), { - target => 'db:pg:foo', - uri => URI->new('db:pg:foo'), -}, 'Should get URI back for URI param'; -isa_ok $sqitch->config_for_target_strict('db:pg:foo')->{uri}, 'URI::db', 'DB URI'; - -# Pass a key. -CONFIG: { - my $mock = Test::MockModule->new('App::Sqitch::Config'); - my @params; - my $ret = { uri => URI->new('db:sqlite:hi') }; - $mock->mock(get_section => sub { shift; @params = @_; $ret }); - is_deeply $sqitch->config_for_target_strict('grokker'), $ret, - 'Should get target back for URI key'; - is_deeply \@params, [section => 'target.grokker'], - 'The target should have been fetched from the config'; - isa_ok $sqitch->config_for_target_strict('bob')->{uri}, 'URI::db', - 'DB URI from config'; - is_deeply \@params, [section => 'target.bob'], - 'The new URI should have been fetched from the config'; - $ret = undef; - throws_ok { $sqitch->config_for_target_strict('grokker') } 'App::Sqitch::X', - 'Should get an exception for unknown config DB key'; - is $@->ident, 'core', 'Unknown key error ident should be "core"'; - is $@->message, __x( - 'Cannot find target "{target}"', - target => 'grokker' - ), 'The unknown key error message should be correct'; -} - -############################################################################## -# Test engine_for_target. -$sqitch = $CLASS->new( _engine => 'sqlite' ); -my $def_uri = $sqitch->engine->uri; -isa_ok $sqitch->engine_for_target, 'App::Sqitch::Engine', 'Engine for DB'; -is $sqitch->engine_for_target->uri, $def_uri, 'Should get default engine for no DB param'; -is $sqitch->engine_for_target(undef)->uri, $def_uri, 'Should get default engine for undef DB param'; -is $sqitch->engine_for_target('')->uri, $def_uri, 'Should get default engine for empty DB param'; -is $sqitch->engine_for_target(0)->uri, $def_uri, 'Should get default engine for DB param 0'; -is $sqitch->engine_for_target->target, $def_uri, 'Should get default engine target'; - -# Pass a URI. -isa_ok my $engine = $sqitch->engine_for_target('db:pg:foo'), - 'App::Sqitch::Engine'; -is $engine->uri, URI->new('db:pg:foo'), - 'Should get properly configured engine URI'; -is $engine->uri, 'db:pg:foo', 'Should get properly-configured target for URI'; - -# Pass a key. -CONFIG: { - my $mock = Test::MockModule->new('App::Sqitch::Config'); - my $ret = { uri => URI->new('db:sqlite:hi') }; - $mock->mock(get_section => sub { $ret }); - is_deeply $sqitch->engine_for_target('grokker')->uri, 'db:sqlite:hi', - 'Should get engine with URI for URI key'; - isa_ok my $engine = $sqitch->engine_for_target('bob'), 'App::Sqitch::Engine', - 'Engine with URI from config'; - is $engine->target, 'bob', 'Engine should know target as "bob"'; - is $engine->uri, URI->new('db:sqlite:hi'), 'Engine should have bob URI'; - - # Should also work when specifying a URI. - isa_ok $engine = $sqitch->engine_for_target('db:sqlite:fred'), 'App::Sqitch::Engine', - 'Engine with URI param'; - is $engine->target, 'db:sqlite:fred', 'Engine should know target by URI'; - is $engine->uri, URI->new('db:sqlite:fred'), 'Engine should have URI'; - - # Now set a default target. - $mock->mock(get => 'bob'); - isa_ok $engine = $sqitch->engine_for_target('db:sqlite:fred'), 'App::Sqitch::Engine', - 'Engine with URI param'; - is $engine->target, 'db:sqlite:fred', 'Engine should know target by URI'; - is $engine->uri, URI->new('db:sqlite:fred'), 'Engine should have URI'; - - $ret = undef; - throws_ok { $sqitch->engine_for_target('grokker') } 'App::Sqitch::X', - 'Should get an exception for unknown config DB key'; - is $@->ident, 'core', 'Unknown key error ident should be "core"'; - is $@->message, __x( - 'Cannot find target "{key}"', - key => 'grokker' - ), 'The unknown key error message should be correct'; -} - -# Test invalid user name and email values. -throws_ok { $CLASS->new(user_name => 'fooident, 'user', 'Invalid user name error ident should be "user"'; -is $@->message, __ 'User name may not contain "<" or start with "["', - 'Invalid user name error message should be correct'; - -throws_ok { $CLASS->new(user_name => '[foobar]') } 'App::Sqitch::X', - 'Should get error for user name starting with "["'; -is $@->ident, 'user', 'Second Invalid user name error ident should be "user"'; -is $@->message, __ 'User name may not contain "<" or start with "["', - 'Second Invalid user name error message should be correct'; - -throws_ok { $CLASS->new(user_email => 'foo>bar') } 'App::Sqitch::X', - 'Should get error for user email containing ">"'; -is $@->ident, 'user', 'Invalid user email error ident should be "user"'; -is $@->message, __ 'User email may not contain ">"', - 'Invalid user email error message should be correct'; - ############################################################################## # Test go(). GO: { @@ -281,14 +61,11 @@ GO: { is_deeply \@params, ['config'], 'Extra args should be passed to execute'; isa_ok my $sqitch = $cmd->sqitch, 'App::Sqitch'; - is $sqitch->engine_key, 'sqlite', 'Engine should be set by option'; - # isa $sqitch->engine, 'App::Sqitch::Engine::sqlite', - # 'Engine object should be constructable'; - is $sqitch->extension, 'ddl', 'ddl should be set by config'; + is $sqitch->options->{engine}, 'sqlite', 'Should have collected --engine'; ok my $config = $sqitch->config, 'Get the Sqitch config'; is $config->get(key => 'core.pg.client'), '/usr/local/pgsql/bin/psql', 'Should have local config overriding user'; - is $config->get(key => 'core.pg.host'), 'localhost', + is $config->get(key => 'core.pg.registry'), 'meta', 'Should fall back on user config'; is $sqitch->user_name, 'Michael Stonebraker', 'Should have read user name from configuration'; diff --git a/t/plan.t b/t/plan.t index db091cc9d..e13f120fb 100644 --- a/t/plan.t +++ b/t/plan.t @@ -1459,7 +1459,7 @@ ok my $orig = $plan->change_at($plan->first_index_of('you')), is $orig->name, 'you', 'It should also be named "you"'; is_deeply [ map { $_->format_name } $orig->rework_tags ], [qw(@bar)], 'And it should have the one rework tag'; -is $orig->deploy_file, $sqitch->deploy_dir->file('you@bar.sql'), +is $orig->deploy_file, $target->deploy_dir->file('you@bar.sql'), 'The original file should now be named you@bar.sql'; is $rev_change->as_string, 'you [you@bar] ' . $rev_change->timestamp->as_string . ' ' From a65ba4b179e42bef4b1fe9c6e5780a24cbbe3388 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 16:49:02 -0700 Subject: [PATCH 49/59] Remove old reference to core.pg.username. --- t/configuration.t | 1 - 1 file changed, 1 deletion(-) diff --git a/t/configuration.t b/t/configuration.t index 82e558bfb..a446b3b71 100644 --- a/t/configuration.t +++ b/t/configuration.t @@ -71,5 +71,4 @@ is_deeply $config->get_section(section => 'core'), { is_deeply $config->get_section(section => 'core.pg'), { client => "/usr/local/pgsql/bin/psql", - username => "theory", }, 'get_section("core.pg") should work'; From 239ffe6226f5f2036255ee2e75a188923cad27ef Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 17:00:13 -0700 Subject: [PATCH 50/59] Teach bundle to use default_target. --- lib/App/Sqitch/Command/bundle.pm | 22 ++++++++++++---------- t/bundle.t | 12 +++++++----- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/lib/App/Sqitch/Command/bundle.pm b/lib/App/Sqitch/Command/bundle.pm index 34863cbf5..cdaf96ba0 100644 --- a/lib/App/Sqitch/Command/bundle.pm +++ b/lib/App/Sqitch/Command/bundle.pm @@ -40,7 +40,7 @@ has dest_top_dir => ( lazy => 1, default => sub { my $self = shift; - dir $self->dest_dir, $self->sqitch->top_dir->relative; + dir $self->dest_dir, $self->default_target->top_dir->relative; }, ); @@ -50,7 +50,7 @@ has dest_deploy_dir => ( lazy => 1, default => sub { my $self = shift; - dir $self->dest_dir, $self->sqitch->deploy_dir->relative; + dir $self->dest_dir, $self->default_target->deploy_dir->relative; }, ); @@ -60,7 +60,7 @@ has dest_revert_dir => ( lazy => 1, default => sub { my $self = shift; - dir $self->dest_dir, $self->sqitch->revert_dir->relative; + dir $self->dest_dir, $self->default_target->revert_dir->relative; }, ); @@ -70,7 +70,7 @@ has dest_verify_dir => ( lazy => 1, default => sub { my $self = shift; - dir $self->dest_dir, $self->sqitch->verify_dir->relative; + dir $self->dest_dir, $self->default_target->verify_dir->relative; }, ); @@ -164,10 +164,11 @@ sub bundle_config { sub bundle_plan { my $self = shift; - my $sqitch = $self->sqitch; + my $target = $self->default_target; + if (!defined $self->from && !defined $self->to) { $self->info(__ 'Writing plan'); - my $file = $self->sqitch->plan_file; + my $file = $target->plan_file; return $self->_copy_if_modified( $file, $self->dest_top_dir->file( $file->basename ), @@ -180,8 +181,8 @@ sub bundle_plan { to => $self->to // '@HEAD', )); - $sqitch->plan->write_to( - $self->dest_top_dir->file( $sqitch->plan_file->basename ), + $target->plan->write_to( + $self->dest_top_dir->file( $target->plan_file->basename ), $self->from, $self->to, ); @@ -189,8 +190,9 @@ sub bundle_plan { sub bundle_scripts { my $self = shift; - my $top = $self->sqitch->top_dir; - my $plan = $self->plan; + my $target = $self->default_target; + my $top = $target->top_dir; + my $plan = $target->plan; my $dir = $self->dest_dir; my $from_index = $plan->index_of( diff --git a/t/bundle.t b/t/bundle.t index 5abb14454..61b240c7f 100644 --- a/t/bundle.t +++ b/t/bundle.t @@ -75,7 +75,7 @@ chdir 't'; $ENV{SQITCH_CONFIG} = 'sqitch.conf'; END { remove_tree 'bundle' if -d 'bundle' } ok $sqitch = App::Sqitch->new( - top_dir => dir 'sql', + options => { top_dir => dir('sql')->stringify }, ), 'Load a sqitch object with top_dir'; $config = $sqitch->config; my $dir = dir qw(_build sql); @@ -102,7 +102,7 @@ for my $sub (qw(deploy revert verify)) { # Try engine project. ok $sqitch = App::Sqitch->new( - top_dir => dir 'engine', + options => { top_dir => dir('engine')->stringify }, ), 'Load a sqitch object with engine top_dir'; isa_ok $bundle = App::Sqitch::Command->load({ sqitch => $sqitch, @@ -255,7 +255,7 @@ isa_ok $bundle = App::Sqitch::Command->load({ }), $CLASS, '--from bundle command'; is $bundle->from, 'widgets', 'From should be "widgets"'; ok $bundle->bundle_plan, 'Bundle the plan file with --from'; -my $plan = $sqitch->plan; +my $plan = $bundle->default_target->plan; is_deeply +MockOutput->get_info, [[__x( 'Writing plan from {from} to {to}', from => 'widgets', @@ -303,8 +303,10 @@ my @files = ( ); file_not_exists_ok $_ for @files; ok $sqitch = App::Sqitch->new( - extension => 'sql', - top_dir => dir 'engine', + options => { + extension => 'sql', + top_dir => dir('engine')->stringify, + }, ), 'Load engine sqitch object'; isa_ok $bundle = App::Sqitch::Command->load({ sqitch => $sqitch, From 2012df4137bd5944f572e6c672c736de14512015 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 17:08:16 -0700 Subject: [PATCH 51/59] Eliminate warnings in tests. --- t/mysql.t | 25 +++++++------------------ t/oracle.t | 16 ++-------------- t/pg.t | 23 ++++++----------------- t/target.t | 2 +- t/vertica.t | 19 ++----------------- 5 files changed, 18 insertions(+), 67 deletions(-) diff --git a/t/mysql.t b/t/mysql.t index 900b1a373..968d6d8cb 100755 --- a/t/mysql.t +++ b/t/mysql.t @@ -116,37 +116,26 @@ is_deeply [$mysql->mysql], [qw( # Now make sure that Sqitch options override configurations. $sqitch = App::Sqitch->new( options => { - engine => 'mysql', - client => '/some/other/mysql', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, -}); + engine => 'mysql', + client => '/some/other/mysql', + }, +); $target = App::Sqitch::Target->new(sqitch => $sqitch); ok $mysql = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a mysql with sqitch with options'; is $mysql->client, '/some/other/mysql', 'client should be as optioned'; -is $mysql->uri->as_string, 'db:mysql://anna@foo.com:98760/widgets_dev', - 'The DB URI should be as optioned'; -is $mysql->target->name, $mysql->uri->as_string, - 'target name should be the URI stringified'; -like $mysql->destination, qr{^db:mysql://anna:?\@foo\.com:98760/widgets_dev$}, - 'destination should be the URI minus the password'; is $mysql->registry, 'meta', 'registry should be as configured'; -is $mysql->registry_uri->as_string, 'db:mysql://anna@foo.com:98760/meta', +is $mysql->registry_uri->as_string, 'db:mysql://foo.com/meta', 'Sqitch DB URI should be the same as uri but with DB name "meta"'; -like $mysql->registry_destination, qr{^db:mysql://anna:?\@foo\.com:98760/meta$}, +is $mysql->registry_destination, 'db:mysql://foo.com/meta', 'registry_destination should be the sqitch DB URL sans password'; is $mysql->registry, 'meta', 'registry should still be as configured'; is_deeply [$mysql->mysql], [qw( /some/other/mysql - --user anna - --database widgets_dev + --database widgets --host foo.com - --port 98760 ), @std_opts], 'mysql command should be as optioned'; ############################################################################## diff --git a/t/oracle.t b/t/oracle.t index 263b7ad76..13c57fd5c 100644 --- a/t/oracle.t +++ b/t/oracle.t @@ -257,12 +257,8 @@ is_deeply [$ora->sqlplus], ['/path/to/sqlplus', @std_opts], # Now make sure that Sqitch options override configurations. $sqitch = App::Sqitch->new( options => { - engine => 'oracle', - client => '/some/other/sqlplus', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, + engine => 'oracle', + client => '/some/other/sqlplus', }, ); @@ -271,14 +267,6 @@ ok $ora = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a ora with sqitch with options'; is $ora->client, '/some/other/sqlplus', 'client should be as optioned'; -is $ora->uri->as_string, 'db:oracle://anna@foo.com:98760/widgets_dev', - 'DB URI should have attributes overridden by options'; -is $ora->target->name, $ora->uri->as_string, - 'Target name should be the URI stringified'; -like $ora->destination, qr{^db:oracle://anna:?\@foo\.com:98760/widgets_dev$}, - 'Destination should be the URI without the password'; -is $ora->registry_destination, $ora->destination, - 'registry_destination should still be the same URI'; is $ora->registry, 'meta', 'registry should still be as configured'; is_deeply [$ora->sqlplus], ['/some/other/sqlplus', @std_opts], 'sqlplus command should be as optioned'; diff --git a/t/pg.t b/t/pg.t index 886eb4e6f..23cf174dd 100644 --- a/t/pg.t +++ b/t/pg.t @@ -131,34 +131,23 @@ is_deeply [$pg->psql], [qw( # Now make sure that (deprecated?) Sqitch options override configurations. $sqitch = App::Sqitch->new( options => { - engine => 'pg', - client => '/some/other/psql', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, -}); + engine => 'pg', + client => '/some/other/psql', + } +); $target = App::Sqitch::Target->new( sqitch => $sqitch ); ok $pg = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a pg with sqitch with options'; is $pg->client, '/some/other/psql', 'client should be as optioned'; -is $pg->uri->as_string, 'db:pg://anna@foo.com:98760/widgets_dev', - 'uri should be as configured'; -is $pg->target->name, $pg->uri->as_string, - 'target name should be the URI stringified'; -like $pg->destination, qr{^db:pg://anna:?\@foo\.com:98760/widgets_dev$}, - 'destination should be the URI without the password'; is $pg->registry_destination, $pg->destination, 'registry_destination should be the same as destination'; is $pg->registry, 'meta', 'registry should still be as configured'; is_deeply [$pg->psql], [qw( /some/other/psql - --username anna - --dbname widgets_dev - --host foo.com - --port 98760 + --dbname try + --host localhost ), @std_opts], 'psql command should be as optioned'; ############################################################################## diff --git a/t/target.t b/t/target.t index 25e6d405e..57e09b843 100644 --- a/t/target.t +++ b/t/target.t @@ -146,7 +146,7 @@ CONSTRUCTOR: { # Try with engine-less URI. @get_params = (); - isa_ok my $target = $CLASS->new( + isa_ok $target = $CLASS->new( sqitch => $sqitch, uri => URI::db->new('db:'), ), $CLASS, 'Engineless target'; diff --git a/t/vertica.t b/t/vertica.t index 0ea0dcf47..7d33f7ad2 100644 --- a/t/vertica.t +++ b/t/vertica.t @@ -147,10 +147,6 @@ $sqitch = App::Sqitch->new( options => { engine => 'vertica', client => '/some/other/vsql', - db_username => 'anna', - db_name => 'widgets_dev', - db_host => 'foo.com', - db_port => 98760, }, ); @@ -159,21 +155,10 @@ ok $vta = $CLASS->new(sqitch => $sqitch, target => $target), 'Create a vertica with sqitch with options'; is $vta->client, '/some/other/vsql', 'client should be as optioned'; -is $vta->uri->as_string, 'db:vertica://anna@foo.com:98760/widgets_dev', - 'uri should be as configured'; -is $vta->target->name, $vta->uri->as_string, - 'Target name should be the URI stringified'; -like $vta->destination, qr{^db:vertica://anna:?\@foo\.com:98760/widgets_dev$}, - 'destination should be the URI without the password'; -is $vta->registry_destination, $vta->destination, - 'registry_destination should be the same as destination'; -is $vta->registry, 'meta', 'registry should still be as configured'; is_deeply [$vta->vsql], [qw( /some/other/vsql - --username anna - --dbname widgets_dev - --host foo.com - --port 98760 + --dbname try + --host localhost ), @std_opts], 'vsql command should be as optioned'; ############################################################################## From 51344f6a682f37c9fae27b66eb1381328e067896 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 17:09:26 -0700 Subject: [PATCH 52/59] Restore test plans. --- t/command.t | 4 ++-- t/init.t | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/t/command.t b/t/command.t index 603e7552b..0ed9ecc74 100755 --- a/t/command.t +++ b/t/command.t @@ -4,8 +4,8 @@ use strict; use warnings; use 5.010; use utf8; -#use Test::More tests => 135; -use Test::More 'no_plan'; +use Test::More tests => 143; +#use Test::More 'no_plan'; $ENV{SQITCH_CONFIG} = 'nonexistent.conf'; $ENV{SQITCH_USER_CONFIG} = 'nonexistent.user'; diff --git a/t/init.t b/t/init.t index 3ecd5421f..ef0a992e0 100644 --- a/t/init.t +++ b/t/init.t @@ -4,8 +4,8 @@ use strict; use warnings; use 5.010; use utf8; -#use Test::More tests => 157; -use Test::More 'no_plan'; +use Test::More tests => 159; +#use Test::More 'no_plan'; use App::Sqitch; use Locale::TextDomain qw(App-Sqitch); use Path::Class; From a07bb2e215703b4c1db5531a835bbf4452c1db71 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 17:11:35 -0700 Subject: [PATCH 53/59] Fix typos. --- lib/App/Sqitch/Command.pm | 4 ++-- xt/release/pod-spelling.t | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/App/Sqitch/Command.pm b/lib/App/Sqitch/Command.pm index 2acd7cbde..1528a624c 100644 --- a/lib/App/Sqitch/Command.pm +++ b/lib/App/Sqitch/Command.pm @@ -379,7 +379,7 @@ C<--engine> option or C configuration option has been set, then the target will support that engine. In the latter case, if C is set, that value will be used. Otherwise, the returned target will have a URI of C and no associated engine; the -C method will throw an exception. This behavior sould be fine for +C method will throw an exception. This behavior should be fine for commands that don't need to load the engine. =head3 C @@ -402,7 +402,7 @@ returned under the "target" key and any subsequent changes must be recognized from I plan. If no target is passed or appears in the arguments, a default target will be -intantiated based on the comnad-line options and configuration. Unlike the +instantiated based on the command-line options and configuration. Unlike the target returned by C, however, it B have an associated engine specified by the C<--engine> option or configuration. This is on the assumption that it will be used by commands that require an engine to do their diff --git a/xt/release/pod-spelling.t b/xt/release/pod-spelling.t index 0d683b342..16ca0a56f 100644 --- a/xt/release/pod-spelling.t +++ b/xt/release/pod-spelling.t @@ -93,3 +93,4 @@ Vertica DBA VM ODBC + From 99c8c2841e047a78a4eb7313dbe55cbbc65e8291 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 17:18:15 -0700 Subject: [PATCH 54/59] Add missing Pod. --- lib/App/Sqitch/Command/help.pm | 6 ++++++ lib/App/Sqitch/Command/status.pm | 7 ++++++- lib/App/Sqitch/Plan.pm | 14 +++++++++++++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/App/Sqitch/Command/help.pm b/lib/App/Sqitch/Command/help.pm index 9c8fb8f5f..c4a44d0ba 100644 --- a/lib/App/Sqitch/Command/help.pm +++ b/lib/App/Sqitch/Command/help.pm @@ -76,6 +76,12 @@ works, read on. =head1 Interface +=head2 Attributes + +=head3 C + +Boolean indicating whether to list the guides. + =head2 Instance Methods =head3 C diff --git a/lib/App/Sqitch/Command/status.pm b/lib/App/Sqitch/Command/status.pm index 1c6eca0db..186592031 100644 --- a/lib/App/Sqitch/Command/status.pm +++ b/lib/App/Sqitch/Command/status.pm @@ -332,9 +332,14 @@ works, read on. =head2 Attributes +=head3 C + +The name or URI of the database target as specified by the C<--target> option. + =head3 C -URI of the database target from which to read the status. +An L object from which to read the status. Must be +instantiated by C. =head2 Instance Methods diff --git a/lib/App/Sqitch/Plan.pm b/lib/App/Sqitch/Plan.pm index ab76e1917..c0200561e 100644 --- a/lib/App/Sqitch/Plan.pm +++ b/lib/App/Sqitch/Plan.pm @@ -1036,10 +1036,22 @@ an L object. =head3 C - my $sqitch = $cmd->sqitch; + my $sqitch = $plan->sqitch; Returns the L object that instantiated the plan. +=head3 C + + my $target = $plan->target + +Returns the L passed to the constructor. + +=head3 C + + my $file = $plan->file; + +The file name from which to read the plan. + =head3 C Returns the current position of the iterator. This is an integer that's used From befc141c64b3f806b1e1805190e5a8ab52a24517 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Thu, 23 Oct 2014 17:31:56 -0700 Subject: [PATCH 55/59] Stub out App::Sqitch::Target docs. --- lib/App/Sqitch/Target.pm | 148 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 8bc7e2a49..3778deee7 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -311,4 +311,152 @@ sub BUILDARGS { } 1; + __END__ + +=head1 Name + +App::Sqitch::Target - Sqitch deployment target + +=head1 Synopsis + + my $plan = App::Sqitch::Target->new( + sqitch => $sqitch, + name => 'development', + ); + $target->engine->deploy; + +=head1 Description + +App::Sqitch::Target provides collects, in one place, the +L, L, and file locations +required to carry out Sqitch commands. All commands should instantiate a +target to work with the plan or database. + +=head1 Interface + +=head3 C + + my $target = App::Sqitch::Target->new( sqitch => $sqitch ); + +Instantiates and returns an App::Sqitch::Target object. The most important +parameters are C, C and C. The constructor tries really hard +to figure out the proper name and URI during construction by taking the +following steps: + +XXX + +=head2 Accessors + +=head3 C + + my $sqitch = $target->sqitch; + +Returns the L object that instantiated the target. + +=head3 C + +=head3 C + + my $name = $target->name; + $name = $target->target; + + + +=head3 C + + my $uri = $target->uri; + + + +=head3 C + + my $engine = $target->engine; + + + +=head3 C + + my $registry = $target->registry; + + + +=head3 C + + my $client = $target->client; + + + +=head3 C + + my $plan_file = $target->plan_file; + + + +=head3 C + + my $top_dir = $target->top_dir; + + + +=head3 C + + my $deploy_dir = $target->deploy_dir; + + + +=head3 C + + my $revert_dir = $target->revert_dir; + + + +=head3 C + + my $verify_dir = $target->verify_dir; + + + +=head3 C + + my $extension = $target->extension; + + + +=head1 See Also + +=over + +=item L + +The Sqitch command-line client. + +=back + +=head1 Author + +David E. Wheeler + +=head1 License + +Copyright (c) 2012-2014 iovation Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +=cut From 58bc473e465368256797ea542b88b5526a4273e3 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 24 Oct 2014 10:50:45 -0700 Subject: [PATCH 56/59] Handle additional target cases. If name is found by finding the engine key, look to see if a target is specified for that key before falling back on `db:$key:`. Then check to see if the name thus found is a URI or a name. --- lib/App/Sqitch/Target.pm | 39 +++++++++++++++++++++++---------------- t/target.t | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 3778deee7..c5bde9a50 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -202,24 +202,27 @@ sub BUILDARGS { return $p; } - # If no name, try to find the default. - my $uri; - my $ekey = $sqitch->options->{engine} || $sqitch->config->get( - key => 'core.engine' - ) or hurl target => __( - 'No engine specified; use --engine or set core.engine' - ); - - my $name = $p->{name} ||= $sqitch->config->get( - key => "core.$ekey.target" - ); + my ($uri, $ekey); + my $name = $p->{name}; - # If no URI, we have to find one. + # If no name, try to find one. if (!$name) { - # Fall back on the default. - $uri = "db:$ekey:"; - } elsif ($name =~ /:/) { + # Look for an engine key. + $ekey = $sqitch->options->{engine} || $sqitch->config->get( + key => 'core.engine' + ) or hurl target => __( + 'No engine specified; use --engine or set core.engine' + ); + + # Find the name in the engine config, or fall back on a simple URI. + $uri = $sqitch->config->get(key => "core.$ekey.target") || "db:$ekey:"; + $p->{name} = $name = $uri; + } + + # Now we should have a name. What is it? + if ($name =~ /:/) { # The name is a URI. + require URI::db; $uri = URI::db->new($name); $name = $p->{name} = undef; } else { @@ -241,7 +244,11 @@ sub BUILDARGS { # Instantiate the URI. require URI::db; - $uri = $p->{uri} = URI::db->new( $uri ); + $uri = $p->{uri} = URI::db->new( $uri ); + $ekey ||= $uri->canonical_engine or hurl target => __x( + 'No engine specified by URI {uri}; URI must start with "db:$engine:"', + uri => $uri->as_string, + ); # Override parts with deprecated command-line options and config. my $opts = $sqitch->options; diff --git a/t/target.t b/t/target.t index 57e09b843..6dc2a552a 100644 --- a/t/target.t +++ b/t/target.t @@ -136,6 +136,26 @@ CONSTRUCTOR: { is_deeply \@get_params, [[key => 'core.engine'], [key => 'core.mysql.target']], 'Should have tried to get core engine and its target'; + # Try with no engine option but a name that looks like a URI. + @get_params = (); + delete $sqitch->options->{engine}; + isa_ok $target = $CLASS->new( + sqitch => $sqitch, + name => 'db:pg:', + ), $CLASS, 'Target with URI in name'; + is $target->name, 'db:pg:', 'Name should be "db:pg:"'; + is $target->uri, URI::db->new('db:pg:'), 'URI should be "db:pg"'; + is_deeply \@get_params, [], 'Should have fetched no config'; + + # Try it with a name with no engine. + throws_ok { $CLASS->new(sqitch => $sqitch, name => 'db:') } 'App::Sqitch::X', + 'Should have error for no engine in URI'; + is $@->ident, 'target', 'Should have target ident'; + is $@->message, __x( + 'No engine specified by URI {uri}; URI must start with "db:$engine:"', + uri => 'db:', + ), 'Should have message about no engine-less URI'; + # Try it with no configured core engine or target. throws_ok { $CLASS->new(sqitch => $sqitch) } 'App::Sqitch::X', 'Should have error for no engine or target'; @@ -224,8 +244,20 @@ CONSTRUCTOR: { is $target->uri, URI::db->new('db:pg:foo'), 'URI should be "db:pg:foo"'; is_deeply \@get_params, [[key => 'target.foo.uri']], 'Should have requested target URI from config'; + is_deeply \@sect_params, [ [section => 'core.pg' ]], + 'Should have requested pg section'; + + # Let the name be looked up by the engine. + @get_params = @sect_params = (); + @get_ret = ('foo', 'db:sqlite:foo'); + isa_ok $target = $CLASS->new(sqitch => $sqitch), $CLASS, + 'Engine named target'; + is $target->name, 'foo', 'Name should be "foo"'; + is $target->uri, URI::db->new('db:sqlite:foo'), 'URI should be "db:sqlite:foo"'; + is_deeply \@get_params, [[key => 'core.sqlite.target'], [key => 'target.foo.uri']], + 'Should have requested engine target and target URI from config'; is_deeply \@sect_params, [ [section => 'core.sqlite' ]], - 'Should have requested sqlite section'; + 'Should have requested pg section'; # Make sure deprecated config options work. @sect_ret = ({ From 12d0e7bf2fe8c91f4047f19a56705f1d13337228 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 24 Oct 2014 11:03:34 -0700 Subject: [PATCH 57/59] Fully document Target. --- lib/App/Sqitch/Target.pm | 219 ++++++++++++++++++++++++++++++++++---- xt/release/pod-spelling.t | 2 +- 2 files changed, 197 insertions(+), 24 deletions(-) diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index c5bde9a50..5ad6f1d56 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -171,18 +171,6 @@ has extension => ( }, ); -# If no name: -# a. use URI for name; or -# b. Look for core.$engine.target. -# If no URI: -# a. Use name if it exists and contains a colon; or -# b. If name exists: look for target.$name.uri or die; or -# c. Default to "db:$engine:" -# If still no name, use URI. - -# Need to move command-line options into a hash, remove accessors in App::Sqitch. -# Remove attributes here from App::Sqitch and Engine. - sub BUILDARGS { my $class = shift; my $p = @_ == 1 && ref $_[0] ? { %{ +shift } } : { @_ }; @@ -222,8 +210,7 @@ sub BUILDARGS { # Now we should have a name. What is it? if ($name =~ /:/) { # The name is a URI. - require URI::db; - $uri = URI::db->new($name); + $uri = $name; $name = $p->{name} = undef; } else { # Well then, there had better be a config with a URI. @@ -347,11 +334,43 @@ target to work with the plan or database. my $target = App::Sqitch::Target->new( sqitch => $sqitch ); Instantiates and returns an App::Sqitch::Target object. The most important -parameters are C, C and C. The constructor tries really hard -to figure out the proper name and URI during construction by taking the -following steps: +parameters are C, C and C. The constructor tries really +hard to figure out the proper name and URI during construction. If the C +parameter is passed, this is straight-forward: if no C is passed, +C will be set to the stringified format of the URI (minus the password, +if present). + +Otherwise, when no URI is passed, the name and URI are determined by taking +the following steps: + +=over + +=item * + +If there is no name, get the engine key from from C<--engine> or the +C configuration option. If no key can be determined, an exception +will be thrown. + +=item * -XXX +Use the key to look up the target name in the C +configuration option. If none is found, use C. + +=item * + +If the name contains a colon (C<:>), assume it is also the value for the URI. + +=item * + +Otherwise, it should be the name of a configured target, so look for a URI in +the C configuration option. + +=back + +As a general rule, then, pass either a target name or URI string in the +C parameter, and Sqitch will do its best to find all the relevant target +information. And if there is no name or URI, it will try to construct a +reasonable default from the command-line options or engine configuration. =head2 Accessors @@ -368,67 +387,221 @@ Returns the L object that instantiated the target. my $name = $target->name; $name = $target->target; - +The name of the target. If there was no name specified, the URI will be used +(minus the password, if there is one). =head3 C my $uri = $target->uri; - +The L object encapsulating the database connection information. =head3 C my $engine = $target->engine; - +A L object to use for database interactions with the +target. =head3 C my $registry = $target->registry; +The name of the registry used by the database. The value comes from one of +these options, searched in this order: + +=over + +=item * C<--registry> + +=item * C + +=item * C +=item * C + +=item * Engine-specific default + +=back =head3 C my $client = $target->client; +Path to the engine command-line client. The value comes from one of these +options, searched in this order: +=over -=head3 C +=item * C<--client> - my $plan_file = $target->plan_file; +=item * C +=item * C +=item * C + +=item * Engine-and-OS-specific default + +=back =head3 C my $top_dir = $target->top_dir; +The path to the top directory of the project. This directory generally +contains the plan file and subdirectories for deploy, revert, and verify +scripts. The value comes from one of these options, searched in this order: + +=over + +=item * C<--top-dir> + +=item * C + +=item * C + +=item * C + +=item * F<.> + +=back + +=head3 C + + my $plan_file = $target->plan_file; + +The path to the plan file. The value comes from one of these options, searched +in this order: + +=over + +=item * C<--plan-file> + +=item * C +=item * C + +=item * C + +=item * F/sqitch.plan> + +=back =head3 C my $deploy_dir = $target->deploy_dir; +The path to the deploy directory of the project. This directory contains all +of the deploy scripts referenced by changes in the C. The value +comes from one of these options, searched in this order: + +=over + +=item * C<--deploy-dir> + +=item * C + +=item * C +=item * C + +=item * F> + +=back =head3 C my $revert_dir = $target->revert_dir; +The path to the revert directory of the project. This directory contains all +of the revert scripts referenced by changes the C. The value comes +from one of these options, searched in this order: +=over + +=item * C<--revert-dir> + +=item * C + +=item * C + +=item * C + +=item * F> + +=back =head3 C my $verify_dir = $target->verify_dir; +The path to the verify directory of the project. This directory contains all +of the verify scripts referenced by changes in the C. The value +comes from one of these options, searched in this order: + +=over + +=item * C<--verify-dir> + +=item * C +=item * C + +=item * C + +=item * F> + +=back =head3 C my $extension = $target->extension; +The file name extension to append to change names to create script file names. + The value comes from one of these options, searched in this order: + +=over + +=item * C<--extension> + +=item * C + +=item * C + +=item * C + +=item * C<"sql"> + +=back + +=head3 C + + my $key = $target->engine_key; + +The key defining which engine to use. This value defines the class loaded by +C. Convenience method for C<< $target->uri->canonical_engine >>. + +=head3 C + + my $dsn = $target->dsn; + +The DSN to use when connecting to the target via the DBI. Convenience method +for C<< $target->uri->dbi_dsn >>. + +=head3 C + + my $username = $target->username; + +The username to use when connecting to the target via the DBI. Convenience +method for C<< $target->uri->user >>. + +=head3 C + + my $password = $target->password; +The password to use when connecting to the target via the DBI. Convenience +method for C<< $target->uri->password >>. =head1 See Also diff --git a/xt/release/pod-spelling.t b/xt/release/pod-spelling.t index 16ca0a56f..81abe4f39 100644 --- a/xt/release/pod-spelling.t +++ b/xt/release/pod-spelling.t @@ -93,4 +93,4 @@ Vertica DBA VM ODBC - +DSN From 81e10813bb6149189c32bab3e3dd6a2b8fe7bdf4 Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 24 Oct 2014 14:21:44 -0700 Subject: [PATCH 58/59] Add support for other config properties to the target command. Includes new `add-` options for each. Properties to set on `add` are now passed via `--set key=value` options. The `--registry` and `--client` options are deprecated. --- lib/App/Sqitch/Command/target.pm | 114 ++++++++++++++++++++++--------- lib/App/Sqitch/Target.pm | 2 +- lib/sqitch-target-usage.pod | 17 +++-- lib/sqitch-target.pod | 110 ++++++++++++++++++++++++----- t/target_cmd.t | 112 ++++++++++++++++++++++++------ 5 files changed, 276 insertions(+), 79 deletions(-) diff --git a/lib/App/Sqitch/Command/target.pm b/lib/App/Sqitch/Command/target.pm index 92d4c0e8b..b7bd6bc13 100644 --- a/lib/App/Sqitch/Command/target.pm +++ b/lib/App/Sqitch/Command/target.pm @@ -5,11 +5,12 @@ use strict; use warnings; use utf8; use Moo; -use Types::Standard qw(Str Int); +use Types::Standard qw(Str Int HashRef); use Locale::TextDomain qw(App-Sqitch); use App::Sqitch::X qw(hurl); use URI::db; use Try::Tiny; +use Path::Class qw(file dir); use List::Util qw(max); use namespace::autoclean; @@ -23,27 +24,48 @@ has verbose => ( default => 0, ); -has registry => ( - is => 'ro', - isa => Str, -); - -has client => ( - is => 'ro', - isa => Str, +has properties => ( + is => 'ro', + isa => HashRef, + default => sub { {} }, ); sub options { return qw( + set|s=s% registry|r=s client|c=s verbose|v+ ); } +my %normalizer_for = ( + top_dir => sub { $_[0] ? dir($_[0])->cleanup : undef }, + plan_file => sub { $_[0] ? file($_[0])->cleanup : undef }, + client => sub { $_[0] }, +); + +$normalizer_for{"$_\_dir"} = $normalizer_for{top_dir} for qw(deploy revert verify); +$normalizer_for{$_} = $normalizer_for{client} for qw(registry extension); + sub configure { my ( $class, $config, $options ) = @_; + # Handle deprecated options. + for my $key (qw(registry client)) { + my $val = delete $options->{$key} or next; + App::Sqitch->warn(__x( + 'Option --{key} has been deprecated; use "--set {key}={val}" instead', + key => $key, + val => $val + )); + my $set = $options->{set} ||= {}; + $set->{$key} = $val; + } + + $options->{properties} = delete $options->{set} + if $options->{set}; + # No config; target config is actually targets. return $options; } @@ -85,27 +107,22 @@ sub add { target => $name ) if $config->get( key => "$key.uri"); - # Set the URI. $config->set( key => "$key.uri", value => URI::db->new($uri, 'db:')->as_string, filename => $config->local_file, ); - # Set the registry, if specified. - if (my $reg = $self->registry) { + # Set properties. + my $props = $self->properties; + while (my ($prop, $val) = each %{ $props } ) { + my $normalizer = $normalizer_for{$prop} or $self->usage(__x( + 'Unknown property "{property}"', + property => $prop, + )); $config->set( - key => "$key.registry", - value => $reg, - filename => $config->local_file, - ); - } - - # Set the client, if specified. - if (my $reg = $self->client) { - $config->set( - key => "$key.client", - value => $reg, + key => "$key.$prop", + value => $normalizer->($val), filename => $config->local_file, ); } @@ -143,12 +160,23 @@ sub set_uri { ); } -sub set_registry { - shift->_set('registry', @_); +sub set_registry { shift->_set('registry', @_) } +sub set_client { shift->_set('client', @_) } +sub set_extension { shift->_set('extension', @_) } + +sub _set_dir { + my ($self, $key, $name, $dir) = @_; + $self->_set( $key, $name, $normalizer_for{top_dir}->($dir) ); } -sub set_client { - shift->_set('client', @_); +sub set_top_dir { shift->_set_dir('top_dir', @_) } +sub set_deploy_dir { shift->_set_dir('deploy_dir', @_) } +sub set_revert_dir { shift->_set_dir('revert_dir', @_) } +sub set_verify_dir { shift->_set_dir('verify_dir', @_) } + +sub set_plan_file { + my ($self, $name, $file) = @_; + $self->_set( 'plan_file', $name, $normalizer_for{plan_file}->($file) ); } sub rm { shift->remove(@_) } @@ -240,13 +268,9 @@ Manages Sqitch targets, which are stored in the local configuration file. =head2 Attributes -=head3 C - -Value to set the client attribute. +=head3 C -=head3 C - -Value to set the registry attribute. +Hash of property values to set. =head3 C @@ -290,6 +314,30 @@ Implements the C action. Implements the C action. +=head3 C + +Implements the C action. + +=head3 C + +Implements the C action. + +=head3 C + +Implements the C action. + +=head3 C + +Implements the C action. + +=head3 C + +Implements the C action. + +=head3 C + +Implements the C action. + =head3 C Implements the C action. diff --git a/lib/App/Sqitch/Target.pm b/lib/App/Sqitch/Target.pm index 5ad6f1d56..dcd0dca54 100644 --- a/lib/App/Sqitch/Target.pm +++ b/lib/App/Sqitch/Target.pm @@ -559,7 +559,7 @@ comes from one of these options, searched in this order: my $extension = $target->extension; The file name extension to append to change names to create script file names. - The value comes from one of these options, searched in this order: +The value comes from one of these options, searched in this order: =over diff --git a/lib/sqitch-target-usage.pod b/lib/sqitch-target-usage.pod index 30e5dc7de..ae76515e0 100644 --- a/lib/sqitch-target-usage.pod +++ b/lib/sqitch-target-usage.pod @@ -5,16 +5,21 @@ sqitch-target-usage - Sqitch target usage statement =head1 Usage sqitch target [-v | --verbose] - sqitch target add [-r ] [-c ] + sqitch target add [-s = ...] sqitch target set-uri - sqitch target set-registry - sqitch target set-client + sqitch target set-registry + sqitch target set-client + sqitch target set-top-dir + sqitch target set-plan-file + sqitch target set-deploy-dir + sqitch target set-revert-dir + sqitch target set-verify-dir + sqitch target set-extension sqitch target remove sqitch target rename sqitch target show =head1 Options - -v, --verbose be verbose; must be placed before an action - -r, --registry, --set-registry set the registry when adding a target - -r, --client, --set-client set the client when adding a target + -v, --verbose be verbose; must be placed before an action + -s, --set KEY=VALUE set target property when adding a target diff --git a/lib/sqitch-target.pod b/lib/sqitch-target.pod index dd23ef5c0..5725a6bef 100644 --- a/lib/sqitch-target.pod +++ b/lib/sqitch-target.pod @@ -5,18 +5,24 @@ sqitch-target - Mange set of target databases =head1 Synopsis sqitch target [-v | --verbose] - sqitch target add [-r ] [-c ] + sqitch target add [-s = ...] sqitch target set-uri - sqitch target set-registry - sqitch target set-client + sqitch target set-registry + sqitch target set-client + sqitch target set-top-dir + sqitch target set-plan-file + sqitch target set-deploy-dir + sqitch target set-revert-dir + sqitch target set-verify-dir + sqitch target set-extension sqitch target remove sqitch target rename sqitch target show =head1 Description -Manage the set of databases ("targets") you deploy to. Each target has three -attributes: +Manage the set of databases ("targets") you deploy to. Each target may have a +number of properties: =over @@ -52,10 +58,43 @@ The name of the registry schema or database. The default is C. The command-line client to use. If not specified, each engine looks in the OS Path for an appropriate client. +=item C + +The path to the top directory for the target. This directory generally +contains the plan file and subdirectories for deploy, revert, and verify +scripts. The default is F<.>, the current directory. + +=item C + +The plan file to use for this target. The default is C<$top_dir/sqitch.plan>. + +=item C + +The path to the deploy directory for the target. This directory contains all +of the deploy scripts referenced by changes in the C. The default +is C<$top_dir/deploy>. + +=item C + +The path to the revert directory for the target. This directory contains all +of the revert scripts referenced by changes in the C. The default +is C<$top_dir/revert>. + +=item C + +The path to the verify directory for the target. This directory contains all +of the verify scripts referenced by changes in the C. The default +is C<$top_dir/verify>. + +=item C + +The file name extension to append to change names to create script file names. +The default is C. + =back -Each of these overrides the corresponding engine-specific configuration -- -that is, the C, C, and +Each of these overrides the corresponding engine-specific configuration -- for +example, the C, C, C L options. =head1 Options @@ -68,21 +107,34 @@ C L options. Be a little more verbose and show remote URI after name. -=item C<-r> +=item C<-s> + +=item C<--set> + + sqitch target add foo uri=db:pg:try -s top_dir=db -s registry=meta + +Set a target property key/value pair. May be specified multiple times. Used +only by the C action. Supported keys are: + +=over + +=item C -=item C<--registry> +=item C -=item C<--set-registry> +=item C -Set the registry for the target. Used only by the C action. +=item C -=item C<-r> +=item C -=item C<--client> +=item C -=item C<--set-client> +=item C -Set the client for the target. Used only by the C action. +=item C + +=back =back @@ -94,9 +146,7 @@ available to perform operations on the targets. =head2 C Add a target named C<< >> for the database at C<< >>. The -C<--registry> option specifies the Sqitch registry schema or database to use. -The C<--client> option specifies the command-line client to use for deployment -scripts. +C<--set> option specifies target-specific properties. =head2 C @@ -110,6 +160,30 @@ Set the registry for target C<< >>. Set the client for target C<< >>. +=head2 C + +Set the top directory for target C<< >>. + +=head2 C + +Set the plan file for target C<< >>. + +=head2 C + +Set the deploy directory for target C<< >>. + +=head2 C + +Set the revert directory for target C<< >>. + +=head2 C + +Set the verify directory for target C<< >>. + +=head2 C + +Set the extension for target C<< >>. + =head2 C, C Remove the target named C<< >>. diff --git a/t/target_cmd.t b/t/target_cmd.t index d147637fb..8bf1066e9 100644 --- a/t/target_cmd.t +++ b/t/target_cmd.t @@ -3,8 +3,8 @@ use strict; use warnings; use utf8; -use Test::More tests => 142; -#use Test::More 'no_plan'; +#use Test::More tests => 142; +use Test::More 'no_plan'; use App::Sqitch; use Locale::TextDomain qw(App-Sqitch); use Test::Exception; @@ -61,15 +61,15 @@ can_ok $cmd, qw( ); is_deeply [$CLASS->options], [qw( + set|s=s% registry|r=s client|c=s verbose|v+ )], 'Options should be correct'; -# Check default attribute values. +# Check default property values. is $cmd->verbose, 0, 'Default verbosity should be 0'; -is $cmd->registry, undef, 'Default registry should be undef'; -is $cmd->client, undef, 'Default client should be undef'; +is_deeply $cmd->properties, {}, 'Default properties should be empty'; # Make sure configure ignores config file. is_deeply $CLASS->configure({ foo => 'bar'}, { hi => 'there' }), @@ -123,44 +123,114 @@ ok $cmd->add('test', 'db:pg:test'), 'Add target "test"'; $config->load; is $config->get(key => 'target.test.uri'), 'db:pg:test', 'Target "test" URI should have been set'; -is $config->get(key => 'target.test.registry'), undef, - 'Target "test" should have no registry set'; -is $config->get(key => 'target.test.client'), undef, - 'Target "test" should have no client set'; +for my $key (qw( + client + registry + top_dir + plan_file + deploy_dir + revert_dir + verify_dir + extension) +) { + is $config->get(key => "target.test.$key"), undef, + qq{Target "test" should have no $key set}; +} # Try adding a target with a registry. -isa_ok $cmd = $CLASS->new({ sqitch => $sqitch, registry => 'meta' }), - $CLASS, 'Target with registry'; +isa_ok $cmd = $CLASS->new({ + sqitch => $sqitch, + properties => { registry => 'meta' }, +}), $CLASS, 'Target with registry'; ok $cmd->add('withreg', 'db:pg:withreg'), 'Add target "withreg"'; $config->load; is $config->get(key => 'target.withreg.uri'), 'db:pg:withreg', 'Target "withreg" URI should have been set'; is $config->get(key => 'target.withreg.registry'), 'meta', 'Target "withreg" registry should have been set'; +for my $key (qw( + client + top_dir + plan_file + deploy_dir + revert_dir + verify_dir + extension) +) { + is $config->get(key => "target.withreg.$key"), undef, + qq{Target "test" should have no $key set}; +} # Try a client. -isa_ok $cmd = $CLASS->new({ sqitch => $sqitch, client => 'hi.exe' }), - $CLASS, 'Target with client'; +isa_ok $cmd = $CLASS->new({ + sqitch => $sqitch, + properties => { client => 'hi.exe' }, +}), $CLASS, 'Target with client'; ok $cmd->add('withcli', 'db:pg:withcli'), 'Add target "withcli"'; $config->load; is $config->get(key => 'target.withcli.uri'), 'db:pg:withcli', 'Target "withcli" URI should have been set'; -is $config->get(key => 'target.withcli.registry'), undef, - 'Target "withcli" registry should not have been set'; is $config->get(key => 'target.withcli.client'), 'hi.exe', 'Target "withcli" should have client set'; +for my $key (qw( + registry + top_dir + plan_file + deploy_dir + revert_dir + verify_dir + extension) +) { + is $config->get(key => "target.withcli.$key"), undef, + qq{Target "withcli" should have no $key set}; +} # Try both. -isa_ok $cmd = $CLASS->new({ sqitch => $sqitch, client => 'ack', registry => 'foo' }), - $CLASS, 'Target with client and registry'; +isa_ok $cmd = $CLASS->new({ + sqitch => $sqitch, + properties => { client => 'ack', registry => 'foo' }, +}), $CLASS, 'Target with client and registry'; ok $cmd->add('withboth', 'db:pg:withboth'), 'Add target "withboth"'; $config->load; is $config->get(key => 'target.withboth.uri'), 'db:pg:withboth', 'Target "withboth" URI should have been set'; is $config->get(key => 'target.withboth.registry'), 'foo', - 'Target "withboth" registry should not been set'; + 'Target "withboth" registry should have been set'; is $config->get(key => 'target.withboth.client'), 'ack', 'Target "withboth" should have client set'; +for my $key (qw( + top_dir + plan_file + deploy_dir + revert_dir + verify_dir + extension) +) { + is $config->get(key => "target.withboth.$key"), undef, + qq{Target "withboth" should have no $key set}; +} + +# Try all the properties. +my %props = ( + client => 'poo', + registry => 'reg', + top_dir => 'top', + plan_file => 'my.plan', + deploy_dir => 'dep', + revert_dir => 'rev', + verify_dir => 'ver', + extension => 'ddl', +); +isa_ok $cmd = $CLASS->new({ + sqitch => $sqitch, + properties => { %props }, +}), $CLASS, 'Target with all properties'; +ok $cmd->add('withall', 'db:pg:withall'), 'Add target "withall"'; +$config->load; +while (my ($k, $v) = each %props) { + is $config->get(key => "target.withall.$k"), $v, + qq{Target "withall" should have $k set}; +} ############################################################################## # Test set_uri(). @@ -201,8 +271,8 @@ is $config->get(key => 'target.withboth.uri'), 'db:postgres:stuff', 'Target "withboth" should have new DB URI'; ############################################################################## -# Test set_registry() and set_client. -for my $key (qw(registry client)) { +# Test other set_* methods +for my $key (keys %props) { my $meth = "set_$key"; MISSINGARGS: { # Test handling of no name. @@ -300,7 +370,7 @@ is $config->get(key => "target.àlafois.uri"), undef, # Test show. ok $cmd->show, 'Run show()'; is_deeply +MockOutput->get_emit, [ - ['dev'], ['prod'], ['qa'], ['test'], ['withcli'], ['withreg'] + ['dev'], ['prod'], ['qa'], ['test'], ['withall'], ['withcli'], ['withreg'] ], 'Show with no names should emit the list of targets'; # Try one target. From d9407bfc43d7ed4c3e703665a6368cf4b80635bb Mon Sep 17 00:00:00 2001 From: "David E. Wheeler" Date: Fri, 24 Oct 2014 15:12:11 -0700 Subject: [PATCH 59/59] Add missing tutorial line. --- lib/sqitchtutorial.pod | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/sqitchtutorial.pod b/lib/sqitchtutorial.pod index 80351ad5e..6c3b4e16d 100644 --- a/lib/sqitchtutorial.pod +++ b/lib/sqitchtutorial.pod @@ -916,6 +916,7 @@ Note the use of C<--show tags> to show all the deployed tags. Now make it so: > git add . [flips e8f4655] Add flips table. + > git commit -am 'Add flips table.' 4 files changed, 37 insertions(+) create mode 100644 deploy/flips.sql create mode 100644 revert/flips.sql