MDL-58428 behat: Check parent themes for Behat override steps.
authorAndrew Nicols <andrew@nicols.co.uk>
Thu, 17 Jan 2019 05:57:12 +0000 (13:57 +0800)
committerMathew May <mathewm@hotmail.co.nz>
Tue, 26 Feb 2019 08:21:23 +0000 (16:21 +0800)
Behat will now look at the current themes' parents for Behat ovveride steps.
If found we will use the steps replacing the Moodle core steps.

admin/tool/behat/tests/fixtures/core/behat_test_context_1.php
admin/tool/behat/tests/fixtures/core/behat_test_context_2.php
lib/behat/classes/behat_config_util.php
lib/behat/classes/behat_context_helper.php
lib/behat/core_behat_file_helper.php [moved from lib/behat/behat_files.php with 97% similarity]
repository/tests/behat/behat_filepicker.php
repository/upload/tests/behat/behat_repository_upload.php
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_filepicker.php
theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_repository_upload.php
theme/bootstrapbase/tests/behat/theme_bootstrapbase_behat_file_helper.php [moved from theme/bootstrapbase/tests/behat/behat_theme_bootstrapbase_behat_files.php with 91% similarity]

index 01c0396..c8819d6 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 /**
  * Test context 1
index 0dbdbeb..9b423ae 100644 (file)
@@ -24,7 +24,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../../lib/behat/behat_base.php');
+require_once(__DIR__ . '/../../../../../../lib/behat/behat_base.php');
 
 /**
  * Test context 2
index 6738ef0..dc5ecbe 100644 (file)
@@ -55,11 +55,6 @@ class behat_config_util {
      */
     private $themecontexts;
 
-    /**
-     * @var array list of all contexts in theme suite.
-     */
-    private $themesuitecontexts;
-
     /**
      * @var array list of overridden theme contexts.
      */
@@ -314,13 +309,16 @@ class behat_config_util {
 
         $this->contexts = array();
         foreach ($components as $componentname => $componentpath) {
+            if (false !== strpos($componentname, 'theme_')) {
+                continue;
+            }
             $componentpath = self::clean_path($componentpath);
 
             if (!file_exists($componentpath . self::get_behat_tests_path())) {
                 continue;
             }
             $diriterator = new DirectoryIterator($componentpath . self::get_behat_tests_path());
-            $regite = new RegexIterator($diriterator, '|behat_.*\.php$|');
+            $regite = new RegexIterator($diriterator, '|^behat_.*\.php$|');
 
             // All behat_*.php inside self::get_behat_tests_path() are added as steps definitions files.
             foreach ($regite as $file) {
@@ -493,24 +491,23 @@ class behat_config_util {
 
         $suites = $this->get_behat_suites($parallelruns, $currentrun);
 
-        $overriddenthemescontexts = $this->get_overridden_theme_contexts();
-        if (!empty($overriddenthemescontexts)) {
-            $allcontexts = array_merge($this->contexts, $overriddenthemescontexts);
-        } else {
-            $allcontexts = $this->contexts;
-        }
-
-        // Remove selectors from step definitions.
-        $themes = $this->get_list_of_themes();
         $selectortypes = ['named_partial', 'named_exact'];
-        foreach ($themes as $theme) {
+        $allpaths = [];
+        foreach (array_keys($suites) as $theme) {
+            // Remove selectors from step definitions.
             foreach ($selectortypes as $selectortype) {
                 // Don't include selector classes.
                 $selectorclass = self::get_behat_theme_selector_override_classname($theme, $selectortype);
-                if (isset($allcontexts[$selectorclass])) {
-                    unset($allcontexts[$selectorclass]);
+                if (isset($suites[$theme]['contexts'][$selectorclass])) {
+                    unset($suites[$theme]['contexts'][$selectorclass]);
                 }
             }
+
+            // Get a list of all step definition paths.
+            $allpaths = array_merge($allpaths, $suites[$theme]['contexts']);
+
+            // Convert the contexts array to a list of names only.
+            $suites[$theme]['contexts'] = array_keys($suites[$theme]['contexts']);
         }
 
         // Comments use black color, so failure path is not visible. Using color other then black/white is safer.
@@ -532,7 +529,7 @@ class behat_config_util {
                     ),
                     'Moodle\BehatExtension' => array(
                         'moodledirroot' => $CFG->dirroot,
-                        'steps_definitions' => $allcontexts,
+                        'steps_definitions' => $allpaths,
                     )
                 )
             )
@@ -1128,9 +1125,8 @@ class behat_config_util {
 
         // Create list of theme suite features and contexts.
         foreach ($themes as $theme) {
-            // Get theme features.
+            // Get theme features and contexts.
             $themefeatures[$theme] = $this->get_behat_features_for_theme($theme);
-
             $themecontexts[$theme] = $this->get_behat_contexts_for_theme($theme);
         }
 
@@ -1141,20 +1137,6 @@ class behat_config_util {
             }
         }
 
-        // Remove list of theme contexts form other suite contexts, as suite don't require other theme specific contexts.
-        foreach ($themecontexts as $themename => $themecontext) {
-            if (!empty($themecontext['contexts'])) {
-                foreach ($themecontext['contexts'] as $contextkey => $contextpath) {
-                    // Remove theme specific contexts from other themes.
-                    foreach ($themes as $currenttheme) {
-                        if (($currenttheme != $themename) && isset($themecontexts[$currenttheme]['suitecontexts'][$contextkey])) {
-                            unset($themecontexts[$currenttheme]['suitecontexts'][$contextkey]);
-                        }
-                    }
-                }
-            }
-        }
-
         // Set suite for each theme.
         $suites = array();
         foreach ($themes as $theme) {
@@ -1186,12 +1168,12 @@ class behat_config_util {
                 $suitename = $theme;
             }
 
-            // Add suite no matter what. If there is no feature in suite then it will just exist successfully with no
-            // scenarios. But if we don't set this then the user has to know which run doesn't have suite and which run do.
+            // Add suite no matter what. If there is no feature in suite then it will just exist successfully with no scenarios.
+            // But if we don't set this then the user has to know which run doesn't have suite and which run do.
             $suites = array_merge($suites, array(
                 $suitename => array(
                     'paths'    => array_values($themesuitefeatures),
-                    'contexts' => array_keys($themecontexts[$theme]['suitecontexts']),
+                    'contexts' => $themecontexts[$theme],
                 )
             ));
         }
@@ -1246,7 +1228,7 @@ class behat_config_util {
     /**
      * Return theme directory.
      *
-     * @param string $themename
+     * @param string $themename name of theme
      * @return string theme directory
      */
     protected function get_theme_test_directory($themename) {
@@ -1339,7 +1321,7 @@ class behat_config_util {
 
         $tests = array();
         $testtypes = array(
-            'contexts' => '|behat_.*\.php$|',
+            'contexts' => '|^behat_.*\.php$|',
             'features' => '|.*\.feature$|',
         );
 
@@ -1427,91 +1409,72 @@ class behat_config_util {
         return $retval;
     }
 
-    /**
-     * Return list of contexts overridden by themes.
-     *
-     * @return array.
-     */
-    protected function get_overridden_theme_contexts() {
-        if (empty($this->overriddenthemescontexts)) {
-            $this->overriddenthemescontexts = array();
-        }
-
-        return $this->overriddenthemescontexts;
-    }
-
     /**
      * Return list of behat contexts for theme and update $this->stepdefinitions list.
      *
      * @param string $theme theme name.
-     * @return array list($themecontexts, $themesuitecontexts)
+     * @return  List of contexts
      */
-    protected function get_behat_contexts_for_theme($theme) {
-
+    protected function get_behat_contexts_for_theme($theme) : array {
         // If we already have this list then just return. This will not change by run.
-        if (!empty($this->themecontexts[$theme]) && !empty($this->themesuitecontexts)) {
-            return array(
-                'contexts' => $this->themecontexts[$theme],
-                'suitecontexts' => $this->themesuitecontexts[$theme],
-            );
+        if (!empty($this->themecontexts[$theme])) {
+            return $this->themecontexts[$theme];
         }
 
-        if (empty($this->overriddenthemescontexts)) {
-            $this->overriddenthemescontexts = array();
+        try {
+            $themeconfig = theme_config::load($theme);
+        } catch (Exception $e) {
+            // This theme has no theme config.
+            return [];
         }
 
-        $contexts = $this->get_components_contexts();
-
-        // Create list of contexts used by theme suite.
-        $themecontexts = $this->get_tests_for_theme($theme, 'contexts');
-        $blacklistedcontexts = $this->get_blacklisted_tests_for_theme($theme, 'contexts');
+        // The theme will use all core contexts, except the one overridden by theme or its parent.
+        $parentcontexts = [];
+        if (isset($themeconfig->parents)) {
+            foreach ($themeconfig->parents as $parent) {
+                if ($parentcontexts = $this->get_behat_contexts_for_theme($parent)) {
+                    break;
+                }
+            }
+        }
 
-        // Theme suite will use all core contexts, except the one overridden by theme.
-        $themesuitecontexts = $contexts;
+        if (empty($parentcontexts)) {
+            $parentcontexts = $this->get_components_contexts();
+        }
 
-        foreach ($themecontexts as $context => $path) {
+        // Remove contexts which have been actively blacklisted.
+        $blacklistedcontexts = $this->get_blacklisted_tests_for_theme($theme, 'contexts');
+        foreach ($blacklistedcontexts as $blacklistpath) {
+            $blacklistcontext = basename($blacklistpath, '.php');
 
-            // If a context in theme starts with behat_theme_{themename}_behat_* then it's overriding core context.
-            if (preg_match('/^behat_theme_'.$theme.'_(\w+)$/', $context, $match)) {
+            unset($parentcontexts[$blacklistcontext]);
+        }
 
-                if (!empty($themesuitecontexts[$match[1]])) {
-                    unset($themesuitecontexts[$match[1]]);
-                }
+        // Apply overrides.
+        $contexts = array_merge($parentcontexts, $this->get_tests_for_theme($theme, 'contexts'));
 
-                // Add this to the list of overridden paths, so it can be added to final contexts list for class resolver.
-                $this->overriddenthemescontexts[$context] = $path;
+        // Remove classes which are overridden.
+        foreach ($contexts as $contextclass => $path) {
+            require_once($path);
+            if (!class_exists($contextclass)) {
+                // This may be a Poorly named class.
+                continue;
             }
 
-            $selectortypes = ['named_partial', 'named_exact'];
-            foreach ($selectortypes as $selectortype) {
-                // Don't include selector classes.
-                if ($context === self::get_behat_theme_selector_override_classname($theme, $selectortype)) {
-                    unset($this->contexts[$context]);
-                    unset($themesuitecontexts[$context]);
-                    continue;
+            $rc = new \ReflectionClass($contextclass);
+            $parent = $rc->getParentClass();
+            while ($rc = $rc->getParentClass()) {
+                if (isset($contexts[$rc->name])) {
+                    unset($contexts[$rc->name]);
                 }
             }
-
-            // Add theme specific contexts with suffix to steps definitions.
-            $themesuitecontexts[$context] = $path;
         }
 
-        // Remove blacklisted contexts.
-        foreach ($blacklistedcontexts as $blacklistpath) {
-            $blacklistcontext = basename($blacklistpath, '.php');
+        // Sort the list of contexts.
+        ksort($contexts);
 
-            unset($themesuitecontexts[$blacklistcontext]);
-        }
-
-        // We are only interested in the class name of context.
-        $this->themesuitecontexts[$theme] = $themesuitecontexts;
-        $this->themecontexts[$theme] = $themecontexts;
-
-        $retval = array(
-            'contexts' => $themecontexts,
-            'suitecontexts' => $themesuitecontexts,
-        );
+        $this->themecontexts[$theme] = $contexts;
 
-        return $retval;
+        return $contexts;
     }
 }
index ffd94cd..8b050cc 100644 (file)
@@ -90,6 +90,13 @@ class behat_context_helper {
      * @return behat_base
      */
     public static function get($classname) {
+        $contexts = self::$environment->getContexts();
+
+        foreach ($contexts as $context) {
+            if (is_a($context, $classname)) {
+                return $context;
+            }
+        }
 
         $suitename = self::$environment->getSuite()->getName();
         // If default suite, then get the default theme name.
similarity index 97%
rename from lib/behat/behat_files.php
rename to lib/behat/core_behat_file_helper.php
index d2cb84b..957a357 100644 (file)
@@ -17,9 +17,8 @@
 /**
  * Files interactions with behat.
  *
- * Note that steps definitions files can not extend other steps definitions files, so
- * steps definitions which makes use of file attachments or filepicker should
- * extend behat_files instead of behat_base.
+ * Note that steps definitions files can not extend other steps definitions files, so steps definitions which makes use
+ * of file attachments or filepicker should use this behat_file_helper trait.
  *
  * @package    core
  * @category   test
@@ -37,16 +36,15 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
 /**
  * Files-related actions.
  *
- * Steps definitions related with filepicker or repositories should extend
- * this class instead of behat_base as it provides useful methods to deal
- * with the common filepicker issues.
+ * Steps definitions related with filepicker or repositories should extend use this trait as it provides useful methods
+ * to deal with the common filepicker issues.
  *
  * @package    core
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class behat_files extends behat_base {
+trait core_behat_file_helper {
 
     /**
      * Gets the NodeElement for filepicker of filemanager moodleform element.
@@ -276,5 +274,4 @@ class behat_files extends behat_base {
             $filepickernode
         );
     }
-
 }
index 42c3d5e..ce22d70 100644 (file)
@@ -25,7 +25,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../lib/behat/behat_files.php');
+require_once(__DIR__ . '/../../../lib/behat/core_behat_file_helper.php');
 
 use Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Gherkin\Node\TableNode as TableNode;
@@ -33,14 +33,13 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
 /**
  * Steps definitions to deal with the filemanager and filepicker.
  *
- * Extends behat_files rather than behat_base as is file-related.
- *
  * @package    core_filepicker
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class behat_filepicker extends behat_files {
+class behat_filepicker extends behat_base {
+    use core_behat_file_helper;
 
     /**
      * Creates a folder with specified name in the current folder and in the specified filemanager field.
index 8552067..6f90d82 100644 (file)
@@ -25,7 +25,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../lib/behat/behat_files.php');
+require_once(__DIR__ . '/../../../../lib/behat/core_behat_file_helper.php');
 
 use Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Gherkin\Node\TableNode as TableNode;
@@ -33,14 +33,14 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
 /**
  * Steps definitions to deal with the upload repository.
  *
- * Extends behat_files rather than behat_base as is file-related.
- *
  * @package    repository_upload
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class behat_repository_upload extends behat_files {
+class behat_repository_upload extends behat_base {
+
+    use core_behat_file_helper;
 
     /**
      * Uploads a file to the specified filemanager leaving other fields in upload form default. The paths should be relative to moodle codebase.
index 3343366..962db2d 100644 (file)
@@ -34,16 +34,22 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
 /**
  * Steps definitions to deal with the filemanager and filepicker overrides.
  *
+ * @package    theme_bootstrapbase
+ * @category   test
  * @copyright  2016 Damyon Wiese
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class behat_theme_bootstrapbase_behat_filepicker extends behat_theme_bootstrapbase_behat_files {
+
+class behat_theme_bootstrapbase_behat_filepicker extends behat_filepicker {
+    use theme_bootstrapbase_behat_file_helper;
+
     public function i_create_folder_in_filemanager($foldername, $filemanagerelement) {
 
         $fieldnode = $this->get_filepicker_node($filemanagerelement);
 
         // Looking for the create folder button inside the specified filemanager.
-        $exception = new ExpectationException('No folders can be created in "'.$filemanagerelement.'" filemanager',$this->getSession());
+        $exception = new ExpectationException('No folders can be created in "'.$filemanagerelement.'" filemanager',
+                $this->getSession());
         $newfolder = $this->find('css', 'div.fp-btn-mkdir a', $exception, $fieldnode);
         $newfolder->click();
 
@@ -143,7 +149,8 @@ class behat_theme_bootstrapbase_behat_filepicker extends behat_theme_bootstrapba
 
         $elements = $this->find_all('xpath', $xpath, false, $filemanagernode);
         if (count($elements) != $elementscount) {
-            throw new ExpectationException('Found '.count($elements).' elements in filemanager instead of expected '.$elementscount, $this->getSession());
+            throw new ExpectationException('Found '.count($elements).
+                    ' elements in filemanager instead of expected '.$elementscount, $this->getSession());
         }
     }
 
index a81bc06..cace980 100644 (file)
@@ -26,6 +26,7 @@
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
 require_once(__DIR__ . '/../../../../repository/upload/tests/behat/behat_repository_upload.php');
+require_once(__DIR__ . '/theme_bootstrapbase_behat_file_helper.php');
 
 use Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Gherkin\Node\TableNode as TableNode;
@@ -40,6 +41,8 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
  */
 class behat_theme_bootstrapbase_behat_repository_upload extends behat_repository_upload {
 
+    use theme_bootstrapbase_behat_file_helper;
+
     protected function upload_file_to_filemanager($filepath, $filemanagerelement, TableNode $data, $overwriteaction = false) {
         global $CFG;
 
@@ -108,5 +111,4 @@ class behat_theme_bootstrapbase_behat_repository_upload extends behat_repository
         }
 
     }
-
 }
@@ -25,7 +25,7 @@
 
 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
 
-require_once(__DIR__ . '/../../../../lib/behat/behat_files.php');
+require_once(__DIR__ . '/../../../../lib/behat/core_behat_file_helper.php');
 
 use Behat\Mink\Exception\ExpectationException as ExpectationException,
     Behat\Mink\Element\NodeElement as NodeElement;
@@ -38,7 +38,11 @@ use Behat\Mink\Exception\ExpectationException as ExpectationException,
  * @copyright  2016 Damyon Wiese
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class behat_theme_bootstrapbase_behat_files extends behat_files {
+trait theme_bootstrapbase_behat_file_helper {
+
+    use core_behat_file_helper {
+        core_behat_file_helper::get_filepicker_node as core_get_filepicker_node;
+    }
 
     protected function get_filepicker_node($filepickerelement) {