Files
wehub-resource-sync 3e779be6f3
CI / lint (push) Failing after 13m4s
CI / test (3.11, ubuntu-latest) (push) Failing after 2m4s
CI / test (3.13, ubuntu-latest) (push) Successful in 13m30s
CI / test (3.14, ubuntu-latest) (push) Successful in 17m21s
CI / test (3.12, ubuntu-latest) (push) Successful in 17m55s
CI / discover-apps-ps (push) Successful in 1m56s
CI / test (3.9, ubuntu-latest) (push) Successful in 13m17s
CI / test (3.10, ubuntu-latest) (push) Successful in 26m21s
CI / audit (push) Successful in 13m38s
Deploy site / deploy (push) Has been cancelled
CI / test (3.14, ubuntu-24.04-arm) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:37 +08:00

242 lines
10 KiB
Python

# SPDX-License-Identifier: MIT
"""Tests for winpodx.utils.btrfs — per-path Copy-on-Write helper.
The module deliberately does NOT operate on podman's graph root; tests
verify behaviour against the explicit path the caller provides (the
winpodx bind-mount storage directory).
"""
from __future__ import annotations
from unittest.mock import patch
from winpodx.utils import btrfs
def _run_factory(scripted):
"""Build a fake _run that pops scripted (rc, stdout, stderr) tuples in order."""
calls = []
def fake(cmd, timeout=5.0):
calls.append(cmd)
if not scripted:
return -1, "", "no more scripted responses"
return scripted.pop(0)
return fake, calls
class TestDetectPathFs:
def test_returns_btrfs_when_findmnt_says_btrfs(self, tmp_path):
scripted = [(0, "btrfs\n", "")]
fake, _ = _run_factory(scripted)
with (
patch.object(btrfs, "_run", fake),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/findmnt"),
):
assert btrfs.detect_path_fs(tmp_path) == "btrfs"
def test_returns_ext4_for_non_btrfs(self, tmp_path):
scripted = [(0, "ext4\n", "")]
fake, _ = _run_factory(scripted)
with (
patch.object(btrfs, "_run", fake),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/findmnt"),
):
assert btrfs.detect_path_fs(tmp_path) == "ext4"
def test_returns_unknown_when_findmnt_missing(self, tmp_path):
with patch.object(btrfs.shutil, "which", return_value=None):
assert btrfs.detect_path_fs(tmp_path) == "unknown"
def test_returns_unknown_when_findmnt_errors(self, tmp_path):
scripted = [(1, "", "findmnt: ...")]
fake, _ = _run_factory(scripted)
with (
patch.object(btrfs, "_run", fake),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/findmnt"),
):
assert btrfs.detect_path_fs(tmp_path) == "unknown"
def test_walks_up_to_existing_parent_for_nonexistent_path(self, tmp_path):
"""Regression for kernalix7's 2026-05-06 silent-NoCoW bug:
``findmnt --target`` on a non-existent path returns rc=1 with
empty stdout on opensuse Tumbleweed — the auto-migration
plan_migration call computes ``target_fs = detect_path_fs(
~/.local/share/winpodx/storage)`` BEFORE the dir is mkdir'd,
so a naive findmnt call returned 'unknown' there, set
``chattr_will_run=False``, and silently skipped the entire
NoCoW path. The fix walks the parent chain until it hits an
existing dir (always succeeds — `/` exists), so callers get
the fs that will contain `path` once it's materialised.
"""
scripted = [(0, "btrfs\n", "")]
fake, calls = _run_factory(scripted)
nonexistent = tmp_path / "nope" / "still_not_here" / "leaf"
# Guard rail: the leaf and its first ancestor really don't exist.
assert not nonexistent.exists()
assert not nonexistent.parent.exists()
with (
patch.object(btrfs, "_run", fake),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/findmnt"),
):
assert btrfs.detect_path_fs(nonexistent) == "btrfs"
# The probe should have walked up to tmp_path (the nearest
# existing ancestor), NOT the original non-existent leaf.
assert calls, "findmnt was not invoked"
last_target = calls[-1][-1]
assert last_target == str(tmp_path), (
f"expected probe to walk up to {tmp_path}, got {last_target!r}"
)
def test_walk_up_terminates_at_root_when_nothing_exists(self, tmp_path, monkeypatch):
"""Loop bound: a pathological symlink loop or absurd path depth
must not spin forever. Walk-up has a 64-step ceiling; well past
that, we fall through to whatever findmnt says about the
original path. Sanity: a 200-segment path doesn't hang."""
scripted = [(0, "btrfs\n", "")]
fake, _ = _run_factory(scripted)
deep = tmp_path
for i in range(200):
deep = deep / f"seg{i}"
with (
patch.object(btrfs, "_run", fake),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/findmnt"),
):
# Should return SOMETHING quickly (either btrfs from the
# scripted response or unknown if probing fell off the ceiling).
result = btrfs.detect_path_fs(deep)
assert result in {"btrfs", "unknown"}
class TestIsCowDisabled:
def test_returns_true_when_C_flag_present(self, tmp_path):
scripted = [(0, "----C-------------- " + str(tmp_path) + "\n", "")]
fake, _ = _run_factory(scripted)
with (
patch.object(btrfs, "_run", fake),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/lsattr"),
):
assert btrfs.is_cow_disabled(tmp_path) is True
def test_returns_false_when_C_flag_absent(self, tmp_path):
scripted = [(0, "------------------- " + str(tmp_path) + "\n", "")]
fake, _ = _run_factory(scripted)
with (
patch.object(btrfs, "_run", fake),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/lsattr"),
):
assert btrfs.is_cow_disabled(tmp_path) is False
def test_returns_none_when_path_missing(self, tmp_path):
gone = tmp_path / "does-not-exist"
with patch.object(btrfs.shutil, "which", return_value="/usr/bin/lsattr"):
assert btrfs.is_cow_disabled(gone) is None
class TestDisableCowOnPath:
def test_path_missing_returns_path_missing(self, tmp_path):
status, _ = btrfs.disable_cow_on_path(tmp_path / "absent")
assert status == "path_missing"
def test_skips_when_not_btrfs(self, tmp_path):
with patch.object(btrfs, "detect_path_fs", return_value="ext4"):
status, detail = btrfs.disable_cow_on_path(tmp_path)
assert status == "not_btrfs"
assert "ext4" in detail
def test_unknown_fs_when_findmnt_unavailable(self, tmp_path):
with patch.object(btrfs, "detect_path_fs", return_value="unknown"):
status, _ = btrfs.disable_cow_on_path(tmp_path)
assert status == "unknown_fs"
def test_already_off_when_cow_already_disabled(self, tmp_path):
with (
patch.object(btrfs, "detect_path_fs", return_value="btrfs"),
patch.object(btrfs, "is_cow_disabled", return_value=True),
):
status, _ = btrfs.disable_cow_on_path(tmp_path)
assert status == "already_off"
def test_failed_when_chattr_missing(self, tmp_path):
with (
patch.object(btrfs, "detect_path_fs", return_value="btrfs"),
patch.object(btrfs, "is_cow_disabled", return_value=False),
patch.object(btrfs.shutil, "which", return_value=None),
):
status, detail = btrfs.disable_cow_on_path(tmp_path)
assert status == "failed"
assert "chattr" in detail
def test_disabled_on_successful_chattr(self, tmp_path):
scripted = [(0, "", "")]
fake, calls = _run_factory(scripted)
with (
patch.object(btrfs, "detect_path_fs", return_value="btrfs"),
patch.object(btrfs, "is_cow_disabled", return_value=False),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/chattr"),
patch.object(btrfs, "_run", fake),
):
status, _ = btrfs.disable_cow_on_path(tmp_path)
assert status == "disabled"
assert calls[0] == ["chattr", "+C", str(tmp_path)]
def test_failed_when_chattr_returns_nonzero(self, tmp_path):
scripted = [(1, "", "Operation not permitted\n")]
fake, _ = _run_factory(scripted)
with (
patch.object(btrfs, "detect_path_fs", return_value="btrfs"),
patch.object(btrfs, "is_cow_disabled", return_value=False),
patch.object(btrfs.shutil, "which", return_value="/usr/bin/chattr"),
patch.object(btrfs, "_run", fake),
):
status, detail = btrfs.disable_cow_on_path(tmp_path)
assert status == "failed"
assert "Operation not permitted" in detail
class TestDoesNotTouchGraphRoot:
"""Regression: this module must NOT have a 'detect_storage_fs' or
similar helper that calls `podman info --format '{{.Store.GraphRoot}}'`.
The earlier design (PR #124) did, and it was rejected because it
affected every future podman volume on the host. The module's public
surface is path-only.
"""
def test_module_does_not_export_graph_root_helper(self):
for forbidden in ("detect_storage_fs", "disable_cow_if_btrfs"):
assert not hasattr(btrfs, forbidden), (
f"btrfs.py must not expose {forbidden!r} — would re-introduce "
f"the graph-root chattr behaviour we explicitly rejected"
)
class TestHostStorageIsSsd:
"""#606: map a path -> backing disk -> /sys/block/<disk>/queue/rotational."""
def _patch(self, monkeypatch, *, source, rotational=None, has_findmnt=True):
fake, _ = _run_factory([(0, source + "\n", "")])
monkeypatch.setattr(btrfs, "_run", fake)
monkeypatch.setattr(
btrfs.shutil, "which", lambda *_: "/usr/bin/findmnt" if has_findmnt else None
)
if rotational is not None:
monkeypatch.setattr(btrfs.Path, "read_text", lambda self, *a, **k: rotational + "\n")
def test_ssd_when_rotational_zero(self, tmp_path, monkeypatch):
self._patch(monkeypatch, source="/dev/nvme0n1p2", rotational="0")
assert btrfs.host_storage_is_ssd(tmp_path) is True
def test_hdd_when_rotational_one(self, tmp_path, monkeypatch):
self._patch(monkeypatch, source="/dev/sda3", rotational="1")
assert btrfs.host_storage_is_ssd(tmp_path) is False
def test_none_for_devmapper_source(self, tmp_path, monkeypatch):
# device-mapper / LVM is too ambiguous to map to one rotational flag.
self._patch(monkeypatch, source="/dev/mapper/vg-root")
assert btrfs.host_storage_is_ssd(tmp_path) is None
def test_none_when_findmnt_missing(self, tmp_path, monkeypatch):
self._patch(monkeypatch, source="/dev/sda1", has_findmnt=False)
assert btrfs.host_storage_is_ssd(tmp_path) is None