Problem/Motivation

This piece of code in KernelTestBase::tearDown()

    // Free up memory: Custom test class properties.
    // Note: Private properties cannot be cleaned up.
    $rc = new \ReflectionClass(__CLASS__);
    $blacklist = [];
    foreach ($rc->getProperties() as $property) {
      $blacklist[$property->name] = $property->getDeclaringClass()->name;
    }
    $rc = new \ReflectionClass($this);
    foreach ($rc->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $property) {
      if (!$property->isStatic() && !isset($blacklist[$property->name])) {
        $this->{$property->name} = NULL;
      }
    }

Prevents to write tests with typehinted properties in PHP 7.4+ - in the sense that yes, you can typehint but always have to allow null value too like e.g.

  protected ?User $user;

which is not something we should encourage.

Also, since Kernel tests run in isolated processes in the PHPUnit era, while the cleanup intent is commendable, it is largely useless.

Proposed resolution

Remove that code and live happily.

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Issue fork drupal-3280882

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mondrake created an issue. See original summary.

mondrake’s picture

Status: Active » Needs review
mondrake’s picture

Issue summary: View changes
mallezie’s picture

The main reason for this code to me is to have memory reduction when running tests.

When indeed those tests are isolated, this does not have much use. Checked the originating issue where this was added #2304461: KernelTestBaseTNG™.
IMO if this was still an issue to have properties cleanup after the test to free up memory, this would also be needed in Browsertest where this would also occur. So IMO this is indeed safe to remove here.

Looking at the test run here, this does seem to run smooth with the cleanup code removed. Although i'm not experienced enough to see if removing this does indeed need more memory or not now.

Another option is to adjust this code to assign empty properties of the correct type instead of null. Which does not need to allow null values for the property on the test.

I use following code in a project (which uses drupal test traits).


  /**
   * Strip properties from object.
   *
   * @param \object $target
   *   Test to strip properties from.
   *
   * @throws \ReflectionException
   */
  public static function stripProperties(object $target): void {
    $reflectionObject = new \ReflectionObject($target);
    foreach ($reflectionObject->getProperties() as $prop) {
      // Only act on non-static properties of test objects.
      // We want to empty the properties of the test object after the test.
      // Those properties can be large, and when kept they are in memory for
      // the entire test run, causing memory limit issues.
      if (!$prop->isStatic() && 0 !== \strncmp($prop->getDeclaringClass()->getName(), 'PHPUnit_', 8)) {
        $prop->setAccessible(TRUE);
        $prop_type = $prop->getType();
        // Properties of a test are typed, so some magic is needed to make sure
        // that we assign an empty object of the correct type to the property.
        // If our property is a primitive type (or untyped) add empty element
        // based on the type.
        if (!is_null($prop_type) && ($prop_type instanceof \ReflectionNamedType) && $prop_type->isBuiltin()) {
          if ($prop_type->getName() === 'string') {
            $prop->setValue($target, '');
          }
        }
        // If our property is a class, assign an empty object of that class.
        elseif (!is_null($prop_type) && ($prop_type instanceof \ReflectionNamedType) && class_exists($prop_type->getName()) && !$prop_type->isBuiltin()) {
          $class = new \ReflectionClass($prop_type->getName());
          $prop->setValue($target, $class->newInstanceWithoutConstructor());
        }
        // If our property is untyped make it NULL.
        else {
          $prop->setValue($target, NULL);
        }
      }
    }
mondrake’s picture

mallezie’s picture

Status: Needs review » Reviewed & tested by the community

Thanks for referencing that issue. It explains a lot. In this case I can see no harm in removing this code.

alexpott’s picture

Status: Reviewed & tested by the community » Needs review

This was added in the initial implementation of KernelTestBase in #2304461: KernelTestBaseTNG™ tried to find out why. Haven't managed to yet. I agree that this looks largely pointless given that we run in isolation. +1 to removing.

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

Epic x-post with @mallezie's more in depth comment. Nice work @mondrake and @mallezie

alexpott’s picture

Version: 10.0.x-dev » 9.4.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed e3983382da to 10.0.x and 4a392a17e7 to 9.5.x and 86819ab5e4 to 9.4.x. Thanks!

TBH we should be discouraging people from adding properties to tests - it's normally a source of interesting problems. Especially in anything extending WebDriverTestBase.

  • alexpott committed e398338 on 10.0.x
    Issue #3280882 by mondrake, mallezie: KernelTestBase::tearDown() cleanup...

  • alexpott committed 4a392a1 on 9.5.x
    Issue #3280882 by mondrake, mallezie: KernelTestBase::tearDown() cleanup...

  • alexpott committed 86819ab on 9.4.x
    Issue #3280882 by mondrake, mallezie: KernelTestBase::tearDown() cleanup...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.