Skip to content

Commit 9b46ef7

Browse files
authored
test(gapic): achieve 100% branch and statement coverage for OTel T3 method tracing
1 parent 4cd28f0 commit 9b46ef7

2 files changed

Lines changed: 214 additions & 1 deletion

File tree

packages/google-api-core/google/api_core/gapic_v1/method.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ def __call__(
218218
span.record_exception(exc)
219219
span.set_status(trace.StatusCode.ERROR, str(exc))
220220
raise
221-
except ImportError:
221+
# If OpenTelemetry cannot be imported in the current environment, continue without tracing.
222+
except ImportError: # pragma: NO COVER
222223
pass
223224

224225
return wrapped_func(*args, **kwargs)

packages/google-api-core/tests/unit/gapic/test_method.py

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,215 @@ def test_wrap_method_otel_tracing_enabled_error(monkeypatch):
446446

447447
mock_span.record_exception.assert_called_once_with(err)
448448
mock_span.set_status.assert_called_once_with("ERROR", str(err))
449+
450+
451+
def test_wrap_method_otel_tracing_bytes_method(monkeypatch):
452+
"""Proves that when raw _method is bytes, it is decoded properly to utf-8."""
453+
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true")
454+
mock_target = mock.Mock(return_value="success")
455+
mock_target._method = (
456+
b"/google.cloud.secretmanager.v1.SecretManagerService/ListSecrets"
457+
)
458+
459+
mock_span = mock.MagicMock()
460+
mock_tracer = mock.MagicMock()
461+
mock_tracer.start_as_current_span.return_value.__enter__.return_value = mock_span
462+
463+
mock_trace = mock.Mock()
464+
mock_trace.get_tracer.return_value = mock_tracer
465+
mock_trace.SpanKind.CLIENT = "CLIENT"
466+
467+
wrapped = google.api_core.gapic_v1.method.wrap_method(
468+
mock_target, default_timeout=60
469+
)
470+
471+
with (
472+
mock.patch(
473+
"google.api_core._observability.is_otel_capabilities_enabled",
474+
return_value=True,
475+
),
476+
mock.patch.dict(
477+
sys.modules,
478+
{
479+
"opentelemetry": mock.Mock(trace=mock_trace),
480+
"opentelemetry.trace": mock_trace,
481+
},
482+
),
483+
):
484+
result = wrapped()
485+
486+
assert result == "success"
487+
mock_tracer.start_as_current_span.assert_called_once_with(
488+
"google.cloud.secretmanager.v1.SecretManagerService/ListSecrets",
489+
kind="CLIENT",
490+
attributes={
491+
"rpc.system": "grpc",
492+
"rpc.service": "google.cloud.secretmanager.v1.SecretManagerService",
493+
"rpc.method": "ListSecrets",
494+
},
495+
)
496+
497+
498+
def test_wrap_method_otel_tracing_fallback_with_name(monkeypatch):
499+
"""Proves that when raw _method is absent, fallback uses target.__name__."""
500+
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true")
501+
502+
def custom_rpc(*args, **kwargs):
503+
return "success"
504+
505+
mock_span = mock.MagicMock()
506+
mock_tracer = mock.MagicMock()
507+
mock_tracer.start_as_current_span.return_value.__enter__.return_value = mock_span
508+
509+
mock_trace = mock.Mock()
510+
mock_trace.get_tracer.return_value = mock_tracer
511+
mock_trace.SpanKind.CLIENT = "CLIENT"
512+
513+
wrapped = google.api_core.gapic_v1.method.wrap_method(custom_rpc)
514+
515+
with (
516+
mock.patch(
517+
"google.api_core._observability.is_otel_capabilities_enabled",
518+
return_value=True,
519+
),
520+
mock.patch.dict(
521+
sys.modules,
522+
{
523+
"opentelemetry": mock.Mock(trace=mock_trace),
524+
"opentelemetry.trace": mock_trace,
525+
},
526+
),
527+
):
528+
result = wrapped()
529+
530+
assert result == "success"
531+
mock_tracer.start_as_current_span.assert_called_once_with(
532+
"google.api_core/custom_rpc",
533+
kind="CLIENT",
534+
attributes={
535+
"rpc.system": "grpc",
536+
"rpc.service": "google.api_core",
537+
"rpc.method": "custom_rpc",
538+
},
539+
)
540+
541+
542+
def test_wrap_method_otel_tracing_fallback_without_name(monkeypatch):
543+
"""Proves that when raw _method is absent and target has no explicit __name__,
544+
fallback uses target class name assigned by error wrapper.
545+
"""
546+
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true")
547+
548+
class TargetWithoutName:
549+
def __call__(self, *args, **kwargs):
550+
return "success"
551+
552+
mock_span = mock.MagicMock()
553+
mock_tracer = mock.MagicMock()
554+
mock_tracer.start_as_current_span.return_value.__enter__.return_value = mock_span
555+
556+
mock_trace = mock.Mock()
557+
mock_trace.get_tracer.return_value = mock_tracer
558+
mock_trace.SpanKind.CLIENT = "CLIENT"
559+
560+
wrapped = google.api_core.gapic_v1.method.wrap_method(TargetWithoutName())
561+
562+
with (
563+
mock.patch(
564+
"google.api_core._observability.is_otel_capabilities_enabled",
565+
return_value=True,
566+
),
567+
mock.patch.dict(
568+
sys.modules,
569+
{
570+
"opentelemetry": mock.Mock(trace=mock_trace),
571+
"opentelemetry.trace": mock_trace,
572+
},
573+
),
574+
):
575+
result = wrapped()
576+
577+
assert result == "success"
578+
mock_tracer.start_as_current_span.assert_called_once_with(
579+
"google.api_core/TargetWithoutName",
580+
kind="CLIENT",
581+
attributes={
582+
"rpc.system": "grpc",
583+
"rpc.service": "google.api_core",
584+
"rpc.method": "TargetWithoutName",
585+
},
586+
)
587+
588+
589+
def test_gapic_callable_otel_tracing_fallback_call_default(monkeypatch):
590+
"""Proves that _GapicCallable defaults method to 'call' if target has no __name__."""
591+
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true")
592+
593+
class NoNameTarget:
594+
def __call__(self, *args, **kwargs):
595+
return "success"
596+
597+
target = NoNameTarget()
598+
callable_obj = google.api_core.gapic_v1.method._GapicCallable(
599+
target, None, None, None
600+
)
601+
602+
mock_span = mock.MagicMock()
603+
mock_tracer = mock.MagicMock()
604+
mock_tracer.start_as_current_span.return_value.__enter__.return_value = mock_span
605+
606+
mock_trace = mock.Mock()
607+
mock_trace.get_tracer.return_value = mock_tracer
608+
mock_trace.SpanKind.CLIENT = "CLIENT"
609+
610+
with (
611+
mock.patch(
612+
"google.api_core._observability.is_otel_capabilities_enabled",
613+
return_value=True,
614+
),
615+
mock.patch.dict(
616+
sys.modules,
617+
{
618+
"opentelemetry": mock.Mock(trace=mock_trace),
619+
"opentelemetry.trace": mock_trace,
620+
},
621+
),
622+
):
623+
result = callable_obj()
624+
625+
assert result == "success"
626+
mock_tracer.start_as_current_span.assert_called_once_with(
627+
"google.api_core/call",
628+
kind="CLIENT",
629+
attributes={
630+
"rpc.system": "grpc",
631+
"rpc.service": "google.api_core",
632+
"rpc.method": "call",
633+
},
634+
)
635+
636+
637+
def test_wrap_method_otel_tracing_import_error(monkeypatch):
638+
"""Proves that if opentelemetry raises ImportError, execution proceeds gracefully."""
639+
monkeypatch.setenv("GOOGLE_SDK_EXPERIMENTAL_PYTHON_TRACING_ENABLED", "true")
640+
mock_target = mock.Mock(return_value="success")
641+
642+
wrapped = google.api_core.gapic_v1.method.wrap_method(mock_target)
643+
644+
with (
645+
mock.patch(
646+
"google.api_core._observability.is_otel_capabilities_enabled",
647+
return_value=True,
648+
),
649+
mock.patch.dict(
650+
sys.modules,
651+
{
652+
"opentelemetry": None,
653+
"opentelemetry.trace": None,
654+
},
655+
),
656+
):
657+
result = wrapped()
658+
659+
assert result == "success"
660+
mock_target.assert_called_once()

0 commit comments

Comments
 (0)