#!/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)