blob: fff9c832c5f9b1d5cb5eb874d9a5e05fd120b89e [file] [edit]
############################################################################
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
############################################################################
"""Test scontrol write config keeps values with spaces or parentheses whole.
scontrol write config strips annotations such as the " sec" in "60 sec" from
the values reported by scontrol show config. That must not cut a value that
legitimately contains a space, like the arguments of ResumeFailProgram or the
default of InteractiveStepOptions, or a "(", like the "name(address)" form of
SlurmctldHost.
"""
import re
import socket
import pytest
import atf
# (name written to the file, name in scontrol show config)
PARAMETERS = [
("InteractiveStepOptions", "InteractiveStepOptions"),
("ResumeFailProgram", "ResumeFailProgram"),
("SlurmctldHost", "SlurmctldHost[0]"),
]
@pytest.fixture(scope="module", autouse=True)
def setup():
atf.require_version(
(26, 11),
"bin/scontrol",
reason="write config truncated these values before 26.11",
)
# InteractiveStepOptions needs no setting: its default has spaces
atf.require_config_parameter("ResumeFailProgram", '"/bin/true --fail"')
# A controller address that differs from its name is written as
# "name(address)". The host's own address keeps the cluster working.
host = atf.get_config_parameter("SlurmctldHost", live=False).split("(")[0]
atf.require_config_parameter(
"SlurmctldHost", f"{host}({socket.gethostbyname(host)})"
)
atf.require_slurm_running()
@pytest.fixture(scope="module")
def shown():
return atf.get_config()
@pytest.fixture(scope="module")
def written():
output = atf.run_command_output("scontrol write config slurm.conf.out", fatal=True)
match = re.search(r"Slurm config saved to (\S+)", output)
assert match, f"scontrol write config did not report a file: {output}"
content = atf.run_command_output(f"cat {match.group(1)}", fatal=True)
# SlurmctldHost is repeated for backup controllers: keep the first, which
# is the primary
lines = {}
for name, value in re.findall(r"^([A-Za-z]+)=(.*)$", content, re.MULTILINE):
lines.setdefault(name, value)
return lines
@pytest.mark.parametrize("written_name,shown_name", PARAMETERS)
def test_value_written_whole(shown, written, written_name, shown_name):
"""The value is written exactly as scontrol show config reports it"""
value = shown[shown_name]
assert re.search(
r"[ (]", value
), f"{shown_name} ({value!r}) has no space or parenthesis to be cut at"
assert written_name in written, f"{written_name} was not written"
# Values containing spaces must be written inside double quotes
expected = f'"{value}"' if " " in value else value
assert (
written[written_name] == expected
), f"write config wrote {written[written_name]!r}, show config reports {value!r}"