From e1e60fd2cccb678a4cde39b9c41046b79a555f0c Mon Sep 17 00:00:00 2001 From: Sjoerd Talsma Date: Sat, 30 Nov 2024 14:33:51 +0100 Subject: [PATCH] Move some tests to api package. Signed-off-by: Sjoerd Talsma --- .../context/api/ContextSnapshotImpl.java | 6 +- .../context/api/ContextSnapshotTest.java | 127 ++++++------------ .../context/api/NoContextManagersTest.java | 73 ++++++++++ .../context/dummy/DummyContext.java | 75 +++++++++++ .../context/dummy/DummyContextManager.java | 58 ++++++++ .../context/dummy/DummyContextTimer.java | 23 ++++ .../context/dummy/ThrowingContextManager.java | 122 +++++++++++++++++ ....talsmasoftware.context.api.ContextManager | 2 + ...nl.talsmasoftware.context.api.ContextTimer | 1 + 9 files changed, 400 insertions(+), 87 deletions(-) rename context-propagation-core/src/test/java/nl/talsmasoftware/context/core/ContextManagersTest.java => context-propagation-api/src/test/java/nl/talsmasoftware/context/api/ContextSnapshotTest.java (55%) create mode 100644 context-propagation-api/src/test/java/nl/talsmasoftware/context/api/NoContextManagersTest.java create mode 100644 context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContext.java create mode 100644 context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextManager.java create mode 100644 context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextTimer.java create mode 100644 context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/ThrowingContextManager.java create mode 100644 context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextManager create mode 100644 context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextTimer diff --git a/context-propagation-api/src/main/java/nl/talsmasoftware/context/api/ContextSnapshotImpl.java b/context-propagation-api/src/main/java/nl/talsmasoftware/context/api/ContextSnapshotImpl.java index 07d38d09..f9d4b95d 100644 --- a/context-propagation-api/src/main/java/nl/talsmasoftware/context/api/ContextSnapshotImpl.java +++ b/context-propagation-api/src/main/java/nl/talsmasoftware/context/api/ContextSnapshotImpl.java @@ -48,9 +48,9 @@ private ContextSnapshotImpl() { values[i] = getActiveContextValue(managers.get(i)); } if (managers.isEmpty() && SNAPSHOT_LOGGER.isLoggable(Level.FINER)) { - SNAPSHOT_LOGGER.finer(this + " was created but no ContextManagers were found! " - + " Thread=" + Thread.currentThread() - + ", ContextClassLoader=" + Thread.currentThread().getContextClassLoader()); + final Thread currentThread = Thread.currentThread(); + SNAPSHOT_LOGGER.finer(this + " was created but no ContextManagers were found! Thread=" + + currentThread.getName() + ", ContextClassLoader=" + currentThread.getContextClassLoader()); } } diff --git a/context-propagation-core/src/test/java/nl/talsmasoftware/context/core/ContextManagersTest.java b/context-propagation-api/src/test/java/nl/talsmasoftware/context/api/ContextSnapshotTest.java similarity index 55% rename from context-propagation-core/src/test/java/nl/talsmasoftware/context/core/ContextManagersTest.java rename to context-propagation-api/src/test/java/nl/talsmasoftware/context/api/ContextSnapshotTest.java index f83d882a..960819a0 100644 --- a/context-propagation-core/src/test/java/nl/talsmasoftware/context/core/ContextManagersTest.java +++ b/context-propagation-api/src/test/java/nl/talsmasoftware/context/api/ContextSnapshotTest.java @@ -13,73 +13,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package nl.talsmasoftware.context.core; +package nl.talsmasoftware.context.api; -import nl.talsmasoftware.context.api.Context; -import nl.talsmasoftware.context.api.ContextSnapshot; -import nl.talsmasoftware.context.core.concurrent.ContextAwareExecutorService; import nl.talsmasoftware.context.dummy.DummyContext; import nl.talsmasoftware.context.dummy.DummyContextManager; +import nl.talsmasoftware.context.dummy.DummyContextTimer; import nl.talsmasoftware.context.dummy.ThrowingContextManager; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; /** * @author Sjoerd Talsma */ -public class ContextManagersTest { +class ContextSnapshotTest { DummyContextManager dummyManager = new DummyContextManager(); @BeforeEach @AfterEach - public void resetContexts() { - ContextManagers.clearActiveContexts(); + void resetContexts() { + ContextManager.clearAll(); + DummyContextTimer.clear(); } @BeforeEach @AfterEach - public void resetContextClassLoader() { - ContextManagers.useClassLoader(null); + void resetContextClassLoader() { + ContextManager.useClassLoader(null); } @Test - public void testUnsupportedConstructor() { - Constructor[] constructors = ContextManagers.class.getDeclaredConstructors(); - assertThat("Number of constructors", constructors.length, is(1)); - assertThat("Constructor parameters", constructors[0].getParameterTypes().length, is(0)); - assertThat("Constructor accessibility", constructors[0].isAccessible(), is(false)); - try { - constructors[0].setAccessible(true); - constructors[0].newInstance(); - fail("Exception expected."); - } catch (IllegalAccessException | InstantiationException e) { - fail("InvocationTargetException expected."); - } catch (InvocationTargetException e) { - assertThat(e.getCause(), is(instanceOf(UnsupportedOperationException.class))); - } - } - - @Test - public void testSnapshot_inSameThread() { + void testSnapshot_inSameThread() { dummyManager.clear(); assertThat(DummyContext.currentValue(), is(nullValue())); @@ -89,7 +68,7 @@ public void testSnapshot_inSameThread() { DummyContext ctx2 = new DummyContext("second value"); assertThat(DummyContext.currentValue(), is("second value")); - ContextSnapshot snapshot = ContextManagers.createContextSnapshot(); + ContextSnapshot snapshot = ContextSnapshot.capture(); assertThat(DummyContext.currentValue(), is("second value")); // No context change because of snapshot. DummyContext ctx3 = new DummyContext("third value"); @@ -116,64 +95,28 @@ public void testSnapshot_inSameThread() { } @Test - public void testSnapshotThreadPropagation() throws ExecutionException, InterruptedException { - DummyContext.reset(); - ExecutorService threadpool = new ContextAwareExecutorService(Executors.newCachedThreadPool()); - assertThat(DummyContext.currentValue(), is(nullValue())); - - DummyContext ctx1 = new DummyContext("initial value"); - assertThat(DummyContext.currentValue(), is("initial value")); - Future threadResult = threadpool.submit(new Callable() { - public String call() throws Exception { - return DummyContext.currentValue(); - } - }); - assertThat(threadResult.get(), is("initial value")); - - DummyContext ctx2 = new DummyContext("second value"); - threadResult = threadpool.submit(new Callable() { - public String call() throws Exception { - String res = DummyContext.currentValue(); - try (DummyContext inThread = new DummyContext("in-thread value")) { - res += ", " + DummyContext.currentValue(); - } - return res + ", " + DummyContext.currentValue(); - } - }); - assertThat(DummyContext.currentValue(), is("second value")); - assertThat(threadResult.get(), is("second value, in-thread value, second value")); - - ctx2.close(); - ctx1.close(); - } - - @Test - public void testConcurrentSnapshots() throws ExecutionException, InterruptedException { - int threadcount = 25; - ExecutorService threadpool = Executors.newFixedThreadPool(threadcount); + void testConcurrentSnapshots() throws ExecutionException, InterruptedException { + int threadCount = 25; + ExecutorService threadPool = Executors.newFixedThreadPool(threadCount); try { - List> snapshots = new ArrayList>(threadcount); - for (int i = 0; i < threadcount; i++) { - snapshots.add(threadpool.submit(new Callable() { - public ContextSnapshot call() throws Exception { - return ContextManagers.createContextSnapshot(); - } - })); + List> snapshots = new ArrayList<>(threadCount); + for (int i = 0; i < threadCount; i++) { + snapshots.add(threadPool.submit(ContextSnapshot::capture)); } - for (int i = 0; i < threadcount; i++) { + for (int i = 0; i < threadCount; i++) { assertThat(snapshots.get(i).get(), is(notNullValue())); } } finally { - threadpool.shutdown(); + threadPool.shutdown(); } } @Test - public void testCreateSnapshot_ExceptionHandling() { + void testCreateSnapshot_ExceptionHandling() { ThrowingContextManager.onGet = new IllegalStateException("No active context!"); Context ctx = new DummyContext("blah"); - ContextSnapshot snapshot = ContextManagers.createContextSnapshot(); + ContextSnapshot snapshot = ContextSnapshot.capture(); ctx.close(); assertThat(DummyContext.currentValue(), is(nullValue())); @@ -184,12 +127,12 @@ public void testCreateSnapshot_ExceptionHandling() { } @Test - public void testReactivateSnapshot_ExceptionHandling() { + void testReactivateSnapshot_ExceptionHandling() { final RuntimeException reactivationException = new IllegalStateException("Cannot create new context!"); ThrowingContextManager mgr = new ThrowingContextManager(); Context ctx1 = new DummyContext("foo"); Context ctx2 = mgr.initializeNewContext("bar"); - ContextSnapshot snapshot = ContextManagers.createContextSnapshot(); + ContextSnapshot snapshot = ContextSnapshot.capture(); ThrowingContextManager.onInitialize = reactivationException; assertThat(DummyContext.currentValue(), is("foo")); @@ -207,14 +150,14 @@ public void testReactivateSnapshot_ExceptionHandling() { } @Test - public void testConcurrentSnapshots_fixedClassLoader() throws ExecutionException, InterruptedException { - ContextManagers.useClassLoader(Thread.currentThread().getContextClassLoader()); + void testConcurrentSnapshots_fixedClassLoader() throws ExecutionException, InterruptedException { + ContextManager.useClassLoader(Thread.currentThread().getContextClassLoader()); int threadcount = 25; ExecutorService threadpool = Executors.newFixedThreadPool(threadcount); try { Future[] snapshots = new Future[threadcount]; for (int i = 0; i < threadcount; i++) { - snapshots[i] = threadpool.submit(ContextManagers::createContextSnapshot); + snapshots[i] = threadpool.submit(ContextSnapshot::capture); } for (int i = 0; i < threadcount; i++) { @@ -226,4 +169,20 @@ public void testConcurrentSnapshots_fixedClassLoader() throws ExecutionException } } + @Test + void toString_isForSnapshot_notSnapshotImpl() { + assertThat(ContextSnapshot.capture(), hasToString(containsString("ContextSnapshot{"))); + } + + @Test + void testTimingDelegation() { + DummyContextTimer.clear(); + assertThat(DummyContextTimer.getLastTimedMillis(ContextSnapshot.class, "capture"), nullValue()); + assertThat(DummyContextTimer.getLastTimedMillis(ContextSnapshot.class, "reactivate"), nullValue()); + + ContextSnapshot.capture().reactivate().close(); + assertThat(DummyContextTimer.getLastTimedMillis(ContextSnapshot.class, "capture"), notNullValue()); + assertThat(DummyContextTimer.getLastTimedMillis(ContextSnapshot.class, "reactivate"), notNullValue()); + } + } diff --git a/context-propagation-api/src/test/java/nl/talsmasoftware/context/api/NoContextManagersTest.java b/context-propagation-api/src/test/java/nl/talsmasoftware/context/api/NoContextManagersTest.java new file mode 100644 index 00000000..34c946a6 --- /dev/null +++ b/context-propagation-api/src/test/java/nl/talsmasoftware/context/api/NoContextManagersTest.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016-2024 Talsma ICT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.talsmasoftware.context.api; + +import nl.talsmasoftware.context.dummy.DummyContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +public class NoContextManagersTest { + private static final String SERVICE_LOCATION = "target/test-classes/META-INF/services/"; + private static final File SERVICE_FILE = new File(SERVICE_LOCATION + ContextManager.class.getName()); + private static final File TMP_SERVICE_FILE = new File(SERVICE_LOCATION + "tmp-ContextManager"); + + @BeforeEach + public void avoidContextManagersCache() { + ContextManager.useClassLoader(new ClassLoader(Thread.currentThread().getContextClassLoader()) { + }); + assertThat("Move service file", SERVICE_FILE.renameTo(TMP_SERVICE_FILE), is(true)); + } + + @AfterEach + public void resetDefaultClassLoader() { + ContextManager.useClassLoader(null); + assertThat("Restore service file!", TMP_SERVICE_FILE.renameTo(SERVICE_FILE), is(true)); + } + + @Test + public void testReactivate_withoutContextManagers() { + Context ctx1 = new DummyContext("foo"); + ContextSnapshot snapshot = ContextSnapshot.capture(); + ctx1.close(); + + ContextSnapshot.Reactivation reactivated = snapshot.reactivate(); + reactivated.close(); + } + + @Test + public void testCreateSnapshot_withoutContextManagers() { + ContextSnapshot snapshot = ContextSnapshot.capture(); + assertThat(snapshot, is(notNullValue())); + + ContextSnapshot.Reactivation reactivated = snapshot.reactivate(); + assertThat(reactivated, is(notNullValue())); + reactivated.close(); + } + + @Test + public void testClearManagedContexts_withoutContextManagers() { + Assertions.assertDoesNotThrow(() -> ContextManager.clearAll()); // there should be no exception + } + +} diff --git a/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContext.java b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContext.java new file mode 100644 index 00000000..c84479b0 --- /dev/null +++ b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContext.java @@ -0,0 +1,75 @@ +/* + * Copyright 2016-2024 Talsma ICT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.talsmasoftware.context.dummy; + +import nl.talsmasoftware.context.api.Context; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * @author Sjoerd Talsma + */ +public final class DummyContext implements Context { + private static final ThreadLocal INSTANCE = new ThreadLocal<>(); + + private final DummyContext parent; + private final String value; + private final AtomicBoolean closed; + + public DummyContext(String newValue) { + this.parent = INSTANCE.get(); + this.value = newValue; + this.closed = new AtomicBoolean(false); + INSTANCE.set(this); + } + + // Public for testing! + public boolean isClosed() { + return closed.get(); + } + + public String getValue() { + return value; + } + + public void close() { + if (closed.compareAndSet(false, true) && INSTANCE.get() == this) { + DummyContext current = INSTANCE.get(); + while (current != null && current.isClosed()) { + current = current.parent; + } + if (current == null) { + INSTANCE.remove(); + } else { + INSTANCE.set(current); + } + } + } + + public static String currentValue() { + final Context currentContext = INSTANCE.get(); + return currentContext != null ? currentContext.getValue() : null; + } + + public static void setCurrentValue(String value) { + new DummyContext(value); + } + + public static void reset() { + INSTANCE.remove(); + } + +} diff --git a/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextManager.java b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextManager.java new file mode 100644 index 00000000..f1b766bf --- /dev/null +++ b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextManager.java @@ -0,0 +1,58 @@ +/* + * Copyright 2016-2024 Talsma ICT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.talsmasoftware.context.dummy; + +import nl.talsmasoftware.context.api.Context; +import nl.talsmasoftware.context.api.ContextManager; + +/** + * Trivial manager around the {@link DummyContext} implementation to be registered as service provider. + * + * @author Sjoerd Talsma + */ +public class DummyContextManager implements ContextManager { + + public Context initializeNewContext(String value) { + return new DummyContext(value); + } + + public String getActiveContextValue() { + return DummyContext.currentValue(); + } + + public void clear() { + DummyContext.reset(); + } + + public static void clearAllContexts() { + DummyContext.reset(); + } + + @Override + public int hashCode() { + return DummyContextManager.class.hashCode(); + } + + @Override + public boolean equals(Object other) { + return this == other || other instanceof DummyContextManager; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } +} diff --git a/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextTimer.java b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextTimer.java new file mode 100644 index 00000000..6d5ca415 --- /dev/null +++ b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/DummyContextTimer.java @@ -0,0 +1,23 @@ +package nl.talsmasoftware.context.dummy; + +import nl.talsmasoftware.context.api.ContextTimer; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class DummyContextTimer implements ContextTimer { + private static final Map LAST_TIMED = new HashMap(); + + public static Long getLastTimedMillis(Class type, String method) { + return LAST_TIMED.get(type.getName() + "." + method); + } + + public void update(Class type, String method, long duration, TimeUnit unit, Throwable error) { + LAST_TIMED.put(type.getName() + "." + method, unit.toMillis(duration)); + } + + public static void clear() { + LAST_TIMED.clear(); + } +} diff --git a/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/ThrowingContextManager.java b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/ThrowingContextManager.java new file mode 100644 index 00000000..d830737d --- /dev/null +++ b/context-propagation-api/src/test/java/nl/talsmasoftware/context/dummy/ThrowingContextManager.java @@ -0,0 +1,122 @@ +/* + * Copyright 2016-2024 Talsma ICT + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package nl.talsmasoftware.context.dummy; + +import nl.talsmasoftware.context.api.Context; +import nl.talsmasoftware.context.api.ContextManager; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Badly behaved {@link ContextManager} implementation that can throw things at us for testing purposes. + * + * @author Sjoerd Talsma + */ +public class ThrowingContextManager implements ContextManager { + public static RuntimeException inConstructor = null, onInitialize = null, onGet = null, onClose = null, onClear = null; + + public ThrowingContextManager() { + if (inConstructor != null) try { + throw inConstructor; + } finally { + inConstructor = null; + } + } + + @Override + public Context initializeNewContext(String value) { + if (onInitialize != null) try { + throw onInitialize; + } finally { + onInitialize = null; + } + return new Ctx(value); + } + + @Override + public String getActiveContextValue() { + if (onGet != null) try { + throw onGet; + } finally { + onGet = null; + } + return Ctx.currentValue(); + } + + @Override + public void clear() { + if (onClear != null) try { + throw onClear; + } finally { + onClear = null; + } + Ctx.remove(); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + private final static class Ctx implements Context { + private static final ThreadLocal STORAGE = new ThreadLocal<>(); + + private final Ctx parent; + private final String value; + private final AtomicBoolean closed; + + private Ctx(String newValue) { + parent = STORAGE.get(); + value = newValue; + closed = new AtomicBoolean(false); + STORAGE.set(this); + } + + @Override + public String getValue() { + return value; + } + + @Override + public void close() { + if (onClose != null) try { + throw onClose; + } finally { + onClose = null; + } + if (closed.compareAndSet(false, true) && STORAGE.get() == this) { + Ctx current = STORAGE.get(); + while (current != null && current.closed.get()) { + current = current.parent; + } + if (current == null) { + STORAGE.remove(); + } else { + STORAGE.set(current); + } + } + } + + private static String currentValue() { + Ctx current = STORAGE.get(); + return current != null ? current.getValue() : null; + } + + private static void remove() { + STORAGE.remove(); + } + } +} diff --git a/context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextManager b/context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextManager new file mode 100644 index 00000000..08742947 --- /dev/null +++ b/context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextManager @@ -0,0 +1,2 @@ +nl.talsmasoftware.context.dummy.DummyContextManager +nl.talsmasoftware.context.dummy.ThrowingContextManager diff --git a/context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextTimer b/context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextTimer new file mode 100644 index 00000000..c1fe1c4a --- /dev/null +++ b/context-propagation-api/src/test/resources/META-INF/services/nl.talsmasoftware.context.api.ContextTimer @@ -0,0 +1 @@ +nl.talsmasoftware.context.dummy.DummyContextTimer