diff --git a/CHANGELOG.md b/CHANGELOG.md index b2692a2..3b5b35e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- The `Reason` column asks `squeue` for `%r` rather than the dual-purpose `%R`, which returns the allocated nodes for anything that is not pending or failed. It now says why a job is in its current state instead of repeating the `Node` column on every running row, sorting by it sorts by the reason, and a job with no reason shows the usual `-` instead of `None`. + ## [0.2.0] - 2026-08-28 ### Added diff --git a/src/backend/query.rs b/src/backend/query.rs index 6267d92..af2c3ba 100644 --- a/src/backend/query.rs +++ b/src/backend/query.rs @@ -192,7 +192,11 @@ pub fn decode_squeue_output(raw: &str, fmt: &str) -> Vec { "%V" => job.submit_time = Some(val), "%S" => job.start_time = Some(val), "%e" => job.end_time = Some(val), - "%R" => job.reason = Some(val), + // squeue prints "None" when a job has no reason, which the + // table shows as the same "-" placeholder as any other unset + // cell. %R is deliberately not accepted here: it returns the + // nodelist for anything that is not pending or failed. + "%r" => job.reason = (val != "None").then_some(val), _ => {} } } diff --git a/src/views/fields.rs b/src/views/fields.rs index b7df8b4..ef9feb4 100644 --- a/src/views/fields.rs +++ b/src/views/fields.rs @@ -74,7 +74,7 @@ impl JobField { JobField::SubmitTime => "%V", JobField::StartTime => "%S", JobField::EndTime => "%e", - JobField::PendReason => "%R", + JobField::PendReason => "%r", } } diff --git a/tests/parsing.rs b/tests/parsing.rs index 705001f..c708b87 100644 --- a/tests/parsing.rs +++ b/tests/parsing.rs @@ -59,7 +59,7 @@ fn blank_lines_are_skipped() { } /// Every column the UI can request must decode into a field; otherwise the -/// column silently renders empty (the bug the `%R` column once had). +/// column silently renders empty. #[test] fn every_field_code_is_decoded() { for field in JobField::enumerate() { @@ -77,3 +77,26 @@ fn every_field_code_is_decoded() { ); } } + +/// `%R` is dual purpose: for a running job it returns the allocated nodes, +/// so a Reason column built on it just repeats the Node column. +#[test] +fn the_reason_column_asks_for_the_single_purpose_code() { + assert_eq!(JobField::PendReason.format_code(), "%r"); +} + +#[test] +fn reason_decodes_for_a_pending_job() { + let raw = line(&["1002", "N/A", "Priority"]); + let jobs = decode_squeue_output(&raw, &["%i", "%N", "%r"].join(FIELD_SEP)); + assert_eq!(jobs[0].reason.as_deref(), Some("Priority")); +} + +/// `squeue` writes "None" rather than an empty cell for a job with no reason, +/// and the table shows an unset reason as the same "-" as any other blank. +#[test] +fn a_reason_of_none_reads_as_unset() { + let raw = line(&["1001", "None"]); + let jobs = decode_squeue_output(&raw, &["%i", "%r"].join(FIELD_SEP)); + assert_eq!(jobs[0].reason, None); +}