Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  namespace core;
  18  
  19  /**
  20   * Unit tests for the xhprof class.
  21   *
  22   * @package   core
  23   * @category  test
  24   * @copyright 2019 Brendan Heywood <brendan@catalyst-au.net>
  25   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  class xhprof_test extends \advanced_testcase {
  28  
  29      public static function setUpBeforeClass(): void {
  30          global $CFG;
  31          require_once($CFG->libdir . '/xhprof/xhprof_moodle.php');
  32      }
  33  
  34      /**
  35       * Data provider for string matches
  36       *
  37       * @return  array
  38       */
  39      public static function profiling_string_matches_provider(): array {
  40          return [
  41              ['/index.php',              '/index.php',           true],
  42              ['/some/dir/index.php',     '/index.php',           false],
  43              ['/course/view.php',        '/course/view.php',     true],
  44              ['/view.php',               '/course/view.php',     false],
  45              ['/mod/forum',              '/mod/forum/*',         false],
  46              ['/mod/forum/',             '/mod/forum/*',         true],
  47              ['/mod/forum/index.php',    '/mod/forum/*',         true],
  48              ['/mod/forum/foo.php',      '/mod/forum/*',         true],
  49              ['/mod/forum/view.php',     '/mod/*/view.php',      true],
  50              ['/mod/one/two/view.php',   '/mod/*/view.php',      true],
  51              ['/view.php',               '*/view.php',           true],
  52              ['/mod/one/two/view.php',   '*/view.php',           true],
  53              ['/foo.php',                '/foo.php,/bar.php',    true],
  54              ['/bar.php',                '/foo.php,/bar.php',    true],
  55              ['/foo/bar.php',            "/foo.php,/bar.php",    false],
  56              ['/foo/bar.php',            "/foo.php,*/bar.php",   true],
  57              ['/foo/bar.php',            "/foo*.php,/bar.php",   true],
  58              ['/foo.php',                "/foo.php\n/bar.php",   true],
  59              ['/bar.php',                "/foo.php\n/bar.php",   true],
  60              ['/foo/bar.php',            "/foo.php\n/bar.php",   false],
  61              ['/foo/bar.php',            "/foo.php\n*/bar.php",  true],
  62              ['/foo/bar.php',            "/foo*.php\n/bar.php",  true],
  63          ];
  64      }
  65  
  66      /**
  67       * Test the matching syntax
  68       *
  69       * @covers ::profiling_string_matches
  70       * @dataProvider profiling_string_matches_provider
  71       * @param   string $string
  72       * @param   string $patterns
  73       * @param   bool   $expected
  74       */
  75      public function test_profiling_string_matches($string, $patterns, $expected) {
  76          $result = profiling_string_matches($string, $patterns);
  77          $this->assertSame($result, $expected);
  78      }
  79  
  80      /**
  81       * Data provider for both the topological sort and the data reduction tests.
  82       *
  83       * @return array
  84       */
  85      public static function run_data_provider(): array {
  86          // This data corresponds to the runs used as example @ MDL-79285.
  87          return [
  88              'sorted_case' => [
  89                  'rundata' => array_flip([
  90                      'A',
  91                      'A==>B',
  92                      'A==>C',
  93                      'A==>__Mustache4',
  94                      'B==>__Mustache1',
  95                      '__Mustache1==>__Mustache2',
  96                      '__Mustache4==>__Mustache2',
  97                      '__Mustache4==>E',
  98                      'E==>F',
  99                      'C==>F',
 100                      '__Mustache2==>F',
 101                      '__Mustache2==>D',
 102                      'D==>__Mustache3',
 103                      '__Mustache3==>F',
 104                  ]),
 105                  'expectations' => [
 106                      'topofirst' => 'A',
 107                      'topolast' => '__Mustache3==>F',
 108                      'topocount' => 14,
 109                      'topoorder' => [
 110                          // Before and after pairs to verify they are ordered.
 111                          ['before' => 'A==>C', 'after' => 'C==>F'],
 112                          ['before' => 'D==>__Mustache3', 'after' => '__Mustache3==>F'],
 113                      ],
 114                      'reducecount' => 8,
 115                      'reduceremoved' => [
 116                          // Elements that will be removed by the reduction.
 117                          '__Mustache1==>__Mustache2',
 118                          '__Mustache4==>__Mustache2',
 119                          '__Mustache2==>F',
 120                          '__Mustache2==>D',
 121                          '__Mustache2==>D',
 122                          '__Mustache3==>F',
 123                      ],
 124                  ],
 125              ],
 126              'unsorted_case' => [
 127                  'rundata' => array_flip([
 128                      'A==>__Mustache4',
 129                      '__Mustache3==>F',
 130                      'A==>B',
 131                      'A==>C',
 132                      'B==>__Mustache1',
 133                      '__Mustache1==>__Mustache2',
 134                      '__Mustache4==>__Mustache2',
 135                      '__Mustache4==>E',
 136                      'E==>F',
 137                      'C==>F',
 138                      '__Mustache2==>F',
 139                      '__Mustache2==>D',
 140                      'D==>__Mustache3',
 141                      'A',
 142                  ]),
 143                  'expectations' => [
 144                      'topofirst' => 'A',
 145                      'topolast' => '__Mustache3==>F',
 146                      'topocount' => 14,
 147                      'topoorder' => [
 148                          // Before and after pairs to verify they are ordered.
 149                          ['before' => 'A==>C', 'after' => 'C==>F'],
 150                          ['before' => 'D==>__Mustache3', 'after' => '__Mustache3==>F'],
 151                      ],
 152                      'reducecount' => 8,
 153                      'reduceremoved' => [
 154                          // Elements that will be removed by the reduction.
 155                          '__Mustache1==>__Mustache2',
 156                          '__Mustache4==>__Mustache2',
 157                          '__Mustache2==>F',
 158                          '__Mustache2==>D',
 159                          '__Mustache2==>D',
 160                          '__Mustache3==>F',
 161                      ],
 162                  ],
 163              ],
 164          ];
 165      }
 166  
 167      /**
 168       * Test that topologically sorting the run data works as expected
 169       *
 170       * @covers \moodle_xhprofrun::xhprof_topo_sort
 171       * @dataProvider run_data_provider
 172       *
 173       * @param array $rundata The run data to be sorted.
 174       * @param array $expectations The expected results.
 175       */
 176      public function test_xhprof_topo_sort(array $rundata, array $expectations) {
 177          // Make sure all the examples in the provider are the same size.
 178          $this->assertSame($expectations['topocount'], count($rundata));
 179  
 180          // Make moodle_xhprofrun::xhprof_topo_sort() accessible.
 181          $reflection = new \ReflectionClass('\moodle_xhprofrun');
 182          $method = $reflection->getMethod('xhprof_topo_sort');
 183          $method->setAccessible(true);
 184          // Sort the data.
 185          $result = $method->invokeArgs(new \moodle_xhprofrun(), [$rundata]);
 186          $this->assertIsArray($result);
 187          $this->assertSame($expectations['topocount'], count($result));
 188          // Convert the array to a list of keys, so we can assert values by position.
 189          $resultkeys = array_keys($result);
 190  
 191          // This is the elements that should be first.
 192          $this->assertSame($expectations['topofirst'], $resultkeys[0]);
 193          // This is the element that should be last.
 194          $this->assertSame($expectations['topolast'], $resultkeys[$expectations['topocount'] - 1]);
 195          // This relative ordering should be respected.
 196          foreach ($expectations['topoorder'] as $order) {
 197              // All the elements in the expectations should be present.
 198              $this->assertArrayHasKey($order['before'], $result);
 199              $this->assertArrayHasKey($order['after'], $result);
 200              // And they should be in the correct relative order.
 201              $this->assertGreaterThan(
 202                  array_search($order['before'], $resultkeys),
 203                  array_search($order['after'], $resultkeys)
 204              );
 205          }
 206  
 207          // Final check, if we sort it again, nothing changes (it's already topologically sorted).
 208          $result2 = $method->invokeArgs(new \moodle_xhprofrun(), [$result]);
 209          $this->assertSame($result, $result2);
 210      }
 211  
 212      /**
 213       * Test that reducing the data complexity works as expected
 214       *
 215       * @covers \moodle_xhprofrun::reduce_run_data
 216       * @dataProvider run_data_provider
 217       *
 218       * @param array $rundata The run data to be reduced.
 219       * @param array $expectations The expected results.
 220       */
 221      public function test_reduce_run_data(array $rundata, array $expectations) {
 222          // Make sure that the expected keys that will be removed are present.
 223          foreach ($expectations['reduceremoved'] as $key) {
 224              $this->assertArrayHasKey($key, $rundata);
 225          }
 226  
 227          // Make moodle_xhprofrun::reduce_run_data() accessible.
 228          $reflection = new \ReflectionClass('\moodle_xhprofrun');
 229          $method = $reflection->getMethod('reduce_run_data');
 230          $method->setAccessible(true);
 231          // Reduce the data.
 232          $result = $method->invokeArgs(new \moodle_xhprofrun(), [$rundata]);
 233          $this->assertIsArray($result);
 234          $this->assertSame($expectations['reducecount'], count($result));
 235          // These have been the removed elements.
 236          foreach ($expectations['reduceremoved'] as $key) {
 237              $this->assertArrayNotHasKey($key, $result);
 238          }
 239  
 240          // Final check, if we reduce it again, nothing changes (it's already reduced).
 241          $result2 = $method->invokeArgs(new \moodle_xhprofrun(), [$result]);
 242          $this->assertSame($result, $result2);
 243      }
 244  }
 245