summaryrefslogtreecommitdiffstats
path: root/test/UnitLoadTorture.cpp
blob: 284cd84aa2e57c46dd33d3e89ea8d52a4b57b013 (plain)
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
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

#include <config.h>

#include "Protocol.hpp"

#include <memory>
#include <string>

#include <Poco/URI.h>
#include <test/lokassert.hpp>

#include <Unit.hpp>
#include <Util.hpp>
#include <helpers.hpp>
#include <net/WebSocketSession.hpp>

class COOLWebSocket;

/// Load torture testcase.
class UnitLoadTorture : public UnitWSD
{
    int loadTorture(const std::string& testname, const std::string& docName,
                    const size_t thread_count, const size_t max_jitter_ms);
    TestResult testLoadTortureODT();
    TestResult testLoadTortureODS();
    TestResult testLoadTortureODP();
    TestResult testLoadTorture();

public:
    UnitLoadTorture();
    void invokeWSDTest() override;
};

int UnitLoadTorture::loadTorture(const std::string& testname, const std::string& docName,
                                 const size_t thread_count, const size_t max_jitter_ms)
{
    // Load same document from many threads together.
    std::string documentPath, documentURL;
    helpers::getDocumentPathAndURL(docName, documentPath, documentURL, testname);

    TST_LOG("Starting test on " << documentURL << ' ' << documentPath);

    std::atomic<int> sum_view_ids;
    sum_view_ids = 0;
    std::atomic<int> num_of_views(0);
    std::atomic<int> num_to_load(thread_count);
    std::shared_ptr<SocketPoll> poll = std::make_shared<SocketPoll>("WebSocketPoll");
    poll->startThread();

    std::vector<std::thread> threads;
    for (size_t i = 0; i < thread_count; ++i)
    {
        threads.emplace_back([&] {
            std::ostringstream oss;
            oss << std::hex << std::this_thread::get_id();
            const std::string id = oss.str();

            TST_LOG(": #" << id << ", views: " << num_of_views << ", to load: " << num_to_load);
            try
            {
                // Load a document and wait for the status.
                auto wsSession = http::WebSocketSession::create(helpers::getTestServerURI());
                http::Request req(documentURL);
                wsSession->asyncRequest(req, poll);

                wsSession->sendMessage("load url=" + documentURL);

                // 20s is double of the default.
                std::vector<char> message
                    = wsSession->waitForMessage("status:", std::chrono::seconds(20), testname + id + ' ');
                const std::string status = COOLProtocol::getFirstLine(message);

                int viewid = -1;
                COOLProtocol::getTokenIntegerFromMessage(status, "viewid", viewid);
                sum_view_ids += viewid;
                ++num_of_views;
                --num_to_load;

                TST_LOG(": #" << id << ", loaded views: " << num_of_views
                              << ", to load: " << num_to_load);

                while (true)
                {
                    if (num_to_load == 0)
                    {
                        // Unload at once, nothing more left to do.
                        TST_LOG(": #" << id << ", no more to load, unloading.");
                        break;
                    }

                    const auto ms
                        = (max_jitter_ms > 0
                               ? std::chrono::milliseconds(Util::rng::getNext() % max_jitter_ms)
                               : std::chrono::milliseconds(0));
                    std::this_thread::sleep_for(ms);

                    // Unload only when we aren't the last/only.
                    if (--num_of_views > 0)
                    {
                        TST_LOG(": #" << id << ", views: " << num_of_views
                                      << " not the last/only, unloading.");
                        break;
                    }
                    else
                    {
                        // Correct back, since we aren't unloading just yet.
                        ++num_of_views;
                    }
                }
            }
            catch (const std::exception& exc)
            {
                TST_LOG(": #" << id << ", Exception: " << exc.what());
                --num_to_load;
            }
        });
    }

    for (auto& thread : threads)
    {
        try
        {
            thread.join();
        }
        catch (const std::exception& exc)
        {
            TST_LOG(": Exception: " << exc.what());
        }
    }

    return sum_view_ids;
}

UnitBase::TestResult UnitLoadTorture::testLoadTortureODT()
{
    const int thread_count = 6;
    const int max_jitter_ms = 100;

    const char* testname = "loadTortureODT ";
    const int sum_view_ids = loadTorture(testname, "empty.odt", thread_count, max_jitter_ms);

    // This only works when the first view-ID is 0 and increments monotonously.
    const int number_of_loads = thread_count;
    const int exp_sum_view_ids = number_of_loads * (number_of_loads - 1) / 2; // 0-based view-ids.
    LOK_ASSERT_EQUAL(exp_sum_view_ids, sum_view_ids);
    return TestResult::Ok;
}

UnitBase::TestResult UnitLoadTorture::testLoadTortureODS()
{
    const int thread_count = 6;
    const int max_jitter_ms = 100;

    const char* testname = "loadTortureODS ";
    const int sum_view_ids = loadTorture(testname, "empty.ods", thread_count, max_jitter_ms);

    // This only works when the first view-ID is 0 and increments monotonously.
    const int number_of_loads = thread_count;
    const int exp_sum_view_ids = number_of_loads * (number_of_loads - 1) / 2; // 0-based view-ids.
    LOK_ASSERT_EQUAL(exp_sum_view_ids, sum_view_ids);
    return TestResult::Ok;
}

UnitBase::TestResult UnitLoadTorture::testLoadTortureODP()
{
    const int thread_count = 6;
    const int max_jitter_ms = 100;

    const char* testname = "loadTortureODP ";
    const int sum_view_ids = loadTorture(testname, "empty.odp", thread_count, max_jitter_ms);

    // For ODP the view-id is always odd, and we expect not to skip any ids.
    const int number_of_loads = thread_count;
    const int exp_sum_view_ids = number_of_loads * (number_of_loads - 1) / 2; // 0-based view-ids.
    LOK_ASSERT_EQUAL(exp_sum_view_ids, sum_view_ids);
    return TestResult::Ok;
}

UnitBase::TestResult UnitLoadTorture::testLoadTorture()
{
    const int thread_count = 3;
    const int max_jitter_ms = 75;

    std::vector<std::string> docNames = { "setclientpart.ods", "hello.odt", "viewcursor.odp" };

    std::vector<std::thread> threads;
    threads.reserve(docNames.size());
    for (const auto& docName : docNames)
    {
        threads.emplace_back([&] {
            const auto testname = "loadTorture_" + docName + ' ';
            loadTorture(testname, docName, thread_count, max_jitter_ms);
        });
    }

    for (auto& thread : threads)
    {
        thread.join();
    }
    return TestResult::Ok;
}

UnitLoadTorture::UnitLoadTorture()
{
    // Double of the default.
    constexpr std::chrono::minutes timeout_minutes(1);
    setTimeout(timeout_minutes);
}

void UnitLoadTorture::invokeWSDTest()
{
    UnitBase::TestResult result = testLoadTortureODT();
    if (result != TestResult::Ok)
        exitTest(result);

    result = testLoadTortureODS();
    if (result != TestResult::Ok)
        exitTest(result);

    result = testLoadTortureODP();
    if (result != TestResult::Ok)
        exitTest(result);

    result = testLoadTorture();
    exitTest(result);
}

UnitBase* unit_create_wsd(void) { return new UnitLoadTorture(); }

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */