Add Chromium-only Blender WebEngine parity work
This commit is contained in:
318
blender-5.2.0/tests/python/bl_http_downloader.py
Normal file
318
blender-5.2.0/tests/python/bl_http_downloader.py
Normal file
@@ -0,0 +1,318 @@
|
||||
# SPDX-FileCopyrightText: 2025 Blender Authors
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
blender -b --factory-startup -P tests/python/bl_http_downloader.py -- --verbose
|
||||
"""
|
||||
|
||||
|
||||
__all__ = (
|
||||
"main",
|
||||
)
|
||||
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from _bpy_internal.http.downloader import RequestDescription
|
||||
|
||||
|
||||
output_dir: Path
|
||||
|
||||
|
||||
class BasicImportTest(unittest.TestCase):
|
||||
"""Just do a basic import and instantiation of classes.
|
||||
|
||||
This doesn't test the functionality, but does ensure that dependencies like
|
||||
third-party libraries are available.
|
||||
"""
|
||||
|
||||
def test_downloader(self) -> None:
|
||||
from _bpy_internal.http import downloader as http_dl
|
||||
|
||||
metadata_provider = http_dl.MetadataProviderFilesystem(
|
||||
cache_location=output_dir / "http_metadata"
|
||||
)
|
||||
downloader = http_dl.ConditionalDownloader(metadata_provider=metadata_provider)
|
||||
self.assertIsNotNone(downloader)
|
||||
|
||||
def test_background_downloader(self) -> None:
|
||||
from _bpy_internal.http import downloader as http_dl
|
||||
|
||||
metadata_provider = http_dl.MetadataProviderFilesystem(
|
||||
cache_location=output_dir / "http_metadata"
|
||||
)
|
||||
options = http_dl.DownloaderOptions(
|
||||
metadata_provider=metadata_provider,
|
||||
timeout=1,
|
||||
http_headers={'X-Unit-Test': self.__class__.__name__}
|
||||
)
|
||||
|
||||
def on_error(req_desc: http_dl.RequestDescription, local_path: Path, ex: Exception) -> None:
|
||||
self.fail(f"unexpected call to on_error({req_desc}, {local_path}, {ex})")
|
||||
|
||||
downloader = http_dl.BackgroundDownloader(
|
||||
options=options,
|
||||
on_callback_error=on_error,
|
||||
)
|
||||
|
||||
# Test some trivial properties that don't require anything running.
|
||||
self.assertTrue(downloader.all_downloads_done)
|
||||
self.assertEqual(0, downloader.num_pending_downloads)
|
||||
self.assertFalse(downloader.is_shutdown_requested)
|
||||
self.assertFalse(downloader.is_shutdown_complete)
|
||||
self.assertFalse(downloader.is_subprocess_alive)
|
||||
|
||||
|
||||
class RequestDescriptionTest(unittest.TestCase):
|
||||
def test_comparison(self) -> None:
|
||||
from _bpy_internal.http import downloader as http_dl
|
||||
|
||||
url = "http://example.com/download.me"
|
||||
http_descr = http_dl.RequestDescription("GET", url)
|
||||
|
||||
# Equality.
|
||||
self.assertEqual(http_descr, http_descr, "Same instance")
|
||||
self.assertEqual(
|
||||
http_descr,
|
||||
http_dl.RequestDescription("GET", url),
|
||||
"Same HTTP method & URL, different instance",
|
||||
)
|
||||
self.assertEqual(
|
||||
http_descr,
|
||||
http_dl.RequestDescription("GET", url, response_headers={"a": "b"}),
|
||||
"Same HTTP method & URL, different response headers",
|
||||
)
|
||||
|
||||
# Inequality.
|
||||
self.assertNotEqual(
|
||||
http_descr,
|
||||
http_dl.RequestDescription("DELETE", url),
|
||||
"Different HTTP method, same URL",
|
||||
)
|
||||
self.assertNotEqual(
|
||||
http_descr,
|
||||
http_dl.RequestDescription("GET", "http://example.com/another-file.md"),
|
||||
"Same HTTP method, different URL",
|
||||
)
|
||||
|
||||
# Safety with other types.
|
||||
self.assertNotEqual(http_descr, None, "comparison should be None-safe")
|
||||
self.assertNotEqual(http_descr, "some other type", "comparison should be safe for other types")
|
||||
|
||||
def test_hash(self) -> None:
|
||||
from _bpy_internal.http import downloader as http_dl
|
||||
|
||||
url = "http://example.com/download.me"
|
||||
http_descr = http_dl.RequestDescription("GET", url)
|
||||
|
||||
# Equality.
|
||||
self.assertEqual(hash(http_descr), hash(http_descr), "Same instance")
|
||||
self.assertEqual(
|
||||
hash(http_descr),
|
||||
hash(http_dl.RequestDescription("GET", url)),
|
||||
"Same HTTP method & URL, different instance",
|
||||
)
|
||||
self.assertEqual(
|
||||
hash(http_descr),
|
||||
hash(http_dl.RequestDescription("GET", url, response_headers={"a": "b"})),
|
||||
"Same HTTP method & URL, different response headers",
|
||||
)
|
||||
|
||||
# Inequality.
|
||||
self.assertNotEqual(
|
||||
hash(http_descr),
|
||||
hash(http_dl.RequestDescription("DELETE", url)),
|
||||
"Different HTTP method, same URL",
|
||||
)
|
||||
self.assertNotEqual(
|
||||
hash(http_descr),
|
||||
hash(http_dl.RequestDescription("GET", "http://example.com/another-file.md")),
|
||||
"Same HTTP method, different URL",
|
||||
)
|
||||
|
||||
# Safety with other types.
|
||||
self.assertNotEqual(hash(http_descr), hash(None))
|
||||
self.assertNotEqual(hash(RequestDescription("", "")), hash(None))
|
||||
|
||||
|
||||
class MaxDownloadSizeTest(unittest.TestCase):
|
||||
def test_max_size(self) -> None:
|
||||
from _bpy_internal.http import downloader as http_dl
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
metadata_provider = MagicMock(spec=http_dl.MetadataProvider)
|
||||
metadata_provider.load.return_value = None
|
||||
|
||||
downloader = http_dl.ConditionalDownloader(metadata_provider=metadata_provider)
|
||||
downloader.max_disk_size_bytes = 100
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.headers = {"Content-Length": "200", "Content-Type": "text/plain"}
|
||||
mock_response.status_code = 200
|
||||
# When used as context manager, return itself.
|
||||
mock_response.__enter__.return_value = mock_response
|
||||
|
||||
# NOTE: when this test fails, it will say "TypeError: a bytes-like object is required, not 'MagicMock'". This
|
||||
# indicates that the HTTP downloader tried to actually read bytes from the mock response, which it doesn't
|
||||
# support.
|
||||
|
||||
with patch.object(http_dl.requests.Session, 'send', return_value=mock_response):
|
||||
with self.assertRaises(http_dl.ContentLengthTooBigError):
|
||||
downloader.download_to_file("https://example.com/huge.json", Path("/tmp/huge.json"))
|
||||
|
||||
|
||||
class BackgroundDownloaderProcessTest(unittest.TestCase):
|
||||
"""Start & stop the background process for the BackgroundDownloader.
|
||||
|
||||
This doesn't test any HTTP requests, but does start & stop the background
|
||||
process to check that this is at least possible.
|
||||
"""
|
||||
|
||||
def test_start_stop(self) -> None:
|
||||
from _bpy_internal.http import downloader as http_dl
|
||||
|
||||
metadata_provider = http_dl.MetadataProviderFilesystem(
|
||||
cache_location=output_dir / "http_metadata"
|
||||
)
|
||||
options = http_dl.DownloaderOptions(
|
||||
metadata_provider=metadata_provider,
|
||||
timeout=1,
|
||||
http_headers={'X-Unit-Test': self.__class__.__name__}
|
||||
)
|
||||
|
||||
def on_error(req_desc: http_dl.RequestDescription, local_path: Path, ex: Exception) -> None:
|
||||
self.fail(f"unexpected call to on_error({req_desc}, {local_path}, {ex})")
|
||||
|
||||
downloader = http_dl.BackgroundDownloader(
|
||||
options=options,
|
||||
on_callback_error=on_error,
|
||||
)
|
||||
|
||||
# Queueing a download before the downloader has started should be rejected.
|
||||
with self.assertRaises(RuntimeError):
|
||||
downloader.queue_download("https://example.com/", output_dir / "download.tmp")
|
||||
|
||||
downloader.start()
|
||||
|
||||
try:
|
||||
self.assertFalse(downloader.is_shutdown_requested)
|
||||
self.assertFalse(downloader.is_shutdown_complete)
|
||||
self.assertTrue(downloader.is_subprocess_alive)
|
||||
|
||||
# For good measure, call the update function a few times to ensure that
|
||||
# any messages are sent. There shouldn't be any, but this should also
|
||||
# not be a problem.
|
||||
downloader.update()
|
||||
downloader.update()
|
||||
downloader.update()
|
||||
finally:
|
||||
# In case any of the pre-shutdown assertions fail, the downloader
|
||||
# should still be shut down.
|
||||
if downloader.is_subprocess_alive:
|
||||
downloader.shutdown()
|
||||
|
||||
downloader.shutdown()
|
||||
|
||||
self.assertTrue(downloader.is_shutdown_requested)
|
||||
self.assertTrue(downloader.is_shutdown_complete)
|
||||
self.assertFalse(downloader.is_subprocess_alive)
|
||||
|
||||
|
||||
class ConditionalDownloaderTest(unittest.TestCase):
|
||||
|
||||
def test_request_cancel(self) -> None:
|
||||
"""Request and immediately cancel a download.
|
||||
|
||||
This doesn't test actual HTTP traffic, but rather cancels the request
|
||||
at the first possible moment.
|
||||
"""
|
||||
from _bpy_internal.http import downloader as http_dl
|
||||
|
||||
# Create the downloader.
|
||||
metadata_provider = http_dl.MetadataProviderFilesystem(
|
||||
cache_location=output_dir / "http_metadata"
|
||||
)
|
||||
downloader = http_dl.ConditionalDownloader(metadata_provider=metadata_provider)
|
||||
|
||||
download_url = "http://localhost:47/download.me"
|
||||
download_path = output_dir / "the_download"
|
||||
|
||||
# Set up the periodic check callback function so it immediately cancels.
|
||||
expected_http_descr = http_dl.RequestDescription("GET", download_url)
|
||||
periodic_check_called = False
|
||||
|
||||
def periodic_check(http_req_descr: http_dl.RequestDescription) -> bool:
|
||||
nonlocal periodic_check_called
|
||||
|
||||
periodic_check_called = True
|
||||
self.assertEqual(expected_http_descr, http_req_descr)
|
||||
|
||||
return False
|
||||
downloader.periodic_check = periodic_check
|
||||
|
||||
# Set up a reporter.
|
||||
reporter = http_dl.QueueingReporter()
|
||||
downloader.add_reporter(reporter)
|
||||
|
||||
# Request the download.
|
||||
with self.assertRaises(http_dl.DownloadCancelled) as cancel_ex:
|
||||
_ = downloader.download_to_file(download_url, download_path)
|
||||
|
||||
# Check the result.
|
||||
self.assertFalse(download_path.exists())
|
||||
self.assertTrue(periodic_check_called)
|
||||
self.assertEqual(
|
||||
[
|
||||
('download_starts', (expected_http_descr, )),
|
||||
('download_error', (expected_http_descr, download_path, cancel_ex.exception)),
|
||||
],
|
||||
list(reporter._queue) # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
|
||||
|
||||
class HumanizeSizeTest(unittest.TestCase):
|
||||
def test_humanize_size(self):
|
||||
from _bpy_internal.http.downloader import humanize_size
|
||||
|
||||
self.assertEqual(humanize_size(0), "0 B")
|
||||
self.assertEqual(humanize_size(1), "1 B")
|
||||
self.assertEqual(humanize_size(1023), "1023 B")
|
||||
|
||||
self.assertEqual(humanize_size(1024), "1 KiB")
|
||||
self.assertEqual(humanize_size(1024**2), "1 MiB")
|
||||
self.assertEqual(humanize_size(1024**3), "1 GiB")
|
||||
self.assertEqual(humanize_size(1024**4), "1 TiB")
|
||||
|
||||
self.assertEqual(humanize_size(1536), "1.5 KiB")
|
||||
self.assertEqual(humanize_size(5046586573), "4.7 GiB")
|
||||
|
||||
# 47.0... GiB -> trailing zero stripped
|
||||
self.assertEqual(humanize_size(50465865728), "47 GiB")
|
||||
|
||||
# Beyond the last unit should also be ok.
|
||||
self.assertEqual(humanize_size(1024**7), "1024 EiB")
|
||||
self.assertEqual(humanize_size(1024**8), "1048576 EiB")
|
||||
|
||||
# Negative values should be handled like positive values.
|
||||
self.assertEqual(humanize_size(-1), "-1 B")
|
||||
self.assertEqual(humanize_size(-2 * 1024**2), "-2 MiB")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
global output_dir
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
argv = [sys.argv[0]]
|
||||
if '--' in sys.argv:
|
||||
argv.extend(sys.argv[sys.argv.index('--') + 1:])
|
||||
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
output_dir = Path(temp_dir)
|
||||
unittest.main(argv=argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user