Skip to content

Commit

Permalink
Merge pull request #54 from ppi-ag/feature-scope-execution
Browse files Browse the repository at this point in the history
Feature scope execution
  • Loading branch information
JanSchankin committed Mar 2, 2021
2 parents f5b00bd + e9cbc32 commit 922511f
Show file tree
Hide file tree
Showing 9 changed files with 114 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@

package de.ppi.deepsampler.core.api;

import de.ppi.deepsampler.core.error.InvalidConfigException;
import de.ppi.deepsampler.core.model.ExecutionRepository;
import de.ppi.deepsampler.core.model.SampleRepository;
import de.ppi.deepsampler.core.model.SingletonScope;
import de.ppi.deepsampler.core.model.ThreadScope;

/**
* Provides functionality for influencing the execution phase of the stubbing done by deepsampler.
Expand Down Expand Up @@ -36,4 +39,22 @@ public static void useGlobal(SampleReturnProcessor sampleReturnProcessor) {
public static void useForLastSample(SampleReturnProcessor sampleReturnProcessor) {
ExecutionRepository.getInstance().addSampleReturnProcessor(SampleRepository.getInstance().getLastSampleDefinition(), sampleReturnProcessor);
}

/**
* Set the scope for all samples created with deepsampler.
*
* @see ScopeType, {@link de.ppi.deepsampler.core.model.Scope}
* @param type the type of the scope you want to set
*/
public static void setScope(ScopeType type) {
if (type == ScopeType.SINGLETON) {
ExecutionRepository.setScope(new SingletonScope<>());
SampleRepository.setScope(new SingletonScope<>());
} else if (type == ScopeType.THREAD) {
ExecutionRepository.setScope(new ThreadScope<>());
SampleRepository.setScope(new ThreadScope<>());
} else {
throw new InvalidConfigException("The scope type %s is not supported!", type);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package de.ppi.deepsampler.core.api;

/**
* Type of the scope used by deepsampler. The scope defines how samples are bound respectively how long they will live in the JVM.
*/
public enum ScopeType {

/**
* Samples are bound to a thread. So every Sample will live and die with the thread they are created in.
*
* @see de.ppi.deepsampler.core.model.ThreadScope
*/
THREAD,

/**
* Samples are bound to the jvm. So every sample will live as long the jvm is active.
*
* @see de.ppi.deepsampler.core.model.SingletonScope
*/
SINGLETON

}

Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,34 @@ public class ExecutionRepository {
private final List<SampleReturnProcessor> globalProcessors = new ArrayList<>();
private final Map<SampleDefinition, List<SampleReturnProcessor>> sampleDefinitionSampleReturnProcessorMap = new HashMap<>();

private static final ThreadLocal<ExecutionRepository> myInstance = ThreadLocal.withInitial(ExecutionRepository::new);
private static Scope<ExecutionRepository> myInstance = new ThreadScope<>();

/**
* Singleton Constructor.
*/
private ExecutionRepository() {}

public static synchronized ExecutionRepository getInstance() {
return myInstance.get();
return myInstance.getOrCreate(ExecutionRepository::new);
}

public Map<Class<?>, ExecutionInformation> getAll() {
return Collections.unmodifiableMap(executionInformation);
}

/**
* Sets the scope of the {@link SampleRepository} end defines the visibility limits of Samples.
* The default {@link Scope} is {@link ThreadScope}, so by default Samples are not shared across {@link Thread}s.
*
* @param executionRepositoryRepository The {@link Scope} that should be used by the {@link SampleRepository}.
*/
public static synchronized void setScope(Scope<ExecutionRepository> executionRepositoryRepository) {
Objects.requireNonNull(executionRepositoryRepository, "The ExecutionRepositoryRepository must not be null.");

ExecutionRepository.myInstance.close();
ExecutionRepository.myInstance = executionRepositoryRepository;
}

public ExecutionInformation getOrCreate(final Class<?> cls) {
return executionInformation.computeIfAbsent(cls, k -> new ExecutionInformation());
}
Expand All @@ -54,6 +67,5 @@ public void clear() {
executionInformation.clear();
globalProcessors.clear();
sampleDefinitionSampleReturnProcessorMap.clear();
myInstance.remove();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class SampleRepository {
private SampleDefinition lastSample;
private List<ParameterMatcher<?>> currentParameterMatchers = new ArrayList<>();

private static Scope sampleRepositoryScope = new ThreadScope();
private static Scope<SampleRepository> sampleRepositoryScope = new ThreadScope<>();

/**
* Singleton Constructor.
Expand All @@ -36,10 +36,10 @@ public static synchronized SampleRepository getInstance() {
*
* @param sampleRepositoryScope The {@link Scope} that should be used by the {@link SampleRepository}.
*/
public static synchronized void setScope(Scope sampleRepositoryScope) {
public static synchronized void setScope(Scope<SampleRepository> sampleRepositoryScope) {
Objects.requireNonNull(sampleRepositoryScope, "The SampleRepositoryScope must not be null.");

SampleRepository.sampleRepositoryScope.cleanUp();
SampleRepository.sampleRepositoryScope.close();
SampleRepository.sampleRepositoryScope = sampleRepositoryScope;
}

Expand All @@ -57,8 +57,8 @@ public void add(final SampleDefinition sampleDefinition) {
/**
* Checks whether both methods are the same or not
*
* @param wantedSampledMethod
* @param sampledMethod
* @param wantedSampledMethod the sampled method defined by the user
* @param sampledMethod the actual method the provider came across
* @return true if both methods are the same
*/
private boolean methodMatches(SampledMethod wantedSampledMethod, SampledMethod sampledMethod) {
Expand All @@ -69,9 +69,9 @@ private boolean methodMatches(SampledMethod wantedSampledMethod, SampledMethod s
* Returns true if the declaring types of wantedSampledMethod and sampledMethod are the same, or if the declaring
* type of wantedSampleMethod extends the declaring type of sampledMethod.
*
* @param wantedSampledMethod
* @param sampledMethod
* @return
* @param wantedSampledMethod the sampled method defined by the user
* @param sampledMethod the actual method the provider came across
* @return true if the type in which the wanted method has been defined matches with the actual method
*/
private boolean wantedTypeExtendsSampledType(SampledMethod wantedSampledMethod, SampledMethod sampledMethod) {
return sampledMethod.getTarget().isAssignableFrom(wantedSampledMethod.getTarget());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,28 @@
import java.util.function.Supplier;

/**
* Defines the {@link SampleRepository}s scope by providing a container that holds the {@link SampleRepository}.
* The {@link Scope} can be set using {@link SampleRepository#setScope(Scope)}.
* Defines the a scope by providing a container that holds the Repositories used by deepsampler.
* The {@link Scope} can be set using {@link de.ppi.deepsampler.core.api.Execution#setScope(de.ppi.deepsampler.core.api.ScopeType)}.
*
* The default scope is {@link ThreadScope}, so Samples and all data associated with Samples are not
* shared across different {@link Thread}s.
*/
public interface Scope {
public interface Scope<T> extends AutoCloseable {

/**
* Retrieves a {@link SampleRepository}, or creates a new one if no one exists yet. The implementation decides
* where the SampleRepository is stored. E.g. If it is stored in a session scoped Bean, the SampleRepository is also
* Retrieves the hold object, or creates a new one if no one exists yet. The implementation decides
* where the object is stored. E.g. If it is stored in a session scoped Bean, the object is also
* session scoped. If it is stored in a {@link ThreadLocal} the scope is thread scoped.
*
* The default scope is thread scope ({@link ThreadScope}.
*
* @param supplier If the current scope doesn't have a {@link SampleRepository} yet, this supplier is used to create a new one.
* @return the {@link SampleRepository} of the current scope.
* @param supplier If the current scope doesn't have an instance of the hold class yet, this supplier is used to create a new one.
* @return T the instance hold by the current scope.
*/
SampleRepository getOrCreate(Supplier<SampleRepository> supplier);
T getOrCreate(Supplier<T> supplier);

/**
* Cleans resources that might be referenced by a Scope. cleanUp() is called on a Scope that will be
* replaced by a new Scope.
* {@inheritDoc}
*/
void cleanUp();

void close();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,25 @@
import java.util.function.Supplier;

/**
* A singleton scope for the {@link SampleRepository}. If this scope is used, Samples and all associated data are
* A singleton scope for the repositories in deepsampler. If this scope is used, Samples and all associated data are
* shared across separated {@link Thread}s.
*
* The default scope is {@link ThreadScope}.
*
* The scope can be changed using {@link SampleRepository#setScope(Scope)}.
* The scope can be changed using {@link de.ppi.deepsampler.core.api.Execution#setScope(de.ppi.deepsampler.core.api.ScopeType)}.
*/
public class SingletonScope implements Scope {
public class SingletonScope<T> implements Scope<T> {

private static SampleRepository sampleRepository;
private T sampleRepository;

/**
* Delivers the global SampleRepository or creates a new one if none exists yet.
* Delivers the global object hold by the scope or creates a new one if none exists yet.
*
* @param supplier A Supplier that is used to create a new {@link SampleRepository} if non exists yet.
* @param supplier A Supplier that is used to create a new object of the hold class if non exists yet.
* @return The global {@link SampleRepository}.
*/
@Override
public synchronized SampleRepository getOrCreate(Supplier<SampleRepository> supplier) {
public synchronized T getOrCreate(Supplier<T> supplier) {
if (sampleRepository == null) {
sampleRepository = supplier.get();
}
Expand All @@ -37,9 +37,7 @@ public synchronized SampleRepository getOrCreate(Supplier<SampleRepository> supp
}

@Override
public void cleanUp() {
if (sampleRepository != null) {
sampleRepository.clear();
}
public void close() {
// nothing to do
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,27 @@
import java.util.function.Supplier;

/**
* A thread scope for the {@link SampleRepository}. If this scope is used, Samples and all associated data cannot be
* A thread scope for an arbitrary object. If this scope is used, Samples and all associated data cannot be
* shared across separated {@link Thread}s.
*
* This is the default scope.
*
* The scope can be changed using {@link SampleRepository#setScope(Scope)}.
* The scope can be changed using {@link de.ppi.deepsampler.core.api.Execution#setScope(de.ppi.deepsampler.core.api.ScopeType)}.
*/
public class ThreadScope implements Scope {
public class ThreadScope<T> implements Scope<T> {

private final ThreadLocal<SampleRepository> sampleRepository = new ThreadLocal<>();
private final ThreadLocal<T> sampleRepository = new ThreadLocal<>();


/**
* Delivers the SampleRepository of the current {@link Thread} or creates a new one if the current {@link Thread} doesn't
* have a {@link SampleRepository}.
* Delivers the hold object of the current {@link Thread} or creates a new one if the current {@link Thread} doesn't
* have an instance yet.
*
* @param supplier A Supplier that is used to create a new {@link SampleRepository} if the current {@link Thread} doesn't have one.
* @return The {@link SampleRepository} of the current {@link Thread}.
* @param supplier A Supplier that is used to create a new object of the hold class if the current {@link Thread} doesn't have one.
* @return The hold instance of the current {@link Thread}.
*/
@Override
public synchronized SampleRepository getOrCreate(Supplier<SampleRepository> supplier) {
public synchronized T getOrCreate(Supplier<T> supplier) {
if (sampleRepository.get() == null) {
sampleRepository.set(supplier.get());
}
Expand All @@ -39,7 +39,7 @@ public synchronized SampleRepository getOrCreate(Supplier<SampleRepository> supp
}

@Override
public void cleanUp() {
public void close() {
sampleRepository.remove();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package de.ppi.deepsampler.core.api;

import de.ppi.deepsampler.core.error.InvalidConfigException;
import de.ppi.deepsampler.core.model.ExecutionRepository;
import de.ppi.deepsampler.core.model.SampleDefinition;
import de.ppi.deepsampler.core.model.SampleRepository;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

class ExecutionTest {

Expand Down Expand Up @@ -34,4 +36,10 @@ void useForLastSample() {
// THEN
assertEquals(sampleReturnProcessor, ExecutionRepository.getInstance().getSampleReturnProcessorsFor(SampleRepository.getInstance().getLastSampleDefinition()).get(0));
}

@Test
void testNotSupported() {
// GIVEN WHEN THEN
assertThrows(InvalidConfigException.class, () -> Execution.setScope(null));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@

package de.ppi.deepsampler.provider.common;

import de.ppi.deepsampler.core.api.Execution;
import de.ppi.deepsampler.core.api.Matchers;
import de.ppi.deepsampler.core.api.Sample;
import de.ppi.deepsampler.core.api.Sampler;
import de.ppi.deepsampler.core.api.*;
import de.ppi.deepsampler.core.error.InvalidConfigException;
import de.ppi.deepsampler.core.error.VerifyException;
import de.ppi.deepsampler.core.internal.FixedQuantity;
import de.ppi.deepsampler.core.model.ExecutionRepository;
import de.ppi.deepsampler.core.model.SampleRepository;
import de.ppi.deepsampler.core.model.SingletonScope;
import de.ppi.deepsampler.core.model.ThreadScope;
import de.ppi.deepsampler.persistence.api.PersistentSampleManager;
import de.ppi.deepsampler.persistence.api.PersistentSampler;
import de.ppi.deepsampler.persistence.error.PersistenceException;
Expand Down Expand Up @@ -676,7 +672,7 @@ public void threadScopeWorks() throws ExecutionException, InterruptedException {
assertEquals(VALUE_A, getTestService().echoParameter(VALUE_A));

// GIVEN
SampleRepository.setScope(new ThreadScope());
Execution.setScope(ScopeType.THREAD);

ExecutorService executorService = Executors.newFixedThreadPool(2);

Expand All @@ -687,6 +683,7 @@ public void threadScopeWorks() throws ExecutionException, InterruptedException {
Sample.of(testServiceSampler.echoParameter(VALUE_B)).is(VALUE_A);

assertEquals(VALUE_A, getTestService().echoParameter(VALUE_B));
assertFalse(ExecutionRepository.getInstance().getOrCreate(TestService.class).getAll().isEmpty());
});

Future<?> findsNoSampler = executorService.submit(() -> {
Expand All @@ -698,14 +695,15 @@ public void threadScopeWorks() throws ExecutionException, InterruptedException {

// THEN
assertEquals(VALUE_B, getTestService().echoParameter(VALUE_B));
assertTrue(ExecutionRepository.getInstance().getOrCreate(TestService.class).getAll().isEmpty());
});

createsASampler.get();
findsNoSampler.get();

// THEN
assertEquals(VALUE_B, getTestService().echoParameter(VALUE_B));

assertTrue(ExecutionRepository.getInstance().getOrCreate(TestService.class).getAll().isEmpty());
}

@Test
Expand All @@ -717,7 +715,7 @@ public void singletonScopeWorks() throws ExecutionException, InterruptedExceptio
assertEquals(VALUE_A, getTestService().echoParameter(VALUE_A));

// GIVEN
SampleRepository.setScope(new SingletonScope());
Execution.setScope(ScopeType.SINGLETON);

ExecutorService executorService = Executors.newFixedThreadPool(2);

Expand All @@ -728,6 +726,7 @@ public void singletonScopeWorks() throws ExecutionException, InterruptedExceptio
Sample.of(testServiceSampler.echoParameter(VALUE_B)).is(VALUE_A);

assertEquals(VALUE_A, getTestService().echoParameter(VALUE_B));
assertFalse(ExecutionRepository.getInstance().getOrCreate(TestService.class).getAll().isEmpty());
});

Future<?> findsNoSampler = executorService.submit(() -> {
Expand All @@ -739,14 +738,15 @@ public void singletonScopeWorks() throws ExecutionException, InterruptedExceptio

// THEN
assertEquals(VALUE_A, getTestService().echoParameter(VALUE_B));
assertFalse(ExecutionRepository.getInstance().getOrCreate(TestService.class).getAll().isEmpty());
});

createsASampler.get();
findsNoSampler.get();

// THEN
assertEquals(VALUE_A, getTestService().echoParameter(VALUE_B));

assertFalse(ExecutionRepository.getInstance().getOrCreate(TestService.class).getAll().isEmpty());
}

@Test
Expand Down

0 comments on commit 922511f

Please sign in to comment.