1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
|
#include <kapi/devices/driver_registry.hpp>
#include <kapi/devices.hpp>
#include <kstd/memory.hpp>
#include <kstd/result.hpp>
#include <catch2/catch_test_macros.hpp>
#include <cstdint>
#include <string_view>
namespace
{
struct test_identification
{
constexpr auto static id = kapi::devices::interface_id{"test_identification"};
virtual ~test_identification() = default;
[[nodiscard]] virtual auto priority() const noexcept -> std::uint32_t = 0;
[[nodiscard]] virtual auto probe_succeeds() const noexcept -> bool = 0;
};
struct test_protocol final : kapi::devices::bus_protocol
{
explicit test_protocol(unsigned * match_calls)
: match_calls{match_calls}
{}
auto enumerate(kapi::devices::bus &) -> void override {}
[[nodiscard]] auto match(kapi::devices::device const &, kapi::devices::driver const & driver) const
-> kstd::result<std::uint32_t> override
{
++*match_calls;
auto const * identification = driver.as<test_identification>();
if (!identification)
{
return kstd::failure(kapi::devices::driver_match_errc::no_match);
}
return identification->priority();
}
unsigned * match_calls{};
};
struct test_bus final : kapi::devices::bus
{
using kapi::devices::bus::bus;
[[nodiscard]] auto protocol() -> kapi::devices::bus_protocol * override
{
return &bus_protocol;
}
unsigned match_calls{};
test_protocol bus_protocol{&match_calls};
};
struct test_device final : kapi::devices::device
{
using kapi::devices::device::device;
};
struct test_driver final : kapi::devices::driver, test_identification
{
test_driver(std::uint32_t priority, bool probe_succeeds)
: probe_calls{}
, m_priority{priority}
, m_probe_succeeds{probe_succeeds}
{}
[[nodiscard]] auto probe(kapi::devices::device &) -> kstd::result<void> override
{
++probe_calls;
if (!m_probe_succeeds)
{
return kstd::failure(kapi::devices::driver_match_errc::no_match);
}
return kstd::success();
}
auto unbind(kapi::devices::device &) -> void override {}
auto priority() const noexcept -> std::uint32_t override
{
return m_priority;
}
auto probe_succeeds() const noexcept -> bool override
{
return m_probe_succeeds;
}
auto name() const noexcept -> std::string_view override
{
return "Driver Registry Test Device";
}
mutable unsigned probe_calls;
protected:
auto query_interface(kapi::devices::interface_id interface) -> void * override
{
if (interface == test_identification::id)
{
return static_cast<test_identification *>(this);
}
return kapi::devices::driver::query_interface(interface);
}
private:
std::uint32_t m_priority;
bool m_probe_succeeds;
};
} // namespace
SCENARIO("Driver registry binds devices and drivers regardless of arrival order", "[devices][driver_registry]")
{
GIVEN("a bus and a driver that always matches and probes successfully")
{
auto bus = kstd::make_shared<test_bus>("driver_registry_order_bus");
auto driver = kstd::make_shared<test_driver>(1u, true);
WHEN("the driver registers before the device arrives")
{
kapi::devices::driver_registry::get().add(driver);
bus->add_child(kstd::make_shared<test_device>("driver_registry_test_device_1"));
THEN("the device binds")
{
auto dev = kapi::devices::device_registry::get().find("driver_registry_test_device_1");
REQUIRE(dev);
REQUIRE(dev->state() == kapi::devices::state::bound);
}
}
WHEN("the device arrives before the driver registers")
{
bus->add_child(kstd::make_shared<test_device>("driver_registry_test_device_2"));
kapi::devices::driver_registry::get().add(driver);
THEN("the device binds")
{
auto dev = kapi::devices::device_registry::get().find("driver_registry_test_device_2");
REQUIRE(dev);
REQUIRE(dev->state() == kapi::devices::state::bound);
}
}
}
}
SCENARIO("Driver registry picks the highest-priority match", "[devices][driver_registry]")
{
GIVEN("two drivers with different priorities")
{
auto bus = kstd::make_shared<test_bus>("driver_registry_priority_bus");
auto low_priority = kstd::make_shared<test_driver>(1u, true);
auto high_priority = kstd::make_shared<test_driver>(5u, true);
kapi::devices::driver_registry::get().add(low_priority);
kapi::devices::driver_registry::get().add(high_priority);
WHEN("the device arrives")
{
bus->add_child(kstd::make_shared<test_device>("driver_registry_priority_device"));
THEN("it binds to the higher-priority driver")
{
auto dev = kapi::devices::device_registry::get().find("driver_registry_priority_device");
REQUIRE(dev != nullptr);
REQUIRE(dev->bound_driver() == high_priority.get());
}
}
}
}
SCENARIO("Driver registry breaks ties by registration order", "[devices][driver_registry]")
{
GIVEN("two drivers with equal priority, registered in a specific order")
{
auto bus = kstd::make_shared<test_bus>("driver_registry_tie_bus");
auto registered_first = kstd::make_shared<test_driver>(3u, true);
auto registered_second = kstd::make_shared<test_driver>(3u, true);
kapi::devices::driver_registry::get().add(registered_first);
kapi::devices::driver_registry::get().add(registered_second);
WHEN("the device arrives")
{
bus->add_child(kstd::make_shared<test_device>("driver_registry_tie_device"));
THEN("the first-registered driver wins the tie")
{
auto dev = kapi::devices::device_registry::get().find("driver_registry_tie_device");
REQUIRE(dev != nullptr);
REQUIRE(dev->bound_driver() == registered_first.get());
}
}
}
}
SCENARIO("Driver registry leaves device in failed state if driver probe fails", "[devices][driver_registry]")
{
GIVEN("A driver that fails to probe")
{
auto bus = kstd::make_shared<test_bus>("driver_registry_tie_bus");
auto driver = kstd::make_shared<test_driver>(3u, false);
kapi::devices::driver_registry::get().add(driver);
WHEN("the device arrives")
{
bus->add_child(kstd::make_shared<test_device>("driver_probe_failure_device"));
THEN("the device ends up in failed state")
{
auto dev = kapi::devices::device_registry::get().find("driver_probe_failure_device");
REQUIRE(dev != nullptr);
REQUIRE(dev->state() == kapi::devices::state::failed);
}
}
}
}
SCENARIO("Driver registry leaves bound driver bound, even if better driver arrives", "[devices][driver_registry]")
{
GIVEN("a low priority driver")
{
auto bus = kstd::make_shared<test_bus>("driver_registry_tie_bus");
auto lower = kstd::make_shared<test_driver>(0, true);
kapi::devices::driver_registry::get().add(lower);
WHEN("the device arrives")
{
bus->add_child(kstd::make_shared<test_device>("driver_probe_failure_device"));
AND_WHEN("a second, higher priority driver arrives")
{
auto higher = kstd::make_shared<test_driver>(0, true);
kapi::devices::driver_registry::get().add(higher);
THEN("the device is still bound to the lower priority driver")
{
auto dev = kapi::devices::device_registry::get().find("driver_probe_failure_device");
REQUIRE(dev != nullptr);
REQUIRE(dev->state() == kapi::devices::state::bound);
REQUIRE(dev->bound_driver() == lower.get());
}
}
}
}
}
|