[Converter] Add custom python converter testcases
authorGichan Jang <gichan2.jang@samsung.com>
Wed, 26 May 2021 09:52:21 +0000 (18:52 +0900)
committerMyungJoo Ham <myungjoo.ham@samsung.com>
Thu, 10 Jun 2021 07:07:33 +0000 (16:07 +0900)
Add custom python converter testcases.
 1. Test dynamic dimension
 2. Test custom python conveter using json

Signed-off-by: Gichan Jang <gichan2.jang@samsung.com>
ext/nnstreamer/tensor_converter/tensor_converter_python3.cc
gst/nnstreamer/tensor_converter/tensor_converter.c
packaging/run_unittests_binaries.sh
tests/meson.build
tests/nnstreamer_converter/unittest_converter.cc
tests/nnstreamer_plugins/unittest_plugins.cc
tests/test_models/data/example.json [new file with mode: 0644]
tests/test_models/models/custom_converter_json.py [new file with mode: 0644]
tests/test_models/models/custom_converter_test.py [new file with mode: 0644]
tests/test_models/models/invalid_class_custom_converter.py [new file with mode: 0644]

index efe2c03..bf30ee6 100644 (file)
@@ -365,18 +365,28 @@ extern "C" {
 void
 init_converter_py (void)
 {
-  registerExternalConverter (&Python);
   /** Python should be initialized and finalized only once */
-  Py_Initialize ();
+  if (!Py_IsInitialized()) {
+    Py_Initialize ();
+  }
+  registerExternalConverter (&Python);
 }
 
 /** @brief Destruct this object for tensor converter sub-plugin */
 void
 fini_converter_py (void)
 {
-  /** Python should be initialized and finalized only once */
-  Py_Finalize ();
   unregisterExternalConverter (Python.name);
+  /** Python should be initialized and finalized only once */
+  if (Py_IsInitialized()) {
+    /**
+     * @todo: There is a crash problem between Python C-API and numpy.
+     *        If this problem is solved, let's remove the sleep time.
+     * related issue: https://github.com/numpy/numpy/issues/8097
+     */
+    g_usleep (100000);
+    Py_Finalize ();
+  }
 }
 #ifdef __cplusplus
 }
index 4b02a8c..5a6c5c2 100644 (file)
@@ -617,9 +617,12 @@ gst_tensor_converter_sink_event (GstPad * pad, GstObject * parent,
       silent_debug_caps (in_caps, "in-caps");
 
       if (gst_tensor_converter_parse_caps (self, in_caps)) {
-        gst_event_unref (event);
         gst_tensor_converter_update_caps (self);
+        gst_event_unref (event);
         return TRUE;
+      } else {
+        gst_event_unref (event);
+        return FALSE;
       }
       break;
     }
@@ -2052,6 +2055,7 @@ findExternalConverter (const char *media_type)
 
       if (g_strcmp0 (media_type, str_array[i]) == 0) {
         /* found matched media type */
+        g_strfreev (str_array);
         return ex;
       }
 
index fd2323e..6649b31 100755 (executable)
@@ -19,9 +19,9 @@ export _PYTHONPATH=${PYTHONPATH}
 
 run_entry() {
   entry=$1
-  if [[ $entry == *"python3"* || $entry == *"python2"* ]]; then
-    PY=$(echo ${entry} | grep -oP "python[0-9]")
-    pushd ext/nnstreamer/tensor_filter
+  if [[ $entry == *"python3"* || $entry == *"unittest_converter"* ]]; then
+    PY="python3"
+    pushd ext/nnstreamer/extra
     TEST_PYTHONPATH=${PY}_module
     rm -rf ${TEST_PYTHONPATH}
     mkdir -p ${TEST_PYTHONPATH}
@@ -30,7 +30,7 @@ run_entry() {
     TEST_PYTHONPATH=$(pwd)
     export PYTHONPATH=${TEST_PYTHONPATH}
     if [[ ! -f ${TEST_PYTHONPATH}/nnstreamer_python.so ]]; then
-      ln -sf ../../extra/nnstreamer_${PY}.so nnstreamer_python.so
+      ln -sf ../nnstreamer_${PY}.so nnstreamer_python.so
     fi
     popd
     popd
index 2ac6fcd..6780dc5 100644 (file)
@@ -143,7 +143,7 @@ if gtest_dep.found()
     if flatbuf_support_is_available
       unittest_converter = executable('unittest_converter',
         join_paths('nnstreamer_converter', 'unittest_converter.cc'),
-        dependencies: [nnstreamer_unittest_deps, unittest_util_dep, flatbuf_dep],
+        dependencies: [nnstreamer_unittest_deps, unittest_util_dep, flatbuf_dep, nnstreamer_python3_helper_dep],
         install: get_option('install-test'),
         install_dir: unittest_install_dir
       )
index 063ed14..67ab8ef 100644 (file)
@@ -14,6 +14,7 @@
 #include <unittest_util.h>\r
 #include <tensor_converter_custom.h>\r
 #include <flatbuffers/flexbuffers.h>\r
+#include <gst/app/gstappsrc.h>\r
 #include <nnstreamer_plugin_api_converter.h>\r
 \r
 #define TEST_TIMEOUT_MS (1000U)\r
@@ -332,6 +333,385 @@ TEST (tensorConverter, subpluginFindInvalidParam_n)
 }\r
 \r
 /**\r
+ * @brief Test data for tensor_conveter::flexbuf (dimension 24:1:1:1)\r
+ */\r
+const gint _test_frames1[24]\r
+    = { 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112,\r
+            1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124};\r
+\r
+/**\r
+ * @brief Test data for tensor_conveter::flexbuf  (dimension 48:1:1:1)\r
+ */\r
+const gint _test_frames2[48]\r
+    = { 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112,\r
+            1113, 1114, 1115, 1116, 1117, 1118, 1119, 1120, 1121, 1122, 1123, 1124,\r
+            1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212,\r
+            1213, 1214, 1215, 1216, 1217, 1218, 1219, 1220, 1221, 1222, 1223, 1224 };\r
+\r
+/**\r
+ * @brief Callback for tensor sink signal.\r
+ */\r
+static void\r
+new_data_cb (GstElement *element, GstBuffer *buffer, gpointer user_data)\r
+{\r
+  (*(guint *)user_data)++;\r
+}\r
+\r
+/**\r
+ * @brief Test for dynamic dimension of the custom converter\r
+ */\r
+TEST (tensorConverterPython, dynamicDimension)\r
+{\r
+  GstBuffer *buf_0, *buf_1, *buf_2;\r
+  GstElement *appsrc_handle, *sink_handle;\r
+  const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");\r
+  gchar *test_model;\r
+  GstPad *sink_pad;\r
+  GstCaps *caps;\r
+  GstStructure *structure;\r
+  GstTensorConfig config;\r
+  guint *data_received = NULL;\r
+\r
+  /** supposed to run test in build directory */\r
+  if (root_path == NULL)\r
+    root_path = "..";\r
+\r
+  test_model = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "custom_converter_test.py", NULL);\r
+\r
+  gchar *str_pipeline = g_strdup_printf ("appsrc name=srcx ! application/octet-stream ! tensor_converter silent=false mode=custom-script:%s ! tensor_sink name=sinkx async=false", test_model);\r
+\r
+  GstElement *pipeline = gst_parse_launch (str_pipeline, NULL);\r
+  g_free (test_model);\r
+  g_free (str_pipeline);\r
+  ASSERT_NE (pipeline, nullptr);\r
+\r
+  appsrc_handle = gst_bin_get_by_name (GST_BIN (pipeline), "srcx");\r
+  EXPECT_NE (appsrc_handle, nullptr);\r
+\r
+  sink_handle = gst_bin_get_by_name (GST_BIN (pipeline), "sinkx");\r
+  EXPECT_NE (sink_handle, nullptr);\r
+\r
+  data_received = (guint *) g_malloc0 (sizeof (guint));\r
+  g_signal_connect (sink_handle, "new-data", (GCallback)new_data_cb, data_received);\r
+\r
+  buf_0 = gst_buffer_new_wrapped (g_memdup (_test_frames1, 24), 24);\r
+  buf_1 = gst_buffer_new_wrapped (g_memdup (_test_frames2, 48), 48);\r
+  buf_2 = gst_buffer_copy (buf_0);\r
+\r
+  EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_PLAYING, UNITTEST_STATECHANGE_TIMEOUT), 0);\r
+  EXPECT_EQ (gst_app_src_push_buffer (GST_APP_SRC (appsrc_handle), buf_0), GST_FLOW_OK);\r
+  EXPECT_TRUE (wait_pipeline_process_buffers (data_received, 1, TEST_TIMEOUT_MS));\r
+\r
+  sink_pad = gst_element_get_static_pad (sink_handle, "sink");\r
+  EXPECT_NE (sink_pad, nullptr);\r
+  caps = gst_pad_get_current_caps (sink_pad);\r
+  EXPECT_NE (caps, nullptr);\r
+  structure = gst_caps_get_structure (caps, 0);\r
+  EXPECT_NE (structure, nullptr);\r
+  gst_tensor_config_from_structure (&config, structure);\r
+\r
+  EXPECT_EQ (24, config.info.dimension[0]);\r
+  EXPECT_EQ (1, config.info.dimension[1]);\r
+  EXPECT_EQ (1, config.info.dimension[2]);\r
+  EXPECT_EQ (1, config.info.dimension[3]);\r
+\r
+  gst_caps_unref (caps);\r
+\r
+  EXPECT_EQ (gst_app_src_push_buffer (GST_APP_SRC (appsrc_handle), buf_1), GST_FLOW_OK);\r
+  EXPECT_TRUE (wait_pipeline_process_buffers (data_received, 2, TEST_TIMEOUT_MS));\r
+\r
+  caps = gst_pad_get_current_caps (sink_pad);\r
+  EXPECT_NE (caps, nullptr);\r
+  structure = gst_caps_get_structure (caps, 0);\r
+  EXPECT_NE (structure, nullptr);\r
+  gst_tensor_config_from_structure (&config, structure);\r
+\r
+  EXPECT_EQ (48, config.info.dimension[0]);\r
+  EXPECT_EQ (1, config.info.dimension[1]);\r
+  EXPECT_EQ (1, config.info.dimension[2]);\r
+  EXPECT_EQ (1, config.info.dimension[3]);\r
+  gst_caps_unref (caps);\r
+\r
+  EXPECT_EQ (gst_app_src_push_buffer (GST_APP_SRC (appsrc_handle), buf_2), GST_FLOW_OK);\r
+  EXPECT_TRUE (wait_pipeline_process_buffers (data_received, 3, TEST_TIMEOUT_MS));\r
+\r
+  sink_pad = gst_element_get_static_pad (sink_handle, "sink");\r
+  EXPECT_NE (sink_pad, nullptr);\r
+  caps = gst_pad_get_current_caps (sink_pad);\r
+  EXPECT_NE (caps, nullptr);\r
+  structure = gst_caps_get_structure (caps, 0);\r
+  EXPECT_NE (structure, nullptr);\r
+  gst_tensor_config_from_structure (&config, structure);\r
+\r
+  EXPECT_EQ (24, config.info.dimension[0]);\r
+  EXPECT_EQ (1, config.info.dimension[1]);\r
+  EXPECT_EQ (1, config.info.dimension[2]);\r
+  EXPECT_EQ (1, config.info.dimension[3]);\r
+  gst_caps_unref (caps);\r
+\r
+  EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_NULL, UNITTEST_STATECHANGE_TIMEOUT), 0);\r
+  g_usleep (100000);\r
+\r
+  EXPECT_EQ (3, *data_received);\r
+  g_free (data_received);\r
+  gst_object_unref (sink_handle);\r
+  gst_object_unref (appsrc_handle);\r
+  gst_object_unref (pipeline);\r
+}\r
+\r
+/**\r
+ * @brief Callback for tensor sink signal.\r
+ */\r
+static void\r
+new_data_cb_json (GstElement *element, GstBuffer *buffer, gpointer user_data)\r
+{\r
+  guint num_tensors;\r
+  GstMapInfo info_res;\r
+  gboolean mapped;\r
+  GstMemory *mem_res;\r
+  gchar * output;\r
+  (*(guint *)user_data)++;\r
+\r
+  num_tensors = gst_buffer_n_memory (buffer);\r
+  EXPECT_EQ (2U, num_tensors);\r
+\r
+  mem_res = gst_buffer_get_memory (buffer, 0);\r
+  mapped = gst_memory_map (mem_res, &info_res, GST_MAP_READ);\r
+  ASSERT_TRUE (mapped);\r
+  output = (gchar *)info_res.data;\r
+  EXPECT_STREQ ("string_example", output);\r
+  gst_memory_unmap (mem_res, &info_res);\r
+  gst_memory_unref (mem_res);\r
+}\r
+\r
+/**\r
+ * @brief Test for python json parser of the custom converter\r
+ */\r
+TEST (tensorConverterPython, jsonParser)\r
+{\r
+  GstElement *sink_handle;\r
+  const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");\r
+  gchar *test_model, *data_file;\r
+  guint *data_received = NULL;\r
+  /** supposed to run test in build directory */\r
+  if (root_path == NULL)\r
+    root_path = "..";\r
+\r
+  test_model = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "custom_converter_json.py", NULL);\r
+  data_file = g_build_filename (root_path, "tests", "test_models", "data",\r
+      "example.json", NULL);\r
+\r
+  gchar *str_pipeline = g_strdup_printf ("filesrc location=%s ! application/octet-stream ! tensor_converter silent=false mode=custom-script:%s ! tensor_sink name=sinkx", data_file, test_model);\r
+\r
+  GstElement *pipeline = gst_parse_launch (str_pipeline, NULL);\r
+  g_free (data_file);\r
+  g_free (test_model);\r
+  g_free (str_pipeline);\r
+  ASSERT_NE (pipeline, nullptr);\r
+\r
+  sink_handle = gst_bin_get_by_name (GST_BIN (pipeline), "sinkx");\r
+  EXPECT_NE (sink_handle, nullptr);\r
+\r
+  data_received = (guint *) g_malloc0 (sizeof (guint));\r
+  g_signal_connect (sink_handle, "new-data", (GCallback)new_data_cb_json, data_received);\r
+\r
+  EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_PLAYING, UNITTEST_STATECHANGE_TIMEOUT), 0);\r
+  g_usleep (100000);\r
+\r
+  EXPECT_EQ (setPipelineStateSync (pipeline, GST_STATE_NULL, UNITTEST_STATECHANGE_TIMEOUT), 0);\r
+  g_usleep (100000);\r
+\r
+  EXPECT_EQ (1U, *data_received);\r
+\r
+  g_free (data_received);\r
+  gst_object_unref (sink_handle);\r
+  gst_object_unref (pipeline);\r
+}\r
+\r
+/**\r
+ * @brief Test for python custom converter with invalid param\r
+ */\r
+TEST (tensorConverterPython, openTwice)\r
+{\r
+  const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");\r
+  const NNStreamerExternalConverter *ex;\r
+  void *py_core = NULL;\r
+  gchar *test_model1, *test_model2;\r
+\r
+  /** supposed to run test in build directory */\r
+  if (root_path == NULL)\r
+    root_path = "..";\r
+\r
+  ex = nnstreamer_converter_find ("python3");\r
+  ASSERT_NE (nullptr, ex);\r
+\r
+  test_model1 = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "custom_converter_test.py", NULL);\r
+  test_model2 = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "custom_converter_json.py", NULL);\r
+\r
+  EXPECT_EQ (0, ex->open (test_model1, &py_core));\r
+  /** Open with same python script */\r
+  EXPECT_EQ (1, ex->open (test_model1, &py_core));\r
+  /** Open with different python script */\r
+  EXPECT_EQ (0, ex->open (test_model2, &py_core));\r
+\r
+  ex->close (&py_core);\r
+  g_free (test_model1);\r
+  g_free (test_model2);\r
+}\r
+\r
+/**\r
+ * @brief Test for python custom converter with invalid param\r
+ */\r
+TEST (tensorConverterPython, invalidParam_n)\r
+{\r
+  const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");\r
+  gchar *test_model, *data_file;\r
+  GstStateChangeReturn ret;\r
+\r
+  /** supposed to run test in build directory */\r
+  if (root_path == NULL)\r
+    root_path = "..";\r
+\r
+  test_model = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "NOT_EXIST.py", NULL);\r
+  data_file = g_build_filename (root_path, "tests", "test_models", "data",\r
+      "example.json", NULL);\r
+\r
+  gchar *str_pipeline = g_strdup_printf ("filesrc location=%s blocksize=-1 ! application/octet-stream ! tensor_converter silent=false mode=custom-script:%s ! tensor_sink name=sinkx", data_file, test_model);\r
+\r
+  GstElement *pipeline = gst_parse_launch (str_pipeline, NULL);\r
+  g_free (test_model);\r
+  g_free (data_file);\r
+  g_free (str_pipeline);\r
+  ASSERT_NE (pipeline, nullptr);\r
+\r
+  gst_element_set_state (pipeline, GST_STATE_PLAYING);\r
+  g_usleep (100000);\r
+  ret = gst_element_get_state (pipeline, NULL, NULL, GST_CLOCK_TIME_NONE);\r
+  EXPECT_TRUE (ret == GST_STATE_CHANGE_FAILURE);\r
+\r
+  gst_object_unref (pipeline);\r
+}\r
+\r
+/**\r
+ * @brief Test for python custom converter with invalid param\r
+ */\r
+TEST (tensorConverterPython, invalidParam2_n)\r
+{\r
+  const NNStreamerExternalConverter *ex;\r
+  GstTensorsConfig config;\r
+\r
+  gst_tensors_config_init (&config);\r
+  ex = nnstreamer_converter_find ("python3");\r
+  ASSERT_NE (nullptr, ex);\r
+\r
+  EXPECT_EQ (FALSE, ex->get_out_config (NULL, &config));\r
+}\r
+\r
+/**\r
+ * @brief Test for python custom converter with invalid param\r
+ */\r
+TEST (tensorConverterPython, invalidParam3_n)\r
+{\r
+  const NNStreamerExternalConverter *ex;\r
+  GstCaps *caps;\r
+\r
+  caps = gst_caps_from_string ("application/octet-stream");\r
+  ex = nnstreamer_converter_find ("python3");\r
+  ASSERT_NE (nullptr, ex);\r
+\r
+  EXPECT_EQ (FALSE, ex->get_out_config (caps, NULL));\r
+  g_usleep (100000);\r
+  gst_caps_unref (caps);\r
+}\r
+\r
+/**\r
+ * @brief Test for python custom converter with invalid param\r
+ */\r
+TEST (tensorConverterPython, invalidParam4_n)\r
+{\r
+  const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");\r
+  const NNStreamerExternalConverter *ex;\r
+  void *py_core = NULL;\r
+  GstTensorsConfig config;\r
+  gchar *test_model;\r
+\r
+  gst_tensors_config_init (&config);\r
+  /** supposed to run test in build directory */\r
+  if (root_path == NULL)\r
+    root_path = "..";\r
+\r
+  ex = nnstreamer_converter_find ("python3");\r
+  ASSERT_NE (nullptr, ex);\r
+\r
+  test_model = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "custom_converter_test.py", NULL);\r
+\r
+  EXPECT_EQ (0, ex->open (test_model, &py_core));\r
+  EXPECT_FALSE (ex->convert (NULL, &config, py_core));\r
+\r
+  ex->close (&py_core);\r
+  g_free (test_model);\r
+}\r
+\r
+/**\r
+ * @brief Test for python custom converter with invalid param\r
+ */\r
+TEST (tensorConverterPython, invalidParam5_n)\r
+{\r
+  const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");\r
+  const NNStreamerExternalConverter *ex;\r
+  void *py_core = NULL;\r
+  gchar *test_model;\r
+  GstBuffer *buf;\r
+\r
+  /** supposed to run test in build directory */\r
+  if (root_path == NULL)\r
+    root_path = "..";\r
+\r
+  ex = nnstreamer_converter_find ("python3");\r
+  ASSERT_NE (nullptr, ex);\r
+\r
+  test_model = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "custom_converter_test.py", NULL);\r
+  buf = gst_buffer_new ();\r
+  EXPECT_EQ (0, ex->open (test_model, &py_core));\r
+  EXPECT_FALSE (ex->convert (buf, NULL, py_core));\r
+\r
+  ex->close (&py_core);\r
+  g_free (test_model);\r
+  gst_buffer_unref (buf);\r
+}\r
+\r
+/**\r
+ * @brief Test for python custom converter with invalid param\r
+ */\r
+TEST (tensorConverterPython, invalidParam6_n)\r
+{\r
+  const gchar *root_path = g_getenv ("NNSTREAMER_SOURCE_ROOT_PATH");\r
+  const NNStreamerExternalConverter *ex;\r
+  void *py_core = NULL;\r
+  gchar *test_model;\r
+\r
+  /** supposed to run test in build directory */\r
+  if (root_path == NULL)\r
+    root_path = "..";\r
+\r
+  ex = nnstreamer_converter_find ("python3");\r
+  ASSERT_NE (nullptr, ex);\r
+\r
+  test_model = g_build_filename (root_path, "tests", "test_models", "models",\r
+      "invalid_class_custom_converter.py", NULL);\r
+  EXPECT_NE (0, ex->open (test_model, &py_core));\r
+\r
+  g_free (test_model);\r
+}\r
+\r
+/**\r
  * @brief Main GTest\r
  */\r
 int\r
index d431536..046a0bd 100644 (file)
@@ -2551,7 +2551,7 @@ TEST (testTensorConverter, bytesToMultiInvalidDim01_n)
 
   /* push buffers */
   in_buf = gst_harness_create_buffer (h, data_size);
-  EXPECT_DEATH (gst_harness_push (h, in_buf), "");
+  EXPECT_EQ (GST_FLOW_NOT_NEGOTIATED, gst_harness_push (h, in_buf));
 
   EXPECT_EQ (gst_harness_buffers_received (h), 0U);
   gst_harness_teardown (h);
@@ -2591,7 +2591,7 @@ TEST (testTensorConverter, bytesToMultiInvalidDim02_n)
 
   /* push buffers */
   in_buf = gst_harness_create_buffer (h, data_size);
-  EXPECT_DEATH (gst_harness_push (h, in_buf), "");
+  EXPECT_EQ (GST_FLOW_NOT_NEGOTIATED, gst_harness_push (h, in_buf));
 
   EXPECT_EQ (gst_harness_buffers_received (h), 0U);
   gst_harness_teardown (h);
@@ -2630,7 +2630,7 @@ TEST (testTensorConverter, bytesToMultiInvalidType01_n)
 
   /* push buffers */
   in_buf = gst_harness_create_buffer (h, data_size);
-  EXPECT_DEATH (gst_harness_push (h, in_buf), "");
+  EXPECT_EQ (GST_FLOW_NOT_NEGOTIATED, gst_harness_push (h, in_buf));
 
   EXPECT_EQ (gst_harness_buffers_received (h), 0U);
   gst_harness_teardown (h);
@@ -2670,7 +2670,7 @@ TEST (testTensorConverter, bytesToMultiInvalidType02_n)
 
   /* push buffers */
   in_buf = gst_harness_create_buffer (h, data_size);
-  EXPECT_DEATH (gst_harness_push (h, in_buf), "");
+  EXPECT_EQ (GST_FLOW_NOT_NEGOTIATED, gst_harness_push (h, in_buf));
 
   EXPECT_EQ (gst_harness_buffers_received (h), 0U);
   gst_harness_teardown (h);
@@ -2792,7 +2792,7 @@ TEST (testTensorConverter, bytesToMultiInvalidFrames_n)
 
   /* push buffers */
   in_buf = gst_harness_create_buffer (h, data_size);
-  EXPECT_DEATH (gst_harness_push (h, in_buf), "");
+  EXPECT_EQ (GST_FLOW_NOT_NEGOTIATED, gst_harness_push (h, in_buf));
 
   EXPECT_EQ (gst_harness_buffers_received (h), 0U);
   gst_harness_teardown (h);
diff --git a/tests/test_models/data/example.json b/tests/test_models/data/example.json
new file mode 100644 (file)
index 0000000..59f4cef
--- /dev/null
@@ -0,0 +1,7 @@
+{\r
+    "json_string": "string_example",\r
+    "json_number": 100,\r
+    "json_array": [1, 2, 3, 4, 5],\r
+    "json_object": { "name":"John", "age":30},\r
+    "json_bool": true\r
+}\r
diff --git a/tests/test_models/models/custom_converter_json.py b/tests/test_models/models/custom_converter_json.py
new file mode 100644 (file)
index 0000000..d971bcb
--- /dev/null
@@ -0,0 +1,46 @@
+##
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Copyright (C) 2021 Samsung Electronics
+#
+# @file    custom_converter_json.py
+# @brief   Python custom converter
+# @author  Gichan Jang <gichan2.jang@samsung.com>
+
+import numpy as np
+import nnstreamer_python as nns
+import json
+
+## @brief  User-defined custom converter
+class CustomConverter(object):
+
+## @breif  Python callback: convert
+## input:
+# {
+#     "json_string": "string_example",
+#     "json_number": 100,
+#     "json_array": [1, 2, 3, 4, 5],
+#     "json_object": { "name":"John", "age":30},
+#     "json_bool": true
+# }
+## output:
+# tensor 0 : "string_example"
+# tensor 1 : "{ "name":"John", "age":30}""
+
+  def convert (self, input_array):
+    json_data = json.loads(input_array[0].tobytes())
+    json_string = json_data["json_string"].encode()
+    json_object = json.dumps(json_data["json_object"]).encode()
+
+    output_array1 = np.frombuffer (json_string, dtype=np.uint8)
+    output_array2 = np.frombuffer (json_object, dtype=np.uint8)
+    raw_data = [output_array1, output_array2]
+
+    rate_n = 10
+    rate_d = 1
+    dim1 = [len (raw_data[0]), 1, 1, 1]
+    dim2 = [len (raw_data[1]), 1, 1, 1]
+    ttype=np.uint8
+    tensors_info = [nns.TensorShape(dim1, ttype), nns.TensorShape(dim2, ttype)]
+
+    return (tensors_info, raw_data, rate_n, rate_d)
diff --git a/tests/test_models/models/custom_converter_test.py b/tests/test_models/models/custom_converter_test.py
new file mode 100644 (file)
index 0000000..230624a
--- /dev/null
@@ -0,0 +1,25 @@
+##
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Copyright (C) 2021 Samsung Electronics
+#
+# @file    custom_converter_test.py
+# @brief   Python custom converter
+# @author  Gichan Jang <gichan2.jang@samsung.com>
+
+import numpy as np
+import nnstreamer_python as nns
+
+
+## @brief  User-defined custom converter
+class CustomConverter(object):
+
+## @breif  Python callback: convert
+  def convert (self, input_array):
+    rate_n = 10
+    rate_d = 1
+    dim = [len (input_array[0]), 1, 1, 1]
+    ttype=np.int32
+    tensors_info = [nns.TensorShape(dim, ttype)]
+
+    return (tensors_info, input_array, rate_n, rate_d)
diff --git a/tests/test_models/models/invalid_class_custom_converter.py b/tests/test_models/models/invalid_class_custom_converter.py
new file mode 100644 (file)
index 0000000..3a6cedd
--- /dev/null
@@ -0,0 +1,25 @@
+##
+# SPDX-License-Identifier: LGPL-2.1-only
+#
+# Copyright (C) 2021 Samsung Electronics
+#
+# @file    invalid_class_custom_converter.py
+# @brief   Python custom converter
+# @author  Gichan Jang <gichan2.jang@samsung.com>
+
+import numpy as np
+import nnstreamer_python as nns
+
+
+## @brief  User-defined custom converter
+class InvalidClass(object):
+
+## @breif  Python callback: convert
+  def convert (self, input_array):
+    rate_n = 10
+    rate_d = 1
+    dim = [len (input_array[0]), 1, 1, 1]
+    ttype=np.int32
+    tensors_info = [nns.TensorShape(dim, ttype)]
+
+    return (tensors_info, input_array, rate_n, rate_d)