Groovy untyped variables: Just Say No
I got thoroughly bitten in the ass yesterday by using untyped variables in a Groovy script in ReadyAPI. Consider, what’s wrong with the following code?
def iterationCount = 0;
def timeoutMinutes = context.expand( '${#TestCase#SW update timeout minutes}' )
log.info "timeoutMinutes : ${timeoutMinutes}"
while (complete == 0){
log.info("iterationCount : $iterationCount, timeoutMinutes : $timeoutMinutes")
if (iterationCount >= timeoutMinutes) {
log.error("Update software timed out after $timeoutMinutes minutes")
testRunner.fail ("Updating software took too long (> $timeoutMinutes minutes)");
complete = 1
} else {
. . .
}
iterationCount++
}
Hint: there’s nothing wrong with the call to context.expand or its parameter.
The first call to log.info logs
timeoutMinutes : 1
The second call logs
iterationCount : 0, timeoutMinutes : 1
iterationCount : 1, timeoutMinutes : 1
iterationCount : 2, timeoutMinutes : 1
iterationCount : 3, timeoutMinutes : 1
iterationCount : 4, timeoutMinutes : 1
iterationCount : 5, timeoutMinutes : 1
etc.
WTF?
The answer is that iterationCount is implicitly made an integer, and because context.expand returns String, timeoutMinutes is implicitly made a String.
Then in the expression (iterationCount >= timeoutMinutes), timeoutMinutes is converted to an integer, but instead of doing the sane thing like Perl would, and converting the string “1” into the integer 1, Groovy converts “1” into the ASCII value of the character 1, which is 49. So the loop can iterate 49 times before timing out.
The solution to this madness is to never use “def” to declare a variable. Declare them as what they are: “int” or “String” or “Boolean” or whatever. Once I did that, Groovy threw an error at assigning the (String) result of context.expand into the int variable timeoutMinutes, and I knew that I needed to add a call to Integer.parseInt to convert the String result of context.expand into an integer value.
This only consumed half a day. Don’t let it happen to you.