Merge branch '3.x' into 4.x

* 3.x:
  Remove Drupal integration tests
  Do not add : void for test methods
  Fix deprecated PHP CS Fixer rules
  Replace FQCN with use statements in SandboxTest
  Fix CS

# Conflicts:
#	.github/workflows/ci.yml
#	tests/Extension/SandboxTest.php
This commit is contained in:
Fabien Potencier
2026-05-22 12:46:51 +02:00
4 changed files with 41 additions and 99 deletions
-32
View File
@@ -98,38 +98,6 @@ jobs:
LANG: 'en_US.UTF-8'
run: phpunit
integration-tests:
if: "false" # TODO re-enable that job when Drupal does not use features deprecated in 3.x anymore
needs:
- 'tests'
name: "Integration tests with PHP ${{ matrix.php-version }}"
runs-on: 'ubuntu-latest'
continue-on-error: true
strategy:
matrix:
php-version:
- '8.2'
steps:
- name: "Checkout code"
uses: actions/checkout@v4
- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
coverage: "none"
extensions: "gd, pdo_sqlite, uuid"
php-version: ${{ matrix.php-version }}
ini-values: memory_limit=-1
tools: composer:v2
- run: bash ./tests/drupal_test.sh
shell: "bash"
phpstan:
name: "PHPStan"
+21 -2
View File
@@ -8,12 +8,11 @@ return (new Config())
->setRules([
'@Symfony' => true,
'@Symfony:risky' => true,
'@PHPUnit75Migration:risky' => true,
'@PHPUnit7x5Migration:risky' => true,
'php_unit_dedicate_assert' => ['target' => '5.6'],
'array_syntax' => ['syntax' => 'short'],
'php_unit_fqcn_annotation' => true,
'no_unreachable_default_argument_value' => false,
'braces' => ['allow_single_line_closure' => true],
'heredoc_to_nowdoc' => false,
'single_line_throw' => false,
'phpdoc_to_comment' => ['ignored_tags' => ['var']],
@@ -21,6 +20,26 @@ return (new Config())
'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
'no_superfluous_phpdoc_tags' => ['allow_mixed' => true],
])
->setRuleCustomisationPolicy(new class implements PhpCsFixer\Config\RuleCustomisationPolicyInterface {
public function getPolicyVersionForCache(): string
{
return hash_file('xxh128', __FILE__);
}
public function getRuleCustomisers(): array
{
return [
'void_return' => static function (SplFileInfo $file) {
// temporary hack due to bug: https://github.com/symfony/symfony/issues/62734
if (!$file instanceof Symfony\Component\Finder\SplFileInfo) {
return false;
}
return !str_contains($file->getRelativePathname(), '/Tests/');
},
];
}
})
->setRiskyAllowed(true)
->setParallelConfig(ParallelConfigFactory::detect())
->setFinder((new Finder())->in(__DIR__))
+20 -14
View File
@@ -34,7 +34,11 @@ use Twig\Sandbox\SecurityNotAllowedMethodError;
use Twig\Sandbox\SecurityNotAllowedPropertyError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityPolicy;
use Twig\Sandbox\SourcePolicyInterface;
use Twig\Source;
use Twig\TwigFilter;
use Twig\TwigFunction;
use Twig\TwigTest;
class SandboxTest extends TestCase
{
@@ -252,7 +256,7 @@ class SandboxTest extends TestCase
public function testSandboxUnallowedToString($template)
{
$twig = $this->getEnvironment(true, [], ['index' => $template], ['if', 'do', 'for', 'set'], ['upper', 'join', 'replace', 'format', 'split'], [FooObject::class => 'getAnotherFooObject'], [], ['random', 'range', 'my_func']);
$twig->addFunction(new \Twig\TwigFunction('my_func', fn ($a) => (string) $a));
$twig->addFunction(new TwigFunction('my_func', static fn ($a) => (string) $a));
try {
$twig->load('index')->render(self::$params);
$this->fail('Sandbox throws a SecurityError exception if an unallowed method "__toString()" method is called in the template');
@@ -329,7 +333,7 @@ class SandboxTest extends TestCase
public function testSandboxBlocksToStringOnFunctionReturn()
{
$twig = $this->getEnvironment(true, [], ['index' => '{{ make_obj() }}'], [], [], [], [], ['make_obj']);
$twig->addFunction(new \Twig\TwigFunction('make_obj', fn () => new FooObject()));
$twig->addFunction(new TwigFunction('make_obj', static fn () => new FooObject()));
try {
$twig->load('index')->render([]);
$this->fail('Sandbox throws a SecurityError exception if __toString is called on the return of an allowed function');
@@ -342,7 +346,7 @@ class SandboxTest extends TestCase
public function testSandboxBlocksToStringOnFilterReturn()
{
$twig = $this->getEnvironment(true, [], ['index' => '{{ "x"|to_obj }}'], [], ['to_obj']);
$twig->addFilter(new \Twig\TwigFilter('to_obj', fn () => new FooObject()));
$twig->addFilter(new TwigFilter('to_obj', static fn () => new FooObject()));
try {
$twig->load('index')->render([]);
$this->fail('Sandbox throws a SecurityError exception if __toString is called on the return of an allowed filter');
@@ -646,6 +650,7 @@ EOF
if (str_contains($message, 'BAD-MACRO-REF')) {
$triggered = true;
}
return true;
}, \E_USER_NOTICE | \E_USER_WARNING);
try {
@@ -676,6 +681,7 @@ EOF
if (str_contains($message, 'BAD-IMPORT-REF')) {
$triggered = true;
}
return true;
}, \E_USER_NOTICE | \E_USER_WARNING);
try {
@@ -835,7 +841,7 @@ EOF
public function testSandboxSourcePolicyEnableReturningFalse()
{
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return '1_basic' != $source->getName();
@@ -846,7 +852,7 @@ EOF
public function testSandboxSourcePolicyEnableReturningTrue()
{
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
$twig = $this->getEnvironment(false, [], self::$templates, [], [], [], [], [], new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return '1_basic' === $source->getName();
@@ -858,7 +864,7 @@ EOF
public function testSandboxSourcePolicyFalseDoesntOverrideOtherEnables()
{
$twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
$twig = $this->getEnvironment(true, [], self::$templates, [], [], [], [], [], new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return false;
@@ -870,7 +876,7 @@ EOF
public function testSourcePolicyAllowsClosureInArrow()
{
$sourcePolicy = new class implements \Twig\Sandbox\SourcePolicyInterface {
$sourcePolicy = new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return true;
@@ -884,7 +890,7 @@ EOF
public function testNeedsIsSandboxedFilterReceivesTrueWhenSandboxed()
{
$twig = $this->getEnvironment(true, [], ['index' => '{{ "foo"|sandbox_aware }}'], [], ['sandbox_aware']);
$twig->addFilter(new \Twig\TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
$twig->addFilter(new TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
return $value.':'.($isSandboxed ? 'on' : 'off');
}, ['needs_is_sandboxed' => true]));
@@ -894,7 +900,7 @@ EOF
public function testNeedsIsSandboxedFilterReceivesFalseWhenNotSandboxed()
{
$twig = $this->getEnvironment(false, [], ['index' => '{{ "foo"|sandbox_aware }}']);
$twig->addFilter(new \Twig\TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
$twig->addFilter(new TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
return $value.':'.($isSandboxed ? 'on' : 'off');
}, ['needs_is_sandboxed' => true]));
@@ -906,13 +912,13 @@ EOF
$twig = $this->getEnvironment(false, [], [
'in' => '{{ "foo"|sandbox_aware }}',
'out' => '{{ "foo"|sandbox_aware }}',
], [], ['sandbox_aware'], [], [], [], new class implements \Twig\Sandbox\SourcePolicyInterface {
], [], ['sandbox_aware'], [], [], [], new class implements SourcePolicyInterface {
public function enableSandbox(Source $source): bool
{
return 'in' === $source->getName();
}
});
$twig->addFilter(new \Twig\TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
$twig->addFilter(new TwigFilter('sandbox_aware', static function (bool $isSandboxed, string $value) {
return $value.':'.($isSandboxed ? 'on' : 'off');
}, ['needs_is_sandboxed' => true]));
@@ -924,7 +930,7 @@ EOF
{
$loader = new ArrayLoader(['index' => '{{ sandbox_aware("foo") }}']);
$twig = new Environment($loader, ['debug' => true, 'cache' => false, 'autoescape' => false]);
$twig->addFunction(new \Twig\TwigFunction('sandbox_aware', static function (bool $isSandboxed, string $value) {
$twig->addFunction(new TwigFunction('sandbox_aware', static function (bool $isSandboxed, string $value) {
return $value.':'.($isSandboxed ? 'on' : 'off');
}, ['needs_is_sandboxed' => true]));
@@ -934,7 +940,7 @@ EOF
public function testNeedsIsSandboxedTestReceivesTrueWhenSandboxed()
{
$twig = $this->getEnvironment(true, [], ['index' => '{{ "foo" is sandbox_aware ? "on" : "off" }}']);
$twig->addTest(new \Twig\TwigTest('sandbox_aware', static function (bool $isSandboxed, string $value) {
$twig->addTest(new TwigTest('sandbox_aware', static function (bool $isSandboxed, string $value) {
return $isSandboxed && 'foo' === $value;
}, ['needs_is_sandboxed' => true]));
@@ -944,7 +950,7 @@ EOF
public function testNeedsIsSandboxedTestReceivesFalseWhenNotSandboxed()
{
$twig = $this->getEnvironment(false, [], ['index' => '{{ "foo" is sandbox_aware ? "on" : "off" }}']);
$twig->addTest(new \Twig\TwigTest('sandbox_aware', static function (bool $isSandboxed, string $value) {
$twig->addTest(new TwigTest('sandbox_aware', static function (bool $isSandboxed, string $value) {
return !$isSandboxed && 'foo' === $value;
}, ['needs_is_sandboxed' => true]));
-51
View File
@@ -1,51 +0,0 @@
#!/bin/bash
set -x
set -e
REPO=`pwd`
cd /tmp
rm -rf drupal-twig-test
composer create-project --no-interaction drupal/recommended-project:10.1.x-dev drupal-twig-test
cd drupal-twig-test
(cd vendor/twig && rm -rf twig && ln -sf $REPO twig)
composer dump-autoload
php ./web/core/scripts/drupal install --no-interaction demo_umami > output
perl -p -i -e 's/^([A-Za-z]+)\: (.+)$/export DRUPAL_\1=\2/' output
source output
#echo '$config["system.logging"]["error_level"] = "verbose";' >> web/sites/default/settings.php
wget https://get.symfony.com/cli/installer -O - | bash
export PATH="$HOME/.symfony5/bin:$PATH"
symfony server:start -d --no-tls
curl -LsS -o blackfire-player.phar https://get.blackfire.io/blackfire-player-v1.31.0.phar
chmod +x blackfire-player.phar
cat > drupal-tests.bkf <<EOF
name "Drupal tests"
scenario
name "homepage"
set name "admin"
set pass "pass"
visit url('/')
expect status_code() == 200
click link('Articles')
expect status_code() == 200
click link('Dairy-free and delicious milk chocolate')
expect body() matches "/Dairy\-free milk chocolate is made in largely the same way as regular chocolate/"
expect status_code() == 200
click link('Log in')
expect status_code() == 200
submit button("Log in")
param name name
param pass pass
expect status_code() == 303
follow
expect status_code() == 200
click link('Structure')
expect status_code() == 200
EOF
./blackfire-player.phar run drupal-tests.bkf --endpoint=`symfony var:export SYMFONY_DEFAULT_ROUTE_URL` --variable name=$DRUPAL_Username --variable pass=$DRUPAL_Password
symfony server:stop