Skip to content

Commit c65d914

Browse files
authored
Merge branch 'master' into endpoint-exclude
2 parents ddcdb9d + f1c71e6 commit c65d914

File tree

284 files changed

+11730
-1610
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

284 files changed

+11730
-1610
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ jobs:
117117
# Make test report accessible from GitHub Actions (as Maven logs are long)
118118
- name: Publish Test Report
119119
if: success() || failure()
120-
uses: mikepenz/action-junit-report@v4
120+
uses: mikepenz/action-junit-report@v5
121121
env:
122-
NODE_OPTIONS: "--max_old_space_size=6144"
122+
NODE_OPTIONS: "--max_old_space_size=8000"
123123
with:
124124
report_paths: '**/target/surefire-reports/TEST-*.xml'
125125
# Upload coverage results

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,48 @@ building on decades of research in the field of [Search-Based Software Testing](
3434
### 1-Minute Example
3535

3636
On a console, copy&paste the following (requires _Docker_ installed).
37-
It will fuzz the PetClinic example API from Swagger, for 30 seconds.
37+
It will fuzz the PetClinic example API from Swagger, for 30 seconds, as shown in the following video.
3838

3939
```
4040
docker run -v "$(pwd)/generated_tests":/generated_tests webfuzzing/evomaster --blackBox true --maxTime 30s --ratePerMinute 60 --bbSwaggerUrl https://petstore.swagger.io/v2/swagger.json
4141
```
42-
Note, if run in a MSYS shell on Windows like _Git Bash_, there is the need of an extra / before the $ (as in the following video).
4342

4443

4544
![](docs/img/evomaster_docker_use.gif)
4645

46+
#### Using Docker on Different Shells
47+
48+
Note that, depending on which shell and operating system you are using, you might need slightly different commands when mounting folders with the `-v` option.
49+
50+
For example, if run in a MSYS shell on Windows like _Git Bash_, there is the need of an extra / before the $.
51+
52+
```
53+
docker run -v "/$(pwd)/generated_tests":/generated_tests webfuzzing/evomaster --blackBox true --maxTime 30s --ratePerMinute 60 --bbSwaggerUrl https://petstore.swagger.io/v2/swagger.json
54+
```
55+
56+
If you are rather using a Command Prompt (Cmd.exe) terminal, you need to use `%CD%` instead of `$(pwd)` to refer to the current folder:
57+
58+
```
59+
docker run -v %CD%/generated_tests:/generated_tests webfuzzing/evomaster --blackBox true --maxTime 30s --ratePerMinute 60 --bbSwaggerUrl https://petstore.swagger.io/v2/swagger.json
60+
```
61+
62+
On the other hand, on a PowerShell you need `${PWD}`:
63+
64+
```
65+
docker run -v ${PWD}/generated_tests:/generated_tests webfuzzing/evomaster --blackBox true --maxTime 30s --ratePerMinute 60 --bbSwaggerUrl https://petstore.swagger.io/v2/swagger.json
66+
```
67+
68+
#### Troubleshooting
69+
70+
If you encounter issues running the command:
71+
72+
* Ensure Docker is installed and running.
73+
* Check that you have the correct rights/permissions to mount the specified volume.
74+
* Consult the Docker documentation for your shell environment for specific syntax requirements.
75+
76+
77+
#### Generated Output
78+
4779
Once the command is executed, you can inspect the generated files under `generated_tests` folder.
4880

4981
Note, since version 4.0.0, now _EvoMaster_ by default also creates an interactive web report.

client-java/controller-api/src/main/java/org/evomaster/client/java/controller/api/dto/ExtraHeuristicEntryDto.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ public class ExtraHeuristicEntryDto implements Serializable {
99

1010
/**
1111
* The type of extra heuristic.
12-
* Note: for the moment, we only have heuristics on SQL commands
12+
* Note: for the moment, we only have heuristics on SQL, MONGO and OPENSEARCH commands
1313
*/
14-
public enum Type {SQL, MONGO}
14+
public enum Type {SQL, MONGO, OPENSEARCH}
1515

1616
/**
1717
* Should we try to minimize or maximize the heuristic?

client-java/controller/src/main/java/org/evomaster/client/java/controller/SutHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ default void extractRPCSchema(){}
179179

180180
default Object getMongoConnection() {return null;}
181181

182+
default Object getOpenSearchConnection() {return null;}
182183

183184
/**
184185
* <p>

client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/EMController.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,7 @@ public Response runSut(SutRunDto dto, @Context HttpServletRequest httpServletReq
387387
noKillSwitch(() -> sutController.initSqlHandler());
388388
noKillSwitch(() -> sutController.registerOrExecuteInitSqlCommandsIfNeeded(true));
389389
noKillSwitch(() -> sutController.initMongoHandler());
390+
noKillSwitch(() -> sutController.initOpenSearchHandler());
390391
} else {
391392
//TODO as starting should be blocking, need to check
392393
//if initialized, and wait if not

client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/SutController.java

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.evomaster.client.java.controller.api.dto.problem.RPCProblemDto;
2828
import org.evomaster.client.java.controller.api.dto.problem.rpc.*;
2929
import org.evomaster.client.java.controller.api.dto.problem.rpc.RPCTestDto;
30+
import org.evomaster.client.java.controller.internal.db.OpenSearchHandler;
3031
import org.evomaster.client.java.sql.DbCleaner;
3132
import org.evomaster.client.java.sql.SqlScriptRunner;
3233
import org.evomaster.client.java.sql.SqlScriptRunnerCached;
@@ -86,6 +87,8 @@ public abstract class SutController implements SutHandler, CustomizationHandler
8687

8788
private final MongoHandler mongoHandler = new MongoHandler();
8889

90+
private final OpenSearchHandler openSearchHandler = new OpenSearchHandler();
91+
8992
private Server controllerServer;
9093

9194
/**
@@ -323,6 +326,18 @@ public final void initMongoHandler() {
323326
}
324327
}
325328

329+
// TODO: Refactor this initialization methods once Redis and OpenSearch implementations are done
330+
public final void initOpenSearchHandler() {
331+
// This is needed because the replacement use to get this info occurs during the start of the SUT.
332+
Object connection = getOpenSearchConnection();
333+
openSearchHandler.setOpenSearchClient(connection);
334+
335+
List<AdditionalInfo> list = getAdditionalInfoList();
336+
if (!list.isEmpty()) {
337+
AdditionalInfo last = list.get(list.size() - 1);
338+
last.getOpenSearchInfoData().forEach(openSearchHandler::handle);
339+
}
340+
}
326341

327342
/**
328343
* TODO further handle multiple connections
@@ -366,7 +381,7 @@ public final ExtraHeuristicsDto computeExtraHeuristics(boolean queryFromDatabase
366381

367382
ExtraHeuristicsDto dto = new ExtraHeuristicsDto();
368383

369-
if (isSQLHeuristicsComputationAllowed() || isMongoHeuristicsComputationAllowed()) {
384+
if (isSQLHeuristicsComputationAllowed() || isMongoHeuristicsComputationAllowed() || isOpenSearchHeuristicsComputationAllowed()) {
370385
List<AdditionalInfo> additionalInfoList = getAdditionalInfoList();
371386

372387
if (isSQLHeuristicsComputationAllowed()) {
@@ -375,6 +390,10 @@ public final ExtraHeuristicsDto computeExtraHeuristics(boolean queryFromDatabase
375390
if (isMongoHeuristicsComputationAllowed()) {
376391
computeMongoHeuristics(dto, additionalInfoList);
377392
}
393+
394+
if (isOpenSearchHeuristicsComputationAllowed()) {
395+
computeOpenSearchHeuristics(dto, additionalInfoList);
396+
}
378397
}
379398
return dto;
380399
}
@@ -387,6 +406,10 @@ private boolean isMongoHeuristicsComputationAllowed() {
387406
return mongoHandler.isCalculateHeuristics() || mongoHandler.isExtractMongoExecution();
388407
}
389408

409+
private boolean isOpenSearchHeuristicsComputationAllowed() {
410+
return openSearchHandler.isCalculateHeuristics();
411+
}
412+
390413
private void computeSQLHeuristics(ExtraHeuristicsDto dto, List<AdditionalInfo> additionalInfoList, boolean queryFromDatabase) {
391414
/*
392415
TODO refactor, once we move SQL analysis into Core
@@ -467,6 +490,34 @@ public final void computeMongoHeuristics(ExtraHeuristicsDto dto, List<Additional
467490
}
468491
}
469492

493+
public final void computeOpenSearchHeuristics(ExtraHeuristicsDto dto, List<AdditionalInfo> additionalInfoList) {
494+
if (openSearchHandler.isCalculateHeuristics()) {
495+
if (!additionalInfoList.isEmpty()) {
496+
AdditionalInfo last = additionalInfoList.get(additionalInfoList.size() - 1);
497+
last.getOpenSearchInfoData().forEach(it -> {
498+
try {
499+
openSearchHandler.handle(it);
500+
} catch (Exception e){
501+
SimpleLogger.error("FAILED TO HANDLE OPENSEARCH COMMAND", e);
502+
assert false;
503+
}
504+
});
505+
}
506+
507+
openSearchHandler.getEvaluatedOpenSearchCommands().stream()
508+
.map(p ->
509+
new ExtraHeuristicEntryDto(
510+
ExtraHeuristicEntryDto.Type.OPENSEARCH,
511+
ExtraHeuristicEntryDto.Objective.MINIMIZE_TO_ZERO,
512+
p.getCommand().toString(),
513+
p.getDistanceWithMetrics().getDistance(),
514+
p.getDistanceWithMetrics().getNumberOfEvaluatedDocuments(),
515+
false
516+
))
517+
.forEach(h -> dto.heuristics.add(h));
518+
}
519+
520+
}
470521

471522
/**
472523
* handle specified init sql script after SUT is started.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.evomaster.client.java.controller.internal.db;
2+
3+
public class OpenSearchCommandWithDistance {
4+
private final Object command;
5+
6+
private final OpenSearchDistanceWithMetrics distanceWithMetrics;
7+
8+
public OpenSearchCommandWithDistance(Object command, OpenSearchDistanceWithMetrics distanceWithMetrics) {
9+
this.command = command;
10+
this.distanceWithMetrics = distanceWithMetrics;
11+
}
12+
13+
public Object getCommand() {
14+
return command;
15+
}
16+
17+
public OpenSearchDistanceWithMetrics getDistanceWithMetrics() {
18+
return distanceWithMetrics;
19+
}
20+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.evomaster.client.java.controller.internal.db;
2+
3+
public class OpenSearchDistanceWithMetrics {
4+
5+
private final double distance;
6+
7+
private final int numberOfEvaluatedDocuments;
8+
9+
public OpenSearchDistanceWithMetrics(double distance, int numberOfEvaluatedDocuments) {
10+
if (distance < 0) {
11+
throw new IllegalArgumentException("distance must be non-negative but value is " + distance);
12+
}
13+
14+
if (numberOfEvaluatedDocuments < 0) {
15+
throw new IllegalArgumentException("numberOfEvaluatedDocuments must be non-negative but value is " + numberOfEvaluatedDocuments);
16+
}
17+
18+
this.distance = distance;
19+
this.numberOfEvaluatedDocuments = numberOfEvaluatedDocuments;
20+
}
21+
22+
public double getDistance() {
23+
return distance;
24+
}
25+
26+
public int getNumberOfEvaluatedDocuments() {
27+
return numberOfEvaluatedDocuments;
28+
}
29+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package org.evomaster.client.java.controller.internal.db;
2+
3+
import java.lang.reflect.Method;
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
import org.evomaster.client.java.controller.opensearch.OpenSearchHeuristicsCalculator;
7+
import org.evomaster.client.java.instrumentation.OpenSearchCommand;
8+
import org.evomaster.client.java.utils.SimpleLogger;
9+
10+
public class OpenSearchHandler {
11+
12+
public static final String OPENSEARCH_CLIENT_CLASS_NAME = "org.opensearch.client.opensearch.OpenSearchClient";
13+
public static final String OPENSEARCH_CLIENT_INDEX_METHOD_NAME = "index";
14+
public static final String OPENSEARCH_CLIENT_SIZE_METHOD_NAME = "size";
15+
public static final String OPENSEARCH_CLIENT_BUILD_METHOD_NAME = "build";
16+
public static final String OPENSEARCH_CLIENT_SEARCH_METHOD_NAME = "search";
17+
public static final String OPENSEARCH_CLIENT_HITS_METHOD_NAME = "hits";
18+
19+
public static final String OPENSEARCH_CLIENT_SEARCH_REQUEST_CLASS_NAME = "org.opensearch.client.opensearch.core.SearchRequest";
20+
public static final String OPENSEARCH_CLIENT_SEARCH_REQUEST_BUILDER_CLASS_NAME = "org.opensearch.client.opensearch.core.SearchRequest$Builder";
21+
22+
private final List<OpenSearchCommand> commands;
23+
24+
private final boolean calculateHeuristics;
25+
26+
/**
27+
* The heuristics based on the OpenSearch execution
28+
*/
29+
private final List<OpenSearchCommandWithDistance> commandsWithDistances;
30+
31+
private final OpenSearchHeuristicsCalculator calculator = new OpenSearchHeuristicsCalculator();
32+
33+
private Object openSearchClient = null;
34+
35+
public OpenSearchHandler() {
36+
this.commands = new ArrayList<>();
37+
this.commandsWithDistances = new ArrayList<>();
38+
this.calculateHeuristics = true;
39+
}
40+
41+
public boolean isCalculateHeuristics() {
42+
return calculateHeuristics;
43+
}
44+
45+
public void handle(OpenSearchCommand command) {
46+
commands.add(command);
47+
}
48+
49+
public List<OpenSearchCommandWithDistance> getEvaluatedOpenSearchCommands() {
50+
commands.stream()
51+
.filter(command -> command.getQuery() != null)
52+
.forEach(openSearchCommand -> {
53+
OpenSearchDistanceWithMetrics distanceWithMetrics = computeCommandDistance(openSearchCommand);
54+
commandsWithDistances.add(new OpenSearchCommandWithDistance(openSearchCommand, distanceWithMetrics));
55+
});
56+
57+
commands.clear();
58+
59+
return commandsWithDistances;
60+
}
61+
62+
private OpenSearchDistanceWithMetrics computeCommandDistance(OpenSearchCommand command) {
63+
List<String> indexName = command.getIndex();
64+
List<?> documents = getDocuments(indexName);
65+
66+
double min = Double.MAX_VALUE;
67+
int numberOfEvaluatedDocuments = 0;
68+
for (Object doc : documents) {
69+
numberOfEvaluatedDocuments += 1;
70+
double findDistance;
71+
try {
72+
findDistance = calculator.computeExpression(command.getQuery(), doc);
73+
} catch (Exception ex) {
74+
SimpleLogger.uniqueWarn("Failed to compute find: " + command.getQuery() + " with data " + doc);
75+
findDistance = Double.MAX_VALUE;
76+
}
77+
78+
if (findDistance == 0) {
79+
return new OpenSearchDistanceWithMetrics(0, numberOfEvaluatedDocuments);
80+
} else if (findDistance < min) {
81+
min = findDistance;
82+
}
83+
}
84+
85+
return new OpenSearchDistanceWithMetrics(min, numberOfEvaluatedDocuments);
86+
}
87+
88+
private List<Object> getDocuments(List<String> indexNames) {
89+
try {
90+
// Get the OpenSearchClient class
91+
Class<?> openSearchClientClass = openSearchClient.getClass();
92+
93+
// Get SearchRequest.Builder class
94+
Class<?> searchRequestBuilderClass = Class.forName(OPENSEARCH_CLIENT_SEARCH_REQUEST_BUILDER_CLASS_NAME);
95+
Object searchRequestBuilder = searchRequestBuilderClass.getDeclaredConstructor().newInstance();
96+
97+
// Set the index/indices on the builder
98+
Method indexMethod = searchRequestBuilderClass.getMethod(OPENSEARCH_CLIENT_INDEX_METHOD_NAME, List.class);
99+
indexMethod.invoke(searchRequestBuilder, indexNames);
100+
101+
// Set a large size to get all documents (could use scroll for large datasets)
102+
Method sizeMethod = searchRequestBuilderClass.getMethod(OPENSEARCH_CLIENT_SIZE_METHOD_NAME, Integer.class);
103+
sizeMethod.invoke(searchRequestBuilder, 10000); // Max 10k documents
104+
105+
// Build the SearchRequest
106+
Method buildMethod = searchRequestBuilderClass.getMethod(OPENSEARCH_CLIENT_BUILD_METHOD_NAME);
107+
Object searchRequest = buildMethod.invoke(searchRequestBuilder);
108+
109+
// Execute the search: openSearchClient.search(searchRequest, Object.class)
110+
Method searchMethod = openSearchClientClass.getMethod(OPENSEARCH_CLIENT_SEARCH_METHOD_NAME,
111+
Class.forName(OPENSEARCH_CLIENT_SEARCH_REQUEST_CLASS_NAME),
112+
Class.class);
113+
Object searchResponse = searchMethod.invoke(openSearchClient, searchRequest, Object.class);
114+
115+
Method hitsMethod = searchResponse.getClass().getMethod(OPENSEARCH_CLIENT_HITS_METHOD_NAME);
116+
Object hitsContainer = hitsMethod.invoke(searchResponse);
117+
118+
Method hitsListMethod = hitsContainer.getClass().getMethod(OPENSEARCH_CLIENT_HITS_METHOD_NAME);
119+
Object hitsList = hitsListMethod.invoke(hitsContainer);
120+
121+
// loop hitslist as if it was a list
122+
List<Object> result = new ArrayList<>();
123+
for (Object hit : (List<Object>)hitsList) {
124+
Method hitSourceMethod = hit.getClass().getMethod("source");
125+
result.add(hitSourceMethod.invoke(hit));
126+
}
127+
128+
return result; // Returns List<Document>
129+
130+
} catch (Exception e) {
131+
throw new RuntimeException("Failed to retrieve documents from OpenSearch indices: " + indexNames, e);
132+
}
133+
}
134+
135+
private Object getDocuments() {
136+
// Default implementation - this method should be called with specific index names
137+
return null;
138+
}
139+
140+
public void setOpenSearchClient(Object openSearchClient) {
141+
this.openSearchClient = openSearchClient;
142+
}
143+
}

0 commit comments

Comments
 (0)