[Test/onnxruntime] Add unit tests and ssat tests for ONNX Runtime
[platform/upstream/nnstreamer.git] / tests / nnstreamer_filter_onnxruntime / unittest_filter_onnxruntime.cc
1 /* SPDX-License-Identifier: LGPL-2.1-only */
2 /**
3  * @file    unittest_filter_onnxruntime.cc
4  * @date    30 Oct 2023
5  * @brief   Unit test for onnxruntime tensor filter sub-plugin
6  * @author  Suyeon Kim <suyeon5.kim@samsung.com>
7  * @see     http://github.com/nnstreamer/nnstreamer
8  * @bug     No known bugs
9  *
10  */
11
12 #include <gtest/gtest.h>
13 #include <glib.h>
14 #include <gst/gst.h>
15
16 #include <nnstreamer_plugin_api_filter.h>
17 #include <nnstreamer_util.h>
18 #include <tensor_common.h>
19 #include <unittest_util.h>
20 #include "nnstreamer_plugin_api.h"
21 #include "nnstreamer_plugin_api_util.h"
22
23 /**
24  * @brief internal function to get model filename
25  */
26 static gboolean
27 _GetModelFilePath (gchar **model_file)
28 {
29   const gchar *src_root = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");
30   g_autofree gchar *root_path = src_root ? g_strdup (src_root) : g_get_current_dir ();
31   std::string model_name = "mobilenet_v2_quant.onnx";
32
33   *model_file = g_build_filename (
34       root_path, "tests", "test_models", "models", model_name.c_str (), NULL);
35
36   return g_file_test (*model_file, G_FILE_TEST_EXISTS);
37 }
38
39 /**
40  * @brief internal function to get the orange.png
41  */
42 static gboolean
43 _GetOrangePngFilePath (gchar **input_file)
44 {
45   const gchar *src_root = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");
46   g_autofree gchar *root_path = src_root ? g_strdup (src_root) : g_get_current_dir ();
47   std::string input_file_name = "orange.png";
48
49   *input_file = g_build_filename (
50       root_path, "tests", "test_models", "data", input_file_name.c_str (), NULL);
51
52   return g_file_test (*input_file, G_FILE_TEST_EXISTS);
53 }
54
55 /**
56  * @brief Set tensor filter properties
57  */
58 static void
59 _SetFilterProp (GstTensorFilterProperties *prop, const gchar *name, const gchar **models)
60 {
61   memset (prop, 0, sizeof (GstTensorFilterProperties));
62   prop->fwname = name;
63   prop->fw_opened = 0;
64   prop->model_files = models;
65   prop->num_models = g_strv_length ((gchar **) models);
66 }
67
68 /**
69  * @brief Signal to validate the result in tensor_sink
70  */
71 static void
72 check_output (GstElement *element, GstBuffer *buffer, gpointer user_data)
73 {
74   GstMemory *mem_res;
75   GstMapInfo info_res;
76   gboolean mapped;
77   UNUSED (element);
78
79   mem_res = gst_buffer_get_memory (buffer, 0);
80   mapped = gst_memory_map (mem_res, &info_res, GST_MAP_READ);
81   ASSERT_TRUE (mapped);
82
83   gint is_float = (gint) * ((guint8 *) user_data);
84   guint idx, max_idx = 0U;
85
86   if (is_float == 0) {
87     guint8 *output = (guint8 *) info_res.data;
88     guint8 max_value = 0;
89
90     for (idx = 0; idx < info_res.size; ++idx) {
91       if (output[idx] > max_value) {
92         max_value = output[idx];
93         max_idx = idx;
94       }
95     }
96   } else if (is_float == 1) {
97     gfloat *output = (gfloat *) info_res.data;
98     gfloat max_value = G_MINFLOAT;
99
100     for (idx = 0; idx < (info_res.size / sizeof (gfloat)); ++idx) {
101       if (output[idx] > max_value) {
102         max_value = output[idx];
103         max_idx = idx;
104       }
105     }
106   }
107
108   gst_memory_unmap (mem_res, &info_res);
109   gst_memory_unref (mem_res);
110
111   EXPECT_EQ (max_idx, 951U);
112 }
113
114 /**
115  * @brief Negative test case with invalid model file path
116  */
117 TEST (nnstreamerFilterOnnxRuntime, openClose00)
118 {
119   int ret;
120   void *data = NULL;
121
122   const gchar *model_files[] = {
123     "some/invalid/model/path.onnx",
124     NULL,
125   };
126
127   const GstTensorFilterFramework *sp = nnstreamer_filter_find ("onnxruntime");
128   EXPECT_NE (sp, nullptr);
129
130   GstTensorFilterProperties prop;
131   _SetFilterProp (&prop, "onnxruntime", model_files);
132
133   ret = sp->open (&prop, &data);
134   EXPECT_NE (ret, 0);
135 }
136
137 /**
138  * @brief Positive case with successful getModelInfo
139  */
140 TEST (nnstreamerFilterOnnxRuntime, getModelInfo00)
141 {
142   int ret;
143   void *data = NULL;
144   gchar *model_file;
145
146   ASSERT_TRUE (_GetModelFilePath (&model_file));
147
148   const gchar *model_files[] = {
149     model_file,
150     NULL,
151   };
152
153   const GstTensorFilterFramework *sp = nnstreamer_filter_find ("onnxruntime");
154   EXPECT_NE (sp, nullptr);
155
156   GstTensorFilterProperties prop;
157   _SetFilterProp (&prop, "onnxruntime", model_files);
158
159   ret = sp->open (&prop, &data);
160   EXPECT_EQ (ret, 0);
161 }
162
163 /**
164  * @brief Positive case with successful getModelInfo
165  */
166 TEST (nnstreamerFilterOnnxRuntime, getModelInfo00_1)
167 {
168   int ret;
169   void *data = NULL;
170   g_autofree gchar *model_file = NULL;
171
172   ASSERT_TRUE (_GetModelFilePath (&model_file));
173
174   const gchar *model_files[] = {
175     model_file,
176     NULL,
177   };
178
179   const GstTensorFilterFramework *sp = nnstreamer_filter_find ("onnxruntime");
180   EXPECT_NE (sp, nullptr);
181
182   GstTensorFilterProperties prop;
183   _SetFilterProp (&prop, "onnxruntime", model_files);
184
185   ret = sp->open (&prop, &data);
186
187   EXPECT_EQ (ret, 0);
188
189   GstTensorsInfo in_info, out_info;
190   ret = sp->getModelInfo (NULL, NULL, data, GET_IN_OUT_INFO, &in_info, &out_info);
191   EXPECT_EQ (ret, 0);
192
193   EXPECT_EQ (in_info.num_tensors, 1U);
194   EXPECT_EQ (in_info.info[0].dimension[0], 224U);
195   EXPECT_EQ (in_info.info[0].dimension[1], 224U);
196   EXPECT_EQ (in_info.info[0].dimension[2], 3U);
197   EXPECT_EQ (in_info.info[0].dimension[3], 1U);
198   EXPECT_EQ (in_info.info[0].type, _NNS_FLOAT32);
199
200   EXPECT_EQ (out_info.num_tensors, 1U);
201   EXPECT_EQ (out_info.info[0].dimension[0], 1000U);
202   EXPECT_EQ (out_info.info[0].dimension[1], 1U);
203   EXPECT_EQ (out_info.info[0].dimension[2], 0U);
204   EXPECT_EQ (out_info.info[0].dimension[3], 0U);
205   EXPECT_EQ (out_info.info[0].type, _NNS_FLOAT32);
206
207   sp->close (&prop, &data);
208
209   gst_tensors_info_free (&in_info);
210   gst_tensors_info_free (&out_info);
211 }
212
213 /**
214  * @brief Test onnxruntime subplugin with successful invoke for sample onnx model (input data type: float)
215  */
216 TEST (nnstreamerFilterOnnxRuntime, invoke00)
217 {
218   int ret;
219   void *data = NULL;
220   GstTensorMemory input, output;
221   g_autofree gchar *model_file = NULL;
222
223   ASSERT_TRUE (_GetModelFilePath (&model_file));
224
225   const gchar *model_files[] = {
226     model_file,
227     NULL,
228   };
229
230   const GstTensorFilterFramework *sp = nnstreamer_filter_find ("onnxruntime");
231   ASSERT_TRUE (sp != nullptr);
232
233   GstTensorFilterProperties prop;
234   _SetFilterProp (&prop, "onnxruntime", model_files);
235
236   input.size = sizeof (float) * 224 * 224 * 3 * 1;
237   output.size = sizeof (float) * 1000 * 1;
238
239   input.data = g_malloc0 (input.size);
240   output.data = g_malloc0 (output.size);
241
242   ret = sp->open (&prop, &data);
243   EXPECT_EQ (ret, 0);
244
245   /* invoke successful */
246   ret = sp->invoke (NULL, &prop, data, &input, &output);
247   EXPECT_EQ (ret, 0);
248
249   g_free (input.data);
250   g_free (output.data);
251
252   sp->close (&prop, &data);
253 }
254
255 /**
256  * @brief Negative case with invalid input/output
257  */
258 TEST (nnstreamerFilterOnnxRuntime, invoke01_n)
259 {
260   int ret;
261   void *data = NULL;
262   GstTensorMemory input, output;
263   g_autofree gchar *model_file = NULL;
264
265   ASSERT_TRUE (_GetModelFilePath (&model_file));
266
267   const gchar *model_files[] = {
268     model_file,
269     NULL,
270   };
271
272   const GstTensorFilterFramework *sp = nnstreamer_filter_find ("onnxruntime");
273   ASSERT_TRUE (sp != nullptr);
274
275   GstTensorFilterProperties prop;
276   _SetFilterProp (&prop, "onnxruntime", model_files);
277
278   output.size = input.size = sizeof (float) * 1;
279   input.data = g_malloc0 (input.size);
280   output.data = g_malloc0 (output.size);
281
282   ret = sp->open (&prop, &data);
283   EXPECT_EQ (ret, 0);
284
285   /* catching exception */
286   EXPECT_NE (sp->invoke (NULL, &prop, data, NULL, &output), 0);
287   EXPECT_NE (sp->invoke (NULL, &prop, data, &input, NULL), 0);
288
289   g_free (input.data);
290   g_free (output.data);
291   sp->close (&prop, &data);
292 }
293
294 /**
295  * @brief Negative case to launch gst pipeline: wrong dimension
296  */
297 TEST (nnstreamerFilterOnnxRuntime, launch00_n)
298 {
299   GstElement *gstpipe;
300   GError *err = NULL;
301   g_autofree gchar *model_file = NULL;
302
303   ASSERT_TRUE (_GetModelFilePath (&model_file));
304
305   /* create a nnstreamer pipeline */
306   g_autofree gchar *pipeline = g_strdup_printf (
307       "videotestsrc num-buffers=10 ! videoconvert ! videoscale ! video/x-raw,format=RGB,width=42,height=42,framerate=0/1 ! tensor_converter ! tensor_filter framework=onnxruntime model=\"%s\" latency=1 ! tensor_sink",
308       model_file);
309
310   gstpipe = gst_parse_launch (pipeline, &err);
311   ASSERT_TRUE (gstpipe != nullptr);
312
313   EXPECT_NE (setPipelineStateSync (gstpipe, GST_STATE_PLAYING, UNITTEST_STATECHANGE_TIMEOUT), 0);
314
315   gst_object_unref (gstpipe);
316 }
317
318 /**
319  * @brief Negative case to launch gst pipeline: wrong data type
320  */
321 TEST (nnstreamerFilterOnnxRuntime, launch01_n)
322 {
323   GstElement *gstpipe;
324   GError *err = NULL;
325   g_autofree gchar *model_file = NULL;
326
327   ASSERT_TRUE (_GetModelFilePath (&model_file));
328
329   /* create a nnstreamer pipeline */
330   g_autofree gchar *pipeline = g_strdup_printf (
331       "videotestsrc num-buffers=10 ! videoconvert ! videoscale ! video/x-raw,format=RGB,width=224,height=224,framerate=0/1 ! tensor_converter ! tensor_filter framework=onnxruntime model=\"%s\" latency=1 ! tensor_sink",
332       model_file);
333
334   gstpipe = gst_parse_launch (pipeline, &err);
335   ASSERT_TRUE (gstpipe != nullptr);
336
337   EXPECT_NE (setPipelineStateSync (gstpipe, GST_STATE_PLAYING, UNITTEST_STATECHANGE_TIMEOUT), 0);
338
339   gst_object_unref (gstpipe);
340 }
341
342 /**
343  * @brief Positive case to launch gst pipeline
344  */
345 TEST (nnstreamerFilterOnnxRuntime, floatModelResult)
346 {
347   GstElement *gstpipe;
348   GError *err = NULL;
349   g_autofree gchar *model_file = NULL;
350   g_autofree gchar *input_file = NULL;
351
352   ASSERT_TRUE (_GetModelFilePath (&model_file));
353   ASSERT_TRUE (_GetOrangePngFilePath (&input_file));
354
355   /* create a nnstreamer pipeline */
356   g_autofree gchar *pipeline = g_strdup_printf (
357       "filesrc location=\"%s\" ! pngdec ! videoconvert ! videoscale ! video/x-raw,format=RGB,width=224,height=224,framerate=0/1 ! tensor_converter ! tensor_transform mode=transpose option=1:2:0:3 ! tensor_transform mode=arithmetic option=typecast:float32,div:127.5,add:-1.0 ! tensor_filter framework=onnxruntime model=\"%s\" ! tensor_sink name=sink",
358       input_file, model_file);
359
360   gstpipe = gst_parse_launch (pipeline, &err);
361   ASSERT_TRUE (gstpipe != nullptr);
362
363   GstElement *sink_handle = gst_bin_get_by_name (GST_BIN (gstpipe), "sink");
364
365   ASSERT_TRUE (sink_handle != nullptr);
366
367   guint8 is_float = 1;
368
369   g_signal_connect (sink_handle, "new-data", (GCallback) check_output, &is_float);
370
371   EXPECT_EQ (setPipelineStateSync (gstpipe, GST_STATE_PLAYING, UNITTEST_STATECHANGE_TIMEOUT * 10),
372       0);
373
374   EXPECT_EQ (setPipelineStateSync (gstpipe, GST_STATE_NULL, UNITTEST_STATECHANGE_TIMEOUT), 0);
375
376   gst_object_unref (sink_handle);
377   gst_object_unref (gstpipe);
378 }
379
380 /**
381  * @brief Negative case with incorrect path
382  */
383 TEST (nnstreamerFilterOnnxRuntime, error00_n)
384 {
385   GstElement *gstpipe;
386   GError *err = NULL;
387   int status = 0;
388   const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");
389   g_autofree gchar *model_file = g_build_filename (
390       root_path, "tests", "test_models", "models", "incorrect_path.onnx", NULL);
391
392   /* Create a nnstreamer pipeline */
393   g_autofree gchar *pipeline = g_strdup_printf (
394       "videotestsrc ! videoconvert ! videoscale ! videorate ! video/x-raw,format=RGB,width=224,height=224 ! tensor_converter ! tensor_filter framework=onnxruntime model=\"%s\" ! fakesink",
395       model_file);
396   gstpipe = gst_parse_launch (pipeline, &err);
397
398   if (gstpipe) {
399     EXPECT_NE (gst_element_set_state (gstpipe, GST_STATE_PLAYING), GST_STATE_CHANGE_SUCCESS);
400     EXPECT_EQ (gst_element_set_state (gstpipe, GST_STATE_PLAYING), GST_STATE_CHANGE_FAILURE);
401     g_usleep (500000);
402     EXPECT_NE (gst_element_set_state (gstpipe, GST_STATE_NULL), GST_STATE_CHANGE_FAILURE);
403     g_usleep (100000);
404
405     gst_object_unref (gstpipe);
406   } else {
407     status = -1;
408     ml_loge ("GST PARSE LAUNCH FAILED: [%s], %s\n", pipeline,
409         (err) ? err->message : "unknown reason");
410     g_clear_error (&err);
411   }
412   EXPECT_EQ (status, 0);
413 }
414
415 /**
416  * @brief Negative case with incorrect tensor meta
417  */
418 TEST (nnstreamerFilterOnnxRuntime, error01_n)
419 {
420   GstElement *gstpipe;
421   GError *err = NULL;
422   int status = 0;
423   g_autofree gchar *model_file = NULL;
424
425   ASSERT_TRUE (_GetModelFilePath (&model_file));
426
427   /* Create a nnstreamer pipeline */
428   g_autofree gchar *pipeline = g_strdup_printf (
429       "videotestsrc ! videoconvert ! videoscale ! videorate ! video/x-raw,format=RGB,width=240,height=224 ! tensor_converter ! tensor_filter framework=onnxruntime model=\"%s\" ! fakesink",
430       model_file);
431
432   gstpipe = gst_parse_launch (pipeline, &err);
433   if (gstpipe) {
434     GstState state, pending;
435
436     EXPECT_NE (gst_element_set_state (gstpipe, GST_STATE_PLAYING), GST_STATE_CHANGE_SUCCESS);
437     g_usleep (500000);
438     /* This should fail: dimension mismatched. */
439     EXPECT_EQ (gst_element_get_state (gstpipe, &state, &pending, GST_SECOND / 4),
440         GST_STATE_CHANGE_FAILURE);
441
442     EXPECT_NE (gst_element_set_state (gstpipe, GST_STATE_NULL), GST_STATE_CHANGE_FAILURE);
443     g_usleep (100000);
444
445     gst_object_unref (gstpipe);
446   } else {
447     status = -1;
448     ml_loge ("GST PARSE LAUNCH FAILED: [%s], %s\n", pipeline,
449         (err) ? err->message : "unknown reason");
450     g_clear_error (&err);
451   }
452   EXPECT_EQ (status, 0);
453 }
454
455 /**
456  * @brief Main GTest
457  */
458 int
459 main (int argc, char **argv)
460 {
461   int result = -1;
462
463   try {
464     testing::InitGoogleTest (&argc, argv);
465   } catch (...) {
466     g_warning ("catch 'testing::internal::<unnamed>::ClassUniqueToAlwaysTrue'");
467   }
468
469   gst_init (&argc, &argv);
470
471   /* Force the binary to use dlog_print of untitest-util by calling it directly */
472   ml_logd ("onnxruntime test starts w/ dummy backend.");
473
474   try {
475     result = RUN_ALL_TESTS ();
476   } catch (...) {
477     g_warning ("catch `testing::internal::GoogleTestFailureException`");
478   }
479
480   return result;
481 }