60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
138 lines
5.5 KiB
Python
138 lines
5.5 KiB
Python
"""Performance regression tests for OpenAPI parsing.
|
|
|
|
These tests ensure that large OpenAPI schemas (like GitHub's API) parse quickly
|
|
and don't regress to the slow performance we had before optimization.
|
|
"""
|
|
|
|
import time
|
|
from typing import Any
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from fastmcp import FastMCP
|
|
|
|
|
|
class TestOpenAPIPerformance:
|
|
"""Performance tests for OpenAPI parsing with real-world large schemas."""
|
|
|
|
# 10 second maximum timeout for this test no matter what
|
|
@pytest.mark.integration
|
|
@pytest.mark.timeout(10)
|
|
async def test_github_api_schema_performance(self):
|
|
"""
|
|
Test that GitHub's full API schema parses quickly.
|
|
|
|
This is a regression test to ensure our performance optimizations
|
|
(eliminating deepcopy, single-pass optimization, smart union adjustment)
|
|
continue to work. Without these optimizations, this test would take
|
|
multiple minutes to parse.
|
|
|
|
On a local machine, this tests passes in ~2 seconds, but in GHA CI we see
|
|
times as high as 6-7 seconds, so the test is asserted to pass in under
|
|
10. Given that, this isn't intended to be a strict performance test, but
|
|
rather a canary to ensure we don't regress significantly.
|
|
"""
|
|
|
|
# Download the full GitHub API schema (typically ~10MB)
|
|
response = httpx.get(
|
|
"https://raw.githubusercontent.com/github/rest-api-description/refs/heads/main/descriptions-next/ghes-3.17/ghes-3.17.json",
|
|
timeout=30.0, # Allow time for download
|
|
)
|
|
response.raise_for_status()
|
|
schema = response.json()
|
|
|
|
# Time the parsing operation
|
|
start_time = time.time()
|
|
|
|
# This should complete quickly with our optimizations
|
|
mcp_server = FastMCP.from_openapi(schema, httpx.AsyncClient())
|
|
|
|
elapsed_time = time.time() - start_time
|
|
|
|
print(f"OpenAPI parsing took {elapsed_time:.2f}s")
|
|
|
|
# Verify the server was created successfully
|
|
assert mcp_server is not None
|
|
|
|
# Performance regression test: should complete in under 10 seconds
|
|
assert elapsed_time < 10.0, (
|
|
f"OpenAPI parsing took {elapsed_time:.2f}s, exceeding 10s limit. "
|
|
f"This suggests a performance regression."
|
|
)
|
|
|
|
# Verify server and tools were created successfully
|
|
tools = await mcp_server.list_tools()
|
|
assert len(tools) > 500
|
|
|
|
def test_medium_schema_performance(self):
|
|
"""
|
|
Test parsing performance with a smaller synthetic schema.
|
|
|
|
This test doesn't require network access and provides a baseline
|
|
for performance testing in CI environments.
|
|
"""
|
|
# Create a medium-sized synthetic schema
|
|
schema: dict[str, Any] = {
|
|
"openapi": "3.0.0",
|
|
"info": {"title": "Test API", "version": "1.0.0"},
|
|
"paths": {},
|
|
}
|
|
|
|
# Generate multiple paths to create a reasonably sized schema
|
|
for i in range(100):
|
|
path = f"/test/{i}"
|
|
schema["paths"][path] = {
|
|
"get": {
|
|
"operationId": f"test_{i}",
|
|
"parameters": [
|
|
{"name": "param1", "in": "query", "schema": {"type": "string"}}
|
|
],
|
|
"responses": {
|
|
"200": {
|
|
"description": "Success",
|
|
"content": {
|
|
"application/json": {
|
|
"schema": {
|
|
"type": "object",
|
|
"properties": {
|
|
"id": {"type": "integer"},
|
|
"name": {"type": "string"},
|
|
"data": {
|
|
"type": "object",
|
|
"additionalProperties": False,
|
|
"properties": {
|
|
"value": {"type": "string"},
|
|
"metadata": {
|
|
"type": "object",
|
|
"properties": {
|
|
"created": {
|
|
"type": "string"
|
|
},
|
|
"updated": {
|
|
"type": "string"
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
},
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
# Time the parsing
|
|
start_time = time.time()
|
|
mcp_server = FastMCP.from_openapi(schema, httpx.AsyncClient())
|
|
elapsed_time = time.time() - start_time
|
|
|
|
# Should be very fast for medium schemas (well under 1 second)
|
|
assert elapsed_time < 1.0, (
|
|
f"Medium schema parsing took {elapsed_time:.3f}s, expected <1s"
|
|
)
|
|
assert mcp_server is not None
|