diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..c60ae7e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -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** diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..e7db919 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -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** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9fdcbac..9f1eec4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,6 +34,9 @@ jobs: libgl1 \ libegl1 \ libdbus-1-3 \ + ruby-dev\ + build-essential\ + rpm\ libxcb-cursor0 - name: Build AppImage run: | diff --git a/README.md b/README.md index ebdca69..082a41f 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/lufus/drives/formatting.py b/src/lufus/drives/formatting.py index 14b8009..643e1a4 100644 --- a/src/lufus/drives/formatting.py +++ b/src/lufus/drives/formatting.py @@ -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] diff --git a/tests/test_formatting.py b/tests/test_formatting.py index cf3db9c..0c3b22b 100644 --- a/tests/test_formatting.py +++ b/tests/test_formatting.py @@ -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: @@ -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: @@ -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()