From 23877c0b7c473d24eae92f17ebed8f2eee9fddaa Mon Sep 17 00:00:00 2001 From: Armin Date: Fri, 28 Jul 2017 16:49:51 +0200 Subject: [PATCH] PERFORMANCE: Remove lut Cache in Accessors Fixes #7839 --- .../src/main/java/org/logstash/Accessors.java | 240 +++++++----------- .../src/main/java/org/logstash/Event.java | 35 +-- .../test/java/org/logstash/AccessorsTest.java | 188 ++++---------- 3 files changed, 157 insertions(+), 306 deletions(-) diff --git a/logstash-core/src/main/java/org/logstash/Accessors.java b/logstash-core/src/main/java/org/logstash/Accessors.java index 9899accc5..d7f545b90 100644 --- a/logstash-core/src/main/java/org/logstash/Accessors.java +++ b/logstash-core/src/main/java/org/logstash/Accessors.java @@ -1,201 +1,157 @@ package org.logstash; -import java.util.HashMap; -import java.util.List; import java.util.Map; -class Accessors { +public final class Accessors { - private final ConvertedMap data; - protected Map lut; - - public Accessors(final ConvertedMap data) { - this.data = data; - this.lut = new HashMap<>(); // reference -> target LUT + private Accessors() { + //Utility Class } - public Object get(String reference) { - FieldReference field = PathCache.cache(reference); - Object target = findTarget(field); - return (target == null) ? null : fetch(target, field.getKey()); - } - - public Object set(String reference, Object value) { + public static Object get(final ConvertedMap data, final String reference) { final FieldReference field = PathCache.cache(reference); - final Object target = findCreateTarget(field); - final String key = field.getKey(); + final Object target = findParent(data, field); + return target == null ? null : fetch(target, field.getKey()); + } + + public static Object set(final ConvertedMap data, final String reference, + final Object value) { + final FieldReference field = PathCache.cache(reference); + return setChild(findCreateTarget(data, field), field.getKey(), value); + } + + public static Object del(final ConvertedMap data, final String reference) { + final FieldReference field = PathCache.cache(reference); + final Object target = findParent(data, field); if (target instanceof ConvertedMap) { - ((ConvertedMap) target).put(key, value); - } else if (target instanceof ConvertedList) { - int i; - try { - i = Integer.parseInt(key); - } catch (NumberFormatException e) { - return null; - } - int size = ((ConvertedList) target).size(); - if (i >= size) { - // grow array by adding trailing null items - // this strategy reflects legacy Ruby impl behaviour and is backed by specs - // TODO: (colin) this is potentially dangerous, and could produce OOM using arbitrary big numbers - // TODO: (colin) should be guard against this? - for (int j = size; j < i; j++) { - ((List) target).add(null); - } - ((List) target).add(value); - } else { - int offset = listIndex(i, ((List) target).size()); - ((ConvertedList) target).set(offset, value); - } + return ((ConvertedMap) target).remove(field.getKey()); } else { - throw newCollectionException(target); + return target == null ? null : delFromList((ConvertedList) target, field.getKey()); } - return value; } - public Object del(String reference) { - FieldReference field = PathCache.cache(reference); - Object target = findTarget(field); - if (target != null) { - if (target instanceof ConvertedMap) { - return ((ConvertedMap) target).remove(field.getKey()); - } else if (target instanceof ConvertedList) { - try { - int i = Integer.parseInt(field.getKey()); - int offset = listIndex(i, ((List) target).size()); - return ((List)target).remove(offset); - } catch (IndexOutOfBoundsException|NumberFormatException e) { - return null; - } - } else { - throw newCollectionException(target); - } - } - return null; - } - - public boolean includes(String reference) { + public static boolean includes(final ConvertedMap data, final String reference) { final FieldReference field = PathCache.cache(reference); - final Object target = findTarget(field); + final Object target = findParent(data, field); final String key = field.getKey(); return target instanceof ConvertedMap && ((ConvertedMap) target).containsKey(key) || target instanceof ConvertedList && foundInList(key, (ConvertedList) target); } - private static boolean foundInList(final String key, final ConvertedList target) { + private static Object delFromList(final ConvertedList list, final String key) { try { - return foundInList(target, Integer.parseInt(key)); - } catch (NumberFormatException e) { - return false; + return list.remove(listIndex(key, list.size())); + } catch (IndexOutOfBoundsException | NumberFormatException e) { + return null; } } - private Object findTarget(FieldReference field) { - final Object target = this.lut.get(field.getReference()); - return target != null ? target : cacheTarget(field); + private static Object setOnList(final String key, final Object value, final ConvertedList list) { + final int index; + try { + index = Integer.parseInt(key); + } catch (NumberFormatException e) { + return null; + } + final int size = list.size(); + if (index >= size) { + appendAtIndex(list, value, index, size); + } else { + list.set(listIndex(index, size), value); + } + return value; } - private Object cacheTarget(final FieldReference field) { - Object target = this.data; + private static void appendAtIndex(final ConvertedList list, final Object value, final int index, + final int size) { + // grow array by adding trailing null items + // this strategy reflects legacy Ruby impl behaviour and is backed by specs + // TODO: (colin) this is potentially dangerous, and could produce OOM using arbitrary big numbers + // TODO: (colin) should be guard against this? + for (int i = size; i < index; i++) { + list.add(null); + } + list.add(value); + } + + private static Object findParent(final ConvertedMap data, final FieldReference field) { + Object target = data; for (final String key : field.getPath()) { target = fetch(target, key); - if (!isCollection(target)) { + if (!(target instanceof ConvertedMap || target instanceof ConvertedList)) { return null; } } - this.lut.put(field.getReference(), target); return target; } - private Object findCreateTarget(FieldReference field) { - Object target; - - // flush the @lut to prevent stale cached fieldref which may point to an old target - // which was overwritten with a new value. for example, if "[a][b]" is cached and we - // set a new value for "[a]" then reading again "[a][b]" would point in a stale target. - // flushing the complete @lut is suboptimal, but a hierarchical lut would be required - // to be able to invalidate fieldrefs from a common root. - // see https://github.com/elastic/logstash/pull/5132 - this.lut.clear(); - - target = this.data; - for (String key : field.getPath()) { - Object result = fetch(target, key); - if (result == null) { - result = new ConvertedMap(1); - if (target instanceof ConvertedMap) { - ((ConvertedMap) target).put(key, result); - } else if (target instanceof ConvertedList) { - try { - int i = Integer.parseInt(key); - // TODO: what about index out of bound? - ((ConvertedList) target).set(i, result); - } catch (NumberFormatException e) { - continue; - } - } else if (target != null) { - throw newCollectionException(target); + private static Object findCreateTarget(final ConvertedMap data, final FieldReference field) { + Object target = data; + boolean create = false; + for (final String key : field.getPath()) { + Object result; + if (create) { + result = createChild((ConvertedMap) target, key); + } else { + result = fetch(target, key); + create = result == null; + if (create) { + result = new ConvertedMap(1); + setChild(target, key, result); } } target = result; } - - this.lut.put(field.getReference(), target); - return target; } - private static boolean foundInList(ConvertedList target, int index) { - try { - int offset = listIndex(index, target.size()); - return target.get(offset) != null; - } catch (IndexOutOfBoundsException e) { - return false; + private static Object setChild(final Object target, final String key, final Object value) { + if (target instanceof Map) { + ((ConvertedMap) target).put(key, value); + return value; + } else { + return setOnList(key, value, (ConvertedList) target); } + } + private static Object createChild(final ConvertedMap target, final String key) { + final Object result = new ConvertedMap(1); + target.put(key, result); + return result; } private static Object fetch(Object target, String key) { - if (target instanceof ConvertedMap) { - return ((ConvertedMap) target).get(key); - } else if (target instanceof ConvertedList) { - try { - int offset = listIndex(Integer.parseInt(key), ((ConvertedList) target).size()); - return ((ConvertedList) target).get(offset); - } catch (IndexOutOfBoundsException|NumberFormatException e) { - return null; - } - } else if (target == null) { + return target instanceof ConvertedMap + ? ((ConvertedMap) target).get(key) : fetchFromList((ConvertedList) target, key); + } + + private static Object fetchFromList(final ConvertedList list, final String key) { + try { + return list.get(listIndex(key, list.size())); + } catch (IndexOutOfBoundsException | NumberFormatException e) { return null; - } else { - throw newCollectionException(target); } } - private static boolean isCollection(Object target) { - return target instanceof ConvertedList || target instanceof ConvertedMap; + private static boolean foundInList(final String key, final ConvertedList target) { + return fetchFromList(target, key) != null; } - private static ClassCastException newCollectionException(Object target) { - return new ClassCastException("expecting ConvertedList or ConvertedMap, found " + target.getClass()); - } - - /* + /** * Returns a positive integer offset for a list of known size. - * - * @param i if positive, and offset from the start of the list. If negative, the offset from the end of the list, where -1 means the last element. * @param size the size of the list. * @return the positive integer offset for the list given by index i. */ public static int listIndex(int i, int size) { - if (i >= size || i < -size) { - throw new IndexOutOfBoundsException("Index " + i + " is out of bounds for a list with size " + size); - } + return i < 0 ? size + i : i; + } - if (i < 0) { // Offset from the end of the array. - return size + i; - } else { - return i; - } + /** + * Returns a positive integer offset for a list of known size. + * @param size the size of the list. + * @return the positive integer offset for the list given by index i. + */ + private static int listIndex(final String key, final int size) { + return listIndex(Integer.parseInt(key), size); } } diff --git a/logstash-core/src/main/java/org/logstash/Event.java b/logstash-core/src/main/java/org/logstash/Event.java index b496cdbf6..d5a6905ce 100644 --- a/logstash-core/src/main/java/org/logstash/Event.java +++ b/logstash-core/src/main/java/org/logstash/Event.java @@ -26,8 +26,6 @@ public final class Event implements Cloneable, Queueable { private ConvertedMap data; private ConvertedMap metadata; private Timestamp timestamp; - private Accessors accessors; - private Accessors metadata_accessors; public static final String METADATA = "@metadata"; public static final String METADATA_BRACKETS = "[" + METADATA + "]"; @@ -49,8 +47,6 @@ public final class Event implements Cloneable, Queueable { this.cancelled = false; this.timestamp = new Timestamp(); this.data.put(TIMESTAMP, this.timestamp); - this.accessors = new Accessors(this.data); - this.metadata_accessors = new Accessors(this.metadata); } /** @@ -79,8 +75,6 @@ public final class Event implements Cloneable, Queueable { } else { this.metadata = new ConvertedMap(10); } - this.metadata_accessors = new Accessors(this.metadata); - this.cancelled = false; Object providedTimestamp = data.get(TIMESTAMP); @@ -89,7 +83,6 @@ public final class Event implements Cloneable, Queueable { this.timestamp = (parsedTimestamp == null) ? Timestamp.now() : parsedTimestamp; this.data.put(TIMESTAMP, this.timestamp); - this.accessors = new Accessors(this.data); // the tag() method has to be called after the Accessors initialization if (parsedTimestamp == null) { @@ -106,10 +99,6 @@ public final class Event implements Cloneable, Queueable { return this.metadata; } - private Accessors getAccessors() { - return this.accessors; - } - public void cancel() { this.cancelled = true; } @@ -144,23 +133,22 @@ public final class Event implements Cloneable, Queueable { if (reference.equals(METADATA)) { return this.metadata; } else if (reference.startsWith(METADATA_BRACKETS)) { - return this.metadata_accessors.get(reference.substring(METADATA_BRACKETS.length())); + return Accessors.get(metadata, reference.substring(METADATA_BRACKETS.length())); } else { - return this.accessors.get(reference); + return Accessors.get(data, reference); } } public void setField(String reference, Object value) { if (reference.equals(TIMESTAMP)) { // TODO(talevy): check type of timestamp - this.accessors.set(reference, value); + Accessors.set(data, reference, value); } else if (reference.equals(METADATA_BRACKETS) || reference.equals(METADATA)) { this.metadata = ConvertedMap.newFromMap((Map) value); - this.metadata_accessors = new Accessors(this.metadata); } else if (reference.startsWith(METADATA_BRACKETS)) { - this.metadata_accessors.set(reference.substring(METADATA_BRACKETS.length()), value); + Accessors.set(metadata, reference.substring(METADATA_BRACKETS.length()), value); } else { - this.accessors.set(reference, Valuefier.convert(value)); + Accessors.set(data, reference, Valuefier.convert(value)); } } @@ -168,9 +156,9 @@ public final class Event implements Cloneable, Queueable { if (reference.equals(METADATA_BRACKETS) || reference.equals(METADATA)) { return true; } else if (reference.startsWith(METADATA_BRACKETS)) { - return this.metadata_accessors.includes(reference.substring(METADATA_BRACKETS.length())); + return Accessors.includes(metadata, reference.substring(METADATA_BRACKETS.length())); } else { - return this.accessors.includes(reference); + return Accessors.includes(data, reference); } } @@ -243,7 +231,6 @@ public final class Event implements Cloneable, Queueable { public Event overwrite(Event e) { this.data = e.getData(); - this.accessors = e.getAccessors(); this.cancelled = e.isCancelled(); try { this.timestamp = e.getTimestamp(); @@ -261,7 +248,7 @@ public final class Event implements Cloneable, Queueable { } public Object remove(String path) { - return this.accessors.del(path); + return Accessors.del(data, path); } public String sprintf(String s) throws IOException { @@ -331,7 +318,7 @@ public final class Event implements Cloneable, Queueable { } public void tag(final String tag) { - final Object tags = accessors.get("tags"); + final Object tags = Accessors.get(data,"tags"); // short circuit the null case where we know we won't need deduplication step below at the end if (tags == null) { initTag(tag); @@ -347,7 +334,7 @@ public final class Event implements Cloneable, Queueable { private void initTag(final String tag) { final ConvertedList list = new ConvertedList(1); list.add(new StringBiValue(tag)); - accessors.set("tags", list); + Accessors.set(data, "tags", list); } /** @@ -373,7 +360,7 @@ public final class Event implements Cloneable, Queueable { // TODO: we should eventually look into using alternate data structures to do more efficient dedup but that will require properly defining the tagging API too if (!tags.contains(tag)) { tags.add(tag); - accessors.set("tags", ConvertedList.newFromList(tags)); + Accessors.set(data,"tags", ConvertedList.newFromList(tags)); } } diff --git a/logstash-core/src/test/java/org/logstash/AccessorsTest.java b/logstash-core/src/test/java/org/logstash/AccessorsTest.java index 4764766ec..ee06aade3 100644 --- a/logstash-core/src/test/java/org/logstash/AccessorsTest.java +++ b/logstash-core/src/test/java/org/logstash/AccessorsTest.java @@ -5,13 +5,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.theories.DataPoint; -import org.junit.experimental.theories.Theories; -import org.junit.experimental.theories.Theory; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; import org.logstash.bivalues.StringBiValue; import static org.junit.Assert.assertEquals; @@ -21,113 +15,68 @@ import static org.junit.Assert.assertTrue; public class AccessorsTest { - private static final class TestableAccessors extends Accessors { - - public TestableAccessors(ConvertedMap data) { - super(data); - } - - public Object lutGet(String reference) { - return this.lut.get(reference); - } - } - @Test public void testBareGet() throws Exception { - final ConvertedMap data = new ConvertedMap(1); + Map data = new HashMap<>(); data.put("foo", "bar"); String reference = "foo"; - - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertEquals("bar", accessors.get(reference)); - assertEquals(data, accessors.lutGet(reference)); + assertEquals(new StringBiValue("bar"), Accessors.get(ConvertedMap.newFromMap(data), reference)); } @Test public void testAbsentBareGet() throws Exception { - final Map java = new HashMap<>(1); - java.put("foo", "bar"); - final ConvertedMap data = ConvertedMap.newFromMap(java); + Map data = new HashMap<>(); + data.put("foo", "bar"); String reference = "baz"; - - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertNull(accessors.get(reference)); - assertEquals(data, accessors.lutGet(reference)); + assertNull(Accessors.get(ConvertedMap.newFromMap(data), reference)); } @Test public void testBareBracketsGet() throws Exception { - final Map java = new HashMap<>(1); - java.put("foo", "bar"); - final ConvertedMap data = ConvertedMap.newFromMap(java); + Map data = new HashMap<>(); + data.put("foo", "bar"); String reference = "[foo]"; - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertEquals(new StringBiValue("bar"), accessors.get(reference)); - assertEquals(data, accessors.lutGet(reference)); - + assertEquals(new StringBiValue("bar"), Accessors.get(ConvertedMap.newFromMap(data), reference)); } @Test public void testDeepMapGet() throws Exception { - final ConvertedMap data = new ConvertedMap(1); - Map inner = new ConvertedMap(1); + Map data = new HashMap<>(); + Map inner = new HashMap<>(); data.put("foo", inner); inner.put("bar", "baz"); - String reference = "[foo][bar]"; - - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertEquals("baz", accessors.get(reference)); - assertEquals(inner, accessors.lutGet(reference)); + assertEquals(new StringBiValue("baz"), Accessors.get(ConvertedMap.newFromMap(data), reference)); } @Test public void testAbsentDeepMapGet() throws Exception { - final ConvertedMap data = new ConvertedMap(1); - Map inner = new ConvertedMap(1); + Map data = new HashMap<>(); + Map inner = new HashMap<>(); data.put("foo", inner); inner.put("bar", "baz"); - String reference = "[foo][foo]"; - - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertNull(accessors.get(reference)); - assertEquals(inner, accessors.lutGet(reference)); + assertNull(Accessors.get(ConvertedMap.newFromMap(data), reference)); } @Test public void testDeepListGet() throws Exception { - final ConvertedMap data = new ConvertedMap(1); - List inner = new ConvertedList(1); + Map data = new HashMap<>(); + List inner = new ArrayList(); data.put("foo", inner); inner.add("bar"); - String reference = "[foo][0]"; - - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertEquals("bar", accessors.get(reference)); - assertEquals(inner, accessors.lutGet(reference)); + assertEquals(new StringBiValue("bar"), Accessors.get(ConvertedMap.newFromMap(data), reference)); } @Test public void testAbsentDeepListGet() throws Exception { - final ConvertedMap data = new ConvertedMap(1); - List inner = new ConvertedList(1); + Map data = new HashMap<>(); + List inner = new ArrayList(); data.put("foo", inner); inner.add("bar"); - String reference = "[foo][1]"; - - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertNull(accessors.get(reference)); - assertEquals(inner, accessors.lutGet(reference)); + assertNull(Accessors.get(ConvertedMap.newFromMap(data), reference)); } /* * Check if accessors are able to recovery from @@ -138,32 +87,25 @@ public class AccessorsTest { @Test public void testInvalidIdList() throws Exception { final ConvertedMap data = new ConvertedMap(1); - List inner = new ConvertedList(1); + List inner = new ConvertedList(2); data.put("map1", inner); inner.add("obj1"); inner.add("obj2"); String reference = "[map1][IdNonNumeric]"; - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertNull(accessors.get(reference)); - assertNull(accessors.set(reference, "obj3")); - assertEquals(inner, accessors.lutGet(reference)); - assertFalse(accessors.includes(reference)); - assertNull(accessors.del(reference)); + assertNull(Accessors.get(data, reference)); + assertNull(Accessors.set(data, reference, "obj3")); + assertFalse(Accessors.includes(data, reference)); + assertNull(Accessors.del(data, reference)); } @Test public void testBarePut() throws Exception { final ConvertedMap data = new ConvertedMap(1); String reference = "foo"; - - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertEquals("bar", accessors.set(reference, "bar")); - assertEquals(data, accessors.lutGet(reference)); - assertEquals("bar", accessors.get(reference)); + assertEquals("bar", Accessors.set(data, reference, "bar")); + assertEquals("bar", Accessors.get(data, reference)); } @Test @@ -171,11 +113,8 @@ public class AccessorsTest { final ConvertedMap data = new ConvertedMap(1); String reference = "[foo]"; - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertEquals("bar", accessors.set(reference, "bar")); - assertEquals(data, accessors.lutGet(reference)); - assertEquals("bar", accessors.get(reference)); + assertEquals("bar", Accessors.set(data, reference, "bar")); + assertEquals("bar", Accessors.get(data, reference)); } @Test @@ -184,11 +123,8 @@ public class AccessorsTest { String reference = "[foo][bar]"; - TestableAccessors accessors = new TestableAccessors(data); - assertNull(accessors.lutGet(reference)); - assertEquals("baz", accessors.set(reference, "baz")); - assertEquals(accessors.lutGet(reference), data.get("foo")); - assertEquals("baz", accessors.get(reference)); + assertEquals("baz", Accessors.set(data, reference, "baz")); + assertEquals("baz", Accessors.get(data, reference)); } @Test @@ -198,44 +134,40 @@ public class AccessorsTest { data.put("foo", inner); inner.add("bar"); data.put("bar", "baz"); - TestableAccessors accessors = new TestableAccessors(data); - assertEquals("bar", accessors.del("[foo][0]")); - assertNull(accessors.del("[foo][0]")); - assertEquals(new ArrayList<>(), accessors.get("[foo]")); - assertEquals("baz", accessors.del("[bar]")); - assertNull(accessors.get("[bar]")); + assertEquals("bar", Accessors.del(data, "[foo][0]")); + assertNull(Accessors.del(data, "[foo][0]")); + assertEquals(new ConvertedList(0), Accessors.get(data,"[foo]")); + assertEquals("baz", Accessors.del(data, "[bar]")); + assertNull(Accessors.get(data, "[bar]")); } @Test public void testNilInclude() throws Exception { - ConvertedMap data = new ConvertedMap(1); + final ConvertedMap data = new ConvertedMap(1); data.put("nilfield", null); - TestableAccessors accessors = new TestableAccessors(data); - assertTrue(accessors.includes("nilfield")); + assertTrue(Accessors.includes(data, "nilfield")); } @Test public void testInvalidPath() throws Exception { - ConvertedMap data = new ConvertedMap(1); - Accessors accessors = new Accessors(data); + final ConvertedMap data = new ConvertedMap(1); - assertEquals(1, accessors.set("[foo]", 1)); - assertNull(accessors.get("[foo][bar]")); + assertEquals(1, Accessors.set(data, "[foo]", 1)); + assertNull(Accessors.get(data, "[foo][bar]")); } @Test public void testStaleTargetCache() throws Exception { - ConvertedMap data = new ConvertedMap(1); + final ConvertedMap data = new ConvertedMap(1); - Accessors accessors = new Accessors(data); - assertNull(accessors.get("[foo][bar]")); - assertEquals("baz", accessors.set("[foo][bar]", "baz")); - assertEquals("baz", accessors.get("[foo][bar]")); + assertNull(Accessors.get(data,"[foo][bar]")); + assertEquals("baz", Accessors.set(data,"[foo][bar]", "baz")); + assertEquals("baz", Accessors.get(data, "[foo][bar]")); - assertEquals("boom", accessors.set("[foo]", "boom")); - assertNull(accessors.get("[foo][bar]")); - assertEquals("boom", accessors.get("[foo]")); + assertEquals("boom", Accessors.set(data, "[foo]", "boom")); + assertNull(Accessors.get(data, "[foo][bar]")); + assertEquals("boom", Accessors.get(data,"[foo]")); } @Test @@ -247,28 +179,4 @@ public class AccessorsTest { assertEquals(1, Accessors.listIndex(-9, 10)); assertEquals(0, Accessors.listIndex(-10, 10)); } - - @RunWith(Theories.class) - public static class TestListIndexFailureCases { - private static final int size = 10; - - @DataPoint - public static final int tooLarge = size; - - @DataPoint - public static final int tooLarge1 = size+1; - - @DataPoint - public static final int tooLargeNegative = -size - 1; - - @Rule - public ExpectedException exception = ExpectedException.none(); - - @Theory - public void testListIndexOutOfBounds(int i) { - exception.expect(IndexOutOfBoundsException.class); - Accessors.listIndex(i, size); - } - } - }