mirror of
https://github.com/anomalyco/opentui.git
synced 2026-09-19 01:26:03 +08:00
62a7c38289
--------- Co-authored-by: Filip Hejmowski <fhejmowski@simplito.com> Co-authored-by: neriousy <34747899+neriousy@users.noreply.github.com> Co-authored-by: kommander <335157+kommander@users.noreply.github.com> Co-authored-by: Tilda <tilda@localhost>
295 lines
13 KiB
TypeScript
295 lines
13 KiB
TypeScript
import { spawnSync } from "node:child_process"
|
|
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
|
import { tmpdir } from "node:os"
|
|
import { dirname, resolve } from "node:path"
|
|
import { fileURLToPath } from "node:url"
|
|
|
|
import { requireNode26 } from "../../../scripts/node26.mjs"
|
|
|
|
const scriptDir = dirname(fileURLToPath(import.meta.url))
|
|
const packageRoot = resolve(scriptDir, "..")
|
|
const workspaceRoot = resolve(packageRoot, "..", "..")
|
|
const outDir = resolve(packageRoot, ".node-test")
|
|
const treeSitterStyledTextDataPath = resolve(tmpdir(), "tree-sitter-styled-text-test")
|
|
const treeSitterCacheTestDataPath = resolve(tmpdir(), "tree-sitter-cache-test")
|
|
const treeSitterDefaultDataPath = resolve(tmpdir(), "tree-sitter-default-node-test")
|
|
const treeSitterMarkdownRenderableTestDataPath = resolve(tmpdir(), "tree-sitter-markdown-renderable-test-data")
|
|
const treeSitterLineNumberTestDataPath = resolve(tmpdir(), "tree-sitter-line-number-test-data")
|
|
const textBufferTestDataPath = resolve(tmpdir(), "text-buffer-node-test")
|
|
const runtimeAssetTestDataPath = resolve(tmpdir(), "opentui-runtime-asset-node-test")
|
|
const audioRecorderTestDataPath = resolve(tmpdir(), "opentui-audio-recorder-node-test")
|
|
const imageTestDataPath = resolve(tmpdir(), "opentui-image-node-test")
|
|
const bufferDumpPath = resolve(packageRoot, "buffer_dump")
|
|
const treeSitterClientTestDataPaths = [
|
|
"tree-sitter-shared-test-data",
|
|
"tree-sitter-injections-test-data",
|
|
"tree-sitter-conceal-test-data",
|
|
"tree-sitter-edge-case-test-data",
|
|
"tree-sitter-reactive-data-path-test",
|
|
"tree-sitter-diff-resize-test-data",
|
|
].map((name) => resolve(tmpdir(), name))
|
|
const treeSitterTestDataPaths = [
|
|
treeSitterStyledTextDataPath,
|
|
treeSitterCacheTestDataPath,
|
|
treeSitterDefaultDataPath,
|
|
treeSitterMarkdownRenderableTestDataPath,
|
|
treeSitterLineNumberTestDataPath,
|
|
textBufferTestDataPath,
|
|
runtimeAssetTestDataPath,
|
|
audioRecorderTestDataPath,
|
|
imageTestDataPath,
|
|
...treeSitterClientTestDataPaths,
|
|
]
|
|
const treeSitterAssetsDir = "src/lib/tree-sitter/assets"
|
|
const audioFixturesDir = "src/tests/fixtures/audio"
|
|
const imageFixturesDir = "src/tests/fixtures/images"
|
|
const iccFixturesDir = "../native/src/tests/fixtures"
|
|
const stagedNativeFixturesRoot = resolve(outDir, iccFixturesDir)
|
|
const stagedNativeRoot = resolve(packageRoot, "native")
|
|
let stagedNativeRootOwned = false
|
|
const nodeTestTimeoutMs = 30_000
|
|
const nodeProcessTimeoutMs = 10 * 60_000
|
|
const nodePath = requireNode26()
|
|
const emittedAllowlist = [
|
|
".node-test/src/platform/ffi.test.js",
|
|
".node-test/src/platform/runtime.test.js",
|
|
".node-test/src/platform/worker.node-test.js",
|
|
".node-test/src/lib/bunfs.test.js",
|
|
".node-test/src/lib/border.test.js",
|
|
".node-test/src/lib/clipboard.test.js",
|
|
".node-test/src/lib/clipboard-service.test.js",
|
|
".node-test/src/lib/host-clipboard.test.js",
|
|
".node-test/src/lib/extmarks.test.js",
|
|
".node-test/src/lib/detect-links.test.js",
|
|
".node-test/src/lib/extmarks-multiwidth.test.js",
|
|
".node-test/src/lib/KeyHandler.test.js",
|
|
".node-test/src/lib/keybinding.internal.test.js",
|
|
".node-test/src/lib/KeyHandler.integration.test.js",
|
|
".node-test/src/lib/parse.keypress-kitty.protocol.test.js",
|
|
".node-test/src/lib/parse.keypress-kitty.test.js",
|
|
".node-test/src/lib/parse.mouse.test.js",
|
|
".node-test/src/lib/RGBA.test.js",
|
|
".node-test/src/lib/tree-sitter/resolve-ft.test.js",
|
|
".node-test/src/tests/renderer.kitty-flags.test.js",
|
|
".node-test/src/tests/renderer.kitty-transport.test.js",
|
|
".node-test/src/buffer.test.js",
|
|
".node-test/src/tests/renderer.idle.test.js",
|
|
".node-test/src/tests/renderer.notifications.test.js",
|
|
".node-test/src/tests/renderer.selection.test.js",
|
|
".node-test/src/console.test.js",
|
|
".node-test/src/native-handle.test.js",
|
|
".node-test/src/renderables/Box.test.js",
|
|
".node-test/src/renderables/Code.test.js",
|
|
".node-test/src/renderables/Diff.regression.test.js",
|
|
".node-test/src/renderables/Diff.resize-wrap.test.js",
|
|
".node-test/src/renderables/Diff.test.js",
|
|
".node-test/src/renderables/EditBufferRenderable.test.js",
|
|
".node-test/src/renderables/EmbeddedTerminal.test.js",
|
|
".node-test/src/renderables/Input.test.js",
|
|
".node-test/src/renderables/Select.test.js",
|
|
".node-test/src/renderables/Slider.test.js",
|
|
".node-test/src/renderables/TabSelect.test.js",
|
|
".node-test/src/renderables/TimeToFirstDraw.test.js",
|
|
".node-test/src/renderables/__tests__/Code.test.js",
|
|
".node-test/src/renderables/__tests__/LineNumberRenderable.paint-window.test.js",
|
|
".node-test/src/renderables/__tests__/LineNumberRenderable.scrollbox-simple.test.js",
|
|
".node-test/src/renderables/__tests__/LineNumberRenderable.scrollbox.test.js",
|
|
".node-test/src/renderables/__tests__/LineNumberRenderable.test.js",
|
|
".node-test/src/renderables/__tests__/LineNumberRenderable.wrapping.test.js",
|
|
".node-test/src/renderables/__tests__/Markdown.code-colors.test.js",
|
|
".node-test/src/renderables/__tests__/Markdown.test.js",
|
|
".node-test/src/renderables/__tests__/MultiRenderable.selection.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.buffer.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.destroyed-events.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.editing.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.error-handling.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.events.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.highlights.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.keybinding.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.paste.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.rendering.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.scroll.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.selection.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.stress.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.undo-redo.test.js",
|
|
".node-test/src/renderables/__tests__/Textarea.visual-lines.test.js",
|
|
".node-test/src/renderables/Text.test.js",
|
|
".node-test/src/renderables/Text.selection-buffer.test.js",
|
|
".node-test/src/renderables/TextTable.test.js",
|
|
".node-test/src/animation/Timeline.test.js",
|
|
".node-test/src/edit-buffer.test.js",
|
|
".node-test/src/editor-view.test.js",
|
|
".node-test/src/lib/data-paths.test.js",
|
|
".node-test/src/lib/env.test.js",
|
|
".node-test/src/lib/KeyHandler.stopPropagation.test.js",
|
|
".node-test/src/lib/objects-in-viewport.test.js",
|
|
".node-test/src/lib/parse.keypress.test.js",
|
|
".node-test/src/lib/renderable.validations.test.js",
|
|
".node-test/src/lib/stdin-parser.test.js",
|
|
".node-test/src/lib/terminal-capability-detection.test.js",
|
|
".node-test/src/lib/terminal-palette.test.js",
|
|
".node-test/src/lib/tree-sitter/cache.test.js",
|
|
".node-test/src/lib/tree-sitter/client.test.js",
|
|
".node-test/src/lib/tree-sitter-styled-text.test.js",
|
|
".node-test/src/lib/styled-text.test.js",
|
|
".node-test/src/lib/yoga.options.test.js",
|
|
".node-test/src/renderables/__tests__/markdown-parser.test.js",
|
|
".node-test/src/renderables/TextNode.test.js",
|
|
".node-test/src/syntax-style.test.js",
|
|
".node-test/src/testing/capture-spans.test.js",
|
|
".node-test/src/testing/test-recorder.test.js",
|
|
".node-test/src/testing/integration.test.js",
|
|
".node-test/src/testing/mock-keys.test.js",
|
|
".node-test/src/testing/mock-mouse.test.js",
|
|
".node-test/src/tests/absolute-positioning.snapshot.test.js",
|
|
".node-test/src/tests/renderable.snapshot.test.js",
|
|
".node-test/src/tests/allocator-stats.test.js",
|
|
".node-test/src/tests/audio-stream.test.js",
|
|
".node-test/src/tests/audio-pcm-stream.test.js",
|
|
".node-test/src/tests/clipboard-native-lifecycle.test.js",
|
|
".node-test/src/tests/audio.test.js",
|
|
".node-test/src/tests/image-renderable.test.js",
|
|
".node-test/src/tests/image-pool.test.js",
|
|
".node-test/src/tests/image.test.js",
|
|
".node-test/src/tests/destroy-on-exit.test.js",
|
|
".node-test/src/tests/destroy-during-render.test.js",
|
|
".node-test/src/tests/ffi-borrowed-pointer-callsites.test.js",
|
|
".node-test/src/tests/hover-cursor.test.js",
|
|
".node-test/src/tests/native-backed-measurement-lifecycle.test.js",
|
|
".node-test/src/tests/native-backed-measurement-parity.test.js",
|
|
".node-test/src/tests/native-span-feed-async.test.js",
|
|
".node-test/src/tests/native-span-feed-close.test.js",
|
|
".node-test/src/tests/native-span-feed-coverage.test.js",
|
|
".node-test/src/tests/native-span-feed-edge-cases.test.js",
|
|
".node-test/src/tests/opacity.test.js",
|
|
".node-test/src/tests/native-span-feed-use-after-free.test.js",
|
|
".node-test/src/tests/renderable.test.js",
|
|
".node-test/src/tests/renderer.clock.test.js",
|
|
".node-test/src/tests/renderer.console-startup.test.js",
|
|
".node-test/src/tests/renderer.control.test.js",
|
|
".node-test/src/tests/renderer.core-slot-binding.test.js",
|
|
".node-test/src/tests/renderer.cursor.test.js",
|
|
".node-test/src/tests/renderer.destroy-during-render.test.js",
|
|
".node-test/src/tests/renderer.frame-admission.test.js",
|
|
".node-test/src/tests/renderer.focus.test.js",
|
|
".node-test/src/tests/renderer.focus-restore.test.js",
|
|
".node-test/src/tests/renderer.input.test.js",
|
|
".node-test/src/tests/renderer.mouse.test.js",
|
|
".node-test/src/tests/renderer.palette.test.js",
|
|
".node-test/src/tests/renderer.scrollback-surface.test.js",
|
|
".node-test/src/tests/renderer.slot-registry.test.js",
|
|
".node-test/src/tests/renderer.useMouse.test.js",
|
|
".node-test/src/tests/scrollbox.test.js",
|
|
".node-test/src/tests/scrollbox-culling-bug.test.js",
|
|
".node-test/src/tests/scrollbox-hitgrid-resize.test.js",
|
|
".node-test/src/tests/scrollbox-hitgrid.test.js",
|
|
".node-test/src/tests/yoga-callback-stress.test.js",
|
|
".node-test/src/tests/yoga-setters.test.js",
|
|
".node-test/src/tests/wrap-resize-perf.test.js",
|
|
".node-test/src/text-buffer.test.js",
|
|
".node-test/src/text-buffer-view.test.js",
|
|
]
|
|
|
|
let exitCode = 0
|
|
|
|
try {
|
|
rmSync(outDir, { recursive: true, force: true })
|
|
|
|
exitCode = run(process.execPath, ["x", "tsc", "-p", "tsconfig.node-test.json"])
|
|
|
|
// node --test silently ignores nonexistent file arguments, so a missing
|
|
// emitted test (e.g. not listed in tsconfig.node-test.json) would skip
|
|
// coverage without failing. Fail loudly instead.
|
|
if (exitCode === 0) {
|
|
writeFileSync(
|
|
resolve(outDir, "package.json"),
|
|
JSON.stringify({
|
|
type: "module",
|
|
imports: {
|
|
"#opentui/runtime-assets": "./src/platform/runtime-assets.node.js",
|
|
},
|
|
}),
|
|
)
|
|
const missing = emittedAllowlist.filter((path) => !existsSync(resolve(packageRoot, path)))
|
|
if (missing.length > 0) {
|
|
console.error(`Missing emitted node tests (add them to tsconfig.node-test.json?):\n${missing.join("\n")}`)
|
|
exitCode = 1
|
|
}
|
|
}
|
|
|
|
if (exitCode === 0) {
|
|
if (existsSync(stagedNativeRoot)) {
|
|
throw new Error(`Refusing to replace existing native fixture staging directory: ${stagedNativeRoot}`)
|
|
}
|
|
cpSync(resolve(packageRoot, treeSitterAssetsDir), resolve(outDir, treeSitterAssetsDir), { recursive: true })
|
|
cpSync(resolve(packageRoot, audioFixturesDir), resolve(outDir, audioFixturesDir), { recursive: true })
|
|
cpSync(resolve(packageRoot, imageFixturesDir), resolve(outDir, imageFixturesDir), { recursive: true })
|
|
stagedNativeRootOwned = true
|
|
cpSync(resolve(packageRoot, iccFixturesDir), stagedNativeFixturesRoot, { recursive: true })
|
|
for (const dataPath of treeSitterTestDataPaths) {
|
|
mkdirSync(dataPath, { recursive: true })
|
|
}
|
|
// Node resolves permission paths before tests can create their output directory.
|
|
mkdirSync(bufferDumpPath, { recursive: true })
|
|
|
|
exitCode = run(
|
|
nodePath,
|
|
[
|
|
"--disable-warning=SecurityWarning",
|
|
"--disable-warning=ExperimentalWarning",
|
|
"--permission",
|
|
`--allow-fs-read=${workspaceRoot}`,
|
|
...treeSitterTestDataPaths.map((path) => `--allow-fs-read=${path}`),
|
|
...treeSitterTestDataPaths.map((path) => `--allow-fs-write=${path}`),
|
|
`--allow-fs-write=${bufferDumpPath}`,
|
|
"--allow-net=127.0.0.1",
|
|
"--allow-child-process",
|
|
"--allow-worker",
|
|
"--allow-ffi",
|
|
"--experimental-ffi",
|
|
"--import",
|
|
"./scripts/test-node-hook.mjs",
|
|
"--test-concurrency=1",
|
|
`--test-timeout=${nodeTestTimeoutMs}`,
|
|
"--test",
|
|
...emittedAllowlist,
|
|
],
|
|
{
|
|
env: {
|
|
...process.env,
|
|
OTUI_TEXT_BUFFER_TEST_TMPDIR: textBufferTestDataPath,
|
|
OTUI_RUNTIME_ASSET_TEST_TMPDIR: runtimeAssetTestDataPath,
|
|
OTUI_AUDIO_RECORDER_TEST_TMPDIR: audioRecorderTestDataPath,
|
|
OTUI_IMAGE_TEST_TMPDIR: imageTestDataPath,
|
|
XDG_DATA_HOME: treeSitterDefaultDataPath,
|
|
},
|
|
timeout: nodeProcessTimeoutMs,
|
|
},
|
|
)
|
|
}
|
|
} finally {
|
|
rmSync(outDir, { recursive: true, force: true })
|
|
if (stagedNativeRootOwned) rmSync(stagedNativeRoot, { recursive: true, force: true })
|
|
}
|
|
|
|
process.exit(exitCode)
|
|
|
|
function run(command: string, args: string[], options: { env?: NodeJS.ProcessEnv; timeout?: number } = {}): number {
|
|
const result = spawnSync(command, args, {
|
|
cwd: packageRoot,
|
|
env: options.env ?? process.env,
|
|
stdio: "inherit",
|
|
timeout: options.timeout,
|
|
})
|
|
|
|
if (result.error) {
|
|
if (result.error.name === "TimeoutError") {
|
|
console.error(`Command timed out after ${options.timeout}ms: ${command} ${args.join(" ")}`)
|
|
}
|
|
|
|
throw result.error
|
|
}
|
|
|
|
return result.status ?? 1
|
|
}
|