@durable-streams/client-conformance-tests 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +451 -0
  2. package/dist/adapters/typescript-adapter.d.ts +1 -0
  3. package/dist/adapters/typescript-adapter.js +586 -0
  4. package/dist/benchmark-runner-C_Yghc8f.js +1333 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/cli.js +265 -0
  7. package/dist/index.d.ts +508 -0
  8. package/dist/index.js +4 -0
  9. package/dist/protocol-DyEvTHPF.d.ts +472 -0
  10. package/dist/protocol-qb83AeUH.js +120 -0
  11. package/dist/protocol.d.ts +2 -0
  12. package/dist/protocol.js +3 -0
  13. package/package.json +53 -0
  14. package/src/adapters/typescript-adapter.ts +848 -0
  15. package/src/benchmark-runner.ts +860 -0
  16. package/src/benchmark-scenarios.ts +311 -0
  17. package/src/cli.ts +294 -0
  18. package/src/index.ts +50 -0
  19. package/src/protocol.ts +656 -0
  20. package/src/runner.ts +1191 -0
  21. package/src/test-cases.ts +475 -0
  22. package/test-cases/consumer/cache-headers.yaml +150 -0
  23. package/test-cases/consumer/error-handling.yaml +108 -0
  24. package/test-cases/consumer/message-ordering.yaml +209 -0
  25. package/test-cases/consumer/offset-handling.yaml +209 -0
  26. package/test-cases/consumer/offset-resumption.yaml +197 -0
  27. package/test-cases/consumer/read-catchup.yaml +173 -0
  28. package/test-cases/consumer/read-longpoll.yaml +132 -0
  29. package/test-cases/consumer/read-sse.yaml +145 -0
  30. package/test-cases/consumer/retry-resilience.yaml +160 -0
  31. package/test-cases/consumer/streaming-equivalence.yaml +226 -0
  32. package/test-cases/lifecycle/dynamic-headers.yaml +147 -0
  33. package/test-cases/lifecycle/headers-params.yaml +117 -0
  34. package/test-cases/lifecycle/stream-lifecycle.yaml +148 -0
  35. package/test-cases/producer/append-data.yaml +142 -0
  36. package/test-cases/producer/batching.yaml +112 -0
  37. package/test-cases/producer/create-stream.yaml +87 -0
  38. package/test-cases/producer/error-handling.yaml +90 -0
  39. package/test-cases/producer/sequence-ordering.yaml +148 -0
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.js ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ import "./protocol-qb83AeUH.js";
3
+ import { runBenchmarks, runConformanceTests } from "./benchmark-runner-C_Yghc8f.js";
4
+
5
+ //#region src/cli.ts
6
+ const HELP = `
7
+ Durable Streams Client Conformance Test Suite
8
+
9
+ Usage:
10
+ npx @durable-streams/client-conformance-tests --run <adapter> [options]
11
+ npx @durable-streams/client-conformance-tests --bench <adapter> [options]
12
+
13
+ Arguments:
14
+ <adapter> Path to client adapter executable, or "ts" for built-in TypeScript adapter
15
+
16
+ Conformance Test Options:
17
+ --run <adapter> Run conformance tests with the specified adapter
18
+ --suite <name> Run only specific suite(s): producer, consumer, lifecycle
19
+ Can be specified multiple times
20
+ --tag <name> Run only tests with specific tag(s)
21
+ Can be specified multiple times
22
+ --fail-fast Stop on first test failure
23
+ --timeout <ms> Timeout for each test in milliseconds (default: 30000)
24
+
25
+ Benchmark Options:
26
+ --bench <adapter> Run benchmarks with the specified adapter
27
+ --scenario <id> Run only specific scenario(s) by ID
28
+ Can be specified multiple times
29
+ --category <name> Run only scenarios in category: latency, throughput, streaming
30
+ Can be specified multiple times
31
+ --format <fmt> Output format: console, json, markdown (default: console)
32
+
33
+ Common Options:
34
+ --verbose Show detailed output for each operation
35
+ --port <port> Port for reference server (default: random)
36
+ --help, -h Show this help message
37
+
38
+ Conformance Test Examples:
39
+ # Test the TypeScript client
40
+ npx @durable-streams/client-conformance-tests --run ts
41
+
42
+ # Test a Python client adapter
43
+ npx @durable-streams/client-conformance-tests --run ./adapters/python_adapter.py
44
+
45
+ # Test only producer functionality
46
+ npx @durable-streams/client-conformance-tests --run ts --suite producer
47
+
48
+ # Test with verbose output and stop on first failure
49
+ npx @durable-streams/client-conformance-tests --run ts --verbose --fail-fast
50
+
51
+ Benchmark Examples:
52
+ # Run all benchmarks with TypeScript client
53
+ npx @durable-streams/client-conformance-tests --bench ts
54
+
55
+ # Run only latency benchmarks
56
+ npx @durable-streams/client-conformance-tests --bench ts --category latency
57
+
58
+ # Run specific scenario
59
+ npx @durable-streams/client-conformance-tests --bench ts --scenario latency-append
60
+
61
+ # Output as JSON for CI
62
+ npx @durable-streams/client-conformance-tests --bench ts --format json
63
+
64
+ Implementing a Client Adapter:
65
+ A client adapter is an executable that communicates via stdin/stdout using
66
+ JSON-line protocol. See the documentation for the protocol specification
67
+ and examples in different languages.
68
+
69
+ The adapter receives JSON commands on stdin (one per line) and responds
70
+ with JSON results on stdout (one per line).
71
+
72
+ Commands: init, create, connect, append, read, head, delete, shutdown, benchmark
73
+
74
+ Example flow:
75
+ Runner -> Client: {"type":"init","serverUrl":"http://localhost:3000"}
76
+ Client -> Runner: {"type":"init","success":true,"clientName":"my-client","clientVersion":"1.0.0"}
77
+ Runner -> Client: {"type":"create","path":"/test-stream"}
78
+ Client -> Runner: {"type":"create","success":true,"status":201}
79
+ ...
80
+ `;
81
+ function parseArgs(args) {
82
+ let mode = null;
83
+ let clientAdapter = ``;
84
+ const suites = [];
85
+ const tags = [];
86
+ let failFast = false;
87
+ let testTimeout = 3e4;
88
+ const scenarios = [];
89
+ const categories = [];
90
+ let format = `console`;
91
+ let verbose = false;
92
+ let serverPort = 0;
93
+ let i = 0;
94
+ while (i < args.length) {
95
+ const arg = args[i];
96
+ if (arg === `--help` || arg === `-h`) {
97
+ console.log(HELP);
98
+ process.exit(0);
99
+ }
100
+ if (arg === `--run`) {
101
+ mode = `conformance`;
102
+ i++;
103
+ if (i >= args.length) {
104
+ console.error(`Error: --run requires an adapter path`);
105
+ return null;
106
+ }
107
+ clientAdapter = args[i];
108
+ } else if (arg === `--bench`) {
109
+ mode = `benchmark`;
110
+ i++;
111
+ if (i >= args.length) {
112
+ console.error(`Error: --bench requires an adapter path`);
113
+ return null;
114
+ }
115
+ clientAdapter = args[i];
116
+ } else if (arg === `--suite`) {
117
+ i++;
118
+ if (i >= args.length) {
119
+ console.error(`Error: --suite requires a suite name`);
120
+ return null;
121
+ }
122
+ const suite = args[i];
123
+ if (![
124
+ `producer`,
125
+ `consumer`,
126
+ `lifecycle`
127
+ ].includes(suite)) {
128
+ console.error(`Error: Invalid suite "${suite}". Must be: producer, consumer, lifecycle`);
129
+ return null;
130
+ }
131
+ suites.push(suite);
132
+ } else if (arg === `--tag`) {
133
+ i++;
134
+ if (i >= args.length) {
135
+ console.error(`Error: --tag requires a tag name`);
136
+ return null;
137
+ }
138
+ tags.push(args[i]);
139
+ } else if (arg === `--scenario`) {
140
+ i++;
141
+ if (i >= args.length) {
142
+ console.error(`Error: --scenario requires a scenario ID`);
143
+ return null;
144
+ }
145
+ scenarios.push(args[i]);
146
+ } else if (arg === `--category`) {
147
+ i++;
148
+ if (i >= args.length) {
149
+ console.error(`Error: --category requires a category name`);
150
+ return null;
151
+ }
152
+ const category = args[i];
153
+ if (![
154
+ `latency`,
155
+ `throughput`,
156
+ `streaming`
157
+ ].includes(category)) {
158
+ console.error(`Error: Invalid category "${category}". Must be: latency, throughput, streaming`);
159
+ return null;
160
+ }
161
+ categories.push(category);
162
+ } else if (arg === `--format`) {
163
+ i++;
164
+ if (i >= args.length) {
165
+ console.error(`Error: --format requires a format name`);
166
+ return null;
167
+ }
168
+ const fmt = args[i];
169
+ if (![
170
+ `console`,
171
+ `json`,
172
+ `markdown`
173
+ ].includes(fmt)) {
174
+ console.error(`Error: Invalid format "${fmt}". Must be: console, json, markdown`);
175
+ return null;
176
+ }
177
+ format = fmt;
178
+ } else if (arg === `--verbose`) verbose = true;
179
+ else if (arg === `--fail-fast`) failFast = true;
180
+ else if (arg === `--timeout`) {
181
+ i++;
182
+ if (i >= args.length) {
183
+ console.error(`Error: --timeout requires a value in milliseconds`);
184
+ return null;
185
+ }
186
+ testTimeout = parseInt(args[i], 10);
187
+ if (isNaN(testTimeout)) {
188
+ console.error(`Error: --timeout must be a number`);
189
+ return null;
190
+ }
191
+ } else if (arg === `--port`) {
192
+ i++;
193
+ if (i >= args.length) {
194
+ console.error(`Error: --port requires a port number`);
195
+ return null;
196
+ }
197
+ serverPort = parseInt(args[i], 10);
198
+ if (isNaN(serverPort)) {
199
+ console.error(`Error: --port must be a number`);
200
+ return null;
201
+ }
202
+ } else if (arg.startsWith(`-`)) {
203
+ console.error(`Error: Unknown option "${arg}"`);
204
+ return null;
205
+ }
206
+ i++;
207
+ }
208
+ if (!mode || !clientAdapter) {
209
+ console.error(`Error: --run <adapter> or --bench <adapter> is required`);
210
+ console.log(`\nRun with --help for usage information`);
211
+ return null;
212
+ }
213
+ if (mode === `conformance`) {
214
+ const options = {
215
+ clientAdapter,
216
+ verbose,
217
+ failFast,
218
+ testTimeout,
219
+ serverPort
220
+ };
221
+ if (suites.length > 0) options.suites = suites;
222
+ if (tags.length > 0) options.tags = tags;
223
+ return {
224
+ mode: `conformance`,
225
+ options
226
+ };
227
+ } else {
228
+ const options = {
229
+ clientAdapter,
230
+ verbose,
231
+ serverPort,
232
+ format
233
+ };
234
+ if (scenarios.length > 0) options.scenarios = scenarios;
235
+ if (categories.length > 0) options.categories = categories;
236
+ return {
237
+ mode: `benchmark`,
238
+ options
239
+ };
240
+ }
241
+ }
242
+ async function main() {
243
+ const args = process.argv.slice(2);
244
+ if (args.length === 0) {
245
+ console.log(HELP);
246
+ process.exit(0);
247
+ }
248
+ const parsed = parseArgs(args);
249
+ if (!parsed) process.exit(1);
250
+ try {
251
+ if (parsed.mode === `conformance`) {
252
+ const summary = await runConformanceTests(parsed.options);
253
+ if (summary.failed > 0) process.exit(1);
254
+ } else {
255
+ const summary = await runBenchmarks(parsed.options);
256
+ if (summary.failed > 0) process.exit(1);
257
+ }
258
+ } catch (err) {
259
+ console.error(`Error running ${parsed.mode}:`, err);
260
+ process.exit(1);
261
+ }
262
+ }
263
+ main();
264
+
265
+ //#endregion