OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git / test / p2p-fetch-size.js

161 lines · javascript ref:
#!/usr/bin/env bare
/**
 * P2P Fetch Size Test
 *
 * Tests whether git fetch over punch:// works for:
 *   1. Small repos (hyper-overlay ~123KB)
 *   2. Large repos (hyper-tps ~1.4GB)
 *   3. Delta-only fetch (already have most data, fetch 1 new commit)
 *
 * Run: bare test/p2p-fetch-size.js [server-key]
 */

import { spawn } from 'bare-subprocess'
import os from 'bare-os'
import process from 'bare-process'
import fs from 'bare-fs'
import path from 'bare-path'

const SERVER_KEY = process.argv[2] || 'f580547b1fdba52331b0ed9483fc2cbe50fb3fb3cc76c904b580b7b7ee6f3d15'
const TIMEOUT = 120000

let passed = 0
let failed = 0

function ok (name, detail) { passed++; console.log(`  ✓ ${name}${detail ? ' — ' + detail : ''}`) }
function fail (name, reason) { failed++; console.log(`  ✗ ${name}: ${reason}`) }

function gitCmd (args, opts = {}) {
  return new Promise((resolve, reject) => {
    const timeout = opts.timeout || TIMEOUT
    let done = false
    const child = spawn('git', args, { stdio: ['ignore', 'pipe', 'pipe'] })

    const timer = setTimeout(() => {
      if (done) return
      done = true
      try { child.kill('SIGKILL') } catch {}
      reject(new Error(`timeout after ${timeout / 1000}s`))
    }, timeout)

    let stdout = ''
    let stderr = ''
    child.stdout.on('data', (d) => { stdout += d.toString() })
    child.stderr.on('data', (d) => { stderr += d.toString() })
    child.on('exit', (code) => {
      if (done) return
      done = true
      clearTimeout(timer)
      if (code === 0) resolve({ stdout: stdout.trim(), stderr: stderr.trim() })
      else reject(new Error(`exit ${code}: ${stderr.trim().split('\n').pop()}`))
    })
    child.on('error', (err) => {
      if (done) return
      done = true
      clearTimeout(timer)
      reject(err)
    })
  })
}

console.log('P2P Fetch Size Tests')
console.log('====================')
console.log(`Host: ${os.hostname()}`)
console.log(`Server: ${SERVER_KEY.slice(0, 8)}...`)
console.log('')

// Test 1: ls-remote (tiny, just refs)
console.log('1. ls-remote (ref list only, ~1KB)')
try {
  const url = `punch://${SERVER_KEY}/hyper-overlay`
  const { stdout } = await gitCmd(['ls-remote', url, 'refs/heads/release'], { timeout: 30000 })
  const ref = stdout.split('\t')[0]
  ok('hyper-overlay ls-remote', ref.slice(0, 12) + '...')
} catch (err) {
  fail('hyper-overlay ls-remote', err.message)
}

// Test 2: ls-remote for hyper-tps
console.log('2. ls-remote hyper-tps (same, just refs)')
try {
  const url = `punch://${SERVER_KEY}/hyper-tps`
  const { stdout } = await gitCmd(['ls-remote', url, 'refs/heads/release'], { timeout: 30000 })
  const ref = stdout.split('\t')[0]
  ok('hyper-tps ls-remote', ref.slice(0, 12) + '...')
} catch (err) {
  fail('hyper-tps ls-remote', err.message)
}

// Test 3: shallow clone small repo
console.log('3. Shallow clone hyper-overlay (small repo, ~123KB)')
const smallDir = path.join(os.tmpdir(), 'p2p-test-small-' + Date.now())
try {
  const url = `punch://${SERVER_KEY}/hyper-overlay`
  await gitCmd(['clone', '--depth', '1', '-b', 'release', url, smallDir], { timeout: 60000 })
  // Check size
  const { stdout } = await gitCmd(['-C', smallDir, 'count-objects', '-vH'])
  const sizeMatch = stdout.match(/size-pack: (.+)/)
  ok('hyper-overlay clone', sizeMatch ? 'pack size: ' + sizeMatch[1] : 'done')
} catch (err) {
  fail('hyper-overlay clone', err.message)
} finally {
  try { fs.rmSync(smallDir, { recursive: true }) } catch {}
}

// Test 4: Delta fetch — fetch into existing repo (should be tiny)
console.log('4. Delta fetch hyper-overlay (already have repo, fetch new commits only)')
try {
  const repoPath = path.join(os.homedir(), 'git-repos', 'hyper-overlay.git')
  await fs.promises.stat(repoPath)
  const url = `punch://${SERVER_KEY}/hyper-overlay`
  const t0 = Date.now()
  const { stderr } = await gitCmd(['-C', repoPath, 'fetch', url, 'release'], { timeout: 30000 })
  const elapsed = Date.now() - t0
  ok('hyper-overlay delta fetch', `${elapsed}ms` + (stderr ? ' — ' + stderr.split('\n').pop() : ''))
} catch (err) {
  if (err.message.includes('ENOENT')) {
    fail('hyper-overlay delta fetch', 'local bare repo not found')
  } else {
    fail('hyper-overlay delta fetch', err.message)
  }
}

// Test 5: Delta fetch hyper-tps (should also be small delta if already have most data)
console.log('5. Delta fetch hyper-tps (existing repo, delta only)')
try {
  const repoPath = path.join(os.homedir(), 'git-repos', 'hyper-tps.git')
  await fs.promises.stat(repoPath)
  const url = `punch://${SERVER_KEY}/hyper-tps`
  const t0 = Date.now()
  const { stderr } = await gitCmd(['-C', repoPath, 'fetch', url, 'release'], { timeout: 60000 })
  const elapsed = Date.now() - t0
  ok('hyper-tps delta fetch', `${elapsed}ms` + (stderr ? ' — ' + stderr.split('\n').pop() : ''))
} catch (err) {
  if (err.message.includes('ENOENT')) {
    fail('hyper-tps delta fetch', 'local bare repo not found')
  } else {
    fail('hyper-tps delta fetch', err.message)
  }
}

// Test 6: Full shallow clone hyper-tps (the big one)
console.log('6. Shallow clone hyper-tps (large repo, ~1.4GB — may timeout)')
const bigDir = path.join(os.tmpdir(), 'p2p-test-big-' + Date.now())
try {
  const url = `punch://${SERVER_KEY}/hyper-tps`
  const t0 = Date.now()
  await gitCmd(['clone', '--depth', '1', '-b', 'release', url, bigDir], { timeout: TIMEOUT })
  const elapsed = Date.now() - t0
  const { stdout } = await gitCmd(['-C', bigDir, 'count-objects', '-vH'])
  const sizeMatch = stdout.match(/size-pack: (.+)/)
  ok('hyper-tps clone', `${(elapsed/1000).toFixed(1)}s, pack: ${sizeMatch ? sizeMatch[1] : '?'}`)
} catch (err) {
  fail('hyper-tps clone', err.message)
} finally {
  try { fs.rmSync(bigDir, { recursive: true }) } catch {}
}

console.log('')
console.log(`Results: ${passed} passed, ${failed} failed`)
process.exit(failed > 0 ? 1 : 0)