Skip to content

Commit 62eb861

Browse files
CopilotJosverl
andauthored
Normalize firmware DB paths to POSIX format for cross-platform compatibility (#92)
* Initial plan * fix: normalize firmware paths to POSIX format for cross-platform DB compatibility Use .as_posix() instead of str() when storing and comparing relative firmware paths in the database. This prevents path separator mismatches (backslash vs forward slash) when the same firmware folder is accessed from both Windows and Linux/WSL2. Fixes: Windows/Linux conflicts in mpflash database Agent-Logs-Url: https://github.com/Josverl/mpflash/sessions/b370abd5-407c-4f64-9aa2-03ffefb02d91 Co-authored-by: Josverl <981654+Josverl@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Josverl <981654+Josverl@users.noreply.github.com>
1 parent c5bed8f commit 62eb861

3 files changed

Lines changed: 80 additions & 3 deletions

File tree

mpflash/download/from_web.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ def fetch_firmware_files(available_firmwares: List[Firmware], firmware_folder: P
187187
r = requests.get(board.source, allow_redirects=True)
188188
with open(filename, "wb") as fw:
189189
fw.write(r.content)
190-
board.firmware_file = str(filename.relative_to(firmware_folder))
190+
board.firmware_file = filename.relative_to(firmware_folder).as_posix()
191191
except requests.RequestException as e:
192192
log.exception(e)
193193
continue

mpflash/downloaded.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def clean_downloaded_firmwares() -> None:
2525
firmware_dir = Path(config.firmware_folder)
2626

2727
firmware_files_on_disk = {
28-
str(f.relative_to(firmware_dir)) for f in firmware_dir.rglob("*") if f.is_file() and f.suffix not in {".db", ".bak", ".jsonl"}
28+
f.relative_to(firmware_dir).as_posix() for f in firmware_dir.rglob("*") if f.is_file() and f.suffix not in {".db", ".bak", ".jsonl"}
2929
}
3030

3131
with database.atomic():

tests/test_downloaded.py

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
from pathlib import Path
2+
from unittest.mock import patch
3+
14
import pytest
25
from pytest_mock import MockerFixture
36

47
from mpflash.db.models import Firmware
58

69
# from mpflash.db.downloads import downloaded_fw
7-
from mpflash.downloaded import find_downloaded_firmware
10+
from mpflash.downloaded import clean_downloaded_firmwares, find_downloaded_firmware
811
from mpflash.versions import clean_version
912

1013
pytestmark = [pytest.mark.mpflash]
@@ -147,3 +150,77 @@ def test_find_downloaded_firmware_preview_no_port(mocker: MockerFixture, session
147150
assert result, "Should find preview firmware even without port"
148151
fw = result[0]
149152
assert "preview" in fw.firmware_file
153+
154+
155+
def test_clean_downloaded_firmwares_uses_posix_paths(tmp_path):
156+
"""Ensure clean_downloaded_firmwares uses POSIX paths for cross-platform DB compat.
157+
158+
On Windows, Path.relative_to() produces backslash separators which would
159+
mismatch DB records stored with forward slashes (e.g. from Linux/WSL2).
160+
The fix normalises disk paths to POSIX before comparing with the DB.
161+
"""
162+
from mpflash.db.models import Board, Firmware, Metadata, database
163+
164+
# Initialize the shared database to an in-memory instance
165+
if not database.is_closed():
166+
database.close()
167+
database.init(":memory:")
168+
database.connect()
169+
database.create_tables([Metadata, Board, Firmware])
170+
171+
try:
172+
# Create a firmware file on disk
173+
fw_dir = tmp_path / "esp32"
174+
fw_dir.mkdir()
175+
fw_file = fw_dir / "ESP32_GENERIC-v1.28.0.bin"
176+
fw_file.write_bytes(b"fake firmware")
177+
178+
# Insert a DB record using POSIX-style path (as Linux/loader would)
179+
posix_path = "esp32/ESP32_GENERIC-v1.28.0.bin"
180+
Firmware.create(
181+
board_id="ESP32_GENERIC",
182+
version="v1.28.0",
183+
firmware_file=posix_path,
184+
port="esp32",
185+
)
186+
187+
# Patch config.firmware_folder to our tmp_path
188+
with patch("mpflash.downloaded.config") as mock_config:
189+
mock_config.firmware_folder = tmp_path
190+
clean_downloaded_firmwares()
191+
192+
# The DB record should still exist (not deleted)
193+
remaining = list(Firmware.select().where(Firmware.firmware_file == posix_path))
194+
assert len(remaining) == 1, "DB record with posix path should NOT be removed when file exists on disk"
195+
finally:
196+
if not database.is_closed():
197+
database.close()
198+
199+
200+
def test_fetch_firmware_files_stores_posix_path(tmp_path):
201+
"""Ensure fetch_firmware_files stores firmware_file as POSIX path in DB."""
202+
from mpflash.download.from_web import fetch_firmware_files
203+
204+
fw = Firmware(
205+
board_id="ESP32_GENERIC",
206+
version="v1.28.0",
207+
firmware_file="ESP32_GENERIC-v1.28.0.bin",
208+
port="esp32",
209+
source="https://example.com/ESP32_GENERIC-v1.28.0.bin",
210+
)
211+
212+
# Mock requests.get to return fake content
213+
class FakeResponse:
214+
content = b"fake firmware data"
215+
216+
import requests as real_requests
217+
218+
with patch.dict("sys.modules", {"requests": real_requests}):
219+
with patch("requests.get", return_value=FakeResponse()):
220+
results = list(fetch_firmware_files([fw], tmp_path, force=True))
221+
222+
assert len(results) == 1
223+
result_path = results[0].firmware_file
224+
# Path must use forward slashes (POSIX), never backslashes
225+
assert "\\" not in result_path, f"firmware_file should use POSIX separators, got: {result_path}"
226+
assert result_path == "esp32/ESP32_GENERIC-v1.28.0.bin"

0 commit comments

Comments
 (0)