import { strict as assert } from "assert" ; import { TelemetryNullLogger } from "@fluidframework/common-utils" ; import { cloneGCData } from "@fluidframework/garbage-collector" ; import { SummaryType } from "@fluidframework/protocol-definitions" ; import { CreateSummarizerNodeSource , IGarbageCollectionData , IGarbageCollectionDetailsBase , ISummarizeInternalResult , ISummarizerNodeWithGC , SummarizeInternalFn , } from "@fluidframework/runtime-definitions" ; import { MockLogger } from "@fluidframework/telemetry-utils" ; import { createRootSummarizerNodeWithGC , IRootSummarizerNodeWithGC } from "../summarizerNode/summarizerNodeWithGc" ; import { mergeStats } from "../summaryUtils" ; describe ( "SummarizerNodeWithGC Tests" , ( ) => { const summarizerNodeId = "testNode" ; const node1Id = "/gcNode1" ; const node2Id = "/gcNode2" ; const subNode1Id = "/gcNode1/subNode" ; const subNode2Id = "/gcNode2/subNode" ; let internalGCData ; let baseGCDetails ; let rootSummarizerNode ; let summarizerNode ; let mockLogger ; beforeEach ( async ( ) => { mockLogger = new MockLogger ( ) ; rootSummarizerNode = createRootSummarizerNodeWithGC ( mockLogger , ( ( ) => undefined ) as unknown as SummarizeInternalFn , 0 , 0 ) ; rootSummarizerNode . startSummary ( 0 , new TelemetryNullLogger ( ) ) ; summarizerNode = rootSummarizerNode . createChild ( summarizeInternal , summarizerNodeId , { type : CreateSummarizerNodeSource . FromSummary } , undefined , getInternalGCData , getBaseGCDetails ) ; internalGCData = { gcNodes : { "/" : [ node1Id , node2Id ] , "/gcNode1" : [ subNode1Id ] } } ; baseGCDetails = { usedRoutes : [ ] } ; } ) ; async function summarizeInternal ( fullTree , trackState ) { const stats = mergeStats ( ) ; stats . treeNodeCount ++ ; return { summary : { type : SummaryType . Tree , tree : { } } , stats , id : summarizerNodeId } ; } const getInternalGCData = async ( ) : Promise < IGarbageCollectionData > => internalGCData ; const getBaseGCDetails = async ( ) : Promise < IGarbageCollectionDetailsBase > => baseGCDetails ; describe ( "getGCData API" , ( ) => { it ( "fails when function to get GC data is not provided" , async ( ) => { let failed = false ; try { await rootSummarizerNode . getGCData ( ) ; } catch { failed = true ; } assert ( failed , "Getting GC data should have failed" ) ; } ) ; it ( "can return GC data when data has changed since last summary" , async ( ) => { summarizerNode . invalidate ( 10 ) ; const gcData = await summarizerNode . getGCData ( ) ; assert . deepStrictEqual ( gcData , internalGCData , "GC data should be generated by calling getInternalGCData" ) ; } ) ; it ( "can return base GC data when nothing has changed since last summary" , async ( ) => { baseGCDetails = { usedRoutes : [ "" ] , gcData : { gcNodes : { "/" : [ node1Id ] , "gcNode1" : [ "/" ] , "gcNode2" : [ subNode1Id , subNode2Id ] } } } ; const gcData = await summarizerNode . getGCData ( ) ; assert . deepStrictEqual ( gcData , baseGCDetails . gcData , "Base GC data should have been returned" ) ; } ) ; it ( "can return GC data when base GC data is not available" , async ( ) => { baseGCDetails = { usedRoutes : [ "" ] , gcData : undefined } ; const gcData = await summarizerNode . getGCData ( ) ; assert . deepStrictEqual ( gcData , internalGCData , "GC data should be generated by calling getInternalGCData" ) ; } ) ; it ( "can return cached GC data" , async ( ) => { baseGCDetails = { usedRoutes : [ "" ] , gcData : undefined } ; let gcData = await summarizerNode . getGCData ( ) ; assert . deepStrictEqual ( gcData , internalGCData , "GC data should be generated by calling getInternalGCData" ) ; const cachedGCData = cloneGCData ( gcData ) ; internalGCData . gcNodes [ subNode1Id ] = [ "/" , subNode2Id ] ; gcData = await summarizerNode . getGCData ( ) ; assert . deepStrictEqual ( gcData , cachedGCData , "GC data from previous run should be returned" ) ; } ) ; it ( "can generate GC data when nothing changed but fullGC flag is true" , async ( ) => { baseGCDetails = { usedRoutes : [ "" ] , gcData : undefined } ; let gcData = await summarizerNode . getGCData ( ) ; assert . deepStrictEqual ( gcData , internalGCData , "GC data should be generated by calling getInternalGCData" ) ; internalGCData . gcNodes [ subNode1Id ] = [ "/" , subNode2Id ] ; gcData = await summarizerNode . getGCData ( true ) ; assert . deepStrictEqual ( gcData , internalGCData , "GC data should be generated by calling getInternalGCData" ) ; } ) ; } ) ; describe ( "summarize API" , ( ) => { it ( "should not allow summarizing without running GC first" , async ( ) => { await assert . rejects ( summarizerNode . summarize ( true ) , "summarize should have thrown since GC was run" ) ; } ) ; it ( "should allow summarizing after running GC" , async ( ) => { summarizerNode . updateUsedRoutes ( [ "" ] ) ; await assert . doesNotReject ( summarizerNode . summarize ( true ) , "summarize should not have thrown an error since GC was run" ) ; } ) ; } ) ; } ) ;