Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ea5ad6e
fix: reduce size of appimage by roughly 80mb by slimming down pyqt
Hog185 Apr 17, 2026
84c7d54
revert last
Hog185 Apr 17, 2026
f886c4c
Modify dependency version requirements
Hog185 Apr 19, 2026
9ff6957
Update pyproject.toml for lower dependencies in all places
Splayer0 Apr 19, 2026
890eb8e
Bump version and update dependencies in pyproject.toml
Hog185 Apr 20, 2026
ceffc31
Modify dependency versions in pyproject.toml
Hog185 Apr 20, 2026
0fbaa62
Update PyInstaller options for lufus build
Hog185 Apr 20, 2026
09ab9a6
Add contribution guidelines section to README
Splayer0 Apr 29, 2026
b7b3dda
Update issue templates
Splayer0 May 6, 2026
8cdf95b
fixed error in test
Splayer0 May 6, 2026
e255a92
added fpm job for rpm and deb
Splayer0 May 8, 2026
93e2fa4
Update build.yml
Splayer0 May 8, 2026
d8d847a
Refactor mkfs call detection in test_formatting
Splayer0 May 8, 2026
d9d6aaf
Update test_formatting.py
Splayer0 May 8, 2026
68fec0a
Add error handling for unknown filesystem types
Splayer0 May 8, 2026
459d1ea
Refactor test cases for formatting functions
Splayer0 May 8, 2026
98e1282
Update test_formatting.py
Splayer0 May 8, 2026
7990a73
Change capsys to caplog in unmount tests
Splayer0 May 8, 2026
bb27c5b
Refactor unmount test assertions for clarity
Splayer0 May 8, 2026
e668f6b
Merge pull request #188 from Splayer0/main
Splayer0 May 8, 2026
1aafe3b
fixed test_formating merge conflict
Splayer0 May 9, 2026
66b42c8
Update pyproject.toml
Splayer0 May 9, 2026
69f92f5
pyproject.toml typos and format fix
Splayer0 May 9, 2026
c4d3d81
format fix
Splayer0 May 9, 2026
881e04a
Merge branch 'Hogjects:dev' into dev
Splayer0 May 9, 2026
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
20 changes: 20 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: Bug
assignees: ''
type: Bug

---

**Describe the bug**


**To Reproduce**


**Expected behavior**


**LOG FILES**
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: Enhancement
assignees: ''
type: Feature

---

**Is your feature request related to a problem? Please describe.**


**Describe the solution you'd like**


**Describe alternatives you've considered**
3 changes: 3 additions & 0 deletions .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: Build AppImage
run: |
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,16 @@ or `mkdocs serve` to run a dev server.
[Read the documentation here (WIP)](https://splayer.4plt.ch/lufus/)
## Contributing

### Contribution Guidelines
1. All PR must be made against the dev branch by default. Any exceptions must be properly stated
2. Provide an example of your feature or screenshots of changes to the GUI wherever applicable
3. Any use of AI must be clearly stated on the PR
4. PR must be properly made with the format, issues and labels
5. Do not create multiple PRs or duplicate PRs at once
6. Follow proper PEP8 naming scheme (experimental)
7. Any known limitations must be clearly stated in the PR

Your PR may be rejected by the maintainers for any of the reasons without prior notice. Please ping other maintainers if you think a mistake has been made. Guidelines are a subject to change.

Feedback, testing, translations, and other contributions are appreciated. Please join our Discord server to get quick support on contributing and debugging.
This is an open-source project maintained by volunteers and hobbyists. Response times for issues and pull requests may vary.
1 change: 1 addition & 0 deletions src/lufus/drives/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ def _status(msg: str) -> None:

if fs_type not in fs_configs:
_status(f"ERROR: Unknown fs_type={fs_type}")
log_unexpected_error()
return False

tool_name, args_fn, fs_label, install_hint = fs_configs[fs_type]
Expand Down
195 changes: 101 additions & 94 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ def fake_run(cmd, check=True, **kwargs):
formatting.disk_format()

# Find the mkfs call (partition scheme parted calls come first)
mkfs_calls = [c for c in calls if c and "mkfs" in c[0]]
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]
assert Path(mkfs_calls[0][0]).name == expected_tool


def test_dskformat_calls_unexpected_for_unknown_fs(monkeypatch) -> None:
Expand Down Expand Up @@ -369,35 +369,40 @@ def test_get_mount_and_drive_falls_back_to_find_dn(monkeypatch) -> None:
# ---------------------------------------------------------------------------


def test_unmount_skips_subprocess_when_no_drive(monkeypatch) -> 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)
assert formatting.unmount() is False
formatting.unmount()
assert "No drive node found" in caplog.text


def test_remount_skips_subprocess_when_no_drive(monkeypatch) -> 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)
assert formatting.remount() is False
formatting.remount()
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 *a, **kw: [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_unmount_calls_unmount_fail_and_returns_false_on_error(monkeypatch) -> None:
Expand Down Expand Up @@ -479,120 +484,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]


@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.state, "filesystem_index", fs_type)
monkeypatch.setattr(formatting.state, "cluster_size", 0)
monkeypatch.setattr(formatting.state, "partition_scheme", 0)
# I think these are Redundant so i commented them out for now

calls = []
# @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)

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

monkeypatch.setattr(formatting.subprocess, "run", fake_run)
# def fake_run(cmd, check=True, **kwargs):
# calls.append(cmd)

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

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

# # 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.state, "filesystem_index", 99)
monkeypatch.setattr(formatting.state, "cluster_size", 0)
monkeypatch.setattr(formatting.state, "partition_scheme", 0)
monkeypatch.setattr(formatting.subprocess, "run", lambda *args, **kwargs: None)

assert formatting.disk_format() is False
# 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}

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_device_node", lambda: None)
monkeypatch.setattr(formatting.state, "device_node", "")
# def fake_unexpected():
# called["unexpected"] = True

result = formatting.get_format_geometry()
assert isinstance(result, tuple)
assert len(result) == 3
cluster1, cluster2, sector = result
assert cluster1 > 0
assert cluster2 > 0
assert sector == cluster1 // cluster2
# monkeypatch.setattr("lufus.drives.formatting.unexpected", fake_unexpected)
# monkeypatch.setattr(formatting.subprocess, "run", lambda *args, **kwargs: None)

# formatting.dskformat()

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_device_node", lambda: "/dev/sdb1")
monkeypatch.setattr(formatting.state, "device_node", "/dev/sdb1")
# assert called["unexpected"] is True

monkeypatch.setattr(formatting.state, "cluster_size", 0)
c1, _, _ = formatting.get_format_geometry()
assert c1 == 4096

monkeypatch.setattr(formatting.state, "cluster_size", 1)
c1, _, _ = formatting.get_format_geometry()
assert c1 == 8192
# 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

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.state, "partition_scheme", 0)

formatting._apply_partition_scheme("/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")

assert any("gpt" in c for c in calls)
# 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

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.state, "partition_scheme", 1)

formatting._apply_partition_scheme("/dev/sdb1")
# 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)

assert any("msdos" in c for c in calls)
# formatting._apply_partition_scheme("/dev/sdb1")

# assert any("gpt" 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_device_node", lambda: None)
monkeypatch.setattr(formatting.state, "device_node", "")

result = formatting.check_device_bad_blocks()
assert result is False
# 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")

def test_volumecustomlabel_no_drive_does_not_crash(monkeypatch) -> None:
"""volume_custom_label() should gracefully handle missing drive node."""
monkeypatch.setattr(formatting.fu, "find_usb", lambda: {})
monkeypatch.setattr(formatting.fu, "find_device_node", lambda: None)
monkeypatch.setattr(formatting.state, "device_node", "")
monkeypatch.setattr(formatting.state, "filesystem_index", 0)
monkeypatch.setattr(formatting.state, "new_label", "TESTLABEL")
# assert any("msdos" in c for c in calls)

# Should not raise
formatting.volume_custom_label()

# 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_disk_format_requires_root(monkeypatch):
"""disk_format should return False without calling mkfs when not root."""
monkeypatch.setattr(formatting, "require_root", lambda: False)
calls = []
monkeypatch.setattr(formatting.subprocess, "run", lambda *a, **kw: calls.append(a))
assert formatting.disk_format() is False
assert len(calls) == 0
# 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")

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