# tq-compability.tt: # Thread::Queue compatibility layer to run a Thread::Queue # test script against Forks::Queue. This should be used # for tests whenever a call is made to a Test::More test # method (like 'ok', 'is', 'is_deeply', etc.) from a # thread that is not the main thread # # To use it, just add # # require "t/tq-compatibility.tt"; # # somewhere near the top of the script # use Test::More; use Carp 'verbose'; use Forks::Queue; use warnings; no warnings 'redefine', 'once'; our $okq //= Forks::Queue->new(impl => 'File'); use threads; $INC{'Thread/Queue.pm'} = 1; alarm ($^O eq 'cygwin' ? 120 : 120) if !$tq::alarm_set++; open XERR, '>&STDERR'; our $DBG = *XERR; unless ($ENV{TQ_DEBUG}) { open $DBG,">",$^O eq 'MSWin32' ? 'nul' : "/dev/null"; } sub Thread::Queue::new { # monkey patch Thread::Queue constructor to return an equivalent # Forks::Queue object. my ($pkg,@list) = @_; my $impl = $ARGV[0] || $Forks::Queue::OPTS{impl} || "File"; print $DBG "Fake Thread::Queue::new impl => $impl\n"; Forks::Queue->new( impl => $impl, on_limit => 'tq-compat', list => \@list ); } sub done_testing { print $DBG "Fake done testing\n"; $okq->end; while (my $item = $okq->get) { tq::process_okq_task($item); } goto &Test::More::done_testing; } # tests that were designed to take place in a thread now take # place in a subprocess, so a different kind of interprocess # communication is needed. # # we will copy the test inputs to a Forks::Queue object, # and the main process can read from the queue and run the tests when the # subprocesses are all complete (see tq::process_okq_task below). sub is ($$;$) { goto &Test::More::is unless threads->tid(); print $DBG "Fake is\n"; $okq->put( [ 'is', @_, threads->tid ] ); return !defined($_[1]) ? !defined($_[0]) : "$_[0]" eq "$_[1]"; } sub is_deeply { goto &Test::More::is_deeply unless threads->tid(); print $DBG "Fake is_deeply\n"; $okq->put( [ 'is_deeply', @_, threads->tid ] ); return 1; } sub ok ($;$) { goto &Test::More::ok unless threads->tid(); print $DBG "Fake ok @_\n"; $okq->put( [ 'ok', @_, threads->tid ] ); return $_[0]; } sub pass (;$) { goto &Test::More::pass unless threads->tid(); print $DBG "Fake pass @_\n"; $okq->put( ['ok', 1, @_, threads->tid ] ); return 1; } sub like ($$;$) { goto &Test::More::like unless threads->tid(); print $DBG "Fake like\n"; $okq->put( [ 'like', @_, threads->tid ] ); return "$_[0]" =~ $_[1]; } # read test results that were written by the subprocesses and # dispatch them to the real Test::More functions. sub tq::process_okq_task { my $task = shift; my $test = shift @$task; my $pid = pop @$task; if ($test eq 'ok') { return Test::More::ok($task->[0], "[$pid] " . $task->[1]); } elsif ($test eq 'is') { return Test::More::is($task->[0],$task->[1],"[$pid] $task->[2]"); } elsif ($test eq 'is_deeply') { return Test::More::is_deeply($task->[0],$task->[1],"[$pid] $task->[2]"); } elsif ($test eq 'like') { return Test::More::like($task->[0], $task->[1], "[$pid] $task->[2]"); } warn "unrecognized test: $test\n"; return 0; } # helper to iterate through implementations our (@IMPL,$impl); sub tq::IMPL { while (my $task = $okq->get_nb) { tq::process_okq_task($task); } if (!@IMPL) { if (@_) { @IMPL = @_; } elsif ($ENV{FORKS_QUEUE_IMPL}) { @IMPL = split /,/, $ENV{FORKS_QUEUE_IMPL}; } elsif (@ARGV) { @IMPL = @ARGV; } else { push @IMPL, 'File' if eval "use Forks::Queue::File;1"; push @IMPL, 'Shmem' if eval "use Forks::Queue::Shmem; -d \$Forks::Queue::Shmem::DEV_SHM"; push @IMPL, 'SQLite' if eval "use DBD::SQLite; use Forks::Queue::SQLite;1"; } push @IMPL,'__DONE__'; } $impl = $Forks::Queue::OPTS{impl} = shift @IMPL; if ($impl eq '__DONE__') { # alarm 0; return; } # alarm 60; return $impl; } 1;