Files
Web_FreeCAD_Bitbybit/scripts/run-browser-occt-harness.mjs

107 lines
5.3 KiB
JavaScript

import { createServer } from 'node:http'
import { mkdir, readFile, writeFile } from 'node:fs/promises'
import { createReadStream } from 'node:fs'
import { existsSync } from 'node:fs'
import { resolve, extname, join, normalize } from 'node:path'
import { spawn } from 'node:child_process'
import { createChromeProfile, removeChromeProfile } from './chrome-profile.mjs'
const root = resolve(new URL('..', import.meta.url).pathname)
const reportDir = resolve(root, '.cache/freecad/browser-occt')
await mkdir(reportDir, { recursive: true })
const contentTypes = { '.html': 'text/html', '.js': 'text/javascript', '.wasm': 'application/wasm', '.json': 'application/json' }
const reportPromises = new Map()
const reportResolvers = new Map()
const waitForReport = (browserId) => {
if (reportPromises.has(browserId)) return reportPromises.get(browserId)
const promise = new Promise((resolveReport, reject) => { reportResolvers.set(browserId, { resolve: resolveReport, reject }) })
reportPromises.set(browserId, promise)
return promise
}
const server = createServer(async (request, response) => {
response.setHeader('Cross-Origin-Opener-Policy', 'same-origin')
response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp')
response.setHeader('Cross-Origin-Resource-Policy', 'same-origin')
if (request.method === 'POST' && request.url?.startsWith('/__occt-report')) {
let body = ''
request.setEncoding('utf8')
request.on('data', (chunk) => { body += chunk })
request.on('end', async () => {
try {
const report = JSON.parse(body)
const browserId = report.browserId || new URL(request.url, 'http://127.0.0.1').searchParams.get('browser') || 'unknown'
await writeFile(resolve(reportDir, `${browserId}.json`), `${JSON.stringify(report, null, 2)}\n`)
reportResolvers.get(browserId)?.resolve(report)
response.writeHead(204)
response.end()
} catch (error) {
response.writeHead(400)
response.end(String(error))
}
})
return
}
const pathname = decodeURIComponent((request.url || '/').split('?')[0])
const relative = pathname === '/occt-browser-harness.html' ? 'public/occt-browser-harness.html' : pathname.startsWith('/native/') ? `public${pathname}` : null
if (!relative) { response.writeHead(404); response.end('not found'); return }
const file = normalize(join(root, relative))
if (!file.startsWith(resolve(root, 'public')) || !existsSync(file)) { response.writeHead(404); response.end('not found'); return }
response.setHeader('content-type', contentTypes[extname(file)] || 'application/octet-stream')
createReadStream(file).pipe(response)
})
await new Promise((resolveServer) => server.listen(0, '127.0.0.1', resolveServer))
const port = server.address().port
const runBrowser = (browserId, executable, args) => new Promise((resolveRun) => {
if (!executable || !existsSync(executable)) { resolveRun({ browserId, status: 'unavailable', executable }); return }
const reportPromise = waitForReport(browserId)
const child = spawn(executable, [...args, `http://127.0.0.1:${port}/occt-browser-harness.html?browser=${browserId}`], { cwd: root, stdio: ['ignore', 'pipe', 'pipe'] })
let stdout = ''
child.stdout.on('data', (chunk) => { stdout += String(chunk) })
let stderr = ''
child.stderr.on('data', (chunk) => { stderr += String(chunk) })
let settled = false
const finish = (result) => {
if (settled) return
settled = true
clearTimeout(timer)
resolveRun({ ...result, stdout: stdout.slice(-4000), stderr: stderr.slice(-2000) })
}
const timer = setTimeout(() => { child.kill('SIGTERM'); finish({ browserId, status: 'timeout' }) }, 240000)
reportPromise.then((report) => {
if (settled) return
child.kill('SIGTERM')
finish({ browserId, status: report.status || 'pass', report, exitCode: child.exitCode, signal: null })
}).catch(() => {})
child.on('close', async (code, signal) => {
if (!settled) {
try {
const report = await Promise.race([reportPromise, new Promise((_, reject) => setTimeout(() => reject(new Error('report timeout')), 5000))])
finish({ browserId, status: report.status || 'pass', report, exitCode: code, signal })
} catch {
finish({ browserId, status: 'failed', exitCode: code, signal })
}
}
})
})
const results = []
const chromeProfile = await createChromeProfile('occt')
try {
results.push(await runBrowser('chrome', process.env.CHROME_BIN || '/home/mes123456/.local/bin/google-chrome', ['--headless=new', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--enable-logging=stderr', '--v=1', `--user-data-dir=${chromeProfile}`]))
} finally {
await removeChromeProfile(chromeProfile)
}
await new Promise((resolveServer) => server.close(resolveServer))
// Keep committed evidence stable: Chrome's temporary profile paths, timestamps
// and verbose stderr are diagnostics, not compatibility claims.
const verification = {
schemaVersion: 1,
headers: { coop: 'same-origin', coep: 'require-corp' },
browsers: results.map(({ stdout, stderr, ...result }) => result),
status: results.every((result) => result.status === 'pass') ? 'pass' : 'partial',
}
await writeFile(resolve(root, 'config/occt-browser-verification.json'), `${JSON.stringify(verification, null, 2)}\n`)
console.log(JSON.stringify(verification, null, 2))
if (results.some((result) => result.status !== 'pass')) process.exit(1)