summaryrefslogtreecommitdiffstats
path: root/container-search/src/test/java/com/yahoo/search/pagetemplates
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-search/src/test/java/com/yahoo/search/pagetemplates
Publish
Diffstat (limited to 'container-search/src/test/java/com/yahoo/search/pagetemplates')
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/MapPageTemplateXMLReadingTestCase.java48
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/PageTemplateXMLReadingTestCase.java279
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceFooter.xml6
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceHeader.xml10
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/footer.xml5
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/generic.xml5
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/header.xml7
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/includer.xml36
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/invalidfilename/invalid.xml4
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/mapexamples/map1.xml21
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richSerp.xml17
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richerSerp.xml45
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/serp.xml5
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/slottingSerp.xml5
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySource.xml4
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceResult.xml41
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceTestCase.java59
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderers.xml17
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersResult.xml36
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersTestCase.java51
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsections.xml20
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsResult.xml29
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsTestCase.java68
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSources.xml7
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesResult.xml17
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesTestCase.java51
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Choices.xml45
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesResult.xml55
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesTestCase.java50
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java74
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSections.xml28
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsResult.xml96
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsTestCase.java90
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSections.xml22
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsResult.xml73
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsTestCase.java88
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Page.xml31
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageResult.xml43
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageTestCase.java44
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlending.xml37
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingResult.xml45
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingTestCase.java44
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRenderer.xml36
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererResult.xml43
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererTestCase.java44
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoice.xml7
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceResult.xml17
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceTestCase.java43
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSources.xml5
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesResult.xml65
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesTestCase.java140
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/test/PageTemplateSearcherTestCase.java220
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParameters.xml16
-rw-r--r--container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParametersTestCase.java54
54 files changed, 2448 insertions, 0 deletions
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/MapPageTemplateXMLReadingTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/MapPageTemplateXMLReadingTestCase.java
new file mode 100644
index 00000000000..052ea62d4f0
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/MapPageTemplateXMLReadingTestCase.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.config.test;
+
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.PageTemplateRegistry;
+import com.yahoo.search.pagetemplates.config.PageTemplateXMLReader;
+import com.yahoo.search.pagetemplates.model.*;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class MapPageTemplateXMLReadingTestCase extends junit.framework.TestCase {
+
+ private String root="src/test/java/com/yahoo/search/pagetemplates/config/test/examples/mapexamples/";
+
+ public void testMap1() {
+ PageTemplateRegistry registry=new PageTemplateXMLReader().read(root);
+ assertCorrectMap1(registry.getComponent("map1"));
+ }
+
+ private void assertCorrectMap1(PageTemplate page) {
+ assertNotNull("map1 was read",page);
+ Section root=page.getSection();
+ assertTrue(((Section)((Section)root.elements(Section.class).get(0)).elements(Section.class).get(0)).elements().get(0) instanceof Placeholder);
+ assertTrue(((Section)((Section)root.elements(Section.class).get(0)).elements(Section.class).get(1)).elements().get(0) instanceof Placeholder);
+ assertTrue(((Section)((Section)root.elements(Section.class).get(1)).elements(Section.class).get(0)).elements().get(0) instanceof Placeholder);
+ assertTrue(((Section)((Section)root.elements(Section.class).get(1)).elements(Section.class).get(1)).elements().get(0) instanceof Placeholder);
+ assertEquals("box1source",((Placeholder) ((Section)((Section)root.elements(Section.class).get(0)).elements(Section.class).get(0)).elements().get(0)).getId());
+ assertEquals("box2source",((Placeholder) ((Section)((Section)root.elements(Section.class).get(0)).elements(Section.class).get(1)).elements().get(0)).getId());
+ assertEquals("box3source",((Placeholder) ((Section)((Section)root.elements(Section.class).get(1)).elements(Section.class).get(0)).elements().get(0)).getId());
+ assertEquals("box4source",((Placeholder) ((Section)((Section)root.elements(Section.class).get(1)).elements(Section.class).get(1)).elements().get(0)).getId());
+
+ MapChoice map=(MapChoice)root.elements().get(2);
+ assertEquals("box1source",map.placeholderIds().get(0));
+ assertEquals("box2source",map.placeholderIds().get(1));
+ assertEquals("box3source",map.placeholderIds().get(2));
+ assertEquals("box4source",map.placeholderIds().get(3));
+ assertEquals("source1",((Source)((List<?>)map.values().get(0)).get(0)).getName());
+ assertEquals("source2",((Source)((List<?>)map.values().get(1)).get(0)).getName());
+ assertEquals("source3",((Source)((List<?>)map.values().get(2)).get(0)).getName());
+ assertEquals("source4",((Source)((List<?>)map.values().get(3)).get(0)).getName());
+
+ PageTemplateXMLReadingTestCase.assertCorrectSources("source1 source2 source3 source4",page);
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/PageTemplateXMLReadingTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/PageTemplateXMLReadingTestCase.java
new file mode 100644
index 00000000000..7832719412a
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/PageTemplateXMLReadingTestCase.java
@@ -0,0 +1,279 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.config.test;
+
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.PageTemplateRegistry;
+import com.yahoo.search.pagetemplates.PageTemplatesConfig;
+import com.yahoo.search.pagetemplates.config.PageTemplateConfigurer;
+import com.yahoo.search.pagetemplates.config.PageTemplateXMLReader;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.pagetemplates.model.Renderer;
+import com.yahoo.search.pagetemplates.model.Section;
+import com.yahoo.search.pagetemplates.model.Source;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class PageTemplateXMLReadingTestCase extends junit.framework.TestCase {
+
+ private String root="src/test/java/com/yahoo/search/pagetemplates/config/test/";
+
+ public void testExamples() {
+ PageTemplateRegistry registry=new PageTemplateXMLReader().read(root + "examples");
+ assertCorrectSerp(registry.getComponent("serp"));
+ assertCorrectSlottingSerp(registry.getComponent("slottingSerp"));
+ assertCorrectRichSerp(registry.getComponent("richSerp"));
+ assertCorrectRicherSerp(registry.getComponent("richerSerp"));
+ assertCorrectIncluder(registry.getComponent("includer"));
+ assertCorrectGeneric(registry.getComponent("generic"));
+ }
+
+ public void testConfigReading() {
+ PageTemplatesConfig config = new PageTemplatesConfig(new PageTemplatesConfig.Builder()
+ .page("<page id=\"slottingSerp\" layout=\"mainAndRight\">\n <section layout=\"column\" region=\"main\" source=\"*\" order=\"-[rank]\"/>\n <section layout=\"column\" region=\"right\" source=\"ads\"/>\n</page>\n")
+ .page("<page id=\"richSerp\" layout=\"mainAndRight\">\n <section layout=\"row\" region=\"main\">\n <section layout=\"column\" description=\"left main pane\">\n <section layout=\"row\" max=\"5\" description=\"Bar of images, from one of two possible sources\">\n <choice method=\"annealing\">\n <source name=\"images\"/>\n <source name=\"flickr\"/>\n </choice>\n </section>\n <section max=\"1\" source=\"local map video ticker weather\" description=\"A single relevant graphically rich element\"/>\n <section order=\"-[rank]\" max=\"10\" source=\"web news\" description=\"Various kinds of traditional search results\"/>\n </section>\n <section layout=\"column\" order=\"[source]\" source=\"answers blogs twitter\" description=\"right main pane, ugc stuff, grouped by source\"/>\n </section>\n <section layout=\"column\" source=\"ads\" region=\"right\"/>\n</page>\n")
+ .page("<page id=\"footer\">\n <section layout=\"row\" source=\"popularSearches\"/>\n <section id=\"extraFooter\" layout=\"row\" source=\"topArticles\"/>\n</page>\n")
+ .page("<page id=\"richerSerp\" layout=\"column\">\n <include idref=\"header\"/>\n <section layout=\"mainAndRight\">\n <section layout=\"row\" region=\"main\">\n <section layout=\"column\" description=\"left main pane\">\n <choice>\n <alternative>\n <section layout=\"row\" max=\"5\" description=\"Bar of images, from one of two possible sources\">\n <choice>\n <source name=\"images\"/>\n <alternative>\n <source name=\"flickr\">\n <renderer name=\"mouseOverImage\"/>\n </source>\n <source name=\"twitpic\">\n <choice>\n <renderer name=\"mouseOverImage\">\n <parameter name=\"hovertime\">5</parameter>\n <parameter name=\"borderColor\">#ff00ff</parameter>\n </renderer>\n <renderer name=\"regularImage\"/>\n </choice>\n <parameter name=\"filter\">origin=twitter</parameter>\n </source>\n </alternative>\n </choice>\n <choice>\n <renderer name=\"regularImageBox\"/>\n <renderer name=\"newImageBox\"/>\n </choice>\n </section>\n <section max=\"1\" source=\"local map video ticker weather\" description=\"A single relevant graphically rich element\"/>\n </alternative>\n <section order=\"[source]\" max=\"10\" source=\"web news\" description=\"Various kinds of traditional search results\"/>\n </choice>\n </section>\n <section layout=\"column\" order=\"[source]\" source=\"answers blogs twitter\" description=\"right main pane, ugc stuff, grouped by source\"/>\n </section>\n <section layout=\"column\" source=\"ads\" region=\"right\" order=\"-[rank] clickProbability\">\n <renderer name=\"newAdBox\"/>\n </section>\n </section>\n <include idref=\"footer\"/>\n</page>\n")
+ .page("<page id=\"header\">\n <section layout=\"row\">\n <section source=\"global\"/>\n <section source=\"notifications\"/>\n </section>\n</page>\n")
+ );
+ PageTemplateRegistry registry = PageTemplateConfigurer.toRegistry(config);
+ assertCorrectSlottingSerp(registry.getComponent("slottingSerp"));
+ assertCorrectRichSerp(registry.getComponent("richSerp"));
+ assertCorrectRicherSerp(registry.getComponent("richerSerp"));
+ }
+
+ public void testInvalidFilename() {
+ try {
+ PageTemplateRegistry registry=new PageTemplateXMLReader().read(root + "examples/invalidfilename");
+ assertEquals(0,registry.allComponents().size());
+ fail("Should have caused an exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("The file name of page template 'notinvalid' must be 'notinvalid.xml' but was 'invalid.xml'",e.getMessage());
+ }
+ }
+
+ protected void assertCorrectSerp(PageTemplate page) {
+ assertNotNull("'serp' was read",page);
+ Section rootSection=page.getSection();
+ assertNotNull(rootSection);
+ assertEquals("mainAndRight",rootSection.getLayout().getName());
+ Section main=(Section)rootSection.elements(Section.class).get(0);
+ assertEquals("column",main.getLayout().getName());
+ assertEquals("main",main.getRegion());
+ assertEquals("web",((Source)main.elements(Source.class).get(0)).getName());
+ Section right=(Section)rootSection.elements(Section.class).get(1);
+ assertEquals("column",right.getLayout().getName());
+ assertEquals("right",right.getRegion());
+ assertEquals("ads",((Source)right.elements(Source.class).get(0)).getName());
+ }
+
+ protected void assertCorrectSlottingSerp(PageTemplate page) {
+ assertNotNull("'slotting serp' was read",page);
+ Section rootSection=page.getSection();
+ Section main=(Section)rootSection.elements(Section.class).get(0);
+ assertEquals("-[rank]",main.getOrder().toString());
+ assertEquals(Source.any,main.elements(Source.class).get(0));
+
+ assertCorrectSources("* ads",page);
+ }
+
+ protected void assertCorrectRichSerp(PageTemplate page) {
+ assertNotNull("'rich serp' was read",page);
+ Section rootSection=page.getSection();
+ assertNotNull(rootSection);
+ assertEquals("mainAndRight",rootSection.getLayout().getName());
+
+ Section main=(Section)rootSection.elements(Section.class).get(0);
+ assertEquals("row",main.getLayout().getName());
+ assertEquals("main",main.getRegion());
+ Section leftMain=(Section)main.elements(Section.class).get(0);
+ assertEquals("column",leftMain.getLayout().getName());
+ Section imageBar=(Section)leftMain.elements(Section.class).get(0);
+ assertEquals("row",imageBar.getLayout().getName());
+ assertEquals(5,imageBar.getMax());
+ assertEquals("annealing",((Choice)imageBar.elements(Source.class).get(0)).getMethod().toString());
+ assertEquals("images",((Source)((Choice)imageBar.elements(Source.class).get(0)).alternatives().get(0).get(0)).getName());
+ assertEquals("flickr",((Source)((Choice)imageBar.elements(Source.class).get(0)).alternatives().get(1).get(0)).getName());
+ Section richElement=(Section)leftMain.elements(Section.class).get(1);
+ assertEquals(1,richElement.getMax());
+ assertEquals("[source 'local', source 'map', source 'video', source 'ticker', source 'weather']",richElement.elements(Source.class).toString());
+ Section webResults=(Section)leftMain.elements(Section.class).get(2);
+ assertEquals("-[rank]",webResults.getOrder().toString());
+ assertEquals(10,webResults.getMax());
+ assertEquals("[source 'web', source 'news']",webResults.elements(Source.class).toString());
+ Section rightMain=(Section)main.elements(Section.class).get(1);
+ assertEquals("column",rightMain.getLayout().getName());
+ assertEquals("+[source]",rightMain.getOrder().toString());
+ assertEquals("[source 'answers', source 'blogs', source 'twitter']",rightMain.elements(Source.class).toString());
+
+ Section right=(Section)rootSection.elements(Section.class).get(1);
+ assertEquals("column",right.getLayout().getName());
+ assertEquals("right",right.getRegion());
+ assertEquals("ads",((Source)right.elements(Source.class).get(0)).getName());
+ }
+
+ protected void assertCorrectRicherSerp(PageTemplate page) {
+ assertNotNull("'richer serp' was read",page);
+
+ // Check resolution as we go
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(Choice.createSingleton(page),null,null);
+
+ Section root=page.getSection();
+ assertNotNull(root);
+ assertEquals("column",root.getLayout().getName());
+
+ assertEquals("Sections was correctly imported and combined with the section in this",4,root.elements(Section.class).size());
+
+ assertCorrectHeader((Section)root.elements(Section.class).get(0));
+
+ Section body=(Section)root.elements(Section.class).get(1);
+ assertEquals("mainAndRight",body.getLayout().getName());
+
+ Section main=(Section)body.elements(Section.class).get(0);
+ assertEquals("row",main.getLayout().getName());
+ assertEquals("main",main.getRegion());
+
+ Section leftMain=(Section)main.elements(Section.class).get(0);
+ assertEquals("column",leftMain.getLayout().getName());
+ assertEquals(1,resolution.getResolution((Choice)leftMain.elements(Section.class).get(0)));
+
+ Section imageBar=(Section)((Choice)leftMain.elements(Section.class).get(0)).alternatives().get(0).get(0);
+ assertEquals("row",imageBar.getLayout().getName());
+ assertEquals(5,imageBar.getMax());
+ assertEquals(2,((Choice)imageBar.elements(Source.class).get(0)).alternatives().size());
+ assertEquals("images",((Source)((Choice)imageBar.elements(Source.class).get(0)).alternatives().get(0).get(0)).getName());
+ assertEquals(1,resolution.getResolution((Choice)imageBar.elements(Source.class).get(0)));
+ assertEquals(1,resolution.getResolution((Choice)imageBar.elements(Renderer.class).get(0)));
+
+ Source flickrSource=(Source)((Choice)imageBar.elements(Source.class).get(0)).alternatives().get(1).get(0);
+ assertEquals("flickr",flickrSource.getName());
+ assertEquals(1,flickrSource.renderers().size());
+ assertEquals("mouseOverImage",((Renderer)flickrSource.renderers().get(0)).getName());
+
+ Source twitpicSource=(Source)((Choice)imageBar.elements(Source.class).get(0)).alternatives().get(1).get(1);
+ assertEquals("twitpic",twitpicSource.getName());
+ assertEquals(1,twitpicSource.parameters().size());
+ assertEquals("origin=twitter",twitpicSource.parameters().get("filter"));
+ assertEquals(2,((Choice)twitpicSource.renderers().get(0)).alternatives().size());
+ assertEquals(1,resolution.getResolution((Choice)twitpicSource.renderers().get(0)));
+
+ Renderer mouseOverImageRenderer=(Renderer)((Choice)twitpicSource.renderers().get(0)).alternatives().get(0).get(0);
+ assertEquals("mouseOverImage", mouseOverImageRenderer.getName());
+ assertEquals(2, mouseOverImageRenderer.parameters().size());
+ assertEquals("5", mouseOverImageRenderer.parameters().get("hovertime"));
+ assertEquals("#ff00ff", mouseOverImageRenderer.parameters().get("borderColor"));
+ assertEquals("regularImage",((Renderer)((Choice)twitpicSource.renderers().get(0)).alternatives().get(1).get(0)).getName());
+ assertEquals(2,((Choice)imageBar.elements(Renderer.class).get(0)).alternatives().size());
+ assertEquals("regularImageBox",((Renderer)((Choice)imageBar.elements(Renderer.class).get(0)).alternatives().get(0).get(0)).getName());
+ assertEquals("newImageBox",((Renderer)((Choice)imageBar.elements(Renderer.class).get(0)).alternatives().get(1).get(0)).getName());
+
+ Section richElement=(Section)((Choice)leftMain.elements(Section.class).get(0)).get(0).get(1);
+ assertEquals(1,richElement.getMax());
+ assertEquals("[source 'local', source 'map', source 'video', source 'ticker', source 'weather']",richElement.elements(Source.class).toString());
+
+ Section webResults=(Section)((Choice)leftMain.elements(Section.class).get(0)).get(1).get(0);
+ assertEquals("+[source]",webResults.getOrder().toString());
+ assertEquals(10,webResults.getMax());
+ assertEquals("[source 'web', source 'news']",webResults.elements(Source.class).toString());
+
+ Section rightMain=(Section)main.elements(Section.class).get(1);
+ assertEquals("column",rightMain.getLayout().getName());
+ assertEquals("+[source]",rightMain.getOrder().toString());
+ assertEquals("[source 'answers', source 'blogs', source 'twitter']",rightMain.elements(Source.class).toString());
+
+ Section right=(Section)body.elements(Section.class).get(1);
+ assertEquals("column",right.getLayout().getName());
+ assertEquals("right",right.getRegion());
+ assertEquals("ads",((Source)right.elements(Source.class).get(0)).getName());
+ assertEquals("newAdBox",((Renderer)right.elements(Renderer.class).get(0)).getName());
+ assertEquals("-[rank] +clickProbability",right.getOrder().toString());
+
+ assertCorrectFooter((Section)root.elements(Section.class).get(2));
+ assertEquals("extraFooter",((Section)root.elements(Section.class).get(3)).getId());
+
+ // Check getSources()
+ assertCorrectSources("ads answers blogs flickr global images local map news notifications " +
+ "popularSearches ticker topArticles twitpic twitter video weather web",page);
+ }
+
+ static void assertCorrectSources(String expectedSourceNameString,PageTemplate page) {
+ String[] expectedSourceNames=expectedSourceNameString.split(" ");
+ Set<String> sourceNames=new HashSet<>();
+ for (Source source : page.getSources())
+ sourceNames.add(source.getName());
+ assertEquals("Expected " + expectedSourceNames.length + " elements in " + sourceNames,
+ expectedSourceNames.length,sourceNames.size());
+ for (String expectedSourceName : expectedSourceNames)
+ assertTrue("Sources did not include '" + expectedSourceName+ "'",sourceNames.contains(expectedSourceName));
+ }
+
+ protected void assertCorrectIncluder(PageTemplate page) {
+ assertNotNull("'includer' was read",page);
+
+ Resolution resolution=new DeterministicResolver().resolve(Choice.createSingleton(page),null,null);
+
+ Section case1=(Section)page.getSection().elements(Section.class).get(0);
+ assertCorrectHeader((Section)case1.elements(Section.class).get(0));
+ assertCorrectFooter((Section)case1.elements(Section.class).get(1));
+
+ Section case2=(Section)page.getSection().elements(Section.class).get(1);
+ assertCorrectHeader((Section)((Choice)case2.elements(Section.class).get(0)).get(0).get(0));
+ assertCorrectFooter((Section)((Choice)case2.elements(Section.class).get(0)).get(1).get(0));
+ assertEquals(1,resolution.getResolution((Choice)case2.elements(Section.class).get(0)));
+
+ Section case3=(Section)page.getSection().elements(Section.class).get(2);
+ assertCorrectHeader((Section)((Choice)case3.elements(Section.class).get(0)).get(0).get(0));
+ assertCorrectFooter((Section)((Choice)case3.elements(Section.class).get(0)).get(1).get(0));
+ assertEquals(1,resolution.getResolution((Choice)case3.elements(Section.class).get(0)));
+
+ Section case4=(Section)page.getSection().elements(Section.class).get(3);
+ assertEquals("first",((Section)((Choice)case4.elements(Section.class).get(0)).get(0).get(0)).getId());
+ assertCorrectHeader((Section)((Choice)case4.elements(Section.class).get(0)).get(1).get(0));
+ assertEquals("middle",((Section)((Choice)case4.elements(Section.class).get(0)).get(2).get(0)).getId());
+ assertCorrectFooter((Section)((Choice)case4.elements(Section.class).get(0)).get(3).get(0));
+ assertEquals("last",((Section)((Choice)case4.elements(Section.class).get(0)).get(4).get(0)).getId());
+ assertEquals(4,resolution.getResolution((Choice)case4.elements(Section.class).get(0)));
+
+ Section case5=(Section)page.getSection().elements(Section.class).get(4);
+ assertEquals(2,((Choice)case5.elements(Section.class).get(0)).alternatives().size());
+ assertCorrectHeader((Section)((Choice)case5.elements(Section.class).get(0)).get(0).get(0));
+ assertEquals("second",((Section)((Choice)case5.elements(Section.class).get(0)).get(1).get(0)).getId());
+ assertEquals(1,resolution.getResolution((Choice)case5.elements(Section.class).get(0)));
+
+ // This case - a choice inside a choice - makes little sense. It is included as a reminder -
+ // what we really want is to be able to include some additional alternatives of a choice,
+ // but without any magic. That requires allowing alternative as a top-level tag, or something
+ Section case6=(Section)page.getSection().elements(Section.class).get(5);
+ Choice includerChoice=(Choice)case6.elements().get(0);
+ Choice includedChoice=(Choice)includerChoice.alternatives().get(0).get(0);
+ assertCorrectFooter((Section)includedChoice.alternatives().get(0).get(0));
+ }
+
+ private void assertCorrectHeader(Section header) {
+ assertEquals("row",header.getLayout().getName());
+ assertEquals(2,header.elements(Section.class).size());
+ assertEquals( "global",((Source)((Section)header.elements(Section.class).get(0)).elements(Source.class).get(0)).getName());
+ assertEquals("notifications",((Source)((Section)header.elements(Section.class).get(1)).elements(Source.class).get(0)).getName());
+ }
+
+ private void assertCorrectFooter(Section footer) {
+ assertEquals("row",footer.getLayout().getName());
+ assertTrue(footer.elements(Section.class).isEmpty());
+ assertEquals("popularSearches",((Source)footer.elements(Source.class).get(0)).getName());
+ }
+
+ private void assertCorrectGeneric(PageTemplate page) {
+ assertEquals("image", ((Source)((Section)page.getSection().elements(Section.class).get(0)).elements(Source.class).get(0)).getName());
+ assertEquals("flickr", ((Source)((Section)page.getSection().elements(Section.class).get(0)).elements(Source.class).get(1)).getName());
+ assertEquals(Source.any,((Section)page.getSection().elements(Section.class).get(1)).elements(Source.class).get(0));
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceFooter.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceFooter.xml
new file mode 100644
index 00000000000..9ebdaeb9302
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceFooter.xml
@@ -0,0 +1,6 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="choiceFooter">
+ <choice>
+ <section layout="row" source="popularSearches"/>
+ </choice>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceHeader.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceHeader.xml
new file mode 100644
index 00000000000..36b0ae6430c
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/choiceHeader.xml
@@ -0,0 +1,10 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="choiceHeader">
+ <choice>
+ <section layout="row">
+ <section source="global"/>
+ <section source="notifications"/>
+ </section>
+ <section id="second" source="blog"/>
+ </choice>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/footer.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/footer.xml
new file mode 100644
index 00000000000..0866aaaa583
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/footer.xml
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="footer">
+ <section layout="row" source="popularSearches"/>
+ <section id="extraFooter" layout="row" source="topArticles"/>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/generic.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/generic.xml
new file mode 100644
index 00000000000..319f3058d24
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/generic.xml
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="generic">
+ <section source="image flickr"/>
+ <section source="*"/>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/header.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/header.xml
new file mode 100644
index 00000000000..a894e8b9a3e
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/header.xml
@@ -0,0 +1,7 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="header">
+ <section layout="row">
+ <section source="global"/>
+ <section source="notifications"/>
+ </section>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/includer.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/includer.xml
new file mode 100644
index 00000000000..6d4f6121991
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/includer.xml
@@ -0,0 +1,36 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="includer" description="Demonstrates the various include cases">
+ <section id="case1" description="No choices">
+ <include idref="header"/>
+ <include idref="footer"/>
+ </section>
+ <section id="case2" description="Include as implicit alternatives">
+ <choice>
+ <include idref="header"/>
+ <include idref="footer"/>
+ </choice>
+ </section>
+ <section id="case3" description="Include as explicit alternatives - same result as above">
+ <choice>
+ <alternative><include idref="header"/></alternative>
+ <alternative><include idref="footer"/></alternative>
+ </choice>
+ </section>
+ <section id="case4" description="Mixed with un-included">
+ <choice>
+ <section id="first" source="music"/>
+ <alternative><include idref="header"/></alternative>
+ <section id="middle" source="video"/>
+ <alternative><include idref="footer"/></alternative>
+ <section id="last" source="books"/>
+ </choice>
+ </section>
+ <section id="case5" description="Including two alternatives">
+ <include idref="choiceHeader"/>
+ </section>
+ <section id="case6" description="Including one choice">
+ <choice>
+ <include idref="choiceFooter"/>
+ </choice>
+ </section>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/invalidfilename/invalid.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/invalidfilename/invalid.xml
new file mode 100644
index 00000000000..0e799a472de
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/invalidfilename/invalid.xml
@@ -0,0 +1,4 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="notinvalid">
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/mapexamples/map1.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/mapexamples/map1.xml
new file mode 100644
index 00000000000..c13fdcdbbca
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/mapexamples/map1.xml
@@ -0,0 +1,21 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="map1" layout="column" description="Contains 4 boxes, to which 4 sources may be added">
+
+ <section layout="row" description="row 1">
+ <section id="box1"><placeholder id="box1source"/></section>
+ <section id="box2"><placeholder id="box2source"/></section>
+ </section>
+ <section layout="row" description="row 2">
+ <section id="box3"><placeholder id="box3source"/></section>
+ <section id="box4"><placeholder id="box4source"/></section>
+ </section>
+
+ <choice>
+ <map to="box1source box2source box3source box4source">
+ <source name="source1"/>
+ <source name="source2"/>
+ <source name="source3"/>
+ <source name="source4"/>
+ </map>
+ </choice>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richSerp.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richSerp.xml
new file mode 100644
index 00000000000..32ab6086b82
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richSerp.xml
@@ -0,0 +1,17 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="richSerp" layout="mainAndRight">
+ <section layout="row" region="main">
+ <section layout="column" description="left main pane">
+ <section layout="row" max="5" description="Bar of images, from one of two possible sources">
+ <choice method="annealing">
+ <source name="images"/>
+ <source name="flickr"/>
+ </choice>
+ </section>
+ <section max="1" source="local map video ticker weather" description="A single relevant graphically rich element"/>
+ <section order="-[rank]" max="10" source="web news" description="Various kinds of traditional search results"/>
+ </section>
+ <section layout="column" order="[source]" source="answers blogs twitter" description="right main pane, ugc stuff, grouped by source"/>
+ </section>
+ <section layout="column" source="ads" region="right"/>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richerSerp.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richerSerp.xml
new file mode 100644
index 00000000000..d3e11288ef1
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/richerSerp.xml
@@ -0,0 +1,45 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="richerSerp" layout="column">
+ <include idref="header"/>
+ <section layout="mainAndRight">
+ <section layout="row" region="main">
+ <section layout="column" description="left main pane">
+ <choice>
+ <alternative>
+ <section layout="row" max="5" description="Bar of images, from one of two possible sources">
+ <choice>
+ <source name="images"/>
+ <alternative>
+ <source name="flickr">
+ <renderer name="mouseOverImage"/>
+ </source>
+ <source name="twitpic">
+ <choice>
+ <renderer name="mouseOverImage">
+ <parameter name="hovertime">5</parameter>
+ <parameter name="borderColor">#ff00ff</parameter>
+ </renderer>
+ <renderer name="regularImage"/>
+ </choice>
+ <parameter name="filter">origin=twitter</parameter>
+ </source>
+ </alternative>
+ </choice>
+ <choice>
+ <renderer name="regularImageBox"/>
+ <renderer name="newImageBox"/>
+ </choice>
+ </section>
+ <section max="1" source="local map video ticker weather" description="A single relevant graphically rich element"/>
+ </alternative>
+ <section order="[source]" max="10" source="web news" description="Various kinds of traditional search results"/>
+ </choice>
+ </section>
+ <section layout="column" order="[source]" source="answers blogs twitter" description="right main pane, ugc stuff, grouped by source"/>
+ </section>
+ <section layout="column" source="ads" region="right" order="-[rank] clickProbability">
+ <renderer name="newAdBox"/>
+ </section>
+ </section>
+ <include idref="footer"/>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/serp.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/serp.xml
new file mode 100644
index 00000000000..194c551f84c
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/serp.xml
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="serp" layout="mainAndRight">
+ <section layout="column" region="main" source="web"/>
+ <section layout="column" region="right" source="ads"/>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/slottingSerp.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/slottingSerp.xml
new file mode 100644
index 00000000000..301f7e77edb
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/config/test/examples/slottingSerp.xml
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="slottingSerp" layout="mainAndRight">
+ <section layout="column" region="main" source="*" order="-[rank]"/>
+ <section layout="column" region="right" source="ads"/>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySource.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySource.xml
new file mode 100644
index 00000000000..4a5b6b3a1dd
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySource.xml
@@ -0,0 +1,4 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="AnySource" source="source3 *">
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceResult.xml
new file mode 100644
index 00000000000..40cc6b6935e
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceResult.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0">
+
+ <hit relevance="1.0" source="source3">
+ <id>source3-1</id>
+ </hit>
+
+ <hit relevance="1.0" source="source1">
+ <id>source1-1</id>
+ </hit>
+
+ <hit relevance="1.0" source="source2">
+ <id>source2-1</id>
+ </hit>
+
+ <hit relevance="0.5" source="source3">
+ <id>source3-2</id>
+ </hit>
+
+ <hit relevance="0.5" source="source1">
+ <id>source1-2</id>
+ </hit>
+
+ <hit relevance="0.5" source="source2">
+ <id>source2-2</id>
+ </hit>
+
+ <hit relevance="0.3333333333333333" source="source3">
+ <id>source3-3</id>
+ </hit>
+
+ <hit relevance="0.3333333333333333" source="source1">
+ <id>source1-3</id>
+ </hit>
+
+ <hit relevance="0.3333333333333333" source="source2">
+ <id>source2-3</id>
+ </hit>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceTestCase.java
new file mode 100644
index 00000000000..dc20e5483ca
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/AnySourceTestCase.java
@@ -0,0 +1,59 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.result.HitGroup;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author bratseth
+ */
+public class AnySourceTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("AnySource.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source2",3));
+ result.hits().add(createHits("source3",3));
+
+ // Resolve (noop here)
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check execution:
+ // all three sources, ordered by relevance, source 3 first in each relevance group
+ HitGroup hits=result.hits();
+ assertEquals(9,hits.size());
+ assertEquals("source3-1",hits.get(0).getId().stringValue());
+ assertEquals("source1-1",hits.get(1).getId().stringValue());
+ assertEquals("source2-1",hits.get(2).getId().stringValue());
+ assertEquals("source3-2",hits.get(3).getId().stringValue());
+ assertEquals("source1-2",hits.get(4).getId().stringValue());
+ assertEquals("source2-2",hits.get(5).getId().stringValue());
+ assertEquals("source3-3",hits.get(6).getId().stringValue());
+ assertEquals("source1-3",hits.get(7).getId().stringValue());
+ assertEquals("source2-3",hits.get(8).getId().stringValue());
+
+ // Check rendering
+ assertRendered(result,"AnySourceResult.xml");
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderers.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderers.xml
new file mode 100644
index 00000000000..e0306872149
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderers.xml
@@ -0,0 +1,17 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="ChoiceOfRenderers" source="source1 source2">
+
+ <choice>
+ <renderer name="sectionLook1"/>
+ <renderer name="sectionLook2"/>
+ </choice>
+ <choice>
+ <renderer name="source1Look1" for="source1"/>
+ <renderer name="source1Look2" for="source1"/>
+ <renderer name="source1Look3" for="source1">
+ <parameter name="color">#ff00ff</parameter>
+ <parameter name="blink">true</parameter>
+ </renderer>
+ </choice>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersResult.xml
new file mode 100644
index 00000000000..47ed1bd2f12
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersResult.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0">
+
+ <renderer name="sectionLook2"/>
+
+ <renderer for="source1" name="source1Look3">
+ <parameter name="color">#ff00ff</parameter>
+ <parameter name="blink">true</parameter>
+ </renderer>
+
+ <hit relevance="1.0" source="source1">
+ <id>source1-1</id>
+ </hit>
+
+ <hit relevance="1.0" source="source2">
+ <id>source2-1</id>
+ </hit>
+
+ <hit relevance="0.5" source="source1">
+ <id>source1-2</id>
+ </hit>
+
+ <hit relevance="0.5" source="source2">
+ <id>source2-2</id>
+ </hit>
+
+ <hit relevance="0.3333333333333333" source="source1">
+ <id>source1-3</id>
+ </hit>
+
+ <hit relevance="0.3333333333333333" source="source2">
+ <id>source2-3</id>
+ </hit>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersTestCase.java
new file mode 100644
index 00000000000..58d05971805
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfRenderersTestCase.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.pagetemplates.model.Renderer;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ChoiceOfRenderersTestCase extends ExecutionAbstractTestCase {
+
+ //This test is order dependent. Fix this!!
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("ChoiceOfRenderers.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source2",3));
+ result.hits().add(createHits("source3",3));
+
+ // Resolve
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+ assertEquals(1,resolution.getResolution((Choice)((PageTemplate)page.get(0).get(0)).getSection().elements(Renderer.class).get(0)));
+ assertEquals(2,resolution.getResolution((Choice)((PageTemplate)page.get(0).get(0)).getSection().elements(Renderer.class).get(1)));
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ assertEquals(6,result.getConcreteHitCount());
+ assertEquals(6,result.getHitCount());
+
+ // Check rendering
+ assertRendered(result,"ChoiceOfRenderersResult.xml");
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsections.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsections.xml
new file mode 100644
index 00000000000..f7323ba094d
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsections.xml
@@ -0,0 +1,20 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="ChoiceOfSubsections">
+ <choice method="method1">
+ <alternative>
+ <section source="source0"/>
+ </alternative>
+ <alternative>
+ <section source="source1"/>
+ </alternative>
+ <alternative>
+ <section source="source2"/>
+ <section>
+ <choice method="method2">
+ <source name="source3"/>
+ <source name="source4"/>
+ </choice>
+ </section>
+ </alternative>
+ </choice>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsResult.xml
new file mode 100644
index 00000000000..b1d29995312
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsResult.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0">
+
+ <section>
+ <hit relevance="1.0" source="source2">
+ <id>source2-1</id>
+ </hit>
+ <hit relevance="0.5" source="source2">
+ <id>source2-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source2">
+ <id>source2-3</id>
+ </hit>
+ </section>
+
+ <section>
+ <hit relevance="1.0" source="source4">
+ <id>source4-1</id>
+ </hit>
+ <hit relevance="0.5" source="source4">
+ <id>source4-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source4">
+ <id>source4-3</id>
+ </hit>
+ </section>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsTestCase.java
new file mode 100644
index 00000000000..3d92a721f0d
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfSubsectionsTestCase.java
@@ -0,0 +1,68 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.result.HitGroup;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ChoiceOfSubsectionsTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("ChoiceOfSubsections.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source2",3));
+ result.hits().add(createHits("source3",3));
+ result.hits().add(createHits("source4",3));
+
+ new Organizer().organize(page,new DeterministicResolverAssertingMethod().resolve(page,query,result),result);
+
+ // Check execution:
+ // Two subsections with one source each
+ assertEquals(2,result.hits().size());
+ HitGroup section1=(HitGroup)result.hits().get(0);
+ HitGroup section2=(HitGroup)result.hits().get(1);
+ assertEqualHitGroups(createHits("source2",3),section1);
+ assertEqualHitGroups(createHits("source4",3),section2);
+
+ // Check rendering
+ assertRendered(result,"ChoiceOfSubsectionsResult.xml");
+ }
+
+ /** Same as deterministic resolver, but asserts that it received the correct method names for each choice */
+ private static class DeterministicResolverAssertingMethod extends DeterministicResolver {
+
+ private int invocationNumber=0;
+
+ /** Chooses the last alternative of any choice */
+ @Override
+ public void resolve(Choice choice, Query query, Result result, Resolution resolution) {
+ invocationNumber++;
+ if (invocationNumber==2)
+ assertEquals("method1",choice.getMethod());
+ else if (invocationNumber==3)
+ assertEquals("method2",choice.getMethod());
+ else if (invocationNumber>3)
+ throw new IllegalStateException("Unexpected number of resolver invocations");
+
+ super.resolve(choice,query,result,resolution);
+ }
+
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSources.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSources.xml
new file mode 100644
index 00000000000..c6a0af9ddd2
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSources.xml
@@ -0,0 +1,7 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="ChoiceOfTwoSources">
+ <choice>
+ <source name="source1"/>
+ <source name="source2"/>
+ </choice>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesResult.xml
new file mode 100644
index 00000000000..35d913fd9fa
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesResult.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0">
+
+ <hit relevance="1.0" source="source2">
+ <id>source2-1</id>
+ </hit>
+
+ <hit relevance="0.5" source="source2">
+ <id>source2-2</id>
+ </hit>
+
+ <hit relevance="0.3333333333333333" source="source2">
+ <id>source2-3</id>
+ </hit>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesTestCase.java
new file mode 100644
index 00000000000..facffb50649
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoiceOfTwoSourcesTestCase.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.pagetemplates.model.Source;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ChoiceOfTwoSourcesTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("ChoiceOfTwoSources.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source2",3));
+ result.hits().add(createHits("source3",3));
+
+ // Resolve
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+ assertEquals(1,resolution.getResolution((Choice)((PageTemplate)page.get(0).get(0)).getSection().elements(Source.class).get(0)));
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check execution:
+ // No subsections: Last choice (source2) used
+ assertEqualHitGroups(createHits("source2",3),result.hits());
+
+ // Check rendering
+ assertRendered(result,"ChoiceOfTwoSourcesResult.xml");
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Choices.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Choices.xml
new file mode 100644
index 00000000000..e8d1736f46c
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Choices.xml
@@ -0,0 +1,45 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="Choices">
+ <choice>
+
+ <alternative>
+ <section layout="row">
+ <section id="realtime">
+ <choice>
+ <source name="news"/>
+ <source name="blog"/>
+ </choice>
+ </section>
+ <section source="images" max="2" id="multimedia"/>
+ <section source="web" id="web"/>
+ </section>
+ </alternative>
+
+ <alternative>
+ <section source="*" id="blended"/>
+ </alternative>
+
+ <alternative>
+ <section layout="row" description="row 1">
+ <section id="box1"><placeholder id="box1source"/></section>
+ <section id="box2"><placeholder id="box2source"/></section>
+ </section>
+ <section layout="row" description="row 2">
+ <section id="box3"><placeholder id="box3source"/></section>
+ <section id="box4"><placeholder id="box4source"/></section>
+ </section>
+
+ <choice method="myMethod">
+ <map to="box1source box2source box3source box4source">
+ <source name="news"/>
+ <source name="web"/>
+ <source name="blog"/>
+ <source name="images"/>
+ </map>
+ </choice>
+
+ </alternative>
+
+ </choice>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesResult.xml
new file mode 100644
index 00000000000..ab995365f22
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesResult.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0">
+
+ <section layout="row">
+ <section id="section:box1">
+ <hit relevance="1.0" source="news">
+ <id>news-1</id>
+ </hit>
+ <hit relevance="0.5" source="news">
+ <id>news-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="news">
+ <id>news-3</id>
+ </hit>
+ </section>
+ <section id="section:box2">
+ <hit relevance="1.0" source="web">
+ <id>web-1</id>
+ </hit>
+ <hit relevance="0.5" source="web">
+ <id>web-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="web">
+ <id>web-3</id>
+ </hit>
+ </section>
+ </section>
+
+ <section layout="row">
+ <section id="section:box3">
+ <hit relevance="1.0" source="blog">
+ <id>blog-1</id>
+ </hit>
+ <hit relevance="0.5" source="blog">
+ <id>blog-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="blog">
+ <id>blog-3</id>
+ </hit>
+ </section>
+ <section id="section:box4">
+ <hit relevance="1.0" source="images">
+ <id>images-1</id>
+ </hit>
+ <hit relevance="0.5" source="images">
+ <id>images-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="images">
+ <id>images-3</id>
+ </hit>
+ </section>
+ </section>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesTestCase.java
new file mode 100644
index 00000000000..a646823c8cb
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ChoicesTestCase.java
@@ -0,0 +1,50 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.pagetemplates.model.PageElement;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ChoicesTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template (second alternative will be chosen)
+ List<PageElement> pages=new ArrayList<>();
+ pages.add(importPage("AnySource.xml"));
+ pages.add(importPage("Choices.xml"));
+ Choice page=Choice.createSingletons(pages);
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("news",3));
+ result.hits().add(createHits("web",3));
+ result.hits().add(createHits("blog",3));
+ result.hits().add(createHits("images",3));
+
+ // Resolve
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check rendering
+ assertRendered(result,"ChoicesResult.xml");
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java
new file mode 100644
index 00000000000..544366758f3
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/ExecutionAbstractTestCase.java
@@ -0,0 +1,74 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.io.IOUtils;
+import com.yahoo.prelude.templates.TiledTemplateSet;
+import com.yahoo.prelude.templates.UserTemplate;
+import com.yahoo.prelude.templates.test.TilingTestCase;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.config.PageTemplateXMLReader;
+import com.yahoo.search.result.Hit;
+import com.yahoo.search.result.HitGroup;
+
+import java.io.*;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author bratseth
+ */
+public class ExecutionAbstractTestCase {
+
+ private static final String root="src/test/java/com/yahoo/search/pagetemplates/engine/test/";
+
+ protected PageTemplate importPage(String name) {
+ PageTemplate template=new PageTemplateXMLReader().readFile(root + name);
+ assertNotNull("Could look up page template '" + name + "'",template);
+ return template;
+ }
+
+ protected void assertEqualHitGroups(HitGroup expected,HitGroup actual) {
+ assertEquals(expected.size(),actual.size());
+ int i=0;
+ for (Hit expectedHit : expected.asList()) {
+ Hit actualHit=actual.get(i++);
+ assertEquals(expectedHit.getId(),actualHit.getId());
+ assertEquals(expectedHit.getSource(),actualHit.getSource());
+ }
+ }
+
+ protected HitGroup createHits(String sourceName,int hitCount) {
+ HitGroup source=new HitGroup("source:" + sourceName);
+ for (int i=1; i<=hitCount; i++) {
+ Hit hit=new Hit(sourceName + "-" + i,1/(double)i);
+ hit.setSource(sourceName);
+ source.add(hit);
+ }
+ return source;
+ }
+
+ protected void assertRendered(Result result,String resultFileName) {
+ assertRendered(result,resultFileName,false);
+ }
+
+ protected void assertRendered(Result result,String resultFileName, UserTemplate<?> template) {
+ assertRendered(result,resultFileName,template,false);
+ }
+
+ protected void assertRendered(Result result,String resultFileName,boolean print) {
+ assertRendered(result,resultFileName,new TiledTemplateSet(),print);
+ }
+
+ @SuppressWarnings("deprecation")
+ protected void assertRendered(Result result,String resultFileName,UserTemplate<?> template, boolean print) {
+ result.getTemplating().setTemplates(template);
+ try {
+ TilingTestCase.assertRendered(IOUtils.readFile(new File(root + resultFileName)), result);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSections.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSections.xml
new file mode 100644
index 00000000000..2bc75fba5f4
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSections.xml
@@ -0,0 +1,28 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="MapSectionsToSections" layout="column" description="Contains 4 boxes, to which 4 sections are mapped">
+
+ <section layout="row" description="row 1">
+ <placeholder id="box1holder"/>
+ <placeholder id="box2holder"/>
+ </section>
+ <section layout="row" description="row 2">
+ <placeholder id="box3holder"/>
+ <placeholder id="box4holder"/>
+ </section>
+
+ <choice method="myMethod">
+ <map to="box1holder box2holder box3holder box4holder">
+ <section id="box1" source="source1"/>
+ <section id="box2" source="source2"/>
+ <item>
+ <section id="box3" source="source3"/>
+ <section id="box5" source="source5"/>
+ </item>
+ <section id="box4" source="source4"/>
+ </map>
+ </choice>
+
+ <choice> <!-- Empty choices should have no effect -->
+ </choice>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsResult.xml
new file mode 100644
index 00000000000..3a163e5f804
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsResult.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0" layout="column">
+
+ <section layout="row">
+ <section id="section:box1">
+ <hit relevance="1.0" source="source1">
+ <id>source1-1</id>
+ </hit>
+ <hit relevance="0.5" source="source1">
+ <id>source1-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source1">
+ <id>source1-3</id>
+ </hit>
+ </section>
+ <section id="section:box2">
+ <hit relevance="1.0" source="source2">
+ <id>source2-1</id>
+ </hit>
+ <hit relevance="0.5" source="source2">
+ <id>source2-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source2">
+ <id>source2-3</id>
+ </hit>
+ <hit relevance="0.25" source="source2">
+ <id>source2-4</id>
+ </hit>
+ </section>
+ </section>
+
+ <section layout="row">
+ <section id="section:box3">
+ <hit relevance="1.0" source="source3">
+ <id>source3-1</id>
+ </hit>
+ <hit relevance="0.5" source="source3">
+ <id>source3-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source3">
+ <id>source3-3</id>
+ </hit>
+ <hit relevance="0.25" source="source3">
+ <id>source3-4</id>
+ </hit>
+ <hit relevance="0.2" source="source3">
+ <id>source3-5</id>
+ </hit>
+ </section>
+ <section id="section:box5">
+ <hit relevance="1.0" source="source5">
+ <id>source5-1</id>
+ </hit>
+ <hit relevance="0.5" source="source5">
+ <id>source5-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source5">
+ <id>source5-3</id>
+ </hit>
+ <hit relevance="0.25" source="source5">
+ <id>source5-4</id>
+ </hit>
+ <hit relevance="0.2" source="source5">
+ <id>source5-5</id>
+ </hit>
+ <hit relevance="0.1666666666666667" source="source5">
+ <id>source5-6</id>
+ </hit>
+ <hit relevance="0.1428571428571428" source="source5">
+ <id>source5-7</id>
+ </hit>
+ </section>
+ <section id="section:box4">
+ <hit relevance="1.0" source="source4">
+ <id>source4-1</id>
+ </hit>
+ <hit relevance="0.5" source="source4">
+ <id>source4-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source4">
+ <id>source4-3</id>
+ </hit>
+ <hit relevance="0.25" source="source4">
+ <id>source4-4</id>
+ </hit>
+ <hit relevance="0.2" source="source4">
+ <id>source4-5</id>
+ </hit>
+ <hit relevance="0.1666666666666667" source="source4">
+ <id>source4-6</id>
+ </hit>
+ </section>
+ </section>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsTestCase.java
new file mode 100644
index 00000000000..54fc342aa22
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSectionsToSectionsTestCase.java
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.pagetemplates.model.MapChoice;
+import com.yahoo.search.pagetemplates.model.PageElement;
+import com.yahoo.search.pagetemplates.model.Section;
+import com.yahoo.search.result.HitGroup;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class MapSectionsToSectionsTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("MapSectionsToSections.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source2",4));
+ result.hits().add(createHits("source3",5));
+ result.hits().add(createHits("source4",6));
+ result.hits().add(createHits("source5",7));
+
+ // Resolve
+ Resolver resolver=new DeterministicResolverAssertingMethod();
+ Resolution resolution=resolver.resolve(page,query,result);
+ Map<String, List<PageElement>> mapping=
+ resolution.getResolution((MapChoice)((PageTemplate)page.get(0).get(0)).getSection().elements().get(2));
+ assertNotNull(mapping);
+ assertEquals("box1",((Section)mapping.get("box1holder").get(0)).getId());
+ assertEquals("box2",((Section)mapping.get("box2holder").get(0)).getId());
+ assertEquals("box3",((Section)mapping.get("box3holder").get(0)).getId());
+ assertEquals("box4",((Section)mapping.get("box4holder").get(0)).getId());
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check execution:
+ // Two subsections, each containing two sub-subsections with one source each
+ assertEquals(2,result.hits().size());
+ HitGroup row1=(HitGroup)result.hits().get(0);
+ HitGroup column11=(HitGroup)row1.get(0);
+ HitGroup column12=(HitGroup)row1.get(1);
+ HitGroup row2=(HitGroup)result.hits().get(1);
+ HitGroup column21a=(HitGroup)row2.get(0);
+ HitGroup column21b=(HitGroup)row2.get(1);
+ HitGroup column22=(HitGroup)row2.get(2);
+ assertEqualHitGroups(createHits("source1",3),column11);
+ assertEqualHitGroups(createHits("source2",4),column12);
+ assertEqualHitGroups(createHits("source3",5),column21a);
+ assertEqualHitGroups(createHits("source5",7),column21b);
+ assertEqualHitGroups(createHits("source4",6),column22);
+
+ // Check rendering
+ assertRendered(result,"MapSectionsToSectionsResult.xml");
+ }
+
+ /** Same as deterministic resolver, but asserts that it received the correct method names for each map choice */
+ private static class DeterministicResolverAssertingMethod extends DeterministicResolver {
+
+ /** Chooses the last alternative of any choice */
+ @Override
+ public void resolve(MapChoice mapChoice, Query query, Result result, Resolution resolution) {
+ assertEquals("myMethod",mapChoice.getMethod());
+ super.resolve(mapChoice,query,result,resolution);
+ }
+
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSections.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSections.xml
new file mode 100644
index 00000000000..7b5c0770096
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSections.xml
@@ -0,0 +1,22 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="MapSourcesToSections" layout="column" description="4 sources are assigned to a section each">
+
+ <section layout="row" description="row 1">
+ <section id="box1"><placeholder id="box1source"/></section>
+ <section id="box2"><placeholder id="box2source"/></section>
+ </section>
+ <section layout="row" description="row 2">
+ <section id="box3"><placeholder id="box3source"/></section>
+ <section id="box4"><placeholder id="box4source"/></section>
+ </section>
+
+ <choice method="myMethod">
+ <map to="box1source box2source box3source box4source">
+ <source name="source1"/>
+ <source name="source2"/>
+ <source name="source3"/>
+ <source name="source4"/>
+ </map>
+ </choice>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsResult.xml
new file mode 100644
index 00000000000..034330c071c
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsResult.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0" layout="column">
+
+ <section layout="row">
+ <section id="section:box1">
+ <hit relevance="1.0" source="source1">
+ <id>source1-1</id>
+ </hit>
+ <hit relevance="0.5" source="source1">
+ <id>source1-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source1">
+ <id>source1-3</id>
+ </hit>
+ </section>
+ <section id="section:box2">
+ <hit relevance="1.0" source="source2">
+ <id>source2-1</id>
+ </hit>
+ <hit relevance="0.5" source="source2">
+ <id>source2-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source2">
+ <id>source2-3</id>
+ </hit>
+ <hit relevance="0.25" source="source2">
+ <id>source2-4</id>
+ </hit>
+ </section>
+ </section>
+
+ <section layout="row">
+ <section id="section:box3">
+ <hit relevance="1.0" source="source3">
+ <id>source3-1</id>
+ </hit>
+ <hit relevance="0.5" source="source3">
+ <id>source3-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source3">
+ <id>source3-3</id>
+ </hit>
+ <hit relevance="0.25" source="source3">
+ <id>source3-4</id>
+ </hit>
+ <hit relevance="0.2" source="source3">
+ <id>source3-5</id>
+ </hit>
+ </section>
+ <section id="section:box4">
+ <hit relevance="1.0" source="source4">
+ <id>source4-1</id>
+ </hit>
+ <hit relevance="0.5" source="source4">
+ <id>source4-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source4">
+ <id>source4-3</id>
+ </hit>
+ <hit relevance="0.25" source="source4">
+ <id>source4-4</id>
+ </hit>
+ <hit relevance="0.2" source="source4">
+ <id>source4-5</id>
+ </hit>
+ <hit relevance="0.1666666666666667" source="source4">
+ <id>source4-6</id>
+ </hit>
+ </section>
+ </section>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsTestCase.java
new file mode 100644
index 00000000000..49cc0411ac5
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/MapSourcesToSectionsTestCase.java
@@ -0,0 +1,88 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.pagetemplates.model.MapChoice;
+import com.yahoo.search.pagetemplates.model.PageElement;
+import com.yahoo.search.pagetemplates.model.Source;
+import com.yahoo.search.result.HitGroup;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class MapSourcesToSectionsTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("MapSourcesToSections.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source2",4));
+ result.hits().add(createHits("source3",5));
+ result.hits().add(createHits("source4",6));
+ result.hits().add(createHits("source5",7));
+
+ // Resolve
+ Resolver resolver=new DeterministicResolverAssertingMethod();
+ Resolution resolution=resolver.resolve(page,query,result);
+ Map<String, List<PageElement>> mapping=
+ resolution.getResolution((MapChoice)((PageTemplate)page.get(0).get(0)).getSection().elements().get(2));
+ assertNotNull(mapping);
+ assertEquals("source1",((Source)mapping.get("box1source").get(0)).getName());
+ assertEquals("source2",((Source)mapping.get("box2source").get(0)).getName());
+ assertEquals("source3",((Source)mapping.get("box3source").get(0)).getName());
+ assertEquals("source4",((Source)mapping.get("box4source").get(0)).getName());
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check execution:
+ // Two subsections, each containing two sub-subsections with one source each
+ assertEquals(2,result.hits().size());
+ HitGroup row1=(HitGroup)result.hits().get(0);
+ HitGroup column11=(HitGroup)row1.get(0);
+ HitGroup column12=(HitGroup)row1.get(1);
+ HitGroup row2=(HitGroup)result.hits().get(1);
+ HitGroup column21=(HitGroup)row2.get(0);
+ HitGroup column22=(HitGroup)row2.get(1);
+ assertEqualHitGroups(createHits("source1",3),column11);
+ assertEqualHitGroups(createHits("source2",4),column12);
+ assertEqualHitGroups(createHits("source3",5),column21);
+ assertEqualHitGroups(createHits("source4",6),column22);
+
+ // Check rendering
+ assertRendered(result,"MapSourcesToSectionsResult.xml");
+ }
+
+ /** Same as deterministic resolver, but asserts that it received the correct method names for each map choice */
+ private static class DeterministicResolverAssertingMethod extends DeterministicResolver {
+
+ /** Chooses the last alternative of any choice */
+ @Override
+ public void resolve(MapChoice mapChoice, Query query, Result result, Resolution resolution) {
+ assertEquals("myMethod",mapChoice.getMethod());
+ super.resolve(mapChoice,query,result,resolution);
+ }
+
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Page.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Page.xml
new file mode 100644
index 00000000000..967da527b6e
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/Page.xml
@@ -0,0 +1,31 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="Page">
+
+ <renderer name="two-column"/>
+
+ <section region="left">
+ <source url="http://carmot.yahoo.com:4080/resource/[news article id]"/>
+ <renderer name="articleBodyRenderer">
+ <parameter name="color">blue</parameter>
+ </renderer>
+ </section>
+
+ <section region="right">
+ <renderer name="multi-item-column">
+ <parameter name="items">3</parameter>
+ </renderer>
+
+ <section region="1" source="news">
+ <renderer name="articleListRenderer"/>
+ </section>
+
+ <section region="2">
+ <source url="http://vitality.yahoo.com:4080/consumption-widget"/>
+ <renderer name="identityRenderer"/>
+ </section>
+
+ <section region="3" source="htmlSource">
+ <renderer name="htmlRenderer"/>
+ </section>
+ </section>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageResult.xml
new file mode 100644
index 00000000000..95b86ef1f4d
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageResult.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page version="1.0">
+
+ <renderer name="two-column"/>
+
+ <section region="left">
+ <source url="http://carmot.yahoo.com:4080/resource/[news article id]"/>
+ <renderer name="articleBodyRenderer">
+ <parameter name="color">blue</parameter>
+ </renderer>
+ </section>
+
+ <section region="right">
+ <renderer name="multi-item-column">
+ <parameter name="items">3</parameter>
+ </renderer>
+ <section region="1">
+ <renderer name="articleListRenderer"/>
+ <content>
+ <hit relevance="1.0" source="news">
+ <id>news-1</id>
+ </hit>
+ <hit relevance="0.5" source="news">
+ <id>news-2</id>
+ </hit>
+ </content>
+ </section>
+ <section region="2">
+ <source url="http://vitality.yahoo.com:4080/consumption-widget"/>
+ <renderer name="identityRenderer"/>
+ </section>
+ <section region="3">
+ <renderer name="htmlRenderer"/>
+ <content>
+ <hit relevance="1.0" source="htmlSource">
+ <id>htmlSource-1</id>
+ </hit>
+ </content>
+ </section>
+ </section>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageTestCase.java
new file mode 100644
index 00000000000..33930f3d0e0
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageTestCase.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.prelude.templates.PageTemplateSet;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import org.junit.Test;
+
+/**
+ * Tests an example page.
+ *
+ * @author bratseth
+ */
+public class PageTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("Page.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("news",2));
+ result.hits().add(createHits("htmlSource",1));
+
+ // Resolve (noop here)
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check rendering
+ assertRendered(result,"PageResult.xml",new PageTemplateSet());
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlending.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlending.xml
new file mode 100644
index 00000000000..dca31eb42e1
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlending.xml
@@ -0,0 +1,37 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="PageWithBlending">
+
+ <renderer name="two-column"/>
+
+ <section region="left">
+ <source url="http://carmot.yahoo.com:4080/resource/[news article id]"/>
+ <renderer name="articleBodyRenderer">
+ <parameter name="color">blue</parameter>
+ </renderer>
+ </section>
+
+ <section region="right">
+ <renderer name="multi-item-column">
+ <parameter name="items">3</parameter>
+ </renderer>
+
+ <section region="1">
+ <renderer for="newsImage" name="newsImageRenderer"/>
+ <source name="news">
+ <renderer name="articleRenderer"/>
+ </source>
+ <source name="image">
+ <renderer name="imageRenderer"/>
+ </source>
+ </section>
+
+ <section region="2">
+ <source url="http://vitality.yahoo.com:4080/consumption-widget"/>
+ <renderer name="identityRenderer"/>
+ </section>
+
+ <section region="3" source="htmlSource">
+ <renderer name="htmlRenderer"/>
+ </section>
+ </section>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingResult.xml
new file mode 100644
index 00000000000..7ac78f3e820
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingResult.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page version="1.0">
+
+ <renderer name="two-column"/>
+
+ <section region="left">
+ <source url="http://carmot.yahoo.com:4080/resource/[news article id]"/>
+ <renderer name="articleBodyRenderer">
+ <parameter name="color">blue</parameter>
+ </renderer>
+ </section>
+
+ <section region="right">
+ <renderer name="multi-item-column">
+ <parameter name="items">3</parameter>
+ </renderer>
+ <section region="1">
+ <renderer for="newsImage" name="newsImageRenderer"/>
+ <renderer for="news" name="articleRenderer"/>
+ <renderer for="image" name="imageRenderer"/>
+ <content>
+ <hit relevance="1.0" source="news">
+ <id>news-1</id>
+ </hit>
+ <hit relevance="0.5" source="news">
+ <id>news-2</id>
+ </hit>
+ </content>
+ </section>
+ <section region="2">
+ <source url="http://vitality.yahoo.com:4080/consumption-widget"/>
+ <renderer name="identityRenderer"/>
+ </section>
+ <section region="3">
+ <renderer name="htmlRenderer"/>
+ <content>
+ <hit relevance="1.0" source="htmlSource">
+ <id>htmlSource-1</id>
+ </hit>
+ </content>
+ </section>
+ </section>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingTestCase.java
new file mode 100644
index 00000000000..445105cfd2f
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithBlendingTestCase.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.prelude.templates.PageTemplateSet;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import org.junit.Test;
+
+/**
+ * Tests an exapnded example.
+ *
+ * @author bratseth
+ */
+public class PageWithBlendingTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("PageWithBlending.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("news",2));
+ result.hits().add(createHits("htmlSource",1));
+
+ // Resolve (noop here)
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check rendering
+ assertRendered(result,"PageWithBlendingResult.xml",new PageTemplateSet());
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRenderer.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRenderer.xml
new file mode 100644
index 00000000000..5d3e38c2beb
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRenderer.xml
@@ -0,0 +1,36 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="PageWithSourceRenderer">
+
+ <renderer name="two-column"/>
+
+ <section region="left">
+ <choice>
+ <source url="notchosen"/>
+ <source url="http://carmot.yahoo.com:4080/resource/[news article id]"/>
+ </choice>
+ <renderer name="articleBodyRenderer">
+ <parameter name="color">blue</parameter>
+ </renderer>
+ </section>
+
+ <section region="right">
+ <renderer name="multi-item-column">
+ <parameter name="items">3</parameter>
+ </renderer>
+
+ <section region="1">
+ <source name="news">
+ <renderer name="articleRenderer"/>
+ </source>
+ </section>
+
+ <section region="2">
+ <source url="http://vitality.yahoo.com:4080/consumption-widget"/>
+ <renderer name="identityRenderer"/>
+ </section>
+
+ <section region="3" source="htmlSource">
+ <renderer name="htmlRenderer"/>
+ </section>
+ </section>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererResult.xml
new file mode 100644
index 00000000000..00656399331
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererResult.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page version="1.0">
+
+ <renderer name="two-column"/>
+
+ <section region="left">
+ <source url="http://carmot.yahoo.com:4080/resource/[news article id]"/>
+ <renderer name="articleBodyRenderer">
+ <parameter name="color">blue</parameter>
+ </renderer>
+ </section>
+
+ <section region="right">
+ <renderer name="multi-item-column">
+ <parameter name="items">3</parameter>
+ </renderer>
+ <section region="1">
+ <renderer for="news" name="articleRenderer"/>
+ <content>
+ <hit relevance="1.0" source="news">
+ <id>news-1</id>
+ </hit>
+ <hit relevance="0.5" source="news">
+ <id>news-2</id>
+ </hit>
+ </content>
+ </section>
+ <section region="2">
+ <source url="http://vitality.yahoo.com:4080/consumption-widget"/>
+ <renderer name="identityRenderer"/>
+ </section>
+ <section region="3">
+ <renderer name="htmlRenderer"/>
+ <content>
+ <hit relevance="1.0" source="htmlSource">
+ <id>htmlSource-1</id>
+ </hit>
+ </content>
+ </section>
+ </section>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererTestCase.java
new file mode 100644
index 00000000000..e7dbae21d47
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/PageWithSourceRendererTestCase.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.prelude.templates.PageTemplateSet;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import org.junit.Test;
+
+/**
+ * Tests an example with two data sources with a renderer each.
+ *
+ * @author bratseth
+ */
+public class PageWithSourceRendererTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("PageWithSourceRenderer.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("news",2));
+ result.hits().add(createHits("htmlSource",1));
+
+ // Resolve
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check rendering
+ assertRendered(result,"PageWithSourceRendererResult.xml",new PageTemplateSet());
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoice.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoice.xml
new file mode 100644
index 00000000000..ff39ef1d3d1
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoice.xml
@@ -0,0 +1,7 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="SourceChoice">
+ <choice>
+ <source name="news"/>
+ <source name="web"/>
+ </choice>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceResult.xml
new file mode 100644
index 00000000000..c9e0909a476
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceResult.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page version="1.0">
+
+ <content>
+ <hit relevance="1.0" source="web">
+ <id>web-1</id>
+ </hit>
+ <hit relevance="0.5" source="web">
+ <id>web-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="web">
+ <id>web-3</id>
+ </hit>
+ </content>
+
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceTestCase.java
new file mode 100644
index 00000000000..04e550a631c
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/SourceChoiceTestCase.java
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.prelude.templates.PageTemplateSet;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.Resolver;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class SourceChoiceTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("SourceChoice.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("web",3));
+ result.hits().add(createHits("news",3));
+ result.hits().add(createHits("blog",3));
+
+ // Resolve (noop here)
+ Resolver resolver=new DeterministicResolver();
+ Resolution resolution=resolver.resolve(page,query,result);
+
+ // Execute
+ Organizer organizer =new Organizer();
+ organizer.organize(page,resolution,result);
+
+ // Check rendering
+ assertRendered(result,"SourceChoiceResult.xml",new PageTemplateSet(),true);
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSources.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSources.xml
new file mode 100644
index 00000000000..36cace66ff7
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSources.xml
@@ -0,0 +1,5 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="TwoSectionsFourSources" layout="twoColumns">
+ <section source="source3 source1" order="[source]" max="8" region="left"/>
+ <section source="source4 source2" order="-[rank]" max="10" region="right"/>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesResult.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesResult.xml
new file mode 100644
index 00000000000..0ca5bfc7ea0
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesResult.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<result version="1.0" layout="twoColumns">
+
+ <section region="left">
+ <hit relevance="1.0" source="source3">
+ <id>source3-1</id>
+ </hit>
+ <hit relevance="0.5" source="source3">
+ <id>source3-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source3">
+ <id>source3-3</id>
+ </hit>
+ <hit relevance="0.25" source="source3">
+ <id>source3-4</id>
+ </hit>
+ <hit relevance="0.2" source="source3">
+ <id>source3-5</id>
+ </hit>
+ <hit relevance="1.0" source="source1">
+ <id>source1-1</id>
+ </hit>
+ <hit relevance="0.5" source="source1">
+ <id>source1-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source1">
+ <id>source1-3</id>
+ </hit>
+ </section>
+
+ <section region="right">
+ <hit relevance="1.0" source="source4">
+ <id>source4-1</id>
+ </hit>
+ <hit relevance="1.0" source="source2">
+ <id>source2-1</id>
+ </hit>
+ <hit relevance="0.5" source="source4">
+ <id>source4-2</id>
+ </hit>
+ <hit relevance="0.5" source="source2">
+ <id>source2-2</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source4">
+ <id>source4-3</id>
+ </hit>
+ <hit relevance="0.3333333333333333" source="source2">
+ <id>source2-3</id>
+ </hit>
+ <hit relevance="0.25" source="source4">
+ <id>source4-4</id>
+ </hit>
+ <hit relevance="0.25" source="source2">
+ <id>source2-4</id>
+ </hit>
+ <hit relevance="0.2" source="source4">
+ <id>source4-5</id>
+ </hit>
+ <hit relevance="0.1666666666666667" source="source4">
+ <id>source4-6</id>
+ </hit>
+ </section>
+
+</result>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesTestCase.java
new file mode 100644
index 00000000000..3fff2103332
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/engine/test/TwoSectionsFourSourcesTestCase.java
@@ -0,0 +1,140 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.engine.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.pagetemplates.engine.Organizer;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.result.Hit;
+import com.yahoo.search.result.HitGroup;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class TwoSectionsFourSourcesTestCase extends ExecutionAbstractTestCase {
+
+ @Test
+ public void testExecution() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("TwoSectionsFourSources.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source2",4));
+ result.hits().add(createHits("source3",12));
+ result.hits().add(createHits("source4",13));
+
+ new Organizer().organize(page,new DeterministicResolver().resolve(page,query,result),result);
+
+ // Check execution:
+ // Two subsections with two sources each, the first grouped the second blended
+ assertEquals(2,result.hits().size());
+ HitGroup section1=(HitGroup)result.hits().get(0);
+ HitGroup section2=(HitGroup)result.hits().get(1);
+ assertGroupedSource3Source1(section1.asList());
+ assertBlendedSource4Source2(section2.asList());
+
+ // Check rendering
+ assertRendered(result,"TwoSectionsFourSourcesResult.xml");
+ }
+
+ @Test
+ public void testExecutionMissingOneSource() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("TwoSectionsFourSources.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source3",12));
+ result.hits().add(createHits("source4",13));
+
+ new Organizer().organize(page,new DeterministicResolver().resolve(page,query,result),result);
+
+ // Check execution:
+ // Two subsections with two sources each, the first grouped the second blended
+ assertEquals(2,result.hits().size());
+ HitGroup section1=(HitGroup)result.hits().get(0);
+ HitGroup section2=(HitGroup)result.hits().get(1);
+ assertGroupedSource3Source1(section1.asList());
+ assertEqualHitGroups(createHits("source4",10),section2);
+ }
+
+ @Test
+ public void testExecutionMissingTwoSources() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("TwoSectionsFourSources.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+ result.hits().add(createHits("source1",3));
+ result.hits().add(createHits("source3",12));
+
+ new Organizer().organize(page,new DeterministicResolver().resolve(page,query,result),result);
+
+ // Check execution:
+ // Two subsections with two sources each, the first grouped the second blended
+ assertEquals(2,result.hits().size());
+ HitGroup section1=(HitGroup)result.hits().get(0);
+ HitGroup section2=(HitGroup)result.hits().get(1);
+ assertGroupedSource3Source1(section1.asList());
+ assertEquals(0,section2.size());
+ }
+
+ @Test
+ public void testExecutionMissingAllSources() {
+ // Create the page template
+ Choice page=Choice.createSingleton(importPage("TwoSectionsFourSources.xml"));
+
+ // Create a federated result
+ Query query=new Query();
+ Result result=new Result(query);
+
+ new Organizer().organize(page,new DeterministicResolver().resolve(page,query,result),result);
+
+ // Check execution:
+ // Two subsections with two sources each, the first grouped the second blended
+ assertEquals(2,result.hits().size());
+ HitGroup section1=(HitGroup)result.hits().get(0);
+ HitGroup section2=(HitGroup)result.hits().get(1);
+ assertEquals(0,section1.size());
+ assertEquals(0,section2.size());
+ }
+
+ private void assertGroupedSource3Source1(List<Hit> hits) {
+ assertEquals(8,hits.size());
+ assertEquals("source3-1",hits.get(0).getId().stringValue());
+ assertEquals("source3-2",hits.get(1).getId().stringValue());
+ assertEquals("source3-3",hits.get(2).getId().stringValue());
+ assertEquals("source3-4",hits.get(3).getId().stringValue());
+ assertEquals("source3-5",hits.get(4).getId().stringValue());
+ assertEquals("source1-1",hits.get(5).getId().stringValue());
+ assertEquals("source1-2",hits.get(6).getId().stringValue());
+ assertEquals("source1-3",hits.get(7).getId().stringValue());
+ }
+
+ private void assertBlendedSource4Source2(List<Hit> hits) {
+ assertEquals(10,hits.size());
+ assertEquals("source4-1",hits.get(0).getId().stringValue());
+ assertEquals("source2-1",hits.get(1).getId().stringValue());
+ assertEquals("source4-2",hits.get(2).getId().stringValue());
+ assertEquals("source2-2",hits.get(3).getId().stringValue());
+ assertEquals("source4-3",hits.get(4).getId().stringValue());
+ assertEquals("source2-3",hits.get(5).getId().stringValue());
+ assertEquals("source4-4",hits.get(6).getId().stringValue());
+ assertEquals("source2-4",hits.get(7).getId().stringValue());
+ assertEquals("source4-5",hits.get(8).getId().stringValue());
+ assertEquals("source4-6",hits.get(9).getId().stringValue());
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/test/PageTemplateSearcherTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/test/PageTemplateSearcherTestCase.java
new file mode 100644
index 00000000000..22c269e761f
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/test/PageTemplateSearcherTestCase.java
@@ -0,0 +1,220 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.test;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.component.chain.Chain;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.Searcher;
+import com.yahoo.search.intent.model.*;
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.PageTemplateRegistry;
+import com.yahoo.search.pagetemplates.PageTemplateSearcher;
+import com.yahoo.search.pagetemplates.engine.Resolution;
+import com.yahoo.search.pagetemplates.engine.resolvers.DeterministicResolver;
+import com.yahoo.search.pagetemplates.model.Choice;
+import com.yahoo.search.pagetemplates.model.PageElement;
+import com.yahoo.search.result.Hit;
+import com.yahoo.search.result.HitGroup;
+import com.yahoo.search.searchchain.Execution;
+import com.yahoo.text.interpretation.Interpretation;
+
+import java.util.*;
+
+/**
+ * @author bratseth
+ */
+@SuppressWarnings("deprecation")
+public class PageTemplateSearcherTestCase extends junit.framework.TestCase {
+
+ public void testSearcher() {
+ PageTemplateSearcher s = new PageTemplateSearcher(createPageTemplateRegistry(), new FirstChoiceResolver());
+ Chain<Searcher> chain = new Chain<>(s,new MockFederator());
+
+ {
+ // No template specified, should use default
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(new Query("?query=foo&page.resolver=native.deterministic"));
+ assertSources("source1 source2",result);
+ }
+
+ {
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(new Query("?query=foo&page.id=oneSource&page.resolver=native.deterministic"));
+ assertSources("source1",result);
+ }
+
+ {
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(new Query("?query=foo&page.id=twoSources&model.sources=source1&page.resolver=native.deterministic"));
+ assertSources("source1",result);
+ }
+
+ {
+ Query query=new Query("?query=foo&page.resolver=native.deterministic");
+ addIntentModelSpecifyingSource3(query);
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertSources("source1 source2",result);
+ }
+
+ {
+ Query query=new Query("?query=foo&page.id=twoSourcesAndAny&page.resolver=native.deterministic");
+ addIntentModelSpecifyingSource3(query);
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertSources("source1 source2 source3",result);
+ }
+
+ {
+ Query query=new Query("?query=foo&page.id=anySource&page.resolver=native.deterministic");
+ addIntentModelSpecifyingSource3(query);
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertSources("source3",result);
+ }
+
+ {
+ Query query=new Query("?query=foo&page.id=anySource&page.resolver=native.deterministic");
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertTrue(query.getModel().getSources().isEmpty());
+ assertNotNull(result.hits().get("source1"));
+ assertNotNull(result.hits().get("source2"));
+ assertNotNull(result.hits().get("source3"));
+ }
+
+ {
+ Query query=new Query("?query=foo&page.id=choiceOfSources&page.resolver=native.deterministic");
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertSources("source1 source2","source2",result);
+ }
+
+ {
+ Query query=new Query("?query=foo&page.id=choiceOfSources&page.resolver=test.firstChoice");
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertSources("source1 source2","source1",result);
+ }
+
+ { // Specifying two templates, should pick the last
+ Query query=new Query("?query=foo&page.id=threeSources+oneSource&page.resolver=native.deterministic");
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertSources("source1 source2 source3","source1",result);
+ }
+
+ { // Specifying two templates as a list, should override the page.id setting
+ Query query=new Query("?query=foo&page.id=anySource&page.resolver=native.deterministic");
+ query.properties().set("page.idList",Arrays.asList("oneSource","threeSources"));
+ Result result=new Execution(chain, Execution.Context.createContextStub()).search(query);
+ assertSources("source1 source2 source3","source1 source2 source3",result);
+ }
+
+ {
+ try {
+ Query query=new Query("?query=foo&page.id=oneSource+choiceOfSources&page.resolver=noneSuch");
+ new Execution(chain, Execution.Context.createContextStub()).search(query);
+ fail("Expected exception");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("No page template resolver 'noneSuch'",e.getMessage());
+ }
+ }
+
+ }
+
+ private PageTemplateRegistry createPageTemplateRegistry() {
+ PageTemplateRegistry registry=new PageTemplateRegistry();
+
+ PageTemplate twoSources=new PageTemplate(new ComponentId("default"));
+ twoSources.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source1"));
+ twoSources.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source2"));
+ registry.register(twoSources);
+
+ PageTemplate oneSource=new PageTemplate(new ComponentId("oneSource"));
+ oneSource.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source1"));
+ registry.register(oneSource);
+
+ PageTemplate threeSources=new PageTemplate(new ComponentId("threeSources"));
+ threeSources.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source1"));
+ threeSources.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source2"));
+ threeSources.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source3"));
+ registry.register(threeSources);
+
+ PageTemplate twoSourcesAndAny=new PageTemplate(new ComponentId("twoSourcesAndAny"));
+ twoSourcesAndAny.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source1"));
+ twoSourcesAndAny.getSection().elements().add(new com.yahoo.search.pagetemplates.model.Source("source2"));
+ twoSourcesAndAny.getSection().elements().add(com.yahoo.search.pagetemplates.model.Source.any);
+ registry.register(twoSourcesAndAny);
+
+ PageTemplate anySource=new PageTemplate(new ComponentId("anySource"));
+ anySource.getSection().elements().add(com.yahoo.search.pagetemplates.model.Source.any);
+ registry.register(anySource);
+
+ PageTemplate choiceOfSources=new PageTemplate(new ComponentId("choiceOfSources"));
+ List<PageElement> alternatives=new ArrayList<>();
+ alternatives.add(new com.yahoo.search.pagetemplates.model.Source("source1"));
+ alternatives.add(new com.yahoo.search.pagetemplates.model.Source("source2"));
+ choiceOfSources.getSection().elements().add(Choice.createSingletons(alternatives));
+ registry.register(choiceOfSources);
+
+ registry.freeze();
+ return registry;
+ }
+
+ private void addIntentModelSpecifyingSource3(Query query) {
+ IntentModel intentModel=new IntentModel();
+ InterpretationNode interpretation=new InterpretationNode(new Interpretation("ignored"));
+ IntentNode intent=new IntentNode(new Intent("ignored"),1.0);
+ intent.children().add(new SourceNode(new com.yahoo.search.intent.model.Source("source3"),1.0));
+ interpretation.children().add(intent);
+ intentModel.children().add(interpretation);
+ intentModel.setTo(query);
+ }
+
+ private void assertSources(String expectedSourceString,Result result) {
+ assertSources(expectedSourceString,expectedSourceString,result);
+ }
+
+ private void assertSources(String expectedQuerySourceString,String expectedResultSourceString,Result result) {
+ Set<String> expectedQuerySources=new HashSet<>(Arrays.asList(expectedQuerySourceString.split(" ")));
+ assertEquals(expectedQuerySources,result.getQuery().getModel().getSources());
+
+ Set<String> expectedResultSources=new HashSet<>(Arrays.asList(expectedResultSourceString.split(" ")));
+ for (String sourceName : Arrays.asList("source1 source2 source3".split(" "))) {
+ if (expectedResultSources.contains(sourceName))
+ assertNotNull("Result contains '" + sourceName + "'",result.hits().get(sourceName));
+ else
+ assertNull("Result does not contain '" + sourceName + "'",result.hits().get(sourceName));
+ }
+ }
+
+ private static class MockFederator extends Searcher {
+
+ @Override
+ public Result search(Query query,Execution execution) {
+ Result result=new Result(query);
+ for (String sourceName : Arrays.asList("source1 source2 source3".split(" ")))
+ if (query.getModel().getSources().isEmpty() || query.getModel().getSources().contains(sourceName))
+ result.hits().add(createSource(sourceName));
+ return result;
+ }
+
+ private HitGroup createSource(String sourceName) {
+ HitGroup hitGroup=new HitGroup("source:" + sourceName);
+ Hit hit=new Hit(sourceName);
+ hit.setSource(sourceName);
+ hitGroup.add(hit);
+ return hitGroup;
+ }
+
+ }
+
+ /** Like the deterministic resolver except that it takes the <i>first</i> option of all choices */
+ private static class FirstChoiceResolver extends DeterministicResolver {
+
+ public FirstChoiceResolver() {
+ super("test.firstChoice");
+ }
+
+ /** Chooses the first alternative of any choice */
+ @Override
+ public void resolve(Choice choice, Query query, Result result, Resolution resolution) {
+ resolution.addChoiceResolution(choice,0);
+ }
+
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParameters.xml b/container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParameters.xml
new file mode 100644
index 00000000000..2a98ef6918f
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParameters.xml
@@ -0,0 +1,16 @@
+<!-- Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->
+<page id="SourceParameters">
+ <source name="source1">
+ <parameter name="p1">source1p1Value</parameter>
+ <parameter name="p2">source1p2Value</parameter>
+ </source>
+ <choice>
+ <source name="source2">
+ <parameter name="p1">source2p1Value</parameter>
+ <parameter name="p3">source2p3Value</parameter>
+ </source>
+ <source name="source3">
+ <parameter name="p1">source3p1Value</parameter>
+ </source>
+ </choice>
+</page>
diff --git a/container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParametersTestCase.java b/container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParametersTestCase.java
new file mode 100644
index 00000000000..1f79637119a
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/search/pagetemplates/test/SourceParametersTestCase.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.search.pagetemplates.test;
+
+import com.yahoo.search.Query;
+import com.yahoo.search.pagetemplates.PageTemplate;
+import com.yahoo.search.pagetemplates.PageTemplateRegistry;
+import com.yahoo.search.pagetemplates.PageTemplateSearcher;
+import com.yahoo.search.pagetemplates.config.PageTemplateXMLReader;
+import com.yahoo.search.searchchain.Execution;
+
+/**
+ * @author bratseth
+ */
+public class SourceParametersTestCase extends junit.framework.TestCase {
+
+ private static final String root="src/test/java/com/yahoo/search/pagetemplates/test/";
+
+ public void testSourceParametersWithSourcesDeterminedByTemplate() {
+ // Create the page template
+ PageTemplateRegistry pageTemplateRegistry=new PageTemplateRegistry();
+ PageTemplate page=importPage("SourceParameters.xml");
+ pageTemplateRegistry.register(page);
+ PageTemplateSearcher s=new PageTemplateSearcher(pageTemplateRegistry);
+ Query query=new Query("?query=foo&page.id=SourceParameters&page.resolver=native.deterministic");
+ new Execution(s, Execution.Context.createContextStub()).search(query);
+ assertEquals("source1p1Value",query.properties().get("source.source1.p1"));
+ assertEquals("source1p1Value",query.properties().get("source.source1.p1"));
+ assertEquals("source2p1Value",query.properties().get("source.source2.p1"));
+ assertEquals("source2p3Value",query.properties().get("source.source2.p3"));
+ assertEquals("source3p1Value",query.properties().get("source.source3.p1"));
+ assertEquals("We get the correct number of parameters",5,query.properties().listProperties("source").size());
+ }
+
+ public void testSourceParametersWithSourcesDeterminedByParameter() {
+ // Create the page template
+ PageTemplateRegistry pageTemplateRegistry=new PageTemplateRegistry();
+ PageTemplate page=importPage("SourceParameters.xml");
+ pageTemplateRegistry.register(page);
+ PageTemplateSearcher s=new PageTemplateSearcher(pageTemplateRegistry);
+ Query query=new Query("?query=foo&page.id=SourceParameters&model.sources=source1,source3&page.resolver=native.deterministic");
+ new Execution(s, Execution.Context.createContextStub()).search(query);
+ assertEquals("source1p1Value",query.properties().get("source.source1.p1"));
+ assertEquals("source1p1Value",query.properties().get("source.source1.p1"));
+ assertEquals("source3p1Value",query.properties().get("source.source3.p1"));
+ assertEquals("We get the correct number of parameters",3,query.properties().listProperties("source").size());
+ }
+
+ protected PageTemplate importPage(String name) {
+ PageTemplate template=new PageTemplateXMLReader().readFile(root + name);
+ assertNotNull("Could look up read template '" + name + "'",template);
+ return template;
+ }
+
+}