diff options
| author | klaar36 <klas.arvidsson@liu.se> | 2015-03-20 17:30:24 +0100 |
|---|---|---|
| committer | klaar36 <klas.arvidsson@liu.se> | 2015-03-20 17:30:24 +0100 |
| commit | e7bc50ca8ffcaa6ed68ebd2315f78b0f5a7d10ad (patch) | |
| tree | 4de97af7207676b69cb6a9aba8cb443cc134855d /src/tests | |
| parent | b0418a24e709f0632d2ede5b0f327c422931939b (diff) | |
| download | pintos-rs-e7bc50ca8ffcaa6ed68ebd2315f78b0f5a7d10ad.tar.gz | |
Initial Pintos
Diffstat (limited to 'src/tests')
438 files changed, 14254 insertions, 0 deletions
diff --git a/src/tests/Algorithm/Diff.pm b/src/tests/Algorithm/Diff.pm new file mode 100644 index 0000000..904c530 --- /dev/null +++ b/src/tests/Algorithm/Diff.pm @@ -0,0 +1,1713 @@ +package Algorithm::Diff; +# Skip to first "=head" line for documentation. +use strict; + +use integer; # see below in _replaceNextLargerWith() for mod to make + # if you don't use this +use vars qw( $VERSION @EXPORT_OK ); +$VERSION = 1.19_01; +# ^ ^^ ^^-- Incremented at will +# | \+----- Incremented for non-trivial changes to features +# \-------- Incremented for fundamental changes +require Exporter; +*import = \&Exporter::import; +@EXPORT_OK = qw( + prepare LCS LCDidx LCS_length + diff sdiff compact_diff + traverse_sequences traverse_balanced +); + +# McIlroy-Hunt diff algorithm +# Adapted from the Smalltalk code of Mario I. Wolczko, <mario@wolczko.com> +# by Ned Konz, perl@bike-nomad.com +# Updates by Tye McQueen, http://perlmonks.org/?node=tye + +# Create a hash that maps each element of $aCollection to the set of +# positions it occupies in $aCollection, restricted to the elements +# within the range of indexes specified by $start and $end. +# The fourth parameter is a subroutine reference that will be called to +# generate a string to use as a key. +# Additional parameters, if any, will be passed to this subroutine. +# +# my $hashRef = _withPositionsOfInInterval( \@array, $start, $end, $keyGen ); + +sub _withPositionsOfInInterval +{ + my $aCollection = shift; # array ref + my $start = shift; + my $end = shift; + my $keyGen = shift; + my %d; + my $index; + for ( $index = $start ; $index <= $end ; $index++ ) + { + my $element = $aCollection->[$index]; + my $key = &$keyGen( $element, @_ ); + if ( exists( $d{$key} ) ) + { + unshift ( @{ $d{$key} }, $index ); + } + else + { + $d{$key} = [$index]; + } + } + return wantarray ? %d : \%d; +} + +# Find the place at which aValue would normally be inserted into the +# array. If that place is already occupied by aValue, do nothing, and +# return undef. If the place does not exist (i.e., it is off the end of +# the array), add it to the end, otherwise replace the element at that +# point with aValue. It is assumed that the array's values are numeric. +# This is where the bulk (75%) of the time is spent in this module, so +# try to make it fast! + +sub _replaceNextLargerWith +{ + my ( $array, $aValue, $high ) = @_; + $high ||= $#$array; + + # off the end? + if ( $high == -1 || $aValue > $array->[-1] ) + { + push ( @$array, $aValue ); + return $high + 1; + } + + # binary search for insertion point... + my $low = 0; + my $index; + my $found; + while ( $low <= $high ) + { + $index = ( $high + $low ) / 2; + + # $index = int(( $high + $low ) / 2); # without 'use integer' + $found = $array->[$index]; + + if ( $aValue == $found ) + { + return undef; + } + elsif ( $aValue > $found ) + { + $low = $index + 1; + } + else + { + $high = $index - 1; + } + } + + # now insertion point is in $low. + $array->[$low] = $aValue; # overwrite next larger + return $low; +} + +# This method computes the longest common subsequence in $a and $b. + +# Result is array or ref, whose contents is such that +# $a->[ $i ] == $b->[ $result[ $i ] ] +# foreach $i in ( 0 .. $#result ) if $result[ $i ] is defined. + +# An additional argument may be passed; this is a hash or key generating +# function that should return a string that uniquely identifies the given +# element. It should be the case that if the key is the same, the elements +# will compare the same. If this parameter is undef or missing, the key +# will be the element as a string. + +# By default, comparisons will use "eq" and elements will be turned into keys +# using the default stringizing operator '""'. + +# Additional parameters, if any, will be passed to the key generation +# routine. + +sub _longestCommonSubsequence +{ + my $a = shift; # array ref or hash ref + my $b = shift; # array ref or hash ref + my $counting = shift; # scalar + my $keyGen = shift; # code ref + my $compare; # code ref + + if ( ref($a) eq 'HASH' ) + { # prepared hash must be in $b + my $tmp = $b; + $b = $a; + $a = $tmp; + } + + # Check for bogus (non-ref) argument values + if ( !ref($a) || !ref($b) ) + { + my @callerInfo = caller(1); + die 'error: must pass array or hash references to ' . $callerInfo[3]; + } + + # set up code refs + # Note that these are optimized. + if ( !defined($keyGen) ) # optimize for strings + { + $keyGen = sub { $_[0] }; + $compare = sub { my ( $a, $b ) = @_; $a eq $b }; + } + else + { + $compare = sub { + my $a = shift; + my $b = shift; + &$keyGen( $a, @_ ) eq &$keyGen( $b, @_ ); + }; + } + + my ( $aStart, $aFinish, $matchVector ) = ( 0, $#$a, [] ); + my ( $prunedCount, $bMatches ) = ( 0, {} ); + + if ( ref($b) eq 'HASH' ) # was $bMatches prepared for us? + { + $bMatches = $b; + } + else + { + my ( $bStart, $bFinish ) = ( 0, $#$b ); + + # First we prune off any common elements at the beginning + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aStart], $b->[$bStart], @_ ) ) + { + $matchVector->[ $aStart++ ] = $bStart++; + $prunedCount++; + } + + # now the end + while ( $aStart <= $aFinish + and $bStart <= $bFinish + and &$compare( $a->[$aFinish], $b->[$bFinish], @_ ) ) + { + $matchVector->[ $aFinish-- ] = $bFinish--; + $prunedCount++; + } + + # Now compute the equivalence classes of positions of elements + $bMatches = + _withPositionsOfInInterval( $b, $bStart, $bFinish, $keyGen, @_ ); + } + my $thresh = []; + my $links = []; + + my ( $i, $ai, $j, $k ); + for ( $i = $aStart ; $i <= $aFinish ; $i++ ) + { + $ai = &$keyGen( $a->[$i], @_ ); + if ( exists( $bMatches->{$ai} ) ) + { + $k = 0; + for $j ( @{ $bMatches->{$ai} } ) + { + + # optimization: most of the time this will be true + if ( $k and $thresh->[$k] > $j and $thresh->[ $k - 1 ] < $j ) + { + $thresh->[$k] = $j; + } + else + { + $k = _replaceNextLargerWith( $thresh, $j, $k ); + } + + # oddly, it's faster to always test this (CPU cache?). + if ( defined($k) ) + { + $links->[$k] = + [ ( $k ? $links->[ $k - 1 ] : undef ), $i, $j ]; + } + } + } + } + + if (@$thresh) + { + return $prunedCount + @$thresh if $counting; + for ( my $link = $links->[$#$thresh] ; $link ; $link = $link->[0] ) + { + $matchVector->[ $link->[1] ] = $link->[2]; + } + } + elsif ($counting) + { + return $prunedCount; + } + + return wantarray ? @$matchVector : $matchVector; +} + +sub traverse_sequences +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $finishedACallback = $callbacks->{'A_FINISHED'}; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $finishedBCallback = $callbacks->{'B_FINISHED'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in @$matchVector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai; + + for ( $ai = 0 ; $ai <= $#$matchVector ; $ai++ ) + { + my $bLine = $matchVector->[$ai]; + if ( defined($bLine) ) # matched + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi < $bLine; + &$matchCallback( $ai, $bi++, @_ ); + } + else + { + &$discardACallback( $ai, $bi, @_ ); + } + } + + # The last entry (if any) processed was a match. + # $ai and $bi point just past the last matching lines in their sequences. + + while ( $ai <= $lastA or $bi <= $lastB ) + { + + # last A? + if ( $ai == $lastA + 1 and $bi <= $lastB ) + { + if ( defined($finishedACallback) ) + { + &$finishedACallback( $lastA, @_ ); + $finishedACallback = undef; + } + else + { + &$discardBCallback( $ai, $bi++, @_ ) while $bi <= $lastB; + } + } + + # last B? + if ( $bi == $lastB + 1 and $ai <= $lastA ) + { + if ( defined($finishedBCallback) ) + { + &$finishedBCallback( $lastB, @_ ); + $finishedBCallback = undef; + } + else + { + &$discardACallback( $ai++, $bi, @_ ) while $ai <= $lastA; + } + } + + &$discardACallback( $ai++, $bi, @_ ) if $ai <= $lastA; + &$discardBCallback( $ai, $bi++, @_ ) if $bi <= $lastB; + } + + return 1; +} + +sub traverse_balanced +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $callbacks = shift || {}; + my $keyGen = shift; + my $matchCallback = $callbacks->{'MATCH'} || sub { }; + my $discardACallback = $callbacks->{'DISCARD_A'} || sub { }; + my $discardBCallback = $callbacks->{'DISCARD_B'} || sub { }; + my $changeCallback = $callbacks->{'CHANGE'}; + my $matchVector = _longestCommonSubsequence( $a, $b, 0, $keyGen, @_ ); + + # Process all the lines in match vector + my $lastA = $#$a; + my $lastB = $#$b; + my $bi = 0; + my $ai = 0; + my $ma = -1; + my $mb; + + while (1) + { + + # Find next match indices $ma and $mb + do { + $ma++; + } while( + $ma <= $#$matchVector + && !defined $matchVector->[$ma] + ); + + last if $ma > $#$matchVector; # end of matchVector? + $mb = $matchVector->[$ma]; + + # Proceed with discard a/b or change events until + # next match + while ( $ai < $ma || $bi < $mb ) + { + + if ( $ai < $ma && $bi < $mb ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai < $ma ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi < $mb + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + # Match + &$matchCallback( $ai++, $bi++, @_ ); + } + + while ( $ai <= $lastA || $bi <= $lastB ) + { + if ( $ai <= $lastA && $bi <= $lastB ) + { + + # Change + if ( defined $changeCallback ) + { + &$changeCallback( $ai++, $bi++, @_ ); + } + else + { + &$discardACallback( $ai++, $bi, @_ ); + &$discardBCallback( $ai, $bi++, @_ ); + } + } + elsif ( $ai <= $lastA ) + { + &$discardACallback( $ai++, $bi, @_ ); + } + else + { + + # $bi <= $lastB + &$discardBCallback( $ai, $bi++, @_ ); + } + } + + return 1; +} + +sub prepare +{ + my $a = shift; # array ref + my $keyGen = shift; # code ref + + # set up code ref + $keyGen = sub { $_[0] } unless defined($keyGen); + + return scalar _withPositionsOfInInterval( $a, 0, $#$a, $keyGen, @_ ); +} + +sub LCS +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + my $matchVector = _longestCommonSubsequence( $a, $b, 0, @_ ); + my @retval; + my $i; + for ( $i = 0 ; $i <= $#$matchVector ; $i++ ) + { + if ( defined( $matchVector->[$i] ) ) + { + push ( @retval, $a->[$i] ); + } + } + return wantarray ? @retval : \@retval; +} + +sub LCS_length +{ + my $a = shift; # array ref + my $b = shift; # array ref or hash ref + return _longestCommonSubsequence( $a, $b, 1, @_ ); +} + +sub LCSidx +{ + my $a= shift @_; + my $b= shift @_; + my $match= _longestCommonSubsequence( $a, $b, 0, @_ ); + my @am= grep defined $match->[$_], 0..$#$match; + my @bm= @{$match}[@am]; + return \@am, \@bm; +} + +sub compact_diff +{ + my $a= shift @_; + my $b= shift @_; + my( $am, $bm )= LCSidx( $a, $b, @_ ); + my @cdiff; + my( $ai, $bi )= ( 0, 0 ); + push @cdiff, $ai, $bi; + while( 1 ) { + while( @$am && $ai == $am->[0] && $bi == $bm->[0] ) { + shift @$am; + shift @$bm; + ++$ai, ++$bi; + } + push @cdiff, $ai, $bi; + last if ! @$am; + $ai = $am->[0]; + $bi = $bm->[0]; + push @cdiff, $ai, $bi; + } + push @cdiff, 0+@$a, 0+@$b + if $ai < @$a || $bi < @$b; + return wantarray ? @cdiff : \@cdiff; +} + +sub diff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $hunk = []; + my $discard = sub { + push @$hunk, [ '-', $_[0], $a->[ $_[0] ] ]; + }; + my $add = sub { + push @$hunk, [ '+', $_[1], $b->[ $_[1] ] ]; + }; + my $match = sub { + push @$retval, $hunk + if 0 < @$hunk; + $hunk = [] + }; + traverse_sequences( $a, $b, + { MATCH => $match, DISCARD_A => $discard, DISCARD_B => $add }, @_ ); + &$match(); + return wantarray ? @$retval : $retval; +} + +sub sdiff +{ + my $a = shift; # array ref + my $b = shift; # array ref + my $retval = []; + my $discard = sub { push ( @$retval, [ '-', $a->[ $_[0] ], "" ] ) }; + my $add = sub { push ( @$retval, [ '+', "", $b->[ $_[1] ] ] ) }; + my $change = sub { + push ( @$retval, [ 'c', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + my $match = sub { + push ( @$retval, [ 'u', $a->[ $_[0] ], $b->[ $_[1] ] ] ); + }; + traverse_balanced( + $a, + $b, + { + MATCH => $match, + DISCARD_A => $discard, + DISCARD_B => $add, + CHANGE => $change, + }, + @_ + ); + return wantarray ? @$retval : $retval; +} + +######################################## +my $Root= __PACKAGE__; +package Algorithm::Diff::_impl; +use strict; + +sub _Idx() { 0 } # $me->[_Idx]: Ref to array of hunk indices + # 1 # $me->[1]: Ref to first sequence + # 2 # $me->[2]: Ref to second sequence +sub _End() { 3 } # $me->[_End]: Diff between forward and reverse pos +sub _Same() { 4 } # $me->[_Same]: 1 if pos 1 contains unchanged items +sub _Base() { 5 } # $me->[_Base]: Added to range's min and max +sub _Pos() { 6 } # $me->[_Pos]: Which hunk is currently selected +sub _Off() { 7 } # $me->[_Off]: Offset into _Idx for current position +sub _Min() { -2 } # Added to _Off to get min instead of max+1 + +sub Die +{ + require Carp; + Carp::confess( @_ ); +} + +sub _ChkPos +{ + my( $me )= @_; + return if $me->[_Pos]; + my $meth= ( caller(1) )[3]; + Die( "Called $meth on 'reset' object" ); +} + +sub _ChkSeq +{ + my( $me, $seq )= @_; + return $seq + $me->[_Off] + if 1 == $seq || 2 == $seq; + my $meth= ( caller(1) )[3]; + Die( "$meth: Invalid sequence number ($seq); must be 1 or 2" ); +} + +sub getObjPkg +{ + my( $us )= @_; + return ref $us if ref $us; + return $us . "::_obj"; +} + +sub new +{ + my( $us, $seq1, $seq2, $opts ) = @_; + my @args; + for( $opts->{keyGen} ) { + push @args, $_ if $_; + } + for( $opts->{keyGenArgs} ) { + push @args, @$_ if $_; + } + my $cdif= Algorithm::Diff::compact_diff( $seq1, $seq2, @args ); + my $same= 1; + if( 0 == $cdif->[2] && 0 == $cdif->[3] ) { + $same= 0; + splice @$cdif, 0, 2; + } + my @obj= ( $cdif, $seq1, $seq2 ); + $obj[_End] = (1+@$cdif)/2; + $obj[_Same] = $same; + $obj[_Base] = 0; + my $me = bless \@obj, $us->getObjPkg(); + $me->Reset( 0 ); + return $me; +} + +sub Reset +{ + my( $me, $pos )= @_; + $pos= int( $pos || 0 ); + $pos += $me->[_End] + if $pos < 0; + $pos= 0 + if $pos < 0 || $me->[_End] <= $pos; + $me->[_Pos]= $pos || !1; + $me->[_Off]= 2*$pos - 1; + return $me; +} + +sub Base +{ + my( $me, $base )= @_; + my $oldBase= $me->[_Base]; + $me->[_Base]= 0+$base if defined $base; + return $oldBase; +} + +sub Copy +{ + my( $me, $pos, $base )= @_; + my @obj= @$me; + my $you= bless \@obj, ref($me); + $you->Reset( $pos ) if defined $pos; + $you->Base( $base ); + return $you; +} + +sub Next { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + if( $steps ) { + my $pos= $me->[_Pos]; + my $new= $pos + $steps; + $new= 0 if $pos && $new < 0; + $me->Reset( $new ) + } + return $me->[_Pos]; +} + +sub Prev { + my( $me, $steps )= @_; + $steps= 1 if ! defined $steps; + my $pos= $me->Next(-$steps); + $pos -= $me->[_End] if $pos; + return $pos; +} + +sub Diff { + my( $me )= @_; + $me->_ChkPos(); + return 0 if $me->[_Same] == ( 1 & $me->[_Pos] ); + my $ret= 0; + my $off= $me->[_Off]; + for my $seq ( 1, 2 ) { + $ret |= $seq + if $me->[_Idx][ $off + $seq + _Min ] + < $me->[_Idx][ $off + $seq ]; + } + return $ret; +} + +sub Min { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off + _Min ]; +} + +sub Max { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off= $me->_ChkSeq($seq); + $base= $me->[_Base] if !defined $base; + return $base + $me->[_Idx][ $off ] -1; +} + +sub Range { + my( $me, $seq, $base )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + $base= $me->[_Base] if !defined $base; + return ( $base + $me->[_Idx][ $off + _Min ] ) + .. ( $base + $me->[_Idx][ $off ] - 1 ); +} + +sub Items { + my( $me, $seq )= @_; + $me->_ChkPos(); + my $off = $me->_ChkSeq($seq); + if( !wantarray ) { + return $me->[_Idx][ $off ] + - $me->[_Idx][ $off + _Min ]; + } + return + @{$me->[$seq]}[ + $me->[_Idx][ $off + _Min ] + .. ( $me->[_Idx][ $off ] - 1 ) + ]; +} + +sub Same { + my( $me )= @_; + $me->_ChkPos(); + return wantarray ? () : 0 + if $me->[_Same] != ( 1 & $me->[_Pos] ); + return $me->Items(1); +} + +my %getName; +BEGIN { + %getName= ( + same => \&Same, + diff => \&Diff, + base => \&Base, + min => \&Min, + max => \&Max, + range=> \&Range, + items=> \&Items, # same thing + ); +} + +sub Get +{ + my $me= shift @_; + $me->_ChkPos(); + my @value; + for my $arg ( @_ ) { + for my $word ( split ' ', $arg ) { + my $meth; + if( $word !~ /^(-?\d+)?([a-zA-Z]+)([12])?$/ + || not $meth= $getName{ lc $2 } + ) { + Die( $Root, ", Get: Invalid request ($word)" ); + } + my( $base, $name, $seq )= ( $1, $2, $3 ); + push @value, scalar( + 4 == length($name) + ? $meth->( $me ) + : $meth->( $me, $seq, $base ) + ); + } + } + if( wantarray ) { + return @value; + } elsif( 1 == @value ) { + return $value[0]; + } + Die( 0+@value, " values requested from ", + $Root, "'s Get in scalar context" ); +} + + +my $Obj= getObjPkg($Root); +no strict 'refs'; + +for my $meth ( qw( new getObjPkg ) ) { + *{$Root."::".$meth} = \&{$meth}; + *{$Obj ."::".$meth} = \&{$meth}; +} +for my $meth ( qw( + Next Prev Reset Copy Base Diff + Same Items Range Min Max Get + _ChkPos _ChkSeq +) ) { + *{$Obj."::".$meth} = \&{$meth}; +} + +1; +__END__ + +=head1 NAME + +Algorithm::Diff - Compute `intelligent' differences between two files / lists + +=head1 SYNOPSIS + + require Algorithm::Diff; + + # This example produces traditional 'diff' output: + + my $diff = Algorithm::Diff->new( \@seq1, \@seq2 ); + + $diff->Base( 1 ); # Return line numbers, not indices + while( $diff->Next() ) { + next if $diff->Same(); + my $sep = ''; + if( ! $diff->Items(2) ) { + sprintf "%d,%dd%d\n", + $diff->Get(qw( Min1 Max1 Max2 )); + } elsif( ! $diff->Items(1) ) { + sprint "%da%d,%d\n", + $diff->Get(qw( Max1 Min2 Max2 )); + } else { + $sep = "---\n"; + sprintf "%d,%dc%d,%d\n", + $diff->Get(qw( Min1 Max1 Min2 Max2 )); + } + print "< $_" for $diff->Items(1); + print $sep; + print "> $_" for $diff->Items(2); + } + + + # Alternate interfaces: + + use Algorithm::Diff qw( + LCS LCS_length LCSidx + diff sdiff compact_diff + traverse_sequences traverse_balanced ); + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + $count = LCS_length( \@seq1, \@seq2 ); + + ( $seq1idxref, $seq2idxref ) = LCSidx( \@seq1, \@seq2 ); + + + # Complicated interfaces: + + @diffs = diff( \@seq1, \@seq2 ); + + @sdiffs = sdiff( \@seq1, \@seq2 ); + + @cdiffs = compact_diff( \@seq1, \@seq2 ); + + traverse_sequences( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + }, + \&key_generator, + @extra_args, + ); + + traverse_balanced( + \@seq1, + \@seq2, + { MATCH => \&callback1, + DISCARD_A => \&callback2, + DISCARD_B => \&callback3, + CHANGE => \&callback4, + }, + \&key_generator, + @extra_args, + ); + + +=head1 INTRODUCTION + +(by Mark-Jason Dominus) + +I once read an article written by the authors of C<diff>; they said +that they worked very hard on the algorithm until they found the +right one. + +I think what they ended up using (and I hope someone will correct me, +because I am not very confident about this) was the `longest common +subsequence' method. In the LCS problem, you have two sequences of +items: + + a b c d f g h j q z + + a b c d e f g i j k r x y z + +and you want to find the longest sequence of items that is present in +both original sequences in the same order. That is, you want to find +a new sequence I<S> which can be obtained from the first sequence by +deleting some items, and from the secend sequence by deleting other +items. You also want I<S> to be as long as possible. In this case I<S> +is + + a b c d f g j z + +From there it's only a small step to get diff-like output: + + e h i k q r x y + + - + + - + + + + +This module solves the LCS problem. It also includes a canned function +to generate C<diff>-like output. + +It might seem from the example above that the LCS of two sequences is +always pretty obvious, but that's not always the case, especially when +the two sequences have many repeated elements. For example, consider + + a x b y c z p d q + a b c a x b y c z + +A naive approach might start by matching up the C<a> and C<b> that +appear at the beginning of each sequence, like this: + + a x b y c z p d q + a b c a b y c z + +This finds the common subsequence C<a b c z>. But actually, the LCS +is C<a x b y c z>: + + a x b y c z p d q + a b c a x b y c z + +or + + a x b y c z p d q + a b c a x b y c z + +=head1 USAGE + +(See also the README file and several example +scripts include with this module.) + +This module now provides an object-oriented interface that uses less +memory and is easier to use than most of the previous procedural +interfaces. It also still provides several exportable functions. We'll +deal with these in ascending order of difficulty: C<LCS>, +C<LCS_length>, C<LCSidx>, OO interface, C<prepare>, C<diff>, C<sdiff>, +C<traverse_sequences>, and C<traverse_balanced>. + +=head2 C<LCS> + +Given references to two lists of items, LCS returns an array containing +their longest common subsequence. In scalar context, it returns a +reference to such a list. + + @lcs = LCS( \@seq1, \@seq2 ); + $lcsref = LCS( \@seq1, \@seq2 ); + +C<LCS> may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L</KEY GENERATION +FUNCTIONS>. + + @lcs = LCS( \@seq1, \@seq2, \&keyGen, @args ); + $lcsref = LCS( \@seq1, \@seq2, \&keyGen, @args ); + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C<LCS_length> + +This is just like C<LCS> except it only returns the length of the +longest common subsequence. This provides a performance gain of about +9% compared to C<LCS>. + +=head2 C<LCSidx> + +Like C<LCS> except it returns references to two arrays. The first array +contains the indices into @seq1 where the LCS items are located. The +second array contains the indices into @seq2 where the LCS items are located. + +Therefore, the following three lists will contain the same values: + + my( $idx1, $idx2 ) = LCSidx( \@seq1, \@seq2 ); + my @list1 = @seq1[ @$idx1 ]; + my @list2 = @seq2[ @$idx2 ]; + my @list3 = LCS( \@seq1, \@seq2 ); + +=head2 C<new> + + $diff = Algorithm::Diffs->new( \@seq1, \@seq2 ); + $diff = Algorithm::Diffs->new( \@seq1, \@seq2, \%opts ); + +C<new> computes the smallest set of additions and deletions necessary +to turn the first sequence into the second and compactly records them +in the object. + +You use the object to iterate over I<hunks>, where each hunk represents +a contiguous section of items which should be added, deleted, replaced, +or left unchanged. + +=over 4 + +The following summary of all of the methods looks a lot like Perl code +but some of the symbols have different meanings: + + [ ] Encloses optional arguments + : Is followed by the default value for an optional argument + | Separates alternate return results + +Method summary: + + $obj = Algorithm::Diff->new( \@seq1, \@seq2, [ \%opts ] ); + $pos = $obj->Next( [ $count : 1 ] ); + $revPos = $obj->Prev( [ $count : 1 ] ); + $obj = $obj->Reset( [ $pos : 0 ] ); + $copy = $obj->Copy( [ $pos, [ $newBase ] ] ); + $oldBase = $obj->Base( [ $newBase ] ); + +Note that all of the following methods C<die> if used on an object that +is "reset" (not currently pointing at any hunk). + + $bits = $obj->Diff( ); + @items|$cnt = $obj->Same( ); + @items|$cnt = $obj->Items( $seqNum ); + @idxs |$cnt = $obj->Range( $seqNum, [ $base ] ); + $minIdx = $obj->Min( $seqNum, [ $base ] ); + $maxIdx = $obj->Max( $seqNum, [ $base ] ); + @values = $obj->Get( @names ); + +Passing in C<undef> for an optional argument is always treated the same +as if no argument were passed in. + +=item C<Next> + + $pos = $diff->Next(); # Move forward 1 hunk + $pos = $diff->Next( 2 ); # Move forward 2 hunks + $pos = $diff->Next(-5); # Move backward 5 hunks + +C<Next> moves the object to point at the next hunk. The object starts +out "reset", which means it isn't pointing at any hunk. If the object +is reset, then C<Next()> moves to the first hunk. + +C<Next> returns a true value iff the move didn't go past the last hunk. +So C<Next(0)> will return true iff the object is not reset. + +Actually, C<Next> returns the object's new position, which is a number +between 1 and the number of hunks (inclusive), or returns a false value. + +=item C<Prev> + +C<Prev($N)> is almost identical to C<Next(-$N)>; it moves to the $Nth +previous hunk. On a 'reset' object, C<Prev()> [and C<Next(-1)>] move +to the last hunk. + +The position returned by C<Prev> is relative to the I<end> of the +hunks; -1 for the last hunk, -2 for the second-to-last, etc. + +=item C<Reset> + + $diff->Reset(); # Reset the object's position + $diff->Reset($pos); # Move to the specified hunk + $diff->Reset(1); # Move to the first hunk + $diff->Reset(-1); # Move to the last hunk + +C<Reset> returns the object, so, for example, you could use +C<< $diff->Reset()->Next(-1) >> to get the number of hunks. + +=item C<Copy> + + $copy = $diff->Copy( $newPos, $newBase ); + +C<Copy> returns a copy of the object. The copy and the orignal object +share most of their data, so making copies takes very little memory. +The copy maintains its own position (separate from the original), which +is the main purpose of copies. It also maintains its own base. + +By default, the copy's position starts out the same as the original +object's position. But C<Copy> takes an optional first argument to set the +new position, so the following three snippets are equivalent: + + $copy = $diff->Copy($pos); + + $copy = $diff->Copy(); + $copy->Reset($pos); + + $copy = $diff->Copy()->Reset($pos); + +C<Copy> takes an optional second argument to set the base for +the copy. If you wish to change the base of the copy but leave +the position the same as in the original, here are two +equivalent ways: + + $copy = $diff->Copy(); + $copy->Base( 0 ); + + $copy = $diff->Copy(undef,0); + +Here are two equivalent way to get a "reset" copy: + + $copy = $diff->Copy(0); + + $copy = $diff->Copy()->Reset(); + +=item C<Diff> + + $bits = $obj->Diff(); + +C<Diff> returns a true value iff the current hunk contains items that are +different between the two sequences. It actually returns one of the +follow 4 values: + +=over 4 + +=item 3 + +C<3==(1|2)>. This hunk contains items from @seq1 and the items +from @seq2 that should replace them. Both sequence 1 and 2 +contain changed items so both the 1 and 2 bits are set. + +=item 2 + +This hunk only contains items from @seq2 that should be inserted (not +items from @seq1). Only sequence 2 contains changed items so only the 2 +bit is set. + +=item 1 + +This hunk only contains items from @seq1 that should be deleted (not +items from @seq2). Only sequence 1 contains changed items so only the 1 +bit is set. + +=item 0 + +This means that the items in this hunk are the same in both sequences. +Neither sequence 1 nor 2 contain changed items so neither the 1 nor the +2 bits are set. + +=back + +=item C<Same> + +C<Same> returns a true value iff the current hunk contains items that +are the same in both sequences. It actually returns the list of items +if they are the same or an emty list if they aren't. In a scalar +context, it returns the size of the list. + +=item C<Items> + + $count = $diff->Items(2); + @items = $diff->Items($seqNum); + +C<Items> returns the (number of) items from the specified sequence that +are part of the current hunk. + +If the current hunk contains only insertions, then +C<< $diff->Items(1) >> will return an empty list (0 in a scalar conext). +If the current hunk contains only deletions, then C<< $diff->Items(2) >> +will return an empty list (0 in a scalar conext). + +If the hunk contains replacements, then both C<< $diff->Items(1) >> and +C<< $diff->Items(2) >> will return different, non-empty lists. + +Otherwise, the hunk contains identical items and all of the following +will return the same lists: + + @items = $diff->Items(1); + @items = $diff->Items(2); + @items = $diff->Same(); + +=item C<Range> + + $count = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum ); + @indices = $diff->Range( $seqNum, $base ); + +C<Range> is like C<Items> except that it returns a list of I<indices> to +the items rather than the items themselves. By default, the index of +the first item (in each sequence) is 0 but this can be changed by +calling the C<Base> method. So, by default, the following two snippets +return the same lists: + + @list = $diff->Items(2); + @list = @seq2[ $diff->Range(2) ]; + +You can also specify the base to use as the second argument. So the +following two snippets I<always> return the same lists: + + @list = $diff->Items(1); + @list = @seq1[ $diff->Range(1,0) ]; + +=item C<Base> + + $curBase = $diff->Base(); + $oldBase = $diff->Base($newBase); + +C<Base> sets and/or returns the current base (usually 0 or 1) that is +used when you request range information. The base defaults to 0 so +that range information is returned as array indices. You can set the +base to 1 if you want to report traditional line numbers instead. + +=item C<Min> + + $min1 = $diff->Min(1); + $min = $diff->Min( $seqNum, $base ); + +C<Min> returns the first value that C<Range> would return (given the +same arguments) or returns C<undef> if C<Range> would return an empty +list. + +=item C<Max> + +C<Max> returns the last value that C<Range> would return or C<undef>. + +=item C<Get> + + ( $n, $x, $r ) = $diff->Get(qw( min1 max1 range1 )); + @values = $diff->Get(qw( 0min2 1max2 range2 same base )); + +C<Get> returns one or more scalar values. You pass in a list of the +names of the values you want returned. Each name must match one of the +following regexes: + + /^(-?\d+)?(min|max)[12]$/i + /^(range[12]|same|diff|base)$/i + +The 1 or 2 after a name says which sequence you want the information +for (and where allowed, it is required). The optional number before +"min" or "max" is the base to use. So the following equalities hold: + + $diff->Get('min1') == $diff->Min(1) + $diff->Get('0min2') == $diff->Min(2,0) + +Using C<Get> in a scalar context when you've passed in more than one +name is a fatal error (C<die> is called). + +=back + +=head2 C<prepare> + +Given a reference to a list of items, C<prepare> returns a reference +to a hash which can be used when comparing this sequence to other +sequences with C<LCS> or C<LCS_length>. + + $prep = prepare( \@seq1 ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $prep, $seq[$i] ); + # do something useful with @lcs + } + +C<prepare> may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L</KEY GENERATION +FUNCTIONS>. + + $prep = prepare( \@seq1, \&keyGen ); + for $i ( 0 .. 10_000 ) + { + @lcs = LCS( $seq[$i], $prep, \&keyGen ); + # do something useful with @lcs + } + +Using C<prepare> provides a performance gain of about 50% when calling LCS +many times compared with not preparing. + +=head2 C<diff> + + @diffs = diff( \@seq1, \@seq2 ); + $diffs_ref = diff( \@seq1, \@seq2 ); + +C<diff> computes the smallest set of additions and deletions necessary +to turn the first sequence into the second, and returns a description +of these changes. The description is a list of I<hunks>; each hunk +represents a contiguous section of items which should be added, +deleted, or replaced. (Hunks containing unchanged items are not +included.) + +The return value of C<diff> is a list of hunks, or, in scalar context, a +reference to such a list. If there are no differences, the list will be +empty. + +Here is an example. Calling C<diff> for the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +would produce the following list: + + ( + [ [ '-', 0, 'a' ] ], + + [ [ '+', 2, 'd' ] ], + + [ [ '-', 4, 'h' ], + [ '+', 4, 'f' ] ], + + [ [ '+', 6, 'k' ] ], + + [ [ '-', 8, 'n' ], + [ '-', 9, 'p' ], + [ '+', 9, 'r' ], + [ '+', 10, 's' ], + [ '+', 11, 't' ] ], + ) + +There are five hunks here. The first hunk says that the C<a> at +position 0 of the first sequence should be deleted (C<->). The second +hunk says that the C<d> at position 2 of the second sequence should +be inserted (C<+>). The third hunk says that the C<h> at position 4 +of the first sequence should be removed and replaced with the C<f> +from position 4 of the second sequence. And so on. + +C<diff> may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L</KEY GENERATION +FUNCTIONS>. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C<sdiff> + + @sdiffs = sdiff( \@seq1, \@seq2 ); + $sdiffs_ref = sdiff( \@seq1, \@seq2 ); + +C<sdiff> computes all necessary components to show two sequences +and their minimized differences side by side, just like the +Unix-utility I<sdiff> does: + + same same + before | after + old < - + - > new + +It returns a list of array refs, each pointing to an array of +display instructions. In scalar context it returns a reference +to such a list. If there are no differences, the list will have one +entry per item, each indicating that the item was unchanged. + +Display instructions consist of three elements: A modifier indicator +(C<+>: Element added, C<->: Element removed, C<u>: Element unmodified, +C<c>: Element changed) and the value of the old and new elements, to +be displayed side-by-side. + +An C<sdiff> of the following two sequences: + + a b c e h j l m n p + b c d e f j k l m r s t + +results in + + ( [ '-', 'a', '' ], + [ 'u', 'b', 'b' ], + [ 'u', 'c', 'c' ], + [ '+', '', 'd' ], + [ 'u', 'e', 'e' ], + [ 'c', 'h', 'f' ], + [ 'u', 'j', 'j' ], + [ '+', '', 'k' ], + [ 'u', 'l', 'l' ], + [ 'u', 'm', 'm' ], + [ 'c', 'n', 'r' ], + [ 'c', 'p', 's' ], + [ '+', '', 't' ], + ) + +C<sdiff> may be passed an optional third parameter; this is a CODE +reference to a key generation function. See L</KEY GENERATION +FUNCTIONS>. + +Additional parameters, if any, will be passed to the key generation +routine. + +=head2 C<compact_diff> + +C<compact_diff> is much like C<sdiff> except it returns a much more +compact description consisting of just one flat list of indices. An +example helps explain the format: + + my @a = qw( a b c e h j l m n p ); + my @b = qw( b c d e f j k l m r s t ); + @cdiff = compact_diff( \@a, \@b ); + # Returns: + # @a @b @a @b + # start start values values + ( 0, 0, # = + 0, 0, # a ! + 1, 0, # b c = b c + 3, 2, # ! d + 3, 3, # e = e + 4, 4, # f ! h + 5, 5, # j = j + 6, 6, # ! k + 6, 7, # l m = l m + 8, 9, # n p ! r s t + 10, 12, # + ); + +The 0th, 2nd, 4th, etc. entries are all indices into @seq1 (@a in the +above example) indicating where a hunk begins. The 1st, 3rd, 5th, etc. +entries are all indices into @seq2 (@b in the above example) indicating +where the same hunk begins. + +So each pair of indices (except the last pair) describes where a hunk +begins (in each sequence). Since each hunk must end at the item just +before the item that starts the next hunk, the next pair of indices can +be used to determine where the hunk ends. + +So, the first 4 entries (0..3) describe the first hunk. Entries 0 and 1 +describe where the first hunk begins (and so are always both 0). +Entries 2 and 3 describe where the next hunk begins, so subtracting 1 +from each tells us where the first hunk ends. That is, the first hunk +contains items C<$diff[0]> through C<$diff[2] - 1> of the first sequence +and contains items C<$diff[1]> through C<$diff[3] - 1> of the second +sequence. + +In other words, the first hunk consists of the following two lists of items: + + # 1st pair 2nd pair + # of indices of indices + @list1 = @a[ $cdiff[0] .. $cdiff[2]-1 ]; + @list2 = @b[ $cdiff[1] .. $cdiff[3]-1 ]; + # Hunk start Hunk end + +Note that the hunks will always alternate between those that are part of +the LCS (those that contain unchanged items) and those that contain +changes. This means that all we need to be told is whether the first +hunk is a 'same' or 'diff' hunk and we can determine which of the other +hunks contain 'same' items or 'diff' items. + +By convention, we always make the first hunk contain unchanged items. +So the 1st, 3rd, 5th, etc. hunks (all odd-numbered hunks if you start +counting from 1) all contain unchanged items. And the 2nd, 4th, 6th, +etc. hunks (all even-numbered hunks if you start counting from 1) all +contain changed items. + +Since @a and @b don't begin with the same value, the first hunk in our +example is empty (otherwise we'd violate the above convention). Note +that the first 4 index values in our example are all zero. Plug these +values into our previous code block and we get: + + @hunk1a = @a[ 0 .. 0-1 ]; + @hunk1b = @b[ 0 .. 0-1 ]; + +And C<0..-1> returns the empty list. + +Move down one pair of indices (2..5) and we get the offset ranges for +the second hunk, which contains changed items. + +Since C<@diff[2..5]> contains (0,0,1,0) in our example, the second hunk +consists of these two lists of items: + + @hunk2a = @a[ $cdiff[2] .. $cdiff[4]-1 ]; + @hunk2b = @b[ $cdiff[3] .. $cdiff[5]-1 ]; + # or + @hunk2a = @a[ 0 .. 1-1 ]; + @hunk2b = @b[ 0 .. 0-1 ]; + # or + @hunk2a = @a[ 0 .. 0 ]; + @hunk2b = @b[ 0 .. -1 ]; + # or + @hunk2a = ( 'a' ); + @hunk2b = ( ); + +That is, we would delete item 0 ('a') from @a. + +Since C<@diff[4..7]> contains (1,0,3,2) in our example, the third hunk +consists of these two lists of items: + + @hunk3a = @a[ $cdiff[4] .. $cdiff[6]-1 ]; + @hunk3a = @b[ $cdiff[5] .. $cdiff[7]-1 ]; + # or + @hunk3a = @a[ 1 .. 3-1 ]; + @hunk3a = @b[ 0 .. 2-1 ]; + # or + @hunk3a = @a[ 1 .. 2 ]; + @hunk3a = @b[ 0 .. 1 ]; + # or + @hunk3a = qw( b c ); + @hunk3a = qw( b c ); + +Note that this third hunk contains unchanged items as our convention demands. + +You can continue this process until you reach the last two indices, +which will always be the number of items in each sequence. This is +required so that subtracting one from each will give you the indices to +the last items in each sequence. + +=head2 C<traverse_sequences> + +C<traverse_sequences> used to be the most general facility provided by +this module (the new OO interface is more powerful and much easier to +use). + +Imagine that there are two arrows. Arrow A points to an element of +sequence A, and arrow B points to an element of the sequence B. +Initially, the arrows point to the first elements of the respective +sequences. C<traverse_sequences> will advance the arrows through the +sequences one element at a time, calling an appropriate user-specified +callback function before each advance. It willadvance the arrows in +such a way that if there are equal elements C<$A[$i]> and C<$B[$j]> +which are equal and which are part of the LCS, there will be some moment +during the execution of C<traverse_sequences> when arrow A is pointing +to C<$A[$i]> and arrow B is pointing to C<$B[$j]>. When this happens, +C<traverse_sequences> will call the C<MATCH> callback function and then +it will advance both arrows. + +Otherwise, one of the arrows is pointing to an element of its sequence +that is not part of the LCS. C<traverse_sequences> will advance that +arrow and will call the C<DISCARD_A> or the C<DISCARD_B> callback, +depending on which arrow it advanced. If both arrows point to elements +that are not part of the LCS, then C<traverse_sequences> will advance +one of them and call the appropriate callback, but it is not specified +which it will call. + +The arguments to C<traverse_sequences> are the two sequences to +traverse, and a hash which specifies the callback functions, like this: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + } + ); + +Callbacks for MATCH, DISCARD_A, and DISCARD_B are invoked with at least +the indices of the two arrows as their arguments. They are not expected +to return any values. If a callback is omitted from the table, it is +not called. + +Callbacks for A_FINISHED and B_FINISHED are invoked with at least the +corresponding index in A or B. + +If arrow A reaches the end of its sequence, before arrow B does, +C<traverse_sequences> will call the C<A_FINISHED> callback when it +advances arrow B, if there is such a function; if not it will call +C<DISCARD_B> instead. Similarly if arrow B finishes first. +C<traverse_sequences> returns when both arrows are at the ends of their +respective sequences. It returns true on success and false on failure. +At present there is no way to fail. + +C<traverse_sequences> may be passed an optional fourth parameter; this +is a CODE reference to a key generation function. See L</KEY GENERATION +FUNCTIONS>. + +Additional parameters, if any, will be passed to the key generation function. + +If you want to pass additional parameters to your callbacks, but don't +need a custom key generation function, you can get the default by +passing undef: + + traverse_sequences( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + }, + undef, # default key-gen + $myArgument1, + $myArgument2, + $myArgument3, + ); + +C<traverse_sequences> does not have a useful return value; you are +expected to plug in the appropriate behavior with the callback +functions. + +=head2 C<traverse_balanced> + +C<traverse_balanced> is an alternative to C<traverse_sequences>. It +uses a different algorithm to iterate through the entries in the +computed LCS. Instead of sticking to one side and showing element changes +as insertions and deletions only, it will jump back and forth between +the two sequences and report I<changes> occurring as deletions on one +side followed immediatly by an insertion on the other side. + +In addition to the C<DISCARD_A>, C<DISCARD_B>, and C<MATCH> callbacks +supported by C<traverse_sequences>, C<traverse_balanced> supports +a C<CHANGE> callback indicating that one element got C<replaced> by another: + + traverse_balanced( + \@seq1, \@seq2, + { MATCH => $callback_1, + DISCARD_A => $callback_2, + DISCARD_B => $callback_3, + CHANGE => $callback_4, + } + ); + +If no C<CHANGE> callback is specified, C<traverse_balanced> +will map C<CHANGE> events to C<DISCARD_A> and C<DISCARD_B> actions, +therefore resulting in a similar behaviour as C<traverse_sequences> +with different order of events. + +C<traverse_balanced> might be a bit slower than C<traverse_sequences>, +noticable only while processing huge amounts of data. + +The C<sdiff> function of this module +is implemented as call to C<traverse_balanced>. + +C<traverse_balanced> does not have a useful return value; you are expected to +plug in the appropriate behavior with the callback functions. + +=head1 KEY GENERATION FUNCTIONS + +Most of the functions accept an optional extra parameter. This is a +CODE reference to a key generating (hashing) function that should return +a string that uniquely identifies a given element. It should be the +case that if two elements are to be considered equal, their keys should +be the same (and the other way around). If no key generation function +is provided, the key will be the element as a string. + +By default, comparisons will use "eq" and elements will be turned into keys +using the default stringizing operator '""'. + +Where this is important is when you're comparing something other than +strings. If it is the case that you have multiple different objects +that should be considered to be equal, you should supply a key +generation function. Otherwise, you have to make sure that your arrays +contain unique references. + +For instance, consider this example: + + package Person; + + sub new + { + my $package = shift; + return bless { name => '', ssn => '', @_ }, $package; + } + + sub clone + { + my $old = shift; + my $new = bless { %$old }, ref($old); + } + + sub hash + { + return shift()->{'ssn'}; + } + + my $person1 = Person->new( name => 'Joe', ssn => '123-45-6789' ); + my $person2 = Person->new( name => 'Mary', ssn => '123-47-0000' ); + my $person3 = Person->new( name => 'Pete', ssn => '999-45-2222' ); + my $person4 = Person->new( name => 'Peggy', ssn => '123-45-9999' ); + my $person5 = Person->new( name => 'Frank', ssn => '000-45-9999' ); + +If you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4, $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +everything would work out OK (each of the objects would be converted +into a string like "Person=HASH(0x82425b0)" for comparison). + +But if you did this: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2 ); + +$person4 and $person4->clone() (which have the same name and SSN) +would be seen as different objects. If you wanted them to be considered +equivalent, you would have to pass in a key generation function: + + my $array1 = [ $person1, $person2, $person4 ]; + my $array2 = [ $person1, $person3, $person4->clone(), $person5 ]; + Algorithm::Diff::diff( $array1, $array2, \&Person::hash ); + +This would use the 'ssn' field in each Person as a comparison key, and +so would consider $person4 and $person4->clone() as equal. + +You may also pass additional parameters to the key generation function +if you wish. + +=head1 ERROR CHECKING + +If you pass these routines a non-reference and they expect a reference, +they will die with a message. + +=head1 AUTHOR + +This version released by Tye McQueen (http://perlmonks.org/?node=tye). + +=head1 LICENSE + +Parts Copyright (c) 2000-2004 Ned Konz. All rights reserved. +Parts by Tye McQueen. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl. + +=head1 MAILING LIST + +Mark-Jason still maintains a mailing list. To join a low-volume mailing +list for announcements related to diff and Algorithm::Diff, send an +empty mail message to mjd-perl-diff-request@plover.com. + +=head1 CREDITS + +Versions through 0.59 (and much of this documentation) were written by: + +Mark-Jason Dominus, mjd-perl-diff@plover.com + +This version borrows some documentation and routine names from +Mark-Jason's, but Diff.pm's code was completely replaced. + +This code was adapted from the Smalltalk code of Mario Wolczko +<mario@wolczko.com>, which is available at +ftp://st.cs.uiuc.edu/pub/Smalltalk/MANCHESTER/manchester/4.0/diff.st + +C<sdiff> and C<traverse_balanced> were written by Mike Schilli +<m@perlmeister.com>. + +The algorithm is that described in +I<A Fast Algorithm for Computing Longest Common Subsequences>, +CACM, vol.20, no.5, pp.350-353, May 1977, with a few +minor improvements to improve the speed. + +Much work was done by Ned Konz (perl@bike-nomad.com). + +The OO interface and some other changes are by Tye McQueen. + +=cut diff --git a/src/tests/Make.tests b/src/tests/Make.tests new file mode 100644 index 0000000..6880fdf --- /dev/null +++ b/src/tests/Make.tests @@ -0,0 +1,98 @@ +# -*- makefile -*- + +include $(patsubst %,$(SRCDIR)/%/Make.tests,$(TEST_SUBDIRS)) + +PROGS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_PROGS)) +TESTS = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_TESTS)) +EXTRA_GRADES = $(foreach subdir,$(TEST_SUBDIRS),$($(subdir)_EXTRA_GRADES)) + +ALLPUTS = $(addsuffix .allput,$(TESTS) $(EXTRA_GRADES)) +OUTPUTS = $(addsuffix .output,$(TESTS) $(EXTRA_GRADES)) +ERRORS = $(addsuffix .errors,$(TESTS) $(EXTRA_GRADES)) +RESULTS = $(addsuffix .result,$(TESTS) $(EXTRA_GRADES)) + +ifdef PROGS +include ../../Makefile.userprog +endif + +TIMEOUT = 60 + +clean:: + rm -f $(OUTPUTS) $(ERRORS) $(RESULTS) $(ALLPUTS) + +grade:: results + $(SRCDIR)/tests/make-grade $(SRCDIR) $< $(GRADING_FILE) | tee $@ + +# klaar@ida 2011-01-12: new rule to re-run only failed tests +# use with care, it will mess with the file dates +recheck:: os.dsk +# @echo "Cleaning all tests that failed on last run:"; + @for d in $(TESTS) $(EXTRA_GRADES); do \ + if echo PASS | cmp -s $$d.result -; then \ +# echo "TOUCH $$d"; \ + touch $$d.??????; \ + else \ +# echo "CLEAN $$d"; \ + rm -f $$d.??????; \ + fi; \ + done + @$(MAKE) check + @touch os.dsk + @echo "WARNING: Only failed tests was rerun on new version." + +check:: results + @cat results + @COUNT="`egrep '^(pass|FAIL) ' $< | wc -l | sed 's/[ ]//g;'`"; \ + FAILURES="`egrep '^FAIL ' $< | wc -l | sed 's/[ ]//g;'`"; \ + if [ $$FAILURES = 0 ]; then \ + echo "All $$COUNT tests passed."; \ + else \ + echo "$$FAILURES of $$COUNT tests failed."; \ + exit 1; \ + fi + +results: $(RESULTS) + @for d in $(TESTS) $(EXTRA_GRADES); do \ + if echo PASS | cmp -s $$d.result -; then \ + echo "pass $$d"; \ + else \ + echo "FAIL $$d"; \ + fi; \ + done > $@ + +outputs:: $(OUTPUTS) + +$(foreach prog,$(PROGS),$(eval $(prog).output: $(prog))) +$(foreach test,$(TESTS),$(eval $(test).output: $($(test)_PUTFILES))) +$(foreach test,$(TESTS),$(eval $(test).output: TEST = $(test))) + +# Prevent an environment variable VERBOSE from surprising us. +VERBOSE = + +TESTCMD = pintos -v -k -T $(TIMEOUT) +TESTCMD += $(SIMULATOR) +TESTCMD += $(PINTOSOPTS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += --fs-disk=$(FSDISK) +TESTCMD += $(foreach file,$(PUTFILES),-p $(file) -a $(notdir $(file))) +endif +ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) +TESTCMD += --swap-disk=4 +endif +TESTCMD += -- -q +TESTCMD += $(KERNELFLAGS) +ifeq ($(filter userprog, $(KERNEL_SUBDIRS)), userprog) +TESTCMD += -f +endif +TESTCMD += $(if $($(TEST)_ARGS),run '$(*F) $($(TEST)_ARGS)',run $(*F)) +TESTCMD += < /dev/null +TESTCMD += 2> $(TEST).errors $(if $(VERBOSE),|tee,>) $(TEST).allput + +# klaar@ida 2011-01-12: added "allput" to be able to strip debug +# messages before checking +%.output: os.dsk + $(TESTCMD) + @grep -v '^# .*$$' $(TEST).allput > $(TEST).output + +%.result: %.ck %.output + perl -I$(SRCDIR) $< $* $@ diff --git a/src/tests/arc4.c b/src/tests/arc4.c new file mode 100644 index 0000000..b033cc6 --- /dev/null +++ b/src/tests/arc4.c @@ -0,0 +1,53 @@ +#include <stdint.h> +#include "tests/arc4.h" + +/* Swap bytes. */ +static inline void +swap_byte (uint8_t *a, uint8_t *b) +{ + uint8_t t = *a; + *a = *b; + *b = t; +} + +void +arc4_init (struct arc4 *arc4, const void *key_, size_t size) +{ + const uint8_t *key = key_; + size_t key_idx; + uint8_t *s; + int i, j; + + s = arc4->s; + arc4->i = arc4->j = 0; + for (i = 0; i < 256; i++) + s[i] = i; + for (key_idx = 0, i = j = 0; i < 256; i++) + { + j = (j + s[i] + key[key_idx]) & 255; + swap_byte (s + i, s + j); + if (++key_idx >= size) + key_idx = 0; + } +} + +void +arc4_crypt (struct arc4 *arc4, void *buf_, size_t size) +{ + uint8_t *buf = buf_; + uint8_t *s; + uint8_t i, j; + + s = arc4->s; + i = arc4->i; + j = arc4->j; + while (size-- > 0) + { + i += 1; + j += s[i]; + swap_byte (s + i, s + j); + *buf++ ^= s[(s[i] + s[j]) & 255]; + } + arc4->i = i; + arc4->j = j; +} diff --git a/src/tests/arc4.h b/src/tests/arc4.h new file mode 100644 index 0000000..61c533a --- /dev/null +++ b/src/tests/arc4.h @@ -0,0 +1,17 @@ +#ifndef TESTS_ARC4_H +#define TESTS_ARC4_H + +#include <stddef.h> +#include <stdint.h> + +/* Alleged RC4 algorithm encryption state. */ +struct arc4 + { + uint8_t s[256]; + uint8_t i, j; + }; + +void arc4_init (struct arc4 *, const void *, size_t); +void arc4_crypt (struct arc4 *, void *, size_t); + +#endif /* tests/arc4.h */ diff --git a/src/tests/arc4.pm b/src/tests/arc4.pm new file mode 100644 index 0000000..df19216 --- /dev/null +++ b/src/tests/arc4.pm @@ -0,0 +1,29 @@ +use strict; +use warnings; + +sub arc4_init { + my ($key) = @_; + my (@s) = 0...255; + my ($j) = 0; + for my $i (0...255) { + $j = ($j + $s[$i] + ord (substr ($key, $i % length ($key), 1))) & 0xff; + @s[$i, $j] = @s[$j, $i]; + } + return (0, 0, @s); +} + +sub arc4_crypt { + my ($arc4, $buf) = @_; + my ($i, $j, @s) = @$arc4; + my ($out) = ""; + for my $c (split (//, $buf)) { + $i = ($i + 1) & 0xff; + $j = ($j + $s[$i]) & 0xff; + @s[$i, $j] = @s[$j, $i]; + $out .= chr (ord ($c) ^ $s[($s[$i] + $s[$j]) & 0xff]); + } + @$arc4 = ($i, $j, @s); + return $out; +} + +1; diff --git a/src/tests/cksum.c b/src/tests/cksum.c new file mode 100644 index 0000000..92a2995 --- /dev/null +++ b/src/tests/cksum.c @@ -0,0 +1,92 @@ +/* crctab[] and cksum() are from the `cksum' entry in SUSv3. */ + +#include <stdint.h> +#include "tests/cksum.h" + +static unsigned long crctab[] = { + 0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* This is the algorithm used by the Posix `cksum' utility. */ +unsigned long +cksum (const void *b_, size_t n) +{ + const unsigned char *b = b_; + uint32_t s = 0; + size_t i; + for (i = n; i > 0; --i) + { + unsigned char c = *b++; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + while (n != 0) + { + unsigned char c = n; + n >>= 8; + s = (s << 8) ^ crctab[(s >> 24) ^ c]; + } + return ~s; +} + +#ifdef STANDALONE_TEST +#include <stdio.h> +int +main (void) +{ + char buf[65536]; + int n = fread (buf, 1, sizeof buf, stdin); + printf ("%lu\n", cksum (buf, n)); + return 0; +} +#endif diff --git a/src/tests/cksum.h b/src/tests/cksum.h new file mode 100644 index 0000000..23a1fe9 --- /dev/null +++ b/src/tests/cksum.h @@ -0,0 +1,8 @@ +#ifndef TESTS_CKSUM_H +#define TESTS_CKSUM_H + +#include <stddef.h> + +unsigned long cksum(const void *, size_t); + +#endif /* tests/cksum.h */ diff --git a/src/tests/cksum.pm b/src/tests/cksum.pm new file mode 100644 index 0000000..73be5f2 --- /dev/null +++ b/src/tests/cksum.pm @@ -0,0 +1,87 @@ +# From the `cksum' entry in SUSv3. + +use strict; +use warnings; + +my (@crctab) = + (0x00000000, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4); + +sub cksum { + my ($b) = @_; + my ($n) = length ($b); + my ($s) = 0; + for my $i (0...$n - 1) { + my ($c) = ord (substr ($b, $i, 1)); + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + while ($n != 0) { + my ($c) = $n & 0xff; + $n >>= 8; + $s = ($s << 8) ^ $crctab[($s >> 24) ^ $c]; + $s &= 0xffff_ffff; + } + return ~$s & 0xffff_ffff; +} + +sub cksum_file { + my ($file) = @_; + open (FILE, '<', $file) or die "$file: open: $!\n"; + my ($data); + sysread (FILE, $data, -s FILE) == -s FILE or die "$file: read: $!\n"; + close (FILE); + return cksum ($data); +} + +1; diff --git a/src/tests/filesys/Grading.no-vm b/src/tests/filesys/Grading.no-vm new file mode 100644 index 0000000..ee98fc1 --- /dev/null +++ b/src/tests/filesys/Grading.no-vm @@ -0,0 +1,18 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# This project is primarily about implementing the file system, but +# all the previous functionality should work too. It's not too easy +# to screw it up, thus the emphasis. + +# 65% for extended file system features. +30% tests/filesys/extended/Rubric.functionality +15% tests/filesys/extended/Rubric.robustness +20% tests/filesys/extended/Rubric.persistence + +# 20% to not break the provided file system features. +20% tests/filesys/base/Rubric + +# 15% for the rest. +10% tests/userprog/Rubric.functionality +5% tests/userprog/Rubric.robustness diff --git a/src/tests/filesys/Grading.with-vm b/src/tests/filesys/Grading.with-vm new file mode 100644 index 0000000..e7c041e --- /dev/null +++ b/src/tests/filesys/Grading.with-vm @@ -0,0 +1,22 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# This project is primarily about implementing the file system, but +# all the previous functionality should work too. It's not too easy +# to screw it up, thus the emphasis. + +# 65% for extended file system features. +30% tests/filesys/extended/Rubric.functionality +15% tests/filesys/extended/Rubric.robustness +20% tests/filesys/extended/Rubric.persistence + +# 20% to not break the provided file system features. +20% tests/filesys/base/Rubric + +# 15% for the rest. +10% tests/userprog/Rubric.functionality +5% tests/userprog/Rubric.robustness + +# Up to 10% bonus for working VM functionality. +8% tests/vm/Rubric.functionality +2% tests/vm/Rubric.robustness diff --git a/src/tests/filesys/base/Make.tests b/src/tests/filesys/base/Make.tests new file mode 100644 index 0000000..e475222 --- /dev/null +++ b/src/tests/filesys/base/Make.tests @@ -0,0 +1,18 @@ +# -*- makefile -*- + +tests/filesys/base_TESTS = $(addprefix tests/filesys/base/,lg-create \ +lg-full lg-random lg-seq-block lg-seq-random sm-create sm-full \ +sm-random sm-seq-block sm-seq-random syn-read syn-remove syn-write) + +tests/filesys/base_PROGS = $(tests/filesys/base_TESTS) $(addprefix \ +tests/filesys/base/,child-syn-read child-syn-wrt) + +$(foreach prog,$(tests/filesys/base_PROGS), \ + $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c)) +$(foreach prog,$(tests/filesys/base_TESTS), \ + $(eval $(prog)_SRC += tests/main.c)) + +tests/filesys/base/syn-read_PUTFILES = tests/filesys/base/child-syn-read +tests/filesys/base/syn-write_PUTFILES = tests/filesys/base/child-syn-wrt + +tests/filesys/base/syn-read.output: TIMEOUT = 300 diff --git a/src/tests/filesys/base/Rubric b/src/tests/filesys/base/Rubric new file mode 100644 index 0000000..49a9d15 --- /dev/null +++ b/src/tests/filesys/base/Rubric @@ -0,0 +1,19 @@ +Functionality of base file system: +- Test basic support for small files. +1 sm-create +2 sm-full +2 sm-random +2 sm-seq-block +3 sm-seq-random + +- Test basic support for large files. +1 lg-create +2 lg-full +2 lg-random +2 lg-seq-block +3 lg-seq-random + +- Test synchronized multiprogram access to files. +4 syn-read +4 syn-write +2 syn-remove diff --git a/src/tests/filesys/base/child-syn-read.c b/src/tests/filesys/base/child-syn-read.c new file mode 100644 index 0000000..77a5e26 --- /dev/null +++ b/src/tests/filesys/base/child-syn-read.c @@ -0,0 +1,44 @@ +/* Child process for syn-read test. + Reads the contents of a test file a byte at a time, in the + hope that this will take long enough that we can get a + significant amount of contention in the kernel file system + code. */ + +#include <random.h> +#include <stdio.h> +#include <stdlib.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/filesys/base/syn-read.h" + +const char *test_name = "child-syn-read"; + +static char buf[BUF_SIZE]; + +int +main (int argc, const char *argv[]) +{ + int child_idx; + int fd; + size_t i; + + quiet = true; + + CHECK (argc == 2, "argc must be 2, actually %d", argc); + child_idx = atoi (argv[1]); + + random_init (0); + random_bytes (buf, sizeof buf); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + for (i = 0; i < sizeof buf; i++) + { + char c; + CHECK (read (fd, &c, 1) > 0, "read \"%s\"", file_name); + compare_bytes (&c, buf + i, 1, i, file_name); + } + close (fd); + + return child_idx; +} + diff --git a/src/tests/filesys/base/child-syn-wrt.c b/src/tests/filesys/base/child-syn-wrt.c new file mode 100644 index 0000000..1b52584 --- /dev/null +++ b/src/tests/filesys/base/child-syn-wrt.c @@ -0,0 +1,35 @@ +/* Child process for syn-read test. + Writes into part of a test file. Other processes will be + writing into other parts at the same time. */ + +#include <random.h> +#include <stdlib.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/filesys/base/syn-write.h" + +char buf[BUF_SIZE]; + +int +main (int argc, char *argv[]) +{ + int child_idx; + int fd; + + quiet = true; + + CHECK (argc == 2, "argc must be 2, actually %d", argc); + child_idx = atoi (argv[1]); + + random_init (0); + random_bytes (buf, sizeof buf); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + seek (fd, CHUNK_SIZE * child_idx); + CHECK (write (fd, buf + CHUNK_SIZE * child_idx, CHUNK_SIZE) > 0, + "write \"%s\"", file_name); + msg ("close \"%s\"", file_name); + close (fd); + + return child_idx; +} diff --git a/src/tests/filesys/base/full.inc b/src/tests/filesys/base/full.inc new file mode 100644 index 0000000..38a0396 --- /dev/null +++ b/src/tests/filesys/base/full.inc @@ -0,0 +1,20 @@ +/* -*- c -*- */ + +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_test_size (void) +{ + return TEST_SIZE; +} + +void +test_main (void) +{ + seq_test ("quux", + buf, sizeof buf, sizeof buf, + return_test_size, NULL); +} diff --git a/src/tests/filesys/base/lg-create.c b/src/tests/filesys/base/lg-create.c new file mode 100644 index 0000000..5c45eee --- /dev/null +++ b/src/tests/filesys/base/lg-create.c @@ -0,0 +1,5 @@ +/* Tests that create properly zeros out the contents of a fairly + large file. */ + +#define TEST_SIZE 75678 +#include "tests/filesys/create.inc" diff --git a/src/tests/filesys/base/lg-create.ck b/src/tests/filesys/base/lg-create.ck new file mode 100644 index 0000000..86b2c51 --- /dev/null +++ b/src/tests/filesys/base/lg-create.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-create) begin +(lg-create) create "blargle" +(lg-create) open "blargle" for verification +(lg-create) verified contents of "blargle" +(lg-create) close "blargle" +(lg-create) end +EOF +pass; diff --git a/src/tests/filesys/base/lg-full.c b/src/tests/filesys/base/lg-full.c new file mode 100644 index 0000000..5f7234d --- /dev/null +++ b/src/tests/filesys/base/lg-full.c @@ -0,0 +1,6 @@ +/* Writes out the contents of a fairly large file all at once, + and then reads it back to make sure that it was written + properly. */ + +#define TEST_SIZE 75678 +#include "tests/filesys/base/full.inc" diff --git a/src/tests/filesys/base/lg-full.ck b/src/tests/filesys/base/lg-full.ck new file mode 100644 index 0000000..ee6c7f9 --- /dev/null +++ b/src/tests/filesys/base/lg-full.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-full) begin +(lg-full) create "quux" +(lg-full) open "quux" +(lg-full) writing "quux" +(lg-full) close "quux" +(lg-full) open "quux" for verification +(lg-full) verified contents of "quux" +(lg-full) close "quux" +(lg-full) end +EOF +pass; diff --git a/src/tests/filesys/base/lg-random.c b/src/tests/filesys/base/lg-random.c new file mode 100644 index 0000000..b6f8873 --- /dev/null +++ b/src/tests/filesys/base/lg-random.c @@ -0,0 +1,7 @@ +/* Writes out the content of a fairly large file in random order, + then reads it back in random order to verify that it was + written properly. */ + +#define BLOCK_SIZE 512 +#define TEST_SIZE (512 * 150) +#include "tests/filesys/base/random.inc" diff --git a/src/tests/filesys/base/lg-random.ck b/src/tests/filesys/base/lg-random.ck new file mode 100644 index 0000000..dd9f1dd --- /dev/null +++ b/src/tests/filesys/base/lg-random.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-random) begin +(lg-random) create "bazzle" +(lg-random) open "bazzle" +(lg-random) write "bazzle" in random order +(lg-random) read "bazzle" in random order +(lg-random) close "bazzle" +(lg-random) end +EOF +pass; diff --git a/src/tests/filesys/base/lg-seq-block.c b/src/tests/filesys/base/lg-seq-block.c new file mode 100644 index 0000000..580c30b --- /dev/null +++ b/src/tests/filesys/base/lg-seq-block.c @@ -0,0 +1,7 @@ +/* Writes out a fairly large file sequentially, one fixed-size + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 75678 +#define BLOCK_SIZE 513 +#include "tests/filesys/base/seq-block.inc" diff --git a/src/tests/filesys/base/lg-seq-block.ck b/src/tests/filesys/base/lg-seq-block.ck new file mode 100644 index 0000000..b789081 --- /dev/null +++ b/src/tests/filesys/base/lg-seq-block.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-seq-block) begin +(lg-seq-block) create "noodle" +(lg-seq-block) open "noodle" +(lg-seq-block) writing "noodle" +(lg-seq-block) close "noodle" +(lg-seq-block) open "noodle" for verification +(lg-seq-block) verified contents of "noodle" +(lg-seq-block) close "noodle" +(lg-seq-block) end +EOF +pass; diff --git a/src/tests/filesys/base/lg-seq-random.c b/src/tests/filesys/base/lg-seq-random.c new file mode 100644 index 0000000..fbb6bba --- /dev/null +++ b/src/tests/filesys/base/lg-seq-random.c @@ -0,0 +1,6 @@ +/* Writes out a fairly large file sequentially, one random-sized + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 75678 +#include "tests/filesys/base/seq-random.inc" diff --git a/src/tests/filesys/base/lg-seq-random.ck b/src/tests/filesys/base/lg-seq-random.ck new file mode 100644 index 0000000..6b2dc82 --- /dev/null +++ b/src/tests/filesys/base/lg-seq-random.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(lg-seq-random) begin +(lg-seq-random) create "nibble" +(lg-seq-random) open "nibble" +(lg-seq-random) writing "nibble" +(lg-seq-random) close "nibble" +(lg-seq-random) open "nibble" for verification +(lg-seq-random) verified contents of "nibble" +(lg-seq-random) close "nibble" +(lg-seq-random) end +EOF +pass; diff --git a/src/tests/filesys/base/random.inc b/src/tests/filesys/base/random.inc new file mode 100644 index 0000000..eeeea68 --- /dev/null +++ b/src/tests/filesys/base/random.inc @@ -0,0 +1,59 @@ +/* -*- c -*- */ + +#include <random.h> +#include <stdio.h> +#include <string.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +#if TEST_SIZE % BLOCK_SIZE != 0 +#error TEST_SIZE must be a multiple of BLOCK_SIZE +#endif + +#define BLOCK_CNT (TEST_SIZE / BLOCK_SIZE) + +char buf[TEST_SIZE]; +int order[BLOCK_CNT]; + +void +test_main (void) +{ + const char *file_name = "bazzle"; + int fd; + size_t i; + + random_init (57); + random_bytes (buf, sizeof buf); + + for (i = 0; i < BLOCK_CNT; i++) + order[i] = i; + + CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + + msg ("write \"%s\" in random order", file_name); + shuffle (order, BLOCK_CNT, sizeof *order); + for (i = 0; i < BLOCK_CNT; i++) + { + size_t ofs = BLOCK_SIZE * order[i]; + seek (fd, ofs); + if (write (fd, buf + ofs, BLOCK_SIZE) != BLOCK_SIZE) + fail ("write %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs); + } + + msg ("read \"%s\" in random order", file_name); + shuffle (order, BLOCK_CNT, sizeof *order); + for (i = 0; i < BLOCK_CNT; i++) + { + char block[BLOCK_SIZE]; + size_t ofs = BLOCK_SIZE * order[i]; + seek (fd, ofs); + if (read (fd, block, BLOCK_SIZE) != BLOCK_SIZE) + fail ("read %d bytes at offset %zu failed", (int) BLOCK_SIZE, ofs); + compare_bytes (block, buf + ofs, BLOCK_SIZE, ofs, file_name); + } + + msg ("close \"%s\"", file_name); + close (fd); +} diff --git a/src/tests/filesys/base/seq-block.inc b/src/tests/filesys/base/seq-block.inc new file mode 100644 index 0000000..d4c1f57 --- /dev/null +++ b/src/tests/filesys/base/seq-block.inc @@ -0,0 +1,20 @@ +/* -*- c -*- */ + +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_block_size (void) +{ + return BLOCK_SIZE; +} + +void +test_main (void) +{ + seq_test ("noodle", + buf, sizeof buf, sizeof buf, + return_block_size, NULL); +} diff --git a/src/tests/filesys/base/seq-random.inc b/src/tests/filesys/base/seq-random.inc new file mode 100644 index 0000000..a4da4c5 --- /dev/null +++ b/src/tests/filesys/base/seq-random.inc @@ -0,0 +1,22 @@ +/* -*- c -*- */ + +#include <random.h> +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_random (void) +{ + return random_ulong () % 1031 + 1; +} + +void +test_main (void) +{ + random_init (-1); + seq_test ("nibble", + buf, sizeof buf, sizeof buf, + return_random, NULL); +} diff --git a/src/tests/filesys/base/sm-create.c b/src/tests/filesys/base/sm-create.c new file mode 100644 index 0000000..6b97ac1 --- /dev/null +++ b/src/tests/filesys/base/sm-create.c @@ -0,0 +1,5 @@ +/* Tests that create properly zeros out the contents of a fairly + small file. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/create.inc" diff --git a/src/tests/filesys/base/sm-create.ck b/src/tests/filesys/base/sm-create.ck new file mode 100644 index 0000000..8ca80dc --- /dev/null +++ b/src/tests/filesys/base/sm-create.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-create) begin +(sm-create) create "blargle" +(sm-create) open "blargle" for verification +(sm-create) verified contents of "blargle" +(sm-create) close "blargle" +(sm-create) end +EOF +pass; diff --git a/src/tests/filesys/base/sm-full.c b/src/tests/filesys/base/sm-full.c new file mode 100644 index 0000000..23ff3d4 --- /dev/null +++ b/src/tests/filesys/base/sm-full.c @@ -0,0 +1,6 @@ +/* Writes out the contents of a fairly small file all at once, + and then reads it back to make sure that it was written + properly. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/base/full.inc" diff --git a/src/tests/filesys/base/sm-full.ck b/src/tests/filesys/base/sm-full.ck new file mode 100644 index 0000000..2e0eb36 --- /dev/null +++ b/src/tests/filesys/base/sm-full.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-full) begin +(sm-full) create "quux" +(sm-full) open "quux" +(sm-full) writing "quux" +(sm-full) close "quux" +(sm-full) open "quux" for verification +(sm-full) verified contents of "quux" +(sm-full) close "quux" +(sm-full) end +EOF +pass; diff --git a/src/tests/filesys/base/sm-random.c b/src/tests/filesys/base/sm-random.c new file mode 100644 index 0000000..42d670f --- /dev/null +++ b/src/tests/filesys/base/sm-random.c @@ -0,0 +1,7 @@ +/* Writes out the content of a fairly small file in random order, + then reads it back in random order to verify that it was + written properly. */ + +#define BLOCK_SIZE 13 +#define TEST_SIZE (13 * 123) +#include "tests/filesys/base/random.inc" diff --git a/src/tests/filesys/base/sm-random.ck b/src/tests/filesys/base/sm-random.ck new file mode 100644 index 0000000..bda049d --- /dev/null +++ b/src/tests/filesys/base/sm-random.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-random) begin +(sm-random) create "bazzle" +(sm-random) open "bazzle" +(sm-random) write "bazzle" in random order +(sm-random) read "bazzle" in random order +(sm-random) close "bazzle" +(sm-random) end +EOF +pass; diff --git a/src/tests/filesys/base/sm-seq-block.c b/src/tests/filesys/base/sm-seq-block.c new file mode 100644 index 0000000..e368327 --- /dev/null +++ b/src/tests/filesys/base/sm-seq-block.c @@ -0,0 +1,7 @@ +/* Writes out a fairly small file sequentially, one fixed-size + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 5678 +#define BLOCK_SIZE 513 +#include "tests/filesys/base/seq-block.inc" diff --git a/src/tests/filesys/base/sm-seq-block.ck b/src/tests/filesys/base/sm-seq-block.ck new file mode 100644 index 0000000..0e2939d --- /dev/null +++ b/src/tests/filesys/base/sm-seq-block.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-seq-block) begin +(sm-seq-block) create "noodle" +(sm-seq-block) open "noodle" +(sm-seq-block) writing "noodle" +(sm-seq-block) close "noodle" +(sm-seq-block) open "noodle" for verification +(sm-seq-block) verified contents of "noodle" +(sm-seq-block) close "noodle" +(sm-seq-block) end +EOF +pass; diff --git a/src/tests/filesys/base/sm-seq-random.c b/src/tests/filesys/base/sm-seq-random.c new file mode 100644 index 0000000..89e5b71 --- /dev/null +++ b/src/tests/filesys/base/sm-seq-random.c @@ -0,0 +1,6 @@ +/* Writes out a fairly large file sequentially, one random-sized + block at a time, then reads it back to verify that it was + written properly. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/base/seq-random.inc" diff --git a/src/tests/filesys/base/sm-seq-random.ck b/src/tests/filesys/base/sm-seq-random.ck new file mode 100644 index 0000000..2fb368b --- /dev/null +++ b/src/tests/filesys/base/sm-seq-random.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(sm-seq-random) begin +(sm-seq-random) create "nibble" +(sm-seq-random) open "nibble" +(sm-seq-random) writing "nibble" +(sm-seq-random) close "nibble" +(sm-seq-random) open "nibble" for verification +(sm-seq-random) verified contents of "nibble" +(sm-seq-random) close "nibble" +(sm-seq-random) end +EOF +pass; diff --git a/src/tests/filesys/base/syn-read.c b/src/tests/filesys/base/syn-read.c new file mode 100644 index 0000000..7c36a42 --- /dev/null +++ b/src/tests/filesys/base/syn-read.c @@ -0,0 +1,31 @@ +/* Spawns 10 child processes, all of which read from the same + file and make sure that the contents are what they should + be. */ + +#include <random.h> +#include <stdio.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" +#include "tests/filesys/base/syn-read.h" + +static char buf[BUF_SIZE]; + +#define CHILD_CNT 10 + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + int fd; + + CHECK (create (file_name, sizeof buf), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + random_bytes (buf, sizeof buf); + CHECK (write (fd, buf, sizeof buf) > 0, "write \"%s\"", file_name); + msg ("close \"%s\"", file_name); + close (fd); + + exec_children ("child-syn-read", children, CHILD_CNT); + wait_children (children, CHILD_CNT); +} diff --git a/src/tests/filesys/base/syn-read.ck b/src/tests/filesys/base/syn-read.ck new file mode 100644 index 0000000..e2f68e8 --- /dev/null +++ b/src/tests/filesys/base/syn-read.ck @@ -0,0 +1,33 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-read) begin +(syn-read) create "data" +(syn-read) open "data" +(syn-read) write "data" +(syn-read) close "data" +(syn-read) exec child 1 of 10: "child-syn-read 0" +(syn-read) exec child 2 of 10: "child-syn-read 1" +(syn-read) exec child 3 of 10: "child-syn-read 2" +(syn-read) exec child 4 of 10: "child-syn-read 3" +(syn-read) exec child 5 of 10: "child-syn-read 4" +(syn-read) exec child 6 of 10: "child-syn-read 5" +(syn-read) exec child 7 of 10: "child-syn-read 6" +(syn-read) exec child 8 of 10: "child-syn-read 7" +(syn-read) exec child 9 of 10: "child-syn-read 8" +(syn-read) exec child 10 of 10: "child-syn-read 9" +(syn-read) wait for child 1 of 10 returned 0 (expected 0) +(syn-read) wait for child 2 of 10 returned 1 (expected 1) +(syn-read) wait for child 3 of 10 returned 2 (expected 2) +(syn-read) wait for child 4 of 10 returned 3 (expected 3) +(syn-read) wait for child 5 of 10 returned 4 (expected 4) +(syn-read) wait for child 6 of 10 returned 5 (expected 5) +(syn-read) wait for child 7 of 10 returned 6 (expected 6) +(syn-read) wait for child 8 of 10 returned 7 (expected 7) +(syn-read) wait for child 9 of 10 returned 8 (expected 8) +(syn-read) wait for child 10 of 10 returned 9 (expected 9) +(syn-read) end +EOF +pass; diff --git a/src/tests/filesys/base/syn-read.h b/src/tests/filesys/base/syn-read.h new file mode 100644 index 0000000..bff8082 --- /dev/null +++ b/src/tests/filesys/base/syn-read.h @@ -0,0 +1,7 @@ +#ifndef TESTS_FILESYS_BASE_SYN_READ_H +#define TESTS_FILESYS_BASE_SYN_READ_H + +#define BUF_SIZE 1024 +static const char file_name[] = "data"; + +#endif /* tests/filesys/base/syn-read.h */ diff --git a/src/tests/filesys/base/syn-remove.c b/src/tests/filesys/base/syn-remove.c new file mode 100644 index 0000000..c9ba110 --- /dev/null +++ b/src/tests/filesys/base/syn-remove.c @@ -0,0 +1,30 @@ +/* Verifies that a deleted file may still be written to and read + from. */ + +#include <random.h> +#include <string.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +char buf1[1234]; +char buf2[1234]; + +void +test_main (void) +{ + const char *file_name = "deleteme"; + int fd; + + CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + CHECK (remove (file_name), "remove \"%s\"", file_name); + random_bytes (buf1, sizeof buf1); + CHECK (write (fd, buf1, sizeof buf1) > 0, "write \"%s\"", file_name); + msg ("seek \"%s\" to 0", file_name); + seek (fd, 0); + CHECK (read (fd, buf2, sizeof buf2) > 0, "read \"%s\"", file_name); + compare_bytes (buf2, buf1, sizeof buf1, 0, file_name); + msg ("close \"%s\"", file_name); + close (fd); +} diff --git a/src/tests/filesys/base/syn-remove.ck b/src/tests/filesys/base/syn-remove.ck new file mode 100644 index 0000000..16ff11e --- /dev/null +++ b/src/tests/filesys/base/syn-remove.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-remove) begin +(syn-remove) create "deleteme" +(syn-remove) open "deleteme" +(syn-remove) remove "deleteme" +(syn-remove) write "deleteme" +(syn-remove) seek "deleteme" to 0 +(syn-remove) read "deleteme" +(syn-remove) close "deleteme" +(syn-remove) end +EOF +pass; diff --git a/src/tests/filesys/base/syn-write.c b/src/tests/filesys/base/syn-write.c new file mode 100644 index 0000000..1439862 --- /dev/null +++ b/src/tests/filesys/base/syn-write.c @@ -0,0 +1,31 @@ +/* Spawns several child processes to write out different parts of + the contents of a file and waits for them to finish. Then + reads back the file and verifies its contents. */ + +#include <random.h> +#include <stdio.h> +#include <string.h> +#include <syscall.h> +#include "tests/filesys/base/syn-write.h" +#include "tests/lib.h" +#include "tests/main.h" + +char buf1[BUF_SIZE]; +char buf2[BUF_SIZE]; + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + int fd; + + CHECK (create (file_name, sizeof buf1), "create \"%s\"", file_name); + + exec_children ("child-syn-wrt", children, CHILD_CNT); + wait_children (children, CHILD_CNT); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + CHECK (read (fd, buf1, sizeof buf1) > 0, "read \"%s\"", file_name); + random_bytes (buf2, sizeof buf2); + compare_bytes (buf1, buf2, sizeof buf1, 0, file_name); +} diff --git a/src/tests/filesys/base/syn-write.ck b/src/tests/filesys/base/syn-write.ck new file mode 100644 index 0000000..629a7a2 --- /dev/null +++ b/src/tests/filesys/base/syn-write.ck @@ -0,0 +1,32 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-write) begin +(syn-write) create "stuff" +(syn-write) exec child 1 of 10: "child-syn-wrt 0" +(syn-write) exec child 2 of 10: "child-syn-wrt 1" +(syn-write) exec child 3 of 10: "child-syn-wrt 2" +(syn-write) exec child 4 of 10: "child-syn-wrt 3" +(syn-write) exec child 5 of 10: "child-syn-wrt 4" +(syn-write) exec child 6 of 10: "child-syn-wrt 5" +(syn-write) exec child 7 of 10: "child-syn-wrt 6" +(syn-write) exec child 8 of 10: "child-syn-wrt 7" +(syn-write) exec child 9 of 10: "child-syn-wrt 8" +(syn-write) exec child 10 of 10: "child-syn-wrt 9" +(syn-write) wait for child 1 of 10 returned 0 (expected 0) +(syn-write) wait for child 2 of 10 returned 1 (expected 1) +(syn-write) wait for child 3 of 10 returned 2 (expected 2) +(syn-write) wait for child 4 of 10 returned 3 (expected 3) +(syn-write) wait for child 5 of 10 returned 4 (expected 4) +(syn-write) wait for child 6 of 10 returned 5 (expected 5) +(syn-write) wait for child 7 of 10 returned 6 (expected 6) +(syn-write) wait for child 8 of 10 returned 7 (expected 7) +(syn-write) wait for child 9 of 10 returned 8 (expected 8) +(syn-write) wait for child 10 of 10 returned 9 (expected 9) +(syn-write) open "stuff" +(syn-write) read "stuff" +(syn-write) end +EOF +pass; diff --git a/src/tests/filesys/base/syn-write.h b/src/tests/filesys/base/syn-write.h new file mode 100644 index 0000000..07a6d5a --- /dev/null +++ b/src/tests/filesys/base/syn-write.h @@ -0,0 +1,9 @@ +#ifndef TESTS_FILESYS_BASE_SYN_WRITE_H +#define TESTS_FILESYS_BASE_SYN_WRITE_H + +#define CHILD_CNT 10 +#define CHUNK_SIZE 512 +#define BUF_SIZE (CHILD_CNT * CHUNK_SIZE) +static const char file_name[] = "stuff"; + +#endif /* tests/filesys/base/syn-write.h */ diff --git a/src/tests/filesys/create.inc b/src/tests/filesys/create.inc new file mode 100644 index 0000000..4baf771 --- /dev/null +++ b/src/tests/filesys/create.inc @@ -0,0 +1,15 @@ +/* -*- c -*- */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +void +test_main (void) +{ + const char *file_name = "blargle"; + CHECK (create (file_name, TEST_SIZE), "create \"%s\"", file_name); + check_file (file_name, buf, TEST_SIZE); +} diff --git a/src/tests/filesys/extended/Make.tests b/src/tests/filesys/extended/Make.tests new file mode 100644 index 0000000..75a872b --- /dev/null +++ b/src/tests/filesys/extended/Make.tests @@ -0,0 +1,61 @@ +# -*- makefile -*- + +raw_tests = dir-empty-name dir-mk-tree dir-mkdir dir-open \ +dir-over-file dir-rm-cwd dir-rm-parent dir-rm-root dir-rm-tree \ +dir-rmdir dir-under-file dir-vine grow-create grow-dir-lg \ +grow-file-size grow-root-lg grow-root-sm grow-seq-lg grow-seq-sm \ +grow-sparse grow-tell grow-two-files syn-rw + +tests/filesys/extended_TESTS = $(patsubst %,tests/filesys/extended/%,$(raw_tests)) +tests/filesys/extended_EXTRA_GRADES = $(patsubst %,tests/filesys/extended/%-persistence,$(raw_tests)) + +tests/filesys/extended_PROGS = $(tests/filesys/extended_TESTS) \ +tests/filesys/extended/child-syn-rw tests/filesys/extended/tar + +$(foreach prog,$(tests/filesys/extended_PROGS), \ + $(eval $(prog)_SRC += $(prog).c tests/lib.c tests/filesys/seq-test.c)) +$(foreach prog,$(tests/filesys/extended_TESTS), \ + $(eval $(prog)_SRC += tests/main.c)) +$(foreach prog,$(tests/filesys/extended_TESTS), \ + $(eval $(prog)_PUTFILES += tests/filesys/extended/tar)) +# The version of GNU make 3.80 on vine barfs if this is split at +# the last comma. +$(foreach test,$(tests/filesys/extended_TESTS),$(eval $(test).output: FSDISK = tmp.dsk)) + +tests/filesys/extended/dir-mk-tree_SRC += tests/filesys/extended/mk-tree.c +tests/filesys/extended/dir-rm-tree_SRC += tests/filesys/extended/mk-tree.c + +tests/filesys/extended/syn-rw_PUTFILES += tests/filesys/extended/child-syn-rw + +tests/filesys/extended/dir-vine.output: TIMEOUT = 150 + +GETTIMEOUT = 60 + +GETCMD = pintos -v -k -T $(GETTIMEOUT) +GETCMD += $(PINTOSOPTS) +GETCMD += $(SIMULATOR) +GETCMD += --fs-disk=$(FSDISK) +GETCMD += -g fs.tar -a $(TEST).tar +ifeq ($(filter vm, $(KERNEL_SUBDIRS)), vm) +GETCMD += --swap-disk=4 +endif +GETCMD += -- -q +GETCMD += $(KERNELFLAGS) +GETCMD += run 'tar fs.tar /' +GETCMD += < /dev/null +GETCMD += 2> $(TEST)-persistence.errors $(if $(VERBOSE),|tee,>) $(TEST)-persistence.output + +tests/filesys/extended/%.output: os.dsk + rm -f tmp.dsk + pintos-mkdisk tmp.dsk 2 + $(TESTCMD) + $(GETCMD) + rm -f tmp.dsk +$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.output: tests/filesys/extended/$(raw_test).output)) +$(foreach raw_test,$(raw_tests),$(eval tests/filesys/extended/$(raw_test)-persistence.result: tests/filesys/extended/$(raw_test).result)) + +TARS = $(addsuffix .tar,$(tests/filesys/extended_TESTS)) + +clean:: + rm -f $(TARS) + rm -f tests/filesys/extended/can-rmdir-cwd diff --git a/src/tests/filesys/extended/Rubric.functionality b/src/tests/filesys/extended/Rubric.functionality new file mode 100644 index 0000000..91ed6f0 --- /dev/null +++ b/src/tests/filesys/extended/Rubric.functionality @@ -0,0 +1,26 @@ +Functionality of extended file system: +- Test directory support. +1 dir-mkdir +3 dir-mk-tree + +1 dir-rmdir +3 dir-rm-tree + +5 dir-vine + +- Test file growth. +1 grow-create +1 grow-seq-sm +3 grow-seq-lg +3 grow-sparse +3 grow-two-files +1 grow-tell +1 grow-file-size + +- Test directory growth. +1 grow-dir-lg +1 grow-root-sm +1 grow-root-lg + +- Test writing from multiple processes. +5 syn-rw diff --git a/src/tests/filesys/extended/Rubric.persistence b/src/tests/filesys/extended/Rubric.persistence new file mode 100644 index 0000000..405620a --- /dev/null +++ b/src/tests/filesys/extended/Rubric.persistence @@ -0,0 +1,24 @@ +Persistence of file system: +1 dir-empty-name-persistence +1 dir-mk-tree-persistence +1 dir-mkdir-persistence +1 dir-open-persistence +1 dir-over-file-persistence +1 dir-rm-cwd-persistence +1 dir-rm-parent-persistence +1 dir-rm-root-persistence +1 dir-rm-tree-persistence +1 dir-rmdir-persistence +1 dir-under-file-persistence +1 dir-vine-persistence +1 grow-create-persistence +1 grow-dir-lg-persistence +1 grow-file-size-persistence +1 grow-root-lg-persistence +1 grow-root-sm-persistence +1 grow-seq-lg-persistence +1 grow-seq-sm-persistence +1 grow-sparse-persistence +1 grow-tell-persistence +1 grow-two-files-persistence +1 syn-rw-persistence diff --git a/src/tests/filesys/extended/Rubric.robustness b/src/tests/filesys/extended/Rubric.robustness new file mode 100644 index 0000000..fb9f32f --- /dev/null +++ b/src/tests/filesys/extended/Rubric.robustness @@ -0,0 +1,9 @@ +Robustness of file system: +1 dir-empty-name +1 dir-open +1 dir-over-file +1 dir-under-file + +3 dir-rm-cwd +2 dir-rm-parent +1 dir-rm-root diff --git a/src/tests/filesys/extended/child-syn-rw.c b/src/tests/filesys/extended/child-syn-rw.c new file mode 100644 index 0000000..0e2217d --- /dev/null +++ b/src/tests/filesys/extended/child-syn-rw.c @@ -0,0 +1,53 @@ +/* Child process for syn-rw. + Reads from a file created by our parent process, which is + growing it. We loop until we've read the whole file + successfully. Many iterations through the loop will return 0 + bytes, because the file has not grown in the meantime. That + is, we are "busy waiting" for the file to grow. + (This test could be improved by adding a "yield" system call + and calling yield whenever we receive a 0-byte read.) */ + +#include <random.h> +#include <stdlib.h> +#include <syscall.h> +#include "tests/filesys/extended/syn-rw.h" +#include "tests/lib.h" + +const char *test_name = "child-syn-rw"; + +static char buf1[BUF_SIZE]; +static char buf2[BUF_SIZE]; + +int +main (int argc, const char *argv[]) +{ + int child_idx; + int fd; + size_t ofs; + + quiet = true; + + CHECK (argc == 2, "argc must be 2, actually %d", argc); + child_idx = atoi (argv[1]); + + random_init (0); + random_bytes (buf1, sizeof buf1); + + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + ofs = 0; + while (ofs < sizeof buf2) + { + int bytes_read = read (fd, buf2 + ofs, sizeof buf2 - ofs); + CHECK (bytes_read >= -1 && bytes_read <= (int) (sizeof buf2 - ofs), + "%zu-byte read on \"%s\" returned invalid value of %d", + sizeof buf2 - ofs, file_name, bytes_read); + if (bytes_read > 0) + { + compare_bytes (buf2 + ofs, buf1 + ofs, bytes_read, ofs, file_name); + ofs += bytes_read; + } + } + close (fd); + + return child_idx; +} diff --git a/src/tests/filesys/extended/dir-empty-name-persistence.ck b/src/tests/filesys/extended/dir-empty-name-persistence.ck new file mode 100644 index 0000000..562c451 --- /dev/null +++ b/src/tests/filesys/extended/dir-empty-name-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({}); +pass; diff --git a/src/tests/filesys/extended/dir-empty-name.c b/src/tests/filesys/extended/dir-empty-name.c new file mode 100644 index 0000000..c4859d2 --- /dev/null +++ b/src/tests/filesys/extended/dir-empty-name.c @@ -0,0 +1,12 @@ +/* Tries to create a directory named as the empty string, + which must return failure. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (!mkdir (""), "mkdir \"\" (must return false)"); +} diff --git a/src/tests/filesys/extended/dir-empty-name.ck b/src/tests/filesys/extended/dir-empty-name.ck new file mode 100644 index 0000000..d6c5621 --- /dev/null +++ b/src/tests/filesys/extended/dir-empty-name.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-empty-name) begin +(dir-empty-name) mkdir "" (must return false) +(dir-empty-name) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-mk-tree-persistence.ck b/src/tests/filesys/extended/dir-mk-tree-persistence.ck new file mode 100644 index 0000000..fb16afd --- /dev/null +++ b/src/tests/filesys/extended/dir-mk-tree-persistence.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +my ($tree); +for my $a (0...3) { + for my $b (0...2) { + for my $c (0...2) { + for my $d (0...3) { + $tree->{$a}{$b}{$c}{$d} = ['']; + } + } + } +} +check_archive ($tree); +pass; diff --git a/src/tests/filesys/extended/dir-mk-tree.c b/src/tests/filesys/extended/dir-mk-tree.c new file mode 100644 index 0000000..a714ff3 --- /dev/null +++ b/src/tests/filesys/extended/dir-mk-tree.c @@ -0,0 +1,12 @@ +/* Creates directories /0/0/0 through /3/2/2 and creates files in + the leaf directories. */ + +#include "tests/filesys/extended/mk-tree.h" +#include "tests/main.h" + +void +test_main (void) +{ + make_tree (4, 3, 3, 4); +} + diff --git a/src/tests/filesys/extended/dir-mk-tree.ck b/src/tests/filesys/extended/dir-mk-tree.ck new file mode 100644 index 0000000..a8507e2 --- /dev/null +++ b/src/tests/filesys/extended/dir-mk-tree.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-mk-tree) begin +(dir-mk-tree) creating /0/0/0/0 through /3/2/2/3... +(dir-mk-tree) open "/0/2/0/3" +(dir-mk-tree) close "/0/2/0/3" +(dir-mk-tree) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-mkdir-persistence.ck b/src/tests/filesys/extended/dir-mkdir-persistence.ck new file mode 100644 index 0000000..7682900 --- /dev/null +++ b/src/tests/filesys/extended/dir-mkdir-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({'a' => {'b' => ["\0" x 512]}}); +pass; diff --git a/src/tests/filesys/extended/dir-mkdir.c b/src/tests/filesys/extended/dir-mkdir.c new file mode 100644 index 0000000..994f41c --- /dev/null +++ b/src/tests/filesys/extended/dir-mkdir.c @@ -0,0 +1,15 @@ +/* Tests mkdir(). */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("a"), "mkdir \"a\""); + CHECK (create ("a/b", 512), "create \"a/b\""); + CHECK (chdir ("a"), "chdir \"a\""); + CHECK (open ("b") > 1, "open \"b\""); +} + diff --git a/src/tests/filesys/extended/dir-mkdir.ck b/src/tests/filesys/extended/dir-mkdir.ck new file mode 100644 index 0000000..4644f80 --- /dev/null +++ b/src/tests/filesys/extended/dir-mkdir.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-mkdir) begin +(dir-mkdir) mkdir "a" +(dir-mkdir) create "a/b" +(dir-mkdir) chdir "a" +(dir-mkdir) open "b" +(dir-mkdir) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-open-persistence.ck b/src/tests/filesys/extended/dir-open-persistence.ck new file mode 100644 index 0000000..26ff2f1 --- /dev/null +++ b/src/tests/filesys/extended/dir-open-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"xyzzy" => {}}); +pass; diff --git a/src/tests/filesys/extended/dir-open.c b/src/tests/filesys/extended/dir-open.c new file mode 100644 index 0000000..29d18b8 --- /dev/null +++ b/src/tests/filesys/extended/dir-open.c @@ -0,0 +1,21 @@ +/* Opens a directory, then tries to write to it, which must + fail. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int fd; + int retval; + + CHECK (mkdir ("xyzzy"), "mkdir \"xyzzy\""); + CHECK ((fd = open ("xyzzy")) > 1, "open \"xyzzy\""); + + msg ("write \"xyzzy\""); + retval = write (fd, "foobar", 6); + CHECK (retval == -1, + "write \"xyzzy\" (must return -1, actually %d)", retval); +} diff --git a/src/tests/filesys/extended/dir-open.ck b/src/tests/filesys/extended/dir-open.ck new file mode 100644 index 0000000..fccc563 --- /dev/null +++ b/src/tests/filesys/extended/dir-open.ck @@ -0,0 +1,20 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(dir-open) begin +(dir-open) mkdir "xyzzy" +(dir-open) open "xyzzy" +(dir-open) write "xyzzy" +(dir-open) write "xyzzy" (must return -1, actually -1) +(dir-open) end +dir-open: exit(0) +EOF +(dir-open) begin +(dir-open) mkdir "xyzzy" +(dir-open) open "xyzzy" +(dir-open) write "xyzzy" +dir-open: exit(-1) +EOF +pass; diff --git a/src/tests/filesys/extended/dir-over-file-persistence.ck b/src/tests/filesys/extended/dir-over-file-persistence.ck new file mode 100644 index 0000000..56b4ed2 --- /dev/null +++ b/src/tests/filesys/extended/dir-over-file-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"abc" => {}}); +pass; diff --git a/src/tests/filesys/extended/dir-over-file.c b/src/tests/filesys/extended/dir-over-file.c new file mode 100644 index 0000000..cdd2c62 --- /dev/null +++ b/src/tests/filesys/extended/dir-over-file.c @@ -0,0 +1,13 @@ +/* Tries to create a file with the same name as an existing + directory, which must return failure. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("abc"), "mkdir \"abc\""); + CHECK (!create ("abc", 0), "create \"abc\" (must return false)"); +} diff --git a/src/tests/filesys/extended/dir-over-file.ck b/src/tests/filesys/extended/dir-over-file.ck new file mode 100644 index 0000000..aae1c1e --- /dev/null +++ b/src/tests/filesys/extended/dir-over-file.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-over-file) begin +(dir-over-file) mkdir "abc" +(dir-over-file) create "abc" (must return false) +(dir-over-file) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-rm-cwd-persistence.ck b/src/tests/filesys/extended/dir-rm-cwd-persistence.ck new file mode 100644 index 0000000..7533570 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-cwd-persistence.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +my ($cwd_removable) = read_text_file ("tests/filesys/extended/can-rmdir-cwd"); +$cwd_removable eq 'YES' || $cwd_removable eq 'NO' or die; +check_archive ($cwd_removable eq 'YES' ? {} : {"a" => {}}); +pass; diff --git a/src/tests/filesys/extended/dir-rm-cwd.c b/src/tests/filesys/extended/dir-rm-cwd.c new file mode 100644 index 0000000..78e13de --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-cwd.c @@ -0,0 +1,75 @@ +/* Tries to remove the current directory, which may succeed or + fail. The requirements in each case are different; refer to + the assignment for details. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +static int +wrap_open (const char *name) +{ + static int fds[8], fd_cnt; + int fd, i; + + CHECK ((fd = open (name)) > 1, "open \"%s\"", name); + for (i = 0; i < fd_cnt; i++) + if (fds[i] == fd) + fail ("fd returned is not unique"); + fds[fd_cnt++] = fd; + return fd; +} + +void +test_main (void) +{ + int root_fd, a_fd0; + char name[READDIR_MAX_LEN + 1]; + + root_fd = wrap_open ("/"); + CHECK (mkdir ("a"), "mkdir \"a\""); + + a_fd0 = wrap_open ("/a"); + CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty"); + CHECK (inumber (root_fd) != inumber (a_fd0), + "\"/\" and \"/a\" must have different inumbers"); + + CHECK (chdir ("a"), "chdir \"a\""); + + msg ("try to remove \"/a\""); + if (remove ("/a")) + { + msg ("remove successful"); + + CHECK (open ("/a") == -1, "open \"/a\" (must fail)"); + CHECK (open (".") == -1, "open \".\" (must fail)"); + CHECK (open ("..") == -1, "open \"..\" (must fail)"); + CHECK (!create ("x", 512), "create \"x\" (must fail)"); + } + else + { + int a_fd1, a_fd2, a_fd3; + + msg ("remove failed"); + + CHECK (!remove ("../a"), "try to remove \"../a\" (must fail)"); + CHECK (!remove (".././a"), "try to remove \".././a\" (must fail)"); + CHECK (!remove ("/./a"), "try to remove \"/./a\" (must fail)"); + + a_fd1 = wrap_open ("/a"); + a_fd2 = wrap_open ("."); + CHECK (inumber (a_fd1) == inumber (a_fd2), + "\"/a\" and \".\" must have same inumber"); + CHECK (inumber (root_fd) != inumber (a_fd1), + "\"/\" and \"/a\" must have different inumbers"); + + CHECK (chdir ("/a"), "chdir \"/a\""); + a_fd3 = wrap_open ("."); + CHECK (inumber (a_fd3) == inumber (a_fd1), + "\".\" must have same inumber as before"); + + CHECK (chdir ("/"), "chdir \"/\""); + CHECK (!remove ("a"), "try to remove \"a\" (must fail: still open)"); + } + CHECK (!readdir (a_fd0, name), "verify \"/a\" is empty"); +} diff --git a/src/tests/filesys/extended/dir-rm-cwd.ck b/src/tests/filesys/extended/dir-rm-cwd.ck new file mode 100644 index 0000000..6fa4739 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-cwd.ck @@ -0,0 +1,51 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +my ($cwd_removable) = check_expected (IGNORE_EXIT_CODES => 1, + {NO => <<'EOF', YES => <<'EOF'}); +(dir-rm-cwd) begin +(dir-rm-cwd) open "/" +(dir-rm-cwd) mkdir "a" +(dir-rm-cwd) open "/a" +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) "/" and "/a" must have different inumbers +(dir-rm-cwd) chdir "a" +(dir-rm-cwd) try to remove "/a" +(dir-rm-cwd) remove failed +(dir-rm-cwd) try to remove "../a" (must fail) +(dir-rm-cwd) try to remove ".././a" (must fail) +(dir-rm-cwd) try to remove "/./a" (must fail) +(dir-rm-cwd) open "/a" +(dir-rm-cwd) open "." +(dir-rm-cwd) "/a" and "." must have same inumber +(dir-rm-cwd) "/" and "/a" must have different inumbers +(dir-rm-cwd) chdir "/a" +(dir-rm-cwd) open "." +(dir-rm-cwd) "." must have same inumber as before +(dir-rm-cwd) chdir "/" +(dir-rm-cwd) try to remove "a" (must fail: still open) +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) end +EOF +(dir-rm-cwd) begin +(dir-rm-cwd) open "/" +(dir-rm-cwd) mkdir "a" +(dir-rm-cwd) open "/a" +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) "/" and "/a" must have different inumbers +(dir-rm-cwd) chdir "a" +(dir-rm-cwd) try to remove "/a" +(dir-rm-cwd) remove successful +(dir-rm-cwd) open "/a" (must fail) +(dir-rm-cwd) open "." (must fail) +(dir-rm-cwd) open ".." (must fail) +(dir-rm-cwd) create "x" (must fail) +(dir-rm-cwd) verify "/a" is empty +(dir-rm-cwd) end +EOF +open (CAN_RMDIR_CWD, ">tests/filesys/extended/can-rmdir-cwd") + or die "tests/filesys/extended/can-rmdir-cwd: create: $!\n"; +print CAN_RMDIR_CWD "$cwd_removable"; +close (CAN_RMDIR_CWD); +pass; diff --git a/src/tests/filesys/extended/dir-rm-parent-persistence.ck b/src/tests/filesys/extended/dir-rm-parent-persistence.ck new file mode 100644 index 0000000..f30b04a --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-parent-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"a" => {"b" => {}}}); +pass; diff --git a/src/tests/filesys/extended/dir-rm-parent.c b/src/tests/filesys/extended/dir-rm-parent.c new file mode 100644 index 0000000..eb43f5b --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-parent.c @@ -0,0 +1,16 @@ +/* Tries to remove a parent of the current directory. This must + fail, because that directory is non-empty. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("a"), "mkdir \"a\""); + CHECK (chdir ("a"), "chdir \"a\""); + CHECK (mkdir ("b"), "mkdir \"b\""); + CHECK (chdir ("b"), "chdir \"b\""); + CHECK (!remove ("/a"), "remove \"/a\" (must fail)"); +} diff --git a/src/tests/filesys/extended/dir-rm-parent.ck b/src/tests/filesys/extended/dir-rm-parent.ck new file mode 100644 index 0000000..9fea8f2 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-parent.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rm-parent) begin +(dir-rm-parent) mkdir "a" +(dir-rm-parent) chdir "a" +(dir-rm-parent) mkdir "b" +(dir-rm-parent) chdir "b" +(dir-rm-parent) remove "/a" (must fail) +(dir-rm-parent) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-rm-root-persistence.ck b/src/tests/filesys/extended/dir-rm-root-persistence.ck new file mode 100644 index 0000000..6315107 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-root-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"a" => ["\0" x 243]}); +pass; diff --git a/src/tests/filesys/extended/dir-rm-root.c b/src/tests/filesys/extended/dir-rm-root.c new file mode 100644 index 0000000..c47f1eb --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-root.c @@ -0,0 +1,13 @@ +/* Try to remove the root directory. + This must fail. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (!remove ("/"), "remove \"/\" (must fail)"); + CHECK (create ("/a", 243), "create \"/a\""); +} diff --git a/src/tests/filesys/extended/dir-rm-root.ck b/src/tests/filesys/extended/dir-rm-root.ck new file mode 100644 index 0000000..8a69ff3 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-root.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rm-root) begin +(dir-rm-root) remove "/" (must fail) +(dir-rm-root) create "/a" +(dir-rm-root) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-rm-tree-persistence.ck b/src/tests/filesys/extended/dir-rm-tree-persistence.ck new file mode 100644 index 0000000..562c451 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-tree-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({}); +pass; diff --git a/src/tests/filesys/extended/dir-rm-tree.c b/src/tests/filesys/extended/dir-rm-tree.c new file mode 100644 index 0000000..bab41a6 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-tree.c @@ -0,0 +1,62 @@ +/* Creates directories /0/0/0 through /3/2/2 and files in the + leaf directories, then removes them. */ + +#include <stdarg.h> +#include <stdio.h> +#include <syscall.h> +#include "tests/filesys/extended/mk-tree.h" +#include "tests/lib.h" +#include "tests/main.h" + +static void remove_tree (int at, int bt, int ct, int dt); + +void +test_main (void) +{ + make_tree (4, 3, 3, 4); + remove_tree (4, 3, 3, 4); +} + +static void do_remove (const char *format, ...) PRINTF_FORMAT (1, 2); + +static void +remove_tree (int at, int bt, int ct, int dt) +{ + char try[128]; + int a, b, c, d; + + msg ("removing /0/0/0/0 through /%d/%d/%d/%d...", + at - 1, bt - 1, ct - 1, dt - 1); + quiet = true; + for (a = 0; a < at; a++) + { + for (b = 0; b < bt; b++) + { + for (c = 0; c < ct; c++) + { + for (d = 0; d < dt; d++) + do_remove ("/%d/%d/%d/%d", a, b, c, d); + do_remove ("/%d/%d/%d", a, b, c); + } + do_remove ("/%d/%d", a, b); + } + do_remove ("/%d", a); + } + quiet = false; + + snprintf (try, sizeof (try), "/%d/%d/%d/%d", at - 1, 0, ct - 1, 0); + CHECK (open (try) == -1, "open \"%s\" (must return -1)", try); +} + +static void +do_remove (const char *format, ...) +{ + char name[128]; + va_list args; + + va_start (args, format); + vsnprintf (name, sizeof name, format, args); + va_end (args); + + CHECK (remove (name), "remove \"%s\"", name); +} diff --git a/src/tests/filesys/extended/dir-rm-tree.ck b/src/tests/filesys/extended/dir-rm-tree.ck new file mode 100644 index 0000000..587b493 --- /dev/null +++ b/src/tests/filesys/extended/dir-rm-tree.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rm-tree) begin +(dir-rm-tree) creating /0/0/0/0 through /3/2/2/3... +(dir-rm-tree) open "/0/2/0/3" +(dir-rm-tree) close "/0/2/0/3" +(dir-rm-tree) removing /0/0/0/0 through /3/2/2/3... +(dir-rm-tree) open "/3/0/2/0" (must return -1) +(dir-rm-tree) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-rmdir-persistence.ck b/src/tests/filesys/extended/dir-rmdir-persistence.ck new file mode 100644 index 0000000..562c451 --- /dev/null +++ b/src/tests/filesys/extended/dir-rmdir-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({}); +pass; diff --git a/src/tests/filesys/extended/dir-rmdir.c b/src/tests/filesys/extended/dir-rmdir.c new file mode 100644 index 0000000..b3cbc6f --- /dev/null +++ b/src/tests/filesys/extended/dir-rmdir.c @@ -0,0 +1,14 @@ +/* Creates and removes a directory, then makes sure that it's + really gone. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mkdir ("a"), "mkdir \"a\""); + CHECK (remove ("a"), "rmdir \"a\""); + CHECK (!chdir ("a"), "chdir \"a\" (must return false)"); +} diff --git a/src/tests/filesys/extended/dir-rmdir.ck b/src/tests/filesys/extended/dir-rmdir.ck new file mode 100644 index 0000000..e0d8922 --- /dev/null +++ b/src/tests/filesys/extended/dir-rmdir.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-rmdir) begin +(dir-rmdir) mkdir "a" +(dir-rmdir) rmdir "a" +(dir-rmdir) chdir "a" (must return false) +(dir-rmdir) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-under-file-persistence.ck b/src/tests/filesys/extended/dir-under-file-persistence.ck new file mode 100644 index 0000000..67ca528 --- /dev/null +++ b/src/tests/filesys/extended/dir-under-file-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"abc" => ['']}); +pass; diff --git a/src/tests/filesys/extended/dir-under-file.c b/src/tests/filesys/extended/dir-under-file.c new file mode 100644 index 0000000..973a8b1 --- /dev/null +++ b/src/tests/filesys/extended/dir-under-file.c @@ -0,0 +1,13 @@ +/* Tries to create a directory with the same name as an existing + file, which must return failure. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (create ("abc", 0), "create \"abc\""); + CHECK (!mkdir ("abc"), "mkdir \"abc\" (must return false)"); +} diff --git a/src/tests/filesys/extended/dir-under-file.ck b/src/tests/filesys/extended/dir-under-file.ck new file mode 100644 index 0000000..cce23b4 --- /dev/null +++ b/src/tests/filesys/extended/dir-under-file.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-under-file) begin +(dir-under-file) create "abc" +(dir-under-file) mkdir "abc" (must return false) +(dir-under-file) end +EOF +pass; diff --git a/src/tests/filesys/extended/dir-vine-persistence.ck b/src/tests/filesys/extended/dir-vine-persistence.ck new file mode 100644 index 0000000..698ef01 --- /dev/null +++ b/src/tests/filesys/extended/dir-vine-persistence.ck @@ -0,0 +1,37 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +# The archive should look like this: +# +# 40642 dir-vine +# 42479 tar +# 0 start +# 11 start/file0 +# 0 start/dir0 +# 11 start/dir0/file1 +# 0 start/dir0/dir1 +# 11 start/dir0/dir1/file2 +# 0 start/dir0/dir1/dir2 +# 11 start/dir0/dir1/dir2/file3 +# 0 start/dir0/dir1/dir2/dir3 +# 11 start/dir0/dir1/dir2/dir3/file4 +# 0 start/dir0/dir1/dir2/dir3/dir4 +# 11 start/dir0/dir1/dir2/dir3/dir4/file5 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/file6 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/file7 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/file8 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8 +# 11 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/file9 +# 0 start/dir0/dir1/dir2/dir3/dir4/dir5/dir6/dir7/dir8/dir9 +my ($dir) = {}; +my ($root) = {"start" => $dir}; +for (my ($i) = 0; $i < 10; $i++) { + $dir->{"file$i"} = ["contents $i\n"]; + $dir = $dir->{"dir$i"} = {}; +} +check_archive ($root); +pass; diff --git a/src/tests/filesys/extended/dir-vine.c b/src/tests/filesys/extended/dir-vine.c new file mode 100644 index 0000000..8a31c38 --- /dev/null +++ b/src/tests/filesys/extended/dir-vine.c @@ -0,0 +1,85 @@ +/* Create a very deep "vine" of directories: /dir0/dir1/dir2/... + and an ordinary file in each of them, until we fill up the + disk. + + Then delete most of them, for two reasons. First, "tar" + limits file names to 100 characters (which could be extended + to 256 without much trouble). Second, a full disk has no room + for the tar archive. */ + +#include <string.h> +#include <stdio.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int i; + + msg ("creating many levels of files and directories..."); + quiet = true; + CHECK (mkdir ("start"), "mkdir \"start\""); + CHECK (chdir ("start"), "chdir \"start\""); + for (i = 0; ; i++) + { + char name[3][READDIR_MAX_LEN + 1]; + char file_name[16], dir_name[16]; + char contents[128]; + int fd; + + /* Create file. */ + snprintf (file_name, sizeof file_name, "file%d", i); + if (!create (file_name, 0)) + break; + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + snprintf (contents, sizeof contents, "contents %d\n", i); + if (write (fd, contents, strlen (contents)) != (int) strlen (contents)) + { + CHECK (remove (file_name), "remove \"%s\"", file_name); + close (fd); + break; + } + close (fd); + + /* Create directory. */ + snprintf (dir_name, sizeof dir_name, "dir%d", i); + if (!mkdir (dir_name)) + { + CHECK (remove (file_name), "remove \"%s\"", file_name); + break; + } + + /* Check for file and directory. */ + CHECK ((fd = open (".")) > 1, "open \".\""); + CHECK (readdir (fd, name[0]), "readdir \".\""); + CHECK (readdir (fd, name[1]), "readdir \".\""); + CHECK (!readdir (fd, name[2]), "readdir \".\" (should fail)"); + CHECK ((!strcmp (name[0], dir_name) && !strcmp (name[1], file_name)) + || (!strcmp (name[1], dir_name) && !strcmp (name[0], file_name)), + "names should be \"%s\" and \"%s\", " + "actually \"%s\" and \"%s\"", + file_name, dir_name, name[0], name[1]); + close (fd); + + /* Descend into directory. */ + CHECK (chdir (dir_name), "chdir \"%s\"", dir_name); + } + CHECK (i > 200, "created files and directories only to level %d", i); + quiet = false; + + msg ("removing all but top 10 levels of files and directories..."); + quiet = true; + while (i-- > 10) + { + char file_name[16], dir_name[16]; + + snprintf (file_name, sizeof file_name, "file%d", i); + snprintf (dir_name, sizeof dir_name, "dir%d", i); + CHECK (chdir (".."), "chdir \"..\""); + CHECK (remove (dir_name), "remove \"%s\"", dir_name); + CHECK (remove (file_name), "remove \"%s\"", file_name); + } + quiet = false; +} diff --git a/src/tests/filesys/extended/dir-vine.ck b/src/tests/filesys/extended/dir-vine.ck new file mode 100644 index 0000000..db452b0 --- /dev/null +++ b/src/tests/filesys/extended/dir-vine.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(dir-vine) begin +(dir-vine) creating many levels of files and directories... +(dir-vine) removing all but top 10 levels of files and directories... +(dir-vine) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-create-persistence.ck b/src/tests/filesys/extended/grow-create-persistence.ck new file mode 100644 index 0000000..bbcb24f --- /dev/null +++ b/src/tests/filesys/extended/grow-create-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"blargle" => ['']}); +pass; diff --git a/src/tests/filesys/extended/grow-create.c b/src/tests/filesys/extended/grow-create.c new file mode 100644 index 0000000..9ccc4ea --- /dev/null +++ b/src/tests/filesys/extended/grow-create.c @@ -0,0 +1,4 @@ +/* Create a file of size 0. */ + +#define TEST_SIZE 0 +#include "tests/filesys/create.inc" diff --git a/src/tests/filesys/extended/grow-create.ck b/src/tests/filesys/extended/grow-create.ck new file mode 100644 index 0000000..b2e69d1 --- /dev/null +++ b/src/tests/filesys/extended/grow-create.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-create) begin +(grow-create) create "blargle" +(grow-create) open "blargle" for verification +(grow-create) verified contents of "blargle" +(grow-create) close "blargle" +(grow-create) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-dir-lg-persistence.ck b/src/tests/filesys/extended/grow-dir-lg-persistence.ck new file mode 100644 index 0000000..989a322 --- /dev/null +++ b/src/tests/filesys/extended/grow-dir-lg-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($fs); +$fs->{'x'}{"file$_"} = [random_bytes (512)] foreach 0...49; +check_archive ($fs); +pass; diff --git a/src/tests/filesys/extended/grow-dir-lg.c b/src/tests/filesys/extended/grow-dir-lg.c new file mode 100644 index 0000000..20a194b --- /dev/null +++ b/src/tests/filesys/extended/grow-dir-lg.c @@ -0,0 +1,6 @@ +/* Creates a directory, + then creates 50 files in that directory. */ + +#define FILE_CNT 50 +#define DIRECTORY "/x" +#include "tests/filesys/extended/grow-dir.inc" diff --git a/src/tests/filesys/extended/grow-dir-lg.ck b/src/tests/filesys/extended/grow-dir-lg.ck new file mode 100644 index 0000000..ec58bd3 --- /dev/null +++ b/src/tests/filesys/extended/grow-dir-lg.ck @@ -0,0 +1,61 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-dir-lg) begin +(grow-dir-lg) mkdir /x +(grow-dir-lg) creating and checking "/x/file0" +(grow-dir-lg) creating and checking "/x/file1" +(grow-dir-lg) creating and checking "/x/file2" +(grow-dir-lg) creating and checking "/x/file3" +(grow-dir-lg) creating and checking "/x/file4" +(grow-dir-lg) creating and checking "/x/file5" +(grow-dir-lg) creating and checking "/x/file6" +(grow-dir-lg) creating and checking "/x/file7" +(grow-dir-lg) creating and checking "/x/file8" +(grow-dir-lg) creating and checking "/x/file9" +(grow-dir-lg) creating and checking "/x/file10" +(grow-dir-lg) creating and checking "/x/file11" +(grow-dir-lg) creating and checking "/x/file12" +(grow-dir-lg) creating and checking "/x/file13" +(grow-dir-lg) creating and checking "/x/file14" +(grow-dir-lg) creating and checking "/x/file15" +(grow-dir-lg) creating and checking "/x/file16" +(grow-dir-lg) creating and checking "/x/file17" +(grow-dir-lg) creating and checking "/x/file18" +(grow-dir-lg) creating and checking "/x/file19" +(grow-dir-lg) creating and checking "/x/file20" +(grow-dir-lg) creating and checking "/x/file21" +(grow-dir-lg) creating and checking "/x/file22" +(grow-dir-lg) creating and checking "/x/file23" +(grow-dir-lg) creating and checking "/x/file24" +(grow-dir-lg) creating and checking "/x/file25" +(grow-dir-lg) creating and checking "/x/file26" +(grow-dir-lg) creating and checking "/x/file27" +(grow-dir-lg) creating and checking "/x/file28" +(grow-dir-lg) creating and checking "/x/file29" +(grow-dir-lg) creating and checking "/x/file30" +(grow-dir-lg) creating and checking "/x/file31" +(grow-dir-lg) creating and checking "/x/file32" +(grow-dir-lg) creating and checking "/x/file33" +(grow-dir-lg) creating and checking "/x/file34" +(grow-dir-lg) creating and checking "/x/file35" +(grow-dir-lg) creating and checking "/x/file36" +(grow-dir-lg) creating and checking "/x/file37" +(grow-dir-lg) creating and checking "/x/file38" +(grow-dir-lg) creating and checking "/x/file39" +(grow-dir-lg) creating and checking "/x/file40" +(grow-dir-lg) creating and checking "/x/file41" +(grow-dir-lg) creating and checking "/x/file42" +(grow-dir-lg) creating and checking "/x/file43" +(grow-dir-lg) creating and checking "/x/file44" +(grow-dir-lg) creating and checking "/x/file45" +(grow-dir-lg) creating and checking "/x/file46" +(grow-dir-lg) creating and checking "/x/file47" +(grow-dir-lg) creating and checking "/x/file48" +(grow-dir-lg) creating and checking "/x/file49" +(grow-dir-lg) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-dir.inc b/src/tests/filesys/extended/grow-dir.inc new file mode 100644 index 0000000..bee0ba0 --- /dev/null +++ b/src/tests/filesys/extended/grow-dir.inc @@ -0,0 +1,41 @@ +/* -*- c -*- */ + +#include <syscall.h> +#include <stdio.h> +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[512]; + +static size_t +return_block_size (void) +{ + return sizeof buf; +} + +void +test_main (void) +{ + size_t i; + +#ifdef DIRECTORY + CHECK (mkdir (DIRECTORY), "mkdir %s", DIRECTORY); +#define DIR_PREFIX DIRECTORY "/" +#else +#define DIR_PREFIX "" +#endif + for (i = 0; i < FILE_CNT; i++) + { + char file_name[128]; + snprintf (file_name, sizeof file_name, "%sfile%zu", DIR_PREFIX, i); + + msg ("creating and checking \"%s\"", file_name); + + quiet = true; + seq_test (file_name, + buf, sizeof buf, sizeof buf, + return_block_size, NULL); + quiet = false; + } +} diff --git a/src/tests/filesys/extended/grow-file-size-persistence.ck b/src/tests/filesys/extended/grow-file-size-persistence.ck new file mode 100644 index 0000000..150f383 --- /dev/null +++ b/src/tests/filesys/extended/grow-file-size-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"testfile" => [random_bytes (2134)]}); +pass; diff --git a/src/tests/filesys/extended/grow-file-size.c b/src/tests/filesys/extended/grow-file-size.c new file mode 100644 index 0000000..3ce8588 --- /dev/null +++ b/src/tests/filesys/extended/grow-file-size.c @@ -0,0 +1,33 @@ +/* Grows a file from 0 bytes to 2,134 bytes, 37 bytes at a time, + and checks that the file's size is reported correctly at each + step. */ + +#include <syscall.h> +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[2134]; + +static size_t +return_block_size (void) +{ + return 37; +} + +static void +check_file_size (int fd, long ofs) +{ + long size = filesize (fd); + if (size != ofs) + fail ("filesize not updated properly: should be %ld, actually %ld", + ofs, size); +} + +void +test_main (void) +{ + seq_test ("testfile", + buf, sizeof buf, 0, + return_block_size, check_file_size); +} diff --git a/src/tests/filesys/extended/grow-file-size.ck b/src/tests/filesys/extended/grow-file-size.ck new file mode 100644 index 0000000..d81feff --- /dev/null +++ b/src/tests/filesys/extended/grow-file-size.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-file-size) begin +(grow-file-size) create "testfile" +(grow-file-size) open "testfile" +(grow-file-size) writing "testfile" +(grow-file-size) close "testfile" +(grow-file-size) open "testfile" for verification +(grow-file-size) verified contents of "testfile" +(grow-file-size) close "testfile" +(grow-file-size) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-root-lg-persistence.ck b/src/tests/filesys/extended/grow-root-lg-persistence.ck new file mode 100644 index 0000000..1692f46 --- /dev/null +++ b/src/tests/filesys/extended/grow-root-lg-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($fs); +$fs->{"file$_"} = [random_bytes (512)] foreach 0...49; +check_archive ($fs); +pass; diff --git a/src/tests/filesys/extended/grow-root-lg.c b/src/tests/filesys/extended/grow-root-lg.c new file mode 100644 index 0000000..d8d6c09 --- /dev/null +++ b/src/tests/filesys/extended/grow-root-lg.c @@ -0,0 +1,4 @@ +/* Creates 50 files in the root directory. */ + +#define FILE_CNT 50 +#include "tests/filesys/extended/grow-dir.inc" diff --git a/src/tests/filesys/extended/grow-root-lg.ck b/src/tests/filesys/extended/grow-root-lg.ck new file mode 100644 index 0000000..b174bc9 --- /dev/null +++ b/src/tests/filesys/extended/grow-root-lg.ck @@ -0,0 +1,60 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-root-lg) begin +(grow-root-lg) creating and checking "file0" +(grow-root-lg) creating and checking "file1" +(grow-root-lg) creating and checking "file2" +(grow-root-lg) creating and checking "file3" +(grow-root-lg) creating and checking "file4" +(grow-root-lg) creating and checking "file5" +(grow-root-lg) creating and checking "file6" +(grow-root-lg) creating and checking "file7" +(grow-root-lg) creating and checking "file8" +(grow-root-lg) creating and checking "file9" +(grow-root-lg) creating and checking "file10" +(grow-root-lg) creating and checking "file11" +(grow-root-lg) creating and checking "file12" +(grow-root-lg) creating and checking "file13" +(grow-root-lg) creating and checking "file14" +(grow-root-lg) creating and checking "file15" +(grow-root-lg) creating and checking "file16" +(grow-root-lg) creating and checking "file17" +(grow-root-lg) creating and checking "file18" +(grow-root-lg) creating and checking "file19" +(grow-root-lg) creating and checking "file20" +(grow-root-lg) creating and checking "file21" +(grow-root-lg) creating and checking "file22" +(grow-root-lg) creating and checking "file23" +(grow-root-lg) creating and checking "file24" +(grow-root-lg) creating and checking "file25" +(grow-root-lg) creating and checking "file26" +(grow-root-lg) creating and checking "file27" +(grow-root-lg) creating and checking "file28" +(grow-root-lg) creating and checking "file29" +(grow-root-lg) creating and checking "file30" +(grow-root-lg) creating and checking "file31" +(grow-root-lg) creating and checking "file32" +(grow-root-lg) creating and checking "file33" +(grow-root-lg) creating and checking "file34" +(grow-root-lg) creating and checking "file35" +(grow-root-lg) creating and checking "file36" +(grow-root-lg) creating and checking "file37" +(grow-root-lg) creating and checking "file38" +(grow-root-lg) creating and checking "file39" +(grow-root-lg) creating and checking "file40" +(grow-root-lg) creating and checking "file41" +(grow-root-lg) creating and checking "file42" +(grow-root-lg) creating and checking "file43" +(grow-root-lg) creating and checking "file44" +(grow-root-lg) creating and checking "file45" +(grow-root-lg) creating and checking "file46" +(grow-root-lg) creating and checking "file47" +(grow-root-lg) creating and checking "file48" +(grow-root-lg) creating and checking "file49" +(grow-root-lg) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-root-sm-persistence.ck b/src/tests/filesys/extended/grow-root-sm-persistence.ck new file mode 100644 index 0000000..2b0b8ab --- /dev/null +++ b/src/tests/filesys/extended/grow-root-sm-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($fs); +$fs->{"file$_"} = [random_bytes (512)] foreach 0...19; +check_archive ($fs); +pass; diff --git a/src/tests/filesys/extended/grow-root-sm.c b/src/tests/filesys/extended/grow-root-sm.c new file mode 100644 index 0000000..ee375d5 --- /dev/null +++ b/src/tests/filesys/extended/grow-root-sm.c @@ -0,0 +1,4 @@ +/* Creates 20 files in the root directory. */ + +#define FILE_CNT 20 +#include "tests/filesys/extended/grow-dir.inc" diff --git a/src/tests/filesys/extended/grow-root-sm.ck b/src/tests/filesys/extended/grow-root-sm.ck new file mode 100644 index 0000000..1aac7e9 --- /dev/null +++ b/src/tests/filesys/extended/grow-root-sm.ck @@ -0,0 +1,30 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-root-sm) begin +(grow-root-sm) creating and checking "file0" +(grow-root-sm) creating and checking "file1" +(grow-root-sm) creating and checking "file2" +(grow-root-sm) creating and checking "file3" +(grow-root-sm) creating and checking "file4" +(grow-root-sm) creating and checking "file5" +(grow-root-sm) creating and checking "file6" +(grow-root-sm) creating and checking "file7" +(grow-root-sm) creating and checking "file8" +(grow-root-sm) creating and checking "file9" +(grow-root-sm) creating and checking "file10" +(grow-root-sm) creating and checking "file11" +(grow-root-sm) creating and checking "file12" +(grow-root-sm) creating and checking "file13" +(grow-root-sm) creating and checking "file14" +(grow-root-sm) creating and checking "file15" +(grow-root-sm) creating and checking "file16" +(grow-root-sm) creating and checking "file17" +(grow-root-sm) creating and checking "file18" +(grow-root-sm) creating and checking "file19" +(grow-root-sm) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-seq-lg-persistence.ck b/src/tests/filesys/extended/grow-seq-lg-persistence.ck new file mode 100644 index 0000000..41aaae0 --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-lg-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"testme" => [random_bytes (72943)]}); +pass; diff --git a/src/tests/filesys/extended/grow-seq-lg.c b/src/tests/filesys/extended/grow-seq-lg.c new file mode 100644 index 0000000..3108d17 --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-lg.c @@ -0,0 +1,5 @@ +/* Grows a file from 0 bytes to 72,943 bytes, 1,234 bytes at a + time. */ + +#define TEST_SIZE 72943 +#include "tests/filesys/extended/grow-seq.inc" diff --git a/src/tests/filesys/extended/grow-seq-lg.ck b/src/tests/filesys/extended/grow-seq-lg.ck new file mode 100644 index 0000000..90fcd8c --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-lg.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-seq-lg) begin +(grow-seq-lg) create "testme" +(grow-seq-lg) open "testme" +(grow-seq-lg) writing "testme" +(grow-seq-lg) close "testme" +(grow-seq-lg) open "testme" for verification +(grow-seq-lg) verified contents of "testme" +(grow-seq-lg) close "testme" +(grow-seq-lg) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-seq-sm-persistence.ck b/src/tests/filesys/extended/grow-seq-sm-persistence.ck new file mode 100644 index 0000000..6cb0bd8 --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-sm-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"testme" => [random_bytes (5678)]}); +pass; diff --git a/src/tests/filesys/extended/grow-seq-sm.c b/src/tests/filesys/extended/grow-seq-sm.c new file mode 100644 index 0000000..3656e2e --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-sm.c @@ -0,0 +1,5 @@ +/* Grows a file from 0 bytes to 5,678 bytes, 1,234 bytes at a + time. */ + +#define TEST_SIZE 5678 +#include "tests/filesys/extended/grow-seq.inc" diff --git a/src/tests/filesys/extended/grow-seq-sm.ck b/src/tests/filesys/extended/grow-seq-sm.ck new file mode 100644 index 0000000..5cf4518 --- /dev/null +++ b/src/tests/filesys/extended/grow-seq-sm.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-seq-sm) begin +(grow-seq-sm) create "testme" +(grow-seq-sm) open "testme" +(grow-seq-sm) writing "testme" +(grow-seq-sm) close "testme" +(grow-seq-sm) open "testme" for verification +(grow-seq-sm) verified contents of "testme" +(grow-seq-sm) close "testme" +(grow-seq-sm) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-seq.inc b/src/tests/filesys/extended/grow-seq.inc new file mode 100644 index 0000000..1b7710c --- /dev/null +++ b/src/tests/filesys/extended/grow-seq.inc @@ -0,0 +1,20 @@ +/* -*- c -*- */ + +#include "tests/filesys/seq-test.h" +#include "tests/main.h" + +static char buf[TEST_SIZE]; + +static size_t +return_block_size (void) +{ + return 1234; +} + +void +test_main (void) +{ + seq_test ("testme", + buf, sizeof buf, 0, + return_block_size, NULL); +} diff --git a/src/tests/filesys/extended/grow-sparse-persistence.ck b/src/tests/filesys/extended/grow-sparse-persistence.ck new file mode 100644 index 0000000..3f06a5b --- /dev/null +++ b/src/tests/filesys/extended/grow-sparse-persistence.ck @@ -0,0 +1,6 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_archive ({"testfile" => ["\0" x 76543]}); +pass; diff --git a/src/tests/filesys/extended/grow-sparse.c b/src/tests/filesys/extended/grow-sparse.c new file mode 100644 index 0000000..6eab210 --- /dev/null +++ b/src/tests/filesys/extended/grow-sparse.c @@ -0,0 +1,25 @@ +/* Tests that seeking past the end of a file and writing will + properly zero out the region in between. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[76543]; + +void +test_main (void) +{ + const char *file_name = "testfile"; + char zero = 0; + int fd; + + CHECK (create (file_name, 0), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + msg ("seek \"%s\"", file_name); + seek (fd, sizeof buf - 1); + CHECK (write (fd, &zero, 1) > 0, "write \"%s\"", file_name); + msg ("close \"%s\"", file_name); + close (fd); + check_file (file_name, buf, sizeof buf); +} diff --git a/src/tests/filesys/extended/grow-sparse.ck b/src/tests/filesys/extended/grow-sparse.ck new file mode 100644 index 0000000..379ba2c --- /dev/null +++ b/src/tests/filesys/extended/grow-sparse.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-sparse) begin +(grow-sparse) create "testfile" +(grow-sparse) open "testfile" +(grow-sparse) seek "testfile" +(grow-sparse) write "testfile" +(grow-sparse) close "testfile" +(grow-sparse) open "testfile" for verification +(grow-sparse) verified contents of "testfile" +(grow-sparse) close "testfile" +(grow-sparse) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-tell-persistence.ck b/src/tests/filesys/extended/grow-tell-persistence.ck new file mode 100644 index 0000000..d93a422 --- /dev/null +++ b/src/tests/filesys/extended/grow-tell-persistence.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"foobar" => [random_bytes (2134)]}); +pass; diff --git a/src/tests/filesys/extended/grow-tell.c b/src/tests/filesys/extended/grow-tell.c new file mode 100644 index 0000000..5f5da5b --- /dev/null +++ b/src/tests/filesys/extended/grow-tell.c @@ -0,0 +1,32 @@ +/* Checks that growing a file updates the file position + correctly. */ + +#include <syscall.h> +#include "tests/filesys/seq-test.h" +#include "tests/lib.h" +#include "tests/main.h" + +static char buf[2134]; + +static size_t +return_block_size (void) +{ + return 37; +} + +static void +check_tell (int fd, long ofs) +{ + long pos = tell (fd); + if (pos != ofs) + fail ("file position not updated properly: should be %ld, actually %ld", + ofs, pos); +} + +void +test_main (void) +{ + seq_test ("foobar", + buf, sizeof buf, 0, + return_block_size, check_tell); +} diff --git a/src/tests/filesys/extended/grow-tell.ck b/src/tests/filesys/extended/grow-tell.ck new file mode 100644 index 0000000..fe94707 --- /dev/null +++ b/src/tests/filesys/extended/grow-tell.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-tell) begin +(grow-tell) create "foobar" +(grow-tell) open "foobar" +(grow-tell) writing "foobar" +(grow-tell) close "foobar" +(grow-tell) open "foobar" for verification +(grow-tell) verified contents of "foobar" +(grow-tell) close "foobar" +(grow-tell) end +EOF +pass; diff --git a/src/tests/filesys/extended/grow-two-files-persistence.ck b/src/tests/filesys/extended/grow-two-files-persistence.ck new file mode 100644 index 0000000..1c4ced1 --- /dev/null +++ b/src/tests/filesys/extended/grow-two-files-persistence.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +my ($a) = random_bytes (8143); +my ($b) = random_bytes (8143); +check_archive ({"a" => [$a], "b" => [$b]}); +pass; diff --git a/src/tests/filesys/extended/grow-two-files.c b/src/tests/filesys/extended/grow-two-files.c new file mode 100644 index 0000000..6a8fb1c --- /dev/null +++ b/src/tests/filesys/extended/grow-two-files.c @@ -0,0 +1,62 @@ +/* Grows two files in parallel and checks that their contents are + correct. */ + +#include <random.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +#define FILE_SIZE 8143 +static char buf_a[FILE_SIZE]; +static char buf_b[FILE_SIZE]; + +static void +write_some_bytes (const char *file_name, int fd, const char *buf, size_t *ofs) +{ + if (*ofs < FILE_SIZE) + { + size_t block_size = random_ulong () % (FILE_SIZE / 8) + 1; + size_t ret_val; + if (block_size > FILE_SIZE - *ofs) + block_size = FILE_SIZE - *ofs; + + ret_val = write (fd, buf + *ofs, block_size); + if (ret_val != block_size) + fail ("write %zu bytes at offset %zu in \"%s\" returned %zu", + block_size, *ofs, file_name, ret_val); + *ofs += block_size; + } +} + +void +test_main (void) +{ + int fd_a, fd_b; + size_t ofs_a = 0, ofs_b = 0; + + random_init (0); + random_bytes (buf_a, sizeof buf_a); + random_bytes (buf_b, sizeof buf_b); + + CHECK (create ("a", 0), "create \"a\""); + CHECK (create ("b", 0), "create \"b\""); + + CHECK ((fd_a = open ("a")) > 1, "open \"a\""); + CHECK ((fd_b = open ("b")) > 1, "open \"b\""); + + msg ("write \"a\" and \"b\" alternately"); + while (ofs_a < FILE_SIZE || ofs_b < FILE_SIZE) + { + write_some_bytes ("a", fd_a, buf_a, &ofs_a); + write_some_bytes ("b", fd_b, buf_b, &ofs_b); + } + + msg ("close \"a\""); + close (fd_a); + + msg ("close \"b\""); + close (fd_b); + + check_file ("a", buf_a, FILE_SIZE); + check_file ("b", buf_b, FILE_SIZE); +} diff --git a/src/tests/filesys/extended/grow-two-files.ck b/src/tests/filesys/extended/grow-two-files.ck new file mode 100644 index 0000000..b5e754a --- /dev/null +++ b/src/tests/filesys/extended/grow-two-files.ck @@ -0,0 +1,23 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(grow-two-files) begin +(grow-two-files) create "a" +(grow-two-files) create "b" +(grow-two-files) open "a" +(grow-two-files) open "b" +(grow-two-files) write "a" and "b" alternately +(grow-two-files) close "a" +(grow-two-files) close "b" +(grow-two-files) open "a" for verification +(grow-two-files) verified contents of "a" +(grow-two-files) close "a" +(grow-two-files) open "b" for verification +(grow-two-files) verified contents of "b" +(grow-two-files) close "b" +(grow-two-files) end +EOF +pass; diff --git a/src/tests/filesys/extended/mk-tree.c b/src/tests/filesys/extended/mk-tree.c new file mode 100644 index 0000000..a36bb88 --- /dev/null +++ b/src/tests/filesys/extended/mk-tree.c @@ -0,0 +1,67 @@ +/* Library function for creating a tree of directories. */ + +#include <stdio.h> +#include <syscall.h> +#include "tests/filesys/extended/mk-tree.h" +#include "tests/lib.h" + +static void do_mkdir (const char *format, ...) PRINTF_FORMAT (1, 2); +static void do_touch (const char *format, ...) PRINTF_FORMAT (1, 2); + +void +make_tree (int at, int bt, int ct, int dt) +{ + char try[128]; + int a, b, c, d; + int fd; + + msg ("creating /0/0/0/0 through /%d/%d/%d/%d...", + at - 1, bt - 1, ct - 1, dt - 1); + quiet = true; + for (a = 0; a < at; a++) + { + do_mkdir ("/%d", a); + for (b = 0; b < bt; b++) + { + do_mkdir ("/%d/%d", a, b); + for (c = 0; c < ct; c++) + { + do_mkdir ("/%d/%d/%d", a, b, c); + for (d = 0; d < dt; d++) + do_touch ("/%d/%d/%d/%d", a, b, c, d); + } + } + } + quiet = false; + + snprintf (try, sizeof try, "/%d/%d/%d/%d", 0, bt - 1, 0, dt - 1); + CHECK ((fd = open (try)) > 1, "open \"%s\"", try); + msg ("close \"%s\"", try); + close (fd); +} + +static void +do_mkdir (const char *format, ...) +{ + char dir[128]; + va_list args; + + va_start (args, format); + vsnprintf (dir, sizeof dir, format, args); + va_end (args); + + CHECK (mkdir (dir), "mkdir \"%s\"", dir); +} + +static void +do_touch (const char *format, ...) +{ + char file[128]; + va_list args; + + va_start (args, format); + vsnprintf (file, sizeof file, format, args); + va_end (args); + + CHECK (create (file, 0), "create \"%s\"", file); +} diff --git a/src/tests/filesys/extended/mk-tree.h b/src/tests/filesys/extended/mk-tree.h new file mode 100644 index 0000000..df0d5a6 --- /dev/null +++ b/src/tests/filesys/extended/mk-tree.h @@ -0,0 +1,6 @@ +#ifndef TESTS_FILESYS_EXTENDED_MK_TREE_H +#define TESTS_FILESYS_EXTENDED_MK_TREE_H + +void make_tree (int at, int bt, int ct, int dt); + +#endif /* tests/filesys/extended/mk-tree.h */ diff --git a/src/tests/filesys/extended/syn-rw-persistence.ck b/src/tests/filesys/extended/syn-rw-persistence.ck new file mode 100644 index 0000000..62d57ee --- /dev/null +++ b/src/tests/filesys/extended/syn-rw-persistence.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_archive ({"child-syn-rw" => "tests/filesys/extended/child-syn-rw", + "logfile" => [random_bytes (8 * 512)]}); +pass; diff --git a/src/tests/filesys/extended/syn-rw.c b/src/tests/filesys/extended/syn-rw.c new file mode 100644 index 0000000..657dfb5 --- /dev/null +++ b/src/tests/filesys/extended/syn-rw.c @@ -0,0 +1,35 @@ +/* Grows a file in chunks while subprocesses read the growing + file. */ + +#include <random.h> +#include <syscall.h> +#include "tests/filesys/extended/syn-rw.h" +#include "tests/lib.h" +#include "tests/main.h" + +char buf[BUF_SIZE]; + +#define CHILD_CNT 4 + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + size_t ofs; + int fd; + + CHECK (create (file_name, 0), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + + exec_children ("child-syn-rw", children, CHILD_CNT); + + random_bytes (buf, sizeof buf); + quiet = true; + for (ofs = 0; ofs < BUF_SIZE; ofs += CHUNK_SIZE) + CHECK (write (fd, buf + ofs, CHUNK_SIZE) > 0, + "write %d bytes at offset %zu in \"%s\"", + (int) CHUNK_SIZE, ofs, file_name); + quiet = false; + + wait_children (children, CHILD_CNT); +} diff --git a/src/tests/filesys/extended/syn-rw.ck b/src/tests/filesys/extended/syn-rw.ck new file mode 100644 index 0000000..ac82aa8 --- /dev/null +++ b/src/tests/filesys/extended/syn-rw.ck @@ -0,0 +1,20 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::random; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(syn-rw) begin +(syn-rw) create "logfile" +(syn-rw) open "logfile" +(syn-rw) exec child 1 of 4: "child-syn-rw 0" +(syn-rw) exec child 2 of 4: "child-syn-rw 1" +(syn-rw) exec child 3 of 4: "child-syn-rw 2" +(syn-rw) exec child 4 of 4: "child-syn-rw 3" +(syn-rw) wait for child 1 of 4 returned 0 (expected 0) +(syn-rw) wait for child 2 of 4 returned 1 (expected 1) +(syn-rw) wait for child 3 of 4 returned 2 (expected 2) +(syn-rw) wait for child 4 of 4 returned 3 (expected 3) +(syn-rw) end +EOF +pass; diff --git a/src/tests/filesys/extended/syn-rw.h b/src/tests/filesys/extended/syn-rw.h new file mode 100644 index 0000000..170aeb4 --- /dev/null +++ b/src/tests/filesys/extended/syn-rw.h @@ -0,0 +1,9 @@ +#ifndef TESTS_FILESYS_EXTENDED_SYN_RW_H +#define TESTS_FILESYS_EXTENDED_SYN_RW_H + +#define CHUNK_SIZE 8 +#define CHUNK_CNT 512 +#define BUF_SIZE (CHUNK_SIZE * CHUNK_CNT) +static const char file_name[] = "logfile"; + +#endif /* tests/filesys/extended/syn-rw.h */ diff --git a/src/tests/filesys/extended/tar.c b/src/tests/filesys/extended/tar.c new file mode 100644 index 0000000..6f70d97 --- /dev/null +++ b/src/tests/filesys/extended/tar.c @@ -0,0 +1,250 @@ +/* tar.c + + Creates a tar archive. */ + +#include <syscall.h> +#include <stdio.h> +#include <string.h> + +static void usage (void); +static bool make_tar_archive (const char *archive_name, + char *files[], size_t file_cnt); + +int +main (int argc, char *argv[]) +{ + if (argc < 3) + usage (); + + return (make_tar_archive (argv[1], argv + 2, argc - 2) + ? EXIT_SUCCESS : EXIT_FAILURE); +} + +static void +usage (void) +{ + printf ("tar, tar archive creator\n" + "Usage: tar ARCHIVE FILE...\n" + "where ARCHIVE is the tar archive to create\n" + " and FILE... is a list of files or directories to put into it.\n" + "(ARCHIVE itself will not be included in the archive, even if it\n" + "is in a directory to be archived.)\n"); + exit (EXIT_FAILURE); +} + +static bool archive_file (char file_name[], size_t file_name_size, + int archive_fd, bool *write_error); + +static bool archive_ordinary_file (const char *file_name, int file_fd, + int archive_fd, bool *write_error); +static bool archive_directory (char file_name[], size_t file_name_size, + int file_fd, int archive_fd, bool *write_error); +static bool write_header (const char *file_name, + char type_flag, int size, unsigned mode, + int archive_fd, bool *write_error); + +static bool do_write (int fd, const char *buffer, int size, bool *write_error); + +static bool +make_tar_archive (const char *archive_name, char *files[], size_t file_cnt) +{ + static const char zeros[512]; + int archive_fd; + bool success = true; + bool write_error = false; + size_t i; + + if (!create (archive_name, 0)) + { + printf ("%s: create failed\n", archive_name); + return false; + } + archive_fd = open (archive_name); + if (archive_fd < 0) + { + printf ("%s: open failed\n", archive_name); + return false; + } + + for (i = 0; i < file_cnt; i++) + { + char file_name[128]; + + strlcpy (file_name, files[i], sizeof file_name); + if (!archive_file (file_name, sizeof file_name, + archive_fd, &write_error)) + success = false; + } + + if (!do_write (archive_fd, zeros, 512, &write_error) + || !do_write (archive_fd, zeros, 512, &write_error)) + success = false; + + close (archive_fd); + + return success; +} + +static bool +archive_file (char file_name[], size_t file_name_size, + int archive_fd, bool *write_error) +{ + int file_fd = open (file_name); + if (file_fd >= 0) + { + bool success; + + if (inumber (file_fd) != inumber (archive_fd)) + { + if (!isdir (file_fd)) + success = archive_ordinary_file (file_name, file_fd, + archive_fd, write_error); + else + success = archive_directory (file_name, file_name_size, file_fd, + archive_fd, write_error); + } + else + { + /* Nothing to do: don't try to archive the archive file. */ + success = true; + } + + close (file_fd); + + return success; + } + else + { + printf ("%s: open failed\n", file_name); + return false; + } +} + +static bool +archive_ordinary_file (const char *file_name, int file_fd, + int archive_fd, bool *write_error) +{ + bool read_error = false; + bool success = true; + int file_size = filesize (file_fd); + + if (!write_header (file_name, '0', file_size, 0644, archive_fd, write_error)) + return false; + + while (file_size > 0) + { + static char buf[512]; + int chunk_size = file_size > 512 ? 512 : file_size; + int read_retval = read (file_fd, buf, chunk_size); + int bytes_read = read_retval > 0 ? read_retval : 0; + + if (bytes_read != chunk_size && !read_error) + { + printf ("%s: read error\n", file_name); + read_error = true; + success = false; + } + + memset (buf + bytes_read, 0, 512 - bytes_read); + if (!do_write (archive_fd, buf, 512, write_error)) + success = false; + + file_size -= chunk_size; + } + + return success; +} + +static bool +archive_directory (char file_name[], size_t file_name_size, int file_fd, + int archive_fd, bool *write_error) +{ + size_t dir_len; + bool success = true; + + dir_len = strlen (file_name); + if (dir_len + 1 + READDIR_MAX_LEN + 1 > file_name_size) + { + printf ("%s: file name too long\n", file_name); + return false; + } + + if (!write_header (file_name, '5', 0, 0755, archive_fd, write_error)) + return false; + + file_name[dir_len] = '/'; + while (readdir (file_fd, &file_name[dir_len + 1])) + if (!archive_file (file_name, file_name_size, archive_fd, write_error)) + success = false; + file_name[dir_len] = '\0'; + + return success; +} + +static bool +write_header (const char *file_name, + char type_flag, int size, unsigned mode, + int archive_fd, bool *write_error) +{ + static char header[512]; + unsigned chksum; + size_t i; + + memset (header, 0, sizeof header); + + /* Drop confusing and possibly dangerous prefixes from + FILE_NAME. */ + while (*file_name == '/' + || !memcmp (file_name, "./", 2) + || !memcmp (file_name, "../", 3)) + file_name = strchr (file_name, '/') + 1; + if (*file_name == '\0') + { + /* Dropped *everything* from FILE_NAME. + Should only be possible for a directory. */ + ASSERT (type_flag == '5'); + return true; + } + else if (strlen (file_name) > 99) + { + printf ("%s: file name too long\n", file_name); + return false; + } + + /* Fill in header except for final checksum. */ + strlcpy (header, file_name, 100); /* name */ + snprintf (header + 100, 8, "%07o", mode); /* mode */ + strlcpy (header + 108, "0000000", 8); /* uid */ + strlcpy (header + 116, "0000000", 8); /* gid */ + snprintf (header + 124, 12, "%011o", size); /* size */ + snprintf (header + 136, 12, "%011o", 1136102400); /* mtime (2006-01-01) */ + memset (header + 148, ' ', 8); /* chksum */ + header[156] = type_flag; /* typeflag */ + strlcpy (header + 257, "ustar", 6); /* magic */ + strlcpy (header + 263, "00", 3); /* version */ + + /* Compute and fill in final checksum. */ + chksum = 0; + for (i = 0; i < 512; i++) + chksum += (uint8_t) header[i]; + snprintf (header + 148, 8, "%07o", chksum); + + /* Write header. */ + return do_write (archive_fd, header, 512, write_error); +} + +static bool +do_write (int fd, const char *buffer, int size, bool *write_error) +{ + if (write (fd, buffer, size) == size) + return true; + else + { + if (!*write_error) + { + printf ("error writing archive\n"); + *write_error = true; + } + return false; + } +} diff --git a/src/tests/filesys/seq-test.c b/src/tests/filesys/seq-test.c new file mode 100644 index 0000000..8ce222c --- /dev/null +++ b/src/tests/filesys/seq-test.c @@ -0,0 +1,37 @@ +#include "tests/filesys/seq-test.h" +#include <random.h> +#include <syscall.h> +#include "tests/lib.h" + +void +seq_test (const char *file_name, void *buf, size_t size, size_t initial_size, + size_t (*block_size_func) (void), + void (*check_func) (int fd, long ofs)) +{ + size_t ofs; + int fd; + + random_bytes (buf, size); + CHECK (create (file_name, initial_size), "create \"%s\"", file_name); + CHECK ((fd = open (file_name)) > 1, "open \"%s\"", file_name); + + ofs = 0; + msg ("writing \"%s\"", file_name); + while (ofs < size) + { + size_t block_size = block_size_func (); + if (block_size > size - ofs) + block_size = size - ofs; + + if (write (fd, buf + ofs, block_size) != (int) block_size) + fail ("write %zu bytes at offset %zu in \"%s\" failed", + block_size, ofs, file_name); + + ofs += block_size; + if (check_func != NULL) + check_func (fd, ofs); + } + msg ("close \"%s\"", file_name); + close (fd); + check_file (file_name, buf, size); +} diff --git a/src/tests/filesys/seq-test.h b/src/tests/filesys/seq-test.h new file mode 100644 index 0000000..0697381 --- /dev/null +++ b/src/tests/filesys/seq-test.h @@ -0,0 +1,11 @@ +#ifndef TESTS_FILESYS_SEQ_TEST_H +#define TESTS_FILESYS_SEQ_TEST_H + +#include <stddef.h> + +void seq_test (const char *file_name, + void *buf, size_t size, size_t initial_size, + size_t (*block_size_func) (void), + void (*check_func) (int fd, long ofs)); + +#endif /* tests/filesys/seq-test.h */ diff --git a/src/tests/internal/list.c b/src/tests/internal/list.c new file mode 100644 index 0000000..836c69e --- /dev/null +++ b/src/tests/internal/list.c @@ -0,0 +1,174 @@ +/* Test program for lib/kernel/list.c. + + Attempts to test the list functionality that is not + sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include <debug.h> +#include <list.h> +#include <random.h> +#include <stdio.h> +#include "threads/test.h" + +/* Maximum number of elements in a linked list that we will + test. */ +#define MAX_SIZE 64 + +/* A linked list element. */ +struct value + { + struct list_elem elem; /* List element. */ + int value; /* Item value. */ + }; + +static void shuffle (struct value[], size_t); +static bool value_less (const struct list_elem *, const struct list_elem *, + void *); +static void verify_list_fwd (struct list *, int size); +static void verify_list_bkwd (struct list *, int size); + +/* Test the linked list implementation. */ +void +test (void) +{ + int size; + + printf ("testing various size lists:"); + for (size = 0; size < MAX_SIZE; size++) + { + int repeat; + + printf (" %d", size); + for (repeat = 0; repeat < 10; repeat++) + { + static struct value values[MAX_SIZE * 4]; + struct list list; + struct list_elem *e; + int i, ofs; + + /* Put values 0...SIZE in random order in VALUES. */ + for (i = 0; i < size; i++) + values[i].value = i; + shuffle (values, size); + + /* Assemble list. */ + list_init (&list); + for (i = 0; i < size; i++) + list_push_back (&list, &values[i].elem); + + /* Verify correct minimum and maximum elements. */ + e = list_min (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == 0 + : e == list_begin (&list)); + e = list_max (&list, value_less, NULL); + ASSERT (size ? list_entry (e, struct value, elem)->value == size - 1 + : e == list_begin (&list)); + + /* Sort and verify list. */ + list_sort (&list, value_less, NULL); + verify_list_fwd (&list, size); + + /* Reverse and verify list. */ + list_reverse (&list); + verify_list_bkwd (&list, size); + + /* Shuffle, insert using list_insert_ordered(), + and verify ordering. */ + shuffle (values, size); + list_init (&list); + for (i = 0; i < size; i++) + list_insert_ordered (&list, &values[i].elem, + value_less, NULL); + verify_list_fwd (&list, size); + + /* Duplicate some items, uniquify, and verify. */ + ofs = size; + for (e = list_begin (&list); e != list_end (&list); + e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + int copies = random_ulong () % 4; + while (copies-- > 0) + { + values[ofs].value = v->value; + list_insert (e, &values[ofs++].elem); + } + } + ASSERT ((size_t) ofs < sizeof values / sizeof *values); + list_unique (&list, NULL, value_less, NULL); + verify_list_fwd (&list, size); + } + } + + printf (" done\n"); + printf ("list: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (struct value *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + struct value t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns true if value A is less than value B, false + otherwise. */ +static bool +value_less (const struct list_elem *a_, const struct list_elem *b_, + void *aux UNUSED) +{ + const struct value *a = list_entry (a_, struct value, elem); + const struct value *b = list_entry (b_, struct value, elem); + + return a->value < b->value; +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in forward order. */ +static void +verify_list_fwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_begin (list); + i < size && e != list_end (list); + i++, e = list_next (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_end (list)); +} + +/* Verifies that LIST contains the values 0...SIZE when traversed + in reverse order. */ +static void +verify_list_bkwd (struct list *list, int size) +{ + struct list_elem *e; + int i; + + for (i = 0, e = list_rbegin (list); + i < size && e != list_rend (list); + i++, e = list_prev (e)) + { + struct value *v = list_entry (e, struct value, elem); + ASSERT (i == v->value); + } + ASSERT (i == size); + ASSERT (e == list_rend (list)); +} diff --git a/src/tests/internal/stdio.c b/src/tests/internal/stdio.c new file mode 100644 index 0000000..fb60cda --- /dev/null +++ b/src/tests/internal/stdio.c @@ -0,0 +1,208 @@ +/* Test program for printf() in lib/stdio.c. + + Attempts to test printf() functionality that is not + sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include <limits.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "threads/test.h" + +/* Number of failures so far. */ +static int failure_cnt; + +static void +checkf (const char *expect, const char *format, ...) +{ + char output[128]; + va_list args; + + printf ("\"%s\" -> \"%s\": ", format, expect); + + va_start (args, format); + vsnprintf (output, sizeof output, format, args); + va_end (args); + + if (strcmp (expect, output)) + { + printf ("\nFAIL: actual output \"%s\"\n", output); + failure_cnt++; + } + else + printf ("okay\n"); +} + +/* Test printf() implementation. */ +void +test (void) +{ + printf ("Testing formats:"); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("1", "%'d", 1); + checkf ("12", "%'d", 12); + checkf ("123", "%'d", 123); + checkf ("1,234", "%'d", 1234); + checkf ("12,345", "%'d", 12345); + checkf ("123,456", "%'ld", 123456L); + checkf ("1,234,567", "%'ld", 1234567L); + checkf ("12,345,678", "%'ld", 12345678L); + checkf ("123,456,789", "%'ld", 123456789L); + checkf ("1,234,567,890", "%'ld", 1234567890L); + checkf ("12,345,678,901", "%'lld", 12345678901LL); + checkf ("123,456,789,012", "%'lld", 123456789012LL); + checkf ("1,234,567,890,123", "%'lld", 1234567890123LL); + checkf ("12,345,678,901,234", "%'lld", 12345678901234LL); + checkf ("123,456,789,012,345", "%'lld", 123456789012345LL); + checkf ("1,234,567,890,123,456", "%'lld", 1234567890123456LL); + checkf ("12,345,678,901,234,567", "%'lld", 12345678901234567LL); + checkf ("123,456,789,012,345,678", "%'lld", 123456789012345678LL); + checkf ("1,234,567,890,123,456,789", "%'lld", 1234567890123456789LL); + + /* Check that commas show up in the right places, for positive + numbers. */ + checkf ("-1", "%'d", -1); + checkf ("-12", "%'d", -12); + checkf ("-123", "%'d", -123); + checkf ("-1,234", "%'d", -1234); + checkf ("-12,345", "%'d", -12345); + checkf ("-123,456", "%'ld", -123456L); + checkf ("-1,234,567", "%'ld", -1234567L); + checkf ("-12,345,678", "%'ld", -12345678L); + checkf ("-123,456,789", "%'ld", -123456789L); + checkf ("-1,234,567,890", "%'ld", -1234567890L); + checkf ("-12,345,678,901", "%'lld", -12345678901LL); + checkf ("-123,456,789,012", "%'lld", -123456789012LL); + checkf ("-1,234,567,890,123", "%'lld", -1234567890123LL); + checkf ("-12,345,678,901,234", "%'lld", -12345678901234LL); + checkf ("-123,456,789,012,345", "%'lld", -123456789012345LL); + checkf ("-1,234,567,890,123,456", "%'lld", -1234567890123456LL); + checkf ("-12,345,678,901,234,567", "%'lld", -12345678901234567LL); + checkf ("-123,456,789,012,345,678", "%'lld", -123456789012345678LL); + checkf ("-1,234,567,890,123,456,789", "%'lld", -1234567890123456789LL); + + /* Check signed integer conversions. */ + checkf (" 0", "%5d", 0); + checkf ("0 ", "%-5d", 0); + checkf (" +0", "%+5d", 0); + checkf ("+0 ", "%+-5d", 0); + checkf (" 0", "% 5d", 0); + checkf ("00000", "%05d", 0); + checkf (" ", "%5.0d", 0); + checkf (" 00", "%5.2d", 0); + checkf ("0", "%d", 0); + + checkf (" 1", "%5d", 1); + checkf ("1 ", "%-5d", 1); + checkf (" +1", "%+5d", 1); + checkf ("+1 ", "%+-5d", 1); + checkf (" 1", "% 5d", 1); + checkf ("00001", "%05d", 1); + checkf (" 1", "%5.0d", 1); + checkf (" 01", "%5.2d", 1); + checkf ("1", "%d", 1); + + checkf (" -1", "%5d", -1); + checkf ("-1 ", "%-5d", -1); + checkf (" -1", "%+5d", -1); + checkf ("-1 ", "%+-5d", -1); + checkf (" -1", "% 5d", -1); + checkf ("-0001", "%05d", -1); + checkf (" -1", "%5.0d", -1); + checkf (" -01", "%5.2d", -1); + checkf ("-1", "%d", -1); + + checkf ("12345", "%5d", 12345); + checkf ("12345", "%-5d", 12345); + checkf ("+12345", "%+5d", 12345); + checkf ("+12345", "%+-5d", 12345); + checkf (" 12345", "% 5d", 12345); + checkf ("12345", "%05d", 12345); + checkf ("12345", "%5.0d", 12345); + checkf ("12345", "%5.2d", 12345); + checkf ("12345", "%d", 12345); + + checkf ("123456", "%5d", 123456); + checkf ("123456", "%-5d", 123456); + checkf ("+123456", "%+5d", 123456); + checkf ("+123456", "%+-5d", 123456); + checkf (" 123456", "% 5d", 123456); + checkf ("123456", "%05d", 123456); + checkf ("123456", "%5.0d", 123456); + checkf ("123456", "%5.2d", 123456); + checkf ("123456", "%d", 123456); + + /* Check unsigned integer conversions. */ + checkf (" 0", "%5u", 0); + checkf (" 0", "%5o", 0); + checkf (" 0", "%5x", 0); + checkf (" 0", "%5X", 0); + checkf (" 0", "%#5o", 0); + checkf (" 0", "%#5x", 0); + checkf (" 0", "%#5X", 0); + checkf (" 00000000", "%#10.8x", 0); + + checkf (" 1", "%5u", 1); + checkf (" 1", "%5o", 1); + checkf (" 1", "%5x", 1); + checkf (" 1", "%5X", 1); + checkf (" 01", "%#5o", 1); + checkf (" 0x1", "%#5x", 1); + checkf (" 0X1", "%#5X", 1); + checkf ("0x00000001", "%#10.8x", 1); + + checkf ("123456", "%5u", 123456); + checkf ("361100", "%5o", 123456); + checkf ("1e240", "%5x", 123456); + checkf ("1E240", "%5X", 123456); + checkf ("0361100", "%#5o", 123456); + checkf ("0x1e240", "%#5x", 123456); + checkf ("0X1E240", "%#5X", 123456); + checkf ("0x0001e240", "%#10.8x", 123456); + + /* Character and string conversions. */ + checkf ("foobar", "%c%c%c%c%c%c", 'f', 'o', 'o', 'b', 'a', 'r'); + checkf (" left-right ", "%6s%s%-7s", "left", "-", "right"); + checkf ("trim", "%.4s", "trimoff"); + checkf ("%%", "%%%%"); + + /* From Cristian Cadar's automatic test case generator. */ + checkf (" abcdefgh", "%9s", "abcdefgh"); + checkf ("36657730000", "%- o", (unsigned) 036657730000); + checkf ("4139757568", "%- u", (unsigned) 4139757568UL); + checkf ("f6bfb000", "%- x", (unsigned) 0xf6bfb000); + checkf ("36657730000", "%-to", (ptrdiff_t) 036657730000); + checkf ("4139757568", "%-tu", (ptrdiff_t) 4139757568UL); + checkf ("-155209728", "%-zi", (size_t) -155209728); + checkf ("-155209728", "%-zd", (size_t) -155209728); + checkf ("036657730000", "%+#o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "%+#x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "% zi", (size_t) -155209728); + checkf ("-155209728", "% zd", (size_t) -155209728); + checkf ("4139757568", "% tu", (ptrdiff_t) 4139757568UL); + checkf ("036657730000", "% #o", (unsigned) 036657730000); + checkf ("0xf6bfb000", "% #x", (unsigned) 0xf6bfb000); + checkf ("0xf6bfb000", "%# x", (unsigned) 0xf6bfb000); + checkf ("-155209728", "%#zd", (size_t) -155209728); + checkf ("-155209728", "%0zi", (size_t) -155209728); + checkf ("4,139,757,568", "%'tu", (ptrdiff_t) 4139757568UL); + checkf ("-155,209,728", "%-'d", -155209728); + checkf ("-155209728", "%.zi", (size_t) -155209728); + checkf ("-155209728", "%zi", (size_t) -155209728); + checkf ("-155209728", "%zd", (size_t) -155209728); + checkf ("-155209728", "%+zi", (size_t) -155209728); + + if (failure_cnt == 0) + printf ("\nstdio: PASS\n"); + else + printf ("\nstdio: FAIL: %d tests failed\n", failure_cnt); +} diff --git a/src/tests/internal/stdlib.c b/src/tests/internal/stdlib.c new file mode 100644 index 0000000..ad0f0f9 --- /dev/null +++ b/src/tests/internal/stdlib.c @@ -0,0 +1,114 @@ +/* Test program for sorting and searching in lib/stdlib.c. + + Attempts to test the sorting and searching functionality that + is not sufficiently tested elsewhere in Pintos. + + This is not a test we will run on your submitted projects. + It is here for completeness. +*/ + +#undef NDEBUG +#include <debug.h> +#include <limits.h> +#include <random.h> +#include <stdlib.h> +#include <stdio.h> +#include "threads/test.h" + +/* Maximum number of elements in an array that we will test. */ +#define MAX_CNT 4096 + +static void shuffle (int[], size_t); +static int compare_ints (const void *, const void *); +static void verify_order (const int[], size_t); +static void verify_bsearch (const int[], size_t); + +/* Test sorting and searching implementations. */ +void +test (void) +{ + int cnt; + + printf ("testing various size arrays:"); + for (cnt = 0; cnt < MAX_CNT; cnt = cnt * 4 / 3 + 1) + { + int repeat; + + printf (" %zu", cnt); + for (repeat = 0; repeat < 10; repeat++) + { + static int values[MAX_CNT]; + int i; + + /* Put values 0...CNT in random order in VALUES. */ + for (i = 0; i < cnt; i++) + values[i] = i; + shuffle (values, cnt); + + /* Sort VALUES, then verify ordering. */ + qsort (values, cnt, sizeof *values, compare_ints); + verify_order (values, cnt); + verify_bsearch (values, cnt); + } + } + + printf (" done\n"); + printf ("stdlib: PASS\n"); +} + +/* Shuffles the CNT elements in ARRAY into random order. */ +static void +shuffle (int *array, size_t cnt) +{ + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + int t = array[j]; + array[j] = array[i]; + array[i] = t; + } +} + +/* Returns 1 if *A is greater than *B, + 0 if *A equals *B, + -1 if *A is less than *B. */ +static int +compare_ints (const void *a_, const void *b_) +{ + const int *a = a_; + const int *b = b_; + + return *a < *b ? -1 : *a > *b; +} + +/* Verifies that ARRAY contains the CNT ints 0...CNT-1. */ +static void +verify_order (const int *array, size_t cnt) +{ + int i; + + for (i = 0; (size_t) i < cnt; i++) + ASSERT (array[i] == i); +} + +/* Checks that bsearch() works properly in ARRAY. ARRAY must + contain the values 0...CNT-1. */ +static void +verify_bsearch (const int *array, size_t cnt) +{ + int not_in_array[] = {0, -1, INT_MAX, MAX_CNT, MAX_CNT + 1, MAX_CNT * 2}; + int i; + + /* Check that all the values in the array are found properly. */ + for (i = 0; (size_t) i < cnt; i++) + ASSERT (bsearch (&i, array, cnt, sizeof *array, compare_ints) + == array + i); + + /* Check that some values not in the array are not found. */ + not_in_array[0] = cnt; + for (i = 0; (size_t) i < sizeof not_in_array / sizeof *not_in_array; i++) + ASSERT (bsearch (¬_in_array[i], array, cnt, sizeof *array, compare_ints) + == NULL); +} diff --git a/src/tests/klaar/Make.tests b/src/tests/klaar/Make.tests new file mode 100644 index 0000000..66b097a --- /dev/null +++ b/src/tests/klaar/Make.tests @@ -0,0 +1,24 @@ +# -*- makefile -*- + +tests/%.output: FSDISK = 2 +tests/%.output: PUTFILES = $(filter-out os.dsk, $^) + +tests/klaar_TESTS = $(addprefix tests/klaar/,read-bad-buf low-mem \ +exec-corrupt) + +tests/klaar_PROGS = $(tests/klaar_TESTS) $(addprefix \ +tests/klaar/,child-simple) + +tests/klaar/read-bad-buf_SRC = tests/klaar/read-bad-buf.c tests/main.c +tests/klaar/low-mem_SRC = tests/klaar/low-mem.c tests/main.c +tests/klaar/exec-corrupt_SRC += tests/klaar/exec-corrupt.c tests/main.c + +tests/klaar/child-simple_SRC = tests/klaar/child-simple.c + +$(foreach prog,$(tests/klaar_PROGS),$(eval $(prog)_SRC += tests/lib.c)) + +tests/klaar/read-bad-buf_PUTFILES += tests/klaar/sample.txt +tests/klaar/low-mem_PUTFILES += tests/klaar/child-simple +tests/klaar/exec-corrupt_PUTFILES += tests/klaar/corrupt-elf + +tests/klaar/low-mem.output: KERNELFLAGS = -tcl=3 diff --git a/src/tests/klaar/child-simple.c b/src/tests/klaar/child-simple.c new file mode 100644 index 0000000..0d2dacf --- /dev/null +++ b/src/tests/klaar/child-simple.c @@ -0,0 +1,15 @@ +/* Child process run by exec-multiple, exec-one, wait-simple, and + wait-twice tests. + Just prints a single message and terminates. */ + +#include <stdio.h> +#include "tests/lib.h" + +const char *test_name = "child-simple"; + +int +main (void) +{ + msg ("run"); + return 81; +} diff --git a/src/tests/klaar/corrupt-elf b/src/tests/klaar/corrupt-elf Binary files differnew file mode 100644 index 0000000..456421b --- /dev/null +++ b/src/tests/klaar/corrupt-elf diff --git a/src/tests/klaar/exec-corrupt.c b/src/tests/klaar/exec-corrupt.c new file mode 100644 index 0000000..9f83692 --- /dev/null +++ b/src/tests/klaar/exec-corrupt.c @@ -0,0 +1,14 @@ +/* + Try to load a corrupt executable. + (klaar@ida) +*/ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("exec(\"corrupt-elf\"): %d", exec ("corrupt-elf")); +} diff --git a/src/tests/klaar/exec-corrupt.ck b/src/tests/klaar/exec-corrupt.ck new file mode 100644 index 0000000..d66eeee --- /dev/null +++ b/src/tests/klaar/exec-corrupt.ck @@ -0,0 +1,31 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF', <<'EOF', <<'EOF']); +(exec-corrupt) begin +load: corrupt-elf: error loading executable +(exec-corrupt) exec("corrupt-elf"): -1 +(exec-corrupt) end +exec-corrupt: exit(0) +EOF +(exec-corrupt) begin +(exec-corrupt) exec("corrupt-elf"): -1 +(exec-corrupt) end +exec-corrupt: exit(0) +EOF +(exec-corrupt) begin +load: corrupt-elf: error loading executable +corrupt-elf: exit(-1) +(exec-corrupt) exec("corrupt-elf"): -1 +(exec-corrupt) end +exec-corrupt: exit(0) +EOF +(exec-corrupt) begin +load: corrupt-elf: error loading executable +(exec-corrupt) exec("corrupt-elf"): -1 +corrupt-elf: exit(-1) +(exec-corrupt) end +exec-corrupt: exit(0) +EOF +pass; diff --git a/src/tests/klaar/low-mem.c b/src/tests/klaar/low-mem.c new file mode 100644 index 0000000..8f1da3e --- /dev/null +++ b/src/tests/klaar/low-mem.c @@ -0,0 +1,15 @@ +/* + Simulate a failure in thread_create. + A real reason for failure could be low memory. + (klaar@ida) +*/ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("exec(\"child-simple\"): %d", exec ("child-simple")); +} diff --git a/src/tests/klaar/low-mem.ck b/src/tests/klaar/low-mem.ck new file mode 100644 index 0000000..f128c30 --- /dev/null +++ b/src/tests/klaar/low-mem.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(low-mem) begin +(low-mem) exec("child-simple"): -1 +(low-mem) end +low-mem: exit(0) +EOF +pass; diff --git a/src/tests/klaar/read-bad-buf.c b/src/tests/klaar/read-bad-buf.c new file mode 100644 index 0000000..1e41562 --- /dev/null +++ b/src/tests/klaar/read-bad-buf.c @@ -0,0 +1,24 @@ +/* Passes an semingly valid pointer to the read system call. + The pointer will however span invalid pages. + The process must be terminated with -1 exit code. + (klaar@ida) +*/ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +char global; /* allocated with process image */ + +void +test_main (void) +{ + int handle; + char local; /* allocated on process stack */ + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + /* buffer will start and end at valid addresses ... */ + read (handle, (char *)&global, &local - &global + 1); + fail ("should not have survived read()"); +} diff --git a/src/tests/klaar/read-bad-buf.ck b/src/tests/klaar/read-bad-buf.ck new file mode 100644 index 0000000..22adc50 --- /dev/null +++ b/src/tests/klaar/read-bad-buf.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(read-bad-buf) begin +(read-bad-buf) open "sample.txt" +(read-bad-buf) end +read-bad-buf: exit(0) +EOF +(read-bad-buf) begin +(read-bad-buf) open "sample.txt" +read-bad-buf: exit(-1) +EOF +pass; diff --git a/src/tests/klaar/sample.txt b/src/tests/klaar/sample.txt new file mode 100644 index 0000000..5050fec --- /dev/null +++ b/src/tests/klaar/sample.txt @@ -0,0 +1,4 @@ +"Amazing Electronic Fact: If you scuffed your feet long enough without + touching anything, you would build up so many electrons that your + finger would explode! But this is nothing to worry about unless you + have carpeting." --Dave Barry diff --git a/src/tests/lib.c b/src/tests/lib.c new file mode 100644 index 0000000..ee36505 --- /dev/null +++ b/src/tests/lib.c @@ -0,0 +1,196 @@ +#include "tests/lib.h" +#include <random.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <syscall.h> + +const char *test_name; +bool quiet = false; + +static void +vmsg (const char *format, va_list args, const char *suffix) +{ + /* We go to some trouble to stuff the entire message into a + single buffer and output it in a single system call, because + that'll (typically) ensure that it gets sent to the console + atomically. Otherwise kernel messages like "foo: exit(0)" + can end up being interleaved if we're unlucky. */ + static char buf[1024]; + + snprintf (buf, sizeof buf, "(%s) ", test_name); + vsnprintf (buf + strlen (buf), sizeof buf - strlen (buf), format, args); + strlcpy (buf + strlen (buf), suffix, sizeof buf - strlen (buf)); + write (STDOUT_FILENO, buf, strlen (buf)); +} + +void +msg (const char *format, ...) +{ + va_list args; + + if (quiet) + return; + va_start (args, format); + vmsg (format, args, "\n"); + va_end (args); +} + +void +fail (const char *format, ...) +{ + va_list args; + + va_start (args, format); + vmsg (format, args, ": FAILED\n"); + va_end (args); + + exit (1); +} + +static void +swap (void *a_, void *b_, size_t size) +{ + uint8_t *a = a_; + uint8_t *b = b_; + size_t i; + + for (i = 0; i < size; i++) + { + uint8_t t = a[i]; + a[i] = b[i]; + b[i] = t; + } +} + +void +shuffle (void *buf_, size_t cnt, size_t size) +{ + char *buf = buf_; + size_t i; + + for (i = 0; i < cnt; i++) + { + size_t j = i + random_ulong () % (cnt - i); + swap (buf + i * size, buf + j * size, size); + } +} + +void +exec_children (const char *child_name, pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + char cmd_line[128]; + snprintf (cmd_line, sizeof cmd_line, "%s %zu", child_name, i); + CHECK ((pids[i] = exec (cmd_line)) != PID_ERROR, + "exec child %zu of %zu: \"%s\"", i + 1, child_cnt, cmd_line); + } +} + +void +wait_children (pid_t pids[], size_t child_cnt) +{ + size_t i; + + for (i = 0; i < child_cnt; i++) + { + int status = wait (pids[i]); + CHECK (status == (int) i, + "wait for child %zu of %zu returned %d (expected %zu)", + i + 1, child_cnt, status, i); + } +} + +void +check_file_handle (int fd, + const char *file_name, const void *buf_, size_t size) +{ + const char *buf = buf_; + size_t ofs = 0; + size_t file_size; + + /* Warn about file of wrong size. Don't fail yet because we + may still be able to get more information by reading the + file. */ + file_size = filesize (fd); + if (file_size != size) + msg ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + /* Read the file block-by-block, comparing data as we go. */ + while (ofs < size) + { + char block[512]; + size_t block_size, ret_val; + + block_size = size - ofs; + if (block_size > sizeof block) + block_size = sizeof block; + + ret_val = read (fd, block, block_size); + if (ret_val != block_size) + fail ("read of %zu bytes at offset %zu in \"%s\" returned %zu", + block_size, ofs, file_name, ret_val); + + compare_bytes (block, buf + ofs, block_size, ofs, file_name); + ofs += block_size; + } + + /* Now fail due to wrong file size. */ + if (file_size != size) + fail ("size of %s (%zu) differs from expected (%zu)", + file_name, file_size, size); + + msg ("verified contents of \"%s\"", file_name); +} + +void +check_file (const char *file_name, const void *buf, size_t size) +{ + int fd; + + CHECK ((fd = open (file_name)) > 1, "open \"%s\" for verification", + file_name); + check_file_handle (fd, file_name, buf, size); + msg ("close \"%s\"", file_name); + close (fd); +} + +void +compare_bytes (const void *read_data_, const void *expected_data_, size_t size, + size_t ofs, const char *file_name) +{ + const uint8_t *read_data = read_data_; + const uint8_t *expected_data = expected_data_; + size_t i, j; + size_t show_cnt; + + if (!memcmp (read_data, expected_data, size)) + return; + + for (i = 0; i < size; i++) + if (read_data[i] != expected_data[i]) + break; + for (j = i + 1; j < size; j++) + if (read_data[j] == expected_data[j]) + break; + + quiet = false; + msg ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected.", j - i, ofs + i, file_name); + show_cnt = j - i; + if (j - i > 64) + { + show_cnt = 64; + msg ("Showing first differing %zu bytes.", show_cnt); + } + msg ("Data actually read:"); + hex_dump (ofs + i, read_data + i, show_cnt, true); + msg ("Expected data:"); + hex_dump (ofs + i, expected_data + i, show_cnt, true); + fail ("%zu bytes read starting at offset %zu in \"%s\" differ " + "from expected", j - i, ofs + i, file_name); +} diff --git a/src/tests/lib.h b/src/tests/lib.h new file mode 100644 index 0000000..648327b --- /dev/null +++ b/src/tests/lib.h @@ -0,0 +1,50 @@ +#ifndef TESTS_LIB_H +#define TESTS_LIB_H + +#include <debug.h> +#include <stdbool.h> +#include <stddef.h> +#include <syscall.h> + +extern const char *test_name; +extern bool quiet; + +void msg (const char *, ...) PRINTF_FORMAT (1, 2); +void fail (const char *, ...) PRINTF_FORMAT (1, 2) NO_RETURN; + +/* Takes an expression to test for SUCCESS and a message, which + may include printf-style arguments. Logs the message, then + tests the expression. If it is zero, indicating failure, + emits the message as a failure. + + Somewhat tricky to use: + + - SUCCESS must not have side effects that affect the + message, because that will cause the original message and + the failure message to differ. + + - The message must not have side effects of its own, because + it will be printed twice on failure, or zero times on + success if quiet is set. */ +#define CHECK(SUCCESS, ...) \ + do \ + { \ + msg (__VA_ARGS__); \ + if (!(SUCCESS)) \ + fail (__VA_ARGS__); \ + } \ + while (0) + +void shuffle (void *, size_t cnt, size_t size); + +void exec_children (const char *child_name, pid_t pids[], size_t child_cnt); +void wait_children (pid_t pids[], size_t child_cnt); + +void check_file_handle (int fd, const char *file_name, + const void *buf_, size_t filesize); +void check_file (const char *file_name, const void *buf, size_t filesize); + +void compare_bytes (const void *read_data, const void *expected_data, + size_t size, size_t ofs, const char *file_name); + +#endif /* test/lib.h */ diff --git a/src/tests/lib.pm b/src/tests/lib.pm new file mode 100644 index 0000000..bc37ae5 --- /dev/null +++ b/src/tests/lib.pm @@ -0,0 +1,19 @@ +use strict; +use warnings; + +use tests::random; + +sub shuffle { + my ($in, $cnt, $sz) = @_; + $cnt * $sz == length $in or die; + my (@a) = 0...$cnt - 1; + for my $i (0...$cnt - 1) { + my ($j) = $i + random_ulong () % ($cnt - $i); + @a[$i, $j] = @a[$j, $i]; + } + my ($out) = ""; + $out .= substr ($in, $_ * $sz, $sz) foreach @a; + return $out; +} + +1; diff --git a/src/tests/main.c b/src/tests/main.c new file mode 100644 index 0000000..ad1b0f1 --- /dev/null +++ b/src/tests/main.c @@ -0,0 +1,15 @@ +#include <random.h> +#include "tests/lib.h" +#include "tests/main.h" + +int +main (int argc UNUSED, char *argv[]) +{ + test_name = argv[0]; + + msg ("begin"); + random_init (0); + test_main (); + msg ("end"); + return 0; +} diff --git a/src/tests/main.h b/src/tests/main.h new file mode 100644 index 0000000..f0e8818 --- /dev/null +++ b/src/tests/main.h @@ -0,0 +1,6 @@ +#ifndef TESTS_MAIN_H +#define TESTS_MAIN_H + +void test_main (void); + +#endif /* tests/main.h */ diff --git a/src/tests/make-grade b/src/tests/make-grade new file mode 100644 index 0000000..a3faa0e --- /dev/null +++ b/src/tests/make-grade @@ -0,0 +1,152 @@ +#! /usr/bin/perl + +use strict; +use warnings; + +@ARGV == 3 || die; +my ($src_dir, $results_file, $grading_file) = @ARGV; + +# Read pass/file verdicts from $results_file. +open (RESULTS, '<', $results_file) || die "$results_file: open: $!\n"; +my (%verdicts, %verdict_counts); +while (<RESULTS>) { + my ($verdict, $test) = /^(pass|FAIL) (.*)$/ or die; + $verdicts{$test} = $verdict eq 'pass'; +} +close RESULTS; + +my (@failures); +my (@overall, @rubrics, @summary); +my ($pct_actual, $pct_possible) = (0, 0); + +# Read grading file. +my (@items); +open (GRADING, '<', $grading_file) || die "$grading_file: open: $!\n"; +while (<GRADING>) { + s/#.*//; + next if /^\s*$/; + my ($max_pct, $rubric_suffix) = /^\s*(\d+(?:\.\d+)?)%\t(.*)/ or die; + my ($dir) = $rubric_suffix =~ /^(.*)\//; + my ($rubric_file) = "$src_dir/$rubric_suffix"; + open (RUBRIC, '<', $rubric_file) or die "$rubric_file: open: $!\n"; + + # Rubric file must begin with title line. + my $title = <RUBRIC>; + chomp $title; + $title =~ s/:$// or die; + $title .= " ($rubric_suffix):"; + push (@rubrics, $title); + + my ($score, $possible) = (0, 0); + my ($cnt, $passed) = (0, 0); + my ($was_score) = 0; + while (<RUBRIC>) { + chomp; + push (@rubrics, "\t$_"), next if /^-/; + push (@rubrics, ""), next if /^\s*$/; + my ($poss, $name) = /^(\d+)\t(.*)$/ or die; + my ($test) = "$dir/$name"; + my ($points) = 0; + if (!defined $verdicts{$test}) { + push (@overall, "warning: $test not tested, assuming failure"); + } elsif ($verdicts{$test}) { + $points = $poss; + $passed++; + } + push (@failures, $test) if !$points; + $verdict_counts{$test}++; + push (@rubrics, sprintf ("\t%4s%2d/%2d %s", + $points ? '' : '**', $points, $poss, $test)); + $score += $points; + $possible += $poss; + $cnt++; + } + close (RUBRIC); + + push (@rubrics, ""); + push (@rubrics, "\t- Section summary."); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $passed, $cnt, 'tests passed')); + push (@rubrics, sprintf ("\t%4s%3d/%3d %s", + '', $score, $possible, 'points subtotal')); + push (@rubrics, ''); + + my ($pct) = ($score / $possible) * $max_pct; + push (@summary, sprintf ("%-45s %3d/%3d %5.1f%%/%5.1f%%", + $rubric_suffix, + $score, $possible, + $pct, $max_pct)); + $pct_actual += $pct; + $pct_possible += $max_pct; +} +close GRADING; + +my ($sum_line) + = "--------------------------------------------- --- --- ------ ------"; +unshift (@summary, + "SUMMARY BY TEST SET", + '', + sprintf ("%-45s %3s %3s %6s %6s", + "Test Set", "Pts", "Max", "% Ttl", "% Max"), + $sum_line); +push (@summary, + $sum_line, + sprintf ("%-45s %3s %3s %5.1f%%/%5.1f%%", + 'Total', '', '', $pct_actual, $pct_possible)); + +unshift (@rubrics, + "SUMMARY OF INDIVIDUAL TESTS", + ''); + +foreach my $name (keys (%verdicts)) { + my ($count) = $verdict_counts{$name}; + if (!defined ($count) || $count != 1) { + if (!defined ($count) || !$count) { + push (@overall, "warning: test $name doesn't count for grading"); + } else { + push (@overall, + "warning: test $name counted $count times in grading"); + } + } +} +push (@overall, sprintf ("TOTAL TESTING SCORE: %.1f%%", $pct_actual)); +if (sprintf ("%.1f", $pct_actual) eq sprintf ("%.1f", $pct_possible)) { + push (@overall, "ALL TESTED PASSED -- PERFECT SCORE"); +} + +my (@divider) = ('', '- ' x 38, ''); + +print map ("$_\n", @overall, @divider, @summary, @divider, @rubrics); + +for my $test (@failures) { + print map ("$_\n", @divider); + print "DETAILS OF $test FAILURE:\n\n"; + + if (open (RESULT, '<', "$test.result")) { + my $first_line = <RESULT>; + my ($cnt) = 0; + while (<RESULT>) { + print; + $cnt++; + } + close (RESULT); + } + + if (open (OUTPUT, '<', "$test.output")) { + print "\nOUTPUT FROM $test:\n\n"; + + my ($panics, $boots) = (0, 0); + while (<OUTPUT>) { + if (/PANIC/ && ++$panics > 2) { + print "[...details of additional panic(s) omitted...]\n"; + last; + } + print; + if (/Pintos booting/ && ++$boots > 1) { + print "[...details of reboot(s) omitted...]\n"; + last; + } + } + close (OUTPUT); + } +} diff --git a/src/tests/random.pm b/src/tests/random.pm new file mode 100644 index 0000000..be008ff --- /dev/null +++ b/src/tests/random.pm @@ -0,0 +1,27 @@ +use strict; +use warnings; + +use tests::arc4; + +my (@arc4); + +sub random_init { + if (@arc4 == 0) { + my ($seed) = @_; + $seed = 0 if !defined $seed; + @arc4 = arc4_init (pack ("V", $seed)); + } +} + +sub random_bytes { + random_init (); + my ($n) = @_; + return arc4_crypt (\@arc4, "\0" x $n); +} + +sub random_ulong { + random_init (); + return unpack ("V", random_bytes (4)); +} + +1; diff --git a/src/tests/tests.pm b/src/tests/tests.pm new file mode 100644 index 0000000..29e0707 --- /dev/null +++ b/src/tests/tests.pm @@ -0,0 +1,622 @@ +use strict; +use warnings; +use tests::Algorithm::Diff; +use File::Temp 'tempfile'; +use Fcntl qw(SEEK_SET SEEK_CUR); + +sub fail; +sub pass; + +die if @ARGV != 2; +our ($test, $src_dir) = @ARGV; + +my ($msg_file) = tempfile (); +select ($msg_file); + +our (@prereq_tests) = (); +if ($test =~ /^(.*)-persistence$/) { + push (@prereq_tests, $1); +} +for my $prereq_test (@prereq_tests) { + my (@result) = read_text_file ("$prereq_test.result"); + fail "Prerequisite test $prereq_test failed.\n" if $result[0] ne 'PASS'; +} + + +# Generic testing. + +sub check_expected { + my ($expected) = pop @_; + my (@options) = @_; + my (@output) = read_text_file ("$test.output"); + common_checks ("run", @output); + compare_output ("run", @options, \@output, $expected); +} + +sub common_checks { + my ($run, @output) = @_; + + fail "\u$run produced no output at all\n" if @output == 0; + + check_for_panic ($run, @output); + check_for_keyword ($run, "FAIL", @output); + check_for_triple_fault ($run, @output); + check_for_keyword ($run, "TIMEOUT", @output); + + fail "\u$run didn't start up properly: no \"Pintos booting\" message\n" + if !grep (/Pintos booting with.*kB RAM\.\.\./, @output); + fail "\u$run didn't start up properly: no \"Boot complete\" message\n" + if !grep (/Boot complete/, @output); + fail "\u$run didn't shut down properly: no \"Timer: # ticks\" message\n" + if !grep (/Timer: \d+ ticks/, @output); + fail "\u$run didn't shut down properly: no \"Powering off\" message\n" + if !grep (/Powering off/, @output); +} + +sub check_for_panic { + my ($run, @output) = @_; + + my ($panic) = grep (/PANIC/, @output); + return unless defined $panic; + + print "Kernel panic in $run: ", substr ($panic, index ($panic, "PANIC")), + "\n"; + + my (@stack_line) = grep (/Call stack:/, @output); + if (@stack_line != 0) { + my ($addrs) = $stack_line[0] =~ /Call stack:((?: 0x[0-9a-f]+)+)/; + + # Find a user program to translate user virtual addresses. + my ($userprog) = ""; + $userprog = "$test" + if grep (hex ($_) < 0xc0000000, split (' ', $addrs)) > 0 && -e $test; + + # Get and print the backtrace. + my ($trace) = scalar (`backtrace kernel.o $userprog $addrs`); + print "Call stack:$addrs\n"; + print "Translation of call stack:\n"; + print $trace; + + # Print disclaimer. + if ($userprog ne '' && index ($trace, $userprog) >= 0) { + print <<EOF; +Translations of user virtual addresses above are based on a guess at +the binary to use. If this guess is incorrect, then those +translations will be misleading. +EOF + } + } + + if ($panic =~ /sec_no \< d-\>capacity/) { + print <<EOF; +\nThis assertion commonly fails when accessing a file via an inode that +has been closed and freed. Freeing an inode clears all its sector +indexes to 0xcccccccc, which is not a valid sector number for disks +smaller than about 1.6 TB. +EOF + } + + fail; +} + +sub check_for_keyword { + my ($run, $keyword, @output) = @_; + + my ($kw_line) = grep (/$keyword/, @output); + return unless defined $kw_line; + + # Most output lines are prefixed by (test-name). Eliminate this + # from our message for brevity. + $kw_line =~ s/^\([^\)]+\)\s+//; + print "$run: $kw_line\n"; + + fail; +} + +sub check_for_triple_fault { + my ($run, @output) = @_; + + my ($reboots) = grep (/Pintos booting/, @output) - 1; + return unless $reboots > 0; + + print <<EOF; +\u$run spontaneously rebooted $reboots times. +This is most often caused by unhandled page faults. +Read the Triple Faults section in the Debugging chapter +of the Pintos manual for more information. +EOF + + fail; +} + +# Get @output without header or trailer. +sub get_core_output { + my ($run, @output) = @_; + my ($p); + + my ($process); + my ($start); + for my $i (0...$#_) { + $start = $i + 1, last + if ($process) = $output[$i] =~ /^Executing '(\S+).*':$/; + } + + my ($end); + for my $i ($start...$#output) { + $end = $i - 1, last if $output[$i] =~ /^Execution of '.*' complete.$/; + } + + fail "\u$run didn't start a thread or process\n" if !defined $start; + fail "\u$run started '$process' but it never finished\n" if !defined $end; + + return @output[$start...$end]; +} + +sub compare_output { + my ($run) = shift @_; + my ($expected) = pop @_; + my ($output) = pop @_; + my (%options) = @_; + + my (@output) = get_core_output ($run, @$output); + fail "\u$run didn't produce any output" if !@output; + + my $ignore_exit_codes = exists $options{IGNORE_EXIT_CODES}; + if ($ignore_exit_codes) { + delete $options{IGNORE_EXIT_CODES}; + @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\-?\d+\)$/, @output); + } + my $ignore_user_faults = exists $options{IGNORE_USER_FAULTS}; + if ($ignore_user_faults) { + delete $options{IGNORE_USER_FAULTS}; + @output = grep (!/^Page fault at.*in user context\.$/ + && !/: dying due to interrupt 0x0e \(.*\).$/ + && !/^Interrupt 0x0e \(.*\) at eip=/ + && !/^ cr2=.* error=.*/ + && !/^ eax=.* ebx=.* ecx=.* edx=.*/ + && !/^ esi=.* edi=.* esp=.* ebp=.*/ + && !/^ cs=.* ds=.* es=.* ss=.*/, @output); + } + die "unknown option " . (keys (%options))[0] . "\n" if %options; + + my ($msg); + + # Compare actual output against each allowed output. + if (ref ($expected) eq 'ARRAY') { + my ($i) = 0; + $expected = {map ((++$i => $_), @$expected)}; + } + foreach my $key (keys %$expected) { + my (@expected) = split ("\n", $expected->{$key}); + + $msg .= "Acceptable output:\n"; + $msg .= join ('', map (" $_\n", @expected)); + + # Check whether actual and expected match. + # If it's a perfect match, we're done. + if ($#output == $#expected) { + my ($eq) = 1; + for (my ($i) = 0; $i <= $#expected; $i++) { + $eq = 0 if $output[$i] ne $expected[$i]; + } + return $key if $eq; + } + + # They differ. Output a diff. + my (@diff) = ""; + my ($d) = Algorithm::Diff->new (\@expected, \@output); + while ($d->Next ()) { + my ($ef, $el, $af, $al) = $d->Get (qw (min1 max1 min2 max2)); + if ($d->Same ()) { + push (@diff, map (" $_\n", $d->Items (1))); + } else { + push (@diff, map ("- $_\n", $d->Items (1))) if $d->Items (1); + push (@diff, map ("+ $_\n", $d->Items (2))) if $d->Items (2); + } + } + + $msg .= "Differences in `diff -u' format:\n"; + $msg .= join ('', @diff); + } + + # Failed to match. Report failure. + $msg .= "\n(Process exit codes are excluded for matching purposes.)\n" + if $ignore_exit_codes; + $msg .= "\n(User fault messages are excluded for matching purposes.)\n" + if $ignore_user_faults; + fail "Test output failed to match any acceptable form.\n\n$msg"; +} + +# File system extraction. + +# check_archive (\%CONTENTS) +# +# Checks that the extracted file system's contents match \%CONTENTS. +# Each key in the hash is a file name. Each value may be: +# +# - $FILE: Name of a host file containing the expected contents. +# +# - [$FILE, $OFFSET, $LENGTH]: An excerpt of host file $FILE +# comprising the $LENGTH bytes starting at $OFFSET. +# +# - [$CONTENTS]: The literal expected file contents, as a string. +# +# - {SUBDIR}: A subdirectory, in the same form described here, +# recursively. +sub check_archive { + my ($expected_hier) = @_; + + my (@output) = read_text_file ("$test.output"); + common_checks ("file system extraction run", @output); + + @output = get_core_output ("file system extraction run", @output); + @output = grep (!/^[a-zA-Z0-9-_]+: exit\(\d+\)$/, @output); + fail join ("\n", "Error extracting file system:", @output) if @output; + + my ($test_base_name) = $test; + $test_base_name =~ s%.*/%%; + $test_base_name =~ s%-persistence$%%; + $expected_hier->{$test_base_name} = $prereq_tests[0]; + $expected_hier->{'tar'} = 'tests/filesys/extended/tar'; + + my (%expected) = normalize_fs (flatten_hierarchy ($expected_hier, "")); + my (%actual) = read_tar ("$prereq_tests[0].tar"); + + my ($errors) = 0; + foreach my $name (sort keys %expected) { + if (exists $actual{$name}) { + if (is_dir ($actual{$name}) && !is_dir ($expected{$name})) { + print "$name is a directory but should be an ordinary file.\n"; + $errors++; + } elsif (!is_dir ($actual{$name}) && is_dir ($expected{$name})) { + print "$name is an ordinary file but should be a directory.\n"; + $errors++; + } + } else { + print "$name is missing from the file system.\n"; + $errors++; + } + } + foreach my $name (sort keys %actual) { + if (!exists $expected{$name}) { + if ($name =~ /^[[:print:]]+$/) { + print "$name exists in the file system but it should not.\n"; + } else { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print <<EOF; +$esc_name exists in the file system but should not. (The name +of this file contains unusual characters that were printed as `.'.) +EOF + } + $errors++; + } + } + if ($errors) { + print "\nActual contents of file system:\n"; + print_fs (%actual); + print "\nExpected contents of file system:\n"; + print_fs (%expected); + } else { + foreach my $name (sort keys %expected) { + if (!is_dir ($expected{$name})) { + my ($exp_file, $exp_length) = open_file ($expected{$name}); + my ($act_file, $act_length) = open_file ($actual{$name}); + $errors += !compare_files ($exp_file, $exp_length, + $act_file, $act_length, $name, + !$errors); + close ($exp_file); + close ($act_file); + } + } + } + fail "Extracted file system contents are not correct.\n" if $errors; +} + +# open_file ([$FILE, $OFFSET, $LENGTH]) +# open_file ([$CONTENTS]) +# +# Opens a file for the contents passed in, which must be in one of +# the two above forms that correspond to check_archive() arguments. +# +# Returns ($HANDLE, $LENGTH), where $HANDLE is the file's handle and +# $LENGTH is the number of bytes in the file's content. +sub open_file { + my ($value) = @_; + die if ref ($value) ne 'ARRAY'; + + my ($file) = tempfile (); + my ($length); + if (@$value == 1) { + $length = length ($value->[0]); + $file = tempfile (); + syswrite ($file, $value->[0]) == $length + or die "writing temporary file: $!\n"; + sysseek ($file, 0, SEEK_SET); + } elsif (@$value == 3) { + $length = $value->[2]; + open ($file, '<', $value->[0]) or die "$value->[0]: open: $!\n"; + die "$value->[0]: file is smaller than expected\n" + if -s $file < $value->[1] + $length; + sysseek ($file, $value->[1], SEEK_SET); + } else { + die; + } + return ($file, $length); +} + +# compare_files ($A, $A_SIZE, $B, $B_SIZE, $NAME, $VERBOSE) +# +# Compares $A_SIZE bytes in $A to $B_SIZE bytes in $B. +# ($A and $B are handles.) +# If their contents differ, prints a brief message describing +# the differences, using $NAME to identify the file. +# The message contains more detail if $VERBOSE is nonzero. +# Returns 1 if the contents are identical, 0 otherwise. +sub compare_files { + my ($a, $a_size, $b, $b_size, $name, $verbose) = @_; + my ($ofs) = 0; + select(STDOUT); + for (;;) { + my ($a_amt) = $a_size >= 1024 ? 1024 : $a_size; + my ($b_amt) = $b_size >= 1024 ? 1024 : $b_size; + my ($a_data, $b_data); + if (!defined (sysread ($a, $a_data, $a_amt)) + || !defined (sysread ($b, $b_data, $b_amt))) { + die "reading $name: $!\n"; + } + + my ($a_len) = length $a_data; + my ($b_len) = length $b_data; + last if $a_len == 0 && $b_len == 0; + + if ($a_data ne $b_data) { + my ($min_len) = $a_len < $b_len ? $a_len : $b_len; + my ($diff_ofs); + for ($diff_ofs = 0; $diff_ofs < $min_len; $diff_ofs++) { + last if (substr ($a_data, $diff_ofs, 1) + ne substr ($b_data, $diff_ofs, 1)); + } + + printf "\nFile $name differs from expected " + . "starting at offset 0x%x.\n", $ofs + $diff_ofs; + if ($verbose ) { + print "Expected contents:\n"; + hex_dump (substr ($a_data, $diff_ofs, 64), $ofs + $diff_ofs); + print "Actual contents:\n"; + hex_dump (substr ($b_data, $diff_ofs, 64), $ofs + $diff_ofs); + } + return 0; + } + + $ofs += $a_len; + $a_size -= $a_len; + $b_size -= $b_len; + } + return 1; +} + +# hex_dump ($DATA, $OFS) +# +# Prints $DATA in hex and text formats. +# The first byte of $DATA corresponds to logical offset $OFS +# in whatever file the data comes from. +sub hex_dump { + my ($data, $ofs) = @_; + + if ($data eq '') { + printf " (File ends at offset %08x.)\n", $ofs; + return; + } + + my ($per_line) = 16; + while ((my $size = length ($data)) > 0) { + my ($start) = $ofs % $per_line; + my ($end) = $per_line; + $end = $start + $size if $end - $start > $size; + my ($n) = $end - $start; + + printf "0x%08x ", int ($ofs / $per_line) * $per_line; + + # Hex version. + print " " x $start; + for my $i ($start...$end - 1) { + printf "%02x", ord (substr ($data, $i - $start, 1)); + print $i == $per_line / 2 - 1 ? '-' : ' '; + } + print " " x ($per_line - $end); + + # Character version. + my ($esc_data) = substr ($data, 0, $n); + $esc_data =~ s/[^[:print:]]/./g; + print "|", " " x $start, $esc_data, " " x ($per_line - $end), "|"; + + print "\n"; + + $data = substr ($data, $n); + $ofs += $n; + } +} + +# print_fs (%FS) +# +# Prints a list of files in %FS, which must be a file system +# as flattened by flatten_hierarchy() and normalized by +# normalize_fs(). +sub print_fs { + my (%fs) = @_; + foreach my $name (sort keys %fs) { + my ($esc_name) = $name; + $esc_name =~ s/[^[:print:]]/./g; + print "$esc_name: "; + if (!is_dir ($fs{$name})) { + print +file_size ($fs{$name}), "-byte file"; + } else { + print "directory"; + } + print "\n"; + } + print "(empty)\n" if !@_; +} + +# normalize_fs (%FS) +# +# Takes a file system as flattened by flatten_hierarchy(). +# Returns a similar file system in which values of the form $FILE +# are replaced by those of the form [$FILE, $OFFSET, $LENGTH]. +sub normalize_fs { + my (%fs) = @_; + foreach my $name (keys %fs) { + my ($value) = $fs{$name}; + next if is_dir ($value) || ref ($value) ne ''; + die "can't open $value\n" if !stat $value; + $fs{$name} = [$value, 0, -s _]; + } + return %fs; +} + +# is_dir ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns 1 if it represents a directory, 0 otherwise. +sub is_dir { + my ($value) = @_; + return ref ($value) eq '' && $value eq 'directory'; +} + +# file_size ($VALUE) +# +# Takes a value like one in the hash returned by flatten_hierarchy() +# and returns the size of the file it represents. +sub file_size { + my ($value) = @_; + die if is_dir ($value); + die if ref ($value) ne 'ARRAY'; + return @$value > 1 ? $value->[2] : length ($value->[0]); +} + +# flatten_hierarchy ($HIER_FS, $PREFIX) +# +# Takes a file system in the format expected by check_archive() and +# returns a "flattened" version in which file names include all parent +# directory names and the value of directories is just "directory". +sub flatten_hierarchy { + my (%hier_fs) = %{$_[0]}; + my ($prefix) = $_[1]; + my (%flat_fs); + for my $name (keys %hier_fs) { + my ($value) = $hier_fs{$name}; + if (ref $value eq 'HASH') { + %flat_fs = (%flat_fs, flatten_hierarchy ($value, "$prefix$name/")); + $flat_fs{"$prefix$name"} = 'directory'; + } else { + $flat_fs{"$prefix$name"} = $value; + } + } + return %flat_fs; +} + +# read_tar ($ARCHIVE) +# +# Reads the ustar-format tar file in $ARCHIVE +# and returns a flattened file system for it. +sub read_tar { + my ($archive) = @_; + my (%content); + open (ARCHIVE, '<', $archive) or fail "$archive: open: $!\n"; + for (;;) { + my ($header); + if ((my $retval = sysread (ARCHIVE, $header, 512)) != 512) { + fail "$archive: unexpected end of file\n" if $retval >= 0; + fail "$archive: read: $!\n"; + } + + last if $header eq "\0" x 512; + + # Verify magic numbers. + if (substr ($header, 257, 6) ne "ustar\0" + || substr ($header, 263, 2) ne '00') { + fail "$archive: corrupt ustar header\n"; + } + + # Verify checksum. + my ($chksum) = oct (unpack ("Z*", substr ($header, 148, 8, ' ' x 8))); + my ($correct_chksum) = unpack ("%32a*", $header); + fail "$archive: bad header checksum\n" if $chksum != $correct_chksum; + + # Get file name. + my ($name) = unpack ("Z100", $header); + my ($prefix) = unpack ("Z*", substr ($header, 345)); + $name = "$prefix/$name" if $prefix ne ''; + fail "$archive: contains file with empty name" if $name eq ''; + + # Get type. + my ($typeflag) = substr ($header, 156, 1); + $typeflag = '0' if $typeflag eq "\0"; + fail "unknown file type '$typeflag'\n" if $typeflag !~ /[05]/; + + # Get size. + my ($size) = oct (unpack ("Z*", substr ($header, 124, 12))); + fail "bad size $size\n" if $size < 0; + $size = 0 if $typeflag eq '5'; + + # Store content. + if (exists $content{$name}) { + fail "$archive: contains multiple entries for $name\n"; + } + if ($typeflag eq '5') { + $content{$name} = 'directory'; + } else { + my ($position) = sysseek (ARCHIVE, 0, SEEK_CUR); + $content{$name} = [$archive, $position, $size]; + sysseek (ARCHIVE, int (($size + 511) / 512) * 512, SEEK_CUR); + } + } + close (ARCHIVE); + return %content; +} + +# Utilities. + +sub fail { + finish ("FAIL", @_); +} + +sub pass { + finish ("PASS", @_); +} + +sub finish { + my ($verdict, @messages) = @_; + + seek ($msg_file, 0, 0); + push (@messages, <$msg_file>); + close ($msg_file); + chomp (@messages); + + my ($result_fn) = "$test.result"; + open (RESULT, '>', $result_fn) or die "$result_fn: create: $!\n"; + print RESULT "$verdict\n"; + print RESULT "$_\n" foreach @messages; + close (RESULT); + + if ($verdict eq 'PASS') { + print STDOUT "pass $test\n"; + } else { + print STDOUT "FAIL $test\n"; + } + print STDOUT "$_\n" foreach @messages; + + exit 0; +} + +sub read_text_file { + my ($file_name) = @_; + open (FILE, '<', $file_name) or die "$file_name: open: $!\n"; + my (@content) = <FILE>; + chomp (@content); + close (FILE); + return @content; +} + +1; diff --git a/src/tests/threads/Grading b/src/tests/threads/Grading new file mode 100644 index 0000000..c9be35f --- /dev/null +++ b/src/tests/threads/Grading @@ -0,0 +1,6 @@ +# Percentage of the testing point total designated for each set of +# tests. + +20.0% tests/threads/Rubric.alarm +40.0% tests/threads/Rubric.priority +40.0% tests/threads/Rubric.mlfqs diff --git a/src/tests/threads/Make.tests b/src/tests/threads/Make.tests new file mode 100644 index 0000000..961c3ce --- /dev/null +++ b/src/tests/threads/Make.tests @@ -0,0 +1,49 @@ +# -*- makefile -*- + +# Test names. +tests/threads_TESTS = $(addprefix tests/threads/,alarm-single \ +alarm-multiple alarm-simultaneous alarm-zero alarm-negative \ +) + +# Sources for tests. +tests/threads_SRC = tests/threads/tests.c +tests/threads_SRC += tests/threads/alarm-wait.c +tests/threads_SRC += tests/threads/alarm-simultaneous.c +tests/threads_SRC += tests/threads/alarm-priority.c +tests/threads_SRC += tests/threads/alarm-zero.c +tests/threads_SRC += tests/threads/alarm-negative.c +tests/threads_SRC += tests/threads/priority-change.c +tests/threads_SRC += tests/threads/priority-donate-one.c +tests/threads_SRC += tests/threads/priority-donate-multiple.c +tests/threads_SRC += tests/threads/priority-donate-multiple2.c +tests/threads_SRC += tests/threads/priority-donate-nest.c +tests/threads_SRC += tests/threads/priority-donate-sema.c +tests/threads_SRC += tests/threads/priority-donate-lower.c +tests/threads_SRC += tests/threads/priority-fifo.c +tests/threads_SRC += tests/threads/priority-preempt.c +tests/threads_SRC += tests/threads/priority-sema.c +tests/threads_SRC += tests/threads/priority-condvar.c +tests/threads_SRC += tests/threads/priority-donate-chain.c +tests/threads_SRC += tests/threads/mlfqs-load-1.c +tests/threads_SRC += tests/threads/mlfqs-load-60.c +tests/threads_SRC += tests/threads/mlfqs-load-avg.c +tests/threads_SRC += tests/threads/mlfqs-recent-1.c +tests/threads_SRC += tests/threads/mlfqs-fair.c +tests/threads_SRC += tests/threads/mlfqs-block.c +tests/threads_SRC += tests/threads/threadtest.c +tests/threads_SRC += tests/threads/simplethreadtest.c + +MLFQS_OUTPUTS = \ +tests/threads/mlfqs-load-1.output \ +tests/threads/mlfqs-load-60.output \ +tests/threads/mlfqs-load-avg.output \ +tests/threads/mlfqs-recent-1.output \ +tests/threads/mlfqs-fair-2.output \ +tests/threads/mlfqs-fair-20.output \ +tests/threads/mlfqs-nice-2.output \ +tests/threads/mlfqs-nice-10.output \ +tests/threads/mlfqs-block.output + +$(MLFQS_OUTPUTS): KERNELFLAGS += -mlfqs +$(MLFQS_OUTPUTS): TIMEOUT = 480 + diff --git a/src/tests/threads/Rubric.alarm b/src/tests/threads/Rubric.alarm new file mode 100644 index 0000000..61abe85 --- /dev/null +++ b/src/tests/threads/Rubric.alarm @@ -0,0 +1,8 @@ +Functionality and robustness of alarm clock: +4 alarm-single +4 alarm-multiple +4 alarm-simultaneous +4 alarm-priority + +1 alarm-zero +1 alarm-negative diff --git a/src/tests/threads/Rubric.mlfqs b/src/tests/threads/Rubric.mlfqs new file mode 100644 index 0000000..f260091 --- /dev/null +++ b/src/tests/threads/Rubric.mlfqs @@ -0,0 +1,14 @@ +Functionality of advanced scheduler: +5 mlfqs-load-1 +5 mlfqs-load-60 +3 mlfqs-load-avg + +5 mlfqs-recent-1 + +5 mlfqs-fair-2 +3 mlfqs-fair-20 + +4 mlfqs-nice-2 +2 mlfqs-nice-10 + +5 mlfqs-block diff --git a/src/tests/threads/Rubric.priority b/src/tests/threads/Rubric.priority new file mode 100644 index 0000000..652bc99 --- /dev/null +++ b/src/tests/threads/Rubric.priority @@ -0,0 +1,15 @@ +Functionality of priority scheduler: +3 priority-change +3 priority-preempt + +3 priority-fifo +3 priority-sema +3 priority-condvar + +3 priority-donate-one +3 priority-donate-multiple +3 priority-donate-multiple2 +3 priority-donate-nest +5 priority-donate-chain +3 priority-donate-sema +3 priority-donate-lower diff --git a/src/tests/threads/alarm-multiple.ck b/src/tests/threads/alarm-multiple.ck new file mode 100644 index 0000000..fd83bcd --- /dev/null +++ b/src/tests/threads/alarm-multiple.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (7); diff --git a/src/tests/threads/alarm-negative.c b/src/tests/threads/alarm-negative.c new file mode 100644 index 0000000..aec52cf --- /dev/null +++ b/src/tests/threads/alarm-negative.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(-100). Only requirement is that it not crash. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_negative (void) +{ + timer_sleep (-100); + pass (); +} diff --git a/src/tests/threads/alarm-negative.ck b/src/tests/threads/alarm-negative.ck new file mode 100644 index 0000000..0d2bab0 --- /dev/null +++ b/src/tests/threads/alarm-negative.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-negative) begin +(alarm-negative) PASS +(alarm-negative) end +EOF +pass; diff --git a/src/tests/threads/alarm-priority.c b/src/tests/threads/alarm-priority.c new file mode 100644 index 0000000..2288ff6 --- /dev/null +++ b/src/tests/threads/alarm-priority.c @@ -0,0 +1,58 @@ +/* Checks that when the alarm clock wakes up threads, the + higher-priority threads run first. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func alarm_priority_thread; +static int64_t wake_time; +static struct semaphore wait_sema; + +void +test_alarm_priority (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + wake_time = timer_ticks () + 5 * TIMER_FREQ; + sema_init (&wait_sema, 0); + + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 5) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, alarm_priority_thread, NULL); + } + + thread_set_priority (PRI_MIN); + + for (i = 0; i < 10; i++) + sema_down (&wait_sema); +} + +static void +alarm_priority_thread (void *aux UNUSED) +{ + /* Busy-wait until the current time changes. */ + int64_t start_time = timer_ticks (); + while (timer_elapsed (start_time) == 0) + continue; + + /* Now we know we're at the very beginning of a timer tick, so + we can call timer_sleep() without worrying about races + between checking the time and a timer interrupt. */ + timer_sleep (wake_time - timer_ticks ()); + + /* Print a message on wake-up. */ + msg ("Thread %s woke up.", thread_name ()); + + sema_up (&wait_sema); +} diff --git a/src/tests/threads/alarm-priority.ck b/src/tests/threads/alarm-priority.ck new file mode 100644 index 0000000..b57c78b --- /dev/null +++ b/src/tests/threads/alarm-priority.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-priority) begin +(alarm-priority) Thread priority 30 woke up. +(alarm-priority) Thread priority 29 woke up. +(alarm-priority) Thread priority 28 woke up. +(alarm-priority) Thread priority 27 woke up. +(alarm-priority) Thread priority 26 woke up. +(alarm-priority) Thread priority 25 woke up. +(alarm-priority) Thread priority 24 woke up. +(alarm-priority) Thread priority 23 woke up. +(alarm-priority) Thread priority 22 woke up. +(alarm-priority) Thread priority 21 woke up. +(alarm-priority) end +EOF +pass; diff --git a/src/tests/threads/alarm-simultaneous.c b/src/tests/threads/alarm-simultaneous.c new file mode 100644 index 0000000..844eea4 --- /dev/null +++ b/src/tests/threads/alarm-simultaneous.c @@ -0,0 +1,94 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_simultaneous (void) +{ + test_sleep (3, 5); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + int *output_pos; /* Current position in output buffer. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + int *output; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Each thread sleeps 10 ticks each time."); + msg ("Within an iteration, all threads should wake up on the same tick."); + + /* Allocate memory. */ + output = malloc (sizeof *output * iterations * thread_cnt * 2); + if (output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + char name[16]; + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, &test); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + iterations * 10 + 100); + + /* Print completion order. */ + msg ("iteration 0, thread 0: woke up after %d ticks", output[0]); + for (i = 1; i < test.output_pos - output; i++) + msg ("iteration %d, thread %d: woke up %d ticks later", + i / thread_cnt, i % thread_cnt, output[i] - output[i - 1]); + + free (output); +} + +/* Sleeper thread. */ +static void +sleeper (void *test_) +{ + struct sleep_test *test = test_; + int i; + + /* Make sure we're at the beginning of a timer tick. */ + timer_sleep (1); + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * 10; + timer_sleep (sleep_until - timer_ticks ()); + *test->output_pos++ = timer_ticks () - test->start; + thread_yield (); + } +} diff --git a/src/tests/threads/alarm-simultaneous.ck b/src/tests/threads/alarm-simultaneous.ck new file mode 100644 index 0000000..406b8b0 --- /dev/null +++ b/src/tests/threads/alarm-simultaneous.ck @@ -0,0 +1,27 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-simultaneous) begin +(alarm-simultaneous) Creating 3 threads to sleep 5 times each. +(alarm-simultaneous) Each thread sleeps 10 ticks each time. +(alarm-simultaneous) Within an iteration, all threads should wake up on the same tick. +(alarm-simultaneous) iteration 0, thread 0: woke up after 10 ticks +(alarm-simultaneous) iteration 0, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 0, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 1, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 1, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 2, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 2, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 3, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 3, thread 2: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 0: woke up 10 ticks later +(alarm-simultaneous) iteration 4, thread 1: woke up 0 ticks later +(alarm-simultaneous) iteration 4, thread 2: woke up 0 ticks later +(alarm-simultaneous) end +EOF +pass; diff --git a/src/tests/threads/alarm-single.ck b/src/tests/threads/alarm-single.ck new file mode 100644 index 0000000..31215df --- /dev/null +++ b/src/tests/threads/alarm-single.ck @@ -0,0 +1,4 @@ +# -*- perl -*- +use tests::tests; +use tests::threads::alarm; +check_alarm (1); diff --git a/src/tests/threads/alarm-wait.c b/src/tests/threads/alarm-wait.c new file mode 100644 index 0000000..37d3afc --- /dev/null +++ b/src/tests/threads/alarm-wait.c @@ -0,0 +1,152 @@ +/* Creates N threads, each of which sleeps a different, fixed + duration, M times. Records the wake-up order and verifies + that it is valid. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_sleep (int thread_cnt, int iterations); + +void +test_alarm_single (void) +{ + test_sleep (5, 1); +} + +void +test_alarm_multiple (void) +{ + test_sleep (5, 7); +} + +/* Information about the test. */ +struct sleep_test + { + int64_t start; /* Current time at start of test. */ + int iterations; /* Number of iterations per thread. */ + + /* Output. */ + struct lock output_lock; /* Lock protecting output buffer. */ + int *output_pos; /* Current position in output buffer. */ + }; + +/* Information about an individual thread in the test. */ +struct sleep_thread + { + struct sleep_test *test; /* Info shared between all threads. */ + int id; /* Sleeper ID. */ + int duration; /* Number of ticks to sleep. */ + int iterations; /* Iterations counted so far. */ + }; + +static void sleeper (void *); + +/* Runs THREAD_CNT threads thread sleep ITERATIONS times each. */ +static void +test_sleep (int thread_cnt, int iterations) +{ + struct sleep_test test; + struct sleep_thread *threads; + int *output, *op; + int product; + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating %d threads to sleep %d times each.", thread_cnt, iterations); + msg ("Thread 0 sleeps 10 ticks each time,"); + msg ("thread 1 sleeps 20 ticks each time, and so on."); + msg ("If successful, product of iteration count and"); + msg ("sleep duration will appear in nondescending order."); + + /* Allocate memory. */ + threads = malloc (sizeof *threads * thread_cnt); + output = malloc (sizeof *output * iterations * thread_cnt * 2); + if (threads == NULL || output == NULL) + PANIC ("couldn't allocate memory for test"); + + /* Initialize test. */ + test.start = timer_ticks () + 100; + test.iterations = iterations; + lock_init (&test.output_lock); + test.output_pos = output; + + /* Start threads. */ + ASSERT (output != NULL); + for (i = 0; i < thread_cnt; i++) + { + struct sleep_thread *t = threads + i; + char name[16]; + + t->test = &test; + t->id = i; + t->duration = (i + 1) * 10; + t->iterations = 0; + + snprintf (name, sizeof name, "thread %d", i); + thread_create (name, PRI_DEFAULT, sleeper, t); + } + + /* Wait long enough for all the threads to finish. */ + timer_sleep (100 + thread_cnt * iterations * 10 + 100); + + /* Acquire the output lock in case some rogue thread is still + running. */ + lock_acquire (&test.output_lock); + + /* Print completion order. */ + product = 0; + for (op = output; op < test.output_pos; op++) + { + struct sleep_thread *t; + int new_prod; + + ASSERT (*op >= 0 && *op < thread_cnt); + t = threads + *op; + + new_prod = ++t->iterations * t->duration; + + msg ("thread %d: duration=%d, iteration=%d, product=%d", + t->id, t->duration, t->iterations, new_prod); + + if (new_prod >= product) + product = new_prod; + else + fail ("thread %d woke up out of order (%d > %d)!", + t->id, product, new_prod); + } + + /* Verify that we had the proper number of wakeups. */ + for (i = 0; i < thread_cnt; i++) + if (threads[i].iterations != iterations) + fail ("thread %d woke up %d times instead of %d", + i, threads[i].iterations, iterations); + + lock_release (&test.output_lock); + free (output); + free (threads); +} + +/* Sleeper thread. */ +static void +sleeper (void *t_) +{ + struct sleep_thread *t = t_; + struct sleep_test *test = t->test; + int i; + + for (i = 1; i <= test->iterations; i++) + { + int64_t sleep_until = test->start + i * t->duration; + timer_sleep (sleep_until - timer_ticks ()); + lock_acquire (&test->output_lock); + *test->output_pos++ = t->id; + lock_release (&test->output_lock); + } +} diff --git a/src/tests/threads/alarm-zero.c b/src/tests/threads/alarm-zero.c new file mode 100644 index 0000000..c8a3ee2 --- /dev/null +++ b/src/tests/threads/alarm-zero.c @@ -0,0 +1,15 @@ +/* Tests timer_sleep(0), which should return immediately. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_alarm_zero (void) +{ + timer_sleep (0); + pass (); +} diff --git a/src/tests/threads/alarm-zero.ck b/src/tests/threads/alarm-zero.ck new file mode 100644 index 0000000..a6b1a3c --- /dev/null +++ b/src/tests/threads/alarm-zero.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(alarm-zero) begin +(alarm-zero) PASS +(alarm-zero) end +EOF +pass; diff --git a/src/tests/threads/alarm.pm b/src/tests/threads/alarm.pm new file mode 100644 index 0000000..84b3b7f --- /dev/null +++ b/src/tests/threads/alarm.pm @@ -0,0 +1,32 @@ +sub check_alarm { + my ($iterations) = @_; + our ($test); + + @output = read_text_file ("$test.output"); + common_checks ("run", @output); + + my (@products); + for (my ($i) = 0; $i < $iterations; $i++) { + for (my ($t) = 0; $t < 5; $t++) { + push (@products, ($i + 1) * ($t + 1) * 10); + } + } + @products = sort {$a <=> $b} @products; + + local ($_); + foreach (@output) { + fail $_ if /out of order/i; + + my ($p) = /product=(\d+)$/; + next if !defined $p; + + my ($q) = shift (@products); + fail "Too many wakeups.\n" if !defined $q; + fail "Out of order wakeups ($p vs. $q).\n" if $p != $q; # FIXME + } + fail scalar (@products) . " fewer wakeups than expected.\n" + if @products != 0; + pass; +} + +1; diff --git a/src/tests/threads/mlfqs-block.c b/src/tests/threads/mlfqs-block.c new file mode 100644 index 0000000..6d4992d --- /dev/null +++ b/src/tests/threads/mlfqs-block.c @@ -0,0 +1,64 @@ +/* Checks that recent_cpu and priorities are updated for blocked + threads. + + The main thread sleeps for 25 seconds, spins for 5 seconds, + then releases a lock. The "block" thread spins for 20 seconds + then attempts to acquire the lock, which will block for 10 + seconds (until the main thread releases it). If recent_cpu + decays properly while the "block" thread sleeps, then the + block thread should be immediately scheduled when the main + thread releases the lock. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void block_thread (void *lock_); + +void +test_mlfqs_block (void) +{ + int64_t start_time; + struct lock lock; + + ASSERT (thread_mlfqs); + + msg ("Main thread acquiring lock."); + lock_init (&lock); + lock_acquire (&lock); + + msg ("Main thread creating block thread, sleeping 25 seconds..."); + thread_create ("block", PRI_DEFAULT, block_thread, &lock); + timer_sleep (25 * TIMER_FREQ); + + msg ("Main thread spinning for 5 seconds..."); + start_time = timer_ticks (); + while (timer_elapsed (start_time) < 5 * TIMER_FREQ) + continue; + + msg ("Main thread releasing lock."); + lock_release (&lock); + + msg ("Block thread should have already acquired lock."); +} + +static void +block_thread (void *lock_) +{ + struct lock *lock = lock_; + int64_t start_time; + + msg ("Block thread spinning for 20 seconds..."); + start_time = timer_ticks (); + while (timer_elapsed (start_time) < 20 * TIMER_FREQ) + continue; + + msg ("Block thread acquiring lock..."); + lock_acquire (lock); + + msg ("...got it."); +} diff --git a/src/tests/threads/mlfqs-block.ck b/src/tests/threads/mlfqs-block.ck new file mode 100644 index 0000000..8833a3a --- /dev/null +++ b/src/tests/threads/mlfqs-block.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(mlfqs-block) begin +(mlfqs-block) Main thread acquiring lock. +(mlfqs-block) Main thread creating block thread, sleeping 25 seconds... +(mlfqs-block) Block thread spinning for 20 seconds... +(mlfqs-block) Block thread acquiring lock... +(mlfqs-block) Main thread spinning for 5 seconds... +(mlfqs-block) Main thread releasing lock. +(mlfqs-block) ...got it. +(mlfqs-block) Block thread should have already acquired lock. +(mlfqs-block) end +EOF +pass; diff --git a/src/tests/threads/mlfqs-fair-2.ck b/src/tests/threads/mlfqs-fair-2.ck new file mode 100644 index 0000000..5b19ff1 --- /dev/null +++ b/src/tests/threads/mlfqs-fair-2.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0, 0], 50); diff --git a/src/tests/threads/mlfqs-fair-20.ck b/src/tests/threads/mlfqs-fair-20.ck new file mode 100644 index 0000000..bb4d051 --- /dev/null +++ b/src/tests/threads/mlfqs-fair-20.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([(0) x 20], 20); diff --git a/src/tests/threads/mlfqs-fair.c b/src/tests/threads/mlfqs-fair.c new file mode 100644 index 0000000..3b1bea5 --- /dev/null +++ b/src/tests/threads/mlfqs-fair.c @@ -0,0 +1,124 @@ +/* Measures the correctness of the "nice" implementation. + + The "fair" tests run either 2 or 20 threads all niced to 0. + The threads should all receive approximately the same number + of ticks. Each test runs for 30 seconds, so the ticks should + also sum to approximately 30 * 100 == 3000 ticks. + + The mlfqs-nice-2 test runs 2 threads, one with nice 0, the + other with nice 5, which should receive 1,904 and 1,096 ticks, + respectively, over 30 seconds. + + The mlfqs-nice-10 test runs 10 threads with nice 0 through 9. + They should receive 672, 588, 492, 408, 316, 232, 152, 92, 40, + and 8 ticks, respectively, over 30 seconds. + + (The above are computed via simulation in mlfqs.pm.) */ + +#include <stdio.h> +#include <inttypes.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/palloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static void test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step); + +void +test_mlfqs_fair_2 (void) +{ + test_mlfqs_fair (2, 0, 0); +} + +void +test_mlfqs_fair_20 (void) +{ + test_mlfqs_fair (20, 0, 0); +} + +void +test_mlfqs_nice_2 (void) +{ + test_mlfqs_fair (2, 0, 5); +} + +void +test_mlfqs_nice_10 (void) +{ + test_mlfqs_fair (10, 0, 1); +} + +#define MAX_THREAD_CNT 20 + +struct thread_info + { + int64_t start_time; + int tick_count; + int nice; + }; + +static void load_thread (void *aux); + +static void +test_mlfqs_fair (int thread_cnt, int nice_min, int nice_step) +{ + struct thread_info info[MAX_THREAD_CNT]; + int64_t start_time; + int nice; + int i; + + ASSERT (thread_mlfqs); + ASSERT (thread_cnt <= MAX_THREAD_CNT); + ASSERT (nice_min >= -10); + ASSERT (nice_step >= 0); + ASSERT (nice_min + nice_step * (thread_cnt - 1) <= 20); + + thread_set_nice (-20); + + start_time = timer_ticks (); + msg ("Starting %d threads...", thread_cnt); + nice = nice_min; + for (i = 0; i < thread_cnt; i++) + { + struct thread_info *ti = &info[i]; + char name[16]; + + ti->start_time = start_time; + ti->tick_count = 0; + ti->nice = nice; + + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, ti); + + nice += nice_step; + } + msg ("Starting threads took %"PRId64" ticks.", timer_elapsed (start_time)); + + msg ("Sleeping 40 seconds to let threads run, please wait..."); + timer_sleep (40 * TIMER_FREQ); + + for (i = 0; i < thread_cnt; i++) + msg ("Thread %d received %d ticks.", i, info[i].tick_count); +} + +static void +load_thread (void *ti_) +{ + struct thread_info *ti = ti_; + int64_t sleep_time = 5 * TIMER_FREQ; + int64_t spin_time = sleep_time + 30 * TIMER_FREQ; + int64_t last_time = 0; + + thread_set_nice (ti->nice); + timer_sleep (sleep_time - timer_elapsed (ti->start_time)); + while (timer_elapsed (ti->start_time) < spin_time) + { + int64_t cur_time = timer_ticks (); + if (cur_time != last_time) + ti->tick_count++; + last_time = cur_time; + } +} diff --git a/src/tests/threads/mlfqs-load-1.c b/src/tests/threads/mlfqs-load-1.c new file mode 100644 index 0000000..a39eea2 --- /dev/null +++ b/src/tests/threads/mlfqs-load-1.c @@ -0,0 +1,60 @@ +/* Verifies that a single busy thread raises the load average to + 0.5 in 38 to 45 seconds. The expected time is 42 seconds, as + you can verify: + perl -e '$i++,$a=(59*$a+1)/60while$a<=.5;print "$i\n"' + + Then, verifies that 10 seconds of inactivity drop the load + average back below 0.5 again. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +void +test_mlfqs_load_1 (void) +{ + int64_t start_time; + int elapsed; + int load_avg; + + ASSERT (thread_mlfqs); + + msg ("spinning for up to 45 seconds, please wait..."); + + start_time = timer_ticks (); + for (;;) + { + load_avg = thread_get_load_avg (); + ASSERT (load_avg >= 0); + elapsed = timer_elapsed (start_time) / TIMER_FREQ; + if (load_avg > 100) + fail ("load average is %d.%02d " + "but should be between 0 and 1 (after %d seconds)", + load_avg / 100, load_avg % 100, elapsed); + else if (load_avg > 50) + break; + else if (elapsed > 45) + fail ("load average stayed below 0.5 for more than 45 seconds"); + } + + if (elapsed < 38) + fail ("load average took only %d seconds to rise above 0.5", elapsed); + msg ("load average rose to 0.5 after %d seconds", elapsed); + + msg ("sleeping for another 10 seconds, please wait..."); + timer_sleep (TIMER_FREQ * 10); + + load_avg = thread_get_load_avg (); + if (load_avg < 0) + fail ("load average fell below 0"); + if (load_avg > 50) + fail ("load average stayed above 0.5 for more than 10 seconds"); + msg ("load average fell back below 0.5 (to %d.%02d)", + load_avg / 100, load_avg % 100); + + pass (); +} diff --git a/src/tests/threads/mlfqs-load-1.ck b/src/tests/threads/mlfqs-load-1.ck new file mode 100644 index 0000000..faf0ffa --- /dev/null +++ b/src/tests/threads/mlfqs-load-1.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +@output = get_core_output ("run", @output); +fail "missing PASS in output" + unless grep ($_ eq '(mlfqs-load-1) PASS', @output); + +pass; diff --git a/src/tests/threads/mlfqs-load-60.c b/src/tests/threads/mlfqs-load-60.c new file mode 100644 index 0000000..b6a3eb6 --- /dev/null +++ b/src/tests/threads/mlfqs-load-60.c @@ -0,0 +1,155 @@ +/* Starts 60 threads that each sleep for 10 seconds, then spin in + a tight loop for 60 seconds, and sleep for another 60 seconds. + Every 2 seconds after the initial sleep, the main thread + prints the load average. + + The expected output is this (some margin of error is allowed): + + After 0 seconds, load average=1.00. + After 2 seconds, load average=2.95. + After 4 seconds, load average=4.84. + After 6 seconds, load average=6.66. + After 8 seconds, load average=8.42. + After 10 seconds, load average=10.13. + After 12 seconds, load average=11.78. + After 14 seconds, load average=13.37. + After 16 seconds, load average=14.91. + After 18 seconds, load average=16.40. + After 20 seconds, load average=17.84. + After 22 seconds, load average=19.24. + After 24 seconds, load average=20.58. + After 26 seconds, load average=21.89. + After 28 seconds, load average=23.15. + After 30 seconds, load average=24.37. + After 32 seconds, load average=25.54. + After 34 seconds, load average=26.68. + After 36 seconds, load average=27.78. + After 38 seconds, load average=28.85. + After 40 seconds, load average=29.88. + After 42 seconds, load average=30.87. + After 44 seconds, load average=31.84. + After 46 seconds, load average=32.77. + After 48 seconds, load average=33.67. + After 50 seconds, load average=34.54. + After 52 seconds, load average=35.38. + After 54 seconds, load average=36.19. + After 56 seconds, load average=36.98. + After 58 seconds, load average=37.74. + After 60 seconds, load average=37.48. + After 62 seconds, load average=36.24. + After 64 seconds, load average=35.04. + After 66 seconds, load average=33.88. + After 68 seconds, load average=32.76. + After 70 seconds, load average=31.68. + After 72 seconds, load average=30.63. + After 74 seconds, load average=29.62. + After 76 seconds, load average=28.64. + After 78 seconds, load average=27.69. + After 80 seconds, load average=26.78. + After 82 seconds, load average=25.89. + After 84 seconds, load average=25.04. + After 86 seconds, load average=24.21. + After 88 seconds, load average=23.41. + After 90 seconds, load average=22.64. + After 92 seconds, load average=21.89. + After 94 seconds, load average=21.16. + After 96 seconds, load average=20.46. + After 98 seconds, load average=19.79. + After 100 seconds, load average=19.13. + After 102 seconds, load average=18.50. + After 104 seconds, load average=17.89. + After 106 seconds, load average=17.30. + After 108 seconds, load average=16.73. + After 110 seconds, load average=16.17. + After 112 seconds, load average=15.64. + After 114 seconds, load average=15.12. + After 116 seconds, load average=14.62. + After 118 seconds, load average=14.14. + After 120 seconds, load average=13.67. + After 122 seconds, load average=13.22. + After 124 seconds, load average=12.78. + After 126 seconds, load average=12.36. + After 128 seconds, load average=11.95. + After 130 seconds, load average=11.56. + After 132 seconds, load average=11.17. + After 134 seconds, load average=10.80. + After 136 seconds, load average=10.45. + After 138 seconds, load average=10.10. + After 140 seconds, load average=9.77. + After 142 seconds, load average=9.45. + After 144 seconds, load average=9.13. + After 146 seconds, load average=8.83. + After 148 seconds, load average=8.54. + After 150 seconds, load average=8.26. + After 152 seconds, load average=7.98. + After 154 seconds, load average=7.72. + After 156 seconds, load average=7.47. + After 158 seconds, load average=7.22. + After 160 seconds, load average=6.98. + After 162 seconds, load average=6.75. + After 164 seconds, load average=6.53. + After 166 seconds, load average=6.31. + After 168 seconds, load average=6.10. + After 170 seconds, load average=5.90. + After 172 seconds, load average=5.70. + After 174 seconds, load average=5.52. + After 176 seconds, load average=5.33. + After 178 seconds, load average=5.16. +*/ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static int64_t start_time; + +static void load_thread (void *aux); + +#define THREAD_CNT 60 + +void +test_mlfqs_load_60 (void) +{ + int i; + + ASSERT (thread_mlfqs); + + start_time = timer_ticks (); + msg ("Starting %d niced load threads...", THREAD_CNT); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, NULL); + } + msg ("Starting threads took %d seconds.", + timer_elapsed (start_time) / TIMER_FREQ); + + for (i = 0; i < 90; i++) + { + int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10); + int load_avg; + timer_sleep (sleep_until - timer_ticks ()); + load_avg = thread_get_load_avg (); + msg ("After %d seconds, load average=%d.%02d.", + i * 2, load_avg / 100, load_avg % 100); + } +} + +static void +load_thread (void *aux UNUSED) +{ + int64_t sleep_time = 10 * TIMER_FREQ; + int64_t spin_time = sleep_time + 60 * TIMER_FREQ; + int64_t exit_time = spin_time + 60 * TIMER_FREQ; + + thread_set_nice (20); + timer_sleep (sleep_time - timer_elapsed (start_time)); + while (timer_elapsed (start_time) < spin_time) + continue; + timer_sleep (exit_time - timer_elapsed (start_time)); +} diff --git a/src/tests/threads/mlfqs-load-60.ck b/src/tests/threads/mlfqs-load-60.ck new file mode 100644 index 0000000..cb69220 --- /dev/null +++ b/src/tests/threads/mlfqs-load-60.ck @@ -0,0 +1,36 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); + +my (@output) = read_text_file ("$test.output"); +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./ + or next; + $actual[$t] = $load_avg; +} + +# Calculate expected values. +my ($load_avg) = 0; +my ($recent) = 0; +my (@expected); +for (my ($t) = 0; $t < 180; $t++) { + my ($ready) = $t < 60 ? 60 : 0; + $load_avg = (59/60) * $load_avg + (1/60) * $ready; + $expected[$t] = $load_avg; +} + +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 3.5, [2, 178, 2], + "Some load average values were missing or " + . "differed from those expected " + . "by more than 3.5."); +pass; diff --git a/src/tests/threads/mlfqs-load-avg.c b/src/tests/threads/mlfqs-load-avg.c new file mode 100644 index 0000000..50e83e2 --- /dev/null +++ b/src/tests/threads/mlfqs-load-avg.c @@ -0,0 +1,167 @@ +/* Starts 60 threads numbered 0 through 59. Thread #i sleeps for + (10+i) seconds, then spins in a loop for 60 seconds, then + sleeps until a total of 120 seconds have passed. Every 2 + seconds, starting 10 seconds in, the main thread prints the + load average. + + The expected output is listed below. Some margin of error is + allowed. + + If your implementation fails this test but passes most other + tests, then consider whether you are doing too much work in + the timer interrupt. If the timer interrupt handler takes too + long, then the test's main thread will not have enough time to + do its own work (printing a message) and go back to sleep + before the next tick arrives. Then the main thread will be + ready, instead of sleeping, when the tick arrives, + artificially driving up the load average. + + After 0 seconds, load average=0.00. + After 2 seconds, load average=0.05. + After 4 seconds, load average=0.16. + After 6 seconds, load average=0.34. + After 8 seconds, load average=0.58. + After 10 seconds, load average=0.87. + After 12 seconds, load average=1.22. + After 14 seconds, load average=1.63. + After 16 seconds, load average=2.09. + After 18 seconds, load average=2.60. + After 20 seconds, load average=3.16. + After 22 seconds, load average=3.76. + After 24 seconds, load average=4.42. + After 26 seconds, load average=5.11. + After 28 seconds, load average=5.85. + After 30 seconds, load average=6.63. + After 32 seconds, load average=7.46. + After 34 seconds, load average=8.32. + After 36 seconds, load average=9.22. + After 38 seconds, load average=10.15. + After 40 seconds, load average=11.12. + After 42 seconds, load average=12.13. + After 44 seconds, load average=13.16. + After 46 seconds, load average=14.23. + After 48 seconds, load average=15.33. + After 50 seconds, load average=16.46. + After 52 seconds, load average=17.62. + After 54 seconds, load average=18.81. + After 56 seconds, load average=20.02. + After 58 seconds, load average=21.26. + After 60 seconds, load average=22.52. + After 62 seconds, load average=23.71. + After 64 seconds, load average=24.80. + After 66 seconds, load average=25.78. + After 68 seconds, load average=26.66. + After 70 seconds, load average=27.45. + After 72 seconds, load average=28.14. + After 74 seconds, load average=28.75. + After 76 seconds, load average=29.27. + After 78 seconds, load average=29.71. + After 80 seconds, load average=30.06. + After 82 seconds, load average=30.34. + After 84 seconds, load average=30.55. + After 86 seconds, load average=30.68. + After 88 seconds, load average=30.74. + After 90 seconds, load average=30.73. + After 92 seconds, load average=30.66. + After 94 seconds, load average=30.52. + After 96 seconds, load average=30.32. + After 98 seconds, load average=30.06. + After 100 seconds, load average=29.74. + After 102 seconds, load average=29.37. + After 104 seconds, load average=28.95. + After 106 seconds, load average=28.47. + After 108 seconds, load average=27.94. + After 110 seconds, load average=27.36. + After 112 seconds, load average=26.74. + After 114 seconds, load average=26.07. + After 116 seconds, load average=25.36. + After 118 seconds, load average=24.60. + After 120 seconds, load average=23.81. + After 122 seconds, load average=23.02. + After 124 seconds, load average=22.26. + After 126 seconds, load average=21.52. + After 128 seconds, load average=20.81. + After 130 seconds, load average=20.12. + After 132 seconds, load average=19.46. + After 134 seconds, load average=18.81. + After 136 seconds, load average=18.19. + After 138 seconds, load average=17.59. + After 140 seconds, load average=17.01. + After 142 seconds, load average=16.45. + After 144 seconds, load average=15.90. + After 146 seconds, load average=15.38. + After 148 seconds, load average=14.87. + After 150 seconds, load average=14.38. + After 152 seconds, load average=13.90. + After 154 seconds, load average=13.44. + After 156 seconds, load average=13.00. + After 158 seconds, load average=12.57. + After 160 seconds, load average=12.15. + After 162 seconds, load average=11.75. + After 164 seconds, load average=11.36. + After 166 seconds, load average=10.99. + After 168 seconds, load average=10.62. + After 170 seconds, load average=10.27. + After 172 seconds, load average=9.93. + After 174 seconds, load average=9.61. + After 176 seconds, load average=9.29. + After 178 seconds, load average=8.98. +*/ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static int64_t start_time; + +static void load_thread (void *seq_no); + +#define THREAD_CNT 60 + +void +test_mlfqs_load_avg (void) +{ + int i; + + ASSERT (thread_mlfqs); + + start_time = timer_ticks (); + msg ("Starting %d load threads...", THREAD_CNT); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + snprintf(name, sizeof name, "load %d", i); + thread_create (name, PRI_DEFAULT, load_thread, (void *) i); + } + msg ("Starting threads took %d seconds.", + timer_elapsed (start_time) / TIMER_FREQ); + thread_set_nice (-20); + + for (i = 0; i < 90; i++) + { + int64_t sleep_until = start_time + TIMER_FREQ * (2 * i + 10); + int load_avg; + timer_sleep (sleep_until - timer_ticks ()); + load_avg = thread_get_load_avg (); + msg ("After %d seconds, load average=%d.%02d.", + i * 2, load_avg / 100, load_avg % 100); + } +} + +static void +load_thread (void *seq_no_) +{ + int seq_no = (int) seq_no_; + int sleep_time = TIMER_FREQ * (10 + seq_no); + int spin_time = sleep_time + TIMER_FREQ * THREAD_CNT; + int exit_time = TIMER_FREQ * (THREAD_CNT * 2); + + timer_sleep (sleep_time - timer_elapsed (start_time)); + while (timer_elapsed (start_time) < spin_time) + continue; + timer_sleep (exit_time - timer_elapsed (start_time)); +} diff --git a/src/tests/threads/mlfqs-load-avg.ck b/src/tests/threads/mlfqs-load-avg.ck new file mode 100644 index 0000000..2254d05 --- /dev/null +++ b/src/tests/threads/mlfqs-load-avg.ck @@ -0,0 +1,36 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $load_avg) = /After (\d+) seconds, load average=(\d+\.\d+)\./ + or next; + $actual[$t] = $load_avg; +} + +# Calculate expected values. +my ($load_avg) = 0; +my ($recent) = 0; +my (@expected); +for (my ($t) = 0; $t < 180; $t++) { + my ($ready) = $t < 60 ? $t : $t < 120 ? 120 - $t : 0; + $load_avg = (59/60) * $load_avg + (1/60) * $ready; + $expected[$t] = $load_avg; +} + +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2], + "Some load average values were missing or " + . "differed from those expected " + . "by more than 2.5."); +pass; diff --git a/src/tests/threads/mlfqs-nice-10.ck b/src/tests/threads/mlfqs-nice-10.ck new file mode 100644 index 0000000..53e0abe --- /dev/null +++ b/src/tests/threads/mlfqs-nice-10.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0...9], 25); diff --git a/src/tests/threads/mlfqs-nice-2.ck b/src/tests/threads/mlfqs-nice-2.ck new file mode 100644 index 0000000..ada366b --- /dev/null +++ b/src/tests/threads/mlfqs-nice-2.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +check_mlfqs_fair ([0, 5], 50); diff --git a/src/tests/threads/mlfqs-recent-1.c b/src/tests/threads/mlfqs-recent-1.c new file mode 100644 index 0000000..4258671 --- /dev/null +++ b/src/tests/threads/mlfqs-recent-1.c @@ -0,0 +1,144 @@ +/* Checks that recent_cpu is calculated properly for the case of + a single ready process. + + The expected output is this (some margin of error is allowed): + + After 2 seconds, recent_cpu is 6.40, load_avg is 0.03. + After 4 seconds, recent_cpu is 12.60, load_avg is 0.07. + After 6 seconds, recent_cpu is 18.61, load_avg is 0.10. + After 8 seconds, recent_cpu is 24.44, load_avg is 0.13. + After 10 seconds, recent_cpu is 30.08, load_avg is 0.15. + After 12 seconds, recent_cpu is 35.54, load_avg is 0.18. + After 14 seconds, recent_cpu is 40.83, load_avg is 0.21. + After 16 seconds, recent_cpu is 45.96, load_avg is 0.24. + After 18 seconds, recent_cpu is 50.92, load_avg is 0.26. + After 20 seconds, recent_cpu is 55.73, load_avg is 0.29. + After 22 seconds, recent_cpu is 60.39, load_avg is 0.31. + After 24 seconds, recent_cpu is 64.90, load_avg is 0.33. + After 26 seconds, recent_cpu is 69.27, load_avg is 0.35. + After 28 seconds, recent_cpu is 73.50, load_avg is 0.38. + After 30 seconds, recent_cpu is 77.60, load_avg is 0.40. + After 32 seconds, recent_cpu is 81.56, load_avg is 0.42. + After 34 seconds, recent_cpu is 85.40, load_avg is 0.44. + After 36 seconds, recent_cpu is 89.12, load_avg is 0.45. + After 38 seconds, recent_cpu is 92.72, load_avg is 0.47. + After 40 seconds, recent_cpu is 96.20, load_avg is 0.49. + After 42 seconds, recent_cpu is 99.57, load_avg is 0.51. + After 44 seconds, recent_cpu is 102.84, load_avg is 0.52. + After 46 seconds, recent_cpu is 106.00, load_avg is 0.54. + After 48 seconds, recent_cpu is 109.06, load_avg is 0.55. + After 50 seconds, recent_cpu is 112.02, load_avg is 0.57. + After 52 seconds, recent_cpu is 114.89, load_avg is 0.58. + After 54 seconds, recent_cpu is 117.66, load_avg is 0.60. + After 56 seconds, recent_cpu is 120.34, load_avg is 0.61. + After 58 seconds, recent_cpu is 122.94, load_avg is 0.62. + After 60 seconds, recent_cpu is 125.46, load_avg is 0.64. + After 62 seconds, recent_cpu is 127.89, load_avg is 0.65. + After 64 seconds, recent_cpu is 130.25, load_avg is 0.66. + After 66 seconds, recent_cpu is 132.53, load_avg is 0.67. + After 68 seconds, recent_cpu is 134.73, load_avg is 0.68. + After 70 seconds, recent_cpu is 136.86, load_avg is 0.69. + After 72 seconds, recent_cpu is 138.93, load_avg is 0.70. + After 74 seconds, recent_cpu is 140.93, load_avg is 0.71. + After 76 seconds, recent_cpu is 142.86, load_avg is 0.72. + After 78 seconds, recent_cpu is 144.73, load_avg is 0.73. + After 80 seconds, recent_cpu is 146.54, load_avg is 0.74. + After 82 seconds, recent_cpu is 148.29, load_avg is 0.75. + After 84 seconds, recent_cpu is 149.99, load_avg is 0.76. + After 86 seconds, recent_cpu is 151.63, load_avg is 0.76. + After 88 seconds, recent_cpu is 153.21, load_avg is 0.77. + After 90 seconds, recent_cpu is 154.75, load_avg is 0.78. + After 92 seconds, recent_cpu is 156.23, load_avg is 0.79. + After 94 seconds, recent_cpu is 157.67, load_avg is 0.79. + After 96 seconds, recent_cpu is 159.06, load_avg is 0.80. + After 98 seconds, recent_cpu is 160.40, load_avg is 0.81. + After 100 seconds, recent_cpu is 161.70, load_avg is 0.81. + After 102 seconds, recent_cpu is 162.96, load_avg is 0.82. + After 104 seconds, recent_cpu is 164.18, load_avg is 0.83. + After 106 seconds, recent_cpu is 165.35, load_avg is 0.83. + After 108 seconds, recent_cpu is 166.49, load_avg is 0.84. + After 110 seconds, recent_cpu is 167.59, load_avg is 0.84. + After 112 seconds, recent_cpu is 168.66, load_avg is 0.85. + After 114 seconds, recent_cpu is 169.69, load_avg is 0.85. + After 116 seconds, recent_cpu is 170.69, load_avg is 0.86. + After 118 seconds, recent_cpu is 171.65, load_avg is 0.86. + After 120 seconds, recent_cpu is 172.58, load_avg is 0.87. + After 122 seconds, recent_cpu is 173.49, load_avg is 0.87. + After 124 seconds, recent_cpu is 174.36, load_avg is 0.88. + After 126 seconds, recent_cpu is 175.20, load_avg is 0.88. + After 128 seconds, recent_cpu is 176.02, load_avg is 0.88. + After 130 seconds, recent_cpu is 176.81, load_avg is 0.89. + After 132 seconds, recent_cpu is 177.57, load_avg is 0.89. + After 134 seconds, recent_cpu is 178.31, load_avg is 0.89. + After 136 seconds, recent_cpu is 179.02, load_avg is 0.90. + After 138 seconds, recent_cpu is 179.72, load_avg is 0.90. + After 140 seconds, recent_cpu is 180.38, load_avg is 0.90. + After 142 seconds, recent_cpu is 181.03, load_avg is 0.91. + After 144 seconds, recent_cpu is 181.65, load_avg is 0.91. + After 146 seconds, recent_cpu is 182.26, load_avg is 0.91. + After 148 seconds, recent_cpu is 182.84, load_avg is 0.92. + After 150 seconds, recent_cpu is 183.41, load_avg is 0.92. + After 152 seconds, recent_cpu is 183.96, load_avg is 0.92. + After 154 seconds, recent_cpu is 184.49, load_avg is 0.92. + After 156 seconds, recent_cpu is 185.00, load_avg is 0.93. + After 158 seconds, recent_cpu is 185.49, load_avg is 0.93. + After 160 seconds, recent_cpu is 185.97, load_avg is 0.93. + After 162 seconds, recent_cpu is 186.43, load_avg is 0.93. + After 164 seconds, recent_cpu is 186.88, load_avg is 0.94. + After 166 seconds, recent_cpu is 187.31, load_avg is 0.94. + After 168 seconds, recent_cpu is 187.73, load_avg is 0.94. + After 170 seconds, recent_cpu is 188.14, load_avg is 0.94. + After 172 seconds, recent_cpu is 188.53, load_avg is 0.94. + After 174 seconds, recent_cpu is 188.91, load_avg is 0.95. + After 176 seconds, recent_cpu is 189.27, load_avg is 0.95. + After 178 seconds, recent_cpu is 189.63, load_avg is 0.95. + After 180 seconds, recent_cpu is 189.97, load_avg is 0.95. +*/ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +/* Sensitive to assumption that recent_cpu updates happen exactly + when timer_ticks() % TIMER_FREQ == 0. */ + +void +test_mlfqs_recent_1 (void) +{ + int64_t start_time; + int last_elapsed = 0; + + ASSERT (thread_mlfqs); + + do + { + msg ("Sleeping 10 seconds to allow recent_cpu to decay, please wait..."); + start_time = timer_ticks (); + timer_sleep (DIV_ROUND_UP (start_time, TIMER_FREQ) - start_time + + 10 * TIMER_FREQ); + } + while (thread_get_recent_cpu () > 700); + + start_time = timer_ticks (); + for (;;) + { + int elapsed = timer_elapsed (start_time); + if (elapsed % (TIMER_FREQ * 2) == 0 && elapsed > last_elapsed) + { + int recent_cpu = thread_get_recent_cpu (); + int load_avg = thread_get_load_avg (); + int elapsed_seconds = elapsed / TIMER_FREQ; + msg ("After %d seconds, recent_cpu is %d.%02d, load_avg is %d.%02d.", + elapsed_seconds, + recent_cpu / 100, recent_cpu % 100, + load_avg / 100, load_avg % 100); + if (elapsed_seconds >= 180) + break; + } + last_elapsed = elapsed; + } +} diff --git a/src/tests/threads/mlfqs-recent-1.ck b/src/tests/threads/mlfqs-recent-1.ck new file mode 100644 index 0000000..a2ba44d --- /dev/null +++ b/src/tests/threads/mlfqs-recent-1.ck @@ -0,0 +1,31 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::threads::mlfqs; + +our ($test); +my (@output) = read_text_file ("$test.output"); +common_checks ("run", @output); +@output = get_core_output ("run", @output); + +# Get actual values. +local ($_); +my (@actual); +foreach (@output) { + my ($t, $recent_cpu) = /After (\d+) seconds, recent_cpu is (\d+\.\d+),/ + or next; + $actual[$t] = $recent_cpu; +} + +# Calculate expected values. +my ($expected_load_avg, $expected_recent_cpu) + = mlfqs_expected_load ([(1) x 180], [(100) x 180]); +my (@expected) = @$expected_recent_cpu; + +# Compare actual and expected values. +mlfqs_compare ("time", "%.2f", \@actual, \@expected, 2.5, [2, 178, 2], + "Some recent_cpu values were missing or " + . "differed from those expected " + . "by more than 2.5."); +pass; diff --git a/src/tests/threads/mlfqs.pm b/src/tests/threads/mlfqs.pm new file mode 100644 index 0000000..184ac16 --- /dev/null +++ b/src/tests/threads/mlfqs.pm @@ -0,0 +1,146 @@ +# -*- perl -*- +use strict; +use warnings; + +sub mlfqs_expected_load { + my ($ready, $recent_delta) = @_; + my (@load_avg) = 0; + my (@recent_cpu) = 0; + my ($load_avg) = 0; + my ($recent_cpu) = 0; + for my $i (0...$#$ready) { + $load_avg = (59/60) * $load_avg + (1/60) * $ready->[$i]; + push (@load_avg, $load_avg); + + if (defined $recent_delta->[$i]) { + my ($twice_load) = $load_avg * 2; + my ($load_factor) = $twice_load / ($twice_load + 1); + $recent_cpu = ($recent_cpu + $recent_delta->[$i]) * $load_factor; + push (@recent_cpu, $recent_cpu); + } + } + return (\@load_avg, \@recent_cpu); +} + +sub mlfqs_expected_ticks { + my (@nice) = @_; + my ($thread_cnt) = scalar (@nice); + my (@recent_cpu) = (0) x $thread_cnt; + my (@slices) = (0) x $thread_cnt; + my (@fifo) = (0) x $thread_cnt; + my ($next_fifo) = 1; + my ($load_avg) = 0; + for my $i (1...750) { + if ($i % 25 == 0) { + # Update load average. + $load_avg = (59/60) * $load_avg + (1/60) * $thread_cnt; + + # Update recent_cpu. + my ($twice_load) = $load_avg * 2; + my ($load_factor) = $twice_load / ($twice_load + 1); + $recent_cpu[$_] = $recent_cpu[$_] * $load_factor + $nice[$_] + foreach 0...($thread_cnt - 1); + } + + # Update priorities. + my (@priority); + foreach my $j (0...($thread_cnt - 1)) { + my ($priority) = int ($recent_cpu[$j] / 4 + $nice[$j] * 2); + $priority = 0 if $priority < 0; + $priority = 63 if $priority > 63; + push (@priority, $priority); + } + + # Choose thread to run. + my $max = 0; + for my $j (1...$#priority) { + if ($priority[$j] < $priority[$max] + || ($priority[$j] == $priority[$max] + && $fifo[$j] < $fifo[$max])) { + $max = $j; + } + } + $fifo[$max] = $next_fifo++; + + # Run thread. + $recent_cpu[$max] += 4; + $slices[$max] += 4; + } + return @slices; +} + +sub check_mlfqs_fair { + my ($nice, $maxdiff) = @_; + our ($test); + my (@output) = read_text_file ("$test.output"); + common_checks ("run", @output); + @output = get_core_output ("run", @output); + + my (@actual); + local ($_); + foreach (@output) { + my ($id, $count) = /Thread (\d+) received (\d+) ticks\./ or next; + $actual[$id] = $count; + } + + my (@expected) = mlfqs_expected_ticks (@$nice); + mlfqs_compare ("thread", "%d", + \@actual, \@expected, $maxdiff, [0, $#$nice, 1], + "Some tick counts were missing or differed from those " + . "expected by more than $maxdiff."); + pass; +} + +sub mlfqs_compare { + my ($indep_var, $format, + $actual_ref, $expected_ref, $maxdiff, $t_range, $message) = @_; + my ($t_min, $t_max, $t_step) = @$t_range; + + my ($ok) = 1; + for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) { + my ($actual) = $actual_ref->[$t]; + my ($expected) = $expected_ref->[$t]; + $ok = 0, last + if !defined ($actual) || abs ($actual - $expected) > $maxdiff + .01; + } + return if $ok; + + print "$message\n"; + mlfqs_row ($indep_var, "actual", "<->", "expected", "explanation"); + mlfqs_row ("------", "--------", "---", "--------", '-' x 40); + for (my ($t) = $t_min; $t <= $t_max; $t += $t_step) { + my ($actual) = $actual_ref->[$t]; + my ($expected) = $expected_ref->[$t]; + my ($diff, $rationale); + if (!defined $actual) { + $actual = 'undef' ; + $diff = ''; + $rationale = 'Missing value.'; + } else { + my ($delta) = abs ($actual - $expected); + if ($delta > $maxdiff + .01) { + my ($excess) = $delta - $maxdiff; + if ($actual > $expected) { + $diff = '>>>'; + $rationale = sprintf "Too big, by $format.", $excess; + } else { + $diff = '<<<'; + $rationale = sprintf "Too small, by $format.", $excess; + } + } else { + $diff = ' = '; + $rationale = ''; + } + $actual = sprintf ($format, $actual); + } + $expected = sprintf ($format, $expected); + mlfqs_row ($t, $actual, $diff, $expected, $rationale); + } + fail; +} + +sub mlfqs_row { + printf "%6s %8s %3s %-8s %s\n", @_; +} + +1; diff --git a/src/tests/threads/priority-change.c b/src/tests/threads/priority-change.c new file mode 100644 index 0000000..810b05a --- /dev/null +++ b/src/tests/threads/priority-change.c @@ -0,0 +1,31 @@ +/* Verifies that lowering a thread's priority so that it is no + longer the highest-priority thread in the system causes it to + yield immediately. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/thread.h" + +static thread_func changing_thread; + +void +test_priority_change (void) +{ + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + msg ("Creating a high-priority thread 2."); + thread_create ("thread 2", PRI_DEFAULT + 1, changing_thread, NULL); + msg ("Thread 2 should have just lowered its priority."); + thread_set_priority (PRI_DEFAULT - 2); + msg ("Thread 2 should have just exited."); +} + +static void +changing_thread (void *aux UNUSED) +{ + msg ("Thread 2 now lowering priority."); + thread_set_priority (PRI_DEFAULT - 1); + msg ("Thread 2 exiting."); +} diff --git a/src/tests/threads/priority-change.ck b/src/tests/threads/priority-change.ck new file mode 100644 index 0000000..f4d9b2f --- /dev/null +++ b/src/tests/threads/priority-change.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-change) begin +(priority-change) Creating a high-priority thread 2. +(priority-change) Thread 2 now lowering priority. +(priority-change) Thread 2 should have just lowered its priority. +(priority-change) Thread 2 exiting. +(priority-change) Thread 2 should have just exited. +(priority-change) end +EOF +pass; diff --git a/src/tests/threads/priority-condvar.c b/src/tests/threads/priority-condvar.c new file mode 100644 index 0000000..c1efb1b --- /dev/null +++ b/src/tests/threads/priority-condvar.c @@ -0,0 +1,53 @@ +/* Tests that cond_signal() wakes up the highest-priority thread + waiting in cond_wait(). */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func priority_condvar_thread; +static struct lock lock; +static struct condition condition; + +void +test_priority_condvar (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + lock_init (&lock); + cond_init (&condition); + + thread_set_priority (PRI_MIN); + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 7) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, priority_condvar_thread, NULL); + } + + for (i = 0; i < 10; i++) + { + lock_acquire (&lock); + msg ("Signaling..."); + cond_signal (&condition, &lock); + lock_release (&lock); + } +} + +static void +priority_condvar_thread (void *aux UNUSED) +{ + msg ("Thread %s starting.", thread_name ()); + lock_acquire (&lock); + cond_wait (&condition, &lock); + msg ("Thread %s woke up.", thread_name ()); + lock_release (&lock); +} diff --git a/src/tests/threads/priority-condvar.ck b/src/tests/threads/priority-condvar.ck new file mode 100644 index 0000000..195c1ab --- /dev/null +++ b/src/tests/threads/priority-condvar.ck @@ -0,0 +1,39 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-condvar) begin +(priority-condvar) Thread priority 23 starting. +(priority-condvar) Thread priority 22 starting. +(priority-condvar) Thread priority 21 starting. +(priority-condvar) Thread priority 30 starting. +(priority-condvar) Thread priority 29 starting. +(priority-condvar) Thread priority 28 starting. +(priority-condvar) Thread priority 27 starting. +(priority-condvar) Thread priority 26 starting. +(priority-condvar) Thread priority 25 starting. +(priority-condvar) Thread priority 24 starting. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 30 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 29 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 28 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 27 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 26 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 25 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 24 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 23 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 22 woke up. +(priority-condvar) Signaling... +(priority-condvar) Thread priority 21 woke up. +(priority-condvar) end +EOF +pass; diff --git a/src/tests/threads/priority-donate-chain.c b/src/tests/threads/priority-donate-chain.c new file mode 100644 index 0000000..3ffabca --- /dev/null +++ b/src/tests/threads/priority-donate-chain.c @@ -0,0 +1,114 @@ +/* The main thread set its priority to PRI_MIN and creates 7 threads + (thread 1..7) with priorities PRI_MIN + 3, 6, 9, 12, ... + The main thread initializes 8 locks: lock 0..7 and acquires lock 0. + + When thread[i] starts, it first acquires lock[i] (unless i == 7.) + Subsequently, thread[i] attempts to acquire lock[i-1], which is held by + thread[i-1], except for lock[0], which is held by the main thread. + Because the lock is held, thread[i] donates its priority to thread[i-1], + which donates to thread[i-2], and so on until the main thread + receives the donation. + + After threads[1..7] have been created and are blocked on locks[0..7], + the main thread releases lock[0], unblocking thread[1], and being + preempted by it. + Thread[1] then completes acquiring lock[0], then releases lock[0], + then releases lock[1], unblocking thread[2], etc. + Thread[7] finally acquires & releases lock[7] and exits, allowing + thread[6], then thread[5] etc. to run and exit until finally the + main thread exits. + + In addition, interloper threads are created at priority levels + p = PRI_MIN + 2, 5, 8, 11, ... which should not be run until the + corresponding thread with priority p + 1 has finished. + + Written by Godmar Back <gback@cs.vt.edu> */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +#define NESTING_DEPTH 8 + +struct lock_pair + { + struct lock *second; + struct lock *first; + }; + +static thread_func donor_thread_func; +static thread_func interloper_thread_func; + +void +test_priority_donate_chain (void) +{ + int i; + struct lock locks[NESTING_DEPTH - 1]; + struct lock_pair lock_pairs[NESTING_DEPTH]; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + thread_set_priority (PRI_MIN); + + for (i = 0; i < NESTING_DEPTH - 1; i++) + lock_init (&locks[i]); + + lock_acquire (&locks[0]); + msg ("%s got lock.", thread_name ()); + + for (i = 1; i < NESTING_DEPTH; i++) + { + char name[16]; + int thread_priority; + + snprintf (name, sizeof name, "thread %d", i); + thread_priority = PRI_MIN + i * 3; + lock_pairs[i].first = i < NESTING_DEPTH - 1 ? locks + i: NULL; + lock_pairs[i].second = locks + i - 1; + + thread_create (name, thread_priority, donor_thread_func, lock_pairs + i); + msg ("%s should have priority %d. Actual priority: %d.", + thread_name (), thread_priority, thread_get_priority ()); + + snprintf (name, sizeof name, "interloper %d", i); + thread_create (name, thread_priority - 1, interloper_thread_func, NULL); + } + + lock_release (&locks[0]); + msg ("%s finishing with priority %d.", thread_name (), + thread_get_priority ()); +} + +static void +donor_thread_func (void *locks_) +{ + struct lock_pair *locks = locks_; + + if (locks->first) + lock_acquire (locks->first); + + lock_acquire (locks->second); + msg ("%s got lock", thread_name ()); + + lock_release (locks->second); + msg ("%s should have priority %d. Actual priority: %d", + thread_name (), (NESTING_DEPTH - 1) * 3, + thread_get_priority ()); + + if (locks->first) + lock_release (locks->first); + + msg ("%s finishing with priority %d.", thread_name (), + thread_get_priority ()); +} + +static void +interloper_thread_func (void *arg_ UNUSED) +{ + msg ("%s finished.", thread_name ()); +} + +// vim: sw=2 diff --git a/src/tests/threads/priority-donate-chain.ck b/src/tests/threads/priority-donate-chain.ck new file mode 100644 index 0000000..213e649 --- /dev/null +++ b/src/tests/threads/priority-donate-chain.ck @@ -0,0 +1,46 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-chain) begin +(priority-donate-chain) main got lock. +(priority-donate-chain) main should have priority 3. Actual priority: 3. +(priority-donate-chain) main should have priority 6. Actual priority: 6. +(priority-donate-chain) main should have priority 9. Actual priority: 9. +(priority-donate-chain) main should have priority 12. Actual priority: 12. +(priority-donate-chain) main should have priority 15. Actual priority: 15. +(priority-donate-chain) main should have priority 18. Actual priority: 18. +(priority-donate-chain) main should have priority 21. Actual priority: 21. +(priority-donate-chain) thread 1 got lock +(priority-donate-chain) thread 1 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 2 got lock +(priority-donate-chain) thread 2 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 3 got lock +(priority-donate-chain) thread 3 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 4 got lock +(priority-donate-chain) thread 4 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 5 got lock +(priority-donate-chain) thread 5 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 6 got lock +(priority-donate-chain) thread 6 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 7 got lock +(priority-donate-chain) thread 7 should have priority 21. Actual priority: 21 +(priority-donate-chain) thread 7 finishing with priority 21. +(priority-donate-chain) interloper 7 finished. +(priority-donate-chain) thread 6 finishing with priority 18. +(priority-donate-chain) interloper 6 finished. +(priority-donate-chain) thread 5 finishing with priority 15. +(priority-donate-chain) interloper 5 finished. +(priority-donate-chain) thread 4 finishing with priority 12. +(priority-donate-chain) interloper 4 finished. +(priority-donate-chain) thread 3 finishing with priority 9. +(priority-donate-chain) interloper 3 finished. +(priority-donate-chain) thread 2 finishing with priority 6. +(priority-donate-chain) interloper 2 finished. +(priority-donate-chain) thread 1 finishing with priority 3. +(priority-donate-chain) interloper 1 finished. +(priority-donate-chain) main finishing with priority 0. +(priority-donate-chain) end +EOF +pass; diff --git a/src/tests/threads/priority-donate-lower.c b/src/tests/threads/priority-donate-lower.c new file mode 100644 index 0000000..4965d75 --- /dev/null +++ b/src/tests/threads/priority-donate-lower.c @@ -0,0 +1,51 @@ +/* The main thread acquires a lock. Then it creates a + higher-priority thread that blocks acquiring the lock, causing + it to donate their priorities to the main thread. The main + thread attempts to lower its priority, which should not take + effect until the donation is released. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func acquire_thread_func; + +void +test_priority_donate_lower (void) +{ + struct lock lock; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&lock); + lock_acquire (&lock); + thread_create ("acquire", PRI_DEFAULT + 10, acquire_thread_func, &lock); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 10, thread_get_priority ()); + + msg ("Lowering base priority..."); + thread_set_priority (PRI_DEFAULT - 10); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 10, thread_get_priority ()); + lock_release (&lock); + msg ("acquire must already have finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT - 10, thread_get_priority ()); +} + +static void +acquire_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire: got the lock"); + lock_release (lock); + msg ("acquire: done"); +} diff --git a/src/tests/threads/priority-donate-lower.ck b/src/tests/threads/priority-donate-lower.ck new file mode 100644 index 0000000..c9bb61b --- /dev/null +++ b/src/tests/threads/priority-donate-lower.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-lower) begin +(priority-donate-lower) Main thread should have priority 41. Actual priority: 41. +(priority-donate-lower) Lowering base priority... +(priority-donate-lower) Main thread should have priority 41. Actual priority: 41. +(priority-donate-lower) acquire: got the lock +(priority-donate-lower) acquire: done +(priority-donate-lower) acquire must already have finished. +(priority-donate-lower) Main thread should have priority 21. Actual priority: 21. +(priority-donate-lower) end +EOF +pass; diff --git a/src/tests/threads/priority-donate-multiple.c b/src/tests/threads/priority-donate-multiple.c new file mode 100644 index 0000000..df4689c --- /dev/null +++ b/src/tests/threads/priority-donate-multiple.c @@ -0,0 +1,77 @@ +/* The main thread acquires locks A and B, then it creates two + higher-priority threads. Each of these threads blocks + acquiring one of the locks and thus donate their priority to + the main thread. The main thread releases the locks in turn + and relinquishes its donated priorities. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin <startled@leland.stanford.edu>, + Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu + <yph@cs.stanford.edu>. Modified by arens. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func a_thread_func; +static thread_func b_thread_func; + +void +test_priority_donate_multiple (void) +{ + struct lock a, b; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + lock_acquire (&b); + + thread_create ("a", PRI_DEFAULT + 1, a_thread_func, &a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + thread_create ("b", PRI_DEFAULT + 2, b_thread_func, &b); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + + lock_release (&b); + msg ("Thread b should have just finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + lock_release (&a); + msg ("Thread a should have just finished."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +a_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread a acquired lock a."); + lock_release (lock); + msg ("Thread a finished."); +} + +static void +b_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread b acquired lock b."); + lock_release (lock); + msg ("Thread b finished."); +} diff --git a/src/tests/threads/priority-donate-multiple.ck b/src/tests/threads/priority-donate-multiple.ck new file mode 100644 index 0000000..0afd20b --- /dev/null +++ b/src/tests/threads/priority-donate-multiple.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-multiple) begin +(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32. +(priority-donate-multiple) Main thread should have priority 33. Actual priority: 33. +(priority-donate-multiple) Thread b acquired lock b. +(priority-donate-multiple) Thread b finished. +(priority-donate-multiple) Thread b should have just finished. +(priority-donate-multiple) Main thread should have priority 32. Actual priority: 32. +(priority-donate-multiple) Thread a acquired lock a. +(priority-donate-multiple) Thread a finished. +(priority-donate-multiple) Thread a should have just finished. +(priority-donate-multiple) Main thread should have priority 31. Actual priority: 31. +(priority-donate-multiple) end +EOF +pass; diff --git a/src/tests/threads/priority-donate-multiple2.c b/src/tests/threads/priority-donate-multiple2.c new file mode 100644 index 0000000..7f65fef --- /dev/null +++ b/src/tests/threads/priority-donate-multiple2.c @@ -0,0 +1,90 @@ +/* The main thread acquires locks A and B, then it creates three + higher-priority threads. The first two of these threads block + acquiring one of the locks and thus donate their priority to + the main thread. The main thread releases the locks in turn + and relinquishes its donated priorities, allowing the third thread + to run. + + In this test, the main thread releases the locks in a different + order compared to priority-donate-multiple.c. + + Written by Godmar Back <gback@cs.vt.edu>. + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin <startled@leland.stanford.edu>, + Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu + <yph@cs.stanford.edu>. Modified by arens. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func a_thread_func; +static thread_func b_thread_func; +static thread_func c_thread_func; + +void +test_priority_donate_multiple2 (void) +{ + struct lock a, b; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + lock_acquire (&b); + + thread_create ("a", PRI_DEFAULT + 3, a_thread_func, &a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 3, thread_get_priority ()); + + thread_create ("c", PRI_DEFAULT + 1, c_thread_func, NULL); + + thread_create ("b", PRI_DEFAULT + 5, b_thread_func, &b); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 5, thread_get_priority ()); + + lock_release (&a); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 5, thread_get_priority ()); + + lock_release (&b); + msg ("Threads b, a, c should have just finished, in that order."); + msg ("Main thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +a_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread a acquired lock a."); + lock_release (lock); + msg ("Thread a finished."); +} + +static void +b_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("Thread b acquired lock b."); + lock_release (lock); + msg ("Thread b finished."); +} + +static void +c_thread_func (void *a_ UNUSED) +{ + msg ("Thread c finished."); +} diff --git a/src/tests/threads/priority-donate-multiple2.ck b/src/tests/threads/priority-donate-multiple2.ck new file mode 100644 index 0000000..b23533a --- /dev/null +++ b/src/tests/threads/priority-donate-multiple2.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-multiple2) begin +(priority-donate-multiple2) Main thread should have priority 34. Actual priority: 34. +(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36. +(priority-donate-multiple2) Main thread should have priority 36. Actual priority: 36. +(priority-donate-multiple2) Thread b acquired lock b. +(priority-donate-multiple2) Thread b finished. +(priority-donate-multiple2) Thread a acquired lock a. +(priority-donate-multiple2) Thread a finished. +(priority-donate-multiple2) Thread c finished. +(priority-donate-multiple2) Threads b, a, c should have just finished, in that order. +(priority-donate-multiple2) Main thread should have priority 31. Actual priority: 31. +(priority-donate-multiple2) end +EOF +pass; diff --git a/src/tests/threads/priority-donate-nest.c b/src/tests/threads/priority-donate-nest.c new file mode 100644 index 0000000..3a3a9a5 --- /dev/null +++ b/src/tests/threads/priority-donate-nest.c @@ -0,0 +1,94 @@ +/* Low-priority main thread L acquires lock A. Medium-priority + thread M then acquires lock B then blocks on acquiring lock A. + High-priority thread H then blocks on acquiring lock B. Thus, + thread H donates its priority to M, which in turn donates it + to thread L. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin <startled@leland.stanford.edu>, + Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu + <yph@cs.stanford.edu>. Modified by arens. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct locks + { + struct lock *a; + struct lock *b; + }; + +static thread_func medium_thread_func; +static thread_func high_thread_func; + +void +test_priority_donate_nest (void) +{ + struct lock a, b; + struct locks locks; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&a); + lock_init (&b); + + lock_acquire (&a); + + locks.a = &a; + locks.b = &b; + thread_create ("medium", PRI_DEFAULT + 1, medium_thread_func, &locks); + thread_yield (); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + + thread_create ("high", PRI_DEFAULT + 2, high_thread_func, &b); + thread_yield (); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + + lock_release (&a); + thread_yield (); + msg ("Medium thread should just have finished."); + msg ("Low thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT, thread_get_priority ()); +} + +static void +medium_thread_func (void *locks_) +{ + struct locks *locks = locks_; + + lock_acquire (locks->b); + lock_acquire (locks->a); + + msg ("Medium thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + msg ("Medium thread got the lock."); + + lock_release (locks->a); + thread_yield (); + + lock_release (locks->b); + thread_yield (); + + msg ("High thread should have just finished."); + msg ("Middle thread finished."); +} + +static void +high_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("High thread got the lock."); + lock_release (lock); + msg ("High thread finished."); +} diff --git a/src/tests/threads/priority-donate-nest.ck b/src/tests/threads/priority-donate-nest.ck new file mode 100644 index 0000000..923460e --- /dev/null +++ b/src/tests/threads/priority-donate-nest.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-nest) begin +(priority-donate-nest) Low thread should have priority 32. Actual priority: 32. +(priority-donate-nest) Low thread should have priority 33. Actual priority: 33. +(priority-donate-nest) Medium thread should have priority 33. Actual priority: 33. +(priority-donate-nest) Medium thread got the lock. +(priority-donate-nest) High thread got the lock. +(priority-donate-nest) High thread finished. +(priority-donate-nest) High thread should have just finished. +(priority-donate-nest) Middle thread finished. +(priority-donate-nest) Medium thread should just have finished. +(priority-donate-nest) Low thread should have priority 31. Actual priority: 31. +(priority-donate-nest) end +EOF +pass; diff --git a/src/tests/threads/priority-donate-one.c b/src/tests/threads/priority-donate-one.c new file mode 100644 index 0000000..3189f3a --- /dev/null +++ b/src/tests/threads/priority-donate-one.c @@ -0,0 +1,65 @@ +/* The main thread acquires a lock. Then it creates two + higher-priority threads that block acquiring the lock, causing + them to donate their priorities to the main thread. When the + main thread releases the lock, the other threads should + acquire it in priority order. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by Matt Franklin <startled@leland.stanford.edu>, + Greg Hutchins <gmh@leland.stanford.edu>, Yu Ping Hu + <yph@cs.stanford.edu>. Modified by arens. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func acquire1_thread_func; +static thread_func acquire2_thread_func; + +void +test_priority_donate_one (void) +{ + struct lock lock; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&lock); + lock_acquire (&lock); + thread_create ("acquire1", PRI_DEFAULT + 1, acquire1_thread_func, &lock); + msg ("This thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 1, thread_get_priority ()); + thread_create ("acquire2", PRI_DEFAULT + 2, acquire2_thread_func, &lock); + msg ("This thread should have priority %d. Actual priority: %d.", + PRI_DEFAULT + 2, thread_get_priority ()); + lock_release (&lock); + msg ("acquire2, acquire1 must already have finished, in that order."); + msg ("This should be the last line before finishing this test."); +} + +static void +acquire1_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire1: got the lock"); + lock_release (lock); + msg ("acquire1: done"); +} + +static void +acquire2_thread_func (void *lock_) +{ + struct lock *lock = lock_; + + lock_acquire (lock); + msg ("acquire2: got the lock"); + lock_release (lock); + msg ("acquire2: done"); +} diff --git a/src/tests/threads/priority-donate-one.ck b/src/tests/threads/priority-donate-one.ck new file mode 100644 index 0000000..b7c8e6f --- /dev/null +++ b/src/tests/threads/priority-donate-one.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-one) begin +(priority-donate-one) This thread should have priority 32. Actual priority: 32. +(priority-donate-one) This thread should have priority 33. Actual priority: 33. +(priority-donate-one) acquire2: got the lock +(priority-donate-one) acquire2: done +(priority-donate-one) acquire1: got the lock +(priority-donate-one) acquire1: done +(priority-donate-one) acquire2, acquire1 must already have finished, in that order. +(priority-donate-one) This should be the last line before finishing this test. +(priority-donate-one) end +EOF +pass; diff --git a/src/tests/threads/priority-donate-sema.c b/src/tests/threads/priority-donate-sema.c new file mode 100644 index 0000000..b33cb72 --- /dev/null +++ b/src/tests/threads/priority-donate-sema.c @@ -0,0 +1,82 @@ +/* Low priority thread L acquires a lock, then blocks downing a + semaphore. Medium priority thread M then blocks waiting on + the same semaphore. Next, high priority thread H attempts to + acquire the lock, donating its priority to L. + + Next, the main thread ups the semaphore, waking up L. L + releases the lock, which wakes up H. H "up"s the semaphore, + waking up M. H terminates, then M, then L, and finally the + main thread. + + Written by Godmar Back <gback@cs.vt.edu>. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct lock_and_sema + { + struct lock lock; + struct semaphore sema; + }; + +static thread_func l_thread_func; +static thread_func m_thread_func; +static thread_func h_thread_func; + +void +test_priority_donate_sema (void) +{ + struct lock_and_sema ls; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + lock_init (&ls.lock); + sema_init (&ls.sema, 0); + thread_create ("low", PRI_DEFAULT + 1, l_thread_func, &ls); + thread_create ("med", PRI_DEFAULT + 3, m_thread_func, &ls); + thread_create ("high", PRI_DEFAULT + 5, h_thread_func, &ls); + sema_up (&ls.sema); + msg ("Main thread finished."); +} + +static void +l_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + lock_acquire (&ls->lock); + msg ("Thread L acquired lock."); + sema_down (&ls->sema); + msg ("Thread L downed semaphore."); + lock_release (&ls->lock); + msg ("Thread L finished."); +} + +static void +m_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + sema_down (&ls->sema); + msg ("Thread M finished."); +} + +static void +h_thread_func (void *ls_) +{ + struct lock_and_sema *ls = ls_; + + lock_acquire (&ls->lock); + msg ("Thread H acquired lock."); + + sema_up (&ls->sema); + lock_release (&ls->lock); + msg ("Thread H finished."); +} diff --git a/src/tests/threads/priority-donate-sema.ck b/src/tests/threads/priority-donate-sema.ck new file mode 100644 index 0000000..92b8d07 --- /dev/null +++ b/src/tests/threads/priority-donate-sema.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-donate-sema) begin +(priority-donate-sema) Thread L acquired lock. +(priority-donate-sema) Thread L downed semaphore. +(priority-donate-sema) Thread H acquired lock. +(priority-donate-sema) Thread H finished. +(priority-donate-sema) Thread M finished. +(priority-donate-sema) Thread L finished. +(priority-donate-sema) Main thread finished. +(priority-donate-sema) end +EOF +pass; diff --git a/src/tests/threads/priority-fifo.c b/src/tests/threads/priority-fifo.c new file mode 100644 index 0000000..3af98a3 --- /dev/null +++ b/src/tests/threads/priority-fifo.c @@ -0,0 +1,99 @@ +/* Creates several threads all at the same priority and ensures + that they consistently run in the same round-robin order. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by by Matt Franklin + <startled@leland.stanford.edu>, Greg Hutchins + <gmh@leland.stanford.edu>, Yu Ping Hu <yph@cs.stanford.edu>. + Modified by arens. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "devices/timer.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" + +struct simple_thread_data + { + int id; /* Sleeper ID. */ + int iterations; /* Iterations so far. */ + struct lock *lock; /* Lock on output. */ + int **op; /* Output buffer position. */ + }; + +#define THREAD_CNT 16 +#define ITER_CNT 16 + +static thread_func simple_thread_func; + +void +test_priority_fifo (void) +{ + struct simple_thread_data data[THREAD_CNT]; + struct lock lock; + int *output, *op; + int i, cnt; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + msg ("%d threads will iterate %d times in the same order each time.", + THREAD_CNT, ITER_CNT); + msg ("If the order varies then there is a bug."); + + output = op = malloc (sizeof *output * THREAD_CNT * ITER_CNT * 2); + ASSERT (output != NULL); + lock_init (&lock); + + thread_set_priority (PRI_DEFAULT + 2); + for (i = 0; i < THREAD_CNT; i++) + { + char name[16]; + struct simple_thread_data *d = data + i; + snprintf (name, sizeof name, "%d", i); + d->id = i; + d->iterations = 0; + d->lock = &lock; + d->op = &op; + thread_create (name, PRI_DEFAULT + 1, simple_thread_func, d); + } + + thread_set_priority (PRI_DEFAULT); + /* All the other threads now run to termination here. */ + ASSERT (lock.holder == NULL); + + cnt = 0; + for (; output < op; output++) + { + struct simple_thread_data *d; + + ASSERT (*output >= 0 && *output < THREAD_CNT); + d = data + *output; + if (cnt % THREAD_CNT == 0) + printf ("(priority-fifo) iteration:"); + printf (" %d", d->id); + if (++cnt % THREAD_CNT == 0) + printf ("\n"); + d->iterations++; + } +} + +static void +simple_thread_func (void *data_) +{ + struct simple_thread_data *data = data_; + int i; + + for (i = 0; i < ITER_CNT; i++) + { + lock_acquire (data->lock); + *(*data->op)++ = data->id; + lock_release (data->lock); + thread_yield (); + } +} diff --git a/src/tests/threads/priority-fifo.ck b/src/tests/threads/priority-fifo.ck new file mode 100644 index 0000000..11f1dd3 --- /dev/null +++ b/src/tests/threads/priority-fifo.ck @@ -0,0 +1,63 @@ +# -*- perl -*- + +# The expected output looks like this: +# +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# (priority-fifo) iteration: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 +# +# A different permutation of 0...15 is acceptable, but every line must +# be in the same order. + +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +my ($thread_cnt) = 16; +my ($iter_cnt) = 16; +my (@order); +my (@t) = (-1) x $thread_cnt; + +my (@iterations) = grep (/iteration:/, @output); +fail "No iterations found in output.\n" if !@iterations; + +my (@numbering) = $iterations[0] =~ /(\d+)/g; +fail "First iteration does not list exactly $thread_cnt threads.\n" + if @numbering != $thread_cnt; + +my (@sorted_numbering) = sort { $a <=> $b } @numbering; +for my $i (0...$#sorted_numbering) { + if ($sorted_numbering[$i] != $i) { + fail "First iteration does not list all threads " + . "0...$#sorted_numbering\n"; + } +} + +for my $i (1...$#iterations) { + if ($iterations[$i] ne $iterations[0]) { + fail "Iteration $i differs from iteration 0\n"; + } +} + +fail "$iter_cnt iterations expected but " . scalar (@iterations) . " found\n" + if $iter_cnt != @iterations; + +pass; diff --git a/src/tests/threads/priority-preempt.c b/src/tests/threads/priority-preempt.c new file mode 100644 index 0000000..3c3aacb --- /dev/null +++ b/src/tests/threads/priority-preempt.c @@ -0,0 +1,41 @@ +/* Ensures that a high-priority thread really preempts. + + Based on a test originally submitted for Stanford's CS 140 in + winter 1999 by by Matt Franklin + <startled@leland.stanford.edu>, Greg Hutchins + <gmh@leland.stanford.edu>, Yu Ping Hu <yph@cs.stanford.edu>. + Modified by arens. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/synch.h" +#include "threads/thread.h" + +static thread_func simple_thread_func; + +void +test_priority_preempt (void) +{ + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + /* Make sure our priority is the default. */ + ASSERT (thread_get_priority () == PRI_DEFAULT); + + thread_create ("high-priority", PRI_DEFAULT + 1, simple_thread_func, NULL); + msg ("The high-priority thread should have already completed."); +} + +static void +simple_thread_func (void *aux UNUSED) +{ + int i; + + for (i = 0; i < 5; i++) + { + msg ("Thread %s iteration %d", thread_name (), i); + thread_yield (); + } + msg ("Thread %s done!", thread_name ()); +} diff --git a/src/tests/threads/priority-preempt.ck b/src/tests/threads/priority-preempt.ck new file mode 100644 index 0000000..43a26ee --- /dev/null +++ b/src/tests/threads/priority-preempt.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-preempt) begin +(priority-preempt) Thread high-priority iteration 0 +(priority-preempt) Thread high-priority iteration 1 +(priority-preempt) Thread high-priority iteration 2 +(priority-preempt) Thread high-priority iteration 3 +(priority-preempt) Thread high-priority iteration 4 +(priority-preempt) Thread high-priority done! +(priority-preempt) The high-priority thread should have already completed. +(priority-preempt) end +EOF +pass; diff --git a/src/tests/threads/priority-sema.c b/src/tests/threads/priority-sema.c new file mode 100644 index 0000000..2834a88 --- /dev/null +++ b/src/tests/threads/priority-sema.c @@ -0,0 +1,45 @@ +/* Tests that the highest-priority thread waiting on a semaphore + is the first to wake up. */ + +#include <stdio.h> +#include "tests/threads/tests.h" +#include "threads/init.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "devices/timer.h" + +static thread_func priority_sema_thread; +static struct semaphore sema; + +void +test_priority_sema (void) +{ + int i; + + /* This test does not work with the MLFQS. */ + ASSERT (!thread_mlfqs); + + sema_init (&sema, 0); + thread_set_priority (PRI_MIN); + for (i = 0; i < 10; i++) + { + int priority = PRI_DEFAULT - (i + 3) % 10 - 1; + char name[16]; + snprintf (name, sizeof name, "priority %d", priority); + thread_create (name, priority, priority_sema_thread, NULL); + } + + for (i = 0; i < 10; i++) + { + sema_up (&sema); + msg ("Back in main thread."); + } +} + +static void +priority_sema_thread (void *aux UNUSED) +{ + sema_down (&sema); + msg ("Thread %s woke up.", thread_name ()); +} diff --git a/src/tests/threads/priority-sema.ck b/src/tests/threads/priority-sema.ck new file mode 100644 index 0000000..559988d --- /dev/null +++ b/src/tests/threads/priority-sema.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(priority-sema) begin +(priority-sema) Thread priority 30 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 29 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 28 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 27 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 26 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 25 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 24 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 23 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 22 woke up. +(priority-sema) Back in main thread. +(priority-sema) Thread priority 21 woke up. +(priority-sema) Back in main thread. +(priority-sema) end +EOF +pass; diff --git a/src/tests/threads/simplethreadtest.c b/src/tests/threads/simplethreadtest.c new file mode 100644 index 0000000..f9c058e --- /dev/null +++ b/src/tests/threads/simplethreadtest.c @@ -0,0 +1,68 @@ +// threadtest.cc +// Simple test case for the threads assignment. +// +// Create two threads, and have them context switch +// back and forth between themselves by calling Thread::Yield, +// to illustratethe inner workings of the thread system. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. +// +// Modified by Viacheslav Izosimov +// - transition from C++ to C (from Nachos to Pintos) + + +//#include "copyright.h" +//#include "system.h" +#include "threads/boundedbuffer.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "tests/threads/tests.h" +#include <stdio.h> +#include <string.h> + +//---------------------------------------------------------------------- +// SimpleThread +// Loop 5 times, yielding the CPU to another ready thread +// each iteration. +// +// "which" is simply a number identifying the thread, for debugging +// purposes. +//---------------------------------------------------------------------- + +void SimpleThread(void *); + +void +SimpleThread(void * which) +{ + int num; + + for (num = 0; num < 5; num++) { + printf("*** thread %d looped %d times\n", (int)which, num); + thread_yield(); +// currentThread->Yield(); + } +} + +//---------------------------------------------------------------------- +// ThreadTest +// Set up a ping-pong between two threads, by forking a thread +// to call SimpleThread, and then calling SimpleThread ourselves. +//---------------------------------------------------------------------- + +void +SimpleThreadTest(void) +{ +// DEBUG('t', "Entering SimpleTest"); + +// Thread *t = new Thread("forked thread"); + char *t_name = "forked thread"; + printf("Entering SimpleTest"); + + thread_create(t_name, PRI_MIN, SimpleThread, (void *)1); + +// t->Fork(SimpleThread, 1); + SimpleThread((void *)0); +} diff --git a/src/tests/threads/tests.c b/src/tests/threads/tests.c new file mode 100644 index 0000000..b8d090d --- /dev/null +++ b/src/tests/threads/tests.c @@ -0,0 +1,104 @@ +#include "tests/threads/tests.h" +#include <debug.h> +#include <string.h> +#include <stdio.h> + +struct test + { + const char *name; + test_func *function; + }; + +static const struct test tests[] = + { + {"alarm-single", test_alarm_single}, + {"alarm-multiple", test_alarm_multiple}, + {"alarm-simultaneous", test_alarm_simultaneous}, + {"alarm-priority", test_alarm_priority}, + {"alarm-zero", test_alarm_zero}, + {"alarm-negative", test_alarm_negative}, + {"priority-change", test_priority_change}, + {"priority-donate-one", test_priority_donate_one}, + {"priority-donate-multiple", test_priority_donate_multiple}, + {"priority-donate-multiple2", test_priority_donate_multiple2}, + {"priority-donate-nest", test_priority_donate_nest}, + {"priority-donate-sema", test_priority_donate_sema}, + {"priority-donate-lower", test_priority_donate_lower}, + {"priority-donate-chain", test_priority_donate_chain}, + {"priority-fifo", test_priority_fifo}, + {"priority-preempt", test_priority_preempt}, + {"priority-sema", test_priority_sema}, + {"priority-condvar", test_priority_condvar}, + {"mlfqs-load-1", test_mlfqs_load_1}, + {"mlfqs-load-60", test_mlfqs_load_60}, + {"mlfqs-load-avg", test_mlfqs_load_avg}, + {"mlfqs-recent-1", test_mlfqs_recent_1}, + {"mlfqs-fair-2", test_mlfqs_fair_2}, + {"mlfqs-fair-20", test_mlfqs_fair_20}, + {"mlfqs-nice-2", test_mlfqs_nice_2}, + {"mlfqs-nice-10", test_mlfqs_nice_10}, + {"mlfqs-block", test_mlfqs_block}, + {"threadtest", ThreadTest}, + {"simplethreadtest", SimpleThreadTest} + }; + +static const char *test_name; + +/* Runs the test named NAME. */ +void +run_test (const char *name) +{ + const struct test *t; + + for (t = tests; t < tests + sizeof tests / sizeof *tests; t++) + if (!strcmp (name, t->name)) + { + test_name = name; + msg ("begin"); + t->function (); + msg ("end"); + return; + } + PANIC ("no test named \"%s\"", name); +} + +/* Prints FORMAT as if with printf(), + prefixing the output by the name of the test + and following it with a new-line character. */ +void +msg (const char *format, ...) +{ + va_list args; + + printf ("(%s) ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); +} + +/* Prints failure message FORMAT as if with printf(), + prefixing the output by the name of the test and FAIL: + and following it with a new-line character, + and then panics the kernel. */ +void +fail (const char *format, ...) +{ + va_list args; + + printf ("(%s) FAIL: ", test_name); + va_start (args, format); + vprintf (format, args); + va_end (args); + putchar ('\n'); + + PANIC ("test failed"); +} + +/* Prints a message indicating the current test passed. */ +void +pass (void) +{ + printf ("(%s) PASS\n", test_name); +} + diff --git a/src/tests/threads/tests.h b/src/tests/threads/tests.h new file mode 100644 index 0000000..1fe3582 --- /dev/null +++ b/src/tests/threads/tests.h @@ -0,0 +1,43 @@ +#ifndef TESTS_THREADS_TESTS_H +#define TESTS_THREADS_TESTS_H + +void run_test (const char *); + +typedef void test_func (void); + +extern test_func test_alarm_single; +extern test_func test_alarm_multiple; +extern test_func test_alarm_simultaneous; +extern test_func test_alarm_priority; +extern test_func test_alarm_zero; +extern test_func test_alarm_negative; +extern test_func test_priority_change; +extern test_func test_priority_donate_one; +extern test_func test_priority_donate_multiple; +extern test_func test_priority_donate_multiple2; +extern test_func test_priority_donate_sema; +extern test_func test_priority_donate_nest; +extern test_func test_priority_donate_lower; +extern test_func test_priority_donate_chain; +extern test_func test_priority_fifo; +extern test_func test_priority_preempt; +extern test_func test_priority_sema; +extern test_func test_priority_condvar; +extern test_func test_mlfqs_load_1; +extern test_func test_mlfqs_load_60; +extern test_func test_mlfqs_load_avg; +extern test_func test_mlfqs_recent_1; +extern test_func test_mlfqs_fair_2; +extern test_func test_mlfqs_fair_20; +extern test_func test_mlfqs_nice_2; +extern test_func test_mlfqs_nice_10; +extern test_func test_mlfqs_block; +extern test_func ThreadTest; +extern test_func SimpleThreadTest; + +void msg (const char *, ...); +void fail (const char *, ...); +void pass (void); + +#endif /* tests/threads/tests.h */ + diff --git a/src/tests/threads/threadtest.c b/src/tests/threads/threadtest.c new file mode 100644 index 0000000..363d532 --- /dev/null +++ b/src/tests/threads/threadtest.c @@ -0,0 +1,250 @@ +// threadtest.c +// Simple test case for the threads assignment. +// +// Create seven threads, and have them context switch +// back and forth between themselves by calling Thread::Yield, +// to illustrate the inner workings of the thread system. +// +// Copyright (c) 1992-1993 The Regents of the University of California. +// All rights reserved. See copyright.h for copyright notice and limitation +// of liability and disclaimer of warranty provisions. +// +// Modified by Levon Saldamli. +// Modified by Andrzej Bednarski: +// - added #ifdef (do not require modification of Makefile) +// Modified by Vlad Jahundovics: +// - transition from C++ to C (from Nachos to Pintos) + +//#ifdef THREADS + +//#include "copyright.h" +//#include "system.h" +//#include "synch.h" +//#include "boundedbuffer.h" +#include "threads/boundedbuffer.h" +#include "threads/malloc.h" +#include "threads/synch.h" +#include "threads/thread.h" +#include "tests/threads/tests.h" +#include <stdio.h> +#include <string.h> + +#define NBR_OF_PROD 50 +//const int nbr_of_prod = 50; +#define NBR_OF_CON 50 +//const int nbr_of_con = 50; +const int prod_switch = 30; +const int con_switch = 20; + +char * prod_name[NBR_OF_PROD]; +char * con_name[NBR_OF_CON]; + +char * msg_array[NBR_OF_PROD]; + +char * received_msg_array[NBR_OF_PROD]; +int received_msg_pos[NBR_OF_PROD]; + + +struct bounded_buffer bounded_buffer[2]; + +struct lock readlock[2]; + +struct lock start_lock; +struct condition start_cond; + +int started_threads; + +/* +class Data +{ +public: + Data(char ch, char* s, int i) { + c = ch; + sender = s; + sender_index = i; + } + char c; + char *sender; + int sender_index; +}; +*/ + +struct Data +{ + char c; + char *sender; + int sender_index; +}; + +void data_init(struct Data *data, char ch, char* s, int i); +void WaitForStart(void); +void Producer(void *index); +void Consumer(void *index); + +void data_init(struct Data *data, char ch, char* s, int i) +{ + data->c = ch; + data->sender = s; + data->sender_index = i; +} + +void +WaitForStart(void) { + lock_acquire(&start_lock); + printf("%s is waiting for start signal\n", thread_name()); + cond_wait(&start_cond,&start_lock); + printf("%s is starting\n", thread_name()); + started_threads++; + lock_release(&start_lock); +} + +void +Producer(void *index) +{ + char *sender_name; + size_t sender_name_length; + WaitForStart(); + int buf=0; + if ((int) index >= prod_switch) + buf=1; + + char *msg = msg_array[(int) index]; + void *p; + + for (; *msg != '\0'; ++msg) { + p = malloc(sizeof(struct Data)); + sender_name_length = strlen(thread_name())+1; + sender_name = calloc(sizeof(char), sender_name_length); + strlcpy(sender_name,thread_name(), sender_name_length); + data_init(p, *msg, sender_name, (int) index); + bb_write(&bounded_buffer[buf],(int) p); + thread_yield(); + } + p = malloc(sizeof(struct Data)); + sender_name_length = strlen(thread_name())+1; + sender_name = calloc(sizeof(char), sender_name_length); + strlcpy(sender_name,thread_name(), sender_name_length); + data_init(p, 0, sender_name, (int) index); + bb_write(&bounded_buffer[buf],(int) p); + printf("%s has finished sending.\n", thread_name()); +} + +void +Consumer(void *index) +{ + WaitForStart(); + + int buf=0; + if ((int) index >= con_switch) + buf=1; + + while (true) { + + lock_acquire(&readlock[buf]); + struct Data *data = (struct Data*) bb_read(&bounded_buffer[buf]); + int i=data->sender_index; + received_msg_array[i][received_msg_pos[i]] = data->c; + received_msg_pos[i]++; + lock_release(&readlock[buf]); + + if (data->c != 0); + /* DEBUG('c', "%s received from %s: %c\n", + currentThread->getName(), + data->sender, + data->c);*/ + else + printf("\n%s: %s's total message was: \n\"%s\"\n", + thread_name(), + data->sender, + received_msg_array[i]); + free(data->sender); + free(data); + thread_yield(); + } +} + + + +//---------------------------------------------------------------------- +// ThreadTest +// Creates three consumers and three producers. The producers +// write different messages to the buffer. The main thread also +// calls Consumer, resulting in four consumers. +//---------------------------------------------------------------------- + +void +ThreadTest(void) +{ + //DEBUG('t', "Entering SimpleTest\n"); + const int nmsg = 5; + char *msg[nmsg]; + msg[0] = "Computer, compute to the last digit the value of pi!"; + msg[1] = "What is now proved was once only imagined."; + msg[2] = "Insufficient facts always invites danger, Captain."; + msg[3] = "The Federation's gone; the Borg is everywhere!"; + msg[4] = "Live long and prosper, Spock."; + + // bounded_buffer[0] = new BoundedBuffer(5); + // bounded_buffer[1] = new BoundedBuffer(5); + + printf("ThreadTest has just started! It's thread name is %s.\n", thread_name()); + + bb_init(&bounded_buffer[0],5); + bb_init(&bounded_buffer[1],5); + lock_init(&readlock[0]); + lock_init(&readlock[1]); + // readlock[0] = new Lock("Read lock 0"); + // readlock[1] = new Lock("Read lock 1"); + + lock_init(&start_lock); + // start_lock = new Lock("Start lock"); + cond_init(&start_cond); + // start_cond = new Condition("Start cond"); + started_threads = 0; + + char pname[] = "Producer"; + char cname[] = "Consumer"; + int i; + + for (i=0;i < NBR_OF_PROD; i++) { + // char *str = new char[strlen(pname)+4]; + char *str = (char *) calloc(sizeof(char), strlen(pname)+4); + snprintf(str,strlen(pname)+4,"%s %02d", pname, i); + + prod_name[i] = str; + msg_array[i] = msg[i%nmsg]; + received_msg_array[i] = (char *) calloc(sizeof(char),strlen(msg_array[i])+1); + //received_msg_array[i] = new char[strlen(msg_array[i])+1]; + received_msg_pos[i] = 0; + // printf("Creating thread with the name: %s\n",str); + thread_create(str, PRI_MIN, Producer, (void *) i); + free(str); + // Thread *t = new Thread(str); + // t->Fork(Producer, i); + } + for (i=0;i < NBR_OF_CON; i++) { + char *str = (char *) calloc(sizeof(char), strlen(cname)+4); + //char *str = new char[strlen(cname)+4]; + snprintf(str, strlen(cname)+4,"%s %02d", cname, i); + // printf("Creating thread with the name: %s\n",str); + thread_create(str, PRI_MIN, Consumer, (void *) i); + free(str); + //Thread *t = new Thread(str); + //t->Fork(Consumer, i); + } + + thread_yield(); + + lock_acquire(&start_lock); + while (started_threads < (NBR_OF_PROD + NBR_OF_CON) ) { + printf("\n\n%s : All threads haven't started. Broadcasting start signal to all threads\n\n", thread_name()); + cond_broadcast(&start_cond, &start_lock); + // start_cond->Broadcast(start_lock); + lock_release(&start_lock); + thread_yield(); + lock_acquire(&start_lock); + } + printf("\n\nAll threads have started. Finishing %s\n\n", thread_name()); +} + +//#endif // THREADS diff --git a/src/tests/userprog/Grading b/src/tests/userprog/Grading new file mode 100644 index 0000000..f70dc99 --- /dev/null +++ b/src/tests/userprog/Grading @@ -0,0 +1,11 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# This project is primarily about implementing system calls. +# If you do so properly, the base file system functionality +# should come "for free". Thus, the points emphasis below. + +35% tests/userprog/Rubric.functionality +25% tests/userprog/Rubric.robustness +10% tests/userprog/no-vm/Rubric +30% tests/filesys/base/Rubric diff --git a/src/tests/userprog/Make.tests b/src/tests/userprog/Make.tests new file mode 100644 index 0000000..1258582 --- /dev/null +++ b/src/tests/userprog/Make.tests @@ -0,0 +1,125 @@ +# -*- makefile -*- + +tests/%.output: FSDISK = 2 +tests/%.output: PUTFILES = $(filter-out os.dsk, $^) + +tests/userprog_TESTS = $(addprefix tests/userprog/,args-none \ +args-single args-multiple args-many args-dbl-space sc-bad-sp \ +sc-bad-arg sc-boundary sc-boundary-2 halt exit create-normal \ +create-empty create-null create-bad-ptr create-long create-exists \ +create-bound open-normal open-missing open-boundary open-empty \ +open-null open-bad-ptr open-twice close-normal close-twice close-stdin \ +close-stdout close-bad-fd read-normal read-bad-ptr read-boundary \ +read-zero read-stdout read-bad-fd write-normal write-bad-ptr \ +write-boundary write-zero write-stdin write-bad-fd exec-once exec-arg \ +exec-multiple exec-missing exec-bad-ptr wait-simple wait-twice \ +wait-killed wait-bad-pid multi-recurse multi-child-fd) + + + +tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \ +tests/userprog/,child-simple child-args child-bad child-close) + + +tests/userprog/args-none_SRC = tests/userprog/args.c +tests/userprog/args-single_SRC = tests/userprog/args.c +tests/userprog/args-multiple_SRC = tests/userprog/args.c +tests/userprog/args-many_SRC = tests/userprog/args.c +tests/userprog/args-dbl-space_SRC = tests/userprog/args.c +tests/userprog/sc-bad-sp_SRC = tests/userprog/sc-bad-sp.c tests/main.c +tests/userprog/sc-bad-arg_SRC = tests/userprog/sc-bad-arg.c tests/main.c + +tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/halt_SRC = tests/userprog/halt.c tests/main.c +tests/userprog/exit_SRC = tests/userprog/exit.c tests/main.c +tests/userprog/create-normal_SRC = tests/userprog/create-normal.c tests/main.c +tests/userprog/create-empty_SRC = tests/userprog/create-empty.c tests/main.c +tests/userprog/create-null_SRC = tests/userprog/create-null.c tests/main.c +tests/userprog/create-bad-ptr_SRC = tests/userprog/create-bad-ptr.c \ +tests/main.c +tests/userprog/create-long_SRC = tests/userprog/create-long.c tests/main.c +tests/userprog/create-exists_SRC = tests/userprog/create-exists.c tests/main.c +tests/userprog/create-bound_SRC = tests/userprog/create-bound.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/open-normal_SRC = tests/userprog/open-normal.c tests/main.c +tests/userprog/open-missing_SRC = tests/userprog/open-missing.c tests/main.c +tests/userprog/open-boundary_SRC = tests/userprog/open-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/open-empty_SRC = tests/userprog/open-empty.c tests/main.c +tests/userprog/open-null_SRC = tests/userprog/open-null.c tests/main.c +tests/userprog/open-bad-ptr_SRC = tests/userprog/open-bad-ptr.c tests/main.c +tests/userprog/open-twice_SRC = tests/userprog/open-twice.c tests/main.c +tests/userprog/close-normal_SRC = tests/userprog/close-normal.c tests/main.c +tests/userprog/close-twice_SRC = tests/userprog/close-twice.c tests/main.c +tests/userprog/close-stdin_SRC = tests/userprog/close-stdin.c tests/main.c +tests/userprog/close-stdout_SRC = tests/userprog/close-stdout.c tests/main.c +tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c +tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c +tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c +tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c +tests/userprog/read-stdout_SRC = tests/userprog/read-stdout.c tests/main.c +tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c +tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c +tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c +tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c +tests/userprog/write-stdin_SRC = tests/userprog/write-stdin.c tests/main.c +tests/userprog/write-bad-fd_SRC = tests/userprog/write-bad-fd.c tests/main.c +tests/userprog/exec-once_SRC = tests/userprog/exec-once.c tests/main.c +tests/userprog/exec-arg_SRC = tests/userprog/exec-arg.c tests/main.c +tests/userprog/exec-multiple_SRC = tests/userprog/exec-multiple.c tests/main.c +tests/userprog/exec-missing_SRC = tests/userprog/exec-missing.c tests/main.c +tests/userprog/exec-bad-ptr_SRC = tests/userprog/exec-bad-ptr.c tests/main.c +tests/userprog/wait-simple_SRC = tests/userprog/wait-simple.c tests/main.c +tests/userprog/wait-twice_SRC = tests/userprog/wait-twice.c tests/main.c +tests/userprog/wait-killed_SRC = tests/userprog/wait-killed.c tests/main.c +tests/userprog/wait-bad-pid_SRC = tests/userprog/wait-bad-pid.c tests/main.c +tests/userprog/multi-recurse_SRC = tests/userprog/multi-recurse.c +tests/userprog/multi-child-fd_SRC = tests/userprog/multi-child-fd.c \ +tests/main.c + + +tests/userprog/child-simple_SRC = tests/userprog/child-simple.c +tests/userprog/child-args_SRC = tests/userprog/args.c +tests/userprog/child-bad_SRC = tests/userprog/child-bad.c tests/main.c +tests/userprog/child-close_SRC = tests/userprog/child-close.c + + +$(foreach prog,$(tests/userprog_PROGS),$(eval $(prog)_SRC += tests/lib.c)) + +tests/userprog/args-single_ARGS = onearg +tests/userprog/args-multiple_ARGS = some arguments for you! +tests/userprog/args-many_ARGS = a b c d e f g h i j k l m n o p q r s t u v +tests/userprog/args-dbl-space_ARGS = two spaces! +tests/userprog/multi-recurse_ARGS = 15 + +tests/userprog/open-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/open-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/open-twice_PUTFILES += tests/userprog/sample.txt +tests/userprog/close-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt +tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt + +tests/userprog/exec-once_PUTFILES += tests/userprog/child-simple +tests/userprog/exec-multiple_PUTFILES += tests/userprog/child-simple +tests/userprog/wait-simple_PUTFILES += tests/userprog/child-simple +tests/userprog/wait-twice_PUTFILES += tests/userprog/child-simple + +tests/userprog/exec-arg_PUTFILES += tests/userprog/child-args +tests/userprog/multi-child-fd_PUTFILES += tests/userprog/child-close +tests/userprog/wait-killed_PUTFILES += tests/userprog/child-bad + diff --git a/src/tests/userprog/Make.tests.odig b/src/tests/userprog/Make.tests.odig new file mode 100644 index 0000000..c762af3 --- /dev/null +++ b/src/tests/userprog/Make.tests.odig @@ -0,0 +1,132 @@ +# -*- makefile -*- + +tests/%.output: FSDISK = 2 +tests/%.output: PUTFILES = $(filter-out os.dsk, $^) + +tests/userprog_TESTS = $(addprefix tests/userprog/,args-none \ +args-single args-multiple args-many args-dbl-space sc-bad-sp \ +sc-bad-arg sc-boundary sc-boundary-2 halt exit create-normal \ +create-empty create-null create-bad-ptr create-long create-exists \ +create-bound open-normal open-missing open-boundary open-empty \ +open-null open-bad-ptr open-twice close-normal close-stdin \ +close-stdout close-bad-fd read-bad-ptr read-boundary \ +read-zero read-stdout read-bad-fd write-normal write-bad-ptr \ +write-boundary write-zero write-stdin write-bad-fd exec-once exec-arg \ +exec-multiple exec-missing exec-bad-ptr wait-simple wait-twice \ +wait-killed wait-bad-pid multi-recurse \ +) + +tests/userprog_PROGS = $(tests/userprog_TESTS) $(addprefix \ +tests/userprog/,child-simple child-args child-bad child-close child-rox) + +tests/userprog/args-none_SRC = tests/userprog/args.c +tests/userprog/args-single_SRC = tests/userprog/args.c +tests/userprog/args-multiple_SRC = tests/userprog/args.c +tests/userprog/args-many_SRC = tests/userprog/args.c +tests/userprog/args-dbl-space_SRC = tests/userprog/args.c +tests/userprog/sc-bad-sp_SRC = tests/userprog/sc-bad-sp.c tests/main.c +tests/userprog/sc-bad-arg_SRC = tests/userprog/sc-bad-arg.c tests/main.c +tests/userprog/bad-read_SRC = tests/userprog/bad-read.c tests/main.c +tests/userprog/bad-write_SRC = tests/userprog/bad-write.c tests/main.c +tests/userprog/bad-jump_SRC = tests/userprog/bad-jump.c tests/main.c +tests/userprog/bad-read2_SRC = tests/userprog/bad-read2.c tests/main.c +tests/userprog/bad-write2_SRC = tests/userprog/bad-write2.c tests/main.c +tests/userprog/bad-jump2_SRC = tests/userprog/bad-jump2.c tests/main.c +tests/userprog/sc-boundary_SRC = tests/userprog/sc-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/sc-boundary-2_SRC = tests/userprog/sc-boundary-2.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/halt_SRC = tests/userprog/halt.c tests/main.c +tests/userprog/exit_SRC = tests/userprog/exit.c tests/main.c +tests/userprog/create-normal_SRC = tests/userprog/create-normal.c tests/main.c +tests/userprog/create-empty_SRC = tests/userprog/create-empty.c tests/main.c +tests/userprog/create-null_SRC = tests/userprog/create-null.c tests/main.c +tests/userprog/create-bad-ptr_SRC = tests/userprog/create-bad-ptr.c \ +tests/main.c +tests/userprog/create-long_SRC = tests/userprog/create-long.c tests/main.c +tests/userprog/create-exists_SRC = tests/userprog/create-exists.c tests/main.c +tests/userprog/create-bound_SRC = tests/userprog/create-bound.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/open-normal_SRC = tests/userprog/open-normal.c tests/main.c +tests/userprog/open-missing_SRC = tests/userprog/open-missing.c tests/main.c +tests/userprog/open-boundary_SRC = tests/userprog/open-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/open-empty_SRC = tests/userprog/open-empty.c tests/main.c +tests/userprog/open-null_SRC = tests/userprog/open-null.c tests/main.c +tests/userprog/open-bad-ptr_SRC = tests/userprog/open-bad-ptr.c tests/main.c +tests/userprog/open-twice_SRC = tests/userprog/open-twice.c tests/main.c +tests/userprog/close-normal_SRC = tests/userprog/close-normal.c tests/main.c +tests/userprog/close-twice_SRC = tests/userprog/close-twice.c tests/main.c +tests/userprog/close-stdin_SRC = tests/userprog/close-stdin.c tests/main.c +tests/userprog/close-stdout_SRC = tests/userprog/close-stdout.c tests/main.c +tests/userprog/close-bad-fd_SRC = tests/userprog/close-bad-fd.c tests/main.c +tests/userprog/read-normal_SRC = tests/userprog/read-normal.c tests/main.c +tests/userprog/read-bad-ptr_SRC = tests/userprog/read-bad-ptr.c tests/main.c +tests/userprog/read-boundary_SRC = tests/userprog/read-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/read-zero_SRC = tests/userprog/read-zero.c tests/main.c +tests/userprog/read-stdout_SRC = tests/userprog/read-stdout.c tests/main.c +tests/userprog/read-bad-fd_SRC = tests/userprog/read-bad-fd.c tests/main.c +tests/userprog/write-normal_SRC = tests/userprog/write-normal.c tests/main.c +tests/userprog/write-bad-ptr_SRC = tests/userprog/write-bad-ptr.c tests/main.c +tests/userprog/write-boundary_SRC = tests/userprog/write-boundary.c \ +tests/userprog/boundary.c tests/main.c +tests/userprog/write-zero_SRC = tests/userprog/write-zero.c tests/main.c +tests/userprog/write-stdin_SRC = tests/userprog/write-stdin.c tests/main.c +tests/userprog/write-bad-fd_SRC = tests/userprog/write-bad-fd.c tests/main.c +tests/userprog/exec-once_SRC = tests/userprog/exec-once.c tests/main.c +tests/userprog/exec-arg_SRC = tests/userprog/exec-arg.c tests/main.c +tests/userprog/exec-multiple_SRC = tests/userprog/exec-multiple.c tests/main.c +tests/userprog/exec-missing_SRC = tests/userprog/exec-missing.c tests/main.c +tests/userprog/exec-bad-ptr_SRC = tests/userprog/exec-bad-ptr.c tests/main.c +tests/userprog/wait-simple_SRC = tests/userprog/wait-simple.c tests/main.c +tests/userprog/wait-twice_SRC = tests/userprog/wait-twice.c tests/main.c +tests/userprog/wait-killed_SRC = tests/userprog/wait-killed.c tests/main.c +tests/userprog/wait-bad-pid_SRC = tests/userprog/wait-bad-pid.c tests/main.c +tests/userprog/multi-recurse_SRC = tests/userprog/multi-recurse.c +tests/userprog/multi-child-fd_SRC = tests/userprog/multi-child-fd.c \ +tests/main.c +tests/userprog/rox-simple_SRC = tests/userprog/rox-simple.c tests/main.c +tests/userprog/rox-child_SRC = tests/userprog/rox-child.c tests/main.c +tests/userprog/rox-multichild_SRC = tests/userprog/rox-multichild.c \ +tests/main.c + +tests/userprog/child-simple_SRC = tests/userprog/child-simple.c +tests/userprog/child-args_SRC = tests/userprog/args.c +tests/userprog/child-bad_SRC = tests/userprog/child-bad.c tests/main.c +tests/userprog/child-close_SRC = tests/userprog/child-close.c +tests/userprog/child-rox_SRC = tests/userprog/child-rox.c + +$(foreach prog,$(tests/userprog_PROGS),$(eval $(prog)_SRC += tests/lib.c)) + +tests/userprog/args-single_ARGS = onearg +tests/userprog/args-multiple_ARGS = some arguments for you! +tests/userprog/args-many_ARGS = a b c d e f g h i j k l m n o p q r s t u v +tests/userprog/args-dbl-space_ARGS = two spaces! +tests/userprog/multi-recurse_ARGS = 15 + +tests/userprog/open-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/open-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/open-twice_PUTFILES += tests/userprog/sample.txt +tests/userprog/close-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/close-twice_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-bad-ptr_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/read-zero_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-normal_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-bad-ptr_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-boundary_PUTFILES += tests/userprog/sample.txt +tests/userprog/write-zero_PUTFILES += tests/userprog/sample.txt +tests/userprog/multi-child-fd_PUTFILES += tests/userprog/sample.txt + +tests/userprog/exec-once_PUTFILES += tests/userprog/child-simple +tests/userprog/exec-multiple_PUTFILES += tests/userprog/child-simple +tests/userprog/wait-simple_PUTFILES += tests/userprog/child-simple +tests/userprog/wait-twice_PUTFILES += tests/userprog/child-simple + +tests/userprog/exec-arg_PUTFILES += tests/userprog/child-args +tests/userprog/multi-child-fd_PUTFILES += tests/userprog/child-close +tests/userprog/wait-killed_PUTFILES += tests/userprog/child-bad +tests/userprog/rox-child_PUTFILES += tests/userprog/child-rox +tests/userprog/rox-multichild_PUTFILES += tests/userprog/child-rox diff --git a/src/tests/userprog/Rubric.functionality b/src/tests/userprog/Rubric.functionality new file mode 100644 index 0000000..ea76c44 --- /dev/null +++ b/src/tests/userprog/Rubric.functionality @@ -0,0 +1,52 @@ +Functionality of system calls: +- Test argument passing on Pintos command line. +3 args-none +3 args-single +3 args-multiple +3 args-many +3 args-dbl-space + +- Test "create" system call. +3 create-empty +3 create-long +3 create-normal +3 create-exists + +- Test "open" system call. +3 open-missing +3 open-normal +3 open-twice + +- Test "read" system call. +3 read-normal +3 read-zero + +- Test "write" system call. +3 write-normal +3 write-zero + +- Test "close" system call. +3 close-normal + +- Test "exec" system call. +5 exec-once +5 exec-multiple +5 exec-arg + +- Test "wait" system call. +5 wait-simple +5 wait-twice + +- Test "exit" system call. +5 exit + +- Test "halt" system call. +3 halt + +- Test recursive execution of user programs. +15 multi-recurse + +- Test read-only executable feature. +3 rox-simple +3 rox-child +3 rox-multichild diff --git a/src/tests/userprog/Rubric.robustness b/src/tests/userprog/Rubric.robustness new file mode 100644 index 0000000..b7d1035 --- /dev/null +++ b/src/tests/userprog/Rubric.robustness @@ -0,0 +1,48 @@ +Robustness of system calls: +- Test robustness of file descriptor handling. +2 close-stdin +2 close-stdout +2 close-bad-fd +2 close-twice +2 read-bad-fd +2 read-stdout +2 write-bad-fd +2 write-stdin +2 multi-child-fd + +- Test robustness of pointer handling. +3 create-bad-ptr +3 exec-bad-ptr +3 open-bad-ptr +3 read-bad-ptr +3 write-bad-ptr + +- Test robustness of buffer copying across page boundaries. +3 create-bound +3 open-boundary +3 read-boundary +3 write-boundary + +- Test handling of null pointer and empty strings. +2 create-null +2 open-null +2 open-empty + +- Test robustness of system call implementation. +3 sc-bad-arg +3 sc-bad-sp +5 sc-boundary +5 sc-boundary-2 + +- Test robustness of "exec" and "wait" system calls. +5 exec-missing +5 wait-bad-pid +5 wait-killed + +- Test robustness of exception handling. +1 bad-read +1 bad-write +1 bad-jump +1 bad-read2 +1 bad-write2 +1 bad-jump2 diff --git a/src/tests/userprog/args-dbl-space.ck b/src/tests/userprog/args-dbl-space.ck new file mode 100644 index 0000000..dfbcf4b --- /dev/null +++ b/src/tests/userprog/args-dbl-space.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 3 +(args) argv[0] = 'args-dbl-space' +(args) argv[1] = 'two' +(args) argv[2] = 'spaces!' +(args) argv[3] = null +(args) end +args-dbl-space: exit(0) +EOF +pass; diff --git a/src/tests/userprog/args-many.ck b/src/tests/userprog/args-many.ck new file mode 100644 index 0000000..214574a --- /dev/null +++ b/src/tests/userprog/args-many.ck @@ -0,0 +1,35 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 23 +(args) argv[0] = 'args-many' +(args) argv[1] = 'a' +(args) argv[2] = 'b' +(args) argv[3] = 'c' +(args) argv[4] = 'd' +(args) argv[5] = 'e' +(args) argv[6] = 'f' +(args) argv[7] = 'g' +(args) argv[8] = 'h' +(args) argv[9] = 'i' +(args) argv[10] = 'j' +(args) argv[11] = 'k' +(args) argv[12] = 'l' +(args) argv[13] = 'm' +(args) argv[14] = 'n' +(args) argv[15] = 'o' +(args) argv[16] = 'p' +(args) argv[17] = 'q' +(args) argv[18] = 'r' +(args) argv[19] = 's' +(args) argv[20] = 't' +(args) argv[21] = 'u' +(args) argv[22] = 'v' +(args) argv[23] = null +(args) end +args-many: exit(0) +EOF +pass; diff --git a/src/tests/userprog/args-multiple.ck b/src/tests/userprog/args-multiple.ck new file mode 100644 index 0000000..227e6cc --- /dev/null +++ b/src/tests/userprog/args-multiple.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 5 +(args) argv[0] = 'args-multiple' +(args) argv[1] = 'some' +(args) argv[2] = 'arguments' +(args) argv[3] = 'for' +(args) argv[4] = 'you!' +(args) argv[5] = null +(args) end +args-multiple: exit(0) +EOF +pass; diff --git a/src/tests/userprog/args-none.ck b/src/tests/userprog/args-none.ck new file mode 100644 index 0000000..146318e --- /dev/null +++ b/src/tests/userprog/args-none.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 1 +(args) argv[0] = 'args-none' +(args) argv[1] = null +(args) end +args-none: exit(0) +EOF +pass; diff --git a/src/tests/userprog/args-single.ck b/src/tests/userprog/args-single.ck new file mode 100644 index 0000000..24582b4 --- /dev/null +++ b/src/tests/userprog/args-single.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(args) begin +(args) argc = 2 +(args) argv[0] = 'args-single' +(args) argv[1] = 'onearg' +(args) argv[2] = null +(args) end +args-single: exit(0) +EOF +pass; diff --git a/src/tests/userprog/args.c b/src/tests/userprog/args.c new file mode 100644 index 0000000..20eda44 --- /dev/null +++ b/src/tests/userprog/args.c @@ -0,0 +1,25 @@ +/* Prints the command-line arguments. + This program is used for all of the args-* tests. Grading is + done differently for each of the args-* tests based on the + output. */ + +#include "tests/lib.h" + +int +main (int argc, char *argv[]) +{ + int i; + + test_name = "args"; + + msg ("begin"); + msg ("argc = %d", argc); + for (i = 0; i <= argc; i++) + if (argv[i] != NULL) + msg ("argv[%d] = '%s'", i, argv[i]); + else + msg ("argv[%d] = null", i); + msg ("end"); + + return 0; +} diff --git a/src/tests/userprog/bad-jump.c b/src/tests/userprog/bad-jump.c new file mode 100644 index 0000000..51b7c9f --- /dev/null +++ b/src/tests/userprog/bad-jump.c @@ -0,0 +1,13 @@ +/* This program attempts to execute code at address 0, which is not mapped. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully called NULL: %d", + ((int (*)(void))NULL)()); + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/bad-jump.ck b/src/tests/userprog/bad-jump.ck new file mode 100644 index 0000000..e1c178b --- /dev/null +++ b/src/tests/userprog/bad-jump.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-jump) begin +bad-jump: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/bad-jump2.c b/src/tests/userprog/bad-jump2.c new file mode 100644 index 0000000..dc7c2a7 --- /dev/null +++ b/src/tests/userprog/bad-jump2.c @@ -0,0 +1,13 @@ +/* This program attempts to execute code at a kernel virtual address. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully called kernel code: %d", + ((int (*)(void))0xC0000000)()); + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/bad-jump2.ck b/src/tests/userprog/bad-jump2.ck new file mode 100644 index 0000000..35f0f97 --- /dev/null +++ b/src/tests/userprog/bad-jump2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-jump2) begin +bad-jump2: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/bad-read.c b/src/tests/userprog/bad-read.c new file mode 100644 index 0000000..904c278 --- /dev/null +++ b/src/tests/userprog/bad-read.c @@ -0,0 +1,13 @@ +/* This program attempts to read memory at an address that is not mapped. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully dereferenced NULL: %d", + *(int *)NULL); + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/bad-read.ck b/src/tests/userprog/bad-read.ck new file mode 100644 index 0000000..4d4d926 --- /dev/null +++ b/src/tests/userprog/bad-read.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-read) begin +bad-read: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/bad-read2.c b/src/tests/userprog/bad-read2.c new file mode 100644 index 0000000..a2fc237 --- /dev/null +++ b/src/tests/userprog/bad-read2.c @@ -0,0 +1,13 @@ +/* This program attempts to read kernel memory. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("Congratulations - you have successfully read kernel memory: %d", + *(int *)0xC0000000); + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/bad-read2.ck b/src/tests/userprog/bad-read2.ck new file mode 100644 index 0000000..fa27c7d --- /dev/null +++ b/src/tests/userprog/bad-read2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-read2) begin +bad-read2: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/bad-write.c b/src/tests/userprog/bad-write.c new file mode 100644 index 0000000..000c26b --- /dev/null +++ b/src/tests/userprog/bad-write.c @@ -0,0 +1,12 @@ +/* This program attempts to write to memory at an address that is not mapped. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + *(int *)NULL = 42; + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/bad-write.ck b/src/tests/userprog/bad-write.ck new file mode 100644 index 0000000..d213b49 --- /dev/null +++ b/src/tests/userprog/bad-write.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-write) begin +bad-write: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/bad-write2.c b/src/tests/userprog/bad-write2.c new file mode 100644 index 0000000..753da1e --- /dev/null +++ b/src/tests/userprog/bad-write2.c @@ -0,0 +1,12 @@ +/* This program attempts to write to kernel memory. + This should terminate the process with a -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + *(int *)0xC0000000 = 42; + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/bad-write2.ck b/src/tests/userprog/bad-write2.ck new file mode 100644 index 0000000..c6a3420 --- /dev/null +++ b/src/tests/userprog/bad-write2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(bad-write2) begin +bad-write2: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/boundary.c b/src/tests/userprog/boundary.c new file mode 100644 index 0000000..59907ec --- /dev/null +++ b/src/tests/userprog/boundary.c @@ -0,0 +1,33 @@ +/* Utility function for tests that try to break system calls by + passing them data that crosses from one virtual page to + another. */ + +#include <inttypes.h> +#include <round.h> +#include <string.h> +#include "tests/userprog/boundary.h" + +static char dst[8192]; + +/* Returns the beginning of a page. There are at least 2048 + modifiable bytes on either side of the pointer returned. */ +void * +get_boundary_area (void) +{ + char *p = (char *) ROUND_UP ((uintptr_t) dst, 4096); + if (p - dst < 2048) + p += 4096; + return p; +} + +/* Returns a copy of SRC split across the boundary between two + pages. */ +char * +copy_string_across_boundary (const char *src) +{ + char *p = get_boundary_area (); + p -= strlen (src) < 4096 ? strlen (src) / 2 : 4096; + strlcpy (p, src, 4096); + return p; +} + diff --git a/src/tests/userprog/boundary.h b/src/tests/userprog/boundary.h new file mode 100644 index 0000000..c8e4b3b --- /dev/null +++ b/src/tests/userprog/boundary.h @@ -0,0 +1,7 @@ +#ifndef TESTS_USERPROG_BOUNDARY_H +#define TESTS_USERPROG_BOUNDARY_H + +void *get_boundary_area (void); +char *copy_string_across_boundary (const char *); + +#endif /* tests/userprog/boundary.h */ diff --git a/src/tests/userprog/child-bad.c b/src/tests/userprog/child-bad.c new file mode 100644 index 0000000..77d7a69 --- /dev/null +++ b/src/tests/userprog/child-bad.c @@ -0,0 +1,14 @@ +/* Child process run by wait-killed test. + Sets the stack pointer (%esp) to an invalid value and invokes + a system call, which should then terminate the process with a + -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl $0x20101234, %esp; int $0x30"); + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/child-close.c b/src/tests/userprog/child-close.c new file mode 100644 index 0000000..ac948c8 --- /dev/null +++ b/src/tests/userprog/child-close.c @@ -0,0 +1,28 @@ +/* Child process run by multi-child-fd test. + + Attempts to close the file descriptor passed as the first + command-line argument. This is invalid, because file + descriptors are not inherited in Pintos. Two results are + allowed: either the system call should return without taking + any action, or the kernel should terminate the process with a + -1 exit code. */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <syscall.h> +#include "tests/lib.h" + +const char *test_name = "child-close"; + +int +main (int argc UNUSED, char *argv[]) +{ + msg ("begin"); + if (!isdigit (*argv[1])) + fail ("bad command-line arguments"); + close (atoi (argv[1])); + msg ("end"); + + return 0; +} diff --git a/src/tests/userprog/child-rox.c b/src/tests/userprog/child-rox.c new file mode 100644 index 0000000..aba808b --- /dev/null +++ b/src/tests/userprog/child-rox.c @@ -0,0 +1,55 @@ +/* Child process run by rox-child and rox-multichild tests. + Opens and tries to write to its own executable, verifying that + that is disallowed. + Then recursively executes itself to the depth indicated by the + first command-line argument. */ + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <syscall.h> +#include "tests/lib.h" + +const char *test_name = "child-rox"; + +static void +try_write (void) +{ + int handle; + char buffer[19]; + + quiet = true; + CHECK ((handle = open ("child-rox")) > 1, "open \"child-rox\""); + quiet = false; + + CHECK (write (handle, buffer, sizeof buffer) == 0, + "try to write \"child-rox\""); + + close (handle); +} + +int +main (int argc UNUSED, char *argv[]) +{ + msg ("begin"); + try_write (); + + if (!isdigit (*argv[1])) + fail ("bad command-line arguments"); + if (atoi (argv[1]) > 1) + { + char cmd[128]; + int child; + + snprintf (cmd, sizeof cmd, "child-rox %d", atoi (argv[1]) - 1); + CHECK ((child = exec (cmd)) != -1, "exec \"%s\"", cmd); + quiet = true; + CHECK (wait (child) == 12, "wait for \"child-rox\""); + quiet = false; + } + + try_write (); + msg ("end"); + + return 12; +} diff --git a/src/tests/userprog/child-simple.c b/src/tests/userprog/child-simple.c new file mode 100644 index 0000000..0d2dacf --- /dev/null +++ b/src/tests/userprog/child-simple.c @@ -0,0 +1,15 @@ +/* Child process run by exec-multiple, exec-one, wait-simple, and + wait-twice tests. + Just prints a single message and terminates. */ + +#include <stdio.h> +#include "tests/lib.h" + +const char *test_name = "child-simple"; + +int +main (void) +{ + msg ("run"); + return 81; +} diff --git a/src/tests/userprog/close-bad-fd.c b/src/tests/userprog/close-bad-fd.c new file mode 100644 index 0000000..f63bb9a --- /dev/null +++ b/src/tests/userprog/close-bad-fd.c @@ -0,0 +1,11 @@ +/* Tries to close an invalid fd, which must either fail silently + or terminate with exit code -1. */ + +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + close (0x20101234); +} diff --git a/src/tests/userprog/close-bad-fd.ck b/src/tests/userprog/close-bad-fd.ck new file mode 100644 index 0000000..497b17c --- /dev/null +++ b/src/tests/userprog/close-bad-fd.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-bad-fd) begin +(close-bad-fd) end +close-bad-fd: exit(0) +EOF +(close-bad-fd) begin +close-bad-fd: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/close-normal.c b/src/tests/userprog/close-normal.c new file mode 100644 index 0000000..8ce04e3 --- /dev/null +++ b/src/tests/userprog/close-normal.c @@ -0,0 +1,14 @@ +/* Opens a file and then closes it. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + msg ("close \"sample.txt\""); + close (handle); +} diff --git a/src/tests/userprog/close-normal.ck b/src/tests/userprog/close-normal.ck new file mode 100644 index 0000000..fe41342 --- /dev/null +++ b/src/tests/userprog/close-normal.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(close-normal) begin +(close-normal) open "sample.txt" +(close-normal) close "sample.txt" +(close-normal) end +close-normal: exit(0) +EOF +pass; diff --git a/src/tests/userprog/close-stdin.c b/src/tests/userprog/close-stdin.c new file mode 100644 index 0000000..9bbf9f2 --- /dev/null +++ b/src/tests/userprog/close-stdin.c @@ -0,0 +1,11 @@ +/* Tries to close the keyboard input stream, which must either + fail silently or terminate with exit code -1. */ + +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + close (0); +} diff --git a/src/tests/userprog/close-stdin.ck b/src/tests/userprog/close-stdin.ck new file mode 100644 index 0000000..3d28507 --- /dev/null +++ b/src/tests/userprog/close-stdin.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-stdin) begin +(close-stdin) end +close-stdin: exit(0) +EOF +(close-stdin) begin +close-stdin: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/close-stdout.c b/src/tests/userprog/close-stdout.c new file mode 100644 index 0000000..886523f --- /dev/null +++ b/src/tests/userprog/close-stdout.c @@ -0,0 +1,11 @@ +/* Tries to close the console output stream, which must either + fail silently or terminate with exit code -1. */ + +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + close (1); +} diff --git a/src/tests/userprog/close-stdout.ck b/src/tests/userprog/close-stdout.ck new file mode 100644 index 0000000..3cbbcff --- /dev/null +++ b/src/tests/userprog/close-stdout.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-stdout) begin +(close-stdout) end +close-stdout: exit(0) +EOF +(close-stdout) begin +close-stdout: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/close-twice.c b/src/tests/userprog/close-twice.c new file mode 100644 index 0000000..830bccf --- /dev/null +++ b/src/tests/userprog/close-twice.c @@ -0,0 +1,18 @@ +/* Opens a file and then tries to close it twice. The second + close must either fail silently or terminate with exit code + -1. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + msg ("close \"sample.txt\""); + close (handle); + msg ("close \"sample.txt\" again"); + close (handle); +} diff --git a/src/tests/userprog/close-twice.ck b/src/tests/userprog/close-twice.ck new file mode 100644 index 0000000..deb55a6 --- /dev/null +++ b/src/tests/userprog/close-twice.ck @@ -0,0 +1,19 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(close-twice) begin +(close-twice) open "sample.txt" +(close-twice) close "sample.txt" +(close-twice) close "sample.txt" again +(close-twice) end +close-twice: exit(0) +EOF +(close-twice) begin +(close-twice) open "sample.txt" +(close-twice) close "sample.txt" +(close-twice) close "sample.txt" again +close-twice: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/create-bad-ptr.c b/src/tests/userprog/create-bad-ptr.c new file mode 100644 index 0000000..4a07bb3 --- /dev/null +++ b/src/tests/userprog/create-bad-ptr.c @@ -0,0 +1,12 @@ +/* Passes a bad pointer to the create system call, + which must cause the process to be terminated with exit code + -1. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(0x20101234): %d", create ((char *) 0x20101234, 0)); +} diff --git a/src/tests/userprog/create-bad-ptr.ck b/src/tests/userprog/create-bad-ptr.ck new file mode 100644 index 0000000..ac13405 --- /dev/null +++ b/src/tests/userprog/create-bad-ptr.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-bad-ptr) begin +create-bad-ptr: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/create-bound.c b/src/tests/userprog/create-bound.c new file mode 100644 index 0000000..0a829f3 --- /dev/null +++ b/src/tests/userprog/create-bound.c @@ -0,0 +1,14 @@ +/* Opens a file whose name spans the boundary between two pages. + This is valid, so it must succeed. */ + +#include <syscall.h> +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(\"quux.dat\"): %d", + create (copy_string_across_boundary ("quux.dat"), 0)); +} diff --git a/src/tests/userprog/create-bound.ck b/src/tests/userprog/create-bound.ck new file mode 100644 index 0000000..7656b7f --- /dev/null +++ b/src/tests/userprog/create-bound.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-bound) begin +(create-bound) create("quux.dat"): 1 +(create-bound) end +create-bound: exit(0) +EOF +pass; diff --git a/src/tests/userprog/create-empty.c b/src/tests/userprog/create-empty.c new file mode 100644 index 0000000..fa26b43 --- /dev/null +++ b/src/tests/userprog/create-empty.c @@ -0,0 +1,10 @@ +/* Tries to create a file with the empty string as its name. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(\"\"): %d", create ("", 0)); +} diff --git a/src/tests/userprog/create-empty.ck b/src/tests/userprog/create-empty.ck new file mode 100644 index 0000000..93a1058 --- /dev/null +++ b/src/tests/userprog/create-empty.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(create-empty) begin +(create-empty) create(""): 0 +(create-empty) end +create-empty: exit(0) +EOF +(create-empty) begin +create-empty: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/create-exists.c b/src/tests/userprog/create-exists.c new file mode 100644 index 0000000..d395008 --- /dev/null +++ b/src/tests/userprog/create-exists.c @@ -0,0 +1,16 @@ +/* Verifies that trying to create a file under a name that + already exists will fail. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (create ("quux.dat", 0), "create quux.dat"); + CHECK (create ("warble.dat", 0), "create warble.dat"); + CHECK (!create ("quux.dat", 0), "try to re-create quux.dat"); + CHECK (create ("baffle.dat", 0), "create baffle.dat"); + CHECK (!create ("warble.dat", 0), "try to re-create quux.dat"); +} diff --git a/src/tests/userprog/create-exists.ck b/src/tests/userprog/create-exists.ck new file mode 100644 index 0000000..006885e --- /dev/null +++ b/src/tests/userprog/create-exists.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-exists) begin +(create-exists) create quux.dat +(create-exists) create warble.dat +(create-exists) try to re-create quux.dat +(create-exists) create baffle.dat +(create-exists) try to re-create quux.dat +(create-exists) end +create-exists: exit(0) +EOF +pass; diff --git a/src/tests/userprog/create-long.c b/src/tests/userprog/create-long.c new file mode 100644 index 0000000..16b31bd --- /dev/null +++ b/src/tests/userprog/create-long.c @@ -0,0 +1,17 @@ +/* Tries to create a file with a name that is much too long, + which must fail. */ + +#include <string.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + static char name[512]; + memset (name, 'x', sizeof name); + name[sizeof name - 1] = '\0'; + + msg ("create(\"x...\"): %d", create (name, 0)); +} diff --git a/src/tests/userprog/create-long.ck b/src/tests/userprog/create-long.ck new file mode 100644 index 0000000..628411c --- /dev/null +++ b/src/tests/userprog/create-long.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-long) begin +(create-long) create("x..."): 0 +(create-long) end +create-long: exit(0) +EOF +pass; diff --git a/src/tests/userprog/create-normal.c b/src/tests/userprog/create-normal.c new file mode 100644 index 0000000..3cbc463 --- /dev/null +++ b/src/tests/userprog/create-normal.c @@ -0,0 +1,10 @@ +/* Creates an ordinary empty file. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (create ("quux.dat", 0), "create quux.dat"); +} diff --git a/src/tests/userprog/create-normal.ck b/src/tests/userprog/create-normal.ck new file mode 100644 index 0000000..ca74a6e --- /dev/null +++ b/src/tests/userprog/create-normal.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-normal) begin +(create-normal) create quux.dat +(create-normal) end +create-normal: exit(0) +EOF +pass; diff --git a/src/tests/userprog/create-null.c b/src/tests/userprog/create-null.c new file mode 100644 index 0000000..287cb23 --- /dev/null +++ b/src/tests/userprog/create-null.c @@ -0,0 +1,11 @@ +/* Tries to create a file with the null pointer as its name. + The process must be terminated with exit code -1. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("create(NULL): %d", create (NULL, 0)); +} diff --git a/src/tests/userprog/create-null.ck b/src/tests/userprog/create-null.ck new file mode 100644 index 0000000..09b7872 --- /dev/null +++ b/src/tests/userprog/create-null.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(create-null) begin +create-null: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/exec-arg.c b/src/tests/userprog/exec-arg.c new file mode 100644 index 0000000..82d0744 --- /dev/null +++ b/src/tests/userprog/exec-arg.c @@ -0,0 +1,10 @@ +/* Tests argument passing to child processes. */ + +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-args childarg")); +} diff --git a/src/tests/userprog/exec-arg.ck b/src/tests/userprog/exec-arg.ck new file mode 100644 index 0000000..b7533ed --- /dev/null +++ b/src/tests/userprog/exec-arg.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exec-arg) begin +(args) begin +(args) argc = 2 +(args) argv[0] = 'child-args' +(args) argv[1] = 'childarg' +(args) argv[2] = null +(args) end +child-args: exit(0) +(exec-arg) end +exec-arg: exit(0) +EOF +pass; diff --git a/src/tests/userprog/exec-bad-ptr.c b/src/tests/userprog/exec-bad-ptr.c new file mode 100644 index 0000000..0abadd3 --- /dev/null +++ b/src/tests/userprog/exec-bad-ptr.c @@ -0,0 +1,11 @@ +/* Passes an invalid pointer to the exec system call. + The process must be terminated with -1 exit code. */ + +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + exec ((char *) 0x20101234); +} diff --git a/src/tests/userprog/exec-bad-ptr.ck b/src/tests/userprog/exec-bad-ptr.ck new file mode 100644 index 0000000..63f5f78 --- /dev/null +++ b/src/tests/userprog/exec-bad-ptr.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(exec-bad-ptr) begin +(exec-bad-ptr) end +exec-bad-ptr: exit(0) +EOF +(exec-bad-ptr) begin +exec-bad-ptr: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/exec-missing.c b/src/tests/userprog/exec-missing.c new file mode 100644 index 0000000..bf08cad --- /dev/null +++ b/src/tests/userprog/exec-missing.c @@ -0,0 +1,12 @@ +/* Tries to execute a nonexistent process. + The exec system call must return -1. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("exec(\"no-such-file\"): %d", exec ("no-such-file")); +} diff --git a/src/tests/userprog/exec-missing.ck b/src/tests/userprog/exec-missing.ck new file mode 100644 index 0000000..0ef7aaa --- /dev/null +++ b/src/tests/userprog/exec-missing.ck @@ -0,0 +1,31 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF', <<'EOF', <<'EOF']); +(exec-missing) begin +load: no-such-file: open failed +(exec-missing) exec("no-such-file"): -1 +(exec-missing) end +exec-missing: exit(0) +EOF +(exec-missing) begin +(exec-missing) exec("no-such-file"): -1 +(exec-missing) end +exec-missing: exit(0) +EOF +(exec-missing) begin +load: no-such-file: open failed +no-such-file: exit(-1) +(exec-missing) exec("no-such-file"): -1 +(exec-missing) end +exec-missing: exit(0) +EOF +(exec-missing) begin +load: no-such-file: open failed +(exec-missing) exec("no-such-file"): -1 +no-such-file: exit(-1) +(exec-missing) end +exec-missing: exit(0) +EOF +pass; diff --git a/src/tests/userprog/exec-multiple.c b/src/tests/userprog/exec-multiple.c new file mode 100644 index 0000000..ba4c26e --- /dev/null +++ b/src/tests/userprog/exec-multiple.c @@ -0,0 +1,14 @@ +/* Executes and waits for multiple child processes. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-simple")); + wait (exec ("child-simple")); + wait (exec ("child-simple")); + wait (exec ("child-simple")); +} diff --git a/src/tests/userprog/exec-multiple.ck b/src/tests/userprog/exec-multiple.ck new file mode 100644 index 0000000..99624cd --- /dev/null +++ b/src/tests/userprog/exec-multiple.ck @@ -0,0 +1,18 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exec-multiple) begin +(child-simple) run +child-simple: exit(81) +(child-simple) run +child-simple: exit(81) +(child-simple) run +child-simple: exit(81) +(child-simple) run +child-simple: exit(81) +(exec-multiple) end +exec-multiple: exit(0) +EOF +pass; diff --git a/src/tests/userprog/exec-once.c b/src/tests/userprog/exec-once.c new file mode 100644 index 0000000..7bae5a1 --- /dev/null +++ b/src/tests/userprog/exec-once.c @@ -0,0 +1,11 @@ +/* Executes and waits for a single child process. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + wait (exec ("child-simple")); +} diff --git a/src/tests/userprog/exec-once.ck b/src/tests/userprog/exec-once.ck new file mode 100644 index 0000000..00b59ed --- /dev/null +++ b/src/tests/userprog/exec-once.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exec-once) begin +(child-simple) run +child-simple: exit(81) +(exec-once) end +exec-once: exit(0) +EOF +pass; diff --git a/src/tests/userprog/exit.c b/src/tests/userprog/exit.c new file mode 100644 index 0000000..cb4eb8f --- /dev/null +++ b/src/tests/userprog/exit.c @@ -0,0 +1,11 @@ +/* Tests the exit system call. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + exit (57); + fail ("should have called exit(57)"); +} diff --git a/src/tests/userprog/exit.ck b/src/tests/userprog/exit.ck new file mode 100644 index 0000000..a552702 --- /dev/null +++ b/src/tests/userprog/exit.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(exit) begin +exit: exit(57) +EOF +pass; diff --git a/src/tests/userprog/halt.c b/src/tests/userprog/halt.c new file mode 100644 index 0000000..4a99bce --- /dev/null +++ b/src/tests/userprog/halt.c @@ -0,0 +1,11 @@ +/* Tests the halt system call. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + halt (); + fail ("should have halted"); +} diff --git a/src/tests/userprog/halt.ck b/src/tests/userprog/halt.ck new file mode 100644 index 0000000..1b701ed --- /dev/null +++ b/src/tests/userprog/halt.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +our ($test); +my (@output) = read_text_file ("$test.output"); + +common_checks ("run", @output); + +fail "missing 'begin' message\n" + if !grep ($_ eq '(halt) begin', @output); +fail "found 'fail' message--halt didn't really halt\n" + if grep ($_ eq '(halt) fail', @output); +pass; diff --git a/src/tests/userprog/lib/.cvsignore b/src/tests/userprog/lib/.cvsignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/src/tests/userprog/lib/.cvsignore @@ -0,0 +1 @@ +*.d diff --git a/src/tests/userprog/lib/user/.cvsignore b/src/tests/userprog/lib/user/.cvsignore new file mode 100644 index 0000000..a438335 --- /dev/null +++ b/src/tests/userprog/lib/user/.cvsignore @@ -0,0 +1 @@ +*.d diff --git a/src/tests/userprog/lib/user/.dummy b/src/tests/userprog/lib/user/.dummy new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/tests/userprog/lib/user/.dummy diff --git a/src/tests/userprog/multi-child-fd.c b/src/tests/userprog/multi-child-fd.c new file mode 100644 index 0000000..48de4b4 --- /dev/null +++ b/src/tests/userprog/multi-child-fd.c @@ -0,0 +1,25 @@ +/* Opens a file and then runs a subprocess that tries to close + the file. (Pintos does not have inheritance of file handles, + so this must fail.) The parent process then attempts to use + the file handle, which must succeed. */ + +#include <stdio.h> +#include <syscall.h> +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char child_cmd[128]; + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + snprintf (child_cmd, sizeof child_cmd, "child-close %d", handle); + + msg ("wait(exec()) = %d", wait (exec (child_cmd))); + + check_file_handle (handle, "sample.txt", sample, sizeof sample - 1); +} diff --git a/src/tests/userprog/multi-child-fd.ck b/src/tests/userprog/multi-child-fd.ck new file mode 100644 index 0000000..d0b3a33 --- /dev/null +++ b/src/tests/userprog/multi-child-fd.ck @@ -0,0 +1,25 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(multi-child-fd) begin +(multi-child-fd) open "sample.txt" +(child-close) begin +(child-close) end +child-close: exit(0) +(multi-child-fd) wait(exec()) = 0 +(multi-child-fd) verified contents of "sample.txt" +(multi-child-fd) end +multi-child-fd: exit(0) +EOF +(multi-child-fd) begin +(multi-child-fd) open "sample.txt" +(child-close) begin +child-close: exit(-1) +(multi-child-fd) wait(exec()) = -1 +(multi-child-fd) verified contents of "sample.txt" +(multi-child-fd) end +multi-child-fd: exit(0) +EOF +pass; diff --git a/src/tests/userprog/multi-recurse.c b/src/tests/userprog/multi-recurse.c new file mode 100644 index 0000000..7172ec3 --- /dev/null +++ b/src/tests/userprog/multi-recurse.c @@ -0,0 +1,34 @@ +/* Executes itself recursively to the depth indicated by the + first command-line argument. */ + +#include <debug.h> +#include <stdlib.h> +#include <stdio.h> +#include <syscall.h> +#include "tests/lib.h" + +const char *test_name = "multi-recurse"; + +int +main (int argc UNUSED, char *argv[]) +{ + int n = atoi (argv[1]); + + msg ("begin %d", n); + if (n != 0) + { + char child_cmd[128]; + pid_t child_pid; + int code; + + snprintf (child_cmd, sizeof child_cmd, "multi-recurse %d", n - 1); + CHECK ((child_pid = exec (child_cmd)) != -1, "exec(\"%s\")", child_cmd); + + code = wait (child_pid); + if (code != n - 1) + fail ("wait(exec(\"%s\")) returned %d", child_cmd, code); + } + + msg ("end %d", n); + return n; +} diff --git a/src/tests/userprog/multi-recurse.ck b/src/tests/userprog/multi-recurse.ck new file mode 100644 index 0000000..41eb4a6 --- /dev/null +++ b/src/tests/userprog/multi-recurse.ck @@ -0,0 +1,70 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(multi-recurse) begin 15 +(multi-recurse) exec("multi-recurse 14") +(multi-recurse) begin 14 +(multi-recurse) exec("multi-recurse 13") +(multi-recurse) begin 13 +(multi-recurse) exec("multi-recurse 12") +(multi-recurse) begin 12 +(multi-recurse) exec("multi-recurse 11") +(multi-recurse) begin 11 +(multi-recurse) exec("multi-recurse 10") +(multi-recurse) begin 10 +(multi-recurse) exec("multi-recurse 9") +(multi-recurse) begin 9 +(multi-recurse) exec("multi-recurse 8") +(multi-recurse) begin 8 +(multi-recurse) exec("multi-recurse 7") +(multi-recurse) begin 7 +(multi-recurse) exec("multi-recurse 6") +(multi-recurse) begin 6 +(multi-recurse) exec("multi-recurse 5") +(multi-recurse) begin 5 +(multi-recurse) exec("multi-recurse 4") +(multi-recurse) begin 4 +(multi-recurse) exec("multi-recurse 3") +(multi-recurse) begin 3 +(multi-recurse) exec("multi-recurse 2") +(multi-recurse) begin 2 +(multi-recurse) exec("multi-recurse 1") +(multi-recurse) begin 1 +(multi-recurse) exec("multi-recurse 0") +(multi-recurse) begin 0 +(multi-recurse) end 0 +multi-recurse: exit(0) +(multi-recurse) end 1 +multi-recurse: exit(1) +(multi-recurse) end 2 +multi-recurse: exit(2) +(multi-recurse) end 3 +multi-recurse: exit(3) +(multi-recurse) end 4 +multi-recurse: exit(4) +(multi-recurse) end 5 +multi-recurse: exit(5) +(multi-recurse) end 6 +multi-recurse: exit(6) +(multi-recurse) end 7 +multi-recurse: exit(7) +(multi-recurse) end 8 +multi-recurse: exit(8) +(multi-recurse) end 9 +multi-recurse: exit(9) +(multi-recurse) end 10 +multi-recurse: exit(10) +(multi-recurse) end 11 +multi-recurse: exit(11) +(multi-recurse) end 12 +multi-recurse: exit(12) +(multi-recurse) end 13 +multi-recurse: exit(13) +(multi-recurse) end 14 +multi-recurse: exit(14) +(multi-recurse) end 15 +multi-recurse: exit(15) +EOF +pass; diff --git a/src/tests/userprog/no-vm/Make.tests b/src/tests/userprog/no-vm/Make.tests new file mode 100644 index 0000000..a545e18 --- /dev/null +++ b/src/tests/userprog/no-vm/Make.tests @@ -0,0 +1,8 @@ +# -*- makefile -*- + +tests/userprog/no-vm_TESTS = tests/userprog/no-vm/multi-oom +tests/userprog/no-vm_PROGS = $(tests/userprog/no-vm_TESTS) +tests/userprog/no-vm/multi-oom_SRC = tests/userprog/no-vm/multi-oom.c \ +tests/lib.c + +tests/userprog/no-vm/multi-oom.output: TIMEOUT = 360 diff --git a/src/tests/userprog/no-vm/Rubric b/src/tests/userprog/no-vm/Rubric new file mode 100644 index 0000000..c3816c6 --- /dev/null +++ b/src/tests/userprog/no-vm/Rubric @@ -0,0 +1,3 @@ +Functionality of features that VM might break: + +1 multi-oom diff --git a/src/tests/userprog/no-vm/multi-oom.c b/src/tests/userprog/no-vm/multi-oom.c new file mode 100644 index 0000000..6a4472d --- /dev/null +++ b/src/tests/userprog/no-vm/multi-oom.c @@ -0,0 +1,179 @@ +/* Recursively executes itself until the child fails to execute. + We expect that at least 30 copies can run. + + We count how many children your kernel was able to execute + before it fails to start a new process. We require that, + if a process doesn't actually get to start, exec() must + return -1, not a valid PID. + + We repeat this process 10 times, checking that your kernel + allows for the same level of depth every time. + + In addition, some processes will spawn children that terminate + abnormally after allocating some resources. + + Written by Godmar Back <godmar@gmail.com> + */ + +#include <debug.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdbool.h> +#include <syscall.h> +#include <random.h> +#include "tests/lib.h" + +static const int EXPECTED_DEPTH_TO_PASS = 30; +static const int EXPECTED_REPETITIONS = 10; + +const char *test_name = "multi-oom"; + +enum child_termination_mode { RECURSE, CRASH }; + +/* Spawn a recursive copy of ourselves, passing along instructions + for the child. */ +static pid_t +spawn_child (int c, enum child_termination_mode mode) +{ + char child_cmd[128]; + snprintf (child_cmd, sizeof child_cmd, + "%s %d %s", test_name, c, mode == CRASH ? "-k" : ""); + return exec (child_cmd); +} + +/* Open a number of files (and fail to close them). + The kernel must free any kernel resources associated + with these file descriptors. */ +static void +consume_some_resources (void) +{ + int fd, fdmax = 126; + + /* Open as many files as we can, up to fdmax. + Depending on how file descriptors are allocated inside + the kernel, open() may fail if the kernel is low on memory. + A low-memory condition in open() should not lead to the + termination of the process. */ + for (fd = 0; fd < fdmax; fd++) + if (open (test_name) == -1) + break; +} + +/* Consume some resources, then terminate this process + in some abnormal way. */ +static int NO_INLINE +consume_some_resources_and_die (int seed) +{ + consume_some_resources (); + random_init (seed); + int *PHYS_BASE = (int *)0xC0000000; + + switch (random_ulong () % 5) + { + case 0: + *(int *) NULL = 42; + + case 1: + return *(int *) NULL; + + case 2: + return *PHYS_BASE; + + case 3: + *PHYS_BASE = 42; + + case 4: + open ((char *)PHYS_BASE); + exit (-1); + + default: + NOT_REACHED (); + } + return 0; +} + +/* The first copy is invoked without command line arguments. + Subsequent copies are invoked with a parameter 'depth' + that describes how many parent processes preceded them. + Each process spawns one or multiple recursive copies of + itself, passing 'depth+1' as depth. + + Some children are started with the '-k' flag, which will + result in abnormal termination. + */ +int +main (int argc, char *argv[]) +{ + int n; + + n = argc > 1 ? atoi (argv[1]) : 0; + bool is_at_root = (n == 0); + if (is_at_root) + msg ("begin"); + + /* If -k is passed, crash this process. */ + if (argc > 2 && !strcmp(argv[2], "-k")) + { + consume_some_resources_and_die (n); + NOT_REACHED (); + } + + int howmany = is_at_root ? EXPECTED_REPETITIONS : 1; + int i, expected_depth = -1; + + for (i = 0; i < howmany; i++) + { + pid_t child_pid; + + /* Spawn a child that will be abnormally terminated. + To speed the test up, do this only for processes + spawned at a certain depth. */ + if (n > EXPECTED_DEPTH_TO_PASS/2) + { + child_pid = spawn_child (n + 1, CRASH); + if (child_pid != -1) + { + if (wait (child_pid) != -1) + fail ("crashed child should return -1."); + } + /* If spawning this child failed, so should + the next spawn_child below. */ + } + + /* Now spawn the child that will recurse. */ + child_pid = spawn_child (n + 1, RECURSE); + + /* If maximum depth is reached, return result. */ + if (child_pid == -1) + return n; + + /* Else wait for child to report how deeply it was able to recurse. */ + int reached_depth = wait (child_pid); + if (reached_depth == -1) + fail ("wait returned -1."); + + /* Record the depth reached during the first run; on subsequent + runs, fail if those runs do not match the depth achieved on the + first run. */ + if (i == 0) + expected_depth = reached_depth; + else if (expected_depth != reached_depth) + fail ("after run %d/%d, expected depth %d, actual depth %d.", + i, howmany, expected_depth, reached_depth); + ASSERT (expected_depth == reached_depth); + } + + consume_some_resources (); + + if (n == 0) + { + if (expected_depth < EXPECTED_DEPTH_TO_PASS) + fail ("should have forked at least %d times.", EXPECTED_DEPTH_TO_PASS); + msg ("success. program forked %d times.", howmany); + msg ("end"); + } + + return expected_depth; +} +// vim: sw=2 diff --git a/src/tests/userprog/no-vm/multi-oom.ck b/src/tests/userprog/no-vm/multi-oom.ck new file mode 100644 index 0000000..59a0bcd --- /dev/null +++ b/src/tests/userprog/no-vm/multi-oom.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, IGNORE_EXIT_CODES => 1, [<<'EOF']); +(multi-oom) begin +(multi-oom) success. program forked 10 times. +(multi-oom) end +EOF +pass; diff --git a/src/tests/userprog/null.ck b/src/tests/userprog/null.ck new file mode 100644 index 0000000..980de35 --- /dev/null +++ b/src/tests/userprog/null.ck @@ -0,0 +1,8 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +system call! +EOF +pass; diff --git a/src/tests/userprog/open-bad-ptr.c b/src/tests/userprog/open-bad-ptr.c new file mode 100644 index 0000000..9cd4edf --- /dev/null +++ b/src/tests/userprog/open-bad-ptr.c @@ -0,0 +1,13 @@ +/* Passes an invalid pointer to the open system call. + The process must be terminated with -1 exit code. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("open(0x20101234): %d", open ((char *) 0x20101234)); + fail ("should have called exit(-1)"); +} diff --git a/src/tests/userprog/open-bad-ptr.ck b/src/tests/userprog/open-bad-ptr.ck new file mode 100644 index 0000000..45349e2 --- /dev/null +++ b/src/tests/userprog/open-bad-ptr.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(open-bad-ptr) begin +(open-bad-ptr) end +open-bad-ptr: exit(0) +EOF +(open-bad-ptr) begin +open-bad-ptr: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/open-boundary.c b/src/tests/userprog/open-boundary.c new file mode 100644 index 0000000..cc8ff8b --- /dev/null +++ b/src/tests/userprog/open-boundary.c @@ -0,0 +1,14 @@ +/* Creates a file whose name spans the boundary between two pages. + This is valid, so it must succeed. */ + +#include <syscall.h> +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (open (copy_string_across_boundary ("sample.txt")) > 1, + "open \"sample.txt\""); +} diff --git a/src/tests/userprog/open-boundary.ck b/src/tests/userprog/open-boundary.ck new file mode 100644 index 0000000..8060d22 --- /dev/null +++ b/src/tests/userprog/open-boundary.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-boundary) begin +(open-boundary) open "sample.txt" +(open-boundary) end +open-boundary: exit(0) +EOF +pass; diff --git a/src/tests/userprog/open-empty.c b/src/tests/userprog/open-empty.c new file mode 100644 index 0000000..3ea9907 --- /dev/null +++ b/src/tests/userprog/open-empty.c @@ -0,0 +1,13 @@ +/* Tries to open a file with the empty string as its name. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle = open (""); + if (handle != -1) + fail ("open() returned %d instead of -1", handle); +} diff --git a/src/tests/userprog/open-empty.ck b/src/tests/userprog/open-empty.ck new file mode 100644 index 0000000..885fb41 --- /dev/null +++ b/src/tests/userprog/open-empty.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-empty) begin +(open-empty) end +open-empty: exit(0) +EOF +pass; diff --git a/src/tests/userprog/open-missing.c b/src/tests/userprog/open-missing.c new file mode 100644 index 0000000..13ecbda --- /dev/null +++ b/src/tests/userprog/open-missing.c @@ -0,0 +1,13 @@ +/* Tries to open a nonexistent file. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle = open ("no-such-file"); + if (handle != -1) + fail ("open() returned %d", handle); +} diff --git a/src/tests/userprog/open-missing.ck b/src/tests/userprog/open-missing.ck new file mode 100644 index 0000000..d72d878 --- /dev/null +++ b/src/tests/userprog/open-missing.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-missing) begin +(open-missing) end +open-missing: exit(0) +EOF +pass; diff --git a/src/tests/userprog/open-normal.c b/src/tests/userprog/open-normal.c new file mode 100644 index 0000000..5132465 --- /dev/null +++ b/src/tests/userprog/open-normal.c @@ -0,0 +1,13 @@ +/* Open a file. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle = open ("sample.txt"); + if (handle < 2) + fail ("open() returned %d", handle); +} diff --git a/src/tests/userprog/open-normal.ck b/src/tests/userprog/open-normal.ck new file mode 100644 index 0000000..4f6c342 --- /dev/null +++ b/src/tests/userprog/open-normal.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-normal) begin +(open-normal) end +open-normal: exit(0) +EOF +pass; diff --git a/src/tests/userprog/open-null.c b/src/tests/userprog/open-null.c new file mode 100644 index 0000000..bb418b8 --- /dev/null +++ b/src/tests/userprog/open-null.c @@ -0,0 +1,12 @@ +/* Tries to open a file with the null pointer as its name. + The process must be terminated with exit code -1. */ + +#include <stddef.h> +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + open (NULL); +} diff --git a/src/tests/userprog/open-null.ck b/src/tests/userprog/open-null.ck new file mode 100644 index 0000000..b4a3bcb --- /dev/null +++ b/src/tests/userprog/open-null.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(open-null) begin +(open-null) end +open-null: exit(0) +EOF +(open-null) begin +open-null: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/open-twice.c b/src/tests/userprog/open-twice.c new file mode 100644 index 0000000..dd333af --- /dev/null +++ b/src/tests/userprog/open-twice.c @@ -0,0 +1,19 @@ +/* Tries to open the same file twice, + which must succeed and must return a different file descriptor + in each case. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int h1 = open ("sample.txt"); + int h2 = open ("sample.txt"); + + CHECK ((h1 = open ("sample.txt")) > 1, "open \"sample.txt\" once"); + CHECK ((h2 = open ("sample.txt")) > 1, "open \"sample.txt\" again"); + if (h1 == h2) + fail ("open() returned %d both times", h1); +} diff --git a/src/tests/userprog/open-twice.ck b/src/tests/userprog/open-twice.ck new file mode 100644 index 0000000..64fa805 --- /dev/null +++ b/src/tests/userprog/open-twice.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(open-twice) begin +(open-twice) open "sample.txt" once +(open-twice) open "sample.txt" again +(open-twice) end +open-twice: exit(0) +EOF +pass; diff --git a/src/tests/userprog/read-bad-fd.c b/src/tests/userprog/read-bad-fd.c new file mode 100644 index 0000000..a8b190d --- /dev/null +++ b/src/tests/userprog/read-bad-fd.c @@ -0,0 +1,21 @@ +/* Tries to read from an invalid fd, + which must either fail silently or terminate the process with + exit code -1. */ + +#include <limits.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char buf; + read (0x20101234, &buf, 1); + read (5, &buf, 1); + read (1234, &buf, 1); + read (-1, &buf, 1); + read (-1024, &buf, 1); + read (INT_MIN, &buf, 1); + read (INT_MAX, &buf, 1); +} diff --git a/src/tests/userprog/read-bad-fd.ck b/src/tests/userprog/read-bad-fd.ck new file mode 100644 index 0000000..5fedcc7 --- /dev/null +++ b/src/tests/userprog/read-bad-fd.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(read-bad-fd) begin +(read-bad-fd) end +read-bad-fd: exit(0) +EOF +(read-bad-fd) begin +read-bad-fd: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/read-bad-ptr.c b/src/tests/userprog/read-bad-ptr.c new file mode 100644 index 0000000..8fe756e --- /dev/null +++ b/src/tests/userprog/read-bad-ptr.c @@ -0,0 +1,16 @@ +/* Passes an invalid pointer to the read system call. + The process must be terminated with -1 exit code. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + read (handle, (char *) 0xc0100000, 123); + fail ("should not have survived read()"); +} diff --git a/src/tests/userprog/read-bad-ptr.ck b/src/tests/userprog/read-bad-ptr.ck new file mode 100644 index 0000000..d10accf --- /dev/null +++ b/src/tests/userprog/read-bad-ptr.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(read-bad-ptr) begin +(read-bad-ptr) open "sample.txt" +(read-bad-ptr) end +read-bad-ptr: exit(0) +EOF +(read-bad-ptr) begin +(read-bad-ptr) open "sample.txt" +read-bad-ptr: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/read-boundary.c b/src/tests/userprog/read-boundary.c new file mode 100644 index 0000000..9c19966 --- /dev/null +++ b/src/tests/userprog/read-boundary.c @@ -0,0 +1,30 @@ +/* Reads data spanning two pages in virtual address space, + which must succeed. */ + +#include <string.h> +#include <syscall.h> +#include "tests/userprog/boundary.h" +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + int byte_cnt; + char *buffer; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + buffer = get_boundary_area () - sizeof sample / 2; + byte_cnt = read (handle, buffer, sizeof sample - 1); + if (byte_cnt != sizeof sample - 1) + fail ("read() returned %d instead of %zu", byte_cnt, sizeof sample - 1); + else if (strcmp (sample, buffer)) + { + msg ("expected text:\n%s", sample); + msg ("text actually read:\n%s", buffer); + fail ("expected text differs from actual"); + } +} diff --git a/src/tests/userprog/read-boundary.ck b/src/tests/userprog/read-boundary.ck new file mode 100644 index 0000000..08dc161 --- /dev/null +++ b/src/tests/userprog/read-boundary.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(read-boundary) begin +(read-boundary) open "sample.txt" +(read-boundary) end +read-boundary: exit(0) +EOF +pass; diff --git a/src/tests/userprog/read-normal.c b/src/tests/userprog/read-normal.c new file mode 100644 index 0000000..16d15cc --- /dev/null +++ b/src/tests/userprog/read-normal.c @@ -0,0 +1,11 @@ +/* Try reading a file in the most normal way. */ + +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + check_file ("sample.txt", sample, sizeof sample - 1); +} diff --git a/src/tests/userprog/read-normal.ck b/src/tests/userprog/read-normal.ck new file mode 100644 index 0000000..0ed2998 --- /dev/null +++ b/src/tests/userprog/read-normal.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(read-normal) begin +(read-normal) open "sample.txt" for verification +(read-normal) verified contents of "sample.txt" +(read-normal) close "sample.txt" +(read-normal) end +read-normal: exit(0) +EOF +pass; diff --git a/src/tests/userprog/read-stdout.c b/src/tests/userprog/read-stdout.c new file mode 100644 index 0000000..d0630b9 --- /dev/null +++ b/src/tests/userprog/read-stdout.c @@ -0,0 +1,14 @@ +/* Try reading from fd 1 (stdout), + which may just fail or terminate the process with -1 exit + code. */ + +#include <stdio.h> +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + char buf; + read (STDOUT_FILENO, &buf, 1); +} diff --git a/src/tests/userprog/read-stdout.ck b/src/tests/userprog/read-stdout.ck new file mode 100644 index 0000000..7d87b52 --- /dev/null +++ b/src/tests/userprog/read-stdout.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(read-stdout) begin +(read-stdout) end +read-stdout: exit(0) +EOF +(read-stdout) begin +read-stdout: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/read-zero.c b/src/tests/userprog/read-zero.c new file mode 100644 index 0000000..e441817 --- /dev/null +++ b/src/tests/userprog/read-zero.c @@ -0,0 +1,22 @@ +/* Try a 0-byte read, which should return 0 without reading + anything. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle, byte_cnt; + char buf; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + buf = 123; + byte_cnt = read (handle, &buf, 0); + if (byte_cnt != 0) + fail ("read() returned %d instead of 0", byte_cnt); + else if (buf != 123) + fail ("0-byte read() modified buffer"); +} diff --git a/src/tests/userprog/read-zero.ck b/src/tests/userprog/read-zero.ck new file mode 100644 index 0000000..8346dbc --- /dev/null +++ b/src/tests/userprog/read-zero.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(read-zero) begin +(read-zero) open "sample.txt" +(read-zero) end +read-zero: exit(0) +EOF +pass; diff --git a/src/tests/userprog/rox-child.c b/src/tests/userprog/rox-child.c new file mode 100644 index 0000000..30afba2 --- /dev/null +++ b/src/tests/userprog/rox-child.c @@ -0,0 +1,5 @@ +/* Ensure that the executable of a running process cannot be + modified, even by a child process. */ + +#define CHILD_CNT "1" +#include "tests/userprog/rox-child.inc" diff --git a/src/tests/userprog/rox-child.ck b/src/tests/userprog/rox-child.ck new file mode 100644 index 0000000..e6363fb --- /dev/null +++ b/src/tests/userprog/rox-child.ck @@ -0,0 +1,20 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(rox-child) begin +(rox-child) open "child-rox" +(rox-child) read "child-rox" +(rox-child) write "child-rox" +(rox-child) exec "child-rox 1" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(rox-child) write "child-rox" +(rox-child) end +rox-child: exit(0) +EOF +pass; diff --git a/src/tests/userprog/rox-child.inc b/src/tests/userprog/rox-child.inc new file mode 100644 index 0000000..1e2ade9 --- /dev/null +++ b/src/tests/userprog/rox-child.inc @@ -0,0 +1,33 @@ +/* -*- c -*- */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + const char *child_cmd = "child-rox " CHILD_CNT; + int handle; + pid_t child; + char buffer[16]; + + /* Open child-rox, read from it, write back same data. */ + CHECK ((handle = open ("child-rox")) > 1, "open \"child-rox\""); + CHECK (read (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "read \"child-rox\""); + seek (handle, 0); + CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "write \"child-rox\""); + + /* Execute child-rox and wait for it. */ + CHECK ((child = exec (child_cmd)) != -1, "exec \"%s\"", child_cmd); + quiet = true; + CHECK (wait (child) == 12, "wait for child"); + quiet = false; + + /* Write to child-rox again. */ + seek (handle, 0); + CHECK (write (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "write \"child-rox\""); +} diff --git a/src/tests/userprog/rox-multichild.c b/src/tests/userprog/rox-multichild.c new file mode 100644 index 0000000..8e74dab --- /dev/null +++ b/src/tests/userprog/rox-multichild.c @@ -0,0 +1,5 @@ +/* Ensure that the executable of a running process cannot be + modified, even in the presence of multiple children. */ + +#define CHILD_CNT "5" +#include "tests/userprog/rox-child.inc" diff --git a/src/tests/userprog/rox-multichild.ck b/src/tests/userprog/rox-multichild.ck new file mode 100644 index 0000000..14b27db --- /dev/null +++ b/src/tests/userprog/rox-multichild.ck @@ -0,0 +1,44 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(rox-multichild) begin +(rox-multichild) open "child-rox" +(rox-multichild) read "child-rox" +(rox-multichild) write "child-rox" +(rox-multichild) exec "child-rox 5" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 4" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 3" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 2" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) exec "child-rox 1" +(child-rox) begin +(child-rox) try to write "child-rox" +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(child-rox) try to write "child-rox" +(child-rox) end +child-rox: exit(12) +(rox-multichild) write "child-rox" +(rox-multichild) end +rox-multichild: exit(0) +EOF +pass; diff --git a/src/tests/userprog/rox-simple.c b/src/tests/userprog/rox-simple.c new file mode 100644 index 0000000..e84a064 --- /dev/null +++ b/src/tests/userprog/rox-simple.c @@ -0,0 +1,19 @@ +/* Ensure that the executable of a running process cannot be + modified. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + char buffer[16]; + + CHECK ((handle = open ("rox-simple")) > 1, "open \"rox-simple\""); + CHECK (read (handle, buffer, sizeof buffer) == (int) sizeof buffer, + "read \"rox-simple\""); + CHECK (write (handle, buffer, sizeof buffer) == 0, + "try to write \"rox-simple\""); +} diff --git a/src/tests/userprog/rox-simple.ck b/src/tests/userprog/rox-simple.ck new file mode 100644 index 0000000..c9dcc66 --- /dev/null +++ b/src/tests/userprog/rox-simple.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(rox-simple) begin +(rox-simple) open "rox-simple" +(rox-simple) read "rox-simple" +(rox-simple) try to write "rox-simple" +(rox-simple) end +rox-simple: exit(0) +EOF +pass; diff --git a/src/tests/userprog/sample.inc b/src/tests/userprog/sample.inc new file mode 100644 index 0000000..59f2bcb --- /dev/null +++ b/src/tests/userprog/sample.inc @@ -0,0 +1,6 @@ +char sample[] = { + "\"Amazing Electronic Fact: If you scuffed your feet long enough without\n" + " touching anything, you would build up so many electrons that your\n" + " finger would explode! But this is nothing to worry about unless you\n" + " have carpeting.\" --Dave Barry\n" +}; diff --git a/src/tests/userprog/sample.txt b/src/tests/userprog/sample.txt new file mode 100644 index 0000000..5050fec --- /dev/null +++ b/src/tests/userprog/sample.txt @@ -0,0 +1,4 @@ +"Amazing Electronic Fact: If you scuffed your feet long enough without + touching anything, you would build up so many electrons that your + finger would explode! But this is nothing to worry about unless you + have carpeting." --Dave Barry diff --git a/src/tests/userprog/sc-bad-arg.c b/src/tests/userprog/sc-bad-arg.c new file mode 100644 index 0000000..0b512a0 --- /dev/null +++ b/src/tests/userprog/sc-bad-arg.c @@ -0,0 +1,17 @@ +/* Sticks a system call number (SYS_EXIT) at the very top of the + stack, then invokes a system call with the stack pointer + (%esp) set to its address. The process must be terminated + with -1 exit code because the argument to the system call + would be above the top of the user address space. */ + +#include <syscall-nr.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl $0xbffffffc, %%esp; movl %0, (%%esp); int $0x30" + : : "i" (SYS_EXIT)); + fail ("should have called exit(-1)"); +} diff --git a/src/tests/userprog/sc-bad-arg.ck b/src/tests/userprog/sc-bad-arg.ck new file mode 100644 index 0000000..8981105 --- /dev/null +++ b/src/tests/userprog/sc-bad-arg.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-bad-arg) begin +sc-bad-arg: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/sc-bad-sp.c b/src/tests/userprog/sc-bad-sp.c new file mode 100644 index 0000000..39cce84 --- /dev/null +++ b/src/tests/userprog/sc-bad-sp.c @@ -0,0 +1,20 @@ +/* Invokes a system call with the stack pointer (%esp) set to a + bad address. The process must be terminated with -1 exit + code. + + For Project 3: The bad address lies approximately 64MB below + the code segment, so there is no ambiguity that this attempt + must be rejected even after stack growth is implemented. + Moreover, a good stack growth heuristics should probably not + grow the stack for the purpose of reading the system call + number and arguments. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl $.-(64*1024*1024), %esp; int $0x30"); + fail ("should have called exit(-1)"); +} diff --git a/src/tests/userprog/sc-bad-sp.ck b/src/tests/userprog/sc-bad-sp.ck new file mode 100644 index 0000000..498cec1 --- /dev/null +++ b/src/tests/userprog/sc-bad-sp.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-bad-sp) begin +sc-bad-sp: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/sc-boundary-2.c b/src/tests/userprog/sc-boundary-2.c new file mode 100644 index 0000000..8acf036 --- /dev/null +++ b/src/tests/userprog/sc-boundary-2.c @@ -0,0 +1,22 @@ +/* Invokes a system call with one byte of the system call's + argument on a separate page from the rest of the bytes. This + must work. */ + +#include <syscall-nr.h> +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + /* Make one byte of a syscall argument hang over into a second + page. */ + int *p = (int *) ((char *) get_boundary_area () - 7); + p[0] = SYS_EXIT; + p[1] = 67; + + /* Invoke the system call. */ + asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p)); + fail ("should have called exit(67)"); +} diff --git a/src/tests/userprog/sc-boundary-2.ck b/src/tests/userprog/sc-boundary-2.ck new file mode 100644 index 0000000..43766bf --- /dev/null +++ b/src/tests/userprog/sc-boundary-2.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-boundary-2) begin +sc-boundary-2: exit(67) +EOF +pass; diff --git a/src/tests/userprog/sc-boundary.c b/src/tests/userprog/sc-boundary.c new file mode 100644 index 0000000..d889535 --- /dev/null +++ b/src/tests/userprog/sc-boundary.c @@ -0,0 +1,22 @@ +/* Invokes a system call with the system call number and its + argument on separate pages. This must work. */ + +#include <syscall-nr.h> +#include "tests/userprog/boundary.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + /* Put a syscall number at the end of one page + and its argument at the beginning of another. */ + int *p = get_boundary_area (); + p--; + p[0] = SYS_EXIT; + p[1] = 42; + + /* Invoke the system call. */ + asm volatile ("movl %0, %%esp; int $0x30" : : "g" (p)); + fail ("should have called exit(42)"); +} diff --git a/src/tests/userprog/sc-boundary.ck b/src/tests/userprog/sc-boundary.ck new file mode 100644 index 0000000..3f7cbaf --- /dev/null +++ b/src/tests/userprog/sc-boundary.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(sc-boundary) begin +sc-boundary: exit(42) +EOF +pass; diff --git a/src/tests/userprog/wait-bad-pid.c b/src/tests/userprog/wait-bad-pid.c new file mode 100644 index 0000000..3fe8ee4 --- /dev/null +++ b/src/tests/userprog/wait-bad-pid.c @@ -0,0 +1,11 @@ +/* Waits for an invalid pid. This may fail or terminate the + process with -1 exit code. */ + +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + wait ((pid_t) 0x0c020301); +} diff --git a/src/tests/userprog/wait-bad-pid.ck b/src/tests/userprog/wait-bad-pid.ck new file mode 100644 index 0000000..db63fb9 --- /dev/null +++ b/src/tests/userprog/wait-bad-pid.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(wait-bad-pid) begin +(wait-bad-pid) end +wait-bad-pid: exit(0) +EOF +(wait-bad-pid) begin +wait-bad-pid: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/wait-killed.c b/src/tests/userprog/wait-killed.c new file mode 100644 index 0000000..6a2a6b5 --- /dev/null +++ b/src/tests/userprog/wait-killed.c @@ -0,0 +1,11 @@ +/* Wait for a process that will be killed for bad behavior. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("wait(exec()) = %d", wait (exec ("child-bad"))); +} diff --git a/src/tests/userprog/wait-killed.ck b/src/tests/userprog/wait-killed.ck new file mode 100644 index 0000000..5df0e9c --- /dev/null +++ b/src/tests/userprog/wait-killed.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(wait-killed) begin +(child-bad) begin +child-bad: exit(-1) +(wait-killed) wait(exec()) = -1 +(wait-killed) end +wait-killed: exit(0) +EOF +pass; diff --git a/src/tests/userprog/wait-simple.c b/src/tests/userprog/wait-simple.c new file mode 100644 index 0000000..d3afcf3 --- /dev/null +++ b/src/tests/userprog/wait-simple.c @@ -0,0 +1,11 @@ +/* Wait for a subprocess to finish. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + msg ("wait(exec()) = %d", wait (exec ("child-simple"))); +} diff --git a/src/tests/userprog/wait-simple.ck b/src/tests/userprog/wait-simple.ck new file mode 100644 index 0000000..93dd577 --- /dev/null +++ b/src/tests/userprog/wait-simple.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(wait-simple) begin +(child-simple) run +child-simple: exit(81) +(wait-simple) wait(exec()) = 81 +(wait-simple) end +wait-simple: exit(0) +EOF +pass; diff --git a/src/tests/userprog/wait-twice.c b/src/tests/userprog/wait-twice.c new file mode 100644 index 0000000..785e684 --- /dev/null +++ b/src/tests/userprog/wait-twice.c @@ -0,0 +1,15 @@ +/* Wait for a subprocess to finish, twice. + The first call must wait in the usual way and return the exit code. + The second wait call must return -1 immediately. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + pid_t child = exec ("child-simple"); + msg ("wait(exec()) = %d", wait (child)); + msg ("wait(exec()) = %d", wait (child)); +} diff --git a/src/tests/userprog/wait-twice.ck b/src/tests/userprog/wait-twice.ck new file mode 100644 index 0000000..6d53843 --- /dev/null +++ b/src/tests/userprog/wait-twice.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(wait-twice) begin +(child-simple) run +child-simple: exit(81) +(wait-twice) wait(exec()) = 81 +(wait-twice) wait(exec()) = -1 +(wait-twice) end +wait-twice: exit(0) +EOF +pass; diff --git a/src/tests/userprog/write-bad-fd.c b/src/tests/userprog/write-bad-fd.c new file mode 100644 index 0000000..f3b1151 --- /dev/null +++ b/src/tests/userprog/write-bad-fd.c @@ -0,0 +1,20 @@ +/* Tries to write to an invalid fd, + which must either fail silently or terminate the process with + exit code -1. */ + +#include <limits.h> +#include <syscall.h> +#include "tests/main.h" + +void +test_main (void) +{ + char buf = 123; + write (0x01012342, &buf, 1); + write (7, &buf, 1); + write (2546, &buf, 1); + write (-5, &buf, 1); + write (-8192, &buf, 1); + write (INT_MIN + 1, &buf, 1); + write (INT_MAX - 1, &buf, 1); +} diff --git a/src/tests/userprog/write-bad-fd.ck b/src/tests/userprog/write-bad-fd.ck new file mode 100644 index 0000000..8da7a8b --- /dev/null +++ b/src/tests/userprog/write-bad-fd.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(write-bad-fd) begin +(write-bad-fd) end +write-bad-fd: exit(0) +EOF +(write-bad-fd) begin +write-bad-fd: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/write-bad-ptr.c b/src/tests/userprog/write-bad-ptr.c new file mode 100644 index 0000000..5336479 --- /dev/null +++ b/src/tests/userprog/write-bad-ptr.c @@ -0,0 +1,16 @@ +/* Passes an invalid pointer to the write system call. + The process must be terminated with -1 exit code. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + write (handle, (char *) 0x10123420, 123); + fail ("should have exited with -1"); +} diff --git a/src/tests/userprog/write-bad-ptr.ck b/src/tests/userprog/write-bad-ptr.ck new file mode 100644 index 0000000..ad9f399 --- /dev/null +++ b/src/tests/userprog/write-bad-ptr.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(write-bad-ptr) begin +(write-bad-ptr) open "sample.txt" +(write-bad-ptr) end +write-bad-ptr: exit(0) +EOF +(write-bad-ptr) begin +(write-bad-ptr) open "sample.txt" +write-bad-ptr: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/write-boundary.c b/src/tests/userprog/write-boundary.c new file mode 100644 index 0000000..d2de1d4 --- /dev/null +++ b/src/tests/userprog/write-boundary.c @@ -0,0 +1,25 @@ +/* Writes data spanning two pages in virtual address space, + which must succeed. */ + +#include <string.h> +#include <syscall.h> +#include "tests/userprog/boundary.h" +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + int byte_cnt; + char *sample_p; + + sample_p = copy_string_across_boundary (sample); + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + byte_cnt = write (handle, sample_p, sizeof sample - 1); + if (byte_cnt != sizeof sample - 1) + fail ("write() returned %d instead of %zu", byte_cnt, sizeof sample - 1); +} diff --git a/src/tests/userprog/write-boundary.ck b/src/tests/userprog/write-boundary.ck new file mode 100644 index 0000000..7883781 --- /dev/null +++ b/src/tests/userprog/write-boundary.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(write-boundary) begin +(write-boundary) open "sample.txt" +(write-boundary) end +write-boundary: exit(0) +EOF +pass; diff --git a/src/tests/userprog/write-normal.c b/src/tests/userprog/write-normal.c new file mode 100644 index 0000000..e0297aa --- /dev/null +++ b/src/tests/userprog/write-normal.c @@ -0,0 +1,20 @@ +/* Try writing a file in the most normal way. */ + +#include <syscall.h> +#include "tests/userprog/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle, byte_cnt; + + CHECK (create ("test.txt", sizeof sample - 1), "create \"test.txt\""); + CHECK ((handle = open ("test.txt")) > 1, "open \"test.txt\""); + + byte_cnt = write (handle, sample, sizeof sample - 1); + if (byte_cnt != sizeof sample - 1) + fail ("write() returned %d instead of %zu", byte_cnt, sizeof sample - 1); +} + diff --git a/src/tests/userprog/write-normal.ck b/src/tests/userprog/write-normal.ck new file mode 100644 index 0000000..9fa6024 --- /dev/null +++ b/src/tests/userprog/write-normal.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(write-normal) begin +(write-normal) create "test.txt" +(write-normal) open "test.txt" +(write-normal) end +write-normal: exit(0) +EOF +pass; diff --git a/src/tests/userprog/write-stdin.c b/src/tests/userprog/write-stdin.c new file mode 100644 index 0000000..491ea53 --- /dev/null +++ b/src/tests/userprog/write-stdin.c @@ -0,0 +1,14 @@ +/* Try writing to fd 0 (stdin), + which may just fail or terminate the process with -1 exit + code. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char buf = 123; + write (0, &buf, 1); +} diff --git a/src/tests/userprog/write-stdin.ck b/src/tests/userprog/write-stdin.ck new file mode 100644 index 0000000..a6caf81 --- /dev/null +++ b/src/tests/userprog/write-stdin.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(write-stdin) begin +(write-stdin) end +write-stdin: exit(0) +EOF +(write-stdin) begin +write-stdin: exit(-1) +EOF +pass; diff --git a/src/tests/userprog/write-zero.c b/src/tests/userprog/write-zero.c new file mode 100644 index 0000000..d8dac9b --- /dev/null +++ b/src/tests/userprog/write-zero.c @@ -0,0 +1,20 @@ +/* Try a 0-byte write, which should return 0 without writing + anything. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle, byte_cnt; + char buf; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + + buf = 123; + byte_cnt = write (handle, &buf, 0); + if (byte_cnt != 0) + fail("write() returned %d instead of 0", byte_cnt); +} diff --git a/src/tests/userprog/write-zero.ck b/src/tests/userprog/write-zero.ck new file mode 100644 index 0000000..cc4cd60 --- /dev/null +++ b/src/tests/userprog/write-zero.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(write-zero) begin +(write-zero) open "sample.txt" +(write-zero) end +write-zero: exit(0) +EOF +pass; diff --git a/src/tests/vm/Grading b/src/tests/vm/Grading new file mode 100644 index 0000000..f0c2c13 --- /dev/null +++ b/src/tests/vm/Grading @@ -0,0 +1,12 @@ +# Percentage of the testing point total designated for each set of +# tests. + +# This project is primarily about virtual memory, but all the previous +# functionality should work too, and it's easy to screw it up, thus +# the equal weight placed on each. + +50% tests/vm/Rubric.functionality +15% tests/vm/Rubric.robustness +10% tests/userprog/Rubric.functionality +5% tests/userprog/Rubric.robustness +20% tests/filesys/base/Rubric diff --git a/src/tests/vm/Make.tests b/src/tests/vm/Make.tests new file mode 100644 index 0000000..04b1b81 --- /dev/null +++ b/src/tests/vm/Make.tests @@ -0,0 +1,103 @@ +# -*- makefile -*- + +tests/vm_TESTS = $(addprefix tests/vm/,pt-grow-stack pt-grow-pusha \ +pt-grow-bad pt-big-stk-obj pt-bad-addr pt-bad-read pt-write-code \ +pt-write-code2 pt-grow-stk-sc page-linear page-parallel page-merge-seq \ +page-merge-par page-merge-stk page-merge-mm page-shuffle mmap-read \ +mmap-close mmap-unmap mmap-overlap mmap-twice mmap-write mmap-exit \ +mmap-shuffle mmap-bad-fd mmap-clean mmap-inherit mmap-misalign \ +mmap-null mmap-over-code mmap-over-data mmap-over-stk mmap-remove \ +mmap-zero) + +tests/vm_PROGS = $(tests/vm_TESTS) $(addprefix tests/vm/,child-linear \ +child-sort child-qsort child-qsort-mm child-mm-wrt child-inherit) + +tests/vm/pt-grow-stack_SRC = tests/vm/pt-grow-stack.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/pt-grow-pusha_SRC = tests/vm/pt-grow-pusha.c tests/lib.c \ +tests/main.c +tests/vm/pt-grow-bad_SRC = tests/vm/pt-grow-bad.c tests/lib.c tests/main.c +tests/vm/pt-big-stk-obj_SRC = tests/vm/pt-big-stk-obj.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/pt-bad-addr_SRC = tests/vm/pt-bad-addr.c tests/lib.c tests/main.c +tests/vm/pt-bad-read_SRC = tests/vm/pt-bad-read.c tests/lib.c tests/main.c +tests/vm/pt-write-code_SRC = tests/vm/pt-write-code.c tests/lib.c tests/main.c +tests/vm/pt-write-code2_SRC = tests/vm/pt-write-code-2.c tests/lib.c tests/main.c +tests/vm/pt-grow-stk-sc_SRC = tests/vm/pt-grow-stk-sc.c tests/lib.c tests/main.c +tests/vm/page-linear_SRC = tests/vm/page-linear.c tests/arc4.c \ +tests/lib.c tests/main.c +tests/vm/page-parallel_SRC = tests/vm/page-parallel.c tests/lib.c tests/main.c +tests/vm/page-merge-seq_SRC = tests/vm/page-merge-seq.c tests/arc4.c \ +tests/lib.c tests/main.c +tests/vm/page-merge-par_SRC = tests/vm/page-merge-par.c \ +tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c +tests/vm/page-merge-stk_SRC = tests/vm/page-merge-stk.c \ +tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c +tests/vm/page-merge-mm_SRC = tests/vm/page-merge-mm.c \ +tests/vm/parallel-merge.c tests/arc4.c tests/lib.c tests/main.c +tests/vm/page-shuffle_SRC = tests/vm/page-shuffle.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/mmap-read_SRC = tests/vm/mmap-read.c tests/lib.c tests/main.c +tests/vm/mmap-close_SRC = tests/vm/mmap-close.c tests/lib.c tests/main.c +tests/vm/mmap-unmap_SRC = tests/vm/mmap-unmap.c tests/lib.c tests/main.c +tests/vm/mmap-overlap_SRC = tests/vm/mmap-overlap.c tests/lib.c tests/main.c +tests/vm/mmap-twice_SRC = tests/vm/mmap-twice.c tests/lib.c tests/main.c +tests/vm/mmap-write_SRC = tests/vm/mmap-write.c tests/lib.c tests/main.c +tests/vm/mmap-exit_SRC = tests/vm/mmap-exit.c tests/lib.c tests/main.c +tests/vm/mmap-shuffle_SRC = tests/vm/mmap-shuffle.c tests/arc4.c \ +tests/cksum.c tests/lib.c tests/main.c +tests/vm/mmap-bad-fd_SRC = tests/vm/mmap-bad-fd.c tests/lib.c tests/main.c +tests/vm/mmap-clean_SRC = tests/vm/mmap-clean.c tests/lib.c tests/main.c +tests/vm/mmap-inherit_SRC = tests/vm/mmap-inherit.c tests/lib.c tests/main.c +tests/vm/mmap-misalign_SRC = tests/vm/mmap-misalign.c tests/lib.c \ +tests/main.c +tests/vm/mmap-null_SRC = tests/vm/mmap-null.c tests/lib.c tests/main.c +tests/vm/mmap-over-code_SRC = tests/vm/mmap-over-code.c tests/lib.c \ +tests/main.c +tests/vm/mmap-over-data_SRC = tests/vm/mmap-over-data.c tests/lib.c \ +tests/main.c +tests/vm/mmap-over-stk_SRC = tests/vm/mmap-over-stk.c tests/lib.c tests/main.c +tests/vm/mmap-remove_SRC = tests/vm/mmap-remove.c tests/lib.c tests/main.c +tests/vm/mmap-zero_SRC = tests/vm/mmap-zero.c tests/lib.c tests/main.c + +tests/vm/child-linear_SRC = tests/vm/child-linear.c tests/arc4.c tests/lib.c +tests/vm/child-qsort_SRC = tests/vm/child-qsort.c tests/vm/qsort.c tests/lib.c +tests/vm/child-qsort-mm_SRC = tests/vm/child-qsort-mm.c tests/vm/qsort.c \ +tests/lib.c +tests/vm/child-sort_SRC = tests/vm/child-sort.c tests/lib.c +tests/vm/child-mm-wrt_SRC = tests/vm/child-mm-wrt.c tests/lib.c tests/main.c +tests/vm/child-inherit_SRC = tests/vm/child-inherit.c tests/lib.c tests/main.c + +tests/vm/pt-bad-read_PUTFILES = tests/vm/sample.txt +tests/vm/pt-write-code2_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-close_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-read_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-unmap_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-twice_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-overlap_PUTFILES = tests/vm/zeros +tests/vm/mmap-exit_PUTFILES = tests/vm/child-mm-wrt +tests/vm/page-parallel_PUTFILES = tests/vm/child-linear +tests/vm/page-merge-seq_PUTFILES = tests/vm/child-sort +tests/vm/page-merge-par_PUTFILES = tests/vm/child-sort +tests/vm/page-merge-stk_PUTFILES = tests/vm/child-qsort +tests/vm/page-merge-mm_PUTFILES = tests/vm/child-qsort-mm +tests/vm/mmap-clean_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-inherit_PUTFILES = tests/vm/sample.txt tests/vm/child-inherit +tests/vm/mmap-misalign_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-null_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-over-code_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-over-data_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-over-stk_PUTFILES = tests/vm/sample.txt +tests/vm/mmap-remove_PUTFILES = tests/vm/sample.txt + +tests/vm/page-linear.output: TIMEOUT = 300 +tests/vm/page-shuffle.output: TIMEOUT = 600 +tests/vm/mmap-shuffle.output: TIMEOUT = 600 +tests/vm/page-merge-seq.output: TIMEOUT = 600 +tests/vm/page-merge-par.output: TIMEOUT = 600 + +tests/vm/zeros: + dd if=/dev/zero of=$@ bs=1024 count=6 + +clean:: + rm -f tests/vm/zeros diff --git a/src/tests/vm/Rubric.functionality b/src/tests/vm/Rubric.functionality new file mode 100644 index 0000000..8a86612 --- /dev/null +++ b/src/tests/vm/Rubric.functionality @@ -0,0 +1,30 @@ +Functionality of virtual memory subsystem: +- Test stack growth. +3 pt-grow-stack +3 pt-grow-stk-sc +3 pt-big-stk-obj +3 pt-grow-pusha + +- Test paging behavior. +3 page-linear +3 page-parallel +3 page-shuffle +4 page-merge-seq +4 page-merge-par +4 page-merge-mm +4 page-merge-stk + +- Test "mmap" system call. +2 mmap-read +2 mmap-write +2 mmap-shuffle + +2 mmap-twice + +2 mmap-unmap +1 mmap-exit + +3 mmap-clean + +2 mmap-close +2 mmap-remove diff --git a/src/tests/vm/Rubric.robustness b/src/tests/vm/Rubric.robustness new file mode 100644 index 0000000..0b2552f --- /dev/null +++ b/src/tests/vm/Rubric.robustness @@ -0,0 +1,21 @@ +Robustness of virtual memory subsystem: +- Test robustness of page table support. +2 pt-bad-addr +3 pt-bad-read +2 pt-write-code +3 pt-write-code2 +4 pt-grow-bad + +- Test robustness of "mmap" system call. +1 mmap-bad-fd +1 mmap-inherit +1 mmap-null +1 mmap-zero + +2 mmap-misalign + +2 mmap-over-code +2 mmap-over-data +2 mmap-over-stk +2 mmap-overlap + diff --git a/src/tests/vm/child-inherit.c b/src/tests/vm/child-inherit.c new file mode 100644 index 0000000..d3186a1 --- /dev/null +++ b/src/tests/vm/child-inherit.c @@ -0,0 +1,16 @@ +/* Child process for mmap-inherit test. + Tries to write to a mapping present in the parent. + The process must be terminated with -1 exit code. */ + +#include <string.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + memset ((char *) 0x54321000, 0, 4096); + fail ("child can modify parent's memory mappings"); +} + diff --git a/src/tests/vm/child-linear.c b/src/tests/vm/child-linear.c new file mode 100644 index 0000000..eca3e3f --- /dev/null +++ b/src/tests/vm/child-linear.c @@ -0,0 +1,36 @@ +/* Child process of page-parallel. + Encrypts 1 MB of zeros, then decrypts it, and ensures that + the zeros are back. */ + +#include <string.h> +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +const char *test_name = "child-linear"; + +#define SIZE (1024 * 1024) +static char buf[SIZE]; + +int +main (int argc, char *argv[]) +{ + const char *key = argv[argc - 1]; + struct arc4 arc4; + size_t i; + + /* Encrypt zeros. */ + arc4_init (&arc4, key, strlen (key)); + arc4_crypt (&arc4, buf, SIZE); + + /* Decrypt back to zeros. */ + arc4_init (&arc4, key, strlen (key)); + arc4_crypt (&arc4, buf, SIZE); + + /* Check that it's all zeros. */ + for (i = 0; i < SIZE; i++) + if (buf[i] != '\0') + fail ("byte %zu != 0", i); + + return 0x42; +} diff --git a/src/tests/vm/child-mm-wrt.c b/src/tests/vm/child-mm-wrt.c new file mode 100644 index 0000000..8419788 --- /dev/null +++ b/src/tests/vm/child-mm-wrt.c @@ -0,0 +1,24 @@ +/* Child process of mmap-exit. + Mmaps a file and writes to it via the mmap'ing, then exits + without calling munmap. The data in the mapped region must be + written out at program termination. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + + CHECK (create ("sample.txt", sizeof sample), "create \"sample.txt\""); + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, ACTUAL) != MAP_FAILED, "mmap \"sample.txt\""); + memcpy (ACTUAL, sample, sizeof sample); +} + diff --git a/src/tests/vm/child-qsort-mm.c b/src/tests/vm/child-qsort-mm.c new file mode 100644 index 0000000..db45499 --- /dev/null +++ b/src/tests/vm/child-qsort-mm.c @@ -0,0 +1,25 @@ +/* Mmaps a 128 kB file "sorts" the bytes in it, using quick sort, + a multi-pass divide and conquer algorithm. */ + +#include <debug.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" +#include "tests/vm/qsort.h" + +const char *test_name = "child-qsort-mm"; + +int +main (int argc UNUSED, char *argv[]) +{ + int handle; + unsigned char *p = (unsigned char *) 0x10000000; + + quiet = true; + + CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]); + CHECK (mmap (handle, p) != MAP_FAILED, "mmap \"%s\"", argv[1]); + qsort_bytes (p, 1024 * 128); + + return 80; +} diff --git a/src/tests/vm/child-qsort.c b/src/tests/vm/child-qsort.c new file mode 100644 index 0000000..355f4eb --- /dev/null +++ b/src/tests/vm/child-qsort.c @@ -0,0 +1,32 @@ +/* Reads a 128 kB file onto the stack and "sorts" the bytes in + it, using quick sort, a multi-pass divide and conquer + algorithm. The sorted data is written back to the same file + in-place. */ + +#include <debug.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" +#include "tests/vm/qsort.h" + +const char *test_name = "child-qsort"; + +int +main (int argc UNUSED, char *argv[]) +{ + int handle; + unsigned char buf[128 * 1024]; + size_t size; + + quiet = true; + + CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]); + + size = read (handle, buf, sizeof buf); + qsort_bytes (buf, sizeof buf); + seek (handle, 0); + write (handle, buf, size); + close (handle); + + return 72; +} diff --git a/src/tests/vm/child-sort.c b/src/tests/vm/child-sort.c new file mode 100644 index 0000000..dff2c77 --- /dev/null +++ b/src/tests/vm/child-sort.c @@ -0,0 +1,42 @@ +/* Reads a 128 kB file into static data and "sorts" the bytes in + it, using counting sort, a single-pass algorithm. The sorted + data is written back to the same file in-place. */ + +#include <debug.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +const char *test_name = "child-sort"; + +unsigned char buf[128 * 1024]; +size_t histogram[256]; + +int +main (int argc UNUSED, char *argv[]) +{ + int handle; + unsigned char *p; + size_t size; + size_t i; + + quiet = true; + + CHECK ((handle = open (argv[1])) > 1, "open \"%s\"", argv[1]); + + size = read (handle, buf, sizeof buf); + for (i = 0; i < size; i++) + histogram[buf[i]]++; + p = buf; + for (i = 0; i < sizeof histogram / sizeof *histogram; i++) + { + size_t j = histogram[i]; + while (j-- > 0) + *p++ = i; + } + seek (handle, 0); + write (handle, buf, size); + close (handle); + + return 123; +} diff --git a/src/tests/vm/mmap-bad-fd.c b/src/tests/vm/mmap-bad-fd.c new file mode 100644 index 0000000..76a7b50 --- /dev/null +++ b/src/tests/vm/mmap-bad-fd.c @@ -0,0 +1,15 @@ +/* Tries to mmap an invalid fd, + which must either fail silently or terminate the process with + exit code -1. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + CHECK (mmap (0x5678, (void *) 0x10000000) == MAP_FAILED, + "try to mmap invalid fd"); +} + diff --git a/src/tests/vm/mmap-bad-fd.ck b/src/tests/vm/mmap-bad-fd.ck new file mode 100644 index 0000000..f3f58d5 --- /dev/null +++ b/src/tests/vm/mmap-bad-fd.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF', <<'EOF']); +(mmap-bad-fd) begin +(mmap-bad-fd) try to mmap invalid fd +(mmap-bad-fd) end +mmap-bad-fd: exit(0) +EOF +(mmap-bad-fd) begin +(mmap-bad-fd) try to mmap invalid fd +mmap-bad-fd: exit(-1) +EOF +pass; diff --git a/src/tests/vm/mmap-clean.c b/src/tests/vm/mmap-clean.c new file mode 100644 index 0000000..ea1dc9c --- /dev/null +++ b/src/tests/vm/mmap-clean.c @@ -0,0 +1,53 @@ +/* Verifies that mmap'd regions are only written back on munmap + if the data was actually modified in memory. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + static const char overwrite[] = "Now is the time for all good..."; + static char buffer[sizeof sample - 1]; + char *actual = (char *) 0x54321000; + int handle; + mapid_t map; + + /* Open file, map, verify data. */ + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\""); + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Modify file. */ + CHECK (write (handle, overwrite, strlen (overwrite)) + == (int) strlen (overwrite), + "write \"sample.txt\""); + + /* Close mapping. Data should not be written back, because we + didn't modify it via the mapping. */ + msg ("munmap \"sample.txt\""); + munmap (map); + + /* Read file back. */ + msg ("seek \"sample.txt\""); + seek (handle, 0); + CHECK (read (handle, buffer, sizeof buffer) == sizeof buffer, + "read \"sample.txt\""); + + /* Verify that file overwrite worked. */ + if (memcmp (buffer, overwrite, strlen (overwrite)) + || memcmp (buffer + strlen (overwrite), sample + strlen (overwrite), + strlen (sample) - strlen (overwrite))) + { + if (!memcmp (buffer, sample, strlen (sample))) + fail ("munmap wrote back clean page"); + else + fail ("read surprising data from file"); + } + else + msg ("file change was retained after munmap"); +} diff --git a/src/tests/vm/mmap-clean.ck b/src/tests/vm/mmap-clean.ck new file mode 100644 index 0000000..1666d6c --- /dev/null +++ b/src/tests/vm/mmap-clean.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-clean) begin +(mmap-clean) open "sample.txt" +(mmap-clean) mmap "sample.txt" +(mmap-clean) write "sample.txt" +(mmap-clean) munmap "sample.txt" +(mmap-clean) seek "sample.txt" +(mmap-clean) read "sample.txt" +(mmap-clean) file change was retained after munmap +(mmap-clean) end +EOF +pass; diff --git a/src/tests/vm/mmap-close.c b/src/tests/vm/mmap-close.c new file mode 100644 index 0000000..d016ee3 --- /dev/null +++ b/src/tests/vm/mmap-close.c @@ -0,0 +1,27 @@ +/* Verifies that memory mappings persist after file close. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + mapid_t map; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\""); + + close (handle); + + if (memcmp (ACTUAL, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + munmap (map); +} diff --git a/src/tests/vm/mmap-close.ck b/src/tests/vm/mmap-close.ck new file mode 100644 index 0000000..d15e41a --- /dev/null +++ b/src/tests/vm/mmap-close.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-close) begin +(mmap-close) open "sample.txt" +(mmap-close) mmap "sample.txt" +(mmap-close) end +EOF +pass; diff --git a/src/tests/vm/mmap-exit.c b/src/tests/vm/mmap-exit.c new file mode 100644 index 0000000..7a2278a --- /dev/null +++ b/src/tests/vm/mmap-exit.c @@ -0,0 +1,22 @@ +/* Executes child-mm-wrt and verifies that the writes that should + have occurred really did. */ + +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + pid_t child; + + /* Make child write file. */ + quiet = true; + CHECK ((child = exec ("child-mm-wrt")) != -1, "exec \"child-mm-wrt\""); + CHECK (wait (child) == 0, "wait for child (should return 0)"); + quiet = false; + + /* Check file contents. */ + check_file ("sample.txt", sample, sizeof sample); +} diff --git a/src/tests/vm/mmap-exit.ck b/src/tests/vm/mmap-exit.ck new file mode 100644 index 0000000..457d34a --- /dev/null +++ b/src/tests/vm/mmap-exit.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-exit) begin +(child-mm-wrt) begin +(child-mm-wrt) create "sample.txt" +(child-mm-wrt) open "sample.txt" +(child-mm-wrt) mmap "sample.txt" +(child-mm-wrt) end +(mmap-exit) open "sample.txt" for verification +(mmap-exit) verified contents of "sample.txt" +(mmap-exit) close "sample.txt" +(mmap-exit) end +EOF +pass; diff --git a/src/tests/vm/mmap-inherit.c b/src/tests/vm/mmap-inherit.c new file mode 100644 index 0000000..7fa9607 --- /dev/null +++ b/src/tests/vm/mmap-inherit.c @@ -0,0 +1,32 @@ +/* Maps a file into memory and runs child-inherit to verify that + mappings are not inherited. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual = (char *) 0x54321000; + int handle; + pid_t child; + + /* Open file, map, verify data. */ + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, actual) != MAP_FAILED, "mmap \"sample.txt\""); + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Spawn child and wait. */ + CHECK ((child = exec ("child-inherit")) != -1, "exec \"child-inherit\""); + quiet = true; + CHECK (wait (child) == -1, "wait for child (should return -1)"); + quiet = false; + + /* Verify data again. */ + CHECK (!memcmp (actual, sample, strlen (sample)), + "checking that mmap'd file still has same data"); +} diff --git a/src/tests/vm/mmap-inherit.ck b/src/tests/vm/mmap-inherit.ck new file mode 100644 index 0000000..7e69122 --- /dev/null +++ b/src/tests/vm/mmap-inherit.ck @@ -0,0 +1,16 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ( [<<'EOF']); +(mmap-inherit) begin +(mmap-inherit) open "sample.txt" +(mmap-inherit) mmap "sample.txt" +(mmap-inherit) exec "child-inherit" +(child-inherit) begin +child-inherit: exit(-1) +(mmap-inherit) checking that mmap'd file still has same data +(mmap-inherit) end +mmap-inherit: exit(0) +EOF +pass; diff --git a/src/tests/vm/mmap-misalign.c b/src/tests/vm/mmap-misalign.c new file mode 100644 index 0000000..34141a9 --- /dev/null +++ b/src/tests/vm/mmap-misalign.c @@ -0,0 +1,16 @@ +/* Verifies that misaligned memory mappings are disallowed. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) 0x10001234) == MAP_FAILED, + "try to mmap at misaligned address"); +} + diff --git a/src/tests/vm/mmap-misalign.ck b/src/tests/vm/mmap-misalign.ck new file mode 100644 index 0000000..145a2e8 --- /dev/null +++ b/src/tests/vm/mmap-misalign.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-misalign) begin +(mmap-misalign) open "sample.txt" +(mmap-misalign) try to mmap at misaligned address +(mmap-misalign) end +EOF +pass; diff --git a/src/tests/vm/mmap-null.c b/src/tests/vm/mmap-null.c new file mode 100644 index 0000000..f8ef075 --- /dev/null +++ b/src/tests/vm/mmap-null.c @@ -0,0 +1,15 @@ +/* Verifies that memory mappings at address 0 are disallowed. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, NULL) == MAP_FAILED, "try to mmap at address 0"); +} + diff --git a/src/tests/vm/mmap-null.ck b/src/tests/vm/mmap-null.ck new file mode 100644 index 0000000..aacdd65 --- /dev/null +++ b/src/tests/vm/mmap-null.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-null) begin +(mmap-null) open "sample.txt" +(mmap-null) try to mmap at address 0 +(mmap-null) end +EOF +pass; diff --git a/src/tests/vm/mmap-over-code.c b/src/tests/vm/mmap-over-code.c new file mode 100644 index 0000000..d3619a3 --- /dev/null +++ b/src/tests/vm/mmap-over-code.c @@ -0,0 +1,19 @@ +/* Verifies that mapping over the code segment is disallowed. */ + +#include <stdint.h> +#include <round.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + uintptr_t test_main_page = ROUND_DOWN ((uintptr_t) test_main, 4096); + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) test_main_page) == MAP_FAILED, + "try to mmap over code segment"); +} + diff --git a/src/tests/vm/mmap-over-code.ck b/src/tests/vm/mmap-over-code.ck new file mode 100644 index 0000000..b5b23c7 --- /dev/null +++ b/src/tests/vm/mmap-over-code.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-over-code) begin +(mmap-over-code) open "sample.txt" +(mmap-over-code) try to mmap over code segment +(mmap-over-code) end +EOF +pass; diff --git a/src/tests/vm/mmap-over-data.c b/src/tests/vm/mmap-over-data.c new file mode 100644 index 0000000..9ea5d49 --- /dev/null +++ b/src/tests/vm/mmap-over-data.c @@ -0,0 +1,21 @@ +/* Verifies that mapping over the data segment is disallowed. */ + +#include <stdint.h> +#include <round.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +static char x; + +void +test_main (void) +{ + uintptr_t x_page = ROUND_DOWN ((uintptr_t) &x, 4096); + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) x_page) == MAP_FAILED, + "try to mmap over data segment"); +} + diff --git a/src/tests/vm/mmap-over-data.ck b/src/tests/vm/mmap-over-data.ck new file mode 100644 index 0000000..98770cc --- /dev/null +++ b/src/tests/vm/mmap-over-data.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-over-data) begin +(mmap-over-data) open "sample.txt" +(mmap-over-data) try to mmap over data segment +(mmap-over-data) end +EOF +pass; diff --git a/src/tests/vm/mmap-over-stk.c b/src/tests/vm/mmap-over-stk.c new file mode 100644 index 0000000..4e241e8 --- /dev/null +++ b/src/tests/vm/mmap-over-stk.c @@ -0,0 +1,19 @@ +/* Verifies that mapping over the stack segment is disallowed. */ + +#include <stdint.h> +#include <round.h> +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + uintptr_t handle_page = ROUND_DOWN ((uintptr_t) &handle, 4096); + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (mmap (handle, (void *) handle_page) == MAP_FAILED, + "try to mmap over stack segment"); +} + diff --git a/src/tests/vm/mmap-over-stk.ck b/src/tests/vm/mmap-over-stk.ck new file mode 100644 index 0000000..e6880cf --- /dev/null +++ b/src/tests/vm/mmap-over-stk.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-over-stk) begin +(mmap-over-stk) open "sample.txt" +(mmap-over-stk) try to mmap over stack segment +(mmap-over-stk) end +EOF +pass; diff --git a/src/tests/vm/mmap-overlap.c b/src/tests/vm/mmap-overlap.c new file mode 100644 index 0000000..668ae5f --- /dev/null +++ b/src/tests/vm/mmap-overlap.c @@ -0,0 +1,20 @@ +/* Verifies that overlapping memory mappings are disallowed. */ + +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *start = (char *) 0x10000000; + int fd[2]; + + CHECK ((fd[0] = open ("zeros")) > 1, "open \"zeros\" once"); + CHECK (mmap (fd[0], start) != MAP_FAILED, "mmap \"zeros\""); + CHECK ((fd[1] = open ("zeros")) > 1 && fd[0] != fd[1], + "open \"zeros\" again"); + CHECK (mmap (fd[1], start + 4096) == MAP_FAILED, + "try to mmap \"zeros\" again"); +} diff --git a/src/tests/vm/mmap-overlap.ck b/src/tests/vm/mmap-overlap.ck new file mode 100644 index 0000000..f13801e --- /dev/null +++ b/src/tests/vm/mmap-overlap.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-overlap) begin +(mmap-overlap) open "zeros" once +(mmap-overlap) mmap "zeros" +(mmap-overlap) open "zeros" again +(mmap-overlap) try to mmap "zeros" again +(mmap-overlap) end +EOF +pass; diff --git a/src/tests/vm/mmap-read.c b/src/tests/vm/mmap-read.c new file mode 100644 index 0000000..c0f23a1 --- /dev/null +++ b/src/tests/vm/mmap-read.c @@ -0,0 +1,32 @@ +/* Uses a memory mapping to read a file. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual = (char *) 0x10000000; + int handle; + mapid_t map; + size_t i; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\""); + + /* Check that data is correct. */ + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Verify that data is followed by zeros. */ + for (i = strlen (sample); i < 4096; i++) + if (actual[i] != 0) + fail ("byte %zu of mmap'd region has value %02hhx (should be 0)", + i, actual[i]); + + munmap (map); + close (handle); +} diff --git a/src/tests/vm/mmap-read.ck b/src/tests/vm/mmap-read.ck new file mode 100644 index 0000000..95ab790 --- /dev/null +++ b/src/tests/vm/mmap-read.ck @@ -0,0 +1,11 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-read) begin +(mmap-read) open "sample.txt" +(mmap-read) mmap "sample.txt" +(mmap-read) end +EOF +pass; diff --git a/src/tests/vm/mmap-remove.c b/src/tests/vm/mmap-remove.c new file mode 100644 index 0000000..5f7444d --- /dev/null +++ b/src/tests/vm/mmap-remove.c @@ -0,0 +1,43 @@ +/* Deletes and closes file that is mapped into memory + and verifies that it can still be read through the mapping. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual = (char *) 0x10000000; + int handle; + mapid_t map; + size_t i; + + /* Map file. */ + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, actual)) != MAP_FAILED, "mmap \"sample.txt\""); + + /* Close file and delete it. */ + close (handle); + CHECK (remove ("sample.txt"), "remove \"sample.txt\""); + CHECK (open ("sample.txt") == -1, "try to open \"sample.txt\""); + + /* Create a new file in hopes of overwriting data from the old + one, in case the file system has incorrectly freed the + file's data. */ + CHECK (create ("another", 4096 * 10), "create \"another\""); + + /* Check that mapped data is correct. */ + if (memcmp (actual, sample, strlen (sample))) + fail ("read of mmap'd file reported bad data"); + + /* Verify that data is followed by zeros. */ + for (i = strlen (sample); i < 4096; i++) + if (actual[i] != 0) + fail ("byte %zu of mmap'd region has value %02hhx (should be 0)", + i, actual[i]); + + munmap (map); +} diff --git a/src/tests/vm/mmap-remove.ck b/src/tests/vm/mmap-remove.ck new file mode 100644 index 0000000..d3cc938 --- /dev/null +++ b/src/tests/vm/mmap-remove.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-remove) begin +(mmap-remove) open "sample.txt" +(mmap-remove) mmap "sample.txt" +(mmap-remove) remove "sample.txt" +(mmap-remove) try to open "sample.txt" +(mmap-remove) create "another" +(mmap-remove) end +EOF +pass; diff --git a/src/tests/vm/mmap-shuffle.c b/src/tests/vm/mmap-shuffle.c new file mode 100644 index 0000000..29921ad --- /dev/null +++ b/src/tests/vm/mmap-shuffle.c @@ -0,0 +1,38 @@ +/* Creates a 128 kB file and repeatedly shuffles data in it + through a memory mapping. */ + +#include <stdio.h> +#include <string.h> +#include <syscall.h> +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define SIZE (128 * 1024) + +static char *buf = (char *) 0x10000000; + +void +test_main (void) +{ + size_t i; + int handle; + + /* Create file, mmap. */ + CHECK (create ("buffer", SIZE), "create \"buffer\""); + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + CHECK (mmap (handle, buf) != MAP_FAILED, "mmap \"buffer\""); + + /* Initialize. */ + for (i = 0; i < SIZE; i++) + buf[i] = i * 257; + msg ("init: cksum=%lu", cksum (buf, SIZE)); + + /* Shuffle repeatedly. */ + for (i = 0; i < 10; i++) + { + shuffle (buf, SIZE, 1); + msg ("shuffle %zu: cksum=%lu", i, cksum (buf, SIZE)); + } +} diff --git a/src/tests/vm/mmap-shuffle.ck b/src/tests/vm/mmap-shuffle.ck new file mode 100644 index 0000000..c158301 --- /dev/null +++ b/src/tests/vm/mmap-shuffle.ck @@ -0,0 +1,47 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::cksum; +use tests::lib; + +my ($init, @shuffle); +if (1) { + # Use precalculated values. + $init = 3115322833; + @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467, + 3022003332, 3614934266, 2704001777, 735775156, 1864109763); +} else { + # Recalculate values. + my ($buf) = ""; + for my $i (0...128 * 1024 - 1) { + $buf .= chr (($i * 257) & 0xff); + } + $init = cksum ($buf); + + random_init (0); + for my $i (1...10) { + $buf = shuffle ($buf, length ($buf), 1); + push (@shuffle, cksum ($buf)); + } +} + +check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]); +(mmap-shuffle) begin +(mmap-shuffle) create "buffer" +(mmap-shuffle) open "buffer" +(mmap-shuffle) mmap "buffer" +(mmap-shuffle) init: cksum=$init +(mmap-shuffle) shuffle 0: cksum=$shuffle[0] +(mmap-shuffle) shuffle 1: cksum=$shuffle[1] +(mmap-shuffle) shuffle 2: cksum=$shuffle[2] +(mmap-shuffle) shuffle 3: cksum=$shuffle[3] +(mmap-shuffle) shuffle 4: cksum=$shuffle[4] +(mmap-shuffle) shuffle 5: cksum=$shuffle[5] +(mmap-shuffle) shuffle 6: cksum=$shuffle[6] +(mmap-shuffle) shuffle 7: cksum=$shuffle[7] +(mmap-shuffle) shuffle 8: cksum=$shuffle[8] +(mmap-shuffle) shuffle 9: cksum=$shuffle[9] +(mmap-shuffle) end +EOF +pass; diff --git a/src/tests/vm/mmap-twice.c b/src/tests/vm/mmap-twice.c new file mode 100644 index 0000000..d277a37 --- /dev/null +++ b/src/tests/vm/mmap-twice.c @@ -0,0 +1,28 @@ +/* Maps the same file into memory twice and verifies that the + same data is readable in both. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *actual[2] = {(char *) 0x10000000, (char *) 0x20000000}; + size_t i; + int handle[2]; + + for (i = 0; i < 2; i++) + { + CHECK ((handle[i] = open ("sample.txt")) > 1, + "open \"sample.txt\" #%zu", i); + CHECK (mmap (handle[i], actual[i]) != MAP_FAILED, + "mmap \"sample.txt\" #%zu at %p", i, (void *) actual[i]); + } + + for (i = 0; i < 2; i++) + CHECK (!memcmp (actual[i], sample, strlen (sample)), + "compare mmap'd file %zu against data", i); +} diff --git a/src/tests/vm/mmap-twice.ck b/src/tests/vm/mmap-twice.ck new file mode 100644 index 0000000..05e9724 --- /dev/null +++ b/src/tests/vm/mmap-twice.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-twice) begin +(mmap-twice) open "sample.txt" #0 +(mmap-twice) mmap "sample.txt" #0 at 0x10000000 +(mmap-twice) open "sample.txt" #1 +(mmap-twice) mmap "sample.txt" #1 at 0x20000000 +(mmap-twice) compare mmap'd file 0 against data +(mmap-twice) compare mmap'd file 1 against data +(mmap-twice) end +EOF +pass; diff --git a/src/tests/vm/mmap-unmap.c b/src/tests/vm/mmap-unmap.c new file mode 100644 index 0000000..d35a79e --- /dev/null +++ b/src/tests/vm/mmap-unmap.c @@ -0,0 +1,23 @@ +/* Maps and unmaps a file and verifies that the mapped region is + inaccessible afterward. */ + +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + mapid_t map; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\""); + + munmap (map); + + fail ("unmapped memory is readable (%d)", *(int *) ACTUAL); +} diff --git a/src/tests/vm/mmap-unmap.ck b/src/tests/vm/mmap-unmap.ck new file mode 100644 index 0000000..119658c --- /dev/null +++ b/src/tests/vm/mmap-unmap.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::vm::process_death; + +check_process_death ('mmap-unmap'); diff --git a/src/tests/vm/mmap-write.c b/src/tests/vm/mmap-write.c new file mode 100644 index 0000000..46e8043 --- /dev/null +++ b/src/tests/vm/mmap-write.c @@ -0,0 +1,32 @@ +/* Writes to a file through a mapping, and unmaps the file, + then reads the data in the file back using the read system + call to verify. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +#define ACTUAL ((void *) 0x10000000) + +void +test_main (void) +{ + int handle; + mapid_t map; + char buf[1024]; + + /* Write file via mmap. */ + CHECK (create ("sample.txt", strlen (sample)), "create \"sample.txt\""); + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK ((map = mmap (handle, ACTUAL)) != MAP_FAILED, "mmap \"sample.txt\""); + memcpy (ACTUAL, sample, strlen (sample)); + munmap (map); + + /* Read back via read(). */ + read (handle, buf, strlen (sample)); + CHECK (!memcmp (buf, sample, strlen (sample)), + "compare read data against written data"); + close (handle); +} diff --git a/src/tests/vm/mmap-write.ck b/src/tests/vm/mmap-write.ck new file mode 100644 index 0000000..d2c9cc5 --- /dev/null +++ b/src/tests/vm/mmap-write.ck @@ -0,0 +1,13 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(mmap-write) begin +(mmap-write) create "sample.txt" +(mmap-write) open "sample.txt" +(mmap-write) mmap "sample.txt" +(mmap-write) compare read data against written data +(mmap-write) end +EOF +pass; diff --git a/src/tests/vm/mmap-zero.c b/src/tests/vm/mmap-zero.c new file mode 100644 index 0000000..368b759 --- /dev/null +++ b/src/tests/vm/mmap-zero.c @@ -0,0 +1,27 @@ +/* Tries to map a zero-length file, which may or may not work but + should not terminate the process or crash. + Then dereferences the address that we tried to map, + and the process must be terminated with -1 exit code. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char *data = (char *) 0x7f000000; + int handle; + + CHECK (create ("empty", 0), "create empty file \"empty\""); + CHECK ((handle = open ("empty")) > 1, "open \"empty\""); + + /* Calling mmap() might succeed or fail. We don't care. */ + msg ("mmap \"empty\""); + mmap (handle, data); + + /* Regardless of whether the call worked, *data should cause + the process to be terminated. */ + fail ("unmapped memory is readable (%d)", *data); +} + diff --git a/src/tests/vm/mmap-zero.ck b/src/tests/vm/mmap-zero.ck new file mode 100644 index 0000000..0130fbd --- /dev/null +++ b/src/tests/vm/mmap-zero.ck @@ -0,0 +1,12 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(mmap-zero) begin +(mmap-zero) create empty file "empty" +(mmap-zero) open "empty" +(mmap-zero) mmap "empty" +mmap-zero: exit(-1) +EOF +pass; diff --git a/src/tests/vm/page-linear.c b/src/tests/vm/page-linear.c new file mode 100644 index 0000000..652a47b --- /dev/null +++ b/src/tests/vm/page-linear.c @@ -0,0 +1,44 @@ +/* Encrypts, then decrypts, 2 MB of memory and verifies that the + values are as they should be. */ + +#include <string.h> +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define SIZE (2 * 1024 * 1024) + +static char buf[SIZE]; + +void +test_main (void) +{ + struct arc4 arc4; + size_t i; + + /* Initialize to 0x5a. */ + msg ("initialize"); + memset (buf, 0x5a, sizeof buf); + + /* Check that it's all 0x5a. */ + msg ("read pass"); + for (i = 0; i < SIZE; i++) + if (buf[i] != 0x5a) + fail ("byte %zu != 0x5a", i); + + /* Encrypt zeros. */ + msg ("read/modify/write pass one"); + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf, SIZE); + + /* Decrypt back to zeros. */ + msg ("read/modify/write pass two"); + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf, SIZE); + + /* Check that it's all 0x5a. */ + msg ("read pass"); + for (i = 0; i < SIZE; i++) + if (buf[i] != 0x5a) + fail ("byte %zu != 0x5a", i); +} diff --git a/src/tests/vm/page-linear.ck b/src/tests/vm/page-linear.ck new file mode 100644 index 0000000..dcbc884 --- /dev/null +++ b/src/tests/vm/page-linear.ck @@ -0,0 +1,14 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-linear) begin +(page-linear) initialize +(page-linear) read pass +(page-linear) read/modify/write pass one +(page-linear) read/modify/write pass two +(page-linear) read pass +(page-linear) end +EOF +pass; diff --git a/src/tests/vm/page-merge-mm.c b/src/tests/vm/page-merge-mm.c new file mode 100644 index 0000000..908c71c --- /dev/null +++ b/src/tests/vm/page-merge-mm.c @@ -0,0 +1,8 @@ +#include "tests/main.h" +#include "tests/vm/parallel-merge.h" + +void +test_main (void) +{ + parallel_merge ("child-qsort-mm", 80); +} diff --git a/src/tests/vm/page-merge-mm.ck b/src/tests/vm/page-merge-mm.ck new file mode 100644 index 0000000..74fa980 --- /dev/null +++ b/src/tests/vm/page-merge-mm.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-mm) begin +(page-merge-mm) init +(page-merge-mm) sort chunk 0 +(page-merge-mm) sort chunk 1 +(page-merge-mm) sort chunk 2 +(page-merge-mm) sort chunk 3 +(page-merge-mm) sort chunk 4 +(page-merge-mm) sort chunk 5 +(page-merge-mm) sort chunk 6 +(page-merge-mm) sort chunk 7 +(page-merge-mm) wait for child 0 +(page-merge-mm) wait for child 1 +(page-merge-mm) wait for child 2 +(page-merge-mm) wait for child 3 +(page-merge-mm) wait for child 4 +(page-merge-mm) wait for child 5 +(page-merge-mm) wait for child 6 +(page-merge-mm) wait for child 7 +(page-merge-mm) merge +(page-merge-mm) verify +(page-merge-mm) success, buf_idx=1,048,576 +(page-merge-mm) end +EOF +pass; diff --git a/src/tests/vm/page-merge-par.c b/src/tests/vm/page-merge-par.c new file mode 100644 index 0000000..e7e1609 --- /dev/null +++ b/src/tests/vm/page-merge-par.c @@ -0,0 +1,8 @@ +#include "tests/main.h" +#include "tests/vm/parallel-merge.h" + +void +test_main (void) +{ + parallel_merge ("child-sort", 123); +} diff --git a/src/tests/vm/page-merge-par.ck b/src/tests/vm/page-merge-par.ck new file mode 100644 index 0000000..31f8aa7 --- /dev/null +++ b/src/tests/vm/page-merge-par.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-par) begin +(page-merge-par) init +(page-merge-par) sort chunk 0 +(page-merge-par) sort chunk 1 +(page-merge-par) sort chunk 2 +(page-merge-par) sort chunk 3 +(page-merge-par) sort chunk 4 +(page-merge-par) sort chunk 5 +(page-merge-par) sort chunk 6 +(page-merge-par) sort chunk 7 +(page-merge-par) wait for child 0 +(page-merge-par) wait for child 1 +(page-merge-par) wait for child 2 +(page-merge-par) wait for child 3 +(page-merge-par) wait for child 4 +(page-merge-par) wait for child 5 +(page-merge-par) wait for child 6 +(page-merge-par) wait for child 7 +(page-merge-par) merge +(page-merge-par) verify +(page-merge-par) success, buf_idx=1,048,576 +(page-merge-par) end +EOF +pass; diff --git a/src/tests/vm/page-merge-seq.c b/src/tests/vm/page-merge-seq.c new file mode 100644 index 0000000..12e3880 --- /dev/null +++ b/src/tests/vm/page-merge-seq.c @@ -0,0 +1,137 @@ +/* Generates about 1 MB of random data that is then divided into + 16 chunks. A separate subprocess sorts each chunk in + sequence. Then we merge the chunks and verify that the result + is what it should be. */ + +#include <syscall.h> +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +/* This is the max file size for an older version of the Pintos + file system that had 126 direct blocks each pointing to a + single disk sector. We could raise it now. */ +#define CHUNK_SIZE (126 * 512) +#define CHUNK_CNT 16 /* Number of chunks. */ +#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */ + +unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE]; +size_t histogram[256]; + +/* Initialize buf1 with random data, + then count the number of instances of each value within it. */ +static void +init (void) +{ + struct arc4 arc4; + size_t i; + + msg ("init"); + + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf1, sizeof buf1); + for (i = 0; i < sizeof buf1; i++) + histogram[buf1[i]]++; +} + +/* Sort each chunk of buf1 using a subprocess. */ +static void +sort_chunks (void) +{ + size_t i; + + create ("buffer", CHUNK_SIZE); + for (i = 0; i < CHUNK_CNT; i++) + { + pid_t child; + int handle; + + msg ("sort chunk %zu", i); + + /* Write this chunk to a file. */ + quiet = true; + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + + /* Sort with subprocess. */ + CHECK ((child = exec ("child-sort buffer")) != -1, + "exec \"child-sort buffer\""); + CHECK (wait (child) == 123, "wait for child-sort"); + + /* Read chunk back from file. */ + CHECK ((handle = open ("buffer")) > 1, "open \"buffer\""); + read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + + quiet = false; + } +} + +/* Merge the sorted chunks in buf1 into a fully sorted buf2. */ +static void +merge (void) +{ + unsigned char *mp[CHUNK_CNT]; + size_t mp_left; + unsigned char *op; + size_t i; + + msg ("merge"); + + /* Initialize merge pointers. */ + mp_left = CHUNK_CNT; + for (i = 0; i < CHUNK_CNT; i++) + mp[i] = buf1 + CHUNK_SIZE * i; + + /* Merge. */ + op = buf2; + while (mp_left > 0) + { + /* Find smallest value. */ + size_t min = 0; + for (i = 1; i < mp_left; i++) + if (*mp[i] < *mp[min]) + min = i; + + /* Append value to buf2. */ + *op++ = *mp[min]; + + /* Advance merge pointer. + Delete this chunk from the set if it's emptied. */ + if ((++mp[min] - buf1) % CHUNK_SIZE == 0) + mp[min] = mp[--mp_left]; + } +} + +static void +verify (void) +{ + size_t buf_idx; + size_t hist_idx; + + msg ("verify"); + + buf_idx = 0; + for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram; + hist_idx++) + { + while (histogram[hist_idx]-- > 0) + { + if (buf2[buf_idx] != hist_idx) + fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx); + buf_idx++; + } + } + + msg ("success, buf_idx=%'zu", buf_idx); +} + +void +test_main (void) +{ + init (); + sort_chunks (); + merge (); + verify (); +} diff --git a/src/tests/vm/page-merge-seq.ck b/src/tests/vm/page-merge-seq.ck new file mode 100644 index 0000000..d78f69d --- /dev/null +++ b/src/tests/vm/page-merge-seq.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-seq) begin +(page-merge-seq) init +(page-merge-seq) sort chunk 0 +(page-merge-seq) sort chunk 1 +(page-merge-seq) sort chunk 2 +(page-merge-seq) sort chunk 3 +(page-merge-seq) sort chunk 4 +(page-merge-seq) sort chunk 5 +(page-merge-seq) sort chunk 6 +(page-merge-seq) sort chunk 7 +(page-merge-seq) sort chunk 8 +(page-merge-seq) sort chunk 9 +(page-merge-seq) sort chunk 10 +(page-merge-seq) sort chunk 11 +(page-merge-seq) sort chunk 12 +(page-merge-seq) sort chunk 13 +(page-merge-seq) sort chunk 14 +(page-merge-seq) sort chunk 15 +(page-merge-seq) merge +(page-merge-seq) verify +(page-merge-seq) success, buf_idx=1,032,192 +(page-merge-seq) end +EOF +pass; diff --git a/src/tests/vm/page-merge-stk.c b/src/tests/vm/page-merge-stk.c new file mode 100644 index 0000000..5eb1069 --- /dev/null +++ b/src/tests/vm/page-merge-stk.c @@ -0,0 +1,8 @@ +#include "tests/main.h" +#include "tests/vm/parallel-merge.h" + +void +test_main (void) +{ + parallel_merge ("child-qsort", 72); +} diff --git a/src/tests/vm/page-merge-stk.ck b/src/tests/vm/page-merge-stk.ck new file mode 100644 index 0000000..c5bc1ae --- /dev/null +++ b/src/tests/vm/page-merge-stk.ck @@ -0,0 +1,29 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-merge-stk) begin +(page-merge-stk) init +(page-merge-stk) sort chunk 0 +(page-merge-stk) sort chunk 1 +(page-merge-stk) sort chunk 2 +(page-merge-stk) sort chunk 3 +(page-merge-stk) sort chunk 4 +(page-merge-stk) sort chunk 5 +(page-merge-stk) sort chunk 6 +(page-merge-stk) sort chunk 7 +(page-merge-stk) wait for child 0 +(page-merge-stk) wait for child 1 +(page-merge-stk) wait for child 2 +(page-merge-stk) wait for child 3 +(page-merge-stk) wait for child 4 +(page-merge-stk) wait for child 5 +(page-merge-stk) wait for child 6 +(page-merge-stk) wait for child 7 +(page-merge-stk) merge +(page-merge-stk) verify +(page-merge-stk) success, buf_idx=1,048,576 +(page-merge-stk) end +EOF +pass; diff --git a/src/tests/vm/page-parallel.c b/src/tests/vm/page-parallel.c new file mode 100644 index 0000000..9d619e0 --- /dev/null +++ b/src/tests/vm/page-parallel.c @@ -0,0 +1,21 @@ +/* Runs 4 child-linear processes at once. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +#define CHILD_CNT 4 + +void +test_main (void) +{ + pid_t children[CHILD_CNT]; + int i; + + for (i = 0; i < CHILD_CNT; i++) + CHECK ((children[i] = exec ("child-linear")) != -1, + "exec \"child-linear\""); + + for (i = 0; i < CHILD_CNT; i++) + CHECK (wait (children[i]) == 0x42, "wait for child %d", i); +} diff --git a/src/tests/vm/page-parallel.ck b/src/tests/vm/page-parallel.ck new file mode 100644 index 0000000..90c14ef --- /dev/null +++ b/src/tests/vm/page-parallel.ck @@ -0,0 +1,17 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(page-parallel) begin +(page-parallel) exec "child-linear" +(page-parallel) exec "child-linear" +(page-parallel) exec "child-linear" +(page-parallel) exec "child-linear" +(page-parallel) wait for child 0 +(page-parallel) wait for child 1 +(page-parallel) wait for child 2 +(page-parallel) wait for child 3 +(page-parallel) end +EOF +pass; diff --git a/src/tests/vm/page-shuffle.c b/src/tests/vm/page-shuffle.c new file mode 100644 index 0000000..095a9da --- /dev/null +++ b/src/tests/vm/page-shuffle.c @@ -0,0 +1,30 @@ +/* Shuffles a 128 kB data buffer 10 times, printing the checksum + after each time. */ + +#include <stdbool.h> +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define SIZE (128 * 1024) + +static char buf[SIZE]; + +void +test_main (void) +{ + size_t i; + + /* Initialize. */ + for (i = 0; i < sizeof buf; i++) + buf[i] = i * 257; + msg ("init: cksum=%lu", cksum (buf, sizeof buf)); + + /* Shuffle repeatedly. */ + for (i = 0; i < 10; i++) + { + shuffle (buf, sizeof buf, 1); + msg ("shuffle %zu: cksum=%lu", i, cksum (buf, sizeof buf)); + } +} diff --git a/src/tests/vm/page-shuffle.ck b/src/tests/vm/page-shuffle.ck new file mode 100644 index 0000000..6447d38 --- /dev/null +++ b/src/tests/vm/page-shuffle.ck @@ -0,0 +1,44 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::cksum; +use tests::lib; + +my ($init, @shuffle); +if (1) { + # Use precalculated values. + $init = 3115322833; + @shuffle = (1691062564, 1973575879, 1647619479, 96566261, 3885786467, + 3022003332, 3614934266, 2704001777, 735775156, 1864109763); +} else { + # Recalculate values. + my ($buf) = ""; + for my $i (0...128 * 1024 - 1) { + $buf .= chr (($i * 257) & 0xff); + } + $init = cksum ($buf); + + random_init (0); + for my $i (1...10) { + $buf = shuffle ($buf, length ($buf), 1); + push (@shuffle, cksum ($buf)); + } +} + +check_expected (IGNORE_EXIT_CODES => 1, [<<EOF]); +(page-shuffle) begin +(page-shuffle) init: cksum=$init +(page-shuffle) shuffle 0: cksum=$shuffle[0] +(page-shuffle) shuffle 1: cksum=$shuffle[1] +(page-shuffle) shuffle 2: cksum=$shuffle[2] +(page-shuffle) shuffle 3: cksum=$shuffle[3] +(page-shuffle) shuffle 4: cksum=$shuffle[4] +(page-shuffle) shuffle 5: cksum=$shuffle[5] +(page-shuffle) shuffle 6: cksum=$shuffle[6] +(page-shuffle) shuffle 7: cksum=$shuffle[7] +(page-shuffle) shuffle 8: cksum=$shuffle[8] +(page-shuffle) shuffle 9: cksum=$shuffle[9] +(page-shuffle) end +EOF +pass; diff --git a/src/tests/vm/parallel-merge.c b/src/tests/vm/parallel-merge.c new file mode 100644 index 0000000..cc09bb1 --- /dev/null +++ b/src/tests/vm/parallel-merge.c @@ -0,0 +1,149 @@ +/* Generates about 1 MB of random data that is then divided into + 16 chunks. A separate subprocess sorts each chunk; the + subprocesses run in parallel. Then we merge the chunks and + verify that the result is what it should be. */ + +#include "tests/vm/parallel-merge.h" +#include <stdio.h> +#include <syscall.h> +#include "tests/arc4.h" +#include "tests/lib.h" +#include "tests/main.h" + +#define CHUNK_SIZE (128 * 1024) +#define CHUNK_CNT 8 /* Number of chunks. */ +#define DATA_SIZE (CHUNK_CNT * CHUNK_SIZE) /* Buffer size. */ + +unsigned char buf1[DATA_SIZE], buf2[DATA_SIZE]; +size_t histogram[256]; + +/* Initialize buf1 with random data, + then count the number of instances of each value within it. */ +static void +init (void) +{ + struct arc4 arc4; + size_t i; + + msg ("init"); + + arc4_init (&arc4, "foobar", 6); + arc4_crypt (&arc4, buf1, sizeof buf1); + for (i = 0; i < sizeof buf1; i++) + histogram[buf1[i]]++; +} + +/* Sort each chunk of buf1 using SUBPROCESS, + which is expected to return EXIT_STATUS. */ +static void +sort_chunks (const char *subprocess, int exit_status) +{ + pid_t children[CHUNK_CNT]; + size_t i; + + for (i = 0; i < CHUNK_CNT; i++) + { + char fn[128]; + char cmd[128]; + int handle; + + msg ("sort chunk %zu", i); + + /* Write this chunk to a file. */ + snprintf (fn, sizeof fn, "buf%zu", i); + create (fn, CHUNK_SIZE); + quiet = true; + CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn); + write (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + + /* Sort with subprocess. */ + snprintf (cmd, sizeof cmd, "%s %s", subprocess, fn); + CHECK ((children[i] = exec (cmd)) != -1, "exec \"%s\"", cmd); + quiet = false; + } + + for (i = 0; i < CHUNK_CNT; i++) + { + char fn[128]; + int handle; + + CHECK (wait (children[i]) == exit_status, "wait for child %zu", i); + + /* Read chunk back from file. */ + quiet = true; + snprintf (fn, sizeof fn, "buf%zu", i); + CHECK ((handle = open (fn)) > 1, "open \"%s\"", fn); + read (handle, buf1 + CHUNK_SIZE * i, CHUNK_SIZE); + close (handle); + quiet = false; + } +} + +/* Merge the sorted chunks in buf1 into a fully sorted buf2. */ +static void +merge (void) +{ + unsigned char *mp[CHUNK_CNT]; + size_t mp_left; + unsigned char *op; + size_t i; + + msg ("merge"); + + /* Initialize merge pointers. */ + mp_left = CHUNK_CNT; + for (i = 0; i < CHUNK_CNT; i++) + mp[i] = buf1 + CHUNK_SIZE * i; + + /* Merge. */ + op = buf2; + while (mp_left > 0) + { + /* Find smallest value. */ + size_t min = 0; + for (i = 1; i < mp_left; i++) + if (*mp[i] < *mp[min]) + min = i; + + /* Append value to buf2. */ + *op++ = *mp[min]; + + /* Advance merge pointer. + Delete this chunk from the set if it's emptied. */ + if ((++mp[min] - buf1) % CHUNK_SIZE == 0) + mp[min] = mp[--mp_left]; + } +} + +static void +verify (void) +{ + size_t buf_idx; + size_t hist_idx; + + msg ("verify"); + + buf_idx = 0; + for (hist_idx = 0; hist_idx < sizeof histogram / sizeof *histogram; + hist_idx++) + { + while (histogram[hist_idx]-- > 0) + { + if (buf2[buf_idx] != hist_idx) + fail ("bad value %d in byte %zu", buf2[buf_idx], buf_idx); + buf_idx++; + } + } + + msg ("success, buf_idx=%'zu", buf_idx); +} + +void +parallel_merge (const char *child_name, int exit_status) +{ + init (); + sort_chunks (child_name, exit_status); + merge (); + verify (); +} diff --git a/src/tests/vm/parallel-merge.h b/src/tests/vm/parallel-merge.h new file mode 100644 index 0000000..a6b6431 --- /dev/null +++ b/src/tests/vm/parallel-merge.h @@ -0,0 +1,6 @@ +#ifndef TESTS_VM_PARALLEL_MERGE +#define TESTS_VM_PARALLEL_MERGE 1 + +void parallel_merge (const char *child_name, int exit_status); + +#endif /* tests/vm/parallel-merge.h */ diff --git a/src/tests/vm/process_death.pm b/src/tests/vm/process_death.pm new file mode 100644 index 0000000..52039a1 --- /dev/null +++ b/src/tests/vm/process_death.pm @@ -0,0 +1,22 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; + +sub check_process_death { + my ($proc_name) = @_; + our ($test); + my (@output) = read_text_file ("$test.output"); + + common_checks ("run", @output); + @output = get_core_output ("run", @output); + fail "First line of output is not `($proc_name) begin' message.\n" + if $output[0] ne "($proc_name) begin"; + fail "Output missing '$proc_name: exit(-1)' message.\n" + if !grep ("$proc_name: exit(-1)" eq $_, @output); + fail "Output contains '($proc_name) end' message.\n" + if grep (/\($proc_name\) end/, @output); + pass; +} + +1; diff --git a/src/tests/vm/pt-bad-addr.c b/src/tests/vm/pt-bad-addr.c new file mode 100644 index 0000000..3ca4084 --- /dev/null +++ b/src/tests/vm/pt-bad-addr.c @@ -0,0 +1,11 @@ +/* Accesses a bad address. + The process must be terminated with -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + fail ("bad addr read as %d", *(int *) 0x04000000); +} diff --git a/src/tests/vm/pt-bad-addr.ck b/src/tests/vm/pt-bad-addr.ck new file mode 100644 index 0000000..09ea039 --- /dev/null +++ b/src/tests/vm/pt-bad-addr.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::vm::process_death; + +check_process_death ('pt-bad-addr'); diff --git a/src/tests/vm/pt-bad-read.c b/src/tests/vm/pt-bad-read.c new file mode 100644 index 0000000..ee791ff --- /dev/null +++ b/src/tests/vm/pt-bad-read.c @@ -0,0 +1,16 @@ +/* Reads from a file into a bad address. + The process must be terminated with -1 exit code. */ + +#include <syscall.h> +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + read (handle, (char *) &handle - 4096, 1); + fail ("survived reading data into bad address"); +} diff --git a/src/tests/vm/pt-bad-read.ck b/src/tests/vm/pt-bad-read.ck new file mode 100644 index 0000000..1f96bb4 --- /dev/null +++ b/src/tests/vm/pt-bad-read.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(pt-bad-read) begin +(pt-bad-read) open "sample.txt" +pt-bad-read: exit(-1) +EOF +pass; diff --git a/src/tests/vm/pt-big-stk-obj.c b/src/tests/vm/pt-big-stk-obj.c new file mode 100644 index 0000000..6b630ec --- /dev/null +++ b/src/tests/vm/pt-big-stk-obj.c @@ -0,0 +1,20 @@ +/* Allocates and writes to a 64 kB object on the stack. + This must succeed. */ + +#include <string.h> +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char stk_obj[65536]; + struct arc4 arc4; + + arc4_init (&arc4, "foobar", 6); + memset (stk_obj, 0, sizeof stk_obj); + arc4_crypt (&arc4, stk_obj, sizeof stk_obj); + msg ("cksum: %lu", cksum (stk_obj, sizeof stk_obj)); +} diff --git a/src/tests/vm/pt-big-stk-obj.ck b/src/tests/vm/pt-big-stk-obj.ck new file mode 100644 index 0000000..eb5853a --- /dev/null +++ b/src/tests/vm/pt-big-stk-obj.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-big-stk-obj) begin +(pt-big-stk-obj) cksum: 3256410166 +(pt-big-stk-obj) end +EOF +pass; diff --git a/src/tests/vm/pt-grow-bad.c b/src/tests/vm/pt-grow-bad.c new file mode 100644 index 0000000..d4beba2 --- /dev/null +++ b/src/tests/vm/pt-grow-bad.c @@ -0,0 +1,14 @@ +/* Read from an address 4,096 bytes below the stack pointer. + The process must be terminated with -1 exit code. */ + +#include <string.h> +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile ("movl -4096(%esp), %eax"); +} diff --git a/src/tests/vm/pt-grow-bad.ck b/src/tests/vm/pt-grow-bad.ck new file mode 100644 index 0000000..4c0ab8a --- /dev/null +++ b/src/tests/vm/pt-grow-bad.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_USER_FAULTS => 1, [<<'EOF']); +(pt-grow-bad) begin +pt-grow-bad: exit(-1) +EOF +pass; diff --git a/src/tests/vm/pt-grow-pusha.c b/src/tests/vm/pt-grow-pusha.c new file mode 100644 index 0000000..f9762a5 --- /dev/null +++ b/src/tests/vm/pt-grow-pusha.c @@ -0,0 +1,20 @@ +/* Expand the stack by 32 bytes all at once using the PUSHA + instruction. + This must succeed. */ + +#include <string.h> +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + asm volatile + ("movl %%esp, %%eax;" /* Save a copy of the stack pointer. */ + "andl $0xfffff000, %%esp;" /* Move stack pointer to bottom of page. */ + "pushal;" /* Push 32 bytes on stack at once. */ + "movl %%eax, %%esp" /* Restore copied stack pointer. */ + : : : "eax"); /* Tell GCC we destroyed eax. */ +} diff --git a/src/tests/vm/pt-grow-pusha.ck b/src/tests/vm/pt-grow-pusha.ck new file mode 100644 index 0000000..5000966 --- /dev/null +++ b/src/tests/vm/pt-grow-pusha.ck @@ -0,0 +1,9 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-grow-pusha) begin +(pt-grow-pusha) end +EOF +pass; diff --git a/src/tests/vm/pt-grow-stack.c b/src/tests/vm/pt-grow-stack.c new file mode 100644 index 0000000..0997a00 --- /dev/null +++ b/src/tests/vm/pt-grow-stack.c @@ -0,0 +1,20 @@ +/* Demonstrate that the stack can grow. + This must succeed. */ + +#include <string.h> +#include "tests/arc4.h" +#include "tests/cksum.h" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + char stack_obj[4096]; + struct arc4 arc4; + + arc4_init (&arc4, "foobar", 6); + memset (stack_obj, 0, sizeof stack_obj); + arc4_crypt (&arc4, stack_obj, sizeof stack_obj); + msg ("cksum: %lu", cksum (stack_obj, sizeof stack_obj)); +} diff --git a/src/tests/vm/pt-grow-stack.ck b/src/tests/vm/pt-grow-stack.ck new file mode 100644 index 0000000..1e669db --- /dev/null +++ b/src/tests/vm/pt-grow-stack.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-grow-stack) begin +(pt-grow-stack) cksum: 3424492700 +(pt-grow-stack) end +EOF +pass; diff --git a/src/tests/vm/pt-grow-stk-sc.c b/src/tests/vm/pt-grow-stk-sc.c new file mode 100644 index 0000000..3efbb5f --- /dev/null +++ b/src/tests/vm/pt-grow-stk-sc.c @@ -0,0 +1,32 @@ +/* This test checks that the stack is properly extended even if + the first access to a stack location occurs inside a system + call. + + From Godmar Back. */ + +#include <string.h> +#include <syscall.h> +#include "tests/vm/sample.inc" +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + int slen = strlen (sample); + char buf2[65536]; + + /* Write file via write(). */ + CHECK (create ("sample.txt", slen), "create \"sample.txt\""); + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + CHECK (write (handle, sample, slen) == slen, "write \"sample.txt\""); + close (handle); + + /* Read back via read(). */ + CHECK ((handle = open ("sample.txt")) > 1, "2nd open \"sample.txt\""); + CHECK (read (handle, buf2 + 32768, slen) == slen, "read \"sample.txt\""); + + CHECK (!memcmp (sample, buf2 + 32768, slen), "compare written data against read data"); + close (handle); +} diff --git a/src/tests/vm/pt-grow-stk-sc.ck b/src/tests/vm/pt-grow-stk-sc.ck new file mode 100644 index 0000000..23d3b02 --- /dev/null +++ b/src/tests/vm/pt-grow-stk-sc.ck @@ -0,0 +1,15 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected (IGNORE_EXIT_CODES => 1, [<<'EOF']); +(pt-grow-stk-sc) begin +(pt-grow-stk-sc) create "sample.txt" +(pt-grow-stk-sc) open "sample.txt" +(pt-grow-stk-sc) write "sample.txt" +(pt-grow-stk-sc) 2nd open "sample.txt" +(pt-grow-stk-sc) read "sample.txt" +(pt-grow-stk-sc) compare written data against read data +(pt-grow-stk-sc) end +EOF +pass; diff --git a/src/tests/vm/pt-write-code-2.c b/src/tests/vm/pt-write-code-2.c new file mode 100644 index 0000000..83bcc2c --- /dev/null +++ b/src/tests/vm/pt-write-code-2.c @@ -0,0 +1,15 @@ +/* Try to write to the code segment using a system call. + The process must be terminated with -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + int handle; + + CHECK ((handle = open ("sample.txt")) > 1, "open \"sample.txt\""); + read (handle, (void *) test_main, 1); + fail ("survived reading data into code segment"); +} diff --git a/src/tests/vm/pt-write-code.c b/src/tests/vm/pt-write-code.c new file mode 100644 index 0000000..5072cec --- /dev/null +++ b/src/tests/vm/pt-write-code.c @@ -0,0 +1,12 @@ +/* Try to write to the code segment. + The process must be terminated with -1 exit code. */ + +#include "tests/lib.h" +#include "tests/main.h" + +void +test_main (void) +{ + *(int *) test_main = 0; + fail ("writing the code segment succeeded"); +} diff --git a/src/tests/vm/pt-write-code.ck b/src/tests/vm/pt-write-code.ck new file mode 100644 index 0000000..65610fb --- /dev/null +++ b/src/tests/vm/pt-write-code.ck @@ -0,0 +1,7 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +use tests::vm::process_death; + +check_process_death ('pt-write-code'); diff --git a/src/tests/vm/pt-write-code2.ck b/src/tests/vm/pt-write-code2.ck new file mode 100644 index 0000000..69ffc77 --- /dev/null +++ b/src/tests/vm/pt-write-code2.ck @@ -0,0 +1,10 @@ +# -*- perl -*- +use strict; +use warnings; +use tests::tests; +check_expected ([<<'EOF']); +(pt-write-code2) begin +(pt-write-code2) open "sample.txt" +pt-write-code2: exit(-1) +EOF +pass; diff --git a/src/tests/vm/qsort.c b/src/tests/vm/qsort.c new file mode 100644 index 0000000..922572c --- /dev/null +++ b/src/tests/vm/qsort.c @@ -0,0 +1,136 @@ +#include "tests/vm/qsort.h" +#include <stdbool.h> +#include <debug.h> +#include <random.h> + +/* Picks a pivot for the quicksort from the SIZE bytes in BUF. */ +static unsigned char +pick_pivot (unsigned char *buf, size_t size) +{ + ASSERT (size >= 1); + return buf[random_ulong () % size]; +} + +/* Checks whether the SIZE bytes in ARRAY are divided into an + initial LEFT_SIZE elements all less than PIVOT followed by + SIZE - LEFT_SIZE elements all greater than or equal to + PIVOT. */ +static bool +is_partitioned (const unsigned char *array, size_t size, + unsigned char pivot, size_t left_size) +{ + size_t i; + + for (i = 0; i < left_size; i++) + if (array[i] >= pivot) + return false; + + for (; i < size; i++) + if (array[i] < pivot) + return false; + + return true; +} + +/* Swaps the bytes at *A and *B. */ +static void +swap (unsigned char *a, unsigned char *b) +{ + unsigned char t = *a; + *a = *b; + *b = t; +} + +/* Partitions ARRAY in-place in an initial run of bytes all less + than PIVOT, followed by a run of bytes all greater than or + equal to PIVOT. Returns the length of the initial run. */ +static size_t +partition (unsigned char *array, size_t size, int pivot) +{ + size_t left_size = size; + unsigned char *first = array; + unsigned char *last = first + left_size; + + for (;;) + { + /* Move FIRST forward to point to first element greater than + PIVOT. */ + for (;;) + { + if (first == last) + { + ASSERT (is_partitioned (array, size, pivot, left_size)); + return left_size; + } + else if (*first >= pivot) + break; + + first++; + } + left_size--; + + /* Move LAST backward to point to last element no bigger + than PIVOT. */ + for (;;) + { + last--; + + if (first == last) + { + ASSERT (is_partitioned (array, size, pivot, left_size)); + return left_size; + } + else if (*last < pivot) + break; + else + left_size--; + } + + /* By swapping FIRST and LAST we extend the starting and + ending sequences that pass and fail, respectively, + PREDICATE. */ + swap (first, last); + first++; + } +} + +/* Returns true if the SIZE bytes in BUF are in nondecreasing + order, false otherwise. */ +static bool +is_sorted (const unsigned char *buf, size_t size) +{ + size_t i; + + for (i = 1; i < size; i++) + if (buf[i - 1] > buf[i]) + return false; + + return true; +} + +/* Sorts the SIZE bytes in BUF into nondecreasing order, using + the quick-sort algorithm. */ +void +qsort_bytes (unsigned char *buf, size_t size) +{ + if (!is_sorted (buf, size)) + { + int pivot = pick_pivot (buf, size); + + unsigned char *left_half = buf; + size_t left_size = partition (buf, size, pivot); + unsigned char *right_half = left_half + left_size; + size_t right_size = size - left_size; + + if (left_size <= right_size) + { + qsort_bytes (left_half, left_size); + qsort_bytes (right_half, right_size); + } + else + { + qsort_bytes (right_half, right_size); + qsort_bytes (left_half, left_size); + } + } +} diff --git a/src/tests/vm/qsort.h b/src/tests/vm/qsort.h new file mode 100644 index 0000000..61b65f3 --- /dev/null +++ b/src/tests/vm/qsort.h @@ -0,0 +1,8 @@ +#ifndef TESTS_VM_QSORT_H +#define TESTS_VM_QSORT_H 1 + +#include <stddef.h> + +void qsort_bytes (unsigned char *buf, size_t size); + +#endif /* tests/vm/qsort.h */ diff --git a/src/tests/vm/sample.inc b/src/tests/vm/sample.inc new file mode 100644 index 0000000..a60a139 --- /dev/null +++ b/src/tests/vm/sample.inc @@ -0,0 +1,19 @@ +char sample[] = { + "=== ALL USERS PLEASE NOTE ========================\n" + "\n" + "CAR and CDR now return extra values.\n" + "\n" + "The function CAR now returns two values. Since it has to go to the\n" + "trouble to figure out if the object is carcdr-able anyway, we figured\n" + "you might as well get both halves at once. For example, the following\n" + "code shows how to destructure a cons (SOME-CONS) into its two slots\n" + "(THE-CAR and THE-CDR):\n" + "\n" + " (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...)\n" + "\n" + "For symmetry with CAR, CDR returns a second value which is the CAR of\n" + "the object. In a related change, the functions MAKE-ARRAY and CONS\n" + "have been fixed so they don't allocate any storage except on the\n" + "stack. This should hopefully help people who don't like using the\n" + "garbage collector because it cold boots the machine so often.\n" +}; diff --git a/src/tests/vm/sample.txt b/src/tests/vm/sample.txt new file mode 100644 index 0000000..c446830 --- /dev/null +++ b/src/tests/vm/sample.txt @@ -0,0 +1,17 @@ +=== ALL USERS PLEASE NOTE ======================== + +CAR and CDR now return extra values. + +The function CAR now returns two values. Since it has to go to the +trouble to figure out if the object is carcdr-able anyway, we figured +you might as well get both halves at once. For example, the following +code shows how to destructure a cons (SOME-CONS) into its two slots +(THE-CAR and THE-CDR): + + (MULTIPLE-VALUE-BIND (THE-CAR THE-CDR) (CAR SOME-CONS) ...) + +For symmetry with CAR, CDR returns a second value which is the CAR of +the object. In a related change, the functions MAKE-ARRAY and CONS +have been fixed so they don't allocate any storage except on the +stack. This should hopefully help people who don't like using the +garbage collector because it cold boots the machine so often. |
