@0x:contracts-integrations
Addressed more review comments
This commit is contained in:
parent
205c895d75
commit
0b3e3ab990
@ -26,7 +26,7 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('runAsync', () => {
|
describe('executeAsync', () => {
|
||||||
it('should call the before function with the provided arguments', async () => {
|
it('should call the before function with the provided arguments', async () => {
|
||||||
let sideEffectTarget = ZERO_AMOUNT;
|
let sideEffectTarget = ZERO_AMOUNT;
|
||||||
const assertion = new FunctionAssertion(exampleContract.returnInteger, {
|
const assertion = new FunctionAssertion(exampleContract.returnInteger, {
|
||||||
@ -36,7 +36,7 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
after: async (beforeInfo: any, result: Result, input: BigNumber) => {},
|
after: async (beforeInfo: any, result: Result, input: BigNumber) => {},
|
||||||
});
|
});
|
||||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||||
await assertion.runAsync(randomInput);
|
await assertion.executeAsync(randomInput);
|
||||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||||
await assertion.runAsync(randomInput);
|
await assertion.executeAsync(randomInput);
|
||||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
before: async () => {},
|
before: async () => {},
|
||||||
after: async (beforeInfo: any, result: Result) => {},
|
after: async (beforeInfo: any, result: Result) => {},
|
||||||
});
|
});
|
||||||
await assertion.runAsync();
|
await assertion.executeAsync();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass the return value of "before" to "after"', async () => {
|
it('should pass the return value of "before" to "after"', async () => {
|
||||||
@ -72,7 +72,7 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
sideEffectTarget = beforeInfo;
|
sideEffectTarget = beforeInfo;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
await assertion.runAsync(randomInput);
|
await assertion.executeAsync(randomInput);
|
||||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,15 +85,17 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
const randomInput = getRandomInteger(ZERO_AMOUNT, MAX_UINT256);
|
||||||
await assertion.runAsync(randomInput);
|
await assertion.executeAsync(randomInput);
|
||||||
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
expect(sideEffectTarget).bignumber.to.be.eq(randomInput);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass the receipt from the function call to "after"', async () => {
|
it('should pass the receipt from the function call to "after"', async () => {
|
||||||
let sideEffectTarget = {} as TransactionReceiptWithDecodedLogs;
|
let sideEffectTarget = {} as TransactionReceiptWithDecodedLogs;
|
||||||
const assertion = new FunctionAssertion(exampleContract.emitEvent, {
|
const assertion = new FunctionAssertion(exampleContract.emitEvent, {
|
||||||
before: async (input: string) => {},
|
before: async (input: string) => {
|
||||||
after: async (beforeInfo: any, result: Result, input: string) => {
|
return {};
|
||||||
|
},
|
||||||
|
after: async (beforeInfo: {}, result: Result, input: string) => {
|
||||||
if (result.receipt) {
|
if (result.receipt) {
|
||||||
sideEffectTarget = result.receipt;
|
sideEffectTarget = result.receipt;
|
||||||
}
|
}
|
||||||
@ -101,7 +103,7 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const input = 'emitted data';
|
const input = 'emitted data';
|
||||||
await assertion.runAsync(input);
|
await assertion.executeAsync(input);
|
||||||
|
|
||||||
// Ensure that the correct events were emitted.
|
// Ensure that the correct events were emitted.
|
||||||
const [event] = filterLogsToArguments<TestFrameworkEventEventArgs>(
|
const [event] = filterLogsToArguments<TestFrameworkEventEventArgs>(
|
||||||
@ -114,13 +116,15 @@ blockchainTests.resets('FunctionAssertion Unit Tests', env => {
|
|||||||
it('should pass the error to "after" if the function call fails', async () => {
|
it('should pass the error to "after" if the function call fails', async () => {
|
||||||
let sideEffectTarget: Error;
|
let sideEffectTarget: Error;
|
||||||
const assertion = new FunctionAssertion(exampleContract.stringRevert, {
|
const assertion = new FunctionAssertion(exampleContract.stringRevert, {
|
||||||
before: async string => {},
|
before: async string => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
after: async (any, result: Result, string) => {
|
after: async (any, result: Result, string) => {
|
||||||
sideEffectTarget = result.data;
|
sideEffectTarget = result.data;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const message = 'error message';
|
const message = 'error message';
|
||||||
await assertion.runAsync(message);
|
await assertion.executeAsync(message);
|
||||||
|
|
||||||
const expectedError = new StringRevertError(message);
|
const expectedError = new StringRevertError(message);
|
||||||
return expect(
|
return expect(
|
||||||
|
@ -29,15 +29,19 @@ export interface Result {
|
|||||||
* function.
|
* function.
|
||||||
*/
|
*/
|
||||||
export interface Condition<TBefore extends any> {
|
export interface Condition<TBefore extends any> {
|
||||||
before?: (...args: any[]) => Promise<TBefore>;
|
before: (...args: any[]) => Promise<TBefore>;
|
||||||
after?: (beforeInfo: TBefore, result: Result, ...args: any[]) => Promise<any>;
|
after: (beforeInfo: TBefore, result: Result, ...args: any[]) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* The basic unit of abstraction for testing. This just consists of a command that
|
||||||
|
* can be run. For example, this can represent a simple command that can be run, or
|
||||||
|
* it can represent a command that executes a "Hoare Triple" (this is what most of
|
||||||
|
* our `Assertion` implementations will do in practice).
|
||||||
|
* @param runAsync The function to execute for the assertion.
|
||||||
*/
|
*/
|
||||||
export interface Assertion {
|
export interface Assertion {
|
||||||
runAsync: (...args: any[]) => Promise<any>;
|
executeAsync: (...args: any[]) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RunResult {
|
export interface RunResult {
|
||||||
@ -45,16 +49,20 @@ export interface RunResult {
|
|||||||
afterInfo: any;
|
afterInfo: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements `Assertion` and represents a "Hoare Triple" that can be
|
||||||
|
* executed.
|
||||||
|
*/
|
||||||
export class FunctionAssertion<TBefore extends any> implements Assertion {
|
export class FunctionAssertion<TBefore extends any> implements Assertion {
|
||||||
// A condition that will be applied to `wrapperFunction`.
|
// A condition that will be applied to `wrapperFunction`.
|
||||||
// Note: `TBefore | undefined` is used because the `before` and `after` functions
|
// Note: `TBefore | undefined` is used because the `before` and `after` functions
|
||||||
// are optional in `Condition`.
|
// are optional in `Condition`.
|
||||||
public condition: Condition<TBefore | undefined>;
|
public condition: Condition<TBefore>;
|
||||||
|
|
||||||
// The wrapper function that will be wrapped in assertions.
|
// The wrapper function that will be wrapped in assertions.
|
||||||
public wrapperFunction: ContractWrapperFunction;
|
public wrapperFunction: ContractWrapperFunction;
|
||||||
|
|
||||||
constructor(wrapperFunction: ContractWrapperFunction, condition: Condition<TBefore | undefined>) {
|
constructor(wrapperFunction: ContractWrapperFunction, condition: Condition<TBefore>) {
|
||||||
this.condition = condition;
|
this.condition = condition;
|
||||||
this.wrapperFunction = wrapperFunction;
|
this.wrapperFunction = wrapperFunction;
|
||||||
}
|
}
|
||||||
@ -63,9 +71,9 @@ export class FunctionAssertion<TBefore extends any> implements Assertion {
|
|||||||
* Runs the wrapped function and fails if the before or after assertions fail.
|
* Runs the wrapped function and fails if the before or after assertions fail.
|
||||||
* @param ...args The args to the contract wrapper function.
|
* @param ...args The args to the contract wrapper function.
|
||||||
*/
|
*/
|
||||||
public async runAsync(...args: any[]): Promise<RunResult> {
|
public async executeAsync(...args: any[]): Promise<RunResult> {
|
||||||
// Call the before condition.
|
// Call the before condition.
|
||||||
const beforeInfo = this.condition.before !== undefined ? await this.condition.before(...args) : undefined;
|
const beforeInfo = await this.condition.before(...args);
|
||||||
|
|
||||||
// Initialize the callResult so that the default success value is true.
|
// Initialize the callResult so that the default success value is true.
|
||||||
let callResult: Result = { success: true };
|
let callResult: Result = { success: true };
|
||||||
@ -85,10 +93,7 @@ export class FunctionAssertion<TBefore extends any> implements Assertion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Call the after condition.
|
// Call the after condition.
|
||||||
const afterInfo =
|
const afterInfo = await this.condition.after(beforeInfo, callResult, ...args);
|
||||||
this.condition.after !== undefined
|
|
||||||
? await this.condition.after(beforeInfo, callResult, ...args)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
beforeInfo,
|
beforeInfo,
|
||||||
@ -97,6 +102,8 @@ export class FunctionAssertion<TBefore extends any> implements Assertion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IndexGenerator = () => number;
|
||||||
|
|
||||||
export type InputGenerator = () => Promise<any[]>;
|
export type InputGenerator = () => Promise<any[]>;
|
||||||
|
|
||||||
export interface AssertionGenerator {
|
export interface AssertionGenerator {
|
||||||
@ -105,28 +112,41 @@ export interface AssertionGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that can run a set of function assertions in a sequence. This will terminate
|
* This class is an abstract way to represent collections of function assertions.
|
||||||
* after every assertion in the sequence has been executed.
|
* Using this, we can use closures to build up many useful collections with different
|
||||||
|
* properties. Notably, this abstraction supports function assertion collections
|
||||||
|
* that can be run continuously and also those that terminate in a finite number
|
||||||
|
* of steps.
|
||||||
*/
|
*/
|
||||||
export class FunctionAssertionSequence {
|
class MetaAssertion implements Assertion {
|
||||||
/**
|
constructor(
|
||||||
* @constructor Initializes a readonly list of AssertionGenerator objects.
|
protected readonly assertionGenerators: AssertionGenerator[],
|
||||||
* @param assertionGenerators A list of objects that contain (1) assertions
|
protected readonly indexGenerator: IndexGenerator,
|
||||||
* and (2) functions that generate the arguments to "run" the assertions.
|
) {}
|
||||||
*/
|
|
||||||
constructor(protected readonly assertionGenerators: AssertionGenerator[]) {}
|
public async executeAsync(): Promise<void> {
|
||||||
|
let idx: number;
|
||||||
|
while ((idx = this.indexGenerator()) > 0) {
|
||||||
|
const args = await this.assertionGenerators[idx].generator();
|
||||||
|
this.assertionGenerators[idx].assertion.executeAsync(...args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute this class's function assertions in the order that they were initialized.
|
* Returns a class that can execute a set of function assertions in sequence.
|
||||||
* The assertions corresponding input generators will provide the arguments when the
|
* @param assertionGenerators A set of assertion generators to run in sequence.
|
||||||
* assertion is executed.
|
|
||||||
*/
|
*/
|
||||||
public async runAsync(): Promise<void> {
|
export function FunctionAssertionSequence(assertionGenerators: AssertionGenerator[]): MetaAssertion {
|
||||||
for (let i = 0; i < this.assertionGenerators.length; i++) {
|
let idx = 0;
|
||||||
const args = await this.assertionGenerators[i].generator();
|
return new MetaAssertion(assertionGenerators, () => {
|
||||||
await this.assertionGenerators[i].assertion.runAsync(...args);
|
if (idx < assertionGenerators.length) {
|
||||||
}
|
return idx++;
|
||||||
|
} else {
|
||||||
|
idx = 0;
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WeightedAssertionGenerator extends AssertionGenerator {
|
export interface WeightedAssertionGenerator extends AssertionGenerator {
|
||||||
@ -134,39 +154,25 @@ export interface WeightedAssertionGenerator extends AssertionGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class that can execute a set of function assertions at random continuously.
|
* Returns a class that can execute a set of function assertions at random continuously.
|
||||||
* This will not terminate unless the process that called `runAsync` terminates.
|
* This will not terminate unless the process that called `runAsync` terminates.
|
||||||
|
* @param weightedAssertionGenerators A set of function assertions that have been
|
||||||
|
* assigned weights.
|
||||||
*/
|
*/
|
||||||
export class ContinuousFunctionAssertionSet {
|
export function ContinuousFunctionAssertionSet(
|
||||||
protected readonly assertionGenerators: AssertionGenerator[] = [];
|
weightedAssertionGenerators: WeightedAssertionGenerator[],
|
||||||
|
): MetaAssertion {
|
||||||
/**
|
// Construct an array of assertion generators that allows random sampling from a
|
||||||
* @constructor Initializes assertion generators so that assertion's can be
|
// uniform distribution to correctly bias assertion selection.
|
||||||
* selected using a uniform distribution and the weights of the
|
let assertionGenerators: AssertionGenerator[] = [];
|
||||||
* assertions hold.
|
|
||||||
* @param weightedAssertionGenerators An array of assertion generators that
|
|
||||||
* have specified "weights." These "weights" specify the relative frequency
|
|
||||||
* that assertions should be executed when the set is run.
|
|
||||||
*/
|
|
||||||
constructor(weightedAssertionGenerators: WeightedAssertionGenerator[]) {
|
|
||||||
for (const { assertion, generator, weight } of weightedAssertionGenerators) {
|
for (const { assertion, generator, weight } of weightedAssertionGenerators) {
|
||||||
const weightedAssertions: AssertionGenerator[] = [];
|
const weightedAssertions: AssertionGenerator[] = [];
|
||||||
_.fill(weightedAssertions, { assertion, generator }, 0, weight || 1);
|
_.fill(weightedAssertions, { assertion, generator }, 0, weight || 1);
|
||||||
this.assertionGenerators = this.assertionGenerators.concat(weightedAssertions);
|
assertionGenerators = assertionGenerators.concat(weightedAssertions);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// The index generator simply needs to sample from a uniform distribution.
|
||||||
* Execute this class's function assertions continuously and randomly using the weights
|
const indexGenerator = () => Math.round(Math.random() * (assertionGenerators.length - 1));
|
||||||
* of the assertions to bias the assertion selection. The assertions corresponding
|
|
||||||
* input generators will provide the arguments when the
|
return new MetaAssertion(assertionGenerators, indexGenerator);
|
||||||
* assertion is executed.
|
|
||||||
*/
|
|
||||||
public async runAsync(): Promise<void> {
|
|
||||||
for (;;) {
|
|
||||||
const randomIdx = Math.round(Math.random() * (this.assertionGenerators.length - 1));
|
|
||||||
const args = await this.assertionGenerators[randomIdx].generator();
|
|
||||||
await this.assertionGenerators[randomIdx].assertion.runAsync(...args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user