Repository: ai-dynamo/nixl
Pull request #150: testing: gtest initial infrastructure
Author: login: mamar123
Labels: name: enhancement
description: New feature or request
name: size/XL
Created: 2025-04-14T07:45:43Z | Merged: 2025-04-22T21:03:50Z

This PR initializes the testing infrastructure using Google Test and sets up the foundation for plugin support, including basic configuration and mock components


Diff:
diff --git a/meson_options.txt b/meson_options.txt
index 5b523b4a5c..f1a80b1e4b 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -22,3 +22,6 @@ option('cudapath_lib', type: 'string', value: '', description: 'Library path for
 option('cudapath_stub', type: 'string', value: '', description: 'Extra Stub path for CUDA')
 option('static_plugins', type: 'string', value: '', description: 'Plugins to be built-in, comma-separated')
 option('build_docs', type: 'boolean', value: false, description: 'Build Doxygen documentation')
+
+# Tests
+option('test_all_plugins', type: 'boolean', value: false, description: 'Testing all plugins in addition to the mocks..')
diff --git a/test/gtest/main.cpp b/test/gtest/main.cpp
new file mode 100644
index 0000000000..acfdfa6810
--- /dev/null
+++ b/test/gtest/main.cpp
@@ -0,0 +1,56 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include <gtest/gtest.h>
+
+namespace gtest {
+std::vector<std::string> SplitWithDelimiter(const std::string &str,
+                                            char delimiter) {
+  std::istringstream tokenStream(str);
+  std::vector<std::string> tokens;
+  std::string token;
+
+  while (std::getline(tokenStream, token, delimiter))
+    tokens.push_back(token);
+
+  return tokens;
+}
+
+void ParseArguments(int argc, char **argv) {
+  for (int i = 1; i < argc; ++i) {
+    if (std::string(argv[i]).find("--tests_plugin_dirs=") == 0) {
+      const std::string plugin_dirs = SplitWithDelimiter(argv[i], '=').back();
+
+      if (!plugin_dirs.empty()) {
+        for (const auto &dir : SplitWithDelimiter(plugin_dirs, ',')) {
+          std::cout << "Adding plugin directory:" << dir << std::endl;
+          nixlPluginManager::getInstance().addPluginDirectory(dir);
+        }
+      }
+    }
+  }
+}
+
+int RunTests(int argc, char **argv) {
+  testing::InitGoogleTest(&argc, argv);
+  ParseArguments(argc, argv);
+
+  return RUN_ALL_TESTS();
+}
+} // namespace gtest
+
+int main(int argc, char **argv) { return gtest::RunTests(argc, argv); }
diff --git a/test/gtest/meson.build b/test/gtest/meson.build
new file mode 100644
index 0000000000..1c2290c793
--- /dev/null
+++ b/test/gtest/meson.build
@@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0', required : false)
+
+if not gtest_dep.found()
+    message('GTest not found, skipping gtest build')
+    subdir_done()
+endif
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+else
+    cuda_dependencies = []
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
+endif
+
+test_exe = executable('gtest',
+    sources : ['main.cpp', 'plugin_manager.cpp'],
+    include_directories: [nixl_inc_dirs, utils_inc_dirs],
+    cpp_args : cpp_flags,
+    dependencies : [nixl_dep, cuda_dep, gtest_dep]
+)
+
+test('gtest', test_exe, args: [plugin_dirs_arg])
diff --git a/test/gtest/mocks/meson.build b/test/gtest/mocks/meson.build
new file mode 100644
index 0000000000..78a7515713
--- /dev/null
+++ b/test/gtest/mocks/meson.build
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+mock_basic_plugin = shared_library('MOCK_BASIC', 'mock_basic_plugin.cpp',
+               dependencies: [nixl_infra],
+               include_directories: [nixl_inc_dirs, utils_inc_dirs],
+               link_with : [ucx_backend_lib],
+               name_prefix: 'libplugin_')
+
+mock_dram_sources = ['mock_dram_plugin.cpp', 'mock_dram_engine.cpp']
+mock_dram_plugin = shared_library('MOCK_DRAM', mock_dram_sources,
+               dependencies: [nixl_infra],
+               include_directories: [nixl_inc_dirs, utils_inc_dirs],
+               link_with : [ucx_backend_lib],
+               name_prefix: 'libplugin_')
+
+source_root = meson.project_source_root()
+mocks_dep = declare_dependency(variables : {'path' : meson.current_source_dir().split(source_root + '/')[1]})
diff --git a/test/gtest/mocks/mock_basic_plugin.cpp b/test/gtest/mocks/mock_basic_plugin.cpp
new file mode 100644
index 0000000000..eeb24012a3
--- /dev/null
+++ b/test/gtest/mocks/mock_basic_plugin.cpp
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "backend/backend_engine.h"
+#include "backend/backend_plugin.h"
+
+namespace mocks {
+namespace basic_plugin {
+
+static constexpr const char *plugin_name = "MOCK_BASIC";
+static constexpr const char *plugin_version = "0.0.1";
+
+static nixlBackendEngine *create_engine(const nixlBackendInitParams *) {
+  return nullptr;
+}
+
+static void destroy_engine(nixlBackendEngine *) {}
+
+static const char *get_plugin_name() { return plugin_name; }
+
+static const char *get_plugin_version() { return plugin_version; }
+
+static nixl_b_params_t get_backend_options() { return nixl_b_params_t(); }
+
+static nixlBackendPlugin plugin = {
+  NIXL_PLUGIN_API_VERSION,
+  create_engine,
+  destroy_engine,
+  get_plugin_name,
+  get_plugin_version,
+  get_backend_options
+};
+} // namespace basic_plugin
+
+} // namespace mocks
+
+extern "C" nixlBackendPlugin *nixl_plugin_init() {
+  return &mocks::basic_plugin::plugin;
+}
+
+extern "C" void nixl_plugin_fini() {}
diff --git a/test/gtest/mocks/mock_dram_engine.cpp b/test/gtest/mocks/mock_dram_engine.cpp
new file mode 100644
index 0000000000..c1e50f34fa
--- /dev/null
+++ b/test/gtest/mocks/mock_dram_engine.cpp
@@ -0,0 +1,105 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mock_dram_engine.h"
+
+namespace mocks {
+MockDramBackendEngine::MockDramBackendEngine(
+    const nixlBackendInitParams *init_params)
+    : nixlBackendEngine(init_params) {}
+
+MockDramBackendEngine::~MockDramBackendEngine() {}
+
+bool MockDramBackendEngine::supportsRemote() const { return true; }
+bool MockDramBackendEngine::supportsLocal() const { return true; }
+bool MockDramBackendEngine::supportsNotif() const { return false; }
+bool MockDramBackendEngine::supportsProgTh() const { return false; };
+
+nixl_mem_list_t MockDramBackendEngine::getSupportedMems() const {
+  return nixl_mem_list_t{DRAM_SEG};
+}
+
+nixl_status_t MockDramBackendEngine::registerMem(const nixlBlobDesc &,
+                                                 const nixl_mem_t &,
+                                                 nixlBackendMD *&) {
+  return NIXL_SUCCESS;
+}
+
+nixl_status_t MockDramBackendEngine::deregisterMem(nixlBackendMD *) {
+  return NIXL_SUCCESS;
+}
+nixl_status_t MockDramBackendEngine::connect(const std::string &) {
+  return NIXL_SUCCESS;
+};
+nixl_status_t MockDramBackendEngine::disconnect(const std::string &) {
+  return NIXL_SUCCESS;
+};
+nixl_status_t MockDramBackendEngine::unloadMD(nixlBackendMD *) {
+  return NIXL_SUCCESS;
+};
+nixl_status_t MockDramBackendEngine::prepXfer(const nixl_xfer_op_t &,
+                                              const nixl_meta_dlist_t &,
+                                              const nixl_meta_dlist_t &,
+                                              const std::string &,
+                                              nixlBackendReqH *&,
+                                              const nixl_opt_b_args_t *) {
+  return NIXL_SUCCESS;
+};
+nixl_status_t MockDramBackendEngine::postXfer(const nixl_xfer_op_t &,
+                                              const nixl_meta_dlist_t &,
+                                              const nixl_meta_dlist_t &,
+                                              const std::string &,
+                                              nixlBackendReqH *&,
+                                              const nixl_opt_b_args_t *) {
+  return NIXL_SUCCESS;
+};
+nixl_status_t MockDramBackendEngine::checkXfer(nixlBackendReqH *) {
+  return NIXL_SUCCESS;
+};
+nixl_status_t MockDramBackendEngine::releaseReqH(nixlBackendReqH *) {
+  return NIXL_SUCCESS;
+};
+
+nixl_status_t MockDramBackendEngine::getPublicData(const nixlBackendMD *,
+                                                   std::string &) const {
+  return NIXL_SUCCESS;
+};
+nixl_status_t MockDramBackendEngine::getConnInfo(std::string &) const {
+  return NIXL_SUCCESS;
+}
+nixl_status_t MockDramBackendEngine::loadRemoteConnInfo(const std::string &,
+                                                        const std::string &) {
+  return NIXL_SUCCESS;
+}
+nixl_status_t MockDramBackendEngine::loadRemoteMD(const nixlBlobDesc &,
+                                                  const nixl_mem_t &,
+                                                  const std::string &,
+                                                  nixlBackendMD *&) {
+  return NIXL_SUCCESS;
+}
+nixl_status_t MockDramBackendEngine::loadLocalMD(nixlBackendMD *,
+                                                 nixlBackendMD *&) {
+  return NIXL_SUCCESS;
+}
+nixl_status_t MockDramBackendEngine::getNotifs(notif_list_t &) {
+  return NIXL_SUCCESS;
+}
+nixl_status_t MockDramBackendEngine::genNotif(const std::string &,
+                                              const std::string &) {
+  return NIXL_SUCCESS;
+}
+int MockDramBackendEngine::progress() { return 0; }
+} // namespace mocks
diff --git a/test/gtest/mocks/mock_dram_engine.h b/test/gtest/mocks/mock_dram_engine.h
new file mode 100644
index 0000000000..f82ac5e3a9
--- /dev/null
+++ b/test/gtest/mocks/mock_dram_engine.h
@@ -0,0 +1,72 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TEST_GTEST_MOCKS_MOCK_DRAM_BACKEND_ENGINE_H
+#define TEST_GTEST_MOCKS_MOCK_DRAM_BACKEND_ENGINE_H
+
+#include "backend/backend_engine.h"
+#include "backend/backend_plugin.h"
+
+namespace mocks {
+
+class MockDramBackendEngine : public nixlBackendEngine {
+public:
+  MockDramBackendEngine(const nixlBackendInitParams *init_params);
+  ~MockDramBackendEngine();
+
+  bool supportsRemote() const override;
+  bool supportsLocal() const override;
+  bool supportsNotif() const override;
+  bool supportsProgTh() const override;
+  nixl_mem_list_t getSupportedMems() const override;
+  nixl_status_t registerMem(const nixlBlobDesc &mem, const nixl_mem_t &nixl_mem,
+                            nixlBackendMD *&out) override;
+  nixl_status_t deregisterMem(nixlBackendMD *meta) override;
+  nixl_status_t connect(const std::string &remote_agent) override;
+  nixl_status_t disconnect(const std::string &remote_agent) override;
+  nixl_status_t unloadMD(nixlBackendMD *input) override;
+  nixl_status_t prepXfer(const nixl_xfer_op_t &operation,
+                         const nixl_meta_dlist_t &local,
+                         const nixl_meta_dlist_t &remote,
+                         const std::string &remote_agent,
+                         nixlBackendReqH *&handle,
+                         const nixl_opt_b_args_t *opt_args) override;
+  nixl_status_t postXfer(const nixl_xfer_op_t &operation,
+                         const nixl_meta_dlist_t &local,
+                         const nixl_meta_dlist_t &remote,
+                         const std::string &remote_agent,
+                         nixlBackendReqH *&handle,
+                         const nixl_opt_b_args_t *opt_args) override;
+  nixl_status_t checkXfer(nixlBackendReqH *handle) override;
+  nixl_status_t releaseReqH(nixlBackendReqH *handle) override;
+  nixl_status_t getPublicData(const nixlBackendMD *meta,
+                              std::string &str) const override;
+  nixl_status_t getConnInfo(std::string &str) const override;
+  nixl_status_t loadRemoteConnInfo(const std::string &remote_agent,
+                                   const std::string &remote_conn_info);
+  nixl_status_t loadRemoteMD(const nixlBlobDesc &input,
+                             const nixl_mem_t &nixl_mem,
+                             const std::string &remote_agent,
+                             nixlBackendMD *&output) override;
+  nixl_status_t loadLocalMD(nixlBackendMD *input, nixlBackendMD *&output);
+  nixl_status_t getNotifs(notif_list_t &notif_list) override;
+  nixl_status_t genNotif(const std::string &remote_agent,
+                         const std::string &msg) override;
+  int progress() override;
+};
+} // namespace mocks
+
+#endif
diff --git a/test/gtest/mocks/mock_dram_plugin.cpp b/test/gtest/mocks/mock_dram_plugin.cpp
new file mode 100644
index 0000000000..218605c7ca
--- /dev/null
+++ b/test/gtest/mocks/mock_dram_plugin.cpp
@@ -0,0 +1,50 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mock_dram_engine.h"
+
+namespace mocks {
+namespace dram_plugin {
+
+static nixlBackendEngine *create_engine(const nixlBackendInitParams *params) {
+  return new MockDramBackendEngine(params);
+}
+
+static void destroy_engine(nixlBackendEngine *engine) { delete engine; }
+
+static const char *get_plugin_name() { return "MOCK_DRAM"; }
+
+static const char *get_plugin_version() { return "0.0.1"; }
+
+static nixl_b_params_t get_backend_options() { return nixl_b_params_t(); }
+
+static nixlBackendPlugin plugin = {
+  NIXL_PLUGIN_API_VERSION,
+  create_engine,
+  destroy_engine,
+  get_plugin_name,
+  get_plugin_version,
+  get_backend_options
+};
+} // namespace dram_plugin
+
+} // namespace mocks
+
+extern "C" nixlBackendPlugin *nixl_plugin_init() {
+  return &mocks::dram_plugin::plugin;
+}
+
+extern "C" void nixl_plugin_fini() {}
diff --git a/test/gtest/plugin_manager.cpp b/test/gtest/plugin_manager.cpp
new file mode 100644
index 0000000000..17624086d1
--- /dev/null
+++ b/test/gtest/plugin_manager.cpp
@@ -0,0 +1,243 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_ALL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real)
+      GTEST_SKIP();
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_ALL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real)
+      return;
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_ALL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real)
+        continue;
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_ALL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real)
+        continue;
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames())
+      prev_plugins_.insert(plugin);
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_)
+      plugin_manager_.unloadPlugin(plugin);
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end())
+      return true;
+    if (loaded_plugins_.find(name) != loaded_plugins_.end())
+      return false;
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end())
+      return;
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end()))
+        return false;
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimpleLifeCycleTest) {
+  EXPECT_TRUE(IsLoaded());
+}
+
+TEST_P(LoadMultiplePluginsTestFixture, SimpleLifeCycleTest) {
+  EXPECT_TRUE(AreAllLoaded());
+}
+
+TEST_F(LoadedPluginTestFixture, NoLoadedPluginsTest) {
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadSinglePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadMultiplePluginsTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadSimplePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadComplexPluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  UnloadPlugin(mock_basic_plugin_name);
+  UnloadPlugin(mock_dram_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(MockLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(PluginDesc{
+                             .name = mock_basic_plugin_name,
+                             .type = PluginDesc::PluginType::Mock}));
+INSTANTIATE_TEST_SUITE_P(UcxLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(GdsLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(gds_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(UcxMoLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_mo_plugin_desc));
+
+/* Load multiple plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(UcxGdsLoadMultiplePluginInstantiation,
+                         LoadMultiplePluginsTestFixture,
+                         testing::Values(std::vector<PluginDesc>{
+                             ucx_plugin_desc, gds_plugin_desc}));
+INSTANTIATE_TEST_SUITE_P(UcxUcxMoLoadMultiplePluginInstantiation,
+                         LoadMultiplePluginsTestFixture,
+                         testing::Values(std::vector<PluginDesc>{
+                             ucx_plugin_desc,
+                             ucx_mo_plugin_desc}));
+INSTANTIATE_TEST_SUITE_P(GdsUcxMoLoadMultiplePluginInstantiation,
+                         LoadMultiplePluginsTestFixture,
+                         testing::Values(std::vector<PluginDesc>{
+                             gds_plugin_desc,
+                             ucx_mo_plugin_desc}));
+INSTANTIATE_TEST_SUITE_P(UcxGdsUcxMoLoadMultiplePluginInstantiation,
+                         LoadMultiplePluginsTestFixture,
+                         testing::Values(std::vector<PluginDesc>{
+                             ucx_plugin_desc, gds_plugin_desc,
+                             ucx_mo_plugin_desc}));
+
+} // namespace plugin_manager
+} // namespace gtest
diff --git a/test/meson.build b/test/meson.build
index ee65dbc03e..aa9fd3acb6 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -15,3 +15,4 @@
 
 subdir('nixl')
 subdir('unit')
+subdir('gtest')


Reviews:
user: login: etoledano
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T08:04:14Z
user: login: tstamler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T13:59:24Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T06:38:37Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:54:28Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T10:54:39Z
user: login: tstamler
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T13:07:25Z
user: login: aranadive
body: Generally looks okay. Mainly superficial comments.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T23:31:52Z
user: login: yosefe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T14:57:04Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:01:14Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:01:47Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:03:46Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:03:59Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:05:08Z
user: login: aranadive
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:10:56Z
user: login: aranadive
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:15:37Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:23:38Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:28:02Z
user: login: aranadive
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:56:10Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T11:17:53Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T11:18:07Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T11:18:23Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T12:46:36Z
user: login: etoledano
body: NOP
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T12:50:13Z
user: login: etoledano
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T12:52:13Z
user: login: yosefe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T15:47:32Z
user: login: yosefe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T15:54:06Z
user: login: yosefe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-20T16:19:46Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T07:08:39Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T07:16:27Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T07:17:53Z
user: login: yosefe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T07:59:19Z
user: login: yosefe
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T08:00:08Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-21T09:26:14Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T08:54:14Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T08:54:21Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T09:02:23Z
user: login: mamar123
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T09:38:13Z
user: login: etoledano
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-22T12:49:26Z

Review comments:
diff_hunk: @@ -0,0 +1,256 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
+ * All rights reserved. SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+namespace utils {
path: test/gtest/plugin_manager.cpp
user: login: etoledano
body: (I know it was initially my mistake) 
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rc-helper

Seems that utils namespace isn't needed
created_at: 2025-04-14T08:02:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,3 +21,6 @@ option('cudapath_inc', type: 'string', value: '', description: 'Include path for
 option('cudapath_lib', type: 'string', value: '', description: 'Library path for CUDA')
 option('cudapath_stub', type: 'string', value: '', description: 'Extra Stub path for CUDA')
 option('static_plugins', type: 'string', value: '', description: 'Plugins to be built-in, comma-separated')
+
+# Tests
+option('tests_skip_native_plugins', type: 'boolean', value: true, description: 'Skip tests of natively supported plugins and only tests using mocks..')
path: meson_options.txt
user: login: etoledano
body: Can rename "native" to "real"?
i.e. without this flag we only test mocks, and with this flag we also test real plugins.
Seems to make more sense
created_at: 2025-04-14T08:04:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
+ * All rights reserved. SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mock_dram_engine.h"
+
+namespace mocks {
+MockDramBackendEngine::MockDramBackendEngine(
+    const nixlBackendInitParams *init_params)
+    : nixlBackendEngine(init_params) {}
+
+MockDramBackendEngine::~MockDramBackendEngine() {}
+
+bool MockDramBackendEngine::supportsRemote() const { return true; }
+bool MockDramBackendEngine::supportsLocal() const { return true; }
+bool MockDramBackendEngine::supportsNotif() const { return false; }
+bool MockDramBackendEngine::supportsProgTh() const { return false; };
+
+nixl_mem_list_t MockDramBackendEngine::getSupportedMems() const {
path: test/gtest/mocks/mock_dram_engine.cpp
user: login: tstamler
body: maybe I'm missing something, but if all these functions are just one liners, can we not just define them in the header instead of having a separate C++ file?
created_at: 2025-04-14T13:59:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,3 +21,6 @@ option('cudapath_inc', type: 'string', value: '', description: 'Include path for
 option('cudapath_lib', type: 'string', value: '', description: 'Library path for CUDA')
 option('cudapath_stub', type: 'string', value: '', description: 'Extra Stub path for CUDA')
 option('static_plugins', type: 'string', value: '', description: 'Plugins to be built-in, comma-separated')
+
+# Tests
+option('test_real_plugins', type: 'boolean', value: false, description: 'Testing the real plugins in addition to the mocks..')
path: meson_options.txt
user: login: aranadive
body: I think test_all_plugins is better. Then description is Testing all plugins in addition to the mocks..
created_at: 2025-04-15T06:18:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
+ * All rights reserved. SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mock_dram_engine.h"
+
+namespace mocks {
+MockDramBackendEngine::MockDramBackendEngine(
+    const nixlBackendInitParams *init_params)
+    : nixlBackendEngine(init_params) {}
+
+MockDramBackendEngine::~MockDramBackendEngine() {}
+
+bool MockDramBackendEngine::supportsRemote() const { return true; }
+bool MockDramBackendEngine::supportsLocal() const { return true; }
+bool MockDramBackendEngine::supportsNotif() const { return false; }
+bool MockDramBackendEngine::supportsProgTh() const { return false; };
+
+nixl_mem_list_t MockDramBackendEngine::getSupportedMems() const {
path: test/gtest/mocks/mock_dram_engine.cpp
user: login: etoledano
body: You aren't missing anything, currently it can fit into the header w/o extra overhead, nevertheless, this engine (vs. basic mock) should have actual implementation for transactions, so in a very short time we'll have to extend it to do so, i.e. its a proactive step.
In general, I don't mind this commit to have everything implemented in the header and have the next commit (which is WIP) move it to the *.cpp since it has more organized approach, just wasn't sure if that was necessary. wdyt? 
created_at: 2025-04-15T06:38:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,3 +21,6 @@ option('cudapath_inc', type: 'string', value: '', description: 'Include path for
 option('cudapath_lib', type: 'string', value: '', description: 'Library path for CUDA')
 option('cudapath_stub', type: 'string', value: '', description: 'Extra Stub path for CUDA')
 option('static_plugins', type: 'string', value: '', description: 'Plugins to be built-in, comma-separated')
+
+# Tests
+option('tests_skip_native_plugins', type: 'boolean', value: true, description: 'Skip tests of natively supported plugins and only tests using mocks..')
path: meson_options.txt
user: login: mamar123
body: Done
created_at: 2025-04-15T10:54:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,256 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
+ * All rights reserved. SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+namespace utils {
path: test/gtest/plugin_manager.cpp
user: login: mamar123
body: Done
created_at: 2025-04-15T10:54:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,3 +21,6 @@ option('cudapath_inc', type: 'string', value: '', description: 'Include path for
 option('cudapath_lib', type: 'string', value: '', description: 'Library path for CUDA')
 option('cudapath_stub', type: 'string', value: '', description: 'Extra Stub path for CUDA')
 option('static_plugins', type: 'string', value: '', description: 'Plugins to be built-in, comma-separated')
+
+# Tests
+option('test_real_plugins', type: 'boolean', value: false, description: 'Testing the real plugins in addition to the mocks..')
path: meson_options.txt
user: login: yosefe
body: any reason to not always test all available real plugins?
created_at: 2025-04-15T14:28:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES.
+ * All rights reserved. SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mock_dram_engine.h"
+
+namespace mocks {
+MockDramBackendEngine::MockDramBackendEngine(
+    const nixlBackendInitParams *init_params)
+    : nixlBackendEngine(init_params) {}
+
+MockDramBackendEngine::~MockDramBackendEngine() {}
+
+bool MockDramBackendEngine::supportsRemote() const { return true; }
+bool MockDramBackendEngine::supportsLocal() const { return true; }
+bool MockDramBackendEngine::supportsNotif() const { return false; }
+bool MockDramBackendEngine::supportsProgTh() const { return false; };
+
+nixl_mem_list_t MockDramBackendEngine::getSupportedMems() const {
path: test/gtest/mocks/mock_dram_engine.cpp
user: login: tstamler
body: all good, if you plan to extend this a little further than no reason to put it all in the header. Just thought it might be cleaner if everything was only going to be one-line functions
created_at: 2025-04-16T13:07:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+mock_basic_plugin = shared_library('MOCK_BASIC_PLUGIN', 'mock_basic_plugin.cpp',
path: test/gtest/mocks/meson.build
user: login: aranadive
body: This should just be called MOCK_BASIC. So the library generated will be libplugin_MOCK_BASIC.so, otherwise it would be libplugin_MOCK_BASIC_PLUGIN.so which is odd.
created_at: 2025-04-16T22:55:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+mock_basic_plugin = shared_library('MOCK_BASIC_PLUGIN', 'mock_basic_plugin.cpp',
+               dependencies: [nixl_infra],
+               include_directories: [nixl_inc_dirs, utils_inc_dirs],
+               link_with : [ucx_backend_lib],
+               name_prefix: 'libplugin_')
+
+mock_dram_sources = ['mock_dram_plugin.cpp', 'mock_dram_engine.cpp']
+mock_dram_plugin = shared_library('MOCK_DRAM_PLUGIN', mock_dram_sources,
path: test/gtest/mocks/meson.build
user: login: aranadive
body: Same here
created_at: 2025-04-16T22:56:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "backend/backend_engine.h"
+#include "backend/backend_plugin.h"
+
+namespace mocks {
+namespace basic_plugin {
+
+static constexpr const char *plugin_name = "MOCK_BASIC_PLUGIN";
path: test/gtest/mocks/mock_basic_plugin.cpp
user: login: aranadive
body: MOCK_BASIC
created_at: 2025-04-16T22:56:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "backend/backend_engine.h"
+#include "backend/backend_plugin.h"
+
+namespace mocks {
+namespace basic_plugin {
+
+static constexpr const char *plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *plugin_version = "0.0.1";
+
+static nixlBackendEngine *create_engine(const nixlBackendInitParams *) {
+  return nullptr;
+}
+
+static void destroy_engine(nixlBackendEngine *) {}
+
+static const char *get_plugin_name() { return plugin_name; }
+
+static const char *get_plugin_version() { return plugin_version; }
+
+static nixl_b_params_t get_backend_options() { return nixl_b_params_t(); }
+static nixlBackendPlugin plugin = {
+    NIXL_PLUGIN_API_VERSION, create_engine,      destroy_engine,
path: test/gtest/mocks/mock_basic_plugin.cpp
user: login: aranadive
body: Can you put these on separate lines for readability?
created_at: 2025-04-16T22:57:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,47 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mock_dram_engine.h"
+
+namespace mocks {
+namespace dram_plugin {
+
+static constexpr const char *plugin_name = "MOCK_DRAM_PLUGIN";
path: test/gtest/mocks/mock_dram_plugin.cpp
user: login: aranadive
body: MOCK_DRAM
created_at: 2025-04-16T23:13:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,47 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "mock_dram_engine.h"
+
+namespace mocks {
+namespace dram_plugin {
+
+static constexpr const char *plugin_name = "MOCK_DRAM_PLUGIN";
+static constexpr const char *plugin_version = "0.0.1";
+
+static nixlBackendEngine *create_engine(const nixlBackendInitParams *params) {
+  return new MockDramBackendEngine(params);
+}
+
+static void destroy_engine(nixlBackendEngine *engine) { delete engine; }
+
+static const char *get_plugin_name() { return plugin_name; }
+
+static const char *get_plugin_version() { return plugin_version; }
+
+static nixl_b_params_t get_backend_options() { return nixl_b_params_t(); }
+static nixlBackendPlugin plugin = {
+    NIXL_PLUGIN_API_VERSION, create_engine,      destroy_engine,
path: test/gtest/mocks/mock_dram_plugin.cpp
user: login: aranadive
body: On separate lines
created_at: 2025-04-16T23:14:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
path: test/gtest/plugin_manager.cpp
user: login: aranadive
body: MOCK_BASIC and MOCK_DRAM
created_at: 2025-04-16T23:14:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      GTEST_SKIP();
+    }
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      return;
+    }
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames()) {
+      prev_plugins_.insert(plugin);
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_) {
+      plugin_manager_.unloadPlugin(plugin);
+    }
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end()) {
+      return true;
+    }
+    if (loaded_plugins_.find(name) != loaded_plugins_.end()) {
+      return false;
+    }
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end()) {
+      return;
+    }
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(IsLoaded());
+}
+
+TEST_P(LoadMultiplePluginsTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(AreAllLoaded());
+}
+
+TEST_F(LoadedPluginTestFixture, NoLoadedPluginsTest) {
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadSinglePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadMultiplePluginsTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadSimplePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadComplexPluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  UnloadPlugin(mock_basic_plugin_name);
+  UnloadPlugin(mock_dram_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(MockLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(PluginDesc{
+                             .name = mock_basic_plugin_name,
+                             .type = PluginDesc::PluginType::Mock}));
+INSTANTIATE_TEST_SUITE_P(UcxLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(GdsLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(gds_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(UcxMoLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_mo_plugin_desc));
+
+/* Load single plugins tests instantiations. */
path: test/gtest/plugin_manager.cpp
user: login: aranadive
body: Load multiple plugins tests ...
created_at: 2025-04-16T23:26:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      GTEST_SKIP();
+    }
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      return;
+    }
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames()) {
+      prev_plugins_.insert(plugin);
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_) {
+      plugin_manager_.unloadPlugin(plugin);
+    }
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end()) {
+      return true;
+    }
+    if (loaded_plugins_.find(name) != loaded_plugins_.end()) {
+      return false;
+    }
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end()) {
+      return;
+    }
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimlpeLifeCycleTest) {
path: test/gtest/plugin_manager.cpp
user: login: aranadive
body: Typo: SimpleLifeCycleTest
created_at: 2025-04-16T23:27:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      GTEST_SKIP();
+    }
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      return;
+    }
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames()) {
+      prev_plugins_.insert(plugin);
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_) {
+      plugin_manager_.unloadPlugin(plugin);
+    }
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end()) {
+      return true;
+    }
+    if (loaded_plugins_.find(name) != loaded_plugins_.end()) {
+      return false;
+    }
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end()) {
+      return;
+    }
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(IsLoaded());
+}
+
+TEST_P(LoadMultiplePluginsTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(AreAllLoaded());
+}
+
+TEST_F(LoadedPluginTestFixture, NoLoadedPluginsTest) {
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadSinglePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadMultiplePluginsTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadSimplePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadComplexPluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  UnloadPlugin(mock_basic_plugin_name);
+  UnloadPlugin(mock_dram_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(MockLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(PluginDesc{
+                             .name = mock_basic_plugin_name,
+                             .type = PluginDesc::PluginType::Mock}));
+INSTANTIATE_TEST_SUITE_P(UcxLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(GdsLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(gds_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(UcxMoLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_mo_plugin_desc));
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(UcxGdsLoadMultiplePluginInstantiation,
path: test/gtest/plugin_manager.cpp
user: login: aranadive
body: We probably need a better way to test multiple plugins as more plugins are written. For now its okay, but maybe add a TODO here regarding that.
created_at: 2025-04-16T23:31:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include <gtest/gtest.h>
+
+namespace gtest {
+namespace utils {
path: test/gtest/main.cpp
user: login: yosefe
body: do we really need to use namespaces for testing code?
created_at: 2025-04-17T14:48:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
path: test/gtest/meson.build
user: login: yosefe
body: where is USE_VRAM used in gtest?
created_at: 2025-04-17T14:49:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
+else
+    cuda_dependencies = []
+    cpp_args = '-UUSE_VRAM'
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
path: test/gtest/meson.build
user: login: yosefe
body: why is it needed as compile time option?
created_at: 2025-04-17T14:50:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "backend/backend_engine.h"
+#include "backend/backend_plugin.h"
+
+namespace mocks {
+namespace basic_plugin {
+
+static constexpr const char *plugin_name = "MOCK_BASIC";
+static constexpr const char *plugin_version = "0.0.1";
path: test/gtest/mocks/mock_basic_plugin.cpp
user: login: yosefe
body: IMO defining variable names for these is redundant, can just 
`static const char *get_plugin_name() { return "MOCK_BASIC"; }`
created_at: 2025-04-17T14:51:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
path: test/gtest/meson.build
user: login: yosefe
body: can we make gtest optional dependency ? so if it's not found, gtests are not built
if not optional it should be added to README.md
created_at: 2025-04-17T14:57:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
+else
+    cuda_dependencies = []
+    cpp_args = '-UUSE_VRAM'
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
path: test/gtest/meson.build
user: login: etoledano
body: AFAIU there it is not required to have all the plugins installed.
created_at: 2025-04-17T17:01:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
path: test/gtest/meson.build
user: login: etoledano
body: Wasn't required yet, i.e can be removed 
created_at: 2025-04-17T17:01:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include <gtest/gtest.h>
+
+namespace gtest {
+namespace utils {
path: test/gtest/main.cpp
user: login: etoledano
body: Do you see a reason not to?
In the term that applying nixl with a namespace which gtest can be within (or you meant parallel?)
created_at: 2025-04-17T17:03:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,3 +21,6 @@ option('cudapath_inc', type: 'string', value: '', description: 'Include path for
 option('cudapath_lib', type: 'string', value: '', description: 'Library path for CUDA')
 option('cudapath_stub', type: 'string', value: '', description: 'Extra Stub path for CUDA')
 option('static_plugins', type: 'string', value: '', description: 'Plugins to be built-in, comma-separated')
+
+# Tests
+option('test_real_plugins', type: 'boolean', value: false, description: 'Testing the real plugins in addition to the mocks..')
path: meson_options.txt
user: login: etoledano
body: Responded in another comment
created_at: 2025-04-17T17:03:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      GTEST_SKIP();
+    }
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      return;
+    }
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames()) {
+      prev_plugins_.insert(plugin);
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_) {
+      plugin_manager_.unloadPlugin(plugin);
+    }
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end()) {
+      return true;
+    }
+    if (loaded_plugins_.find(name) != loaded_plugins_.end()) {
+      return false;
+    }
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end()) {
+      return;
+    }
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(IsLoaded());
+}
+
+TEST_P(LoadMultiplePluginsTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(AreAllLoaded());
+}
+
+TEST_F(LoadedPluginTestFixture, NoLoadedPluginsTest) {
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadSinglePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadMultiplePluginsTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadSimplePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadComplexPluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  UnloadPlugin(mock_basic_plugin_name);
+  UnloadPlugin(mock_dram_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(MockLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(PluginDesc{
+                             .name = mock_basic_plugin_name,
+                             .type = PluginDesc::PluginType::Mock}));
+INSTANTIATE_TEST_SUITE_P(UcxLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(GdsLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(gds_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(UcxMoLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_mo_plugin_desc));
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(UcxGdsLoadMultiplePluginInstantiation,
path: test/gtest/plugin_manager.cpp
user: login: etoledano
body: Can you please elaborate on what's missing? if so, can we track it in a bug rather than a TODO?
created_at: 2025-04-17T17:05:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      GTEST_SKIP();
+    }
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      return;
+    }
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames()) {
+      prev_plugins_.insert(plugin);
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_) {
+      plugin_manager_.unloadPlugin(plugin);
+    }
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end()) {
+      return true;
+    }
+    if (loaded_plugins_.find(name) != loaded_plugins_.end()) {
+      return false;
+    }
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end()) {
+      return;
+    }
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(IsLoaded());
+}
+
+TEST_P(LoadMultiplePluginsTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(AreAllLoaded());
+}
+
+TEST_F(LoadedPluginTestFixture, NoLoadedPluginsTest) {
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadSinglePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadMultiplePluginsTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadSimplePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadComplexPluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  UnloadPlugin(mock_basic_plugin_name);
+  UnloadPlugin(mock_dram_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(MockLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(PluginDesc{
+                             .name = mock_basic_plugin_name,
+                             .type = PluginDesc::PluginType::Mock}));
+INSTANTIATE_TEST_SUITE_P(UcxLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(GdsLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(gds_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(UcxMoLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_mo_plugin_desc));
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(UcxGdsLoadMultiplePluginInstantiation,
path: test/gtest/plugin_manager.cpp
user: login: aranadive
body: Here you are adding combinations of various plugins to load/unload. This list could get really large once we have more plugins and we need to test more combinations. I suggested to adding a note here to be able to handle all those combinations at a later date. We can instead add a github issue to track this.
created_at: 2025-04-17T17:10:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
path: test/gtest/meson.build
user: login: aranadive
body: Good point. Please make gtests optional. We will always enable these in CI then.
created_at: 2025-04-17T17:15:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+mock_basic_plugin = shared_library('MOCK_BASIC_PLUGIN', 'mock_basic_plugin.cpp',
path: test/gtest/mocks/meson.build
user: login: etoledano
body: We may want to consider using a more common naming convention such `libnixl_ucxplugin`. or even keep `plugin` out.
created_at: 2025-04-17T17:23:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      GTEST_SKIP();
+    }
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      return;
+    }
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames()) {
+      prev_plugins_.insert(plugin);
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_) {
+      plugin_manager_.unloadPlugin(plugin);
+    }
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end()) {
+      return true;
+    }
+    if (loaded_plugins_.find(name) != loaded_plugins_.end()) {
+      return false;
+    }
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end()) {
+      return;
+    }
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(IsLoaded());
+}
+
+TEST_P(LoadMultiplePluginsTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(AreAllLoaded());
+}
+
+TEST_F(LoadedPluginTestFixture, NoLoadedPluginsTest) {
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadSinglePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadMultiplePluginsTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadSimplePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadComplexPluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  UnloadPlugin(mock_basic_plugin_name);
+  UnloadPlugin(mock_dram_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(MockLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(PluginDesc{
+                             .name = mock_basic_plugin_name,
+                             .type = PluginDesc::PluginType::Mock}));
+INSTANTIATE_TEST_SUITE_P(UcxLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(GdsLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(gds_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(UcxMoLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_mo_plugin_desc));
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(UcxGdsLoadMultiplePluginInstantiation,
path: test/gtest/plugin_manager.cpp
user: login: etoledano
body: Do we expect different behavior when loading a plugin depending on which plugin it is? I would assume that if this is the case, then we would need some coverage testing the plugins, rather than the plugin manager.
created_at: 2025-04-17T17:28:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,253 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "nixl.h"
+#include "plugin_manager.h"
+
+namespace gtest {
+namespace plugin_manager {
+struct PluginDesc {
+  enum PluginType { Real, Mock };
+  const char *name;
+  const PluginType type;
+};
+
+/* Utililty print function for readable log. */
+void PrintTo(const PluginDesc &plugin_desc, ::std::ostream *os) {
+  *os << plugin_desc.name;
+}
+
+static constexpr const char *mock_basic_plugin_name = "MOCK_BASIC_PLUGIN";
+static constexpr const char *mock_dram_plugin_name = "MOCK_DRAM_PLUGIN";
+
+const PluginDesc ucx_plugin_desc{.name = "UCX",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc gds_plugin_desc{.name = "GDS",
+                                 .type = PluginDesc::PluginType::Real};
+const PluginDesc ucx_mo_plugin_desc{
+    .name = "UCX_MO", .type = PluginDesc::PluginType::Real};
+
+class LoadSinglePluginTestFixture
+    : public testing::TestWithParam<PluginDesc> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::shared_ptr<nixlPluginHandle> plugin_handle_;
+
+  void SetUp() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      GTEST_SKIP();
+    }
+#endif
+    plugin_handle_ = plugin_manager_.loadPlugin(GetParam().name);
+  }
+
+  void TearDown() override {
+#if !TEST_REAL_PLUGINS
+    if (GetParam().type == PluginDesc::PluginType::Real) {
+      return;
+    }
+#endif
+    plugin_manager_.unloadPlugin(GetParam().name);
+  }
+
+  /* Returns true if the plugin was successfully loaded, otherwise false. */
+  bool IsLoaded() { return plugin_handle_ != nullptr; }
+};
+
+class LoadMultiplePluginsTestFixture
+    : public testing::TestWithParam<std::vector<PluginDesc>> {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::vector<std::shared_ptr<nixlPluginHandle>> plugin_handles_;
+
+  void SetUp() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_handles_.push_back(plugin_manager_.loadPlugin(plugin.name));
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : GetParam()) {
+#if !TEST_REAL_PLUGINS
+      if (plugin.type == PluginDesc::PluginType::Real) {
+        continue;
+      }
+#endif
+      plugin_manager_.unloadPlugin(plugin.name);
+    }
+  }
+
+  /*
+   * Returns true if all the plugins were successfully loaded, otherwise false.
+   */
+  bool AreAllLoaded() {
+    return all_of(
+        plugin_handles_.begin(), plugin_handles_.end(),
+        [](std::shared_ptr<nixlPluginHandle> ptr) { return ptr != nullptr; });
+  }
+};
+
+class LoadedPluginTestFixture : public testing::Test {
+protected:
+  nixlPluginManager &plugin_manager_ = nixlPluginManager::getInstance();
+  std::set<std::string> prev_plugins_;
+  std::set<std::string> loaded_plugins_;
+
+  void SetUp() override {
+    for (const auto &plugin : plugin_manager_.getLoadedPluginNames()) {
+      prev_plugins_.insert(plugin);
+    }
+  }
+
+  void TearDown() override {
+    for (const auto &plugin : loaded_plugins_) {
+      plugin_manager_.unloadPlugin(plugin);
+    }
+  }
+
+  /*
+   * Load a plugin to plugin manager if isn't already loaded.
+   *
+   * Returns true if the plugin loaded successfully, otherwise false.
+   */
+  bool LoadPlugin(std::string name) {
+    if (prev_plugins_.find(name) != prev_plugins_.end()) {
+      return true;
+    }
+    if (loaded_plugins_.find(name) != loaded_plugins_.end()) {
+      return false;
+    }
+    loaded_plugins_.insert(name);
+    return plugin_manager_.loadPlugin(name) != nullptr;
+  }
+
+  /* Unload a plugin from plugin manager if it was loaded during this test. */
+  void UnloadPlugin(std::string name) {
+    if (loaded_plugins_.find(name) == loaded_plugins_.end()) {
+      return;
+    }
+    plugin_manager_.unloadPlugin(name);
+    loaded_plugins_.erase(name);
+  }
+
+  /*
+   * Returns true if the only non static plugins are similar to the loaded ones,
+   * otherwise false.
+   */
+  bool HasOnlyLoadedPlugins() {
+    const auto &pm_loaded = plugin_manager_.getLoadedPluginNames();
+    for (const auto &pm_loaded_plugin : pm_loaded) {
+      if ((prev_plugins_.find(pm_loaded_plugin) == prev_plugins_.end()) &&
+          (loaded_plugins_.find(pm_loaded_plugin) == loaded_plugins_.end())) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+TEST_P(LoadSinglePluginTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(IsLoaded());
+}
+
+TEST_P(LoadMultiplePluginsTestFixture, SimlpeLifeCycleTest) {
+  EXPECT_TRUE(AreAllLoaded());
+}
+
+TEST_F(LoadedPluginTestFixture, NoLoadedPluginsTest) {
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadSinglePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadMultiplePluginsTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadSimplePluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+TEST_F(LoadedPluginTestFixture, LoadUnloadComplexPluginTest) {
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(LoadPlugin(mock_dram_plugin_name));
+  UnloadPlugin(mock_basic_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  EXPECT_TRUE(LoadPlugin(mock_basic_plugin_name));
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+
+  UnloadPlugin(mock_basic_plugin_name);
+  UnloadPlugin(mock_dram_plugin_name);
+  EXPECT_TRUE(HasOnlyLoadedPlugins());
+}
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(MockLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(PluginDesc{
+                             .name = mock_basic_plugin_name,
+                             .type = PluginDesc::PluginType::Mock}));
+INSTANTIATE_TEST_SUITE_P(UcxLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(GdsLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(gds_plugin_desc));
+INSTANTIATE_TEST_SUITE_P(UcxMoLoadPluginInstantiation,
+                         LoadSinglePluginTestFixture,
+                         testing::Values(ucx_mo_plugin_desc));
+
+/* Load single plugins tests instantiations. */
+INSTANTIATE_TEST_SUITE_P(UcxGdsLoadMultiplePluginInstantiation,
path: test/gtest/plugin_manager.cpp
user: login: aranadive
body: No, loading of any plugin does not instantiate it so should be the same for all plugins. The tests in test/unit/plugins cover most/all the specific plugin functionality.
created_at: 2025-04-17T19:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "backend/backend_engine.h"
+#include "backend/backend_plugin.h"
+
+namespace mocks {
+namespace basic_plugin {
+
+static constexpr const char *plugin_name = "MOCK_BASIC";
+static constexpr const char *plugin_version = "0.0.1";
path: test/gtest/mocks/mock_basic_plugin.cpp
user: login: mamar123
body: Done
created_at: 2025-04-20T11:17:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
path: test/gtest/meson.build
user: login: mamar123
body: Done
created_at: 2025-04-20T11:18:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
path: test/gtest/meson.build
user: login: mamar123
body: Removed
created_at: 2025-04-20T11:18:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include <gtest/gtest.h>
+
+namespace gtest {
+namespace utils {
path: test/gtest/main.cpp
user: login: mamar123
body: @yosefe could you please review and resolve your comments ?
created_at: 2025-04-20T12:46:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include <gtest/gtest.h>
+
+namespace gtest {
+namespace utils {
path: test/gtest/main.cpp
user: login: yosefe
body: well, IMO namespaces may complicate things a bit and make code bit harder to maintain, since gtests is a separate executable (not a library), i would keep things as simple as possible in it. if introducing namespaces, i'd do it for NIXL core/api and not necessarily for the gtests. and even if adding namespaces to gtest then maybe no need for 2 nested , and one would be enough
created_at: 2025-04-20T15:47:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
+else
+    cuda_dependencies = []
+    cpp_args = '-UUSE_VRAM'
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
path: test/gtest/meson.build
user: login: yosefe
body: i'd expect gtest to test all internal plugins (including the mock plugins) without the need to install anything (i.e no need to run ninja install). 
and in any case, i'd prefer run-time options (such as --tests_plugin_dirs) over compile time options since it's easier to run this way (no need to recompile) and code looks better (no ifdef)
created_at: 2025-04-20T15:54:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0', required : false)
+
+if not gtest_dep.found()
+    message('GTest not found, skipping gtest build')
+    subdir_done()
+endif
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+else
+    cuda_dependencies = []
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
+endif
+
+test_exe = executable('test_executable',
+    sources : ['main.cpp', 'plugin_manager.cpp'],
+    include_directories: [nixl_inc_dirs, utils_inc_dirs],
+    cpp_args : cpp_flags,
+    dependencies : [nixl_dep, cuda_dep, gtest_dep]
+)
+
+test('gtest', test_exe, args: [plugin_dirs_arg])
path: test/gtest/meson.build
user: login: yosefe
body: shall we add "ninja test" command - and uploading/printing the log file - to CI?
created_at: 2025-04-20T16:16:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0', required : false)
+
+if not gtest_dep.found()
+    message('GTest not found, skipping gtest build')
+    subdir_done()
+endif
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+else
+    cuda_dependencies = []
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
+endif
+
+test_exe = executable('test_executable',
path: test/gtest/meson.build
user: login: yosefe
body: maybe just "gtest" (instead of test_executable)?
created_at: 2025-04-20T16:16:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "backend/backend_engine.h"
+#include "backend/backend_plugin.h"
+
+namespace mocks {
+namespace basic_plugin {
+
+static constexpr const char *plugin_name = "MOCK_BASIC";
+static constexpr const char *plugin_version = "0.0.1";
path: test/gtest/mocks/mock_basic_plugin.cpp
user: login: yosefe
body: seems it is not resolved
created_at: 2025-04-20T16:19:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
+else
+    cuda_dependencies = []
+    cpp_args = '-UUSE_VRAM'
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
path: test/gtest/meson.build
user: login: etoledano
body: I'm not sure about testing all plugins since I assume not any user should have all of them installed, I agree with you for having a runtime option rather than a compile time. @mamar123 can we change the flag to runtime?
created_at: 2025-04-21T07:08:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include <gtest/gtest.h>
+
+namespace gtest {
+namespace utils {
path: test/gtest/main.cpp
user: login: etoledano
body: I agree that `utils` is redundant (@mamar123 can we remove it?) and even contradicts some guidelines. In general, I had in mind having gtest for unit test but keep integration tests using a different suite, hence gtest namespace is to be under testing namespace such that general testing schemes can be shared or not shared using this differentiation, wdyt?
created_at: 2025-04-21T07:16:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0', required : false)
+
+if not gtest_dep.found()
+    message('GTest not found, skipping gtest build')
+    subdir_done()
+endif
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+else
+    cuda_dependencies = []
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
+endif
+
+test_exe = executable('test_executable',
+    sources : ['main.cpp', 'plugin_manager.cpp'],
+    include_directories: [nixl_inc_dirs, utils_inc_dirs],
+    cpp_args : cpp_flags,
+    dependencies : [nixl_dep, cuda_dep, gtest_dep]
+)
+
+test('gtest', test_exe, args: [plugin_dirs_arg])
path: test/gtest/meson.build
user: login: etoledano
body: Sounds good, any changes needed here for this? as preparation
created_at: 2025-04-21T07:17:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
+else
+    cuda_dependencies = []
+    cpp_args = '-UUSE_VRAM'
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
path: test/gtest/meson.build
user: login: yosefe
body: not sure what you mean by "installed" plugins.. for example when you configure NIXL with X,Y,Z backends support, i'd expect gtest to test X,Y,Z + mock.
created_at: 2025-04-21T07:59:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0', required : false)
+
+if not gtest_dep.found()
+    message('GTest not found, skipping gtest build')
+    subdir_done()
+endif
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+else
+    cuda_dependencies = []
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
+endif
+
+test_exe = executable('test_executable',
+    sources : ['main.cpp', 'plugin_manager.cpp'],
+    include_directories: [nixl_inc_dirs, utils_inc_dirs],
+    cpp_args : cpp_flags,
+    dependencies : [nixl_dep, cuda_dep, gtest_dep]
+)
+
+test('gtest', test_exe, args: [plugin_dirs_arg])
path: test/gtest/meson.build
user: login: yosefe
body: i think you can just add "ninja test" this to test_cpp.sh
created_at: 2025-04-21T08:00:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
+else
+    cuda_dependencies = []
+    cpp_args = '-UUSE_VRAM'
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
path: test/gtest/meson.build
user: login: etoledano
body: After some thinking I reminded why I chose compile time over runtime.
Generally we could check during runtime which plugins are available and in each test:
```
TEST(..) {
 if (plugins.find("...")) {
   // Plugin specific code
 }
}
```

But this isn't scalable nor looked like a viable option for coding tests, when looking at the other option of doing it in compile time which also provides more testing ability using `TEST_P`.

This flag of including all the plugins uses the fact the NIXL loads the plugin from a corresponding directory, allowing the tests and the code use the same loaded plugins, so the tests can be written accordingly.

created_at: 2025-04-21T09:26:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,61 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "plugin_manager.h"
+#include <gtest/gtest.h>
+
+namespace gtest {
+namespace utils {
path: test/gtest/main.cpp
user: login: mamar123
body: removing `utils`
created_at: 2025-04-22T08:54:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0')
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+    cpp_args = '-DUSE_VRAM'
+else
+    cuda_dependencies = []
+    cpp_args = '-UUSE_VRAM'
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
path: test/gtest/meson.build
user: login: mamar123
body: For now keeping it at compile time. If a requirement arises for runtime support, we'll make the change accordingly
created_at: 2025-04-22T08:54:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0', required : false)
+
+if not gtest_dep.found()
+    message('GTest not found, skipping gtest build')
+    subdir_done()
+endif
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+else
+    cuda_dependencies = []
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
+endif
+
+test_exe = executable('test_executable',
path: test/gtest/meson.build
user: login: mamar123
body: Done
created_at: 2025-04-22T09:02:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gtest_dep = dependency('gtest', version : '>=1.11.0', required : false)
+
+if not gtest_dep.found()
+    message('GTest not found, skipping gtest build')
+    subdir_done()
+endif
+
+if cuda_dep.found()
+    cuda_dependencies = [cuda_dep]
+else
+    cuda_dependencies = []
+endif
+
+subdir('mocks')
+
+plugin_dirs_arg = '--tests_plugin_dirs=' + mocks_dep.get_variable('path')
+
+cpp_flags = []
+
+if get_option('test_all_plugins')
+  cpp_flags+='-DTEST_ALL_PLUGINS'
+endif
+
+test_exe = executable('test_executable',
+    sources : ['main.cpp', 'plugin_manager.cpp'],
+    include_directories: [nixl_inc_dirs, utils_inc_dirs],
+    cpp_args : cpp_flags,
+    dependencies : [nixl_dep, cuda_dep, gtest_dep]
+)
+
+test('gtest', test_exe, args: [plugin_dirs_arg])
path: test/gtest/meson.build
user: login: mamar123
body: Done
created_at: 2025-04-22T09:38:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+mock_basic_plugin = shared_library('MOCK_BASIC', 'mock_basic_plugin.cpp',
+               dependencies: [nixl_infra],
+               include_directories: [nixl_inc_dirs, utils_inc_dirs],
+               link_with : [ucx_backend_lib],
+               name_prefix: 'libplugin_')
+
+mock_dram_sources = ['mock_dram_plugin.cpp', 'mock_dram_engine.cpp']
+mock_dram_plugin = shared_library('MOCK_DRAM', mock_dram_sources,
+               dependencies: [nixl_infra],
+               include_directories: [nixl_inc_dirs, utils_inc_dirs],
+               link_with : [ucx_backend_lib],
+               name_prefix: 'libplugin_')
+
+source_root = meson.source_root()
path: test/gtest/mocks/meson.build
user: login: etoledano
body: @mamar123 

Seems this creates a warning:
```
test/gtest/mocks/meson.build:36: WARNING: Project targets '>= 0.64.0' but uses feature deprecated since '0.56.0': meson.source_root. use meson.project_source_root() or meson.global_source_root() instead.
Build targets in project: 46
WARNING: Deprecated features used:
* 0.56.0: {'meson.source_root'}
```

Should probably be changed to
`source_root = meson.project_source_root()`
created_at: 2025-04-22T12:49:26Z
author_association: CONTRIBUTOR

PR comments:
user: login: copy-pr-bot[bot]
created_at: 2025-04-14T07:45:46Z
body: This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities [here](https://docs.gha-runners.nvidia.com/cpr/vetters).

Contributors can view more details about this message [here](https://docs.gha-runners.nvidia.com/cpr/contributors).

author_association: NONE
user: login: etoledano
created_at: 2025-04-20T12:40:59Z
body: /ok to test 1348d87c1d6a0e82cd69c12a2c0d29236a43f60e
author_association: CONTRIBUTOR
user: login: etoledano
created_at: 2025-04-20T14:18:09Z
body: /ok to test 599634241ab5d089ec1c97e50da53235bc361bb3
author_association: CONTRIBUTOR
user: login: etoledano
created_at: 2025-04-22T09:57:44Z
body: /ok to test a8b176999a90701ce50972fb1dabddab34504950
author_association: CONTRIBUTOR
user: login: etoledano
created_at: 2025-04-22T09:58:21Z
body: @aranadive PTAL
author_association: CONTRIBUTOR
user: login: etoledano
created_at: 2025-04-22T11:34:08Z
body: /ok to test 79d794310fa30114bd397bb32f0b3a948063423f
author_association: CONTRIBUTOR
user: login: w1ldptr
created_at: 2025-04-22T12:12:34Z
body: The code seems to use 2-whitespace indentation (which is strange, but can be automatically resolved by pending clang-format merge) and also has bunch of single-statement blocks wrapped in curly braces (which AFAIK won't be changed automatically by clang-format).
author_association: CONTRIBUTOR
user: login: etoledano
created_at: 2025-04-22T12:30:06Z
body: > The code seems to use 2-whitespace indentation (which is strange, but can be automatically resolved by pending clang-format merge) and also has bunch of single-statement blocks wrapped in curly braces (which AFAIK won't be changed automatically by clang-format).

Correct, clang will not change those.
@mamar123 Can you please remove braces on a single statements blocks?
author_association: CONTRIBUTOR
user: login: etoledano
created_at: 2025-04-22T17:27:07Z
body: /ok to test fdb5204b45a36fe6c0f810b97e5a2eb4202cbde5
author_association: CONTRIBUTOR