| … | … | @@ -0,0 +1,160 @@ |
| 1 | #!/usr/bin/env bare |
| 2 | /** |
| 3 | * P2P Fetch Size Test |
| 4 | * |
| 5 | * Tests whether git fetch over punch:// works for: |
| 6 | * 1. Small repos (hyper-overlay ~123KB) |
| 7 | * 2. Large repos (hyper-tps ~1.4GB) |
| 8 | * 3. Delta-only fetch (already have most data, fetch 1 new commit) |
| 9 | * |
| 10 | * Run: bare test/p2p-fetch-size.js [server-key] |
| 11 | */ |
| 12 | |
| 13 | import { spawn } from 'bare-subprocess' |
| 14 | import os from 'bare-os' |
| 15 | import process from 'bare-process' |
| 16 | import fs from 'bare-fs' |
| 17 | import path from 'bare-path' |
| 18 | |
| 19 | const SERVER_KEY = process.argv[2] || 'f580547b1fdba52331b0ed9483fc2cbe50fb3fb3cc76c904b580b7b7ee6f3d15' |
| 20 | const TIMEOUT = 120000 |
| 21 | |
| 22 | let passed = 0 |
| 23 | let failed = 0 |
| 24 | |
| 25 | function ok (name, detail) { passed++; console.log(` ✓ ${name}${detail ? ' — ' + detail : ''}`) } |
| 26 | function fail (name, reason) { failed++; console.log(` ✗ ${name}: ${reason}`) } |
| 27 | |
| 28 | function gitCmd (args, opts = {}) { |
| 29 | return new Promise((resolve, reject) => { |
| 30 | const timeout = opts.timeout || TIMEOUT |
| 31 | let done = false |
| 32 | const child = spawn('git', args, { stdio: ['ignore', 'pipe', 'pipe'] }) |
| 33 | |
| 34 | const timer = setTimeout(() => { |
| 35 | if (done) return |
| 36 | done = true |
| 37 | try { child.kill('SIGKILL') } catch {} |
| 38 | reject(new Error(`timeout after ${timeout / 1000}s`)) |
| 39 | }, timeout) |
| 40 | |
| 41 | let stdout = '' |
| 42 | let stderr = '' |
| 43 | child.stdout.on('data', (d) => { stdout += d.toString() }) |
| 44 | child.stderr.on('data', (d) => { stderr += d.toString() }) |
| 45 | child.on('exit', (code) => { |
| 46 | if (done) return |
| 47 | done = true |
| 48 | clearTimeout(timer) |
| 49 | if (code === 0) resolve({ stdout: stdout.trim(), stderr: stderr.trim() }) |
| 50 | else reject(new Error(`exit ${code}: ${stderr.trim().split('\n').pop()}`)) |
| 51 | }) |
| 52 | child.on('error', (err) => { |
| 53 | if (done) return |
| 54 | done = true |
| 55 | clearTimeout(timer) |
| 56 | reject(err) |
| 57 | }) |
| 58 | }) |
| 59 | } |
| 60 | |
| 61 | console.log('P2P Fetch Size Tests') |
| 62 | console.log('====================') |
| 63 | console.log(`Host: ${os.hostname()}`) |
| 64 | console.log(`Server: ${SERVER_KEY.slice(0, 8)}...`) |
| 65 | console.log('') |
| 66 | |
| 67 | // Test 1: ls-remote (tiny, just refs) |
| 68 | console.log('1. ls-remote (ref list only, ~1KB)') |
| 69 | try { |
| 70 | const url = `punch://${SERVER_KEY}/hyper-overlay` |
| 71 | const { stdout } = await gitCmd(['ls-remote', url, 'refs/heads/release'], { timeout: 30000 }) |
| 72 | const ref = stdout.split('\t')[0] |
| 73 | ok('hyper-overlay ls-remote', ref.slice(0, 12) + '...') |
| 74 | } catch (err) { |
| 75 | fail('hyper-overlay ls-remote', err.message) |
| 76 | } |
| 77 | |
| 78 | // Test 2: ls-remote for hyper-tps |
| 79 | console.log('2. ls-remote hyper-tps (same, just refs)') |
| 80 | try { |
| 81 | const url = `punch://${SERVER_KEY}/hyper-tps` |
| 82 | const { stdout } = await gitCmd(['ls-remote', url, 'refs/heads/release'], { timeout: 30000 }) |
| 83 | const ref = stdout.split('\t')[0] |
| 84 | ok('hyper-tps ls-remote', ref.slice(0, 12) + '...') |
| 85 | } catch (err) { |
| 86 | fail('hyper-tps ls-remote', err.message) |
| 87 | } |
| 88 | |
| 89 | // Test 3: shallow clone small repo |
| 90 | console.log('3. Shallow clone hyper-overlay (small repo, ~123KB)') |
| 91 | const smallDir = path.join(os.tmpdir(), 'p2p-test-small-' + Date.now()) |
| 92 | try { |
| 93 | const url = `punch://${SERVER_KEY}/hyper-overlay` |
| 94 | await gitCmd(['clone', '--depth', '1', '-b', 'release', url, smallDir], { timeout: 60000 }) |
| 95 | // Check size |
| 96 | const { stdout } = await gitCmd(['-C', smallDir, 'count-objects', '-vH']) |
| 97 | const sizeMatch = stdout.match(/size-pack: (.+)/) |
| 98 | ok('hyper-overlay clone', sizeMatch ? 'pack size: ' + sizeMatch[1] : 'done') |
| 99 | } catch (err) { |
| 100 | fail('hyper-overlay clone', err.message) |
| 101 | } finally { |
| 102 | try { fs.rmSync(smallDir, { recursive: true }) } catch {} |
| 103 | } |
| 104 | |
| 105 | // Test 4: Delta fetch — fetch into existing repo (should be tiny) |
| 106 | console.log('4. Delta fetch hyper-overlay (already have repo, fetch new commits only)') |
| 107 | try { |
| 108 | const repoPath = path.join(os.homedir(), 'git-repos', 'hyper-overlay.git') |
| 109 | await fs.promises.stat(repoPath) |
| 110 | const url = `punch://${SERVER_KEY}/hyper-overlay` |
| 111 | const t0 = Date.now() |
| 112 | const { stderr } = await gitCmd(['-C', repoPath, 'fetch', url, 'release'], { timeout: 30000 }) |
| 113 | const elapsed = Date.now() - t0 |
| 114 | ok('hyper-overlay delta fetch', `${elapsed}ms` + (stderr ? ' — ' + stderr.split('\n').pop() : '')) |
| 115 | } catch (err) { |
| 116 | if (err.message.includes('ENOENT')) { |
| 117 | fail('hyper-overlay delta fetch', 'local bare repo not found') |
| 118 | } else { |
| 119 | fail('hyper-overlay delta fetch', err.message) |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | // Test 5: Delta fetch hyper-tps (should also be small delta if already have most data) |
| 124 | console.log('5. Delta fetch hyper-tps (existing repo, delta only)') |
| 125 | try { |
| 126 | const repoPath = path.join(os.homedir(), 'git-repos', 'hyper-tps.git') |
| 127 | await fs.promises.stat(repoPath) |
| 128 | const url = `punch://${SERVER_KEY}/hyper-tps` |
| 129 | const t0 = Date.now() |
| 130 | const { stderr } = await gitCmd(['-C', repoPath, 'fetch', url, 'release'], { timeout: 60000 }) |
| 131 | const elapsed = Date.now() - t0 |
| 132 | ok('hyper-tps delta fetch', `${elapsed}ms` + (stderr ? ' — ' + stderr.split('\n').pop() : '')) |
| 133 | } catch (err) { |
| 134 | if (err.message.includes('ENOENT')) { |
| 135 | fail('hyper-tps delta fetch', 'local bare repo not found') |
| 136 | } else { |
| 137 | fail('hyper-tps delta fetch', err.message) |
| 138 | } |
| 139 | } |
| 140 | |
| 141 | // Test 6: Full shallow clone hyper-tps (the big one) |
| 142 | console.log('6. Shallow clone hyper-tps (large repo, ~1.4GB — may timeout)') |
| 143 | const bigDir = path.join(os.tmpdir(), 'p2p-test-big-' + Date.now()) |
| 144 | try { |
| 145 | const url = `punch://${SERVER_KEY}/hyper-tps` |
| 146 | const t0 = Date.now() |
| 147 | await gitCmd(['clone', '--depth', '1', '-b', 'release', url, bigDir], { timeout: TIMEOUT }) |
| 148 | const elapsed = Date.now() - t0 |
| 149 | const { stdout } = await gitCmd(['-C', bigDir, 'count-objects', '-vH']) |
| 150 | const sizeMatch = stdout.match(/size-pack: (.+)/) |
| 151 | ok('hyper-tps clone', `${(elapsed/1000).toFixed(1)}s, pack: ${sizeMatch ? sizeMatch[1] : '?'}`) |
| 152 | } catch (err) { |
| 153 | fail('hyper-tps clone', err.message) |
| 154 | } finally { |
| 155 | try { fs.rmSync(bigDir, { recursive: true }) } catch {} |
| 156 | } |
| 157 | |
| 158 | console.log('') |
| 159 | console.log(`Results: ${passed} passed, ${failed} failed`) |
| 160 | process.exit(failed > 0 ? 1 : 0) |