Add combinatorial tests for internal Exchange functions (#807)
* WIP add combinatorial tests for internal Exchange functions * Change combinitorial testing strategy based on feedback * Check value of filled[orderHash] in updateFilledState tests * Add combinatorial tests for addFillResults * Add combinatorial tests for getPartialAmount * Implement generic `testWithReferenceFuncAsync` * Implement generic `testCombinatoriallyWithReferenceFuncAsync` * Add combinatorial tests for isRoundingError * Add combinatorial tests for calculateFillResults * Add support for Geth in internal contract tests * Fix contract artifacts * Change DECIMAL_PLACES to 78 and add a note. * Document new functions in utils * Optimize tests by only reseting state when needed * Rename/move some files * Print parameter names on failure in testWithReferenceFuncAsync * Add to changelog for utils package * Appease various linters * Rename some more things related to FillOrderCombinatorialUtils * Remove .only from test/exchange/internal.ts * Remove old test for isRoundingError and getPartialAmount * Appease linters again * Remove old todos * Fix typos, add comments, rename some things * Re-add some LibMath tests * Update contract internal tests to use new SafeMath revert reasons * Apply PR feedback from Amir * Apply PR feedback from Remco * Re-add networks to ZRXToken artifact * Remove duplicate Whitelist in compiler.json
This commit is contained in:
119
packages/contracts/test/utils/test_with_reference.ts
Normal file
119
packages/contracts/test/utils/test_with_reference.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { chaiSetup } from './chai_setup';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
export async function testWithReferenceFuncAsync<P0, R>(
|
||||
referenceFunc: (p0: P0) => Promise<R>,
|
||||
testFunc: (p0: P0) => Promise<R>,
|
||||
values: [P0],
|
||||
): Promise<void>;
|
||||
export async function testWithReferenceFuncAsync<P0, P1, R>(
|
||||
referenceFunc: (p0: P0, p1: P1) => Promise<R>,
|
||||
testFunc: (p0: P0, p1: P1) => Promise<R>,
|
||||
values: [P0, P1],
|
||||
): Promise<void>;
|
||||
export async function testWithReferenceFuncAsync<P0, P1, P2, R>(
|
||||
referenceFunc: (p0: P0, p1: P1, p2: P2) => Promise<R>,
|
||||
testFunc: (p0: P0, p1: P1, p2: P2) => Promise<R>,
|
||||
values: [P0, P1, P2],
|
||||
): Promise<void>;
|
||||
export async function testWithReferenceFuncAsync<P0, P1, P2, P3, R>(
|
||||
referenceFunc: (p0: P0, p1: P1, p2: P2, p3: P3) => Promise<R>,
|
||||
testFunc: (p0: P0, p1: P1, p2: P2, p3: P3) => Promise<R>,
|
||||
values: [P0, P1, P2, P3],
|
||||
): Promise<void>;
|
||||
export async function testWithReferenceFuncAsync<P0, P1, P2, P3, P4, R>(
|
||||
referenceFunc: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4) => Promise<R>,
|
||||
testFunc: (p0: P0, p1: P1, p2: P2, p3: P3, p4: P4) => Promise<R>,
|
||||
values: [P0, P1, P2, P3, P4],
|
||||
): Promise<void>;
|
||||
|
||||
/**
|
||||
* Tests the behavior of a test function by comparing it to the expected
|
||||
* behavior (defined by a reference function).
|
||||
*
|
||||
* First the reference function will be called to obtain an "expected result",
|
||||
* or if the reference function throws/rejects, an "expected error". Next, the
|
||||
* test function will be called to obtain an "actual result", or if the test
|
||||
* function throws/rejects, an "actual error". The test passes if at least one
|
||||
* of the following conditions is met:
|
||||
*
|
||||
* 1) Neither the reference function or the test function throw and the
|
||||
* "expected result" equals the "actual result".
|
||||
*
|
||||
* 2) Both the reference function and the test function throw and the "actual
|
||||
* error" message *contains* the "expected error" message.
|
||||
*
|
||||
* @param referenceFuncAsync a reference function implemented in pure
|
||||
* JavaScript/TypeScript which accepts N arguments and returns the "expected
|
||||
* result" or throws/rejects with the "expected error".
|
||||
* @param testFuncAsync a test function which, e.g., makes a call or sends a
|
||||
* transaction to a contract. It accepts the same N arguments returns the
|
||||
* "actual result" or throws/rejects with the "actual error".
|
||||
* @param values an array of N values, where each value corresponds in-order to
|
||||
* an argument to both the test function and the reference function.
|
||||
* @return A Promise that resolves if the test passes and rejects if the test
|
||||
* fails, according to the rules described above.
|
||||
*/
|
||||
export async function testWithReferenceFuncAsync(
|
||||
referenceFuncAsync: (...args: any[]) => Promise<any>,
|
||||
testFuncAsync: (...args: any[]) => Promise<any>,
|
||||
values: any[],
|
||||
): Promise<void> {
|
||||
let expectedResult: any;
|
||||
let expectedErr: string | undefined;
|
||||
try {
|
||||
expectedResult = await referenceFuncAsync(...values);
|
||||
} catch (e) {
|
||||
expectedErr = e.message;
|
||||
}
|
||||
let actualResult: any | undefined;
|
||||
try {
|
||||
actualResult = await testFuncAsync(...values);
|
||||
if (!_.isUndefined(expectedErr)) {
|
||||
throw new Error(
|
||||
`Expected error containing ${expectedErr} but got no error\n\tTest case: ${_getTestCaseString(
|
||||
referenceFuncAsync,
|
||||
values,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (_.isUndefined(expectedErr)) {
|
||||
throw new Error(`${e.message}\n\tTest case: ${_getTestCaseString(referenceFuncAsync, values)}`);
|
||||
} else {
|
||||
expect(e.message).to.contain(
|
||||
expectedErr,
|
||||
`${e.message}\n\tTest case: ${_getTestCaseString(referenceFuncAsync, values)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (!_.isUndefined(actualResult) && !_.isUndefined(expectedResult)) {
|
||||
expect(actualResult).to.deep.equal(
|
||||
expectedResult,
|
||||
`Test case: ${_getTestCaseString(referenceFuncAsync, values)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function _getTestCaseString(referenceFuncAsync: (...args: any[]) => Promise<any>, values: any[]): string {
|
||||
const paramNames = _getParameterNames(referenceFuncAsync);
|
||||
return JSON.stringify(_.zipObject(paramNames, values));
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/questions/1007981/how-to-get-function-parameter-names-values-dynamically
|
||||
function _getParameterNames(func: (...args: any[]) => any): string[] {
|
||||
return _.toString(func)
|
||||
.replace(/[/][/].*$/gm, '') // strip single-line comments
|
||||
.replace(/\s+/g, '') // strip white space
|
||||
.replace(/[/][*][^/*]*[*][/]/g, '') // strip multi-line comments
|
||||
.split('){', 1)[0]
|
||||
.replace(/^[^(]*[(]/, '') // extract the parameters
|
||||
.replace(/=[^,]+/g, '') // strip any ES6 defaults
|
||||
.split(',')
|
||||
.filter(Boolean); // split & filter [""]
|
||||
}
|
Reference in New Issue
Block a user