Skip to content

Commit c4af3ff

Browse files
authored
feat(examples): enable history tests in most examples (#346)
*Issue #, if available:* #261 *Description of changes:* Generating history files and adding `assertHistoryEvents` to all non-callback and non-invoke example tests. All generated histories were created using the testing library using the `generate-history.ts` script (also added in this PR). Event signatures are matching with the backend, while all histories were generated by the testing library. This means the testing library and backend have full history parity! (in terms of the examples that we have, and the event types generated - possibly not the event details yet) Currently, all tests are logging a warning if the history isn't generated yet. ## Limitations The local runner history results do not match exactly in cases when skipTime is enabled. In particular, the number of invocations will often not be the same when running with skipTime, since the checkpoint response will instantly return the timer result, and the invocation doesn't need to exit. For tests with skipTime enabled, I have disabled InvocationCompleted assertions when running in local mode. Additionally, some situations result in more retries when skipTime is enabled, since retries completely instantly. I have disabled skipTime in those cases (just the promise-any test). ## Next steps - Generate histories for callback/invoke examples - Make tests throw an error if the event history assertion isn't there 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 2f0f269 commit c4af3ff

File tree

105 files changed

+13427
-1447
lines changed

Some content is hidden

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

105 files changed

+13427
-1447
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { exampleStorage } from "../src/utils";
2+
import fs from "fs";
3+
import path from "path";
4+
import { LocalDurableTestRunner } from "@aws/durable-execution-sdk-js-testing";
5+
import { ArgumentParser, BooleanOptionalAction } from "argparse";
6+
7+
async function main() {
8+
const parser = new ArgumentParser({
9+
description: "Generate history files for durable execution examples",
10+
});
11+
12+
parser.add_argument("--pattern", {
13+
type: "str",
14+
help: "String pattern to generate history for a specific example that matches the pattern (default: generate all examples)",
15+
});
16+
17+
parser.add_argument("--log", {
18+
action: "store_true",
19+
help: "Log the history events to the console",
20+
});
21+
22+
parser.add_argument("--skip-time", {
23+
action: BooleanOptionalAction,
24+
help: "Enable skip time in test environment",
25+
default: true,
26+
});
27+
28+
parser.add_argument("--suffix", {
29+
help: "Optional suffix for test case",
30+
type: "str",
31+
});
32+
33+
parser.add_argument("--payload", {
34+
help: "Optional payload for test case",
35+
type: "str",
36+
});
37+
38+
parser.add_argument("--only-missing", {
39+
action: BooleanOptionalAction,
40+
help: "Only add missing history files for the examples specified",
41+
default: true,
42+
});
43+
44+
const args = parser.parse_args();
45+
46+
const pattern = args.pattern;
47+
const logEvents = args.log;
48+
const skipTime = args.skip_time;
49+
const suffix = args.suffix;
50+
const onlyMissing = args.only_missing;
51+
52+
const examples = await exampleStorage.getExamples();
53+
54+
const filteredExamples = pattern
55+
? examples.filter((example) => example.path.includes(pattern))
56+
: examples;
57+
58+
if (pattern && filteredExamples.length === 0) {
59+
console.log(`No examples found matching pattern: ${pattern}`);
60+
return;
61+
}
62+
63+
if (pattern) {
64+
console.log(
65+
`Found ${filteredExamples.length} example(s) matching pattern "${pattern}":`,
66+
);
67+
filteredExamples.forEach((example) => console.log(` - ${example.name}`));
68+
} else {
69+
console.log(
70+
`Generating history for all ${filteredExamples.length} examples`,
71+
);
72+
}
73+
74+
try {
75+
await LocalDurableTestRunner.setupTestEnvironment({
76+
skipTime: skipTime,
77+
});
78+
79+
const generated: string[] = [];
80+
for (const example of filteredExamples) {
81+
if (
82+
example.path.includes("callback") ||
83+
example.path.includes("invoke") ||
84+
!example.durableConfig
85+
) {
86+
console.log("Skipping example", example.name);
87+
continue;
88+
}
89+
90+
const exampleDir = path.dirname(example.path);
91+
const exampleBaseName = path.basename(
92+
example.path,
93+
path.extname(example.path),
94+
);
95+
if (
96+
// If any .history.json file exists in this directory, don't generate a new one
97+
fs
98+
.readdirSync(exampleDir)
99+
.some(
100+
(file) =>
101+
file.startsWith(exampleBaseName) &&
102+
file.endsWith(".history.json"),
103+
) &&
104+
onlyMissing
105+
) {
106+
console.log(`History file already exists for ${example.name}`);
107+
continue;
108+
}
109+
110+
try {
111+
console.log(`Generating history for ${example.name}`);
112+
113+
const runner = new LocalDurableTestRunner({
114+
handlerFunction: (await import(example.path)).handler,
115+
});
116+
const result = await runner.run({
117+
payload: args.payload ? JSON.parse(args.payload) : undefined,
118+
});
119+
120+
const historyEvents = result.getHistoryEvents();
121+
122+
const outputPath = `${exampleDir}/${exampleBaseName + (suffix ? `-${suffix}` : "")}.history.json`;
123+
console.log(`Output: ${outputPath}`);
124+
fs.writeFileSync(outputPath, JSON.stringify(historyEvents, null, 2));
125+
generated.push(example.name);
126+
127+
if (logEvents) {
128+
console.log(
129+
`History events for ${example.name}:`,
130+
JSON.stringify(historyEvents, null, 2),
131+
);
132+
}
133+
} catch (err) {
134+
console.log(`Error generating history for ${example.name}`, err);
135+
}
136+
}
137+
console.log(
138+
`Generated ${generated.length} history files: ${JSON.stringify(generated)}`,
139+
);
140+
} finally {
141+
await LocalDurableTestRunner.teardownTestEnvironment();
142+
}
143+
}
144+
145+
main().catch((err) => {
146+
console.error(err);
147+
process.exit(1);
148+
});
Lines changed: 137 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,140 @@
1-
{
2-
"expectedHistory": {
3-
"events": [
4-
{
5-
"EventType": "ExecutionStarted",
6-
"EventId": 1,
7-
"Name": "block-example-typescript-acceptance-test",
8-
"ExecutionStartedDetails": {
9-
"Input": {
10-
"Truncated": true
11-
},
12-
"ExecutionTimeout": 300
13-
}
14-
},
15-
{
16-
"EventType": "ContextStarted",
17-
"SubType": "RunInChildContext",
18-
"EventId": 2,
19-
"Name": "parent_block",
20-
"ContextStartedDetails": {}
21-
},
22-
{
23-
"EventType": "StepStarted",
24-
"SubType": "Step",
25-
"EventId": 3,
26-
"Name": "nested_step",
27-
"ParentId": "c4ca4238a0b92382",
28-
"StepStartedDetails": {}
29-
},
30-
{
31-
"EventType": "StepSucceeded",
32-
"SubType": "Step",
33-
"EventId": 4,
34-
"Name": "nested_step",
35-
"ParentId": "c4ca4238a0b92382",
36-
"StepSucceededDetails": {
37-
"Result": {
38-
"Truncated": true
39-
},
40-
"RetryDetails": {
41-
"CurrentAttempt": 0
42-
}
43-
}
44-
},
45-
{
46-
"EventType": "ContextStarted",
47-
"SubType": "RunInChildContext",
48-
"EventId": 5,
49-
"Name": "nested_block",
50-
"ParentId": "c4ca4238a0b92382",
51-
"ContextStartedDetails": {}
52-
},
53-
{
54-
"EventType": "WaitStarted",
55-
"SubType": "Wait",
56-
"EventId": 6,
57-
"ParentId": "98c6f2c2287f4c73",
58-
"WaitStartedDetails": {
59-
"Duration": 1
60-
}
61-
},
62-
{
63-
"EventType": "WaitSucceeded",
64-
"EventId": 7,
65-
"WaitSucceededDetails": {}
66-
},
67-
{
68-
"EventType": "ContextSucceeded",
69-
"SubType": "RunInChildContext",
70-
"EventId": 8,
71-
"Name": "nested_block",
72-
"ParentId": "c4ca4238a0b92382",
73-
"ContextSucceededDetails": {
74-
"Result": {
75-
"Truncated": true
76-
}
77-
}
78-
},
79-
{
80-
"EventType": "ContextSucceeded",
81-
"SubType": "RunInChildContext",
82-
"EventId": 9,
83-
"Name": "parent_block",
84-
"ContextSucceededDetails": {
85-
"Result": {
86-
"Truncated": true
87-
}
88-
}
1+
[
2+
{
3+
"EventType": "ExecutionStarted",
4+
"EventId": 1,
5+
"Id": "ec1e8feb-0e24-4870-a740-cdb91b06501c",
6+
"EventTimestamp": "2025-12-03T22:57:07.710Z",
7+
"ExecutionStartedDetails": {
8+
"Input": {
9+
"Payload": "{}"
10+
}
11+
}
12+
},
13+
{
14+
"EventType": "ContextStarted",
15+
"SubType": "RunInChildContext",
16+
"EventId": 2,
17+
"Id": "c4ca4238a0b92382",
18+
"Name": "parent_block",
19+
"EventTimestamp": "2025-12-03T22:57:07.716Z",
20+
"ContextStartedDetails": {}
21+
},
22+
{
23+
"EventType": "StepStarted",
24+
"SubType": "Step",
25+
"EventId": 3,
26+
"Id": "ea66c06c1e1c05fa",
27+
"Name": "nested_step",
28+
"EventTimestamp": "2025-12-03T22:57:07.716Z",
29+
"ParentId": "c4ca4238a0b92382",
30+
"StepStartedDetails": {}
31+
},
32+
{
33+
"EventType": "StepSucceeded",
34+
"SubType": "Step",
35+
"EventId": 4,
36+
"Id": "ea66c06c1e1c05fa",
37+
"Name": "nested_step",
38+
"EventTimestamp": "2025-12-03T22:57:07.716Z",
39+
"ParentId": "c4ca4238a0b92382",
40+
"StepSucceededDetails": {
41+
"Result": {
42+
"Payload": "\"nested step result\""
8943
},
90-
{
91-
"EventType": "ExecutionSucceeded",
92-
"EventId": 10,
93-
"Name": "history-generation-1757540244",
94-
"ExecutionSucceededDetails": {
95-
"Result": {
96-
"Truncated": true
97-
}
98-
}
44+
"RetryDetails": {}
45+
}
46+
},
47+
{
48+
"EventType": "ContextStarted",
49+
"SubType": "RunInChildContext",
50+
"EventId": 5,
51+
"Id": "98c6f2c2287f4c73",
52+
"Name": "nested_block",
53+
"EventTimestamp": "2025-12-03T22:57:07.717Z",
54+
"ParentId": "c4ca4238a0b92382",
55+
"ContextStartedDetails": {}
56+
},
57+
{
58+
"EventType": "WaitStarted",
59+
"SubType": "Wait",
60+
"EventId": 6,
61+
"Id": "6151f5ab282d90e4",
62+
"EventTimestamp": "2025-12-03T22:57:07.719Z",
63+
"ParentId": "98c6f2c2287f4c73",
64+
"WaitStartedDetails": {
65+
"Duration": 1,
66+
"ScheduledEndTimestamp": "2025-12-03T22:57:08.719Z"
67+
}
68+
},
69+
{
70+
"EventType": "InvocationCompleted",
71+
"EventId": 7,
72+
"EventTimestamp": "2025-12-03T22:57:07.771Z",
73+
"InvocationCompletedDetails": {
74+
"StartTimestamp": "2025-12-03T22:57:07.710Z",
75+
"EndTimestamp": "2025-12-03T22:57:07.771Z",
76+
"Error": {},
77+
"RequestId": "41787b19-d7ee-4aea-b701-7b3007bf5d64"
78+
}
79+
},
80+
{
81+
"EventType": "WaitSucceeded",
82+
"SubType": "Wait",
83+
"EventId": 8,
84+
"Id": "6151f5ab282d90e4",
85+
"EventTimestamp": "2025-12-03T22:57:08.721Z",
86+
"ParentId": "98c6f2c2287f4c73",
87+
"WaitSucceededDetails": {
88+
"Duration": 1
89+
}
90+
},
91+
{
92+
"EventType": "ContextSucceeded",
93+
"SubType": "RunInChildContext",
94+
"EventId": 9,
95+
"Id": "98c6f2c2287f4c73",
96+
"Name": "nested_block",
97+
"EventTimestamp": "2025-12-03T22:57:08.723Z",
98+
"ParentId": "c4ca4238a0b92382",
99+
"ContextSucceededDetails": {
100+
"Result": {
101+
"Payload": "\"nested block result\""
102+
}
103+
}
104+
},
105+
{
106+
"EventType": "ContextSucceeded",
107+
"SubType": "RunInChildContext",
108+
"EventId": 10,
109+
"Id": "c4ca4238a0b92382",
110+
"Name": "parent_block",
111+
"EventTimestamp": "2025-12-03T22:57:08.724Z",
112+
"ContextSucceededDetails": {
113+
"Result": {
114+
"Payload": "{\"nestedStep\":\"nested step result\",\"nestedBlock\":\"nested block result\"}"
115+
}
116+
}
117+
},
118+
{
119+
"EventType": "InvocationCompleted",
120+
"EventId": 11,
121+
"EventTimestamp": "2025-12-03T22:57:08.724Z",
122+
"InvocationCompletedDetails": {
123+
"StartTimestamp": "2025-12-03T22:57:08.722Z",
124+
"EndTimestamp": "2025-12-03T22:57:08.724Z",
125+
"Error": {},
126+
"RequestId": "21a2b7fc-9b58-4957-ab15-eb555118008d"
127+
}
128+
},
129+
{
130+
"EventType": "ExecutionSucceeded",
131+
"EventId": 12,
132+
"Id": "ec1e8feb-0e24-4870-a740-cdb91b06501c",
133+
"EventTimestamp": "2025-12-03T22:57:08.724Z",
134+
"ExecutionSucceededDetails": {
135+
"Result": {
136+
"Payload": "{\"nestedStep\":\"nested step result\",\"nestedBlock\":\"nested block result\"}"
99137
}
100-
]
138+
}
101139
}
102-
}
140+
]

0 commit comments

Comments
 (0)