Skip to content

Commit 900bdd5

Browse files
authored
Closes #309 - added Criteria evaluation for the test feature (#310)
1 parent f485ac5 commit 900bdd5

File tree

17 files changed

+252
-102
lines changed

17 files changed

+252
-102
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,15 @@ SwitcherExecutor.forget(FEATURE01);
270270
switcher.isItOn(); // Now, it's going to return the result retrieved from the API or the Snapshot file
271271
```
272272

273+
For more complex scenarios where you need to test features based on specific inputs, you can use test conditions.
274+
275+
```java
276+
Switcher switcher = MyAppFeatures.getSwitcher(FEATURE01).checkValue("My value").build();
277+
278+
SwitcherExecutor.assume(FEATURE01, true).when(StrategyValidator.VALUE, "My value");
279+
switcher.isItOn(); // 'true'
280+
281+
```
273282
## Smoke test
274283
Validate Switcher Keys on your testing pipelines before deploying a change.
275284
Switcher Keys may not be configured correctly and can cause your code to have undesired results.
@@ -314,3 +323,11 @@ void testMyFeature() {
314323
assertTrue(instance.myFeature());
315324
}
316325
```
326+
327+
Using SwitcherTestWhen to define a specific condition for the test:
328+
```java
329+
@SwitcherTest(key = MY_SWITCHER, when = @SwitcherTestWhen(value = "My value"))
330+
void testMyFeature() {
331+
assertTrue(instance.myFeature());
332+
}
333+
```

pom.xml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<groupId>com.github.switcherapi</groupId>
99
<artifactId>switcher-client</artifactId>
1010
<packaging>jar</packaging>
11-
<version>2.1.1-SNAPSHOT</version>
11+
<version>2.2.0-SNAPSHOT</version>
1212

1313
<name>Switcher Client</name>
1414
<description>Switcher Client SDK for working with Switcher API</description>
@@ -52,9 +52,9 @@
5252
<argLine />
5353

5454
<!-- rest/json libs -->
55-
<jersey-client.version>3.1.8</jersey-client.version>
56-
<jersey-hk2.version>3.1.8</jersey-hk2.version>
57-
<jersey-media-json-jackson.version>3.1.8</jersey-media-json-jackson.version>
55+
<jersey-client.version>3.1.9</jersey-client.version>
56+
<jersey-hk2.version>3.1.9</jersey-hk2.version>
57+
<jersey-media-json-jackson.version>3.1.9</jersey-media-json-jackson.version>
5858
<gson.version>2.11.0</gson.version>
5959

6060
<!-- utils -->
@@ -64,7 +64,7 @@
6464

6565
<!-- test -->
6666
<okhttp.version>5.0.0-alpha.14</okhttp.version>
67-
<junit.version>5.11.1</junit.version>
67+
<junit.version>5.11.3</junit.version>
6868
<junit-pioneer.version>2.2.0</junit-pioneer.version>
6969

7070
<!-- Plugins -->
@@ -173,19 +173,19 @@
173173
<dependency>
174174
<groupId>com.fasterxml.jackson.core</groupId>
175175
<artifactId>jackson-annotations</artifactId>
176-
<version>2.17.0</version>
176+
<version>2.17.3</version>
177177
</dependency>
178178

179179
<dependency>
180180
<groupId>com.fasterxml.jackson.core</groupId>
181181
<artifactId>jackson-databind</artifactId>
182-
<version>2.17.0</version>
182+
<version>2.17.3</version>
183183
</dependency>
184184

185185
<dependency>
186186
<groupId>com.fasterxml.jackson.module</groupId>
187187
<artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>
188-
<version>2.17.0</version>
188+
<version>2.17.3</version>
189189
</dependency>
190190
</dependencies>
191191
</dependencyManagement>

src/main/java/com/github/switcherapi/client/SwitcherContextBase.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.github.switcherapi.client.service.local.SwitcherLocalService;
1111
import com.github.switcherapi.client.service.remote.SwitcherRemoteService;
1212
import com.github.switcherapi.client.utils.SnapshotEventHandler;
13+
import com.github.switcherapi.client.utils.SnapshotWatcher;
1314
import com.github.switcherapi.client.utils.SwitcherUtils;
1415
import org.apache.commons.lang3.StringUtils;
1516
import org.apache.logging.log4j.LogManager;
@@ -19,6 +20,7 @@
1920
import java.io.InputStream;
2021
import java.lang.reflect.Field;
2122
import java.util.*;
23+
import java.util.concurrent.ExecutorService;
2224
import java.util.concurrent.Executors;
2325
import java.util.concurrent.ScheduledExecutorService;
2426
import java.util.concurrent.TimeUnit;
@@ -67,6 +69,8 @@ public abstract class SwitcherContextBase {
6769
protected static Map<String, Switcher> switchers;
6870
protected static SwitcherExecutor instance;
6971
private static ScheduledExecutorService scheduledExecutorService;
72+
private static ExecutorService watcherExecutorService;
73+
private static SnapshotWatcher watcher;
7074

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

9599
Properties prop = new Properties();
@@ -190,7 +194,7 @@ public static boolean scheduleSnapshotAutoUpdate(String intervalValue, SnapshotC
190194
}
191195
};
192196

193-
initExecutorService();
197+
initSnapshotExecutorService();
194198
scheduledExecutorService.scheduleAtFixedRate(runnableSnapshotValidate, 0, interval, TimeUnit.MILLISECONDS);
195199
return true;
196200
}
@@ -209,14 +213,26 @@ public static boolean scheduleSnapshotAutoUpdate(String intervalValue) {
209213
/**
210214
* Configure Executor Service for Snapshot Update Worker
211215
*/
212-
private static void initExecutorService() {
216+
private static void initSnapshotExecutorService() {
213217
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(r -> {
214218
Thread thread = new Thread(r);
215219
thread.setName(WorkerName.SNAPSHOT_UPDATE_WORKER.toString());
216220
thread.setDaemon(true);
217221
return thread;
218222
});
219223
}
224+
225+
/**
226+
* Configure Executor Service for Snapshot Watch Worker
227+
*/
228+
private static void initWatcherExecutorService() {
229+
watcherExecutorService = Executors.newSingleThreadExecutor(r -> {
230+
Thread thread = new Thread(r);
231+
thread.setName(WorkerName.SNAPSHOT_WATCH_WORKER.toString());
232+
thread.setDaemon(true);
233+
return thread;
234+
});
235+
}
220236

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

296-
SwitcherLocalService executorInstance = (SwitcherLocalService) instance;
297-
SwitcherUtils.watchSnapshot(executorInstance, handler);
312+
if (watcher == null) {
313+
watcher = new SnapshotWatcher((SwitcherLocalService) instance, handler);
314+
}
315+
316+
initWatcherExecutorService();
317+
watcherExecutorService.submit(watcher);
298318
}
299319

300320
/**
@@ -303,7 +323,11 @@ public static void watchSnapshot(SnapshotEventHandler handler) {
303323
* @throws SwitcherException if watch thread never started
304324
*/
305325
public static void stopWatchingSnapshot() {
306-
SwitcherUtils.stopWatchingSnapshot();
326+
if (watcher != null) {
327+
watcherExecutorService.shutdownNow();
328+
watcher.terminate();
329+
watcher = null;
330+
}
307331
}
308332

309333
/**

src/main/java/com/github/switcherapi/client/SwitcherExecutor.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,20 @@ protected Domain initializeSnapshotFromAPI(ClientRemote clientRemote) {
9191
throw e;
9292
}
9393
}
94+
95+
public boolean isLocalEnabled() {
96+
return SwitcherContextBase.contextBol(ContextKey.LOCAL_MODE);
97+
}
9498

9599
/**
96100
* It manipulates the result of a given Switcher key.
97101
*
98102
* @param key name of the key that you want to change the result
99103
* @param expectedResult that will be returned when performing isItOn
104+
* @return CriteriaResponse with the manipulated result
100105
*/
101-
public static void assume(final String key, boolean expectedResult) {
102-
assume(key, expectedResult, null);
106+
public static CriteriaResponse assume(final String key, boolean expectedResult) {
107+
return assume(key, expectedResult, null);
103108
}
104109

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

122128
bypass.put(key, criteriaResponse);
129+
return criteriaResponse;
123130
}
124131

125132
/**

src/main/java/com/github/switcherapi/client/model/AsyncSwitcher.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,22 +24,20 @@ public class AsyncSwitcher implements Runnable {
2424

2525
private final ExecutorService executorService;
2626

27-
private Switcher switcher;
27+
private final Switcher switcher;
2828

2929
private long nextRun = 0;
3030

31-
public AsyncSwitcher() {
31+
public AsyncSwitcher(final Switcher switcher) {
3232
this.executorService = Executors.newCachedThreadPool();
33+
this.switcher = switcher;
3334
}
3435

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

4543
if (nextRun < System.currentTimeMillis()) {
@@ -53,10 +51,10 @@ public synchronized void execute(final Switcher switcher) {
5351
@Override
5452
public void run() {
5553
try {
56-
final CriteriaResponse response = switcher.getContext().executeCriteria(this.switcher);
54+
final CriteriaResponse response = switcher.getContext().executeCriteria(switcher);
5755
switcher.getHistoryExecution().removeIf(item ->
58-
this.switcher.getSwitcherKey().equals(item.getSwitcherKey()) &&
59-
this.switcher.getEntry().equals(item.getEntry()));
56+
switcher.getSwitcherKey().equals(item.getSwitcherKey()) &&
57+
switcher.getEntry().equals(item.getEntry()));
6058
switcher.getHistoryExecution().add(response);
6159
} catch (SwitcherException e) {
6260
logger.error(e);

src/main/java/com/github/switcherapi/client/model/Switcher.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ public final class Switcher extends SwitcherBuilder {
2525

2626
public static final String BYPASS_METRIC = "bypassMetric";
2727

28-
private final SwitcherExecutor context;
29-
3028
private final String switcherKey;
3129

3230
private final Set<CriteriaResponse> historyExecution;
@@ -40,8 +38,8 @@ public final class Switcher extends SwitcherBuilder {
4038
* @param context client context in which the switcher will be executed (local/remote)
4139
*/
4240
public Switcher(final String switcherKey, final SwitcherExecutor context) {
41+
super(context);
4342
this.switcherKey = switcherKey;
44-
this.context = context;
4543
this.historyExecution = new HashSet<>();
4644
}
4745

@@ -105,10 +103,10 @@ public CriteriaResponse submit() throws SwitcherException {
105103

106104
if (canUseAsync()) {
107105
if (asyncSwitcher == null) {
108-
asyncSwitcher = new AsyncSwitcher();
106+
asyncSwitcher = new AsyncSwitcher(this);
109107
}
110108

111-
asyncSwitcher.execute(this);
109+
asyncSwitcher.execute();
112110
final Optional<CriteriaResponse> response = getFromHistory();
113111
if (response.isPresent()) {
114112
return response.get();
@@ -150,10 +148,6 @@ public void resetEntry() {
150148
public synchronized Set<CriteriaResponse> getHistoryExecution() {
151149
return this.historyExecution;
152150
}
153-
154-
public SwitcherExecutor getContext() {
155-
return context;
156-
}
157151

158152
@Override
159153
public String toString() {

src/main/java/com/github/switcherapi/client/model/SwitcherBuilder.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.github.switcherapi.client.model;
22

33
import com.github.switcherapi.client.SwitcherContext;
4-
import com.github.switcherapi.client.SwitcherContextBase;
4+
import com.github.switcherapi.client.SwitcherExecutor;
55
import com.github.switcherapi.client.exception.SwitcherContextException;
66
import com.github.switcherapi.client.exception.SwitcherException;
77
import com.github.switcherapi.client.model.response.CriteriaResponse;
@@ -17,6 +17,8 @@
1717
* @author Roger Floriano (petruki)
1818
*/
1919
public abstract class SwitcherBuilder {
20+
21+
protected final SwitcherExecutor context;
2022

2123
protected long delay;
2224

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

2931
protected List<Entry> entry;
3032

31-
protected SwitcherBuilder() {
33+
protected SwitcherBuilder(final SwitcherExecutor context) {
34+
this.context = context;
3235
this.entry = new ArrayList<>();
3336
this.delay = 0;
3437
}
@@ -53,7 +56,7 @@ public SwitcherBuilder throttle(long delay) {
5356
* @throws SwitcherContextException if Switcher is not configured to run locally using local mode
5457
*/
5558
public SwitcherBuilder remote(boolean remote) {
56-
if (!SwitcherContextBase.contextBol(ContextKey.LOCAL_MODE)) {
59+
if (!this.context.isLocalEnabled()) {
5760
throw new SwitcherContextException("Switcher is not configured to run locally");
5861
}
5962

@@ -236,4 +239,8 @@ public boolean isRemote() {
236239
public String getDefaultResult() {
237240
return defaultResult;
238241
}
242+
243+
public SwitcherExecutor getContext() {
244+
return context;
245+
}
239246
}

0 commit comments

Comments
 (0)