Improve online_delete configuration and DB tuning:

* Document delete_batch, back_off_milliseconds, age_threshold_seconds.
* Convert those time values to chrono types.
* Fix bug that ignored age_threshold_seconds.
* Add a "recovery buffer" to the config that gives the node a chance to
  recover before aborting online delete.
* Add begin/end log messages around the SQL queries.
* Add a new configuration section: [sqlite] to allow tuning the sqlite
  database operations. Ignored on full/large history servers.
* Update documentation of [node_db] and [sqlite] in the
  rippled-example.cfg file.

Resolves #3321
This commit is contained in:
Edward Hennis
2020-05-11 16:48:34 -04:00
committed by Nik Bougalis
parent 00702f28c2
commit 4702c8b591
21 changed files with 1086 additions and 271 deletions

View File

@@ -27,6 +27,7 @@
#include <memory>
#include <sstream>
#include <test/jtx.h>
#include <test/jtx/CheckMessageLogs.h>
namespace ripple {
namespace test {
@@ -34,56 +35,6 @@ namespace test {
class LedgerHistory_test : public beast::unit_test::suite
{
public:
/** Log manager that searches for a specific message substring
*/
class CheckMessageLogs : public Logs
{
std::string msg_;
bool& found_;
class CheckMessageSink : public beast::Journal::Sink
{
CheckMessageLogs& owner_;
public:
CheckMessageSink(
beast::severities::Severity threshold,
CheckMessageLogs& owner)
: beast::Journal::Sink(threshold, false), owner_(owner)
{
}
void
write(beast::severities::Severity level, std::string const& text)
override
{
if (text.find(owner_.msg_) != std::string::npos)
owner_.found_ = true;
}
};
public:
/** Constructor
@param msg The message string to search for
@param found The variable to set to true if the message is found
*/
CheckMessageLogs(std::string msg, bool& found)
: Logs{beast::severities::kDebug}
, msg_{std::move(msg)}
, found_{found}
{
}
std::unique_ptr<beast::Journal::Sink>
makeSink(
std::string const& partition,
beast::severities::Severity threshold) override
{
return std::make_unique<CheckMessageSink>(threshold, *this);
}
};
/** Generate a new ledger by hand, applying a specific close time offset
and optionally inserting a transaction.
@@ -149,7 +100,7 @@ public:
Env env{
*this,
envconfig(),
std::make_unique<CheckMessageLogs>("MISMATCH ", found)};
std::make_unique<CheckMessageLogs>("MISMATCH ", &found)};
LedgerHistory lh{beast::insight::NullCollector::New(), env.app()};
auto const genesis = makeLedger({}, env, lh, 0s);
uint256 const dummyTxHash{1};
@@ -166,7 +117,7 @@ public:
*this,
envconfig(),
std::make_unique<CheckMessageLogs>(
"MISMATCH on close time", found)};
"MISMATCH on close time", &found)};
LedgerHistory lh{beast::insight::NullCollector::New(), env.app()};
auto const genesis = makeLedger({}, env, lh, 0s);
auto const ledgerA = makeLedger(genesis, env, lh, 4s);
@@ -186,7 +137,7 @@ public:
*this,
envconfig(),
std::make_unique<CheckMessageLogs>(
"MISMATCH on prior ledger", found)};
"MISMATCH on prior ledger", &found)};
LedgerHistory lh{beast::insight::NullCollector::New(), env.app()};
auto const genesis = makeLedger({}, env, lh, 0s);
auto const ledgerA = makeLedger(genesis, env, lh, 4s);
@@ -212,7 +163,7 @@ public:
Env env{
*this,
envconfig(),
std::make_unique<CheckMessageLogs>(msg, found)};
std::make_unique<CheckMessageLogs>(msg, &found)};
LedgerHistory lh{beast::insight::NullCollector::New(), env.app()};
Account alice{"A1"};

View File

@@ -256,6 +256,7 @@ public:
{
DatabaseCon::Setup setup;
setup.dataDir = getDatabasePath();
BEAST_EXPECT(!setup.useGlobalPragma);
DatabaseCon dbCon(
setup,
dbName.data(),

View File

@@ -0,0 +1,80 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/basics/Log.h>
namespace ripple {
namespace test {
/**
* @brief Log manager for CaptureSinks. This class holds the stream
* instance that is written to by the sinks. Upon destruction, all
* contents of the stream are assigned to the string specified in the
* ctor
*/
class CaptureLogs : public Logs
{
std::stringstream strm_;
std::string* pResult_;
/**
* @brief sink for writing all log messages to a stringstream
*/
class CaptureSink : public beast::Journal::Sink
{
std::stringstream& strm_;
public:
CaptureSink(
beast::severities::Severity threshold,
std::stringstream& strm)
: beast::Journal::Sink(threshold, false), strm_(strm)
{
}
void
write(beast::severities::Severity level, std::string const& text)
override
{
strm_ << text;
}
};
public:
explicit CaptureLogs(std::string* pResult)
: Logs(beast::severities::kInfo), pResult_(pResult)
{
}
~CaptureLogs() override
{
*pResult_ = strm_.str();
}
std::unique_ptr<beast::Journal::Sink>
makeSink(
std::string const& partition,
beast::severities::Severity threshold) override
{
return std::make_unique<CaptureSink>(threshold, strm_);
}
};
} // namespace test
} // namespace ripple

View File

@@ -0,0 +1,75 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <ripple/basics/Log.h>
namespace ripple {
namespace test {
/** Log manager that searches for a specific message substring
*/
class CheckMessageLogs : public Logs
{
std::string msg_;
bool* pFound_;
class CheckMessageSink : public beast::Journal::Sink
{
CheckMessageLogs& owner_;
public:
CheckMessageSink(
beast::severities::Severity threshold,
CheckMessageLogs& owner)
: beast::Journal::Sink(threshold, false), owner_(owner)
{
}
void
write(beast::severities::Severity level, std::string const& text)
override
{
if (text.find(owner_.msg_) != std::string::npos)
*owner_.pFound_ = true;
}
};
public:
/** Constructor
@param msg The message string to search for
@param pFound Pointer to the variable to set to true if the message is
found
*/
CheckMessageLogs(std::string msg, bool* pFound)
: Logs{beast::severities::kDebug}, msg_{std::move(msg)}, pFound_{pFound}
{
}
std::unique_ptr<beast::Journal::Sink>
makeSink(
std::string const& partition,
beast::severities::Severity threshold) override
{
return std::make_unique<CheckMessageSink>(threshold, *this);
}
};
} // namespace test
} // namespace ripple

View File

@@ -27,6 +27,7 @@
#include <ripple/basics/Log.h>
#include <ripple/basics/chrono.h>
#include <ripple/beast/cxx17/type_traits.h> // <type_traits>
#include <ripple/beast/utility/Journal.h>
#include <ripple/core/Config.h>
#include <ripple/json/json_value.h>
#include <ripple/json/to_string.h>
@@ -131,7 +132,8 @@ private:
AppBundle(
beast::unit_test::suite& suite,
std::unique_ptr<Config> config,
std::unique_ptr<Logs> logs);
std::unique_ptr<Logs> logs,
beast::severities::Severity thresh);
~AppBundle();
};
@@ -163,12 +165,10 @@ public:
Env(beast::unit_test::suite& suite_,
std::unique_ptr<Config> config,
FeatureBitset features,
std::unique_ptr<Logs> logs = nullptr)
std::unique_ptr<Logs> logs = nullptr,
beast::severities::Severity thresh = beast::severities::kError)
: test(suite_)
, bundle_(
suite_,
std::move(config),
logs ? std::move(logs) : std::make_unique<SuiteLogs>(suite_))
, bundle_(suite_, std::move(config), std::move(logs), thresh)
, journal{bundle_.app->journal("Env")}
{
memoize(Account::master);
@@ -211,11 +211,13 @@ public:
*/
Env(beast::unit_test::suite& suite_,
std::unique_ptr<Config> config,
std::unique_ptr<Logs> logs = nullptr)
std::unique_ptr<Logs> logs = nullptr,
beast::severities::Severity thresh = beast::severities::kError)
: Env(suite_,
std::move(config),
supported_amendments(),
std::move(logs))
std::move(logs),
thresh)
{
}

View File

@@ -59,12 +59,22 @@ namespace jtx {
Env::AppBundle::AppBundle(
beast::unit_test::suite& suite,
std::unique_ptr<Config> config,
std::unique_ptr<Logs> logs)
std::unique_ptr<Logs> logs,
beast::severities::Severity thresh)
: AppBundle()
{
using namespace beast::severities;
// Use kFatal threshold to reduce noise from STObject.
setDebugLogSink(std::make_unique<SuiteJournalSink>("Debug", kFatal, suite));
if (logs)
{
setDebugLogSink(logs->makeSink("Debug", kFatal));
}
else
{
logs = std::make_unique<SuiteLogs>(suite);
// Use kFatal threshold to reduce noise from STObject.
setDebugLogSink(
std::make_unique<SuiteJournalSink>("Debug", kFatal, suite));
}
auto timeKeeper_ = std::make_unique<ManualTimeKeeper>();
timeKeeper = timeKeeper_.get();
// Hack so we don't have to call Config::setup
@@ -72,7 +82,7 @@ Env::AppBundle::AppBundle(
owned = make_Application(
std::move(config), std::move(logs), std::move(timeKeeper_));
app = owned.get();
app->logs().threshold(kError);
app->logs().threshold(thresh);
if (!app->setup())
Throw<std::runtime_error>("Env::AppBundle: setup failed");
timeKeeper->set(app->getLedgerMaster().getClosedLedger()->info().closeTime);

View File

@@ -18,8 +18,12 @@
//==============================================================================
#include <ripple/beast/utility/temp_dir.h>
#include <ripple/core/DatabaseCon.h>
#include <ripple/nodestore/DummyScheduler.h>
#include <ripple/nodestore/Manager.h>
#include <test/jtx.h>
#include <test/jtx/CheckMessageLogs.h>
#include <test/jtx/envconfig.h>
#include <test/nodestore/TestBase.h>
#include <test/unit_test/SuiteJournal.h>
@@ -35,6 +39,409 @@ public:
{
}
void
testConfig()
{
testcase("Config");
using namespace ripple::test;
using namespace ripple::test::jtx;
auto const integrityWarning =
"reducing the data integrity guarantees from the "
"default [sqlite] behavior is not recommended for "
"nodes storing large amounts of history, because of the "
"difficulty inherent in rebuilding corrupted data.";
{
// defaults
Env env(*this);
auto const s = setup_DatabaseCon(env.app().config());
if (BEAST_EXPECT(s.globalPragma->size() == 3))
{
BEAST_EXPECT(
s.globalPragma->at(0) == "PRAGMA journal_mode=wal;");
BEAST_EXPECT(
s.globalPragma->at(1) == "PRAGMA synchronous=normal;");
BEAST_EXPECT(
s.globalPragma->at(2) == "PRAGMA temp_store=file;");
}
}
{
// High safety level
DatabaseCon::Setup::globalPragma.reset();
bool found = false;
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("safety_level", "high");
}
p->LEDGER_HISTORY = 100'000'000;
return Env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(
integrityWarning, &found),
beast::severities::kWarning);
}();
BEAST_EXPECT(!found);
auto const s = setup_DatabaseCon(env.app().config());
if (BEAST_EXPECT(s.globalPragma->size() == 3))
{
BEAST_EXPECT(
s.globalPragma->at(0) == "PRAGMA journal_mode=wal;");
BEAST_EXPECT(
s.globalPragma->at(1) == "PRAGMA synchronous=normal;");
BEAST_EXPECT(
s.globalPragma->at(2) == "PRAGMA temp_store=file;");
}
}
{
// Low safety level
DatabaseCon::Setup::globalPragma.reset();
bool found = false;
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("safety_level", "low");
}
p->LEDGER_HISTORY = 100'000'000;
return Env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(
integrityWarning, &found),
beast::severities::kWarning);
}();
BEAST_EXPECT(found);
auto const s = setup_DatabaseCon(env.app().config());
if (BEAST_EXPECT(s.globalPragma->size() == 3))
{
BEAST_EXPECT(
s.globalPragma->at(0) == "PRAGMA journal_mode=memory;");
BEAST_EXPECT(
s.globalPragma->at(1) == "PRAGMA synchronous=off;");
BEAST_EXPECT(
s.globalPragma->at(2) == "PRAGMA temp_store=memory;");
}
}
{
// Override individual settings
DatabaseCon::Setup::globalPragma.reset();
bool found = false;
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("journal_mode", "off");
section.set("synchronous", "extra");
section.set("temp_store", "default");
}
return Env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(
integrityWarning, &found),
beast::severities::kWarning);
}();
// No warning, even though higher risk settings were used because
// LEDGER_HISTORY is small
BEAST_EXPECT(!found);
auto const s = setup_DatabaseCon(env.app().config());
if (BEAST_EXPECT(s.globalPragma->size() == 3))
{
BEAST_EXPECT(
s.globalPragma->at(0) == "PRAGMA journal_mode=off;");
BEAST_EXPECT(
s.globalPragma->at(1) == "PRAGMA synchronous=extra;");
BEAST_EXPECT(
s.globalPragma->at(2) == "PRAGMA temp_store=default;");
}
}
{
// Override individual settings with large history
DatabaseCon::Setup::globalPragma.reset();
bool found = false;
Env env = [&]() {
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("journal_mode", "off");
section.set("synchronous", "extra");
section.set("temp_store", "default");
}
p->LEDGER_HISTORY = 50'000'000;
return Env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(
integrityWarning, &found),
beast::severities::kWarning);
}();
// No warning, even though higher risk settings were used because
// LEDGER_HISTORY is small
BEAST_EXPECT(found);
auto const s = setup_DatabaseCon(env.app().config());
if (BEAST_EXPECT(s.globalPragma->size() == 3))
{
BEAST_EXPECT(
s.globalPragma->at(0) == "PRAGMA journal_mode=off;");
BEAST_EXPECT(
s.globalPragma->at(1) == "PRAGMA synchronous=extra;");
BEAST_EXPECT(
s.globalPragma->at(2) == "PRAGMA temp_store=default;");
}
}
{
// Error: Mix safety_level and individual settings
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: "
"Configuration file may not define both \"safety_level\" and "
"\"journal_mode\"";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("safety_level", "low");
section.set("journal_mode", "off");
section.set("synchronous", "extra");
section.set("temp_store", "default");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
{
// Error: Mix safety_level and one setting (gotta catch 'em all)
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: Configuration file may "
"not define both \"safety_level\" and \"journal_mode\"";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("safety_level", "high");
section.set("journal_mode", "off");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
{
// Error: Mix safety_level and one setting (gotta catch 'em all)
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: Configuration file may "
"not define both \"safety_level\" and \"synchronous\"";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("safety_level", "low");
section.set("synchronous", "extra");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
{
// Error: Mix safety_level and one setting (gotta catch 'em all)
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: Configuration file may "
"not define both \"safety_level\" and \"temp_store\"";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("safety_level", "high");
section.set("temp_store", "default");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
{
// Error: Invalid value
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: Invalid safety_level "
"value: slow";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("safety_level", "slow");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
{
// Error: Invalid value
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: Invalid journal_mode "
"value: fast";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("journal_mode", "fast");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
{
// Error: Invalid value
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: Invalid synchronous "
"value: instant";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("synchronous", "instant");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
{
// Error: Invalid value
DatabaseCon::Setup::globalPragma.reset();
auto const expected =
"Failed to initialize SQLite databases: Invalid temp_store "
"value: network";
bool found = false;
auto p = test::jtx::envconfig();
{
auto& section = p->section("sqlite");
section.set("temp_store", "network");
}
try
{
Env env(
*this,
std::move(p),
std::make_unique<CheckMessageLogs>(expected, &found),
beast::severities::kWarning);
fail();
}
catch (...)
{
BEAST_EXPECT(found);
}
}
}
//--------------------------------------------------------------------------
void
testImport(
std::string const& destBackendType,
@@ -221,6 +628,8 @@ public:
{
std::int64_t const seedValue = 50;
testConfig();
testNodeStore("memory", false, seedValue);
// Persistent backend tests

View File

@@ -31,6 +31,7 @@
#include <chrono>
#include <stdexcept>
#include <test/jtx.h>
#include <test/jtx/CaptureLogs.h>
#include <test/jtx/envconfig.h>
#include <test/unit_test/SuiteJournal.h>
#include <thread>
@@ -375,60 +376,6 @@ public:
pass();
}
/**
* @brief sink for writing all log messages to a stringstream
*/
class CaptureSink : public beast::Journal::Sink
{
std::stringstream& strm_;
public:
CaptureSink(
beast::severities::Severity threshold,
std::stringstream& strm)
: beast::Journal::Sink(threshold, false), strm_(strm)
{
}
void
write(beast::severities::Severity level, std::string const& text)
override
{
strm_ << text;
}
};
/**
* @brief Log manager for CaptureSinks. This class holds the stream
* instance that is written to by the sinks. Upon destruction, all
* contents of the stream are assigned to the string specified in the
* ctor
*/
class CaptureLogs : public Logs
{
std::stringstream strm_;
std::string& result_;
public:
explicit CaptureLogs(std::string& result)
: Logs(beast::severities::kInfo), result_(result)
{
}
~CaptureLogs() override
{
result_ = strm_.str();
}
std::unique_ptr<beast::Journal::Sink>
makeSink(
std::string const& partition,
beast::severities::Severity threshold) override
{
return std::make_unique<CaptureSink>(threshold, strm_);
}
};
void
testBadConfig()
{
@@ -444,7 +391,7 @@ public:
(*cfg).deprecatedClearSection("port_rpc");
return cfg;
}),
std::make_unique<CaptureLogs>(messages)};
std::make_unique<CaptureLogs>(&messages)};
});
BEAST_EXPECT(
messages.find("Missing 'ip' in [port_rpc]") != std::string::npos);
@@ -457,7 +404,7 @@ public:
(*cfg)["port_rpc"].set("ip", getEnvLocalhostAddr());
return cfg;
}),
std::make_unique<CaptureLogs>(messages)};
std::make_unique<CaptureLogs>(&messages)};
});
BEAST_EXPECT(
messages.find("Missing 'port' in [port_rpc]") != std::string::npos);
@@ -471,7 +418,7 @@ public:
(*cfg)["port_rpc"].set("port", "0");
return cfg;
}),
std::make_unique<CaptureLogs>(messages)};
std::make_unique<CaptureLogs>(&messages)};
});
BEAST_EXPECT(
messages.find("Invalid value '0' for key 'port' in [port_rpc]") !=
@@ -487,7 +434,7 @@ public:
(*cfg)["port_rpc"].set("protocol", "");
return cfg;
}),
std::make_unique<CaptureLogs>(messages)};
std::make_unique<CaptureLogs>(&messages)};
});
BEAST_EXPECT(
messages.find("Missing 'protocol' in [port_rpc]") !=
@@ -522,7 +469,7 @@ public:
(*cfg)["port_ws"].set("admin", getEnvLocalhostAddr());
return cfg;
}),
std::make_unique<CaptureLogs>(messages)};
std::make_unique<CaptureLogs>(&messages)};
});
BEAST_EXPECT(
messages.find("Required section [server] is missing") !=
@@ -548,7 +495,7 @@ public:
(*cfg)["server"].append("port_ws");
return cfg;
}),
std::make_unique<CaptureLogs>(messages)};
std::make_unique<CaptureLogs>(&messages)};
});
BEAST_EXPECT(
messages.find("Missing section: [port_peer]") != std::string::npos);