1 /** @file
2 
3   Unit tests for a class that deals with remap plugins
4 
5   @section license License
6 
7   Licensed to the Apache Software Foundation (ASF) under one
8   or more contributor license agreements.  See the NOTICE file
9   distributed with this work for additional information
10   regarding copyright ownership.  The ASF licenses this file
11   to you under the Apache License, Version 2.0 (the
12   "License"); you may not use this file except in compliance
13   with the License.  You may obtain a copy of the License at
14 
15       http://www.apache.org/licenses/LICENSE-2.0
16 
17   Unless required by applicable law or agreed to in writing, software
18   distributed under the License is distributed on an "AS IS" BASIS,
19   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20   See the License for the specific language governing permissions and
21   limitations under the License.
22 
23   @section details Details
24 
25   Implements code necessary for Reverse Proxy which mostly consists of
26   general purpose hostname substitution in URLs.
27 
28  */
29 
30 #define CATCH_CONFIG_MAIN /* include main function */
31 #include <catch.hpp>      /* catch unit-test framework */
32 #include <fstream>        /* ofstream */
33 #include <string>
34 
35 #include "plugin_testing_common.h"
36 #include "../RemapPluginInfo.h"
37 
38 thread_local PluginThreadContext *pluginThreadContext;
39 
40 static void *INSTANCE_HANDLER = (void *)789;
41 std::error_code ec;
42 
43 /* The following are paths that are used commonly in the unit-tests */
44 static fs::path sandboxDir     = getTemporaryDir();
45 static fs::path runtimeDir     = sandboxDir / "runtime";
46 static fs::path searchDir      = sandboxDir / "search";
47 static fs::path pluginBuildDir = fs::current_path() / "unit-tests/.libs";
48 
49 void
clean()50 clean()
51 {
52   fs::remove(sandboxDir, ec);
53 }
54 
55 /* Mock used only to make unit testing convenient to check if callbacks are really called and check errors */
56 class RemapPluginUnitTest : public RemapPluginInfo
57 {
58 public:
RemapPluginUnitTest(const fs::path & configPath,const fs::path & effectivePath,const fs::path & runtimePath)59   RemapPluginUnitTest(const fs::path &configPath, const fs::path &effectivePath, const fs::path &runtimePath)
60     : RemapPluginInfo(configPath, effectivePath, runtimePath)
61   {
62   }
63   std::string
getError(const char * required,const char * requiring=nullptr)64   getError(const char *required, const char *requiring = nullptr)
65   {
66     return missingRequiredSymbolError(_configPath.string(), required, requiring);
67   }
68 
69   PluginDebugObject *
getDebugObject()70   getDebugObject()
71   {
72     std::string error; /* ignore the error, return nullptr if symbol not defined */
73     void *address = nullptr;
74     getSymbol("getPluginDebugObjectTest", address, error);
75     GetPluginDebugObjectFunction *getObject = reinterpret_cast<GetPluginDebugObjectFunction *>(address);
76     if (getObject) {
77       PluginDebugObject *object = reinterpret_cast<PluginDebugObject *>(getObject());
78       return object;
79     } else {
80       return nullptr;
81     }
82   }
83 };
84 
85 RemapPluginUnitTest *
setupSandBox(const fs::path configPath)86 setupSandBox(const fs::path configPath)
87 {
88   std::string error;
89   clean();
90 
91   /* Create the directory structure and install plugins */
92   CHECK(fs::create_directories(searchDir, ec));
93   fs::copy(pluginBuildDir / configPath, searchDir, ec);
94   CHECK(fs::create_directories(runtimeDir, ec));
95 
96   fs::path effectivePath   = searchDir / configPath;
97   fs::path runtimePath     = runtimeDir / configPath;
98   fs::path pluginBuildPath = pluginBuildDir / configPath;
99 
100   /* Instantiate and initialize a plugin DSO instance. */
101   RemapPluginUnitTest *plugin = new RemapPluginUnitTest(configPath, effectivePath, runtimePath);
102 
103   return plugin;
104 }
105 
106 bool
loadPlugin(RemapPluginUnitTest * plugin,std::string & error,PluginDebugObject * & debugObject)107 loadPlugin(RemapPluginUnitTest *plugin, std::string &error, PluginDebugObject *&debugObject)
108 {
109   bool result = plugin->load(error);
110   debugObject = plugin->getDebugObject();
111   return result;
112 }
113 
114 void
cleanupSandBox(RemapPluginInfo * plugin)115 cleanupSandBox(RemapPluginInfo *plugin)
116 {
117   delete plugin;
118   clean();
119 }
120 
121 SCENARIO("loading remap plugins", "[plugin][core]")
122 {
123   REQUIRE_FALSE(sandboxDir.empty());
124 
125   std::string error;
126   PluginDebugObject *debugObject = nullptr;
127 
128   GIVEN("a plugin which has only minimum required call back functions")
129   {
130     fs::path pluginConfigPath   = fs::path("plugin_required_cb.so");
131     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
132 
133     WHEN("loading")
134     {
135       bool result = loadPlugin(plugin, error, debugObject);
136 
137       THEN("expect it to successfully load")
138       {
139         CHECK(true == result);
140         CHECK(error.empty());
141       }
142       cleanupSandBox(plugin);
143     }
144   }
145 
146   GIVEN("a plugin which is missing the plugin TSREMAP_FUNCNAME_INIT function")
147   {
148     fs::path pluginConfigPath   = fs::path("plugin_missing_init.so");
149     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
150 
151     WHEN("loading")
152     {
153       bool result = loadPlugin(plugin, error, debugObject);
154 
155       THEN("expect it to successfully load")
156       {
157         CHECK_FALSE(result);
158         CHECK(error == plugin->getError(TSREMAP_FUNCNAME_INIT));
159       }
160       cleanupSandBox(plugin);
161     }
162   }
163 
164   GIVEN("a plugin which is missing the TSREMAP_FUNCNAME_DO_REMAP function")
165   {
166     fs::path pluginConfigPath   = fs::path("plugin_missing_doremap.so");
167     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
168 
169     WHEN("loading")
170     {
171       bool result = loadPlugin(plugin, error, debugObject);
172 
173       THEN("expect it to fail")
174       {
175         CHECK_FALSE(result);
176         CHECK(error == plugin->getError(TSREMAP_FUNCNAME_DO_REMAP));
177       }
178       cleanupSandBox(plugin);
179     }
180   }
181 
182   GIVEN("a plugin which has TSREMAP_FUNCNAME_NEW_INSTANCE but is missing the TSREMAP_FUNCNAME_DELETE_INSTANCE function")
183   {
184     fs::path pluginConfigPath   = fs::path("plugin_missing_deleteinstance.so");
185     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
186 
187     WHEN("loading")
188     {
189       bool result = loadPlugin(plugin, error, debugObject);
190 
191       THEN("expect it to fail")
192       {
193         CHECK_FALSE(result);
194         CHECK(error == plugin->getError(TSREMAP_FUNCNAME_DELETE_INSTANCE, TSREMAP_FUNCNAME_NEW_INSTANCE));
195       }
196       cleanupSandBox(plugin);
197     }
198   }
199 
200   GIVEN("a plugin which has TSREMAP_FUNCNAME_DELETE_INSTANCE but is missing the TSREMAP_FUNCNAME_NEW_INSTANCE function")
201   {
202     fs::path pluginConfigPath   = fs::path("plugin_missing_newinstance.so");
203     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
204 
205     WHEN("loading")
206     {
207       bool result = loadPlugin(plugin, error, debugObject);
208 
209       THEN("expect it to fail")
210       {
211         CHECK_FALSE(result);
212         CHECK(error == plugin->getError(TSREMAP_FUNCNAME_NEW_INSTANCE, TSREMAP_FUNCNAME_DELETE_INSTANCE));
213       }
214       cleanupSandBox(plugin);
215     }
216   }
217 }
218 
219 void
prepCallTest(bool toFail,PluginDebugObject * debugObject)220 prepCallTest(bool toFail, PluginDebugObject *debugObject)
221 {
222   debugObject->clear();
223   debugObject->fail = toFail; // Tell the mock init to succeed or succeed.
224 }
225 
226 void
checkCallTest(bool shouldHaveFailed,bool result,const std::string & error,std::string & expectedError,int & called)227 checkCallTest(bool shouldHaveFailed, bool result, const std::string &error, std::string &expectedError, int &called)
228 {
229   CHECK(1 == called); // Init was called.
230   if (shouldHaveFailed) {
231     CHECK(false == result);
232     CHECK(error == expectedError); // Appropriate error was returned.
233   } else {
234     CHECK(true == result); // Init succesfull - returned TS_SUCCESS.
235     CHECK(error.empty());  // No error was returned.
236   }
237 }
238 
239 SCENARIO("invoking plugin init", "[plugin][core]")
240 {
241   REQUIRE_FALSE(sandboxDir.empty());
242 
243   std::string error;
244   PluginDebugObject *debugObject = nullptr;
245 
246   GIVEN("plugin init function")
247   {
248     fs::path pluginConfigPath   = fs::path("plugin_testing_calls.so");
249     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
250 
251     bool result = loadPlugin(plugin, error, debugObject);
252     CHECK(true == result);
253 
254     WHEN("init succeeds")
255     {
256       prepCallTest(/* toFail */ false, debugObject);
257 
258       result = plugin->init(error);
259 
260       THEN("expect init to be called, success code and no error to be returned")
261       {
262         std::string expectedError;
263 
264         checkCallTest(/* shouldHaveFailed */ false, result, error, expectedError, debugObject->initCalled);
265       }
266       cleanupSandBox(plugin);
267     }
268 
269     WHEN("init fails")
270     {
271       prepCallTest(/* toFail */ true, debugObject);
272 
273       result = plugin->init(error);
274 
275       THEN("expect init to be called, failure code and an error to be returned")
276       {
277         std::string expectedError;
278         expectedError.assign("failed to initialize plugin ").append(pluginConfigPath.string()).append(": Init failed");
279 
280         checkCallTest(/* shouldHaveFailed */ true, result, error, expectedError, debugObject->initCalled);
281       }
282       cleanupSandBox(plugin);
283     }
284   }
285 }
286 
287 SCENARIO("invoking plugin instance init", "[plugin][core]")
288 {
289   REQUIRE_FALSE(sandboxDir.empty());
290 
291   std::string error;
292   PluginDebugObject *debugObject = nullptr;
293   void *ih                       = nullptr; // Instance handler pointer.
294 
295   /* a sample test set of parameters */
296   static const char *args[] = {"arg1", "arg2", "arg3"};
297   static char **ARGV        = const_cast<char **>(args);
298   static char ARGC          = sizeof ARGV;
299 
300   GIVEN("an instance init function")
301   {
302     fs::path pluginConfigPath   = fs::path("plugin_testing_calls.so");
303     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
304 
305     bool result = loadPlugin(plugin, error, debugObject);
306     CHECK(true == result);
307 
308     WHEN("instance init succeeds")
309     {
310       prepCallTest(/* toFail */ false, debugObject);
311       debugObject->input_ih = INSTANCE_HANDLER; /* this is what the plugin instance init will return */
312 
313       result = plugin->initInstance(ARGC, ARGV, &ih, error);
314 
315       THEN("expect init to be called successfully with no error and expected instance handler")
316       {
317         std::string expectedError;
318 
319         checkCallTest(/* shouldHaveFailed */ false, result, error, expectedError, debugObject->initInstanceCalled);
320 
321         /* Verify expected handler */
322         CHECK(INSTANCE_HANDLER == ih);
323         /* Plugin received the parameters that we passed */
324         CHECK(ARGC == debugObject->argc);
325         CHECK(ARGV == debugObject->argv);
326         for (int i = 0; i < 3; i++) {
327           CHECK(0 == strcmp(ARGV[i], debugObject->argv[i]));
328         }
329       }
330       cleanupSandBox(plugin);
331     }
332 
333     WHEN("instance init fails")
334     {
335       prepCallTest(/* toFail */ true, debugObject);
336 
337       result = plugin->initInstance(ARGC, ARGV, &ih, error);
338 
339       THEN("expect init to be called but failed with expected error and no instance handler")
340       {
341         std::string expectedError;
342         expectedError.assign("failed to create instance for plugin ").append(pluginConfigPath.string()).append(": Init failed");
343 
344         checkCallTest(/* shouldHaveFailed */ true, result, error, expectedError, debugObject->initInstanceCalled);
345 
346         /* Ideally instance handler should not be touched in case of failure */
347         CHECK(nullptr == ih);
348         /* Plugin received the parameters that we passed */
349         CHECK(ARGC == debugObject->argc);
350         CHECK(ARGV == debugObject->argv);
351         for (int i = 0; i < 3; i++) {
352           CHECK(0 == strcmp(ARGV[i], debugObject->argv[i]));
353         }
354       }
355       cleanupSandBox(plugin);
356     }
357   }
358 }
359 
360 SCENARIO("unloading the plugin", "[plugin][core]")
361 {
362   REQUIRE_FALSE(sandboxDir.empty());
363 
364   std::string error;
365   PluginDebugObject *debugObject = nullptr;
366 
367   GIVEN("a 'done' function")
368   {
369     fs::path pluginConfigPath   = fs::path("plugin_testing_calls.so");
370     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
371 
372     bool result = loadPlugin(plugin, error, debugObject);
373     CHECK(true == result);
374 
375     WHEN("'done' is called")
376     {
377       debugObject->clear();
378 
379       plugin->done();
380 
381       THEN("expect it to run") { CHECK(1 == debugObject->doneCalled); }
382       cleanupSandBox(plugin);
383     }
384   }
385 
386   GIVEN("a 'delete_instance' function")
387   {
388     fs::path pluginConfigPath   = fs::path("plugin_testing_calls.so");
389     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
390 
391     bool result = loadPlugin(plugin, error, debugObject);
392     CHECK(true == result);
393 
394     WHEN("'delete_instance' is called")
395     {
396       debugObject->clear();
397 
398       plugin->doneInstance(INSTANCE_HANDLER);
399 
400       THEN("expect it to run and receive the right instance handler")
401       {
402         CHECK(1 == debugObject->deleteInstanceCalled);
403         CHECK(INSTANCE_HANDLER == debugObject->ih);
404       }
405       cleanupSandBox(plugin);
406     }
407   }
408 }
409 
410 SCENARIO("config reload", "[plugin][core]")
411 {
412   REQUIRE_FALSE(sandboxDir.empty());
413 
414   std::string error;
415   PluginDebugObject *debugObject = nullptr;
416 
417   GIVEN("a 'config reload' callback function")
418   {
419     fs::path pluginConfigPath   = fs::path("plugin_testing_calls.so");
420     RemapPluginUnitTest *plugin = setupSandBox(pluginConfigPath);
421 
422     bool result = loadPlugin(plugin, error, debugObject);
423     CHECK(true == result);
424 
425     WHEN("'config reload' failed")
426     {
427       debugObject->clear();
428 
429       plugin->indicatePreReload();
430       plugin->indicatePostReload(TSREMAP_CONFIG_RELOAD_FAILURE);
431 
432       THEN("expect it to run")
433       {
434         CHECK(1 == debugObject->preReloadConfigCalled);
435         CHECK(1 == debugObject->postReloadConfigCalled);
436         CHECK(TSREMAP_CONFIG_RELOAD_FAILURE == debugObject->postReloadConfigStatus);
437       }
438       cleanupSandBox(plugin);
439     }
440 
441     WHEN("'config reload' is successful and the plugin is part of the new configuration")
442     {
443       debugObject->clear();
444 
445       plugin->indicatePreReload();
446       plugin->indicatePostReload(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED);
447 
448       THEN("expect it to run")
449       {
450         CHECK(1 == debugObject->preReloadConfigCalled);
451         CHECK(1 == debugObject->postReloadConfigCalled);
452         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject->postReloadConfigStatus);
453       }
454       cleanupSandBox(plugin);
455     }
456 
457     WHEN("'config reload' is successful and the plugin is part of the new configuration")
458     {
459       debugObject->clear();
460 
461       plugin->indicatePreReload();
462       plugin->indicatePostReload(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_UNUSED);
463 
464       THEN("expect it to run")
465       {
466         CHECK(1 == debugObject->preReloadConfigCalled);
467         CHECK(1 == debugObject->postReloadConfigCalled);
468         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_UNUSED == debugObject->postReloadConfigStatus);
469       }
470       cleanupSandBox(plugin);
471     }
472   }
473 }
474