|
| 1 | +# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. |
| 2 | +# SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +"""Tests for validate_setup pre-flight check and Makefile arch detection.""" |
| 5 | + |
| 6 | +import subprocess |
| 7 | +from pathlib import Path |
| 8 | + |
| 9 | +import pytest |
| 10 | + |
| 11 | +from srtctl.cli.submit import validate_setup |
| 12 | + |
| 13 | + |
| 14 | +class TestValidateSetup: |
| 15 | + """Tests for the validate_setup function.""" |
| 16 | + |
| 17 | + def test_passes_when_all_binaries_exist(self, tmp_path: Path): |
| 18 | + """validate_setup succeeds when all required binaries are present.""" |
| 19 | + (tmp_path / "configs").mkdir() |
| 20 | + (tmp_path / "configs" / "nats-server").touch() |
| 21 | + (tmp_path / "configs" / "etcd").touch() |
| 22 | + (tmp_path / "bin").mkdir() |
| 23 | + (tmp_path / "bin" / "uv").touch() |
| 24 | + |
| 25 | + # Should not raise |
| 26 | + validate_setup(tmp_path) |
| 27 | + |
| 28 | + def test_fails_when_nats_missing(self, tmp_path: Path): |
| 29 | + """validate_setup fails when nats-server is missing.""" |
| 30 | + (tmp_path / "configs").mkdir() |
| 31 | + (tmp_path / "configs" / "etcd").touch() |
| 32 | + (tmp_path / "bin").mkdir() |
| 33 | + (tmp_path / "bin" / "uv").touch() |
| 34 | + |
| 35 | + with pytest.raises(SystemExit): |
| 36 | + validate_setup(tmp_path) |
| 37 | + |
| 38 | + def test_fails_when_etcd_missing(self, tmp_path: Path): |
| 39 | + """validate_setup fails when etcd is missing.""" |
| 40 | + (tmp_path / "configs").mkdir() |
| 41 | + (tmp_path / "configs" / "nats-server").touch() |
| 42 | + (tmp_path / "bin").mkdir() |
| 43 | + (tmp_path / "bin" / "uv").touch() |
| 44 | + |
| 45 | + with pytest.raises(SystemExit): |
| 46 | + validate_setup(tmp_path) |
| 47 | + |
| 48 | + def test_fails_when_uv_missing(self, tmp_path: Path): |
| 49 | + """validate_setup fails when bin/uv is missing.""" |
| 50 | + (tmp_path / "configs").mkdir() |
| 51 | + (tmp_path / "configs" / "nats-server").touch() |
| 52 | + (tmp_path / "configs" / "etcd").touch() |
| 53 | + |
| 54 | + with pytest.raises(SystemExit): |
| 55 | + validate_setup(tmp_path) |
| 56 | + |
| 57 | + def test_fails_when_all_missing(self, tmp_path: Path): |
| 58 | + """validate_setup fails when nothing has been set up.""" |
| 59 | + with pytest.raises(SystemExit): |
| 60 | + validate_setup(tmp_path) |
| 61 | + |
| 62 | + |
| 63 | +class TestMakefileArchDetection: |
| 64 | + """Test that the file | grep pattern used in Makefile matches correctly. |
| 65 | +
|
| 66 | + The Makefile uses `file <binary> | grep -q "$ARCH_FILE_PATTERN"` to check |
| 67 | + if an existing binary matches the requested architecture. These tests verify |
| 68 | + the pattern works by creating minimal ELF binaries and checking `file` output. |
| 69 | + """ |
| 70 | + |
| 71 | + # Minimal ELF headers: just enough for `file` to identify the architecture |
| 72 | + # ELF magic + class(64-bit) + data(little-endian) + version + OS/ABI + padding + type + machine |
| 73 | + ELF_X86_64 = b"\x7fELF\x02\x01\x01\x00" + b"\x00" * 8 + b"\x02\x00\x3e\x00" |
| 74 | + ELF_AARCH64 = b"\x7fELF\x02\x01\x01\x00" + b"\x00" * 8 + b"\x02\x00\xb7\x00" |
| 75 | + |
| 76 | + @staticmethod |
| 77 | + def _file_description(path: Path) -> str: |
| 78 | + """Get just the description part of file(1) output (after the colon).""" |
| 79 | + result = subprocess.run(["file", str(path)], capture_output=True, text=True) |
| 80 | + return result.stdout.split(":", 1)[1] if ":" in result.stdout else result.stdout |
| 81 | + |
| 82 | + def test_file_detects_x86_64(self, tmp_path: Path): |
| 83 | + """file(1) description for x86_64 ELF contains 'x86-64' (hyphen, not underscore).""" |
| 84 | + binary = tmp_path / "fake_bin" |
| 85 | + binary.write_bytes(self.ELF_X86_64 + b"\x00" * 44) |
| 86 | + desc = self._file_description(binary) |
| 87 | + assert "x86-64" in desc, f"Expected 'x86-64' in: {desc}" |
| 88 | + assert "x86_64" not in desc, f"file uses hyphen not underscore: {desc}" |
| 89 | + |
| 90 | + def test_file_detects_aarch64(self, tmp_path: Path): |
| 91 | + """file(1) description for aarch64 ELF contains 'aarch64'.""" |
| 92 | + binary = tmp_path / "fake_bin" |
| 93 | + binary.write_bytes(self.ELF_AARCH64 + b"\x00" * 44) |
| 94 | + desc = self._file_description(binary) |
| 95 | + assert "aarch64" in desc, f"Expected 'aarch64' in: {desc}" |
| 96 | + |
| 97 | + def test_x86_64_not_matched_by_aarch64_pattern(self, tmp_path: Path): |
| 98 | + """An x86_64 binary must not match the aarch64 pattern.""" |
| 99 | + binary = tmp_path / "fake_bin" |
| 100 | + binary.write_bytes(self.ELF_X86_64 + b"\x00" * 44) |
| 101 | + desc = self._file_description(binary) |
| 102 | + assert "aarch64" not in desc |
| 103 | + |
| 104 | + def test_aarch64_not_matched_by_x86_pattern(self, tmp_path: Path): |
| 105 | + """An aarch64 binary must not match the x86-64 pattern.""" |
| 106 | + binary = tmp_path / "fake_bin" |
| 107 | + binary.write_bytes(self.ELF_AARCH64 + b"\x00" * 44) |
| 108 | + desc = self._file_description(binary) |
| 109 | + assert "x86-64" not in desc |
0 commit comments