/* * Copyright (c) 2015 Spotify AB * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.spotify.folsom; import com.google.common.base.Charsets; import com.google.common.net.HostAndPort; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutionException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; public class MisbehavingServerTest { private Server server; @Before public void setup() throws Exception { } @After public void tearDown() throws Exception { server.stop(); } @Test public void testInvalidAsciiResponse() throws Throwable { testAsciiGet("HIPPO\r\n", "Unexpected line: HIPPO"); } @Test public void testInvalidAsciiResponse2() throws Throwable { testAsciiGet("HIPPOS\r\n", "Unexpected line: HIPPOS"); } @Test public void testInvalidAsciiResponse3() throws Throwable { testAsciiGet("AAAAAAAAAAAAAAARGH\r\n", "Unexpected line: AAAAAAAAAAAAAAARGH"); } @Test public void testAsciiNotANumber() throws Throwable { testAsciiGet("123ABC\r\n", "Unexpected line: 123ABC"); } @Test public void testEmptyAsciiResponse() throws Throwable { testAsciiGet("\r\n", "Unexpected line: "); } @Test public void testNotNewline() throws Throwable { testAsciiGet("\rFoo\n", "Expected newline, got something else"); } @Test public void testBadAsciiGet() throws Throwable { testAsciiGet("VALUE\r\n", "Unexpected line: VALUE"); } @Test public void testBadAsciiGet2() throws Throwable { testAsciiGet("VALUE \r\n", "Unexpected line: VALUE "); } @Test public void testBadAsciiGet3() throws Throwable { testAsciiGet("VALUE key\r\n", "Unexpected line: VALUE key"); } @Test public void testBadAsciiGet4() throws Throwable { testAsciiGet("VALUE key 123\r\n", "Unexpected line: VALUE key 123"); } @Test public void testBadAsciiGet5() throws Throwable { testAsciiGet("VALUE key 123 456\r\n", "Timeout"); } @Test public void testBadAsciiGet6() throws Throwable { testAsciiGet("VALUE key 123 0\r\nfoo\r\n", "Unexpected end of data block: foo"); } @Test public void testBadAsciiGet7() throws Throwable { testAsciiGet("VALUE key 123 0\r\n\r\nSTORED\r\n", "Unexpected line: STORED"); } @Test public void testBadAsciiGet8() throws Throwable { testAsciiGet("VALUE key 123 1a3\r\n", "Unexpected line: VALUE key 123 1a3"); } @Test public void testWrongAsciiKey() throws Throwable { testAsciiGet("VALUE otherkey 123 0\r\n\r\nEND\r\n", "Expected key key but got otherkey"); } @Test public void testTooManyAsciiValues() throws Throwable { testAsciiGet("" + "VALUE key 123 0\r\n" + "\r\n" + "VALUE key 123 0\r\n" + "\r\n" + "END\r\n", "Too many responses, expected 1 but got 2"); } @Test public void testAsciiWrongResponseType() throws Throwable { testAsciiGet("1234\r\n", "Unexpected response type: NUMERIC_VALUE"); } @Test public void testBadAsciiTouch() throws Throwable { testAsciiTouch("STORED\r\n", "Unexpected line: STORED"); } @Test public void testBadAsciiSet() throws Throwable { testAsciiSet("TOUCHED\r\n", "Unexpected line: TOUCHED"); } private void testAsciiGet(String response, String expectedError) throws Exception { MemcacheClient<String> client = setupAscii(response); try { client.get("key").get(); fail(); } catch (ExecutionException e) { Throwable cause = e.getCause(); assertEquals(MemcacheClosedException.class, cause.getClass()); assertEquals(expectedError, cause.getMessage()); } } private void testAsciiTouch(String response, String expectedError) throws Exception { MemcacheClient<String> client = setupAscii(response); try { client.touch("key", 123).get(); fail(); } catch (ExecutionException e) { Throwable cause = e.getCause(); assertEquals(MemcacheClosedException.class, cause.getClass()); assertEquals(expectedError, cause.getMessage()); } } private void testAsciiSet(String response, String expectedError) throws Exception { MemcacheClient<String> client = setupAscii(response); try { client.set("key", "value", 123).get(); fail(); } catch (ExecutionException e) { Throwable cause = e.getCause(); assertEquals(MemcacheClosedException.class, cause.getClass()); assertEquals(expectedError, cause.getMessage()); } } private MemcacheClient<String> setupAscii(String response) throws Exception { server = new Server(response); MemcacheClient<String> client = MemcacheClientBuilder.newStringClient() .withAddress(HostAndPort.fromParts("localhost", server.port)) .withRequestTimeoutMillis(100L) .withRetry(false) .connectAscii(); ConnectFuture.connectFuture(client).get(); return client; } private static class Server { private final int port; private final ServerSocket serverSocket; private final Thread thread; private volatile Throwable failure; private volatile Socket socket; private Server(String responseString) throws IOException { final byte[] response = responseString.getBytes(Charsets.UTF_8); serverSocket = new ServerSocket(0); port = serverSocket.getLocalPort(); thread = new Thread(new Runnable() { @Override public void run() { try { socket = serverSocket.accept(); handleConnection(socket); } catch (Throwable e) { failure = e; failure.printStackTrace(); } } private void handleConnection(Socket socket) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()), 1); String s = reader.readLine(); if (s.startsWith("get ") || s.startsWith("touch ")) { // Don't need to read any more lines } else if (s.startsWith("set ")) { // Read the value too reader.readLine(); } else { throw new RuntimeException("Unhandled command: " + s); } socket.getOutputStream().write(response); socket.getOutputStream().flush(); } }); thread.start(); } public void stop() throws Exception { thread.join(); serverSocket.close(); if (socket != null) { socket.close(); } if (failure != null) { fail(failure.getClass().getSimpleName() + ": " + failure.getMessage()); } } } }