A clone detector run over the whole module, tests included, finds duplication four earlier audits missed, #3618840: Audit of duplicated logic: the exactly-once timeout claim written twice, a migration preview that cannot follow the migration, three definitions of the definition an instance runs under, and a mail attachment mapped twice among them, which was itself the duplication audit. That one excluded tests as boilerplate and grouped clone windows only across different files, so within-file repeats and the entire test suite were invisible to it. What remains: 89 groups of byte-identical method bodies, 953 redundant lines, 57 of those groups in tests alone.
The test helpers have diverged, not merely duplicated. getParkedNodes(), which answers "what nodes is this run parked on", is declared in 23 test classes as six different implementations. They do not agree: one resets the entity storage cache before loading and the others do not, so two classes asking the same question can be looking at different state. The same shape repeats for getParkedTokenId (10 declarations, 3 implementations), getTokensByNode (6 in 3), getWorkItem (5 in 3) and getParkedTokensAtNode (4).
An existing trait is being bypassed. DrainEngineQueueTrait provides drainQueue() in six lines. Two test classes carry their own thirteen-line variant instead of using it.
Scaffolding. One identical six-schema installEntitySchema() sequence appears in 58 test classes, and 14 distinct sequences are shared by two or more. The suite has over 200 test classes and three traits.
In production, the storage schemas. Eight of the nine *StorageSchema classes hand-roll the same getEntitySchema() wrapper, calling the parent, reading the base table, merging indexes and returning, around nothing but a different index literal. The ninth, PaymentStorageSchema, already uses the getIndexes() seam its Kessai parent provides, which is exactly the fix for the other eight.
TimeoutSweeper::fire() and fireTimer() are 86% identical, two parallel paths for a token and for a timer.
Dismissed after reading them, recorded so a later audit does not chase them again: declarative data tables (tokenInfo(), the audit channel and action labels) collide only because the detector collapses string literals; symmetric pairs such as getIncomingFlows() and getOutgoingFlows() would need a direction argument to merge, which reads worse than the pair; plugin buildConfigurationForm() methods share a shape but no content, and two textfields are not duplication; and dependency-injection create() boilerplate.
AI-Generated: Yes (Claude Code was used to write the clone detector, to triage its output by reading each candidate, and to write the fix. The dismissed candidates above were each read before being dismissed.)
Issue fork orchestra-3619728
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
Comment #3
mably commentedComment #5
mably commented