|
| 1 | +from datetime import datetime, timedelta, timezone |
| 2 | +from unittest.mock import Mock, patch |
| 3 | + |
| 4 | +from galileo.logger import GalileoLogger |
| 5 | +from tests.testutils.setup import setup_mock_logstreams_client, setup_mock_projects_client |
| 6 | + |
| 7 | + |
| 8 | +@patch("galileo.logger.logger.LogStreams") |
| 9 | +@patch("galileo.logger.logger.Projects") |
| 10 | +def test_rapid_span_creation_ensures_uniqueness(mock_projects_client: Mock, mock_logstreams_client: Mock): |
| 11 | + """Tests that creating spans in a tight loop results in unique, monotonically increasing timestamps.""" |
| 12 | + setup_mock_projects_client(mock_projects_client) |
| 13 | + setup_mock_logstreams_client(mock_logstreams_client) |
| 14 | + logger = GalileoLogger(project="test", log_stream="test") |
| 15 | + logger.start_trace(input="test") |
| 16 | + for _ in range(5): |
| 17 | + logger.add_llm_span(input="test", output="test", model="test") |
| 18 | + logger.conclude(output="test") |
| 19 | + |
| 20 | + trace = logger.traces[0] |
| 21 | + timestamps = [span.created_at for span in trace.spans] |
| 22 | + assert len(timestamps) == len(set(timestamps)), "Timestamps should be unique" |
| 23 | + assert timestamps == sorted(timestamps), "Timestamps should be monotonically increasing" |
| 24 | + |
| 25 | + |
| 26 | +@patch("galileo.logger.logger.LogStreams") |
| 27 | +@patch("galileo.logger.logger.Projects") |
| 28 | +def test_user_provided_timestamps_are_respected(mock_projects_client: Mock, mock_logstreams_client: Mock): |
| 29 | + """Tests that timestamps provided by the user are not modified.""" |
| 30 | + setup_mock_projects_client(mock_projects_client) |
| 31 | + setup_mock_logstreams_client(mock_logstreams_client) |
| 32 | + logger = GalileoLogger(project="test", log_stream="test") |
| 33 | + logger.start_trace(input="test") |
| 34 | + |
| 35 | + # Create timestamps in reverse order to test that the logger doesn't alter them |
| 36 | + now = datetime.now(timezone.utc) |
| 37 | + timestamps = [now - timedelta(seconds=i) for i in range(5)] |
| 38 | + |
| 39 | + for ts in timestamps: |
| 40 | + logger.add_llm_span(input="test", output="test", model="test", created_at=ts) |
| 41 | + |
| 42 | + logger.conclude(output="test") |
| 43 | + |
| 44 | + trace = logger.traces[0] |
| 45 | + span_timestamps = [span.created_at for span in trace.spans] |
| 46 | + assert span_timestamps == timestamps, "User-provided timestamps should be respected" |
| 47 | + |
| 48 | + |
| 49 | +@patch("galileo.logger.logger.LogStreams") |
| 50 | +@patch("galileo.logger.logger.Projects") |
| 51 | +def test_mixed_default_and_user_timestamps(mock_projects_client: Mock, mock_logstreams_client: Mock): |
| 52 | + """Tests that the internal state for default timestamp generation is not affected by user-provided timestamps.""" |
| 53 | + setup_mock_projects_client(mock_projects_client) |
| 54 | + setup_mock_logstreams_client(mock_logstreams_client) |
| 55 | + logger = GalileoLogger(project="test", log_stream="test") |
| 56 | + logger.start_trace(input="test") |
| 57 | + |
| 58 | + # 1. Add a default span |
| 59 | + logger.add_llm_span(input="test", output="test", model="test") |
| 60 | + |
| 61 | + # 2. Add a user-provided span with a timestamp in the past |
| 62 | + past_timestamp = datetime.now(timezone.utc) - timedelta(seconds=10) |
| 63 | + logger.add_llm_span(input="test", output="test", model="test", created_at=past_timestamp) |
| 64 | + |
| 65 | + # 3. Add a user-provided span with a timestamp in the future |
| 66 | + future_timestamp = datetime.now(timezone.utc) + timedelta(seconds=10) |
| 67 | + logger.add_llm_span(input="test", output="test", model="test", created_at=future_timestamp) |
| 68 | + |
| 69 | + # 4. Add a final default span |
| 70 | + logger.add_llm_span(input="test", output="test", model="test") |
| 71 | + |
| 72 | + logger.conclude(output="test") |
| 73 | + trace = logger.traces[0] |
| 74 | + |
| 75 | + default_span1_ts = trace.spans[0].created_at |
| 76 | + past_user_span_ts = trace.spans[1].created_at |
| 77 | + future_user_span_ts = trace.spans[2].created_at |
| 78 | + default_span2_ts = trace.spans[3].created_at |
| 79 | + |
| 80 | + assert past_user_span_ts == past_timestamp, "User-provided past timestamp should be respected" |
| 81 | + assert future_user_span_ts == future_timestamp, "User-provided future timestamp should be respected" |
| 82 | + assert default_span2_ts > default_span1_ts, "Second default timestamp should be greater than the first" |
| 83 | + assert default_span2_ts > past_user_span_ts, "Default timestamps are separate from past user-provided timestamps" |
| 84 | + assert default_span2_ts < future_user_span_ts, ( |
| 85 | + "Default timestamps are separate from future user-provided timestamps" |
| 86 | + ) |
0 commit comments