From d8b98782d44dfb537d14872dfc12beb601024f17 Mon Sep 17 00:00:00 2001 From: Jaco Date: Sat, 9 Jul 2022 18:59:17 +0200 Subject: [PATCH] Adjust CI checks (#7851) --- jest-ci.config.cjs | 10 ++ package.json | 5 +- .../apps-config/src/ci/chainEndpoints.spec.ts | 6 +- .../apps-config/src/ci/chainTypes.spec.ts | 6 +- packages/apps-config/src/ci/check.ts | 88 ++++++++++++++ packages/apps-config/src/ci/fetch.ts | 38 ++++++ packages/apps-config/src/ci/runner.ts | 37 ++++++ packages/apps-config/src/ci/util.ts | 112 ------------------ 8 files changed, 186 insertions(+), 116 deletions(-) create mode 100644 jest-ci.config.cjs create mode 100644 packages/apps-config/src/ci/check.ts create mode 100644 packages/apps-config/src/ci/fetch.ts create mode 100644 packages/apps-config/src/ci/runner.ts delete mode 100644 packages/apps-config/src/ci/util.ts diff --git a/jest-ci.config.cjs b/jest-ci.config.cjs new file mode 100644 index 000000000000..f2ced3584c8d --- /dev/null +++ b/jest-ci.config.cjs @@ -0,0 +1,10 @@ +// Copyright 2017-2022 @polkadot/apps authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +const config = require('@polkadot/dev/config/jest.cjs'); + +module.exports = { + ...config, + moduleNameMapper: {}, + testTimeout: 2 * 60 * 1000 +}; diff --git a/package.json b/package.json index 8bb9ae8c2494..fa4438cafccb 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "url": "https://github.com/polkadot-js/apps.git" }, "sideEffects": false, + "type": "module", "version": "0.116.2-93-x", "workspaces": [ "packages/*" @@ -36,8 +37,8 @@ "build:release:www": "yarn polkadot-ci-ghact-build && yarn build:release:ghpages && yarn build:release:ipfs", "build:robohash": "node scripts/robohash.cjs", "build:www": "rm -rf packages/apps/build && mkdir -p packages/apps/build && yarn run build:i18n && cd packages/apps && yarn webpack --config webpack.config.cjs", - "ci:chainEndpoints": "NODE_OPTIONS=--max_old_space_size=8192 polkadot-dev-run-test --runInBand packages/apps-config/src/ci/chainEndpoints", - "ci:chainTypes": "NODE_OPTIONS=--max_old_space_size=8192 polkadot-dev-run-test --runInBand packages/apps-config/src/ci/chainTypes", + "ci:chainEndpoints": "NODE_OPTIONS=--experimental-vm-modules polkadot-dev-run-test --config ./jest-ci.config.cjs --runInBand --detectOpenHandles packages/apps-config/src/ci/chainEndpoints", + "ci:chainTypes": "NODE_OPTIONS=--experimental-vm-modules polkadot-dev-run-test --config ./jest-ci.config.cjs --runInBand --detectOpenHandles packages/apps-config/src/ci/chainTypes", "clean": "polkadot-dev-clean-build", "clean:electronBuild": "cd packages/apps-electron && polkadot-dev-clean-build", "clean:electronRelease": "cd packages/apps-electron && rm -rf release", diff --git a/packages/apps-config/src/ci/chainEndpoints.spec.ts b/packages/apps-config/src/ci/chainEndpoints.spec.ts index 4103407e189a..ec84c7e422f3 100644 --- a/packages/apps-config/src/ci/chainEndpoints.spec.ts +++ b/packages/apps-config/src/ci/chainEndpoints.spec.ts @@ -1,9 +1,13 @@ // Copyright 2017-2022 @polkadot/apps-config authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { checkEndpoints } from './util'; +import { checkEndpoints } from './runner'; describe('--SLOW--: check configured chain endpoints', (): void => { + beforeAll((): void => { + jest.setTimeout(2 * 60 * 1000); + }); + checkEndpoints('./.github/chain-endpoints.md', [ 'No DNS entry for', 'Timeout connecting to', diff --git a/packages/apps-config/src/ci/chainTypes.spec.ts b/packages/apps-config/src/ci/chainTypes.spec.ts index 2fb46e1e2238..47197225019d 100644 --- a/packages/apps-config/src/ci/chainTypes.spec.ts +++ b/packages/apps-config/src/ci/chainTypes.spec.ts @@ -1,9 +1,13 @@ // Copyright 2017-2022 @polkadot/apps-config authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { checkEndpoints } from './util'; +import { checkEndpoints } from './runner'; describe.skip('--SLOW--: check configured chain types', (): void => { + beforeAll((): void => { + jest.setTimeout(2 * 60 * 1000); + }); + checkEndpoints('./.github/chain-types.md', [ 'Unknown types' ]); diff --git a/packages/apps-config/src/ci/check.ts b/packages/apps-config/src/ci/check.ts new file mode 100644 index 000000000000..0fa4959f633d --- /dev/null +++ b/packages/apps-config/src/ci/check.ts @@ -0,0 +1,88 @@ +// Copyright 2017-2022 @polkadot/apps-config authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import fs from 'fs'; + +import { ApiPromise, WsProvider } from '@polkadot/api'; +import { assert, isError } from '@polkadot/util'; + +import { typesBundle, typesChain } from '../api'; +import { fetchJson } from './fetch'; + +interface DnsResponse { + Answer?: { name: string }[]; + Question: { name: string }[]; +} + +const TICK = '`'; + +export function checkEndpoint (issueFile: string, failures: string[]): (name: string, ws: string) => Promise { + return async (name: string, ws: string): Promise => { + const [,, hostWithPort] = ws.split('/'); + const [host] = hostWithPort.split(':'); + const json = await fetchJson(`https://dns.google/resolve?name=${host}`); + + let provider: WsProvider | null = null; + let api: ApiPromise | null = null; + let timerId: NodeJS.Timeout | null = null; + + try { + assert(json.Answer, `No DNS entry for ${host}`); + + provider = new WsProvider(ws, false); + api = new ApiPromise({ + provider, + throwOnConnect: true, + throwOnUnknown: false, + typesBundle, + typesChain + }); + + setTimeout((): void => { + provider && + provider + .connect() + .catch(() => undefined); + }, 1000); + + await Promise.race([ + // eslint-disable-next-line promise/param-names + new Promise((_, reject): void => { + timerId = setTimeout((): void => { + timerId = null; + reject(new Error(`Timeout connecting to ${ws}`)); + }, 30_000); + }), + api.isReadyOrError + .then((a) => a.rpc.chain.getBlock()) + .then((b) => console.log(b.toHuman())) + ]); + } catch (error) { + if (isError(error) && failures.some((f) => (error as Error).message.includes(f))) { + process.env.CI_LOG && fs.appendFileSync(issueFile, `\n${TICK}${name} @ ${ws} ${error.message}${TICK}\n`); + + throw error; + } + + console.error(JSON.stringify(error)); + } finally { + if (timerId) { + clearTimeout(timerId); + } + + if (provider) { + try { + if (api) { + await api.disconnect(); + } else { + await provider.disconnect(); + } + } catch { + // ignore + } + } + } + + return Promise.resolve(true); + }; +} diff --git a/packages/apps-config/src/ci/fetch.ts b/packages/apps-config/src/ci/fetch.ts new file mode 100644 index 000000000000..93e8267da1a3 --- /dev/null +++ b/packages/apps-config/src/ci/fetch.ts @@ -0,0 +1,38 @@ +// Copyright 2017-2022 @polkadot/apps authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { fetch } from '@polkadot/x-fetch'; + +// a fetch with a 2s timeout +async function fetchWithTimeout (url: string, timeout = 2000): Promise { + const controller = new AbortController(); + let isAborted = false; + const id = setTimeout((): void => { + console.log(`Timeout on ${url}`); + + isAborted = true; + controller.abort(); + }, timeout); + + try { + const response = await fetch(url, { signal: controller.signal }); + + clearTimeout(id); + + return response; + } catch (error) { + if (!isAborted) { + clearTimeout(id); + } + + throw error; + } +} + +export function fetchJson (url: string, timeout = 2000): Promise { + return fetchWithTimeout(url, timeout).then((r) => r.json()); +} + +export function fetchText (url: string, timeout = 2000): Promise { + return fetchWithTimeout(url, timeout).then((r) => r.text()); +} diff --git a/packages/apps-config/src/ci/runner.ts b/packages/apps-config/src/ci/runner.ts new file mode 100644 index 000000000000..e46ccd079d79 --- /dev/null +++ b/packages/apps-config/src/ci/runner.ts @@ -0,0 +1,37 @@ +// Copyright 2017-2022 @polkadot/apps-config authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import { isString } from '@polkadot/util'; + +import { createWsEndpoints } from '../endpoints'; +import { checkEndpoint } from './check'; + +interface Endpoint { + name: string; + ws: string; +} + +export function checkEndpoints (issueFile: string, failures: string[]): void { + const checker = checkEndpoint(issueFile, failures); + + it.each( + createWsEndpoints() + .filter(({ isDisabled, isUnreachable, value }) => + !isDisabled && + !isUnreachable && + value && + isString(value) && + !value.includes('127.0.0.1') && + !value.startsWith('light://') + ) + .map(({ text, value }): Partial => ({ + name: text as string, + ws: value + })) + .filter((v): v is Endpoint => !!v.ws) + )('%name @ %$ws', ({ name, ws }): Promise => { + console.error(`>>> ${name} @ ${ws}`); + + return checker(name, ws).then((r) => expect(r).toBe(true)); + }); +} diff --git a/packages/apps-config/src/ci/util.ts b/packages/apps-config/src/ci/util.ts deleted file mode 100644 index 86155b69bc48..000000000000 --- a/packages/apps-config/src/ci/util.ts +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2017-2022 @polkadot/apps-config authors & contributors -// SPDX-License-Identifier: Apache-2.0 - -import fs from 'fs'; - -import { ApiPromise, WsProvider } from '@polkadot/api'; -import { assert, isError, isString } from '@polkadot/util'; -import { fetch } from '@polkadot/x-fetch'; - -import { typesBundle, typesChain } from '../api'; -import { createWsEndpoints } from '../endpoints'; - -interface Endpoint { - name: string; - ws: string; -} - -interface DnsResponse { - Answer?: { name: string }[]; - Question: { name: string }[]; -} - -const TICK = '`'; - -export function checkEndpoints (issueFile: string, failures: string[]): void { - createWsEndpoints() - .filter(({ isDisabled, isUnreachable, value }) => - !isDisabled && - !isUnreachable && - value && - isString(value) && - !value.includes('127.0.0.1') && - !value.startsWith('light://') - ) - .map(({ text, value }): Partial => ({ - name: text as string, - ws: value - })) - .filter((v): v is Endpoint => !!v.ws) - .forEach(({ name, ws }) => - it(`${name} @ ${ws}`, async (): Promise => { - console.error(`>>> ${name} @ ${ws}`); - - const [,, hostWithPort] = ws.split('/'); - const [host] = hostWithPort.split(':'); - - const response = await fetch(`https://dns.google/resolve?name=${host}`); - const json = await response.json() as DnsResponse; - - let provider: WsProvider | null = null; - let api: ApiPromise | null = null; - let timerId: NodeJS.Timeout | null = null; - - try { - assert(json.Answer, `No DNS entry for ${host}`); - - provider = new WsProvider(ws, false); - api = new ApiPromise({ - provider, - throwOnConnect: true, - throwOnUnknown: false, - typesBundle, - typesChain - }); - - setTimeout((): void => { - provider && - provider - .connect() - .catch(() => undefined); - }, 1000); - - await Promise.race([ - // eslint-disable-next-line promise/param-names - new Promise((_, reject): void => { - timerId = setTimeout((): void => { - timerId = null; - reject(new Error(`Timeout connecting to ${ws}`)); - }, 30_000); - }), - api.isReadyOrError - .then((a) => a.rpc.chain.getBlock()) - .then((b) => console.log(b.toHuman())) - ]); - } catch (error) { - if (isError(error) && failures.some((f) => (error as Error).message.includes(f))) { - process.env.CI_LOG && fs.appendFileSync(issueFile, `\n${TICK}${name} @ ${ws} ${error.message}${TICK}\n`); - - throw error; - } - - console.error(JSON.stringify(error)); - } finally { - if (timerId) { - clearTimeout(timerId); - } - - if (provider) { - try { - if (api) { - await api.disconnect(); - } else { - await provider.disconnect(); - } - } catch { - // ignore - } - } - } - }) - ); -}