flexisip/tester/utils/asserts.hh
2025-06-16 13:54:12 +07:00

175 lines
6.3 KiB
C++

/*
Flexisip, a flexible SIP proxy server with media capabilities.
Copyright (C) 2010-2023 Belledonne Communications SARL, All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <chrono>
#include <functional>
#include <string>
#include <thread>
#include <cstdint>
#include <sys/types.h>
#include "bctoolbox/tester.h"
#include "linphone++/linphone.hh"
#include "utils/test-patterns/test.hh"
namespace flexisip::tester {
struct AssertionResult {
const char* const file;
const int line;
std::string reason;
// Asserts that the assertion passed. Logs the error otherwise.
bool assert_passed() const {
return bc_assert(file, line, operator bool(), reason.c_str());
}
void hard_assert_passed() const {
bc_hard_assert(file, line, operator bool(), reason.c_str());
}
operator bool() const { // Assertion is true if and only if there is no failure reason
return reason.empty();
}
AssertionResult(const char* const file, const int line, const char* const reason)
: file(file), line(line), reason(reason == nullptr ? "" : reason) {
}
AssertionResult(const bool b) // Convert from bool for seemless integration with existing code
: file(__FILE__), line(__LINE__),
reason(b ? "" : "Context Missing. Please rewrite your test to use AssertionResult instead of bool.") {
}
};
#define ASSERTION_FAILED(reason) AssertionResult(__FILE__, __LINE__, "ASSERTION_FAILED(" reason ")")
#define ASSERTION_PASSED() AssertionResult(__FILE__, __LINE__, nullptr)
#define ASSERTION_CONTINUE() AssertionResult(false)
#define FAIL_IF(assertion) \
if (assertion) return AssertionResult(__FILE__, __LINE__, "FAIL_IF(" #assertion ")")
#define LOOP_ASSERTION(assertion) \
AssertionResult(__FILE__, __LINE__, (assertion) ? nullptr : "LOOP_ASSERTION(" #assertion ")")
#define ASSERT_PASSED(assertionResult) \
bc_assert(__FILE__, __LINE__, assertionResult.assert_passed(), "ASSERT_PASSED(" #assertionResult ")")
constexpr auto kDefaultSleepInterval = std::chrono::nanoseconds(std::chrono::milliseconds(10));
constexpr auto kNoSleep = std::chrono::nanoseconds(0);
template <const std::chrono::nanoseconds& sleepBetweenIterations = kDefaultSleepInterval>
class BcAssert {
public:
BcAssert() = default;
BcAssert(const std::initializer_list<std::function<void()>>& mIterateFuncs) : mIterateFuncs(mIterateFuncs) {
}
void addCustomIterate(const std::function<void()>& iterate) {
mIterateFuncs.push_back(iterate);
}
template <typename Func>
[[nodiscard]] AssertionResult waitUntil(const std::chrono::duration<double> timeout, Func&& condition) {
const auto timeLimit = std::chrono::steady_clock::now() + timeout;
return loopAssert([&timeLimit] { return timeLimit < std::chrono::steady_clock::now(); },
std::forward<Func>(condition));
}
template <typename Func>
[[nodiscard]] AssertionResult wait(Func condition) {
return waitUntil(std::chrono::seconds(2), condition);
}
template <typename Func>
[[nodiscard]] AssertionResult iterateUpTo(const uint32_t iterations,
Func condition,
std::chrono::milliseconds minTime = std::chrono::milliseconds{1}) {
auto remaining = iterations + 1;
auto beforePlusMinTime = std::chrono::system_clock::now() + minTime;
return loopAssert(
[&remaining, beforePlusMinTime] {
if (remaining != 0) --remaining;
return remaining == 0 && beforePlusMinTime < std::chrono::system_clock::now();
},
std::forward<Func>(condition));
}
template <typename AssertFunc, typename StopFunc>
[[nodiscard]] AssertionResult loopAssert(StopFunc stopCondition, AssertFunc assertion) {
const auto before = std::chrono::system_clock::now();
for (uint32_t iterations = 0;; ++iterations) {
iterateAllOnce();
AssertionResult result = assertion();
if (result) return result;
if (stopCondition()) {
result.reason += "\n -> Still failing after " + std::to_string(iterations) + " iterations and " +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - before)
.count()) +
"ms";
return result;
}
if constexpr (0 < sleepBetweenIterations.count()) {
std::this_thread::sleep_for(sleepBetweenIterations);
}
}
}
/**
* Iterate for at least "iterations" number of iterations and at least "minTime" milliseconds.
* Then test the provided condition and return the result.
*/
template <typename Func>
[[nodiscard]] AssertionResult
forceIterateThenAssert(const uint32_t minIterations, std::chrono::milliseconds minTime, Func condition) {
const auto before = std::chrono::system_clock::now();
uint32_t iterations = 0;
for (; (iterations < minIterations) || (before + minTime > std::chrono::system_clock::now()); ++iterations) {
iterateAllOnce();
}
AssertionResult result = condition();
if (!result) {
result.reason += "\n -> Failed after " + std::to_string(iterations) + " iterations and " +
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now() - before)
.count()) +
"ms";
}
return result;
}
private:
void iterateAllOnce() {
for (const auto& iterate : mIterateFuncs) {
iterate();
}
}
std::vector<std::function<void()>> mIterateFuncs;
};
} // namespace flexisip::tester