191 lines
5.1 KiB
Perl
191 lines
5.1 KiB
Perl
#! /somewhere/perl -w
|
|
|
|
use strict;
|
|
use Machine;
|
|
use Term::ReadLine;
|
|
use IO::File;
|
|
use IO::Pty;
|
|
use Logger;
|
|
use Cwd;
|
|
use POSIX qw(_exit dup2);
|
|
use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC);
|
|
|
|
$SIG{PIPE} = 'IGNORE'; # because Unix domain sockets may die unexpectedly
|
|
|
|
STDERR->autoflush(1);
|
|
|
|
my $log = new Logger;
|
|
|
|
|
|
# Start vde_switch for each network required by the test.
|
|
my %vlans;
|
|
foreach my $vlan (split / /, $ENV{VLANS} || "") {
|
|
next if defined $vlans{$vlan};
|
|
# Start vde_switch as a child process. We don't run it in daemon
|
|
# mode because we want the child process to be cleaned up when we
|
|
# die. Since we have to make sure that the control socket is
|
|
# ready, we send a dummy command to vde_switch (via stdin) and
|
|
# wait for a reply. Note that vde_switch requires stdin to be a
|
|
# TTY, so we create one.
|
|
$log->log("starting VDE switch for network $vlan");
|
|
my $socket = Cwd::abs_path "./vde$vlan.ctl";
|
|
my $pty = new IO::Pty;
|
|
my ($stdoutR, $stdoutW); pipe $stdoutR, $stdoutW;
|
|
my $pid = fork(); die "cannot fork" unless defined $pid;
|
|
if ($pid == 0) {
|
|
dup2(fileno($pty->slave), 0);
|
|
dup2(fileno($stdoutW), 1);
|
|
exec "vde_switch -s $socket" or _exit(1);
|
|
}
|
|
close $stdoutW;
|
|
print $pty "version\n";
|
|
readline $stdoutR or die "cannot start vde_switch";
|
|
$ENV{"QEMU_VDE_SOCKET_$vlan"} = $socket;
|
|
$vlans{$vlan} = $pty;
|
|
die unless -e "$socket/ctl";
|
|
}
|
|
|
|
|
|
my %vms;
|
|
my $context = "";
|
|
|
|
sub createMachine {
|
|
my ($args) = @_;
|
|
my $vm = Machine->new({%{$args}, log => $log, redirectSerial => ($ENV{USE_SERIAL} // "0") ne "1"});
|
|
$vms{$vm->name} = $vm;
|
|
$context .= "my \$" . $vm->name . " = \$vms{'" . $vm->name . "'}; ";
|
|
return $vm;
|
|
}
|
|
|
|
foreach my $vmScript (@ARGV) {
|
|
my $vm = createMachine({startCommand => $vmScript});
|
|
}
|
|
|
|
|
|
sub startAll {
|
|
$log->nest("starting all VMs", sub {
|
|
$_->start foreach values %vms;
|
|
});
|
|
}
|
|
|
|
|
|
# Wait until all VMs have terminated.
|
|
sub joinAll {
|
|
$log->nest("waiting for all VMs to finish", sub {
|
|
$_->waitForShutdown foreach values %vms;
|
|
});
|
|
}
|
|
|
|
|
|
# In interactive tests, this allows the non-interactive test script to
|
|
# be executed conveniently.
|
|
sub testScript {
|
|
eval "$context $ENV{testScript};\n";
|
|
warn $@ if $@;
|
|
}
|
|
|
|
|
|
my $nrTests = 0;
|
|
my $nrSucceeded = 0;
|
|
|
|
|
|
sub subtest {
|
|
my ($name, $coderef) = @_;
|
|
$log->nest("subtest: $name", sub {
|
|
$nrTests++;
|
|
eval { &$coderef };
|
|
if ($@) {
|
|
$log->log("error: $@", { error => 1 });
|
|
} else {
|
|
$nrSucceeded++;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
sub runTests {
|
|
if (defined $ENV{tests}) {
|
|
$log->nest("running the VM test script", sub {
|
|
eval "$context $ENV{tests}";
|
|
if ($@) {
|
|
$log->log("error: $@", { error => 1 });
|
|
die $@;
|
|
}
|
|
}, { expanded => 1 });
|
|
} else {
|
|
my $term = Term::ReadLine->new('nixos-vm-test');
|
|
$term->ReadHistory;
|
|
while (defined ($_ = $term->readline("> "))) {
|
|
eval "$context $_\n";
|
|
warn $@ if $@;
|
|
}
|
|
$term->WriteHistory;
|
|
}
|
|
|
|
# Copy the kernel coverage data for each machine, if the kernel
|
|
# has been compiled with coverage instrumentation.
|
|
$log->nest("collecting coverage data", sub {
|
|
foreach my $vm (values %vms) {
|
|
my $gcovDir = "/sys/kernel/debug/gcov";
|
|
|
|
next unless $vm->isUp();
|
|
|
|
my ($status, $out) = $vm->execute("test -e $gcovDir");
|
|
next if $status != 0;
|
|
|
|
# Figure out where to put the *.gcda files so that the
|
|
# report generator can find the corresponding kernel
|
|
# sources.
|
|
my $kernelDir = $vm->mustSucceed("echo \$(dirname \$(readlink -f /run/current-system/kernel))/.build/linux-*");
|
|
chomp $kernelDir;
|
|
my $coverageDir = "/tmp/xchg/coverage-data/$kernelDir";
|
|
|
|
# Copy all the *.gcda files.
|
|
$vm->execute("for d in $gcovDir/nix/store/*/.build/linux-*; do for i in \$(cd \$d && find -name '*.gcda'); do echo \$i; mkdir -p $coverageDir/\$(dirname \$i); cp -v \$d/\$i $coverageDir/\$i; done; done");
|
|
}
|
|
});
|
|
|
|
$log->nest("syncing", sub {
|
|
foreach my $vm (values %vms) {
|
|
next unless $vm->isUp();
|
|
$vm->execute("sync");
|
|
}
|
|
});
|
|
|
|
if ($nrTests != 0) {
|
|
$log->log("$nrSucceeded out of $nrTests tests succeeded",
|
|
($nrSucceeded < $nrTests ? { error => 1 } : { }));
|
|
}
|
|
}
|
|
|
|
|
|
# Create an empty raw virtual disk with the given name and size (in
|
|
# MiB).
|
|
sub createDisk {
|
|
my ($name, $size) = @_;
|
|
system("qemu-img create -f raw $name ${size}M") == 0
|
|
or die "cannot create image of size $size";
|
|
}
|
|
|
|
|
|
END {
|
|
$log->nest("cleaning up", sub {
|
|
foreach my $vm (values %vms) {
|
|
if ($vm->{pid}) {
|
|
$log->log("killing " . $vm->{name} . " (pid " . $vm->{pid} . ")");
|
|
kill 9, $vm->{pid};
|
|
}
|
|
}
|
|
});
|
|
$log->close();
|
|
}
|
|
|
|
my $now1 = clock_gettime(CLOCK_MONOTONIC);
|
|
|
|
runTests;
|
|
|
|
my $now2 = clock_gettime(CLOCK_MONOTONIC);
|
|
|
|
printf STDERR "test script finished in %.2fs\n", $now2 - $now1;
|
|
|
|
exit ($nrSucceeded < $nrTests ? 1 : 0);
|