DateTime
values so I could assert in a test that a date time value fell into that range. I was using Joda Time's DateTime
class. The java.util.Date
class is compatible with Groovy's ObjectRange
becuase it's decorated with next
and previous
methods by the GDK. Joda's DateTime
isn't, so I ended up doing this:DateTime start = new DateTime()I could have created a custom Range implementation but I'd have lost the option of using the
DateTime end = start.plusYears(1)
Range range = start.millis..end.millis
x..y
notation and I'm a sucker for brevity. I figured Groovy has an IntRange
class so there's probably a LongRange
as well. As it turns out there isn't and what I ended up with is an ObjectRange
.Fundamentally this works, although I did find something worth bearing in mind. When it came to my assertion I tried to do this:
static void assertInRange(Range expected, actual) {Simple enough, right? Except running the code caused the test to hang. Looking into the implementation of
if (!expected.contains(actual) {
fail("Expected value in range:<${expected}> but was:<${actual}>")
}
}
ObjectRange
it became clear why. Groovy's Range
interface extends java.util.List
and that's where the contains
method is specified. For compatibility with the List
interface ObjectRange
iterates over the values between its from
and to
properties using the next
method I mentioned above. When from
and to
are Long
instances, it turns out this iteration takes a while - on my 2.16GHz iMac the contains
method takes over 4½ minutes on a 1 year range!The
Range
interface also specifies a containsWithinBounds
method and ObjectRange
implements this by simply checking that the argument is >= from
and <=to
. Changing my assertion to use containsWithinBounds
got rid of the long wait.What
ObjectRange
is doing does make perfect sense. Given two arbitrary objects that implement Comparable
and the next
and previous
methods it needs to implement contains
in a way that conforms to the List
interface. When you think about how the GDK implements next
and previous
on java.util.Date
for example you can see that there may be possible values of a given class that fall inside a range but would never be part of the List
generated by repeatedly using next
on the range's from
property. This isn't true of Long
, of course, but ObjectRange
is a general purpose class. The IntRange
class has a much optimised version of contains
and so could a theoretical LongRange
implementation.Anyway, getting back to the example. After changing
contains
to containsWithinBounds
the code ran much faster, unfortunately it also threw java.lang.OutOfMemoryError: Java heap space
. It turns out this is down to how GString
handles the ObjectRange
value I inserted into it. It spots that the object it's got implements List
and uses InvokerHelper.toListString
on it. This will attempt to create a String in the form "[0, 1, 2, 3...]"
. As you can imagine, building such a String is going to take some doing with over 31½ billion elements in the List
, hence the heap space ran out.A final working implementation of my assertion method is:
static void assertInRange(Range expected, actual) {I think going for the custom
if (!expected.containsWithinBounds(actual)) {
fail "Expected:<${expected.toString()}> but was:<${actual}>"
}
}
Range
implementation in the first place might have actually been the right choice.
No comments:
Post a Comment