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 478d0cd3c76a2c365b3cebf9a63e325588f2fc7c Author: Walter Duque de Estrada <[email protected]> AuthorDate: Thu Aug 13 11:21:38 2026 -0500 Add test coverage for AbstractTraitApplyingGormASTTransformation's default trait-weaving path AbstractTraitApplyingGormASTTransformation sat at 57% coverage. Its only concrete subclass in this module, ServiceTransformation, overrides shouldWeave with its own logic and calls the static weaveTraitWithGenerics directly rather than through the instance weaveTrait method - so the base class's own default behavior (shouldWeave returning true, weaveTrait delegating to Groovy's TraitComposer, and several weaveTraitWithGenerics edge branches: no-generics traits, interface class nodes, and partial/full generic-arity mismatches) was never exercised. Add a spec covering these directly: the generics edge cases against bare ClassNodes (same technique as the sibling AbstractGormASTTransformationSpec), and the instance weaveTrait method - including the real TraitComposer.doExtendTraits call - by compiling a class through a test-only local transform (TestTraitWeavingTransformation, applied via ApplyTestTraitWeaving) and asserting the compiled class actually gained the woven trait's method, following the same local-transform-testing pattern used for DetachedCriteriaASTTransformation earlier in this branch. Coverage moves 57% -> 98% instruction, 40% -> 72% branch. Co-Authored-By: Claude Sonnet 5 <[email protected]> --- ...ctTraitApplyingGormASTTransformationSpec.groovy | 170 +++++++++++++++++++++ .../gorm/transform/ApplyTestTraitWeaving.java | 38 +++++ .../gorm/transform/DoubleGenericTestTrait.groovy | 37 +++++ .../gorm/transform/SingleGenericTestTrait.groovy | 31 ++++ .../TestTraitWeavingTransformation.groovy | 61 ++++++++ .../gorm/transform/TestWeavableTrait.groovy | 31 ++++ 6 files changed, 368 insertions(+) diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/AbstractTraitApplyingGormASTTransformationSpec.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/AbstractTraitApplyingGormASTTransformationSpec.groovy new file mode 100644 index 0000000000..c0b96170b7 --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/AbstractTraitApplyingGormASTTransformationSpec.groovy @@ -0,0 +1,170 @@ +/* + * 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.reflect.Modifier + +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode + +import spock.lang.Specification + +/** + * {@code AbstractTraitApplyingGormASTTransformation} is only ever exercised in this module through + * its one concrete subclass, {@code ServiceTransformation}, which overrides {@code shouldWeave} + * with its own logic (never delegating to the default) and, for the interface/abstract-class case it + * actually tests, calls the static {@code weaveTraitWithGenerics} directly rather than going through + * the instance {@code weaveTrait} method. That leaves this base class's own default behavior - + * {@code shouldWeave} returning {@code true}, the instance {@code weaveTrait} delegating to + * {@code TraitComposer.doExtendTraits}, and several edge branches of {@code weaveTraitWithGenerics} + * (a trait with no generic type parameters, an interface class node that must be skipped entirely, + * and a trait whose generic arity does not match - fully or partially - the number of arguments + * supplied) - unexercised. These specs cover that gap directly: the {@code weaveTraitWithGenerics} + * edge branches and the default {@code shouldWeave} are tested against bare, detached + * {@code ClassNode}s (the technique used in {@link AbstractGormASTTransformationSpec}), while the + * instance {@code weaveTrait} method - and the real {@code TraitComposer.doExtendTraits} + * call it makes - is proven by compiling a real class through {@link TestTraitWeavingTransformation}, + * a test-only local transform applied via {@link ApplyTestTraitWeaving}, and asserting the compiled + * class actually gained the trait's method. + */ +class AbstractTraitApplyingGormASTTransformationSpec extends Specification { + + static class MinimalTraitTransformation extends AbstractTraitApplyingGormASTTransformation { + + @Override + protected Class getTraitClass() { + TestWeavableTrait + } + + @Override + protected ClassNode getAnnotationType() { + ClassHelper.make(ApplyTestTraitWeaving) + } + + @Override + protected Object getAppliedMarker() { + new Object() + } + + @Override + int priority() { + 0 + } + } + + private static ClassNode newTargetClassNode(String name) { + new ClassNode(name, 0, ClassHelper.OBJECT_TYPE) + } + + void "shouldWeave defaults to true when a subclass does not override it"() { + given: + MinimalTraitTransformation transformation = new MinimalTraitTransformation() + ClassNode targetClassNode = newTargetClassNode('org.grails.datastore.gorm.transform.fixture.ShouldWeaveTarget') + AnnotationNode annotationNode = new AnnotationNode(ClassHelper.make(ApplyTestTraitWeaving)) + + expect: + transformation.shouldWeave(annotationNode, targetClassNode) + } + + void "weaveTraitWithGenerics adds the plain interface when the trait declares no generic type parameters"() { + given: + ClassNode targetClassNode = newTargetClassNode('org.grails.datastore.gorm.transform.fixture.NoGenericsWeaveTarget') + + expect: + !targetClassNode.implementsInterface(ClassHelper.make(TestWeavableTrait)) + + when: + AbstractTraitApplyingGormASTTransformation.weaveTraitWithGenerics(targetClassNode, TestWeavableTrait) + + then: + targetClassNode.implementsInterface(ClassHelper.make(TestWeavableTrait)) + } + + void "weaveTraitWithGenerics pads missing generic arguments with Object when fewer arguments are supplied than the trait declares"() { + given: + ClassNode targetClassNode = newTargetClassNode('org.grails.datastore.gorm.transform.fixture.GenericsPaddingWeaveTarget') + ClassNode traitClassNode = ClassHelper.make(SingleGenericTestTrait) + + expect: + !targetClassNode.implementsInterface(traitClassNode) + + when: 'weaving with zero generic arguments even though the trait declares one' + AbstractTraitApplyingGormASTTransformation.weaveTraitWithGenerics(targetClassNode, SingleGenericTestTrait) + + then: 'the missing generic argument slot is padded with Object' + targetClassNode.implementsInterface(traitClassNode) + ClassNode wovenInterface = targetClassNode.interfaces.find { it.name == SingleGenericTestTrait.name } + wovenInterface.genericsTypes.length == 1 + wovenInterface.genericsTypes[0].type == ClassHelper.OBJECT_TYPE + } + + void "weaveTraitWithGenerics is a no-op for an interface class node"() { + given: 'a class node that is itself an interface' + ClassNode interfaceClassNode = new ClassNode( + 'org.grails.datastore.gorm.transform.fixture.NoWeaveInterfaceTarget', + Modifier.PUBLIC | Modifier.INTERFACE, + ClassHelper.OBJECT_TYPE) + + expect: + interfaceClassNode.interface + + when: + AbstractTraitApplyingGormASTTransformation.weaveTraitWithGenerics(interfaceClassNode, TestWeavableTrait) + + then: 'the trait is never added because interfaces are skipped entirely' + !interfaceClassNode.implementsInterface(ClassHelper.make(TestWeavableTrait)) + interfaceClassNode.interfaces.length == 0 + } + + void "weaveTraitWithGenerics pads only the missing slots when some, but not all, generic arguments are supplied"() { + given: + ClassNode targetClassNode = newTargetClassNode('org.grails.datastore.gorm.transform.fixture.PartialGenericsWeaveTarget') + ClassNode traitClassNode = ClassHelper.make(DoubleGenericTestTrait) + ClassNode suppliedArgument = ClassHelper.make(String) + + when: 'only the first of the two declared generic arguments is supplied' + AbstractTraitApplyingGormASTTransformation.weaveTraitWithGenerics(targetClassNode, DoubleGenericTestTrait, suppliedArgument) + + then: 'the supplied argument is used for the first slot and Object pads the second' + targetClassNode.implementsInterface(traitClassNode) + ClassNode wovenInterface = targetClassNode.interfaces.find { it.name == DoubleGenericTestTrait.name } + wovenInterface.genericsTypes.length == 2 + wovenInterface.genericsTypes[0].type == ClassHelper.make(String) + wovenInterface.genericsTypes[1].type == ClassHelper.OBJECT_TYPE + } + + void "weaveTrait composes the trait onto the class via a real compilation unit"() { + given: + GroovyClassLoader classLoader = new GroovyClassLoader(getClass().classLoader) + + when: + Class<?> compiled = classLoader.parseClass(''' + package org.grails.datastore.gorm.transform.fixture + + @org.grails.datastore.gorm.transform.ApplyTestTraitWeaving + class WeavingTarget { + } + ''') + + then: 'the class implements the woven trait and the trait method was composed into it' + TestWeavableTrait.isAssignableFrom(compiled) + compiled.getDeclaredConstructor().newInstance().testTraitMarkerValue() == 'test-trait-woven' + } +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyTestTraitWeaving.java b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyTestTraitWeaving.java new file mode 100644 index 0000000000..f97b75ed03 --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/ApplyTestTraitWeaving.java @@ -0,0 +1,38 @@ +/* + * 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 AbstractTraitApplyingGormASTTransformation} + * through a real, live compilation - triggering {@link TestTraitWeavingTransformation} so that the + * annotated class is woven with {@link TestWeavableTrait} via a genuine {@code CompilationUnit}. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +@GroovyASTTransformationClass("org.grails.datastore.gorm.transform.TestTraitWeavingTransformation") +public @interface ApplyTestTraitWeaving { +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/DoubleGenericTestTrait.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/DoubleGenericTestTrait.groovy new file mode 100644 index 0000000000..c1939e71db --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/DoubleGenericTestTrait.groovy @@ -0,0 +1,37 @@ +/* + * 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 + +/** + * A trait with two declared generic type parameters, used only to exercise + * {@link AbstractTraitApplyingGormASTTransformation#weaveTraitWithGenerics} in tests where only + * some, but not all, of the declared generic type parameters are supplied with an argument - + * so that both the "argument supplied" and "argument padded with Object" code paths run in the + * same call. + */ +trait DoubleGenericTestTrait<A, B> { + + A firstValue(A value) { + value + } + + B secondValue(B value) { + value + } +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/SingleGenericTestTrait.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/SingleGenericTestTrait.groovy new file mode 100644 index 0000000000..266d12f9ad --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/SingleGenericTestTrait.groovy @@ -0,0 +1,31 @@ +/* + * 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 + +/** + * A trait with exactly one declared generic type parameter, used only to exercise + * {@link AbstractTraitApplyingGormASTTransformation#weaveTraitWithGenerics} in tests where the + * number of supplied generic arguments does not match the number the trait declares. + */ +trait SingleGenericTestTrait<T> { + + T identity(T value) { + value + } +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/TestTraitWeavingTransformation.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/TestTraitWeavingTransformation.groovy new file mode 100644 index 0000000000..eaaff25423 --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/TestTraitWeavingTransformation.groovy @@ -0,0 +1,61 @@ +/* + * 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.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.control.CompilePhase +import org.codehaus.groovy.transform.ASTTransformation +import org.codehaus.groovy.transform.GroovyASTTransformation + +/** + * Local, annotation-driven transformation used only to test {@link AbstractTraitApplyingGormASTTransformation} + * through a genuine {@code CompilationUnit}. It deliberately does not override {@code shouldWeave}, + * so the default implementation runs, and it weaves {@link TestWeavableTrait} - a trait with no + * generic type parameters - onto whatever class is annotated with {@link ApplyTestTraitWeaving}. + * + * @see AbstractTraitApplyingGormASTTransformationSpec + */ +@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) +class TestTraitWeavingTransformation extends AbstractTraitApplyingGormASTTransformation implements ASTTransformation { + + private static final ClassNode MY_TYPE = ClassHelper.make(ApplyTestTraitWeaving) + private static final Object APPLIED_MARKER = new Object() + + @Override + protected Class getTraitClass() { + TestWeavableTrait + } + + @Override + protected ClassNode getAnnotationType() { + MY_TYPE + } + + @Override + protected Object getAppliedMarker() { + APPLIED_MARKER + } + + @Override + int priority() { + 0 + } +} diff --git a/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/TestWeavableTrait.groovy b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/TestWeavableTrait.groovy new file mode 100644 index 0000000000..d45b896f52 --- /dev/null +++ b/grails-datamapping-core/src/test/groovy/org/grails/datastore/gorm/transform/TestWeavableTrait.groovy @@ -0,0 +1,31 @@ +/* + * 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 + +/** + * A trait with no generic type parameters, used only to exercise + * {@link AbstractTraitApplyingGormASTTransformation} in tests: weaving a trait that declares no + * generics onto a target class node. + */ +trait TestWeavableTrait { + + String testTraitMarkerValue() { + 'test-trait-woven' + } +}
