OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git commits / a8c66b3

Add P2P connectivity test suite
5-level test: Hyperswarm init → DHT bootstrap → self-announce → remote server connection → git ls-remote via punch:// Run on any machine: bare test/p2p-connectivity.js [server-key] [repo] Pinpoints exactly where P2P fails. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
a8c66b3e5ebc0e23d4c52d3ce49deeb65dc88d6d
by plankatron <fleeky@gmail.com> · 2026-04-07T13:35:25.000Z

Changed files (1) +157 -0

test/p2p-connectivity.js +157 -0
……@@ -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
16import Hyperswarm from 'hyperswarm'
17import crypto from 'hypercore-crypto'
18import { spawn } from 'bare-subprocess'
19import os from 'bare-os'
20import process from 'bare-process'
21
22const SERVER_KEY = process.argv[2] || 'f580547b1fdba52331b0ed9483fc2cbe50fb3fb3cc76c904b580b7b7ee6f3d15'
23const REPO = process.argv[3] || 'hyper-overlay'
24const TIMEOUT = 15000
25
26let passed = 0
27let failed = 0
28
29function ok (name) { passed++; console.log(` ✓ ${name}`) }
30function fail (name, reason) { failed++; console.log(` ✗ ${name}: ${reason}`) }
31
32async 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
39console.log('P2P Connectivity Tests')
40console.log('======================')
41console.log(`Host: ${os.hostname()}`)
42console.log(`Server key: ${SERVER_KEY.slice(0, 8)}...`)
43console.log(`Test repo: ${REPO}`)
44console.log('')
45
46// Test 1: Hyperswarm initialization
47console.log('1. Hyperswarm initialization')
48let swarm
49try {
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
58console.log('2. DHT bootstrap')
59try {
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)
71console.log('3. Self-announce test')
72try {
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
97console.log('4. Remote server connection')
98try {
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://
124console.log('5. git ls-remote via punch://')
125try {
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
152await swarm.destroy()
153
154// Summary
155console.log('')
156console.log(`Results: ${passed} passed, ${failed} failed`)
157process.exit(failed > 0 ? 1 : 0)