Skip to content

Commit dcad635

Browse files
committed
Teach our integrations utils about http tests
And add a test for http otlp exporter user-agent.
1 parent 7ca4f0d commit dcad635

File tree

2 files changed

+135
-6
lines changed

2 files changed

+135
-6
lines changed

tests/integration/test_integration.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,16 @@
1919
import pytest
2020

2121
from elasticotel.distro import version
22-
from .utils import ElasticIntegrationTestCase, OTEL_INSTRUMENTATION_VERSION, ROOT_DIR
22+
from .utils import (
23+
ElasticIntegrationGRPCTestCase,
24+
ElasticIntegrationHTTPTestCase,
25+
OTEL_INSTRUMENTATION_VERSION,
26+
ROOT_DIR,
27+
)
2328

2429

2530
@pytest.mark.integration
26-
class IntegrationTestCase(ElasticIntegrationTestCase):
31+
class GRPCIntegrationTestCase(ElasticIntegrationGRPCTestCase):
2732
@classmethod
2833
def requirements(cls):
2934
requirements = super().requirements()
@@ -176,7 +181,47 @@ def test_script():
176181

177182

178183
@pytest.mark.integration
179-
class OperatorTestCase(ElasticIntegrationTestCase):
184+
class HTTPIntegrationTestCase(ElasticIntegrationHTTPTestCase):
185+
@classmethod
186+
def requirements(cls):
187+
requirements = super().requirements()
188+
return requirements + [f"opentelemetry-instrumentation-sqlite3=={OTEL_INSTRUMENTATION_VERSION}"]
189+
190+
def test_edot_user_agent_is_used_in_otlp_http_exporter(self):
191+
def test_script():
192+
import sqlite3
193+
194+
from opentelemetry._events import Event, get_event_logger
195+
196+
connection = sqlite3.connect(":memory:")
197+
cursor = connection.cursor()
198+
cursor.execute("CREATE TABLE movie(title, year, score)")
199+
200+
event = Event(name="test.event", attributes={}, body={"key": "value"})
201+
event_logger = get_event_logger(__name__)
202+
event_logger.emit(event)
203+
204+
stdout, stderr, returncode = self.run_script(test_script, wrapper_script="opentelemetry-instrument")
205+
206+
telemetry = self.get_telemetry()
207+
(metrics_headers, logs_headers, traces_headers) = (
208+
telemetry["metrics_headers"],
209+
telemetry["logs_headers"],
210+
telemetry["traces_headers"],
211+
)
212+
213+
assert metrics_headers
214+
assert traces_headers
215+
assert logs_headers
216+
217+
edot_user_agent = "elastic-otlp-http-python/" + version.__version__
218+
self.assertIn(edot_user_agent, metrics_headers[0]["User-Agent"])
219+
self.assertIn(edot_user_agent, traces_headers[0]["User-Agent"])
220+
self.assertIn(edot_user_agent, logs_headers[0]["User-Agent"])
221+
222+
223+
@pytest.mark.integration
224+
class OperatorTestCase(ElasticIntegrationHTTPTestCase):
180225
@staticmethod
181226
def _read_operator_requirements():
182227
requirements = []

tests/integration/utils.py

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import os
2121
import tempfile
2222
import unittest
23+
from http.server import BaseHTTPRequestHandler, HTTPServer
2324
from pathlib import Path
2425
from typing import Callable, Mapping, Optional
2526

@@ -33,7 +34,7 @@
3334
ROOT_DIR = os.path.abspath(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
3435

3536

36-
class ElasticIntegrationTestCase(unittest.TestCase):
37+
class ElasticIntegrationGRPCTestCase(unittest.TestCase):
3738
"""This is an experimental reimplementation of OtelTest using unittest
3839
3940
The idea is to do integration testing by creating a separate virtualenv for each TestCase inheriting
@@ -42,7 +43,7 @@ class ElasticIntegrationTestCase(unittest.TestCase):
4243
4344
A basic TestCase would look like:
4445
45-
class MyTestCase(ElasticIntegrationTestCase):
46+
class MyTestCase(ElasticIntegrationGRPCTestCase):
4647
@classmethod
4748
def requirements(cls):
4849
requirements = super().requirements()
@@ -65,6 +66,8 @@ def test_one_span_generated(self):
6566
Each TestCase costs around 10 seconds for settings up the virtualenv so split tests accordingly.
6667
"""
6768

69+
sink_class = ot.GrpcSink
70+
6871
@classmethod
6972
def requirements(cls) -> list:
7073
"""Installs requirements in a virtualenv
@@ -96,7 +99,7 @@ def tearDownClass(cls):
9699

97100
def setUp(self):
98101
self.handler = ot.AccumulatingHandler()
99-
self.sink = ot.GrpcSink(self.handler)
102+
self.sink = self.sink_class(self.handler)
100103
self.sink.start()
101104

102105
def tearDown(self):
@@ -274,3 +277,84 @@ def run_script(
274277
ex.stderr.decode() if ex.stderr else None,
275278
proc.returncode,
276279
)
280+
281+
282+
class HttpSink(ot.HttpSink):
283+
"""Backports teardown fixes, drop if we ever rebase on top of oteltest 0.37.0+"""
284+
285+
# This code is copyright Pablo Collins under Apache-2.0
286+
287+
def __init__(self, listener, port=4318, daemon=True):
288+
self.httpd = None
289+
super().__init__(listener, port, daemon)
290+
291+
def run_server(self):
292+
class Handler(BaseHTTPRequestHandler):
293+
def do_POST(this):
294+
# /v1/traces
295+
content_length = int(this.headers["Content-Length"])
296+
post_data = this.rfile.read(content_length)
297+
298+
otlp_handler_func = self.handlers.get(this.path)
299+
if otlp_handler_func:
300+
otlp_handler_func(post_data, {k: v for k, v in this.headers.items()})
301+
302+
this.send_response(200)
303+
this.send_header("Content-type", "text/html")
304+
this.end_headers()
305+
306+
this.wfile.write("OK".encode("utf-8"))
307+
308+
self.httpd = HTTPServer(("", self.port), Handler)
309+
self.httpd.serve_forever()
310+
311+
def stop(self):
312+
self.httpd.shutdown()
313+
super().stop()
314+
315+
316+
class ElasticIntegrationHTTPTestCase(ElasticIntegrationGRPCTestCase):
317+
"""This is an experimental reimplementation of OtelTest using unittest
318+
319+
The idea is to do integration testing by creating a separate virtualenv for each TestCase inheriting
320+
from ElasticIntegrationTestCase, run a script, collect OTLP http/protobuf calls and make the received data
321+
available in order to add assertions.
322+
323+
A basic TestCase would look like:
324+
325+
class MyTestCase(ElasticIntegrationHTTPTestCase):
326+
@classmethod
327+
def requirements(cls):
328+
requirements = super().requirements()
329+
return requirements + [f"my-library"]
330+
331+
def script(self):
332+
import sqlite3
333+
334+
connection = sqlite3.connect(":memory:")
335+
cursor = connection.cursor()
336+
cursor.execute("CREATE TABLE movie(title, year, score)")
337+
338+
def test_one_span_generated(self):
339+
stdout, stderr, returncode = self.run_script(self.script, wrapper_script="opentelemetry-instrument")
340+
341+
telemetry.get_telemetry()
342+
assert len(telemetry["traces"], 1)
343+
344+
345+
Each TestCase costs around 10 seconds for settings up the virtualenv so split tests accordingly.
346+
"""
347+
348+
sink_class = HttpSink
349+
350+
def run_script(
351+
self,
352+
script_func: Callable[[], None],
353+
environment_variables: Optional[Mapping[str, str]] = None,
354+
wrapper_script: Optional[str] = None,
355+
on_start: Optional[Callable[[], Optional[float]]] = None,
356+
):
357+
# Add a sane default so that callers don't need to remember to set that
358+
if environment_variables is None:
359+
environment_variables = {"OTEL_EXPORTER_OTLP_PROTOCOL": "http/protobuf"}
360+
return super().run_script(script_func, environment_variables, wrapper_script, on_start)

0 commit comments

Comments
 (0)