Skip to content

Commit 0b2410d

Browse files
kittaakosper1234
andauthored
test: run cloud sketches tests on the CI (#2092)
- fix(test): integration tests are more resilient. - run the Create integration tests with other slow tests, - queued `PUT`/`DELETE` requests to make the test timeout happy, - reduced the `/sketches/search` offset to 1/5th and - remove Create API logging. - fix(linter): ignore `lib` folder. Remove obsolete `.node_modules` pattern. - feat(ci): enable Create API integration tests. Signed-off-by: Akos Kitta <[email protected]> Co-authored-by: per1234 <[email protected]>
1 parent 3f4d274 commit 0b2410d

File tree

5 files changed

+94
-37
lines changed

5 files changed

+94
-37
lines changed

Diff for: .eslintrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ module.exports = {
1010
ignorePatterns: [
1111
'node_modules/*',
1212
'**/node_modules/*',
13-
'.node_modules/*',
1413
'.github/*',
1514
'.browser_modules/*',
1615
'docs/*',
@@ -21,6 +20,7 @@ module.exports = {
2120
'!electron-app/webpack.config.js',
2221
'plugins/*',
2322
'arduino-ide-extension/src/node/cli-protocol',
23+
'**/lib/*',
2424
],
2525
settings: {
2626
react: {

Diff for: .github/workflows/build.yml

+5
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,11 @@ jobs:
296296
IS_NIGHTLY: ${{ needs.build-type-determination.outputs.is-nightly }}
297297
IS_RELEASE: ${{ needs.build-type-determination.outputs.is-release }}
298298
CAN_SIGN: ${{ secrets[matrix.config.certificate-secret] != '' }}
299+
# The CREATE_* environment vars are only used to run tests. These secrets are optional. Dependent tests will
300+
# be skipped if not available.
301+
CREATE_USERNAME: ${{ secrets.CREATE_USERNAME }}
302+
CREATE_PASSWORD: ${{ secrets.CREATE_PASSWORD }}
303+
CREATE_CLIENT_SECRET: ${{ secrets.CREATE_CLIENT_SECRET }}
299304
run: |
300305
# See: https://www.electron.build/code-signing
301306
if [ $CAN_SIGN = false ]; then

Diff for: arduino-ide-extension/src/browser/create/create-api.ts

+2-18
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ export class CreateApi {
179179
);
180180
})
181181
.catch((reason) => {
182-
if (reason?.status === 404) return [] as Create.Resource[];
182+
if (reason?.status === 404)
183+
return [] as Create.Resource[]; // TODO: must not swallow 404
183184
else throw reason;
184185
});
185186
}
@@ -486,18 +487,12 @@ export class CreateApi {
486487
await this.run(url, init, ResponseResultProvider.NOOP);
487488
}
488489

489-
private fetchCounter = 0;
490490
private async run<T>(
491491
requestInfo: URL,
492492
init: RequestInit | undefined,
493493
resultProvider: ResponseResultProvider = ResponseResultProvider.JSON
494494
): Promise<T> {
495-
const fetchCount = `[${++this.fetchCounter}]`;
496-
const fetchStart = performance.now();
497-
const method = init?.method ? `${init.method}: ` : '';
498-
const url = requestInfo.toString();
499495
const response = await fetch(requestInfo.toString(), init);
500-
const fetchEnd = performance.now();
501496
if (!response.ok) {
502497
let details: string | undefined = undefined;
503498
try {
@@ -508,18 +503,7 @@ export class CreateApi {
508503
const { statusText, status } = response;
509504
throw new CreateError(statusText, status, details);
510505
}
511-
const parseStart = performance.now();
512506
const result = await resultProvider(response);
513-
const parseEnd = performance.now();
514-
console.debug(
515-
`HTTP ${fetchCount} ${method}${url} [fetch: ${(
516-
fetchEnd - fetchStart
517-
).toFixed(2)} ms, parse: ${(parseEnd - parseStart).toFixed(
518-
2
519-
)} ms] body: ${
520-
typeof result === 'string' ? result : JSON.stringify(result)
521-
}`
522-
);
523507
return result;
524508
}
525509

Diff for: arduino-ide-extension/src/browser/create/typings.ts

+7
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ export function isNotFound(err: unknown): err is NotFoundError {
8282
return isErrorWithStatusOf(err, 404);
8383
}
8484

85+
export type UnprocessableContentError = CreateError & { status: 422 };
86+
export function isUnprocessableContent(
87+
err: unknown
88+
): err is UnprocessableContentError {
89+
return isErrorWithStatusOf(err, 422);
90+
}
91+
8592
function isErrorWithStatusOf(
8693
err: unknown,
8794
status: number

Diff for: arduino-ide-extension/src/test/browser/create-api.test.ts renamed to arduino-ide-extension/src/test/browser/create-api.slow-test.ts

+79-18
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,24 @@ import {
55
} from '@theia/core/shared/inversify';
66
import { assert, expect } from 'chai';
77
import fetch from 'cross-fetch';
8+
import { rejects } from 'node:assert';
89
import { posix } from 'node:path';
10+
import PQueue from 'p-queue';
11+
import queryString from 'query-string';
912
import { v4 } from 'uuid';
1013
import { ArduinoPreferences } from '../../browser/arduino-preferences';
1114
import { AuthenticationClientService } from '../../browser/auth/authentication-client-service';
1215
import { CreateApi } from '../../browser/create/create-api';
1316
import { splitSketchPath } from '../../browser/create/create-paths';
14-
import { Create, CreateError } from '../../browser/create/typings';
17+
import {
18+
Create,
19+
CreateError,
20+
isNotFound,
21+
isUnprocessableContent,
22+
} from '../../browser/create/typings';
1523
import { SketchCache } from '../../browser/widgets/cloud-sketchbook/cloud-sketch-cache';
1624
import { SketchesService } from '../../common/protocol';
1725
import { AuthenticationSession } from '../../node/auth/types';
18-
import queryString from 'query-string';
1926

2027
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
2128
/* eslint-disable @typescript-eslint/no-non-null-assertion */
@@ -44,6 +51,11 @@ describe('create-api', () => {
4451
await cleanAllSketches();
4552
});
4653

54+
afterEach(async function () {
55+
this.timeout(timeout);
56+
await cleanAllSketches();
57+
});
58+
4759
function createContainer(accessToken: string): Container {
4860
const container = new Container({ defaultScope: 'Singleton' });
4961
container.load(
@@ -120,13 +132,14 @@ describe('create-api', () => {
120132

121133
async function cleanAllSketches(): Promise<void> {
122134
let sketches = await createApi.sketches();
123-
// Cannot delete the sketches with `await Promise.all` as all delete promise successfully resolve, but the sketch is not deleted from the server.
124-
await sketches
125-
.map(({ path }) => createApi.deleteSketch(path))
126-
.reduce(async (acc, curr) => {
127-
await acc;
128-
return curr;
129-
}, Promise.resolve());
135+
const deleteExecutionQueue = new PQueue({
136+
concurrency: 5,
137+
autoStart: true,
138+
});
139+
sketches.forEach(({ path }) =>
140+
deleteExecutionQueue.add(() => createApi.deleteSketch(path))
141+
);
142+
await deleteExecutionQueue.onIdle();
130143
sketches = await createApi.sketches();
131144
expect(sketches).to.be.empty;
132145
}
@@ -229,8 +242,52 @@ describe('create-api', () => {
229242
expect(findByName(otherName, sketches)).to.be.not.undefined;
230243
});
231244

245+
it('should error with HTTP 422 when reading a file but is a directory', async () => {
246+
const name = v4();
247+
const content = 'void setup(){} void loop(){}';
248+
const posixPath = toPosix(name);
249+
250+
await createApi.createSketch(posixPath, content);
251+
const resources = await createApi.readDirectory(posixPath);
252+
expect(resources).to.be.not.empty;
253+
254+
await rejects(createApi.readFile(posixPath), (thrown) =>
255+
isUnprocessableContent(thrown)
256+
);
257+
});
258+
259+
it('should error with HTTP 422 when listing a directory but is a file', async () => {
260+
const name = v4();
261+
const content = 'void setup(){} void loop(){}';
262+
const posixPath = toPosix(name);
263+
264+
await createApi.createSketch(posixPath, content);
265+
const mainSketchFilePath = posixPath + posixPath + '.ino';
266+
const sketchContent = await createApi.readFile(mainSketchFilePath);
267+
expect(sketchContent).to.be.equal(content);
268+
269+
await rejects(createApi.readDirectory(mainSketchFilePath), (thrown) =>
270+
isUnprocessableContent(thrown)
271+
);
272+
});
273+
274+
it("should error with HTTP 404 when deleting a non-existing directory via the '/files/d' endpoint", async () => {
275+
const name = v4();
276+
const posixPath = toPosix(name);
277+
278+
const sketches = await createApi.sketches();
279+
const sketch = findByName(name, sketches);
280+
expect(sketch).to.be.undefined;
281+
282+
await rejects(createApi.deleteDirectory(posixPath), (thrown) =>
283+
isNotFound(thrown)
284+
);
285+
});
286+
232287
['.', '-', '_'].map((char) => {
233-
it(`should create a new sketch with '${char}' in the sketch folder name although it's disallowed from the Create Editor`, async () => {
288+
it(`should create a new sketch with '${char}' (character code: ${char.charCodeAt(
289+
0
290+
)}) in the sketch folder name although it's disallowed from the Create Editor`, async () => {
234291
const name = `sketch${char}`;
235292
const posixPath = toPosix(name);
236293
const newSketch = await createApi.createSketch(
@@ -300,19 +357,23 @@ describe('create-api', () => {
300357
diff < 0 ? '<' : diff > 0 ? '>' : '='
301358
} limit)`, async () => {
302359
const content = 'void setup(){} void loop(){}';
303-
const maxLimit = 50; // https://github.com/arduino/arduino-ide/pull/875
360+
const maxLimit = 10;
304361
const sketchCount = maxLimit + diff;
305362
const sketchNames = [...Array(sketchCount).keys()].map(() => v4());
306363

307-
await sketchNames
308-
.map((name) => createApi.createSketch(toPosix(name), content))
309-
.reduce(async (acc, curr) => {
310-
await acc;
311-
return curr;
312-
}, Promise.resolve() as Promise<unknown>);
364+
const createExecutionQueue = new PQueue({
365+
concurrency: 5,
366+
autoStart: true,
367+
});
368+
sketchNames.forEach((name) =>
369+
createExecutionQueue.add(() =>
370+
createApi.createSketch(toPosix(name), content)
371+
)
372+
);
373+
await createExecutionQueue.onIdle();
313374

314375
createApi.resetRequestRecording();
315-
const sketches = await createApi.sketches();
376+
const sketches = await createApi.sketches(maxLimit);
316377
const allRequests = createApi.requestRecording.slice();
317378

318379
expect(sketches.length).to.be.equal(sketchCount);

0 commit comments

Comments
 (0)