From 45a66de9f40c3e19fd3a38aaf336bcdd0be41855 Mon Sep 17 00:00:00 2001 From: Copilot <223556219+Copilot@users.noreply.github.com> Date: Wed, 5 Aug 2026 01:54:46 -0700 Subject: [PATCH] TEST strengthen Crescendo context isolation Exercise the shared attack instance with deterministic overlapping setup tasks so the isolation test proves concurrent context safety rather than sequential behavior. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 329b71cb-578d-4997-a199-7416b27f3eed --- .../attack/multi_turn/test_crescendo.py | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/tests/unit/executor/attack/multi_turn/test_crescendo.py b/tests/unit/executor/attack/multi_turn/test_crescendo.py index 61ee7bd607..8c1e4e850c 100644 --- a/tests/unit/executor/attack/multi_turn/test_crescendo.py +++ b/tests/unit/executor/attack/multi_turn/test_crescendo.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +import asyncio import json import uuid from pathlib import Path @@ -2548,19 +2549,35 @@ async def test_concurrent_context_isolation( context1 = CrescendoAttackContext(params=AttackParameters(objective="Objective 1")) context2 = CrescendoAttackContext(params=AttackParameters(objective="Objective 2")) - # Mock conversation manager for both setups - mock_state1 = ConversationState(turn_count=0) - mock_state2 = ConversationState(turn_count=0) + setup_started: set[str] = set() + both_setups_started = asyncio.Event() + + async def initialize_context_async( + *, + context: CrescendoAttackContext, + **_kwargs: object, + ) -> ConversationState: + setup_started.add(context.objective) + if len(setup_started) == 2: + both_setups_started.set() + await both_setups_started.wait() + return ConversationState(turn_count=0) with patch.object( - attack._conversation_manager, "initialize_context_async", side_effect=[mock_state1, mock_state2] + attack._conversation_manager, + "initialize_context_async", + new_callable=AsyncMock, + side_effect=initialize_context_async, ): - # Simulate concurrent setup - both contexts use the same attack instance - await attack._setup_async(context=context1) - await attack._setup_async(context=context2) + # The first setup waits for the second to start, guaranteeing real overlap. + await asyncio.gather( + attack._setup_async(context=context1), + attack._setup_async(context=context2), + ) # Verify contexts remain independent # Each should maintain its own state without interference + assert setup_started == {"Objective 1", "Objective 2"} assert context1.objective == "Objective 1" assert context2.objective == "Objective 2" # Most importantly, they should have different conversation IDs