use strict;
use IPC::Open3;
use Getopt::Std;
use File::Find;
use File::Path;
use File::Spec::Functions;
use Cwd;
my $stress = 'stress.pl';
my $dbrecover = undef;
sub init_repo
{
my ( $repo, $create, $no_sync, $fsfs ) = @_;
if ( $create )
{
rmtree([$repo]) if -e $repo;
my $svnadmin_cmd = "svnadmin create $repo";
$svnadmin_cmd .= " --fs-type bdb" if not $fsfs;
$svnadmin_cmd .= " --bdb-txn-nosync" if $no_sync;
system( $svnadmin_cmd) and die "$stress: $svnadmin_cmd: failed: $?\n";
open ( CONF, ">>$repo/conf/svnserve.conf")
or die "$stress: open svnserve.conf: $!\n";
print CONF "[general]\nanon-access = write\n";
close CONF or die "$stress: close svnserve.conf: $!\n";
}
$repo = getcwd . "/$repo" if not file_name_is_absolute $repo;
$dbrecover = 1 if -e "$repo/db/__db.register";
print "$stress: BDB automatic database recovery enabled\n" if $dbrecover;
return $repo;
}
sub check_out
{
my ( $url, $options ) = @_;
my $wc_dir = "wcstress.$$";
mkdir "$wc_dir", 0755 or die "$stress: mkdir wcstress.$$: $!\n";
my $svn_cmd = "svn co $url $wc_dir $options";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
return $wc_dir;
}
sub status_update
{
my ( $options, $wc_dir, $wait_for_key, $disable_status,
$resolve_conflicts ) = @_;
my $svn_cmd = "svn st -u $options $wc_dir";
if ( not $disable_status ) {
print "Status:\n";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
}
print "Press return to update/commit\n" if $wait_for_key;
read STDIN, $wait_for_key, 1 if $wait_for_key;
print "Updating:\n";
$svn_cmd = "svn up --non-interactive $options $wc_dir";
my $pid = open3(\*UPDATE_WRITE, \*UPDATE_READ, \*UPDATE_ERR_READ,
$svn_cmd);
my @conflicts = ();
while ( <UPDATE_READ> )
{
print;
s/\r*$//; # [Windows compat] Remove trailing \r's
if ( /^C (.*)$/ )
{
push(@conflicts, ($1))
}
}
my $acceptable_error = 0;
while ( <UPDATE_ERR_READ> )
{
print;
if ($dbrecover)
{
s/\r*$//; # [Windows compat] Remove trailing \r's
$acceptable_error = 1 if ( /^svn:[ ]
(
bdb:[ ]PANIC
|
DB_RUNRECOVERY
)
/x );
}
}
close UPDATE_ERR_READ or die "$stress: close UPDATE_ERR_READ: $!\n";
close UPDATE_WRITE or die "$stress: close UPDATE_WRITE: $!\n";
close UPDATE_READ or die "$stress: close UPDATE_READ: $!\n";
die "$stress: waitpid: $!\n" if $pid != waitpid $pid, 0;
die "$stress: unexpected update fail: exit status: $?\n"
unless $? == 0 or ( $? == 256 and $acceptable_error );
if ($resolve_conflicts)
{
foreach my $conflict (@conflicts)
{
$svn_cmd = "svn resolved $conflict";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
}
}
}
sub status_update_commit
{
my ( $options, $wc_dir, $wait_for_key, $disable_status,
$resolve_conflicts ) = @_;
status_update $options, $wc_dir, $wait_for_key, $disable_status, \
$resolve_conflicts;
print "Committing:\n";
my $now_time = localtime;
my $svn_cmd = "svn ci $options $wc_dir -m \"$now_time\"";
my $pid = open3(\*COMMIT_WRITE, \*COMMIT_READ, \*COMMIT_ERR_READ,
$svn_cmd);
print while ( <COMMIT_READ> );
my $acceptable_error = 0;
while ( <COMMIT_ERR_READ> )
{
print;
s/\r*$//; # [Windows compat] Remove trailing \r's
$acceptable_error = 1 if ( /^svn:[ ]
(
.*out[ ]of[ ]date
|
Conflict[ ]at
|
Baseline[ ]incorrect
|
)
/ix )
or ( $dbrecover and ( /^svn:[ ]
(
bdb:[ ]PANIC
|
DB_RUNRECOVERY
)
/x ));
}
close COMMIT_ERR_READ or die "$stress: close COMMIT_ERR_READ: $!\n";
close COMMIT_WRITE or die "$stress: close COMMIT_WRITE: $!\n";
close COMMIT_READ or die "$stress: close COMMIT_READ: $!\n";
die "$stress: waitpid: $!\n" if $pid != waitpid $pid, 0;
die "$stress: unexpected commit fail: exit status: $?\n"
if ( $? != 0 and $? != 256 ) or ( $? == 256 and $acceptable_error != 1 );
return $? == 256 ? 1 : 0;
}
{
my @get_list_of_files_helper_array;
sub GetListOfFilesHelper
{
$File::Find::prune = 1 if $File::Find::name =~ m[/.svn];
return if $File::Find::prune or -d;
push @get_list_of_files_helper_array, $File::Find::name;
}
sub GetListOfFiles
{
my ( $wc_dir ) = @_;
@get_list_of_files_helper_array = ();
find( \&GetListOfFilesHelper, $wc_dir);
return @get_list_of_files_helper_array;
}
}
sub populate
{
my ( $dir, $dir_width, $file_width, $depth, $pad, $props ) = @_;
return if not $depth--;
for my $nfile ( 1..$file_width )
{
my $filename = "$dir/foo$nfile";
open( FOO, ">$filename" ) or die "$stress: open $filename: $!\n";
for my $line ( 0..9 )
{
print FOO "A$line\n$line\n"
or die "$stress: write to $filename: $!\n";
map { print FOO $_ x 255, "\n"; } ("a", "b", "c", "d")
foreach (1..$pad);
}
print FOO "\$HeadURL: \$\n"
or die "$stress: write to $filename: $!\n" if $props;
close FOO or die "$stress: close $filename: $!\n";
my $svn_cmd = "svn add $filename";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
if ( $props )
{
$svn_cmd = "svn propset svn:eol-style native $filename";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
$svn_cmd = "svn propset svn:keywords HeadURL $filename";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
}
}
if ( $depth )
{
for my $ndir ( 1..$dir_width )
{
my $dirname = "$dir/bar$ndir";
my $svn_cmd = "svn mkdir $dirname";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
populate( "$dirname", $dir_width, $file_width, $depth, $pad,
$props );
}
}
}
sub ModFile
{
my ( $filename, $mod_number, $id ) = @_;
open( FOO, "<$filename" ) or die "$stress: open $filename: $!\n";
my @lines = map { s[(^$id.*)][$1,$mod_number]; $_ } <FOO>;
close FOO or die "$stress: close $filename: $!\n";
open( FOO, ">$filename" ) or die "$stress: open $filename: $!\n";
print FOO or die "$stress: print $filename: $!\n" foreach @lines;
close FOO or die "$stress: close $filename: $!\n";
}
sub ParseCommandLine
{
my %cmd_opts;
my $usage = "
usage: stress.pl [-cdfhprW] [-i num] [-n num] [-s secs] [-x num] [-o options]
[-D num] [-F num] [-N num] [-P num] [-R path] [-S path]
[-U url]
where
-c cause repository creation
-d don't make the status calls
-f use --fs-type fsfs during repository creation
-h show this help information (other options will be ignored)
-i the ID (valid IDs are 0 to 9, default is 0 if -c given, 1 otherwise)
-n the number of sets of changes to commit
-p add svn:eol-style and svn:keywords properties to the files
-r perform update-time conflict resolution
-s the sleep delay (-1 wait for key, 0 none)
-x the number of files to modify in each commit
-o options to pass for subversion client
-D the number of sub-directories per directory in the tree
-F the number of files per directory in the tree
-N the depth of the tree
-P the number of 10K blocks with which to pad the file
-R the path to the repository
-S the path to the file whose presence stops this script
-U the URL to the repository (file:///<-R path> by default)
-W use --bdb-txn-nosync during repository creation
";
$cmd_opts{'D'} = 2; $cmd_opts{'F'} = 2; $cmd_opts{'N'} = 2; $cmd_opts{'P'} = 0; $cmd_opts{'R'} = "repostress"; $cmd_opts{'S'} = "stop"; $cmd_opts{'U'} = "none"; $cmd_opts{'W'} = 0; $cmd_opts{'c'} = 0; $cmd_opts{'d'} = 0; $cmd_opts{'f'} = 0; $cmd_opts{'h'} = 0; $cmd_opts{'i'} = 0; $cmd_opts{'n'} = 200; $cmd_opts{'p'} = 0; $cmd_opts{'r'} = 0; $cmd_opts{'s'} = -1; $cmd_opts{'x'} = 4; $cmd_opts{'o'} = "";
getopts( 'cdfhi:n:prs:x:o:D:F:N:P:R:S:U:W', \%cmd_opts ) or die $usage;
if ( $cmd_opts{'h'} )
{
print( $usage );
exit 0;
}
$cmd_opts{'i'} = 1 - $cmd_opts{'c'} if not $cmd_opts{'i'};
die $usage if $cmd_opts{'i'} !~ /^[0-9]$/;
return %cmd_opts;
}
srand 123456789;
my %cmd_opts = ParseCommandLine();
my $repo = init_repo( $cmd_opts{'R'}, $cmd_opts{'c'}, $cmd_opts{'W'},
$cmd_opts{'f'} );
my $urlsep = ($repo =~ m/^\// ? '//' : '///');
$repo =~ s/\\/\//g;
$cmd_opts{'U'} = "file:$urlsep$repo" if $cmd_opts{'U'} eq "none";
my $wc_dir = check_out $cmd_opts{'U'}, $cmd_opts{'o'};
if ( $cmd_opts{'c'} )
{
my $svn_cmd = "svn mkdir $wc_dir/trunk";
system( $svn_cmd ) and die "$stress: $svn_cmd: failed: $?\n";
populate( "$wc_dir/trunk", $cmd_opts{'D'}, $cmd_opts{'F'}, $cmd_opts{'N'},
$cmd_opts{'P'}, $cmd_opts{'p'} );
status_update_commit $cmd_opts{'o'}, $wc_dir, 0, 1
and die "$stress: populate checkin failed\n";
}
my @wc_files = GetListOfFiles $wc_dir;
die "$stress: not enough files in repository\n"
if $
my $wait_for_key = $cmd_opts{'s'} < 0;
my $stop_file = $cmd_opts{'S'};
for my $mod_number ( 1..$cmd_opts{'n'} )
{
my @chosen;
for ( 1..$cmd_opts{'x'} )
{
my $mod_file = splice @wc_files, int rand $ ModFile $mod_file, $mod_number, $cmd_opts{'i'};
push @chosen, $mod_file;
}
push @wc_files, @chosen;
if ( $cmd_opts{'x'} > 0 ) {
1 while not -e $stop_file
and status_update_commit $cmd_opts{'o'}, $wc_dir, $wait_for_key, \
$cmd_opts{'d'}, $cmd_opts{'r'};
} else {
status_update $cmd_opts{'o'}, $wc_dir, $wait_for_key, $cmd_opts{'d'}, \
$cmd_opts{'r'};
}
print( "stop file '$stop_file' detected\n" ), last if -e $stop_file;
sleep $cmd_opts{'s'} if $cmd_opts{'s'} > 0;
}