package com.opengamma.sesame.engine;
import static com.opengamma.sesame.config.ConfigBuilder.argument;
import static com.opengamma.sesame.config.ConfigBuilder.arguments;
import static com.opengamma.sesame.config.ConfigBuilder.column;
import static com.opengamma.sesame.config.ConfigBuilder.config;
import static com.opengamma.sesame.config.ConfigBuilder.configureView;
import static com.opengamma.sesame.config.ConfigBuilder.function;
import static com.opengamma.sesame.config.ConfigBuilder.implementations;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertNotSame;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertSame;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.Callable;
import org.testng.annotations.Test;
import org.threeten.bp.ZonedDateTime;
import com.codahale.metrics.MetricRegistry;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.opengamma.core.position.Trade;
import com.opengamma.financial.security.equity.EquitySecurity;
import com.opengamma.service.ServiceContext;
import com.opengamma.service.ThreadLocalServiceContext;
import com.opengamma.sesame.DirectExecutorService;
import com.opengamma.sesame.EngineTestUtils;
import com.opengamma.sesame.cache.CacheKey;
import com.opengamma.sesame.cache.Cacheable;
import com.opengamma.sesame.cache.FunctionCache;
import com.opengamma.sesame.cache.NoOpCacheInvalidator;
import com.opengamma.sesame.config.FunctionModelConfig;
import com.opengamma.sesame.config.ViewConfig;
import com.opengamma.sesame.function.AvailableImplementations;
import com.opengamma.sesame.function.AvailableImplementationsImpl;
import com.opengamma.sesame.function.AvailableOutputs;
import com.opengamma.sesame.function.AvailableOutputsImpl;
import com.opengamma.sesame.function.Output;
import com.opengamma.sesame.marketdata.MarketDataEnvironment;
import com.opengamma.sesame.marketdata.MarketDataEnvironmentBuilder;
import com.opengamma.util.test.TestGroup;
/**
* Tests the behaviour of {@link ViewFactory} WRT cache behaviour.
*/
@Test(groups = TestGroup.UNIT)
public class ViewFactoryCacheTest {
/**
* checks that cached values created by a view are available next time it's run.
*/
private void cacheIsSharedBetweenRuns(Class<?> implClass) {
ViewConfig viewConfig =
configureView(
"view name",
column(
"Foo",
config(
implementations(TestFn.class, implClass),
arguments(function(implClass, argument("s", "s"))))));
ViewFactory viewFactory = createViewFactory(TestFn.class);
View view = viewFactory.createView(viewConfig, String.class);
CalculationArguments calculationArguments = CalculationArguments.builder().build();
MarketDataEnvironment marketDataEnvironment = MarketDataEnvironmentBuilder.empty();
Results results1 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
Results results2 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
assertSame(results1.get(0, 0).getResult().getValue(), results2.get(0, 0).getResult().getValue());
}
/**
* checks that cached values created by a view are available next time it's run.
*/
@Test
public void cacheIsSharedBetweenRuns() {
cacheIsSharedBetweenRuns(AutomaticCaching.class);
}
/**
* checks that values cached by user code in a {@link FunctionCache} are available next time it's run.
*/
@Test
public void functionCacheIsSharedBetweenRuns() {
cacheIsSharedBetweenRuns(ExplicitCaching.class);
}
/**
* checks that no caching is done when the function services don't include caching.
*/
private void noCachingWhenCacheServiceNotIncluded(Class<?> implClass) {
ViewConfig viewConfig =
configureView(
"view name",
column(
"Foo",
config(
implementations(TestFn.class, implClass),
arguments(function(implClass, argument("s", "s"))))));
ViewFactory viewFactory = createViewFactory(TestFn.class);
View view = viewFactory.createView(viewConfig, FunctionService.NONE, String.class);
CalculationArguments calculationArguments = CalculationArguments.builder().build();
MarketDataEnvironment marketDataEnvironment = MarketDataEnvironmentBuilder.empty();
Results results1 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
Results results2 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
assertNotSame(results1.get(0, 0).getResult().getValue(), results2.get(0, 0).getResult().getValue());
}
@Test
public void noCachingWhenCacheServiceNotIncluded() {
noCachingWhenCacheServiceNotIncluded(AutomaticCaching.class);
}
@Test
public void noFunctionCachingWhenCacheServiceNotIncluded() {
noCachingWhenCacheServiceNotIncluded(ExplicitCaching.class);
}
/**
* Checks that whe we are capturing the cycle we do not use cached
* values. (If we did we would not be able to intercept calls to the
* sources as they would not be called.)
*/
private void cacheIsNotSharedBetweenRunsWhenCapturingCycle(Class<?> implClass) {
ThreadLocalServiceContext.init(ServiceContext.of(ImmutableMap.<Class<?>, Object>of()));
ViewConfig viewConfig =
configureView(
"view name",
column(
"Foo",
config(
implementations(TestFn.class, implClass),
arguments(function(implClass, argument("s", "s"))))));
ViewFactory viewFactory = createViewFactory(TestFn.class);
View view = viewFactory.createView(viewConfig, String.class);
CalculationArguments calculationArguments =
CalculationArguments.builder()
.valuationTime(ZonedDateTime.now())
.captureInputs(true).build();
MarketDataEnvironment marketDataEnvironment = MarketDataEnvironmentBuilder.empty();
Results results1 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
Results results2 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
assertNotSame(results1.get(0, 0).getResult().getValue(), results2.get(0, 0).getResult().getValue());
assertNotNull(results2.getViewInputs());
assertNotNull(results1.getViewInputs());
}
/**
* Checks that whe we are capturing the cycle we do not use cached
* values. (If we did we would not be able to intercept calls to the
* sources as they would not be called.)
*/
@Test
public void cacheIsNotSharedBetweenRunsWhenCapturingCycle() {
cacheIsNotSharedBetweenRunsWhenCapturingCycle(AutomaticCaching.class);
}
/**
* Checks that whe we are capturing the cycle we do not use cached
* values. (If we did we would not be able to intercept calls to the
* sources as they would not be called.)
*/
@Test
public void functionCacheIsNotSharedBetweenRunsWhenCapturingCycle() {
cacheIsNotSharedBetweenRunsWhenCapturingCycle(ExplicitCaching.class);
}
/**
* checks that cached values created by a view are available to other views built by the same view factory.
*/
private void cacheIsSharedBetweenViews(Class<?> implClass) {
ViewConfig viewConfig =
configureView(
"view name",
column(
"Foo",
config(
implementations(TestFn.class, implClass),
arguments(function(implClass, argument("s", "s"))))));
ViewFactory viewFactory = createViewFactory(TestFn.class);
View view1 = viewFactory.createView(viewConfig, String.class);
View view2 = viewFactory.createView(viewConfig, String.class);
CalculationArguments calculationArguments = CalculationArguments.builder().build();
MarketDataEnvironment marketDataEnvironment = MarketDataEnvironmentBuilder.empty();
Results results1 = view1.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
Results results2 = view2.run(calculationArguments, marketDataEnvironment, ImmutableList.of("bar"));
assertSame(results1.get(0, 0).getResult().getValue(), results2.get(0, 0).getResult().getValue());
}
/**
* checks that cached values created by a view are available to other views built by the same view factory.
*/
@Test
public void cacheIsSharedBetweenViews() {
cacheIsSharedBetweenViews(AutomaticCaching.class);
}
/**
* checks that cached values created by a view are available to other views built by the same view factory.
*/
@Test
public void functionCacheIsSharedBetweenViews() {
cacheIsSharedBetweenViews(ExplicitCaching.class);
}
/**
* tests clearing the cache causes a value to be recalculated in the next cycle in a single view.
*/
@Test
public void clearCacheSameView() {
ViewConfig viewConfig =
configureView(
"test view",
config(implementations(CacheFn1.class, Impl1.class,
CacheFn2.class, Impl2.class,
RootFn.class, RootImpl.class)),
column("Foo"));
ViewFactory viewFactory = createViewFactory(RootFn.class);
View view = viewFactory.createView(viewConfig, EquitySecurity.class);
CalculationArguments calculationArguments = CalculationArguments.builder().build();
MarketDataEnvironment marketDataEnvironment = MarketDataEnvironmentBuilder.empty();
Trade equityTrade = EngineTestUtils.createEquityTrade();
Results results1 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
Object value1 = results1.get(0, 0).getResult().getValue();
Results results2 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
Object value2 = results2.get(0, 0).getResult().getValue();
assertEquals(value1, value2);
viewFactory.clearCache();
Results results3 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
Object value3 = results3.get(0, 0).getResult().getValue();
assertFalse(value1.equals(value3));
}
/**
* tests clearing the cache causes a value to be recalculated in the next cycle when the value is shared
* between two views.
*/
@Test
public void clearCacheDifferentView() {
ViewConfig viewConfig =
configureView(
"test view",
config(implementations(CacheFn1.class, Impl1.class,
CacheFn2.class, Impl2.class,
RootFn.class, RootImpl.class)),
column("Foo"));
ViewFactory viewFactory = createViewFactory(RootFn.class);
View view1 = viewFactory.createView(viewConfig, EquitySecurity.class);
View view2 = viewFactory.createView(viewConfig, EquitySecurity.class);
CalculationArguments calculationArguments = CalculationArguments.builder().build();
MarketDataEnvironment marketDataEnvironment = MarketDataEnvironmentBuilder.empty();
Trade equityTrade = EngineTestUtils.createEquityTrade();
Results results1 = view1.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
Object value1 = results1.get(0, 0).getResult().getValue();
Results results2 = view2.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
Object value2 = results2.get(0, 0).getResult().getValue();
assertEquals(value1, value2);
viewFactory.clearCache();
Results results3 = view2.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
Object value3 = results3.get(0, 0).getResult().getValue();
assertFalse(value1.equals(value3));
}
/**
* tests that clearing the cache doesn't affect a running calculation cycle
*/
@Test
public void clearCacheDuringCycle() {
ViewFactory viewFactory = createViewFactory(CacheClearingFn.class);
ViewConfig viewConfig =
configureView(
"test view",
config(implementations(CacheFn1.class, Impl1.class,
CacheFn2.class, Impl2.class,
RootFn.class, RootImpl.class),
arguments(function(CacheClearingFn.class, argument("viewFactory", viewFactory)))),
column("Bar"));
View view = viewFactory.createView(viewConfig, EquitySecurity.class);
CalculationArguments calculationArguments = CalculationArguments.builder().build();
MarketDataEnvironment marketDataEnvironment = MarketDataEnvironmentBuilder.empty();
Trade equityTrade = EngineTestUtils.createEquityTrade();
// check that the same result is return from 2 calls to TestFn.foo() even if the cache is cleared between
Results results1 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
List<?> values1 = (List<?>) results1.get(0, 0).getResult().getValue();
assertEquals(values1.get(0), values1.get(1));
// check that the result is different on the second run as a result of the cache being cleared on the first
Results results2 = view.run(calculationArguments, marketDataEnvironment, ImmutableList.of(equityTrade));
List<?> values2 = (List<?>) results2.get(0, 0).getResult().getValue();
assertFalse(values1.get(0).equals(values2.get(0)));
}
private ViewFactory createViewFactory(Class<?> function) {
AvailableOutputs availableOutputs = new AvailableOutputsImpl(String.class, EquitySecurity.class);
AvailableImplementations availableImplementations = new AvailableImplementationsImpl();
availableOutputs.register(function);
availableImplementations.register(AutomaticCaching.class);
return new ViewFactory(new DirectExecutorService(),
ComponentMap.EMPTY,
availableOutputs,
availableImplementations,
FunctionModelConfig.EMPTY,
EnumSet.of(FunctionService.CACHING),
EngineTestUtils.createCacheBuilder(),
new NoOpCacheInvalidator(),
Optional.<MetricRegistry>absent());
}
public interface TestFn {
@Output("Foo")
Object foo(String arg);
}
public static class AutomaticCaching implements TestFn {
private final String _s;
public AutomaticCaching(String s) {
_s = s;
}
@Cacheable
@Override
public Object foo(String arg) {
return _s + new Object();
}
}
public static class ExplicitCaching implements TestFn {
private final String _s;
private final FunctionCache _cache;
public ExplicitCaching(String s, FunctionCache cache) {
_s = s;
_cache = cache;
}
@Override
public Object foo(String arg) {
CacheKey key = CacheKey.of(this, "the cache key");
return _cache.get(key, new Callable<Object>() {
@Override
public Object call() throws Exception {
return _s + new Object();
}
});
}
}
public interface CacheFn1 {
@Cacheable
int bar(EquitySecurity arg);
}
public static class Impl1 implements CacheFn1 {
private final CacheFn2 _cacheFn2;
public Impl1(CacheFn2 cacheFn2) {
_cacheFn2 = cacheFn2;
}
@Override
public int bar(EquitySecurity arg) {
return _cacheFn2.bar(arg);
}
}
public interface CacheFn2 {
@Cacheable
int bar(EquitySecurity arg);
}
public static class Impl2 implements CacheFn2 {
private static int i = 0;
@Override
public int bar(EquitySecurity arg) {
return i++;
}
}
public static class CacheClearingFn {
private final ViewFactory _viewFactory;
private final RootFn _rootFn;
public CacheClearingFn(ViewFactory viewFactory, RootFn rootFn) {
_viewFactory = viewFactory;
_rootFn = rootFn;
}
/**
* Calls {@link CacheFn1#bar} twice, clearing the cache between the calls, and returns a list of the return values.
* {@code foo()} returns a different value on each call, so the values will only be equal if the second one
* is retrieved from the cache. This confirms that clearing the cache has no effect on a running cycle.
*/
@Output("Bar")
public List<Integer> getValues(EquitySecurity arg) {
List<Integer> values = new ArrayList<>();
values.add(_rootFn.foo(arg));
_viewFactory.clearCache();
values.add(_rootFn.foo(arg));
return values;
}
}
public interface RootFn {
@Output("Foo")
int foo(EquitySecurity arg);
}
public static class RootImpl implements RootFn {
private final CacheFn1 _cacheFn1;
public RootImpl(CacheFn1 cacheFn1) {
_cacheFn1 = cacheFn1;
}
@Override
public int foo(EquitySecurity arg) {
return _cacheFn1.bar(arg);
}
}
}