Skip to content

Commit

Permalink
fix: support path with special characters on windows (#1730)
Browse files Browse the repository at this point in the history
* fix: utf8

* fix: uft8 for cli

* fix: codecvt_utf8_utf16 is deprecated

* fix: more

* feat: support wstring string conversion

* fix: build

* fix: engine path env

* fix: wstring

* fix: cli start server

* fix: utf8 file

* fix: get env

* fix: db

* fix: e2e

* fix: e2e

* fix: cli delete

* fix: comment

* fix: e2e windows

* fix: e2e windows continue

* fix: e2e windows skip because of progress bar log issue

* fix: add sleep in case of cuda for e2e

* fix: import

---------

Co-authored-by: vansangpfiev <[email protected]>
  • Loading branch information
vansangpfiev and sangjanai authored Nov 28, 2024
1 parent 43dab3b commit 719de33
Show file tree
Hide file tree
Showing 24 changed files with 340 additions and 140 deletions.
4 changes: 4 additions & 0 deletions engine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ if(MSVC)
$<$<CONFIG:Debug>:/MTd> #---|-- Statically link the runtime libraries
$<$<CONFIG:Release>:/MT> #--|
)

add_compile_options(/utf-8)
add_definitions(-DUNICODE -D_UNICODE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DUNICODE /D_UNICODE")
endif()

if(NOT DEFINED CORTEX_VARIANT)
Expand Down
4 changes: 4 additions & 0 deletions engine/cli/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ if(MSVC)
$<$<CONFIG:Debug>:/MTd> #---|-- Statically link the runtime libraries
$<$<CONFIG:Release>:/MT> #--|
)

add_compile_options(/utf-8)
add_definitions(-DUNICODE -D_UNICODE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DUNICODE /D_UNICODE")
endif()

if(NOT DEFINED CORTEX_VARIANT)
Expand Down
42 changes: 26 additions & 16 deletions engine/cli/commands/server_start_cmd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "commands/cortex_upd_cmd.h"
#include "utils/cortex_utils.h"
#include "utils/file_manager_utils.h"
#include "utils/widechar_conv.h"

namespace commands {

Expand Down Expand Up @@ -57,24 +58,32 @@ bool ServerStartCmd::Exec(const std::string& host, int port,
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
std::string params = "--start-server";
params += " --config_file_path " + get_config_file_path();
params += " --data_folder_path " + get_data_folder_path();
params += " --loglevel " + log_level_;
std::string cmds = cortex_utils::GetCurrentPath() + "/" + exe + " " + params;
std::wstring params = L"--start-server";
params += L" --config_file_path " +
file_manager_utils::GetConfigurationPath().wstring();
params += L" --data_folder_path " +
file_manager_utils::GetCortexDataPath().wstring();
params += L" --loglevel " + cortex::wc::Utf8ToWstring(log_level_);
std::wstring exe_w = cortex::wc::Utf8ToWstring(exe);
std::wstring current_path_w =
file_manager_utils::GetExecutableFolderContainerPath().wstring();
std::wstring wcmds = current_path_w + L"/" + exe_w + L" " + params;
CTL_DBG("wcmds: " << wcmds);
std::vector<wchar_t> mutable_cmds(wcmds.begin(), wcmds.end());
mutable_cmds.push_back(L'\0');
// Create child process
if (!CreateProcess(
NULL, // No module name (use command line)
const_cast<char*>(
cmds.c_str()), // Command line (replace with your actual executable)
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi)) // Pointer to PROCESS_INFORMATION structure
mutable_cmds
.data(), // Command line (replace with your actual executable)
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi)) // Pointer to PROCESS_INFORMATION structure
{
std::cout << "Could not start server: " << GetLastError() << std::endl;
return false;
Expand Down Expand Up @@ -115,7 +124,8 @@ bool ServerStartCmd::Exec(const std::string& host, int port,
std::string p = cortex_utils::GetCurrentPath() + "/" + exe;
execl(p.c_str(), exe.c_str(), "--start-server", "--config_file_path",
get_config_file_path().c_str(), "--data_folder_path",
get_data_folder_path().c_str(), "--loglevel", log_level_.c_str(), (char*)0);
get_data_folder_path().c_str(), "--loglevel", log_level_.c_str(),
(char*)0);
} else {
// Parent process
if (!TryConnectToServer(host, port)) {
Expand Down
19 changes: 15 additions & 4 deletions engine/cli/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
#error "Unsupported platform!"
#endif

#include <codecvt>
#include <locale>

void RemoveBinaryTempFileIfExists() {
auto temp =
file_manager_utils::GetExecutableFolderContainerPath() / "cortex_temp";
Expand All @@ -40,11 +43,20 @@ void RemoveBinaryTempFileIfExists() {
void SetupLogger(trantor::FileLogger& async_logger, bool verbose) {
if (!verbose) {
auto config = file_manager_utils::GetCortexConfig();

std::filesystem::create_directories(
#if defined(_WIN32)
std::filesystem::u8path(config.logFolderPath) /
#else
std::filesystem::path(config.logFolderPath) /
#endif
std::filesystem::path(cortex_utils::logs_folder));
async_logger.setFileName(config.logFolderPath + "/" +
cortex_utils::logs_cli_base_name);

// Do not need to use u8path here because trantor handles itself
async_logger.setFileName(
(std::filesystem::path(config.logFolderPath) /
std::filesystem::path(cortex_utils::logs_cli_base_name))
.string());
async_logger.setMaxLines(config.maxLogLines); // Keep last 100000 lines
async_logger.startLogging();
trantor::Logger::setOutputFunction(
Expand Down Expand Up @@ -192,8 +204,7 @@ int main(int argc, char* argv[]) {
// Check if server exists, if not notify to user to install server
auto exe = commands::GetCortexServerBinary();
auto server_binary_path =
std::filesystem::path(cortex_utils::GetCurrentPath()) /
std::filesystem::path(exe);
file_manager_utils::GetExecutableFolderContainerPath() / exe;
if (!std::filesystem::exists(server_binary_path)) {
std::cout << CORTEX_CPP_VERSION
<< " requires server binary, to install server, run: "
Expand Down
2 changes: 1 addition & 1 deletion engine/database/database.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class Database {

private:
Database()
: db_(file_manager_utils::GetCortexDataPath().string() + "/cortex.db",
: db_(file_manager_utils::GetCortexDataPath() / "cortex.db",
SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE) {}
SQLite::Database db_;
};
Expand Down
31 changes: 16 additions & 15 deletions engine/e2e-test/test_api_engine_uninstall.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import pytest
import time
import requests
from test_runner import (
run,
Expand All @@ -21,26 +22,26 @@ def setup_and_teardown(self):

# Teardown
stop_server()

def test_engines_uninstall_llamacpp_should_be_successful(self):
# install first, using cli for synchronously
run(
"Install Engine",
["engines", "install", "llama-cpp"],
timeout=120,
capture=False,
)

@pytest.mark.asyncio
async def test_engines_uninstall_llamacpp_should_be_successful(self):
response = requests.post("http://localhost:3928/v1/engines/llama-cpp/install")
assert response.status_code == 200
await wait_for_websocket_download_success_event(timeout=None)
time.sleep(30)

response = requests.delete("http://localhost:3928/v1/engines/llama-cpp/install")
assert response.status_code == 200

def test_engines_uninstall_llamacpp_with_only_version_should_be_failed(self):
@pytest.mark.asyncio
async def test_engines_uninstall_llamacpp_with_only_version_should_be_failed(self):
# install first
run(
"Install Engine",
["engines", "install", "llama-cpp", "-v", "v0.1.35"],
timeout=None,
capture=False,
data = {"variant": "mac-arm64"}
install_response = requests.post(
"http://127.0.0.1:3928/v1/engines/llama-cpp/install", json=data
)
await wait_for_websocket_download_success_event(timeout=120)
assert install_response.status_code == 200

data = {"version": "v0.1.35"}
response = requests.delete(
Expand Down
30 changes: 20 additions & 10 deletions engine/e2e-test/test_api_model_start.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import pytest
import time
import requests
from test_runner import run, start_server, stop_server


from test_runner import (
wait_for_websocket_download_success_event
)
class TestApiModelStart:

@pytest.fixture(autouse=True)
Expand All @@ -12,20 +14,28 @@ def setup_and_teardown(self):
success = start_server()
if not success:
raise Exception("Failed to start server")
run("Install engine", ["engines", "install", "llama-cpp"], 5 * 60)
run("Delete model", ["models", "delete", "tinyllama:gguf"])
run(
"Pull model",
["pull", "tinyllama:gguf"],
timeout=None,
)

yield

# Teardown
stop_server()

def test_models_start_should_be_successful(self):

@pytest.mark.asyncio
async def test_models_start_should_be_successful(self):
response = requests.post("http://localhost:3928/v1/engines/llama-cpp/install")
assert response.status_code == 200
await wait_for_websocket_download_success_event(timeout=None)
# TODO(sang) need to fix for cuda download
time.sleep(30)

json_body = {
"model": "tinyllama:gguf"
}
response = requests.post("http://localhost:3928/v1/models/pull", json=json_body)
assert response.status_code == 200, f"Failed to pull model: tinyllama:gguf"
await wait_for_websocket_download_success_event(timeout=None)

json_body = {"model": "tinyllama:gguf"}
response = requests.post(
"http://localhost:3928/v1/models/start", json=json_body
Expand Down
14 changes: 11 additions & 3 deletions engine/e2e-test/test_api_model_stop.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import pytest
import time
import requests
from test_runner import run, start_server, stop_server

from test_runner import (
wait_for_websocket_download_success_event
)

class TestApiModelStop:

Expand All @@ -13,14 +16,19 @@ def setup_and_teardown(self):
if not success:
raise Exception("Failed to start server")

run("Install engine", ["engines", "install", "llama-cpp"], 5 * 60)
yield

run("Uninstall engine", ["engines", "uninstall", "llama-cpp"])
# Teardown
stop_server()

def test_models_stop_should_be_successful(self):
@pytest.mark.asyncio
async def test_models_stop_should_be_successful(self):
response = requests.post("http://localhost:3928/v1/engines/llama-cpp/install")
assert response.status_code == 200
await wait_for_websocket_download_success_event(timeout=None)
time.sleep(30)

json_body = {"model": "tinyllama:gguf"}
response = requests.post(
"http://localhost:3928/v1/models/start", json=json_body
Expand Down
3 changes: 3 additions & 0 deletions engine/e2e-test/test_cli_engine_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def setup_and_teardown(self):
# Teardown
stop_server()

@pytest.mark.skipif(platform.system() == "Windows", reason="Progress bar log issue on Windows")
def test_engines_install_llamacpp_should_be_successfully(self):
exit_code, output, error = run(
"Install Engine",
Expand Down Expand Up @@ -46,6 +47,7 @@ def test_engines_install_onnx_on_tensorrt_should_be_failed(self):
assert "is not supported on" in output, "Should display error message"
assert exit_code == 0, f"Install engine failed with error: {error}"

@pytest.mark.skipif(platform.system() == "Windows", reason="Progress bar log issue on Windows")
def test_engines_install_pre_release_llamacpp(self):
engine_version = "v0.1.29"
exit_code, output, error = run(
Expand All @@ -67,6 +69,7 @@ def test_engines_install_pre_release_llamacpp(self):
assert is_engine_version_exist, f"Engine version {engine_version} is not found"
assert exit_code == 0, f"Install engine failed with error: {error}"

@pytest.mark.skipif(platform.system() == "Windows", reason="Progress bar log issue on Windows")
def test_engines_should_fallback_to_download_llamacpp_engine_if_not_exists(self):
exit_code, output, error = run(
"Install Engine",
Expand Down
2 changes: 1 addition & 1 deletion engine/e2e-test/test_cli_engine_uninstall.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def setup_and_teardown(self):
@pytest.mark.asyncio
async def test_engines_uninstall_llamacpp_should_be_successfully(self):
requests.post("http://127.0.0.1:3928/v1/engines/llama-cpp/install")
await wait_for_websocket_download_success_event(timeout=120)
await wait_for_websocket_download_success_event(timeout=None)
exit_code, output, error = run(
"Uninstall engine", ["engines", "uninstall", "llama-cpp"]
)
Expand Down
18 changes: 13 additions & 5 deletions engine/e2e-test/test_cli_model_delete.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import pytest
import requests
from test_runner import popen, run
from test_runner import start_server, stop_server
from test_runner import (
wait_for_websocket_download_success_event
)

class TestCliModelDelete:

Expand All @@ -11,18 +15,22 @@ def setup_and_teardown(self):
if not success:
raise Exception("Failed to start server")

# Pull model

run("Pull model", ["pull", "tinyllama:gguf"], timeout=None,)

yield

# Teardown
# Clean up
run("Delete model", ["models", "delete", "tinyllama:gguf"])
stop_server()

def test_models_delete_should_be_successful(self):
@pytest.mark.asyncio
async def test_models_delete_should_be_successful(self):
json_body = {
"model": "tinyllama:gguf"
}
response = requests.post("http://localhost:3928/v1/models/pull", json=json_body)
assert response.status_code == 200, f"Failed to pull model: tinyllama:gguf"
await wait_for_websocket_download_success_event(timeout=None)

exit_code, output, error = run(
"Delete model", ["models", "delete", "tinyllama:gguf"]
)
Expand Down
Loading

0 comments on commit 719de33

Please sign in to comment.