import { strict as assert } from "assert" ; import { ContainerRuntimeFactoryWithDefaultDataStore , DataObjectFactory } from "@fluidframework/aqueduct" ; import { TelemetryNullLogger } from "@fluidframework/common-utils" ; import { Container } from "@fluidframework/container-loader" ; import { ContainerRuntime , IAckedSummary , IContainerRuntimeOptions , SummaryCollection , } from "@fluidframework/container-runtime" ; import { IDocumentStorageService , ISummaryContext } from "@fluidframework/driver-definitions" ; import { ISummaryTree , SummaryType } from "@fluidframework/protocol-definitions" ; import { channelsTreeName } from "@fluidframework/runtime-definitions" ; import { requestFluidObject } from "@fluidframework/runtime-utils" ; import { ITestObjectProvider } from "@fluidframework/test-utils" ; import { describeFullCompat } from "@fluidframework/test-version-utils" ; import { wrapDocumentServiceFactory } from "./gcDriverWrappers" ; import { loadSummarizer , TestDataObject , submitAndAckSummary } from "./mockSummarizerClient" ; /**
 * Validates that the 'unreferenced' property in the summary tree of unreferenced data stores is present
 * as expected in the snapshot downloaded from server. Basically, the 'unreferenced' property is preserved
 * across summary upload and download.
 */ describeFullCompat ( "GC unreferenced flag in downloaded snapshot" , ( getTestObjectProvider ) => { let provider ; const factory = new DataObjectFactory ( "TestDataObject" , TestDataObject , [ ] , [ ] ) ; const runtimeOptions = { summaryOptions : { disableSummaries : true } , gcOptions : { gcAllowed : true } } ; const runtimeFactory = new ContainerRuntimeFactoryWithDefaultDataStore ( factory , [ [ factory . type , Promise . resolve ( factory ) ] , ] , undefined , undefined , runtimeOptions ) ; const logger = new TelemetryNullLogger ( ) ; let mainContainer ; let mainDataStore ; let documentStorage ; let latestUploadedSummary ; let latestSummaryContext ; let latestAckedSummary ; const createContainer = async ( ) : Promise < Container > => { return await provider . createContainer ( runtimeFactory ) as Container ; } ; const getNewSummarizer = async ( ) => { return loadSummarizer ( provider , runtimeFactory , mainContainer . deltaManager . lastSequenceNumber ) ; } ; /**
     * Callback that will be called by the document storage service whenever a summary is uploaded by the client.
     * Update the summary context to include the summary proposal and ack handle as per the latest ack for the
     * document.
     */ function uploadSummaryCb ( summaryTree , context ) { latestUploadedSummary = summaryTree ; latestSummaryContext = context ; const newSummaryContext = { ... context } ; if ( latestAckedSummary !== undefined ) { newSummaryContext . ackHandle = latestAckedSummary . summaryAck . contents . handle ; newSummaryContext . proposalHandle = latestAckedSummary . summaryOp . contents . handle ; } return newSummaryContext ; } param summarySequenceNumber param unreferencedDataStoreIds param summaryVersion async function validateUnreferencedFlag ( summarizerClient , unreferencedDataStoreIds ) { const summaryResult = await submitAndAckSummary ( provider , summarizerClient , logger ) ; latestAckedSummary = summaryResult . ackedSummary ; assert ( latestSummaryContext && latestSummaryContext . referenceSequenceNumber >= summaryResult . summarySequenceNumber , `Did not get expected summary. Expected: ${ summaryResult . summarySequenceNumber }. ` + `Actual: ${ latestSummaryContext ?. referenceSequenceNumber }.` ) ; assert ( latestUploadedSummary !== undefined , "Did not get a summary" ) ; { const dataStoreTrees = ( latestUploadedSummary . tree [ channelsTreeName ] as ISummaryTree ) ?. tree ?? latestUploadedSummary . tree ; for ( const [ key , value ] of Object . entries ( dataStoreTrees ) ) { if ( value . type === SummaryType . Tree ) { if ( unreferencedDataStoreIds . includes ( key ) ) { assert ( value . unreferenced , `Data store ${ key } should be marked as unreferenced in summary` ) ; } else { assert ( value . unreferenced === undefined , `Data store ${ key } should not be marked as unreferenced in summary` ) ; } } } } { const versions = await documentStorage . getVersions ( latestAckedSummary . summaryAck . contents . handle , 1 ) ; const snapshot = await documentStorage . getSnapshotTree ( versions [ 0 ] ) ; assert ( snapshot !== null , "Snapshot could not be downloaded from server" ) ; const dataStoreTrees = snapshot . trees [ channelsTreeName ] ?. trees ?? snapshot . trees ; for ( const [ key , value ] of Object . entries ( dataStoreTrees ) ) { if ( unreferencedDataStoreIds . includes ( key ) ) { assert ( value . unreferenced , `Data store ${ key } should be marked as unreferenced in snapshot` ) ; } else { assert ( value . unreferenced === undefined , `Data store ${ key } should not be marked as unreferenced in snapshot` ) ; } } } } before ( function ( ) { provider = getTestObjectProvider ( ) ; if ( provider . driver . type !== "odsp" ) { this . skip ( ) ; } } ) ; beforeEach ( async ( ) => { ( provider as any ) . _documentServiceFactory = wrapDocumentServiceFactory ( provider . documentServiceFactory , uploadSummaryCb ) ; mainContainer = await createContainer ( ) ; assert ( mainContainer . storage !== undefined , "Container does not have storage service" ) ; documentStorage = mainContainer . storage ; mainDataStore = await requestFluidObject < TestDataObject > ( mainContainer , "default" ) ; await provider . ensureSynchronized ( ) ; } ) ; afterEach ( ( ) => { latestAckedSummary = undefined ; latestSummaryContext = undefined ; latestUploadedSummary = undefined ; } ) ; it ( "should return the unreferenced flag correctly in snapshot for deleted data stores" , async ( ) => { const deletedDataStoreIds = [ ] ; const summarizerClient = await getNewSummarizer ( ) ; const dataStore2 = await factory . createInstance ( mainDataStore . containerRuntime ) ; const dataStore3 = await factory . createInstance ( mainDataStore . containerRuntime ) ; { mainDataStore . _root . set ( "dataStore2" , dataStore2 . handle ) ; mainDataStore . _root . set ( "dataStore3" , dataStore3 . handle ) ; await validateUnreferencedFlag ( summarizerClient , deletedDataStoreIds ) ; } { mainDataStore . _root . delete ( "dataStore2" ) ; deletedDataStoreIds . push ( dataStore2 . id ) ; await validateUnreferencedFlag ( summarizerClient , deletedDataStoreIds ) ; } { mainDataStore . _root . delete ( "dataStore3" ) ; deletedDataStoreIds . push ( dataStore3 . id ) ; await validateUnreferencedFlag ( summarizerClient , deletedDataStoreIds ) ; } } ) . timeout ( 20000 ) ; it ( "should return the unreferenced flag correctly in snapshot for revived data stores" , async ( ) => { let deletedDataStoreIds = [ ] ; const summarizerClient = await getNewSummarizer ( ) ; const dataStore2 = await factory . createInstance ( mainDataStore . containerRuntime ) ; const dataStore3 = await factory . createInstance ( mainDataStore . containerRuntime ) ; { mainDataStore . _root . set ( "dataStore2" , dataStore2 . handle ) ; mainDataStore . _root . set ( "dataStore3" , dataStore3 . handle ) ; console . log ( "waiting for summary 1" ) ; await validateUnreferencedFlag ( summarizerClient , deletedDataStoreIds ) ; } { mainDataStore . _root . delete ( "dataStore2" ) ; mainDataStore . _root . delete ( "dataStore3" ) ; deletedDataStoreIds . push ( dataStore2 . id ) ; deletedDataStoreIds . push ( dataStore3 . id ) ; console . log ( "waiting for summary 2" ) ; await validateUnreferencedFlag ( summarizerClient , deletedDataStoreIds ) ; } { mainDataStore . _root . set ( "dataStore2" , dataStore2 . handle ) ; mainDataStore . _root . set ( "dataStore3" , dataStore3 . handle ) ; deletedDataStoreIds = [ ] ; console . log ( "waiting for summary 3" ) ; await validateUnreferencedFlag ( summarizerClient , deletedDataStoreIds ) ; console . log ( "waiting for summary done" ) ; } } ) . timeout ( 20000 ) ; } ) ;