license import { ENGINE } from './engine' ; import { inferShape } from './tensor_util_env' ; import { RecursiveArray , TensorLike , TypedArray } from './types' ; import { arraysEqual , encodeString , flatten , isString , isTypedArray } from './util' ; const TEST_EPSILON_FLOAT32 = 1e-3 ; export const TEST_EPSILON_FLOAT16 = 1e-1 ; export function expectArraysClose ( actual , expected , epsilon ? ) { if ( epsilon == null ) { epsilon = testEpsilon ( ) ; } return expectArraysPredicate ( actual , expected , ( a , b ) => areClose ( a as number , b as number , epsilon ) ) ; } export function testEpsilon ( ) { return ENGINE . backend . floatPrecision ( ) === 32 ? TEST_EPSILON_FLOAT32 : TEST_EPSILON_FLOAT16 ; } function expectArraysPredicate ( actual , expected , predicate ) { let checkClassType = true ; if ( isTypedArray ( actual ) || isTypedArray ( expected ) ) { checkClassType = false ; } if ( isTypedArray ( actual ) && isTypedArray ( expected ) ) { checkClassType = true ; } if ( checkClassType ) { const aType = actual . constructor . name ; const bType = expected . constructor . name ; if ( aType !== bType ) { throw new Error ( `Arrays are of different type. Actual: ${ aType }. ` + `Expected: ${ bType }` ) ; } } if ( Array . isArray ( actual ) && Array . isArray ( expected ) ) { const actualShape = inferShape ( actual ) ; const expectedShape = inferShape ( expected ) ; if ( ! arraysEqual ( actualShape , expectedShape ) ) { throw new Error ( `Arrays have different shapes. ` + `Actual: [${ actualShape }]. Expected: [${ expectedShape }]` ) ; } } const actualFlat = isTypedArray ( actual ) ? actual : flatten ( actual as RecursiveArray < number > ) ; const expectedFlat = isTypedArray ( expected ) ? expected : flatten ( expected as RecursiveArray < number > ) ; if ( actualFlat . length !== expectedFlat . length ) { throw new Error ( `Arrays have different lengths actual: ${ actualFlat . length } vs ` + `expected: ${ expectedFlat . length }.\n` + `Actual:   ${ actualFlat }.\n` + `Expected: ${ expectedFlat }.` ) ; } for ( let i = 0 ; i < expectedFlat . length ; ++ i ) { const a = actualFlat [ i ] ; const e = expectedFlat [ i ] ; if ( ! predicate ( a , e ) ) { throw new Error ( `Arrays differ: actual[${ i }] = ${ a }, expected[${ i }] = ${ e }.\n` + `Actual:   ${ actualFlat }.\n` + `Expected: ${ expectedFlat }.` ) ; } } } export interface DoneFn { ( ) : void ; fail : ( message ? : Error | string ) => void ; } export function expectPromiseToFail ( fn , done ) { fn ( ) . then ( ( ) => done . fail ( ) , ( ) => done ( ) ) ; } export function expectArraysEqual ( actual , expected ) { const exp = typeof expected === 'string' || typeof expected === 'number' || typeof expected === 'boolean' ? [ expected ] as number [ ] : expected as number [ ] ; if ( isString ( actual ) || isString ( ( actual as string [ ] ) [ 0 ] ) || isString ( expected ) || isString ( ( expected as string [ ] ) [ 0 ] ) ) { return expectArraysPredicate ( actual , exp , ( a , b ) => a == b ) ; } return expectArraysPredicate ( actual , expected , ( a , b ) => areClose ( a as number , b as number , 0 ) ) ; } export function expectNumbersClose ( a , e , epsilon ? ) { if ( epsilon == null ) { epsilon = testEpsilon ( ) ; } if ( ! areClose ( a , e , epsilon ) ) { throw new Error ( `Numbers differ: actual === ${ a }, expected === ${ e }` ) ; } } function areClose ( a , e , epsilon ) { if ( ! isFinite ( a ) && ! isFinite ( e ) ) { return true ; } if ( isNaN ( a ) || isNaN ( e ) || Math . abs ( a - e ) > epsilon ) { return false ; } return true ; } export function expectValuesInRange ( actual , low , high ) { for ( let i = 0 ; i < actual . length ; i ++ ) { if ( actual [ i ] < low || actual [ i ] > high ) { throw new Error ( `Value out of range:${ actual [ i ] } low: ${ low }, high: ${ high }` ) ; } } } export function expectArrayBuffersEqual ( actual , expected ) { const actualArray = new Float32Array ( actual ) ; const expectedArray = new Float32Array ( expected ) ; if ( actualArray . length !== expectedArray . length ) { throw new Error ( 'Expected ArrayBuffer to be of length ' + `${ expectedArray . length }, but it was ${ actualArray . length }` ) ; } for ( let i = 0 ; i < expectedArray . length ; i ++ ) { if ( actualArray [ i ] !== expectedArray [ i ] ) { throw new Error ( `Expected ArrayBuffer value at ${ i } to be ` + `${ expectedArray [ i ] } but got ${ actualArray [ i ] } instead` ) ; } } } /** Encodes strings into utf-8 bytes. */ export function encodeStrings ( a ) { for ( let i = 0 ; i < ( a as Array < { } > ) . length ; i ++ ) { const val = a [ i ] ; if ( Array . isArray ( val ) ) { encodeStrings ( val ) ; } else { a [ i ] = encodeString ( val as string ) ; } } return a as RecursiveArray < Uint8Array > ; }