function ConfigurableLanguageManagerSwitchLinksTest::testSwitchLinksFiberConcurrency

Same name and namespace in other branches
  1. main core/modules/language/tests/src/Unit/ConfigurableLanguageManagerSwitchLinksTest.php \Drupal\Tests\language\Unit\ConfigurableLanguageManagerSwitchLinksTest::testSwitchLinksFiberConcurrency()

Test that change of negotiated languages stays in getLanguageSwitchLinks().

File

core/modules/language/tests/src/Unit/ConfigurableLanguageManagerSwitchLinksTest.php, line 34

Class

ConfigurableLanguageManagerSwitchLinksTest
Tests getting the language switch links works correctly running in fibers.

Namespace

Drupal\Tests\language\Unit

Code

public function testSwitchLinksFiberConcurrency() : void {
  // Mock two languages and a URL per language for use in the language switch
  // links.
  foreach ([
    'de',
    'en',
  ] as $langcode) {
    $languages[$langcode] = $this->createMock('\\Drupal\\Core\\Language\\LanguageInterface');
    $languages[$langcode]->method('getId')
      ->willReturn($langcode);
    // ConfigurableLanguageManager::getLanguageSwitchLinks() changes the
    // negotiated language to the language of each link before checking access
    // to the link's URL. If this is running in a fiber, it is possible that
    // calling ::access() on the URL object can suspend the fiber, if entities
    // are loaded as part of the access check. Simulate this in the mock URL
    // object by forcing the fiber to be suspended in ::access();
    $urls[$langcode] = $this->createMock(Url::class);
    $urls[$langcode]->method('access')
      ->willReturnCallback(function () {
      if (\Fiber::getCurrent() !== NULL) {
        \Fiber::suspend();
      }
      return TRUE;
    });
  }
  // Create mock objects needed to instantiate ConfigurableLanguageManager.
  $negotiationMethod = $this->createMockForIntersectionOfInterfaces([
    LanguageNegotiationMethodInterface::class,
    LanguageSwitcherInterface::class,
  ]);
  $negotiationMethod->method('getLanguageSwitchLinks')
    ->willReturnCallback(function () use ($languages, $urls) {
    $links = [];
    foreach ($languages as $langcode => $language) {
      $links[$langcode] = [
        'language' => $language,
        'url' => $urls[$langcode],
      ];
    }
    return $links;
  });
  $negotiator = $this->createMock(LanguageNegotiatorInterface::class);
  $negotiator->method('getNegotiationMethods')
    ->with(LanguageInterface::TYPE_INTERFACE)
    ->willReturn([
    'language-test-fiber' => [
      'class' => $negotiationMethod::class,
    ],
  ]);
  $negotiator->method('getNegotiationMethodInstance')
    ->with('language-test-fiber')
    ->willReturn($negotiationMethod);
  $defaultLanguage = $this->createMock(LanguageDefault::class);
  $defaultLanguage->method('get')
    ->willReturn($languages['en']);
  $configFactory = $this->createMock(ConfigFactoryInterface::class);
  $configFactory->method('listAll')
    ->willReturn([]);
  $configFactory->method('loadMultiple')
    ->willReturn([]);
  $requestStack = $this->createMock(RequestStack::class);
  $requestStack->method('getCurrentRequest')
    ->willReturn($this->createStub(Request::class));
  // Instantiate the language manager and initialize the negotiator.
  $languageManager = new ConfigurableLanguageManager($defaultLanguage, $configFactory, $this->createStub(ModuleHandlerInterface::class), $this->createStub(LanguageConfigFactoryOverrideInterface::class), $requestStack, $this->createStub(CacheBackendInterface::class));
  $languageManager->setNegotiator($negotiator);
  // Initialize the negotiated languages.
  $originalLanguage = $languageManager->getCurrentLanguage();
  // Simulate ConfigurableLanguageManager::getLanguageSwitchLinks() running in
  // a fiber in parallel with another process running in a fiber. The second
  // fiber just returns the value of the current language, which should not
  // be from the original language when the second fiber is running,
  // regardless of whether the first fiber suspended while checking access on
  // the link URLs.
  $fibers[] = new \Fiber(fn() => $languageManager->getLanguageSwitchLinks(LanguageInterface::TYPE_INTERFACE, $this->createMock(Url::class)));
  $fibers[] = new \Fiber(fn() => $languageManager->getCurrentLanguage()
    ->getId());
  $return = [];
  // Process fibers until all complete.
  do {
    foreach ($fibers as $key => $fiber) {
      if (!$fiber->isStarted()) {
        $fiber->start();
      }
      elseif ($fiber->isSuspended()) {
        $fiber->resume();
      }
      elseif ($fiber->isTerminated()) {
        $return[$key] = $fiber->getReturn();
        unset($fibers[$key]);
      }
    }
  } while (!empty($fibers));
  // Confirm that the switch links are generated correctly from the first
  // fiber.
  $expectedLinks = [
    'de' => [
      'language' => $languages['de'],
      'url' => $urls['de'],
    ],
    'en' => [
      'language' => $languages['en'],
      'url' => $urls['en'],
    ],
  ];
  $this->assertEquals($expectedLinks, $return[0]->links);
  // Confirm that the original language matches current language when the
  // second fiber ran.
  $this->assertSame($originalLanguage->getId(), $return[1]);
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.