Commit 01a79a31 authored by ramil g's avatar ramil g Committed by Youri van Koppen
Browse files

Issue #2978490 by ramil g, MegaChriz, andypost, joelpittet, andileco: Optimize...

Issue #2978490 by ramil g, MegaChriz, andypost, joelpittet, andileco: Optimize Feeds queue: put reference to feed on queue task instead of the whole feed object.
parent d24b3031
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -40,7 +40,11 @@ class FeedsQueueBatch extends FeedsBatchBase {
    // Queue all operations now.
    foreach ($this->operations as $operation) {
      $this->queueFactory->get('feeds_feed_refresh:' . $this->feed->bundle())
        ->createItem([$this->feed, $operation['stage'], $operation['params']]);
        ->createItem([
          $this->feed->id(),
          $operation['stage'],
          $operation['params'],
        ]);
    }
  }

+24 −5
Original line number Diff line number Diff line
@@ -23,13 +23,19 @@ class FeedRefresh extends FeedQueueWorkerBase {
  public function processItem($data) {
    [$feed, $stage, $params] = $data;

    if (!$feed instanceof FeedInterface) {
    // In earlier versions of Feeds, a full Feed object was put on the queue.
    // In such case, check if the feed still exists. Or else abort.
    if ($feed instanceof FeedInterface && !$this->feedExists($feed)) {
      return;
    }

    // Check if the feed still exists.
    if (!$this->feedExists($feed)) {
      // The feed in question has been deleted. Abort.
    // Load the feed if an ID is given. This is the default.
    if (is_numeric($feed)) {
      $feed = $this->feedLoad($feed);
    }

    // If we have no feed by now, abort the process.
    if (!$feed instanceof FeedInterface) {
      return;
    }

@@ -46,11 +52,24 @@ class FeedRefresh extends FeedQueueWorkerBase {
    return \Drupal::service('class_resolver')->getInstanceFromDefinition(FeedsQueueExecutable::class);
  }

  /**
   * Loads a feed entity.
   *
   * @param int $fid
   *   The feed entity ID to load.
   *
   * @return \Drupal\feeds\FeedInterface|null
   *   The feed entity or NULL otherwise.
   */
  protected function feedLoad($fid) {
    return $this->entityTypeManager->getStorage('feeds_feed')->load($fid);
  }

  /**
   * Returns if a feed entity still exists or not.
   *
   * @param \Drupal\feeds\FeedInterface $feed
   *   The feed entity to check for existance in the database.
   *   The feed entity to check for existence in the database.
   *
   * @return bool
   *   True if the feed still exists, false otherwise.
+3 −1
Original line number Diff line number Diff line
@@ -164,10 +164,12 @@ class CronTest extends FeedsBrowserTestBase {
    // Now delete the feed type.
    $feed_type->delete();

    // And run cron.
    // And run cron. The cron run should not fail. No import should happen.
    $this->cronRun();
    $this->assertNodeCount(0);

    // Assert that an exception gets thrown upon trying to start an import.
    $feed = $this->reloadEntity($feed);
    $this->expectException(EntityStorageException::class);
    $this->expectExceptionMessage('The feed type "foo" for feed 1 no longer exists.');
    $feed->startCronImport();
+70 −0
Original line number Diff line number Diff line
@@ -2,6 +2,8 @@

namespace Drupal\Tests\feeds\Functional;

use Drupal\feeds\FeedsExecutableInterface;

/**
 * Tests behavior involving the queue.
 *
@@ -68,6 +70,36 @@ class QueueTest extends FeedsBrowserTestBase {
    $this->assertNodeCount(6);
  }

  /**
   * Tests running an import when a queue task contains a feed object.
   *
   * In Feeds 8.x-3.0-beta2 and lower, when an import was queued, a complete
   * feed object was set on the queue - instead of only a reference to the feed.
   *
   * This test exists for backwards compatibility. People that are updating
   * the Feeds module can still have import tasks on their queue.
   */
  public function testQueueWithFullFeedObject() {
    $feed_type = $this->createFeedType();

    // Create a feed with a source.
    $feed = $this->createFeed($feed_type->id(), [
      'source' => $this->resourcesUrl() . '/rss/googlenewstz.rss2',
    ]);

    // Manually put task on the queue in the way it was done in Feeds
    // 8.x-3.0-beta2 and lower.
    $this->container->get('queue')
      ->get('feeds_feed_refresh:' . $feed->bundle())
      ->createItem([$feed, FeedsExecutableInterface::BEGIN, []]);

    // Run cron to import.
    $this->cronRun();

    // Assert that 6 nodes have been created.
    $this->assertNodeCount(6);
  }

  /**
   * Tests if a feed is removed from the queue when the feed gets deleted.
   */
@@ -101,4 +133,42 @@ class QueueTest extends FeedsBrowserTestBase {
    $this->assertQueueItemCount(0, 'feeds_feed_refresh:' . $feed_type->id());
  }

  /**
   * Tests feed deletion with a full feed object on the queue.
   *
   * In Feeds 8.x-3.0-beta2 and lower, when an import was queued, a complete
   * feed object was set on the queue - instead of only a reference to the feed.
   *
   * This test exists for backwards compatibility. People that are updating
   * the Feeds module can still have import tasks on their queue.
   */
  public function testQueueAfterDeletingFeedWithFullFeedObject() {
    $feed_type = $this->createFeedType();

    // Create a feed with a source.
    $feed = $this->createFeed($feed_type->id(), [
      'source' => $this->resourcesUrl() . '/rss/googlenewstz.rss2',
    ]);

    // Manually put task on the queue in the way it was done in Feeds
    // 8.x-3.0-beta2 and lower.
    $this->container->get('queue')
      ->get('feeds_feed_refresh:' . $feed->bundle())
      ->createItem([$feed, FeedsExecutableInterface::BEGIN, []]);

    // Assert that the item exists on the queue.
    $this->assertQueueItemCount(1, 'feeds_feed_refresh:' . $feed_type->id());

    // Now delete the feed.
    $feed->delete();

    // Run cron to import.
    $this->cronRun();

    // Assert that no nodes have been created.
    $this->assertNodeCount(0);
    // Assert that the queue is empty.
    $this->assertQueueItemCount(0, 'feeds_feed_refresh:' . $feed_type->id());
  }

}
+107 −24
Original line number Diff line number Diff line
@@ -20,7 +20,6 @@ use Drupal\feeds\Result\ParserResult;
use Drupal\feeds\StateInterface;
use Drupal\Tests\feeds\Unit\FeedsUnitTestCase;
use Prophecy\Argument;
use RuntimeException;
use Symfony\Component\EventDispatcher\EventDispatcher;

/**
@@ -69,7 +68,7 @@ class FeedRefreshTest extends FeedsUnitTestCase {
    $executable->setStringTranslation($this->getStringTranslationStub());

    $this->plugin = $this->getMockBuilder(FeedRefresh::class)
      ->setMethods(['feedExists', 'getExecutable'])
      ->setMethods(['feedExists', 'feedLoad', 'getExecutable'])
      ->setConstructorArgs([
        [],
        'feeds_feed_refresh',
@@ -80,28 +79,105 @@ class FeedRefreshTest extends FeedsUnitTestCase {
        $entity_type_manager,
      ])
      ->getMock();
    $this->plugin->expects($this->any())
      ->method('feedExists')
      ->will($this->returnValue(TRUE));
    $this->plugin->expects($this->any())
      ->method('getExecutable')
      ->will($this->returnValue($executable));

    $connection = $this->prophesize(Connection::class);
    $connection->query(Argument::type('string'), Argument::type('array'))->willReturn($this->createMock(StatementInterface::class));

    $this->feed = $this->getMockFeed();

    // For all tests, the feed ID is 1.
    $this->feed->expects($this->any())
      ->method('id')
      ->will($this->returnValue(1));

    // Make sure a CleanState object is returned when asking for state object in
    // the clean phase.
    $this->feed->expects($this->any())
      ->method('getState')
      ->with(StateInterface::CLEAN)
      ->will($this->returnValue(new CleanState(1, $connection->reveal())));

    $this->plugin->expects($this->any())
      ->method('getExecutable')
      ->will($this->returnValue($executable));
  }

  /**
   * Sets the expected return value for feedLoad().
   */
  protected function setExpectedFeed($expected) {
    $this->plugin->expects($this->any())
      ->method('feedLoad')
      ->will($this->returnValue($expected));
  }

  /**
   * Tests processing an empty task.
   */
  public function testEmptyTask() {
    // Process should be aborted early on.
    $this->plugin->expects($this->never())
      ->method('getExecutable');

    $this->plugin->processItem(NULL);
  }

  /**
   * Tests initiating an import.
   */
  public function testBeginStage() {
    $this->plugin->processItem(NULL);
    $this->setExpectedFeed($this->feed);
    $this->plugin->processItem([
      $this->feed->id(),
      FeedsExecutableInterface::BEGIN,
      [],
    ]);
  }

  /**
   * Tests with a non-existing feed.
   */
  public function testBeginStageWithNonExistingFeed() {
    $this->setExpectedFeed(NULL);

    // Process should be aborted early on.
    $this->plugin->expects($this->never())
      ->method('getExecutable');

    $this->plugin->processItem([
      $this->feed->id(),
      FeedsExecutableInterface::BEGIN,
      [],
    ]);
  }

  /**
   * Tests initiating an import with a full feed object.
   */
  public function testBeginStageWithFullFeedObject() {
    $this->plugin->expects($this->atLeastOnce())
      ->method('feedExists')
      ->will($this->returnValue(TRUE));

    $this->plugin->processItem([
      $this->feed,
      FeedsExecutableInterface::BEGIN,
      [],
    ]);
  }

  /**
   * Tests initiating an import with a full feed object that no longer exists.
   */
  public function testBeginStageWithNonExistingFullFeedObject() {
    $this->plugin->expects($this->atLeastOnce())
      ->method('feedExists')
      ->will($this->returnValue(FALSE));

    // Process should be aborted early on.
    $this->plugin->expects($this->never())
      ->method('getExecutable');

    $this->plugin->processItem([
      $this->feed,
      FeedsExecutableInterface::BEGIN,
@@ -113,11 +189,12 @@ class FeedRefreshTest extends FeedsUnitTestCase {
   * Tests that an import cannot start when the feed is locked.
   */
  public function testLockException() {
    $this->setExpectedFeed($this->feed);
    $this->feed->expects($this->once())
      ->method('lock')
      ->will($this->throwException(new LockException()));
    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::BEGIN,
      [],
    ]);
@@ -127,13 +204,14 @@ class FeedRefreshTest extends FeedsUnitTestCase {
   * Tests that a fetch event is dispatched when initiating an import.
   */
  public function testExceptionOnFetchEvent() {
    $this->setExpectedFeed($this->feed);
    $this->dispatcher->addListener(FeedsEvents::FETCH, function ($parse_event) {
      throw new RuntimeException();
      throw new \RuntimeException();
    });

    $this->expectException(RuntimeException::class);
    $this->expectException(\RuntimeException::class);
    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::FETCH,
      [],
    ]);
@@ -143,6 +221,7 @@ class FeedRefreshTest extends FeedsUnitTestCase {
   * Tests the parse stage of an import.
   */
  public function testParseStage() {
    $this->setExpectedFeed($this->feed);
    $this->dispatcher->addListener(FeedsEvents::PARSE, function ($parse_event) {
      $parser_result = new ParserResult();
      $parser_result->addItem(new DynamicItem());
@@ -152,7 +231,7 @@ class FeedRefreshTest extends FeedsUnitTestCase {
    $fetcher_result = new FetcherResult('');

    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::PARSE, [
        'fetcher_result' => $fetcher_result,
      ],
@@ -166,13 +245,14 @@ class FeedRefreshTest extends FeedsUnitTestCase {
   * dispatched.
   */
  public function testExceptionOnParseEvent() {
    $this->setExpectedFeed($this->feed);
    $this->dispatcher->addListener(FeedsEvents::PARSE, function ($parse_event) {
      throw new RuntimeException();
      throw new \RuntimeException();
    });

    $this->expectException(RuntimeException::class);
    $this->expectException(\RuntimeException::class);
    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::PARSE, [
        'fetcher_result' => new FetcherResult(''),
      ],
@@ -183,8 +263,9 @@ class FeedRefreshTest extends FeedsUnitTestCase {
   * Tests the process stage of an import.
   */
  public function testProcessStage() {
    $this->setExpectedFeed($this->feed);
    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::PROCESS, [
        'item' => new DynamicItem(),
      ],
@@ -198,13 +279,14 @@ class FeedRefreshTest extends FeedsUnitTestCase {
   * dispatched.
   */
  public function testExceptionOnProcessEvent() {
    $this->setExpectedFeed($this->feed);
    $this->dispatcher->addListener(FeedsEvents::PROCESS, function ($parse_event) {
      throw new RuntimeException();
      throw new \RuntimeException();
    });

    $this->expectException(RuntimeException::class);
    $this->expectException(\RuntimeException::class);
    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::PROCESS, [
        'item' => new DynamicItem(),
      ],
@@ -215,8 +297,9 @@ class FeedRefreshTest extends FeedsUnitTestCase {
   * Tests the final stage of an import.
   */
  public function testFinalPass() {
    $this->setExpectedFeed($this->feed);
    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::FINISH, [
        'fetcher_result' => new FetcherResult(''),
      ],
@@ -227,7 +310,7 @@ class FeedRefreshTest extends FeedsUnitTestCase {
      ->will($this->returnValue(StateInterface::BATCH_COMPLETE));

    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::FINISH, [
        'fetcher_result' => new FetcherResult(''),
      ],
@@ -236,7 +319,7 @@ class FeedRefreshTest extends FeedsUnitTestCase {
      ->method('progressFetching')
      ->will($this->returnValue(StateInterface::BATCH_COMPLETE));
    $this->plugin->processItem([
      $this->feed,
      $this->feed->id(),
      FeedsExecutableInterface::FINISH, [
        'fetcher_result' => new FetcherResult(''),
      ],