Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit 40bb126

Browse files
authored
Merge branch 'dev' into fix/folder-for-root
2 parents bb06984 + 7a192b8 commit 40bb126

File tree

8 files changed

+260
-22
lines changed

8 files changed

+260
-22
lines changed

engine/controllers/command_line_parser.cc

Lines changed: 62 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ void CommandLineParser::SetupCommonCommands() {
111111
" pull [options] [model_id]");
112112
model_pull_cmd->add_option("model_id", cml_data_.model_id, "");
113113
model_pull_cmd->callback([this, model_pull_cmd]() {
114+
if (std::exchange(executed_, true))
115+
return;
114116
if (cml_data_.model_id.empty()) {
115117
CLI_LOG("[model_id] is required\n");
116118
CLI_LOG(model_pull_cmd->help());
@@ -130,6 +132,8 @@ void CommandLineParser::SetupCommonCommands() {
130132
run_cmd->add_option("model_id", cml_data_.model_id, "");
131133
run_cmd->add_flag("--chat", cml_data_.chat_flag, "Flag for interactive mode");
132134
run_cmd->callback([this, run_cmd] {
135+
if (std::exchange(executed_, true))
136+
return;
133137
if (cml_data_.model_id.empty()) {
134138
CLI_LOG("[model_id] is required\n");
135139
CLI_LOG(run_cmd->help());
@@ -151,6 +155,8 @@ void CommandLineParser::SetupCommonCommands() {
151155
chat_cmd->add_option("-m,--message", cml_data_.msg,
152156
"Message to chat with model");
153157
chat_cmd->callback([this, chat_cmd] {
158+
if (std::exchange(executed_, true))
159+
return;
154160
if (cml_data_.model_id.empty()) {
155161
CLI_LOG("[model_id] is required\n");
156162
CLI_LOG(chat_cmd->help());
@@ -184,7 +190,9 @@ void CommandLineParser::SetupModelCommands() {
184190
" models [options] [subcommand]");
185191
models_cmd->group(kModelsGroup);
186192

187-
models_cmd->callback([models_cmd] {
193+
models_cmd->callback([this, models_cmd] {
194+
if (std::exchange(executed_, true))
195+
return;
188196
if (models_cmd->get_subcommands().empty()) {
189197
CLI_LOG(models_cmd->help());
190198
}
@@ -197,6 +205,8 @@ void CommandLineParser::SetupModelCommands() {
197205
model_start_cmd->add_option("model_id", cml_data_.model_id, "");
198206
model_start_cmd->group(kSubcommands);
199207
model_start_cmd->callback([this, model_start_cmd]() {
208+
if (std::exchange(executed_, true))
209+
return;
200210
if (cml_data_.model_id.empty()) {
201211
CLI_LOG("[model_id] is required\n");
202212
CLI_LOG(model_start_cmd->help());
@@ -214,6 +224,8 @@ void CommandLineParser::SetupModelCommands() {
214224
stop_model_cmd->group(kSubcommands);
215225
stop_model_cmd->add_option("model_id", cml_data_.model_id, "");
216226
stop_model_cmd->callback([this, stop_model_cmd]() {
227+
if (std::exchange(executed_, true))
228+
return;
217229
if (cml_data_.model_id.empty()) {
218230
CLI_LOG("[model_id] is required\n");
219231
CLI_LOG(stop_model_cmd->help());
@@ -227,7 +239,11 @@ void CommandLineParser::SetupModelCommands() {
227239
auto list_models_cmd =
228240
models_cmd->add_subcommand("list", "List all models locally");
229241
list_models_cmd->group(kSubcommands);
230-
list_models_cmd->callback([]() { commands::ModelListCmd().Exec(); });
242+
list_models_cmd->callback([this]() {
243+
if (std::exchange(executed_, true))
244+
return;
245+
commands::ModelListCmd().Exec();
246+
});
231247

232248
auto get_models_cmd =
233249
models_cmd->add_subcommand("get", "Get info of {model_id} locally");
@@ -236,6 +252,8 @@ void CommandLineParser::SetupModelCommands() {
236252
get_models_cmd->group(kSubcommands);
237253
get_models_cmd->add_option("model_id", cml_data_.model_id, "");
238254
get_models_cmd->callback([this, get_models_cmd]() {
255+
if (std::exchange(executed_, true))
256+
return;
239257
if (cml_data_.model_id.empty()) {
240258
CLI_LOG("[model_id] is required\n");
241259
CLI_LOG(get_models_cmd->help());
@@ -251,6 +269,8 @@ void CommandLineParser::SetupModelCommands() {
251269
model_del_cmd->group(kSubcommands);
252270
model_del_cmd->add_option("model_id", cml_data_.model_id, "");
253271
model_del_cmd->callback([&]() {
272+
if (std::exchange(executed_, true))
273+
return;
254274
if (cml_data_.model_id.empty()) {
255275
CLI_LOG("[model_id] is required\n");
256276
CLI_LOG(model_del_cmd->help());
@@ -271,6 +291,8 @@ void CommandLineParser::SetupModelCommands() {
271291
model_alias_cmd->add_option("--alias", cml_data_.model_alias,
272292
"new alias to be set");
273293
model_alias_cmd->callback([this, model_alias_cmd]() {
294+
if (std::exchange(executed_, true))
295+
return;
274296
if (cml_data_.model_id.empty() || cml_data_.model_alias.empty()) {
275297
CLI_LOG("[model_id] and [alias] are required\n");
276298
CLI_LOG(model_alias_cmd->help());
@@ -294,6 +316,8 @@ void CommandLineParser::SetupModelCommands() {
294316
"Absolute path to .gguf model, the path should "
295317
"include the gguf file name");
296318
model_import_cmd->callback([this, model_import_cmd]() {
319+
if (std::exchange(executed_, true))
320+
return;
297321
if (cml_data_.model_id.empty() || cml_data_.model_path.empty()) {
298322
CLI_LOG("[model_id] and [model_path] are required\n");
299323
CLI_LOG(model_import_cmd->help());
@@ -310,7 +334,9 @@ void CommandLineParser::SetupEngineCommands() {
310334
engines_cmd->usage("Usage:\n" + commands::GetCortexBinary() +
311335
" engines [options] [subcommand]");
312336
engines_cmd->group(kEngineGroup);
313-
engines_cmd->callback([engines_cmd] {
337+
engines_cmd->callback([this, engines_cmd] {
338+
if (std::exchange(executed_, true))
339+
return;
314340
if (engines_cmd->get_subcommands().empty()) {
315341
CLI_LOG("A subcommand is required\n");
316342
CLI_LOG(engines_cmd->help());
@@ -320,7 +346,9 @@ void CommandLineParser::SetupEngineCommands() {
320346
auto list_engines_cmd =
321347
engines_cmd->add_subcommand("list", "List all cortex engines");
322348
list_engines_cmd->group(kSubcommands);
323-
list_engines_cmd->callback([]() {
349+
list_engines_cmd->callback([this]() {
350+
if (std::exchange(executed_, true))
351+
return;
324352
commands::EngineListCmd command;
325353
command.Exec();
326354
});
@@ -329,7 +357,9 @@ void CommandLineParser::SetupEngineCommands() {
329357
install_cmd->usage("Usage:\n" + commands::GetCortexBinary() +
330358
" engines install [engine_name] [options]");
331359
install_cmd->group(kSubcommands);
332-
install_cmd->callback([install_cmd] {
360+
install_cmd->callback([this, install_cmd] {
361+
if (std::exchange(executed_, true))
362+
return;
333363
if (install_cmd->get_subcommands().empty()) {
334364
CLI_LOG("[engine_name] is required\n");
335365
CLI_LOG(install_cmd->help());
@@ -345,7 +375,9 @@ void CommandLineParser::SetupEngineCommands() {
345375
engines_cmd->add_subcommand("uninstall", "Uninstall engine");
346376
uninstall_cmd->usage("Usage:\n" + commands::GetCortexBinary() +
347377
" engines uninstall [engine_name] [options]");
348-
uninstall_cmd->callback([uninstall_cmd] {
378+
uninstall_cmd->callback([this, uninstall_cmd] {
379+
if (std::exchange(executed_, true))
380+
return;
349381
if (uninstall_cmd->get_subcommands().empty()) {
350382
CLI_LOG("[engine_name] is required\n");
351383
CLI_LOG(uninstall_cmd->help());
@@ -366,6 +398,8 @@ void CommandLineParser::SetupSystemCommands() {
366398
cml_data_.port = std::stoi(cml_data_.config.apiServerPort);
367399
start_cmd->add_option("-p, --port", cml_data_.port, "Server port to listen");
368400
start_cmd->callback([this] {
401+
if (std::exchange(executed_, true))
402+
return;
369403
if (cml_data_.port != stoi(cml_data_.config.apiServerPort)) {
370404
CTL_INF("apiServerPort changed from " << cml_data_.config.apiServerPort
371405
<< " to " << cml_data_.port);
@@ -381,6 +415,8 @@ void CommandLineParser::SetupSystemCommands() {
381415
auto stop_cmd = app_.add_subcommand("stop", "Stop the API server");
382416
stop_cmd->group(kSystemGroup);
383417
stop_cmd->callback([this] {
418+
if (std::exchange(executed_, true))
419+
return;
384420
commands::ServerStopCmd ssc(cml_data_.config.apiServerHost,
385421
std::stoi(cml_data_.config.apiServerPort));
386422
ssc.Exec();
@@ -391,6 +427,8 @@ void CommandLineParser::SetupSystemCommands() {
391427
ps_cmd->group(kSystemGroup);
392428
ps_cmd->usage("Usage:\n" + commands::GetCortexBinary() + "ps");
393429
ps_cmd->callback([&]() {
430+
if (std::exchange(executed_, true))
431+
return;
394432
commands::PsCmd().Exec(cml_data_.config.apiServerHost,
395433
std::stoi(cml_data_.config.apiServerPort));
396434
});
@@ -399,6 +437,8 @@ void CommandLineParser::SetupSystemCommands() {
399437
update_cmd->group(kSystemGroup);
400438
update_cmd->add_option("-v", cml_data_.cortex_version, "");
401439
update_cmd->callback([this] {
440+
if (std::exchange(executed_, true))
441+
return;
402442
#if !defined(_WIN32)
403443
if (getuid()) {
404444
CLI_LOG("Error: Not root user. Please run with sudo.");
@@ -425,7 +465,9 @@ void CommandLineParser::EngineInstall(CLI::App* parent,
425465
install_engine_cmd->add_option("-s, --source", src,
426466
"Install engine by local path");
427467

428-
install_engine_cmd->callback([engine_name, &version, &src] {
468+
install_engine_cmd->callback([this, engine_name, &version, &src] {
469+
if (std::exchange(executed_, true))
470+
return;
429471
try {
430472
commands::EngineInstallCmd().Exec(engine_name, version, src);
431473
} catch (const std::exception& e) {
@@ -441,7 +483,9 @@ void CommandLineParser::EngineUninstall(CLI::App* parent,
441483
" engines install " + engine_name + " [options]");
442484
uninstall_engine_cmd->group(kEngineGroup);
443485

444-
uninstall_engine_cmd->callback([engine_name] {
486+
uninstall_engine_cmd->callback([this, engine_name] {
487+
if (std::exchange(executed_, true))
488+
return;
445489
try {
446490
commands::EngineUninstallCmd().Exec(engine_name);
447491
} catch (const std::exception& e) {
@@ -455,7 +499,9 @@ void CommandLineParser::EngineGet(CLI::App* parent) {
455499
get_cmd->usage("Usage:\n" + commands::GetCortexBinary() +
456500
" engines get [engine_name] [options]");
457501
get_cmd->group(kSubcommands);
458-
get_cmd->callback([get_cmd] {
502+
get_cmd->callback([this, get_cmd] {
503+
if (std::exchange(executed_, true))
504+
return;
459505
if (get_cmd->get_subcommands().empty()) {
460506
CLI_LOG("[engine_name] is required\n");
461507
CLI_LOG(get_cmd->help());
@@ -470,8 +516,11 @@ void CommandLineParser::EngineGet(CLI::App* parent) {
470516
engine_get_cmd->usage("Usage:\n" + commands::GetCortexBinary() +
471517
" engines get " + engine_name + " [options]");
472518
engine_get_cmd->group(kEngineGroup);
473-
engine_get_cmd->callback(
474-
[engine_name] { commands::EngineGetCmd().Exec(engine_name); });
519+
engine_get_cmd->callback([this, engine_name] {
520+
if (std::exchange(executed_, true))
521+
return;
522+
commands::EngineGetCmd().Exec(engine_name);
523+
});
475524
}
476525
}
477526

@@ -536,6 +585,8 @@ void CommandLineParser::ModelUpdate(CLI::App* parent) {
536585
}
537586

538587
model_update_cmd->callback([this]() {
588+
if (std::exchange(executed_, true))
589+
return;
539590
commands::ModelUpdCmd command(cml_data_.model_id);
540591
command.Exec(cml_data_.model_update_options);
541592
});

engine/controllers/command_line_parser.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ class CommandLineParser {
4444
std::unordered_map<std::string, std::string> model_update_options;
4545
};
4646
CmlData cml_data_;
47+
bool executed_ = false;
4748
};

engine/test/components/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ find_package(LibArchive REQUIRED)
2222
find_package(CURL REQUIRED)
2323
find_package(SQLiteCpp REQUIRED)
2424

25-
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon GTest::gtest GTest::gtest_main yaml-cpp::yaml-cpp
25+
target_link_libraries(${PROJECT_NAME} PRIVATE Drogon::Drogon GTest::gtest GTest::gtest_main yaml-cpp::yaml-cpp
2626
${CMAKE_THREAD_LIBS_INIT})
2727

2828
target_link_libraries(${PROJECT_NAME} PRIVATE httplib::httplib)
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#include <gtest/gtest.h>
2+
#include "utils/cuda_toolkit_utils.h"
3+
4+
// Test fixture for cuda_toolkit_utils
5+
class CudaToolkitUtilsTest : public ::testing::Test {};
6+
7+
// Tests for cuda_toolkit_utils
8+
9+
TEST_F(CudaToolkitUtilsTest, WindowsCompatibleVersions) {
10+
EXPECT_EQ("12.4", cuda_toolkit_utils::GetCompatibleCudaToolkitVersion(
11+
"527.41", "windows", ""));
12+
EXPECT_EQ("11.7", cuda_toolkit_utils::GetCompatibleCudaToolkitVersion(
13+
"452.39", "windows", ""));
14+
}
15+
16+
TEST_F(CudaToolkitUtilsTest, LinuxCompatibleVersions) {
17+
EXPECT_EQ("12.4", cuda_toolkit_utils::GetCompatibleCudaToolkitVersion(
18+
"525.60.13", "linux", ""));
19+
EXPECT_EQ("11.7", cuda_toolkit_utils::GetCompatibleCudaToolkitVersion(
20+
"450.80.02", "linux", ""));
21+
}
22+
23+
TEST_F(CudaToolkitUtilsTest, TensorRTLLMEngine) {
24+
EXPECT_EQ("12.4", cuda_toolkit_utils::GetCompatibleCudaToolkitVersion(
25+
"527.41", "windows", "cortex.tensorrt-llm"));
26+
EXPECT_EQ("12.4", cuda_toolkit_utils::GetCompatibleCudaToolkitVersion(
27+
"525.60.13", "linux", "cortex.tensorrt-llm"));
28+
}
29+
30+
TEST_F(CudaToolkitUtilsTest, UnsupportedDriverVersion) {
31+
EXPECT_THROW(cuda_toolkit_utils::GetCompatibleCudaToolkitVersion(
32+
"450.00", "windows", ""),
33+
std::runtime_error);
34+
EXPECT_THROW(cuda_toolkit_utils::GetCompatibleCudaToolkitVersion("450.00",
35+
"linux", ""),
36+
std::runtime_error);
37+
}
38+
39+
TEST_F(CudaToolkitUtilsTest, UnsupportedOS) {
40+
EXPECT_THROW(cuda_toolkit_utils::GetCompatibleCudaToolkitVersion("527.41",
41+
"macos", ""),
42+
std::runtime_error);
43+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include <gmock/gmock.h>
2+
#include <gtest/gtest.h>
3+
#include <filesystem>
4+
#include "utils/config_yaml_utils.h"
5+
#include "utils/file_manager_utils.h"
6+
7+
// Mock for filesystem operations
8+
9+
// Test fixture
10+
class FileManagerConfigTest : public ::testing::Test {};
11+
12+
// Tests for file_manager_utils
13+
14+
TEST_F(FileManagerConfigTest, GetExecutableFolderContainerPath) {
15+
auto path = file_manager_utils::GetExecutableFolderContainerPath();
16+
EXPECT_FALSE(path.empty());
17+
EXPECT_TRUE(std::filesystem::is_directory(path));
18+
}
19+
20+
TEST_F(FileManagerConfigTest, GetHomeDirectoryPath) {
21+
auto path = file_manager_utils::GetHomeDirectoryPath();
22+
EXPECT_FALSE(path.empty());
23+
EXPECT_TRUE(std::filesystem::is_directory(path));
24+
}
25+
26+
TEST_F(FileManagerConfigTest, GetConfigurationPath) {
27+
auto path = file_manager_utils::GetConfigurationPath();
28+
EXPECT_FALSE(path.empty());
29+
EXPECT_TRUE(path.has_filename());
30+
}
31+
32+
TEST_F(FileManagerConfigTest, GetDefaultDataFolderName) {
33+
auto folder_name = file_manager_utils::GetDefaultDataFolderName();
34+
EXPECT_FALSE(folder_name.empty());
35+
EXPECT_TRUE(folder_name.find("cortexcpp") != std::string::npos);
36+
}
37+
38+
TEST_F(FileManagerConfigTest, CreateConfigFileIfNotExist) {
39+
40+
file_manager_utils::CreateConfigFileIfNotExist();
41+
EXPECT_TRUE(
42+
std::filesystem::exists(file_manager_utils::GetConfigurationPath()));
43+
std::filesystem::remove(file_manager_utils::GetConfigurationPath());
44+
}
45+
46+
TEST_F(FileManagerConfigTest, GetCortexConfig) {
47+
file_manager_utils::CreateConfigFileIfNotExist();
48+
auto config = file_manager_utils::GetCortexConfig();
49+
EXPECT_FALSE(config.dataFolderPath.empty());
50+
EXPECT_FALSE(config.logFolderPath.empty());
51+
EXPECT_GT(config.maxLogLines, 0);
52+
}
53+
54+
// Tests for config_yaml_utils
55+
56+
TEST_F(FileManagerConfigTest, DumpYamlConfig) {
57+
config_yaml_utils::CortexConfig config{.logFolderPath = "/path/to/logs",
58+
.dataFolderPath = "/path/to/data",
59+
.maxLogLines = 1000,
60+
.apiServerHost = "localhost",
61+
.apiServerPort = "8080"};
62+
63+
std::string test_file = "test_config.yaml";
64+
config_yaml_utils::DumpYamlConfig(config, test_file);
65+
66+
EXPECT_TRUE(std::filesystem::exists(test_file));
67+
68+
// Clean up
69+
std::filesystem::remove(test_file);
70+
}
71+
72+
TEST_F(FileManagerConfigTest, FromYaml) {
73+
// Create a test YAML file
74+
std::string test_file = "test_config.yaml";
75+
std::ofstream out_file(test_file);
76+
out_file << "logFolderPath: /path/to/logs\n"
77+
<< "dataFolderPath: /path/to/data\n"
78+
<< "maxLogLines: 1000\n"
79+
<< "apiServerHost: localhost\n"
80+
<< "apiServerPort: '8080'\n";
81+
out_file.close();
82+
83+
config_yaml_utils::CortexConfig default_config{};
84+
auto config = config_yaml_utils::FromYaml(test_file, default_config);
85+
86+
EXPECT_EQ(config.logFolderPath, "/path/to/logs");
87+
EXPECT_EQ(config.dataFolderPath, "/path/to/data");
88+
EXPECT_EQ(config.maxLogLines, 1000);
89+
EXPECT_EQ(config.apiServerHost, "localhost");
90+
EXPECT_EQ(config.apiServerPort, "8080");
91+
92+
// Clean up
93+
std::filesystem::remove(test_file);
94+
}

0 commit comments

Comments
 (0)