diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d94f8a..32b89a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: operating-system: ['ubuntu-latest'] - php-versions: ['7.2', '7.3', '7.4', '8.0'] + php-versions: ['8.1'] steps: - name: Checkout uses: actions/checkout@v2 @@ -21,5 +21,8 @@ jobs: - name: Install dependencies run: composer install + - name: Install fixtures + run: composer install-fixtures + - name: Run unit tests run: ./vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 8f7045c..4ece3d5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /composer.lock /vendor/ /.phpunit.result.cache +/tests/fixtures/custom-vendor/foo +/tests/fixtures/default/web +/tests/fixtures/default/vendor +/tests/fixtures/*/composer.lock diff --git a/README.md b/README.md index c68c09a..768140d 100644 --- a/README.md +++ b/README.md @@ -3,45 +3,22 @@ [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/webflo/drupal-finder/CI)](https://github.com/webflo/drupal-finder/actions/workflows/ci.yml) [![Packagist](https://img.shields.io/packagist/v/webflo/drupal-finder.svg)](https://packagist.org/packages/webflo/drupal-finder) -Drupal Finder provides a class to locate a Drupal installation in a given path. +Drupal Finder provides a class to locate a Drupal installation based on Composer metadata. ## Usage ```PHP -$drupalFinder = new \DrupalFinder\DrupalFinder(getcwd()); +$drupalFinder = new \DrupalFinder\DrupalFinder(); $drupalRoot = $drupalFinder->getDrupalRoot(); $composerRoot = $drupalFinder->getComposerRoot(); $vendorDir = $drupalFinder->getVendorDir(); ``` -### Environment variables - -If a set of environment variables is specified, then Drupal Finder uses those -values to determine the paths of the pertinent directories: - -- `DRUPAL_FINDER_DRUPAL_ROOT` -- `DRUPAL_FINDER_COMPOSER_ROOT` -- `DRUPAL_FINDER_VENDOR_DIR` - -For example: - -- `DRUPAL_FINDER_DRUPAL_ROOT=/var/www/web` -- `DRUPAL_FINDER_COMPOSER_ROOT=/var/www` -- `DRUPAL_FINDER_VENDOR_DIR=/var/www/vendor` - -This is useful for situations where you are containerizing an application, -directories may be in odd places, or a composer.json might be missing since it -is unneeded in a final build artifact. - -You are not required to set all the environment variables to use this -feature. If you set an environment variable, then its associated getter -function will return the value assigned to the environment variable. - ## Examples - [Drupal Console Launcher](https://github.com/hechoendrupal/drupal-console-launcher) -- [Drush Launcher](https://github.com/drush-ops/drush-launcher) +- [Drush](https://github.com/drush-ops/drush) ## License diff --git a/composer.json b/composer.json index 703c2a8..cd72939 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "name": "webflo/drupal-finder", - "description": "Helper class to locate a Drupal installation from a given path.", + "description": "Helper class to locate a Drupal installation.", "license": "GPL-2.0-or-later", "type": "library", "authors": [ @@ -10,12 +10,13 @@ } ], "require": { - "ext-json": "*" + "composer-runtime-api": "^2.2", + "php": ">=8.1" }, "autoload": { - "classmap": [ - "src/DrupalFinder.php" - ] + "psr-4": { + "DrupalFinder\\": "src/" + } }, "autoload-dev": { "psr-4": { @@ -23,7 +24,27 @@ } }, "require-dev": { - "phpunit/phpunit": "^8.5.14", - "mikey179/vfsstream": "^1.6" + "phpunit/phpunit": "^10.4" + }, + "config": { + "platform": { + "php": "8.1" + } + }, + "scripts": { + "install-fixtures": [ + "cd tests/fixtures/custom-vendor && composer install", + "cd tests/fixtures/default && composer install" + ], + "uninstall-fixtures": [ + "rm -rf tests/fixtures/*/composer.lock", + "rm -rf tests/fixtures/custom-vendor/foo", + "rm -rf tests/fixtures/default/web", + "rm -rf tests/fixtures/default/vendor" + ], + "reinstall-fixtures": [ + "@uninstall-fixtures", + "@install-fixtures" + ] } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 92f27db..34265ab 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -3,6 +3,7 @@ ./tests + ./tests/fixtures diff --git a/src/DrupalFinder.php b/src/DrupalFinder.php index ffbcecd..cbeae74 100644 --- a/src/DrupalFinder.php +++ b/src/DrupalFinder.php @@ -7,294 +7,35 @@ namespace DrupalFinder; +use Composer\InstalledVersions; + class DrupalFinder { /** - * Drupal root environment variable. - */ - const ENV_DRUPAL_ROOT = 'DRUPAL_FINDER_DRUPAL_ROOT'; - - /** - * Composer root environment variable. - */ - const ENV_COMPOSER_ROOT = 'DRUPAL_FINDER_COMPOSER_ROOT'; - - /** - * Vendor directory environment variable. - */ - const ENV_VENDOR_DIR = 'DRUPAL_FINDER_VENDOR_DIR'; - - /** - * Drupal web public directory. - * - * @var string - */ - private $drupalRoot; - - /** - * Drupal package composer directory. - * - * @var bool - */ - private $composerRoot; - - /** - * Composer vendor directory. - * - * @var string - * - * @see https://getcomposer.org/doc/06-config.md#vendor-dir - */ - private $vendorDir; - - /** - * Initialize finder. - * - * Optionally pass the starting path. - * - * @param string|null $start_path - * The path to begin the search from. - * - * @throws \Exception - * @todo Make $start_path mandatory in v2. + * Get the Drupal root path. */ - public function __construct($start_path = null) { - // Initialize path variables to false, indicating their locations are - // not yet known. - $this->drupalRoot = false; - $this->composerRoot = false; - $this->vendorDir = false; - - // If a starting path was provided, attempt to locate and set path - // variables. - if (!empty($start_path)) { - $this->discoverRoots($start_path); - } - } - - /** - * Locate Drupal, Composer, and vendor directory paths. - * - * @param string $start_path - * The path to begin the search from. - * - * @return bool - * True if the Drupal root was identified, false otherwise. - * - * @throws \Exception - * - * @deprecated Will be removed in v2. Future usage should instantiate - * a new DrupalFinder object by passing the starting path to its - * constructor. - */ - public function locateRoot($start_path) + public function getDrupalRoot(): ?string { - $this->discoverRoots($start_path); - return !empty($this->getDrupalRoot()); + $core = InstalledVersions::getInstallPath('drupal/core'); + return $core ? realpath(dirname($core)) : null; } /** - * Get the Drupal root. - * - * @return string|bool - * The path to the Drupal root, if it was discovered. False otherwise. + * Get the path to the Composer root directory. */ - public function getDrupalRoot() + public function getComposerRoot(): ?string { - $environment_path = $this->getValidEnvironmentVariablePath(self::ENV_DRUPAL_ROOT); - - return !empty($environment_path) ? $environment_path : $this->drupalRoot; - } - - /** - * Get the Composer root. - * - * @return string|bool - * The path to the Composer root, if it was discovered. False otherwise. - */ - public function getComposerRoot() - { - $environment_path = $this->getValidEnvironmentVariablePath(self::ENV_COMPOSER_ROOT); - return !empty($environment_path) ? $environment_path : $this->composerRoot; + $root = InstalledVersions::getRootPackage(); + return realpath($root['install_path']); } /** * Get the vendor path. - * - * @return string|bool - * The path to the vendor directory, if it was found. False otherwise. - */ - public function getVendorDir() - { - $environment_path = $this->getValidEnvironmentVariablePath(self::ENV_VENDOR_DIR); - return !empty($environment_path) ? $environment_path : $this->vendorDir; - } - - /** - * Discover all valid paths. - * - * @param $start_path - * The path to start the search from. - * - * @throws \Exception - */ - protected function discoverRoots($start_path) { - // Since we are discovering, reset all path variables. - $this->drupalRoot = false; - $this->composerRoot = false; - $this->vendorDir = false; - - foreach (array(true, false) as $follow_symlinks) { - $path = $start_path; - if ($follow_symlinks && is_link($path)) { - $path = realpath($path); - } - - // Check the start path. - if ($this->findAndValidateRoots($path)) { - return; - } else { - // Move up dir by dir and check each. - while ($path = $this->shiftPathUp($path)) { - if ($follow_symlinks && is_link($path)) { - $path = realpath($path); - } - if ($this->findAndValidateRoots($path)) { - return; - } - } - } - } - } - - /** - * Determine if a valid Drupal root exists. - * - * In addition, set any valid path properties if they are found. - * - * @param $path - * The starting path to search from. - * - * @return bool - * True if all roots were discovered and validated. False otherwise. - */ - protected function findAndValidateRoots($path) - { - - if (!empty($path) && is_dir($path) && file_exists($path . '/autoload.php') && file_exists($path . '/' . $this->getComposerFileName())) { - // Additional check for the presence of core/composer.json to - // grant it is not a Drupal 7 site with a base folder named "core". - $candidate = 'core/includes/common.inc'; - if (file_exists($path . '/' . $candidate) && file_exists($path . '/core/core.services.yml')) { - if (file_exists($path . '/core/misc/drupal.js') || file_exists($path . '/core/assets/js/drupal.js')) { - $this->composerRoot = $path; - $this->drupalRoot = $path; - $this->vendorDir = $this->composerRoot . '/vendor'; - } - } - } - if (!empty($path) && is_dir($path) && file_exists($path . '/' . $this->getComposerFileName())) { - $json = json_decode( - file_get_contents($path . '/' . $this->getComposerFileName()), - true - ); - - if (is_null($json)) { - throw new \Exception('Unable to decode ' . $path . '/' . $this->getComposerFileName()); - } - - if (is_array($json)) { - if (isset($json['extra']['installer-paths']) && is_array($json['extra']['installer-paths'])) { - foreach ($json['extra']['installer-paths'] as $install_path => $items) { - if (in_array('type:drupal-core', $items) || - in_array('drupal/core', $items) || - in_array('drupal/drupal', $items)) { - $this->composerRoot = $path; - // @todo: Remove this magic and detect the major version instead. - if (($install_path == 'core') || ((isset($json['name'])) && ($json['name'] == 'drupal/drupal'))) { - $install_path = ''; - } elseif (substr($install_path, -5) == '/core') { - $install_path = substr($install_path, 0, -5); - } - $this->drupalRoot = rtrim($path . '/' . $install_path, '/'); - $this->vendorDir = $this->composerRoot . '/vendor'; - } - } - } - } - } - if ($this->composerRoot && file_exists($this->composerRoot . '/' . $this->getComposerFileName())) { - $json = json_decode( - file_get_contents($path . '/' . $this->getComposerFileName()), - true - ); - if (is_array($json) && isset($json['config']['vendor-dir'])) { - $this->vendorDir = $this->composerRoot . '/' . $json['config']['vendor-dir']; - } - } - - return $this->allPathsDiscovered(); - } - - /** - * @return string - */ - protected function getComposerFileName() - { - return trim(getenv('COMPOSER')) ?: 'composer.json'; - } - - /** - * Helper function to quickly determine whether or not all paths were discovered. - * - * @return bool - * True if all paths have been discovered, false if one or more haven't been found. */ - protected function allPathsDiscovered() { - return !empty($this->drupalRoot) && !empty($this->composerRoot) && !empty($this->vendorDir); - } - - /** - * Helper function to quickly determine whether or not all paths are known. - * - * @return bool - * True if all paths are known, false if one or more paths are unknown. - */ - protected function allPathsKnown() { - return !empty($this->getDrupalRoot()) && !empty($this->getComposerRoot()) && !empty($this->getVendorDir()); - } - - /** - * Get path stored in environment variable. - * - * @param string $variable - * The name of the environment variable to retrieve the path from. - * - * @return false|string - * A path if it is valid. False otherwise. - */ - protected function getValidEnvironmentVariablePath($variable) { - $path = getenv($variable); - if (is_string($path) && is_dir($path)) { - return $path; - } - return false; - } - - /** - * Returns parent directory. - * - * @param string - * Path to start from - * - * @return string|false - * Parent path of given path or false when $path is filesystem root - */ - private function shiftPathUp($path) + public function getVendorDir(): ?string { - $parent = dirname($path); - - return in_array($parent, ['.', $path]) ? false : $parent; + $reflection = new \ReflectionClass(InstalledVersions::class); + return realpath(dirname(dirname($reflection->getFileName()))); } } diff --git a/tests/Drupal7FinderTest.php b/tests/Drupal7FinderTest.php deleted file mode 100644 index 9d84d61..0000000 --- a/tests/Drupal7FinderTest.php +++ /dev/null @@ -1,169 +0,0 @@ - [ - 'common.inc' => '', - ], - 'misc' => [ - 'drupal.js' => '', - ], - 'sites' => [ - 'all' => [ - 'modules' => [] - ] - ] - ]; - - /** - * @return array - */ - protected function getDrupalComposerStructure() - { - $fileStructure = [ - 'web' => static::$fileStructure, - 'composer.json' => [ - 'require' => [ - 'drupal/drupal' => '*', - ], - 'extra' => [ - 'installer-paths' => [ - 'web/' => [ - 'type:drupal-core', - ], - ], - ], - ], - 'vendor' => [], - ]; - return $fileStructure; - } - - public function testDrupalComposerStructure() - { - $fileStructure = $this->getDrupalComposerStructure(); - $this->assertComposerStructure($fileStructure); - } - - public function testDrupalComposerStructureWithoutRequire() - { - $fileStructure = [ - 'web' => static::$fileStructure, - 'composer.json' => [ - 'extra' => [ - 'installer-paths' => [ - 'web' => [ - 'drupal/drupal', - ], - ], - ], - ], - ]; - $this->assertComposerStructure($fileStructure); - } - - public function testNoDrupalRootWithRealFilesystem() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - - $this->assertFalse($finder->locateRoot($root)); - $this->assertFalse($finder->getDrupalRoot()); - $this->assertFalse($finder->getComposerRoot()); - $this->assertFalse($finder->getVendorDir()); - } - - public function testDrupalComposerStructureWithRealFilesystem() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem($this->getDrupalComposerStructure(), $root); - - $this->assertTrue($finder->locateRoot($root)); - $this->assertSame($root . '/web', $finder->getDrupalRoot()); - $this->assertSame($root, $finder->getComposerRoot()); - $this->assertSame($root . '/vendor', $finder->getVendorDir()); - - // Test symlink implementation - $symlink = $this->tempdir(sys_get_temp_dir()); - $this->symlink($root, $symlink . '/foo'); - - $this->assertTrue($finder->locateRoot($symlink . '/foo')); - $this->assertSame($root . '/web', $finder->getDrupalRoot()); - $this->assertSame($root, $finder->getComposerRoot()); - $this->assertSame($root . '/vendor', $finder->getVendorDir()); - } - - public function testDrupalWithLinkedModule() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem($this->getDrupalComposerStructure(), $root); - - $module = $this->tempdir(sys_get_temp_dir()); - $module_link = $root . '/web/sites/all/modules/foo'; - $this->symlink($module, $module_link); - - $this->assertTrue($finder->locateRoot($module_link)); - $this->assertSame($root . '/web', realpath($finder->getDrupalRoot())); - $this->assertSame($root, realpath($finder->getComposerRoot())); - $this->assertSame($root . '/vendor', realpath($finder->getVendorDir())); - } - - public function testDrupalWithCustomVendor() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - $fileStructure = $this->getDrupalComposerStructure(); - $composerJson = $fileStructure['composer.json']; - $composerJson['config']['vendor-dir'] = 'vendor-foo'; - $fileStructure['composer.json'] = $composerJson; - $fileStructure['vendor-foo'] = []; - $this->dumpToFileSystem($fileStructure, $root); - - $this->assertTrue($finder->locateRoot($root)); - $this->assertSame($root . '/web', realpath($finder->getDrupalRoot())); - $this->assertSame($root, realpath($finder->getComposerRoot())); - $this->assertSame($root . '/vendor-foo', realpath($finder->getVendorDir())); - } - - /** - * @param $fileStructure - */ - protected function assertComposerStructure($fileStructure) - { - $finder = new DrupalFinder(); - $fileStructure = $this->prepareFileStructure($fileStructure); - $root = vfsStream::setup('root', null, $fileStructure); - $this->assertTrue($finder->locateRoot($root->url() . '/web')); - $this->assertSame('vfs://root/web', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url() . '/web/misc')); - $this->assertSame('vfs://root/web', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url())); - $this->assertSame('vfs://root/web', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $root = vfsStream::setup( - 'root', - null, - ['nested_folder' => $fileStructure] - ); - $this->assertFalse($finder->locateRoot($root->url())); - $this->assertFalse($finder->getDrupalRoot()); - $this->assertFalse($finder->getComposerRoot()); - $this->assertFalse($finder->getVendorDir()); - } -} diff --git a/tests/Drupal8FinderTest.php b/tests/Drupal8FinderTest.php deleted file mode 100644 index 2d00e44..0000000 --- a/tests/Drupal8FinderTest.php +++ /dev/null @@ -1,343 +0,0 @@ - '', - 'composer.json' => [ - 'extra' => [ - 'installer-paths' => [ - 'core' => [ - 'type:drupal-core' - ] - ] - ] - ], - 'core' => [ - 'includes' => [ - 'common.inc' => '', - ], - 'misc' => [ - 'drupal.js' => '', - ], - 'core.services.yml' => '', - ], - 'modules' => [], - 'vendor' => [], - ]; - - protected static $fileStructureDrupal_8_8_x = [ - 'autoload.php' => '', - 'composer.json' => [ - 'name' => 'drupal/drupal', - 'require' => [ - 'drupal/core' => 'self.version', - ], - 'extra' => [ - 'installer-paths' => [ - 'vendor/drupal/core' => [ - 'type:drupal-core', - ], - ], - ], - ], - 'core' => [ - 'includes' => [ - 'common.inc' => '', - ], - 'misc' => [ - 'drupal.js' => '', - ], - 'core.services.yml' => '', - ], - 'modules' => [], - 'vendor' => [], - ]; - - /** - * @return array - */ - protected function getDrupalComposerStructure() - { - $fileStructure = [ - 'web' => static::$fileStructure, - 'composer.json' => [ - 'require' => [ - 'drupal/core' => '*', - ], - 'extra' => [ - 'installer-paths' => [ - 'web/core' => [ - 'type:drupal-core', - ], - ], - ], - ], - 'vendor' => [], - ]; - unset($fileStructure['web']['composer.json']); - unset($fileStructure['web']['vendor']); - - return $fileStructure; - } - - public function testDrupalDefaultStructure() - { - $finder = new DrupalFinder(); - $root = vfsStream::setup('root', null, $this->prepareFileStructure(static::$fileStructure)); - - $this->assertTrue($finder->locateRoot($root->url())); - $this->assertSame('vfs://root', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url() . '/misc')); - $this->assertSame('vfs://root', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $root = vfsStream::setup( - 'root', - null, - ['project' => $this->prepareFileStructure(static::$fileStructure)] - ); - $this->assertFalse( - $finder->locateRoot($root->url()), - 'Not in the scope of the project' - ); - $this->assertFalse($finder->getDrupalRoot()); - $this->assertFalse($finder->getComposerRoot()); - $this->assertFalse($finder->getVendorDir()); - } - - public function testDrupalDefaultStructure_8_8_x() - { - $finder = new DrupalFinder(); - $root = vfsStream::setup('root', null, $this->prepareFileStructure(static::$fileStructureDrupal_8_8_x)); - - $this->assertTrue($finder->locateRoot($root->url())); - $this->assertSame('vfs://root', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url() . '/misc')); - $this->assertSame('vfs://root', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $root = vfsStream::setup( - 'root', - null, - ['project' => $this->prepareFileStructure(static::$fileStructure)] - ); - $this->assertFalse( - $finder->locateRoot($root->url()), - 'Not in the scope of the project' - ); - $this->assertFalse($finder->getDrupalRoot()); - $this->assertFalse($finder->getComposerRoot()); - $this->assertFalse($finder->getVendorDir()); - } - - public function testDrupalComposerStructure() - { - $fileStructure = $this->getDrupalComposerStructure(); - $this->assertComposerStructure($fileStructure); - } - - public function testDrupalComposerStructureWithCustomRoot() - { - $finder = new DrupalFinder(); - $fileStructure = [ - 'src' => static::$fileStructure, - 'composer.json' => [ - 'require' => [ - 'drupal/core' => '*', - ], - 'extra' => [ - 'installer-paths' => [ - 'src/core' => [ - 'type:drupal-core', - ], - ], - ], - ], - 'vendor' => [], - ]; - unset($fileStructure['src']['composer.json']); - unset($fileStructure['src']['vendor']); - - $fileStructure = $this->prepareFileStructure($fileStructure); - $root = vfsStream::setup('root', null, $fileStructure); - $this->assertTrue($finder->locateRoot($root->url() . '/src')); - $this->assertSame('vfs://root/src', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url() . '/src/misc')); - $this->assertSame('vfs://root/src', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url())); - $this->assertSame('vfs://root/src', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $root = vfsStream::setup( - 'root', - null, - ['nested_folder' => $fileStructure] - ); - $this->assertFalse($finder->locateRoot($root->url())); - $this->assertFalse($finder->getDrupalRoot()); - $this->assertFalse($finder->getComposerRoot()); - $this->assertFalse($finder->getVendorDir()); - } - - public function testDrupalComposerStructureWithoutRequire() - { - $fileStructure = [ - 'web' => static::$fileStructure, - 'composer.json' => [ - 'extra' => [ - 'installer-paths' => [ - 'web/core' => [ - 'drupal/core', - ], - ], - ], - ], - ]; - unset($fileStructure['web']['composer.json']); - $this->assertComposerStructure($fileStructure); - } - - public function testNoDrupalRootWithRealFilesystem() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - - $this->assertFalse($finder->locateRoot($root)); - $this->assertFalse($finder->getDrupalRoot()); - $this->assertFalse($finder->getComposerRoot()); - $this->assertFalse($finder->getVendorDir()); - } - - public function testDrupalDefaultStructureWithRealFilesystem() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem(static::$fileStructure, $root); - - $this->assertTrue($finder->locateRoot($root)); - $this->assertSame($root, $finder->getDrupalRoot()); - $this->assertSame($root, $finder->getComposerRoot()); - $this->assertSame($root . '/vendor', $finder->getVendorDir()); - - // Test symlink implementation - $symlink = $this->tempdir(sys_get_temp_dir()); - $this->symlink($root, $symlink . '/foo'); - - $this->assertTrue($finder->locateRoot($symlink . '/foo')); - $this->assertSame($root, $finder->getDrupalRoot()); - $this->assertSame($root, $finder->getComposerRoot()); - $this->assertSame($root . '/vendor', $finder->getVendorDir()); - } - - public function testDrupalComposerStructureWithRealFilesystem() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem($this->getDrupalComposerStructure(), $root); - - $this->assertTrue($finder->locateRoot($root)); - $this->assertSame($root . '/web', $finder->getDrupalRoot()); - $this->assertSame($root, $finder->getComposerRoot()); - $this->assertSame($root . '/vendor', $finder->getVendorDir()); - - // Test symlink implementation - $symlink = $this->tempdir(sys_get_temp_dir()); - $this->symlink($root, $symlink . '/foo'); - - $this->assertTrue($finder->locateRoot($symlink . '/foo')); - $this->assertSame($root . '/web', $finder->getDrupalRoot()); - $this->assertSame($root, $finder->getComposerRoot()); - $this->assertSame($root . '/vendor', $finder->getVendorDir()); - } - - public function testDrupalWithLinkedModule() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem(static::$fileStructure, $root); - - $module = $this->tempdir(sys_get_temp_dir()); - $module_link = $root . '/modules/foo'; - $this->symlink($module, $module_link); - - $this->assertTrue($finder->locateRoot($module_link)); - $this->assertSame($root, realpath($finder->getDrupalRoot())); - $this->assertSame($root, realpath($finder->getComposerRoot())); - $this->assertSame($root . '/vendor', realpath($finder->getVendorDir())); - } - - public function testDrupalWithCustomVendor() - { - $finder = new DrupalFinder(); - $root = $this->tempdir(sys_get_temp_dir()); - $fileStructure = static::$fileStructure; - $fileStructure['composer.json'] = [ - 'config' => [ - 'vendor-dir' => 'vendor-foo' - ] - ]; - $fileStructure['vendor-foo'] = []; - $this->dumpToFileSystem($fileStructure, $root); - - $this->assertTrue($finder->locateRoot($root)); - $this->assertSame($root, realpath($finder->getDrupalRoot())); - $this->assertSame($root, realpath($finder->getComposerRoot())); - $this->assertSame($root . '/vendor-foo', realpath($finder->getVendorDir())); - } - - /** - * @param $fileStructure - */ - protected function assertComposerStructure($fileStructure) - { - $finder = new DrupalFinder(); - $fileStructure = $this->prepareFileStructure($fileStructure); - $root = vfsStream::setup('root', null, $fileStructure); - $this->assertTrue($finder->locateRoot($root->url() . '/web')); - $this->assertSame('vfs://root/web', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url() . '/web/misc')); - $this->assertSame('vfs://root/web', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $this->assertTrue($finder->locateRoot($root->url())); - $this->assertSame('vfs://root/web', $finder->getDrupalRoot()); - $this->assertSame('vfs://root', $finder->getComposerRoot()); - $this->assertSame('vfs://root/vendor', $finder->getVendorDir()); - - $root = vfsStream::setup( - 'root', - null, - ['nested_folder' => $fileStructure] - ); - $this->assertFalse($finder->locateRoot($root->url())); - $this->assertFalse($finder->getDrupalRoot()); - $this->assertFalse($finder->getComposerRoot()); - $this->assertFalse($finder->getVendorDir()); - } - -} diff --git a/tests/DrupalFinderTest.php b/tests/DrupalFinderTest.php new file mode 100644 index 0000000..c525a90 --- /dev/null +++ b/tests/DrupalFinderTest.php @@ -0,0 +1,39 @@ +assertDirectoryExists($basePath . '/vendor', static::installFixtures); + $this->assertDirectoryExists($basePath . '/web', static::installFixtures); + + $result = json_decode(require $basePath . '/drupal-finder.php', TRUE); + $this->assertSame($result['getComposerRoot'], $basePath); + $this->assertSame($result['getVendorDir'], $basePath . '/vendor'); + $this->assertSame($result['getDrupalRoot'], $basePath . '/web'); + } + + /** + * @runInSeparateProcess + */ + public function testCustomVendor() { + $basePath = realpath( __DIR__ . '/fixtures/custom-vendor'); + $this->assertDirectoryExists($basePath . '/foo/bar', static::installFixtures); + $this->assertDirectoryExists($basePath . '/foo/bar/drupal', static::installFixtures); + + $result = json_decode(require $basePath . '/drupal-finder.php', TRUE); + $this->assertSame($result['getComposerRoot'], $basePath); + $this->assertSame($result['getVendorDir'], $basePath . '/foo/bar'); + $this->assertSame($result['getDrupalRoot'], $basePath . '/foo/bar/drupal'); + } + +} diff --git a/tests/DrupalFinderTestBase.php b/tests/DrupalFinderTestBase.php deleted file mode 100644 index d8c99a3..0000000 --- a/tests/DrupalFinderTestBase.php +++ /dev/null @@ -1,184 +0,0 @@ -envNameDrupal = DrupalFinder::ENV_DRUPAL_ROOT; - $this->envNameComposer = DrupalFinder::ENV_COMPOSER_ROOT; - $this->envNameVendor = DrupalFinder::ENV_VENDOR_DIR; - } - - protected function tearDown(): void - { - parent::tearDown(); - // Unset variables to ensure their values don't carry over into other - // tests that are going to run. - putenv('DRUPAL_FINDER_DRUPAL_ROOT'); - putenv('DRUPAL_FINDER_COMPOSER_ROOT'); - putenv('DRUPAL_FINDER_VENDOR_DIR'); - } - - public function testOnlyDrupalEnvironmentVariable() { - $finder = new DrupalFinder(); - $fileStructure = [ - 'web' => [], - ]; - - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem($fileStructure, $root); - - $drupal_root = $root . '/web'; - - putenv("{$this->envNameDrupal}=$drupal_root"); - - // DrupalFinder::locateRoot should be true if the Drupal root is known. - $this->assertTrue($finder->locateRoot($root)); - $this->assertSame($finder->getDrupalRoot(), $drupal_root); - } - - public function testOnlyVendorEnvironmentVariable() { - $finder = new DrupalFinder(); - $fileStructure = [ - 'vendor' => [], - ]; - - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem($fileStructure, $root); - - $vendor_dir = $root . '/vendor'; - - putenv("{$this->envNameVendor}=$vendor_dir"); - - // DrupalFinder::locateRoot should be false since Drupal root is unknown. - $this->assertFalse($finder->locateRoot($root)); - $this->assertSame($finder->getVendorDir(), $vendor_dir); - } - - public function testOnlyComposerEnvironmentVariable() { - $finder = new DrupalFinder(); - $fileStructure = []; - - $root = $this->tempdir(sys_get_temp_dir()); - $this->dumpToFileSystem($fileStructure, $root); - - $composer_dir = $root; - - putenv("{$this->envNameComposer}=$composer_dir"); - - // DrupalFinder::locateRoot should be false since Drupal root is unknown. - $this->assertFalse($finder->locateRoot($root)); - $this->assertSame($finder->getComposerRoot(), $composer_dir); - } - - protected function dumpToFileSystem($fileStructure, $root) - { - $fileStructure = $this->prepareFileStructure($fileStructure); - foreach ($fileStructure as $name => $content) { - if (is_array($content)) { - mkdir($root . '/' . $name); - $this->dumpToFileSystem($content, $root . '/' . $name); - } else { - file_put_contents($root . '/' . $name, $content); - } - } - } - - protected function prepareFileStructure($fileStructure) - { - foreach ($fileStructure as $name => $content) { - if (($name === 'composer.json' || $name === 'composer.lock') && is_array($content)) { - $fileStructure[$name] = json_encode($content, JSON_UNESCAPED_SLASHES); - } elseif (is_array($content)) { - $fileStructure[$name] = $this->prepareFileStructure($content); - } - } - return $fileStructure; - } - - protected function tempdir($dir, $prefix = '', $mode = 0700) - { - if (substr($dir, -1) != '/') { - $dir .= '/'; - } - do { - $path = $dir . $prefix . mt_rand(0, 9999999); - } while (!mkdir($path, $mode)); - register_shutdown_function( - [static::class, 'tempdir_remove'], - $path - ); - - return realpath($path); - } - - public static function tempdir_remove($path) - { - if (is_link($path)) { - if (defined('PHP_WINDOWS_VERSION_BUILD')) { - rmdir($path); - } else { - unlink($path); - } - - return; - } - - foreach (scandir($path) as $child) { - if (in_array($child, ['.', '..'])) { - continue; - } - $child = "$path/$child"; - is_dir($child) ? static::tempdir_remove($child) : unlink($child); - } - rmdir($path); - } - - /** - * @param $target - * @param $link - * - * @throws SkippedTestError - */ - protected function symlink($target, $link) - { - try { - return symlink($target, $link); - } catch (Exception $e) { - if (defined('PHP_WINDOWS_VERSION_BUILD') - && strstr($e->getMessage(), WIN_ERROR_PRIVILEGE_NOT_HELD) - ) { - $this->markTestSkipped(<<<'MESSAGE' -No privilege to create symlinks. Run test as Administrator (elevated process). -MESSAGE - ); - } - throw $e; - } - } -} - -define('WIN_ERROR_PRIVILEGE_NOT_HELD', '1314'); diff --git a/tests/fixtures/custom-vendor/composer.json b/tests/fixtures/custom-vendor/composer.json new file mode 100644 index 0000000..0d1a7f3 --- /dev/null +++ b/tests/fixtures/custom-vendor/composer.json @@ -0,0 +1,17 @@ +{ + "repositories": { + "drupal-finder": { + "type": "path", + "url": "../../../" + } + }, + "require": { + "webflo/drupal-finder": "*", + "drupal/core": "^10" + }, + "config": { + "vendor-dir": "foo/bar" + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/tests/fixtures/custom-vendor/drupal-finder.php b/tests/fixtures/custom-vendor/drupal-finder.php new file mode 100644 index 0000000..89e6dd5 --- /dev/null +++ b/tests/fixtures/custom-vendor/drupal-finder.php @@ -0,0 +1,14 @@ + $finder->getComposerRoot(), + 'getVendorDir' => $finder->getVendorDir(), + 'getDrupalRoot' => $finder->getDrupalRoot(), + ] +); diff --git a/tests/fixtures/default/composer.json b/tests/fixtures/default/composer.json new file mode 100644 index 0000000..7741bd3 --- /dev/null +++ b/tests/fixtures/default/composer.json @@ -0,0 +1,27 @@ +{ + "repositories": { + "drupal-finder": { + "type": "path", + "url": "../../../" + } + }, + "require": { + "composer/installers": "^2.2", + "drupal/core": "^10", + "webflo/drupal-finder": "*" + }, + "config": { + "allow-plugins": { + "composer/installers": true + } + }, + "extra": { + "installer-paths": { + "web/core": [ + "type:drupal-core" + ] + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/tests/fixtures/default/drupal-finder.php b/tests/fixtures/default/drupal-finder.php new file mode 100644 index 0000000..2669234 --- /dev/null +++ b/tests/fixtures/default/drupal-finder.php @@ -0,0 +1,14 @@ + $finder->getComposerRoot(), + 'getVendorDir' => $finder->getVendorDir(), + 'getDrupalRoot' => $finder->getDrupalRoot(), + ] +);