aboutsummaryrefslogtreecommitdiffstats
path: root/container-search/src/test/java/com/yahoo
diff options
context:
space:
mode:
authorHenning Baldersheim <balder@yahoo-inc.com>2019-09-17 22:39:26 +0200
committerGitHub <noreply@github.com>2019-09-17 22:39:26 +0200
commita573985d1127835f0ecb5047694ffe23e8baefe7 (patch)
tree463ab5145edf4a26243cc4a92d04bfb82a3c1580 /container-search/src/test/java/com/yahoo
parentddb9cd0a539b57c41587ccdec1040b48169d3cec (diff)
Revert "Balder/no more fs4 dispatching from fastsearcher"
Diffstat (limited to 'container-search/src/test/java/com/yahoo')
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/PacketQueryTracerTestCase.java110
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/mplex/BackendTestCase.java208
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/test/GetDocSumsPacketTestCase.java107
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/test/HexByteIteratorTestCase.java42
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/test/PacketDecoderTestCase.java186
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/test/PacketTestCase.java229
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/test/QueryResultTestCase.java113
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/test/QueryTestCase.java319
-rw-r--r--container-search/src/test/java/com/yahoo/fs4/test/RankFeaturesTestCase.java (renamed from container-search/src/test/java/com/yahoo/search/query/test/RankFeaturesTestCase.java)8
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java67
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/FS4SearchInvokerTestCase.java72
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/DirectSearchTestCase.java137
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTestCase.java364
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTester.java127
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/MockDispatcher.java19
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/DispatchThread.java101
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockBackend.java53
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFDispatch.java212
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFS4ResourcePool.java63
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFSChannel.java176
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/NonWorkingMockFSChannel.java21
-rw-r--r--container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java5
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/vespa/HitConverterTestCase.java14
23 files changed, 2703 insertions, 50 deletions
diff --git a/container-search/src/test/java/com/yahoo/fs4/PacketQueryTracerTestCase.java b/container-search/src/test/java/com/yahoo/fs4/PacketQueryTracerTestCase.java
new file mode 100644
index 00000000000..20d9b61c177
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/PacketQueryTracerTestCase.java
@@ -0,0 +1,110 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.ByteBuffer;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.yahoo.fs4.mplex.FS4Channel;
+import com.yahoo.fs4.mplex.InvalidChannelException;
+import com.yahoo.search.Query;
+
+/**
+ * Tests hex dumping of packets.
+ *
+ * @author Steinar Knutsen
+ */
+public class PacketQueryTracerTestCase {
+
+ FS4Channel channel;
+ BasicPacket packet;
+ PacketListener tracer;
+
+ static class MockChannel extends FS4Channel {
+
+ @Override
+ public void setQuery(Query query) {
+ super.setQuery(query);
+ }
+
+ @Override
+ public Query getQuery() {
+ return super.getQuery();
+ }
+
+ @Override
+ public Integer getChannelId() {
+ return 1;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public boolean sendPacket(BasicPacket packet) {
+ return true;
+ }
+
+ @Override
+ public BasicPacket[] receivePackets(long timeout, int packetCount) {
+ return null;
+ }
+
+ @Override
+ public BasicPacket nextPacket(long timeout) {
+ return null;
+ }
+
+ @Override
+ protected void addPacket(BasicPacket packet) {
+ }
+
+ @Override
+ public boolean isValid() {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "MockChannel";
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ channel = new MockChannel();
+ channel.setQuery(new Query("/?query=a&tracelevel=11"));
+ packet = new PingPacket();
+ tracer = new PacketQueryTracer();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public final void testPacketSent() throws IOException {
+ byte[] simulatedPacket = new byte[] { 1, 2, 3 };
+ tracer.packetReceived(channel, packet, ByteBuffer.wrap(simulatedPacket));
+ StringWriter w = new StringWriter();
+ channel.getQuery().getContext(false).render(w);
+ assertTrue(w.getBuffer().toString().indexOf("PingPacket: 010203") != -1);
+ }
+
+ @Test
+ public final void testPacketReceived() throws IOException {
+ byte[] simulatedPacket = new byte[] { 1, 2, 3 };
+ tracer.packetReceived(channel, packet, ByteBuffer.wrap(simulatedPacket));
+ StringWriter w = new StringWriter();
+ channel.getQuery().getContext(false).render(w);
+ assertTrue(w.getBuffer().toString().indexOf("PingPacket: 010203") != -1);
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/fs4/mplex/BackendTestCase.java b/container-search/src/test/java/com/yahoo/fs4/mplex/BackendTestCase.java
new file mode 100644
index 00000000000..8696ca08d2b
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/mplex/BackendTestCase.java
@@ -0,0 +1,208 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4.mplex;
+
+import com.yahoo.container.QrConfig;
+import com.yahoo.container.search.Fs4Config;
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.ChannelTimeoutException;
+import com.yahoo.fs4.PingPacket;
+import com.yahoo.fs4.QueryPacket;
+import com.yahoo.fs4.mplex.Backend.BackendStatistics;
+import com.yahoo.prelude.fastsearch.FS4ResourcePool;
+import com.yahoo.search.Query;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test networking code for talking to dispatch.
+ *
+ * @author Steinar Knutsen
+ */
+public class BackendTestCase {
+ private static final long TIMEOUT = 30000;
+
+ public static class MockDispatch implements Runnable {
+
+ public final ServerSocket socket;
+ public volatile Socket connection;
+ volatile int channelId;
+
+ public byte[] packetData = new byte[] {0,0,0,104,
+ 0,0,0,217-256,
+ 0,0,0,1,
+ 0,0,0,0,
+ 0,0,0,2,
+ 0,0,0,0,0,0,0,5,
+ 0x40,0x39,0,0,0,0,0,0,
+ 0,0,0,111,
+ 0,0,0,97,
+ 0,0,0,3, 1,1,1,1,1,1,1,1,1,1,1,1, 0x40,0x37,0,0,0,0,0,23, 0,0,0,7, 0,0,0,36,
+ 0,0,0,4, 2,2,2,2,2,2,2,2,2,2,2,2, 0x40,0x35,0,0,0,0,0,21, 0,0,0,8, 0,0,0,37};
+
+ MockDispatch(ServerSocket socket) {
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+ try {
+ connection = socket.accept();
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+ requestRespond();
+ }
+
+ void requestRespond() {
+ byte[] length = new byte[4];
+ try {
+ connection.getInputStream().read(length);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+ int actual = ByteBuffer.wrap(length).getInt();
+
+ int read = 0;
+ int i = 0;
+ while (read != -1 && i < actual) {
+ try {
+ read = connection.getInputStream().read();
+ ++i;
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+ }
+ ByteBuffer reply = ByteBuffer.wrap(packetData);
+ if (channelId != -1) {
+ reply.putInt(8, channelId);
+ }
+ try {
+ connection.getOutputStream().write(packetData);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ public void setNoChannel() {
+ channelId = -1;
+ }
+
+ }
+
+ public static class MockServer {
+ public InetSocketAddress host;
+ public Thread worker;
+ public MockDispatch dispatch;
+
+ public MockServer() throws IOException {
+ ServerSocket socket = new ServerSocket(0, 50, InetAddress.getLoopbackAddress());
+ host = (InetSocketAddress) socket.getLocalSocketAddress();
+ dispatch = new MockDispatch(socket);
+ worker = new Thread(dispatch);
+ worker.start();
+ }
+
+ }
+
+ private Backend backend;
+ private MockServer server;
+ private Logger logger;
+ private boolean initUseParent;
+ private FS4ResourcePool listeners;
+
+ public static final byte[] PONG = new byte[] { 0, 0, 0, 32, 0, 0, 0, 221 - 256,
+ 0,0,0,1, 0, 0, 0, 42, 0, 0, 0, 127, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0,
+ 0, 0, 1 };
+
+ public void setUp() throws Exception {
+ logger = Logger.getLogger(Backend.class.getName());
+ initUseParent = logger.getUseParentHandlers();
+ logger.setUseParentHandlers(false);
+ listeners = new FS4ResourcePool(new Fs4Config(new Fs4Config.Builder()), new QrConfig(new QrConfig.Builder()));
+
+ server = new MockServer();
+ backend = listeners.getBackend(server.host.getHostString(), server.host.getPort());
+ }
+
+ public void tearDown() throws Exception {
+ listeners.deconstruct();
+ server.dispatch.socket.close();
+ if (server.dispatch.connection !=null) server.dispatch.connection.close();
+ if (server.worker!=null) server.worker.join();
+ if (logger !=null) logger.setUseParentHandlers(initUseParent);
+ }
+
+ private BasicPacket [] read(FS4Channel channel) throws InvalidChannelException {
+ try {
+ return channel.receivePackets(TIMEOUT, 1);
+ } catch (ChannelTimeoutException e) {
+ fail("Could not get packets from simulated backend.");
+ }
+ return new BasicPacket[1];
+ }
+
+ @Test
+ public void testAll() throws Exception {
+ setUp();
+ doTestBackend();
+ tearDown();
+
+ setUp();
+ doTestPinging();
+ tearDown();
+
+ setUp();
+ doRequireStatistics();
+ tearDown();
+ }
+
+ private void doTestBackend() throws IOException, InvalidChannelException {
+ FS4Channel channel = backend.openChannel();
+ Query q = new Query("/?query=a");
+ int channelId = channel.getChannelId();
+ server.dispatch.channelId = channelId;
+
+ assertTrue(backend.sendPacket(QueryPacket.create("container.0", q), channelId));
+ BasicPacket[] b = read(channel);
+ assertEquals(1, b.length);
+ assertEquals(217, b[0].getCode());
+ channel.close();
+ }
+
+ private void doTestPinging() throws IOException, InvalidChannelException {
+ FS4Channel channel = backend.openPingChannel();
+ server.dispatch.setNoChannel();
+ server.dispatch.packetData = PONG;
+
+ assertTrue(channel.sendPacket(new PingPacket()));
+ BasicPacket[] b = read(channel);
+ assertEquals(1, b.length);
+ assertEquals(221, b[0].getCode());
+ channel.close();
+ }
+
+ private void doRequireStatistics() throws IOException, InvalidChannelException {
+ FS4Channel channel = backend.openPingChannel();
+ server.dispatch.channelId = -1;
+ server.dispatch.packetData = PONG;
+
+ assertTrue(channel.sendPacket(new PingPacket()));
+ read(channel);
+ BackendStatistics stats = backend.getStatistics();
+ assertEquals(1, stats.totalConnections());
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/fs4/test/GetDocSumsPacketTestCase.java b/container-search/src/test/java/com/yahoo/fs4/test/GetDocSumsPacketTestCase.java
new file mode 100644
index 00000000000..527a2736fee
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/test/GetDocSumsPacketTestCase.java
@@ -0,0 +1,107 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4.test;
+
+import com.yahoo.fs4.BufferTooSmallException;
+import com.yahoo.fs4.GetDocSumsPacket;
+import com.yahoo.prelude.fastsearch.FastHit;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.query.SessionId;
+import com.yahoo.search.result.Hit;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests the GetDocsumsPacket
+ *
+ * @author Bjorn Borud
+ */
+public class GetDocSumsPacketTestCase {
+
+ private static final byte IGNORE = 69;
+
+ @Test
+ public void testDefaultDocsumClass() {
+ Query query = new Query("/?query=chain");
+ assertNull(query.getPresentation().getSummary());
+ }
+
+ @Test
+ public void testEncodingWithQuery() throws BufferTooSmallException {
+ Hit hit = new FastHit();
+ assertPacket(true, hit, new byte[] {0, 0, 0, 53, 0, 0, 0, -37, 0, 0, 8, 21, 0, 0, 0, 0, IGNORE, IGNORE, IGNORE,
+ IGNORE, 7, 100, 101, 102, 97, 117, 108, 116, 0, 0, 0, 0x03, 0, 0, 0, 7,
+ 100, 101, 102, 97, 117, 108, 116, 0, 0, 0, 1, 0, 0, 0, 6, 4, 0, 3, 102, 111, 111});
+ }
+
+ @Test
+ public void testEncodingWithoutQuery() throws BufferTooSmallException {
+ Hit hit = new FastHit();
+ assertPacket(false, hit, new byte[] { 0, 0, 0, 39, 0, 0, 0, -37, 0, 0, 8, 17, 0, 0, 0, 0, IGNORE, IGNORE, IGNORE,
+ IGNORE, 7, 100, 101, 102, 97, 117, 108, 116, 0, 0, 0, 0x03, 0, 0, 0, 7, 100, 101, 102, 97, 117, 108, 116
+ });
+ }
+
+ @Test
+ public void requireThatSessionIdIsEncodedAsPropertyWhenUsingSearchSession() throws BufferTooSmallException {
+ Result result = new Result(new Query("?query=foo&groupingSessionCache=false"));
+ SessionId sessionId = result.getQuery().getSessionId("node-0");
+ result.getQuery().getRanking().setQueryCache(true);
+ FastHit hit = new FastHit();
+ result.hits().add(hit);
+ ByteBuffer answer = ByteBuffer.allocate(1024);
+ //assertEquals(0, sessionId.asUtf8String().getByteLength());
+ answer.put(new byte[] { 0, 0, 0, (byte)(103+sessionId.asUtf8String().getByteLength()), 0, 0, 0, -37, 0, 0, 24, 17, 0, 0, 0, 0,
+ // query timeout
+ IGNORE, IGNORE, IGNORE, IGNORE,
+ // "default" - rank profile
+ 7, 'd', 'e', 'f', 'a', 'u', 'l', 't', 0, 0, 0, 0x03,
+ // "default" - summaryclass
+ 0, 0, 0, 7, 'd', 'e', 'f', 'a', 'u', 'l', 't',
+ // 2 property entries
+ 0, 0, 0, 2,
+ // rank: sessionId => qrserver.0.XXXXXXXXXXXXX.0
+ 0, 0, 0, 4, 'r', 'a', 'n', 'k', 0, 0, 0, 1, 0, 0, 0, 9, 's', 'e', 's', 's', 'i', 'o', 'n', 'I', 'd'});
+ answer.putInt(sessionId.asUtf8String().getByteLength());
+ answer.put(sessionId.asUtf8String().getBytes());
+ answer.put(new byte [] {
+ // caches: features => true
+ 0, 0, 0, 6, 'c', 'a', 'c', 'h', 'e', 's',
+ 0, 0, 0, 1, 0, 0, 0, 5, 'q', 'u', 'e', 'r', 'y', 0, 0, 0, 4, 't', 'r', 'u', 'e'});
+ byte [] expected = new byte [answer.position()];
+ answer.flip();
+ answer.get(expected);
+ assertPacket(false, result, expected);
+ }
+
+ private static void assertPacket(boolean sendQuery, Hit hit, byte[] expected) throws BufferTooSmallException {
+ Result result = new Result(new Query("?query=foo"));
+ result.hits().add(hit);
+ assertPacket(sendQuery, result, expected);
+ }
+
+ private static void assertPacket(boolean sendQuery, Result result, byte[] expected) throws BufferTooSmallException {
+ GetDocSumsPacket packet = GetDocSumsPacket.create(result, "default", sendQuery);
+ ByteBuffer buf = ByteBuffer.allocate(1024);
+ packet.encode(buf);
+ buf.flip();
+
+ byte[] actual = new byte[buf.remaining()];
+ buf.get(actual);
+ // assertEquals(Arrays.toString(expected), Arrays.toString(actual));
+
+ assertEquals("Equal length", expected.length, actual.length);
+ for (int i = 0; i < expected.length; ++i) {
+ if (expected[i] == IGNORE) {
+ actual[i] = IGNORE;
+ }
+ }
+
+ assertArrayEquals(expected, actual);
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/fs4/test/HexByteIteratorTestCase.java b/container-search/src/test/java/com/yahoo/fs4/test/HexByteIteratorTestCase.java
new file mode 100644
index 00000000000..022feb09b2e
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/test/HexByteIteratorTestCase.java
@@ -0,0 +1,42 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4.test;
+
+import java.nio.ByteBuffer;
+
+import com.yahoo.fs4.HexByteIterator;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test of HexByteIterator
+ *
+ * @author Tony Vaagenes
+ */
+public class HexByteIteratorTestCase {
+
+ @Test
+ public void testHexByteIterator() {
+ int[] numbers = { 0x00, 0x01, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF };
+
+ HexByteIterator i = new HexByteIterator(
+ ByteBuffer.wrap(toBytes(numbers)));
+
+ assertEquals("00", i.next());
+ assertEquals("01", i.next());
+ assertEquals("DE", i.next());
+ assertEquals("AD", i.next());
+ assertEquals("BE", i.next());
+ assertEquals("EF", i.next());
+ assertEquals("FF", i.next());
+ assertTrue(!i.hasNext());
+ }
+
+ private byte[] toBytes(int[] ints) {
+ byte[] bytes = new byte[ints.length];
+ for (int i=0; i<bytes.length; ++i)
+ bytes[i] = (byte)ints[i];
+ return bytes;
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/fs4/test/PacketDecoderTestCase.java b/container-search/src/test/java/com/yahoo/fs4/test/PacketDecoderTestCase.java
new file mode 100644
index 00000000000..2a6ab6a7644
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/test/PacketDecoderTestCase.java
@@ -0,0 +1,186 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4.test;
+
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.BufferTooSmallException;
+import com.yahoo.fs4.ErrorPacket;
+import com.yahoo.fs4.PacketDecoder;
+import com.yahoo.fs4.PacketDecoder.DecodedPacket;
+import com.yahoo.fs4.QueryResultPacket;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests the PacketDecoder
+ *
+ * @author Bjørn Borud
+ */
+public class PacketDecoderTestCase {
+
+ static byte[] queryResultPacketData
+ = new byte[] {0,0,0,104,
+ 0,0,0,217-256,
+ 0,0,0,1,
+ 0,0,0,0,
+ 0,0,0,2,
+ 0,0,0,0,0,0,0,5,
+ 0x40,0x39,0,0,0,0,0,0,
+ 0,0,0,111,
+ 0,0,0,97,
+ 0,0,0,3, 1,1,1,1,1,1,1,1,1,1,1,1, 0x40,0x37,0,0,0,0,0,23, 0,0,0,7, 0,0,0,36,
+ 0,0,0,4, 2,2,2,2,2,2,2,2,2,2,2,2, 0x40,0x35,0,0,0,0,0,21, 0,0,0,8, 0,0,0,37};
+ static int len = queryResultPacketData.length;
+
+ /**
+ * In this testcase we have exactly one packet which fills the
+ * entire buffer
+ */
+ @Test
+ public void testOnePacket () throws BufferTooSmallException {
+ ByteBuffer data = ByteBuffer.allocate(len);
+ data.put(queryResultPacketData);
+ data.flip();
+
+ // not really necessary for testing, but these help visualize
+ // the state the buffer should be in so a reader of this test
+ // will not have to
+ assertEquals(0, data.position());
+ assertEquals(len, data.limit());
+ assertEquals(len, data.capacity());
+ assertEquals(data.limit(), data.capacity());
+
+ PacketDecoder.DecodedPacket p = PacketDecoder.extractPacket(data);
+ assertTrue(p.packet instanceof QueryResultPacket);
+
+ // now the buffer should have position == capacity == limit
+ assertEquals(len, data.position());
+ assertEquals(len, data.limit());
+ assertEquals(len, data.capacity());
+
+ // next call to decode on same bufer should result
+ // in null and buffer should be reset for writing.
+ p = PacketDecoder.extractPacket(data);
+ assertTrue(p == null);
+
+ // make sure the buffer is now ready for reading
+ assertEquals(0, data.position());
+ assertEquals(len, data.limit());
+ assertEquals(len, data.capacity());
+ }
+
+ /**
+ * In this testcase we only have 3 bytes so we can't
+ * even determine the size of the packet.
+ */
+ @Test
+ public void testThreeBytesPacket () throws BufferTooSmallException {
+ ByteBuffer data = ByteBuffer.allocate(len);
+ data.put(queryResultPacketData, 0, 3);
+ data.flip();
+
+ // packetLength() should return -1 since we don't even have
+ // the size of the packet
+ assertEquals(-1, PacketDecoder.packetLength(data));
+
+ // since we can't determine the size we don't get a packet.
+ // the buffer should now be at offset 3 so we can read more
+ // data and limit should be set to capacity
+ PacketDecoder.DecodedPacket p = PacketDecoder.extractPacket(data);
+ assertTrue(p == null);
+ assertEquals(3, data.position());
+ assertEquals(len, data.limit());
+ assertEquals(len, data.capacity());
+ }
+
+ /**
+ * In this testcase we have a partial packet and room for
+ * more data
+ */
+ @Test
+ public void testPartialWithMoreRoom () throws BufferTooSmallException {
+ ByteBuffer data = ByteBuffer.allocate(len);
+ data.put(queryResultPacketData, 0, 10);
+ data.flip();
+
+ PacketDecoder.DecodedPacket p = PacketDecoder.extractPacket(data);
+ assertTrue(p == null);
+
+ }
+
+ /**
+ * In this testcase we have one and a half packet
+ */
+ @Test
+ public void testOneAndAHalfPackets () throws BufferTooSmallException {
+ int half = len / 2;
+ ByteBuffer data = ByteBuffer.allocate(len + half);
+ data.put(queryResultPacketData);
+ data.put(queryResultPacketData, 0, half);
+ assertEquals((len + half), data.position());
+ data.flip();
+
+ // the first packet we should be able to extract just fine
+ BasicPacket p1 = PacketDecoder.extractPacket(data).packet;
+ assertTrue(p1 instanceof QueryResultPacket);
+
+ PacketDecoder.DecodedPacket p2 = PacketDecoder.extractPacket(data);
+ assertTrue(p2 == null);
+
+ // at this point the buffer should be ready for more
+ // reading so position should be at the end and limit
+ // should be at capacity
+ assertEquals(half, data.position());
+ assertEquals(data.capacity(), data.limit());
+ }
+
+ /**
+ * Test the case where the buffer is too small for the
+ * packet
+ */
+ @Test
+ public void testTooSmallBufferForPacket () {
+ ByteBuffer data = ByteBuffer.allocate(10);
+ data.put(queryResultPacketData, 0, 10);
+ data.flip();
+
+ try {
+ PacketDecoder.extractPacket(data);
+ fail();
+ }
+ catch (BufferTooSmallException e) {
+
+ }
+ }
+
+ @Test
+ public void testErrorPacket() throws BufferTooSmallException {
+ ByteBuffer b = ByteBuffer.allocate(100);
+ b.putInt(0);
+ b.putInt(203);
+ b.putInt(1);
+ b.putInt(37);
+ b.putInt(5);
+ b.put(new byte[] { (byte) 'n', (byte) 'a', (byte) 'l', (byte) 'l', (byte) 'e' });
+ b.putInt(0, b.position() - 4);
+ b.flip();
+ DecodedPacket p = PacketDecoder.extractPacket(b);
+ ErrorPacket e = (ErrorPacket) p.packet;
+ assertEquals("nalle (37)", e.toString());
+ assertEquals(203, e.getCode());
+ assertEquals(37, e.getErrorCode());
+ b = ByteBuffer.allocate(100);
+ // warn if encoding support is added untested
+ e.encode(b);
+ b.position(0);
+ assertEquals(4, b.getInt());
+ assertEquals(203, b.getInt());
+ assertFalse(b.hasRemaining());
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/fs4/test/PacketTestCase.java b/container-search/src/test/java/com/yahoo/fs4/test/PacketTestCase.java
new file mode 100644
index 00000000000..7ee445bc6ba
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/test/PacketTestCase.java
@@ -0,0 +1,229 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4.test;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.BufferTooSmallException;
+import com.yahoo.fs4.Packet;
+import com.yahoo.fs4.QueryPacket;
+import com.yahoo.search.Query;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests the Packet class. Specifically made this unit test suite
+ * for checking that queries that are too large for the buffer
+ * are handled gracefully.
+ *
+ * @author Bjorn Borud
+ */
+public class PacketTestCase {
+
+ /**
+ * Make sure we don't get false negatives for reasonably sized
+ * buffers
+ */
+ @Test
+ public void testSmallQueryOK () {
+ Query query = new Query("/?query=foo");
+ assertNotNull(query);
+
+ QueryPacket queryPacket = QueryPacket.create("container.0", query);
+ assertNotNull(queryPacket);
+
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ int position = buffer.position();
+
+ try {
+ queryPacket.encode(buffer, 0);
+ }
+ catch (BufferTooSmallException e) {
+ fail();
+ }
+
+ // make sure state of buffer HAS changed and is according
+ // to contract
+ assertTrue(position != buffer.position());
+ assertTrue(buffer.position() == buffer.limit());
+ }
+
+ /**
+ * Make a query that is too large and then try to encode it
+ * into a small ByteBuffer
+ */
+ @Test
+ public void testLargeQueryFail () {
+ StringBuilder queryBuffer = new StringBuilder(4008);
+ queryBuffer.append("/?query=");
+ for (int i=0; i < 1000; i++) {
+ queryBuffer.append("the%20");
+ }
+ Query query = new Query(queryBuffer.toString());
+ assertNotNull(query);
+
+ QueryPacket queryPacket = QueryPacket.create("container.0", query);
+ assertNotNull(queryPacket);
+
+ ByteBuffer buffer = ByteBuffer.allocate(100);
+ int position = buffer.position();
+ int limit = buffer.limit();
+ try {
+ queryPacket.encode(buffer, 0);
+ fail();
+ }
+ catch (BufferTooSmallException e) {
+ // success if exception is thrown
+ }
+
+ // make sure state of buffer is unchanged
+ assertEquals(position, buffer.position());
+ assertEquals(limit, buffer.limit());
+ }
+
+ @Test
+ public void requireThatPacketsCanTurnOnCompression() throws BufferTooSmallException {
+ QueryPacket queryPacket = QueryPacket.create("container.0", new Query("/?query=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&groupingSessionCache=false"));
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ int channel = 32;
+
+ queryPacket.encode(buffer, channel);
+ buffer.flip();
+ assertEquals(86, buffer.getInt()); // size
+ assertEquals(0xda, buffer.getInt()); // code
+ assertEquals(channel, buffer.getInt());
+
+ queryPacket.setCompressionLimit(88);
+ buffer.clear();
+ queryPacket.encode(buffer, channel);
+ buffer.flip();
+ assertEquals(86, buffer.getInt()); // size
+ assertEquals(0xda, buffer.getInt()); // code
+
+ queryPacket.setCompressionLimit(84);
+ buffer.clear();
+ queryPacket.encode(buffer, channel);
+ buffer.flip();
+ assertEquals(57, buffer.getInt()); // size
+ assertEquals(0x060000da, buffer.getInt()); // code
+ assertEquals(channel, buffer.getInt());
+ }
+
+ @Test
+ public void requireThatUncompressablePacketsArentCompressed() throws BufferTooSmallException {
+ QueryPacket queryPacket = QueryPacket.create("container.0", new Query("/?query=aaaaaaaaaaaaaaa&groupingSessionCache=false"));
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ int channel = 32;
+
+ queryPacket.setCompressionLimit(10);
+ buffer.clear();
+ queryPacket.encode(buffer, channel);
+ buffer.flip();
+ assertEquals(56, buffer.getInt()); // size
+ assertEquals(0xda, buffer.getInt()); // code
+ assertEquals(channel, buffer.getInt());
+ }
+
+ class MyPacket extends Packet {
+ private String bodyString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ private int myCode = 1234;
+
+ @Override
+ public int getCode() {
+ return myCode;
+ }
+
+ @Override
+ protected void encodeBody(ByteBuffer buffer) {
+ buffer.put(bodyString.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void codeDecodedHook(int code) {
+ assertEquals(myCode, code);
+ }
+
+ @Override
+ public void decodeBody(ByteBuffer buffer) {
+ byte[] bytes = new byte[bodyString.length()];
+ buffer.get(bytes);
+ assertEquals(bodyString, new String(bytes));
+ }
+ }
+
+ @Test
+ public void requireThatCompressedPacketsCanBeDecompressed() throws BufferTooSmallException {
+
+ MyPacket packet = new MyPacket();
+ packet.setCompressionLimit(10);
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ int channel = 32;
+ packet.encode(buffer, channel);
+
+ buffer.flip();
+ new MyPacket().decode(buffer);
+ }
+
+ @Test
+ public void requireThatCompressedByteBufferMayContainExtraData() throws BufferTooSmallException {
+
+ MyPacket packet = new MyPacket();
+ packet.setCompressionLimit(10);
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ buffer.putLong(0xdeadbeefL);
+ int channel = 32;
+ packet.encode(buffer, channel);
+ buffer.limit(buffer.limit() + 8);
+ buffer.putLong(0xdeadbeefL);
+
+ buffer.flip();
+ assertEquals(0xdeadbeefL, buffer.getLong()); // read initial content.
+ new MyPacket().decode(buffer);
+ assertEquals(0xdeadbeefL, buffer.getLong()); // read final content.
+ }
+
+ class MyBasicPacket extends BasicPacket {
+ private String bodyString = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ private int myCode = 1234;
+
+ @Override
+ public int getCode() {
+ return myCode;
+ }
+
+ @Override
+ protected void encodeBody(ByteBuffer buffer) {
+ buffer.put(bodyString.getBytes(StandardCharsets.UTF_8));
+ }
+
+ @Override
+ public void codeDecodedHook(int code) {
+ assertEquals(myCode, code);
+ }
+
+ @Override
+ public void decodeBody(ByteBuffer buffer) {
+ byte[] bytes = new byte[bodyString.length()];
+ buffer.get(bytes);
+ assertEquals(bodyString, new String(bytes));
+ }
+ }
+
+ @Test
+ public void requireThatCompressedBasicPacketsCanBeDecompressed() throws BufferTooSmallException {
+
+ MyBasicPacket packet = new MyBasicPacket();
+ packet.setCompressionLimit(10);
+ ByteBuffer buffer = ByteBuffer.allocate(1024);
+ packet.encode(buffer);
+
+ buffer.flip();
+ new MyBasicPacket().decode(buffer);
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/fs4/test/QueryResultTestCase.java b/container-search/src/test/java/com/yahoo/fs4/test/QueryResultTestCase.java
new file mode 100644
index 00000000000..edf117b1195
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/test/QueryResultTestCase.java
@@ -0,0 +1,113 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4.test;
+
+import com.yahoo.document.GlobalId;
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.DocumentInfo;
+import com.yahoo.fs4.PacketDecoder;
+import com.yahoo.fs4.QueryResultPacket;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests encoding of query packages
+ *
+ * @author bratseth
+ */
+public class QueryResultTestCase {
+
+ private static final double delta = 0.00000001;
+
+ private static GlobalId gid1 = new GlobalId(new byte[] {1,1,1,1,1,1,1,1,1,1,1,1});
+ private static GlobalId gid2 = new GlobalId(new byte[] {2,2,2,2,2,2,2,2,2,2,2,2});
+
+ @Test
+ public void testDecodeQueryResultX() {
+ byte[] packetData = new byte[] {
+ 0,0,0,100,
+ 0,0,0,217-256,
+ 0,0,0,1,
+ 0,0,0,1,
+ 0,0,0,0,
+ 0,0,0,2,
+ 0,0,0,0,0,0,0,5,
+ 0x40,0x39,0,0,0,0,0,0,
+ 0,0,0,111,
+ 0,0,0,0,0,0,0,89,
+ 0,0,0,0,0,0,0,90,
+ 0,0,0,0,0,0,0,91,
+ 0,0,0,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1, 0x40,0x37,0,0,0,0,0,0, 0,0,0,7, 0,0,0,36,
+ 2,2,2,2,2,2,2,2,2,2,2,2, 0x40,0x35,0,0,0,0,0,0, 0,0,0,8, 0,0,0,37
+ };
+ ByteBuffer buffer = ByteBuffer.allocate(200);
+ buffer.put(packetData);
+ buffer.flip();
+ BasicPacket packet = PacketDecoder.decode(buffer);
+ assertTrue(packet instanceof QueryResultPacket);
+ QueryResultPacket result = (QueryResultPacket)packet;
+
+ assertTrue(result.getMldFeature());
+
+ assertEquals(5, result.getTotalDocumentCount());
+ assertEquals(25, result.getMaxRank());
+ assertEquals(111, result.getDocstamp());
+ assertEquals(89, result.getCoverageDocs());
+ assertEquals(90, result.getActiveDocs());
+ assertEquals(91, result.getSoonActiveDocs());
+ assertEquals(1, result.getDegradedReason());
+
+ assertEquals(2, result.getDocuments().size());
+ DocumentInfo document1 = result.getDocuments().get(0);
+ assertEquals(gid1, document1.getGlobalId());
+ assertEquals(23.0, document1.getMetric(), delta);
+ assertEquals(7, document1.getPartId());
+ assertEquals(36, document1.getDistributionKey());
+ DocumentInfo document2 = result.getDocuments().get(1);
+ assertEquals(gid2, document2.getGlobalId());
+ assertEquals(21.0, document2.getMetric(), delta);
+ assertEquals(8, document2.getPartId());
+ assertEquals(37, document2.getDistributionKey());
+ }
+
+ @Test
+ public void testDecodeQueryResultMoreHits() {
+ byte[] packetData = new byte[] {
+ 0,0,0,100,
+ 0,0,0,217-256,
+ 0,0,0,1,
+ 0,0,0,3,
+ 0,0,0,0,
+ 0,0,0,2,
+ 0,0,0,0,0,0,0,5,
+ 0x40,0x39,0,0,0,0,0,0,
+ 0,0,0,111,
+ 0,6,0,5,
+ 0,0,0,0,0,0,0,89,
+ 0,0,0,0,0,0,0,90,
+ 0,0,0,0,0,0,0,91,
+ 0,0,0,1,
+ 1,1,1,1,1,1,1,1,1,1,1,1, 0x40,0x37,0,0,0,0,0,0, 0,0,0,7, 0,0,0,36,
+ 2,2,2,2,2,2,2,2,2,2,2,2, 0x40,0x35,0,0,0,0,0,0, 0,0,0,8, 0,0,0,37
+ };
+ ByteBuffer buffer = ByteBuffer.allocate(200);
+ buffer.put(packetData);
+ buffer.flip();
+ BasicPacket packet = PacketDecoder.decode(buffer);
+ assertTrue(packet instanceof QueryResultPacket);
+ QueryResultPacket result = (QueryResultPacket)packet;
+
+ assertEquals(2, result.getDocuments().size());
+ DocumentInfo document1 = result.getDocuments().get(0);
+ assertEquals(gid1, document1.getGlobalId());
+ DocumentInfo document2 = result.getDocuments().get(1);
+ assertEquals(gid2, document2.getGlobalId());
+ assertEquals(6, result.getNodesQueried());
+ assertEquals(5, result.getNodesReplied());
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/fs4/test/QueryTestCase.java b/container-search/src/test/java/com/yahoo/fs4/test/QueryTestCase.java
new file mode 100644
index 00000000000..10d55b91131
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/fs4/test/QueryTestCase.java
@@ -0,0 +1,319 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.fs4.test;
+
+import com.yahoo.fs4.BufferTooSmallException;
+import com.yahoo.fs4.Packet;
+import com.yahoo.fs4.QueryPacket;
+import com.yahoo.prelude.Freshness;
+import com.yahoo.prelude.query.AndItem;
+import com.yahoo.prelude.query.Highlight;
+import com.yahoo.prelude.query.PhraseItem;
+import com.yahoo.prelude.query.PhraseSegmentItem;
+import com.yahoo.prelude.query.WeightedSetItem;
+import com.yahoo.prelude.query.WordItem;
+import com.yahoo.search.Query;
+import com.yahoo.search.query.ranking.SoftTimeout;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Tests encoding of query x packages
+ *
+ * @author bratseth
+ */
+public class QueryTestCase {
+
+ @Test
+ public void testEncodePacket() {
+ Query query = new Query("/?query=chain&timeout=0&groupingSessionCache=false");
+ query.setWindow(2, 8);
+ QueryPacket packet = QueryPacket.create("container.0", query);
+ assertEquals(2, packet.getOffset());
+ assertEquals(8, packet.getHits());
+
+ byte[] encoded = packetToBytes(packet);
+ byte[] correctBuffer = new byte[] {0,0,0,46,0,0,0,-38,0,0,0,0, // Header
+ 0,0,0,6, // Features
+ 2,
+ 8,
+ 0,0,0,1, // querytimeout
+ 0,0,0x40,0x03, // qflags
+ 7,
+ 'd', 'e', 'f', 'a', 'u', 'l', 't',
+ 0,0,0,1,0,0,0,8,4,
+ 0,5,
+ 99,104,97,105,110};
+ assertEqualArrays(correctBuffer, encoded);
+ }
+
+ @Test
+ public void testEncodeQueryPacketWithSomeAdditionalFeatures() {
+ Query query = new Query("/?query=chain&dataset=10&type=phrase&timeout=0&groupingSessionCache=false");
+ query.properties().set(SoftTimeout.enableProperty, false);
+
+ // Because the rank mapping now needs config and a searcher,
+ // we do the sledgehammer dance:
+ query.getRanking().setProfile("two");
+ query.setWindow(2, 8);
+ QueryPacket packet = QueryPacket.create("container.0", query);
+ byte[] encoded = packetToBytes(packet);
+ byte[] correctBuffer = new byte[] {0,0,0,42,0,0,0,-38,0,0,0,0, // Header
+ 0,0,0,6, // Features
+ 2,
+ 8,
+ 0,0,0,1, // querytimeout
+ 0,0,0x40,0x03, // QFlags
+ 3,
+ 't','w','o', // Ranking
+ 0,0,0,1,0,0,0,8,4,
+ 0,5,
+ 99,104,97,105,110};
+ assertEqualArrays(correctBuffer, encoded);
+ }
+
+ /** This test will tell you if you have screwed up the binary encoding, but it won't tell you how */
+ @Test
+ public void testEncodeQueryPacketWithManyFeatures() {
+ Query query = new Query("/?query=chain" +
+ "&ranking.features.query(foo)=30.3&ranking.features.query(bar)=0" +
+ "&ranking.properties.property.p1=v1&ranking.properties.property.p2=v2" +
+ "&pos.ll=S22.4532;W123.9887&pos.radius=3&pos.attribute=place&ranking.freshness=37" +
+ "&model.searchPath=7/3" +
+ "&groupingSessionCache=false");
+ query.getRanking().setFreshness(new Freshness("123456"));
+ query.getRanking().setSorting("+field1 -field2");
+ query.getRanking().setProfile("two");
+ Highlight highlight = new Highlight();
+ highlight.addHighlightTerm("field1", "term1");
+ highlight.addHighlightTerm("field1", "term2");
+ query.getPresentation().setHighlight(highlight);
+
+ query.prepare();
+
+ QueryPacket packet = QueryPacket.create("container.0", query);
+ byte[] encoded = packetToBytes(packet);
+ byte[] correctBuffer=new byte[] {
+ 0, 0, 1, 23, 0, 0, 0, -38, 0, 0, 0, 0, 0, 16, 0, -122, 0, 10, ignored, ignored, ignored, ignored, 0, 0, 0x40, 0x03, 3, 't', 'w', 'o', 0, 0, 0, 3, 0, 0, 0, 4, 'r', 'a', 'n', 'k', 0, 0, 0, 5, 0, 0, 0, 11, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y', 46, 'p', '2', 0, 0, 0, 2, 'v', '2', 0, 0, 0, 11, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y', 46, 'p', '1', 0, 0, 0, 2, 'v', '1', 0, 0, 0, 3, 'f', 'o', 'o', 0, 0, 0, 4, '3', '0', 46, '3', 0, 0, 0, 3, 'b', 'a', 'r', 0, 0, 0, 1, '0', 0, 0, 0, 9, 'v', 'e', 's', 'p', 'a', 46, 'n', 'o', 'w', 0, 0, 0, 6, '1', '2', '3', '4', '5', '6', 0, 0, 0, 14, 'h', 'i', 'g', 'h', 'l', 'i', 'g', 'h', 't', 't', 'e', 'r', 'm', 's', 0, 0, 0, 3, 0, 0, 0, 6, 'f', 'i', 'e', 'l', 'd', '1', 0, 0, 0, 1, '2', 0, 0, 0, 6, 'f', 'i', 'e', 'l', 'd', '1', 0, 0, 0, 5, 't', 'e', 'r', 'm', '1', 0, 0, 0, 6, 'f', 'i', 'e', 'l', 'd', '1', 0, 0, 0, 5, 't', 'e', 'r', 'm', '2', 0, 0, 0, 5, 'm', 'o', 'd', 'e', 'l', 0, 0, 0, 1, 0, 0, 0, 10, 's', 'e', 'a', 'r', 'c', 'h', 'p', 'a', 't', 'h', 0, 0, 0, 3, '7', 47, '3', 0, 0, 0, 15, 43, 'f', 'i', 'e', 'l', 'd', '1', 32, 45, 'f', 'i', 'e', 'l', 'd', '2', 0, 0, 0, 1, 0, 0, 0, 9, 68, 1, 0, 5, 'c', 'h', 'a', 'i', 'n'
+ };
+ assertEqualArrays(correctBuffer,encoded);
+ }
+
+ /** This test will tell you if you have screwed up the binary encoding, but it won't tell you how */
+ @Test
+ public void testEncodeQueryPacketWithManyFeaturesFresnhessAsString() {
+ Query query = new Query("/?query=chain" +
+ "&ranking.features.query(foo)=30.3&ranking.features.query(bar)=0" +
+ "&ranking.properties.property.p1=v1&ranking.properties.property.p2=v2" +
+ "&pos.ll=S22.4532;W123.9887&pos.radius=3&pos.attribute=place&ranking.freshness=37" +
+ "&model.searchPath=7/3" +
+ "&groupingSessionCache=false");
+ query.getRanking().setFreshness("123456");
+ query.getRanking().setSorting("+field1 -field2");
+ query.getRanking().setProfile("two");
+ Highlight highlight = new Highlight();
+ highlight.addHighlightTerm("field1", "term1");
+ highlight.addHighlightTerm("field1", "term2");
+ query.getPresentation().setHighlight(highlight);
+
+ query.prepare();
+
+ QueryPacket packet = QueryPacket.create("container.0", query);
+ byte[] encoded = packetToBytes(packet);
+ byte[] correctBuffer=new byte[] {
+ 0, 0, 1, 23, 0, 0, 0, -38, 0, 0, 0, 0, 0, 16, 0, -122, 0, 10, ignored, ignored, ignored, ignored, 0, 0, 0x40, 0x03, 3, 't', 'w', 'o', 0, 0, 0, 3, 0, 0, 0, 4, 'r', 'a', 'n', 'k', 0, 0, 0, 5, 0, 0, 0, 11, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y', 46, 'p', '2', 0, 0, 0, 2, 'v', '2', 0, 0, 0, 11, 'p', 'r', 'o', 'p', 'e', 'r', 't', 'y', 46, 'p', '1', 0, 0, 0, 2, 'v', '1', 0, 0, 0, 3, 'f', 'o', 'o', 0, 0, 0, 4, '3', '0', 46, '3', 0, 0, 0, 3, 'b', 'a', 'r', 0, 0, 0, 1, '0', 0, 0, 0, 9, 'v', 'e', 's', 'p', 'a', 46, 'n', 'o', 'w', 0, 0, 0, 6, '1', '2', '3', '4', '5', '6', 0, 0, 0, 14, 'h', 'i', 'g', 'h', 'l', 'i', 'g', 'h', 't', 't', 'e', 'r', 'm', 's', 0, 0, 0, 3, 0, 0, 0, 6, 'f', 'i', 'e', 'l', 'd', '1', 0, 0, 0, 1, '2', 0, 0, 0, 6, 'f', 'i', 'e', 'l', 'd', '1', 0, 0, 0, 5, 't', 'e', 'r', 'm', '1', 0, 0, 0, 6, 'f', 'i', 'e', 'l', 'd', '1', 0, 0, 0, 5, 't', 'e', 'r', 'm', '2', 0, 0, 0, 5, 'm', 'o', 'd', 'e', 'l', 0, 0, 0, 1, 0, 0, 0, 10, 's', 'e', 'a', 'r', 'c', 'h', 'p', 'a', 't', 'h', 0, 0, 0, 3, '7', 47, '3', 0, 0, 0, 15, 43, 'f', 'i', 'e', 'l', 'd', '1', 32, 45, 'f', 'i', 'e', 'l', 'd', '2', 0, 0, 0, 1, 0, 0, 0, 9, 68, 1, 0, 5, 'c', 'h', 'a', 'i', 'n'
+ };
+ assertEqualArrays(correctBuffer, encoded);
+ }
+
+ @Test
+ public void testEncodeQueryPacketWithLabelsConnectivityAndSignificance() {
+ Query query = new Query();
+ query.setGroupingSessionCache(false);
+ AndItem and = new AndItem();
+ WeightedSetItem taggable1 = new WeightedSetItem("field1");
+ taggable1.setLabel("foo");
+ WeightedSetItem taggable2 = new WeightedSetItem("field2");
+ taggable1.setLabel("bar");
+ and.addItem(taggable1);
+ and.addItem(taggable2);
+ WordItem word1 = new WordItem("word1", "field3");
+ word1.setSignificance(0.37);
+ WordItem word2 = new WordItem("word1", "field3");
+ word2.setSignificance(0.81);
+ word2.setConnectivity(word1, 0.15);
+ and.addItem(word1);
+ and.addItem(word2);
+
+ query.getModel().getQueryTree().setRoot(and);
+
+ query.prepare();
+
+ QueryPacket packet = QueryPacket.create("container.0", query);
+ byte[] encoded = packetToBytes(packet);
+ byte[] correctBuffer=new byte[] {
+ 0, 0, 1, 16, 0, 0, 0, -38, 0, 0, 0, 0, 0, 16, 0, 6, 0, 10, ignored, ignored, ignored, ignored, 0, 0, 0x40, 0x03, 7, 'd', 'e', 'f', 'a', 'u', 'l', 't', 0, 0, 0, 1, 0, 0, 0, 4, 'r', 'a', 'n', 'k', 0, 0, 0, 5, 0, 0, 0, 18, 'v', 'e', 's', 'p', 'a', 46, 'l', 'a', 'b', 'e', 'l', 46, 'b', 'a', 'r', 46, 'i', 'd', 0, 0, 0, 1, '1', 0, 0, 0, 22, 'v', 'e', 's', 'p', 'a', 46, 't', 'e', 'r', 'm', 46, '4', 46, 'c', 'o', 'n', 'n', 'e', 'x', 'i', 't', 'y', 0, 0, 0, 1, '3', 0, 0, 0, 22, 'v', 'e', 's', 'p', 'a', 46, 't', 'e', 'r', 'm', 46, '4', 46, 'c', 'o', 'n', 'n', 'e', 'x', 'i', 't', 'y', 0, 0, 0, 4, '0', 46, '1', '5', 0, 0, 0, 25, 'v', 'e', 's', 'p', 'a', 46, 't', 'e', 'r', 'm', 46, '3', 46, 's', 'i', 'g', 'n', 'i', 'f', 'i', 'c', 'a', 'n', 'c', 'e', 0, 0, 0, 4, '0', 46, '3', '7', 0, 0, 0, 25, 'v', 'e', 's', 'p', 'a', 46, 't', 'e', 'r', 'm', 46, '4', 46, 's', 'i', 'g', 'n', 'i', 'f', 'i', 'c', 'a', 'n', 'c', 'e', 0, 0, 0, 4, '0', 46, '8', '1', 0, 0, 0, 5, 0, 0, 0, '4', 1, 4, 79, 1, 0, 6, 'f', 'i', 'e', 'l', 'd', '1', 79, 2, 0, 6, 'f', 'i', 'e', 'l', 'd', '2', 68, 3, 6, 'f', 'i', 'e', 'l', 'd', '3', 5, 'w', 'o', 'r', 'd', '1', 68, 4, 6, 'f', 'i', 'e', 'l', 'd', '3', 5, 'w', 'o', 'r', 'd', 49
+ };
+ assertEqualArrays(correctBuffer, encoded);
+ }
+
+ @Test
+ public void testEncodeSortSpec() throws BufferTooSmallException {
+ Query query = new Query("/?query=chain&sortspec=%2Ba+-b&timeout=0&groupingSessionCache=false");
+ query.setWindow(2, 8);
+ QueryPacket packet = QueryPacket.create("container.0", query);
+ ByteBuffer buffer = ByteBuffer.allocate(500);
+ buffer.limit(0);
+ packet.encode(buffer, 0);
+ byte[] encoded = new byte[buffer.position()];
+ buffer.rewind();
+ buffer.get(encoded);
+ byte[] correctBuffer = new byte[] {0,0,0,55,0,0,0,-38,0,0,0,0, // Header
+ 0,0,0,-122, // Features
+ 2, // offset
+ 8, // maxhits
+ 0,0,0,1, // querytimeout
+ 0,0,0x40,0x03, // qflags
+ 7,
+ 'd', 'e', 'f', 'a', 'u', 'l', 't',
+ 0,0,0,5, // sortspec length
+ 43,97,32,45,98, // sortspec
+ 0,0,0,1, // num stackitems
+ 0,0,0,8,4,
+ 0,5,
+ 99,104,97,105,110};
+ assertEqualArrays(correctBuffer, encoded);
+
+ // Encode again to test grantEncodingBuffer
+ buffer = packet.grantEncodingBuffer(0);
+ encoded = new byte[buffer.limit()];
+ buffer.get(encoded);
+ assertEqualArrays(correctBuffer, encoded);
+ }
+
+ @Test
+ public void testBufferExpands() throws BufferTooSmallException {
+ Query query = new Query("/?query=chain&sortspec=%2Ba+-b&timeout=0");
+ QueryPacket packet = QueryPacket.create("container.0", query);
+
+ ByteBuffer buffer = packet.grantEncodingBuffer(0, ByteBuffer.allocate(2));
+ assertEquals(128, buffer.capacity());
+ }
+
+ @Test
+ public void testPhraseEqualsPhraseWithPhraseSegment() throws BufferTooSmallException {
+ Query query = new Query();
+ query.setGroupingSessionCache(false);
+ PhraseItem p = new PhraseItem();
+ PhraseSegmentItem ps = new PhraseSegmentItem("a b", false, false);
+ ps.addItem(new WordItem("a"));
+ ps.addItem(new WordItem("b"));
+ p.addItem(ps);
+ query.getModel().getQueryTree().setRoot(p);
+
+ query.setTimeout(0);
+ QueryPacket queryPacket = QueryPacket.create("container.0", query);
+
+ ByteBuffer buffer1 = ByteBuffer.allocate(1024);
+
+ queryPacket.encode(buffer1, 0);
+
+ query = new Query();
+ query.setGroupingSessionCache(false);
+ p = new PhraseItem();
+ p.addItem(new WordItem("a"));
+ p.addItem(new WordItem("b"));
+ query.getModel().getQueryTree().setRoot(p);
+
+ query.setTimeout(0);
+ queryPacket = QueryPacket.create("container.0", query);
+ assertNotNull(queryPacket);
+
+ ByteBuffer buffer2 = ByteBuffer.allocate(1024);
+
+ queryPacket.encode(buffer2, 0);
+
+ byte[] encoded1 = new byte[buffer1.position()];
+ buffer1.rewind();
+ buffer1.get(encoded1);
+ byte[] encoded2 = new byte[buffer2.position()];
+ buffer2.rewind();
+ buffer2.get(encoded2);
+ assertEqualArrays(encoded2, encoded1);
+ }
+
+ @Test
+ public void testPatchInChannelId() {
+ Query query = new Query("/?query=chain&timeout=0&groupingSessionCache=false");
+ query.setWindow(2, 8);
+ QueryPacket packet = QueryPacket.create("container.0", query);
+ assertEquals(2,packet.getOffset());
+ assertEquals(8, packet.getHits());
+
+ ByteBuffer buffer = packet.grantEncodingBuffer(0x07070707);
+
+ byte[] correctBuffer = new byte[] {0,0,0,46,0,0,0,-38,7,7,7,7, // Header
+ 0,0,0,6, // Features
+ 2,
+ 8,
+ 0,0,0,1, // querytimeout
+ 0,0,0x40,0x03, // qflags
+ 7,
+ 'd', 'e', 'f', 'a', 'u', 'l', 't',
+ 0,0,0,1,0,0,0,8,4,
+ 0,5,
+ 99,104,97,105,110};
+
+ byte[] encoded = new byte[buffer.limit()];
+ buffer.get(encoded);
+
+ assertEqualArrays(correctBuffer,encoded);
+
+ buffer = packet.grantEncodingBuffer(0x09090909);
+ correctBuffer = new byte[] {0,0,0,46,0,0,0,-38,9,9,9,9, // Header
+ 0,0,0,6, // Features
+ 2,
+ 8,
+ 0,0,0,1, // querytimeout
+ 0,0,0x40,0x03, // qflags
+ 7,
+ 'd', 'e', 'f', 'a', 'u', 'l', 't',
+ 0,0,0,1,0,0,0,8,4,
+ 0,5,
+ 99,104,97,105,110};
+
+ encoded = new byte[buffer.limit()];
+ buffer.get(encoded);
+
+ assertEqualArrays(correctBuffer,encoded);
+ }
+
+ public static byte[] packetToBytes(Packet packet) {
+ try {
+ ByteBuffer buffer = ByteBuffer.allocate(500);
+ buffer.limit(0);
+ packet.encode(buffer, 0);
+ byte[] encoded = new byte[buffer.position()];
+ buffer.rewind();
+ buffer.get(encoded);
+ return encoded;
+ }
+ catch (BufferTooSmallException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void assertEqualArrays(byte[] correct, byte[] test) {
+ assertEquals("Incorrect length,", correct.length, test.length);
+ for (int i = 0; i < correct.length; i++) {
+ if (correct[i] == ignored) continue; // Special value used to ignore bytes we don't want to check
+ assertEquals("Byte nr " + i, correct[i], test[i]);
+ }
+ }
+
+ public static final byte ignored = -128;
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/query/test/RankFeaturesTestCase.java b/container-search/src/test/java/com/yahoo/fs4/test/RankFeaturesTestCase.java
index 8aff81a90db..b52c708ce4b 100644
--- a/container-search/src/test/java/com/yahoo/search/query/test/RankFeaturesTestCase.java
+++ b/container-search/src/test/java/com/yahoo/fs4/test/RankFeaturesTestCase.java
@@ -1,5 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.search.query.test;
+package com.yahoo.fs4.test;
import com.yahoo.io.GrowableByteBuffer;
import com.yahoo.search.query.ranking.RankFeatures;
@@ -11,11 +11,7 @@ import com.yahoo.text.Utf8;
import org.junit.Test;
import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
+import java.util.*;
import static org.junit.Assert.assertEquals;
diff --git a/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java b/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java
index 9ea7276583b..86553a86add 100644
--- a/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/cluster/ClusterSearcherTestCase.java
@@ -7,6 +7,8 @@ import com.yahoo.container.QrConfig;
import com.yahoo.container.QrSearchersConfig;
import com.yahoo.container.handler.VipStatus;
import com.yahoo.container.protect.Error;
+import com.yahoo.container.search.Fs4Config;
+import com.yahoo.fs4.QueryPacket;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.IndexModel;
import com.yahoo.prelude.SearchDefinition;
@@ -46,12 +48,12 @@ import static org.junit.Assert.assertTrue;
* @author bratseth
*/
public class ClusterSearcherTestCase {
- private static final double DELTA = 0.0000000000000001;
@Test
public void testNoBackends() {
ClusterSearcher cluster = new ClusterSearcher(new LinkedHashSet<>(Arrays.asList("dummy")));
try {
+ cluster.getMonitor().getConfiguration().setRequestTimeout(100);
Execution execution = new Execution(cluster, Execution.Context.createContextStub());
Query query = new Query("query=hello");
query.setHits(10);
@@ -144,7 +146,7 @@ public class ClusterSearcherTestCase {
private final String type3 = "type3";
private final Map<String, List<Hit>> results = new LinkedHashMap<>();
private final boolean expectAttributePrefetch;
- static final String ATTRIBUTE_PREFETCH = "attributeprefetch";
+ public static final String ATTRIBUTE_PREFETCH = "attributeprefetch";
private String getId(String type, int i) {
return "id:ns:" + type + "::" + i;
@@ -194,7 +196,7 @@ public class ClusterSearcherTestCase {
createHit(getId(type3, 2), 5)));
}
- MyMockSearcher(boolean expectAttributePrefetch) {
+ public MyMockSearcher(boolean expectAttributePrefetch) {
this.expectAttributePrefetch = expectAttributePrefetch;
init();
}
@@ -261,7 +263,8 @@ public class ClusterSearcherTestCase {
}
private Execution createExecution(List<String> docTypesList, boolean expectAttributePrefetch) {
- Set<String> documentTypes = new LinkedHashSet<>(docTypesList);
+ Set<String> documentTypes = new LinkedHashSet<>();
+ documentTypes.addAll(docTypesList);
ClusterSearcher cluster = new ClusterSearcher(documentTypes);
try {
cluster.addBackendSearcher(new MyMockSearcher(
@@ -274,7 +277,6 @@ public class ClusterSearcherTestCase {
}
}
- @Test
public void testThatSingleDocumentTypeCanBeSearched() {
{ // Explicit 1 type in restrict set
Execution execution = createExecution();
@@ -283,9 +285,9 @@ public class ClusterSearcherTestCase {
assertEquals(3, result.getTotalHitCount());
List<Hit> hits = result.hits().asList();
assertEquals(3, hits.size());
- assertEquals(9.0, hits.get(0).getRelevance().getScore(), DELTA);
- assertEquals(6.0, hits.get(1).getRelevance().getScore(), DELTA);
- assertEquals(3.0, hits.get(2).getRelevance().getScore(), DELTA);
+ assertEquals(9.0, hits.get(0).getRelevance().getScore());
+ assertEquals(6.0, hits.get(1).getRelevance().getScore());
+ assertEquals(3.0, hits.get(2).getRelevance().getScore());
}
{ // Only 1 registered type in cluster searcher, empty restrict set
// NB ! Empty restrict sets does not exist below the cluster searcher.
@@ -297,11 +299,10 @@ public class ClusterSearcherTestCase {
assertEquals(3, result.getTotalHitCount());
List<Hit> hits = result.hits().asList();
assertEquals(3, hits.size());
- assertEquals(9.0, hits.get(0).getRelevance().getScore(), DELTA);
+ assertEquals(9.0, hits.get(0).getRelevance().getScore());
}
}
- @Test
public void testThatSubsetOfDocumentTypesCanBeSearched() {
Execution execution = createExecution();
Query query = new Query("?query=hello&restrict=type1,type3");
@@ -310,15 +311,14 @@ public class ClusterSearcherTestCase {
assertEquals(6, result.getTotalHitCount());
List<Hit> hits = result.hits().asList();
assertEquals(6, hits.size());
- assertEquals(11.0, hits.get(0).getRelevance().getScore(), DELTA);
- assertEquals(9.0, hits.get(1).getRelevance().getScore(), DELTA);
- assertEquals(8.0, hits.get(2).getRelevance().getScore(), DELTA);
- assertEquals(6.0, hits.get(3).getRelevance().getScore(), DELTA);
- assertEquals(5.0, hits.get(4).getRelevance().getScore(), DELTA);
- assertEquals(3.0, hits.get(5).getRelevance().getScore(), DELTA);
+ assertEquals(11.0, hits.get(0).getRelevance().getScore());
+ assertEquals(9.0, hits.get(1).getRelevance().getScore());
+ assertEquals(8.0, hits.get(2).getRelevance().getScore());
+ assertEquals(6.0, hits.get(3).getRelevance().getScore());
+ assertEquals(5.0, hits.get(4).getRelevance().getScore());
+ assertEquals(3.0, hits.get(5).getRelevance().getScore());
}
- @Test
public void testThatMultipleDocumentTypesCanBeSearchedAndFilled() {
Execution execution = createExecution();
Query query = new Query("?query=hello");
@@ -327,15 +327,15 @@ public class ClusterSearcherTestCase {
assertEquals(9, result.getTotalHitCount());
List<Hit> hits = result.hits().asList();
assertEquals(9, hits.size());
- assertEquals(11.0, hits.get(0).getRelevance().getScore(), DELTA);
- assertEquals(10.0, hits.get(1).getRelevance().getScore(), DELTA);
- assertEquals(9.0, hits.get(2).getRelevance().getScore(), DELTA);
- assertEquals(8.0, hits.get(3).getRelevance().getScore(), DELTA);
- assertEquals(7.0, hits.get(4).getRelevance().getScore(), DELTA);
- assertEquals(6.0, hits.get(5).getRelevance().getScore(), DELTA);
- assertEquals(5.0, hits.get(6).getRelevance().getScore(), DELTA);
- assertEquals(4.0, hits.get(7).getRelevance().getScore(), DELTA);
- assertEquals(3.0, hits.get(8).getRelevance().getScore(), DELTA);
+ assertEquals(11.0, hits.get(0).getRelevance().getScore());
+ assertEquals(10.0, hits.get(1).getRelevance().getScore());
+ assertEquals(9.0, hits.get(2).getRelevance().getScore());
+ assertEquals(8.0, hits.get(3).getRelevance().getScore());
+ assertEquals(7.0, hits.get(4).getRelevance().getScore());
+ assertEquals(6.0, hits.get(5).getRelevance().getScore());
+ assertEquals(5.0, hits.get(6).getRelevance().getScore());
+ assertEquals(4.0, hits.get(7).getRelevance().getScore());
+ assertEquals(3.0, hits.get(8).getRelevance().getScore());
for (int i = 0; i < 9; ++i) {
assertNull(hits.get(i).getField("score"));
}
@@ -390,7 +390,7 @@ public class ClusterSearcherTestCase {
assertResult(9, Arrays.asList(5.0, 4.0), getResult(6, 2, ex));
assertResult(9, Arrays.asList(4.0, 3.0), getResult(7, 2, ex));
assertResult(9, Arrays.asList(3.0), getResult(8, 2, ex));
- assertResult(9, new ArrayList<>(), getResult(9, 2, ex));
+ assertResult(9, new ArrayList<Double>(), getResult(9, 2, ex));
assertResult(9, Arrays.asList(11.0, 10.0, 9.0, 8.0, 7.0), getResult(0, 5, ex));
assertResult(9, Arrays.asList(6.0, 5.0, 4.0, 3.0), getResult(5, 5, ex));
@@ -425,7 +425,11 @@ public class ClusterSearcherTestCase {
final String yahoo = "www.yahoo.com";
try {
- canFindYahoo = (null != InetAddress.getByName(yahoo));
+ if (null != InetAddress.getByName(yahoo)) {
+ canFindYahoo = true;
+ } else {
+ canFindYahoo = false;
+ }
} catch (Exception e) {
canFindYahoo = false;
}
@@ -538,11 +542,12 @@ public class ClusterSearcherTestCase {
qrSearchersConfig.build(),
clusterConfig.build(),
documentDbConfig.build(),
+ new QrMonitorConfig.Builder().build(),
new DispatchConfig.Builder().build(),
createClusterInfoConfig(),
Statistics.nullImplementation,
new MockMetric(),
- new FS4ResourcePool(new QrConfig.Builder().build()),
+ new FS4ResourcePool(new Fs4Config.Builder().build(), new QrConfig.Builder().build()),
new VipStatus());
}
@@ -585,7 +590,7 @@ public class ClusterSearcherTestCase {
@Test
public void testThatQueryTimeoutIsCappedWithSpecifiedMax() {
- QueryTimeoutFixture f = new QueryTimeoutFixture(70.0, null);
+ QueryTimeoutFixture f = new QueryTimeoutFixture(Double.valueOf(70), null);
f.query.setTimeout(70001);
f.search();
assertEquals(70000, f.query.getTimeout());
@@ -611,7 +616,7 @@ public class ClusterSearcherTestCase {
@Test
public void testThatQueryCacheIsDisabledIfTimeoutIsLargerThanConfiguredMax() {
- QueryTimeoutFixture f = new QueryTimeoutFixture(null, 5.0);
+ QueryTimeoutFixture f = new QueryTimeoutFixture(null, Double.valueOf(5));
f.query.setTimeout(5001);
f.query.getRanking().setQueryCache(true);
f.search();
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/FS4SearchInvokerTestCase.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/FS4SearchInvokerTestCase.java
new file mode 100644
index 00000000000..b9119528490
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/FS4SearchInvokerTestCase.java
@@ -0,0 +1,72 @@
+// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.prelude.fastsearch;
+
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.mplex.FS4Channel;
+import com.yahoo.fs4.mplex.InvalidChannelException;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.dispatch.InterleavedSearchInvoker;
+import com.yahoo.search.dispatch.MockSearchCluster;
+import com.yahoo.search.dispatch.ResponseMonitor;
+import com.yahoo.search.searchchain.Execution;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Optional;
+
+import static org.junit.Assert.assertThat;
+
+public class FS4SearchInvokerTestCase {
+ @SuppressWarnings("resource")
+ @Test
+ public void testThatConnectionErrorsAreReportedImmediately() throws IOException {
+ var query = new Query("?");
+ query.setTimeout(1000);
+
+ var searcher = mockSearcher();
+ var cluster = new MockSearchCluster("?", 1, 1);
+ var fs4invoker = new FS4SearchInvoker(searcher, query, mockFailingChannel(), Optional.empty());
+ var interleave = new InterleavedSearchInvoker(Collections.singleton(fs4invoker), cluster, null);
+
+ long start = System.currentTimeMillis();
+ interleave.search(query, null);
+ long elapsed = System.currentTimeMillis() - start;
+
+ assertThat("Connection error should fail fast", elapsed, Matchers.lessThan(500L));
+ }
+
+ private static VespaBackEndSearcher mockSearcher() {
+ return new VespaBackEndSearcher() {
+ @Override
+ protected Result doSearch2(Query query, Execution execution) {
+ return null;
+ }
+
+ @Override
+ protected void doPartialFill(Result result, String summaryClass) {}
+ };
+ }
+
+ private static FS4Channel mockFailingChannel() {
+ return new FS4Channel() {
+ @Override
+ public boolean sendPacket(BasicPacket packet) throws InvalidChannelException, IOException {
+ // pretend there's a connection error
+ return false;
+ }
+
+ @Override
+ public void setQuery(Query q) {}
+
+ @Override
+ public void setResponseMonitor(ResponseMonitor<FS4Channel> monitor) {}
+
+ @Override
+ public void close() {}
+ };
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/DirectSearchTestCase.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/DirectSearchTestCase.java
new file mode 100644
index 00000000000..b0662a93f62
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/DirectSearchTestCase.java
@@ -0,0 +1,137 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.fastsearch.test;
+
+import com.yahoo.prelude.fastsearch.FastHit;
+import com.yahoo.search.Result;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests that FastSearcher will bypass dispatch when the conditions are right
+ *
+ * @author bratseth
+ */
+public class DirectSearchTestCase {
+
+ @Test
+ public void testDirectSearchEnabled() {
+ FastSearcherTester tester = new FastSearcherTester(1, FastSearcherTester.selfHostname + ":9999:0");
+ tester.search("?query=test&dispatch.direct=true");
+ assertEquals("The FastSearcher has used the local search node connection", 1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+ @Test
+ public void testDirectSearchDisabled() {
+ FastSearcherTester tester = new FastSearcherTester(1, FastSearcherTester.selfHostname + ":9999:0");
+ tester.search("?query=test&dispatch.direct=false&dispatch.internal=false");
+ assertEquals(0, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+ @Test
+ public void testDirectSearchEnabledByDefault() {
+ FastSearcherTester tester = new FastSearcherTester(1, FastSearcherTester.selfHostname + ":9999:0");
+ tester.search("?query=test");
+ assertEquals("The FastSearcher has used the local search node connection", 1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+ @Test
+ public void testNoDirectSearchWhenMoreSearchNodesThanContainers() {
+ FastSearcherTester tester = new FastSearcherTester(1, FastSearcherTester.selfHostname + ":9999:0", "otherhost:9999:1");
+ tester.search("?query=test&dispatch.direct=true&dispatch.internal=false");
+ assertEquals(0, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+ @Test
+ public void testDirectSearchWhenMultipleGroupsAndEnoughContainers() {
+ FastSearcherTester tester = new FastSearcherTester(2, FastSearcherTester.selfHostname + ":9999:0", "otherhost:9999:1");
+ tester.search("?query=test&dispatch.direct=true");
+ assertEquals(1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+ @Test
+ public void testDirectSearchSummaryFetchGoToLocalNode() {
+ FastSearcherTester tester = new FastSearcherTester(2, "otherhost:9999:1", FastSearcherTester.selfHostname + ":9999:0");
+ int localDistributionKey = tester.dispatcher().searchCluster().nodesByHost().get(FastSearcherTester.selfHostname).asList().get(0).key();
+ assertEquals(1, localDistributionKey);
+ Result result = tester.search("?query=test&dispatch.direct=true");
+ assertEquals(1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ FastHit hit = (FastHit)result.hits().get(0);
+ assertEquals(localDistributionKey, hit.getDistributionKey());
+ }
+
+ @Test
+ public void testNoDirectSearchWhenMultipleNodesPerGroup() {
+ FastSearcherTester tester = new FastSearcherTester(2, FastSearcherTester.selfHostname + ":9999:0", "otherhost:9999:0");
+ tester.search("?query=test&dispatch.direct=true&dispatch.internal=false");
+ assertEquals(0, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+ @Test
+ public void testNoDirectSearchWhenLocalNodeIsDown() {
+ FastSearcherTester tester = new FastSearcherTester(2, FastSearcherTester.selfHostname + ":9999:0", "otherhost:9999:1");
+ assertTrue(tester.vipStatus().isInRotation());
+ tester.setResponding(FastSearcherTester.selfHostname, false);
+ assertFalse(tester.vipStatus().isInRotation());
+ assertEquals("1 ping request, 0 search requests", 1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.search("?query=test&dispatch.direct=true&nocache");
+ assertEquals("1 ping request, 0 search requests", 1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.setResponding(FastSearcherTester.selfHostname, true);
+ assertTrue(tester.vipStatus().isInRotation());
+ assertEquals("2 ping requests, 0 search request", 2, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.search("?query=test&dispatch.direct=true&nocache");
+ assertEquals("2 ping requests, 1 search request", 3, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+ @Test
+ public void testNoDirectDispatchWhenInsufficientCoverage() {
+ FastSearcherTester tester = new FastSearcherTester(3,
+ FastSearcherTester.selfHostname + ":9999:0",
+ "host1:9999:1",
+ "host2:9999:2");
+ double k = 38.78955; // multiply all document counts by some number > 1 to test that we compute % correctly
+
+ tester.setActiveDocuments(FastSearcherTester.selfHostname, (long) (96 * k));
+ tester.setActiveDocuments("host1", (long) (100 * k));
+ tester.setActiveDocuments("host2", (long) (100 * k));
+ assertEquals("1 ping request, 0 search requests", 1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.search("?query=test&dispatch.direct=true&nocache");
+ assertEquals("Still 1 ping request, 0 search requests because the default coverage is 97%, and we only have 96% locally",
+ 1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.waitForInRotationIs(false);
+
+ tester.setActiveDocuments(FastSearcherTester.selfHostname, (long) (99 * k));
+ assertEquals("2 ping request, 0 search requests", 2, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.search("?query=test&dispatch.direct=true&nocache");
+ assertEquals("2 ping request, 1 search requests because we now have 99% locally",
+ 3, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.waitForInRotationIs(true);
+
+ tester.setActiveDocuments("host1", (long) (104 * k));
+ assertEquals("2 ping request, 1 search requests", 3, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.search("?query=test&dispatch.direct=true&nocache");
+ assertEquals("2 ping request, 2 search requests because 99/((104+100)/2) > 0.97",
+ 4, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.waitForInRotationIs(true);
+
+ tester.setActiveDocuments("host2", (long) (102 * k));
+ assertEquals("2 ping request, 2 search requests", 4, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.search("?query=test&dispatch.direct=true&nocache");
+ assertEquals("Still 2 ping request, 2 search requests because 99/((104+102)/2) < 0.97",
+ 4, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.waitForInRotationIs(false);
+ }
+
+ @Test
+ public void testCoverageWithSingleGroup() {
+ FastSearcherTester tester = new FastSearcherTester(1, FastSearcherTester.selfHostname + ":9999:0");
+
+ tester.setActiveDocuments(FastSearcherTester.selfHostname, 100);
+ assertEquals("1 ping request, 0 search requests", 1, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ tester.search("?query=test&dispatch.direct=true&nocache");
+ assertEquals("1 ping request, 1 search requests", 2, tester.requestCount(FastSearcherTester.selfHostname, 9999));
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTestCase.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTestCase.java
index eb4d65693bb..c6e87170f07 100644
--- a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTestCase.java
@@ -3,34 +3,64 @@ package com.yahoo.prelude.fastsearch.test;
import com.google.common.collect.ImmutableList;
import com.yahoo.component.chain.Chain;
+import com.yahoo.config.subscription.ConfigGetter;
+import com.yahoo.container.QrConfig;
+import com.yahoo.container.handler.VipStatus;
import com.yahoo.container.protect.Error;
+import com.yahoo.container.search.Fs4Config;
+import com.yahoo.document.GlobalId;
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.Packet;
+import com.yahoo.fs4.mplex.Backend;
+import com.yahoo.fs4.mplex.BackendTestCase;
+import com.yahoo.fs4.test.QueryTestCase;
import com.yahoo.language.simple.SimpleLinguistics;
+import com.yahoo.prelude.Ping;
+import com.yahoo.prelude.Pong;
import com.yahoo.prelude.fastsearch.ClusterParams;
import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig;
+import com.yahoo.prelude.fastsearch.FS4ResourcePool;
+import com.yahoo.prelude.fastsearch.FastHit;
import com.yahoo.prelude.fastsearch.FastSearcher;
import com.yahoo.prelude.fastsearch.SummaryParameters;
+import com.yahoo.prelude.fastsearch.test.fs4mock.MockBackend;
+import com.yahoo.prelude.fastsearch.test.fs4mock.MockFS4ResourcePool;
+import com.yahoo.prelude.fastsearch.test.fs4mock.MockFSChannel;
+import com.yahoo.processing.execution.Execution.Trace;
import com.yahoo.search.Query;
import com.yahoo.search.Result;
import com.yahoo.search.Searcher;
+import com.yahoo.search.dispatch.rpc.MockRpcResourcePoolBuilder;
import com.yahoo.search.dispatch.searchcluster.Node;
import com.yahoo.search.grouping.GroupingRequest;
import com.yahoo.search.grouping.request.AllOperation;
import com.yahoo.search.grouping.request.EachOperation;
import com.yahoo.search.grouping.request.GroupingOperation;
+import com.yahoo.search.query.SessionId;
import com.yahoo.search.rendering.RendererRegistry;
import com.yahoo.search.result.ErrorMessage;
import com.yahoo.search.searchchain.Execution;
+import com.yahoo.yolean.trace.TraceNode;
+import com.yahoo.yolean.trace.TraceVisitor;
import org.junit.Test;
+import java.io.IOException;
+import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
/**
* Tests the Fast searcher
@@ -40,12 +70,30 @@ import static org.junit.Assert.assertNotNull;
public class FastSearcherTestCase {
private final static DocumentdbInfoConfig documentdbInfoConfig = new DocumentdbInfoConfig(new DocumentdbInfoConfig.Builder());
+ private MockBackend mockBackend;
+ @Test
+ public void testNoNormalizing() {
+ Logger.getLogger(FastSearcher.class.getName()).setLevel(Level.ALL);
+ FastSearcher fastSearcher = new FastSearcher(new MockBackend(),
+ new FS4ResourcePool("container.0", 1),
+ MockDispatcher.create(Collections.emptyList()),
+ new SummaryParameters(null),
+ new ClusterParams("testhittype"),
+ documentdbInfoConfig);
+
+ MockFSChannel.setEmptyDocsums(false);
+
+ Result result = doSearch(fastSearcher, new Query("?query=ignored"), 0, 10);
+
+ assertTrue(result.hits().get(0).getRelevance().getScore() > 1000);
+ }
@Test
public void testNullQuery() {
Logger.getLogger(FastSearcher.class.getName()).setLevel(Level.ALL);
- FastSearcher fastSearcher = new FastSearcher("container.0",
+ FastSearcher fastSearcher = new FastSearcher(new MockBackend(),
+ new FS4ResourcePool("container.0", 1),
MockDispatcher.create(Collections.emptyList()),
new SummaryParameters(null),
new ClusterParams("testhittype"),
@@ -61,6 +109,152 @@ public class FastSearcherTestCase {
assertEquals(Error.NULL_QUERY.code, message.getCode());
}
+ @Test
+ public void testDispatchDotSummaries() {
+ Logger.getLogger(FastSearcher.class.getName()).setLevel(Level.ALL);
+ DocumentdbInfoConfig documentdbConfigWithOneDb =
+ new DocumentdbInfoConfig(new DocumentdbInfoConfig.Builder().documentdb(new DocumentdbInfoConfig.Documentdb.Builder()
+ .name("testDb")
+ .summaryclass(new DocumentdbInfoConfig.Documentdb.Summaryclass.Builder().name("simple").id(7))
+ .rankprofile(new DocumentdbInfoConfig.Documentdb.Rankprofile.Builder()
+ .name("simpler").hasRankFeatures(false).hasSummaryFeatures(false))));
+
+ List<Node> nodes = new ArrayList<>();
+ nodes.add(new Node(0, "host1", 5000, 0));
+ nodes.add(new Node(1, "host2", 5000, 0));
+
+ var mockFs4ResourcePool = new MockFS4ResourcePool();
+ var mockRpcResourcePool = new MockRpcResourcePoolBuilder().connection(0).connection(1).build();
+
+ FastSearcher fastSearcher = new FastSearcher(new MockBackend(),
+ mockFs4ResourcePool,
+ MockDispatcher.create(nodes, mockFs4ResourcePool, mockRpcResourcePool, 1, new VipStatus()),
+ new SummaryParameters(null),
+ new ClusterParams("testhittype"),
+ documentdbConfigWithOneDb);
+
+ { // No direct.summaries
+ String query = "?query=sddocname:a&summary=simple&timeout=20s";
+ Result result = doSearch(fastSearcher, new Query(query), 0, 10);
+ doFill(fastSearcher, result);
+ ErrorMessage error = result.hits().getError();
+ assertNull("Since we don't route to the dispatcher we hit the mock backend, so no error", error);
+ }
+
+ { // direct.summaries due to query cache
+ String query = "?query=sddocname:a&ranking.queryCache&timeout=20s";
+ Result result = doSearch(fastSearcher, new Query(query), 0, 10);
+ doFill(fastSearcher, result);
+ ErrorMessage error = result.hits().getError();
+ assertEquals("Since we don't actually run summary backends we get this error when the Dispatcher is used",
+ "getDocsums(..) attempted for node X", error.getDetailedMessage().replaceAll("\\d", "X"));
+ }
+
+ { // direct.summaries due to no summary features
+ String query = "?query=sddocname:a&dispatch.summaries&summary=simple&ranking=simpler&timeout=20s";
+ Result result = doSearch(fastSearcher, new Query(query), 0, 10);
+ doFill(fastSearcher, result);
+ ErrorMessage error = result.hits().getError();
+ assertEquals("Since we don't actually run summary backends we get this error when the Dispatcher is used",
+ "getDocsums(..) attempted for node X", error.getDetailedMessage().replaceAll("\\d", "X"));
+ }
+ }
+
+ @Test
+ public void testQueryWithRestrict() {
+ mockBackend = new MockBackend();
+ DocumentdbInfoConfig documentdbConfigWithOneDb =
+ new DocumentdbInfoConfig(new DocumentdbInfoConfig.Builder().documentdb(new DocumentdbInfoConfig.Documentdb.Builder().name("testDb")));
+ FastSearcher fastSearcher = new FastSearcher(mockBackend,
+ new FS4ResourcePool("container.0", 1),
+ MockDispatcher.create(Collections.emptyList()),
+ new SummaryParameters(null),
+ new ClusterParams("testhittype"),
+ documentdbConfigWithOneDb);
+
+ Query query = new Query("?query=foo&model.restrict=testDb&groupingSessionCache=false");
+ query.prepare();
+ doSearch(fastSearcher, query, 0, 10);
+
+ Packet receivedPacket = mockBackend.getChannel().getLastQueryPacket();
+ byte[] encoded = QueryTestCase.packetToBytes(receivedPacket);
+ byte[] correct = new byte[] {
+ 0, 0, 0, 100, 0, 0, 0, -38, 0, 0, 0, 0, 0, 16, 0, 6, 0, 10,
+ QueryTestCase.ignored, QueryTestCase.ignored, QueryTestCase.ignored, QueryTestCase.ignored, // time left
+ 0, 0, 0x40, 0x03, 7, 100, 101, 102, 97, 117, 108, 116, 0, 0, 0, 1, 0, 0, 0, 5, 109, 97, 116, 99, 104, 0, 0, 0, 1, 0, 0, 0, 24, 100, 111, 99, 117, 109, 101, 110, 116, 100, 98, 46, 115, 101, 97, 114, 99, 104, 100, 111, 99, 116, 121, 112, 101, 0, 0, 0, 6, 116, 101, 115, 116, 68, 98, 0, 0, 0, 1, 0, 0, 0, 7, 68, 1, 0, 3, 102, 111, 111
+ };
+ QueryTestCase.assertEqualArrays(correct, encoded);
+ }
+
+ @Test
+ public void testSearch() {
+ FastSearcher fastSearcher = createFastSearcher();
+
+ Result result = doSearch(fastSearcher, new Query("?query=ignored"), 0, 10);
+
+ Execution execution = new Execution(chainedAsSearchChain(fastSearcher), Execution.Context.createContextStub());
+ assertEquals(2, result.getHitCount());
+ execution.fill(result);
+ assertCorrectHit1((FastHit)result.hits().get(0));
+ assertCorrectTypes1((FastHit)result.hits().get(0));
+ for (int idx = 0; idx < result.getHitCount(); idx++) {
+ assertTrue(!result.hits().get(idx).isCached());
+ }
+
+ // Repeat the request a couple of times, to verify whether the packet cache works
+ result = doSearch(fastSearcher,new Query("?query=ignored"), 0, 10);
+ assertEquals(2, result.getHitCount());
+ execution.fill(result);
+ assertCorrectHit1((FastHit) result.hits().get(0));
+ for (int i = 0; i < result.getHitCount(); i++) {
+ assertFalse(result.hits().get(i) + " should never be cached",
+ result.hits().get(i).isCached());
+ }
+
+ result = doSearch(fastSearcher,new Query("?query=ignored"), 0, 10);
+ assertEquals(2, result.getHitCount());
+ execution.fill(result);
+ assertCorrectHit1((FastHit) result.hits().get(0));
+ assertTrue("All hits are not cached", !result.isCached());
+ for (int i = 0; i < result.getHitCount(); i++) {
+ assertTrue(!result.hits().get(i).isCached());
+ }
+
+ // Test that partial result sets can be retrieved from the cache
+ result = doSearch(fastSearcher,new Query("?query=ignored"), 0, 1);
+ assertEquals(1, result.getConcreteHitCount());
+ execution.fill(result);
+
+ result = doSearch(fastSearcher,new Query("?query=ignored"), 0, 2);
+ assertEquals(2, result.getConcreteHitCount());
+ execution.fill(result);
+ // No hit should be cached
+ assertFalse(result.hits().get(0).isCached());
+ assertFalse(result.hits().get(1).isCached());
+
+ // Still nothing cached
+ result = doSearch(fastSearcher,new Query("?query=ignored"), 0, 2);
+ assertEquals(2, result.getConcreteHitCount());
+ execution.fill(result);
+ // both first and second should now be cached
+ assertFalse(result.hits().get(0).isCached());
+ assertFalse(result.hits().get(1).isCached());
+
+ // Tests that the cache _hit_ is not returned if _another_
+ // hit is requested
+
+ result = doSearch(fastSearcher,new Query("?query=ignored"), 0, 1);
+ assertEquals(1, result.getConcreteHitCount());
+
+ result = doSearch(fastSearcher,new Query("?query=ignored"), 1, 1);
+ assertEquals(1, result.getConcreteHitCount());
+
+ for (int i = 0; i < result.getHitCount(); i++) {
+ assertFalse("Hit " + i + " should not be cached.",
+ result.hits().get(i).isCached());
+ }
+ }
+
private Chain<Searcher> chainedAsSearchChain(Searcher topOfChain) {
List<Searcher> searchers = new ArrayList<>();
searchers.add(topOfChain);
@@ -78,9 +272,79 @@ public class FastSearcherTestCase {
return new Execution(chainedAsSearchChain(searcher), context);
}
+ private void doFill(Searcher searcher, Result result) {
+ createExecution(searcher).fill(result);
+ }
+
+ @Test
+ public void testThatPropertiesAreReencoded() throws Exception {
+ FastSearcher fastSearcher = createFastSearcher();
+
+ Query query = new Query("?query=ignored&dispatch.summaries=false&groupingSessionCache=false");
+ query.getRanking().setQueryCache(true);
+ Result result = doSearch(fastSearcher, query, 0, 10);
+
+ Execution execution = new Execution(chainedAsSearchChain(fastSearcher), Execution.Context.createContextStub());
+ assertEquals(2, result.getHitCount());
+ execution.fill(result);
+
+ BasicPacket receivedPacket = mockBackend.getChannel().getLastReceived();
+ ByteBuffer buf = ByteBuffer.allocate(1000);
+ receivedPacket.encode(buf);
+ buf.flip();
+ byte[] actual = new byte[buf.remaining()];
+ buf.get(actual);
+
+ SessionId sessionId = query.getSessionId();
+ byte IGNORE = 69;
+ ByteBuffer answer = ByteBuffer.allocate(1024);
+ answer.put(new byte[] { 0, 0, 0, (byte)(141+sessionId.asUtf8String().getByteLength()), 0, 0, 0, -37, 0, 0, 16, 17, 0, 0, 0, 0,
+ // query timeout
+ IGNORE, IGNORE, IGNORE, IGNORE,
+ // "default" - rank profile
+ 7, 'd', 'e', 'f', 'a', 'u', 'l', 't', 0, 0, 0, 0x03,
+ // 3 property entries (rank, match, caches)
+ 0, 0, 0, 3,
+ // rank: sessionId => qrserver.0.XXXXXXXXXXXXX.0
+ 0, 0, 0, 4, 'r', 'a', 'n', 'k', 0, 0, 0, 1, 0, 0, 0, 9, 's', 'e', 's', 's', 'i', 'o', 'n', 'I', 'd'});
+ answer.putInt(sessionId.asUtf8String().getBytes().length);
+ answer.put(sessionId.asUtf8String().getBytes());
+ answer.put(new byte [] {
+ // match: documentdb.searchdoctype => test
+ 0, 0, 0, 5, 'm', 'a', 't', 'c', 'h', 0, 0, 0, 1, 0, 0, 0, 24, 'd', 'o', 'c', 'u', 'm', 'e', 'n', 't', 'd', 'b', '.', 's', 'e', 'a', 'r', 'c', 'h', 'd', 'o', 'c', 't', 'y', 'p', 'e', 0, 0, 0, 4, 't', 'e', 's', 't',
+ // sessionId => qrserver.0.XXXXXXXXXXXXX.0
+ 0, 0, 0, 6, 'c', 'a', 'c', 'h', 'e', 's', 0, 0, 0, 1, 0, 0, 0, 5, 'q', 'u', 'e', 'r', 'y', 0, 0, 0, 4, 't', 'r', 'u', 'e'});
+ byte [] expected = new byte [answer.position()];
+ answer.flip();
+ answer.get(expected);
+
+ for (int i = 0; i < expected.length; ++i) {
+ if (expected[i] == IGNORE) {
+ actual[i] = IGNORE;
+ }
+ }
+ assertArrayEquals(expected, actual);
+ }
+
+ private FastSearcher createFastSearcher() {
+ mockBackend = new MockBackend();
+ ConfigGetter<DocumentdbInfoConfig> getter = new ConfigGetter<>(DocumentdbInfoConfig.class);
+ DocumentdbInfoConfig config = getter.getConfig("file:src/test/java/com/yahoo/prelude/fastsearch/test/documentdb-info.cfg");
+
+ MockFSChannel.resetDocstamp();
+ Logger.getLogger(FastSearcher.class.getName()).setLevel(Level.ALL);
+ return new FastSearcher(mockBackend,
+ new FS4ResourcePool("container.0", 1),
+ MockDispatcher.create(Collections.emptyList()),
+ new SummaryParameters(null),
+ new ClusterParams("testhittype"),
+ config);
+ }
+
@Test
public void testSinglePassGroupingIsForcedWithSingleNodeGroups() {
- FastSearcher fastSearcher = new FastSearcher("container.0",
+ FastSearcher fastSearcher = new FastSearcher(new MockBackend(),
+ new FS4ResourcePool("container.0", 1),
MockDispatcher.create(Collections.singletonList(new Node(0, "host0", 123, 0))),
new SummaryParameters(null),
new ClusterParams("testhittype"),
@@ -104,7 +368,8 @@ public class FastSearcherTestCase {
public void testSinglePassGroupingIsNotForcedWithSingleNodeGroups() {
MockDispatcher dispatcher = MockDispatcher.create(ImmutableList.of(new Node(0, "host0", 123, 0), new Node(2, "host1", 123, 0)));
- FastSearcher fastSearcher = new FastSearcher("container.0",
+ FastSearcher fastSearcher = new FastSearcher(new MockBackend(),
+ new FS4ResourcePool("container.0", 1),
dispatcher,
new SummaryParameters(null),
new ClusterParams("testhittype"),
@@ -136,4 +401,95 @@ public class FastSearcherTestCase {
assertForceSinglePassIs(expected, child);
}
+ @Test
+ public void testPing() throws IOException, InterruptedException {
+ Logger.getLogger(FastSearcher.class.getName()).setLevel(Level.ALL);
+ BackendTestCase.MockServer server = new BackendTestCase.MockServer();
+ FS4ResourcePool listeners = new FS4ResourcePool(new Fs4Config(new Fs4Config.Builder()), new QrConfig(new QrConfig.Builder()));
+ Backend backend = listeners.getBackend(server.host.getHostString(),server.host.getPort());
+ FastSearcher fastSearcher = new FastSearcher(backend,
+ new FS4ResourcePool("container.0", 1),
+ MockDispatcher.create(Collections.emptyList()),
+ new SummaryParameters(null),
+ new ClusterParams("testhittype"),
+ documentdbInfoConfig);
+ server.dispatch.packetData = BackendTestCase.PONG;
+ server.dispatch.setNoChannel();
+ Chain<Searcher> chain = new Chain<>(fastSearcher);
+ Execution e = new Execution(chain, Execution.Context.createContextStub());
+ Pong pong = e.ping(new Ping());
+ backend.shutdown();
+ server.dispatch.socket.close();
+ server.dispatch.connection.close();
+ server.worker.join();
+ pong.setPingInfo("blbl");
+ assertEquals("Result of pinging using blbl", pong.toString());
+ }
+
+ private void assertCorrectTypes1(FastHit hit) {
+ assertEquals(String.class, hit.getField("TITLE").getClass());
+ assertEquals(Integer.class, hit.getField("BYTES").getClass());
+ }
+
+ private void assertCorrectHit1(FastHit hit) {
+ assertEquals(
+ "StudyOfMadonna.com - Interviews, Articles, Reviews, Quotes, Essays and more..",
+ hit.getField("TITLE"));
+ assertEquals("352", hit.getField("WORDS").toString());
+ assertEquals(2003., hit.getRelevance().getScore(), 0.01d);
+ assertEquals("index:testhittype/234/" + asHexString(hit.getGlobalId()), hit.getId().toString());
+ assertEquals("9190", hit.getField("BYTES").toString());
+ assertEquals("testhittype", hit.getSource());
+ }
+
+ private static String asHexString(GlobalId gid) {
+ StringBuilder sb = new StringBuilder();
+ byte[] rawGid = gid.getRawId();
+ for (byte b : rawGid) {
+ String hex = Integer.toHexString(0xFF & b);
+ if (hex.length() == 1)
+ sb.append('0');
+ sb.append(hex);
+ }
+ return sb.toString();
+ }
+
+ @Test
+ public void null_summary_is_included_in_trace() {
+ String summary = null;
+ assertThat(getTraceString(summary), containsString("summary=[null]"));
+ }
+
+ @Test
+ public void non_null_summary_is_included_in_trace() {
+ String summary = "all";
+ assertThat(getTraceString(summary), containsString("summary='all'"));
+ }
+
+ private String getTraceString(String summary) {
+ FastSearcher fastSearcher = createFastSearcher();
+
+ Query query = new Query("?query=ignored");
+ query.getPresentation().setSummary(summary);
+ query.setTraceLevel(2);
+
+ Result result = doSearch(fastSearcher, query, 0, 10);
+ doFill(fastSearcher, result);
+
+ Trace trace = query.getContext(false).getTrace();
+ final AtomicReference<String> fillTraceString = new AtomicReference<>();
+
+
+ trace.traceNode().accept(new TraceVisitor() {
+ @Override
+ public void visit(TraceNode traceNode) {
+ if (traceNode.payload() instanceof String && traceNode.payload().toString().contains("fill to dispatch"))
+ fillTraceString.set((String) traceNode.payload());
+
+ }
+ });
+
+ return fillTraceString.get();
+ }
+
}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTester.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTester.java
new file mode 100644
index 00000000000..6eab16045c2
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/FastSearcherTester.java
@@ -0,0 +1,127 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.fastsearch.test;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.yahoo.container.QrSearchersConfig;
+import com.yahoo.container.handler.VipStatus;
+import com.yahoo.net.HostName;
+import com.yahoo.prelude.fastsearch.ClusterParams;
+import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig;
+import com.yahoo.prelude.fastsearch.FastSearcher;
+import com.yahoo.prelude.fastsearch.SummaryParameters;
+import com.yahoo.prelude.fastsearch.test.fs4mock.MockBackend;
+import com.yahoo.prelude.fastsearch.test.fs4mock.MockFS4ResourcePool;
+import com.yahoo.search.Query;
+import com.yahoo.search.Result;
+import com.yahoo.search.dispatch.rpc.MockRpcResourcePoolBuilder;
+import com.yahoo.search.dispatch.rpc.RpcResourcePool;
+import com.yahoo.search.dispatch.searchcluster.Node;
+import com.yahoo.search.searchchain.Execution;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author bratseth
+ */
+class FastSearcherTester {
+
+ public static final String selfHostname = HostName.getLocalhost();
+
+ private final MockFS4ResourcePool mockFS4ResourcePool;
+ private final RpcResourcePool mockRpcResourcePool;
+ private final FastSearcher fastSearcher;
+ private final MockDispatcher mockDispatcher;
+ private final VipStatus vipStatus;
+
+ public FastSearcherTester(int containerClusterSize, Node searchNode) {
+ this(containerClusterSize, Collections.singletonList(searchNode));
+ }
+
+ public FastSearcherTester(int containerClusterSize, String... hostAndPortAndGroupStrings) {
+ this(containerClusterSize, toNodes(hostAndPortAndGroupStrings));
+ }
+
+ public FastSearcherTester(int containerClusterSize, List<Node> searchNodes) {
+ String clusterId = "a";
+
+ var b = new QrSearchersConfig.Builder();
+ var searchClusterB = new QrSearchersConfig.Searchcluster.Builder();
+ searchClusterB.name(clusterId);
+ b.searchcluster(searchClusterB);
+ vipStatus = new VipStatus(b.build());
+
+ mockFS4ResourcePool = new MockFS4ResourcePool();
+ var builder = new MockRpcResourcePoolBuilder();
+ searchNodes.forEach(node -> builder.connection(node.key()));
+ mockRpcResourcePool = builder.build();
+ mockDispatcher = MockDispatcher.create(searchNodes, mockFS4ResourcePool, mockRpcResourcePool, containerClusterSize, vipStatus);
+ fastSearcher = new FastSearcher(new MockBackend(selfHostname, 0L, true),
+ mockFS4ResourcePool,
+ mockDispatcher,
+ new SummaryParameters(null),
+ new ClusterParams("testhittype"),
+ new DocumentdbInfoConfig(new DocumentdbInfoConfig.Builder()));
+ }
+
+ private static List<Node> toNodes(String... hostAndPortAndGroupStrings) {
+ List<Node> nodes = new ArrayList<>();
+ int key = 0;
+ for (String s : hostAndPortAndGroupStrings) {
+ String[] parts = s.split(":");
+ nodes.add(new Node(key++, parts[0], Integer.parseInt(parts[1]), Integer.parseInt(parts[2])));
+ }
+ return nodes;
+ }
+
+ public Result search(String query) {
+ Result result = fastSearcher.search(new Query(query), new Execution(Execution.Context.createContextStub()));
+ assertEquals(null, result.hits().getError());
+ return result;
+ }
+
+ /** Returns the number of times a backend for this hostname and port has been requested */
+ public int requestCount(String hostname, int port) {
+ return mockFS4ResourcePool.requestCount(hostname, port);
+ }
+
+ public MockDispatcher dispatcher() { return mockDispatcher; }
+
+ /** Sets the response status of a node and ping it to update the monitor status */
+ public void setResponding(String hostname, boolean responding) {
+ // Start/stop returning a failing backend
+ mockFS4ResourcePool.setResponding(hostname, responding);
+
+ // Make the search cluster monitor notice right now in this thread
+ Node node = mockDispatcher.searchCluster().nodesByHost().get(hostname).iterator().next();
+ mockDispatcher.searchCluster().ping(node, MoreExecutors.directExecutor());
+ }
+
+ /** Sets the response status of a node and ping it to update the monitor status */
+ public void setActiveDocuments(String hostname, long activeDocuments) {
+ mockFS4ResourcePool.setActiveDocuments(hostname, activeDocuments);
+
+ // Make the search cluster monitor notice right now in this thread
+ Node node = mockDispatcher.searchCluster().nodesByHost().get(hostname).iterator().next();
+ mockDispatcher.searchCluster().ping(node, MoreExecutors.directExecutor());
+ mockDispatcher.searchCluster().pingIterationCompleted();
+ }
+
+ public VipStatus vipStatus() { return vipStatus; }
+
+ /** Retrying is needed because earlier pings from the monitoring thread may interfere with the testing thread */
+ public void waitForInRotationIs(boolean expectedRotationStatus) {
+ int triesLeft = 9000;
+ while (vipStatus.isInRotation() != expectedRotationStatus && triesLeft > 0) {
+ triesLeft--;
+ try { Thread.sleep(10); } catch (InterruptedException e) {}
+ }
+ if (triesLeft == 0)
+ fail("Did not reach VIP in rotation status = " + expectedRotationStatus + " after trying for 90 seconds");
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/MockDispatcher.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/MockDispatcher.java
index afb9cf6f571..ccb265b799b 100644
--- a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/MockDispatcher.java
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/MockDispatcher.java
@@ -2,6 +2,8 @@
package com.yahoo.prelude.fastsearch.test;
import com.yahoo.container.handler.VipStatus;
+import com.yahoo.prelude.fastsearch.FS4PingFactory;
+import com.yahoo.prelude.fastsearch.FS4ResourcePool;
import com.yahoo.search.dispatch.Dispatcher;
import com.yahoo.search.dispatch.rpc.RpcInvokerFactory;
import com.yahoo.search.dispatch.rpc.RpcResourcePool;
@@ -13,24 +15,23 @@ import java.util.List;
class MockDispatcher extends Dispatcher {
public static MockDispatcher create(List<Node> nodes) {
+ var fs4ResourcePool = new FS4ResourcePool("container.0", 1);
var rpcResourcePool = new RpcResourcePool(toDispatchConfig(nodes));
- return create(nodes, rpcResourcePool, 1, new VipStatus());
+ return create(nodes, fs4ResourcePool, rpcResourcePool, 1, new VipStatus());
}
- public static MockDispatcher create(List<Node> nodes, RpcResourcePool rpcResourcePool,
+ public static MockDispatcher create(List<Node> nodes, FS4ResourcePool fs4ResourcePool, RpcResourcePool rpcResourcePool,
int containerClusterSize, VipStatus vipStatus) {
var dispatchConfig = toDispatchConfig(nodes);
var searchCluster = new SearchCluster("a", dispatchConfig, containerClusterSize, vipStatus);
- return new MockDispatcher(searchCluster, dispatchConfig, rpcResourcePool);
+ return new MockDispatcher(searchCluster, dispatchConfig, fs4ResourcePool, rpcResourcePool);
}
- private MockDispatcher(SearchCluster searchCluster, DispatchConfig dispatchConfig, RpcResourcePool rpcResourcePool) {
- this(searchCluster, dispatchConfig, new RpcInvokerFactory(rpcResourcePool, searchCluster, dispatchConfig.dispatchWithProtobuf()));
- }
-
- private MockDispatcher(SearchCluster searchCluster, DispatchConfig dispatchConfig, RpcInvokerFactory invokerFactory) {
- super(searchCluster, dispatchConfig, invokerFactory, invokerFactory, new MockMetric());
+ private MockDispatcher(SearchCluster searchCluster, DispatchConfig dispatchConfig, FS4ResourcePool fs4ResourcePool,
+ RpcResourcePool rpcResourcePool) {
+ super(searchCluster, dispatchConfig, new RpcInvokerFactory(rpcResourcePool, searchCluster, dispatchConfig.dispatchWithProtobuf()),
+ new FS4PingFactory(fs4ResourcePool), new MockMetric());
}
private static DispatchConfig toDispatchConfig(List<Node> nodes) {
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/DispatchThread.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/DispatchThread.java
new file mode 100644
index 00000000000..d09e8856ee7
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/DispatchThread.java
@@ -0,0 +1,101 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+// -*- mode: java; folded-file: t; c-basic-offset: 4 -*-
+//
+//
+package com.yahoo.prelude.fastsearch.test.fs4mock;
+
+
+import com.yahoo.prelude.ConfigurationException;
+
+
+/**
+ * Thread-wrapper for MockFDispatch
+ *
+ * @author <a href="mailto:borud@yahoo-inc.com">Bjorn Borud</a>
+ */
+public class DispatchThread extends Thread {
+ int listenPort;
+ long replyDelay;
+ long byteDelay;
+ MockFDispatch dispatch;
+ Object barrier = new Object();
+
+ /**
+ * Instantiate MockFDispatch; if the wanted port is taken we
+ * bump the port number. Note that the delays are not
+ * accurate: in reality they will be significantly longer for
+ * low values.
+ *
+ * @param listenPort Wanted port number, note that this may be
+ * bumped if someone is already running something
+ * on this port, so it is a starting point for
+ * scanning only
+ * @param replyDelay how many milliseconds we should delay when
+ * replying
+ * @param byteDelay how many milliseconds we delay for each byte
+ * written
+ */
+
+ public DispatchThread(int listenPort, long replyDelay, long byteDelay) {
+ this.listenPort = listenPort;
+ this.replyDelay = replyDelay;
+ this.byteDelay = byteDelay;
+ dispatch = new MockFDispatch(listenPort, replyDelay, byteDelay);
+ dispatch.setBarrier(barrier);
+ }
+
+ /**
+ * Run the MockFDispatch and anticipate multiple instances of
+ * same running.
+ */
+ public void run() {
+ int maxTries = 20;
+ // the following section is here to make sure that this
+ // test is somewhat robust, ie. if someone is already
+ // listening to the port in question, we'd like to NOT
+ // fail, but keep probing until we find a port we can use.
+ boolean up = false;
+
+ while ((!up) && (maxTries-- != 0)) {
+ try {
+ dispatch.run();
+ up = true;
+ } catch (ConfigurationException e) {
+ listenPort++;
+ dispatch.setListenPort(listenPort);
+ }
+ }
+ }
+
+ /**
+ * Wait until MockFDispatch is ready to accept connections
+ * or we time out and indicate which of the two outcomes it was.
+ *
+ * @return If we time out we return <code>false</code>. Else we
+ * return <code>true</code>
+ *
+ */
+ public boolean waitOnBarrier(long timeout) throws InterruptedException {
+ long start = System.currentTimeMillis();
+
+ synchronized (barrier) {
+ barrier.wait(timeout);
+ }
+ long diff = System.currentTimeMillis() - start;
+
+ return (diff < timeout);
+ }
+
+ /**
+ * Return the port on which the MockFDispatch actually listens.
+ * use this instead of assuming where it is since, if more than
+ * one application tries to use the port we've assigned to it
+ * we might have to up the port number.
+ *
+ * @return port number of active MockFDispatch instance
+ *
+ */
+ public int listenPort() {
+ return listenPort;
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockBackend.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockBackend.java
new file mode 100644
index 00000000000..d3fbf8f3645
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockBackend.java
@@ -0,0 +1,53 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.fastsearch.test.fs4mock;
+
+import com.yahoo.fs4.mplex.Backend;
+import com.yahoo.fs4.mplex.FS4Channel;
+
+/**
+ * @author bratseth
+ */
+public class MockBackend extends Backend {
+
+ private String hostname;
+ private final long activeDocumentsInBackend;
+ private final boolean working;
+
+ /** Created lazily as we want to have just one but it depends on the channel */
+ private MockFSChannel channel = null;
+
+ public MockBackend() {
+ this("", 0L, true);
+ }
+
+ public MockBackend(String hostname, long activeDocumentsInBackend, boolean working) {
+ super();
+ this.hostname = hostname;
+ this.activeDocumentsInBackend = activeDocumentsInBackend;
+ this.working = working;
+ }
+
+ @Override
+ public FS4Channel openChannel() {
+ if (channel == null)
+ channel = working ? new MockFSChannel(activeDocumentsInBackend, this)
+ : new NonWorkingMockFSChannel(this);
+ return channel;
+ }
+
+ @Override
+ public FS4Channel openPingChannel() { return openChannel(); }
+
+ @Override
+ public String getHost() { return hostname; }
+
+ /** Returns the channel in use or null if no channel has been used yet */
+ public MockFSChannel getChannel() { return channel; }
+
+ public void shutdown() {}
+
+ @Override
+ public boolean probeConnection() {
+ return working;
+ }
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFDispatch.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFDispatch.java
new file mode 100644
index 00000000000..6956f288d1a
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFDispatch.java
@@ -0,0 +1,212 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.fastsearch.test.fs4mock;
+
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.yahoo.prelude.ConfigurationException;
+import com.yahoo.prelude.fastsearch.test.DocsumDefinitionTestCase;
+
+
+/**
+ * A server which replies to any query with the same query result after
+ * a configurable delay, with a configurable slowness (delay between each byte).
+ * Connections are never timed out.
+ *
+ * @author bratseth
+ */
+public class MockFDispatch {
+
+ private static int connectionCount = 0;
+
+ private static Logger log = Logger.getLogger(MockFDispatch.class.getName());
+
+ /** The port we accept incoming requests at */
+ private int listenPort = 0;
+
+ private long replyDelay;
+
+ private long byteDelay;
+
+ private Object barrier;
+
+ private static byte[] queryResultPacketData = new byte[] {
+ 0, 0, 0, 64, 0, 0,
+ 0, 214 - 256, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 0, 0,
+ 25, 0, 0, 0, 111, 0, 0, 0, 97, 0, 0, 0, 3, 0, 0, 0, 23, 0, 0, 0, 7, 0, 0,
+ 0, 36, 0, 0, 0, 4, 0, 0, 0, 21, 0, 0, 0, 8, 0, 0, 0, 37};
+
+ private static byte[] docsumData = DocsumDefinitionTestCase.makeDocsum();
+
+ private static byte[] docsumHeadPacketData = new byte[] {
+ 0, 0, 3, 39, 0, 0,
+ 0, 205 - 256, 0, 0, 0, 1, 0, 0, 0, 0};
+
+ private static byte[] eolPacketData = new byte[] {
+ 0, 0, 0, 8, 0, 0, 0,
+ 200 - 256, 0, 0, 0, 1 };
+
+ private Set<ConnectionThread> connectionThreads = new HashSet<>();
+
+ public MockFDispatch(int listenPort, long replyDelay, long byteDelay) {
+ this.replyDelay = replyDelay;
+ this.byteDelay = byteDelay;
+ this.listenPort = listenPort;
+ }
+
+ public void setBarrier(Object barrier) {
+ this.barrier = barrier;
+ }
+
+ public void setListenPort(int listenPort) {
+ this.listenPort = listenPort;
+ }
+
+ public void run() {
+ try {
+ ServerSocketChannel channel = createServerSocket(listenPort);
+
+ channel.socket().setReuseAddress(true);
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ // notify those waiting at the barrier that they
+ // can now proceed and talk to us
+ synchronized (barrier) {
+ if (barrier != null) {
+ barrier.notify();
+ }
+ }
+ SocketChannel socketChannel = channel.accept();
+
+ connectionThreads.add(new ConnectionThread(socketChannel));
+ } catch (ClosedByInterruptException e) {// We'll exit
+ } catch (ClosedChannelException e) {
+ return;
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Unexpected error reading request", e);
+ }
+ }
+ channel.close();
+ } catch (IOException e) {
+ throw new ConfigurationException("Socket channel failure", e);
+ }
+ }
+
+ private ServerSocketChannel createServerSocket(int listenPort)
+ throws IOException {
+ ServerSocketChannel channel = ServerSocketChannel.open();
+ ServerSocket socket = channel.socket();
+
+ socket.bind(
+ new InetSocketAddress(InetAddress.getLocalHost(), listenPort));
+ String host = socket.getInetAddress().getHostName();
+
+ log.fine("Accepting dfispatch requests at " + host + ":" + listenPort);
+ return channel;
+ }
+
+ public static void main(String[] args) {
+ log.setLevel(Level.FINE);
+ MockFDispatch m = new MockFDispatch(7890, Integer.parseInt(args[0]),
+ Integer.parseInt(args[1]));
+
+ m.run();
+ }
+
+ private class ConnectionThread extends Thread {
+
+ private ByteBuffer writeBuffer = ByteBuffer.allocate(2000);
+
+ private ByteBuffer readBuffer = ByteBuffer.allocate(2000);
+
+ private int connectionNr = 0;
+
+ private SocketChannel channel;
+
+ public ConnectionThread(SocketChannel channel) {
+ this.channel = channel;
+ fillBuffer(writeBuffer);
+ start();
+ }
+
+ private void fillBuffer(ByteBuffer buffer) {
+ buffer.clear();
+ buffer.put(queryResultPacketData);
+ buffer.put(docsumHeadPacketData);
+ buffer.put(docsumData);
+ buffer.put(docsumHeadPacketData);
+ buffer.put(docsumData);
+ buffer.put(eolPacketData);
+ }
+
+ public void run() {
+ connectionNr = connectionCount++;
+ log.fine("Opened connection " + connectionNr);
+
+ try {
+ long lastRequest = System.currentTimeMillis();
+
+ while ((System.currentTimeMillis() - lastRequest) <= 5000
+ && (!isInterrupted())) {
+ readBuffer.clear();
+ channel.read(readBuffer);
+ lastRequest = System.currentTimeMillis();
+ delay(replyDelay);
+
+ if (byteDelay > 0) {
+ writeSlow(writeBuffer);
+ } else {
+ write(writeBuffer);
+ }
+ log.fine(
+ "Replied in "
+ + (System.currentTimeMillis() - lastRequest)
+ + " ms");
+ }
+
+ log.fine("Closing timed out connection " + connectionNr);
+ connectionCount--;
+ channel.close();
+ } catch (IOException e) {}
+ }
+
+ private void write(ByteBuffer writeBuffer) throws IOException {
+ writeBuffer.flip();
+ channel.write(writeBuffer);
+ }
+
+ private void writeSlow(ByteBuffer writeBuffer) throws IOException {
+ writeBuffer.flip();
+ int dataSize = writeBuffer.limit();
+
+ for (int i = 0; i < dataSize; i++) {
+ writeBuffer.position(i);
+ writeBuffer.limit(i + 1);
+ channel.write(writeBuffer);
+ delay(byteDelay);
+ }
+ writeBuffer.limit(dataSize);
+ }
+
+ private void delay(long delay) {
+
+ try {
+ Thread.sleep(delay);
+ } catch (InterruptedException e) {}
+ }
+
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFS4ResourcePool.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFS4ResourcePool.java
new file mode 100644
index 00000000000..0d756cbeff3
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFS4ResourcePool.java
@@ -0,0 +1,63 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.fastsearch.test.fs4mock;
+
+import com.yahoo.fs4.mplex.Backend;
+import com.yahoo.prelude.fastsearch.FS4ResourcePool;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author bratseth
+ */
+public class MockFS4ResourcePool extends FS4ResourcePool {
+
+ private final Map<String, Integer> requestsPerBackend = new HashMap<>();
+ private final Set<String> nonRespondingBackends = new HashSet<>();
+ private final Map<String, Long> activeDocumentsInBackend = new HashMap<>();
+ private final long testingThreadId;
+
+ public MockFS4ResourcePool() {
+ super("container.0", 1);
+ this.testingThreadId = Thread.currentThread().getId();
+ }
+
+ @Override
+ public Backend getBackend(String hostname, int port) {
+ countRequest(hostname + ":" + port);
+ if (nonRespondingBackends.contains(hostname))
+ return new MockBackend(hostname, 0L, false);
+ else
+ return new MockBackend(hostname, activeDocumentsInBackend.getOrDefault(hostname, 0L), true);
+ }
+
+ /**
+ * Returns the number of times a backend for this hostname and port has been requested
+ * from the thread creating this
+ */
+ public int requestCount(String hostname, int port) {
+ return requestsPerBackend.getOrDefault(hostname + ":" + port, 0);
+ }
+
+ /** Sets the number of active documents the given host will report to have in ping responses */
+ public void setActiveDocuments(String hostname, long activeDocuments) {
+ activeDocumentsInBackend.put(hostname, activeDocuments);
+ }
+
+ private void countRequest(String hostAndPort) {
+ // ignore requests from the ping thread to avoid timing issues
+ if (Thread.currentThread().getId() != testingThreadId) return;
+
+ requestsPerBackend.put(hostAndPort, requestsPerBackend.getOrDefault(hostAndPort, 0) + 1);
+ }
+
+ public void setResponding(String hostname, boolean responding) {
+ if (responding)
+ nonRespondingBackends.remove(hostname);
+ else
+ nonRespondingBackends.add(hostname);
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFSChannel.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFSChannel.java
new file mode 100644
index 00000000000..db14a2894db
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/MockFSChannel.java
@@ -0,0 +1,176 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.fastsearch.test.fs4mock;
+
+import com.yahoo.document.GlobalId;
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.BufferTooSmallException;
+import com.yahoo.fs4.DocumentInfo;
+import com.yahoo.fs4.EolPacket;
+import com.yahoo.fs4.GetDocSumsPacket;
+import com.yahoo.fs4.Packet;
+import com.yahoo.fs4.PacketDecoder;
+import com.yahoo.fs4.PingPacket;
+import com.yahoo.fs4.PongPacket;
+import com.yahoo.fs4.QueryPacket;
+import com.yahoo.fs4.QueryResultPacket;
+import com.yahoo.fs4.mplex.Backend;
+import com.yahoo.fs4.mplex.FS4Channel;
+import com.yahoo.prelude.fastsearch.test.DocsumDefinitionTestCase;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * A channel which returns hardcoded packets of the same type as fdispatch
+ */
+public class MockFSChannel extends FS4Channel {
+
+ /** The number of active documents this should report in ping reponses */
+ private final long activeDocuments;
+
+ MockFSChannel(Backend backend) {
+ this(0, backend);
+ }
+
+ MockFSChannel(long activeDocuments, Backend backend) {
+ super(backend, 0);
+ this.activeDocuments = activeDocuments;
+ }
+
+ private BasicPacket lastReceived = null;
+
+ private static QueryPacket lastQueryPacket = null;
+
+ /** Initial value of docstamp */
+ private static int docstamp = 1088490666;
+
+ private static boolean emptyDocsums = false;
+
+ @Override
+ public synchronized boolean sendPacket(BasicPacket packet) {
+ try {
+ if (packet instanceof Packet)
+ packet.encode(ByteBuffer.allocate(65536), 0);
+ } catch (BufferTooSmallException e) {
+ throw new RuntimeException("Too small buffer to encode packet in mock backend.");
+ }
+
+ if (packet instanceof QueryPacket)
+ lastQueryPacket = (QueryPacket) packet;
+
+ lastReceived = packet;
+ notifyMonitor();
+ return true;
+ }
+
+ /** Change docstamp to invalidate cache */
+ public static void resetDocstamp() {
+ docstamp = 1088490666;
+ }
+
+ /** Flip sending (in)valid docsums */
+ public static void setEmptyDocsums(boolean d) {
+ emptyDocsums = d;
+ }
+
+ /** Returns the last query packet received or null if none */
+ public QueryPacket getLastQueryPacket() {
+ return lastQueryPacket;
+ }
+
+ public BasicPacket getLastReceived() {
+ return lastReceived;
+ }
+
+ public BasicPacket[] receivePackets(long timeout, int packetCount) {
+ List<BasicPacket> packets = new java.util.ArrayList<>();
+
+ if (lastReceived instanceof QueryPacket) {
+ lastQueryPacket = (QueryPacket) lastReceived;
+ QueryResultPacket result = QueryResultPacket.create();
+
+ result.setDocstamp(docstamp);
+ result.setChannel(0);
+ result.setTotalDocumentCount(2);
+ result.setOffset(lastQueryPacket.getOffset());
+
+ if (lastQueryPacket.getOffset() == 0
+ && lastQueryPacket.getLastOffset() >= 1) {
+ result.addDocument(
+ new DocumentInfo(DocsumDefinitionTestCase.createGlobalId(123),
+ 2003, 234, 0));
+ }
+ if (lastQueryPacket.getOffset() <= 1
+ && lastQueryPacket.getLastOffset() >= 2) {
+ result.addDocument(
+ new DocumentInfo(DocsumDefinitionTestCase.createGlobalId(456),
+ 1855, 234, 1));
+ }
+ packets.add(result);
+ }
+ else if (lastReceived instanceof GetDocSumsPacket) {
+ addDocsums(packets, lastQueryPacket);
+ }
+ else if (lastReceived instanceof PingPacket) {
+ packets.add(new PongPacket(activeDocuments));
+ }
+ while (packetCount >= 0 && packets.size() > packetCount) {
+ packets.remove(packets.size() - 1);
+ }
+
+ return packets.toArray(new BasicPacket[packets.size()]);
+ }
+
+ /** Adds the number of docsums requested in queryPacket.getHits() */
+ private void addDocsums(List<BasicPacket> packets, QueryPacket queryPacket) {
+ int numHits = queryPacket.getHits();
+
+ if (lastReceived instanceof GetDocSumsPacket) {
+ numHits = ((GetDocSumsPacket) lastReceived).getNumDocsums();
+ }
+ for (int i = 0; i < numHits; i++) {
+ ByteBuffer buffer;
+
+ if (emptyDocsums) {
+ buffer = createEmptyDocsumPacketData();
+ } else {
+ int[] docids = {
+ 123, 456, 789, 789, 789, 789, 789, 789, 789,
+ 789, 789, 789 };
+
+ buffer = createDocsumPacketData(docids[i], DocsumDefinitionTestCase.makeDocsum());
+ }
+ buffer.position(0);
+ packets.add(PacketDecoder.decode(buffer));
+ }
+ packets.add(EolPacket.create());
+ }
+
+ private ByteBuffer createEmptyDocsumPacketData() {
+ ByteBuffer buffer = ByteBuffer.allocate(16);
+
+ buffer.limit(buffer.capacity());
+ buffer.position(0);
+ buffer.putInt(12); // length
+ buffer.putInt(205); // a code for docsumpacket
+ buffer.putInt(0); // channel
+ buffer.putInt(0); // dummy location
+ return buffer;
+ }
+
+ private ByteBuffer createDocsumPacketData(int docid, byte[] docsumData) {
+ ByteBuffer buffer = ByteBuffer.allocate(docsumData.length + 4 + 8 + GlobalId.LENGTH);
+
+ buffer.limit(buffer.capacity());
+ buffer.position(0);
+ buffer.putInt(docsumData.length + 8 + GlobalId.LENGTH);
+ buffer.putInt(205); // Docsum packet code
+ buffer.putInt(0);
+ byte[] rawGid = DocsumDefinitionTestCase.createGlobalId(docid).getRawId();
+ buffer.put(rawGid);
+ buffer.put(docsumData);
+ return buffer;
+ }
+
+ public void close() {}
+}
diff --git a/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/NonWorkingMockFSChannel.java b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/NonWorkingMockFSChannel.java
new file mode 100644
index 00000000000..c7425afd611
--- /dev/null
+++ b/container-search/src/test/java/com/yahoo/prelude/fastsearch/test/fs4mock/NonWorkingMockFSChannel.java
@@ -0,0 +1,21 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.prelude.fastsearch.test.fs4mock;
+
+import com.yahoo.fs4.BasicPacket;
+import com.yahoo.fs4.mplex.Backend;
+
+/**
+ * @author bratseth
+ */
+public class NonWorkingMockFSChannel extends MockFSChannel {
+
+ public NonWorkingMockFSChannel(Backend backend) {
+ super(backend);
+ }
+
+ @Override
+ public synchronized boolean sendPacket(BasicPacket bPacket) {
+ return false;
+ }
+
+}
diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java b/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java
index 7ee62ae9978..42a22f6f86b 100644
--- a/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java
+++ b/container-search/src/test/java/com/yahoo/search/dispatch/MockSearchCluster.java
@@ -89,6 +89,11 @@ public class MockSearchCluster extends SearchCluster {
}
@Override
+ public ImmutableMultimap<String, Node> nodesByHost() {
+ return nodesByHost;
+ }
+
+ @Override
public Optional<Node> directDispatchTarget() {
return Optional.empty();
}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/vespa/HitConverterTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/vespa/HitConverterTestCase.java
index 7bdf1916d85..6ed8a209cc5 100644
--- a/container-search/src/test/java/com/yahoo/search/grouping/vespa/HitConverterTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/grouping/vespa/HitConverterTestCase.java
@@ -3,6 +3,7 @@ package com.yahoo.search.grouping.vespa;
import com.yahoo.document.DocumentId;
import com.yahoo.document.GlobalId;
+import com.yahoo.fs4.QueryPacketData;
import com.yahoo.net.URI;
import com.yahoo.prelude.fastsearch.GroupingListHit;
import com.yahoo.prelude.fastsearch.DocsumDefinitionSet;
@@ -61,6 +62,19 @@ public class HitConverterTestCase {
}
@Test
+ public void requireThatHitTagIsCopiedFromGroupingListContext() {
+ QueryPacketData ctxTag = new QueryPacketData();
+ GroupingListHit ctxHit = context();
+ ctxHit.setQueryPacketData(ctxTag);
+
+ HitConverter converter = new HitConverter(new MySearcher(), new Query());
+ Hit hit = converter.toSearchHit("default", new FS4Hit(1, createGlobalId(2), 3).setContext(ctxHit));
+ assertNotNull(hit);
+ assertTrue(hit instanceof FastHit);
+ assertSame(ctxTag, ((FastHit)hit).getQueryPacketData());
+ }
+
+ @Test
public void requireThatSummaryClassIsSet() {
Searcher searcher = new MySearcher();
HitConverter converter = new HitConverter(searcher, new Query());