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";
}
}
|