Public release release-7fcdc75d8eaf
Source commit: 7fcdc75d8eaf4dc03b09086568dbb184f903f6a4 Public tree identity: sha256:de9eec9b4e2f4cba2fa32b6ecb4b3424cce793a0f8a969707cc9c4565acdbb4e
This commit is contained in:
parent
2a0f7ded04
commit
f4590ca576
10 changed files with 521 additions and 109 deletions
|
|
@ -1208,10 +1208,12 @@ fn emit_runtime_outcome(
|
|||
)?;
|
||||
}
|
||||
RuntimeContinuationOutcome::Terminal(record) => {
|
||||
let exit_code = record.status_code.unwrap_or(1);
|
||||
apply_runtime_record_with_thread_events(writer, state, record)?;
|
||||
if state.last_task_failed {
|
||||
writer.output("stderr", format!("{}\n", state.command_status))?;
|
||||
}
|
||||
writer.event("exited", json!({ "exitCode": exit_code }))?;
|
||||
writer.event("terminated", json!({}))?;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use breakpoints::{
|
|||
#[cfg(test)]
|
||||
use dap_protocol::{initialize_capabilities, read_message};
|
||||
#[cfg(test)]
|
||||
use runtime_client::{client_user_request, parse_task_restart_response};
|
||||
use runtime_client::{client_user_request, parse_task_restart_response, whole_process_status_code};
|
||||
#[cfg(test)]
|
||||
use variables::variables_response;
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1164,6 +1164,8 @@ pub(crate) fn observe_services_runtime(
|
|||
};
|
||||
if !has_current_runtime_task(&task_snapshots) {
|
||||
if let RuntimeContinuationOutcome::Terminal(record) = &mut outcome {
|
||||
record.status_code =
|
||||
whole_process_status_code(record.status_code, &task_snapshots);
|
||||
record.node_report = json!({
|
||||
"terminal_event": record.node_report.get("terminal_event"),
|
||||
"task_snapshots": task_snapshots,
|
||||
|
|
@ -1368,6 +1370,8 @@ pub(crate) fn observe_services_runtime(
|
|||
};
|
||||
if !has_current_runtime_task(&task_snapshots) {
|
||||
if let RuntimeContinuationOutcome::Terminal(record) = &mut outcome {
|
||||
record.status_code =
|
||||
whole_process_status_code(record.status_code, &task_snapshots);
|
||||
record.node_report = json!({
|
||||
"terminal_event": record.node_report.get("terminal_event"),
|
||||
"task_snapshots": task_snapshots,
|
||||
|
|
@ -1543,6 +1547,39 @@ fn has_current_runtime_task(task_snapshots: &Value) -> bool {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn whole_process_status_code(
|
||||
main_status_code: Option<i32>,
|
||||
task_snapshots: &Value,
|
||||
) -> Option<i32> {
|
||||
let main_status_code = main_status_code?;
|
||||
if main_status_code != 0 {
|
||||
return Some(main_status_code);
|
||||
}
|
||||
|
||||
let snapshots = task_snapshots.get("snapshots").and_then(Value::as_array)?;
|
||||
for snapshot in snapshots
|
||||
.iter()
|
||||
.filter(|snapshot| snapshot.get("current").and_then(Value::as_bool) == Some(true))
|
||||
{
|
||||
match snapshot.get("state").and_then(Value::as_str) {
|
||||
Some("completed") => {}
|
||||
Some("failed" | "cancelled" | "failed_awaiting_action") => {
|
||||
return Some(
|
||||
snapshot
|
||||
.get("status_code")
|
||||
.and_then(Value::as_i64)
|
||||
.and_then(|status| i32::try_from(status).ok())
|
||||
.filter(|status| *status != 0)
|
||||
.unwrap_or(1),
|
||||
);
|
||||
}
|
||||
Some("queued" | "running") => return None,
|
||||
_ => return Some(1),
|
||||
}
|
||||
}
|
||||
Some(0)
|
||||
}
|
||||
|
||||
fn terminal_runtime_outcome(
|
||||
coordinator: &str,
|
||||
state: &AdapterState,
|
||||
|
|
|
|||
|
|
@ -922,6 +922,98 @@ fn detects_current_failed_attempt_awaiting_operator_action() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn whole_process_terminal_status_covers_every_current_logical_task_attempt() {
|
||||
let snapshots = |items: serde_json::Value| json!({ "snapshots": items });
|
||||
|
||||
assert_eq!(
|
||||
whole_process_status_code(
|
||||
Some(0),
|
||||
&snapshots(json!([
|
||||
{
|
||||
"task": "child",
|
||||
"current": true,
|
||||
"state": "completed",
|
||||
"status_code": 0
|
||||
}
|
||||
]))
|
||||
),
|
||||
Some(0),
|
||||
"a successful main and final child must succeed"
|
||||
);
|
||||
for (state, status_code) in [("failed", 23), ("cancelled", 1)] {
|
||||
assert_eq!(
|
||||
whole_process_status_code(
|
||||
Some(0),
|
||||
&snapshots(json!([
|
||||
{
|
||||
"task": "child",
|
||||
"current": true,
|
||||
"state": state,
|
||||
"status_code": status_code
|
||||
}
|
||||
]))
|
||||
),
|
||||
Some(status_code),
|
||||
"a terminal child must determine the whole-process result"
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
whole_process_status_code(
|
||||
Some(0),
|
||||
&snapshots(json!([
|
||||
{
|
||||
"task": "accepted-failure",
|
||||
"current": true,
|
||||
"state": "failed",
|
||||
"command_state": "failure_accepted",
|
||||
"status_code": 17
|
||||
}
|
||||
]))
|
||||
),
|
||||
Some(17),
|
||||
"accepting an AwaitOperator failure releases the process but does not turn failure into success"
|
||||
);
|
||||
assert_eq!(
|
||||
whole_process_status_code(
|
||||
Some(0),
|
||||
&snapshots(json!([
|
||||
{
|
||||
"task": "restarted",
|
||||
"attempt_id": "attempt-1",
|
||||
"current": false,
|
||||
"state": "failed",
|
||||
"status_code": 7
|
||||
},
|
||||
{
|
||||
"task": "restarted",
|
||||
"attempt_id": "attempt-2",
|
||||
"current": true,
|
||||
"state": "completed",
|
||||
"status_code": 0
|
||||
}
|
||||
]))
|
||||
),
|
||||
Some(0),
|
||||
"a successful replacement attempt is authoritative over stale failed attempts"
|
||||
);
|
||||
assert_eq!(
|
||||
whole_process_status_code(
|
||||
Some(9),
|
||||
&snapshots(json!([
|
||||
{
|
||||
"task": "child",
|
||||
"current": true,
|
||||
"state": "completed",
|
||||
"status_code": 0
|
||||
}
|
||||
]))
|
||||
),
|
||||
Some(9),
|
||||
"a failed coordinator main cannot be masked by successful children"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_task_instance_ids_keep_stable_dap_threads_across_snapshot_reordering_and_retry() {
|
||||
let mut state = AdapterState::default();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue