"use strict";
/*!
 * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
 * Licensed under the MIT License.
 */
Object.defineProperty(exports, "__esModule", { value: true });
const node_assert_1 = require("node:assert");
const sinon_1 = require("sinon");
const index_js_1 = require("../../gc/index.js");
describe("Garbage Collection Tests", () => {
    let clock;
    before(() => {
        clock = (0, sinon_1.useFakeTimers)();
    });
    afterEach(() => {
        clock.reset();
    });
    after(() => {
        clock.restore();
    });
    describe("UnreferencedStateTracker", () => {
        let tracker;
        afterEach(() => {
            tracker.stopTracking();
        });
        /**
         * During the lifetime of an unreferenced object, its state is tracked and updated in two ways.
         * Timers are set to trigger transitioning to the next state, and updateTracking is also called
         * whenever GC runs.
         * These tests specify how to advance the clock (to hit the timers) and also when to call updateTracking,
         * checking that the expected state transitions occur as specified
         */
        function runTestCase(allSteps) {
            const [start, ...steps] = allSteps;
            clock.tick(start.time);
            tracker = new index_js_1.UnreferencedStateTracker(0 /* unreferencedTimestampMs */, 10 /* inactiveTimeoutMs */, start.time /* currentReferenceTimestampMs */, 20 /* tombstoneTimeoutMs */, start.sweepGracePeriodMs ?? 10 /* sweepGracePeriodMs */);
            node_assert_1.strict.equal(tracker.state, start.state, `Wrong starting state`);
            for (const [index, { time: advanceClockTo, updateWith, state: expectedState },] of steps.entries()) {
                (0, node_assert_1.strict)(advanceClockTo > clock.now, "INVALID TEST CASE: steps must move forward in time, following start");
                clock.tick(advanceClockTo - clock.now);
                if (updateWith !== undefined) {
                    tracker.updateTracking(updateWith);
                }
                node_assert_1.strict.equal(tracker.state, expectedState, `Wrong state at step ${index + 1}`); // 0-indexed including start
            }
        }
        /**
         * Test cases to run through above function runTestCase
         *
         * In all cases:
         * - unreferencedTimestampMs = 0
         * - inactiveTimeoutMs = 10
         * - tombstoneTimeoutMs = 20
         * - sweepGracePeriodMs defaults to 10 (so sweep at 30)
         */
        const testCases = [
            {
                name: "No calls to updateTracking",
                steps: [
                    { time: 0, state: "Active" },
                    { time: 3, state: "Active" },
                    { time: 5, state: "Active" },
                    { time: 12, state: "Inactive" },
                    { time: 15, state: "Inactive" },
                    { time: 25, state: "TombstoneReady" },
                    { time: 35, state: "SweepReady" },
                ],
            },
            {
                name: "No calls to updateTracking - sweepGracePeriodMs 0 (no Tombstone phase)",
                steps: [
                    { time: 0, state: "Active", sweepGracePeriodMs: 0 },
                    { time: 3, state: "Active" },
                    { time: 5, state: "Active" },
                    { time: 12, state: "Inactive" },
                    { time: 19, state: "Inactive" },
                    { time: 20, state: "SweepReady" },
                    { time: 21, state: "SweepReady" },
                ],
            },
            {
                name: "Skip to SweepReady",
                steps: [
                    { time: 0, state: "Active" },
                    { time: 5, state: "Active" },
                    { time: 35, state: "SweepReady" },
                ],
            },
            {
                name: "Skip to SweepReady - sweepGracePeriodMs 0 (no Tombstone phase)",
                steps: [
                    { time: 0, state: "Active", sweepGracePeriodMs: 0 },
                    { time: 5, state: "Active" },
                    { time: 20, state: "SweepReady" },
                ],
            },
            {
                name: "Skip to SweepReady (via updateTracking) - sweepGracePeriodMs 0 (no Tombstone phase)",
                steps: [
                    { time: 0, state: "Active", sweepGracePeriodMs: 0 },
                    { time: 5, state: "Active" },
                    { time: 20, updateWith: 20, state: "SweepReady" },
                ],
            },
            {
                name: "Call update, but triggered via timers",
                steps: [
                    { time: 0, state: "Active" },
                    { time: 3, updateWith: 2, state: "Active" },
                    { time: 5, updateWith: 5, state: "Active" },
                    { time: 12, updateWith: 9, state: "Inactive" }, // Timer will have fired even though server time hasn't passed threshold
                    { time: 17, updateWith: 15, state: "Inactive" }, // No-op, timer already fired
                ],
            },
            {
                name: "currentReferenceTimestampMs jumps ahead",
                steps: [
                    { time: 0, state: "Active" },
                    { time: 5, state: "Active" },
                    { time: 10, state: "Inactive" },
                    { time: 11, updateWith: 20, state: "TombstoneReady" }, // Shouldn't be physically possible, but supported in API
                ],
            },
            {
                name: "Start Inactive",
                steps: [
                    { time: 12, state: "Inactive" },
                    { time: 15, state: "Inactive" },
                    { time: 20, state: "TombstoneReady" },
                    { time: 35, state: "SweepReady" },
                ],
            },
            {
                name: "Start TombstoneReady",
                steps: [
                    { time: 22, state: "TombstoneReady" },
                    { time: 25, state: "TombstoneReady" },
                    { time: 35, state: "SweepReady" },
                ],
            },
            {
                name: "Start SweepReady",
                steps: [{ time: 32, state: "SweepReady" }],
            },
        ];
        for (const testCase of testCases) {
            it(testCase.name, () => {
                runTestCase(testCase.steps);
            });
        }
        it("Non-zero unreferencedTimestampMs properly offsets", () => {
            tracker = new index_js_1.UnreferencedStateTracker(10 /* unreferencedTimestampMs */, 3 /* inactiveTimeoutMs */, 11 /* currentReferenceTimestampMs */, 7 /* tombstoneTimeoutMs */, 15 /* sweepGracePeriodMs */);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Active, "Should start as Active");
            clock.tick(2);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Inactive, "Should be Inactive 2ms later (at 13)");
            tracker.updateTracking(17);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.TombstoneReady, "Should be TombstoneReady after currentReferenceTimestampMs=17");
            clock.tick(15);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.SweepReady, "Should be SweepReady 15ms later");
        });
        it("Timers can't be crossed", () => {
            tracker = new index_js_1.UnreferencedStateTracker(0 /* unreferencedTimestampMs */, 10 /* inactiveTimeoutMs */, 0 /* currentReferenceTimestampMs */, 12 /* tombstoneTimeoutMs */, 0 /* sweepGracePeriodMs */);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Active, "Should start as Active");
            tracker.updateTracking(10);
            // Would be 10ms left on Inactive timer, but it was just cleared. 2ms left on Sweep timer
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Inactive, "Should be Inactive after currentReferenceTimestampMs=10");
            clock.tick(2);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.SweepReady, "Should be SweepReady 2ms later");
        });
        it("Timers can tighten up over time", () => {
            clock.tick(10);
            tracker = new index_js_1.UnreferencedStateTracker(0 /* unreferencedTimestampMs */, 20 /* inactiveTimeoutMs */, 5 /* currentReferenceTimestampMs */, undefined /* tombstoneTimeoutMs */, 0 /* sweepGracePeriodMs */);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Active, "Should start as Active");
            // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access -- Accessing private property
            const timerClearSpy = (0, sinon_1.spy)(tracker.inactiveTimer, "clear");
            // At T10 we had 15 to go based on server timestamps, so Timer is set to 25
            clock.tick(6); // at T16 (9 to go)
            tracker.updateTracking(15); // Simulate processing a more-recent Summary (reference time 15 at T16). Pulls in timer to 21 (5 to go)
            (0, node_assert_1.strict)(timerClearSpy.callCount > 0, "Expected underlying Timer to clear and reset to support shorter timeout");
            clock.tick(5);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Inactive, "Should be Inactive at T21");
        });
        it("Timers can loosen up over time", () => {
            tracker = new index_js_1.UnreferencedStateTracker(0 /* unreferencedTimestampMs */, 10 /* inactiveTimeoutMs */, 0 /* currentReferenceTimestampMs */, undefined /* tombstoneTimeoutMs */, 0 /* sweepGracePeriodMs */);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Active, "Should start as Active");
            clock.tick(5); // at T5, 5 to go
            tracker.updateTracking(1); // Simulate processing an older Summary (reference time 1 at T5). Pushes out timer to 14 (9 to go)
            clock.tick(5);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Active, "Should still be Active since timer was pushed out");
            clock.tick(4);
            node_assert_1.strict.equal(tracker.state, index_js_1.UnreferencedState.Inactive, "Should be Inactive finally at T14");
        });
    });
});
//# sourceMappingURL=gcUnreferencedStateTracker.spec.js.map