aboutsummaryrefslogtreecommitdiffstats
path: root/jdisc_core/src/test/java
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 /jdisc_core/src/test/java
Publish
Diffstat (limited to 'jdisc_core/src/test/java')
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/AbstractResourceTestCase.java133
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/ContainerTestCase.java49
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/HeaderFieldsTestCase.java372
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/ProxyRequestHandlerTestCase.java583
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/ReferencedResourceTestCase.java34
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/ReferencesTestCase.java27
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/RequestTestCase.java381
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/ResponseTestCase.java86
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/AbstractApplicationTestCase.java98
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/ApplicationNotReadyTestCase.java53
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingMatchTestCase.java52
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingRepositoryTestCase.java181
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java506
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/BundleInstallationExceptionTestCase.java53
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerBuilderTestCase.java116
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerThreadTestCase.java61
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/GlobPatternTestCase.java157
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/GuiceRepositoryTestCase.java197
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/MetricImplTestCase.java150
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiHeaderTestCase.java20
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiRepositoryTestCase.java18
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/ResourcePoolTestCase.java168
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/ServerRepositoryTestCase.java88
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/application/UriPatternTestCase.java342
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/BindingMatchingTestCase.java126
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/LatencyTestCase.java264
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/ThroughputTestCase.java180
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/UriMatchingTestCase.java81
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/client/AbstractClientApplicationTestCase.java138
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/client/ClientDriverTestCase.java78
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerFinalizerTest.java75
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerTestCase.java160
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationConfigModuleTestCase.java114
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationEnvironmentModuleTestCase.java57
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationLoaderTestCase.java259
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationRestartTestCase.java153
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationShutdownTestCase.java122
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/BootstrapDaemonTestCase.java154
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/BundleLocationResolverTestCase.java87
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogFormatterTestCase.java270
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogListenerTestCase.java115
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogManagerTestCase.java90
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerResourceTestCase.java162
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerShutdownTestCase.java848
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java211
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerTerminationTestCase.java76
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/DefaultBindingSelectorTestCase.java38
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesTestCase.java30
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixFrameworkTestCase.java41
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixParamsTestCase.java67
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogHandlerTestCase.java192
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogManagerTestCase.java184
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogServiceTestCase.java105
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ScheduledQueueTestCase.java149
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/SystemTimerTestCase.java31
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/TimeoutManagerImplTestCase.java579
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractContentOutputStreamTestCase.java127
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractRequestHandlerTestCase.java187
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/BindingNotFoundTestCase.java49
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/BlockingContentWriterTestCase.java210
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/BufferedContentChannelTestCase.java257
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableRequestDispatchTestCase.java51
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableResponseDispatchTestCase.java30
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/ContentInputStreamTestCase.java32
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentOutputStreamTestCase.java70
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentWriterTestCase.java241
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureCompletionTestCase.java106
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureConjunctionTestCase.java255
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureResponseTestCase.java81
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/NullContentTestCase.java48
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/ReadableContentChannelTestCase.java320
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDeniedTestCase.java70
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDispatchTestCase.java253
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/ResponseDispatchTestCase.java206
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/RunnableLatch.java22
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/ThreadedRequestHandlerTestCase.java228
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/handler/UnsafeContentInputStreamTestCase.java139
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractClientProviderTestCase.java34
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractServerProviderTestCase.java51
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/service/BindingSetNotFoundTestCase.java58
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/service/ConnectToHandlerTestCase.java99
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/service/ContainerNotReadyTestCase.java28
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/service/CurrentContainerTestCase.java27
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/service/NoBindingSetSelectedTestCase.java55
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingClientTestCase.java54
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingCompletionHandlerTestCase.java43
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingContentChannelTestCase.java80
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingOsgiFrameworkTestCase.java72
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestHandlerTestCase.java42
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestTestCase.java35
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingResponseHandlerTestCase.java25
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingServerTestCase.java35
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/ServerProviderConformanceTestTest.java657
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/test/TestDriverTestCase.java163
94 files changed, 13701 insertions, 0 deletions
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/AbstractResourceTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/AbstractResourceTestCase.java
new file mode 100644
index 00000000000..d43771c9cfd
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/AbstractResourceTestCase.java
@@ -0,0 +1,133 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AbstractResourceTestCase {
+
+ @Test
+ public void requireThatDestroyIsCalledWhenReleased() {
+ MyResource res = new MyResource();
+ assertFalse(res.destroyed);
+ res.release();
+ assertTrue(res.destroyed);
+ }
+
+ @Test
+ public void requireThatDestroyIsCalledWhenRetainCountReachesZero() {
+ MyResource res = new MyResource();
+ assertEquals(1, res.retainCount());
+ assertFalse(res.destroyed);
+ final ResourceReference reference = res.refer();
+ assertEquals(2, res.retainCount());
+ res.release();
+ assertEquals(1, res.retainCount());
+ assertFalse(res.destroyed);
+ reference.close();
+ assertEquals(0, res.retainCount());
+ assertTrue(res.destroyed);
+ }
+
+ @Test
+ public void requireThatDestroyIsCalledWhenRetainCountReachesZeroOppositeOrder() {
+ MyResource res = new MyResource();
+ assertEquals(1, res.retainCount());
+ assertFalse(res.destroyed);
+ final ResourceReference reference = res.refer();
+ assertEquals(2, res.retainCount());
+ reference.close();
+ assertEquals(1, res.retainCount());
+ assertFalse(res.destroyed);
+ res.release();
+ assertEquals(0, res.retainCount());
+ assertTrue(res.destroyed);
+ }
+
+ @Test
+ public void requireThatReleaseCanOnlyBeCalledOnceEvenWhenReferenceCountIsPositive() {
+ MyResource res = new MyResource();
+ final ResourceReference secondReference = res.refer();
+ res.release();
+ try {
+ res.release();
+ fail();
+ } catch (IllegalStateException e) {
+ // As expected.
+ }
+ secondReference.close();
+ }
+
+ @Test
+ public void requireThatSecondaryReferenceCanOnlyBeClosedOnceEvenWhenReferenceCountIsPositive() {
+ MyResource res = new MyResource();
+ final ResourceReference secondReference = res.refer();
+ secondReference.close();
+ try {
+ secondReference.close();
+ fail();
+ } catch (IllegalStateException e) {
+ // As expected.
+ }
+ res.release();
+ }
+
+ @Test
+ public void requireThatReleaseAfterDestroyThrows() {
+ MyResource res = new MyResource();
+ res.release();
+ assertTrue(res.destroyed);
+ try {
+ res.release();
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ assertEquals(0, res.retainCount());
+ try {
+ res.release();
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ assertEquals(0, res.retainCount());
+ }
+
+ @Test
+ public void requireThatReferAfterDestroyThrows() {
+ MyResource res = new MyResource();
+ res.release();
+ assertTrue(res.destroyed);
+ try {
+ res.refer();
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ assertEquals(0, res.retainCount());
+ try {
+ res.refer();
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ assertEquals(0, res.retainCount());
+ }
+
+ private static class MyResource extends AbstractResource {
+
+ boolean destroyed = false;
+
+ @Override
+ protected void destroy() {
+ destroyed = true;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/ContainerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/ContainerTestCase.java
new file mode 100644
index 00000000000..9eef00b2cbe
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/ContainerTestCase.java
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import com.google.inject.AbstractModule;
+import com.yahoo.jdisc.service.BindingSetNotFoundException;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerTestCase {
+
+ @Test
+ public void requireThatNewRequestsReferenceSameSnapshot() throws Exception {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Request foo = new Request(driver, URI.create("http://foo"));
+ Request bar = new Request(foo, URI.create("http://bar"));
+ assertNotNull(foo.container());
+ assertSame(foo.container(), bar.container());
+ foo.release();
+ bar.release();
+ driver.close();
+ }
+
+ @Test
+ public void requireThatInjectionWorks() throws BindingSetNotFoundException {
+ final Object foo = new Object();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(Object.class).toInstance(foo);
+ }
+ });
+ driver.activateContainer(driver.newContainerBuilder());
+ Request request = new Request(driver, URI.create("http://host/path"));
+ assertSame(foo, request.container().getInstance(Object.class));
+ request.release();
+ driver.close();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/HeaderFieldsTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/HeaderFieldsTestCase.java
new file mode 100644
index 00000000000..2d0ada8113c
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/HeaderFieldsTestCase.java
@@ -0,0 +1,372 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class HeaderFieldsTestCase {
+
+ @Test
+ public void requireThatSizeWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertEquals(0, headers.size());
+ headers.add("foo", "bar");
+ assertEquals(1, headers.size());
+ headers.add("foo", "baz");
+ assertEquals(1, headers.size());
+ headers.add("bar", "baz");
+ assertEquals(2, headers.size());
+ headers.remove("foo");
+ assertEquals(1, headers.size());
+ headers.remove("bar");
+ assertEquals(0, headers.size());
+ }
+
+ @Test
+ public void requireThatIsEmptyWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertTrue(headers.isEmpty());
+ headers.add("foo", "bar");
+ assertFalse(headers.isEmpty());
+ headers.remove("foo");
+ assertTrue(headers.isEmpty());
+ }
+
+ @Test
+ public void requireThatContainsKeyWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertFalse(headers.containsKey("foo"));
+ assertFalse(headers.containsKey("FOO"));
+ headers.add("foo", "bar");
+ assertTrue(headers.containsKey("foo"));
+ assertTrue(headers.containsKey("FOO"));
+ }
+
+ @Test
+ public void requireThatContainsValueWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertFalse(headers.containsValue(Arrays.asList("bar")));
+ headers.add("foo", "bar");
+ assertTrue(headers.containsValue(Arrays.asList("bar")));
+ }
+
+ @Test
+ public void requireThatContainsWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertFalse(headers.contains("foo", "bar"));
+ assertFalse(headers.contains("FOO", "bar"));
+ assertFalse(headers.contains("foo", "BAR"));
+ assertFalse(headers.contains("FOO", "BAR"));
+ headers.add("foo", "bar");
+ assertTrue(headers.contains("foo", "bar"));
+ assertTrue(headers.contains("FOO", "bar"));
+ assertFalse(headers.contains("foo", "BAR"));
+ assertFalse(headers.contains("FOO", "BAR"));
+ }
+
+ @Test
+ public void requireThatContainsIgnoreCaseWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertFalse(headers.containsIgnoreCase("foo", "bar"));
+ assertFalse(headers.containsIgnoreCase("FOO", "bar"));
+ assertFalse(headers.containsIgnoreCase("foo", "BAR"));
+ assertFalse(headers.containsIgnoreCase("FOO", "BAR"));
+ headers.add("foo", "bar");
+ assertTrue(headers.containsIgnoreCase("foo", "bar"));
+ assertTrue(headers.containsIgnoreCase("FOO", "bar"));
+ assertTrue(headers.containsIgnoreCase("foo", "BAR"));
+ assertTrue(headers.containsIgnoreCase("FOO", "BAR"));
+ }
+
+ @Test
+ public void requireThatAddStringWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertNull(headers.get("foo"));
+ headers.add("foo", "bar");
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ headers.add("foo", "baz");
+ assertEquals(Arrays.asList("bar", "baz"), headers.get("foo"));
+ }
+
+ @Test
+ public void requireThatAddListWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertNull(headers.get("foo"));
+ headers.add("foo", Arrays.asList("bar"));
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ headers.add("foo", Arrays.asList("baz", "cox"));
+ assertEquals(Arrays.asList("bar", "baz", "cox"), headers.get("foo"));
+ }
+
+ @Test
+ public void requireThatAddAllWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ headers.add("foo", "bar");
+ headers.add("bar", "baz");
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ assertEquals(Arrays.asList("baz"), headers.get("bar"));
+
+ Map<String, List<String>> map = new HashMap<>();
+ map.put("foo", Arrays.asList("baz", "cox"));
+ map.put("bar", Arrays.asList("cox"));
+ headers.addAll(map);
+
+ assertEquals(Arrays.asList("bar", "baz", "cox"), headers.get("foo"));
+ assertEquals(Arrays.asList("baz", "cox"), headers.get("bar"));
+ }
+
+ @Test
+ public void requireThatPutStringWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertNull(headers.get("foo"));
+ headers.put("foo", "bar");
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ headers.put("foo", "baz");
+ assertEquals(Arrays.asList("baz"), headers.get("foo"));
+ }
+
+ @Test
+ public void requireThatPutListWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertNull(headers.get("foo"));
+ headers.put("foo", Arrays.asList("bar"));
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ headers.put("foo", Arrays.asList("baz", "cox"));
+ assertEquals(Arrays.asList("baz", "cox"), headers.get("foo"));
+ }
+
+ @Test
+ public void requireThatPutAllWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ headers.add("foo", "bar");
+ headers.add("bar", "baz");
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ assertEquals(Arrays.asList("baz"), headers.get("bar"));
+
+ Map<String, List<String>> map = new HashMap<>();
+ map.put("foo", Arrays.asList("baz", "cox"));
+ map.put("bar", Arrays.asList("cox"));
+ headers.putAll(map);
+
+ assertEquals(Arrays.asList("baz", "cox"), headers.get("foo"));
+ assertEquals(Arrays.asList("cox"), headers.get("bar"));
+ }
+
+ @Test
+ public void requireThatRemoveWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ headers.put("foo", Arrays.asList("bar", "baz"));
+ assertEquals(Arrays.asList("bar", "baz"), headers.get("foo"));
+ assertEquals(Arrays.asList("bar", "baz"), headers.remove("foo"));
+ assertNull(headers.get("foo"));
+ assertNull(headers.remove("foo"));
+ }
+
+ @Test
+ public void requireThatRemoveStringWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ headers.put("foo", Arrays.asList("bar", "baz"));
+ assertEquals(Arrays.asList("bar", "baz"), headers.get("foo"));
+ assertTrue(headers.remove("foo", "bar"));
+ assertFalse(headers.remove("foo", "cox"));
+ assertEquals(Arrays.asList("baz"), headers.get("foo"));
+ assertTrue(headers.remove("foo", "baz"));
+ assertFalse(headers.remove("foo", "cox"));
+ assertNull(headers.get("foo"));
+ }
+
+ @Test
+ public void requireThatClearWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ headers.add("foo", "bar");
+ headers.add("bar", "baz");
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ assertEquals(Arrays.asList("baz"), headers.get("bar"));
+ headers.clear();
+ assertNull(headers.get("foo"));
+ assertNull(headers.get("bar"));
+ }
+
+ @Test
+ public void requireThatGetWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertNull(headers.get("foo"));
+ headers.add("foo", "bar");
+ assertEquals(Arrays.asList("bar"), headers.get("foo"));
+ }
+
+ @Test
+ public void requireThatGetFirstWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertNull(headers.getFirst("foo"));
+ headers.add("foo", Arrays.asList("bar", "baz"));
+ assertEquals("bar", headers.getFirst("foo"));
+ }
+
+ @Test
+ public void requireThatIsTrueWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertFalse(headers.isTrue("foo"));
+ headers.put("foo", Arrays.asList("true"));
+ assertTrue(headers.isTrue("foo"));
+ headers.put("foo", Arrays.asList("true", "true"));
+ assertTrue(headers.isTrue("foo"));
+ headers.put("foo", Arrays.asList("true", "false"));
+ assertFalse(headers.isTrue("foo"));
+ headers.put("foo", Arrays.asList("false", "true"));
+ assertFalse(headers.isTrue("foo"));
+ headers.put("foo", Arrays.asList("false", "false"));
+ assertFalse(headers.isTrue("foo"));
+ headers.put("foo", Arrays.asList("false"));
+ assertFalse(headers.isTrue("foo"));
+ }
+
+ @Test
+ public void requireThatKeySetWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertTrue(headers.keySet().isEmpty());
+ headers.add("foo", "bar");
+ assertEquals(new HashSet<>(Arrays.asList("foo")), headers.keySet());
+ headers.add("bar", "baz");
+ assertEquals(new HashSet<>(Arrays.asList("foo", "bar")), headers.keySet());
+ }
+
+ @Test
+ public void requireThatValuesWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertTrue(headers.values().isEmpty());
+ headers.add("foo", "bar");
+ Collection<List<String>> values = headers.values();
+ assertEquals(1, values.size());
+ assertTrue(values.contains(Arrays.asList("bar")));
+
+ headers.add("bar", "baz");
+ values = headers.values();
+ assertEquals(2, values.size());
+ assertTrue(values.contains(Arrays.asList("bar")));
+ assertTrue(values.contains(Arrays.asList("baz")));
+ }
+
+ @Test
+ public void requireThatEntrySetWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertTrue(headers.entrySet().isEmpty());
+ headers.put("foo", Arrays.asList("bar", "baz"));
+
+ Set<Map.Entry<String, List<String>>> entries = headers.entrySet();
+ assertEquals(1, entries.size());
+ Map.Entry<String, List<String>> entry = entries.iterator().next();
+ assertNotNull(entry);
+ assertEquals("foo", entry.getKey());
+ assertEquals(Arrays.asList("bar", "baz"), entry.getValue());
+ }
+
+ @Test
+ public void requireThatEntriesWorksAsExpected() {
+ HeaderFields headers = new HeaderFields();
+ assertTrue(headers.entries().isEmpty());
+ headers.put("foo", Arrays.asList("bar", "baz"));
+
+ List<Map.Entry<String, String>> entries = headers.entries();
+ assertEquals(2, entries.size());
+
+ Map.Entry<String, String> entry = entries.get(0);
+ assertNotNull(entry);
+ assertEquals("foo", entry.getKey());
+ assertEquals("bar", entry.getValue());
+
+ assertNotNull(entry = entries.get(1));
+ assertEquals("foo", entry.getKey());
+ assertEquals("baz", entry.getValue());
+ }
+
+ @Test
+ public void requireThatEntryIsUnmodifiable() {
+ HeaderFields headers = new HeaderFields();
+ headers.put("foo", "bar");
+ Map.Entry<String, String> entry = headers.entries().get(0);
+ try {
+ entry.setValue("baz");
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatEntriesAreUnmodifiable() {
+ HeaderFields headers = new HeaderFields();
+ headers.put("foo", "bar");
+ List<Map.Entry<String, String>> entries = headers.entries();
+ try {
+ entries.add(new MyEntry());
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ try {
+ entries.remove(new MyEntry());
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatEqualsWorksAsExpected() {
+ HeaderFields lhs = new HeaderFields();
+ HeaderFields rhs = new HeaderFields();
+ assertTrue(lhs.equals(rhs));
+ lhs.add("foo", "bar");
+ assertFalse(lhs.equals(rhs));
+ rhs.add("foo", "bar");
+ assertTrue(lhs.equals(rhs));
+ }
+
+ @Test
+ public void requireThatHashCodeWorksAsExpected() {
+ HeaderFields lhs = new HeaderFields();
+ HeaderFields rhs = new HeaderFields();
+ assertTrue(lhs.hashCode() == rhs.hashCode());
+ lhs.add("foo", "bar");
+ assertTrue(lhs.hashCode() != rhs.hashCode());
+ rhs.add("foo", "bar");
+ assertTrue(lhs.hashCode() == rhs.hashCode());
+ }
+
+ private static class MyEntry implements Map.Entry<String, String> {
+
+ @Override
+ public String getKey() {
+ return "key";
+ }
+
+ @Override
+ public String getValue() {
+ return "value";
+ }
+
+ @Override
+ public String setValue(String value) {
+ return "value";
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/ProxyRequestHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/ProxyRequestHandlerTestCase.java
new file mode 100644
index 00000000000..54a5a697791
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/ProxyRequestHandlerTestCase.java
@@ -0,0 +1,583 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ProxyRequestHandlerTestCase {
+
+ @Test
+ public void requireThatRequestHandlerIsProxied() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Request request = newRequest(driver, requestHandler);
+ RequestHandler resolvedHandler = new ProxyRequestHandler(request.container().resolveHandler(request));
+ MyResponseHandler responseHandler = MyResponseHandler.newEagerCompletion();
+ resolvedHandler.handleRequest(request, responseHandler).close(null);
+ request.release();
+ assertNotNull(requestHandler.handler);
+ resolvedHandler.handleTimeout(request, responseHandler);
+ assertTrue(requestHandler.timeout);
+ requestHandler.respond();
+
+ requestHandler.release();
+ final ResourceReference resolvedHandlerReference = resolvedHandler.refer();
+ assertTrue(driver.close()); // release installed ref
+
+ assertFalse(requestHandler.destroyed);
+ resolvedHandlerReference.close();
+ assertTrue(requestHandler.destroyed);
+ }
+
+ @Test
+ public void requireThatRequestContentCompletedIsProxied() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newInstance();
+ Request request = newRequest(driver, requestHandler);
+ ContentChannel resolvedContent = request.connect(MyResponseHandler.newEagerCompletion());
+ request.release();
+
+ assertSame(request, requestHandler.request);
+
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ resolvedContent.write(buf, null);
+ assertSame(buf, requestHandler.content.writeBuf);
+ requestHandler.content.writeCompletion.completed();
+ MyCompletion writeCompletion = new MyCompletion();
+ resolvedContent.write(buf = ByteBuffer.allocate(69), writeCompletion);
+ assertSame(buf, requestHandler.content.writeBuf);
+ assertFalse(writeCompletion.completed);
+ assertNull(writeCompletion.failed);
+ requestHandler.content.writeCompletion.completed();
+ assertTrue(writeCompletion.completed);
+ assertNull(writeCompletion.failed);
+
+ MyCompletion closeCompletion = new MyCompletion();
+ resolvedContent.close(closeCompletion);
+ assertTrue(requestHandler.content.closed);
+ assertFalse(closeCompletion.completed);
+ assertNull(writeCompletion.failed);
+ requestHandler.content.closeCompletion.completed();
+ assertTrue(closeCompletion.completed);
+ assertNull(closeCompletion.failed);
+
+ requestHandler.respond();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentFailedIsProxied() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newInstance();
+ Request request = newRequest(driver, requestHandler);
+ ContentChannel resolvedContent = request.connect(MyResponseHandler.newEagerCompletion());
+ request.release();
+
+ assertSame(request, requestHandler.request);
+
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ resolvedContent.write(buf, null);
+ assertSame(buf, requestHandler.content.writeBuf);
+ requestHandler.content.writeCompletion.completed();
+ MyCompletion writeCompletion = new MyCompletion();
+ resolvedContent.write(buf = ByteBuffer.allocate(69), writeCompletion);
+ assertSame(buf, requestHandler.content.writeBuf);
+ assertFalse(writeCompletion.completed);
+ assertNull(writeCompletion.failed);
+ MyException writeFailed = new MyException();
+ requestHandler.content.writeCompletion.failed(writeFailed);
+ assertFalse(writeCompletion.completed);
+ assertSame(writeFailed, writeCompletion.failed);
+
+ MyCompletion closeCompletion = new MyCompletion();
+ resolvedContent.close(closeCompletion);
+ assertTrue(requestHandler.content.closed);
+ assertFalse(closeCompletion.completed);
+ assertNull(closeCompletion.failed);
+ MyException closeFailed = new MyException();
+ requestHandler.content.closeCompletion.failed(closeFailed);
+ assertFalse(writeCompletion.completed);
+ assertSame(closeFailed, closeCompletion.failed);
+
+ requestHandler.respond();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatNullRequestContentIsProxied() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newNullContent();
+ Request request = newRequest(driver, requestHandler);
+ request.connect(MyResponseHandler.newEagerCompletion()).close(null);
+ request.release();
+
+ requestHandler.respond();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatRequestWriteCompletionCanOnlyBeCalledOnce() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newInstance();
+ Request request = newRequest(driver, requestHandler);
+ ContentChannel resolvedContent = request.connect(MyResponseHandler.newEagerCompletion());
+ request.release();
+
+ CountingCompletionHandler completion = new CountingCompletionHandler();
+ resolvedContent.write(ByteBuffer.allocate(0), completion);
+ assertEquals(0, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ requestHandler.content.writeCompletion.completed();
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ requestHandler.content.writeCompletion.completed();
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ requestHandler.content.writeCompletion.failed(new Throwable());
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+
+ resolvedContent.close(null);
+ requestHandler.content.closeCompletion.completed();
+ requestHandler.respond();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatRequestCloseCompletionCanOnlyBeCalledOnce() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newInstance();
+ Request request = newRequest(driver, requestHandler);
+ ContentChannel resolvedContent = request.connect(MyResponseHandler.newEagerCompletion());
+ request.release();
+
+ CountingCompletionHandler completion = new CountingCompletionHandler();
+ resolvedContent.close(completion);
+ assertEquals(0, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ requestHandler.content.closeCompletion.completed();
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ requestHandler.content.closeCompletion.completed();
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ requestHandler.content.closeCompletion.failed(new Throwable());
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+
+ requestHandler.respond();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentCompletedIsProxied() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Request request = newRequest(driver, requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ request.connect(responseHandler).close(null);
+ request.release();
+ Response response = new Response(Response.Status.OK);
+ ContentChannel resolvedContent = requestHandler.handler.handleResponse(response);
+
+ assertSame(response, responseHandler.response);
+
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ resolvedContent.write(buf, null);
+ assertSame(buf, responseHandler.content.writeBuf);
+ responseHandler.content.writeCompletion.completed();
+ MyCompletion writeCompletion = new MyCompletion();
+ resolvedContent.write(buf = ByteBuffer.allocate(69), writeCompletion);
+ assertSame(buf, responseHandler.content.writeBuf);
+ assertFalse(writeCompletion.completed);
+ assertNull(writeCompletion.failed);
+ responseHandler.content.writeCompletion.completed();
+ assertTrue(writeCompletion.completed);
+ assertNull(writeCompletion.failed);
+
+ MyCompletion closeCompletion = new MyCompletion();
+ resolvedContent.close(closeCompletion);
+ assertTrue(responseHandler.content.closed);
+ assertFalse(closeCompletion.completed);
+ assertNull(closeCompletion.failed);
+ responseHandler.content.closeCompletion.completed();
+ assertTrue(closeCompletion.completed);
+ assertNull(closeCompletion.failed);
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentFailedIsProxied() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Request request = newRequest(driver, requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ request.connect(responseHandler).close(null);
+ request.release();
+ Response response = new Response(Response.Status.OK);
+ ContentChannel resolvedContent = requestHandler.handler.handleResponse(response);
+
+ assertSame(response, responseHandler.response);
+
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ resolvedContent.write(buf, null);
+ assertSame(buf, responseHandler.content.writeBuf);
+ responseHandler.content.writeCompletion.completed();
+ MyCompletion writeCompletion = new MyCompletion();
+ resolvedContent.write(buf = ByteBuffer.allocate(69), writeCompletion);
+ assertSame(buf, responseHandler.content.writeBuf);
+ assertFalse(writeCompletion.completed);
+ assertNull(writeCompletion.failed);
+ MyException writeFailed = new MyException();
+ responseHandler.content.writeCompletion.failed(writeFailed);
+ assertFalse(writeCompletion.completed);
+ assertSame(writeFailed, writeCompletion.failed);
+
+ MyCompletion closeCompletion = new MyCompletion();
+ resolvedContent.close(closeCompletion);
+ assertTrue(responseHandler.content.closed);
+ assertFalse(closeCompletion.completed);
+ assertNull(closeCompletion.failed);
+ MyException closeFailed = new MyException();
+ responseHandler.content.closeCompletion.failed(closeFailed);
+ assertFalse(closeCompletion.completed);
+ assertSame(closeFailed, closeCompletion.failed);
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatNullResponseContentIsProxied() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Request request = newRequest(driver, requestHandler);
+ ResponseHandler responseHandler = new ResponseHandler() {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return null;
+ }
+ };
+ request.connect(responseHandler).close(null);
+ requestHandler.handler.handleResponse(new Response(Response.Status.OK)).close(null);
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatResponseWriteCompletionCanOnlyBeCalledOnce() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ Request request = newRequest(driver, requestHandler);
+ request.connect(responseHandler).close(null);
+ request.release();
+ ContentChannel resolvedContent = requestHandler.handler.handleResponse(new Response(Response.Status.OK));
+
+ CountingCompletionHandler completion = new CountingCompletionHandler();
+ resolvedContent.write(ByteBuffer.allocate(0), completion);
+ assertEquals(0, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ responseHandler.content.writeCompletion.completed();
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ responseHandler.content.writeCompletion.completed();
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ responseHandler.content.writeCompletion.failed(new Throwable());
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+
+ resolvedContent.close(null);
+ responseHandler.content.closeCompletion.completed();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatResponseCloseCompletionCanOnlyBeCalledOnce() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ Request request = newRequest(driver, requestHandler);
+ request.connect(responseHandler).close(null);
+ request.release();
+ ContentChannel resolvedContent = requestHandler.handler.handleResponse(new Response(Response.Status.OK));
+
+ CountingCompletionHandler completion = new CountingCompletionHandler();
+ resolvedContent.close(completion);
+ assertEquals(0, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ responseHandler.content.closeCompletion.completed();
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ responseHandler.content.closeCompletion.completed();
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ try {
+ responseHandler.content.closeCompletion.failed(new Throwable());
+ fail();
+ } catch (IllegalStateException e) {
+ // ignore
+ }
+ assertEquals(1, completion.numCompleted.get());
+ assertEquals(0, completion.numFailed.get());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatUncaughtCompletionFailureIsLogged() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = MyRequestHandler.newInstance();
+ Request request = newRequest(driver, requestHandler);
+ ContentChannel resolvedContent = request.connect(MyResponseHandler.newEagerCompletion());
+ request.release();
+
+ MyLogHandler logHandler = new MyLogHandler();
+ Logger.getLogger(ProxyRequestHandler.class.getName()).addHandler(logHandler);
+
+ resolvedContent.write(ByteBuffer.allocate(69), null);
+ MyException writeFailed = new MyException();
+ requestHandler.content.writeCompletion.failed(writeFailed);
+ assertNotNull(logHandler.record);
+ assertSame(writeFailed, logHandler.record.getThrown());
+
+ resolvedContent.close(null);
+ MyException closeFailed = new MyException();
+ requestHandler.content.closeCompletion.failed(closeFailed);
+ assertNotNull(logHandler.record);
+ assertSame(closeFailed, logHandler.record.getThrown());
+
+ requestHandler.respond();
+ assertTrue(driver.close());
+ }
+
+ private static Request newRequest(TestDriver driver, RequestHandler requestHandler) {
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://host/path", requestHandler);
+ driver.activateContainer(builder);
+ return new Request(driver, URI.create("http://host/path"));
+ }
+
+ private static class MyException extends RuntimeException {
+
+ }
+
+ private static class MyCompletion implements CompletionHandler {
+
+ boolean completed = false;
+ Throwable failed = null;
+
+ @Override
+ public void completed() {
+ completed = true;
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ failed = t;
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ final boolean eagerCompletion;
+ CompletionHandler writeCompletion = null;
+ CompletionHandler closeCompletion = null;
+ ByteBuffer writeBuf = null;
+ boolean closed = false;
+
+ MyContent(boolean eagerCompletion) {
+ this.eagerCompletion = eagerCompletion;
+ }
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ writeBuf = buf;
+ writeCompletion = handler;
+ if (eagerCompletion) {
+ writeCompletion.completed();
+ }
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ closed = true;
+ closeCompletion = handler;
+ if (eagerCompletion) {
+ closeCompletion.completed();
+ }
+ }
+
+ static MyContent newInstance() {
+ return new MyContent(false);
+ }
+
+ static MyContent newEagerCompletion() {
+ return new MyContent(true);
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractResource implements RequestHandler {
+
+ final MyContent content;
+ Request request = null;
+ ResponseHandler handler = null;
+ boolean timeout = false;
+ boolean destroyed = false;
+
+ MyRequestHandler(MyContent content) {
+ this.content = content;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ this.request = request;
+ this.handler = handler;
+ return content;
+ }
+
+ @Override
+ public void handleTimeout(Request request, ResponseHandler handler) {
+ timeout = true;
+ }
+
+ @Override
+ public void destroy() {
+ destroyed = true;
+ }
+
+ void respond() {
+ handler.handleResponse(new Response(Response.Status.OK)).close(null);
+ }
+
+ static MyRequestHandler newInstance() {
+ return new MyRequestHandler(MyContent.newInstance());
+ }
+
+ static MyRequestHandler newEagerCompletion() {
+ return new MyRequestHandler(MyContent.newEagerCompletion());
+ }
+
+ static MyRequestHandler newNullContent() {
+ return new MyRequestHandler(null);
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final MyContent content;
+ Response response;
+
+ MyResponseHandler(MyContent content) {
+ this.content = content;
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ this.response = response;
+ return content;
+ }
+
+ static MyResponseHandler newInstance() {
+ return new MyResponseHandler(MyContent.newInstance());
+ }
+
+ static MyResponseHandler newEagerCompletion() {
+ return new MyResponseHandler(MyContent.newEagerCompletion());
+ }
+ }
+
+ private static class MyLogHandler extends Handler {
+
+ LogRecord record;
+
+ @Override
+ public void publish(LogRecord record) {
+ this.record = record;
+ }
+
+ @Override
+ public void flush() {
+
+ }
+
+ @Override
+ public void close() throws SecurityException {
+
+ }
+ }
+
+ private static class CountingCompletionHandler implements CompletionHandler {
+
+ final AtomicInteger numCompleted = new AtomicInteger(0);
+ final AtomicInteger numFailed = new AtomicInteger(0);
+
+ @Override
+ public void completed() {
+ numCompleted.incrementAndGet();
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ numFailed.incrementAndGet();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/ReferencedResourceTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/ReferencedResourceTestCase.java
new file mode 100644
index 00000000000..4337d8b8c6c
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/ReferencedResourceTestCase.java
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * @author <a href="mailto:bakksjo@yahoo-inc.com">Oyvind Bakksjo</a>
+ */
+public class ReferencedResourceTestCase {
+ @Test
+ public void requireThatGettersMatchConstructor() {
+ final SharedResource resource = mock(SharedResource.class);
+ final ResourceReference reference = mock(ResourceReference.class);
+ final ReferencedResource<SharedResource> referencedResource = new ReferencedResource<>(resource, reference);
+ assertThat(referencedResource.getResource(), is(sameInstance(resource)));
+ assertThat(referencedResource.getReference(), is(sameInstance(reference)));
+ }
+
+ @Test
+ public void requireThatCloseCallsReferenceClose() {
+ final SharedResource resource = mock(SharedResource.class);
+ final ResourceReference reference = mock(ResourceReference.class);
+ final ReferencedResource<SharedResource> referencedResource = new ReferencedResource<>(resource, reference);
+ referencedResource.close();
+ verify(reference, times(1)).close();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/ReferencesTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/ReferencesTestCase.java
new file mode 100644
index 00000000000..d8cbe36c04f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/ReferencesTestCase.java
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+/**
+ * @author <a href="mailto:bakksjo@yahoo-inc.com">Oyvind Bakksjo</a>
+ */
+public class ReferencesTestCase {
+ @Test
+ public void requireThatFromResourceCallsReleaseOnResource() {
+ final SharedResource resource = mock(SharedResource.class);
+ final ResourceReference reference = References.fromResource(resource);
+ reference.close();
+ verify(resource, times(1)).release();
+ }
+
+ @Test
+ public void requireThatNoopReferenceCanBeCalledMultipleTimes() {
+ References.NOOP_REFERENCE.close();
+ References.NOOP_REFERENCE.close();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/RequestTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/RequestTestCase.java
new file mode 100644
index 00000000000..fe5af79a6d3
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/RequestTestCase.java
@@ -0,0 +1,381 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Key;
+import com.yahoo.jdisc.application.BindingMatch;
+import com.yahoo.jdisc.application.UriPattern;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.BindingSetNotFoundException;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+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.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RequestTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() throws BindingSetNotFoundException {
+ MyTimer timer = new MyTimer();
+ timer.currentTime = 69;
+
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(timer);
+ driver.activateContainer(driver.newContainerBuilder());
+ Request request = new Request(driver, URI.create("http://foo/bar"));
+ assertNotNull(request);
+ assertEquals(URI.create("http://foo/bar"), request.getUri());
+ request.setUri(URI.create("http://baz/cox"));
+ assertEquals(URI.create("http://baz/cox"), request.getUri());
+ assertTrue(request.isServerRequest());
+ request.setServerRequest(false);
+ assertFalse(request.isServerRequest());
+ assertEquals(69, request.creationTime(TimeUnit.MILLISECONDS));
+ assertNull(request.getTimeout(TimeUnit.MILLISECONDS));
+ request.setTimeout(10, TimeUnit.MILLISECONDS);
+ assertNotNull(request.getTimeout(TimeUnit.MILLISECONDS));
+ assertEquals(10, request.timeRemaining(TimeUnit.MILLISECONDS).longValue());
+ assertTrue(request.context().isEmpty());
+ assertTrue(request.headers().isEmpty());
+ TimeoutManager timeoutManager = new MyTimeoutManager();
+ request.setTimeoutManager(timeoutManager);
+ assertSame(timeoutManager, request.getTimeoutManager());
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatCancelWorks() {
+ MyTimer timer = new MyTimer();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(timer);
+ Request request = newRequest(driver);
+ assertFalse(request.isCancelled());
+ request.cancel();
+ assertTrue(request.isCancelled());
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDefaultTimeoutIsInfinite() {
+ MyTimer timer = new MyTimer();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(timer);
+ Request request = newRequest(driver);
+ assertNull(request.getTimeout(TimeUnit.MILLISECONDS));
+ assertNull(request.timeRemaining(TimeUnit.MILLISECONDS));
+ assertFalse(request.isCancelled());
+ timer.currentTime = Long.MAX_VALUE;
+ assertFalse(request.isCancelled());
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatTimeRemainingUsesTimer() {
+ MyTimer timer = new MyTimer();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(timer);
+ Request request = newRequest(driver);
+ request.setTimeout(10, TimeUnit.MILLISECONDS);
+ for (timer.currentTime = 0; timer.currentTime <= request.getTimeout(TimeUnit.MILLISECONDS);
+ ++timer.currentTime)
+ {
+ assertEquals(request.getTimeout(TimeUnit.MILLISECONDS) - timer.currentTime,
+ request.timeRemaining(TimeUnit.MILLISECONDS).longValue());
+ }
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatTimeoutCausesCancel() {
+ MyTimer timer = new MyTimer();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(timer);
+ Request request = newRequest(driver);
+ request.setTimeout(10, TimeUnit.MILLISECONDS);
+ assertFalse(request.isCancelled());
+ timer.currentTime = 10;
+ assertTrue(request.isCancelled());
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatCancelIsTrueIfParentIsCancelled() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ Request parent = newRequest(driver);
+ Request child = new Request(parent, URI.create("http://localhost/"));
+ parent.cancel();
+ assertTrue(child.isCancelled());
+ parent.release();
+ child.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDestroyReleasesContainer() {
+ final MyContainer container = new MyContainer();
+ Request request = new Request(new CurrentContainer() {
+
+ @Override
+ public Container newReference(URI uri) {
+ return container;
+ }
+ }, URI.create("http://localhost/"));
+ assertEquals(1, container.refCount);
+ request.release();
+ assertEquals(0, container.refCount);
+ }
+
+ @Test
+ public void requireThatServerConnectResolvesToServerBinding() {
+ MyContainer container = new MyContainer();
+ Request request = new Request(container, URI.create("http://localhost/"));
+ request.connect(new MyResponseHandler());
+ assertNotNull(container.asServer);
+ assertTrue(container.asServer);
+ }
+
+ @Test
+ public void requireThatClientConnectResolvesToClientBinding() {
+ MyContainer container = new MyContainer();
+ Request serverReq = new Request(container, URI.create("http://localhost/"));
+ Request clientReq = new Request(serverReq, URI.create("http://localhost/"));
+ clientReq.connect(new MyResponseHandler());
+ assertNotNull(container.asServer);
+ assertFalse(container.asServer);
+ }
+
+ @Test
+ public void requireThatNullTimeoutManagerThrowsException() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ Request request = newRequest(driver);
+
+ try {
+ request.setTimeoutManager(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatTimeoutManagerCanNotBeReplaced() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ Request request = newRequest(driver);
+
+ TimeoutManager manager = new MyTimeoutManager();
+ request.setTimeoutManager(manager);
+ try {
+ request.setTimeoutManager(manager);
+ fail();
+ } catch (IllegalStateException e) {
+ assertEquals("Timeout manager already set.", e.getMessage());
+ }
+
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatSetTimeoutCallsTimeoutManager() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ Request request = newRequest(driver);
+
+ MyTimeoutManager timeoutManager = new MyTimeoutManager();
+ request.setTimeoutManager(timeoutManager);
+ request.setTimeout(6, TimeUnit.SECONDS);
+ assertEquals(6000, timeoutManager.timeoutMillis);
+
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatSetTimeoutManagerPropagatesCurrentTimeout() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ Request request = newRequest(driver);
+
+ MyTimeoutManager timeoutManager = new MyTimeoutManager();
+ request.setTimeout(6, TimeUnit.SECONDS);
+ request.setTimeoutManager(timeoutManager);
+ assertEquals(6000, timeoutManager.timeoutMillis);
+
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatUriIsNormalized() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+
+ assertUri(driver, "http://host/foo", "http://host/foo");
+ assertUri(driver, "http://host/./foo", "http://host/foo");
+ assertUri(driver, "http://host/././foo", "http://host/foo");
+ assertUri(driver, "http://host/foo/", "http://host/foo/");
+ assertUri(driver, "http://host/foo/.", "http://host/foo/");
+ assertUri(driver, "http://host/foo/./", "http://host/foo/");
+ assertUri(driver, "http://host/foo/./.", "http://host/foo/");
+ assertUri(driver, "http://host/foo/././", "http://host/foo/");
+ assertUri(driver, "http://host/foo/..", "http://host/");
+ assertUri(driver, "http://host/foo/../", "http://host/");
+ assertUri(driver, "http://host/foo/../bar", "http://host/bar");
+ assertUri(driver, "http://host/foo/../bar/", "http://host/bar/");
+ assertUri(driver, "http://host//foo//", "http://host/foo/");
+ assertUri(driver, "http://host///foo///", "http://host/foo/");
+ assertUri(driver, "http://host///foo///bar///", "http://host/foo/bar/");
+
+ assertTrue(driver.close());
+ }
+
+ private static void assertUri(CurrentContainer container, String requestUri, String expectedUri) {
+ Request serverReq = new Request(container, URI.create(requestUri));
+ assertEquals(expectedUri, serverReq.getUri().toString());
+
+ serverReq.setUri(URI.create(requestUri));
+ assertEquals(expectedUri, serverReq.getUri().toString());
+
+ Request clientReq = new Request(serverReq, URI.create(requestUri));
+ assertEquals(expectedUri, clientReq.getUri().toString());
+
+ serverReq.release();
+ clientReq.release();
+ }
+
+ private static Request newRequest(TestDriver driver) {
+ driver.activateContainer(driver.newContainerBuilder());
+ return new Request(driver, URI.create("http://host/path"));
+ }
+
+ private static class MyTimer extends AbstractModule implements Timer {
+
+ long currentTime = 0;
+
+ @Override
+ public long currentTimeMillis() {
+ return currentTime;
+ }
+
+ @Override
+ protected void configure() {
+ bind(Timer.class).toInstance(this);
+ }
+ }
+
+ private static class MyContainer implements CurrentContainer, Container {
+
+ Boolean asServer = null;
+ int refCount = 1;
+
+ @Override
+ public Container newReference(URI uri) {
+ return this;
+ }
+
+ @Override
+ public RequestHandler resolveHandler(Request request) {
+ this.asServer = request.isServerRequest();
+ RequestHandler requestHandler = new MyRequestHandler();
+ request.setBindingMatch(new BindingMatch<>(
+ new UriPattern("http://*/*").match(request.getUri()),
+ requestHandler));
+ return requestHandler;
+ }
+
+ @Override
+ public <T> T getInstance(Key<T> key) {
+ return Guice.createInjector().getInstance(key);
+ }
+
+ @Override
+ public <T> T getInstance(Class<T> type) {
+ return Guice.createInjector().getInstance(type);
+ }
+
+ @Override
+ public ResourceReference refer() {
+ ++refCount;
+ return new ResourceReference() {
+ @Override
+ public void close() {
+ --refCount;
+ }
+ };
+ }
+
+ @Override
+ public void release() {
+ --refCount;
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return 0;
+ }
+ }
+
+ private static class MyRequestHandler extends NoopSharedResource implements RequestHandler {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ return new MyContentChannel();
+ }
+
+ @Override
+ public void handleTimeout(Request request, ResponseHandler handler) {
+
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return null;
+ }
+ }
+
+ private static class MyContentChannel implements ContentChannel {
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+
+ }
+ }
+
+ private static class MyTimeoutManager implements TimeoutManager {
+
+ long timeoutMillis;
+
+ @Override
+ public void scheduleTimeout(Request request) {
+ timeoutMillis = request.getTimeout(TimeUnit.MILLISECONDS);
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/ResponseTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/ResponseTestCase.java
new file mode 100644
index 00000000000..303d6b0dc55
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/ResponseTestCase.java
@@ -0,0 +1,86 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ResponseTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Response response = new Response(69);
+ assertEquals(69, response.getStatus());
+ response.setStatus(96);
+ assertEquals(96, response.getStatus());
+ Throwable t = new Throwable();
+ response.setError(t);
+ assertSame(t, response.getError());
+ assertTrue(response.context().isEmpty());
+ assertTrue(response.headers().isEmpty());
+ }
+
+ @Test
+ public void requireThatStatusCodesDoNotChange() {
+ assertEquals(100, Response.Status.CONTINUE);
+ assertEquals(101, Response.Status.SWITCHING_PROTOCOLS);
+ assertEquals(102, Response.Status.PROCESSING);
+
+ assertEquals(200, Response.Status.OK);
+ assertEquals(201, Response.Status.CREATED);
+ assertEquals(202, Response.Status.ACCEPTED);
+ assertEquals(203, Response.Status.NON_AUTHORITATIVE_INFORMATION);
+ assertEquals(204, Response.Status.NO_CONTENT);
+ assertEquals(205, Response.Status.RESET_CONTENT);
+ assertEquals(206, Response.Status.PARTIAL_CONTENT);
+ assertEquals(207, Response.Status.MULTI_STATUS);
+
+ assertEquals(300, Response.Status.MULTIPLE_CHOICES);
+ assertEquals(301, Response.Status.MOVED_PERMANENTLY);
+ assertEquals(302, Response.Status.FOUND);
+ assertEquals(303, Response.Status.SEE_OTHER);
+ assertEquals(304, Response.Status.NOT_MODIFIED);
+ assertEquals(305, Response.Status.USE_PROXY);
+ assertEquals(307, Response.Status.TEMPORARY_REDIRECT);
+
+ assertEquals(400, Response.Status.BAD_REQUEST);
+ assertEquals(401, Response.Status.UNAUTHORIZED);
+ assertEquals(402, Response.Status.PAYMENT_REQUIRED);
+ assertEquals(403, Response.Status.FORBIDDEN);
+ assertEquals(404, Response.Status.NOT_FOUND);
+ assertEquals(405, Response.Status.METHOD_NOT_ALLOWED);
+ assertEquals(406, Response.Status.NOT_ACCEPTABLE);
+ assertEquals(407, Response.Status.PROXY_AUTHENTICATION_REQUIRED);
+ assertEquals(408, Response.Status.REQUEST_TIMEOUT);
+ assertEquals(409, Response.Status.CONFLICT);
+ assertEquals(410, Response.Status.GONE);
+ assertEquals(411, Response.Status.LENGTH_REQUIRED);
+ assertEquals(412, Response.Status.PRECONDITION_FAILED);
+ assertEquals(413, Response.Status.REQUEST_TOO_LONG);
+ assertEquals(414, Response.Status.REQUEST_URI_TOO_LONG);
+ assertEquals(415, Response.Status.UNSUPPORTED_MEDIA_TYPE);
+ assertEquals(416, Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE);
+ assertEquals(417, Response.Status.EXPECTATION_FAILED);
+ assertEquals(419, Response.Status.INSUFFICIENT_SPACE_ON_RESOURCE);
+ assertEquals(420, Response.Status.METHOD_FAILURE);
+ assertEquals(422, Response.Status.UNPROCESSABLE_ENTITY);
+ assertEquals(423, Response.Status.LOCKED);
+ assertEquals(424, Response.Status.FAILED_DEPENDENCY);
+
+ assertEquals(505, Response.Status.VERSION_NOT_SUPPORTED);
+ assertEquals(500, Response.Status.INTERNAL_SERVER_ERROR);
+ assertEquals(501, Response.Status.NOT_IMPLEMENTED);
+ assertEquals(502, Response.Status.BAD_GATEWAY);
+ assertEquals(503, Response.Status.SERVICE_UNAVAILABLE);
+ assertEquals(504, Response.Status.GATEWAY_TIMEOUT);
+ assertEquals(505, Response.Status.VERSION_NOT_SUPPORTED);
+ assertEquals(507, Response.Status.INSUFFICIENT_STORAGE);
+ }
+
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/AbstractApplicationTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/AbstractApplicationTestCase.java
new file mode 100644
index 00000000000..da5f046ef1f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/AbstractApplicationTestCase.java
@@ -0,0 +1,98 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+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 AbstractApplicationTestCase {
+
+ @Test
+ public void requireThatContainerApiIsAvailable() {
+ TestDriver driver = TestDriver.newInjectedApplicationInstance(MyApplication.class);
+ MyApplication app = (MyApplication)driver.application();
+ app.activateContainer(app.newContainerBuilder());
+ assertNotNull(app.container());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDestroySignalsTermination() {
+ TestDriver driver = TestDriver.newInjectedApplicationInstance(MyApplication.class);
+ MyApplication app = (MyApplication)driver.application();
+ assertFalse(app.isTerminated());
+ assertTrue(driver.close());
+ assertTrue(app.isTerminated());
+ }
+
+ @Test
+ public void requireThatTerminationCanBeWaitedForWithTimeout() throws InterruptedException {
+ TestDriver driver = TestDriver.newInjectedApplicationInstance(MyApplication.class);
+ final MyApplication app = (MyApplication)driver.application();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Executors.newSingleThreadExecutor().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ app.awaitTermination(600, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ latch.countDown();
+ }
+ });
+ assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
+ assertTrue(driver.close());
+ assertTrue(latch.await(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatTerminationCanBeWaitedForWithoutTimeout() throws InterruptedException {
+ TestDriver driver = TestDriver.newInjectedApplicationInstance(MyApplication.class);
+ final MyApplication app = (MyApplication)driver.application();
+ final CountDownLatch latch = new CountDownLatch(1);
+ Executors.newSingleThreadExecutor().execute(new Runnable() {
+
+ @Override
+ public void run() {
+ try {
+ app.awaitTermination();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ latch.countDown();
+ }
+ });
+ assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
+ assertTrue(driver.close());
+ assertTrue(latch.await(600, TimeUnit.SECONDS));
+ }
+
+ private static class MyApplication extends AbstractApplication {
+
+ @Inject
+ public MyApplication(BundleInstaller bundleInstaller, ContainerActivator activator,
+ CurrentContainer container) {
+ super(bundleInstaller, activator, container);
+ }
+
+ @Override
+ public void start() {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/ApplicationNotReadyTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ApplicationNotReadyTestCase.java
new file mode 100644
index 00000000000..1351e717015
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ApplicationNotReadyTestCase.java
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.google.inject.Inject;
+import com.google.inject.ProvisionException;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ApplicationNotReadyTestCase {
+
+ @Test
+ public void requireThatExceptionIsThrown() {
+ try {
+ TestDriver.newInjectedApplicationInstanceWithoutOsgi(MyApplication.class);
+ fail();
+ } catch (ProvisionException e) {
+ Throwable t = e.getCause();
+ assertNotNull(t);
+ assertTrue(t instanceof ApplicationNotReadyException);
+ }
+ }
+
+ private static class MyApplication implements Application {
+
+ @Inject
+ MyApplication(ContainerActivator activator) {
+ activator.activateContainer(activator.newContainerBuilder());
+ }
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void stop() {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingMatchTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingMatchTestCase.java
new file mode 100644
index 00000000000..59d0535f99e
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingMatchTestCase.java
@@ -0,0 +1,52 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BindingMatchTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Object obj = new Object();
+ BindingMatch<Object> match = new BindingMatch<>(
+ new UriPattern("http://*/*").match(URI.create("http://localhost:69/status.html")),
+ obj);
+ assertSame(obj, match.target());
+ assertEquals(3, match.groupCount());
+ assertEquals("localhost", match.group(0));
+ assertEquals("69", match.group(1));
+ assertEquals("status.html", match.group(2));
+ }
+
+ @Test
+ public void requireThatConstructorArgumentsCanNotBeNull() {
+ try {
+ new BindingMatch<>(null, null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ try {
+ new BindingMatch<>(new UriPattern("http://*/*").match(URI.create("http://localhost/")), null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ try {
+ new BindingMatch<>(null, new Object());
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingRepositoryTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingRepositoryTestCase.java
new file mode 100644
index 00000000000..aa9bc783b74
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingRepositoryTestCase.java
@@ -0,0 +1,181 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.yahoo.jdisc.NoopSharedResource;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.test.NonWorkingRequestHandler;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BindingRepositoryTestCase {
+
+ @Test
+ public void requireThatRepositoryCanBeActivated() {
+ BindingRepository<Object> bindings = new BindingRepository<>();
+ bindings.bind("http://host/path", new Object());
+
+ BindingSet<Object> set = bindings.activate();
+ assertNotNull(set);
+ Iterator<Map.Entry<UriPattern, Object>> it = set.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ assertNotNull(it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatActivationIsSnapshotOfRepository() {
+ BindingRepository<Object> bindings = new BindingRepository<>();
+ bindings.bind("http://host/path", new Object());
+
+ BindingSet<Object> set = bindings.activate();
+ assertNotNull(set);
+ bindings.clear();
+
+ Iterator<Map.Entry<UriPattern, Object>> it = set.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ assertNotNull(it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatObjectsCanBeBound() {
+ BindingRepository<Object> bindings = new BindingRepository<>();
+ Object foo = new Object();
+ Object bar = new Object();
+ bindings.bind("http://host/foo", foo);
+ bindings.bind("http://host/bar", bar);
+
+ Iterator<Map.Entry<UriPattern, Object>> it = bindings.activate().iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ Map.Entry<UriPattern, Object> entry = it.next();
+ assertNotNull(entry);
+ assertEquals(new UriPattern("http://host/foo"), entry.getKey());
+ assertSame(foo, entry.getValue());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/bar"), entry.getKey());
+ assertSame(bar, entry.getValue());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatPatternCannotBeStolen() {
+ final String pattern = "http://host/path";
+ final RequestHandler originallyBoundHandler = new NonWorkingRequestHandler();
+
+ BindingRepository<Object> bindings = new BindingRepository<>();
+ bindings.bind(pattern, originallyBoundHandler);
+ bindings.bind(pattern, new PatternStealingRequestHandler());
+
+ BindingSet bindingSet = bindings.activate();
+ assertEquals(originallyBoundHandler, bindingSet.resolve(URI.create(pattern)));
+ }
+
+ @Test
+ public void requireThatBindAllMethodWorks() {
+ Object foo = new Object();
+ Object bar = new Object();
+ Object baz = new Object();
+
+ Map<String, Object> toAdd = new HashMap<>();
+ toAdd.put("http://host/foo", foo);
+ toAdd.put("http://host/bar", bar);
+
+ BindingRepository<Object> addTo = new BindingRepository<>();
+ addTo.bind("http://host/baz", baz);
+ addTo.bindAll(toAdd);
+
+ Iterator<Map.Entry<UriPattern, Object>> it = addTo.activate().iterator();
+ Map.Entry<UriPattern, Object> entry = it.next();
+ assertNotNull(entry);
+ assertEquals(new UriPattern("http://host/foo"), entry.getKey());
+ assertSame(foo, entry.getValue());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/baz"), entry.getKey());
+ assertSame(baz, entry.getValue());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/bar"), entry.getKey());
+ assertSame(bar, entry.getValue());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatPutAllMethodWorks() {
+ Object foo = new Object();
+ Object bar = new Object();
+ Object baz = new Object();
+
+ BindingRepository<Object> toAdd = new BindingRepository<>();
+ toAdd.bind("http://host/foo", foo);
+ toAdd.bind("http://host/bar", bar);
+
+ BindingRepository<Object> addTo = new BindingRepository<>();
+ addTo.bind("http://host/baz", baz);
+ addTo.putAll(toAdd);
+
+ Iterator<Map.Entry<UriPattern, Object>> it = addTo.activate().iterator();
+ assertTrue(it.hasNext());
+ Map.Entry<UriPattern, Object> entry = it.next();
+ assertNotNull(entry);
+ assertEquals(new UriPattern("http://host/foo"), entry.getKey());
+ assertSame(foo, entry.getValue());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/baz"), entry.getKey());
+ assertSame(baz, entry.getValue());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/bar"), entry.getKey());
+ assertSame(bar, entry.getValue());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatPutNullThrowsException() {
+ try {
+ new BindingRepository<>().put(null, new Object());
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ try {
+ new BindingRepository<>().put(new UriPattern("http://host/foo"), null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ static class PatternStealingRequestHandler extends NoopSharedResource implements RequestHandler {
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void handleTimeout(Request request, ResponseHandler handler) { }
+
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java
new file mode 100644
index 00000000000..7ff2aa6908b
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BindingSetTestCase.java
@@ -0,0 +1,506 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.test.NonWorkingRequestHandler;
+import static com.yahoo.vespa.defaults.Defaults.getDefaults;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BindingSetTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/foo"), foo);
+ RequestHandler bar = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/bar"), bar);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+
+ Iterator<Map.Entry<UriPattern, RequestHandler>> it = bindings.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ Map.Entry<UriPattern, RequestHandler> entry = it.next();
+ assertNotNull(entry);
+ assertEquals(new UriPattern("http://host/foo"), entry.getKey());
+ assertSame(foo, entry.getValue());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/bar"), entry.getKey());
+ assertSame(bar, entry.getValue());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatSimpleResolutionWorks() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/foo"), foo);
+ RequestHandler bar = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/bar"), bar);
+
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ BindingMatch<RequestHandler> match = bindings.match(URI.create("http://host/foo"));
+ assertNotNull(match);
+ assertEquals(0, match.groupCount());
+ assertSame(foo, match.target());
+ assertSame(foo, bindings.resolve(URI.create("http://host/foo")));
+
+ assertNotNull(match = bindings.match(URI.create("http://host/bar")));
+ assertEquals(0, match.groupCount());
+ assertSame(bar, match.target());
+ assertSame(bar, bindings.resolve(URI.create("http://host/bar")));
+ }
+
+ @Test
+ public void requireThatPatternResolutionWorks() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/*"), foo);
+ RequestHandler bar = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/path"), bar);
+
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ BindingMatch<RequestHandler> match = bindings.match(URI.create("http://host/anon"));
+ assertNotNull(match);
+ assertEquals(1, match.groupCount());
+ assertEquals("anon", match.group(0));
+ assertSame(foo, match.target());
+ assertSame(foo, bindings.resolve(URI.create("http://host/anon")));
+
+ assertNotNull(match = bindings.match(URI.create("http://host/path")));
+ assertEquals(0, match.groupCount());
+ assertSame(bar, match.target());
+ assertSame(bar, bindings.resolve(URI.create("http://host/path")));
+ }
+
+ @Test
+ public void requireThatPatternResolutionWorksForWildCards() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host:*/bar"), foo);
+ RequestHandler bob = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://*abc:*/*bar"), bob);
+ RequestHandler car = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("*://*:21/*"), car);
+
+
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ BindingMatch<RequestHandler> match = bindings.match(URI.create("http://host:8080/bar"));
+ assertNotNull(match);
+ assertEquals(1, match.groupCount());
+ assertEquals("8080", match.group(0));
+ assertSame(foo, match.target());
+ assertSame(foo, bindings.resolve(URI.create("http://host:8080/bar")));
+
+ match = bindings.match(URI.create("http://host:8080/foo/bar"));
+ assertNull(match);
+
+ match = bindings.match(URI.create("http://xyzabc:8080/pqrbar"));
+ assertNotNull(match);
+ assertSame(bob, match.target());
+
+ match = bindings.match(URI.create("ftp://lmn:21/abc"));
+ assertNotNull(match);
+ assertSame(car, match.target());
+ }
+
+ @Test
+ public void requireThatPatternResolutionWorksForFilters() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://*/filtered/*"), foo);
+
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ BindingMatch<RequestHandler> match = bindings.match(URI.create("http://localhost:80/status.html"));
+ assertNull(match);
+ match = bindings.match(URI.create("http://localhost/filtered/status.html"));
+ assertNotNull(match);
+ assertSame(foo, match.target());
+ }
+
+ @Test
+ public void requireThatTreeSplitCanBeBoundForSchemes() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler httpfoo = new NonWorkingRequestHandler();
+ RequestHandler httpsfoo = new NonWorkingRequestHandler();
+ RequestHandler ftpfoo = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/foo"), httpfoo);
+ handlers.put(new UriPattern("https://host/foo"), httpsfoo);
+ handlers.put(new UriPattern("ftp://host/foo"), ftpfoo);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ }
+
+ @Test
+ public void requireThatTreeSplitCanBeBoundForHosts() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler foobar = new NonWorkingRequestHandler();
+ RequestHandler fooqux = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://hostabc/foo"), foobar);
+ handlers.put(new UriPattern("http://hostpqr/foo"), fooqux);
+ handlers.put(new UriPattern("http://host/foo"), foo);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ }
+
+ @Test
+ public void requireThatTreeSplitCanBeBoundForPorts() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo8080 = new NonWorkingRequestHandler();
+ RequestHandler foo80 = new NonWorkingRequestHandler();
+ RequestHandler foobar = new NonWorkingRequestHandler();
+ RequestHandler foopqrbar = new NonWorkingRequestHandler();
+
+ handlers.put(new UriPattern("http://host:8080/foo"), foo8080);
+ handlers.put(new UriPattern("http://host:70/foo"), foo80);
+ handlers.put(new UriPattern("http://hostpqr:70/foo"), foopqrbar);
+ handlers.put(new UriPattern("http://host:80/foobar"), foobar);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ }
+
+ @Test
+ public void requireThatTreeSplitCanBeBoundForPaths() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler foobar = new NonWorkingRequestHandler();
+ RequestHandler fooqux = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/foobar"), foobar);
+ handlers.put(new UriPattern("http://host/fooqux"), fooqux);
+ handlers.put(new UriPattern("http://host/foo"), foo);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ }
+
+ @Test
+ public void requireThatTreeSplitCanBeBoundForWildcards() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo8080 = new NonWorkingRequestHandler();
+ RequestHandler foo80 = new NonWorkingRequestHandler();
+ RequestHandler foobar = new NonWorkingRequestHandler();
+ RequestHandler foopqrbar = new NonWorkingRequestHandler();
+
+ handlers.put(new UriPattern("http://host:8080/foo"), foo8080);
+ handlers.put(new UriPattern("http://host:708/foo"), foo80);
+ handlers.put(new UriPattern("http://host:80/foobar"), foobar);
+ handlers.put(new UriPattern("http://hos*:708/foo"), foopqrbar);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(foopqrbar, bindings.resolve(URI.create("http://hostabc:708/foo")));
+ assertSame(foo80, bindings.resolve(URI.create("http://host:708/foo")));
+ assertSame(foo8080, bindings.resolve(URI.create("http://host:8080/foo")));
+ }
+
+ @Test
+ public void requireThatTreeWorksForURIWithQueryOrFragments() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://*/application/v1/session"), foo);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(foo, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v1/session?name=base")));
+ assertSame(foo, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v1/session#application")));
+ }
+
+ @Test
+ public void requireThatTreeWorksForURIWithPathWildCards() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler foo1 = new NonWorkingRequestHandler();
+ RequestHandler foo2 = new NonWorkingRequestHandler();
+ RequestHandler foo3 = new NonWorkingRequestHandler();
+ RequestHandler foo4 = new NonWorkingRequestHandler();
+ RequestHandler foo5 = new NonWorkingRequestHandler();
+ RequestHandler foo6 = new NonWorkingRequestHandler();
+ RequestHandler foo7 = new NonWorkingRequestHandler();
+ RequestHandler foo8 = new NonWorkingRequestHandler();
+ RequestHandler foo9 = new NonWorkingRequestHandler();
+ RequestHandler foo10 = new NonWorkingRequestHandler();
+ RequestHandler foo11 = new NonWorkingRequestHandler();
+ RequestHandler foo12 = new NonWorkingRequestHandler();
+ RequestHandler foo13 = new NonWorkingRequestHandler();
+ RequestHandler foo14 = new NonWorkingRequestHandler();
+ RequestHandler foo15 = new NonWorkingRequestHandler();
+
+ handlers.put(new UriPattern("http://*/config/v1/*"), foo);
+ handlers.put(new UriPattern("http://*/config/v1/*/"), foo1);
+ handlers.put(new UriPattern("http://*/config/v1/*/*"), foo2);
+ handlers.put(new UriPattern("http://*/config/v1/*/*/"), foo3);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/"), foo4);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/*"), foo5);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/*/session"), foo6);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/*/session/*/prepared"), foo7);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/*/session/*/active"), foo8);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/*/session/*/content/*"), foo9);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/*/application/"), foo10);
+ handlers.put(new UriPattern("http://*/application/v2/tenant/*/application/*/environment/*/" +
+ "region/*/instance/*/content/*"), foo11);
+ handlers.put(new UriPattern("http://*/config/v2/tenant/*/application/*/*"), foo12);
+ handlers.put(new UriPattern("http://*/config/v2/tenant/*/application/*/*/*"), foo13);
+ handlers.put(new UriPattern("http://*/config/v2/tenant/*/application/*/environment" +
+ "/*/region/*/instance/*/*"), foo14);
+ handlers.put(new UriPattern("http://*/config/v2/tenant/*/application/*/*/*/"), foo15);
+
+
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(foo, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v1/cloud.config.log.logd")));
+ assertSame(foo1, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v1/cloud.config.log.logd/")));
+ assertSame(foo2, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v1/cloud.config.log.logd/admin")));
+ assertSame(foo3, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v1/cloud.config.log.logd/admin/")));
+ assertSame(foo4, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/")));
+ assertSame(foo5, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/b")));
+ assertSame(foo6, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/bar/session")));
+ assertSame(foo7, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/bar/session/aef/prepared")));
+ assertSame(foo8, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/bar/session/a/active")));
+ assertSame(foo9, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/bar/session/aef/content/x")));
+ assertSame(foo10, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/bar/session/application/")));
+ assertSame(foo11, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/application/v2/tenant/bar/application/bbc/environment/xyz/region/m/inst" +
+ "ance/a/content/l")));
+ assertSame(foo12, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v2/tenant/bar/application/bbc/xyz")));
+ assertSame(foo13, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v2/tenant/bar/application/bbc/xyz/a")));
+ assertSame(foo14, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v2/tenant/bar/application/bbc/environment/a/region/b/instance/a/b")));
+ assertSame(foo15, bindings.resolve(URI.create("http://abcxyz.yahoo.com:19071" +
+ "/config/v2/tenant/bar/application/bbc/xyz/a/c/")));
+ }
+
+ @Test
+ public void requireThatPathOverPortWorks() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler applicationStatus = new NonWorkingRequestHandler();
+ RequestHandler search = new NonWorkingRequestHandler();
+ RequestHandler legacy = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://*/processing/*"), new NonWorkingRequestHandler());
+ handlers.put(new UriPattern("http://*/statistics/*"), new NonWorkingRequestHandler());
+ handlers.put(new UriPattern("http://*/state/v1/*"), new NonWorkingRequestHandler());
+ handlers.put(new UriPattern("http://*/search/*"), search);
+ handlers.put(new UriPattern("http://*/status.html"), new NonWorkingRequestHandler());
+ handlers.put(new UriPattern("http://*/ApplicationStatus"), applicationStatus);
+ handlers.put(new UriPattern("http://*:" + getDefaults().vespaWebServicePort() + "/*"), legacy);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+
+ assertSame(applicationStatus, bindings.resolve(URI.create
+ ("http://abcxyz.yahoo.com:" + getDefaults().vespaWebServicePort() + "/ApplicationStatus")));
+ assertSame(search, bindings.resolve(URI.create
+ ("http://abcxyz.yahoo.com:" + getDefaults().vespaWebServicePort() + "/search/?query=sddocname:music")));
+ assertSame(legacy, bindings.resolve(URI.create
+ ("http://abcxyz.yahoo.com:" + getDefaults().vespaWebServicePort() + "/stats/?query=stat:query")));
+ }
+
+ @Test
+ public void requireThatPathOverPortsDoNotWorkOverStricterPatterns() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler bar = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host:4050/a/"), foo);
+ handlers.put(new UriPattern("http://host/a/"), bar);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(foo, bindings.resolve(URI.create("http://host:4050/a/")));
+ }
+
+ @Test
+ public void requireThatSchemeOrderOverHost() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler bar = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host:5050/a/"), foo);
+ handlers.put(new UriPattern("ftp://host:5050/a/"), bar);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(foo, bindings.resolve(URI.create("http://host:5050/a/")));
+ assertSame(bar, bindings.resolve(URI.create("ftp://host:5050/a/")));
+ }
+
+ @Test
+ public void requireThatPortsAreOrdered() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler bar = new NonWorkingRequestHandler();
+ RequestHandler car = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host:5050/a/"), foo);
+ handlers.put(new UriPattern("http://host:5051/a/"), bar);
+ handlers.put(new UriPattern("http://host/a/"), car);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(foo, bindings.resolve(URI.create("http://host:5050/a/")));
+ assertSame(bar, bindings.resolve(URI.create("http://host:5051/a/")));
+ assertSame(car, bindings.resolve(URI.create("http://host/a/")));
+ assertSame(car, bindings.resolve(URI.create("http://host:8080/a/")));
+ assertSame(car, bindings.resolve(URI.create("http://host:80/a/")));
+ }
+
+ @Test
+ public void requireThatPathsAreOrdered() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler bar = new NonWorkingRequestHandler();
+ RequestHandler car = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host:5050/a/"), foo);
+ handlers.put(new UriPattern("http://host:5050/b/"), bar);
+ handlers.put(new UriPattern("http://host/a/"), car);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(foo, bindings.resolve(URI.create("http://host:5050/a/")));
+ assertSame(bar, bindings.resolve(URI.create("http://host:5050/b/")));
+ assertSame(car, bindings.resolve(URI.create("http://host/a/")));
+ assertSame(car, bindings.resolve(URI.create("http://host:8080/a/")));
+ assertSame(car, bindings.resolve(URI.create("http://host:80/a/")));
+ }
+
+ @Test
+ public void requireThatStrictPatternsOrderBeforeWildcards() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+
+ RequestHandler fooScheme = new NonWorkingRequestHandler();
+ RequestHandler barScheme = new NonWorkingRequestHandler();
+
+ RequestHandler fooHost = new NonWorkingRequestHandler();
+ RequestHandler barHost = new NonWorkingRequestHandler();
+
+ RequestHandler fooPort = new NonWorkingRequestHandler();
+ RequestHandler barPort = new NonWorkingRequestHandler();
+ RequestHandler carPort = new NonWorkingRequestHandler();
+
+ RequestHandler fooPath = new NonWorkingRequestHandler();
+ RequestHandler barPath = new NonWorkingRequestHandler();
+
+ handlers.put(new UriPattern("http://host/x/"), fooScheme);
+ handlers.put(new UriPattern("*://host/x/"), barScheme);
+
+ handlers.put(new UriPattern("http://host/abc/"), fooHost);
+ handlers.put(new UriPattern("http://*/abc/"), barHost);
+
+ handlers.put(new UriPattern("http://host:*/a/"), fooPort);
+ handlers.put(new UriPattern("http://host:5050/b/"), barPort);
+ handlers.put(new UriPattern("http://host/b/"), carPort);
+
+ handlers.put(new UriPattern("http://hostname/abcde/"), fooPath);
+ handlers.put(new UriPattern("http://hostname/*/"), barPath);
+
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertSame(fooScheme, bindings.resolve(URI.create("http://host/x/")));
+ assertSame(barScheme, bindings.resolve(URI.create("ftp://host/x/")));
+
+ assertSame(fooHost, bindings.resolve(URI.create("http://host:8080/abc/")));
+ assertSame(barHost, bindings.resolve(URI.create("http://lmn:5050/abc/")));
+
+ assertSame(fooPort, bindings.resolve(URI.create("http://host:5050/a/")));
+ assertSame(barPort, bindings.resolve(URI.create("http://host:5050/b/")));
+ assertSame(carPort, bindings.resolve(URI.create("http://host/b/")));
+ assertSame(carPort, bindings.resolve(URI.create("http://host:8080/b/")));
+ assertSame(carPort, bindings.resolve(URI.create("http://host:80/b/")));
+ assertSame(fooPath, bindings.resolve(URI.create("http://hostname/abcde/")));
+ assertSame(barPath, bindings.resolve(URI.create("http://hostname/abcd/")));
+
+ }
+
+ @Test
+ public void requireThatToStringMethodWorks() {
+ Map<UriPattern, RequestHandler> handlers = new LinkedHashMap<>();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler bar = new NonWorkingRequestHandler();
+ handlers.put(new UriPattern("http://host/foo"), foo);
+ handlers.put(new UriPattern("http://host/bar"), bar);
+ BindingSet<RequestHandler> bindings = new BindingSet<>(handlers.entrySet());
+ assertNotNull(bindings);
+ assertNotNull(bindings.toString()); //Just to get code coverage.
+ }
+
+
+ @Test
+ public void requireThatPatternsAreOrderedMoreSpecificToLess() {
+ assertOrder("3://host/path", "2://host/path", "1://host/path");
+ assertOrder("http://3/path", "http://2/path", "http://1/path");
+ assertOrder("http://host:3/path", "http://host:2/path", "http://host:1/path");
+ assertOrder("http://host/3", "http://host/2", "http://host/1");
+ assertOrder("http://*/*", "*://host/2", "*://host/1");
+ assertOrder("http://host/*", "http://*/2", "http://*/1");
+ assertOrder("http://host:*/3", "http://host:2/2", "http://host:1/1");
+ assertOrder("http://host/*/3/2/", "http://host/*/1/2", "http://host/*/2/*");
+ assertOrder("http://host:69/path",
+ "http://host/*",
+ "http://*:69/path",
+ "http://*/path",
+ "http://*:69/*",
+ "http://*/*",
+ "*://host/path",
+ "*://*/path",
+ "*://*/*");
+ assertOrder("http://*/HelloWorld",
+ "http://*:4080/state/v1/*",
+ "http://*:4083/*",
+ "http://*:4081/*",
+ "http://*:4080/*");
+ }
+
+ private static void assertOrder(String... expected) {
+ for (int off = 0; off < expected.length; ++off) {
+ List<String> actual = new ArrayList<>();
+ for (int i = 0; i < expected.length; ++i) {
+ actual.add(expected[(off + i) % expected.length]);
+ }
+ assertOrder(Arrays.asList(expected), actual);
+
+ actual = new ArrayList<>();
+ for (int i = expected.length; --i >= 0; ) {
+ actual.add(expected[(off + i) % expected.length]);
+ }
+ assertOrder(Arrays.asList(expected), actual);
+ }
+ }
+
+ private static void assertOrder(List<String> expected, List<String> actual) {
+ BindingRepository<Object> repo = new BindingRepository<>();
+ for (String pattern : actual) {
+ repo.bind(pattern, new Object());
+ }
+ BindingSet<Object> bindings = repo.activate();
+ Iterator<Map.Entry<UriPattern, Object>> it = bindings.iterator();
+ for (String pattern : expected) {
+ assertTrue(it.hasNext());
+ assertEquals(new UriPattern(pattern), it.next().getKey());
+ }
+ assertFalse(it.hasNext());
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/BundleInstallationExceptionTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BundleInstallationExceptionTestCase.java
new file mode 100644
index 00000000000..870db066dc0
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/BundleInstallationExceptionTestCase.java
@@ -0,0 +1,53 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class BundleInstallationExceptionTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Throwable t = new Throwable("foo");
+ Collection<Bundle> bundles = new LinkedList<>();
+ bundles.add(Mockito.mock(Bundle.class));
+ BundleInstallationException e = new BundleInstallationException(bundles, t);
+ assertSame(t, e.getCause());
+ assertEquals(t.getMessage(), e.getCause().getMessage());
+ assertEquals(bundles, e.installedBundles());
+ }
+
+ @Test
+ public void requireThatBundlesCollectionIsDefensivelyCopied() {
+ Collection<Bundle> bundles = new LinkedList<>();
+ bundles.add(Mockito.mock(Bundle.class));
+ BundleInstallationException e = new BundleInstallationException(bundles, new Throwable());
+ bundles.add(Mockito.mock(Bundle.class));
+ assertEquals(1, e.installedBundles().size());
+ }
+
+ @Test
+ public void requireThatBundlesCollectionIsUnmodifiable() {
+ BundleInstallationException e = new BundleInstallationException(Arrays.asList(Mockito.mock(Bundle.class)),
+ new Throwable());
+ try {
+ e.installedBundles().add(Mockito.mock(Bundle.class));
+ fail();
+ } catch (UnsupportedOperationException f) {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerBuilderTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerBuilderTestCase.java
new file mode 100644
index 00000000000..811f8fa901b
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerBuilderTestCase.java
@@ -0,0 +1,116 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerBuilderTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() throws URISyntaxException {
+ final Object obj = new Object();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(Object.class).toInstance(obj);
+ bind(String.class).annotatedWith(Names.named("foo")).toInstance("foo");
+ }
+ });
+ ContainerBuilder builder = driver.newContainerBuilder();
+ assertSame(obj, builder.getInstance(Object.class));
+ assertEquals("foo", builder.getInstance(Key.get(String.class, Names.named("foo"))));
+
+ Object ctx = new Object();
+ builder.setAppContext(ctx);
+ assertSame(ctx, builder.appContext());
+
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatContainerThreadFactoryIsBound() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ assertSame(ContainerThread.Factory.class, builder.getInstance(ThreadFactory.class).getClass());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatThreadFactoryCanBeReconfigured() {
+ final ThreadFactory factory = Executors.defaultThreadFactory();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.guiceModules().install(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(ThreadFactory.class).toInstance(factory);
+ }
+ });
+ assertSame(factory, builder.getInstance(ThreadFactory.class));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatBindingSetsAreCreatedOnDemand() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ BindingRepository repo = builder.serverBindings("foo");
+ assertNotNull(repo);
+ assertSame(repo, builder.serverBindings("foo"));
+ assertNotNull(repo = builder.serverBindings("bar"));
+ assertSame(repo, builder.serverBindings("bar"));
+ assertNotNull(repo = builder.clientBindings("baz"));
+ assertSame(repo, builder.clientBindings("baz"));
+ assertNotNull(repo = builder.clientBindings("cox"));
+ assertSame(repo, builder.clientBindings("cox"));
+ driver.close();
+ }
+
+ @Test
+ public void requireThatSafeClassCastWorks() {
+ ContainerBuilder.safeClassCast(Integer.class, Integer.class);
+ }
+
+ @Test
+ public void requireThatSafeClassCastThrowsIllegalArgument() {
+ try {
+ ContainerBuilder.safeClassCast(Integer.class, Double.class);
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatSafeStringSplitWorks() {
+ assertTrue(ContainerBuilder.safeStringSplit(new Object(), ",").isEmpty());
+ assertTrue(ContainerBuilder.safeStringSplit("", ",").isEmpty());
+ assertTrue(ContainerBuilder.safeStringSplit(" \f\n\r\t", ",").isEmpty());
+ assertEquals(Arrays.asList("foo"), ContainerBuilder.safeStringSplit("foo", ","));
+ assertEquals(Arrays.asList("foo"), ContainerBuilder.safeStringSplit(" foo", ","));
+ assertEquals(Arrays.asList("foo"), ContainerBuilder.safeStringSplit("foo ", ","));
+ assertEquals(Arrays.asList("foo"), ContainerBuilder.safeStringSplit("foo, ", ","));
+ assertEquals(Arrays.asList("foo"), ContainerBuilder.safeStringSplit("foo ,", ","));
+ assertEquals(Arrays.asList("foo", "bar"), ContainerBuilder.safeStringSplit("foo, bar", ","));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerThreadTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerThreadTestCase.java
new file mode 100644
index 00000000000..d92512b3650
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ContainerThreadTestCase.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.yahoo.jdisc.Metric;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertSame;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerThreadTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ MetricConsumer consumer = new MyConsumer();
+ ContainerThread thread = new ContainerThread(new MyTask(), consumer);
+ assertSame(consumer, thread.consumer());
+ }
+
+ @Test
+ public void requireThatTaskIsRun() throws InterruptedException {
+ MyTask task = new MyTask();
+ ContainerThread thread = new ContainerThread(task, null);
+ thread.start();
+ task.latch.await(600, TimeUnit.SECONDS);
+ }
+
+ private static class MyConsumer implements MetricConsumer {
+
+ @Override
+ public void set(String key, Number val, Metric.Context ctx) {
+
+ }
+
+ @Override
+ public void add(String key, Number val, Metric.Context ctx) {
+
+ }
+
+ @Override
+ public Metric.Context createContext(Map<String, ?> properties) {
+ return null;
+ }
+ }
+
+ private static class MyTask implements Runnable {
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void run() {
+ latch.countDown();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/GlobPatternTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/GlobPatternTestCase.java
new file mode 100644
index 00000000000..c9b4650e572
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/GlobPatternTestCase.java
@@ -0,0 +1,157 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class GlobPatternTestCase {
+
+ @Test
+ public void requireThatCompileCreatesExpectedParts() {
+ assertToString("foo");
+ assertToString("*foo");
+ assertToString("*oo");
+ assertToString("f*o");
+ assertToString("fo*");
+ assertToString("foo*");
+ assertToString("**foo");
+ assertToString("**oo");
+ assertToString("**o");
+ assertToString("f**");
+ assertToString("fo**");
+ assertToString("foo**");
+ assertToString("");
+ assertToString("*");
+ }
+
+ @Test
+ public void requireThatGlobMatcherWorks() {
+ assertMatch("foo", "foo", Collections.<String>emptyList());
+ assertNotMatch("foo", "bar");
+
+ assertMatch("*", "foo", Arrays.asList("foo"));
+ assertMatch("*", "bar", Arrays.asList("bar"));
+
+ assertMatch("*foo", "foo", Arrays.asList(""));
+ assertMatch("*oo", "foo", Arrays.asList("f"));
+ assertMatch("f*o", "foo", Arrays.asList("o"));
+ assertMatch("fo*", "foo", Arrays.asList("o"));
+ assertMatch("foo*", "foo", Arrays.asList(""));
+
+ assertNotMatch("*foo", "bar");
+ assertNotMatch("*oo", "bar");
+ assertNotMatch("f*o", "bar");
+ assertNotMatch("fo*", "bar");
+ assertNotMatch("foo*", "bar");
+
+ assertMatch("**foo", "foo", Arrays.asList("", ""));
+ assertMatch("**oo", "foo", Arrays.asList("", "f"));
+ assertMatch("f**o", "foo", Arrays.asList("", "o"));
+ assertMatch("fo**", "foo", Arrays.asList("", "o"));
+ assertMatch("foo**", "foo", Arrays.asList("", ""));
+
+ assertNotMatch("**foo", "bar");
+ assertNotMatch("**oo", "bar");
+ assertNotMatch("f**o", "bar");
+ assertNotMatch("fo**", "bar");
+ assertNotMatch("foo**", "bar");
+
+ assertMatch("foo bar", "foo bar", Collections.<String>emptyList());
+ assertMatch("*foo *bar", "foo bar", Arrays.asList("", ""));
+ assertMatch("foo* bar*", "foo bar", Arrays.asList("", ""));
+ assertMatch("f* *r", "foo bar", Arrays.asList("oo", "ba"));
+
+ assertNotMatch("foo bar", "baz cox");
+ assertNotMatch("*foo *bar", "baz cox");
+ assertNotMatch("foo* bar*", "baz cox");
+ assertNotMatch("f* *r", "baz cox");
+ }
+
+ @Test
+ public void requireThatGlobPatternOrdersMoreSpecificFirst() {
+ assertCompareEq("foo", "foo");
+ assertCompareLt("foo", "foo*");
+ assertCompareLt("foo", "*foo");
+
+ assertCompareEq("foo/bar", "foo/bar");
+ assertCompareLt("foo/bar", "foo");
+ assertCompareLt("foo/bar", "foo*");
+ assertCompareLt("foo/bar", "*foo");
+
+ assertCompareLt("foo/bar", "foo*bar");
+ assertCompareLt("foo/bar", "foo*bar*");
+ assertCompareLt("foo/bar", "*foo*bar");
+
+ assertCompareLt("foo*bar", "foo");
+ assertCompareLt("foo*bar", "foo*");
+ assertCompareLt("foo*bar", "*foo");
+
+ assertCompareLt("foo", "foo*bar*");
+ assertCompareLt("foo*bar*", "foo*");
+ assertCompareLt("*foo", "foo*bar*");
+
+ assertCompareLt("foo", "*foo*bar");
+ assertCompareLt("*foo*bar", "foo*");
+ assertCompareLt("*foo*bar", "*foo");
+
+ assertCompareLt("*/3/2", "*/1/2");
+ assertCompareLt("*/1/2", "*/2/*");
+ }
+
+ @Test
+ public void requireThatEqualsIsImplemented() {
+ assertTrue(GlobPattern.compile("foo").equals(GlobPattern.compile("foo")));
+ assertFalse(GlobPattern.compile("foo").equals(GlobPattern.compile("bar")));
+ }
+
+ @Test
+ public void requireThatHashCodeIsImplemented() {
+ assertTrue(GlobPattern.compile("foo").hashCode() == GlobPattern.compile("foo").hashCode());
+ assertFalse(GlobPattern.compile("foo").hashCode() == GlobPattern.compile("bar").hashCode());
+ }
+
+ private static void assertCompareLt(String lhs, String rhs) {
+ assertTrue(compare(lhs, rhs) < 0);
+ assertTrue(compare(rhs, lhs) > 0);
+ }
+
+ private static void assertCompareEq(String lhs, String rhs) {
+ assertEquals(0, compare(lhs, rhs));
+ assertEquals(0, compare(rhs, lhs));
+ }
+
+ private static int compare(String lhs, String rhs) {
+ return GlobPattern.compile(lhs).compareTo(GlobPattern.compile(rhs));
+ }
+
+ private static void assertMatch(String glob, String str, List<String> expected) {
+ GlobPattern.Match match = GlobPattern.match(glob, str);
+ assertNotNull(match);
+ List<String> actual = new ArrayList<>(match.groupCount());
+ for (int i = 0, len = match.groupCount(); i < len; ++i) {
+ actual.add(match.group(i));
+ }
+ assertEquals(expected, actual);
+ }
+
+ private static void assertNotMatch(String glob, String str) {
+ assertNull(GlobPattern.match(glob, str));
+ }
+
+ private static void assertToString(String pattern) {
+ assertEquals(pattern, GlobPattern.compile(pattern).toString());
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/GuiceRepositoryTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/GuiceRepositoryTestCase.java
new file mode 100644
index 00000000000..39981a812b5
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/GuiceRepositoryTestCase.java
@@ -0,0 +1,197 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.ConfigurationException;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.PrivateModule;
+import com.google.inject.name.Named;
+import com.google.inject.name.Names;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GuiceRepositoryTestCase {
+
+ @Test
+ public void requireThatInstallWorks() {
+ GuiceRepository guice = new GuiceRepository();
+ StringBinding module = new StringBinding("fooKey", "fooVal");
+ guice.install(module);
+ assertBinding(guice, "fooKey", "fooVal");
+
+ Iterator<Module> it = guice.iterator();
+ assertTrue(it.hasNext());
+ assertSame(module, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatInstallAllWorks() {
+ GuiceRepository guice = new GuiceRepository();
+ StringBinding foo = new StringBinding("fooKey", "fooVal");
+ StringBinding bar = new StringBinding("barKey", "barVal");
+ guice.installAll(Arrays.asList(foo, bar));
+ assertBinding(guice, "fooKey", "fooVal");
+ assertBinding(guice, "barKey", "barVal");
+
+ Iterator<Module> it = guice.iterator();
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatUninstallWorks() {
+ GuiceRepository guice = new GuiceRepository();
+ StringBinding module = new StringBinding("fooKey", "fooVal");
+ guice.install(module);
+ assertBinding(guice, "fooKey", "fooVal");
+
+ guice.uninstall(module);
+ assertNoBinding(guice, "fooKey");
+ assertFalse(guice.iterator().hasNext());
+ }
+
+ @Test
+ public void requireThatUninstallAllWorks() {
+ GuiceRepository guice = new GuiceRepository();
+ StringBinding foo = new StringBinding("fooKey", "fooVal");
+ StringBinding bar = new StringBinding("barKey", "barVal");
+ StringBinding baz = new StringBinding("bazKey", "bazVal");
+ guice.installAll(Arrays.asList(foo, bar, baz));
+ assertBinding(guice, "fooKey", "fooVal");
+ assertBinding(guice, "barKey", "barVal");
+ assertBinding(guice, "bazKey", "bazVal");
+
+ guice.uninstallAll(Arrays.asList(foo, baz));
+ assertNoBinding(guice, "fooKey");
+ assertBinding(guice, "barKey", "barVal");
+ assertNoBinding(guice, "bazKey");
+
+ Iterator<Module> it = guice.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatBindingsCanBeOverridden() {
+ GuiceRepository guice = new GuiceRepository();
+ guice.install(new StringBinding("fooKey", "fooVal1"));
+ assertBinding(guice, "fooKey", "fooVal1");
+ guice.install(new StringBinding("fooKey", "fooVal2"));
+ assertBinding(guice, "fooKey", "fooVal2");
+ }
+
+ @Test
+ public void requireThatModulesAreOnlyEvaluatedOnce() {
+ GuiceRepository guice = new GuiceRepository();
+ EvalCounter foo = new EvalCounter();
+ EvalCounter bar = new EvalCounter();
+ assertEquals(0, foo.cnt);
+ assertEquals(0, bar.cnt);
+ guice.install(foo);
+ assertEquals(1, foo.cnt);
+ assertEquals(0, bar.cnt);
+ guice.install(bar);
+ assertEquals(1, foo.cnt);
+ assertEquals(1, bar.cnt);
+ }
+
+ @Test
+ public void requireThatPrivateModulesWorks() {
+ GuiceRepository guice = new GuiceRepository();
+
+ List<Named> names = Arrays.asList(Names.named("A"), Names.named("B"));
+
+ for (Named name: names) {
+ guice.install(createPrivateInjectNameModule(name));
+ }
+
+ Injector injector = guice.getInjector();
+
+ for (Named name: names) {
+ NameHolder nameHolder = injector.getInstance(Key.get(NameHolder.class, name));
+ assertEquals(name, nameHolder.name);
+ }
+ }
+
+ private Module createPrivateInjectNameModule(final Named name) {
+ return new PrivateModule() {
+ @Override
+ protected void configure() {
+ bind(NameHolder.class).annotatedWith(name).to(NameHolder.class);
+ expose(NameHolder.class).annotatedWith(name);
+ bind(Named.class).toInstance(name);
+ }
+ };
+ }
+
+ private static void assertBinding(GuiceRepository guice, String name, String expected) {
+ assertEquals(expected, guice.getInjector().getInstance(Key.get(String.class, Names.named(name))));
+ }
+
+ private static void assertNoBinding(GuiceRepository guice, String name) {
+ try {
+ guice.getInjector().getInstance(Key.get(String.class, Names.named(name)));
+ fail();
+ } catch (ConfigurationException e) {
+
+ }
+ }
+
+ private static class EvalCounter extends AbstractModule {
+
+ int cnt = 0;
+
+ @Override
+ protected void configure() {
+ ++cnt;
+ }
+ }
+
+ private static class StringBinding extends AbstractModule {
+
+ final String name;
+ final String val;
+
+ StringBinding(String name, String val) {
+ this.name = name;
+ this.val = val;
+ }
+
+ @Override
+ protected void configure() {
+ bind(String.class).annotatedWith(Names.named(name)).toInstance(val);
+ }
+ }
+
+ public static final class NameHolder {
+ public final Named name;
+
+ @Inject
+ public NameHolder(Named name) {
+ this.name = name;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/MetricImplTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/MetricImplTestCase.java
new file mode 100644
index 00000000000..84ee425f55f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/MetricImplTestCase.java
@@ -0,0 +1,150 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.yahoo.jdisc.Metric;
+import org.junit.Test;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MetricImplTestCase {
+
+ @Test
+ public void requireThatClassIsInjectedByDefault() {
+ Metric metric = Guice.createInjector().getInstance(Metric.class);
+ assertTrue(metric instanceof MetricImpl);
+ }
+
+ @Test
+ public void requireThatConsumerIsOptional() {
+ Injector injector = Guice.createInjector();
+ Metric metric = injector.getInstance(Metric.class);
+ metric.set("foo", 6, null);
+ metric.add("foo", 9, null);
+ }
+
+ @Test
+ public void requireThatConsumerIsCalled() throws InterruptedException {
+ final MyConsumer consumer = new MyConsumer();
+ Injector injector = Guice.createInjector(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(MetricConsumer.class).toInstance(consumer);
+ }
+ });
+ Metric metric = injector.getInstance(Metric.class);
+ metric.set("foo", 6, null);
+ assertEquals(6, consumer.map.get("foo").intValue());
+ metric.add("foo", 9, null);
+ assertEquals(15, consumer.map.get("foo").intValue());
+ Metric.Context ctx = metric.createContext(null);
+ assertEquals(consumer.ctx, ctx);
+ }
+
+ @Test
+ public void requireThatWorkerMetricHasPrecedence() throws InterruptedException {
+ final MyConsumer globalConsumer = new MyConsumer();
+ Injector injector = Guice.createInjector(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(MetricConsumer.class).toInstance(globalConsumer);
+ }
+ });
+ Metric metric = injector.getInstance(Metric.class);
+
+ MyConsumer localConsumer = new MyConsumer();
+ localConsumer.latchRef.set(new CountDownLatch(1));
+ new ContainerThread(new SetTask(metric, "foo", 6), localConsumer).start();
+ localConsumer.latchRef.get().await(600, TimeUnit.SECONDS);
+ assertEquals(6, localConsumer.map.get("foo").intValue());
+ assertTrue(globalConsumer.map.isEmpty());
+
+ localConsumer.latchRef.set(new CountDownLatch(1));
+ new ContainerThread(new AddTask(metric, "foo", 9), localConsumer).start();
+ localConsumer.latchRef.get().await(600, TimeUnit.SECONDS);
+ assertEquals(15, localConsumer.map.get("foo").intValue());
+ assertTrue(globalConsumer.map.isEmpty());
+ }
+
+ private static class SetTask implements Runnable {
+
+ final Metric metric;
+ final String key;
+ final Number val;
+
+ public SetTask(Metric metric, String key, Number val) {
+ this.metric = metric;
+ this.key = key;
+ this.val = val;
+ }
+
+ @Override
+ public void run() {
+ metric.set(key, val, null);
+ }
+ }
+
+ private static class AddTask implements Runnable {
+
+ final Metric metric;
+ final String key;
+ final Number val;
+
+ public AddTask(Metric metric, String key, Number val) {
+ this.metric = metric;
+ this.key = key;
+ this.val = val;
+ }
+
+ @Override
+ public void run() {
+ metric.add(key, val, null);
+ }
+ }
+
+ private static class MyConsumer implements MetricConsumer {
+
+ final ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
+ final AtomicReference<CountDownLatch> latchRef = new AtomicReference<>();
+ final Metric.Context ctx = new Metric.Context() { };
+
+ @Override
+ public void set(String key, Number val, Metric.Context ctx) {
+ map.put(key, val.intValue());
+ CountDownLatch latch = latchRef.get();
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void add(String key, Number val, Metric.Context ctx) {
+ map.put(key, map.get(key) + val.intValue());
+ CountDownLatch latch = this.latchRef.get();
+ if (latch != null) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public Metric.Context createContext(Map<String, ?> properties) {
+ return ctx;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiHeaderTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiHeaderTestCase.java
new file mode 100644
index 00000000000..2d167aa08b6
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiHeaderTestCase.java
@@ -0,0 +1,20 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OsgiHeaderTestCase {
+
+ @Test
+ public void requireThatOsgiHeadersDoNotChange() {
+ assertEquals("X-JDisc-Application", OsgiHeader.APPLICATION);
+ assertEquals("X-JDisc-Preinstall-Bundle", OsgiHeader.PREINSTALL_BUNDLE);
+ assertEquals("X-JDisc-Privileged-Activator", OsgiHeader.PRIVILEGED_ACTIVATOR);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiRepositoryTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiRepositoryTestCase.java
new file mode 100644
index 00000000000..2da63190616
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/OsgiRepositoryTestCase.java
@@ -0,0 +1,18 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OsgiRepositoryTestCase {
+
+ @Test
+ public void requireNothingSinceIntegrationModuleTestsThis() {
+ assertTrue(true);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/ResourcePoolTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ResourcePoolTestCase.java
new file mode 100644
index 00000000000..ed113572bb7
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ResourcePoolTestCase.java
@@ -0,0 +1,168 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Key;
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.ResourceReference;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ResourcePoolTestCase {
+
+ @Test
+ public void requireThatAddReturnsArgument() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyResource foo = new MyResource();
+ assertSame(foo, new ResourcePool(driver.newContainerBuilder()).add(foo));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatAddDoesNotRetainArgument() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyResource foo = new MyResource();
+ assertEquals(1, foo.retainCount());
+ new ResourcePool(driver.newContainerBuilder()).add(foo);
+ assertEquals(1, foo.retainCount());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatAddCanBeUsedWithoutContainerBuilder() {
+ new ResourcePool().add(new MyResource());
+ }
+
+ @Test
+ public void requireThatRetainReturnsArgument() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyResource foo = new MyResource();
+ assertSame(foo, new ResourcePool(driver.newContainerBuilder()).retain(foo));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatRetainRetainsArgument() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyResource foo = new MyResource();
+ assertEquals(1, foo.retainCount());
+ new ResourcePool(driver.newContainerBuilder()).retain(foo);
+ assertEquals(2, foo.retainCount());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatRetainCanBeUsedWithoutContainerBuilder() {
+ new ResourcePool().retain(new MyResource());
+ }
+
+ @Test
+ public void requireThatGetReturnsBoundInstance() {
+ final MyResource foo = new MyResource();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(MyResource.class).toInstance(foo);
+ }
+ });
+ ResourcePool pool = new ResourcePool(driver.newContainerBuilder());
+ assertSame(foo, pool.get(MyResource.class));
+ assertSame(foo, pool.get(Key.get(MyResource.class)));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatGetDoesNotRetainArgument() {
+ final MyResource foo = new MyResource();
+ assertEquals(1, foo.retainCount());
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(MyResource.class).toInstance(foo);
+ }
+ });
+ ResourcePool pool = new ResourcePool(driver.newContainerBuilder());
+ pool.get(MyResource.class);
+ assertEquals(1, foo.retainCount());
+ pool.get(Key.get(MyResource.class));
+ assertEquals(1, foo.retainCount());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatGetCanNotBeUsedWithoutContainerBuilder() {
+ ResourcePool pool = new ResourcePool();
+ try {
+ pool.get(MyResource.class);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ try {
+ pool.get(Key.get(MyResource.class));
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatResourcesAreReleasedOnDestroy() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+
+ ResourcePool pool = new ResourcePool(driver.newContainerBuilder());
+ MyResource foo = pool.add(new MyResource());
+ MyResource bar = pool.add(new MyResource());
+ MyResource baz = pool.add(new MyResource());
+ assertEquals(1, pool.retainCount());
+ assertEquals(1, foo.retainCount());
+ assertEquals(1, bar.retainCount());
+ assertEquals(1, baz.retainCount());
+
+ final ResourceReference secondPoolReference = pool.refer();
+ assertEquals(2, pool.retainCount());
+ assertEquals(1, foo.retainCount());
+ assertEquals(1, bar.retainCount());
+ assertEquals(1, baz.retainCount());
+
+ secondPoolReference.close();
+ assertEquals(1, pool.retainCount());
+ assertEquals(1, foo.retainCount());
+ assertEquals(1, bar.retainCount());
+ assertEquals(1, baz.retainCount());
+
+ pool.release();
+ assertEquals(0, pool.retainCount());
+ assertEquals(0, foo.retainCount());
+ assertEquals(0, bar.retainCount());
+ assertEquals(0, baz.retainCount());
+
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatAutoCloseCallsRelease() throws Exception {
+ MyResource foo = new MyResource();
+ assertEquals(1, foo.retainCount());
+ try (ResourcePool pool = new ResourcePool()) {
+ pool.retain(foo);
+ assertEquals(2, foo.retainCount());
+ }
+ assertEquals(1, foo.retainCount());
+ }
+
+ private static class MyResource extends AbstractResource {
+
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/ServerRepositoryTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ServerRepositoryTestCase.java
new file mode 100644
index 00000000000..6ce125ff590
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/ServerRepositoryTestCase.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.jdisc.application;
+
+import com.yahoo.jdisc.NoopSharedResource;
+import com.yahoo.jdisc.service.ServerProvider;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ServerRepositoryTestCase {
+
+ @Test
+ public void requireThatInstallWorks() {
+ ServerRepository servers = newServerRepository();
+ MyServer server = new MyServer();
+ servers.install(server);
+
+ Iterator<ServerProvider> it = servers.iterator();
+ assertTrue(it.hasNext());
+ assertSame(server, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatInstallAllWorks() {
+ ServerRepository servers = newServerRepository();
+ ServerProvider foo = new MyServer();
+ ServerProvider bar = new MyServer();
+ servers.installAll(Arrays.asList(foo, bar));
+
+ Iterator<ServerProvider> it = servers.iterator();
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ @Test
+ public void requireThatUninstallWorks() {
+ ServerRepository servers = newServerRepository();
+ ServerProvider server = new MyServer();
+ servers.install(server);
+ servers.uninstall(server);
+ assertFalse(servers.iterator().hasNext());
+ }
+
+ @Test
+ public void requireThatUninstallAllWorks() {
+ ServerRepository servers = newServerRepository();
+ ServerProvider foo = new MyServer();
+ ServerProvider bar = new MyServer();
+ ServerProvider baz = new MyServer();
+ servers.installAll(Arrays.asList(foo, bar, baz));
+ servers.uninstallAll(Arrays.asList(foo, bar));
+ Iterator<ServerProvider> it = servers.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ assertSame(baz, it.next());
+ assertFalse(it.hasNext());
+ }
+
+ private static ServerRepository newServerRepository() {
+ return new ServerRepository(new GuiceRepository());
+ }
+
+ private static class MyServer extends NoopSharedResource implements ServerProvider {
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/application/UriPatternTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/application/UriPatternTestCase.java
new file mode 100644
index 00000000000..c7c45be481a
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/application/UriPatternTestCase.java
@@ -0,0 +1,342 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.application;
+
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class UriPatternTestCase {
+
+ private static final List<String> NO_GROUPS = Collections.emptyList();
+
+ @Test
+ public void requireThatIllegalPatternsAreDetected() {
+ assertIllegalPattern("scheme");
+ assertIllegalPattern("scheme://");
+ assertIllegalPattern("scheme://host");
+ assertIllegalPattern("scheme://host:0");
+ assertIllegalPattern("scheme://host:69");
+ assertIllegalPattern("scheme://host:-69");
+ assertIllegalPattern("scheme://host:6*/");
+ assertIllegalPattern("scheme://host:6*9/");
+ assertIllegalPattern("scheme://host:*9/");
+ }
+
+ @Test
+ public void requireThatNoPortImpliesWildcard() {
+ assertEquals(new UriPattern("scheme://host/path"),
+ new UriPattern("scheme://host:*/path"));
+ }
+
+ @Test
+ public void requireThatPatternMatches() {
+ // scheme matching
+ UriPattern pattern = new UriPattern("bar://host:69/path");
+ assertNotMatch(pattern, "foobar://host:69/path");
+ assertMatch(pattern, "bar://host:69/path", NO_GROUPS);
+ assertNotMatch(pattern, "barbaz://host:69/path");
+
+ pattern = new UriPattern("*://host:69/path");
+ assertMatch(pattern, "foobar://host:69/path", Arrays.asList("foobar"));
+ assertMatch(pattern, "bar://host:69/path", Arrays.asList("bar"));
+ assertMatch(pattern, "barbaz://host:69/path", Arrays.asList("barbaz"));
+
+ pattern = new UriPattern("*bar://host:69/path");
+ assertMatch(pattern, "foobar://host:69/path", Arrays.asList("foo"));
+ assertMatch(pattern, "bar://host:69/path", Arrays.asList(""));
+ assertNotMatch(pattern, "barbaz://host:69/path");
+
+ pattern = new UriPattern("bar*://host:69/path");
+ assertNotMatch(pattern, "foobar://host:69/path");
+ assertMatch(pattern, "bar://host:69/path", Arrays.asList(""));
+ assertMatch(pattern, "barbaz://host:69/path", Arrays.asList("baz"));
+
+ // host matching
+ pattern = new UriPattern("scheme://bar:69/path");
+ assertNotMatch(pattern, "scheme://foobar:69/path");
+ assertMatch(pattern, "scheme://bar:69/path", NO_GROUPS);
+ assertNotMatch(pattern, "scheme://barbaz:69/path");
+
+ pattern = new UriPattern("scheme://*:69/path");
+ assertMatch(pattern, "scheme://foobar:69/path", Arrays.asList("foobar"));
+ assertMatch(pattern, "scheme://bar:69/path", Arrays.asList("bar"));
+ assertMatch(pattern, "scheme://barbaz:69/path", Arrays.asList("barbaz"));
+
+ pattern = new UriPattern("scheme://*bar:69/path");
+ assertMatch(pattern, "scheme://foobar:69/path", Arrays.asList("foo"));
+ assertMatch(pattern, "scheme://bar:69/path", Arrays.asList(""));
+ assertNotMatch(pattern, "scheme://barbaz:69/path");
+
+ pattern = new UriPattern("scheme://bar*:69/path");
+ assertNotMatch(pattern, "scheme://foobar:69/path");
+ assertMatch(pattern, "scheme://bar:69/path", Arrays.asList(""));
+ assertMatch(pattern, "scheme://barbaz:69/path", Arrays.asList("baz"));
+
+ // port matching
+ pattern = new UriPattern("scheme://host:69/path");
+ assertNotMatch(pattern, "scheme://host:669/path");
+ assertMatch(pattern, "scheme://host:69/path", NO_GROUPS);
+ assertNotMatch(pattern, "scheme://host:699/path");
+
+ pattern = new UriPattern("scheme://host:*/path");
+ assertMatch(pattern, "scheme://host:669/path", Arrays.asList("669"));
+ assertMatch(pattern, "scheme://host:69/path", Arrays.asList("69"));
+ assertMatch(pattern, "scheme://host:699/path", Arrays.asList("699"));
+
+ // path matching
+ pattern = new UriPattern("scheme://host:69/");
+ assertMatch(pattern, "scheme://host:69/", NO_GROUPS);
+ assertNotMatch(pattern, "scheme://host:69/foo");
+
+ pattern = new UriPattern("scheme://host:69/bar");
+ assertNotMatch(pattern, "scheme://host:69/foobar");
+ assertMatch(pattern, "scheme://host:69/bar", NO_GROUPS);
+ assertNotMatch(pattern, "scheme://host:69/barbaz");
+
+ pattern = new UriPattern("scheme://host:69/*");
+ assertMatch(pattern, "scheme://host:69/", Arrays.asList(""));
+ assertMatch(pattern, "scheme://host:69/foobar", Arrays.asList("foobar"));
+ assertMatch(pattern, "scheme://host:69/bar", Arrays.asList("bar"));
+ assertMatch(pattern, "scheme://host:69/barbaz", Arrays.asList("barbaz"));
+
+ pattern = new UriPattern("scheme://host:69/*bar");
+ assertMatch(pattern, "scheme://host:69/foobar", Arrays.asList("foo"));
+ assertMatch(pattern, "scheme://host:69/bar", Arrays.asList(""));
+ assertNotMatch(pattern, "scheme://host:69/barbaz");
+
+ pattern = new UriPattern("scheme://host:69/bar*");
+ assertNotMatch(pattern, "scheme://host:69/foobar");
+ assertMatch(pattern, "scheme://host:69/bar", Arrays.asList(""));
+ assertMatch(pattern, "scheme://host:69/barbaz", Arrays.asList("baz"));
+ }
+
+ @Test
+ public void requireThatUriWithoutHostDoesNotThrowException() {
+ String schemeOnly = "scheme:schemeSpecificPart";
+ String schemeAndPath = "scheme:/path";
+ String pathOnly = "path";
+ String pathOnlyWithSlash = "/path";
+
+ UriPattern pattern = new UriPattern("scheme://host/path");
+ assertNotMatch(pattern, schemeOnly);
+ assertNotMatch(pattern, schemeAndPath);
+ assertNotMatch(pattern, pathOnly);
+ assertNotMatch(pattern, pathOnlyWithSlash);
+
+ pattern = new UriPattern("scheme*://host*/path*");
+ assertNotMatch(pattern, schemeOnly);
+ assertNotMatch(pattern, schemeAndPath);
+ assertNotMatch(pattern, pathOnly);
+ assertNotMatch(pattern, pathOnlyWithSlash);
+
+ pattern = new UriPattern("*://*/*");
+ assertMatch(pattern, schemeOnly, Arrays.asList("scheme", "", ""));
+ assertMatch(pattern, schemeAndPath, Arrays.asList("scheme", "", "path"));
+ assertMatch(pattern, pathOnly, Arrays.asList("", "", "path"));
+ assertMatch(pattern, pathOnlyWithSlash, Arrays.asList("", "", "path"));
+ }
+
+ @Test
+ public void requireThatUriWithoutPathDoesNotThrowException() {
+ UriPattern pattern = new UriPattern("scheme://host/path");
+ assertNotMatch(pattern, "scheme://host");
+
+ pattern = new UriPattern("scheme://host/*");
+ assertMatch(pattern, "scheme://host", Arrays.asList(""));
+ }
+
+ @Test
+ public void requireThatOnlySchemeHostPortAndPathIsMatched() {
+ UriPattern pattern = new UriPattern("scheme://host:69/path");
+ assertMatch(pattern, "scheme://host:69/path?foo", NO_GROUPS);
+ assertMatch(pattern, "scheme://host:69/path?foo#bar", NO_GROUPS);
+ }
+
+ @Test
+ public void requireThatHostSupportsWildcard() {
+ UriPattern pattern = new UriPattern("scheme://*.host/path");
+ assertMatch(pattern, "scheme://a.host/path", Arrays.asList("a"));
+ assertMatch(pattern, "scheme://a.b.host/path", Arrays.asList("a.b"));
+ }
+
+ @Test
+ public void requireThatPrioritiesAreOrderedDescending() {
+ assertCompareLt(new UriPattern("scheme://host:69/path", 1),
+ new UriPattern("scheme://host:69/path", 0));
+ }
+
+ @Test
+ public void requireThatPriorityOrdersBeforeScheme() {
+ assertCompareLt(new UriPattern("*://host:69/path", 1),
+ new UriPattern("scheme://host:69/path", 0));
+ }
+
+ @Test
+ public void requireThatSchemesAreOrdered() {
+ assertCompareLt("b://host:69/path",
+ "a://host:69/path");
+ }
+
+ @Test
+ public void requireThatSchemeOrdersBeforeHost() {
+ assertCompareLt("b://*:69/path",
+ "a://host:69/path");
+ }
+
+ @Test
+ public void requireThatHostsAreOrdered() {
+ assertCompareLt("scheme://b:69/path",
+ "scheme://a:69/path");
+ }
+
+ @Test
+ public void requireThatHostOrdersBeforePath() {
+ assertCompareLt("scheme://b:69/*",
+ "scheme://a:69/path");
+ }
+
+ @Test
+ public void requireThatPortsAreOrdered() {
+ for (int i = 1; i < 69; ++i) {
+ assertCompareEq("scheme://host:" + i + "/path",
+ "scheme://host:" + i + "/path");
+ assertCompareLt("scheme://host:" + (i + 1) + "/path",
+ "scheme://host:" + i + "/path");
+ assertCompareLt("scheme://host:" + i + "/path",
+ "scheme://host:*/path");
+ }
+ }
+
+ @Test
+ public void requireThatPathsAreOrdered() {
+ assertCompareLt("scheme://host:69/b",
+ "scheme://host:69/a");
+ }
+
+ @Test
+ public void requireThatPathOrdersBeforePort() {
+ assertCompareLt("scheme://host:*/b",
+ "scheme://host:69/a");
+ }
+
+ @Test
+ public void requireThatEqualPatternsOrderEqual() {
+ assertCompareEq("scheme://host:69/path",
+ "scheme://host:69/path");
+ assertCompareEq("*://host:69/path",
+ "*://host:69/path");
+ assertCompareEq("scheme://*:69/path",
+ "scheme://*:69/path");
+ assertCompareEq("scheme://host:*/path",
+ "scheme://host:*/path");
+ assertCompareEq("scheme://host:69/*",
+ "scheme://host:69/*");
+ }
+
+ @Test
+ public void requireThatStrictPatternsOrderBeforeWildcards() {
+ assertCompareLt("scheme://host:69/path",
+ "*://host:69/path");
+ assertCompareLt("scheme://a:69/path",
+ "scheme://*:69/path");
+ assertCompareLt("scheme://a:69/path",
+ "scheme://*a:69/path");
+ assertCompareLt("scheme://*aa:69/path",
+ "scheme://*a:69/path");
+ assertCompareLt("scheme://host:69/path",
+ "scheme://host:*/path");
+ assertCompareLt("scheme://host:69/a",
+ "scheme://host:69/*");
+ assertCompareLt("scheme://host:69/a",
+ "scheme://host:69/a*");
+ assertCompareLt("scheme://host:69/aa*",
+ "scheme://host:69/a*");
+ assertCompareLt("scheme://*:69/path",
+ "*://host:69/path");
+ assertCompareLt("scheme://host:*/path",
+ "scheme://*:69/path");
+ assertCompareLt("scheme://host:*/path",
+ "scheme://host:69/*");
+ assertCompareLt("scheme://host:69/foo",
+ "scheme://host:69/*");
+ assertCompareLt("scheme://host:69/foo/bar",
+ "scheme://host:69/foo/*");
+ assertCompareLt("scheme://host:69/foo/bar/baz",
+ "scheme://host:69/foo/bar/*");
+ }
+
+ @Test
+ public void requireThatLongPatternsOrderBeforeShort() {
+ assertCompareLt("scheme://host:69/foo/bar",
+ "scheme://host:69/foo");
+ assertCompareLt("scheme://host:69/foo/bar/baz",
+ "scheme://host:69/foo/bar");
+ }
+
+ private static void assertIllegalPattern(String uri) {
+ try {
+ new UriPattern(uri);
+ fail();
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ private static void assertCompareLt(String lhs, String rhs) {
+ assertCompareLt(new UriPattern(lhs, 0), new UriPattern(rhs, 0));
+ }
+
+ private static void assertCompareLt(UriPattern lhs, UriPattern rhs) {
+ assertEquals(-1, compare(lhs, rhs));
+ }
+
+ private static void assertCompareEq(String lhs, String rhs) {
+ assertCompareEq(new UriPattern(lhs, 0), new UriPattern(rhs, 0));
+ }
+
+ private static void assertCompareEq(UriPattern lhs, UriPattern rhs) {
+ assertEquals(0, compare(lhs, rhs));
+ }
+
+ private static int compare(UriPattern lhs, UriPattern rhs) {
+ int lhsCmp = lhs.compareTo(rhs);
+ int rhsCmp = rhs.compareTo(lhs);
+ if (lhsCmp < 0) {
+ assertTrue(rhsCmp > 0);
+ return -1;
+ }
+ if (lhsCmp > 0) {
+ assertTrue(rhsCmp < 0);
+ return 1;
+ }
+ assertTrue(rhsCmp == 0);
+ return 0;
+ }
+
+ private static void assertMatch(UriPattern pattern, String uri, List<String> expected) {
+ UriPattern.Match match = pattern.match(URI.create(uri));
+ assertNotNull(match);
+ List<String> actual = new ArrayList<>(match.groupCount());
+ for (int i = 0, len = match.groupCount(); i < len; ++i) {
+ actual.add(match.group(i));
+ }
+ assertEquals(expected, actual);
+ }
+
+ private static void assertNotMatch(UriPattern pattern, String uri) {
+ assertNull(pattern.match(URI.create(uri)));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/BindingMatchingTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/BindingMatchingTestCase.java
new file mode 100644
index 00000000000..7c858a38b80
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/BindingMatchingTestCase.java
@@ -0,0 +1,126 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.benchmark;
+
+import com.yahoo.jdisc.application.BindingRepository;
+import com.yahoo.jdisc.application.BindingSet;
+import com.yahoo.jdisc.application.UriPattern;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class BindingMatchingTestCase {
+
+ private static final int NUM_CANDIDATES = 1024;
+ private static final int NUM_MATCHES = 100;
+ private static final int MIN_THREADS = 1;
+ private static final int MAX_THREADS = 64;
+ private static final Random random = new Random();
+ private static final ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS);
+
+ @Test
+ public void runThroughtputMeasurements() throws Exception {
+ System.err.format("%15s%15s%15s%15s%15s%15s%15s%15s\n",
+ "No. of Bindings", "1 thread", "2 thread", "4 thread", "8 thread", "16 thread", "32 thread", "64 thread");
+ for (int numBindings : Arrays.asList(1, 10, 25, 50, 100, 250)) {
+ BindingRepository<Object> repo = new BindingRepository<>();
+ for (int binding = 0; binding < numBindings; ++binding) {
+ repo.bind("http://*/v" + binding + "/*/data/", new Object());
+ }
+ System.err.format("%15s", numBindings + " binding(s)");
+
+ List<URI> candidates = newCandidates(repo);
+ measureThroughput(repo.activate(), candidates, MAX_THREADS); // warmup
+
+ BindingSet<Object> bindings = repo.activate();
+ for (int numThreads = MIN_THREADS;
+ numThreads <= MAX_THREADS;
+ numThreads *= 2)
+ {
+ System.err.format("%15s", measureThroughput(bindings, candidates, numThreads));
+ }
+ System.err.format("\n");
+ }
+ }
+
+ private static long measureThroughput(BindingSet<Object> bindings, List<URI> candidates, int numThreads) throws Exception {
+ List<MatchTask> tasks = new LinkedList<>();
+ for (int i = 0; i < numThreads; ++i) {
+ MatchTask task = new MatchTask(bindings, candidates);
+ tasks.add(task);
+ }
+ List<Future<Long>> results = executor.invokeAll(tasks);
+ long nanos = 0;
+ for (Future<Long> res : results) {
+ nanos = Math.max(nanos, res.get());
+ }
+ return (numThreads * NUM_MATCHES * TimeUnit.SECONDS.toNanos(1)) / nanos;
+ }
+
+ private List<URI> newCandidates(BindingRepository<Object> bindings) {
+ List<URI> lst = new ArrayList<>(NUM_CANDIDATES);
+ Iterator<Map.Entry<UriPattern, Object>> it = bindings.iterator();
+ for (int i = 0; i < NUM_CANDIDATES; ++i) {
+ if (!it.hasNext()) {
+ it = bindings.iterator();
+ }
+ lst.add(newCandidate(it.next().getKey()));
+ }
+ return lst;
+ }
+
+ private URI newCandidate(UriPattern key) {
+ String pattern = key.toString();
+ StringBuilder uri = new StringBuilder();
+ for (int i = 0, len = pattern.length(); i < len; ++i) {
+ char c = pattern.charAt(i);
+ if (c == '*') {
+ uri.append(random.nextInt(Integer.MAX_VALUE));
+ } else {
+ uri.append(c);
+ }
+ }
+ return URI.create(uri.toString());
+ }
+
+ private static class MatchTask implements Callable<Long> {
+
+ final BindingSet<Object> bindings;
+ final List<URI> candidates;
+
+ MatchTask(BindingSet<Object> bindings, List<URI> candidates) {
+ this.bindings = bindings;
+ this.candidates = candidates;
+ }
+
+ @Override
+ public Long call() throws Exception {
+ Iterator<URI> it = candidates.iterator();
+ for (int i = 0, len = random.nextInt(candidates.size()); i < len; ++i) {
+ it.next();
+ }
+ long time = System.nanoTime();
+ for (int i = 0; i < NUM_MATCHES; ++i) {
+ if (!it.hasNext()) {
+ it = candidates.iterator();
+ }
+ bindings.match(it.next());
+ }
+ return System.nanoTime() - time;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/LatencyTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/LatencyTestCase.java
new file mode 100644
index 00000000000..ec5d1d2f908
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/LatencyTestCase.java
@@ -0,0 +1,264 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.benchmark;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class LatencyTestCase {
+
+ private static final int NUM_REQUESTS = 100;
+
+ @Test
+ public void runLatencyMeasurements() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ MyRequestHandler foo = new MyRequestHandler("foo");
+ MyRequestHandler bar = new MyRequestHandler("bar");
+ MyRequestHandler baz = new MyRequestHandler("baz");
+ builder.serverBindings().bind(foo.uri, foo);
+ builder.serverBindings().bind(bar.uri, bar);
+ builder.serverBindings().bind(baz.uri, baz);
+ driver.activateContainer(builder);
+
+ measureLatencies(NUM_REQUESTS, driver, foo, bar, baz);
+ TimeTrack time = measureLatencies(NUM_REQUESTS, driver, foo, bar, baz);
+ System.err.println("\n" + time);
+
+ foo.release();
+ bar.release();
+ baz.release();
+ assertTrue(driver.close());
+ }
+
+ private static TimeTrack measureLatencies(int numRequests, CurrentContainer container,
+ MyRequestHandler... requestHandlers)
+ {
+ TimeTrack track = new TimeTrack();
+ Random rnd = new Random();
+ for (int i = 0; i < numRequests; ++i) {
+ track.add(measureLatency(container, requestHandlers[rnd.nextInt(requestHandlers.length)]));
+ }
+ return track;
+ }
+
+ private static TimeFrame measureLatency(CurrentContainer container, MyRequestHandler requestHandler) {
+ TimeFrame frame = new TimeFrame();
+
+ Request request = null;
+ ContentChannel requestContent = null;
+ MyResponseHandler responseHandler = new MyResponseHandler();
+ try {
+ URI uri = URI.create(requestHandler.uri);
+ request = new Request(container, uri);
+ frame.handleRequestBegin = System.nanoTime();
+ requestContent = request.connect(responseHandler);
+ frame.handleRequestEnd = requestHandler.handleTime;
+ } finally {
+ if (request != null) {
+ request.release();
+ }
+ }
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ MyCompletion requestWrite = new MyCompletion();
+ frame.requestWriteBegin = System.nanoTime();
+ requestContent.write(buf, requestWrite);
+ frame.requestWriteEnd = requestHandler.requestContent.writeTime;
+ frame.requestWriteCompletionBegin = System.nanoTime();
+ requestHandler.requestContent.writeCompletion.completed();
+ frame.requestWriteCompletionEnd = requestWrite.completedTime;
+
+ MyCompletion requestClose = new MyCompletion();
+ frame.requestCloseBegin = System.nanoTime();
+ requestContent.close(requestClose);
+ frame.requestCloseEnd = requestHandler.requestContent.closeTime;
+ frame.requestCloseCompletionBegin = System.nanoTime();
+ requestHandler.requestContent.closeCompletion.completed();
+ frame.requestCloseCompletionEnd = requestClose.completedTime;
+
+ Response response = new Response(Response.Status.OK);
+ frame.handleResponseBegin = System.nanoTime();
+ ContentChannel responseContent = requestHandler.responseHandler.handleResponse(response);
+ frame.handleResponseEnd = responseHandler.handleTime;
+ MyCompletion responseWrite = new MyCompletion();
+ frame.responseWriteBegin = System.nanoTime();
+ responseContent.write(buf, responseWrite);
+ frame.responseWriteEnd = responseHandler.responseContent.writeTime;
+ frame.responseWriteCompletionBegin = System.nanoTime();
+ responseHandler.responseContent.writeCompletion.completed();
+ frame.responseWriteCompletionEnd = responseWrite.completedTime;
+
+ MyCompletion responseClose = new MyCompletion();
+ frame.responseCloseBegin = System.nanoTime();
+ responseContent.close(responseClose);
+ frame.responseCloseEnd = responseHandler.responseContent.closeTime;
+ frame.responseCloseCompletionBegin = System.nanoTime();
+ responseHandler.responseContent.closeCompletion.completed();
+ frame.responseCloseCompletionEnd = responseClose.completedTime;
+
+ return frame;
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ final MyContent requestContent = new MyContent();
+ final String uri;
+ long handleTime;
+ Request request;
+ ResponseHandler responseHandler;
+
+ MyRequestHandler(String path) {
+ this.uri = "http://localhost/" + path;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ handleTime = System.nanoTime();
+ this.request = request;
+ responseHandler = handler;
+ return requestContent;
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final MyContent responseContent = new MyContent();
+ long handleTime;
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ handleTime = System.nanoTime();
+ return responseContent;
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ long writeTime;
+ long closeTime;
+ CompletionHandler writeCompletion;
+ CompletionHandler closeCompletion;
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ writeTime = System.nanoTime();
+ writeCompletion = handler;
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ closeTime = System.nanoTime();
+ closeCompletion = handler;
+ }
+ }
+
+ private static class MyCompletion implements CompletionHandler {
+
+ long completedTime;
+
+ @Override
+ public void completed() {
+ completedTime = System.nanoTime();
+ }
+
+ @Override
+ public void failed(Throwable t) {
+
+ }
+ }
+
+ private static class TimeFrame {
+
+ long handleRequestBegin;
+ long handleRequestEnd;
+ long requestWriteBegin;
+ long requestWriteEnd;
+ long requestWriteCompletionBegin;
+ long requestWriteCompletionEnd;
+ long requestCloseBegin;
+ long requestCloseEnd;
+ long requestCloseCompletionBegin;
+ long requestCloseCompletionEnd;
+ long handleResponseBegin;
+ long handleResponseEnd;
+ long responseWriteBegin;
+ long responseWriteEnd;
+ long responseWriteCompletionBegin;
+ long responseWriteCompletionEnd;
+ long responseCloseBegin;
+ long responseCloseEnd;
+ long responseCloseCompletionBegin;
+ long responseCloseCompletionEnd;
+ }
+
+ private static class TimeTrack {
+
+ long frameCnt = 0;
+ long handleRequest;
+ long requestWrite;
+ long requestWriteCompletion;
+ long requestClose;
+ long requestCloseCompletion;
+ long handleResponse;
+ long responseWrite;
+ long responseWriteCompletion;
+ long responseClose;
+ long responseCloseCompletion;
+
+ public void add(TimeFrame frame) {
+ ++frameCnt;
+ handleRequest += frame.handleRequestEnd - frame.handleRequestBegin;
+ requestWrite += frame.requestWriteEnd - frame.requestWriteBegin;
+ requestWriteCompletion += frame.requestWriteCompletionEnd - frame.requestWriteCompletionBegin;
+ requestClose += frame.requestCloseEnd - frame.requestCloseBegin;
+ requestCloseCompletion += frame.requestCloseCompletionEnd - frame.requestCloseCompletionBegin;
+ handleResponse += frame.handleResponseEnd - frame.handleResponseBegin;
+ responseWrite += frame.responseWriteEnd - frame.responseWriteBegin;
+ responseWriteCompletion += frame.responseWriteCompletionEnd - frame.responseWriteCompletionBegin;
+ responseClose += frame.responseCloseEnd - frame.responseCloseBegin;
+ responseCloseCompletion += frame.responseCloseCompletionEnd - frame.responseCloseCompletionBegin;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder();
+ ret.append("------------------------------------\n");
+ ret.append(String.format("HandleRequest : %10.2f\n", (double)handleRequest / frameCnt));
+ ret.append(String.format("RequestWrite : %10.2f\n", (double)requestWrite / frameCnt));
+ ret.append(String.format("RequestWriteCompletion : %10.2f\n", (double)requestWriteCompletion / frameCnt));
+ ret.append(String.format("RequestClose : %10.2f\n", (double)requestClose / frameCnt));
+ ret.append(String.format("RequestCloseCompletion : %10.2f\n", (double)requestCloseCompletion / frameCnt));
+ ret.append(String.format("HandleResponse : %10.2f\n", (double)handleResponse / frameCnt));
+ ret.append(String.format("ResponseWrite : %10.2f\n", (double)responseWrite / frameCnt));
+ ret.append(String.format("ResponseWriteCompletion : %10.2f\n", (double)responseWriteCompletion / frameCnt));
+ ret.append(String.format("ResponseClose : %10.2f\n", (double)responseClose / frameCnt));
+ ret.append(String.format("ResponseCloseCompletion : %10.2f\n", (double)responseCloseCompletion / frameCnt));
+ ret.append("------------------------------------\n");
+
+ double time = (handleRequest + requestWrite + requestWriteCompletion + requestClose +
+ requestCloseCompletion + handleResponse + responseWrite + responseWriteCompletion +
+ responseClose + responseCloseCompletion) / frameCnt;
+ ret.append(String.format("Total nanos : %10.2f\n", time));
+ ret.append(String.format("Requests per second : %10.2f\n", TimeUnit.SECONDS.toNanos(1) / time));
+ ret.append("------------------------------------\n");
+ return ret.toString();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/ThroughputTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/ThroughputTestCase.java
new file mode 100644
index 00000000000..54a94e3e2dd
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/ThroughputTestCase.java
@@ -0,0 +1,180 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.benchmark;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CallableResponseDispatch;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestDispatch;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseDispatch;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ThroughputTestCase {
+
+ private static final int NUM_REQUESTS = 100;
+ private static final int MIN_THREADS = 1;
+ private static final int MAX_THREADS = 64;
+ private static final int MIN_LOOPS = 0;
+ private static final int MAX_LOOPS = 1024;
+
+ private static final String HANDLER_URI = "http://localhost/";
+ private static final URI REQUEST_URI = URI.create(HANDLER_URI);
+ private static final ExecutorService executor = Executors.newFixedThreadPool(MAX_THREADS * 2);
+ private static long preventOptimization = 0;
+
+ @Test
+ public void runUnthreadedMeasurementsWithWorkload() throws Exception {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ runMeasurements(driver, new UnthreadedHandler(MAX_LOOPS)); // warmup
+
+ StringBuilder out = new StringBuilder();
+ out.append("\n");
+ out.append(" | ");
+ for (int i = MIN_THREADS; i <= MAX_THREADS; i *= 2) {
+ out.append(String.format("%10d", i));
+ }
+ out.append("\n");
+ out.append("------+-");
+ for (int i = MIN_THREADS; i <= MAX_THREADS; i *= 2) {
+ out.append("----------");
+ }
+ out.append("\n");
+ for (int i = MIN_LOOPS; i <= MAX_LOOPS; i = Math.max(1, i * 2)) {
+ out.append(String.format("%5d | ", i));
+ RequestHandler handler = new UnthreadedHandler(i);
+ for (Long val : runMeasurements(driver, handler)) {
+ out.append(String.format("%10d", val));
+ }
+ out.append("\n");
+ }
+ System.err.println(out);
+ System.err.println(preventOptimization);
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void runThreadedMeasurements() throws Exception {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ runMeasurements(driver, new ThreadedHandler()); // warmup
+
+ Iterator<Long> it = runMeasurements(driver, new ThreadedHandler()).iterator();
+ for (int numThreads = MIN_THREADS; numThreads <= MAX_THREADS; numThreads *= 2) {
+ System.err.println(String.format("%2d threads: %10d", numThreads, it.next()));
+ }
+ assertTrue(driver.close());
+ }
+
+ private static List<Long> runMeasurements(TestDriver driver, RequestHandler handler) throws Exception {
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind(HANDLER_URI, handler);
+ driver.activateContainer(builder);
+ handler.release();
+ List<Long> ret = new LinkedList<>();
+ for (int i = MIN_THREADS; i <= MAX_THREADS; i *= 2) {
+ ret.add(measureThroughput(driver, i));
+ }
+ return ret;
+ }
+
+ private static long measureThroughput(CurrentContainer container, int numThreads) throws Exception {
+ List<RequestTask> tasks = new LinkedList<>();
+ for (int i = 0; i < numThreads; ++i) {
+ RequestTask task = new RequestTask(container);
+ tasks.add(task);
+ }
+ List<Future<Long>> results = executor.invokeAll(tasks);
+ long nanos = 0;
+ for (Future<Long> res : results) {
+ nanos = Math.max(nanos, res.get());
+ }
+ return (numThreads * NUM_REQUESTS * TimeUnit.SECONDS.toNanos(1)) / nanos;
+ }
+
+ private static class RequestTask implements Callable<Long> {
+
+ final CurrentContainer container;
+
+ RequestTask(CurrentContainer container) {
+ this.container = container;
+ }
+
+ @Override
+ public Long call() throws Exception {
+ long time = System.nanoTime();
+ for (int i = 0; i < NUM_REQUESTS; ++i) {
+ new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ Request request = new Request(container, REQUEST_URI);
+ request.setTimeout(600, TimeUnit.SECONDS);
+ return request;
+ }
+ }.dispatch().get();
+ }
+ return System.nanoTime() - time;
+ }
+ }
+
+ private static class UnthreadedHandler extends AbstractRequestHandler {
+
+ final int numLoops;
+
+ UnthreadedHandler(int numLoops) {
+ this.numLoops = numLoops;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ ResponseDispatch.newInstance(Response.Status.OK).dispatch(handler);
+ preventOptimization += nextLong();
+ return null;
+ }
+
+ long nextLong() {
+ Random rnd = new Random();
+ int k = 0;
+ for (int i = 0; i < numLoops; ++i) {
+ k += rnd.nextInt();
+ }
+ return k;
+ }
+ }
+
+ private static class ThreadedHandler extends AbstractRequestHandler {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ executor.submit(new CallableResponseDispatch(handler) {
+
+ @Override
+ public Response newResponse() {
+ return new Response(Response.Status.OK);
+ }
+ });
+ return null;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/UriMatchingTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/UriMatchingTestCase.java
new file mode 100644
index 00000000000..df2402d1283
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/benchmark/UriMatchingTestCase.java
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.benchmark;
+
+import com.yahoo.jdisc.application.UriPattern;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class UriMatchingTestCase {
+
+ private static final int NUM_MATCHES = 100;
+ private static long preventOptimization = 0;
+
+ @Test
+ public void requireThatUriPatternMatchingIsFast() {
+ List<String> inputs = Arrays.asList(
+ "other://host/",
+ "scheme://other/",
+ "scheme://host/",
+ "scheme://host/foo",
+ "scheme://host/foo/bar",
+ "scheme://host/foo/bar/baz",
+ "scheme://host/other",
+ "scheme://host/other/bar",
+ "scheme://host/other/bar/baz",
+ "scheme://host/foo/other",
+ "scheme://host/foo/other/baz",
+ "scheme://host/foo/bar/other",
+ "scheme://host:69/",
+ "scheme://host:69/foo",
+ "scheme://host:69/foo/bar",
+ "scheme://host:69/foo/bar/baz",
+ "scheme://host:96/");
+ benchmarkMatch("*://*/*", inputs); // warmup
+
+ runBenchmark("*://*/*", inputs);
+ runBenchmark("scheme://*/*", inputs);
+ runBenchmark("scheme://host/*", inputs);
+ runBenchmark("scheme://host:69/*", inputs);
+ runBenchmark("scheme://host:69/foo", inputs);
+ runBenchmark("scheme://host:69/foo/bar", inputs);
+ runBenchmark("scheme://host:69/foo/bar/baz", inputs);
+ runBenchmark("*://host:69/foo/bar/baz", inputs);
+ runBenchmark("*://*/foo/*", inputs);
+ runBenchmark("*://*/foo/*/baz", inputs);
+ runBenchmark("*://*/foo/bar/*", inputs);
+ runBenchmark("*://*/foo/bar/baz", inputs);
+ runBenchmark("*://*/*/bar", inputs);
+ runBenchmark("*://*/*/bar/baz", inputs);
+ runBenchmark("*://*/*/*/baz", inputs);
+
+ System.out.println(">>>>> " + preventOptimization);
+ }
+
+ private static void runBenchmark(String pattern, List<String> inputs) {
+ System.out.format("%-30s %10d\n", pattern, benchmarkMatch(pattern, inputs));
+ }
+
+ private static long benchmarkMatch(String pattern, List<String> inputs) {
+ UriPattern compiled = new UriPattern(pattern);
+ List<URI> uriList = new ArrayList<>(inputs.size());
+ for (String input : inputs) {
+ uriList.add(URI.create(input));
+ }
+ long now = System.nanoTime();
+ for (int i = 0; i < NUM_MATCHES; ++i) {
+ for (URI uri : uriList) {
+ UriPattern.Match match = compiled.match(uri);
+ preventOptimization += match != null ? match.groupCount() : 1;
+ }
+ }
+ return TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - now);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/client/AbstractClientApplicationTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/client/AbstractClientApplicationTestCase.java
new file mode 100644
index 00000000000..3f04d86c170
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/client/AbstractClientApplicationTestCase.java
@@ -0,0 +1,138 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.client;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import com.yahoo.jdisc.application.BundleInstaller;
+import com.yahoo.jdisc.application.ContainerActivator;
+import com.yahoo.jdisc.service.CurrentContainer;
+import org.junit.Test;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class AbstractClientApplicationTestCase {
+
+ @Test
+ public void requireThatApplicationCanBeShutdown() throws Exception {
+ MyDriver driver = newDriver();
+ assertFalse(driver.awaitDone(100, TimeUnit.MILLISECONDS));
+ assertTrue(driver.awaitApp(600, TimeUnit.SECONDS));
+ driver.app.shutdown();
+ assertTrue(driver.app.isShutdown());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatShutdownCanBeWaitedForWithTimeout() throws Exception {
+ final MyDriver driver = newDriver();
+ assertFalse(driver.awaitDone(100, TimeUnit.MILLISECONDS));
+ assertTrue(driver.awaitApp(600, TimeUnit.SECONDS));
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Executors.newSingleThreadExecutor().submit(new Callable<Boolean>() {
+
+ @Override
+ public Boolean call() throws Exception {
+ driver.app.awaitShutdown(600, TimeUnit.SECONDS);
+ latch.countDown();
+ return Boolean.TRUE;
+ }
+ });
+ assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
+ driver.app.shutdown();
+ assertTrue(driver.close());
+ assertTrue(latch.await(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatShutdownCanBeWaitedForWithoutTimeout() throws Exception {
+ final MyDriver driver = newDriver();
+ assertFalse(driver.awaitDone(100, TimeUnit.MILLISECONDS));
+ assertTrue(driver.awaitApp(600, TimeUnit.SECONDS));
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ Executors.newSingleThreadExecutor().submit(new Callable<Boolean>() {
+
+ @Override
+ public Boolean call() throws Exception {
+ driver.app.awaitShutdown();
+ latch.countDown();
+ return Boolean.TRUE;
+ }
+ });
+ assertFalse(latch.await(100, TimeUnit.MILLISECONDS));
+ driver.app.shutdown();
+ assertTrue(driver.close());
+ assertTrue(latch.await(600, TimeUnit.SECONDS));
+ }
+
+ private static MyDriver newDriver() {
+ final MyDriver driver = new MyDriver();
+ driver.done = Executors.newSingleThreadExecutor().submit(new Callable<Boolean>() {
+
+ @Override
+ public Boolean call() throws Exception {
+ ClientDriver.runApplication(MyApplication.class, driver);
+ return Boolean.TRUE;
+ }
+ });
+ return driver;
+ }
+
+ private static class MyDriver extends AbstractModule {
+
+ final CountDownLatch appLatch = new CountDownLatch(1);
+ Future<Boolean> done;
+ MyApplication app;
+
+ @Override
+ protected void configure() {
+ bind(MyDriver.class).toInstance(this);
+ }
+
+ boolean awaitApp(int timeout, TimeUnit unit) throws InterruptedException {
+ return appLatch.await(timeout, unit);
+ }
+
+ boolean awaitDone(int timeout, TimeUnit unit) throws ExecutionException, InterruptedException {
+ try {
+ done.get(timeout, unit);
+ return app.isTerminated();
+ } catch (TimeoutException e) {
+ return false;
+ }
+ }
+
+ boolean close() throws ExecutionException, InterruptedException {
+ return awaitDone(600, TimeUnit.SECONDS);
+ }
+ }
+
+ private static class MyApplication extends AbstractClientApplication {
+
+ @Inject
+ MyApplication(BundleInstaller bundleInstaller, ContainerActivator activator,
+ CurrentContainer container, MyDriver driver) {
+ super(bundleInstaller, activator, container);
+ driver.app = this;
+ driver.appLatch.countDown();
+ }
+
+ @Override
+ public void start() {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/client/ClientDriverTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/client/ClientDriverTestCase.java
new file mode 100644
index 00000000000..bef78a22de7
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/client/ClientDriverTestCase.java
@@ -0,0 +1,78 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.client;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ClientDriverTestCase {
+
+ @Test
+ public void requireThatApplicationInstanceInjectionWorks() throws Exception {
+ MyModule module = new MyModule();
+ ClientDriver.runApplication(new MyApplication(module));
+ assertEquals(5, module.state);
+ }
+
+ @Test
+ public void requireThatApplicationClassInjectionWorks() throws Exception {
+ MyModule module = new MyModule();
+ ClientDriver.runApplication(MyApplication.class, module);
+ assertEquals(5, module.state);
+ }
+
+ private static class MyApplication implements ClientApplication {
+
+ final MyModule module;
+
+ @Inject
+ MyApplication(MyModule module) {
+ this.module = module;
+ module.state = 1;
+ }
+
+ @Override
+ public void start() {
+ if (++module.state != 2) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public void run() {
+ if (++module.state != 3) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public void stop() {
+ if (++module.state != 4) {
+ throw new IllegalStateException();
+ }
+ }
+
+ @Override
+ public void destroy() {
+ if (++module.state != 5) {
+ throw new IllegalStateException();
+ }
+ }
+ }
+
+ private static class MyModule extends AbstractModule {
+
+ int state = 0;
+
+ @Override
+ protected void configure() {
+ bind(MyModule.class).toInstance(this);
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerFinalizerTest.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerFinalizerTest.java
new file mode 100644
index 00000000000..b2fd357b30c
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerFinalizerTest.java
@@ -0,0 +1,75 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.Container;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.test.TestDriver;
+
+import java.net.URI;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+@SuppressWarnings("UnusedAssignment")
+public class ActiveContainerFinalizerTest {
+
+ @Test
+ public void requireThatMissingContainerReleaseDoesNotPreventShutdown() throws InterruptedException {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Container container = driver.newReference(URI.create("scheme://host"));
+ assertNotNull(container);
+
+ final Termination termination = new Termination();
+ driver.activateContainer(null).notifyTermination(termination);
+ assertFalse(termination.await(100, TimeUnit.MILLISECONDS));
+
+ container = null; // intentionally doing this instead of container.release()
+ assertTrue(termination.await(600, TimeUnit.SECONDS));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatMissingRequestReleaseDoesNotPreventShutdown() throws InterruptedException {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Request request = new Request(driver, URI.create("scheme://host"));
+ assertNotNull(request);
+
+ final Termination termination = new Termination();
+ driver.activateContainer(null).notifyTermination(termination);
+ assertFalse(termination.await(100, TimeUnit.MILLISECONDS));
+
+ request = null; // intentionally doing this instead of request.release()
+ assertTrue(termination.await(600, TimeUnit.SECONDS));
+ assertTrue(driver.close());
+ }
+
+ private static class Termination implements Runnable {
+
+ volatile boolean done;
+
+ @Override
+ public void run() {
+ done = true;
+ }
+
+ boolean await(final int timeout, final TimeUnit unit) throws InterruptedException {
+ final long timeoutAt = System.currentTimeMillis() + unit.toMillis(timeout);
+ while (!done) {
+ if (System.currentTimeMillis() > timeoutAt) {
+ return false;
+ }
+ Thread.sleep(10);
+ System.gc();
+ }
+ return true;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerTestCase.java
new file mode 100644
index 00000000000..5d61e55b7b4
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ActiveContainerTestCase.java
@@ -0,0 +1,160 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.AbstractModule;
+import com.yahoo.jdisc.application.BindingSet;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.application.UriPattern;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.service.ServerProvider;
+import com.yahoo.jdisc.test.NonWorkingRequestHandler;
+import com.yahoo.jdisc.test.NonWorkingServerProvider;
+import com.yahoo.jdisc.test.TestDriver;
+
+import java.util.Iterator;
+import java.util.Map;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ActiveContainerTestCase {
+
+ @Test
+ public void requireThatGuiceAccessorWorks() {
+ final Object obj = new Object();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(Object.class).toInstance(obj);
+ }
+ });
+ ActiveContainer container = new ActiveContainer(driver.newContainerBuilder());
+ assertSame(obj, container.guiceInjector().getInstance(Object.class));
+ driver.close();
+ }
+
+ @Test
+ public void requireThatServerAccessorWorks() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ ServerProvider foo = new NonWorkingServerProvider();
+ builder.serverProviders().install(foo);
+ ServerProvider bar = new NonWorkingServerProvider();
+ builder.serverProviders().install(bar);
+ ActiveContainer container = new ActiveContainer(builder);
+
+ Iterator<ServerProvider> it = container.serverProviders().iterator();
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ driver.close();
+ }
+
+ @Test
+ public void requireThatServerBindingAccessorWorks() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler bar = new NonWorkingRequestHandler();
+ builder.serverBindings().bind("http://host/foo", foo);
+ builder.serverBindings("bar").bind("http://host/bar", bar);
+ ActiveContainer container = new ActiveContainer(builder);
+
+ Map<String, BindingSet<RequestHandler>> bindings = container.serverBindings();
+ assertNotNull(bindings);
+ assertEquals(2, bindings.size());
+
+ BindingSet<RequestHandler> set = bindings.get(BindingSet.DEFAULT);
+ assertNotNull(set);
+ Iterator<Map.Entry<UriPattern, RequestHandler>> it = set.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ Map.Entry<UriPattern, RequestHandler> entry = it.next();
+ assertNotNull(entry);
+ assertEquals(new UriPattern("http://host/foo"), entry.getKey());
+ assertSame(foo, entry.getValue());
+ assertFalse(it.hasNext());
+
+ assertNotNull(set = bindings.get("bar"));
+ assertNotNull(it = set.iterator());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/bar"), entry.getKey());
+ assertSame(bar, entry.getValue());
+ assertFalse(it.hasNext());
+
+ assertNotNull(bindings = container.clientBindings());
+ assertEquals(1, bindings.size());
+ assertNotNull(set = bindings.get(BindingSet.DEFAULT));
+ assertNotNull(it = set.iterator());
+ assertFalse(it.hasNext());
+
+ driver.close();
+ }
+
+ @Test
+ public void requireThatClientBindingAccessorWorks() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ RequestHandler foo = new NonWorkingRequestHandler();
+ RequestHandler bar = new NonWorkingRequestHandler();
+ builder.clientBindings().bind("http://host/foo", foo);
+ builder.clientBindings("bar").bind("http://host/bar", bar);
+ ActiveContainer container = new ActiveContainer(builder);
+
+ Map<String, BindingSet<RequestHandler>> bindings = container.clientBindings();
+ assertNotNull(bindings);
+ assertEquals(2, bindings.size());
+
+ BindingSet<RequestHandler> set = bindings.get(BindingSet.DEFAULT);
+ assertNotNull(set);
+ Iterator<Map.Entry<UriPattern, RequestHandler>> it = set.iterator();
+ assertNotNull(it);
+ assertTrue(it.hasNext());
+ Map.Entry<UriPattern, RequestHandler> entry = it.next();
+ assertNotNull(entry);
+ assertEquals(new UriPattern("http://host/foo"), entry.getKey());
+ assertSame(foo, entry.getValue());
+ assertFalse(it.hasNext());
+
+ assertNotNull(set = bindings.get("bar"));
+ assertNotNull(it = set.iterator());
+ assertTrue(it.hasNext());
+ assertNotNull(entry = it.next());
+ assertEquals(new UriPattern("http://host/bar"), entry.getKey());
+ assertSame(bar, entry.getValue());
+ assertFalse(it.hasNext());
+
+ assertNotNull(bindings = container.serverBindings());
+ assertEquals(1, bindings.size());
+ assertNotNull(set = bindings.get(BindingSet.DEFAULT));
+ assertNotNull(it = set.iterator());
+ assertFalse(it.hasNext());
+
+ driver.close();
+ }
+
+ @Test
+ public void requireThatDefaultBindingsAreAlwaysCreated() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ ActiveContainer container = new ActiveContainer(builder);
+
+ Map<String, BindingSet<RequestHandler>> bindings = container.serverBindings();
+ assertNotNull(bindings);
+ assertEquals(1, bindings.size());
+ BindingSet<RequestHandler> set = bindings.get(BindingSet.DEFAULT);
+ assertFalse(set.iterator().hasNext());
+ driver.close();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationConfigModuleTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationConfigModuleTestCase.java
new file mode 100644
index 00000000000..1d23d671e0f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationConfigModuleTestCase.java
@@ -0,0 +1,114 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.Module;
+import com.google.inject.name.Names;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.Test;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ApplicationConfigModuleTestCase {
+
+ @Test
+ public void requireThatEntriesAreBoundWithLowerCaseKeys() {
+ Map<String, String> config = new HashMap<>();
+ config.put("foo_key", "foo");
+ config.put("BAR_key", "bar");
+ config.put("BAZ_KEY", "baz");
+
+ Injector injector = Guice.createInjector(new ApplicationConfigModule(config));
+ assertBinding(injector, "foo_key", "foo");
+ assertBinding(injector, "bar_key", "bar");
+ assertBinding(injector, "baz_key", "baz");
+ }
+
+ @Test
+ public void requireThatEntriesAreBoundWithUnmodifiedValue() {
+ Map<String, String> config = new HashMap<>();
+ config.put("foo", "foo_val");
+ config.put("bar", "BAR_val");
+ config.put("baz", "BAZ_VAL");
+
+ Injector injector = Guice.createInjector(new ApplicationConfigModule(config));
+ assertBinding(injector, "foo", "foo_val");
+ assertBinding(injector, "bar", "BAR_val");
+ assertBinding(injector, "baz", "BAZ_VAL");
+ }
+
+ @Test
+ public void requireThatUpperCaseKeysPrecedeLowerCaseKeys() {
+ Map<String, String> config = new HashMap<>();
+ config.put("foo", "lower-case");
+ assertBinding(config, "foo", "lower-case");
+
+ config.put("Foo", "mixed-case 1");
+ assertBinding(config, "foo", "mixed-case 1");
+
+ config.put("FOO", "upper-case");
+ assertBinding(config, "foo", "upper-case");
+
+ config.put("FOo", "mixed-case 2");
+ assertBinding(config, "foo", "upper-case");
+ }
+
+ @Test
+ public void requireThatNullFileNameThrowsException() throws IOException {
+ try {
+ ApplicationConfigModule.newInstanceFromFile(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatFileNotFoundThrowsException() throws IOException {
+ try {
+ ApplicationConfigModule.newInstanceFromFile("/file/not/found");
+ fail();
+ } catch (FileNotFoundException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatPropertieFilesCanBeRead() throws IOException {
+ Properties props = new Properties();
+ props.put("foo_key", "foo_val");
+
+ File file = File.createTempFile("config-", ".properties");
+ file.deleteOnExit();
+ FileOutputStream out = new FileOutputStream(file);
+ props.store(out, null);
+ out.close();
+
+ assertBinding(ApplicationConfigModule.newInstanceFromFile(file.getAbsolutePath()), "foo_key", "foo_val");
+ }
+
+ private static void assertBinding(Map<String, String> config, String stringName, String expected) {
+ assertBinding(new ApplicationConfigModule(config), stringName, expected);
+ }
+
+ private static void assertBinding(Module module, String stringName, String expected) {
+ assertBinding(Guice.createInjector(module), stringName, expected);
+ }
+
+ private static void assertBinding(Injector injector, String stringName, String expected) {
+ assertEquals(expected, injector.getInstance(Key.get(String.class, Names.named(stringName))));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationEnvironmentModuleTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationEnvironmentModuleTestCase.java
new file mode 100644
index 00000000000..77af705cfac
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationEnvironmentModuleTestCase.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.*;
+import com.yahoo.jdisc.application.ContainerActivator;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.application.OsgiFramework;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.NonWorkingOsgiFramework;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ThreadFactory;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ApplicationEnvironmentModuleTestCase {
+
+ @Test
+ public void requireThatBindingsExist() {
+ List<Class> expected = new LinkedList<>();
+ expected.add(ContainerActivator.class);
+ expected.add(ContainerBuilder.class);
+ expected.add(CurrentContainer.class);
+ expected.add(OsgiFramework.class);
+ expected.add(ThreadFactory.class);
+
+ Injector injector = Guice.createInjector();
+ for (Map.Entry<Key<?>, Binding<?>> entry : injector.getBindings().entrySet()) {
+ expected.add(entry.getKey().getTypeLiteral().getRawType());
+ }
+
+ ApplicationLoader loader = new ApplicationLoader(new NonWorkingOsgiFramework(),
+ Collections.<Module>emptyList());
+ injector = Guice.createInjector(new ApplicationEnvironmentModule(loader));
+ for (Map.Entry<Key<?>, Binding<?>> entry : injector.getBindings().entrySet()) {
+ assertNotNull(expected.remove(entry.getKey().getTypeLiteral().getRawType()));
+ }
+ assertTrue(expected.isEmpty());
+ }
+
+ @Test
+ public void requireThatContainerBuilderCanBeInjected() {
+ ApplicationLoader loader = new ApplicationLoader(new NonWorkingOsgiFramework(),
+ Collections.<Module>emptyList());
+ assertNotNull(new ApplicationEnvironmentModule(loader).containerBuilder());
+ assertNotNull(Guice.createInjector(new ApplicationEnvironmentModule(loader))
+ .getInstance(ContainerBuilder.class));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationLoaderTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationLoaderTestCase.java
new file mode 100644
index 00000000000..398fbcba839
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationLoaderTestCase.java
@@ -0,0 +1,259 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.ConfigurationException;
+import com.google.inject.Module;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.Application;
+import com.yahoo.jdisc.application.ApplicationNotReadyException;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.NonWorkingOsgiFramework;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ApplicationLoaderTestCase {
+
+ @Test
+ public void requireThatStartFailsWithoutApplication() throws Exception {
+ ApplicationLoader loader = new ApplicationLoader(new NonWorkingOsgiFramework(),
+ Collections.<Module>emptyList());
+ try {
+ loader.init(null, false);
+ loader.start();
+ fail();
+ } catch (ConfigurationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatStopDoesNotFailWithoutStart() throws Exception {
+ ApplicationLoader loader = new ApplicationLoader(new NonWorkingOsgiFramework(),
+ Collections.<Module>emptyList());
+ loader.stop();
+ loader.destroy();
+ }
+
+ @Test
+ public void requireThatDestroyDoesNotFailWithActiveContainer() throws Exception {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ assertNull(driver.activateContainer(driver.newContainerBuilder()));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatApplicationStartExceptionUnsetsAndDestroysApplication() throws Exception {
+ MyApplication app = MyApplication.newStartException();
+ ApplicationLoader loader = new ApplicationLoader(new NonWorkingOsgiFramework(),
+ Arrays.asList(new MyApplicationModule(app)));
+ loader.init(null, false);
+ try {
+ loader.start();
+ fail();
+ } catch (MyException e) {
+
+ }
+ assertNull(loader.application());
+ assertFalse(app.stop.await(100, TimeUnit.MILLISECONDS));
+ assertTrue(app.destroy.await(600, TimeUnit.SECONDS));
+ try {
+ loader.activateContainer(loader.newContainerBuilder());
+ fail();
+ } catch (ApplicationNotReadyException e) {
+
+ }
+ loader.stop();
+ loader.destroy();
+ }
+
+ @Test
+ public void requireThatApplicationStopExceptionDestroysApplication() throws Exception {
+ MyApplication app = MyApplication.newStopException();
+ ApplicationLoader loader = new ApplicationLoader(new NonWorkingOsgiFramework(),
+ Arrays.asList(new MyApplicationModule(app)));
+ loader.init(null, false);
+ loader.start();
+ try {
+ loader.stop();
+ } catch (MyException e) {
+
+ }
+ assertTrue(app.destroy.await(600, TimeUnit.SECONDS));
+ loader.destroy();
+ }
+
+ @Test
+ public void requireThatApplicationDestroyIsCalledAfterContainerTermination() throws InterruptedException {
+ MyApplication app = MyApplication.newInstance();
+ TestDriver driver = TestDriver.newInjectedApplicationInstance(app);
+ ContainerBuilder builder = driver.newContainerBuilder();
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ builder.serverBindings().bind("scheme://host/path", requestHandler);
+ driver.activateContainer(builder);
+ driver.dispatchRequest("scheme://host/path", new MyResponseHandler());
+ driver.scheduleClose();
+ assertFalse(app.destroy.await(100, TimeUnit.MILLISECONDS));
+ requestHandler.responseHandler.handleResponse(new Response(Response.Status.OK)).close(null);
+ assertTrue(app.destroy.await(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatContainerActivatorReturnsPrev() throws Exception {
+ TestDriver driver = TestDriver.newInjectedApplicationInstance(MyApplication.newInstance());
+ assertNull(driver.activateContainer(driver.newContainerBuilder()));
+ assertNotNull(driver.activateContainer(null));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatOsgiServicesAreRegistered() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstance();
+ BundleContext ctx = driver.osgiFramework().bundleContext();
+ Object service = ctx.getService(ctx.getServiceReference(CurrentContainer.class.getName()));
+ assertTrue(service instanceof CurrentContainer);
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatThreadFactoryCanBeBound() {
+ final ThreadFactory factory = Executors.defaultThreadFactory();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(ThreadFactory.class).toInstance(factory);
+ }
+ });
+ ContainerBuilder builder = driver.newContainerBuilder();
+ assertSame(factory, builder.getInstance(ThreadFactory.class));
+ assertTrue(driver.close());
+ }
+
+ private static class MyApplicationModule extends AbstractModule {
+
+ final Application application;
+
+ public MyApplicationModule(Application application) {
+ this.application = application;
+ }
+
+ @Override
+ protected void configure() {
+ bind(Application.class).toInstance(application);
+ }
+ }
+
+ private static class MyApplication implements Application {
+
+ final CountDownLatch start = new CountDownLatch(1);
+ final CountDownLatch stop = new CountDownLatch(1);
+ final CountDownLatch destroy = new CountDownLatch(1);
+ final boolean startException;
+ final boolean stopException;
+
+ MyApplication(boolean startException, boolean stopException) {
+ this.startException = startException;
+ this.stopException = stopException;
+ }
+
+ @Override
+ public void start() {
+ start.countDown();
+ if (startException) {
+ throw new MyException();
+ }
+ }
+
+ @Override
+ public void stop() {
+ stop.countDown();
+ if (stopException) {
+ throw new MyException();
+ }
+ }
+
+ @Override
+ public void destroy() {
+ destroy.countDown();
+ }
+
+ public static MyApplication newInstance() {
+ return new MyApplication(false, false);
+ }
+
+ public static MyApplication newStartException() {
+ return new MyApplication(true, false);
+ }
+
+ public static MyApplication newStopException() {
+ return new MyApplication(false, true);
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ ResponseHandler responseHandler;
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ responseHandler = handler;
+ return new MyContentChannel();
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return new MyContentChannel();
+ }
+ }
+
+ private static class MyContentChannel 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();
+ }
+ }
+ }
+
+ private static class MyException extends RuntimeException {
+
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationRestartTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationRestartTestCase.java
new file mode 100644
index 00000000000..2943e44bc4c
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationRestartTestCase.java
@@ -0,0 +1,153 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.AbstractModule;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.Application;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.test.NonWorkingOsgiFramework;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ApplicationRestartTestCase {
+
+ @Test
+ public void requireThatStopStartDoesNotBreakShutdown() throws Exception {
+ ApplicationLoader loader = newApplicationLoader();
+ loader.init(null, false);
+ loader.start();
+ assertGracefulStop(loader);
+ loader.start();
+ assertGracefulStop(loader);
+ loader.destroy();
+ }
+
+ @Test
+ public void requireThatDestroyInitDoesNotBreakShutdown() throws Exception {
+ ApplicationLoader loader = newApplicationLoader();
+ loader.init(null, false);
+ loader.start();
+ assertGracefulStop(loader);
+ loader.destroy();
+ loader.init(null, false);
+ loader.start();
+ assertGracefulStop(loader);
+ loader.destroy();
+ }
+
+ private static ApplicationLoader newApplicationLoader() {
+ return new ApplicationLoader(new NonWorkingOsgiFramework(),
+ Arrays.asList(new AbstractModule() {
+ @Override
+ public void configure() {
+ bind(Application.class).to(SimpleApplication.class);
+ }
+ }));
+ }
+
+ private static void assertGracefulStop(ApplicationLoader loader) throws Exception {
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ ContainerBuilder builder = loader.newContainerBuilder();
+ builder.serverBindings().bind("http://host/path", requestHandler);
+ loader.activateContainer(builder);
+
+ MyResponseHandler responseHandler = new MyResponseHandler();
+ Request request = new Request(loader, URI.create("http://host/path"));
+ request.connect(responseHandler).close(null);
+ request.release();
+
+ StopTask task = new StopTask(loader);
+ task.start();
+ assertFalse(task.latch.await(100, TimeUnit.MILLISECONDS));
+ requestHandler.responseHandler.handleResponse(new Response(Response.Status.OK)).close(null);
+ assertTrue(task.latch.await(600, TimeUnit.SECONDS));
+ }
+
+ private static class StopTask extends Thread {
+
+ final ApplicationLoader loader;
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ StopTask(ApplicationLoader loader) {
+ this.loader = loader;
+ }
+
+ @Override
+ public void run() {
+ try {
+ loader.stop();
+ } catch (Exception e) {
+ e.printStackTrace();
+ return;
+ }
+ latch.countDown();
+ }
+ }
+
+ private static class SimpleApplication implements Application {
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void stop() {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ ResponseHandler responseHandler;
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ this.responseHandler = handler;
+ return new MyContentChannel();
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return new MyContentChannel();
+ }
+ }
+
+ private static class MyContentChannel implements ContentChannel {
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ handler.completed();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ handler.completed();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationShutdownTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationShutdownTestCase.java
new file mode 100644
index 00000000000..986ceddb3e4
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ApplicationShutdownTestCase.java
@@ -0,0 +1,122 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ApplicationShutdownTestCase {
+
+ @Test
+ public void requireThatStopWaitsForPreviousContainer() throws Exception {
+ Context ctx = new Context();
+ MyRequestHandler requestHandler = new MyRequestHandler();
+ ctx.activateContainer(requestHandler);
+ ctx.dispatchRequest();
+ ctx.activateContainer(null);
+ ctx.driver.scheduleClose();
+ assertFalse(ctx.driver.awaitClose(100, TimeUnit.MILLISECONDS));
+ requestHandler.respond();
+ assertTrue(ctx.driver.awaitClose(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatStopWaitsForAllPreviousContainers() {
+ Context ctx = new Context();
+ MyRequestHandler requestHandlerA = new MyRequestHandler();
+ ctx.activateContainer(requestHandlerA);
+ ctx.dispatchRequest();
+
+ MyRequestHandler requestHandlerB = new MyRequestHandler();
+ ctx.activateContainer(requestHandlerB);
+ ctx.dispatchRequest();
+
+ MyRequestHandler requestHandlerC = new MyRequestHandler();
+ ctx.activateContainer(requestHandlerC);
+ ctx.dispatchRequest();
+
+ ctx.driver.scheduleClose();
+ assertFalse(ctx.driver.awaitClose(100, TimeUnit.MILLISECONDS));
+ requestHandlerB.respond();
+ assertFalse(ctx.driver.awaitClose(100, TimeUnit.MILLISECONDS));
+ requestHandlerC.respond();
+ assertFalse(ctx.driver.awaitClose(100, TimeUnit.MILLISECONDS));
+ requestHandlerA.respond();
+ assertTrue(ctx.driver.awaitClose(600, TimeUnit.SECONDS));
+ }
+
+ private static class Context {
+
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+
+ void activateContainer(RequestHandler requestHandler) {
+ ContainerBuilder builder;
+ if (requestHandler != null) {
+ builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://host/path", requestHandler);
+ } else {
+ builder = null;
+ }
+ driver.activateContainer(builder);
+ }
+
+ void dispatchRequest() {
+ Request request = new Request(driver, URI.create("http://host/path"));
+ request.connect(new MyResponseHandler()).close(null);
+ request.release();
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ ResponseHandler handler = null;
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ this.handler = handler;
+ return new MyContent();
+ }
+
+ void respond() {
+ handler.handleResponse(new Response(Response.Status.OK)).close(null);
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return new MyContent();
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ handler.completed();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ handler.completed();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/BootstrapDaemonTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/BootstrapDaemonTestCase.java
new file mode 100644
index 00000000000..bc80358b760
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/BootstrapDaemonTestCase.java
@@ -0,0 +1,154 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.apache.commons.daemon.DaemonContext;
+import org.apache.commons.daemon.DaemonController;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BootstrapDaemonTestCase {
+
+ @Test
+ public void requireThatPrivilegedLifecycleWorks() throws Exception {
+ MyLoader loader = new MyLoader();
+ BootstrapDaemon daemon = new BootstrapDaemon(loader, true);
+ daemon.init(new MyContext("foo"));
+ assertTrue(loader.hasState(true, false, false, false));
+ assertTrue(loader.privileged);
+ daemon.start();
+ assertTrue(loader.hasState(true, true, false, false));
+ daemon.stop();
+ assertTrue(loader.hasState(true, true, true, false));
+ daemon.destroy();
+ assertTrue(loader.hasState(true, true, true, true));
+ }
+
+ @Test
+ public void requireThatNonPrivilegedLifecycleWorks() throws Exception {
+ MyLoader loader = new MyLoader();
+ BootstrapDaemon daemon = new BootstrapDaemon(loader, false);
+ daemon.init(new MyContext("foo"));
+ assertTrue(loader.hasState(false, false, false, false));
+ daemon.start();
+ assertTrue(loader.hasState(true, true, false, false));
+ assertFalse(loader.privileged);
+ daemon.stop();
+ assertTrue(loader.hasState(true, true, true, false));
+ daemon.destroy();
+ assertTrue(loader.hasState(true, true, true, true));
+ }
+
+ @Test
+ public void requireThatBundleLocationIsRequired() throws Exception {
+ MyLoader loader = new MyLoader();
+ BootstrapDaemon daemon = new BootstrapDaemon(loader, true);
+ try {
+ daemon.init(new MyContext((String[])null));
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertNull(loader.bundleLocation);
+ }
+ try {
+ daemon.init(new MyContext());
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertNull(loader.bundleLocation);
+ }
+ try {
+ daemon.init(new MyContext((String)null));
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertNull(loader.bundleLocation);
+ }
+ try {
+ daemon.init(new MyContext("foo", "bar"));
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertNull(loader.bundleLocation);
+ }
+
+ daemon.init(new MyContext("foo"));
+ daemon.start();
+
+ assertNotNull(loader.bundleLocation);
+ assertEquals("foo", loader.bundleLocation);
+
+ daemon.stop();
+ daemon.destroy();
+ }
+
+ @Test
+ public void requireThatEnvironmentIsRequired() {
+ try {
+ new BootstrapDaemon();
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ private static class MyLoader implements BootstrapLoader {
+
+ String bundleLocation = null;
+ boolean privileged = false;
+ boolean initCalled = false;
+ boolean startCalled = false;
+ boolean stopCalled = false;
+ boolean destroyCalled = false;
+
+ boolean hasState(boolean initCalled, boolean startCalled, boolean stopCalled, boolean destroyCalled) {
+ return this.initCalled == initCalled && this.startCalled == startCalled &&
+ this.stopCalled == stopCalled && this.destroyCalled == destroyCalled;
+ }
+
+ @Override
+ public void init(String bundleLocation, boolean privileged) throws Exception {
+ this.bundleLocation = bundleLocation;
+ this.privileged = privileged;
+ initCalled = true;
+ }
+
+ @Override
+ public void start() throws Exception {
+ startCalled = true;
+ }
+
+ @Override
+ public void stop() throws Exception {
+ stopCalled = true;
+ }
+
+ @Override
+ public void destroy() {
+ destroyCalled = true;
+ }
+ }
+
+ private static class MyContext implements DaemonContext {
+
+ final String[] args;
+
+ MyContext(String... args) {
+ this.args = args;
+ }
+
+ @Override
+ public DaemonController getController() {
+ return null;
+ }
+
+ @Override
+ public String[] getArguments() {
+ return args;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/BundleLocationResolverTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/BundleLocationResolverTestCase.java
new file mode 100644
index 00000000000..843ca91db68
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/BundleLocationResolverTestCase.java
@@ -0,0 +1,87 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class BundleLocationResolverTestCase {
+
+ @Test
+ public void requireThatDollarsAreIncludedInLocation() {
+ assertLocation("scheme:$foo", "scheme:$foo");
+ assertLocation("scheme:foo$bar", "scheme:foo$bar");
+ }
+
+ @Test
+ public void requireThatCurlyBracesAreIncludedInLocation() {
+ assertLocation("scheme:{foo", "scheme:{foo");
+ assertLocation("scheme:foo{", "scheme:foo{");
+ assertLocation("scheme:foo{bar", "scheme:foo{bar");
+ assertLocation("scheme:}foo", "scheme:}foo");
+ assertLocation("scheme:foo}", "scheme:foo}");
+ assertLocation("scheme:foo}bar", "scheme:foo}bar");
+ assertLocation("scheme:{foo}bar", "scheme:{foo}bar");
+ assertLocation("scheme:foo{bar}", "scheme:foo{bar}");
+ }
+
+ @Test
+ public void requireThatUnterminatedPropertiesAreIncludedInLocation() {
+ assertLocation("scheme:${foo", "scheme:${foo");
+ assertLocation("scheme:foo${", "scheme:foo${");
+ assertLocation("scheme:foo${bar", "scheme:foo${bar");
+ }
+
+ @Test
+ public void requireThatAllSystemPropertiesAreExpanded() throws IOException {
+ assertCanonicalPath("", "${foo}");
+ assertCanonicalPath("barcox", "${foo}bar${baz}cox");
+ assertCanonicalPath("foobaz", "foo${bar}baz${cox}");
+
+ System.setProperty("requireThatAllSystemPropertiesAreExpanded.foo", "FOO");
+ System.setProperty("requireThatAllSystemPropertiesAreExpanded.bar", "BAR");
+ System.setProperty("requireThatAllSystemPropertiesAreExpanded.baz", "BAZ");
+ System.setProperty("requireThatAllSystemPropertiesAreExpanded.cox", "COX");
+ assertCanonicalPath("FOO", "${requireThatAllSystemPropertiesAreExpanded.foo}");
+ assertCanonicalPath("FOObarBAZcox", "${requireThatAllSystemPropertiesAreExpanded.foo}bar" +
+ "${requireThatAllSystemPropertiesAreExpanded.baz}cox");
+ assertCanonicalPath("fooBARbazCOX", "foo${requireThatAllSystemPropertiesAreExpanded.bar}" +
+ "baz${requireThatAllSystemPropertiesAreExpanded.cox}");
+ }
+
+ @Test
+ public void requireThatUnschemedLocationsAreExpandedToBundleLocationProperty() throws IOException {
+ assertCanonicalPath(BundleLocationResolver.BUNDLE_PATH + "foo", "foo");
+ }
+
+ @Test
+ public void requireThatFileSchemedLocationsAreCanonicalized() throws IOException {
+ assertCanonicalPath("", "file:");
+ assertCanonicalPath("foo", "file:foo");
+ assertCanonicalPath("foo", "file:./foo");
+ assertCanonicalPath("foo/bar", "file:foo/bar");
+ assertCanonicalPath("foo/bar", "file:./foo/../foo/./bar");
+ assertCanonicalPath("foo", " \f\n\r\tfile:foo");
+ }
+
+ @Test
+ public void requireThatOtherSchemedLocationsAreUntouched() {
+ assertLocation("foo:", "foo:");
+ assertLocation("foo:bar", "foo:bar");
+ assertLocation("foo:bar/baz", "foo:bar/baz");
+ }
+
+ private static void assertCanonicalPath(String expected, String bundleLocation) throws IOException {
+ assertLocation("file:" + new File(expected).getCanonicalPath(), bundleLocation);
+ }
+
+ private static void assertLocation(String expected, String bundleLocation) {
+ assertEquals(expected, BundleLocationResolver.resolve(bundleLocation));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogFormatterTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogFormatterTestCase.java
new file mode 100644
index 00000000000..901817dbd26
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogFormatterTestCase.java
@@ -0,0 +1,270 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogService;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.Writer;
+
+import static org.junit.Assert.assertEquals;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ConsoleLogFormatterTestCase {
+
+ private static final ConsoleLogFormatter SIMPLE_FORMATTER = new ConsoleLogFormatter(null, null, null);
+ private static final LogEntry SIMPLE_ENTRY = new MyEntry(0, 0, null);
+
+ // TODO: Should (at least) use ConsoleLogFormatter.ABSENCE_REPLACEMENT instead of literal '-'. See ticket 7128315.
+
+ @Test
+ public void requireThatMillisecondsArePadded() {
+ for (int i = 0; i < 10000; ++i) {
+ LogEntry entry = new MyEntry(i, 0, null);
+ assertEquals(String.format("%d.%03d\t-\t-\t-\t-\tunknown\t", i / 1000, i % 1000),
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+ }
+
+ @Test
+ public void requireThatHostNameIsIncluded() {
+ assertEquals("0.000\thostName\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter("hostName", null, null).formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatHostNameIsOptional() {
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, null, null).formatEntry(SIMPLE_ENTRY));
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter("", null, null).formatEntry(SIMPLE_ENTRY));
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(" ", null, null).formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatProcessIdIsIncluded() {
+ assertEquals("0.000\t-\tprocessId\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, "processId", null).formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatProcessIdIsOptional() {
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, null, null).formatEntry(SIMPLE_ENTRY));
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, "", null).formatEntry(SIMPLE_ENTRY));
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, " ", null).formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatProcessIdIncludesThreadIdWhenAvailable() {
+ LogEntry entry = new MyEntry(0, 0, null).putProperty("THREAD_ID", "threadId");
+ assertEquals("0.000\t-\tprocessId/threadId\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, "processId", null).formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatServiceNameIsIncluded() {
+ assertEquals("0.000\t-\t-\tserviceName\t-\tunknown\t",
+ new ConsoleLogFormatter(null, null, "serviceName").formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatServiceNameIsOptional() {
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, null, null).formatEntry(SIMPLE_ENTRY));
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, null, "").formatEntry(SIMPLE_ENTRY));
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ new ConsoleLogFormatter(null, null, " ").formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatBundleNameIsIncluded() {
+ LogEntry entry = new MyEntry(0, 0, null).setBundleSymbolicName("bundleName");
+ assertEquals("0.000\t-\t-\t-\tbundleName\tunknown\t",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatBundleNameIsOptional() {
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ SIMPLE_FORMATTER.formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatLoggerNameIsIncluded() {
+ LogEntry entry = new MyEntry(0, 0, null).putProperty("LOGGER_NAME", "loggerName");
+ assertEquals("0.000\t-\t-\t-\t/loggerName\tunknown\t",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatLoggerNameIsOptional() {
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ SIMPLE_FORMATTER.formatEntry(SIMPLE_ENTRY));
+ }
+
+ @Test
+ public void requireThatBundleAndLoggerNameIsCombined() {
+ LogEntry entry = new MyEntry(0, 0, null).setBundleSymbolicName("bundleName")
+ .putProperty("LOGGER_NAME", "loggerName");
+ assertEquals("0.000\t-\t-\t-\tbundleName/loggerName\tunknown\t",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatLevelNameIsIncluded() {
+ ConsoleLogFormatter formatter = SIMPLE_FORMATTER;
+ assertEquals("0.000\t-\t-\t-\t-\terror\t",
+ formatter.formatEntry(new MyEntry(0, LogService.LOG_ERROR, null)));
+ assertEquals("0.000\t-\t-\t-\t-\twarning\t",
+ formatter.formatEntry(new MyEntry(0, LogService.LOG_WARNING, null)));
+ assertEquals("0.000\t-\t-\t-\t-\tinfo\t",
+ formatter.formatEntry(new MyEntry(0, LogService.LOG_INFO, null)));
+ assertEquals("0.000\t-\t-\t-\t-\tdebug\t",
+ formatter.formatEntry(new MyEntry(0, LogService.LOG_DEBUG, null)));
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ formatter.formatEntry(new MyEntry(0, 69, null)));
+ }
+
+ @Test
+ public void requireThatMessageIsIncluded() {
+ LogEntry entry = new MyEntry(0, 0, "message");
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\tmessage",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatMessageIsOptional() {
+ LogEntry entry = new MyEntry(0, 0, null);
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatMessageIsEscaped() {
+ LogEntry entry = new MyEntry(0, 0, "\\\n\r\t");
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t\\\\\\n\\r\\t",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatExceptionIsIncluded() {
+ Throwable t = new Throwable();
+ LogEntry entry = new MyEntry(0, 0, null).setException(t);
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t\\n" + formatThrowable(t),
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatExceptionIsEscaped() {
+ Throwable t = new Throwable("\\\n\r\t");
+ LogEntry entry = new MyEntry(0, 0, null).setException(t);
+ assertEquals("0.000\t-\t-\t-\t-\tunknown\t\\n" + formatThrowable(t),
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatExceptionIsSimplifiedForInfoEntries() {
+ Throwable t = new Throwable("exception");
+ LogEntry entry = new MyEntry(0, LogService.LOG_INFO, "entry").setException(t);
+ assertEquals("0.000\t-\t-\t-\t-\tinfo\tentry: exception",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatSimplifiedExceptionIsEscaped() {
+ Throwable t = new Throwable("\\\n\r\t");
+ LogEntry entry = new MyEntry(0, LogService.LOG_INFO, "entry").setException(t);
+ assertEquals("0.000\t-\t-\t-\t-\tinfo\tentry: \\\\\\n\\r\\t",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ @Test
+ public void requireThatSimplifiedExceptionMessageIsOptional() {
+ Throwable t = new Throwable();
+ LogEntry entry = new MyEntry(0, LogService.LOG_INFO, "entry").setException(t);
+ assertEquals("0.000\t-\t-\t-\t-\tinfo\tentry: java.lang.Throwable",
+ SIMPLE_FORMATTER.formatEntry(entry));
+ }
+
+ private static String formatThrowable(Throwable t) {
+ Writer out = new StringWriter();
+ t.printStackTrace(new PrintWriter(out));
+ return out.toString().replace("\\", "\\\\").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
+ }
+
+ private static class MyEntry implements LogEntry {
+
+ final String message;
+ final int level;
+ final long time;
+ Bundle bundle = null;
+ ServiceReference serviceReference = null;
+ Throwable exception;
+
+ MyEntry(long time, int level, String message) {
+ this.message = message;
+ this.level = level;
+ this.time = time;
+ }
+
+ MyEntry setBundleSymbolicName(String symbolicName) {
+ this.bundle = Mockito.mock(Bundle.class);
+ Mockito.doReturn(symbolicName).when(this.bundle).getSymbolicName();
+ return this;
+ }
+
+ MyEntry setException(Throwable exception) {
+ this.exception = exception;
+ return this;
+ }
+
+ MyEntry putProperty(String key, String val) {
+ this.serviceReference = Mockito.mock(ServiceReference.class);
+ Mockito.doReturn(val).when(this.serviceReference).getProperty(key);
+ return this;
+ }
+
+ @Override
+ public long getTime() {
+ return time;
+ }
+
+ @Override
+ public int getLevel() {
+ return level;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public Throwable getException() {
+ return exception;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return bundle;
+ }
+
+ @Override
+ public ServiceReference getServiceReference() {
+ return serviceReference;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogListenerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogListenerTestCase.java
new file mode 100644
index 00000000000..3ac30f0456e
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogListenerTestCase.java
@@ -0,0 +1,115 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogEntry;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogService;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:vikasp@yahoo-inc.com">Vikas Panwar</a>
+ */
+public class ConsoleLogListenerTestCase {
+
+ private static final String HOSTNAME = ConsoleLogFormatter.formatOptional(ConsoleLogListener.getHostname());
+ private static final String PROCESS_ID = ConsoleLogListener.getProcessId();
+
+ @Test
+ public void requireThatLogLevelParserKnowsOsgiLogLevels() {
+ assertEquals(LogService.LOG_ERROR, ConsoleLogListener.parseLogLevel("ERROR"));
+ assertEquals(LogService.LOG_WARNING, ConsoleLogListener.parseLogLevel("WARNING"));
+ assertEquals(LogService.LOG_INFO, ConsoleLogListener.parseLogLevel("INFO"));
+ assertEquals(LogService.LOG_DEBUG, ConsoleLogListener.parseLogLevel("DEBUG"));
+ }
+
+ @Test
+ public void requireThatLogLevelParserKnowsOff() {
+ assertEquals(Integer.MIN_VALUE, ConsoleLogListener.parseLogLevel("OFF"));
+ }
+
+ @Test
+ public void requireThatLogLevelParserKnowsAll() {
+ assertEquals(Integer.MAX_VALUE, ConsoleLogListener.parseLogLevel("ALL"));
+ }
+
+ @Test
+ public void requireThatLogLevelParserKnowsIntegers() {
+ for (int i = -69; i < 69; ++i) {
+ assertEquals(i, ConsoleLogListener.parseLogLevel(String.valueOf(i)));
+ }
+ }
+
+ @Test
+ public void requireThatLogLevelParserErrorsReturnDefault() {
+ assertEquals(ConsoleLogListener.DEFAULT_LOG_LEVEL, ConsoleLogListener.parseLogLevel(null));
+ assertEquals(ConsoleLogListener.DEFAULT_LOG_LEVEL, ConsoleLogListener.parseLogLevel(""));
+ assertEquals(ConsoleLogListener.DEFAULT_LOG_LEVEL, ConsoleLogListener.parseLogLevel("foo"));
+ }
+
+ @Test
+ public void requireThatLogEntryWithLevelAboveThresholdIsNotOutput() {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ LogListener listener = new ConsoleLogListener(new PrintStream(out), null, "5");
+ for (int i = 0; i < 10; ++i) {
+ listener.logged(new MyEntry(0, i, "message"));
+ }
+ // TODO: Should use ConsoleLogFormatter.ABSENCE_REPLACEMENT instead of literal '-'. See ticket 7128315.
+ assertEquals("0.000\t" + HOSTNAME + "\t" + PROCESS_ID + "\t-\t-\tunknown\tmessage\n" +
+ "0.000\t" + HOSTNAME + "\t" + PROCESS_ID + "\t-\t-\terror\tmessage\n" +
+ "0.000\t" + HOSTNAME + "\t" + PROCESS_ID + "\t-\t-\twarning\tmessage\n" +
+ "0.000\t" + HOSTNAME + "\t" + PROCESS_ID + "\t-\t-\tinfo\tmessage\n" +
+ "0.000\t" + HOSTNAME + "\t" + PROCESS_ID + "\t-\t-\tdebug\tmessage\n" +
+ "0.000\t" + HOSTNAME + "\t" + PROCESS_ID + "\t-\t-\tunknown\tmessage\n",
+ out.toString());
+ }
+
+ private static class MyEntry implements LogEntry {
+
+ final String message;
+ final int level;
+ final long time;
+
+ MyEntry(long time, int level, String message) {
+ this.message = message;
+ this.level = level;
+ this.time = time;
+ }
+
+ @Override
+ public long getTime() {
+ return time;
+ }
+
+ @Override
+ public int getLevel() {
+ return level;
+ }
+
+ @Override
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public Throwable getException() {
+ return null;
+ }
+
+ @Override
+ public Bundle getBundle() {
+ return null;
+ }
+
+ @Override
+ public ServiceReference getServiceReference() {
+ return null;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogManagerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogManagerTestCase.java
new file mode 100644
index 00000000000..5bc1a29e2f7
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ConsoleLogManagerTestCase.java
@@ -0,0 +1,90 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.log.LogListener;
+import org.osgi.service.log.LogReaderService;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ConsoleLogManagerTestCase {
+
+ @Test
+ public void requireThatManagerCanNotBeInstalledTwice() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.start();
+
+ ConsoleLogManager manager = new ConsoleLogManager();
+ manager.install(felix.bundleContext());
+ try {
+ manager.install(felix.bundleContext());
+ fail();
+ } catch (IllegalStateException e) {
+ assertEquals("ConsoleLogManager already installed.", e.getMessage());
+ }
+
+ felix.stop();
+ }
+
+ @Test
+ public void requireThatManagerCanBeUninstalledTwice() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.start();
+
+ ConsoleLogManager manager = new ConsoleLogManager();
+ assertFalse(manager.uninstall());
+ manager.install(felix.bundleContext());
+ assertTrue(manager.uninstall());
+ assertFalse(manager.uninstall());
+
+ felix.stop();
+ }
+
+ @Test
+ public void requireThatLogReaderServicesAreTracked() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.start();
+ BundleContext ctx = felix.bundleContext();
+
+ LogReaderService foo = Mockito.mock(LogReaderService.class);
+ ctx.registerService(LogReaderService.class.getName(), foo, null);
+ Mockito.verify(foo).addLogListener(Mockito.any(LogListener.class));
+
+ LogReaderService bar = Mockito.mock(LogReaderService.class);
+ ctx.registerService(LogReaderService.class.getName(), bar, null);
+ Mockito.verify(bar).addLogListener(Mockito.any(LogListener.class));
+
+ ConsoleLogManager manager = new ConsoleLogManager();
+ manager.install(felix.bundleContext());
+
+ Mockito.verify(foo, Mockito.times(2)).addLogListener(Mockito.any(LogListener.class));
+ Mockito.verify(bar, Mockito.times(2)).addLogListener(Mockito.any(LogListener.class));
+
+ LogReaderService baz = Mockito.mock(LogReaderService.class);
+ ctx.registerService(LogReaderService.class.getName(), baz, null);
+ Mockito.verify(baz, Mockito.times(2)).addLogListener(Mockito.any(LogListener.class));
+
+ assertTrue(manager.uninstall());
+
+ Mockito.verify(foo).removeLogListener(Mockito.any(LogListener.class));
+ Mockito.verify(bar).removeLogListener(Mockito.any(LogListener.class));
+ Mockito.verify(baz).removeLogListener(Mockito.any(LogListener.class));
+
+ felix.stop();
+
+ Mockito.verify(foo, Mockito.times(2)).removeLogListener(Mockito.any(LogListener.class));
+ Mockito.verify(bar, Mockito.times(2)).removeLogListener(Mockito.any(LogListener.class));
+ Mockito.verify(baz, Mockito.times(2)).removeLogListener(Mockito.any(LogListener.class));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerResourceTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerResourceTestCase.java
new file mode 100644
index 00000000000..11b6f27296d
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerResourceTestCase.java
@@ -0,0 +1,162 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.Container;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.ResourceReference;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.ServerProvider;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerResourceTestCase {
+
+ @Test
+ public void requireThatBoundRequestHandlersAreRetainedOnActivate() {
+ MyRequestHandler foo = new MyRequestHandler();
+ MyRequestHandler bar = new MyRequestHandler();
+
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings("foo").bind("http://foo/", foo);
+ builder.serverBindings("bar").bind("http://bar/", bar);
+ assertEquals(0, foo.retainCnt.get());
+ assertEquals(0, bar.retainCnt.get());
+
+ driver.activateContainer(builder);
+ assertEquals(1, foo.retainCnt.get());
+ assertEquals(1, bar.retainCnt.get());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatBoundRequestHandlersAreReleasedOnTermination() {
+ MyRequestHandler handler = new MyRequestHandler();
+
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://localhost/", handler);
+ driver.activateContainer(builder);
+
+ Container container = driver.newReference(URI.create("http://localhost/"));
+ assertEquals(1, handler.retainCnt.get());
+ driver.activateContainer(null);
+ assertEquals(1, handler.retainCnt.get());
+ container.release();
+ assertEquals(0, handler.retainCnt.get());
+
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatServerProvidersAreRetainedOnActivate() {
+ MyServerProvider foo = new MyServerProvider();
+ MyServerProvider bar = new MyServerProvider();
+
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverProviders().install(foo);
+ builder.serverProviders().install(bar);
+ assertEquals(0, foo.retainCnt.get());
+ assertEquals(0, bar.retainCnt.get());
+
+ driver.activateContainer(builder);
+ assertEquals(1, foo.retainCnt.get());
+ assertEquals(1, bar.retainCnt.get());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatServerProvidersAreReleasedOnTermination() {
+ MyServerProvider server = new MyServerProvider();
+
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverProviders().install(server);
+ driver.activateContainer(builder);
+
+ Container container = driver.newReference(URI.create("http://localhost/"));
+ assertEquals(1, server.retainCnt.get());
+ driver.activateContainer(null);
+ assertEquals(1, server.retainCnt.get());
+ container.release();
+ assertEquals(0, server.retainCnt.get());
+
+ assertTrue(driver.close());
+ }
+
+ private static class MyRequestHandler implements RequestHandler {
+
+ final AtomicInteger retainCnt = new AtomicInteger(0);
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void handleTimeout(Request request, ResponseHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResourceReference refer() {
+ retainCnt.incrementAndGet();
+ return new ResourceReference() {
+ @Override
+ public void close() {
+ retainCnt.decrementAndGet();
+ }
+ };
+ }
+
+ @Override
+ public void release() {
+ retainCnt.decrementAndGet();
+ }
+ }
+
+ private static class MyServerProvider implements ServerProvider {
+
+ final AtomicInteger retainCnt = new AtomicInteger(0);
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public ResourceReference refer() {
+ retainCnt.incrementAndGet();
+ return new ResourceReference() {
+ @Override
+ public void close() {
+ retainCnt.decrementAndGet();
+ }
+ };
+ }
+
+ @Override
+ public void release() {
+ retainCnt.decrementAndGet();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerShutdownTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerShutdownTestCase.java
new file mode 100644
index 00000000000..488628867b4
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerShutdownTestCase.java
@@ -0,0 +1,848 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.Container;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerShutdownTestCase {
+
+ @Test
+ public void requireThatContainerBlocksTermination() {
+ Context ctx = Context.newInstance();
+ Container container = ctx.driver.newReference(URI.create("http://host/path"));
+ assertFalse(ctx.shutdown());
+ container.release();
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatNewRequestBlocksTermination() {
+ Context ctx = Context.newPendingRequest(MyRequestHandler.newInstance());
+ assertFalse(ctx.shutdown());
+ ctx.request.release();
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatOpenRequestBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertFalse(ctx.shutdown());
+ requestContent.close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponsePendingBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ctx.request.connect(MyResponseHandler.newEagerCompletion()).close(null);
+ ctx.request.release();
+ assertFalse(ctx.shutdown());
+ requestHandler.respond().close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatOpenResponseBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ctx.request.connect(MyResponseHandler.newEagerCompletion()).close(null);
+ ctx.request.release();
+ ContentChannel responseContent = requestHandler.respond();
+ assertFalse(ctx.shutdown());
+ responseContent.close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestExceptionDoesNotBlockTermination() {
+ Context ctx = Context.newPendingRequest(MyRequestHandler.newRequestException());
+ try {
+ ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ ctx.request.release();
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestExceptionWithEagerHandleResponseBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newRequestExceptionWithEagerHandleResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ try {
+ ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ ctx.request.release();
+ assertFalse(ctx.shutdown());
+ requestHandler.responseContent.close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestExceptionWithEagerCloseResponseDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newRequestExceptionWithEagerCloseResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ try {
+ ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ ctx.request.release();
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatNullRequestContentBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newNullContent();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ctx.request.connect(MyResponseHandler.newEagerCompletion()).close(null);
+ ctx.request.release();
+ assertFalse(ctx.shutdown());
+ requestHandler.respond();
+ requestHandler.responseContent.close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatNullRequestContentWithEagerHandleResponseBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newNullContentWithEagerHandleResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ctx.request.connect(MyResponseHandler.newEagerCompletion()).close(null);
+ ctx.request.release();
+ assertFalse(ctx.shutdown());
+ requestHandler.responseContent.close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatNullRequestContentWithEagerCloseResponseBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newNulContentWithEagerCloseResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ ctx.request.release();
+ assertFalse(ctx.shutdown());
+ requestContent.close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentWriteFailedDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerFail();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ requestContent.write(ByteBuffer.allocate(69), null);
+ requestContent.close(null);
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentWriteExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newContentWriteExceptionWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ try {
+ requestContent.write(ByteBuffer.allocate(69), null);
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ requestContent.close(null);
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentWriteExceptionDoesNotForceTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newContentWriteExceptionWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ try {
+ requestContent.write(ByteBuffer.allocate(69), null);
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertFalse(ctx.shutdown());
+ requestContent.close(null);
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentWriteExceptionWithCompletionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newContentWriteExceptionWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ try {
+ requestContent.write(ByteBuffer.allocate(69), MyCompletion.newInstance());
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ requestContent.close(null);
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentCloseFailedDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerFail();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ requestContent.close(null);
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentCloseExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newContentCloseException();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ try {
+ requestContent.close(null);
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestContentCloseExceptionWithCompletionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newContentCloseException();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ try {
+ requestContent.close(MyCompletion.newInstance());
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ ctx.request.release();
+ requestHandler.respond().close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestWriteCompletionBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCloseResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ ctx.request.release();
+ requestContent.write(null, MyCompletion.newInstance());
+ requestContent.close(null);
+ requestHandler.requestContent.closeCompletion.completed();
+ assertFalse(ctx.shutdown());
+ requestHandler.requestContent.writeCompletion.completed();
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestWriteCompletionExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCloseResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ ctx.request.release();
+ requestContent.write(null, MyCompletion.newException());
+ requestContent.close(null);
+ requestHandler.requestContent.closeCompletion.completed();
+ assertFalse(ctx.shutdown());
+ try {
+ requestHandler.requestContent.writeCompletion.completed();
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestCloseCompletionBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCloseResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ ctx.request.release();
+ requestContent.close(MyCompletion.newInstance());
+ assertFalse(ctx.shutdown());
+ requestHandler.requestContent.closeCompletion.completed();
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatRequestCloseCompletionExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCloseResponse();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ContentChannel requestContent = ctx.request.connect(MyResponseHandler.newEagerCompletion());
+ ctx.request.release();
+ requestContent.close(MyCompletion.newException());
+ assertFalse(ctx.shutdown());
+ try {
+ requestHandler.requestContent.closeCompletion.completed();
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatNullResponseContentBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerRespondWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newNullContent();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+
+ assertFalse(ctx.shutdown());
+ requestHandler.responseContent.close(MyCompletion.newInstance());
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ctx.request.connect(MyResponseHandler.newResponseException()).close(null);
+ ctx.request.release();
+ try {
+ requestHandler.respond();
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentWriteFailedDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newEagerFail();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+ requestHandler.respond();
+ requestHandler.responseContent.write(ByteBuffer.allocate(69), null);
+ requestHandler.responseContent.close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentCloseFailedDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newEagerFail();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+ requestHandler.respond();
+ requestHandler.responseContent.close(null);
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentWriteExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newContentWriteException();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+ requestHandler.respond();
+ try {
+ requestHandler.responseContent.write(ByteBuffer.allocate(69), null);
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ requestHandler.responseContent.close(null);
+ responseHandler.content.closeCompletion.completed();
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentWriteExceptionDoesNotForceTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newContentWriteException();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+ requestHandler.respond();
+ try {
+ requestHandler.responseContent.write(ByteBuffer.allocate(69), null);
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertFalse(ctx.shutdown());
+ requestHandler.responseContent.close(null);
+ responseHandler.content.closeCompletion.completed();
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentWriteExceptionWithCompletionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newContentWriteException();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+ requestHandler.respond();
+ try {
+ requestHandler.responseContent.write(ByteBuffer.allocate(69), MyCompletion.newInstance());
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ requestHandler.responseContent.close(null);
+ responseHandler.content.closeCompletion.completed();
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentCloseExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ctx.request.connect(MyResponseHandler.newContentCloseException()).close(null);
+ ctx.request.release();
+ try {
+ requestHandler.respond().close(null);
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseContentCloseExceptionWithCompletionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ ctx.request.connect(MyResponseHandler.newContentCloseException()).close(null);
+ ctx.request.release();
+ try {
+ requestHandler.respond().close(MyCompletion.newInstance());
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertTrue(ctx.shutdown());
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseWriteCompletionBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerRespondWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+
+ requestHandler.responseContent.write(null, MyCompletion.newInstance());
+ requestHandler.responseContent.close(null);
+ responseHandler.content.closeCompletion.completed();
+ assertFalse(ctx.shutdown());
+ responseHandler.content.writeCompletion.completed();
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseWriteCompletionExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerRespondWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+
+ requestHandler.responseContent.write(null, MyCompletion.newException());
+ requestHandler.responseContent.close(null);
+ responseHandler.content.closeCompletion.completed();
+ assertFalse(ctx.shutdown());
+ try {
+ responseHandler.content.writeCompletion.completed();
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseCloseCompletionBlocksTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerRespondWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+
+ requestHandler.responseContent.close(MyCompletion.newInstance());
+ assertFalse(ctx.shutdown());
+ responseHandler.content.closeCompletion.completed();
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ @Test
+ public void requireThatResponseCloseCompletionExceptionDoesNotBlockTermination() {
+ MyRequestHandler requestHandler = MyRequestHandler.newEagerRespondWithEagerCompletion();
+ Context ctx = Context.newPendingRequest(requestHandler);
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ ctx.request.connect(responseHandler).close(null);
+ ctx.request.release();
+
+ requestHandler.responseContent.close(MyCompletion.newException());
+ assertFalse(ctx.shutdown());
+ try {
+ responseHandler.content.closeCompletion.completed();
+ fail();
+ } catch (MyException e) {
+ // ignore
+ }
+ assertTrue(ctx.terminated);
+ assertTrue(ctx.driver.close());
+ }
+
+ private static class Context {
+
+ final TestDriver driver;
+ final Request request;
+ boolean terminated = false;
+
+ Context(TestDriver driver, Request request) {
+ this.driver = driver;
+ this.request = request;
+ }
+
+ boolean shutdown() {
+ driver.activateContainer(null).notifyTermination(new Runnable() {
+
+ @Override
+ public void run() {
+ terminated = true;
+ }
+ });
+ return terminated;
+ }
+
+ static Context newInstance() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ return new Context(driver, null);
+ }
+
+ static Context newPendingRequest(RequestHandler requestHandler) {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://host/path", requestHandler);
+ driver.activateContainer(builder);
+ return new Context(driver, new Request(driver, URI.create("http://host/path")));
+ }
+ }
+
+ private static class MyCompletion implements CompletionHandler {
+
+ final boolean throwException;
+
+ MyCompletion(boolean throwException) {
+ this.throwException = throwException;
+ }
+
+ @Override
+ public void completed() {
+ if (throwException) {
+ throw new MyException();
+ }
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ if (throwException) {
+ throw new MyException();
+ }
+ }
+
+ static MyCompletion newInstance() {
+ return new MyCompletion(false);
+ }
+
+ static MyCompletion newException() {
+ return new MyCompletion(true);
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ final boolean eagerCompletion;
+ final boolean eagerFail;
+ final boolean writeException;
+ final boolean closeException;
+ CompletionHandler writeCompletion = null;
+ CompletionHandler closeCompletion = null;
+
+ MyContent(boolean eagerCompletion, boolean eagerFail, boolean writeException, boolean closeException) {
+ this.eagerCompletion = eagerCompletion;
+ this.eagerFail = eagerFail;
+ this.writeException = writeException;
+ this.closeException = closeException;
+ }
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ writeCompletion = handler;
+ if (eagerCompletion) {
+ writeCompletion.completed();
+ } else if (eagerFail) {
+ writeCompletion.failed(new MyException());
+ }
+ if (writeException) {
+ throw new MyException();
+ }
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ closeCompletion = handler;
+ if (eagerCompletion) {
+ closeCompletion.completed();
+ } else if (eagerFail) {
+ closeCompletion.failed(new MyException());
+ }
+ if (closeException) {
+ throw new MyException();
+ }
+ }
+
+ static MyContent newInstance() {
+ return new MyContent(false, false, false, false);
+ }
+
+ static MyContent newEagerCompletion() {
+ return new MyContent(true, false, false, false);
+ }
+
+ static MyContent newEagerFail() {
+ return new MyContent(false, true, false, false);
+ }
+
+ static MyContent newWriteException() {
+ return new MyContent(false, false, true, false);
+ }
+
+ static MyContent newWriteExceptionWithEagerCompletion() {
+ return new MyContent(true, false, true, false);
+ }
+
+ static MyContent newCloseException() {
+ return new MyContent(false, false, false, true);
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ final MyContent requestContent;
+ final boolean eagerRespond;
+ final boolean closeResponse;
+ final boolean throwException;
+ ContentChannel responseContent = null;
+ ResponseHandler handler = null;
+
+ MyRequestHandler(MyContent requestContent, boolean eagerRespond, boolean closeResponse,
+ boolean throwException)
+ {
+ this.requestContent = requestContent;
+ this.eagerRespond = eagerRespond;
+ this.closeResponse = closeResponse;
+ this.throwException = throwException;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ this.handler = handler;
+ if (eagerRespond) {
+ respond();
+ }
+ if (throwException) {
+ throw new MyException();
+ }
+ return requestContent;
+ }
+
+ ContentChannel respond() {
+ responseContent = handler.handleResponse(new Response(Response.Status.OK));
+ if (responseContent != null && closeResponse) {
+ responseContent.close(null);
+ }
+ return responseContent;
+ }
+
+ static MyRequestHandler newInstance() {
+ return new MyRequestHandler(MyContent.newInstance(), false, false, false);
+ }
+
+ static MyRequestHandler newEagerCompletion() {
+ return new MyRequestHandler(MyContent.newEagerCompletion(), false, false, false);
+ }
+
+ static MyRequestHandler newEagerFail() {
+ return new MyRequestHandler(MyContent.newEagerFail(), false, false, false);
+ }
+
+ static RequestHandler newRequestException() {
+ return new MyRequestHandler(null, false, false, true);
+ }
+
+ static MyRequestHandler newNullContent() {
+ return new MyRequestHandler(null, false, false, false);
+ }
+
+ static MyRequestHandler newNullContentWithEagerHandleResponse() {
+ return new MyRequestHandler(null, true, false, false);
+ }
+
+ static MyRequestHandler newNulContentWithEagerCloseResponse() {
+ return new MyRequestHandler(null, true, true, false);
+ }
+
+ static MyRequestHandler newRequestExceptionWithEagerHandleResponse() {
+ return new MyRequestHandler(null, true, false, true);
+ }
+
+ static MyRequestHandler newRequestExceptionWithEagerCloseResponse() {
+ return new MyRequestHandler(null, true, true, true);
+ }
+
+ static MyRequestHandler newContentWriteExceptionWithEagerCompletion() {
+ return new MyRequestHandler(MyContent.newWriteExceptionWithEagerCompletion(), true, true, false);
+ }
+
+ static MyRequestHandler newContentCloseException() {
+ return new MyRequestHandler(MyContent.newCloseException(), true, true, false);
+ }
+
+ static MyRequestHandler newEagerRespondWithEagerCompletion() {
+ return new MyRequestHandler(MyContent.newEagerCompletion(), true, false, false);
+ }
+
+ static MyRequestHandler newEagerCloseResponse() {
+ return new MyRequestHandler(MyContent.newInstance(), true, true, false);
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final MyContent content;
+ final boolean throwException;
+
+ MyResponseHandler(MyContent content, boolean throwException) {
+ this.content = content;
+ this.throwException = throwException;
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ if (throwException) {
+ throw new MyException();
+ }
+ return content;
+ }
+
+ static MyResponseHandler newInstance() {
+ return new MyResponseHandler(MyContent.newInstance(), false);
+ }
+
+ static MyResponseHandler newEagerCompletion() {
+ return new MyResponseHandler(MyContent.newEagerCompletion(), false);
+ }
+
+ static MyResponseHandler newEagerFail() {
+ return new MyResponseHandler(MyContent.newEagerFail(), false);
+ }
+
+ static MyResponseHandler newNullContent() {
+ return new MyResponseHandler(null, false);
+ }
+
+ static MyResponseHandler newResponseException() {
+ return new MyResponseHandler(null, true);
+ }
+
+ static MyResponseHandler newContentWriteException() {
+ return new MyResponseHandler(MyContent.newWriteException(), false);
+ }
+
+ static MyResponseHandler newContentCloseException() {
+ return new MyResponseHandler(MyContent.newCloseException(), false);
+ }
+ }
+
+ private static final class MyException extends RuntimeException {
+
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java
new file mode 100644
index 00000000000..15bd3b050d4
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerSnapshotTestCase.java
@@ -0,0 +1,211 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.application.BindingMatch;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerSnapshotTestCase {
+
+ @Test
+ public void requireThatServerHandlerCanBeResolved() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://foo/*", MyRequestHandler.newInstance());
+ driver.activateContainer(builder);
+
+ Request request = new Request(driver, URI.create("http://foo/"));
+ assertNotNull(request.container().resolveHandler(request));
+ assertNotNull(request.getBindingMatch());
+ request.release();
+
+ request = new Request(driver, URI.create("http://foo/"));
+ request.setServerRequest(false);
+ assertNull(request.container().resolveHandler(request));
+ assertNull(request.getBindingMatch());
+ request.release();
+
+ request = new Request(driver, URI.create("http://bar/"));
+ assertNull(request.container().resolveHandler(request));
+ assertNull(request.getBindingMatch());
+ request.release();
+
+ request = new Request(driver, URI.create("http://bar/"));
+ request.setServerRequest(false);
+ assertNull(request.container().resolveHandler(request));
+ assertNull(request.getBindingMatch());
+ request.release();
+
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatClientHandlerCanBeResolved() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.clientBindings().bind("http://foo/*", MyRequestHandler.newInstance());
+ driver.activateContainer(builder);
+
+ Request request = new Request(driver, URI.create("http://foo/"));
+ assertNull(request.container().resolveHandler(request));
+ assertNull(request.getBindingMatch());
+ request.release();
+
+ request = new Request(driver, URI.create("http://foo/"));
+ request.setServerRequest(false);
+ assertNotNull(request.container().resolveHandler(request));
+ assertNotNull(request.getBindingMatch());
+ request.release();
+
+ request = new Request(driver, URI.create("http://bar/"));
+ assertNull(request.container().resolveHandler(request));
+ assertNull(request.getBindingMatch());
+ request.release();
+
+ request = new Request(driver, URI.create("http://bar/"));
+ request.setServerRequest(false);
+ assertNull(request.container().resolveHandler(request));
+ assertNull(request.getBindingMatch());
+ request.release();
+
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatClientBindingsAreUsed() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.clientBindings().bind("http://host/path", MyRequestHandler.newInstance());
+ driver.activateContainer(builder);
+ Request request = new Request(driver, URI.create("http://host/path"));
+ assertNull(request.container().resolveHandler(request));
+ request.setServerRequest(false);
+ assertNotNull(request.container().resolveHandler(request));
+ request.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatBindingMatchIsSetByResolveHandler() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://*/*", MyRequestHandler.newInstance());
+ driver.activateContainer(builder);
+
+ Request request = new Request(driver, URI.create("http://localhost:69/status.html"));
+ assertNotNull(request.container().resolveHandler(request));
+ BindingMatch<RequestHandler> match = request.getBindingMatch();
+ assertNotNull(match);
+ assertEquals(3, match.groupCount());
+ assertEquals("localhost", match.group(0));
+ assertEquals("69", match.group(1));
+ assertEquals("status.html", match.group(2));
+ request.release();
+
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatNewRequestHasSameSnapshot() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Request foo = new Request(driver, URI.create("http://host/foo"));
+ Request bar = new Request(foo, URI.create("http://host/bar"));
+ assertSame(foo.container(), bar.container());
+ foo.release();
+ bar.release();
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatActiveInjectorIsUsed() {
+ final Object obj = new Object();
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(Object.class).toInstance(obj);
+ bind(String.class).annotatedWith(Names.named("foo")).toInstance("foo");
+ }
+ });
+ ActiveContainer active = new ActiveContainer(driver.newContainerBuilder());
+ ContainerSnapshot snapshot = new ContainerSnapshot(active, null, null);
+ assertSame(obj, snapshot.getInstance(Object.class));
+ assertEquals("foo", snapshot.getInstance(Key.get(String.class, Names.named("foo"))));
+ snapshot.release();
+ assertTrue(driver.close());
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ CompletionHandler writeCompletion = null;
+ CompletionHandler closeCompletion = null;
+ ByteBuffer writeBuf = null;
+ boolean closed = false;
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ writeBuf = buf;
+ writeCompletion = handler;
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ closed = true;
+ closeCompletion = handler;
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractResource implements RequestHandler {
+
+ final MyContent content = new MyContent();
+ Request request = null;
+ ResponseHandler handler = null;
+ boolean timeout = false;
+ boolean destroyed = false;
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ this.request = request;
+ this.handler = handler;
+ return content;
+ }
+
+ @Override
+ public void handleTimeout(Request request, ResponseHandler handler) {
+ timeout = true;
+ }
+
+ @Override
+ public void destroy() {
+ destroyed = true;
+ }
+
+ static MyRequestHandler newInstance() {
+ return new MyRequestHandler();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerTerminationTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerTerminationTestCase.java
new file mode 100644
index 00000000000..bc2591d94b0
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ContainerTerminationTestCase.java
@@ -0,0 +1,76 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.application.DeactivatedContainer;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerTerminationTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Object obj = new Object();
+ ContainerTermination termination = new ContainerTermination(obj);
+ assertSame(obj, termination.appContext());
+ }
+
+ @Test
+ public void requireThatAppContextIsFromBuilder() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ Object obj = new Object();
+ builder.setAppContext(obj);
+ driver.activateContainer(builder);
+ DeactivatedContainer container = driver.activateContainer(null);
+ assertSame(obj, container.appContext());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatEarlyTerminationIsNotified() {
+ ContainerTermination termination = new ContainerTermination(null);
+ termination.run();
+ MyTask task = new MyTask();
+ termination.notifyTermination(task);
+ assertTrue(task.done);
+ }
+
+ @Test
+ public void requireThatLaterTerminationIsNotified() {
+ ContainerTermination termination = new ContainerTermination(null);
+ MyTask task = new MyTask();
+ termination.notifyTermination(task);
+ assertFalse(task.done);
+ termination.run();
+ assertTrue(task.done);
+ }
+
+ @Test
+ public void requireThatNotifyCanOnlyBeCalledOnce() {
+ ContainerTermination termination = new ContainerTermination(null);
+ termination.notifyTermination(new MyTask());
+ try {
+ termination.notifyTermination(new MyTask());
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ private static class MyTask implements Runnable {
+
+ boolean done = false;
+
+ @Override
+ public void run() {
+ done = true;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/DefaultBindingSelectorTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/DefaultBindingSelectorTestCase.java
new file mode 100644
index 00000000000..1154d01dfe5
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/DefaultBindingSelectorTestCase.java
@@ -0,0 +1,38 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.Guice;
+import com.yahoo.jdisc.application.BindingSet;
+import com.yahoo.jdisc.application.BindingSetSelector;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class DefaultBindingSelectorTestCase {
+
+ @Test
+ public void requireThatClassIsInjectedByDefault() {
+ BindingSetSelector selector = Guice.createInjector().getInstance(BindingSetSelector.class);
+ assertTrue(selector instanceof DefaultBindingSelector);
+ }
+
+ @Test
+ public void requireThatDefaultSetIsAlwaysSelected() {
+ DefaultBindingSelector selector = new DefaultBindingSelector();
+ assertEquals(BindingSet.DEFAULT, selector.select(null));
+ for (int i = 0; i < 69; ++i) {
+ assertEquals(BindingSet.DEFAULT, selector.select(newUri()));
+ }
+ }
+
+ private static URI newUri() {
+ return URI.create("foo" + System.nanoTime() + "://bar" + System.nanoTime() + "/baz" + System.nanoTime());
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesTestCase.java
new file mode 100644
index 00000000000..a61ee4efc2d
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesTestCase.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileReader;
+import java.util.Properties;
+
+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 ExportPackagesTestCase {
+
+ @Test
+ public void requireThatPropertiesAreWritten() throws Exception {
+ File file = new File("target", ExportPackages.PROPERTIES_FILE);
+ file.deleteOnExit();
+ ExportPackages.main(new String[] { file.getAbsolutePath() });
+ assertTrue(file.exists());
+ Properties props = new Properties();
+ try (FileReader reader = new FileReader(file)) {
+ props.load(reader);
+ assertNotNull(props.getProperty(ExportPackages.EXPORT_PACKAGES));
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixFrameworkTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixFrameworkTestCase.java
new file mode 100644
index 00000000000..69696ff62ec
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixFrameworkTestCase.java
@@ -0,0 +1,41 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+import org.osgi.framework.BundleException;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FelixFrameworkTestCase {
+
+ @Test
+ public void requireThatLifecycleWorks() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.start();
+ felix.stop();
+ }
+
+ @Test
+ public void requireThatStopWithoutStartDoesNotThrowException() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.stop();
+ }
+
+ @Test
+ public void requireThatInstallCanThrowException() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.start();
+ try {
+ felix.installBundle("file:notfound.jar");
+ fail();
+ } catch (BundleException e) {
+
+ }
+ felix.stop();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixParamsTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixParamsTestCase.java
new file mode 100644
index 00000000000..216b79c1d7f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/FelixParamsTestCase.java
@@ -0,0 +1,67 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.junit.Test;
+import org.osgi.framework.Constants;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FelixParamsTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ FelixParams params = new FelixParams();
+ params.setCachePath("foo");
+ assertEquals("foo", params.getCachePath());
+ params.setLoggerEnabled(true);
+ assertTrue(params.isLoggerEnabled());
+ }
+
+ @Test
+ public void requireThatSystemPackagesAreNotReplaced() {
+ FelixParams params = new FelixParams();
+ Map<String, String> config = params.toConfig();
+ assertNotNull(config);
+ String str = config.get(Constants.FRAMEWORK_SYSTEMPACKAGES);
+ assertNotNull(str);
+ assertTrue(str.contains(ExportPackages.getSystemPackages()));
+
+ params.exportPackage("foo");
+ assertNotNull(config = params.toConfig());
+ assertNotNull(str = config.get(Constants.FRAMEWORK_SYSTEMPACKAGES));
+ assertTrue(str.contains(ExportPackages.getSystemPackages()));
+ assertTrue(str.contains("foo"));
+ }
+
+ @Test
+ public void requireThatExportsAreIncludedInConfig() {
+ FelixParams params = new FelixParams();
+ Map<String, String> config = params.toConfig();
+ assertNotNull(config);
+ String[] prev = config.get(Constants.FRAMEWORK_SYSTEMPACKAGES).split(",");
+
+ params.exportPackage("foo");
+ params.exportPackage("bar");
+ assertNotNull(config = params.toConfig());
+ String[] next = config.get(Constants.FRAMEWORK_SYSTEMPACKAGES).split(",");
+
+ assertEquals(prev.length + 2, next.length);
+
+ List<String> diff = new LinkedList<>();
+ diff.addAll(Arrays.asList(next));
+ diff.removeAll(Arrays.asList(prev));
+ assertEquals(2, diff.size());
+ assertTrue(diff.contains("foo"));
+ assertTrue(diff.contains("bar"));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogHandlerTestCase.java
new file mode 100644
index 00000000000..eb18e6f8e49
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogHandlerTestCase.java
@@ -0,0 +1,192 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.junit.Test;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.log.LogService;
+
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.ResourceBundle;
+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.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class OsgiLogHandlerTestCase {
+
+ @Test
+ public void requireThatLogRecordsArePublishedToLogService() {
+ MyLogService logService = new MyLogService();
+ Logger log = newLogger(logService);
+
+ log.log(Level.INFO, "foo");
+ assertEquals(OsgiLogHandler.toServiceLevel(Level.INFO), logService.lastLevel);
+ assertEquals("foo", logService.lastMessage);
+ assertNull(logService.lastThrowable);
+
+ Throwable t = new Throwable();
+ log.log(Level.SEVERE, "bar", t);
+ assertEquals(OsgiLogHandler.toServiceLevel(Level.SEVERE), logService.lastLevel);
+ assertEquals("bar", logService.lastMessage);
+ assertEquals(t, logService.lastThrowable);
+ }
+
+ @Test
+ public void requireThatStadardLogLevelsAreConverted() {
+ assertLogLevel(LogService.LOG_ERROR, Level.SEVERE);
+ assertLogLevel(LogService.LOG_WARNING, Level.WARNING);
+ assertLogLevel(LogService.LOG_INFO, Level.INFO);
+ assertLogLevel(LogService.LOG_DEBUG, Level.CONFIG);
+ assertLogLevel(LogService.LOG_DEBUG, Level.FINE);
+ assertLogLevel(LogService.LOG_DEBUG, Level.FINER);
+ assertLogLevel(LogService.LOG_DEBUG, Level.FINEST);
+ }
+
+ @Test
+ public void requireThatCustomLogLevelsAreConverted() {
+ for (int i = Level.ALL.intValue() - 69; i < Level.OFF.intValue() + 69; ++i) {
+ int expectedLevel;
+ if (i >= Level.SEVERE.intValue()) {
+ expectedLevel = LogService.LOG_ERROR;
+ } else if (i >= Level.WARNING.intValue()) {
+ expectedLevel = LogService.LOG_WARNING;
+ } else if (i >= Level.INFO.intValue()) {
+ expectedLevel = LogService.LOG_INFO;
+ } else {
+ expectedLevel = LogService.LOG_DEBUG;
+ }
+ assertLogLevel(expectedLevel, new MyLogLevel(i));
+ }
+ }
+
+ @Test
+ public void requireThatJdk14PropertiesAreAvailableThroughServiceReference() {
+ MyLogService logService = new MyLogService();
+
+ Logger log = newLogger(logService);
+ LogRecord record = new LogRecord(Level.INFO, "message");
+ record.setLoggerName("loggerName");
+ record.setMillis(69);
+ Object[] parameters = new Object[0];
+ record.setParameters(parameters);
+ ResourceBundle resouceBundle = new MyResourceBundle();
+ record.setResourceBundle(resouceBundle);
+ record.setResourceBundleName("resourceBundleName");
+ record.setSequenceNumber(69);
+ record.setSourceClassName("sourceClassName");
+ record.setSourceMethodName("sourceMethodName");
+ record.setThreadID(69);
+ Throwable thrown = new Throwable();
+ record.setThrown(thrown);
+ log.log(record);
+
+ ServiceReference ref = logService.lastServiceReference;
+ assertNotNull(ref);
+ assertTrue(Arrays.equals(new String[] { "LEVEL",
+ "LOGGER_NAME",
+ "MESSAGE",
+ "MILLIS",
+ "PARAMETERS",
+ "RESOURCE_BUNDLE",
+ "RESOURCE_BUNDLE_NAME",
+ "SEQUENCE_NUMBER",
+ "SOURCE_CLASS_NAME",
+ "SOURCE_METHOD_NAME",
+ "THREAD_ID",
+ "THROWN" },
+ ref.getPropertyKeys()));
+ assertEquals(Level.INFO, ref.getProperty("LEVEL"));
+ assertEquals("loggerName", ref.getProperty("LOGGER_NAME"));
+ assertEquals("message", ref.getProperty("MESSAGE"));
+ assertEquals(69L, ref.getProperty("MILLIS"));
+ assertSame(parameters, ref.getProperty("PARAMETERS"));
+ assertSame(resouceBundle, ref.getProperty("RESOURCE_BUNDLE"));
+ assertEquals("resourceBundleName", ref.getProperty("RESOURCE_BUNDLE_NAME"));
+ assertEquals(69L, ref.getProperty("SEQUENCE_NUMBER"));
+ assertEquals("sourceClassName", ref.getProperty("SOURCE_CLASS_NAME"));
+ assertEquals("sourceMethodName", ref.getProperty("SOURCE_METHOD_NAME"));
+ assertEquals(69, ref.getProperty("THREAD_ID"));
+ assertSame(thrown, ref.getProperty("THROWN"));
+ assertNull(ref.getProperty("unknown"));
+ }
+
+ private static void assertLogLevel(int expectedLevel, Level level) {
+ MyLogService logService = new MyLogService();
+ Logger log = newLogger(logService);
+ log.log(level, "message");
+ assertEquals(expectedLevel, logService.lastLevel);
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Logger newLogger(LogService logService) {
+ Logger log = Logger.getAnonymousLogger();
+ log.setUseParentHandlers(false);
+ log.setLevel(Level.ALL);
+ for (Handler handler : log.getHandlers()) {
+ log.removeHandler(handler);
+ }
+ log.addHandler(new OsgiLogHandler(logService));
+ return log;
+ }
+
+ private static class MyLogLevel extends Level {
+
+ protected MyLogLevel(int val) {
+ super("foo", val);
+ }
+ }
+
+ private static class MyLogService implements LogService {
+
+ ServiceReference lastServiceReference;
+ int lastLevel;
+ String lastMessage;
+ Throwable lastThrowable;
+
+ @Override
+ public void log(int level, String message) {
+ log(null, level, message, null);
+ }
+
+ @Override
+ public void log(int level, String message, Throwable throwable) {
+ log(null, level, message, throwable);
+ }
+
+ @Override
+ public void log(ServiceReference serviceReference, int level, String message) {
+ log(serviceReference, level, message, null);
+ }
+
+ @Override
+ public void log(ServiceReference serviceReference, int level, String message, Throwable throwable) {
+ lastServiceReference = serviceReference;
+ lastLevel = level;
+ lastMessage = message;
+ lastThrowable = throwable;
+ }
+ }
+
+ private static class MyResourceBundle extends ResourceBundle {
+
+ @Override
+ protected Object handleGetObject(String key) {
+ return null;
+ }
+
+ @Override
+ public Enumeration<String> getKeys() {
+ return null;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogManagerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogManagerTestCase.java
new file mode 100644
index 00000000000..32e4b37aca8
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogManagerTestCase.java
@@ -0,0 +1,184 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.log.LogService;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class OsgiLogManagerTestCase {
+
+ @Test
+ public void requireThatAllLogMethodsAreImplemented() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.start();
+
+ BundleContext ctx = felix.bundleContext();
+ OsgiLogManager manager = new OsgiLogManager(true);
+ manager.install(ctx);
+ MyLogService service = new MyLogService();
+ ctx.registerService(LogService.class.getName(), service, null);
+
+ manager.log(2, "a");
+ assertLast(service, null, 2, "a", null);
+
+ Throwable t1 = new Throwable();
+ manager.log(4, "b", t1);
+ assertLast(service, null, 4, "b", t1);
+
+ ServiceReference ref1 = Mockito.mock(ServiceReference.class);
+ manager.log(ref1, 8, "c");
+ assertLast(service, ref1, 8, "c", null);
+
+ ServiceReference ref2 = Mockito.mock(ServiceReference.class);
+ Throwable t2 = new Throwable();
+ manager.log(ref2, 16, "d", t2);
+ assertLast(service, ref2, 16, "d", t2);
+
+ manager.uninstall();
+ felix.stop();
+ }
+
+ @Test
+ public void requireThatLogManagerWritesToAllRegisteredLogServices() throws BundleException {
+ FelixFramework felix = TestDriver.newOsgiFramework();
+ felix.start();
+
+ BundleContext ctx = felix.bundleContext();
+ MyLogService foo = new MyLogService();
+ ServiceRegistration<LogService> fooReg = ctx.registerService(LogService.class, foo, null);
+
+ OsgiLogManager manager = new OsgiLogManager(true);
+ manager.install(ctx);
+
+ ServiceReference ref1 = Mockito.mock(ServiceReference.class);
+ Throwable t1 = new Throwable();
+ manager.log(ref1, 2, "a", t1);
+ assertLast(foo, ref1, 2, "a", t1);
+
+ MyLogService bar = new MyLogService();
+ ServiceRegistration<LogService> barReg = ctx.registerService(LogService.class, bar, null);
+
+ ServiceReference ref2 = Mockito.mock(ServiceReference.class);
+ Throwable t2 = new Throwable();
+ manager.log(ref2, 4, "b", t2);
+ assertLast(foo, ref2, 4, "b", t2);
+ assertLast(bar, ref2, 4, "b", t2);
+
+ MyLogService baz = new MyLogService();
+ ServiceRegistration<LogService> bazReg = ctx.registerService(LogService.class, baz, null);
+
+ ServiceReference ref3 = Mockito.mock(ServiceReference.class);
+ Throwable t3 = new Throwable();
+ manager.log(ref3, 8, "c", t3);
+ assertLast(foo, ref3, 8, "c", t3);
+ assertLast(bar, ref3, 8, "c", t3);
+ assertLast(baz, ref3, 8, "c", t3);
+
+ fooReg.unregister();
+
+ ServiceReference ref4 = Mockito.mock(ServiceReference.class);
+ Throwable t4 = new Throwable();
+ manager.log(ref4, 16, "d", t4);
+ assertLast(foo, ref3, 8, "c", t3);
+ assertLast(bar, ref4, 16, "d", t4);
+ assertLast(baz, ref4, 16, "d", t4);
+
+ barReg.unregister();
+
+ ServiceReference ref5 = Mockito.mock(ServiceReference.class);
+ Throwable t5 = new Throwable();
+ manager.log(ref5, 32, "e", t5);
+ assertLast(foo, ref3, 8, "c", t3);
+ assertLast(bar, ref4, 16, "d", t4);
+ assertLast(baz, ref5, 32, "e", t5);
+
+ bazReg.unregister();
+
+ ServiceReference ref6 = Mockito.mock(ServiceReference.class);
+ Throwable t6 = new Throwable();
+ manager.log(ref6, 64, "f", t6);
+ assertLast(foo, ref3, 8, "c", t3);
+ assertLast(bar, ref4, 16, "d", t4);
+ assertLast(baz, ref5, 32, "e", t5);
+
+ manager.uninstall();
+ felix.stop();
+ }
+
+ @Test
+ public void requireThatRootLoggerModificationCanBeDisabled() throws BundleException {
+ Logger logger = Logger.getLogger("");
+ logger.setLevel(Level.WARNING);
+
+ new OsgiLogManager(false).install(Mockito.mock(BundleContext.class));
+ assertEquals(Level.WARNING, logger.getLevel());
+
+ new OsgiLogManager(true).install(Mockito.mock(BundleContext.class));
+ assertEquals(Level.ALL, logger.getLevel());
+ }
+
+ @Test
+ public void requireThatRootLoggerLevelIsModifiedIfNoLoggerConfigIsGiven() {
+ Logger logger = Logger.getLogger("");
+ logger.setLevel(Level.WARNING);
+
+ OsgiLogManager.newInstance().install(Mockito.mock(BundleContext.class));
+
+ assertNull(System.getProperty("java.util.logging.config.file"));
+ assertEquals(Level.ALL, logger.getLevel());
+ }
+
+ private static void assertLast(MyLogService service, ServiceReference ref, int level, String message, Throwable t) {
+ assertSame(ref, service.lastServiceReference);
+ assertEquals(level, service.lastLevel);
+ assertEquals(message, service.lastMessage);
+ assertSame(t, service.lastThrowable);
+ }
+
+ private static class MyLogService implements LogService {
+
+ ServiceReference lastServiceReference;
+ int lastLevel;
+ String lastMessage;
+ Throwable lastThrowable;
+
+ @Override
+ public void log(int level, String message) {
+ log(null, level, message, null);
+ }
+
+ @Override
+ public void log(int level, String message, Throwable throwable) {
+ log(null, level, message, throwable);
+ }
+
+ @Override
+ public void log(ServiceReference serviceReference, int level, String message) {
+ log(serviceReference, level, message, null);
+ }
+
+ @Override
+ public void log(ServiceReference serviceReference, int level, String message, Throwable throwable) {
+ lastServiceReference = serviceReference;
+ lastLevel = level;
+ lastMessage = message;
+ lastThrowable = throwable;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogServiceTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogServiceTestCase.java
new file mode 100644
index 00000000000..fbd6f5a3f88
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/OsgiLogServiceTestCase.java
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.yahoo.jdisc.application.OsgiFramework;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.service.log.LogReaderService;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class OsgiLogServiceTestCase {
+
+ @Test
+ public void requireThatLogServiceIsRegistered() throws BundleException, InterruptedException {
+ OsgiFramework osgi = TestDriver.newOsgiFramework();
+ osgi.start();
+
+ ServiceTracker logs = newTracker(osgi, LogService.class);
+ ServiceTracker logReaders = newTracker(osgi, LogReaderService.class);
+ assertEquals(1, logs.getTrackingCount());
+ assertEquals(1, logReaders.getTrackingCount());
+
+ OsgiLogService service = new OsgiLogService();
+ service.start(osgi.bundleContext());
+
+ assertEquals(2, logs.getTrackingCount());
+ assertEquals(2, logReaders.getTrackingCount());
+ osgi.stop();
+ }
+
+ @Test
+ public void requireThatLogServiceCanNotBeStartedTwice() throws BundleException {
+ OsgiFramework osgi = TestDriver.newOsgiFramework();
+ osgi.start();
+
+ BundleContext ctx = osgi.bundleContext();
+ OsgiLogService service = new OsgiLogService();
+ service.start(ctx);
+
+ try {
+ service.start(ctx);
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+
+ osgi.stop();
+ }
+
+ @Test
+ public void requireThatLogServiceCanNotBeStoppedTwice() throws BundleException {
+ OsgiFramework osgi = TestDriver.newOsgiFramework();
+ osgi.start();
+
+ BundleContext ctx = osgi.bundleContext();
+ OsgiLogService service = new OsgiLogService();
+ service.start(ctx);
+ service.stop();
+
+ try {
+ service.stop();
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+
+ osgi.stop();
+ }
+
+ @Test
+ public void requireThatUnstartedLogServiceCanNotBeStopped() throws BundleException {
+ try {
+ new OsgiLogService().stop();
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatLogServiceCanNotStartWithoutBundleContext() throws BundleException {
+ try {
+ new OsgiLogService().start(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static ServiceTracker newTracker(OsgiFramework osgi, Class trackedClass) {
+ ServiceTracker tracker = new ServiceTracker(osgi.bundleContext(), trackedClass, null);
+ tracker.open();
+ return tracker;
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ScheduledQueueTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ScheduledQueueTestCase.java
new file mode 100644
index 00000000000..b208d5e5b9d
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ScheduledQueueTestCase.java
@@ -0,0 +1,149 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Queue;
+
+import static com.yahoo.jdisc.core.ScheduledQueue.MILLIS_PER_SLOT;
+import static com.yahoo.jdisc.core.ScheduledQueue.NUM_SLOTS;
+import static com.yahoo.jdisc.core.ScheduledQueue.NUM_SLOTS_UNDILATED;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ScheduledQueueTestCase {
+
+ @Test
+ public void requireThatSlotMaskPreventsOverflow() {
+ for (int slot = 0; slot < NUM_SLOTS * 2; ++slot) {
+ assertTrue((slot & ScheduledQueue.SLOT_MASK) < NUM_SLOTS);
+ }
+ }
+
+ @Test
+ public void requireThatIterShiftDiscardsSlotBits() {
+ for (int slot = 0; slot < NUM_SLOTS * 2; ++slot) {
+ assertEquals(slot / NUM_SLOTS, slot >> ScheduledQueue.ITER_SHIFT);
+ }
+ }
+
+ @Test
+ public void requireThatNewEntryDoesNotAcceptNull() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ try {
+ queue.newEntry(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatEntriesCanBeScheduled() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ Object foo = new Object();
+ ScheduledQueue.Entry entry = queue.newEntry(foo);
+ entry.scheduleAt(200);
+
+ assertDrainTo(queue, 150);
+ assertDrainTo(queue, 250, foo);
+ }
+
+ @Test
+ public void requireThatEntriesCanBeRescheduled() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ Object foo = new Object();
+ ScheduledQueue.Entry entry = queue.newEntry(foo);
+ entry.scheduleAt(200);
+ entry.scheduleAt(100);
+
+ assertDrainTo(queue, 150, foo);
+ assertDrainTo(queue, 250);
+ }
+
+ @Test
+ public void requireThatEntriesCanBeUnscheduled() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ Object foo = new Object();
+ ScheduledQueue.Entry entry = queue.newEntry(foo);
+ entry.scheduleAt(100);
+ entry.unschedule();
+
+ assertDrainTo(queue, 150);
+ }
+
+ @Test
+ public void requireThatDrainToOnlyDrainsExpiredEntries() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ Object foo = scheduleAt(queue, 100);
+ Object bar = scheduleAt(queue, 300);
+ Object baz = scheduleAt(queue, 200);
+
+ assertDrainTo(queue, 150, foo);
+ assertDrainTo(queue, 250, baz);
+ assertDrainTo(queue, 350, bar);
+ assertDrainTo(queue, 450);
+ }
+
+ @Test
+ public void requireThatEntriesDoNotExpireMoreThanOnce() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ Object foo = scheduleAt(queue, NUM_SLOTS * MILLIS_PER_SLOT + 50);
+
+ long now = 0;
+ for (int i = 0; i < NUM_SLOTS; ++i, now += MILLIS_PER_SLOT) {
+ assertDrainTo(queue, now);
+ }
+ assertDrainTo(queue, now += MILLIS_PER_SLOT, foo);
+ for (int i = 0; i < NUM_SLOTS; ++i, now += MILLIS_PER_SLOT) {
+ assertDrainTo(queue, now);
+ }
+ }
+
+ @Test
+ public void requireThatNegativeScheduleTranslatesToNow() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ Object foo = scheduleAt(queue, -100);
+
+ assertDrainTo(queue, 0, foo);
+ }
+
+ @Test
+ public void requireThatDrainToPerformsTimeDilationWhenOverloaded() {
+ ScheduledQueue queue = new ScheduledQueue(0);
+ List<Object> payloads = new LinkedList<>();
+ for (int i = 1; i <= NUM_SLOTS_UNDILATED + 1; ++i) {
+ payloads.add(scheduleAt(queue, i * MILLIS_PER_SLOT));
+ }
+
+ Queue<Object> expired = new LinkedList<>();
+ long currentTimeMillis = payloads.size() * MILLIS_PER_SLOT;
+ queue.drainTo(currentTimeMillis, expired);
+ assertEquals(NUM_SLOTS_UNDILATED, expired.size());
+
+ expired = new LinkedList<>();
+ currentTimeMillis += MILLIS_PER_SLOT;
+ queue.drainTo(currentTimeMillis, expired);
+ assertEquals(1, expired.size());
+ }
+
+ private static Object scheduleAt(ScheduledQueue queue, long expireAtMillis) {
+ Object obj = new Object();
+ queue.newEntry(obj).scheduleAt(expireAtMillis);
+ return obj;
+ }
+
+ private static void assertDrainTo(ScheduledQueue queue, long currentTimeMillis, Object... expected) {
+ Queue<Object> expired = new LinkedList<>();
+ queue.drainTo(currentTimeMillis, expired);
+ assertEquals(expected.length, expired.size());
+ assertEquals(Arrays.asList(expected), expired);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/SystemTimerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/SystemTimerTestCase.java
new file mode 100644
index 00000000000..e4d0df36260
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/SystemTimerTestCase.java
@@ -0,0 +1,31 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.Guice;
+import com.yahoo.jdisc.Timer;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SystemTimerTestCase {
+
+ @Test
+ public void requireThatClassIsInjectedByDefault() {
+ Timer timer = Guice.createInjector().getInstance(Timer.class);
+ assertTrue(timer instanceof SystemTimer);
+ }
+
+ @Test
+ public void requireThatSystemTimerIsSane() {
+ long before = System.currentTimeMillis();
+ long millis = new SystemTimer().currentTimeMillis();
+ long after = System.currentTimeMillis();
+
+ assertTrue(before <= millis);
+ assertTrue(after >= millis);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/TimeoutManagerImplTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/TimeoutManagerImplTestCase.java
new file mode 100644
index 00000000000..46464a9b05a
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/TimeoutManagerImplTestCase.java
@@ -0,0 +1,579 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.core;
+
+import com.google.inject.Binder;
+import com.google.inject.Module;
+import com.yahoo.jdisc.AbstractResource;
+import com.yahoo.jdisc.Container;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.TimeoutManager;
+import com.yahoo.jdisc.Timer;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestDeniedException;
+import com.yahoo.jdisc.handler.RequestDispatch;
+import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.NonWorkingRequest;
+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.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TimeoutManagerImplTestCase {
+
+ private static final String REQUEST_URI = "http://host/path";
+
+ @Test
+ public void requireThatDefaultIsNoTimeout() {
+ Context ctx = new Context(MyRequestHandler.newEagerResponse());
+ assertNull(ctx.dispatchRequest(null, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutCanBeSetByServerProvider() {
+ Context ctx = new Context(MyRequestHandler.newEagerResponse());
+ assertEquals(Long.valueOf(69), ctx.dispatchRequest(69L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutCanBeSetByRequestHandler() {
+ Context ctx = new Context(MyRequestHandler.newTimeoutWithEagerResponse(69));
+ assertEquals(Long.valueOf(69), ctx.dispatchRequest(null, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutRequestHandlerTimeoutHasPrecedence() {
+ Context ctx = new Context(MyRequestHandler.newTimeoutWithEagerResponse(6));
+ assertEquals(Long.valueOf(6), ctx.dispatchRequest(9L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatResponseCancelsTimeout() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newEagerResponse());
+ assertEquals(Response.Status.OK, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertEquals(Response.Status.OK, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatNullRequestContentCanTimeout() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newNullContent());
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutWorksAfterRequestDenied() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newFirstRequestDenied());
+ try {
+ ctx.dispatchRequest(null, MyResponseHandler.newInstance());
+ fail();
+ } catch (RequestDeniedException e) {
+
+ }
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutWorksAfterResponseDenied() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newInstance());
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newResponseDenied()));
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutWorksAfterResponseThrowsException() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newInstance());
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newThrowException()));
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutWorksAfterResponseInterruptsThread() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newInstance());
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInterruptThread()));
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatTimeoutOccursInOrder() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newInstance());
+ MyResponseHandler foo = MyResponseHandler.newInstance();
+ ctx.dispatchRequest(300L, foo);
+
+ MyResponseHandler bar = MyResponseHandler.newInstance();
+ ctx.dispatchRequest(100L, bar);
+
+ MyResponseHandler baz = MyResponseHandler.newInstance();
+ ctx.dispatchRequest(200L, baz);
+
+ ctx.forwardToTime(100);
+ assertFalse(foo.await(10, TimeUnit.MILLISECONDS));
+ assertTrue(bar.await(600, TimeUnit.SECONDS));
+ assertFalse(baz.await(10, TimeUnit.MILLISECONDS));
+
+ ctx.forwardToTime(200);
+ assertFalse(foo.await(10, TimeUnit.MILLISECONDS));
+ assertTrue(baz.await(600, TimeUnit.SECONDS));
+
+ ctx.forwardToTime(300);
+ assertTrue(foo.await(600, TimeUnit.SECONDS));
+
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatResponseHandlerIsWellBehavedAfterTimeout() throws InterruptedException {
+ Context ctx = new Context(MyRequestHandler.newInstance());
+ assertEquals(Response.Status.REQUEST_TIMEOUT, ctx.awaitResponse(69L, MyResponseHandler.newInstance()));
+
+ ContentChannel content = ctx.requestHandler.responseHandler.handleResponse(new Response(Response.Status.OK));
+ assertNotNull(content);
+
+ content.write(ByteBuffer.allocate(69), null);
+ MyCompletion completion = new MyCompletion();
+ content.write(ByteBuffer.allocate(69), completion);
+ assertTrue(completion.completed.await(600, TimeUnit.SECONDS));
+
+ completion = new MyCompletion();
+ content.close(completion);
+ assertTrue(completion.completed.await(600, TimeUnit.SECONDS));
+
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatManagedHandlerForwardsAllCalls() throws InterruptedException {
+ Request request = NonWorkingRequest.newInstance(REQUEST_URI);
+ MyRequestHandler requestHandler = MyRequestHandler.newInstance();
+ TimeoutManagerImpl timeoutManager = new TimeoutManagerImpl(Executors.defaultThreadFactory(),
+ new SystemTimer());
+ RequestHandler managedHandler = timeoutManager.manageHandler(requestHandler);
+
+ MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+ ContentChannel requestContent = managedHandler.handleRequest(request, responseHandler);
+ assertNotNull(requestContent);
+
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ requestContent.write(buf, null);
+ assertSame(buf, requestHandler.content.buf);
+ MyCompletion writeCompletion = new MyCompletion();
+ requestContent.write(buf = ByteBuffer.allocate(69), writeCompletion);
+ assertSame(buf, requestHandler.content.buf);
+ requestHandler.content.writeCompletion.completed();
+ assertTrue(writeCompletion.completed.await(600, TimeUnit.SECONDS));
+
+ MyCompletion closeCompletion = new MyCompletion();
+ requestContent.close(closeCompletion);
+ requestHandler.content.closeCompletion.completed();
+ assertTrue(closeCompletion.completed.await(600, TimeUnit.SECONDS));
+
+ managedHandler.release();
+ assertTrue(requestHandler.destroyed);
+
+ Response response = new Response(Response.Status.OK);
+ ContentChannel responseContent = requestHandler.responseHandler.handleResponse(response);
+ assertNotNull(responseContent);
+
+ responseContent.write(buf = ByteBuffer.allocate(69), null);
+ assertSame(buf, responseHandler.content.buf);
+ responseContent.write(buf = ByteBuffer.allocate(69), writeCompletion = new MyCompletion());
+ assertSame(buf, responseHandler.content.buf);
+ responseHandler.content.writeCompletion.completed();
+ assertTrue(writeCompletion.completed.await(600, TimeUnit.SECONDS));
+
+ responseContent.close(closeCompletion = new MyCompletion());
+ responseHandler.content.closeCompletion.completed();
+ assertTrue(closeCompletion.completed.await(600, TimeUnit.SECONDS));
+
+ assertSame(response, responseHandler.response.get());
+ }
+
+ @Test
+ public void requireThatTimeoutOccursAtExpectedTime() throws InterruptedException {
+ final Context ctx = new Context(MyRequestHandler.newInstance());
+ final MyResponseHandler responseHandler = MyResponseHandler.newInstance();
+
+ ctx.forwardToTime(100);
+ new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ Request request = new Request(ctx.driver, URI.create(REQUEST_URI));
+ request.setTimeout(300, TimeUnit.MILLISECONDS);
+ return request;
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return responseHandler.handleResponse(response);
+ }
+ }.dispatch();
+
+ ctx.forwardToTime(300);
+ assertFalse(responseHandler.await(100, TimeUnit.MILLISECONDS));
+ ctx.forwardToTime(400);
+ assertTrue(responseHandler.await(600, TimeUnit.SECONDS));
+
+ Response response = responseHandler.response.get();
+ assertNotNull(response);
+ assertEquals(Response.Status.REQUEST_TIMEOUT, response.getStatus());
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatQueueEntryIsRemovedWhenResponseHandlerIsCalledBeforeTimeout() {
+ Context ctx = new Context(MyRequestHandler.newInstance());
+ ctx.dispatchRequest(69L, MyResponseHandler.newInstance());
+ assertTrue(ctx.awaitQueueSize(1, 600, TimeUnit.SECONDS));
+ ctx.requestHandler.respond();
+ assertTrue(ctx.awaitQueueSize(0, 600, TimeUnit.SECONDS));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatNoEntryIsMadeIfTimeoutIsNull() {
+ Context ctx = new Context(MyRequestHandler.newInstance());
+ ctx.dispatchRequest(null, MyResponseHandler.newInstance());
+ assertFalse(ctx.awaitQueueSize(1, 100, TimeUnit.MILLISECONDS));
+ assertTrue(ctx.awaitQueueSize(0, 600, TimeUnit.SECONDS));
+ ctx.requestHandler.respond();
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatNoEntryIsMadeIfHandleRequestCallsHandleResponse() {
+ Context ctx = new Context(MyRequestHandler.newEagerResponse());
+ ctx.dispatchRequest(69L, MyResponseHandler.newInstance());
+ assertFalse(ctx.awaitQueueSize(1, 100, TimeUnit.MILLISECONDS));
+ assertTrue(ctx.awaitQueueSize(0, 600, TimeUnit.SECONDS));
+ assertTrue(ctx.close());
+ }
+
+ @Test
+ public void requireThatNoEntryIsMadeIfTimeoutHandlerHasBeenSet() {
+ final Context ctx = new Context(MyRequestHandler.newInstance());
+ new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ Request request = new Request(ctx.driver, URI.create(REQUEST_URI));
+ request.setTimeout(10, TimeUnit.MILLISECONDS);
+ request.setTimeoutManager(new TimeoutManager() {
+
+ @Override
+ public void scheduleTimeout(Request request) {
+
+ }
+ });
+ return request;
+ }
+ }.dispatch();
+
+ assertFalse(ctx.awaitQueueSize(1, 100, TimeUnit.MILLISECONDS));
+ assertTrue(ctx.awaitQueueSize(0, 600, TimeUnit.SECONDS));
+ ctx.requestHandler.respond();
+ assertTrue(ctx.close());
+ }
+
+ private static class Context implements Module, Timer {
+
+ final MyRequestHandler requestHandler;
+ final TimeoutManagerImpl timeoutManager;
+ final TestDriver driver;
+ long millis = 0;
+
+ Context(MyRequestHandler requestHandler) {
+ this.requestHandler = requestHandler;
+ this.driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(this);
+
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind(REQUEST_URI, requestHandler);
+ driver.activateContainer(builder);
+
+ Container ref = driver.newReference(URI.create(REQUEST_URI));
+ timeoutManager = ref.getInstance(TimeoutManagerImpl.class);
+ ref.release();
+ }
+
+ void forwardToTime(long millis) {
+ while (this.millis < millis) {
+ this.millis += ScheduledQueue.MILLIS_PER_SLOT;
+ timeoutManager.checkTasks(this.millis);
+ }
+ }
+
+ boolean close() {
+ return driver.close();
+ }
+
+ @Override
+ public void configure(Binder binder) {
+ binder.bind(Timer.class).toInstance(this);
+ }
+
+ @Override
+ public long currentTimeMillis() {
+ return millis;
+ }
+
+ int awaitResponse(Long serverProviderTimeout, MyResponseHandler responseHandler) throws InterruptedException {
+ Long timeout = new MyServerProvider(serverProviderTimeout).dispatchRequest(driver, responseHandler);
+ long timeoutAt;
+ if (timeout == null) {
+ timeoutAt = millis + TimeUnit.SECONDS.toMillis(120);
+ } else {
+ timeoutAt = millis + timeout;
+ }
+ forwardToTime(timeoutAt);
+ if (!responseHandler.await(600, TimeUnit.SECONDS)) {
+ fail("Request handler failed to respond within allocated time.");
+ }
+ return responseHandler.response.get().getStatus();
+ }
+
+ boolean awaitQueueSize(int expectedSize, int timeout, TimeUnit unit) {
+ for (long i = 0, len = unit.toMillis(timeout) / 100; i < len; ++i) {
+ if (timeoutManager.queueSize() == expectedSize) {
+ return true;
+ }
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ fail();
+ }
+ }
+ return false;
+ }
+
+ public Long dispatchRequest(Long serverProviderTimeout, MyResponseHandler responseHandler) {
+ return new MyServerProvider(serverProviderTimeout).dispatchRequest(driver, responseHandler);
+ }
+ }
+
+ private static class MyServerProvider {
+
+ final Long timeout;
+
+ MyServerProvider(Long timeout) {
+ this.timeout = timeout;
+ }
+
+ Long dispatchRequest(CurrentContainer container, ResponseHandler responseHandler) {
+ Request request = null;
+ ContentChannel content = null;
+ try {
+ request = new Request(container, URI.create(REQUEST_URI));
+ if (timeout != null) {
+ request.setTimeout(timeout, TimeUnit.MILLISECONDS);
+ }
+ content = request.connect(responseHandler);
+ } finally {
+ if (request != null) {
+ request.release();
+ }
+ if (content != null) {
+ content.close(null);
+ }
+ }
+ return request.getTimeout(TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private static class MyCompletion implements CompletionHandler {
+
+ final CountDownLatch completed = new CountDownLatch(1);
+
+ @Override
+ public void completed() {
+ completed.countDown();
+ }
+
+ @Override
+ public void failed(Throwable t) {
+
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ ByteBuffer buf;
+ CompletionHandler writeCompletion;
+ CompletionHandler closeCompletion;
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ this.buf = buf;
+ this.writeCompletion = handler;
+ if (handler != null) {
+ handler.completed();
+ }
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ this.closeCompletion = handler;
+ if (handler != null) {
+ handler.completed();
+ }
+ }
+
+ static MyContent newInstance() {
+ return new MyContent();
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final AtomicReference<CountDownLatch> latch = new AtomicReference<>(new CountDownLatch(1));
+ final AtomicReference<Response> response = new AtomicReference<>();
+ final MyContent content;
+ final boolean throwException;
+ final boolean interruptThread;
+
+ MyResponseHandler(MyContent content, boolean throwException, boolean interruptThread) {
+ this.content = content;
+ this.throwException = throwException;
+ this.interruptThread = interruptThread;
+ }
+
+ boolean await(long timeout, TimeUnit unit) throws InterruptedException {
+ return latch.get().await(timeout, unit);
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ if (this.response.getAndSet(response) != null) {
+ throw new IllegalStateException("Response already received.");
+ }
+ latch.get().countDown();
+ if (interruptThread) {
+ Thread.currentThread().interrupt();
+ }
+ if (throwException) {
+ throw new MyException();
+ }
+ return content;
+ }
+
+ static MyResponseHandler newInstance() {
+ return new MyResponseHandler(MyContent.newInstance(), false, false);
+ }
+
+ static MyResponseHandler newResponseDenied() {
+ return new MyResponseHandler(null, false, false);
+ }
+
+ static MyResponseHandler newThrowException() {
+ return new MyResponseHandler(null, true, false);
+ }
+
+ static MyResponseHandler newInterruptThread() {
+ return new MyResponseHandler(MyContent.newInstance(), false, true);
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractResource implements RequestHandler {
+
+ final MyContent content;
+ final Long timeout;
+ int numDenied;
+ int numEager;
+ Request request = null;
+ ResponseHandler responseHandler = null;
+ boolean destroyed = false;
+
+ MyRequestHandler(int numDenied, MyContent content, Long timeout, int numEager) {
+ this.numDenied = numDenied;
+ this.content = content;
+ this.timeout = timeout;
+ this.numEager = numEager;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ if (--numDenied >= 0) {
+ throw new RequestDeniedException(request);
+ }
+ this.request = request;
+ this.responseHandler = handler;
+ if (timeout != null) {
+ request.setTimeout(timeout, TimeUnit.MILLISECONDS);
+ }
+ if (--numEager >= 0) {
+ respond();
+ }
+ return content;
+ }
+
+ @Override
+ public void handleTimeout(Request request, ResponseHandler handler) {
+ Response.dispatchTimeout(handler);
+ }
+
+ @Override
+ protected void destroy() {
+ destroyed = true;
+ }
+
+ void respond() {
+ ContentChannel content = responseHandler.handleResponse(new Response(Response.Status.OK));
+ if (content != null) {
+ content.close(null);
+ }
+ }
+
+ static MyRequestHandler newInstance() {
+ return new MyRequestHandler(0, MyContent.newInstance(), null, 0);
+ }
+
+ static MyRequestHandler newTimeoutWithEagerResponse(long millis) {
+ return new MyRequestHandler(0, MyContent.newInstance(), millis, Integer.MAX_VALUE);
+ }
+
+ static MyRequestHandler newFirstRequestDenied() {
+ return new MyRequestHandler(1, MyContent.newInstance(), null, 0);
+ }
+
+ static MyRequestHandler newEagerResponse() {
+ return new MyRequestHandler(0, MyContent.newInstance(), null, Integer.MAX_VALUE);
+ }
+
+ public static MyRequestHandler newNullContent() {
+ return new MyRequestHandler(0, null, null, 0);
+ }
+ }
+
+ private static class MyException extends RuntimeException {
+
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractContentOutputStreamTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractContentOutputStreamTestCase.java
new file mode 100644
index 00000000000..d314b303bd4
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractContentOutputStreamTestCase.java
@@ -0,0 +1,127 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class AbstractContentOutputStreamTestCase {
+
+ @Test
+ public void requireThatStreamCanBeWrittenTo() throws IOException {
+ MyOutputStream out = new MyOutputStream();
+ int len = 2 * AbstractContentOutputStream.BUFFERSIZE;
+ for (int i = 0; i < len; ++i) {
+ out.write(69);
+ out.write(new byte[] { });
+ out.write(new byte[] { 6, 9 });
+ out.write(new byte[] { 6, 69, 9 }, 1, 0); // zero length
+ out.write(new byte[] { 6, 69, 9 }, 1, 1);
+ }
+ out.close();
+
+ InputStream in = out.toInputStream();
+ for (int i = 0; i < len; ++i) {
+ assertEquals(69, in.read());
+ assertEquals(6, in.read());
+ assertEquals(9, in.read());
+ assertEquals(69, in.read());
+ }
+ assertEquals(-1, in.read());
+ assertTrue(out.closed);
+ }
+
+ @Test
+ public void requireThatBigBuffersAreWrittenInOrder() throws IOException {
+ MyOutputStream out = new MyOutputStream();
+ out.write(6);
+ out.write(new byte[2 * AbstractContentOutputStream.BUFFERSIZE]);
+ out.write(9);
+ out.close();
+ InputStream in = out.toInputStream();
+ assertEquals(6, in.read());
+ for (int i = 0, len = 2 * AbstractContentOutputStream.BUFFERSIZE; i < len; ++i) {
+ assertEquals(0, in.read());
+ }
+ assertEquals(9, in.read());
+ assertEquals(-1, in.read());
+ assertTrue(out.closed);
+ }
+
+ @Test
+ public void requireThatEmptyBuffersAreNotFlushed() throws Exception {
+ MyOutputStream out = new MyOutputStream();
+ out.close();
+ assertTrue(out.writes.isEmpty());
+ assertTrue(out.closed);
+ }
+
+ @Test
+ public void requireThatNoExcessiveBytesAreWritten() throws Exception {
+ MyOutputStream out = new MyOutputStream();
+ out.write(new byte[] { 6, 9 });
+ out.close();
+
+ InputStream in = out.toInputStream();
+ assertEquals(2, in.available());
+ assertEquals(6, in.read());
+ assertEquals(9, in.read());
+ assertEquals(0, in.available());
+ assertEquals(-1, in.read());
+ assertTrue(out.closed);
+ }
+
+ @Test
+ public void requireThatWrittenArraysAreCopied() throws Exception {
+ MyOutputStream out = new MyOutputStream();
+ byte[] buf = new byte[1];
+ for (byte b = 0; b < 127; ++b) {
+ buf[0] = b;
+ out.write(buf);
+ }
+ out.close();
+
+ InputStream in = out.toInputStream();
+ for (byte b = 0; b < 127; ++b) {
+ assertEquals(b, in.read());
+ }
+ }
+
+ private static class MyOutputStream extends AbstractContentOutputStream {
+
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ final List<ByteBuffer> writes = new ArrayList<>();
+ boolean closed;
+
+ @Override
+ protected void doFlush(ByteBuffer buf) {
+ writes.add(buf);
+ buf = buf.slice();
+ while (buf.hasRemaining()) {
+ out.write(buf.get());
+ }
+ }
+
+ @Override
+ protected void doClose() {
+ closed = true;
+ }
+
+ InputStream toInputStream() {
+ return new ByteArrayInputStream(out.toByteArray());
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractRequestHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractRequestHandlerTestCase.java
new file mode 100644
index 00000000000..661165ac5e8
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/AbstractRequestHandlerTestCase.java
@@ -0,0 +1,187 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.test.NonWorkingRequest;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AbstractRequestHandlerTestCase {
+
+ private static final Charset UTF8 = Charset.forName("utf-8");
+ private static int NUM_REQUESTS = 666;
+
+ @Test
+ public void requireThatHandleTimeoutIsImplemented() throws Exception {
+ FutureResponse handler = new FutureResponse();
+ new AbstractRequestHandler() {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ return null;
+ }
+ }.handleTimeout(NonWorkingRequest.newInstance("http://localhost/"), handler);
+ Response response = handler.get(600, TimeUnit.SECONDS);
+ assertNotNull(response);
+ assertEquals(Response.Status.REQUEST_TIMEOUT, response.getStatus());
+ }
+
+ @Test
+ public void requireThatHelloWorldWorks() throws InterruptedException {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://localhost/", new HelloWorldHandler());
+ driver.activateContainer(builder);
+
+ for (int i = 0; i < NUM_REQUESTS; ++i) {
+ MyResponseHandler responseHandler = new MyResponseHandler();
+ driver.newRequestDispatch("http://localhost/", responseHandler).dispatch();
+
+ ByteBuffer buf = responseHandler.content.read();
+ assertNotNull(buf);
+ assertEquals("Hello World!", new String(buf.array(), buf.arrayOffset() + buf.position(), buf.remaining(), UTF8));
+ assertNull(responseHandler.content.read());
+ }
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatEchoWorks() throws InterruptedException {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://localhost/", new EchoHandler());
+ driver.activateContainer(builder);
+
+ for (int i = 0; i < NUM_REQUESTS; ++i) {
+ MyResponseHandler responseHandler = new MyResponseHandler();
+ RequestDispatch dispatch = driver.newRequestDispatch("http://localhost/", responseHandler);
+ FastContentWriter requestContent = dispatch.connectFastWriter();
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ requestContent.write(buf);
+ requestContent.close();
+
+ assertSame(buf, responseHandler.content.read());
+ assertNull(responseHandler.content.read());
+ }
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatForwardWorks() throws InterruptedException {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://localhost/", new ForwardHandler());
+ builder.clientBindings().bind("http://remotehost/", new EchoHandler());
+ driver.activateContainer(builder);
+
+ for (int i = 0; i < NUM_REQUESTS; ++i) {
+ MyResponseHandler responseHandler = new MyResponseHandler();
+ RequestDispatch dispatch = driver.newRequestDispatch("http://localhost/", responseHandler);
+ FastContentWriter requestContent = dispatch.connectFastWriter();
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ requestContent.write(buf);
+ requestContent.close();
+
+ assertSame(buf, responseHandler.content.read());
+ assertNull(responseHandler.content.read());
+ }
+ assertTrue(driver.close());
+ }
+
+ private static class HelloWorldHandler extends AbstractRequestHandler {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ FastContentWriter writer = ResponseDispatch.newInstance(Response.Status.OK).connectFastWriter(handler);
+ try {
+ writer.write("Hello World!");
+ } finally {
+ writer.close();
+ }
+ return null;
+ }
+ }
+
+ private static class EchoHandler extends AbstractRequestHandler {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ return new WritingContentChannel(new FastContentWriter(ResponseDispatch.newInstance(Response.Status.OK).connect(handler)));
+ }
+ }
+
+ private static class ForwardHandler extends AbstractRequestHandler {
+
+ @Override
+ public ContentChannel handleRequest(final Request request, final ResponseHandler handler) {
+ return new WritingContentChannel(new FastContentWriter(new RequestDispatch() {
+
+ @Override
+ public Request newRequest() {
+ return new Request(request, URI.create("http://remotehost/"));
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return handler.handleResponse(response);
+ }
+ }.connect()));
+ }
+ }
+
+ private static class WritingContentChannel implements ContentChannel {
+
+ final FastContentWriter writer;
+
+ WritingContentChannel(FastContentWriter writer) {
+ this.writer = writer;
+ }
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ try {
+ writer.write(buf);
+ handler.completed();
+ } catch (Exception e) {
+ handler.failed(e);
+ }
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ try {
+ writer.close();
+ handler.completed();
+ } catch (Exception e) {
+ handler.failed(e);
+ }
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final ReadableContentChannel content = new ReadableContentChannel();
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return content;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BindingNotFoundTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BindingNotFoundTestCase.java
new file mode 100644
index 00000000000..58900174ee2
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BindingNotFoundTestCase.java
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BindingNotFoundTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ URI uri = URI.create("http://host/path");
+ BindingNotFoundException e = new BindingNotFoundException(uri);
+ assertEquals(uri, e.uri());
+ }
+
+ @Test
+ public void requireThatBindingNotFoundIsThrown() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Request request = new Request(driver, URI.create("http://host/path"));
+ try {
+ request.connect(new MyResponseHandler());
+ fail();
+ } catch (BindingNotFoundException e) {
+ assertEquals(request.getUri(), e.uri());
+ }
+ request.release();
+ driver.close();
+ }
+
+ private class MyResponseHandler implements ResponseHandler {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return null;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BlockingContentWriterTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BlockingContentWriterTestCase.java
new file mode 100644
index 00000000000..b539c135edb
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BlockingContentWriterTestCase.java
@@ -0,0 +1,210 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.*;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BlockingContentWriterTestCase {
+
+ @Test
+ public void requireThatContentChannelIsNotNull() {
+ try {
+ new BlockingContentWriter(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatWriteDeliversBuffer() throws InterruptedException {
+ MyContent content = MyContent.newNonBlockingContent();
+ BlockingContentWriter writer = new BlockingContentWriter(content);
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ writer.write(buf);
+ assertSame(buf, content.writeBuf);
+ }
+
+ @Test
+ public void requireThatWriteIsBlocking() throws Exception {
+ MyContent content = MyContent.newBlockingContent();
+ BlockingContentWriter writer = new BlockingContentWriter(content);
+ FutureTask<Boolean> task = new FutureTask<>(new WriteTask(writer, ByteBuffer.allocate(69)));
+ Executors.newSingleThreadExecutor().submit(task);
+ content.writeLatch.await(600, TimeUnit.SECONDS);
+ try {
+ task.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ content.writeCompletion.completed();
+ assertTrue(task.get(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatWriteExceptionIsThrown() throws Exception {
+ Throwable throwMe = new RuntimeException();
+ try {
+ new BlockingContentWriter(MyContent.newFailedContent(throwMe)).write(ByteBuffer.allocate(69));
+ } catch (Throwable t) {
+ assertSame(throwMe, t);
+ }
+ throwMe = new Error();
+ try {
+ new BlockingContentWriter(MyContent.newFailedContent(throwMe)).write(ByteBuffer.allocate(69));
+ } catch (Throwable t) {
+ assertSame(throwMe, t);
+ }
+ throwMe = new Exception();
+ try {
+ new BlockingContentWriter(MyContent.newFailedContent(throwMe)).write(ByteBuffer.allocate(69));
+ } catch (Throwable t) {
+ assertNotSame(throwMe, t);
+ assertSame(throwMe, t.getCause());
+ }
+ }
+
+ @Test
+ public void requireThatCloseIsBlocking() throws Exception {
+ MyContent content = MyContent.newBlockingContent();
+ BlockingContentWriter writer = new BlockingContentWriter(content);
+ FutureTask<Boolean> task = new FutureTask<>(new CloseTask(writer));
+ Executors.newSingleThreadExecutor().submit(task);
+ content.closeLatch.await(600, TimeUnit.SECONDS);
+ try {
+ task.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ content.closeCompletion.completed();
+ assertTrue(task.get(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatCloseExceptionIsThrown() throws Exception {
+ Throwable throwMe = new RuntimeException();
+ try {
+ new BlockingContentWriter(MyContent.newFailedContent(throwMe)).close();
+ } catch (Throwable t) {
+ assertSame(throwMe, t);
+ }
+ throwMe = new Error();
+ try {
+ new BlockingContentWriter(MyContent.newFailedContent(throwMe)).close();
+ } catch (Throwable t) {
+ assertSame(throwMe, t);
+ }
+ throwMe = new Exception();
+ try {
+ new BlockingContentWriter(MyContent.newFailedContent(throwMe)).close();
+ } catch (Throwable t) {
+ assertNotSame(throwMe, t);
+ assertSame(throwMe, t.getCause());
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ final CountDownLatch writeLatch = new CountDownLatch(1);
+ final CountDownLatch closeLatch = new CountDownLatch(1);
+ final Throwable eagerFailure;
+ final boolean eagerCompletion;
+ CompletionHandler writeCompletion;
+ CompletionHandler closeCompletion;
+ ByteBuffer writeBuf;
+
+ MyContent(boolean eagerCompletion, Throwable eagerFailure) {
+ this.eagerCompletion = eagerCompletion;
+ this.eagerFailure = eagerFailure;
+ }
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ writeBuf = buf;
+ if (eagerFailure != null) {
+ handler.failed(eagerFailure);
+ } else if (eagerCompletion) {
+ handler.completed();
+ } else {
+ writeCompletion = handler;
+ }
+ writeLatch.countDown();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ if (eagerFailure != null) {
+ handler.failed(eagerFailure);
+ } else if (eagerCompletion) {
+ handler.completed();
+ } else {
+ closeCompletion = handler;
+ }
+ closeLatch.countDown();
+ }
+
+ static MyContent newBlockingContent() {
+ return new MyContent(false, null);
+ }
+
+ static MyContent newNonBlockingContent() {
+ return new MyContent(true, null);
+ }
+
+ static MyContent newFailedContent(Throwable e) {
+ return new MyContent(false, e);
+ }
+ }
+
+ private static class WriteTask implements Callable<Boolean> {
+
+ final BlockingContentWriter writer;
+ final ByteBuffer buf;
+
+ WriteTask(BlockingContentWriter writer, ByteBuffer buf) {
+ this.writer = writer;
+ this.buf = buf;
+ }
+
+ @Override
+ public Boolean call() {
+ try {
+ writer.write(buf);
+ } catch (Throwable t) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ private static class CloseTask implements Callable<Boolean> {
+
+ final BlockingContentWriter writer;
+
+ CloseTask(BlockingContentWriter writer) {
+ this.writer = writer;
+ }
+
+ @Override
+ public Boolean call() {
+ try {
+ writer.close();
+ } catch (Throwable t) {
+ return false;
+ }
+ return true;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BufferedContentChannelTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BufferedContentChannelTestCase.java
new file mode 100644
index 00000000000..c6714f11203
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/BufferedContentChannelTestCase.java
@@ -0,0 +1,257 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.*;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BufferedContentChannelTestCase {
+
+ @Test
+ public void requireThatIsConnectedWorks() {
+ MyContent target = new MyContent();
+ BufferedContentChannel content = new BufferedContentChannel();
+ assertFalse(content.isConnected());
+ content.connectTo(target);
+ assertTrue(content.isConnected());
+ }
+
+ @Test
+ public void requireThatConnectToNullThrowsException() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ try {
+ content.connectTo(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatWriteAfterCloseThrowsException() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ content.close(null);
+ try {
+ content.write(ByteBuffer.allocate(69), null);
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatCloseAfterCloseThrowsException() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ content.close(null);
+ try {
+ content.close(null);
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatConnecToAfterConnecToThrowsException() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ content.connectTo(new MyContent());
+ try {
+ content.connectTo(new MyContent());
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatWriteBeforeConnectToWritesToTarget() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ MyCompletion completion = new MyCompletion();
+ content.write(buf, completion);
+ MyContent target = new MyContent();
+ content.connectTo(target);
+ assertSame(buf, target.writeBuf);
+ assertSame(completion, target.writeCompletion);
+ }
+
+ @Test
+ public void requireThatWriteAfterConnectToWritesToTarget() {
+ MyContent target = new MyContent();
+ BufferedContentChannel content = new BufferedContentChannel();
+ content.connectTo(target);
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ MyCompletion completion = new MyCompletion();
+ content.write(buf, completion);
+ assertSame(buf, target.writeBuf);
+ assertSame(completion, target.writeCompletion);
+ }
+
+ @Test
+ public void requireThatCloseBeforeConnectToClosesTarget() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ MyCompletion completion = new MyCompletion();
+ content.close(completion);
+ MyContent target = new MyContent();
+ content.connectTo(target);
+ assertTrue(target.closed);
+ assertSame(completion, target.closeCompletion);
+ }
+
+ @Test
+ public void requireThatCloseAfterConnectToClosesTarget() {
+ MyContent target = new MyContent();
+ BufferedContentChannel content = new BufferedContentChannel();
+ content.connectTo(target);
+ MyCompletion completion = new MyCompletion();
+ content.close(completion);
+ assertTrue(target.closed);
+ assertSame(completion, target.closeCompletion);
+ }
+
+ @Test
+ public void requireThatIsConnectedIsTrueWhenConnectedBeforeClose() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ assertFalse(content.isConnected());
+ content.connectTo(new MyContent());
+ assertTrue(content.isConnected());
+ content.close(null);
+ assertTrue(content.isConnected());
+ }
+
+ @Test
+ public void requireThatIsConnectedIsTrueWhenClosedBeforeConnected() {
+ BufferedContentChannel content = new BufferedContentChannel();
+ assertFalse(content.isConnected());
+ content.close(null);
+ assertFalse(content.isConnected());
+ content.connectTo(new MyContent());
+ assertTrue(content.isConnected());
+ }
+
+ @Test
+ public void requireThatContentIsThreadSafe() throws Exception {
+ ExecutorService executor = Executors.newFixedThreadPool(101);
+ for (int run = 0; run < 69; ++run) {
+ List<ByteBuffer> bufs = new LinkedList<>();
+ for (int buf = 0; buf < 100; ++buf) {
+ bufs.add(ByteBuffer.allocate(buf));
+ }
+ BufferedContentChannel content = new BufferedContentChannel();
+ List<Callable<Boolean>> tasks = new LinkedList<>();
+ for (ByteBuffer buf : bufs) {
+ tasks.add(new WriteTask(content, buf));
+ }
+ MyConcurrentContent target = new MyConcurrentContent();
+ tasks.add(new ConnectTask(content, target));
+ List<Future<Boolean>> results = executor.invokeAll(tasks);
+ for (Future<Boolean> result : results) {
+ assertTrue(result.get());
+ }
+ assertEquals(bufs.size(), target.bufs.size());
+ for (ByteBuffer buf : target.bufs) {
+ assertTrue(bufs.remove(buf));
+ }
+ assertTrue(bufs.isEmpty());
+ }
+ }
+
+ private static class WriteTask implements Callable<Boolean> {
+
+ final Random rnd = new Random();
+ final BufferedContentChannel content;
+ final ByteBuffer buf;
+
+ WriteTask(BufferedContentChannel content, ByteBuffer buf) {
+ this.content = content;
+ this.buf = buf;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ if (rnd.nextBoolean()) {
+ Thread.sleep(rnd.nextInt(5));
+ }
+ content.write(buf, null);
+ return Boolean.TRUE;
+ }
+ }
+
+ private static class ConnectTask implements Callable<Boolean> {
+
+ final BufferedContentChannel content;
+ final ContentChannel target;
+
+ ConnectTask(BufferedContentChannel content, ContentChannel target) {
+ this.content = content;
+ this.target = target;
+ }
+
+ @Override
+ public Boolean call() throws Exception {
+ content.connectTo(target);
+ return Boolean.TRUE;
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ ByteBuffer writeBuf = null;
+ CompletionHandler writeCompletion;
+ CompletionHandler closeCompletion;
+ boolean closed = false;
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ writeBuf = buf;
+ writeCompletion = handler;
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ closeCompletion = handler;
+ closed = true;
+ }
+ }
+
+ private static class MyConcurrentContent implements ContentChannel {
+
+ ConcurrentLinkedQueue<ByteBuffer> bufs = new ConcurrentLinkedQueue<>();
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ bufs.add(buf);
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+
+ }
+ }
+
+ private static class MyCompletion implements CompletionHandler {
+
+ @Override
+ public void completed() {
+
+ }
+
+ @Override
+ public void failed(Throwable throwable) {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableRequestDispatchTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableRequestDispatchTestCase.java
new file mode 100644
index 00000000000..d2768707528
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableRequestDispatchTestCase.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CallableRequestDispatchTestCase {
+
+ @Test
+ public void requireThatDispatchIsCalled() throws Exception {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ Response response = new Response(Response.Status.OK);
+ builder.serverBindings().bind("http://host/path", new MyRequestHandler(response));
+ driver.activateContainer(builder);
+ assertSame(response, new CallableRequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ return new Request(driver, URI.create("http://host/path"));
+ }
+ }.call());
+ assertTrue(driver.close());
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ final Response response;
+
+ MyRequestHandler(Response response) {
+ this.response = response;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ ResponseDispatch.newInstance(response).dispatch(handler);
+ return null;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableResponseDispatchTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableResponseDispatchTestCase.java
new file mode 100644
index 00000000000..9b107f93178
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/CallableResponseDispatchTestCase.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.yahoo.jdisc.Response;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertSame;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CallableResponseDispatchTestCase {
+
+ @Test
+ public void requireThatDispatchIsCalled() throws Exception {
+ final Response response = new Response(Response.Status.OK);
+ FutureResponse handler = new FutureResponse();
+ new CallableResponseDispatch(handler) {
+
+ @Override
+ protected Response newResponse() {
+ return response;
+ }
+ }.call();
+ assertSame(response, handler.get(600, TimeUnit.SECONDS));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ContentInputStreamTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ContentInputStreamTestCase.java
new file mode 100644
index 00000000000..618c1b1ed1c
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ContentInputStreamTestCase.java
@@ -0,0 +1,32 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+
+import java.util.concurrent.Future;
+
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContentInputStreamTestCase {
+
+ @Test
+ public void requireThatContentInputStreamExtendsUnsafeContentInputStream() {
+ assertTrue(UnsafeContentInputStream.class.isAssignableFrom(ContentInputStream.class));
+ }
+
+ @Test
+ @SuppressWarnings("FinalizeCalledExplicitly")
+ public void requireThatFinalizerClosesStream() throws Throwable {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ FastContentWriter writer = new FastContentWriter(channel);
+ writer.write("foo");
+ writer.close();
+
+ new ContentInputStream(channel.toReadable()).finalize();
+ }
+
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentOutputStreamTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentOutputStreamTestCase.java
new file mode 100644
index 00000000000..00ea92ff246
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentOutputStreamTestCase.java
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.nio.ByteBuffer;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class FastContentOutputStreamTestCase {
+
+ @Test
+ public void requireThatNullConstructorArgumentThrows() {
+ try {
+ new FastContentOutputStream((ContentChannel)null);
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("out", e.getMessage());
+ }
+ try {
+ new FastContentOutputStream((FastContentWriter)null);
+ fail();
+ } catch (NullPointerException e) {
+ assertEquals("out", e.getMessage());
+ }
+ }
+
+ @Test
+ public void requireThatAllMethodsDelegateToWriter() throws Exception {
+ FastContentWriter writer = Mockito.mock(FastContentWriter.class);
+ FastContentOutputStream out = new FastContentOutputStream(writer);
+
+ out.write(new byte[] { 6, 9 });
+ out.flush();
+ Mockito.verify(writer).write(Mockito.any(ByteBuffer.class));
+
+ out.close();
+ Mockito.verify(writer).close();
+
+ out.cancel(true);
+ Mockito.verify(writer).cancel(true);
+ out.cancel(false);
+ Mockito.verify(writer).cancel(false);
+
+ out.isCancelled();
+ Mockito.verify(writer).isCancelled();
+
+ out.isDone();
+ Mockito.verify(writer).isDone();
+
+ out.get();
+ Mockito.verify(writer).get();
+
+ out.get(600, TimeUnit.SECONDS);
+ Mockito.verify(writer).get(600, TimeUnit.SECONDS);
+
+ Runnable listener = Mockito.mock(Runnable.class);
+ Executor executor = Mockito.mock(Executor.class);
+ out.addListener(listener, executor);
+ Mockito.verify(writer).addListener(listener, executor);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentWriterTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentWriterTestCase.java
new file mode 100644
index 00000000000..e3bedaf5c2a
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FastContentWriterTestCase.java
@@ -0,0 +1,241 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertArrayEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class FastContentWriterTestCase {
+
+ @Test
+ public void requireThatContentCanBeWritten() throws ExecutionException, InterruptedException {
+ ReadableContentChannel content = new ReadableContentChannel();
+ FastContentWriter out = new FastContentWriter(content);
+
+ ByteBuffer foo = ByteBuffer.allocate(69);
+ out.write(foo);
+ ByteBuffer bar = ByteBuffer.allocate(69);
+ out.write(bar);
+ out.close();
+
+ assertFalse(out.isDone());
+ assertSame(foo, content.read());
+ assertFalse(out.isDone());
+ assertSame(bar, content.read());
+ assertFalse(out.isDone());
+ assertNull(content.read());
+ assertTrue(out.isDone());
+ }
+
+ @Test
+ public void requireThatStringsAreUtf8Encoded() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ FastContentWriter out = new FastContentWriter(content);
+
+ String in = "\u6211\u80FD\u541E\u4E0B\u73BB\u7483\u800C\u4E0D\u4F24\u8EAB\u4F53\u3002";
+ out.write(in);
+ out.close();
+
+ ByteBuffer buf = content.read();
+ byte[] arr = new byte[buf.remaining()];
+ buf.get(arr);
+ assertArrayEquals(in.getBytes(StandardCharsets.UTF_8), arr);
+ }
+
+ @Test
+ public void requireThatCancelThrowsUnsupportedOperation() {
+ try {
+ new FastContentWriter(Mockito.mock(ContentChannel.class)).cancel(true);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatCancelIsAlwaysFalse() {
+ FastContentWriter writer = new FastContentWriter(Mockito.mock(ContentChannel.class));
+ assertFalse(writer.isCancelled());
+ try {
+ writer.cancel(true);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(writer.isCancelled());
+ }
+
+ @Test
+ public void requireThatGetThrowsTimeoutUntilCloseCompletionHandlerIsCalled() throws Exception {
+ ReadableContentChannel buf = new ReadableContentChannel();
+ FastContentWriter out = new FastContentWriter(buf);
+
+ out.write(new byte[] { 6, 9 });
+ assertFalse(out.isDone());
+ try {
+ out.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+
+ assertNotNull(buf.read());
+ assertFalse(out.isDone());
+ try {
+ out.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+
+ out.close();
+ assertFalse(out.isDone());
+ try {
+ out.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+
+ assertNull(buf.read());
+ assertTrue(out.isDone());
+ assertTrue(out.get(600, TimeUnit.SECONDS));
+ assertTrue(out.get());
+ }
+
+ @Test
+ public void requireThatSyncWriteExceptionFailsFuture() throws InterruptedException {
+ IllegalStateException expected = new IllegalStateException();
+ ContentChannel content = Mockito.mock(ContentChannel.class);
+ Mockito.doThrow(expected)
+ .when(content).write(Mockito.any(ByteBuffer.class), Mockito.any(CompletionHandler.class));
+ FastContentWriter out = new FastContentWriter(content);
+ try {
+ out.write("foo");
+ fail();
+ } catch (Throwable t) {
+ assertSame(expected, t);
+ }
+ try {
+ out.get();
+ fail();
+ } catch (ExecutionException e) {
+ assertSame(expected, e.getCause());
+ }
+ }
+
+ @Test
+ public void requireThatSyncCloseExceptionFailsFuture() throws InterruptedException {
+ IllegalStateException expected = new IllegalStateException();
+ ContentChannel content = Mockito.mock(ContentChannel.class);
+ Mockito.doThrow(expected)
+ .when(content).close(Mockito.any(CompletionHandler.class));
+ FastContentWriter out = new FastContentWriter(content);
+ try {
+ out.close();
+ fail();
+ } catch (Throwable t) {
+ assertSame(expected, t);
+ }
+ try {
+ out.get();
+ fail();
+ } catch (ExecutionException e) {
+ assertSame(expected, e.getCause());
+ }
+ }
+
+ @Test
+ public void requireThatAsyncExceptionFailsFuture() throws InterruptedException {
+ IllegalStateException expected = new IllegalStateException();
+ ReadableContentChannel content = new ReadableContentChannel();
+ FastContentWriter out = new FastContentWriter(content);
+ out.write("foo");
+ content.failed(expected);
+ try {
+ out.get();
+ fail();
+ } catch (ExecutionException e) {
+ assertSame(expected, e.getCause());
+ }
+ }
+
+ @Test
+ public void requireThatWriterCanBeListenedTo() throws InterruptedException {
+ ReadableContentChannel buf = new ReadableContentChannel();
+ FastContentWriter out = new FastContentWriter(buf);
+ RunnableLatch listener = new RunnableLatch();
+ out.addListener(listener, MoreExecutors.sameThreadExecutor());
+
+ out.write(new byte[] { 6, 9 });
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ assertNotNull(buf.read());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ out.close();
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ assertNull(buf.read());
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatWriterIsThreadSafe() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(2);
+ final ReadableContentChannel content = new ReadableContentChannel();
+ Future<Integer> read = Executors.newSingleThreadExecutor().submit(new Callable<Integer>() {
+
+ @Override
+ public Integer call() throws Exception {
+ latch.countDown();
+ latch.await(600, TimeUnit.SECONDS);
+
+ int bufCnt = 0;
+ while (content.read() != null) {
+ ++bufCnt;
+ }
+ return bufCnt;
+ }
+ });
+ Future<Integer> write = Executors.newSingleThreadExecutor().submit(new Callable<Integer>() {
+
+ @Override
+ public Integer call() throws Exception {
+ FastContentWriter out = new FastContentWriter(content);
+ ByteBuffer buf = ByteBuffer.wrap(new byte[69]);
+ int bufCnt = 4096 + new Random().nextInt(4096);
+
+ latch.countDown();
+ latch.await(600, TimeUnit.SECONDS);
+ for (int i = 0; i < bufCnt; ++i) {
+ out.write(buf.slice());
+ }
+ out.close();
+ return bufCnt;
+ }
+ });
+ assertEquals(read.get(600, TimeUnit.SECONDS),
+ write.get(600, TimeUnit.SECONDS));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureCompletionTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureCompletionTestCase.java
new file mode 100644
index 00000000000..e886d663a72
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureCompletionTestCase.java
@@ -0,0 +1,106 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.Test;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FutureCompletionTestCase {
+
+ @Test
+ public void requireThatCancelIsUnsupported() {
+ FutureCompletion future = new FutureCompletion();
+ assertFalse(future.isCancelled());
+ try {
+ future.cancel(true);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(future.isCancelled());
+ try {
+ future.cancel(false);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(future.isCancelled());
+ }
+
+ @Test
+ public void requireThatCompletedReturnsTrue() throws Exception {
+ FutureCompletion future = new FutureCompletion();
+ try {
+ future.get(0, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ future.completed();
+ assertTrue(future.get(0, TimeUnit.MILLISECONDS));
+ assertTrue(future.get());
+ }
+
+ @Test
+ public void requireThatCompletionIsDoneWhenCompleted() {
+ FutureCompletion future = new FutureCompletion();
+ assertFalse(future.isDone());
+ future.completed();
+ assertTrue(future.isDone());
+ }
+
+ @Test
+ public void requireThatCompletionIsDoneWhenFailed() {
+ FutureCompletion future = new FutureCompletion();
+ assertFalse(future.isDone());
+ future.failed(new Throwable());
+ assertTrue(future.isDone());
+ }
+
+ @Test
+ public void requireThatFailedCauseIsRethrown() throws Exception {
+ FutureCompletion future = new FutureCompletion();
+ Throwable t = new Throwable();
+ future.failed(t);
+ try {
+ future.get(0, TimeUnit.SECONDS);
+ fail();
+ } catch (ExecutionException e) {
+ assertSame(t, e.getCause());
+ }
+ try {
+ future.get();
+ fail();
+ } catch (ExecutionException e) {
+ assertSame(t, e.getCause());
+ }
+ }
+
+ @Test
+ public void requireThatCompletionCanBeListenedTo() throws InterruptedException {
+ FutureCompletion completion = new FutureCompletion();
+ RunnableLatch listener = new RunnableLatch();
+ completion.addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ completion.completed();
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+
+ completion = new FutureCompletion();
+ listener = new RunnableLatch();
+ completion.addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ completion.failed(new Throwable());
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureConjunctionTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureConjunctionTestCase.java
new file mode 100644
index 00000000000..3916eb7ffd6
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureConjunctionTestCase.java
@@ -0,0 +1,255 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.google.common.util.concurrent.AbstractFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import com.google.common.util.concurrent.MoreExecutors;
+import org.junit.Test;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class FutureConjunctionTestCase {
+
+ private final ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
+
+ @Test
+ public void requireThatAllFuturesAreWaitedFor() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ FutureConjunction future = new FutureConjunction();
+ future.addOperand(executor.submit(new Callable<Boolean>() {
+
+ @Override
+ public Boolean call() throws Exception {
+ return latch.await(600, TimeUnit.SECONDS);
+ }
+ }));
+ try {
+ future.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ latch.countDown();
+ assertTrue(future.get(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatGetReturnValueIsAConjunction() throws Exception {
+ assertTrue(tryGet(true));
+ assertTrue(tryGet(true, true));
+ assertTrue(tryGet(true, true, true));
+
+ assertFalse(tryGet(false));
+ assertFalse(tryGet(false, true));
+ assertFalse(tryGet(true, false));
+ assertFalse(tryGet(false, true, true));
+ assertFalse(tryGet(true, false, true));
+ assertFalse(tryGet(true, true, false));
+ assertFalse(tryGet(false, false, true));
+ assertFalse(tryGet(false, true, false));
+ assertFalse(tryGet(true, false, false));
+ }
+
+ @Test
+ public void requireThatIsDoneReturnValueIsAConjunction() {
+ assertTrue(tryIsDone(true));
+ assertTrue(tryIsDone(true, true));
+ assertTrue(tryIsDone(true, true, true));
+
+ assertFalse(tryIsDone(false));
+ assertFalse(tryIsDone(false, true));
+ assertFalse(tryIsDone(true, false));
+ assertFalse(tryIsDone(false, true, true));
+ assertFalse(tryIsDone(true, false, true));
+ assertFalse(tryIsDone(true, true, false));
+ assertFalse(tryIsDone(false, false, true));
+ assertFalse(tryIsDone(false, true, false));
+ assertFalse(tryIsDone(true, false, false));
+ }
+
+ @Test
+ public void requireThatCancelReturnValueIsAConjuction() {
+ assertTrue(tryCancel(true));
+ assertTrue(tryCancel(true, true));
+ assertTrue(tryCancel(true, true, true));
+
+ assertFalse(tryCancel(false));
+ assertFalse(tryCancel(false, true));
+ assertFalse(tryCancel(true, false));
+ assertFalse(tryCancel(false, true, true));
+ assertFalse(tryCancel(true, false, true));
+ assertFalse(tryCancel(true, true, false));
+ assertFalse(tryCancel(false, false, true));
+ assertFalse(tryCancel(false, true, false));
+ assertFalse(tryCancel(true, false, false));
+ }
+
+ @Test
+ public void requireThatIsCancelledReturnValueIsAConjuction() {
+ assertTrue(tryIsCancelled(true));
+ assertTrue(tryIsCancelled(true, true));
+ assertTrue(tryIsCancelled(true, true, true));
+
+ assertFalse(tryIsCancelled(false));
+ assertFalse(tryIsCancelled(false, true));
+ assertFalse(tryIsCancelled(true, false));
+ assertFalse(tryIsCancelled(false, true, true));
+ assertFalse(tryIsCancelled(true, false, true));
+ assertFalse(tryIsCancelled(true, true, false));
+ assertFalse(tryIsCancelled(false, false, true));
+ assertFalse(tryIsCancelled(false, true, false));
+ assertFalse(tryIsCancelled(true, false, false));
+ }
+
+ @Test
+ public void requireThatConjunctionCanBeListenedTo() throws InterruptedException {
+ FutureConjunction conjunction = new FutureConjunction();
+ RunnableLatch listener = new RunnableLatch();
+ conjunction.addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+
+ conjunction = new FutureConjunction();
+ FutureBoolean foo = new FutureBoolean();
+ conjunction.addOperand(foo);
+ FutureBoolean bar = new FutureBoolean();
+ conjunction.addOperand(bar);
+ listener = new RunnableLatch();
+ conjunction.addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ foo.set(true);
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ bar.set(true);
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+
+ conjunction = new FutureConjunction();
+ foo = new FutureBoolean();
+ conjunction.addOperand(foo);
+ bar = new FutureBoolean();
+ conjunction.addOperand(bar);
+ listener = new RunnableLatch();
+ conjunction.addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ bar.set(true);
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ foo.set(true);
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+ }
+
+ private static boolean tryGet(boolean... operands) throws Exception {
+ FutureConjunction foo = new FutureConjunction();
+ FutureConjunction bar = new FutureConjunction();
+ for (boolean op : operands) {
+ foo.addOperand(MyFuture.newInstance(op));
+ bar.addOperand(MyFuture.newInstance(op));
+ }
+ boolean fooResult = foo.get();
+ boolean barResult = foo.get(0, TimeUnit.SECONDS);
+ assertEquals(fooResult, barResult);
+ return fooResult;
+ }
+
+ private static boolean tryIsDone(boolean... operands) {
+ FutureConjunction foo = new FutureConjunction();
+ for (boolean op : operands) {
+ foo.addOperand(MyFuture.newIsDone(op));
+ }
+ return foo.isDone();
+ }
+
+ private static boolean tryCancel(boolean... operands) {
+ FutureConjunction foo = new FutureConjunction();
+ FutureConjunction bar = new FutureConjunction();
+ for (boolean op : operands) {
+ foo.addOperand(MyFuture.newCanCancel(op));
+ bar.addOperand(MyFuture.newCanCancel(op));
+ }
+ boolean fooResult = foo.cancel(true);
+ boolean barResult = foo.cancel(false);
+ assertEquals(fooResult, barResult);
+ return fooResult;
+ }
+
+ private static boolean tryIsCancelled(boolean... operands) {
+ FutureConjunction foo = new FutureConjunction();
+ for (boolean op : operands) {
+ foo.addOperand(MyFuture.newIsCancelled(op));
+ }
+ return foo.isCancelled();
+ }
+
+ private static class FutureBoolean extends AbstractFuture<Boolean> {
+
+ public boolean set(Boolean val) {
+ return super.set(val);
+ }
+ }
+
+ private static class MyFuture extends AbstractFuture<Boolean> {
+
+ final boolean value;
+ final boolean isDone;
+ final boolean canCancel;
+ final boolean isCancelled;
+
+ MyFuture(boolean value, boolean isDone, boolean canCancel, boolean isCancelled) {
+ this.value = value;
+ this.isDone = isDone;
+ this.canCancel = canCancel;
+ this.isCancelled = isCancelled;
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return canCancel;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return isCancelled;
+ }
+
+ @Override
+ public boolean isDone() {
+ return isDone;
+ }
+
+ @Override
+ public Boolean get() {
+ return value;
+ }
+
+ @Override
+ public Boolean get(long timeout, TimeUnit unit) {
+ return value;
+ }
+
+ static ListenableFuture<Boolean> newInstance(boolean value) {
+ return new MyFuture(value, false, false, false);
+ }
+
+ static ListenableFuture<Boolean> newIsDone(boolean isDone) {
+ return new MyFuture(false, isDone, false, false);
+ }
+
+ static ListenableFuture<Boolean> newCanCancel(boolean canCancel) {
+ return new MyFuture(false, false, canCancel, false);
+ }
+
+ static ListenableFuture<Boolean> newIsCancelled(boolean isCancelled) {
+ return new MyFuture(false, false, false, isCancelled);
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureResponseTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureResponseTestCase.java
new file mode 100644
index 00000000000..925c09f763d
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/FutureResponseTestCase.java
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.test.NonWorkingContentChannel;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FutureResponseTestCase {
+
+ @Test
+ public void requireThatCancelIsUnsupported() {
+ FutureResponse future = new FutureResponse();
+ assertFalse(future.isCancelled());
+ try {
+ future.cancel(true);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(future.isCancelled());
+ try {
+ future.cancel(false);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(future.isCancelled());
+ }
+
+ @Test
+ public void requireThatCompletionIsDoneWhenHandlerIsCalled() {
+ FutureResponse future = new FutureResponse();
+ assertFalse(future.isDone());
+ future.handleResponse(new Response(69));
+ assertTrue(future.isDone());
+ }
+
+ @Test
+ public void requireThatResponseBecomesAvailable() throws Exception {
+ FutureResponse future = new FutureResponse();
+ try {
+ future.get(0, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ Response response = new Response(Response.Status.OK);
+ future.handleResponse(response);
+ assertSame(response, future.get(0, TimeUnit.MILLISECONDS));
+ }
+
+ @Test
+ public void requireThatResponseContentIsReturnedToCaller() throws Exception {
+ ContentChannel content = new NonWorkingContentChannel();
+ FutureResponse future = new FutureResponse(content);
+ Response response = new Response(Response.Status.OK);
+ assertSame(content, future.handleResponse(response));
+ }
+
+ @Test
+ public void requireThatResponseCanBeListenedTo() throws InterruptedException {
+ FutureResponse response = new FutureResponse();
+ RunnableLatch listener = new RunnableLatch();
+ response.addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ response.handleResponse(new Response(Response.Status.OK));
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/NullContentTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/NullContentTestCase.java
new file mode 100644
index 00000000000..40f6cd88216
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/NullContentTestCase.java
@@ -0,0 +1,48 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NullContentTestCase {
+
+ @Test
+ public void requireThatWriteThrowsException() {
+ CompletionHandler completion = Mockito.mock(CompletionHandler.class);
+ try {
+ NullContent.INSTANCE.write(ByteBuffer.allocate(69), completion);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ Mockito.verifyZeroInteractions(completion);
+ }
+
+ @Test
+ public void requireThatWriteEmptyDoesNotThrowException() {
+ CompletionHandler completion = Mockito.mock(CompletionHandler.class);
+ NullContent.INSTANCE.write(ByteBuffer.allocate(0), completion);
+ Mockito.verify(completion).completed();
+ Mockito.verifyNoMoreInteractions(completion);
+ }
+
+ @Test
+ public void requireThatCloseCallsCompletion() {
+ CompletionHandler completion = Mockito.mock(CompletionHandler.class);
+ NullContent.INSTANCE.close(completion);
+ Mockito.verify(completion).completed();
+ Mockito.verifyNoMoreInteractions(completion);
+ }
+
+ @Test
+ public void requireThatCloseWithoutCompletionDoesNotThrow() {
+ NullContent.INSTANCE.close(null);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ReadableContentChannelTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ReadableContentChannelTestCase.java
new file mode 100644
index 00000000000..378da5449c2
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ReadableContentChannelTestCase.java
@@ -0,0 +1,320 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.*;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ReadableContentChannelTestCase {
+
+ @Test
+ public void requireThatWriteNullThrowsException() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ try {
+ content.write(null, new MyCompletion());
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatWriteAfterCloseThrowsException() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.close(null);
+ try {
+ content.write(ByteBuffer.allocate(69), new MyCompletion());
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatWriteAfterFailedThrowsException() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.failed(new RuntimeException());
+ try {
+ content.write(ByteBuffer.allocate(69), new MyCompletion());
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatCloseAfterCloseThrowsException() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.close(null);
+ try {
+ content.close(null);
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatCloseAfterFailedThrowsException() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.failed(new RuntimeException());
+ try {
+ content.close(null);
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatFailedAfterFailedThrowsException() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.failed(new RuntimeException());
+ try {
+ content.failed(new RuntimeException());
+ fail();
+ } catch (IllegalStateException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatIteratorDoesNotSupportRemove() {
+ try {
+ new ReadableContentChannel().iterator().remove();
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatWrittenBufferCanBeRead() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ content.write(buf, null);
+ assertSame(buf, content.read());
+ }
+
+ @Test
+ public void requireThatWrittenBuffersAreReadInOrder() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ ByteBuffer foo = ByteBuffer.allocate(69);
+ content.write(foo, null);
+ ByteBuffer bar = ByteBuffer.allocate(69);
+ content.write(bar, null);
+ content.close(null);
+ assertSame(foo, content.read());
+ assertSame(bar, content.read());
+ }
+
+ @Test
+ public void requireThatReadAfterCloseIsNull() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.close(null);
+ assertNull(content.read());
+ assertNull(content.read());
+ }
+
+ @Test
+ public void requireThatWrittenBufferCanBeReadByIterator() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ ByteBuffer foo = ByteBuffer.allocate(69);
+ content.write(foo, null);
+ ByteBuffer bar = ByteBuffer.allocate(69);
+ content.write(bar, null);
+ content.close(null);
+
+ Iterator<ByteBuffer> it = content.iterator();
+ assertTrue(it.hasNext());
+ assertSame(foo, it.next());
+ assertTrue(it.hasNext());
+ assertSame(bar, it.next());
+ assertFalse(it.hasNext());
+ try {
+ it.next();
+ fail();
+ } catch (NoSuchElementException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatReadAfterFailedIsNull() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.failed(new RuntimeException());
+ assertNull(content.read());
+ assertNull(content.read());
+ }
+
+ @Test
+ public void requireThatReadCallsCompletion() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ MyCompletion completion = new MyCompletion();
+ content.write(buf, completion);
+ assertFalse(completion.completed);
+ assertSame(buf, content.read());
+ assertTrue(completion.completed);
+
+ completion = new MyCompletion();
+ content.close(completion);
+ assertFalse(completion.completed);
+ assertNull(content.read());
+ assertTrue(completion.completed);
+ }
+
+ @Test
+ public void requireThatReadWaitsForWrite() throws Exception {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ ReadableContentChannel content = new ReadableContentChannel();
+ Future<ByteBuffer> readBuf = executor.submit(new ReadTask(content));
+ try {
+ readBuf.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ content.write(buf, null);
+ assertSame(buf, readBuf.get(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatCloseNotifiesRead() throws Exception {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ ReadableContentChannel content = new ReadableContentChannel();
+ Future<ByteBuffer> buf = executor.submit(new ReadTask(content));
+ try {
+ buf.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ content.close(null);
+ assertNull(buf.get(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatFailedNotifiesRead() throws Exception {
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ ReadableContentChannel content = new ReadableContentChannel();
+ Future<ByteBuffer> buf = executor.submit(new ReadTask(content));
+ try {
+ buf.get(100, TimeUnit.MILLISECONDS);
+ fail();
+ } catch (TimeoutException e) {
+
+ }
+ content.failed(new RuntimeException());
+ assertNull(buf.get(600, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatFailedCallsPendingCompletions() {
+ MyCompletion foo = new MyCompletion();
+ MyCompletion bar = new MyCompletion();
+ ReadableContentChannel content = new ReadableContentChannel();
+ content.write(ByteBuffer.allocate(69), foo);
+ content.write(ByteBuffer.allocate(69), bar);
+ RuntimeException e = new RuntimeException();
+ content.failed(e);
+ assertSame(e, foo.failed);
+ assertSame(e, bar.failed);
+ }
+
+ @Test
+ public void requireThatAvailableIsNotBlocking() {
+ ReadableContentChannel content = new ReadableContentChannel();
+ assertEquals(0, content.available());
+ ByteBuffer buf = ByteBuffer.wrap(new byte[] { 6, 9 });
+ content.write(buf, null);
+ assertTrue(content.available() > 0);
+ assertSame(buf, content.read());
+ assertEquals(0, content.available());
+ content.close(null);
+ assertNull(content.read());
+ assertEquals(0, content.available());
+ }
+
+ @Test
+ public void requireThatContentIsThreadSafe() {
+ ExecutorService executor = Executors.newFixedThreadPool(100);
+ for (int run = 0; run < 69; ++run) {
+ List<ByteBuffer> bufs = new LinkedList<>();
+ for (int buf = 0; buf < 100; ++buf) {
+ bufs.add(ByteBuffer.allocate(buf));
+ }
+ ReadableContentChannel content = new ReadableContentChannel();
+ for (ByteBuffer buf : bufs) {
+ executor.execute(new WriteTask(content, buf));
+ }
+ for (int buf = 0; buf < 100; ++buf) {
+ assertTrue(bufs.remove(content.read()));
+ }
+ content.close(null);
+ assertNull(content.read());
+ }
+ }
+
+ private static class MyCompletion implements CompletionHandler {
+
+ boolean completed = false;
+ Throwable failed = null;
+
+ @Override
+ public void completed() {
+ completed = true;
+ }
+
+ @Override
+ public void failed(Throwable t) {
+ failed = t;
+ }
+ }
+
+ private static class ReadTask implements Callable<ByteBuffer> {
+
+ final ReadableContentChannel content;
+
+ ReadTask(ReadableContentChannel content) {
+ this.content = content;
+ }
+
+ @Override
+ public ByteBuffer call() throws Exception {
+ return content.read();
+ }
+ }
+
+ private static class WriteTask implements Runnable {
+
+ final ContentChannel content;
+ final ByteBuffer buf;
+
+ WriteTask(ContentChannel content, ByteBuffer buf) {
+ this.content = content;
+ this.buf = buf;
+ }
+
+ @Override
+ public void run() {
+ content.write(buf, null);
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDeniedTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDeniedTestCase.java
new file mode 100644
index 00000000000..3cfe794dfed
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDeniedTestCase.java
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.yahoo.jdisc.NoopSharedResource;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RequestDeniedTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Request request = new Request(driver, URI.create("http://host/path"));
+ RequestDeniedException e = new RequestDeniedException(request);
+ assertSame(request, e.request());
+ request.release();
+ driver.close();
+ }
+
+ @Test
+ public void requireThatRequestDeniedIsThrown() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ RequestHandler requestHandler = new MyRequestHandler();
+ builder.serverBindings().bind("http://host/path", requestHandler);
+ driver.activateContainer(builder);
+ Request request = new Request(driver, URI.create("http://host/path"));
+ try {
+ request.connect(new MyResponseHandler());
+ fail();
+ } catch (RequestDeniedException e) {
+ assertSame(request, e.request());
+ }
+ request.release();
+ driver.close();
+ }
+
+ private static class MyRequestHandler extends NoopSharedResource implements RequestHandler {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ throw new RequestDeniedException(request);
+ }
+
+ @Override
+ public void handleTimeout(Request request, ResponseHandler handler) {
+
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return null;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDispatchTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDispatchTestCase.java
new file mode 100644
index 00000000000..f13473a1660
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RequestDispatchTestCase.java
@@ -0,0 +1,253 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class RequestDispatchTestCase {
+
+ @Test
+ public void requireThatRequestCanBeDispatched() throws Exception {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ final List<ByteBuffer> writtenContent = Arrays.asList(ByteBuffer.allocate(6), ByteBuffer.allocate(9));
+ ReadableContentChannel receivedContent = new ReadableContentChannel();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ Response response = new Response(Response.Status.OK);
+ builder.serverBindings().bind("http://localhost/", new MyRequestHandler(receivedContent, response));
+ driver.activateContainer(builder);
+ RequestDispatch dispatch = new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ return new Request(driver, URI.create("http://localhost/"));
+ }
+
+ @Override
+ protected Iterable<ByteBuffer> requestContent() {
+ return writtenContent;
+ }
+ };
+ dispatch.dispatch();
+ assertFalse(dispatch.isDone());
+ assertSame(writtenContent.get(0), receivedContent.read());
+ assertFalse(dispatch.isDone());
+ assertSame(writtenContent.get(1), receivedContent.read());
+ assertFalse(dispatch.isDone());
+ assertNull(receivedContent.read());
+ assertTrue(dispatch.isDone());
+ assertSame(response, dispatch.get(600, TimeUnit.SECONDS));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatStreamCanBeConnected() throws IOException {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ ReadableContentChannel content = new ReadableContentChannel();
+ MyRequestHandler requestHandler = new MyRequestHandler(content, new Response(Response.Status.OK));
+ builder.serverBindings().bind("http://localhost/", requestHandler);
+ driver.activateContainer(builder);
+
+ OutputStream out = new FastContentOutputStream(driver.newRequestDispatch("http://localhost/", new FutureResponse()).connect());
+ out.write(6);
+ out.write(9);
+ out.close();
+
+ InputStream in = content.toStream();
+ assertEquals(6, in.read());
+ assertEquals(9, in.read());
+ assertEquals(-1, in.read());
+
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatCancelIsUnsupported() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ RequestDispatch dispatch = driver.newRequestDispatch("http://localhost/", new FutureResponse());
+ assertFalse(dispatch.isCancelled());
+ try {
+ dispatch.cancel(true);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(dispatch.isCancelled());
+ try {
+ dispatch.cancel(false);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(dispatch.isCancelled());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDispatchHandlesConnectException() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://localhost/", new AbstractRequestHandler() {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ throw new RuntimeException();
+ }
+ });
+ driver.activateContainer(builder);
+ try {
+ driver.newRequestDispatch("http://localhost/", new FutureResponse()).dispatch();
+ fail();
+ } catch (RuntimeException e) {
+
+ }
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDispatchHandlesWriteException() {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ Response response = new Response(Response.Status.OK);
+ builder.serverBindings().bind("http://localhost/", new MyRequestHandler(new ContentChannel() {
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ throw new RuntimeException();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ handler.completed();
+ }
+ }, response));
+ driver.activateContainer(builder);
+ try {
+ new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ return new Request(driver, URI.create("http://localhost/"));
+ }
+
+ @Override
+ protected Iterable<ByteBuffer> requestContent() {
+ return Arrays.asList(ByteBuffer.allocate(69));
+ }
+ }.dispatch();
+ fail();
+ } catch (RuntimeException e) {
+
+ }
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDispatchHandlesCloseException() {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ Response response = new Response(Response.Status.OK);
+ builder.serverBindings().bind("http://localhost/", new MyRequestHandler(new ContentChannel() {
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ handler.completed();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ throw new RuntimeException();
+ }
+ }, response));
+ driver.activateContainer(builder);
+ try {
+ new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ return new Request(driver, URI.create("http://localhost/"));
+ }
+
+ @Override
+ protected Iterable<ByteBuffer> requestContent() {
+ return Arrays.asList(ByteBuffer.allocate(69));
+ }
+ }.dispatch();
+ fail();
+ } catch (RuntimeException e) {
+
+ }
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDispatchCanBeListenedTo() throws InterruptedException {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ ReadableContentChannel requestContent = new ReadableContentChannel();
+ MyRequestHandler requestHandler = new MyRequestHandler(requestContent, null);
+ builder.serverBindings().bind("http://localhost/", requestHandler);
+ driver.activateContainer(builder);
+ RunnableLatch listener = new RunnableLatch();
+ new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ return new Request(driver, URI.create("http://localhost/"));
+ }
+ }.dispatch().addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ ContentChannel responseContent = ResponseDispatch.newInstance(Response.Status.OK)
+ .connect(requestHandler.responseHandler);
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ assertNull(requestContent.read());
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+ responseContent.close(null);
+ assertTrue(driver.close());
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ final ContentChannel content;
+ final Response response;
+ ResponseHandler responseHandler;
+
+ MyRequestHandler(ContentChannel content, Response response) {
+ this.content = content;
+ this.response = response;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ if (response != null) {
+ ResponseDispatch.newInstance(response).dispatch(handler);
+ } else {
+ responseHandler = handler;
+ }
+ return content;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ResponseDispatchTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ResponseDispatchTestCase.java
new file mode 100644
index 00000000000..92fc0f90c07
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ResponseDispatchTestCase.java
@@ -0,0 +1,206 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.google.common.util.concurrent.MoreExecutors;
+import com.yahoo.jdisc.Response;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ResponseDispatchTestCase {
+
+ @Test
+ public void requireThatFactoryMethodsWork() throws Exception {
+ {
+ FutureResponse handler = new FutureResponse();
+ ResponseDispatch.newInstance(69).dispatch(handler);
+ Response response = handler.get(600, TimeUnit.SECONDS);
+ assertNotNull(response);
+ assertEquals(69, response.getStatus());
+ }
+ {
+ FutureResponse handler = new FutureResponse();
+ Response sentResponse = new Response(69);
+ ResponseDispatch.newInstance(sentResponse).dispatch(handler);
+ Response receivedResponse = handler.get(600, TimeUnit.SECONDS);
+ assertSame(sentResponse, receivedResponse);
+ }
+ {
+ ReadableContentChannel content = new ReadableContentChannel();
+ FutureResponse handler = new FutureResponse(content);
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ ResponseDispatch.newInstance(69, Arrays.asList(buf)).dispatch(handler);
+ Response response = handler.get(600, TimeUnit.SECONDS);
+ assertNotNull(response);
+ assertEquals(69, response.getStatus());
+ assertSame(buf, content.read());
+ assertNull(content.read());
+ }
+ {
+ ReadableContentChannel content = new ReadableContentChannel();
+ FutureResponse handler = new FutureResponse(content);
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ ResponseDispatch.newInstance(69, Arrays.asList(buf)).dispatch(handler);
+ Response response = handler.get(600, TimeUnit.SECONDS);
+ assertNotNull(response);
+ assertEquals(69, response.getStatus());
+ assertSame(buf, content.read());
+ assertNull(content.read());
+ }
+ {
+ ReadableContentChannel content = new ReadableContentChannel();
+ FutureResponse handler = new FutureResponse(content);
+ ByteBuffer buf = ByteBuffer.allocate(69);
+ Response sentResponse = new Response(69);
+ ResponseDispatch.newInstance(sentResponse, Arrays.asList(buf)).dispatch(handler);
+ Response receivedResponse = handler.get(600, TimeUnit.SECONDS);
+ assertSame(sentResponse, receivedResponse);
+ assertSame(buf, content.read());
+ assertNull(content.read());
+ }
+ }
+
+ @Test
+ public void requireThatResponseCanBeDispatched() throws Exception {
+ final Response response = new Response(Response.Status.OK);
+ final List<ByteBuffer> writtenContent = Arrays.asList(ByteBuffer.allocate(6), ByteBuffer.allocate(9));
+ ResponseDispatch dispatch = new ResponseDispatch() {
+
+ @Override
+ protected Response newResponse() {
+ return response;
+ }
+
+ @Override
+ protected Iterable<ByteBuffer> responseContent() {
+ return writtenContent;
+ }
+ };
+ ReadableContentChannel receivedContent = new ReadableContentChannel();
+ MyResponseHandler responseHandler = new MyResponseHandler(receivedContent);
+ dispatch.dispatch(responseHandler);
+ assertFalse(dispatch.isDone());
+ assertSame(response, responseHandler.response);
+ assertSame(writtenContent.get(0), receivedContent.read());
+ assertFalse(dispatch.isDone());
+ assertSame(writtenContent.get(1), receivedContent.read());
+ assertFalse(dispatch.isDone());
+ assertNull(receivedContent.read());
+ assertTrue(dispatch.isDone());
+ assertTrue(dispatch.get(600, TimeUnit.SECONDS));
+ assertTrue(dispatch.get());
+ }
+
+ @Test
+ public void requireThatStreamCanBeConnected() throws IOException {
+ ReadableContentChannel responseContent = new ReadableContentChannel();
+ OutputStream out = new FastContentOutputStream(new ResponseDispatch() {
+
+ @Override
+ protected Response newResponse() {
+ return new Response(Response.Status.OK);
+ }
+ }.connect(new MyResponseHandler(responseContent)));
+ out.write(6);
+ out.write(9);
+ out.close();
+
+ InputStream in = responseContent.toStream();
+ assertEquals(6, in.read());
+ assertEquals(9, in.read());
+ assertEquals(-1, in.read());
+ }
+
+ @Test
+ public void requireThatCancelIsUnsupported() {
+ ResponseDispatch dispatch = ResponseDispatch.newInstance(69);
+ assertFalse(dispatch.isCancelled());
+ try {
+ dispatch.cancel(true);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(dispatch.isCancelled());
+ try {
+ dispatch.cancel(false);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertFalse(dispatch.isCancelled());
+ }
+
+ @Test
+ public void requireThatDispatchClosesContentIfWriteThrowsException() {
+ final AtomicBoolean closed = new AtomicBoolean(false);
+ try {
+ ResponseDispatch.newInstance(6, ByteBuffer.allocate(9)).dispatch(
+ new MyResponseHandler(new ContentChannel() {
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ closed.set(true);
+ }
+ }));
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ assertTrue(closed.get());
+ }
+
+ @Test
+ public void requireThatDispatchCanBeListenedTo() throws InterruptedException {
+ RunnableLatch listener = new RunnableLatch();
+ ReadableContentChannel responseContent = new ReadableContentChannel();
+ ResponseDispatch.newInstance(6, ByteBuffer.allocate(9))
+ .dispatch(new MyResponseHandler(responseContent))
+ .addListener(listener, MoreExecutors.sameThreadExecutor());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ assertNotNull(responseContent.read());
+ assertFalse(listener.await(100, TimeUnit.MILLISECONDS));
+ assertNull(responseContent.read());
+ assertTrue(listener.await(600, TimeUnit.SECONDS));
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final ContentChannel content;
+ Response response;
+
+ MyResponseHandler(ContentChannel content) {
+ this.content = content;
+ }
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ this.response = response;
+ return content;
+ }
+ }
+
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RunnableLatch.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RunnableLatch.java
new file mode 100644
index 00000000000..e81d7eb16ef
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/RunnableLatch.java
@@ -0,0 +1,22 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+class RunnableLatch implements Runnable {
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+
+ @Override
+ public void run() {
+ latch.countDown();
+ }
+
+ public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
+ return latch.await(timeout, unit);
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ThreadedRequestHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ThreadedRequestHandlerTestCase.java
new file mode 100644
index 00000000000..545be4e03ce
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/ThreadedRequestHandlerTestCase.java
@@ -0,0 +1,228 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @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 requireThatAccessorWork() {
+ MyRequestHandler requestHandler = new MyRequestHandler(newExecutor());
+ requestHandler.setTimeout(1000, TimeUnit.MILLISECONDS);
+ assertEquals(1000, requestHandler.getTimeout(TimeUnit.MILLISECONDS));
+ assertEquals(1, requestHandler.getTimeout(TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void requireThatHandlerSetsRequestTimeout() throws InterruptedException {
+ MyRequestHandler requestHandler = new MyRequestHandler(newExecutor());
+ requestHandler.setTimeout(600, TimeUnit.SECONDS);
+ TestDriver driver = newTestDriver("http://localhost/", requestHandler);
+
+ MyResponseHandler responseHandler = new MyResponseHandler();
+ driver.dispatchRequest("http://localhost/", responseHandler);
+
+ requestHandler.entryLatch.countDown();
+ assertTrue(requestHandler.exitLatch.await(600, TimeUnit.SECONDS));
+ assertNull(requestHandler.content.read());
+ assertNotNull(requestHandler.request.getTimeout(TimeUnit.MILLISECONDS));
+
+ assertTrue(responseHandler.latch.await(600, TimeUnit.SECONDS));
+ assertNull(responseHandler.content.read());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatRequestAndResponseReachHandlers() throws InterruptedException {
+ MyRequestHandler requestHandler = new MyRequestHandler(newExecutor());
+ TestDriver driver = newTestDriver("http://localhost/", requestHandler);
+
+ 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(600, TimeUnit.SECONDS));
+ assertSame(request, requestHandler.request);
+ assertSame(buf, requestHandler.content.read());
+ assertNull(requestHandler.content.read());
+
+ assertTrue(responseHandler.latch.await(600, TimeUnit.SECONDS));
+ assertSame(requestHandler.response, responseHandler.response);
+ assertNull(responseHandler.content.read());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatNotImplementedHandlerDoesNotPreventShutdown() throws Exception {
+ TestDriver driver = newTestDriver("http://localhost/", new ThreadedRequestHandler(newExecutor()) {
+
+ });
+ assertEquals(Response.Status.NOT_IMPLEMENTED,
+ dispatchRequest(driver, "http://localhost/", ByteBuffer.wrap(new byte[] { 69 }))
+ .get(600, TimeUnit.SECONDS).getStatus());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatThreadedRequestHandlerRetainsTheRequestUntilHandlerIsRun() throws Exception {
+ final TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ final AtomicInteger baseRetainCount = new AtomicInteger();
+ builder.serverBindings().bind("http://localhost/base", new AbstractRequestHandler() {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ baseRetainCount.set(request.retainCount());
+ handler.handleResponse(new Response(Response.Status.OK)).close(null);
+ return null;
+ }
+ });
+ final CountDownLatch entryLatch = new CountDownLatch(1);
+ final CountDownLatch exitLatch = new CountDownLatch(1);
+ final AtomicInteger testRetainCount = new AtomicInteger();
+ builder.serverBindings().bind("http://localhost/test", new ThreadedRequestHandler(newExecutor()) {
+
+ @Override
+ public void handleRequest(Request request, ReadableContentChannel requestContent,
+ ResponseHandler responseHandler) {
+ try {
+ entryLatch.await(600, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ return;
+ }
+ testRetainCount.set(request.retainCount());
+ responseHandler.handleResponse(new Response(Response.Status.OK)).close(null);
+ requestContent.read(); // drain content to call completion handlers
+ exitLatch.countDown();
+ }
+ });
+ driver.activateContainer(builder);
+ dispatchRequest(driver, "http://localhost/base");
+ dispatchRequest(driver, "http://localhost/test");
+ entryLatch.countDown();
+ exitLatch.await(600, TimeUnit.SECONDS);
+ assertEquals(baseRetainCount.get(), testRetainCount.get());
+ assertTrue(driver.close());
+ }
+
+ private static TestDriver newTestDriver(String uri, RequestHandler requestHandler) {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind(uri, requestHandler);
+ driver.activateContainer(builder);
+ return driver;
+ }
+
+ private static ListenableFuture<Response> dispatchRequest(final CurrentContainer container, final String uri,
+ final ByteBuffer... content) {
+ return new RequestDispatch() {
+
+ @Override
+ protected Request newRequest() {
+ return new Request(container, URI.create(uri));
+ }
+
+ @Override
+ protected Iterable<ByteBuffer> requestContent() {
+ return Arrays.asList(content);
+ }
+ }.dispatch();
+ }
+
+ private static Executor newExecutor() {
+ return Executors.newSingleThreadExecutor();
+ }
+
+ private static class MyRequestHandler extends ThreadedRequestHandler {
+
+ final CountDownLatch entryLatch = new CountDownLatch(1);
+ final CountDownLatch exitLatch = new CountDownLatch(1);
+ final ReadableContentChannel content = new ReadableContentChannel();
+ Response response = null;
+ Request request = null;
+
+ MyRequestHandler(Executor executor) {
+ super(executor);
+ }
+
+ @Override
+ public void handleRequest(Request request, BufferedContentChannel content, ResponseHandler handler) {
+ try {
+ if (!entryLatch.await(600, TimeUnit.SECONDS)) {
+ return;
+ }
+ this.request = request;
+ content.connectTo(this.content);
+ response = new Response(Response.Status.OK);
+ handler.handleResponse(response).close(null);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ } finally {
+ exitLatch.countDown();
+ }
+ }
+ }
+
+ 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;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/handler/UnsafeContentInputStreamTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/UnsafeContentInputStreamTestCase.java
new file mode 100644
index 00000000000..9aac2c4ea7f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/handler/UnsafeContentInputStreamTestCase.java
@@ -0,0 +1,139 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.handler;
+
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.util.concurrent.Future;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class UnsafeContentInputStreamTestCase {
+
+ @Test
+ public void requireThatBytesCanBeRead() throws IOException {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ FastContentWriter writer = new FastContentWriter(channel);
+ writer.write("Hello ");
+ writer.write("World!");
+ writer.close();
+
+ BufferedReader reader = asBufferedReader(channel);
+ assertEquals("Hello World!", reader.readLine());
+ assertNull(reader.readLine());
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void requireThatCompletionsAreCalledWithDeprecatedContentWriter() throws IOException {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ FastContentWriter writer = new FastContentWriter(channel);
+ writer.write("foo");
+ writer.close();
+
+ InputStream stream = asInputStream(channel);
+ assertEquals('f', stream.read());
+ assertEquals('o', stream.read());
+ assertEquals('o', stream.read());
+ assertEquals(-1, stream.read());
+ assertTrue(writer.isDone());
+ }
+
+ @Test
+ public void requireThatCompletionsAreCalled() throws IOException {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ FastContentWriter writer = new FastContentWriter(channel);
+ writer.write("foo");
+ writer.close();
+
+ InputStream stream = asInputStream(channel);
+ assertEquals('f', stream.read());
+ assertEquals('o', stream.read());
+ assertEquals('o', stream.read());
+ assertEquals(-1, stream.read());
+ assertTrue(writer.isDone());
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void requireThatCloseDrainsStreamWithDeprecatedContentWriter() {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ FastContentWriter writer = new FastContentWriter(channel);
+ writer.write("foo");
+ writer.close();
+
+ asInputStream(channel).close();
+ assertTrue(writer.isDone());
+ }
+
+ @Test
+ public void requireThatCloseDrainsStream() {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ FastContentWriter writer = new FastContentWriter(channel);
+ writer.write("foo");
+ writer.close();
+
+ asInputStream(channel).close();
+ assertTrue(writer.isDone());
+ }
+
+ @Test
+ public void requireThatAvailableIsNotBlocking() throws IOException {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ InputStream stream = asInputStream(channel);
+ assertEquals(0, stream.available());
+ channel.write(ByteBuffer.wrap(new byte[] { 6, 9 }), null);
+ assertTrue(stream.available() > 0);
+ assertEquals(6, stream.read());
+ assertTrue(stream.available() > 0);
+ assertEquals(9, stream.read());
+ assertEquals(0, stream.available());
+ channel.close(null);
+ assertEquals(-1, stream.read());
+ assertEquals(0, stream.available());
+ }
+
+ @Test
+ public void requireThatReadLargeArrayIsNotBlocking() throws IOException {
+ BufferedContentChannel channel = new BufferedContentChannel();
+ InputStream stream = asInputStream(channel);
+ assertEquals(0, stream.available());
+ channel.write(ByteBuffer.wrap(new byte[] { 6, 9 }), null);
+ assertTrue(stream.available() > 0);
+ byte[] buf = new byte[69];
+ assertEquals(2, stream.read(buf));
+ assertEquals(6, buf[0]);
+ assertEquals(9, buf[1]);
+ assertEquals(0, stream.available());
+ channel.close(null);
+ assertEquals(-1, stream.read(buf));
+ assertEquals(0, stream.available());
+ }
+
+ @Test
+ public void requireThatAllByteValuesCanBeRead() throws IOException {
+ ReadableContentChannel content = new ReadableContentChannel();
+ InputStream in = new UnsafeContentInputStream(content);
+ for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; ++i) {
+ content.write(ByteBuffer.wrap(new byte[] { (byte)i }), null);
+ assertEquals(i, (byte)in.read());
+ }
+ }
+
+ private static BufferedReader asBufferedReader(BufferedContentChannel channel) {
+ return new BufferedReader(new InputStreamReader(asInputStream(channel)));
+ }
+
+ private static UnsafeContentInputStream asInputStream(BufferedContentChannel channel) {
+ return new UnsafeContentInputStream(channel.toReadable());
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractClientProviderTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractClientProviderTestCase.java
new file mode 100644
index 00000000000..93e96c29e6f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractClientProviderTestCase.java
@@ -0,0 +1,34 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.service;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AbstractClientProviderTestCase {
+
+ @Test
+ public void requireThatAbstractClassIsAClientProvider() {
+ assertTrue(ClientProvider.class.isInstance(new MyClientProvider()));
+ }
+
+ @Test
+ public void requireThatStartDoesNotThrowAnException() {
+ new MyClientProvider().start();
+ }
+
+ private static class MyClientProvider extends AbstractClientProvider {
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ return null;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractServerProviderTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractServerProviderTestCase.java
new file mode 100644
index 00000000000..c6230e928b7
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/service/AbstractServerProviderTestCase.java
@@ -0,0 +1,51 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.service;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AbstractServerProviderTestCase {
+
+ @Test
+ public void requireThatAbstractClassIsAServerProvider() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ assertTrue(ServerProvider.class.isInstance(new MyServerProvider(driver)));
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatAccessorsWork() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ MyServerProvider server = builder.getInstance(MyServerProvider.class);
+ assertNotNull(server.container());
+ assertTrue(driver.close());
+ }
+
+ private static class MyServerProvider extends AbstractServerProvider {
+
+ @Inject
+ public MyServerProvider(CurrentContainer container) {
+ super(container);
+ }
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/service/BindingSetNotFoundTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/service/BindingSetNotFoundTestCase.java
new file mode 100644
index 00000000000..6d3ed636972
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/service/BindingSetNotFoundTestCase.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.service;
+
+import com.google.inject.AbstractModule;
+import com.yahoo.jdisc.application.BindingSetSelector;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class BindingSetNotFoundTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ BindingSetNotFoundException e = new BindingSetNotFoundException("foo");
+ assertEquals("foo", e.bindingSet());
+ }
+
+ @Test
+ public void requireThatBindingSetNotFoundIsThrown() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(BindingSetSelector.class).toInstance(new MySelector("foo"));
+ }
+ });
+ driver.activateContainer(driver.newContainerBuilder());
+ try {
+ driver.newReference(URI.create("http://host"));
+ fail();
+ } catch (BindingSetNotFoundException e) {
+ assertEquals("foo", e.bindingSet());
+ }
+ driver.close();
+ }
+
+ private static class MySelector implements BindingSetSelector {
+
+ final String setName;
+
+ MySelector(String setName) {
+ this.setName = setName;
+ }
+
+ @Override
+ public String select(URI uri) {
+ return setName;
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/service/ConnectToHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/service/ConnectToHandlerTestCase.java
new file mode 100644
index 00000000000..a516e42c11a
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/service/ConnectToHandlerTestCase.java
@@ -0,0 +1,99 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.service;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ConnectToHandlerTestCase {
+
+ @Test
+ public void requireThatNullResponseHandlerThrowsException() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Request request = new Request(driver, URI.create("http://host/path"));
+ try {
+ request.connect(null);
+ fail();
+ } catch (NullPointerException e) {
+ // expected
+ }
+ request.release();
+ driver.close();
+ }
+
+ @Test
+ public void requireThatConnectToHandlerWorks() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = new MyRequestHandler(new MyContent());
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("http://host/*", requestHandler);
+ driver.activateContainer(builder);
+ Request request = new Request(driver, URI.create("http://host/path"));
+ MyResponseHandler responseHandler = new MyResponseHandler();
+ ContentChannel content = request.connect(responseHandler);
+ request.release();
+ assertNotNull(content);
+ content.close(null);
+ assertNotNull(requestHandler.handler);
+ assertSame(request, requestHandler.request);
+ requestHandler.handler.handleResponse(new Response(Response.Status.OK)).close(null);
+ driver.close();
+ }
+
+ private class MyRequestHandler extends AbstractRequestHandler {
+
+ final ContentChannel content;
+ Request request;
+ ResponseHandler handler;
+
+ MyRequestHandler(ContentChannel content) {
+ this.content = content;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ this.request = request;
+ this.handler = handler;
+ return content;
+ }
+ }
+
+ private class MyResponseHandler implements ResponseHandler {
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return null;
+ }
+ }
+
+ private static class MyContent implements ContentChannel {
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ handler.completed();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ handler.completed();
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/service/ContainerNotReadyTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/service/ContainerNotReadyTestCase.java
new file mode 100644
index 00000000000..423e6e4cc91
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/service/ContainerNotReadyTestCase.java
@@ -0,0 +1,28 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.service;
+
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ContainerNotReadyTestCase {
+
+ @Test
+ public void requireThatExceptionIsThrown() throws BindingSetNotFoundException {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ try {
+ driver.newReference(URI.create("http://host"));
+ fail();
+ } catch (ContainerNotReadyException e) {
+
+ }
+ driver.close();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/service/CurrentContainerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/service/CurrentContainerTestCase.java
new file mode 100644
index 00000000000..8a5a6aeb913
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/service/CurrentContainerTestCase.java
@@ -0,0 +1,27 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.service;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertNotNull;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CurrentContainerTestCase {
+
+ @Test
+ public void requireThatNewRequestsCreateSnapshot() throws Exception {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ driver.activateContainer(driver.newContainerBuilder());
+ Request request = new Request(driver, URI.create("http://host/path"));
+ assertNotNull(request.container());
+ request.release();
+ driver.close();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/service/NoBindingSetSelectedTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/service/NoBindingSetSelectedTestCase.java
new file mode 100644
index 00000000000..56ea80c2001
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/service/NoBindingSetSelectedTestCase.java
@@ -0,0 +1,55 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.service;
+
+import com.google.inject.AbstractModule;
+import com.yahoo.jdisc.application.BindingSetSelector;
+import com.yahoo.jdisc.test.TestDriver;
+import org.junit.Test;
+
+import java.net.URI;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NoBindingSetSelectedTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ URI uri = URI.create("http://host/path");
+ NoBindingSetSelectedException e = new NoBindingSetSelectedException(uri);
+ assertEquals(uri, e.uri());
+ }
+
+ @Test
+ public void requireThatNoBindingSetSelectedIsThrown() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi(new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(BindingSetSelector.class).toInstance(new MySelector());
+ }
+ });
+ driver.activateContainer(driver.newContainerBuilder());
+ URI uri = URI.create("http://host");
+ try {
+ driver.newReference(uri);
+ fail();
+ } catch (NoBindingSetSelectedException e) {
+ assertEquals(uri, e.uri());
+ }
+ driver.close();
+ }
+
+ private static class MySelector implements BindingSetSelector {
+
+ @Override
+ public String select(URI uri) {
+ return null;
+ }
+ }
+
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingClientTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingClientTestCase.java
new file mode 100644
index 00000000000..ff70ee35049
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingClientTestCase.java
@@ -0,0 +1,54 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.yahoo.jdisc.service.ClientProvider;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingClientTestCase {
+
+ @Test
+ public void requireThatHandleRequestThrowsException() {
+ ClientProvider client = new NonWorkingClientProvider();
+ try {
+ client.handleRequest(null, null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatHandleTimeoutThrowsException() {
+ ClientProvider client = new NonWorkingClientProvider();
+ try {
+ client.handleTimeout(null, null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatStartDoesNotThrow() {
+ ClientProvider client = new NonWorkingClientProvider();
+ client.start();
+ }
+
+ @Test
+ public void requireThatRetainDoesNotThrow() {
+ ClientProvider client = new NonWorkingClientProvider();
+ client.release();
+ }
+
+ @Test
+ public void requireThatReleaseDoesNotThrow() {
+ ClientProvider client = new NonWorkingClientProvider();
+ client.release();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingCompletionHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingCompletionHandlerTestCase.java
new file mode 100644
index 00000000000..9ee5f5dd265
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingCompletionHandlerTestCase.java
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.yahoo.jdisc.handler.CompletionHandler;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingCompletionHandlerTestCase {
+
+ @Test
+ public void requireThatCompletedThrowsException() {
+ CompletionHandler completion = new NonWorkingCompletionHandler();
+ try {
+ completion.completed();
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatFailedThrowsException() {
+ CompletionHandler completion = new NonWorkingCompletionHandler();
+ try {
+ completion.failed(null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ completion = new NonWorkingCompletionHandler();
+ try {
+ completion.failed(new Throwable());
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingContentChannelTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingContentChannelTestCase.java
new file mode 100644
index 00000000000..73adca81bf2
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingContentChannelTestCase.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.jdisc.test;
+
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingContentChannelTestCase {
+
+ @Test
+ public void requireThatWriteThrowsException() {
+ ContentChannel content = new NonWorkingContentChannel();
+ try {
+ content.write(null, null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ content = new NonWorkingContentChannel();
+ try {
+ content.write(ByteBuffer.allocate(69), null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ content = new NonWorkingContentChannel();
+ try {
+ content.write(ByteBuffer.allocate(69), new MyCompletion());
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ content = new NonWorkingContentChannel();
+ try {
+ content.write(null, new MyCompletion());
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatCloseThrowsException() {
+ ContentChannel content = new NonWorkingContentChannel();
+ try {
+ content.close(null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ content = new NonWorkingContentChannel();
+ try {
+ content.close(new MyCompletion());
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ private static class MyCompletion implements CompletionHandler {
+
+ @Override
+ public void completed() {
+
+ }
+
+ @Override
+ public void failed(Throwable t) {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingOsgiFrameworkTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingOsgiFrameworkTestCase.java
new file mode 100644
index 00000000000..ad1e34aba66
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingOsgiFrameworkTestCase.java
@@ -0,0 +1,72 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.yahoo.jdisc.application.OsgiFramework;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleException;
+
+import java.util.Collections;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingOsgiFrameworkTestCase {
+
+ @Test
+ public void requireThatFrameworkCanStartAndStop() throws BundleException {
+ OsgiFramework osgi = new NonWorkingOsgiFramework();
+ osgi.start();
+ osgi.stop();
+ }
+
+ @Test
+ public void requireThatFrameworkHasNoBundles() throws BundleException {
+ OsgiFramework osgi = new NonWorkingOsgiFramework();
+ assertTrue(osgi.bundles().isEmpty());
+ }
+
+ @Test
+ public void requireThatFrameworkHasNoBundleContext() {
+ OsgiFramework osgi = new NonWorkingOsgiFramework();
+ assertNull(osgi.bundleContext());
+ }
+
+ @Test
+ public void requireThatFrameworkThrowsOnInstallBundle() throws BundleException {
+ OsgiFramework osgi = new NonWorkingOsgiFramework();
+ try {
+ osgi.installBundle("file:bundle.jar");
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void requireThatFrameworkThrowsOnStartBundles() throws BundleException {
+ OsgiFramework osgi = new NonWorkingOsgiFramework();
+ try {
+ osgi.startBundles(Collections.<Bundle>emptyList(), false);
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+
+ @Test
+ public void requireThatFrameworkThrowsOnRefreshPackages() throws BundleException, InterruptedException {
+ OsgiFramework osgi = new NonWorkingOsgiFramework();
+ try {
+ osgi.refreshPackages();
+ fail();
+ } catch (UnsupportedOperationException e) {
+ // expected
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestHandlerTestCase.java
new file mode 100644
index 00000000000..7893a2d1cf7
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestHandlerTestCase.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.jdisc.test;
+
+import com.yahoo.jdisc.handler.RequestHandler;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingRequestHandlerTestCase {
+
+ @Test
+ public void requireThatHandleRequestThrowsException() {
+ RequestHandler requestHandler = new NonWorkingRequestHandler();
+ try {
+ requestHandler.handleRequest(null, null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatHandleTimeoutThrowsException() {
+ RequestHandler requestHandler = new NonWorkingRequestHandler();
+ try {
+ requestHandler.handleTimeout(null, null);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatDestroyDoesNotThrow() {
+ RequestHandler requestHandler = new NonWorkingRequestHandler();
+ requestHandler.release();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestTestCase.java
new file mode 100644
index 00000000000..0d1b33fa72f
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingRequestTestCase.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.Key;
+import com.google.inject.name.Names;
+import com.yahoo.jdisc.Request;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingRequestTestCase {
+
+ @Test
+ public void requireThatFactoryMethodWorks() {
+ assertNotNull(NonWorkingRequest.newInstance("scheme://host/path"));
+ }
+
+ @Test
+ public void requireThatGuiceModulesAreInjected() {
+ Request request = NonWorkingRequest.newInstance("scheme://host/path", new AbstractModule() {
+
+ @Override
+ protected void configure() {
+ bind(String.class).annotatedWith(Names.named("foo")).toInstance("bar");
+ }
+ });
+ assertEquals("bar", request.container().getInstance(Key.get(String.class, Names.named("foo"))));
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingResponseHandlerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingResponseHandlerTestCase.java
new file mode 100644
index 00000000000..fb76e66e1e1
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingResponseHandlerTestCase.java
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.yahoo.jdisc.Response;
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingResponseHandlerTestCase {
+
+ @Test
+ public void requireThatHandleResponseThrowsException() {
+ NonWorkingResponseHandler handler = new NonWorkingResponseHandler();
+ try {
+ handler.handleResponse(new Response(Response.Status.OK));
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingServerTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingServerTestCase.java
new file mode 100644
index 00000000000..eccf4dbc655
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/NonWorkingServerTestCase.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.yahoo.jdisc.service.ServerProvider;
+import org.junit.Test;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NonWorkingServerTestCase {
+
+ @Test
+ public void requireThatStartDoesNotThrow() {
+ ServerProvider server = new NonWorkingServerProvider();
+ server.start();
+ }
+
+ @Test
+ public void requireThatCloseDoesNotThrow() {
+ ServerProvider server = new NonWorkingServerProvider();
+ server.close();
+ }
+
+ @Test
+ public void requireThatReferDoesNotThrow() {
+ ServerProvider server = new NonWorkingServerProvider();
+ server.refer();
+ }
+
+ @Test
+ public void requireThatReleaseDoesNotThrow() {
+ ServerProvider server = new NonWorkingServerProvider();
+ server.release();
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/ServerProviderConformanceTestTest.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/ServerProviderConformanceTestTest.java
new file mode 100644
index 00000000000..e59f8c96100
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/ServerProviderConformanceTestTest.java
@@ -0,0 +1,657 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.google.common.util.concurrent.SettableFuture;
+import com.google.inject.Inject;
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+import com.yahoo.jdisc.NoopSharedResource;
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.CurrentContainer;
+import com.yahoo.jdisc.service.ServerProvider;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.net.URI;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ServerProviderConformanceTestTest extends ServerProviderConformanceTest {
+
+ @Override
+ @Test
+ public void testContainerNotReadyException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testBindingSetNotFoundException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testNoBindingSetSelectedException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testBindingNotFoundException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestHandlerWithSyncCloseResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestHandlerWithSyncWriteResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestHandlerWithSyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestHandlerWithAsyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestExceptionWithSyncCloseResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestExceptionWithSyncWriteResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestNondeterministicExceptionWithSyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestExceptionBeforeResponseWriteWithSyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestExceptionAfterResponseWriteWithSyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestNondeterministicExceptionWithAsyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestExceptionBeforeResponseWriteWithAsyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestExceptionAfterResponseCloseNoContentWithAsyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestExceptionAfterResponseWriteWithAsyncHandleResponse() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithNondeterministicSyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithSyncFailureBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithSyncFailureAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithNondeterministicAsyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithAsyncFailureBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithAsyncFailureAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteWithAsyncFailureAfterResponseCloseNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteNondeterministicException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionAfterResponseCloseNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteNondeterministicExceptionWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionBeforeResponseWriteWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionAfterResponseWriteWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionAfterResponseCloseNoContentWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteNondeterministicExceptionWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionBeforeResponseWriteWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionAfterResponseWriteWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionAfterResponseCloseNoContentWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithNondeterministicSyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithSyncFailureBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithSyncFailureAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithSyncFailureAfterResponseCloseNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithNondeterministicAsyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithAsyncFailureBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithAsyncFailureAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentWriteExceptionWithAsyncFailureAfterResponseCloseNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithNondeterministicSyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithSyncFailureBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithSyncFailureAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithSyncFailureAfterResponseCloseNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithNondeterministicAsyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithAsyncFailureBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithAsyncFailureAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseWithAsyncFailureAfterResponseCloseNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseNondeterministicException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionBeforeResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseWrite() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseCloseNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseNondeterministicExceptionWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionBeforeResponseWriteWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseWriteWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithSyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseNondeterministicExceptionWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionBeforeResponseWriteWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseWriteWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithAsyncCompletion() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseNondeterministicExceptionWithSyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionBeforeResponseWriteWithSyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseWriteWithSyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithSyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseNondeterministicExceptionWithAsyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionBeforeResponseWriteWithAsyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseWriteWithAsyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testRequestContentCloseExceptionAfterResponseCloseNoContentWithAsyncFailure() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testResponseWriteCompletionException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testResponseCloseCompletionException() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ @Override
+ @Test
+ public void testResponseCloseCompletionExceptionNoContent() throws Throwable {
+ runTest(new MyAdapter());
+ }
+
+ private static void tryWrite(final ContentChannel out, final String str) {
+ try {
+ out.write(StandardCharsets.UTF_8.encode(str), null);
+ } catch (Throwable t) {
+ // Simulate handling the failure.
+ t.getMessage();
+ }
+ }
+
+ private static void tryClose(final ContentChannel out) {
+ try {
+ out.close(null);
+ } catch (Throwable t) {
+ // Simulate handling the failure.
+ t.getMessage();
+ }
+ }
+
+ private static void tryComplete(final CompletionHandler handler) {
+ try {
+ handler.completed();
+ } catch (Throwable t) {
+ // Simulate handling the failure.
+ t.getMessage();
+ }
+ }
+
+ private static class MyServer extends NoopSharedResource implements ServerProvider {
+
+ final CurrentContainer container;
+
+ @Inject
+ MyServer(final CurrentContainer container) {
+ this.container = container;
+ }
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void close() {
+
+ }
+ }
+
+ private static class MyClient {
+
+ final MyServer server;
+
+ MyClient(final MyServer server) {
+ this.server = server;
+ }
+
+ MyResponseHandler executeRequest(final boolean withRequestContent)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ final MyResponseHandler responseHandler = new MyResponseHandler();
+ final Request request;
+ try {
+ request = new Request(server.container, URI.create("http://localhost/"));
+ } catch (Throwable t) {
+ responseHandler.response.set(new Response(Response.Status.INTERNAL_SERVER_ERROR, t));
+ return responseHandler;
+ }
+ try {
+ final ContentChannel out = request.connect(responseHandler);
+ if (withRequestContent) {
+ tryWrite(out, "myRequestContent");
+ }
+ tryClose(out);
+ } catch (Throwable t) {
+ responseHandler.response.set(new Response(Response.Status.INTERNAL_SERVER_ERROR, t));
+ // Simulate handling the failure.
+ t.getMessage();
+ return responseHandler;
+ } finally {
+ request.release();
+ }
+ return responseHandler;
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final SettableFuture<Response> response = SettableFuture.create();
+ final SettableFuture<String> content = SettableFuture.create();
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ @Override
+ public ContentChannel handleResponse(final Response response) {
+ this.response.set(response);
+ return new ContentChannel() {
+
+ @Override
+ public void write(final ByteBuffer buf, final CompletionHandler handler) {
+ while (buf.hasRemaining()) {
+ out.write(buf.get());
+ }
+ tryComplete(handler);
+ }
+
+ @Override
+ public void close(final CompletionHandler handler) {
+ content.set(new String(out.toByteArray(), StandardCharsets.UTF_8));
+ tryComplete(handler);
+ }
+ };
+ }
+ }
+
+ private static class MyAdapter implements Adapter<MyServer, MyClient, MyResponseHandler> {
+
+ @Override
+ public Module newConfigModule() {
+ return Modules.EMPTY_MODULE;
+ }
+
+ @Override
+ public Class<MyServer> getServerProviderClass() {
+ return MyServer.class;
+ }
+
+ @Override
+ public MyClient newClient(final MyServer server) throws Throwable {
+ return new MyClient(server);
+ }
+
+ @Override
+ public MyResponseHandler executeRequest(
+ final MyClient client,
+ final boolean withRequestContent) throws Throwable {
+ return client.executeRequest(withRequestContent);
+ }
+
+ @Override
+ public Iterable<ByteBuffer> newResponseContent() {
+ return Collections.singleton(StandardCharsets.UTF_8.encode("myResponseContent"));
+ }
+
+ @Override
+ public void validateResponse(final MyResponseHandler responseHandler) throws Throwable {
+ responseHandler.response.get(600, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/test/TestDriverTestCase.java b/jdisc_core/src/test/java/com/yahoo/jdisc/test/TestDriverTestCase.java
new file mode 100644
index 00000000000..bea231d19d9
--- /dev/null
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/test/TestDriverTestCase.java
@@ -0,0 +1,163 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.test;
+
+import com.yahoo.jdisc.Request;
+import com.yahoo.jdisc.Response;
+import com.yahoo.jdisc.application.Application;
+import com.yahoo.jdisc.application.ContainerBuilder;
+import com.yahoo.jdisc.handler.AbstractRequestHandler;
+import com.yahoo.jdisc.handler.CompletionHandler;
+import com.yahoo.jdisc.handler.ContentChannel;
+import com.yahoo.jdisc.handler.RequestDeniedException;
+import com.yahoo.jdisc.handler.ResponseHandler;
+import com.yahoo.jdisc.service.ContainerNotReadyException;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TestDriverTestCase {
+
+ @Test
+ public void requireThatFactoryMethodsWork() {
+ TestDriver.newInjectedApplicationInstance(MyApplication.class).close();
+ TestDriver.newInjectedApplicationInstanceWithoutOsgi(MyApplication.class).close();
+ TestDriver.newInjectedApplicationInstance(new MyApplication()).close();
+ TestDriver.newInjectedApplicationInstanceWithoutOsgi(new MyApplication()).close();
+ TestDriver.newSimpleApplicationInstance().close();
+ TestDriver.newSimpleApplicationInstanceWithoutOsgi().close();
+ }
+
+ @Test
+ public void requireThatAccessorsWork() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ assertNotNull(driver.bootstrapLoader());
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatConnectRequestWorks() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = new MyRequestHandler(new MyContentChannel());
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("scheme://host/path", requestHandler);
+ driver.activateContainer(builder);
+ ContentChannel content = driver.connectRequest("scheme://host/path", new MyResponseHandler());
+ assertNotNull(content);
+ content.close(null);
+ assertNotNull(requestHandler.handler);
+ requestHandler.handler.handleResponse(new Response(Response.Status.OK)).close(null);
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatDispatchRequestWorks() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ MyRequestHandler requestHandler = new MyRequestHandler(new MyContentChannel());
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("scheme://host/path", requestHandler);
+ driver.activateContainer(builder);
+ driver.dispatchRequest("scheme://host/path", new MyResponseHandler());
+ assertNotNull(requestHandler.handler);
+ assertTrue(requestHandler.content.closed);
+ requestHandler.handler.handleResponse(new Response(Response.Status.OK)).close(null);
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatFailedRequestCreateDoesNotBlockClose() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ try {
+ driver.connectRequest("scheme://host/path", new MyResponseHandler());
+ fail();
+ } catch (ContainerNotReadyException e) {
+
+ }
+ assertTrue(driver.close());
+ }
+
+ @Test
+ public void requireThatFailedRequestConnectDoesNotBlockClose() {
+ TestDriver driver = TestDriver.newSimpleApplicationInstanceWithoutOsgi();
+ ContainerBuilder builder = driver.newContainerBuilder();
+ builder.serverBindings().bind("scheme://host/path", new MyRequestHandler(null));
+ driver.activateContainer(builder);
+ try {
+ driver.connectRequest("scheme://host/path", new MyResponseHandler());
+ fail();
+ } catch (RequestDeniedException e) {
+
+ }
+ assertTrue(driver.close());
+ }
+
+ private static class MyApplication implements Application {
+
+ @Override
+ public void start() {
+
+ }
+
+ @Override
+ public void stop() {
+
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+ }
+
+ private static class MyRequestHandler extends AbstractRequestHandler {
+
+ final MyContentChannel content;
+ ResponseHandler handler;
+
+ MyRequestHandler(MyContentChannel content) {
+ this.content = content;
+ }
+
+ @Override
+ public ContentChannel handleRequest(Request request, ResponseHandler handler) {
+ this.handler = handler;
+ if (content == null) {
+ throw new RequestDeniedException(request);
+ }
+ return content;
+ }
+ }
+
+ private static class MyResponseHandler implements ResponseHandler {
+
+ final MyContentChannel content = new MyContentChannel();
+
+ @Override
+ public ContentChannel handleResponse(Response response) {
+ return content;
+ }
+ }
+
+ private static class MyContentChannel implements ContentChannel {
+
+ boolean closed = false;
+
+ @Override
+ public void write(ByteBuffer buf, CompletionHandler handler) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close(CompletionHandler handler) {
+ closed = true;
+ handler.completed();
+ }
+ }
+}