The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

# Test that two processes can write at once, assuming we commit timely.

use strict;
BEGIN {
	$|  = 1;
	$^W = 1;
}

use t::lib::Test qw/connect_ok dbfile @CALL_FUNCS/;
use Test::More;
use Test::NoWarnings;

plan tests => 11 * @CALL_FUNCS + 1;

foreach my $call_func (@CALL_FUNCS) {

	my $dbh = connect_ok(
	    dbfile     => 'foo',
	    RaiseError => 1,
	    PrintError => 0,
	    AutoCommit => 0,
	);

	my $dbh2 = connect_ok(
	    dbfile     => 'foo',
	    RaiseError => 1,
	    PrintError => 0,
	    AutoCommit => 0,
	);

	my $dbfile = dbfile('foo');

	# NOTE: Let's make it clear what we're doing here.
	# $dbh starts locking with the first INSERT statement.
	# $dbh2 tries to INSERT, but as the database is locked,
	# it starts waiting. However, $dbh won't release the lock.
	# Eventually $dbh2 gets timed out, and spits an error, saying
	# the database is locked. So, we don't need to let $dbh2 wait
	# too much here. It should be timed out anyway.
	ok($dbh2->$call_func(300, 'busy_timeout'));

	ok($dbh->do("CREATE TABLE Blah ( id INTEGER, val VARCHAR )"));
	ok($dbh->commit);
	ok($dbh->do("INSERT INTO Blah VALUES ( 1, 'Test1' )"));
	eval {
	    $dbh2->do("INSERT INTO Blah VALUES ( 2, 'Test2' )");
	};
	ok($@);
	if ($@) {
	    print "# expected insert failure : $@";
	    $dbh2->rollback;
	}

	$dbh->commit;
	ok($dbh2->do("INSERT INTO Blah VALUES ( 2, 'Test2' )"));
	$dbh2->commit;

	$dbh2->disconnect;
	undef($dbh2);

	# NOTE: The second test is to see what happens if a lock is
	# is released while waiting. When both parent and child are
	# ready, the database is locked by the child. The parent
	# starts waiting for a long enough time (apparently we need
	# to wait much longer than we expected, as testers may use
	# very slow (virtual) machines to test, but don't worry,
	# it's only for the slowest environment). After a short sleep,
	# the child commits and releases the lock. Eventually the parent
	# notices that, and does the pended INSERT (hopefully before
	# it is timed out). As both the parent and the child wait till
	# both are ready, we don't need to sleep for a long time.
	pipe(READER, WRITER);
	my $pid = fork;
	if (!defined($pid)) {
	    # fork failed
	    SKIP: {
	        skip("No fork here", 3);
	    }
	    $dbh->disconnect;
	    unlink $dbfile;
	} elsif (!$pid) {
	    # child

	    # avoid resource collisions after fork
	    # http://www.slideshare.net/kazuho/un-5457977
	    unless ($^O eq 'MSWin32') {  # ignore fork emulation
	        $dbh->{InactiveDestroy} = 1;
	        undef $dbh;
	    }

	    my $dbh2 = DBI->connect("dbi:SQLite:$dbfile", '', '', 
	    {
	        RaiseError => 1,
	        PrintError => 0,
	        AutoCommit => 0,
	    });
	    $dbh2->do("INSERT INTO Blah VALUES ( 3, 'Test3' )");
	    select WRITER; $| = 1; select STDOUT;
	    print WRITER "Ready\n";
	    sleep(2);
	    $dbh2->commit;
	    $dbh2->disconnect;
	    exit;
	} else {
	    # parent
	    close WRITER;
	    my $line = <READER>;
	    chomp($line);
	    ok($line, "Ready");
	    ok($dbh->$call_func(100000, 'busy_timeout'));
	    eval { $dbh->do("INSERT INTO Blah VALUES (4, 'Test4' )") };
	    ok !$@;
	    if ($@) {
	        print STDERR "# Your testing environment might be too slow to pass this test: $@";
	        $dbh->rollback;
	    }
	    else {
	        $dbh->commit;
	    }
	    wait;
	    $dbh->disconnect;
	    unlink $dbfile;
	}
}