1 /** @file
2 
3   Unit tests for a class that deals with plugin Dynamic Shared Objects (DSO)
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 <utime.h>
34 
35 #include "plugin_testing_common.h"
36 #include "../PluginFactory.h"
37 #include "../PluginDso.h"
38 #include "I_EventSystem.h"
39 #include "tscore/I_Layout.h"
40 #include "diags.i"
41 
42 #define TEST_THREADS 2
43 struct EventProcessorListener : Catch::TestEventListenerBase {
44   using TestEventListenerBase::TestEventListenerBase;
45 
46   void
testRunStartingEventProcessorListener47   testRunStarting(Catch::TestRunInfo const &testRunInfo) override
48   {
49     Layout::create();
50     init_diags("", nullptr);
51     RecProcessInit(RECM_STAND_ALONE);
52 
53     ink_event_system_init(EVENT_SYSTEM_MODULE_PUBLIC_VERSION);
54     eventProcessor.start(TEST_THREADS, 1048576);
55 
56     EThread *main_thread = new EThread;
57     main_thread->set_specific();
58   }
59 };
60 
61 CATCH_REGISTER_LISTENER(EventProcessorListener);
62 
63 thread_local PluginThreadContext *pluginThreadContext;
64 
65 std::error_code ec;
66 static void *INSTANCE_HANDLER = (void *)789;
67 
68 /* Mock of PluginFactory just to get consisten UUID to be able to test consistently */
69 static fs::path tempComponent = fs::path("c71e2bab-90dc-4770-9535-c9304c3de38e");
70 class PluginFactoryUnitTest : public PluginFactory
71 {
72 public:
PluginFactoryUnitTest(const fs::path & tempComponent)73   PluginFactoryUnitTest(const fs::path &tempComponent)
74   {
75     _tempComponent      = tempComponent;
76     _preventiveCleaning = false;
77   }
78 
79 protected:
80   const char *
getUuid()81   getUuid()
82   {
83     return _tempComponent.c_str();
84   }
85 
86   fs::path _tempComponent;
87 };
88 
89 PluginDebugObject *
getDebugObject(const PluginDso & plugin)90 getDebugObject(const PluginDso &plugin)
91 {
92   std::string error; /* ignore the error, return nullptr if symbol not defined */
93   void *address = nullptr;
94   plugin.getSymbol("getPluginDebugObjectTest", address, error);
95   GetPluginDebugObjectFunction *getObject = reinterpret_cast<GetPluginDebugObjectFunction *>(address);
96   if (getObject) {
97     PluginDebugObject *object = reinterpret_cast<PluginDebugObject *>(getObject());
98     return object;
99   } else {
100     return nullptr;
101   }
102 }
103 
104 /* The following are paths that are used commonly in the unit-tests */
105 static fs::path sandboxDir     = getTemporaryDir();
106 static fs::path runtimeRootDir = sandboxDir / "runtime";
107 static fs::path runtimeDir     = runtimeRootDir / tempComponent;
108 static fs::path searchDir      = sandboxDir / "search";
109 static fs::path pluginBuildDir = fs::current_path() / "unit-tests/.libs";
110 
111 void
clean()112 clean()
113 {
114   fs::remove(sandboxDir, ec);
115 }
116 
117 static void
setupConfigPathTest(const fs::path & configPath,const fs::path & pluginBuildPath,const fs::path & uuid,fs::path & effectivePath,fs::path & runtimePath,time_t mtime=0,bool append=false)118 setupConfigPathTest(const fs::path &configPath, const fs::path &pluginBuildPath, const fs::path &uuid, fs::path &effectivePath,
119                     fs::path &runtimePath, time_t mtime = 0, bool append = false)
120 {
121   std::string error;
122   if (!append) {
123     clean();
124   }
125 
126   effectivePath = configPath.is_absolute() ? configPath : searchDir / configPath;
127   runtimePath   = runtimeRootDir / uuid / effectivePath.relative_path();
128 
129   /* Create the directory structure and install plugins */
130   fs::create_directories(effectivePath.parent_path(), ec);
131   fs::copy(pluginBuildPath, effectivePath, ec);
132   if (0 != mtime) {
133     struct stat sb;
134     struct utimbuf new_times;
135     stat(effectivePath.c_str(), &sb);
136     new_times.actime  = sb.st_atime; /* keep atime unchanged */
137     new_times.modtime = mtime;       /* set mtime to current time */
138     utime(effectivePath.c_str(), &new_times);
139   }
140 
141   CHECK(fs::exists(effectivePath));
142 }
143 
144 static PluginFactoryUnitTest *
getFactory(const fs::path & uuid)145 getFactory(const fs::path &uuid)
146 {
147   /* Instantiate and initialize a plugin factory. */
148   PluginFactoryUnitTest *factory = new PluginFactoryUnitTest(uuid);
149   factory->setRuntimeDir(runtimeRootDir);
150   factory->addSearchDir(searchDir);
151   return factory;
152 }
153 
154 static void
teardownConfigPathTest(PluginFactoryUnitTest * factory)155 teardownConfigPathTest(PluginFactoryUnitTest *factory)
156 {
157   delete factory;
158   clean();
159 }
160 
161 static void
validateSuccessfulConfigPathTest(const RemapPluginInst * pluginInst,const std::string & error,const fs::path & effectivePath,const fs::path & runtimePath)162 validateSuccessfulConfigPathTest(const RemapPluginInst *pluginInst, const std::string &error, const fs::path &effectivePath,
163                                  const fs::path &runtimePath)
164 {
165   CHECK(nullptr != pluginInst);
166   CHECK("" == error);
167   CHECK(effectivePath == pluginInst->_plugin.effectivePath());
168   CHECK(runtimePath == pluginInst->_plugin.runtimePath());
169 }
170 
171 SCENARIO("loading plugins", "[plugin][core]")
172 {
173   REQUIRE_FALSE(sandboxDir.empty());
174 
175   fs::path effectivePath;
176   fs::path runtimePath;
177   std::string error;
178 
179   GIVEN("an existing plugin")
180   {
181     fs::path pluginName = fs::path("plugin_v1.so");
182     fs::path buildPath  = pluginBuildDir / pluginName;
183 
184     WHEN("config using plugin file name only")
185     {
186       fs::path configPath = pluginName;
187       CHECK(configPath.is_relative()); /* make sure this is relative path - this is what we are testing */
188 
189       setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath);
190       PluginFactoryUnitTest *factory = getFactory(tempComponent);
191       RemapPluginInst *plugin        = factory->getRemapPlugin(configPath, 0, nullptr, error);
192 
193       THEN("expect it to successfully load")
194       {
195         validateSuccessfulConfigPathTest(plugin, error, effectivePath, runtimePath);
196         CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath));
197       }
198 
199       teardownConfigPathTest(factory);
200     }
201 
202     WHEN("config is using plugin relative filename")
203     {
204       fs::path configPath = fs::path("subdir") / pluginName;
205       CHECK(configPath.is_relative()); /* make sure this is relative path - this is what we are testing */
206 
207       setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath);
208       PluginFactoryUnitTest *factory = getFactory(tempComponent);
209       RemapPluginInst *plugin        = factory->getRemapPlugin(configPath, 0, nullptr, error);
210 
211       THEN("expect it to successfully load")
212       {
213         validateSuccessfulConfigPathTest(plugin, error, effectivePath, runtimePath);
214         CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath));
215       }
216 
217       teardownConfigPathTest(factory);
218     }
219 
220     WHEN("config is using plugin absolute path")
221     {
222       fs::path configPath = searchDir / "subdir" / pluginName;
223       CHECK(configPath.is_absolute()); /* make sure this is absolute path - this is what we are testing */
224 
225       setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath);
226       PluginFactoryUnitTest *factory = getFactory(tempComponent);
227       RemapPluginInst *plugin        = factory->getRemapPlugin(configPath, 0, nullptr, error);
228 
229       THEN("expect it to successfully load")
230       {
231         validateSuccessfulConfigPathTest(plugin, error, effectivePath, runtimePath);
232         CHECK(nullptr != PluginDso::loadedPlugins()->findByEffectivePath(effectivePath));
233       }
234 
235       teardownConfigPathTest(factory);
236     }
237 
238     WHEN("config using nonexisting relative plugin file name")
239     {
240       fs::path relativeExistingPath = pluginName;
241       CHECK(relativeExistingPath.is_relative());
242       fs::path relativeNonexistingPath("subdir");
243       relativeNonexistingPath /= fs::path("nonexisting_plugin.so");
244       CHECK(relativeNonexistingPath.is_relative());
245 
246       setupConfigPathTest(relativeExistingPath, buildPath, tempComponent, effectivePath, runtimePath);
247       PluginFactoryUnitTest *factory = getFactory(tempComponent);
248       RemapPluginInst *plugin        = factory->getRemapPlugin(relativeNonexistingPath, 0, nullptr, error);
249 
250       THEN("expect it to fail with appropriate error message")
251       {
252         std::string expectedError;
253         expectedError.append("failed to find plugin '").append(relativeNonexistingPath.string()).append("'");
254         CHECK(nullptr == plugin);
255         CHECK(expectedError == error);
256       }
257 
258       teardownConfigPathTest(factory);
259     }
260 
261     WHEN("config using nonexisting absolute plugin file name")
262     {
263       fs::path relativeExistingPath = pluginName;
264       CHECK(relativeExistingPath.is_relative());
265       fs::path absoluteNonexistingPath = searchDir / "subdir" / "nonexisting_plugin.so";
266       CHECK(absoluteNonexistingPath.is_absolute());
267 
268       setupConfigPathTest(relativeExistingPath, buildPath, tempComponent, effectivePath, runtimePath);
269       PluginFactoryUnitTest *factory = getFactory(tempComponent);
270       RemapPluginInst *plugin        = factory->getRemapPlugin(absoluteNonexistingPath, 0, nullptr, error);
271 
272       THEN("expect it to fail with appropriate error message")
273       {
274         std::string expectedError;
275         expectedError.append("failed to find plugin '").append(absoluteNonexistingPath.string()).append("'");
276         CHECK(nullptr == plugin);
277         CHECK(expectedError == error);
278       }
279 
280       teardownConfigPathTest(factory);
281     }
282 
283     WHEN("plugin initialization fails")
284     {
285       fs::path configPath = fs::path("plugin_init_fail.so");
286       fs::path buildPath  = pluginBuildDir / configPath;
287       setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath);
288       PluginFactoryUnitTest *factory = getFactory(tempComponent);
289       RemapPluginInst *plugin        = factory->getRemapPlugin(configPath, 0, nullptr, error);
290 
291       THEN("expect it to unload the plugin dso")
292       {
293         CHECK(nullptr == plugin);
294         CHECK(nullptr == PluginDso::loadedPlugins()->findByEffectivePath(effectivePath));
295       }
296 
297       teardownConfigPathTest(factory);
298     }
299 
300     WHEN("instance initialization fails")
301     {
302       fs::path configPath = fs::path("plugin_instinit_fail.so");
303       fs::path buildPath  = pluginBuildDir / configPath;
304       setupConfigPathTest(configPath, buildPath, tempComponent, effectivePath, runtimePath);
305       PluginFactoryUnitTest *factory = getFactory(tempComponent);
306       RemapPluginInst *plugin        = factory->getRemapPlugin(configPath, 0, nullptr, error);
307 
308       THEN("expect it to unload the plugin dso")
309       {
310         CHECK(nullptr == plugin);
311         CHECK(nullptr == PluginDso::loadedPlugins()->findByEffectivePath(effectivePath));
312       }
313 
314       teardownConfigPathTest(factory);
315     }
316   }
317 }
318 
319 SCENARIO("multiple search dirs + multiple or no plugins installed", "[plugin][core]")
320 {
321   REQUIRE_FALSE(sandboxDir.empty());
322 
323   GIVEN("multiple search dirs specified for the plugin search")
324   {
325     /* Create the directory structure and install plugins */
326     fs::path configPath              = fs::path("plugin_v1.so");
327     fs::path pluginName              = fs::path("plugin_v1.so");
328     fs::path searchDir1              = sandboxDir / "search1";
329     fs::path searchDir2              = sandboxDir / "search2";
330     fs::path searchDir3              = sandboxDir / "search3";
331     std::vector<fs::path> searchDirs = {searchDir1, searchDir2, searchDir3};
332     fs::path effectivePath1          = searchDir1 / configPath;
333     fs::path effectivePath2          = searchDir2 / configPath;
334     fs::path effectivePath3          = searchDir3 / configPath;
335     fs::path runtimePath1            = runtimeDir / effectivePath1.relative_path();
336     fs::path runtimePath2            = runtimeDir / effectivePath2.relative_path();
337     fs::path runtimePath3            = runtimeDir / effectivePath3.relative_path();
338     fs::path pluginBuildPath         = fs::current_path() / fs::path("unit-tests/.libs") / pluginName;
339 
340     std::string error;
341 
342     for (auto searchDir : searchDirs) {
343       CHECK(fs::create_directories(searchDir, ec));
344       fs::copy(pluginBuildPath, searchDir, ec);
345     }
346     CHECK(fs::create_directories(runtimeDir, ec));
347 
348     /* Instantiate and initialize a plugin DSO instance. */
349     PluginFactoryUnitTest factory(tempComponent);
350     factory.setRuntimeDir(runtimeRootDir);
351     for (auto searchDir : searchDirs) {
352       factory.addSearchDir(searchDir);
353     }
354 
355     CHECK(fs::exists(effectivePath1));
356     CHECK(fs::exists(effectivePath2));
357     CHECK(fs::exists(effectivePath3));
358 
359     WHEN("loading an existing plugin using its absolute path but the plugin is not located in any of the search dirs")
360     {
361       /* Prepare "unregistered" directory containing a valid plugin but not registered with the factory as a search directory */
362       fs::path unregisteredDir = sandboxDir / searchDir / "unregistered";
363       CHECK(fs::create_directories(unregisteredDir, ec));
364       fs::copy(pluginBuildPath, unregisteredDir, ec);
365       fs::path abEffectivePath = unregisteredDir / pluginName;
366       fs::path absRuntimePath  = runtimeDir / abEffectivePath.relative_path();
367       CHECK(abEffectivePath.is_absolute());
368       CHECK(fs::exists(abEffectivePath));
369 
370       /* Now use an absolute path containing the unregistered search directory */
371       RemapPluginInst *pluginInst = factory.getRemapPlugin(abEffectivePath, 0, nullptr, error);
372 
373       THEN("Expect it to successfully load")
374       {
375         CHECK(nullptr != pluginInst);
376         CHECK(error.empty());
377         CHECK(abEffectivePath == pluginInst->_plugin.effectivePath());
378         CHECK(absRuntimePath == pluginInst->_plugin.runtimePath());
379       }
380       CHECK(fs::remove(sandboxDir, ec));
381     }
382 
383     WHEN("a valid plugin is found in the first search path")
384     {
385       RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error);
386 
387       THEN("Expect it to successfully load the one found in the first search dir and copy it in the runtime dir")
388       {
389         CHECK(nullptr != pluginInst);
390         CHECK(error.empty());
391         CHECK(effectivePath1 == pluginInst->_plugin.effectivePath());
392         CHECK(runtimePath1 == pluginInst->_plugin.runtimePath());
393       }
394       CHECK(fs::remove(sandboxDir, ec));
395     }
396 
397     WHEN("the first search dir is missing the plugin but the second search has it")
398     {
399       CHECK(fs::remove(effectivePath1, ec));
400       RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error);
401 
402       THEN("Expect it to successfully load the one found in the second search dir")
403       {
404         CHECK(nullptr != pluginInst);
405         CHECK(error.empty());
406         CHECK(effectivePath2 == pluginInst->_plugin.effectivePath());
407         CHECK(runtimePath2 == pluginInst->_plugin.runtimePath());
408       }
409       CHECK(fs::remove(sandboxDir, ec));
410     }
411 
412     WHEN("the first and second search dir are missing the plugin but the third search has it")
413     {
414       CHECK(fs::remove(effectivePath1, ec));
415       CHECK(fs::remove(effectivePath2, ec));
416       RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error);
417 
418       THEN("Expect it to successfully load the one found in the third search dir")
419       {
420         CHECK(nullptr != pluginInst);
421         CHECK(error.empty());
422         CHECK(effectivePath3 == pluginInst->_plugin.effectivePath());
423         CHECK(runtimePath3 == pluginInst->_plugin.runtimePath());
424       }
425       CHECK(fs::remove(sandboxDir, ec));
426     }
427 
428     WHEN("none of the search dirs contains a valid plugin")
429     {
430       CHECK(fs::remove(effectivePath1, ec));
431       CHECK(fs::remove(effectivePath2, ec));
432       CHECK(fs::remove(effectivePath3, ec));
433 
434       THEN("expect the plugin load to fail.")
435       {
436         RemapPluginInst *pluginInst = factory.getRemapPlugin(configPath, 0, nullptr, error);
437         CHECK(nullptr == pluginInst);
438         CHECK(std::string("failed to find plugin '").append(configPath.string()).append("'") == error);
439         CHECK_FALSE(fs::exists(runtimePath1));
440         CHECK_FALSE(fs::exists(runtimePath2));
441         CHECK_FALSE(fs::exists(runtimePath3));
442       }
443       CHECK(fs::remove(sandboxDir, ec));
444     }
445   }
446 }
447 
448 static int
getPluginVersion(const PluginDso & plugin)449 getPluginVersion(const PluginDso &plugin)
450 {
451   std::string error;
452   void *s = nullptr;
453   CHECK(plugin.getSymbol("pluginDsoVersionTest", s, error));
454   int (*version)() = reinterpret_cast<int (*)()>(s);
455   return version ? version() : -1;
456 }
457 
458 SCENARIO("loading multiple version of the same plugin at the same time", "[plugin][core]")
459 {
460   REQUIRE_FALSE(sandboxDir.empty());
461 
462   static fs::path uuid_t1 = fs::path("c71e2bab-90dc-4770-9535-c9304c3de381"); /* UUID at moment t1 */
463   static fs::path uuid_t2 = fs::path("c71e2bab-90dc-4770-9535-e7304c3ee732"); /* UUID at moment t2 */
464 
465   fs::path effectivePath_v1;            /* expected effective path for DSO v1 */
466   fs::path effectivePath_v2;            /* expected effective path for DSO v2 */
467   fs::path runtimePath_v1;              /* expected runtime path for DSO v1 */
468   fs::path runtimePath_v2;              /* expected runtime path for DSO v2 */
469   void *tsRemapInitSym_v1_t1 = nullptr; /* callback address from DSO v1 at moment t1 */
470   void *tsRemapInitSym_v1_t2 = nullptr; /* callback address from DSO v1 at moment t2 */
471   void *tsRemapInitSym_v2_t2 = nullptr; /* callback address from DSO v2 at moment t2 */
472 
473   std::string error;
474   std::string error1;
475   std::string error2;
476 
477   fs::path configName   = fs::path("plugin.so");                     /* use same config name for all following tests */
478   fs::path buildPath_v1 = pluginBuildDir / fs::path("plugin_v1.so"); /* DSO v1 */
479   fs::path buildPath_v2 = pluginBuildDir / fs::path("plugin_v2.so"); /* DSO v1 */
480 
481   GIVEN("two different versions v1 and v2 of same plugin")
482   {
483     WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, "
484          "(*) v1 and v2 DSOs modification time are different (changed)")
485     {
486       /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */
487       setupConfigPathTest(configName, buildPath_v1, uuid_t1, effectivePath_v1, runtimePath_v1, 1556825556);
488       PluginFactoryUnitTest *factory1 = getFactory(uuid_t1);
489       RemapPluginInst *plugin_v1      = factory1->getRemapPlugin(configName, 0, nullptr, error1);
490       plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t1, error);
491 
492       /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */
493       /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so which was v1 */
494       setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825557);
495       PluginFactoryUnitTest *factory2 = getFactory(uuid_t2);
496       RemapPluginInst *plugin_v2      = factory2->getRemapPlugin(configName, 0, nullptr, error2);
497 
498       /* Make sure plugin.so was overriden */
499       CHECK(effectivePath_v1 == effectivePath_v2);
500 
501       /* Although effective path is the same runtime paths should be different */
502       CHECK(runtimePath_v1 != runtimePath_v2);
503 
504       THEN("expect both to be successfully loaded and used simultaneously")
505       {
506         /* Both loadings should succeed */
507         validateSuccessfulConfigPathTest(plugin_v1, error1, effectivePath_v1, runtimePath_v1);
508         validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v2);
509 
510         /* Make sure what we installed and loaded first was v1 and after the plugin reload we run v2 */
511         CHECK(1 == getPluginVersion(plugin_v1->_plugin));
512         CHECK(2 == getPluginVersion(plugin_v2->_plugin));
513 
514         /* Make sure the symbols we get from the 2 loaded plugins don't yield the same callback function pointer */
515         plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error);
516         plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error);
517         CHECK(nullptr != tsRemapInitSym_v1_t2);
518         CHECK(nullptr != tsRemapInitSym_v2_t2);
519         CHECK(tsRemapInitSym_v1_t2 != tsRemapInitSym_v2_t2);
520 
521         /* Make sure v1 callback functions addresses did not change for v1 after v2 was loaded */
522         CHECK(tsRemapInitSym_v1_t1 == tsRemapInitSym_v1_t2);
523       }
524 
525       teardownConfigPathTest(factory1);
526       teardownConfigPathTest(factory2);
527     }
528   }
529 
530   GIVEN("two different versions v1 and v2 of same plugin")
531   {
532     WHEN("(1) loading v1, (2) overwriting with v2 and then (3) reloading by using the same plugin name, "
533          "(*) v1 and v2 DSOs modification time are same (did NOT change)")
534     {
535       /* Simulate installing plugin plugin_v1.so (ver 1) as plugin.so and loading it at some point of time t1 */
536       setupConfigPathTest(configName, buildPath_v1, uuid_t1, effectivePath_v1, runtimePath_v1, 1556825556);
537       PluginFactoryUnitTest *factory1 = getFactory(uuid_t1);
538       RemapPluginInst *plugin_v1      = factory1->getRemapPlugin(configName, 0, nullptr, error1);
539 
540       /* Simulate installing plugin plugin_v2.so (v1) as plugin.so and loading it at some point of time t2 */
541       /* Note that during the installation plugin_v2.so (v2) is "barberically" overriding the existing plugin.so
542          which was v1, since the modification time is exactly the same the new v2 plugin would not be loaded and
543          we should get the same PluginDso address and same effective and runtime paths */
544       setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825556);
545       PluginFactoryUnitTest *factory2 = getFactory(uuid_t2);
546       RemapPluginInst *plugin_v2      = factory2->getRemapPlugin(configName, 0, nullptr, error2);
547 
548       /* Make sure plugin.so was overriden */
549       CHECK(effectivePath_v1 == effectivePath_v2);
550 
551       THEN("expect only v1 plugin to be loaded since the timestamp has not changed")
552       {
553         /* Both getRemapPlugin() calls should succeed but only v1 plugin DSO should be used */
554         validateSuccessfulConfigPathTest(plugin_v1, error1, effectivePath_v1, runtimePath_v1);
555         validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v1);
556 
557         /* Make sure we ended up with the same DSO object and runtime paths should be same - no new plugin was loaded */
558         CHECK(&(plugin_v1->_plugin) == &(plugin_v2->_plugin));
559         CHECK(plugin_v1->_plugin.runtimePath() == plugin_v2->_plugin.runtimePath());
560 
561         /* Make sure v2 DSO was NOT loaded both instances should return same v1 version */
562         CHECK(1 == getPluginVersion(plugin_v1->_plugin));
563         CHECK(1 == getPluginVersion(plugin_v2->_plugin));
564 
565         /* Make sure the symbols we get from the 2 loaded plugins yield the same callback function pointer */
566         plugin_v1->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v1_t2, error);
567         plugin_v2->_plugin.getSymbol("TSRemapInit", tsRemapInitSym_v2_t2, error);
568         CHECK(nullptr != tsRemapInitSym_v1_t2);
569         CHECK(nullptr != tsRemapInitSym_v2_t2);
570         CHECK(tsRemapInitSym_v1_t2 == tsRemapInitSym_v2_t2);
571       }
572 
573       teardownConfigPathTest(factory1);
574       teardownConfigPathTest(factory2);
575     }
576   }
577 
578   /* Since factories share the list of loaded plugins to avoid unnecessary loading of unchanged plugins
579    * lets check if destroying a factory impacts plugins loaded from another factory */
580   GIVEN("configurations with and without plugins")
581   {
582     WHEN("loading a configuration without plugins and then reloading configuration with a plugin")
583     {
584       /* Simulate configuration without plugins - an unused factory */
585       PluginFactoryUnitTest *factory1 = getFactory(uuid_t1);
586 
587       /* Now provision and load a plugin using a second factory */
588       setupConfigPathTest(configName, buildPath_v2, uuid_t2, effectivePath_v2, runtimePath_v2, 1556825556);
589       PluginFactoryUnitTest *factory2 = getFactory(uuid_t2);
590       RemapPluginInst *plugin_v2      = factory2->getRemapPlugin(configName, 0, nullptr, error2);
591 
592       THEN("the plugin from the second factory to work")
593       {
594         validateSuccessfulConfigPathTest(plugin_v2, error2, effectivePath_v2, runtimePath_v2);
595 
596         /* Now delete the first factory and call a plugin from the second factory */
597         delete factory1;
598         CHECK(TSREMAP_NO_REMAP == plugin_v2->_plugin.doRemap(INSTANCE_HANDLER, nullptr, nullptr));
599       }
600 
601       teardownConfigPathTest(factory2);
602     }
603   }
604 }
605 
606 SCENARIO("notifying plugins of config reload", "[plugin][core]")
607 {
608   REQUIRE_FALSE(sandboxDir.empty());
609 
610   /* use 2 copies of the same plugin to test */
611   fs::path configName1 = fs::path("plugin_testing_calls_1.so");
612   fs::path configName2 = fs::path("plugin_testing_calls_2.so");
613   fs::path buildPath   = pluginBuildDir / fs::path("plugin_testing_calls.so");
614 
615   static fs::path uuid_t1 = fs::path("c71e2bab-90dc-4770-9535-c9304c3de381"); /* UUID at moment t1 */
616   static fs::path uuid_t2 = fs::path("c71e2bab-90dc-4770-9535-e7304c3ee732"); /* UUID at moment t2 */
617 
618   fs::path effectivePath1;
619   fs::path effectivePath2;
620   fs::path runtimePath1;
621   fs::path runtimePath2;
622 
623   std::string error;
624 
625   GIVEN("simple configuration with 1 plugin and 1 factory")
626   {
627     WHEN("(1) signal pre-new-config-load, (2) signal post-new-config-load, (3) old-config deactivate")
628     {
629       /* Simulate configuration with 1 factory and 1 plugin */
630       setupConfigPathTest(configName1, buildPath, uuid_t1, effectivePath1, runtimePath1, 1556825556);
631       PluginFactoryUnitTest *factory1 = getFactory(uuid_t1);
632       RemapPluginInst *plugin1        = factory1->getRemapPlugin(configName1, 0, nullptr, error);
633 
634       /* check if loaded successfully */
635       validateSuccessfulConfigPathTest(plugin1, error, effectivePath1, runtimePath1);
636 
637       /* Prapare the debug object */
638       PluginDebugObject *debugObject = getDebugObject(plugin1->_plugin);
639 
640       THEN("expect pre-, post- and done/delete_instance to be called accordingly ")
641       {
642         /* Signal before loading the config */
643         debugObject->clear();
644 
645         factory1->indicatePreReload();
646         CHECK(0 == debugObject->deleteInstanceCalled);
647         CHECK(0 == debugObject->doneCalled);
648         CHECK(1 == debugObject->preReloadConfigCalled);
649 
650         /* ... parse the new remap config ... */
651 
652         /* Assume (re)load was done OK and test */
653         debugObject->clear();
654         factory1->indicatePostReload(/* reload succeeded */ true);
655         CHECK(0 == debugObject->deleteInstanceCalled);
656         CHECK(0 == debugObject->doneCalled);
657         CHECK(1 == debugObject->postReloadConfigCalled);
658         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject->postReloadConfigStatus);
659 
660         /* Assume (re)load failed and test */
661         debugObject->clear();
662         factory1->indicatePostReload(/* reload succeeded */ false);
663         CHECK(0 == debugObject->deleteInstanceCalled);
664         CHECK(0 == debugObject->doneCalled);
665         CHECK(1 == debugObject->postReloadConfigCalled);
666         CHECK(TSREMAP_CONFIG_RELOAD_FAILURE == debugObject->postReloadConfigStatus);
667 
668         /* ... swap the new and the old config ... */
669 
670         /* Signal de-activation of the old config */
671         debugObject->clear();
672         factory1->deactivate();
673         CHECK(1 == debugObject->deleteInstanceCalled);
674         CHECK(1 == debugObject->doneCalled);
675         CHECK(0 == debugObject->preReloadConfigCalled);
676       }
677 
678       teardownConfigPathTest(factory1);
679     }
680   }
681 
682   GIVEN("configuration with 2 plugins loaded by 1 factory")
683   {
684     WHEN("(1) signal pre-new-config-load, (2) signal post-new-config-load, (3) old-config deactivate")
685     {
686       /* Simulate configuration with 1 factories and 2 plugin */
687       setupConfigPathTest(configName1, buildPath, uuid_t1, effectivePath1, runtimePath1, 1556825556);
688       setupConfigPathTest(configName2, buildPath, uuid_t1, effectivePath2, runtimePath2, 1556825556, /* append */ true);
689       PluginFactoryUnitTest *factory1 = getFactory(uuid_t1);
690       RemapPluginInst *plugin1        = factory1->getRemapPlugin(configName1, 0, nullptr, error);
691       RemapPluginInst *plugin2        = factory1->getRemapPlugin(configName2, 0, nullptr, error);
692 
693       /* check if loaded successfully */
694       validateSuccessfulConfigPathTest(plugin1, error, effectivePath1, runtimePath1);
695       validateSuccessfulConfigPathTest(plugin2, error, effectivePath2, runtimePath2);
696 
697       /* Prapare the debug objects */
698       PluginDebugObject *debugObject1 = getDebugObject(plugin1->_plugin);
699       PluginDebugObject *debugObject2 = getDebugObject(plugin2->_plugin);
700 
701       THEN("expect pre-, post- and done/delete_instance to be called accordingly")
702       {
703         /* Signal before loading the config */
704         debugObject1->clear();
705         debugObject2->clear();
706         factory1->indicatePreReload();
707         CHECK(0 == debugObject1->deleteInstanceCalled);
708         CHECK(0 == debugObject1->doneCalled);
709         CHECK(1 == debugObject1->preReloadConfigCalled);
710         CHECK(0 == debugObject2->doneCalled);
711         CHECK(0 == debugObject2->deleteInstanceCalled);
712         CHECK(1 == debugObject2->preReloadConfigCalled);
713 
714         /* ... parse the new remap config ... */
715 
716         /* Assume (re)load was done OK */
717         debugObject1->clear();
718         debugObject2->clear();
719         factory1->indicatePostReload(/* reload succeeded */ true);
720         CHECK(0 == debugObject1->deleteInstanceCalled);
721         CHECK(0 == debugObject1->doneCalled);
722         CHECK(1 == debugObject1->postReloadConfigCalled);
723         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject1->postReloadConfigStatus);
724         CHECK(0 == debugObject2->deleteInstanceCalled);
725         CHECK(0 == debugObject2->doneCalled);
726         CHECK(1 == debugObject2->postReloadConfigCalled);
727         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject2->postReloadConfigStatus);
728 
729         /* Assume (re)load failed */
730         debugObject1->clear();
731         debugObject2->clear();
732         factory1->indicatePostReload(/* reload succeeded */ false);
733         CHECK(0 == debugObject1->deleteInstanceCalled);
734         CHECK(0 == debugObject1->doneCalled);
735         CHECK(1 == debugObject1->postReloadConfigCalled);
736         CHECK(TSREMAP_CONFIG_RELOAD_FAILURE == debugObject1->postReloadConfigStatus);
737         CHECK(0 == debugObject2->deleteInstanceCalled);
738         CHECK(0 == debugObject2->doneCalled);
739         CHECK(1 == debugObject2->postReloadConfigCalled);
740         CHECK(TSREMAP_CONFIG_RELOAD_FAILURE == debugObject2->postReloadConfigStatus);
741 
742         /* ... swap the new and the old config ... */
743 
744         /* Signal de-activation of the old config */
745         debugObject1->clear();
746         debugObject2->clear();
747         factory1->deactivate();
748         CHECK(1 == debugObject1->deleteInstanceCalled);
749         CHECK(1 == debugObject1->doneCalled);
750         CHECK(0 == debugObject1->preReloadConfigCalled);
751         CHECK(1 == debugObject2->deleteInstanceCalled);
752         CHECK(1 == debugObject2->doneCalled);
753         CHECK(0 == debugObject2->preReloadConfigCalled);
754       }
755 
756       teardownConfigPathTest(factory1);
757     }
758   }
759 
760   GIVEN("configuration with 1 plugin loaded by 2 separate factories")
761   {
762     WHEN("indicating de-activation of the factories")
763     {
764       /* Simulate configuration with 2 factories and 1 plugin */
765       setupConfigPathTest(configName1, buildPath, uuid_t1, effectivePath1, runtimePath1, 1556825556);
766       PluginFactoryUnitTest *factory1 = getFactory(uuid_t1);
767       PluginFactoryUnitTest *factory2 = getFactory(uuid_t2);
768       RemapPluginInst *plugin1        = factory1->getRemapPlugin(configName1, 0, nullptr, error);
769       RemapPluginInst *plugin2        = factory2->getRemapPlugin(configName1, 0, nullptr, error);
770 
771       /* Prepare the debug objects */
772       PluginDebugObject *debugObject1 = getDebugObject(plugin1->_plugin);
773       PluginDebugObject *debugObject2 = getDebugObject(plugin2->_plugin);
774 
775       THEN("expect instance 'done' to be always called, but plugin 'done' called only after destroying both factories")
776       {
777         debugObject2->clear();
778         factory2->deactivate();
779         CHECK(0 == debugObject2->doneCalled);
780         CHECK(1 == debugObject2->deleteInstanceCalled);
781         CHECK(0 == debugObject2->preReloadConfigCalled);
782 
783         delete factory2;
784 
785         debugObject1->clear();
786         factory1->deactivate();
787         CHECK(1 == debugObject1->doneCalled);
788         CHECK(1 == debugObject1->deleteInstanceCalled);
789         CHECK(0 == debugObject1->preReloadConfigCalled);
790 
791         delete factory1;
792       }
793 
794       clean();
795     }
796   }
797 
798   GIVEN("configuration with 2 plugin loaded by 2 factories")
799   {
800     WHEN("2 plugins are loaded by the 1st factory and only one of them loaded by the 2nd factory")
801     {
802       /* Simulate configuration with 2 factories and 2 different plugins */
803       setupConfigPathTest(configName1, buildPath, uuid_t1, effectivePath1, runtimePath1, 1556825556, /* append */ false);
804       setupConfigPathTest(configName2, buildPath, uuid_t1, effectivePath2, runtimePath2, 1556825556, /* append */ true);
805       PluginFactoryUnitTest *factory1 = getFactory(uuid_t1);
806       PluginFactoryUnitTest *factory2 = getFactory(uuid_t2);
807 
808       /* 2 plugins loaded by the 1st factory */
809       RemapPluginInst *pluginInst1 = factory1->getRemapPlugin(configName1, 0, nullptr, error);
810       RemapPluginInst *pluginInst2 = factory1->getRemapPlugin(configName2, 0, nullptr, error);
811 
812       /* only 1 plugin loaded by the 2st factory */
813       RemapPluginInst *pluginInst3 = factory2->getRemapPlugin(configName1, 0, nullptr, error);
814 
815       /* pluginInst1 and pluginInst3 should be using the same plugin DSO named configName1
816        * pluginInst2 should be using plugin DSO named configName 2*/
817       CHECK_FALSE(nullptr == pluginInst1);
818       CHECK_FALSE(nullptr == pluginInst2);
819       CHECK_FALSE(nullptr == pluginInst3);
820       CHECK(&pluginInst1->_plugin == &pluginInst3->_plugin);
821 
822       /* Get test objects for the 2 plugins used by the 3 instances from the 2 factories */
823       PluginDebugObject *debugObject1 = getDebugObject(pluginInst1->_plugin);
824       PluginDebugObject *debugObject2 = getDebugObject(pluginInst2->_plugin);
825 
826       THEN(
827         "expect the plugin that is not loaded by the second factory to receive 'plugin unused' notification from the 2nd factory")
828       {
829         /* Factory 1: reload was OK and both plugins were part of the configuration that used/instantiated that factory */
830         debugObject1->clear();
831         debugObject2->clear();
832 
833         factory1->indicatePostReload(/* reload succeeded */ true);
834         CHECK(1 == debugObject1->postReloadConfigCalled);
835         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject1->postReloadConfigStatus);
836         CHECK(1 == debugObject2->postReloadConfigCalled);
837         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject2->postReloadConfigStatus);
838 
839         /* Factory 2: (re)load was OK and only 1 plugin was part of the configuration that used/instantiated that factory */
840         debugObject1->clear();
841         debugObject2->clear();
842 
843         factory2->indicatePostReload(/* reload succeeded */ true);
844         CHECK(1 == debugObject1->postReloadConfigCalled);
845         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_USED == debugObject1->postReloadConfigStatus);
846         CHECK(1 == debugObject2->postReloadConfigCalled);
847         CHECK(TSREMAP_CONFIG_RELOAD_SUCCESS_PLUGIN_UNUSED == debugObject2->postReloadConfigStatus);
848 
849         delete factory1;
850         delete factory2;
851       }
852 
853       clean();
854     }
855   }
856 }
857