Skip to content

Commit 508bc4a

Browse files
committed
[TEST] Add test case for lxm service internal API
- Added positive and negative TCs for lxm service internal API Signed-off-by: hyunil park <hyunil46.park@samsung.com>
1 parent ea06b44 commit 508bc4a

2 files changed

Lines changed: 341 additions & 0 deletions

File tree

tests/capi/meson.build

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,22 @@ if get_option('enable-ml-service')
7676
test('unittest_capi_service_training_offloading', unittest_capi_service_training_offloading, env: testenv, timeout: 100)
7777
endif
7878
endif
79+
80+
# LXM Service Tests
81+
# These tests require both ml-service and llamacpp to be enabled.
82+
llamacpp_dep = dependency('llama', required: false)
83+
if llamacpp_dep.found()
84+
# Note: The source file itself is also conditionally compiled with ENABLE_LLAMACPP.
85+
unittest_capi_lxm_service = executable('unittest_capi_lxm_service',
86+
'unittest_capi_lxm_service.cc',
87+
dependencies: service_unittest_deps,
88+
install: get_option('install-test'),
89+
install_dir: unittest_install_dir
90+
)
91+
test('unittest_capi_lxm_service', unittest_capi_lxm_service, env: testenv, timeout: 120) # Increased timeout for LLM response
92+
else
93+
message('LXM Service tests will be skipped because llama dependency was not found.')
94+
endif
7995
endif
8096

8197
if nnfw_dep.found()
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/* SPDX-License-Identifier: Apache-2.0 */
2+
/**
3+
* @file unittest_capi_lxm_service.cc
4+
* @date 26 JULY 2025
5+
* @brief Unit test for ml-lxm-service.
6+
* @see https://github.com/nnstreamer/api
7+
* @author Hyunil Park <hyunil46.park@samsung.com>
8+
* @bug No known bugs
9+
*/
10+
11+
#include <gtest/gtest.h>
12+
#include <glib.h>
13+
#include <ml-api-common.h>
14+
#include <ml-api-service-private.h>
15+
#include <ml-api-service.h>
16+
#include <string.h>
17+
#include "ml-lxm-service-internal.h"
18+
#include "unittest_util.h"
19+
20+
#if defined(ENABLE_LLAMACPP)
21+
22+
/**
23+
* @brief Internal function to get the model file path.
24+
*/
25+
static gchar *
26+
_get_model_path (const gchar *model_name)
27+
{
28+
const gchar *root_path = g_getenv ("MLAPI_SOURCE_ROOT_PATH");
29+
30+
/* Supposed to run test in build directory. */
31+
if (root_path == NULL)
32+
root_path = "..";
33+
34+
gchar *model_file = g_build_filename (
35+
root_path, "tests", "test_models", "models", model_name, NULL);
36+
37+
return model_file;
38+
}
39+
40+
/**
41+
* @brief Macro to skip testcase if required files are not ready.
42+
*/
43+
#define skip_lxm_tc(tc_name) \
44+
do { \
45+
g_autofree gchar *model_file = _get_model_path ("llama-2-7b-chat.Q2_K.gguf"); \
46+
if (!g_file_test (model_file, G_FILE_TEST_EXISTS)) { \
47+
g_autofree gchar *msg = g_strdup_printf ( \
48+
"Skipping '%s' due to missing model file. " \
49+
"Please download model file from https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF.", \
50+
tc_name); \
51+
GTEST_SKIP () << msg; \
52+
} \
53+
} while (0)
54+
55+
/**
56+
* @brief Test data structure to pass to the callback.
57+
*/
58+
typedef struct {
59+
int token_count;
60+
GString *received_tokens;
61+
} lxm_test_data_s;
62+
63+
/**
64+
* @brief Callback function for LXM service token streaming.
65+
*/
66+
static void
67+
_lxm_token_cb (ml_service_event_e event, ml_information_h event_data, void *user_data)
68+
{
69+
lxm_test_data_s *tdata = (lxm_test_data_s *) user_data;
70+
ml_tensors_data_h data = NULL;
71+
void *_raw = NULL;
72+
size_t _size = 0;
73+
int status;
74+
75+
switch (event) {
76+
case ML_SERVICE_EVENT_NEW_DATA:
77+
ASSERT_TRUE (event_data != NULL);
78+
79+
status = ml_information_get (event_data, "data", &data);
80+
EXPECT_EQ (status, ML_ERROR_NONE);
81+
if (status != ML_ERROR_NONE)
82+
return;
83+
84+
status = ml_tensors_data_get_tensor_data (data, 0U, &_raw, &_size);
85+
EXPECT_EQ (status, ML_ERROR_NONE);
86+
if (status != ML_ERROR_NONE)
87+
return;
88+
89+
if (tdata) {
90+
if (tdata->received_tokens) {
91+
g_string_append_len (tdata->received_tokens, (const char *) _raw, _size);
92+
}
93+
tdata->token_count++;
94+
}
95+
g_print ("%.*s", (int) _size, (char *) _raw); // Print received token
96+
break;
97+
default:
98+
// Handle unknown or unimplemented events if necessary
99+
g_printerr ("Received unhandled LXM service event: %d\n", event);
100+
break;
101+
}
102+
}
103+
104+
/**
105+
* @brief Internal function to run a full LXM session test.
106+
*/
107+
static void
108+
_run_lxm_session_test (const gchar *config_path, const gchar *input_text, ml_option_h options)
109+
{
110+
ml_lxm_session_h session = NULL;
111+
ml_lxm_prompt_h prompt = NULL;
112+
lxm_test_data_s tdata = { 0, NULL };
113+
int status;
114+
115+
tdata.received_tokens = g_string_new ("");
116+
117+
// 1. Create session
118+
status = ml_lxm_session_create (config_path, NULL, &session);
119+
ASSERT_EQ (status, ML_ERROR_NONE);
120+
ASSERT_TRUE (session != NULL);
121+
122+
// 2. Create prompt
123+
status = ml_lxm_prompt_create (&prompt);
124+
ASSERT_EQ (status, ML_ERROR_NONE);
125+
ASSERT_TRUE (prompt != NULL);
126+
127+
status = ml_lxm_prompt_append_text (prompt, input_text);
128+
ASSERT_EQ (status, ML_ERROR_NONE);
129+
130+
// 3. Set callback first
131+
status = ml_lxm_session_set_event_cb (session, _lxm_token_cb, &tdata);
132+
ASSERT_EQ (status, ML_ERROR_NONE);
133+
134+
// 4. Generate response
135+
status = ml_lxm_session_respond (session, prompt, options);
136+
ASSERT_EQ (status, ML_ERROR_NONE);
137+
138+
// Wait for the callback to receive data.
139+
// 10 seconds should be enough for a simple response.
140+
g_usleep (10000000U);
141+
142+
// 5. Verify results
143+
EXPECT_GT (tdata.token_count, 0);
144+
EXPECT_GT (tdata.received_tokens->len, 0U);
145+
146+
g_print ("\nReceived total tokens: %d\n", tdata.token_count);
147+
g_print ("Full received text: %s\n", tdata.received_tokens->str);
148+
149+
// 6. Cleanup
150+
status = ml_lxm_prompt_destroy (prompt);
151+
EXPECT_EQ (status, ML_ERROR_NONE);
152+
153+
status = ml_lxm_session_destroy (session);
154+
EXPECT_EQ (status, ML_ERROR_NONE);
155+
156+
if (tdata.received_tokens) {
157+
g_string_free (tdata.received_tokens, TRUE);
158+
}
159+
}
160+
161+
/**
162+
* @brief Test basic flow of LXM service.
163+
*/
164+
TEST (MLLxmService, basicFlow_p)
165+
{
166+
skip_lxm_tc ("basicFlow_p");
167+
168+
g_autofree gchar *config = get_config_path ("config_single_llamacpp.conf");
169+
ASSERT_TRUE (config != NULL);
170+
171+
const gchar input_text[] = "Hello LXM, how are you?";
172+
ml_option_h options = NULL;
173+
int status;
174+
175+
// Create options
176+
status = ml_option_create (&options);
177+
ASSERT_EQ (status, ML_ERROR_NONE);
178+
ASSERT_TRUE (options != NULL);
179+
180+
// Set temperature option
181+
status = ml_option_set (options, "temperature", g_strdup_printf ("%f", 0.8), g_free);
182+
ASSERT_EQ (status, ML_ERROR_NONE);
183+
184+
// Set max_tokens option
185+
status = ml_option_set (
186+
options, "max_tokens", g_strdup_printf ("%zu", (size_t) 32), g_free);
187+
ASSERT_EQ (status, ML_ERROR_NONE);
188+
189+
_run_lxm_session_test (config, input_text, options);
190+
191+
// Cleanup options
192+
ml_option_destroy (options);
193+
}
194+
195+
196+
/**
197+
* @brief Test LXM service with invalid parameters.
198+
*/
199+
TEST (MLLxmService, invalidParams_n)
200+
{
201+
ml_lxm_session_h session = NULL;
202+
ml_lxm_prompt_h prompt = NULL;
203+
int status;
204+
ml_option_h options = NULL;
205+
g_autofree gchar *valid_config = get_config_path ("config_single_llamacpp.conf");
206+
207+
// Create options for testing
208+
status = ml_option_create (&options);
209+
ASSERT_EQ (status, ML_ERROR_NONE);
210+
ml_option_set (options, "temperature", g_strdup_printf ("%f", 0.5), g_free);
211+
ml_option_set (options, "max_tokens", g_strdup_printf ("%zu", (size_t) 10), g_free);
212+
213+
// ml_lxm_session_create
214+
status = ml_lxm_session_create (valid_config, NULL, NULL);
215+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
216+
status = ml_lxm_session_create (NULL, NULL, &session);
217+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
218+
219+
status = ml_lxm_session_create ("non_existent_config.conf", NULL, &session);
220+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
221+
222+
status = ml_lxm_session_create (valid_config, NULL, &session);
223+
if (status == ML_ERROR_NONE) {
224+
// ml_lxm_prompt_create
225+
status = ml_lxm_prompt_create (NULL);
226+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
227+
228+
status = ml_lxm_prompt_create (&prompt);
229+
ASSERT_EQ (status, ML_ERROR_NONE);
230+
231+
// ml_lxm_prompt_append_text
232+
status = ml_lxm_prompt_append_text (NULL, "text");
233+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
234+
status = ml_lxm_prompt_append_text (prompt, NULL);
235+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
236+
237+
// ml_lxm_prompt_append_instruction
238+
status = ml_lxm_prompt_append_instruction (NULL, "instruction");
239+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
240+
status = ml_lxm_prompt_append_instruction (prompt, NULL);
241+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
242+
243+
// ml_lxm_session_set_instructions
244+
status = ml_lxm_session_set_instructions (NULL, "new instructions");
245+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
246+
status = ml_lxm_session_set_instructions (session, NULL);
247+
EXPECT_EQ (status, ML_ERROR_NONE);
248+
status = ml_lxm_session_set_instructions (session, "new instructions");
249+
EXPECT_EQ (status, ML_ERROR_NONE);
250+
251+
// ml_lxm_session_set_event_cb - invalid parameters
252+
status = ml_lxm_session_set_event_cb (NULL, _lxm_token_cb, NULL);
253+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
254+
status = ml_lxm_session_set_event_cb (session, NULL, NULL);
255+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
256+
257+
// ml_lxm_session_respond - should fail without callback set
258+
status = ml_lxm_session_respond (NULL, prompt, options);
259+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
260+
status = ml_lxm_session_respond (session, NULL, options);
261+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
262+
status = ml_lxm_session_respond (session, prompt, options);
263+
EXPECT_EQ (status, ML_ERROR_STREAMS_PIPE);
264+
265+
// Set callback for valid session
266+
status = ml_lxm_session_set_event_cb (session, _lxm_token_cb, NULL);
267+
EXPECT_EQ (status, ML_ERROR_NONE);
268+
269+
// Now ml_lxm_session_respond should succeed with valid parameters
270+
status = ml_lxm_session_respond (session, prompt, options);
271+
EXPECT_EQ (status, ML_ERROR_NONE);
272+
273+
// ml_lxm_prompt_destroy
274+
status = ml_lxm_prompt_destroy (NULL);
275+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
276+
status = ml_lxm_prompt_destroy (prompt);
277+
EXPECT_EQ (status, ML_ERROR_NONE);
278+
prompt = NULL;
279+
280+
// ml_lxm_session_destroy
281+
status = ml_lxm_session_destroy (NULL);
282+
EXPECT_EQ (status, ML_ERROR_INVALID_PARAMETER);
283+
status = ml_lxm_session_destroy (session);
284+
EXPECT_EQ (status, ML_ERROR_NONE);
285+
session = NULL;
286+
} else {
287+
g_print ("Skipping part of invalidParams_n as session creation failed (possibly due to missing models/config).\n");
288+
}
289+
290+
// Cleanup options
291+
ml_option_destroy (options);
292+
}
293+
294+
/**
295+
* @brief Main function to run the test.
296+
*/
297+
int
298+
main (int argc, char **argv)
299+
{
300+
int result = -1;
301+
302+
try {
303+
testing::InitGoogleTest (&argc, argv);
304+
} catch (...) {
305+
g_warning ("catch 'testing::internal::<unnamed>::ClassUniqueToAlwaysTrue'");
306+
}
307+
308+
/* ignore tizen feature status while running the testcases */
309+
set_feature_state (ML_FEATURE, SUPPORTED);
310+
set_feature_state (ML_FEATURE_INFERENCE, SUPPORTED);
311+
set_feature_state (ML_FEATURE_SERVICE, SUPPORTED);
312+
313+
try {
314+
result = RUN_ALL_TESTS ();
315+
} catch (...) {
316+
g_warning ("catch `testing::internal::GoogleTestFailureException`");
317+
}
318+
319+
set_feature_state (ML_FEATURE, NOT_CHECKED_YET);
320+
set_feature_state (ML_FEATURE_INFERENCE, NOT_CHECKED_YET);
321+
set_feature_state (ML_FEATURE_SERVICE, NOT_CHECKED_YET);
322+
323+
return result;
324+
}
325+
#endif /* ENABLE_LLAMACPP */

0 commit comments

Comments
 (0)