Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/worktrees/state-examples-docs
Submodule state-examples-docs added at 3ff4eb
1,525 changes: 1,525 additions & 0 deletions docs/superpowers/plans/2026-05-25-sdk-tests-testcontainers-migration.md

Large diffs are not rendered by default.

Large diffs are not rendered by default.

25 changes: 25 additions & 0 deletions sdk-tests/src/test/java/io/dapr/it/AppRun.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,31 @@ public class AppRun implements Stoppable {
this.maxWaitMilliseconds = maxWaitMilliseconds;
}

/**
* Overload used by {@link io.dapr.it.containers.BaseContainerIT} when the Dapr
* sidecar runs in a Testcontainer rather than via {@code dapr run}. The
* {@code DAPR_HTTP_PORT} / {@code DAPR_GRPC_PORT} env vars on the spawned
* app process point at the explicit override values (typically the
* DaprContainer's mapped host ports) instead of {@code ports.getHttpPort() /
* ports.getGrpcPort()}.
*/
public AppRun(DaprPorts ports,
String successMessage,
Class serviceClass,
int maxWaitMilliseconds,
int daprHttpPortOverride,
int daprGrpcPortOverride) {
this.command = new Command(
successMessage,
buildCommand(serviceClass, ports),
new HashMap<>() {{
put("DAPR_HTTP_PORT", Integer.toString(daprHttpPortOverride));
put("DAPR_GRPC_PORT", Integer.toString(daprGrpcPortOverride));
}});
this.ports = ports;
this.maxWaitMilliseconds = maxWaitMilliseconds;
}

public void start() throws InterruptedException, IOException {
long start = System.currentTimeMillis();
// First, try to stop previous run (if left running).
Expand Down
47 changes: 47 additions & 0 deletions sdk-tests/src/test/java/io/dapr/it/AppRunOverrideTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2025 The Dapr Authors
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
limitations under the License.
*/

package io.dapr.it;

import org.junit.jupiter.api.Test;

import java.lang.reflect.Field;
import java.util.Map;

import static org.junit.jupiter.api.Assertions.assertEquals;

class AppRunOverrideTest {

/**
* Verifies that when we construct AppRun with explicit Dapr port overrides,
* the DAPR_HTTP_PORT / DAPR_GRPC_PORT env vars on the spawned command point
* at the override values, not at the DaprPorts-allocated ones.
*/
@Test
void daprPortOverridesAreUsedInEnv() throws Exception {
DaprPorts ports = DaprPorts.build(true, true, true);
AppRun app = new AppRun(ports, "ready", Object.class, 1000, 12345, 67890);

Field commandField = AppRun.class.getDeclaredField("command");
commandField.setAccessible(true);
Command command = (Command) commandField.get(app);

Field envField = Command.class.getDeclaredField("env");
envField.setAccessible(true);
@SuppressWarnings("unchecked")
Map<String, String> env = (Map<String, String>) envField.get(command);

assertEquals("12345", env.get("DAPR_HTTP_PORT"));
assertEquals("67890", env.get("DAPR_GRPC_PORT"));
}
}
34 changes: 19 additions & 15 deletions sdk-tests/src/test/java/io/dapr/it/DaprRun.java
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,13 @@ public void stop() throws InterruptedException, IOException {
System.out.println("Stopping dapr application ...");
try {
this.stopCommand.run();

System.out.println("Dapr application stopped.");
} catch (RuntimeException e) {
System.out.println("Could not stop app " + this.appName + ": " + e.getMessage());
if (e.getMessage() != null && e.getMessage().contains("Could not find success criteria")) {
System.out.println("App " + this.appName + " already stopped or not found (ignored).");
} else {
System.out.println("Could not stop app " + this.appName + ": " + e.getMessage());
}
}
}

Expand Down Expand Up @@ -219,8 +222,7 @@ public void waitForAppHealth(int maxWaitMilliseconds) throws InterruptedExceptio
while (System.currentTimeMillis() <= maxWait) {
try {
stub.healthCheck(Empty.getDefaultInstance());
// artursouza: workaround due to race condition with runtime's probe on app's health.
Thread.sleep(5000);
Thread.sleep(2000);
return;
} catch (Exception e) {
Thread.sleep(1000);
Expand All @@ -232,29 +234,31 @@ public void waitForAppHealth(int maxWaitMilliseconds) throws InterruptedExceptio
channel.shutdown();
}
} else {
Duration waitDuration = Duration.ofMillis(maxWaitMilliseconds);
long maxWait = System.currentTimeMillis() + maxWaitMilliseconds;
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_1_1)
.connectTimeout(waitDuration)
.connectTimeout(Duration.ofSeconds(5))
.build();
String url = "http://127.0.0.1:" + this.getAppPort() + "/health";
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(url))
.build();

try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());

if (response.statusCode() != 200) {
throw new RuntimeException("error: HTTP service is not healthy.");
while (System.currentTimeMillis() <= maxWait) {
try {
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() == 200) {
Thread.sleep(2000);
return;
}
} catch (IOException e) {
// not ready yet
}
} catch (IOException e) {
throw new RuntimeException("exception: HTTP service is not healthy.");
Thread.sleep(1000);
}

// artursouza: workaround due to race condition with runtime's probe on app's health.
Thread.sleep(5000);
throw new RuntimeException("timeout: HTTP service is not healthy.");
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,24 +129,19 @@ public void reminderRecoveryTest(
) throws Exception {
setup(actorType);

logger.debug("Pausing 3 seconds to let gRPC connection get ready");
Thread.sleep(3000);

logger.debug("Invoking actor method 'startReminder' which will register a reminder");
proxy.invokeMethod("setReminderData", reminderDataParam).block();

proxy.invokeMethod("startReminder", reminderName).block();

logger.debug("Pausing 7 seconds to allow reminder to fire");
Thread.sleep(7000);

logger.debug("Waiting for reminder to fire at least 3 times");
final List<MethodEntryTracker> logs = new ArrayList<>();
callWithRetry(() -> {
logs.clear();
logs.addAll(fetchMethodCallLogs(proxy));
validateMethodCalls(logs, METHOD_NAME, 3);
validateMessageContent(logs, METHOD_NAME, expectedReminderStateText);
}, 5000);
}, 30000);

// Restarts runtime only.
logger.info("Stopping Dapr sidecar");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public void timerRecoveryTest() throws Exception {
true,
60000);

Thread.sleep(3000);
String actorType="MyActorTest";
logger.debug("Creating proxy builder");

Expand All @@ -68,16 +67,14 @@ public void timerRecoveryTest() throws Exception {
logger.debug("Invoking actor method 'startTimer' which will register a timer");
proxy.invokeMethod("startTimer", "myTimer").block();

logger.debug("Pausing 7 seconds to allow timer to fire");
Thread.sleep(7000);

logger.debug("Waiting for timer to fire at least 3 times");
final List<MethodEntryTracker> logs = new ArrayList<>();
callWithRetry(() -> {
logs.clear();
logs.addAll(fetchMethodCallLogs(proxy));
validateMethodCalls(logs, METHOD_NAME, 3);
validateMessageContent(logs, METHOD_NAME, "ping!");
}, 5000);
}, 30000);

// Restarts app only.
runs.left.stop();
Expand All @@ -91,7 +88,7 @@ public void timerRecoveryTest() throws Exception {
newLogs.clear();
newLogs.addAll(fetchMethodCallLogs(proxy));
validateMethodCalls(newLogs, METHOD_NAME, 3);
}, 10000);
}, 30000);

// Check that the restart actually happened by confirming the old logs are not in the new logs.
for (MethodEntryTracker oldLog: logs) {
Expand Down
Loading
Loading