OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / reconcile-sweep-skip.test.js

131 lines · javascript ref:
// The periodic sweep must be able to SKIP a repo that provably has nothing to
// do — and must never skip one that does.
//
// Measured on the public backend (146 repos, 0.1.219): reconcileAll degraded the
// daemon from 11ms to ~450ms TTFB for ~57s of every 300s — a ~19% duty cycle, so
// about one request in five. `git` subprocess count was 0 for the whole window,
// so the cost is the per-tracker `listRefs()` (an `await base.update()` plus a
// full ref range-scan), not git plumbing. Yielding between repos (0.1.180)
// bounded a single stall but not the total, because the work itself was unchanged.
//
// The sweep is a BACKSTOP — per-repo reconcile is already event-driven off the
// tracker's `update` — so a materialized, quiet repo gains nothing from another
// pass. needsPass() decides, and it must fail SAFE: answer true whenever we
// cannot prove there is nothing to do.

import test from 'brittle'
import { mkdtempSync, writeFileSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'

import { RefReconciler } from '../lib/services/ref-reconciler.js'
import { execFileSync } from '../lib/_compat-exec.js'

function git (args, opts = {}) {
  return execFileSync('git', args, { encoding: 'utf8', ...opts }).toString().trim()
}

// Tracker whose linearized view version we can advance, the way replication does.
function fakeTracker (refs, { version = 1, withVersion = true } = {}) {
  const t = {
    base: {
      on () {},
      off () {},
      view: withVersion ? { version } : {}
    },
    async listRefs () { return t._refs.slice() },
    _refs: refs.slice(),
    setVersion (v) { if (t.base.view) t.base.view.version = v }
  }
  return t
}

async function makeRepo () {
  const dir = mkdtempSync(join(tmpdir(), 'pgv3-skip-'))
  const bare = join(dir, 'repo.git')
  const work = join(dir, 'work')
  git(['init', '--bare', '-q', bare])
  git(['init', '-q', work])
  git(['-C', work, 'config', 'user.email', 't@t'])
  git(['-C', work, 'config', 'user.name', 'T'])
  git(['-C', work, 'config', 'commit.gpgsign', 'false'])
  writeFileSync(join(work, 'a.txt'), 'one\n')
  git(['-C', work, 'add', '.'])
  git(['-C', work, 'commit', '-q', '-m', 'c1'])
  const oid = git(['-C', work, 'rev-parse', 'HEAD'])
  git(['-C', work, 'push', '-q', bare, 'HEAD:refs/heads/main'])
  return { bare, oid }
}

test('a reconciler that has never passed always needs one', async (t) => {
  const { bare, oid } = await makeRepo()
  const rec = new RefReconciler({ tracker: fakeTracker([{ ref: 'refs/heads/main', hash: oid }]), repoPath: bare })
  t.ok(rec.needsPass(), 'no pass has completed yet, so nothing is proven')
})

test('a materialized, quiet repo does NOT need another pass', async (t) => {
  const { bare, oid } = await makeRepo()
  const rec = new RefReconciler({ tracker: fakeTracker([{ ref: 'refs/heads/main', hash: oid }]), repoPath: bare })

  await rec.reconcile()

  t.absent(rec.needsPass(), 'view unchanged + nothing deferred => the sweep can skip it')
})

test('an advancing view re-arms the sweep', async (t) => {
  const { bare, oid } = await makeRepo()
  const tracker = fakeTracker([{ ref: 'refs/heads/main', hash: oid }])
  const rec = new RefReconciler({ tracker, repoPath: bare })

  await rec.reconcile()
  t.absent(rec.needsPass(), 'quiet after the pass')

  tracker.setVersion(2) // a peer's write replicated in
  t.ok(rec.needsPass(), 'the view advanced, so there is work to do')
})

// The safety case. A ref whose objects never arrived is exactly the state the
// sweep exists to heal, so a quiet view must NOT be enough to skip it.
test('a ref left unmaterialized keeps the repo in the sweep', async (t) => {
  const { bare } = await makeRepo()
  const missing = 'd'.repeat(40) // a well-formed oid that is not in the repo
  const rec = new RefReconciler({
    tracker: fakeTracker([{ ref: 'refs/heads/orphan', hash: missing }]),
    repoPath: bare
  })

  await rec.reconcile()

  // rev-parse exits non-zero for an absent ref, which the helper turns into a throw.
  let materialized = true
  try { git(['--git-dir', bare, 'rev-parse', '--verify', 'refs/heads/orphan']) } catch { materialized = false }

  t.absent(materialized, 'the ref was not materialized (its object is absent)')
  t.ok(rec.needsPass(), 'deferred work outranks a quiet view — the sweep must retry')
})

test('an unreadable view version never allows a skip', async (t) => {
  const { bare, oid } = await makeRepo()
  const rec = new RefReconciler({
    tracker: fakeTracker([{ ref: 'refs/heads/main', hash: oid }], { withVersion: false }),
    repoPath: bare
  })

  await rec.reconcile()

  t.ok(rec.needsPass(), 'no usable change signal => fail safe and do the work')
})

test('the backstop deadline re-arms an otherwise quiet repo', async (t) => {
  const { bare, oid } = await makeRepo()
  const rec = new RefReconciler({
    tracker: fakeTracker([{ ref: 'refs/heads/main', hash: oid }]),
    repoPath: bare,
    backstopMs: 0 // every pass is immediately overdue
  })

  await rec.reconcile()

  t.ok(rec.needsPass(), 'silent drift still heals because the backstop expires')
})