| ############################################################################ | |
| # Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| ############################################################################ | |
| """Test sinfo --states name parsing (ticket 25499).""" | |
| import pytest | |
| import atf | |
| # Setup | |
| @pytest.fixture(scope="module", autouse=True) | |
| def setup(): | |
| atf.require_slurm_running() | |
| # REBOOT_REQUESTED/REBOOT_ISSUED only parse as of 26.05 (ticket 25499); share | |
| # the gate between both params so the version boundary lives in one place. | |
| reboot_name_skip = pytest.mark.skipif( | |
| atf.get_version("bin/sinfo") < (26, 5), | |
| reason="Ticket 25499: REBOOT_REQUESTED/REBOOT_ISSUED were not " | |
| "accepted by --states", | |
| ) | |
| @pytest.mark.skipif( | |
| atf.get_version("bin/sinfo") < (25, 11), | |
| reason="Ticket 23752: sinfo prints an error and exits non-zero on an " | |
| "invalid --states value", | |
| ) | |
| # npc/perfctrs are the removed PERFCTRS state: rejected on every supported | |
| # version, so this pins the removal against a future re-add. | |
| @pytest.mark.parametrize("state", ["NOTASTATE", "npc", "perfctrs"]) | |
| def test_states_invalid_rejected(state): | |
| """Verify sinfo rejects unknown and removed --states values""" | |
| error = atf.run_command_error(f"sinfo --states={state}", fatal=True, xfail=True) | |
| assert ( | |
| "Bad state string" in error | |
| ), f"sinfo should report a 'Bad state string' error for --states={state}" | |
| def test_states_all_accepted(): | |
| """Verify sinfo --states=all is accepted""" | |
| exit_code = atf.run_command_exit("sinfo --states=all") | |
| assert exit_code == 0, "sinfo should accept --states=all" | |
| @pytest.mark.parametrize( | |
| "state", | |
| [ | |
| # Compact aliases users filtered with before the fix; these parse on | |
| # every supported version, so they guard against a refactor dropping | |
| # the hand-maintained special cases that live outside the state table. | |
| "reboot", | |
| "BOOT", | |
| pytest.param("REBOOT_REQUESTED", marks=reboot_name_skip), | |
| pytest.param("REBOOT_ISSUED", marks=reboot_name_skip), | |
| ], | |
| ) | |
| def test_states_reboot_names(state): | |
| """Verify the compact and documented reboot --states names parse""" | |
| exit_code = atf.run_command_exit(f"sinfo --states={state}") | |
| assert exit_code == 0, f"sinfo should accept --states={state}" | |
| @pytest.mark.skipif( | |
| atf.get_version("bin/sinfo") < (26, 11), | |
| reason="Ticket 25499: --helpstate and --states are table-driven (kept in " | |
| "sync) as of 26.11", | |
| ) | |
| def test_states_helpstate_roundtrip(): | |
| """Verify every node state sinfo --helpstate advertises is accepted by --states. | |
| --helpstate lists only the canonical names (the compact aliases are omitted | |
| by design), so this guards the advertised canonical names and the parser | |
| against drifting apart; it does not cover the compact aliases. | |
| The list and parser are driven from the same table as of 26.11. | |
| """ | |
| states = atf.run_command_output("sinfo --helpstate", fatal=True).split() | |
| assert states, "sinfo --helpstate should list node states" | |
| for state in states: | |
| exit_code = atf.run_command_exit(f"sinfo --states={state}") | |
| assert exit_code == 0, f"sinfo should accept --states={state}" |