2018-04-14 23:03:04 +02:00
|
|
|
#include "../interface.h"
|
|
|
|
|
|
|
|
#include <c++utilities/chrono/datetime.h>
|
|
|
|
#include <c++utilities/chrono/timespan.h>
|
2018-04-18 00:15:31 +02:00
|
|
|
#include <c++utilities/conversion/stringbuilder.h>
|
2018-04-14 23:03:04 +02:00
|
|
|
#include <c++utilities/conversion/stringconversion.h>
|
|
|
|
#include <c++utilities/io/path.h>
|
|
|
|
#include <c++utilities/tests/testutils.h>
|
|
|
|
|
|
|
|
#include <cppunit/TestFixture.h>
|
|
|
|
#include <cppunit/extensions/HelperMacros.h>
|
|
|
|
|
2023-02-11 15:55:45 +01:00
|
|
|
#include <chrono>
|
2018-04-17 23:54:43 +02:00
|
|
|
#include <cstdlib>
|
2019-06-10 22:48:26 +02:00
|
|
|
#include <filesystem>
|
2018-04-17 23:54:43 +02:00
|
|
|
#include <functional>
|
2019-07-18 16:38:19 +02:00
|
|
|
#include <thread>
|
2018-04-17 23:54:43 +02:00
|
|
|
|
2023-12-30 19:24:28 +01:00
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
|
|
#include <windows.h>
|
|
|
|
#endif
|
|
|
|
|
2018-04-14 23:03:04 +02:00
|
|
|
using namespace std;
|
2019-06-10 22:48:26 +02:00
|
|
|
using namespace CppUtilities;
|
2018-04-14 23:03:04 +02:00
|
|
|
using namespace LibSyncthing;
|
|
|
|
|
|
|
|
using namespace CPPUNIT_NS;
|
|
|
|
|
|
|
|
/*!
|
2019-10-13 19:37:32 +02:00
|
|
|
* \brief The InterfaceTests class tests the C++ interface of "libsyncthing".
|
2018-04-14 23:03:04 +02:00
|
|
|
*/
|
|
|
|
class InterfaceTests : public TestFixture {
|
|
|
|
CPPUNIT_TEST_SUITE(InterfaceTests);
|
2019-07-18 16:38:19 +02:00
|
|
|
CPPUNIT_TEST(testInitialState);
|
2018-04-14 23:03:04 +02:00
|
|
|
CPPUNIT_TEST(testVersion);
|
2019-10-13 19:37:32 +02:00
|
|
|
CPPUNIT_TEST(testRunWithoutConfig);
|
2021-10-13 00:14:49 +02:00
|
|
|
CPPUNIT_TEST(testRunWithConfig);
|
2023-05-29 17:29:41 +02:00
|
|
|
CPPUNIT_TEST(testRunCli);
|
|
|
|
CPPUNIT_TEST(testRunCommand);
|
2018-04-14 23:03:04 +02:00
|
|
|
CPPUNIT_TEST_SUITE_END();
|
|
|
|
|
|
|
|
public:
|
|
|
|
InterfaceTests();
|
|
|
|
|
2018-04-17 23:54:43 +02:00
|
|
|
void testInitialState();
|
2018-04-14 23:03:04 +02:00
|
|
|
void testVersion();
|
2019-10-13 19:37:32 +02:00
|
|
|
void testRunWithoutConfig();
|
2021-10-13 00:14:49 +02:00
|
|
|
void testRunWithConfig();
|
2023-05-29 17:29:41 +02:00
|
|
|
void testRunCli();
|
|
|
|
void testRunCommand();
|
2018-04-14 23:03:04 +02:00
|
|
|
|
2019-10-13 18:28:32 +02:00
|
|
|
void setUp() override;
|
|
|
|
void tearDown() override;
|
2018-04-14 23:03:04 +02:00
|
|
|
|
|
|
|
private:
|
2019-10-13 19:37:32 +02:00
|
|
|
std::string setupTestConfigDir();
|
|
|
|
void testRun(const std::function<long long(void)> &runFunction, bool assertTestConfig);
|
2018-04-14 23:03:04 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
CPPUNIT_TEST_SUITE_REGISTRATION(InterfaceTests);
|
|
|
|
|
|
|
|
InterfaceTests::InterfaceTests()
|
|
|
|
{
|
2023-12-30 19:24:28 +01:00
|
|
|
#ifdef PLATFORM_WINDOWS
|
|
|
|
SetEnvironmentVariableW(L"STNOUPGRADE", L"1");
|
|
|
|
#else
|
2018-04-17 23:52:20 +02:00
|
|
|
setenv("STNOUPGRADE", "1", 1);
|
2023-12-30 19:24:28 +01:00
|
|
|
#endif
|
2018-04-14 23:03:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceTests::setUp()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void InterfaceTests::tearDown()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2018-04-17 23:54:43 +02:00
|
|
|
* \brief Initializes the Syncthing config for this fixture (currently using same config as in connector test).
|
|
|
|
* \returns Returns the config directory.
|
2018-04-14 23:03:04 +02:00
|
|
|
*/
|
2019-10-13 19:37:32 +02:00
|
|
|
string InterfaceTests::setupTestConfigDir()
|
2018-04-14 23:03:04 +02:00
|
|
|
{
|
2018-04-15 20:19:14 +02:00
|
|
|
// setup Syncthing config (currently using same config as in connector test)
|
2018-04-14 23:03:04 +02:00
|
|
|
const auto configFilePath(workingCopyPath("testconfig/config.xml"));
|
|
|
|
if (configFilePath.empty()) {
|
|
|
|
throw runtime_error("Unable to setup Syncthing config directory.");
|
|
|
|
}
|
2019-06-10 22:48:26 +02:00
|
|
|
|
2018-04-18 00:15:31 +02:00
|
|
|
// clean database
|
|
|
|
const auto configDir(directory(configFilePath));
|
2019-06-10 22:48:26 +02:00
|
|
|
try {
|
|
|
|
const auto dirIterator = filesystem::directory_iterator(configDir);
|
|
|
|
for (const auto &dir : dirIterator) {
|
2019-06-22 16:38:59 +02:00
|
|
|
const auto dirPath = dir.path();
|
|
|
|
if (!dir.is_directory() || dirPath == "." || dirPath == "..") {
|
2019-06-10 22:48:26 +02:00
|
|
|
continue;
|
|
|
|
}
|
2019-07-18 16:38:19 +02:00
|
|
|
const auto subdirIterator = filesystem::directory_iterator(dirPath);
|
2019-06-10 22:48:26 +02:00
|
|
|
for (const auto &file : subdirIterator) {
|
|
|
|
if (file.is_directory()) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-07-18 16:38:19 +02:00
|
|
|
const auto toRemove = file.path().string();
|
2019-06-10 22:48:26 +02:00
|
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("removing " + toRemove, 0, remove(toRemove.data()));
|
|
|
|
}
|
2018-04-18 00:15:31 +02:00
|
|
|
}
|
2019-06-10 22:48:26 +02:00
|
|
|
|
|
|
|
} catch (const filesystem::filesystem_error &error) {
|
|
|
|
CPPUNIT_FAIL(argsToString("Unable to clean config dir ", configDir, ": ", error.what()));
|
2018-04-18 00:15:31 +02:00
|
|
|
}
|
|
|
|
return configDir;
|
2018-04-17 23:54:43 +02:00
|
|
|
}
|
2018-04-14 23:03:04 +02:00
|
|
|
|
2018-04-17 23:54:43 +02:00
|
|
|
/*!
|
|
|
|
* \brief Tests behavior in initial state, when Syncthing isn't supposed to be running.
|
|
|
|
*/
|
|
|
|
void InterfaceTests::testInitialState()
|
|
|
|
{
|
|
|
|
CPPUNIT_ASSERT_MESSAGE("initially not running", !isSyncthingRunning());
|
2018-04-14 23:03:04 +02:00
|
|
|
|
2019-07-17 17:53:36 +02:00
|
|
|
// stopping Syncthing when not running should not cause any trouble
|
2018-04-17 23:54:43 +02:00
|
|
|
stopSyncthing();
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2019-07-18 16:38:19 +02:00
|
|
|
* \brief Tests whether the version() functions at least return something.
|
|
|
|
*/
|
|
|
|
void InterfaceTests::testVersion()
|
|
|
|
{
|
|
|
|
const auto version(syncthingVersion());
|
|
|
|
const auto longVersion(longSyncthingVersion());
|
|
|
|
cout << "\nversion: " << version;
|
|
|
|
cout << "\nlong version: " << longVersion << endl;
|
|
|
|
CPPUNIT_ASSERT(!version.empty());
|
|
|
|
CPPUNIT_ASSERT(!longVersion.empty());
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2019-10-13 19:37:32 +02:00
|
|
|
* \brief Test helper for running Syncthing and checking log (according the the test configuration).
|
2018-04-17 23:54:43 +02:00
|
|
|
*/
|
2019-10-13 19:37:32 +02:00
|
|
|
void InterfaceTests::testRun(const std::function<long long()> &runFunction, bool assertTestConfig)
|
2018-04-17 23:54:43 +02:00
|
|
|
{
|
2018-04-15 20:19:14 +02:00
|
|
|
// keep track of certain log messages
|
2018-04-14 23:03:04 +02:00
|
|
|
const auto startTime(DateTime::gmtNow());
|
|
|
|
bool myIdAnnounced = false, performanceAnnounced = false;
|
|
|
|
bool testDir1Ready = false, testDir2Ready = false;
|
|
|
|
bool testDev1Ready = false, testDev2Ready = false;
|
|
|
|
bool shuttingDown = false, shutDownLogged = false;
|
|
|
|
|
|
|
|
setLoggingCallback([&](LogLevel logLevel, const char *message, std::size_t messageSize) {
|
|
|
|
// ignore debug/verbose messages
|
|
|
|
if (logLevel < LogLevel::Info) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-15 20:19:14 +02:00
|
|
|
CPPUNIT_ASSERT_MESSAGE("Syncthing should be running right now", isSyncthingRunning());
|
|
|
|
|
2018-04-14 23:03:04 +02:00
|
|
|
// check whether the usual log messages appear
|
|
|
|
const string msg(message, messageSize);
|
|
|
|
if (startsWith(msg, "My ID: ")) {
|
|
|
|
myIdAnnounced = true;
|
|
|
|
} else if (startsWith(msg, "Single thread SHA256 performance is")) {
|
|
|
|
performanceAnnounced = true;
|
2019-07-18 16:38:19 +02:00
|
|
|
} else if (msg == "Ready to synchronize test1 (sendreceive)") {
|
2018-04-14 23:03:04 +02:00
|
|
|
testDir1Ready = true;
|
2019-07-18 16:38:19 +02:00
|
|
|
} else if (msg == "Ready to synchronize test2 (sendreceive)") {
|
2018-04-14 23:03:04 +02:00
|
|
|
testDir2Ready = true;
|
|
|
|
} else if (msg == "Device 6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4 is \"Test dev 1\" at [dynamic]") {
|
|
|
|
testDev1Ready = true;
|
2018-10-14 23:31:19 +02:00
|
|
|
} else if (msg == "Device MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7 is \"Test dev 2\" at [tcp://192.168.2.2:22001]") {
|
2018-04-14 23:03:04 +02:00
|
|
|
testDev2Ready = true;
|
2019-07-18 16:38:19 +02:00
|
|
|
} else if (msg == "Exiting") {
|
2018-04-14 23:03:04 +02:00
|
|
|
shutDownLogged = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// print the message on cout (which results in duplicated messages, but allows to check whether we've got everything)
|
2019-07-18 16:38:19 +02:00
|
|
|
cout << "logging callback (" << static_cast<std::underlying_type<LogLevel>::type>(logLevel) << "): ";
|
2018-04-14 23:03:04 +02:00
|
|
|
cout.write(message, static_cast<std::streamsize>(messageSize));
|
|
|
|
cout << endl;
|
|
|
|
|
|
|
|
// stop Syncthing again if the found the messages we've been looking for or we've timed out
|
|
|
|
const auto timeout((DateTime::gmtNow() - startTime) > TimeSpan::fromSeconds(30));
|
2019-10-13 19:37:32 +02:00
|
|
|
if (!timeout && (!myIdAnnounced || !performanceAnnounced || (assertTestConfig && (!testDir1Ready || !testDev1Ready || !testDev2Ready)))) {
|
2019-07-18 16:38:19 +02:00
|
|
|
// log status
|
2021-07-03 19:29:49 +02:00
|
|
|
cout << "still waiting for:";
|
2019-07-18 16:38:19 +02:00
|
|
|
if (!myIdAnnounced) {
|
|
|
|
cout << " myIdAnnounced";
|
|
|
|
}
|
|
|
|
if (!performanceAnnounced) {
|
|
|
|
cout << " performanceAnnounced";
|
|
|
|
}
|
2019-10-13 19:37:32 +02:00
|
|
|
if (assertTestConfig) {
|
|
|
|
if (!testDir1Ready) {
|
|
|
|
cout << " testDir1Ready";
|
|
|
|
}
|
|
|
|
if (!testDir2Ready) {
|
|
|
|
cout << " testDir2Ready";
|
|
|
|
}
|
|
|
|
if (!testDev1Ready) {
|
|
|
|
cout << " testDev1Ready";
|
|
|
|
}
|
|
|
|
if (!testDev2Ready) {
|
|
|
|
cout << " testDev2Ready";
|
|
|
|
}
|
2019-07-18 16:38:19 +02:00
|
|
|
}
|
|
|
|
cout << endl;
|
2018-04-14 23:03:04 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!shuttingDown) {
|
|
|
|
cerr << "stopping Syncthing again" << endl;
|
|
|
|
shuttingDown = true;
|
2019-07-18 16:38:19 +02:00
|
|
|
std::thread stopThread(stopSyncthing);
|
|
|
|
stopThread.detach();
|
2018-04-14 23:03:04 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-04-17 23:54:43 +02:00
|
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("Syncthing exited without error", 0ll, runFunction());
|
2018-04-14 23:03:04 +02:00
|
|
|
|
2018-04-15 20:19:14 +02:00
|
|
|
// assert whether all expected log messages were present
|
2018-04-14 23:03:04 +02:00
|
|
|
CPPUNIT_ASSERT(myIdAnnounced);
|
|
|
|
CPPUNIT_ASSERT(performanceAnnounced);
|
2019-10-13 19:37:32 +02:00
|
|
|
if (assertTestConfig) {
|
|
|
|
CPPUNIT_ASSERT(testDir1Ready);
|
|
|
|
CPPUNIT_ASSERT(!testDir2Ready);
|
|
|
|
CPPUNIT_ASSERT(testDev1Ready);
|
|
|
|
CPPUNIT_ASSERT(testDev2Ready);
|
|
|
|
}
|
2018-04-14 23:03:04 +02:00
|
|
|
CPPUNIT_ASSERT(shutDownLogged);
|
2018-04-17 23:50:45 +02:00
|
|
|
|
2019-10-13 19:37:32 +02:00
|
|
|
// check for random crashes afterwards
|
|
|
|
if (assertTestConfig) {
|
|
|
|
cerr << "\nkeep running a bit longer to check whether the application would not crash in the next few seconds"
|
|
|
|
"\n(could happen if Syncthing's extra threads haven't been stopped correctly)";
|
2023-02-11 15:55:45 +01:00
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(5));
|
2019-10-13 19:37:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests whether Syncthing can be started (and stopped again) when the config directory has not been created yet.
|
|
|
|
* \remarks It is expected that Syncthing creates the config directory automatically with a default config and new certs.
|
|
|
|
*/
|
|
|
|
void InterfaceTests::testRunWithoutConfig()
|
|
|
|
{
|
|
|
|
RuntimeOptions options;
|
|
|
|
options.configDir = TestApplication::instance()->workingDirectory() + "/does/not/exist";
|
2020-05-05 19:01:05 +02:00
|
|
|
options.dataDir = TestApplication::instance()->workingDirectory() + "/does/also/not/exist";
|
2019-10-13 19:37:32 +02:00
|
|
|
filesystem::remove_all(TestApplication::instance()->workingDirectory() + "/does");
|
|
|
|
testRun(bind(static_cast<std::int64_t (*)(const RuntimeOptions &)>(&runSyncthing), cref(options)), false);
|
2018-04-17 23:50:45 +02:00
|
|
|
}
|
2018-04-17 23:54:43 +02:00
|
|
|
|
2019-07-18 16:38:19 +02:00
|
|
|
/*!
|
2019-10-13 19:37:32 +02:00
|
|
|
* \brief Tests whether Syncthing can be started (and stopped again).
|
|
|
|
* \remarks This test uses the usual test config (same as for connector and CLI) and runs some checks against it.
|
2019-07-18 16:38:19 +02:00
|
|
|
*/
|
2021-10-13 00:14:49 +02:00
|
|
|
void InterfaceTests::testRunWithConfig()
|
2018-04-17 23:54:43 +02:00
|
|
|
{
|
|
|
|
RuntimeOptions options;
|
2020-05-05 19:01:05 +02:00
|
|
|
options.configDir = options.dataDir = setupTestConfigDir();
|
2019-10-13 19:37:32 +02:00
|
|
|
testRun(bind(static_cast<std::int64_t (*)(const RuntimeOptions &)>(&runSyncthing), cref(options)), true);
|
2018-04-14 23:03:04 +02:00
|
|
|
}
|
2022-06-26 02:25:23 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests running Syncthing's CLI.
|
|
|
|
*/
|
2023-05-29 17:29:41 +02:00
|
|
|
void InterfaceTests::testRunCli()
|
2022-06-26 02:25:23 +02:00
|
|
|
{
|
2022-07-05 12:26:17 +02:00
|
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("run arbitrary CLI command", 0ll, runCli({ "config", "version", "--help" }));
|
2022-06-26 02:25:23 +02:00
|
|
|
}
|
2023-05-29 17:29:41 +02:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \brief Tests running Syncthing command.
|
|
|
|
*/
|
|
|
|
void InterfaceTests::testRunCommand()
|
|
|
|
{
|
|
|
|
CPPUNIT_ASSERT_EQUAL_MESSAGE("run arbitrary CLI command", 0ll, runCommand({ "--help" }));
|
|
|
|
}
|