mirror of
https://github.com/XRPLF/clio.git
synced 2025-12-06 17:27:58 +00:00
@@ -128,7 +128,18 @@ TEST_F(AnyOperationTests, RepeatingOpRequestStopCallPropagated)
|
||||
repeatingOp.abort();
|
||||
}
|
||||
|
||||
TEST_F(AnyOperationTests, RepeatingOpInvokeCallPropagated)
|
||||
{
|
||||
EXPECT_CALL(mockRepeatingOp, invoke());
|
||||
repeatingOp.invoke();
|
||||
}
|
||||
|
||||
TEST_F(AnyOperationDeathTest, CallAbortOnNonStoppableOrCancellableOperation)
|
||||
{
|
||||
EXPECT_DEATH(voidOp.abort(), ".*");
|
||||
}
|
||||
|
||||
TEST_F(AnyOperationDeathTest, CallInvokeOnNonForceInvocableOperation)
|
||||
{
|
||||
EXPECT_DEATH(voidOp.invoke(), ".*");
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ struct AnyStrandTests : ::testing::Test {
|
||||
template <typename T>
|
||||
using StoppableOperationType = ::testing::NiceMock<MockStoppableOperation<T>>;
|
||||
|
||||
template <typename T>
|
||||
using RepeatingOperationType = NiceMock<MockRepeatingOperation<T>>;
|
||||
|
||||
::testing::NaggyMock<MockStrand> mockStrand;
|
||||
AnyStrand strand{static_cast<MockStrand&>(mockStrand)};
|
||||
};
|
||||
@@ -146,3 +149,15 @@ TEST_F(AnyStrandTests, ExecuteWithTimoutAndStopTokenAndReturnValueThrowsExceptio
|
||||
[[maybe_unused]] auto unused = strand.execute([](auto) { return 42; }, std::chrono::milliseconds{1})
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(AnyStrandTests, RepeatingOperation)
|
||||
{
|
||||
auto mockRepeatingOp = RepeatingOperationType<std::any>{};
|
||||
EXPECT_CALL(mockRepeatingOp, wait());
|
||||
EXPECT_CALL(mockStrand, executeRepeatedly(std::chrono::milliseconds{1}, A<std::function<std::any()>>()))
|
||||
.WillOnce([&mockRepeatingOp] -> RepeatingOperationType<std::any> const& { return mockRepeatingOp; });
|
||||
|
||||
auto res = strand.executeRepeatedly(std::chrono::milliseconds{1}, [] -> void { throw 0; });
|
||||
static_assert(std::is_same_v<decltype(res), AnyOperation<void>>);
|
||||
res.wait();
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <ranges>
|
||||
#include <semaphore>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@@ -220,6 +222,24 @@ TYPED_TEST(ExecutionContextTests, repeatingOperation)
|
||||
EXPECT_LE(callCount, expectedActualCount); // never should be called more times than possible before timeout
|
||||
}
|
||||
|
||||
TYPED_TEST(ExecutionContextTests, repeatingOperationForceInvoke)
|
||||
{
|
||||
std::atomic_size_t callCount = 64uz;
|
||||
std::binary_semaphore unblock(0);
|
||||
|
||||
auto res = this->ctx.executeRepeatedly(std::chrono::seconds{10}, [&] {
|
||||
if (--callCount == 0uz)
|
||||
unblock.release();
|
||||
});
|
||||
for ([[maybe_unused]] auto unused : std::views::iota(0uz, callCount.load()))
|
||||
res.invoke();
|
||||
|
||||
unblock.acquire();
|
||||
res.abort();
|
||||
|
||||
EXPECT_EQ(callCount, 0uz);
|
||||
}
|
||||
|
||||
TYPED_TEST(ExecutionContextTests, strandMove)
|
||||
{
|
||||
auto strand = this->ctx.makeStrand();
|
||||
@@ -273,6 +293,43 @@ TYPED_TEST(ExecutionContextTests, strandWithTimeout)
|
||||
EXPECT_EQ(res.get().value(), 42);
|
||||
}
|
||||
|
||||
TYPED_TEST(ExecutionContextTests, strandedRepeatingOperation)
|
||||
{
|
||||
auto strand = this->ctx.makeStrand();
|
||||
auto const repeatDelay = std::chrono::milliseconds{1};
|
||||
auto const timeout = std::chrono::milliseconds{15};
|
||||
auto callCount = 0uz;
|
||||
|
||||
auto res = strand.executeRepeatedly(repeatDelay, [&] { ++callCount; });
|
||||
auto timeSpent = util::timed([timeout] { std::this_thread::sleep_for(timeout); }); // calculate actual time spent
|
||||
|
||||
res.abort(); // outside of the above stopwatch because it blocks and can take arbitrary time
|
||||
auto const expectedPureCalls = timeout.count() / repeatDelay.count();
|
||||
auto const expectedActualCount = timeSpent / repeatDelay.count();
|
||||
|
||||
EXPECT_GE(callCount, expectedPureCalls / 2u); // expect at least half of the scheduled calls
|
||||
EXPECT_LE(callCount, expectedActualCount); // never should be called more times than possible before timeout
|
||||
}
|
||||
|
||||
TYPED_TEST(ExecutionContextTests, strandedRepeatingOperationForceInvoke)
|
||||
{
|
||||
auto strand = this->ctx.makeStrand();
|
||||
auto callCount = 64uz; // does not need to be atomic since we are on a strand
|
||||
std::binary_semaphore unblock(0);
|
||||
|
||||
auto res = strand.executeRepeatedly(std::chrono::seconds{10}, [&] {
|
||||
if (--callCount == 0uz)
|
||||
unblock.release();
|
||||
});
|
||||
for ([[maybe_unused]] auto unused : std::views::iota(0uz, callCount))
|
||||
res.invoke();
|
||||
|
||||
unblock.acquire();
|
||||
res.abort();
|
||||
|
||||
EXPECT_EQ(callCount, 0uz);
|
||||
}
|
||||
|
||||
TYPED_TEST(AsyncExecutionContextTests, executeAutoAborts)
|
||||
{
|
||||
auto value = 0;
|
||||
|
||||
@@ -126,4 +126,3 @@ TEST(ArrayTest, addNullRequired)
|
||||
auto const error = arr.addNull();
|
||||
EXPECT_TRUE(error.has_value());
|
||||
}
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
@@ -334,7 +334,8 @@ TEST_F(ClioConfigDefinitionParseArrayTest, emptyArray)
|
||||
{
|
||||
auto const configJson = boost::json::parse(R"json({
|
||||
"array": []
|
||||
})json").as_object();
|
||||
})json")
|
||||
.as_object();
|
||||
|
||||
auto const result = config.parse(ConfigFileJson{configJson});
|
||||
EXPECT_FALSE(result.has_value());
|
||||
@@ -355,20 +356,23 @@ TEST_F(ClioConfigDefinitionParseArrayTest, fullArray)
|
||||
{"int": 1, "string": "one"},
|
||||
{"int": 2, "string": "two"}
|
||||
]
|
||||
})json").as_object();
|
||||
})json")
|
||||
.as_object();
|
||||
|
||||
auto const result = config.parse(ConfigFileJson{configJson});
|
||||
EXPECT_FALSE(result.has_value());
|
||||
EXPECT_EQ(config.arraySize("array.[]"), 2);
|
||||
}
|
||||
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, onlyRequiredFields) {
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, onlyRequiredFields)
|
||||
{
|
||||
auto const configJson = boost::json::parse(R"json({
|
||||
"array": [
|
||||
{"int": 1},
|
||||
{"int": 2}
|
||||
]
|
||||
})json").as_object();
|
||||
})json")
|
||||
.as_object();
|
||||
|
||||
auto const configFile = ConfigFileJson{configJson};
|
||||
auto const result = config.parse(configFile);
|
||||
@@ -388,7 +392,8 @@ TEST_F(ClioConfigDefinitionParseArrayTest, someOptionalFieldsMissing)
|
||||
{"int": 1, "string": "one"},
|
||||
{"int": 2}
|
||||
]
|
||||
})json").as_object();
|
||||
})json")
|
||||
.as_object();
|
||||
|
||||
auto const configFile = ConfigFileJson{configJson};
|
||||
auto const result = config.parse(configFile);
|
||||
@@ -401,13 +406,15 @@ TEST_F(ClioConfigDefinitionParseArrayTest, someOptionalFieldsMissing)
|
||||
EXPECT_FALSE(config.getArray("array.[].string").valueAt(1).hasValue());
|
||||
}
|
||||
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, optionalFieldMissingAtFirstPosition) {
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, optionalFieldMissingAtFirstPosition)
|
||||
{
|
||||
auto const configJson = boost::json::parse(R"json({
|
||||
"array": [
|
||||
{"int": 1},
|
||||
{"int": 2, "string": "two"}
|
||||
]
|
||||
})json").as_object();
|
||||
})json")
|
||||
.as_object();
|
||||
|
||||
auto const configFile = ConfigFileJson{configJson};
|
||||
auto const result = config.parse(configFile);
|
||||
@@ -421,13 +428,15 @@ TEST_F(ClioConfigDefinitionParseArrayTest, optionalFieldMissingAtFirstPosition)
|
||||
EXPECT_EQ(config.getArray("array.[].string").valueAt(1).asString(), "two");
|
||||
}
|
||||
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, missingRequiredFields) {
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, missingRequiredFields)
|
||||
{
|
||||
auto const configJson = boost::json::parse(R"json({
|
||||
"array": [
|
||||
{"int": 1},
|
||||
{"string": "two"}
|
||||
]
|
||||
})json").as_object();
|
||||
})json")
|
||||
.as_object();
|
||||
|
||||
auto const configFile = ConfigFileJson{configJson};
|
||||
auto const result = config.parse(configFile);
|
||||
@@ -436,13 +445,15 @@ TEST_F(ClioConfigDefinitionParseArrayTest, missingRequiredFields) {
|
||||
EXPECT_THAT(result->at(0).error, testing::StartsWith("array.[].int"));
|
||||
}
|
||||
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, missingAllRequiredFields) {
|
||||
TEST_F(ClioConfigDefinitionParseArrayTest, missingAllRequiredFields)
|
||||
{
|
||||
auto const configJson = boost::json::parse(R"json({
|
||||
"array": [
|
||||
{"string": "one"},
|
||||
{"string": "two"}
|
||||
]
|
||||
})json").as_object();
|
||||
})json")
|
||||
.as_object();
|
||||
|
||||
auto const configFile = ConfigFileJson{configJson};
|
||||
auto const result = config.parse(configFile);
|
||||
|
||||
@@ -420,7 +420,8 @@ TEST_F(ConfigFileJsonTest, getArrayObjectInArray)
|
||||
EXPECT_EQ(std::get<std::string>(strings.at(1).value()), "some string");
|
||||
}
|
||||
|
||||
TEST_F(ConfigFileJsonTest, getArrayOptionalInArray) {
|
||||
TEST_F(ConfigFileJsonTest, getArrayOptionalInArray)
|
||||
{
|
||||
auto const jsonStr = R"json({
|
||||
"array": [
|
||||
{ "int": 42 },
|
||||
|
||||
Reference in New Issue
Block a user