@harmonyos-arkts/d2h 0.1.20 → 0.1.22

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.
Files changed (152) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +40 -0
  3. package/agents/android-to-hmos-00-orchestrator.md +3 -315
  4. package/agents/d2h.md +53 -38
  5. package/contracts/workflow/artifacts.md +22 -0
  6. package/contracts/workflow/delivery.md +54 -0
  7. package/contracts/workflow/dispatch.md +26 -0
  8. package/contracts/workflow/execution.md +125 -0
  9. package/contracts/workflow/generation.md +40 -0
  10. package/contracts/workflow/orchestration.md +49 -0
  11. package/contracts/workflow/overview.md +58 -0
  12. package/contracts/workflow/plan.md +119 -0
  13. package/contracts/workflow/scope.md +151 -0
  14. package/dist/bin/install-opencode.js +34 -15
  15. package/dist/src/install-codex.js +131 -0
  16. package/dist/src/install-opencode.js +5 -5
  17. package/dist/src/workflow.js +54 -0
  18. package/docs/architecture-plan-guide.zh-CN.md +70 -0
  19. package/docs/incremental-spec-evaluation-design.md +670 -0
  20. package/docs/spec-plan-guide.md +226 -0
  21. package/docs/workflow-decoupling.md +59 -0
  22. package/opencode/interactive-tools/advance-progress-demo.mjs +21 -0
  23. package/opencode/interactive-tools/browser-host.ts +110 -0
  24. package/opencode/interactive-tools/core.ts +242 -0
  25. package/opencode/interactive-tools/demo-workflow.ts +55 -0
  26. package/opencode/interactive-tools/demo.mjs +37 -0
  27. package/opencode/interactive-tools/package-lock.json +1876 -0
  28. package/opencode/interactive-tools/package.json +23 -0
  29. package/opencode/interactive-tools/panel.ts +40 -0
  30. package/opencode/interactive-tools/prepare-demo.mjs +17 -0
  31. package/opencode/interactive-tools/prepare-progress-demo.mjs +13 -0
  32. package/opencode/interactive-tools/progress.ts +118 -0
  33. package/opencode/interactive-tools/server-legacy.mjs +2 -0
  34. package/opencode/interactive-tools/server.mjs +40 -0
  35. package/opencode/interactive-tools/tui.tsx +230 -0
  36. package/package.json +9 -1
  37. package/schemas/native-libs-analysis-report.schema.json +695 -0
  38. package/schemas/native-libs-verify-report.schema.json +566 -0
  39. package/schemas/spec-native-libs-shard.schema.json +110 -0
  40. package/skills/arkts-icon-sizing/SKILL.md +0 -10
  41. package/skills/cpp/d2h-native-library-analyse/SKILL.md +91 -0
  42. package/skills/cpp/d2h-native-library-analyse/scripts/analyse-android-so.py +630 -0
  43. package/skills/cpp/d2h-native-library-build/SKILL.md +403 -0
  44. package/skills/cpp/d2h-native-library-build/references/build-config-mapping.md +180 -0
  45. package/skills/cpp/d2h-native-library-build/references/jni-to-napi-mapping.md +74 -0
  46. package/skills/cpp/d2h-native-library-build/scripts/check-android-so.py +580 -0
  47. package/skills/cpp/d2h-native-library-verify/SKILL.md +111 -0
  48. package/skills/cpp/d2h-native-library-verify/references/demo-build-sign-run.md +67 -0
  49. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/app.json5 +1 -0
  50. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/AppScope/resources/base/element/string.json +1 -0
  51. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/README.md +25 -0
  52. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/build-profile.json5 +24 -0
  53. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/build-profile.json5 +1 -0
  54. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/hvigorfile.ts +2 -0
  55. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/oh-package.json5 +1 -0
  56. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/entryability/EntryAbility.ets +5 -0
  57. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/Index.ets +3 -0
  58. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/ets/pages/NativeVerifyDemo.ets +276 -0
  59. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/module.json5 +1 -0
  60. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/color.json +1 -0
  61. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/element/string.json +1 -0
  62. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/main/resources/base/profile/main_pages.json +1 -0
  63. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/FFmpegVerify.test.ets +176 -0
  64. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/ets/test/List.test.ets +2 -0
  65. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/entry/src/ohosTest/module.json5 +1 -0
  66. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigor/hvigor-config.json5 +1 -0
  67. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/hvigorfile.ts +2 -0
  68. package/skills/cpp/d2h-native-library-verify/references/demo-project-template/oh-package.json5 +9 -0
  69. package/skills/cpp/d2h-native-library-verify/references/demo-template.md +176 -0
  70. package/skills/cpp/d2h-native-library-verify/references/hypium-test-template.md +163 -0
  71. package/skills/cpp/d2h-native-library-verify/scripts/__pycache__/native_common.cpython-313.pyc +0 -0
  72. package/skills/cpp/d2h-native-library-verify/scripts/device-verify.py +304 -0
  73. package/skills/cpp/d2h-native-library-verify/scripts/native_common.py +341 -0
  74. package/skills/cpp/d2h-native-library-verify/scripts/verify-ohos-native.py +1099 -0
  75. package/skills/d2h-app-solution/SKILL.md +198 -0
  76. package/skills/d2h-app-solution/agents/openai.yaml +4 -0
  77. package/skills/d2h-app-solution/references/framework-evidence.md +117 -0
  78. package/skills/d2h-app-solution/references/hierarchy-rules.md +88 -0
  79. package/skills/d2h-app-solution/references/inventory-schema.md +305 -0
  80. package/skills/d2h-app-solution/references/technical-analysis.md +101 -0
  81. package/skills/d2h-app-solution/scripts/scan_repositories.py +350 -0
  82. package/skills/d2h-app-solution/scripts/validate_inventory.py +337 -0
  83. package/skills/d2h-architecture-plan/SKILL.md +35 -0
  84. package/skills/d2h-architecture-plan/references/directory-creation.md +43 -0
  85. package/skills/d2h-architecture-plan/references/hybrid-design.md +84 -0
  86. package/skills/d2h-architecture-plan/references/layout-reference.md +83 -0
  87. package/skills/d2h-architecture-plan/references/plan-contract.md +51 -0
  88. package/skills/d2h-architecture-plan/references/source-review.md +50 -0
  89. package/skills/d2h-cloud-knowledge/SKILL.md +28 -7
  90. package/skills/d2h-cloud-knowledge/kb_query.mjs +8 -0
  91. package/skills/d2h-dashboard/SKILL.md +56 -0
  92. package/skills/d2h-product-family-migration/SKILL.md +103 -0
  93. package/skills/d2h-product-family-migration/agents/openai.yaml +3 -0
  94. package/skills/d2h-product-family-migration/assets/incremental-plan.template.md +55 -0
  95. package/skills/d2h-product-family-migration/references/advanced/behavioral-verification.md +59 -0
  96. package/skills/d2h-product-family-migration/references/advanced/existing-skill-integration.md +30 -0
  97. package/skills/d2h-product-family-migration/references/advanced/packaging-and-verification.md +43 -0
  98. package/skills/d2h-product-family-migration/references/advanced/platform-probes.md +26 -0
  99. package/skills/d2h-product-family-migration/references/advanced/product-line-design.md +40 -0
  100. package/skills/d2h-product-family-migration/references/baseline-and-delta.md +50 -0
  101. package/skills/d2h-product-family-migration/tests/markdown-cases.md +76 -0
  102. package/skills/d2h-spec-develop/SKILL.md +41 -0
  103. package/skills/d2h-spec-develop/agents/openai.yaml +7 -0
  104. package/skills/d2h-spec-develop/references/worker-workflow.md +26 -0
  105. package/skills/{d2h-page-spec-generate → d2h-spec-generate}/SKILL.md +24 -13
  106. package/skills/d2h-spec-generate/assets/spec-template.md +109 -0
  107. package/skills/d2h-spec-generate/references/from-plan.md +19 -0
  108. package/skills/d2h-spec-generate/references/spec-content.md +34 -0
  109. package/skills/{d2h-page-spec-generate → d2h-spec-generate}/scripts/build_batch_report.py +35 -84
  110. package/skills/d2h-spec-plan/SKILL.md +38 -0
  111. package/skills/d2h-spec-plan/agents/openai.yaml +4 -0
  112. package/skills/d2h-spec-plan/references/architecture-handoff.md +9 -0
  113. package/skills/d2h-spec-plan/references/plan-contract.md +13 -0
  114. package/skills/d2h-spec-plan/references/planning-method.md +46 -0
  115. package/skills/d2h-spec-plan/references/round-scope-assessment.md +49 -0
  116. package/skills/d2h-spec-plan/references/work-packets.md +61 -0
  117. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +2 -2
  118. package/skills/d2h-utg-ai-generate/references/page-schema.json +2 -2
  119. package/skills/d2h-utg-ai-generate/references/utg-schema.json +2 -2
  120. package/skills/d2h-utg-static-generate/SKILL.md +2 -2
  121. package/skills/managing-d2h-toolchain/environment-requirements.json +1 -1
  122. package/skills/managing-d2h-toolchain/toolchain.json +1 -1
  123. package/skills/preparing-migration-workspace/SKILL.md +32 -2
  124. package/workflow/__pycache__/artifact_bindings.cpython-313.pyc +0 -0
  125. package/workflow/__pycache__/spec_runtime.cpython-313.pyc +0 -0
  126. package/workflow/artifact_bindings.py +224 -0
  127. package/workflow/assets/dashboard.html +88 -0
  128. package/workflow/build_batch_report.py +8 -0
  129. package/workflow/collect_code_metrics.py +75 -0
  130. package/workflow/dashboard_overview.py +109 -0
  131. package/workflow/delivery_view.py +82 -0
  132. package/workflow/feature_plan.py +234 -0
  133. package/workflow/page_batches.py +223 -0
  134. package/workflow/spec_generation.py +120 -0
  135. package/workflow/spec_runtime.py +890 -0
  136. package/workflow/work_packets.py +85 -0
  137. package/skills/d2h-page-develop/SKILL.md +0 -126
  138. package/skills/d2h-page-develop/agents/openai.yaml +0 -7
  139. package/skills/harmonyos-migration-playbook/SKILL.md +0 -83
  140. package/skills/harmonyos-migration-playbook/agents/openai.yaml +0 -7
  141. package/skills/harmonyos-migration-playbook/references/arkts-compile.md +0 -24
  142. package/skills/harmonyos-migration-playbook/references/harmony-runtime.md +0 -53
  143. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +0 -45
  144. package/skills/harmonyos-migration-playbook/references/protocol-e2e.md +0 -23
  145. package/skills/harmonyos-migration-playbook/references/ui-automation.md +0 -53
  146. package/skills/harmonyos-migration-playbook/references/windows-environment.md +0 -38
  147. package/skills/native-library-substitution/SKILL.md +0 -385
  148. package/skills/native-library-substitution/references/native-library-substitution.json +0 -56906
  149. package/skills/native-library-substitution/references/native-library-substitution.md +0 -163
  150. package/skills/porting-cmake-native-libraries/SKILL.md +0 -57
  151. package/skills/porting-cmake-native-libraries/references/platform-adaptation.md +0 -49
  152. package/skills/porting-cmake-native-libraries/references/report-and-build.md +0 -60
@@ -0,0 +1,176 @@
1
+ import { describe, it, expect, beforeAll } from '@ohos/hypium';
2
+ import { FFmpeg, FFAbortController, FFAudioPlayer } from '@sj/ffmpeg';
3
+ import { abilityDelegatorRegistry } from '@kit.TestKit';
4
+ import { common } from '@kit.AbilityKit';
5
+ import { fileIo as fs } from '@kit.CoreFileKit';
6
+
7
+ let filesDir = '';
8
+ let wav = '';
9
+
10
+ function run(cmd: string[]): Promise<string> {
11
+ return new Promise<string>((resolve) => {
12
+ let out = '';
13
+ FFmpeg.execute(cmd, {
14
+ logCallback: (m: string) => { out += m; },
15
+ outputCallback: (m: string) => { out += m; },
16
+ }).then(() => resolve(out)).catch((e: Error) => resolve('ERR:' + e.message));
17
+ });
18
+ }
19
+
20
+ export default function ffmpegVerifyTest() {
21
+ describe('FFmpegVerify', () => {
22
+ beforeAll(async () => {
23
+ const delegator = abilityDelegatorRegistry.getAbilityDelegator();
24
+ const ability = await delegator.getCurrentTopAbility();
25
+ const ctx = ability.context as common.UIAbilityContext;
26
+ filesDir = ctx.filesDir;
27
+ wav = `${filesDir}/ffmpeg_test.wav`;
28
+ const raw = await ctx.resourceManager.getRawFileContent('ffmpeg_test.wav');
29
+ const f = fs.openSync(wav, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
30
+ fs.writeSync(f.fd, raw.buffer);
31
+ fs.closeSync(f);
32
+ });
33
+
34
+ // ===== 14 个 verify 用例(对齐 api-map disposition=verify)=====
35
+
36
+ // 1. FFmpegCmd.handle → FFmpeg.execute(命令执行)
37
+ it('01_handle_executes', 0, async () => {
38
+ const out = await run(['ffmpeg', '-version']);
39
+ expect(out).assertContain('ffmpeg version');
40
+ });
41
+
42
+ // 2. FFmpegCmd.handleProbe → FFmpeg.execute(探测)
43
+ it('02_handleProbe_executes', 0, async () => {
44
+ const out = await run(['ffprobe', '-version']);
45
+ expect(out).assertContain('ffprobe version');
46
+ });
47
+
48
+ // 3. FFmpegCmd.cancelTaskJni → FFAbortController.abort(取消)
49
+ it('03_cancelTask_abort', 0, async () => {
50
+ const c = new FFAbortController();
51
+ const p = FFmpeg.execute(['ffmpeg', '-i', wav, `${filesDir}/abort.m4a`, '-y'],
52
+ { signal: c.signal, logCallback: (m: string) => {} });
53
+ setTimeout(() => { c.abort(); }, 200);
54
+ let aborted = false;
55
+ try { await p; } catch (err) { aborted = true; }
56
+ console.info(`[NATIVE_TEST] 03 abort=${aborted}`);
57
+ expect(aborted).assertTrue();
58
+ });
59
+
60
+ // 4. AudioPlayer.native_play → FFAudioPlayer(音频播放)
61
+ it('04_native_play_audio', 0, async () => {
62
+ const p = new FFAudioPlayer();
63
+ p.url = `file://${wav}`;
64
+ p.prepare();
65
+ await new Promise<void>((r) => setTimeout(r, 1500));
66
+ p.play();
67
+ await new Promise<void>((r) => setTimeout(r, 800));
68
+ const playing = p.playWhenReady;
69
+ p.pause();
70
+ expect(playing).assertTrue();
71
+ });
72
+
73
+ // 5. AudioPlayer.native_get_duration → FFAudioPlayer.duration
74
+ it('05_native_get_duration', 0, async () => {
75
+ const p = new FFAudioPlayer();
76
+ p.url = `file://${wav}`;
77
+ p.prepare();
78
+ await new Promise<void>((r) => setTimeout(r, 1500));
79
+ console.info(`[NATIVE_TEST] 05 duration=${p.duration}`);
80
+ expect(p.duration).assertLarger(0);
81
+ });
82
+
83
+ // 6. AudioPlayer.native_get_position → FFAudioPlayer.currentTime
84
+ it('06_native_get_position', 0, async () => {
85
+ const p = new FFAudioPlayer();
86
+ p.url = `file://${wav}`;
87
+ p.prepare();
88
+ await new Promise<void>((r) => setTimeout(r, 1500));
89
+ p.play();
90
+ await new Promise<void>((r) => setTimeout(r, 500));
91
+ const pos = p.currentTime;
92
+ p.pause();
93
+ console.info(`[NATIVE_TEST] 06 pos=${pos}`);
94
+ expect(pos).assertLargerOrEqual(0);
95
+ });
96
+
97
+ // 7. AudioPlayer.native_again → seek(0)+play(重新播放)
98
+ it('07_native_again_replay', 0, async () => {
99
+ const p = new FFAudioPlayer();
100
+ p.url = `file://${wav}`;
101
+ p.prepare();
102
+ await new Promise<void>((r) => setTimeout(r, 1500));
103
+ p.play();
104
+ await new Promise<void>((r) => setTimeout(r, 500));
105
+ p.seek(0);
106
+ p.play();
107
+ const playing = p.playWhenReady;
108
+ p.pause();
109
+ expect(playing).assertTrue();
110
+ });
111
+
112
+ // 8. VideoPlayer.executeTranscode → FFmpeg.execute(转码)
113
+ it('08_executeTranscode_output', 0, async () => {
114
+ const out = `${filesDir}/t.m4a`;
115
+ await run(['ffmpeg', '-i', wav, out, '-y']);
116
+ let ok = false;
117
+ try { fs.accessSync(out); ok = true; } catch (e) { ok = false; }
118
+ console.info(`[NATIVE_TEST] 08 transcode=${ok}`);
119
+ expect(ok).assertTrue();
120
+ });
121
+
122
+ // 9. VideoPlayer.again → FFmpeg.execute(重跑命令)
123
+ it('09_again_rerun', 0, async () => {
124
+ const out1 = await run(['ffmpeg', '-version']);
125
+ const out2 = await run(['ffmpeg', '-version']);
126
+ expect(out1).assertContain('ffmpeg version');
127
+ expect(out2).assertContain('ffmpeg version');
128
+ });
129
+
130
+ // 10. VideoPlayer.playAudio → FFAudioPlayer play/pause
131
+ it('10_playAudio_toggle', 0, async () => {
132
+ const p = new FFAudioPlayer();
133
+ p.url = `file://${wav}`;
134
+ p.prepare();
135
+ await new Promise<void>((r) => setTimeout(r, 1500));
136
+ p.play();
137
+ await new Promise<void>((r) => setTimeout(r, 300));
138
+ p.pause();
139
+ expect(p.playWhenReady).assertFalse();
140
+ });
141
+
142
+ // 11. FFmpegMediaRetriever.native_setDataSource → ffprobe -i
143
+ it('11_setDataSource_probe', 0, async () => {
144
+ const out = await run(['ffprobe', '-i', wav]);
145
+ console.info(`[NATIVE_TEST] 11 probe=${out.slice(0, 80)}`);
146
+ expect(out.length).assertLarger(0);
147
+ });
148
+
149
+ // 12. FFmpegMediaRetriever.native_extractMetadata → ffprobe -show_format
150
+ it('12_extractMetadata_format', 0, async () => {
151
+ const out = await run(['ffprobe', '-v', 'info', '-of', 'json', '-show_format', '-i', wav]);
152
+ console.info(`[NATIVE_TEST] 12 meta=${out.slice(0, 100)}`);
153
+ expect(out).assertContain('format');
154
+ });
155
+
156
+ // 13. FFmpegMediaRetriever.native_getFrameAtTime → ffmpeg 截帧
157
+ it('13_getFrameAtTime_screenshot', 0, async () => {
158
+ await run(['ffmpeg', '-f', 'lavfi', '-i', 'color=c=red:s=64x64:d=1', `${filesDir}/gen.mp4`, '-y']);
159
+ await run(['ffmpeg', '-ss', '0', '-i', `${filesDir}/gen.mp4`, '-frames:v', '1', `${filesDir}/frame.jpg`, '-y']);
160
+ let ok = false;
161
+ try { fs.accessSync(`${filesDir}/frame.jpg`); ok = true; } catch (e) { ok = false; }
162
+ console.info(`[NATIVE_TEST] 13 frame=${ok}`);
163
+ expect(ok).assertTrue();
164
+ });
165
+
166
+ // 14. FFmpegMediaRetriever.native_getScaleFrameAtTime → ffmpeg 缩放截帧
167
+ it('14_getScaleFrame_scaled', 0, async () => {
168
+ await run(['ffmpeg', '-ss', '0', '-i', `${filesDir}/gen.mp4`, '-frames:v', '1',
169
+ '-vf', 'scale=32:32', `${filesDir}/frame_small.jpg`, '-y']);
170
+ let ok = false;
171
+ try { fs.accessSync(`${filesDir}/frame_small.jpg`); ok = true; } catch (e) { ok = false; }
172
+ console.info(`[NATIVE_TEST] 14 scale=${ok}`);
173
+ expect(ok).assertTrue();
174
+ });
175
+ });
176
+ }
@@ -0,0 +1,2 @@
1
+ import t from './FFmpegVerify.test';
2
+ export default function testsuite(){t();}
@@ -0,0 +1 @@
1
+ {"module":{"name":"entry_test","type":"feature","deviceTypes":["phone"],"deliveryWithInstall":true,"installationFree":false}}
@@ -0,0 +1 @@
1
+ {"modelVersion":"26.0.0","dependencies":{},"execution":{},"logging":{},"debugging":{},"nodeOptions":{}}
@@ -0,0 +1,2 @@
1
+ import { appTasks } from '@ohos/hvigor-ohos-plugin';
2
+ export default { system: appTasks, plugins: [] }
@@ -0,0 +1,9 @@
1
+ {
2
+ "modelVersion": "26.0.0",
3
+ "description": "FFmpeg so verify",
4
+ "dependencies": {
5
+ "@sj/ffmpeg": "^2.0.2"
6
+ },
7
+ "devDependencies": {},
8
+ "dynamicDependencies": {}
9
+ }
@@ -0,0 +1,176 @@
1
+ # ArkTS/NAPI 验证 Demo 模板
2
+
3
+ 本模板规范 `d2h-native-library-verify` 第 4–6 步的 demo 生成。按 api-map 的用例数生成卡片式验证页;
4
+ `bridge-required` 库先按「二」生成桥接模块。所有代码放验证专用目录,不混入业务实现。
5
+
6
+ ## 一、工程布局
7
+
8
+ ```
9
+ entry/src/main/
10
+ ├── libs/arm64-v8a/libdemo.so # 被验证的 OHOS 库(不入业务模块)
11
+ ├── cpp/ # 仅 bridge-required 库需要
12
+ │ ├── CMakeLists.txt
13
+ │ └── demo_bridge.cpp # 纯转发桥接层
14
+ ├── ets/pages/NativeVerifyDemo.ets # 验证页(卡片列表)
15
+ └── resources/base/profile/main_pages.json # 注册页面
16
+ ```
17
+
18
+ ## 二、NAPI 桥接层(仅 bridge-required)
19
+
20
+ **原则:只做类型转发,不含任何业务逻辑**——桥接层引入的差异会污染验证结论。
21
+
22
+ ```cpp
23
+ // demo_bridge.cpp:libdemo.so 导出 int add(int, int)
24
+ #include "napi/native_api.h"
25
+
26
+ extern int add(int a, int b); // 与 .so 导出一一对应(对照 api-map ohos_symbol)
27
+
28
+ static napi_value Add(napi_env env, napi_callback_info info) {
29
+ size_t argc = 2;
30
+ napi_value args[2] = {nullptr};
31
+ napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
32
+ int32_t a = 0, b = 0;
33
+ napi_get_value_int32(env, args[0], &a);
34
+ napi_get_value_int32(env, args[1], &b);
35
+ napi_value result;
36
+ napi_create_int32(env, add(a, b), &result); // 转发调用
37
+ return result;
38
+ }
39
+
40
+ EXTERN_C_START
41
+ static napi_value Init(napi_env env, napi_value exports) {
42
+ napi_property_descriptor desc[] = {
43
+ { "add", nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr },
44
+ // ...api-map 每个 ohos_symbol 一条
45
+ };
46
+ napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc);
47
+ return exports;
48
+ }
49
+ EXTERN_C_END
50
+
51
+ static napi_module demoBridgeModule = {
52
+ .nm_version = 1, .nm_flags = 0, .nm_filename = nullptr,
53
+ .nm_register_func = Init, .nm_modname = "demo_bridge",
54
+ .nm_priv = nullptr, .reserved = { 0 },
55
+ };
56
+ extern "C" __attribute__((constructor)) void RegisterDemoBridge(void) {
57
+ napi_module_register(&demoBridgeModule);
58
+ }
59
+ ```
60
+
61
+ `CMakeLists.txt`:
62
+
63
+ ```cmake
64
+ cmake_minimum_required(VERSION 3.5.0)
65
+ project(demo_bridge)
66
+ add_library(demo_bridge SHARED demo_bridge.cpp)
67
+ target_link_libraries(demo_bridge PUBLIC libdemo.so) # 链接被验证库
68
+ ```
69
+
70
+ 类型映射按 `d2h-native-library-build` §6.4(jstring→napi_value 等)。
71
+
72
+ ## 三、类型声明(`.d.ts`)
73
+
74
+ `entry/src/main/cpp/types/demo_bridge/index.d.ts`(napi-ready 库把模块名换成 `libdemo.so`):
75
+
76
+ ```typescript
77
+ export const add: (a: number, b: number) => number;
78
+ // ...api-map 每个 API 一条;签名与 ohos_symbol 实际原型一致(从 .h/文档核对,不确定查 d2h-cloud-knowledge)
79
+ ```
80
+
81
+ ## 四、验证页(ArkTS)
82
+
83
+ > 按钮交互的已知坑(真实工程踩过):
84
+ > ① `List`/`ListItem` 内的 `Button.onClick` 在部分版本被列表点击拦截吞掉——用 `Scroll`+`Column`+`ForEach` 替代;
85
+ > ② ForEach 的 key 用会变的字段(如 `api+input`)会在数据更新时重建条目、闭包 `i` 可能失效——用稳定唯一 key(`api`);
86
+ > ③ ArkUI V1 `@State` 对**对象数组**深层观察失效——`map` 重赋值新数组仍不刷新条目文本;**改用 V2 状态管理**(`@ObservedV2` class + `@Trace` 字段 + `@ComponentV2`/`@Local`),`this.cases[i].actual = ...` 直接赋值即刷新;
87
+ > ④ `aboutToAppear` 若已自动执行全部用例,按钮重跑同命令结果相同 → 「点击无变化」其实是正常现象;加「运行中...」即时反馈让点击必有可见变化。
88
+ > **且按钮 demo 本就脆弱,功能验证首选 Hypium 测试用例(见 hypium-test-template.md)**。
89
+
90
+ **结果颜色规范**:`actual` 文本动态着色——`✗`(失败/差异/未实现)与**`异常:`** **红色**、`✓`(通过)**绿色**、`运行中...`/`-` 灰色:
91
+
92
+ ```typescript
93
+ .fontColor(
94
+ (this.cases[i].actual.startsWith('✗') || this.cases[i].actual.startsWith('异常'))
95
+ ? Color.Red : Color.Green)
96
+ ```
97
+
98
+ ```typescript
99
+ // NativeVerifyDemo.ets:api-map 每条用例一个卡片
100
+ import bridge from 'libdemo_bridge.so'; // napi-ready 库:import demo from 'libdemo.so'
101
+
102
+ interface ApiCase {
103
+ api: string; // 'add'
104
+ input: string; // '1, 2'(取 api-map demo_input)
105
+ expected: string; // '3'(取 api-map android_expected)
106
+ actual: string; // 运行时填写
107
+ }
108
+
109
+ @Entry
110
+ @Component
111
+ struct NativeVerifyDemo {
112
+ @State cases: ApiCase[] = [
113
+ { api: 'add', input: '1, 2', expected: '3', actual: '-' },
114
+ // ...每条 api-map 用例一顶;多输入场景拆多条
115
+ ];
116
+
117
+ runCase(i: number): void {
118
+ const c = this.cases[i];
119
+ try {
120
+ // 按 api 生成调用(add → bridge.add(1, 2);参数从 demo_input 解析)
121
+ const out = bridge.add(1, 2);
122
+ c.actual = String(out);
123
+ } catch (e) {
124
+ c.actual = '异常: ' + JSON.stringify(e); // 异常路径也是验证对象(Android 侧行为对照)
125
+ }
126
+ this.cases[i] = c;
127
+ // 同步写 hilog:api/input/expected/actual,供证据采集
128
+ console.info(`[NATIVE_VERIFY] ${c.api} input=${c.input} expected=${c.expected} actual=${c.actual}`);
129
+ }
130
+
131
+ build() {
132
+ List() {
133
+ ForEach(this.cases, (c: ApiCase, i: number) => {
134
+ ListItem() {
135
+ Column({ space: 6 }) {
136
+ Text(`${c.api}(${c.input})`).fontSize(16).fontWeight(FontWeight.Bold)
137
+ Text(`期望: ${c.expected} 实际: ${c.actual}`)
138
+ .fontColor(c.actual === c.expected ? Color.Green : Color.Red)
139
+ Button('运行').onClick(() => this.runCase(i))
140
+ }.width('100%').padding(12)
141
+ }
142
+ }, (c: ApiCase) => c.api + c.input)
143
+ }
144
+ }
145
+ }
146
+ ```
147
+
148
+ ## 四·五、无 UI 交互的自动执行与 CLI 签名
149
+
150
+ - **自动执行**:验证页 `aboutToAppear()` 里循环触发每个用例,`EntryAbility.loadContent` 直接指向验证页 → 启动即执行,无需人工点击「运行」按钮(无头运行时验证的前提)。
151
+ - **CLI 自动签名 + 部署采集**:见 [demo-build-sign-run.md](demo-build-sign-run.md)——hvigor headless 构建、复用本机 auto-sign 材料签名、`hdc install` + `aa start` + `hilog -x` 采集。
152
+
153
+ ## 五、采集与回填
154
+
155
+ 1. **运行**:逐卡片执行;`hdc shell hilog | grep NATIVE_VERIFY` 抓取输出存
156
+ `.migration/native-libs/verify-evidence/<lib>/run.log`(日志格式必须保持
157
+ `[NATIVE_VERIFY] api input=... expected=... actual=...`,采集器按此解析)。
158
+ 2. **采集**:
159
+ ```bash
160
+ python3 ../d2h-native-library-verify/scripts/verify-ohos-native.py \
161
+ --collect-diff .migration/native-libs/verify-evidence/<lib>/run.log \
162
+ --ohos-lib libdemo.so --api-map .migration/native-libs/api-map.json --json
163
+ ```
164
+ 三态自动判定(一致/不一致/未运行 `-`)、异常路径记 note、证据=日志:行号;
165
+ 同一用例重跑取最后一次。
166
+ 3. **复核合并**:AI 核对草稿(期望值来源、未运行用例改 `unverifiable` 并注明原因、
167
+ 差异描述补 note)后合并回 `verify-report.json` 的 `functional_diffs[]`。
168
+ 4. **沉淀**:可断言用例交 `d2h-hypium-forge` 生成回归测试;差异项打回 `d2h-native-library-build` 处置。
169
+
170
+ ## 六、检查单
171
+
172
+ - [ ] api-map 覆盖校验 0 缺失(或缺失项已记 inconsistent/unverifiable)
173
+ - [ ] bridge-required 库桥接层无业务逻辑(纯转发)
174
+ - [ ] 每条用例的证据文件存在(日志/截图)
175
+ - [ ] `android_expected` 的来源已注明(真机采集 / 源码推断)
176
+ - [ ] 差异清单三态齐备,无「猜测为一致」的条目
@@ -0,0 +1,163 @@
1
+ # Hypium 测试用例验证模板(断言式,替代按钮 UI demo)
2
+
3
+ 功能一致性验证的**推荐方式**:用 `@ohos/hypium` 写断言式测试用例,`hdc shell aa test` 无头运行。相比「验证页 + 按钮」的 UI demo,测试用例**可断言、可 CI、无 UI 交互、无响应式坑**(ArkTS `@State` 数组原地突变不刷新等)。
4
+
5
+ ## 一、工程结构
6
+
7
+ ```
8
+ entry/src/ohosTest/
9
+ ├── module.json5 # 测试模块(name 必须 "<模块名>_test")
10
+ └── ets/test/
11
+ ├── List.test.ets # testsuite 聚合
12
+ └── Ability.test.ets # 测试用例(断言)
13
+ ```
14
+
15
+ `entry/oh-package.json5` 加 devDependency:
16
+
17
+ ```json5
18
+ "devDependencies": { "@ohos/hypium": "1.0.25" }
19
+ ```
20
+
21
+ `entry/build-profile.json5` 的 `targets` 必须含 `ohosTest`(否则测试 HAP 不产出):
22
+
23
+ ```json5
24
+ "targets": [ { "name": "default" }, { "name": "ohosTest" } ]
25
+ ```
26
+
27
+ ## 二、测试用例写法(Ability.test.ets)
28
+
29
+ > **用例生成规则(按 Android 使用面全量闭合)**:用例不是挑「好测的」子集,而是从 Android 源码对 so/a 的**全部使用点**生成——每个功能域(命令执行/音频播放/视频滤镜/元数据帧提取/可视化/播放器引擎/推流)每条映射 → 一个 `it` 用例或一条 `unverifiable` 登记,数量对账闭合。无 API 的功能域显式写 `it('xxx_no_ohos_api', ...)` 断言缺失(或登记 functional_diffs `unverifiable`),使差异清单完整覆盖使用面。
30
+
31
+ ```typescript
32
+ import { describe, it, expect } from '@ohos/hypium';
33
+ import { FFmpeg } from '@sj/ffmpeg';
34
+
35
+ function run(cmd: string[]): Promise<string> {
36
+ return new Promise<string>((resolve) => {
37
+ let out = '';
38
+ FFmpeg.execute(cmd, {
39
+ logCallback: (msg: string) => { out += msg; },
40
+ outputCallback: (msg: string) => { out += msg; },
41
+ }).then(() => resolve(out)).catch(() => resolve(out));
42
+ });
43
+ }
44
+
45
+ export default function ffmpegTest() {
46
+ describe('FFmpegVerify', () => {
47
+ it('ffmpeg_executes_version', 0, async () => {
48
+ const out = await run(['ffmpeg', '-version']);
49
+ expect(out).assertContain('ffmpeg version');
50
+ });
51
+ // ...每个功能一个 it 用例,assertContain/assertEqual 断言实际输出
52
+ });
53
+ }
54
+ ```
55
+
56
+ `List.test.ets`:
57
+
58
+ ```typescript
59
+ import ffmpegTest from './Ability.test';
60
+ export default function testsuite() {
61
+ ffmpegTest();
62
+ }
63
+ ```
64
+
65
+ **常用用例模式**(按 Android 使用面):
66
+
67
+ ```typescript
68
+ // 音频播放(AudioPlayer.native_play → FFAudioPlayer)
69
+ it('audio_player_plays_wav', 0, async () => {
70
+ const player = new FFAudioPlayer();
71
+ player.url = `file://${filesDir}/test.wav`;
72
+ player.prepare();
73
+ await new Promise<void>((r) => setTimeout(r, 1500));
74
+ expect(player.duration > 0).assertTrue();
75
+ player.play();
76
+ await new Promise<void>((r) => setTimeout(r, 1000));
77
+ expect(player.playWhenReady).assertTrue();
78
+ player.pause();
79
+ });
80
+
81
+ // 帧提取/截图(FFmpegMediaRetriever.getFrameAtTime → ffmpeg 截帧,无原生 API 用命令变通)
82
+ it('frame_extract_creates_image', 0, async () => {
83
+ await run(['ffmpeg', '-f', 'lavfi', '-i', 'color=c=red:s=64x64:d=1', `${filesDir}/gen.mp4`, '-y']);
84
+ await run(['ffmpeg', '-ss', '0', '-i', `${filesDir}/gen.mp4`, '-frames:v', '1', `${filesDir}/frame.jpg`, '-y']);
85
+ // fs.accessSync 断言产物存在
86
+ });
87
+
88
+ // 取消(cancelTaskJni → FFAbortController,实测可能为真实差异——abort 未中断则断言失败=差异证据)
89
+ it('abort_cancels_task', 0, async () => {
90
+ const c = new FFAbortController();
91
+ const p = FFmpeg.execute([...转码...], { signal: c.signal });
92
+ setTimeout(() => c.abort(), 200);
93
+ try { await p; expect(false).assertTrue(); } catch (e) { /* 中断=符合预期 */ }
94
+ });
95
+ ```
96
+
97
+ 测试内获取 filesDir(rawfile 拷贝)用 `abilityDelegatorRegistry`(注意小写开头):
98
+
99
+ ```typescript
100
+ import { abilityDelegatorRegistry } from '@kit.TestKit';
101
+ const delegator = abilityDelegatorRegistry.getAbilityDelegator();
102
+ const ability = await delegator.getCurrentTopAbility();
103
+ const ctx = ability.context; // ctx.filesDir / ctx.resourceManager
104
+ ```
105
+
106
+ `module.json5`:
107
+
108
+ ```json5
109
+ {
110
+ "module": {
111
+ "name": "entry_test",
112
+ "type": "feature",
113
+ "deviceTypes": [ "phone" ],
114
+ "deliveryWithInstall": true,
115
+ "installationFree": false
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## 三、构建 · 安装 · 运行(全 CLI)
121
+
122
+ ```bash
123
+ export DEVECO_SDK_HOME="/Applications/DevEco-Studio.app/Contents/sdk"
124
+
125
+ # 应用 HAP
126
+ node <DevEco>/tools/hvigor/bin/hvigorw.js --mode module -p product=default assembleHap
127
+ # 测试 HAP(产物 entry/build/default/outputs/ohosTest/entry-ohosTest-signed.hap)
128
+ node <DevEco>/tools/hvigor/bin/hvigorw.js --mode module -p module=entry@ohosTest -p product=default assembleHap
129
+
130
+ hdc -t <设备> install -r entry-default-signed.hap
131
+ hdc -t <设备> install -r entry-ohosTest-signed.hap
132
+
133
+ hdc -t <设备> shell aa test -b <bundleName> -m <模块名>_test -s unittest OpenHarmonyTestRunner -s timeout 20000
134
+ ```
135
+
136
+ ## 四、结果判定
137
+
138
+ `aa test` 输出末尾:
139
+
140
+ ```
141
+ OHOS_REPORT_RESULT: stream=Tests run: 4, Failure: 0, Error: 0, Pass: 4, Ignore: 0
142
+ TestFinished-ResultCode: 0
143
+ ```
144
+
145
+ - `ResultCode: 0` 且 `Failure=0, Error=0` → 全部通过
146
+ - 断言失败 → `Failure>0` 且 `ResultCode` 非 0;结果作为 `functional_diffs[]` 的 `inconsistent` 证据
147
+
148
+ ## 五、与功能差异清单的衔接
149
+
150
+ **unverifiable 登记规范**(无 API 的功能域不写测试用例,但必须登记——三要素句式):
151
+
152
+ | functional_diffs 条目 | 值 |
153
+ |---|---|
154
+ | status | `unverifiable` |
155
+ | ohos_actual | `鸿蒙 so 未实现:未导出 <具体 NAPI 能力>` |
156
+ | note | 处置建议(自实现 / 系统能力替代如 AVPlayer / 裁剪) |
157
+
158
+ 闭合校验:`Hypium 用例数 + unverifiable 登记数 = api-map 条目数`。
159
+
160
+ 每个 `it` 用例对应 api-map 一个 API 的功能验证:
161
+ - 断言「输出含预期标记」→ 功能可用(`consistent`)
162
+ - 断言「不含某 Android 特定值」→ 版本/行为差异(`inconsistent`,如 `ffmpeg version 8` ≠ Android `6.0.1`)
163
+ - 文件级用例(转码产物存在、元数据字段)→ rawfile 拷贝到 `filesDir` 后断言