summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@oath.com>2018-12-05 14:23:12 -0800
committergjoranv <gv@oath.com>2019-01-21 15:09:30 +0100
commit879da966c503e1de9e6ab2a80f0a2ff397cff1e3 (patch)
tree21b4f18186233dd9a176269ebc8081e920fe7834
parent0f63b23a4ea60b78ad97f1c19245023fb29545c0 (diff)
Testing of dynamic summary rendering
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/hitfield/HitField.java22
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java1
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java28
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java9
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/searcher/test/JuniperSearcherTestCase.java2
-rw-r--r--container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java207
-rw-r--r--container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java250
-rw-r--r--container-search/src/test/java/com/yahoo/search/statistics/ElapsedTimeTestCase.java6
9 files changed, 372 insertions, 154 deletions
diff --git a/container-search/src/main/java/com/yahoo/prelude/hitfield/HitField.java b/container-search/src/main/java/com/yahoo/prelude/hitfield/HitField.java
index 8778b09934c..2702da099be 100644
--- a/container-search/src/main/java/com/yahoo/prelude/hitfield/HitField.java
+++ b/container-search/src/main/java/com/yahoo/prelude/hitfield/HitField.java
@@ -24,7 +24,7 @@ public class HitField {
private boolean xmlProperty;
private List<FieldPart> tokenizedContent = null;
- private String content = null;
+ private String content;
private Object original;
@@ -48,7 +48,7 @@ public class HitField {
/**
* @param f The field name
* @param c The field content
- * @param cjk true if this is a cjk-document
+ * @param cjk true if the content is CJK text
*/
public HitField(String f, String c, boolean cjk) {
this(f, c, cjk, false);
@@ -57,7 +57,7 @@ public class HitField {
/**
* @param f The field name
* @param c The field content
- * @param cjk true if this is a cjk-document
+ * @param cjk true if the content is CJK text
*/
public HitField(String f, XMLString c, boolean cjk) {
this(f, c.toString(), cjk, true);
@@ -66,7 +66,7 @@ public class HitField {
/**
* @param f The field name
* @param c The field content
- * @param cjk true if this is a cjk-document
+ * @param cjk true if the content is CJK text
* @param xmlProperty true if this should not quote XML syntax
*/
public HitField(String f, String c, boolean cjk, boolean xmlProperty) {
@@ -279,9 +279,8 @@ public class HitField {
// Must null content reference _before_ calling getContent()
content = null;
}
- /**
- * @return the content of this field
- */
+
+ /** Returns the content of this field */
public String getContent() {
if (content == null) {
StringBuilder buf = new StringBuilder();
@@ -294,13 +293,8 @@ public class HitField {
return content;
}
- /**
- * @return the content of this field, using the arguments as bolding
- * tags
- */
- public String getContent(String boldOpenTag,
- String boldCloseTag,
- String separatorTag) {
+ /** Returns the content of this field, using the arguments as bolding tags */
+ public String getContent(String boldOpenTag, String boldCloseTag, String separatorTag) {
StringBuilder buf = new StringBuilder();
Iterator<FieldPart> iter = ensureTokenized().iterator();
while(iter.hasNext()) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java b/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java
index 06db012309e..eee7b310d13 100644
--- a/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java
+++ b/container-search/src/main/java/com/yahoo/prelude/hitfield/JSONString.java
@@ -86,6 +86,7 @@ public class JSONString implements Inspectable {
didInitContent = true;
}
+ @Override
public String toString() {
if (value != null) {
return renderFromInspector();
diff --git a/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java b/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java
index 521ea83f712..d11b8d86d29 100644
--- a/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/searcher/JuniperSearcher.java
@@ -77,10 +77,9 @@ public class JuniperSearcher extends Searcher {
@Override
public void fill(Result result, String summaryClass, Execution execution) {
- Result workResult = result;
- int worstCase = workResult.getHitCount();
+ int worstCase = result.getHitCount();
List<Hit> hits = new ArrayList<>(worstCase);
- for (Iterator<Hit> i = workResult.hits().deepIterator(); i.hasNext();) {
+ for (Iterator<Hit> i = result.hits().deepIterator(); i.hasNext();) {
Hit sniffHit = i.next();
if ( ! (sniffHit instanceof FastHit)) continue;
@@ -89,26 +88,26 @@ public class JuniperSearcher extends Searcher {
hits.add(hit);
}
- execution.fill(workResult, summaryClass);
- highlight(workResult.getQuery().getPresentation().getBolding(), hits.iterator(), summaryClass,
+ execution.fill(result, summaryClass);
+ highlight(result.getQuery().getPresentation().getBolding(), hits.iterator(), summaryClass,
execution.context().getIndexFacts().newSession(result.getQuery()));
}
private void highlight(boolean bolding, Iterator<Hit> hitsToHighlight,
String summaryClass, IndexFacts.Session indexFacts) {
while (hitsToHighlight.hasNext()) {
- Hit sniffHit = hitsToHighlight.next();
- if ( ! (sniffHit instanceof FastHit)) continue;
+ Hit hit = hitsToHighlight.next();
+ if ( ! (hit instanceof FastHit)) continue;
- FastHit hit = (FastHit) sniffHit;
- if (summaryClass != null && ! hit.isFilled(summaryClass)) continue;
+ FastHit fastHit = (FastHit) hit;
+ if (summaryClass != null && ! fastHit.isFilled(summaryClass)) continue;
- Object searchDefinitionField = hit.getField(MAGIC_FIELD);
+ Object searchDefinitionField = fastHit.getField(MAGIC_FIELD);
if (searchDefinitionField == null) continue;
for (Index index : indexFacts.getIndexes(searchDefinitionField.toString())) {
if (index.getDynamicSummary() || index.getHighlightSummary()) {
- HitField fieldValue = hit.buildHitField(index.getName(), true);
+ HitField fieldValue = fastHit.buildHitField(index.getName(), true);
if (fieldValue != null)
insertTags(fieldValue, bolding, index.getDynamicSummary());
}
@@ -116,9 +115,9 @@ public class JuniperSearcher extends Searcher {
}
}
- private void insertTags(HitField oldProperty, boolean bolding, boolean dynteaser) {
+ private void insertTags(HitField field, boolean bolding, boolean dynteaser) {
boolean insideHighlight = false;
- for (ListIterator<FieldPart> i = oldProperty.listIterator(); i.hasNext();) {
+ for (ListIterator<FieldPart> i = field.listIterator(); i.hasNext();) {
FieldPart f = i.next();
if (f instanceof SeparatorFieldPart)
setSeparatorString(bolding, (SeparatorFieldPart) f);
@@ -138,8 +137,7 @@ public class JuniperSearcher extends Searcher {
break;
case RAW_SEPARATOR_CHAR:
newFieldParts = initFieldParts(newFieldParts);
- addSeparator(bolding, dynteaser, f, toQuote, newFieldParts,
- previous, j);
+ addSeparator(bolding, dynteaser, f, toQuote, newFieldParts, previous, j);
previous = j + 1;
break;
default:
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
index c102d1fa258..e8af150ce25 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
@@ -18,6 +18,7 @@ import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.document.datatypes.TensorFieldValue;
import com.yahoo.document.json.JsonWriter;
import com.yahoo.lang.MutableBoolean;
+import com.yahoo.prelude.hitfield.HitField;
import com.yahoo.processing.Response;
import com.yahoo.processing.execution.Execution.Trace;
import com.yahoo.processing.rendering.AsynchronousSectionedRenderer;
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java
index 5f99c531c95..5586fd2f996 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/XmlRenderer.java
@@ -349,15 +349,12 @@ public final class XmlRenderer extends AsynchronousSectionedRenderer<Result> {
}
private Result getResult() {
- Result r;
try {
- r = (Result) getResponse();
+ return (Result) getResponse();
} catch (ClassCastException e) {
- throw new IllegalArgumentException(
- "XmlRenderer attempted used outside a search context, got a "
- + getResponse().getClass().getName());
+ throw new IllegalArgumentException("XmlRenderer attempted used outside a search context, got a " +
+ getResponse().getClass().getName());
}
- return r;
}
@Override
diff --git a/container-search/src/test/java/com/yahoo/prelude/searcher/test/JuniperSearcherTestCase.java b/container-search/src/test/java/com/yahoo/prelude/searcher/test/JuniperSearcherTestCase.java
index 9f4a12d24e6..24af91cb5c0 100644
--- a/container-search/src/test/java/com/yahoo/prelude/searcher/test/JuniperSearcherTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/searcher/test/JuniperSearcherTestCase.java
@@ -47,7 +47,7 @@ public class JuniperSearcherTestCase {
DocumentSourceSearcher docsource = new DocumentSourceSearcher();
addResult(new Query("?query=12"), sdName, content, docsource);
addResult(new Query("?query=12&bolding=false"), sdName, content, docsource);
- return new Chain<Searcher>(searcher, docsource);
+ return new Chain<>(searcher, docsource);
}
private void addResult(Query query, String sdName, String content, DocumentSourceSearcher docsource) {
diff --git a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
index 9e16ddba6fc..10a3b695f64 100644
--- a/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/rendering/JsonRendererTestCase.java
@@ -1,14 +1,13 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.rendering;
-import com.fasterxml.jackson.core.JsonGenerator;
-import com.fasterxml.jackson.core.JsonParseException;
-import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
+import com.yahoo.component.ComponentId;
import com.yahoo.component.chain.Chain;
+import com.yahoo.container.QrSearchersConfig;
import com.yahoo.data.access.simple.Value;
import com.yahoo.data.access.slime.SlimeAdapter;
import com.yahoo.document.DataType;
@@ -19,8 +18,13 @@ import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.document.datatypes.Struct;
import com.yahoo.document.datatypes.TensorFieldValue;
import com.yahoo.document.predicate.Predicate;
+import com.yahoo.prelude.Index;
+import com.yahoo.prelude.IndexFacts;
+import com.yahoo.prelude.IndexModel;
+import com.yahoo.prelude.SearchDefinition;
import com.yahoo.prelude.fastsearch.FastHit;
import com.yahoo.prelude.hitfield.JSONString;
+import com.yahoo.prelude.searcher.JuniperSearcher;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
@@ -38,6 +42,7 @@ import com.yahoo.search.result.NanNumber;
import com.yahoo.search.result.Relevance;
import com.yahoo.search.result.StructuredData;
import com.yahoo.search.searchchain.Execution;
+import com.yahoo.search.searchchain.testutil.DocumentSourceSearcher;
import com.yahoo.search.statistics.ElapsedTimeTestCase;
import com.yahoo.search.statistics.ElapsedTimeTestCase.CreativeTimeSource;
import com.yahoo.search.statistics.ElapsedTimeTestCase.UselessSearcher;
@@ -51,32 +56,31 @@ import com.yahoo.yolean.trace.TraceNode;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
-import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-import org.mockito.Mockito;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.times;
/**
* Functional testing of {@link JsonRenderer}.
*
* @author Steinar Knutsen
+ * @author bratseth
*/
public class JsonRendererTestCase {
- JsonRenderer originalRenderer;
- JsonRenderer renderer;
+ private JsonRenderer originalRenderer;
+ private JsonRenderer renderer;
public JsonRendererTestCase() {
originalRenderer = new JsonRenderer();
@@ -84,23 +88,11 @@ public class JsonRendererTestCase {
@Before
public void setUp() throws Exception {
- // Do the same dance as in production
+ // Use the shared renderer as a prototype object, as specified in the API contract
renderer = (JsonRenderer) originalRenderer.clone();
renderer.init();
}
- @After
- public void tearDown() throws Exception {
- renderer = null;
- }
-
- private static final class Thingie {
- @Override
- public String toString() {
- return "thingie";
- }
- }
-
@Test
public void testDocumentId() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
@@ -130,14 +122,6 @@ public class JsonRendererTestCase {
assertEqualJson(expected, summary);
}
- private Result newEmptyResult(String[] args) {
- return new Result(new Query("/?" + String.join("&", args)));
- }
-
- private Result newEmptyResult() {
- return newEmptyResult(new String[] {"query=a"});
- }
-
@Test
public void testDataTypes() throws IOException, InterruptedException, ExecutionException, JSONException {
String expected = "{\n"
@@ -188,7 +172,7 @@ public class JsonRendererTestCase {
@Test
- public final void testTracing() throws IOException, InterruptedException, ExecutionException {
+ public void testTracing() throws IOException, InterruptedException, ExecutionException {
// which clearly shows a trace child is created once too often...
String expected = "{\n"
+ " \"root\": {\n"
@@ -243,7 +227,7 @@ public class JsonRendererTestCase {
}
@Test
- public final void testEmptyTracing() throws IOException, InterruptedException, ExecutionException {
+ public void testEmptyTracing() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"fields\": {\n"
@@ -272,7 +256,7 @@ public class JsonRendererTestCase {
@SuppressWarnings("unchecked")
@Test
- public final void testTracingWithEmptySubtree() throws IOException, InterruptedException, ExecutionException {
+ public void testTracingWithEmptySubtree() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"fields\": {\n"
@@ -346,15 +330,8 @@ public class JsonRendererTestCase {
assertEqualJson(expected, summary);
}
- private void subExecution(Execution execution, String color, int traceLevel) {
- Execution e2 = new Execution(new Chain<Searcher>(), execution.context());
- Query subQuery = new Query("/?query=b&tracelevel=" + traceLevel);
- e2.search(subQuery);
- subQuery.trace(color, 1);
- }
-
@Test
- public final void testTracingOfNodesWithBothChildrenAndData() throws IOException, InterruptedException, ExecutionException {
+ public void testTracingOfNodesWithBothChildrenAndData() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"fields\": {\n"
@@ -400,7 +377,7 @@ public class JsonRendererTestCase {
@Test
- public final void testTracingOfNodesWithBothChildrenAndDataAndEmptySubnode() throws IOException, InterruptedException, ExecutionException {
+ public void testTracingOfNodesWithBothChildrenAndDataAndEmptySubnode() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"fields\": {\n"
@@ -441,7 +418,7 @@ public class JsonRendererTestCase {
}
@Test
- public final void testTracingOfNestedNodesWithDataAndSubnodes() throws IOException, InterruptedException, ExecutionException {
+ public void testTracingOfNestedNodesWithDataAndSubnodes() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"fields\": {\n"
@@ -490,7 +467,7 @@ public class JsonRendererTestCase {
@Test
- public final void test() throws IOException, InterruptedException, ExecutionException {
+ public void test() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"children\": [\n"
@@ -883,7 +860,7 @@ public class JsonRendererTestCase {
});
GroupList gl = new GroupList("customer");
Group g = new Group(new DoubleBucketId(1.0, 2.0), new Relevance(1.0));
- g.setField("something()", Integer.valueOf(7));
+ g.setField("something()", 7);
gl.add(g);
rg.add(gl);
r.hits().add(rg);
@@ -956,7 +933,7 @@ public class JsonRendererTestCase {
}
@Test
- public final void testFieldValueInHit() throws IOException, InterruptedException, ExecutionException, JSONException {
+ public void testFieldValueInHit() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"children\": [\n"
@@ -991,7 +968,7 @@ public class JsonRendererTestCase {
}
@Test
- public final void testHiddenFields() throws IOException, InterruptedException, ExecutionException, JSONException {
+ public void testHiddenFields() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"children\": [\n"
@@ -1015,17 +992,8 @@ public class JsonRendererTestCase {
assertEqualJson(expected, summary);
}
- private Hit createHitWithOnlyHiddenFields() {
- Hit h = new Hit("hiddenFields");
- h.setField("NaN", NanNumber.NaN);
- h.setField("emptyString", "");
- h.setField("emptyStringFieldValue", new StringFieldValue(""));
- h.setField("$vespaImplementationDetail", "Hello, World!");
- return h;
- }
-
@Test
- public final void testDebugRendering() throws IOException, InterruptedException, ExecutionException, JSONException {
+ public void testDebugRendering() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"children\": [\n"
@@ -1056,7 +1024,7 @@ public class JsonRendererTestCase {
}
@Test
- public final void testTimingRendering() throws InterruptedException, ExecutionException, JsonParseException, JsonMappingException, IOException {
+ public void testTimingRendering() throws InterruptedException, ExecutionException, IOException {
String expected = "{"
+ " \"root\": {"
+ " \"fields\": {"
@@ -1091,7 +1059,7 @@ public class JsonRendererTestCase {
}
@Test
- public final void testJsonCallback() throws IOException, InterruptedException, ExecutionException, JSONException {
+ public void testJsonCallback() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"children\": [\n"
@@ -1129,7 +1097,7 @@ public class JsonRendererTestCase {
}
@Test
- public final void testMapInField() throws IOException, InterruptedException, ExecutionException, JSONException {
+ public void testMapInField() throws IOException, InterruptedException, ExecutionException {
String expected = "{\n"
+ " \"root\": {\n"
+ " \"children\": [\n"
@@ -1185,6 +1153,54 @@ public class JsonRendererTestCase {
+ "}";
assertEquals("Duplicate key \"duplicate\"", validateJSON(json));
}
+
+ @Test
+ public void testDynamicSummary() throws Exception {
+ String content = "\uFFF9Feeding\uFFFAfeed\uFFFB \u001F\uFFF9documents\uFFFAdocument\uFFFB\u001F into Vespa \uFFF9is\uFFFAbe\u001Eincrement of a set of \u001F\uFFF9documents\uFFFAdocument\uFFFB\u001F fed into Vespa \uFFF9is\u001Efloat in XML when \u001Fdocument\u001F attribute \uFFF9is\uFFFAbe\uFFFB int\u001E";
+ Result result = createResult("one", content, true);
+
+ String summary = render(result);
+
+ String expected =
+ "{ \n" +
+ " \"root\":{ " +
+ " \"id\":\"toplevel\"," +
+ " \"relevance\":1.0," +
+ " \"fields\":{ " +
+ " \"totalCount\":0" +
+ " }," +
+ " \"children\":[ " +
+ " { " +
+ " \"id\":\"http://abc.html/\"," +
+ " \"relevance\":1.0," +
+ " \"fields\":{ " +
+ " \"sddocname\":\"one\",\n" +
+ " \"dynteaser\":\"Feeding <hi>documents</hi> into Vespa is<sep />increment of a set of <hi>documents</hi> fed into Vespa <sep />float in XML when <hi>document</hi> attribute is int<sep />\"\n" +
+ " }\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ "}\n";
+ assertEqualJson(expected, summary);
+ }
+
+ private Result newEmptyResult(String[] args) {
+ return new Result(new Query("/?" + String.join("&", args)));
+ }
+
+ private Result newEmptyResult() {
+ return newEmptyResult(new String[] {"query=a"});
+ }
+
+ private Hit createHitWithOnlyHiddenFields() {
+ Hit h = new Hit("hiddenFields");
+ h.setField("NaN", NanNumber.NaN);
+ h.setField("emptyString", "");
+ h.setField("emptyStringFieldValue", new StringFieldValue(""));
+ h.setField("$vespaImplementationDetail", "Hello, World!");
+ return h;
+ }
+
private String render(Result r) throws InterruptedException, ExecutionException {
Execution execution = new Execution(Execution.Context.createContextStub());
return render(execution, r);
@@ -1207,6 +1223,7 @@ public class JsonRendererTestCase {
assertEquals("", validateJSON(expected));
assertEquals("", validateJSON(generated));
}
+
private String validateJSON(String presumablyValidJson) {
try {
new JSONObject(presumablyValidJson);
@@ -1216,4 +1233,76 @@ public class JsonRendererTestCase {
}
}
+ private static final class Thingie {
+ @Override
+ public String toString() {
+ return "thingie";
+ }
+ }
+
+ private Result createResult(String sdName, String content, boolean bolding) {
+ Chain<Searcher> chain = createSearchChain(sdName, content);
+ Query query = new Query("?query=12");
+ if ( ! bolding)
+ query = new Query("?query=12&bolding=false");
+ Execution execution = createExecution(chain);
+ Result result = execution.search(query);
+ execution.fill(result);
+ return result;
+ }
+
+ /**
+ * Creates a search chain which always returns a result with one hit containing information given in this
+ *
+ * @param sdName the search definition type of the returned hit
+ * @param content the content of the "dynteaser" field of the returned hit
+ */
+ private Chain<Searcher> createSearchChain(String sdName, String content) {
+ JuniperSearcher searcher = new JuniperSearcher(new ComponentId("test"),
+ new QrSearchersConfig(new QrSearchersConfig.Builder()));
+
+ DocumentSourceSearcher docsource = new DocumentSourceSearcher();
+ addResult(new Query("?query=12"), sdName, content, docsource);
+ addResult(new Query("?query=12&bolding=false"), sdName, content, docsource);
+ return new Chain<>(searcher, docsource);
+ }
+
+ private void addResult(Query query, String sdName, String content, DocumentSourceSearcher docsource) {
+ Result r = new Result(query);
+ FastHit hit = new FastHit();
+ hit.setId("http://abc.html");
+ hit.setRelevance(new Relevance(1));
+ hit.setField(Hit.SDDOCNAME_FIELD, sdName);
+ hit.setField("dynteaser", content);
+ r.hits().add(hit);
+ docsource.addResult(query, r);
+ }
+
+ private Execution createExecution(Chain<Searcher> chain) {
+ Map<String, List<String>> clusters = new LinkedHashMap<>();
+ Map<String, SearchDefinition> searchDefs = new LinkedHashMap<>();
+ searchDefs.put("one", createSearchDefinitionOne());
+ SearchDefinition union = new SearchDefinition("union");
+ IndexModel indexModel = new IndexModel(clusters, searchDefs, union);
+ return new Execution(chain, Execution.Context.createContextStub(new IndexFacts(indexModel)));
+ }
+
+ private SearchDefinition createSearchDefinitionOne() {
+ SearchDefinition one = new SearchDefinition("one");
+
+ Index dynteaser = new Index("dynteaser");
+ dynteaser.setDynamicSummary(true);
+ one.addIndex(dynteaser);
+
+ Index bigteaser = new Index("bigteaser");
+ dynteaser.setHighlightSummary(true);
+ one.addIndex(bigteaser);
+
+ Index otherteaser = new Index("otherteaser");
+ otherteaser.setDynamicSummary(true);
+ one.addIndex(otherteaser);
+
+ return one;
+ }
+
}
diff --git a/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java
index ea3b46aaaa9..15b6dd1a01d 100644
--- a/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/rendering/XMLRendererTestCase.java
@@ -4,9 +4,21 @@ package com.yahoo.search.rendering;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
-
-import org.junit.After;
-import org.junit.Before;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.yahoo.component.ComponentId;
+import com.yahoo.container.QrSearchersConfig;
+import com.yahoo.prelude.Index;
+import com.yahoo.prelude.IndexFacts;
+import com.yahoo.prelude.IndexModel;
+import com.yahoo.prelude.SearchDefinition;
+import com.yahoo.prelude.searcher.JuniperSearcher;
+import com.yahoo.search.result.Hit;
+import com.yahoo.search.result.Relevance;
+import com.yahoo.search.searchchain.Execution;
+import com.yahoo.search.searchchain.testutil.DocumentSourceSearcher;
import org.junit.Test;
import com.google.common.util.concurrent.ListenableFuture;
@@ -21,50 +33,114 @@ import com.yahoo.search.result.HitGroup;
import com.yahoo.search.statistics.ElapsedTimeTestCase;
import com.yahoo.search.statistics.TimeTracker;
import com.yahoo.search.statistics.ElapsedTimeTestCase.CreativeTimeSource;
-import com.yahoo.search.statistics.ElapsedTimeTestCase.UselessSearcher;
import com.yahoo.text.Utf8;
/**
* Test the XML renderer
*
* @author Steinar Knutsen
+ * @author bratseth
*/
public class XMLRendererTestCase {
- XmlRenderer d;
-
- @Before
- public void setUp() throws Exception {
- d = new XmlRenderer();
- d.init();
+ @Test
+ public void testGetEncoding() {
+ XmlRenderer renderer = new XmlRenderer();
+ renderer.init();
+ assertEquals("utf-8", renderer.getEncoding());
}
- @After
- public void tearDown() throws Exception {
+ @Test
+ public void testGetMimeType() {
+ XmlRenderer renderer = new XmlRenderer();
+ renderer.init();
+ assertEquals("text/xml", renderer.getMimeType());
}
@Test
- public final void testGetEncoding() {
- assertEquals("utf-8", d.getEncoding());
+ public void testXmlRendering() throws Exception {
+ Query q = new Query("/?query=a");
+
+ Result result = new Result(q);
+ result.setCoverage(new Coverage(500, 1));
+
+ FastHit h = new FastHit("http://localhost/", .95);
+ h.setField("$a", "Hello, world.");
+ h.setField("b", "foo");
+ result.hits().add(h);
+
+ HitGroup g = new HitGroup("usual");
+ h = new FastHit("http://localhost/1", .90);
+ h.setField("c", "d");
+ g.add(h);
+ result.hits().add(g);
+
+ HitGroup gg = new HitGroup("type grouphit");
+ gg.types().add("grouphit");
+ gg.setField("e", "f");
+ result.hits().add(gg);
+ result.hits().addError(ErrorMessage.createInternalServerError("message"));
+
+ String summary = render(result);
+
+ String expected =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+ "<result total-hit-count=\"0\" coverage-docs=\"500\" coverage-nodes=\"1\" coverage-full=\"false\" coverage=\"0\" results-full=\"0\" results=\"1\">\n" +
+ " <error code=\"18\">Internal server error.</error>\n" +
+ " <errordetails>\n" +
+ " <error error=\"Internal server error.\" code=\"18\">message</error>\n" +
+ " </errordetails>\n" +
+ " <group relevancy=\"1.0\">\n" +
+ " <hit type=\"summary\" relevancy=\"0.9\">\n" +
+ " <field name=\"relevancy\">0.9</field>\n" +
+ " <field name=\"uri\">http://localhost/1</field>\n" +
+ " <field name=\"c\">d</field>\n" +
+ " </hit>\n" +
+ " </group>\n" +
+ " <hit type=\"grouphit\" relevancy=\"1.0\">\n" +
+ " <id>type grouphit</id>\n" +
+ " </hit>\n" +
+ " <hit type=\"summary\" relevancy=\"0.95\">\n" +
+ " <field name=\"relevancy\">0.95</field>\n" +
+ " <field name=\"uri\">http://localhost/</field>\n" +
+ " <field name=\"b\">foo</field>\n" +
+ " </hit>\n" +
+ "</result>\n";
+
+ assertEquals(expected, summary);
}
@Test
- public final void testGetMimeType() {
- assertEquals("text/xml", d.getMimeType());
+ public void testXmlRenderingOfDynamicSummary() throws Exception {
+ String content = "\uFFF9Feeding\uFFFAfeed\uFFFB \u001F\uFFF9documents\uFFFAdocument\uFFFB\u001F into Vespa \uFFF9is\uFFFAbe\u001Eincrement of a set of \u001F\uFFF9documents\uFFFAdocument\uFFFB\u001F fed into Vespa \uFFF9is\u001Efloat in XML when \u001Fdocument\u001F attribute \uFFF9is\uFFFAbe\uFFFB int\u001E";
+ Result result = createResult("one", content, true);
+
+ String summary = render(result);
+
+ String expected =
+ "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
+ "<result total-hit-count=\"0\">\n" +
+ " <hit relevancy=\"1.0\">\n" +
+ " <field name=\"relevancy\">1.0</field>\n" +
+ " <field name=\"sddocname\">one</field>\n" +
+ " <field name=\"dynteaser\">Feeding <hi>documents</hi> into Vespa is<sep />increment of a set of <hi>documents</hi> fed into Vespa <sep />float in XML when <hi>document</hi> attribute is int<sep /></field>\n" +
+ " </hit>\n" +
+ "</result>\n";
+ assertEquals(expected, summary);
}
@Test
- public void testImplicitDefaultRender() throws Exception {
+ public void testXmlRenderingWithTimeTracking() throws Exception {
Query q = new Query("/?query=a&tracelevel=5");
q.getPresentation().setTiming(true);
- Result r = new Result(q);
- r.setCoverage(new Coverage(500, 1));
-
- TimeTracker t = new TimeTracker(new Chain<Searcher>(
- new UselessSearcher("first"), new UselessSearcher("second"),
- new UselessSearcher("third")));
- ElapsedTimeTestCase.doInjectTimeSource(t, new CreativeTimeSource(
- new long[] { 1L, 2L, 3L, 4L, 5L, 6L, 7L }));
+
+ Result result = new Result(q);
+ result.setCoverage(new Coverage(500, 1));
+
+ TimeTracker t = new TimeTracker(new Chain<Searcher>(new NoopSearcher("first"),
+ new NoopSearcher("second"),
+ new NoopSearcher("third")));
+ ElapsedTimeTestCase.doInjectTimeSource(t, new CreativeTimeSource(new long[] { 1L, 2L, 3L, 4L, 5L, 6L, 7L }));
t.sampleSearch(0, true);
t.sampleSearch(1, true);
t.sampleSearch(2, true);
@@ -72,47 +148,103 @@ public class XMLRendererTestCase {
t.sampleSearchReturn(2, true, null);
t.sampleSearchReturn(1, true, null);
t.sampleSearchReturn(0, true, null);
- r.getElapsedTime().add(t);
- FastHit h = new FastHit("http://localhost/", .95);
- h.setField("$a", "Hello, world.");
- h.setField("b", "foo");
- r.hits().add(h);
- HitGroup g = new HitGroup("usual");
- h = new FastHit("http://localhost/1", .90);
- h.setField("c", "d");
- g.add(h);
- r.hits().add(g);
- HitGroup gg = new HitGroup("type grouphit");
- gg.types().add("grouphit");
- gg.setField("e", "f");
- r.hits().add(gg);
- r.hits().addError(ErrorMessage.createInternalServerError("boom"));
+ result.getElapsedTime().add(t);
+ String summary = render(result);
+
+ assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<result total-hit-count=\"0\"",
+ summary.substring(0, 67));
+ assertTrue(summary.contains("querytime="));
+ assertTrue(summary.contains("summaryfetchtime="));
+ assertTrue(summary.contains("searchtime="));
+ assertTrue(summary.contains("<meta type=\"context\">"));
+ }
+
+ private String render(Result result) throws Exception {
+ XmlRenderer renderer = new XmlRenderer();
+ renderer.init();
ByteArrayOutputStream bs = new ByteArrayOutputStream();
- ListenableFuture<Boolean> f = d.render(bs, r, null, null);
+ ListenableFuture<Boolean> f = renderer.render(bs, result, null, null);
assertTrue(f.get());
- String summary = Utf8.toString(bs.toByteArray());
+ return Utf8.toString(bs.toByteArray());
+ }
- assertEquals("<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n" +
- "<result total-hit-count=\"0\"",
- summary.substring(0, 67)
- );
- assertTrue(summary.contains("<meta type=\"context\">"));
- assertTrue(summary.contains("<error code=\"18\">Internal server error.</error>"));
- assertTrue(summary.contains("<hit type=\"grouphit\" relevancy=\"1.0\">"));
- assertTrue(summary.contains("<hit type=\"summary\" relevancy=\"0.95\">"));
- assertEquals(2, occurrences("<error ", summary));
- assertTrue(summary.length() > 900);
+ private Result createResult(String sdName, String content, boolean bolding) {
+ Chain<Searcher> chain = createSearchChain(sdName, content);
+ Query query = new Query("?query=12");
+ if ( ! bolding)
+ query = new Query("?query=12&bolding=false");
+ Execution execution = createExecution(chain);
+ Result result = execution.search(query);
+ execution.fill(result);
+ return result;
+ }
+
+ /**
+ * Creates a search chain which always returns a result with one hit containing information given in this
+ *
+ * @param sdName the search definition type of the returned hit
+ * @param content the content of the "dynteaser" field of the returned hit
+ */
+ private Chain<Searcher> createSearchChain(String sdName, String content) {
+ JuniperSearcher searcher = new JuniperSearcher(new ComponentId("test"),
+ new QrSearchersConfig(new QrSearchersConfig.Builder()));
+
+ DocumentSourceSearcher docsource = new DocumentSourceSearcher();
+ addResult(new Query("?query=12"), sdName, content, docsource);
+ addResult(new Query("?query=12&bolding=false"), sdName, content, docsource);
+ return new Chain<>(searcher, docsource);
+ }
+
+ private void addResult(Query query, String sdName, String content, DocumentSourceSearcher docsource) {
+ Result r = new Result(query);
+ FastHit hit = new FastHit();
+ hit.setId("http://abc.html");
+ hit.setRelevance(new Relevance(1));
+ hit.setField(Hit.SDDOCNAME_FIELD, sdName);
+ hit.setField("dynteaser", content);
+ r.hits().add(hit);
+ docsource.addResult(query, r);
+ }
+
+ private Execution createExecution(Chain<Searcher> chain) {
+ Map<String, List<String>> clusters = new LinkedHashMap<>();
+ Map<String, SearchDefinition> searchDefs = new LinkedHashMap<>();
+ searchDefs.put("one", createSearchDefinitionOne());
+ SearchDefinition union = new SearchDefinition("union");
+ IndexModel indexModel = new IndexModel(clusters, searchDefs, union);
+ return new Execution(chain, Execution.Context.createContextStub(new IndexFacts(indexModel)));
}
- private int occurrences(String fragment, String string) {
- int occurrences = 0;
- int cursor = 0;
- while ( -1 != (cursor = string.indexOf(fragment, cursor))) {
- occurrences++;
- cursor += fragment.length();
+ private SearchDefinition createSearchDefinitionOne() {
+ SearchDefinition one = new SearchDefinition("one");
+
+ Index dynteaser = new Index("dynteaser");
+ dynteaser.setDynamicSummary(true);
+ one.addIndex(dynteaser);
+
+ Index bigteaser = new Index("bigteaser");
+ dynteaser.setHighlightSummary(true);
+ one.addIndex(bigteaser);
+
+ Index otherteaser = new Index("otherteaser");
+ otherteaser.setDynamicSummary(true);
+ one.addIndex(otherteaser);
+
+ return one;
+ }
+
+ public static class NoopSearcher extends Searcher {
+
+ public NoopSearcher(String name) {
+ super(new ComponentId(name));
}
- return occurrences;
+
+ @Override
+ public Result search(Query query, Execution execution) {
+ return execution.search(query);
+ }
+
}
}
diff --git a/container-search/src/test/java/com/yahoo/search/statistics/ElapsedTimeTestCase.java b/container-search/src/test/java/com/yahoo/search/statistics/ElapsedTimeTestCase.java
index 15937e77899..7491e970b23 100644
--- a/container-search/src/test/java/com/yahoo/search/statistics/ElapsedTimeTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/statistics/ElapsedTimeTestCase.java
@@ -50,6 +50,7 @@ public class ElapsedTimeTestCase {
}
public static class UselessSearcher extends Searcher {
+
public UselessSearcher(String name) {
super(new ComponentId(name));
}
@@ -58,9 +59,11 @@ public class ElapsedTimeTestCase {
public Result search(Query query, Execution execution) {
return execution.search(query);
}
+
}
private static class AlmostUselessSearcher extends Searcher {
+
AlmostUselessSearcher(String name) {
super(new ComponentId(name));
}
@@ -73,9 +76,11 @@ public class ElapsedTimeTestCase {
r.hits().add(h);
return r;
}
+
}
private static class NoForwardSearcher extends Searcher {
+
@Override
public Result search(Query query, Execution execution) {
Result r = new Result(query);
@@ -84,6 +89,7 @@ public class ElapsedTimeTestCase {
r.hits().add(h);
return r;
}
+
}
private class TestingSearcher extends Searcher {