From 875b1df37b3e97a5277cf3abde7c0bc4cf5023a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20K=C3=B6hnecke?= Date: Tue, 21 May 2024 10:32:27 +0200 Subject: [PATCH] feat: add tests for undefined behavior --- .../core/tracer/composite_tracer.py | 9 +-- src/intelligence_layer/core/tracer/tracer.py | 8 +++ tests/core/tracer/test_composite_tracer.py | 4 +- tests/core/tracer/test_tracer.py | 62 ++++++++++++++----- 4 files changed, 59 insertions(+), 24 deletions(-) diff --git a/src/intelligence_layer/core/tracer/composite_tracer.py b/src/intelligence_layer/core/tracer/composite_tracer.py index f9c581c26..a74717f1c 100644 --- a/src/intelligence_layer/core/tracer/composite_tracer.py +++ b/src/intelligence_layer/core/tracer/composite_tracer.py @@ -44,9 +44,7 @@ def span( timestamp: Optional[datetime] = None, ) -> "CompositeSpan[Span]": timestamp = timestamp or utc_now() - return CompositeSpan( - [tracer.span(name, timestamp) for tracer in self.tracers] - ) + return CompositeSpan([tracer.span(name, timestamp) for tracer in self.tracers]) def task_span( self, @@ -56,10 +54,7 @@ def task_span( ) -> "CompositeTaskSpan": timestamp = timestamp or utc_now() return CompositeTaskSpan( - [ - tracer.task_span(task_name, input, timestamp) - for tracer in self.tracers - ] + [tracer.task_span(task_name, input, timestamp) for tracer in self.tracers] ) def export_for_viewing(self) -> Sequence[ExportedSpan]: diff --git a/src/intelligence_layer/core/tracer/tracer.py b/src/intelligence_layer/core/tracer/tracer.py index 81223277c..477f5ad43 100644 --- a/src/intelligence_layer/core/tracer/tracer.py +++ b/src/intelligence_layer/core/tracer/tracer.py @@ -212,8 +212,11 @@ def __init__(self, context: Optional[Context] = None): trace_id = context.trace_id self.context = Context(trace_id=trace_id, span_id=span_id) self.status_code = SpanStatus.OK + self._closed = False def __enter__(self) -> Self: + if self._closed: + raise ValueError("Spans cannot be opened once they have been close.") return self @abstractmethod @@ -228,6 +231,8 @@ def log( By default, the `Input` and `Output` of each :class:`Task` are logged automatically, but you can log anything else that seems relevant to understanding the process of a given task. + Logging to closed spans is undefined behavior. + Args: message: A description of the value you are logging, such as the step in the task this is related to. @@ -242,6 +247,8 @@ def end(self, timestamp: Optional[datetime] = None) -> None: """Marks the Span as done, with the end time of the span. The Span should be regarded as complete, and no further logging should happen with it. + Ending a closed span in undefined behavior. + Args: timestamp: Optional override of the timestamp, otherwise should be set to now. """ @@ -272,6 +279,7 @@ def __exit__( self.log(error_value.message, error_value) self.status_code = SpanStatus.ERROR self.end() + self._closed = True class TaskSpan(Span): diff --git a/tests/core/tracer/test_composite_tracer.py b/tests/core/tracer/test_composite_tracer.py index 63d18b210..06cc3bae2 100644 --- a/tests/core/tracer/test_composite_tracer.py +++ b/tests/core/tracer/test_composite_tracer.py @@ -1,7 +1,5 @@ from intelligence_layer.core import ( CompositeTracer, - FileTracer, - InMemorySpan, InMemoryTracer, Task, ) @@ -18,4 +16,4 @@ def test_composite_tracer(test_task: Task[str, str]) -> None: assert trace_1.attributes == trace_2.attributes assert trace_1.status == trace_2.status assert trace_1.context.trace_id != trace_2.context.trace_id - assert trace_1.context.span_id != trace_2.context.span_id \ No newline at end of file + assert trace_1.context.span_id != trace_2.context.span_id diff --git a/tests/core/tracer/test_tracer.py b/tests/core/tracer/test_tracer.py index 5d369dc59..f7f32e7f9 100644 --- a/tests/core/tracer/test_tracer.py +++ b/tests/core/tracer/test_tracer.py @@ -1,10 +1,8 @@ import pytest -from pytest import fixture from pydantic import BaseModel +from pytest import fixture -from intelligence_layer.core import CompositeTracer -from intelligence_layer.core import FileTracer -from intelligence_layer.core import InMemoryTracer +from intelligence_layer.core import CompositeTracer, FileTracer, InMemoryTracer from intelligence_layer.core.tracer.tracer import ( SpanStatus, SpanType, @@ -20,12 +18,10 @@ class DummyObject(BaseModel): @fixture -def composite_tracer( - in_memory_tracer: InMemoryTracer, - file_tracer: FileTracer -): +def composite_tracer(in_memory_tracer: InMemoryTracer, file_tracer: FileTracer): return CompositeTracer(in_memory_tracer, file_tracer) + tracer_fixtures = ["in_memory_tracer", "file_tracer", "composite_tracer"] @@ -181,7 +177,7 @@ def test_tracer_exports_part_of_a_trace_correctly( with tracer.span("name") as root_span: child_span = root_span.span("name-2") child_span.log("test_message", "test_body") - + unified_format = child_span.export_for_viewing() assert len(unified_format) == 2 @@ -193,6 +189,41 @@ def test_tracer_exports_part_of_a_trace_correctly( assert span_1.context.trace_id != span_2.context.trace_id +@pytest.mark.skip("Not yet implemented") +@pytest.mark.parametrize( + "tracer_fixture", + tracer_fixtures, +) +def test_spans_cannot_be_closed_twice( + tracer_fixture: str, + request: pytest.FixtureRequest, +) -> None: + tracer: Tracer = request.getfixturevalue(tracer_fixture) + + span = tracer.span("name") + span.end() + span.end() + + +@pytest.mark.parametrize( + "tracer_fixture", + tracer_fixtures, +) +def test_spans_cannot_be_used_as_context_twice( + tracer_fixture: str, + request: pytest.FixtureRequest, +) -> None: + tracer: Tracer = request.getfixturevalue(tracer_fixture) + + span = tracer.span("name") + with span: + pass + with pytest.raises(Exception): + with span: + pass + + +@pytest.mark.skip("Not yet implemented") @pytest.mark.parametrize( "tracer_fixture", tracer_fixtures, @@ -203,15 +234,18 @@ def test_tracer_can_not_log_on_closed_span( ) -> None: tracer: Tracer = request.getfixturevalue(tracer_fixture) - span = tracer.span("name") + span = tracer.span("name") + # ok + span.log("test_message", "test_body") + span.end() + # not ok with pytest.raises(Exception): span.log("test_message", "test_body") + span = tracer.span("name") + # ok with span: span.log("test_message", "test_body") + # not ok with pytest.raises(Exception): span.log("test_message", "test_body") - - - -