From a9c656ad4c2df06b503a2b8e04188690fbcdb2a9 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 23 May 2014 09:52:14 +0200
Subject: [PATCH 1/2] Replace FieldDefinitionTestBase::getNamespacePath() with
 FieldDefinitionTestBase::getModuleInfoFilePath()

---
 .../tests/src/Field/PathFieldDefinitionTest.php    |  4 +--
 .../Tests/Core/Field/FieldDefinitionTestBase.php   | 36 +++++++++++++---------
 2 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php b/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php
index fb99b82..ac3d1fe 100644
--- a/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php
+++ b/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php
@@ -41,8 +41,8 @@ protected function getPluginId() {
   /**
    * {@inheritdoc}
    */
-  protected function getNamespacePath() {
-    return dirname(dirname(dirname(__DIR__))) . '/lib/Drupal/path';
+  protected function getModuleInfoFilePath() {
+    return dirname(dirname(dirname(__DIR__))) . '/path.info.yml';
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php b/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php
index 49a3922..35d454d 100644
--- a/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php
@@ -28,15 +28,22 @@
    * {@inheritdoc}
    */
   public function setUp() {
-    $namespace_path = $this->getNamespacePath();
-    // Suppport both PSR-0 and PSR-4 directory layouts.
-    $module_name = basename($namespace_path);
-    if ($module_name == 'src') {
-      $module_name = basename($module_name);
+
+    $module_info_file = $this->getModuleInfoFilePath();
+    if (!preg_match('#^(.*)/([^/]+)\.info\.yml$#', $module_info_file, $m)) {
+      throw new \Exception("Unexpected module info file.");
     }
-    $namespaces = new \ArrayObject(array(
-      'Drupal\\' . $module_name => $namespace_path,
-    ));
+
+    list(, $module_dir, $module_name) = $m;
+
+    $namespaces = new \ArrayObject(
+      array(
+        "Drupal\\$module_name" => array(
+          $module_dir . '/src',
+          $module_dir . '/lib/Drupal/' . $module_name,
+        ),
+      )
+    );
 
     $language_manager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
     $language_manager->expects($this->once())
@@ -72,15 +79,16 @@ public function setUp() {
   abstract protected function getPluginId();
 
   /**
-   * Returns the path to the module's classes.
+   * Returns the path to the *.info.yml file of the module where the test should
+   * look for plugins.
    *
-   * Depending on whether the module follows the PSR-0 or PSR-4 directory layout
-   * this should be either /path/to/module/lib/Drupal/mymodule or
-   * /path/to/module/src.
+   * This will be used to determine both the module name and the module
+   * directory.
    *
    * @return string
-   *   The path to the module's classes.
+   *   The path to the MODULE.info.yml file, e.g.
+   *   DRUPAL_ROOT . "/core/modules/path/path.info.yml".
    */
-  abstract protected function getNamespacePath();
+  abstract protected function getModuleInfoFilePath();
 
 }
-- 
1.8.5.1


From 7d9a760821a7faa15ca897b23fcaeb7c703eee19 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 23 May 2014 09:58:11 +0200
Subject: [PATCH 2/2] OPTIONAL: Add default implementation for
 getModuleInfoFilePath(), so that subclasses don't need to implement it.

---
 .../tests/src/Field/PathFieldDefinitionTest.php    |  7 --
 .../Tests/Core/Field/FieldDefinitionTestBase.php   | 91 +++++++++++++++++++++-
 2 files changed, 90 insertions(+), 8 deletions(-)

diff --git a/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php b/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php
index ac3d1fe..42144d3 100644
--- a/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php
+++ b/core/modules/path/tests/src/Field/PathFieldDefinitionTest.php
@@ -39,13 +39,6 @@ protected function getPluginId() {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  protected function getModuleInfoFilePath() {
-    return dirname(dirname(dirname(__DIR__))) . '/path.info.yml';
-  }
-
-  /**
    * Tests FieldDefinition::getColumns().
    *
    * @covers \Drupal\Core\Field\FieldDefinition::getColumns
diff --git a/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php b/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php
index 35d454d..fcd6222 100644
--- a/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Field/FieldDefinitionTestBase.php
@@ -85,10 +85,99 @@ public function setUp() {
    * This will be used to determine both the module name and the module
    * directory.
    *
+   * This defaults to the module that contains the test, but can be overridden.
+   *
    * @return string
    *   The path to the MODULE.info.yml file, e.g.
    *   DRUPAL_ROOT . "/core/modules/path/path.info.yml".
+   *
+   * @throws \Exception
+   */
+  protected function getModuleInfoFilePath() {
+
+    $class = get_class($this);
+    $module_info_file = $this->classExtractModuleInfoFilePath($class);
+    if ($module_info_file === FALSE) {
+      throw new \Exception("No module info file found for class '$class'.");
+    }
+    return $module_info_file;
+  }
+
+  /**
+   * Determines if a given class is part of a Drupal module, and determines the
+   * path to the *.info.yml file, if possible.
+   *
+   * @todo Put this in a globally accessible place.
+   *
+   * @param string $class
+   *   The fully-qualified class name, e.g.
+   *   "Drupal\\path\\Tests\\PathFieldDefinitionTest".
+   *
+   * @return string|false
+   *   The path to the module info file, or FALSE if it could not be determined.
+   */
+  private function classExtractModuleInfoFilePath($class) {
+
+    if (!preg_match("/Drupal\\\\(.+)\\\\Tests\\\\(.+)$/", $class, $m)) {
+      return FALSE;
+    }
+
+    list(, $module_name, $relative_class_name) = $m;
+
+    $relative_path = str_replace('\\', '/', $relative_class_name) . '.php';
+
+    $suffixes = array(
+      // PSR-4 location for PHPUnit tests.
+      '/tests/src/' . $relative_path,
+      // PSR-0 location for PHPUnit tests.
+      // @todo Remove when PSR-0 support has ended.
+      "/tests/Drupal/$module_name/Tests/" . $relative_path,
+    );
+
+    $class_file = (new \ReflectionClass($class))->getFileName();
+    if ($class_file === FALSE) {
+      return FALSE;
+    }
+
+    // Normalize the directory separator to '/'.
+    $class_file = str_replace(DIRECTORY_SEPARATOR, '/', $class_file);
+
+    foreach ($suffixes as $suffix) {
+      $dir = $this->stringRemoveSuffix($class_file, $suffix);
+      if (FALSE !== $dir) {
+        $module_info_file = $dir . '/' . $module_name . '.info.yml';
+        if (is_file($module_info_file)) {
+          return $module_info_file;
+        }
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Removes a suffix from a string, if possible.
+   *
+   * @todo Put this in a globally accessible place.
+   *
+   * @param string $string
+   *   A string to test.
+   * @param string $suffix
+   *   A string that could be a suffix of $string.
+   *
+   * @return bool|string
+   *   The $prefix, so that $prefix . $suffix === $string, or
+   *   FALSE, if $string does not end with $suffix.
    */
-  abstract protected function getModuleInfoFilePath();
+  private function stringRemoveSuffix($string, $suffix) {
+    if (FALSE !== $pos = strrpos($string, $suffix)) {
+      if ($pos + strlen($suffix) === strlen($string)) {
+        // $string ends with $suffix, so return the beginning of $string.
+        return substr($string, 0, $pos);
+      }
+    }
+    // $string does not end with $suffix.
+    return FALSE;
+  }
 
 }
-- 
1.8.5.1

