| … | … | @@ -0,0 +1,157 @@ |
| 1 | #!/usr/bin/env bare |
| 2 | /** |
| 3 | * P2P Connectivity Test Suite |
| 4 | * |
| 5 | * Run on any machine to verify Hyperswarm connectivity: |
| 6 | * bare test/p2p-connectivity.js [tabertron-server-key] |
| 7 | * |
| 8 | * Tests: |
| 9 | * 1. Hyperswarm initialization (can we create a swarm?) |
| 10 | * 2. DHT bootstrap (can we reach the DHT network?) |
| 11 | * 3. Topic announcement (can we announce + discover?) |
| 12 | * 4. Peer connection (can we connect to a specific server?) |
| 13 | * 5. git-remote-punch (does the git transport work?) |
| 14 | */ |
| 15 | |
| 16 | import Hyperswarm from 'hyperswarm' |
| 17 | import crypto from 'hypercore-crypto' |
| 18 | import { spawn } from 'bare-subprocess' |
| 19 | import os from 'bare-os' |
| 20 | import process from 'bare-process' |
| 21 | |
| 22 | const SERVER_KEY = process.argv[2] || 'f580547b1fdba52331b0ed9483fc2cbe50fb3fb3cc76c904b580b7b7ee6f3d15' |
| 23 | const REPO = process.argv[3] || 'hyper-overlay' |
| 24 | const TIMEOUT = 15000 |
| 25 | |
| 26 | let passed = 0 |
| 27 | let failed = 0 |
| 28 | |
| 29 | function ok (name) { passed++; console.log(` ✓ ${name}`) } |
| 30 | function fail (name, reason) { failed++; console.log(` ✗ ${name}: ${reason}`) } |
| 31 | |
| 32 | async function withTimeout (promise, ms, label) { |
| 33 | return Promise.race([ |
| 34 | promise, |
| 35 | new Promise((_, reject) => setTimeout(() => reject(new Error(`timeout after ${ms}ms`)), ms)) |
| 36 | ]) |
| 37 | } |
| 38 | |
| 39 | console.log('P2P Connectivity Tests') |
| 40 | console.log('======================') |
| 41 | console.log(`Host: ${os.hostname()}`) |
| 42 | console.log(`Server key: ${SERVER_KEY.slice(0, 8)}...`) |
| 43 | console.log(`Test repo: ${REPO}`) |
| 44 | console.log('') |
| 45 | |
| 46 | // Test 1: Hyperswarm initialization |
| 47 | console.log('1. Hyperswarm initialization') |
| 48 | let swarm |
| 49 | try { |
| 50 | swarm = new Hyperswarm() |
| 51 | ok('Created Hyperswarm instance') |
| 52 | } catch (err) { |
| 53 | fail('Hyperswarm creation', err.message) |
| 54 | process.exit(1) |
| 55 | } |
| 56 | |
| 57 | // Test 2: DHT bootstrap |
| 58 | console.log('2. DHT bootstrap') |
| 59 | try { |
| 60 | // The swarm's DHT bootstraps on first operation |
| 61 | const topic = crypto.randomBytes(32) |
| 62 | const discovery = swarm.join(topic, { server: false, client: true }) |
| 63 | await withTimeout(swarm.flush(), 10000, 'DHT flush') |
| 64 | discovery.destroy() |
| 65 | ok('DHT bootstrap + flush succeeded') |
| 66 | } catch (err) { |
| 67 | fail('DHT bootstrap', err.message) |
| 68 | } |
| 69 | |
| 70 | // Test 3: Self-connection test (announce + discover same topic) |
| 71 | console.log('3. Self-announce test') |
| 72 | try { |
| 73 | const swarm2 = new Hyperswarm() |
| 74 | const topic = crypto.randomBytes(32) |
| 75 | |
| 76 | const serverReady = new Promise((resolve) => { |
| 77 | swarm2.on('connection', (conn) => { |
| 78 | conn.destroy() |
| 79 | resolve() |
| 80 | }) |
| 81 | }) |
| 82 | |
| 83 | swarm2.join(topic, { server: true, client: false }) |
| 84 | await swarm2.flush() |
| 85 | |
| 86 | swarm.join(topic, { server: false, client: true }) |
| 87 | |
| 88 | await withTimeout(serverReady, TIMEOUT, 'self-connection') |
| 89 | ok('Self-connection works (announce + discover)') |
| 90 | |
| 91 | await swarm2.destroy() |
| 92 | } catch (err) { |
| 93 | fail('Self-connection', err.message) |
| 94 | } |
| 95 | |
| 96 | // Test 4: Connect to remote server topic |
| 97 | console.log('4. Remote server connection') |
| 98 | try { |
| 99 | const keyBuf = Buffer.from(SERVER_KEY, 'hex') |
| 100 | const topic = crypto.hash(Buffer.concat([keyBuf, Buffer.from(REPO, 'utf-8')])).subarray(0, 32) |
| 101 | |
| 102 | const swarm3 = new Hyperswarm() |
| 103 | swarm3.join(topic, { server: false, client: true }) |
| 104 | |
| 105 | const conn = await withTimeout( |
| 106 | new Promise((resolve, reject) => { |
| 107 | swarm3.once('connection', (conn, info) => { |
| 108 | resolve({ conn, key: info.publicKey.toString('hex').slice(0, 8) }) |
| 109 | }) |
| 110 | swarm3.once('error', reject) |
| 111 | }), |
| 112 | TIMEOUT, |
| 113 | 'remote connection' |
| 114 | ) |
| 115 | |
| 116 | ok(`Connected to peer ${conn.key}...`) |
| 117 | conn.conn.destroy() |
| 118 | await swarm3.destroy() |
| 119 | } catch (err) { |
| 120 | fail('Remote connection', err.message) |
| 121 | } |
| 122 | |
| 123 | // Test 5: git ls-remote via punch:// |
| 124 | console.log('5. git ls-remote via punch://') |
| 125 | try { |
| 126 | const url = `punch://${SERVER_KEY}/${REPO}` |
| 127 | const result = await withTimeout( |
| 128 | new Promise((resolve, reject) => { |
| 129 | const child = spawn('git', ['ls-remote', url, 'refs/heads/release'], { |
| 130 | stdio: ['ignore', 'pipe', 'pipe'] |
| 131 | }) |
| 132 | let stdout = '' |
| 133 | let stderr = '' |
| 134 | child.stdout.on('data', (d) => { stdout += d.toString() }) |
| 135 | child.stderr.on('data', (d) => { stderr += d.toString() }) |
| 136 | child.on('exit', (code) => { |
| 137 | if (code === 0) resolve(stdout.trim()) |
| 138 | else reject(new Error(`exit ${code}: ${stderr.trim().split('\n').pop()}`)) |
| 139 | }) |
| 140 | child.on('error', reject) |
| 141 | }), |
| 142 | 30000, |
| 143 | 'git ls-remote' |
| 144 | ) |
| 145 | const ref = result.split('\t')[0] |
| 146 | ok(`git ls-remote returned: ${ref.slice(0, 12)}...`) |
| 147 | } catch (err) { |
| 148 | fail('git ls-remote', err.message) |
| 149 | } |
| 150 | |
| 151 | // Cleanup |
| 152 | await swarm.destroy() |
| 153 | |
| 154 | // Summary |
| 155 | console.log('') |
| 156 | console.log(`Results: ${passed} passed, ${failed} failed`) |
| 157 | process.exit(failed > 0 ? 1 : 0) |