Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 48 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ jobs:
libgl1 \
libegl1 \
libdbus-1-3 \
ruby-dev\
build-essential\
rpm\
libxcb-cursor0
- name: Prepare Scripts
run: |
Expand Down Expand Up @@ -109,4 +112,48 @@ jobs:
files: |
Lufus-x86_64-${{ github.ref_name }}.tar.gz
Lufus-x86_64-${{ github.ref_name }}.tar.gz.sha256
append_body: true
append_body: true

# for fpm packages
build-fpm:
needs: build-tarball
runs-on: ubuntu-latest
permissions:
contents: write
strategy:
matrix:
pkg_type: [deb, rpm]
steps:
- uses: actions/checkout@v4

- name: Set up Ruby
uses: actions/setup-ruby@v1
with:
ruby-version: '3.2'

- name: Install FPM
run: gem install fpm

- name: Download tarball from release
run: |
wget https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/Lufus-x86_64-${{ github.ref_name }}.tar.gz

- name: Build ${{ matrix.pkg_type }} Package
run: |
mkdir tmp_source && tar -xzf Lufus-x86_64-${{ github.ref_name }}.tar.gz -C tmp_source
fpm -s dir -t ${{ matrix.pkg_type }} \
-n "Lufus" \
-v "${{ github.ref_name }}" \
--iteration 1 \
--prefix /opt/lufus \
--description "Physical drive imaging and formatting utility" \
-C tmp_source/Lufus-x86_64-${{ github.ref_name }} .

- name: Upload to Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
files: |
*.deb
*.rpm
append_body: true
1 change: 1 addition & 0 deletions src/lufus/drives/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,7 @@ def _status(msg: str) -> None:

else:
_status(f"ERROR: Unknown fs_type={fs_type}")
unexpected()
return False

# Apply volume label after successful format
Expand Down
4 changes: 2 additions & 2 deletions tests/test_flash_windows_and_install_ventoy_fixes.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@ class TestFlashWindowsOsErrorOnMissingIso:

def test_returns_false_when_iso_does_not_exist(self, tmp_path):
missing_iso = str(tmp_path / "nonexistent.iso")
result = fw_module.flash_windows("/dev/sdb", missing_iso)
result = fw_module.flash_windows("/dev/sdb", missing_iso, fw_module.PartitionScheme.SIMPLE_FAT32)
assert result is False

def test_returns_false_when_iso_is_a_directory(self, tmp_path):
result = fw_module.flash_windows("/dev/sdb", str(tmp_path))
result = fw_module.flash_windows("/dev/sdb", str(tmp_path), fw_module.PartitionScheme.SIMPLE_FAT32)
assert result is False

class TestGetWimSizeCaseInsensitive:
Expand Down
189 changes: 96 additions & 93 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ def fake_run(cmd, check=True, **kwargs):
formatting.dskformat()

# Find the mkfs call (partition scheme parted calls come first)
mkfs_calls = [c for c in calls if c and c[0].startswith("mkfs")]
mkfs_calls = [c for c in calls if c and Path(c[0]).name.startswith("mkfs")]
assert len(mkfs_calls) == 1, f"Expected 1 mkfs call, got: {calls}"
assert mkfs_calls[0][0] == expected_tool
assert Path(mkfs_calls[0][0]).name == expected_tool


def test_dskformat_calls_unexpected_for_unknown_fs(monkeypatch) -> None:
Expand Down Expand Up @@ -352,38 +352,40 @@ def test_get_mount_and_drive_falls_back_to_find_dn(monkeypatch) -> None:
# unmount / remount
# ---------------------------------------------------------------------------

def test_unmount_skips_subprocess_when_no_drive(monkeypatch, capsys) -> None:
def test_unmount_skips_subprocess_when_no_drive(monkeypatch, caplog) -> None:
monkeypatch.setattr(formatting, "_get_mount_and_drive", lambda: (None, None, {}))

def bad_run(*a, **kw):
raise AssertionError("subprocess.run must not be called")

monkeypatch.setattr(formatting.subprocess, "run", bad_run)
formatting.unmount()
out = capsys.readouterr()
assert out.out or out.err
assert "No drive node found" in caplog.text


def test_remount_skips_subprocess_when_no_drive(monkeypatch, capsys) -> None:
def test_remount_skips_subprocess_when_no_drive(monkeypatch, caplog) -> None:
monkeypatch.setattr(formatting, "_get_mount_and_drive", lambda: (None, None, {}))

def bad_run(*a, **kw):
raise AssertionError("subprocess.run must not be called")

monkeypatch.setattr(formatting.subprocess, "run", bad_run)
formatting.remount()
out = capsys.readouterr()
assert out.out or out.err
assert "No drive node found" in caplog.text


def test_unmount_issues_umount_command(monkeypatch) -> None:
mount = "/media/testuser/USB"
drive = "/dev/sdb1"
monkeypatch.setattr(formatting, "_get_mount_and_drive", lambda: (mount, drive, {}))
monkeypatch.setattr(formatting.glob, "glob", lambda path: [drive])
calls = []
monkeypatch.setattr(formatting.subprocess, "run", lambda cmd, *a, **kw: calls.append(cmd))
monkeypatch.setattr(formatting.time, "sleep", lambda x: None)
formatting.unmount()
assert calls and calls[0][0] == "umount" and drive in calls[0]
assert any("umount" in cmd for cmd in calls), f"umount not found in {calls}"
assert any(drive in cmd for cmd in calls)
assert calls[-1] == ["udevadm", "settle"]


def test_remount_issues_mount_command(monkeypatch) -> None:
Expand All @@ -396,121 +398,122 @@ def test_remount_issues_mount_command(monkeypatch) -> None:
assert calls and calls[0][0] == "mount" and drive in calls[0] and mount in calls[0]


# I think these are Redundant so i commented them out for now

@pytest.mark.parametrize(
("fs_type", "expected_tool"),
[
(0, "mkfs.ntfs"),
(1, "mkfs.vfat"),
(2, "mkfs.exfat"),
(3, "mkfs.ext4"),
],
)
def test_dskformat_runs_expected_mkfs_command(monkeypatch, fs_type: int, expected_tool: str) -> None:
_setup_common_monkeypatch(monkeypatch)
monkeypatch.setattr(formatting.states, "currentFS", fs_type)
monkeypatch.setattr(formatting.states, "cluster_size", 0)
monkeypatch.setattr(formatting.states, "partition_scheme", 0)
# @pytest.mark.parametrize(
# ("fs_type", "expected_tool"),
# [
# (0, "mkfs.ntfs"),
# (1, "mkfs.vfat"),
# (2, "mkfs.exfat"),
# (3, "mkfs.ext4"),
# ],
# )
# def test_dskformat_runs_expected_mkfs_command(monkeypatch, fs_type: int, expected_tool: str) -> None:
# _setup_common_monkeypatch(monkeypatch)
# monkeypatch.setattr(formatting.states, "currentFS", fs_type)
# monkeypatch.setattr(formatting.states, "cluster_size", 0)
# monkeypatch.setattr(formatting.states, "partition_scheme", 0)

calls = []
# calls = []

def fake_run(cmd, check=True, **kwargs):
calls.append(cmd)
# def fake_run(cmd, check=True, **kwargs):
# calls.append(cmd)

monkeypatch.setattr(formatting.subprocess, "run", fake_run)
# monkeypatch.setattr(formatting.subprocess, "run", fake_run)

formatting.dskformat()
# formatting.dskformat()

# Find the mkfs call (partition scheme parted calls come first)
mkfs_calls = [c for c in calls if c and c[0].startswith("mkfs")]
assert len(mkfs_calls) == 1, f"Expected 1 mkfs call, got: {calls}"
assert mkfs_calls[0][0] == expected_tool
# # Find the mkfs call (partition scheme parted calls come first)
# mkfs_calls = [c for c in calls if c and Path(c[0]).name.startswith("mkfs")]
# assert len(mkfs_calls) == 1, f"Expected 1 mkfs call, got: {calls}"
# assert expected_tool in mkfs_calls[0][0]


def test_dskformat_calls_unexpected_for_unknown_fs(monkeypatch) -> None:
_setup_common_monkeypatch(monkeypatch)
monkeypatch.setattr(formatting.states, "currentFS", 99)
monkeypatch.setattr(formatting.states, "cluster_size", 0)
monkeypatch.setattr(formatting.states, "partition_scheme", 0)
# def test_dskformat_calls_unexpected_for_unknown_fs(monkeypatch) -> None:
# _setup_common_monkeypatch(monkeypatch)
# monkeypatch.setattr(formatting.states, "currentFS", 99)
# monkeypatch.setattr(formatting.states, "cluster_size", 0)
# monkeypatch.setattr(formatting.states, "partition_scheme", 0)

called = {"unexpected": False}
# called = {"unexpected": False}

def fake_unexpected():
called["unexpected"] = True
# def fake_unexpected():
# called["unexpected"] = True

monkeypatch.setattr(formatting, "unexpected", fake_unexpected)
monkeypatch.setattr(formatting.subprocess, "run", lambda *args, **kwargs: None)
# monkeypatch.setattr("lufus.drives.formatting.unexpected", fake_unexpected)
# monkeypatch.setattr(formatting.subprocess, "run", lambda *args, **kwargs: None)

formatting.dskformat()
# formatting.dskformat()

assert called["unexpected"] is True
# assert called["unexpected"] is True


def test_cluster_returns_tuple_even_without_usb(monkeypatch) -> None:
"""cluster() must never crash — it must always return a valid 3-tuple."""
monkeypatch.setattr(formatting.fu, "find_usb", lambda: {})
monkeypatch.setattr(formatting.fu, "find_DN", lambda: None)
monkeypatch.setattr(formatting.states, "DN", "")
# def test_cluster_returns_tuple_even_without_usb(monkeypatch) -> None:
# """cluster() must never crash — it must always return a valid 3-tuple."""
# monkeypatch.setattr(formatting.fu, "find_usb", lambda: {})
# monkeypatch.setattr(formatting.fu, "find_DN", lambda: None)
# monkeypatch.setattr(formatting.states, "DN", "")

result = formatting.cluster()
assert isinstance(result, tuple)
assert len(result) == 3
cluster1, cluster2, sector = result
assert cluster1 > 0
assert cluster2 > 0
assert sector == cluster1 // cluster2
# result = formatting.cluster()
# assert isinstance(result, tuple)
# assert len(result) == 3
# cluster1, cluster2, sector = result
# assert cluster1 > 0
# assert cluster2 > 0
# assert sector == cluster1 // cluster2


def test_cluster_respects_cluster_size_state(monkeypatch) -> None:
monkeypatch.setattr(formatting.fu, "find_usb", lambda: {"/media/testuser/USB": "USB"})
monkeypatch.setattr(formatting.fu, "find_DN", lambda: "/dev/sdb1")
monkeypatch.setattr(formatting.states, "DN", "/dev/sdb1")
# def test_cluster_respects_cluster_size_state(monkeypatch) -> None:
# monkeypatch.setattr(formatting.fu, "find_usb", lambda: {"/media/testuser/USB": "USB"})
# monkeypatch.setattr(formatting.fu, "find_DN", lambda: "/dev/sdb1")
# monkeypatch.setattr(formatting.states, "DN", "/dev/sdb1")

monkeypatch.setattr(formatting.states, "cluster_size", 0)
c1, _, _ = formatting.cluster()
assert c1 == 4096
# monkeypatch.setattr(formatting.states, "cluster_size", 0)
# c1, _, _ = formatting.cluster()
# assert c1 == 4096

monkeypatch.setattr(formatting.states, "cluster_size", 1)
c1, _, _ = formatting.cluster()
assert c1 == 8192
# monkeypatch.setattr(formatting.states, "cluster_size", 1)
# c1, _, _ = formatting.cluster()
# assert c1 == 8192


def test_apply_partition_scheme_gpt(monkeypatch) -> None:
calls = []
monkeypatch.setattr(formatting.subprocess, "run", lambda cmd, check=True, **kw: calls.append(cmd))
monkeypatch.setattr(formatting.states, "partition_scheme", 0)
# def test_apply_partition_scheme_gpt(monkeypatch) -> None:
# calls = []
# monkeypatch.setattr(formatting.subprocess, "run", lambda cmd, check=True, **kw: calls.append(cmd))
# monkeypatch.setattr(formatting.states, "partition_scheme", 0)

formatting._apply_partition_scheme("/dev/sdb1")
# formatting._apply_partition_scheme("/dev/sdb1")

assert any("gpt" in c for c in calls)
# assert any("gpt" in c for c in calls)


def test_apply_partition_scheme_mbr(monkeypatch) -> None:
calls = []
monkeypatch.setattr(formatting.subprocess, "run", lambda cmd, check=True, **kw: calls.append(cmd))
monkeypatch.setattr(formatting.states, "partition_scheme", 1)
# def test_apply_partition_scheme_mbr(monkeypatch) -> None:
# calls = []
# monkeypatch.setattr(formatting.subprocess, "run", lambda cmd, check=True, **kw: calls.append(cmd))
# monkeypatch.setattr(formatting.states, "partition_scheme", 1)

formatting._apply_partition_scheme("/dev/sdb1")
# formatting._apply_partition_scheme("/dev/sdb1")

assert any("msdos" in c for c in calls)
# assert any("msdos" in c for c in calls)


def test_checkdevicebadblock_returns_false_when_no_drive(monkeypatch) -> None:
monkeypatch.setattr(formatting.fu, "find_usb", lambda: {})
monkeypatch.setattr(formatting.fu, "find_DN", lambda: None)
monkeypatch.setattr(formatting.states, "DN", "")
# def test_checkdevicebadblock_returns_false_when_no_drive(monkeypatch) -> None:
# monkeypatch.setattr(formatting.fu, "find_usb", lambda: {})
# monkeypatch.setattr(formatting.fu, "find_DN", lambda: None)
# monkeypatch.setattr(formatting.states, "DN", "")

result = formatting.checkdevicebadblock()
assert result is False
# result = formatting.checkdevicebadblock()
# assert result is False


def test_volumecustomlabel_no_drive_does_not_crash(monkeypatch) -> None:
"""volumecustomlabel() should gracefully handle missing drive node."""
monkeypatch.setattr(formatting.fu, "find_usb", lambda: {})
monkeypatch.setattr(formatting.fu, "find_DN", lambda: None)
monkeypatch.setattr(formatting.states, "DN", "")
monkeypatch.setattr(formatting.states, "currentFS", 0)
monkeypatch.setattr(formatting.states, "new_label", "TESTLABEL")
# def test_volumecustomlabel_no_drive_does_not_crash(monkeypatch) -> None:
# """volumecustomlabel() should gracefully handle missing drive node."""
# monkeypatch.setattr(formatting.fu, "find_usb", lambda: {})
# monkeypatch.setattr(formatting.fu, "find_DN", lambda: None)
# monkeypatch.setattr(formatting.states, "DN", "")
# monkeypatch.setattr(formatting.states, "currentFS", 0)
# monkeypatch.setattr(formatting.states, "new_label", "TESTLABEL")

# Should not raise
formatting.volumecustomlabel()
# # Should not raise
# formatting.volumecustomlabel()
Loading