Merge pull request #42115 from BerriAI/litellm_migrate_tests_p10

test: migrate nvidia, oci, ocr, oobabooga and openai legacy tests to tests/unit
This commit is contained in:
yuneng-jiang 2026-09-20 03:13:07 -07:00 • committed by GitHub
commit e08df02ebe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 0 additions and 236 deletions

View file

@ -62,19 +62,6 @@ def test_resample_16khz_mono_passes_through_int16_bytes_match_length():
assert resampled.duration_seconds == pytest.approx(1.0, abs=0.001)
def test_resample_preserves_int16_clip_range():
sample_rate = 16000
samples = np.array([2.0, -2.0, 0.0, 1.0], dtype=np.float32)
wav_in = _wav_bytes(samples, sample_rate)
resampled = resample_to_riva_pcm(wav_in)
decoded = np.frombuffer(resampled.pcm_bytes, dtype="<i2")
# Anything outside [-1, 1] should clip to int16 boundary.
assert decoded.max() <= 32767
assert decoded.min() >= -32767
def test_unknown_format_raises_clear_error():
# 4 random bytes are not valid audio in any container we can decode.
with pytest.raises(NvidiaRivaException) as excinfo:

View file

@ -922,91 +922,6 @@ class TestOCISignerSupport:
assert wrapper.path_url == "/api/v1/chat"
class TestOCISplitChunks:
"""
Unit tests for the SSE split_chunks helpers used in sync and async streaming.
These validate the fix for:
- Sync: JSONDecodeError when iter_text() returns chunks spanning multiple events
- Async: whitespace-only chunks being yielded before stripping (Greptile P2)
"""
def _run_sync_split(self, raw_chunks):
"""Invoke the sync split_chunks logic directly (extracted for testability)."""
results = []
for item in raw_chunks:
for chunk in item.split("\n\n"):
stripped = chunk.strip()
if stripped:
results.append(stripped)
return results
async def _run_async_split(self, raw_chunks):
"""Invoke the async split_chunks logic directly."""
results = []
async def _gen():
for c in raw_chunks:
yield c
async for item in _gen():
for chunk in item.split("\n\n"):
stripped = chunk.strip()
if stripped:
results.append(stripped)
return results
def test_sync_single_event_per_chunk(self):
"""Normal case: one SSE event per iter_text() chunk."""
chunks = ['data: {"text":"hello"}', 'data: {"text":"world"}']
assert self._run_sync_split(chunks) == [
'data: {"text":"hello"}',
'data: {"text":"world"}',
]
def test_sync_multiple_events_in_one_chunk(self):
"""iter_text() returns two SSE events concatenated — must be split."""
chunks = ['data: {"text":"a"}\n\ndata: {"text":"b"}']
assert self._run_sync_split(chunks) == [
'data: {"text":"a"}',
'data: {"text":"b"}',
]
def test_sync_whitespace_only_chunks_discarded(self):
"""Whitespace between events must not be yielded."""
chunks = ["data: {}\n\n \n\ndata: {}"]
result = self._run_sync_split(chunks)
assert result == ["data: {}", "data: {}"]
def test_sync_empty_string_discarded(self):
"""Empty string produced by splitting trailing \\n\\n must be discarded."""
chunks = ["data: {}\n\n"]
assert self._run_sync_split(chunks) == ["data: {}"]
@pytest.mark.asyncio
async def test_async_whitespace_only_chunks_discarded(self):
"""
Regression test for Greptile P2: async version was checking `if not chunk`
BEFORE stripping, so '\\n ' would pass the guard and yield '' downstream,
causing ValueError in chunk_creator ('Chunk does not start with data:').
"""
chunks = ["data: {}\n\n \n\ndata: {}"]
result = await self._run_async_split(chunks)
assert result == ["data: {}", "data: {}"]
@pytest.mark.asyncio
async def test_async_empty_string_discarded(self):
"""Trailing \\n\\n must not produce an empty yielded chunk in async path."""
chunks = ["data: {}\n\n"]
result = await self._run_async_split(chunks)
assert result == ["data: {}"]
@pytest.mark.asyncio
async def test_async_multiple_events_in_one_chunk(self):
"""Async path must split concatenated SSE events just like sync."""
chunks = ['data: {"text":"x"}\n\ndata: {"text":"y"}']
result = await self._run_async_split(chunks)
assert result == ['data: {"text":"x"}', 'data: {"text":"y"}']
class TestOCIProviderEmbeddingConfig:
@ -1026,21 +941,6 @@ class TestOCIProviderEmbeddingConfig:
)
assert isinstance(config, OCIEmbedConfig)
def test_no_duplicate_oci_branch(self):
"""
Ensure utils.py does not contain two separate OCI embedding branches.
The dead code was removed in commit 64dfbe2b; this test guards against
regression (e.g. a future merge re-introducing it).
"""
import inspect
from litellm.utils import ProviderConfigManager
source = inspect.getsource(ProviderConfigManager.get_provider_embedding_config)
oci_count = source.count("LlmProviders.OCI")
assert oci_count == 1, (
f"Expected exactly 1 OCI branch in get_provider_embedding_config, found {oci_count}. "
"A duplicate dead-code branch may have been reintroduced."
)
class TestOCICohereParamMapping:
@ -1586,57 +1486,7 @@ def config():
class TestOCIKeyNormalization:
"""Tests for OCI private key content normalization."""
def test_oci_key_with_escaped_newlines(self, config):
"""Test that escaped newlines (\\n) are converted to actual newlines."""
# Simulate PEM content with escaped newlines (as would come from JSON/UI input)
escaped_pem = "-----BEGIN RSA PRIVATE KEY-----\\nMIIEowIBAAKCAQEA...\\n-----END RSA PRIVATE KEY-----"
optional_params = {
"oci_user": "ocid1.user.oc1..test",
"oci_fingerprint": "aa:bb:cc:dd",
"oci_tenancy": "ocid1.tenancy.oc1..test",
"oci_region": "us-ashburn-1",
"oci_key": escaped_pem,
}
# We can't fully test signing without a real key, but we can verify
# the error message indicates the key was processed (not a type error)
with pytest.raises(Exception, match='why-can-t-i-import-my-pem-file for more details\\.') as exc_info:
sign_with_manual_credentials(
headers={},
optional_params=optional_params,
request_data={"test": "data"},
api_base="https://test.oci.oraclecloud.com/api",
)
# The error should be about key format/loading, not about type
# This confirms the string was processed and newlines were normalized
error_message = str(exc_info.value)
assert "must be a string" not in error_message.lower()
def test_oci_key_with_crlf_newlines(self, config):
"""Test that Windows-style CRLF newlines are normalized to LF."""
# Simulate PEM content with CRLF newlines
crlf_pem = "-----BEGIN RSA PRIVATE KEY-----\r\nMIIEowIBAAKCAQEA...\r\n-----END RSA PRIVATE KEY-----"
optional_params = {
"oci_user": "ocid1.user.oc1..test",
"oci_fingerprint": "aa:bb:cc:dd",
"oci_tenancy": "ocid1.tenancy.oc1..test",
"oci_region": "us-ashburn-1",
"oci_key": crlf_pem,
}
with pytest.raises(Exception, match='why-can-t-i-import-my-pem-file for more details\\.') as exc_info:
sign_with_manual_credentials(
headers={},
optional_params=optional_params,
request_data={"test": "data"},
api_base="https://test.oci.oraclecloud.com/api",
)
error_message = str(exc_info.value)
assert "must be a string" not in error_message.lower()
def test_oci_key_rejects_non_string_type(self, config):
"""Test that non-string oci_key values raise OCIError."""

View file

@ -966,13 +966,6 @@ class TestOCICohereStreaming:
completion_stream=mock_stream, model=mock_model, logging_obj=mock_logging
)
def test_cohere_streaming_wrapper_initialization(self):
"""Test OCIStreamWrapper initialization"""
stream_wrapper = self._create_stream_wrapper()
# chunk_creator is the public dispatch entry point
assert hasattr(stream_wrapper, "chunk_creator")
assert callable(stream_wrapper.chunk_creator)
def test_cohere_streaming_chunk_parsing(self):
"""Test parsing of Cohere streaming chunks"""
@ -1003,16 +996,3 @@ class TestOCICohereStreaming:
# Test non-JSON chunk
with pytest.raises(OCIError, match="Chunk cannot be parsed as JSON"):
stream_wrapper.chunk_creator("data: invalid json")
def test_cohere_streaming_generic_chunk_fallback(self):
"""Test fallback to generic chunk handling for non-Cohere chunks"""
stream_wrapper = self._create_stream_wrapper()
# Test generic chunk (no apiFormat or different apiFormat)
generic_chunk = {"apiFormat": "GEMINI", "text": "Hello from Gemini"}
chunk_data = f"data: {json.dumps(generic_chunk)}"
# This should fall back to generic handling
result = stream_wrapper.chunk_creator(chunk_data)
# The exact structure depends on the generic handler implementation
assert hasattr(result, "choices")

View file

@ -450,15 +450,3 @@ class TestGpt5MaxCompletionTokens:
)
assert out.get("maxTokens") == 64
assert "maxCompletionTokens" not in out
def test_payload_serializes_max_completion_tokens(self):
from litellm.types.llms.oci import OCIChatRequestPayload
payload = OCIChatRequestPayload(
apiFormat="GENERIC",
messages=[],
maxCompletionTokens=64,
)
dumped = payload.model_dump(exclude_none=True)
assert dumped["maxCompletionTokens"] == 64
assert "maxTokens" not in dumped

View file

@ -269,28 +269,6 @@ class TestOCIEmbedConfig:
assert result.model == "cohere.embed-v3.0"
assert result.usage.prompt_tokens == 10
def test_transform_response_no_usage(self):
cfg = self._config()
model_response = EmbeddingResponse()
raw = self._mock_response(
200,
{
"embeddings": [[0.1]],
"modelId": "cohere.embed-v3.0",
"modelVersion": "3.0.0",
},
)
result = cfg.transform_embedding_response(
model="cohere.embed-v3.0",
raw_response=raw,
model_response=model_response,
logging_obj=MagicMock(),
api_key=None,
request_data={},
optional_params={},
litellm_params={},
)
assert len(result.data) == 1
def test_transform_response_http_error_raises(self):
cfg = self._config()

View file

@ -545,25 +545,6 @@ class TestOpenAIChatCompletionsHandlerToolCallsInput:
assert data["messages"][0]["content"] == "HELLO"
assert data["messages"][1]["content"] == "HI THERE!"
@pytest.mark.asyncio
async def test_empty_tool_calls_list(self):
"""Test that empty tool_calls list is handled correctly"""
handler = OpenAIChatCompletionsHandler()
guardrail = MockGuardrail()
data = {
"messages": [
{"role": "assistant", "content": "Hello", "tool_calls": []},
]
}
# Process the input
await handler.process_input_messages(data, guardrail)
# Verify empty tool_calls doesn't cause issues
assert guardrail.last_inputs is not None
tool_calls = guardrail.last_inputs.get("tool_calls", [])
assert len(tool_calls) == 0
class TestOpenAIChatCompletionsHandlerToolCallsOutput: