the scenario is, i can write groovy code that will be executed by a script engine within another application.
the only thing i know is the function name and it takes one argument, for example:
def runGroovyCode(name1) { ... }
is there a way to figure out from within the groovy code itself, what other variable or objects (other than the name1 passed in) that is available to be used by the groovy code?
hope i described this clearly. it's kind of like the groovy code self-discovering what external variables (data) are within it's scope.
basically, i need more data for my groovy code. and i need to confirm, if name1 is the only data i have, or maybe there are more variables available, but i don't know what their names are otherwise i cannot access them.
i need to find out what variables or objects are available in this scripting engine execution environment that my groovy code will be running within.
there is no further documentation. so basically, my groovy code is running in a black box.
You can have a look at groovy.lang.Script class. It has a property called binding which contains all the variables that have been passed to the script.
Here is an example
class Main {
def static SCRIPT = """
// binding var contains all the bindings of the script
println binding.variables
"""
public static void main(String[] args) {
def factory = new ScriptEngineManager();
def engine = factory.getEngineByName("groovy");
engine.eval(SCRIPT, new SimpleBindings([param1: 1, param2: 2]));
}
}
Output:
[param1:1, param2:2, context:javax.script.SimpleScriptContext#2f7c2f4f, out:java.io.PrintWriter#6af93788]
Related
I would like to know if it is possible to retrieve the name of a variable.
For example if I have a method:
def printSomething(def something){
//instead of having the literal String something, I want to be able to use the name of the variable that was passed
println('something is: ' + something)
}
If I call this method as follows:
def ordinary = 58
printSomething(ordinary)
I want to get:
ordinary is 58
On the other hand if I call this method like this:
def extraOrdinary = 67
printSomething(extraOrdinary)
I want to get:
extraOrdinary is 67
Edit
I need the variable name because I have this snippet of code which runs before each TestSuite in Katalon Studio, basically it gives you the flexibility of passing GlobalVariables using a katalon.features file. The idea is from: kazurayam/KatalonPropertiesDemo
#BeforeTestSuite
def sampleBeforeTestSuite(TestSuiteContext testSuiteContext) {
KatalonProperties props = new KatalonProperties()
// get appropriate value for GlobalVariable.hostname loaded from katalon.properties files
WebUI.comment(">>> GlobalVariable.G_Url default value: \'${GlobalVariable.G_Url}\'");
//gets the internal value of GlobalVariable.G_Url, if it's empty then use the one from katalon.features file
String preferedHostname = props.getProperty('GlobalVariable.G_Url')
if (preferedHostname != null) {
GlobalVariable.G_Url = preferedHostname;
WebUI.comment(">>> GlobalVariable.G_Url new value: \'${preferedHostname}\'");
} else {
WebUI.comment(">>> GlobalVariable.G_Url stays unchanged");
}
//doing the same for other variables is a lot of duplicate code
}
Now this only handles 1 variable value, if I do this for say 20 variables, that is a lot of duplicate code, so I wanted to create a helper function:
def setProperty(KatalonProperties props, GlobalVariable var){
WebUI.comment(">>> " + var.getName()" + default value: \'${var}\'");
//gets the internal value of var, if it's null then use the one from katalon.features file
GlobalVariable preferedVar = props.getProperty(var.getName())
if (preferedVar != null) {
var = preferedVar;
WebUI.comment(">>> " + var.getName() + " new value: \'${preferedVar}\'");
} else {
WebUI.comment(">>> " + var.getName() + " stays unchanged");
}
}
Here I just put var.getName() to explain what I am looking for, that is just a method I assume.
Yes, this is possible with ASTTransformations or with Macros (Groovy 2.5+).
I currently don't have a proper dev environment, but here are some pointers:
Not that both options are not trivial, are not what I would recommend a Groovy novice and you'll have to do some research. If I remember correctly either option requires a separate build/project from your calling code to work reliable. Also either of them might give you obscure and hard to debug compile time errors, for example when your code expects a variable as parameter but a literal or a method call is passed. So: there be dragons. That being said: I have worked a lot with these things and they can be really fun ;)
Groovy Documentation for Macros
If you are on Groovy 2.5+ you can use Macros. For your use-case take a look at the #Macro methods section. Your Method will have two parameters: MacroContext macroContext, MethodCallExpression callExpression the latter being the interesting one. The MethodCallExpression has the getArguments()-Methods, which allows you to access the Abstract Syntax Tree Nodes that where passed to the method as parameter. In your case that should be a VariableExpression which has the getName() method to give you the name that you're looking for.
Developing AST transformations
This is the more complicated version. You'll still get to the same VariableExpression as with the Macro-Method, but it'll be tedious to get there as you'll have to identify the correct MethodCallExpression yourself. You start from a ClassNode and work your way to the VariableExpression yourself. I would recommend to use a local transformation and create an Annotation. But identifying the correct MethodCallExpression is not trivial.
no. it's not possible.
however think about using map as a parameter and passing name and value of the property:
def printSomething(Map m){
println m
}
printSomething(ordinary:58)
printSomething(extraOrdinary:67)
printSomething(ordinary:11,extraOrdinary:22)
this will output
[ordinary:58]
[extraOrdinary:67]
[ordinary:11, extraOrdinary:22]
I am trying to assert my API response data with a value I obtained from my Database.
My code fails to compare my variables unless I add toString() to both my variable. Is there any way around it or is toString() mandatory?
The code in question is :
Boolean comparision = false;
for (int i; i < vars.getObject("dbData").size(); i++) {
if (vars.getObject("dbData").get(i).get("DbData").toString().equals(${codeid_API}.toString()))
{
comparision = true;
}
}
${codeid_API} is the variable where I stored my API response data.
(vars.getObject("dbData").get(i).get("DbData") gets the value from my DB.
You can use Objects.equals instead
Objects.equals(vars.getObject("dbData").get(i).get("DbData"), ${codeid_API});
Returns true if the arguments are equal to each other and false otherwise.
For integer you can compare using == and use as int for casting
if (vars.getObject("dbData").get(i).get("DbData") as int == vars.get("codeid_API") as int );
Don't inline JMeter variables in form of ${codeid_API} into Groovy scripts, in case of enabled compiled scripts caching it will be resolved only once and it might break your script logic.
Consider replacing it with vars.get('codeid_API) instead where vars is the shorthand for JMeterVariables class instance
Quote from JSR223 Sampler documentation:
JMeter processes function and variable references before passing the script field to the interpreter, so the references will only be resolved once. Variable and function references in script files will be passed verbatim to the interpreter, which is likely to cause a syntax error. In order to use runtime variables, please use the appropriate props methods, e.g.
props.get("START.HMS");
props.put("PROP1","1234");
More information: Debugging JDBC Sampler Results in JMeter
I am new to Groovy.
I have a function in which I am writing a value to map.
def addTraceEntry(key, value) {
def traceability = [:]
traceability[key] = value.trim()
println "This print happens in function addTraceEntry " + traceability
}
I have another function that needs to verify whether the above function works properly.
def testAddTraceEntry() {
def key = 'test_key'
def value = 'test_value'
addTraceEntry(key, value)
println "This print happens in function testAddTraceEntry " + traceability
assert value == traceability[key]
}
I am invoking the testAddTraceEntry() function using the function name:
testAddTraceEntry()
When I run this, I get the ERROR:
This print happens in function addTraceEntry [test_key:test_value]
Caught: groovy.lang.MissingPropertyException: No such property: traceability for class: HelloWorld
groovy.lang.MissingPropertyException: No such property: traceability for class: HelloWorld
at HelloWorld.testAddTraceEntry(HelloWorld.groovy:53)
at HelloWorld.run(HelloWorld.groovy:57)
In the function testAddTraceEntry it clearly does not know the value of traceability so seems like its giving an ERROR for that.
I tried to return the value of traceability.
def addTraceEntry(key, value) {
def traceability = [:]
traceability[key] = value.trim()
println "This print happens in function addTraceEntry " + traceability
return traceability
}
But this yields the same ERROR.
There are a bunch of things worth mentioning after seeing the code you have wrote.
First thing - the scope of variables and encapsulation. Let's throw away technicalities for a moment and focus on something even more important. In method addTraceEntry you persist some state, which is fine. However, the implementation of the method testAddTraceEntry reveals that this method tries to know way to much about the implementation details of addTraceEntry. It encapsulates (hides in other words) persistence logic (from the API point of view you, as a caller, don't know that it persists key and a value inside the map) and that is why testAddTraceEntry should never ever make assumptions that calling this method mutated some structure. If you do so, then:
your test method contracts side effects and not the expected business logic (storing data in some kind of global map - don't do it. Ever)
your test blocks any evolution of tested method implementation - imagine, that you decided to store key and value in a different structure. You may do it without breaking any API contract (your function produces the same results), but the test method will fail and you will have to modify it.
Second thing - your addTraceEntry method always produces a map with a single entry. It doesn't make much sense and if you call your function let's say 4 times you will end up with 4 maps where each one of them contain a single key mapped to a single value.
There are at least various ways to improve implementation of your methods. The simplest thing you can do is to implement a class that encapsulates logic for storing keys and values. Consider following example:
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
class TraceEntriesStorage {
private final ConcurrentMap<String, Object> entries = [:] as ConcurrentHashMap
def addTraceEntry(String key, Object value) {
entries.put(key, value)
}
def containsTraceEntry(String key) {
return entries.containsKey(key)
}
def retrieveTraceEntryForKey(String key) {
return entries.get(key)
}
}
This is a simple class with 3 short methods. It stores trace entries inside the internal concurrent map (to solve problems with concurrent access). Now, your test method could look like this:
def storage = new TraceEntriesStorage()
storage.addTraceEntry("test_key", "test_value")
assert storage.containsTraceEntry("test_key")
assert storage.retrieveTraceEntryForKey("test_key") == "test_value"
You create an instance of this class, you add an entry and you check if methods containsTraceEntry and retrieveTraceEntryForKey return expected values. As you can see it doesn't matter where we stored this trace entry - it matters that the class we have implemented behaves as expected. To make this test method even better you could add an assertion that checks if there is no trace entry for test_key before we actually insert it - this way we know that adding trace entry change internal state of the class. But what is nice in this approach is that as long as we don't break the contract, we can experiment and modify implementation of TraceEntriesStorage. Because what is most important - adding trace entries have to allow to retrieve them back from the object. How it gets stored, where it gets stored - it doesn't matter.
I hope you find this answer useful and it will help you in learning Groovy and designing a better programs. Happy hacking!
You need to combine adding the return statement to addTraceEntry() with assigning the returned value to a variable in testAddTraceEntry():
def traceability = addTraceEntry(key, value)
I'm getting a text which contains ${somethingElse} inside, but it's just a normal String.
I've got a class:
class Whatever {
def somethingElse = 5
void action(String sth) {
def test = []
test.testing = sth
assert test.testing == 5
}
}
Is it possible with groovy?
EDIT:
my scenario is: load xml file, which contains nodes with values pointing to some other values in my application. So let's say I've got shell.setVariable("current", myClass). And now, in my xml I want to be able to put ${current.someField} as a value.
The trouble is, that the value from xml is a string and I can't evaluate it easily.
I can't predict how these "values" will be created by user, I just give them ability to use few classes.
I cannot convert it when the xml file is loaded, it has to be "on demand", since I use it in specific cases and I want them to be able to use values at that moment in time, and not when xml file is loaded.
Any tips?
One thing you could do is:
class Whatever {
def somethingElse = 5
void action( String sth ) {
def result = new groovy.text.GStringTemplateEngine().with {
createTemplate( sth ).make( this.properties ).toString()
}
assert result == "Number 5"
}
}
// Pass it a String
new Whatever().action( 'Number ${somethingElse}' )
At first, what we did, was used this format in xml:
normalText#codeToExecuteOrEvaluate#normalText
and used replace closure to regexp and groovyShell.evaluate() the code.
Insane. It took a lot of time and a lot of memory.
In the end we changed the format to the original one and crated scripts for each string we wanted to be able to evaluate:
Script script = shell.parse("\""+stringToParse+"\"")
where
stringToParse = "Hello world # ${System.currentTimeMillis()}!!"
Then we were able to call script.run() as many times as we wanted and everything performed well.
It actually still does.
I'd like to re-implement a method of a Java class. For example, for "hi".length() to return 4. (How) Can I do that?
I know using SomeClass.metaClass I can get a reference to an existing method and define new (or overriding) method, but I can't seem to be able to do that for existing Java methods.
Using Groovy, you can replace any method (even those of final classes) with your own implementation. Method replacement in Groovy uses the meta-object protocol, not inheritance.
Here's the example you requested, i.e. how to make String.length() always return 4
// Redefine the method
String.metaClass.invokeMethod = { name, args ->
def metaMethod = delegate.metaClass.getMetaMethod(name, args)
def result = metaMethod.invoke(delegate, args)
name == 'length' ? 4 : result
}
// Test it
assert "i_do_not_have_4_chars".length() == 4
Seems like it could be possible by abusing String metaClass. But the attempt I've done so far in groovy console didn't led to the expected result :
def oldLength = String.metaClass.length
String.metaClass.length = { ->
return oldLength+10;
}
println "hi".length()
outputs the sad 2
I think you could take a look at Proxy MetaClass or Delegating metaClass.
If you did redefine it, it would only work in Groovy code. Groovy can't change the way Java code executes.
In Groovy, "hi".length() is roughly equivalent to this Java:
stringMetaClass.invokeMethod("hi","length");
Because Groovy doesn't actually call length directly, metaClass tricks work in Groovy code. But Java doesn't know about MetaClasses, so there is no way to make this work.
Although this question is very old I like to point out another way (at least for newer Groovy versions) .
The length() method in java.lang.String is implemented from java.lang.CharSequence interface. In order to reimplement the method using the String-metaClass you need to "override" the method in the metaClass of the interface first.
CharSequence.metaClass.length = { -> -1}
String.metaClass.length = { -> 4 }
assert "i_do_not_have_4_chars".length() == 4
The solution using String.metaClass.invokeMethod changes the behaviour of all String-methods and is problematic. For instance, simply invoking "asdf".size() leads to an exception on my setup.