Merge branch '2.1' into 2.2
* 2.1: [HttpKernel] tweaked previous merge #7531: [HttpKernel][Config] FileLocator adds NULL as global resource path Fix autocompletion of command names when namespaces conflict Fix timeout in Process::stop method fixed CS Round stream_select fifth argument up. Fix Process timeout Conflicts: src/Symfony/Component/Process/Process.php src/Symfony/Component/Process/ProcessBuilder.php
This commit is contained in:
commit
2965a52d20
@ -492,20 +492,24 @@ class Application
|
||||
*/
|
||||
public function findNamespace($namespace)
|
||||
{
|
||||
$allNamespaces = array();
|
||||
foreach ($this->getNamespaces() as $n) {
|
||||
$allNamespaces[$n] = explode(':', $n);
|
||||
}
|
||||
|
||||
$found = array();
|
||||
$allNamespaces = $this->getNamespaces();
|
||||
$found = '';
|
||||
foreach (explode(':', $namespace) as $i => $part) {
|
||||
$abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $allNamespaces)))));
|
||||
// select sub-namespaces matching the current namespace we found
|
||||
$namespaces = array();
|
||||
foreach ($allNamespaces as $n) {
|
||||
if ('' === $found || 0 === strpos($n, $found)) {
|
||||
$namespaces[$n] = explode(':', $n);
|
||||
}
|
||||
}
|
||||
|
||||
$abbrevs = static::getAbbreviations(array_unique(array_values(array_filter(array_map(function ($p) use ($i) { return isset($p[$i]) ? $p[$i] : ''; }, $namespaces)))));
|
||||
|
||||
if (!isset($abbrevs[$part])) {
|
||||
$message = sprintf('There are no commands defined in the "%s" namespace.', $namespace);
|
||||
|
||||
if (1 <= $i) {
|
||||
$part = implode(':', $found).':'.$part;
|
||||
$part = $found.':'.$part;
|
||||
}
|
||||
|
||||
if ($alternatives = $this->findAlternativeNamespace($part, $abbrevs)) {
|
||||
@ -521,14 +525,19 @@ class Application
|
||||
throw new \InvalidArgumentException($message);
|
||||
}
|
||||
|
||||
// there are multiple matches, but $part is an exact match of one of them so we select it
|
||||
if (in_array($part, $abbrevs[$part])) {
|
||||
$abbrevs[$part] = array($part);
|
||||
}
|
||||
|
||||
if (count($abbrevs[$part]) > 1) {
|
||||
throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions($abbrevs[$part])));
|
||||
}
|
||||
|
||||
$found[] = $abbrevs[$part][0];
|
||||
$found .= $found ? ':' . $abbrevs[$part][0] : $abbrevs[$part][0];
|
||||
}
|
||||
|
||||
return implode(':', $found);
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -651,21 +660,12 @@ class Application
|
||||
{
|
||||
$abbrevs = array();
|
||||
foreach ($names as $name) {
|
||||
for ($len = strlen($name) - 1; $len > 0; --$len) {
|
||||
for ($len = strlen($name); $len > 0; --$len) {
|
||||
$abbrev = substr($name, 0, $len);
|
||||
if (!isset($abbrevs[$abbrev])) {
|
||||
$abbrevs[$abbrev] = array($name);
|
||||
} else {
|
||||
$abbrevs[$abbrev][] = $name;
|
||||
}
|
||||
$abbrevs[$abbrev][] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
// Non-abbreviations always get entered, even if they aren't unique
|
||||
foreach ($names as $name) {
|
||||
$abbrevs[$name] = array($name);
|
||||
}
|
||||
|
||||
return $abbrevs;
|
||||
}
|
||||
|
||||
|
@ -345,6 +345,16 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testFindNamespaceDoesNotFailOnDeepSimilarNamespaces()
|
||||
{
|
||||
$application = $this->getMock('Symfony\Component\Console\Application', array('getNamespaces'));
|
||||
$application->expects($this->once())
|
||||
->method('getNamespaces')
|
||||
->will($this->returnValue(array('foo:sublong', 'bar:sub')));
|
||||
|
||||
$this->assertEquals('foo:sublong', $application->findNamespace('f:sub'));
|
||||
}
|
||||
|
||||
public function testSetCatchExceptions()
|
||||
{
|
||||
$application = $this->getMock('Symfony\Component\Console\Application', array('getTerminalWidth'));
|
||||
|
@ -28,14 +28,16 @@ class FileLocator extends BaseFileLocator
|
||||
* Constructor.
|
||||
*
|
||||
* @param KernelInterface $kernel A KernelInterface instance
|
||||
* @param string $path The path the global resource directory
|
||||
* @param string|array $paths A path or an array of paths where to look for resources
|
||||
* @param null|string $path The path the global resource directory
|
||||
* @param array $paths An array of paths where to look for resources
|
||||
*/
|
||||
public function __construct(KernelInterface $kernel, $path = null, array $paths = array())
|
||||
{
|
||||
$this->kernel = $kernel;
|
||||
$this->path = $path;
|
||||
$paths[] = $path;
|
||||
if (null !== $path) {
|
||||
$this->path = $path;
|
||||
$paths[] = $path;
|
||||
}
|
||||
|
||||
parent::__construct($paths);
|
||||
}
|
||||
|
47
src/Symfony/Component/HttpKernel/Tests/Config/FileLocatorTest.php
Executable file
47
src/Symfony/Component/HttpKernel/Tests/Config/FileLocatorTest.php
Executable file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\HttpKernel\Tests\Config;
|
||||
|
||||
use Symfony\Component\HttpKernel\Config\FileLocator;
|
||||
|
||||
class FileLocatorTest extends \PHPUnit_Framework_TestCase
|
||||
{
|
||||
public function testLocate()
|
||||
{
|
||||
$kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface');
|
||||
$kernel
|
||||
->expects($this->atLeastOnce())
|
||||
->method('locateResource')
|
||||
->with('@BundleName/some/path', null, true)
|
||||
->will($this->returnValue('/bundle-name/some/path'));
|
||||
$locator = new FileLocator($kernel);
|
||||
$this->assertEquals('/bundle-name/some/path', $locator->locate('@BundleName/some/path'));
|
||||
|
||||
$kernel
|
||||
->expects($this->never())
|
||||
->method('locateResource');
|
||||
$this->setExpectedException('LogicException');
|
||||
$locator->locate('/some/path');
|
||||
}
|
||||
|
||||
public function testLocateWithGlobalResourcePath()
|
||||
{
|
||||
$kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface');
|
||||
$kernel
|
||||
->expects($this->atLeastOnce())
|
||||
->method('locateResource')
|
||||
->with('@BundleName/some/path', '/global/resource/path', false);
|
||||
|
||||
$locator = new FileLocator($kernel, '/global/resource/path');
|
||||
$locator->locate('@BundleName/some/path', null, false);
|
||||
}
|
||||
}
|
@ -35,10 +35,14 @@ class Process
|
||||
const STDOUT = 1;
|
||||
const STDERR = 2;
|
||||
|
||||
// Timeout Precision in seconds.
|
||||
const TIMEOUT_PRECISION = 0.2;
|
||||
|
||||
private $commandline;
|
||||
private $cwd;
|
||||
private $env;
|
||||
private $stdin;
|
||||
private $starttime;
|
||||
private $timeout;
|
||||
private $options;
|
||||
private $exitcode;
|
||||
@ -228,6 +232,7 @@ class Process
|
||||
throw new RuntimeException('Process is already running');
|
||||
}
|
||||
|
||||
$this->starttime = microtime(true);
|
||||
$this->stdout = '';
|
||||
$this->stderr = '';
|
||||
$this->incrementalOutputOffset = 0;
|
||||
@ -304,7 +309,7 @@ class Process
|
||||
$w = $writePipes;
|
||||
$e = null;
|
||||
|
||||
$n = @stream_select($r, $w, $e, $this->timeout);
|
||||
$n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6));
|
||||
|
||||
if (false === $n) {
|
||||
break;
|
||||
@ -337,6 +342,8 @@ class Process
|
||||
unset($this->pipes[$type]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->checkTimeout();
|
||||
}
|
||||
|
||||
$this->updateStatus();
|
||||
@ -360,7 +367,7 @@ class Process
|
||||
public function restart($callback = null)
|
||||
{
|
||||
if ($this->isRunning()) {
|
||||
throw new \RuntimeException('Process is already running');
|
||||
throw new RuntimeException('Process is already running');
|
||||
}
|
||||
|
||||
$process = clone $this;
|
||||
@ -391,13 +398,15 @@ class Process
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD') && $this->fileHandles) {
|
||||
$this->processFileHandles($callback, !$this->pipes);
|
||||
}
|
||||
$this->checkTimeout();
|
||||
|
||||
if ($this->pipes) {
|
||||
$r = $this->pipes;
|
||||
$w = null;
|
||||
$e = null;
|
||||
|
||||
if (false === $n = @stream_select($r, $w, $e, $this->timeout)) {
|
||||
// let's have a look if something changed in streams
|
||||
if (false === $n = @stream_select($r, $w, $e, 0, ceil(static::TIMEOUT_PRECISION * 1E6))) {
|
||||
$lastError = error_get_last();
|
||||
|
||||
// stream_select returns false when the `select` system call is interrupted by an incoming signal
|
||||
@ -407,10 +416,10 @@ class Process
|
||||
|
||||
continue;
|
||||
}
|
||||
if (0 === $n) {
|
||||
proc_terminate($this->process);
|
||||
|
||||
throw new RuntimeException('The process timed out.');
|
||||
// nothing has changed
|
||||
if (0 === $n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($r as $pipe) {
|
||||
@ -712,7 +721,7 @@ class Process
|
||||
*/
|
||||
public function stop($timeout = 10)
|
||||
{
|
||||
$timeoutMicro = (int) $timeout*10E6;
|
||||
$timeoutMicro = (int) $timeout*1E6;
|
||||
if ($this->isRunning()) {
|
||||
proc_terminate($this->process);
|
||||
$time = 0;
|
||||
@ -721,6 +730,10 @@ class Process
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
if (!defined('PHP_WINDOWS_VERSION_BUILD') && $this->isRunning()) {
|
||||
proc_terminate($this->process, SIGKILL);
|
||||
}
|
||||
|
||||
foreach ($this->pipes as $pipe) {
|
||||
fclose($pipe);
|
||||
}
|
||||
@ -800,7 +813,7 @@ class Process
|
||||
*
|
||||
* To disable the timeout, set this value to null.
|
||||
*
|
||||
* @param integer|null $timeout The timeout in seconds
|
||||
* @param float|null $timeout The timeout in seconds
|
||||
*
|
||||
* @return self The current Process instance
|
||||
*
|
||||
@ -814,10 +827,10 @@ class Process
|
||||
return $this;
|
||||
}
|
||||
|
||||
$timeout = (integer) $timeout;
|
||||
$timeout = (float) $timeout;
|
||||
|
||||
if ($timeout < 0) {
|
||||
throw new InvalidArgumentException('The timeout value must be a valid positive integer.');
|
||||
throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
|
||||
}
|
||||
|
||||
$this->timeout = $timeout;
|
||||
@ -982,6 +995,24 @@ class Process
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a check between the timeout definition and the time the process
|
||||
* started
|
||||
*
|
||||
* In case you run a background process (with the start method), you should
|
||||
* trigger this method regularly to ensure the process timeout
|
||||
*
|
||||
* @throws RuntimeException In case the timeout was reached
|
||||
*/
|
||||
public function checkTimeout()
|
||||
{
|
||||
if (0 < $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
|
||||
$this->stop(0);
|
||||
|
||||
throw new RuntimeException('The process timed-out.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds up the callback used by wait().
|
||||
*
|
||||
|
@ -103,7 +103,7 @@ class ProcessBuilder
|
||||
*
|
||||
* To disable the timeout, set this value to null.
|
||||
*
|
||||
* @param integer|null
|
||||
* @param float|null
|
||||
*
|
||||
* @return ProcessBuilder
|
||||
*
|
||||
@ -117,10 +117,10 @@ class ProcessBuilder
|
||||
return $this;
|
||||
}
|
||||
|
||||
$timeout = (integer) $timeout;
|
||||
$timeout = (float) $timeout;
|
||||
|
||||
if ($timeout < 0) {
|
||||
throw new InvalidArgumentException('The timeout value must be a valid positive integer.');
|
||||
throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
|
||||
}
|
||||
|
||||
$this->timeout = $timeout;
|
||||
|
@ -12,6 +12,7 @@
|
||||
namespace Symfony\Component\Process\Tests;
|
||||
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Process\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* @author Robert Schönthal <seroscho@googlemail.com>
|
||||
@ -44,6 +45,31 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
||||
$this->assertNull($p->getTimeout());
|
||||
}
|
||||
|
||||
public function testStopWithTimeoutIsActuallyWorking()
|
||||
{
|
||||
if (defined('PHP_WINDOWS_VERSION_BUILD')) {
|
||||
$this->markTestSkipped('Stop with timeout does not work on windows, it requires posix signals');
|
||||
}
|
||||
if (!function_exists('pcntl_signal')) {
|
||||
$this->markTestSkipped('This test require pcntl_signal function');
|
||||
}
|
||||
|
||||
// exec is mandatory here since we send a signal to the process
|
||||
// see https://github.com/symfony/symfony/issues/5030 about prepending
|
||||
// command with exec
|
||||
$p = $this->getProcess('exec php '.__DIR__.'/NonStopableProcess.php 3');
|
||||
$p->start();
|
||||
usleep(100000);
|
||||
$start = microtime(true);
|
||||
$p->stop(1.1);
|
||||
while ($p->isRunning()) {
|
||||
usleep(1000);
|
||||
}
|
||||
$duration = microtime(true) - $start;
|
||||
|
||||
$this->assertLessThan(1.3, $duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* tests results from sub processes
|
||||
*
|
||||
@ -320,6 +346,47 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
||||
// PHP will deadlock when it tries to cleanup $process
|
||||
}
|
||||
|
||||
public function testRunProcessWithTimeout()
|
||||
{
|
||||
$timeout = 0.5;
|
||||
$process = $this->getProcess('sleep 3');
|
||||
$process->setTimeout($timeout);
|
||||
$start = microtime(true);
|
||||
try {
|
||||
$process->run();
|
||||
$this->fail('A RuntimeException should have been raised');
|
||||
} catch (RuntimeException $e) {
|
||||
|
||||
}
|
||||
$duration = microtime(true) - $start;
|
||||
|
||||
$this->assertLessThan($timeout + Process::TIMEOUT_PRECISION, $duration);
|
||||
}
|
||||
|
||||
public function testCheckTimeoutOnStartedProcess()
|
||||
{
|
||||
$timeout = 0.5;
|
||||
$precision = 100000;
|
||||
$process = $this->getProcess('sleep 3');
|
||||
$process->setTimeout($timeout);
|
||||
$start = microtime(true);
|
||||
|
||||
$process->start();
|
||||
|
||||
try {
|
||||
while ($process->isRunning()) {
|
||||
$process->checkTimeout();
|
||||
usleep($precision);
|
||||
}
|
||||
$this->fail('A RuntimeException should have been raised');
|
||||
} catch (RuntimeException $e) {
|
||||
|
||||
}
|
||||
$duration = microtime(true) - $start;
|
||||
|
||||
$this->assertLessThan($timeout + $precision, $duration);
|
||||
}
|
||||
|
||||
public function responsesCodeProvider()
|
||||
{
|
||||
return array(
|
||||
|
37
src/Symfony/Component/Process/Tests/NonStopableProcess.php
Normal file
37
src/Symfony/Component/Process/Tests/NonStopableProcess.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Runs a PHP script that can be stopped only with a SIGKILL (9) signal for 3 seconds
|
||||
*
|
||||
* @args duration Run this script with a custom duration
|
||||
*
|
||||
* @example `php NonStopableProcess.php 42` will run the script for 42 seconds
|
||||
*/
|
||||
|
||||
function handleSignal($signal)
|
||||
{
|
||||
switch ($signal) {
|
||||
case SIGTERM:
|
||||
$name = 'SIGTERM';
|
||||
break;
|
||||
case SIGINT:
|
||||
$name = 'SIGINT';
|
||||
break;
|
||||
default:
|
||||
$name = $signal . ' (unknown)';
|
||||
break;
|
||||
}
|
||||
|
||||
echo "received signal $name\n";
|
||||
}
|
||||
|
||||
declare(ticks=1);
|
||||
pcntl_signal(SIGTERM, 'handleSignal');
|
||||
pcntl_signal(SIGINT, 'handleSignal');
|
||||
|
||||
$duration = isset($argv[1]) ? (int) $argv[1] : 3;
|
||||
$start = microtime(true);
|
||||
|
||||
while ($duration > (microtime(true) - $start)) {
|
||||
usleep(1000);
|
||||
}
|
Reference in New Issue
Block a user