#!/usr/bin/env perl
# mt-aws-glacier - Amazon Glacier sync client
# Copyright (C) 2012-2013 Victor Efimov
# http://mt-aws.com (also http://vs-dev.com) vs@vs-dev.com
# License: GPLv3
#
# This file is part of "mt-aws-glacier"
#
# mt-aws-glacier is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# mt-aws-glacier is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
use strict;
use warnings;
use utf8;
use FindBin;
use lib map { "$FindBin::RealBin/$_" } qw{../lib ../../lib};
use Carp;
use List::Util qw/first/;
use Scalar::Util qw/looks_like_number/;
use Test::Spec;
use Test::More;
use Test::Deep;
use Data::Dumper;
use TestUtils;
use App::MtAws::Journal;
require App::MtAws::Command::Sync;
warning_fatal();
plan tests => 475;
describe "command" => sub {
my $j;
before each => sub {
$j = App::MtAws::Journal->new(journal_file => 'x', 'root_dir' => 'x' );
};
describe "modified processing" => sub {
my @all_detect = qw/treehash mtime mtime-and-treehash mtime-or-treehash always-positive size-only/; # TODO: fetch from ConfigDefinition
my @detect_with_mtime = grep /mtime/, @all_detect;
my @detect_without_mtime = grep !/mtime/, @all_detect;
describe "is_mtime_differs" => sub {
it "should work when mtime same" => sub {
App::MtAws::Command::Sync->expects("file_mtime")->returns(sub{ is shift, 'file1'; 123;})->once;
ok !App::MtAws::Command::Sync::is_mtime_differs({detect => 'mtime-and-treehash'},{mtime => 123}, 'file1');
};
it "should work when mtime greater than" => sub {
App::MtAws::Command::Sync->expects("file_mtime")->returns(sub{ is shift, 'file1'; 456;})->once;
ok App::MtAws::Command::Sync::is_mtime_differs({detect => 'mtime-and-treehash'},{mtime => 123}, 'file1');
};
it "should work when mtime less than" => sub {
App::MtAws::Command::Sync->expects("file_mtime")->returns(sub{ is shift, 'file1'; 42;})->once;
ok App::MtAws::Command::Sync::is_mtime_differs({detect => 'mtime-and-treehash'},{mtime => 123}, 'file1');
};
it "should work when mtime is undefined in journal" => sub {
App::MtAws::Command::Sync->expects("file_mtime")->never;
ok !App::MtAws::Command::Sync::is_mtime_differs({detect => 'mtime-and-treehash'},{mtime => undef}, 'file1');
};
it "should work when detect contains mtime" => sub {
for (@detect_with_mtime) {
App::MtAws::Command::Sync->expects("file_mtime")->returns(sub{ is shift, 'file1'; 42;})->once;
ok App::MtAws::Command::Sync::is_mtime_differs({detect => $_},{mtime => 123}, 'file1');
}
};
it "should work when detect does not contain mtime" => sub {
for (@detect_without_mtime) {
App::MtAws::Command::Sync->expects("file_mtime")->never;
ok ! defined App::MtAws::Command::Sync::is_mtime_differs({detect => $_},{mtime => 123}, 'file1');
}
};
};
describe "should_upload" => sub {
it "should define unique constants" => sub {
ok App::MtAws::Command::Sync::SHOULD_CREATE() != App::MtAws::Command::Sync::SHOULD_TREEHASH();
ok App::MtAws::Command::Sync::SHOULD_CREATE() != App::MtAws::Command::Sync::SHOULD_NOACTION();
ok App::MtAws::Command::Sync::SHOULD_CREATE();
ok App::MtAws::Command::Sync::SHOULD_TREEHASH();
ok !App::MtAws::Command::Sync::SHOULD_NOACTION(); # one should be FALSE
# numeric eq only
ok looks_like_number App::MtAws::Command::Sync::SHOULD_CREATE();
ok looks_like_number App::MtAws::Command::Sync::SHOULD_TREEHASH();
ok looks_like_number App::MtAws::Command::Sync::SHOULD_NOACTION();
};
it "should almost always return create if file size differs" => sub {
for (grep $_ ne 'always-positive', @all_detect) {
App::MtAws::Command::Sync->expects("is_mtime_differs")->never;
App::MtAws::Command::Sync->expects("file_size")->returns(42)->once;
is App::MtAws::Command::Sync::should_upload({detect => $_},{mtime => 123, size => 43}, 'file1'),
App::MtAws::Command::Sync::SHOULD_CREATE();
}
};
sub test_should_upload
{
my ($detect, $mtime_differs, $size_differs, $expected) = @_;
my $opts = {detect => $detect};
my $file = {mtime => 123, size => 42};
if (defined $mtime_differs) {
App::MtAws::Command::Sync->expects("is_mtime_differs")->returns(sub {
cmp_deeply [$opts, $file, 'file1'], [@_];
return $mtime_differs;
})->once
} else {
App::MtAws::Command::Sync->expects("is_mtime_differs")->never;
}
if (defined $size_differs) {
App::MtAws::Command::Sync->expects("file_size")->returns(sub {
cmp_deeply ['file1'], [@_];
return $size_differs ? 43 : 42;
})->once
} else {
App::MtAws::Command::Sync->expects("file_size")->never;
}
cmp_deeply App::MtAws::Command::Sync::should_upload($opts, $file, 'file1'), $expected;
}
describe "detect=mtime" => sub {
it "should return 'create' when mtime differs" => sub {
test_should_upload('mtime', 1, 0, App::MtAws::Command::Sync::SHOULD_CREATE());
};
it "should return FALSE when mtime same" => sub {
test_should_upload('mtime', 0, 0, App::MtAws::Command::Sync::SHOULD_NOACTION());
};
};
describe "detect=treehash" => sub {
it "should return 'treehash' mtime is irrelevant" => sub {
test_should_upload('treehash', undef, 0, App::MtAws::Command::Sync::SHOULD_TREEHASH());
};
};
describe "detect=mtime-and-treehash" => sub {
it "should return 'treehash' when mtime differs" => sub {
test_should_upload('mtime-and-treehash', 1, 0, App::MtAws::Command::Sync::SHOULD_TREEHASH());
};
it "should return FALSE when mtime same" => sub {
test_should_upload('mtime-and-treehash', 0, 0, App::MtAws::Command::Sync::SHOULD_NOACTION());
};
};
describe "detect=mtime-or-treehash" => sub {
it "should return 'create' when mtime differs" => sub {
test_should_upload('mtime-or-treehash', 1, 0, App::MtAws::Command::Sync::SHOULD_CREATE());
};
it "should return 'treehash' when mtime same" => sub {
test_should_upload('mtime-or-treehash', 0, 0, App::MtAws::Command::Sync::SHOULD_TREEHASH());
};
};
describe "detect=always-positive" => sub {
it "should return 'create' always" => sub {
test_should_upload('always-positive', undef, undef, App::MtAws::Command::Sync::SHOULD_CREATE());
};
};
describe "detect=size-only" => sub {
it "should return 'create' if size differs" => sub {
test_should_upload('size-only', undef, 1, App::MtAws::Command::Sync::SHOULD_CREATE());
};
it "should return 'no action' if size same" => sub {
test_should_upload('size-only', undef, 0, App::MtAws::Command::Sync::SHOULD_NOACTION());
};
};
describe "detect is unknown" => sub {
my $file = {mtime => 123, size => 42};
App::MtAws::Command::Sync->expects("file_size")->returns(42)->once;
ok ! defined eval { App::MtAws::Command::Sync::should_upload({detect => 'xyz'}, $file, 'file1'); 1; };
ok $@ =~ /Invalid detect option in should_upload/;
}
};
describe "next_modified" => sub {
my $options;
before each => sub {
$options = { partsize => 2};
};
sub expect_should_upload
{
my ($options, $j, $file, $toreturn) = @_;
App::MtAws::Command::Sync->expects("should_upload")->returns(sub {
my ($opt, $f, $absfilename) = @_;
cmp_deeply $opt, $options;
cmp_deeply $f, $file;
is $absfilename, $j->absfilename($file->{relfilename});
return $toreturn;
})->once;
}
sub verify_create_job
{
my ($options, $j, $file, $rec) = @_;
ok $rec->isa('App::MtAws::QueueJob::Upload');
is $rec->{partsize}, $options->{partsize}*1024*1024;
is $rec->{relfilename}, $file->{relfilename};
is $rec->{filename}, $j->absfilename($file->{relfilename});
ok $rec->{delete_after_upload};
is $rec->{archive_id}, $file->{archive_id};
}
sub verify_treehash_job
{
my ($options, $j, $file, $rec) = @_;
ok $rec->isa('App::MtAws::QueueJob::VerifyAndUpload');
is $rec->{filename}, $j->absfilename($file->{relfilename});
is $rec->{relfilename}, $file->{relfilename};
ok $rec->{delete_after_upload};
is $rec->{archive_id}, $file->{archive_id};
is $rec->{treehash}, $file->{treehash};
is $rec->{partsize}, $options->{partsize}*1024*1024;
}
it "should work with zero files" => sub {
$j->{listing}{existing} = [];
ok !defined App::MtAws::Command::Sync::next_modified($options, $j);
};
it "should work when should_upload returns SHOULD_CREATE" => sub {
my $file = {relfilename => 'file1', archive_id => 'zz1'};
$j->{listing}{existing} = [$file];
$j->_add_filename($file);
expect_should_upload($options, $j, $file, App::MtAws::Command::Sync::SHOULD_CREATE());
my $rec = App::MtAws::Command::Sync::next_modified($options, $j);
verify_create_job($options, $j, $file, $rec);
is scalar @{ $j->{listing}{existing} }, 0;
ok !defined (App::MtAws::Command::Sync::next_modified($options, $j));
};
it "should work with two files" => sub {
my $file1 = {relfilename => 'file1', archive_id => 'zz1'};
my $file2 = {relfilename => 'file2', archive_id => 'zz2'};
$j->{listing}{existing} = [$file1, $file2];
$j->_add_filename($file1);
$j->_add_filename($file2);
expect_should_upload($options, $j, $file1, App::MtAws::Command::Sync::SHOULD_CREATE());
my $rec = App::MtAws::Command::Sync::next_modified($options, $j);
verify_create_job($options, $j, $file1, $rec);
is scalar @{ $j->{listing}{existing} }, 1;
expect_should_upload($options, $j, $file2, App::MtAws::Command::Sync::SHOULD_CREATE());
$rec = App::MtAws::Command::Sync::next_modified($options, $j);
verify_create_job($options, $j, $file2, $rec);
};
it "should work with latest version of file" => sub {
my $file = {relfilename => 'file1', size => 123};
$j->{listing}{existing} = [$file];
$j->_add_filename({relfilename => 'file1', archive_id => 'zz1', size => 123, time => 42, mtime => 111, , treehash => 'abc0'});
$j->_add_filename(my $r = {relfilename => 'file1', archive_id => 'zz2', size => 123, time => 42, mtime => 113, treehash => 'abc'});
$j->_add_filename({relfilename => 'file1', archive_id => 'zz3', size => 123, time => 42, mtime => 112, , treehash => 'abc2'});
expect_should_upload($options, $j, $r, App::MtAws::Command::Sync::SHOULD_TREEHASH());
my $rec = App::MtAws::Command::Sync::next_modified($options, $j);
verify_treehash_job($options, $j, $r, $rec);
is scalar @{ $j->{listing}{existing} }, 0;
};
it "should call latest() to get latest version of file" => sub {
my $file = {relfilename => 'file1', size => 123};
$j->{listing}{existing} = [$file];
$j->_add_filename({relfilename => 'file1', archive_id => 'zz1', size => 123, time => 42, mtime => 111, , treehash => 'abc0'});
$j->_add_filename(my $r = {relfilename => 'file1', archive_id => 'zz2', size => 123, time => 42, mtime => 113, treehash => 'abc'});
$j->_add_filename({relfilename => 'file1', archive_id => 'zz3', size => 123, time => 42, mtime => 112, , treehash => 'abc2'});
expect_should_upload($options, $j, $r, App::MtAws::Command::Sync::SHOULD_TREEHASH());
App::MtAws::Journal->expects("latest")->returns(sub{ is $_[1], "file1"; $r})->once;
App::MtAws::Command::Sync::next_modified($options, $j);
};
it "should work when should_upload returns SHOULD_TREEHASH" => sub {
my $file = {relfilename => 'file1', archive_id => 'zz1', treehash => 'abcdef'};
$j->{listing}{existing} = [$file];
$j->_add_filename($file);
expect_should_upload($options, $j, $file, App::MtAws::Command::Sync::SHOULD_TREEHASH());
my $rec = App::MtAws::Command::Sync::next_modified($options, $j);
verify_treehash_job($options, $j, $file, $rec);
is scalar @{ $j->{listing}{existing} }, 0;
ok !defined (App::MtAws::Command::Sync::next_modified($options, $j));
};
it "should skip to next file when should_upload returns SHOULD_NOACTION" => sub {
for (1..10) {
my $file = {relfilename => "file$_", archive_id => "zz$_"};
push @{ $j->{listing}{existing} }, $file;
$j->_add_filename($file);
}
my $file;
App::MtAws::Command::Sync->expects("should_upload")->returns(sub {
my ($opt, $f, $absfilename) = @_;
$file = $f;
return $f->{relfilename} eq 'file7' ? App::MtAws::Command::Sync::SHOULD_CREATE() : App::MtAws::Command::Sync::SHOULD_NOACTION();
})->exactly(10);
my $rec = App::MtAws::Command::Sync::next_modified($options, $j);
verify_create_job($options, $j, $file, $rec);
is scalar @{ $j->{listing}{existing} }, 3;
ok !defined App::MtAws::Command::Sync::next_modified($options, $j);
};
it "should confess when should_upload returns something else" => sub {
my $file = {relfilename => 'file1', archive_id => 'zz1'};
$j->{listing}{existing} = [$file];
$j->_add_filename($file);
expect_should_upload($options, $j, $file, 7656348);
ok !defined eval{ App::MtAws::Command::Sync::next_modified($options, $j); 1};
ok $@ =~ /Unknown value returned by should_upload/;
};
};
};
describe "next_new" => sub {
my $options;
before each => sub {
$options = { partsize => 2};
};
it "should work with one file" => sub {
$j->{listing}{new} = [{relfilename => 'file1'}];
my $rec = App::MtAws::Command::Sync::next_new($options, $j);
ok $rec->isa('App::MtAws::QueueJob::Upload');
is $rec->{partsize}, $options->{partsize}*1024*1024;
is $rec->{relfilename}, 'file1';
is $rec->{filename}, $j->absfilename('file1');
ok !$rec->{delete_after_upload};
is scalar @{ $j->{listing}{new} }, 0;
ok !defined (App::MtAws::Command::Sync::next_new($options, $j));
};
it "should work with two files" => sub {
$j->{listing}{new} = [{relfilename => 'file1'}, {relfilename => 'file2'}];
my $rec = App::MtAws::Command::Sync::next_new($options, $j);
ok $rec->isa('App::MtAws::QueueJob::Upload');
is $rec->{relfilename}, 'file1';
is scalar @{ $j->{listing}{new} }, 1;
$rec = App::MtAws::Command::Sync::next_new($options, $j);
is $rec->{relfilename}, 'file2';
};
it "should work with zero files" => sub {
$j->{listing}{new} = [];
ok ! defined( App::MtAws::Command::Sync::next_new($options, $j) );
};
};
describe "next_missing" => sub {
my $options;
before each => sub {
$options = { };
};
it "should work with one file" => sub {
my $r = {relfilename => 'file1', archive_id => 'somearchive'};
$j->{listing}{missing} = [{relfilename => $r->{relfilename}}];
$j->_add_filename($r);
my $rec = App::MtAws::Command::Sync::next_missing($options, $j);
ok $rec->isa('App::MtAws::QueueJob::Delete');
is $rec->{archive_id}, 'somearchive';
is $rec->{relfilename}, 'file1';
is scalar @{ $j->{listing}{missing} }, 0;
ok !defined (App::MtAws::Command::Sync::next_missing($options, $j));
};
it "should work with two files" => sub {
for ({relfilename => 'file1', archive_id => 'somearchive1'}, {relfilename => 'file2', archive_id => 'somearchive2'}) {
push @{ $j->{listing}{missing} }, {relfilename => $_->{relfilename}};
$j->_add_filename($_);
}
my $rec = App::MtAws::Command::Sync::next_missing($options, $j);
ok $rec->isa('App::MtAws::QueueJob::Delete');
is $rec->{archive_id}, 'somearchive1';
is $rec->{relfilename}, 'file1';
is scalar @{ $j->{listing}{missing} }, 1;
$rec = App::MtAws::Command::Sync::next_missing($options, $j);
is $rec->{archive_id}, 'somearchive2';
is $rec->{relfilename}, 'file2';
};
it "should work with zero files" => sub {
$j->{listing}{missing} = [];
ok ! defined( App::MtAws::Command::Sync::next_missing($options, $j) );
};
it "should work with latest version of file" => sub {
my $r = {relfilename => 'file1'};
$j->{listing}{missing} = [$r];
$j->_add_filename({relfilename => 'file1', archive_id => 'zz1', size => 123, time => 42, mtime => 111});
$j->_add_filename({relfilename => 'file1', archive_id => 'zz2', size => 123, time => 42, mtime => 113});
$j->_add_filename({relfilename => 'file1', archive_id => 'zz3', size => 123, time => 42, mtime => 112});
my $rec = App::MtAws::Command::Sync::next_missing($options, $j);
ok $rec->isa('App::MtAws::QueueJob::Delete');
is $rec->{relfilename}, 'file1';
is $rec->{archive_id}, 'zz2';
};
it "should call latest() to get latest version of file" => sub {
$j->{listing}{missing} = [{relfilename => 'file1'}];
$j->_add_filename({relfilename => 'file1', archive_id => 'zz1', size => 123, time => 42, mtime => 111});
$j->_add_filename(my $r2 = {relfilename => 'file1', archive_id => 'zz2', size => 123, time => 42, mtime => 113});
App::MtAws::Journal->expects("latest")->returns(sub{ is $_[1], "file1"; $r2})->once;
my $rec = App::MtAws::Command::Sync::next_missing($options, $j);
};
};
describe "get_journal_opts" => sub {
it "should work in all cases" => sub {
for my $n (0, 1) {
for my $r (0, 1) {
for my $d (0, 1) {
my $options = {};
$options->{new} = 1 if $n;
$options->{'replace-modified'} = 1 if $r;
$options->{'delete-removed'} = 1 if $d;
my $res = App::MtAws::Command::Sync::get_journal_opts($options);
ok ! first { !/^(new|existing|missing)$/ } keys %$res; # make sure we don't have other keys here
cmp_deeply $res->{new}, bool $n; # can be 0, undef, not existant etc
cmp_deeply $res->{existing}, bool $r;
cmp_deeply $res->{missing}, bool $d;
}
}
}
};
};
describe "print_dry_run" => sub {
{
package WillDoTest;
use Carp;
sub will_do {
my ($self) = @_;
if ($self->{toprint_a}) {
map "Will ".$_ , @{ $self->{toprint_a} };
} elsif ($self->{toprint}) {
"Will ".$self->{toprint};
} elsif ($self->{empty}) {
''
} else {
return;
}
}
}
it "should work with zero elements" => sub {
capture_stdout my $out => sub {
App::MtAws::Command::Sync::print_dry_run(sub {});
};
is $out, "";
};
it "should work with one element when it returns empty list" => sub {
my @a = bless {}, "WillDoTest";
capture_stdout my $out => sub {
App::MtAws::Command::Sync::print_dry_run(sub { shift @a });
};
is $out, "";
};
it "should work with one element when it returns empty string" => sub {
my @a = bless {empty=>'1'}, "WillDoTest";
capture_stdout my $out => sub {
App::MtAws::Command::Sync::print_dry_run(sub { shift @a });
};
is $out, "\n";
};
it "should work with one element" => sub {
my @a = bless { toprint => 42}, "WillDoTest";
capture_stdout my $out => sub {
App::MtAws::Command::Sync::print_dry_run(sub { shift @a });
};
is $out, "Will 42\n";
};
it "should work with two elements" => sub {
my @a = (bless({ toprint => 42}, "WillDoTest"),bless({ toprint => 123}, "WillDoTest"));
capture_stdout my $out => sub {
App::MtAws::Command::Sync::print_dry_run(sub { shift @a });
};
is $out, "Will 42\nWill 123\n";
};
it "should work with list elements" => sub {
my @a = bless { toprint_a => [42, 'zz']}, "WillDoTest";
capture_stdout my $out => sub {
App::MtAws::Command::Sync::print_dry_run(sub { shift @a });
};
is $out, "Will 42\nWill zz\n";
};
it "should work with two list elements" => sub {
my @a = ( bless({ toprint_a => [42, 'zz']}, "WillDoTest"), bless({ toprint_a => [123, 'ff']}, "WillDoTest"));
capture_stdout my $out => sub {
App::MtAws::Command::Sync::print_dry_run(sub { shift @a });
};
is $out, "Will 42\nWill zz\nWill 123\nWill ff\n";
};
};
describe "run" => sub {
sub expect_with_forks
{
App::MtAws::Command::Sync->expects("with_forks")->returns_ordered(sub{
my ($flag, $options, $cb) = @_;
is $flag, !$options->{'dry-run'};
is $options, $options;
$cb->();
});
}
sub expect_journal_init
{
my ($options, $j, $read_files_mode) = @_;
$j->expects("read_journal")->returns_ordered(sub{
shift;
cmp_deeply({@_}, {should_exist => 0});
})->once;#returns(sub{ is ++shift->{_stage}, 1 })
App::MtAws::Journal->expects("read_files")->returns_ordered(sub {
shift;
cmp_deeply [@_], [$read_files_mode, $options->{'max-number-of-files'}];
})->once;
App::MtAws::Journal->expects("open_for_write")->returns_ordered->once;
}
sub expect_fork_engine
{
App::MtAws::Command::Sync->expects("fork_engine")->returns_ordered(sub {
bless { parent_worker =>
bless {}, 'App::MtAws::ParentWorker'
}, 'App::MtAws::ForkEngine';
})->once;
}
sub expect_journal_close
{
App::MtAws::Journal->expects("close_for_write")->returns_ordered->once;
}
sub expect_process_task
{
my ($j, $cb) = @_;
App::MtAws::ParentWorker->expects("process_task")->returns_ordered(sub {
my ($self, $job, $journal) = @_;
ok $self->isa('App::MtAws::ParentWorker');
is $journal, $j;
$cb->($job);
} )->once;
}
it "should work with new" => sub {
my $options = { 'max-number-of-files' => 10, partsize => 2, new => 1 };
ordered_test sub {
expect_with_forks;
expect_journal_init($options, $j, {new=>1});
expect_fork_engine;
my @files = qw/file1 file2 file3 file4/;
expect_process_task($j, sub {
my ($job) = @_;
ok $job->isa('App::MtAws::QueueJob::Iterator');
ok ! keys %{$job->{jobs}}; # no pending jobs
my $itt = $job->{iterator}->()->{iterator}; # resolve iterator of iterators
for (@files) {
my $task = $itt->();
is $task->{relfilename}, $_;
is $task->{partsize}, $options->{partsize}*1024*1024;
ok ! $task->{delete_after_upload};
ok $task->isa('App::MtAws::QueueJob::Upload');
}
return (1)
});
expect_journal_close;
$j->{listing}{existing} = [];
$j->{listing}{new} = [ map { relfilename => $_ }, @files ];
App::MtAws::Command::Sync::run($options, $j);
};
};
it "should work with replace-modified" => sub {
my $options = { 'max-number-of-files' => 10, partsize => 2, 'replace-modified' => 1, detect => 'mtime-and-treehash' };
ordered_test sub {
expect_with_forks;
expect_journal_init($options, $j, {existing=>1});
expect_fork_engine;
my %files = (
file1 => {size => 123, archive_id => 'a1'},
file2 => {size => 456, archive_id => 'a2'},
file3 => {size => 789, archive_id => 'a3'},
file4 => {size => 42, archive_id => 'a4'}
);
expect_process_task($j, sub {
my ($job) = @_;
ok $job->isa('App::MtAws::QueueJob::Iterator');
ok ! keys %{$job->{jobs}}; # no pending jobs
my $itt = $job->{iterator}->()->{iterator}; # resolve iterator of iterators
for (sort keys %files) {
my $task = $itt->();
is $task->{relfilename}, $_;
is $task->{partsize}, $options->{partsize}*1024*1024;
ok $task->{delete_after_upload};
is $task->{archive_id}, $files{$_}{archive_id};
ok $task->isa('App::MtAws::QueueJob::Upload');
}
return (1)
});
expect_journal_close;
$j->{listing}{new} = [];
for (sort keys %files) {
my $r = {relfilename => $_};
$j->_add_filename({%$r, %{$files{$_}}});
push @{ $j->{listing}{existing} }, $r;
}
App::MtAws::Command::Sync->expects("file_size")->returns(sub {
my ($file) = @_;
$file =~ m!([^/]+)$! or confess;
$files{$1}{size}+1 or confess; # return different filesize
})->exactly(scalar keys %files);
App::MtAws::Command::Sync::run($options, $j);
};
};
it "should work with delete-removed" => sub {
my $options = { 'max-number-of-files' => 10, partsize => 2, 'delete-removed' => 1 };
ordered_test sub {
expect_with_forks;
expect_journal_init($options, $j, {missing=>1});
expect_fork_engine;
my %files = (
file1 => {archive_id => 'z123'},
file2 => {archive_id => 'z456'},
file3 => {archive_id => 'z789'},
file4 => {archive_id => 'z42'}
);
expect_process_task($j, sub {
my ($job) = @_;
ok $job->isa('App::MtAws::QueueJob::Iterator');
ok ! keys %{$job->{jobs}}; # no pending jobs
my $itt = $job->{iterator}->()->{iterator}; # resolve iterator of iterators
for (sort keys %files) {
my $task = $itt->();
is $task->{relfilename}, $_;
is $task->{archive_id}, $files{$_}{archive_id};
ok $task->isa('App::MtAws::QueueJob::Delete');
}
return (1)
});
expect_journal_close;
$j->{listing}{missing} = [];
for (sort keys %files) {
my $r = {relfilename => $_, archive_id => $files{$_}{archive_id}};
$j->_add_filename($r);
push @{ $j->{listing}{missing} }, {relfilename => $r->{relfilename}};
}
App::MtAws::Command::Sync::run($options, $j);
};
};
it "should work with combination of options" => sub {
for my $n (0, 1) { for my $r (0, 1) { for my $d (0, 1) {
my $options = {
'max-number-of-files' => 10, partsize => 2,
$n ? (new => 1) : (),
$r ? ('replace-modified' => 1, detect => 'mtime-or-treehash') : (),
$d ? ('delete-removed' => 1) : (),
};
ordered_test sub {
expect_with_forks;
expect_journal_init($options, $j, App::MtAws::Command::Sync::get_journal_opts($options));
my @files = qw/file1 file2 file3 file4/;
{
my $res = App::MtAws::Command::Sync->expects("next_new")->returns("sub_next_new");
$n ? $res->once : $res->never;
}
{
my $res = App::MtAws::Command::Sync->expects("next_modified")->returns("sub_next_modified");
$r ? $res->once : $res->never;
}
{
my $res = App::MtAws::Command::Sync->expects("next_missing")->returns("sub_next_missing");
$d ? $res->once : $res->never;
}
if ($n || $r || $d) {
expect_fork_engine;
expect_process_task($j, sub {
my ($job) = @_;
my @jobs;
while (my $ji = $job->{iterator}->() ) { push @jobs, $ji; }
cmp_deeply [ map { $_->{iterator}->() } @jobs ], [
$n ? ('sub_next_new') : (),
$r ? ('sub_next_modified') : (),
$d ? ('sub_next_missing') : (),
];
return (1);
});
} else {
App::MtAws::Command::Sync->expects("fork_engine")->never;
App::MtAws::ParentWorker->expects("process_task")->never;
ok 1; # test that we got there, just in case
}
expect_journal_close;
App::MtAws::Command::Sync::run($options, $j);
};
}}}
};
it "should work with combination of options in dry-run mode" => sub {
for my $n (0, 1) { for my $r (0, 1) { for my $d (0, 1) {
my $options = {
'max-number-of-files' => 10, partsize => 2, 'dry-run' => 1,
$n ? (new => 1) : (),
$r ? ('replace-modified' => 1, detect => 'mtime-or-treehash') : (),
$d ? ('delete-removed' => 1) : (),
};
ordered_test sub {
expect_with_forks;
expect_journal_init($options, $j, App::MtAws::Command::Sync::get_journal_opts($options));
{
my $res = App::MtAws::Command::Sync->expects("next_new")->returns("sub_next_new");
$n ? $res->once : $res->never;
}
{
my $res = App::MtAws::Command::Sync->expects("next_modified")->returns("sub_next_modified");
$r ? $res->once : $res->never;
}
{
my $res = App::MtAws::Command::Sync->expects("next_missing")->returns("sub_next_missing");
$d ? $res->once : $res->never;
}
my @dry_run_args;
App::MtAws::Command::Sync->expects("print_dry_run")->returns(sub {
push @dry_run_args, shift;
})->any_number;
App::MtAws::Command::Sync->expects("fork_engine")->never;
App::MtAws::ParentWorker->expects("process_task")->never;
expect_journal_close;
App::MtAws::Command::Sync::run($options, $j);
cmp_deeply [ map $_->(), @dry_run_args ], [
$n ? ('sub_next_new') : (),
$r ? ('sub_next_modified') : (),
$d ? ('sub_next_missing') : (),
];
};
}}}
}
}
};
runtests unless caller;
1;