import { every } from 'lodash' ; import { Engine } from 'random-js' ; import { stdOpts , CheckOptions } from './constants' ; import { Arbitrary } from './arbitrary' ; import { shrinkNoop } from './shrink' ; import { take , join , Seq } from './seq' ; /**
 *  A result of property test.
 */ export interface TestResult { ok : boolean ; samples : any [ ] ; theException : Error | null ; } /**
 * Succeed Result.
 */ function succeed ( samples ) { return { ok : true , samples : samples , theException : null } ; } /**
 * Failed Result.
 */ function failed ( samples , error = null ) { return { ok : false , samples : samples , theException : error } ; } /**
 * A predicate takes an entity or entities in the domain of discourse as input
 * and outputs either True or False.
 */ export type Predicate = Function ; /**
 * A property is a promise like Generator, but generate a
 * test result.
 */ export type Property = ( engine : Engine , locale : string ) => Promise < TestResult > ; /**
 * Apply a predicate to an given samples.
 */ export async function apply ( predicate , samples ) { return new Promise < TestResult > ( ( resolve , reject ) => { try { const r = predicate . apply ( null , samples ) ; resolve ( r ? succeed ( samples ) : failed ( samples ) ) ; } catch ( error ) { resolve ( failed ( samples , error ) ) ; } } ) ; } export async function shrink ( predicate , failure , shrunkens ) { return new Promise < TestResult > ( async ( resolve , reject ) => { let samples = take ( shrunkens , 1 ) ; if ( samples . length === 0 ) return resolve ( failure ) ; let r = await apply ( predicate , samples [ 0 ] ) ; if ( r . ok ) return resolve ( failure ) ; return resolve ( await shrink ( predicate , r , shrunkens ) ) ; } ) ; } /**
 * Check Result.
 */ export interface CheckResult { ok : boolean ; tests : number ; pass : number ; samples : any [ ] | null ; theException : Error | null ; seed : number ; } /**
 * Check the property.
 */ export async function check ( property , opts ) { const result = await safeCheck ( property , opts ) ; return opts . format ? formatCheckResult ( result ) : result ; } export async function safeCheck ( property , opts ) { opts . engine . seed ( opts . seed ) ; const safe = await property ( opts . engine , opts . locale ) ; return safe . ok ? await checkWith ( property , opts ) : { ok : false , tests : 1 , pass : 0 , samples : safe . samples , theException : safe . theException , seed : opts . seed } ; } export async function checkWith ( property , opts ) { const tests = [ ] ; for ( let i = 0 ; i < opts . tests ; i ++ ) { tests . push ( property ( opts . engine , opts . locale ) ) ; } const results = await Promise . all ( tests ) ; const total = results . length ; let pass = 0 ; for ( let i = 0 ; i < total ; i ++ ) { if ( results [ i ] . ok ) { pass += 1 ; } } let samples = null ; if ( total !== pass ) { const r = results . find ( r => ! r . ok ) ; if ( ! ! r ) { samples = r . samples ; } } return { ok : total === pass , tests : total , pass : pass , samples : samples , theException : null , seed : opts . seed } ; } /**
 * Format the results.
 */ export function formatCheckResult ( result ) { const msg = [ ] ; msg . push ( result . ok ? '+ Ok' : '+ Fail' ) ; msg . push ( ', ' ) ; msg . push ( `pass ${ result . pass } tests` ) ; msg . push ( ', ' ) ; msg . push ( `seed: ${ result . seed }` ) ; msg . push ( `, ` ) ; msg . push ( `samples: ${ result . samples }` ) ; msg . push ( '\n' ) ; if ( result . theException ) { msg . push ( result . theException . stack ) ; } return msg . join ( '' ) ; } /**
 * Create a property.
 */ export function property ( predicate , ... quantifiers ) { return new ForAll ( quantifiers ) . predicate ( predicate ) . makeProperty ( ) ; } ; /**
 * Constructor a ForAll.
 */ export function forAll ( ... arbs ) { return new ForAll ( arbs ) ; } ; /**
 * The thing which can be tested,
 */ export class ForAll { _predicate : Predicate ; quantifiers : Arbitrary < any , any > [ ] ; /**
     * Make a Prop.
     */ constructor ( quantifiers ) { this . quantifiers = quantifiers ; } /**
     * Set predicate.
     */ predicate ( pf ) { if ( pf . length !== this . quantifiers . length ) { throw new Error ( 'quantifiers parameter do not match signature of predicate' ) ; } this . _predicate = pf ; return this ; } /**
     * Generate sample values.
     */ makeSamples ( engine , locale ) { const samples = this . quantifiers . map ( q => q . makeGenerator ( ) ( engine , locale ) ) ; return samples ; } makeShrunkens ( seeds , locale ) { const shrinkers = this . quantifiers . map ( q => q . shrinker || shrinkNoop ) ; const seqs = seeds . map ( ( v , i ) => shrinkers [ i ] ( v ) ) ; return join ( seqs ) ; } /**
     * Convert the thing to a property.
     */ makeProperty ( ) { return async ( engine , locale ) => { const r = < TestResult > await apply ( this . _predicate , this . makeSamples ( engine , locale ) ) ; if ( r . ok ) { return r ; } else { return await shrink ( this . _predicate , r , this . makeShrunkens ( r . samples , locale ) ) ; } } ; } /**
     * Evaluate the function with quantifiers.
     */ eval ( f , opts = stdOpts ) { return f . apply ( null , this . makeSamples ( opts . engine , opts . locale ) ) ; } /**
     * Check this property.
     */ check ( opts = stdOpts ) { check ( this . makeProperty ( ) , opts ) . then ( console . log ) . catch ( console . error ) ; } }