Just started learning Groovy, got the PragProg book "Programming Groovy" and had a problem compiling one of the sample scripts:
class GCar2 {
final miles = 0
def getMiles() {
println "getMiles called"
miles
}
def drive(dist) {
if (dist > 0) {
miles += dist
}
}
}
def car = new GCar2()
println "Miles: $car.miles"
println 'Driving'
car.drive(10)
println "Miles: $car.miles"
try {
print 'Can I see the miles? '
car.miles = 12
} catch (groovy.lang.ReadOnlyPropertyException ex) {
println ex.message
GroovyCar2.groovy: 20: cannnot access final field or property outside of constructor.
# line 20, column 35.
def drive(dist) { if (dist > 0) miles += dist }
^
Groovy versions prior to 1.7 do not give an error. I looked through whatever documentation I could find and did not see the issue discussed. What is going on here?
Aaron
I don't know much about Groovy 1.7, but it looks like a bug in earlier versions which has now been fixed - if a variable is final, you shouldn't be able to assign to it outside the constructor (or its declaration). If you can, what's the point of making it final?
I doubt that it'll stop you from reading it outside the constructor though...
You shouldn't be able to assign to a final variable in a normal method. It was a bug in groovy, fixed in 1.7.
Related
I have groovy code as below:
def randomInt = RandomUtil.getRandomInt(1,200);
log.info randomInt
def chars = (("1".."9") + ("A".."Z") + ("a".."z")).join()
def randomString = RandomUtil.getRandomString(chars, randomInt) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
I want to evaluate a String which one like a {classname}.{methodname} in SoapUI with Groovy, just like above, but got error here, how to handle this and make it works well as I expect?
I have tried as blew:
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
Error As below:
Thu May 23 22:26:30 CST 2019:ERROR:An error occurred [No such property: getRandomString(chars, randomInt) for class: com.hypers.test.apitest.util.RandomUtil], see error log for details
The following code:
log = [info: { println(it) }]
class RandomUtil {
static def random = new Random()
static int getRandomInt(int from, int to) {
from + random.nextInt(to - from)
}
static String getRandomString(alphabet, len) {
def s = alphabet.size()
(1..len).collect { alphabet[random.nextInt(s)] }.join()
}
}
randomInt = RandomUtil.getRandomInt(1, 200)
log.info randomInt
chars = ('a'..'z') + ('A'..'Z') + ('0'..'9')
def randomString = RandomUtil.getRandomString(chars, 10) //works well with this code
log.info randomString
evaluate("log.info new Date()")
evaluate('RandomUtil.getRandomString(chars, randomInt)') //got error with this code
emulates your code, works, and produces the following output when run:
~> groovy solution.groovy
70
DDSQi27PYG
Thu May 23 20:51:58 CEST 2019
~>
I made up the RandomUtil class as you did not include the code for it.
I think the reason you are seeing the error you are seeing is that you define your variables char and randomInt using:
def chars = ...
and
def randomInt = ...
this puts the variables in local script scope. Please see this stackoverflow answer for an explanation with links to documentation of different ways of putting things in the script global scope and an explanation of how this works.
Essentially your groovy script code is implicitly an instance of the groovy Script class which in turn has an implicit Binding instance associated with it. When you write def x = ..., your variable is locally scoped, when you write x = ... or binding.x = ... the variable is defined in the script binding.
The evaluate method uses the same binding as the implicit script object. So the reason my example above works is that I omitted the def and just typed chars = and randomInt = which puts the variables in the script binding thus making them available for the code in the evaluate expression.
Though I have to say that even with all that, the phrasing No such property: getRandomString(chars, randomInt) seems really strange to me...I would have expected No such method or No such property: chars etc.
Sharing the code for for RandomUtil might help here.
//Code 1
log.info undefined
When we run the code 1 , we get below error in soapui/readyapi as
please note :- Line number is visible in error message
However to avoid this alert , we used try/catch to print this, so the above code is amended to below as code 2
//code 2
try
{
log.info undefined
}
catch(Exception e)
{
log.info e
}
when we run the code 2 we get below results
Mon Mar 19 15:04:16 IST 2018:INFO:groovy.lang.MissingPropertyException: No such property: undefined for class: Script6
Problem :- How can we see the line number where the problem is just like we are able to see in code1
Requirement :- Our exception block should be able to tell problem is in which line.
Since its a small code we are able to know, Sometimes the code is having 100+ lines and its difficult to know where the exception is
Building on #tim_yates answer of using e.stackTrace.head().linenumber:
import org.codehaus.groovy.runtime.StackTraceUtils
try {
println undefined
} catch (Exception e) {
StackTraceUtils.sanitize(e)
e.stackTrace.head().lineNumber
}
Use sanitize() on your Exception to remove all the weird Groovy internal stuff from the stack trace for your Exception. Otherwise when you look at the first StackTraceElement, it probably won't be the one you want.
deepSanitize() is the same, but also applies the transform to all the nested Exceptions if there are any.
Thanks Chris and Jeremy for solving my problem.
I have used the below solution using Chris answer with all full respect to your answer
try
{
log.info undefined
}
catch(Exception e)
{
log.error "Exception = " + e
String str= e.getStackTrace().toString()
def pattern = ( str =~ /groovy.(\d+)./ )
log.error " Error at line number = " + pattern[0][1]
}
The reason i am using that answer is i can avoid an import in all my scripts.
I have used pattern matching to extract the line number as it always come like
(Script18.groovy:17),
so i have used the pattern
/groovy.(\d+)./
Now i get both the exception details and line number
You can use log.info e.getStackTrace().toString(); to get the full stack trace.
However, it'll be hard to pick out the issue. Here's my Groovy script....
try
{
log.info undefined
}
catch(Exception e)
{
log.info e.getStackTrace().toString();
}
Here's the trace....
Mon Mar 19 17:15:20 GMT 2018:INFO:[org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:50), org.codehaus.groovy.runtime.callsite.PogoGetPropertySite.getProperty(PogoGetPropertySite.java:49), org.codehaus.groovy.runtime.callsite.AbstractCallSite.callGroovyObjectGetProperty(AbstractCallSite.java:231), Script21.run(Script21.groovy:3), com.eviware.soapui.support.scripting.groovy.SoapUIGroovyScriptEngine.run(SoapUIGroovyScriptEngine.java:100), com.eviware.soapui.support.scripting.groovy.SoapUIProGroovyScriptEngineFactory$SoapUIProGroovyScriptEngine.run(SourceFile:89), com.eviware.soapui.impl.wsdl.teststeps.WsdlGroovyScriptTestStep.run(WsdlGroovyScriptTestStep.java:154), com.eviware.soapui.impl.wsdl.panels.teststeps.GroovyScriptStepDesktopPanel$RunAction$1.run(GroovyScriptStepDesktopPanel.java:277), java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source), java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source), java.lang.Thread.run(Unknown Source)]
Note- the emboldened part. It's line 3 and that is what I expected. However, SoapUI must use internal numbering for scripts as I called the script "Dummy Groovy Script" and the stack trace says "Script21".
Anyhow, I do think you ought look at your Groovy script, 100+ lines in a Try seems a bit much and as you pint out, it's difficult to see the issue.
I'd suggest breaking it down into functions, or even better, call an Java class external to SoapUI, that has nice well-defined functions.
The SmartBear site describes how this can be done. Plus, it remove a lot of the bloat from the SoapUI project file.
When try this sample code:
use(groovy.time.TimeCategory) {
800.millisecond + 300.millisecond
}
in groovy web console, I get a funny result:
0.1100 seconds
Does any one know why this happens or how to fix it?
That looks like a bug, the TimeDuration contains 1100 milliseconds, but when it prints it out, it converts it wrongly to seconds.
I've added it to the Groovy JIRA as a bug EDIT It's now marked as FIXED for versions 2.0.6, 1.8.9 and 2.1.0
In the mean time, I guess you'll need to do your own converter from TimeDuration to String :-/
Edit
You could do something like this (and there is probably a neater way of doing it)
groovy.time.TimeDuration.metaClass.normalize = { ->
def newdmap = ['days','hours','minutes','seconds','millis'].collectEntries {
[ (it):delegate."$it" ]
}.with { dmap ->
[millis:1000,seconds:60,minutes:60,hours:24,days:-1].inject( [ dur:[ days:0, hours:0, minutes:0, seconds:0, millis:0 ], roll:0 ] ) { val, field ->
val.dur."$field.key" = dmap."$field.key" + val.roll
val.roll = val.dur."$field.key".intdiv( field.value )
val.dur."$field.key" = field.value < 0 ?
val.dur."$field.key" :
val.dur."$field.key" % field.value
val
}.dur
}
new TimeDuration( newdmap.days, newdmap.hours, newdmap.minutes, newdmap.seconds, newdmap.millis )
}
That adds a normalize method to TimeDuration, so then doing:
use(groovy.time.TimeCategory) {
800.millisecond + 300.millisecond
}.normalize()
Shows 1.100 seconds
I haven't done a huge amount of testing on that method, so be warned it could do with some unit tests to make sure it doesn't fall over with other situations.
How would one build a list of all Fridays in 2011, and allow for different date output, e.g. mm/dd/yyyy and yyyymmdd?
An alternative to ataylor's correct answer, you could do:
import static java.util.Calendar.*
def s = Date.parse("MM/dd/yyyy", "01/01/2011")
def e = Date.parse("MM/dd/yyyy", "12/31/2011")
(s..e).findAll {
it[ DAY_OF_WEEK ] == FRIDAY
}.each {
println it.format("MM/dd/yyyy")
}
I'd go with something like this:
use (groovy.time.TimeCategory) {
def d = Date.parse("MM/dd/yyyy", "01/01/2011")
while (d[Calendar.DAY_OF_WEEK] != Calendar.FRIDAY) {
d = d + 1.day
}
while (d[Calendar.YEAR] == 2011) {
println d.format("MM/dd/yyyy")
d = d + 1.week
}
}
Both ataylor's answer and Tim's answer are correct and very informative -- see comments if you're coming up to speed on Groovy.
I hate these situations where I'm supposed to pick one as the answer over the other. They are both right answers. Stackoverflow won't change their stuff so I can select both as correct, so I will merely point to both as being correct.
are there any improvements where i can improve this code? Maybe there are some groovy language features? This snippet flattens a xml file to: node/node/node
def root = new XmlParser().parse("src/your_xml.xml")
root.depthFirst().each { n ->
def name = n.name()
while(n?.parent()){
name = "${n?.parent()?.name()}/${name}";
n = n?.parent()
}
println name
}
I might refactor the code to use a more functional style.
def x = """
<test>
<test1>
<test2/>
</test1>
<test2>
<test3/>
<test4>
<test5/>
</test4>
</test2>
</test>
""".trim()
def root = new XmlParser().parseText(x)
def nodePath(node) {
node.parent() ? "${nodePath(node.parent())}/${node.name()}" : node.name()
}
root.depthFirst().each {
println nodePath(it)
}
assert nodePath(root.test2[0].test4[0].test5[0]) == "test/test2/test4/test5"
-- Edit: Ignore me, I'm wrong [see comments] (not the last line though);
I suspect you can write (but I could be wrong, I have no experience with this language)
while(n = n?.parent()){
But honestly; don't go with something that is cool, go with something that is readable.