mirror of
https://github.com/simplex-chat/simplexmq.git
synced 2026-05-25 03:45:23 +00:00
fix test 3
This commit is contained in:
@@ -1,85 +1,55 @@
|
||||
import {spawn, execSync, ChildProcess} from 'child_process'
|
||||
import {createHash} from 'crypto'
|
||||
import {createConnection} from 'net'
|
||||
import {createConnection, createServer} from 'net'
|
||||
import {resolve, join} from 'path'
|
||||
import {readFileSync, mkdtempSync, writeFileSync, copyFileSync, existsSync, unlinkSync} from 'fs'
|
||||
import {tmpdir} from 'os'
|
||||
|
||||
const XFTP_PORT = 7000
|
||||
const LOCK_FILE = join(tmpdir(), 'xftp-test-server.pid')
|
||||
export const PORT_FILE = join(tmpdir(), 'xftp-test-server.port')
|
||||
|
||||
// Find a free port by binding to port 0
|
||||
function findFreePort(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const srv = createServer()
|
||||
srv.listen(0, '127.0.0.1', () => {
|
||||
const addr = srv.address()
|
||||
if (addr && typeof addr === 'object') {
|
||||
const port = addr.port
|
||||
srv.close(() => resolve(port))
|
||||
} else {
|
||||
srv.close(() => reject(new Error('Could not get port')))
|
||||
}
|
||||
})
|
||||
srv.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
let server: ChildProcess | null = null
|
||||
let isOwner = false
|
||||
|
||||
// Kill any process listening on the given port (cross-platform)
|
||||
function killProcessOnPort(port: number): void {
|
||||
try {
|
||||
// Try lsof first (common on Mac/Linux)
|
||||
execSync(`lsof -ti :${port} | xargs kill -9 2>/dev/null`, {stdio: 'ignore'})
|
||||
return
|
||||
} catch (_) {}
|
||||
try {
|
||||
// Fallback: use netstat + awk (works on most systems)
|
||||
const cmd = process.platform === 'darwin'
|
||||
? `netstat -anv -p tcp | awk '$4 ~ /:${port}$/ && $6 == "LISTEN" {print $9}' | xargs kill -9 2>/dev/null`
|
||||
: `ss -tlnp 'sport = :${port}' | awk 'NR>1 {match($0, /pid=([0-9]+)/, a); print a[1]}' | xargs kill -9 2>/dev/null`
|
||||
execSync(cmd, {stdio: 'ignore'})
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Check if port is currently in use
|
||||
function isPortInUse(port: number): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const sock = createConnection({port, host: 'localhost'}, () => {
|
||||
sock.destroy()
|
||||
resolve(true)
|
||||
})
|
||||
sock.on('error', () => {
|
||||
sock.destroy()
|
||||
resolve(false)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export async function setup() {
|
||||
// Always check if port is in use first, regardless of lock file state
|
||||
if (await isPortInUse(XFTP_PORT)) {
|
||||
// Check if we have a valid lock file owner
|
||||
if (existsSync(LOCK_FILE)) {
|
||||
const pid = parseInt(readFileSync(LOCK_FILE, 'utf-8').trim(), 10)
|
||||
try {
|
||||
process.kill(pid, 0) // check if process exists
|
||||
// Lock owner is alive and port is in use — likely a valid running server
|
||||
await waitForPort(XFTP_PORT)
|
||||
return
|
||||
} catch (_) {
|
||||
// Lock owner is dead but port is in use — orphaned process
|
||||
}
|
||||
}
|
||||
// Port in use but no valid lock owner — kill the orphaned process
|
||||
console.log('[globalSetup] Port in use without valid lock, killing orphaned process...')
|
||||
killProcessOnPort(XFTP_PORT)
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
// Verify port is now free
|
||||
if (await isPortInUse(XFTP_PORT)) {
|
||||
throw new Error(`Port ${XFTP_PORT} still in use after cleanup attempt`)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up stale lock file if it exists
|
||||
if (existsSync(LOCK_FILE)) {
|
||||
// Check if another test process owns the server
|
||||
if (existsSync(LOCK_FILE) && existsSync(PORT_FILE)) {
|
||||
const pid = parseInt(readFileSync(LOCK_FILE, 'utf-8').trim(), 10)
|
||||
const port = parseInt(readFileSync(PORT_FILE, 'utf-8').trim(), 10)
|
||||
try {
|
||||
process.kill(pid, 0)
|
||||
// Process exists and port wasn't in use — wait for it
|
||||
await waitForPort(XFTP_PORT)
|
||||
process.kill(pid, 0) // check if process exists
|
||||
// Lock owner is alive — wait for server to be ready
|
||||
await waitForPort(port)
|
||||
return
|
||||
} catch (_) {
|
||||
unlinkSync(LOCK_FILE)
|
||||
// Lock owner is dead — clean up
|
||||
try { unlinkSync(LOCK_FILE) } catch (_) {}
|
||||
try { unlinkSync(PORT_FILE) } catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
// Find a free port dynamically
|
||||
const xftpPort = await findFreePort()
|
||||
|
||||
writeFileSync(LOCK_FILE, String(process.pid))
|
||||
writeFileSync(PORT_FILE, String(xftpPort))
|
||||
isOwner = true
|
||||
|
||||
const fixtures = resolve(__dirname, '../../tests/fixtures')
|
||||
@@ -107,7 +77,7 @@ enable: off
|
||||
|
||||
[TRANSPORT]
|
||||
host: localhost
|
||||
port: ${XFTP_PORT}
|
||||
port: ${xftpPort}
|
||||
|
||||
[FILES]
|
||||
path: ${filesDir}
|
||||
@@ -136,12 +106,13 @@ key: ${join(fixtures, 'web.key')}
|
||||
})
|
||||
|
||||
// Poll-connect until the server is actually listening
|
||||
await waitForServerReady(server, XFTP_PORT)
|
||||
await waitForServerReady(server, xftpPort)
|
||||
}
|
||||
|
||||
export async function teardown() {
|
||||
if (isOwner) {
|
||||
try { unlinkSync(LOCK_FILE) } catch (_) {}
|
||||
try { unlinkSync(PORT_FILE) } catch (_) {}
|
||||
if (server) {
|
||||
server.kill('SIGTERM')
|
||||
await new Promise<void>(resolve => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import {defineConfig, type Plugin} from 'vite'
|
||||
import {readFileSync} from 'fs'
|
||||
import {createHash} from 'crypto'
|
||||
import presets from './web/servers.json'
|
||||
import {PORT_FILE} from './test/globalSetup'
|
||||
|
||||
function parseHost(addr: string): string {
|
||||
const m = addr.match(/@(.+)$/)
|
||||
@@ -35,7 +36,9 @@ export default defineConfig(({mode}) => {
|
||||
const der = Buffer.from(pem.replace(/-----[^-]+-----/g, '').replace(/\s/g, ''), 'base64')
|
||||
const fp = createHash('sha256').update(der).digest('base64')
|
||||
.replace(/\+/g, '-').replace(/\//g, '_')
|
||||
servers = [`xftp://${fp}@localhost:7000`]
|
||||
// PORT_FILE is written by globalSetup before vite build runs
|
||||
const port = readFileSync(PORT_FILE, 'utf-8').trim()
|
||||
servers = [`xftp://${fp}@localhost:${port}`]
|
||||
define['__XFTP_SERVERS__'] = JSON.stringify(servers)
|
||||
} else {
|
||||
servers = [...presets.simplex, ...presets.flux]
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
import {defineConfig} from 'vitest/config'
|
||||
import {defineConfig, type Plugin} from 'vitest/config'
|
||||
import {readFileSync} from 'fs'
|
||||
import {createHash} from 'crypto'
|
||||
import {PORT_FILE} from './test/globalSetup'
|
||||
|
||||
// Compute fingerprint from ca.crt (SHA-256 of DER, same as Haskell's loadFileFingerprint)
|
||||
const pem = readFileSync('../tests/fixtures/ca.crt', 'utf-8')
|
||||
const der = Buffer.from(pem.replace(/-----[^-]+-----/g, '').replace(/\s/g, ''), 'base64')
|
||||
const fingerprint = createHash('sha256').update(der).digest('base64').replace(/\+/g, '-').replace(/\//g, '_')
|
||||
const serverAddr = `xftp://${fingerprint}@localhost:7000`
|
||||
|
||||
// Plugin to inject XFTP_SERVER at transform time (after globalSetup writes PORT_FILE)
|
||||
function xftpServerPlugin(): Plugin {
|
||||
let serverAddr: string | null = null
|
||||
return {
|
||||
name: 'xftp-server-define',
|
||||
transform(code, id) {
|
||||
if (!code.includes('import.meta.env.XFTP_SERVER')) return null
|
||||
if (!serverAddr) {
|
||||
const port = readFileSync(PORT_FILE, 'utf-8').trim()
|
||||
serverAddr = `xftp://${fingerprint}@localhost:${port}`
|
||||
}
|
||||
return code.replace(/import\.meta\.env\.XFTP_SERVER/g, JSON.stringify(serverAddr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
esbuild: {target: 'esnext'},
|
||||
optimizeDeps: {esbuildOptions: {target: 'esnext'}},
|
||||
define: {
|
||||
'import.meta.env.XFTP_SERVER': JSON.stringify(serverAddr)
|
||||
},
|
||||
plugins: [xftpServerPlugin()],
|
||||
test: {
|
||||
include: ['test/**/*.test.ts'],
|
||||
browser: {
|
||||
|
||||
Reference in New Issue
Block a user