OverviewFilesCommitsIssuesDocsReleasesPeers

hyperswarm-peer / test / test_cpp_to_cpp.js

175 lines · javascript ref:
#!/usr/bin/env node
// E2E Test: C++ server ↔ C++ client via local testnet.
//
// Spawns both test_interop_server_bin and test_interop_client against a
// shared HyperDHT testnet. This tests the pure C++ path — no JS peers.
//
// Flow:
//   1. Create local testnet (3 HyperDHT nodes)
//   2. Spawn C++ server, wait for PUBLIC_KEY + READY
//   3. Spawn C++ client with server's public key
//   4. Both should holepunch/LAN connect and exit 0
//
// Usage: node test_cpp_to_cpp.js
// Requires: test_interop_server_bin + test_interop_client built via build_tests.sh

const createTestnet = require('hyperdht/testnet')
const { spawn } = require('child_process')
const path = require('path')

const TIMEOUT_MS = 20000

async function main () {
  console.log('=== E2E Test: C++ Server ↔ C++ Client ===\n')

  let passed = 0
  let failed = 0
  const check = (cond, name) => {
    if (cond) {
      passed++
      console.log('  PASS:', name)
    } else {
      failed++
      console.log('  FAIL:', name)
    }
  }

  // Create local testnet.
  console.log('Creating local testnet (3 HyperDHT nodes)...')
  const testnet = await createTestnet(3, { teardown: () => {} })
  const bootstrapStr = testnet.bootstrap.map(b => b.host + ':' + b.port)
  console.log('Bootstrap:', bootstrapStr.join(', '))
  check(testnet.nodes.length === 3, 'Testnet created with 3 nodes')

  const allBootstrap = bootstrapStr.join(',')

  // Spawn C++ server.
  const serverBin = path.join(__dirname, 'test_interop_server_bin')
  console.log('\nSpawning C++ server...')
  const server = spawn(serverBin, [allBootstrap], {
    stdio: ['pipe', 'pipe', 'pipe']
  })

  let serverStdout = ''
  let serverPk = null
  let serverReady = false

  server.stderr.on('data', d => process.stderr.write('[server err] ' + d))

  // Wait for server READY.
  const serverReadyPromise = new Promise((resolve, reject) => {
    const readyTimeout = setTimeout(() => {
      reject(new Error('C++ server did not become READY within timeout'))
    }, TIMEOUT_MS)

    server.stdout.on('data', (d) => {
      const s = d.toString()
      serverStdout += s
      for (const line of s.split('\n').filter(Boolean)) {
        console.log('[server]', line)
        if (line.startsWith('PUBLIC_KEY:')) {
          serverPk = line.slice('PUBLIC_KEY:'.length).trim()
          console.log('  → Parsed server PK:', serverPk.slice(0, 16) + '...')
        }
        if (line === 'READY') {
          serverReady = true
          clearTimeout(readyTimeout)
          resolve()
        }
      }
    })

    server.on('error', (err) => {
      clearTimeout(readyTimeout)
      reject(err)
    })

    server.on('exit', (code) => {
      clearTimeout(readyTimeout)
      if (!serverReady) {
        reject(new Error('C++ server exited before READY (code=' + code + ')'))
      }
    })
  })

  try {
    await serverReadyPromise
    check(serverReady, 'C++ server bootstrapped and listening')
    check(serverPk !== null && serverPk.length === 64, 'C++ server public key parsed')

    // Spawn C++ client.
    const clientBin = path.join(__dirname, 'test_interop_client')
    console.log('\nSpawning C++ client...')
    console.log('  Target PK:', serverPk.slice(0, 16) + '...\n')

    const client = spawn(clientBin, [allBootstrap, serverPk], {
      stdio: ['pipe', 'pipe', 'pipe']
    })

    let clientStdout = ''
    client.stdout.on('data', d => {
      const s = d.toString()
      clientStdout += s
      for (const line of s.split('\n').filter(Boolean)) {
        console.log('[client]', line)
      }
    })
    client.stderr.on('data', d => process.stderr.write('[client err] ' + d))

    // Wait for both processes.
    const overallTimeout = setTimeout(() => {
      console.log('\nTIMEOUT: killing both processes')
      server.kill('SIGKILL')
      client.kill('SIGKILL')
    }, TIMEOUT_MS)

    let clientExitCode = null
    let serverExitCode = null

    const clientDone = new Promise(resolve => {
      client.on('exit', code => {
        clientExitCode = code
        console.log('\nclient exited with code', code)
        // Client connected successfully — kill server.
        if (code === 0) {
          server.kill('SIGTERM')
        }
        resolve()
      })
    })

    const serverDone = new Promise(resolve => {
      server.on('exit', code => {
        serverExitCode = code
        console.log('server exited with code', code)
        resolve()
      })
    })

    await Promise.all([clientDone, serverDone])
    clearTimeout(overallTimeout)

    // Check results.
    console.log('\n--- Results ---')
    check(clientExitCode === 0, 'C++ client connected (exit 0)')
    check(serverExitCode === 0 || serverExitCode === null, 'C++ server connected or was terminated')
    check(clientStdout.includes('connected'), 'C++ client reported connection')
    check(serverStdout.includes('CONNECTED') || serverStdout.includes('HANDSHAKE'), 'C++ server received handshake/connection')

  } catch (err) {
    console.error('\nTest error:', err.message)
    check(false, 'Test completed without error')
    try { server.kill('SIGKILL') } catch (_) {}
  } finally {
    await testnet.destroy()
  }

  console.log('\n=== Summary:', passed + '/' + (passed + failed), 'passed ===')
  process.exit(failed > 0 ? 1 : 0)
}

main().catch((err) => {
  console.error('Fatal:', err)
  process.exit(1)
})