From 205c895d75a9a7df2fa7c22b156e0eaabd1962b8 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Tue, 29 Oct 2019 23:19:28 -0700 Subject: [PATCH] `@0x:contracts-integrations` Added better documentation to FunctionAssertions --- .../test/utils/function_assertions.ts | 124 +++++++++++------- 1 file changed, 76 insertions(+), 48 deletions(-) diff --git a/contracts/integrations/test/utils/function_assertions.ts b/contracts/integrations/test/utils/function_assertions.ts index 0fdc4d076f..653c8b37c3 100644 --- a/contracts/integrations/test/utils/function_assertions.ts +++ b/contracts/integrations/test/utils/function_assertions.ts @@ -18,9 +18,26 @@ export interface Result { receipt?: TransactionReceiptWithDecodedLogs; } -export interface Condition { - before?: (...args: any[]) => Promise; - after?: (beforeInfo: any, result: Result, ...args: any[]) => Promise; +/** + * This interface represents a condition that can be placed on a contract function. + * This can be used to represent the pre- and post-conditions of a "Hoare Triple" on a + * given contract function. The "Hoare Triple" is a way to represent the way that a + * function changes state. + * @param before A function that will be run before a call to the contract wrapper + * function. Ideally, this will be a "precondition." + * @param after A function that will be run after a call to the contract wrapper + * function. + */ +export interface Condition { + before?: (...args: any[]) => Promise; + after?: (beforeInfo: TBefore, result: Result, ...args: any[]) => Promise; +} + +/** + * + */ +export interface Assertion { + runAsync: (...args: any[]) => Promise; } export interface RunResult { @@ -28,18 +45,16 @@ export interface RunResult { afterInfo: any; } -export interface Assertion { - runAsync: (...args: any[]) => Promise; -} - -export class FunctionAssertion implements Assertion { - // A before and an after assertion that will be called around the wrapper function. - public condition: Condition; +export class FunctionAssertion implements Assertion { + // A condition that will be applied to `wrapperFunction`. + // Note: `TBefore | undefined` is used because the `before` and `after` functions + // are optional in `Condition`. + public condition: Condition; // The wrapper function that will be wrapped in assertions. public wrapperFunction: ContractWrapperFunction; - constructor(wrapperFunction: ContractWrapperFunction, condition: Condition) { + constructor(wrapperFunction: ContractWrapperFunction, condition: Condition) { this.condition = condition; this.wrapperFunction = wrapperFunction; } @@ -82,63 +97,76 @@ export class FunctionAssertion implements Assertion { } } +export type InputGenerator = () => Promise; + +export interface AssertionGenerator { + assertion: Assertion; + generator: InputGenerator; +} + /** - * Note: This can be treated like a normal `FunctionAssertion`. + * A class that can run a set of function assertions in a sequence. This will terminate + * after every assertion in the sequence has been executed. */ export class FunctionAssertionSequence { - public readonly assertions: Assertion[] = []; - public readonly inputGenerators: Array<() => any[]> = []; + /** + * @constructor Initializes a readonly list of AssertionGenerator objects. + * @param assertionGenerators A list of objects that contain (1) assertions + * and (2) functions that generate the arguments to "run" the assertions. + */ + constructor(protected readonly assertionGenerators: AssertionGenerator[]) {} /** - * Set up a set of function assertions equipped with generator functions. - * A number can be specified for each assertion that will detail the frequency - * that this assertion will be called relative to the other assertions in the - * set. + * Execute this class's function assertions in the order that they were initialized. + * The assertions corresponding input generators will provide the arguments when the + * assertion is executed. */ - constructor(assertionGenerators: [Assertion, (() => any[])][]) { - for (const assertionGenerator of assertionGenerators) { - this.assertions.push(assertionGenerator[0]); - this.inputGenerators.push(assertionGenerator[1]); - } - } - - /** - * Run the functions in this assertion set in order. - */ - public async runAsync(environment: BlockchainTestsEnvironment): Promise { - for (let i = 0; i < this.assertions.length; i++) { - await this.assertions[i].runAsync(...this.inputGenerators[i]()); + public async runAsync(): Promise { + for (let i = 0; i < this.assertionGenerators.length; i++) { + const args = await this.assertionGenerators[i].generator(); + await this.assertionGenerators[i].assertion.runAsync(...args); } } } +export interface WeightedAssertionGenerator extends AssertionGenerator { + weight?: number; +} + +/** + * A class that can execute a set of function assertions at random continuously. + * This will not terminate unless the process that called `runAsync` terminates. + */ export class ContinuousFunctionAssertionSet { - public readonly assertions: Assertion[] = []; - public readonly inputGenerators: Array<() => any[]> = []; + protected readonly assertionGenerators: AssertionGenerator[] = []; /** - * Set up a set of function assertions equipped with generator functions. - * A number can be specified for each assertion that will detail the frequency - * that this assertion will be called relative to the other assertions in the - * set. + * @constructor Initializes assertion generators so that assertion's can be + * selected using a uniform distribution and the weights of the + * 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(assertionGenerators: [Assertion, (() => any[]), number?][]) { - for (const assertionGenerator of assertionGenerators) { - const frequency = assertionGenerator[2] || 1; - for (let i = 0; i < frequency; i++) { - this.assertions.push(assertionGenerator[0]); - this.inputGenerators.push(assertionGenerator[1]); - } + constructor(weightedAssertionGenerators: WeightedAssertionGenerator[]) { + for (const { assertion, generator, weight } of weightedAssertionGenerators) { + const weightedAssertions: AssertionGenerator[] = []; + _.fill(weightedAssertions, { assertion, generator }, 0, weight || 1); + this.assertionGenerators = this.assertionGenerators.concat(weightedAssertions); } } /** - * Run the functions in this assertion set continuously. + * Execute this class's function assertions continuously and randomly using the weights + * of the assertions to bias the assertion selection. The assertions corresponding + * input generators will provide the arguments when the + * assertion is executed. */ - public async runAsync(environment: BlockchainTestsEnvironment): Promise { + public async runAsync(): Promise { for (;;) { - const randomIdx = Math.round(Math.random() * (this.assertions.length - 1)); - await this.assertions[randomIdx].runAsync(...this.inputGenerators[randomIdx]()); + const randomIdx = Math.round(Math.random() * (this.assertionGenerators.length - 1)); + const args = await this.assertionGenerators[randomIdx].generator(); + await this.assertionGenerators[randomIdx].assertion.runAsync(...args); } } }