feat: 编辑器实时探测并显示已关联虚拟环境
fix: 按系统 ANSI 代码页解码子进程输出,修复中文日志乱码
This commit is contained in:
@@ -719,3 +719,105 @@ async fn scheduler_restores_enabled_tasks_after_restart() {
|
||||
second_state.shutdown();
|
||||
second_pool.close().await;
|
||||
}
|
||||
|
||||
async fn run_command_task(app: &TestApp, id: &str, args: Vec<&str>) -> i64 {
|
||||
let working_dir = app._temp_dir.path().to_string_lossy().into_owned();
|
||||
let task = script_task(id, &working_dir, "cmd.exe", "/C", args, "0 0 2 * * *", 10);
|
||||
let create_response = send_json(&app.router, Method::POST, "/api/v1/tasks", Some(task)).await;
|
||||
assert_eq!(create_response.status(), StatusCode::CREATED);
|
||||
let run_response = send_json(
|
||||
&app.router,
|
||||
Method::POST,
|
||||
&format!("/api/v1/tasks/{id}/run"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(run_response.status(), StatusCode::ACCEPTED);
|
||||
response_json(run_response).await["id"]
|
||||
.as_i64()
|
||||
.expect("run id should be an integer")
|
||||
}
|
||||
|
||||
async fn read_log_chunk(app: &TestApp, run_id: i64, offset: u64) -> Value {
|
||||
let response = send_json(
|
||||
&app.router,
|
||||
Method::GET,
|
||||
&format!("/api/v1/runs/{run_id}/logs?offset={offset}"),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
|
||||
response_json(response).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn gbk_encoded_output_is_decoded_to_utf8() {
|
||||
let app = test_app().await;
|
||||
// 固定的 GBK 字节只在中日韩等使用该代码页的系统上才有意义。
|
||||
let encoding = task_pilot_backend::runs::executor::OutputEncoding::from_env();
|
||||
if encoding.name() != "GBK" {
|
||||
return;
|
||||
}
|
||||
|
||||
let payload = app._temp_dir.path().join("gbk.txt");
|
||||
// "中文" 的 GBK 编码
|
||||
std::fs::write(&payload, [0xD6, 0xD0, 0xCE, 0xC4]).expect("GBK fixture should be written");
|
||||
let run_id = run_command_task(&app, "gbk_task", vec!["type", "gbk.txt"]).await;
|
||||
|
||||
let completed = wait_for_run(&app.router, run_id).await;
|
||||
assert_eq!(completed["status"], "succeeded");
|
||||
let log = read_log_chunk(&app, run_id, 0).await;
|
||||
let content = log["content"].as_str().expect("log content should be text");
|
||||
|
||||
assert!(
|
||||
content.contains("中文"),
|
||||
"unexpected log content: {content}"
|
||||
);
|
||||
assert!(!content.contains('\u{fffd}'));
|
||||
app.state.shutdown();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn long_utf8_log_is_reassembled_across_chunk_boundaries() {
|
||||
let app = test_app().await;
|
||||
let payload = app._temp_dir.path().join("long.txt");
|
||||
let body = format!("{}\n尾巴\n", "中".repeat(30_000));
|
||||
std::fs::write(&payload, body.as_bytes()).expect("UTF-8 fixture should be written");
|
||||
let run_id = run_command_task(&app, "long_log_task", vec!["type", "long.txt"]).await;
|
||||
|
||||
let completed = wait_for_run(&app.router, run_id).await;
|
||||
assert_eq!(completed["status"], "succeeded");
|
||||
|
||||
let first = read_log_chunk(&app, run_id, 0).await;
|
||||
assert_eq!(
|
||||
first["complete"], false,
|
||||
"a terminal run must not report completion before the whole log is read"
|
||||
);
|
||||
let mut content = first["content"].as_str().unwrap_or_default().to_owned();
|
||||
let mut offset = first["next_offset"]
|
||||
.as_u64()
|
||||
.expect("next_offset should be an integer");
|
||||
|
||||
for _ in 0..20 {
|
||||
let chunk = read_log_chunk(&app, run_id, offset).await;
|
||||
content.push_str(chunk["content"].as_str().unwrap_or_default());
|
||||
offset = chunk["next_offset"]
|
||||
.as_u64()
|
||||
.expect("next_offset should be an integer");
|
||||
if chunk["complete"].as_bool().unwrap_or(false) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(
|
||||
!content.contains('\u{fffd}'),
|
||||
"log lost a character boundary"
|
||||
);
|
||||
assert!(content.contains("尾巴"));
|
||||
assert_eq!(
|
||||
content.len(),
|
||||
usize::try_from(offset).expect("offset fits usize")
|
||||
);
|
||||
app.state.shutdown();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user