diff --git a/build-scripts/src/main/perl/Test/Quattor.pm b/build-scripts/src/main/perl/Test/Quattor.pm index 75cd2dd2..4ee7548e 100644 --- a/build-scripts/src/main/perl/Test/Quattor.pm +++ b/build-scripts/src/main/perl/Test/Quattor.pm @@ -61,7 +61,7 @@ use CAF::Process; use CAF::FileEditor; use CAF::Application; use CAF::Path; -use CAF::Object qw(SUCCESS); +use CAF::Object qw(SUCCESS CHANGED); use IO::String; use base 'Exporter'; use Cwd; @@ -73,10 +73,14 @@ use Test::More; use CAF::Service qw(@FLAVOURS); use Test::Quattor::ProfileCache qw(prepare_profile_cache get_config_for_profile); use Test::Quattor::Object qw(warn_is_ok); +use Cwd; use Readonly; # "File" content that will appear as a directory Readonly our $DIRECTORY => 'MAGIC STRING, THIS IS A MOCKED DIRECTORY'; +# "File" content that will appear as a symlink +Readonly our $SYMLINK => 'MAGIC STRING, THIS IS A MOCKED SYMLINK: '; +Readonly our $HARDLINK => 'MAGIC STRING, THIS IS A MOCKED HARDLINK: '; =over @@ -209,8 +213,7 @@ our @EXPORT = qw(get_command set_file_contents get_file set_desired_output set_desired_err get_config_for_profile set_command_status command_history_reset command_history_ok set_service_variant set_caf_file_close_diff - make_directory remove_any reset_caf_path - warn_is_ok); + make_directory remove_any reset_caf_path warn_is_ok); my @logopts = qw(--verbose); my $debuglevel = $ENV{QUATTOR_TEST_LOG_DEBUGLEVEL}; @@ -520,6 +523,141 @@ Return the mocked C $cpath->mock("any_exists", sub {shift; return is_any(shift); }); +=item is_symlink + +Test if given C is a mocked symlink + +=cut + +$cpath->mock("is_symlink", sub { + my ($self, $path) = @_; + $path = sane_path($path); + + return $files_contents{$path} && "$files_contents{$path}" =~ qr/^$SYMLINK/; +}); + +=item has_hardlinks + +Test if given C is a mocked hardlink + +Note that it is not a perfect replacement for the c C because +the current implementation of mocked hardlinks does not allow to mimic multiple references +to an inode. The differences are : the link used at creation time must be queried, not the +target (where in a real hardlink target and link are undistinguishable); if the path is +a hardlink the number of references for the inode is always 1. + +=cut + +$cpath->mock("has_hardlinks", sub { + my ($self, $path) = @_; + $path = sane_path($path); + + return $files_contents{$path} && "$files_contents{$path}" =~ qr/^$HARDLINK/; +}); + +=item is_hardlink + +Test if C and C are hardlinked + +=cut + +$cpath->mock("is_hardlink", sub { + my ($self, $path1, $path2) = @_; + $path1 = sane_path($path1); + $path2 = sane_path($path2); + + # Order of arguments does not matter with real hardlink() method: + # mimic this behaviour. + # If both paths are hardlinks, they are considered different + my $link_path; + my $target; + if ( $self->has_hardlinks($path1) ) { + $link_path = $path1; + $target = $path2; + } elsif ( $self->has_hardlinks($path2) && ! defined($link_path) ) { + $link_path = $path2; + $target = $path1; + } else { + return; + } + + if ( ($files_contents{$link_path} =~ qr/^$HARDLINK(\S+)/) && ($1 eq $target) ) { + return SUCCESS; + } else { + return; + } + +}); + +=item _make_link + +Add a mocked C<_make_link>. + +This mocked method implements most of the checks done in C, the function +doing the real work in C<_make_link>, and returns the same values as C C<_make_link>. +See C comments for details. + +=cut + +$cpath->mock('_make_link', sub { + my ($self, $target, $link_path, %opts) = @_; + $link_path = sane_path($link_path); + $target = sane_path($target); + + # Check options passed + Readonly my @MAKE_LINK_VALID_OPTS => qw(hard backup nocheck force); + for my $opt (sort keys %opts) { + unless ( grep (/^$opt$/, @MAKE_LINK_VALID_OPTS) ) { + ok(0, "Invalid option ($opt) passed to _make_link()"); + return; + } + } + + my $target_full_path = $target; + unless ( $target_full_path =~ qr{^/} ) { + $target_full_path = cwd() . "/$target_full_path"; + } + + # Check that target exists if it is a hardlink or if option 'nocheck' is false + if ( $opts{hard} or ! $opts{nocheck} ) { + unless ( $files_contents{$target_full_path} ) { + ok(0, "Symlink target ($target_full_path) doesn't exist"); + return; + } + } + + my $link_pattern = 0; + my $is_link_method; + if ( $opts{hard} ) { + $link_pattern = $HARDLINK; + $is_link_method = "has_hardlinks"; + } else { + $link_pattern = $SYMLINK; + $is_link_method = "is_symlink"; + } + if ( $self->$is_link_method($link_path) ) { + if ( ($files_contents{$link_path} =~ qr/^$link_pattern(\S+)/) && ($1 eq $target) ) { + # Link already properly defined + return SUCCESS; + }; + } + if ( ! $self->$is_link_method($link_path) ) { + if ( $self->file_exists($link_path) ) { + unless ( $opts{force} ) { + ok(0, "File $link_path already exists and option 'force' not specified"); + return + } + } elsif ( $self->any_exists($link_path) ) { + ok(0, "$link_path already exists and is not a symlink"); + return + } + } + + $files_contents{$link_path} = "$link_pattern$target"; + + return CHANGED; +}); + =item C Return directory name unless mocked C or mocked C fail. @@ -943,7 +1081,6 @@ sub is_directory } =item is_any - Test if given C is known (as file or directory or anything else) =cut diff --git a/build-scripts/src/test/perl/quattor_path.t b/build-scripts/src/test/perl/quattor_path.t index 156455e2..3bd7a747 100644 --- a/build-scripts/src/test/perl/quattor_path.t +++ b/build-scripts/src/test/perl/quattor_path.t @@ -3,7 +3,7 @@ use warnings; use Test::More; -use CAF::Object qw(SUCCESS); +use CAF::Object qw(SUCCESS CHANGED); use Test::Quattor; use Test::Quattor::Object; @@ -26,6 +26,16 @@ my $s = simple_caf->new(log => $obj); isa_ok($s, "simple_caf", "simple_caf instance created"); +# Mocked ok() function required by some tests involving +# execution of a failed ok() call +my $ok; +my $mocked_ok = sub { + my($self, $test, $name) = @_; + diag("Test ok mocked: ".(defined($test) ? $test : ""). " $name"); + $ok = "$test $name"; +}; +my $mock_test_builder = Test::MockModule->new('Test::Builder'); + =head2 Test mocked filecreation =cut @@ -35,6 +45,7 @@ my $filename = "$filebase/file1"; # Test file does not exist (mocked) ok(! $s->file_exists($filename), "file $filename does not exist"); +ok(! $s->is_symlink($filename), "is_symlink returns false (file $filename does not exist)"); # Create file with simple_caf is($s->make_file($filename, $TEXT), SUCCESS, "make_file returns success"); @@ -46,6 +57,7 @@ is("$fh", $TEXT, "make_file made correct file"); ok($s->file_exists($filename), "mocked file $filename returns true file_exists"); ok(!$s->directory_exists($filename), "mocked file $filename returns false directory_exists"); ok($s->any_exists($filename), "mocked file $filename returns true any_exists"); +ok(! $s->is_symlink($filename), "mocked file $filename returns false is_symlink"); # Test creation of basedir of filename ok(!$s->file_exists($filebase), "mocked dir $filebase returns false file_exists"); @@ -106,6 +118,91 @@ is_deeply([sort keys %Test::Quattor::files_contents ], [qw(/ /some)], is_deeply({ %Test::Quattor::desired_file_contents }, {}, "entries in desired_file_contents are also deleted"); + +=head2 Test mocked symlink creation + +=cut + +my $targetbase = "$BASEPATH/files"; +my $linkbase = "$BASEPATH/files/subdir"; +my $symlink1 = "$linkbase/symlink1"; +my $symlink2 = "$linkbase/symlink2"; +my $target1 = "$targetbase/target1"; +my $target2 = "$targetbase/target2"; +my $target3 = "$targetbase/target3"; +my $unexisting_target = "$targetbase/unexisting_target"; +my $dirtest = "$targetbase/dirtest"; +is($s->make_file($target1, "Link tests: target1"), SUCCESS, "make_file returns success for $target1"); +is($s->make_file($target2, "Link tests: target2"), SUCCESS, "make_file returns success for $target2"); +is($s->make_file($target3, "Link tests: target3"), SUCCESS, "make_file returns success for $target3"); +is($s->make_directory($dirtest, "Link tests: dirtest"), SUCCESS, "make_directory returns success for $dirtest"); + +is($s->symlink($target1, $symlink1), CHANGED, "Symlink $symlink1 successfully created"); +ok($s->is_symlink($symlink1), "$symlink1 is a symlink"); +is($s->symlink($target1, $symlink1), SUCCESS, "Symlink $symlink1 already exists with the right target"); +ok($s->is_symlink($symlink1), "$symlink1 is still a symlink"); +is($s->symlink($target2, $symlink1), CHANGED, "Symlink $symlink1 has been successfully updated"); +ok($s->is_symlink($symlink1), "$symlink1 is still a symlink after being updated"); + +# The following tests will cause a ok() call to fail: mock ok() to intercept it +# Global variable $ok contains the ok() arguments as a string +$mock_test_builder->mock('ok', $mocked_ok); +$s->symlink($target2, $target3); +$mock_test_builder->unmock_all(); +is($ok, + "0 File $target3 already exists and option 'force' not specified", + "$target3 is a file: cannot be updated to a symlink without 'force'"); + +my %opts; +$opts{invalid_option} = 1; +$mock_test_builder->mock('ok', $mocked_ok); +$s->symlink($target2, $target3, %opts); +$mock_test_builder->unmock_all(); +is($ok, + "0 Invalid option (invalid_option) passed to _make_link()", + "symlink() invalid option triggers a test failure"); +delete $opts{invalid_option}; + +$opts{check} = 1; +$mock_test_builder->mock('ok', $mocked_ok); +$s->symlink($unexisting_target, $symlink2, %opts); +$mock_test_builder->unmock_all(); +is($ok, + "0 Symlink target ($unexisting_target) doesn't exist", + "symlink() fails if target doesn't exist with check=1"); +delete $opts{check}; + +$mock_test_builder->mock('ok', $mocked_ok); +$s->symlink($target1, $dirtest); +$mock_test_builder->unmock_all(); +is($ok, + "0 $dirtest already exists and is not a symlink", + "symlink() fails if target exiss and is not a file or symlink"); + + +$opts{force} = 1; +is($s->symlink($target2, $target3, %opts), CHANGED, "$target3 updated to a symlink ('force' option present)"); +ok($s->is_symlink($target3), "$target3 is a symlink"); + + +=head2 Test mocked hardlink creation + +=cut + +my $hardlink1 = "$linkbase/hardlink1"; +my $hardlink2 = "$linkbase/hardlink2"; + +is($s->hardlink($target1, $hardlink1), CHANGED, "Hardlink $hardlink1 successfully created"); +ok($s->has_hardlinks($hardlink1), "$hardlink1 is a hardlink"); +ok($s->is_hardlink($target1, $hardlink1), "$hardlink1 and $target1 are hardlinked"); +is($s->hardlink($target1, $hardlink1), SUCCESS, "Hardlink $hardlink1 already exists with the right target"); +ok($s->has_hardlinks($hardlink1), "$hardlink1 is still a hardlink"); +ok($s->is_hardlink($hardlink1, $target1), "$hardlink1 and $target1 remain hardlinked"); +is($s->hardlink($target2, $hardlink1), CHANGED, "Hardlink $hardlink1 has been successfully updated"); +ok($s->has_hardlinks($hardlink1), "$hardlink1 is still a hardlink after being updated"); +ok($s->is_hardlink($target2, $hardlink1), "$hardlink1 and $target2 are hardlinked"); + + =head2 mock LC and cleanup =cut @@ -119,6 +216,7 @@ ok(! $s->directory_exists($dirbase), "directory $dirbase cleanedup (via recursiv is_deeply($Test::Quattor::caf_path->{cleanup}, [[[$BASEPATH, 1], {option => 2}]], "caf_path hash updated after CAF::Path::cleanup"); + =head2 mock move =cut