Skip to content

Commit 08dfae7

Browse files
committed
Changes from PR review
1 parent d14b33f commit 08dfae7

7 files changed

Lines changed: 262 additions & 81 deletions

File tree

client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/RedisCommandEvaluation.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import org.evomaster.client.java.instrumentation.RedisCommand;
44

5+
/**
6+
* This class will link a given RedisCommand to the result of the distance calculation for that commmand.
7+
*/
58
public class RedisCommandEvaluation {
69
public final RedisCommand redisCommand;
710
public final RedisDistanceWithMetrics redisDistanceWithMetrics;

client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/RedisDistanceWithMetrics.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package org.evomaster.client.java.controller.internal.db;
22

3+
/**
4+
* This class will have the distance for a RedisCommand (between 0 and 1)
5+
* and the number of evaluated keys in that distance calculation.
6+
*/
37
public class RedisDistanceWithMetrics {
48
public final double redisDistance; // A number between 0 and 1.
59
public final int numberOfEvaluatedKeys;

client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/RedisHandler.java

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
import java.util.*;
1111

12+
import static org.evomaster.client.java.controller.redis.RedisHeuristicsCalculator.MAX_REDIS_DISTANCE;
13+
1214
/**
1315
* Class used to act upon Redis commands executed by the SUT
1416
*/
@@ -41,6 +43,9 @@ public class RedisHandler {
4143

4244
private final RedisHeuristicsCalculator calculator = new RedisHeuristicsCalculator(new TaintHandlerExecutionTracer());
4345

46+
private static final String REDIS_HASH_TYPE = "hash";
47+
private static final String REDIS_SET_TYPE = "set";
48+
private static final String REDIS_STRING_TYPE = "string";
4449

4550
public RedisHandler() {
4651
operations = new ArrayList<>();
@@ -98,7 +103,7 @@ private RedisDistanceWithMetrics computeDistance(RedisCommand redisCommand, Redi
98103
}
99104

100105
case GET: {
101-
List<RedisInfo> redisInfo = createRedisInfoForKeysByType("string", redisClient);
106+
List<RedisInfo> redisInfo = createRedisInfoForKeysByType(REDIS_STRING_TYPE, redisClient);
102107
return calculator.computeDistance(redisCommand, redisInfo);
103108
}
104109

@@ -109,12 +114,12 @@ private RedisDistanceWithMetrics computeDistance(RedisCommand redisCommand, Redi
109114
}
110115

111116
case HGETALL: {
112-
List<RedisInfo> redisInfo = createRedisInfoForKeysByType("hash", redisClient);
117+
List<RedisInfo> redisInfo = createRedisInfoForKeysByType(REDIS_HASH_TYPE, redisClient);
113118
return calculator.computeDistance(redisCommand, redisInfo);
114119
}
115120

116121
case SMEMBERS: {
117-
List<RedisInfo> redisInfo = createRedisInfoForKeysByType("set", redisClient);
122+
List<RedisInfo> redisInfo = createRedisInfoForKeysByType(REDIS_SET_TYPE, redisClient);
118123
return calculator.computeDistance(redisCommand, redisInfo);
119124
}
120125

@@ -125,11 +130,11 @@ private RedisDistanceWithMetrics computeDistance(RedisCommand redisCommand, Redi
125130
}
126131

127132
default:
128-
return new RedisDistanceWithMetrics(1d, 0);
133+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
129134
}
130135
} catch (Exception e) {
131136
SimpleLogger.warn("Could not compute distance for " + type + ": " + e.getMessage());
132-
return new RedisDistanceWithMetrics(1d, 0);
137+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
133138
}
134139
}
135140

@@ -158,7 +163,7 @@ private List<RedisInfo> createRedisInfoForKeysByType(String type, RedisClient re
158163
}
159164

160165
private List<RedisInfo> createRedisInfoForKeysByField(String field, RedisClient redisClient) {
161-
Set<String> keys = redisClient.getKeysByType("hash");
166+
Set<String> keys = redisClient.getKeysByType(REDIS_HASH_TYPE);
162167
List<RedisInfo> redisData = new ArrayList<>();
163168
keys.forEach(key -> redisData.add(new RedisInfo(key, redisClient.hashFieldExists(key, field))));
164169
return redisData;

client-java/controller/src/main/java/org/evomaster/client/java/controller/redis/RedisHeuristicsCalculator.java

Lines changed: 23 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,11 @@
1010

1111
import java.util.*;
1212

13+
import static org.evomaster.client.java.controller.redis.RedisUtils.redisPatternToRegex;
14+
1315
public class RedisHeuristicsCalculator {
1416

15-
public static final double MAX_DISTANCE = 1d;
17+
public static final double MAX_REDIS_DISTANCE = 1d;
1618

1719
private final TaintHandler taintHandler;
1820

@@ -59,11 +61,11 @@ public RedisDistanceWithMetrics computeDistance(RedisCommand redisCommand, List<
5961
}
6062

6163
default:
62-
return new RedisDistanceWithMetrics(MAX_DISTANCE, 0);
64+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
6365
}
6466
} catch (Exception e) {
6567
SimpleLogger.warn("Could not compute distance for " + type + ": " + e.getMessage());
66-
return new RedisDistanceWithMetrics(MAX_DISTANCE, 0);
68+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
6769
}
6870
}
6971

@@ -77,8 +79,15 @@ public RedisDistanceWithMetrics computeDistance(RedisCommand redisCommand, List<
7779
private RedisDistanceWithMetrics calculateDistanceForPattern(
7880
String pattern,
7981
List<RedisInfo> keys) {
80-
double minDist = MAX_DISTANCE;
82+
double minDist = MAX_REDIS_DISTANCE;
8183
int eval = 0;
84+
String regex;
85+
try {
86+
regex = redisPatternToRegex(pattern);
87+
} catch (IllegalArgumentException e) {
88+
SimpleLogger.uniqueWarn("Invalid Redis pattern. Cannot compute regex for: " + pattern);
89+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
90+
}
8291
for (RedisInfo k : keys) {
8392
double d = TruthnessUtils.normalizeValue(
8493
RegexDistanceUtils.getStandardDistance(k.getKey(), redisPatternToRegex(pattern)));
@@ -102,10 +111,10 @@ private RedisDistanceWithMetrics calculateDistanceForKeyMatch(
102111
List<RedisInfo> candidateKeys
103112
) {
104113
if (candidateKeys.isEmpty()) {
105-
return new RedisDistanceWithMetrics(MAX_DISTANCE, 0);
114+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
106115
}
107116

108-
double minDist = MAX_DISTANCE;
117+
double minDist = MAX_REDIS_DISTANCE;
109118
int evaluated = 0;
110119

111120
for (RedisInfo k : candidateKeys) {
@@ -138,17 +147,17 @@ private RedisDistanceWithMetrics calculateDistanceForFieldInHash(
138147
List<RedisInfo> keys
139148
) {
140149
if (keys.isEmpty()) {
141-
return new RedisDistanceWithMetrics(MAX_DISTANCE, 0);
150+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
142151
}
143152

144-
double minDist = MAX_DISTANCE;
153+
double minDist = MAX_REDIS_DISTANCE;
145154
int evaluated = 0;
146155

147156
for (RedisInfo k : keys) {
148157
try {
149158
long keyDist = DistanceHelper.getLeftAlignmentDistance(targetKey, k.getKey());
150159

151-
double fieldDist = k.hasField() ? 0d : MAX_DISTANCE;
160+
double fieldDist = k.hasField() ? 0d : MAX_REDIS_DISTANCE;
152161

153162
double combined = TruthnessUtils.normalizeValue(keyDist + fieldDist);
154163

@@ -176,7 +185,7 @@ private RedisDistanceWithMetrics calculateDistanceForIntersection(
176185
List<RedisInfo> keys
177186
) {
178187
if (keys == null || keys.isEmpty()) {
179-
return new RedisDistanceWithMetrics(MAX_DISTANCE, 0);
188+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, 0);
180189
}
181190

182191
double total = 0d;
@@ -188,15 +197,15 @@ private RedisDistanceWithMetrics calculateDistanceForIntersection(
188197
RedisInfo k = keys.get(i);
189198
String type = k.getType();
190199
if (!"set".equalsIgnoreCase(type)) {
191-
return new RedisDistanceWithMetrics(MAX_DISTANCE, evaluated);
200+
return new RedisDistanceWithMetrics(MAX_REDIS_DISTANCE, evaluated);
192201
}
193202

194203
Set<String> set = k.getMembers();
195204
if (set == null) set = Collections.emptySet();
196205

197206
if (i == 0) {
198207
currentIntersection = new HashSet<>(set);
199-
double d0 = currentIntersection.isEmpty() ? MAX_DISTANCE : 0d;
208+
double d0 = currentIntersection.isEmpty() ? MAX_REDIS_DISTANCE : 0d;
200209
total += d0;
201210
evaluated++;
202211
} else {
@@ -221,10 +230,10 @@ private RedisDistanceWithMetrics calculateDistanceForIntersection(
221230
*/
222231
private double computeSetIntersectionDistance(Set<String> s1, Set<String> s2) {
223232
if (s1.isEmpty() || s2.isEmpty()) {
224-
return MAX_DISTANCE;
233+
return MAX_REDIS_DISTANCE;
225234
}
226235

227-
double min = MAX_DISTANCE;
236+
double min = MAX_REDIS_DISTANCE;
228237
for (String a : s1) {
229238
for (String b : s2) {
230239
long raw = DistanceHelper.getLeftAlignmentDistance(a, b);
@@ -236,65 +245,4 @@ private double computeSetIntersectionDistance(Set<String> s1, Set<String> s2) {
236245
return min;
237246
}
238247

239-
/**
240-
* Translates a Redis glob-style pattern into a valid Java regex pattern.
241-
* Supported conversions:
242-
* - * → .*
243-
* - ? → .
244-
* - [ae] → [ae]
245-
* - [^e] → [^e]
246-
* - [a-b] → [a-b]
247-
* Other regex metacharacters are properly escaped.
248-
*
249-
* @param redisPattern the Redis glob-style pattern (e.g., "h?llo*", "user:[0-9]*")
250-
* @return a valid Java regex string equivalent to the Redis pattern.
251-
*/
252-
private static String redisPatternToRegex(String redisPattern) {
253-
if (redisPattern == null || redisPattern.isEmpty()) {
254-
return ".*";
255-
}
256-
257-
StringBuilder regex = new StringBuilder();
258-
boolean inBrackets = false;
259-
260-
for (int i = 0; i < redisPattern.length(); i++) {
261-
char c = redisPattern.charAt(i);
262-
263-
switch (c) {
264-
case '*':
265-
regex.append(".*");
266-
break;
267-
case '?':
268-
regex.append('.');
269-
break;
270-
case '[':
271-
inBrackets = true;
272-
regex.append('[');
273-
if (i + 1 < redisPattern.length() && redisPattern.charAt(i + 1) == '^') {
274-
regex.append('^');
275-
i++;
276-
}
277-
break;
278-
case ']':
279-
if (inBrackets) {
280-
regex.append(']');
281-
inBrackets = false;
282-
} else {
283-
regex.append("\\]");
284-
}
285-
break;
286-
case '\\':
287-
regex.append("\\\\");
288-
break;
289-
default:
290-
if (!inBrackets && ".+(){}|^$".indexOf(c) >= 0) {
291-
regex.append('\\');
292-
}
293-
regex.append(c);
294-
break;
295-
}
296-
}
297-
298-
return "^" + regex + "$";
299-
}
300248
}

client-java/controller/src/main/java/org/evomaster/client/java/controller/redis/RedisInfo.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
import java.util.Set;
44

5+
/**
6+
* This class will contain all necessary information from Redis to perform the distance calculation for a given command.
7+
* Hence, RedisHeuristicCalculator will be decoupled from Redis.
8+
* There'll be no need to call Redis to calculate distances.
9+
*/
510
public class RedisInfo {
611
private String key;
712
private String type;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package org.evomaster.client.java.controller.redis;
2+
3+
/**
4+
* Utils class for auxiliary operations in Redis heuristic value calculations.
5+
*/
6+
public class RedisUtils {
7+
8+
/**
9+
* Translates a Redis glob-style pattern into a valid Java regex pattern.
10+
* Supported glob-style patterns:
11+
*
12+
* h?llo matches hello, hallo and hxllo
13+
* h*llo matches hllo and heeeello
14+
* h[ae]llo matches hello and hallo, but not hillo
15+
* h[^e]llo matches hallo, hbllo, ... but not hello
16+
* h[a-b]llo matches hallo and hbllo
17+
* Use \ to escape special characters if you want to match them verbatim.
18+
*
19+
* Supported conversions:
20+
* - * → .*
21+
* - ? → .
22+
* - [ae] → [ae]
23+
* - [^e] → [^e]
24+
* - [a-b] → [a-b]
25+
* Other regex metacharacters are properly escaped.
26+
*
27+
* @param redisPattern the Redis glob-style pattern (e.g., "h?llo*", "user:[0-9]*")
28+
* @return a valid Java regex string equivalent to the Redis pattern.
29+
*/
30+
public static String redisPatternToRegex(String redisPattern) {
31+
if (redisPattern == null || redisPattern.isEmpty()) {
32+
return ".*";
33+
}
34+
35+
StringBuilder regex = new StringBuilder();
36+
boolean inBrackets = false;
37+
boolean escaping = false;
38+
39+
for (int i = 0; i < redisPattern.length(); i++) {
40+
char c = redisPattern.charAt(i);
41+
42+
if (escaping) {
43+
if (".+(){}|^$[]\\".indexOf(c) >= 0) {
44+
regex.append('\\');
45+
}
46+
regex.append(c);
47+
escaping = false;
48+
continue;
49+
}
50+
51+
switch (c) {
52+
case '\\':
53+
escaping = true;
54+
break;
55+
56+
case '*':
57+
regex.append(".*");
58+
break;
59+
60+
case '?':
61+
regex.append('.');
62+
break;
63+
64+
case '[':
65+
if (inBrackets) {
66+
throw new IllegalArgumentException("Malformed Redis pattern: nested [");
67+
}
68+
inBrackets = true;
69+
regex.append('[');
70+
if (i + 1 < redisPattern.length() && redisPattern.charAt(i + 1) == '^') {
71+
regex.append('^');
72+
i++;
73+
}
74+
break;
75+
76+
case ']':
77+
if (inBrackets) {
78+
regex.append(']');
79+
inBrackets = false;
80+
} else {
81+
regex.append("\\]");
82+
}
83+
break;
84+
85+
default:
86+
if (!inBrackets && ".+(){}|^$".indexOf(c) >= 0) {
87+
regex.append('\\');
88+
}
89+
regex.append(c);
90+
break;
91+
}
92+
}
93+
94+
if (escaping) {
95+
throw new IllegalArgumentException("Malformed Redis pattern: trailing backslash");
96+
}
97+
98+
if (inBrackets) {
99+
throw new IllegalArgumentException("Malformed Redis pattern: unclosed [");
100+
}
101+
102+
return "^" + regex + "$";
103+
}
104+
}

0 commit comments

Comments
 (0)