From 12a152b6664f2143eddab218bcf22fa39e998261 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 26 Nov 2015 10:39:31 +0100 Subject: [PATCH 1/4] [appveyor] Workaround transient segfault when APCu is enabled --- .travis.yml | 2 +- appveyor.yml | 4 ++-- phpunit | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index c347943fb4..c2933ed76e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ before_install: - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then phpenv config-rm xdebug.ini; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then echo "extension = mongo.so" >> $INI_FILE; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then echo "extension = memcache.so" >> $INI_FILE; fi; - - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.7 && echo "apc.enable_cli = 1" >> $INI_FILE) || echo "Let's continue without apcu extension"; fi; + - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then (echo yes | pecl install -f apcu-4.0.8 && echo "apc.enable_cli = 1" >> $INI_FILE) || echo "Let's continue without apcu extension"; fi; - if [[ "$TRAVIS_PHP_VERSION" = 5.* ]]; then pecl install -f memcached-2.1.0 || echo "Let's continue without memcached extension"; fi; - if [[ "$TRAVIS_PHP_VERSION" != "hhvm" ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi; - if [ "$deps" != "skip" ]; then ./phpunit install; fi; diff --git a/appveyor.yml b/appveyor.yml index 5237ef69e4..45a9e7e990 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,8 +26,8 @@ install: - IF %PHP%==1 cd ext - IF %PHP%==1 appveyor DownloadFile http://nebm.ist.utl.pt/~glopes/misc/intl_win/php_intl-3.0.0-5.3-nts-vc9-x86.zip - IF %PHP%==1 7z x php_intl-3.0.0-5.3-nts-vc9-x86.zip -y >nul - - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.7/php_apcu-4.0.7-5.3-nts-vc9-x86.zip - - IF %PHP%==1 7z x php_apcu-4.0.7-5.3-nts-vc9-x86.zip -y >nul + - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/apcu/4.0.8/php_apcu-4.0.8-5.3-nts-vc9-x86.zip + - IF %PHP%==1 7z x php_apcu-4.0.8-5.3-nts-vc9-x86.zip -y >nul - IF %PHP%==1 appveyor DownloadFile http://windows.php.net/downloads/pecl/releases/memcache/3.0.8/php_memcache-3.0.8-5.3-nts-vc9-x86.zip - IF %PHP%==1 7z x php_memcache-3.0.8-5.3-nts-vc9-x86.zip -y >nul - IF %PHP%==1 del /Q *.zip diff --git a/phpunit b/phpunit index 2ab4f25e75..3a3b3dd881 100755 --- a/phpunit +++ b/phpunit @@ -164,7 +164,8 @@ if (isset($argv[1]) && 'symfony' === $argv[1]) { unlink($file); } - if ($procStatus) { + // Fail on any individual component failures but ignore STATUS_STACK_BUFFER_OVERRUN (-1073740791) on Windows when APCu is enabled + if ($procStatus && ('\\' !== DIRECTORY_SEPARATOR || !extension_loaded('apcu') || !ini_get('apc.enable_cli') || -1073740791 !== $procStatus)) { $exit = 1; echo "\033[41mKO\033[0m $component\n\n"; } else { From 8588a4f63b718eff02554e766388f1ca05acc35c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 27 Nov 2015 09:21:43 +0100 Subject: [PATCH 2/4] [Process] Don't catch RuntimeException when it complicates tests debugging --- .../Process/Tests/SimpleProcessTest.php | 47 ++++++++----------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php index a52cd437a8..4419581167 100644 --- a/src/Symfony/Component/Process/Tests/SimpleProcessTest.php +++ b/src/Symfony/Component/Process/Tests/SimpleProcessTest.php @@ -152,46 +152,37 @@ class SimpleProcessTest extends AbstractProcessTest public function testStopTerminatesProcessCleanly() { - try { - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - $process->stop(); - }); - } catch (\RuntimeException $e) { - $this->fail('A call to stop() is not expected to cause wait() to throw a RuntimeException'); - } + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + $process->stop(); + }); + $this->assertTrue(true, 'A call to stop() is not expected to cause wait() to throw a RuntimeException'); } public function testKillSignalTerminatesProcessCleanly() { $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - try { - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - if ($process->isRunning()) { - $process->signal(defined('SIGKILL') ? SIGKILL : 9); - } - }); - } catch (\RuntimeException $e) { - $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); - } + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(defined('SIGKILL') ? SIGKILL : 9); + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); } public function testTermSignalTerminatesProcessCleanly() { $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - try { - $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); - $process->run(function () use ($process) { - if ($process->isRunning()) { - $process->signal(defined('SIGTERM') ? SIGTERM : 15); - } - }); - } catch (\RuntimeException $e) { - $this->fail('A call to signal() is not expected to cause wait() to throw a RuntimeException'); - } + $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"'); + $process->run(function () use ($process) { + if ($process->isRunning()) { + $process->signal(defined('SIGTERM') ? SIGTERM : 15); + } + }); + $this->assertTrue(true, 'A call to signal() is not expected to cause wait() to throw a RuntimeException'); } public function testStopWithTimeoutIsActuallyWorking() From 1179f0727bfe8425642abda290477098e911b46a Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 26 Nov 2015 15:25:37 +0100 Subject: [PATCH 3/4] [Form] Fixed: Duplicate choice labels are remembered when using "choices_as_values" = false --- .../Form/Extension/Core/Type/ChoiceType.php | 72 ++++++++++++++++++- .../Extension/Core/Type/ChoiceTypeTest.php | 17 +++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php index 528834d1e3..4822bcf68b 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php @@ -238,6 +238,7 @@ class ChoiceType extends AbstractType */ public function configureOptions(OptionsResolver $resolver) { + $choiceLabels = array(); $choiceListFactory = $this->choiceListFactory; $emptyData = function (Options $options) { @@ -252,6 +253,44 @@ class ChoiceType extends AbstractType return $options['required'] ? null : ''; }; + // BC closure, to be removed in 3.0 + $choicesNormalizer = function (Options $options, $choices) use (&$choiceLabels) { + // Unset labels from previous invocations + $choiceLabels = array(); + + // This closure is irrelevant when "choices_as_values" is set to true + if ($options['choices_as_values']) { + return $choices; + } + + ChoiceType::normalizeLegacyChoices($choices, $choiceLabels); + + return $choices; + }; + + // BC closure, to be removed in 3.0 + $choiceLabel = function (Options $options) use (&$choiceLabels) { + // If the choices contain duplicate labels, the normalizer of the + // "choices" option stores them in the $choiceLabels variable + + // Trigger the normalizer + $options->offsetGet('choices'); + + // Pick labels from $choiceLabels if available + // Don't invoke count() to avoid creating a copy of the array (yet) + if ($choiceLabels) { + // Don't pass the labels by reference. We do want to create a + // copy here so that every form has an own version of that + // variable (contrary to the global reference shared by all + // forms) + return function ($choice, $key) use ($choiceLabels) { + return $choiceLabels[$key]; + }; + } + + return; + }; + $choiceListNormalizer = function (Options $options, $choiceList) use ($choiceListFactory) { if ($choiceList) { @trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED); @@ -322,7 +361,7 @@ class ChoiceType extends AbstractType 'choices' => array(), 'choices_as_values' => false, 'choice_loader' => null, - 'choice_label' => null, + 'choice_label' => $choiceLabel, 'choice_name' => null, 'choice_value' => null, 'choice_attr' => null, @@ -340,6 +379,7 @@ class ChoiceType extends AbstractType 'choice_translation_domain' => true, )); + $resolver->setNormalizer('choices', $choicesNormalizer); $resolver->setNormalizer('choice_list', $choiceListNormalizer); $resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); @@ -454,4 +494,34 @@ class ChoiceType extends AbstractType $options['choice_attr'] ); } + + /** + * When "choices_as_values" is set to false, the choices are in the keys and + * their labels in the values. Labels may occur twice. The form component + * flips the choices array in the new implementation, so duplicate labels + * are lost. Store them in a utility array that is used from the + * "choice_label" closure by default. + * + * @param array $choices The choice labels indexed by choices. + * Labels are replaced by generated keys. + * @param array $choiceLabels The array that receives the choice labels + * indexed by generated keys. + * @param int|null $nextKey The next generated key. + * + * @internal Public only to be accessible from closures on PHP 5.3. Don't + * use this method, as it may be removed without notice. + */ + public static function normalizeLegacyChoices(array &$choices, array &$choiceLabels, &$nextKey = 0) + { + foreach ($choices as $choice => &$choiceLabel) { + if (is_array($choiceLabel)) { + self::normalizeLegacyChoices($choiceLabel, $choiceLabels, $nextKey); + continue; + } + + $choiceLabels[$nextKey] = $choiceLabel; + $choices[$choice] = $nextKey; + ++$nextKey; + } + } } diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php index 215ddac936..c1c1f9a327 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/ChoiceTypeTest.php @@ -1694,6 +1694,23 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase ), $view->vars['choices']); } + /** + * @group legacy + */ + public function testDuplicateChoiceLabels() + { + $form = $this->factory->create('choice', null, array( + 'choices' => array('a' => 'A', 'b' => 'B', 'c' => 'A'), + )); + $view = $form->createView(); + + $this->assertEquals(array( + new ChoiceView('a', 'a', 'A'), + new ChoiceView('b', 'b', 'B'), + new ChoiceView('c', 'c', 'A'), + ), $view->vars['choices']); + } + public function testAdjustFullNameForMultipleNonExpanded() { $form = $this->factory->createNamed('name', 'choice', null, array( From 59b782a200e9cd03082a931dd00e45d01cd0e444 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 28 Nov 2015 10:05:13 +0100 Subject: [PATCH 4/4] [ci] Force update of ./phpunit deps --- phpunit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpunit b/phpunit index 3a3b3dd881..78710c1274 100755 --- a/phpunit +++ b/phpunit @@ -11,7 +11,7 @@ */ // Please update when phpunit needs to be reinstalled with fresh deps: -// Cache-Id-Version: 2015-11-18 14:14 UTC +// Cache-Id-Version: 2015-11-28 09:05 UTC use Symfony\Component\Process\ProcessUtils;