App-Sqitch
view release on metacpan or search on metacpan
#!/usr/bin/perl -w
use strict;
use warnings;
use utf8;
use Test::More tests => 256;
# use Test::More 'no_plan';
use App::Sqitch;
use Locale::TextDomain qw(App-Sqitch);
use Test::Exception;
use Test::Warn;
use App::Sqitch::Command::add;
use Path::Class;
use Test::File qw(file_not_exists_ok file_exists_ok);
use Test::File::Contents qw(file_contents_identical file_contents_is files_eq);
use File::Path qw(make_path remove_tree);
use Test::NoWarnings;
use lib 't/lib';
use MockOutput;
use TestConfig;
my $CLASS = 'App::Sqitch::Command::rework';
my $test_dir = dir 'test-rework';
my $config = TestConfig->new(
'core.engine' => 'sqlite',
'core.top_dir' => $test_dir->stringify,
);
ok my $sqitch = App::Sqitch->new(config => $config), 'Load a sqitch object';
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 => $rework->default_target->plan,
);
$dep->project;
return $dep;
}
can_ok $CLASS, qw(
change_name
requires
conflicts
note
execute
does
);
ok $CLASS->does("App::Sqitch::Role::ContextCommand"),
"$CLASS does ContextCommand";
is_deeply [$CLASS->options], [qw(
change-name|change|c=s
requires|r=s@
conflicts|x=s@
all|a!
note|n|m=s@
open-editor|edit|e!
plan-file|f=s
top-dir=s
)], 'Options should be set up';
warning_is {
Getopt::Long::Configure(qw(bundling pass_through));
ok Getopt::Long::GetOptionsFromArray(
[], {}, App::Sqitch->_core_opts, $CLASS->options,
'Grabs nothing from config';
ok my $sqitch = App::Sqitch->new(config => $config), 'Load Sqitch project';
isa_ok my $rework = App::Sqitch::Command->load({
sqitch => $sqitch,
command => 'rework',
config => $config,
}), $CLASS, 'rework command';
ok $rework->open_editor, 'Coerces rework.open_editor from config string boolean';
}
##############################################################################
# Test attributes.
is_deeply $rework->requires, [], 'Requires should be an arrayref';
is_deeply $rework->conflicts, [], 'Conflicts should be an arrayref';
is_deeply $rework->note, [], 'Note should be an arrayref';
##############################################################################
# Test execute().
make_path $test_dir->stringify;
END { remove_tree $test_dir->stringify if -e $test_dir->stringify };
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 = $target->plan;
throws_ok { $rework->execute('foo') } 'App::Sqitch::X',
'Should get an example for nonexistent change';
is $@->ident, 'plan', 'Nonexistent change error ident should be "plan"';
is $@->message, __x(
qq{Change "{change}" does not exist in {file}.\n}
. 'Use "sqitch add {change}" to add it to the plan',
change => 'foo',
file => $plan->file,
), 'Fail message should say the step does not exist';
# Use the add command to create a step.
my $deploy_file = file qw(test-rework deploy foo.sql);
my $revert_file = file qw(test-rework revert foo.sql);
my $verify_file = file qw(test-rework verify foo.sql);
my $change_mocker = Test::MockModule->new('App::Sqitch::Plan::Change');
my %request_params;
$change_mocker->mock(request_note => sub {
my $self = shift;
%request_params = @_;
return $self->note;
});
# 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,
change_name => 'foo',
template_directory => Path::Class::dir(qw(etc templates))
), 'Create another add with template_directory';
file_not_exists_ok($_) for ($deploy_file, $revert_file, $verify_file);
ok $add->execute, 'Execute with the --change option';
file_exists_ok($_) for ($deploy_file, $revert_file, $verify_file);
ok my $foo = $plan->get('foo'), 'Get the "foo" change';
throws_ok { $rework->execute('foo') } 'App::Sqitch::X',
'Should get an example for duplicate change';
is $@->ident, 'plan', 'Duplicate change error ident should be "plan"';
is $@->message, __x(
qq{Cannot rework "{change}" without an intervening tag.\n}
. 'Use "sqitch tag" to create a tag and try again',
change => 'foo',
), 'Fail message should say a tag is needed';
# Tag it, and *then* it should work.
ok $plan->tag( name => '@alpha' ), 'Tag it';
my $deploy_file2 = file qw(test-rework deploy foo@alpha.sql);
my $revert_file2 = file qw(test-rework revert foo@alpha.sql);
my $verify_file2 = file qw(test-rework verify foo@alpha.sql);
MockOutput->get_info;
file_not_exists_ok($_) for ($deploy_file2, $revert_file2, $verify_file2);
ok $rework->execute('foo'), 'Rework "foo"';
# The files should have been copied.
file_exists_ok($_) for ($deploy_file, $revert_file, $verify_file);
file_exists_ok($_) for ($deploy_file2, $revert_file2, $verify_file2);
file_contents_identical($deploy_file2, $deploy_file);
file_contents_identical($verify_file2, $verify_file);
file_contents_identical($revert_file, $deploy_file);
file_contents_is($revert_file2, <<'EOF', 'New revert should revert');
-- Revert empty:foo from sqlite
BEGIN;
-- XXX Add DDLs here.
COMMIT;
EOF
# The note should have been required.
is_deeply \%request_params, {
for => __ 'rework',
scripts => [$deploy_file, $revert_file, $verify_file],
}, 'It should have prompted for a note';
# The plan file should have been updated.
ok $plan->load, 'Reload the plan file';
ok my @steps = $plan->changes, 'Get the steps';
is @steps, 2, 'Should have two steps';
is $steps[0]->name, 'foo', 'First step should be "foo"';
is $steps[1]->name, 'foo', 'Second step should also be "foo"';
is_deeply [$steps[1]->requires], [dep 'foo@alpha'],
'Reworked step should require the previous step';
is_deeply +MockOutput->get_info, [
[__x(
'Added "{change}" to {file}.',
change => 'foo [foo@alpha]',
file => $target->plan_file,
)],
[__n(
'Modify this file as appropriate:',
'Modify these files as appropriate:',
3,
)],
[" * $deploy_file"],
[" * $revert_file"],
[" * $verify_file"],
], 'And the info message should suggest editing the old files';
is_deeply +MockOutput->get_debug, [
[' ', __x 'Created {file}', file => dir qw(test-rework deploy) ],
[' ', __x 'Created {file}', file => dir qw(test-rework revert) ],
[' ', __x 'Created {file}', file => dir qw(test-rework verify) ],
[__x(
'Copied {src} to {dest}',
dest => $deploy_file2,
src => $deploy_file,
)],
[__x(
'Copied {src} to {dest}',
dest => $revert_file2,
src => $revert_file,
)],
[__x(
'Copied {src} to {dest}',
dest => $verify_file2,
src => $verify_file,
)],
[__x(
'Copied {src} to {dest}',
dest => $revert_file,
src => $deploy_file,
)],
], 'Debug should show file copying';
##############################################################################
# Let's do that again. This time with more dependencies and fewer files.
$deploy_file = file qw(test-rework deploy bar.sql);
$revert_file = file qw(test-rework revert bar.sql);
$verify_file = file qw(test-rework verify bar.sql);
ok $add = App::Sqitch::Command::add->new(
sqitch => $sqitch,
template_directory => Path::Class::dir(qw(etc templates)),
with_scripts => { revert => 0, verify => 0 },
), 'Create another add with template_directory';
file_not_exists_ok($_) for ($deploy_file, $revert_file, $verify_file);
$add->execute('bar');
file_exists_ok($deploy_file);
file_not_exists_ok($_) for ($revert_file, $verify_file);
ok $plan->tag( name => '@beta' ), 'Tag it with @beta';
my $deploy_file3 = file qw(test-rework deploy bar@beta.sql);
my $revert_file3 = file qw(test-rework revert bar@beta.sql);
my $verify_file3 = file qw(test-rework verify bar@beta.sql);
MockOutput->get_info;
isa_ok $rework = App::Sqitch::Command::rework->new(
sqitch => $sqitch,
command => 'rework',
config => $config,
requires => ['foo'],
note => [qw(hi there)],
conflicts => ['dr_evil'],
), $CLASS, 'rework command with requirements and conflicts';
# Check the files.
file_not_exists_ok($_) for ($deploy_file3, $revert_file3, $verify_file3);
ok $rework->execute('bar'), 'Rework "bar"';
file_exists_ok($deploy_file);
file_not_exists_ok($_) for ($revert_file, $verify_file);
file_exists_ok($deploy_file3);
file_not_exists_ok($_) for ($revert_file3, $verify_file3);
# The note should have been required.
is_deeply \%request_params, {
for => __ 'rework',
scripts => [$deploy_file],
}, 'It should have prompted for a note';
# The plan file should have been updated.
ok $plan->load, 'Reload the plan file again';
ok @steps = $plan->changes, 'Get the steps';
is @steps, 4, 'Should have four steps';
is $steps[0]->name, 'foo', 'First step should be "foo"';
is $steps[1]->name, 'foo', 'Second step should also be "foo"';
is $steps[2]->name, 'bar', 'First step should be "bar"';
is $steps[3]->name, 'bar', 'Second step should also be "bar"';
is_deeply [$steps[3]->requires], [dep 'bar@beta', dep 'foo'],
'Requires should have been passed to reworked change';
is_deeply [$steps[3]->conflicts], [dep '!dr_evil'],
'Conflicts should have been passed to reworked change';
is $steps[3]->note, "hi\n\nthere",
'Note should have been passed as comment';
is_deeply +MockOutput->get_info, [
[__x(
'Added "{change}" to {file}.',
change => 'bar [bar@beta foo !dr_evil]',
file => $target->plan_file,
)],
[__n(
'Modify this file as appropriate:',
'Modify these files as appropriate:',
1,
)],
[" * $deploy_file"],
], 'And the info message should show only the one file to modify';
is_deeply +MockOutput->get_debug, [
[__x(
'Copied {src} to {dest}',
dest => $deploy_file3,
src => $deploy_file,
)],
[__x(
'Skipped {dest}: {src} does not exist',
dest => $revert_file3,
src => $revert_file,
)],
[__x(
'Skipped {dest}: {src} does not exist',
dest => $verify_file3,
src => $verify_file,
)],
[__x(
'Skipped {dest}: {src} does not exist',
dest => $revert_file,
src => $revert_file3, # No previous revert, no need for new revert.
)],
], 'Should have debug oputput for missing files';
# Make sure --open-editor works
MOCKSHELL: {
my $sqitch_mocker = Test::MockModule->new('App::Sqitch');
my $shell_cmd;
$sqitch_mocker->mock(shell => sub { $shell_cmd = $_[1] });
$sqitch_mocker->mock(quote_shell => sub { shift; join ' ' => @_ });
ok $rework = $CLASS->new(
sqitch => $sqitch,
template_directory => Path::Class::dir(qw(etc templates)),
note => ['Testing --open-editor'],
open_editor => 1,
), 'Create another add with open_editor';
ok $plan->tag( name => '@gamma' ), 'Tag it';
my $rework_file = file qw(test-rework deploy bar.sql);
my $deploy_file = file qw(test-rework deploy bar@gamma.sql);
my $revert_file = file qw(test-rework revert bar@gamma.sql);
my $verify_file = file qw(test-rework verify bar@gamma.sql);
MockOutput->get_info;
file_not_exists_ok($_) for ($deploy_file, $revert_file, $verify_file);
ok $rework->execute('bar'), 'Rework "bar"';
# The files should have been copied.
file_exists_ok($_) for ($rework_file, $deploy_file);
file_not_exists_ok($_) for ($revert_file, $verify_file);
is $shell_cmd, join(' ', $sqitch->editor, $rework_file),
'It should have prompted to edit sql files';
is_deeply +MockOutput->get_info, [
[__x(
'Added "{change}" to {file}.',
change => 'bar [bar@gamma]',
file => $target->plan_file,
)],
[__n(
'Modify this file as appropriate:',
'Modify these files as appropriate:',
1,
)],
[" * $rework_file"],
], 'And the info message should suggest editing the old files';
MockOutput->get_debug; # empty debug.
};
# Make sure we properly handle a reworked directory.
$mock_plan->unmock('plan');
REWORKED_DIR: {
my $dstring = $test_dir->stringify;
remove_tree $dstring;
make_path $dstring;
END { remove_tree $dstring if -e $dstring };
chdir $dstring;
my $conf = file 'rework_dir.conf';
$conf->spew(join "\n",
'[core]',
'reworked_dir = _reworked',
'engine = sqlite',
);
file('sqitch.plan')->spew(join "\n",
'%project=rework', '',
'widgets 2012-07-16T17:25:07Z anna <a@n.na>',
'gadgets 2012-07-16T18:25:07Z anna <a@n.na>',
'@foo 2012-07-16T17:24:07Z julie <j@ul.ie>', '',
);
# Create the scripts.
my (@change, @reworked);
for my $type (qw(deploy revert verify)) {
my $dir = dir $type;
$dir->mkpath;
my $script = $dir->file('gadgets.sql');
$script->spew("-- $dir gadgets");
push @change => $script;
push @reworked => dir('_reworked', $type)->file('gadgets@foo.sql');
}
# We should have the change scripts but not yet reworked.
file_exists_ok $_ for @change;
file_not_exists_ok '_reworked';
file_not_exists_ok $_ for @reworked;
my $config = TestConfig->from(local => $conf);
my $sqitch = App::Sqitch->new(config => $config);
ok $rework = $CLASS->new(
sqitch => $sqitch,
note => ['Testing reworked_dir'],
template_directory => dir->parent->subdir(qw(etc templates))
), 'Create another rework with custom reworked_dir config';
# Let's do this thing!
ok $rework->execute('gadgets'), 'Rework change "gadgets"';
my $target = $rework->default_target;
ok my $head = $target->plan->get('gadgets@HEAD'),
"Get gadgets\@HEAD from the plan";
ok my $foo = $target->plan->get('gadgets@foo'),
"Get gadgets\@foo from the plan";
cmp_ok $head->id, 'ne', $foo->id,
"The two gadgets should be different changes";
# All the files should exist, now.
file_exists_ok '_reworked';
file_exists_ok $_ for @change, @reworked;
is_deeply \%request_params, {
for => __ 'rework',
scripts => \@change,
}, 'Should have listed scripts in the note prompt';
# Should have info output.
is_deeply +MockOutput->get_info, [
[__x(
'Added "{change}" to {file}.',
change => 'gadgets [gadgets@foo]',
file => $target->plan_file,
)],
[__n(
'Modify this file as appropriate:',
'Modify these files as appropriate:',
3,
)],
map { [" * $_"] } @change,
], 'And the info message should suggest editing the old files';
# use Data::Dump; ddx +MockOutput->get_debug;
is_deeply +MockOutput->get_debug, [
[' ', __x 'Created {file}', file => dir qw(_reworked deploy) ],
[__x(
'Copied {src} to {dest}',
src => $change[0],
dest => $reworked[0],
)],
[' ', __x 'Created {file}', file => dir qw(_reworked revert) ],
[__x(
'Copied {src} to {dest}',
src => $change[1],
dest => $reworked[1],
)],
[' ', __x 'Created {file}', file => dir qw(_reworked verify) ],
[__x(
'Copied {src} to {dest}',
src => $change[2],
( run in 0.606 second using v1.01-cache-2.11-cpan-e1769b4cff6 )