[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/behat/classes/ -> behat_config_manager.php (source)

   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  /**
  18   * Utils to set Behat config
  19   *
  20   * @package    core
  21   * @category   test
  22   * @copyright  2012 David Monllaó
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  defined('MOODLE_INTERNAL') || die();
  27  
  28  require_once (__DIR__ . '/../lib.php');
  29  require_once (__DIR__ . '/behat_command.php');
  30  require_once (__DIR__ . '/../../testing/classes/tests_finder.php');
  31  
  32  /**
  33   * Behat configuration manager
  34   *
  35   * Creates/updates Behat config files getting tests
  36   * and steps from Moodle codebase
  37   *
  38   * @package    core
  39   * @category   test
  40   * @copyright  2012 David Monllaó
  41   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  42   */
  43  class behat_config_manager {
  44  
  45      /**
  46       * @var bool Keep track of the automatic profile conversion. So we can notify user.
  47       */
  48      public static $autoprofileconversion = false;
  49  
  50      /**
  51       * Updates a config file
  52       *
  53       * The tests runner and the steps definitions list uses different
  54       * config files to avoid problems with concurrent executions.
  55       *
  56       * The steps definitions list can be filtered by component so it's
  57       * behat.yml is different from the $CFG->dirroot one.
  58       *
  59       * @param  string $component Restricts the obtained steps definitions to the specified component
  60       * @param  string $testsrunner If the config file will be used to run tests
  61       * @param  string $tags features files including tags.
  62       * @return void
  63       */
  64      public static function update_config_file($component = '', $testsrunner = true, $tags = '') {
  65          global $CFG;
  66  
  67          // Behat must have a separate behat.yml to have access to the whole set of features and steps definitions.
  68          if ($testsrunner === true) {
  69              $configfilepath = behat_command::get_behat_dir() . '/behat.yml';
  70          } else {
  71              // Alternative for steps definitions filtering, one for each user.
  72              $configfilepath = self::get_steps_list_config_filepath();
  73          }
  74  
  75          // Gets all the components with features.
  76          $features = array();
  77          $components = tests_finder::get_components_with_tests('features');
  78          if ($components) {
  79              foreach ($components as $componentname => $path) {
  80                  $path = self::clean_path($path) . self::get_behat_tests_path();
  81                  if (empty($featurespaths[$path]) && file_exists($path)) {
  82  
  83                      // Standarizes separator (some dirs. comes with OS-dependant separator).
  84                      $uniquekey = str_replace('\\', '/', $path);
  85                      $featurespaths[$uniquekey] = $path;
  86                  }
  87              }
  88              foreach ($featurespaths as $path) {
  89                  $additional = glob("$path/*.feature");
  90                  $features = array_merge($features, $additional);
  91              }
  92          }
  93  
  94          // Optionally include features from additional directories.
  95          if (!empty($CFG->behat_additionalfeatures)) {
  96              $features = array_merge($features, array_map("realpath", $CFG->behat_additionalfeatures));
  97          }
  98  
  99          // Gets all the components with steps definitions.
 100          $stepsdefinitions = array();
 101          $steps = self::get_components_steps_definitions();
 102          if ($steps) {
 103              foreach ($steps as $key => $filepath) {
 104                  if ($component == '' || $component === $key) {
 105                      $stepsdefinitions[$key] = $filepath;
 106                  }
 107              }
 108          }
 109  
 110          // We don't want the deprecated steps definitions here.
 111          if (!$testsrunner) {
 112              unset($stepsdefinitions['behat_deprecated']);
 113          }
 114  
 115          // Behat config file specifing the main context class,
 116          // the required Behat extensions and Moodle test wwwroot.
 117          $contents = self::get_config_file_contents(self::get_features_with_tags($features, $tags), $stepsdefinitions);
 118  
 119          // Stores the file.
 120          if (!file_put_contents($configfilepath, $contents)) {
 121              behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $configfilepath . ' can not be created');
 122          }
 123  
 124      }
 125  
 126      /**
 127       * Search feature files for set of tags.
 128       *
 129       * @param array $features set of feature files.
 130       * @param string $tags list of tags (currently support && only.)
 131       * @return array filtered list of feature files with tags.
 132       */
 133      public static function get_features_with_tags($features, $tags) {
 134          if (empty($tags)) {
 135              return $features;
 136          }
 137          $newfeaturelist = array();
 138          // Split tags in and and or.
 139          $tags = explode('&&', $tags);
 140          $andtags = array();
 141          $ortags = array();
 142          foreach ($tags as $tag) {
 143              // Explode all tags seperated by , and add it to ortags.
 144              $ortags = array_merge($ortags, explode(',', $tag));
 145              // And tags will be the first one before comma(,).
 146              $andtags[] = preg_replace('/,.*/', '', $tag);
 147          }
 148  
 149          foreach ($features as $featurefile) {
 150              $contents = file_get_contents($featurefile);
 151              $includefeature = true;
 152              foreach ($andtags as $tag) {
 153                  // If negitive tag, then ensure it don't exist.
 154                  if (strpos($tag, '~') !== false) {
 155                      $tag = substr($tag, 1);
 156                      if ($contents && strpos($contents, $tag) !== false) {
 157                          $includefeature = false;
 158                          break;
 159                      }
 160                  } else if ($contents && strpos($contents, $tag) === false) {
 161                      $includefeature = false;
 162                      break;
 163                  }
 164              }
 165  
 166              // If feature not included then check or tags.
 167              if (!$includefeature && !empty($ortags)) {
 168                  foreach ($ortags as $tag) {
 169                      if ($contents && (strpos($tag, '~') === false) && (strpos($contents, $tag) !== false)) {
 170                          $includefeature = true;
 171                          break;
 172                      }
 173                  }
 174              }
 175  
 176              if ($includefeature) {
 177                  $newfeaturelist[] = $featurefile;
 178              }
 179          }
 180          return $newfeaturelist;
 181      }
 182  
 183      /**
 184       * Gets the list of Moodle steps definitions
 185       *
 186       * Class name as a key and the filepath as value
 187       *
 188       * Externalized from update_config_file() to use
 189       * it from the steps definitions web interface
 190       *
 191       * @return array
 192       */
 193      public static function get_components_steps_definitions() {
 194  
 195          $components = tests_finder::get_components_with_tests('stepsdefinitions');
 196          if (!$components) {
 197              return false;
 198          }
 199  
 200          $stepsdefinitions = array();
 201          foreach ($components as $componentname => $componentpath) {
 202              $componentpath = self::clean_path($componentpath);
 203  
 204              if (!file_exists($componentpath . self::get_behat_tests_path())) {
 205                  continue;
 206              }
 207              $diriterator = new DirectoryIterator($componentpath . self::get_behat_tests_path());
 208              $regite = new RegexIterator($diriterator, '|behat_.*\.php$|');
 209  
 210              // All behat_*.php inside behat_config_manager::get_behat_tests_path() are added as steps definitions files.
 211              foreach ($regite as $file) {
 212                  $key = $file->getBasename('.php');
 213                  $stepsdefinitions[$key] = $file->getPathname();
 214              }
 215          }
 216  
 217          return $stepsdefinitions;
 218      }
 219  
 220      /**
 221       * Returns the behat config file path used by the steps definition list
 222       *
 223       * @return string
 224       */
 225      public static function get_steps_list_config_filepath() {
 226          global $USER;
 227  
 228          // We don't cygwin-it as it is called using exec() which uses cmd.exe.
 229          $userdir = behat_command::get_behat_dir() . '/users/' . $USER->id;
 230          make_writable_directory($userdir);
 231  
 232          return $userdir . '/behat.yml';
 233      }
 234  
 235      /**
 236       * Returns the behat config file path used by the behat cli command.
 237       *
 238       * @param int $runprocess Runprocess.
 239       * @return string
 240       */
 241      public static function get_behat_cli_config_filepath($runprocess = 0) {
 242          global $CFG;
 243  
 244          if ($runprocess) {
 245              if (isset($CFG->behat_parallel_run[$runprocess - 1 ]['behat_dataroot'])) {
 246                  $command = $CFG->behat_parallel_run[$runprocess - 1]['behat_dataroot'];
 247              } else {
 248                  $command = $CFG->behat_dataroot . $runprocess;
 249              }
 250          } else {
 251              $command = $CFG->behat_dataroot;
 252          }
 253          $command .= DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'behat.yml';
 254  
 255          // Cygwin uses linux-style directory separators.
 256          if (testing_is_cygwin()) {
 257              $command = str_replace('\\', '/', $command);
 258          }
 259  
 260          return $command;
 261      }
 262  
 263      /**
 264       * Returns the path to the parallel run file which specifies if parallel test environment is enabled
 265       * and how many parallel runs to execute.
 266       *
 267       * @param int $runprocess run process for which behat dir is returned.
 268       * @return string
 269       */
 270      public final static function get_parallel_test_file_path($runprocess = 0) {
 271          return behat_command::get_behat_dir($runprocess) . '/parallel_environment_enabled.txt';
 272      }
 273  
 274      /**
 275       * Returns number of parallel runs for which site is initialised.
 276       *
 277       * @param int $runprocess run process for which behat dir is returned.
 278       * @return int
 279       */
 280      public final static function get_parallel_test_runs($runprocess = 0) {
 281  
 282          $parallelrun = 0;
 283          // Get parallel run info from first file and last file.
 284          $parallelrunconfigfile = self::get_parallel_test_file_path($runprocess);
 285          if (file_exists($parallelrunconfigfile)) {
 286              if ($parallel = file_get_contents($parallelrunconfigfile)) {
 287                  $parallelrun = (int) $parallel;
 288              }
 289          }
 290  
 291          return $parallelrun;
 292      }
 293  
 294      /**
 295       * Drops parallel site links.
 296       *
 297       * @return bool true on success else false.
 298       */
 299      public final static function drop_parallel_site_links() {
 300          global $CFG;
 301  
 302          // Get parallel test runs from first run.
 303          $parallelrun = self::get_parallel_test_runs(1);
 304  
 305          if (empty($parallelrun)) {
 306              return false;
 307          }
 308  
 309          // If parallel run then remove links and original file.
 310          clearstatcache();
 311          for ($i = 1; $i <= $parallelrun; $i++) {
 312              // Don't delete links for specified sites, as they should be accessible.
 313              if (!empty($CFG->behat_parallel_run['behat_wwwroot'][$i - 1]['behat_wwwroot'])) {
 314                  continue;
 315              }
 316              $link = $CFG->dirroot . '/' . BEHAT_PARALLEL_SITE_NAME . $i;
 317              if (file_exists($link) && is_link($link)) {
 318                  @unlink($link);
 319              }
 320          }
 321          return true;
 322      }
 323  
 324      /**
 325       * Create parallel site links.
 326       *
 327       * @param int $fromrun first run
 328       * @param int $torun last run.
 329       * @return bool true for sucess, else false.
 330       */
 331      public final static function create_parallel_site_links($fromrun, $torun) {
 332          global $CFG;
 333  
 334          // Create site symlink if necessary.
 335          clearstatcache();
 336          for ($i = $fromrun; $i <= $torun; $i++) {
 337              // Don't create links for specified sites, as they should be accessible.
 338              if (!empty($CFG->behat_parallel_run['behat_wwwroot'][$i - 1]['behat_wwwroot'])) {
 339                  continue;
 340              }
 341              $link = $CFG->dirroot.'/'.BEHAT_PARALLEL_SITE_NAME.$i;
 342              clearstatcache();
 343              if (file_exists($link)) {
 344                  if (!is_link($link) || !is_dir($link)) {
 345                      echo "File exists at link location ($link) but is not a link or directory!" . PHP_EOL;
 346                      return false;
 347                  }
 348              } else if (!symlink($CFG->dirroot, $link)) {
 349                  // Try create link in case it's not already present.
 350                  echo "Unable to create behat site symlink ($link)" . PHP_EOL;
 351                  return false;
 352              }
 353          }
 354          return true;
 355      }
 356  
 357      /**
 358       * Behat config file specifing the main context class,
 359       * the required Behat extensions and Moodle test wwwroot.
 360       *
 361       * @param array $features The system feature files
 362       * @param array $stepsdefinitions The system steps definitions
 363       * @return string
 364       */
 365      protected static function get_config_file_contents($features, $stepsdefinitions) {
 366          global $CFG;
 367  
 368          // We require here when we are sure behat dependencies are available.
 369          require_once($CFG->dirroot . '/vendor/autoload.php');
 370  
 371          $selenium2wdhost = array('wd_host' => 'http://localhost:4444/wd/hub');
 372  
 373          $parallelruns = self::get_parallel_test_runs();
 374          // If parallel run, then only divide features.
 375          if (!empty($CFG->behatrunprocess) && !empty($parallelruns)) {
 376              // Attempt to split into weighted buckets using timing information, if available.
 377              if ($alloc = self::profile_guided_allocate($features, max(1, $parallelruns), $CFG->behatrunprocess)) {
 378                  $features = $alloc;
 379              } else {
 380                  // Divide the list of feature files amongst the parallel runners.
 381                  srand(crc32(floor(time() / 3600 / 24) . var_export($features, true)));
 382                  shuffle($features);
 383                  // Pull out the features for just this worker.
 384                  if (count($features)) {
 385                      $features = array_chunk($features, ceil(count($features) / max(1, $parallelruns)));
 386                      // Check if there is any feature file for this process.
 387                      if (!empty($features[$CFG->behatrunprocess - 1])) {
 388                          $features = $features[$CFG->behatrunprocess - 1];
 389                      } else {
 390                          $features = null;
 391                      }
 392                  }
 393              }
 394              // Set proper selenium2 wd_host if defined.
 395              if (!empty($CFG->behat_parallel_run[$CFG->behatrunprocess - 1]['wd_host'])) {
 396                  $selenium2wdhost = array('wd_host' => $CFG->behat_parallel_run[$CFG->behatrunprocess - 1]['wd_host']);
 397              }
 398          }
 399  
 400          // It is possible that it has no value as we don't require a full behat setup to list the step definitions.
 401          if (empty($CFG->behat_wwwroot)) {
 402              $CFG->behat_wwwroot = 'http://itwillnotbeused.com';
 403          }
 404  
 405          // Comments use black color, so failure path is not visible. Using color other then black/white is safer.
 406          // https://github.com/Behat/Behat/pull/628.
 407          $config = array(
 408              'default' => array(
 409                  'formatters' => array(
 410                      'moodle_progress' => array(
 411                          'output_styles' => array(
 412                              'comment' => array('magenta'))
 413                          )
 414                  ),
 415                  'suites' => array(
 416                      'default' => array(
 417                          'paths' => $features,
 418                          'contexts' => array_keys($stepsdefinitions)
 419                      )
 420                  ),
 421                  'extensions' => array(
 422                      'Behat\MinkExtension' => array(
 423                          'base_url' => $CFG->behat_wwwroot,
 424                          'goutte' => null,
 425                          'selenium2' => $selenium2wdhost
 426                      ),
 427                      'Moodle\BehatExtension' => array(
 428                          'moodledirroot' => $CFG->dirroot,
 429                          'steps_definitions' => $stepsdefinitions
 430                      )
 431                  )
 432              )
 433          );
 434  
 435          // In case user defined overrides respect them over our default ones.
 436          if (!empty($CFG->behat_config)) {
 437              foreach ($CFG->behat_config as $profile => $values) {
 438                  $config = self::merge_config($config, self::merge_behat_config($profile, $values));
 439              }
 440          }
 441          // Check for Moodle custom ones.
 442          if (!empty($CFG->behat_profiles) && is_array($CFG->behat_profiles)) {
 443              foreach ($CFG->behat_profiles as $profile => $values) {
 444                  $config = self::merge_config($config, self::get_behat_profile($profile, $values));
 445              }
 446          }
 447  
 448          return Symfony\Component\Yaml\Yaml::dump($config, 10, 2);
 449      }
 450  
 451      /**
 452       * Parse $CFG->behat_config and return the array with required config structure for behat.yml
 453       *
 454       * @param string $profile profile name
 455       * @param array $values values for profile
 456       * @return array
 457       */
 458      protected static function merge_behat_config($profile, $values) {
 459          // Only add profile which are compatible with Behat 3.x
 460          // Just check if any of Bheat 2.5 config is set. Not checking for 3.x as it might have some other configs
 461          // Like : rerun_cache etc.
 462          if (!isset($values['filters']['tags']) && !isset($values['extensions']['Behat\MinkExtension\Extension'])) {
 463              return array($profile => $values);
 464          }
 465  
 466          // Parse 2.5 format and get related values.
 467          $oldconfigvalues = array();
 468          if (isset($values['extensions']['Behat\MinkExtension\Extension'])) {
 469              $extensionvalues = $values['extensions']['Behat\MinkExtension\Extension'];
 470              if (isset($extensionvalues['selenium2']['browser'])) {
 471                  $oldconfigvalues['browser'] = $extensionvalues['selenium2']['browser'];
 472              }
 473              if (isset($extensionvalues['selenium2']['wd_host'])) {
 474                  $oldconfigvalues['wd_host'] = $extensionvalues['selenium2']['wd_host'];
 475              }
 476              if (isset($extensionvalues['capabilities'])) {
 477                  $oldconfigvalues['capabilities'] = $extensionvalues['capabilities'];
 478              }
 479          }
 480  
 481          if (isset($values['filters']['tags'])) {
 482              $oldconfigvalues['tags'] = $values['filters']['tags'];
 483          }
 484  
 485          if (!empty($oldconfigvalues)) {
 486              self::$autoprofileconversion = true;
 487              return self::get_behat_profile($profile, $oldconfigvalues);
 488          }
 489  
 490          // If nothing set above then return empty array.
 491          return array();
 492      }
 493  
 494      /**
 495       * Parse $CFG->behat_profile and return the array with required config structure for behat.yml.
 496       *
 497       * $CFG->behat_profiles = array(
 498       *     'profile' = array(
 499       *         'browser' => 'firefox',
 500       *         'tags' => '@javascript',
 501       *         'wd_host' => 'http://127.0.0.1:4444/wd/hub',
 502       *         'capabilities' => array(
 503       *             'platform' => 'Linux',
 504       *             'version' => 44
 505       *         )
 506       *     )
 507       * );
 508       *
 509       * @param string $profile profile name
 510       * @param array $values values for profile.
 511       * @return array
 512       */
 513      protected static function get_behat_profile($profile, $values) {
 514          // Values should be an array.
 515          if (!is_array($values)) {
 516              return array();
 517          }
 518  
 519          // Check suite values.
 520          $behatprofilesuites = array();
 521          // Fill tags information.
 522          if (isset($values['tags'])) {
 523              $behatprofilesuites = array(
 524                  'suites' => array(
 525                      'default' => array(
 526                          'filters' => array(
 527                              'tags' => $values['tags'],
 528                          )
 529                      )
 530                  )
 531              );
 532          }
 533  
 534          // Selenium2 config values.
 535          $behatprofileextension = array();
 536          $seleniumconfig = array();
 537          if (isset($values['browser'])) {
 538              $seleniumconfig['browser'] = $values['browser'];
 539          }
 540          if (isset($values['wd_host'])) {
 541              $seleniumconfig['wd_host'] = $values['wd_host'];
 542          }
 543          if (isset($values['capabilities'])) {
 544              $seleniumconfig['capabilities'] = $values['capabilities'];
 545          }
 546          if (!empty($seleniumconfig)) {
 547              $behatprofileextension = array(
 548                  'extensions' => array(
 549                      'Behat\MinkExtension' => array(
 550                          'selenium2' => $seleniumconfig,
 551                      )
 552                  )
 553              );
 554          }
 555  
 556          return array($profile => array_merge($behatprofilesuites, $behatprofileextension));
 557      }
 558  
 559      /**
 560       * Attempt to split feature list into fairish buckets using timing information, if available.
 561       * Simply add each one to lightest buckets until all files allocated.
 562       * PGA = Profile Guided Allocation. I made it up just now.
 563       * CAUTION: workers must agree on allocation, do not be random anywhere!
 564       *
 565       * @param array $features Behat feature files array
 566       * @param int $nbuckets Number of buckets to divide into
 567       * @param int $instance Index number of this instance
 568       * @return array Feature files array, sorted into allocations
 569       */
 570      protected static function profile_guided_allocate($features, $nbuckets, $instance) {
 571  
 572          $behattimingfile = defined('BEHAT_FEATURE_TIMING_FILE') &&
 573              @filesize(BEHAT_FEATURE_TIMING_FILE) ? BEHAT_FEATURE_TIMING_FILE : false;
 574  
 575          if (!$behattimingfile || !$behattimingdata = @json_decode(file_get_contents($behattimingfile), true)) {
 576              // No data available, fall back to relying on steps data.
 577              $stepfile = "";
 578              if (defined('BEHAT_FEATURE_STEP_FILE') && BEHAT_FEATURE_STEP_FILE) {
 579                  $stepfile = BEHAT_FEATURE_STEP_FILE;
 580              }
 581              // We should never get this. But in case we can't do this then fall back on simple splitting.
 582              if (empty($stepfile) || !$behattimingdata = @json_decode(file_get_contents($stepfile), true)) {
 583                  return false;
 584              }
 585          }
 586  
 587          arsort($behattimingdata); // Ensure most expensive is first.
 588  
 589          $realroot = realpath(__DIR__.'/../../../').'/';
 590          $defaultweight = array_sum($behattimingdata) / count($behattimingdata);
 591          $weights = array_fill(0, $nbuckets, 0);
 592          $buckets = array_fill(0, $nbuckets, array());
 593          $totalweight = 0;
 594  
 595          // Re-key the features list to match timing data.
 596          foreach ($features as $k => $file) {
 597              $key = str_replace($realroot, '', $file);
 598              $features[$key] = $file;
 599              unset($features[$k]);
 600              if (!isset($behattimingdata[$key])) {
 601                  $behattimingdata[$key] = $defaultweight;
 602              }
 603          }
 604  
 605          // Sort features by known weights; largest ones should be allocated first.
 606          $behattimingorder = array();
 607          foreach ($features as $key => $file) {
 608              $behattimingorder[$key] = $behattimingdata[$key];
 609          }
 610          arsort($behattimingorder);
 611  
 612          // Finally, add each feature one by one to the lightest bucket.
 613          foreach ($behattimingorder as $key => $weight) {
 614              $file = $features[$key];
 615              $lightbucket = array_search(min($weights), $weights);
 616              $weights[$lightbucket] += $weight;
 617              $buckets[$lightbucket][] = $file;
 618              $totalweight += $weight;
 619          }
 620  
 621          if ($totalweight && !defined('BEHAT_DISABLE_HISTOGRAM') && $instance == $nbuckets) {
 622              echo "Bucket weightings:\n";
 623              foreach ($weights as $k => $weight) {
 624                  echo $k + 1 . ": " . str_repeat('*', 70 * $nbuckets * $weight / $totalweight) . PHP_EOL;
 625              }
 626          }
 627  
 628          // Return the features for this worker.
 629          return $buckets[$instance - 1];
 630      }
 631  
 632      /**
 633       * Overrides default config with local config values
 634       *
 635       * array_merge does not merge completely the array's values
 636       *
 637       * @param mixed $config The node of the default config
 638       * @param mixed $localconfig The node of the local config
 639       * @return mixed The merge result
 640       */
 641      protected static function merge_config($config, $localconfig) {
 642  
 643          if (!is_array($config) && !is_array($localconfig)) {
 644              return $localconfig;
 645          }
 646  
 647          // Local overrides also deeper default values.
 648          if (is_array($config) && !is_array($localconfig)) {
 649              return $localconfig;
 650          }
 651  
 652          foreach ($localconfig as $key => $value) {
 653  
 654              // If defaults are not as deep as local values let locals override.
 655              if (!is_array($config)) {
 656                  unset($config);
 657              }
 658  
 659              // Add the param if it doesn't exists or merge branches.
 660              if (empty($config[$key])) {
 661                  $config[$key] = $value;
 662              } else {
 663                  $config[$key] = self::merge_config($config[$key], $localconfig[$key]);
 664              }
 665          }
 666  
 667          return $config;
 668      }
 669  
 670      /**
 671       * Cleans the path returned by get_components_with_tests() to standarize it
 672       *
 673       * @see tests_finder::get_all_directories_with_tests() it returns the path including /tests/
 674       * @param string $path
 675       * @return string The string without the last /tests part
 676       */
 677      protected final static function clean_path($path) {
 678  
 679          $path = rtrim($path, DIRECTORY_SEPARATOR);
 680  
 681          $parttoremove = DIRECTORY_SEPARATOR . 'tests';
 682  
 683          $substr = substr($path, strlen($path) - strlen($parttoremove));
 684          if ($substr == $parttoremove) {
 685              $path = substr($path, 0, strlen($path) - strlen($parttoremove));
 686          }
 687  
 688          return rtrim($path, DIRECTORY_SEPARATOR);
 689      }
 690  
 691      /**
 692       * The relative path where components stores their behat tests
 693       *
 694       * @return string
 695       */
 696      protected final static function get_behat_tests_path() {
 697          return DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'behat';
 698      }
 699  
 700  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1