From aa90302d1bc0438d080e105620752636894974ff Mon Sep 17 00:00:00 2001 From: Andrey Breslav Date: Wed, 2 Oct 2013 17:11:30 +0400 Subject: [PATCH] Tests for storage manager --- .../jet/storage/StorageManagerTest.java | 456 ++++++++++++++++++ .../jet/storage/LockBasedStorageManager.java | 5 +- 2 files changed, 460 insertions(+), 1 deletion(-) create mode 100644 compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java diff --git a/compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java b/compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java new file mode 100644 index 00000000000..0865f060d2a --- /dev/null +++ b/compiler/tests/org/jetbrains/jet/storage/StorageManagerTest.java @@ -0,0 +1,456 @@ +package org.jetbrains.jet.storage; + +import com.intellij.openapi.util.Computable; +import com.intellij.util.Consumer; +import com.intellij.util.Function; +import junit.framework.TestCase; +import org.jetbrains.jet.utils.WrappedValues; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import static org.jetbrains.jet.storage.StorageManager.ReferenceKind.STRONG; + +public class StorageManagerTest extends TestCase { + + private StorageManager m; + + @Override + public void setUp() throws Exception { + super.setUp(); + m = new LockBasedStorageManager(); + } + + public static void doTestComputesOnce(Computable v, T expected, Counter counter) throws Exception { + assert 0 == counter.getCount(); + + T result1 = v.compute(); + T result2 = v.compute(); + assertEquals(1, counter.getCount()); + assertEquals(expected, result1); + assertEquals(result1, result2); + } + + public static void doTestExceptionPreserved(Computable v, Class expected, Counter counter) + throws Exception { + assert 0 == counter.getCount(); + + Throwable caught1 = null; + try { + v.compute(); + fail(); + } + catch (Throwable e) { + caught1 = e; + } + Throwable caught2 = null; + try { + v.compute(); + fail(); + } + catch (Throwable e) { + caught2 = e; + } + + assertEquals(1, counter.getCount()); + assertTrue("Wrong exception class: " + caught1, expected.isInstance(caught1)); + assertSame(caught1, caught2); + } + + // Functions + + + public void testIsComputed() throws Exception { + NotNullLazyValue value = m.createLazyValue(new CounterValue()); + assertFalse(value.isComputed()); + value.compute(); + assertTrue(value.isComputed()); + } + + public void testIsNullableComputed() throws Exception { + NullableLazyValue value = m.createNullableLazyValue(new CounterValueNull()); + assertFalse(value.isComputed()); + value.compute(); + assertTrue(value.isComputed()); + } + + public void testIsComputedAfterException() throws Exception { + NotNullLazyValue value = m.createLazyValue(new ExceptionCounterValue()); + assertFalse(value.isComputed()); + + try { + value.compute(); + } + catch (Exception ignored) { + } + + assertTrue(value.isComputed()); + } + + public void testIsNullableComputedAfterException() throws Exception { + NullableLazyValue value = m.createNullableLazyValue(new ExceptionCounterValue()); + assertFalse(value.isComputed()); + + try { + value.compute(); + } + catch (Exception ignored) { + } + + assertTrue(value.isComputed()); + } + + public void testFunctionComputesOnce() throws Exception { + CounterFunction counter = new CounterFunction(); + MemoizedFunctionToNotNull f = m.createMemoizedFunction(counter, STRONG); + doTestComputesOnce(apply(f, "ok"), "ok1", counter); + } + + public void testNullableFunctionComputesOnce() throws Exception { + CounterFunction counter = new CounterFunction(); + MemoizedFunctionToNullable f = m.createMemoizedFunctionWithNullableValues(counter, STRONG); + doTestComputesOnce(apply(f, "ok"), "ok1", counter); + } + + public void testNullIsNotConfusedForNotComputedInFunction() throws Exception { + CounterFunctionToNull counter = new CounterFunctionToNull(); + MemoizedFunctionToNullable f = m.createMemoizedFunctionWithNullableValues(counter, STRONG); + doTestComputesOnce(apply(f, ""), null, counter); + } + + public void testFunctionPreservesExceptions() throws Exception { + ExceptionCounterFunction counter = new ExceptionCounterFunction(); + MemoizedFunctionToNotNull f = m.createMemoizedFunction(counter, STRONG); + doTestExceptionPreserved(apply(f, ""), UnsupportedOperationException.class, counter); + } + + public void testNullableFunctionPreservesExceptions() throws Exception { + ExceptionCounterFunction counter = new ExceptionCounterFunction(); + MemoizedFunctionToNullable f = m.createMemoizedFunctionWithNullableValues(counter, STRONG); + doTestExceptionPreserved(apply(f, ""), UnsupportedOperationException.class, counter); + } + + // Values + + public void testNotNullLazyComputedOnce() throws Exception { + CounterValue counter = new CounterValue(); + NotNullLazyValue value = m.createLazyValue(counter); + doTestComputesOnce(value, "ok1", counter); + } + + public void testNullableLazyComputedOnce() throws Exception { + CounterValue counter = new CounterValue(); + NullableLazyValue value = m.createNullableLazyValue(counter); + doTestComputesOnce(value, "ok1", counter); + } + + public void testNullIsNotConfusedForNotComputed() throws Exception { + CounterValueNull counter = new CounterValueNull(); + NullableLazyValue value = m.createNullableLazyValue(counter); + doTestComputesOnce(value, null, counter); + } + + public void testNotNullLazyPreservesException() throws Exception { + ExceptionCounterValue counter = new ExceptionCounterValue(); + NotNullLazyValue value = m.createLazyValue(counter); + doTestExceptionPreserved(value, UnsupportedOperationException.class, counter); + } + + public void testNullableLazyPreservesException() throws Exception { + ExceptionCounterValue counter = new ExceptionCounterValue(); + NullableLazyValue value = m.createNullableLazyValue(counter); + doTestExceptionPreserved(value, UnsupportedOperationException.class, counter); + } + + public void testRecursionIntolerance() throws Exception { + class C { + NotNullLazyValue rec = m.createLazyValue(new Computable() { + @Override + public String compute() { + return rec.compute(); + } + }); + } + + try { + new C().rec.compute(); + fail(); + } + catch (IllegalStateException e) { + // OK + } + } + + public void testNullableRecursionIntolerance() throws Exception { + class C { + NullableLazyValue rec = m.createNullableLazyValue(new Computable() { + @Override + public String compute() { + return rec.compute(); + } + }); + } + + try { + new C().rec.compute(); + fail(); + } + catch (IllegalStateException e) { + // OK + } + } + + public void testRecursionTolerance() throws Exception { + class C { + NotNullLazyValue rec = m.createRecursionTolerantLazyValue(new Computable() { + @Override + public String compute() { + assertEquals("rec", rec.compute()); + return "tolerant!"; + } + }, "rec"); + } + + assertEquals("tolerant!", new C().rec.compute()); + } + + public void testNullableRecursionTolerance() throws Exception { + class C { + NullableLazyValue rec = m.createRecursionTolerantNullableLazyValue(new Computable() { + @Override + public String compute() { + assertEquals(null, rec.compute()); + return "tolerant!"; + } + }, null); + } + + assertEquals("tolerant!", new C().rec.compute()); + } + + public void testRecursionIntoleranceWithPostCompute() throws Exception { + @SuppressWarnings("unchecked") + class C { + NotNullLazyValue rec = m.createLazyValueWithPostCompute( + new Computable() { + @Override + public String compute() { + return rec.compute(); + } + }, + null, + Consumer.EMPTY_CONSUMER + ); + } + + try { + new C().rec.compute(); + fail(); + } + catch (IllegalStateException e) { + // OK + } + } + + public void testRecursionToleranceAndPostCompute() throws Exception { + final CounterImpl counter = new CounterImpl(); + class C { + NotNullLazyValue rec = m.createLazyValueWithPostCompute( + new Computable() { + @Override + public String compute() { + return rec.compute(); + } + }, + new Function() { + @Override + public Object fun(Boolean aBoolean) { + return "tolerant"; + } + }, + new Consumer() { + @Override + public void consume(String s) { + counter.inc(); + assertEquals("tolerant", s); + } + } + ); + } + + C c = new C(); + assertEquals("tolerant", c.rec.compute()); + c.rec.compute(); + assertEquals("postCompute() called more than once", 1, counter.getCount()); + } + + public void testPostComputeNoRecursion() throws Exception { + final CounterImpl counter = new CounterImpl(); + NotNullLazyValue> v = m.createLazyValueWithPostCompute( + new Computable>() { + @Override + public Collection compute() { + List strings = new ArrayList(); + strings.add("first"); + return strings; + } + }, + null, + new Consumer>() { + @Override + public void consume(Collection strings) { + counter.inc(); + strings.add("postComputed"); + } + } + ); + + assertEquals(Arrays.asList("first", "postComputed"), v.compute()); + v.compute(); + assertEquals(1, counter.getCount()); + } + + public void testNullablePostComputeNoRecursion() throws Exception { + final CounterImpl counter = new CounterImpl(); + NullableLazyValue> v = m.createNullableLazyValueWithPostCompute( + new Computable>() { + @Override + public Collection compute() { + ArrayList strings = new ArrayList(); + strings.add("first"); + return strings; + } + }, + new Consumer>() { + @Override + public void consume(Collection strings) { + counter.inc(); + strings.add("postComputed"); + } + } + ); + + assertEquals(Arrays.asList("first", "postComputed"), v.compute()); + v.compute(); + assertEquals(1, counter.getCount()); + } + + public void testRecursionPreventionWithDefaultOnSecondRun() throws Exception { + @SuppressWarnings("unchecked") + class C { + NotNullLazyValue rec = m.createLazyValueWithPostCompute( + new Computable() { + @Override + public String compute() { + return rec.compute(); + } + }, + new Function() { + @Override + public Object fun(Boolean firstTime) { + if (firstTime) { + return WrappedValues.escapeThrowable(new ReenteringLazyValueComputationException()); + } + return "second"; + } + }, + new Consumer() { + @Override + public void consume(String s) { + fail("Recursion-tolerating value should not be post computed"); + } + } + ); + } + + C c = new C(); + try { + c.rec.compute(); + fail(); + } + catch (ReenteringLazyValueComputationException e) { + // OK + } + + assertEquals("second", c.rec.compute()); + } + + // Utilities + + private static Computable apply(final Function f, final K x) { + return new Computable() { + @Override + public V compute() { + return f.fun(x); + } + }; + } + + private interface Counter { + int getCount(); + } + + private static class CounterImpl implements Counter { + + private int count; + + public void inc() { + count++; + } + + @Override + public int getCount() { + return count; + } + } + + private static class CounterValueNull extends CounterImpl implements Computable, Counter { + @Override + public String compute() { + inc(); + return null; + } + } + + private static class CounterValue extends CounterValueNull { + @Override + public String compute() { + inc(); + return "ok" + getCount(); + } + } + + private static class ExceptionCounterValue extends CounterValueNull { + @Override + public String compute() { + inc(); + throw new UnsupportedOperationException(); + } + } + + private static class CounterFunctionToNull extends CounterImpl implements Function, Counter { + @Override + public String fun(String s) { + inc(); + return null; + } + } + + private static class CounterFunction extends CounterFunctionToNull { + @Override + public String fun(String s) { + inc(); + return s + getCount(); + } + } + + private static class ExceptionCounterFunction extends CounterFunctionToNull { + @Override + public String fun(String s) { + inc(); + throw new UnsupportedOperationException(); + } + } +} diff --git a/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java b/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java index 132908ff15f..4f9a51f9209 100644 --- a/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java +++ b/core/util.runtime/src/org/jetbrains/jet/storage/LockBasedStorageManager.java @@ -206,7 +206,10 @@ public class LockBasedStorageManager implements StorageManager { return typedValue; } catch (Throwable throwable) { - value = WrappedValues.escapeThrowable(throwable); + if (value == NotValue.COMPUTING) { + // Store only if it's a genuine result, not something thrown through recursionDetected() + value = WrappedValues.escapeThrowable(throwable); + } throw ExceptionUtils.rethrow(throwable); } }