Skip to content

Commit d2b0f7d

Browse files
authored
fix(cts): retry e2e tests (#3341)
1 parent a009967 commit d2b0f7d

File tree

14 files changed

+135
-70
lines changed

14 files changed

+135
-70
lines changed

.github/workflows/check.yml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,13 @@ jobs:
242242
key: node-modules-tests-${{ hashFiles('tests/output/javascript/yarn.lock') }}
243243

244244
- name: Run CTS
245-
run: yarn cli cts run javascript ${{ fromJSON(needs.setup.outputs.JAVASCRIPT_DATA).toRun }}
245+
id: cts
246+
continue-on-error: true
247+
run: yarn cli cts run javascript ${{ fromJSON(needs.setup.outputs.JAVASCRIPT_DATA).toRun }} ${{ github.event.pull_request.head.repo.fork && '--exclude-e2e' || '' }}
248+
249+
- name: Retry e2e CTS
250+
if: ${{ !github.event.pull_request.head.repo.fork && github.event.number && steps.cts.outcome == 'failure' }}
251+
run: yarn cli cts run javascript ${{ fromJSON(needs.setup.outputs.JAVASCRIPT_DATA).toRun }} --exclude-unit
246252

247253
- name: Generate code snippets for documentation
248254
run: yarn cli snippets javascript ${{ fromJSON(needs.setup.outputs.JAVASCRIPT_DATA).toRun }}
@@ -326,7 +332,13 @@ jobs:
326332
run: yarn cli cts generate ${{ matrix.client.language }} ${{ matrix.client.toRun }}
327333

328334
- name: Run CTS
329-
run: yarn cli cts run ${{ matrix.client.language }} ${{ matrix.client.toRun }}
335+
id: cts
336+
continue-on-error: true
337+
run: yarn cli cts run ${{ matrix.client.language }} ${{ matrix.client.toRun }} ${{ github.event.pull_request.head.repo.fork && '--exclude-e2e' || '' }}
338+
339+
- name: Retry e2e CTS
340+
if: ${{ !github.event.pull_request.head.repo.fork && steps.cts.outcome == 'failure' }}
341+
run: yarn cli cts run ${{ matrix.client.language }} ${{ matrix.client.toRun }} --exclude-unit
330342

331343
- name: Generate code snippets for documentation
332344
run: yarn cli snippets ${{ matrix.client.language }} ${{ matrix.client.toRun }}
@@ -522,6 +534,8 @@ jobs:
522534
- check_green
523535
if: |
524536
always() &&
537+
!contains(needs.*.result, 'cancelled') &&
538+
!contains(needs.*.result, 'failure') &&
525539
github.ref == 'refs/heads/main'
526540
permissions:
527541
pull-requests: write

netlify.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
publish="website/build"
44

55
[build.environment]
6-
YARN_VERSION = "3.5.0"
6+
YARN_VERSION = "4.3.1"

scripts/cli/index.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -129,16 +129,29 @@ ctsCommand
129129
.addArgument(args.language)
130130
.addArgument(args.clients)
131131
.option(flags.verbose.flag, flags.verbose.description)
132-
.action(async (langArg: LangArg, clientArg: string[], { verbose }) => {
133-
const { language, client } = transformSelection({
134-
langArg,
135-
clientArg,
136-
});
137-
138-
setVerbose(Boolean(verbose));
139-
140-
await runCts(language === ALL ? LANGUAGES : [language], client);
141-
});
132+
.option('-e, --exclude-e2e', "don't run the e2e tests, useful for offline testing")
133+
.option('-u, --exclude-unit', "don't run the client and requests tests")
134+
.action(
135+
async (
136+
langArg: LangArg,
137+
clientArg: string[],
138+
{ verbose, excludeE2e: excludeE2E, excludeUnit },
139+
) => {
140+
const { language, client } = transformSelection({
141+
langArg,
142+
clientArg,
143+
});
144+
145+
setVerbose(Boolean(verbose));
146+
147+
await runCts(
148+
language === ALL ? LANGUAGES : [language],
149+
client,
150+
Boolean(excludeE2E),
151+
Boolean(excludeUnit),
152+
);
153+
},
154+
);
142155

143156
ctsCommand
144157
.command('server')

scripts/cts/runCts.ts

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,86 @@ import { assertChunkWrapperValid } from './testServer/chunkWrapper.js';
77
import { assertValidReplaceAllObjects } from './testServer/replaceAllObjects.js';
88
import { assertValidTimeouts } from './testServer/timeout.js';
99

10-
async function runCtsOne(language: string): Promise<void> {
11-
const spinner = createSpinner(`running cts for '${language}'`);
10+
async function runCtsOne(
11+
language: Language,
12+
excludeE2E: boolean,
13+
excludeUnit: boolean,
14+
): Promise<void> {
1215
const cwd = `tests/output/${language}`;
16+
17+
const folders: string[] = [];
18+
if (language !== 'dart' && language !== 'kotlin' && !excludeE2E) {
19+
folders.push('e2e');
20+
}
21+
if (!excludeUnit) {
22+
folders.push('client', 'requests');
23+
}
24+
25+
const spinner = createSpinner(`running cts for '${language}' in folder(s) ${folders.join(', ')}`);
26+
27+
if (folders.length === 0) {
28+
spinner.succeed(`skipping '${language}' because all tests are excluded`);
29+
return;
30+
}
31+
32+
const filter = (mapper: (f: string) => string): string => folders.map(mapper).join(' ');
33+
1334
switch (language) {
1435
case 'csharp':
15-
await run('dotnet test /clp:ErrorsOnly', { cwd, language });
36+
await run(
37+
`dotnet test /clp:ErrorsOnly --filter 'Algolia.Search.Tests${folders.map((f) => `|Algolia.Search.${f}`).join('')}'`,
38+
{ cwd, language },
39+
);
1640
break;
1741
case 'dart':
18-
await run('dart test', { cwd, language });
19-
break;
20-
case 'go':
21-
await run(`go test -race -count 1 ${isVerbose() ? '-v' : ''} ./...`, {
42+
await run(`dart test ${filter((f) => `test/${f}`)}`, {
2243
cwd,
2344
language,
2445
});
2546
break;
47+
case 'go':
48+
await run(
49+
`go test -race -count 1 ${isVerbose() ? '-v' : ''} ${filter((f) => `gotests/tests/${f}/...`)}`,
50+
{
51+
cwd,
52+
language,
53+
},
54+
);
55+
break;
2656
case 'java':
27-
await run('./gradle/gradlew -p tests/output/java test --rerun', { language });
57+
await run(
58+
`./gradle/gradlew -p tests/output/java test --rerun ${filter((f) => `--tests 'com.algolia.${f}*'`)}`,
59+
{ language },
60+
);
2861
break;
2962
case 'javascript':
30-
await run('YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install && yarn test', {
31-
cwd,
32-
});
63+
await run(
64+
`YARN_ENABLE_IMMUTABLE_INSTALLS=false yarn install && yarn test ${filter((f) => `dist/${f}`)}`,
65+
{
66+
cwd,
67+
},
68+
);
3369
break;
3470
case 'kotlin':
3571
await run('./gradle/gradlew -p tests/output/kotlin allTests', { language });
3672
break;
3773
case 'php':
3874
await runComposerInstall();
3975
await run(
40-
`php ./clients/algoliasearch-client-php/vendor/bin/phpunit --testdox --fail-on-warning ${cwd}`,
76+
`php ./clients/algoliasearch-client-php/vendor/bin/phpunit --testdox --fail-on-warning ${filter((f) => `${cwd}/src/${f}`)}`,
4177
{
4278
language,
4379
},
4480
);
4581
break;
4682
case 'python':
47-
await run('poetry lock --no-update && poetry install --sync && poetry run pytest -vv', {
48-
cwd,
49-
language,
50-
});
83+
await run(
84+
`poetry lock --no-update && poetry install --sync && poetry run pytest -vv ${filter((f) => `tests/${f}`)}`,
85+
{
86+
cwd,
87+
language,
88+
},
89+
);
5190
break;
5291
case 'ruby':
5392
await run(`bundle install && bundle exec rake test --trace`, {
@@ -56,14 +95,20 @@ async function runCtsOne(language: string): Promise<void> {
5695
});
5796
break;
5897
case 'scala':
59-
await run('sbt test', { cwd, language });
60-
break;
61-
case 'swift':
62-
await run('swift test -Xswiftc -suppress-warnings -q --parallel', {
98+
await run(`sbt test`, {
6399
cwd,
64100
language,
65101
});
66102
break;
103+
case 'swift':
104+
await run(
105+
`swift test -Xswiftc -suppress-warnings -q --parallel ${filter((f) => `--filter ${f}.*`)}`,
106+
{
107+
cwd,
108+
language,
109+
},
110+
);
111+
break;
67112
default:
68113
spinner.warn(`skipping unknown language '${language}' to run the CTS`);
69114
return;
@@ -72,14 +117,19 @@ async function runCtsOne(language: string): Promise<void> {
72117
}
73118

74119
// the clients option is only used to determine if we need to start the test server, it will run the tests for all clients anyway.
75-
export async function runCts(languages: Language[], clients: string[]): Promise<void> {
76-
const useTestServer = clients.includes('search') || clients.includes('all');
120+
export async function runCts(
121+
languages: Language[],
122+
clients: string[],
123+
excludeE2E: boolean,
124+
excludeUnit: boolean,
125+
): Promise<void> {
126+
const useTestServer = !excludeUnit && (clients.includes('search') || clients.includes('all'));
77127
let close: () => Promise<void> = async () => {};
78128
if (useTestServer) {
79129
close = await startTestServer();
80130
}
81131
for (const lang of languages) {
82-
await runCtsOne(lang);
132+
await runCtsOne(lang, excludeE2E, excludeUnit);
83133
}
84134

85135
if (useTestServer) {

scripts/cts/testServer/timeout.ts

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import type { Server } from 'http';
22

3+
import { expect } from 'chai';
34
import type express from 'express';
45

56
import { setupServer } from '.';
67

78
const timeoutState: Record<string, { timestamp: number[]; duration: number[] }> = {};
89

9-
function aboutEqual(a: number, b: number, epsilon = 100): boolean {
10-
return Math.abs(a - b) <= epsilon;
11-
}
12-
1310
export function assertValidTimeouts(expectedCount: number): void {
1411
// 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
1512
// there should be no delay between requests, only an increase in timeout.
@@ -18,37 +15,22 @@ export function assertValidTimeouts(expectedCount: number): void {
1815
}
1916

2017
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-
}
18+
expect(state.timestamp.length).to.equal(3);
19+
expect(state.duration.length).to.equal(3);
20+
expect(state.timestamp[1] - state.timestamp[0]).to.be.closeTo(state.duration[0], 100);
21+
expect(state.timestamp[2] - state.timestamp[1]).to.be.closeTo(state.duration[1], 100);
3422

3523
// languages are not consistent yet for the delay between requests
3624
switch (lang) {
3725
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-
}
26+
expect(state.duration[0] * 4).to.be.closeTo(state.duration[1], 200);
4127
break;
4228
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-
}
29+
expect(state.duration[0] * 2).to.be.closeTo(state.duration[1], 200);
4630
break;
4731
default:
4832
// 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-
}
33+
expect(state.duration[0]).to.be.closeTo(state.duration[1], 100);
5234
break;
5335
}
5436
}

templates/csharp/tests/client/suite.mustache

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ using System.Text.Json;
1111
using Xunit;
1212
using Quibble.Xunit;
1313

14+
namespace Algolia.Search.client;
15+
1416
public class {{client}}Tests
1517
{
1618
private readonly EchoHttpRequester _echo;
@@ -69,4 +71,4 @@ public void Dispose()
6971
}
7072
{{/tests}}
7173
{{/blocksClient}}
72-
}
74+
}

templates/csharp/tests/e2e/e2e.mustache

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ using Quibble.Xunit;
1010
using dotenv.net;
1111
using Action = Algolia.Search.Models.Search.Action;
1212

13+
namespace Algolia.Search.e2e;
14+
1315
public class {{client}}RequestTestsE2E
1416
{
1517
private readonly {{client}} _client;

templates/csharp/tests/requests/requests.mustache

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ using Quibble.Xunit;
1010
using dotenv.net;
1111
using Action = Algolia.Search.Models.Search.Action;
1212

13+
namespace Algolia.Search.requests;
14+
1315
public class {{client}}RequestTests
1416
{
1517
private readonly {{client}} _client;
@@ -84,4 +86,4 @@ private readonly {{client}} _client;
8486
}
8587
{{/tests}}
8688
{{/blocksRequests}}
87-
}
89+
}

templates/go/tests/e2e/e2e.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// {{generationBanner}}
2-
package requestse2e
2+
package e2e
33

44
import (
55
"os"

templates/java/tests/e2e/e2e.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.algolia.methods.e2e;
1+
package com.algolia.e2e;
22

33
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
44

templates/java/tests/requests/requests.mustache

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package com.algolia.methods.requests;
1+
package com.algolia.requests;
22

33
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
44
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -102,4 +102,4 @@ class {{client}}RequestsTests {
102102
}
103103
{{/tests}}
104104
{{/blocksRequests}}
105-
}
105+
}

templates/javascript/tests/package.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "javascript-tests",
33
"version": "1.0.0",
44
"scripts": {
5-
"test": "rm -rf dist || true && tsc && jest dist --passWithNoTests"
5+
"test": "rm -rf dist || true && tsc && jest --passWithNoTests"
66
},
77
"dependencies": {
88
{{#packageDependencies}}

templates/kotlin/tests/requests/requests.mustache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// {{generationBanner}}
2-
package com.algolia.methods.requests
2+
package com.algolia.requests
33

44
import com.algolia.client.api.{{client}}
55
import com.algolia.client.model.{{import}}.*

tests/output/python/tests/e2e/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)