Andreas,

One place to start is everywhere that "AnnotatedNode.setSynthetic(true)" is 
currently called.  I think this misses the methods added for a property.  But 
it does cover several AST transforms.  And maybe the transforms that add 
methods and don't call this method could do so in addition to the modification 
to add "@Generated".  Maybe the call to setSynthetic could actually add the 
annotation.  Or you could create a utility method that sets synthetic and adds 
"@Generated".


________________________________
From: Andres Almiray <aalmi...@gmail.com>
Sent: Tuesday, August 14, 2018 3:42 PM
To: dev@groovy.apache.org
Subject: Updates on JaCoCo support

Hello everyone,

I've spent a couple of hours with JaCoCo team members at the Hackergarten Bern 
this evening.
The goal of the session was to get started with an integration test for the 
@Generated feature
added in Groovy 2.5.0.

You can see the outcome at 
https://github.com/jacoco/jacoco/pull/733<https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_jacoco_jacoco_pull_733&d=DwMFaQ&c=4ZIZThykDLcoWk-GVjSLmy8-1Cr1I4FWIvbLFebwKgY&r=tPJuIuL_GkTEazjQW7vvl7mNWVGXn3yJD5LGBHYYHww&m=hAfmXOD5R2AP8VF69wcL-UqYZEv2tXAbyBRfDJh0lfg&s=kxj4hAyP-_6cuc12J6CSMgl_xJRqfEjVcnMHfxzID8w&e=>

The good news is that Groovy applies @Generated on constructors added by 
@Cannonical as well
as methods defined by the GroovyObject interface. The bad news is that the test 
still fails
because the expectation is that *every* method generated by the compiler that 
does not map
to a particular source line *should* be annotated with @Generated. The 
following source

----
// This annotation generates the following
// - a constructor that takes an int as argument
// - a suitable implementation of toString()
// - a suitable implementation of hashCode()
// - a suitable implementation of equals(Object)
// - a public method named canEqual(Object)
// - a getter & setter for the valRead property
@groovy.transform.Canonical
class GroovyDataClassTarget { // assertFullyCovered()

    int valRead // assertNotCovered()

    static void main(String[] args) {
        new GroovyDataClassTarget() // assertFullyCovered()
    }
}
----

Generates bytecode equivalent to (decompiled with IntelliJ)

----
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.jacoco.core.test.validation.groovy.targets;

import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.EqualsAndHashCode;
import groovy.transform.Generated;
import groovy.transform.ToString;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import org.codehaus.groovy.runtime.typehandling.ShortTypeHandling;
import org.codehaus.groovy.util.HashCodeHelper;

@ToString
@EqualsAndHashCode
public class GroovyDataClassTarget implements GroovyObject {
    private int valRead;

    @Generated
    public GroovyDataClassTarget(int valRead) {
        CallSite[] var2 = $getCallSiteArray();
        super();
        MetaClass var3 = this.$getStaticMetaClass();
        this.metaClass = var3;
        this.valRead = DefaultTypeTransformation.intUnbox(valRead);
    }

    public GroovyDataClassTarget() {
        CallSite[] var1 = $getCallSiteArray();
        this(Integer.valueOf(0));
    }

    public static void main(String... args) {
        CallSite[] var1 = $getCallSiteArray();
        var1[0].callConstructor(GroovyDataClassTarget.class);
    }

    public String toString() {
        CallSite[] var1 = $getCallSiteArray();
        Object _result = var1[1].callConstructor(StringBuilder.class);
        Object $toStringFirst = Boolean.TRUE;
        var1[2].call(_result, 
"org.jacoco.core.test.validation.groovy.targets.GroovyDataClassTarget(");
        if (DefaultTypeTransformation.booleanUnbox($toStringFirst)) {
            Boolean var4 = Boolean.FALSE;
        } else {
            var1[3].call(_result, ", ");
        }

        var1[4].call(_result, var1[5].callStatic(InvokerHelper.class, 
var1[6].callCurrent(this)));
        var1[7].call(_result, ")");
        return (String)ShortTypeHandling.castToString(var1[8].call(_result));
    }

    public int hashCode() {
        CallSite[] var1 = $getCallSiteArray();
        Object _result = var1[9].callStatic(HashCodeHelper.class);
        if 
(!DefaultTypeTransformation.booleanUnbox(var1[10].call(var1[11].callCurrent(this),
 this))) {
            Object var3 = var1[12].callStatic(HashCodeHelper.class, _result, 
var1[13].callCurrent(this));
            _result = var3;
        }

        return DefaultTypeTransformation.intUnbox(_result);
    }

    public boolean canEqual(Object other) {
        CallSite[] var2 = $getCallSiteArray();
        return other instanceof GroovyDataClassTarget;
    }

    public boolean equals(Object other) {
        CallSite[] var2 = $getCallSiteArray();
        if (ScriptBytecodeAdapter.compareEqual(other, (Object)null)) {
            return false;
        } else if 
(DefaultTypeTransformation.booleanUnbox(var2[14].callCurrent(this, other))) {
            return true;
        } else if (!(other instanceof GroovyDataClassTarget)) {
            return false;
        } else {
            GroovyDataClassTarget otherTyped = (GroovyDataClassTarget)other;
            if 
(!DefaultTypeTransformation.booleanUnbox(var2[15].call(otherTyped, this))) {
                return false;
            } else {
                return 
ScriptBytecodeAdapter.compareEqual(var2[16].callCurrent(this), 
var2[17].call(otherTyped));
            }
        }
    }

    public int getValRead() {
        return this.valRead;
    }

    public void setValRead(int var1) {
        this.valRead = var1;
    }
}
----

We can appreciate that the methods added by @ToString, @EqualsAndHashcode, and 
the property getter/setter are not
annotated with @Generated, which will prompt JaCoCo to mark them as not 
covered. The rationale from the JaCoCo team
is that these methods should be annotated as the compiler is "trusted", only 
those methods explicitly added to the
source should be covered.

Thus, here comes the call to action and the reason why I wanted to start this 
conversation in the first place:
 - modify the Groovy compiler to add @Generated on property getters and setters.
 - modify core AST xforms to add @Generated where it makes sense.

Related to the original @Generated issue (as commented by Evgeny at 
https://github.com/jacoco/jacoco/pull/610<https://urldefense.proofpoint.com/v2/url?u=https-3A__github.com_jacoco_jacoco_pull_610&d=DwMFaQ&c=4ZIZThykDLcoWk-GVjSLmy8-1Cr1I4FWIvbLFebwKgY&r=tPJuIuL_GkTEazjQW7vvl7mNWVGXn3yJD5LGBHYYHww&m=hAfmXOD5R2AP8VF69wcL-UqYZEv2tXAbyBRfDJh0lfg&s=44Nu_515VJUtJ8wIVcQJF2ow_axTotUprpo9KwRmQMY&e=>)
 fields
do not have line numbers, would be good to have them.

What do you think?

Cheers,
Andres

-------------------------------------------
Java Champion; Groovy Enthusiast
JCP EC Associate Seat
http://andresalmiray.com<https://urldefense.proofpoint.com/v2/url?u=http-3A__andresalmiray.com&d=DwMFaQ&c=4ZIZThykDLcoWk-GVjSLmy8-1Cr1I4FWIvbLFebwKgY&r=tPJuIuL_GkTEazjQW7vvl7mNWVGXn3yJD5LGBHYYHww&m=hAfmXOD5R2AP8VF69wcL-UqYZEv2tXAbyBRfDJh0lfg&s=ynGizu7RDzXxq8Mp65SEUYPVyKHTo7kc14uNurHMLRM&e=>
http://www.linkedin.com/in/aalmiray<https://urldefense.proofpoint.com/v2/url?u=http-3A__www.linkedin.com_in_aalmiray&d=DwMFaQ&c=4ZIZThykDLcoWk-GVjSLmy8-1Cr1I4FWIvbLFebwKgY&r=tPJuIuL_GkTEazjQW7vvl7mNWVGXn3yJD5LGBHYYHww&m=hAfmXOD5R2AP8VF69wcL-UqYZEv2tXAbyBRfDJh0lfg&s=jMgNE8ncoIZmIOY8tu6FwSSs_8KA4Vh6-7GpRqDNEd4&e=>
--
What goes up, must come down. Ask any system administrator.
There are 10 types of people in the world: Those who understand binary, and 
those who don't.
To understand recursion, we must first understand recursion.

Reply via email to