LoggingTest.php 10.7 KB
Newer Older
1 2
<?php

3
namespace Drupal\KernelTests\Core\Database;
4

5
use Drupal\Core\Database\Log;
6 7 8
use Drupal\Core\Database\Database;

/**
9 10
 * Tests the query logging facility.
 *
11 12
 * @coversDefaultClass \Drupal\Core\Database\Log
 *
13
 * @group Database
14 15 16 17
 */
class LoggingTest extends DatabaseTestBase {

  /**
18
   * Tests that we can log the existence of a query.
19
   */
20
  public function testEnableLogging() {
21
    Database::startLog('testing');
22

23 24
    $this->connection->query('SELECT name FROM {test} WHERE age > :age', [':age' => 25])->fetchCol();
    $this->connection->query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchCol();
25

26
    // Trigger a call that does not have file in the backtrace.
27 28
    call_user_func_array([Database::getConnection(), 'query'], ['SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo']])->fetchCol();

29 30
    $queries = Database::getLog('testing', 'default');

31
    $this->assertEqual(count($queries), 3, 'Correct number of queries recorded.');
32 33

    foreach ($queries as $query) {
34
      $this->assertEqual($query['caller']['function'], __FUNCTION__, 'Correct function in query log.');
35 36 37 38
    }
  }

  /**
39
   * Tests that we can run two logs in parallel.
40
   */
41
  public function testEnableMultiLogging() {
42 43
    Database::startLog('testing1');

44
    $this->connection->query('SELECT name FROM {test} WHERE age > :age', [':age' => 25])->fetchCol();
45 46 47

    Database::startLog('testing2');

48
    $this->connection->query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchCol();
49 50 51 52

    $queries1 = Database::getLog('testing1');
    $queries2 = Database::getLog('testing2');

53 54
    $this->assertEqual(count($queries1), 2, 'Correct number of queries recorded for log 1.');
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for log 2.');
55 56 57
  }

  /**
58
   * Tests logging queries against multiple targets on the same connection.
59
   */
60
  public function testEnableTargetLogging() {
61
    // Clone the primary credentials to a replica connection and to another fake
62 63
    // connection.
    $connection_info = Database::getConnectionInfo('default');
64
    Database::addConnectionInfo('default', 'replica', $connection_info['default']);
65 66 67

    Database::startLog('testing1');

68
    $this->connection->query('SELECT name FROM {test} WHERE age > :age', [':age' => 25])->fetchCol();
69

70
    Database::getConnection('replica')->query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchCol();
71 72 73

    $queries1 = Database::getLog('testing1');

74 75
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
76
    $this->assertEqual($queries1[1]['target'], 'replica', 'Second query used replica target.');
77 78 79
  }

  /**
80
   * Tests that logs to separate targets use the same connection properly.
81 82 83 84 85
   *
   * This test is identical to the one above, except that it doesn't create
   * a fake target so the query should fall back to running on the default
   * target.
   */
86
  public function testEnableTargetLoggingNoTarget() {
87 88
    Database::startLog('testing1');

89
    $this->connection->query('SELECT name FROM {test} WHERE age > :age', [':age' => 25])->fetchCol();
90 91 92

    // We use "fake" here as a target because any non-existent target will do.
    // However, because all of the tests in this class share a single page
93
    // request there is likely to be a target of "replica" from one of the other
94 95
    // unit tests, so we use a target here that we know with absolute certainty
    // does not exist.
96
    Database::getConnection('fake')->query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchCol();
97 98 99

    $queries1 = Database::getLog('testing1');

100 101 102
    $this->assertEqual(count($queries1), 2, 'Recorded queries from all targets.');
    $this->assertEqual($queries1[0]['target'], 'default', 'First query used default target.');
    $this->assertEqual($queries1[1]['target'], 'default', 'Second query used default target as fallback.');
103 104 105
  }

  /**
106
   * Tests that we can log queries separately on different connections.
107
   */
108
  public function testEnableMultiConnectionLogging() {
109
    // Clone the primary credentials to a fake connection.
110 111 112 113 114 115 116
    // That both connections point to the same physical database is irrelevant.
    $connection_info = Database::getConnectionInfo('default');
    Database::addConnectionInfo('test2', 'default', $connection_info['default']);

    Database::startLog('testing1');
    Database::startLog('testing1', 'test2');

117
    $this->connection->query('SELECT name FROM {test} WHERE age > :age', [':age' => 25])->fetchCol();
118

119
    $old_key = Database::setActiveConnection('test2');
120

121
    Database::getConnection('replica')->query('SELECT age FROM {test} WHERE name = :name', [':name' => 'Ringo'])->fetchCol();
122

123
    Database::setActiveConnection($old_key);
124 125 126 127

    $queries1 = Database::getLog('testing1');
    $queries2 = Database::getLog('testing1', 'test2');

128 129
    $this->assertEqual(count($queries1), 1, 'Correct number of queries recorded for first connection.');
    $this->assertEqual(count($queries2), 1, 'Correct number of queries recorded for second connection.');
130
  }
131

132 133 134
  /**
   * Tests that getLog with a wrong key return an empty array.
   */
135
  public function testGetLoggingWrongKey() {
136 137 138 139 140
    $result = Database::getLog('wrong');

    $this->assertEqual($result, [], 'The function getLog with a wrong key returns an empty array.');
  }

141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
  /**
   * Tests that a log called by a custom database driver returns proper caller.
   *
   * @param string $driver_namespace
   *   The driver namespace to be tested.
   * @param string $stack
   *   A test debug_backtrace stack.
   * @param array $expected_entry
   *   The expected stack entry.
   *
   * @covers ::findCaller
   *
   * @dataProvider providerContribDriverLog
   */
  public function testContribDriverLog($driver_namespace, $stack, array $expected_entry) {
    $mock_builder = $this->getMockBuilder(Log::class);
    $log = $mock_builder
      ->setMethods(['getDriverNamespace', 'getDebugBacktrace'])
      ->getMock();
    $log->expects($this->any())
      ->method('getDriverNamespace')
      ->will($this->returnValue($driver_namespace));
    $log->expects($this->once())
      ->method('getDebugBacktrace')
      ->will($this->returnValue($stack));

    $result = $log->findCaller($stack);
    $this->assertEquals($expected_entry, $result);
  }

  /**
   * Provides data for the testContribDriverLog test.
   *
   * @return array[]
   *   A associative array of simple arrays, each having the following elements:
   *   - the contrib driver PHP namespace
   *   - a test debug_backtrace stack
   *   - the stack entry expected to be returned.
   *
   * @see ::testContribDriverLog()
   */
  public function providerContribDriverLog() {
    $stack = [
      [
        'file' => '/var/www/core/lib/Drupal/Core/Database/Log.php',
        'line' => 125,
        'function' => 'findCaller',
        'class' => 'Drupal\\Core\\Database\\Log',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => '/var/www/libraries/drudbal/lib/Statement.php',
        'line' => 264,
        'function' => 'log',
        'class' => 'Drupal\\Core\\Database\\Log',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => '/var/www/libraries/drudbal/lib/Connection.php',
        'line' => 213,
        'function' => 'execute',
        'class' => 'Drupal\\Driver\\Database\\dbal\\Statement',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => '/var/www/core/tests/Drupal/KernelTests/Core/Database/LoggingTest.php',
        'line' => 23,
        'function' => 'query',
        'class' => 'Drupal\\Driver\\Database\\dbal\\Connection',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => '/var/www/vendor/phpunit/phpunit/src/Framework/TestCase.php',
        'line' => 1154,
        'function' => 'testEnableLogging',
        'class' => 'Drupal\\KernelTests\\Core\\Database\\LoggingTest',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => '/var/www/vendor/phpunit/phpunit/src/Framework/TestCase.php',
        'line' => 842,
        'function' => 'runTest',
        'class' => 'PHPUnit\\Framework\\TestCase',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => '/var/www/vendor/phpunit/phpunit/src/Framework/TestResult.php',
        'line' => 693,
        'function' => 'runBare',
        'class' => 'PHPUnit\\Framework\\TestCase',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => '/var/www/vendor/phpunit/phpunit/src/Framework/TestCase.php',
        'line' => 796,
        'function' => 'run',
        'class' => 'PHPUnit\\Framework\\TestResult',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => 'Standard input code',
        'line' => 57,
        'function' => 'run',
        'class' => 'PHPUnit\\Framework\\TestCase',
        'object' => 'test',
        'type' => '->',
        'args' => [
          0 => 'test',
        ],
      ],
      [
        'file' => 'Standard input code',
        'line' => 111,
        'function' => '__phpunit_run_isolated_test',
        'args' => [
          0 => 'test',
        ],
      ],
    ];

    return [
      // Test that if the driver namespace is in the stack trace, the first
      // non-database entry is returned.
      'contrib driver namespace' => [
        'Drupal\\Driver\\Database\\dbal',
        $stack,
        [
          'class' => 'Drupal\\KernelTests\\Core\\Database\\LoggingTest',
          'function' => 'testEnableLogging',
          'file' => '/var/www/core/tests/Drupal/KernelTests/Core/Database/LoggingTest.php',
          'line' => 23,
          'type' => '->',
          'args' => [
            0 => 'test',
          ],
        ],
      ],
      // Extreme case, should not happen at normal runtime - if the driver
      // namespace is not in the stack trace, the first entry to a method
      // in core database namespace is returned.
      'missing driver namespace' => [
        'Drupal\\Driver\\Database\\fake',
        $stack,
        [
          'class' => 'Drupal\\Driver\\Database\\dbal\\Statement',
          'function' => 'execute',
          'file' => '/var/www/libraries/drudbal/lib/Statement.php',
          'line' => 264,
          'type' => '->',
          'args' => [
            0 => 'test',
          ],
        ],
      ],
    ];
  }

330
}