Skip to content

Commit 449fe01

Browse files
committed
[Process] Trow exceptions in case a Process method is supposed to be called after termination
1 parent 0ae6858 commit 449fe01

File tree

2 files changed

+119
-29
lines changed

2 files changed

+119
-29
lines changed

src/Symfony/Component/Process/Process.php

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -296,13 +296,11 @@ public function restart($callback = null)
296296
*
297297
* @throws RuntimeException When process timed out
298298
* @throws RuntimeException When process stopped after receiving signal
299-
* @throws LogicException When process is not started
299+
* @throws LogicException When process is not yet started
300300
*/
301301
public function wait($callback = null)
302302
{
303-
if (!$this->isStarted()) {
304-
throw new LogicException(sprintf('Process must be started before calling %s', __FUNCTION__));
305-
}
303+
$this->requireProcessIsStarted(__FUNCTION__);
306304

307305
$this->updateStatus(false);
308306
if (null !== $callback) {
@@ -377,9 +375,7 @@ public function signal($signal)
377375
*/
378376
public function getOutput()
379377
{
380-
if (!$this->isStarted()) {
381-
throw new LogicException(sprintf('Process must be started before calling %s', __FUNCTION__));
382-
}
378+
$this->requireProcessIsStarted(__FUNCTION__);
383379

384380
$this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
385381

@@ -392,10 +388,14 @@ public function getOutput()
392388
* In comparison with the getOutput method which always return the whole
393389
* output, this one returns the new output since the last call.
394390
*
391+
* @throws LogicException In case the process is not started
392+
*
395393
* @return string The process output since the last call
396394
*/
397395
public function getIncrementalOutput()
398396
{
397+
$this->requireProcessIsStarted(__FUNCTION__);
398+
399399
$data = $this->getOutput();
400400

401401
$latest = substr($data, $this->incrementalOutputOffset);
@@ -415,9 +415,7 @@ public function getIncrementalOutput()
415415
*/
416416
public function getErrorOutput()
417417
{
418-
if (!$this->isStarted()) {
419-
throw new LogicException(sprintf('Process must be started before calling %s', __FUNCTION__));
420-
}
418+
$this->requireProcessIsStarted(__FUNCTION__);
421419

422420
$this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
423421

@@ -431,10 +429,14 @@ public function getErrorOutput()
431429
* whole error output, this one returns the new error output since the last
432430
* call.
433431
*
432+
* @throws LogicException In case the process is not started
433+
*
434434
* @return string The process error output since the last call
435435
*/
436436
public function getIncrementalErrorOutput()
437437
{
438+
$this->requireProcessIsStarted(__FUNCTION__);
439+
438440
$data = $this->getErrorOutput();
439441

440442
$latest = substr($data, $this->incrementalErrorOutputOffset);
@@ -446,7 +448,7 @@ public function getIncrementalErrorOutput()
446448
/**
447449
* Returns the exit code returned by the process.
448450
*
449-
* @return integer The exit status code
451+
* @return null|integer The exit status code, null if the Process is not terminated
450452
*
451453
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
452454
*
@@ -469,14 +471,18 @@ public function getExitCode()
469471
* This method relies on the Unix exit code status standardization
470472
* and might not be relevant for other operating systems.
471473
*
472-
* @return string A string representation for the exit status code
474+
* @return null|string A string representation for the exit status code, null if the Process is not terminated.
475+
*
476+
* @throws RuntimeException In case --enable-sigchild is activated and the sigchild compatibility mode is disabled
473477
*
474478
* @see http://tldp.org/LDP/abs/html/exitcodes.html
475479
* @see http://en.wikipedia.org/wiki/Unix_signal
476480
*/
477481
public function getExitCodeText()
478482
{
479-
$exitcode = $this->getExitCode();
483+
if (null === $exitcode = $this->getExitCode()) {
484+
return;
485+
}
480486

481487
return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error';
482488
}
@@ -501,11 +507,14 @@ public function isSuccessful()
501507
* @return Boolean
502508
*
503509
* @throws RuntimeException In case --enable-sigchild is activated
510+
* @throws LogicException In case the process is not terminated.
504511
*
505512
* @api
506513
*/
507514
public function hasBeenSignaled()
508515
{
516+
$this->requireProcessIsTerminated(__FUNCTION__);
517+
509518
if ($this->isSigchildEnabled()) {
510519
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
511520
}
@@ -523,11 +532,14 @@ public function hasBeenSignaled()
523532
* @return integer
524533
*
525534
* @throws RuntimeException In case --enable-sigchild is activated
535+
* @throws LogicException In case the process is not terminated.
526536
*
527537
* @api
528538
*/
529539
public function getTermSignal()
530540
{
541+
$this->requireProcessIsTerminated(__FUNCTION__);
542+
531543
if ($this->isSigchildEnabled()) {
532544
throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.');
533545
}
@@ -544,10 +556,14 @@ public function getTermSignal()
544556
*
545557
* @return Boolean
546558
*
559+
* @throws LogicException In case the process is not terminated.
560+
*
547561
* @api
548562
*/
549563
public function hasBeenStopped()
550564
{
565+
$this->requireProcessIsTerminated(__FUNCTION__);
566+
551567
$this->updateStatus(false);
552568

553569
return $this->processInformation['stopped'];
@@ -560,10 +576,14 @@ public function hasBeenStopped()
560576
*
561577
* @return integer
562578
*
579+
* @throws LogicException In case the process is not terminated.
580+
*
563581
* @api
564582
*/
565583
public function getStopSignal()
566584
{
585+
$this->requireProcessIsTerminated(__FUNCTION__);
586+
567587
$this->updateStatus(false);
568588

569589
return $this->processInformation['stopsig'];
@@ -945,6 +965,10 @@ public function setEnhanceSigchildCompatibility($enhance)
945965
*/
946966
public function checkTimeout()
947967
{
968+
if ($this->status !== self::STATUS_STARTED) {
969+
return;
970+
}
971+
948972
if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
949973
$this->stop(0);
950974

@@ -1162,4 +1186,32 @@ private function doSignal($signal, $throwException)
11621186

11631187
return true;
11641188
}
1189+
1190+
/**
1191+
* Ensures the process is running or terminated, throws a LogicException if the process has a not started.
1192+
*
1193+
* @param $functionName The function name that was called.
1194+
*
1195+
* @throws LogicException If the process has not run.
1196+
*/
1197+
private function requireProcessIsStarted($functionName)
1198+
{
1199+
if (!$this->isStarted()) {
1200+
throw new LogicException(sprintf('Process must be started before calling %s.', $functionName));
1201+
}
1202+
}
1203+
1204+
/**
1205+
* Ensures the process is terminated, throws a LogicException if the process has a status different than `terminated`.
1206+
*
1207+
* @param $functionName The function name that was called.
1208+
*
1209+
* @throws LogicException If the process is not yet terminated.
1210+
*/
1211+
private function requireProcessIsTerminated($functionName)
1212+
{
1213+
if (!$this->isTerminated()) {
1214+
throw new LogicException(sprintf('Process must be terminated before calling %s.', $functionName));
1215+
}
1216+
}
11651217
}

src/Symfony/Component/Process/Tests/AbstractProcessTest.php

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,12 @@ public function testTTYCommandExitCode()
272272
$this->assertTrue($process->isSuccessful());
273273
}
274274

275+
public function testExitCodeTextIsNullWhenExitCodeIsNull()
276+
{
277+
$process = $this->getProcess('');
278+
$this->assertNull($process->getExitCodeText());
279+
}
280+
275281
public function testExitCodeText()
276282
{
277283
$process = $this->getProcess('');
@@ -512,6 +518,19 @@ public function testRunProcessWithTimeout()
512518
$this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration);
513519
}
514520

521+
public function testCheckTimeoutOnNonStartedProcess()
522+
{
523+
$process = $this->getProcess('php -r "sleep(3);"');
524+
$process->checkTimeout();
525+
}
526+
527+
public function testCheckTimeoutOnTerminatedProcess()
528+
{
529+
$process = $this->getProcess('php -v');
530+
$process->run();
531+
$process->checkTimeout();
532+
}
533+
515534
public function testCheckTimeoutOnStartedProcess()
516535
{
517536
$timeout = 0.5;
@@ -618,33 +637,52 @@ public function testSignalProcessNotRunning()
618637
}
619638

620639
/**
621-
* @expectedException \Symfony\Component\Process\Exception\LogicException
622-
* @expectedExceptionMessage Process must be started before calling getOutput
640+
* @dataProvider provideMethodsThatNeedARunningProcess
623641
*/
624-
public function testGetOutputProcessNotStarted()
642+
public function testMethodsThatNeedARunningProcess($method)
625643
{
626644
$process = $this->getProcess('php -m');
627-
$process->getOutput();
645+
$this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
646+
call_user_func(array($process, $method));
628647
}
629648

630-
/**
631-
* @expectedException \Symfony\Component\Process\Exception\LogicException
632-
* @expectedExceptionMessage Process must be started before calling getErrorOutput
633-
*/
634-
public function testGetErrorOutputProcessNotStarted()
649+
public function provideMethodsThatNeedARunningProcess()
635650
{
636-
$process = $this->getProcess('php -m');
637-
$process->getErrorOutput();
651+
return array(
652+
array('getOutput'),
653+
array('getIncrementalOutput'),
654+
array('getErrorOutput'),
655+
array('getIncrementalErrorOutput'),
656+
array('wait'),
657+
);
638658
}
639659

640660
/**
641-
* @expectedException \Symfony\Component\Process\Exception\LogicException
642-
* @expectedExceptionMessage Process must be started before calling wait
661+
* @dataProvider provideMethodsThatNeedATerminatedProcess
643662
*/
644-
public function testWaitProcessWithoutTimeoutNotStarted()
663+
public function testMethodsThatNeedATerminatedProcess($method)
645664
{
646-
$process = $this->getProcess('php -m')->setTimeout(null);
647-
$process->wait();
665+
$process = $this->getProcess('php -r "sleep(1);"');
666+
$process->start();
667+
try {
668+
call_user_func(array($process, $method));
669+
$process->stop(0);
670+
$this->fail('A LogicException must have been thrown');
671+
} catch (\Exception $e) {
672+
$this->assertInstanceOf('Symfony\Component\Process\Exception\LogicException', $e);
673+
$this->assertEquals(sprintf('Process must be terminated before calling %s.', $method), $e->getMessage());
674+
}
675+
$process->stop(0);
676+
}
677+
678+
public function provideMethodsThatNeedATerminatedProcess()
679+
{
680+
return array(
681+
array('hasBeenSignaled'),
682+
array('getTermSignal'),
683+
array('hasBeenStopped'),
684+
array('getStopSignal'),
685+
);
648686
}
649687

650688
private function verifyPosixIsEnabled()

0 commit comments

Comments
 (0)