Skip to content

Commit

Permalink
Move some tests to api package.
Browse files Browse the repository at this point in the history
Signed-off-by: Sjoerd Talsma <[email protected]>
  • Loading branch information
sjoerdtalsma committed Nov 30, 2024
1 parent 1cbaa47 commit e1e60fd
Show file tree
Hide file tree
Showing 9 changed files with 400 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()));

Expand All @@ -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");
Expand All @@ -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<String> threadResult = threadpool.submit(new Callable<String>() {
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<String>() {
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<Future<ContextSnapshot>> snapshots = new ArrayList<Future<ContextSnapshot>>(threadcount);
for (int i = 0; i < threadcount; i++) {
snapshots.add(threadpool.submit(new Callable<ContextSnapshot>() {
public ContextSnapshot call() throws Exception {
return ContextManagers.createContextSnapshot();
}
}));
List<Future<ContextSnapshot>> 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<String> ctx = new DummyContext("blah");
ContextSnapshot snapshot = ContextManagers.createContextSnapshot();
ContextSnapshot snapshot = ContextSnapshot.capture();
ctx.close();

assertThat(DummyContext.currentValue(), is(nullValue()));
Expand All @@ -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<String> ctx1 = new DummyContext("foo");
Context<String> ctx2 = mgr.initializeNewContext("bar");
ContextSnapshot snapshot = ContextManagers.createContextSnapshot();
ContextSnapshot snapshot = ContextSnapshot.capture();
ThrowingContextManager.onInitialize = reactivationException;

assertThat(DummyContext.currentValue(), is("foo"));
Expand All @@ -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<ContextSnapshot>[] 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++) {
Expand All @@ -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());
}

}
Original file line number Diff line number Diff line change
@@ -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<String> 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
}

}
Original file line number Diff line number Diff line change
@@ -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<String> {
private static final ThreadLocal<DummyContext> 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<String> currentContext = INSTANCE.get();
return currentContext != null ? currentContext.getValue() : null;
}

public static void setCurrentValue(String value) {
new DummyContext(value);
}

public static void reset() {
INSTANCE.remove();
}

}
Loading

0 comments on commit e1e60fd

Please sign in to comment.