Skip to content

Commit 9ea80f9

Browse files
committed
break thread test into 4 tests
1 parent 06c05a2 commit 9ea80f9

File tree

1 file changed

+127
-173
lines changed

1 file changed

+127
-173
lines changed

py/test/selenium/webdriver/common/bidi_browsing_context_tests.py

Lines changed: 127 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
# under the License.
1717

1818
import base64
19+
import concurrent.futures
1920
import threading
21+
import time
2022

2123
import pytest
2224

@@ -930,197 +932,149 @@ def on_context_created_2(info):
930932
driver.browsing_context.remove_event_handler("context_created", callback_id_2)
931933

932934

933-
def test_event_handler_thread_safety(driver):
934-
"""Test thread safety with multiple non-atomic operations in callbacks."""
935-
import concurrent.futures
936-
import time
937-
938-
events_received = []
939-
context_counts = {}
940-
event_type_counts = {}
941-
processing_times = []
942-
consistency_errors = []
943-
thread_errors = []
944-
945-
data_lock = threading.Lock()
946-
callback_ids = []
947-
registration_complete = threading.Event()
948-
949-
def complex_event_callback(info):
950-
"""Callback with multiple non-atomic operations that require thread synchronization."""
951-
start_time = time.time()
952-
time.sleep(0.02) # Create race condition window
953-
954-
with data_lock:
955-
# Multiple operations that could race without proper locking
956-
initial_event_count = len(events_received)
957-
_ = sum(context_counts.values()) if context_counts else 0
958-
_ = sum(event_type_counts.values()) if event_type_counts else 0
959-
960-
events_received.append(info)
961-
962-
context_id = info.context
963-
if context_id not in context_counts:
964-
context_counts[context_id] = 0
965-
context_counts[context_id] += 1
966-
967-
event_type = info.__class__.__name__
968-
if event_type not in event_type_counts:
969-
event_type_counts[event_type] = 0
970-
event_type_counts[event_type] += 1
971-
972-
processing_time = time.time() - start_time
973-
processing_times.append(processing_time)
974-
975-
# Verify data consistency
976-
final_event_count = len(events_received)
977-
final_context_total = sum(context_counts.values())
978-
final_type_total = sum(event_type_counts.values())
979-
final_processing_count = len(processing_times)
980-
981-
expected_count = initial_event_count + 1
982-
if not (
983-
final_event_count == final_context_total == final_type_total == final_processing_count == expected_count
984-
):
985-
error_msg = (
986-
f"Data consistency error! Events: {final_event_count}, "
987-
f"Contexts: {final_context_total}, Types: {final_type_total}, "
988-
f"Times: {final_processing_count}, Expected: {expected_count}"
989-
)
990-
consistency_errors.append(error_msg)
991-
992-
def register_handler(thread_id):
935+
class _EventHandlerTestHelper:
936+
def __init__(self, driver):
937+
self.driver = driver
938+
self.events_received = []
939+
self.context_counts = {}
940+
self.event_type_counts = {}
941+
self.processing_times = []
942+
self.consistency_errors = []
943+
self.thread_errors = []
944+
self.callback_ids = []
945+
self.data_lock = threading.Lock()
946+
self.registration_complete = threading.Event()
947+
948+
def make_callback(self):
949+
def callback(info):
950+
start_time = time.time()
951+
time.sleep(0.02) # Simulate race window
952+
953+
with self.data_lock:
954+
initial_event_count = len(self.events_received)
955+
956+
self.events_received.append(info)
957+
958+
context_id = info.context
959+
self.context_counts.setdefault(context_id, 0)
960+
self.context_counts[context_id] += 1
961+
962+
event_type = info.__class__.__name__
963+
self.event_type_counts.setdefault(event_type, 0)
964+
self.event_type_counts[event_type] += 1
965+
966+
processing_time = time.time() - start_time
967+
self.processing_times.append(processing_time)
968+
969+
final_event_count = len(self.events_received)
970+
final_context_total = sum(self.context_counts.values())
971+
final_type_total = sum(self.event_type_counts.values())
972+
final_processing_count = len(self.processing_times)
973+
974+
expected_count = initial_event_count + 1
975+
if not (
976+
final_event_count
977+
== final_context_total
978+
== final_type_total
979+
== final_processing_count
980+
== expected_count
981+
):
982+
self.consistency_errors.append("Data consistency error")
983+
984+
return callback
985+
986+
def register_handler(self, thread_id):
993987
try:
994-
callback_id = driver.browsing_context.add_event_handler("context_created", complex_event_callback)
995-
with data_lock:
996-
callback_ids.append(callback_id)
997-
if len(callback_ids) == 5:
998-
registration_complete.set()
988+
callback = self.make_callback()
989+
callback_id = self.driver.browsing_context.add_event_handler("context_created", callback)
990+
with self.data_lock:
991+
self.callback_ids.append(callback_id)
992+
if len(self.callback_ids) == 5:
993+
self.registration_complete.set()
999994
return callback_id
1000995
except Exception as e:
1001-
with data_lock:
1002-
thread_errors.append(f"Thread {thread_id}: Registration failed: {e}")
996+
with self.data_lock:
997+
self.thread_errors.append(f"Thread {thread_id}: Registration failed: {e}")
1003998
return None
1004999

1005-
def remove_handler(callback_id, thread_id):
1000+
def remove_handler(self, callback_id, thread_id):
10061001
try:
1007-
driver.browsing_context.remove_event_handler("context_created", callback_id)
1002+
self.driver.browsing_context.remove_event_handler("context_created", callback_id)
10081003
except Exception as e:
1009-
with data_lock:
1010-
thread_errors.append(f"Thread {thread_id}: Removal failed: {e}")
1004+
with self.data_lock:
1005+
self.thread_errors.append(f"Thread {thread_id}: Removal failed: {e}")
1006+
10111007

1012-
initial_context = driver.browsing_context.create(type=WindowTypes.TAB)
1008+
def test_concurrent_event_handler_registration(driver):
1009+
helper = _EventHandlerTestHelper(driver)
10131010

1014-
# Concurrent registration
10151011
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
1016-
futures = {}
1017-
for i in range(5):
1018-
future = executor.submit(register_handler, f"reg-{i}")
1019-
futures[future] = f"reg-{i}"
1012+
futures = [executor.submit(helper.register_handler, f"reg-{i}") for i in range(5)]
1013+
for future in futures:
1014+
future.result(timeout=15)
1015+
1016+
helper.registration_complete.wait(timeout=5)
1017+
assert len(helper.callback_ids) == 5, f"Expected 5 handlers, got {len(helper.callback_ids)}"
1018+
assert not helper.thread_errors, "Errors during registration: \n" + "\n".join(helper.thread_errors)
1019+
1020+
1021+
def test_event_callback_data_consistency(driver):
1022+
helper = _EventHandlerTestHelper(driver)
1023+
1024+
for i in range(5):
1025+
helper.register_handler(f"reg-{i}")
1026+
1027+
test_contexts = []
1028+
for _ in range(3):
1029+
context = driver.browsing_context.create(type=WindowTypes.TAB)
1030+
test_contexts.append(context)
1031+
1032+
for ctx in test_contexts:
1033+
driver.browsing_context.close(ctx)
1034+
1035+
with helper.data_lock:
1036+
assert not helper.consistency_errors, "Consistency errors: " + str(helper.consistency_errors)
1037+
assert len(helper.events_received) > 0, "No events received"
1038+
assert len(helper.events_received) == sum(helper.context_counts.values())
1039+
assert len(helper.events_received) == sum(helper.event_type_counts.values())
1040+
assert len(helper.events_received) == len(helper.processing_times)
10201041

1042+
1043+
def test_concurrent_event_handler_removal(driver):
1044+
helper = _EventHandlerTestHelper(driver)
1045+
1046+
for i in range(5):
1047+
helper.register_handler(f"reg-{i}")
1048+
1049+
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
1050+
futures = [
1051+
executor.submit(helper.remove_handler, callback_id, f"rem-{i}")
1052+
for i, callback_id in enumerate(helper.callback_ids)
1053+
]
10211054
for future in futures:
1022-
thread_id = futures[future]
1023-
try:
1024-
future.result(timeout=15)
1025-
except concurrent.futures.TimeoutError:
1026-
with data_lock:
1027-
thread_errors.append(f"Thread {thread_id}: Registration timed out")
1028-
except Exception as e:
1029-
with data_lock:
1030-
thread_errors.append(f"Thread {thread_id}: Registration exception: {e}")
1031-
1032-
registration_complete.wait(timeout=5)
1033-
1034-
with data_lock:
1035-
successful_registrations = len(callback_ids)
1036-
1037-
# Trigger events while handlers are active
1038-
if successful_registrations > 0:
1039-
test_contexts = []
1040-
for i in range(3):
1041-
try:
1042-
context = driver.browsing_context.create(type=WindowTypes.TAB)
1043-
test_contexts.append(context)
1044-
time.sleep(0.1)
1045-
except Exception as e:
1046-
thread_errors.append(f"Failed to create test context {i}: {e}")
1047-
1048-
time.sleep(1.0) # Allow event processing
1049-
1050-
for context in test_contexts:
1051-
try:
1052-
driver.browsing_context.close(context)
1053-
except Exception:
1054-
pass
1055-
1056-
# Concurrent removal
1057-
if callback_ids:
1058-
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
1059-
futures = {}
1060-
for i, callback_id in enumerate(callback_ids):
1061-
future = executor.submit(remove_handler, callback_id, f"rem-{i}")
1062-
futures[future] = f"rem-{i}"
1063-
1064-
for future in futures:
1065-
thread_id = futures[future]
1066-
try:
1067-
future.result(timeout=15)
1068-
except concurrent.futures.TimeoutError:
1069-
with data_lock:
1070-
thread_errors.append(f"Thread {thread_id}: Removal timed out")
1071-
except Exception as e:
1072-
with data_lock:
1073-
thread_errors.append(f"Thread {thread_id}: Removal exception: {e}")
1074-
1075-
time.sleep(0.5)
1076-
1077-
# Verify handlers are removed
1078-
with data_lock:
1079-
events_before_removal_test = len(events_received)
1055+
future.result(timeout=15)
10801056

1081-
try:
1082-
post_removal_context = driver.browsing_context.create(type=WindowTypes.TAB)
1083-
time.sleep(0.8)
1084-
driver.browsing_context.close(post_removal_context)
1085-
except Exception as e:
1086-
thread_errors.append(f"Failed to create post-removal test context: {e}")
1057+
assert not helper.thread_errors, "Errors during removal: \n" + "\n".join(helper.thread_errors)
10871058

1088-
with data_lock:
1089-
events_after_removal = len(events_received) - events_before_removal_test
10901059

1091-
# Cleanup
1092-
try:
1093-
driver.browsing_context.close(initial_context)
1094-
except Exception as e:
1095-
thread_errors.append(f"Cleanup error: {e}")
1060+
def test_no_event_after_handler_removal(driver):
1061+
helper = _EventHandlerTestHelper(driver)
10961062

1097-
# Assertions
1098-
all_errors = thread_errors + consistency_errors
1099-
if all_errors:
1100-
pytest.fail("Thread safety test failed with errors:\n" + "\n".join(all_errors))
1063+
for i in range(5):
1064+
helper.register_handler(f"reg-{i}")
11011065

1102-
assert successful_registrations > 0, f"No handlers were successfully registered (got {successful_registrations})"
1103-
assert len(events_received) > 0, "No events were received during test"
1066+
context = driver.browsing_context.create(type=WindowTypes.TAB)
1067+
driver.browsing_context.close(context)
11041068

1105-
# Verify data consistency across multiple counters
1106-
with data_lock:
1107-
total_context_events = sum(context_counts.values()) if context_counts else 0
1108-
total_type_events = sum(event_type_counts.values()) if event_type_counts else 0
1069+
events_before = len(helper.events_received)
11091070

1110-
assert len(events_received) == total_context_events, (
1111-
f"Context count mismatch: {len(events_received)} vs {total_context_events}"
1112-
)
1113-
assert len(events_received) == total_type_events, (
1114-
f"Type count mismatch: {len(events_received)} vs {total_type_events}"
1115-
)
1116-
assert len(events_received) == len(processing_times), (
1117-
f"Processing time count mismatch: {len(events_received)} vs {len(processing_times)}"
1118-
)
1071+
for i, callback_id in enumerate(helper.callback_ids):
1072+
helper.remove_handler(callback_id, f"rem-{i}")
1073+
1074+
post_context = driver.browsing_context.create(type=WindowTypes.TAB)
1075+
driver.browsing_context.close(post_context)
11191076

1120-
# Verify handlers were properly removed
1121-
assert events_after_removal == 0, f"Handlers still active after removal! Got {events_after_removal} events"
1077+
with helper.data_lock:
1078+
new_events = len(helper.events_received) - events_before
11221079

1123-
# Verify event object
1124-
for i, event in enumerate(events_received):
1125-
assert hasattr(event, "context"), f"Event {i} missing 'context' attribute"
1126-
assert isinstance(event.context, str), f"Event {i} 'context' is not string: {type(event.context)}"
1080+
assert new_events == 0, f"Expected 0 new events after removal, got {new_events}"

0 commit comments

Comments
 (0)