/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package groovy.lang; import junit.framework.TestCase; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; /** * Provides unit tests for ranges of numbers. */ public abstract class NumberRangeTestCase extends TestCase { /** * Records the values passed to a closure. */ protected static class RecordingClosure extends Closure { /** * Holds the values passed in */ final List callLog; /** * Creates a new <code>RecordingClosure</code> * * @param callLog is filled with the values passed to <code>doCall</code> */ RecordingClosure(final List callLog) { super(null); this.callLog = callLog; } /** * Stores <code>params</code> in the <code>callLog</code>. * * @param params the parameters. * @return null */ public Object doCall(final Object params) { callLog.add(params); return null; } } /** * Creates a {@link Range} to test. * * @param from the first value in the range. * @param to the last value in the range. * @return a {@link Range} to test */ protected abstract Range createRange(final int from, final int to); /** * Creates a value in the range. * * @param value the value to create. * @return a value in the range. */ protected abstract Comparable createValue(final int value); /** * Tests <code>hashCode</code> and <code>equals</code> comparing one {@link IntRange} to another {@link IntRange}. */ public final void testHashCodeAndEquals() { Range a = createRange(1, 11); Range b = createRange(1, 11); Range c = createRange(2, 11); assertEquals("hashcode", a.hashCode(), b.hashCode()); assertTrue("hashcode", a.hashCode() != c.hashCode()); assertEquals("a and b", a, b); assertFalse("a != c", a.equals(c)); } /** * Tests using different classes for 'from' and 'to'. */ public void testDifferentClassesForFromAndTo() { final Integer from = new Integer(1); final Comparable to = createValue(5); final Range range = new ObjectRange(from, to); checkRangeValues(from, to, range); } protected void checkRangeValues(Integer from, Comparable to, Range range) { assertEquals("wrong 'from' value", from, range.getFrom()); assertEquals("wrong 'to' value", to, range.getTo()); } /** * Tests a <code>null</code> 'from' value. */ public void testNullFrom() { try { new ObjectRange(null, createValue(5)); fail("null 'from' accepted"); } catch (IllegalArgumentException e) { assertTrue("expected exception thrown", true); } } /** * Tests a <code>null</code> 'to' value. */ public void testNullTo() { try { new ObjectRange(createValue(23), null); fail("null 'to' accepted"); } catch (IllegalArgumentException e) { assertTrue("expected exception thrown", true); } } /** * Tests stepping through a range by two with a closure. */ public void testStepByTwoWithClosure() { final List callLog = new ArrayList(); final Closure closure = new RecordingClosure(callLog); final Range range = createRange(0, 4); range.step(2, closure); assertEquals("wrong number of calls to closure", 3, callLog.size()); final Iterator iter = callLog.iterator(); for (int i = 0; i <= 4; i += 2) { assertEquals("wrong argument passed to closure", createValue(i), iter.next()); } } /** * Tests iterating over a one-element range. */ public void testOneElementRange() { final Range range = createRange(1, 1); int next = 1; for (Object value : range) { final Number number = (Number) value; assertEquals("wrong number", createValue(next++), number); } assertEquals("wrong number of elements in iteration", 2, next); } /** * Tests stepping through a reversed range by two with a closure. */ public void testReverseStepByTwoWithClosure() { final List callLog = new ArrayList(); final Closure closure = new RecordingClosure(callLog); final Range range = createRange(4, 0); range.step(2, closure); assertEquals("wrong number of calls to closure", 3, callLog.size()); final Iterator iter = callLog.iterator(); for (int i = 4; i >= 0; i -= 2) { assertEquals("wrong argument passed to closure", createValue(i), iter.next()); } } /** * Tests stepping through a range with a closure. */ public void testStepByOneWithClosure() { final List callLog = new ArrayList(); final Closure closure = new RecordingClosure(callLog); final Range range = createRange(1, 5); range.step(1, closure); assertEquals("wrong number of calls to closure", 5, callLog.size()); final Iterator iter = callLog.iterator(); for (int i = 1; i <= 5; i++) { assertEquals("wrong argument passed to closure", createValue(i), iter.next()); } } /** * Tests stepping through a reversed range by one with a closure. */ public void testReverseStepByOneWithClosure() { final List callLog = new ArrayList(); final Closure closure = new RecordingClosure(callLog); final Range range = createRange(5, 1); range.step(1, closure); assertEquals("wrong number of calls to closure", 5, callLog.size()); final Iterator iter = callLog.iterator(); for (int i = 5; i >= 1; i--) { assertEquals("wrong argument passed to closure", createValue(i), iter.next()); } } /** * Tests stepping backwards through a range with a closure. */ public void testNegativeStepByOneWithClosure() { final List callLog = new ArrayList(); final Closure closure = new RecordingClosure(callLog); final Range range = createRange(1, 5); range.step(-1, closure); assertEquals("wrong number of calls to closure", 5, callLog.size()); final Iterator iter = callLog.iterator(); for (int i = 5; i >= 1; i--) { assertEquals("wrong argument passed to closure", createValue(i), iter.next()); } } /** * Tests stepping backwards through a reversed range with a closure. */ public void testNegativeReverseStepByOneWithClosure() { final List callLog = new ArrayList(); final Closure closure = new RecordingClosure(callLog); final Range range = createRange(5, 1); range.step(-1, closure); assertEquals("wrong number of calls to closure", 5, callLog.size()); final Iterator iter = callLog.iterator(); for (int i = 1; i <= 5; i++) { assertEquals("wrong argument passed to closure", createValue(i), iter.next()); } } /** * Tests stepping backwards through a range with a step size greater than the range size. */ public void testStepLargerThanRange() { final List callLog = new ArrayList(); final Closure closure = new RecordingClosure(callLog); final Range range = createRange(1, 5); range.step(6, closure); assertEquals("wrong number of calls to closure", 1, callLog.size()); assertEquals("wrong value", createValue(1), callLog.get(0)); final List stepList = range.step(6); assertEquals("wrong number of values in result", 1, stepList.size()); assertEquals("wrong value", createValue(1), callLog.get(0)); } /** * Tests stepping through a range by one. */ public void testStepByOne() { final Range range = createRange(1, 5); final List result = range.step(1); assertEquals("wrong number of calls", 5, result.size()); final Iterator iter = result.iterator(); for (int i = 1; i <= 5; i++) { assertEquals("incorrect value in result", createValue(i), iter.next()); } } /** * Tests stepping through a range by two. */ public void testStepByTwo() { final Range range = createRange(1, 5); final List result = range.step(2); assertEquals("wrong number of calls", 3, result.size()); final Iterator iter = result.iterator(); for (int i = 1; i <= 5; i += 2) { assertEquals("incorrect value in result", createValue(i), iter.next()); } } /** * Tests getting the size. */ public void testSize() { Range range = createRange(0, 10); assertEquals("Size of " + range, 11, range.size()); range = createRange(0, 1); assertEquals("Size of " + range, 2, range.size()); range = createRange(0, 0); assertEquals("Size of " + range, 1, range.size()); } /** * Tests asking for an index outside of the valid range */ public void testGetOutOfRange() { Range r = createRange(10, 20); try { r.get(-1); fail("Should have thrown IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException e) { assertTrue("expected exception thrown", true); } try { r.get(11); fail("Should have thrown IndexOutOfBoundsException"); } catch (IndexOutOfBoundsException e) { assertTrue("expected exception thrown", true); } } /** * Tests getting a sub list. */ public void testSubList() { Range range = createRange(0, 5); List subList = range.subList(2, 4); assertEquals("size", 2, subList.size()); assertTrue("sublist not a range", subList instanceof Range); Range subListRange = (Range) subList; assertEquals("from", createValue(2), subListRange.getFrom()); assertEquals("to", createValue(3), subListRange.getTo()); subList = range.subList(0, 6); assertEquals("size", 6, subList.size()); assertTrue("sublist not a range", subList instanceof Range); subListRange = (Range) subList; assertEquals("from", createValue(0), subListRange.getFrom()); assertEquals("to", createValue(5), subListRange.getTo()); } /** * Tests creating a sub list with a negative "from" index. */ public void testSubListNegativeFrom() { try { final Range range = createRange(1, 5); range.subList(-1, 3); fail("accepted sub list with negative index"); } catch (IndexOutOfBoundsException e) { assertTrue("expected exception thrown", true); } } /** * Tests creating a sub list with an out of range "to" index. */ public void testSubListOutOfRangeTo() { try { final Range range = createRange(0, 3); range.subList(0, 5); fail("accepted sub list with invalid 'to'"); } catch (IndexOutOfBoundsException e) { assertTrue("expected exception thrown", true); } } /** * Tests creating a sub list with "from" grater than "to." */ public void testSubListFromGreaterThanTo() { try { final Range range = createRange(1, 5); range.subList(3, 2); fail("accepted sub list with 'from' greater than 'to'"); } catch (IllegalArgumentException e) { assertTrue("expected exception thrown", true); } } /** * Tests creating an empty sub list. */ public void testEmptySubList() { final Range range = createRange(1, 5); List subList = range.subList(0, 0); assertEquals("wrong number of elements in sub list", 0, subList.size()); subList = range.subList(2, 2); assertEquals("wrong number of elements in sub list", 0, subList.size()); } /** * Tests iterating over a non-reversed range. */ public void testIterate() { final Range range = createRange(1, 5); int next = 1; final Iterator iter = range.iterator(); while (iter.hasNext()) { final Object value = iter.next(); assertEquals("wrong next value", createValue(next++), value); } assertEquals("wrong number of elements in iteration", 6, next); try { iter.next(); fail("successfully got element from exhausted iterator"); } catch(NoSuchElementException ignore) { } } /** * Tests removing an element from the range using an iterator (not supported). */ public void testRemoveFromIterator() { final Range range = createRange(1, 5); try { final Iterator iter = range.iterator(); iter.remove(); fail("successfully removed an element using an iterator"); } catch (UnsupportedOperationException e) { assertTrue("expected exception thrown", true); } } /** * Tests iterating over a reversed range. */ public void testIterateReversed() { final Range range = createRange(5, 1); int next = 5; for (Object value : range) { assertEquals("wrong number", createValue(next--), value); } assertEquals("wrong number of elements in iteration", 0, next); } /** * Tests creating an <code>IntRange</code> with from > to. */ public void testFromGreaterThanTo() { final int from = 9; final int to = 0; final Range range = createRange(from, to); assertTrue("range not reversed", range.isReverse()); // make sure to/from are swapped assertEquals("from incorrect", createValue(to), range.getFrom()); assertEquals("to incorrect", createValue(from), range.getTo()); assertEquals("wrong size", 10, range.size()); assertEquals("wrong first element", createValue(9), range.get(0)); assertEquals("wrong last element", createValue(0), range.get(9)); } /** * Tests creating an <code>IntRange</code> with from == to. */ public void testFromEqualsTo() { final Range range = createRange(5, 5); assertFalse("range reversed", range.isReverse()); assertEquals("wrong size", 1, range.size()); } /** * Tests creating an <code>IntRange</code> with from < to. */ public void testFromLessThanTo() { final int from = 1; final int to = 4; final Range range = createRange(from, to); assertFalse("range reversed", range.isReverse()); assertEquals("to incorrect", createValue(from), range.getFrom()); assertEquals("from incorrect", createValue(to), range.getTo()); assertEquals("wrong size", 4, range.size()); } /** * Making a range equal a list is not actually possible, since list.equals(range) will not evaluate to * <code>true</code> and <code>equals</code> should be symmetric. */ public void testEqualsList() { final List list = new ArrayList(); list.add(createValue(1)); list.add(createValue(2)); final Range range = createRange(1, 2); // cast to Object to test routing through equals(Object) assertTrue("range does not equal list", range.equals((Object) list)); assertTrue("list does not equal range", list.equals(range)); assertEquals("hash codes are not equal", range.hashCode(), list.hashCode()); // compare lists that are the same size but contain different elements list.set(0, createValue(3)); assertFalse("range equals list", range.equals(list)); assertFalse("list equals range", list.equals(range)); assertFalse("hash codes are equal", range.hashCode() == list.hashCode()); // compare a list longer than the range list.set(0, createValue(1)); list.add(createValue(3)); assertFalse("range equals list", range.equals(list)); assertFalse("list equals range", list.equals(range)); assertFalse("hash are equal", range.hashCode() == list.hashCode()); // compare a list shorter than the range list.remove(2); list.remove(1); assertFalse("range equals list", range.equals(list)); assertFalse("list equals range", list.equals(range)); assertFalse("hash are equal", range.hashCode() == list.hashCode()); } /** * Tests comparing {@link Range} to an object that is not a {@link Range}. */ public void testEqualsNonRange() { final Range range = createRange(1, 5); assertFalse("range equal to string", range.equals("hello")); } /** * Tests comparing a {@link Range} cast to an {@link Object} */ public void testEqualsRangeAsObject() { final Range range1 = createRange(1, 5); final Range range2 = createRange(1, 5); assertTrue("ranges not equal", range1.equals((Object) range2)); } /** * Tests comparing two {@link Range}s to each other. */ public void testEqualsRange() { final Range range1 = createRange(1, 5); Range range2 = createRange(1, 5); assertTrue("ranges not equal", range1.equals((Object) range2)); assertTrue("ranges not equal", range2.equals((Object) range1)); assertEquals("hash codes not equal", range1.hashCode(), range2.hashCode()); range2 = createRange(0, 5); assertFalse("ranges equal", range1.equals((Object) range2)); assertFalse("ranges equal", range2.equals((Object) range1)); assertFalse("hash codes equal", range1.hashCode() == range2.hashCode()); range2 = createRange(1, 6); assertFalse("ranges equal", range1.equals((Object) range2)); assertFalse("ranges equal", range2.equals((Object) range1)); assertFalse("hash codes equal", range1.hashCode() == range2.hashCode()); range2 = createRange(0, 6); assertFalse("ranges equal", range1.equals((Object) range2)); assertFalse("ranges equal", range2.equals((Object) range1)); assertFalse("hash codes equal", range1.hashCode() == range2.hashCode()); range2 = createRange(2, 4); assertFalse("ranges equal", range1.equals((Object) range2)); assertFalse("ranges equal", range2.equals((Object) range1)); assertFalse("hash codes equal", range1.hashCode() == range2.hashCode()); range2 = createRange(5, 1); assertFalse("ranges equal", range1.equals((Object) range2)); assertFalse("ranges equal", range2.equals((Object) range1)); assertFalse("hash codes equal", range1.hashCode() == range2.hashCode()); } /** * Tests <code>toString</code> and <code>inspect</code> */ public void testToStringAndInspect() { Range range = createRange(1, 5); String expected = range.getFrom() + ".." + range.getTo(); assertEquals("wrong string representation", expected, range.toString()); assertEquals("wrong string representation", expected, range.inspect()); range = createRange(5, 1); expected = range.getTo() + ".." + range.getFrom(); assertEquals("wrong string representation", expected, range.toString()); assertEquals("wrong string representation", expected, range.inspect()); } /** * Tests <code>getFrom</code> and <code>getTo</code>. */ public void testGetFromAndTo() { final int from = 1, to = 5; final Range range = createRange(from, to); assertEquals("wrong 'from' value", createValue(from), range.getFrom()); assertEquals("wrong 'to' value", createValue(to), range.getTo()); } /** * Tests comparing a {@link Range} to <code>null</code>. */ public void testEqualsNull() { final Range range = createRange(1, 5); assertFalse("range equal to null", range.equals(null)); assertFalse("range equal to null Object", range.equals((Object) null)); assertFalse("range equal to null Range", range.equals((Range) null)); assertFalse("range equal to null List", range.equals((List) null)); } /** * Tests attempting to add a value to a range. */ public void testAddValue() { try { final Range range = createRange(1, 5); range.add(createValue(20)); fail("expected exception not thrown"); } catch (UnsupportedOperationException e) { assertTrue("expected exception thrown", true); } } /** * Tests attempting to remove a value from a range. */ public void testRemoveValue() { try { final Range range = createRange(1, 5); range.remove(0); fail("expected exception not thrown"); } catch (UnsupportedOperationException e) { assertTrue("expected exception thrown", true); } } private void doTestContains(int from, int to, Range range) { // test integers assertTrue("missing 'from' value", range.contains(createValue(from))); assertTrue("missing 'to' value", range.contains(createValue(to))); assertTrue("missing mid point", range.contains(createValue((from + to) / 2))); assertFalse("contains out of range value", range.contains(createValue(from - 1))); assertFalse("contains out of range value", range.contains(createValue(to + 1))); // test ranges assertTrue("missing same range", range.containsAll(createRange(from, to))); assertTrue("missing same range", range.containsAll(createRange(to, from))); assertTrue("missing strict subset", range.containsAll(createRange(from + 1, to - 1))); assertTrue("missing subset", range.containsAll(createRange(from, to - 1))); assertTrue("missing subset", range.containsAll(createRange(from + 1, to))); assertFalse("contains non-subset", range.containsAll(createRange(from - 1, to))); assertFalse("contains non-subset", range.containsAll(createRange(from, to + 1))); assertFalse("contains non-subset", range.containsAll(createRange(from - 2, from - 1))); // ranges don't contain other ranges assertFalse("range contains sub-range", range.contains(createRange(from + 1, to - 1))); // test list final List list = new ArrayList(); list.add(createValue(from)); list.add(createValue(to)); assertTrue("missing strict subset", range.containsAll(list)); // test non-integer number assertFalse("contains Float", range.contains(new Float((to + from) / 2.0 + 0.3))); } /** * Tests whether the range contains a {@link Comparable} object which is not comparable with a {@link Number}. */ public void testContainsIncompatibleComparable() { final Range range = createRange(1, 5); assertFalse("range contains string", range.contains("hello")); assertFalse("range contains string", range.contains("1")); } /** * Tests whether the range contains a non-comparable object. */ public void testContainsNonComparable() { final Range range = createRange(1, 5); assertFalse("range contains hash map", range.contains(new HashMap())); } /** * Tests whether a {@link Range} contains another {@link Range} or a specific integer. */ public void testContains() { final int from = 1, to = 5; doTestContains(from, to, createRange(from, to)); doTestContains(from, to, createRange(to, from)); } /** * Tests <code>get</code> from a reversed range. */ public void testGetFromReversedRange() { final Range range = createRange(5, 1); for (int i = 0; i < 5; i++) { assertEquals("wrong element at position " + i, createValue(5 - i), range.get(i)); } } /** * Tests getting values from the range. */ public void testGet() { final Range range = createRange(10, 20); for (int i = 0; i <= 10; i++) { assertEquals("Item at index: " + i, createValue(i + 10), range.get(i)); } } }