| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138 |
- /** Exercise filtered Desktop native and HTML dependencies under its bundled Node. */
- import assert from 'node:assert/strict'
- import { closeSync, mkdtempSync, openSync, readFileSync, readSync, writeFileSync } from 'node:fs'
- import { rm } from 'node:fs/promises'
- import { createRequire } from 'node:module'
- import { tmpdir } from 'node:os'
- import { join, resolve } from 'node:path'
- const runtime = process.argv[2]
- assert.ok(runtime, 'Pass the filtered resources/dsh directory')
- const root = resolve(runtime)
- const descriptor = JSON.parse(readFileSync(join(root, 'desktop-runtime.json'), 'utf8'))
- assert.equal(process.versions.node, descriptor.release.nodeVersion, 'Run with the bundled Node version')
- assert.equal(process.platform, descriptor.platform)
- assert.equal(process.arch, descriptor.arch)
- const requireRuntime = createRequire(join(root, 'package.json'))
- const scratch = mkdtempSync(join(tmpdir(), 'dsh-runtime-payload-'))
- /** Spawn only a fixed Node program and await the terminal's drained exit event. */
- async function checkPty() {
- const pty = requireRuntime('node-pty')
- const script = join(scratch, 'pty.cjs')
- writeFileSync(script, "process.stdout.write('runtime-payload-pty-ok\\n')\n", { flag: 'wx', mode: 0o600 })
- const env = Object.fromEntries(Object.entries(process.env).filter(([name]) => (
- /^(?:path|systemroot|windir|comspec)$/iu.test(name)
- )))
- Object.assign(env, { HOME: scratch, USERPROFILE: scratch, TMP: scratch, TEMP: scratch, TMPDIR: scratch })
- const terminal = pty.spawn(process.execPath, [script], { cwd: scratch, env, cols: 80, rows: 24 })
- let output = ''
- let exited = false
- let timedOut = false
- let exitSubscription
- const exit = new Promise(resolveExit => {
- exitSubscription = terminal.onExit(event => {
- exited = true
- resolveExit(event)
- })
- })
- const dataSubscription = terminal.onData(data => { output += data })
- let timer
- try {
- const deadline = new Promise((_, reject) => {
- timer = setTimeout(() => {
- timedOut = true
- reject(new Error('Packaged PTY did not exit within 45 seconds'))
- }, 45_000)
- })
- const result = await Promise.race([exit, deadline])
- assert.equal(timedOut, false)
- assert.ok(result.signal === undefined || result.signal === 0, 'PTY exited without a signal')
- assert.equal(result.exitCode, 0)
- assert.match(output, /runtime-payload-pty-ok/u)
- } finally {
- clearTimeout(timer)
- dataSubscription.dispose()
- try {
- // node-pty's Windows natural-exit event closes output but leaves its ConPTY worker owned by kill().
- if (!exited || process.platform === 'win32') terminal.kill()
- await exit
- } finally {
- exitSubscription.dispose()
- }
- }
- }
- /** fs-ext implements seek on Windows through SetFilePointerEx and on POSIX through lseek. */
- function checkFsExt() {
- const fsExt = requireRuntime('fs-ext')
- const file = join(scratch, 'seek.txt')
- writeFileSync(file, 'abcdef', { flag: 'wx', mode: 0o600 })
- const fd = openSync(file, 'r')
- try {
- assert.equal(fsExt.seekSync(fd, 2, fsExt.constants.SEEK_SET), 2)
- const bytes = Buffer.alloc(4)
- assert.equal(readSync(fd, bytes, 0, bytes.length, null), 4)
- assert.equal(bytes.toString(), 'cdef')
- } finally {
- closeSync(fd)
- }
- }
- /** Resolve one system function through Koffi's packaged native module. */
- function checkKoffi() {
- const koffi = requireRuntime('koffi')
- const library = koffi.load(process.platform === 'win32' ? 'kernel32.dll' : null)
- try {
- const getPid = process.platform === 'win32'
- ? library.func('uint32_t __stdcall GetCurrentProcessId(void)')
- : library.func('int getpid(void)')
- assert.equal(getPid(), process.pid)
- } finally {
- library.unload()
- }
- }
- /** Encode and decode a pixel through the packaged libvips binary. */
- async function checkSharp() {
- const sharp = requireRuntime('sharp')
- const pixel = Buffer.from([17, 103, 231])
- const png = await sharp(pixel, { raw: { width: 1, height: 1, channels: 3 } }).png().toBuffer()
- const decoded = await sharp(png).raw().toBuffer({ resolveWithObject: true })
- assert.equal(decoded.info.width, 1)
- assert.equal(decoded.info.height, 1)
- assert.equal(decoded.info.channels, 3)
- assert.deepEqual(decoded.data, pixel)
- }
- /** Exercise Domino parsing through the HTML converter and GFM plugin used by web_fetch. */
- function checkHtml() {
- const Turndown = requireRuntime('turndown')
- const { gfm } = requireRuntime('@joplin/turndown-plugin-gfm')
- const converter = new Turndown({ bulletListMarker: '-' })
- converter.use(gfm)
- const markdown = converter.turndown('<p>A & B ©</p><ul><li>first</li><li>second</li></ul>'
- + '<table><thead><tr><th>Name</th><th>Value</th></tr></thead><tbody><tr><td>x</td><td>7</td></tr></tbody></table>')
- assert.match(markdown, /A & B ©/u)
- assert.match(markdown, /-\s+first\n-\s+second/u)
- assert.match(markdown, /\| Name \| Value \|/u)
- assert.match(markdown, /\| x\s+\| 7\s+\|/u)
- }
- try {
- checkFsExt()
- checkKoffi()
- await checkSharp()
- checkHtml()
- await checkPty()
- } finally {
- // This private tree contains only fixture files; Windows may release handles after terminal exit.
- await rm(scratch, { recursive: true, force: true, maxRetries: 20, retryDelay: 50 })
- }
- // Natural event-loop drain includes node-pty's worker and console-list helper teardown.
- process.once('beforeExit', () => {
- console.log(JSON.stringify({ node: process.versions.node, platform: process.platform, arch: process.arch,
- fsExt: true, koffi: true, sharp: true, html: true, pty: true }))
- })
|