test: rebuildable bundles, npm test entry point
- esbuild alias fixed in the session/sysinfo/sftp test commands (they could not build at all), .sftp-svc.mjs build documented, real connection-stability assertions - tests/build-bundles.cjs builds every bundle fresh; npm test runs the seven offline suites; esbuild pinned in devDependencies - remove the assertion-less .exact-inline.mjs; ignore/clean test temp dirs
This commit is contained in:
1 parent
24f7e7c52a
commit
cca4ba029a
12 files changed
+108
-92
No files matched your search
@@ -33,6 +33,9 @@ release/
|
|||||||
# stray local test artifacts
|
# stray local test artifacts
|
||||||
.min-test.bin
|
.min-test.bin
|
||||||
.min2-test.bin
|
.min2-test.bin
|
||||||
|
.exact-test.bin
|
||||||
|
tests/.zm-src-*/
|
||||||
|
tests/.zm-out-*/
|
||||||
|
|
||||||
# release notes drafts (consumed by scripts/release.cjs + sync-changelog.cjs)
|
# release notes drafts (consumed by scripts/release.cjs + sync-changelog.cjs)
|
||||||
RELEASE_NOTES*.md
|
RELEASE_NOTES*.md
|
||||||
|
|||||||
Generated
+3
-1
@@ -7,6 +7,7 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "open-terminal",
|
"name": "open-terminal",
|
||||||
"version": "1.0.13",
|
"version": "1.0.13",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lydell/node-pty": "^1.2.0-beta.15",
|
"@lydell/node-pty": "^1.2.0-beta.15",
|
||||||
"@xterm/addon-fit": "^0.11.0",
|
"@xterm/addon-fit": "^0.11.0",
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
"electron": "^44.2.0",
|
"electron": "^44.2.0",
|
||||||
"electron-builder": "^26.15.3",
|
"electron-builder": "^26.15.3",
|
||||||
"electron-vite": "^5.0.0",
|
"electron-vite": "^5.0.0",
|
||||||
|
"esbuild": "^0.25.12",
|
||||||
"typescript": "~5.9.0",
|
"typescript": "~5.9.0",
|
||||||
"vite": "^7.0.0"
|
"vite": "^7.0.0"
|
||||||
}
|
}
|
||||||
@@ -4291,7 +4293,7 @@
|
|||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.25.12",
|
"version": "0.25.12",
|
||||||
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
|
||||||
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
"integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"build": "electron-vite build",
|
"build": "electron-vite build",
|
||||||
"preview": "electron-vite preview",
|
"preview": "electron-vite preview",
|
||||||
"typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json",
|
"typecheck": "tsc --noEmit -p tsconfig.node.json && tsc --noEmit -p tsconfig.web.json",
|
||||||
|
"test": "node tests/build-bundles.cjs && node tests/ssh-loopback.mjs && node tests/commands-store.mjs && node tests/settings-store.mjs && node tests/.hl-split-smoke.cjs && node tests/zmodem-e2e.mjs && node tests/ssh-session-e2e.mjs && node tests/sysinfo-e2e.mjs",
|
||||||
"dist": "electron-vite build && electron-builder --win msi nsis",
|
"dist": "electron-vite build && electron-builder --win msi nsis",
|
||||||
"dist:dir": "electron-vite build && electron-builder --win --dir"
|
"dist:dir": "electron-vite build && electron-builder --win --dir"
|
||||||
},
|
},
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
"electron": "^44.2.0",
|
"electron": "^44.2.0",
|
||||||
"electron-builder": "^26.15.3",
|
"electron-builder": "^26.15.3",
|
||||||
"electron-vite": "^5.0.0",
|
"electron-vite": "^5.0.0",
|
||||||
|
"esbuild": "^0.25.12",
|
||||||
"typescript": "~5.9.0",
|
"typescript": "~5.9.0",
|
||||||
"vite": "^7.0.0"
|
"vite": "^7.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
// Decisive test: exact sftp.ts upload code, inline (no esbuild bundle).
|
|
||||||
import { createRequire } from 'module'
|
|
||||||
import { promises as fsp } from 'fs'
|
|
||||||
const require_ = createRequire(import.meta.url)
|
|
||||||
const ssh2 = require_('ssh2')
|
|
||||||
const sessionLayer = require_('D:/Coding/Terminal/tests/.session-e2e.cjs')
|
|
||||||
|
|
||||||
let clientProvider
|
|
||||||
function p(fn) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fn((err, res) => (err ? reject(err) : resolve(res)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionLayer.configureSessionRuntime({
|
|
||||||
broadcast: () => {},
|
|
||||||
getConnection: () => ({
|
|
||||||
id: 'jd', name: 't', host: process.env.JD_HOST, port: 22, username: process.env.JD_USER,
|
|
||||||
auth: 'password', askPasswordAtConnect: false, askPassphraseAtConnect: false,
|
|
||||||
keepaliveIntervalSec: 30, createdAt: Date.now(),
|
|
||||||
savedAuth: { hasPassword: true, hasKeyContent: false, hasPassphrase: false }
|
|
||||||
}),
|
|
||||||
getSecret: () => process.env.JD_PASS,
|
|
||||||
touch: () => {},
|
|
||||||
knownHosts: { check: () => ({ status: 'match' }), accept: () => {} },
|
|
||||||
promptHostKey: () => { throw new Error('x') }
|
|
||||||
})
|
|
||||||
|
|
||||||
const { id } = await sessionLayer.openSession({ kind: 'ssh', connectionId: 'jd' })
|
|
||||||
const client = sessionLayer.getSshClient(id)
|
|
||||||
|
|
||||||
// --- exact sftp.ts upload pattern ---
|
|
||||||
const transfer = { kind: 'upload', cancelled: false }
|
|
||||||
const emit = (e) => console.log('[evt]', e.state, e.file, e.bytes + '/' + e.totalBytes, e.error ?? '')
|
|
||||||
const local = './.exact-test.bin'
|
|
||||||
const content = Buffer.alloc(256 * 1024, 2)
|
|
||||||
await fsp.writeFile(local, content)
|
|
||||||
const remote = '/root/ot_exact.bin'
|
|
||||||
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
client.sftp((err, sftp) => (err != null ? reject(err) : resolve(sftp)))
|
|
||||||
})
|
|
||||||
const sftp = await new Promise((resolve, reject) => {
|
|
||||||
client.sftp((err, sftp_) => (err != null ? reject(err) : resolve(sftp_)))
|
|
||||||
})
|
|
||||||
console.log('[exact] sftp channel open, calling fastPut')
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
sftp.fastPut(local, remote, {
|
|
||||||
concurrency: 4,
|
|
||||||
step: (totalTransferred, _chunk, total) => {
|
|
||||||
console.log('[exact] step', totalTransferred, '/', total)
|
|
||||||
if (transfer.cancelled) {
|
|
||||||
reject(new Error('已取消'))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, (err) => (err ? reject(err) : resolve()))
|
|
||||||
})
|
|
||||||
const st = await new Promise((resolve, reject) => {
|
|
||||||
sftp.stat(remote, (err, s) => (err ? reject(err) : resolve(s)))
|
|
||||||
})
|
|
||||||
console.log('[exact] OK, remote size:', st.size, 'expected:', content.length)
|
|
||||||
await new Promise((resolve, reject) => sftp.unlink(remote, (e) => (e ? reject(e) : resolve())))
|
|
||||||
await fsp.rm(local, { force: true })
|
|
||||||
sessionLayer.killPty(id)
|
|
||||||
process.exit(0)
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/* Rebuild every esbuild bundle the tests load, so a test run can never exercise
|
||||||
|
a stale bundle. `npm test` calls this first; run it standalone when you only
|
||||||
|
want fresh bundles.
|
||||||
|
|
||||||
|
The aliases live here (and nowhere else) so every bundle resolves `electron`
|
||||||
|
and `@shared` the same way. A bundle built without
|
||||||
|
`--alias:@shared=./src/shared` does not even compile: pty.ts reaches
|
||||||
|
@shared/theme through settingsStore.ts -> windowChrome.ts.
|
||||||
|
|
||||||
|
Usage: node tests/build-bundles.cjs */
|
||||||
|
const path = require('node:path')
|
||||||
|
const esbuild = require('esbuild')
|
||||||
|
|
||||||
|
const ROOT = path.join(__dirname, '..')
|
||||||
|
|
||||||
|
const BUNDLES = [
|
||||||
|
// Real session layer: pty.ts also re-exports the ssh + sysinfo engines.
|
||||||
|
{ entry: 'src/main/pty.ts', out: 'tests/.session-e2e.cjs', external: ['@lydell/node-pty', 'ssh2'] },
|
||||||
|
// ESM (`.mjs`): tests/sftp-*.mjs load it with `await import()`.
|
||||||
|
{ entry: 'src/main/sftp.ts', out: 'tests/.sftp-svc.mjs', format: 'esm', external: ['ssh2'] },
|
||||||
|
{ entry: 'src/main/commands.ts', out: 'tests/.commands-store.cjs' },
|
||||||
|
{ entry: 'src/main/settingsStore.ts', out: 'tests/.settings-store.cjs' },
|
||||||
|
// zmodem.js stays bundled (NOT external) — the test drives a second in-process
|
||||||
|
// Sentry from the same library.
|
||||||
|
{ entry: 'src/main/zmodem.ts', out: 'tests/.zmodem-e2e.cjs', external: ['ssh2'] },
|
||||||
|
// The smoke test imports the renderer engine (.ts), so it needs bundling too.
|
||||||
|
{ entry: 'tests/hl-split-smoke.mjs', out: 'tests/.hl-split-smoke.cjs' }
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const { entry, out, format = 'cjs', external = [] } of BUNDLES) {
|
||||||
|
esbuild.buildSync({
|
||||||
|
absWorkingDir: ROOT,
|
||||||
|
entryPoints: [path.join(ROOT, entry)],
|
||||||
|
outfile: path.join(ROOT, out),
|
||||||
|
bundle: true,
|
||||||
|
platform: 'node',
|
||||||
|
format,
|
||||||
|
external,
|
||||||
|
alias: { electron: './tests/electron-stub.cjs', '@shared': './src/shared' },
|
||||||
|
logLevel: 'warning'
|
||||||
|
})
|
||||||
|
console.log(`built ${out}`)
|
||||||
|
}
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
* --alias:@shared=./src/shared
|
* --alias:@shared=./src/shared
|
||||||
* Run: node tests/commands-store.mjs (must exit 0)
|
* Run: node tests/commands-store.mjs (must exit 0)
|
||||||
*/
|
*/
|
||||||
import { mkdtempSync, readFileSync, writeFileSync } from 'fs'
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'fs'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { tmpdir } from 'os'
|
import { tmpdir } from 'os'
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
@@ -227,5 +227,9 @@ const expected =
|
|||||||
'partial-tail'
|
'partial-tail'
|
||||||
ok(readFileSync(startB.file, 'utf8') === expected, 'burst writes + stop tail land in order')
|
ok(readFileSync(startB.file, 'utf8') === expected, 'burst writes + stop tail land in order')
|
||||||
|
|
||||||
|
// The store wrote into a temp userData dir; drop it so repeated runs do not
|
||||||
|
// litter %TEMP%.
|
||||||
|
rmSync(userData, { recursive: true, force: true })
|
||||||
|
|
||||||
console.log('\n[commands] ALL CHECKS PASSED')
|
console.log('\n[commands] ALL CHECKS PASSED')
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
* --alias:@shared=./src/shared
|
* --alias:@shared=./src/shared
|
||||||
* node tests/settings-store.mjs
|
* node tests/settings-store.mjs
|
||||||
*/
|
*/
|
||||||
import { mkdtempSync, readFileSync, existsSync, writeFileSync } from 'node:fs'
|
import { mkdtempSync, readFileSync, existsSync, rmSync, writeFileSync } from 'node:fs'
|
||||||
import { tmpdir } from 'node:os'
|
import { tmpdir } from 'node:os'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { createRequire } from 'node:module'
|
import { createRequire } from 'node:module'
|
||||||
@@ -111,5 +111,9 @@ console.log('[warnings log]')
|
|||||||
ok(readFileSync(file, 'utf8').length === size, 'identical warnings are not appended twice')
|
ok(readFileSync(file, 'utf8').length === size, 'identical warnings are not appended twice')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The store wrote settings into a temp userData dir; drop it so repeated runs do
|
||||||
|
// not litter %TEMP%.
|
||||||
|
rmSync(userData, { recursive: true, force: true })
|
||||||
|
|
||||||
console.log(failed === 0 ? '\n[settings] ALL CHECKS PASSED' : `\n[settings] ${failed} CHECK(S) FAILED`)
|
console.log(failed === 0 ? '\n[settings] ALL CHECKS PASSED' : `\n[settings] ${failed} CHECK(S) FAILED`)
|
||||||
process.exit(failed === 0 ? 0 : 1)
|
process.exit(failed === 0 ? 0 : 1)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// Real-server chmod/chown + mode-string verification (M4). Credentials from env:
|
// Real-server chmod/chown + mode-string verification (M4). Credentials from env:
|
||||||
// JD_HOST / JD_USER / JD_PASS
|
// JD_HOST / JD_USER / JD_PASS
|
||||||
// Prereq bundles: tests/.session-e2e.cjs (pty session layer), tests/.sftp-svc.mjs (sftp service)
|
// Prereq bundles: `node tests/build-bundles.cjs` builds both of them —
|
||||||
|
// tests/.session-e2e.cjs (pty session layer), tests/.sftp-svc.mjs (sftp service)
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
const require_ = createRequire(import.meta.url)
|
const require_ = createRequire(import.meta.url)
|
||||||
const sessionLayer = require_('./.session-e2e.cjs')
|
const sessionLayer = require_('./.session-e2e.cjs')
|
||||||
@@ -71,7 +72,8 @@ try {
|
|||||||
console.log('[chmod] cleanup ok')
|
console.log('[chmod] cleanup ok')
|
||||||
|
|
||||||
const exits = events.filter((e) => e.channel === 'pty:exit')
|
const exits = events.filter((e) => e.channel === 'pty:exit')
|
||||||
console.log('[chmod] unexpected exits:', exits.length)
|
if (exits.length) fail(`session exited unexpectedly during the chmod test (${exits.length}x)`)
|
||||||
|
console.log('[chmod] no unexpected session exits')
|
||||||
|
|
||||||
sessionLayer.killPty(id)
|
sessionLayer.killPty(id)
|
||||||
console.log('[chmod] ALL CHECKS PASSED')
|
console.log('[chmod] ALL CHECKS PASSED')
|
||||||
|
|||||||
+4
-2
@@ -1,6 +1,7 @@
|
|||||||
// Real-server SFTP verification (M4). Credentials from env (never hardcoded):
|
// Real-server SFTP verification (M4). Credentials from env (never hardcoded):
|
||||||
// JD_HOST / JD_USER / JD_PASS
|
// JD_HOST / JD_USER / JD_PASS
|
||||||
// Prereq bundles: tests/.session-e2e.cjs (pty session layer), tests/.sftp-svc.cjs (sftp service)
|
// Prereq bundles: `node tests/build-bundles.cjs` builds both of them —
|
||||||
|
// tests/.session-e2e.cjs (pty session layer), tests/.sftp-svc.mjs (sftp service)
|
||||||
import { createRequire } from 'module'
|
import { createRequire } from 'module'
|
||||||
import { randomUUID } from 'crypto'
|
import { randomUUID } from 'crypto'
|
||||||
import { promises as fsp } from 'fs'
|
import { promises as fsp } from 'fs'
|
||||||
@@ -100,7 +101,8 @@ try {
|
|||||||
console.log('[sftp] recursive delete ok')
|
console.log('[sftp] recursive delete ok')
|
||||||
|
|
||||||
const exits = events.filter((e) => e.channel === 'pty:exit')
|
const exits = events.filter((e) => e.channel === 'pty:exit')
|
||||||
console.log('[sftp] unexpected exits:', exits.length)
|
if (exits.length) fail(`session exited unexpectedly during the transfer (${exits.length}x) — see the debug log above`)
|
||||||
|
console.log('[sftp] no unexpected session exits')
|
||||||
|
|
||||||
await fsp.rm(localFile, { force: true })
|
await fsp.rm(localFile, { force: true })
|
||||||
await fsp.rm(dlDir, { recursive: true, force: true })
|
await fsp.rm(dlDir, { recursive: true, force: true })
|
||||||
|
|||||||
@@ -4,9 +4,13 @@
|
|||||||
* in-process ssh2.Server — the same openSession/write/resize/kill pipeline the
|
* in-process ssh2.Server — the same openSession/write/resize/kill pipeline the
|
||||||
* app uses, minus the IPC + renderer shells.
|
* app uses, minus the IPC + renderer shells.
|
||||||
*
|
*
|
||||||
* Pre-req: npx esbuild src/main/pty.ts --bundle --platform=node --format=cjs
|
* Pre-req: node tests/build-bundles.cjs (or, by hand:)
|
||||||
* --outfile=tests/.session-e2e.cjs --external:@lydell/node-pty
|
* npx esbuild src/main/pty.ts --bundle --platform=node --format=cjs
|
||||||
* --external:ssh2 --alias:electron=./tests/electron-stub.cjs
|
* --outfile=tests/.session-e2e.cjs --external:@lydell/node-pty
|
||||||
|
* --external:ssh2 --alias:electron=./tests/electron-stub.cjs
|
||||||
|
* --alias:@shared=./src/shared
|
||||||
|
* (--alias:@shared=./src/shared is required: pty.ts reaches @shared/theme
|
||||||
|
* through settingsStore.ts -> windowChrome.ts)
|
||||||
* Run: node tests/ssh-session-e2e.mjs (must exit 0)
|
* Run: node tests/ssh-session-e2e.mjs (must exit 0)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,13 @@
|
|||||||
* broadcast, growing samples (usage + net rates), and that stopPolling halts
|
* broadcast, growing samples (usage + net rates), and that stopPolling halts
|
||||||
* the stream.
|
* the stream.
|
||||||
*
|
*
|
||||||
* Pre-req: npx esbuild src/main/pty.ts --bundle --platform=node --format=cjs
|
* Pre-req: node tests/build-bundles.cjs (or, by hand:)
|
||||||
* --outfile=tests/.session-e2e.cjs --external:@lydell/node-pty
|
* npx esbuild src/main/pty.ts --bundle --platform=node --format=cjs
|
||||||
* --external:ssh2 --alias:electron=./tests/electron-stub.cjs
|
* --outfile=tests/.session-e2e.cjs --external:@lydell/node-pty
|
||||||
|
* --external:ssh2 --alias:electron=./tests/electron-stub.cjs
|
||||||
|
* --alias:@shared=./src/shared
|
||||||
|
* (--alias:@shared=./src/shared is required: pty.ts reaches @shared/theme
|
||||||
|
* through settingsStore.ts -> windowChrome.ts)
|
||||||
* Run: node tests/sysinfo-e2e.mjs (must exit 0)
|
* Run: node tests/sysinfo-e2e.mjs (must exit 0)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|||||||
+25
-13
@@ -38,6 +38,20 @@ const fail = (msg) => {
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Temp dirs the scenarios create under tests/. They are removed on the way out,
|
||||||
|
// including when fail() exits the process mid-scenario — hence the exit hook
|
||||||
|
// next to the try/finally at the bottom.
|
||||||
|
const tempDirs = []
|
||||||
|
const mkTemp = (prefix) => {
|
||||||
|
const dir = mkdtempSync(join(__dirname, prefix))
|
||||||
|
tempDirs.push(dir)
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
const cleanupTempDirs = () => {
|
||||||
|
for (const dir of tempDirs.splice(0)) rmSync(dir, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
process.on('exit', cleanupTempDirs)
|
||||||
|
|
||||||
// ---- 0. ensure bundle ---------------------------------------------------------
|
// ---- 0. ensure bundle ---------------------------------------------------------
|
||||||
const bundlePath = join(__dirname, '.zmodem-e2e.cjs')
|
const bundlePath = join(__dirname, '.zmodem-e2e.cjs')
|
||||||
if (!existsSync(bundlePath)) {
|
if (!existsSync(bundlePath)) {
|
||||||
@@ -117,11 +131,11 @@ function makeRemoteSentry(remoteOnDetect, routeOut) {
|
|||||||
async function scenarioDownload() {
|
async function scenarioDownload() {
|
||||||
console.log('\n=== 场景 1: 远端 sz + 本地接收 (download) ===')
|
console.log('\n=== 场景 1: 远端 sz + 本地接收 (download) ===')
|
||||||
const sessionId = `sess-${randomBytes(3).toString('hex')}`
|
const sessionId = `sess-${randomBytes(3).toString('hex')}`
|
||||||
const srcDir = mkdtempSync(join(__dirname, '.zm-src-dl-'))
|
const srcDir = mkTemp('.zm-src-dl-')
|
||||||
const srcFile = join(srcDir, 'transferred.bin')
|
const srcFile = join(srcDir, 'transferred.bin')
|
||||||
const payload = randomBytes(200_000)
|
const payload = randomBytes(200_000)
|
||||||
writeFileSync(srcFile, payload)
|
writeFileSync(srcFile, payload)
|
||||||
const outDir = mkdtempSync(join(__dirname, '.zm-out-dl-'))
|
const outDir = mkTemp('.zm-out-dl-')
|
||||||
|
|
||||||
const events = []
|
const events = []
|
||||||
const remote = makeRemoteSentry((detection) => {
|
const remote = makeRemoteSentry((detection) => {
|
||||||
@@ -170,20 +184,17 @@ async function scenarioDownload() {
|
|||||||
}
|
}
|
||||||
console.log(`[download] file received: ${got.length} bytes, content matches`)
|
console.log(`[download] file received: ${got.length} bytes, content matches`)
|
||||||
console.log(`[download] progress events: ${prog.length} (state->done), done(ok)=true`)
|
console.log(`[download] progress events: ${prog.length} (state->done), done(ok)=true`)
|
||||||
|
|
||||||
rmSync(srcDir, { recursive: true, force: true })
|
|
||||||
rmSync(outDir, { recursive: true, force: true })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- SCENARIO 2: upload (our engine -> remote `rz`) ----------------------------
|
// ---- SCENARIO 2: upload (our engine -> remote `rz`) ----------------------------
|
||||||
async function scenarioUpload() {
|
async function scenarioUpload() {
|
||||||
console.log('\n=== 场景 2: 本地上传 + 远端 rz (upload) ===')
|
console.log('\n=== 场景 2: 本地上传 + 远端 rz (upload) ===')
|
||||||
const sessionId = `sess-${randomBytes(3).toString('hex')}`
|
const sessionId = `sess-${randomBytes(3).toString('hex')}`
|
||||||
const srcDir = mkdtempSync(join(__dirname, '.zm-src-up-'))
|
const srcDir = mkTemp('.zm-src-up-')
|
||||||
const srcFile = join(srcDir, 'sendme.bin')
|
const srcFile = join(srcDir, 'sendme.bin')
|
||||||
const payload = randomBytes(150_000)
|
const payload = randomBytes(150_000)
|
||||||
writeFileSync(srcFile, payload)
|
writeFileSync(srcFile, payload)
|
||||||
const outDir = mkdtempSync(join(__dirname, '.zm-out-up-'))
|
const outDir = mkTemp('.zm-out-up-')
|
||||||
|
|
||||||
const events = []
|
const events = []
|
||||||
let remoteGot = null
|
let remoteGot = null
|
||||||
@@ -243,15 +254,16 @@ async function scenarioUpload() {
|
|||||||
}
|
}
|
||||||
console.log(`[upload] remote received ${remoteGot.length} bytes, content matches`)
|
console.log(`[upload] remote received ${remoteGot.length} bytes, content matches`)
|
||||||
console.log(`[upload] progress events: ${prog.length}, done(ok)=true`)
|
console.log(`[upload] progress events: ${prog.length}, done(ok)=true`)
|
||||||
|
|
||||||
rmSync(srcDir, { recursive: true, force: true })
|
|
||||||
rmSync(outDir, { recursive: true, force: true })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- run ----------------------------------------------------------------------
|
// ---- run ----------------------------------------------------------------------
|
||||||
const timer = setTimeout(() => fail('zmodem-e2e timed out'), 30000)
|
const timer = setTimeout(() => fail('zmodem-e2e timed out'), 30000)
|
||||||
await scenarioDownload()
|
try {
|
||||||
await scenarioUpload()
|
await scenarioDownload()
|
||||||
clearTimeout(timer)
|
await scenarioUpload()
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timer)
|
||||||
|
cleanupTempDirs()
|
||||||
|
}
|
||||||
console.log('\n[zmodem-e2e] ALL CHECKS PASSED')
|
console.log('\n[zmodem-e2e] ALL CHECKS PASSED')
|
||||||
process.exit(0)
|
process.exit(0)
|
||||||
Reference in new issue
Block a user