test(unit): running unit tests under deno
This commit is contained in:
parent
9ea1d1c581
commit
641df422cc
10 changed files with 296 additions and 41 deletions
57
.config/vite-utils/chai-setup.ts
Normal file
57
.config/vite-utils/chai-setup.ts
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
export function setupChai(chai: any, vitestExpect: any) {
|
||||||
|
chai.use(vitestExpect.JestExtend)
|
||||||
|
chai.use(vitestExpect.JestChaiExpect)
|
||||||
|
chai.use(vitestExpect.JestAsymmetricMatchers)
|
||||||
|
chai.use((chai: any, utils: any) => {
|
||||||
|
utils.addMethod(
|
||||||
|
chai.Assertion.prototype,
|
||||||
|
'toMatchInlineSnapshot',
|
||||||
|
function (properties?: object, inlineSnapshot?: string, message?: string) {
|
||||||
|
// based on https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/integrations/snapshot/chai.ts
|
||||||
|
|
||||||
|
const received = utils.flag(this, 'object')
|
||||||
|
if (typeof properties === 'string') {
|
||||||
|
message = inlineSnapshot
|
||||||
|
inlineSnapshot = properties
|
||||||
|
properties = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof inlineSnapshot !== 'string') {
|
||||||
|
throw new Error('toMatchInlineSnapshot requires a string argument')
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo use @vitest/snapshot
|
||||||
|
if (typeof received === 'string') {
|
||||||
|
const snapshot = '"' + received + '"'
|
||||||
|
return chai.expect(snapshot).eql(inlineSnapshot.trim())
|
||||||
|
} else {
|
||||||
|
const obj = eval('(' + inlineSnapshot + ')') // idc lol
|
||||||
|
return chai.expect(received).eql(obj)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.addMethod(chai.Assertion.prototype, 'toMatchSnapshot', function () {
|
||||||
|
// todo use @vitest/snapshot
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
vitestExpect.setState(
|
||||||
|
{
|
||||||
|
assertionCalls: 0,
|
||||||
|
isExpectingAssertions: false,
|
||||||
|
isExpectingAssertionsError: null,
|
||||||
|
expectedAssertionsNumber: null,
|
||||||
|
expectedAssertionsNumberErrorGen: null,
|
||||||
|
environment: 'deno',
|
||||||
|
testPath: 'deno-test.ts',
|
||||||
|
currentTestName: 'deno-test',
|
||||||
|
},
|
||||||
|
chai.expect,
|
||||||
|
)
|
||||||
|
Object.defineProperty(globalThis, vitestExpect.GLOBAL_EXPECT, {
|
||||||
|
value: chai.expect,
|
||||||
|
writable: true,
|
||||||
|
configurable: true,
|
||||||
|
})
|
||||||
|
}
|
26
.config/vite-utils/collect-test-entrypoints.ts
Normal file
26
.config/vite-utils/collect-test-entrypoints.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import { join, resolve } from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import { globSync } from 'glob'
|
||||||
|
|
||||||
|
export function collectTestEntrypoints(params: { skipPackages: string[]; skipTests: string[] }) {
|
||||||
|
const files: string[] = []
|
||||||
|
|
||||||
|
const packages = resolve(__dirname, '../../packages')
|
||||||
|
|
||||||
|
const skipTests = params.skipTests.map((path) => resolve(packages, path))
|
||||||
|
|
||||||
|
for (const dir of fs.readdirSync(packages)) {
|
||||||
|
if (dir.startsWith('.') || params.skipPackages.includes(dir)) continue
|
||||||
|
if (!fs.statSync(resolve(packages, dir)).isDirectory()) continue
|
||||||
|
|
||||||
|
const fullDir = resolve(packages, dir)
|
||||||
|
|
||||||
|
for (const file of globSync(join(fullDir, '**/*.test.ts'))) {
|
||||||
|
if (skipTests.includes(file)) continue
|
||||||
|
if (file.match(/\/(node_modules|dist)\//)) continue
|
||||||
|
files.push(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
83
.config/vite-utils/fixup-deno-test.ts
Normal file
83
.config/vite-utils/fixup-deno-test.ts
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
// @ts-expect-error no typings
|
||||||
|
import { describe as _describe, it, beforeEach, afterEach, beforeAll, afterAll } from 'jsr:@std/testing/bdd'
|
||||||
|
// @ts-expect-error no typings
|
||||||
|
import * as vitestSpy from 'npm:@vitest/spy'
|
||||||
|
// @ts-expect-error no typings
|
||||||
|
import * as chai from 'npm:chai'
|
||||||
|
// @ts-expect-error no typings
|
||||||
|
import * as vitestExpect from 'npm:@vitest/expect'
|
||||||
|
import util from 'node:util'
|
||||||
|
import { setupChai } from './chai-setup'
|
||||||
|
|
||||||
|
export { it, beforeEach, afterEach, beforeAll, afterAll }
|
||||||
|
|
||||||
|
setupChai(chai, vitestExpect)
|
||||||
|
|
||||||
|
Object.defineProperty(it, 'each', {
|
||||||
|
value: (items: any[][]) => (name: string, fn: Function) => {
|
||||||
|
return items.map((item) => {
|
||||||
|
return it(`${util.format(name, ...item)}`, () => fn(...item))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const describe = (...args) => {
|
||||||
|
const fn = args.find((arg) => typeof arg === 'function')
|
||||||
|
if (fn.toString().startsWith('async')) {
|
||||||
|
// https://github.com/denoland/deno_std/issues/4634
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return _describe(...args)
|
||||||
|
}
|
||||||
|
describe.skip = _describe.skip
|
||||||
|
describe.only = _describe.only
|
||||||
|
describe.ignore = _describe.ignore
|
||||||
|
|
||||||
|
export const expect = chai.expect
|
||||||
|
|
||||||
|
const stubbedGlobal = new Map()
|
||||||
|
function stubGlobal(name, value) {
|
||||||
|
stubbedGlobal.set(name, globalThis[name])
|
||||||
|
globalThis[name] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
function unstubAllGlobals() {
|
||||||
|
for (const [name, value] of stubbedGlobal) {
|
||||||
|
globalThis[name] = value
|
||||||
|
}
|
||||||
|
stubbedGlobal.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
export const vi = {
|
||||||
|
...vitestSpy,
|
||||||
|
mocked: (fn: any) => fn,
|
||||||
|
stubGlobal,
|
||||||
|
unstubAllGlobals,
|
||||||
|
waitFor: async (fn: Function) => {
|
||||||
|
// less customizations than vi.waitFor but it's good enough for now
|
||||||
|
const timeout = Date.now() + 5000
|
||||||
|
|
||||||
|
let lastError: unknown
|
||||||
|
while (Date.now() < timeout) {
|
||||||
|
try {
|
||||||
|
return await fn()
|
||||||
|
} catch (e) {
|
||||||
|
lastError = e
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw lastError
|
||||||
|
},
|
||||||
|
// todo use @sinonjs/fake-timers (see https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/integrations/mock/timers.ts)
|
||||||
|
...['setSystemTime', 'advanceTimersByTimeAsync', 'advanceTimersByTime', 'doMock'].reduce(
|
||||||
|
(acc, name) => ({
|
||||||
|
...acc,
|
||||||
|
[name]: () => {
|
||||||
|
throw new Error(name)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ import { setPlatform } from '../../packages/core/src/platform.js'
|
||||||
|
|
||||||
// @ts-expect-error no .env here
|
// @ts-expect-error no .env here
|
||||||
const TEST_ENV = import.meta.env.TEST_ENV
|
const TEST_ENV = import.meta.env.TEST_ENV
|
||||||
if (TEST_ENV === 'browser') {
|
if (TEST_ENV === 'browser' || TEST_ENV === 'deno') {
|
||||||
setPlatform(new (await import('../../packages/web/src/platform.js')).WebPlatform())
|
setPlatform(new (await import('../../packages/web/src/platform.js')).WebPlatform())
|
||||||
} else {
|
} else {
|
||||||
setPlatform(new (await import('../../packages/node/src/common-internals-node/platform.js')).NodePlatform())
|
setPlatform(new (await import('../../packages/node/src/common-internals-node/platform.js')).NodePlatform())
|
||||||
|
|
|
@ -4,47 +4,28 @@ import { resolve, join } from 'path'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import { fixupCjs } from './vite-utils/fixup-cjs'
|
import { fixupCjs } from './vite-utils/fixup-cjs'
|
||||||
import { testSetup } from './vite-utils/test-setup-plugin'
|
import { testSetup } from './vite-utils/test-setup-plugin'
|
||||||
|
import { collectTestEntrypoints } from './vite-utils/collect-test-entrypoints'
|
||||||
|
|
||||||
const SKIP_PACKAGES = ['create-bot', 'crypto-node']
|
|
||||||
|
|
||||||
// https://github.com/oven-sh/bun/issues/4145 prevents us from using vitest directly
|
|
||||||
// so we have to use bun's native test runner
|
|
||||||
const FIXUP_TEST = resolve(__dirname, 'vite-utils/fixup-bun-test.ts')
|
const FIXUP_TEST = resolve(__dirname, 'vite-utils/fixup-bun-test.ts')
|
||||||
|
|
||||||
|
|
||||||
// bun:test doesn't support certain features of vitest, so we'll skip them for now
|
|
||||||
// https://github.com/oven-sh/bun/issues/1825
|
|
||||||
const SKIP_TESTS = [
|
|
||||||
// uses timers
|
|
||||||
'core/src/network/config-manager.test.ts',
|
|
||||||
// incompatible spies
|
|
||||||
'core/src/utils/crypto/mtproto.test.ts',
|
|
||||||
// snapshot format
|
|
||||||
'tl-utils/src/codegen/errors.test.ts'
|
|
||||||
].map(path => resolve(__dirname, '../packages', path))
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
build: {
|
build: {
|
||||||
lib: {
|
lib: {
|
||||||
entry: process.env.ENTRYPOINT ? [process.env.ENTRYPOINT] : (() => {
|
entry: process.env.ENTRYPOINT ? [process.env.ENTRYPOINT] : collectTestEntrypoints({
|
||||||
const files: string[] = []
|
// https://github.com/oven-sh/bun/issues/4145 prevents us from using vitest directly
|
||||||
|
// so we have to use bun's native test runner
|
||||||
const packages = resolve(__dirname, '../packages')
|
skipPackages: ['create-bot', 'crypto-node'],
|
||||||
|
// bun:test doesn't support certain features of vitest, so we'll skip them for now
|
||||||
for (const dir of fs.readdirSync(packages)) {
|
// https://github.com/oven-sh/bun/issues/1825
|
||||||
if (dir.startsWith('.') || SKIP_PACKAGES.includes(dir)) continue
|
skipTests: [
|
||||||
if (!fs.statSync(resolve(packages, dir)).isDirectory()) continue
|
// uses timers
|
||||||
|
'core/src/network/config-manager.test.ts',
|
||||||
const fullDir = resolve(packages, dir)
|
// incompatible spies
|
||||||
|
'core/src/utils/crypto/mtproto.test.ts',
|
||||||
for (const file of globSync(join(fullDir, '**/*.test.ts'))) {
|
// snapshot format
|
||||||
if (SKIP_TESTS.includes(file)) continue
|
'tl-utils/src/codegen/errors.test.ts'
|
||||||
files.push(file)
|
],
|
||||||
}
|
}),
|
||||||
}
|
|
||||||
|
|
||||||
return files
|
|
||||||
})(),
|
|
||||||
formats: ['es'],
|
formats: ['es'],
|
||||||
},
|
},
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
|
|
100
.config/vite.deno.mts
Normal file
100
.config/vite.deno.mts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { resolve, join } from 'path'
|
||||||
|
import { fixupCjs } from './vite-utils/fixup-cjs'
|
||||||
|
import { testSetup } from './vite-utils/test-setup-plugin'
|
||||||
|
import { collectTestEntrypoints } from './vite-utils/collect-test-entrypoints'
|
||||||
|
|
||||||
|
const FIXUP_TEST = resolve(__dirname, 'vite-utils/fixup-deno-test.ts')
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: process.env.ENTRYPOINT ? [process.env.ENTRYPOINT] : collectTestEntrypoints({
|
||||||
|
// these packages rely on node apis and are not meant to be run under deno
|
||||||
|
skipPackages: ['create-bot', 'crypto-node', 'bun', 'node', 'http-proxy', 'socks-proxy', 'mtproxy'],
|
||||||
|
skipTests: [
|
||||||
|
// uses timers
|
||||||
|
'core/src/network/config-manager.test.ts',
|
||||||
|
'core/src/network/persistent-connection.test.ts',
|
||||||
|
// https://github.com/denoland/deno/issues/22470
|
||||||
|
'wasm/tests/gunzip.test.ts',
|
||||||
|
'wasm/tests/zlib.test.ts',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
formats: ['es'],
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [
|
||||||
|
// todo which of these are actually needed?
|
||||||
|
'zlib',
|
||||||
|
'vitest',
|
||||||
|
'stream',
|
||||||
|
'net',
|
||||||
|
'crypto',
|
||||||
|
'module',
|
||||||
|
'fs',
|
||||||
|
'fs/promises',
|
||||||
|
'readline',
|
||||||
|
'worker_threads',
|
||||||
|
'events',
|
||||||
|
'path',
|
||||||
|
'util',
|
||||||
|
'os',
|
||||||
|
//
|
||||||
|
/^(jsr|npm|node|https?):/,
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
chunkFileNames: 'chunk-[hash].js',
|
||||||
|
entryFileNames: '[name]-[hash].test.js',
|
||||||
|
minifyInternalExports: false,
|
||||||
|
},
|
||||||
|
treeshake: false,
|
||||||
|
},
|
||||||
|
commonjsOptions: {
|
||||||
|
ignoreDynamicRequires: true,
|
||||||
|
},
|
||||||
|
outDir: process.env.OUT_DIR || 'dist/tests',
|
||||||
|
emptyOutDir: true,
|
||||||
|
target: 'esnext',
|
||||||
|
minify: false,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
fixupCjs(),
|
||||||
|
{
|
||||||
|
name: 'fix-vitest',
|
||||||
|
transform(code) {
|
||||||
|
if (!code.includes('vitest')) return code
|
||||||
|
code = code.replace(/^import {(.+?)} from ['"]vitest['"]/gms, (_, names) => {
|
||||||
|
const namesParsed = names.split(',').map((name) => name.trim())
|
||||||
|
|
||||||
|
return `import {${namesParsed.join(', ')}} from '${FIXUP_TEST}'`
|
||||||
|
})
|
||||||
|
return code
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fix-events',
|
||||||
|
transform(code) {
|
||||||
|
if (!code.includes('events')) return code
|
||||||
|
return code.replace(/^import (.+?) from ['"]events['"]/gms, (_, name) => {
|
||||||
|
return `import ${name} from 'node:events'`
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// todo
|
||||||
|
// {
|
||||||
|
// name: 'fix-wasm-load',
|
||||||
|
// async transform(code, id) {
|
||||||
|
// if (code.includes('@mtcute/wasm/mtcute.wasm')) {
|
||||||
|
// return code.replace('@mtcute/wasm/mtcute.wasm', resolve(__dirname, '../packages/wasm/mtcute.wasm'))
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return code
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
testSetup(),
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
'import.meta.env.TEST_ENV': '"deno"',
|
||||||
|
},
|
||||||
|
})
|
|
@ -167,6 +167,9 @@ export class SessionConnection extends PersistentConnection {
|
||||||
|
|
||||||
if (forever) {
|
if (forever) {
|
||||||
this.removeAllListeners()
|
this.removeAllListeners()
|
||||||
|
this.on('error', (err) => {
|
||||||
|
this.log.warn('caught error after destroying: %s', err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -309,6 +312,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
this._session.authorizationPending = false
|
this._session.authorizationPending = false
|
||||||
|
if (this._destroyed) return
|
||||||
this.log.error('Authorization error: %s', err.message)
|
this.log.error('Authorization error: %s', err.message)
|
||||||
this.onError(err)
|
this.onError(err)
|
||||||
this.reconnect()
|
this.reconnect()
|
||||||
|
@ -476,6 +480,7 @@ export class SessionConnection extends PersistentConnection {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch((err: Error) => {
|
.catch((err: Error) => {
|
||||||
|
if (this._destroyed) return
|
||||||
this.log.error('PFS Authorization error: %s', err.message)
|
this.log.error('PFS Authorization error: %s', err.message)
|
||||||
|
|
||||||
if (this._isPfsBindingPendingInBackground) {
|
if (this._isPfsBindingPendingInBackground) {
|
||||||
|
@ -492,6 +497,9 @@ export class SessionConnection extends PersistentConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForUnencryptedMessage(timeout = 5000): Promise<Uint8Array> {
|
waitForUnencryptedMessage(timeout = 5000): Promise<Uint8Array> {
|
||||||
|
if (this._destroyed) {
|
||||||
|
return Promise.reject(new MtcuteError('Connection destroyed'))
|
||||||
|
}
|
||||||
const promise = createControllablePromise<Uint8Array>()
|
const promise = createControllablePromise<Uint8Array>()
|
||||||
const timeoutId = setTimeout(() => {
|
const timeoutId = setTimeout(() => {
|
||||||
promise.reject(new MtTimeoutError(timeout))
|
promise.reject(new MtTimeoutError(timeout))
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { gzipSync, inflateSync } from 'node:zlib'
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
import { gzipSync, inflateSync } from 'zlib'
|
|
||||||
|
|
||||||
import { getPlatform } from '@mtcute/core/platform.js'
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
import { dataViewFromBuffer, ICryptoProvider } from '@mtcute/core/utils.js'
|
import { dataViewFromBuffer, ICryptoProvider } from '@mtcute/core/utils.js'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { gzipSync } from 'node:zlib'
|
||||||
import { beforeAll, describe, expect, it } from 'vitest'
|
import { beforeAll, describe, expect, it } from 'vitest'
|
||||||
import { gzipSync } from 'zlib'
|
|
||||||
|
|
||||||
import { getPlatform } from '@mtcute/core/platform.js'
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ beforeAll(async () => {
|
||||||
const p = getPlatform()
|
const p = getPlatform()
|
||||||
|
|
||||||
function gzipSyncWrap(data: Uint8Array) {
|
function gzipSyncWrap(data: Uint8Array) {
|
||||||
if (import.meta.env.TEST_ENV === 'browser') {
|
if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') {
|
||||||
// @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason
|
// @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason
|
||||||
data._isBuffer = true
|
data._isBuffer = true
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { inflateSync } from 'node:zlib'
|
||||||
import { beforeAll, describe, expect, it } from 'vitest'
|
import { beforeAll, describe, expect, it } from 'vitest'
|
||||||
import { inflateSync } from 'zlib'
|
|
||||||
|
|
||||||
import { getPlatform } from '@mtcute/core/platform.js'
|
import { getPlatform } from '@mtcute/core/platform.js'
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ beforeAll(async () => {
|
||||||
const p = getPlatform()
|
const p = getPlatform()
|
||||||
|
|
||||||
function inflateSyncWrap(data: Uint8Array) {
|
function inflateSyncWrap(data: Uint8Array) {
|
||||||
if (import.meta.env.TEST_ENV === 'browser') {
|
if (import.meta.env.TEST_ENV === 'browser' || import.meta.env.TEST_ENV === 'deno') {
|
||||||
// @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason
|
// @ts-expect-error fucking crutch because @jspm/core uses Buffer.isBuffer for some reason
|
||||||
data._isBuffer = true
|
data._isBuffer = true
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue