mimic++ v9.2.1
Loading...
Searching...
No Matches
ObjectWatcher.hpp
Go to the documentation of this file.
1// Copyright Dominic (DNKpp) Koepke 2024 - 2025.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at
4// https://www.boost.org/LICENSE_1_0.txt)
5
6#ifndef MIMICPP_OBJECT_WATCHER_HPP
7#define MIMICPP_OBJECT_WATCHER_HPP
8
9#pragma once
10
11#include "mimic++/Fwd.hpp"
12#include "mimic++/Mock.hpp"
15
16#ifndef MIMICPP_DETAIL_IS_MODULE
17 #include <algorithm>
18 #include <concepts>
19 #include <cstddef>
20 #include <memory>
21 #include <stdexcept>
22 #include <type_traits>
23 #include <utility>
24#endif
25
26namespace mimicpp::detail
27{
28 template <typename Base>
29 [[nodiscard]]
30 static StringT generate_lifetime_watcher_mock_name()
31 {
32 StringStreamT out{};
33 out << "LifetimeWatcher for " << print_type<Base>();
34 return std::move(out).str();
35 }
36
37 template <typename Base>
38 [[nodiscard]]
39 static StringT generate_relocation_watcher_mock_name()
40 {
41 StringStreamT out{};
42 out << "RelocationWatcher for " << print_type<Base>();
43 return std::move(out).str();
44 }
45}
46
48{
57
58 template <typename Base>
60 {
61 using type = Base;
62 };
63
64 template <typename Base>
66
97 {
98 // omits the following entries:
99 // - ~LifetimeWatcher
100 // - ~CombinedWatchers
101 // - ~BasicWatched
102 // - ~Watched
103 static constexpr std::size_t stacktraceSkip = 4u;
104
105 public:
111 ~LifetimeWatcher() noexcept(false)
112 {
113 if (const std::unique_ptr destruction = std::exchange(
114 m_DestructionMock,
115 nullptr))
116 {
117 (*destruction)();
118 }
119 }
120
124 [[nodiscard]]
125 LifetimeWatcher() = default;
126
127 template <typename Base>
128 [[nodiscard]] explicit LifetimeWatcher([[maybe_unused]] const for_base_tag<Base>)
131 .name = detail::generate_lifetime_watcher_mock_name<Base>(),
132 .stacktraceSkip = stacktraceSkip}
133 }
134 {
135 }
136
147 [[nodiscard]]
149 : LifetimeWatcher{other.m_MockSettings}
150 {
151 }
152
169 {
170 // let's make this a two-step.
171 // First destroy the previous instance, which may already report a violation.
172 // If we would already have the new instance created, this would lead also to
173 // a violation report, which actually might break everything.
174 {
175 LifetimeWatcher temp{std::move(*this)};
176 }
177
178 *this = LifetimeWatcher{other.m_MockSettings};
179
180 return *this;
181 }
182
188 [[nodiscard]]
190
197
206 [[nodiscard]]
208 {
209 if (std::exchange(m_HasDestructExpectation, true))
210 {
211 throw std::logic_error{
212 "LifetimeWatcher: A destruct expectation can not be created more than once for a single instance."};
213 }
214
215 return m_DestructionMock->expect_call()
216 and expect::once(); // prevent further times specifications
217 }
218
219 private:
220 bool m_HasDestructExpectation{};
221 MockSettings m_MockSettings{};
222 std::unique_ptr<Mock<void()>> m_DestructionMock{
223 std::make_unique<Mock<void()>>(m_MockSettings)};
224
225 [[nodiscard]]
226 explicit LifetimeWatcher(MockSettings settings)
227 : m_MockSettings{std::move(settings)}
228 {
229 }
230 };
231
263 {
264 // omits the following entries:
265 // - RelocationWatcher::handle_move
266 // - RelocationWatcher move ctor/assignment
267 // - CombinedWatchers move ctor/assignment
268 // - BasicWatched move ctor/assignment
269 // - Watched move ctor/assignment
270 static constexpr std::size_t stacktraceSkip = 5u;
271
272 public:
277
281 [[nodiscard]]
282 RelocationWatcher() = default;
283
284 template <typename Base>
285 [[nodiscard]] explicit RelocationWatcher([[maybe_unused]] const for_base_tag<Base>)
288 .name = detail::generate_relocation_watcher_mock_name<Base>(),
289 .stacktraceSkip = stacktraceSkip}
290 }
291 {
292 }
293
301 [[nodiscard]]
303 : RelocationWatcher{other.m_MockSettings}
304 {
305 }
306
315 {
316 m_MockSettings = other.m_MockSettings;
317
318 // explicitly circumvent default construct and assign, because otherwise that would
319 // involve the move-assignment.
320 m_RelocationMock = Mock<void()>{m_MockSettings};
321
322 return *this;
323 }
324
330 [[nodiscard]]
331 RelocationWatcher(RelocationWatcher&& other) noexcept(false)
332 {
333 handle_move(std::move(other));
334 }
335
342 {
343 handle_move(std::move(other));
344
345 return *this;
346 }
347
355 [[nodiscard]]
357 {
358 return m_RelocationMock.expect_call();
359 }
360
361 private:
362 MockSettings m_MockSettings{};
363 Mock<void()> m_RelocationMock{m_MockSettings};
364
365 [[nodiscard]]
366 explicit RelocationWatcher(MockSettings settings)
367 : m_MockSettings{std::move(settings)}
368 {
369 }
370
371 void handle_move(RelocationWatcher&& other)
372 {
373 other.m_RelocationMock();
374
375 std::ranges::swap(m_MockSettings, other.m_MockSettings);
376 // do not swap here, because we want the target mock to be destroyed NOW
377 m_RelocationMock = std::move(other).m_RelocationMock;
378 }
379 };
380
381 template <typename T, typename Base>
382 concept object_watcher_for = std::is_constructible_v<T, for_base_tag<Base>>
383 && std::is_copy_constructible_v<T>
384 && std::is_copy_assignable_v<T>
385 && std::is_move_constructible_v<T>
386 && std::is_move_assignable_v<T>
387 && std::is_destructible_v<T>;
388}
389
390namespace mimicpp::detail
391{
392 template <typename Base, typename... Watchers>
393 class CombinedWatchers
394 : public Watchers...
395 {
396 public:
397 ~CombinedWatchers() noexcept(std::is_nothrow_destructible_v<Base>) = default;
398
399 CombinedWatchers()
400 : Watchers{for_base_v<Base>}...
401 {
402 }
403
404 CombinedWatchers(const CombinedWatchers&) = default;
405 CombinedWatchers& operator=(const CombinedWatchers&) = default;
406
407 CombinedWatchers(CombinedWatchers&& other) noexcept(std::is_nothrow_move_constructible_v<Base>) = default;
408 CombinedWatchers& operator=(CombinedWatchers&& other) noexcept(std::is_nothrow_move_assignable_v<Base>) = default;
409 };
410
411 template <typename Base, typename... Watchers>
412 class BasicWatched
413 : public CombinedWatchers<Base, Watchers...>,
414 public Base
415 {
416 public:
417 ~BasicWatched() = default;
418
419 using Base::Base;
420
421 BasicWatched(const BasicWatched&) = default;
422 BasicWatched& operator=(const BasicWatched&) = default;
423 BasicWatched(BasicWatched&&) = default;
424 BasicWatched& operator=(BasicWatched&&) = default;
425 };
426
427 template <util::satisfies<std::has_virtual_destructor> Base, typename... Watchers>
428 class BasicWatched<Base, Watchers...>
429 : public CombinedWatchers<Base, Watchers...>,
430 public Base
431 {
432 public:
433 ~BasicWatched() override = default;
434
435 using Base::Base;
436
437 BasicWatched(const BasicWatched&) = default;
438 BasicWatched& operator=(const BasicWatched&) = default;
439 BasicWatched(BasicWatched&&) = default;
440 BasicWatched& operator=(BasicWatched&&) = default;
441 };
442}
443
445{
499 template <typename Base, object_watcher_for<Base>... Watchers>
500 requires std::same_as<Base, std::remove_cvref_t<Base>>
502 : public detail::BasicWatched<Base, Watchers...>
503 {
504 using SuperT = detail::BasicWatched<Base, Watchers...>;
505
506 public:
507 ~Watched() = default;
508
509 using SuperT::SuperT;
510
511 Watched(const Watched&) = default;
512 Watched& operator=(const Watched&) = default;
513 Watched(Watched&&) = default;
514 Watched& operator=(Watched&&) = default;
515 };
516
520}
521
522#endif
#define MIMICPP_DETAIL_MODULE_EXPORT
Definition Config.hpp:19
A Mock type, which fully supports overload sets.
Definition Mock.hpp:463
Definition Mock.hpp:35
Watched(const Watched &)=default
Watched & operator=(const Watched &)=default
~Watched()=default
Watched(Watched &&)=default
Watched & operator=(Watched &&)=default
Definition ObjectWatcher.hpp:382
consteval auto once() noexcept
Specifies a times policy with both limits set to 1.
Definition ControlPolicies.hpp:355
LifetimeWatcher()=default
Default constructor.
auto expect_relocate()
Begins a relocation-expectation construction.
Definition ObjectWatcher.hpp:356
RelocationWatcher(RelocationWatcher &&other) noexcept(false)
Move-constructor, which reports a relocation.
Definition ObjectWatcher.hpp:331
~LifetimeWatcher() noexcept(false)
Destructor, which reports the call.
Definition ObjectWatcher.hpp:111
~RelocationWatcher()=default
Defaulted destructor.
RelocationWatcher(const for_base_tag< Base >)
Definition ObjectWatcher.hpp:285
RelocationWatcher(const RelocationWatcher &other)
Copy-constructor.
Definition ObjectWatcher.hpp:302
RelocationWatcher()=default
Defaulted default constructor.
LifetimeWatcher & operator=(LifetimeWatcher &&)=default
Defaulted move-assignment-operator.
LifetimeWatcher(const LifetimeWatcher &other)
Copy-constructor.
Definition ObjectWatcher.hpp:148
RelocationWatcher & operator=(const RelocationWatcher &other)
Copy-assignment-operator.
Definition ObjectWatcher.hpp:314
LifetimeWatcher & operator=(const LifetimeWatcher &other)
Copy-assignment-operator.
Definition ObjectWatcher.hpp:168
RelocationWatcher & operator=(RelocationWatcher &&other) noexcept(false)
Move-assignment-operator, which reports a relocation.
Definition ObjectWatcher.hpp:341
LifetimeWatcher(LifetimeWatcher &&)=default
Defaulted move-constructor.
LifetimeWatcher(const for_base_tag< Base >)
Definition ObjectWatcher.hpp:128
auto expect_destruct()
Begins a destruction-expectation construction.
Definition ObjectWatcher.hpp:207
Base type
Definition ObjectWatcher.hpp:61
constexpr for_base_tag< Base > for_base_v
Definition ObjectWatcher.hpp:65
constexpr printing::PrintTypeFn< T > print_type
Functional object, converting the given type to its textual representation.
Definition PrintType.hpp:478
Definition Call.hpp:24
std::basic_ostringstream< CharT, CharTraitsT > StringStreamT
Definition Format.hpp:35
std::basic_string< CharT, CharTraitsT > StringT
Definition Fwd.hpp:391
Definition ObjectWatcher.hpp:60