Skip to content

Commit fb08009

Browse files
CopilotnikhilNava
andcommitted
Clean up test_output_scope.py - remove redundant tests and aggregate similar ones
Reduced from 10 tests to 4 focused tests: - test_output_scope_creates_span_with_messages (merged span name and messages tests) - test_record_output_messages_appends (merged multiple append tests) - test_output_scope_with_parent_id (parent linking) - test_output_scope_dispose (manual dispose) All tests use real spans via InMemorySpanExporter (no mocks). Co-authored-by: nikhilNava <211831449+nikhilNava@users.noreply.github.com>
1 parent b965b0f commit fb08009

1 file changed

Lines changed: 51 additions & 187 deletions

File tree

tests/observability/core/test_output_scope.py

Lines changed: 51 additions & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,18 @@
2323

2424

2525
class TestOutputScope(unittest.TestCase):
26-
"""Unit tests for OutputScope and its methods."""
26+
"""Unit tests for OutputScope."""
2727

2828
@classmethod
2929
def setUpClass(cls):
3030
"""Set up test environment once for all tests."""
31-
# Configure Microsoft Agent 365 for testing
3231
os.environ["ENABLE_A365_OBSERVABILITY"] = "true"
3332

3433
configure(
3534
service_name="test-output-scope-service",
3635
service_namespace="test-namespace",
3736
)
38-
# Create test data
37+
3938
cls.tenant_details = TenantDetails(tenant_id="12345678-1234-5678-1234-567812345678")
4039
cls.agent_details = AgentDetails(
4140
agent_id="test-agent-123",
@@ -46,18 +45,16 @@ def setUpClass(cls):
4645
def setUp(self):
4746
super().setUp()
4847

49-
# Reset TelemetryManager state to ensure fresh configuration
48+
# Reset TelemetryManager state
5049
_telemetry_manager._tracer_provider = None
5150
_telemetry_manager._span_processors = {}
5251
OpenTelemetryScope._tracer = None
5352

54-
# Reconfigure to get a fresh TracerProvider
5553
configure(
5654
service_name="test-output-scope-service",
5755
service_namespace="test-namespace",
5856
)
5957

60-
# Set up tracer to capture spans
6158
self.span_exporter = InMemorySpanExporter()
6259
tracer_provider = get_tracer_provider()
6360
tracer_provider.add_span_processor(SimpleSpanProcessor(self.span_exporter))
@@ -66,219 +63,86 @@ def tearDown(self):
6663
super().tearDown()
6764
self.span_exporter.clear()
6865

69-
def test_output_scope_creation(self):
70-
"""Test that OutputScope can be created successfully."""
71-
response = Response(messages=["Hello, how can I help you?"])
72-
73-
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
74-
75-
self.assertIsNotNone(scope)
76-
scope.dispose()
77-
78-
def test_record_output_messages_method_exists(self):
79-
"""Test that record_output_messages method exists on OutputScope."""
80-
response = Response(messages=["Initial message"])
81-
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
82-
83-
if scope is not None:
84-
# Test that the method exists
85-
self.assertTrue(hasattr(scope, "record_output_messages"))
86-
self.assertTrue(callable(scope.record_output_messages))
87-
scope.dispose()
88-
89-
def test_output_messages_set_on_span(self):
90-
"""Test that output messages are set on span attributes."""
91-
response = Response(messages=["This is the agent response"])
92-
93-
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
94-
95-
if scope is not None:
96-
scope.dispose()
97-
98-
finished_spans = self.span_exporter.get_finished_spans()
99-
self.assertTrue(finished_spans, "Expected at least one span to be created")
100-
101-
span = finished_spans[-1]
102-
span_attributes = getattr(span, "attributes", {}) or {}
103-
104-
self.assertIn(
105-
GEN_AI_OUTPUT_MESSAGES_KEY,
106-
span_attributes,
107-
"Expected output messages key to be set on span",
108-
)
109-
110-
# Verify the message content is in the serialized output
111-
output_value = span_attributes[GEN_AI_OUTPUT_MESSAGES_KEY]
112-
self.assertIn("This is the agent response", output_value)
113-
114-
def test_multiple_output_messages(self):
115-
"""Test that multiple output messages are properly recorded."""
116-
response = Response(messages=["First response", "Second response", "Third response"])
117-
118-
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
119-
120-
if scope is not None:
121-
scope.dispose()
122-
123-
finished_spans = self.span_exporter.get_finished_spans()
124-
self.assertTrue(finished_spans, "Expected at least one span to be created")
125-
126-
span = finished_spans[-1]
127-
span_attributes = getattr(span, "attributes", {}) or {}
128-
129-
self.assertIn(
130-
GEN_AI_OUTPUT_MESSAGES_KEY,
131-
span_attributes,
132-
"Expected output messages key to be set on span",
133-
)
134-
135-
output_value = span_attributes[GEN_AI_OUTPUT_MESSAGES_KEY]
136-
self.assertIn("First response", output_value)
137-
self.assertIn("Second response", output_value)
138-
self.assertIn("Third response", output_value)
139-
140-
def test_record_output_messages_updates_span(self):
141-
"""Test that record_output_messages appends messages to the span."""
142-
response = Response(messages=["Initial message"])
143-
144-
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
145-
146-
if scope is not None:
147-
# Record additional messages (should append, not replace)
148-
scope.record_output_messages(["Appended message 1", "Appended message 2"])
149-
scope.dispose()
150-
66+
def _get_last_span(self):
67+
"""Helper to get the last finished span and its attributes."""
15168
finished_spans = self.span_exporter.get_finished_spans()
15269
self.assertTrue(finished_spans, "Expected at least one span to be created")
153-
15470
span = finished_spans[-1]
155-
span_attributes = getattr(span, "attributes", {}) or {}
156-
157-
self.assertIn(
158-
GEN_AI_OUTPUT_MESSAGES_KEY,
159-
span_attributes,
160-
"Expected output messages key to be set on span",
161-
)
162-
163-
# The span should have all messages (initial + appended)
164-
output_value = span_attributes[GEN_AI_OUTPUT_MESSAGES_KEY]
165-
self.assertIn("Initial message", output_value)
166-
self.assertIn("Appended message 1", output_value)
167-
self.assertIn("Appended message 2", output_value)
168-
169-
def test_record_output_messages_multiple_appends(self):
170-
"""Test that multiple calls to record_output_messages accumulate messages."""
171-
response = Response(messages=["First message"])
71+
attributes = getattr(span, "attributes", {}) or {}
72+
return span, attributes
17273

173-
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
74+
def test_output_scope_creates_span_with_messages(self):
75+
"""Test OutputScope creates span with output messages attribute."""
76+
response = Response(messages=["First message", "Second message"])
17477

175-
if scope is not None:
176-
# First append
177-
scope.record_output_messages(["Second message"])
178-
# Second append
179-
scope.record_output_messages(["Third message", "Fourth message"])
180-
scope.dispose()
78+
with OutputScope.start(self.agent_details, self.tenant_details, response):
79+
pass
18180

182-
finished_spans = self.span_exporter.get_finished_spans()
183-
self.assertTrue(finished_spans, "Expected at least one span to be created")
81+
span, attributes = self._get_last_span()
18482

185-
span = finished_spans[-1]
186-
span_attributes = getattr(span, "attributes", {}) or {}
83+
# Verify span name contains operation name and agent id
84+
self.assertIn("output_messages", span.name)
85+
self.assertIn(self.agent_details.agent_id, span.name)
18786

188-
output_value = span_attributes[GEN_AI_OUTPUT_MESSAGES_KEY]
189-
# All four messages should be present
87+
# Verify output messages are set
88+
self.assertIn(GEN_AI_OUTPUT_MESSAGES_KEY, attributes)
89+
output_value = attributes[GEN_AI_OUTPUT_MESSAGES_KEY]
19090
self.assertIn("First message", output_value)
19191
self.assertIn("Second message", output_value)
192-
self.assertIn("Third message", output_value)
193-
self.assertIn("Fourth message", output_value)
19492

195-
def test_output_scope_context_manager(self):
196-
"""Test that OutputScope works as a context manager."""
197-
response = Response(messages=["Context manager test"])
93+
def test_record_output_messages_appends(self):
94+
"""Test record_output_messages appends to accumulated messages."""
95+
response = Response(messages=["Initial"])
19896

19997
with OutputScope.start(self.agent_details, self.tenant_details, response) as scope:
200-
self.assertIsNotNone(scope)
201-
202-
finished_spans = self.span_exporter.get_finished_spans()
203-
self.assertTrue(finished_spans, "Expected at least one span to be created")
204-
205-
def test_output_scope_span_name(self):
206-
"""Test that OutputScope creates spans with correct operation name."""
207-
response = Response(messages=["Test message"])
98+
scope.record_output_messages(["Appended 1"])
99+
scope.record_output_messages(["Appended 2", "Appended 3"])
208100

209-
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
210-
211-
if scope is not None:
212-
scope.dispose()
213-
214-
finished_spans = self.span_exporter.get_finished_spans()
215-
self.assertTrue(finished_spans, "Expected at least one span to be created")
101+
_, attributes = self._get_last_span()
216102

217-
span = finished_spans[-1]
218-
# The activity name should contain "output_messages" and the agent id
219-
self.assertIn("output_messages", span.name)
220-
self.assertIn(self.agent_details.agent_id, span.name)
103+
output_value = attributes[GEN_AI_OUTPUT_MESSAGES_KEY]
104+
# All messages should be present (initial + all appended)
105+
self.assertIn("Initial", output_value)
106+
self.assertIn("Appended 1", output_value)
107+
self.assertIn("Appended 2", output_value)
108+
self.assertIn("Appended 3", output_value)
221109

222110
def test_output_scope_with_parent_id(self):
223-
"""Test that OutputScope uses parent_id to link span to parent."""
224-
response = Response(messages=["Test message with parent"])
225-
# W3C Trace Context format: "00-{trace_id}-{span_id}-{trace_flags}"
226-
# trace_id: 32 hex chars, span_id: 16 hex chars
111+
"""Test OutputScope uses parent_id to link span to parent context."""
112+
response = Response(messages=["Test"])
227113
parent_trace_id = "1234567890abcdef1234567890abcdef"
228114
parent_span_id = "abcdefabcdef1234"
229115
parent_id = f"00-{parent_trace_id}-{parent_span_id}-01"
230116

231-
scope = OutputScope.start(
117+
with OutputScope.start(
232118
self.agent_details, self.tenant_details, response, parent_id=parent_id
233-
)
119+
):
120+
pass
234121

235-
if scope is not None:
236-
scope.dispose()
237-
238-
finished_spans = self.span_exporter.get_finished_spans()
239-
self.assertTrue(finished_spans, "Expected at least one span to be created")
240-
241-
span = finished_spans[-1]
122+
span, _ = self._get_last_span()
242123

243-
# Verify the span has the correct parent trace context
244-
# The span's trace_id should match the parent's trace_id
124+
# Verify span inherits parent's trace_id
245125
span_trace_id = f"{span.context.trace_id:032x}"
246-
self.assertEqual(
247-
span_trace_id,
248-
parent_trace_id,
249-
"Expected span's trace_id to match parent's trace_id",
250-
)
126+
self.assertEqual(span_trace_id, parent_trace_id)
251127

252-
# The span's parent_span_id should match the parent's span_id
253-
span_parent_id = None
254-
if span.parent and hasattr(span.parent, "span_id"):
255-
span_parent_id = f"{span.parent.span_id:016x}"
256-
self.assertEqual(
257-
span_parent_id,
258-
parent_span_id,
259-
"Expected span's parent_span_id to match parent's span_id",
260-
)
128+
# Verify span's parent_span_id matches
129+
self.assertIsNotNone(span.parent, "Expected span to have a parent")
130+
self.assertTrue(hasattr(span.parent, "span_id"), "Expected parent to have span_id")
131+
span_parent_id = f"{span.parent.span_id:016x}"
132+
self.assertEqual(span_parent_id, parent_span_id)
261133

262-
def test_output_scope_without_parent_id(self):
263-
"""Test that OutputScope creates a span without forced parent when not provided."""
264-
response = Response(messages=["Test message without parent"])
134+
def test_output_scope_dispose(self):
135+
"""Test OutputScope dispose method ends the span."""
136+
response = Response(messages=["Test"])
265137

266138
scope = OutputScope.start(self.agent_details, self.tenant_details, response)
139+
self.assertIsNotNone(scope)
140+
scope.dispose()
267141

268-
if scope is not None:
269-
scope.dispose()
270-
142+
# Verify span was created and ended
271143
finished_spans = self.span_exporter.get_finished_spans()
272-
self.assertTrue(finished_spans, "Expected at least one span to be created")
273-
274-
span = finished_spans[-1]
275-
276-
# When no parent_id is provided, the span should either have no parent
277-
# or inherit from the current context (which in tests is typically empty)
278-
# We just verify the span was created successfully
279-
self.assertIsNotNone(span.context.span_id)
144+
self.assertEqual(len(finished_spans), 1)
280145

281146

282147
if __name__ == "__main__":
283-
# Run pytest only on the current file
284148
sys.exit(pytest.main([str(Path(__file__))] + sys.argv[1:]))

0 commit comments

Comments
 (0)