Summary

DrupalTrait::tearDownDrupal() deletes every marked fixture entity one at a time:

foreach ($this->cleanupEntities as $entity) {
    $entity->delete();
}

For N fixtures, that's N separate single-entity storage deletes. EntityStorageInterface::delete() already accepts an array and fires the same per-entity hooks whether passed one entity or many, so the loop is pure round-trip overhead — worst in entity-heavy tests that create many nodes/users per method.

Proposed fix

Iterate in reverse creation order and issue one $storage->delete($batch) call per run of consecutive same-type entities:

$entity_type_manager = $this->container->get('entity_type.manager');
$batch = [];
$batch_entity_type_id = null;
foreach (array_reverse($this->cleanupEntities) as $entity) {
    $entity_type_id = $entity->getEntityTypeId();
    if ($entity_type_id !== $batch_entity_type_id && $batch !== []) {
        $entity_type_manager->getStorage($batch_entity_type_id)->delete($batch);
        $batch = [];
    }
    $batch_entity_type_id = $entity_type_id;
    $batch[] = $entity;
}
if ($batch !== []) {
    $entity_type_manager->getStorage($batch_entity_type_id)->delete($batch);
}

Risk

Ordering is preserved — later-created entities still delete first, and each call handles only one type, so cross-type dependency ordering can't be violated. $storage->delete([$a, $b]) fires the same per-entity hooks as N individual calls, and bucketing only consecutive same-type runs keeps interleaved-type ordering intact.

Measured impact

Near-zero saving on tests with one or two entities, largest on entity-heavy classes. Measured together with two related fixes (pre-flight visit skip, watchdog memoization), the combined per-test body time dropped roughly 5.75s → ~3.9s on a smoke-test probe. Verified with a behavior test driving the real tearDownDrupal() with interleaved node/user fixtures, confirming every one is gone afterward.

Patch

Attached: dtt-batched-entity-cleanup.patch (against 2.7.0 tip). Replaces the per-entity delete loop in src/DrupalTrait.php::tearDownDrupal() with the batched approach above. Includes the interleaved-fixture behavior test.

AI-assistance disclosure

Per drupal.org's policy on AI use in contributions: AI-Generated: Yes. An AI coding assistant helped draft this patch and write-up under my direction. I reviewed the change, ran its behavior test locally, and confirmed the measured numbers come from real test runs — not generated. I take full responsibility for its correctness and will respond to maintainer feedback on this issue.

CommentFileSizeAuthor
dtt-batched-entity-cleanup.patch1.82 KBgrasmash

Issue fork dtt-3608416

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

grasmash created an issue. See original summary.

grasmash’s picture

Issue summary: View changes

moshe weitzman made their first commit to this issue’s fork.

moshe weitzman’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

acbramley’s picture

Version: » 2.7.1

This has caused some issues with deleting menu link content entities, I'll try to track down why but the error looks like this:

Drupal\Core\Entity\EntityStorageException: Entity not found through the menu link plugin definition and could not fallback on UUID '09ce18b5-ebb7-48fc-9f84-afc5ae474048'

acbramley’s picture

Status: Fixed » Needs work

I think this needs to be reverted and re-thought. The issue seems to be that now the MenuLinkContent entities that are related to nodes are deleted first, but because of static caching, menu_link_content_entity_predelete still loads the link plugins in loadLinksByRoute when the node gets deleted and then that exception is thrown in MenuLinkContent::getEntity.

mstrelan’s picture

This also broke one of my tests:

Drupal\Tests\nsk_blocks\Functional\NskBlockTestBaseTest::testCreateLayoutBuilderEntity
Drupal\Core\Entity\EntityStorageException: The "node" entity cannot have a URI as it does not have an ID

/data/app/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php:803
/data/vendor/weitzman/drupal-test-traits/src/DrupalTrait.php:155
/data/vendor/weitzman/drupal-test-traits/src/ExistingSiteBase.php:91
/data/bin/phpunit:122

+1 to revert. I'm not really sure it was worthwhile in the first place? Think we need to see more detailed profiling.

mstrelan’s picture

Status: Needs work » Reviewed & tested by the community

  • acbramley committed e91e66f1 on 2.x
    Revert "Issue #3608416: Batch tearDownDrupal() entity cleanup into one...
acbramley’s picture

Status: Reviewed & tested by the community » Needs work

Revert is in, back to Needs work to rethink the solution.