Groovyc successfully compiles invalid groovy scripts - groovy

c:\>cat invalid.groovy
com.test.build(binding)
c:\junk>groovyc invalid.groovy
c:\junk>ls invalid.class
invalid.class
Why does this not result in an compilation error? There is no such method com.test.build!
In what scenario will this compilation be successful?

As Groovy is dynamic it cannot know that there will be no com.test.build at runtime
com may be an object added to the binding at a later date in the code's execution
An example:
Say we have this bit of groovy inside Script.groovy
com.test.build( binding )
Compile it with groovyc:
groovyc Script.groovy
Then, keep the Script.class file, and throw away the groovy script to make sure we are using the class file:
rm Script.groovy
Now, say we have the following in Runner.groovy
// Create a class which you can call test.build( a ) on
class Example {
Map test = [
build : { it ->
println "build called with parameter $it"
}
]
}
// Create one of our Com objects
def variable = new Example()
// Load the Script.class, and make an instance of it
def otherscript = new GroovyClassLoader().loadClass( 'Script' ).newInstance()
// Then, set `com` in the scripts binding to be our variable from above
otherscript.binding.com = variable
// Then, run the script
otherscript.run()
That prints:
build called with parameter groovy.lang.Binding#7bf35647
So as you can see, it gets the test parameter of the com object (the map above) and calls the build closure with the binding as a parameter...
Hope this makes sense...

Related

Variable visibility in a Groovy Script

I have created a temp.groovy script:
def loggedMessages = [];
def log(message) {
loggedMessages << message;
}
log "Blah"
print loggedMessages.join('\n')
When I run it I get an exception relating to the log method:
groovy.lang.MissingPropertyException: No such property: loggedMessages
Why can't the log method see the loggedMessages variable? I obviously have a fundamental misunderstanding of what's going on. I've tried to read the docs but I can't seem to find the relevant section.
From the comments, as an answer:
The Script class documentation states (in the last section 3.4 Variables) that:
if the variable is declared as in the first example [with a type definition], it is a local variable. It will be declared in the run method that the compiler will generate and will not be visible outside of the script main body. In particular, such a variable will not be visible in other methods of the script
And further: if the variable is undeclared, it goes into the script binding. The binding is visible from the methods [...]
Therefore removing the def from loggedMessages fixes the error:
loggedMessages = [];
def log(message) {
loggedMessages << message;
}
log "Blah"
print loggedMessages.join('\n')
(Alternatively it can be annotated using #groovy.transform.Field as #aetheria pointed out in the comments)

Groovy - Type check AST generated code

I have a Groovy application that can be custimized by a small Groovy DSL I wrote. On startup, the application loads several Groovy scripts, applies some AST transformations and finally executes whatever was specified in the scripts.
One of the AST transformations inserts a couple of lines of code into certain methods. That works fine and I can see the different behavior during runtime. However, sometimes the generated code is not correct. Although I load the scripts with the TypeChecked customizer in place, my generated code is never checked for soundness.
To show my problem, I constructed an extreme example. I have the following script:
int test = 10
println test // prints 10 when executed without AST
I load this script and insert a new line of code between the declaration of test and println:
public void visitBlockStatement(BlockStatement block) {
def assignment = (new AstBuilder().buildFromSpec {
binary {
variable "test"
token "="
constant 15
}
}).first()
def newStmt = new ExpressionStatement(assignment)
newStmt.setSourcePosition(block.statements[1])
block.statements.add(2, newStmt)
super.visitBlockStatement(block)
}
After applying this AST, the script prints 15. When I use AstNodeToScriptVisitor to print the Groovy code of the resulting script, I can see the new assignment added to the code.
However, if I change the value of the assignment to a String value:
// ...
def assignment = (new AstBuilder().buildFromSpec {
binary {
variable "test"
token "="
constant "some value"
}
}).first()
// ...
I get a GroovyCastExcpetion at runtime. Although the resulting script looks like this:
int test = 10
test = "some value" // no compile error but a GroovyCastException at runtime here. WHY?
println test
no error is raised by TypeChecked. I read in this mailing list, that you need to set the source position for generated code to be checked, but I'm doing that an it still doesn't work. Can anyone provide some feedback of what I am doing wrong? Thank you very much!
Update
I call the AST by attaching it to the GroovyShell like this:
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(TypeChecked)
)
config.addCompilationCustomizers(
new ASTTransformationCustomizer(new AddAssignmentAST())
)
def shell = new GroovyShell(config)
shell.evaluate(new File("./path/to/file.groovy"))
The class for the AST itself looks like this:
#GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
class AddAssignmentAST implements ASTTransformation {
#Override
public void visit(ASTNode[] nodes, SourceUnit source) {
def transformer = new AddAssignmentTransformer()
source.getAST().getStatementBlock().visit(transformer)
}
private class AddAssignmentTransformer extends CodeVisitorSupport {
#Override
public void visitBlockStatement(BlockStatement block) {
// already posted above
}
}
}
Since my Groovy script only consists of one block (for this small example) the visitBlockStatement method is called exactly once, adds the assignment (which I can verify since the output changes) but does not ever throw a compile-time error.

groovy script binding does not seem to operate

though I like Groovy sometimes the documentation is ... well terse
I have a problem to pass a binding to a script.
here is the code :
def conf = new CompilerConfiguration()
Binding env = new Binding()
//....
env.setProperty( // some key and value set
//
conf.setScriptBaseClass(// name of class that is a Script
def shell = new GroovyShell(env,conf)
//isReader is the reader of the code// argz is an array of Strings
shell.run(isReader, scriptName, argz)
well, for one, the "scriptname" parameter in "run" is an obscure feature (any hint? -the reader does not have the notion of a file-)
then the Binding I get in my script instance is different from the one I created! (so unable to pass variables back and forth)
I need to invoke my script using my script base class and pass a Binding and parameters to it ... if there is another way that works I would be delighted! thanks!
edit ... trying to set up examples :
package abcgroovy
abstract class MyScript extends Script{
MyScript() {
super()
def _env = getBinding()
println _env
}
MyScript(Binding binding) {
super(binding)
def _env = binding
println _env
}
}
then the invoking code :
def conf = new CompilerConfiguration()
Binding env = new Binding()
println "ENV calling :$env"
conf.setScriptBaseClass("abcgroovy.MyScript")
def shell = new GroovyShell(env,conf)
Reader isReader = new StringReader("println 'hello world'")
// second argument: can't guess what to put
shell.run(isReader,'abcgroovy.MyScript',new String[0])
now the result of a run :
ENV calling :groovy.lang.Binding#67a20f67
groovy.lang.Binding#6a192cfe
hello world
From my understanding the second parameters (fileName) is the name of the file that is parsed. This has no relevance to any functionality, but is used for error reporting. So if you have an error in your provided script, the fileName will be used in the error message. Thus, when using a different signature where you provide a URI or a File that parameter cannot be specified.
In your example the Binding from the GroovyShell actually is used, which you can verify by printing the Binding in the actual script.
def conf = new CompilerConfiguration()
Binding env = new Binding()
println "ENV calling :$env"
conf.setScriptBaseClass("abcgroovy.MyScript")
def shell = new GroovyShell(env,conf)
Reader isReader = new StringReader('println "binding in script: ${getBinding()}"')
shell.run(isReader,'abcgroovy.MyScript',new String[0])
The reason your example outputs a different Binding is the way the script is initialized. First a new MyScript instance is created without passing the Binding, then the Binding from the GroovyShell is set using setBinding. The constructor with the Binding parameter is not used.
abstract class MyScript extends Script {
MyScript() {
super()
println "MyScript() with binding ${getBinding()}"
}
MyScript(Binding binding) {
super(binding)
// not invoked!
println "MyScript(${binding})"
}
public void setBinding(Binding binding) {
super.setBinding(binding)
println "setBinding to ${binding}"
}
}
Example output
ENV calling :groovy.lang.Binding#335eadca
MyScript() with binding groovy.lang.Binding#45018215
setBinding to groovy.lang.Binding#335eadca
binding in script: groovy.lang.Binding#335eadca
Note that the Binding is temporarily different in the MyScript() constructor only.

how to detect caller instance in SoapUI groovy script?

A SoapUI project can run random script upon load.
Load Script is invoked with log and project variables.
In my shared lib I have method - addAsserts() that traverses the whole project and adds schema compliance assertions to SOAP test steps. In my Load Script I call shared method
addAsserts(this)
passing 'this' as a parameter and set closure.delegate to it inside addAsserts method to make 'project' variable accessible within the closure scope
addAsserts method is defined in sharedUtil.groovy:
static def addAsserts(that){
def closure={
project.testSuites.each { testSuiteName, testSuiteObject ->
testSuiteObject.testCases.each { testCaseName, testCaseObject ->
testCaseObject.testSteps.each { testStepName, testStepObject ->
if ("class com.eviware.soapui.impl.wsdl.teststeps.WsdlTestRequestStep" == testStepObject.getClass().toString() ) {
log.info "adding 'Schema Compliance' assertion to ${testSuiteName}/${testCaseName}/${testStepName}"
testStepObject.addAssertion('Schema Compliance')
}
}
}
}
}//closure
closure.delegate=that // <--- i would like NOT to pass 'that' as parameter
// but rather detect in runtime with some kind of
// getCallerInstance() method
return closure.call()
}
QUESTION:
Is it possible to detect caller instance in runtime with some kind of getCallerInstance() method ?
No, I don't believe this is possible. Wasn't in Java either (you can find out the name/method of the calling class using some horrible stacktrace hacking, but not the instance of the class itself)
Edit...
It might be possible with a Category (but I am not experienced with SoapUI, so I don't know if this technique would fit)
Say we have a class Example defined like so:
class Example {
String name
}
We can then write a class very similar to your example code, which in this case will set the delegate of the closure, and the closure will print out the name property of the delegate (as we have set the resolve strategy to DELEGATE_ONLY)
class AssetAddingCategory {
static def addAsserts( that ) {
def closure = {
"Name of object: $name"
}
closure.delegate = that
closure.resolveStrategy = Closure.DELEGATE_ONLY
closure.call()
}
}
Later on in our code, it is then possible to do:
def tim = new Example( name:'tim' )
use( AssetAddingCategory ) {
println tim.addAsserts()
}
And this will print out
Name of object: tim

Groovy-script in jenkins println output disappears when called inside class environment

The output from println from within a class function is lost.
An example script (outputclass.groovy):
class OutputClass
{
OutputClass()
{
println("Inside class") // This will not show in the console
}
}
println("Outside class") // Only this is shown in the console
output = new OutputClass()
I use Jenkins CLI to execute the groovy script
java -jar ..\jenkins-cli.jar -s JENKINS_SERVER_URL groovy outputclass.groovy
It only outputs this:
Outside class
It seems like the class inmplicitly uses println from System.out.println, and System.out is directed to the log files, but the println outside the class is using something else, which is outputted in the script console. The following code shows the behavior.
System.out.println("First")
println("Second")
Output:
Second
How do I explicitly set the output device to output to the Jenkins script console?
I found the solution myself here http://mriet.wordpress.com.
When the Groovy plugin starts is passes two bindings to the script. From the bindings we can get the out variable. Get it and use out.println to output to the script console, not the plain println.
The script below shows full solution.
import hudson.model.*
// Get the out variable
def out = getBinding().out;
class OutputClass
{
OutputClass(out) // Have to pass the out variable to the class
{
out.println ("Inside class")
}
}
out.println("Outside class")
output = new OutputClass(out)
If you use the skript as a post build step (I'm not shure whether it works with the mentioned CLI) you can use the build in logger:
manager.listener.logger.println("some output")
So in your case something like this may be helpful:
class OutputClass
{
OutputClass(logger) // Have to pass the out variable to the class
{
logger.println ("Inside class")
}
}
output = new OutputClass(manager.listener.logger)
See also Example 10 in Groovy Plugin Doc
Does this mailing list post help?
the output is sent to standard output, so if you check your log file, you
will probably see something like this: INFO [STDOUT] Hello World
if you insist on using system script, you have to pass out variable to
your class, as the binding is not visible inside the class (so it's
passed to standard output). You should use something like this
public class Hello {
static void say(out) {
out << "Hello World "
}
}
println "Started ..."
Hello.say(out)
A simple solution that worked well for me was to add this line on top of each script. This enables usage of traditional println commands all over the code (inside and outside of classes) leaving the code intuitive.
import hudson.model.*
System.out = getBinding().out;
This enables to create log entries like this:
println("Outside class");
class OutputClass {
OutputClass() {
println ("Inside class")
}
}
new OutputClass();
It replaces the default print stream in System.out with the one handed over from Jenkins via bindings.

Resources