脚本没启动归为宿主侧故障而不是命令失败
export/draft/xhs_minitool/build.rs:conclude 增加 script_launched 判据,用 target_exec != established 区分「根本没跑起来」与「跑了被信号杀掉」;前者报 exportUnavailable,由前端既有路径抛进错误池上报,后者维持 commandFailed。补对应单测。
This commit is contained in:
@@ -49,16 +49,16 @@ pub(crate) async fn run(
|
||||
.await
|
||||
.map_err(ExportUnavailable::new)?;
|
||||
|
||||
// `target_exec != "established"` 就是「脚本根本没跑起来」:沙箱建不起来(`not-attempted`)或目标
|
||||
// 可执行文件执行失败(`failed`)。这跟「脚本跑了但被信号杀掉」(`exit_code` 同样是 None,但
|
||||
// `target_exec` 是 `established`)不是一回事,所以判据用它,而不是 `exit_code.is_none()`。
|
||||
let script_launched = result.target_exec == "established";
|
||||
let exit_code = result.exit_code;
|
||||
let timed_out = result.timed_out;
|
||||
let output = result.output;
|
||||
|
||||
// 收尾判产物同样是同步 IO(symlink_metadata),一样搬去阻塞线程池。
|
||||
offload(move || {
|
||||
conclude(
|
||||
&root,
|
||||
result.exit_code,
|
||||
result.timed_out,
|
||||
&result.output,
|
||||
)
|
||||
})
|
||||
.await
|
||||
offload(move || conclude(&root, exit_code, timed_out, script_launched, &output)).await
|
||||
}
|
||||
|
||||
/// 构建前置(同步):找到脚本、确认权限点位没被拒、清掉上一次的产物。
|
||||
@@ -99,16 +99,28 @@ fn prepare(root: &Path) -> Result<NpmScript, XHSMiniToolExportBuildError> {
|
||||
Ok(script)
|
||||
}
|
||||
|
||||
/// 把宿主的构建结果收成导出结果:先判成功,再确认产物真的在。
|
||||
/// 把宿主的构建结果收成导出结果:先判脚本有没有真的跑起来,再判成功,最后确认产物真的在。
|
||||
///
|
||||
/// 只吃三个字段而不是整份 `ProjectVerificationResult`:这条判据是导出链路里唯一"脚本跑完之后
|
||||
/// 算不算数"的规则,抽成纯函数才能脱离"真的跑一遍 npm"逐条测(非零退出、超时、产物缺失、成功)。
|
||||
/// 只吃四个字段而不是整份 `ProjectVerificationResult`:这条判据是导出链路里唯一"脚本跑完之后
|
||||
/// 算不算数"的规则,抽成纯函数才能脱离"真的跑一遍 npm"逐条测(未启动、非零退出、超时、产物缺失、
|
||||
/// 成功)。
|
||||
fn conclude(
|
||||
root: &Path,
|
||||
exit_code: Option<i32>,
|
||||
timed_out: bool,
|
||||
script_launched: bool,
|
||||
output: &str,
|
||||
) -> Result<XHSMiniToolExportRunResult, XHSMiniToolExportBuildError> {
|
||||
if !script_launched {
|
||||
// 脚本根本没跑起来(沙箱建不起来 / 目标可执行文件执行失败):这是宿主侧事实故障,
|
||||
// 不是项目代码的毛病。报 `exportUnavailable` 让前端按宿主故障处理(并抛进错误池上报),
|
||||
// 而不是 `commandFailed` 去引导 code agent 改一份它改不动的宿主环境。
|
||||
let (output_tail, _) = tail(output);
|
||||
return Err(XHSMiniToolExportBuildError::ExportUnavailable(
|
||||
ExportUnavailable::new(output_tail),
|
||||
));
|
||||
}
|
||||
|
||||
if timed_out || exit_code != Some(0) {
|
||||
let (output_tail, omitted_characters) = tail(output);
|
||||
return Err(XHSMiniToolExportBuildError::CommandFailed(CommandFailed {
|
||||
@@ -216,7 +228,7 @@ mod tests {
|
||||
fn a_non_zero_exit_keeps_the_code_and_the_output_tail() {
|
||||
let root = fixture_root("non-zero");
|
||||
|
||||
let error = conclude(&root, Some(1), false, "vite 构建失败\n入口不存在")
|
||||
let error = conclude(&root, Some(1), false, true, "vite 构建失败\n入口不存在")
|
||||
.expect_err("non-zero exit must fail");
|
||||
let XHSMiniToolExportBuildError::CommandFailed(failure) = error else {
|
||||
panic!("非零退出必须映射成 commandFailed");
|
||||
@@ -230,7 +242,7 @@ mod tests {
|
||||
fn a_timeout_still_reports_command_failed_without_an_exit_code() {
|
||||
let root = fixture_root("timeout");
|
||||
|
||||
let error = conclude(&root, None, true, "已取消")
|
||||
let error = conclude(&root, None, true, true, "已取消")
|
||||
.expect_err("timeout must fail even with exit code 0");
|
||||
let XHSMiniToolExportBuildError::CommandFailed(failure) = error else {
|
||||
panic!("超时必须映射成 commandFailed");
|
||||
@@ -244,7 +256,7 @@ mod tests {
|
||||
fn a_successful_script_without_the_artifact_is_reported_as_missing() {
|
||||
let root = fixture_root("no-artifact");
|
||||
|
||||
let error = conclude(&root, Some(0), false, "构建完成").expect_err("产物缺失必须失败");
|
||||
let error = conclude(&root, Some(0), false, true, "构建完成").expect_err("产物缺失必须失败");
|
||||
let XHSMiniToolExportBuildError::ArtifactMissing(missing) = error else {
|
||||
panic!("没有 zip 必须映射成 artifactMissing");
|
||||
};
|
||||
@@ -257,7 +269,7 @@ mod tests {
|
||||
write_artifact(&root, b"");
|
||||
|
||||
assert!(matches!(
|
||||
conclude(&root, Some(0), false, "构建完成"),
|
||||
conclude(&root, Some(0), false, true, "构建完成"),
|
||||
Err(XHSMiniToolExportBuildError::ArtifactMissing(_))
|
||||
));
|
||||
}
|
||||
@@ -274,7 +286,7 @@ mod tests {
|
||||
|
||||
assert!(
|
||||
matches!(
|
||||
conclude(&root, Some(0), false, "构建完成"),
|
||||
conclude(&root, Some(0), false, true, "构建完成"),
|
||||
Err(XHSMiniToolExportBuildError::ArtifactMissing(_))
|
||||
),
|
||||
"指向项目外非空文件的链接不能算产出成功"
|
||||
@@ -290,6 +302,7 @@ mod tests {
|
||||
&root,
|
||||
Some(0),
|
||||
false,
|
||||
true,
|
||||
"zip 已生成:.export/xhs-minitool.zip(3 个条目)",
|
||||
)
|
||||
.expect("产物在就应当成功");
|
||||
@@ -297,6 +310,20 @@ mod tests {
|
||||
assert_eq!(result.omitted_characters, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_script_that_never_launched_is_host_unavailable_not_command_failed() {
|
||||
let root = fixture_root("not-launched");
|
||||
|
||||
// 沙箱建不起来或目标可执行文件执行失败:`exit_code` 同样是 None,但脚本根本没跑,
|
||||
// 必须报宿主侧故障,不能让「让陶泥儿修」的按钮出现在一个它改不动的环境问题上。
|
||||
let error = conclude(&root, None, false, false, "命令沙箱不可用")
|
||||
.expect_err("脚本没跑起来必须失败");
|
||||
let XHSMiniToolExportBuildError::ExportUnavailable(unavailable) = error else {
|
||||
panic!("脚本未启动必须映射成 exportUnavailable");
|
||||
};
|
||||
assert_eq!(unavailable.cause, "命令沙箱不可用");
|
||||
}
|
||||
|
||||
/// 真起进程跑一遍宿主链路:解析 npm 包、走 `command.exec` 边界执行项目脚本、再按产物判据收尾。
|
||||
///
|
||||
/// 默认 `#[ignore]`:它要本机 `node` / `npm` 与可用的命令沙箱,慢且依赖宿主环境,不进常规单测。
|
||||
|
||||
Reference in New Issue
Block a user