CommandExpectations.php 7.37 KB
Newer Older
1 2 3 4
<?php

namespace MongoDB\Tests\SpecTests;

5 6
use ArrayIterator;
use LogicException;
7 8 9
use MongoDB\Driver\Monitoring\CommandFailedEvent;
use MongoDB\Driver\Monitoring\CommandStartedEvent;
use MongoDB\Driver\Monitoring\CommandSubscriber;
10
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
11
use MultipleIterator;
12
use function count;
13
use function in_array;
14 15 16
use function key;
use function MongoDB\Driver\Monitoring\addSubscriber;
use function MongoDB\Driver\Monitoring\removeSubscriber;
17 18 19 20 21 22

/**
 * Spec test CommandStartedEvent expectations.
 */
class CommandExpectations implements CommandSubscriber
{
23
    /** @var array */
24
    private $actualEvents = [];
25 26

    /** @var array */
27
    private $expectedEvents = [];
28 29

    /** @var boolean */
30
    private $ignoreCommandFailed = false;
31 32

    /** @var boolean */
33
    private $ignoreCommandStarted = false;
34 35

    /** @var boolean */
36
    private $ignoreCommandSucceeded = false;
37 38

    /** @var boolean */
39
    private $ignoreExtraEvents = false;
40

41 42 43
    /** @var string[] */
    private $ignoredCommandNames = [];

44
    private function __construct(array $events)
45
    {
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
        foreach ($events as $event) {
            switch (key($event)) {
                case 'command_failed_event':
                    $this->expectedEvents[] = [$event->command_failed_event, CommandFailedEvent::class];
                    break;

                case 'command_started_event':
                    $this->expectedEvents[] = [$event->command_started_event, CommandStartedEvent::class];
                    break;

                case 'command_succeeded_event':
                    $this->expectedEvents[] = [$event->command_succeeded_event, CommandSucceededEvent::class];
                    break;

                default:
                    throw new LogicException('Unsupported event type: ' . key($event));
62 63
            }
        }
64 65
    }

66 67 68 69 70 71 72 73 74
    public static function fromChangeStreams(array $expectedEvents)
    {
        $o = new self($expectedEvents);

        $o->ignoreCommandFailed = true;
        $o->ignoreCommandSucceeded = true;
        /* Change Streams spec tests do not include getMore commands in the
         * list of expected events, so ignore any observed events beyond the
         * number that are expected. */
Jeremy Mikola's avatar
Jeremy Mikola committed
75
        $o->ignoreExtraEvents = true;
76 77 78 79

        return $o;
    }

80 81 82 83 84 85 86 87 88 89
    public static function fromClientSideEncryption(array $expectedEvents)
    {
        $o = new self($expectedEvents);

        $o->ignoreCommandFailed = true;
        $o->ignoreCommandSucceeded = true;

        return $o;
    }

90 91 92 93 94
    public static function fromCommandMonitoring(array $expectedEvents)
    {
        return new self($expectedEvents);
    }

95 96 97 98 99 100 101 102 103 104
    public static function fromCrud(array $expectedEvents)
    {
        $o = new self($expectedEvents);

        $o->ignoreCommandFailed = true;
        $o->ignoreCommandSucceeded = true;

        return $o;
    }

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
    public static function fromRetryableReads(array $expectedEvents)
    {
        $o = new self($expectedEvents);

        $o->ignoreCommandFailed = true;
        $o->ignoreCommandSucceeded = true;

        /* Retryable read spec tests don't include extra commands, e.g. the
         * killCursors command issued when a change stream is garbage collected.
         * We ignore any extra events for that reason. \*/
        $o->ignoreExtraEvents = true;

        return $o;
    }

120 121 122 123 124 125
    public static function fromTransactions(array $expectedEvents)
    {
        $o = new self($expectedEvents);

        $o->ignoreCommandFailed = true;
        $o->ignoreCommandSucceeded = true;
126

127 128 129 130 131 132 133 134
        /* Ignore the buildInfo and getParameter commands as they are used to
         * check for the availability of configureFailPoint and are not expected
         * to be called by any spec tests.
         * configureFailPoint needs to be ignored as the targetedFailPoint
         * operation will be caught by command monitoring and is also not
         * present in the expected commands in spec tests. */
        $o->ignoredCommandNames = ['buildInfo', 'getParameter', 'configureFailPoint'];

135 136 137 138 139 140 141 142 143 144
        return $o;
    }

    /**
     * Not used.
     *
     * @see https://www.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandfailed.php
     */
    public function commandFailed(CommandFailedEvent $event)
    {
145
        if ($this->ignoreCommandFailed || $this->isEventIgnored($event)) {
146 147 148 149
            return;
        }

        $this->actualEvents[] = $event;
150 151 152 153 154 155 156 157 158
    }

    /**
     * Tracks outgoing commands for spec test APM assertions.
     *
     * @see https://www.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandstarted.php
     */
    public function commandStarted(CommandStartedEvent $event)
    {
159
        if ($this->ignoreCommandStarted || $this->isEventIgnored($event)) {
160 161 162 163
            return;
        }

        $this->actualEvents[] = $event;
164 165 166 167 168 169 170 171 172
    }

    /**
     * Not used.
     *
     * @see https://www.php.net/manual/en/mongodb-driver-monitoring-commandsubscriber.commandsucceeded.php
     */
    public function commandSucceeded(CommandSucceededEvent $event)
    {
173
        if ($this->ignoreCommandSucceeded || $this->isEventIgnored($event)) {
174 175 176 177
            return;
        }

        $this->actualEvents[] = $event;
178 179 180 181 182 183 184
    }

    /**
     * Start command monitoring.
     */
    public function startMonitoring()
    {
185
        addSubscriber($this);
186 187 188 189 190 191 192
    }

    /**
     * Stop command monitoring.
     */
    public function stopMonitoring()
    {
193
        removeSubscriber($this);
194 195 196 197 198 199 200 201 202 203
    }

    /**
     * Assert that the command expectations match the monitored events.
     *
     * @param FunctionalTestCase $test    Test instance
     * @param Context            $context Execution context
     */
    public function assert(FunctionalTestCase $test, Context $context)
    {
204
        $test->assertCount(count($this->expectedEvents), $this->actualEvents);
205 206

        $mi = new MultipleIterator(MultipleIterator::MIT_NEED_ANY);
207
        $mi->attachIterator(new ArrayIterator($this->expectedEvents));
208
        $mi->attachIterator(new ArrayIterator($this->actualEvents));
209 210

        foreach ($mi as $events) {
211 212 213 214
            list($expectedEventAndClass, $actualEvent) = $events;
            list($expectedEvent, $expectedClass) = $expectedEventAndClass;

            $test->assertInstanceOf($expectedClass, $actualEvent);
215 216 217 218 219 220 221 222 223 224

            if (isset($expectedEvent->command_name)) {
                $test->assertSame($expectedEvent->command_name, $actualEvent->getCommandName());
            }

            if (isset($expectedEvent->database_name)) {
                $test->assertSame($expectedEvent->database_name, $actualEvent->getDatabaseName());
            }

            if (isset($expectedEvent->command)) {
225
                $test->assertInstanceOf(CommandStartedEvent::class, $actualEvent);
226 227
                $expectedCommand = $expectedEvent->command;
                $context->replaceCommandSessionPlaceholder($expectedCommand);
228
                $test->assertCommandMatches($expectedCommand, $actualEvent->getCommand());
229 230
            }

231 232 233 234 235
            if (isset($expectedEvent->reply)) {
                $test->assertInstanceOf(CommandSucceededEvent::class, $actualEvent);
                $test->assertCommandReplyMatches($expectedEvent->reply, $actualEvent->getReply());
            }
        }
236
    }
237 238 239 240 241 242

    private function isEventIgnored($event)
    {
        return ($this->ignoreExtraEvents && count($this->actualEvents) === count($this->expectedEvents))
            || in_array($event->getCommandName(), $this->ignoredCommandNames);
    }
243
}