Skip to content

Commit ec849fa

Browse files
committed
Misc test fixes/thread-unsafe markers
1 parent 5e243f3 commit ec849fa

4 files changed

Lines changed: 46 additions & 32 deletions

File tree

‎cuda_core/tests/graph/test_graph_node_update.py‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import pytest
1515
from helpers.graph_kernels import compile_common_kernels
1616

17-
from cuda.core import LaunchConfig, LegacyPinnedMemoryResource
17+
from cuda.core import Device, LaunchConfig, LegacyPinnedMemoryResource
1818
from cuda.core._utils._weak_handles import weak_handle
1919
from cuda.core._utils.cuda_utils import CUDAError, driver, handle_return
2020
from cuda.core._utils.version import driver_version
@@ -619,7 +619,10 @@ def assert_exec_uses(graph, expected):
619619
def definition_update_case(request, init_cuda):
620620
if driver_version() < (12, 2, 0):
621621
pytest.skip("individual graph node updates require CUDA 12.2+")
622-
return request.param(init_cuda)
622+
factory = request.param
623+
# pytest-run-parallel shares this fixture object across workers. Build the
624+
# case at call time on Device() so each worker gets its own graph/node.
625+
return lambda: factory(Device())
623626

624627

625628
@pytest.mark.agent_authored(model="gpt-5.6")
@@ -745,7 +748,7 @@ def test_memcpy_update_between_host_and_device(init_cuda, device_operand):
745748
def test_definition_node_update_changes_future_instantiations(
746749
definition_update_case,
747750
):
748-
case = definition_update_case
751+
case = definition_update_case()
749752
assert case.original != case.replacement
750753
old_graph = case.graph_def.instantiate()
751754

@@ -762,7 +765,7 @@ def test_definition_node_update_changes_future_instantiations(
762765
def test_destroyed_definition_node_rejects_update(
763766
definition_update_case,
764767
):
765-
case = definition_update_case
768+
case = definition_update_case()
766769
case.node.destroy()
767770

768771
assert not case.node.is_valid
@@ -777,7 +780,7 @@ def test_destroyed_definition_node_rejects_update(
777780
def test_failed_definition_node_update_preserves_state(
778781
definition_update_case,
779782
):
780-
case = definition_update_case
783+
case = definition_update_case()
781784

782785
assert case.invalid_update is not None
783786
assert case.invalid_exception is not None
@@ -793,17 +796,18 @@ def test_failed_definition_node_update_preserves_state(
793796
def test_definition_node_update_rejects_wrong_type(
794797
definition_update_case,
795798
):
796-
if definition_update_case.invalid_argument_update is None:
799+
case = definition_update_case()
800+
if case.invalid_argument_update is None:
797801
pytest.skip("update method has no typed positional argument")
798802
with pytest.raises(TypeError):
799-
definition_update_case.invalid_argument_update()
803+
case.invalid_argument_update()
800804

801805

802806
@pytest.mark.agent_authored(model="gpt-5.6")
803807
def test_executable_node_update_changes_existing_exec(
804808
definition_update_case,
805809
):
806-
case = definition_update_case
810+
case = definition_update_case()
807811
graph = case.graph_def.instantiate()
808812

809813
_update_executable_case(graph, case)
@@ -974,6 +978,7 @@ def test_rejected_executable_update_rolls_back_owners(init_cuda):
974978
assert ctypes.c_int.from_address(int(active.handle)).value == 1
975979

976980

981+
@pytest.mark.thread_unsafe(reason="deferred cleanup on main thread which would wait")
977982
@pytest.mark.agent_authored(model="gpt-5.6")
978983
def test_whole_update_replaces_executable_attachment_accumulator(init_cuda):
979984
if driver_version() < (12, 2, 0):
@@ -1132,6 +1137,7 @@ def second():
11321137
_wait_until(lambda: first_weak() is None and second_weak() is None)
11331138

11341139

1140+
@pytest.mark.thread_unsafe(reason="deferred cleanup on main thread which would wait")
11351141
@pytest.mark.agent_authored(model="claude-opus-5")
11361142
def test_child_graph_update_transfers_source_owners_to_executable(init_cuda):
11371143
if driver_version() < (12, 2, 0):

‎cuda_core/tests/memory/test_copy_batch.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ def test_batch_matches_sequential_d2d(self, copy_batch_device, device_bufs, copy
212212
class TestCopyBatchStreamSemantics:
213213
"""Where the batch sits in stream order, and what it cannot be part of."""
214214

215+
@pytest.mark.thread_unsafe(reason="shared copy_stream and buffers must not interleave")
215216
@pytest.mark.agent_authored(model="Claude Opus 5")
216217
def test_ordered_between_prior_and_later_stream_work(self, device_bufs, copy_stream):
217218
"""The batch must observe prior stream work and precede later work.

‎cuda_core/tests/memory/test_copy_single_options.py‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def test_options_copy_from_data_correct(single_copy_device, single_copy_stream,
369369

370370

371371
@pytest.mark.agent_authored(model="Claude Sonnet 4.6")
372-
def test_options_copy_to_rejected_under_graph_capture(single_copy_stream, pinned_mr):
372+
def test_options_copy_to_rejected_under_graph_capture(single_copy_device, pinned_mr):
373373
"""copy_to with options raises TypeError when the stream is capturing,
374374
matching copy_batch. Use GraphNode.memcpy to build attributed copies
375375
into a graph instead; options=None keeps working under capture as it
@@ -378,57 +378,63 @@ def test_options_copy_to_rejected_under_graph_capture(single_copy_stream, pinned
378378
src = pinned_mr.allocate(SIZE)
379379
dst = pinned_mr.allocate(SIZE)
380380
opts = CopyOptions(src_access_order=MemcpySrcAccessOrder.ANY)
381+
stream = single_copy_device.create_stream()
381382

382-
gb = single_copy_stream.create_graph_builder().begin_building()
383+
gb = stream.create_graph_builder().begin_building()
383384
try:
384385
with pytest.raises(TypeError, match="graph capture"):
385386
src.copy_to(dst, stream=gb, options=opts)
386387
finally:
387388
gb.end_building()
388389
gb.close()
390+
stream.close()
389391

390392
src.close()
391393
dst.close()
392394

393395

394396
@pytest.mark.agent_authored(model="Claude Sonnet 4.6")
395-
def test_options_copy_from_rejected_under_graph_capture(single_copy_stream, pinned_mr):
397+
def test_options_copy_from_rejected_under_graph_capture(single_copy_device, pinned_mr):
396398
"""Same as the copy_to variant, exercising copy_from instead."""
397399
src = pinned_mr.allocate(SIZE)
398400
dst = pinned_mr.allocate(SIZE)
399401
opts = CopyOptions(src_access_order=MemcpySrcAccessOrder.STREAM)
402+
stream = single_copy_device.create_stream()
400403

401-
gb = single_copy_stream.create_graph_builder().begin_building()
404+
gb = stream.create_graph_builder().begin_building()
402405
try:
403406
with pytest.raises(TypeError, match="graph capture"):
404407
dst.copy_from(src, stream=gb, options=opts)
405408
finally:
406409
gb.end_building()
407410
gb.close()
411+
stream.close()
408412

409413
src.close()
410414
dst.close()
411415

412416

413417
@pytest.mark.agent_authored(model="Claude Sonnet 4.6")
414-
def test_options_none_copy_to_still_works_under_graph_capture(single_copy_stream, pinned_mr):
418+
def test_options_none_copy_to_still_works_under_graph_capture(single_copy_device, pinned_mr):
415419
"""options=None never touches the attributes path, so copy_to keeps
416420
working under graph capture exactly as it did before options existed.
417421
"""
418422
src = pinned_mr.allocate(SIZE)
419423
set_buffer(src, 0xBB)
420424
dst = pinned_mr.allocate(SIZE)
425+
stream = single_copy_device.create_stream()
421426

422-
gb = single_copy_stream.create_graph_builder().begin_building()
427+
gb = stream.create_graph_builder().begin_building()
423428
src.copy_to(dst, stream=gb)
424429
graph = gb.end_building().complete()
425-
graph.launch(single_copy_stream)
426-
single_copy_stream.sync()
430+
graph.launch(stream)
431+
stream.sync()
427432

428433
assert compare_equal_buffers(src, dst)
429434

430435
src.close()
431436
dst.close()
437+
stream.close()
432438

433439

434440
@pytest.mark.agent_authored(model="Claude Sonnet 5")

‎cuda_core/tests/test_object_protocols.py‎

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -751,30 +751,31 @@ def sample_object_b(request):
751751

752752

753753
@pytest.mark.agent_authored(model="gpt-5.6")
754-
@pytest.mark.parametrize("fixture_name", CLOSEABLE_TYPES)
755-
def test_closeable_object_state_and_safe_inspection(fixture_name, request):
754+
@pytest.mark.thread_unsafe(reason="closes a fixture object shared between threads")
755+
@pytest.mark.parametrize("sample_object", CLOSEABLE_TYPES, indirect=True)
756+
def test_closeable_object_state_and_safe_inspection(sample_object):
756757
"""Closing is idempotent, updates named state, and leaves inspection safe."""
757-
obj = request.getfixturevalue(fixture_name)
758-
assert not obj.is_closed
758+
assert not sample_object.is_closed
759759

760-
obj.close()
761-
assert obj.is_closed
762-
assert bool(obj) is True # Preserve backward-compatible truthiness after close.
763-
repr(obj)
764-
if hasattr(obj, "handle"):
765-
_ = obj.handle
760+
sample_object.close()
761+
assert sample_object.is_closed
762+
assert bool(sample_object) is True # Preserve backward-compatible truthiness after close.
763+
repr(sample_object)
764+
if hasattr(sample_object, "handle"):
765+
_ = sample_object.handle
766766

767-
obj.close()
768-
assert obj.is_closed
767+
sample_object.close()
768+
assert sample_object.is_closed
769769

770770

771771
@pytest.mark.agent_authored(model="gpt-5.6")
772-
def test_graph_object_validity_uses_named_state(sample_graphdef):
772+
def test_graph_object_validity_uses_named_state(init_cuda):
773773
"""Graph objects remain truthy when their named validity becomes false."""
774-
assert sample_graphdef.is_valid
775-
assert sample_graphdef._entry.is_valid
774+
graph_def = GraphDefinition()
775+
assert graph_def.is_valid
776+
assert graph_def._entry.is_valid
776777

777-
node = sample_graphdef.empty()
778+
node = graph_def.empty()
778779
assert node.is_valid
779780
node.destroy()
780781
assert not node.is_valid

0 commit comments

Comments
 (0)