Devtools: add more test cases (#36910)

This patch adds test cases for Debugger > Sources with inline module
scripts and external classic scripts. We also fix the big test case in
#36401 to receive and assert the worker script URLs.

Testing: this patch improves test coverage for the Debugger > Sources
feature
Part of: #36325

---------

Signed-off-by: Delan Azabani <dazabani@igalia.com>
Signed-off-by: atbrakhi <atbrakhi@igalia.com>
Co-authored-by: atbrakhi <atbrakhi@igalia.com>
This commit is contained in:
delan azabani 2025-05-09 16:10:00 +08:00 committed by GitHub
parent 063bfc761e
commit c6f61e6b6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -25,6 +25,10 @@ from typing import Optional
import unittest import unittest
# Set this to true to log requests in the internal web servers.
LOG_REQUESTS = False
class DevtoolsTests(unittest.IsolatedAsyncioTestCase): class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
# /path/to/servo/python/servo # /path/to/servo/python/servo
script_path = None script_path = None
@ -36,23 +40,49 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
self.web_server = None self.web_server = None
self.web_server_thread = None self.web_server_thread = None
# Classic script vs module script:
# - <https://html.spec.whatwg.org/multipage/#classic-script>
# - <https://html.spec.whatwg.org/multipage/#module-script>
# Worker scripts can be classic or module:
# - <https://html.spec.whatwg.org/multipage/#fetch-a-classic-worker-script>
# - <https://html.spec.whatwg.org/multipage/#fetch-a-module-worker-script-tree>
# Non-worker(?) script sources can be inline, external, or blob.
# Worker script sources can be external or blob.
def test_sources_list(self): def test_sources_list(self):
self.run_servoshell(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources")) self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources"))
self.assert_sources_list([f"{self.base_url}/classic.js", f"{self.base_url}/test.html", "https://servo.org/js/load-table.js"]) self.run_servoshell()
self.assert_sources_list(2, set([
tuple([f"{self.base_url}/classic.js", f"{self.base_url}/test.html", "https://servo.org/js/load-table.js"]),
tuple([f"{self.base_url}/worker.js"]),
]))
def test_sources_list_with_data_no_scripts(self): def test_sources_list_with_data_no_scripts(self):
self.run_servoshell(url="data:text/html,") self.run_servoshell(url="data:text/html,")
self.assert_sources_list([]) self.assert_sources_list(1, set([tuple()]))
def test_sources_list_with_data_empty_inline_script(self): def test_sources_list_with_data_empty_inline_classic_script(self):
self.run_servoshell(url="data:text/html,<script></script>") self.run_servoshell(url="data:text/html,<script></script>")
self.assert_sources_list([]) self.assert_sources_list(1, set([tuple()]))
def test_sources_list_with_data_inline_script(self): def test_sources_list_with_data_inline_classic_script(self):
self.run_servoshell(url="data:text/html,<script>;</script>") self.run_servoshell(url="data:text/html,<script>;</script>")
self.assert_sources_list(["data:text/html,<script>;</script>"]) self.assert_sources_list(1, set([tuple(["data:text/html,<script>;</script>"])]))
def run_servoshell(self, *, test_dir=None, url=None): def test_sources_list_with_data_external_classic_script(self):
self.start_web_server(test_dir=os.path.join(DevtoolsTests.script_path, "devtools_tests/sources"))
self.run_servoshell(url=f"data:text/html,<script src=\"{self.base_url}/classic.js\"></script>")
self.assert_sources_list(1, set([tuple([f"{self.base_url}/classic.js"])]))
def test_sources_list_with_data_empty_inline_module_script(self):
self.run_servoshell(url="data:text/html,<script type=module></script>")
self.assert_sources_list(1, set([tuple()]))
def test_sources_list_with_data_inline_module_script(self):
self.run_servoshell(url="data:text/html,<script type=module>;</script>")
self.assert_sources_list(1, set([tuple(["data:text/html,<script type=module>;</script>"])]))
# Sets `base_url` and `web_server` and `web_server_thread`.
def start_web_server(self, *, test_dir=None):
if test_dir is None: if test_dir is None:
test_dir = os.path.join(DevtoolsTests.script_path, "devtools_tests") test_dir = os.path.join(DevtoolsTests.script_path, "devtools_tests")
base_url = Future() base_url = Future()
@ -62,9 +92,8 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
super().__init__(*args, directory=test_dir, **kwargs) super().__init__(*args, directory=test_dir, **kwargs)
def log_message(self, format, *args): def log_message(self, format, *args):
# Uncomment this to log requests. if LOG_REQUESTS:
# return super().log_message(format, *args) return super().log_message(format, *args)
pass
def server_thread(): def server_thread():
self.web_server = socketserver.TCPServer(("0.0.0.0", 0), Handler) self.web_server = socketserver.TCPServer(("0.0.0.0", 0), Handler)
@ -76,6 +105,8 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
self.web_server_thread.start() self.web_server_thread.start()
self.base_url = base_url.result(1) self.base_url = base_url.result(1)
# Sets `servoshell`.
def run_servoshell(self, *, url=None):
# Change this setting if you want to debug Servo. # Change this setting if you want to debug Servo.
os.environ["RUST_LOG"] = "error,devtools=warn" os.environ["RUST_LOG"] = "error,devtools=warn"
@ -89,13 +120,21 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
def tearDown(self): def tearDown(self):
# Terminate servoshell. # Terminate servoshell.
self.servoshell.terminate() if self.servoshell is not None:
self.servoshell.terminate()
self.servoshell = None
# Stop the web server. # Stop the web server.
self.web_server.shutdown() if self.web_server is not None:
self.web_server_thread.join() self.web_server.shutdown()
self.web_server = None
if self.web_server_thread is not None:
self.web_server_thread.join()
self.web_server_thread = None
if self.base_url is not None:
self.base_url = None
def assert_sources_list(self, expected_urls): def assert_sources_list(self, expected_targets: int, expected_urls_by_target: set[tuple[str]]):
client = RDPClient() client = RDPClient()
client.connect("127.0.0.1", 6080) client.connect("127.0.0.1", 6080)
root = RootActor(client) root = RootActor(client)
@ -105,41 +144,59 @@ class DevtoolsTests(unittest.IsolatedAsyncioTestCase):
watcher = tab.get_watcher() watcher = tab.get_watcher()
watcher = WatcherActor(client, watcher["actor"]) watcher = WatcherActor(client, watcher["actor"])
target = Future() done = Future()
targets = []
def on_target(data): def on_target(data):
if data["target"]["browsingContextID"] == tab_dict["browsingContextID"]: try:
target.set_result(data["target"]) targets.append(data["target"])
if len(targets) == expected_targets:
done.set_result(None)
except Exception as e:
# Raising here does nothing, for some reason.
# Send the exception back so it can be raised.
done.set_result(e)
client.add_event_listener( client.add_event_listener(
watcher.actor_id, Events.Watcher.TARGET_AVAILABLE_FORM, on_target, watcher.actor_id, Events.Watcher.TARGET_AVAILABLE_FORM, on_target,
) )
watcher.watch_targets(WatcherActor.Targets.FRAME) watcher.watch_targets(WatcherActor.Targets.FRAME)
watcher.watch_targets(WatcherActor.Targets.WORKER)
result: Optional[Exception] = done.result(1)
if result:
raise result
done = Future() done = Future()
target = target.result(1) # NOTE: breaks if two targets have the same list of source urls.
# This should really be a multiset, but Python does not have multisets.
actual_urls_by_target: set[tuple[str]] = set()
def on_source_resource(data): def on_source_resource(data):
for [resource_type, sources] in data["array"]: for [resource_type, sources] in data["array"]:
try: try:
self.assertEqual(resource_type, "source") self.assertEqual(resource_type, "source")
self.assertEqual([source["url"] for source in sources], expected_urls) source_urls = tuple([source["url"] for source in sources])
done.set_result(None) self.assertFalse(source_urls in sources) # See NOTE above
actual_urls_by_target.add(source_urls)
if len(actual_urls_by_target) == expected_targets:
done.set_result(None)
except Exception as e: except Exception as e:
# Raising here does nothing, for some reason. # Raising here does nothing, for some reason.
# Send the exception back so it can be raised. # Send the exception back so it can be raised.
done.set_result(e) done.set_result(e)
client.add_event_listener( for target in targets:
target["actor"], client.add_event_listener(
Events.Watcher.RESOURCES_AVAILABLE_ARRAY, target["actor"],
on_source_resource, Events.Watcher.RESOURCES_AVAILABLE_ARRAY,
) on_source_resource,
)
watcher.watch_resources([Resources.SOURCE]) watcher.watch_resources([Resources.SOURCE])
result: Optional[Exception] = done.result(1) result: Optional[Exception] = done.result(1)
if result: if result:
raise result raise result
self.assertEqual(actual_urls_by_target, expected_urls_by_target)
client.disconnect() client.disconnect()