File: interfacetests.cpp

package info (click to toggle)
syncthingtray 1.7.5-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 6,804 kB
  • sloc: cpp: 31,085; xml: 1,694; java: 570; sh: 81; javascript: 53; makefile: 25
file content (284 lines) | stat: -rw-r--r-- 9,929 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#include "../interface.h"

#include <c++utilities/chrono/datetime.h>
#include <c++utilities/chrono/timespan.h>
#include <c++utilities/conversion/stringbuilder.h>
#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>

#include <chrono>
#include <cstdlib>
#include <filesystem>
#include <functional>
#include <thread>

#ifdef PLATFORM_WINDOWS
#include <windows.h>
#endif

using namespace std;
using namespace CppUtilities;
using namespace LibSyncthing;

using namespace CPPUNIT_NS;

/*!
 * \brief The InterfaceTests class tests the C++ interface of "libsyncthing".
 */
class InterfaceTests : public TestFixture {
    CPPUNIT_TEST_SUITE(InterfaceTests);
    CPPUNIT_TEST(testInitialState);
    CPPUNIT_TEST(testVersion);
    CPPUNIT_TEST(testRunWithoutConfig);
    CPPUNIT_TEST(testRunWithConfig);
    CPPUNIT_TEST(testRunCli);
    CPPUNIT_TEST(testRunCommand);
    CPPUNIT_TEST_SUITE_END();

public:
    InterfaceTests();

    void testInitialState();
    void testVersion();
    void testRunWithoutConfig();
    void testRunWithConfig();
    void testRunCli();
    void testRunCommand();

    void setUp() override;
    void tearDown() override;

private:
    std::string setupTestConfigDir();
    void testRun(const std::function<long long(void)> &runFunction, bool assertTestConfig);
};

CPPUNIT_TEST_SUITE_REGISTRATION(InterfaceTests);

InterfaceTests::InterfaceTests()
{
#ifdef PLATFORM_WINDOWS
    SetEnvironmentVariableW(L"STNOUPGRADE", L"1");
#else
    setenv("STNOUPGRADE", "1", 1);
#endif
}

void InterfaceTests::setUp()
{
}

void InterfaceTests::tearDown()
{
}

/*!
 * \brief Initializes the Syncthing config for this fixture (currently using same config as in connector test).
 * \returns Returns the config directory.
 */
string InterfaceTests::setupTestConfigDir()
{
    // setup Syncthing config (currently using same config as in connector test)
    const auto configFilePath(workingCopyPath("testconfig/config.xml"));
    if (configFilePath.empty()) {
        throw runtime_error("Unable to setup Syncthing config directory.");
    }

    // clean database
    const auto configDir(directory(configFilePath));
    try {
        const auto dirIterator = filesystem::directory_iterator(configDir);
        for (const auto &dir : dirIterator) {
            const auto dirPath = dir.path();
            if (!dir.is_directory() || dirPath == "." || dirPath == "..") {
                continue;
            }
            const auto subdirIterator = filesystem::directory_iterator(dirPath);
            for (const auto &file : subdirIterator) {
                if (file.is_directory()) {
                    continue;
                }
                const auto toRemove = file.path().string();
                CPPUNIT_ASSERT_EQUAL_MESSAGE("removing " + toRemove, 0, remove(toRemove.data()));
            }
        }

    } catch (const filesystem::filesystem_error &error) {
        CPPUNIT_FAIL(argsToString("Unable to clean config dir ", configDir, ": ", error.what()));
    }
    return configDir;
}

/*!
 * \brief Tests behavior in initial state, when Syncthing isn't supposed to be running.
 */
void InterfaceTests::testInitialState()
{
    CPPUNIT_ASSERT_MESSAGE("initially not running", !isSyncthingRunning());

    // stopping Syncthing when not running should not cause any trouble
    stopSyncthing();
}

/*!
 * \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());
}

/*!
 * \brief Test helper for running Syncthing and checking log (according the the test configuration).
 */
void InterfaceTests::testRun(const std::function<long long()> &runFunction, bool assertTestConfig)
{
    // keep track of certain log messages
    const auto startTime(DateTime::gmtNow());
    bool myIdAnnounced = false, performanceAnnounced = false, guiAnnounced = 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;
        }

        CPPUNIT_ASSERT_MESSAGE("Syncthing should be running right now", isSyncthingRunning());

        // 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") || startsWith(msg, "Hashing performance is")) {
            performanceAnnounced = true;
        } else if (startsWith(msg, "GUI and API listening on")) {
            guiAnnounced = true;
        } else if (msg == "Ready to synchronize test1 (sendreceive)") {
            testDir1Ready = true;
        } else if (msg == "Ready to synchronize test2 (sendreceive)") {
            testDir2Ready = true;
        } else if (msg == "Device 6EIS2PN-J2IHWGS-AXS3YUL-HC5FT3K-77ZXTLL-AKQLJ4C-7SWVPUS-AZW4RQ4 is \"Test dev 1\" at [dynamic]") {
            testDev1Ready = true;
        } else if (msg == "Device MMGUI6U-WUEZQCP-XZZ6VYB-LCT4TVC-ER2HAVX-QYT6X7D-S6ZSG2B-323KLQ7 is \"Test dev 2\" at [tcp://192.168.2.2:22001]") {
            testDev2Ready = true;
        } else if (msg == "Exiting") {
            shutDownLogged = true;
        }

        // print the message on cout (which results in duplicated messages, but allows to check whether we've got everything)
        cout << "logging callback (" << static_cast<std::underlying_type<LogLevel>::type>(logLevel) << "): ";
        cout.write(message, static_cast<std::streamsize>(messageSize));
        cout << endl;

        // stop Syncthing again if the messages we've been looking for were logged or we've timed out
        const auto timeout((DateTime::gmtNow() - startTime) > TimeSpan::fromSeconds(30));
        if (!timeout
            && (!myIdAnnounced || !performanceAnnounced || !guiAnnounced
                || (assertTestConfig && (!testDir1Ready || !testDev1Ready || !testDev2Ready)))) {
            // log status
            cout << "still waiting for:";
            if (!myIdAnnounced) {
                cout << " myIdAnnounced";
            }
            if (!performanceAnnounced) {
                cout << " performanceAnnounced";
            }
            if (!guiAnnounced) {
                cout << " guiAnnounced";
            }
            if (assertTestConfig) {
                if (!testDir1Ready) {
                    cout << " testDir1Ready";
                }
                if (!testDir2Ready) {
                    cout << " testDir2Ready";
                }
                if (!testDev1Ready) {
                    cout << " testDev1Ready";
                }
                if (!testDev2Ready) {
                    cout << " testDev2Ready";
                }
            }
            cout << endl;
            return;
        }
        if (!shuttingDown) {
            cerr << "stopping Syncthing again" << endl;
            shuttingDown = true;
            std::thread(stopSyncthing).detach();
        }
    });

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Syncthing exited without error", 0ll, runFunction());

    // assert whether all expected log messages were present
    CPPUNIT_ASSERT(myIdAnnounced);
    CPPUNIT_ASSERT(performanceAnnounced);
    if (assertTestConfig) {
        CPPUNIT_ASSERT(testDir1Ready);
        CPPUNIT_ASSERT(!testDir2Ready);
        CPPUNIT_ASSERT(testDev1Ready);
        CPPUNIT_ASSERT(testDev2Ready);
    }
    CPPUNIT_ASSERT(shutDownLogged);

    // 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)";
        std::this_thread::sleep_for(std::chrono::seconds(5));
    }
}

/*!
 * \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";
    options.dataDir = TestApplication::instance()->workingDirectory() + "/does/also/not/exist";
    filesystem::remove_all(TestApplication::instance()->workingDirectory() + "/does");
    testRun(bind(static_cast<std::int64_t (*)(const RuntimeOptions &)>(&runSyncthing), cref(options)), false);
}

/*!
 * \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.
 */
void InterfaceTests::testRunWithConfig()
{
    RuntimeOptions options;
    options.configDir = options.dataDir = setupTestConfigDir();
    testRun(bind(static_cast<std::int64_t (*)(const RuntimeOptions &)>(&runSyncthing), cref(options)), true);
}

/*!
 * \brief Tests running Syncthing's CLI.
 */
void InterfaceTests::testRunCli()
{
    CPPUNIT_ASSERT_EQUAL_MESSAGE("run arbitrary CLI command", 0ll, runCli({ "config", "version", "--help" }));
}

/*!
 * \brief Tests running Syncthing command.
 */
void InterfaceTests::testRunCommand()
{
    CPPUNIT_ASSERT_EQUAL_MESSAGE("run arbitrary CLI command", 0ll, runCommand({ "--help" }));
}