Skip to content

Commit 868fc9a

Browse files
authored
feat(cts): test the timeouts of the retry stragety (#3264)
1 parent e9def4a commit 868fc9a

File tree

8 files changed

+117
-31
lines changed

8 files changed

+117
-31
lines changed

scripts/cts/runCts.ts

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createSpinner } from '../spinners.js';
55
import type { Language } from '../types.js';
66

77
import { startTestServer } from './testServer';
8-
import { getTimeoutCounter } from './testServer/timeout.js';
8+
import { assertValidTimeouts } from './testServer/timeout.js';
99

1010
async function runCtsOne(language: string): Promise<void> {
1111
const spinner = createSpinner(`running cts for '${language}'`);
@@ -86,17 +86,9 @@ export async function runCts(languages: Language[], clients: string[]): Promise<
8686
if (useTestServer) {
8787
await close();
8888

89-
if (languages.length !== getTimeoutCounter()) {
90-
throw new Error(
91-
`Expected ${languages.length} timeout(s), got ${getTimeoutCounter()} instead.`,
92-
);
93-
}
89+
assertValidTimeouts(languages.length);
9490

9591
// uncomment this once all languages are supported
96-
// if (languages.length !== numberOfSuccessfulReplaceAllObjectsCalls()) {
97-
// throw new Error(
98-
// `Expected ${languages.length} call to replaceAllObjects, got ${numberOfSuccessfulReplaceAllObjectsCalls()} instead.`,
99-
// );
100-
// }
92+
// assertValidReplaceAllObjects(languages.length);
10193
}
10294
}

scripts/cts/testServer/gzip.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import zlib from 'zlib';
44
import crc32 from 'crc/crc32';
55
import type { Express } from 'express';
66

7+
import { retryHandler } from './timeout';
8+
79
import { setupServer } from '.';
810

911
function addRoutes(app: Express): void {
10-
app.get('/1/test/retry', (req, res) => {
11-
res.json({ message: 'ok test server response' });
12-
});
12+
app.get('/1/test/retry/:lang', retryHandler(0, 'ok test server response'));
1313

1414
app.post('/1/test/gzip', (req, res) => {
1515
let rawBody = Buffer.from([]);

scripts/cts/testServer/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ import { createSpinner } from '../../spinners';
88
import { gzipServer } from './gzip';
99
import { replaceAllObjectsServer } from './replaceAllObjects';
1010
import { timeoutServer } from './timeout';
11+
import { timeoutServerBis } from './timeoutBis';
1112

1213
export async function startTestServer(): Promise<() => Promise<void>> {
13-
const servers = await Promise.all([timeoutServer(), gzipServer(), replaceAllObjectsServer()]);
14+
const servers = await Promise.all([
15+
timeoutServer(),
16+
timeoutServerBis(),
17+
gzipServer(),
18+
replaceAllObjectsServer(),
19+
]);
1420

1521
return async () => {
1622
await Promise.all(

scripts/cts/testServer/replaceAllObjects.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@ const raoState: Record<
1717
}
1818
> = {};
1919

20-
export function numberOfSuccessfulReplaceAllObjectsCalls(): number {
21-
return Object.values(raoState).filter((s) => s.successful).length;
20+
export function assertValidReplaceAllObjects(expectedCount: number): void {
21+
const count = Object.values(raoState).filter((s) => s.successful).length;
22+
if (count !== expectedCount) {
23+
throw new Error(`Expected ${expectedCount} call to replaceAllObjects, got ${count} instead.`);
24+
}
2225
}
2326

2427
function addRoutes(app: Express): void {

scripts/cts/testServer/timeout.ts

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,89 @@ import type express from 'express';
44

55
import { setupServer } from '.';
66

7-
let timeoutCounter = 0;
7+
const timeoutState: Record<string, { timestamp: number[]; duration: number[] }> = {};
88

9-
export function getTimeoutCounter(): number {
10-
return timeoutCounter;
9+
function aboutEqual(a: number, b: number, epsilon = 100): boolean {
10+
return Math.abs(a - b) <= epsilon;
11+
}
12+
13+
export function assertValidTimeouts(expectedCount: number): void {
14+
// assert that the retry strategy uses the correct timings, by checking the time between each request, and how long each request took before being timed out
15+
// there should be no delay between requests, only an increase in timeout.
16+
if (Object.keys(timeoutState).length !== expectedCount) {
17+
throw new Error(`Expected ${expectedCount} timeout(s)`);
18+
}
19+
20+
for (const [lang, state] of Object.entries(timeoutState)) {
21+
if (state.timestamp.length !== 3 || state.duration.length !== 3) {
22+
throw new Error(`Expected 3 requests for ${lang}, got ${state.timestamp.length}`);
23+
}
24+
25+
let delay = state.timestamp[1] - state.timestamp[0];
26+
if (!aboutEqual(delay, state.duration[0])) {
27+
throw new Error(`Expected no delay between requests for ${lang}, got ${delay}ms`);
28+
}
29+
30+
delay = state.timestamp[2] - state.timestamp[1];
31+
if (!aboutEqual(delay, state.duration[1])) {
32+
throw new Error(`Expected no delay between requests for ${lang}, got ${delay}ms`);
33+
}
34+
35+
// languages are not consistent yet for the delay between requests
36+
switch (lang) {
37+
case 'JavaScript':
38+
if (!aboutEqual(state.duration[0] * 4, state.duration[1], 200)) {
39+
throw new Error(`Expected increasing delay between requests for ${lang}`);
40+
}
41+
break;
42+
case 'PHP':
43+
if (!aboutEqual(state.duration[0] * 2, state.duration[1], 200)) {
44+
throw new Error(`Expected increasing delay between requests for ${lang}`);
45+
}
46+
break;
47+
default:
48+
// the delay should be the same, because the `retryCount` is per host instead of global
49+
if (!aboutEqual(state.duration[0], state.duration[1])) {
50+
throw new Error(`Expected the same delay between requests for ${lang}`);
51+
}
52+
break;
53+
}
54+
}
55+
}
56+
57+
export function retryHandler(after: number, message: string): express.RequestHandler {
58+
return (req, res) => {
59+
const timeout = setTimeout(() => {
60+
res.json({ message });
61+
}, after);
62+
63+
const lang = req.params.lang;
64+
const startTime = Date.now();
65+
if (!timeoutState[lang]) {
66+
timeoutState[lang] = {
67+
timestamp: [],
68+
duration: [],
69+
};
70+
}
71+
72+
timeoutState[lang].timestamp.push(startTime);
73+
74+
req.on('close', () => {
75+
const endTime = Date.now();
76+
const duration = endTime - startTime;
77+
timeoutState[lang].duration.push(duration);
78+
79+
clearTimeout(timeout);
80+
res.end();
81+
});
82+
};
1183
}
1284

1385
function addRoutes(app: express.Express): void {
1486
// this endpoint is also defined in the gzip server but without the timeout
15-
app.get('/1/test/retry', (req, res) => {
16-
// this is safe because js is single threaded
17-
timeoutCounter++;
18-
// wait for 7.5 seconds, the default read timeout is 5 seconds + 2s of connection timeout
19-
setTimeout(() => {
20-
res.json({ message: 'timeout test server response' });
21-
}, 7500);
22-
});
87+
app.get('/1/test/retry/:lang', retryHandler(20000, 'timeout test server response'));
2388
}
2489

2590
export function timeoutServer(): Promise<Server> {
26-
return setupServer('timeout', 6677, addRoutes);
91+
return setupServer('timeout', 6676, addRoutes);
2792
}

scripts/cts/testServer/timeoutBis.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Server } from 'http';
2+
3+
import type express from 'express';
4+
5+
import { retryHandler } from './timeout';
6+
7+
import { setupServer } from '.';
8+
9+
function addRoutes(app: express.Express): void {
10+
// this endpoint is also defined in the gzip server but without the timeout
11+
app.get('/1/test/retry/:lang', retryHandler(20000, 'timeout bis test server response'));
12+
}
13+
14+
export function timeoutServerBis(): Promise<Server> {
15+
return setupServer('timeoutBis', 6677, addRoutes);
16+
}

templates/javascript/tests/client/suite.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ describe('{{testType}}', () => {
5151
{{/testResponse}}
5252
{{/isError}}
5353
{{/steps}}
54-
});
54+
}, 15000);
5555

5656
{{/tests}}
5757
})

tests/CTS/client/search/api.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959
"appId": "test-app-id",
6060
"apiKey": "test-api-key",
6161
"customHosts": [
62+
{
63+
"host": "${{localhost}}",
64+
"port": 6676
65+
},
6266
{
6367
"host": "${{localhost}}",
6468
"port": 6677
@@ -75,7 +79,7 @@
7579
"object": "$client",
7680
"path": "customGet",
7781
"parameters": {
78-
"path": "1/test/retry"
82+
"path": "1/test/retry/${{languageCased}}"
7983
},
8084
"expected": {
8185
"type": "response",

0 commit comments

Comments
 (0)