diff options
author | gjoranv <gv@verizonmedia.com> | 2021-02-17 16:16:19 +0100 |
---|---|---|
committer | gjoranv <gv@verizonmedia.com> | 2021-02-17 17:13:44 +0100 |
commit | da183fe82e5d9eaccf3cbf03a0751cc74851ec31 (patch) | |
tree | 70cb9d97a7d2bdd5785e7512d7f88d5823e1407e /container-core/src/test/java | |
parent | a0ae5022c689578e456eba2b5f89ac077e0b07e1 (diff) |
Add java source files from the processing module.
Diffstat (limited to 'container-core/src/test/java')
20 files changed, 1806 insertions, 0 deletions
diff --git a/container-core/src/test/java/com/yahoo/component/chain/dependencies/ordering/ChainBuilderTest.java b/container-core/src/test/java/com/yahoo/component/chain/dependencies/ordering/ChainBuilderTest.java new file mode 100644 index 00000000000..07ac302f1f3 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/component/chain/dependencies/ordering/ChainBuilderTest.java @@ -0,0 +1,242 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.component.chain.dependencies.ordering; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.chain.Chain; +import com.yahoo.component.chain.ChainedComponent; +import com.yahoo.component.chain.Phase; +import com.yahoo.component.chain.dependencies.After; +import com.yahoo.component.chain.dependencies.Before; +import com.yahoo.component.chain.dependencies.Provides; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author Tony Vaagenes + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ChainBuilderTest { + + private void addAtoG(ChainBuilder chainBuilder) throws ReflectiveOperationException { + List<Class<? extends ChainedComponent>> componentTypes = new ArrayList<>(); + + componentTypes.add(A.class); + componentTypes.add(B.class); + componentTypes.add(C.class); + componentTypes.add(D.class); + componentTypes.add(E.class); + componentTypes.add(F.class); + componentTypes.add(G.class); + + permute(componentTypes); + + for (Class<? extends ChainedComponent> searcherClass : componentTypes) { + chainBuilder.addComponent(searcherClass.getDeclaredConstructor().newInstance()); + } + } + + + private void permute(List<Class<? extends ChainedComponent>> searcherTypes) { + for (int i=0; i<searcherTypes.size(); ++i) { + int j = (int) (Math.random() * searcherTypes.size()); + Class<? extends ChainedComponent> tmp = searcherTypes.get(i); + searcherTypes.set(i,searcherTypes.get(j)); + searcherTypes.set(j, tmp); + } + } + + @Test + public void testRegular() throws Exception { + ChainBuilder chainBuilder = createDependencyHandler(); + + addAtoG(chainBuilder); + + Chain<ChainedComponent> res = chainBuilder.orderNodes(); + + Iterator<ChainedComponent> i = res.components().iterator(); + for (char j=0; j< 'G' - 'A'; ++j) { + assertEquals(String.valueOf((char)('A' + j)), name(i.next())); + } + } + + @Test + public void testCycle() throws Exception { + + ChainBuilder chainBuilder = createDependencyHandler(); + + addAtoG(chainBuilder); + chainBuilder.addComponent(new H()); + + boolean cycle = false; + try { + chainBuilder.orderNodes(); + } catch (CycleDependenciesException e) { + cycle = true; + } + assertTrue(cycle); + } + + + @Test + public void testPhaseAndSearcher() { + ChainBuilder depHandler = newChainBuilder(); + depHandler.addPhase(new Phase("phase1", set("phase2"), Collections.<String>emptySet())); + depHandler.addPhase(new Phase("phase2", set("phase3"), set("phase1"))); + depHandler.addPhase(new Phase("phase3", Collections.<String>emptySet(), set("phase2", "phase1"))); + ChainedComponent first = new First(); + ChainedComponent second = new Second(); + + depHandler.addComponent(first); + depHandler.addComponent(second); + assertEquals(depHandler.orderNodes().components(), Arrays.asList(first, second)); + + } + + @Test + public void testInputOrderPreservedWhenProvidesOverlap() { + ChainBuilder chainBuilder = newChainBuilder(); + + A a1 = new A(); + C c = new C(); + A a2 = new A(); + + chainBuilder.addComponent(a1); + chainBuilder.addComponent(c); + chainBuilder.addComponent(a2); + + assertEquals(Arrays.asList(a1, c, a2), chainBuilder.orderNodes().components()); + } + + private ChainBuilder newChainBuilder() { + return new ChainBuilder(new ComponentId("test")); + } + + private Set<String> set(String... strings) { + return new HashSet<>(Arrays.asList(strings)); + } + + @Before("phase1") + static class First extends NoopComponent { + + } + + @After("phase3") + static class Second extends NoopComponent { + + } + + @Test + public void testAfterAll1() throws Exception { + ChainBuilder chainBuilder = createDependencyHandler(); + ChainedComponent afterAll1 = new AfterAll(); + chainBuilder.addComponent(afterAll1); + addAtoG(chainBuilder); + + List<ChainedComponent> resolution= chainBuilder.orderNodes().components(); + assertEquals(afterAll1,resolution.get(resolution.size()-1)); + } + + @Test + public void testAfterAll2() throws Exception { + ChainBuilder chainBuilder = createDependencyHandler(); + addAtoG(chainBuilder); + ChainedComponent afterAll1 = new AfterAll(); + chainBuilder.addComponent(afterAll1); + + List<ChainedComponent> resolution = chainBuilder.orderNodes().components(); + assertEquals(afterAll1,resolution.get(resolution.size()-1)); + } + + @Test + public void testAfterImplicitProvides() + throws InstantiationException, IllegalAccessException { + ChainBuilder chainBuilder = createDependencyHandler(); + ChainedComponent afterProvidesNothing=new AfterProvidesNothing(); + ChainedComponent providesNothing=new ProvidesNothing(); + chainBuilder.addComponent(afterProvidesNothing); + chainBuilder.addComponent(providesNothing); + List<ChainedComponent> resolution = chainBuilder.orderNodes().components(); + assertEquals(providesNothing,resolution.get(0)); + assertEquals(afterProvidesNothing,resolution.get(1)); + } + + private ChainBuilder createDependencyHandler() { + ChainBuilder chainBuilder = newChainBuilder(); + chainBuilder.addPhase(new Phase("phase1", Collections.<String>emptySet(), Collections.<String>emptySet())); + chainBuilder.addPhase(new Phase("phase2", Collections.<String>emptySet(), Collections.<String>emptySet())); + chainBuilder.addPhase(new Phase("phase3", Collections.<String>emptySet(), Collections.<String>emptySet())); + return chainBuilder; + } + + private String name(ChainedComponent searcher) { + return searcher.getClass().getSimpleName(); + } + + @Provides("A") + static class A extends NoopComponent { + } + + @Provides("B") + @After("A") + @Before({"D", "phase1"}) + static class B extends NoopComponent { + } + + @Provides("C") + @After("phase1") + static class C extends NoopComponent { + } + + @Provides("D") + @After({"C","A"}) + static class D extends NoopComponent { + } + + @Provides("E") + @After({"B","D"}) + @Before("phase2") + static class E extends NoopComponent { + } + + @Provides("F") + @After("phase2") + static class F extends NoopComponent { + } + + @Provides("G") + @After("F") + static class G extends NoopComponent { + } + + @Provides("H") + @Before("A") + @After("F") + static class H extends NoopComponent { + } + + @Provides("AfterAll") + @After("*") + static class AfterAll extends NoopComponent { + } + + static class ProvidesNothing extends NoopComponent { + } + + @After("ProvidesNothing") + static class AfterProvidesNothing extends NoopComponent { + } + + static class NoopComponent extends ChainedComponent { + } + +} diff --git a/container-core/src/test/java/com/yahoo/component/chain/dependencies/ordering/OrderedReadyNodesTest.java b/container-core/src/test/java/com/yahoo/component/chain/dependencies/ordering/OrderedReadyNodesTest.java new file mode 100644 index 00000000000..77729a99012 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/component/chain/dependencies/ordering/OrderedReadyNodesTest.java @@ -0,0 +1,107 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.component.chain.dependencies.ordering; + +import static org.junit.Assert.assertEquals; + +import java.util.Arrays; + +import com.yahoo.component.chain.ChainedComponent; +import com.yahoo.component.chain.dependencies.Dependencies; +import org.junit.Before; +import org.junit.Test; + +import com.yahoo.component.ComponentId; + + + +/** + * Test for OrderedReadyNodes. + * + * @author Tony Vaagenes + */ +@SuppressWarnings("rawtypes") +public class OrderedReadyNodesTest { + + class ComponentA extends ChainedComponent { + public ComponentA(ComponentId id) { + super(id); + } + + @Override + public Dependencies getDependencies() { + return new Dependencies(Arrays.asList(getId().getName()), null, null); + } + } + + class ComponentB extends ComponentA { + public ComponentB(ComponentId id) { + super(id); + } + } + + private OrderedReadyNodes readyNodes; + + @Before + public void setup() { + readyNodes = new OrderedReadyNodes(); + } + + @Test + public void require_NameProviders_before_SearcherNodes() { + NameProvider nameProvider = createDummyNameProvider(100); + ComponentNode componentNode = new ComponentNode<>(createFakeComponentA("a"), 1); + + addNodes(nameProvider, componentNode); + + assertEquals(nameProvider, pop()); + assertEquals(componentNode, pop()); + } + + private NameProvider createDummyNameProvider(int priority) { + return new NameProvider("anonymous", priority) { + @Override + protected void addNode(ComponentNode node) { + throw new UnsupportedOperationException(); + } + + @Override + int classPriority() { + return 0; + } + }; + } + + @Test + public void require_SearcherNodes_ordered_by_insertion_order() { + int priority = 0; + ComponentNode a = new ComponentNode<>(createFakeComponentB("1"), priority++); + ComponentNode b = new ComponentNode<>(createFakeComponentA("2"), priority++); + ComponentNode c = new ComponentNode<>(createFakeComponentA("03"), priority++); + + addNodes(a, b, c); + + assertEquals(a, pop()); + assertEquals(b, pop()); + assertEquals(c, pop()); + } + + ChainedComponent createFakeComponentA(String id) { + return new ComponentA(ComponentId.fromString(id)); + } + + ChainedComponent createFakeComponentB(String id) { + return new ComponentB(ComponentId.fromString(id)); + } + + + private void addNodes(Node... nodes) { + for (Node node : nodes) { + readyNodes.add(node); + } + } + + private Node pop() { + return readyNodes.pop(); + } + +} diff --git a/container-core/src/test/java/com/yahoo/component/chain/model/ChainsModelBuilderTest.java b/container-core/src/test/java/com/yahoo/component/chain/model/ChainsModelBuilderTest.java new file mode 100644 index 00000000000..7cef03bbb06 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/component/chain/model/ChainsModelBuilderTest.java @@ -0,0 +1,71 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.component.chain.model; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.ComponentSpecification; +import com.yahoo.container.core.ChainsConfig; +import org.junit.Test; + +import java.util.Map; +import java.util.Set; + +import static com.yahoo.container.core.ChainsConfig.Components; +import static com.yahoo.container.core.ChainsConfig.Chains; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author gjoranv + */ +public class ChainsModelBuilderTest { + + @Test + public void components_are_added_to_componentModels() throws Exception { + ChainsModel model = chainsModel(); + assertEquals(2, model.allComponents().size()); + assertTrue(model.componentModels().containsKey(new ComponentId("componentA"))); + } + + @Test + public void components_are_added_to_chainSpecification() throws Exception { + ChainsModel model = chainsModel(); + ChainSpecification chainSpec = model.chainSpecifications().get(new ComponentId("chain1")).model(); + assertTrue(getComponentsByName(chainSpec.componentReferences).containsKey("componentA")); + } + + @Test + public void inherited_chains_are_added_to_chainSpecification() throws Exception { + ChainsModel model = chainsModel(); + ChainSpecification inheritsChain1 = model.chainSpecifications().get(new ComponentId("inheritsChain1")).model(); + assertEquals(2, model.allChainsFlattened().size()); + assertTrue(getComponentsByName(inheritsChain1.inheritance.chainSpecifications).containsKey("chain1")); + assertTrue(getComponentsByName(inheritsChain1.inheritance.excludedComponents).containsKey("componentA")); + } + + private ChainsModel chainsModel() { + ChainsConfig.Builder builder = new ChainsConfig.Builder() + .components(new Components.Builder() + .id("componentA")) + .components(new Components.Builder() + .id("componentB")) + .chains(new Chains.Builder() + .id("chain1") + .components("componentA") + .components("componentB")) + .chains(new Chains.Builder() + .id("inheritsChain1") + .inherits("chain1") + .excludes("componentA")); + ChainsConfig config = new ChainsConfig(builder); + + ChainsModel model = ChainsModelBuilder.buildFromConfig(config); + model.validate(); + return model; + } + + private static Map<String, ComponentSpecification> + getComponentsByName(Set<ComponentSpecification> componentSpecifications) { + return ChainSpecification.componentsByName(componentSpecifications); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/ResponseTestCase.java b/container-core/src/test/java/com/yahoo/processing/ResponseTestCase.java new file mode 100644 index 00000000000..02c6049de49 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/ResponseTestCase.java @@ -0,0 +1,139 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing; + +import com.yahoo.processing.response.ArrayDataList; +import com.yahoo.processing.response.DataList; +import com.yahoo.processing.test.ProcessorLibrary; +import com.yahoo.processing.test.Responses; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.*; + +/** + * @author bratseth + */ +@SuppressWarnings("unchecked") +public class ResponseTestCase { + + /** + * Create a nested async tree of data elements, complete it recursively and check completion order. + * Check the recursive toString printing along the way. + * List variable names ends by numbers specifying the index of the list at each level. + */ + @SuppressWarnings("unchecked") + @Test + public void testRecursiveCompletionAndToString() throws InterruptedException, ExecutionException { + // create lists + Request request = new Request(); + DataList list1 = ArrayDataList.create(request); + DataList list11 = ArrayDataList.create(request); + DataList list12 = ArrayDataList.createAsync(request); + DataList list13 = ArrayDataList.createAsync(request); + DataList list14 = ArrayDataList.create(request); + DataList list121 = ArrayDataList.createAsync(request); + DataList list122 = ArrayDataList.create(request); + DataList list123 = ArrayDataList.createAsync(request); + DataList list1231 = ArrayDataList.createAsync(request); + DataList list1232 = ArrayDataList.create(request); + // wire tree + list1.add(list11); + list1.add(list12); + list1.add(list13); + list1.add(list14); + list12.add(list121); + list12.add(list122); + list12.add(list123); + list123.add(list1231); + list123.add(list1232); + // add sync data elements + list1.add(new ProcessorLibrary.StringData(request,"list1")); + list12.add(new ProcessorLibrary.StringData(request,"list12")); + list14.add(new ProcessorLibrary.StringData(request,"list14")); + list122.add(new ProcessorLibrary.StringData(request,"list122")); + list1231.add(new ProcessorLibrary.StringData(request,"list1231")); + + assertEqualsIgnoreObjectNumbers("Uncompleted tree, no incoming",uncompletedTreeUncompletedIncoming,Responses.recursiveToString(list1)); + + // provide all async incoming data + list12.incoming().markComplete(); + list121.incoming().addLast(new ProcessorLibrary.StringData(request,"list121async1")); + list123.incoming().markComplete(); + list1231.incoming().add(new ProcessorLibrary.StringData(request,"list13231async1")); + list1231.incoming().addLast(new ProcessorLibrary.StringData(request,"list1231async2")); + list13.incoming().add(new ProcessorLibrary.StringData(request,"list13async1")); + list13.incoming().addLast(new ProcessorLibrary.StringData(request,"list13async2")); + + assertEqualsIgnoreObjectNumbers("Uncompleted tree, incoming complete", uncompletedTreeCompletedIncoming, Responses.recursiveToString(list1)); + + // complete all + Response.recursiveComplete(list1).get(); + assertEqualsIgnoreObjectNumbers("Completed tree", completedTree, Responses.recursiveToString(list1)); + } + + private void assertEqualsIgnoreObjectNumbers(String explanation,String expected,String actual) { + assertEquals(explanation,expected,removeObjectNumbers(actual)); + } + + /** Removes all object numbers (occurrences of @hexnumber) */ + private String removeObjectNumbers(String s) { + return s.replaceAll("@[0-9a-f]+",""); + } + + private static final String uncompletedTreeUncompletedIncoming= + "com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: incomplete, data []]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: incomplete, data []]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " list122\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: incomplete, data []]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: incomplete, data []]\n" + + " list1231\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " list12\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: incomplete, data []]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " list14\n" + + " list1\n"; + + private static final String uncompletedTreeCompletedIncoming= + "com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: complete, data []]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: complete, data [list121async1]]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " list122\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: complete, data []]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: complete, data [list13231async1, list1231async2]]\n" + + " list1231\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " list12\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, incoming: complete, data [list13async1, list13async2]]\n" + + " com.yahoo.processing.response.ArrayDataList [incomplete, (no incoming)]\n" + + " list14\n" + + " list1\n"; + + private static final String completedTree= + "com.yahoo.processing.response.ArrayDataList [completed]\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " list121async1\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " list122\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " list1231\n" + + " list13231async1\n" + + " list1231async2\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " list12\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " list13async1\n" + + " list13async2\n" + + " com.yahoo.processing.response.ArrayDataList [completed]\n" + + " list14\n" + + " list1\n"; +} diff --git a/container-core/src/test/java/com/yahoo/processing/execution/test/AsyncExecutionTestCase.java b/container-core/src/test/java/com/yahoo/processing/execution/test/AsyncExecutionTestCase.java new file mode 100644 index 00000000000..b821461fdc6 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/execution/test/AsyncExecutionTestCase.java @@ -0,0 +1,46 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.execution.test; + +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.Execution; +import org.junit.Test; + +import static com.yahoo.processing.test.ProcessorLibrary.*; +import static org.junit.Assert.assertEquals; + +/** + * @author bratseth + */ +public class AsyncExecutionTestCase { + + /** Execute a processing chain which forks off into multiple threads */ + @Test + public void testAsyncExecution() { + // Create a chain + Chain<Processor> chain=new Chain<>(new CombineData(),new BlockingSplitter(2),new Get6DataItems(), new DataSource()); + + // Execute it + Request request=new Request(); + request.properties().set("appendage",1); + Response response=Execution.createRoot(chain,0,Execution.Environment.createEmpty()).process(request); + + // Verify the result + assertEquals(6*2-1,response.data().asList().size()); + assertEquals("first.2, third.2",response.data().get(0).toString()); + assertEquals("second.2",response.data().get(1).toString()); + assertEquals("first.3",response.data().get(2).toString()); + assertEquals("second.3",response.data().get(3).toString()); + assertEquals("third.3",response.data().get(4).toString()); + // from the parallel execution + assertEquals("first.2",response.data().get(5).toString()); + assertEquals("second.2",response.data().get(6).toString()); + assertEquals("third.2",response.data().get(7).toString()); + assertEquals("first.3",response.data().get(8).toString()); + assertEquals("second.3",response.data().get(9).toString()); + assertEquals("third.3",response.data().get(10).toString()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/execution/test/ExecutionContextTestCase.java b/container-core/src/test/java/com/yahoo/processing/execution/test/ExecutionContextTestCase.java new file mode 100644 index 00000000000..9c4d4de47dc --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/execution/test/ExecutionContextTestCase.java @@ -0,0 +1,103 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.execution.test; + +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.Processor; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.test.ProcessorLibrary; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +/** + * @author bratseth + */ +public class ExecutionContextTestCase { + + private final Chain<Processor> chain = new Chain<Processor>(new ProcessorLibrary.DataSource()); + + /** Tests combined use of trace messages, context values and access log entries */ + @Test + public void testtrace() { + Execution execution1=Execution.createRoot(chain,2,Execution.Environment.createEmpty()); + execution1.trace().setProperty("a","a1"); + execution1.trace().logValue("a","a1"); + execution1.trace().trace("root 1", 2); + execution1.trace().setProperty("a","a2"); + execution1.trace().setProperty("b","b1"); + execution1.trace().logValue("a","a2"); + execution1.trace().logValue("b","b1"); + + Execution execution2=new Execution(chain,execution1); + execution2.trace().setProperty("b","b2"); + execution2.trace().logValue("b","b2"); + execution2.trace().trace(" child-1 1", 2); + execution2.trace().setProperty("b", "b3"); + execution2.trace().logValue("b","b3"); + + execution1.trace().setProperty("b","b4"); + execution1.trace().logValue("b","b4"); + + Execution execution3=new Execution(chain,execution1); + execution3.trace().setProperty("b","b5"); + execution3.trace().setProperty("c","c1"); + execution3.trace().logValue("b","b5"); + execution3.trace().logValue("c","c1"); + execution3.trace().trace(" child-2 1", 2); + + execution2.trace().setProperty("c","c2"); + execution2.trace().logValue("c","c2"); + + execution1.trace().trace("root 2", 2); + execution3.trace().setProperty("d", "d1"); + execution1.trace().logValue("d","d1"); + + execution2.trace().trace(" child-1 2", 2); + execution2.trace().setProperty("c", "c3"); + execution2.trace().logValue("c","c3"); + + execution1.trace().setProperty("c","c4"); + execution1.trace().logValue("c","c4"); + + Iterator<String> traceIterator=execution1.trace().traceNode().root().descendants(String.class).iterator(); + assertEquals("root 1",traceIterator.next()); + assertEquals(" child-1 1",traceIterator.next()); + assertEquals(" child-1 2",traceIterator.next()); + assertEquals(" child-2 1",traceIterator.next()); + assertEquals("root 2",traceIterator.next()); + assertFalse(traceIterator.hasNext()); + + // Verify context variables + assertEquals("a2", execution1.trace().getProperty("a")); + assertEquals("b5", execution1.trace().getProperty("b")); + assertEquals("c4", execution1.trace().getProperty("c")); + assertEquals("d1", execution1.trace().getProperty("d")); + assertNull(execution1.trace().getProperty("e")); + + // Verify access log + Set<String> logValues=new HashSet<>(); + for (Iterator<Execution.Trace.LogValue> logValueIterator=execution1.trace().logValueIterator(); logValueIterator.hasNext(); ) + logValues.add(logValueIterator.next().toString()); + assertEquals(12,logValues.size()); + assertTrue(logValues.contains("a=a1")); + assertTrue(logValues.contains("a=a2")); + assertTrue(logValues.contains("b=b1")); + assertTrue(logValues.contains("b=b2")); + assertTrue(logValues.contains("b=b3")); + assertTrue(logValues.contains("b=b4")); + assertTrue(logValues.contains("b=b5")); + assertTrue(logValues.contains("c=c1")); + assertTrue(logValues.contains("c=c2")); + assertTrue(logValues.contains("d=d1")); + assertTrue(logValues.contains("c=c3")); + assertTrue(logValues.contains("c=c4")); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/execution/test/FutureDataTestCase.java b/container-core/src/test/java/com/yahoo/processing/execution/test/FutureDataTestCase.java new file mode 100644 index 00000000000..c22e34515bf --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/execution/test/FutureDataTestCase.java @@ -0,0 +1,173 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.execution.test; + +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.response.DataList; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static com.yahoo.processing.test.ProcessorLibrary.*; +import static org.junit.Assert.assertEquals; + +/** + * Tests scenarios where a data producer returns a promise of some future data rather than the data itself. + * As no processor waits for the data it is returned all the way to the caller. + * + * @author bratseth + */ +public class FutureDataTestCase { + + /** Run a chain which ends in a processor which returns a response containing future data. */ + @SuppressWarnings("unchecked") + @Test + public void testFutureDataPassThrough() throws InterruptedException, ExecutionException, TimeoutException { + // Set up + FutureDataSource futureDataSource=new FutureDataSource(); + Chain<Processor> chain=new Chain<>(new DataCounter(),futureDataSource); + + // Execute + Request request=new Request(); + Response response=Execution.createRoot(chain,0,Execution.Environment.createEmpty()).process(request); // Urk ... + + // Verify the result prior to completion of delayed data + assertEquals(1,response.data().asList().size()); + assertEquals("Data count: 0",response.data().get(0).toString()); + + // complete delayed data + assertEquals("Delayed data was requested once", 1, futureDataSource.incomingData.size()); + futureDataSource.incomingData.get(0).add(new StringData(request, "d1")); + futureDataSource.incomingData.get(0).addLast(new StringData(request, "d2")); + assertEquals("New data is not visible because we haven't asked for it", 1, response.data().asList().size()); + response.data().complete().get(1000, TimeUnit.MILLISECONDS); + assertEquals("Now the data is available", 3, response.data().asList().size()); + assertEquals("d1",response.data().get(1).toString().toString()); + assertEquals("d2",response.data().get(2).toString().toString()); + } + + /** Federate to one source which returns data immediately and one who return future data */ + @SuppressWarnings("unchecked") + @Test + public void testFederateSyncAndAsyncData() throws InterruptedException, ExecutionException, TimeoutException { + // Set up + FutureDataSource futureDataSource=new FutureDataSource(); + Chain<Processor> chain=new Chain<>(new DataCounter(),new Federator(new Chain<>(new DataSource()),new Chain<>(futureDataSource))); + + // Execute + Request request=new Request(); + request.properties().set("appendage",1); + Response response=Execution.createRoot(chain,0,Execution.Environment.createEmpty()).process(request); + + // Verify the result prior to completion of delayed data + assertEquals(3,response.data().asList().size()); // The sync data list + the (currently empty) future data list) + the data count + DataList syncData=(DataList)response.data().get(0); + DataList asyncData=(DataList)response.data().get(1); + StringData countData=(StringData)response.data().get(2); + + assertEquals("The sync data is available",3,syncData.asList().size()); + assertEquals( "first.1",syncData.get(0).toString()); + assertEquals("second.1", syncData.get(1).toString()); + assertEquals( "third.1",syncData.get(2).toString()); + assertEquals("No async data yet",0,asyncData.asList().size()); + assertEquals("The data counter has run and accessed the sync data","Data count: 3",countData.toString()); + + // complete async data + futureDataSource.incomingData.get(0).add(new StringData(request, "d1")); + futureDataSource.incomingData.get(0).addLast(new StringData(request, "d2")); + assertEquals("New data is not visible because we haven't asked for it", 0, asyncData.asList().size()); + asyncData.complete().get(1000, TimeUnit.MILLISECONDS); + assertEquals("Now the data is available", 2, asyncData.asList().size()); + assertEquals("d1",asyncData.get(0).toString().toString()); + assertEquals("d2", asyncData.get(1).toString().toString()); + } + + /** Register a chain which will be called when some async data is available */ + @SuppressWarnings("unchecked") + @Test + public void testAsyncDataProcessing() throws InterruptedException, ExecutionException, TimeoutException { + // Set up + FutureDataSource futureDataSource=new FutureDataSource(); + Chain<Processor> asyncChain=new Chain<Processor>(new DataCounter()); + Chain<Processor> chain=new Chain<>(new AsyncDataProcessingInitiator(asyncChain),futureDataSource); + + // Execute + Request request=new Request(); + Response response=Execution.createRoot(chain,0,Execution.Environment.createEmpty()).process(request); + + // Verify the result prior to completion of delayed data + assertEquals("No data yet",0,response.data().asList().size()); + + // complete async data + futureDataSource.incomingData.get(0).add(new StringData(request, "d1")); + assertEquals("New data is not visible because it is not complete", 0, response.data().asList().size()); + futureDataSource.incomingData.get(0).addLast(new StringData(request, "d2")); + assertEquals("Not visible because it has not been synced yet", 0, response.data().asList().size()); + response.data().complete().get(1000, TimeUnit.MILLISECONDS); + assertEquals("Now the data as well as the count is available", 3, response.data().asList().size()); + assertEquals("d1",response.data().get(0).toString().toString()); + assertEquals("d2",response.data().get(1).toString().toString()); + assertEquals("Data count: 2",response.data().get(2).toString()); + } + + /** + * Register a chain which federates over three sources, two of which are future. + * When the first of the futures are done one additional chain is to be run. + * When both are done another chain is to be run. + */ + @SuppressWarnings("unchecked") + @Test + public void testAsyncDataProcessingOfFederatedResult() throws InterruptedException, ExecutionException, TimeoutException { + // Set up + // Source 1 (async with completion chain) + FutureDataSource futureSource1=new FutureDataSource(); + Chain<Processor> asyncChainSource1=new Chain<Processor>(new DataCounter("source1")); + Chain<Processor> chainSource1=new Chain<>(new AsyncDataProcessingInitiator(asyncChainSource1),futureSource1); + // Source 2 (async source) + FutureDataSource futureSource2=new FutureDataSource(); + Chain<Processor> chainSource2=new Chain<Processor>(futureSource2); + // Source 3 (sync source) + Chain<Processor> chainSource3=new Chain<Processor>(new DataSource()); + // Main chain federating to the above - not waiting for source 1 and 2 but invoking asyncMain when both are complete + Chain<Processor> asyncMain=new Chain<Processor>(new DataCounter("main")); + Chain<Processor> main=new Chain<>(new AsyncDataProcessingInitiator(asyncMain),new Federator(chainSource1,chainSource2,chainSource3)); + + // Execute + Request request=new Request(); + Response response=Execution.createRoot(main,0,Execution.Environment.createEmpty()).process(request); + + // Verify the result prior to completion of delayed data + assertEquals("We have the sync data plus placeholders for the async lists",3,response.data().asList().size()); + DataList source1Data=((DataList)response.data().get(0)); + DataList source2Data=((DataList)response.data().get(1)); + DataList source3Data=((DataList)response.data().get(2)); + + assertEquals("No data yet",0,source1Data.asList().size()); + assertEquals("No data yet",0,source2Data.asList().size()); + assertEquals(3,source3Data.asList().size()); + + // complete async data in source1 + futureSource1.incomingData.get(0).addLast(new StringData(request,"source1Data")); + assertEquals("Not visible yet", 0, source1Data.asList().size()); + source1Data.complete().get(1000, TimeUnit.MILLISECONDS); + assertEquals(2, source1Data.asList().size()); + assertEquals("source1Data",source1Data.get(0).toString()); + assertEquals("Completion listener chain on this has run", "[source1] Data count: 1", source1Data.get(1).toString()); + + // source2 & main completion + assertEquals("Main completion listener has not run", 3, response.data().asList().size()); + futureSource2.incomingData.get(0).addLast(new StringData(request, "source2Data")); + assertEquals("Main completion listener has not run", 3, response.data().asList().size()); + + Response.recursiveComplete(response.data()).get(); + assertEquals("Main completion listener has run", 4, response.data().asList().size()); + assertEquals("The main data counter saw all sync data, but not source2 data as it executes after this", + "[main] Data count: " + (2 + 0 + 3), response.data().get(3).toString()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/execution/test/StreamingTestCase.java b/container-core/src/test/java/com/yahoo/processing/execution/test/StreamingTestCase.java new file mode 100644 index 00000000000..6ab37f54d7b --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/execution/test/StreamingTestCase.java @@ -0,0 +1,107 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.execution.test; + +import com.google.common.util.concurrent.MoreExecutors; +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.response.Data; +import com.yahoo.processing.response.IncomingData; +import com.yahoo.processing.test.ProcessorLibrary; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.assertEquals; + +/** + * Tests listening on every available new piece of data in a response + * + * @author bratseth + */ +public class StreamingTestCase { + + /** Tests adding a chain which is called every time new data is added to a data list */ + @SuppressWarnings("unchecked") + @Test + public void testStreamingData() throws InterruptedException, ExecutionException, TimeoutException { + // Set up + StreamProcessor streamProcessor = new StreamProcessor(); + Chain<Processor> streamProcessing = new Chain<Processor>(streamProcessor); + ProcessorLibrary.FutureDataSource futureDataSource=new ProcessorLibrary.FutureDataSource(); + Chain<Processor> main=new Chain<>(new ProcessorLibrary.DataCounter(), + new ProcessorLibrary.StreamProcessingInitiator(streamProcessing), + futureDataSource); + + // Execute + Request request=new Request(); + Response response= Execution.createRoot(main, 0, Execution.Environment.createEmpty()).process(request); + IncomingData incomingData = futureDataSource.incomingData.get(0); + + // State prior to receiving any additional data + assertEquals(1,response.data().asList().size()); + assertEquals("Data count: 0",response.data().get(0).toString()); + assertEquals("Add data listener invoked also for DataCounter", 1, streamProcessor.invocationCount); + assertEquals("Initial data count", 1, response.data().asList().size()); + + // add first data - we have no listener so the data is held in the incoming buffer + incomingData.add(new ProcessorLibrary.StringData(request, "d1")); + assertEquals("Data add listener not invoked as we are not listening on new data yet",1, streamProcessor.invocationCount); + assertEquals("New data is not consumed", 1, response.data().asList().size()); + + // start listening on incoming data - this is what a renderer will do + incomingData.addNewDataListener(new MockNewDataListener(incomingData), MoreExecutors.directExecutor()); + assertEquals("We got a data add event for the data which was already added", 2, streamProcessor.invocationCount); + assertEquals("New data is consumed", 2, response.data().asList().size()); + + incomingData.add(new ProcessorLibrary.StringData(request, "d2")); + assertEquals("We are now getting data add events each time", 3, streamProcessor.invocationCount); + assertEquals("New data is consumed", 3, response.data().asList().size()); + + incomingData.addLast(new ProcessorLibrary.StringData(request, "d3")); + assertEquals("We are getting data add events also the last time", 4, streamProcessor.invocationCount); + assertEquals("New data is consumed", 4, response.data().asList().size()); + + response.data().complete().get(1000, TimeUnit.MILLISECONDS); // no-op here + assertEquals("d1",response.data().get(1).toString().toString()); + assertEquals("d2",response.data().get(2).toString().toString()); + assertEquals("d3",response.data().get(3).toString().toString()); + } + + private static class MockNewDataListener implements Runnable { + + private final IncomingData<Data> incomingData; + + public MockNewDataListener(IncomingData<Data> incomingData) { + this.incomingData = incomingData; + } + + @Override + public void run() { + // consume new data + for (Data newData : incomingData.drain()) { + incomingData.getOwner().add(newData); + } + // actual rendering would go here (at this point data add listeners will have executed) + } + + } + + private static class StreamProcessor extends Processor { + + int invocationCount; + + @Override + public Response process(Request request, Execution execution) { + invocationCount++; + return execution.process(request); + } + + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/request/CompoundNameTestCase.java b/container-core/src/test/java/com/yahoo/processing/request/CompoundNameTestCase.java new file mode 100644 index 00000000000..382abe8d2ca --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/request/CompoundNameTestCase.java @@ -0,0 +1,158 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.request; + +import static org.junit.Assert.*; + +import java.util.Iterator; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.base.Splitter; +import com.yahoo.text.Lowercase; + +/** + * Module local test of the basic property name building block. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class CompoundNameTestCase { + + private static final String NAME = "com.yahoo.processing.request.CompoundNameTestCase"; + private CompoundName cn; + + @Before + public void setUp() throws Exception { + cn = new CompoundName(NAME); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public final void testLast() { + assertEquals(NAME.substring(NAME.lastIndexOf('.') + 1), cn.last()); + } + + @Test + public final void testFirst() { + assertEquals(NAME.substring(0, NAME.indexOf('.')), cn.first()); + } + + @Test + public final void testRest() { + assertEquals(NAME.substring(NAME.indexOf('.') + 1), cn.rest().toString()); + } + + @Test + public final void testRestN() { + assertEquals("a.b.c.d.e", new CompoundName("a.b.c.d.e").rest(0).toString()); + assertEquals("b.c.d.e", new CompoundName("a.b.c.d.e").rest(1).toString()); + assertEquals("c.d.e", new CompoundName("a.b.c.d.e").rest(2).toString()); + assertEquals("d.e", new CompoundName("a.b.c.d.e").rest(3).toString()); + assertEquals("e", new CompoundName("a.b.c.d.e").rest(4).toString()); + assertEquals("", new CompoundName("a.b.c.d.e").rest(5).toString()); + } + + @Test + public final void testPrefix() { + assertTrue(new CompoundName("a.b.c").hasPrefix(new CompoundName(""))); + assertTrue(new CompoundName("a.b.c").hasPrefix(new CompoundName("a"))); + assertTrue(new CompoundName("a.b.c").hasPrefix(new CompoundName("a.b"))); + assertTrue(new CompoundName("a.b.c").hasPrefix(new CompoundName("a.b.c"))); + + assertFalse(new CompoundName("a.b.c").hasPrefix(new CompoundName("a.b.c.d"))); + assertFalse(new CompoundName("a.b.c").hasPrefix(new CompoundName("a.b.d"))); + } + + @Test + public final void testSize() { + Splitter s = Splitter.on('.'); + Iterable<String> i = s.split(NAME); + int n = 0; + for (@SuppressWarnings("unused") String x : i) { + ++n; + } + assertEquals(n, cn.size()); + } + + @Test + public final void testGet() { + String s = cn.get(0); + assertEquals(NAME.substring(0, NAME.indexOf('.')), s); + } + + @Test + public final void testIsCompound() { + assertTrue(cn.isCompound()); + } + + @Test + public final void testIsEmpty() { + assertFalse(cn.isEmpty()); + } + + @Test + public final void testAsList() { + List<String> l = cn.asList(); + Splitter peoplesFront = Splitter.on('.'); + Iterable<String> answer = peoplesFront.split(NAME); + Iterator<String> expected = answer.iterator(); + for (int i = 0; i < l.size(); ++i) { + assertEquals(expected.next(), l.get(i)); + } + assertFalse(expected.hasNext()); + } + + @Test + public final void testEqualsObject() { + assertFalse(cn.equals(NAME)); + assertFalse(cn.equals(null)); + assertTrue(cn.equals(cn)); + assertTrue(cn.equals(new CompoundName(NAME))); + } + + @Test + public final void testEmptyNonEmpty() { + assertTrue(new CompoundName("").isEmpty()); + assertEquals(0, new CompoundName("").size()); + assertFalse(new CompoundName("a").isEmpty()); + assertEquals(1, new CompoundName("a").size()); + CompoundName empty = new CompoundName("a.b.c"); + assertTrue(empty == empty.rest(0)); + assertFalse(empty == empty.rest(1)); + } + + @Test + public final void testGetLowerCasedName() { + assertEquals(Lowercase.toLowerCase(NAME), cn.getLowerCasedName()); + } + + @Test + public void testAppend() { + assertEquals(new CompoundName("a.b.c.d"), new CompoundName("").append(new CompoundName("a.b.c.d"))); + assertEquals(new CompoundName("a.b.c.d"), new CompoundName("a").append(new CompoundName("b.c.d"))); + assertEquals(new CompoundName("a.b.c.d"), new CompoundName("a.b").append(new CompoundName("c.d"))); + assertEquals(new CompoundName("a.b.c.d"), new CompoundName("a.b.c").append(new CompoundName("d"))); + assertEquals(new CompoundName("a.b.c.d"), new CompoundName("a.b.c.d").append(new CompoundName(""))); + } + + @Test + public void empty_CompoundName_is_prefix_of_any_CompoundName() { + CompoundName empty = new CompoundName(""); + + assertTrue(empty.hasPrefix(empty)); + assertTrue(new CompoundName("a").hasPrefix(empty)); + } + + @Test + public void whole_components_must_match_to_be_prefix() { + CompoundName stringPrefix = new CompoundName("a"); + CompoundName name = new CompoundName("aa"); + + assertFalse(name.hasPrefix(stringPrefix)); + } +} diff --git a/container-core/src/test/java/com/yahoo/processing/request/test/CompoundNameBenchmark.java b/container-core/src/test/java/com/yahoo/processing/request/test/CompoundNameBenchmark.java new file mode 100644 index 00000000000..002b6bac4a8 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/request/test/CompoundNameBenchmark.java @@ -0,0 +1,52 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.request.test; + +import com.yahoo.processing.request.CompoundName; + +/** + * @author baldersheim + */ +public class CompoundNameBenchmark { + public void run() { + long result=0; + String strings[] = createStrings(1000); + // Warm-up + out("Warming up..."); + for (int i=0; i<10*1000; i++) + result+=createCompundName(strings); + + long startTime=System.currentTimeMillis(); + out("Running..."); + for (int i=0; i<100*1000; i++) + result+=createCompundName(strings); + out("Ignore this: " + result); // Make sure we are not fooled by optimization by creating an observable result + long endTime=System.currentTimeMillis(); + out("Compoundification 1000 strings 100.000 times took " + (endTime-startTime) + " ms"); + } + + private final String [] createStrings(int num) { + String strings [] = new String [num]; + for(int i=0; i < strings.length; i++) { + strings[i] = "this.is.a.short.compound.name." + i; + } + return strings; + } + + private final int createCompundName(String [] strings) { + int retval = 0; + for (int i=0; i < strings.length; i++) { + CompoundName n = new CompoundName(strings[i]); + retval += n.size(); + } + return retval; + } + + private void out(String string) { + System.out.println(string); + } + + public static void main(String[] args) { + new CompoundNameBenchmark().run(); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/request/test/CompoundNameTestCase.java b/container-core/src/test/java/com/yahoo/processing/request/test/CompoundNameTestCase.java new file mode 100644 index 00000000000..f5033565786 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/request/test/CompoundNameTestCase.java @@ -0,0 +1,66 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.request.test; + +import com.yahoo.processing.request.CompoundName; +import static org.junit.Assert.assertEquals; +import org.junit.Test; + +/** + * @author bratseth + */ +public class CompoundNameTestCase { + + @Test + public void testFirstRest() { + assertEquals(CompoundName.empty, CompoundName.empty.rest()); + + CompoundName n=new CompoundName("on.two.three"); + assertEquals("on", n.first()); + assertEquals("two.three", n.rest().toString()); + n=n.rest(); + assertEquals("two", n.first()); + assertEquals("three", n.rest().toString()); + n=n.rest(); + assertEquals("three", n.first()); + assertEquals("", n.rest().toString()); + n=n.rest(); + assertEquals("", n.first()); + assertEquals("", n.rest().toString()); + n=n.rest(); + assertEquals("", n.first()); + assertEquals("", n.rest().toString()); + } + + @Test + public void testHashCodeAndEquals() { + CompoundName n1 = new CompoundName("venn.d.a"); + CompoundName n2 = new CompoundName(n1.asList()); + assertEquals(n1.hashCode(), n2.hashCode()); + assertEquals(n1, n2); + } + + @Test + public void testAppend() { + assertEquals("a",new CompoundName("a").append("").toString()); + assertEquals("a",new CompoundName("").append("a").toString()); + assertEquals("a.b",new CompoundName("a").append("b").toString()); + + CompoundName name = new CompoundName("a.b"); + assertEquals("a.b.c",name.append("c").toString()); + assertEquals("a.b.d",name.append("d").toString()); + } + + @Test + public void testEmpty() { + CompoundName empty=new CompoundName(""); + assertEquals("", empty.toString()); + assertEquals(0, empty.asList().size()); + } + + @Test + public void testAsList() { + assertEquals("[one]", new CompoundName("one").asList().toString()); + assertEquals("[one, two, three]", new CompoundName("one.two.three").asList().toString()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/request/test/ErrorMessageTestCase.java b/container-core/src/test/java/com/yahoo/processing/request/test/ErrorMessageTestCase.java new file mode 100644 index 00000000000..70f4a7720ee --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/request/test/ErrorMessageTestCase.java @@ -0,0 +1,61 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.request.test; + +import com.yahoo.processing.request.ErrorMessage; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +/** + * @author bratseth + */ +public class ErrorMessageTestCase { + + @Test + public void testToString() { + assertEquals("message",new ErrorMessage("message").toString()); + assertEquals("message: hello",new ErrorMessage("message",new Exception("hello")).toString()); + assertEquals("message: detail",new ErrorMessage("message","detail").toString()); + assertEquals("37: message: detail",new ErrorMessage(37,"message","detail").toString()); + assertEquals("message: detail: hello",new ErrorMessage("message","detail",new Exception("hello")).toString()); + assertEquals("message: detail: hello: world",new ErrorMessage("message","detail",new Exception("hello",new Exception("world"))).toString()); + assertEquals("message: detail: hello: Exception",new ErrorMessage("message","detail",new Exception("hello",new Exception())).toString()); + assertEquals("message: detail: hello",new ErrorMessage("message","detail",new Exception(new Exception("hello"))).toString()); + assertEquals("message: detail: java.lang.Exception: Exception",new ErrorMessage("message","detail",new Exception(new Exception())).toString()); + } + + @Test + public void testAccessors() { + ErrorMessage m = new ErrorMessage(37,"message","detail",new Exception("hello")); + assertEquals(37,m.getCode()); + assertEquals("message",m.getMessage()); + assertEquals("detail",m.getDetailedMessage()); + assertEquals("hello",m.getCause().getMessage()); + } + + @Test + public void testEquality() { + assertEquals(new ErrorMessage(37,"message","detail",new Exception("hello")), + new ErrorMessage(37,"message","detail",new Exception("hello"))); + assertEquals(new ErrorMessage("message","detail",new Exception("hello")), + new ErrorMessage("message","detail",new Exception("hello"))); + assertEquals(new ErrorMessage("message",new Exception("hello")), + new ErrorMessage("message",new Exception("hello"))); + assertEquals(new ErrorMessage("message"), + new ErrorMessage("message")); + assertEquals(new ErrorMessage("message",new Exception()), + new ErrorMessage("message")); + assertNotEquals(new ErrorMessage("message"), + new ErrorMessage("message","detail")); + assertNotEquals(new ErrorMessage(37,"message"), + new ErrorMessage("message")); + assertNotEquals(new ErrorMessage(37,"message"), + new ErrorMessage(38,"message")); + assertNotEquals(new ErrorMessage("message","detail1"), + new ErrorMessage("message","detail2")); + assertNotEquals(new ErrorMessage("message1"), + new ErrorMessage("message2")); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/request/test/PropertyMapTestCase.java b/container-core/src/test/java/com/yahoo/processing/request/test/PropertyMapTestCase.java new file mode 100644 index 00000000000..3c351d341d0 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/request/test/PropertyMapTestCase.java @@ -0,0 +1,99 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.request.test; + +import com.yahoo.processing.request.properties.PropertyMap; +import com.yahoo.processing.request.properties.PublicCloneable; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author bratseth + */ +public class PropertyMapTestCase { + + @Test + public void testObjectCloning() { + PropertyMap map = new PropertyMap(); + map.set("clonable", new ClonableObject()); + map.set("publicClonable", new PublicClonableObject()); + map.set("nonclonable", new NonClonableObject()); + map.set("clonableArray", new ClonableObject[] {new ClonableObject()}); + map.set("publicClonableArray", new ClonableObject[] {new ClonableObject()}); + map.set("nonclonableArray", new NonClonableObject[] {new NonClonableObject()}); + map.set("clonableList", Collections.singletonList(new ClonableObject())); + map.set("nonclonableList", Collections.singletonList(new NonClonableObject())); + assertNotNull(map.get("clonable")); + assertNotNull(map.get("nonclonable")); + + PropertyMap mapClone=map.clone(); + assertTrue(map.get("clonable") != mapClone.get("clonable")); + assertTrue(map.get("publicClonable")!= mapClone.get("publicClonable")); + assertTrue(map.get("nonclonable") == mapClone.get("nonclonable")); + + assertTrue(map.get("clonableArray") != mapClone.get("clonableArray")); + assertTrue(first(map.get("clonableArray")) != first(mapClone.get("clonableArray"))); + assertTrue(map.get("publicClonableArray") != mapClone.get("publicClonableArray")); + assertTrue(first(map.get("publicClonableArray")) != first(mapClone.get("publicClonableArray"))); + assertTrue(first(map.get("nonclonableArray")) == first(mapClone.get("nonclonableArray"))); + } + + @Test + public void testArrayCloning() { + PropertyMap map = new PropertyMap(); + byte[] byteArray = new byte[] {2, 4, 7}; + map.set("byteArray", byteArray); + + PropertyMap mapClone = map.clone(); + assertArrayEquals(byteArray, (byte[])mapClone.get("byteArray")); + assertTrue("Array was cloned", mapClone.get("byteArray") != byteArray); + } + + private Object first(Object object) { + if (object instanceof Object[]) + return ((Object[])object)[0]; + if (object instanceof List) + return ((List<?>)object).get(0); + throw new IllegalArgumentException(); + } + + public static class ClonableObject implements Cloneable { + + @Override + public ClonableObject clone() { + try { + return (ClonableObject)super.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + } + + public static class PublicClonableObject implements PublicCloneable<PublicClonableObject> { + + @Override + public PublicClonableObject clone() { + try { + return (PublicClonableObject)super.clone(); + } + catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + } + + } + + private static class NonClonableObject { + + } + + +} diff --git a/container-core/src/test/java/com/yahoo/processing/request/test/RequestTestCase.java b/container-core/src/test/java/com/yahoo/processing/request/test/RequestTestCase.java new file mode 100644 index 00000000000..032fdd71f88 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/request/test/RequestTestCase.java @@ -0,0 +1,141 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.request.test; + +import com.yahoo.processing.Request; +import com.yahoo.processing.request.CompoundName; +import com.yahoo.processing.request.ErrorMessage; +import com.yahoo.processing.request.Properties; +import com.yahoo.processing.request.properties.PropertyMap; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Tests using requests + * + * @author bratseth + */ +public class RequestTestCase { + + private static final double delta = 0.0000000001; + + @Test + public void testProperties() { + Properties p = new PropertyMap(); + p.set("a", "a1"); + Request r = new Request(p); + r.properties().set("b", "b1"); + assertEquals(2, r.properties().listProperties().size()); + assertEquals("a1", r.properties().get("a")); + + assertEquals("b1", r.properties().get("b")); + assertEquals("b1", r.properties().get("b", "default")); + assertEquals("default", r.properties().get("c", "default")); + assertNull(r.properties().get("c")); + assertEquals("b1", r.properties().get(new CompoundName("b"))); + assertEquals("b1", r.properties().get(new CompoundName("b"), "default")); + assertEquals("default", r.properties().get(new CompoundName("c"), "default")); + assertNull(r.properties().get(new CompoundName("c"))); + + assertEquals("b1", r.properties().getString("b")); + assertEquals("b1", r.properties().getString("b","default")); + assertEquals("default", r.properties().getString("c","default")); + assertEquals(null, r.properties().getString("c")); + assertEquals("b1", r.properties().getString(new CompoundName("b"))); + assertEquals("b1", r.properties().getString(new CompoundName("b"),"default")); + assertEquals("default", r.properties().getString(new CompoundName("c"),"default")); + assertEquals(null, r.properties().getString(new CompoundName("c"))); + + r.properties().set("i",7); + assertEquals(7, (int)r.properties().getInteger("i")); + assertEquals(7, (int)r.properties().getInteger("i",3)); + assertEquals(3, (int)r.properties().getInteger("n",3)); + assertNull(r.properties().getInteger("n")); + assertEquals(7, (int)r.properties().getInteger(new CompoundName("i"))); + assertEquals(7, (int)r.properties().getInteger(new CompoundName("i"),3)); + assertEquals(3, (int)r.properties().getInteger(new CompoundName("n"),3)); + assertNull(r.properties().getInteger("n")); + + r.properties().set(new CompoundName("l"), 7); + assertEquals(7, (long) r.properties().getLong("l")); + assertEquals(7, (long)r.properties().getLong("l",3l)); + assertEquals(3, (long)r.properties().getLong("m",3l)); + assertNull(r.properties().getInteger("m")); + assertEquals(7, (long)r.properties().getLong(new CompoundName("l"))); + assertEquals(7, (long)r.properties().getLong(new CompoundName("l"),3l)); + assertEquals(3, (long)r.properties().getLong(new CompoundName("m"),3l)); + assertNull(r.properties().getInteger("m")); + + r.properties().set("d", 7.3); + assertEquals(7.3, r.properties().getDouble("d"), delta); + assertEquals(7.3, r.properties().getDouble("d",3.4d), delta); + assertEquals(3.4, r.properties().getDouble("f",3.4d), delta); + assertNull(r.properties().getDouble("f")); + assertEquals(7.3, r.properties().getDouble(new CompoundName("d")), delta); + assertEquals(7.3, r.properties().getDouble(new CompoundName("d"),3.4d), delta); + assertEquals(3.4, r.properties().getDouble(new CompoundName("f"),3.4d), delta); + assertNull(r.properties().getDouble("f")); + + r.properties().set("o",true); + assertEquals(true, r.properties().getBoolean("o")); + assertEquals(true, r.properties().getBoolean("o",true)); + assertEquals(true, r.properties().getBoolean("g",true)); + assertEquals(false, r.properties().getBoolean("g")); + assertEquals(true, r.properties().getBoolean(new CompoundName("o"))); + assertEquals(true, r.properties().getBoolean(new CompoundName("o"),true)); + assertEquals(true, r.properties().getBoolean(new CompoundName("g"),true)); + assertEquals(false, r.properties().getBoolean("g")); + + r.properties().set(new CompoundName("x.y"), "x1.y1"); + r.properties().set("x.z", "x1.z1"); + + assertEquals(8, r.properties().listProperties().size()); + assertEquals(0, r.properties().listProperties("a").size()); + assertEquals(0, r.properties().listProperties(new CompoundName("a")).size()); + assertEquals(0, r.properties().listProperties(new CompoundName("none")).size()); + assertEquals(2, r.properties().listProperties(new CompoundName("x")).size()); + assertEquals(2, r.properties().listProperties("x").size()); + } + + @Test + public void testErrorMessages() { + Request r = new Request(); + r.errors().add(new ErrorMessage("foo")); + r.errors().add(new ErrorMessage("bar")); + assertEquals(2,r.errors().size()); + assertEquals("foo", r.errors().get(0).getMessage()); + assertEquals("bar", r.errors().get(1).getMessage()); + } + + @Test + public void testCloning() { + Request request = new Request(); + request.properties().set("a","a1"); + request.properties().set("b","b1"); + request.errors().add(new ErrorMessage("foo")); + request.errors().add(new ErrorMessage("bar")); + Request rcloned = request.clone(); + rcloned.properties().set("c", "c1"); + rcloned.errors().add(new ErrorMessage("baz")); + request.properties().set("d", "d1"); + request.errors().add(new ErrorMessage("boz")); + + assertEquals("a1", request.properties().get("a")); + assertEquals("a1", rcloned.properties().get("a")); + assertEquals("b1", request.properties().get("b")); + assertEquals("b1", rcloned.properties().get("b")); + assertEquals(null, request.properties().get("c")); + assertEquals("c1", rcloned.properties().get("c")); + assertEquals("d1", request.properties().get("d")); + assertEquals(null, rcloned.properties().get("d")); + + assertEquals(3, request.errors().size()); + assertEquals(1, rcloned.errors().size()); + assertEquals("foo",request.errors().get(0).getMessage()); + assertEquals("bar",request.errors().get(1).getMessage()); + assertEquals("boz",request.errors().get(2).getMessage()); + assertEquals("baz",rcloned.errors().get(0).getMessage()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/test/DocumentationTestCase.java b/container-core/src/test/java/com/yahoo/processing/test/DocumentationTestCase.java new file mode 100644 index 00000000000..ca8fb377f09 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/test/DocumentationTestCase.java @@ -0,0 +1,44 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.test; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.test.documentation.AsyncDataProcessingInitiator; +import com.yahoo.processing.test.documentation.AsyncDataProducer; +import com.yahoo.processing.test.documentation.ExampleProcessor; +import com.yahoo.processing.test.documentation.Federator; + +/** + * See to it we can actually run the examples in the doc. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class DocumentationTestCase { + + @SuppressWarnings("unchecked") + @Test + public final void test() { + Processor p = new ExampleProcessor(); + Chain<Processor> basic = new Chain<>(p); + Processor initiator = new AsyncDataProcessingInitiator(basic); + Chain<Processor> postProcessing = new Chain<>(initiator); + Execution e = Execution.createRoot(postProcessing, 0, Execution.Environment.createEmpty()); + Response r = e.process(new Request()); + // just adds a listener to the result returned from basic + assertEquals(0, r.data().asList().size()); + Processor producer = new AsyncDataProducer(); + Chain<Processor> asyncChain = new Chain<>(producer); + Processor federator = new Federator(basic, asyncChain); + e = Execution.createRoot(federator, 0, Execution.Environment.createEmpty()); + r = e.process(new Request()); + assertEquals(2, r.data().asList().size()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/test/ProcessingTestCase.java b/container-core/src/test/java/com/yahoo/processing/test/ProcessingTestCase.java new file mode 100644 index 00000000000..77c2ca7d10d --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/test/ProcessingTestCase.java @@ -0,0 +1,60 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.test; + +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.Execution; +import org.junit.Test; + +import static com.yahoo.processing.test.ProcessorLibrary.*; +import static org.junit.Assert.assertEquals; + +/** + * Tests the basic of the processing framework + */ +public class ProcessingTestCase { + + /** Execute three simple processors doing some phony processing */ + @Test + public void testChainedProcessing1() { + // Create a chain + Chain<Processor> chain=new Chain<>(new CombineData(),new Get6DataItems(), new DataSource()); + + // Execute it + Request request=new Request(); + request.properties().set("appendage",1); + Response response=Execution.createRoot(chain,0,Execution.Environment.createEmpty()).process(request); + + // Verify the result + assertEquals(6-1,response.data().asList().size()); + assertEquals("first.2, third.2",response.data().get(0).toString()); + assertEquals("second.2",response.data().get(1).toString()); + assertEquals("first.3",response.data().get(2).toString()); + assertEquals("second.3",response.data().get(3).toString()); + assertEquals("third.3",response.data().get(4).toString()); + } + + /** Execute the same processors in a different order */ + @Test + public void testChainedProcessing2() { + // Create a chain + Chain<Processor> chain=new Chain<>(new Get6DataItems(),new CombineData(), new DataSource()); + + // Execute it + Request request=new Request(); + request.properties().set("appendage",1); + Response response=Execution.createRoot(chain,0,Execution.Environment.createEmpty()).process(request); + + // Check the result + assertEquals(6,response.data().asList().size()); + assertEquals("first.2, third.2",response.data().get(0).toString()); + assertEquals("second.2",response.data().get(1).toString()); + assertEquals("first.4, third.4",response.data().get(2).toString()); + assertEquals("second.4",response.data().get(3).toString()); + assertEquals("first.6, third.6",response.data().get(4).toString()); + assertEquals("second.6",response.data().get(5).toString()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/test/documentation/AsyncDataProcessingInitiator.java b/container-core/src/test/java/com/yahoo/processing/test/documentation/AsyncDataProcessingInitiator.java new file mode 100644 index 00000000000..0de20b962d7 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/test/documentation/AsyncDataProcessingInitiator.java @@ -0,0 +1,30 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.test.documentation; + +import com.google.common.util.concurrent.MoreExecutors; +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.*; +import com.yahoo.processing.execution.*; + +/** + * A processor which registers a listener on the future completion of + * asynchronously arriving data to perform another chain at that point. + */ +public class AsyncDataProcessingInitiator extends Processor { + + private final Chain<Processor> asyncChain; + + public AsyncDataProcessingInitiator(Chain<Processor> asyncChain) { + this.asyncChain=asyncChain; + } + + @Override + public Response process(Request request, Execution execution) { + Response response=execution.process(request); + response.data().complete().addListener(new RunnableExecution(request, + new ExecutionWithResponse(asyncChain, response, execution)), + MoreExecutors.directExecutor()); + return response; + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/test/documentation/AsyncDataProducer.java b/container-core/src/test/java/com/yahoo/processing/test/documentation/AsyncDataProducer.java new file mode 100644 index 00000000000..c6c9c0d785b --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/test/documentation/AsyncDataProducer.java @@ -0,0 +1,37 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.test.documentation; + +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.response.ArrayDataList; +import com.yahoo.processing.response.DataList; +import com.yahoo.processing.response.IncomingData; +import com.yahoo.processing.test.ProcessorLibrary.StringData; + +/** + * A data producer which producer data which will receive asynchronously. + * This is not a realistic, thread safe implementation as only the incoming data + * from the last created incoming data can be completed. + */ +public class AsyncDataProducer extends Processor { + + private IncomingData incomingData; + + @SuppressWarnings("unchecked") + @Override + public Response process(Request request, Execution execution) { + DataList dataList = ArrayDataList.createAsync(request); // Default implementation + incomingData=dataList.incoming(); + return new Response(dataList); + } + + /** Called by some other data producing thread, later */ + @SuppressWarnings("unchecked") + public void completeLateData() { + incomingData.addLast(new StringData(incomingData.getOwner().request(), + "A late hello, world!")); + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/test/documentation/ExampleProcessor.java b/container-core/src/test/java/com/yahoo/processing/test/documentation/ExampleProcessor.java new file mode 100644 index 00000000000..6a1e0cbdd58 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/test/documentation/ExampleProcessor.java @@ -0,0 +1,25 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.test.documentation; + +import com.yahoo.processing.*; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.test.ProcessorLibrary.StringData; + +public class ExampleProcessor extends Processor { + + @SuppressWarnings("unchecked") + @Override + public Response process(Request request, Execution execution) { + // Process the Request: + request.properties().set("foo","bar"); + + // Pass it down the chain to get a response + Response response=execution.process(request); + + // process the response + response.data().add(new StringData(request,"Hello, world!")); + + return response; + } + +} diff --git a/container-core/src/test/java/com/yahoo/processing/test/documentation/Federator.java b/container-core/src/test/java/com/yahoo/processing/test/documentation/Federator.java new file mode 100644 index 00000000000..079c4912fd9 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/processing/test/documentation/Federator.java @@ -0,0 +1,45 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.processing.test.documentation; + +import com.yahoo.component.chain.Chain; +import com.yahoo.processing.Processor; +import com.yahoo.processing.Request; +import com.yahoo.processing.Response; +import com.yahoo.processing.execution.AsyncExecution; +import com.yahoo.processing.execution.Execution; +import com.yahoo.processing.response.FutureResponse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Call a number of chains in parallel + */ +public class Federator extends Processor { + + private final List<Chain<? extends Processor>> chains; + + @SafeVarargs + @SuppressWarnings("varargs") + public Federator(Chain<? extends Processor> ... chains) { + this.chains = Arrays.asList(chains); + } + + @SuppressWarnings("unchecked") + @Override + public Response process(Request request, Execution execution) { + List<FutureResponse> futureResponses=new ArrayList<>(chains.size()); + for (Chain<? extends Processor> chain : chains) { + futureResponses.add(new AsyncExecution(chain,execution).process(request)); + } + Response response=execution.process(request); + AsyncExecution.waitForAll(futureResponses,1000); + for (FutureResponse futureResponse : futureResponses) { + Response federatedResponse=futureResponse.get(); + response.data().add(federatedResponse.data()); + response.mergeWith(federatedResponse); + } + return response; + } +} |