blob: 5c89a6dbb4a3932025c88018dca5df48a95997a6 [file]
#!/usr/bin/env fuchsia-vendored-python
# Copyright 2026 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unit tests for the hardware profiler."""
import shutil
import tempfile
import unittest
from pathlib import Path
from typing import Optional
import hardware_profiler
# --- Abstract Test Base Class with Mock /proc File System Lifecycle ---
class ProcMockTestCase(unittest.TestCase):
"""Base test case that automatically manages a hermetic mock /proc filesystem."""
def setUp(self) -> None:
super().setUp()
self.test_dir = Path(tempfile.mkdtemp())
self.proc_dir = self.test_dir / "proc"
self.proc_dir.mkdir()
def tearDown(self) -> None:
shutil.rmtree(self.test_dir)
super().tearDown()
class HardwareProfileTest(ProcMockTestCase):
def setUp(self) -> None:
super().setUp()
self.sys_dir = self.test_dir / "sys"
self.sys_dir.mkdir()
(self.sys_dir / "block").mkdir()
def write_mock_mounts(self, content: str) -> None:
with open(self.proc_dir / "mounts", "w") as f:
f.write(content)
def write_mock_disk(
self, dev_name: str, rotational: str, size: int
) -> None:
dev_dir = self.sys_dir / "block" / dev_name
dev_dir.mkdir(parents=True)
(dev_dir / "queue").mkdir()
with open(dev_dir / "queue" / "rotational", "w") as f:
f.write(rotational + "\n")
with open(dev_dir / "size", "w") as f:
f.write(str(size) + "\n")
def write_mock_cpuinfo(self, content: str) -> None:
with open(self.proc_dir / "cpuinfo", "w") as f:
f.write(content)
def write_mock_meminfo(self, content: str) -> None:
with open(self.proc_dir / "meminfo", "w") as f:
f.write(content)
def write_mock_net_interface(
self, name: str, operstate: str, speed: Optional[int]
) -> None:
iface_dir = self.sys_dir / "class" / "net" / name
iface_dir.mkdir(parents=True)
with open(iface_dir / "operstate", "w") as f:
f.write(operstate + "\n")
if speed is not None:
with open(iface_dir / "speed", "w") as f:
f.write(str(speed) + "\n")
def test_sanitize_path(self) -> None:
# 1. Exact full-component match must be redacted
self.assertEqual(
hardware_profiler._sanitize_path(
"/home/fangism/workspace", "fangism"
),
"/home/$USER/workspace",
)
# 2. Partial substring component match must NOT be redacted (boundary protection)
self.assertEqual(
hardware_profiler._sanitize_path(
"/home/fangism2/workspace", "fang"
),
"/home/fangism2/workspace",
)
# 3. Empty username must return the original path unchanged
self.assertEqual(
hardware_profiler._sanitize_path("/home/fangism/workspace", ""),
"/home/fangism/workspace",
)
def test_parse_mounts_content_pure(self) -> None:
raw_mounts = (
"/dev/sda1 / ext4 rw,noatime,discard,errors=remount-ro 0 0\n"
"tmpfs /run tmpfs rw,nosuid,nodev 0 0\n"
"/dev/sdb1 /home/fuchsia ext4 rw,relatime 0 0\n"
)
workspace_dir = "/home/fuchsia/my_workspace"
# 1. Mount point '/' is a parent of '/home/fuchsia/my_workspace' -> Match!
# 2. Mount point '/home/fuchsia' is a parent of '/home/fuchsia/my_workspace' -> Match!
# 3. Mount point '/run' is NOT a parent of '/home/fuchsia/my_workspace' -> Ignored!
results = list(
hardware_profiler._parse_mounts_content(raw_mounts, workspace_dir)
)
self.assertEqual(len(results), 2)
self.assertEqual(results[0].mount_point, "/")
self.assertEqual(
results[0].options, "rw,noatime,discard,errors=remount-ro"
)
# Call .sanitize() explicitly to check that MountInfo.sanitize redacts username components correctly!
sanitized_mount = results[1].sanitize("fuchsia")
self.assertEqual(sanitized_mount.mount_point, "/home/$USER")
self.assertEqual(sanitized_mount.options, "rw,relatime")
def test_parse_disk_properties_pure(self) -> None:
# Verify VirtIO virtual disk parsing (vda) and DiskInfo dataclass generation
self.write_mock_disk("vda", "0", 488281250) # 250 GB SSD
disks = hardware_profiler._parse_disk_properties(str(self.sys_dir))
self.assertEqual(len(disks), 1)
self.assertIsInstance(disks["vda"], hardware_profiler.DiskInfo)
self.assertEqual(disks["vda"].rotational, False)
self.assertEqual(disks["vda"].size_gb, 250.0)
def test_parse_cpuinfo_pure(self) -> None:
raw_cpuinfo = (
"processor\t: 0\n"
"model name\t: Intel(R) Xeon(R) CPU @ 2.20GHz\n"
"cpu cores\t: 2\n"
"\n"
"processor\t: 1\n"
"model name\t: Intel(R) Xeon(R) CPU @ 2.20GHz\n"
"cpu cores\t: 2\n"
)
info = hardware_profiler._parse_cpuinfo_content(raw_cpuinfo)
self.assertEqual(info.model_name, "Intel(R) Xeon(R) CPU @ 2.20GHz")
self.assertEqual(info.logical_processors, 2)
self.assertEqual(info.physical_cores, 2)
def test_parse_meminfo_pure(self) -> None:
raw_meminfo = (
"MemTotal: 65873920 kB\n" "MemFree: 4312690 kB\n"
)
info = hardware_profiler._parse_meminfo_total(raw_meminfo)
self.assertEqual(
info.total_ram_gb, 67.45
) # 65873920 * 1024 / 1e9 = 67.4548...
def test_collect_hardware_profile(self) -> None:
self.write_mock_mounts(
"/dev/sda1 / ext4 rw,noatime,discard,errors=remount-ro 0 0\n"
)
self.write_mock_disk("sda", "0", 488281250)
self.write_mock_cpuinfo(
"processor\t: 0\n"
"model name\t: AMD EPYC 7B12\n"
"cpu cores\t: 8\n"
)
self.write_mock_meminfo("MemTotal: 16000000 kB\n")
self.write_mock_net_interface("eth0", "up", 10000)
self.write_mock_net_interface("lo", "unknown", None) # Skip loopback
profile = hardware_profiler.collect_hardware_profile(
proc_dir=str(self.proc_dir),
sys_dir=str(self.sys_dir),
workspace_dir=str(self.test_dir / "workspace"),
username="mockuser",
)
# 1. Verify CPU
self.assertEqual(profile["cpu"]["model_name"], "AMD EPYC 7B12")
self.assertEqual(profile["cpu"]["physical_cores"], 8)
# 2. Verify Memory
self.assertEqual(profile["memory"]["total_ram_gb"], 16.38)
# 3. Verify Disks
self.assertEqual(profile["disks"]["sda"]["size_gb"], 250.0)
# 4. Verify Network
self.assertEqual(len(profile["network"]), 1)
self.assertEqual(profile["network"][0]["name"], "eth0")
self.assertEqual(profile["network"][0]["operstate"], "up")
self.assertEqual(profile["network"][0]["speed_mbps"], 10000)
# 5. Verify Mounts
self.assertEqual(len(profile["mounts"]), 1)
self.assertEqual(profile["mounts"][0]["mount_point"], "/")
if __name__ == "__main__":
unittest.main()