diff options
author | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
---|---|---|
committer | Jon Bratseth <bratseth@yahoo-inc.com> | 2016-06-15 23:09:44 +0200 |
commit | 72231250ed81e10d66bfe70701e64fa5fe50f712 (patch) | |
tree | 2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-core/src/test/java/com/yahoo/container |
Publish
Diffstat (limited to 'container-core/src/test/java/com/yahoo/container')
20 files changed, 2182 insertions, 0 deletions
diff --git a/container-core/src/test/java/com/yahoo/container/handler/AccessLogRequestHandlerTest.java b/container-core/src/test/java/com/yahoo/container/handler/AccessLogRequestHandlerTest.java new file mode 100644 index 00000000000..ad266a4a87f --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/handler/AccessLogRequestHandlerTest.java @@ -0,0 +1,47 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler; + +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.logging.CircularArrayAccessLogKeeper; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.Executor; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +public class AccessLogRequestHandlerTest { + + private final CircularArrayAccessLogKeeper keeper = new CircularArrayAccessLogKeeper(); + private final Executor executor = mock(Executor.class); + private final AccessLogRequestHandler handler = new AccessLogRequestHandler(executor, keeper); + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + + @Test + public void testOneLogLine() throws IOException { + keeper.addUri("foo"); + HttpResponse response = handler.handle(null); + response.render(out); + assertThat(out.toString(), is("{\"entries\":[{\"url\":\"foo\"}]}")); + } + + @Test + public void testEmpty() throws IOException { + HttpResponse response = handler.handle(null); + response.render(out); + assertThat(out.toString(), is("{\"entries\":[]}")); + } + + @Test + public void testManyLogLines() throws IOException { + keeper.addUri("foo"); + keeper.addUri("foo"); + HttpResponse response = handler.handle(null); + response.render(out); + assertThat(out.toString(), is("{\"entries\":[{\"url\":\"foo\"},{\"url\":\"foo\"}]}")); + } + +}
\ No newline at end of file diff --git a/container-core/src/test/java/com/yahoo/container/handler/ThreadPoolProviderTestCase.java b/container-core/src/test/java/com/yahoo/container/handler/ThreadPoolProviderTestCase.java new file mode 100644 index 00000000000..95ce73b4414 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/handler/ThreadPoolProviderTestCase.java @@ -0,0 +1,134 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler; + +import static org.junit.Assert.fail; + +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +import com.yahoo.container.protect.ProcessTerminator; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; + +import com.yahoo.concurrent.Receiver; +import com.yahoo.concurrent.Receiver.MessageState; +import com.yahoo.collections.Tuple2; +import com.yahoo.jdisc.Metric; + +import static org.junit.Assert.assertEquals; + +/** + * Check threadpool provider accepts tasks and shuts down properly. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class ThreadPoolProviderTestCase { + + @Test + public final void testThreadPoolProvider() throws InterruptedException { + ThreadpoolConfig config = new ThreadpoolConfig(new ThreadpoolConfig.Builder().maxthreads(1)); + ThreadPoolProvider provider = new ThreadPoolProvider(config, Mockito.mock(Metric.class)); + Executor exec = provider.get(); + Tuple2<MessageState, Boolean> reply; + FlipIt command = new FlipIt(); + for (boolean done = false; !done;) { + try { + exec.execute(command); + done = true; + } catch (RejectedExecutionException e) { + // just try again + } + } + reply = command.didItRun.get(5 * 60 * 1000); + if (reply.first != MessageState.VALID) { + fail("Executor task probably timed out, five minutes should be enough to flip a boolean."); + } + if (reply.second != Boolean.TRUE) { + fail("Executor task seemed to run, but did not get correct value."); + } + provider.deconstruct(); + command = new FlipIt(); + try { + exec.execute(command); + } catch (final RejectedExecutionException e) { + // this is what should happen + return; + } + fail("Pool did not reject tasks after shutdown."); + } + + private class FlipIt implements Runnable { + public final Receiver<Boolean> didItRun = new Receiver<>(); + + @Override + public void run() { + didItRun.put(Boolean.TRUE); + } + } + + @Test + @Ignore + public final void testThreadPoolProviderTerminationOnBreakdown() throws InterruptedException { + ThreadpoolConfig config = new ThreadpoolConfig(new ThreadpoolConfig.Builder().maxthreads(2) + .maxThreadExecutionTimeSeconds(1)); + MockProcessTerminator terminator = new MockProcessTerminator(); + ThreadPoolProvider provider = new ThreadPoolProvider(config, Mockito.mock(Metric.class), terminator); + + // No dying when threads hang shorter than max thread execution time + provider.get().execute(new Hang(500)); + provider.get().execute(new Hang(500)); + assertEquals(0, terminator.dieRequests); + assertRejected(provider, new Hang(500)); // no more threads + assertEquals(0, terminator.dieRequests); // ... but not for long enough yet + try { Thread.sleep(1500); } catch (InterruptedException e) {} + provider.get().execute(new Hang(1)); + assertEquals(0, terminator.dieRequests); + try { Thread.sleep(50); } catch (InterruptedException e) {} // Make sure both threads are available + + // Dying when hanging both thread pool threads for longer than max thread execution time + provider.get().execute(new Hang(2000)); + provider.get().execute(new Hang(2000)); + assertEquals(0, terminator.dieRequests); + assertRejected(provider, new Hang(2000)); // no more threads + assertEquals(0, terminator.dieRequests); // ... but not for long enough yet + try { Thread.sleep(1500); } catch (InterruptedException e) {} + assertRejected(provider, new Hang(2000)); // no more threads + assertEquals(1, terminator.dieRequests); // ... for longer than maxThreadExecutionTime + } + + private void assertRejected(ThreadPoolProvider provider, Runnable task) { + try { + provider.get().execute(task); + fail("Expected execution rejected"); + } catch (final RejectedExecutionException expected) { + } + } + + private class Hang implements Runnable { + + private final long hangMillis; + + public Hang(int hangMillis) { + this.hangMillis = hangMillis; + } + + @Override + public void run() { + try { Thread.sleep(hangMillis); } catch (InterruptedException e) {} + } + + } + + private static class MockProcessTerminator extends ProcessTerminator { + + public volatile int dieRequests = 0; + + @Override + public void logAndDie(String message, boolean dumpThreads) { + dieRequests++; + } + + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java b/container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java new file mode 100644 index 00000000000..a4f7bec07f6 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java @@ -0,0 +1,220 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler; + +import com.google.inject.Key; +import com.yahoo.container.core.VipStatusConfig; +import com.yahoo.jdisc.Container; +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.References; +import com.yahoo.jdisc.ResourceReference; +import com.yahoo.jdisc.handler.BufferedContentChannel; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.ReadableContentChannel; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.HttpRequest; +import com.yahoo.jdisc.service.CurrentContainer; +import com.yahoo.text.Utf8; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * Check semantics of VIP status handler. Do note this handler does not need to + * care about the incoming URI, that's 100% handled in JDIsc by the binding + * pattern. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class VipStatusHandlerTestCase { + + public static final class MockResponseHandler implements ResponseHandler { + final ReadableContentChannel channel = new ReadableContentChannel(); + + @Override + public ContentChannel handleResponse( + final com.yahoo.jdisc.Response response) { + return channel; + } + } + + Metric metric = Mockito.mock(Metric.class); + + @Test + public final void testHandleRequest() { + final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(false) + .noSearchBackendsImpliesOutOfService(false)); + final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); + final MockResponseHandler responseHandler = new MockResponseHandler(); + final HttpRequest request = createRequest(); + final BufferedContentChannel requestContent = createChannel(); + handler.handleRequest(request, requestContent, responseHandler); + final ByteBuffer b = responseHandler.channel.read(); + final byte[] asBytes = new byte[b.remaining()]; + b.get(asBytes); + assertEquals(VipStatusHandler.OK_MESSAGE, Utf8.toString(asBytes)); + } + + public static final class NotFoundResponseHandler implements + ResponseHandler { + final ReadableContentChannel channel = new ReadableContentChannel(); + + @Override + public ContentChannel handleResponse( + final com.yahoo.jdisc.Response response) { + assertEquals(com.yahoo.jdisc.Response.Status.NOT_FOUND, + response.getStatus()); + return channel; + } + } + + @Test + public final void testFileNotFound() { + final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(true) + .statusfile("/VipStatusHandlerTestCaseFileThatReallyReallyShouldNotExist") + .noSearchBackendsImpliesOutOfService(false)); + final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); + final NotFoundResponseHandler responseHandler = new NotFoundResponseHandler(); + final HttpRequest request = createRequest(); + final BufferedContentChannel requestContent = createChannel(); + handler.handleRequest(request, requestContent, responseHandler); + final ByteBuffer b = responseHandler.channel.read(); + final byte[] asBytes = new byte[b.remaining()]; + b.get(asBytes); + assertEquals( + VipStatusHandler.StatusResponse.COULD_NOT_FIND_STATUS_FILE, + Utf8.toString(asBytes)); + } + + @Test + public final void testFileFound() throws IOException { + final File statusFile = File.createTempFile("VipStatusHandlerTestCase", + null); + try { + final FileWriter writer = new FileWriter(statusFile); + final String OK = "OK\n"; + writer.write(OK); + writer.close(); + final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(true) + .statusfile(statusFile.getAbsolutePath()).noSearchBackendsImpliesOutOfService(false)); + final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric); + final MockResponseHandler responseHandler = new MockResponseHandler(); + final HttpRequest request = createRequest(); + final BufferedContentChannel requestContent = createChannel(); + handler.handleRequest(request, requestContent, responseHandler); + final ByteBuffer b = responseHandler.channel.read(); + final byte[] asBytes = new byte[b.remaining()]; + b.get(asBytes); + assertEquals(OK, Utf8.toString(asBytes)); + } finally { + statusFile.delete(); + } + } + + @Test + public final void testProgrammaticallyRemovedFromRotation() throws IOException { + VipStatus vipStatus = new VipStatus(); + final VipStatusConfig config = new VipStatusConfig(new VipStatusConfig.Builder().accessdisk(false) + .noSearchBackendsImpliesOutOfService(true)); + final VipStatusHandler handler = new VipStatusHandler(Executors.newCachedThreadPool(), config, metric, vipStatus); + + vipStatus.removeFromRotation(this); + + { + final MockResponseHandler responseHandler = new MockResponseHandler(); + final HttpRequest request = createRequest(); + final BufferedContentChannel requestContent = createChannel(); + handler.handleRequest(request, requestContent, responseHandler); + final ByteBuffer b = responseHandler.channel.read(); + final byte[] asBytes = new byte[b.remaining()]; + b.get(asBytes); + assertEquals(VipStatusHandler.StatusResponse.NO_SEARCH_BACKENDS, Utf8.toString(asBytes)); + } + + vipStatus.addToRotation(this); + + { + final MockResponseHandler responseHandler = new MockResponseHandler(); + final HttpRequest request = createRequest(); + final BufferedContentChannel requestContent = createChannel(); + handler.handleRequest(request, requestContent, responseHandler); + final ByteBuffer b = responseHandler.channel.read(); + final byte[] asBytes = new byte[b.remaining()]; + b.get(asBytes); + assertEquals(VipStatusHandler.OK_MESSAGE, Utf8.toString(asBytes)); + } + } + + public static HttpRequest createRequest() { + return createRequest("http://localhost/search/?query=geewhiz"); + } + + public static HttpRequest createRequest(String uri) { + HttpRequest request = null; + try { + request = HttpRequest.newClientRequest(new com.yahoo.jdisc.Request( + new MockCurrentContainer(), new URI(uri)), new URI(uri), + HttpRequest.Method.GET, HttpRequest.Version.HTTP_1_1); + request.setRemoteAddress(new InetSocketAddress(0)); + } catch (URISyntaxException e) { + fail("Illegal URI string in test?"); + } + return request; + } + + public static BufferedContentChannel createChannel() { + BufferedContentChannel channel = new BufferedContentChannel(); + channel.close(null); + return channel; + } + + private static class MockCurrentContainer implements CurrentContainer { + @Override + public Container newReference(java.net.URI uri) { + return new Container() { + + @Override + public RequestHandler resolveHandler(com.yahoo.jdisc.Request request) { + return null; + } + + @Override + public <T> T getInstance(Key<T> tKey) { + return null; + } + + @Override + public <T> T getInstance(Class<T> tClass) { + return null; + } + + @Override + public ResourceReference refer() { + return References.NOOP_REFERENCE; + } + + @Override + public void release() { + // NOP + } + + @Override + public long currentTimeMillis() { + return 0; + } + }; + } + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java b/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java new file mode 100644 index 00000000000..740f15622bc --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java @@ -0,0 +1,42 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * Smoke test that VipStatus has the right basic logic. + * + * @author steinar + */ +public class VipStatusTestCase { + + @Test + public final void testSmoke() { + Object cluster1 = new Object(); + Object cluster2 = new Object(); + Object cluster3 = new Object(); + VipStatus v = new VipStatus(); + // initial state + assertTrue(v.isInRotation()); + // all clusters down + v.removeFromRotation(cluster1); + v.removeFromRotation(cluster2); + v.removeFromRotation(cluster3); + assertFalse(v.isInRotation()); + // some clusters down + v.addToRotation(cluster2); + assertTrue(v.isInRotation()); + // all clusters up + v.addToRotation(cluster1); + v.addToRotation(cluster3); + assertTrue(v.isInRotation()); + // and down again + v.removeFromRotation(cluster1); + v.removeFromRotation(cluster2); + v.removeFromRotation(cluster3); + assertFalse(v.isInRotation()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/handler/test/MockServiceTest.java b/container-core/src/test/java/com/yahoo/container/handler/test/MockServiceTest.java new file mode 100644 index 00000000000..d582566cb03 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/handler/test/MockServiceTest.java @@ -0,0 +1,88 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.handler.test; + +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.logging.AccessLog; +import com.yahoo.filedistribution.fileacquirer.MockFileAcquirer; +import org.junit.Test; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.concurrent.Executor; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; + +/** + * @author lulf + */ +public class MockServiceTest { + + private final File testFile = new File("src/test/java/com/yahoo/container/handler/test/test.txt"); + + @Test + public void testHandlerTextFormat() throws InterruptedException, IOException { + HttpResponse response = runHandler(com.yahoo.jdisc.http.HttpRequest.Method.GET, "/foo/bar"); + assertResponse(response, 200, "Hello\nThere!"); + + response = runHandler(com.yahoo.jdisc.http.HttpRequest.Method.GET, "http://my.host:8080/foo/bar?key1=foo&key2=bar"); + assertResponse(response, 200, "With params!"); + + response = runHandler(com.yahoo.jdisc.http.HttpRequest.Method.PUT, "/bar"); + assertResponse(response, 301, "My data is on a single line"); + } + + @Test + public void testNoHandlerFound() throws InterruptedException, IOException { + HttpResponse response = runHandler(com.yahoo.jdisc.http.HttpRequest.Method.DELETE, "/foo/bar"); + assertThat(response.getStatus(), is(404)); + assertResponseContents(response, "DELETE:/foo/bar was not found"); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnknownFileType() throws InterruptedException, IOException { + runHandlerWithFile(com.yahoo.jdisc.http.HttpRequest.Method.GET, "", new File("nonexistant")); + } + + @Test(expected = FileNotFoundException.class) + public void testExceptionResponse() throws InterruptedException, IOException { + runHandlerWithFile(com.yahoo.jdisc.http.HttpRequest.Method.GET, "", new File("nonexistant.txt")); + } + + private void assertResponse(HttpResponse response, int expectedCode, String expectedMessage) throws IOException { + assertThat(response.getStatus(), is(expectedCode)); + assertResponseContents(response, expectedMessage); + } + + private void assertResponseContents(HttpResponse response, String expected) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + response.render(baos); + assertThat(baos.toString(), is(expected)); + } + + private void assertResponseOk(HttpResponse response) { + assertThat(response.getStatus(), is(200)); + assertThat(response.getContentType(), is("text/plain")); + } + + private HttpResponse runHandler(com.yahoo.jdisc.http.HttpRequest.Method method, String path) throws InterruptedException, IOException { + return runHandlerWithFile(method, path, testFile); + } + + private HttpResponse runHandlerWithFile(com.yahoo.jdisc.http.HttpRequest.Method method, String path, File file) throws InterruptedException, IOException { + MockserviceConfig.Builder builder = new MockserviceConfig.Builder(); + builder.file(file.getPath()); + MockService handler = new MockService(new MockExecutor(), AccessLog.voidAccessLog(), MockFileAcquirer.returnFile(file), new MockserviceConfig(builder), null); + return handler.handle(HttpRequest.createTestRequest(path, method)); + } + + private static class MockExecutor implements Executor { + @Override + public void execute(Runnable command) { + command.run(); + } + } +} diff --git a/container-core/src/test/java/com/yahoo/container/handler/test/test.txt b/container-core/src/test/java/com/yahoo/container/handler/test/test.txt new file mode 100644 index 00000000000..baca20fbbdc --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/handler/test/test.txt @@ -0,0 +1,6 @@ +GET:/foo/bar:200:Hello +There! + +PUT:/bar:301:My data is on a single line + +GET:/foo/bar?key1=foo&key2=bar:200:With params! diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/ExtendedResponseTestCase.java b/container-core/src/test/java/com/yahoo/container/jdisc/ExtendedResponseTestCase.java new file mode 100644 index 00000000000..78424b2f4e0 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/ExtendedResponseTestCase.java @@ -0,0 +1,89 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.handler.CompletionHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.text.Utf8; + +/** + * API test for ExtendedResponse. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class ExtendedResponseTestCase { + + private static final String COM_YAHOO_CONTAINER_JDISC_EXTENDED_RESPONSE_TEST_CASE = "com.yahoo.container.jdisc.ExtendedResponseTestCase"; + ExtendedResponse r; + + private static class TestResponse extends ExtendedResponse { + + public TestResponse(int status) { + super(status); + } + + + @Override + public void render(OutputStream output, ContentChannel networkChannel, + CompletionHandler handler) throws IOException { + // yes, this is sync rendering, so sue me :p + try { + output.write(Utf8.toBytes(COM_YAHOO_CONTAINER_JDISC_EXTENDED_RESPONSE_TEST_CASE)); + } finally { + if (networkChannel != null) { + networkChannel.close(handler); + } + } + } + } + + + @Before + public void setUp() throws Exception { + r = new TestResponse(Response.Status.OK); + } + + @After + public void tearDown() throws Exception { + r = null; + } + + @Test + public final void testRenderOutputStreamContentChannelCompletionHandler() throws IOException { + ByteArrayOutputStream b = new ByteArrayOutputStream(); + r.render(b, null, null); + assertEquals(COM_YAHOO_CONTAINER_JDISC_EXTENDED_RESPONSE_TEST_CASE, Utf8.toString(b.toByteArray())); + } + + + @Test + public final void testGetParsedQuery() { + assertNull(r.getParsedQuery()); + } + + @Test + public final void testGetTiming() { + assertNull(r.getTiming()); + } + + @Test + public final void testGetCoverage() { + assertNull(r.getCoverage()); + } + + @Test + public final void testGetHitCounts() { + assertNull(r.getHitCounts()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/HttpRequestTestCase.java b/container-core/src/test/java/com/yahoo/container/jdisc/HttpRequestTestCase.java new file mode 100644 index 00000000000..6434c1c3e7e --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/HttpRequestTestCase.java @@ -0,0 +1,104 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import static org.junit.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.yahoo.jdisc.http.HttpRequest.Method; +import com.yahoo.text.Utf8; + +/** + * API control of HttpRequest. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class HttpRequestTestCase { + private static final String X_RAY_YANKEE_ZULU = "x-ray yankee zulu"; + private static final String HTTP_MAILHOST_25_ALPHA_BRAVO_CHARLIE_DELTA = "http://mailhost:25/alpha?bravo=charlie&falseboolean=false&trueboolean=true"; + HttpRequest r; + InputStream requestData; + + @Before + public void setUp() throws Exception { + requestData = new ByteArrayInputStream(Utf8.toBytes(X_RAY_YANKEE_ZULU)); + r = HttpRequest.createTestRequest(HTTP_MAILHOST_25_ALPHA_BRAVO_CHARLIE_DELTA, Method.GET, requestData, Collections.singletonMap("foxtrot", "golf")); + } + + @After + public void tearDown() throws Exception { + r = null; + } + + @Test + public final void testGetMethod() { + assertSame(Method.GET, r.getMethod()); + } + + @Test + public final void testGetUri() throws URISyntaxException { + assertEquals(new URI(HTTP_MAILHOST_25_ALPHA_BRAVO_CHARLIE_DELTA), r.getUri()); + } + + @Test + public final void testGetJDiscRequest() throws URISyntaxException { + assertEquals(new URI(HTTP_MAILHOST_25_ALPHA_BRAVO_CHARLIE_DELTA), r.getJDiscRequest().getUri()); + } + + @Test + public final void testGetProperty() { + assertEquals("charlie", r.getProperty("bravo")); + assertEquals("golf", r.getProperty("foxtrot")); + assertNull(r.getProperty("zulu")); + } + + @Test + public final void testPropertyMap() { + assertEquals(4, r.propertyMap().size()); + } + + @Test + public final void testGetBooleanProperty() { + assertTrue(r.getBooleanProperty("trueboolean")); + assertFalse(r.getBooleanProperty("falseboolean")); + assertFalse(r.getBooleanProperty("bravo")); + } + + @Test + public final void testHasProperty() { + assertFalse(r.hasProperty("alpha")); + assertTrue(r.hasProperty("bravo")); + } + + @Test + public final void testGetHeader() { + assertNull(r.getHeader("SyntheticHeaderFor-com.yahoo.container.jdisc.HttpRequestTestCase")); + } + + @Test + public final void testGetHost() { + assertEquals("mailhost", r.getHost()); + } + + @Test + public final void testGetPort() { + assertEquals(25, r.getPort()); + } + + @Test + public final void testGetData() throws IOException { + byte[] b = new byte[X_RAY_YANKEE_ZULU.length()]; + r.getData().read(b); + assertEquals(X_RAY_YANKEE_ZULU, Utf8.toString(b)); + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/HttpResponseTestCase.java b/container-core/src/test/java/com/yahoo/container/jdisc/HttpResponseTestCase.java new file mode 100644 index 00000000000..6349da6e771 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/HttpResponseTestCase.java @@ -0,0 +1,82 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import static org.junit.Assert.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.yahoo.jdisc.Response; +import com.yahoo.text.Utf8; + +/** + * API test for HttpResponse. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class HttpResponseTestCase { + + private static final String COM_YAHOO_CONTAINER_JDISC_HTTP_RESPONSE_TEST_CASE_TEST_RESPONSE = "com.yahoo.container.jdisc.HttpResponseTestCase.TestResponse"; + + private static class TestResponse extends HttpResponse { + + public TestResponse(int status) { + super(status); + } + + @Override + public void render(OutputStream outputStream) throws IOException { + outputStream.write(Utf8.toBytes(COM_YAHOO_CONTAINER_JDISC_HTTP_RESPONSE_TEST_CASE_TEST_RESPONSE)); + } + } + + HttpResponse r; + + @Before + public void setUp() throws Exception { + r = new TestResponse(Response.Status.OK); + } + + @After + public void tearDown() throws Exception { + r = null; + } + + @Test + public final void testRender() throws IOException { + ByteArrayOutputStream o = new ByteArrayOutputStream(1024); + r.render(o); + assertEquals(COM_YAHOO_CONTAINER_JDISC_HTTP_RESPONSE_TEST_CASE_TEST_RESPONSE, Utf8.toString(o.toByteArray())); + } + + @Test + public final void testGetStatus() { + assertEquals(Response.Status.OK, r.getStatus()); + } + + @Test + public final void testHeaders() { + assertNotNull(r.headers()); + } + + @Test + public final void testGetJdiscResponse() { + assertNotNull(r.getJdiscResponse()); + } + + @Test + public final void testGetContentType() { + assertEquals(HttpResponse.DEFAULT_MIME_TYPE, r.getContentType()); + } + + @Test + public final void testGetCharacterEncoding() { + assertEquals(HttpResponse.DEFAULT_CHARACTER_ENCODING, r.getCharacterEncoding()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/LoggingRequestHandlerTestCase.java b/container-core/src/test/java/com/yahoo/container/jdisc/LoggingRequestHandlerTestCase.java new file mode 100644 index 00000000000..d2d74502102 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/LoggingRequestHandlerTestCase.java @@ -0,0 +1,224 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.URISyntaxException; +import java.nio.ByteBuffer; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import com.google.inject.Key; +import com.yahoo.container.logging.HitCounts; +import com.yahoo.jdisc.Container; +import com.yahoo.jdisc.References; +import com.yahoo.jdisc.ResourceReference; +import com.yahoo.jdisc.handler.RequestHandler; +import com.yahoo.jdisc.service.CurrentContainer; +import java.net.URI; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.yahoo.component.ComponentId; +import com.yahoo.component.provider.ComponentRegistry; +import com.yahoo.container.handler.Coverage; +import com.yahoo.container.handler.Timing; +import com.yahoo.container.logging.AccessLog; +import com.yahoo.container.logging.AccessLogEntry; +import com.yahoo.container.logging.AccessLogInterface; +import com.yahoo.jdisc.handler.BufferedContentChannel; +import com.yahoo.jdisc.handler.CompletionHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.ResponseHandler; + +/** + * Test contracts in LoggingRequestHandler. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class LoggingRequestHandlerTestCase { + + StartTimePusher accessLogging; + AccessLogTestHandler handler; + ExecutorService executor; + + public static final class NoTimingResponse extends ExtendedResponse { + + public NoTimingResponse() { + super(200); + } + + + @Override + public HitCounts getHitCounts() { + return new HitCounts(1, 1, 1, 1, 1); + } + + @Override + public Timing getTiming() { + return null; + } + + @Override + public Coverage getCoverage() { + return new Coverage(1, 1, true); + } + + + @Override + public void render(OutputStream output, ContentChannel networkChannel, + CompletionHandler handler) throws IOException { + networkChannel.close(handler); + } + } + + static class CloseableContentChannel implements ContentChannel { + + @Override + public void write(ByteBuffer buf, CompletionHandler handler) { + if (handler != null) { + handler.completed(); + } + } + + @Override + public void close(CompletionHandler handler) { + if (handler != null) { + handler.completed(); + } + } + + } + + public static final class MockResponseHandler implements ResponseHandler { + public final ContentChannel channel = new CloseableContentChannel(); + + @Override + public ContentChannel handleResponse( + final com.yahoo.jdisc.Response response) { + return channel; + } + } + + static final class AccessLogTestHandler extends LoggingRequestHandler { + + public AccessLogTestHandler(Executor executor, AccessLog accessLog) { + super(executor, accessLog); + } + + @Override + public HttpResponse handle(HttpRequest request) { + return new NoTimingResponse(); + } + + } + + static final class StartTimePusher implements AccessLogInterface { + + public final ArrayBlockingQueue<Long> starts = new ArrayBlockingQueue<>(1); + + @Override + public void log(final AccessLogEntry accessLogEntry) { + starts.offer(Long.valueOf(accessLogEntry.getTimeStampMillis())); + } + } + + @Before + public void setUp() throws Exception { + accessLogging = new StartTimePusher(); + ComponentRegistry<AccessLogInterface> implementers = new ComponentRegistry<>(); + implementers.register(new ComponentId("nalle"), accessLogging); + implementers.freeze(); + executor = Executors.newCachedThreadPool(); + handler = new AccessLogTestHandler(executor, new AccessLog(implementers)); + } + + @After + public void tearDown() throws Exception { + accessLogging = null; + handler = null; + executor.shutdown(); + executor = null; + } + + @Test + public final void checkStartIsNotZeroWithoutTimingInstance() throws InterruptedException { + Long startTime; + + MockResponseHandler responseHandler = new MockResponseHandler(); + com.yahoo.jdisc.http.HttpRequest request = createRequest(); + BufferedContentChannel requestContent = new BufferedContentChannel(); + requestContent.close(null); + handler.handleRequest(request, requestContent, responseHandler); + startTime = accessLogging.starts.poll(5, TimeUnit.MINUTES); + if (startTime == null) { + // test timed out, ignoring + } else { + assertFalse( + "Start time was 0, that should never happen after the first millisecond of 1970.", + startTime.longValue() == 0L); + } + } + + public static com.yahoo.jdisc.http.HttpRequest createRequest() { + return createRequest("http://localhost/search/?query=geewhiz"); + } + + public static com.yahoo.jdisc.http.HttpRequest createRequest(String uri) { + com.yahoo.jdisc.http.HttpRequest request = null; + try { + request = com.yahoo.jdisc.http.HttpRequest.newClientRequest(new com.yahoo.jdisc.Request(new MockCurrentContainer(), new URI(uri)), new URI(uri), + com.yahoo.jdisc.http.HttpRequest.Method.GET, com.yahoo.jdisc.http.HttpRequest.Version.HTTP_1_1); + request.setRemoteAddress(new InetSocketAddress(0)); + } catch (URISyntaxException e) { + fail("Illegal URI string in test?"); + } + return request; + } + + private static class MockCurrentContainer implements CurrentContainer { + @Override + public Container newReference(java.net.URI uri) { + return new Container() { + + @Override + public RequestHandler resolveHandler(com.yahoo.jdisc.Request request) { + return null; + } + + @Override + public <T> T getInstance(Key<T> tKey) { + return null; + } + + @Override + public <T> T getInstance(Class<T> tClass) { + return null; + } + + @Override + public ResourceReference refer() { + return References.NOOP_REFERENCE; + } + + @Override + public void release() { + // NOP + } + + @Override + public long currentTimeMillis() { + return 37; + } + }; + } + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/LoggingTestCase.java b/container-core/src/test/java/com/yahoo/container/jdisc/LoggingTestCase.java new file mode 100644 index 00000000000..6fc93c2eea4 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/LoggingTestCase.java @@ -0,0 +1,109 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.jdisc.handler.CompletionHandler; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.log.LogLevel; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +/** + * Check error logging from ContentChannelOutputStream is sane. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class LoggingTestCase { + + Logger logger = Logger.getLogger(ContentChannelOutputStream.class.getName()); + boolean initUseParentHandlers = logger.getUseParentHandlers(); + LogCheckHandler logChecker; + Level initLevel; + + private static class FailingContentChannel implements ContentChannel { + + @Override + public void write(ByteBuffer buf, CompletionHandler handler) { + handler.failed(new RuntimeException()); + } + + @Override + public void close(CompletionHandler handler) { + // NOP + + } + } + + private class LogCheckHandler extends Handler { + Map<Level, Integer> errorCounter = new HashMap<>(); + + @Override + public void publish(LogRecord record) { + synchronized (errorCounter) { + Integer count = errorCounter.get(record.getLevel()); + if (count == null) { + count = Integer.valueOf(0); + } + errorCounter.put(record.getLevel(), + Integer.valueOf(count.intValue() + 1)); + } + } + + @Override + public void flush() { + } + + @Override + public void close() throws SecurityException { + } + } + + ContentChannelOutputStream stream; + + @Before + public void setUp() throws Exception { + stream = new ContentChannelOutputStream(new FailingContentChannel()); + logger = Logger.getLogger(ContentChannelOutputStream.class.getName()); + initUseParentHandlers = logger.getUseParentHandlers(); + logger.setUseParentHandlers(false); + logger.setLevel(Level.ALL); + logChecker = new LogCheckHandler(); + logger.addHandler(logChecker); + } + + @After + public void tearDown() throws Exception { + logger.removeHandler(logChecker); + logger.setUseParentHandlers(initUseParentHandlers); + logger.setLevel(initLevel); + } + + private ByteBuffer createData() { + ByteBuffer b = ByteBuffer.allocate(10); + return b; + } + + @Test + public final void testFailed() throws IOException, InterruptedException { + stream.send(createData()); + stream.send(createData()); + stream.send(createData()); + stream.flush(); + assertNull(logChecker.errorCounter.get(LogLevel.INFO)); + assertEquals(1, logChecker.errorCounter.get(LogLevel.DEBUG).intValue()); + assertEquals(2, logChecker.errorCounter.get(LogLevel.SPAM).intValue()); + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/RequestBuilderTestCase.java b/container-core/src/test/java/com/yahoo/container/jdisc/RequestBuilderTestCase.java new file mode 100644 index 00000000000..375e7fe39f5 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/RequestBuilderTestCase.java @@ -0,0 +1,46 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import static org.junit.Assert.*; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.yahoo.jdisc.http.HttpRequest.Method; + +/** + * API check for HttpRequest.Builder. + * + * @author <a href="mailto:steinar@yahoo-inc.com">Steinar Knutsen</a> + */ +public class RequestBuilderTestCase { + HttpRequest.Builder b; + + @Before + public void setUp() throws Exception { + HttpRequest r = HttpRequest.createTestRequest("http://ssh:22/alpha?bravo=charlie", Method.GET); + b = new HttpRequest.Builder(r); + } + + @After + public void tearDown() throws Exception { + b = null; + } + + @Test + public final void testBasic() { + HttpRequest r = b.put("delta", "echo").createDirectRequest(); + assertEquals("charlie", r.getProperty("bravo")); + assertEquals("echo", r.getProperty("delta")); + } + + @Test + public void testRemove() { + HttpRequest orig = b.put("delta", "echo").createDirectRequest(); + + HttpRequest child = new HttpRequest.Builder(orig).removeProperty("delta").createDirectRequest(); + assertFalse(child.propertyMap().containsKey("delta")); + } + +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/ThreadedRequestHandlerTestCase.java b/container-core/src/test/java/com/yahoo/container/jdisc/ThreadedRequestHandlerTestCase.java new file mode 100644 index 00000000000..400cb507620 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/ThreadedRequestHandlerTestCase.java @@ -0,0 +1,317 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc; + +import com.yahoo.jdisc.Request; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.application.ContainerBuilder; +import com.yahoo.jdisc.handler.*; +import com.yahoo.jdisc.test.TestDriver; +import org.junit.Test; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a> + */ +public class ThreadedRequestHandlerTestCase { + + @Test + public void requireThatNullExecutorThrowsException() { + try { + new ThreadedRequestHandler(null) { + + @Override + public void handleRequest(Request request, BufferedContentChannel content, ResponseHandler handler) { + + } + }; + fail(); + } catch (NullPointerException e) { + + } + } + + @Test + public void requireThatHandlerSetsRequestTimeout() throws InterruptedException { + Executor executor = Executors.newSingleThreadExecutor(); + TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(); + ContainerBuilder builder = driver.newContainerBuilder(); + MyRequestHandler requestHandler = MyRequestHandler.newInstance(executor); + builder.serverBindings().bind("http://localhost/", requestHandler); + driver.activateContainer(builder); + + MyResponseHandler responseHandler = new MyResponseHandler(); + driver.dispatchRequest("http://localhost/", responseHandler); + + requestHandler.entryLatch.countDown(); + assertTrue(requestHandler.exitLatch.await(60, TimeUnit.SECONDS)); + assertNull(requestHandler.content.read()); + assertNotNull(requestHandler.request.getTimeout(TimeUnit.MILLISECONDS)); + + assertTrue(responseHandler.latch.await(60, TimeUnit.SECONDS)); + assertNull(responseHandler.content.read()); + assertTrue(driver.close()); + } + + @Test + public void requireThatRequestAndResponseReachHandlers() throws InterruptedException { + Executor executor = Executors.newSingleThreadExecutor(); + TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(); + ContainerBuilder builder = driver.newContainerBuilder(); + MyRequestHandler requestHandler = MyRequestHandler.newInstance(executor); + builder.serverBindings().bind("http://localhost/", requestHandler); + driver.activateContainer(builder); + + MyResponseHandler responseHandler = new MyResponseHandler(); + Request request = new Request(driver, URI.create("http://localhost/")); + ContentChannel requestContent = request.connect(responseHandler); + ByteBuffer buf = ByteBuffer.allocate(69); + requestContent.write(buf, null); + requestContent.close(null); + request.release(); + + requestHandler.entryLatch.countDown(); + assertTrue(requestHandler.exitLatch.await(60, TimeUnit.SECONDS)); + assertSame(request, requestHandler.request); + assertSame(buf, requestHandler.content.read()); + assertNull(requestHandler.content.read()); + + assertTrue(responseHandler.latch.await(60, TimeUnit.SECONDS)); + assertSame(requestHandler.response, responseHandler.response); + assertNull(responseHandler.content.read()); + assertTrue(driver.close()); + } + + @Test + public void requireThatRejectedExecutionIsHandledGracefully() throws Exception { + // Instrumentation. + final Executor executor = new Executor() { + @Override + public void execute(final Runnable command) { + throw new RejectedExecutionException("Deliberately thrown; simulating overloaded executor"); + } + }; + final RequestHandler requestHandler = new ThreadedRequestHandler(executor) { + @Override + protected void handleRequest(Request request, BufferedContentChannel requestContent, ResponseHandler responseHandler) { + throw new AssertionError("Should never get here"); + } + }; + + // Setup. + final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(); + final ContainerBuilder builder = driver.newContainerBuilder(); + builder.serverBindings().bind("http://localhost/", requestHandler); + driver.activateContainer(builder); + final MyResponseHandler responseHandler = new MyResponseHandler(); + + // Execution. + try { + driver.dispatchRequest("http://localhost/", responseHandler); + fail("Above statement should throw exception"); + } catch (OverloadException e) { + // As expected. + } + + // Verification. + assertEquals("Response handler should be invoked synchronously in this case.", 0, responseHandler.latch.getCount()); + assertEquals(Response.Status.SERVICE_UNAVAILABLE, responseHandler.response.getStatus()); + assertNull(responseHandler.content.read()); + assertTrue(driver.close()); + } + + @Test + public void requireThatRequestContentIsClosedIfHandlerIgnoresIt() throws InterruptedException { + Executor executor = Executors.newSingleThreadExecutor(); + TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(); + ContainerBuilder builder = driver.newContainerBuilder(); + MyRequestHandler requestHandler = MyRequestHandler.newIgnoreContent(executor); + builder.serverBindings().bind("http://localhost/", requestHandler); + driver.activateContainer(builder); + + MyResponseHandler responseHandler = new MyResponseHandler(); + ContentChannel content = driver.connectRequest("http://localhost/", responseHandler); + MyCompletion writeCompletion = new MyCompletion(); + content.write(ByteBuffer.allocate(69), writeCompletion); + MyCompletion closeCompletion = new MyCompletion(); + content.close(closeCompletion); + + requestHandler.entryLatch.countDown(); + assertTrue(requestHandler.exitLatch.await(60, TimeUnit.SECONDS)); + assertTrue(writeCompletion.latch.await(60, TimeUnit.SECONDS)); + assertTrue(writeCompletion.completed); + assertTrue(closeCompletion.latch.await(60, TimeUnit.SECONDS)); + assertTrue(writeCompletion.completed); + + assertTrue(responseHandler.latch.await(60, TimeUnit.SECONDS)); + assertSame(requestHandler.response, responseHandler.response); + assertNull(responseHandler.content.read()); + assertTrue(driver.close()); + } + + @Test + public void requireThatResponseIsDispatchedIfHandlerIgnoresIt() throws InterruptedException { + Executor executor = Executors.newSingleThreadExecutor(); + TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(); + ContainerBuilder builder = driver.newContainerBuilder(); + MyRequestHandler requestHandler = MyRequestHandler.newIgnoreResponse(executor); + builder.serverBindings().bind("http://localhost/", requestHandler); + driver.activateContainer(builder); + + MyResponseHandler responseHandler = new MyResponseHandler(); + driver.dispatchRequest("http://localhost/", responseHandler); + requestHandler.entryLatch.countDown(); + assertTrue(requestHandler.exitLatch.await(60, TimeUnit.SECONDS)); + assertNull(requestHandler.content.read()); + + assertTrue(responseHandler.latch.await(60, TimeUnit.SECONDS)); + assertEquals(Response.Status.INTERNAL_SERVER_ERROR, responseHandler.response.getStatus()); + assertNull(responseHandler.content.read()); + assertTrue(driver.close()); + } + + @Test + public void requireThatRequestContentIsClosedAndResponseIsDispatchedIfHandlerIgnoresIt() + throws InterruptedException + { + Executor executor = Executors.newSingleThreadExecutor(); + assertThatRequestContentIsClosedAndResponseIsDispatchedIfHandlerIgnoresIt( + MyRequestHandler.newIgnoreAll(executor)); + assertThatRequestContentIsClosedAndResponseIsDispatchedIfHandlerIgnoresIt( + MyRequestHandler.newThrowException(executor)); + } + + private static void assertThatRequestContentIsClosedAndResponseIsDispatchedIfHandlerIgnoresIt( + MyRequestHandler requestHandler) + throws InterruptedException + { + TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(); + ContainerBuilder builder = driver.newContainerBuilder(); + builder.serverBindings().bind("http://localhost/", requestHandler); + driver.activateContainer(builder); + + MyResponseHandler responseHandler = new MyResponseHandler(); + ContentChannel content = driver.connectRequest("http://localhost/", responseHandler); + MyCompletion writeCompletion = new MyCompletion(); + content.write(ByteBuffer.allocate(69), writeCompletion); + MyCompletion closeCompletion = new MyCompletion(); + content.close(closeCompletion); + + requestHandler.entryLatch.countDown(); + assertTrue(requestHandler.exitLatch.await(60, TimeUnit.SECONDS)); + assertTrue(writeCompletion.latch.await(60, TimeUnit.SECONDS)); + assertTrue(writeCompletion.completed); + assertTrue(closeCompletion.latch.await(60, TimeUnit.SECONDS)); + assertTrue(writeCompletion.completed); + + assertTrue(responseHandler.latch.await(60, TimeUnit.SECONDS)); + assertEquals(Response.Status.INTERNAL_SERVER_ERROR, responseHandler.response.getStatus()); + assertNull(responseHandler.content.read()); + assertTrue(driver.close()); + } + + private static class MyRequestHandler extends ThreadedRequestHandler { + + final CountDownLatch entryLatch = new CountDownLatch(1); + final CountDownLatch exitLatch = new CountDownLatch(1); + final ReadableContentChannel content = new ReadableContentChannel(); + final boolean consumeContent; + final boolean createResponse; + final boolean throwException; + Response response = null; + Request request = null; + + MyRequestHandler(Executor executor, boolean consumeContent, boolean createResponse, boolean throwException) { + super(executor); + this.consumeContent = consumeContent; + this.createResponse = createResponse; + this.throwException = throwException; + } + + @Override + public void handleRequest(Request request, BufferedContentChannel content, ResponseHandler handler) { + try { + if (!entryLatch.await(60, TimeUnit.SECONDS)) { + return; + } + if (throwException) { + throw new RuntimeException(); + } + this.request = request; + if (consumeContent) { + content.connectTo(this.content); + } + if (createResponse) { + response = new Response(Response.Status.OK); + handler.handleResponse(response).close(null); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + exitLatch.countDown(); + } + } + + static MyRequestHandler newInstance(Executor executor) { + return new MyRequestHandler(executor, true, true, false); + } + + static MyRequestHandler newThrowException(Executor executor) { + return new MyRequestHandler(executor, true, true, true); + } + + static MyRequestHandler newIgnoreContent(Executor executor) { + return new MyRequestHandler(executor, false, true, false); + } + + static MyRequestHandler newIgnoreResponse(Executor executor) { + return new MyRequestHandler(executor, true, false, false); + } + + static MyRequestHandler newIgnoreAll(Executor executor) { + return new MyRequestHandler(executor, false, false, false); + } + } + + private static class MyResponseHandler implements ResponseHandler { + + final CountDownLatch latch = new CountDownLatch(1); + final ReadableContentChannel content = new ReadableContentChannel(); + Response response = null; + + @Override + public ContentChannel handleResponse(Response response) { + this.response = response; + latch.countDown(); + + BufferedContentChannel content = new BufferedContentChannel(); + content.connectTo(this.content); + return content; + } + } + + private static class MyCompletion implements CompletionHandler { + + final CountDownLatch latch = new CountDownLatch(1); + boolean completed; + + @Override + public void completed() { + completed = true; + latch.countDown(); + } + + @Override + public void failed(Throwable t) { + latch.countDown(); + } + } +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/state/MetricConsumerProviders.java b/container-core/src/test/java/com/yahoo/container/jdisc/state/MetricConsumerProviders.java new file mode 100644 index 00000000000..ff62f1f4078 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/state/MetricConsumerProviders.java @@ -0,0 +1,21 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.state; + +import com.google.inject.Provider; +import com.yahoo.jdisc.application.MetricConsumer; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +class MetricConsumerProviders { + + public static Provider<MetricConsumer> wrap(final StateMonitor statetMonitor) { + return new Provider<MetricConsumer>() { + + @Override + public MetricConsumer get() { + return statetMonitor.newMetricConsumer(); + } + }; + } +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/state/MetricSnapshotTest.java b/container-core/src/test/java/com/yahoo/container/jdisc/state/MetricSnapshotTest.java new file mode 100644 index 00000000000..9dc9379e585 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/state/MetricSnapshotTest.java @@ -0,0 +1,23 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.state; + +import org.junit.Test; +import static org.junit.Assert.*; + +public class MetricSnapshotTest { + /** + * Aggregate metrics are not cloned into new snapshot. In turn, a metric + * set with only aggregates will be added as an empty set if we do not + * filter these away at clone time. This test ensures that we do just that. + * If/when we start carrying aggregates across snapshots, this test will + * most likely be deprecated. + */ + @Test + public void emptyMetricSetNotAddedToClonedSnapshot() { + final StateMetricContext ctx = StateMetricContext.newInstance(null); + MetricSnapshot snapshot = new MetricSnapshot(); + snapshot.add(ctx, "foo", 1234); + MetricSnapshot newSnapshot = snapshot.createSnapshot(); + assertFalse(newSnapshot.iterator().hasNext()); + } +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/state/StateHandlerTest.java b/container-core/src/test/java/com/yahoo/container/jdisc/state/StateHandlerTest.java new file mode 100644 index 00000000000..49e97fc9d06 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/state/StateHandlerTest.java @@ -0,0 +1,409 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.state; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.inject.AbstractModule; +import com.yahoo.container.core.ApplicationMetadataConfig; +import com.yahoo.container.jdisc.config.HealthMonitorConfig; +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.Timer; +import com.yahoo.jdisc.application.ContainerBuilder; +import com.yahoo.jdisc.application.MetricConsumer; +import com.yahoo.jdisc.handler.BufferedContentChannel; +import com.yahoo.jdisc.handler.ContentChannel; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.test.TestDriver; +import com.yahoo.vespa.defaults.Defaults; +import org.junit.After; +import org.junit.Test; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Map; +import java.util.TreeMap; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class StateHandlerTest { + + private final static long SNAPSHOT_INTERVAL = TimeUnit.SECONDS.toMillis(300); + private final static long META_GENERATION = 69; + private final TestDriver driver; + private final StateMonitor monitor; + private final Metric metric; + private volatile long currentTimeMillis = 0; + + public StateHandlerTest() { + driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() { + + @Override + protected void configure() { + bind(Timer.class).toInstance(new Timer() { + + @Override + public long currentTimeMillis() { + return currentTimeMillis; + } + }); + } + }); + ContainerBuilder builder = driver.newContainerBuilder(); + builder.guiceModules().install(new AbstractModule() { + + @Override + protected void configure() { + bind(HealthMonitorConfig.class) + .toInstance(new HealthMonitorConfig(new HealthMonitorConfig.Builder().snapshot_interval( + TimeUnit.MILLISECONDS.toSeconds(SNAPSHOT_INTERVAL)))); + } + }); + monitor = builder.guiceModules().getInstance(StateMonitor.class); + builder.guiceModules().install(new AbstractModule() { + + @Override + protected void configure() { + bind(StateMonitor.class).toInstance(monitor); + bind(MetricConsumer.class).toProvider(MetricConsumerProviders.wrap(monitor)); + bind(ApplicationMetadataConfig.class).toInstance(new ApplicationMetadataConfig( + new ApplicationMetadataConfig.Builder().generation(META_GENERATION))); + } + }); + builder.serverBindings().bind("http://*/*", builder.getInstance(StateHandler.class)); + driver.activateContainer(builder); + metric = builder.getInstance(Metric.class); + } + + @After + public void closeTestDriver() { + assertTrue(driver.close()); + } + + @Test + public void testReportPriorToFirstSnapshot() throws Exception { + metric.add("foo", 1, null); + metric.set("bar", 4, null); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertEquals(json.toString(), "up", json.get("status").get("code").asText()); + assertFalse(json.toString(), json.get("metrics").has("values")); + } + + @Test + public void testReportIncludesMetricsAfterSnapshot() throws Exception { + metric.add("foo", 1, null); + metric.set("bar", 4, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertEquals(json.toString(), "up", json.get("status").get("code").asText()); + assertEquals(json.toString(), 2, json.get("metrics").get("values").size()); + } + + /** + * Tests that we restart an metric when it changes type from gauge to counter or back. + * This may happen in practice on config reloads. + */ + @Test + public void testMetricTypeChangeIsAllowed() { + String metricName = "myMetric"; + Metric.Context metricContext = null; + + { + // Add a count metric + metric.add(metricName, 1, metricContext); + metric.add(metricName, 2, metricContext); + // Change it to a gauge metric + metric.set(metricName, 9, metricContext); + incrementCurrentTime(SNAPSHOT_INTERVAL); + MetricValue resultingMetric = monitor.snapshot().iterator().next().getValue().get(metricName); + assertEquals(GaugeMetric.class, resultingMetric.getClass()); + assertEquals("Value was reset and produces the last gauge value", + 9.0, ((GaugeMetric) resultingMetric).getLast(), 0.000001); + } + + { + // Add a gauge metric + metric.set(metricName, 9, metricContext); + // Change it to a count metric + metric.add(metricName, 1, metricContext); + metric.add(metricName, 2, metricContext); + incrementCurrentTime(SNAPSHOT_INTERVAL); + MetricValue resultingMetric = monitor.snapshot().iterator().next().getValue().get(metricName); + assertEquals(CountMetric.class, resultingMetric.getClass()); + assertEquals("Value was reset, and changed to add semantics giving 1+2", + 3, ((CountMetric) resultingMetric).getCount()); + } + } + + @Test + public void testAverageAggregationOfValues() throws Exception { + metric.set("bar", 4, null); + metric.set("bar", 5, null); + metric.set("bar", 7, null); + metric.set("bar", 2, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertEquals(json.toString(), "up", json.get("status").get("code").asText()); + assertEquals(json.toString(), 1, json.get("metrics").get("values").size()); + assertEquals(json.toString(), 4.5, + json.get("metrics").get("values").get(0).get("values").get("average").asDouble(), 0.001); + } + + @Test + public void testSumAggregationOfCounts() throws Exception { + metric.add("foo", 1, null); + metric.add("foo", 1, null); + metric.add("foo", 2, null); + metric.add("foo", 1, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertEquals(json.toString(), "up", json.get("status").get("code").asText()); + assertEquals(json.toString(), 1, json.get("metrics").get("values").size()); + assertEquals(json.toString(), 5, + json.get("metrics").get("values").get(0).get("values").get("count").asDouble(), 0.001); + } + + @Test + public void testReadabilityOfJsonReport() throws Exception { + metric.add("foo", 1, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + assertEquals("{\n" + + " \"metrics\": {\n" + + " \"snapshot\": {\n" + + " \"from\": 0,\n" + + " \"to\": 300\n" + + " },\n" + + " \"values\": [{\n" + + " \"name\": \"foo\",\n" + + " \"values\": {\n" + + " \"count\": 1,\n" + + " \"rate\": 0.0033333333333333335\n" + + " }\n" + + " }]\n" + + " },\n" + + " \"status\": {\"code\": \"up\"},\n" + + " \"time\": 300000\n" + + "}", + requestAsString("http://localhost/state/v1/all")); + + Metric.Context ctx = metric.createContext(Collections.singletonMap("component", "test")); + metric.set("bar", 2, ctx); + metric.set("bar", 3, ctx); + metric.set("bar", 4, ctx); + metric.set("bar", 5, ctx); + incrementCurrentTime(SNAPSHOT_INTERVAL); + assertEquals("{\n" + + " \"metrics\": {\n" + + " \"snapshot\": {\n" + + " \"from\": 300,\n" + + " \"to\": 600\n" + + " },\n" + + " \"values\": [\n" + + " {\n" + + " \"name\": \"foo\",\n" + + " \"values\": {\n" + + " \"count\": 0,\n" + + " \"rate\": 0\n" + + " }\n" + + " },\n" + + " {\n" + + " \"dimensions\": {\"component\": \"test\"},\n" + + " \"name\": \"bar\",\n" + + " \"values\": {\n" + + " \"average\": 3.5,\n" + + " \"count\": 4,\n" + + " \"last\": 5,\n" + + " \"max\": 5,\n" + + " \"min\": 2,\n" + + " \"rate\": 0.013333333333333334\n" + + " }\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"status\": {\"code\": \"up\"},\n" + + " \"time\": 600000\n" + + "}", + requestAsString("http://localhost/state/v1/all")); + } + + @Test + public void testNotAggregatingCountsBeyondSnapshots() throws Exception { + metric.add("foo", 1, null); + metric.add("foo", 1, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + metric.add("foo", 2, null); + metric.add("foo", 1, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertEquals(json.toString(), "up", json.get("status").get("code").asText()); + assertEquals(json.toString(), 1, json.get("metrics").get("values").size()); + assertEquals(json.toString(), 3, + json.get("metrics").get("values").get(0).get("values").get("count").asDouble(), 0.001); + } + + @Test + public void testSnapshottingTimes() throws Exception { + metric.add("foo", 1, null); + metric.set("bar", 3, null); + // At this time we should not have done any snapshotting + incrementCurrentTime(SNAPSHOT_INTERVAL - 1); + { + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertFalse(json.toString(), json.get("metrics").has("snapshot")); + } + // At this time first snapshot should have been generated + incrementCurrentTime(1); + { + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertTrue(json.toString(), json.get("metrics").has("snapshot")); + assertEquals(0.0, json.get("metrics").get("snapshot").get("from").asDouble(), 0.00001); + assertEquals(300.0, json.get("metrics").get("snapshot").get("to").asDouble(), 0.00001); + } + // No new snapshot at this time + incrementCurrentTime(SNAPSHOT_INTERVAL - 1); + { + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertTrue(json.toString(), json.get("metrics").has("snapshot")); + assertEquals(0.0, json.get("metrics").get("snapshot").get("from").asDouble(), 0.00001); + assertEquals(300.0, json.get("metrics").get("snapshot").get("to").asDouble(), 0.00001); + } + // A new snapshot + incrementCurrentTime(1); + { + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertTrue(json.toString(), json.get("metrics").has("snapshot")); + assertEquals(300.0, json.get("metrics").get("snapshot").get("from").asDouble(), 0.00001); + assertEquals(600.0, json.get("metrics").get("snapshot").get("to").asDouble(), 0.00001); + } + } + + @Test + public void testFreshStartOfValuesBeyondSnapshot() throws Exception { + metric.set("bar", 4, null); + metric.set("bar", 5, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + metric.set("bar", 4, null); + metric.set("bar", 2, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + assertEquals(json.toString(), "up", json.get("status").get("code").asText()); + assertEquals(json.toString(), 1, json.get("metrics").get("values").size()); + assertEquals(json.toString(), 3, + json.get("metrics").get("values").get(0).get("values").get("average").asDouble(), 0.001); + } + + @Test + public void snapshotsPreserveLastGaugeValue() throws Exception { + metric.set("bar", 4, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + JsonNode metricValues = getFirstMetricValueNode(json); + assertEquals(json.toString(), 4, metricValues.get("last").asDouble(), 0.001); + // Use 'last' as avg/min/max when none has been set explicitly during snapshot period + assertEquals(json.toString(), 4, metricValues.get("average").asDouble(), 0.001); + assertEquals(json.toString(), 4, metricValues.get("min").asDouble(), 0.001); + assertEquals(json.toString(), 4, metricValues.get("max").asDouble(), 0.001); + // Count is tracked per period. + assertEquals(json.toString(), 0, metricValues.get("count").asInt()); + } + + private JsonNode getFirstMetricValueNode(JsonNode root) { + assertEquals(root.toString(), 1, root.get("metrics").get("values").size()); + JsonNode metricValues = root.get("metrics").get("values").get(0).get("values"); + assertTrue(root.toString(), metricValues.has("last")); + return metricValues; + } + + @Test + public void gaugeSnapshotsTracksCountMinMaxAvgPerPeriod() throws Exception { + metric.set("bar", 10000, null); // Ensure any cross-snapshot noise is visible + incrementCurrentTime(SNAPSHOT_INTERVAL); + metric.set("bar", 20, null); + metric.set("bar", 40, null); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/all"); + JsonNode metricValues = getFirstMetricValueNode(json); + assertEquals(json.toString(), 40, metricValues.get("last").asDouble(), 0.001); + // Last snapshot had explicit values set + assertEquals(json.toString(), 30, metricValues.get("average").asDouble(), 0.001); + assertEquals(json.toString(), 20, metricValues.get("min").asDouble(), 0.001); + assertEquals(json.toString(), 40, metricValues.get("max").asDouble(), 0.001); + assertEquals(json.toString(), 2, metricValues.get("count").asInt()); + } + + @Test + public void testHealthAggregation() throws Exception { + Map<String, String> dimensions1 = new TreeMap<>(); + dimensions1.put("port", String.valueOf(Defaults.getDefaults().vespaWebServicePort())); + Metric.Context context1 = metric.createContext(dimensions1); + Map<String, String> dimensions2 = new TreeMap<>(); + dimensions2.put("port", "80"); + Metric.Context context2 = metric.createContext(dimensions2); + + metric.add("serverNumSuccessfulResponses", 4, context1); + metric.add("serverNumSuccessfulResponses", 2, context2); + metric.set("serverTotalSuccessfulResponseLatency", 20, context1); + metric.set("serverTotalSuccessfulResponseLatency", 40, context2); + metric.add("random", 3, context1); + incrementCurrentTime(SNAPSHOT_INTERVAL); + JsonNode json = requestAsJson("http://localhost/state/v1/health"); + assertEquals(json.toString(), "up", json.get("status").get("code").asText()); + assertEquals(json.toString(), 2, json.get("metrics").get("values").size()); + assertEquals(json.toString(), "requestsPerSecond", + json.get("metrics").get("values").get(0).get("name").asText()); + assertEquals(json.toString(), 6, + json.get("metrics").get("values").get(0).get("values").get("count").asDouble(), 0.001); + assertEquals(json.toString(), "latencySeconds", + json.get("metrics").get("values").get(1).get("name").asText()); + assertEquals(json.toString(), 0.03, + json.get("metrics").get("values").get(1).get("values").get("average").asDouble(), 0.001); + } + + @Test + public void testStateConfig() throws Exception { + JsonNode root = requestAsJson("http://localhost/state/v1/config"); + + JsonNode config = root.get("config"); + JsonNode container = config.get("container"); + assertEquals(META_GENERATION, container.get("generation").asLong()); + } + + private void incrementCurrentTime(long val) { + currentTimeMillis += val; + monitor.checkTime(); + } + + private String requestAsString(String requestUri) throws Exception { + final BufferedContentChannel content = new BufferedContentChannel(); + Response response = driver.dispatchRequest(requestUri, new ResponseHandler() { + + @Override + public ContentChannel handleResponse(Response response) { + return content; + } + }).get(60, TimeUnit.SECONDS); + assertNotNull(response); + assertEquals(Response.Status.OK, response.getStatus()); + StringBuilder str = new StringBuilder(); + Reader in = new InputStreamReader(content.toStream(), StandardCharsets.UTF_8); + for (int c; (c = in.read()) != -1; ) { + str.append((char)c); + } + return str.toString(); + } + + private JsonNode requestAsJson(String requestUri) throws Exception { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readTree(mapper.getFactory().createParser(requestAsString(requestUri))); + } +} diff --git a/container-core/src/test/java/com/yahoo/container/jdisc/state/StateMonitorBenchmarkTest.java b/container-core/src/test/java/com/yahoo/container/jdisc/state/StateMonitorBenchmarkTest.java new file mode 100644 index 00000000000..103d22afe6d --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/jdisc/state/StateMonitorBenchmarkTest.java @@ -0,0 +1,80 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.jdisc.state; + +import com.google.inject.Provider; +import com.yahoo.container.jdisc.config.HealthMonitorConfig; +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.application.ContainerThread; +import com.yahoo.jdisc.application.MetricConsumer; +import com.yahoo.jdisc.application.MetricProvider; +import com.yahoo.jdisc.core.SystemTimer; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertTrue; + +/** + * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a> + */ +public class StateMonitorBenchmarkTest { + + private final static int NUM_THREADS = 32; + private final static int NUM_UPDATES = 1000;//0000; + + @Test + public void requireThatHealthMonitorDoesNotBlockMetricThreads() throws Exception { + StateMonitor monitor = new StateMonitor(new HealthMonitorConfig(new HealthMonitorConfig.Builder()), + new SystemTimer()); + Provider<MetricConsumer> provider = MetricConsumerProviders.wrap(monitor); + performUpdates(provider, 8); + for (int i = 1; i <= NUM_THREADS; i *= 2) { + long millis = performUpdates(provider, i); + System.err.format("%2d threads, %5d millis => %9d ups\n", + i, millis, (int)((i * NUM_UPDATES) / (millis / 1000.0))); + } + monitor.deconstruct(); + } + + private long performUpdates(Provider<MetricConsumer> metricProvider, int numThreads) throws Exception { + ThreadFactory threadFactory = new ContainerThread.Factory(metricProvider); + ExecutorService executor = Executors.newFixedThreadPool(numThreads, threadFactory); + List<Callable<Boolean>> tasks = new ArrayList<>(numThreads); + for (int i = 0; i < numThreads; ++i) { + tasks.add(new UpdateTask(new MetricProvider(metricProvider).get())); + } + long before = System.nanoTime(); + List<Future<Boolean>> results = executor.invokeAll(tasks); + long after = System.nanoTime(); + for (Future<Boolean> result : results) { + assertTrue(result.get()); + } + return TimeUnit.NANOSECONDS.toMillis(after - before); + } + + public static class UpdateTask implements Callable<Boolean> { + + final Metric metric; + + UpdateTask(Metric metric) { + this.metric = metric; + } + + @Override + public Boolean call() throws Exception { + Metric.Context ctx = metric.createContext(Collections.<String, Object>emptyMap()); + for (int i = 0; i < NUM_UPDATES; ++i) { + metric.add("foo", 69L, ctx); + } + return true; + } + } +} diff --git a/container-core/src/test/java/com/yahoo/container/messagebus/cfg-disabled/.gitignore b/container-core/src/test/java/com/yahoo/container/messagebus/cfg-disabled/.gitignore new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/messagebus/cfg-disabled/.gitignore diff --git a/container-core/src/test/java/com/yahoo/container/xml/bind/JAXBContextFactoryTest.java b/container-core/src/test/java/com/yahoo/container/xml/bind/JAXBContextFactoryTest.java new file mode 100644 index 00000000000..c027574a208 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/xml/bind/JAXBContextFactoryTest.java @@ -0,0 +1,45 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.xml.bind; + +import com.yahoo.container.xml.providers.JAXBContextFactoryProvider; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author einarmr + * @author gjoranv + * @since 5.3 + */ +public class JAXBContextFactoryTest { + @Test + public void testInstantiationAndDestruction() { + + JAXBContextFactoryProvider provider = new JAXBContextFactoryProvider(); + JAXBContextFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(JAXBContextFactoryProvider.FACTORY_CLASS)); + + try { + JAXBContextFactory.getContextPath((Class) null); + fail("Should have failed with null classes."); + } catch (Exception e) { } + + try { + JAXBContextFactory.getContextPath(); + fail("Should have failed with empty list."); + } catch (Exception e) { } + + assertThat(JAXBContextFactory.getContextPath(this.getClass()), + equalTo(this.getClass().getPackage().getName())); + + assertThat(JAXBContextFactory.getContextPath(this.getClass(), + String.class), + equalTo(this.getClass().getPackage().getName() + ":" + + String.class.getPackage().getName())); + + provider.deconstruct(); + + } +} diff --git a/container-core/src/test/java/com/yahoo/container/xml/providers/XMLProviderTest.java b/container-core/src/test/java/com/yahoo/container/xml/providers/XMLProviderTest.java new file mode 100644 index 00000000000..2c38d71a6f2 --- /dev/null +++ b/container-core/src/test/java/com/yahoo/container/xml/providers/XMLProviderTest.java @@ -0,0 +1,96 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.container.xml.providers; + +import com.yahoo.container.Server; +import com.yahoo.container.xml.bind.JAXBContextFactory; +import com.yahoo.container.xml.providers.DatatypeFactoryProvider; +import com.yahoo.container.xml.providers.DocumentBuilderFactoryProvider; +import com.yahoo.container.xml.providers.JAXBContextFactoryProvider; +import com.yahoo.container.xml.providers.SAXParserFactoryProvider; +import com.yahoo.container.xml.providers.SchemaFactoryProvider; +import com.yahoo.container.xml.providers.TransformerFactoryProvider; +import com.yahoo.container.xml.providers.XMLEventFactoryProvider; +import com.yahoo.container.xml.providers.XMLInputFactoryProvider; +import com.yahoo.container.xml.providers.XMLOutputFactoryProvider; +import com.yahoo.container.xml.providers.XPathFactoryProvider; +import org.junit.Test; + +import javax.xml.datatype.DatatypeFactory; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.stream.XMLEventFactory; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.transform.TransformerFactory; +import javax.xml.validation.SchemaFactory; +import javax.xml.xpath.XPathFactory; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a> + * @since 5.1.29 + */ +public class XMLProviderTest { + + @Test + public void testInstantiationAndDestruction() { + { + DatatypeFactoryProvider provider = new DatatypeFactoryProvider(); + DatatypeFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(DatatypeFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + DocumentBuilderFactoryProvider provider = new DocumentBuilderFactoryProvider(); + DocumentBuilderFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(DocumentBuilderFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + SAXParserFactoryProvider provider = new SAXParserFactoryProvider(); + SAXParserFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(SAXParserFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + SchemaFactoryProvider provider = new SchemaFactoryProvider(); + SchemaFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(SchemaFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + TransformerFactoryProvider provider = new TransformerFactoryProvider(); + TransformerFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(TransformerFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + XMLEventFactoryProvider provider = new XMLEventFactoryProvider(); + XMLEventFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(XMLEventFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + XMLInputFactoryProvider provider = new XMLInputFactoryProvider(); + XMLInputFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(XMLInputFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + XMLOutputFactoryProvider provider = new XMLOutputFactoryProvider(); + XMLOutputFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(XMLOutputFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + { + XPathFactoryProvider provider = new XPathFactoryProvider(); + XPathFactory factory = provider.get(); + assertThat(factory.getClass().getName(), equalTo(XPathFactoryProvider.FACTORY_CLASS)); + provider.deconstruct(); + } + } + +} |