This is an automated email from the ASF dual-hosted git repository. borinquenkid pushed a commit to branch test/document-datamapping-core-transformers in repository https://gitbox.apache.org/repos/asf/grails-core.git
commit fc06b784c3a39ab821c1f1615492d85853386b62 Author: Walter Duque de Estrada <[email protected]> AuthorDate: Thu Aug 13 12:20:22 2026 -0500 Fix compiler crash in OrderedGormTransformation's error path and add coverage OrderedGormTransformation is the shared dispatcher every GORM annotation-driven transform routes through (@Tenant, @CurrentTenant, @WithoutTenant, @Transactional, @Rollback, @ReadOnly), but it was only ever exercised via real transforms that are all CompilationUnitAware - so the branch of collectAndOrderGormTransformations taken for a discovered transform that ISN'T CompilationUnitAware, and the catch block that runs when a transform's GormASTTransformationClass name can't be loaded, were both untested. Writing a test for the unloadable-transform-name path surfaced the same bug fixed earlier in TenantTransform: the catch block calls the inherited AbstractASTTransformation#addError(String, ASTNode), which reads that class's own sourceUnit field - never populated here, since visit() never called init() to set it. Any misconfigured or broken custom GORM transform reference would crash the compiler with an internal NullPointerException instead of a clean error message. Fixed with a one-line call to the inherited init(astNodes, source), the idiomatic way AbstractASTTransformation subclasses are meant to populate that field. Add a spec covering both previously-unexercised branches plus priority(), using test-only marker annotations/transforms following the same local-transform-testing pattern used elsewhere in this branch. Coverage moves 70% -> 82% instruction, 71% -> 73% branch. Co-Authored-By: Claude Sonnet 5 <[email protected]> --- .../transform/OrderedGormTransformation.groovy | 2 + .../ApplyNonCompilationUnitAwareTransform.java | 40 +++++++++++++ .../transform/ApplyUnloadableGormTransform.java | 39 ++++++++++++ ...onCompilationUnitAwareTestTransformation.groovy | 39 ++++++++++++ .../transform/OrderedGormTransformationSpec.groovy | 69 ++++++++++++++++++++++ 5 files changed, 189 insertions(+) diff --git a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/transform/OrderedGormTransformation.groovy b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/transform/OrderedGormTransformation.groovy index 4268096780..a0a30fea93 100644 --- a/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/transform/OrderedGormTransformation.groovy +++ b/grails-datamapping-core/src/main/groovy/org/grails/datastore/gorm/transform/OrderedGormTransformation.groovy @@ -60,6 +60,8 @@ class OrderedGormTransformation extends AbstractASTTransformation implements Com throw new RuntimeException("Internal error: wrong types: ${astNodes[0].getClass()} / ${astNodes[1].getClass()}") } + init(astNodes, source) + AnnotatedNode annotatedNode = (AnnotatedNode) astNodes[1] Iterable<TransformationInvocation> astTransformations = collectAndOrderGormTransformations(annotatedNode) for (transform in astTransformations) { diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyNonCompilationUnitAwareTransform.java b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyNonCompilationUnitAwareTransform.java new file mode 100644 index 0000000000..68bef89294 --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyNonCompilationUnitAwareTransform.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.grails.datastore.gorm.transform; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +/** + * Local marker annotation used only for testing {@link OrderedGormTransformation} - it routes + * through {@code OrderedGormTransformation} (like every real GORM annotation does) but resolves, + * via {@link GormASTTransformationClass}, to {@link NonCompilationUnitAwareTestTransformation}, + * which is not {@code CompilationUnitAware}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@GroovyASTTransformationClass("org.grails.datastore.gorm.transform.OrderedGormTransformation") +@GormASTTransformationClass("org.grails.datastore.gorm.transform.NonCompilationUnitAwareTestTransformation") +public @interface ApplyNonCompilationUnitAwareTransform { +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyUnloadableGormTransform.java b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyUnloadableGormTransform.java new file mode 100644 index 0000000000..2f1c7ba426 --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyUnloadableGormTransform.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.grails.datastore.gorm.transform; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +/** + * Local marker annotation used only for testing {@link OrderedGormTransformation}'s error path in + * {@code collectAndOrderGormTransformations}: the {@link GormASTTransformationClass} value + * deliberately names a class that does not exist, so loading it throws and the catch block runs. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@GroovyASTTransformationClass("org.grails.datastore.gorm.transform.OrderedGormTransformation") +@GormASTTransformationClass("org.grails.datastore.gorm.transform.ThisClassDoesNotExist") +public @interface ApplyUnloadableGormTransform { +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/NonCompilationUnitAwareTestTransformation.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/NonCompilationUnitAwareTestTransformation.groovy new file mode 100644 index 0000000000..b04be1fc78 --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/NonCompilationUnitAwareTestTransformation.groovy @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.datastore.gorm.transform + +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.control.SourceUnit +import org.codehaus.groovy.transform.ASTTransformation + +/** + * A minimal {@link ASTTransformation} that deliberately does NOT implement + * {@code groovy.transform.CompilationUnitAware}, used only to exercise the branch of + * {@link OrderedGormTransformation#collectAndOrderGormTransformations} taken for a discovered + * transform that isn't compilation-unit-aware (every other GORM transform in this codebase is, + * via {@link AbstractGormASTTransformation}, so that branch is otherwise unexercised). + * + * @see OrderedGormTransformationSpec + */ +class NonCompilationUnitAwareTestTransformation implements ASTTransformation { + + @Override + void visit(ASTNode[] astNodes, SourceUnit sourceUnit) { + } +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/OrderedGormTransformationSpec.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/OrderedGormTransformationSpec.groovy new file mode 100644 index 0000000000..b0a00632aa --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/OrderedGormTransformationSpec.groovy @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.grails.datastore.gorm.transform + +import org.apache.grails.common.compiler.GroovyTransformOrder + +import spock.lang.Specification + +/** + * {@code OrderedGormTransformation} is the shared dispatcher every GORM annotation-driven + * transform routes through ({@code @Tenant}, {@code @CurrentTenant}, {@code @WithoutTenant}, + * {@code @Transactional}, {@code @Rollback}, {@code @ReadOnly}), but every one of those real + * transforms is {@code CompilationUnitAware} - so the branch of + * {@code collectAndOrderGormTransformations} taken for a discovered transform that is NOT + * {@code CompilationUnitAware} was never exercised. Neither was the catch block that runs when a + * transform's {@code GormASTTransformationClass} name can't be loaded at all. + */ +class OrderedGormTransformationSpec extends Specification { + + void "a discovered transform that is not CompilationUnitAware is still collected and invoked"() { + when: 'a class is annotated with a marker that resolves to a plain, non-CompilationUnitAware transform' + new GroovyClassLoader().parseClass(''' + package org.grails.datastore.gorm.transform.fixture + + @org.grails.datastore.gorm.transform.ApplyNonCompilationUnitAwareTransform + class NonCompilationUnitAwareTarget { + } + ''') + + then: 'the class compiles cleanly, proving the transform was collected and invoked without attempting an invalid CompilationUnitAware cast' + noExceptionThrown() + } + + void "a transform whose class name cannot be loaded is reported as a compile error instead of crashing"() { + when: 'a class is annotated with a marker whose GormASTTransformationClass names a nonexistent class' + new GroovyClassLoader().parseClass(''' + package org.grails.datastore.gorm.transform.fixture + + @org.grails.datastore.gorm.transform.ApplyUnloadableGormTransform + class UnloadableTransformTarget { + } + ''') + + then: 'a normal compile error is reported rather than an internal compiler crash' + org.codehaus.groovy.control.MultipleCompilationErrorsException e = thrown() + e.message.contains('Could not load GORM transform') + } + + void "priority orders the transform via GORM_TRANSFORMS_ORDER"() { + expect: + new OrderedGormTransformation().priority() == GroovyTransformOrder.GORM_TRANSFORMS_ORDER + } +}
