mirror of
https://github.com/HorlogeSkynet/archey4
synced 2024-11-24 04:00:10 +01:00
641dcd21ad
Incl. changed tests where appropriate.
502 lines
19 KiB
Python
502 lines
19 KiB
Python
"""Test module for Archey's disks usage detection module"""
|
|
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
from archey.colors import Colors
|
|
from archey.entries.disk import Disk
|
|
from archey.test.entries import HelperMethods
|
|
|
|
|
|
class TestDiskEntry(unittest.TestCase):
|
|
"""
|
|
Here, we mock `subprocess.run` calls to disk utility tools.
|
|
"""
|
|
|
|
def setUp(self):
|
|
"""We use this mock so often, it's worth defining here."""
|
|
self.disk_instance_mock = HelperMethods.entry_mock(Disk)
|
|
|
|
# Used to make `_replace_apfs_volumes_by_their_containers` call void (see below).
|
|
@patch.object(
|
|
Disk,
|
|
"_replace_apfs_volumes_by_their_containers",
|
|
)
|
|
def test_disk_get_local_filesystems(self, apfs_disk_dict_mock):
|
|
"""Tests `Disk._get_local_filesystems`."""
|
|
with self.subTest("Ignoring loop devs, dev mappers & network shares."):
|
|
# This minimal `_disk_dict` is sufficient for this test.
|
|
self.disk_instance_mock._disk_dict = { # pylint: disable=protected-access
|
|
"/very/good/mountpoint": {
|
|
"device_path": "/dev/sda1",
|
|
},
|
|
"/mounted/here/too": {
|
|
"device_path": "/dev/sda1",
|
|
},
|
|
"/other/acceptable/device/paths": {
|
|
"device_path": "/dev/anything-really",
|
|
},
|
|
"/a/samba/share": {
|
|
"device_path": "//server.local/cool_share", # ignored - not `/dev/...`
|
|
},
|
|
"/linux/loop/device/one": {
|
|
"device_path": "/dev/loop0", # ignored - loop device
|
|
},
|
|
"/linux/loop/device/two": {
|
|
"device_path": "/dev/blah/loop0", # ignored - loop device
|
|
},
|
|
"/bsd/s/loop/device/one": {
|
|
"device_path": "/dev/svnd", # ignored - loop device
|
|
},
|
|
"/bsd/s/loop/device/two": {
|
|
"device_path": "/dev/blah/svnd1", # ignored - loop device
|
|
},
|
|
"/bsd/r/loop/device/one": {
|
|
"device_path": "/dev/rvnd", # ignored - loop device
|
|
},
|
|
"/bsd/r/loop/device/two": {
|
|
"device_path": "/dev/blah/rvnd1", # ignored - loop device
|
|
},
|
|
"/solaris/loop/device/one": {
|
|
"device_path": "/dev/lofi1", # ignored - loop device
|
|
},
|
|
"/solaris/loop/device/two": {
|
|
"device_path": "/dev/blah/lofi", # ignored - loop device
|
|
},
|
|
"/linux/device/mapper": {
|
|
"device_path": "/dev/dm-1", # ignored - device mapper
|
|
},
|
|
}
|
|
|
|
apfs_disk_dict_mock.return_value = (
|
|
self.disk_instance_mock._disk_dict # pylint: disable=protected-access
|
|
)
|
|
|
|
self.assertDictEqual(
|
|
Disk._get_local_filesystems( # pylint: disable=protected-access
|
|
self.disk_instance_mock
|
|
),
|
|
{
|
|
"/very/good/mountpoint": {
|
|
"device_path": "/dev/sda1",
|
|
},
|
|
"/other/acceptable/device/paths": {
|
|
"device_path": "/dev/anything-really",
|
|
},
|
|
},
|
|
)
|
|
|
|
@patch(
|
|
"archey.entries.disk.check_output",
|
|
# This diskutil output is greatly simplified for brevity
|
|
return_value=b"""\
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
<plist version="1.0">
|
|
<dict>
|
|
<key>Containers</key>
|
|
<array>
|
|
<dict>
|
|
<key>ContainerReference</key>
|
|
<string>disk1</string>
|
|
<key>DesignatedPhysicalStore</key>
|
|
<string>disk0s1</string>
|
|
<key>Volumes</key>
|
|
<array>
|
|
<dict>
|
|
<key>DeviceIdentifier</key>
|
|
<string>disk1s1</string>
|
|
</dict>
|
|
<dict>
|
|
<key>DeviceIdentifier</key>
|
|
<string>disk1s2</string>
|
|
</dict>
|
|
</array>
|
|
</dict>
|
|
<dict>
|
|
<key>ContainerReference</key>
|
|
<string>disk2</string>
|
|
<key>DesignatedPhysicalStore</key>
|
|
<string>disk0s3</string>
|
|
<key>Volumes</key>
|
|
<array>
|
|
<dict>
|
|
<key>DeviceIdentifier</key>
|
|
<string>disk2s1</string>
|
|
</dict>
|
|
</array>
|
|
</dict>
|
|
<dict>
|
|
<key>ContainerReference</key>
|
|
<string>disk3</string>
|
|
<key>DesignatedPhysicalStore</key>
|
|
<string>disk0s2</string>
|
|
<key>Volumes</key>
|
|
<array>
|
|
<dict>
|
|
<key>DeviceIdentifier</key>
|
|
<string>disk3s1</string>
|
|
</dict>
|
|
<dict>
|
|
<key>DeviceIdentifier</key>
|
|
<string>disk3s5</string>
|
|
</dict>
|
|
<dict>
|
|
<key>DeviceIdentifier</key>
|
|
<string>disk3s6</string>
|
|
</dict>
|
|
</array>
|
|
</dict>
|
|
</array>
|
|
</dict>
|
|
</plist>
|
|
""",
|
|
)
|
|
def test_replace_apfs_volumes_by_their_containers(self, _):
|
|
"""Tests `Disk._replace_apfs_volumes_by_their_containers` for APFS volumes deduplication."""
|
|
with self.subTest("Ignoring APFS volumes on macOS"):
|
|
# Shortened example from issue #115, ie standard macOS 12.4 install on M1
|
|
# see https://eclecticlight.co/2021/01/14/m1-macs-radically-change-boot-and-recovery/
|
|
self.disk_instance_mock._disk_dict = { # pylint: disable=protected-access
|
|
"/": {
|
|
"device_path": "/dev/disk3s1s1", # in apfs container (disk0s2)
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
"/System/Volumes/VM": {
|
|
"device_path": "/dev/disk3s6", # in apfs container (disk0s2)
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
"/System/Volumes/xarts": {
|
|
"device_path": "/dev/disk1s2", # in iboot system container (disk0s1)
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
"/System/Volumes/iSCPreboot": {
|
|
"device_path": "/dev/disk1s1", # in iboot system container (disk0s1)
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
"/System/Volumes/Data": {
|
|
"device_path": "/dev/disk3s5", # in apfs container (disk0s2)
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
"/System/Volumes/Update/SFR/mnt1": {
|
|
"device_path": "/dev/disk2s1", # in recovery container (disk0s3)
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
}
|
|
# We should end up with the 3 container device paths
|
|
self.assertDictEqual(
|
|
Disk._replace_apfs_volumes_by_their_containers( # pylint: disable=protected-access
|
|
self.disk_instance_mock
|
|
),
|
|
{
|
|
"disk3": {
|
|
"device_path": "/dev/disk0s2",
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
"disk1": {
|
|
"device_path": "/dev/disk0s1",
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
"disk2": {
|
|
"device_path": "/dev/disk0s3",
|
|
"used_blocks": 0,
|
|
"total_blocks": 0,
|
|
},
|
|
},
|
|
)
|
|
|
|
with self.subTest("Adding usage of ignored APFS volumes on macOS"):
|
|
# As above test, but checking for correct usage and total figures once combined
|
|
self.disk_instance_mock._disk_dict = { # pylint: disable=protected-access
|
|
"/": {
|
|
"device_path": "/dev/disk3s1s1", # in apfs container (disk0s2)
|
|
"used_blocks": 23068672,
|
|
"total_blocks": 970981376,
|
|
},
|
|
"/System/Volumes/VM": {
|
|
"device_path": "/dev/disk3s6", # in apfs container (disk0s2)
|
|
"used_blocks": 1048576,
|
|
"total_blocks": 970981376,
|
|
},
|
|
"/System/Volumes/xarts": {
|
|
"device_path": "/dev/disk1s2", # in iboot system container (disk0s1)
|
|
"used_blocks": 6144,
|
|
"total_blocks": 512000,
|
|
},
|
|
"/System/Volumes/iSCPreboot": {
|
|
"device_path": "/dev/disk1s1", # in iboot system container (disk0s1)
|
|
"used_blocks": 7578,
|
|
"total_blocks": 512000,
|
|
},
|
|
"/System/Volumes/Data": {
|
|
"device_path": "/dev/disk3s5", # in apfs container (disk0s2)
|
|
"used_blocks": 266338304,
|
|
"total_blocks": 970981376,
|
|
},
|
|
"/System/Volumes/Update/SFR/mnt1": {
|
|
"device_path": "/dev/disk2s1", # in recovery container (disk0s3)
|
|
"used_blocks": 1677722,
|
|
"total_blocks": 5242880,
|
|
},
|
|
}
|
|
# We should end up with the 3 container device paths
|
|
self.assertDictEqual(
|
|
Disk._replace_apfs_volumes_by_their_containers( # pylint: disable=protected-access
|
|
self.disk_instance_mock
|
|
),
|
|
{
|
|
"disk3": {
|
|
"device_path": "/dev/disk0s2",
|
|
"used_blocks": 290455552,
|
|
"total_blocks": 970981376,
|
|
},
|
|
"disk1": {
|
|
"device_path": "/dev/disk0s1",
|
|
"used_blocks": 13722,
|
|
"total_blocks": 512000,
|
|
},
|
|
"disk2": {
|
|
"device_path": "/dev/disk0s3",
|
|
"used_blocks": 1677722,
|
|
"total_blocks": 5242880,
|
|
},
|
|
},
|
|
)
|
|
|
|
def test_disk_get_specified_filesystems(self):
|
|
"""Tests `Disk._get_specified_filesystems`."""
|
|
# This minimal `_disk_dict` contains everything this method touches.
|
|
self.disk_instance_mock._disk_dict = { # pylint: disable=protected-access
|
|
"/very/good/mountpoint": {
|
|
"device_path": "/dev/sda1",
|
|
},
|
|
"/mounted/here/too": {
|
|
"device_path": "/dev/sda1",
|
|
},
|
|
"/less/good/mountpoint": {
|
|
"device_path": "/dev/sda2",
|
|
},
|
|
"/a/samba/share": {
|
|
"device_path": "//server.local/cool_share",
|
|
},
|
|
}
|
|
|
|
with self.subTest("Get all filesystems with mount points."):
|
|
# pylint: disable=protected-access
|
|
self.assertDictEqual(
|
|
Disk._get_specified_filesystems(
|
|
self.disk_instance_mock,
|
|
self.disk_instance_mock._disk_dict, # recall dicts are iterables of their keys.
|
|
),
|
|
self.disk_instance_mock._disk_dict,
|
|
)
|
|
# pylint: enable=protected-access
|
|
|
|
with self.subTest("Get only `/dev/sda1` filesystems."):
|
|
self.assertDictEqual(
|
|
Disk._get_specified_filesystems( # pylint: disable=protected-access
|
|
self.disk_instance_mock, ("/dev/sda1",)
|
|
),
|
|
{
|
|
"/very/good/mountpoint": {
|
|
"device_path": "/dev/sda1",
|
|
}
|
|
},
|
|
)
|
|
|
|
@patch("archey.entries.disk.run")
|
|
def test_disk_df_output_dict(self, run_mock):
|
|
"""Test method to get `df` output as a dict by mocking calls to `subprocess.run`"""
|
|
with self.subTest("`df` regular output."):
|
|
run_mock.return_value.stdout = "\n".join(
|
|
[
|
|
# pylint: disable=line-too-long
|
|
"Filesystem 1024-blocks Used Available Capacity Mounted on",
|
|
"/dev/nvme0n1p2 499581952 427458276 67779164 87% /",
|
|
"tmpfs 8127236 292 8126944 1% /tmp",
|
|
"/dev/nvme0n1p1 523248 35908 487340 7% /boot",
|
|
"/dev/sda1 1624 42 1582 1% /what is this",
|
|
"map auto_home 0 0 0 100% /System/Volumes/Data/home",
|
|
"/Applications/Among Us.app/Wrapper 0 0 0 100% /System/Volumes/Data/game",
|
|
"",
|
|
# pylint: enable=line-too-long
|
|
]
|
|
)
|
|
self.assertDictEqual(
|
|
Disk._get_df_output_dict(), # pylint: disable=protected-access
|
|
{
|
|
"/": {
|
|
"device_path": "/dev/nvme0n1p2",
|
|
"used_blocks": 427458276,
|
|
"total_blocks": 499581952,
|
|
},
|
|
"/tmp": {"device_path": "tmpfs", "used_blocks": 292, "total_blocks": 8127236},
|
|
"/boot": {
|
|
"device_path": "/dev/nvme0n1p1",
|
|
"used_blocks": 35908,
|
|
"total_blocks": 523248,
|
|
},
|
|
"/what is this": {
|
|
"device_path": "/dev/sda1",
|
|
"used_blocks": 42,
|
|
"total_blocks": 1624,
|
|
},
|
|
},
|
|
)
|
|
|
|
with self.subTest("`df` missing from system."):
|
|
run_mock.side_effect = FileNotFoundError()
|
|
self.assertDictEqual(Disk._get_df_output_dict(), {}) # pylint: disable=protected-access
|
|
|
|
def test_disk_blocks_to_human_readable(self):
|
|
"""Test method to convert 1024-byte blocks to a human readable format."""
|
|
# Each tuple is a number of blocks followed by the expected output.
|
|
test_cases = (
|
|
(1, "1.0 KiB"),
|
|
(1024, "1.0 MiB"),
|
|
(2048, "2.0 MiB"),
|
|
(95604, "93.4 MiB"),
|
|
(1048576, "1.0 GiB"),
|
|
(2097152, "2.0 GiB"),
|
|
(92156042, "87.9 GiB"),
|
|
(1073742000, "1.0 TiB"),
|
|
(2147484000, "2.0 TiB"),
|
|
(458028916298, "426.6 TiB"),
|
|
(1099512000000, "1.0 PiB"),
|
|
(2199023000000, "2.0 PiB"), # I think we can safely stop here :)
|
|
)
|
|
for test_case in test_cases:
|
|
with self.subTest(test_case[1]):
|
|
self.assertEqual(
|
|
Disk._blocks_to_human_readable( # pylint: disable=protected-access
|
|
test_case[0]
|
|
),
|
|
test_case[1],
|
|
)
|
|
|
|
def test_disk_colors(self):
|
|
"""Test output disk level coloring."""
|
|
# This dict's values are tuples of used blocks, and the level's corresponding color.
|
|
# For reference, this test uses a disk whose total block count is 100.
|
|
levels = {
|
|
"normal": (45.0, Colors.GREEN_NORMAL),
|
|
"warning": (70.0, Colors.YELLOW_NORMAL),
|
|
"danger": (95.0, Colors.RED_NORMAL),
|
|
}
|
|
for level, blocks_color_tuple in levels.items():
|
|
with self.subTest(level):
|
|
self.disk_instance_mock.value = {
|
|
"mount_point": {
|
|
"device_path": "/dev/my-cool-disk",
|
|
"used_blocks": blocks_color_tuple[0],
|
|
"total_blocks": 100,
|
|
}
|
|
}
|
|
self.assertListEqual(
|
|
list(self.disk_instance_mock),
|
|
[
|
|
(
|
|
"Disk",
|
|
f"{blocks_color_tuple[1]}{blocks_color_tuple[0]} KiB{Colors.CLEAR} "
|
|
"/ 100.0 KiB",
|
|
)
|
|
],
|
|
)
|
|
|
|
def test_disk_output(self):
|
|
"""Test multi-line capability."""
|
|
self.disk_instance_mock.value = {
|
|
"first_mount_point": {
|
|
"device_path": "/dev/my-cool-disk",
|
|
"used_blocks": 10,
|
|
"total_blocks": 10,
|
|
},
|
|
"second_mount_point": {
|
|
"device_path": "/dev/my-cooler-disk",
|
|
"used_blocks": 10,
|
|
"total_blocks": 30,
|
|
},
|
|
}
|
|
|
|
with self.subTest("Single-line combined output."):
|
|
self.assertListEqual(
|
|
list(self.disk_instance_mock),
|
|
[("Disk", f"{Colors.YELLOW_NORMAL}20.0 KiB{Colors.CLEAR} / 40.0 KiB")],
|
|
)
|
|
|
|
with self.subTest("Normal output"):
|
|
self.disk_instance_mock.options["combine_total"] = False
|
|
self.assertListEqual(
|
|
list(self.disk_instance_mock),
|
|
[
|
|
("Disk", f"{Colors.RED_NORMAL}10.0 KiB{Colors.CLEAR} / 10.0 KiB"),
|
|
("Disk", f"{Colors.GREEN_NORMAL}10.0 KiB{Colors.CLEAR} / 30.0 KiB"),
|
|
],
|
|
)
|
|
|
|
with self.subTest("Entry name labeling (device path with entry name)"):
|
|
self.disk_instance_mock.options = {
|
|
"combine_total": False,
|
|
"disk_labels": "device_paths",
|
|
}
|
|
self.assertListEqual(
|
|
list(self.disk_instance_mock),
|
|
[
|
|
(
|
|
"Disk (/dev/my-cool-disk)",
|
|
f"{Colors.RED_NORMAL}10.0 KiB{Colors.CLEAR} / 10.0 KiB",
|
|
),
|
|
(
|
|
"Disk (/dev/my-cooler-disk)",
|
|
f"{Colors.GREEN_NORMAL}10.0 KiB{Colors.CLEAR} / 30.0 KiB",
|
|
),
|
|
],
|
|
)
|
|
|
|
with self.subTest("Entry name labeling (mount points without entry name)"):
|
|
self.disk_instance_mock.options = {
|
|
"combine_total": False,
|
|
"disk_labels": "mount_points",
|
|
"hide_entry_name": True,
|
|
}
|
|
self.assertListEqual(
|
|
list(self.disk_instance_mock),
|
|
[
|
|
(
|
|
"(first_mount_point)",
|
|
f"{Colors.RED_NORMAL}10.0 KiB{Colors.CLEAR} / 10.0 KiB",
|
|
),
|
|
(
|
|
"(second_mount_point)",
|
|
f"{Colors.GREEN_NORMAL}10.0 KiB{Colors.CLEAR} / 30.0 KiB",
|
|
),
|
|
],
|
|
)
|
|
|
|
with self.subTest("Entry name labeling (without disk label nor entry name)"):
|
|
self.disk_instance_mock.options = {
|
|
"combine_total": False,
|
|
"disk_labels": False,
|
|
# `hide_entry_name` is being ignored as `disk_labels` evaluates to "falsy" too.
|
|
"hide_entry_name": True,
|
|
}
|
|
self.assertListEqual(
|
|
list(self.disk_instance_mock),
|
|
[
|
|
("Disk", f"{Colors.RED_NORMAL}10.0 KiB{Colors.CLEAR} / 10.0 KiB"),
|
|
("Disk", f"{Colors.GREEN_NORMAL}10.0 KiB{Colors.CLEAR} / 30.0 KiB"),
|
|
],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|