diff --git a/opentelemetry-sdk/tests/trace/export/test_export.py b/opentelemetry-sdk/tests/trace/export/test_export.py index d45afc299a2..9ad65aea881 100644 --- a/opentelemetry-sdk/tests/trace/export/test_export.py +++ b/opentelemetry-sdk/tests/trace/export/test_export.py @@ -27,6 +27,7 @@ class MySpanExporter(export.SpanExporter): def __init__(self, destination, max_export_batch_size=None): self.destination = destination self.max_export_batch_size = max_export_batch_size + self.is_shutdown = False def export(self, spans: trace.Span) -> export.SpanExportResult: if ( @@ -37,6 +38,9 @@ def export(self, spans: trace.Span) -> export.SpanExportResult: self.destination.extend(span.name for span in spans) return export.SpanExportResult.SUCCESS + def shutdown(self): + self.is_shutdown = True + class TestSimpleExportSpanProcessor(unittest.TestCase): def test_simple_span_processor(self): @@ -55,6 +59,9 @@ def test_simple_span_processor(self): self.assertListEqual(["xxx", "bar", "foo"], spans_names_list) + span_processor.shutdown() + self.assertTrue(my_exporter.is_shutdown) + def test_simple_span_processor_no_context(self): """Check that we process spans that are never made active. @@ -102,6 +109,8 @@ def test_batch_span_processor(self): span_processor.shutdown() self.assertListEqual(span_names, spans_names_list) + self.assertTrue(my_exporter.is_shutdown) + def test_batch_span_processor_lossless(self): """Test that no spans are lost when sending max_queue_size spans""" spans_names_list = [] diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 50249479a7e..6938f464d87 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import subprocess import unittest from unittest import mock @@ -26,6 +27,75 @@ def test_extends_api(self): tracer = trace.Tracer() self.assertIsInstance(tracer, trace_api.Tracer) + def test_shutdown(self): + tracer = trace.Tracer() + + mock_processor1 = mock.Mock(spec=trace.SpanProcessor) + tracer.add_span_processor(mock_processor1) + + mock_processor2 = mock.Mock(spec=trace.SpanProcessor) + tracer.add_span_processor(mock_processor2) + + tracer.shutdown() + + mock_processor1.shutdown.assert_called_once() + mock_processor2.shutdown.assert_called_once() + + shutdown_python_code = """ +import atexit +from unittest import mock + +from opentelemetry.sdk import trace + +mock_processor = mock.Mock(spec=trace.SpanProcessor) + +def print_shutdown_count(): + print(mock_processor.shutdown.call_count) + +# atexit hooks are called in inverse order they are added, so do this before +# creating the tracer +atexit.register(print_shutdown_count) + +tracer = trace.Tracer({tracer_parameters}) +tracer.add_span_processor(mock_processor) + +{tracer_shutdown} +""" + + def run_general_code(shutdown_on_exit, explicit_shutdown): + tracer_parameters = "" + tracer_shutdown = "" + + if not shutdown_on_exit: + tracer_parameters = "shutdown_on_exit=False" + + if explicit_shutdown: + tracer_shutdown = "tracer.shutdown()" + + return subprocess.check_output( + [ + "python", + "-c", + shutdown_python_code.format( + tracer_parameters=tracer_parameters, + tracer_shutdown=tracer_shutdown, + ), + ] + ) + + # test default shutdown_on_exit (True) + out = run_general_code(True, False) + self.assertTrue(out.startswith(b"1")) + + # test that shutdown is called only once even if Tracer.shutdown is + # called explicitely + out = run_general_code(True, True) + self.assertTrue(out.startswith(b"1")) + + # test shutdown_on_exit=False + out = run_general_code(False, False) + self.assertTrue(out.startswith(b"0")) + class TestTracerSampling(unittest.TestCase): def test_default_sampler(self):