|
18 | 18 | and audio format conversion (e.g., PCM16 to WAV) |
19 | 19 | """ |
20 | 20 |
|
| 21 | +import os |
| 22 | +import subprocess |
| 23 | +import sys |
| 24 | +import textwrap |
21 | 25 | from pathlib import Path |
22 | 26 | from unittest.mock import patch |
23 | 27 |
|
@@ -65,6 +69,55 @@ def _read_audio_file(filename: str) -> bytes: |
65 | 69 | with open(filepath, "rb") as file_obj: |
66 | 70 | return file_obj.read() |
67 | 71 |
|
| 72 | + @staticmethod |
| 73 | + def test_import_without_audio_libs_does_not_write_to_standard_streams(): |
| 74 | + """Missing optional audio libs should not emit import-time output.""" |
| 75 | + project_root = Path(__file__).parents[4] |
| 76 | + util_genai_src = Path(__file__).parents[2] / "src" |
| 77 | + instrumentation_src = ( |
| 78 | + project_root / "opentelemetry-instrumentation" / "src" |
| 79 | + ) |
| 80 | + env = os.environ.copy() |
| 81 | + pythonpath_parts = [ |
| 82 | + str(util_genai_src), |
| 83 | + str(instrumentation_src), |
| 84 | + env.get("PYTHONPATH", ""), |
| 85 | + ] |
| 86 | + env["PYTHONPATH"] = os.pathsep.join( |
| 87 | + part for part in pythonpath_parts if part |
| 88 | + ) |
| 89 | + |
| 90 | + script = textwrap.dedent( |
| 91 | + """ |
| 92 | + import importlib.abc |
| 93 | + import logging |
| 94 | + import sys |
| 95 | +
|
| 96 | + class BlockAudioLibs(importlib.abc.MetaPathFinder): |
| 97 | + def find_spec(self, fullname, path=None, target=None): |
| 98 | + if fullname == "numpy" or fullname.startswith("numpy."): |
| 99 | + raise ImportError(fullname) |
| 100 | + if fullname == "soundfile" or fullname.startswith("soundfile."): |
| 101 | + raise ImportError(fullname) |
| 102 | + return None |
| 103 | +
|
| 104 | + sys.meta_path.insert(0, BlockAudioLibs()) |
| 105 | + logging.basicConfig(level=logging.WARNING, stream=sys.stdout) |
| 106 | + import opentelemetry.util.genai._multimodal_upload.pre_uploader # noqa: F401 |
| 107 | + """ |
| 108 | + ) |
| 109 | + |
| 110 | + completed = subprocess.run( |
| 111 | + [sys.executable, "-c", script], |
| 112 | + env=env, |
| 113 | + check=True, |
| 114 | + capture_output=True, |
| 115 | + text=True, |
| 116 | + ) |
| 117 | + |
| 118 | + assert completed.stdout == "" |
| 119 | + assert completed.stderr == "" |
| 120 | + |
68 | 121 | # ========== Edge Case Tests ========== |
69 | 122 |
|
70 | 123 | @staticmethod |
@@ -174,6 +227,53 @@ def test_pcm16_to_wav_conversion(pre_uploader, pcm_mime_type): |
174 | 227 | # If library unavailable, should keep original format |
175 | 228 | assert uploads[0].content_type == pcm_mime_type |
176 | 229 |
|
| 230 | + @staticmethod |
| 231 | + def test_pcm16_conversion_missing_audio_libs_logs_single_warning( |
| 232 | + caplog, |
| 233 | + ): |
| 234 | + """Missing optional audio libs should only log the actual conversion skip.""" |
| 235 | + with ( |
| 236 | + patch( |
| 237 | + "opentelemetry.util.genai._multimodal_upload.pre_uploader._audio_libs_available", |
| 238 | + False, |
| 239 | + ), |
| 240 | + patch( |
| 241 | + "opentelemetry.util.genai._multimodal_upload.pre_uploader.np", |
| 242 | + None, |
| 243 | + ), |
| 244 | + patch( |
| 245 | + "opentelemetry.util.genai._multimodal_upload.pre_uploader.sf", |
| 246 | + None, |
| 247 | + ), |
| 248 | + ): |
| 249 | + pre_uploader = MultimodalPreUploader(base_path="/tmp/test_upload") |
| 250 | + part = Blob( |
| 251 | + content=b"\x00\x01" * 1000, |
| 252 | + mime_type="audio/pcm16", |
| 253 | + modality="audio", |
| 254 | + ) |
| 255 | + input_messages = [InputMessage(role="user", parts=[part])] |
| 256 | + |
| 257 | + with caplog.at_level( |
| 258 | + "WARNING", |
| 259 | + logger=( |
| 260 | + "opentelemetry.util.genai._multimodal_upload.pre_uploader" |
| 261 | + ), |
| 262 | + ): |
| 263 | + uploads = pre_uploader.pre_upload( |
| 264 | + span_context=None, |
| 265 | + start_time_utc_nano=1000000000000000000, |
| 266 | + input_messages=input_messages, |
| 267 | + output_messages=None, |
| 268 | + ) |
| 269 | + |
| 270 | + assert len(uploads) == 1 |
| 271 | + assert uploads[0].content_type == "audio/pcm16" |
| 272 | + warning_messages = [record.getMessage() for record in caplog.records] |
| 273 | + assert warning_messages == [ |
| 274 | + "Failed to convert PCM16 to WAV, using original format" |
| 275 | + ] |
| 276 | + |
177 | 277 | @staticmethod |
178 | 278 | def test_pcm16_conversion_disabled_by_default(): |
179 | 279 | """Test PCM16 conversion stays disabled when env var is unset""" |
|
0 commit comments