This patch adds test cases for unit testing of parsing the MLAgent URIs.
Signed-off-by: Wook Song <wook16.song@samsung.com>
subdir('nnstreamer_datarepo')
endif
+ if ml_agent_support_is_available
+ ml_agent_lib_common_objs = nnstreamer_single_lib.extract_objects('ml_agent.c')
+
+ lib_mlagent_mock = static_library('mock_mlagentmock',
+ join_paths('unittest_mlagent', 'mock_mlagent.cc'),
+ dependencies: [glib_dep, json_glib_dep],
+ include_directories: nnstreamer_inc,
+ install: get_option('install-test'),
+ install_dir: nnstreamer_libdir
+ )
+
+ whole_dep = declare_dependency(link_whole: lib_mlagent_mock)
+
+ filter_mlagent = executable('unittest_mlagent',
+ join_paths('unittest_mlagent', 'unittest_mlagent.cc'),
+ objects:ml_agent_lib_common_objs,
+ dependencies: [nnstreamer_unittest_deps, nnstreamer_internal_deps, whole_dep],
+ )
+ endif
+
# ONNXRUNTIME unittest
if onnxruntime_support_is_available
unittest_filter_onnxruntime = executable('unittest_filter_onnxruntime',
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-only */
+/**
+ * @file mlagent_mock.cc
+ * @date 30 Nov 2023
+ * @brief A class that mocks the ML Agent instance
+ * @author Wook Song <wook16.song@samsung.com>
+ * @see http://github.com/nnstreamer/nnstreamer
+ * @bug No known bugs
+ *
+ */
+
+#include <glib.h>
+#include <json-glib/json-glib.h>
+#include <nnstreamer_util.h>
+
+#include <functional>
+#include <iostream>
+#include <memory>
+
+#include "mock_mlagent.h"
+
+static std::unique_ptr<MockMLAgent> uptr_mock;
+
+/**
+ * @brief Generate C-stringified JSON
+ */
+gchar *
+MockModel::to_cstr_json ()
+{
+ using json_member_name_to_cb_t
+ = std::pair<std::string, std::function<std::string ()>>;
+
+ const std::vector<json_member_name_to_cb_t> json_mem_to_cb_map{
+ json_member_name_to_cb_t (
+ "path", [this] () -> std::string { return path (); }),
+ json_member_name_to_cb_t (
+ "description", [this] () -> std::string { return desc (); }),
+ json_member_name_to_cb_t (
+ "app_info", [this] () -> std::string { return app_info (); }),
+ json_member_name_to_cb_t ("version",
+ [this] () -> std::string { return std::to_string (version ()); }),
+ json_member_name_to_cb_t ("active",
+ [this] () -> std::string { return (is_activated () ? "T" : "F"); }),
+ };
+
+ g_autoptr (JsonBuilder) builder = json_builder_new ();
+ g_autoptr (JsonGenerator) gen = NULL;
+
+ json_builder_begin_object (builder);
+ for (auto iter : json_mem_to_cb_map) {
+ json_builder_set_member_name (builder, iter.first.c_str ());
+ json_builder_add_string_value (builder, iter.second ().c_str ());
+ }
+ json_builder_end_object (builder);
+
+ {
+ g_autoptr (JsonNode) root = json_builder_get_root (builder);
+
+ gen = json_generator_new ();
+ json_generator_set_root (gen, root);
+ json_generator_set_pretty (gen, TRUE);
+ }
+
+ return json_generator_to_data (gen, NULL);
+}
+
+/**
+ * @brief Initialize the static unique_ptr of MockMLAgent
+ */
+void
+ml_agent_mock_init ()
+{
+ uptr_mock = std::make_unique<MockMLAgent> ();
+}
+
+/**
+ * @brief C-wrapper for the MockModel's method add_model.
+ */
+bool
+ml_agent_mock_add_model (const gchar *name, const gchar *path, const gchar *app_info,
+ const bool is_activated, const char *desc, const guint version)
+{
+ MockModel model{ name, path, app_info, is_activated, desc, version };
+
+ return uptr_mock->add_model (model);
+}
+
+/**
+ * @brief C-wrapper for the MockModel's method get_model.
+ */
+MockModel *
+ml_agent_mock_model_get (const gchar *name, const guint version)
+{
+ return uptr_mock->get_model (name, version);
+}
+
+/**
+ * @brief Pass the JSON c-string generated by the ML Agent mock class to the caller.
+ */
+gint
+ml_agent_model_get (const gchar *name, const guint version, gchar **description, GError **err)
+{
+ MockModel *model_ptr = ml_agent_mock_model_get (name, version);
+
+ g_return_val_if_fail (err == NULL || *err == NULL, -EINVAL);
+
+ if (model_ptr == nullptr) {
+ return -EINVAL;
+ }
+
+ *description = model_ptr->to_cstr_json ();
+
+ return 0;
+}
--- /dev/null
+/**
+ * SPDX-License-Identifier: LGPL-2.1-only
+ *
+ * @file mlagent_mock.h
+ * @date 29 Nov 2023
+ * @brief Mocking ML Agent APIs
+ * @see https://github.com/nnstreamer/nnstreamer
+ * @author Wook Song <wook.song16@samsung.com>
+ * @bug No known bugs
+ */
+#ifndef __NNS_MLAGENT_MOCK_H__
+#define __NNS_MLAGENT_MOCK_H__
+
+#include <glib.h>
+
+#include <unordered_map>
+#include <vector>
+
+struct MockModel;
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/**
+ * @brief Initialize the static unique_ptr of MockMLAgent
+ */
+void ml_agent_mock_init ();
+
+/**
+ * @brief C-wrapper for the MockModel's method add_model.
+ * @param[in] name The model's name to add
+ * @param[in] path The model's name to add
+ * @param[in] app_info The model's name to add
+ * @param[in] is_activated The model's name to add
+ * @param[in] desc The model's version to add
+ * @param[in] version The JSON c-string containing the information of the model matching to the given name and version
+ * @return true if there is a matching model to the given name and version, otherwise a negative integer
+ * @note ML Agent provides the original implementation of this function and
+ * the following implementation is only for testing purposes.
+ */
+bool ml_agent_mock_add_model (const gchar *name, const gchar *path, const gchar *info,
+ const bool is_activated, const char *desc, const guint version);
+
+/**
+ * @brief C-wrapper for the MockModel's method get_model.
+ * @param[in] name The model's name to retreive
+ * @param[in] version The model's version to retreive
+ * @return A pointer to the model matching the given information. If there is no model possible, NULL is returned.
+ */
+MockModel *ml_agent_mock_get_model (const gchar *name, const guint version);
+
+/**
+ * @brief Pass the JSON c-string generated by the ML Agent mock class to the caller.
+ * @param[in] name The model's name to retreive
+ * @param[in] version The model's version to retreive
+ * @param[out] description The JSON c-string, containing the information of the model matching the given name and version
+ * @param[out] err The return location for a recoverable error. This can be NULL.
+ * @return 0 if there is a matching model to the given name and version otherwise a negative integer
+ * @note ML Agent provides the original implementation of this function and
+ * the following implementation is only for testing purposes.
+ */
+gint ml_agent_model_get (
+ const gchar *name, const guint version, gchar **description, GError **err);
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+/**
+ * @brief Class for mock Model object
+ */
+struct MockModel {
+ // Constructors
+ MockModel () = delete;
+ MockModel (std::string name, std::string path = {}, std::string app_info = {},
+ bool is_activated = false, std::string desc = {}, guint version = 0U)
+ : name_{ name }, path_{ path }, app_info_{ app_info },
+ is_activated_{ is_activated }, desc_{ desc }, version_{ version } {
+
+ };
+ // Copy constructor
+ MockModel (const MockModel &other)
+ : MockModel (other.name_, other.path_, other.app_info_,
+ other.is_activated_, other.desc_, other.version_){
+
+ };
+ // Move constructor
+ MockModel (MockModel &&other) noexcept
+ {
+ *this = std::move (other);
+ }
+ MockModel &operator= (const MockModel &other) = delete;
+ MockModel &operator= (MockModel &&other) noexcept
+ {
+ if (this == &other)
+ return *this;
+
+ this->name_ = std::move (other.name_);
+ this->path_ = std::move (other.path_);
+ this->app_info_ = std::move (other.app_info_);
+ this->is_activated_ = std::exchange (other.is_activated_, false);
+ this->desc_ = std::move (other.desc_);
+ this->version_ = std::exchange (other.version_, 0U);
+
+ return *this;
+ }
+
+ bool operator== (const MockModel &model) const
+ {
+ if (name_ == model.name_ && version_ == model.version_)
+ return true;
+ return false;
+ }
+
+ // Getters
+ std::string name () const
+ {
+ return name_;
+ }
+
+ std::string path () const
+ {
+ return path_;
+ }
+ std::string app_info () const
+ {
+ return app_info_;
+ }
+ bool is_activated () const
+ {
+ return is_activated_;
+ }
+
+ std::string desc () const
+ {
+ return desc_;
+ }
+
+ guint version () const
+ {
+ return version_;
+ }
+
+ // Setters
+ void path (const std::string &path)
+ {
+ path_ = path;
+ }
+
+ void is_activated (bool flag)
+ {
+ is_activated_ = flag;
+ }
+
+ void desc (const std::string &description)
+ {
+ desc_ = description;
+ }
+
+ void app_info (const std::string &info)
+ {
+ app_info_ = info;
+ }
+
+ void version (guint ver)
+ {
+ version_ = ver;
+ }
+
+ /**
+ * @brief Generate C-stringified JSON
+ * @return C-stringified JSON
+ */
+ gchar *to_cstr_json ();
+
+ private:
+ std::string name_;
+ std::string path_{};
+ std::string app_info_{};
+ bool is_activated_{ false };
+ std::string desc_{};
+ guint version_{ 0U };
+};
+
+struct MockMLAgent {
+ MockMLAgent ()
+ {
+ }
+
+ bool add_model (MockModel model)
+ {
+ std::string key = model.name ();
+
+ {
+ auto range = models_dict_.equal_range (key);
+
+ for (auto it = range.first; it != range.second; ++it) {
+ if (model == it->second)
+ return false;
+ }
+ }
+
+ models_dict_.insert (std::make_pair (key, model));
+
+ return true;
+ }
+
+ MockModel *get_model (const gchar *name, guint version)
+ {
+ std::string key{ name };
+ auto range = models_dict_.equal_range (key);
+
+ for (auto it = range.first; it != range.second; ++it) {
+ if (version == it->second.version ())
+ return &it->second;
+ }
+
+ return nullptr;
+ }
+
+ private:
+ std::unordered_multimap<std::string, MockModel> models_dict_;
+};
+
+#endif /* __NNS_MLAGENT_MOCK_H__ */
--- /dev/null
+/* SPDX-License-Identifier: LGPL-2.1-only */
+/**
+ * @file unittest_mlagent.cc
+ * @date 30 Nov 2023
+ * @brief Unit test for MLAgent URI parsing
+ * @author Wook Song <wook.song16@samsung.com>
+ * @see http://github.com/nnstreamer/nnstreamer
+ * @bug No known bugs
+ *
+ */
+
+#include <gtest/gtest.h>
+#include <glib.h>
+#include <gst/gst.h>
+
+#include <iostream>
+
+#ifdef __cplusplus
+extern "C" {
+#include "ml_agent.h"
+}
+#endif //__cplusplus
+
+#include "mock_mlagent.h"
+
+static const std::vector<MockModel> default_models{
+ MockModel{ "MobileNet_v1", "/tmp/mobilenet_v1_0.tflite", "", false, "", 0U },
+ MockModel{ "MobileNet_v1", "/tmp/mobilenet_v1_1.tflite", "", false, "", 1U },
+ MockModel{ "ResNet50_v1", "/tmp/resnet50_v1_0.tflite", "", false, "", 0U },
+ MockModel{ "ResNet50_v1", "/tmp/resnet50_v1_1.tflite", "", false, "", 1U },
+};
+
+/**
+ * @brief Initialize the MockMLAgent using given MockModels
+ * @param[in] models A vector containg MockModels
+ */
+void
+_init (const std::vector<MockModel> &models = default_models)
+{
+ ml_agent_mock_init ();
+
+ for (auto iter = models.begin (); iter != models.end (); ++iter) {
+ ml_agent_mock_add_model (iter->name ().c_str (), iter->path ().c_str (),
+ iter->app_info ().c_str (), iter->is_activated (),
+ iter->desc ().c_str (), iter->version ());
+ }
+}
+
+constexpr gchar valid_uri_format_literal[] = "mlagent://model/%s/%u";
+constexpr gchar invalid_uri_format_literal[] = "ml-agent://model/%s/%u";
+
+/**
+ * @brief tests of getting model paths with valid URIs
+ */
+TEST (testMLAgent, GetModelValidURIs_p)
+{
+ _init ();
+
+ // Test the valid URI cases
+ GValue val = G_VALUE_INIT;
+ g_value_init (&val, G_TYPE_STRING);
+ const std::vector<MockModel> &models = default_models;
+
+ for (auto iter = models.begin (); iter != models.end (); ++iter) {
+ g_autofree gchar *uri = g_strdup_printf (
+ valid_uri_format_literal, iter->name ().c_str (), iter->version ());
+ g_autofree gchar *path = NULL;
+
+ g_value_set_string (&val, uri);
+
+ path = mlagent_get_model_path_from (&val);
+
+ EXPECT_STREQ (path, iter->path ().c_str ());
+
+ g_value_reset (&val);
+ }
+}
+
+/**
+ * @brief tests of getting model paths using URIs with invalid format
+ */
+TEST (testMLAgent, GetModelInvalidURIFormats_n)
+{
+ GValue val = G_VALUE_INIT;
+ g_value_init (&val, G_TYPE_STRING);
+ const std::vector<MockModel> &models = default_models;
+
+ for (auto iter = models.begin (); iter != models.end (); ++iter) {
+ g_autofree gchar *uri = g_strdup_printf (
+ invalid_uri_format_literal, iter->name ().c_str (), iter->version ());
+ g_autofree gchar *path = NULL;
+
+ g_value_set_string (&val, uri);
+
+ path = mlagent_get_model_path_from (&val);
+
+ /* In the case that invalid URIs are given, mlagent_get_model_path_from () returns
+ * the given URI as it is so that it is handled by the fallback procedure (i.e., regarding it as a file path).
+ */
+ EXPECT_STREQ (uri, path);
+
+ g_value_reset (&val);
+ }
+}
+
+/**
+ * @brief tests of getting model paths with invalid URIs
+ */
+TEST (testMLAgent, GetModelInvalidModel_n)
+{
+ // Clear the MLAgentMock instance
+ _init ();
+
+ // Test the valid URIs
+ GValue val = G_VALUE_INIT;
+ g_value_init (&val, G_TYPE_STRING);
+
+ g_autofree gchar *uri
+ = g_strdup_printf (valid_uri_format_literal, "InvalidModelName", UINT32_MAX);
+ g_autofree gchar *path = NULL;
+
+ g_value_set_string (&val, uri);
+
+ path = mlagent_get_model_path_from (&val);
+
+ /* In the case that invalid URIs are given, mlagent_get_model_path_from () returns
+ * the given URI as it is so that it is handled by the fallback procedure (i.e., regarding it as a file path).
+ */
+ EXPECT_STREQ (uri, path);
+}
+
+/**
+ * @brief Main function for this unit test
+ */
+int
+main (int argc, char *argv[])
+{
+ int ret = -1;
+
+ try {
+ testing::InitGoogleTest (&argc, argv);
+ } catch (...) {
+ g_warning ("catch 'testing::internal::<unnamed>::ClassUniqueToAlwaysTrue'");
+ }
+
+ gst_init (&argc, &argv);
+
+ try {
+ ret = RUN_ALL_TESTS ();
+ } catch (...) {
+ g_warning ("catch `testing::internal::GoogleTestFailureException`");
+ }
+
+ return ret;
+}