Skip to content

Commit 273ca36

Browse files
authored
feat(testing-sdk): add full support for run-durable CLI (#392)
*Issue #, if available:* #388 *Description of changes:* run-durable CLI is currently not exported from our package, but it is advertised. It also uses unintuitive arguments which would not be easy to maintain. Since it's not working right now, we can improve this before actually launching it. Changes: - Change language SDK DurableExecutionInvocationInputWithClient to work for objects that look the same, even if they are not the same instance. This is necessary since customers might bundle the language SDK when running their script, and we need the testing library to work with different instances as long as they match. - Using `commander` library as a dependency for the testing library for argument parsing - Change default skipTime to false for more accurate testing by default - Refactor run-durable CLI to separate files - Add unit and integration tests for run-durable CLI with CJS and MJS handlers Example usage: ``` npx run-durable --show-history step-basic.js ``` or with a custom loader ``` NODE_OPTIONS="--import tsx" npx run-durable --show-history step-basic.ts ``` By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.
1 parent f4ed60a commit 273ca36

File tree

29 files changed

+4539
-887
lines changed

29 files changed

+4539
-887
lines changed

package-lock.json

Lines changed: 3110 additions & 749 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"postpublish": "git restore .",
1515
"clean": "rm -rf node_modules && npm run clean --workspaces --if-present",
1616
"prepare": "husky install",
17-
"run-durable": "ORIGINAL_CWD=$PWD npm run run-durable -w packages/aws-durable-execution-sdk-js-testing --"
17+
"run-durable": "NODE_OPTIONS=\"--import tsx\" npx run-durable --"
1818
},
1919
"repository": {
2020
"type": "git",

packages/aws-durable-execution-sdk-js-examples/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
"@types/node": "^22.13.5",
6666
"argparse": "^2.0.1",
6767
"eslint": "^9.23.0",
68-
"jest": "^29.7.0",
68+
"jest": "^30.2.0",
6969
"js-yaml": "^4.1.0",
7070
"prettier": "^3.5.2",
7171
"ts-jest": "^29.2.6",
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
2+
3+
exports[`run-durable CLI Integration Tests with format=cjs Core Error Cases should error when file does not exist 1`] = `
4+
"Error: File not found: nonexistent-file.js
5+
"
6+
`;
7+
8+
exports[`run-durable CLI Integration Tests with format=cjs End-to-End Execution should successfully execute test handler end-to-end 1`] = `
9+
[
10+
"Skip time: false, Verbose: false, Show history: false",
11+
"",
12+
"No operations found.",
13+
"",
14+
"Execution Status: SUCCEEDED",
15+
"",
16+
"Result:",
17+
""Test CLI Success!"",
18+
"",
19+
]
20+
`;
21+
22+
exports[`run-durable CLI Integration Tests with format=cjs End-to-End Execution should successfully execute with custom handler export 1`] = `
23+
[
24+
"Skip time: false, Verbose: false, Show history: false",
25+
"",
26+
"No operations found.",
27+
"",
28+
"Execution Status: SUCCEEDED",
29+
"",
30+
"Result:",
31+
""Custom Handler Success!"",
32+
"",
33+
]
34+
`;
35+
36+
exports[`run-durable CLI Integration Tests with format=mjs Core Error Cases should error when file does not exist 1`] = `
37+
"Error: File not found: nonexistent-file.js
38+
"
39+
`;
40+
41+
exports[`run-durable CLI Integration Tests with format=mjs End-to-End Execution should successfully execute test handler end-to-end 1`] = `
42+
[
43+
"Skip time: false, Verbose: false, Show history: false",
44+
"",
45+
"No operations found.",
46+
"",
47+
"Execution Status: SUCCEEDED",
48+
"",
49+
"Result:",
50+
""Test CLI Success!"",
51+
"",
52+
]
53+
`;
54+
55+
exports[`run-durable CLI Integration Tests with format=mjs End-to-End Execution should successfully execute with custom handler export 1`] = `
56+
[
57+
"Skip time: false, Verbose: false, Show history: false",
58+
"",
59+
"No operations found.",
60+
"",
61+
"Execution Status: SUCCEEDED",
62+
"",
63+
"Result:",
64+
""Custom Handler Success!"",
65+
"",
66+
]
67+
`;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { withDurableExecution } = require("@aws/durable-execution-sdk-js");
2+
3+
module.exports.handler = withDurableExecution(async (event, context) => {
4+
return "Test CLI Success!";
5+
});
6+
7+
module.exports.customHandler = withDurableExecution(async (event, context) => {
8+
return "Custom Handler Success!";
9+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Test handler for CLI integration tests
2+
// This file is committed and doesn't require a build step
3+
4+
import { withDurableExecution } from "@aws/durable-execution-sdk-js";
5+
6+
export const handler = withDurableExecution(async (event, context) => {
7+
return "Test CLI Success!";
8+
});
9+
10+
export const customHandler = withDurableExecution(async (event, context) => {
11+
return "Custom Handler Success!";
12+
});
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { execSync } from "child_process";
2+
import { join } from "path";
3+
4+
const TEST_HANDLER_PATH = join(__dirname, "fixtures/test-handler");
5+
6+
/**
7+
* Helper function to execute CLI commands and capture output
8+
*/
9+
function runDurableCli(args: string[]): {
10+
stdout: string;
11+
stderr: string;
12+
exitCode: number;
13+
} {
14+
try {
15+
const stdout = execSync(`npx run-durable ${args.join(" ")}`, {
16+
encoding: "utf8",
17+
cwd: join(__dirname, "../../../aws-durable-execution-sdk-js-testing"),
18+
timeout: 30000,
19+
});
20+
return { stdout, stderr: "", exitCode: 0 };
21+
} catch (error: any) {
22+
return {
23+
stdout: error.stdout || "",
24+
stderr: error.stderr || "",
25+
exitCode: error.status || 1,
26+
};
27+
}
28+
}
29+
30+
describe.each(["cjs", "mjs"])(
31+
`run-durable CLI Integration Tests with format=%s`,
32+
(format) => {
33+
const testHandlerPath = `${TEST_HANDLER_PATH}.${format}`;
34+
35+
describe("End-to-End Execution", () => {
36+
it("should successfully execute test handler end-to-end", () => {
37+
const { stdout, stderr, exitCode } = runDurableCli([testHandlerPath]);
38+
39+
expect(stderr).toBe("");
40+
41+
const lines = stdout.split("\n");
42+
expect(lines[0]).toContain("Running durable function from");
43+
expect(lines.slice(1)).toMatchSnapshot();
44+
expect(exitCode).toBe(0);
45+
});
46+
47+
it("should successfully execute with custom handler export", () => {
48+
const { stdout, stderr, exitCode } = runDurableCli([
49+
testHandlerPath,
50+
"--handler-export",
51+
"customHandler",
52+
]);
53+
54+
expect(stderr).toBe("");
55+
const lines = stdout.split("\n");
56+
expect(lines[0]).toContain("Running durable function from");
57+
expect(lines.slice(1)).toMatchSnapshot();
58+
expect(exitCode).toBe(0);
59+
});
60+
});
61+
62+
describe("Core Error Cases", () => {
63+
it("should error when file does not exist", () => {
64+
const { stdout, stderr, exitCode } = runDurableCli([
65+
"nonexistent-file.js",
66+
]);
67+
68+
expect(exitCode).toBe(1);
69+
expect(stdout).toBe("");
70+
expect(stderr).toMatchSnapshot();
71+
});
72+
73+
it("should error when handler export not found", () => {
74+
const { stdout, stderr, exitCode } = runDurableCli([
75+
testHandlerPath,
76+
"--handler-export",
77+
"nonExistentHandler",
78+
]);
79+
80+
expect(exitCode).toBe(1);
81+
expect(stdout).toBe("");
82+
expect(stderr).toContain(
83+
"Error: Export 'nonExistentHandler' not found in ",
84+
);
85+
});
86+
});
87+
},
88+
);

packages/aws-durable-execution-sdk-js-testing/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ This package provides testing tools for durable functions both locally and in th
2222
- **Callback testing** - Send callback responses and verify callback behavior
2323
- **Retry validation** - Test step retry logic and failure scenarios
2424

25+
- **run-durable CLI** - Command-line tool for quick testing without writing test code
26+
- **Zero setup** - Run any durable function with `npx run-durable your-function.js`
27+
- **Operation inspection** - View detailed operation tables and execution history
28+
- **Time control** - Skip time for fast testing or use real timing for validation
29+
2530
## Installation
2631

2732
```bash
@@ -159,6 +164,7 @@ describe("CloudDurableTestRunner", () => {
159164
This README provides a quick reference for the Testing SDK's main features. For more detailed information:
160165

161166
- **[API Reference](../../docs/api-reference/durable-execution-sdk-js-testing.md)** - Complete technical reference with detailed type definitions and operation specifications
167+
- **[run-durable CLI Guide](./RUN_DURABLE_CLI.md)** - Complete guide for using the command-line tool to quickly test durable functions
162168
- **[Contributing](../../CONTRIBUTING.md)** - Learn about contributing to the AWS Durable Execution Testing SDK for JavaScript
163169

164170
## LocalDurableTestRunner

packages/aws-durable-execution-sdk-js-testing/RUN_DURABLE_CLI.md

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,68 @@ A command-line tool to quickly run and test durable functions locally without wr
55
## Usage
66

77
```bash
8-
npm run run-durable -- <path-to-handler-file> [no-skip-time] [verbose] [show-history]
8+
npx run-durable [options] <file>
99
```
1010

11-
### Parameters
11+
### Arguments
1212

13-
1. **Path to handler file** (required) - Path to the TypeScript file containing the durable function
14-
2. **no-skip-time** (optional) - Disables time skipping; waits will actually wait for the specified duration
15-
- Default: time skipping is enabled (waits complete instantly)
16-
3. **verbose** (optional) - Enables verbose logging to see detailed execution flow
17-
- Default: verbose logging is disabled
18-
4. **show-history** (optional) - Displays a table of history events after execution
19-
- Default: history is not shown
13+
- **`<file>`** (required) - Path to the TypeScript or JavaScript file containing the durable function
14+
15+
### Options
16+
17+
- **`--skip-time`** - Enable time skipping in test environment; waits complete instantly
18+
- Default: time skipping is disabled (waits actually wait for the specified duration)
19+
- **`-v, --verbose`** - Enable verbose logging output
20+
- Default: verbose logging is disabled
21+
- **`--show-history`** - Display execution history events after completion
22+
- Default: history is not shown
23+
- **`--handler-export <name>`** - The exported handler function key
24+
- Default: "handler"
25+
- **`--help`** - Show help information
26+
- **`--version`** - Show version number
2027

2128
## Examples
2229

2330
### Basic Usage
2431

2532
```bash
26-
# Run with default settings (skip time, no verbose, no history)
27-
npm run run-durable -- packages/aws-durable-execution-sdk-js-examples/src/examples/hello-world.ts
33+
# Run with default settings (no skip time, no verbose, no history)
34+
npx run-durable hello-world.js
2835
```
2936

30-
### With Time Skipping Disabled
37+
### With Time Skipping Enabled
3138

3239
```bash
33-
# Actually wait for the specified duration
34-
npm run run-durable -- packages/aws-durable-execution-sdk-js-examples/src/examples/test-wait-simple.ts no-skip-time
40+
# Skip time - waits complete instantly
41+
npx run-durable test-wait-simple.js
3542
```
3643

3744
### With Verbose Logging
3845

3946
```bash
4047
# See detailed execution logs
41-
npm run run-durable -- packages/aws-durable-execution-sdk-js-examples/src/examples/step-basic.ts skip-time verbose
48+
npx run-durable -v step-basic.js
4249
```
4350

4451
### With History Display
4552

4653
```bash
4754
# Show history events table
48-
npm run run-durable -- packages/aws-durable-execution-sdk-js-examples/src/examples/step-basic.ts skip-time no-verbose show-history
55+
npx run-durable --show-history step-basic.js
56+
```
57+
58+
### Custom Handler Export
59+
60+
```bash
61+
# Use a custom export name instead of "handler"
62+
npx run-durable --handler-export my-function.js
4963
```
5064

5165
### All Options Combined
5266

5367
```bash
54-
# Don't skip time + verbose logging + show history
55-
npm run run-durable -- packages/aws-durable-execution-sdk-js-examples/src/examples/comprehensive-operations.ts no-skip-time verbose show-history
68+
# Skip time + verbose logging + show history
69+
npx run-durable --skip-time --verbose --show-history comprehensive-operations.js
5670
```
5771

5872
## Output
@@ -82,7 +96,7 @@ When `show-history` is enabled, you'll see a detailed table including:
8296
- **EventType**: ExecutionStarted, StepStarted, StepSucceeded, WaitStarted, WaitSucceeded, etc.
8397
- **EventId**: Sequential event identifier
8498
- **Id**: Operation identifier
85-
- **EventTimestamp**: Unix timestamp of the event
99+
- **EventTimestamp**: Timestamp of the event
86100
- **Event-specific details**: StartedDetails, SucceededDetails, FailedDetails, etc.
87101

88102
## Requirements
@@ -95,7 +109,7 @@ When `show-history` is enabled, you'll see a detailed table including:
95109
```
96110
Checkpoint server listening on port 54867
97111
Running durable function from: packages/aws-durable-execution-sdk-js-examples/src/examples/step-basic.ts
98-
Skip time: true, Verbose: false, Show history: false
112+
Skip time: false, Verbose: false, Show history: false
99113
100114
┌─────────┬──────────┬──────┬────────┬─────────┬─────────────┬────────────────────────────┬────────────────────────────┬──────────┐
101115
│ (index) │ parentId │ name │ type │ subType │ status │ startTime │ endTime │ duration │
@@ -111,37 +125,39 @@ Result:
111125

112126
## Running from Different Locations
113127

114-
### From monorepo root:
128+
The CLI accepts both absolute and relative file paths, making it easy to run from any directory:
115129

116-
```bash
117-
npm run run-durable -- packages/aws-durable-execution-sdk-js-examples/src/examples/hello-world.ts
118-
```
119-
120-
### From testing package directory:
130+
### From any directory with relative paths:
121131

122132
```bash
123-
npm run run-durable -- ../aws-durable-execution-sdk-js-examples/src/examples/hello-world.ts
133+
npx run-durable ./src/examples/hello-world.js
124134
```
125135

126-
### From examples package directory:
136+
### With absolute paths:
127137

128138
```bash
129-
npm run run-durable -- src/examples/hello-world.ts
139+
npx run-durable /full/path/to/your/durable-function.js
130140
```
131141

132142
## Troubleshooting
133143

134144
**Function hangs or doesn't complete:**
135145

136-
- Try running with `verbose` to see detailed execution logs
146+
- Try running with `--verbose` to see detailed execution logs
137147
- Check if there are any infinite loops or blocking operations
138148

149+
**Time-based operations take too long:**
150+
151+
- Use `--skip-time` to make waits complete instantly for faster testing
152+
- Default behavior is to actually wait for the specified duration
153+
139154
**Time-based operations complete instantly:**
140155

141-
- This is expected behavior with default settings
142-
- Use `no-skip-time` parameter to actually wait for the specified duration
156+
- This happens when using `--skip-time` flag
157+
- Remove the `--skip-time` flag if you want waits to actually wait for the specified duration
143158

144159
**Cannot find handler:**
145160

146161
- Ensure the file exports `handler` or `default`
162+
- Use `--handler-export <name>` if your handler has a different export name
147163
- Verify the path is correct relative to where you're running the command

0 commit comments

Comments
 (0)