From a8c44713c43d450d4a7018c3206ef81f57550d2f Mon Sep 17 00:00:00 2001 From: Matthias Pigulla Date: Wed, 22 Apr 2015 08:26:28 +0200 Subject: [PATCH] [2.6][Translator] Extend, refactor and simplify Translator tests. --- .../Translation/Tests/TranslatorCacheTest.php | 288 +++++++++--------- .../Translation/Tests/TranslatorTest.php | 39 ++- 2 files changed, 180 insertions(+), 147 deletions(-) diff --git a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php index 14631ba910..abe364c736 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorCacheTest.php @@ -11,10 +11,11 @@ namespace Symfony\Component\Translation\Tests; +use Symfony\Component\Config\Resource\ResourceInterface; use Symfony\Component\Translation\Loader\ArrayLoader; +use Symfony\Component\Translation\Loader\LoaderInterface; use Symfony\Component\Translation\Translator; use Symfony\Component\Translation\MessageCatalogue; -use Symfony\Component\Translation\MessageSelector; class TranslatorCacheTest extends \PHPUnit_Framework_TestCase { @@ -51,91 +52,107 @@ class TranslatorCacheTest extends \PHPUnit_Framework_TestCase rmdir($this->tmpDir); } - public function testTransWithoutCaching() + /** + * @dataProvider runForDebugAndProduction + */ + public function testThatACacheIsUsed($debug) { - $translator = $this->getTranslator($this->getLoader()); - $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin')); + $locale = 'any_locale'; + $format = 'some_format'; + $msgid = 'test'; - $this->assertEquals('foo (FR)', $translator->trans('foo')); - $this->assertEquals('bar (EN)', $translator->trans('bar')); - $this->assertEquals('foobar (ES)', $translator->trans('foobar')); - $this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0)); - $this->assertEquals('no translation', $translator->trans('no translation')); - $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); - $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); - $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); - $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); + // Prime the cache + $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator->addLoader($format, new ArrayLoader()); + $translator->addResource($format, array($msgid => 'OK'), $locale); + $translator->trans($msgid); + + // Try again and see we get a valid result whilst no loader can be used + $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator->addLoader($format, $this->createFailingLoader()); + $translator->addResource($format, array($msgid => 'OK'), $locale); + $this->assertEquals('OK', $translator->trans($msgid), '-> caching does not work in '.($debug ? 'debug' : 'production')); } - public function testTransWithCaching() + public function testCatalogueIsReloadedWhenResourcesAreNoLongerFresh() { - // prime the cache - $translator = $this->getTranslator($this->getLoader(), $this->tmpDir); - $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin')); + /* + * The testThatACacheIsUsed() test showed that we don't need the loader as long as the cache + * is fresh. + * + * Now we add a Resource that is never fresh and make sure that the + * cache is discarded (the loader is called twice). + * + * We need to run this for debug=true only because in production the cache + * will never be revalidated. + */ - $this->assertEquals('foo (FR)', $translator->trans('foo')); - $this->assertEquals('bar (EN)', $translator->trans('bar')); - $this->assertEquals('foobar (ES)', $translator->trans('foobar')); - $this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0)); - $this->assertEquals('no translation', $translator->trans('no translation')); - $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); - $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); - $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); - $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); + $locale = 'any_locale'; + $format = 'some_format'; + $msgid = 'test'; - // do it another time as the cache is primed now + $catalogue = new MessageCatalogue($locale, array()); + $catalogue->addResource(new StaleResource()); // better use a helper class than a mock, because it gets serialized in the cache and re-loaded + + /** @var LoaderInterface|\PHPUnit_Framework_MockObject_MockObject $loader */ $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); - $translator = $this->getTranslator($loader, $this->tmpDir); - $translator->setLocale('fr'); - $translator->setFallbackLocales(array('en', 'es', 'pt-PT', 'pt_BR', 'fr.UTF-8', 'sr@latin')); + $loader + ->expects($this->exactly(2)) + ->method('load') + ->will($this->returnValue($catalogue)) + ; - $this->assertEquals('foo (FR)', $translator->trans('foo')); - $this->assertEquals('bar (EN)', $translator->trans('bar')); - $this->assertEquals('foobar (ES)', $translator->trans('foobar')); - $this->assertEquals('choice 0 (EN)', $translator->transChoice('choice', 0)); - $this->assertEquals('no translation', $translator->trans('no translation')); - $this->assertEquals('foobarfoo (PT-PT)', $translator->trans('foobarfoo')); - $this->assertEquals('other choice 1 (PT-BR)', $translator->transChoice('other choice', 1)); - $this->assertEquals('foobarbaz (fr.UTF-8)', $translator->trans('foobarbaz')); - $this->assertEquals('foobarbax (sr@latin)', $translator->trans('foobarbax')); + // 1st pass + $translator = new Translator($locale, null, $this->tmpDir, true); + $translator->addLoader($format, $loader); + $translator->addResource($format, null, $locale); + $translator->trans($msgid); + + // 2nd pass + $translator = new Translator($locale, null, $this->tmpDir, true); + $translator->addLoader($format, $loader); + $translator->addResource($format, null, $locale); + $translator->trans($msgid); } - public function testTransWithCachingWithInvalidLocale() + /** + * @dataProvider runForDebugAndProduction + */ + public function testDifferentTranslatorsForSameLocaleDoNotOverwriteEachOthersCache($debug) { - $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); - $translator = $this->getTranslator($loader, $this->tmpDir, 'Symfony\Component\Translation\Tests\TranslatorWithInvalidLocale'); + /* + * Similar to the previous test. After we used the second translator, make + * sure there's still a useable cache for the first one. + */ - $translator->setLocale('invalid locale'); + $locale = 'any_locale'; + $format = 'some_format'; + $msgid = 'test'; - try { - $translator->trans('foo'); - $this->fail(); - } catch (\InvalidArgumentException $e) { - $this->assertFalse(file_exists($this->tmpDir.'/catalogue.invalid locale.php')); - } - } + // Create a Translator and prime its cache + $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator->addLoader($format, new ArrayLoader()); + $translator->addResource($format, array($msgid => 'OK'), $locale); + $translator->trans($msgid); - public function testLoadCatalogueWithCachingWithInvalidLocale() - { - $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); - $translator = $this->getTranslator($loader, $this->tmpDir, 'Symfony\Component\Translation\Tests\TranslatorWithInvalidLocale'); + // Create another Translator with a different catalogue for the same locale + $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator->addLoader($format, new ArrayLoader()); + $translator->addResource($format, array($msgid => 'FAIL'), $locale); + $translator->trans($msgid); - try { - $translator->proxyLoadCatalogue('invalid locale'); - $this->fail(); - } catch (\InvalidArgumentException $e) { - $this->assertFalse(file_exists($this->tmpDir.'/catalogue.invalid locale.php')); - } + // Now the first translator must still have a useable cache. + $translator = new Translator($locale, null, $this->tmpDir, $debug); + $translator->addLoader($format, $this->createFailingLoader()); + $translator->addResource($format, array($msgid => 'OK'), $locale); + $this->assertEquals('OK', $translator->trans($msgid), '-> the cache was overwritten by another translator instance in '.($debug ? 'debug' : 'production')); } public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales() { /* * Because the cache file contains a catalogue including all of its fallback - * catalogues (either "inlined" in Symfony 2.7 production or "standalone"), - * we must take the active set of fallback locales into consideration when + * catalogues, we must take the set of fallback locales into consideration when * loading a catalogue from the cache. */ $translator = new Translator('a', null, $this->tmpDir); @@ -161,6 +178,54 @@ class TranslatorCacheTest extends \PHPUnit_Framework_TestCase $this->assertEquals('bar', $translator->trans('bar')); } + public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardlessOfCaching() + { + /* + * As a safeguard against potential BC breaks, make sure that primary and fallback + * catalogues (reachable via getFallbackCatalogue()) always contain the full set of + * messages provided by the loader. This must also be the case when these catalogues + * are (internally) read from a cache. + * + * Optimizations inside the translator must not change this behaviour. + */ + + /* + * Create a translator that loads two catalogues for two different locales. + * The catalogues contain distinct sets of messages. + */ + $translator = new Translator('a', null, $this->tmpDir); + $translator->setFallbackLocales(array('b')); + + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foo (a)'), 'a'); + $translator->addResource('array', array('foo' => 'foo (b)'), 'b'); + $translator->addResource('array', array('bar' => 'bar (b)'), 'b'); + + $catalogue = $translator->getCatalogue('a'); + $this->assertFalse($catalogue->defines('bar')); // Sure, the "a" catalogue does not contain that message. + + $fallback = $catalogue->getFallbackCatalogue(); + $this->assertTrue($fallback->defines('foo')); // "foo" is present in "a" and "b" + + /* + * Now, repeat the same test. + * Behind the scenes, the cache is used. But that should not matter, right? + */ + $translator = new Translator('a', null, $this->tmpDir); + $translator->setFallbackLocales(array('b')); + + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('foo' => 'foo (a)'), 'a'); + $translator->addResource('array', array('foo' => 'foo (b)'), 'b'); + $translator->addResource('array', array('bar' => 'bar (b)'), 'b'); + + $catalogue = $translator->getCatalogue('a'); + $this->assertFalse($catalogue->defines('bar')); + + $fallback = $catalogue->getFallbackCatalogue(); + $this->assertTrue($fallback->defines('foo')); + } + public function testRefreshCacheWhenResourcesAreNoLongerFresh() { $resource = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface'); @@ -197,93 +262,38 @@ class TranslatorCacheTest extends \PHPUnit_Framework_TestCase return $catalogue; } - protected function getLoader() + public function runForDebugAndProduction() + { + return array(array(true), array(false)); + } + + /** + * @return LoaderInterface + */ + private function createFailingLoader() { $loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface'); $loader - ->expects($this->at(0)) - ->method('load') - ->will($this->returnValue($this->getCatalogue('fr', array( - 'foo' => 'foo (FR)', - )))) - ; - $loader - ->expects($this->at(1)) - ->method('load') - ->will($this->returnValue($this->getCatalogue('en', array( - 'foo' => 'foo (EN)', - 'bar' => 'bar (EN)', - 'choice' => '{0} choice 0 (EN)|{1} choice 1 (EN)|]1,Inf] choice inf (EN)', - )))) - ; - $loader - ->expects($this->at(2)) - ->method('load') - ->will($this->returnValue($this->getCatalogue('es', array( - 'foobar' => 'foobar (ES)', - )))) - ; - $loader - ->expects($this->at(3)) - ->method('load') - ->will($this->returnValue($this->getCatalogue('pt-PT', array( - 'foobarfoo' => 'foobarfoo (PT-PT)', - )))) - ; - $loader - ->expects($this->at(4)) - ->method('load') - ->will($this->returnValue($this->getCatalogue('pt_BR', array( - 'other choice' => '{0} other choice 0 (PT-BR)|{1} other choice 1 (PT-BR)|]1,Inf] other choice inf (PT-BR)', - )))) - ; - $loader - ->expects($this->at(5)) - ->method('load') - ->will($this->returnValue($this->getCatalogue('fr.UTF-8', array( - 'foobarbaz' => 'foobarbaz (fr.UTF-8)', - )))) - ; - $loader - ->expects($this->at(6)) - ->method('load') - ->will($this->returnValue($this->getCatalogue('sr@latin', array( - 'foobarbax' => 'foobarbax (sr@latin)', - )))) - ; + ->expects($this->never()) + ->method('load'); return $loader; } - - public function getTranslator($loader, $cacheDir = null, $translatorClass = '\Symfony\Component\Translation\Translator') - { - $translator = new $translatorClass('fr', new MessageSelector(), $cacheDir); - - $translator->addLoader('loader', $loader); - $translator->addResource('loader', 'foo', 'fr'); - $translator->addResource('loader', 'foo', 'en'); - $translator->addResource('loader', 'foo', 'es'); - $translator->addResource('loader', 'foo', 'pt-PT'); // European Portuguese - $translator->addResource('loader', 'foo', 'pt_BR'); // Brazilian Portuguese - $translator->addResource('loader', 'foo', 'fr.UTF-8'); - $translator->addResource('loader', 'foo', 'sr@latin'); // Latin Serbian - - return $translator; - } } -class TranslatorWithInvalidLocale extends Translator +class StaleResource implements ResourceInterface { - /** - * {@inheritdoc} - */ - public function setLocale($locale) + public function isFresh($timestamp) { - $this->locale = $locale; + return false; } - public function proxyLoadCatalogue($locale) + public function getResource() { - $this->loadCatalogue($locale); + } + + public function __toString() + { + return ''; } } diff --git a/src/Symfony/Component/Translation/Tests/TranslatorTest.php b/src/Symfony/Component/Translation/Tests/TranslatorTest.php index e60d8faff4..3ee9be019d 100644 --- a/src/Symfony/Component/Translation/Tests/TranslatorTest.php +++ b/src/Symfony/Component/Translation/Tests/TranslatorTest.php @@ -85,6 +85,30 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase $this->assertEquals(new MessageCatalogue('fr'), $translator->getCatalogue('fr')); } + public function testGetCatalogueReturnsConsolidatedCatalogue() + { + /* + * This will be useful once we refactor so that different domains will be loaded lazily (on-demand). + * In that case, getCatalogue() will probably have to load all missing domains in order to return + * one complete catalogue. + */ + + $locale = 'whatever'; + $translator = new Translator($locale); + $translator->addLoader('loader-a', new ArrayLoader()); + $translator->addLoader('loader-b', new ArrayLoader()); + $translator->addResource('loader-a', array('foo' => 'foofoo'), $locale, 'domain-a'); + $translator->addResource('loader-b', array('bar' => 'foobar'), $locale, 'domain-b'); + + /* + * Test that we get a single catalogue comprising messages + * from different loaders and different domains + */ + $catalogue = $translator->getCatalogue($locale); + $this->assertTrue($catalogue->defines('foo', 'domain-a')); + $this->assertTrue($catalogue->defines('bar', 'domain-b')); + } + public function testSetFallbackLocales() { $translator = new Translator('en'); @@ -136,12 +160,11 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase public function testTransWithFallbackLocale() { $translator = new Translator('fr_FR'); - $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foofoo'), 'en_US'); - $translator->addResource('array', array('bar' => 'foobar'), 'en'); - $translator->setFallbackLocales(array('en')); + $translator->addLoader('array', new ArrayLoader()); + $translator->addResource('array', array('bar' => 'foobar'), 'en'); + $this->assertEquals('foobar', $translator->trans('bar')); } @@ -280,12 +303,12 @@ class TranslatorTest extends \PHPUnit_Framework_TestCase */ public function testTransValidLocale($locale) { - $translator = new Translator('en', new MessageSelector()); + $translator = new Translator($locale, new MessageSelector()); $translator->addLoader('array', new ArrayLoader()); - $translator->addResource('array', array('foo' => 'foofoo'), 'en'); + $translator->addResource('array', array('test' => 'OK'), $locale); - $translator->trans('foo', array(), '', $locale); - // no assertion. this method just asserts that no exception is thrown + $this->assertEquals('OK', $translator->trans('test')); + $this->assertEquals('OK', $translator->trans('test', array(), null, $locale)); } /**