at 18.03-beta 5.3 kB view raw
1#! /somewhere/perl -w 2 3use strict; 4use Machine; 5use Term::ReadLine; 6use IO::File; 7use IO::Pty; 8use Logger; 9use Cwd; 10use POSIX qw(_exit dup2); 11use Time::HiRes qw(clock_gettime CLOCK_MONOTONIC); 12 13$SIG{PIPE} = 'IGNORE'; # because Unix domain sockets may die unexpectedly 14 15STDERR->autoflush(1); 16 17my $log = new Logger; 18 19 20# Start vde_switch for each network required by the test. 21my %vlans; 22foreach my $vlan (split / /, $ENV{VLANS} || "") { 23 next if defined $vlans{$vlan}; 24 # Start vde_switch as a child process. We don't run it in daemon 25 # mode because we want the child process to be cleaned up when we 26 # die. Since we have to make sure that the control socket is 27 # ready, we send a dummy command to vde_switch (via stdin) and 28 # wait for a reply. Note that vde_switch requires stdin to be a 29 # TTY, so we create one. 30 $log->log("starting VDE switch for network $vlan"); 31 my $socket = Cwd::abs_path "./vde$vlan.ctl"; 32 my $pty = new IO::Pty; 33 my ($stdoutR, $stdoutW); pipe $stdoutR, $stdoutW; 34 my $pid = fork(); die "cannot fork" unless defined $pid; 35 if ($pid == 0) { 36 dup2(fileno($pty->slave), 0); 37 dup2(fileno($stdoutW), 1); 38 exec "vde_switch -s $socket --dirmode 0700" or _exit(1); 39 } 40 close $stdoutW; 41 print $pty "version\n"; 42 readline $stdoutR or die "cannot start vde_switch"; 43 $ENV{"QEMU_VDE_SOCKET_$vlan"} = $socket; 44 $vlans{$vlan} = $pty; 45 die unless -e "$socket/ctl"; 46} 47 48 49my %vms; 50my $context = ""; 51 52sub createMachine { 53 my ($args) = @_; 54 my $vm = Machine->new({%{$args}, log => $log, redirectSerial => ($ENV{USE_SERIAL} // "0") ne "1"}); 55 $vms{$vm->name} = $vm; 56 $context .= "my \$" . $vm->name . " = \$vms{'" . $vm->name . "'}; "; 57 return $vm; 58} 59 60foreach my $vmScript (@ARGV) { 61 my $vm = createMachine({startCommand => $vmScript}); 62} 63 64 65sub startAll { 66 $log->nest("starting all VMs", sub { 67 $_->start foreach values %vms; 68 }); 69} 70 71 72# Wait until all VMs have terminated. 73sub joinAll { 74 $log->nest("waiting for all VMs to finish", sub { 75 $_->waitForShutdown foreach values %vms; 76 }); 77} 78 79 80# In interactive tests, this allows the non-interactive test script to 81# be executed conveniently. 82sub testScript { 83 eval "$context $ENV{testScript};\n"; 84 warn $@ if $@; 85} 86 87 88my $nrTests = 0; 89my $nrSucceeded = 0; 90 91 92sub subtest { 93 my ($name, $coderef) = @_; 94 $log->nest("subtest: $name", sub { 95 $nrTests++; 96 eval { &$coderef }; 97 if ($@) { 98 $log->log("error: $@", { error => 1 }); 99 } else { 100 $nrSucceeded++; 101 } 102 }); 103} 104 105 106sub runTests { 107 if (defined $ENV{tests}) { 108 $log->nest("running the VM test script", sub { 109 eval "$context $ENV{tests}"; 110 if ($@) { 111 $log->log("error: $@", { error => 1 }); 112 die $@; 113 } 114 }, { expanded => 1 }); 115 } else { 116 my $term = Term::ReadLine->new('nixos-vm-test'); 117 $term->ReadHistory; 118 while (defined ($_ = $term->readline("> "))) { 119 eval "$context $_\n"; 120 warn $@ if $@; 121 } 122 $term->WriteHistory; 123 } 124 125 # Copy the kernel coverage data for each machine, if the kernel 126 # has been compiled with coverage instrumentation. 127 $log->nest("collecting coverage data", sub { 128 foreach my $vm (values %vms) { 129 my $gcovDir = "/sys/kernel/debug/gcov"; 130 131 next unless $vm->isUp(); 132 133 my ($status, $out) = $vm->execute("test -e $gcovDir"); 134 next if $status != 0; 135 136 # Figure out where to put the *.gcda files so that the 137 # report generator can find the corresponding kernel 138 # sources. 139 my $kernelDir = $vm->mustSucceed("echo \$(dirname \$(readlink -f /run/current-system/kernel))/.build/linux-*"); 140 chomp $kernelDir; 141 my $coverageDir = "/tmp/xchg/coverage-data/$kernelDir"; 142 143 # Copy all the *.gcda files. 144 $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"); 145 } 146 }); 147 148 $log->nest("syncing", sub { 149 foreach my $vm (values %vms) { 150 next unless $vm->isUp(); 151 $vm->execute("sync"); 152 } 153 }); 154 155 if ($nrTests != 0) { 156 $log->log("$nrSucceeded out of $nrTests tests succeeded", 157 ($nrSucceeded < $nrTests ? { error => 1 } : { })); 158 } 159} 160 161 162# Create an empty raw virtual disk with the given name and size (in 163# MiB). 164sub createDisk { 165 my ($name, $size) = @_; 166 system("qemu-img create -f raw $name ${size}M") == 0 167 or die "cannot create image of size $size"; 168} 169 170 171END { 172 $log->nest("cleaning up", sub { 173 foreach my $vm (values %vms) { 174 if ($vm->{pid}) { 175 $log->log("killing " . $vm->{name} . " (pid " . $vm->{pid} . ")"); 176 kill 9, $vm->{pid}; 177 } 178 } 179 }); 180 $log->close(); 181} 182 183my $now1 = clock_gettime(CLOCK_MONOTONIC); 184 185runTests; 186 187my $now2 = clock_gettime(CLOCK_MONOTONIC); 188 189printf STDERR "test script finished in %.2fs\n", $now2 - $now1; 190 191exit ($nrSucceeded < $nrTests ? 1 : 0);