morph
0.1.0
Asynchronous bridge between GUI thread and business models
Toggle main menu visibility
Loading...
Searching...
No Matches
owner_probe_recorder.hpp
Go to the documentation of this file.
1
// SPDX-License-Identifier: Apache-2.0
2
3
#pragma once
4
#include <algorithm>
5
#include <atomic>
6
#include <core/async/ExecutorContext.hpp>
7
#include <core/async/IExecutor.hpp>
8
#include <cstddef>
9
#include <mutex>
10
#include <string>
11
#include <string_view>
12
#include <thread>
13
#include <vector>
14
15
#include "
../core/detail/owner_probe.hpp
"
16
17
/// @file
18
/// @brief `morph::testing::OwnerProbeRecorder`: observes the owner checks a test's code makes, instead of
19
/// letting a debug build assert on them.
20
21
namespace
morph::testing {
22
23
/// @brief Records every owner check a loop-owned body makes while this object is
24
/// alive (`morph::exec::detail::noteOwner`), and what the calling thread's
25
/// executor scope said at that moment.
26
///
27
/// What a posted-call test reads: a verb called from a thread that is not the
28
/// loop, whose body then shows up here with `onOwner == true`, was posted. A
29
/// body run inline on the caller's thread shows up with `onOwner == false`.
30
/// The scope is read here, by the test, not taken from the component.
31
class
OwnerProbeRecorder
{
32
public
:
33
/// @brief One owner check.
34
struct
Seen
{
35
/// @brief The site the check named.
36
std::string
site
;
37
/// @brief Whether the checking thread was inside a task of the owner the check named.
38
bool
onOwner
{
false
};
39
/// @brief Whether the check named the owner this recorder expects.
40
bool
expectedOwner
{
false
};
41
};
42
43
/// @brief Installs this recorder as the process's owner probe.
44
/// @param expected The owner every check is expected to name: the
45
/// `IoLoop`'s loop.
46
explicit
OwnerProbeRecorder
(::core::async::IExecutor
const
& expected) : _expected{&expected} {
47
current().store(
this
);
48
::morph::exec::detail::ownerProbe().store(&OwnerProbeRecorder::record);
49
}
50
51
/// @brief Uninstalls the probe, then waits for every thread still inside `record()`.
52
~OwnerProbeRecorder
() {
53
::morph::exec::detail::ownerProbe().store(
nullptr
);
54
current().store(
nullptr
);
55
// A thread that read `current()` before it was cleared may still be
56
// inside `record()`, about to lock this object's mutex. Its members
57
// must outlive it.
58
while
(inRecord().load() != 0) {
59
std::this_thread::yield();
60
}
61
}
62
63
OwnerProbeRecorder
(
const
OwnerProbeRecorder
&) =
delete
;
64
OwnerProbeRecorder
& operator=(
const
OwnerProbeRecorder
&) =
delete
;
65
OwnerProbeRecorder
(
OwnerProbeRecorder
&&) =
delete
;
66
OwnerProbeRecorder
& operator=(
OwnerProbeRecorder
&&) =
delete
;
67
68
/// @brief The checks made at one site.
69
/// @param site The site name.
70
/// @return Every check made at @p site so far.
71
[[nodiscard]] std::vector<Seen>
at
(std::string_view site)
const
{
72
std::scoped_lock
const
lock{_mtx};
73
std::vector<Seen> matching;
74
for
(
auto
const
& seen : _seen) {
75
if
(seen.site == site) {
76
matching.push_back(seen);
77
}
78
}
79
return
matching;
80
}
81
82
/// @brief How many checks were made at one site.
83
/// @param site The site name.
84
/// @return How many checks were made at @p site.
85
[[nodiscard]] std::size_t
count
(std::string_view site)
const
{
return
at
(site).size(); }
86
87
/// @brief Whether every check at one site ran posted to the expected owner.
88
/// @param site The site name.
89
/// @return Whether at least one check was made at @p site, and every one
90
/// of them ran inside a task of the expected owner.
91
[[nodiscard]]
bool
allPosted
(std::string_view site)
const
{
92
auto
const
seen =
at
(site);
93
if
(seen.empty()) {
94
return
false
;
95
}
96
return
std::ranges::all_of(seen, [](
auto
const
& one) {
return
one.onOwner && one.expectedOwner; });
97
}
98
99
private
:
100
static
std::atomic<OwnerProbeRecorder*>& current() {
101
static
std::atomic<OwnerProbeRecorder*> recorder{
nullptr
};
102
return
recorder;
103
}
104
105
/// How many threads are inside `record()`. Counted before `current()` is
106
/// read, so the destructor, which clears `current()` and then waits for
107
/// this to reach zero, cannot miss a caller that has seen the pointer.
108
static
std::atomic<std::size_t>& inRecord() {
109
static
std::atomic<std::size_t>
count
{0};
110
return
count
;
111
}
112
113
static
void
record(
char
const
* site, ::core::async::IExecutor
const
& owner)
noexcept
{
114
struct
InRecord {
115
InRecord() { inRecord().fetch_add(1); }
116
~InRecord() { inRecord().fetch_sub(1); }
117
InRecord(
const
InRecord&) =
delete
;
118
InRecord& operator=(
const
InRecord&) =
delete
;
119
InRecord(InRecord&&) =
delete
;
120
InRecord& operator=(InRecord&&) =
delete
;
121
}
const
counted;
122
OwnerProbeRecorder
*
const
self
= current().load();
123
if
(self ==
nullptr
) {
124
return
;
125
}
126
bool
const
onOwner = ::core::async::ExecutorScope::anyInForce(
127
[&owner](::core::async::ExecutorScope
const
& scope) {
return
&scope.executor() == &owner; });
128
try
{
129
std::scoped_lock
const
lock{
self
->_mtx};
130
self
->_seen.push_back(
Seen
{.site = site, .onOwner = onOwner, .expectedOwner = &owner ==
self
->_expected});
131
}
catch
(...) {
// NOLINT(bugprone-empty-catch)
132
// An allocation failure loses one record; the test then sees too
133
// few, never a false pass.
134
}
135
}
136
137
::core::async::IExecutor
const
* _expected;
138
mutable
std::mutex _mtx;
139
std::vector<Seen> _seen;
140
};
141
142
}
// namespace morph::testing
morph::ui::author::self
constexpr Self self
The dialog or drawer a command list runs in.
Definition
author.hpp:1295
morph::testing::OwnerProbeRecorder
Records every owner check a loop-owned body makes while this object is alive (morph::exec::detail::no...
Definition
owner_probe_recorder.hpp:31
morph::testing::OwnerProbeRecorder::at
std::vector< Seen > at(std::string_view site) const
The checks made at one site.
Definition
owner_probe_recorder.hpp:71
morph::testing::OwnerProbeRecorder::allPosted
bool allPosted(std::string_view site) const
Whether every check at one site ran posted to the expected owner.
Definition
owner_probe_recorder.hpp:91
morph::testing::OwnerProbeRecorder::OwnerProbeRecorder
OwnerProbeRecorder(::core::async::IExecutor const &expected)
Installs this recorder as the process's owner probe.
Definition
owner_probe_recorder.hpp:46
morph::testing::OwnerProbeRecorder::count
std::size_t count(std::string_view site) const
How many checks were made at one site.
Definition
owner_probe_recorder.hpp:85
morph::testing::OwnerProbeRecorder::~OwnerProbeRecorder
~OwnerProbeRecorder()
Uninstalls the probe, then waits for every thread still inside record().
Definition
owner_probe_recorder.hpp:52
owner_probe.hpp
The check every loop-owned body makes on entry: "am I on my owner?".
morph::testing::OwnerProbeRecorder::Seen
One owner check.
Definition
owner_probe_recorder.hpp:34
morph::testing::OwnerProbeRecorder::Seen::site
std::string site
The site the check named.
Definition
owner_probe_recorder.hpp:36
morph::testing::OwnerProbeRecorder::Seen::expectedOwner
bool expectedOwner
Whether the check named the owner this recorder expects.
Definition
owner_probe_recorder.hpp:40
morph::testing::OwnerProbeRecorder::Seen::onOwner
bool onOwner
Whether the checking thread was inside a task of the owner the check named.
Definition
owner_probe_recorder.hpp:38
morph
testing
owner_probe_recorder.hpp
Generated by
1.17.0