Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -799,12 +799,15 @@ if (NOT SKIP_GRPC_BUILD)
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
CXX_SCAN_FOR_MODULES OFF
POSITION_INDEPENDENT_CODE ON
)

target_compile_options(cuopt_grpc_server
PRIVATE "$<$<COMPILE_LANGUAGE:CXX>:${CUOPT_CXX_FLAGS}>"
)

target_link_options(cuopt_grpc_server PRIVATE -pie)

target_include_directories(cuopt_grpc_server
PRIVATE
"${CMAKE_CURRENT_SOURCE_DIR}/src"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,12 @@ def _start_grpc_server_fixture(port_offset):
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
time.sleep(0.5)
if proc.poll() is not None:
pytest.skip(
f"cuopt_grpc_server exited immediately (rc={proc.returncode}), "
"binary may be unable to load shared libraries in this environment"
)
if not _wait_for_port(port, timeout=15):
proc.kill()
proc.wait()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import ctypes
import ctypes.util
import platform
import shutil
import subprocess

import pytest


def test_cuopt_grpc_server_on_path():
assert shutil.which("cuopt_grpc_server") is not None, (
"cuopt_grpc_server should be on PATH after installing cuopt-server"
)


def _check_libuuid():
"""Return (found: bool, detail: str) for libuuid availability."""
name = ctypes.util.find_library("uuid")
if name is None:
return False, "ctypes.util.find_library('uuid') returned None"
try:
ctypes.CDLL(name)
return True, f"loaded {name}"
except OSError as exc:
return False, f"find_library returned '{name}' but load failed: {exc}"


def test_cuopt_grpc_server_help():
result = subprocess.run(
["cuopt_grpc_server", "--help"],
capture_output=True,
text=True,
timeout=10,
)
if result.returncode != 0 and not result.stdout and not result.stderr:
uuid_ok, uuid_detail = _check_libuuid()
pytest.skip(
f"cuopt_grpc_server binary failed to load "
f"(rc={result.returncode}, arch={platform.machine()}). "
f"libuuid: {uuid_detail}"
)
assert result.returncode == 0, (
f"cuopt_grpc_server --help failed (rc={result.returncode}): "
f"{result.stdout}\n{result.stderr}"
)
output = f"{result.stdout}\n{result.stderr}"
assert "cuopt_grpc_server" in output, (
f"Expected 'cuopt_grpc_server' in --help output, got: {output}"
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
16 changes: 16 additions & 0 deletions python/libcuopt/libcuopt/_grpc_server_wrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

import os
import subprocess
import sys


def main():
"""
This connects to the gRPC server binary situated under libcuopt/bin folder.
"""
server_path = os.path.join(
os.path.dirname(__file__), "bin", "cuopt_grpc_server"
)
sys.exit(subprocess.call([server_path] + sys.argv[1:]))
Comment on lines +13 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Handle missing/non-executable binary gracefully.

On Line 16, a missing or non-executable binary will surface as a Python traceback. Add an explicit guard and exit with a clear error message.

Suggested hardening patch
 def main():
@@
     server_path = os.path.join(
         os.path.dirname(__file__), "bin", "cuopt_grpc_server"
     )
-    sys.exit(subprocess.call([server_path] + sys.argv[1:]))
+    if not os.path.isfile(server_path) or not os.access(server_path, os.X_OK):
+        print(
+            f"cuopt_grpc_server binary not found or not executable: {server_path}",
+            file=sys.stderr,
+        )
+        sys.exit(127)
+    sys.exit(subprocess.call([server_path] + sys.argv[1:]))
🧰 Tools
🪛 Ruff (0.15.10)

[error] 16-16: subprocess call: check for execution of untrusted input

(S603)


[warning] 16-16: Consider [server_path, *sys.argv[1:]] instead of concatenation

Replace with [server_path, *sys.argv[1:]]

(RUF005)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python/libcuopt/libcuopt/_grpc_server_wrapper.py` around lines 13 - 16, The
code currently constructs server_path and calls subprocess.call([server_path] +
sys.argv[1:]) which will raise a traceback if the binary is missing or not
executable; update the wrapper to first check server_path exists and is
executable (use os.path.exists/server_path and os.access(server_path, os.X_OK)),
and if not, write a clear error message to stderr and exit with a non‑zero code;
additionally wrap the subprocess.call invocation in a try/except to catch
OSError/CalledProcessError and print the captured error details before calling
sys.exit(1) so failures are reported cleanly.

1 change: 1 addition & 0 deletions python/libcuopt/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ libcuopt = "libcuopt"

[project.scripts]
cuopt_cli = "libcuopt._cli_wrapper:main"
cuopt_grpc_server = "libcuopt._grpc_server_wrapper:main"

[tool.pydistcheck]
select = [
Expand Down
Loading