Files
2026-07-13 13:22:34 +08:00

112 lines
4.1 KiB
Python

import pytest
from opentelemetry import trace as trace_api
from opentelemetry.proto.trace.v1.trace_pb2 import Status as OTelProtoStatus
from opentelemetry.trace import Status as OTelStatus
from opentelemetry.trace import StatusCode as OTelStatusCode
from mlflow.entities import SpanStatus, SpanStatusCode
from mlflow.exceptions import MlflowException
@pytest.mark.parametrize("status_code", [SpanStatusCode.OK, "OK"])
def test_span_status_init(status_code):
span_status = SpanStatus(status_code)
assert span_status.status_code == SpanStatusCode.OK
def test_span_status_raise_invalid_status_code():
with pytest.raises(MlflowException, match=r"INVALID is not a valid SpanStatusCode value."):
SpanStatus("INVALID", description="test")
@pytest.mark.parametrize(
("status_code", "otel_status_code"),
[
(SpanStatusCode.OK, trace_api.StatusCode.OK),
(SpanStatusCode.ERROR, trace_api.StatusCode.ERROR),
(SpanStatusCode.UNSET, trace_api.StatusCode.UNSET),
],
)
def test_otel_status_conversion(status_code, otel_status_code):
span_status = SpanStatus(status_code, description="test")
otel_status = span_status.to_otel_status()
# OpenTelemetry only allows specify description when status is ERROR
# Otherwise it will be ignored with warning message.
expected_description = "test" if status_code == SpanStatusCode.ERROR else None
assert otel_status.status_code == otel_status_code
assert otel_status.description == expected_description
span_status = SpanStatus.from_otel_status(otel_status)
assert span_status.status_code == status_code
assert span_status.description == (expected_description or "")
@pytest.mark.parametrize(
("status_code", "status_desc", "expected_code", "expected_desc"),
[
# OTel only keeps description for ERROR status
(OTelStatusCode.OK, "Success", SpanStatusCode.OK, ""),
(OTelStatusCode.ERROR, "Failed", SpanStatusCode.ERROR, "Failed"),
(OTelStatusCode.UNSET, "", SpanStatusCode.UNSET, ""),
],
)
def test_otel_status_to_mlflow_status_conversion(
status_code, status_desc, expected_code, expected_desc
):
# Create OTel status
otel_status = OTelStatus(status_code, status_desc)
# Convert to MLflow status
mlflow_status = SpanStatus.from_otel_status(otel_status)
assert mlflow_status.status_code == expected_code
# OTel SDK clears description for non-ERROR statuses
assert mlflow_status.description == (status_desc if status_code == OTelStatusCode.ERROR else "")
# Convert back to OTel status
converted_status = mlflow_status.to_otel_status()
assert converted_status.status_code == status_code
# Description is only preserved for ERROR status
assert converted_status.description == expected_desc
@pytest.mark.parametrize(
("proto_code", "expected_mlflow_code"),
[
(OTelProtoStatus.STATUS_CODE_OK, SpanStatusCode.OK),
(OTelProtoStatus.STATUS_CODE_ERROR, SpanStatusCode.ERROR),
(OTelProtoStatus.STATUS_CODE_UNSET, SpanStatusCode.UNSET),
],
)
def test_otel_proto_status_to_mlflow_status_conversion(proto_code, expected_mlflow_code):
# Create proto status
proto_status = OTelProtoStatus()
proto_status.code = proto_code
proto_status.message = "test message"
# Convert to MLflow status
mlflow_status = SpanStatus.from_otel_proto_status(proto_status)
assert mlflow_status.status_code == expected_mlflow_code
assert mlflow_status.description == "test message"
@pytest.mark.parametrize(
("mlflow_code", "description", "expected_proto_code"),
[
(SpanStatusCode.OK, "Success", OTelProtoStatus.STATUS_CODE_OK),
(SpanStatusCode.ERROR, "Failed", OTelProtoStatus.STATUS_CODE_ERROR),
(SpanStatusCode.UNSET, "", OTelProtoStatus.STATUS_CODE_UNSET),
],
)
def test_mlflow_status_to_otel_proto_status_conversion(
mlflow_code, description, expected_proto_code
):
# Create MLflow status
mlflow_status = SpanStatus(mlflow_code, description)
# Convert to OTel proto status
proto_status = mlflow_status.to_otel_proto_status()
assert proto_status.code == expected_proto_code
assert proto_status.message == description