/**
 * Copyright 2018 Twitter, Inc.
 * Licensed under the Apache License, Version 2.0
 * http://www.apache.org/licenses/LICENSE-2.0
 */ import { SqrlExecutionState } from "../execute/SqrlExecutionState" ; import { FeatureMap , Manipulator } from "../api/execute" ; import SqrlAst from "../ast/SqrlAst" ; import { SqrlParserState } from "../compile/SqrlParserState" ; import { StatementAst } from "../ast/Ast" ; import { parseSqrl } from "../parser/SqrlParse" ; import { SqrlInstance } from "../function/Instance" ; import { SqrlCompiledOutput } from "../compile/SqrlCompiledOutput" ; import { JsExecutionContext } from "../node/JsExecutionContext" ; import SqrlSourcePrinter from "../compile/SqrlSourcePrinter" ; import { Filesystem } from "../api/filesystem" ; import { Context } from "../api/ctx" ; const DEFAULT_FEATURE_TIMEOUT = 5000 ; export interface MutationOptions { repeat : number ; skipWait : boolean ; } export type MutationCallback = ( state : SqrlExecutionState , options : MutationOptions ) => Promise < void > ; export class SqrlTest { executionContext : JsExecutionContext ; statements : StatementAst [ ] ; files ; extendTimeout : ( ) => void ; allowPrivate : boolean ; calculateCost : boolean ; inputs : FeatureMap ; mutate : MutationCallback | null ; featureTimeout : number ; manipulatorFactory : ( ( ) => Manipulator ) | null ; private filesystem : Filesystem ; constructor ( private instance , props = { } ) { this . featureTimeout = props . featureTimeout || DEFAULT_FEATURE_TIMEOUT ; this . executionContext = new JsExecutionContext ( instance ) ; this . files = props . files || { } ; this . extendTimeout = props . extendTimeout || ( ( ) => { } ) ; this . manipulatorFactory = props . manipulatorFactory || null ; this . mutate = props . mutate || null ; this . allowPrivate = props . allowPrivate || false ; this . calculateCost = props . calculateCost || false ; this . inputs = props . inputs || { } ; this . filesystem = props . filesystem || null ; this . statements = [ ] ; } async run ( ctx , sqrl ) { const statements = parseSqrl ( sqrl , { customFunctions : this . instance . customFunctions , } ) . statements ; return this . runStatements ( ctx , statements ) ; } async runStatements ( ctx , statements ) { let assertions = [ ] ; const codedErrors = [ ] ; const executions = [ ] ; const prevStatements = [ ... this . statements ] ; const execute = async ( wait = false ) => { try { const state = await this . executeStatements ( ctx , assertions , wait ) ; executions . push ( state ) ; codedErrors . push ( ... state . loggedCodedErrors ) ; assertions = [ ] ; return state ; } catch ( err ) { this . statements = prevStatements ; throw err ; } } ; for ( const statement of statements ) { if ( statement . type === "assert" ) { assertions . push ( statement ) ; } else if ( statement . type === "execute" ) { for ( let idx = 0 ; idx < statement . repeat ; idx ++ ) { const state = await execute ( true ) ; if ( this . mutate ) { await this . mutate ( state , { skipWait : statement . skipWait , repeat : statement . repeat || 1 , } ) ; } else if ( state . manipulator ) { await state . manipulator . mutate ( ctx ) ; } else { ctx . warn ( { } , "Mutation function / manipulator is not injected" ) ; } } } else { if ( assertions . length ) { await execute ( ) ; } if ( statement . type === "let" && SqrlAst . isConstantTrue ( statement . where ) ) { const replace = this . statements . findIndex ( ( s ) => s . type === "let" && s . feature === statement . feature ) ; if ( replace >= 0 ) { this . statements [ replace ] = statement ; continue ; } } this . statements . push ( statement ) ; } } if ( assertions . length || ! executions . length ) { await execute ( ) ; } return { codedErrors , executions , } ; } async executeStatements ( ctx , statements , waitExecute = false ) { const parserState = new SqrlParserState ( { statements : [ ... this . statements , ... statements ] , allowAssertions : true , allowReplaceInput : true , allowPrivate : this . allowPrivate , instance : this . instance , filesystem : this . filesystem , } ) ; const compiled = await SqrlCompiledOutput . build ( ctx , parserState , { skipCostCalculations : ! this . calculateCost , } ) ; const slotCallback = this . executionContext . compileSlots ( compiled . slotJs ) ; const sourcePrinter = new SqrlSourcePrinter ( { slotNames : compiled . slotNames , slotJs : compiled . slotJs , } ) ; const manipulator = this . manipulatorFactory ? this . manipulatorFactory ( ) : null ; const state = new SqrlExecutionState ( ctx , slotCallback , compiled . slotNames , manipulator , { sourcePrinter , featureTimeout : this . featureTimeout , ruleSpecs : compiled . ruleSpecs , } , this . inputs ) ; await state . fetchClock ( ) ; await Promise . all ( [ state . tryWait ( "SqrlAssertionStatements" ) . then ( ( ) => this . extendTimeout ( ) ) , state . tryWait ( "SqrlLogStatements" ) . then ( ( ) => this . extendTimeout ( ) ) , waitExecute ? state . fetchByName ( "SqrlExecutionComplete" ) . then ( ( ) => this . extendTimeout ( ) ) : Promise . resolve ( ) , ] ) ; return state ; } }