mirror of
https://github.com/lutris/lutris.git
synced 2026-07-31 07:35:44 -04:00
Removed the importlib machinery used in `_test_gog_dlcs.py` and `_test_collection_progress.py` to fix issues with due to test module discovery order causing bespoke mocked modules to be placed in the sys.modules. Instead the builtin Python UnitTest Mock and Patch framework is used to properly patch the methods needed to run the UnitTest instead of manipulating the importlibs and a bespoke manner. Added missing `__init__.py` to the `lutris/tests/util` directory to allow discovery of those UnitTest by the nose2 and the Python builtin UnitTest framework. Those test weren't not being run by either `nose2` nor the `UnitTest` framework. Fixed the UnitTest that imports the pytest module. Replaced the usage of `pytest` inside of the `lutris/tests/util` directory to use the python UnitTest framework. Lutris does NOT specify a dependency on `pytest `and therefore when those test were actually enabled to be run, they would complain about the missing `pytest` import.
623 lines
24 KiB
Python
623 lines
24 KiB
Python
"""Tests for DownloadCollectionProgressBox multi-file parallel downloads.
|
|
|
|
These tests verify the prefetch-one concurrent download model, aggregate
|
|
progress, per-file error handling, cancel-all, and edge cases — all
|
|
without a running GTK display (GTK widgets are mocked).
|
|
|
|
The module is loaded via importlib to avoid the normal lutris.gui package
|
|
chain that requires a real GTK installation / display.
|
|
"""
|
|
|
|
from unittest import TestCase
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from lutris.gui.widgets.download_collection_progress_box import (
|
|
MAX_CONCURRENT_FILES,
|
|
DownloadCollectionProgressBox,
|
|
_ActiveDownload,
|
|
)
|
|
|
|
# ── Helpers ──────────────────────────────────────────────────────────
|
|
|
|
|
|
def _make_file(
|
|
filename="game.bin",
|
|
url="https://cdn.example.com/game.bin",
|
|
dest="/tmp/downloads/game.bin",
|
|
size=1000,
|
|
downloader_class=None,
|
|
referer=None,
|
|
):
|
|
"""Create a mock InstallerFile."""
|
|
f = MagicMock()
|
|
f.filename = filename
|
|
f.url = url
|
|
f.dest_file = dest
|
|
f.tmp_file = None
|
|
f.referer = referer
|
|
if downloader_class:
|
|
f.downloader_class = downloader_class
|
|
else:
|
|
# Simulate no downloader_class attribute: getattr returns None
|
|
del f.downloader_class
|
|
return f
|
|
|
|
|
|
def _make_collection(files, human_url="Test Collection"):
|
|
"""Create a mock InstallerFileCollection."""
|
|
coll = MagicMock()
|
|
coll.files_list = files
|
|
coll.human_url = human_url
|
|
coll.num_files = len(files)
|
|
coll.full_size = sum(getattr(f, "_size", 1000) for f in files)
|
|
return coll
|
|
|
|
|
|
def _make_downloader(state="DOWNLOADING", downloaded_size=0):
|
|
"""Create a mock Downloader with state constants."""
|
|
dl = MagicMock()
|
|
dl.DOWNLOADING = "DOWNLOADING"
|
|
dl.COMPLETED = "COMPLETED"
|
|
dl.CANCELLED = "CANCELLED"
|
|
dl.ERROR = "ERROR"
|
|
dl.state = state
|
|
dl.downloaded_size = downloaded_size
|
|
dl.error = None
|
|
dl.dest = "/tmp/test.tmp"
|
|
return dl
|
|
|
|
|
|
# ── Fixture ──────────────────────────────────────────────────────────
|
|
|
|
|
|
def three_files():
|
|
"""Three files for prefetch boundary test."""
|
|
return [
|
|
_make_file("a.bin", dest="/tmp/dl/a.bin"),
|
|
_make_file("b.bin", dest="/tmp/dl/b.bin"),
|
|
_make_file("c.bin", dest="/tmp/dl/c.bin"),
|
|
]
|
|
|
|
|
|
# ── Tests: _ActiveDownload ───────────────────────────────────────────
|
|
|
|
|
|
class TestActiveDownload(TestCase):
|
|
def test_init_sets_fields(self):
|
|
f = _make_file()
|
|
dl = _make_downloader()
|
|
ad = _ActiveDownload(f, dl)
|
|
assert ad.file is f
|
|
assert ad.downloader is dl
|
|
assert ad.num_retries == 0
|
|
|
|
def test_retry_count_increments(self):
|
|
ad = _ActiveDownload(_make_file(), _make_downloader())
|
|
ad.num_retries += 1
|
|
assert ad.num_retries == 1
|
|
|
|
|
|
# ── Tests: Initialisation ───────────────────────────────────────────
|
|
|
|
|
|
class TestInit(TestCase):
|
|
def test_active_downloads_initially_empty(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._active_downloads = []
|
|
assert box._active_downloads == []
|
|
|
|
def test_completed_sizes_initially_empty(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._completed_sizes = {}
|
|
assert box._completed_sizes == {}
|
|
|
|
def test_max_concurrent_files_is_two(self):
|
|
assert MAX_CONCURRENT_FILES == 2
|
|
|
|
|
|
# ── Tests: _create_downloader ───────────────────────────────────────
|
|
|
|
|
|
class TestCreateDownloader(TestCase):
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.create_cache_lock")
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.SimpleDownloader")
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.os.path.exists", return_value=False)
|
|
def test_creates_downloader_with_correct_args(self, mock_exists, MockDL, mock_lock):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
f = _make_file(url="https://cdn.example.com/f.bin", dest="/tmp/dl/f.bin")
|
|
|
|
box._create_downloader(f)
|
|
|
|
MockDL.assert_called_once_with(
|
|
"https://cdn.example.com/f.bin",
|
|
"/tmp/dl/f.bin.tmp",
|
|
referer=None,
|
|
overwrite=True,
|
|
)
|
|
assert f.tmp_file == "/tmp/dl/f.bin.tmp"
|
|
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.create_cache_lock")
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.SimpleDownloader")
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.os.path.exists", return_value=True)
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.os.remove")
|
|
def test_removes_existing_tmp_file(self, mock_remove, mock_exists, MockDL, mock_lock):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
f = _make_file(dest="/tmp/dl/f.bin")
|
|
|
|
box._create_downloader(f)
|
|
mock_remove.assert_called_once_with("/tmp/dl/f.bin.tmp")
|
|
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.create_cache_lock")
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.os.path.exists", return_value=False)
|
|
def test_uses_custom_downloader_class(self, mock_exists, mock_lock):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
custom_dl_cls = MagicMock()
|
|
f = _make_file(dest="/tmp/dl/f.bin")
|
|
f.downloader_class = custom_dl_cls
|
|
|
|
box._create_downloader(f)
|
|
custom_dl_cls.assert_called_once()
|
|
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.display_error")
|
|
@patch("lutris.gui.widgets.download_collection_progress_box.os.path.exists", return_value=False)
|
|
def test_returns_none_on_runtime_error(self, mock_exists, mock_display):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box.get_toplevel = MagicMock(return_value=None)
|
|
f = _make_file(dest="/tmp/dl/f.bin")
|
|
# Simulate no downloader_class → use default Downloader which will fail
|
|
with patch(
|
|
"lutris.gui.widgets.download_collection_progress_box.SimpleDownloader",
|
|
side_effect=RuntimeError("oops"),
|
|
):
|
|
result = box._create_downloader(f)
|
|
assert result is None
|
|
mock_display.assert_called_once()
|
|
|
|
|
|
# ── Tests: _pop_next_downloadable_file ───────────────────────────────
|
|
|
|
|
|
class TestPopNextDownloadableFile(TestCase):
|
|
def test_returns_file_when_not_cached(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
f = _make_file(dest="/tmp/dl/notexist.bin")
|
|
box._file_queue = [f]
|
|
box.num_files_downloaded = 0
|
|
box._completed_sizes = {}
|
|
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.os.path.exists", return_value=False):
|
|
result = box._pop_next_downloadable_file()
|
|
assert result is f
|
|
|
|
def test_skips_cached_file(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
cached = _make_file("cached.bin", dest="/tmp/dl/cached.bin")
|
|
fresh = _make_file("fresh.bin", dest="/tmp/dl/fresh.bin")
|
|
box._file_queue = [fresh, cached] # pop() takes from end
|
|
box.num_files_downloaded = 0
|
|
box._completed_sizes = {}
|
|
|
|
def exists_side(path):
|
|
return path == "/tmp/dl/cached.bin"
|
|
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.os.path.exists", side_effect=exists_side):
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.os.path.getsize", return_value=500):
|
|
result = box._pop_next_downloadable_file()
|
|
|
|
assert result is fresh
|
|
assert box.num_files_downloaded == 1
|
|
assert box._completed_sizes["/tmp/dl/cached.bin"] == 500
|
|
|
|
def test_returns_none_when_empty(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._file_queue = []
|
|
box.num_files_downloaded = 0
|
|
box._completed_sizes = {}
|
|
assert box._pop_next_downloadable_file() is None
|
|
|
|
|
|
# ── Tests: Aggregate progress ────────────────────────────────────────
|
|
|
|
|
|
class TestAggregateDownloadedSize(TestCase):
|
|
def test_sums_completed_and_active(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._completed_sizes = {"/a": 100, "/b": 200}
|
|
|
|
dl1 = _make_downloader(downloaded_size=50)
|
|
dl2 = _make_downloader(downloaded_size=75)
|
|
ad1 = _ActiveDownload(_make_file(), dl1)
|
|
ad2 = _ActiveDownload(_make_file(), dl2)
|
|
box._active_downloads = [ad1, ad2]
|
|
|
|
assert box._aggregate_downloaded_size() == 100 + 200 + 50 + 75
|
|
|
|
def test_empty_state(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._completed_sizes = {}
|
|
box._active_downloads = []
|
|
assert box._aggregate_downloaded_size() == 0
|
|
|
|
|
|
# ── Tests: File labels ───────────────────────────────────────────────
|
|
|
|
|
|
class TestUpdateActiveFileLabels(TestCase):
|
|
def test_comma_separated_names(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box.file_name_label = MagicMock()
|
|
|
|
f1 = _make_file("part1.bin")
|
|
f2 = _make_file("part2.bin")
|
|
box._active_downloads = [
|
|
_ActiveDownload(f1, _make_downloader()),
|
|
_ActiveDownload(f2, _make_downloader()),
|
|
]
|
|
|
|
box._update_active_file_labels()
|
|
box.file_name_label.set_text.assert_called_once_with("part1.bin, part2.bin")
|
|
|
|
def test_single_name(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box.file_name_label = MagicMock()
|
|
|
|
f = _make_file("only.bin")
|
|
box._active_downloads = [_ActiveDownload(f, _make_downloader())]
|
|
|
|
box._update_active_file_labels()
|
|
box.file_name_label.set_text.assert_called_once_with("only.bin")
|
|
|
|
def test_empty_when_no_active(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box.file_name_label = MagicMock()
|
|
box._active_downloads = []
|
|
|
|
box._update_active_file_labels()
|
|
box.file_name_label.set_text.assert_called_once_with("")
|
|
|
|
|
|
# ── Tests: Start / prefetch ──────────────────────────────────────────
|
|
|
|
|
|
class TestStartPrefetch(TestCase):
|
|
def setUp(self):
|
|
self.three_files = three_files()
|
|
|
|
def test_start_launches_two_downloads(self):
|
|
"""start() should launch primary + prefetch = 2 concurrent downloads."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._file_queue = self.three_files.copy()
|
|
box.downloader = None
|
|
box.is_complete = False
|
|
box.num_files_downloaded = 0
|
|
box._active_downloads = []
|
|
box._completed_sizes = {}
|
|
box.cancel_button = MagicMock()
|
|
box.file_name_label = MagicMock()
|
|
box.emit = MagicMock()
|
|
|
|
mock_dl = _make_downloader()
|
|
with patch.object(box, "_create_downloader", return_value=mock_dl):
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.schedule_repeating_at_idle"):
|
|
box.start()
|
|
|
|
assert len(box._active_downloads) == 2
|
|
# One file left in queue
|
|
assert len(box._file_queue) == 1
|
|
|
|
def test_start_emits_complete_when_no_files(self):
|
|
"""start() with empty queue should emit complete."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._file_queue = []
|
|
box.downloader = None
|
|
box.is_complete = False
|
|
box._active_downloads = []
|
|
box._completed_sizes = {}
|
|
box.cancel_button = MagicMock()
|
|
box.emit = MagicMock()
|
|
|
|
with patch.object(box, "_pop_next_downloadable_file", return_value=None):
|
|
box.start()
|
|
|
|
box.emit.assert_called_once_with("complete", {})
|
|
assert box.is_complete is True
|
|
|
|
def test_prefetch_respects_max_concurrent(self):
|
|
"""_start_prefetch() should not exceed MAX_CONCURRENT_FILES."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._file_queue = [_make_file(), _make_file()]
|
|
box._active_downloads = [
|
|
_ActiveDownload(_make_file(), _make_downloader()),
|
|
_ActiveDownload(_make_file(), _make_downloader()),
|
|
]
|
|
box.file_name_label = MagicMock()
|
|
|
|
box._start_prefetch()
|
|
# Should NOT have added more
|
|
assert len(box._active_downloads) == 2
|
|
|
|
def test_prefetch_starts_when_room(self):
|
|
"""_start_prefetch() launches download when under limit."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
f = _make_file(dest="/tmp/dl/next.bin")
|
|
box._file_queue = [f]
|
|
box._active_downloads = [_ActiveDownload(_make_file(), _make_downloader())]
|
|
box.file_name_label = MagicMock()
|
|
box.num_files_downloaded = 0
|
|
box._completed_sizes = {}
|
|
|
|
mock_dl = _make_downloader()
|
|
with patch.object(box, "_create_downloader", return_value=mock_dl):
|
|
with patch(
|
|
"lutris.gui.widgets.download_collection_progress_box.os.path.exists",
|
|
return_value=False,
|
|
):
|
|
box._start_prefetch()
|
|
|
|
assert len(box._active_downloads) == 2
|
|
|
|
|
|
# ── Tests: Progress callback ─────────────────────────────────────────
|
|
|
|
|
|
class TestProgress(TestCase):
|
|
def _setup_box(self):
|
|
"""Create a box ready for _progress() calls."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._active_downloads = []
|
|
box._completed_sizes = {}
|
|
box._file_queue = []
|
|
box.full_size = 2000
|
|
box.num_files_downloaded = 0
|
|
box.is_complete = False
|
|
box.downloader = None
|
|
box.progressbar = MagicMock()
|
|
box.progress_label = MagicMock()
|
|
box.cancel_button = MagicMock()
|
|
box.file_name_label = MagicMock()
|
|
box.emit = MagicMock()
|
|
box.time_left = "00:00:00"
|
|
box.time_left_check_time = 0
|
|
box.last_size = 0
|
|
box.avg_speed = 0
|
|
box.speed_list = []
|
|
return box
|
|
|
|
def test_returns_false_when_no_active(self):
|
|
box = self._setup_box()
|
|
assert box._progress() is False
|
|
|
|
def test_returns_true_when_downloading(self):
|
|
box = self._setup_box()
|
|
dl = _make_downloader(state="DOWNLOADING", downloaded_size=500)
|
|
ad = _ActiveDownload(_make_file(), dl)
|
|
box._active_downloads = [ad]
|
|
box._file_queue = [_make_file()] # still more to do
|
|
|
|
result = box._progress()
|
|
assert result is True
|
|
|
|
def test_completed_file_moves_to_completed_sizes(self):
|
|
box = self._setup_box()
|
|
dl = _make_downloader(state="COMPLETED", downloaded_size=1000)
|
|
f = _make_file(dest="/tmp/dl/done.bin")
|
|
f.tmp_file = "/tmp/dl/done.bin.tmp"
|
|
ad = _ActiveDownload(f, dl)
|
|
box._active_downloads = [ad]
|
|
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.os.rename") as mock_rename:
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.update_cache_lock"):
|
|
box._progress()
|
|
|
|
assert box._completed_sizes["/tmp/dl/done.bin"] == 1000
|
|
assert box.num_files_downloaded == 1
|
|
mock_rename.assert_called_once_with("/tmp/dl/done.bin.tmp", "/tmp/dl/done.bin")
|
|
|
|
def test_completion_triggers_prefetch(self):
|
|
box = self._setup_box()
|
|
dl = _make_downloader(state="COMPLETED", downloaded_size=500)
|
|
f = _make_file(dest="/tmp/dl/done.bin")
|
|
f.tmp_file = "/tmp/dl/done.bin.tmp"
|
|
ad = _ActiveDownload(f, dl)
|
|
box._active_downloads = [ad]
|
|
box._file_queue = [_make_file()] # one more in queue
|
|
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.os.rename"):
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.update_cache_lock"):
|
|
with patch.object(box, "_start_prefetch") as mock_prefetch:
|
|
box._progress()
|
|
|
|
mock_prefetch.assert_called_once()
|
|
|
|
def test_all_done_emits_complete(self):
|
|
box = self._setup_box()
|
|
dl = _make_downloader(state="COMPLETED", downloaded_size=1000)
|
|
f = _make_file(dest="/tmp/dl/last.bin")
|
|
f.tmp_file = "/tmp/dl/last.bin.tmp"
|
|
ad = _ActiveDownload(f, dl)
|
|
box._active_downloads = [ad]
|
|
box._file_queue = [] # nothing left
|
|
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.os.rename"):
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.update_cache_lock"):
|
|
result = box._progress()
|
|
|
|
assert result is False
|
|
box.emit.assert_called_with("complete", {})
|
|
assert box.is_complete is True
|
|
|
|
def test_cancelled_state_cancels_all(self):
|
|
box = self._setup_box()
|
|
dl1 = _make_downloader(state="CANCELLED")
|
|
dl2 = _make_downloader(state="DOWNLOADING")
|
|
box._active_downloads = [
|
|
_ActiveDownload(_make_file(), dl1),
|
|
_ActiveDownload(_make_file(), dl2),
|
|
]
|
|
|
|
result = box._progress()
|
|
assert result is False
|
|
# Should cancel remaining downloads
|
|
dl2.cancel.assert_called()
|
|
|
|
def test_error_retries_independently(self):
|
|
box = self._setup_box()
|
|
dl = _make_downloader(state="ERROR")
|
|
dl.error = RuntimeError("network failure")
|
|
f = _make_file(dest="/tmp/dl/fail.bin")
|
|
ad = _ActiveDownload(f, dl)
|
|
ad.num_retries = 0
|
|
box._active_downloads = [ad]
|
|
box._file_queue = [_make_file()] # keep loop going
|
|
|
|
new_dl = _make_downloader(state="DOWNLOADING")
|
|
with patch.object(box, "_create_downloader", return_value=new_dl):
|
|
box._progress()
|
|
|
|
assert ad.num_retries == 1
|
|
assert ad.downloader is new_dl
|
|
new_dl.start.assert_called_once()
|
|
|
|
def test_error_exhausts_retries_emits_error(self):
|
|
box = self._setup_box()
|
|
dl = _make_downloader(state="ERROR")
|
|
dl.error = RuntimeError("permanent failure")
|
|
f = _make_file(dest="/tmp/dl/fail.bin")
|
|
ad = _ActiveDownload(f, dl)
|
|
ad.num_retries = 3 # equals max_retries
|
|
box._active_downloads = [ad]
|
|
|
|
result = box._progress()
|
|
assert result is False
|
|
box.emit.assert_called_with("error", dl.error)
|
|
|
|
|
|
# ── Tests: Cancel ────────────────────────────────────────────────────
|
|
|
|
|
|
class TestCancel(TestCase):
|
|
def test_cancel_stops_all_active(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
dl1 = _make_downloader()
|
|
dl2 = _make_downloader()
|
|
box._active_downloads = [
|
|
_ActiveDownload(_make_file(), dl1),
|
|
_ActiveDownload(_make_file(), dl2),
|
|
]
|
|
box.downloader = MagicMock()
|
|
box.cancel_button = MagicMock()
|
|
box.emit = MagicMock()
|
|
|
|
box.on_cancel_clicked()
|
|
|
|
dl1.cancel.assert_called_once()
|
|
dl2.cancel.assert_called_once()
|
|
assert box._active_downloads == []
|
|
assert box.downloader is None
|
|
box.emit.assert_called_once_with("cancel")
|
|
|
|
def test_cancel_all_internal(self):
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
dl = _make_downloader()
|
|
box._active_downloads = [_ActiveDownload(_make_file(), dl)]
|
|
box.cancel_button = MagicMock()
|
|
box.downloader = MagicMock()
|
|
|
|
box._cancel_all()
|
|
|
|
dl.cancel.assert_called_once()
|
|
assert box._active_downloads == []
|
|
assert box.downloader is None
|
|
|
|
|
|
# ── Tests: Edge cases ────────────────────────────────────────────────
|
|
|
|
|
|
class TestEdgeCases(TestCase):
|
|
def test_single_file_collection(self):
|
|
"""A single-file collection should work normally without prefetch."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
f = _make_file(dest="/tmp/dl/only.bin")
|
|
box._file_queue = [f]
|
|
box.downloader = None
|
|
box.is_complete = False
|
|
box.num_files_downloaded = 0
|
|
box._active_downloads = []
|
|
box._completed_sizes = {}
|
|
box.cancel_button = MagicMock()
|
|
box.file_name_label = MagicMock()
|
|
box.emit = MagicMock()
|
|
|
|
mock_dl = _make_downloader()
|
|
with patch.object(box, "_create_downloader", return_value=mock_dl):
|
|
with patch(
|
|
"lutris.gui.widgets.download_collection_progress_box.os.path.exists",
|
|
return_value=False,
|
|
):
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.schedule_repeating_at_idle"):
|
|
box.start()
|
|
|
|
# Only 1 active (no prefetch since queue is empty)
|
|
assert len(box._active_downloads) == 1
|
|
|
|
def test_all_cached_emits_complete(self):
|
|
"""When all files exist in cache, should immediately complete."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box.downloader = None
|
|
box.is_complete = False
|
|
box.num_files_downloaded = 0
|
|
box._active_downloads = []
|
|
box._completed_sizes = {}
|
|
box.cancel_button = MagicMock()
|
|
box.file_name_label = MagicMock()
|
|
box.emit = MagicMock()
|
|
|
|
f1 = _make_file(dest="/tmp/dl/cached1.bin")
|
|
f2 = _make_file(dest="/tmp/dl/cached2.bin")
|
|
box._file_queue = [f1, f2]
|
|
|
|
with patch(
|
|
"lutris.gui.widgets.download_collection_progress_box.os.path.exists",
|
|
return_value=True,
|
|
):
|
|
with patch(
|
|
"lutris.gui.widgets.download_collection_progress_box.os.path.getsize",
|
|
return_value=500,
|
|
):
|
|
box.start()
|
|
|
|
box.emit.assert_called_with("complete", {})
|
|
assert box.is_complete is True
|
|
assert box.num_files_downloaded == 2
|
|
|
|
def test_retry_button_clears_active_downloads(self):
|
|
"""on_retry_clicked should clear active downloads and restart."""
|
|
box = DownloadCollectionProgressBox.__new__(DownloadCollectionProgressBox)
|
|
box._active_downloads = [
|
|
_ActiveDownload(_make_file(), _make_downloader()),
|
|
]
|
|
box.downloader = _make_downloader()
|
|
box.cancel_cb_id = 123
|
|
box.cancel_button = MagicMock()
|
|
box._file_queue = [_make_file()]
|
|
box.is_complete = False
|
|
box.num_files_downloaded = 0
|
|
box._completed_sizes = {}
|
|
box.file_name_label = MagicMock()
|
|
box.emit = MagicMock()
|
|
|
|
mock_dl = _make_downloader()
|
|
button = MagicMock()
|
|
button.connect = MagicMock(return_value=456)
|
|
|
|
with patch.object(box, "_create_downloader", return_value=mock_dl):
|
|
with patch(
|
|
"lutris.gui.widgets.download_collection_progress_box.os.path.exists",
|
|
return_value=False,
|
|
):
|
|
with patch("lutris.gui.widgets.download_collection_progress_box.schedule_repeating_at_idle"):
|
|
box.on_retry_clicked(button)
|
|
|
|
# Active downloads should have been cleared before restart
|
|
assert len(box._active_downloads) >= 1 # re-populated by start()
|