Skip to content

Commit fa01f1f

Browse files
authored
Closes #309 - added Criteria evaluation for the test feature (#310) (#311)
* Closes #309 - added Criteria evaluation for the test feature (#310) * Replaced List.of with Collections.singleton for JRE8 * Bump version to 1.6.0
1 parent 97ea909 commit fa01f1f

File tree

17 files changed

+256
-109
lines changed

17 files changed

+256
-109
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: 13 additions & 13 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>1.5.1-SNAPSHOT</version>
11+
<version>1.6.0-SNAPSHOT</version>
1212

1313
<name>Switcher Client</name>
1414
<description>Switcher Client SDK for working with Switcher API</description>
@@ -59,11 +59,11 @@
5959
<!-- utils -->
6060
<commons-lang3.version>3.17.0</commons-lang3.version>
6161
<commons-net.version>3.11.1</commons-net.version>
62-
<log4j.version>2.23.1</log4j.version>
62+
<log4j.version>2.24.1</log4j.version>
6363

6464
<!-- test -->
6565
<okhttp.version>5.0.0-alpha.14</okhttp.version>
66-
<junit.version>5.11.0</junit.version>
66+
<junit.version>5.11.3</junit.version>
6767
<junit-pioneer.version>1.9.1</junit-pioneer.version>
6868

6969
<!-- Plugins -->
@@ -81,10 +81,10 @@
8181
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
8282
<sonar.language>java</sonar.language>
8383
<sonar.coverage.exclusions>
84-
**/model/**/*.java,
85-
**/exception/**/*.java,
86-
**/service/validators/RegexValidatorV8.java
87-
</sonar.coverage.exclusions>
84+
**/model/**/*.java,
85+
**/exception/**/*.java,
86+
**/service/validators/RegexValidatorV8.java
87+
</sonar.coverage.exclusions>
8888
</properties>
8989

9090
<dependencies>
@@ -104,7 +104,7 @@
104104
<artifactId>jersey-media-json-jackson</artifactId>
105105
<version>${jersey-media-json-jackson.version}</version>
106106
</dependency>
107-
107+
108108
<dependency>
109109
<groupId>com.google.code.gson</groupId>
110110
<artifactId>gson</artifactId>
@@ -166,25 +166,25 @@
166166
<dependency>
167167
<groupId>junit</groupId>
168168
<artifactId>junit</artifactId>
169-
<version>4.13.1</version>
169+
<version>4.13.2</version>
170170
</dependency>
171171

172172
<dependency>
173173
<groupId>com.fasterxml.jackson.core</groupId>
174174
<artifactId>jackson-annotations</artifactId>
175-
<version>2.17.0</version>
175+
<version>2.17.3</version>
176176
</dependency>
177177

178178
<dependency>
179179
<groupId>com.fasterxml.jackson.core</groupId>
180180
<artifactId>jackson-databind</artifactId>
181-
<version>2.17.0</version>
181+
<version>2.17.3</version>
182182
</dependency>
183183

184184
<dependency>
185185
<groupId>com.fasterxml.jackson.module</groupId>
186186
<artifactId>jackson-module-jakarta-xmlbind-annotations</artifactId>
187-
<version>2.17.0</version>
187+
<version>2.17.3</version>
188188
</dependency>
189189
</dependencies>
190190
</dependencyManagement>
@@ -312,7 +312,7 @@
312312
<plugins>
313313
<plugin>
314314
<groupId>org.apache.maven.plugins</groupId>
315-
<artifactId>maven-compiler-plugin-plugin</artifactId>
315+
<artifactId>maven-compiler-plugin</artifactId>
316316
<version>${maven-compiler-plugin.version}</version>
317317
</plugin>
318318
<plugin>

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)