From db1beb12cd12d4f47309de3b2d977f8d32299d6a Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 16:20:41 +0200 Subject: [PATCH 01/17] feat: add matrix benchmark enums --- .../heigit/ors/benchmark/BenchmarkEnums.java | 44 +++++++++++++++++++ .../ors/benchmark/BenchmarkEnumsTest.java | 38 ++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 4c5864912f..51fce91341 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -73,4 +73,48 @@ public String getValue() { } } + /** + * Enum representing different matrix modes for benchmarking. + * getRequestParams() provides the parameters to trigger each matrix algorithm. + */ + public enum MatrixModes { + ALGO_DIJKSTRA_MATRIX, + ALGO_CORE_MATRIX, + ALGO_RPHAST_MATRIX; + + public static MatrixModes fromString(String value) { + return switch (value.toLowerCase()) { + case "algodijkstra" -> ALGO_DIJKSTRA_MATRIX; + case "algocore" -> ALGO_CORE_MATRIX; + case "algorphast" -> ALGO_RPHAST_MATRIX; + default -> throw new IllegalArgumentException("Invalid directions mode: " + value); + }; + } + + public List getProfiles() { + return switch (this) { + case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car", "driving-hgv", "cycling-regular", "foot-walking"); + }; + } + /** + * Returns the request parameters for the matrix algorithm. + * These parameters are used to trigger the specific matrix algorithm. + * This is not great as we have to maintain this in multiple places, + * at the moment this is the only way to trigger the algorithms. + * What would be better is to have a common interface for the algorithms, + * but that would require a larger refactor of the codebase. + * @return a map of request parameters + */ + public Map getRequestParams() { + return switch (this) { + case ALGO_RPHAST_MATRIX -> Map.of(PREFERENCE, RECOMMENDED); + case ALGO_CORE_MATRIX -> Map.of(PREFERENCE, + RECOMMENDED, "options", Map.of("dynamic_speeds", "true")); + case ALGO_DIJKSTRA_MATRIX -> Map.of(PREFERENCE, + RECOMMENDED, "options", List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); + + }; + } + } + } diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java index a243611701..2d51ed12be 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java @@ -52,4 +52,42 @@ void testRangeTypeGetValue() { assertEquals("time", BenchmarkEnums.RangeType.TIME.getValue()); assertEquals("distance", BenchmarkEnums.RangeType.DISTANCE.getValue()); } + + @Test + void testMatrixModesFromString() { + assertEquals(BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX, BenchmarkEnums.MatrixModes.fromString("algodijkstra")); + assertEquals(BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX, BenchmarkEnums.MatrixModes.fromString("algocore")); + assertEquals(BenchmarkEnums.MatrixModes.ALGO_RPHAST_MATRIX, BenchmarkEnums.MatrixModes.fromString("algorphast")); + + Throwable exception = assertThrows(IllegalArgumentException.class, () -> BenchmarkEnums.MatrixModes.fromString("invalid")); + assertTrue(exception instanceof IllegalArgumentException); + } + + @Test + void testMatrixModesGetDefaultProfiles() { + List dijkstraProfiles = BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX.getProfiles(); + assertTrue(dijkstraProfiles.contains("driving-car")); + assertTrue(dijkstraProfiles.contains("foot-walking")); + assertEquals(4, dijkstraProfiles.size()); + + List coreProfiles = BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX.getProfiles(); + assertTrue(coreProfiles.contains("driving-car")); + assertTrue(coreProfiles.contains("foot-walking")); + assertEquals(4, coreProfiles.size()); + } + + @Test + void testMatrixModesGetRequestParams() { + Map rphastParams = BenchmarkEnums.MatrixModes.ALGO_RPHAST_MATRIX.getRequestParams(); + assertEquals("recommended", rphastParams.get("preference")); + assertEquals(1, rphastParams.size()); + + Map coreParams = BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX.getRequestParams(); + assertEquals("recommended", coreParams.get("preference")); + assertTrue(coreParams.get("options") instanceof Map); + + Map dijkstraParams = BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX.getRequestParams(); + assertEquals("recommended", dijkstraParams.get("preference")); + assertTrue(dijkstraParams.get("options") instanceof List); + } } From ed95708c512c4074588a1d13ac4e8b431e2e6984 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 17:01:15 +0200 Subject: [PATCH 02/17] fix: Update Loadtests and config to reflect matrix properly --- .../java/org/heigit/ors/benchmark/Config.java | 20 +- .../benchmark/MatrixAlgorithmLoadTest.java | 196 +++++++++++++++--- .../RequestBodyCreationException.java | 4 + 3 files changed, 190 insertions(+), 30 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java index fdae9d2070..73e9a0a024 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java @@ -1,6 +1,7 @@ package org.heigit.ors.benchmark; import org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; import org.heigit.ors.benchmark.BenchmarkEnums.TestUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,6 +10,7 @@ import java.util.List; import static org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes.*; +import static org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes.*; public class Config { private static final Logger logger = LoggerFactory.getLogger(Config.class); @@ -28,7 +30,8 @@ public class Config { private final boolean parallelExecution; private final TestUnit testUnit; private final List sourceFiles; - private final List modes; + private final List directionsModes; + private final List matrixModes; private final List ranges; public Config() { @@ -48,8 +51,8 @@ public Config() { this.testUnit = TestUnit.fromString(getSystemProperty("test_unit", "distance")); this.sourceFiles = parseCommaSeparatedStringToStrings(getSystemProperty("source_files", "")); this.ranges = parseCommaSeparatedStringToInts(this.range); - this.modes = parseCommaSeparatedStringToStrings(getSystemProperty("modes", "")); - } + this.directionsModes = parseCommaSeparatedStringToStrings(getSystemProperty("modes", "")); + this.matrixModes = parseCommaSeparatedStringToStrings(getSystemProperty("matrix_modes", ""));} private String getSystemProperty(String key, String defaultValue) { String value = System.getProperty(key) != null ? System.getProperty(key) : defaultValue; @@ -145,9 +148,16 @@ public List getRanges() { } public List getDirectionsModes() { - return modes.isEmpty() ? List.of(ALGO_CH, ALGO_CORE, ALGO_LM_ASTAR) - : modes.stream() + return directionsModes.isEmpty() ? List.of(ALGO_CH, ALGO_CORE, ALGO_LM_ASTAR) + : directionsModes.stream() .map(DirectionsModes::fromString) .toList(); } + + public List getMatrixModes() { + return matrixModes.isEmpty() ? List.of(ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX) + : directionsModes.stream() + .map(MatrixModes::fromString) + .toList(); + } } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 768645a968..902b3be1ec 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -5,7 +5,7 @@ import io.gatling.javaapi.core.ScenarioBuilder; import io.gatling.javaapi.core.Session; import io.gatling.javaapi.http.HttpRequestActionBuilder; -import org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; import org.heigit.ors.benchmark.exceptions.RequestBodyCreationException; import org.heigit.ors.util.SourceUtils; import org.slf4j.LoggerFactory; @@ -20,16 +20,38 @@ import static io.gatling.javaapi.http.HttpDsl.http; import static io.gatling.javaapi.http.HttpDsl.status; +/** + * Load test implementation for OpenRouteService Matrix API using Gatling + * framework. + * + * This class performs load testing on the matrix endpoint by: + * - Reading matrix test data from CSV files containing coordinates, sources, + * and destinations + * - Creating HTTP requests to the /v2/matrix/{profile} endpoint + * - Testing different matrix calculation modes and routing profiles + * - Measuring response times and throughput under concurrent load + * + * The test data is expected to be in CSV format with columns: + * coordinates, sources, destinations, distances, profile + */ public class MatrixAlgorithmLoadTest extends AbstractLoadTest { static { logger = LoggerFactory.getLogger(MatrixAlgorithmLoadTest.class); } + /** + * Constructs a new MatrixAlgorithmLoadTest instance. + * Initializes the load test with configuration from the parent class. + */ public MatrixAlgorithmLoadTest() { super(); } + /** + * Logs configuration information specific to matrix load testing. + * Displays source files, concurrent users, and execution mode. + */ @Override protected void logConfigInfo() { logger.info("Initializing MatrixAlgorithmLoadTest:"); @@ -38,33 +60,69 @@ protected void logConfigInfo() { logger.info("- Execution mode: {}", config.isParallelExecution() ? "parallel" : "sequential"); } + /** + * Logs the type of test being performed. + */ @Override protected void logTestTypeInfo() { logger.info("Testing matrix"); } + /** + * Creates test scenarios for all combinations of matrix modes, source files, + * and profiles. + * + * @param isParallel whether scenarios should be executed in parallel + * @return stream of PopulationBuilder instances for each test scenario + */ @Override protected Stream createScenarios(boolean isParallel) { - return config.getDirectionsModes().stream() + return config.getMatrixModes().stream() .flatMap(mode -> config.getSourceFiles().stream() .flatMap(sourceFile -> mode.getProfiles().stream() .map(profile -> createScenarioWithInjection(sourceFile, isParallel, mode, profile)))); } - private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, DirectionsModes mode, + /** + * Creates a single test scenario with user injection configuration. + * + * @param sourceFile path to the CSV file containing test data + * @param isParallel whether the scenario runs in parallel mode + * @param mode the matrix calculation mode to test + * @param profile the routing profile to test + * @return PopulationBuilder configured with the specified parameters + */ + private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, MatrixModes mode, String profile) { String scenarioName = formatScenarioName(mode, profile, isParallel); return createMatrixScenario(scenarioName, sourceFile, config, mode, profile) .injectOpen(atOnceUsers(config.getNumConcurrentUsers())); } - private String formatScenarioName(DirectionsModes mode, String profile, boolean isParallel) { + /** + * Formats a descriptive name for the test scenario. + * + * @param mode the matrix calculation mode + * @param profile the routing profile + * @param isParallel whether the scenario runs in parallel + * @return formatted scenario name string + */ + private String formatScenarioName(MatrixModes mode, String profile, boolean isParallel) { return String.format("%s - %s - %s", isParallel ? "Parallel" : "Sequential", mode, profile); } + /** + * Creates a Gatling scenario for matrix load testing. + * + * @param name descriptive name for the scenario + * @param sourceFile path to CSV file containing test coordinates + * @param config test configuration parameters + * @param mode matrix calculation mode to test + * @param profile routing profile to test + * @return ScenarioBuilder configured for matrix testing + */ private static ScenarioBuilder createMatrixScenario(String name, String sourceFile, Config config, - DirectionsModes mode, String profile) { - + MatrixModes mode, String profile) { try { List> records = csv(sourceFile).readRecords(); List> targetRecords = SourceUtils.getRecordsByProfile(records, profile); @@ -86,7 +144,16 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi } } - private static HttpRequestActionBuilder createRequest(String name, Config config, DirectionsModes mode, + /** + * Creates an HTTP request action for the matrix API endpoint. + * + * @param name request name for identification in test results + * @param config test configuration + * @param mode matrix calculation mode + * @param profile routing profile + * @return HttpRequestActionBuilder configured for matrix API calls + */ + private static HttpRequestActionBuilder createRequest(String name, Config config, MatrixModes mode, String profile) { return http(name) .post("/v2/matrix/" + profile) @@ -95,12 +162,27 @@ private static HttpRequestActionBuilder createRequest(String name, Config config .check(status().is(200)); } - static String createRequestBody(Session session, Config config, DirectionsModes mode) { + /** + * Creates the JSON request body for matrix API calls from CSV session data. + * + * @param session Gatling session containing CSV row data + * @param config test configuration + * @param mode matrix calculation mode providing additional parameters + * @return JSON string representation of the request body + * @throws RequestBodyCreationException if JSON serialization fails + */ + static String createRequestBody(Session session, Config config, MatrixModes mode) { try { + // Get the data from the CSV row + String coordinatesStr = (String) session.get("coordinates"); + String sourcesStr = (String) session.get("sources"); + String destinationsStr = (String) session.get("destinations"); + Map requestBody = new java.util.HashMap<>(Map.of( - "locations", createLocationsListFromArrays(session, config), - "sources", List.of(0), - "destinations", List.of(1))); + "locations", parseCoordinatesFromString(coordinatesStr), + "sources", parseIntegerArrayFromString(sourcesStr), + "destinations", parseIntegerArrayFromString(destinationsStr))); + requestBody.putAll(mode.getRequestParams()); return objectMapper.writeValueAsString(requestBody); } catch (JsonProcessingException e) { @@ -108,21 +190,85 @@ static String createRequestBody(Session session, Config config, DirectionsModes } } - static List> createLocationsListFromArrays(Session session, Config config) { - List> locations = new ArrayList<>(); + /** + * Parses coordinate pairs from CSV string format to nested list structure. + * + * Converts strings like "[[8.695556, 49.392701], [8.684623, 49.398284]]" + * into List> format expected by the matrix API. + * + * @param coordinatesStr string representation of coordinate array from CSV + * @return list of coordinate pairs as [longitude, latitude] arrays + * @throws RequestBodyCreationException if parsing fails or format is invalid + */ + static List> parseCoordinatesFromString(String coordinatesStr) { + try { + if (coordinatesStr == null || coordinatesStr.trim().isEmpty()) { + throw new RequestBodyCreationException("Coordinates string is null or empty"); + } + + // Remove quotes if present + String cleaned = coordinatesStr.trim(); + if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { + cleaned = cleaned.substring(1, cleaned.length() - 1); + } + + // Remove outer brackets + cleaned = cleaned.substring(2, cleaned.length() - 2); + String[] coordinatePairs = cleaned.split("\\], \\["); + + List> locations = new ArrayList<>(); + for (String pair : coordinatePairs) { + String[] values = pair.split(", "); + if (values.length != 2) { + throw new RequestBodyCreationException("Invalid coordinate pair: " + pair); + } + double lon = Double.parseDouble(values[0]); + double lat = Double.parseDouble(values[1]); + locations.add(List.of(lon, lat)); + } + return locations; + } catch (Exception e) { + throw new RequestBodyCreationException("Failed to parse coordinates: " + coordinatesStr, e); + } + } + + /** + * Parses integer arrays from CSV string format. + * + * Converts strings like "[0, 1, 2]" into List format + * for sources and destinations parameters. + * + * @param arrayStr string representation of integer array from CSV + * @return list of integers + * @throws RequestBodyCreationException if parsing fails or format is invalid + */ + static List parseIntegerArrayFromString(String arrayStr) { try { - Double startLon = Double.valueOf((String) session.getList(config.getFieldStartLon()).get(0)); - Double startLat = Double.valueOf((String) session.getList(config.getFieldStartLat()).get(0)); - locations.add(List.of(startLon, startLat)); - Double endLon = Double.valueOf((String) session.getList(config.getFieldEndLon()).get(0)); - Double endLat = Double.valueOf((String) session.getList(config.getFieldEndLat()).get(0)); - locations.add(List.of(endLon, endLat)); - } catch (NumberFormatException e) { - String errorMessage = String.format( - "Failed to parse coordinate values in locations list at index %d. Original value could not be converted to double", - locations.size()); - throw new RequestBodyCreationException("Error processing coordinates: " + errorMessage, e); + if (arrayStr == null || arrayStr.trim().isEmpty()) { + throw new RequestBodyCreationException("Array string is null or empty"); + } + + // Remove quotes if present + String cleaned = arrayStr.trim(); + if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { + cleaned = cleaned.substring(1, cleaned.length() - 1); + } + + // Remove brackets + cleaned = cleaned.substring(1, cleaned.length() - 1); + + if (cleaned.trim().isEmpty()) { + return new ArrayList<>(); + } + + String[] values = cleaned.split(", "); + List result = new ArrayList<>(); + for (String value : values) { + result.add(Integer.parseInt(value.trim())); + } + return result; + } catch (Exception e) { + throw new RequestBodyCreationException("Failed to parse integer array: " + arrayStr, e); } - return locations; } } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java index d3550d93d1..d01a9eb149 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java @@ -4,4 +4,8 @@ public class RequestBodyCreationException extends RuntimeException { public RequestBodyCreationException(String message, Throwable cause) { super(message, cause); } + + public RequestBodyCreationException(String message) { + super(message); + } } From 1fed439e9f8d179f6dcbaaf92d0200796eacfd6c Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 17:14:31 +0200 Subject: [PATCH 03/17] feat: add testing for load test --- .../MatrixAlgorithmLoadTestTest.java | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java new file mode 100644 index 0000000000..9c880e3b47 --- /dev/null +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -0,0 +1,279 @@ +package org.heigit.ors.benchmark; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.gatling.javaapi.core.Session; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; +import org.heigit.ors.benchmark.exceptions.RequestBodyCreationException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MatrixAlgorithmLoadTestTest { + private ObjectMapper objectMapper; + private Session mockSession; + private Config mockConfig; + private MatrixModes mockMode; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockSession = mock(Session.class); + mockConfig = mock(Config.class); + mockMode = mock(MatrixModes.class); + + // Mock CSV data as it would appear in the session + when(mockSession.get("coordinates")) + .thenReturn("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]"); + when(mockSession.get("sources")).thenReturn("[0, 1]"); + when(mockSession.get("destinations")).thenReturn("[2]"); + when(mockMode.getRequestParams()).thenReturn(Map.of("preference", "recommended")); + } + + @Test + void createRequestBody_ShouldCreateValidJson() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + + // then + JsonNode json = objectMapper.readTree(result); + assertThat(json.get("locations")).isNotNull(); + assertThat(json.get("sources")).isNotNull(); + assertThat(json.get("destinations")).isNotNull(); + assertThat(json.get("preference").asText()).isEqualTo("recommended"); + } + + @Test + void createRequestBody_ShouldIncludeCorrectLocations() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(3, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + assertEquals(8.684623, locations.get(1).get(0).asDouble(), 0.000001); + assertEquals(49.398284, locations.get(1).get(1).asDouble(), 0.000001); + assertEquals(8.705916, locations.get(2).get(0).asDouble(), 0.000001); + assertEquals(49.406309, locations.get(2).get(1).asDouble(), 0.000001); + } + + @Test + void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(2, sources.size()); + assertEquals(0, sources.get(0).asInt()); + assertEquals(1, sources.get(1).asInt()); + + assertEquals(1, destinations.size()); + assertEquals(2, destinations.get(0).asInt()); + } + + @Test + void parseCoordinatesFromString_ShouldParseValidCoordinates() { + // given + String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284]]"; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(2, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + assertEquals(8.684623, result.get(1).get(0), 0.000001); + assertEquals(49.398284, result.get(1).get(1), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldHandleQuotedStrings() { + // given + String coordinatesStr = "\"[[8.695556, 49.392701], [8.684623, 49.398284]]\""; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(2, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForNullInput() { + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(null)); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForEmptyInput() { + // when & then + assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString("")); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForInvalidCoordinatePair() { + // given + String invalidCoordinates = "[[8.695556], [8.684623, 49.398284]]"; + + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(invalidCoordinates)); + } + + @Test + void parseIntegerArrayFromString_ShouldParseValidArray() { + // given + String arrayStr = "[0, 1, 2]"; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertEquals(3, result.size()); + assertEquals(0, result.get(0)); + assertEquals(1, result.get(1)); + assertEquals(2, result.get(2)); + } + + @Test + void parseIntegerArrayFromString_ShouldHandleQuotedStrings() { + // given + String arrayStr = "\"[0, 1, 2]\""; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertEquals(3, result.size()); + assertEquals(0, result.get(0)); + assertEquals(1, result.get(1)); + assertEquals(2, result.get(2)); + } + + @Test + void parseIntegerArrayFromString_ShouldReturnEmptyListForEmptyArray() { + // given + String arrayStr = "[]"; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertTrue(result.isEmpty()); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForNullInput() { + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(null)); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForEmptyInput() { + // when & then + assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString("")); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForInvalidInteger() { + // given + String invalidArray = "[0, invalid, 2]"; + + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(invalidArray)); + } + + @Test + void createRequestBody_WithDifferentMatrixMode() throws JsonProcessingException { + // given + when(mockMode.getRequestParams()).thenReturn(Map.of( + "preference", "fastest", + "options", Map.of("avoid_features", Arrays.asList("highways")))); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + assertEquals("fastest", json.get("preference").asText()); + assertThat(json.get("options")).isNotNull(); + } + + @Test + void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonProcessingException { + // given + when(mockSession.get("sources")).thenReturn("[0, 1, 2, 3]"); + when(mockSession.get("destinations")).thenReturn("[4, 5, 6, 7, 8]"); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(4, sources.size()); + assertEquals(5, destinations.size()); + assertEquals(0, sources.get(0).asInt()); + assertEquals(3, sources.get(3).asInt()); + assertEquals(4, destinations.get(0).asInt()); + assertEquals(8, destinations.get(4).asInt()); + } + + @Test + void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingException { + // given + when(mockSession.get("coordinates")).thenReturn("[[8.695556, 49.392701]]"); + when(mockSession.get("sources")).thenReturn("[0]"); + when(mockSession.get("destinations")).thenReturn("[0]"); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(1, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldHandleLargeCoordinateArray() { + // given + String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309], [8.689981, 49.394522], [8.681502, 49.394791]]"; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(5, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + assertEquals(8.681502, result.get(4).get(0), 0.000001); + assertEquals(49.394791, result.get(4).get(1), 0.000001); + } +} \ No newline at end of file From d3c471a537b057daf98dfdf2620d7d0065b5f775 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 16:20:41 +0200 Subject: [PATCH 04/17] feat: add matrix benchmark enums --- .../heigit/ors/benchmark/BenchmarkEnums.java | 44 +++++++++++++++++++ .../ors/benchmark/BenchmarkEnumsTest.java | 38 ++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 4c5864912f..51fce91341 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -73,4 +73,48 @@ public String getValue() { } } + /** + * Enum representing different matrix modes for benchmarking. + * getRequestParams() provides the parameters to trigger each matrix algorithm. + */ + public enum MatrixModes { + ALGO_DIJKSTRA_MATRIX, + ALGO_CORE_MATRIX, + ALGO_RPHAST_MATRIX; + + public static MatrixModes fromString(String value) { + return switch (value.toLowerCase()) { + case "algodijkstra" -> ALGO_DIJKSTRA_MATRIX; + case "algocore" -> ALGO_CORE_MATRIX; + case "algorphast" -> ALGO_RPHAST_MATRIX; + default -> throw new IllegalArgumentException("Invalid directions mode: " + value); + }; + } + + public List getProfiles() { + return switch (this) { + case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car", "driving-hgv", "cycling-regular", "foot-walking"); + }; + } + /** + * Returns the request parameters for the matrix algorithm. + * These parameters are used to trigger the specific matrix algorithm. + * This is not great as we have to maintain this in multiple places, + * at the moment this is the only way to trigger the algorithms. + * What would be better is to have a common interface for the algorithms, + * but that would require a larger refactor of the codebase. + * @return a map of request parameters + */ + public Map getRequestParams() { + return switch (this) { + case ALGO_RPHAST_MATRIX -> Map.of(PREFERENCE, RECOMMENDED); + case ALGO_CORE_MATRIX -> Map.of(PREFERENCE, + RECOMMENDED, "options", Map.of("dynamic_speeds", "true")); + case ALGO_DIJKSTRA_MATRIX -> Map.of(PREFERENCE, + RECOMMENDED, "options", List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); + + }; + } + } + } diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java index a243611701..2d51ed12be 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java @@ -52,4 +52,42 @@ void testRangeTypeGetValue() { assertEquals("time", BenchmarkEnums.RangeType.TIME.getValue()); assertEquals("distance", BenchmarkEnums.RangeType.DISTANCE.getValue()); } + + @Test + void testMatrixModesFromString() { + assertEquals(BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX, BenchmarkEnums.MatrixModes.fromString("algodijkstra")); + assertEquals(BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX, BenchmarkEnums.MatrixModes.fromString("algocore")); + assertEquals(BenchmarkEnums.MatrixModes.ALGO_RPHAST_MATRIX, BenchmarkEnums.MatrixModes.fromString("algorphast")); + + Throwable exception = assertThrows(IllegalArgumentException.class, () -> BenchmarkEnums.MatrixModes.fromString("invalid")); + assertTrue(exception instanceof IllegalArgumentException); + } + + @Test + void testMatrixModesGetDefaultProfiles() { + List dijkstraProfiles = BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX.getProfiles(); + assertTrue(dijkstraProfiles.contains("driving-car")); + assertTrue(dijkstraProfiles.contains("foot-walking")); + assertEquals(4, dijkstraProfiles.size()); + + List coreProfiles = BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX.getProfiles(); + assertTrue(coreProfiles.contains("driving-car")); + assertTrue(coreProfiles.contains("foot-walking")); + assertEquals(4, coreProfiles.size()); + } + + @Test + void testMatrixModesGetRequestParams() { + Map rphastParams = BenchmarkEnums.MatrixModes.ALGO_RPHAST_MATRIX.getRequestParams(); + assertEquals("recommended", rphastParams.get("preference")); + assertEquals(1, rphastParams.size()); + + Map coreParams = BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX.getRequestParams(); + assertEquals("recommended", coreParams.get("preference")); + assertTrue(coreParams.get("options") instanceof Map); + + Map dijkstraParams = BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX.getRequestParams(); + assertEquals("recommended", dijkstraParams.get("preference")); + assertTrue(dijkstraParams.get("options") instanceof List); + } } From 565d657301f17bd380cd4c18f9b507cbc0395373 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 17:01:15 +0200 Subject: [PATCH 05/17] fix: Update Loadtests and config to reflect matrix properly --- .../java/org/heigit/ors/benchmark/Config.java | 20 +- .../benchmark/MatrixAlgorithmLoadTest.java | 196 +++++++++++++++--- .../RequestBodyCreationException.java | 4 + 3 files changed, 190 insertions(+), 30 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java index fdae9d2070..73e9a0a024 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java @@ -1,6 +1,7 @@ package org.heigit.ors.benchmark; import org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; import org.heigit.ors.benchmark.BenchmarkEnums.TestUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,6 +10,7 @@ import java.util.List; import static org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes.*; +import static org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes.*; public class Config { private static final Logger logger = LoggerFactory.getLogger(Config.class); @@ -28,7 +30,8 @@ public class Config { private final boolean parallelExecution; private final TestUnit testUnit; private final List sourceFiles; - private final List modes; + private final List directionsModes; + private final List matrixModes; private final List ranges; public Config() { @@ -48,8 +51,8 @@ public Config() { this.testUnit = TestUnit.fromString(getSystemProperty("test_unit", "distance")); this.sourceFiles = parseCommaSeparatedStringToStrings(getSystemProperty("source_files", "")); this.ranges = parseCommaSeparatedStringToInts(this.range); - this.modes = parseCommaSeparatedStringToStrings(getSystemProperty("modes", "")); - } + this.directionsModes = parseCommaSeparatedStringToStrings(getSystemProperty("modes", "")); + this.matrixModes = parseCommaSeparatedStringToStrings(getSystemProperty("matrix_modes", ""));} private String getSystemProperty(String key, String defaultValue) { String value = System.getProperty(key) != null ? System.getProperty(key) : defaultValue; @@ -145,9 +148,16 @@ public List getRanges() { } public List getDirectionsModes() { - return modes.isEmpty() ? List.of(ALGO_CH, ALGO_CORE, ALGO_LM_ASTAR) - : modes.stream() + return directionsModes.isEmpty() ? List.of(ALGO_CH, ALGO_CORE, ALGO_LM_ASTAR) + : directionsModes.stream() .map(DirectionsModes::fromString) .toList(); } + + public List getMatrixModes() { + return matrixModes.isEmpty() ? List.of(ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX) + : directionsModes.stream() + .map(MatrixModes::fromString) + .toList(); + } } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 768645a968..902b3be1ec 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -5,7 +5,7 @@ import io.gatling.javaapi.core.ScenarioBuilder; import io.gatling.javaapi.core.Session; import io.gatling.javaapi.http.HttpRequestActionBuilder; -import org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; import org.heigit.ors.benchmark.exceptions.RequestBodyCreationException; import org.heigit.ors.util.SourceUtils; import org.slf4j.LoggerFactory; @@ -20,16 +20,38 @@ import static io.gatling.javaapi.http.HttpDsl.http; import static io.gatling.javaapi.http.HttpDsl.status; +/** + * Load test implementation for OpenRouteService Matrix API using Gatling + * framework. + * + * This class performs load testing on the matrix endpoint by: + * - Reading matrix test data from CSV files containing coordinates, sources, + * and destinations + * - Creating HTTP requests to the /v2/matrix/{profile} endpoint + * - Testing different matrix calculation modes and routing profiles + * - Measuring response times and throughput under concurrent load + * + * The test data is expected to be in CSV format with columns: + * coordinates, sources, destinations, distances, profile + */ public class MatrixAlgorithmLoadTest extends AbstractLoadTest { static { logger = LoggerFactory.getLogger(MatrixAlgorithmLoadTest.class); } + /** + * Constructs a new MatrixAlgorithmLoadTest instance. + * Initializes the load test with configuration from the parent class. + */ public MatrixAlgorithmLoadTest() { super(); } + /** + * Logs configuration information specific to matrix load testing. + * Displays source files, concurrent users, and execution mode. + */ @Override protected void logConfigInfo() { logger.info("Initializing MatrixAlgorithmLoadTest:"); @@ -38,33 +60,69 @@ protected void logConfigInfo() { logger.info("- Execution mode: {}", config.isParallelExecution() ? "parallel" : "sequential"); } + /** + * Logs the type of test being performed. + */ @Override protected void logTestTypeInfo() { logger.info("Testing matrix"); } + /** + * Creates test scenarios for all combinations of matrix modes, source files, + * and profiles. + * + * @param isParallel whether scenarios should be executed in parallel + * @return stream of PopulationBuilder instances for each test scenario + */ @Override protected Stream createScenarios(boolean isParallel) { - return config.getDirectionsModes().stream() + return config.getMatrixModes().stream() .flatMap(mode -> config.getSourceFiles().stream() .flatMap(sourceFile -> mode.getProfiles().stream() .map(profile -> createScenarioWithInjection(sourceFile, isParallel, mode, profile)))); } - private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, DirectionsModes mode, + /** + * Creates a single test scenario with user injection configuration. + * + * @param sourceFile path to the CSV file containing test data + * @param isParallel whether the scenario runs in parallel mode + * @param mode the matrix calculation mode to test + * @param profile the routing profile to test + * @return PopulationBuilder configured with the specified parameters + */ + private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, MatrixModes mode, String profile) { String scenarioName = formatScenarioName(mode, profile, isParallel); return createMatrixScenario(scenarioName, sourceFile, config, mode, profile) .injectOpen(atOnceUsers(config.getNumConcurrentUsers())); } - private String formatScenarioName(DirectionsModes mode, String profile, boolean isParallel) { + /** + * Formats a descriptive name for the test scenario. + * + * @param mode the matrix calculation mode + * @param profile the routing profile + * @param isParallel whether the scenario runs in parallel + * @return formatted scenario name string + */ + private String formatScenarioName(MatrixModes mode, String profile, boolean isParallel) { return String.format("%s - %s - %s", isParallel ? "Parallel" : "Sequential", mode, profile); } + /** + * Creates a Gatling scenario for matrix load testing. + * + * @param name descriptive name for the scenario + * @param sourceFile path to CSV file containing test coordinates + * @param config test configuration parameters + * @param mode matrix calculation mode to test + * @param profile routing profile to test + * @return ScenarioBuilder configured for matrix testing + */ private static ScenarioBuilder createMatrixScenario(String name, String sourceFile, Config config, - DirectionsModes mode, String profile) { - + MatrixModes mode, String profile) { try { List> records = csv(sourceFile).readRecords(); List> targetRecords = SourceUtils.getRecordsByProfile(records, profile); @@ -86,7 +144,16 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi } } - private static HttpRequestActionBuilder createRequest(String name, Config config, DirectionsModes mode, + /** + * Creates an HTTP request action for the matrix API endpoint. + * + * @param name request name for identification in test results + * @param config test configuration + * @param mode matrix calculation mode + * @param profile routing profile + * @return HttpRequestActionBuilder configured for matrix API calls + */ + private static HttpRequestActionBuilder createRequest(String name, Config config, MatrixModes mode, String profile) { return http(name) .post("/v2/matrix/" + profile) @@ -95,12 +162,27 @@ private static HttpRequestActionBuilder createRequest(String name, Config config .check(status().is(200)); } - static String createRequestBody(Session session, Config config, DirectionsModes mode) { + /** + * Creates the JSON request body for matrix API calls from CSV session data. + * + * @param session Gatling session containing CSV row data + * @param config test configuration + * @param mode matrix calculation mode providing additional parameters + * @return JSON string representation of the request body + * @throws RequestBodyCreationException if JSON serialization fails + */ + static String createRequestBody(Session session, Config config, MatrixModes mode) { try { + // Get the data from the CSV row + String coordinatesStr = (String) session.get("coordinates"); + String sourcesStr = (String) session.get("sources"); + String destinationsStr = (String) session.get("destinations"); + Map requestBody = new java.util.HashMap<>(Map.of( - "locations", createLocationsListFromArrays(session, config), - "sources", List.of(0), - "destinations", List.of(1))); + "locations", parseCoordinatesFromString(coordinatesStr), + "sources", parseIntegerArrayFromString(sourcesStr), + "destinations", parseIntegerArrayFromString(destinationsStr))); + requestBody.putAll(mode.getRequestParams()); return objectMapper.writeValueAsString(requestBody); } catch (JsonProcessingException e) { @@ -108,21 +190,85 @@ static String createRequestBody(Session session, Config config, DirectionsModes } } - static List> createLocationsListFromArrays(Session session, Config config) { - List> locations = new ArrayList<>(); + /** + * Parses coordinate pairs from CSV string format to nested list structure. + * + * Converts strings like "[[8.695556, 49.392701], [8.684623, 49.398284]]" + * into List> format expected by the matrix API. + * + * @param coordinatesStr string representation of coordinate array from CSV + * @return list of coordinate pairs as [longitude, latitude] arrays + * @throws RequestBodyCreationException if parsing fails or format is invalid + */ + static List> parseCoordinatesFromString(String coordinatesStr) { + try { + if (coordinatesStr == null || coordinatesStr.trim().isEmpty()) { + throw new RequestBodyCreationException("Coordinates string is null or empty"); + } + + // Remove quotes if present + String cleaned = coordinatesStr.trim(); + if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { + cleaned = cleaned.substring(1, cleaned.length() - 1); + } + + // Remove outer brackets + cleaned = cleaned.substring(2, cleaned.length() - 2); + String[] coordinatePairs = cleaned.split("\\], \\["); + + List> locations = new ArrayList<>(); + for (String pair : coordinatePairs) { + String[] values = pair.split(", "); + if (values.length != 2) { + throw new RequestBodyCreationException("Invalid coordinate pair: " + pair); + } + double lon = Double.parseDouble(values[0]); + double lat = Double.parseDouble(values[1]); + locations.add(List.of(lon, lat)); + } + return locations; + } catch (Exception e) { + throw new RequestBodyCreationException("Failed to parse coordinates: " + coordinatesStr, e); + } + } + + /** + * Parses integer arrays from CSV string format. + * + * Converts strings like "[0, 1, 2]" into List format + * for sources and destinations parameters. + * + * @param arrayStr string representation of integer array from CSV + * @return list of integers + * @throws RequestBodyCreationException if parsing fails or format is invalid + */ + static List parseIntegerArrayFromString(String arrayStr) { try { - Double startLon = Double.valueOf((String) session.getList(config.getFieldStartLon()).get(0)); - Double startLat = Double.valueOf((String) session.getList(config.getFieldStartLat()).get(0)); - locations.add(List.of(startLon, startLat)); - Double endLon = Double.valueOf((String) session.getList(config.getFieldEndLon()).get(0)); - Double endLat = Double.valueOf((String) session.getList(config.getFieldEndLat()).get(0)); - locations.add(List.of(endLon, endLat)); - } catch (NumberFormatException e) { - String errorMessage = String.format( - "Failed to parse coordinate values in locations list at index %d. Original value could not be converted to double", - locations.size()); - throw new RequestBodyCreationException("Error processing coordinates: " + errorMessage, e); + if (arrayStr == null || arrayStr.trim().isEmpty()) { + throw new RequestBodyCreationException("Array string is null or empty"); + } + + // Remove quotes if present + String cleaned = arrayStr.trim(); + if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { + cleaned = cleaned.substring(1, cleaned.length() - 1); + } + + // Remove brackets + cleaned = cleaned.substring(1, cleaned.length() - 1); + + if (cleaned.trim().isEmpty()) { + return new ArrayList<>(); + } + + String[] values = cleaned.split(", "); + List result = new ArrayList<>(); + for (String value : values) { + result.add(Integer.parseInt(value.trim())); + } + return result; + } catch (Exception e) { + throw new RequestBodyCreationException("Failed to parse integer array: " + arrayStr, e); } - return locations; } } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java index d3550d93d1..d01a9eb149 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java @@ -4,4 +4,8 @@ public class RequestBodyCreationException extends RuntimeException { public RequestBodyCreationException(String message, Throwable cause) { super(message, cause); } + + public RequestBodyCreationException(String message) { + super(message); + } } From 4cdb54ede955a56a27f148c5726322ae191ff29e Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 17:14:31 +0200 Subject: [PATCH 06/17] feat: add testing for load test --- .../MatrixAlgorithmLoadTestTest.java | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java new file mode 100644 index 0000000000..9c880e3b47 --- /dev/null +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -0,0 +1,279 @@ +package org.heigit.ors.benchmark; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.gatling.javaapi.core.Session; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; +import org.heigit.ors.benchmark.exceptions.RequestBodyCreationException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MatrixAlgorithmLoadTestTest { + private ObjectMapper objectMapper; + private Session mockSession; + private Config mockConfig; + private MatrixModes mockMode; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockSession = mock(Session.class); + mockConfig = mock(Config.class); + mockMode = mock(MatrixModes.class); + + // Mock CSV data as it would appear in the session + when(mockSession.get("coordinates")) + .thenReturn("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]"); + when(mockSession.get("sources")).thenReturn("[0, 1]"); + when(mockSession.get("destinations")).thenReturn("[2]"); + when(mockMode.getRequestParams()).thenReturn(Map.of("preference", "recommended")); + } + + @Test + void createRequestBody_ShouldCreateValidJson() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + + // then + JsonNode json = objectMapper.readTree(result); + assertThat(json.get("locations")).isNotNull(); + assertThat(json.get("sources")).isNotNull(); + assertThat(json.get("destinations")).isNotNull(); + assertThat(json.get("preference").asText()).isEqualTo("recommended"); + } + + @Test + void createRequestBody_ShouldIncludeCorrectLocations() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(3, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + assertEquals(8.684623, locations.get(1).get(0).asDouble(), 0.000001); + assertEquals(49.398284, locations.get(1).get(1).asDouble(), 0.000001); + assertEquals(8.705916, locations.get(2).get(0).asDouble(), 0.000001); + assertEquals(49.406309, locations.get(2).get(1).asDouble(), 0.000001); + } + + @Test + void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(2, sources.size()); + assertEquals(0, sources.get(0).asInt()); + assertEquals(1, sources.get(1).asInt()); + + assertEquals(1, destinations.size()); + assertEquals(2, destinations.get(0).asInt()); + } + + @Test + void parseCoordinatesFromString_ShouldParseValidCoordinates() { + // given + String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284]]"; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(2, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + assertEquals(8.684623, result.get(1).get(0), 0.000001); + assertEquals(49.398284, result.get(1).get(1), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldHandleQuotedStrings() { + // given + String coordinatesStr = "\"[[8.695556, 49.392701], [8.684623, 49.398284]]\""; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(2, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForNullInput() { + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(null)); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForEmptyInput() { + // when & then + assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString("")); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForInvalidCoordinatePair() { + // given + String invalidCoordinates = "[[8.695556], [8.684623, 49.398284]]"; + + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(invalidCoordinates)); + } + + @Test + void parseIntegerArrayFromString_ShouldParseValidArray() { + // given + String arrayStr = "[0, 1, 2]"; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertEquals(3, result.size()); + assertEquals(0, result.get(0)); + assertEquals(1, result.get(1)); + assertEquals(2, result.get(2)); + } + + @Test + void parseIntegerArrayFromString_ShouldHandleQuotedStrings() { + // given + String arrayStr = "\"[0, 1, 2]\""; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertEquals(3, result.size()); + assertEquals(0, result.get(0)); + assertEquals(1, result.get(1)); + assertEquals(2, result.get(2)); + } + + @Test + void parseIntegerArrayFromString_ShouldReturnEmptyListForEmptyArray() { + // given + String arrayStr = "[]"; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertTrue(result.isEmpty()); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForNullInput() { + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(null)); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForEmptyInput() { + // when & then + assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString("")); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForInvalidInteger() { + // given + String invalidArray = "[0, invalid, 2]"; + + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(invalidArray)); + } + + @Test + void createRequestBody_WithDifferentMatrixMode() throws JsonProcessingException { + // given + when(mockMode.getRequestParams()).thenReturn(Map.of( + "preference", "fastest", + "options", Map.of("avoid_features", Arrays.asList("highways")))); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + assertEquals("fastest", json.get("preference").asText()); + assertThat(json.get("options")).isNotNull(); + } + + @Test + void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonProcessingException { + // given + when(mockSession.get("sources")).thenReturn("[0, 1, 2, 3]"); + when(mockSession.get("destinations")).thenReturn("[4, 5, 6, 7, 8]"); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(4, sources.size()); + assertEquals(5, destinations.size()); + assertEquals(0, sources.get(0).asInt()); + assertEquals(3, sources.get(3).asInt()); + assertEquals(4, destinations.get(0).asInt()); + assertEquals(8, destinations.get(4).asInt()); + } + + @Test + void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingException { + // given + when(mockSession.get("coordinates")).thenReturn("[[8.695556, 49.392701]]"); + when(mockSession.get("sources")).thenReturn("[0]"); + when(mockSession.get("destinations")).thenReturn("[0]"); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(1, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldHandleLargeCoordinateArray() { + // given + String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309], [8.689981, 49.394522], [8.681502, 49.394791]]"; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(5, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + assertEquals(8.681502, result.get(4).get(0), 0.000001); + assertEquals(49.394791, result.get(4).get(1), 0.000001); + } +} \ No newline at end of file From 9695ebbd7c48bc91590c91418df3750edf49f45b Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 1 Jun 2025 13:47:24 +0200 Subject: [PATCH 07/17] fix: address sonarqube and copilot PR comments --- .../org/heigit/ors/benchmark/BenchmarkEnums.java | 12 +++++++----- .../ors/benchmark/MatrixAlgorithmLoadTest.java | 14 ++++++-------- .../ors/benchmark/MatrixAlgorithmLoadTestTest.java | 12 ++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 51fce91341..8d48605a33 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -9,6 +9,8 @@ public class BenchmarkEnums { public static final String PREFERENCE = "preference"; // Constant for recommended public static final String RECOMMENDED = "recommended"; + public static final String OPTIONS = "options"; + public enum TestUnit { DISTANCE, @@ -47,8 +49,8 @@ public Map getRequestParams() { return switch (this) { case ALGO_CH -> Map.of(PREFERENCE, RECOMMENDED); case ALGO_CORE -> Map.of(PREFERENCE, - RECOMMENDED, "options", Map.of("avoid_features", List.of("ferries"))); - case ALGO_LM_ASTAR -> Map.of(PREFERENCE, RECOMMENDED, "options", + RECOMMENDED, OPTIONS, Map.of("avoid_features", List.of("ferries"))); + case ALGO_LM_ASTAR -> Map.of(PREFERENCE, RECOMMENDED, OPTIONS, Map.of("avoid_polygons", Map.of("type", "Polygon", "coordinates", List.of(List.of(List.of(100.0, 100.0), List.of(100.001, 100.0), @@ -87,7 +89,7 @@ public static MatrixModes fromString(String value) { case "algodijkstra" -> ALGO_DIJKSTRA_MATRIX; case "algocore" -> ALGO_CORE_MATRIX; case "algorphast" -> ALGO_RPHAST_MATRIX; - default -> throw new IllegalArgumentException("Invalid directions mode: " + value); + default -> throw new IllegalArgumentException("Invalid matrix mode: " + value); }; } @@ -109,9 +111,9 @@ public Map getRequestParams() { return switch (this) { case ALGO_RPHAST_MATRIX -> Map.of(PREFERENCE, RECOMMENDED); case ALGO_CORE_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, "options", Map.of("dynamic_speeds", "true")); + RECOMMENDED, OPTIONS, Map.of("dynamic_speeds", "true")); case ALGO_DIJKSTRA_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, "options", List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); + RECOMMENDED, OPTIONS, List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); }; } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 902b3be1ec..9aa7082aa7 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -95,7 +95,7 @@ protected Stream createScenarios(boolean isParallel) { private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, MatrixModes mode, String profile) { String scenarioName = formatScenarioName(mode, profile, isParallel); - return createMatrixScenario(scenarioName, sourceFile, config, mode, profile) + return createMatrixScenario(scenarioName, sourceFile, mode, profile) .injectOpen(atOnceUsers(config.getNumConcurrentUsers())); } @@ -121,7 +121,7 @@ private String formatScenarioName(MatrixModes mode, String profile, boolean isPa * @param profile routing profile to test * @return ScenarioBuilder configured for matrix testing */ - private static ScenarioBuilder createMatrixScenario(String name, String sourceFile, Config config, + private static ScenarioBuilder createMatrixScenario(String name, String sourceFile, MatrixModes mode, String profile) { try { List> records = csv(sourceFile).readRecords(); @@ -135,7 +135,7 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi return scenario(name) .feed(targetRecords.iterator(), 1) .asLongAs(session -> remainingRecords.decrementAndGet() >= 0) - .on(exec(createRequest(name, config, mode, profile))); + .on(exec(createRequest(name, mode, profile))); } catch (IllegalStateException e) { logger.error("Error building scenario: ", e); @@ -148,16 +148,15 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi * Creates an HTTP request action for the matrix API endpoint. * * @param name request name for identification in test results - * @param config test configuration * @param mode matrix calculation mode * @param profile routing profile * @return HttpRequestActionBuilder configured for matrix API calls */ - private static HttpRequestActionBuilder createRequest(String name, Config config, MatrixModes mode, + private static HttpRequestActionBuilder createRequest(String name, MatrixModes mode, String profile) { return http(name) .post("/v2/matrix/" + profile) - .body(StringBody(session -> createRequestBody(session, config, mode))) + .body(StringBody(session -> createRequestBody(session, mode))) .asJson() .check(status().is(200)); } @@ -166,12 +165,11 @@ private static HttpRequestActionBuilder createRequest(String name, Config config * Creates the JSON request body for matrix API calls from CSV session data. * * @param session Gatling session containing CSV row data - * @param config test configuration * @param mode matrix calculation mode providing additional parameters * @return JSON string representation of the request body * @throws RequestBodyCreationException if JSON serialization fails */ - static String createRequestBody(Session session, Config config, MatrixModes mode) { + static String createRequestBody(Session session, MatrixModes mode) { try { // Get the data from the CSV row String coordinatesStr = (String) session.get("coordinates"); diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java index 9c880e3b47..e26559a505 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -42,7 +42,7 @@ void setUp() { @Test void createRequestBody_ShouldCreateValidJson() throws JsonProcessingException { // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); // then JsonNode json = objectMapper.readTree(result); @@ -55,7 +55,7 @@ void createRequestBody_ShouldCreateValidJson() throws JsonProcessingException { @Test void createRequestBody_ShouldIncludeCorrectLocations() throws JsonProcessingException { // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -72,7 +72,7 @@ void createRequestBody_ShouldIncludeCorrectLocations() throws JsonProcessingExce @Test void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonProcessingException { // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -213,7 +213,7 @@ void createRequestBody_WithDifferentMatrixMode() throws JsonProcessingException "options", Map.of("avoid_features", Arrays.asList("highways")))); // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -228,7 +228,7 @@ void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonPr when(mockSession.get("destinations")).thenReturn("[4, 5, 6, 7, 8]"); // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -251,7 +251,7 @@ void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingExcep when(mockSession.get("destinations")).thenReturn("[0]"); // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then From 46341ca3eb3b6ff18995f3a11296edea5521e416 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 1 Jun 2025 16:59:31 +0200 Subject: [PATCH 08/17] fix: fix csv reading structure --- .../heigit/ors/benchmark/BenchmarkEnums.java | 14 +- .../java/org/heigit/ors/benchmark/Config.java | 2 +- .../benchmark/MatrixAlgorithmLoadTest.java | 148 +++++-------- .../MatrixAlgorithmLoadTestTest.java | 209 +++++++++++------- 4 files changed, 187 insertions(+), 186 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 8d48605a33..404ffcd72f 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -7,6 +7,8 @@ public class BenchmarkEnums { // Constant for preference public static final String PREFERENCE = "preference"; + public static final String METRICS = "metrics"; + public static final String DURATION = "duration"; // Constant for recommended public static final String RECOMMENDED = "recommended"; public static final String OPTIONS = "options"; @@ -95,7 +97,7 @@ public static MatrixModes fromString(String value) { public List getProfiles() { return switch (this) { - case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car", "driving-hgv", "cycling-regular", "foot-walking"); + case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car");//, "driving-hgv", "cycling-regular", "foot-walking"); }; } /** @@ -109,11 +111,11 @@ public List getProfiles() { */ public Map getRequestParams() { return switch (this) { - case ALGO_RPHAST_MATRIX -> Map.of(PREFERENCE, RECOMMENDED); - case ALGO_CORE_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, OPTIONS, Map.of("dynamic_speeds", "true")); - case ALGO_DIJKSTRA_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, OPTIONS, List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); + case ALGO_RPHAST_MATRIX -> Map.of(METRICS, List.of(DURATION)); + case ALGO_CORE_MATRIX -> Map.of(METRICS, + List.of(DURATION), OPTIONS, Map.of("dynamic_speeds", "true")); + case ALGO_DIJKSTRA_MATRIX -> Map.of(METRICS, + List.of(DURATION), OPTIONS, Map.of("dynamic_speeds", "false", "avoid_features", List.of("ferries"))); }; } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java index 73e9a0a024..278dfdb806 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java @@ -156,7 +156,7 @@ public List getDirectionsModes() { public List getMatrixModes() { return matrixModes.isEmpty() ? List.of(ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX) - : directionsModes.stream() + : matrixModes.stream() .map(MatrixModes::fromString) .toList(); } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 9aa7082aa7..51581e8a15 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -1,6 +1,7 @@ package org.heigit.ors.benchmark; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import io.gatling.javaapi.core.PopulationBuilder; import io.gatling.javaapi.core.ScenarioBuilder; import io.gatling.javaapi.core.Session; @@ -11,6 +12,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -116,7 +118,6 @@ private String formatScenarioName(MatrixModes mode, String profile, boolean isPa * * @param name descriptive name for the scenario * @param sourceFile path to CSV file containing test coordinates - * @param config test configuration parameters * @param mode matrix calculation mode to test * @param profile routing profile to test * @return ScenarioBuilder configured for matrix testing @@ -156,6 +157,7 @@ private static HttpRequestActionBuilder createRequest(String name, MatrixModes m String profile) { return http(name) .post("/v2/matrix/" + profile) + .header("Accept", "application/json;charset=UTF-8") .body(StringBody(session -> createRequestBody(session, mode))) .asJson() .check(status().is(200)); @@ -163,110 +165,70 @@ private static HttpRequestActionBuilder createRequest(String name, MatrixModes m /** * Creates the JSON request body for matrix API calls from CSV session data. - * + * * @param session Gatling session containing CSV row data * @param mode matrix calculation mode providing additional parameters * @return JSON string representation of the request body - * @throws RequestBodyCreationException if JSON serialization fails + * @throws RequestBodyCreationException if JSON serialization fails or data is missing */ static String createRequestBody(Session session, MatrixModes mode) { try { - // Get the data from the CSV row - String coordinatesStr = (String) session.get("coordinates"); - String sourcesStr = (String) session.get("sources"); - String destinationsStr = (String) session.get("destinations"); - - Map requestBody = new java.util.HashMap<>(Map.of( - "locations", parseCoordinatesFromString(coordinatesStr), - "sources", parseIntegerArrayFromString(sourcesStr), - "destinations", parseIntegerArrayFromString(destinationsStr))); - - requestBody.putAll(mode.getRequestParams()); - return objectMapper.writeValueAsString(requestBody); - } catch (JsonProcessingException e) { - throw new RequestBodyCreationException("Failed to create request body", e); - } - } - - /** - * Parses coordinate pairs from CSV string format to nested list structure. - * - * Converts strings like "[[8.695556, 49.392701], [8.684623, 49.398284]]" - * into List> format expected by the matrix API. - * - * @param coordinatesStr string representation of coordinate array from CSV - * @return list of coordinate pairs as [longitude, latitude] arrays - * @throws RequestBodyCreationException if parsing fails or format is invalid - */ - static List> parseCoordinatesFromString(String coordinatesStr) { - try { - if (coordinatesStr == null || coordinatesStr.trim().isEmpty()) { - throw new RequestBodyCreationException("Coordinates string is null or empty"); + // 1) Retrieve the raw feeder values. Gatling will give us a List for each column. + @SuppressWarnings("unchecked") + List coordsList = (List) session.get("coordinates"); + @SuppressWarnings("unchecked") + List sourcesList = (List) session.get("sources"); + @SuppressWarnings("unchecked") + List destsList = (List) session.get("destinations"); + + // 2) Fail fast if any column is missing or empty + if (coordsList == null || coordsList.isEmpty()) { + throw new RequestBodyCreationException("'coordinates' field is missing or empty in session"); } - - // Remove quotes if present - String cleaned = coordinatesStr.trim(); - if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { - cleaned = cleaned.substring(1, cleaned.length() - 1); + if (sourcesList == null || sourcesList.isEmpty()) { + throw new RequestBodyCreationException("'sources' field is missing or empty in session"); } - - // Remove outer brackets - cleaned = cleaned.substring(2, cleaned.length() - 2); - String[] coordinatePairs = cleaned.split("\\], \\["); - - List> locations = new ArrayList<>(); - for (String pair : coordinatePairs) { - String[] values = pair.split(", "); - if (values.length != 2) { - throw new RequestBodyCreationException("Invalid coordinate pair: " + pair); - } - double lon = Double.parseDouble(values[0]); - double lat = Double.parseDouble(values[1]); - locations.add(List.of(lon, lat)); - } - return locations; - } catch (Exception e) { - throw new RequestBodyCreationException("Failed to parse coordinates: " + coordinatesStr, e); - } - } - - /** - * Parses integer arrays from CSV string format. - * - * Converts strings like "[0, 1, 2]" into List format - * for sources and destinations parameters. - * - * @param arrayStr string representation of integer array from CSV - * @return list of integers - * @throws RequestBodyCreationException if parsing fails or format is invalid - */ - static List parseIntegerArrayFromString(String arrayStr) { - try { - if (arrayStr == null || arrayStr.trim().isEmpty()) { - throw new RequestBodyCreationException("Array string is null or empty"); - } - - // Remove quotes if present - String cleaned = arrayStr.trim(); - if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { - cleaned = cleaned.substring(1, cleaned.length() - 1); + if (destsList == null || destsList.isEmpty()) { + throw new RequestBodyCreationException("'destinations' field is missing or empty in session"); } - // Remove brackets - cleaned = cleaned.substring(1, cleaned.length() - 1); + // 3) The first element of each List is the actual JSON‐style value. + String coordinatesJson = coordsList.get(0); + String sourcesJson = sourcesList.get(0); + String destsJson = destsList.get(0); + + logger.debug( + "Raw CSV values → coordinatesJson: {}, sourcesJson: {}, destsJson: {}", + coordinatesJson, sourcesJson, destsJson + ); + + // 4) Let Jackson parse "[[lon, lat], [lon, lat], …]" into List> + List> locations = objectMapper.readValue( + coordinatesJson, new TypeReference>>() {} + ); + + // 5) Similarly parse "[0, 1, 2]" into List + List sources = objectMapper.readValue( + sourcesJson, new TypeReference>() {} + ); + List destinations = objectMapper.readValue( + destsJson, new TypeReference>() {} + ); + + // 6) Build the request body map and merge in any extra params from MatrixModes + Map requestBody = new HashMap<>(Map.of( + "locations", locations, + "sources", sources, + "destinations", destinations + )); + requestBody.putAll(mode.getRequestParams()); - if (cleaned.trim().isEmpty()) { - return new ArrayList<>(); - } + // 7) Serialize to JSON and return + return objectMapper.writeValueAsString(requestBody); - String[] values = cleaned.split(", "); - List result = new ArrayList<>(); - for (String value : values) { - result.add(Integer.parseInt(value.trim())); - } - return result; - } catch (Exception e) { - throw new RequestBodyCreationException("Failed to parse integer array: " + arrayStr, e); + } catch (JsonProcessingException e) { + // Jackson failed to parse or serialize + throw new RequestBodyCreationException("Failed to serialize request body to JSON", e); } } } diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java index e26559a505..4d0113f304 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -21,21 +21,20 @@ class MatrixAlgorithmLoadTestTest { private ObjectMapper objectMapper; private Session mockSession; - private Config mockConfig; private MatrixModes mockMode; @BeforeEach void setUp() { objectMapper = new ObjectMapper(); mockSession = mock(Session.class); - mockConfig = mock(Config.class); mockMode = mock(MatrixModes.class); - // Mock CSV data as it would appear in the session + // Mock CSV data as it would appear in the session after CSV feeder + // Each CSV column becomes a List in the session when(mockSession.get("coordinates")) - .thenReturn("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]"); - when(mockSession.get("sources")).thenReturn("[0, 1]"); - when(mockSession.get("destinations")).thenReturn("[2]"); + .thenReturn(Arrays.asList("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0, 1]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[2]")); when(mockMode.getRequestParams()).thenReturn(Map.of("preference", "recommended")); } @@ -88,121 +87,93 @@ void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonP } @Test - void parseCoordinatesFromString_ShouldParseValidCoordinates() { + void createRequestBody_ShouldThrowExceptionForMissingCoordinates() { // given - String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284]]"; + when(mockSession.get("coordinates")).thenReturn(null); - // when - List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); - - // then - assertEquals(2, result.size()); - assertEquals(8.695556, result.get(0).get(0), 0.000001); - assertEquals(49.392701, result.get(0).get(1), 0.000001); - assertEquals(8.684623, result.get(1).get(0), 0.000001); - assertEquals(49.398284, result.get(1).get(1), 0.000001); + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseCoordinatesFromString_ShouldHandleQuotedStrings() { + void createRequestBody_ShouldThrowExceptionForEmptyCoordinatesList() { // given - String coordinatesStr = "\"[[8.695556, 49.392701], [8.684623, 49.398284]]\""; - - // when - List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); - - // then - assertEquals(2, result.size()); - assertEquals(8.695556, result.get(0).get(0), 0.000001); - assertEquals(49.392701, result.get(0).get(1), 0.000001); - } + when(mockSession.get("coordinates")).thenReturn(Arrays.asList()); - @Test - void parseCoordinatesFromString_ShouldThrowExceptionForNullInput() { // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(null)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseCoordinatesFromString_ShouldThrowExceptionForEmptyInput() { + void createRequestBody_ShouldThrowExceptionForMissingSources() { + // given + when(mockSession.get("sources")).thenReturn(null); + // when & then - assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString("")); + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseCoordinatesFromString_ShouldThrowExceptionForInvalidCoordinatePair() { + void createRequestBody_ShouldThrowExceptionForEmptySourcesList() { // given - String invalidCoordinates = "[[8.695556], [8.684623, 49.398284]]"; + when(mockSession.get("sources")).thenReturn(Arrays.asList()); // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(invalidCoordinates)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldParseValidArray() { + void createRequestBody_ShouldThrowExceptionForMissingDestinations() { // given - String arrayStr = "[0, 1, 2]"; + when(mockSession.get("destinations")).thenReturn(null); - // when - List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); - - // then - assertEquals(3, result.size()); - assertEquals(0, result.get(0)); - assertEquals(1, result.get(1)); - assertEquals(2, result.get(2)); + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldHandleQuotedStrings() { + void createRequestBody_ShouldThrowExceptionForEmptyDestinationsList() { // given - String arrayStr = "\"[0, 1, 2]\""; + when(mockSession.get("destinations")).thenReturn(Arrays.asList()); - // when - List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); - - // then - assertEquals(3, result.size()); - assertEquals(0, result.get(0)); - assertEquals(1, result.get(1)); - assertEquals(2, result.get(2)); + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldReturnEmptyListForEmptyArray() { + void createRequestBody_ShouldThrowExceptionForInvalidCoordinatesJson() { // given - String arrayStr = "[]"; - - // when - List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); - - // then - assertTrue(result.isEmpty()); - } + when(mockSession.get("coordinates")).thenReturn(Arrays.asList("invalid json")); - @Test - void parseIntegerArrayFromString_ShouldThrowExceptionForNullInput() { // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(null)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldThrowExceptionForEmptyInput() { + void createRequestBody_ShouldThrowExceptionForInvalidSourcesJson() { + // given + when(mockSession.get("sources")).thenReturn(Arrays.asList("invalid json")); + // when & then - assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString("")); + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldThrowExceptionForInvalidInteger() { + void createRequestBody_ShouldThrowExceptionForInvalidDestinationsJson() { // given - String invalidArray = "[0, invalid, 2]"; + when(mockSession.get("destinations")).thenReturn(Arrays.asList("invalid json")); // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(invalidArray)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test @@ -224,8 +195,8 @@ void createRequestBody_WithDifferentMatrixMode() throws JsonProcessingException @Test void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonProcessingException { // given - when(mockSession.get("sources")).thenReturn("[0, 1, 2, 3]"); - when(mockSession.get("destinations")).thenReturn("[4, 5, 6, 7, 8]"); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0, 1, 2, 3]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[4, 5, 6, 7, 8]")); // when String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); @@ -246,9 +217,9 @@ void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonPr @Test void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingException { // given - when(mockSession.get("coordinates")).thenReturn("[[8.695556, 49.392701]]"); - when(mockSession.get("sources")).thenReturn("[0]"); - when(mockSession.get("destinations")).thenReturn("[0]"); + when(mockSession.get("coordinates")).thenReturn(Arrays.asList("[[8.695556, 49.392701]]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[0]")); // when String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); @@ -262,18 +233,84 @@ void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingExcep } @Test - void parseCoordinatesFromString_ShouldHandleLargeCoordinateArray() { + void createRequestBody_ShouldHandleLargeCoordinateArray() throws JsonProcessingException { // given - String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309], [8.689981, 49.394522], [8.681502, 49.394791]]"; + when(mockSession.get("coordinates")).thenReturn(Arrays.asList( + "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309], [8.689981, 49.394522], [8.681502, 49.394791]]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0, 1, 2]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[3, 4]")); // when - List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); // then - assertEquals(5, result.size()); - assertEquals(8.695556, result.get(0).get(0), 0.000001); - assertEquals(49.392701, result.get(0).get(1), 0.000001); - assertEquals(8.681502, result.get(4).get(0), 0.000001); - assertEquals(49.394791, result.get(4).get(1), 0.000001); + JsonNode locations = json.get("locations"); + assertEquals(5, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + assertEquals(8.681502, locations.get(4).get(0).asDouble(), 0.000001); + assertEquals(49.394791, locations.get(4).get(1).asDouble(), 0.000001); + } + + @Test + void createRequestBody_ShouldHandleEmptyArrays() throws JsonProcessingException { + // given + when(mockSession.get("coordinates")).thenReturn(Arrays.asList("[]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[]")); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(0, locations.size()); + assertEquals(0, sources.size()); + assertEquals(0, destinations.size()); + } + + @Test + void createRequestBody_ShouldMergeMatrixModeParameters() throws JsonProcessingException { + // given + when(mockMode.getRequestParams()).thenReturn(Map.of( + "preference", "recommended", + "units", "m", + "metrics", Arrays.asList("distance", "duration"))); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + assertEquals("recommended", json.get("preference").asText()); + assertEquals("m", json.get("units").asText()); + assertThat(json.get("metrics")).isNotNull(); + assertEquals(2, json.get("metrics").size()); + assertEquals("distance", json.get("metrics").get(0).asText()); + assertEquals("duration", json.get("metrics").get(1).asText()); + } + + @Test + void createRequestBody_ShouldHandleNestedCoordinates() throws JsonProcessingException { + // given - coordinates with high precision + when(mockSession.get("coordinates")).thenReturn(Arrays.asList( + "[[8.695556789, 49.392701123], [8.684623456, 49.398284789]]")); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(2, locations.size()); + assertEquals(8.695556789, locations.get(0).get(0).asDouble(), 0.000000001); + assertEquals(49.392701123, locations.get(0).get(1).asDouble(), 0.000000001); + assertEquals(8.684623456, locations.get(1).get(0).asDouble(), 0.000000001); + assertEquals(49.398284789, locations.get(1).get(1).asDouble(), 0.000000001); } } \ No newline at end of file From eaf55d958cc5462fb5f4a9294262ca7275771f32 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 16:20:41 +0200 Subject: [PATCH 09/17] feat: add matrix benchmark enums --- .../heigit/ors/benchmark/BenchmarkEnums.java | 44 +++++++++++++++++++ .../ors/benchmark/BenchmarkEnumsTest.java | 38 ++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 4c5864912f..51fce91341 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -73,4 +73,48 @@ public String getValue() { } } + /** + * Enum representing different matrix modes for benchmarking. + * getRequestParams() provides the parameters to trigger each matrix algorithm. + */ + public enum MatrixModes { + ALGO_DIJKSTRA_MATRIX, + ALGO_CORE_MATRIX, + ALGO_RPHAST_MATRIX; + + public static MatrixModes fromString(String value) { + return switch (value.toLowerCase()) { + case "algodijkstra" -> ALGO_DIJKSTRA_MATRIX; + case "algocore" -> ALGO_CORE_MATRIX; + case "algorphast" -> ALGO_RPHAST_MATRIX; + default -> throw new IllegalArgumentException("Invalid directions mode: " + value); + }; + } + + public List getProfiles() { + return switch (this) { + case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car", "driving-hgv", "cycling-regular", "foot-walking"); + }; + } + /** + * Returns the request parameters for the matrix algorithm. + * These parameters are used to trigger the specific matrix algorithm. + * This is not great as we have to maintain this in multiple places, + * at the moment this is the only way to trigger the algorithms. + * What would be better is to have a common interface for the algorithms, + * but that would require a larger refactor of the codebase. + * @return a map of request parameters + */ + public Map getRequestParams() { + return switch (this) { + case ALGO_RPHAST_MATRIX -> Map.of(PREFERENCE, RECOMMENDED); + case ALGO_CORE_MATRIX -> Map.of(PREFERENCE, + RECOMMENDED, "options", Map.of("dynamic_speeds", "true")); + case ALGO_DIJKSTRA_MATRIX -> Map.of(PREFERENCE, + RECOMMENDED, "options", List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); + + }; + } + } + } diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java index a243611701..2d51ed12be 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java @@ -52,4 +52,42 @@ void testRangeTypeGetValue() { assertEquals("time", BenchmarkEnums.RangeType.TIME.getValue()); assertEquals("distance", BenchmarkEnums.RangeType.DISTANCE.getValue()); } + + @Test + void testMatrixModesFromString() { + assertEquals(BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX, BenchmarkEnums.MatrixModes.fromString("algodijkstra")); + assertEquals(BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX, BenchmarkEnums.MatrixModes.fromString("algocore")); + assertEquals(BenchmarkEnums.MatrixModes.ALGO_RPHAST_MATRIX, BenchmarkEnums.MatrixModes.fromString("algorphast")); + + Throwable exception = assertThrows(IllegalArgumentException.class, () -> BenchmarkEnums.MatrixModes.fromString("invalid")); + assertTrue(exception instanceof IllegalArgumentException); + } + + @Test + void testMatrixModesGetDefaultProfiles() { + List dijkstraProfiles = BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX.getProfiles(); + assertTrue(dijkstraProfiles.contains("driving-car")); + assertTrue(dijkstraProfiles.contains("foot-walking")); + assertEquals(4, dijkstraProfiles.size()); + + List coreProfiles = BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX.getProfiles(); + assertTrue(coreProfiles.contains("driving-car")); + assertTrue(coreProfiles.contains("foot-walking")); + assertEquals(4, coreProfiles.size()); + } + + @Test + void testMatrixModesGetRequestParams() { + Map rphastParams = BenchmarkEnums.MatrixModes.ALGO_RPHAST_MATRIX.getRequestParams(); + assertEquals("recommended", rphastParams.get("preference")); + assertEquals(1, rphastParams.size()); + + Map coreParams = BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX.getRequestParams(); + assertEquals("recommended", coreParams.get("preference")); + assertTrue(coreParams.get("options") instanceof Map); + + Map dijkstraParams = BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX.getRequestParams(); + assertEquals("recommended", dijkstraParams.get("preference")); + assertTrue(dijkstraParams.get("options") instanceof List); + } } From 9f340462a1305e19158b0519e8d93d111d9be904 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 17:01:15 +0200 Subject: [PATCH 10/17] fix: Update Loadtests and config to reflect matrix properly --- .../java/org/heigit/ors/benchmark/Config.java | 20 +- .../benchmark/MatrixAlgorithmLoadTest.java | 196 +++++++++++++++--- .../RequestBodyCreationException.java | 4 + 3 files changed, 190 insertions(+), 30 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java index fdae9d2070..73e9a0a024 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java @@ -1,6 +1,7 @@ package org.heigit.ors.benchmark; import org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; import org.heigit.ors.benchmark.BenchmarkEnums.TestUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -9,6 +10,7 @@ import java.util.List; import static org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes.*; +import static org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes.*; public class Config { private static final Logger logger = LoggerFactory.getLogger(Config.class); @@ -28,7 +30,8 @@ public class Config { private final boolean parallelExecution; private final TestUnit testUnit; private final List sourceFiles; - private final List modes; + private final List directionsModes; + private final List matrixModes; private final List ranges; public Config() { @@ -48,8 +51,8 @@ public Config() { this.testUnit = TestUnit.fromString(getSystemProperty("test_unit", "distance")); this.sourceFiles = parseCommaSeparatedStringToStrings(getSystemProperty("source_files", "")); this.ranges = parseCommaSeparatedStringToInts(this.range); - this.modes = parseCommaSeparatedStringToStrings(getSystemProperty("modes", "")); - } + this.directionsModes = parseCommaSeparatedStringToStrings(getSystemProperty("modes", "")); + this.matrixModes = parseCommaSeparatedStringToStrings(getSystemProperty("matrix_modes", ""));} private String getSystemProperty(String key, String defaultValue) { String value = System.getProperty(key) != null ? System.getProperty(key) : defaultValue; @@ -145,9 +148,16 @@ public List getRanges() { } public List getDirectionsModes() { - return modes.isEmpty() ? List.of(ALGO_CH, ALGO_CORE, ALGO_LM_ASTAR) - : modes.stream() + return directionsModes.isEmpty() ? List.of(ALGO_CH, ALGO_CORE, ALGO_LM_ASTAR) + : directionsModes.stream() .map(DirectionsModes::fromString) .toList(); } + + public List getMatrixModes() { + return matrixModes.isEmpty() ? List.of(ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX) + : directionsModes.stream() + .map(MatrixModes::fromString) + .toList(); + } } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 768645a968..902b3be1ec 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -5,7 +5,7 @@ import io.gatling.javaapi.core.ScenarioBuilder; import io.gatling.javaapi.core.Session; import io.gatling.javaapi.http.HttpRequestActionBuilder; -import org.heigit.ors.benchmark.BenchmarkEnums.DirectionsModes; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; import org.heigit.ors.benchmark.exceptions.RequestBodyCreationException; import org.heigit.ors.util.SourceUtils; import org.slf4j.LoggerFactory; @@ -20,16 +20,38 @@ import static io.gatling.javaapi.http.HttpDsl.http; import static io.gatling.javaapi.http.HttpDsl.status; +/** + * Load test implementation for OpenRouteService Matrix API using Gatling + * framework. + * + * This class performs load testing on the matrix endpoint by: + * - Reading matrix test data from CSV files containing coordinates, sources, + * and destinations + * - Creating HTTP requests to the /v2/matrix/{profile} endpoint + * - Testing different matrix calculation modes and routing profiles + * - Measuring response times and throughput under concurrent load + * + * The test data is expected to be in CSV format with columns: + * coordinates, sources, destinations, distances, profile + */ public class MatrixAlgorithmLoadTest extends AbstractLoadTest { static { logger = LoggerFactory.getLogger(MatrixAlgorithmLoadTest.class); } + /** + * Constructs a new MatrixAlgorithmLoadTest instance. + * Initializes the load test with configuration from the parent class. + */ public MatrixAlgorithmLoadTest() { super(); } + /** + * Logs configuration information specific to matrix load testing. + * Displays source files, concurrent users, and execution mode. + */ @Override protected void logConfigInfo() { logger.info("Initializing MatrixAlgorithmLoadTest:"); @@ -38,33 +60,69 @@ protected void logConfigInfo() { logger.info("- Execution mode: {}", config.isParallelExecution() ? "parallel" : "sequential"); } + /** + * Logs the type of test being performed. + */ @Override protected void logTestTypeInfo() { logger.info("Testing matrix"); } + /** + * Creates test scenarios for all combinations of matrix modes, source files, + * and profiles. + * + * @param isParallel whether scenarios should be executed in parallel + * @return stream of PopulationBuilder instances for each test scenario + */ @Override protected Stream createScenarios(boolean isParallel) { - return config.getDirectionsModes().stream() + return config.getMatrixModes().stream() .flatMap(mode -> config.getSourceFiles().stream() .flatMap(sourceFile -> mode.getProfiles().stream() .map(profile -> createScenarioWithInjection(sourceFile, isParallel, mode, profile)))); } - private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, DirectionsModes mode, + /** + * Creates a single test scenario with user injection configuration. + * + * @param sourceFile path to the CSV file containing test data + * @param isParallel whether the scenario runs in parallel mode + * @param mode the matrix calculation mode to test + * @param profile the routing profile to test + * @return PopulationBuilder configured with the specified parameters + */ + private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, MatrixModes mode, String profile) { String scenarioName = formatScenarioName(mode, profile, isParallel); return createMatrixScenario(scenarioName, sourceFile, config, mode, profile) .injectOpen(atOnceUsers(config.getNumConcurrentUsers())); } - private String formatScenarioName(DirectionsModes mode, String profile, boolean isParallel) { + /** + * Formats a descriptive name for the test scenario. + * + * @param mode the matrix calculation mode + * @param profile the routing profile + * @param isParallel whether the scenario runs in parallel + * @return formatted scenario name string + */ + private String formatScenarioName(MatrixModes mode, String profile, boolean isParallel) { return String.format("%s - %s - %s", isParallel ? "Parallel" : "Sequential", mode, profile); } + /** + * Creates a Gatling scenario for matrix load testing. + * + * @param name descriptive name for the scenario + * @param sourceFile path to CSV file containing test coordinates + * @param config test configuration parameters + * @param mode matrix calculation mode to test + * @param profile routing profile to test + * @return ScenarioBuilder configured for matrix testing + */ private static ScenarioBuilder createMatrixScenario(String name, String sourceFile, Config config, - DirectionsModes mode, String profile) { - + MatrixModes mode, String profile) { try { List> records = csv(sourceFile).readRecords(); List> targetRecords = SourceUtils.getRecordsByProfile(records, profile); @@ -86,7 +144,16 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi } } - private static HttpRequestActionBuilder createRequest(String name, Config config, DirectionsModes mode, + /** + * Creates an HTTP request action for the matrix API endpoint. + * + * @param name request name for identification in test results + * @param config test configuration + * @param mode matrix calculation mode + * @param profile routing profile + * @return HttpRequestActionBuilder configured for matrix API calls + */ + private static HttpRequestActionBuilder createRequest(String name, Config config, MatrixModes mode, String profile) { return http(name) .post("/v2/matrix/" + profile) @@ -95,12 +162,27 @@ private static HttpRequestActionBuilder createRequest(String name, Config config .check(status().is(200)); } - static String createRequestBody(Session session, Config config, DirectionsModes mode) { + /** + * Creates the JSON request body for matrix API calls from CSV session data. + * + * @param session Gatling session containing CSV row data + * @param config test configuration + * @param mode matrix calculation mode providing additional parameters + * @return JSON string representation of the request body + * @throws RequestBodyCreationException if JSON serialization fails + */ + static String createRequestBody(Session session, Config config, MatrixModes mode) { try { + // Get the data from the CSV row + String coordinatesStr = (String) session.get("coordinates"); + String sourcesStr = (String) session.get("sources"); + String destinationsStr = (String) session.get("destinations"); + Map requestBody = new java.util.HashMap<>(Map.of( - "locations", createLocationsListFromArrays(session, config), - "sources", List.of(0), - "destinations", List.of(1))); + "locations", parseCoordinatesFromString(coordinatesStr), + "sources", parseIntegerArrayFromString(sourcesStr), + "destinations", parseIntegerArrayFromString(destinationsStr))); + requestBody.putAll(mode.getRequestParams()); return objectMapper.writeValueAsString(requestBody); } catch (JsonProcessingException e) { @@ -108,21 +190,85 @@ static String createRequestBody(Session session, Config config, DirectionsModes } } - static List> createLocationsListFromArrays(Session session, Config config) { - List> locations = new ArrayList<>(); + /** + * Parses coordinate pairs from CSV string format to nested list structure. + * + * Converts strings like "[[8.695556, 49.392701], [8.684623, 49.398284]]" + * into List> format expected by the matrix API. + * + * @param coordinatesStr string representation of coordinate array from CSV + * @return list of coordinate pairs as [longitude, latitude] arrays + * @throws RequestBodyCreationException if parsing fails or format is invalid + */ + static List> parseCoordinatesFromString(String coordinatesStr) { + try { + if (coordinatesStr == null || coordinatesStr.trim().isEmpty()) { + throw new RequestBodyCreationException("Coordinates string is null or empty"); + } + + // Remove quotes if present + String cleaned = coordinatesStr.trim(); + if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { + cleaned = cleaned.substring(1, cleaned.length() - 1); + } + + // Remove outer brackets + cleaned = cleaned.substring(2, cleaned.length() - 2); + String[] coordinatePairs = cleaned.split("\\], \\["); + + List> locations = new ArrayList<>(); + for (String pair : coordinatePairs) { + String[] values = pair.split(", "); + if (values.length != 2) { + throw new RequestBodyCreationException("Invalid coordinate pair: " + pair); + } + double lon = Double.parseDouble(values[0]); + double lat = Double.parseDouble(values[1]); + locations.add(List.of(lon, lat)); + } + return locations; + } catch (Exception e) { + throw new RequestBodyCreationException("Failed to parse coordinates: " + coordinatesStr, e); + } + } + + /** + * Parses integer arrays from CSV string format. + * + * Converts strings like "[0, 1, 2]" into List format + * for sources and destinations parameters. + * + * @param arrayStr string representation of integer array from CSV + * @return list of integers + * @throws RequestBodyCreationException if parsing fails or format is invalid + */ + static List parseIntegerArrayFromString(String arrayStr) { try { - Double startLon = Double.valueOf((String) session.getList(config.getFieldStartLon()).get(0)); - Double startLat = Double.valueOf((String) session.getList(config.getFieldStartLat()).get(0)); - locations.add(List.of(startLon, startLat)); - Double endLon = Double.valueOf((String) session.getList(config.getFieldEndLon()).get(0)); - Double endLat = Double.valueOf((String) session.getList(config.getFieldEndLat()).get(0)); - locations.add(List.of(endLon, endLat)); - } catch (NumberFormatException e) { - String errorMessage = String.format( - "Failed to parse coordinate values in locations list at index %d. Original value could not be converted to double", - locations.size()); - throw new RequestBodyCreationException("Error processing coordinates: " + errorMessage, e); + if (arrayStr == null || arrayStr.trim().isEmpty()) { + throw new RequestBodyCreationException("Array string is null or empty"); + } + + // Remove quotes if present + String cleaned = arrayStr.trim(); + if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { + cleaned = cleaned.substring(1, cleaned.length() - 1); + } + + // Remove brackets + cleaned = cleaned.substring(1, cleaned.length() - 1); + + if (cleaned.trim().isEmpty()) { + return new ArrayList<>(); + } + + String[] values = cleaned.split(", "); + List result = new ArrayList<>(); + for (String value : values) { + result.add(Integer.parseInt(value.trim())); + } + return result; + } catch (Exception e) { + throw new RequestBodyCreationException("Failed to parse integer array: " + arrayStr, e); } - return locations; } } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java index d3550d93d1..d01a9eb149 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/exceptions/RequestBodyCreationException.java @@ -4,4 +4,8 @@ public class RequestBodyCreationException extends RuntimeException { public RequestBodyCreationException(String message, Throwable cause) { super(message, cause); } + + public RequestBodyCreationException(String message) { + super(message); + } } From ab8fc2614dd43e275f7eaa62453fe4615b16e0c4 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 25 May 2025 17:14:31 +0200 Subject: [PATCH 11/17] feat: add testing for load test --- .../MatrixAlgorithmLoadTestTest.java | 279 ++++++++++++++++++ 1 file changed, 279 insertions(+) create mode 100644 ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java new file mode 100644 index 0000000000..9c880e3b47 --- /dev/null +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -0,0 +1,279 @@ +package org.heigit.ors.benchmark; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.gatling.javaapi.core.Session; +import org.heigit.ors.benchmark.BenchmarkEnums.MatrixModes; +import org.heigit.ors.benchmark.exceptions.RequestBodyCreationException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MatrixAlgorithmLoadTestTest { + private ObjectMapper objectMapper; + private Session mockSession; + private Config mockConfig; + private MatrixModes mockMode; + + @BeforeEach + void setUp() { + objectMapper = new ObjectMapper(); + mockSession = mock(Session.class); + mockConfig = mock(Config.class); + mockMode = mock(MatrixModes.class); + + // Mock CSV data as it would appear in the session + when(mockSession.get("coordinates")) + .thenReturn("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]"); + when(mockSession.get("sources")).thenReturn("[0, 1]"); + when(mockSession.get("destinations")).thenReturn("[2]"); + when(mockMode.getRequestParams()).thenReturn(Map.of("preference", "recommended")); + } + + @Test + void createRequestBody_ShouldCreateValidJson() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + + // then + JsonNode json = objectMapper.readTree(result); + assertThat(json.get("locations")).isNotNull(); + assertThat(json.get("sources")).isNotNull(); + assertThat(json.get("destinations")).isNotNull(); + assertThat(json.get("preference").asText()).isEqualTo("recommended"); + } + + @Test + void createRequestBody_ShouldIncludeCorrectLocations() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(3, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + assertEquals(8.684623, locations.get(1).get(0).asDouble(), 0.000001); + assertEquals(49.398284, locations.get(1).get(1).asDouble(), 0.000001); + assertEquals(8.705916, locations.get(2).get(0).asDouble(), 0.000001); + assertEquals(49.406309, locations.get(2).get(1).asDouble(), 0.000001); + } + + @Test + void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonProcessingException { + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(2, sources.size()); + assertEquals(0, sources.get(0).asInt()); + assertEquals(1, sources.get(1).asInt()); + + assertEquals(1, destinations.size()); + assertEquals(2, destinations.get(0).asInt()); + } + + @Test + void parseCoordinatesFromString_ShouldParseValidCoordinates() { + // given + String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284]]"; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(2, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + assertEquals(8.684623, result.get(1).get(0), 0.000001); + assertEquals(49.398284, result.get(1).get(1), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldHandleQuotedStrings() { + // given + String coordinatesStr = "\"[[8.695556, 49.392701], [8.684623, 49.398284]]\""; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(2, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForNullInput() { + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(null)); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForEmptyInput() { + // when & then + assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString("")); + } + + @Test + void parseCoordinatesFromString_ShouldThrowExceptionForInvalidCoordinatePair() { + // given + String invalidCoordinates = "[[8.695556], [8.684623, 49.398284]]"; + + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(invalidCoordinates)); + } + + @Test + void parseIntegerArrayFromString_ShouldParseValidArray() { + // given + String arrayStr = "[0, 1, 2]"; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertEquals(3, result.size()); + assertEquals(0, result.get(0)); + assertEquals(1, result.get(1)); + assertEquals(2, result.get(2)); + } + + @Test + void parseIntegerArrayFromString_ShouldHandleQuotedStrings() { + // given + String arrayStr = "\"[0, 1, 2]\""; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertEquals(3, result.size()); + assertEquals(0, result.get(0)); + assertEquals(1, result.get(1)); + assertEquals(2, result.get(2)); + } + + @Test + void parseIntegerArrayFromString_ShouldReturnEmptyListForEmptyArray() { + // given + String arrayStr = "[]"; + + // when + List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); + + // then + assertTrue(result.isEmpty()); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForNullInput() { + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(null)); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForEmptyInput() { + // when & then + assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString("")); + } + + @Test + void parseIntegerArrayFromString_ShouldThrowExceptionForInvalidInteger() { + // given + String invalidArray = "[0, invalid, 2]"; + + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(invalidArray)); + } + + @Test + void createRequestBody_WithDifferentMatrixMode() throws JsonProcessingException { + // given + when(mockMode.getRequestParams()).thenReturn(Map.of( + "preference", "fastest", + "options", Map.of("avoid_features", Arrays.asList("highways")))); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + assertEquals("fastest", json.get("preference").asText()); + assertThat(json.get("options")).isNotNull(); + } + + @Test + void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonProcessingException { + // given + when(mockSession.get("sources")).thenReturn("[0, 1, 2, 3]"); + when(mockSession.get("destinations")).thenReturn("[4, 5, 6, 7, 8]"); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(4, sources.size()); + assertEquals(5, destinations.size()); + assertEquals(0, sources.get(0).asInt()); + assertEquals(3, sources.get(3).asInt()); + assertEquals(4, destinations.get(0).asInt()); + assertEquals(8, destinations.get(4).asInt()); + } + + @Test + void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingException { + // given + when(mockSession.get("coordinates")).thenReturn("[[8.695556, 49.392701]]"); + when(mockSession.get("sources")).thenReturn("[0]"); + when(mockSession.get("destinations")).thenReturn("[0]"); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(1, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + } + + @Test + void parseCoordinatesFromString_ShouldHandleLargeCoordinateArray() { + // given + String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309], [8.689981, 49.394522], [8.681502, 49.394791]]"; + + // when + List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + + // then + assertEquals(5, result.size()); + assertEquals(8.695556, result.get(0).get(0), 0.000001); + assertEquals(49.392701, result.get(0).get(1), 0.000001); + assertEquals(8.681502, result.get(4).get(0), 0.000001); + assertEquals(49.394791, result.get(4).get(1), 0.000001); + } +} \ No newline at end of file From c9c62de210571ffcb1b7b53d0b476eb7354924a0 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 1 Jun 2025 13:47:24 +0200 Subject: [PATCH 12/17] fix: address sonarqube and copilot PR comments --- .../org/heigit/ors/benchmark/BenchmarkEnums.java | 12 +++++++----- .../ors/benchmark/MatrixAlgorithmLoadTest.java | 14 ++++++-------- .../ors/benchmark/MatrixAlgorithmLoadTestTest.java | 12 ++++++------ 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 51fce91341..8d48605a33 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -9,6 +9,8 @@ public class BenchmarkEnums { public static final String PREFERENCE = "preference"; // Constant for recommended public static final String RECOMMENDED = "recommended"; + public static final String OPTIONS = "options"; + public enum TestUnit { DISTANCE, @@ -47,8 +49,8 @@ public Map getRequestParams() { return switch (this) { case ALGO_CH -> Map.of(PREFERENCE, RECOMMENDED); case ALGO_CORE -> Map.of(PREFERENCE, - RECOMMENDED, "options", Map.of("avoid_features", List.of("ferries"))); - case ALGO_LM_ASTAR -> Map.of(PREFERENCE, RECOMMENDED, "options", + RECOMMENDED, OPTIONS, Map.of("avoid_features", List.of("ferries"))); + case ALGO_LM_ASTAR -> Map.of(PREFERENCE, RECOMMENDED, OPTIONS, Map.of("avoid_polygons", Map.of("type", "Polygon", "coordinates", List.of(List.of(List.of(100.0, 100.0), List.of(100.001, 100.0), @@ -87,7 +89,7 @@ public static MatrixModes fromString(String value) { case "algodijkstra" -> ALGO_DIJKSTRA_MATRIX; case "algocore" -> ALGO_CORE_MATRIX; case "algorphast" -> ALGO_RPHAST_MATRIX; - default -> throw new IllegalArgumentException("Invalid directions mode: " + value); + default -> throw new IllegalArgumentException("Invalid matrix mode: " + value); }; } @@ -109,9 +111,9 @@ public Map getRequestParams() { return switch (this) { case ALGO_RPHAST_MATRIX -> Map.of(PREFERENCE, RECOMMENDED); case ALGO_CORE_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, "options", Map.of("dynamic_speeds", "true")); + RECOMMENDED, OPTIONS, Map.of("dynamic_speeds", "true")); case ALGO_DIJKSTRA_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, "options", List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); + RECOMMENDED, OPTIONS, List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); }; } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 902b3be1ec..9aa7082aa7 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -95,7 +95,7 @@ protected Stream createScenarios(boolean isParallel) { private PopulationBuilder createScenarioWithInjection(String sourceFile, boolean isParallel, MatrixModes mode, String profile) { String scenarioName = formatScenarioName(mode, profile, isParallel); - return createMatrixScenario(scenarioName, sourceFile, config, mode, profile) + return createMatrixScenario(scenarioName, sourceFile, mode, profile) .injectOpen(atOnceUsers(config.getNumConcurrentUsers())); } @@ -121,7 +121,7 @@ private String formatScenarioName(MatrixModes mode, String profile, boolean isPa * @param profile routing profile to test * @return ScenarioBuilder configured for matrix testing */ - private static ScenarioBuilder createMatrixScenario(String name, String sourceFile, Config config, + private static ScenarioBuilder createMatrixScenario(String name, String sourceFile, MatrixModes mode, String profile) { try { List> records = csv(sourceFile).readRecords(); @@ -135,7 +135,7 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi return scenario(name) .feed(targetRecords.iterator(), 1) .asLongAs(session -> remainingRecords.decrementAndGet() >= 0) - .on(exec(createRequest(name, config, mode, profile))); + .on(exec(createRequest(name, mode, profile))); } catch (IllegalStateException e) { logger.error("Error building scenario: ", e); @@ -148,16 +148,15 @@ private static ScenarioBuilder createMatrixScenario(String name, String sourceFi * Creates an HTTP request action for the matrix API endpoint. * * @param name request name for identification in test results - * @param config test configuration * @param mode matrix calculation mode * @param profile routing profile * @return HttpRequestActionBuilder configured for matrix API calls */ - private static HttpRequestActionBuilder createRequest(String name, Config config, MatrixModes mode, + private static HttpRequestActionBuilder createRequest(String name, MatrixModes mode, String profile) { return http(name) .post("/v2/matrix/" + profile) - .body(StringBody(session -> createRequestBody(session, config, mode))) + .body(StringBody(session -> createRequestBody(session, mode))) .asJson() .check(status().is(200)); } @@ -166,12 +165,11 @@ private static HttpRequestActionBuilder createRequest(String name, Config config * Creates the JSON request body for matrix API calls from CSV session data. * * @param session Gatling session containing CSV row data - * @param config test configuration * @param mode matrix calculation mode providing additional parameters * @return JSON string representation of the request body * @throws RequestBodyCreationException if JSON serialization fails */ - static String createRequestBody(Session session, Config config, MatrixModes mode) { + static String createRequestBody(Session session, MatrixModes mode) { try { // Get the data from the CSV row String coordinatesStr = (String) session.get("coordinates"); diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java index 9c880e3b47..e26559a505 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -42,7 +42,7 @@ void setUp() { @Test void createRequestBody_ShouldCreateValidJson() throws JsonProcessingException { // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); // then JsonNode json = objectMapper.readTree(result); @@ -55,7 +55,7 @@ void createRequestBody_ShouldCreateValidJson() throws JsonProcessingException { @Test void createRequestBody_ShouldIncludeCorrectLocations() throws JsonProcessingException { // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -72,7 +72,7 @@ void createRequestBody_ShouldIncludeCorrectLocations() throws JsonProcessingExce @Test void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonProcessingException { // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -213,7 +213,7 @@ void createRequestBody_WithDifferentMatrixMode() throws JsonProcessingException "options", Map.of("avoid_features", Arrays.asList("highways")))); // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -228,7 +228,7 @@ void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonPr when(mockSession.get("destinations")).thenReturn("[4, 5, 6, 7, 8]"); // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then @@ -251,7 +251,7 @@ void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingExcep when(mockSession.get("destinations")).thenReturn("[0]"); // when - String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockConfig, mockMode); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); JsonNode json = objectMapper.readTree(result); // then From 1c20680006bc48a97a3af998ed7ed617b6eba963 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Sun, 1 Jun 2025 16:59:31 +0200 Subject: [PATCH 13/17] fix: fix csv reading structure --- .../heigit/ors/benchmark/BenchmarkEnums.java | 14 +- .../java/org/heigit/ors/benchmark/Config.java | 2 +- .../benchmark/MatrixAlgorithmLoadTest.java | 148 +++++-------- .../MatrixAlgorithmLoadTestTest.java | 209 +++++++++++------- 4 files changed, 187 insertions(+), 186 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 8d48605a33..404ffcd72f 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -7,6 +7,8 @@ public class BenchmarkEnums { // Constant for preference public static final String PREFERENCE = "preference"; + public static final String METRICS = "metrics"; + public static final String DURATION = "duration"; // Constant for recommended public static final String RECOMMENDED = "recommended"; public static final String OPTIONS = "options"; @@ -95,7 +97,7 @@ public static MatrixModes fromString(String value) { public List getProfiles() { return switch (this) { - case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car", "driving-hgv", "cycling-regular", "foot-walking"); + case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car");//, "driving-hgv", "cycling-regular", "foot-walking"); }; } /** @@ -109,11 +111,11 @@ public List getProfiles() { */ public Map getRequestParams() { return switch (this) { - case ALGO_RPHAST_MATRIX -> Map.of(PREFERENCE, RECOMMENDED); - case ALGO_CORE_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, OPTIONS, Map.of("dynamic_speeds", "true")); - case ALGO_DIJKSTRA_MATRIX -> Map.of(PREFERENCE, - RECOMMENDED, OPTIONS, List.of(Map.of("dynamic_speeds", "false"), Map.of("avoid_features", List.of("ferries")))); + case ALGO_RPHAST_MATRIX -> Map.of(METRICS, List.of(DURATION)); + case ALGO_CORE_MATRIX -> Map.of(METRICS, + List.of(DURATION), OPTIONS, Map.of("dynamic_speeds", "true")); + case ALGO_DIJKSTRA_MATRIX -> Map.of(METRICS, + List.of(DURATION), OPTIONS, Map.of("dynamic_speeds", "false", "avoid_features", List.of("ferries"))); }; } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java index 73e9a0a024..278dfdb806 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/Config.java @@ -156,7 +156,7 @@ public List getDirectionsModes() { public List getMatrixModes() { return matrixModes.isEmpty() ? List.of(ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX) - : directionsModes.stream() + : matrixModes.stream() .map(MatrixModes::fromString) .toList(); } diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 9aa7082aa7..51581e8a15 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -1,6 +1,7 @@ package org.heigit.ors.benchmark; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import io.gatling.javaapi.core.PopulationBuilder; import io.gatling.javaapi.core.ScenarioBuilder; import io.gatling.javaapi.core.Session; @@ -11,6 +12,7 @@ import org.slf4j.LoggerFactory; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -116,7 +118,6 @@ private String formatScenarioName(MatrixModes mode, String profile, boolean isPa * * @param name descriptive name for the scenario * @param sourceFile path to CSV file containing test coordinates - * @param config test configuration parameters * @param mode matrix calculation mode to test * @param profile routing profile to test * @return ScenarioBuilder configured for matrix testing @@ -156,6 +157,7 @@ private static HttpRequestActionBuilder createRequest(String name, MatrixModes m String profile) { return http(name) .post("/v2/matrix/" + profile) + .header("Accept", "application/json;charset=UTF-8") .body(StringBody(session -> createRequestBody(session, mode))) .asJson() .check(status().is(200)); @@ -163,110 +165,70 @@ private static HttpRequestActionBuilder createRequest(String name, MatrixModes m /** * Creates the JSON request body for matrix API calls from CSV session data. - * + * * @param session Gatling session containing CSV row data * @param mode matrix calculation mode providing additional parameters * @return JSON string representation of the request body - * @throws RequestBodyCreationException if JSON serialization fails + * @throws RequestBodyCreationException if JSON serialization fails or data is missing */ static String createRequestBody(Session session, MatrixModes mode) { try { - // Get the data from the CSV row - String coordinatesStr = (String) session.get("coordinates"); - String sourcesStr = (String) session.get("sources"); - String destinationsStr = (String) session.get("destinations"); - - Map requestBody = new java.util.HashMap<>(Map.of( - "locations", parseCoordinatesFromString(coordinatesStr), - "sources", parseIntegerArrayFromString(sourcesStr), - "destinations", parseIntegerArrayFromString(destinationsStr))); - - requestBody.putAll(mode.getRequestParams()); - return objectMapper.writeValueAsString(requestBody); - } catch (JsonProcessingException e) { - throw new RequestBodyCreationException("Failed to create request body", e); - } - } - - /** - * Parses coordinate pairs from CSV string format to nested list structure. - * - * Converts strings like "[[8.695556, 49.392701], [8.684623, 49.398284]]" - * into List> format expected by the matrix API. - * - * @param coordinatesStr string representation of coordinate array from CSV - * @return list of coordinate pairs as [longitude, latitude] arrays - * @throws RequestBodyCreationException if parsing fails or format is invalid - */ - static List> parseCoordinatesFromString(String coordinatesStr) { - try { - if (coordinatesStr == null || coordinatesStr.trim().isEmpty()) { - throw new RequestBodyCreationException("Coordinates string is null or empty"); + // 1) Retrieve the raw feeder values. Gatling will give us a List for each column. + @SuppressWarnings("unchecked") + List coordsList = (List) session.get("coordinates"); + @SuppressWarnings("unchecked") + List sourcesList = (List) session.get("sources"); + @SuppressWarnings("unchecked") + List destsList = (List) session.get("destinations"); + + // 2) Fail fast if any column is missing or empty + if (coordsList == null || coordsList.isEmpty()) { + throw new RequestBodyCreationException("'coordinates' field is missing or empty in session"); } - - // Remove quotes if present - String cleaned = coordinatesStr.trim(); - if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { - cleaned = cleaned.substring(1, cleaned.length() - 1); + if (sourcesList == null || sourcesList.isEmpty()) { + throw new RequestBodyCreationException("'sources' field is missing or empty in session"); } - - // Remove outer brackets - cleaned = cleaned.substring(2, cleaned.length() - 2); - String[] coordinatePairs = cleaned.split("\\], \\["); - - List> locations = new ArrayList<>(); - for (String pair : coordinatePairs) { - String[] values = pair.split(", "); - if (values.length != 2) { - throw new RequestBodyCreationException("Invalid coordinate pair: " + pair); - } - double lon = Double.parseDouble(values[0]); - double lat = Double.parseDouble(values[1]); - locations.add(List.of(lon, lat)); - } - return locations; - } catch (Exception e) { - throw new RequestBodyCreationException("Failed to parse coordinates: " + coordinatesStr, e); - } - } - - /** - * Parses integer arrays from CSV string format. - * - * Converts strings like "[0, 1, 2]" into List format - * for sources and destinations parameters. - * - * @param arrayStr string representation of integer array from CSV - * @return list of integers - * @throws RequestBodyCreationException if parsing fails or format is invalid - */ - static List parseIntegerArrayFromString(String arrayStr) { - try { - if (arrayStr == null || arrayStr.trim().isEmpty()) { - throw new RequestBodyCreationException("Array string is null or empty"); - } - - // Remove quotes if present - String cleaned = arrayStr.trim(); - if (cleaned.startsWith("\"") && cleaned.endsWith("\"")) { - cleaned = cleaned.substring(1, cleaned.length() - 1); + if (destsList == null || destsList.isEmpty()) { + throw new RequestBodyCreationException("'destinations' field is missing or empty in session"); } - // Remove brackets - cleaned = cleaned.substring(1, cleaned.length() - 1); + // 3) The first element of each List is the actual JSON‐style value. + String coordinatesJson = coordsList.get(0); + String sourcesJson = sourcesList.get(0); + String destsJson = destsList.get(0); + + logger.debug( + "Raw CSV values → coordinatesJson: {}, sourcesJson: {}, destsJson: {}", + coordinatesJson, sourcesJson, destsJson + ); + + // 4) Let Jackson parse "[[lon, lat], [lon, lat], …]" into List> + List> locations = objectMapper.readValue( + coordinatesJson, new TypeReference>>() {} + ); + + // 5) Similarly parse "[0, 1, 2]" into List + List sources = objectMapper.readValue( + sourcesJson, new TypeReference>() {} + ); + List destinations = objectMapper.readValue( + destsJson, new TypeReference>() {} + ); + + // 6) Build the request body map and merge in any extra params from MatrixModes + Map requestBody = new HashMap<>(Map.of( + "locations", locations, + "sources", sources, + "destinations", destinations + )); + requestBody.putAll(mode.getRequestParams()); - if (cleaned.trim().isEmpty()) { - return new ArrayList<>(); - } + // 7) Serialize to JSON and return + return objectMapper.writeValueAsString(requestBody); - String[] values = cleaned.split(", "); - List result = new ArrayList<>(); - for (String value : values) { - result.add(Integer.parseInt(value.trim())); - } - return result; - } catch (Exception e) { - throw new RequestBodyCreationException("Failed to parse integer array: " + arrayStr, e); + } catch (JsonProcessingException e) { + // Jackson failed to parse or serialize + throw new RequestBodyCreationException("Failed to serialize request body to JSON", e); } } } diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java index e26559a505..4d0113f304 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -21,21 +21,20 @@ class MatrixAlgorithmLoadTestTest { private ObjectMapper objectMapper; private Session mockSession; - private Config mockConfig; private MatrixModes mockMode; @BeforeEach void setUp() { objectMapper = new ObjectMapper(); mockSession = mock(Session.class); - mockConfig = mock(Config.class); mockMode = mock(MatrixModes.class); - // Mock CSV data as it would appear in the session + // Mock CSV data as it would appear in the session after CSV feeder + // Each CSV column becomes a List in the session when(mockSession.get("coordinates")) - .thenReturn("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]"); - when(mockSession.get("sources")).thenReturn("[0, 1]"); - when(mockSession.get("destinations")).thenReturn("[2]"); + .thenReturn(Arrays.asList("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0, 1]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[2]")); when(mockMode.getRequestParams()).thenReturn(Map.of("preference", "recommended")); } @@ -88,121 +87,93 @@ void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonP } @Test - void parseCoordinatesFromString_ShouldParseValidCoordinates() { + void createRequestBody_ShouldThrowExceptionForMissingCoordinates() { // given - String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284]]"; + when(mockSession.get("coordinates")).thenReturn(null); - // when - List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); - - // then - assertEquals(2, result.size()); - assertEquals(8.695556, result.get(0).get(0), 0.000001); - assertEquals(49.392701, result.get(0).get(1), 0.000001); - assertEquals(8.684623, result.get(1).get(0), 0.000001); - assertEquals(49.398284, result.get(1).get(1), 0.000001); + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseCoordinatesFromString_ShouldHandleQuotedStrings() { + void createRequestBody_ShouldThrowExceptionForEmptyCoordinatesList() { // given - String coordinatesStr = "\"[[8.695556, 49.392701], [8.684623, 49.398284]]\""; - - // when - List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); - - // then - assertEquals(2, result.size()); - assertEquals(8.695556, result.get(0).get(0), 0.000001); - assertEquals(49.392701, result.get(0).get(1), 0.000001); - } + when(mockSession.get("coordinates")).thenReturn(Arrays.asList()); - @Test - void parseCoordinatesFromString_ShouldThrowExceptionForNullInput() { // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(null)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseCoordinatesFromString_ShouldThrowExceptionForEmptyInput() { + void createRequestBody_ShouldThrowExceptionForMissingSources() { + // given + when(mockSession.get("sources")).thenReturn(null); + // when & then - assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString("")); + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseCoordinatesFromString_ShouldThrowExceptionForInvalidCoordinatePair() { + void createRequestBody_ShouldThrowExceptionForEmptySourcesList() { // given - String invalidCoordinates = "[[8.695556], [8.684623, 49.398284]]"; + when(mockSession.get("sources")).thenReturn(Arrays.asList()); // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseCoordinatesFromString(invalidCoordinates)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldParseValidArray() { + void createRequestBody_ShouldThrowExceptionForMissingDestinations() { // given - String arrayStr = "[0, 1, 2]"; + when(mockSession.get("destinations")).thenReturn(null); - // when - List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); - - // then - assertEquals(3, result.size()); - assertEquals(0, result.get(0)); - assertEquals(1, result.get(1)); - assertEquals(2, result.get(2)); + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldHandleQuotedStrings() { + void createRequestBody_ShouldThrowExceptionForEmptyDestinationsList() { // given - String arrayStr = "\"[0, 1, 2]\""; + when(mockSession.get("destinations")).thenReturn(Arrays.asList()); - // when - List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); - - // then - assertEquals(3, result.size()); - assertEquals(0, result.get(0)); - assertEquals(1, result.get(1)); - assertEquals(2, result.get(2)); + // when & then + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldReturnEmptyListForEmptyArray() { + void createRequestBody_ShouldThrowExceptionForInvalidCoordinatesJson() { // given - String arrayStr = "[]"; - - // when - List result = MatrixAlgorithmLoadTest.parseIntegerArrayFromString(arrayStr); - - // then - assertTrue(result.isEmpty()); - } + when(mockSession.get("coordinates")).thenReturn(Arrays.asList("invalid json")); - @Test - void parseIntegerArrayFromString_ShouldThrowExceptionForNullInput() { // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(null)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldThrowExceptionForEmptyInput() { + void createRequestBody_ShouldThrowExceptionForInvalidSourcesJson() { + // given + when(mockSession.get("sources")).thenReturn(Arrays.asList("invalid json")); + // when & then - assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString("")); + assertThrows(RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test - void parseIntegerArrayFromString_ShouldThrowExceptionForInvalidInteger() { + void createRequestBody_ShouldThrowExceptionForInvalidDestinationsJson() { // given - String invalidArray = "[0, invalid, 2]"; + when(mockSession.get("destinations")).thenReturn(Arrays.asList("invalid json")); // when & then assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.parseIntegerArrayFromString(invalidArray)); + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } @Test @@ -224,8 +195,8 @@ void createRequestBody_WithDifferentMatrixMode() throws JsonProcessingException @Test void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonProcessingException { // given - when(mockSession.get("sources")).thenReturn("[0, 1, 2, 3]"); - when(mockSession.get("destinations")).thenReturn("[4, 5, 6, 7, 8]"); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0, 1, 2, 3]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[4, 5, 6, 7, 8]")); // when String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); @@ -246,9 +217,9 @@ void createRequestBody_ShouldHandleComplexSourcesAndDestinations() throws JsonPr @Test void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingException { // given - when(mockSession.get("coordinates")).thenReturn("[[8.695556, 49.392701]]"); - when(mockSession.get("sources")).thenReturn("[0]"); - when(mockSession.get("destinations")).thenReturn("[0]"); + when(mockSession.get("coordinates")).thenReturn(Arrays.asList("[[8.695556, 49.392701]]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[0]")); // when String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); @@ -262,18 +233,84 @@ void createRequestBody_ShouldHandleSingleCoordinate() throws JsonProcessingExcep } @Test - void parseCoordinatesFromString_ShouldHandleLargeCoordinateArray() { + void createRequestBody_ShouldHandleLargeCoordinateArray() throws JsonProcessingException { // given - String coordinatesStr = "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309], [8.689981, 49.394522], [8.681502, 49.394791]]"; + when(mockSession.get("coordinates")).thenReturn(Arrays.asList( + "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309], [8.689981, 49.394522], [8.681502, 49.394791]]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[0, 1, 2]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[3, 4]")); // when - List> result = MatrixAlgorithmLoadTest.parseCoordinatesFromString(coordinatesStr); + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); // then - assertEquals(5, result.size()); - assertEquals(8.695556, result.get(0).get(0), 0.000001); - assertEquals(49.392701, result.get(0).get(1), 0.000001); - assertEquals(8.681502, result.get(4).get(0), 0.000001); - assertEquals(49.394791, result.get(4).get(1), 0.000001); + JsonNode locations = json.get("locations"); + assertEquals(5, locations.size()); + assertEquals(8.695556, locations.get(0).get(0).asDouble(), 0.000001); + assertEquals(49.392701, locations.get(0).get(1).asDouble(), 0.000001); + assertEquals(8.681502, locations.get(4).get(0).asDouble(), 0.000001); + assertEquals(49.394791, locations.get(4).get(1).asDouble(), 0.000001); + } + + @Test + void createRequestBody_ShouldHandleEmptyArrays() throws JsonProcessingException { + // given + when(mockSession.get("coordinates")).thenReturn(Arrays.asList("[]")); + when(mockSession.get("sources")).thenReturn(Arrays.asList("[]")); + when(mockSession.get("destinations")).thenReturn(Arrays.asList("[]")); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + JsonNode sources = json.get("sources"); + JsonNode destinations = json.get("destinations"); + + assertEquals(0, locations.size()); + assertEquals(0, sources.size()); + assertEquals(0, destinations.size()); + } + + @Test + void createRequestBody_ShouldMergeMatrixModeParameters() throws JsonProcessingException { + // given + when(mockMode.getRequestParams()).thenReturn(Map.of( + "preference", "recommended", + "units", "m", + "metrics", Arrays.asList("distance", "duration"))); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + assertEquals("recommended", json.get("preference").asText()); + assertEquals("m", json.get("units").asText()); + assertThat(json.get("metrics")).isNotNull(); + assertEquals(2, json.get("metrics").size()); + assertEquals("distance", json.get("metrics").get(0).asText()); + assertEquals("duration", json.get("metrics").get(1).asText()); + } + + @Test + void createRequestBody_ShouldHandleNestedCoordinates() throws JsonProcessingException { + // given - coordinates with high precision + when(mockSession.get("coordinates")).thenReturn(Arrays.asList( + "[[8.695556789, 49.392701123], [8.684623456, 49.398284789]]")); + + // when + String result = MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode); + JsonNode json = objectMapper.readTree(result); + + // then + JsonNode locations = json.get("locations"); + assertEquals(2, locations.size()); + assertEquals(8.695556789, locations.get(0).get(0).asDouble(), 0.000000001); + assertEquals(49.392701123, locations.get(0).get(1).asDouble(), 0.000000001); + assertEquals(8.684623456, locations.get(1).get(0).asDouble(), 0.000000001); + assertEquals(49.398284789, locations.get(1).get(1).asDouble(), 0.000000001); } } \ No newline at end of file From cba5e06db10c2f998701a3182413f9674f6020b5 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Mon, 9 Jun 2025 15:30:27 +0200 Subject: [PATCH 14/17] fix: Address sonarqube issues --- .../heigit/ors/benchmark/BenchmarkEnums.java | 2 +- .../benchmark/MatrixAlgorithmLoadTest.java | 1 - .../MatrixAlgorithmLoadTestTest.java | 121 ++++++------------ 3 files changed, 38 insertions(+), 86 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java index 404ffcd72f..d281cad0e4 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/BenchmarkEnums.java @@ -97,7 +97,7 @@ public static MatrixModes fromString(String value) { public List getProfiles() { return switch (this) { - case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car");//, "driving-hgv", "cycling-regular", "foot-walking"); + case ALGO_DIJKSTRA_MATRIX, ALGO_CORE_MATRIX, ALGO_RPHAST_MATRIX -> List.of("driving-car"); }; } /** diff --git a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java index 51581e8a15..e6645934d0 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTest.java @@ -11,7 +11,6 @@ import org.heigit.ors.util.SourceUtils; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java index 4d0113f304..9da93422b1 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/MatrixAlgorithmLoadTestTest.java @@ -8,6 +8,8 @@ import org.heigit.ors.benchmark.exceptions.RequestBodyCreationException; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import java.util.Arrays; import java.util.List; @@ -22,6 +24,14 @@ class MatrixAlgorithmLoadTestTest { private ObjectMapper objectMapper; private Session mockSession; private MatrixModes mockMode; + private static final List VALID_COORDINATES = + Arrays.asList( + "[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]" + ); + private static final List VALID_SOURCES = + Arrays.asList("[0, 1]"); + private static final List VALID_DESTINATIONS = + Arrays.asList("[2]"); @BeforeEach void setUp() { @@ -32,9 +42,9 @@ void setUp() { // Mock CSV data as it would appear in the session after CSV feeder // Each CSV column becomes a List in the session when(mockSession.get("coordinates")) - .thenReturn(Arrays.asList("[[8.695556, 49.392701], [8.684623, 49.398284], [8.705916, 49.406309]]")); - when(mockSession.get("sources")).thenReturn(Arrays.asList("[0, 1]")); - when(mockSession.get("destinations")).thenReturn(Arrays.asList("[2]")); + .thenReturn(VALID_COORDINATES); + when(mockSession.get("sources")).thenReturn(VALID_SOURCES); + when(mockSession.get("destinations")).thenReturn(VALID_DESTINATIONS); when(mockMode.getRequestParams()).thenReturn(Map.of("preference", "recommended")); } @@ -86,94 +96,37 @@ void createRequestBody_ShouldIncludeCorrectSourcesAndDestinations() throws JsonP assertEquals(2, destinations.get(0).asInt()); } - @Test - void createRequestBody_ShouldThrowExceptionForMissingCoordinates() { - // given - when(mockSession.get("coordinates")).thenReturn(null); - - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); - } - - @Test - void createRequestBody_ShouldThrowExceptionForEmptyCoordinatesList() { - // given - when(mockSession.get("coordinates")).thenReturn(Arrays.asList()); - - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); - } - - @Test - void createRequestBody_ShouldThrowExceptionForMissingSources() { - // given - when(mockSession.get("sources")).thenReturn(null); - - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); - } - - @Test - void createRequestBody_ShouldThrowExceptionForEmptySourcesList() { - // given - when(mockSession.get("sources")).thenReturn(Arrays.asList()); - - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); - } - - @Test - void createRequestBody_ShouldThrowExceptionForMissingDestinations() { - // given - when(mockSession.get("destinations")).thenReturn(null); - - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); + @ParameterizedTest(name = "createRequestBody throws if '{0}' list is empty") + @ValueSource(strings = { "coordinates", "sources", "destinations" }) + void createRequestBody_ShouldThrowExceptionForEmptyList(String emptyKey) { + // only override the one under test to be empty + when(mockSession.get(emptyKey)).thenReturn(List.of()); + + // verify that empty‐list triggers the exception + assertThrows( + RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode) + ); } - @Test - void createRequestBody_ShouldThrowExceptionForEmptyDestinationsList() { - // given - when(mockSession.get("destinations")).thenReturn(Arrays.asList()); - - // when & then + @ParameterizedTest(name = "createRequestBody should throw when '{0}' is missing") + @ValueSource(strings = { "coordinates", "sources", "destinations" }) + void createRequestBody_ShouldThrowExceptionForMissingRequiredSessionAttributes(String missingAttribute) { + when(mockSession.get(missingAttribute)).thenReturn(null); assertThrows(RequestBodyCreationException.class, () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); } - @Test - void createRequestBody_ShouldThrowExceptionForInvalidCoordinatesJson() { - // given - when(mockSession.get("coordinates")).thenReturn(Arrays.asList("invalid json")); + @ParameterizedTest(name = "createRequestBody throws when '{0}' contains invalid JSON") + @ValueSource(strings = { "coordinates", "sources", "destinations" }) + void createRequestBody_ShouldThrowExceptionForInvalidJson(String invalidKey) { + // override the one under test to invalid JSON + when(mockSession.get(invalidKey)).thenReturn(List.of("invalid json")); - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); - } - - @Test - void createRequestBody_ShouldThrowExceptionForInvalidSourcesJson() { - // given - when(mockSession.get("sources")).thenReturn(Arrays.asList("invalid json")); - - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); - } - - @Test - void createRequestBody_ShouldThrowExceptionForInvalidDestinationsJson() { - // given - when(mockSession.get("destinations")).thenReturn(Arrays.asList("invalid json")); - - // when & then - assertThrows(RequestBodyCreationException.class, - () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode)); + assertThrows( + RequestBodyCreationException.class, + () -> MatrixAlgorithmLoadTest.createRequestBody(mockSession, mockMode) + ); } @Test From b866c254de2ed5c6530dfba2f7e41a383ef9e6f7 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Mon, 9 Jun 2025 15:34:08 +0200 Subject: [PATCH 15/17] fix: remove profiles other than driving-car --- .../java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java index 2d51ed12be..63b6d03215 100644 --- a/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java +++ b/ors-benchmark/src/test/java/org/heigit/ors/benchmark/BenchmarkEnumsTest.java @@ -67,13 +67,7 @@ void testMatrixModesFromString() { void testMatrixModesGetDefaultProfiles() { List dijkstraProfiles = BenchmarkEnums.MatrixModes.ALGO_DIJKSTRA_MATRIX.getProfiles(); assertTrue(dijkstraProfiles.contains("driving-car")); - assertTrue(dijkstraProfiles.contains("foot-walking")); - assertEquals(4, dijkstraProfiles.size()); - - List coreProfiles = BenchmarkEnums.MatrixModes.ALGO_CORE_MATRIX.getProfiles(); - assertTrue(coreProfiles.contains("driving-car")); - assertTrue(coreProfiles.contains("foot-walking")); - assertEquals(4, coreProfiles.size()); + assertEquals(1, dijkstraProfiles.size()); } @Test From 1a7fe2bcc505563c1074b0c930526340d30f969f Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Thu, 19 Jun 2025 14:45:44 +0200 Subject: [PATCH 16/17] feat: change matrix generation, use single coordinate as seed --- .../generators/CoordinateGeneratorMatrix.java | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java b/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java index 2253e51668..a7c9dd4a0c 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java @@ -329,12 +329,15 @@ private boolean generateMatricesForProfile() { int currRetry = 0; List snappedCoordinates = new ArrayList<>(); + //First, generate a center coordinate for a matrix extent + double[] randomCoordinate = CoordinateGeneratorHelper.generateRandomPoint(extent); + double[] localExtent = createExtentAroundPoint(randomCoordinate, effectiveMaxDistance); while (snappedCoordinates.size() < targetNum && currRetry < maxRetries) { - List randomCoordinate = CoordinateGeneratorHelper.randomCoordinatesInExtent(1, - extent); + List matrixCoordinate = CoordinateGeneratorHelper.randomCoordinatesInExtent(1, + localExtent); // Snap the coordinates to the road network - List snappedCoordinate = coordinateSnapper.snapCoordinates(randomCoordinate, profile); + List snappedCoordinate = coordinateSnapper.snapCoordinates(matrixCoordinate, profile); snappedCoordinates.addAll(snappedCoordinate); currRetry++; } @@ -347,6 +350,30 @@ private boolean generateMatricesForProfile() { return processSnappedCoordinates(snappedCoordinates, effectiveMaxDistance); } + public static double[] createExtentAroundPoint(double[] coord, double maxExtentMeters) { + // Mean Earth radius in meters + final double R = 6_371_008.8; + + // Half the desired side length + double halfSide = maxExtentMeters / 2.0; + + // Convert center latitude to radians for the longitude calculation + double latRad = Math.toRadians(coord[1]); + + // Δ latitude in degrees: (distance / radius) × (180/π) + double deltaLat = Math.toDegrees(halfSide / R); + + // Δ longitude in degrees: (distance / (radius × cos(lat))) × (180/π) + double deltaLon = Math.toDegrees(halfSide / (R * Math.cos(latRad))); + + double minLat = coord[1] - deltaLat; + double maxLat = coord[1] + deltaLat; + double minLon = coord[0] - deltaLon; + double maxLon = coord[0] + deltaLon; + + return new double[] { minLon, minLat, maxLon, maxLat }; + } + private boolean processSnappedCoordinates(List snappedCoordinates, double maxDistance) { boolean addedNewMatrix = false; try { @@ -409,11 +436,11 @@ private boolean hasNearbyPoints(List row, List col, double m for (double[] rowCoord : row) { for (double[] colCoord : col) { double distance = CoordinateGeneratorHelper.calculateHaversineDistance(rowCoord, colCoord); - if (distance <= maxDistance) - return true; + if (distance > maxDistance) + return false; } } - return false; + return true; } /** From ea1bc9c5013d428a378eb902394f1ca6c7dfa670 Mon Sep 17 00:00:00 2001 From: Hendrik Leuschner Date: Thu, 19 Jun 2025 15:13:54 +0200 Subject: [PATCH 17/17] fix: use correct destination indices --- .../generators/CoordinateGeneratorMatrix.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java b/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java index a7c9dd4a0c..766bcafe36 100644 --- a/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java +++ b/ors-benchmark/src/main/java/org/heigit/ors/coordinates_generator/generators/CoordinateGeneratorMatrix.java @@ -480,7 +480,7 @@ private boolean rowsColsRouteable(List row, List col) { */ private boolean computeAndProcessMatrix(List snappedCoordinates) { int[] sources = java.util.stream.IntStream.range(0, numRows).toArray(); - int[] destinations = java.util.stream.IntStream.range(0, numCols).toArray(); + int[] destinations = java.util.stream.IntStream.range(numRows, numRows + numCols).toArray(); Optional fullMatrixResult = matrixCalculator.calculateAsymmetricMatrix( snappedCoordinates, sources, destinations, profile);