@0x:contracts-integrations Addressed more review comments
				
					
				
			This commit is contained in:
		@@ -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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user