20
20
import os
21
21
import tempfile
22
22
import unittest
23
+ from http .server import BaseHTTPRequestHandler , HTTPServer
23
24
from pathlib import Path
24
25
from typing import Callable , Mapping , Optional
25
26
33
34
ROOT_DIR = os .path .abspath (os .path .dirname (os .path .dirname (os .path .dirname (__file__ ))))
34
35
35
36
36
- class ElasticIntegrationTestCase (unittest .TestCase ):
37
+ class ElasticIntegrationGRPCTestCase (unittest .TestCase ):
37
38
"""This is an experimental reimplementation of OtelTest using unittest
38
39
39
40
The idea is to do integration testing by creating a separate virtualenv for each TestCase inheriting
@@ -42,7 +43,7 @@ class ElasticIntegrationTestCase(unittest.TestCase):
42
43
43
44
A basic TestCase would look like:
44
45
45
- class MyTestCase(ElasticIntegrationTestCase ):
46
+ class MyTestCase(ElasticIntegrationGRPCTestCase ):
46
47
@classmethod
47
48
def requirements(cls):
48
49
requirements = super().requirements()
@@ -65,6 +66,8 @@ def test_one_span_generated(self):
65
66
Each TestCase costs around 10 seconds for settings up the virtualenv so split tests accordingly.
66
67
"""
67
68
69
+ sink_class = ot .GrpcSink
70
+
68
71
@classmethod
69
72
def requirements (cls ) -> list :
70
73
"""Installs requirements in a virtualenv
@@ -96,7 +99,7 @@ def tearDownClass(cls):
96
99
97
100
def setUp (self ):
98
101
self .handler = ot .AccumulatingHandler ()
99
- self .sink = ot . GrpcSink (self .handler )
102
+ self .sink = self . sink_class (self .handler )
100
103
self .sink .start ()
101
104
102
105
def tearDown (self ):
@@ -274,3 +277,84 @@ def run_script(
274
277
ex .stderr .decode () if ex .stderr else None ,
275
278
proc .returncode ,
276
279
)
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