File: SdkTestTransferMaxSpeeds_test.cpp

package info (click to toggle)
megacmd 2.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 32,592 kB
  • sloc: cpp: 326,437; ansic: 34,524; python: 4,630; java: 3,965; sh: 2,869; objc: 2,459; makefile: 197; xml: 113
file content (276 lines) | stat: -rw-r--r-- 11,053 bytes parent folder | download | duplicates (2)
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
/**
 * @file
 * @brief This file defines some tests for validating changes in the max download/upload speed
 * limits for transfers
 */

#include "integration/integration_test_utils.h"
#include "integration/mock_listeners.h"
#include "sdk_test_utils.h"
#include "SdkTest_test.h"

#include <chrono>

using namespace sdk_test;

/**
 * @class TransferProgressReporter
 * @brief A helper functor to pass as callback to the expectations on onTransferUpdate.
 */
struct TransferProgressReporter
{
public:
    /**
     * @brief A factor used to validate received updates on the speed:
     * receivedSpeed <= MAX_PERMITTED_SPEED_FACTOR * targetMaxSpeed
     * @note Why 3? For the current state of the code a factor of 2 caused the tests to fail on
     * macos some times. With this value we pass the tests successfully and confirm that the bug is
     * not present any more.
     */
    static constexpr double MAX_PERMITTED_SPEED_FACTOR = 3.;

    /**
     * @brief The fraction of the given expectedTime to wait before starting to apply the
     * checks on the received speed updates. Useful to wait for some initial stabilization of the
     * values.
     */
    static constexpr double STABILIZATION_TIME_FRACTION = 0.2;

    TransferProgressReporter(const std::chrono::seconds expectedTime,
                             const unsigned targetMaxSpeed):
        mStartTime{std::chrono::steady_clock::now()},
        mExpectedTime{expectedTime},
        mTargetMaxSpeed{targetMaxSpeed}
    {}

    /**
     * @brief Validate expectations on the received transfer updates
     */
    void operator()(MegaApi*, MegaTransfer* transfer)
    {
        if (stabilizing())
            return;
        const auto speed = transfer->getSpeed();
        EXPECT_LE(speed, static_cast<unsigned>(MAX_PERMITTED_SPEED_FACTOR * mTargetMaxSpeed))
            << "Received a transfer update with a speed outside of the accepted range";
    }

private:
    std::chrono::steady_clock::time_point mStartTime;
    std::chrono::seconds mExpectedTime{0};
    unsigned mTargetMaxSpeed{0};

    bool stabilizing() const
    {
        const auto secondsSinceStart = std::chrono::steady_clock::now() - mStartTime;
        return secondsSinceStart < mExpectedTime * STABILIZATION_TIME_FRACTION;
    }
};

class SdkTestTransferMaxSpeeds: public SdkTest
{
public:
    static constexpr auto MAX_TIMEOUT = 3min; // Timeout for some operations in this tests suite

    void SetUp() override
    {
        SdkTest::SetUp();
        ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1));
    }

    void TearDown() override
    {
        megaApi[0]->setMaxUploadSpeed(-1);
        megaApi[0]->setMaxDownloadSpeed(-1);
        SdkTest::TearDown();
    }

    /**
     * @brief Performs an upload limiting the speed to the given value. Monitorices the progress
     * using TransferProgressReporter.
     *
     * @param expectedTime The amount of time in seconds that the transfer is expected to take if it
     * goes at max speed.
     * @param maxSpeed The max speed set for the transfer in bytes per second
     * @param filePath The path to the file that will be uploaded
     *
     * @return The total time taken for the transfer to complete in seconds if the upload succeeded
     */
    std::optional<std::chrono::seconds>
        performAndMonitorUpload(const std::chrono::seconds expectedTime,
                                const unsigned maxSpeed,
                                const fs::path& filePath) const
    {
        LOG_debug << getLogPrefix() << "Setting upload speed limit";
        if (const bool setLimitSucced = megaApi[0]->setMaxUploadSpeed(maxSpeed); !setLimitSucced)
        {
            EXPECT_TRUE(setLimitSucced) << "Error setting upload max speed";
            return {};
        }

        const auto starter = [&filePath, this](auto* transferListener)
        {
            const std::string fileName{getFilePrefix() + ".txt"};
            MegaUploadOptions uploadOptions;
            uploadOptions.fileName = fileName;
            uploadOptions.mtime = MegaApi::INVALID_CUSTOM_MOD_TIME;

            auto rootNode = std::unique_ptr<MegaNode>{megaApi[0]->getRootNode()};
            const auto localPath = filePath.string();
            megaApi[0]->startUpload(localPath,
                                    rootNode.get(),
                                    nullptr,
                                    &uploadOptions,
                                    transferListener);
        };
        return performAndMonitorTransferAux(expectedTime, maxSpeed, std::move(starter));
    }

    /**
     * @brief Performs a download limiting the speed to the given value. Monitorices the progress
     * using TransferProgressReporter.
     *
     * @param expectedTime The amount of time in seconds that the transfer is expected to take if it
     * goes at max speed.
     * @param maxSpeed The max speed set for the transfer in bytes per second
     * @param nodeToDownload The node that will be downloaded
     *
     * @return The total time taken for the transfer to complete in seconds if the upload succeeded
     */
    std::optional<std::chrono::seconds>
        performAndMonitorDownload(const std::chrono::seconds expectedTime,
                                  const unsigned maxSpeed,
                                  MegaNode* nodeToDownload) const
    {
        LOG_debug << getLogPrefix() << "Setting download speed limit";
        if (const bool setLimitSucced = megaApi[0]->setMaxDownloadSpeed(maxSpeed); !setLimitSucced)
        {
            EXPECT_TRUE(setLimitSucced) << "Error setting download max speed";
            return {};
        }
        const auto starter = [nodeToDownload, this](auto* transferListener)
        {
            megaApi[0]->startDownload(
                nodeToDownload,
                "./",
                nullptr /*customName*/,
                nullptr /*appData*/,
                false /*startFirst*/,
                nullptr /*cancelToken*/,
                MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/,
                MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */,
                false /* undelete */,
                transferListener);
        };
        return performAndMonitorTransferAux(expectedTime, maxSpeed, std::move(starter));
    }

private:
    /**
     * @brief Auxiliary method to handle both uploads and downloads
     */
    template<typename TransferStarter>
    std::optional<std::chrono::seconds>
        performAndMonitorTransferAux(const std::chrono::seconds expectedTime,
                                     const unsigned maxSpeed,
                                     TransferStarter&& transferStarter) const
    {
        const auto logPre{getLogPrefix()};
        LOG_debug << logPre << "Starting the transfer";
        testing::NiceMock<MockMegaTransferListener> mtl{megaApi[0].get()};
        std::chrono::steady_clock::time_point startTime;
        EXPECT_CALL(mtl, onTransferStart)
            .WillOnce(
                [&startTime]
                {
                    startTime = std::chrono::steady_clock::now();
                });
        std::chrono::steady_clock::time_point endTime;
        EXPECT_CALL(mtl, onTransferFinish)
            .WillOnce(
                [&mtl, &endTime]
                {
                    endTime = std::chrono::steady_clock::now();
                    mtl.markAsFinished();
                });
        EXPECT_CALL(mtl, onTransferUpdate)
            .WillRepeatedly(TransferProgressReporter(expectedTime, maxSpeed));

        transferStarter(&mtl);

        LOG_debug << logPre << "Waiting for the transfer to finish";
        if (const bool transferFinished = mtl.waitForFinishOrTimeout(MAX_TIMEOUT);
            !transferFinished)
        {
            EXPECT_TRUE(transferFinished)
                << "The transfer didn't finish successfully in the given time window";
            return {};
        }
        return std::chrono::duration_cast<chrono::seconds>(endTime - startTime);
    }
};

/**
 * @brief SdkTestTransferMaxSpeeds.MaxUploadSpeed
 *
 * Validates the MegaApi::setMaxUploadSpeed public method by:
 * - Uploading a file
 * - Track the received onTransferUpdate callbacks and check if the reported speed is reasonable
 *   for the given limit
 * - At the end, check if the upload took a reasonable amount of time
 *
 * @note This test sets a low limit, so it is almost guaranteed that the transfer is throttled.
 * However, as this might not be the case in jenkins, the strongest test conditions are validated on
 * the side were the speed limit is highly exceeded.
 */
TEST_F(SdkTestTransferMaxSpeeds, MaxUploadSpeed)
{
    constexpr auto MAX_SPEED_BYTES_PER_SECOND{10000u};
    constexpr auto EXPECTED_TIME_FOR_TRANSFER{40s};
    constexpr auto FILE_SIZE = MAX_SPEED_BYTES_PER_SECOND * EXPECTED_TIME_FOR_TRANSFER.count();

    LOG_debug << getLogPrefix() << "Create the file to be uploaded";
    const fs::path filePath{getFilePrefix() + ".txt"};
    LocalTempFile f(filePath, FILE_SIZE);

    const auto requiredTime =
        performAndMonitorUpload(EXPECTED_TIME_FOR_TRANSFER, MAX_SPEED_BYTES_PER_SECOND, filePath);
    ASSERT_TRUE(requiredTime) << "Something went wrong during the upload";
    EXPECT_GE(requiredTime->count(),
              EXPECTED_TIME_FOR_TRANSFER.count() /
                  TransferProgressReporter::MAX_PERMITTED_SPEED_FACTOR)
        << "The transfer took shorter than expected to complete";
}

/**
 * @brief SdkTestTransferMaxSpeeds.MaxDownloadSpeed
 *
 * Same as SdkTestTransferMaxSpeeds.MaxUploadSpeed but for downloads.
 * In this case we test for two different max limits. One below 100KB and other above. This is done
 * because that limit sets a different buffer size in libcurl.
 */
TEST_F(SdkTestTransferMaxSpeeds, MaxDownloadSpeed)
{
    constexpr auto EXPECTED_TIME_FOR_TRANSFER{40s};

    for (const auto maxSpeedBytesPerSecond: {10000u, 200000u})
    {
        const unsigned fileSize = maxSpeedBytesPerSecond * EXPECTED_TIME_FOR_TRANSFER.count();

        LOG_debug << getLogPrefix() << "Uploading file to be downloaded after. Size: " << fileSize;
        const std::string fileName{getFilePrefix() + std::to_string(maxSpeedBytesPerSecond) +
                                   ".txt"};
        const auto nodeToDownload =
            sdk_test::uploadFile(megaApi[0].get(), LocalTempFile{fileName, fileSize});
        ASSERT_TRUE(nodeToDownload);

        const auto requiredTime = performAndMonitorDownload(EXPECTED_TIME_FOR_TRANSFER,
                                                            maxSpeedBytesPerSecond,
                                                            nodeToDownload.get());
        ASSERT_TRUE(requiredTime) << "Something went wrong during the download";
        EXPECT_GE(requiredTime->count(),
                  EXPECTED_TIME_FOR_TRANSFER.count() /
                      TransferProgressReporter::MAX_PERMITTED_SPEED_FACTOR)
            << "The transfer took shorter than expected to complete";
    }
}