Skip to content

Commit b59051b

Browse files
committed
Optimise tests to prefer non-Docker and be cross-platform
1 parent 27ea6a4 commit b59051b

4 files changed

Lines changed: 174 additions & 65 deletions

File tree

‎scripts/run_petstore_test.py‎

Lines changed: 166 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
import shutil
44
import subprocess
55
import time
6+
import socket
7+
8+
9+
def find_free_port():
10+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
11+
s.bind(("", 0))
12+
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
13+
return s.getsockname()[1]
614

715

816
def main():
@@ -16,6 +24,7 @@ def main():
1624

1725
json_file = sys.argv[1]
1826
tmp_dir = sys.argv[2]
27+
tmp_server_dir = tmp_dir + "_server"
1928

2029
input_path = os.path.join(test_harness_dir, json_file)
2130
if not os.path.exists(input_path):
@@ -38,40 +47,102 @@ def main():
3847
print(f"Running petstore SDK generation against {input_path} -> {tmp_dir}")
3948
subprocess.run(cmd, check=True)
4049

41-
# Test against Docker JVM Petstore server
42-
container_name = f"petstore_server_{os.getpid()}"
43-
image_name = (
44-
"swaggerapi/petstore-v3"
45-
if "oas3" in json_file.lower()
46-
else "swaggerapi/petstore"
47-
)
48-
49-
fallback_image_name = (
50-
"openapitools/openapi-petstore"
51-
if "oas3" in json_file.lower()
52-
else "swaggerapi/swagger-petstore" # Fallback guess
53-
)
50+
# Try to run Python-based generated server first
51+
host_port = None
52+
server_process = None
53+
is_oas3 = "oas3" in json_file.lower()
5454

5555
try:
56-
print(f"Starting docker container {image_name}...")
56+
print("Attempting to run Python-based mock server...")
5757
subprocess.run(
5858
[
59-
"docker",
59+
"uv",
6060
"run",
61-
"-d",
62-
"-P",
63-
"--name",
64-
container_name,
65-
image_name,
61+
"cdd-python",
62+
"from_openapi",
63+
"to_server",
64+
"-i",
65+
input_path,
66+
"-o",
67+
tmp_server_dir,
6668
],
6769
check=True,
68-
capture_output=True,
6970
)
71+
host_port = str(find_free_port())
72+
73+
env = os.environ.copy()
74+
env["PORT"] = host_port
75+
env.pop("VIRTUAL_ENV", None)
76+
77+
subprocess.run(["uv", "venv"], cwd=tmp_server_dir, env=env, check=True)
78+
subprocess.run(
79+
["uv", "pip", "install", "-e", "."],
80+
cwd=tmp_server_dir,
81+
env=env,
82+
check=True,
83+
)
84+
85+
server_process = subprocess.Popen(
86+
[
87+
os.path.join(".venv", "bin", "python"),
88+
"main.py",
89+
"--ephemeral",
90+
"--seed",
91+
],
92+
cwd=tmp_server_dir,
93+
env=env,
94+
stdout=subprocess.PIPE,
95+
stderr=subprocess.STDOUT,
96+
)
97+
98+
import urllib.request
99+
100+
server_ready = False
101+
for _ in range(30):
102+
time.sleep(1)
103+
try:
104+
urllib.request.urlopen(
105+
f"http://localhost:{host_port}/openapi.json", timeout=1
106+
)
107+
server_ready = True
108+
break
109+
except Exception:
110+
pass
111+
112+
if not server_ready:
113+
print("Python mock server failed to start. Output:")
114+
server_process.terminate()
115+
try:
116+
stdout, _ = server_process.communicate(timeout=5)
117+
print(
118+
f"--- SERVER OUTPUT ---\n{stdout.decode('utf-8', errors='ignore')}\n-------------------"
119+
)
120+
except Exception as e:
121+
print(f"Failed to get server output: {e}")
122+
server_process = None
123+
host_port = None
124+
70125
except Exception as e:
71-
print(
72-
f"Failed to start JVM image {image_name}, falling back to {fallback_image_name}: {e}"
126+
print(f"Failed to start Python mock server: {e}")
127+
if server_process:
128+
server_process.terminate()
129+
server_process = None
130+
host_port = None
131+
132+
container_name = None
133+
if not host_port:
134+
print("Falling back to Docker JVM Petstore server...")
135+
container_name = f"petstore_server_{os.getpid()}"
136+
image_name = "swaggerapi/petstore-v3" if is_oas3 else "swaggerapi/petstore"
137+
138+
fallback_image_name = (
139+
"openapitools/openapi-petstore"
140+
if is_oas3
141+
else "swaggerapi/swagger-petstore" # Fallback guess
73142
)
143+
74144
try:
145+
print(f"Starting docker container {image_name}...")
75146
subprocess.run(
76147
[
77148
"docker",
@@ -80,20 +151,55 @@ def main():
80151
"-P",
81152
"--name",
82153
container_name,
83-
fallback_image_name,
154+
image_name,
84155
],
85156
check=True,
86157
capture_output=True,
87158
)
88-
except Exception as e2:
159+
except Exception as e:
89160
print(
90-
f"Fallback docker test failed (maybe docker not available?): {e2}"
161+
f"Failed to start JVM image {image_name}, falling back to {fallback_image_name}: {e}"
91162
)
92-
return
163+
try:
164+
subprocess.run(
165+
[
166+
"docker",
167+
"run",
168+
"-d",
169+
"-P",
170+
"--name",
171+
container_name,
172+
fallback_image_name,
173+
],
174+
check=True,
175+
capture_output=True,
176+
)
177+
except Exception as e2:
178+
print(
179+
f"Fallback docker test failed (maybe docker not available?): {e2}"
180+
)
181+
return
182+
183+
try:
184+
import urllib.request
185+
186+
for _ in range(10):
187+
time.sleep(3)
188+
try:
189+
port_res = subprocess.run(
190+
["docker", "port", container_name, "8080"],
191+
check=True,
192+
capture_output=True,
193+
text=True,
194+
)
195+
host_port = port_res.stdout.strip().split(":")[-1]
196+
urllib.request.urlopen(
197+
f"http://localhost:{host_port}/api/swagger.json"
198+
)
199+
break
200+
except Exception:
201+
pass
93202

94-
try:
95-
for _ in range(10):
96-
time.sleep(3)
97203
try:
98204
port_res = subprocess.run(
99205
["docker", "port", container_name, "8080"],
@@ -102,35 +208,27 @@ def main():
102208
text=True,
103209
)
104210
host_port = port_res.stdout.strip().split(":")[-1]
105-
import urllib.request
106-
107-
urllib.request.urlopen(
108-
f"http://localhost:{host_port}/api/swagger.json"
109-
)
110-
break
111-
except Exception:
112-
pass
113-
114-
try:
115-
port_res = subprocess.run(
116-
["docker", "port", container_name, "8080"],
117-
check=True,
118-
capture_output=True,
119-
text=True,
120-
)
121-
host_port = port_res.stdout.strip().split(":")[-1]
122-
except subprocess.CalledProcessError as e:
123-
print("Failed to get docker port:", e.stderr)
124-
print("Failing gracefully.")
211+
except subprocess.CalledProcessError as e:
212+
print("Failed to get docker port:", e.stderr)
213+
print("Failing gracefully.")
214+
return
215+
except Exception as e:
216+
print(f"Docker fallback setup failed: {e}")
125217
return
126218

219+
try:
127220
# Use SDK to get inventory
128221
env = os.environ.copy()
129-
env["API_BASE_URL"] = (
130-
f"http://localhost:{host_port}/api/v3"
131-
if "oas3" in json_file.lower()
132-
else f"http://localhost:{host_port}/api"
133-
)
222+
if server_process:
223+
# Python mock server has endpoints at root
224+
env["API_BASE_URL"] = f"http://localhost:{host_port}"
225+
else:
226+
env["API_BASE_URL"] = (
227+
f"http://localhost:{host_port}/api/v3"
228+
if is_oas3
229+
else f"http://localhost:{host_port}/api"
230+
)
231+
134232
test_cmd = [
135233
"uv",
136234
"run",
@@ -143,16 +241,27 @@ def main():
143241
_ = subprocess.run(
144242
test_cmd, env=env, check=True, capture_output=True, text=True
145243
)
244+
print("SDK test passed.")
146245
except subprocess.CalledProcessError as e:
147246
print("SDK test failed with error:", e.stderr)
148247
print("Failing gracefully.")
149-
pass
150248
finally:
151-
subprocess.run(["docker", "rm", "-f", container_name], capture_output=True)
249+
if server_process:
250+
server_process.terminate()
251+
try:
252+
server_process.wait(timeout=5)
253+
except subprocess.TimeoutExpired:
254+
server_process.kill()
255+
if container_name:
256+
subprocess.run(
257+
["docker", "rm", "-f", container_name], capture_output=True
258+
)
152259

153260
finally:
154261
if os.path.exists(tmp_dir):
155262
shutil.rmtree(tmp_dir)
263+
if os.path.exists(tmp_server_dir):
264+
shutil.rmtree(tmp_server_dir)
156265

157266

158267
if __name__ == "__main__": # pragma: no cover

‎tests/test_coverage_cli_branches.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ def test_serve_json_rpc_break(monkeypatch, capsys):
1515

1616

1717
def test_apply_env_vars_store_true():
18+
"""Test test_apply_env_vars_store_true."""
1819
from openapi_client.cli import apply_env_vars_to_parser
1920
import argparse
2021
import os
2122

22-
"""Test test_apply_env_vars_store_true."""
2323
parser = argparse.ArgumentParser()
2424
parser.add_argument("--test-true", action="store_true")
2525
parser.add_argument("--test-false", action="store_false")

‎tests/test_coverage_gap.py‎

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,9 @@ def test_mcp_read_resource_error():
168168

169169

170170
def test_mcp_client_notifications():
171+
"""Test building various notifications."""
171172
from openapi_client.mcp_client.client import MCPClient
172173

173-
"""Test building various notifications."""
174174
client = MCPClient()
175175
assert client.build_cancelled_notification(1)["method"] == "notifications/cancelled"
176176
assert (
@@ -204,9 +204,9 @@ def test_mcp_client_notifications():
204204

205205

206206
def test_mcp_client_cursor():
207+
"""Test building various requests with cursor."""
207208
from openapi_client.mcp_client.client import MCPClient
208209

209-
"""Test building various requests with cursor."""
210210
client = MCPClient()
211211
assert (
212212
client.build_list_resource_templates_request("cursor")["params"]["cursor"]
@@ -215,9 +215,9 @@ def test_mcp_client_cursor():
215215

216216

217217
def test_mcp_client_more_methods():
218+
"""Test test_mcp_client_more_methods."""
218219
from openapi_client.mcp_client.client import MCPClient
219220

220-
"""Test test_mcp_client_more_methods."""
221221
client = MCPClient()
222222
assert client.build_ping_request()["method"] == "ping"
223223
assert client.build_initialize_request()["method"] == "initialize"
@@ -270,9 +270,8 @@ def test_mcp_all_methods():
270270

271271

272272
def test_mcp_client_more_missing():
273-
from openapi_client.mcp_client.client import MCPClient
274-
275273
"""Test test_mcp_client_more_missing."""
274+
from openapi_client.mcp_client.client import MCPClient
276275

277276
client = MCPClient()
278277
assert (
@@ -281,9 +280,8 @@ def test_mcp_client_more_missing():
281280

282281

283282
def test_mcp_client_cursor_none_no_params():
284-
from openapi_client.mcp_client.client import MCPClient
285-
286283
"""Test test_mcp_client_cursor_none_no_params."""
284+
from openapi_client.mcp_client.client import MCPClient
287285

288286
client = MCPClient()
289287

‎tests/test_coverage_write_generated_local.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""Tests for test_coverage_write_generated_local.py."""
2+
13
import unittest.mock
24
from openapi_client.cli import sync_dir
35

0 commit comments

Comments
 (0)