// 使用真实 Sink 实现及 COM 生效函数;仅替换时钟、继电器和南向传输。 // node tests/sink_init/run.cjs [gcc路径]。临时编译产物不写入固件工程。 const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const cp = require('node:child_process'); const root = path.resolve(__dirname, '../..'); const app = path.join(root, 'apps/chrg/applications'); const cc = process.argv[2] || process.env.CC || 'gcc'; if (path.isAbsolute(cc)) process.env.PATH = path.dirname(cc) + path.delimiter + process.env.PATH; const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'chrg-sink-test-')); const source = fs.readFileSync(path.join(app, 'thread/chrg_comm.c'), 'utf8'); function definition(name) { const pattern = new RegExp('static\\s+[\\w]+\\s+' + name + '\\s*\\([^;{]*\\)\\s*\\{', 'g'); const match = pattern.exec(source); if (!match) return ''; let depth = 1, end = pattern.lastIndex; while (depth && end < source.length) { if (source[end] === '{') depth++; if (source[end] === '}') depth--; end++; } return source.slice(match.index, end); } const type = source.match(/typedef struct\s*\{[^}]*\}\s*chrg_param_diff_t;/)[0]; const functions = ['chrg_comm_get_param_diff', 'chrg_comm_sink_config_equal', 'chrg_comm_copy_sink_config', 'chrg_comm_handle_sink_load_current', 'chrg_sink_diff_send_reg', 'chrg_comm_apply_sink_effect', 'chrg_mb_write_sys']; const roll = fs.readFileSync(path.join(app, 'thread/chrg_roll_nor.c'), 'utf8'); const dispatch = roll.slice(roll.indexOf('void chrg_roll_nor_thread_entry')) .match(/if \(\(pSW->id == ID_SOURCE\)[\s\S]+?state_work_ret = chrg_sink_work[\s\S]+?\n\t\t\t\}/)[0]; const unit = '#include "' + path.join(app, 'utils/chrg_sink.c').replaceAll('\\', '/') + '"\n' + '#include "chrg_thread.h"\n#define LOG_E(...) ((void)0)\n' + 'static rt_uint8_t g_shadow_dirty[4], last_pd_pro[4];\n#define SOURCE_PD_STATE_UNKNOWN 255\n' + 'static eMBException chrg_comm_apply_source_effect(rt_uint8_t ch, struct chrg_switch_t *p, const struct chrg_switch_t *s);\n' + type + '\n' + functions.map(definition).join('\n') + '\n' + 'static int poll_state(struct chrg_switch_t *pSW) { int idx=0, state_work_ret=0;\n' + dispatch + '\nreturn state_work_ret; }\n' + fs.readFileSync(path.join(__dirname, 'cases.c'), 'utf8'); const scenarios = ['duplicate_exit', 'preserve_runtime', 'busy_changed_config', 'recover_init', 'stop_during_init', 'first_submit_failure', 'stage_submit_failure', 'failed_batch_retry', 'complete_then_restart', 'duplicate_version', 'busy_version', 'stop_submit_retry', 'rest_submit_failure', 'role_roundtrip']; try { fs.writeFileSync(path.join(dir, 'test.c'), unit); const exe = path.join(dir, process.platform === 'win32' ? 'test.exe' : 'test'); const args = ['-std=c99', '-O0', '-ffunction-sections', '-fdata-sections', '-Werror=implicit-function-declaration', '-Wl,--gc-sections', '-I' + __dirname, ...['utils', 'thread', 'bsp'].map(p => '-I' + path.join(app, p)), path.join(dir, 'test.c'), '-o', exe]; const build = cp.spawnSync(cc, args, {encoding: 'utf8'}); if (build.status !== 0) throw Error(build.stderr || String(build.error || build.status)); let failed = 0; for (const name of scenarios) { const run = cp.spawnSync(exe, [name], {encoding: 'utf8'}); console.log((run.status === 0 ? 'PASS ' : 'FAIL ') + name); if (run.status !== 0) { failed++; console.log(run.stdout + run.stderr); } } console.log(`${scenarios.length - failed}/${scenarios.length} passed`); process.exitCode = failed ? 1 : 0; } finally { fs.rmSync(dir, {recursive: true, force: true}); }