/* * Copyright 2010-2015 JetBrains s.r.o. * * Licensed 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 org.jetbrains.kotlin.storage; import junit.framework.TestCase; import kotlin.Unit; import kotlin.jvm.functions.Function0; import kotlin.jvm.functions.Function1; import org.jetbrains.kotlin.util.ReenteringLazyValueComputationException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; public class StorageManagerTest extends TestCase { private StorageManager m; @Override public void setUp() throws Exception { super.setUp(); m = new LockBasedStorageManager(); } public static <T> void doTestComputesOnce(Function0<T> v, T expected, Counter counter) throws Exception { assert 0 == counter.getCount(); T result1 = v.invoke(); T result2 = v.invoke(); assertEquals(1, counter.getCount()); assertEquals(expected, result1); assertEquals(result1, result2); } public static <T> void doTestExceptionPreserved(Function0<T> v, Class<? extends Throwable> expected, Counter counter) throws Exception { assert 0 == counter.getCount(); Throwable caught1 = null; try { v.invoke(); fail(); } catch (Throwable e) { caught1 = e; } Throwable caught2 = null; try { v.invoke(); 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<String> value = m.createLazyValue(new CounterValue()); assertFalse(value.isComputed()); value.invoke(); assertTrue(value.isComputed()); } public void testIsNullableComputed() throws Exception { NullableLazyValue<String> value = m.createNullableLazyValue(new CounterValueNull()); assertFalse(value.isComputed()); value.invoke(); assertTrue(value.isComputed()); } public void testIsComputedAfterException() throws Exception { NotNullLazyValue<String> value = m.createLazyValue(new ExceptionCounterValue()); assertFalse(value.isComputed()); try { value.invoke(); } catch (Exception ignored) { } assertTrue(value.isComputed()); } public void testIsNullableComputedAfterException() throws Exception { NullableLazyValue<String> value = m.createNullableLazyValue(new ExceptionCounterValue()); assertFalse(value.isComputed()); try { value.invoke(); } catch (Exception ignored) { } assertTrue(value.isComputed()); } public void testFunctionComputesOnce() throws Exception { CounterFunction counter = new CounterFunction(); MemoizedFunctionToNotNull<String, String> f = m.createMemoizedFunction(counter); doTestComputesOnce(apply(f, "ok"), "ok1", counter); } public void testNullableFunctionComputesOnce() throws Exception { CounterFunction counter = new CounterFunction(); MemoizedFunctionToNullable<String, String> f = m.createMemoizedFunctionWithNullableValues(counter); doTestComputesOnce(apply(f, "ok"), "ok1", counter); } public void testNullIsNotConfusedForNotComputedInFunction() throws Exception { CounterFunctionToNull counter = new CounterFunctionToNull(); MemoizedFunctionToNullable<String, String> f = m.createMemoizedFunctionWithNullableValues(counter); doTestComputesOnce(apply(f, ""), null, counter); } public void testFunctionPreservesExceptions() throws Exception { ExceptionCounterFunction counter = new ExceptionCounterFunction(); MemoizedFunctionToNotNull<String, String> f = m.createMemoizedFunction(counter); doTestExceptionPreserved(apply(f, ""), UnsupportedOperationException.class, counter); } public void testNullableFunctionPreservesExceptions() throws Exception { ExceptionCounterFunction counter = new ExceptionCounterFunction(); MemoizedFunctionToNullable<String, String> f = m.createMemoizedFunctionWithNullableValues(counter); doTestExceptionPreserved(apply(f, ""), UnsupportedOperationException.class, counter); } public void testRecursionDetection() throws Exception { class C { MemoizedFunctionToNotNull<String, String> rec = m.createMemoizedFunction( new Function1<String, String>() { @Override public String invoke(String s) { return rec.invoke("!!!"); } } ); } try { new C().rec.invoke(""); fail(); } catch (AssertionError e) { assertTrue(e.getMessage().startsWith("Recursion detected on input: !!!")); } } // Values public void testNotNullLazyComputedOnce() throws Exception { CounterValue counter = new CounterValue(); NotNullLazyValue<String> value = m.createLazyValue(counter); doTestComputesOnce(value, "ok1", counter); } public void testNullableLazyComputedOnce() throws Exception { CounterValue counter = new CounterValue(); NullableLazyValue<String> value = m.createNullableLazyValue(counter); doTestComputesOnce(value, "ok1", counter); } public void testNullIsNotConfusedForNotComputed() throws Exception { CounterValueNull counter = new CounterValueNull(); NullableLazyValue<String> value = m.createNullableLazyValue(counter); doTestComputesOnce(value, null, counter); } public void testNotNullLazyPreservesException() throws Exception { ExceptionCounterValue counter = new ExceptionCounterValue(); NotNullLazyValue<String> value = m.createLazyValue(counter); doTestExceptionPreserved(value, UnsupportedOperationException.class, counter); } public void testNullableLazyPreservesException() throws Exception { ExceptionCounterValue counter = new ExceptionCounterValue(); NullableLazyValue<String> value = m.createNullableLazyValue(counter); doTestExceptionPreserved(value, UnsupportedOperationException.class, counter); } public void testRecursionIntolerance() throws Exception { class C { NotNullLazyValue<String> rec = m.createLazyValue(new Function0<String>() { @Override public String invoke() { return rec.invoke(); } }); } try { new C().rec.invoke(); fail(); } catch (IllegalStateException e) { // OK } } public void testNullableRecursionIntolerance() throws Exception { class C { NullableLazyValue<String> rec = m.createNullableLazyValue(new Function0<String>() { @Override public String invoke() { return rec.invoke(); } }); } try { new C().rec.invoke(); fail(); } catch (IllegalStateException e) { // OK } } public void testRecursionTolerance() throws Exception { class C { NotNullLazyValue<String> rec = m.createRecursionTolerantLazyValue(new Function0<String>() { @Override public String invoke() { assertEquals("rec", rec.invoke()); return "tolerant!"; } }, "rec"); } assertEquals("tolerant!", new C().rec.invoke()); } public void testNullableRecursionTolerance() throws Exception { class C { NullableLazyValue<String> rec = m.createRecursionTolerantNullableLazyValue(new Function0<String>() { @Override public String invoke() { assertEquals(null, rec.invoke()); return "tolerant!"; } }, null); } assertEquals("tolerant!", new C().rec.invoke()); } public void testRecursionIntoleranceWithPostCompute() throws Exception { @SuppressWarnings("unchecked") class C { NotNullLazyValue<String> rec = m.createLazyValueWithPostCompute( new Function0<String>() { @Override public String invoke() { return rec.invoke(); } }, null, s -> Unit.INSTANCE ); } try { new C().rec.invoke(); fail(); } catch (IllegalStateException e) { // OK } } public void testRecursionToleranceAndPostCompute() throws Exception { CounterImpl counter = new CounterImpl(); class C { NotNullLazyValue<String> rec = m.createLazyValueWithPostCompute( new Function0<String>() { @Override public String invoke() { return rec.invoke(); } }, aBoolean -> "tolerant", s -> { counter.inc(); assertEquals("tolerant", s); return Unit.INSTANCE; } ); } C c = new C(); assertEquals("tolerant", c.rec.invoke()); c.rec.invoke(); assertEquals("postCompute() called more than once", 1, counter.getCount()); } public void testPostComputeNoRecursion() throws Exception { CounterImpl counter = new CounterImpl(); NotNullLazyValue<Collection<String>> v = m.createLazyValueWithPostCompute( () -> { List<String> strings = new ArrayList<>(); strings.add("first"); return strings; }, null, strings -> { counter.inc(); strings.add("postComputed"); return Unit.INSTANCE; } ); assertEquals(Arrays.asList("first", "postComputed"), v.invoke()); v.invoke(); assertEquals(1, counter.getCount()); } public void testNullablePostComputeNoRecursion() throws Exception { CounterImpl counter = new CounterImpl(); NullableLazyValue<Collection<String>> v = m.createNullableLazyValueWithPostCompute( () -> { ArrayList<String> strings = new ArrayList<>(); strings.add("first"); return strings; }, strings -> { counter.inc(); strings.add("postComputed"); return Unit.INSTANCE; } ); assertEquals(Arrays.asList("first", "postComputed"), v.invoke()); v.invoke(); assertEquals(1, counter.getCount()); } public void testRecursionPreventionWithDefaultOnSecondRun() throws Exception { @SuppressWarnings("unchecked") class C { NotNullLazyValue<String> rec = m.createLazyValueWithPostCompute( new Function0<String>() { @Override public String invoke() { return rec.invoke(); } }, firstTime -> { if (firstTime) { throw new ReenteringLazyValueComputationException(); } return "second"; }, s -> { fail("Recursion-tolerating value should not be post computed"); return Unit.INSTANCE; } ); } C c = new C(); try { c.rec.invoke(); fail(); } catch (ReenteringLazyValueComputationException e) { // OK } assertEquals("second", c.rec.invoke()); } public void testFallThrough() throws Exception { CounterImpl c = new CounterImpl(); class C { NotNullLazyValue<Integer> rec = LockBasedStorageManager.NO_LOCKS.createLazyValue(new Function0<Integer>() { @Override public Integer invoke() { c.inc(); if (c.getCount() < 2) { return rec.invoke(); } else { return c.getCount(); } } }); } assertEquals(2, new C().rec.invoke().intValue()); assertEquals(2, c.getCount()); } // ExceptionHandlingStrategy public void testExceptionHandlingStrategyForLazyValues() throws Exception { class RethrownException extends RuntimeException {} LockBasedStorageManager m = LockBasedStorageManager.createWithExceptionHandling(throwable -> { throw new RethrownException(); }); try { m.createLazyValue(() -> { throw new RuntimeException(); }).invoke(); fail("Exception should have occurred"); } catch (RethrownException ignored) { } } public void testExceptionHandlingStrategyForMemoizedFunctions() throws Exception { class RethrownException extends RuntimeException {} LockBasedStorageManager m = LockBasedStorageManager.createWithExceptionHandling(throwable -> { throw new RethrownException(); }); try { m.createMemoizedFunction(o -> { throw new RuntimeException(); }).invoke(""); fail("Exception should have occurred"); } catch (RethrownException ignored) { } } // Utilities private static <K, V> Function0<V> apply(Function1<K, V> f, K x) { return () -> f.invoke(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 Function0<String>, Counter { @Override public String invoke() { inc(); return null; } } private static class CounterValue extends CounterValueNull { @Override public String invoke() { inc(); return "ok" + getCount(); } } private static class ExceptionCounterValue extends CounterValueNull { @Override public String invoke() { inc(); throw new UnsupportedOperationException(); } } private static class CounterFunctionToNull extends CounterImpl implements Function1<String, String>, Counter { @Override public String invoke(String s) { inc(); return null; } } private static class CounterFunction extends CounterFunctionToNull { @Override public String invoke(String s) { inc(); return s + getCount(); } } private static class ExceptionCounterFunction extends CounterFunctionToNull { @Override public String invoke(String s) { inc(); throw new UnsupportedOperationException(); } } }