Ctrl K
ComunidadGuías…

deploy/tmp-fixtures.test.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * The temp-tree ownership guard is only worth having if it CAN fail — a fixture
 * that cannot fail is worse than no fixture, because it reports safety it has
 * not established. These tests are therefore about the mechanism itself, not
 * about deploy behaviour.
 *
 * They pin four claims:
 *   1. A tree is REMOVED after the test that made it — the property the whole
 *      change exists for. Proven against a real throw, because "it cleans up
 *      when everything passes" is the half that was already true.
 *   2. The sweep removes EVERY pending tree, not just the most recent one, so
 *      a fixture creating two trees in one test cannot smuggle one out.
 *   3. The sweep removes only what this module created: a bystander directory
 *      in the same tmpdir survives, which is the "never remove a path you did
 *      not create" constraint stated as an assertion rather than a promise.
 *   4. Removing an already-removed tree does not throw, so a suite that both
 *      cleans up eagerly and relies on the sweep stays green.
 *
 * Each test that sweeps does so itself and then asserts the count, so no test
 * here depends on hook ordering to stay correct.
 */

import { describe, expect, test, afterEach, afterAll } from 'bun:test'
import { existsSync, mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
  installCleanup,
  mkTempTree,
  pendingTempTrees,
  sweepTempTrees,
  withTempTree
} from './tmp-fixtures'

installCleanup()

// Sweep between tests so one test's tree is never this test's evidence.
afterEach(() => {
  sweepTempTrees()
})

describe('deploy temp-tree ownership', () => {
  test('the sweep removes every tree created since the last sweep', () => {
    const a = mkTempTree('synapto-owner-a-')
    const b = mkTempTree('synapto-owner-b-')
    expect(existsSync(a)).toBe(true)
    expect(existsSync(b)).toBe(true)
    expect(pendingTempTrees()).toEqual([a, b])

    expect(sweepTempTrees()).toBe(2)

    expect(existsSync(a)).toBe(false)
    expect(existsSync(b)).toBe(false)
    expect(pendingTempTrees()).toEqual([])
  })

  test('a tree whose test threw is still removed', () => {
    // Run the failing test as a FUNCTION so this test can observe what survived
    // it. A real `expect().toThrow()` alone could not: the sweep happens in the
    // suite's afterEach, which is outside the assertion.
    const tree = mkTempTree('synapto-owner-throw-')
    const failingTest = () => {
      // A tree created by the failing test itself, not the outer one.
      mkTempTree('synapto-owner-inner-')
      throw new Error('simulated test failure')
    }

    expect(failingTest).toThrow('simulated test failure')
    sweepTempTrees()

    expect(existsSync(tree)).toBe(false)
    expect(pendingTempTrees()).toEqual([])
  })

  test('the sweep leaves a directory this module never created alone', () => {
    // The "never remove a path you did not create" constraint, asserted. The
    // bystander stands in for another session's scratch in the host's /tmp.
    const bystander = mkdtempSync(join(tmpdir(), 'synapto-owner-bystander-'))
    const ours = mkTempTree('synapto-owner-ours-')
    try {
      sweepTempTrees()
      expect(existsSync(ours)).toBe(false)
      // The assertion that makes the sweep safe to run at all.
      expect(existsSync(bystander)).toBe(true)
    } finally {
      rmSync(bystander, { recursive: true, force: true })
    }
  })

  test('sweeping with nothing pending removes nothing and throws nothing', () => {
    expect(pendingTempTrees()).toEqual([])
    expect(sweepTempTrees()).toBe(0)
    expect(sweepTempTrees()).toBe(0)
  })

  test('withTempTree releases the tree even when the builder throws', () => {
    const built = mkdtempSync(join(tmpdir(), 'synapto-owner-value-'))
    try {
      const result = withTempTree('synapto-owner-scope-', path => {
        expect(existsSync(path)).toBe(true)
        return 'the value the test asserts on'
      })
      expect(result).toBe('the value the test asserts on')
      expect(pendingTempTrees()).toEqual([])
    } finally {
      rmSync(built, { recursive: true, force: true })
    }

    const path = mkdtempSync(join(tmpdir(), 'synapto-owner-throwing-'))
    let captured = ''
    try {
      expect(() =>
        withTempTree('synapto-owner-scope-fail-', p => {
          captured = p
          throw new Error('builder failed')
        })
      ).toThrow('builder failed')
      // The finally released it: pending is empty and the tree is gone.
      expect(pendingTempTrees()).toEqual([])
      expect(existsSync(captured)).toBe(false)
    } finally {
      rmSync(path, { recursive: true, force: true })
    }
  })

  test('a tree that no test created is not removed by the suite hooks', () => {
    // Guards the hooks themselves rather than a direct sweep() call: create a
    // tree with the RAW mkdtempSync the old fixtures used, and prove neither
    // the afterEach nor the exit backstop touches it. If someone reintroduces
    // bare mkdtempSync into a fixture, this is the shape of the leak that
    // returns — and the ownership tests above would then be describing a
    // mechanism nothing uses.
    const unowned = mkdtempSync(join(tmpdir(), 'synapto-owner-unowned-'))
    afterAll(() => {
      // Nothing in this file should have claimed it.
      expect(pendingTempTrees()).not.toContain(unowned)
      rmSync(unowned, { recursive: true, force: true })
    })
    expect(existsSync(unowned)).toBe(true)
  })
})