import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions" ; import { IIntegerRange } from "../base" ; import { Heap , RedBlackTree , Stack , } from "../collections" ; import { ClientSeq , clientSeqComparer , compareNumbers , IncrementalExecOp , IncrementalMapState , ISegment , MergeTree , } from "../mergeTree" ; import { PropertySet } from "../properties" ; import { MergeTreeTextHelper , TextSegment } from "../textSegment" ; import { TestClient } from "./testClient" ; /**
 * Server for tests.  Simulates client communication by directing placing
 * messages in client queues.
 */ export class TestServer extends TestClient { seq = 1 ; clients : TestClient [ ] ; listeners : TestClient [ ] ; clientSeqNumbers : Heap < ClientSeq > ; upstreamMap : RedBlackTree < number , number > ; constructor ( options ? ) { super ( options ) ; } addUpstreamClients ( upstreamClients ) { this . upstreamMap = new RedBlackTree < number , number > ( compareNumbers ) ; for ( const upstreamClient of upstreamClients ) { this . clientSeqNumbers . add ( { refSeq : upstreamClient . getCurrentSeq ( ) , clientId : upstreamClient . longClientId } ) ; } } addClients ( clients ) { this . clientSeqNumbers = new Heap < ClientSeq > ( [ ] , clientSeqComparer ) ; this . clients = clients ; for ( const client of clients ) { this . clientSeqNumbers . add ( { refSeq : client . getCurrentSeq ( ) , clientId : client . longClientId } ) ; } } addListeners ( listeners ) { this . listeners = listeners ; } applyMsg ( msg ) { super . applyMsg ( msg ) ; if ( TestClient . useCheckQ ) { const clid = this . getShortClientId ( msg . clientId ) ; return checkTextMatchRelative ( msg . referenceSequenceNumber , clid , this , msg ) ; } else { return false ; } } transformUpstreamMessage ( msg ) { if ( msg . referenceSequenceNumber > 0 ) { msg . referenceSequenceNumber = this . upstreamMap . get ( msg . referenceSequenceNumber ) . data ; } msg . origin = { id : "A" , sequenceNumber : msg . sequenceNumber , minimumSequenceNumber : msg . minimumSequenceNumber } ; this . upstreamMap . put ( msg . sequenceNumber , this . seq ) ; msg . sequenceNumber = - 1 ; } copyMsg ( msg ) { return { clientId : msg . clientId , clientSequenceNumber : msg . clientSequenceNumber , contents : msg . contents , minimumSequenceNumber : msg . minimumSequenceNumber , referenceSequenceNumber : msg . referenceSequenceNumber , sequenceNumber : msg . sequenceNumber , type : msg . type } as ISequencedDocumentMessage ; } private minSeq = 0 ; applyMessages ( msgCount ) { let _msgCount = msgCount ; while ( _msgCount > 0 ) { const msg = this . q . dequeue ( ) ; if ( msg ) { if ( msg . sequenceNumber >= 0 ) { this . transformUpstreamMessage ( msg ) ; } msg . sequenceNumber = this . seq ++ ; msg . minimumSequenceNumber = this . minSeq ; if ( this . applyMsg ( msg ) ) { return true ; } if ( this . clients ) { let minCli = this . clientSeqNumbers . peek ( ) ; if ( minCli && ( minCli . clientId == msg . clientId ) && ( minCli . refSeq < msg . referenceSequenceNumber ) ) { const cliSeq = this . clientSeqNumbers . get ( ) ; const oldSeq = cliSeq . refSeq ; cliSeq . refSeq = msg . referenceSequenceNumber ; this . clientSeqNumbers . add ( cliSeq ) ; minCli = this . clientSeqNumbers . peek ( ) ; if ( minCli . refSeq > oldSeq ) { msg . minimumSequenceNumber = minCli . refSeq ; this . minSeq = minCli . refSeq ; } } for ( const client of this . clients ) { client . enqueueMsg ( msg ) ; } if ( this . listeners ) { for ( const listener of this . listeners ) { listener . enqueueMsg ( this . copyMsg ( msg ) ) ; } } } } else { break ; } _msgCount -- ; } return false ; } public incrementalGetText ( start ? , end ? ) { const range = { start , end } ; if ( range . start === undefined ) { range . start = 0 ; } if ( range . end === undefined ) { range . end = this . getLength ( ) ; } const context = new TextSegment ( "" ) ; const stack = new Stack < IncrementalMapState < TextSegment > > ( ) ; const initialState = new IncrementalMapState ( this . mergeTree . root , { leaf : incrementalGatherText } , 0 , this . getCurrentSeq ( ) , this . getClientId ( ) , context , range . start , range . end , 0 ) ; stack . push ( initialState ) ; while ( ! stack . empty ( ) ) { this . mergeTree . incrementalBlockMap ( stack ) ; } return context . text ; } } function incrementalGatherText ( segment , state ) { if ( TextSegment . is ( segment ) ) { if ( MergeTree . traceGatherText ) { console . log ( `@cli ${ state . clientId ? state . clientId : - 1 } ` + `gather seg seq ${ segment . seq } rseq ${ segment . removedSeq } text ${ segment . text }` ) ; } if ( ( state . start <= 0 ) && ( state . end >= segment . text . length ) ) { state . context . text += segment . text ; } else { if ( state . end >= segment . text . length ) { state . context . text += segment . text . substring ( state . start ) ; } else { state . context . text += segment . text . substring ( state . start , state . end ) ; } } } state . op = IncrementalExecOp . Go ; } /**
 * Used for in-memory testing.  This will queue a reference string for each client message.
 */ export function checkTextMatchRelative ( refSeq , clientId , server , msg ) { const client = server . clients [ clientId ] ; const serverText = new MergeTreeTextHelper ( server . mergeTree ) . getText ( refSeq , clientId ) ; const cliText = client . checkQ . dequeue ( ) ; if ( ( cliText === undefined ) || ( cliText != serverText ) ) { console . log ( `mismatch ` ) ; console . log ( msg ) ; console . log ( server . mergeTree . toString ( ) ) ; console . log ( client . mergeTree . toString ( ) ) ; return true ; } return false ; }