diff options
Diffstat (limited to 'utils/t/update_alternatives.t')
-rw-r--r-- | utils/t/update_alternatives.t | 568 |
1 files changed, 568 insertions, 0 deletions
diff --git a/utils/t/update_alternatives.t b/utils/t/update_alternatives.t new file mode 100644 index 0000000..c80edba --- /dev/null +++ b/utils/t/update_alternatives.t @@ -0,0 +1,568 @@ +#!/usr/bin/perl +# +# This program 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 2 of the License, or +# (at your option) any later version. +# +# This program 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 <https://www.gnu.org/licenses/>. + +use strict; +use warnings; + +use Test::More; + +use File::Spec; + +use Dpkg::IPC; +use Dpkg::File qw(file_slurp); +use Dpkg::Path qw(find_command); + +my $srcdir = $ENV{srcdir} || '.'; +my $tmpdir = 't.tmp/update_alternatives'; +my $admindir = File::Spec->rel2abs("$tmpdir/admindir"), +my $altdir = File::Spec->rel2abs("$tmpdir/alternatives"); +my $bindir = File::Spec->rel2abs("$tmpdir/bin"); +my @ua = ("$ENV{builddir}/update-alternatives", '--log', '/dev/null', + '--quiet', '--admindir', "$admindir", '--altdir', "$altdir"); + +my %paths = ( + true => find_command('true'), + false => find_command('false'), + yes => find_command('yes'), + cat => find_command('cat'), + date => find_command('date'), + sleep => find_command('sleep'), +); + +if (! -x "$ENV{builddir}/update-alternatives") { + plan skip_all => 'update-alternatives not available'; + exit(0); +} + +my $main_link = "$bindir/generic-test"; +my $main_name = 'generic-test'; +my @choices = ( + { + path => $paths{true}, + priority => 20, + slaves => [ + { + link => "$bindir/slave2", + name => 'slave2', + path => $paths{cat}, + }, + { + link => "$bindir/slave3", + name => 'slave3', + path => $paths{cat}, + }, + { + link => "$bindir/slave1", + name => 'slave1', + path => $paths{yes}, + }, + { + link => "$bindir/slave4", + name => 'slave4', + path => $paths{cat}, + }, + ], + }, + { + path => $paths{false}, + priority => 10, + slaves => [ + { + link => "$bindir/slave1", + name => 'slave1', + path => $paths{date}, + }, + ], + }, + { + path => $paths{sleep}, + priority => 5, + slaves => [], + }, +); +my $nb_slaves = 4; +plan tests => (4 * ($nb_slaves + 1) + 2) * 26 # number of check_choices + + 110; # rest + +sub cleanup { + system("rm -rf $tmpdir && mkdir -p $admindir && mkdir -p $altdir"); + system("mkdir -p $bindir/more"); +} + +sub call_ua { + my ($params, %opts) = @_; + spawn(exec => [ @ua, @$params ], nocheck => 1, + wait_child => 1, env => { LC_ALL => 'C' }, %opts); + my $test_id = ''; + $test_id = "$opts{test_id}: " if defined $opts{test_id}; + if ($opts{expect_failure}) { + ok($? != 0, "${test_id}update-alternatives @$params should fail.") or + diag("Did not fail as expected: @ua @$params"); + } else { + ok($? == 0, "${test_id}update-alternatives @$params should work.") or + diag("Did not succeed as expected: @ua @$params"); + } +} + +sub install_choice { + my ($id, %opts) = @_; + my $alt = $choices[$id]; + my @params; + push @params, @{$opts{params}} if exists $opts{params}; + push @params, '--install', "$main_link", "$main_name", + $alt->{path}, $alt->{priority}; + foreach my $slave (@{ $alt->{slaves} }) { + push @params, '--slave', $slave->{link}, $slave->{name}, $slave->{path}; + } + call_ua(\@params, %opts); +} + +sub remove_choice { + my ($id, %opts) = @_; + my $alt = $choices[$id]; + my @params; + push @params, @{$opts{params}} if exists $opts{params}; + push @params, '--remove', $main_name, $alt->{path}; + call_ua(\@params, %opts); +} + +sub remove_all_choices { + my (%opts) = @_; + my @params; + push @params, @{$opts{params}} if exists $opts{params}; + push @params, '--remove-all', $main_name; + call_ua(\@params, %opts); +} + +sub set_choice { + my ($id, %opts) = @_; + my $alt = $choices[$id]; + my @params; + push @params, @{$opts{params}} if exists $opts{params}; + push @params, '--set', $main_name, $alt->{path}; + call_ua(\@params, %opts); +} + +sub config_choice { + my ($id, %opts) = @_; + my ($input, $output) = ('', ''); + if ($id >= 0) { + my $alt = $choices[$id]; + $input = $alt->{path}; + } else { + $input = '0'; + } + $input .= "\n"; + $opts{from_string} = \$input; + $opts{to_string} = \$output; + my @params; + push @params, @{$opts{params}} if exists $opts{params}; + push @params, '--config', $main_name; + call_ua(\@params, %opts); +} + +sub get_slaves_status { + my ($id) = @_; + my %slaves; + # None of the slaves are installed + foreach my $alt (@choices) { + for my $i (0 .. @{$alt->{slaves}} - 1) { + $slaves{$alt->{slaves}[$i]{name}} = $alt->{slaves}[$i]; + $slaves{$alt->{slaves}[$i]{name}}{installed} = 0; + } + } + # except those of the current alternative (minus optional slaves) + if (defined($id)) { + my $alt = $choices[$id]; + for my $i (0 .. @{$alt->{slaves}} - 1) { + $slaves{$alt->{slaves}[$i]{name}} = $alt->{slaves}[$i]; + if (-e $alt->{slaves}[$i]{path}) { + $slaves{$alt->{slaves}[$i]{name}}{installed} = 1; + } + } + } + my @slaves = sort { $a->{name} cmp $b->{name} } values %slaves; + + return @slaves; +} + +sub check_link { + my ($link, $value, $msg) = @_; + ok(-l $link, "$msg: $link disappeared."); + is(readlink($link), $value, "$link doesn't point to $value."); +} +sub check_no_link { + my ($link, $msg) = @_; + lstat($link); + ok(!-e _, "$msg: $link still exists."); + ok(1, 'fake test'); # Same number of tests as check_link +} + +sub check_slaves { + my ($id, $msg) = @_; + foreach my $slave (get_slaves_status($id)) { + if ($slave->{installed}) { + check_link("$altdir/$slave->{name}", $slave->{path}, $msg); + check_link($slave->{link}, "$altdir/$slave->{name}", $msg); + } else { + check_no_link("$altdir/$slave->{name}", $msg); + check_no_link($slave->{link}, $msg); + } + } +} +# (4 * (nb_slaves+1) + 2) tests in each check_choice() call +sub check_choice { + my ($id, $mode, $msg) = @_; + my $output; + if (defined $id) { + # Check status + call_ua([ '--query', "$main_name" ], to_string => \$output, test_id => $msg); + $output =~ /^Status: (.*)$/im; + is($1, $mode, "$msg: status is not $mode."); + # Check links + my $alt = $choices[$id]; + check_link("$altdir/$main_name", $alt->{path}, $msg); + check_link($main_link, "$altdir/$main_name", $msg); + check_slaves($id, $msg); + } else { + call_ua([ '--query', "$main_name" ], error_to_string => \$output, + expect_failure => 1, test_id => $msg); + ok($output =~ /no alternatives/, "$msg: bad error message for --query."); + # Check that all links have disappeared + check_no_link("$altdir/$main_name", $msg); + check_no_link($main_link, $msg); + check_slaves(undef, $msg); + } +} + +### START OF TESTS +cleanup(); +# removal when not installed should not fail +remove_choice(0); +# successive install in auto mode +install_choice(1); +check_choice(1, 'auto', 'initial install 1'); +install_choice(2); # 2 is lower prio, stays at 1 +check_choice(1, 'auto', 'initial install 2'); +install_choice(0); # 0 is higher priority +check_choice(0, 'auto', 'initial install 3'); + +# verify that the administrative file is sorted properly +{ + my $content = file_slurp("$admindir/generic-test"); + my $expected = +"auto +$bindir/generic-test +slave1 +$bindir/slave1 +slave2 +$bindir/slave2 +slave3 +$bindir/slave3 +slave4 +$bindir/slave4 + +"; + + my %slaves; + + # Store slaves in a hash to easily retrieve present and missing ones. + foreach my $alt (@choices) { + foreach my $slave (@{$alt->{slaves}}) { + $slaves{$slave->{name}}{$alt->{path}} = $slave; + } + } + + foreach my $alt (sort { $a->{path} cmp $b->{path} } @choices) { + $expected .= $alt->{path} . "\n"; + $expected .= $alt->{priority} . "\n"; + foreach my $slave_name (sort keys %slaves) { + $expected .= $slaves{$slave_name}{$alt->{path}}{path} || ''; + $expected .= "\n"; + } + } + $expected .= "\n"; + + is($content, $expected, 'administrative file is as expected'); +} + +# manual change with --set-selections +my $input = "doesntexist auto $paths{date}\ngeneric-test manual $paths{false}\n"; +my $output = ''; +call_ua(['--set-selections'], from_string => \$input, + to_string => \$output, test_id => 'manual update with --set-selections'); +check_choice(1, 'manual', 'manual update with --set-selections'); +$input = "generic-test auto $paths{true}\n"; +call_ua(['--set-selections'], from_string => \$input, + to_string => \$output, test_id => 'auto update with --set-selections'); +check_choice(0, 'auto', 'auto update with --set-selections'); +# manual change with set +set_choice(2, test_id => 'manual update with --set'); +check_choice(2, 'manual', 'manual update with --set'); # test #388313 +remove_choice(2, test_id => 'remove manual, back to auto'); +check_choice(0, 'auto', 'remove manual, back to auto'); +remove_choice(0, test_id => 'remove best'); +check_choice(1, 'auto', 'remove best'); +remove_choice(1, test_id => 'no alternative left'); +check_choice(undef, '', 'no alternative left'); +# single choice in manual mode, to be removed +install_choice(1); +set_choice(1); +check_choice(1, 'manual', 'single manual choice'); +remove_choice(1); +check_choice(undef, '', 'removal single manual'); +# test --remove-all +install_choice(0); +install_choice(1); +install_choice(2); +remove_all_choices(test_id => 'remove all'); +check_choice(undef, '', 'no alternative left'); +# check auto-recovery of user mistakes (#100135) +install_choice(1); +ok(unlink("$bindir/generic-test"), 'failed removal'); +ok(unlink("$bindir/slave1"), 'failed removal'); +install_choice(1); +check_choice(1, 'auto', 'recreate links in auto mode'); +set_choice(1); +ok(unlink("$bindir/generic-test"), 'failed removal'); +ok(unlink("$bindir/slave1"), 'failed removal'); +install_choice(1); +check_choice(1, 'manual', 'recreate links in manual mode'); +# check recovery of /etc/alternatives/* +install_choice(0); +ok(unlink("$altdir/generic-test"), 'failed removal'); +install_choice(1); +check_choice(0, 'auto', '<altdir>/generic-test lost, back to auto'); +# test --config +config_choice(0); +check_choice(0, 'manual', 'config to best but manual'); +config_choice(1); +check_choice(1, 'manual', 'config to manual'); +config_choice(-1); +check_choice(0, 'auto', 'config auto'); + +# test rename of links +install_choice(0); +my $old_slave = $choices[0]{slaves}[0]{link}; +my $old_link = $main_link; +$choices[0]{slaves}[0]{link} = "$bindir/more/generic-slave"; +$main_link = "$bindir/more/mytest"; +install_choice(0); +check_choice(0, 'auto', 'test rename of links'); +check_no_link($old_link, 'test rename of links'); +check_no_link($old_slave, 'test rename of links'); +# rename with installing other alternatives +$old_link = $main_link; +$main_link = "$bindir/generic-test"; +install_choice(1); +check_choice(0, 'auto', 'rename link'); +check_no_link($old_link, 'rename link'); +# rename with lost file +unlink($old_slave); +$old_slave = $choices[0]{slaves}[0]{link}; +$choices[0]{slaves}[0]{link} = "$bindir/generic-slave-bis"; +install_choice(0); +check_choice(0, 'auto', 'rename lost file'); +check_no_link($old_slave, 'rename lost file'); +# update of alternative with many slaves not currently installed +# and the link of the renamed slave exists while it should not +set_choice(1); +symlink("$paths{cat}", "$bindir/generic-slave-bis"); +$choices[0]{slaves}[0]{link} = "$bindir/slave2"; +install_choice(0, test_id => 'update with non-installed slaves'); +check_no_link("$bindir/generic-slave-bis", + 'drop renamed symlink that should not be installed'); + +# test install with empty admin file (#457863) +cleanup(); +system("touch $admindir/generic-test"); +install_choice(0); +# test install with garbage admin file +cleanup(); +system("echo garbage > $admindir/generic-test"); +install_choice(0, error_to_file => '/dev/null', expect_failure => 1); + +# test invalid usages +cleanup(); +install_choice(0); +# try to install a slave alternative as new master +call_ua(['--install', "$bindir/testmaster", 'slave1', "$paths{date}", '10'], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# try to install a master alternative as slave +call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10', + '--slave', "$bindir/testslave", 'generic-test', "$paths{true}" ], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# try to reuse master link in slave +call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10', + '--slave', "$bindir/testmaster", 'testslave', "$paths{true}" ], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# try to reuse links in master alternative +call_ua(['--install', "$bindir/slave1", 'testmaster', "$paths{date}", '10'], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# try to reuse links in slave alternative +call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10', + '--slave', "$bindir/generic-test", 'testslave', "$paths{true}" ], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# try to reuse slave link in another slave alternative of another choice of +# the same main alternative +call_ua(['--install', $main_link, $main_name, "$paths{date}", '10', + '--slave', "$bindir/slave1", 'testslave', "$paths{true}" ], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# lack of absolute filenames in links or file path, non-existing path, +call_ua(['--install', '../testmaster', 'testmaster', "$paths{date}", '10'], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +call_ua(['--install', "$bindir/testmaster", 'testmaster', './update-alternatives.pl', '10'], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# non-existing alternative path +call_ua(['--install', "$bindir/testmaster", 'testmaster', "$bindir/doesntexist", '10'], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# invalid alternative name in master +call_ua(['--install', "$bindir/testmaster", 'test/master', "$paths{date}", '10'], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# invalid alternative name in slave +call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10', + '--slave', "$bindir/testslave", 'test slave', "$paths{true}" ], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +# install in non-existing dir should fail +call_ua(['--install', "$bindir/doesntexist/testmaster", 'testmaster', "$paths{date}", '10', + '--slave', "$bindir/testslave", 'testslave', "$paths{true}" ], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); +call_ua(['--install', "$bindir/testmaster", 'testmaster', "$paths{date}", '10', + '--slave', "$bindir/doesntexist/testslave", 'testslave', "$paths{true}" ], + expect_failure => 1, to_file => '/dev/null', error_to_file => '/dev/null'); + +# non-existing alternative path in slave is not a failure +my $old_path = $choices[0]{slaves}[0]{path}; +$old_slave = $choices[0]{slaves}[0]{link}; +$choices[0]{slaves}[0]{path} = "$bindir/doesntexist"; +$choices[0]{slaves}[0]{link} = "$bindir/baddir/slave2"; +# test rename of slave link that existed but that doesn't anymore +# and link is moved into non-existing dir at the same time +install_choice(0); +check_choice(0, 'auto', 'optional renamed slave2 in non-existing dir'); +# same but on fresh install +cleanup(); +install_choice(0); +check_choice(0, 'auto', 'optional slave2 in non-existing dir'); +$choices[0]{slaves}[0]{link} = $old_slave; +# test fresh install with a non-existing slave file +cleanup(); +install_choice(0); +check_choice(0, 'auto', 'optional slave2'); +$choices[0]{slaves}[0]{path} = $old_path; + +# test management of pre-existing files +cleanup(); +system("touch $main_link $bindir/slave1"); +install_choice(0); +ok(!-l $main_link, 'install preserves files that should be links'); +ok(!-l "$bindir/slave1", 'install preserves files that should be slave links'); +remove_choice(0); +ok(-f $main_link, 'removal keeps real file installed as master link'); +ok(-f "$bindir/slave1", 'removal keeps real files installed as slave links'); +install_choice(0, params => ['--force']); +check_choice(0, 'auto', 'install --force replaces files with links'); + +# test management of pre-existing files #2 +cleanup(); +system("touch $main_link $bindir/slave2"); +install_choice(0); +install_choice(1); +ok(!-l $main_link, 'inactive install preserves files that should be links'); +ok(!-l "$bindir/slave2", 'inactive install preserves files that should be slave links'); +ok(-f $main_link, 'inactive install keeps real file installed as master link'); +ok(-f "$bindir/slave2", 'inactive install keeps real files installed as slave links'); +set_choice(1); +ok(!-l $main_link, 'manual switching preserves files that should be links'); +ok(!-l "$bindir/slave2", 'manual switching preserves files that should be slave links'); +ok(-f $main_link, 'manual switching keeps real file installed as master link'); +ok(-f "$bindir/slave2", 'manual switching keeps real files installed as slave links'); +remove_choice(1); +ok(!-l $main_link, 'auto switching preserves files that should be links'); +ok(!-l "$bindir/slave2", 'auto switching preserves files that should be slave links'); +ok(-f $main_link, 'auto switching keeps real file installed as master link'); +ok(-f "$bindir/slave2", 'auto switching keeps real files installed as slave links'); +remove_all_choices(params => ['--force']); +ok(!-e "$bindir/slave2", 'forced removeall drops real files installed as slave links'); + +# test management of pre-existing files #3 +cleanup(); +system("touch $main_link $bindir/slave2"); +install_choice(0); +install_choice(1); +remove_choice(0); +ok(!-l $main_link, 'removal + switching preserves files that should be links'); +ok(!-l "$bindir/slave2", 'removal + switching preserves files that should be slave links'); +ok(-f $main_link, 'removal + switching keeps real file installed as master link'); +ok(-f "$bindir/slave2", 'removal + switching keeps real files installed as slave links'); +install_choice(0); +ok(!-l $main_link, 'install + switching preserves files that should be links'); +ok(!-l "$bindir/slave2", 'install + switching preserves files that should be slave links'); +ok(-f $main_link, 'install + switching keeps real file installed as master link'); +ok(-f "$bindir/slave2", 'install + switching keeps real files installed as slave links'); +set_choice(1, params => ['--force']); +ok(!-e "$bindir/slave2", 'forced switching w/o slave drops real files installed as slave links'); +check_choice(1, 'manual', 'set --force replaces files with links'); + +# check disappearence of obsolete slaves (#916799) +cleanup(); +call_ua([ + '--install', "$bindir/test-obsolete", 'test-obsolete', "$paths{date}", '10', + '--slave', "$bindir/test-slave-a", 'test-slave-a', "$bindir/impl-slave-a", + '--slave', "$bindir/test-slave-b", 'test-slave-b', "$bindir/impl-slave-b", + '--slave', "$bindir/test-slave-c", 'test-slave-c', "$bindir/impl-slave-c", +], to_file => '/dev/null', error_to_file => '/dev/null'); + +my $content; +my $expected; + +$content = file_slurp("$admindir/test-obsolete"); +$expected = +"auto +$bindir/test-obsolete +test-slave-a +$bindir/test-slave-a +test-slave-b +$bindir/test-slave-b +test-slave-c +$bindir/test-slave-c + +$paths{date} +10 +$bindir/impl-slave-a +$bindir/impl-slave-b +$bindir/impl-slave-c + +"; +is($content, $expected, 'administrative file for non-obsolete slaves is as expected'); + +call_ua([ + '--install', "$bindir/test-obsolete", 'test-obsolete', "$paths{date}", '20', + '--slave', "$bindir/test-slave-c", 'test-slave-c', "$bindir/impl-slave-c", +], to_file => '/dev/null', error_to_file => '/dev/null'); + +$content = file_slurp("$admindir/test-obsolete"); +$expected = +"auto +$bindir/test-obsolete +test-slave-c +$bindir/test-slave-c + +$paths{date} +20 +$bindir/impl-slave-c + +"; +is($content, $expected, 'administrative file for obsolete slaves is as expected'); |