| /** @file |
| |
| Unit tests for a class that deals with remap plugins |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you 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. |
| |
| @section details Details |
| |
| Implements code necessary for Reverse Proxy which mostly consists of |
| general purpose hostname substitution in URLs. |
| |
| */ |
| |
| #define CATCH_CONFIG_MAIN /* include main function */ |
| #include <catch.hpp> /* catch unit-test framework */ |
| #include <fstream> /* ofstream */ |
| #include <string> |
| |
| #include "plugin_testing_common.h" |
| #include "../RemapPluginInfo.h" |
| |
| thread_local PluginThreadContext *pluginThreadContext; |
| |
| static void *INSTANCE_HANDLER = (void *)789; |
| std::error_code ec; |
| |
| /* The following are paths that are used commonly in the unit-tests */ |
| static fs::path sandboxDir = getTemporaryDir(); |
| static fs::path runtimeDir = sandboxDir / "runtime"; |
| static fs::path searchDir = sandboxDir / "search"; |
| static fs::path pluginBuildDir = fs::current_path() / "unit-tests/.libs"; |
| |
| void |
| clean() |
| { |
| fs::remove(sandboxDir, ec); |
| } |
| |
| /* Mock used only to make unit testing convenient to check if callbacks are really called and check errors */ |
| class RemapPluginUnitTest : public RemapPluginInfo |
| { |
| public: |
| RemapPluginUnitTest(const fs::path &configPath, const fs::path &effectivePath, const fs::path &runtimePath) |
| : RemapPluginInfo(configPath, effectivePath, runtimePath) |
| { |
| } |
| std::string |
| getError(const char *required, const char *requiring = nullptr) |
| { |
| return missingRequiredSymbolError(_configPath.string(), required, requiring); |
| } |
| |
| PluginDebugObject * |
| getDebugObject() |
| { |
| std::string error; /* ignore the error, return nullptr if symbol not defined */ |
| void *address = nullptr; |
| getSymbol("getPluginDebugObjectTest", address, error); |
| GetPluginDebugObjectFunction *getObject = reinterpret_cast<GetPluginDebugObjectFunction *>(address); |
| if (getObject) { |
| PluginDebugObject *object = reinterpret_cast<PluginDebugObject *>(getObject()); |
| return object; |
| } else { |
| return nullptr; |
| } |
| } |
| }; |
| |
| RemapPluginUnitTest * |
| setupSandBox(const fs::path configPath) |
| { |
| std::string error; |
| clean(); |
| |
| /* Create the directory structure and install plugins */ |
| CHECK(fs::create_directories(searchDir, ec)); |
| fs::copy(pluginBuildDir / configPath, searchDir, ec); |
| CHECK(fs::create_directories(runtimeDir, ec)); |
| |
| fs::path effectivePath = searchDir / configPath; |
| fs::path runtimePath = runtimeDir / configPath; |
| fs::path pluginBuildPath = pluginBuildDir / configPath; |
| |
| /* Instantiate and initialize a plugin DSO instance. */ |
| RemapPluginUnitTest *plugin = new RemapPluginUnitTest(configPath, effectivePath, runtimePath); |
| |
| return plugin; |
| } |
| |
| bool |
| loadPlugin(RemapPluginUnitTest *plugin, std::string &error, PluginDebugObject *&debugObject) |
| { |
| bool result = plugin->load(error); |
| debugObject = plugin->getDebugObject(); |
| return result; |
| } |
| |
| void |
| cleanupSandBox(RemapPluginInfo *plugin) |
| { |
| delete plugin; |
| clean(); |
| } |
| |
| SCENARIO("loading remap plugins", "[plugin][core]") |
| { |
| REQUIRE_FALSE(sandboxDir.empty()); |
| |
| std::string error; |
| PluginDebugObject *debugObject = nullptr; |
| |
| GIVEN("a plugin which has only minimum required call back functions") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_required_cb.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| WHEN("loading") |
| { |
| bool result = loadPlugin(plugin, error, debugObject); |
| |
| THEN("expect it to successfully load") |
| { |
| CHECK(true == result); |
| CHECK(error.empty()); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| |
| GIVEN("a plugin which is missing the plugin TSREMAP_FUNCNAME_INIT function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_missing_init.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| WHEN("loading") |
| { |
| bool result = loadPlugin(plugin, error, debugObject); |
| |
| THEN("expect it to successfully load") |
| { |
| CHECK_FALSE(result); |
| CHECK(error == plugin->getError(TSREMAP_FUNCNAME_INIT)); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| |
| GIVEN("a plugin which is missing the TSREMAP_FUNCNAME_DO_REMAP function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_missing_doremap.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| WHEN("loading") |
| { |
| bool result = loadPlugin(plugin, error, debugObject); |
| |
| THEN("expect it to fail") |
| { |
| CHECK_FALSE(result); |
| CHECK(error == plugin->getError(TSREMAP_FUNCNAME_DO_REMAP)); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| |
| GIVEN("a plugin which has TSREMAP_FUNCNAME_NEW_INSTANCE but is missing the TSREMAP_FUNCNAME_DELETE_INSTANCE function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_missing_deleteinstance.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| WHEN("loading") |
| { |
| bool result = loadPlugin(plugin, error, debugObject); |
| |
| THEN("expect it to fail") |
| { |
| CHECK_FALSE(result); |
| CHECK(error == plugin->getError(TSREMAP_FUNCNAME_DELETE_INSTANCE, TSREMAP_FUNCNAME_NEW_INSTANCE)); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| |
| GIVEN("a plugin which has TSREMAP_FUNCNAME_DELETE_INSTANCE but is missing the TSREMAP_FUNCNAME_NEW_INSTANCE function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_missing_newinstance.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| WHEN("loading") |
| { |
| bool result = loadPlugin(plugin, error, debugObject); |
| |
| THEN("expect it to fail") |
| { |
| CHECK_FALSE(result); |
| CHECK(error == plugin->getError(TSREMAP_FUNCNAME_NEW_INSTANCE, TSREMAP_FUNCNAME_DELETE_INSTANCE)); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| } |
| |
| void |
| prepCallTest(bool toFail, PluginDebugObject *debugObject) |
| { |
| debugObject->clear(); |
| debugObject->fail = toFail; // Tell the mock init to succeed or succeed. |
| } |
| |
| void |
| checkCallTest(bool shouldHaveFailed, bool result, const std::string &error, std::string &expectedError, int &called) |
| { |
| CHECK(1 == called); // Init was called. |
| if (shouldHaveFailed) { |
| CHECK(false == result); |
| CHECK(error == expectedError); // Appropriate error was returned. |
| } else { |
| CHECK(true == result); // Init succesfull - returned TS_SUCCESS. |
| CHECK(error.empty()); // No error was returned. |
| } |
| } |
| |
| SCENARIO("invoking plugin init", "[plugin][core]") |
| { |
| REQUIRE_FALSE(sandboxDir.empty()); |
| |
| std::string error; |
| PluginDebugObject *debugObject = nullptr; |
| |
| GIVEN("plugin init function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_testing_calls.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| bool result = loadPlugin(plugin, error, debugObject); |
| CHECK(true == result); |
| |
| WHEN("init succeeds") |
| { |
| prepCallTest(/* toFail */ false, debugObject); |
| |
| result = plugin->init(error); |
| |
| THEN("expect init to be called, success code and no error to be returned") |
| { |
| std::string expectedError; |
| |
| checkCallTest(/* shouldHaveFailed */ false, result, error, expectedError, debugObject->initCalled); |
| } |
| cleanupSandBox(plugin); |
| } |
| |
| WHEN("init fails") |
| { |
| prepCallTest(/* toFail */ true, debugObject); |
| |
| result = plugin->init(error); |
| |
| THEN("expect init to be called, failure code and an error to be returned") |
| { |
| std::string expectedError; |
| expectedError.assign("failed to initialize plugin ").append(pluginConfigPath.string()).append(": Init failed"); |
| |
| checkCallTest(/* shouldHaveFailed */ true, result, error, expectedError, debugObject->initCalled); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| } |
| |
| SCENARIO("invoking plugin instance init", "[plugin][core]") |
| { |
| REQUIRE_FALSE(sandboxDir.empty()); |
| |
| std::string error; |
| PluginDebugObject *debugObject = nullptr; |
| void *ih = nullptr; // Instance handler pointer. |
| |
| /* a sample test set of parameters */ |
| static const char *args[] = {"arg1", "arg2", "arg3"}; |
| static char **ARGV = const_cast<char **>(args); |
| static char ARGC = sizeof ARGV; |
| |
| GIVEN("an instance init function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_testing_calls.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| bool result = loadPlugin(plugin, error, debugObject); |
| CHECK(true == result); |
| |
| WHEN("instance init succeeds") |
| { |
| prepCallTest(/* toFail */ false, debugObject); |
| debugObject->input_ih = INSTANCE_HANDLER; /* this is what the plugin instance init will return */ |
| |
| result = plugin->initInstance(ARGC, ARGV, &ih, error); |
| |
| THEN("expect init to be called successfully with no error and expected instance handler") |
| { |
| std::string expectedError; |
| |
| checkCallTest(/* shouldHaveFailed */ false, result, error, expectedError, debugObject->initInstanceCalled); |
| |
| /* Verify expected handler */ |
| CHECK(INSTANCE_HANDLER == ih); |
| /* Plugin received the parameters that we passed */ |
| CHECK(ARGC == debugObject->argc); |
| CHECK(ARGV == debugObject->argv); |
| for (int i = 0; i < 3; i++) { |
| CHECK(0 == strcmp(ARGV[i], debugObject->argv[i])); |
| } |
| } |
| cleanupSandBox(plugin); |
| } |
| |
| WHEN("instance init fails") |
| { |
| prepCallTest(/* toFail */ true, debugObject); |
| |
| result = plugin->initInstance(ARGC, ARGV, &ih, error); |
| |
| THEN("expect init to be called but failed with expected error and no instance handler") |
| { |
| std::string expectedError; |
| expectedError.assign("failed to create instance for plugin ").append(pluginConfigPath.string()).append(": Init failed"); |
| |
| checkCallTest(/* shouldHaveFailed */ true, result, error, expectedError, debugObject->initInstanceCalled); |
| |
| /* Ideally instance handler should not be touched in case of failure */ |
| CHECK(nullptr == ih); |
| /* Plugin received the parameters that we passed */ |
| CHECK(ARGC == debugObject->argc); |
| CHECK(ARGV == debugObject->argv); |
| for (int i = 0; i < 3; i++) { |
| CHECK(0 == strcmp(ARGV[i], debugObject->argv[i])); |
| } |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| } |
| |
| SCENARIO("unloading the plugin", "[plugin][core]") |
| { |
| REQUIRE_FALSE(sandboxDir.empty()); |
| |
| std::string error; |
| PluginDebugObject *debugObject = nullptr; |
| |
| GIVEN("a 'done' function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_testing_calls.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| bool result = loadPlugin(plugin, error, debugObject); |
| CHECK(true == result); |
| |
| WHEN("'done' is called") |
| { |
| debugObject->clear(); |
| |
| plugin->done(); |
| |
| THEN("expect it to run") { CHECK(1 == debugObject->doneCalled); } |
| cleanupSandBox(plugin); |
| } |
| } |
| |
| GIVEN("a 'delete_instance' function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_testing_calls.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| bool result = loadPlugin(plugin, error, debugObject); |
| CHECK(true == result); |
| |
| WHEN("'delete_instance' is called") |
| { |
| debugObject->clear(); |
| |
| plugin->doneInstance(INSTANCE_HANDLER); |
| |
| THEN("expect it to run and receive the right instance handler") |
| { |
| CHECK(1 == debugObject->deleteInstanceCalled); |
| CHECK(INSTANCE_HANDLER == debugObject->ih); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| } |
| |
| SCENARIO("config reload", "[plugin][core]") |
| { |
| REQUIRE_FALSE(sandboxDir.empty()); |
| |
| std::string error; |
| PluginDebugObject *debugObject = nullptr; |
| |
| GIVEN("a 'config reload' callback function") |
| { |
| fs::path pluginConfigPath = fs::path("plugin_testing_calls.so"); |
| RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath); |
| |
| bool result = loadPlugin(plugin, error, debugObject); |
| CHECK(true == result); |
| |
| WHEN("'config reload' failed") |
| { |
| debugObject->clear(); |
| |
| plugin->indicatePreReload(); |
| plugin->indicatePostReload(TSREMAP_CONFIG_RELOAD_FAILURE); |
| |
| THEN("expect it to run") |
| { |
| CHECK(1 == debugObject->preReloadConfigCalled); |
| CHECK(1 == debugObject->postReloadConfigCalled); |
| CHECK(TSREMAP_CONFIG_RELOAD_FAILURE == debugObject->postReloadConfigStatus); |
| } |
| cleanupSandBox(plugin); |
| } |
| |
| WHEN("'config reload' is successful and the plugin is part of the new configuration") |
| { |
| debugObject->clear(); |
| |
| plugin->indicatePreReload(); |
| plugin->indicatePostReload(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED); |
| |
| THEN("expect it to run") |
| { |
| CHECK(1 == debugObject->preReloadConfigCalled); |
| CHECK(1 == debugObject->postReloadConfigCalled); |
| CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject->postReloadConfigStatus); |
| } |
| cleanupSandBox(plugin); |
| } |
| |
| WHEN("'config reload' is successful and the plugin is part of the new configuration") |
| { |
| debugObject->clear(); |
| |
| plugin->indicatePreReload(); |
| plugin->indicatePostReload(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_UNUSED); |
| |
| THEN("expect it to run") |
| { |
| CHECK(1 == debugObject->preReloadConfigCalled); |
| CHECK(1 == debugObject->postReloadConfigCalled); |
| CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_UNUSED == debugObject->postReloadConfigStatus); |
| } |
| cleanupSandBox(plugin); |
| } |
| } |
| } |