From bf927d516c1a2fe982909f2fea8ec58edbe5ac1a Mon Sep 17 00:00:00 2001 From: Bernd Hufmann Date: Mon, 16 Dec 2024 14:59:07 -0500 Subject: [PATCH] server.tests: Add tests for querying arrows using a Time Graph DP Signed-off-by: Bernd Hufmann --- .../TimeGraphDataProviderServiceTest.java | 191 +++++++++++++----- .../stubs/TgArrowsOutputResponseStub.java | 55 +++++ .../core/tests/stubs/TimeGraphArrowStub.java | 109 ++++++++++ .../rest/core/tests/utils/RestServerTest.java | 23 +++ 4 files changed, 323 insertions(+), 55 deletions(-) create mode 100644 trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TgArrowsOutputResponseStub.java create mode 100644 trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TimeGraphArrowStub.java diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/TimeGraphDataProviderServiceTest.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/TimeGraphDataProviderServiceTest.java index ce41f12de..22c938d28 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/TimeGraphDataProviderServiceTest.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/services/TimeGraphDataProviderServiceTest.java @@ -36,6 +36,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.model.views.QueryParameters; import org.eclipse.tracecompass.incubator.internal.trace.server.jersey.rest.core.services.DataProviderService; @@ -44,10 +45,12 @@ import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.OutputElementStyleStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.OutputStyleModelStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.StylesOutputResponseStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TgArrowsOutputResponseStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TgEntryModelStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TgStatesOutputResponseStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TgTooltipOutputResponseStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TgTreeOutputResponseStub; +import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TimeGraphArrowStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TimeGraphEntryStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TimeGraphModelStub; import org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs.TimeGraphRowStub; @@ -70,6 +73,8 @@ */ @SuppressWarnings({"null", "restriction"}) public class TimeGraphDataProviderServiceTest extends RestServerTest { + + private static final String THREAD_STATUS_DP_ID = "org.eclipse.tracecompass.internal.analysis.os.linux.core.threadstatus.ThreadStatusDataProvider"; private static final String DATA_PROVIDER_RESPONSE_FAILED_MSG = "There should be a positive response for the data provider"; private static final String MODEL_NULL_MSG = "The model is null, maybe the analysis did not run long enough?"; private static final int MAX_ITER = 40; @@ -159,73 +164,56 @@ public void testStyles() { @Test public void testStylesErrors() { ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesUstNotInitializedStub.getName(), sfContextSwitchesUstNotInitializedStub); + executePostErrorTests(exp, RestServerTest::getStylesEndpoint, CALL_STACK_DATAPROVIDER_ID, false); + } + + /** + * Tests querying arrows for a time graph data provider + * + * @throws InterruptedException + * if such exception happens + */ + @Test + public void testArrows() throws InterruptedException { + ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesKernelNotInitializedStub.getName(), sfContextSwitchesKernelNotInitializedStub); + + Set entries = loadDataProvider(exp, THREAD_STATUS_DP_ID); + WebTarget arrowsEndpoint = getArrowsEndpoint(exp.getUUID().toString(), THREAD_STATUS_DP_ID); - // Invalid UUID string - WebTarget stylesEndpoint = getStylesEndpoint(INVALID_EXP_UUID, CALL_STACK_DATAPROVIDER_ID); Map parameters = new HashMap<>(); - try (Response response = stylesEndpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { - assertNotNull(response); - assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); - } + parameters.remove(REQUESTED_TIMES_KEY); + parameters.put(REQUESTED_TIMERANGE_KEY, ImmutableMap.of(START, 1450193714978685130L, END, 1450193715011015823L, NB_TIMES, 1000)); - // Unknown experiment - stylesEndpoint = getStylesEndpoint(UUID.randomUUID().toString(), CALL_STACK_DATAPROVIDER_ID); - try (Response response = stylesEndpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { - assertNotNull(response); - assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); - assertEquals(EndpointConstants.NO_SUCH_TRACE, response.readEntity(String.class)); - } + try (Response arrowsResponse = arrowsEndpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, Status.OK.getStatusCode(), arrowsResponse.getStatus()); - // Missing parameters - stylesEndpoint = getStylesEndpoint(exp.getUUID().toString(), CALL_STACK_DATAPROVIDER_ID); - try (Response response = stylesEndpoint.request().post(Entity.json(NO_PARAMETERS))) { - assertNotNull(response); - assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } + TgArrowsOutputResponseStub tgArrowsModelResponse = arrowsResponse.readEntity(TgArrowsOutputResponseStub.class); + assertNotNull(tgArrowsModelResponse); - // Unknown data provider - stylesEndpoint = getStylesEndpoint(exp.getUUID().toString(), UNKNOWN_DP_ID); - try (Response response = stylesEndpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { - assertNotNull(response); - assertEquals(Status.METHOD_NOT_ALLOWED.getStatusCode(), response.getStatus()); - assertEquals(EndpointConstants.NO_PROVIDER, response.readEntity(String.class)); + List tgModel = tgArrowsModelResponse.getModel(); + assertNotNull(tgModel); + assertFalse(tgModel.isEmpty()); + + TimeGraphArrowStub arrow = tgModel.get(0); + // Verify first arrow in list + verifyArrow(entries, arrow); } } + /** + * Tests error cases when querying arrows for a time graph data provider + */ + @Test + public void testArrowsErrors() { + ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesKernelNotInitializedStub.getName(), sfContextSwitchesKernelNotInitializedStub); + executePostErrorTests(exp, RestServerTest::getArrowsEndpoint, THREAD_STATUS_DP_ID, true); + } + private static void testGetStates(String filterStrategy) throws InterruptedException { - long start = 1450193697034689597L; - long end = 1450193745774189602L; try { ExperimentModelStub exp = assertPostExperiment(sfContextSwitchesUstNotInitializedStub.getName(), sfContextSwitchesUstNotInitializedStub); + Set entries = loadDataProvider(exp, CALL_STACK_DATAPROVIDER_ID); - // Test getting the time graph tree - WebTarget callstackTree = getTimeGraphTreeEndpoint(exp.getUUID().toString(), CALL_STACK_DATAPROVIDER_ID); - - Map parameters = new HashMap<>(); - TgTreeOutputResponseStub responseModel; - parameters.put(REQUESTED_TIMES_KEY, List.of(start, end)); - try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { - assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus()); - responseModel = treeResponse.readEntity(TgTreeOutputResponseStub.class); - assertNotNull(responseModel); - } - - // Make sure the analysis ran enough and we have a model - int iteration = 0; - while ((responseModel.isRunning() || responseModel.getModel() == null) && iteration < MAX_ITER) { - Thread.sleep(100); - try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { - assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus()); - responseModel = treeResponse.readEntity(TgTreeOutputResponseStub.class); - assertNotNull(responseModel); - iteration++; - } - } - - TgEntryModelStub model = responseModel.getModel(); - assertNotNull(MODEL_NULL_MSG + responseModel, model); - Set entries = model.getEntries(); - assertFalse(entries.isEmpty()); // add entries for the states query, and make sure they don't have // extra time fields List items = new ArrayList<>(); @@ -236,6 +224,7 @@ private static void testGetStates(String filterStrategy) throws InterruptedExcep // Test getting the time graph row data WebTarget tgStatesEnpoint = getTimeGraphStatesEndpoint(exp.getUUID().toString(), CALL_STACK_DATAPROVIDER_ID); + Map parameters = new HashMap<>(); parameters.remove(REQUESTED_TIMES_KEY); parameters.put(REQUESTED_TIMERANGE_KEY, ImmutableMap.of(START, 1450193697034689597L, END, 1450193697118480368L, NB_TIMES, 10)); parameters.put(REQUESTED_ITEMS_KEY, items); @@ -333,6 +322,36 @@ private static void testGetStates(String filterStrategy) throws InterruptedExcep } } + private static @NonNull Set loadDataProvider(ExperimentModelStub exp, String dataProviderId) throws InterruptedException { + // Test getting the time graph tree + WebTarget callstackTree = getTimeGraphTreeEndpoint(exp.getUUID().toString(), dataProviderId); + + Map parameters = new HashMap<>(); + TgTreeOutputResponseStub responseModel; + try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus()); + responseModel = treeResponse.readEntity(TgTreeOutputResponseStub.class); + assertNotNull(responseModel); + } + + // Make sure the analysis ran enough and we have a model + int iteration = 0; + while ((responseModel.isRunning() || responseModel.getModel() == null) && iteration < MAX_ITER) { + Thread.sleep(100); + try (Response treeResponse = callstackTree.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertEquals(DATA_PROVIDER_RESPONSE_FAILED_MSG, 200, treeResponse.getStatus()); + responseModel = treeResponse.readEntity(TgTreeOutputResponseStub.class); + assertNotNull(responseModel); + iteration++; + } + } + TgEntryModelStub model = responseModel.getModel(); + assertNotNull(MODEL_NULL_MSG + responseModel, model); + Set entries = model.getEntries(); + assertFalse(entries.isEmpty()); + return entries; + } + private static int findCallStackEntry(Set entries) { // Find trace entry Optional traceOptional = entries.stream().filter(entry -> entry.getParentId() == -1).findFirst(); @@ -423,4 +442,66 @@ private static void verifyEntry(TimeGraphEntryStub entry) { assertNull(entry.getMetadata()); } } + + private static void verifyArrow(Set entries, TimeGraphArrowStub arrow) { + Optional entryOptional = entries.stream().filter(entry -> entry.getId() == arrow.getSourceId()).findFirst(); + assertTrue(entryOptional.isPresent()); + TimeGraphEntryStub sourceEntry = entryOptional.get(); + assertEquals("lsmod", sourceEntry.getLabels().get(0)); + + entryOptional = entries.stream().filter(entry -> entry.getId() == arrow.getTargetId()).findFirst(); + assertTrue(entryOptional.isPresent()); + TimeGraphEntryStub targetEntry = entryOptional.get(); + assertEquals("rcu_preempt", targetEntry.getLabels().get(0)); + + assertNotNull(arrow.getStyle()); + assertTrue(arrow.getEndTime() > arrow.getStartTime()); + } + + private interface IEndpointResolver { + WebTarget getEndpoint(String expUUID, String dataProviderId); + } + + private static void executePostErrorTests (ExperimentModelStub exp, IEndpointResolver resolver, String dpId, boolean hasParameters) { + // Invalid UUID string + WebTarget endpoint = resolver.getEndpoint(INVALID_EXP_UUID, dpId); + Map parameters = new HashMap<>(); + try (Response response = endpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertNotNull(response); + assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); + } + + // Unknown experiment + endpoint = resolver.getEndpoint(UUID.randomUUID().toString(), dpId); + try (Response response = endpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertNotNull(response); + assertEquals(Status.NOT_FOUND.getStatusCode(), response.getStatus()); + assertEquals(EndpointConstants.NO_SUCH_TRACE, response.readEntity(String.class)); + } + + // Missing parameters + endpoint = resolver.getEndpoint(exp.getUUID().toString(), dpId); + try (Response response = endpoint.request().post(Entity.json(NO_PARAMETERS))) { + assertNotNull(response); + assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + + if (hasParameters) { + // Missing parameters + endpoint = resolver.getEndpoint(exp.getUUID().toString(), dpId); + try (Response response = endpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertNotNull(response); + assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + } + + // Unknown data provider + endpoint = resolver.getEndpoint(exp.getUUID().toString(), UNKNOWN_DP_ID); + try (Response response = endpoint.request().post(Entity.json(new QueryParameters(parameters, Collections.emptyList())))) { + assertNotNull(response); + assertEquals(Status.METHOD_NOT_ALLOWED.getStatusCode(), response.getStatus()); + assertEquals(EndpointConstants.NO_PROVIDER, response.readEntity(String.class)); + } + } + } diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TgArrowsOutputResponseStub.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TgArrowsOutputResponseStub.java new file mode 100644 index 000000000..828af4baa --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TgArrowsOutputResponseStub.java @@ -0,0 +1,55 @@ +/********************************************************************** + * Copyright (c) 2024 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +package org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A stub class for the response to a time graph arrows' request. It contains + * the generic response, as well as an {@link TimeGraphArrowStub} + * + * @author Bernd Hufmann + */ +public class TgArrowsOutputResponseStub extends OutputResponseStub { + + private static final long serialVersionUID = -2103307674960234620L; + private final List fModel; + + /** + * {@link JsonCreator} Constructor from json + * + * @param model + * The model for this response + * @param status + * The status of the response + * @param statusMessage + * The custom status message of the response + */ + public TgArrowsOutputResponseStub(@JsonProperty("model") List model, + @JsonProperty("status") String status, + @JsonProperty("statusMessage") String statusMessage) { + super(status, statusMessage); + fModel = model; + } + + /** + * Get the model for this response + * + * @return The model for the response + */ + public List getModel() { + return fModel; + } + +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TimeGraphArrowStub.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TimeGraphArrowStub.java new file mode 100644 index 000000000..b7d9060a5 --- /dev/null +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/stubs/TimeGraphArrowStub.java @@ -0,0 +1,109 @@ +/********************************************************************** + * Copyright (c) 2024 Ericsson + * + * All rights reserved. This program and the accompanying materials are + * made available under the terms of the Eclipse Public License 2.0 which + * accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + **********************************************************************/ +package org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests.stubs; + +import java.io.Serializable; +import java.util.Objects; + +import org.eclipse.tracecompass.tmf.core.model.timegraph.TimeGraphArrow; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A Stub class for the time graph arrow elements. It matches the trace server + * protocol's {@link TimeGraphArrow} schema + * + * @author Bernd Hufmann + */ +public class TimeGraphArrowStub implements Serializable { + + private static final long serialVersionUID = 7583805352991909215L; + + private final long fStartTime; + private final long fEndTime; + private final long fSourceId; + private final long fTargetId; + private final OutputElementStyleStub fStyle; + + /** + * {@link JsonCreator} Constructor for final fields + * + * @param startTime + * The start time of the state + * @param endTime + * The end time of this state + * @param sourceId + * The ID of the source entry + * @param targetId + * The ID of the target entry + * @param style + * The style of this state + */ + @JsonCreator + public TimeGraphArrowStub(@JsonProperty("start") Long startTime, + @JsonProperty("end") Long endTime, + @JsonProperty("sourceId") Long sourceId, + @JsonProperty("targetId") Long targetId, + @JsonProperty("style") OutputElementStyleStub style) { + fStartTime = Objects.requireNonNull(startTime, "The 'start' json field was not set"); + fEndTime = Objects.requireNonNull(endTime, "The 'end' json field was not set"); + fSourceId = sourceId; + fTargetId = targetId; + fStyle = style; + } + + /** + * Get the ID of the source entry + * + * @return The ID of the source entry + */ + public long getStartTime() { + return fStartTime; + } + + /** + * Get the ID of the target entry + * + * @return The ID of the target entry + */ + public long getEndTime() { + return fEndTime; + } + + /** + * Get the label of this state + * + * @return The label of the state + */ + public long getSourceId() { + return fSourceId; + } + + /** + * Get the tags of this state + * + * @return The tags of the state + */ + public long getTargetId() { + return fTargetId; + } + + /** + * Get the style of this state + * + * @return The style of the state + */ + public OutputElementStyleStub getStyle() { + return fStyle; + } + +} diff --git a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java index 9c3367366..8a40e94b0 100644 --- a/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java +++ b/trace-server/org.eclipse.tracecompass.incubator.trace.server.jersey.rest.core.tests/src/org/eclipse/tracecompass/incubator/trace/server/jersey/rest/core/tests/utils/RestServerTest.java @@ -167,6 +167,11 @@ public abstract class RestServerTest { */ public static final String STATE_PATH = "states"; + /** + * Arrows path segment + */ + public static final String ARROWS_PATH = "arrows"; + /** * Tooltip path segment */ @@ -519,6 +524,24 @@ public static WebTarget getTimeGraphStatesEndpoint(String expUUID, String dataPr .path(STATE_PATH); } + /** + * Get the {@link WebTarget} for the time graph data provider arrows endpoint. + * + * @param expUUID + * Experiment UUID + * @param dataProviderId + * Data provider ID + * @return The time graph tree endpoint + */ + public static WebTarget getArrowsEndpoint(String expUUID, String dataProviderId) { + return getApplicationEndpoint().path(EXPERIMENTS) + .path(expUUID) + .path(OUTPUTS_PATH) + .path(TIMEGRAPH_PATH) + .path(dataProviderId) + .path(ARROWS_PATH); + } + /** * Get the {@link WebTarget} for the time graph tooltip endpoint. *