431 lines
17 KiB
TypeScript
431 lines
17 KiB
TypeScript
import { BlockchainLifecycle, web3Factory } from '@0x/dev-utils';
|
|
import { RPCSubprovider, Web3ProviderEngine } from '@0x/subproviders';
|
|
import { providerUtils } from '@0x/utils';
|
|
import { TxData, Web3Wrapper } from '@0x/web3-wrapper';
|
|
import * as _ from 'lodash';
|
|
import * as mocha from 'mocha';
|
|
import * as process from 'process';
|
|
|
|
import { provider, providerConfigs, txDefaults, web3Wrapper } from './web3_wrapper';
|
|
|
|
// tslint:disable: no-namespace only-arrow-functions no-unbound-method max-classes-per-file
|
|
|
|
export type ISuite = mocha.ISuite;
|
|
export type ISuiteCallbackContext = mocha.ISuiteCallbackContext;
|
|
export type SuiteCallback = (this: ISuiteCallbackContext) => void;
|
|
export type ContextDefinitionCallback<T> = (description: string, callback: SuiteCallback) => T;
|
|
export type BlockchainSuiteCallback = (this: ISuiteCallbackContext, env: BlockchainTestsEnvironment) => void;
|
|
export type BlockchainContextDefinitionCallback<T> = (description: string, callback: BlockchainSuiteCallback) => T;
|
|
export interface ContextDefinition extends mocha.IContextDefinition {
|
|
optional: ContextDefinitionCallback<ISuite | void>;
|
|
}
|
|
|
|
/**
|
|
* `blockchainTests()` config options.
|
|
*/
|
|
export interface BlockchainContextConfig {
|
|
fork: Partial<{
|
|
// Accounts to unlock on ganache.
|
|
unlockedAccounts: string[];
|
|
}>;
|
|
}
|
|
|
|
let TEST_ENV_CONFIG: Partial<BlockchainContextConfig> = {};
|
|
|
|
/**
|
|
* Interface for `blockchainTests()`.
|
|
*/
|
|
export interface BlockchainContextDefinition {
|
|
(description: string, callback: BlockchainSuiteCallback): ISuite;
|
|
configure: (config?: Partial<BlockchainContextConfig>) => void;
|
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
|
skip: BlockchainContextDefinitionCallback<void>;
|
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
|
resets: BlockchainContextDefinitionCallback<ISuite | void> & {
|
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
|
skip: BlockchainContextDefinitionCallback<void>;
|
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
|
};
|
|
fork: BlockchainContextDefinitionCallback<ISuite | void> & {
|
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
|
skip: BlockchainContextDefinitionCallback<void>;
|
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
|
resets: BlockchainContextDefinitionCallback<ISuite | void>;
|
|
};
|
|
live: BlockchainContextDefinitionCallback<ISuite | void> & {
|
|
only: BlockchainContextDefinitionCallback<ISuite>;
|
|
skip: BlockchainContextDefinitionCallback<void>;
|
|
optional: BlockchainContextDefinitionCallback<ISuite | void>;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Describes the environment object passed into the `blockchainTests()` callback.
|
|
*/
|
|
export interface BlockchainTestsEnvironment {
|
|
blockchainLifecycle: BlockchainLifecycle;
|
|
provider: Web3ProviderEngine;
|
|
txDefaults: Partial<TxData>;
|
|
web3Wrapper: Web3Wrapper;
|
|
getChainIdAsync(): Promise<number>;
|
|
getAccountAddressesAsync(): Promise<string[]>;
|
|
}
|
|
|
|
class BlockchainTestsEnvironmentBase {
|
|
public blockchainLifecycle!: BlockchainLifecycle;
|
|
public provider!: Web3ProviderEngine;
|
|
public txDefaults!: Partial<TxData>;
|
|
public web3Wrapper!: Web3Wrapper;
|
|
|
|
public async getChainIdAsync(): Promise<number> {
|
|
return providerUtils.getChainIdAsync(this.provider);
|
|
}
|
|
|
|
public async getAccountAddressesAsync(): Promise<string[]> {
|
|
return this.web3Wrapper.getAvailableAddressesAsync();
|
|
}
|
|
}
|
|
|
|
interface BlockchainEnvironmentFactory {
|
|
create(): BlockchainTestsEnvironment;
|
|
}
|
|
|
|
/**
|
|
* `BlockchainTestsEnvironment` that uses the default ganache provider.
|
|
*/
|
|
export class StandardBlockchainTestsEnvironmentSingleton extends BlockchainTestsEnvironmentBase {
|
|
private static _instance: StandardBlockchainTestsEnvironmentSingleton | undefined;
|
|
|
|
// Create or retrieve the singleton instance of this class.
|
|
public static create(): StandardBlockchainTestsEnvironmentSingleton {
|
|
if (StandardBlockchainTestsEnvironmentSingleton._instance === undefined) {
|
|
StandardBlockchainTestsEnvironmentSingleton._instance = new StandardBlockchainTestsEnvironmentSingleton();
|
|
}
|
|
return StandardBlockchainTestsEnvironmentSingleton._instance;
|
|
}
|
|
|
|
// Reset the singleton.
|
|
public static reset(): void {
|
|
StandardBlockchainTestsEnvironmentSingleton._instance = undefined;
|
|
}
|
|
|
|
// Get the singleton instance of this class.
|
|
public static getInstance(): StandardBlockchainTestsEnvironmentSingleton | undefined {
|
|
return StandardBlockchainTestsEnvironmentSingleton._instance;
|
|
}
|
|
|
|
protected constructor() {
|
|
super();
|
|
this.blockchainLifecycle = new BlockchainLifecycle(web3Wrapper);
|
|
this.provider = provider;
|
|
this.txDefaults = txDefaults;
|
|
this.web3Wrapper = web3Wrapper;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `BlockchainTestsEnvironment` that uses a forked ganache provider.
|
|
*/
|
|
export class ForkedBlockchainTestsEnvironmentSingleton extends BlockchainTestsEnvironmentBase {
|
|
private static _instance: ForkedBlockchainTestsEnvironmentSingleton | undefined;
|
|
|
|
// Create or retrieve the singleton instance of this class.
|
|
public static create(): ForkedBlockchainTestsEnvironmentSingleton {
|
|
if (ForkedBlockchainTestsEnvironmentSingleton._instance === undefined) {
|
|
ForkedBlockchainTestsEnvironmentSingleton._instance = new ForkedBlockchainTestsEnvironmentSingleton();
|
|
}
|
|
return ForkedBlockchainTestsEnvironmentSingleton._instance;
|
|
}
|
|
|
|
// Reset the singleton.
|
|
public static reset(): void {
|
|
ForkedBlockchainTestsEnvironmentSingleton._instance = undefined;
|
|
}
|
|
|
|
protected static _createWeb3Provider(forkHost: string): Web3ProviderEngine {
|
|
const forkConfig = TEST_ENV_CONFIG.fork || {};
|
|
const unlockedAccounts = forkConfig.unlockedAccounts;
|
|
return web3Factory.getRpcProvider({
|
|
...providerConfigs,
|
|
fork: forkHost,
|
|
blockTime: 0,
|
|
...(unlockedAccounts ? { unlocked_accounts: unlockedAccounts } : {}),
|
|
});
|
|
}
|
|
|
|
// Get the singleton instance of this class.
|
|
public static getInstance(): ForkedBlockchainTestsEnvironmentSingleton | undefined {
|
|
return ForkedBlockchainTestsEnvironmentSingleton._instance;
|
|
}
|
|
|
|
protected constructor() {
|
|
super();
|
|
this.txDefaults = txDefaults;
|
|
this.provider = process.env.FORK_RPC_URL
|
|
? ForkedBlockchainTestsEnvironmentSingleton._createWeb3Provider(process.env.FORK_RPC_URL)
|
|
: // Create a dummy provider if no RPC backend supplied.
|
|
createDummyProvider();
|
|
this.web3Wrapper = new Web3Wrapper(this.provider);
|
|
this.blockchainLifecycle = new BlockchainLifecycle(this.web3Wrapper);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* `BlockchainTestsEnvironment` that uses a live web3 provider.
|
|
*/
|
|
export class LiveBlockchainTestsEnvironmentSingleton extends BlockchainTestsEnvironmentBase {
|
|
private static _instance: LiveBlockchainTestsEnvironmentSingleton | undefined;
|
|
|
|
// Create or retrieve the singleton instance of this class.
|
|
public static create(): LiveBlockchainTestsEnvironmentSingleton {
|
|
if (LiveBlockchainTestsEnvironmentSingleton._instance === undefined) {
|
|
LiveBlockchainTestsEnvironmentSingleton._instance = new LiveBlockchainTestsEnvironmentSingleton();
|
|
}
|
|
return LiveBlockchainTestsEnvironmentSingleton._instance;
|
|
}
|
|
|
|
// Reset the singleton.
|
|
public static reset(): void {
|
|
LiveBlockchainTestsEnvironmentSingleton._instance = undefined;
|
|
}
|
|
|
|
protected static _createWeb3Provider(rpcHost: string): Web3ProviderEngine {
|
|
const providerEngine = new Web3ProviderEngine();
|
|
providerEngine.addProvider(new RPCSubprovider(rpcHost));
|
|
providerUtils.startProviderEngine(providerEngine);
|
|
return providerEngine;
|
|
}
|
|
|
|
// Get the singleton instance of this class.
|
|
public static getInstance(): LiveBlockchainTestsEnvironmentSingleton | undefined {
|
|
return LiveBlockchainTestsEnvironmentSingleton._instance;
|
|
}
|
|
|
|
protected constructor() {
|
|
super();
|
|
this.txDefaults = txDefaults;
|
|
this.provider = process.env.LIVE_RPC_URL
|
|
? LiveBlockchainTestsEnvironmentSingleton._createWeb3Provider(process.env.LIVE_RPC_URL)
|
|
: // Create a dummy provider if no RPC backend supplied.
|
|
createDummyProvider();
|
|
this.web3Wrapper = new Web3Wrapper(this.provider);
|
|
const snapshotHandlerAsync = async (): Promise<void> => {
|
|
throw new Error('Snapshots are not supported with a live provider.');
|
|
};
|
|
this.blockchainLifecycle = {
|
|
startAsync: snapshotHandlerAsync,
|
|
revertAsync: snapshotHandlerAsync,
|
|
} as any;
|
|
}
|
|
}
|
|
|
|
// The original `describe()` global provided by mocha.
|
|
const mochaDescribe = (global as any).describe as mocha.IContextDefinition;
|
|
|
|
/**
|
|
* An augmented version of mocha's `describe()`.
|
|
*/
|
|
export const describe = _.assign(mochaDescribe, {
|
|
optional(description: string, callback: SuiteCallback): ISuite | void {
|
|
const describeCall = process.env.TEST_ALL ? mochaDescribe : mochaDescribe.skip;
|
|
return describeCall(description, callback);
|
|
},
|
|
}) as ContextDefinition;
|
|
|
|
/**
|
|
* Like mocha's `describe()`, but sets up a blockchain environment for you.
|
|
*/
|
|
export const blockchainTests: BlockchainContextDefinition = _.assign(
|
|
function(description: string, callback: BlockchainSuiteCallback): ISuite {
|
|
return defineBlockchainSuite(StandardBlockchainTestsEnvironmentSingleton, description, callback, describe);
|
|
},
|
|
{
|
|
configure(config?: Partial<BlockchainContextConfig>): void {
|
|
// Update the global config and reset all environment singletons.
|
|
TEST_ENV_CONFIG = {
|
|
...TEST_ENV_CONFIG,
|
|
...config,
|
|
};
|
|
ForkedBlockchainTestsEnvironmentSingleton.reset();
|
|
StandardBlockchainTestsEnvironmentSingleton.reset();
|
|
LiveBlockchainTestsEnvironmentSingleton.reset();
|
|
},
|
|
only(description: string, callback: BlockchainSuiteCallback): ISuite {
|
|
return defineBlockchainSuite(
|
|
StandardBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe.only,
|
|
);
|
|
},
|
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
|
return defineBlockchainSuite(
|
|
StandardBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe.skip,
|
|
);
|
|
},
|
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineBlockchainSuite(
|
|
StandardBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.TEST_ALL ? describe : describe.skip,
|
|
);
|
|
},
|
|
fork: _.assign(
|
|
function(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineBlockchainSuite(
|
|
ForkedBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.FORK_RPC_URL ? describe : describe.skip,
|
|
);
|
|
},
|
|
{
|
|
only(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineBlockchainSuite(
|
|
ForkedBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.FORK_RPC_URL ? describe.only : describe.skip,
|
|
);
|
|
},
|
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
|
return defineBlockchainSuite(
|
|
ForkedBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe.skip,
|
|
);
|
|
},
|
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineBlockchainSuite(
|
|
ForkedBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.FORK_RPC_URL ? describe.optional : describe.skip,
|
|
);
|
|
},
|
|
resets(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineResetsBlockchainSuite(
|
|
ForkedBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.FORK_RPC_URL ? describe : describe.skip,
|
|
);
|
|
},
|
|
},
|
|
),
|
|
live: _.assign(
|
|
function(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineBlockchainSuite(
|
|
LiveBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.LIVE_RPC_URL ? describe : describe.skip,
|
|
);
|
|
},
|
|
{
|
|
only(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineBlockchainSuite(
|
|
LiveBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.LIVE_RPC_URL ? describe.only : describe.skip,
|
|
);
|
|
},
|
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
|
return defineBlockchainSuite(
|
|
LiveBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe.skip,
|
|
);
|
|
},
|
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineBlockchainSuite(
|
|
LiveBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
process.env.LIVE_RPC_URL ? describe.optional : describe.skip,
|
|
);
|
|
},
|
|
},
|
|
),
|
|
resets: _.assign(
|
|
function(description: string, callback: BlockchainSuiteCallback): ISuite {
|
|
return defineResetsBlockchainSuite(
|
|
StandardBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe,
|
|
);
|
|
},
|
|
{
|
|
only(description: string, callback: BlockchainSuiteCallback): ISuite {
|
|
return defineResetsBlockchainSuite(
|
|
StandardBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe.only,
|
|
);
|
|
},
|
|
skip(description: string, callback: BlockchainSuiteCallback): void {
|
|
return defineResetsBlockchainSuite(
|
|
StandardBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe.skip,
|
|
);
|
|
},
|
|
optional(description: string, callback: BlockchainSuiteCallback): ISuite | void {
|
|
return defineResetsBlockchainSuite(
|
|
StandardBlockchainTestsEnvironmentSingleton,
|
|
description,
|
|
callback,
|
|
describe.optional,
|
|
);
|
|
},
|
|
},
|
|
),
|
|
},
|
|
) as BlockchainContextDefinition;
|
|
|
|
function defineBlockchainSuite<T>(
|
|
envFactory: BlockchainEnvironmentFactory,
|
|
description: string,
|
|
callback: BlockchainSuiteCallback,
|
|
describeCall: ContextDefinitionCallback<T>,
|
|
): T {
|
|
return describeCall(description, function(this: ISuiteCallbackContext): void {
|
|
callback.call(this, envFactory.create());
|
|
});
|
|
}
|
|
|
|
function defineResetsBlockchainSuite<T>(
|
|
envFactory: BlockchainEnvironmentFactory,
|
|
description: string,
|
|
callback: BlockchainSuiteCallback,
|
|
describeCall: ContextDefinitionCallback<T>,
|
|
): T {
|
|
return describeCall(description, function(this: ISuiteCallbackContext): void {
|
|
const env = envFactory.create();
|
|
beforeEach(async () => env.blockchainLifecycle.startAsync());
|
|
afterEach(async () => env.blockchainLifecycle.revertAsync());
|
|
callback.call(this, env);
|
|
});
|
|
}
|
|
|
|
function createDummyProvider(): Web3ProviderEngine {
|
|
return {
|
|
addProvider: _.noop,
|
|
on: _.noop,
|
|
send: _.noop,
|
|
sendAsync: _.noop,
|
|
start: _.noop,
|
|
stop: _.noop,
|
|
};
|
|
}
|