From feba280470b1427ef5afdb32e333ada907783b8a Mon Sep 17 00:00:00 2001 From: Ingo Schommer Date: Wed, 14 Nov 2012 15:21:12 +0100 Subject: [PATCH] NEW LocatorProcessor and ClassGuesser to manage modules Important for running behat tests per-module, while stil keeping parameters like "base_url" configurable without modifying behat.yml files in the modules own source code. --- README.md | 60 +++++----- .../Console/Processor/LocatorProcessor.php | 110 ++++++++++++++++++ .../ModuleContextClassGuesser.php | 53 +++++++++ .../BehatExtension/services/silverstripe.yml | 13 ++- 4 files changed, 203 insertions(+), 33 deletions(-) create mode 100644 src/SilverStripe/BehatExtension/Console/Processor/LocatorProcessor.php create mode 100644 src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php diff --git a/README.md b/README.md index c395eff..dd484fe 100644 --- a/README.md +++ b/README.md @@ -57,10 +57,7 @@ And get the latest Selenium2 server (requires Java): As a convention, SilverStripe Behat tests live in a `tests/behat` subfolder of your module. You can create it with the following command: - cd mymodule - mkdir -p tests/behat - cd tests/behat - ../../../vendor/bin/behat --init + mkdir -p mymodule/tests/behat/features/bootstrap/MyModule/Test/Behaviour ### FeatureContext @@ -69,10 +66,10 @@ here as well. The only major difference is the base class from which to extend your own `FeatureContext`: It should be `SilverStripeContext` rather than `BehatContext`. -Example: FeatureContext.php +Example: mymodule/tests/behat/features/bootstrap/MyModule/Test/Behaviour/FeatureContext.php container = $container; + } + + /** + * Configures command to be able to process it later. + * + * @param Command $command + */ + public function configure(Command $command) + { + $command->addArgument('features', InputArgument::OPTIONAL, + "Feature(s) to run. Could be:". + "\n- a dir (src/to/module/Features/), " . + "\n- a feature (src/to/module/Features/*.feature), " . + "\n- a scenario at specific line (src/to/module/Features/*.feature:10). " . + "\n- Also, you can use short module notation (@moduleName/*.feature:10)" + ); + } + + /** + * Processes data from container and console input. + * + * @param InputInterface $input + * @param OutputInterface $output + * + * @throws \RuntimeException + */ + public function process(InputInterface $input, OutputInterface $output) + { + // Bootstrap SS so we can use module listing + $frameworkPath = $this->container->getParameter('behat.silverstripe_extension.framework_path'); + $_GET['flush'] = 1; + require_once $frameworkPath . '/core/Core.php'; + unset($_GET['flush']); + + $featuresPath = $input->getArgument('features'); + $pathSuffix = $this->container->getParameter('behat.silverstripe_extension.context.path_suffix'); + + $currentModuleName = null; + $modules = \SS_ClassLoader::instance()->getManifest()->getModules(); + + // get module specified in behat.yml + $currentModuleName = $this->container->getParameter('behat.silverstripe_extension.module'); + + // get module from short notation if path starts from @ + if ($featuresPath && preg_match('/^\@([^\/\\\\]+)(.*)$/', $featuresPath, $matches)) { + $currentModuleName = $matches[1]; + // TODO Replace with proper module loader once AJShort's changes are merged into core + $currentModulePath = $modules[$currentModuleName]; + $featuresPath = str_replace( + '@'.$currentModuleName, + $currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix, + $featuresPath + ); + // get module from provided features path + } elseif (!$currentModuleName && $featuresPath) { + $path = realpath(preg_replace('/\.feature\:.*$/', '.feature', $featuresPath)); + foreach ($modules as $moduleName => $modulePath) { + if (false !== strpos($path, realpath($modulePath))) { + $currentModuleName = $moduleName; + break; + } + } + // if module is configured for profile and feature provided + } elseif ($currentModuleName && $featuresPath) { + $currentModulePath = $modules[$currentModuleName]; + $featuresPath = $currentModulePath.DIRECTORY_SEPARATOR.$pathSuffix.DIRECTORY_SEPARATOR.$featuresPath; + } + + if ($currentModuleName) { + $this->container + ->get('behat.silverstripe_extension.context.class_guesser') + ->setModuleNamespace(ucfirst($currentModuleName)); + } + + if (!$featuresPath) { + $featuresPath = $this->container->getParameter('behat.paths.features'); + } + + $this->container + ->get('behat.console.command') + ->setFeaturesPaths($featuresPath ? array($featuresPath) : array()); + } +} diff --git a/src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php b/src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php new file mode 100644 index 0000000..ce988af --- /dev/null +++ b/src/SilverStripe/BehatExtension/Context/ClassGuesser/ModuleContextClassGuesser.php @@ -0,0 +1,53 @@ +classSuffix = $classSuffix; + } + + /** + * Sets bundle namespace to use for guessing. + * + * @param string $namespace + */ + public function setModuleNamespace($namespace) + { + $this->namespace = $namespace; + } + + /** + * Tries to guess context classname. + * + * @return string + */ + public function guess() + { + // Try fully qualified namespace + if (class_exists($class = $this->namespace.'\\'.$this->classSuffix)) { + return $class; + } + // Fall back to namespace with SilverStripe prefix + // TODO Remove once core has namespace capabilities for modules + if (class_exists($class = 'SilverStripe\\'.$this->namespace.'\\'.$this->classSuffix)) { + return $class; + } + } +} diff --git a/src/SilverStripe/BehatExtension/services/silverstripe.yml b/src/SilverStripe/BehatExtension/services/silverstripe.yml index b34fae3..bfa9279 100644 --- a/src/SilverStripe/BehatExtension/services/silverstripe.yml +++ b/src/SilverStripe/BehatExtension/services/silverstripe.yml @@ -1,11 +1,16 @@ parameters: behat.silverstripe_extension.context.initializer.class: SilverStripe\BehatExtension\Context\Initializer\SilverStripeAwareInitializer - behat.silverstripe_extension.framework_path: ~ + behat.silverstripe_extension.context.class_guesser.class: SilverStripe\BehatExtension\Context\ClassGuesser\ModuleContextClassGuesser + behat.console.processor.locator.class: SilverStripe\BehatExtension\Console\Processor\LocatorProcessor + behat.silverstripe_extension.context.class_suffix: Test\Behaviour\FeatureContext + behat.silverstripe_extension.framework_path: framework behat.silverstripe_extension.ajax_steps: ~ behat.silverstripe_extension.ajax_timeout: ~ behat.silverstripe_extension.admin_url: ~ behat.silverstripe_extension.login_url: ~ behat.silverstripe_extension.screenshot_path: ~ + behat.silverstripe_extension.module: + behat.silverstripe_extension.context.path_suffix: tests/behat/features/ services: behat.silverstripe_extension.context.initializer: class: %behat.silverstripe_extension.context.initializer.class% @@ -20,3 +25,9 @@ services: - [setScreenshotPath, [%behat.silverstripe_extension.screenshot_path%]] tags: - { name: behat.context.initializer } + behat.silverstripe_extension.context.class_guesser: + class: %behat.silverstripe_extension.context.class_guesser.class% + arguments: + - %behat.silverstripe_extension.context.class_suffix% + tags: + - { name: behat.context.class_guesser, priority: 10 } \ No newline at end of file