Spock: Reusable Data Tables - groovy

Can I take a test like this and extract the where clause data table into a reusable block?
#Unroll
void "test that doSomething with #a and #b does not fail"(String a, String b) {
when:
doSomethingWithAandB(a, b)
then:
notThrown(Exception)
where:
a | b
"foo" | "bar"
"foo" | "baz"
"foo" | "foo"
}
something like this (pseudo code):
#Unroll
void "test that doSomethingElse with #a and #b does not fail"(String a, String b) {
when:
doSomethingElseWithAandB(a, b)
then:
notThrown(Exception)
where:
dataTable()
}
def dataTable(a, b) { // this is now reusable in multiple tests
a | b
"foo" | "bar"
"foo" | "baz"
"foo" | "foo"
}

Table-formatted data in where clause is actually parsed as a set of OR expressions at compile time, collected into list of lists and then transposed, so this:
where:
a | b | c
1 | 0 | 1
2 | 2 | 2
4 | 5 | 5
will be transformed into this:
where:
a << [1, 2, 4]
b << [0, 2, 5]
c << [1, 2, 5]
before the test methods are generated (see org.spockframework.compiler.WhereBlockRewriter for details). This var << list construct is referred to as "data pipe" in documentation.
Upgrading the existing answers a bit, as of Spock 1.1, one can shorten the code a bit using a construct called "Multi-Variable Data Pipes", which will transpose the table:
class SampleTest extends Specification {
#Unroll
def "max of #a and #b gives #c"() {
expect:
Math.max(a, b) == c
where:
[a, b, c] << dataTable()
}
static def dataTable() {
[
[1, 0, 1],
[2, 2, 2],
[4, 5, 5]
]
}
}
Fun fact: while the docs on Syntactic Variations don't explain why, it is because the table rows are parsed as a set of OR expressions, the double bars can also be used -
where:
a | b || c
1 | 0 || 1
2 | 2 || 2
4 | 5 || 5

Yes, you can.
import spock.lang.Specification
import spock.lang.Unroll
class SampleTest extends Specification {
#Unroll
def "max of #a and #b gives #c"() {
expect:
Math.max(a, b) == c
where:
a << aProvider()
b << bProvider()
c << cProvider()
}
private List<Integer> aProvider() {
[1 ,2 ,4]
}
private List<Integer> bProvider() {
[0 ,2 ,5]
}
private List<Integer> cProvider() {
[1 ,2 ,5]
}
}
Of course, aProvider/bProvider/cProvider can be re-written in a 'groovier way' and among other things, can be externalized to some class and reused in many tests. You don't have to specify a table, but can supply 'data pipes'. Read more in Data Driven Testing chapter.

Thanks, Mark, for your answer! Based on what I learned, I've arrived at a reasonably simple solution.
import spock.lang.Specification
import spock.lang.Unroll
class SampleTest extends Specification {
#Unroll
def "max of #a and #b gives #c"() {
expect:
Math.max(a, b) == c
where:
params << dataTable()
a = params.a
b = params.b
c = params.c
}
// this is now reusable in multiple tests
def dataTable() {
return [
// case 1
[
a: 1,
b: 0,
c: 1
],
// case 2
[
a: 1,
b: 2,
c: 2
],
// case 3
[
a: 4,
b: 5,
c: 5
]
]
}
}

Related

Groovy #Memoize expiry based on custom event

I am using groovy to process a batch job, I am planning to cache domain object by using groovy's #Memoize annotation, but problem is the expiry, I want to expire the cache when the job is finished. Is there any way to tell groovy to expire the cache from code?
As per the docs, #Memoized features only a max value and a protection cache size parameters.
Since the AST under the covers will create the memoize mechanism through Closure::memoize, you could emulate it with a map and memoized closures which can be disposed:
class Job {
def log = []
def repoBase = [
sum: { a, b ->
log << "sum $a and $b"
a + b
},
multiply: { a, b ->
log << "multiply $a and $b"
}
]
def repo
def startJob() {
repo = repoBase.collectEntries { key, value ->
[(key): value.memoize()]
} as Expando
}
def withJob(Closure c) {
startJob()
c.delegate = repo
c.delegateStrategy = Closure.DELEGATE_FIRST
c(this)
repo = null
}
}
And test:
j = new Job()
j.withJob {
multiply 3, 4
multiply 3, 4
multiply 8, 8
sum 9, 9
sum 1, 2
sum 9, 9
}
assert j.log == [
"multiply 3 and 4",
"multiply 8 and 8",
"sum 9 and 9",
"sum 1 and 2"
]

I need a shorthand way to combine Maps in groovy with slightly different results than +

NOTE: This can be done as a method call or an operator override pretty easily, I am looking for an intrinsic one-line solution that I don't have to carry around in a library.
When you combine(add) Maps, you get a result like this:
println [a:1,c:3] + [a:2]
// prints {a=2, c=3}
I seem to keep needing results more like:
{a=[1, 2], c=[3]}
In other words, something that combines all the values from identical keys in the Maps.
Is there an operator or simple function call that does this, because doing it myself always seems to break my stride a little. It seems like the * operator might do this nicely, but it doesn't.
Is there an easy way to do this?
Another alternative (adding it to the * operator on Maps)
def a = [ a:1, c:10 ]
def b = [ b:1, a:3 ]
Map.metaClass.multiply = { Map other ->
(delegate.keySet() + other.keySet()).inject( [:].withDefault { [] } ) { m, v ->
if (delegate[v] != null) { m[v] << delegate[v] }
if (other[v] != null) { m[v] << other[v] }
m
}
}
assert a * b == [a:[1, 3], c:[10], b:[1]]
Came up with this as well, but it's late and there are probably better, shorter ways
def a = [ a:1, c:10 ]
def b = [ b:1, a:3 ]
[a,b]*.collect {k,v -> [(k):v]}
.flatten()
.groupBy { it.keySet()[0]}
.inject([:].withDefault{[]}) {m,v->
m << [(v.key):v.value[v.key]]
}
​​
Nothing came to my mind, so I start the bidding with this:
m1 = [a:1, c:666]; m2 = [a:2, b:42]
result = [:].withDefault{[]}
[m1,m2].each{ it.each{ result[it.key] << it.value } }
assert result == [a:[1,2], b:[42], c:[666]]

Remove numbers which are repeated several times in a row

I have collection
def list = [4,1,1,1,3,5,1,1]
and I need to remove numbers which are repeated three times in a row. As a result I have to get an [4,3,5,1,1]. How to do this in groovy ?
This can be done by copying the list while ensuring the two previous elements are not the same as the one to be copied. If they are, drop the two previous elements, otherwise copy as normal.
This can be implemented with inject like this:
def list = [4,1,1,1,3,5,1,1]
def result = list.drop(2).inject(list.take(2)) { result, element ->
def prefixSize = result.size() - 2
if ([element] * 2 == result.drop(prefixSize)) {
result.take(prefixSize)
} else {
result + element
}
}
assert result == [4,3,5,1,1]
You can calculate the uniques size in the next three elements and drop them when they are 1:
def list = [4,1,1,1,3,5,1,1]
assert removeTriplets(list) == [4,3,5,1,1]
def removeTriplets(list) {
listCopy = [] + list
(list.size()-3).times { index ->
uniques = list[index..(index+2)].unique false
if (uniques.size() == 1)
listCopy = listCopy[0..(index-1)] + listCopy[(index+3)..-1]
}
listCopy
}
Another option is to use Run Length Encoding
First lets define a class which will hold our object and the number of times it occurs in a row:
class RleUnit {
def object
int runLength
RleUnit( object ) {
this( object, 1 )
}
RleUnit( object, int runLength ) {
this.object = object
this.runLength = runLength
}
RleUnit inc() {
new RleUnit( object, runLength + 1 )
}
String toString() { "$object($runLength)" }
}
We can then define a method which will encode a List into a List of RleUnit objects:
List<RleUnit> rleEncode( List list ) {
list.inject( [] ) { r, v ->
if( r && r[ -1 ].object == v ) {
r.take( r.size() - 1 ) << r[ -1 ].inc()
}
else {
r << new RleUnit( v )
}
}
}
And a method that takes a List of RleUnit objects, and unpacks it back to the original list:
List rleDecode( List<RleUnit> rle ) {
rle.inject( [] ) { r, v ->
r.addAll( [ v.object ] * v.runLength )
r
}
}
We can then encode the original list:
def list = [ 4, 1, 1, 1, 3, 5, 1, 1 ]
rle = rleEncode( list )
And filter this RleUnit list with the Groovy find method:
// remove all elements with a runLength of 3
noThrees = rle.findAll { it.runLength != 3 }
unpackNoThrees = rleDecode( noThrees )
assert unpackNoThrees == [ 4, 3, 5, 1, 1 ]
// remove all elements with a runLength of less than 3
threeOrMore = rle.findAll { it.runLength >= 3 }
unpackThreeOrMore = rleDecode( threeOrMore )
assert unpackThreeOrMore == [ 1, 1, 1 ]

In Groovy, how do I add up the values for a certain property in a map?

I have the following map:
def map = [];
map.add([ item: "Shampoo", count: 5 ])
map.add([ item: "Soap", count: 3 ])
I would like to get the sum of all the count properties in the map. In C# using LINQ, it would be something like:
map.Sum(x => x.count)
How do I do the same in Groovy?
Assuming you have a list like so:
List list = [ [item: "foo", count: 5],
[item: "bar", count: 3] ]
Then there are multiple ways of doing it. The most readable is probably
int a = list.count.sum()
Or you could use the Closure form of sum on the whole list
int b = list.sum { it.count }
Or you could even use a more complex route such as inject
int c = list.count.inject { tot, ele -> tot + ele } // Groovy 2.0
// c = list.count.inject( 0 ) { tot, ele -> tot + ele } // Groovy < 2.0
All of these give the same result.
assert ( a == b ) && ( b == c ) && ( c == 8 )
I would use the first one.
You want to use the collect operator. I checked the following code with groovysh:
list1 = []
total = 0
list1[0] = [item: "foo", count: 5]
list1[1] = [item: "bar", count: 3]
list1.collect{ total += it.count }
println "total = ${total}"
First of all, you're confusing map and list syntax in your example. Anyhow, Groovy injects a .sum(closure) method to all collections.
Example:
[[a:1,b:2], [a:5,b:4]].sum { it.a }
===> 6

how to accept multiple parameters from returning function in groovy

I want to return multiple values from a function written in groovy and receive them , but i am getting an error
class org.codehaus.groovy.ast.expr.ListExpression, with its value '[a,
b]', is a bad expression as the left hand side of an assignment
operator
My code is
int a=10
int b=0
println "a is ${a} , b is ${b}"
[a,b]=f1(a)
println "a is NOW ${a} , b is NOW ${b}"
def f1(int x) {
return [a*10,a*20]
}
You almost have it. Conceptually [ a, b ] creates a list, and ( a, b ) unwraps one, so you want (a,b)=f1(a) instead of [a,b]=f1(a).
int a=10
int b=0
println "a is ${a} , b is ${b}"
(a,b)=f1(a)
println "a is NOW ${a} , b is NOW ${b}"
def f1(int x) {
return [x*10,x*20]
}
Another example returning objects, which don't need to be the same type:
final Date foo
final String bar
(foo, bar) = baz()
println foo
println bar
def baz() {
return [ new Date(0), 'Test' ]
}
Additionally you can combine the declaration and assignment:
final def (Date foo, String bar) = baz()
println foo
println bar
def baz() {
return [ new Date(0), 'Test' ]
}
You can declare and assign the variables in which the return values are stored in one line like this, which is a slightly more compact syntax than that used in Justin's answer:
def (int a, int b) = f1(22)
In your particular case you may not be able to use this because one of the variables passed to f1 is also used to store a return value
When running Groovy in the context of Jenkins pipeline job the above answers do not work (at least on version 2.60.2), but the following does:
node {
obj = ret2()
fw = obj[0]
lw = obj[1]
echo "fw=${fw}"
echo "lw=${lw}"
}
def ret2()
{
return [5, 7]
}
Or alternatively:
node {
obj = ret2()
fw = obj.a
lw = obj.b
echo "fw=${fw}"
echo "lw=${lw}"
}
def ret2()
{
return [a:5, b:7]
}

Resources