summaryrefslogtreecommitdiffstats
path: root/container-core/src/test/java/com/yahoo/container
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /container-core/src/test/java/com/yahoo/container
Publish
Diffstat (limited to 'container-core/src/test/java/com/yahoo/container')
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/AccessLogRequestHandlerTest.java47
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/ThreadPoolProviderTestCase.java134
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/VipStatusHandlerTestCase.java220
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java42
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/test/MockServiceTest.java88
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/test/test.txt6
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/ExtendedResponseTestCase.java89
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/HttpRequestTestCase.java104
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/HttpResponseTestCase.java82
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/LoggingRequestHandlerTestCase.java224
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/LoggingTestCase.java109
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/RequestBuilderTestCase.java46
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/ThreadedRequestHandlerTestCase.java317
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/state/MetricConsumerProviders.java21
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/state/MetricSnapshotTest.java23
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/state/StateHandlerTest.java409
-rw-r--r--container-core/src/test/java/com/yahoo/container/jdisc/state/StateMonitorBenchmarkTest.java80
-rw-r--r--container-core/src/test/java/com/yahoo/container/messagebus/cfg-disabled/.gitignore0
-rw-r--r--container-core/src/test/java/com/yahoo/container/xml/bind/JAXBContextFactoryTest.java45
-rw-r--r--container-core/src/test/java/com/yahoo/container/xml/providers/XMLProviderTest.java96
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();
+ }
+ }
+
+}