From 88ba8ba3cc3a8f8507e33dc902b73d55ab3630cf Mon Sep 17 00:00:00 2001 From: Michel Jouvin Date: Sat, 4 Mar 2017 23:37:45 +0100 Subject: [PATCH] Test::Quattor: mock CAF::Path link-related methods - is_symlink(), has_hardlink(), is_hardlink() and _make_link() - See comments for restrictions with mocked xxx_harlink() methods Fixes #141 --- build-scripts/src/main/perl/Test/Quattor.pm | 143 +++++++++++++++++++- build-scripts/src/test/perl/quattor_path.t | 25 +++- 2 files changed, 163 insertions(+), 5 deletions(-) diff --git a/build-scripts/src/main/perl/Test/Quattor.pm b/build-scripts/src/main/perl/Test/Quattor.pm index 75cd2dd2..0e9e9eeb 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,139 @@ 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) ) { + $self->error("Invalid option ($opt) passed to _make_link()"); + } + } + + 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} ) { + $self->error("Symlink target ($target_full_path) doesn't exist"); + } + } + + my $is_link_pattern = 0; + my $is_link_method; + if ( $opts{hard} ) { + $is_link_pattern = $HARDLINK; + $is_link_method = "has_hardlinks"; + } else { + $is_link_pattern = $SYMLINK; + $is_link_method = "is_symlink"; + } + if ( $self->$is_link_method($link_path) ) { + if ( ($files_contents{$link_path} =~ qr/^$is_link_pattern(\S+)/) && ($1 eq $target) ) { + # Link already properly defined + return SUCCESS; + }; + } + if ( ! $self->is_symlink($link_path) ) { + if ( $self->file_exists($link_path) ) { + unless ( $opts{force} ) { + $self->error("File $link_path already exists and option 'force' not specified"); + return + } + } elsif ( $self->any_exists($link_path) ) { + $self->error("$link_path already exists and is not a symlink"); + return + } + } + + $files_contents{$link_path} = "$SYMLINK$target"; + + return CHANGED; +}); + =item C Return directory name unless mocked C or mocked C fail. @@ -943,7 +1079,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..e7728c34 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; @@ -35,6 +35,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 +47,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 +108,26 @@ 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"; + +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"); + + =head2 mock LC and cleanup =cut @@ -119,6 +141,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