Skip to content

fix: preserve full transaction attribute state in GrailsTransactionAttribute (web) copy constructors - #16065

Open
borinquenkid wants to merge 1 commit into
apache:8.0.xfrom
borinquenkid:fix/web-transaction-attribute-copy
Open

fix: preserve full transaction attribute state in GrailsTransactionAttribute (web) copy constructors#16065
borinquenkid wants to merge 1 commit into
apache:8.0.xfrom
borinquenkid:fix/web-transaction-attribute-copy

Conversation

@borinquenkid

Copy link
Copy Markdown
Member

fix: preserve full transaction attribute state in GrailsTransactionAttribute copy constructors

Follow-up to #16063 (fix/customizable-rollback-tx-attribute-copy), which fixed the same bug in org.grails.datastore.mapping.transactions.CustomizableRollbackTransactionAttribute. That PR's "known follow-up" section named this class as carrying the identical pattern; this PR is that fix.

Problem

org.grails.transaction.GrailsTransactionAttribute (grails-core) had the same lossy copy constructors as its GORM-module namesake and the CRTA class:

  • The (TransactionAttribute) overload copied only the five TransactionDefinition fields — rollback rules, qualifier, labels, descriptor, and timeoutString were silently dropped.
  • The (TransactionDefinition) overload had the same gap.
  • The (RuleBasedTransactionAttribute) overload called super(other) (copying definition fields and rules) but still dropped qualifier, labels, descriptor, and timeoutString.
  • Two trace log statements used GString-style placeholders ("$ex", "$winner") that never interpolate in .java source, and a third trace statement logged unconditionally without an isTraceEnabled() guard.

This class has no in-repo constructor callers — greping new GrailsTransactionAttribute( across the whole repo turns up only GrailsTransactionTemplate.groovy:55, which resolves to the same-package GORM twin (grails.gorm.transactions.GrailsTransactionAttribute, fixed separately in #16064), not this class. This class is public API consumed by downstream plugins and applications that construct or extend it directly.

Fix

Same pattern as the CRTA fix (4042a87d54):

  • (TransactionAttribute) now delegates to (TransactionDefinition).
  • (TransactionDefinition) copies the five definition fields, then recovers the dynamic type via pattern-matching instanceof: copyAttributeState for TransactionAttribute sources, and a rollback-rules snapshot through a temporary new RuleBasedTransactionAttribute(other).getRollbackRules() — never the source's lazy getter, so the source is never mutated.
  • (RuleBasedTransactionAttribute) does super(other) + copyAttributeState + copyGrailsState.
  • New private helpers copyAttributeState (descriptor/timeoutString when source is DefaultTransactionAttribute, qualifier, defensive new ArrayList<>(labels) copy) and copyGrailsState (inheritRollbackOnly, this class's only Grails-specific field — it has no connection field, unlike the CRTA class).
  • Log placeholders fixed to string concatenation; the previously-unguarded trace call is now behind isTraceEnabled().

No other production files touched.

Behavior change (release-note material)

Same shape as the CRTA change: downstream code that passes its own rule-bearing TransactionAttribute into this class's copy constructors will now have those rules honored — an exception matching a NoRollbackRuleAttribute commits instead of rolling back. Previously the rules were silently dropped and every exception rolled back. One-directional: no scenario turns a commit into a rollback. Since this class has no in-repo callers, the practical impact is scoped to plugin/application code outside this repository.

Tests

GrailsTransactionAttributeSpec (13 tests, modeled on CustomizableRollbackTransactionAttributeSpec): rule-list and labels deep-copy independence in both directions with explicit non-mutation-of-source assertions, qualifier/labels/inheritRollbackOnly preservation, all five definition fields, descriptor/timeoutString, a plain RuleBasedTransactionAttribute source, rollback-on behavior (no-rollback rule honored, deepest rule wins, rollback-everything default), and the statically-dispatched TransactionDefinition/TransactionAttribute entry points.

  • :grails-core:test — 475 tests, 0 failures.
  • :grails-core:codeStyle — clean.

🤖 Generated with Claude Code

…tribute copy constructors

Mirrors the CustomizableRollbackTransactionAttribute fix (4042a87)
split out of PR apache#15779 review, applied to the web-tier twin in
org.grails.transaction.

The RuleBasedTransactionAttribute overload now delegates to Spring's
own copy constructor (super(other)), which snapshots the rule list from
the field without invoking the source's lazy getRollbackRules() (which
would mutate the source by assigning a new list into it). The
TransactionDefinition and TransactionAttribute overloads recover the
dynamic type and snapshot rules through a temporary Spring copy, so the
source is never mutated on any path.

All paths now explicitly carry the attribute-level state that
Spring 7's DefaultTransactionAttribute copy constructor does not:
descriptor, timeoutString, qualifier, and labels (defensively copied,
since setLabels stores the given reference), plus inheritRollbackOnly.

Also fixes GString-style placeholders ("$ex", "$winner") in trace
logging that never interpolated in this .java source, and guards the
remaining trace call behind isTraceEnabled().

Covered by GrailsTransactionAttributeSpec (copy independence and state
preservation for every constructor dispatch path, including statically
dispatched entries).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 29, 2026 17:06
@testlens-app

testlens-app Bot commented Jul 29, 2026

Copy link
Copy Markdown

🚨 TestLens detected 13 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

CI - Groovy Joint Validation Build / build_grails > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails Forge (Java 21, indy=false) > :grails-forge-cli:test [grails-forge]

Test Runs Flakiness
CreateControllerCommandSpec > test app with controller 1% 🟡

CI / Build Grails-Core (macos-latest, 21) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core (ubuntu-latest, 21) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core (ubuntu-latest, 25) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core (windows-latest, 25) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

🏷️ Commit: afe91b0
▶️ Tests: 38362 executed
⚪️ Checks: 59/59 completed

Test Failures (first 10 of 13)

CreateControllerCommandSpec > test app with controller (:grails-forge-cli:test [grails-forge] in CI / Build Grails Forge (Java 21, indy=false))
Condition not satisfied after 240.00 seconds and 238 attempts
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:205)
	at spock.util.concurrent.PollingConditions.eventually(PollingConditions.java:157)
	at org.grails.forge.cli.CommandSpec.testOutputContains(CommandSpec.groovy:64)
	at org.grails.forge.cli.command.CreateControllerCommandSpec.test app with controller(CreateControllerCommandSpec.groovy:73)
Caused by: Condition not satisfied:

output.toString().contains(value)
|      |          |        |
|      |          false    BUILD SUCCESSFUL
|      To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
|      Daemon will be stopped at the end of the build 
|       
|      > Task :assetCompile
|      Starting AssetCompiler [-i, /tmp/grailsforgetmp15386057026918852847/grails-app/assets, -o, /tmp/grailsforgetmp15386057026918852847/build/assets, -j, /home/runner/.gradle/caches/modules-2/files-2.1/org.webjars.npm/bootstrap/5.3.8/1a2f960f569cba04bce1c79ff07ab8985a0f56a0/bootstrap-5.3.8.jar, -j, /home/runner/.gradle/caches/modules-2/files-2.1/org.webjars.npm/bootstrap-icons/1.13.1/add743da3f8acf43906ece5d5fb8b759d2b0eeed/bootstrap-icons-1.13.1.jar, -j, /home/runner/.gradle/caches/modules-2/files-2.1/org.webjars.npm/jquery/4.0.0/bfedbeaadde5b1aa3d3403fa36a995e685e2a867/jquery-4.0.0.jar, -B, eyJjb25maWdPcHRpb25zIjp7fSwiZW5hYmxlRGlnZXN0cyI6dHJ1ZSwiZW5hYmxlR3ppcCI6dHJ1ZSwiZW5hYmxlU291cmNlTWFwcyI6dHJ1ZSwiZXhjbHVkZXNHemlwIjpbXSwibWF4VGhyZWFkcyI6bnVsbCwibWluaWZ5Q3NzIjp0cnVlLCJtaW5pZnlKcyI6dHJ1ZSwic2tpcE5vbkRpZ2VzdHMiOnRydWUsIm1pbmlmeU9wdGlvbnMiOnt9LCJ2ZXJib3NlIjp0cnVlLCJleGNsdWRlcyI6WyJ3ZWJqYXJzL2pxdWVyeS8qKiIsIndlYmphcnMvYm9vdHN0cmFwLyoqIiwid2ViamFycy9ib290c3RyYXAtaWNvbnMvKioiXSwiaW5jbHVkZXMiOlsid2ViamFycy9qcXVlcnkvKi9kaXN0L2pxdWVyeS5qcyIsIndlYmphcnMvYm9vdHN0cmFwLyovZGlzdC9qcy9ib290c3RyYXAuYnVuZGxlLmpzIiwid2ViamFycy9ib290c3RyYXAvKi9kaXN0L2Nzcy9ib290c3RyYXAuY3NzIiwid2ViamFycy9ib290c3RyYXAtaWNvbnMvKi9mb250L2Jvb3RzdHJhcC1pY29ucy5jc3MiLCJ3ZWJqYXJzL2Jvb3RzdHJhcC1pY29ucy8qL2ZvbnQvZm9udHMvKiJdLCJyZXNvbHZlcnMiOltdLCJhc3NldHNQYXRoIjoiL3RtcC9ncmFpbHNmb3JnZXRtcDE1Mzg2MDU3MDI2OTE4ODUyODQ3L2dyYWlscy1hcHAvYXNzZXRzIiwiY2FjaGVMb2NhdGlvbiI6Ii90bXAvZ3JhaWxzZm9yZ2V0bXAxNTM4NjA1NzAyNjkxODg1Mjg0Ny9idWlsZC8uYXNzZXRjYWNoZSJ9]
|      17:56:06.678 [main] INFO asset.pipeline.LoggingAssetEventListener -- Finished Precompiling Assets
|       
|      > Task :compileJava NO-SOURCE
|      > Task :compileGroovy
|      > Task :buildProperties
|      > Task :processResources
|      > Task :classes
|      > Task :compileWebappGroovyPages NO-SOURCE
|      > Task :compileGroovyPages
|      > Task :findMainClass
|      > Task :bootWar
|      > Task :jar
|      > Task :war
|      > Task :assemble
|      > Task :compileTestJava NO-SOURCE
|      > Task :compileTestGroovy
|      > Task :processTestResources NO-SOURCE
|      > Task :testClasses
|      > Task :test
|      > Task :compileIntegrationTestJava NO-SOURCE
|      > Task :compileIntegrationTestGroovy
|      > Task :processIntegrationTestResources NO-SOURCE
|      > Task :integrationTestClasses
|      > Task :integrationTest
To honour the JVM settings for this build a single-use Daemon process will be forked. For more on this, please refer to https://docs.gradle.org/9.6.0/userguide/gradle_daemon.html#sec:disabling_the_daemon in the Gradle documentation.
Daemon will be stopped at the end of the build 
 
> Task :assetCompile
Starting AssetCompiler [-i, /tmp/grailsforgetmp15386057026918852847/grails-app/assets, -o, /tmp/grailsforgetmp15386057026918852847/build/assets, -j, /home/runner/.gradle/caches/modules-2/files-2.1/org.webjars.npm/bootstrap/5.3.8/1a2f960f569cba04bce1c79ff07ab8985a0f56a0/bootstrap-5.3.8.jar, -j, /home/runner/.gradle/caches/modules-2/files-2.1/org.webjars.npm/bootstrap-icons/1.13.1/add743da3f8acf43906ece5d5fb8b759d2b0eeed/bootstrap-icons-1.13.1.jar, -j, /home/runner/.gradle/caches/modules-2/files-2.1/org.webjars.npm/jquery/4.0.0/bfedbeaadde5b1aa3d3403fa36a995e685e2a867/jquery-4.0.0.jar, -B, eyJjb25maWdPcHRpb25zIjp7fSwiZW5hYmxlRGlnZXN0cyI6dHJ1ZSwiZW5hYmxlR3ppcCI6dHJ1ZSwiZW5hYmxlU291cmNlTWFwcyI6dHJ1ZSwiZXhjbHVkZXNHemlwIjpbXSwibWF4VGhyZWFkcyI6bnVsbCwibWluaWZ5Q3NzIjp0cnVlLCJtaW5pZnlKcyI6dHJ1ZSwic2tpcE5vbkRpZ2VzdHMiOnRydWUsIm1pbmlmeU9wdGlvbnMiOnt9LCJ2ZXJib3NlIjp0cnVlLCJleGNsdWRlcyI6WyJ3ZWJqYXJzL2pxdWVyeS8qKiIsIndlYmphcnMvYm9vdHN0cmFwLyoqIiwid2ViamFycy9ib290c3RyYXAtaWNvbnMvKioiXSwiaW5jbHVkZXMiOlsid2ViamFycy9qcXVlcnkvKi9kaXN0L2pxdWVyeS5qcyIsIndlYmphcnMvYm9vdHN0cmFwLyovZGlzdC9qcy9ib290c3RyYXAuYnVuZGxlLmpzIiwid2ViamFycy9ib290c3RyYXAvKi9kaXN0L2Nzcy9ib290c3RyYXAuY3NzIiwid2ViamFycy9ib290c3RyYXAtaWNvbnMvKi9mb250L2Jvb3RzdHJhcC1pY29ucy5jc3MiLCJ3ZWJqYXJzL2Jvb3RzdHJhcC1pY29ucy8qL2ZvbnQvZm9udHMvKiJdLCJyZXNvbHZlcnMiOltdLCJhc3NldHNQYXRoIjoiL3RtcC9ncmFpbHNmb3JnZXRtcDE1Mzg2MDU3MDI2OTE4ODUyODQ3L2dyYWlscy1hcHAvYXNzZXRzIiwiY2FjaGVMb2NhdGlvbiI6Ii90bXAvZ3JhaWxzZm9yZ2V0bXAxNTM4NjA1NzAyNjkxODg1Mjg0Ny9idWlsZC8uYXNzZXRjYWNoZSJ9]
17:56:06.678 [main] INFO asset.pipeline.LoggingAssetEventListener -- Finished Precompiling Assets
 
> Task :compileJava NO-SOURCE
> Task :compileGroovy
> Task :buildProperties
> Task :processResources
> Task :classes
> Task :compileWebappGroovyPages NO-SOURCE
> Task :compileGroovyPages
> Task :findMainClass
> Task :bootWar
> Task :jar
> Task :war
> Task :assemble
> Task :compileTestJava NO-SOURCE
> Task :compileTestGroovy
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
> Task :compileIntegrationTestJava NO-SOURCE
> Task :compileIntegrationTestGroovy
> Task :processIntegrationTestResources NO-SOURCE
> Task :integrationTestClasses
> Task :integrationTest

	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy:65)
	at org.grails.forge.cli.CommandSpec.testOutputContains_closure1(CommandSpec.groovy)
	at spock.util.concurrent.PollingConditions.within(PollingConditions.java:185)
	... 3 more
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (macos-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@248f1090

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (macos-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@50dc7396

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@12fdff78

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@58e7de77

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@585f319e

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@2fc2ea96

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (windows-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@260287a7

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (windows-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@4237aaa6

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@393e3f4b

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)

Muted Tests

Select tests to mute in this pull request:

  • CreateControllerCommandSpec > test app with controller
  • GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer
  • GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes lossy copy-constructor behavior in org.grails.transaction.GrailsTransactionAttribute so that copy construction preserves transaction attribute state beyond the five TransactionDefinition fields (notably rollback rules, qualifier/labels, descriptor, and timeoutString), matching the intent described in the PR metadata and aligning behavior with the earlier related fixes.

Changes:

  • Make GrailsTransactionAttribute(TransactionAttribute) delegate to the TransactionDefinition constructor and enhance the TransactionDefinition constructor to preserve attribute state (incl. rollback rules snapshotting without mutating the source).
  • Ensure the RuleBasedTransactionAttribute copy path also preserves qualifier/labels/descriptor/timeoutString and Grails-specific inheritRollbackOnly.
  • Add a comprehensive Spock spec covering deep-copy independence, non-mutation of sources, definition/attribute state preservation, and rollbackOn behavior.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
grails-core/src/main/groovy/org/grails/transaction/GrailsTransactionAttribute.java Updates copy constructors to preserve full attribute state (rollback rules, qualifier/labels, descriptor/timeoutString, Grails state) and fixes/guards trace logging.
grails-core/src/test/groovy/org/grails/transaction/GrailsTransactionAttributeSpec.groovy Adds targeted regression tests for copy semantics, defensive copying, and rollback rule behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@borinquenkid

Copy link
Copy Markdown
Member Author

The TestLens failures for GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default and GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer are a pre-existing bug on 8.0.x, unrelated to this PR's changes -- reproducible on plain 8.0.x today. DefaultStackTraceFilterer.STACK_LOG routes through a jcl-over-slf4j commons-logging binding, so the tests' System.setErr() capture never observes the emitted message.

Fix: #16067 (replaces the System.err capture with a Logback appender attached directly to the logger). Once that merges, rebasing onto 8.0.x should clear this failure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants