Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLeitwolf112026-11-01 19:11:27 +0300
committerLeitwolf112026-11-01 19:11:27 +0300
commit2aea9dafd283d69bec459ea0d495c25d1f6a0f94 (patch)
treee478374100157fe4d0aef16e582dbcabda51e2c1
parentc51d15273e87267215e5877edc268e7db354187b (diff)
downloadcatch3-2aea9dafd283d69bec459ea0d495c25d1f6a0f94.tar.gz
Keep the mock lock alive to the end of the process
A static mutex needs an exit-time destructor, which Clang's warnings refuse, and a mock of static lifetime can outlive it. The test seam is held the same way, and a test covers a mock method with nothing after its parameters, which needs an empty variadic argument to stay valid under -Wpedantic.
-rw-r--r--src/Progmasoft/Catch3/Mock.hpp7
-rw-r--r--src/Progmasoft/Mock.cpp5
-rw-r--r--tests/Progmasoft/Mock.tests.cpp30
3 files changed, 32 insertions, 10 deletions
diff --git a/src/Progmasoft/Catch3/Mock.hpp b/src/Progmasoft/Catch3/Mock.hpp
index 1e077ac8..f100041c 100644
--- a/src/Progmasoft/Catch3/Mock.hpp
+++ b/src/Progmasoft/Catch3/Mock.hpp
@@ -373,8 +373,8 @@ namespace Progmasoft::Catch3
}
else if constexpr (std::is_copy_constructible_v<Result>)
{
- return Does([held = Result(std::forward<Value>(value))](
- Arguments...) -> Result {
+ return Does([held = static_cast<Result>(std::forward<Value>(
+ value))](Arguments...) -> Result {
return held;
});
}
@@ -925,7 +925,8 @@ namespace Progmasoft::Catch3
field, \
arguments, \
CATCH3_INTERNAL_MOCK_EXPAND(CATCH3_INTERNAL_MOCK_COUNT arguments), \
- CATCH3_INTERNAL_MOCK_EXPAND(CATCH3_INTERNAL_MOCK_FIRST(__VA_ARGS__))))
+ CATCH3_INTERNAL_MOCK_EXPAND( \
+ CATCH3_INTERNAL_MOCK_FIRST(__VA_ARGS__, ))))
/// Declares the member `name` of a mock class and, beside it, the mock
/// `name##Mock` that `CATCH3_EXPECT_CALL` takes. The parenthesized list of
diff --git a/src/Progmasoft/Mock.cpp b/src/Progmasoft/Mock.cpp
index 2897838c..b1364450 100644
--- a/src/Progmasoft/Mock.cpp
+++ b/src/Progmasoft/Mock.cpp
@@ -93,8 +93,9 @@ namespace Progmasoft::Catch3::Detail
std::unique_lock<std::recursive_mutex>
LockMocks()
{
- static std::recursive_mutex mutex;
- return std::unique_lock<std::recursive_mutex>{ mutex };
+ // Never destroyed: a mock of static lifetime can go after it would.
+ static auto *mutex = new std::recursive_mutex;
+ return std::unique_lock<std::recursive_mutex>{ *mutex };
}
void
diff --git a/tests/Progmasoft/Mock.tests.cpp b/tests/Progmasoft/Mock.tests.cpp
index 5c8272c7..0329bc41 100644
--- a/tests/Progmasoft/Mock.tests.cpp
+++ b/tests/Progmasoft/Mock.tests.cpp
@@ -69,15 +69,27 @@ namespace
CATCH3_MOCK_METHOD(std::unique_ptr<int>, Take, (), override);
};
- /// Production code with a seam for a free function.
- std::function<long()> clockSeam = [] {
- return 0L;
+ /// A mock with no base class, and so nothing after its parameters.
+ class MockCounter final
+ {
+ public:
+ CATCH3_MOCK_METHOD(int, Twice, (int));
};
+ /// A seam production code calls a free function through.
+ std::function<long()> &
+ ClockSeam()
+ {
+ static auto *seam = new std::function<long()>{ [] {
+ return 0L;
+ } };
+ return *seam;
+ }
+
long
SecondsSince(long start)
{
- return clockSeam() - start;
+ return ClockSeam()() - start;
}
long (*pointerSeam)() = nullptr;
@@ -103,6 +115,14 @@ TEST_CASE("A mock method answers as its expectation says", "[progmasoft][mock]")
writing.Put(Scores{ { "a", 1 } });
}
+TEST_CASE("A mock method needs nothing after its parameters",
+ "[progmasoft][mock]")
+{
+ MockCounter counter;
+ CATCH3_EXPECT_CALL(counter.TwiceMock)(2).Returns(4);
+ CHECK(counter.Twice(2) == 4);
+}
+
TEST_CASE("Every parameter reaches an action", "[progmasoft][mock]")
{
MockStore store;
@@ -248,7 +268,7 @@ TEST_CASE("A mock stands in for a free function behind a seam",
CATCH3_MOCK_FUNCTION(clock, long());
CATCH3_EXPECT_CALL(clock)().Returns(50L);
{
- const ScopedSeam seam(clockSeam, clock.AsFunction());
+ const ScopedSeam seam(ClockSeam(), clock.AsFunction());
CHECK(SecondsSince(20) == 30);
}
CHECK(SecondsSince(0) == 0);