diff options
author | Henning Baldersheim <balder@yahoo-inc.com> | 2019-09-17 22:39:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-09-17 22:39:26 +0200 |
commit | a573985d1127835f0ecb5047694ffe23e8baefe7 (patch) | |
tree | 463ab5145edf4a26243cc4a92d04bfb82a3c1580 /container-search/src/test/java/com/yahoo | |
parent | ddb9cd0a539b57c41587ccdec1040b48169d3cec (diff) |
Revert "Balder/no more fs4 dispatching from fastsearcher"
Diffstat (limited to 'container-search/src/test/java/com/yahoo')
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()); |