Issue #3391776 by alexpott, smustgrave, catch: InfoParser returns an empty array if passed a non-existing file

merge-requests/5548/head
Dave Long 2023-11-25 11:33:10 +00:00
parent 184f22eef0
commit 5ef2211afc
No known key found for this signature in database
GPG Key ID: ED52AE211E142771
12 changed files with 76 additions and 149 deletions

View File

@ -37,68 +37,68 @@ class InfoParserDynamic implements InfoParserInterface {
*/
public function parse($filename) {
if (!file_exists($filename)) {
$parsed_info = [];
throw new InfoParserException("Unable to parse $filename as it does not exist");
}
else {
try {
$parsed_info = Yaml::decode(file_get_contents($filename));
}
catch (InvalidDataTypeException $e) {
throw new InfoParserException("Unable to parse $filename " . $e->getMessage());
}
$missing_keys = array_diff($this->getRequiredKeys(), array_keys($parsed_info));
if (!empty($missing_keys)) {
throw new InfoParserException('Missing required keys (' . implode(', ', $missing_keys) . ') in ' . $filename);
}
if (!isset($parsed_info['core_version_requirement'])) {
if (str_starts_with($filename, 'core/') || str_starts_with($filename, $this->root . '/core/')) {
// Core extensions do not need to specify core compatibility: they are
// by definition compatible so a sensible default is used. Core
// modules are allowed to provide these for testing purposes.
$parsed_info['core_version_requirement'] = \Drupal::VERSION;
}
elseif (isset($parsed_info['package']) && $parsed_info['package'] === 'Testing') {
// Modules in the testing package are exempt as well. This makes it
// easier for contrib to use test modules.
$parsed_info['core_version_requirement'] = \Drupal::VERSION;
}
else {
// Non-core extensions must specify core compatibility.
throw new InfoParserException("The 'core_version_requirement' key must be present in " . $filename);
}
}
// Determine if the extension is compatible with the current version of
// Drupal core.
try {
$parsed_info['core_incompatible'] = !Semver::satisfies(\Drupal::VERSION, $parsed_info['core_version_requirement']);
try {
$parsed_info = Yaml::decode(file_get_contents($filename));
}
catch (InvalidDataTypeException $e) {
throw new InfoParserException("Unable to parse $filename " . $e->getMessage());
}
$missing_keys = array_diff($this->getRequiredKeys(), array_keys($parsed_info));
if (!empty($missing_keys)) {
throw new InfoParserException('Missing required keys (' . implode(', ', $missing_keys) . ') in ' . $filename);
}
if (!isset($parsed_info['core_version_requirement'])) {
if (str_starts_with($filename, 'core/') || str_starts_with($filename, $this->root . '/core/')) {
// Core extensions do not need to specify core compatibility: they are
// by definition compatible so a sensible default is used. Core
// modules are allowed to provide these for testing purposes.
$parsed_info['core_version_requirement'] = \Drupal::VERSION;
}
catch (\UnexpectedValueException $exception) {
throw new InfoParserException("The 'core_version_requirement' constraint ({$parsed_info['core_version_requirement']}) is not a valid value in $filename");
elseif (isset($parsed_info['package']) && $parsed_info['package'] === 'Testing') {
// Modules in the testing package are exempt as well. This makes it
// easier for contrib to use test modules.
$parsed_info['core_version_requirement'] = \Drupal::VERSION;
}
if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
$parsed_info['version'] = \Drupal::VERSION;
}
$parsed_info += [ExtensionLifecycle::LIFECYCLE_IDENTIFIER => ExtensionLifecycle::STABLE];
$lifecycle = $parsed_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER];
if (!ExtensionLifecycle::isValid($lifecycle)) {
$valid_values = [
ExtensionLifecycle::EXPERIMENTAL,
ExtensionLifecycle::STABLE,
ExtensionLifecycle::DEPRECATED,
ExtensionLifecycle::OBSOLETE,
];
throw new InfoParserException("'lifecycle: {$lifecycle}' is not valid in $filename. Valid values are: '" . implode("', '", $valid_values) . "'.");
}
if (in_array($lifecycle, [ExtensionLifecycle::DEPRECATED, ExtensionLifecycle::OBSOLETE], TRUE)) {
if (empty($parsed_info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER])) {
throw new InfoParserException(sprintf("Extension %s (%s) has 'lifecycle: %s' but is missing a '%s' entry.", $parsed_info['name'], $filename, $lifecycle, ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER));
}
if (!filter_var($parsed_info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER], FILTER_VALIDATE_URL)) {
throw new InfoParserException(sprintf("Extension %s (%s) has a '%s' entry that is not a valid URL.", $parsed_info['name'], $filename, ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER));
}
else {
// Non-core extensions must specify core compatibility.
throw new InfoParserException("The 'core_version_requirement' key must be present in " . $filename);
}
}
// Determine if the extension is compatible with the current version of
// Drupal core.
try {
$parsed_info['core_incompatible'] = !Semver::satisfies(\Drupal::VERSION, $parsed_info['core_version_requirement']);
}
catch (\UnexpectedValueException $exception) {
throw new InfoParserException("The 'core_version_requirement' constraint ({$parsed_info['core_version_requirement']}) is not a valid value in $filename");
}
if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
$parsed_info['version'] = \Drupal::VERSION;
}
$parsed_info += [ExtensionLifecycle::LIFECYCLE_IDENTIFIER => ExtensionLifecycle::STABLE];
$lifecycle = $parsed_info[ExtensionLifecycle::LIFECYCLE_IDENTIFIER];
if (!ExtensionLifecycle::isValid($lifecycle)) {
$valid_values = [
ExtensionLifecycle::EXPERIMENTAL,
ExtensionLifecycle::STABLE,
ExtensionLifecycle::DEPRECATED,
ExtensionLifecycle::OBSOLETE,
];
throw new InfoParserException("'lifecycle: {$lifecycle}' is not valid in $filename. Valid values are: '" . implode("', '", $valid_values) . "'.");
}
if (in_array($lifecycle, [ExtensionLifecycle::DEPRECATED, ExtensionLifecycle::OBSOLETE], TRUE)) {
if (empty($parsed_info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER])) {
throw new InfoParserException(sprintf("Extension %s (%s) has 'lifecycle: %s' but is missing a '%s' entry.", $parsed_info['name'], $filename, $lifecycle, ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER));
}
if (!filter_var($parsed_info[ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER], FILTER_VALIDATE_URL)) {
throw new InfoParserException(sprintf("Extension %s (%s) has a '%s' entry that is not a valid URL.", $parsed_info['name'], $filename, ExtensionLifecycle::LIFECYCLE_LINK_IDENTIFIER));
}
}
return $parsed_info;
}

View File

@ -633,8 +633,6 @@ trait FunctionalTestSetupTrait {
$this->classLoader = require __DIR__ . '/../../../../../autoload.php';
$request = Request::createFromGlobals();
$kernel = TestRunnerKernel::createFromRequest($request, $this->classLoader);
// TestRunnerKernel expects the working directory to be DRUPAL_ROOT.
chdir(DRUPAL_ROOT);
$kernel->boot();
$kernel->preHandle($request);
$this->prepareDatabasePrefix();

View File

@ -2,12 +2,9 @@
namespace Drupal\KernelTests\Core\Theme;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Extension\InfoParserException;
use Drupal\Core\Extension\ThemeExtensionList;
use Drupal\Core\Site\Settings;
use Drupal\KernelTests\KernelTestBase;
use org\bovigo\vfs\vfsStream;
/**
* Tests the behavior of a theme when base_theme info key is missing.
@ -34,80 +31,23 @@ class BaseThemeMissingTest extends KernelTestBase {
protected function setUp(): void {
parent::setUp();
// Add a directory to extension discovery to find the theme with a missing
// base class.
// @see \Drupal\Core\Extension\ExtensionDiscovery::scan()
$settings = Settings::getAll();
$settings['test_parent_site'] = 'core/tests/fixtures/test_missing_base_theme';
new Settings($settings);
$this->themeInstaller = $this->container->get('theme_installer');
}
/**
* {@inheritdoc}
*/
public function register(ContainerBuilder $container) {
parent::register($container);
$container->getDefinition('extension.list.theme')
->setClass(VfsThemeExtensionList::class);
}
/**
* {@inheritdoc}
*/
protected function setUpFilesystem() {
parent::setUpFilesystem();
$vfs_root = vfsStream::setup('core');
vfsStream::create([
'themes' => [
'test_missing_base_theme' => [
'test_missing_base_theme.info.yml' => file_get_contents(DRUPAL_ROOT . '/core/tests/fixtures/test_missing_base_theme/test_missing_base_theme.info.yml'),
'test_missing_base_theme.theme' => file_get_contents(DRUPAL_ROOT . '/core/tests/fixtures/test_missing_base_theme/test_missing_base_theme.theme'),
],
],
], $vfs_root);
}
/**
* Tests exception is thrown.
*/
public function testMissingBaseThemeException() {
$this->container->get('extension.list.theme')
->setExtensionDiscovery(new ExtensionDiscovery('vfs://core'));
$this->expectException(InfoParserException::class);
$this->expectExceptionMessage('Missing required key ("base theme") in themes/test_missing_base_theme/test_missing_base_theme.info.yml, see https://www.drupal.org/node/3066038');
$this->expectExceptionMessage('Missing required key ("base theme") in core/tests/fixtures/test_missing_base_theme/test_missing_base_theme.info.yml, see https://www.drupal.org/node/3066038');
$this->themeInstaller->install(['test_missing_base_theme']);
}
}
/**
* Test theme extension list class.
*/
class VfsThemeExtensionList extends ThemeExtensionList {
/**
* The extension discovery for this extension list.
*
* @var \Drupal\Core\Extension\ExtensionDiscovery
*/
protected $extensionDiscovery;
/**
* Sets the extension discovery.
*
* @param \Drupal\Core\Extension\ExtensionDiscovery $discovery
* The extension discovery.
*
* @return self
*/
public function setExtensionDiscovery(ExtensionDiscovery $discovery) {
$this->extensionDiscovery = $discovery;
return $this;
}
/**
* {@inheritdoc}
*/
public function getExtensionDiscovery() {
return $this->extensionDiscovery;
}
}

View File

@ -241,9 +241,6 @@ abstract class KernelTestBase extends TestCase implements ServiceProviderInterfa
public static function setUpBeforeClass(): void {
parent::setUpBeforeClass();
VarDumper::setHandler(TestVarDumper::class . '::cliHandler');
// Change the current dir to DRUPAL_ROOT.
chdir(static::getDrupalRoot());
}
/**

View File

@ -30,6 +30,8 @@ class TestSiteReleaseLocksCommand extends Command {
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$root = dirname(__DIR__, 5);
chdir($root);
TestDatabase::releaseAllTestLocks();
$output->writeln('<info>Successfully released all the test database locks</info>');
return 0;

View File

@ -38,6 +38,8 @@ class TestSiteTearDownCommand extends Command {
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$root = dirname(__DIR__, 5);
chdir($root);
$db_prefix = $input->getArgument('db-prefix');
// Validate the db_prefix argument.
try {

View File

@ -247,11 +247,6 @@ class CssOptimizerUnitTest extends UnitTestCase {
$original_base_path = $base_path;
$base_path = '/';
// \Drupal\Core\Asset\CssOptimizer::loadFile() relies on the current working
// directory being the one that is used when index.php is the entry point.
// Note: PHPUnit automatically restores the original working directory.
chdir(realpath(__DIR__ . '/../../../../../../'));
$this->assertEquals($expected, $this->optimizer->optimize($css_asset), 'Group of file CSS assets optimized correctly.');
$base_path = $original_base_path;

View File

@ -54,7 +54,6 @@ class QuickStartTest extends TestCase {
$php_executable_finder = new PhpExecutableFinder();
$this->php = $php_executable_finder->find();
$this->root = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)), 2);
chdir($this->root);
if (!is_writable("{$this->root}/sites/simpletest")) {
$this->markTestSkipped('This test requires a writable sites/simpletest directory');
}

View File

@ -21,16 +21,6 @@ use Drupal\Tests\UnitTestCase;
*/
class UrlConversionTest extends UnitTestCase {
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->root = dirname(__FILE__, 7);
// This unit test relies on reading files relative to Drupal root.
chdir($this->root);
}
/**
* @covers ::convertDbUrlToConnectionInfo
*

View File

@ -45,8 +45,9 @@ class InfoParserUnitTest extends UnitTestCase {
*/
public function testInfoParserNonExisting() {
vfsStream::setup('modules');
$info = $this->infoParser->parse(vfsStream::url('modules') . '/does_not_exist.info.txt');
$this->assertEmpty($info, 'Non existing info.yml returns empty array.');
$this->expectException('\Drupal\Core\Extension\InfoParserException');
$this->expectExceptionMessage('Unable to parse vfs://modules/does_not_exist.info.txt as it does not exist');
$this->infoParser->parse(vfsStream::url('modules') . '/does_not_exist.info.txt');
}
/**

View File

@ -42,7 +42,6 @@ class TestSiteApplicationTest extends UnitTestCase {
parent::setUp();
$php_executable_finder = new PhpExecutableFinder();
$this->php = $php_executable_finder->find();
$this->root = dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)), 2);
}
/**

View File

@ -179,3 +179,7 @@ if (getenv('SYMFONY_DEPRECATIONS_HELPER') === FALSE) {
$deprecation_ignore_filename = realpath(__DIR__ . "/../.deprecation-ignore.txt");
putenv("SYMFONY_DEPRECATIONS_HELPER=ignoreFile=$deprecation_ignore_filename");
}
// Drupal expects to be run from its root directory. This ensures all test types
// are consistent.
chdir(dirname(__DIR__, 2));