Problem:
Create GroovyClassLoader GCLA with explicitly set CompilerConfiguration
Create another GroovyClassLoader GCLB with different CompilerConfiguration that sets the BaseScriptClass and uses GCLA as parent
-> a script loaded in GCLB will not have the BaseScriptClass set correctly (but uses the potentially set basescript of GCLA)
Long Story:
My application uses four Groovy scripts to allow customization of certain actions. Each script provides a small DSL, where the DLSs are different for each script. When the action is excecuted in the application, the corresponding script will be called. Additionally some common groovy scripts can be loaded at application startup for storing of common data or definition of helper functions.
The scripts are heavily typechecked at the start of the application.
My first approach was to have one GroovyClassLoader which first loads the common groovy scripts and then does a loadClass for each of the scripts. The DSL is created by simply adding corresponding elements to the binding of each script befor the script is run. The GroovyClassLoader uses a CompilerConfiguration with TypeCheck-Extensions to typecheck the scripts.
This works quite well but has two severe limitations:
the typechecking has to be performed based on the name of the script (because the DSLs for each script is different)
the typechecking script gets quite complicated because it has to handle all four DSLs
To get rid of the named limitations I tried to use a distinct GroovyClassLoader (with my actual ClassLoader as parent) for each of the script (i. e. one per DSL) and set a corresponding ScriptBaseClass which provides the functionality for the DSL. This works very well: the typechecking code is reduced drastically, I can have distinct typechecking scripts for each DSL and I don't have to mess with the name of the script.
But I don't get this to work with the common groovy scripts. As soon as I try to use the GroovyClassLoader of the common scripts as parent for the GroovyClassLoader of the DSL scripts, the DSL no longer gets the correct BaseScriptClass although it's explictly set in the CompilerConfiguration. It seems that the child GroovyClassLoader uses the BaseScriptClass of the parent GroovyClassLoader.
Any ideas of how to get this working?
UPDATE
In my original post I did not mention that I'm using scala. Actually I tried the following code snippet:
val rootConfig = {
val cf = new CompilerConfiguration(CompilerConfiguration.DEFAULT)
cf.setSourceEncoding("UTF-8")
cf.addCompilationCustomizers(new ASTTransformationCustomizer(classOf[CompileStatic]))
cf
}
val childConfig = {
val cfg = new CompilerConfiguration(CompilerConfiguration.DEFAULT)
cfg.setSourceEncoding("UTF-8")
cfg.setScriptBaseClass("NonExisting")
cfg.addCompilationCustomizers(new ASTTransformationCustomizer(classOf[CompileStatic]))
cfg
}
val rootGCL = new GroovyClassLoader(getClass.getClassLoader, rootConfig, false)
val childGCL = new GroovyClassLoader(rootGCL, childConfig, false)
This does not work and the child ClassLoader seems to ignore the CompilerConfiguration completely: it does not complain about the non-exiting BaseScriptClass and if I switch off the static-compilation in the rootConfig it will not even perform the static checks.
If I move the ClassLoader creation to a Java file everything works as expected
GroovyClassLoader has several constructor. For example one for ClassLoader and one for GroovyClassLoader. The GroovyClassLoader version will take the configuration from it. But if you do for example new GroovyClassLoader((ClassLoader) parent) or go with giving the configuration yourself. And for that it is even enough to give null as configuration. The constructor logic will take CompilerConfiguration.DEFAULT then to configure the GroovyClassLoader: new GroovyClassLoader(parent,null) and if you want to be very sure no strange paths are added to the GroovyClassLoader, use the variant with a boolean new GroovyClassLoader(parent,null,false)
Related
I want to structure my code well to use in JMeter, so I plan to have the groovy scripts in separate groovy files. The JSR223 script can load the script file and execute it. So far so good.
The groovy scripts will get complex, so I want to split the files into multiple files and use them. For e.g. have a file utils.groovy with content
public class SharedStore {
// Initializes the store to store all information about all the data being worked upon
def initializeStore() {
HashMap allStore = new HashMap();
// def props = org.apache.jmeter.util.JMeterUtils.getJMeterProperties()
props.put("${storeHashSet}", allStore);
}
This class will grow with more methods and will be used by all scripts invoked from JMeter. There will be other similar classes.
One JMeter sampler called InitializeEnvironment.groovy wants to use this method thus:
evaluate(new File("<<path>>/Utils.groovy")); // This line is an attempt to include utils.groovy
var shr = new SharedStore().initializeStore();
The above attempt to include groovy file is based on this discussion - https://stackoverflow.com/a/9154553 (and I have tried other options from that discussion to no avail)
Using the above option throws the error:
Script70.groovy: 2: unable to resolve class SharedStore
# line 2, column 11.
var shr = new SharedStore().initializeStore();
I would prefer to wrap things utility methods a class but I can live with having keep methods in global space.
If I do manage to load the additional file like in this - https://stackoverflow.com/a/15904699 - suggestion, without using "class" and have groovy wrap it up in a class for me, props are not available in utils.groovy unless I include the line def props = org.apache.jmeter.util.JMeterUtils.getJMeterProperties() and even then ${storeHashSet} cannot be used
No such property: storeHashSet for class: SharedStore
I want to be able to decompose the scripts into more manageable files, and be able to access JMeter variables and structures in these files.
Any advise on how I can do that in JMeter?
The most straightforward solution would be:
Compiling these individual files into classes
Packaging them into .jar (this one you can skip)
Putting the .jar or (class files if you skipped step 2) in the JMeter Classpath
Using the .jar file in your JSR223 Test Elements
I have an external groovy file containing all common functions required to automate my web service testing. I reference those common functions by creating an instance of the Class defined within the external file. Now I have a situation to create an instance of the Class in first groovy test step and to use the same instance in other groovy test steps within my test case.
import groovy.lang.Binding
import groovy.util.GroovyScriptEngine
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context)
// location of script file is relative to SOAPUI project file.
String scriptPath = groovyUtils.projectPath + "\\Scripts\\"
// Create Groovy Script Engine to run the script.
GroovyScriptEngine gse = new GroovyScriptEngine(scriptPath)
// Load the Groovy Script file
externalScript = gse.loadScriptByName("CustomerQuotes.groovy")
def cq = externalScript.newInstance(context: context, log: log, testRunner: testRunner)
How do I achieve this? I need the reference of cq object in other groovy test steps to call the remaining common functions available within my external grooy file? Please help.
As per your question, the mentioned groovy script test step is placed in an arbitrary test case though it is not a natural fit.
The natural fit for the above script is to use Load Script which is at project level.
In the script, which is mentioned in the question, change below statements
From:
def groovyUtils = new com.eviware.soapui.support.GroovyUtils(context)
// location of script file is relative to SOAPUI project file.
String scriptPath = groovyUtils.projectPath + "\\Scripts\\"
To:
def projectPath = new File(project.path).parent.toString()
String scriptPath = "${projectPath}/Scripts"
And keep the rest of the script same.
Place the script (after the above change) at Project level's Load Script.
Remove the groovy script test step from wherever it is because of the above mentioned reason.
At the end of the script, add the below statement. Basically using the groovy's Meta Programming feature to store the object for sharing.
project.metaClass.myObject = cq
Next step: How to re-use the object (which is the main question)?
Since, your object cq is added to project object, the same can be accessed in any of the groovy script test steps (at any suite or case) using below statement:
def cq = context.testCase.testSuite.project.myObject
//Now call the other methods using cq.method(arguments)
EDIT: It appears that the above solution works for simple/Primitive data types.
However, you have a class instance. For that some more changes are required.
Here is your complete Project level Load Script (includes your code snippet)
def projectPath = new File(project.path).parent.toString()
String scriptPath = "${projectPath}/Scripts"
GroovyScriptEngine gse = new GroovyScriptEngine(scriptPath)
def externalScript = gse.loadScriptByName("CustomerQuotes.groovy")
project.metaClass.myObject {
externalScript.newInstance(context: it, log: log, testRunner: it.testRunner)
}
And the script for Groovy Script test step in different test cases is as follows i.e., just calling the methods of your CustomerQuotes.groovy class.
def obj = context.testCase.testSuite.project.myObject(context)
obj.run()
Assuming that there is a method in the groovy file called run. Of course, you can use your own method.
EDIT 2:
There is another alternative approach too. You need to compile the groovy classes, create jar, copy it under SOAPUI_HOME/bin/ext directory. Of course, soapui tool needs to be restarted after that.
Now you can create instance and make the desired call to the methods as needed in any of the groovy script test steps.
I've written a number of Java annotation processors that write some arbitrary data to text files that will be included in my class directory / jar file. I typically use code that looks like this:
final OutputStream out = processingEnv
.getFiler()
.createResource(StandardLocation.CLASS_OUTPUT, "", "myFile")
.openOutputStream();
I'm trying to do something similar in a groovy ASTTransformation. I've tried adding a new source file but that (expectedly) must be valid groovy. How do I write arbitrary resources from an ASTTransformation? Is it even possible?
As part of implementing your ASTTransformation, you need to implement the void visit(ASTNode[] nodes, SourceUnit source) method. In it you can call source.getConfiguration().getTargetDirectory() and it will return your build output directory, e.g. /Users/skissane/my-groovy-project/build/classes/groovy/main). You can then write your resources into there, and whatever is packaging them into the JAR (such as Gradle) should pull them from that.
In my case, I wanted to delay writing the resources until OUTPUT phase – since I was creating META-INF/services files, and I wanted to wait until I'd seen all the annotated classes before writing them, or else I'd be repeatedly adding to them for each annotated class – so I also implemented CompilationUnitAware, and then in my setCompilationUnit method I call unit.addPhaseOperation() and pass it a method reference to run during OUTPUT. Note, if you are using a local ASTTransformation, setCompilationUnit will be called multiple times (each time on a new instance of your transformation class); to avoid adding the phase operation repeatedly, I used a map in a static field to track if I'd seen this CompilationUnit before or not. My addPhaseOperation method is called once per an output class, so I used a boolean field to make sure I only wrote the resource files out once.
Doing this caused a warning to be printed:
> Task :compileGroovy
warning: Implicitly compiled files were not subject to annotation processing.
Use -implicit to specify a policy for implicit compilation.
1 warning
Adding this to build.gradle made the warning go away:
compileGroovy {
options.compilerArgs += ['-implicit:none']
}
Is there any way to automatically load user classes in the groovy interpreter, the way System.out is automatically loaded (so you don't have to import System.out to use println)? I want to be able to write scripts that employ custom classes and run the scripts in the groovy interpreter without having to import all the classes all the time.
Yep, you just need to create a profile/rc file. Just create a file at ~/.groovy/groovysh.profile and put your imports in there. You'll also want to make sure that any additional classes you want to include are part of your CLASSPATH.
ex: ~/.groovy/groovysh.profile:
import org.apache.commons.lang.StringUtils
println "in groovysh.profile"
Then run groovysh and use a method from StringUtils:
% groovysh
in groovysh.profile
Groovy Shell (1.7.3, JVM: 1.6.0_20)
Type 'help' or '\h' for help.
---------------------------------------------------------------------------------------------------------------------------------
groovy:000> StringUtils.isWhitespace(" ")
===> true
groovy:000>
You can see that the import is in place (and that it also printed out the println I had in the profile. This example will only work if you've got the commons-lang jar file is in your classpath.
See the Groovy Shell page for more details.
Groovy adds some methods to Object, including methods like println and printf that you'd expect on printWriters. They implicitly use System.out. This is actually how groovy makes if feel like System.out is globally available.
If you want to import a set of classes by default, so they can be used without specifying the full package name, Ted's comment about groovysh.profile applies.
However, if you want a specific object, like System.out, global available so its methods can be called without referencing the object, then you can add some dynamic methods to Object. For example, to make the logging methods of the default global JDK logger globally available:
Object.metaClass.info = { String message ->
java.util.logging.Logger.global.info(message)
}
Object.metaClass.warning = { String message ->
java.util.logging.Logger.global.warning(message)
}
Object.metaClass.severe = { String message ->
java.util.logging.Logger.global.severe(message)
}
etc...
Once those methods are applied to the base Object metaClass, any object can call info("message") and have it logged, effectively making Logger.global available in the same way System.out is.
For example, if I execute a Groovy script, which modifies the String meta class, adding a method foo()
GroovyShell shell1 = new GroovyShell();
shell1.evaluate("String.metaClass.foo = {-> delegate.toUpperCase()}");
when I create a new shell after that and execute it, the changes are still there
GroovyShell shell2 = new GroovyShell();
Object result = shell2.evaluate("'a'.foo()");
Is there a way to undo all meta class changes after executing the GroovyShell? I tried
shell1.getClassLoader().clearCache();
and
shell1.resetLoadedClasses();
but that did not make a change.
You can use
GroovySystem.metaClassRegistry.removeMetaClass(String.class);
to revert all changes made to the String meta class.
Alternatively you could only change the meta class of a certain String instance, thus not all instances of String would be affected.
You can use MetaClassRegistryCleaner too.
Before doing some metaclass changes, you can do
MetaClassRegistryCleaner registryCleaner = MetaClassRegistryCleaner.createAndRegister()
GroovySystem.metaClassRegistry.addMetaClassRegistryChangeEventListener(registryCleaner)
And when you want to reset the metaclass changes to the state they were earlier.
You can do
registryCleaner.clean()
GroovySystem.metaClassRegistry.removeMetaClassRegistryChangeEventListener(registryCleaner)
This way you can reset all the metaclass changes made during the duration.
I realise that this is a somewhat older question, but it's the first result on Google when I was searching for exactly the same issue.
The solution I chose was to put groovy into a new classloader (by using plexus-classworlds), so when the script is finished, the classloader is disposed (and so any changes to the metaclass are also disposed).