5
0
mirror of git://git.proxmox.com/git/pve-common.git synced 2025-01-18 14:03:34 +03:00
Thomas Lamprecht ffe0b0e075 runtest: clone config before passing to write to avoid side effects
write changes some parts of the config, if this is a reference to the
config used for checking parsing it may lead to unexpected failures
due to those side effects..

For me it was "cidr" and "cidr6" getting deleted, and thus upcomming
tests for a compat change failing without any apparent reason.. :/

Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
2020-03-13 11:02:01 +01:00

212 lines
5.0 KiB
Perl
Executable File

#!/usr/bin/perl
use lib '../../src';
use lib '.';
use strict;
use warnings;
use Carp;
use POSIX;
use IO::Handle;
use Storable qw(dclone);
use PVE::INotify;
# Current config, r() parses a network interface string into this variable
our $config;
##
## Temporary files:
##
# perl conveniently lets you open a string as filehandle so we allow tests
# to temporarily save interface files to virtual files:
my %saved_files;
# Load a temp-file and return it as a string, if it didn't exist, try loading
# a real file.
sub load($) {
my ($from) = @_;
if (my $local = $saved_files{$from}) {
return $local;
}
open my $fh, '<', $from or die "failed to open $from: $!";
local $/ = undef;
my $data = <$fh>;
close $fh;
return $data;
}
# Save a temporary file.
sub save($$) {
my ($file, $data) = @_;
$saved_files{$file} = $data;
}
# Delete a temporary file
sub delfile($) {
my $file = @_;
die "no such file: $file" if !delete $saved_files{$file};
}
# Delete all temporary files.
sub flush_files() {
foreach (keys %saved_files) {
delete $saved_files{$_} if $_ !~ m,^shared/,;
}
}
##
## Interface parsing:
##
# Read an interfaces file with optional /proc/net/dev file content string and
# the list of active interfaces, which otherwise default
sub r($;$$) {
my ($ifaces, $proc_net_dev, $active) = @_;
$proc_net_dev //= load('proc_net_dev');
$active //= [split(/\s+/, load('active_interfaces'))];
open my $fh1, '<', \$ifaces;
open my $fh2, '<', \$proc_net_dev;
$config = PVE::INotify::__read_etc_network_interfaces($fh1, $fh2, $active);
close $fh1;
}
# Turn the current network config into a string.
sub w() {
# write shouldn't be able to change a previously parsed config
my $config_clone = dclone($config);
return PVE::INotify::__write_etc_network_interfaces($config_clone);
}
##
## Interface modification helpers
##
# Update an interface
sub update_iface($$%) {
my ($name, $families, %extra) = @_;
my $ifaces = $config->{ifaces};
my $if = $ifaces->{$name};
die "no such interface: $name\n" if !$if;
$if->{exists} = 1;
# merge extra flags (like bridge_ports, ovs_*) directly
$if->{$_} = $extra{$_} foreach keys %extra;
return if !$families;
my $if_families = $if->{families} ||= [];
foreach my $family (@$families) {
my $type = delete $family->{family};
@$if_families = ((grep { $_ ne $type } @$if_families), $type);
(my $suffix = $type) =~ s/^inet//;
$if->{"method$suffix"} = $family->{address} ? 'static' : 'manual';
foreach(qw(address netmask gateway options)) {
if (my $value = delete $family->{$_}) {
$if->{"$_${suffix}"} = $value;
}
}
}
}
# Create an interface and error if it already exists.
sub new_iface($$$%) {
my ($name, $type, $families, %extra) = @_;
my $ifaces = $config->{ifaces};
croak "interface already exists: $name" if $ifaces->{$name};
$ifaces->{$name} = { type => $type };
update_iface($name, $families, %extra);
}
# Delete an interface and error if it did not exist.
sub delete_iface($;$) {
my ($name, $family) = @_;
my $ifaces = $config->{ifaces};
my $if = $ifaces->{$name} ||= {};
croak "interface doesn't exist: $name" if !$if;
if (!$family) {
delete $ifaces->{$name};
return;
}
my $families = $if->{families};
@$families = grep {$_ ne $family} @$families;
(my $suffix = $family) =~ s/^inet//;
delete $if->{"$_$suffix"} foreach qw(address netmask gateway options);
}
##
## Test helpers:
##
# Compare two strings line by line and show a diff/error if they differ.
sub diff($$) {
my ($a, $b) = @_;
return if $a eq $b;
my ($ra, $wa) = POSIX::pipe();
my ($rb, $wb) = POSIX::pipe();
my $ha = IO::Handle->new_from_fd($wa, 'w');
my $hb = IO::Handle->new_from_fd($wb, 'w');
open my $diffproc, '-|', 'diff', '-up', "/dev/fd/$ra", "/dev/fd/$rb"
or die "failed to run program 'diff': $!";
POSIX::close($ra);
POSIX::close($rb);
open my $f1, '<', \$a;
open my $f2, '<', \$b;
my ($line1, $line2);
do {
$ha->print($line1) if defined($line1 = <$f1>);
$hb->print($line2) if defined($line2 = <$f2>);
} while (defined($line1 // $line2));
close $f1;
close $f2;
close $ha;
close $hb;
local $/ = undef;
my $diff = <$diffproc>;
close $diffproc;
die "files differ:\n$diff";
}
# Write the current interface config and compare the result to a string.
sub expect($) {
my ($expected) = @_;
my $got = w();
diff($expected, $got);
}
##
## Main test execution:
##
# (sorted, it's not used right now but tests could pass on temporary files by
# prefixing the name with shared/ and thus you might want to split a larger
# test into t.01.first-part.pl, t.02.second-part.pl, etc.
my $total = 0;
my $failed = 0;
for our $Test (sort <t.*.pl>) {
$total++;
flush_files();
eval {
require $Test;
};
if ($@) {
print "FAIL: $Test\n$@\n\n";
$failed++;
} else {
print "PASS: $Test\n";
}
}
die "$failed out of $total tests failed\n" if $failed;