diff --git a/ext/opentelemetry-ext-zipkin/CHANGELOG.md b/ext/opentelemetry-ext-zipkin/CHANGELOG.md
new file mode 100644
index 00000000000..617d979ab29
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/CHANGELOG.md
@@ -0,0 +1,4 @@
+# Changelog
+
+## Unreleased
+
diff --git a/ext/opentelemetry-ext-zipkin/README.rst b/ext/opentelemetry-ext-zipkin/README.rst
new file mode 100644
index 00000000000..f91d0c2c6a7
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/README.rst
@@ -0,0 +1,67 @@
+OpenTelemetry Zipkin Exporter
+=============================
+
+|pypi|
+
+.. |pypi| image:: https://badge.fury.io/py/opentelemetry-ext-zipkin.svg
+ :target: https://pypi.org/project/opentelemetry-ext-zipkin/
+
+This library allows to export tracing data to `Zipkin `_.
+
+Installation
+------------
+
+::
+
+ pip install opentelemetry-ext-zipkin
+
+
+Usage
+-----
+
+The **OpenTelemetry Zipkin Exporter** allows to export `OpenTelemetry`_ traces to `Zipkin`_.
+This exporter always send traces to the configured Zipkin collector using HTTP.
+
+
+.. _Zipkin: https://zipkin.io/
+.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
+
+.. code:: python
+
+ from opentelemetry import trace
+ from opentelemetry.ext import zipkin
+ from opentelemetry.sdk.trace import TracerSource
+ from opentelemetry.sdk.trace.export import BatchExportSpanProcessor
+
+ trace.set_preferred_tracer_source_implementation(lambda T: TracerSource())
+ tracer = trace.tracer_source().get_tracer(__name__)
+
+ # create a ZipkinSpanExporter
+ zipkin_exporter = zipkin.ZipkinSpanExporter(
+ service_name="my-helloworld-service",
+ # optional:
+ # host_name="localhost",
+ # port=9411,
+ # endpoint="/api/v2/spans",
+ # protocol="http",
+ # ipv4="",
+ # ipv6="",
+ # retry=False,
+ )
+
+ # Create a BatchExportSpanProcessor and add the exporter to it
+ span_processor = BatchExportSpanProcessor(zipkin_exporter)
+
+ # add to the tracer
+ trace.tracer_source().add_span_processor(span_processor)
+
+ with tracer.start_as_current_span("foo"):
+ print("Hello world!")
+
+The `examples <./examples>`_ folder contains more elaborated examples.
+
+References
+----------
+
+* `Zipkin `_
+* `OpenTelemetry Project `_
diff --git a/ext/opentelemetry-ext-zipkin/setup.cfg b/ext/opentelemetry-ext-zipkin/setup.cfg
new file mode 100644
index 00000000000..89d60d149a7
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/setup.cfg
@@ -0,0 +1,47 @@
+# Copyright 2019, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+[metadata]
+name = opentelemetry-ext-zipkin
+description = Zipkin Span Exporter for OpenTelemetry
+long_description = file: README.rst
+long_description_content_type = text/x-rst
+author = OpenTelemetry Authors
+author_email = cncf-opentelemetry-contributors@lists.cncf.io
+url = https://github.com/open-telemetry/opentelemetry-python/ext/opentelemetry-ext-zipkin
+platforms = any
+license = Apache-2.0
+classifiers =
+ Development Status :: 3 - Alpha
+ Intended Audience :: Developers
+ License :: OSI Approved :: Apache Software License
+ Programming Language :: Python
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.4
+ Programming Language :: Python :: 3.5
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+
+[options]
+python_requires = >=3.4
+package_dir=
+ =src
+packages=find_namespace:
+install_requires =
+ requests~=2.7
+ opentelemetry-api
+ opentelemetry-sdk
+
+[options.packages.find]
+where = src
diff --git a/ext/opentelemetry-ext-zipkin/setup.py b/ext/opentelemetry-ext-zipkin/setup.py
new file mode 100644
index 00000000000..f93bbad4490
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/setup.py
@@ -0,0 +1,26 @@
+# Copyright 2019, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import os
+
+import setuptools
+
+BASE_DIR = os.path.dirname(__file__)
+VERSION_FILENAME = os.path.join(
+ BASE_DIR, "src", "opentelemetry", "ext", "zipkin", "version.py"
+)
+PACKAGE_INFO = {}
+with open(VERSION_FILENAME) as f:
+ exec(f.read(), PACKAGE_INFO)
+
+setuptools.setup(version=PACKAGE_INFO["__version__"])
diff --git a/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/__init__.py b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/__init__.py
new file mode 100644
index 00000000000..e0b5791d1e1
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/__init__.py
@@ -0,0 +1,184 @@
+# Copyright 2019, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Zipkin Span Exporter for OpenTelemetry."""
+
+import json
+import logging
+from typing import Optional, Sequence
+
+import requests
+
+from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
+from opentelemetry.trace import Span, SpanContext, SpanKind
+
+DEFAULT_ENDPOINT = "/api/v2/spans"
+DEFAULT_HOST_NAME = "localhost"
+DEFAULT_PORT = 9411
+DEFAULT_PROTOCOL = "http"
+DEFAULT_RETRY = False
+ZIPKIN_HEADERS = {"Content-Type": "application/json"}
+
+SPAN_KIND_MAP = {
+ SpanKind.INTERNAL: None,
+ SpanKind.SERVER: "SERVER",
+ SpanKind.CLIENT: "CLIENT",
+ SpanKind.PRODUCER: "PRODUCER",
+ SpanKind.CONSUMER: "CONSUMER",
+}
+
+SUCCESS_STATUS_CODES = (200, 202)
+
+logger = logging.getLogger(__name__)
+
+
+class ZipkinSpanExporter(SpanExporter):
+ """Zipkin span exporter for OpenTelemetry.
+
+ Args:
+ service_name: Service that logged an annotation in a trace.Classifier
+ when query for spans.
+ host_name: The host name of the Zipkin server
+ port: The port of the Zipkin server
+ endpoint: The endpoint of the Zipkin server
+ protocol: The protocol used for the request.
+ ipv4: Primary IPv4 address associated with this connection.
+ ipv6: Primary IPv6 address associated with this connection.
+ retry: Set to True to configure the exporter to retry on failure.
+ """
+
+ def __init__(
+ self,
+ service_name: str,
+ host_name: str = DEFAULT_HOST_NAME,
+ port: int = DEFAULT_PORT,
+ endpoint: str = DEFAULT_ENDPOINT,
+ protocol: str = DEFAULT_PROTOCOL,
+ ipv4: Optional[str] = None,
+ ipv6: Optional[str] = None,
+ retry: Optional[str] = DEFAULT_RETRY,
+ ):
+ self.service_name = service_name
+ self.host_name = host_name
+ self.port = port
+ self.endpoint = endpoint
+ self.protocol = protocol
+ self.url = "{}://{}:{}{}".format(
+ self.protocol, self.host_name, self.port, self.endpoint
+ )
+ self.ipv4 = ipv4
+ self.ipv6 = ipv6
+ self.retry = retry
+
+ def export(self, spans: Sequence[Span]) -> SpanExportResult:
+ zipkin_spans = self._translate_to_zipkin(spans)
+ result = requests.post(
+ url=self.url, data=json.dumps(zipkin_spans), headers=ZIPKIN_HEADERS
+ )
+
+ if result.status_code not in SUCCESS_STATUS_CODES:
+ logger.error(
+ "Traces cannot be uploaded; status code: %s, message %s",
+ result.status_code,
+ result.text,
+ )
+
+ if self.retry:
+ return SpanExportResult.FAILED_RETRYABLE
+ return SpanExportResult.FAILED_NOT_RETRYABLE
+ return SpanExportResult.SUCCESS
+
+ def _translate_to_zipkin(self, spans: Sequence[Span]):
+
+ local_endpoint = {
+ "serviceName": self.service_name,
+ "port": self.port,
+ }
+
+ if self.ipv4 is not None:
+ local_endpoint["ipv4"] = self.ipv4
+
+ if self.ipv6 is not None:
+ local_endpoint["ipv6"] = self.ipv6
+
+ zipkin_spans = []
+ for span in spans:
+ context = span.get_context()
+ trace_id = context.trace_id
+ span_id = context.span_id
+
+ # Timestamp in zipkin spans is int of microseconds.
+ # see: https://zipkin.io/pages/instrumenting.html
+ start_timestamp_mus = _nsec_to_usec_round(span.start_time)
+ duration_mus = _nsec_to_usec_round(span.end_time - span.start_time)
+
+ zipkin_span = {
+ "traceId": format(trace_id, "x"),
+ "id": format(span_id, "x"),
+ "name": span.name,
+ "timestamp": start_timestamp_mus,
+ "duration": duration_mus,
+ "localEndpoint": local_endpoint,
+ "kind": SPAN_KIND_MAP[span.kind],
+ "tags": _extract_tags_from_span(span.attributes),
+ "annotations": _extract_annotations_from_events(span.events),
+ }
+
+ if context.trace_options.sampled:
+ zipkin_span["debug"] = 1
+
+ if isinstance(span.parent, Span):
+ zipkin_span["parentId"] = format(
+ span.parent.get_context().span_id, "x"
+ )
+ elif isinstance(span.parent, SpanContext):
+ zipkin_span["parentId"] = format(span.parent.span_id, "x")
+
+ zipkin_spans.append(zipkin_span)
+ return zipkin_spans
+
+ def shutdown(self) -> None:
+ pass
+
+
+def _extract_tags_from_span(attr):
+ if not attr:
+ return None
+ tags = {}
+ for attribute_key, attribute_value in attr.items():
+ if isinstance(attribute_value, (int, bool, float)):
+ value = str(attribute_value)
+ elif isinstance(attribute_value, str):
+ value = attribute_value[:128]
+ else:
+ logger.warning("Could not serialize tag %s", attribute_key)
+ continue
+ tags[attribute_key] = value
+ return tags
+
+
+def _extract_annotations_from_events(events):
+ return (
+ [
+ {"timestamp": _nsec_to_usec_round(e.timestamp), "value": e.name}
+ for e in events
+ ]
+ if events
+ else None
+ )
+
+
+def _nsec_to_usec_round(nsec):
+ """Round nanoseconds to microseconds"""
+ return (nsec + 500) // 10 ** 3
diff --git a/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/version.py b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/version.py
new file mode 100644
index 00000000000..93ef792d051
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/src/opentelemetry/ext/zipkin/version.py
@@ -0,0 +1,15 @@
+# Copyright 2019, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+__version__ = "0.3.dev0"
diff --git a/ext/opentelemetry-ext-zipkin/tests/__init__.py b/ext/opentelemetry-ext-zipkin/tests/__init__.py
new file mode 100644
index 00000000000..d853a7bcf65
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/tests/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2019, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py b/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
new file mode 100644
index 00000000000..745c662f53b
--- /dev/null
+++ b/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
@@ -0,0 +1,242 @@
+# Copyright 2019, OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import json
+import unittest
+from unittest.mock import MagicMock, patch
+
+from opentelemetry import trace as trace_api
+from opentelemetry.ext.zipkin import ZipkinSpanExporter
+from opentelemetry.sdk import trace
+from opentelemetry.sdk.trace.export import SpanExportResult
+from opentelemetry.trace import TraceOptions
+
+
+class MockResponse:
+ def __init__(self, status_code):
+ self.status_code = status_code
+ self.text = status_code
+
+
+class TestZipkinSpanExporter(unittest.TestCase):
+ def setUp(self):
+ # create and save span to be used in tests
+ context = trace_api.SpanContext(
+ trace_id=0x000000000000000000000000DEADBEEF,
+ span_id=0x00000000DEADBEF0,
+ )
+
+ self._test_span = trace.Span("test_span", context=context)
+ self._test_span.start()
+ self._test_span.end()
+
+ def test_constructor_default(self):
+ """Test the default values assigned by constructor."""
+ service_name = "my-service-name"
+ host_name = "localhost"
+ port = 9411
+ endpoint = "/api/v2/spans"
+ exporter = ZipkinSpanExporter(service_name)
+ ipv4 = None
+ ipv6 = None
+ protocol = "http"
+ url = "http://localhost:9411/api/v2/spans"
+
+ self.assertEqual(exporter.service_name, service_name)
+ self.assertEqual(exporter.host_name, host_name)
+ self.assertEqual(exporter.port, port)
+ self.assertEqual(exporter.endpoint, endpoint)
+ self.assertEqual(exporter.ipv4, ipv4)
+ self.assertEqual(exporter.ipv6, ipv6)
+ self.assertEqual(exporter.protocol, protocol)
+ self.assertEqual(exporter.url, url)
+
+ def test_constructor_explicit(self):
+ """Test the constructor passing all the options."""
+ service_name = "my-opentelemetry-zipkin"
+ host_name = "opentelemetry.io"
+ port = 15875
+ endpoint = "/myapi/traces?format=zipkin"
+ ipv4 = "1.2.3.4"
+ ipv6 = "2001:0db8:85a3:0000:0000:8a2e:0370:7334"
+ protocol = "https"
+ url = "https://opentelemetry.io:15875/myapi/traces?format=zipkin"
+ exporter = ZipkinSpanExporter(
+ service_name=service_name,
+ host_name=host_name,
+ port=port,
+ endpoint=endpoint,
+ ipv4=ipv4,
+ ipv6=ipv6,
+ protocol=protocol,
+ )
+
+ self.assertEqual(exporter.service_name, service_name)
+ self.assertEqual(exporter.host_name, host_name)
+ self.assertEqual(exporter.port, port)
+ self.assertEqual(exporter.endpoint, endpoint)
+ self.assertEqual(exporter.ipv4, ipv4)
+ self.assertEqual(exporter.ipv6, ipv6)
+ self.assertEqual(exporter.protocol, protocol)
+ self.assertEqual(exporter.url, url)
+
+ # pylint: disable=too-many-locals
+ def test_export(self):
+
+ span_names = ("test1", "test2", "test3")
+ trace_id = 0x6E0C63257DE34C926F9EFCD03927272E
+ span_id = 0x34BF92DEEFC58C92
+ parent_id = 0x1111111111111111
+ other_id = 0x2222222222222222
+
+ base_time = 683647322 * 10 ** 9 # in ns
+ start_times = (
+ base_time,
+ base_time + 150 * 10 ** 6,
+ base_time + 300 * 10 ** 6,
+ )
+ durations = (50 * 10 ** 6, 100 * 10 ** 6, 200 * 10 ** 6)
+ end_times = (
+ start_times[0] + durations[0],
+ start_times[1] + durations[1],
+ start_times[2] + durations[2],
+ )
+
+ span_context = trace_api.SpanContext(
+ trace_id,
+ span_id,
+ trace_options=TraceOptions(TraceOptions.SAMPLED),
+ )
+ parent_context = trace_api.SpanContext(trace_id, parent_id)
+ other_context = trace_api.SpanContext(trace_id, other_id)
+
+ event_attributes = {
+ "annotation_bool": True,
+ "annotation_string": "annotation_test",
+ "key_float": 0.3,
+ }
+
+ event_timestamp = base_time + 50 * 10 ** 6
+ event = trace_api.Event(
+ name="event0",
+ timestamp=event_timestamp,
+ attributes=event_attributes,
+ )
+
+ link_attributes = {"key_bool": True}
+
+ link = trace_api.Link(
+ context=other_context, attributes=link_attributes
+ )
+
+ otel_spans = [
+ trace.Span(
+ name=span_names[0],
+ context=span_context,
+ parent=parent_context,
+ events=(event,),
+ links=(link,),
+ ),
+ trace.Span(
+ name=span_names[1], context=parent_context, parent=None
+ ),
+ trace.Span(name=span_names[2], context=other_context, parent=None),
+ ]
+
+ otel_spans[0].start_time = start_times[0]
+ # added here to preserve order
+ otel_spans[0].set_attribute("key_bool", False)
+ otel_spans[0].set_attribute("key_string", "hello_world")
+ otel_spans[0].set_attribute("key_float", 111.22)
+ otel_spans[0].end_time = end_times[0]
+
+ otel_spans[1].start_time = start_times[1]
+ otel_spans[1].end_time = end_times[1]
+
+ otel_spans[2].start_time = start_times[2]
+ otel_spans[2].end_time = end_times[2]
+
+ service_name = "test-service"
+ local_endpoint = {
+ "serviceName": service_name,
+ "port": 9411,
+ }
+
+ exporter = ZipkinSpanExporter(service_name)
+ expected = [
+ {
+ "traceId": format(trace_id, "x"),
+ "id": format(span_id, "x"),
+ "name": span_names[0],
+ "timestamp": start_times[0] // 10 ** 3,
+ "duration": durations[0] // 10 ** 3,
+ "localEndpoint": local_endpoint,
+ "kind": None,
+ "tags": {
+ "key_bool": "False",
+ "key_string": "hello_world",
+ "key_float": "111.22",
+ },
+ "annotations": [
+ {
+ "timestamp": event_timestamp // 10 ** 3,
+ "value": "event0",
+ }
+ ],
+ "debug": 1,
+ "parentId": format(parent_id, "x"),
+ },
+ {
+ "traceId": format(trace_id, "x"),
+ "id": format(parent_id, "x"),
+ "name": span_names[1],
+ "timestamp": start_times[1] // 10 ** 3,
+ "duration": durations[1] // 10 ** 3,
+ "localEndpoint": local_endpoint,
+ "kind": None,
+ "tags": None,
+ "annotations": None,
+ },
+ {
+ "traceId": format(trace_id, "x"),
+ "id": format(other_id, "x"),
+ "name": span_names[2],
+ "timestamp": start_times[2] // 10 ** 3,
+ "duration": durations[2] // 10 ** 3,
+ "localEndpoint": local_endpoint,
+ "kind": None,
+ "tags": None,
+ "annotations": None,
+ },
+ ]
+
+ mock_post = MagicMock()
+ with patch("requests.post", mock_post):
+ mock_post.return_value = MockResponse(200)
+ status = exporter.export(otel_spans)
+ self.assertEqual(SpanExportResult.SUCCESS, status)
+
+ mock_post.assert_called_with(
+ url="http://localhost:9411/api/v2/spans",
+ data=json.dumps(expected),
+ headers={"Content-Type": "application/json"},
+ )
+
+ @patch("requests.post")
+ def test_invalid_response(self, mock_post):
+ mock_post.return_value = MockResponse(404)
+ spans = []
+ exporter = ZipkinSpanExporter("test-service")
+ status = exporter.export(spans)
+ self.assertEqual(SpanExportResult.FAILED_NOT_RETRYABLE, status)
diff --git a/scripts/coverage.sh b/scripts/coverage.sh
index bddf39a90cd..9b981b0817c 100755
--- a/scripts/coverage.sh
+++ b/scripts/coverage.sh
@@ -17,10 +17,12 @@ coverage erase
cov opentelemetry-api
cov opentelemetry-sdk
+cov ext/opentelemetry-ext-flask
cov ext/opentelemetry-ext-http-requests
cov ext/opentelemetry-ext-jaeger
cov ext/opentelemetry-ext-opentracing-shim
cov ext/opentelemetry-ext-wsgi
+cov ext/opentelemetry-ext-zipkin
cov examples/opentelemetry-example-app
coverage report
diff --git a/tox.ini b/tox.ini
index 2ded7d1b753..8a4321a95c5 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,10 +2,10 @@
skipsdist = True
skip_missing_interpreters = True
envlist =
- py3{4,5,6,7,8}-test-{api,sdk,example-app,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,opentracing-shim}
- pypy3-test-{api,sdk,example-app,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,opentracing-shim}
- py3{4,5,6,7,8}-test-{api,sdk,example-app,example-basic-tracer,example-http,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,opentracing-shim}
- pypy3-test-{api,sdk,example-app,example-basic-tracer,example-http,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,opentracing-shim}
+ py3{4,5,6,7,8}-test-{api,sdk,example-app,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,ext-zipkin,opentracing-shim}
+ pypy3-test-{api,sdk,example-app,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,ext-zipkin,opentracing-shim}
+ py3{4,5,6,7,8}-test-{api,sdk,example-app,example-basic-tracer,example-http,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,ext-zipkin,opentracing-shim}
+ pypy3-test-{api,sdk,example-app,example-basic-tracer,example-http,ext-wsgi,ext-flask,ext-http-requests,ext-jaeger,ext-pymongo,ext-zipkin,opentracing-shim}
py3{4,5,6,7,8}-coverage
; Coverage is temporarily disabled for pypy3 due to the pytest bug.
@@ -38,6 +38,7 @@ changedir =
test-ext-jaeger: ext/opentelemetry-ext-jaeger/tests
test-ext-pymongo: ext/opentelemetry-ext-pymongo/tests
test-ext-wsgi: ext/opentelemetry-ext-wsgi/tests
+ test-ext-zipkin: ext/opentelemetry-ext-zipkin/tests
test-ext-flask: ext/opentelemetry-ext-flask/tests
test-example-app: examples/opentelemetry-example-app/tests
test-example-basic-tracer: examples/basic_tracer/tests
@@ -71,6 +72,7 @@ commands_pre =
jaeger: pip install {toxinidir}/opentelemetry-sdk
jaeger: pip install {toxinidir}/ext/opentelemetry-ext-jaeger
opentracing-shim: pip install {toxinidir}/opentelemetry-sdk {toxinidir}/ext/opentelemetry-ext-opentracing-shim
+ zipkin: pip install {toxinidir}/ext/opentelemetry-ext-zipkin
; In order to get a healthy coverage report,
; we have to install packages in editable mode.