Skip to content

Commit 26df23b

Browse files
Dwij1704dot-agi
andauthored
Updated unit tests for decorators by adding workflow and task nesting validation. (#863)
* Updated unit tests for decorators by adding workflow and task nesting validation. * Refactor span attribute keys in decorators to use constants from SpanAttributes class. Update unit tests to reflect changes in attribute access for operation names and versions. --------- Co-authored-by: Pratyush Shukla <ps4534@nyu.edu>
1 parent 8e832de commit 26df23b

File tree

3 files changed

+111
-17
lines changed

3 files changed

+111
-17
lines changed

agentops/sdk/decorators/utility.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ def _create_as_current_span(
119119
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = span_kind
120120

121121
# Add standard attributes
122-
attributes["agentops.operation.name"] = operation_name
122+
attributes[SpanAttributes.OPERATION_NAME] = operation_name
123123
if version is not None:
124-
attributes["agentops.operation.version"] = version
124+
attributes[SpanAttributes.OPERATION_VERSION] = version
125125

126126
# Get current context explicitly to debug it
127127
current_context = context_api.get_current()
@@ -182,9 +182,9 @@ def _make_span(
182182
attributes[SpanAttributes.AGENTOPS_SPAN_KIND] = span_kind
183183

184184
# Add standard attributes
185-
attributes["agentops.operation.name"] = operation_name
185+
attributes[SpanAttributes.OPERATION_NAME] = operation_name
186186
if version is not None:
187-
attributes["agentops.operation.version"] = version
187+
attributes[SpanAttributes.OPERATION_VERSION] = version
188188

189189
# Get current context explicitly
190190
current_context = context_api.get_current()

agentops/semconv/span_attributes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,7 @@ class SpanAttributes:
5555
AGENTOPS_ENTITY_INPUT = "agentops.entity.input"
5656
AGENTOPS_SPAN_KIND = "agentops.span.kind"
5757
AGENTOPS_ENTITY_NAME = "agentops.entity.name"
58+
59+
# Operation attributes
60+
OPERATION_NAME = "operation.name"
61+
OPERATION_VERSION = "operation.version"

tests/unit/sdk/test_decorators.py

Lines changed: 103 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
from opentelemetry import trace
66
from opentelemetry.sdk.trace import ReadableSpan
77

8-
from agentops.sdk.decorators import agent, operation, session, workflow
8+
from agentops.sdk.decorators import agent, operation, session, workflow, task
99
from agentops.semconv import SpanKind
1010
from agentops.semconv.span_attributes import SpanAttributes
11+
from agentops.semconv import SpanAttributes
1112
from tests.unit.sdk.instrumentation_tester import InstrumentationTester
1213

1314

@@ -75,9 +76,9 @@ def test_session():
7576
nested_operation = None
7677

7778
for span in operation_spans:
78-
if span.attributes and span.attributes.get('agentops.operation.name') == 'main_operation':
79+
if span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'main_operation':
7980
main_operation = span
80-
elif span.attributes and span.attributes.get('agentops.operation.name') == 'nested_operation':
81+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'nested_operation':
8182
nested_operation = span
8283

8384
assert main_operation is not None, "main_operation span not found"
@@ -164,9 +165,9 @@ async def test_async_session():
164165
nested_operation = None
165166

166167
for span in operation_spans:
167-
if span.attributes and span.attributes.get('agentops.operation.name') == 'main_async_operation':
168+
if span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'main_async_operation':
168169
main_operation = span
169-
elif span.attributes and span.attributes.get('agentops.operation.name') == 'nested_async_operation':
170+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'nested_async_operation':
170171
nested_operation = span
171172

172173
assert main_operation is not None, "main_async_operation span not found"
@@ -255,9 +256,9 @@ def test_generator_session():
255256
nested_operation = None
256257

257258
for span in operation_spans:
258-
if span.attributes and span.attributes.get('agentops.operation.name') == 'main_generator_operation':
259+
if span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'main_generator_operation':
259260
main_operation = span
260-
elif span.attributes and span.attributes.get('agentops.operation.name') == 'nested_generator':
261+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'nested_generator':
261262
nested_operation = span
262263

263264
assert main_operation is not None, "main_generator_operation span not found"
@@ -347,9 +348,9 @@ async def test_async_generator_session():
347348
nested_operation = None
348349

349350
for span in operation_spans:
350-
if span.attributes and span.attributes.get('agentops.operation.name') == 'main_async_generator_operation':
351+
if span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'main_async_generator_operation':
351352
main_operation = span
352-
elif span.attributes and span.attributes.get('agentops.operation.name') == 'nested_async_generator':
353+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'nested_async_generator':
353354
nested_operation = span
354355

355356
assert main_operation is not None, "main_async_generator_operation span not found"
@@ -442,11 +443,11 @@ def test_complex_session():
442443
level3_operation = None
443444

444445
for span in operation_spans:
445-
if span.attributes and span.attributes.get('agentops.operation.name') == 'level1_operation':
446+
if span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'level1_operation':
446447
level1_operation = span
447-
elif span.attributes and span.attributes.get('agentops.operation.name') == 'level2_operation':
448+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'level2_operation':
448449
level2_operation = span
449-
elif span.attributes and span.attributes.get('agentops.operation.name') == 'level3_operation':
450+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'level3_operation':
450451
level3_operation = span
451452

452453
assert level1_operation is not None, "level1_operation span not found"
@@ -478,4 +479,93 @@ def test_complex_session():
478479
assert level2_operation.context is not None
479480
assert level3_operation.parent.span_id == level2_operation.context.span_id
480481

481-
482+
def test_workflow_and_task_nesting(self, instrumentation: InstrumentationTester):
483+
"""Test that workflow and task decorators create proper span nesting."""
484+
485+
# Define a workflow with tasks
486+
@workflow
487+
def data_processing_workflow(data):
488+
"""Main workflow that processes data through multiple tasks"""
489+
result = process_input(data)
490+
result = transform_data(result)
491+
return result
492+
493+
@task
494+
def process_input(data):
495+
"""Task to process input data"""
496+
return f"Processed: {data}"
497+
498+
@task
499+
def transform_data(data):
500+
"""Task to transform processed data"""
501+
return f"Transformed: {data}"
502+
503+
# Test session with the workflow
504+
@session
505+
def test_workflow_session():
506+
return data_processing_workflow("test data")
507+
508+
# Run the test
509+
result = test_workflow_session()
510+
511+
# Verify the result
512+
assert result == "Transformed: Processed: test data"
513+
514+
# Get all spans captured during the test
515+
spans = instrumentation.get_finished_spans()
516+
517+
# Print detailed span information for debugging
518+
print("\nDetailed span information for workflow and task test:")
519+
for i, span in enumerate(spans):
520+
parent_id = span.parent.span_id if span.parent else "None"
521+
span_id = span.context.span_id if span.context else "None"
522+
print(f"Span {i}: name={span.name}, span_id={span_id}, parent_id={parent_id}")
523+
524+
# We should have 4 spans: session, workflow, and two tasks
525+
assert len(spans) == 4
526+
527+
# Verify span kinds
528+
session_spans = [s for s in spans if s.attributes and s.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == SpanKind.SESSION]
529+
workflow_spans = [s for s in spans if s.attributes and s.attributes.get(SpanAttributes.AGENTOPS_SPAN_KIND) == SpanKind.WORKFLOW]
530+
task_spans = [s for s in spans if s.attributes and s.attributes.get(
531+
SpanAttributes.AGENTOPS_SPAN_KIND) == SpanKind.TASK]
532+
533+
assert len(session_spans) == 1
534+
assert len(workflow_spans) == 1
535+
assert len(task_spans) == 2
536+
537+
# Find the workflow and task spans
538+
workflow_span = None
539+
process_task = None
540+
transform_task = None
541+
542+
for span in spans:
543+
if span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'data_processing_workflow':
544+
workflow_span = span
545+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'process_input':
546+
process_task = span
547+
elif span.attributes and span.attributes.get(SpanAttributes.OPERATION_NAME) == 'transform_data':
548+
transform_task = span
549+
550+
assert workflow_span is not None, "workflow span not found"
551+
assert process_task is not None, "process_input task span not found"
552+
assert transform_task is not None, "transform_data task span not found"
553+
554+
# Verify the session span is the root
555+
session_span = session_spans[0]
556+
assert session_span.parent is None
557+
558+
# Verify the workflow span is a child of the session span
559+
assert workflow_span.parent is not None
560+
assert session_span.context is not None
561+
assert workflow_span.parent.span_id == session_span.context.span_id
562+
563+
# Verify process_task is a child of the workflow span
564+
assert process_task.parent is not None
565+
assert workflow_span.context is not None
566+
assert process_task.parent.span_id == workflow_span.context.span_id
567+
568+
# Verify transform_task is a child of the workflow span
569+
assert transform_task.parent is not None
570+
assert workflow_span.context is not None
571+
assert transform_task.parent.span_id == workflow_span.context.span_id

0 commit comments

Comments
 (0)