/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.sesame.graph; import static com.opengamma.sesame.config.ConfigBuilder.argument; import static com.opengamma.sesame.config.ConfigBuilder.arguments; import static com.opengamma.sesame.config.ConfigBuilder.config; import static com.opengamma.sesame.config.ConfigBuilder.function; import static com.opengamma.sesame.config.ConfigBuilder.implementations; import static org.testng.AssertJUnit.assertEquals; import static org.testng.AssertJUnit.assertFalse; import static org.testng.AssertJUnit.assertNotNull; import static org.testng.AssertJUnit.assertTrue; import java.util.Collections; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import javax.inject.Provider; import org.testng.annotations.Test; import org.threeten.bp.LocalDate; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.opengamma.core.config.Config; import com.opengamma.core.link.ConfigLink; import com.opengamma.sesame.config.EngineUtils; import com.opengamma.sesame.config.FunctionModelConfig; import com.opengamma.sesame.engine.ComponentMap; import com.opengamma.sesame.function.FunctionMetadata; import com.opengamma.sesame.function.Output; import com.opengamma.sesame.function.scenarios.ScenarioArgument; import com.opengamma.sesame.function.scenarios.ScenarioFunction; import com.opengamma.sesame.graph.convert.DefaultArgumentConverter; import com.opengamma.util.test.TestGroup; /** * Test. */ @Test(groups = TestGroup.UNIT) public class FunctionModelTest { private static final String INFRASTRUCTURE_COMPONENT = "some pretend infrastructure"; private static final FunctionMetadata METADATA = EngineUtils.createMetadata(TestFn.class, "foo"); @Test public void basicImpl() { FunctionModelConfig config = config(implementations(TestFn.class, BasicImpl.class)); FunctionModel functionModel = FunctionModel.forFunction(METADATA, config); TestFn fn = (TestFn) functionModel.build(new FunctionBuilder(), ComponentMap.EMPTY).getReceiver(); assertTrue(fn instanceof BasicImpl); } @Test public void infrastructure() { ComponentMap infrastructure = ComponentMap.of(ImmutableMap.<Class<?>, Object>of(String.class, INFRASTRUCTURE_COMPONENT)); FunctionModelConfig config = config(implementations(TestFn.class, InfrastructureImpl.class)); FunctionModel functionModel = FunctionModel.forFunction(METADATA, config, infrastructure.getComponentTypes()); TestFn fn = (TestFn) functionModel.build(new FunctionBuilder(), infrastructure).getReceiver(); assertTrue(fn instanceof InfrastructureImpl); //noinspection ConstantConditions assertEquals(INFRASTRUCTURE_COMPONENT, ((InfrastructureImpl) fn)._infrastructureComponent); } @Test public void functionCallingOtherFunction() { FunctionModelConfig config = config(implementations(TestFn.class, CallsOtherFn.class, CollaboratorFn.class, Collaborator.class)); FunctionModel functionModel = FunctionModel.forFunction(METADATA, config); TestFn fn = (TestFn) functionModel.build(new FunctionBuilder(), ComponentMap.EMPTY).getReceiver(); assertTrue(fn instanceof CallsOtherFn); //noinspection ConstantConditions assertTrue(((CallsOtherFn) fn)._collaborator instanceof Collaborator); } @Test public void concreteTypes() { FunctionMetadata metadata = EngineUtils.createMetadata(Concrete1.class, "foo"); FunctionModel functionModel = FunctionModel.forFunction(metadata); Concrete1 fn = (Concrete1) functionModel.build(new FunctionBuilder(), ComponentMap.EMPTY).getReceiver(); assertNotNull(fn._concrete); } public void provider() { FunctionMetadata metadata = EngineUtils.createMetadata(PrivateConstructor.class, "getName"); String providerName = "the provider name"; FunctionModelConfig config = config(implementations(PrivateConstructor.class, PrivateConstructorProvider.class), arguments( function(PrivateConstructorProvider.class, argument("providerName", providerName)))); FunctionModel functionModel = FunctionModel.forFunction(metadata, config); PrivateConstructor fn = (PrivateConstructor) functionModel.build(new FunctionBuilder(), ComponentMap.EMPTY).getReceiver(); assertEquals(providerName, fn.getName()); } @Test public void decorators() { NodeDecorator decorator = new NodeDecorator() { @Override public FunctionModelNode decorateNode(final FunctionModelNode node) { return new DependentNode(Object.class, null, node) { @Override protected Object doCreate(ComponentMap componentMap, List<Object> dependencies, FunctionIdProvider idProvider) { final TestFn fn = (TestFn) dependencies.get(0); return new TestFn() { @Override public Object foo() { return Lists.newArrayList("decorated", fn.foo()); } }; } @Override protected String prettyPrintLine() { return ""; } }; } }; FunctionModelConfig config = config(implementations(TestFn.class, BasicImpl.class)); FunctionModel functionModel = FunctionModel.forFunction(METADATA, config, ComponentMap.EMPTY.getComponentTypes(), decorator); TestFn fn = (TestFn) functionModel.build(new FunctionBuilder(), ComponentMap.EMPTY).getReceiver(); // the basic method just returns "foo" assertEquals(Lists.newArrayList("decorated", "foo"), fn.foo()); } @Test public void buildDirectly1() { Concrete1 fn = FunctionModel.build(Concrete1.class); assertNotNull(fn); } @Test public void buildDirectly2() { FunctionModelConfig config = config(implementations(TestFn.class, BasicImpl.class)); TestFn fn = FunctionModel.build(TestFn.class, config); assertTrue(fn instanceof BasicImpl); } @Test public void buildDirectly3() { ComponentMap infrastructure = ComponentMap.of(ImmutableMap.<Class<?>, Object>of(String.class, INFRASTRUCTURE_COMPONENT)); FunctionModelConfig config = config(implementations(TestFn.class, InfrastructureImpl.class)); TestFn fn = FunctionModel.build(TestFn.class, config, infrastructure); assertTrue(fn instanceof InfrastructureImpl); //noinspection ConstantConditions assertEquals(INFRASTRUCTURE_COMPONENT, ((InfrastructureImpl) fn)._infrastructureComponent); } @Test public void noVisibleConstructors() { FunctionMetadata metadata = EngineUtils.createMetadata(PrivateConstructor.class, "getName"); FunctionModelConfig config = config(arguments(function(PrivateConstructor.class, argument("name", "the name")))); FunctionModel functionModel = FunctionModel.forFunction(metadata, config); assertFalse(functionModel.isValid()); } @Test public void infrastructureNotFound() { FunctionModelConfig config = config(implementations(TestFn.class, InfrastructureImpl.class)); FunctionModel functionModel = FunctionModel.forFunction(METADATA, config, ComponentMap.EMPTY.getComponentTypes()); assertFalse(functionModel.isValid()); } @Test public void multipleInjectableConstructors() { FunctionMetadata metadata = EngineUtils.createMetadata(NoSuitableConstructor.class, "foo"); FunctionModel functionModel = FunctionModel.forFunction(metadata); assertFalse(functionModel.isValid()); } /** test that error nodes are marked in the pretty printed output */ @Test public void prettyPrintErrors() { FunctionModelConfig config = config(implementations(TestFn.class, CallsOtherFn.class, CollaboratorFn.class, BrokenCollaborator.class)); FunctionModel functionModel = FunctionModel.forFunction(METADATA, config); String tree = functionModel.prettyPrint(); String[] lines = tree.split("\n"); assertEquals(4, lines.length); System.out.println(tree); assertTrue(lines[3].startsWith("->")); } /** test that non-error nodes aren't marked in the pretty printed output */ @Test public void prettyPrintNoErrors() { FunctionModelConfig config = config(implementations(TestFn.class, CallsOtherFn.class, CollaboratorFn.class, Collaborator.class)); FunctionModel functionModel = FunctionModel.forFunction(METADATA, config); String tree = functionModel.prettyPrint(); String[] lines = tree.split("\n"); assertEquals(3, lines.length); assertFalse(lines[2].startsWith("->")); System.out.println(tree); } @Test public void decoratorFunction() { FunctionModelConfig config = config(implementations(Fn.class, Impl.class)); FunctionModelConfig decoratedConfig = config.decoratedWith(Decorator1.class); Fn fn = FunctionModel.build(Fn.class, decoratedConfig); assertEquals("2", fn.foo(1)); } @Test public void decoratorFunctions() { FunctionModelConfig config = config(implementations(Fn.class, Impl.class)); FunctionModelConfig decoratedConfig = config.decoratedWith(Decorator2.class).decoratedWith(Decorator1.class); Fn fn = FunctionModel.build(Fn.class, decoratedConfig); assertEquals("5", fn.foo(2)); } @Test public void decoratorFunctionsReversed() { FunctionModelConfig config = config(implementations(Fn.class, Impl.class)); FunctionModelConfig decoratedConfig = config.decoratedWith(Decorator1.class).decoratedWith(Decorator2.class); Fn fn = FunctionModel.build(Fn.class, decoratedConfig); assertEquals("6", fn.foo(2)); } @Test public void linkWithWrongType() { FunctionModelConfig config = config(arguments(function(WithLink.class, argument("arg", ConfigLink.resolved(123))))); FunctionMetadata metadata = EngineUtils.createMetadata(WithLink.class, "foo"); FunctionModel model = FunctionModel.forFunction(metadata, config); assertFalse(model.isValid()); FunctionModelNode node = model.getRoot().getDependencies().get(0); assertTrue(node instanceof IncompatibleArgumentTypeNode); } @Test public void argumentWithWrongType() { FunctionModelConfig config = config(arguments(function(WithLink.class, argument("arg", 123)))); FunctionMetadata metadata = EngineUtils.createMetadata(WithLink.class, "foo"); FunctionModel model = FunctionModel.forFunction(metadata, config); assertFalse(model.isValid()); FunctionModelNode node = model.getRoot().getDependencies().get(0); assertTrue(node instanceof IncompatibleArgumentTypeNode); } @Config public static class WithLink { public WithLink(String arg) { } @Output("Foo") public Object foo() { return null; } } @Test public void missingArgument() { FunctionMetadata metadata = EngineUtils.createMetadata(WithLink.class, "foo"); FunctionModel model = FunctionModel.forFunction(metadata, FunctionModelConfig.EMPTY); assertFalse(model.isValid()); FunctionModelNode node = model.getRoot().getDependencies().get(0); assertTrue(node instanceof MissingArgumentNode); } @Test public void missingConfigArgument() { FunctionMetadata metadata = EngineUtils.createMetadata(UsesConfigArg.class, "foo"); FunctionModel model = FunctionModel.forFunction(metadata, FunctionModelConfig.EMPTY); assertFalse(model.isValid()); FunctionModelNode node = model.getRoot().getDependencies().get(0); assertTrue(node instanceof MissingConfigNode); } public static class UsesConfigArg { public UsesConfigArg(ConfigArg configArg) { } @Output("Foo") public Object foo() { return null; } } @Config public static class ConfigArg { } @Test public void convertArgumentsFromStrings() { FunctionMetadata metadata = EngineUtils.createMetadata(ConvertArgsFromStrings.class, "foo"); FunctionModelConfig config = config( arguments( function( ConvertArgsFromStrings.class, argument("integers", "1, 2, 3"), argument("date", "2011-03-08")))); FunctionModel model = FunctionModel.forFunction(metadata, config, Collections.<Class<?>>emptySet(), NodeDecorator.IDENTITY, new DefaultArgumentConverter()); assertTrue(model.isValid()); ConvertArgsFromStrings fn = (ConvertArgsFromStrings) model.build(new FunctionBuilder(), ComponentMap.EMPTY).getReceiver(); assertEquals(Lists.newArrayList(1, 2, 3), fn._integers); assertEquals(LocalDate.of(2011, 3, 8), fn._date); } @Test public void conversionErrors() { FunctionMetadata metadata = EngineUtils.createMetadata(ConvertArgsFromStrings.class, "foo"); FunctionModelConfig config = config( arguments( function( ConvertArgsFromStrings.class, argument("integers", "1, 2, A"), argument("date", "2011-03-08-foo")))); FunctionModel model = FunctionModel.forFunction(metadata, config, Collections.<Class<?>>emptySet(), NodeDecorator.IDENTITY, new DefaultArgumentConverter()); assertFalse(model.isValid()); List<FunctionModelNode> dependencies = model.getRoot().getDependencies(); assertEquals(2, dependencies.size()); assertTrue(dependencies.get(0) instanceof ArgumentConversionErrorNode); assertTrue(dependencies.get(1) instanceof ArgumentConversionErrorNode); assertEquals("1, 2, A", ((ArgumentConversionErrorNode) dependencies.get(0)).getValue()); assertEquals("2011-03-08-foo", ((ArgumentConversionErrorNode) dependencies.get(1)).getValue()); } public static class ConvertArgsFromStrings { private final List<Integer> _integers; private final LocalDate _date; public ConvertArgsFromStrings(List<Integer> integers, LocalDate date) { _integers = integers; _date = date; } @Output("Foo") public Object foo() { return null; } } @Test(expectedExceptions = IllegalArgumentException.class) public void nodeAndMetadataMismatch() { FunctionMetadata metadata = EngineUtils.createMetadata(Fn.class, "foo"); FunctionModelConfig config = config(implementations(TestFn.class, BasicImpl.class)); FunctionModelNode node = FunctionModelNode.create(TestFn.class, config, Collections.<Class<?>>emptySet(), NodeDecorator.IDENTITY); FunctionModel.forFunction(metadata, node); } @Test public void invalidFunctionImplementation() { Map<Class<?>, Class<?>> impls = ImmutableMap.<Class<?>, Class<?>>of(Fn.class, Object.class); FunctionModelConfig config = new FunctionModelConfig(impls); FunctionMetadata metadata = EngineUtils.createMetadata(Fn.class, "foo"); FunctionModel model = FunctionModel.forFunction(metadata, config); assertFalse(model.isValid()); assertTrue(model.getRoot() instanceof ErrorNode); assertFalse(model.getRoot().getExceptions().isEmpty()); assertTrue(model.getRoot().getExceptions().get(0) instanceof InvalidImplementationException); } public interface Fn { @Output("Foo") String foo(Integer d); } public static class Impl implements Fn { @Override public String foo(Integer d) { return d.toString(); } } public static class Decorator1 implements Fn, ScenarioFunction<Arg1, Decorator1> { private final Fn _delegate; public Decorator1(Fn delegate) { _delegate = delegate; } @Override public String foo(Integer d) { return _delegate.foo(2 * d); } @Nullable @Override public Class<Arg1> getArgumentType() { return Arg1.class; } } public static class Arg1 implements ScenarioArgument<Arg1, Decorator1> { @Override public Class<Decorator1> getFunctionType() { return Decorator1.class; } } public static class Decorator2 implements Fn, ScenarioFunction<Arg2, Decorator2> { private final Fn _delegate; public Decorator2(Fn fn) { _delegate = fn; } @Override public String foo(Integer d) { return _delegate.foo(1 + d); } @Nullable @Override public Class<Arg2> getArgumentType() { return Arg2.class; } } public static class Arg2 implements ScenarioArgument<Arg2, Decorator2> { @Override public Class<Decorator2> getFunctionType() { return Decorator2.class; } } } /* package */ interface TestFn { @Output("Foo") Object foo(); } /* package */ class BasicImpl implements TestFn { public BasicImpl() { } @Override public Object foo() { return "foo"; } } /* package */ class InfrastructureImpl implements TestFn { /* package */ final String _infrastructureComponent; public InfrastructureImpl(String infrastructureComponent) { _infrastructureComponent = infrastructureComponent; } @Override public Object foo() { return null; } } /* package */ class CallsOtherFn implements TestFn { /* package */ final CollaboratorFn _collaborator; public CallsOtherFn(CollaboratorFn collaborator) { _collaborator = collaborator; } @Override public Object foo() { return null; } } /* package */ interface CollaboratorFn { } /* package */ class Collaborator implements CollaboratorFn { public Collaborator() { } } /* package */ class BrokenCollaborator implements CollaboratorFn { public BrokenCollaborator(Object unsatisfiedArg) { } } /* package */ class Concrete1 { /* package */ final Concrete2 _concrete; public Concrete1(Concrete2 concrete) { _concrete = concrete; } @Output("Foo") public Object foo() { return null; } } /* package */ class Concrete2 { public Concrete2() { } } /** * A class with a private constructor that can only be created via a factory method. Need to use a provider to build. */ /* package */ class PrivateConstructor { private final String _name; private PrivateConstructor(String name) { _name = name; } /* package */ static PrivateConstructor build(String name) { return new PrivateConstructor(name); } @Output("Name") public String getName() { return _name; } } /** * A provider that creates a class using a factory method. Has injectable parameters of its own. */ /* package */ class PrivateConstructorProvider implements Provider<PrivateConstructor> { private final String _providerName; public PrivateConstructorProvider(String providerName) { _providerName = providerName; } @Override public PrivateConstructor get() { return PrivateConstructor.build(_providerName); } } /* package */ class NoSuitableConstructor { public NoSuitableConstructor() { } public NoSuitableConstructor(String ignored) { } @Output("Foo") public Object foo() { return null; } }