#! /usr/bin/perl -W # # Run package tests using Fiasco-UX # # # Adam Lackorzynski # Ronald Aigner # # This script is derived from the autocheck script which can be found in # kernel/fiasco/tools. It has been slightly modified to suit the demands of # the package tests. # # make it pass for now #exit 0; use strict; use Getopt::Long; my $FIASCOUX; my $FIASCOUX_PARAM; my $L4DIR; my $OBJ_BASE; my $OBJDIR = 'OBJ-x86_586-l4v2'; my $EXPECTED_OUT = 'expected.txt'; my $TMP_OUT = 'tmp_out.txt'; my $PKGNAME; my $COMPARE_CMD = 'diff -u $EXPECTED_OUT $TMP_OUT'; my $ALARM_TIMEOUT = 60; # in seconds my %templs = ( roottask => '%s/pkg/roottask/server/src/%s/roottask', sigma0 => '%s/pkg/sigma0/server/src/%s/sigma0', ); my $Verbose = 0; my $Quiet = 0; my $Generate = 0; my $Memory = 32; my $Plainrun = 0; my $No_fb = 0; my $Use_Symbols = 0; my $Use_Lines = 0; my %progs; my %results; my %output; my @Baseservers; my $Client; my $Server; my $Fiasco_Dir; # filehandle for fiasco output my $Input; my $Output; my $Exit_code = 0; my $pid = 0; my $FILTER_LEVEL_FIASCO = 0; my $FILTER_LEVEL_ROOTTASK = 1; my $FILTER_LEVEL_USER = 2; my $Filter_state = $FILTER_LEVEL_FIASCO; my $Filter_level = $FILTER_LEVEL_USER; sub usage() { print < -s --l4dir, -l path Path to an L4 directory --fiascoux, -f file Path to the Fiasco-UX binary --fiascoux_param, -p params Parameters for Fiasco-UX --memory, -m megabyte Number of Megabytes used by Fiasco. (default: 32) --objdir, -O objdir Object dir, currently: $OBJDIR --verbose, -v Be verbose (e.g. show output of L4 apps) --quiet, -q Tell nothing, just set the exit code --generate Generate the output file instead of comparing to it --server, -s file The server to be tested --client, -c file The file to run as test-application --base, -b file Additional base servers required by the application Can be a comma seperated list. (Only specify the binary names!) (default: log, names, dm_phys) --timeout, -t timeout Time in seconds to wait before shooting down a runaway fiasco. (default: 60) --expectfail Expect failure. Return 0 on failure, 1 on success. Environment variables: L4DIR Path to an L4 directory OBJ_BASE Path to build directory FIASCOUX Path to the Fiasco-UX binary EXPECTED_OUT Filename of the file containing the expected output TMP_OUT Filename of the tempfile containing Fiasco's output COMPARE_CMD Command to compare output and expected output Notes: Environment variables override options! EOU } ## # Check if L4DIR looks like an L4 directory sub check_for_l4dir() { unless (-d "$L4DIR/pkg/l4sys/include" || -d "$L4DIR/../kernel/fiasco/src/kern/ux") { die "$L4DIR doesn't look like an L4 directory!"; } } ## # Just check if the supplied binary in $FIASCOUX is really # a Fiasco UX version. Native versions will just segfault. sub check_for_fiasco_ux() { unless ((-x $FIASCOUX && -f $FIASCOUX) || (-l $FIASCOUX && -x readlink $FIASCOUX && -f readlink $FIASCOUX)) { die "$FIASCOUX: Does not exist or isn't an executable file"; } system("$FIASCOUX -h >/dev/null 2>&1"); die "$FIASCOUX doesn't seem to be a UX version." if $?; $FIASCOUX =~ /(.*)\/([^\/]*)$/; $Fiasco_Dir = $1; } # check for user app in binary path of L4 sub check_user_app($) { my $app_with_args = shift; # extract arch and api from OBJ-dir $OBJDIR =~ /OBJ-([^-]*)-(.*)/; my $arch = $1; my $api = $2; my @check = split(/\s+/, $app_with_args); my $app = shift @check; my $p = sprintf "%s/bin/%s/%s/%s", $OBJ_BASE, $arch, $api, $app; die "There's no $p!" unless -f $p; return $p.'\\ '.join('\\ ', @check) if @check; $p; } ## # Check for userland (roottask, sigma0, ...) sub check_for_userland() { foreach my $t (keys %templs) { my $p = sprintf $templs{$t}, $OBJ_BASE, $OBJDIR; die "There's no $p!" unless -f $p; $progs{$t} = $p; } # check for base-servers my @servers = @Baseservers; @Baseservers = {}; if (scalar(@servers) eq 0) { @servers = ( "log", "names", "dm_phys" ); } # iterate over servers and test if they exist in the bin dir foreach my $s (@servers) { push @Baseservers, check_user_app($s); } # test client and server push @Baseservers, check_user_app($Client) if defined($Client); push @Baseservers, check_user_app($Server); } ## # Does the comparison of the output sub run_diff { print STDERR "Running compare command \"$COMPARE_CMD\".\n" if $Verbose; open(TMP, "$COMPARE_CMD |") or die "Cannot run diff: $!"; my @diff = ; close TMP; # reset console in interactive mode system "if tty -s; then stty echo icrnl icanon; fi"; # check if there are differences between expected and generated output if (@diff == 0) { print STDERR "Output of test in $PKGNAME ok.\n"; return $Exit_code; } print STDERR "Test in $PKGNAME generated unexpected output:\n"; @diff = splice @diff, 1000 if @diff > 1000; print STDERR @diff, "\n"; return 1 - $Exit_code; } ## # Called if second alarm signal received # # Now all the output of Fiasco-UX should be through and we can savely kill # Fiasco-UX. We then check the generated output and terminate ourselves. sub got_sig_alarm_second { print STDERR "Timeout for flushing Fiasco-UX output\n" if $Verbose; print STDERR "Sending SIGKILL to $pid and diff output\n" if $Verbose; kill KILL => $pid; alarm 0; # some sanity checks if ( $Filter_state < $FILTER_LEVEL_USER ) { print $Output "\n"; print $Output "User tasks did not start. Maybe overlapping modules?\n"; print $Output "Run \"" . fiascoux_cmdline() . "\" manually to check.\n"; } close $Output; # in generate mode, simply return if ($Generate) { system "stty echo"; exit 0; } exit run_diff(); } ## # Called if first alarm signal received # # To flush Fiasco Output we send it a SIGINT (^C). Then we set up a second # timeout and return, so the filter can process the output which we forced to # be flushed. sub got_sig_alarm_first { print STDERR "Timeout for Fiasco-UX hit!\n" if $Verbose; print STDERR "Sending SIGINT to $pid\n" if $Verbose; kill INT => $pid; $SIG{ALRM} = \&got_sig_alarm_second; alarm 2; # time to flush output # return to keep on filtering the output of Fiasco } ## # Runs the timer and kills fiasco if runaway # # - sets the signal handler # - initializes the timeout sub set_alarm { $SIG{ALRM} = \&got_sig_alarm_first; alarm $ALARM_TIMEOUT; print STDERR "Set alarm to $ALARM_TIMEOUT seconds\n" if $Verbose; } ## # Build the fiasco command line # # Adds the binaries with the appropriate parameters. sub fiascoux_cmdline() { (my $p = $FIASCOUX) =~ s/\/[^\/]+$//; my $cmdline = "$FIASCOUX"; $cmdline .= " $FIASCOUX_PARAM" if defined $FIASCOUX_PARAM; $cmdline .= " -symbols $Fiasco_Dir/Symbols" if $Use_Symbols; $cmdline .= " -lines $Fiasco_Dir/Lines" if $Use_Lines; $cmdline .= " -m $Memory"; $cmdline .= " -R $progs{roottask}"; # -quiet"; $cmdline .= "\"" if $Use_Symbols || $Use_Lines; $cmdline .= " -symbols" if $Use_Symbols; $cmdline .= " -lines" if $Use_Lines; $cmdline .= "\"" if $Use_Symbols || $Use_Lines; $cmdline .= " -S $progs{sigma0}"; # --quiet"; # when we change this to not cd into Fiasco dir, then # prepend $p to $irq0 $cmdline .= " -I ".$p."/irq0"; if ($No_fb eq 1) { $cmdline .= " -F /bin/true"; } else { $cmdline .= " -F ".$p."/con_ux"; } # add -l to base-servers, which already contains client and server for my $s (@Baseservers) { if (ref($s) ne "HASH") { # just checking for Hashes that smuggled in $cmdline .= " -l ".$s; } } $cmdline; } ## # filters the output of fiasco # # We want to see everything once roottask are finished loading. # Therefore, we need some state machine to skip everything before and while # roottask is running. sub filter_fiasco_out { $_ = shift; # filter escape sequences s/[[:cntrl:]]\[(\d;)?\d{0,2}m//g; s/ //g; s/[[:cntrl:]]\[K//g; s/[[:cntrl:]]\[\d+;\d+[Hfr]//g; # check if we have to change state if (/^Roottask: Loading \d+ modules\./ && $Filter_state eq $FILTER_LEVEL_FIASCO) { $Filter_state = $FILTER_LEVEL_ROOTTASK; print STDERR "Changed state to Roottask-Output\n" if $Verbose; } if ( /^$/ && $Filter_state eq $FILTER_LEVEL_ROOTTASK) { $Filter_state = $FILTER_LEVEL_USER; print STDERR "Changed state to Userland-Output\n" if $Verbose; } # skip everything before and from roottask return undef if $Filter_state < $Filter_level; # if we are not supposed to filter anything, then return the line return $_ if $Filter_level eq $FILTER_LEVEL_FIASCO; # filter empty lines return undef if /^$/; # filter JDB warning and prompt return undef if /^Terminal probably too small, should be at least/; return undef if /^\([a-f0-9]\.\d\d\) jdb:/; return undef if /^--.*ESP:.*EIP:.*/; # filter memory dump return undef if /^([a-f0-9]){8}:/; $_; } ## # call_test sub run_fiasco { my $cmdline = fiascoux_cmdline(); print "Calling: $cmdline\n" if $Verbose; $pid = open($Input, "$cmdline|"); die "Can't start Fiasco: $!" unless defined $pid; print "Run Fiasco-UX in $pid\n" if $Verbose; # if in generate mode, we redirect output to EXPECTED_OUT # otherwise we redirect stdout to the TMP_OUT file my $filename = $Generate ? $EXPECTED_OUT : $TMP_OUT; open($Output, ">$filename") || die "Cannot open output file $filename"; my $oldfh = select($Output); $| = 1; select($oldfh); print STDERR "Opened $filename, now setting timer\n" if $Verbose; # the parent sets up the timer (it will eventually call the run_diff sub) set_alarm(); my $o; while (<$Input>) { # we have to strip some lines $o = filter_fiasco_out($_); next unless defined $o; print $Output $o; } print STDERR "Fiasco terminated.\n" if $Verbose; close $Input; close $Output; # when we drop out of this loop fiasco terminated alarm 0; # in generate mode, simply return exit 0 if $Generate; exit run_diff(); } ## # Plain run of Fiasco UX # # Not timer, no filter, simply run UX sub run_plain_fiasco { my $cmdline = fiascoux_cmdline(); print "Running UX: \"$cmdline\"\n"; exec($cmdline) or die "Can't start Fiasco: $!"; exit 1; } # ------------------------------------------------------------- unless (GetOptions("help|h", sub { usage(); exit(0); }, "l4dir|l=s", \$L4DIR, "builddir=s", \$OBJ_BASE, "fiascoux|f=s", \$FIASCOUX, "fiascoux_param|p=s", \$FIASCOUX_PARAM, "memory|m=s", \$Memory, "objdir|O=s", \$OBJDIR, "verbose|v!", \$Verbose, "quiet|q!", \$Quiet, "generate!", \$Generate, "expectfail!", \$Exit_code, "client|c=s", \$Client, "server|s=s", \$Server, "base|b=s", \@Baseservers, "timeout|t=s", \$ALARM_TIMEOUT, "plainrun!", \$Plainrun, "filterlevel=i", \$Filter_level, "nofb!", \$No_fb, "symbols!", \$Use_Symbols, "lines!", \$Use_Lines, )) { usage(); exit(1); } @Baseservers = split(/,/,join(',',@Baseservers)); $L4DIR = $ENV{L4DIR} || die "Need an L4DIR set!" unless $L4DIR; $OBJ_BASE = $ENV{OBJ_BASE} || die "Need an builddir (OBJ_BASE) set!" unless $OBJ_BASE; $FIASCOUX = $ENV{FIASCOUX} || die "Need a Fiasco-UX path!" unless $FIASCOUX; if ($ENV{EXPECTED_OUT}) { $EXPECTED_OUT = $ENV{EXPECTED_OUT}; } die "Need filename of expected output!" unless $EXPECTED_OUT; if ($ENV{TMP_OUT}) { $TMP_OUT = $ENV{TMP_OUT}; } die "No valid temporary file set!" unless $TMP_OUT; $PKGNAME = $ENV{PKGNAME} || $Server || die "No package-name set!" unless $PKGNAME; if ($ENV{COMPARE_CMD}) { $COMPARE_CMD = $ENV{COMPARE_CMD}; } die "No compare command set!" unless $COMPARE_CMD; check_for_l4dir(); check_for_fiasco_ux(); check_for_userland(); ## # The package test script: # This script forks off Fiasco and sets up a timeout. When the timeout # strikes, it sends a SIGINT to Fiasco to force a flush of the output buffer. # Then it sends a SIGKILL to terminate Fiasco. print "Main called by $$.\n" if $Verbose; # if plainrun, start run_plain_fiasco (it never returns) run_plain_fiasco() if $Plainrun; # this function forks off fiasco run_fiasco(); # when we reach this point, something terribly went wrong die "Oops, error in script!\n";