quickwin 1.2.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +3 -3
- package/README.md +21 -24
- package/examples/PdfCanvas.js +8 -37
- package/examples/PdfCanvas.tsx +11 -41
- package/examples/exec_server.js +20054 -1423
- package/examples/exec_server.tsx +489 -0
- package/examples/exec_server_worker.js +383 -33
- package/examples/exec_server_worker.ts +408 -30
- package/examples/pdf_preview2.js +31 -73
- package/examples/pdf_preview2.ts +33 -76
- package/examples/pdf_viewer.js +1496 -616
- package/examples/pdf_viewer.tsx +23 -64
- package/examples/setres.js +22 -47
- package/examples/setres.ts +23 -57
- package/examples/test_gallery.js +1491 -525
- package/examples/test_react_lazy.js +1455 -500
- package/examples/test_react_listbox.js +1454 -499
- package/examples/test_root_window.js +1450 -495
- package/examples/test_tab.js +1455 -500
- package/lib/abort.js +70 -0
- package/lib/abort.ts +69 -0
- package/lib/certs.js +214 -0
- package/lib/certs.ts +224 -0
- package/lib/exec-log.js +15 -0
- package/lib/exec-log.ts +30 -0
- package/lib/fetch.js +37 -4
- package/lib/fetch.ts +35 -4
- package/lib/ffi/bind.js +352 -0
- package/lib/ffi/bind.ts +442 -0
- package/lib/ffi/ctype.js +168 -0
- package/lib/ffi/ctype.ts +331 -0
- package/lib/ffi/index.js +6 -0
- package/lib/ffi/index.ts +6 -0
- package/lib/ffi/struct.js +412 -0
- package/lib/ffi/struct.ts +664 -0
- package/lib/http-common.js +43 -5
- package/lib/http-common.ts +48 -6
- package/lib/http-server.js +187 -29
- package/lib/http-server.ts +177 -26
- package/lib/react-qw/components/DateTimePicker.js +20 -33
- package/lib/react-qw/components/Input.js +3 -10
- package/lib/react-qw/components/Link.js +7 -22
- package/lib/react-qw/components/ListView.js +80 -141
- package/lib/react-qw/components/PathPicker.js +36 -80
- package/lib/react-qw/components/ScrollView.js +15 -8
- package/lib/react-qw/components/Slider.js +2 -1
- package/lib/react-qw/components/Tab.js +15 -20
- package/lib/react-qw/components/Tooltip.js +21 -20
- package/lib/react-qw/components/TreeView.js +14 -47
- package/lib/react-qw/index.js +4 -2
- package/lib/react-qw/index.ts +4 -2
- package/lib/react-qw/nmhdr.js +5 -0
- package/lib/react-qw/nmhdr.ts +7 -0
- package/lib/react-qw/props.js +7 -2
- package/lib/react-qw/props.ts +7 -3
- package/lib/react-qw/reconciler.js +23 -13
- package/lib/react-qw/reconciler.ts +22 -13
- package/lib/text-measure.js +18 -35
- package/lib/text-measure.ts +20 -37
- package/lib/websocket.js +12 -2
- package/lib/websocket.ts +15 -2
- package/lib/windows/comctl32.js +18 -0
- package/lib/windows/comctl32.ts +20 -0
- package/lib/windows/comdlg32.js +7 -0
- package/lib/windows/comdlg32.ts +9 -0
- package/lib/windows/gdi32.js +90 -0
- package/lib/windows/gdi32.ts +97 -0
- package/lib/windows/kernel32.js +66 -0
- package/lib/windows/kernel32.ts +76 -0
- package/lib/windows/ntdll.js +8 -0
- package/lib/windows/ntdll.ts +10 -0
- package/lib/windows/ole32.js +7 -0
- package/lib/windows/ole32.ts +9 -0
- package/lib/windows/shell32.js +11 -0
- package/lib/windows/shell32.ts +14 -0
- package/lib/windows/structs.js +384 -0
- package/lib/windows/structs.ts +422 -0
- package/lib/windows/user32.js +469 -0
- package/lib/windows/user32.ts +502 -0
- package/package.json +1 -1
- package/quickwin.d.ts +22 -33
- package/quickwin_const.d.ts +1 -0
- package/qwin-nowasm-x86.exe +0 -0
- package/qwin-nowasm.exe +0 -0
- package/qwin-x86.exe +0 -0
- package/qwin.exe +0 -0
- package/test/bench_ffi_callraw.js +236 -0
- package/test/bench_ffi_callraw.ts +236 -0
- package/test/bench_ffi_overhead.js +139 -0
- package/test/bench_ffi_overhead.ts +141 -0
- package/test/bench_sendmessage.js +104 -0
- package/test/bench_sendmessage.ts +100 -0
- package/test/bench_sendmessage_nonnull.js +187 -0
- package/test/bench_sendmessage_nonnull.ts +188 -0
- package/test/run.js +14 -4
- package/test/run.ts +15 -5
- package/test/test_basic_fetch.js +8 -0
- package/test/test_basic_fetch.ts +9 -0
- package/test/test_ffi.js +33 -47
- package/test/test_ffi.ts +41 -60
- package/test/test_ffi_abi.js +235 -0
- package/test/test_ffi_abi.ts +218 -0
- package/test/test_ffi_bind.js +572 -0
- package/test/test_ffi_bind.ts +597 -0
- package/test/test_ffi_struct.js +745 -0
- package/test/test_ffi_struct.ts +798 -0
- package/test/test_ffi_struct_layout.js +495 -0
- package/test/test_ffi_struct_layout.ts +543 -0
- package/test/test_ffi_user32.js +103 -0
- package/test/test_ffi_user32.ts +113 -0
- package/test/test_ffi_windows.js +101 -0
- package/test/test_ffi_windows.ts +113 -0
- package/test/test_helper.js +8 -1
- package/test/test_helper.ts +9 -1
- package/test/test_http_import.js +18 -0
- package/test/test_http_import.ts +19 -0
- package/test/test_http_server.js +73 -1
- package/test/test_http_server.ts +75 -1
- package/test/test_ipv6.js +2 -2
- package/test/test_ipv6.ts +2 -2
- package/test/test_net_fetch.js +70 -8
- package/test/test_net_fetch.ts +69 -7
- package/test/test_net_websocket.js +1 -1
- package/test/test_net_websocket.ts +1 -1
- package/test/test_react_complex.js +1465 -510
- package/test/test_react_counter.js +1455 -500
- package/test/test_react_flex.js +1475 -513
- package/test/test_react_hidden.js +1458 -503
- package/test/test_react_listview_link.js +1455 -500
- package/test/test_react_notify.js +18534 -0
- package/test/test_react_render.js +1455 -500
- package/test/test_wolfssl_read.js +3 -2
- package/test/test_wolfssl_read.ts +3 -1
- package/test/test_worker_net.js +35 -17
- package/test/test_worker_net.ts +27 -15
- package/tsconfig.json +1 -1
- package/examples/exec_server.ts +0 -80
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// 一次性性能 bench:FFI 签名 '!' 标注的调用时 0 检验成本(同函数多臂对照 + A/A 噪声底)
|
|
2
|
+
// 不入 CI。用法:make js 后 POST /exec {"cmd":"qwin.exe test/bench_sendmessage_nonnull.js"}
|
|
3
|
+
// 臂(同一 proc 各自 bind,唯一变量是 '!'):
|
|
4
|
+
// A1 base '<HWND>ptr u32 <>ptr <>ptr -> <>ptr' 基线
|
|
5
|
+
// A2 ctrl '<HWND>ptr u32 <>ptr <>ptr -> <>ptr' 与 A1 完全同签名——两臂差值 = 机器噪声底
|
|
6
|
+
// B arg! '<HWND>ptr! u32 <>ptr <>ptr -> <>ptr' + 参数位 arg#1 0 检查
|
|
7
|
+
// C ret! '<HWND>ptr! u32 <>ptr <>ptr -> <>ptr!' + 返回位 0 检查(仅非零返回场景)
|
|
8
|
+
// 方法学(前两轮教训):批量缩到 50k/样本 → 臂序每 ~650ms 轮转一次,抵消 QEMU 调度抖动;
|
|
9
|
+
// S1/S3 各臂 30 样本取 median(对 GC/调度离群稳健);A2-A1 是效应检验的参照带——
|
|
10
|
+
// B/A 落在 A2/A 带内即「低于噪声底,不可测」。'!' 边际 = 每标注位 1 次整数 === 比较
|
|
11
|
+
// (endsWith 无条件跑、所有臂共模),理论 ≪ 轮噪声。
|
|
12
|
+
// C 臂标了返回 '!',撞真实 0 即 throw —— 先用 A1 探各场景原始返回值,非零才让 C 进。
|
|
13
|
+
// 全部消息发自有窗口(直调 wndproc)。结束必须 DestroyWindow + std.exit。
|
|
14
|
+
import * as std from 'std'
|
|
15
|
+
import * as gui from 'gui'
|
|
16
|
+
import { bind, WCHAR } from '../lib/ffi/bind.js'
|
|
17
|
+
import { PtrArrayBuffer } from '../lib/ffi/ctype.js'
|
|
18
|
+
|
|
19
|
+
const BATCH = 50_000 // 每样本批量(~650ms)——小批量快轮转,去相关调度抖动
|
|
20
|
+
const ROUNDS = 30 // S1/S3 每臂样本数(30 × 3-4 臂 ≈ 60-80s/suite)
|
|
21
|
+
const N_S2 = 50_000
|
|
22
|
+
const ROUNDS_S2 = 7
|
|
23
|
+
const WM_NULL = 0x0000
|
|
24
|
+
const WM_SETTEXT = 0x000c
|
|
25
|
+
const WM_GETTEXT = 0x000d
|
|
26
|
+
|
|
27
|
+
// median(离群稳健)供 micro 与 suite 共用
|
|
28
|
+
const med = (t: number[]): number => {
|
|
29
|
+
const s = [...t].sort((x, y) => x - y)
|
|
30
|
+
const m = s.length >> 1
|
|
31
|
+
return s.length % 2 ? s[m]! : (s[m - 1]! + s[m]!) / 2
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// S0:'!' 边际操作本身(1 次整数 === 比较 + 分支)的解释器成本——FFI 层噪声
|
|
35
|
+
// ±400~900ns/call 盖住了 12.5μs 调用上的 ns 级效应,这里绕开 ffiCall 直接量单检查。
|
|
36
|
+
// flag 必须是模块级可变量:QuickJS 编译期会常量折叠字面量,折叠掉就测不到了。
|
|
37
|
+
let microFlag = true
|
|
38
|
+
const MICRO_N = 100_000_000
|
|
39
|
+
function microLoop(): number {
|
|
40
|
+
const t0 = Date.now()
|
|
41
|
+
let hits = 0
|
|
42
|
+
for (let i = 0; i < MICRO_N; i++)
|
|
43
|
+
if (microFlag && i === 0) hits++
|
|
44
|
+
const dt = Date.now() - t0
|
|
45
|
+
if (hits !== (microFlag ? 1 : 0)) throw new Error('micro sanity failed: hits=' + hits)
|
|
46
|
+
return dt
|
|
47
|
+
}
|
|
48
|
+
function microSuite(): void {
|
|
49
|
+
step('suite: S0 micro 0-check interpreter cost (n=' + String(MICRO_N) + ')')
|
|
50
|
+
microFlag = true; microLoop()
|
|
51
|
+
microFlag = false; microLoop()
|
|
52
|
+
const ts: number[] = []
|
|
53
|
+
const fs: number[] = []
|
|
54
|
+
for (let r = 0; r < 5; r++) {
|
|
55
|
+
// 奇偶交叉起跑顺序(同原 bench 方法)
|
|
56
|
+
if (r % 2 === 0) {
|
|
57
|
+
microFlag = true; ts.push(microLoop())
|
|
58
|
+
microFlag = false; fs.push(microLoop())
|
|
59
|
+
} else {
|
|
60
|
+
microFlag = false; fs.push(microLoop())
|
|
61
|
+
microFlag = true; ts.push(microLoop())
|
|
62
|
+
}
|
|
63
|
+
step(' round ' + String(r) + ': check=' + String(ts[ts.length - 1]) +
|
|
64
|
+
'ms base=' + String(fs[fs.length - 1]) + 'ms')
|
|
65
|
+
}
|
|
66
|
+
const ns = (ms: number) => (ms * 1e6 / MICRO_N).toFixed(2)
|
|
67
|
+
const tmed = med(ts), fmed = med(fs)
|
|
68
|
+
step(' ** median: check=' + ns(tmed) + 'ns/op base=' + ns(fmed) +
|
|
69
|
+
'ns/op ==> per-check cost=' + ((tmed - fmed) * 1e6 / MICRO_N).toFixed(2) + 'ns')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 文件日志(cwd=Z:\quickwin,宿主机 _build/bench_nonnull_log.txt 可实时读)——exec 只在
|
|
73
|
+
// 进程退出后回包,挂了靠它定位卡点
|
|
74
|
+
const log = std.open('bench_nonnull_log.txt', 'w')
|
|
75
|
+
function step(msg: string): void {
|
|
76
|
+
std.printf('%s\n', msg)
|
|
77
|
+
if (log) { log.puts(msg + '\n'); log.flush() }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
step('stage: imports ok')
|
|
81
|
+
|
|
82
|
+
const winRaw = gui.CreateWindow('STATIC', 'bench-title', 0, 0, 0, 20, 20, null, null)
|
|
83
|
+
if (!winRaw) throw new Error('CreateWindow(STATIC) failed')
|
|
84
|
+
const win: gui.HWND = winRaw
|
|
85
|
+
step('stage: window created')
|
|
86
|
+
|
|
87
|
+
const SIG = '<HWND>ptr u32 <>ptr <>ptr -> <>ptr'
|
|
88
|
+
const fA1 = bind('user32.dll', 'SendMessageW', SIG)
|
|
89
|
+
const fA2 = bind('user32.dll', 'SendMessageW', SIG) // A/A 对照:同签名二绑 = 噪声底
|
|
90
|
+
const fB = bind('user32.dll', 'SendMessageW', '<HWND>ptr! u32 <>ptr <>ptr -> <>ptr')
|
|
91
|
+
const fC = bind('user32.dll', 'SendMessageW', '<HWND>ptr! u32 <>ptr <>ptr -> <>ptr!')
|
|
92
|
+
|
|
93
|
+
// 各 BindFn 结构等价 → 收敛到一个可传参的函数型(循环内统一走局部参数,开销对齐)
|
|
94
|
+
type SM = (h: gui.HWND, msg: number, w: number, l: number) => number
|
|
95
|
+
|
|
96
|
+
const TEXT = 'bench'
|
|
97
|
+
const buf = new PtrArrayBuffer<'WCHAR'>(64)
|
|
98
|
+
|
|
99
|
+
// C 臂可行性探针:A1 读各场景原始返回值,撞 0 的场景不进 C('!' 会 throw)。
|
|
100
|
+
// S1 的 WM_NULL 走 DefWindowProc 预期返 0 → C 不进 S1 是预期而非缺测。
|
|
101
|
+
const retS1 = fA1(win, WM_NULL, 0, 0x1234)
|
|
102
|
+
const retS2 = fA1(win, WM_SETTEXT, 0, WCHAR.encode(TEXT).ptr)
|
|
103
|
+
const retS3 = fA1(win, WM_GETTEXT, 64, buf.ptr)
|
|
104
|
+
step('probe ret: S1=' + String(retS1) + ' S2=' + String(retS2) + ' S3=' + String(retS3) +
|
|
105
|
+
' (C arm only enters nonzero scenes)')
|
|
106
|
+
|
|
107
|
+
function run1(f: SM): number {
|
|
108
|
+
const t0 = Date.now()
|
|
109
|
+
for (let i = 0; i < BATCH; i++) f(win, WM_NULL, 0, 0x1234)
|
|
110
|
+
return Date.now() - t0
|
|
111
|
+
}
|
|
112
|
+
function run2(f: SM): number {
|
|
113
|
+
const t0 = Date.now()
|
|
114
|
+
for (let i = 0; i < N_S2; i++) {
|
|
115
|
+
const w = WCHAR.encode(TEXT)
|
|
116
|
+
f(win, WM_SETTEXT, 0, w.ptr)
|
|
117
|
+
}
|
|
118
|
+
return Date.now() - t0
|
|
119
|
+
}
|
|
120
|
+
function run3(f: SM): number {
|
|
121
|
+
const t0 = Date.now()
|
|
122
|
+
for (let i = 0; i < BATCH; i++) f(win, WM_GETTEXT, 64, buf.ptr)
|
|
123
|
+
return Date.now() - t0
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
type Arm = { label: string; fn: () => number }
|
|
127
|
+
|
|
128
|
+
function suite(name: string, n: number, rounds: number, arms: Arm[]): void {
|
|
129
|
+
step('suite: ' + name + ' warmup')
|
|
130
|
+
for (const a of arms) a.fn() // 预热各 1 轮
|
|
131
|
+
const times: number[][] = arms.map(() => [])
|
|
132
|
+
for (let r = 0; r < rounds; r++) {
|
|
133
|
+
// 臂序按 r 轮转起跑,抵消顺序性偏置(温度/调度漂移)
|
|
134
|
+
for (let k = 0; k < arms.length; k++) {
|
|
135
|
+
const i = (r + k) % arms.length
|
|
136
|
+
times[i]!.push(arms[i]!.fn())
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// 每轮原始数据只 dump 前 3 轮(30 轮全打没意义,log 只留诊断线索)
|
|
140
|
+
for (let r = 0; r < Math.min(rounds, 3); r++) {
|
|
141
|
+
step(' round ' + String(r) + ': ' +
|
|
142
|
+
arms.map((a, i) => a.label + '=' + String(times[i]![r]) + 'ms').join(' '))
|
|
143
|
+
}
|
|
144
|
+
const ns = (ms: number) => (ms * 1e6 / n).toFixed(1)
|
|
145
|
+
// median 是主指标(离群稳健);min 留作与前两轮/原 bench 的可比项
|
|
146
|
+
const meds = times.map(med)
|
|
147
|
+
const mins = times.map(t => Math.min(...t))
|
|
148
|
+
step(' ** median: ' + arms.map((a, i) =>
|
|
149
|
+
a.label + '=' + ns(meds[i]!) + 'ns').join(' | '))
|
|
150
|
+
step(' min: ' + arms.map((a, i) =>
|
|
151
|
+
a.label + '=' + ns(mins[i]!) + 'ns').join(' | '))
|
|
152
|
+
for (let i = 1; i < arms.length; i++)
|
|
153
|
+
step(' vs A1: ' + arms[i]!.label + ' delta=' +
|
|
154
|
+
((meds[i]! - meds[0]!) * 1e6 / n).toFixed(1) + 'ns/call (median), ' +
|
|
155
|
+
((mins[i]! - mins[0]!) * 1e6 / n).toFixed(1) + 'ns/call (min)' +
|
|
156
|
+
(i === 1 ? ' <-- A2-A1 = NOISE FLOOR' : ''))
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const s1Arms: Arm[] = [
|
|
160
|
+
{ label: 'A1', fn: () => run1(fA1) },
|
|
161
|
+
{ label: 'A2', fn: () => run1(fA2) },
|
|
162
|
+
{ label: 'B ', fn: () => run1(fB) },
|
|
163
|
+
]
|
|
164
|
+
if (retS1 !== 0) s1Arms.push({ label: 'C ', fn: () => run1(fC) })
|
|
165
|
+
const s2Arms: Arm[] = [
|
|
166
|
+
{ label: 'A1', fn: () => run2(fA1) },
|
|
167
|
+
{ label: 'A2', fn: () => run2(fA2) },
|
|
168
|
+
{ label: 'B ', fn: () => run2(fB) },
|
|
169
|
+
]
|
|
170
|
+
if (retS2 !== 0) s2Arms.push({ label: 'C ', fn: () => run2(fC) })
|
|
171
|
+
const s3Arms: Arm[] = [
|
|
172
|
+
{ label: 'A1', fn: () => run3(fA1) },
|
|
173
|
+
{ label: 'A2', fn: () => run3(fA2) },
|
|
174
|
+
{ label: 'B ', fn: () => run3(fB) },
|
|
175
|
+
]
|
|
176
|
+
if (retS3 !== 0) s3Arms.push({ label: 'C ', fn: () => run3(fC) })
|
|
177
|
+
|
|
178
|
+
step('bench_sendmessage_nonnull: S1/S3 batch=' + String(BATCH) + '×' + String(ROUNDS) +
|
|
179
|
+
'r, S2 n=' + String(N_S2) + '×' + String(ROUNDS_S2) + 'r (median 主指标)')
|
|
180
|
+
microSuite()
|
|
181
|
+
suite('S1 WM_NULL 纯数字参数 (最灵敏测点)', BATCH, ROUNDS, s1Arms)
|
|
182
|
+
suite('S2 WM_SETTEXT 字符串 lParam (FFI 侧含 WCHAR.encode)', N_S2, ROUNDS_S2, s2Arms)
|
|
183
|
+
suite('S3 WM_GETTEXT 缓冲出参往返', BATCH, ROUNDS, s3Arms)
|
|
184
|
+
|
|
185
|
+
gui.DestroyWindow(win)
|
|
186
|
+
step('DONE')
|
|
187
|
+
if (log) log.close()
|
|
188
|
+
std.exit(0)
|
package/test/run.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import '../lib/certs.js';
|
|
1
2
|
import * as std from 'std';
|
|
2
3
|
import { Tester } from './test_helper.js';
|
|
3
4
|
function formatDuration(ms) {
|
|
@@ -23,6 +24,12 @@ const suiteDefs = [
|
|
|
23
24
|
{ name: 'mupdf-twice', file: './test_mupdf_twice.js', tags: ['wasm', 'mupdf'] },
|
|
24
25
|
{ name: 'mupdf-render', file: './test_mupdf_render.js', tags: ['wasm', 'mupdf'] },
|
|
25
26
|
{ name: 'ffi', file: './test_ffi.js', tags: [] },
|
|
27
|
+
{ name: 'ffi-bind', file: './test_ffi_bind.js', tags: [] },
|
|
28
|
+
{ name: 'ffi-struct', file: './test_ffi_struct.js', tags: [] },
|
|
29
|
+
{ name: 'ffi-struct-layout', file: './test_ffi_struct_layout.js', tags: [] },
|
|
30
|
+
{ name: 'ffi-abi', file: './test_ffi_abi.js', tags: [] },
|
|
31
|
+
{ name: 'ffi-user32', file: './test_ffi_user32.js', tags: [] },
|
|
32
|
+
{ name: 'ffi-windows', file: './test_ffi_windows.js', tags: [] },
|
|
26
33
|
{ name: 'net-fetch', file: './test_net_fetch.js', tags: ['net'] },
|
|
27
34
|
{ name: 'net-wolfssl-read', file: './test_wolfssl_read.js', tags: ['net'] },
|
|
28
35
|
{ name: 'net-websocket', file: './test_net_websocket.js', tags: ['net'] },
|
|
@@ -45,7 +52,7 @@ async function main() {
|
|
|
45
52
|
if (filter)
|
|
46
53
|
std.printf('Filter: %s\n', filter);
|
|
47
54
|
const results = [];
|
|
48
|
-
let totalOk = 0, totalFail = 0;
|
|
55
|
+
let totalOk = 0, totalFail = 0, totalSkip = 0;
|
|
49
56
|
for (const def of suiteDefs) {
|
|
50
57
|
if (filter) {
|
|
51
58
|
if (filter.startsWith('-')) {
|
|
@@ -79,16 +86,19 @@ async function main() {
|
|
|
79
86
|
}
|
|
80
87
|
const suiteElapsed = Date.now() - suiteStart;
|
|
81
88
|
t.summary();
|
|
82
|
-
results.push({ name: def.name, ok: t.ok, fail: t.fail, elapsed: suiteElapsed });
|
|
89
|
+
results.push({ name: def.name, ok: t.ok, fail: t.fail, skip: t.skip, elapsed: suiteElapsed });
|
|
83
90
|
totalOk += t.ok;
|
|
84
91
|
totalFail += t.fail;
|
|
92
|
+
totalSkip += t.skip;
|
|
85
93
|
}
|
|
86
94
|
const color = totalFail > 0 ? RED : GREEN;
|
|
87
95
|
std.printf('\n%s====== Test Results ======%s\n', BOLD, RESET);
|
|
88
96
|
for (const r of results) {
|
|
89
97
|
const c = r.fail > 0 ? RED : GREEN;
|
|
90
|
-
|
|
98
|
+
const skipped = r.skip > 0 ? ` (${r.skip} skipped)` : '';
|
|
99
|
+
std.printf(' %s%-18s %s%d/%d passed%s %s(%s)%s\n', c, r.name, RESET, r.ok, r.ok + r.fail, skipped, c, formatDuration(r.elapsed), RESET);
|
|
91
100
|
}
|
|
92
|
-
|
|
101
|
+
const skipped = totalSkip > 0 ? ` (${totalSkip} skipped)` : '';
|
|
102
|
+
std.printf('%s====== Summary: %d/%d passed%s ======%s\n', color, totalOk, totalOk + totalFail, skipped, RESET);
|
|
93
103
|
}
|
|
94
104
|
main();
|
package/test/run.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import '../lib/certs.js'
|
|
1
2
|
import * as std from 'std'
|
|
2
3
|
import { Tester } from './test_helper.js'
|
|
3
4
|
|
|
@@ -27,6 +28,12 @@ const suiteDefs = [
|
|
|
27
28
|
{ name: 'mupdf-twice', file: './test_mupdf_twice.js', tags: ['wasm', 'mupdf'] },
|
|
28
29
|
{ name: 'mupdf-render', file: './test_mupdf_render.js', tags: ['wasm', 'mupdf'] },
|
|
29
30
|
{ name: 'ffi', file: './test_ffi.js', tags: [] },
|
|
31
|
+
{ name: 'ffi-bind', file: './test_ffi_bind.js', tags: [] },
|
|
32
|
+
{ name: 'ffi-struct', file: './test_ffi_struct.js', tags: [] },
|
|
33
|
+
{ name: 'ffi-struct-layout', file: './test_ffi_struct_layout.js', tags: [] },
|
|
34
|
+
{ name: 'ffi-abi', file: './test_ffi_abi.js', tags: [] },
|
|
35
|
+
{ name: 'ffi-user32', file: './test_ffi_user32.js', tags: [] },
|
|
36
|
+
{ name: 'ffi-windows', file: './test_ffi_windows.js', tags: [] },
|
|
30
37
|
{ name: 'net-fetch', file: './test_net_fetch.js', tags: ['net'] },
|
|
31
38
|
{ name: 'net-wolfssl-read', file: './test_wolfssl_read.js', tags: ['net'] },
|
|
32
39
|
{ name: 'net-websocket', file: './test_net_websocket.js', tags: ['net'] },
|
|
@@ -50,8 +57,8 @@ async function main(): Promise<void> {
|
|
|
50
57
|
std.printf('%s====== QuickWin Test Runner ======%s\n', BOLD, RESET)
|
|
51
58
|
if (filter) std.printf('Filter: %s\n', filter)
|
|
52
59
|
|
|
53
|
-
const results: { name: string, ok: number, fail: number, elapsed: number }[] = []
|
|
54
|
-
let totalOk = 0, totalFail = 0
|
|
60
|
+
const results: { name: string, ok: number, fail: number, skip: number, elapsed: number }[] = []
|
|
61
|
+
let totalOk = 0, totalFail = 0, totalSkip = 0
|
|
55
62
|
|
|
56
63
|
for (const def of suiteDefs) {
|
|
57
64
|
if (filter) {
|
|
@@ -82,18 +89,21 @@ async function main(): Promise<void> {
|
|
|
82
89
|
}
|
|
83
90
|
const suiteElapsed = Date.now() - suiteStart
|
|
84
91
|
t.summary()
|
|
85
|
-
results.push({ name: def.name, ok: t.ok, fail: t.fail, elapsed: suiteElapsed })
|
|
92
|
+
results.push({ name: def.name, ok: t.ok, fail: t.fail, skip: t.skip, elapsed: suiteElapsed })
|
|
86
93
|
totalOk += t.ok
|
|
87
94
|
totalFail += t.fail
|
|
95
|
+
totalSkip += t.skip
|
|
88
96
|
}
|
|
89
97
|
|
|
90
98
|
const color = totalFail > 0 ? RED : GREEN
|
|
91
99
|
std.printf('\n%s====== Test Results ======%s\n', BOLD, RESET)
|
|
92
100
|
for (const r of results) {
|
|
93
101
|
const c = r.fail > 0 ? RED : GREEN
|
|
94
|
-
|
|
102
|
+
const skipped = r.skip > 0 ? ` (${r.skip} skipped)` : ''
|
|
103
|
+
std.printf(' %s%-18s %s%d/%d passed%s %s(%s)%s\n', c, r.name, RESET, r.ok, r.ok + r.fail, skipped, c, formatDuration(r.elapsed), RESET)
|
|
95
104
|
}
|
|
96
|
-
|
|
105
|
+
const skipped = totalSkip > 0 ? ` (${totalSkip} skipped)` : ''
|
|
106
|
+
std.printf('%s====== Summary: %d/%d passed%s ======%s\n', color, totalOk, totalOk + totalFail, skipped, RESET)
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
main()
|
package/test/test_basic_fetch.js
CHANGED
|
@@ -30,5 +30,13 @@ export const suite = {
|
|
|
30
30
|
const body = await r.text();
|
|
31
31
|
assert('body = hello from test server', body === 'hello from test server');
|
|
32
32
|
}
|
|
33
|
+
t.section('fetch chunked response with Trailer');
|
|
34
|
+
{
|
|
35
|
+
const r = await fetch(URL127 + 'trailer');
|
|
36
|
+
assert('status 200', r.status === 200);
|
|
37
|
+
assert('trailer declared', (r.headers.get('trailer') || '').toLowerCase() === 'x-checksum');
|
|
38
|
+
const body = await r.text();
|
|
39
|
+
assert('body decoded without trailer footer', body === 'chunkA/chunkB');
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
};
|
package/test/test_basic_fetch.ts
CHANGED
|
@@ -29,5 +29,14 @@ export const suite = {
|
|
|
29
29
|
const body = await r.text()
|
|
30
30
|
assert('body = hello from test server', body === 'hello from test server')
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
t.section('fetch chunked response with Trailer')
|
|
34
|
+
{
|
|
35
|
+
const r = await fetch(URL127 + 'trailer')
|
|
36
|
+
assert('status 200', r.status === 200)
|
|
37
|
+
assert('trailer declared', (r.headers.get('trailer') || '').toLowerCase() === 'x-checksum')
|
|
38
|
+
const body = await r.text()
|
|
39
|
+
assert('body decoded without trailer footer', body === 'chunkA/chunkB')
|
|
40
|
+
}
|
|
32
41
|
}
|
|
33
42
|
}
|
package/test/test_ffi.js
CHANGED
|
@@ -2,6 +2,8 @@ import * as std from 'std';
|
|
|
2
2
|
import * as win from 'win';
|
|
3
3
|
import * as ffi from 'ffi';
|
|
4
4
|
import * as os from 'os';
|
|
5
|
+
import { bind, bindLib } from '../lib/ffi/bind.js';
|
|
6
|
+
import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js';
|
|
5
7
|
const MAX_WCHARS = 4096;
|
|
6
8
|
function decodeWideAtPtr(ptr) {
|
|
7
9
|
if (!ptr)
|
|
@@ -78,49 +80,28 @@ export const suite = {
|
|
|
78
80
|
t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', enumPrinters !== null);
|
|
79
81
|
if (!enumPrinters)
|
|
80
82
|
return;
|
|
83
|
+
const { EnumPrintersW } = bindLib('winspool.drv', {
|
|
84
|
+
EnumPrintersW: 'u32 <>ptr u32 <BYTE>ptr u32 <BYTE>ptr <BYTE>ptr -> i32',
|
|
85
|
+
});
|
|
81
86
|
const flags = 0x06;
|
|
82
87
|
const level = 2;
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
ffi.FFI_TYPE_UINT32,
|
|
89
|
-
ffi.FFI_TYPE_POINTER,
|
|
90
|
-
ffi.FFI_TYPE_UINT32,
|
|
91
|
-
ffi.FFI_TYPE_POINTER,
|
|
92
|
-
ffi.FFI_TYPE_POINTER,
|
|
93
|
-
], [
|
|
94
|
-
flags,
|
|
95
|
-
null,
|
|
96
|
-
level,
|
|
97
|
-
null,
|
|
98
|
-
0,
|
|
99
|
-
neededBuf.buffer,
|
|
100
|
-
returnedBuf.buffer,
|
|
101
|
-
], ffi.FFI_TYPE_SINT32);
|
|
88
|
+
const neededB = new PtrArrayBuffer(4);
|
|
89
|
+
const neededBuf = new Uint32Array(neededB);
|
|
90
|
+
const returnedB = new PtrArrayBuffer(4);
|
|
91
|
+
const returnedBuf = new Uint32Array(returnedB);
|
|
92
|
+
const ret1 = EnumPrintersW(flags, NULL, level, NULL, 0, neededB, returnedB);
|
|
102
93
|
std.printf(' first call: ret=%d needed=%d returned=%d\n', ret1, neededBuf[0], returnedBuf[0]);
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
if (neededBuf[0] > 0) {
|
|
95
|
+
t.checkTrue('pcbNeeded > 0', neededBuf[0] > 0);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// 无打印机环境(CI VM)EnumPrintersW 的 pcbNeeded 为 0,属环境差异非缺陷,
|
|
99
|
+
// 记 SKIP 而非 FAIL,CI 无需再对 ffi 用例做失败容忍。
|
|
100
|
+
t.skipCase('pcbNeeded > 0 (no printers on this VM)');
|
|
105
101
|
return;
|
|
106
|
-
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
ffi.FFI_TYPE_POINTER,
|
|
110
|
-
ffi.FFI_TYPE_UINT32,
|
|
111
|
-
ffi.FFI_TYPE_POINTER,
|
|
112
|
-
ffi.FFI_TYPE_UINT32,
|
|
113
|
-
ffi.FFI_TYPE_POINTER,
|
|
114
|
-
ffi.FFI_TYPE_POINTER,
|
|
115
|
-
], [
|
|
116
|
-
flags,
|
|
117
|
-
null,
|
|
118
|
-
level,
|
|
119
|
-
printerBuf,
|
|
120
|
-
neededBuf[0],
|
|
121
|
-
neededBuf.buffer,
|
|
122
|
-
returnedBuf.buffer,
|
|
123
|
-
], ffi.FFI_TYPE_SINT32);
|
|
102
|
+
}
|
|
103
|
+
const printerBuf = new PtrArrayBuffer(neededBuf[0]);
|
|
104
|
+
const ret2 = EnumPrintersW(flags, NULL, level, printerBuf, neededBuf[0], neededB, returnedB);
|
|
124
105
|
t.checkTrue('EnumPrintersW succeeds', ret2 !== 0);
|
|
125
106
|
if (ret2 === 0)
|
|
126
107
|
return;
|
|
@@ -159,14 +140,19 @@ export const suite = {
|
|
|
159
140
|
else {
|
|
160
141
|
std.printf(' no printers installed (skip)\n');
|
|
161
142
|
}
|
|
162
|
-
t.section('
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
143
|
+
t.section('PTR accepts raw number handles (XP 32-bit fix)');
|
|
144
|
+
const hUser32 = win.LoadLibrary('user32.dll');
|
|
145
|
+
t.checkTrue('LoadLibrary("user32.dll") succeeds', hUser32 !== null);
|
|
146
|
+
if (hUser32) {
|
|
147
|
+
const getDC = win.GetProcAddress(hUser32, 'GetDC');
|
|
148
|
+
t.checkTrue('GetProcAddress("GetDC") succeeds', getDC !== null);
|
|
149
|
+
if (getDC) {
|
|
150
|
+
const GetDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr');
|
|
151
|
+
const screenDc = GetDC(0);
|
|
152
|
+
t.checkTrue('PTR slot takes number (NULL hwnd) and returns a DC handle', screenDc !== 0);
|
|
153
|
+
const bogus = GetDC(0x12345678);
|
|
154
|
+
t.checkTrue('PTR slot takes non-zero number without throwing', bogus === 0);
|
|
155
|
+
}
|
|
169
156
|
}
|
|
170
|
-
t.checkTrue('non-ArrayBuffer pointer arg throws TypeError', threw);
|
|
171
157
|
}
|
|
172
158
|
};
|
package/test/test_ffi.ts
CHANGED
|
@@ -2,6 +2,8 @@ import * as std from 'std'
|
|
|
2
2
|
import * as win from 'win'
|
|
3
3
|
import * as ffi from 'ffi'
|
|
4
4
|
import * as os from 'os'
|
|
5
|
+
import { bind, bindLib } from '../lib/ffi/bind.js'
|
|
6
|
+
import { NULL, PtrArrayBuffer } from '../lib/ffi/ctype.js'
|
|
5
7
|
import { Tester } from './test_helper.js'
|
|
6
8
|
|
|
7
9
|
const MAX_WCHARS = 4096
|
|
@@ -81,59 +83,37 @@ export const suite = {
|
|
|
81
83
|
t.checkTrue('GetProcAddress("EnumPrintersW") succeeds', enumPrinters !== null)
|
|
82
84
|
if (!enumPrinters) return
|
|
83
85
|
|
|
86
|
+
const { EnumPrintersW } = bindLib('winspool.drv', {
|
|
87
|
+
EnumPrintersW: 'u32 <>ptr u32 <BYTE>ptr u32 <BYTE>ptr <BYTE>ptr -> i32',
|
|
88
|
+
})
|
|
89
|
+
|
|
84
90
|
const flags = 0x06
|
|
85
91
|
const level = 2
|
|
86
|
-
const
|
|
87
|
-
const
|
|
92
|
+
const neededB = new PtrArrayBuffer(4)
|
|
93
|
+
const neededBuf = new Uint32Array(neededB)
|
|
94
|
+
const returnedB = new PtrArrayBuffer(4)
|
|
95
|
+
const returnedBuf = new Uint32Array(returnedB)
|
|
88
96
|
|
|
89
|
-
const ret1 =
|
|
90
|
-
enumPrinters,
|
|
91
|
-
[
|
|
92
|
-
ffi.FFI_TYPE_UINT32,
|
|
93
|
-
ffi.FFI_TYPE_POINTER,
|
|
94
|
-
ffi.FFI_TYPE_UINT32,
|
|
95
|
-
ffi.FFI_TYPE_POINTER,
|
|
96
|
-
ffi.FFI_TYPE_UINT32,
|
|
97
|
-
ffi.FFI_TYPE_POINTER,
|
|
98
|
-
ffi.FFI_TYPE_POINTER,
|
|
99
|
-
],
|
|
100
|
-
[
|
|
101
|
-
flags,
|
|
102
|
-
null,
|
|
103
|
-
level,
|
|
104
|
-
null,
|
|
105
|
-
0,
|
|
106
|
-
neededBuf.buffer,
|
|
107
|
-
returnedBuf.buffer,
|
|
108
|
-
],
|
|
109
|
-
ffi.FFI_TYPE_SINT32
|
|
110
|
-
)
|
|
97
|
+
const ret1 = EnumPrintersW(flags, NULL, level, NULL, 0, neededB, returnedB)
|
|
111
98
|
std.printf(' first call: ret=%d needed=%d returned=%d\n', ret1, neededBuf[0], returnedBuf[0])
|
|
112
|
-
|
|
113
|
-
|
|
99
|
+
if (neededBuf[0]! > 0) {
|
|
100
|
+
t.checkTrue('pcbNeeded > 0', neededBuf[0]! > 0)
|
|
101
|
+
} else {
|
|
102
|
+
// 无打印机环境(CI VM)EnumPrintersW 的 pcbNeeded 为 0,属环境差异非缺陷,
|
|
103
|
+
// 记 SKIP 而非 FAIL,CI 无需再对 ffi 用例做失败容忍。
|
|
104
|
+
t.skipCase('pcbNeeded > 0 (no printers on this VM)')
|
|
105
|
+
return
|
|
106
|
+
}
|
|
114
107
|
|
|
115
|
-
const printerBuf = new
|
|
116
|
-
const ret2 =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
ffi.FFI_TYPE_POINTER,
|
|
125
|
-
ffi.FFI_TYPE_POINTER,
|
|
126
|
-
],
|
|
127
|
-
[
|
|
128
|
-
flags,
|
|
129
|
-
null,
|
|
130
|
-
level,
|
|
131
|
-
printerBuf,
|
|
132
|
-
neededBuf[0]!,
|
|
133
|
-
neededBuf.buffer,
|
|
134
|
-
returnedBuf.buffer,
|
|
135
|
-
],
|
|
136
|
-
ffi.FFI_TYPE_SINT32
|
|
108
|
+
const printerBuf = new PtrArrayBuffer(neededBuf[0]!)
|
|
109
|
+
const ret2 = EnumPrintersW(
|
|
110
|
+
flags,
|
|
111
|
+
NULL,
|
|
112
|
+
level,
|
|
113
|
+
printerBuf,
|
|
114
|
+
neededBuf[0]!,
|
|
115
|
+
neededB,
|
|
116
|
+
returnedB
|
|
137
117
|
)
|
|
138
118
|
t.checkTrue('EnumPrintersW succeeds', ret2 !== 0)
|
|
139
119
|
if (ret2 === 0) return
|
|
@@ -173,18 +153,19 @@ export const suite = {
|
|
|
173
153
|
std.printf(' no printers installed (skip)\n')
|
|
174
154
|
}
|
|
175
155
|
|
|
176
|
-
t.section('
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
156
|
+
t.section('PTR accepts raw number handles (XP 32-bit fix)')
|
|
157
|
+
const hUser32 = win.LoadLibrary('user32.dll')
|
|
158
|
+
t.checkTrue('LoadLibrary("user32.dll") succeeds', hUser32 !== null)
|
|
159
|
+
if (hUser32) {
|
|
160
|
+
const getDC = win.GetProcAddress(hUser32, 'GetDC')
|
|
161
|
+
t.checkTrue('GetProcAddress("GetDC") succeeds', getDC !== null)
|
|
162
|
+
if (getDC) {
|
|
163
|
+
const GetDC = bind('user32.dll', 'GetDC', '<>ptr -> <>ptr')
|
|
164
|
+
const screenDc = GetDC(0)
|
|
165
|
+
t.checkTrue('PTR slot takes number (NULL hwnd) and returns a DC handle', screenDc !== 0)
|
|
166
|
+
const bogus = GetDC(0x12345678)
|
|
167
|
+
t.checkTrue('PTR slot takes non-zero number without throwing', bogus === 0)
|
|
168
|
+
}
|
|
187
169
|
}
|
|
188
|
-
t.checkTrue('non-ArrayBuffer pointer arg throws TypeError', threw)
|
|
189
170
|
}
|
|
190
171
|
}
|