diff --git a/src/filesystem/__tests__/PhutilDeferredLogTestCase.php b/src/filesystem/__tests__/PhutilDeferredLogTestCase.php index 3e6c6432..1d129871 100644 --- a/src/filesystem/__tests__/PhutilDeferredLogTestCase.php +++ b/src/filesystem/__tests__/PhutilDeferredLogTestCase.php @@ -1,169 +1,147 @@ checkLog( "derp\n", 'derp', array()); $this->checkLog( "[20 Aug 1984] alincoln\n", '[%T] %u', array( 'T' => '20 Aug 1984', 'u' => 'alincoln', )); $this->checkLog( "%%%%%\n", '%%%%%%%%%%', array( '%' => '%', )); $this->checkLog( "\\000\\001\\002\n", '%a%b%c', array( 'a' => chr(0), 'b' => chr(1), 'c' => chr(2), )); $this->checkLog( "Download: 100%\n", 'Download: %C', array( 'C' => '100%', )); $this->checkLog( "- bee -\n", '%a %b %c', array( 'b' => 'bee', )); $this->checkLog( "\\\\\n", '%b', array( 'b' => '\\', )); $this->checkLog( "a\t\\t\n", "%a\t%b", array( 'a' => 'a', 'b' => "\t", )); $this->checkLog( "\1ab\n", "\1a%a", array( 'a' => 'b', )); $this->checkLog( "a % xb\n", '%a %% x%b', array( 'a' => 'a', 'b' => 'b', )); } public function testLogWriteFailure() { $caught = null; try { if (phutil_is_hiphop_runtime()) { // In HipHop exceptions thrown in destructors are not normally // catchable, so call __destruct() explicitly. $log = new PhutilDeferredLog('/derp/derp/derp/derp/derp', 'derp'); $log->__destruct(); } else { new PhutilDeferredLog('/derp/derp/derp/derp/derp', 'derp'); } } catch (Exception $ex) { $caught = $ex; } $this->assertTrue($caught instanceof Exception); } - public function testManyWriters() { - $root = phutil_get_library_root('arcanist').'/../'; - $bin = $root.'scripts/test/deferred_log.php'; - - $n_writers = 3; - $n_lines = 8; - - $tmp = new TempFile(); - - $futures = array(); - for ($ii = 0; $ii < $n_writers; $ii++) { - $futures[] = new ExecFuture('%s %d %s', $bin, $n_lines, (string)$tmp); - } - - id(new FutureIterator($futures)) - ->resolveAll(); - - $this->assertEqual( - str_repeat("abcdefghijklmnopqrstuvwxyz\n", $n_writers * $n_lines), - Filesystem::readFile($tmp)); - } - public function testNoWrite() { $tmp = new TempFile(); $log = new PhutilDeferredLog($tmp, 'xyz'); $log->setFile(null); unset($log); $this->assertEqual('', Filesystem::readFile($tmp), pht('No Write')); } public function testDoubleWrite() { $tmp = new TempFile(); $log = new PhutilDeferredLog($tmp, 'xyz'); $log->write(); $log->write(); unset($log); $this->assertEqual( "xyz\n", Filesystem::readFile($tmp), pht('Double Write')); } public function testSetAfterWrite() { $tmp1 = new TempFile(); $tmp2 = new TempFile(); $log = new PhutilDeferredLog($tmp1, 'xyz'); $log->write(); $caught = null; try { $log->setFile($tmp2); } catch (Exception $ex) { $caught = $ex; } $this->assertTrue($caught instanceof Exception, pht('Set After Write')); } private function checkLog($expect, $format, $data) { $tmp = new TempFile(); $log = new PhutilDeferredLog($tmp, $format); $log->setData($data); unset($log); $this->assertEqual($expect, Filesystem::readFile($tmp), $format); } } diff --git a/src/filesystem/__tests__/PhutilFileLockTestCase.php b/src/filesystem/__tests__/PhutilFileLockTestCase.php index 17f9632b..9dcc2c30 100644 --- a/src/filesystem/__tests__/PhutilFileLockTestCase.php +++ b/src/filesystem/__tests__/PhutilFileLockTestCase.php @@ -1,184 +1,186 @@ assertTrue($this->lockTest($file)); $this->assertTrue($this->lockTest($file)); } public function testLockHolding() { // When a process is holding a lock, other processes should be unable // to acquire it. $file = new TempFile(); $hold = $this->holdLock($file); $this->assertFalse($this->lockTest($file)); $hold->resolveKill(); $this->assertTrue($this->lockTest($file)); } public function testInProcessLocking() { // Other processes should be unable to lock a file if we hold the lock. $file = new TempFile(); $lock = PhutilFileLock::newForPath($file); $lock->lock(); $this->assertFalse($this->lockTest($file)); $lock->unlock(); $this->assertTrue($this->lockTest($file)); } public function testInProcessHolding() { // We should be unable to lock a file if another process is holding the // lock. $file = new TempFile(); $lock = PhutilFileLock::newForPath($file); $hold = $this->holdLock($file); $caught = null; try { $lock->lock(); } catch (PhutilLockException $ex) { $caught = $ex; } $this->assertTrue($caught instanceof PhutilLockException); $hold->resolveKill(); $this->assertTrue($this->lockTest($file)); $lock->lock(); $lock->unlock(); } public function testRelock() { // Trying to lock a file twice should throw an exception. $file = new TempFile(); $lock = PhutilFileLock::newForPath($file); $lock->lock(); $caught = null; try { $lock->lock(); } catch (Exception $ex) { $caught = $ex; } $this->assertTrue($caught instanceof Exception); } public function testExcessiveUnlock() { // Trying to unlock a file twice should throw an exception. $file = new TempFile(); $lock = PhutilFileLock::newForPath($file); $lock->lock(); $lock->unlock(); $caught = null; try { $lock->unlock(); } catch (Exception $ex) { $caught = $ex; } $this->assertTrue($caught instanceof Exception); } public function testUnlockAll() { // unlockAll() should release all locks. $file = new TempFile(); $lock = PhutilFileLock::newForPath($file); $lock->lock(); $this->assertFalse($this->lockTest($file)); PhutilFileLock::unlockAll(); $this->assertTrue($this->lockTest($file)); // Calling this again shouldn't do anything bad. PhutilFileLock::unlockAll(); $this->assertTrue($this->lockTest($file)); $lock->lock(); $lock->unlock(); } public function testIsLocked() { // isLocked() should report lock status accurately. $file = new TempFile(); $lock = PhutilFileLock::newForPath($file); $this->assertFalse($lock->isLocked()); $lock->lock(); $this->assertTrue($lock->isLocked()); $lock->unlock(); $this->assertFalse($lock->isLocked()); } private function lockTest($file) { list($err) = $this->buildLockFuture('--test', $file)->resolve(); return ($err == 0); } private function holdLock($file) { $future = $this->buildLockFuture('--hold', $file); // We can't return until we're sure the subprocess has had time to acquire // the lock. Since actually testing for the lock would be kind of silly // and guarantee that we loop forever if the locking primitive broke, // watch stdout for a *claim* that it has acquired the lock instead. // Make sure we don't loop forever, no matter how bad things get. $future->setTimeout(30); $buf = ''; while (!$future->isReady()) { list($stdout) = $future->read(); $buf .= $stdout; if (strpos($buf, 'LOCK ACQUIRED') !== false) { return $future; } } throw new Exception(pht('Unable to hold lock in external process!')); } private function buildLockFuture($flags, $file) { $root = dirname(phutil_get_library_root('arcanist')); - $bin = $root.'/scripts/utils/lock.php'; + $bin = $root.'/support/test/lock-file.php'; + + $flags = (array)$flags; // NOTE: Use `exec` so this passes on Ubuntu, where the default `dash` shell // will eat any kills we send during the tests. - $future = new ExecFuture('exec php %s %C %s', $bin, $flags, $file); + $future = new ExecFuture('exec php -f %R -- %Ls %R', $bin, $flags, $file); $future->start(); return $future; } } diff --git a/support/test/lock-file.php b/support/test/lock-file.php new file mode 100644 index 00000000..b7ec9df3 --- /dev/null +++ b/support/test/lock-file.php @@ -0,0 +1,82 @@ +#!/usr/bin/env php +setTagline(pht('acquire and hold a lockfile')); +$args->setSynopsis(<<parseStandardArguments(); +$args->parse(array( + array( + 'name' => 'test', + 'help' => pht('Instead of holding the lock, release it and exit.'), + ), + array( + 'name' => 'hold', + 'help' => pht('Hold indefinitely without prompting.'), + ), + array( + 'name' => 'wait', + 'param' => 'n', + 'help' => pht('Block for up to __n__ seconds waiting for the lock.'), + 'default' => 0, + ), + array( + 'name' => 'file', + 'wildcard' => true, + ), +)); + + +$file = $args->getArg('file'); +if (count($file) !== 1) { + $args->printHelpAndExit(); +} +$file = head($file); + +$console = PhutilConsole::getConsole(); +$console->writeOut( + "%s\n", + pht('This process has PID %d. Acquiring lock...', getmypid())); + +$lock = PhutilFileLock::newForPath($file); + +try { + $lock->lock($args->getArg('wait')); +} catch (PhutilFileLockException $ex) { + $console->writeOut( + "**%s** %s\n", + pht('UNABLE TO ACQUIRE LOCK:'), + pht('Lock is already held.')); + exit(1); +} + +// NOTE: This string is magic, the unit tests look for it. +$console->writeOut("%s\n", pht('LOCK ACQUIRED')); +if ($args->getArg('test')) { + $lock->unlock(); + exit(0); +} + +if ($args->getArg('hold')) { + while (true) { + sleep(1); + } +} + +while (!$console->confirm(pht('Release lock?'))) { + // Keep asking until they say yes. +} + +$console->writeOut("%s\n", pht('Unlocking...')); +$lock->unlock(); + +$console->writeOut("%s\n", pht('Done.')); +exit(0);