3 namespace Drupal\DrupalExtension\Context\Environment\Reader;
5 use Behat\Behat\Context\Environment\UninitializedContextEnvironment;
6 use Behat\Behat\Context\Environment\ContextEnvironment;
7 use Behat\Behat\Context\Reader\ContextReader;
8 use Behat\Testwork\Call\Callee;
9 use Behat\Testwork\Environment\Environment;
10 use Behat\Testwork\Environment\Exception\EnvironmentReadException;
11 use Behat\Testwork\Environment\Reader\EnvironmentReader;
13 use Drupal\DrupalDriverManager;
14 use Drupal\Driver\SubDriverFinderInterface;
16 use RecursiveDirectoryIterator;
17 use RecursiveIteratorIterator;
21 * Read in additional contexts provided by core and contrib.
23 final class Reader implements EnvironmentReader {
26 * @var ContextReader[]
28 private $contextReaders = array();
31 * Drupal driver manager.
33 * @var \Drupal\DrupalDriverManager
38 * Configuration parameters for this suite.
43 * Statically cached lists of subcontexts by path.
47 static protected $subContexts;
50 * Register the Drupal driver manager.
52 public function __construct(DrupalDriverManager $drupal, array $parameters) {
53 $this->drupal = $drupal;
54 $this->parameters = $parameters;
58 * Registers context loader.
60 * @param ContextReader $contextReader
62 public function registerContextReader(ContextReader $contextReader) {
63 $this->contextReaders[] = $contextReader;
69 public function supportsEnvironment(Environment $environment) {
70 return $environment instanceof ContextEnvironment;
76 public function readEnvironmentCallees(Environment $environment) {
78 if (!$environment instanceof ContextEnvironment) {
79 throw new EnvironmentReadException(sprintf(
80 'ContextEnvironmentReader does not support `%s` environment.',
81 get_class($environment)
86 if (!$environment instanceof UninitializedContextEnvironment) {
90 $contextClasses = $this->findSubContextClasses();
92 foreach ($contextClasses as $contextClass) {
93 // When executing test scenarios with an examples table the registering of
94 // contexts is handled differently in newer version of Behat. Starting
95 // with Behat 3.2.0 the contexts are already registered, and the callees
96 // are returned by the default reader.
97 // Work around this and provide compatibility with Behat 3.1.0 as well as
98 // 3.2.0 and higher by checking if the class already exists before
99 // registering it and returning the callees.
100 // @see https://github.com/Behat/Behat/issues/758
101 if (!$environment->hasContextClass($contextClass)) {
102 $callees = array_merge(
104 $this->readContextCallees($environment, $contextClass)
108 $environment->registerContextClass($contextClass, array($this->drupal));
116 * Reads callees from a specific suite's context.
118 * @param ContextEnvironment $environment
119 * @param string $contextClass
123 private function readContextCallees(ContextEnvironment $environment, $contextClass)
126 foreach ($this->contextReaders as $loader) {
127 $callees = array_merge(
129 $loader->readContextCallees($environment, $contextClass)
137 * Finds and loads available subcontext classes.
139 private function findSubContextClasses() {
140 $class_names = array();
142 // Initialize any available sub-contexts.
143 if (isset($this->parameters['subcontexts'])) {
145 // Drivers may specify paths to subcontexts.
146 if ($this->parameters['subcontexts']['autoload']) {
147 foreach ($this->drupal->getDrivers() as $name => $driver) {
148 if ($driver instanceof SubDriverFinderInterface) {
149 $paths += $driver->getSubDriverPaths();
154 // Additional subcontext locations may be specified manually in behat.yml.
155 if (isset($this->parameters['subcontexts']['paths'])) {
156 $paths = array_merge($paths, $this->parameters['subcontexts']['paths']);
160 foreach ($paths as $path) {
161 if ($subcontexts = $this->findAvailableSubContexts($path)) {
162 $this->loadSubContexts($subcontexts);
166 // Find all subcontexts, excluding abstract base classes.
167 $classes = get_declared_classes();
168 foreach ($classes as $class) {
169 $reflect = new \ReflectionClass($class);
170 if (!$reflect->isAbstract() && $reflect->implementsInterface('Drupal\DrupalExtension\Context\DrupalSubContextInterface')) {
171 $class_names[] = $class;
181 * Find Sub-contexts matching a given pattern located at the passed path.
183 * @param string $path
184 * Absolute path to the directory to search for sub-contexts.
185 * @param string $pattern
186 * File pattern to match. Defaults to `/^.+\.behat\.inc/i`.
191 private function findAvailableSubContexts($path, $pattern = '/^.+\.behat\.inc/i') {
193 if (isset(static::$subContexts[$pattern][$path])) {
194 return static::$subContexts[$pattern][$path];
197 static::$subContexts[$pattern][$path] = array();
199 $fileIterator = new RegexIterator(
200 new RecursiveIteratorIterator(
201 new RecursiveDirectoryIterator($path)
205 foreach ($fileIterator as $found) {
206 static::$subContexts[$pattern][$path][$found->getRealPath()] = $found->getFileName();
209 return static::$subContexts[$pattern][$path];
213 * Load each subcontext file.
215 * @param array $subcontexts
216 * An array of files to include.
218 private function loadSubContexts($subcontexts) {
219 foreach ($subcontexts as $path => $subcontext) {
220 if (!file_exists($path)) {
221 throw new \RuntimeException(sprintf('Subcontext path %s path does not exist.', $path));