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 [®istry]() { 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 [®istry]() { 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