Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,15 @@ SwitcherExecutor.forget(FEATURE01);
switcher.isItOn(); // Now, it's going to return the result retrieved from the API or the Snapshot file
```

For more complex scenarios where you need to test features based on specific inputs, you can use test conditions.

```java
Switcher switcher = MyAppFeatures.getSwitcher(FEATURE01).checkValue("My value").build();

SwitcherExecutor.assume(FEATURE01, true).when(StrategyValidator.VALUE, "My value");
switcher.isItOn(); // 'true'

```
## Smoke test
Validate Switcher Keys on your testing pipelines before deploying a change.
Switcher Keys may not be configured correctly and can cause your code to have undesired results.
Expand Down Expand Up @@ -314,3 +323,11 @@ void testMyFeature() {
assertTrue(instance.myFeature());
}
```

Using SwitcherTestWhen to define a specific condition for the test:
```java
@SwitcherTest(key = MY_SWITCHER, when = @SwitcherTestWhen(value = "My value"))
void testMyFeature() {
assertTrue(instance.myFeature());
}
```
16 changes: 8 additions & 8 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<groupId>com.github.switcherapi</groupId>
<artifactId>switcher-client</artifactId>
<packaging>jar</packaging>
<version>2.1.1-SNAPSHOT</version>
<version>2.2.0-SNAPSHOT</version>

<name>Switcher Client</name>
<description>Switcher Client SDK for working with Switcher API</description>
Expand Down Expand Up @@ -52,9 +52,9 @@
<argLine />

<!-- rest/json libs -->
<jersey-client.version>3.1.8</jersey-client.version>
<jersey-hk2.version>3.1.8</jersey-hk2.version>
<jersey-media-json-jackson.version>3.1.8</jersey-media-json-jackson.version>
<jersey-client.version>3.1.9</jersey-client.version>
<jersey-hk2.version>3.1.9</jersey-hk2.version>
<jersey-media-json-jackson.version>3.1.9</jersey-media-json-jackson.version>
<gson.version>2.11.0</gson.version>

<!-- utils -->
Expand All @@ -64,7 +64,7 @@

<!-- test -->
<okhttp.version>5.0.0-alpha.14</okhttp.version>
<junit.version>5.11.1</junit.version>
<junit.version>5.11.3</junit.version>
<junit-pioneer.version>2.2.0</junit-pioneer.version>

<!-- Plugins -->
Expand Down Expand Up @@ -173,19 +173,19 @@
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.17.0</version>
<version>2.17.3</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
<version>2.17.3</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>
<version>2.17.0</version>
<version>2.17.3</version>
</dependency>
</dependencies>
</dependencyManagement>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.github.switcherapi.client.service.local.SwitcherLocalService;
import com.github.switcherapi.client.service.remote.SwitcherRemoteService;
import com.github.switcherapi.client.utils.SnapshotEventHandler;
import com.github.switcherapi.client.utils.SnapshotWatcher;
import com.github.switcherapi.client.utils.SwitcherUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
Expand All @@ -19,6 +20,7 @@
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -67,6 +69,8 @@ public abstract class SwitcherContextBase {
protected static Map<String, Switcher> switchers;
protected static SwitcherExecutor instance;
private static ScheduledExecutorService scheduledExecutorService;
private static ExecutorService watcherExecutorService;
private static SnapshotWatcher watcher;

protected SwitcherContextBase() {
throw new IllegalStateException("Context class cannot be instantiated");
Expand All @@ -89,7 +93,7 @@ protected SwitcherContextBase() {
* @param contextFilename to load properties from
*/
public static void loadProperties(String contextFilename) {
try (InputStream input = SwitcherContext.class
try (InputStream input = SwitcherContextBase.class
.getClassLoader().getResourceAsStream(String.format("%s.properties", contextFilename))) {

Properties prop = new Properties();
Expand Down Expand Up @@ -190,7 +194,7 @@ public static boolean scheduleSnapshotAutoUpdate(String intervalValue, SnapshotC
}
};

initExecutorService();
initSnapshotExecutorService();
scheduledExecutorService.scheduleAtFixedRate(runnableSnapshotValidate, 0, interval, TimeUnit.MILLISECONDS);
return true;
}
Expand All @@ -209,14 +213,26 @@ public static boolean scheduleSnapshotAutoUpdate(String intervalValue) {
/**
* Configure Executor Service for Snapshot Update Worker
*/
private static void initExecutorService() {
private static void initSnapshotExecutorService() {
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(r -> {
Thread thread = new Thread(r);
thread.setName(WorkerName.SNAPSHOT_UPDATE_WORKER.toString());
thread.setDaemon(true);
return thread;
});
}

/**
* Configure Executor Service for Snapshot Watch Worker
*/
private static void initWatcherExecutorService() {
watcherExecutorService = Executors.newSingleThreadExecutor(r -> {
Thread thread = new Thread(r);
thread.setName(WorkerName.SNAPSHOT_WATCH_WORKER.toString());
thread.setDaemon(true);
return thread;
});
}

/**
* Return a ready-to-use Switcher that will invoke the criteria configured into the Switcher API or Snapshot
Expand Down Expand Up @@ -293,8 +309,12 @@ public static void watchSnapshot(SnapshotEventHandler handler) {
throw new SwitcherException("Cannot watch snapshot when using remote", new UnsupportedOperationException());
}

SwitcherLocalService executorInstance = (SwitcherLocalService) instance;
SwitcherUtils.watchSnapshot(executorInstance, handler);
if (watcher == null) {
watcher = new SnapshotWatcher((SwitcherLocalService) instance, handler);
}

initWatcherExecutorService();
watcherExecutorService.submit(watcher);
}

/**
Expand All @@ -303,7 +323,11 @@ public static void watchSnapshot(SnapshotEventHandler handler) {
* @throws SwitcherException if watch thread never started
*/
public static void stopWatchingSnapshot() {
SwitcherUtils.stopWatchingSnapshot();
if (watcher != null) {
watcherExecutorService.shutdownNow();
watcher.terminate();
watcher = null;
}
}

/**
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/com/github/switcherapi/client/SwitcherExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,20 @@ protected Domain initializeSnapshotFromAPI(ClientRemote clientRemote) {
throw e;
}
}

public boolean isLocalEnabled() {
return SwitcherContextBase.contextBol(ContextKey.LOCAL_MODE);
}

/**
* It manipulates the result of a given Switcher key.
*
* @param key name of the key that you want to change the result
* @param expectedResult that will be returned when performing isItOn
* @return CriteriaResponse with the manipulated result
*/
public static void assume(final String key, boolean expectedResult) {
assume(key, expectedResult, null);
public static CriteriaResponse assume(final String key, boolean expectedResult) {
return assume(key, expectedResult, null);
}

/**
Expand All @@ -108,8 +113,9 @@ public static void assume(final String key, boolean expectedResult) {
* @param key name of the key that you want to change the result
* @param metadata additional information about the assumption (JSON)
* @param expectedResult that will be returned when performing isItOn
* @return CriteriaResponse with the manipulated result
*/
public static void assume(final String key, boolean expectedResult, String metadata) {
public static CriteriaResponse assume(final String key, boolean expectedResult, String metadata) {
CriteriaResponse criteriaResponse = new CriteriaResponse();
criteriaResponse.setResult(expectedResult);
criteriaResponse.setReason("Switcher bypassed");
Expand All @@ -120,6 +126,7 @@ public static void assume(final String key, boolean expectedResult, String metad
}

bypass.put(key, criteriaResponse);
return criteriaResponse;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,20 @@ public class AsyncSwitcher implements Runnable {

private final ExecutorService executorService;

private Switcher switcher;
private final Switcher switcher;

private long nextRun = 0;

public AsyncSwitcher() {
public AsyncSwitcher(final Switcher switcher) {
this.executorService = Executors.newCachedThreadPool();
this.switcher = switcher;
}

/**
* Validate if next run is ready to be performed, otherwise it will skip and delegate the
* Switcher result for the Switcher history execution.
*
* @param switcher Instance of the current switcher being executed
*/
public synchronized void execute(final Switcher switcher) {
this.switcher = switcher;
public synchronized void execute() {
SwitcherUtils.debug(logger, "nextRun: {} - currentTimeMillis: {}", nextRun, System.currentTimeMillis());

if (nextRun < System.currentTimeMillis()) {
Expand All @@ -53,10 +51,10 @@ public synchronized void execute(final Switcher switcher) {
@Override
public void run() {
try {
final CriteriaResponse response = switcher.getContext().executeCriteria(this.switcher);
final CriteriaResponse response = switcher.getContext().executeCriteria(switcher);
switcher.getHistoryExecution().removeIf(item ->
this.switcher.getSwitcherKey().equals(item.getSwitcherKey()) &&
this.switcher.getEntry().equals(item.getEntry()));
switcher.getSwitcherKey().equals(item.getSwitcherKey()) &&
switcher.getEntry().equals(item.getEntry()));
switcher.getHistoryExecution().add(response);
} catch (SwitcherException e) {
logger.error(e);
Expand Down
12 changes: 3 additions & 9 deletions src/main/java/com/github/switcherapi/client/model/Switcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ public final class Switcher extends SwitcherBuilder {

public static final String BYPASS_METRIC = "bypassMetric";

private final SwitcherExecutor context;

private final String switcherKey;

private final Set<CriteriaResponse> historyExecution;
Expand All @@ -40,8 +38,8 @@ public final class Switcher extends SwitcherBuilder {
* @param context client context in which the switcher will be executed (local/remote)
*/
public Switcher(final String switcherKey, final SwitcherExecutor context) {
super(context);
this.switcherKey = switcherKey;
this.context = context;
this.historyExecution = new HashSet<>();
}

Expand Down Expand Up @@ -105,10 +103,10 @@ public CriteriaResponse submit() throws SwitcherException {

if (canUseAsync()) {
if (asyncSwitcher == null) {
asyncSwitcher = new AsyncSwitcher();
asyncSwitcher = new AsyncSwitcher(this);
}

asyncSwitcher.execute(this);
asyncSwitcher.execute();
final Optional<CriteriaResponse> response = getFromHistory();
if (response.isPresent()) {
return response.get();
Expand Down Expand Up @@ -150,10 +148,6 @@ public void resetEntry() {
public synchronized Set<CriteriaResponse> getHistoryExecution() {
return this.historyExecution;
}

public SwitcherExecutor getContext() {
return context;
}

@Override
public String toString() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.github.switcherapi.client.model;

import com.github.switcherapi.client.SwitcherContext;
import com.github.switcherapi.client.SwitcherContextBase;
import com.github.switcherapi.client.SwitcherExecutor;
import com.github.switcherapi.client.exception.SwitcherContextException;
import com.github.switcherapi.client.exception.SwitcherException;
import com.github.switcherapi.client.model.response.CriteriaResponse;
Expand All @@ -17,6 +17,8 @@
* @author Roger Floriano (petruki)
*/
public abstract class SwitcherBuilder {

protected final SwitcherExecutor context;

protected long delay;

Expand All @@ -28,7 +30,8 @@ public abstract class SwitcherBuilder {

protected List<Entry> entry;

protected SwitcherBuilder() {
protected SwitcherBuilder(final SwitcherExecutor context) {
this.context = context;
this.entry = new ArrayList<>();
this.delay = 0;
}
Expand All @@ -53,7 +56,7 @@ public SwitcherBuilder throttle(long delay) {
* @throws SwitcherContextException if Switcher is not configured to run locally using local mode
*/
public SwitcherBuilder remote(boolean remote) {
if (!SwitcherContextBase.contextBol(ContextKey.LOCAL_MODE)) {
if (!this.context.isLocalEnabled()) {
throw new SwitcherContextException("Switcher is not configured to run locally");
}

Expand Down Expand Up @@ -236,4 +239,8 @@ public boolean isRemote() {
public String getDefaultResult() {
return defaultResult;
}

public SwitcherExecutor getContext() {
return context;
}
}
Loading