Skip to content

Commit

Permalink
Update testing lib (#273)
Browse files Browse the repository at this point in the history
* update testing helpers

* update testing

* fix
  • Loading branch information
xlc authored May 1, 2023
1 parent ba21091 commit 3d2d9ca
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 100 deletions.
12 changes: 6 additions & 6 deletions packages/chopsticks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
"clean": "rm -rf lib tsconfig.tsbuildinfo",
"build": "tsc -p ./tsconfig.json",
"script:start": "cd ../..; ts-node --transpile-only packages/chopsticks/src/cli.ts",
"script:run": "cd ../..; LOG_LEVEL=trace ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- dev --config=configs/dev.yml",
"dev:karura": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- dev --config=configs/karura.yml",
"dev:acala": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- dev --config=configs/acala.yml",
"dev:polkadot": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- dev --config=configs/polkadot.yml",
"dev:moonriver": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- dev --config=configs/moonriver.yml",
"dev:moonbeam": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- dev --config=configs/moonbeam.yml"
"script:run": "cd ../..; LOG_LEVEL=trace ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- --config=configs/dev.yml",
"dev:karura": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- --config=configs/karura.yml",
"dev:acala": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- --config=configs/acala.yml",
"dev:polkadot": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- --config=configs/polkadot.yml",
"dev:moonriver": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- --config=configs/moonriver.yml",
"dev:moonbeam": "cd ../..; ts-node-dev --transpile-only --inspect --notify=false packages/chopsticks/src/cli.ts -- --config=configs/moonbeam.yml"
},
"dependencies": {
"@acala-network/chopsticks-executor": "workspace:*",
Expand Down
10 changes: 9 additions & 1 deletion packages/chopsticks/src/blockchain/txpool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class TxPool {
readonly #dmp: DownwardMessage[] = []
readonly #hrmp: Record<number, HorizontalMessage[]> = {}

readonly #mode: BuildBlockMode
#mode: BuildBlockMode
readonly #inherentProvider: InherentProvider
readonly #pendingBlocks: { params: BuildBlockParams; deferred: Deferred<void> }[] = []

Expand Down Expand Up @@ -74,6 +74,14 @@ export class TxPool {
return this.#hrmp
}

get mode(): BuildBlockMode {
return this.#mode
}

set mode(mode: BuildBlockMode) {
this.#mode = mode
}

clear() {
this.#pool.length = 0
for (const id of Object.keys(this.#ump)) {
Expand Down
10 changes: 10 additions & 0 deletions packages/chopsticks/src/rpc/dev/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HexString } from '@polkadot/util/types'
import _ from 'lodash'

import { Block } from '../../blockchain/block'
import { BuildBlockMode } from '@acala-network/chopsticks/blockchain/txpool'
import { Handlers, ResponseError } from '../shared'
import { StorageValues, setStorage } from '../../utils/set-storage'
import { defaultLogger } from '../../logger'
Expand Down Expand Up @@ -71,6 +72,15 @@ const handlers: Handlers = {
return block.hash
},
dev_dryRun,
dev_setBlockBuildMode: async (context, [mode]) => {
logger.debug({ mode }, 'dev_setBlockBuildMode')

if (BuildBlockMode[mode] === undefined) {
throw new ResponseError(1, `Invalid mode ${mode}`)
}

context.chain.txPool.mode = mode
},
}

export default handlers
14 changes: 7 additions & 7 deletions packages/e2e/src/dev.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { expectJson, testingPairs } from './helper'
import networks from './networks'

describe('dev rpc', async () => {
const { alice, test1 } = testingPairs()
const { alice, bob } = testingPairs()

const acala = await networks.acala()
const { api, dev, ws } = acala
Expand All @@ -30,22 +30,22 @@ describe('dev rpc', async () => {

expectJson(await api.query.sudo.key()).toMatchSnapshot()

await api.tx.sudo.sudo(api.tx.balances.setBalance(test1.address, 1000000000000, 0)).signAndSend(alice)
await api.tx.sudo.sudo(api.tx.balances.setBalance(bob.address, 1000000000000, 0)).signAndSend(alice)
const hash = await dev.newBlock()

expectJson(await api.query.system.account(test1.address)).toMatchSnapshot()
expectJson(await api.query.system.account(bob.address)).toMatchSnapshot()

await dev.setStorage([[api.query.system.account.key(test1.address), null]], hash)
await dev.setStorage([[api.query.system.account.key(bob.address), null]], hash)

expectJson(await api.query.system.account(test1.address)).toMatchSnapshot()
expectJson(await api.query.system.account(bob.address)).toMatchSnapshot()

await dev.setStorage({
System: {
Account: [[[test1.address], { data: { free: 100000 }, nonce: 1 }]],
Account: [[[bob.address], { data: { free: 100000 }, nonce: 1 }]],
},
})

expectJson(await api.query.system.account(test1.address)).toMatchSnapshot()
expectJson(await api.query.system.account(bob.address)).toMatchSnapshot()
})

it('setStorage handle errors', async () => {
Expand Down
13 changes: 6 additions & 7 deletions packages/e2e/src/genesis-provider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,21 @@ describe('genesis provider works', () => {
it('handles tx', async () => {
await dev.newBlock()

const properties = await chain.api.chainProperties
const { test1, test2 } = testingPairs(properties.ss58Format)
const { alice, bob } = testingPairs()

await dev.setStorage({
System: {
Account: [[[test1.address], { data: { free: 1000 * 1e12 } }]],
Account: [[[alice.address], { data: { free: 1000 * 1e12 } }]],
},
})

expectJson(await api.query.system.account(test1.address)).toMatchSnapshot()
expectJson(await api.query.system.account(alice.address)).toMatchSnapshot()

await api.tx.currencies.transferNativeCurrency(test2.address, 100 * 1e12).signAndSend(test1)
await api.tx.currencies.transferNativeCurrency(bob.address, 100 * 1e12).signAndSend(alice)

await dev.newBlock()

expectJson(await api.query.system.account(test1.address)).toMatchSnapshot()
expectJson(await api.query.system.account(test2.address)).toMatchSnapshot()
expectJson(await api.query.system.account(alice.address)).toMatchSnapshot()
expectJson(await api.query.system.account(bob.address)).toMatchSnapshot()
})
})
24 changes: 1 addition & 23 deletions packages/e2e/src/helper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ApiPromise, WsProvider } from '@polkadot/api'
import { Codec } from '@polkadot/types/types'
import { HexString } from '@polkadot/util/types'
import { Keyring } from '@polkadot/keyring'
import { beforeAll, beforeEach, expect, vi } from 'vitest'

import { Api } from '@acala-network/chopsticks'
Expand All @@ -21,7 +20,7 @@ import { createServer } from '@acala-network/chopsticks/server'
import { defer } from '@acala-network/chopsticks/utils'
import { handler } from '@acala-network/chopsticks/rpc'

export { expectJson, expectHex, expectHuman } from '@acala-network/chopsticks-testing'
export { expectJson, expectHex, testingPairs } from '@acala-network/chopsticks-testing'

export type SetupOption = {
endpoint?: string
Expand Down Expand Up @@ -179,25 +178,4 @@ export const mockCallback = () => {

export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

export const testingPairs = (ss58Format?: number) => {
const keyring = new Keyring({ type: 'ed25519', ss58Format }) // cannot use sr25519 as it is non determinstic
const alice = keyring.addFromUri('//Alice')
const bob = keyring.addFromUri('//Bob')
const charlie = keyring.addFromUri('//Charlie')
const dave = keyring.addFromUri('//Dave')
const eve = keyring.addFromUri('//Eve')
const test1 = keyring.addFromUri('//test1')
const test2 = keyring.addFromUri('//test2')
return {
alice,
bob,
charlie,
dave,
eve,
test1,
test2,
keyring,
}
}

export { defer }
222 changes: 222 additions & 0 deletions packages/testing/src/check.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
import { ApiPromise } from '@polkadot/api'
import { Codec } from '@polkadot/types/types'
import { expect } from 'vitest'

type CodecOrArray = Codec | Codec[]

const processCodecOrArray = (codec: CodecOrArray, fn: (c: Codec) => any) =>
Array.isArray(codec) ? codec.map(fn) : fn(codec)

const toHuman = (codec: CodecOrArray) => processCodecOrArray(codec, (c) => c?.toHuman?.() ?? c)
const toHex = (codec: CodecOrArray) => processCodecOrArray(codec, (c) => c?.toHex?.() ?? c)
const toJson = (codec: CodecOrArray) => processCodecOrArray(codec, (c) => c?.toJSON?.() ?? c)

export type EventFilter = string | { method: string; section: string }

export type RedactOptions = {
number?: boolean | number // precision
hash?: boolean // 32 byte hex
hex?: boolean // any hex with 0x prefix
address?: boolean // base58 address
}

export class Checker {
readonly #value: any
readonly #pipeline: Array<(value: any) => any> = []

#format: 'human' | 'hex' | 'json' = 'json'
#message: string | undefined
#redactOptions: RedactOptions | undefined

constructor(value: any, message?: string) {
this.#value = value
this.#message = message
}

toHuman() {
this.#format = 'human'
return this
}

toHex() {
this.#format = 'hex'
return this
}

toJson() {
this.#format = 'json'
return this
}

message(message: string) {
this.#message = message
return this
}

filterEvents(...filters: EventFilter[]) {
this.toHuman()
this.#pipeline.push((value) => {
let data = value.map(({ event: { index: _, ...event } }: any) => event)
if (filters.length > 0) {
data = data.filter((evt: any) => {
return filters.some((filter) => {
if (typeof filter === 'string') {
return evt.section === filter
} else if ('method' in filter) {
const { section, method } = filter
return evt.section === section && evt.method === method
}
})
})
}
return data
})
return this
}

redact(options: RedactOptions = { number: 2, hash: true }) {
this.#redactOptions = {
...this.#redactOptions,
...options,
}
return this
}

#redact(value: any) {
if (!this.#redactOptions) {
return value
}

const redactNumber = this.#redactOptions.number === true || typeof this.#redactOptions.number === 'number'
const precision = redactNumber
? typeof this.#redactOptions.number === 'number'
? this.#redactOptions.number
: 0
: 0
const redactHash = this.#redactOptions.hash === true
const redactHex = this.#redactOptions.hex === true
const redactAddress = this.#redactOptions.address === true

const processNumber = (value: number) => {
if (precision > 0) {
const rounded = parseFloat(value.toPrecision(precision))
if (rounded === value) {
return rounded
}
return `(rounded ${rounded})`
}
return '(number)'
}

const process = (obj: any): any => {
if (obj == null) {
return obj
}
if (Array.isArray(obj)) {
return obj.map(process)
}
if (redactNumber && typeof obj === 'number') {
return processNumber(obj)
}
if (typeof obj === 'string') {
if (redactNumber && obj.match(/0x000000[0-9a-f]{26}/)) {
// this is very likely u128 encoded in hex
const num = parseInt(obj)
return processNumber(num)
}
if (redactHash && obj.match(/0x[0-9a-f]{64}/)) {
return '(hash)'
}
if (redactHex && obj.match(/0x[0-9a-f]+/)) {
return '(hex)'
}
if (redactAddress && obj.match(/^[1-9A-HJ-NP-Za-km-z]{46,48}$/)) {
return '(address)'
}
if (redactNumber && obj.match(/^-?[\d,]+$/)) {
const num = parseInt(obj.replace(/,/g, ''))
return processNumber(num)
}
return obj
}
if (typeof obj === 'object') {
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, process(v)]))
}
return obj
}

return process(value)
}

map(fn: (value: any) => any) {
this.#pipeline.push(fn)
return this
}

pipe(fn?: (value: Checker) => Checker) {
return fn ? fn(this) : this
}

async value() {
let value = await this.#value

switch (this.#format) {
case 'human':
value = toHuman(value)
break
case 'hex':
value = toHex(value)
break
case 'json':
value = toJson(value)
break
}

for (const fn of this.#pipeline) {
value = await fn(value)
}

value = this.#redact(value)

return value
}

async toMatchSnapshot(msg?: string) {
return expect(await this.value()).toMatchSnapshot(msg ?? this.#message)
}
}

export const check = (value: any, msg?: string) => {
if (value instanceof Checker) {
if (msg) {
return value.message(msg)
}
return value
}
return new Checker(value, msg)
}

type Api = { api: ApiPromise }

export const checkEvents = ({ events }: { events: Promise<Codec[] | Codec> }, ...filters: EventFilter[]) =>
check(events, 'events')
.filterEvents(...filters)
.redact()

export const checkSystemEvents = ({ api }: Api, ...filters: EventFilter[]) =>
check(api.query.system.events(), 'system events')
.filterEvents(...filters)
.redact()

export const checkUmp = ({ api }: Api) =>
check(api.query.parachainSystem.upwardMessages(), 'ump').map((value) =>
api.createType('Vec<XcmVersionedXcm>', value).toJSON()
)

export const checkHrmp = ({ api }: Api) =>
check(api.query.parachainSystem.hrmpOutboundMessages(), 'hrmp').map((value) =>
(value as any[]).map(({ recipient, data }) => ({
data: api.createType('(XcmpMessageFormat, XcmVersionedXcm)', data),
recipient,
}))
)
Loading

0 comments on commit 3d2d9ca

Please sign in to comment.