1 /*
2   Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.
3 
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License, version 2.0,
6   as published by the Free Software Foundation.
7 
8   This program is also distributed with certain software (including
9   but not limited to OpenSSL) that is licensed under separate terms,
10   as designated in a particular file or component or in included license
11   documentation.  The authors of MySQL hereby grant you an additional
12   permission to link the program and your derivative works with the
13   separately licensed software that they have included with MySQL.
14 
15   This program is distributed in the hope that it will be useful,
16   but WITHOUT ANY WARRANTY; without even the implied warranty of
17   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   GNU General Public License for more details.
19 
20   You should have received a copy of the GNU General Public License
21   along with this program; if not, write to the Free Software
22   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23 */
24 
25 #define UNIT_TESTS  // used in router_app.h
26 #include "config_files.h"
27 #include "dim.h"
28 #include "mysql/harness/config_parser.h"
29 #include "mysql/harness/loader.h"
30 #include "mysql/harness/logging/registry.h"
31 #include "mysql/harness/vt100_filter.h"
32 #include "mysqlrouter/utils.h"
33 #include "router_app.h"
34 #include "router_config.h"
35 #include "router_test_helpers.h"
36 #include "test/helpers.h"
37 
38 // ignore GMock warnings
39 #ifdef __clang__
40 #pragma clang diagnostic push
41 #pragma clang diagnostic ignored "-Wsign-conversion"
42 #endif
43 
44 #include <gmock/gmock.h>
45 #include "gtest_consoleoutput.h"
46 
47 #ifdef __clang__
48 #pragma clang diagnostic pop
49 #endif
50 
51 #include <cstdio>
52 #include <sstream>
53 #include <stdexcept>
54 #include <streambuf>
55 #ifndef _WIN32
56 #include <pwd.h>
57 #include <unistd.h>
58 #endif
59 
60 static const std::string kPluginNameMagic("routertestplugin_magic");
61 static const std::string kPluginNameLifecycle("routertestplugin_lifecycle");
62 static const std::string kPluginNameLifecycle3("routertestplugin_lifecycle3");
63 
64 using std::string;
65 using std::vector;
66 
67 using ::testing::_;
68 using ::testing::EndsWith;
69 using ::testing::Ge;
70 using ::testing::HasSubstr;
71 using ::testing::IsEmpty;
72 using ::testing::NotNull;
73 using ::testing::Return;
74 using ::testing::SizeIs;
75 using ::testing::StartsWith;
76 using ::testing::StrEq;
77 
78 #ifndef _WIN32
79 using mysqlrouter::SysUserOperationsBase;
80 
81 class MockSysUserOperations : public SysUserOperationsBase {
82  public:
83   MOCK_METHOD2(initgroups, int(const char *, gid_type));
84   MOCK_METHOD1(setgid, int(gid_t));
85   MOCK_METHOD1(setuid, int(uid_t));
86   MOCK_METHOD1(setegid, int(gid_t));
87   MOCK_METHOD1(seteuid, int(uid_t));
88   MOCK_METHOD0(geteuid, uid_t());
89   MOCK_METHOD1(getpwnam, struct passwd *(const char *));
90   MOCK_METHOD1(getpwuid, struct passwd *(uid_t));
91   MOCK_METHOD3(chown, int(const char *, uid_t, gid_t));
92 };
93 
94 #endif  // #ifndef _WIN32
95 
96 using mysql_harness::Path;
97 
get_cwd()98 const string get_cwd() {
99   char buffer[FILENAME_MAX];
100   if (!getcwd(buffer, FILENAME_MAX)) {
101     throw std::runtime_error("getcwd failed: " + string(strerror(errno)));
102   }
103   return string(buffer);
104 }
105 
106 Path g_origin;
107 
108 class AppTest : public ::testing::Test {
109  protected:
SetUp()110   virtual void SetUp() {
111     init_test_logger();
112 #ifndef _WIN32
113     mock_sys_user_operations.reset(new MockSysUserOperations());
114 #endif
115 
116     config_dir = Path(mysql_harness::get_tests_data_dir(g_origin.str()));
117   }
118 
TearDown()119   virtual void TearDown() {}
120 
121 #ifndef _WIN32
122   std::unique_ptr<MockSysUserOperations> mock_sys_user_operations;
123 #endif
124   Path config_dir;
125 };
126 
TEST_F(AppTest,DefaultConstructor)127 TEST_F(AppTest, DefaultConstructor) {
128   MySQLRouter r;
129   ASSERT_STREQ(MYSQL_ROUTER_VERSION, r.get_version().c_str());
130 }
131 
TEST_F(AppTest,GetVersionAsString)132 TEST_F(AppTest, GetVersionAsString) {
133   MySQLRouter r;
134   ASSERT_STREQ(MYSQL_ROUTER_VERSION, r.get_version().c_str());
135 }
136 
TEST_F(AppTest,GetVersionLine)137 TEST_F(AppTest, GetVersionLine) {
138   MySQLRouter r;
139   ASSERT_THAT(r.get_version_line(), StartsWith(MYSQL_ROUTER_PACKAGE_NAME));
140   ASSERT_THAT(r.get_version_line(), HasSubstr(MYSQL_ROUTER_VERSION));
141   ASSERT_THAT(r.get_version_line(), HasSubstr(MYSQL_ROUTER_VERSION_EDITION));
142   ASSERT_THAT(r.get_version_line(), HasSubstr(MYSQL_ROUTER_PACKAGE_PLATFORM));
143   ASSERT_THAT(r.get_version_line(), HasSubstr(MYSQL_ROUTER_PACKAGE_ARCH_CPU));
144 }
145 
TEST_F(AppTest,CheckConfigFilesSuccess)146 TEST_F(AppTest, CheckConfigFilesSuccess) {
147   MySQLRouter r;
148 
149   r.default_config_files_ = {};
150   r.extra_config_files_ = {config_dir.join("mysqlrouter_extra.conf").str()};
151   ASSERT_THROW(r.check_config_files(), std::runtime_error);
152 }
153 
TEST_F(AppTest,CmdLineConfig)154 TEST_F(AppTest, CmdLineConfig) {
155   vector<string> argv = {"--config", config_dir.join("mysqlrouter.conf").str()};
156   // ASSERT_NO_THROW({ MySQLRouter r(g_origin, argv); });
157   MySQLRouter r(g_origin, argv);
158   ASSERT_THAT(r.get_config_files().at(0), EndsWith("mysqlrouter.conf"));
159   // ASSERT_THAT(r.get_default_config_files(), IsEmpty());
160   ASSERT_THAT(r.get_extra_config_files(), IsEmpty());
161 }
162 
TEST_F(AppTest,CmdLineConfigFailRead)163 TEST_F(AppTest, CmdLineConfigFailRead) {
164   string not_existing = "foobar.conf";
165   vector<string> argv = {
166       "--config",
167       config_dir.join(not_existing).str(),
168   };
169   ASSERT_THROW({ MySQLRouter r(g_origin, argv); }, std::runtime_error);
170   try {
171     MySQLRouter r(g_origin, argv);
172     FAIL() << "Should throw";
173   } catch (const std::runtime_error &exc) {
174     EXPECT_THAT(exc.what(), HasSubstr("The configuration file"));
175     EXPECT_THAT(exc.what(), HasSubstr(not_existing));
176     EXPECT_THAT(exc.what(), HasSubstr("does not exist"));
177   }
178 }
179 
TEST_F(AppTest,CmdLineMultipleConfig)180 TEST_F(AppTest, CmdLineMultipleConfig) {
181   vector<string> argv = {"--config", config_dir.join("mysqlrouter.conf").str(),
182                          "-c",       config_dir.join("config_a.conf").str(),
183                          "--config", config_dir.join("config_b.conf").str()};
184   ASSERT_THROW({ MySQLRouter r(g_origin, argv); }, std::runtime_error);
185   try {
186     MySQLRouter r(g_origin, argv);
187     FAIL() << "Should throw";
188   } catch (const std::runtime_error &exc) {
189     ASSERT_THAT(exc.what(), HasSubstr("can only be used once"));
190   }
191 }
192 
TEST_F(AppTest,CmdLineExtraConfig)193 TEST_F(AppTest, CmdLineExtraConfig) {
194   vector<string> argv = {"-c", config_dir.join("config_a.conf").str(),
195                          "--extra-config",
196                          config_dir.join("config_b.conf").str()};
197   ASSERT_NO_THROW({ MySQLRouter r(g_origin, argv); });
198   MySQLRouter r(g_origin, argv);
199   ASSERT_THAT(r.get_extra_config_files().at(0), EndsWith("config_b.conf"));
200   // ASSERT_THAT(r.get_default_config_files(), SizeIs(0));
201   ASSERT_THAT(r.get_config_files(), SizeIs(1));
202 }
203 
TEST_F(AppTest,CmdLineExtraConfigFailRead)204 TEST_F(AppTest, CmdLineExtraConfigFailRead) {
205   string not_existing = "foobar.conf";
206   vector<string> argv = {"-c", config_dir.join("config_a.conf").str(),
207                          "--extra-config", config_dir.join(not_existing).str()};
208   ASSERT_THROW({ MySQLRouter r(g_origin, argv); }, std::runtime_error);
209   try {
210     MySQLRouter r(g_origin, argv);
211     FAIL() << "Should throw";
212   } catch (const std::runtime_error &exc) {
213     EXPECT_THAT(exc.what(), HasSubstr("The configuration file"));
214     EXPECT_THAT(exc.what(), HasSubstr(not_existing));
215     EXPECT_THAT(exc.what(), HasSubstr("does not exist"));
216   }
217 }
218 
TEST_F(AppTest,CmdLineMultipleExtraConfig)219 TEST_F(AppTest, CmdLineMultipleExtraConfig) {
220   vector<string> argv = {"-c",
221                          config_dir.join("mysqlrouter.conf").str(),
222                          "-a",
223                          config_dir.join("config_a.conf").str(),
224                          "--extra-config",
225                          config_dir.join("config_b.conf").str()};
226   ASSERT_NO_THROW({ MySQLRouter r(g_origin, argv); });
227   MySQLRouter r(g_origin, argv);
228   ASSERT_THAT(r.get_config_files().at(0).c_str(), EndsWith("mysqlrouter.conf"));
229   ASSERT_THAT(r.get_extra_config_files().at(0).c_str(),
230               EndsWith("config_a.conf"));
231   ASSERT_THAT(r.get_extra_config_files().at(1).c_str(),
232               EndsWith("config_b.conf"));
233   // ASSERT_THAT(r.get_default_config_files(), SizeIs(0));
234   ASSERT_THAT(r.get_config_files(), SizeIs(1));
235 }
236 
TEST_F(AppTest,CmdLineMultipleDuplicateExtraConfig)237 TEST_F(AppTest, CmdLineMultipleDuplicateExtraConfig) {
238   string duplicate = "config_a.conf";
239   vector<string> argv = {
240       "-c",
241       config_dir.join("config_a.conf").str(),
242       "--extra-config",
243       config_dir.join("mysqlrouter.conf").str(),
244       "-a",
245       config_dir.join(duplicate).str(),
246       "--extra-config",
247       config_dir.join(duplicate).str(),
248   };
249   ASSERT_THROW({ MySQLRouter r(g_origin, argv); }, std::runtime_error);
250   try {
251     MySQLRouter r(g_origin, argv);
252     FAIL() << "Should throw";
253   } catch (const std::runtime_error &exc) {
254     EXPECT_THAT(exc.what(), HasSubstr("The configuration file"));
255     EXPECT_THAT(exc.what(), HasSubstr(duplicate));
256     EXPECT_THAT(exc.what(), HasSubstr("is provided multiple times"));
257   }
258 }
259 
TEST_F(AppTest,CmdLineExtraConfigNoDeafultFail)260 TEST_F(AppTest, CmdLineExtraConfigNoDeafultFail) {
261   /*
262    * Check if mysqlrouter.conf does not exist in default locations.
263    */
264   std::stringstream ss_line{CONFIG_FILES};
265 
266   for (string path; std::getline(ss_line, path, ';');) {
267     // malformed env var will result in error, valid or missing env var results
268     // in success
269     bool parse_ok = mysqlrouter::substitute_envvar(path);
270     if (parse_ok) {
271       std::string real_path =
272           mysqlrouter::substitute_variable(path, "{origin}", g_origin.str());
273       ASSERT_FALSE(mysql_harness::Path(real_path).exists());
274     }
275   }
276 
277   vector<string> argv = {
278       "--extra-config",
279       config_dir.join("mysqlrouter.conf").str(),
280   };
281   ASSERT_THROW({ MySQLRouter r(g_origin, argv); }, std::runtime_error);
282   try {
283     MySQLRouter r(g_origin, argv);
284     FAIL() << "Should throw";
285   } catch (const std::runtime_error &exc) {
286     EXPECT_THAT(exc.what(), HasSubstr("Extra configuration files"));
287     EXPECT_THAT(exc.what(),
288                 HasSubstr(" provided, but neither default configuration files "
289                           "nor --config=<file> are readable files"));
290   }
291 }
292 
TEST_F(AppTest,CheckConfigFileFallbackToIniSuccess)293 TEST_F(AppTest, CheckConfigFileFallbackToIniSuccess) {
294   MySQLRouter r;
295 
296   r.default_config_files_ = {config_dir.join("config_c.conf").str()};
297   auto res = r.check_config_files();
298   ASSERT_EQ(1u, res.size());
299   ASSERT_THAT(res.at(0), HasSubstr("config_c.ini"));
300 }
301 
TEST_F(AppTest,CheckConfigFileFallbackToInNoDefault)302 TEST_F(AppTest, CheckConfigFileFallbackToInNoDefault) {
303   // falling back to ini should not work for command line passed configs
304   MySQLRouter r;
305 
306   r.config_files_ = {config_dir.join("config_c.conf").str()};
307 
308   try {
309     r.check_config_files();
310     FAIL() << "Should throw";
311   } catch (const std::runtime_error &exc) {
312     EXPECT_THAT(exc.what(), HasSubstr("The configuration file"));
313     EXPECT_THAT(exc.what(), HasSubstr("is not readable"));
314   }
315 }
316 
317 #ifndef _WIN32
TEST_F(AppTest,CmdLineUserBeforeBootstrap)318 TEST_F(AppTest, CmdLineUserBeforeBootstrap) {
319   MySQLRouter router;
320   vector<string> arguments = {"--user", "mysqlrouter", "--bootstrap",
321                               "127.0.0.1:5000"};
322   ASSERT_THROW(router.parse_command_options(arguments), std::runtime_error);
323 
324   try {
325     router.parse_command_options(arguments);
326     FAIL() << "Should throw";
327   } catch (const std::runtime_error &exc) {
328     EXPECT_THAT(
329         exc.what(),
330         StrEq("One can only use the -u/--user switch if running as root"));
331   }
332 }
333 
TEST_F(AppTest,CmdLineUserShortBeforeBootstrap)334 TEST_F(AppTest, CmdLineUserShortBeforeBootstrap) {
335   MySQLRouter router;
336   vector<string> arguments = {"-u", "mysqlrouter", "--bootstrap",
337                               "127.0.0.1:5000"};
338   ASSERT_THROW(router.parse_command_options(arguments), std::runtime_error);
339 
340   try {
341     router.parse_command_options(arguments);
342     FAIL() << "Should throw";
343   } catch (const std::runtime_error &exc) {
344     EXPECT_THAT(
345         exc.what(),
346         HasSubstr("One can only use the -u/--user switch if running as root"));
347   }
348 }
349 #endif  // #ifndef _WIN32
350 
TEST_F(AppTest,CmdLineVersion)351 TEST_F(AppTest, CmdLineVersion) {
352   vector<string> argv = {"--version"};
353 
354   // filter out the ANSI ESC sequences
355   std::stringstream out_stream;
356   Vt100Filter filtered_out_streambuf(out_stream.rdbuf());
357   std::ostream filtered_out_stream(&filtered_out_streambuf);
358 
359   MySQLRouter r(g_origin, argv, filtered_out_stream);
360   ASSERT_THAT(out_stream.str(), StartsWith(r.get_version_line()));
361 }
362 
TEST_F(AppTest,CmdLineVersionShort)363 TEST_F(AppTest, CmdLineVersionShort) {
364   vector<string> argv = {"-V"};
365 
366   // filter out the ANSI ESC sequences
367   std::stringstream out_stream;
368   Vt100Filter filtered_out_streambuf(out_stream.rdbuf());
369   std::ostream filtered_out_stream(&filtered_out_streambuf);
370 
371   MySQLRouter r(g_origin, argv, filtered_out_stream);
372   ASSERT_THAT(out_stream.str(), StartsWith("MySQL Router"));
373 }
374 
TEST_F(AppTest,CmdLineHelp)375 TEST_F(AppTest, CmdLineHelp) {
376   vector<string> argv = {"--help"};
377   // filter out the ANSI ESC sequences
378   std::stringstream out_stream;
379   Vt100Filter filtered_out_streambuf(out_stream.rdbuf());
380   std::ostream filtered_out_stream(&filtered_out_streambuf);
381 
382   MySQLRouter r(g_origin, argv, filtered_out_stream);
383 
384   // several substrings from help output that are unlikely to change soon
385   EXPECT_THAT(out_stream.str(), HasSubstr("MySQL Router  V"));
386   EXPECT_THAT(
387       out_stream.str(),
388       HasSubstr(
389           "Oracle is a registered trademark of Oracle Corporation and/or its"));
390   EXPECT_THAT(out_stream.str(), HasSubstr("Usage\n\nmysqlrouter"));
391 }
392 
TEST_F(AppTest,CmdLineHelpShort)393 TEST_F(AppTest, CmdLineHelpShort) {
394   vector<string> argv = {"-?"};
395   std::stringstream out_stream;
396   Vt100Filter filtered_out_streambuf(out_stream.rdbuf());
397   std::ostream filtered_out_stream(&filtered_out_streambuf);
398   MySQLRouter r(g_origin, argv, filtered_out_stream);
399 
400   // several substrings from help output that are unlikely to change soon
401   EXPECT_THAT(out_stream.str(), HasSubstr("MySQL Router  V"));
402   EXPECT_THAT(
403       out_stream.str(),
404       HasSubstr(
405           "Oracle is a registered trademark of Oracle Corporation and/or its"));
406   EXPECT_THAT(out_stream.str(), HasSubstr("Usage\n\nmysqlrouter"));
407 }
408 
TEST_F(AppTest,ConfigFileParseError)409 TEST_F(AppTest, ConfigFileParseError) {
410   vector<string> argv = {
411       "--config",
412       config_dir.join("parse_error.conf").str(),
413   };
414   ASSERT_THROW(
415       {
416         MySQLRouter r(g_origin, argv);
417         r.start();
418       },
419       std::runtime_error);
420   try {
421     MySQLRouter r(g_origin, argv);
422     r.start();
423     FAIL() << "Should throw";
424   } catch (const std::runtime_error &exc) {
425     EXPECT_THAT(exc.what(),
426                 HasSubstr("Configuration error: Malformed section header:"));
427   }
428 }
429 
TEST_F(AppTest,SectionOverMultipleConfigFiles)430 TEST_F(AppTest, SectionOverMultipleConfigFiles) {
431   string extra_config = config_dir.join("mysqlrouter_extra.conf").str();
432   vector<string> argv = {"--config", config_dir.join("mysqlrouter.conf").str(),
433                          "--extra-config=" + extra_config};
434   ASSERT_NO_THROW({ MySQLRouter r(g_origin, argv); });
435 
436   MySQLRouter r(g_origin, argv);
437   ASSERT_THAT(r.get_config_files().at(0).c_str(), EndsWith("mysqlrouter.conf"));
438   ASSERT_THAT(r.get_extra_config_files().at(0).c_str(),
439               EndsWith("mysqlrouter_extra.conf"));
440 
441   // let the Loader load the configuration files
442   ASSERT_NO_THROW(r.start());
443 
444   auto section = r.loader_->get_config().get(kPluginNameMagic, "");
445   ASSERT_THAT(section.get("foo"), StrEq("bar"));
446   ASSERT_THROW(section.get("NotInTheSection"), mysql_harness::bad_option);
447 }
448 
TEST_F(AppTest,CanStartTrue)449 TEST_F(AppTest, CanStartTrue) {
450   vector<string> argv = {"--config", config_dir.join("mysqlrouter.conf").str()};
451   ASSERT_NO_THROW({ MySQLRouter r(g_origin, argv); });
452 }
453 
TEST_F(AppTest,CanStartFalse)454 TEST_F(AppTest, CanStartFalse) {
455   vector<vector<string>> cases = {
456       {""},
457   };
458   for (auto &argv : cases) {
459     ASSERT_THROW(
460         {
461           MySQLRouter r(g_origin, argv);
462           r.start();
463         },
464         std::runtime_error);
465   }
466 }
467 
468 /*
469  * We don't switch user for windows
470  */
471 #ifndef _WIN32
472 
473 /**
474  * @test
475  *       Verify that if --user/-u option is used, then user is switched before
476  * logger is initialized.
477  */
TEST_F(AppTest,SetCommandLineUserBeforeInitializingLogger)478 TEST_F(AppTest, SetCommandLineUserBeforeInitializingLogger) {
479   const char *user = "mysqlrouter";
480 
481   vector<string> argv = {
482       "--config", config_dir.join("mysqlrouter.conf").str(),
483       "--extra-config=" + config_dir.join("mysqlrouter_extra.conf").str(),
484       "--user=" + std::string(user)};
485 
486   // set empty Registry (is_ready() return false)
487   std::unique_ptr<mysql_harness::logging::Registry> registry(
488       new mysql_harness::logging::Registry());
489   mysql_harness::DIM::instance().set_LoggingRegistry(
490       [&registry]() { return registry.release(); },
491       std::default_delete<mysql_harness::logging::Registry>());
492   mysql_harness::DIM::instance().reset_LoggingRegistry();
493 
494   struct passwd user_info;
495   user_info.pw_gid = 12;
496   user_info.pw_uid = 17;
497 
498   EXPECT_CALL(*mock_sys_user_operations, geteuid())
499       .Times(1)
500       .WillOnce(Return(0));
501   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(user)))
502       .Times(1)
503       .WillOnce(Return(&user_info));
504   EXPECT_CALL(*mock_sys_user_operations,
505               initgroups(StrEq(user),
506                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
507       .Times(1);
508   EXPECT_CALL(*mock_sys_user_operations, setgid(user_info.pw_gid))
509       .Times(1)
510       .WillOnce(Return(0));
511   EXPECT_CALL(*mock_sys_user_operations, setuid(user_info.pw_uid))
512       .Times(1)
513       .WillOnce(testing::DoAll(
514           testing::InvokeWithoutArgs([&] {
515             ASSERT_FALSE(mysql_harness::DIM::instance()
516                              .get_LoggingRegistry()
517                              .is_ready());
518           }),
519           // we proved that the user got set first, now init the logger properly
520           // for the further loader to use it
521           testing::InvokeWithoutArgs([&] { init_test_logger(); }),
522           (Return(0))));
523 
524   MySQLRouter r(g_origin, argv, std::cout, std::cerr,
525                 mock_sys_user_operations.get());
526   ASSERT_NO_THROW(r.start());
527 }
528 
529 /**
530  * @test
531  *       Verify that if --user/-u option is used, then user is switched before
532  * logger is initialized.
533  */
TEST_F(AppTest,SetConfigUserBeforeInitializingLogger)534 TEST_F(AppTest, SetConfigUserBeforeInitializingLogger) {
535   const char *user = "mysqlrouter";
536 
537   std::string tmp_dir = mysql_harness::get_tmp_dir("AppTest");
538   std::shared_ptr<void> exit_guard(
539       nullptr, [&](void *) { mysql_harness::delete_dir_recursive(tmp_dir); });
540 
541   // copy config file and add user option to [DEFAULT] section
542   {
543     std::ofstream destination_stream(
544         mysql_harness::Path(tmp_dir).join("mysqlrouter.conf").str());
545     std::ifstream source_stream(config_dir.join("mysqlrouter.conf").str());
546 
547     std::string line;
548     while (source_stream.good() && destination_stream.good()) {
549       getline(source_stream, line);
550       destination_stream << line << std::endl;
551       if (line.find("DEFAULT]") != line.npos)
552         destination_stream << "user=" << std::string(user) << std::endl;
553     }
554   }
555 
556   vector<string> argv = {
557       "--config", mysql_harness::Path(tmp_dir).join("mysqlrouter.conf").str(),
558       "--extra-config=" + config_dir.join("mysqlrouter_extra.conf").str()};
559 
560   // set empty Registry (is_ready() return false)
561   std::unique_ptr<mysql_harness::logging::Registry> registry(
562       new mysql_harness::logging::Registry());
563   mysql_harness::DIM::instance().set_LoggingRegistry(
564       [&registry]() { return registry.release(); },
565       std::default_delete<mysql_harness::logging::Registry>());
566   mysql_harness::DIM::instance().reset_LoggingRegistry();
567 
568   struct passwd user_info;
569   user_info.pw_gid = 12;
570   user_info.pw_uid = 17;
571 
572   EXPECT_CALL(*mock_sys_user_operations, geteuid())
573       .Times(1)
574       .WillOnce(Return(0));
575   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(user)))
576       .Times(1)
577       .WillOnce(Return(&user_info));
578   EXPECT_CALL(*mock_sys_user_operations,
579               initgroups(StrEq(user),
580                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
581       .Times(1);
582   EXPECT_CALL(*mock_sys_user_operations, setgid(user_info.pw_gid))
583       .Times(1)
584       .WillOnce(Return(0));
585   EXPECT_CALL(*mock_sys_user_operations, setuid(user_info.pw_uid))
586       .Times(1)
587       .WillOnce(testing::DoAll(
588           testing::InvokeWithoutArgs([&] {
589             ASSERT_FALSE(mysql_harness::DIM::instance()
590                              .get_LoggingRegistry()
591                              .is_ready());
592           }),
593           // we proved that the user got set first, now init the logger properly
594           // for the further loader to use it
595           testing::InvokeWithoutArgs([&] { init_test_logger(); }),
596           (Return(0))));
597 
598   MySQLRouter r(g_origin, argv, std::cout, std::cerr,
599                 mock_sys_user_operations.get());
600   ASSERT_NO_THROW(r.start());
601 }
602 
603 #endif  // #ifndef _WIN32
604 
TEST_F(AppTest,ShowingInfoTrue)605 TEST_F(AppTest, ShowingInfoTrue) {
606   vector<vector<string>> cases = {
607       {"--help"},
608       {"--version"},
609       {"--help", "--config", config_dir.join("mysqlrouter.conf").str()},
610       {"--config", config_dir.join("mysqlrouter.conf").str(), "--help"},
611   };
612 
613   // Make sure we do not start when showing information
614   for (auto &argv : cases) {
615     // filter out the ANSI ESC sequences
616     std::stringstream out_stream;
617     Vt100Filter filtered_out_streambuf(out_stream.rdbuf());
618     std::ostream filtered_out_stream(&filtered_out_streambuf);
619 
620     ASSERT_NO_THROW({
621       MySQLRouter r(g_origin, argv, filtered_out_stream);
622       r.start();
623     });
624     ASSERT_THAT(out_stream.str(), HasSubstr("MySQL Router  V")) << argv[0];
625   }
626 }
627 
TEST_F(AppTest,ShowingInfoFalse)628 TEST_F(AppTest, ShowingInfoFalse) {
629   // Cases should be allowing Router to start
630   vector<vector<string>> cases = {
631       {"--config", config_dir.join("mysqlrouter.conf").str(),
632        "--extra-config=" + config_dir.join("mysqlrouter_extra.conf").str()}};
633 
634   for (auto &argv : cases) {
635     ASSERT_NO_THROW({
636       MySQLRouter r(g_origin, argv);
637       r.start();
638     });
639   }
640 }
641 
642 #ifndef _WIN32
643 
TEST_F(AppTest,UserSetPermanentlyByName)644 TEST_F(AppTest, UserSetPermanentlyByName) {
645   const char *USER = "mysqluser";
646 
647   struct passwd user_info;
648   user_info.pw_gid = 12;
649   user_info.pw_uid = 17;
650 
651   EXPECT_CALL(*mock_sys_user_operations, geteuid())
652       .Times(1)
653       .WillOnce(Return(0));
654   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
655       .Times(1)
656       .WillOnce(Return(&user_info));
657   EXPECT_CALL(*mock_sys_user_operations,
658               initgroups(StrEq(USER),
659                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
660       .Times(1);
661   EXPECT_CALL(*mock_sys_user_operations, setgid(user_info.pw_gid))
662       .Times(1)
663       .WillOnce(Return(0));
664   EXPECT_CALL(*mock_sys_user_operations, setuid(user_info.pw_uid))
665       .Times(1)
666       .WillOnce(Return(0));
667 
668   ASSERT_NO_THROW({ set_user(USER, true, mock_sys_user_operations.get()); });
669 }
670 
TEST_F(AppTest,UserSetPermanentlyById)671 TEST_F(AppTest, UserSetPermanentlyById) {
672   const char *USER = "1234";
673 
674   struct passwd user_info;
675   user_info.pw_gid = 12;
676   user_info.pw_uid = 17;
677 
678   EXPECT_CALL(*mock_sys_user_operations, geteuid())
679       .Times(1)
680       .WillOnce(Return(0));
681   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
682       .Times(1)
683       .WillOnce(Return(nullptr));
684   EXPECT_CALL(*mock_sys_user_operations, getpwuid((uid_t)atoi(USER)))
685       .Times(1)
686       .WillOnce(Return(&user_info));
687   EXPECT_CALL(*mock_sys_user_operations,
688               initgroups(StrEq(USER),
689                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
690       .Times(1);
691   EXPECT_CALL(*mock_sys_user_operations, setgid(user_info.pw_gid))
692       .Times(1)
693       .WillOnce(Return(0));
694   EXPECT_CALL(*mock_sys_user_operations, setuid(user_info.pw_uid))
695       .Times(1)
696       .WillOnce(Return(0));
697 
698   ASSERT_NO_THROW({ set_user(USER, true, mock_sys_user_operations.get()); });
699 }
700 
TEST_F(AppTest,UserSetPermanentlyByNotExistingId)701 TEST_F(AppTest, UserSetPermanentlyByNotExistingId) {
702   const char *USER = "124";
703 
704   EXPECT_CALL(*mock_sys_user_operations, geteuid())
705       .Times(1)
706       .WillOnce(Return(0));
707   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
708       .Times(1)
709       .WillOnce(Return(nullptr));
710   EXPECT_CALL(*mock_sys_user_operations, getpwuid((uid_t)atoi(USER)))
711       .Times(1)
712       .WillOnce(Return(nullptr));
713 
714   try {
715     set_user(USER, true, mock_sys_user_operations.get());
716     FAIL() << "Should throw";
717   } catch (const std::runtime_error &exc) {
718     EXPECT_THAT(exc.what(), StrEq("Can't use user '124'. "
719                                   "Please check that the user exists!"));
720   }
721 }
722 
TEST_F(AppTest,UserSetPermanentlyByNotExistingName)723 TEST_F(AppTest, UserSetPermanentlyByNotExistingName) {
724   const char *USER = "124name";
725 
726   EXPECT_CALL(*mock_sys_user_operations, geteuid())
727       .Times(1)
728       .WillOnce(Return(0));
729   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
730       .Times(1)
731       .WillOnce(Return(nullptr));
732 
733   try {
734     set_user(USER, true, mock_sys_user_operations.get());
735     FAIL() << "Should throw";
736   } catch (const std::runtime_error &exc) {
737     EXPECT_THAT(exc.what(), StrEq("Can't use user '124name'. "
738                                   "Please check that the user exists!"));
739   }
740 }
741 
TEST_F(AppTest,UserSetPermanentlyByNonRootUser)742 TEST_F(AppTest, UserSetPermanentlyByNonRootUser) {
743   const char *USER = "mysqlrouter";
744 
745   EXPECT_CALL(*mock_sys_user_operations, geteuid())
746       .Times(1)
747       .WillOnce(Return(1));
748   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
749       .Times(1)
750       .WillOnce(Return(nullptr));
751 
752   try {
753     set_user(USER, true, mock_sys_user_operations.get());
754     FAIL() << "Should throw";
755   } catch (const std::runtime_error &exc) {
756     EXPECT_THAT(
757         exc.what(),
758         StrEq("One can only use the -u/--user switch if running as root"));
759   }
760 }
761 
TEST_F(AppTest,UserSetPermanentlySetEGidFails)762 TEST_F(AppTest, UserSetPermanentlySetEGidFails) {
763   const char *USER = "mysqlrouter";
764 
765   struct passwd user_info;
766   user_info.pw_gid = 12;
767   user_info.pw_uid = 17;
768 
769   EXPECT_CALL(*mock_sys_user_operations, geteuid())
770       .Times(1)
771       .WillOnce(Return(0));
772   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
773       .Times(1)
774       .WillOnce(Return(&user_info));
775   EXPECT_CALL(*mock_sys_user_operations,
776               initgroups(StrEq(USER),
777                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
778       .Times(1);
779   EXPECT_CALL(*mock_sys_user_operations, setgid(user_info.pw_gid))
780       .Times(1)
781       .WillOnce(Return(-1));
782 
783   try {
784     set_user(USER, true, mock_sys_user_operations.get());
785     FAIL() << "Should throw";
786   } catch (const std::runtime_error &exc) {
787     EXPECT_THAT(exc.what(),
788                 StartsWith("Error trying to set the user. setgid failed:"));
789   }
790 }
791 
TEST_F(AppTest,UserSetPermanentlySetEUidFails)792 TEST_F(AppTest, UserSetPermanentlySetEUidFails) {
793   const char *USER = "mysqlrouter";
794 
795   struct passwd user_info;
796   user_info.pw_gid = 12;
797   user_info.pw_uid = 17;
798 
799   EXPECT_CALL(*mock_sys_user_operations, geteuid())
800       .Times(1)
801       .WillOnce(Return(0));
802   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
803       .Times(1)
804       .WillOnce(Return(&user_info));
805   EXPECT_CALL(*mock_sys_user_operations,
806               initgroups(StrEq(USER),
807                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
808       .Times(1);
809   EXPECT_CALL(*mock_sys_user_operations, setgid(user_info.pw_gid))
810       .Times(1)
811       .WillOnce(Return(0));
812   EXPECT_CALL(*mock_sys_user_operations, setuid(user_info.pw_uid))
813       .Times(1)
814       .WillOnce(Return(-1));
815 
816   try {
817     set_user(USER, true, mock_sys_user_operations.get());
818     FAIL() << "Should throw";
819   } catch (const std::runtime_error &exc) {
820     EXPECT_THAT(exc.what(),
821                 StartsWith("Error trying to set the user. setuid failed:"));
822   }
823 }
824 
TEST_F(AppTest,UserSetByName)825 TEST_F(AppTest, UserSetByName) {
826   const char *USER = "mysqluser";
827 
828   struct passwd user_info;
829   user_info.pw_gid = 12;
830   user_info.pw_uid = 17;
831 
832   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
833       .Times(1)
834       .WillOnce(Return(&user_info));
835   EXPECT_CALL(*mock_sys_user_operations,
836               initgroups(StrEq(USER),
837                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
838       .Times(1);
839   EXPECT_CALL(*mock_sys_user_operations, setegid(user_info.pw_gid))
840       .Times(1)
841       .WillOnce(Return(0));
842   EXPECT_CALL(*mock_sys_user_operations, seteuid(user_info.pw_uid))
843       .Times(1)
844       .WillOnce(Return(0));
845 
846   ASSERT_NO_THROW({ set_user(USER, false, mock_sys_user_operations.get()); });
847 }
848 
TEST_F(AppTest,UserSetById)849 TEST_F(AppTest, UserSetById) {
850   const char *USER = "1234";
851 
852   struct passwd user_info;
853   user_info.pw_gid = 12;
854   user_info.pw_uid = 17;
855 
856   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
857       .Times(1)
858       .WillOnce(Return(nullptr));
859   EXPECT_CALL(*mock_sys_user_operations, getpwuid((uid_t)atoi(USER)))
860       .Times(1)
861       .WillOnce(Return(&user_info));
862   EXPECT_CALL(*mock_sys_user_operations,
863               initgroups(StrEq(USER),
864                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
865       .Times(1);
866   EXPECT_CALL(*mock_sys_user_operations, setegid(user_info.pw_gid))
867       .Times(1)
868       .WillOnce(Return(0));
869   EXPECT_CALL(*mock_sys_user_operations, seteuid(user_info.pw_uid))
870       .Times(1)
871       .WillOnce(Return(0));
872 
873   ASSERT_NO_THROW({ set_user(USER, false, mock_sys_user_operations.get()); });
874 }
875 
TEST_F(AppTest,UserSetByNotExistingId)876 TEST_F(AppTest, UserSetByNotExistingId) {
877   const char *USER = "124";
878 
879   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
880       .Times(1)
881       .WillOnce(Return(nullptr));
882   EXPECT_CALL(*mock_sys_user_operations, getpwuid((uid_t)atoi(USER)))
883       .Times(1)
884       .WillOnce(Return(nullptr));
885 
886   try {
887     set_user(USER, false, mock_sys_user_operations.get());
888     FAIL() << "Should throw";
889   } catch (const std::runtime_error &exc) {
890     EXPECT_THAT(exc.what(), StrEq("Can't use user '124'. "
891                                   "Please check that the user exists!"));
892   }
893 }
894 
TEST_F(AppTest,UserSetByNotExistingName)895 TEST_F(AppTest, UserSetByNotExistingName) {
896   const char *USER = "124name";
897 
898   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
899       .Times(1)
900       .WillOnce(Return(nullptr));
901 
902   try {
903     set_user(USER, false, mock_sys_user_operations.get());
904     FAIL() << "Should throw";
905   } catch (const std::runtime_error &exc) {
906     EXPECT_THAT(exc.what(), StrEq("Can't use user '124name'. "
907                                   "Please check that the user exists!"));
908   }
909 }
910 
TEST_F(AppTest,UserSetSetGidFails)911 TEST_F(AppTest, UserSetSetGidFails) {
912   const char *USER = "mysqlrouter";
913 
914   struct passwd user_info;
915   user_info.pw_gid = 12;
916   user_info.pw_uid = 17;
917 
918   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
919       .Times(1)
920       .WillOnce(Return(&user_info));
921   EXPECT_CALL(*mock_sys_user_operations,
922               initgroups(StrEq(USER),
923                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
924       .Times(1);
925   EXPECT_CALL(*mock_sys_user_operations, setegid(user_info.pw_gid))
926       .Times(1)
927       .WillOnce(Return(-1));
928 
929   try {
930     set_user(USER, false, mock_sys_user_operations.get());
931     FAIL() << "Should throw";
932   } catch (const std::runtime_error &exc) {
933     EXPECT_THAT(exc.what(),
934                 StartsWith("Error trying to set the user. setegid failed:"));
935   }
936 }
937 
TEST_F(AppTest,UserSetSetUidFails)938 TEST_F(AppTest, UserSetSetUidFails) {
939   const char *USER = "mysqlrouter";
940 
941   struct passwd user_info;
942   user_info.pw_gid = 12;
943   user_info.pw_uid = 17;
944 
945   EXPECT_CALL(*mock_sys_user_operations, getpwnam(StrEq(USER)))
946       .Times(1)
947       .WillOnce(Return(&user_info));
948   EXPECT_CALL(*mock_sys_user_operations,
949               initgroups(StrEq(USER),
950                          (SysUserOperationsBase::gid_type)user_info.pw_gid))
951       .Times(1);
952   EXPECT_CALL(*mock_sys_user_operations, setegid(user_info.pw_gid))
953       .Times(1)
954       .WillOnce(Return(0));
955   EXPECT_CALL(*mock_sys_user_operations, seteuid(user_info.pw_uid))
956       .Times(1)
957       .WillOnce(Return(-1));
958 
959   try {
960     set_user(USER, false, mock_sys_user_operations.get());
961     FAIL() << "Should throw";
962   } catch (const std::runtime_error &exc) {
963     EXPECT_THAT(exc.what(),
964                 StartsWith("Error trying to set the user. seteuid failed:"));
965   }
966 }
967 
TEST_F(AppTest,BootstrapSuperuserNoUserOption)968 TEST_F(AppTest, BootstrapSuperuserNoUserOption) {
969   vector<string> argv = {"--bootstrap", "127.0.0.1:3060"};
970 
971   EXPECT_CALL(*mock_sys_user_operations, geteuid())
972       .Times(1)
973       .WillOnce(Return(0));
974 
975   try {
976     MySQLRouter r(g_origin, argv, std::cout, std::cerr,
977                   mock_sys_user_operations.get());
978     FAIL() << "Should throw";
979   } catch (const std::runtime_error &exc) {
980     EXPECT_THAT(exc.what(), StartsWith("You are bootstraping as a superuser."));
981   }
982 }
983 
984 /**
985  * @test
986  *      Verify that std::runtime_error is thrown when --master-key-reader option
987  * is used in non-bootstrap mode.
988  */
TEST_F(AppTest,ThrowWhenMasterKeyReaderUsedWithoutBootstrap)989 TEST_F(AppTest, ThrowWhenMasterKeyReaderUsedWithoutBootstrap) {
990   vector<string> argv = {"--master-key-reader=reader.sh"};
991   ASSERT_THROW_LIKE(MySQLRouter(g_origin, argv, std::cout, std::cerr,
992                                 mock_sys_user_operations.get()),
993                     std::runtime_error,
994                     "Option --master-key-reader can only be used together with "
995                     "-B/--bootstrap");
996 }
997 
998 /**
999  * @test
1000  *       Verify that std::runtime_error is thrown when --master_key-writer
1001  * option is used in non-bootstrap mode.
1002  */
TEST_F(AppTest,ThrowWhenMasterKeyWriterUsedWithoutBootstrap)1003 TEST_F(AppTest, ThrowWhenMasterKeyWriterUsedWithoutBootstrap) {
1004   vector<string> argv = {"--master-key-writer=writer.sh"};
1005   ASSERT_THROW_LIKE(MySQLRouter(g_origin, argv, std::cout, std::cerr,
1006                                 mock_sys_user_operations.get()),
1007                     std::runtime_error,
1008                     "Option --master-key-writer can only be used together with "
1009                     "-B/--bootstrap");
1010 }
1011 
1012 /**
1013  * @test
1014  *       Verify that std::runtime_error is thrown when --master-key-reader
1015  * option is used without value.
1016  */
TEST_F(AppTest,ThrowWhenMasterKeyReaderUsedWithoutValue)1017 TEST_F(AppTest, ThrowWhenMasterKeyReaderUsedWithoutValue) {
1018   vector<string> argv = {"--bootstrap", "127.0.0.1:3060",
1019                          "--master-key-reader"};
1020   ASSERT_THROW_LIKE(
1021       MySQLRouter(g_origin, argv, std::cout, std::cerr,
1022                   mock_sys_user_operations.get()),
1023       std::runtime_error,
1024       "option '--master-key-reader' expects a value, got nothing");
1025 }
1026 
1027 /**
1028  * @test
1029  *       Verify that std::runtime_error is thrown when --master-key-writer
1030  * option is used without value.
1031  */
TEST_F(AppTest,ThrowWhenMasterKeyWriterUsedWithoutValue)1032 TEST_F(AppTest, ThrowWhenMasterKeyWriterUsedWithoutValue) {
1033   vector<string> argv = {"--bootstrap", "127.0.0.1:3060",
1034                          "--master-key-writer"};
1035   ASSERT_THROW_LIKE(
1036       MySQLRouter(g_origin, argv, std::cout, std::cerr,
1037                   mock_sys_user_operations.get()),
1038       std::runtime_error,
1039       "option '--master-key-writer' expects a value, got nothing");
1040 }
1041 
1042 /**
1043  * @test
1044  *       Verify that std::runtime_error is throw when --master-key-reader option
1045  * is used without using --master-key-writer option.
1046  */
TEST_F(AppTest,ThrowWhenMasterKeyReaderUsedWithoutMasterKeyWriter)1047 TEST_F(AppTest, ThrowWhenMasterKeyReaderUsedWithoutMasterKeyWriter) {
1048   vector<string> argv = {"--bootstrap", "127.0.0.1:3060",
1049                          "--master-key-reader=reader.sh"};
1050   ASSERT_THROW_LIKE(MySQLRouter(g_origin, argv, std::cout, std::cerr,
1051                                 mock_sys_user_operations.get()),
1052                     std::runtime_error,
1053                     "Option --master-key-reader can only be used together with "
1054                     "--master-key-writer.");
1055 }
1056 
1057 /**
1058  * @test
1059  *       Verify that std::runtime_error is thrown when --master-key-writer
1060  * option is used without using --master-key-reader option.
1061  */
TEST_F(AppTest,ThrowWhenMasterKeyWriterUsedWithoutMasterKeyReader)1062 TEST_F(AppTest, ThrowWhenMasterKeyWriterUsedWithoutMasterKeyReader) {
1063   vector<string> argv = {"--bootstrap", "127.0.0.1:3060",
1064                          "--master-key-writer=writer.sh"};
1065   ASSERT_THROW_LIKE(MySQLRouter(g_origin, argv, std::cout, std::cerr,
1066                                 mock_sys_user_operations.get()),
1067                     std::runtime_error,
1068                     "Option --master-key-writer can only be used together with "
1069                     "--master-key-reader.");
1070 }
1071 
1072 #endif  // #ifndef _WIN32
1073 
1074 class AppLoggerTest : public ConsoleOutputTest {
1075  protected:
SetUp()1076   void SetUp() override {
1077     set_origin(g_origin);
1078     ConsoleOutputTest::SetUp();
1079   }
1080 };
1081 
TEST_F(AppLoggerTest,TestLogger)1082 TEST_F(AppLoggerTest, TestLogger) {
1083   // This test verifies that:
1084   // - setting log level works (by overriding the default)
1085   // - a logger is created for each of: main exec and all plugins
1086 
1087   // create config file
1088   Path config_path(*temp_dir);
1089   config_path.append("test_mysqlrouter_app.conf");
1090   std::ofstream ofs_config(config_path.str());
1091   if (ofs_config.good()) {
1092     ofs_config << "[DEFAULT]\n";
1093     ofs_config << "logging_folder =\n";
1094     ofs_config << "plugin_folder = " << plugin_dir->str() << "\n";
1095     ofs_config << "runtime_folder = " << temp_dir->str() << "\n";
1096     ofs_config << "config_folder = " << config_dir->str() << "\n";
1097     ofs_config << "\n";
1098     ofs_config << "[logger]\n";
1099     ofs_config << "level = DEBUG\n";  // override the default (WARNING)
1100     ofs_config << "\n";
1101     ofs_config << "[" << kPluginNameMagic << "]\n";  // magic plugin
1102     ofs_config << "do_magic = yes\n";
1103     ofs_config << "message = It is some kind of magic\n";
1104     ofs_config << "\n";
1105     ofs_config << "[" << kPluginNameLifecycle3
1106                << "]\n";  // lifecycle3 plugin (lifecycle dependency)
1107     ofs_config << "[" << kPluginNameLifecycle
1108                << ":instance1]\n";  // lifecycle plugin
1109     ofs_config.close();
1110   } else {
1111     throw std::runtime_error("Failed creating config file '" +
1112                              config_path.str() + "'");
1113   }
1114 
1115   // run MySQLRouter
1116   reset_ssout();
1117   vector<string> argv = {"-c", config_path.c_str()};
1118   MySQLRouter r(g_origin, argv);
1119   ASSERT_NO_THROW(r.start()) << get_log_stream().str();
1120 
1121   // verify that all plugins have a module registered with the logger
1122   auto loggers =
1123       mysql_harness::DIM::instance().get_LoggingRegistry().get_logger_names();
1124   EXPECT_THAT(loggers, testing::UnorderedElementsAre(
1125                            mysql_harness::logging::kMainLogger,
1126                            kPluginNameMagic, kPluginNameLifecycle,
1127                            kPluginNameLifecycle3, "sql", "logger"));
1128 
1129   // verify the log contains what we expect it to contain. We're looking for
1130   // lines like this:
1131   {
1132     // 2017-05-03 11:30:25 magic INFO [7ffff5e34700] It is some kind of magic
1133     EXPECT_THAT(get_log_stream().str(),
1134                 HasSubstr(" " + kPluginNameMagic + " INFO "));
1135     EXPECT_THAT(get_log_stream().str(), HasSubstr(" It is some kind of magic"));
1136 
1137     // 2017-05-03 11:30:25 lifecycle INFO [7faefa705780] lifecycle:all
1138     // init():begin
1139     EXPECT_THAT(get_log_stream().str(),
1140                 HasSubstr(" " + kPluginNameLifecycle + " INFO "));
1141     EXPECT_THAT(get_log_stream().str(),
1142                 HasSubstr(" lifecycle:all init():begin"));
1143   }
1144 }
1145 
TEST_F(AppTest,EmptyConfigPath)1146 TEST_F(AppTest, EmptyConfigPath) {
1147   vector<string> argv = {"--config", ""};
1148   EXPECT_THROW({ MySQLRouter r(g_origin, argv); }, std::runtime_error);
1149 }
1150 
main(int argc,char * argv[])1151 int main(int argc, char *argv[]) {
1152   g_origin = Path(argv[0]).dirname();
1153 
1154   ::testing::InitGoogleTest(&argc, argv);
1155   return RUN_ALL_TESTS();
1156 }
1157