| /** @file |
| |
| Unit tests for a class that deals with plugin Dynamic Shared Objects (DSO) |
| |
| @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 "plugin_testing_common.h" |
| #include "../PluginDso.h" |
| |
| class PluginContext; |
| thread_local PluginThreadContext *pluginThreadContext; |
| |
| std::error_code ec; |
| |
| /* The following are dirs that are used commonly in the unit-tests */ |
| static fs::path sandboxDir = getTemporaryDir(); |
| static fs::path runtimeDir = sandboxDir / fs::path("runtime"); |
| static fs::path searchDir = sandboxDir / fs::path("search"); |
| static fs::path pluginBuildDir = fs::current_path() / fs::path("unit-tests/.libs"); |
| |
| /* The following are paths used in all scenarios in the unit tests */ |
| static fs::path configPath = fs::path("plugin_v1.so"); |
| static fs::path pluginBuildPath = pluginBuildDir / configPath; |
| static fs::path effectivePath = searchDir / configPath; |
| static fs::path runtimePath = runtimeDir / configPath; |
| |
| void |
| clean() |
| { |
| fs::remove(sandboxDir, ec); |
| } |
| |
| /* Mock used only to make PluginDso concrete enough to be tested */ |
| class PluginDsoUnitTest : public PluginDso |
| { |
| public: |
| PluginDsoUnitTest(const fs::path &configPath, const fs::path &effectivePath, const fs::path &runtimePath) |
| : PluginDso(configPath, effectivePath, runtimePath) |
| { |
| /* don't remove runtime DSO copy preventively so we can check if it was created properly */ |
| _preventiveCleaning = false; |
| } |
| |
| virtual void |
| indicatePreReload() |
| { |
| } |
| virtual void |
| indicatePostReload(TSRemapReloadStatus reloadStatus) |
| { |
| } |
| virtual bool |
| init(std::string &error) |
| { |
| return true; |
| } |
| virtual void |
| done() |
| { |
| } |
| }; |
| |
| /* |
| * The following scenario tests loading and unloading of plugins |
| */ |
| SCENARIO("loading plugins", "[plugin][core]") |
| { |
| REQUIRE_FALSE(sandboxDir.empty()); |
| |
| clean(); |
| std::string error; |
| |
| GIVEN("a valid plugin") |
| { |
| /* Setup the test fixture - search, runtime dirs and install a plugin with some defined callback functions */ |
| CHECK(fs::create_directories(searchDir, ec)); |
| CHECK(fs::create_directories(runtimeDir, ec)); |
| fs::copy(pluginBuildPath, searchDir, ec); |
| |
| /* Instantiate and initialize a plugin DSO instance. Make sure effective path exists, used to load */ |
| CHECK(fs::exists(effectivePath)); |
| PluginDsoUnitTest plugin(configPath, effectivePath, runtimePath); |
| |
| WHEN("loading a valid plugin") |
| { |
| bool result = plugin.load(error); |
| |
| THEN("expect it to successfully load") |
| { |
| CHECK(true == result); |
| CHECK(error.empty()); |
| CHECK(effectivePath == plugin.effectivePath()); |
| CHECK(runtimePath == plugin.runtimePath()); |
| CHECK(fs::exists(runtimePath)); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("loading a valid plugin") |
| { |
| bool result = plugin.load(error); |
| |
| THEN("expect saving the right DSO file modification time") |
| { |
| CHECK(true == result); |
| CHECK(error.empty()); |
| std::error_code ec; |
| fs::file_status fs = fs::status(effectivePath, ec); |
| CHECK(plugin.modTime() == fs::modification_time(fs)); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("loading a valid plugin but missing runtime dir") |
| { |
| CHECK(fs::remove(runtimeDir, ec)); |
| CHECK_FALSE(fs::exists(runtimePath)); |
| bool result = plugin.load(error); |
| |
| THEN("expect it to fail") |
| { |
| CHECK_FALSE(true == result); |
| CHECK("failed to create a copy: No such file or directory" == error); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("loading a valid plugin twice in a row") |
| { |
| /* First attempt OK */ |
| bool result = plugin.load(error); |
| CHECK(true == result); |
| CHECK(error.empty()); |
| |
| /* Second attempt */ |
| result = plugin.load(error); |
| |
| THEN("expect it to fail the second attempt") |
| { |
| CHECK_FALSE(true == result); |
| CHECK("plugin already loaded" == error); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("explicitly unloading a valid but not loaded plugin") |
| { |
| /* Make sure it is not loaded, runtime DSO not present */ |
| CHECK_FALSE(fs::exists(runtimePath)); |
| |
| /* Unload w/o loading beforehand */ |
| bool result = plugin.unload(error); |
| |
| THEN("expect the unload to fail") |
| { |
| CHECK(false == result); |
| CHECK_FALSE(error.empty()); |
| CHECK_FALSE(fs::exists(runtimePath)); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("unloading a valid plugin twice in a row") |
| { |
| /* First attempt OK */ |
| bool result = plugin.load(error); |
| CHECK(true == result); |
| CHECK(error.empty()); |
| result = plugin.unload(error); |
| CHECK(true == result); |
| CHECK("" == error); |
| |
| /* Second attempt */ |
| result = plugin.unload(error); |
| |
| THEN("expect it to fail the second attempt") |
| { |
| CHECK_FALSE(true == result); |
| CHECK("no plugin loaded" == error); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("explicitly unloading a valid and loaded plugin") |
| { |
| /* Make sure it is not loaded, runtime DSO not present */ |
| CHECK_FALSE(fs::exists(runtimePath)); |
| |
| /* Load and make sure it is loaded */ |
| CHECK(plugin.load(error)); |
| /* Effective and runtime path set */ |
| CHECK(effectivePath == plugin.effectivePath()); |
| CHECK(runtimePath == plugin.runtimePath()); |
| /* Runtime DSO should be present */ |
| CHECK(fs::exists(runtimePath)); |
| |
| /* Unload */ |
| bool result = plugin.unload(error); |
| |
| THEN("expect it to successfully unload") |
| { |
| CHECK(true == result); |
| CHECK(error.empty()); |
| /* Effective and runtime path still set */ |
| CHECK(effectivePath == plugin.effectivePath()); |
| CHECK(runtimePath == plugin.runtimePath()); |
| /* Runtime DSO should not be found anymore */ |
| CHECK_FALSE(fs::exists(runtimePath)); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("implicitly unloading a valid and loaded plugin") |
| { |
| { |
| PluginDsoUnitTest localPlugin(configPath, effectivePath, runtimePath); |
| |
| /* Load and make sure it is loaded */ |
| CHECK(localPlugin.load(error)); |
| /* Effective and runtime path set */ |
| CHECK(effectivePath == localPlugin.effectivePath()); |
| CHECK(runtimePath == localPlugin.runtimePath()); |
| /* Runtime DSO should be present */ |
| CHECK(fs::exists(runtimePath)); |
| |
| /* Unload by going out of scope */ |
| } |
| |
| THEN("expect it to successfully unload and clean after itself") |
| { |
| /* Runtime path should be removed after unloading */ |
| CHECK_FALSE(fs::exists(runtimePath)); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| } |
| |
| GIVEN("a plugin instance initialized with an empty effective path") |
| { |
| std::string error; |
| PluginDsoUnitTest plugin(configPath, /* effectivePath */ fs::path(), runtimePath); |
| |
| WHEN("loading the plugin") |
| { |
| bool result = plugin.load(error); |
| |
| THEN("expect the load to fail") |
| { |
| CHECK_FALSE(true == result); |
| CHECK("empty effective path" == error); |
| CHECK(plugin.effectivePath().empty()); |
| CHECK(0 == plugin.modTime()); |
| CHECK(runtimePath == plugin.runtimePath()); |
| CHECK_FALSE(fs::exists(runtimePath)); |
| } |
| } |
| } |
| |
| GIVEN("an invalid plugin") |
| { |
| /* Create the directory structure and install plugins */ |
| CHECK(fs::create_directories(searchDir, ec)); |
| CHECK(fs::create_directories(runtimeDir, ec)); |
| /* Create an invalid plugin and make sure the effective path to it exists */ |
| std::ofstream file(effectivePath.string()); |
| file << "Invalid plugin DSO content"; |
| file.close(); |
| CHECK(fs::exists(effectivePath)); |
| |
| /* Instantiate and initialize a plugin DSO instance. */ |
| std::string error; |
| PluginDsoUnitTest plugin(configPath, effectivePath, runtimePath); |
| |
| WHEN("loading an invalid plugin") |
| { |
| bool result = plugin.load(error); |
| |
| THEN("expect it to fail to load") |
| { |
| /* After calling load() the following should be set correctly */ |
| CHECK(effectivePath == plugin.effectivePath()); |
| CHECK(runtimePath == plugin.runtimePath()); |
| |
| /* But the load should fail and an error should be returned */ |
| CHECK(false == result); |
| CHECK_FALSE(error.empty()); |
| |
| /* Runtime DSO should not exist since the load failed. */ |
| CHECK_FALSE(fs::exists(runtimePath)); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| } |
| } |
| |
| /* |
| * The following scenario tests finding symbols inside the DSO. |
| */ |
| SCENARIO("looking for symbols inside a plugin DSO", "[plugin][core]") |
| { |
| REQUIRE_FALSE(sandboxDir.empty()); |
| |
| clean(); |
| std::string error; |
| |
| /* Setup the test fixture - search, runtime dirs and install a plugin with some defined callback functions */ |
| CHECK(fs::create_directories(searchDir, ec)); |
| CHECK(fs::create_directories(runtimeDir, ec)); |
| fs::copy(pluginBuildDir / configPath, searchDir, ec); |
| |
| /* Initialize a plugin DSO instance */ |
| PluginDsoUnitTest plugin(configPath, effectivePath, runtimePath); |
| |
| /* Now test away. */ |
| GIVEN("plugin loaded successfully") |
| { |
| CHECK(plugin.load(error)); |
| |
| WHEN("looking for an existing symbol") |
| { |
| THEN("expect to find it") |
| { |
| void *s = nullptr; |
| CHECK(plugin.getSymbol("TSRemapInit", s, error)); |
| CHECK(nullptr != s); |
| CHECK(error.empty()); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("looking for non-existing symbol") |
| { |
| THEN("expect not to find it and get an error") |
| { |
| void *s = nullptr; |
| CHECK_FALSE(plugin.getSymbol("NONEXISTING_SYMBOL", s, error)); |
| CHECK(nullptr == s); |
| CHECK_FALSE(error.empty()); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| WHEN("looking for multiple existing symbols") |
| { |
| THEN("expect to find them all") |
| { |
| std::vector<const char *> list{"TSRemapInit", "TSRemapDone", "TSRemapDoRemap", "TSRemapNewInstance", |
| "TSRemapDeleteInstance", "TSRemapOSResponse", "TSPluginInit", "pluginDsoVersionTest"}; |
| for (auto symbol : list) { |
| void *s = nullptr; |
| CHECK(plugin.getSymbol(symbol, s, error)); |
| CHECK(nullptr != s); |
| CHECK(error.empty()); |
| } |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| |
| /* The following version function is used only for unit-testing of the plugin factory functionality */ |
| WHEN("using a symbol to call the corresponding version function") |
| { |
| THEN("expect to return the version number") |
| { |
| void *s = nullptr; |
| CHECK(plugin.getSymbol("pluginDsoVersionTest", s, error)); |
| int (*version)() = reinterpret_cast<int (*)()>(s); |
| int ver = version ? version() : -1; |
| CHECK(1 == ver); |
| } |
| CHECK(fs::remove(sandboxDir, ec)); |
| } |
| } |
| } |