Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
async_if_test_helper.hxx
1// Helper classes for writing unittests testing the entire asynchronous
2// stack. Allows to send incoming messages (in gridconnect format) and set
3// expectations on messages produced.
4//
5// Only include this file in unittests.
6
7#ifndef _UTILS_ASYNC_IF_TEST_HELPER_HXX_
8#define _UTILS_ASYNC_IF_TEST_HELPER_HXX_
9
11#include "openlcb/IfCan.hxx"
14#include "openlcb/NodeInitializeFlow.hxx"
16#include "nmranet_config.h"
18#include "utils/test_main.hxx"
19
20using ::testing::AtLeast;
21using ::testing::AtMost;
22using ::testing::Eq;
23using ::testing::Field;
24using ::testing::Invoke;
25using ::testing::IsNull;
26using ::testing::Mock;
27using ::testing::NiceMock;
28using ::testing::NotNull;
29using ::testing::Pointee;
30using ::testing::Return;
31using ::testing::StrCaseEq;
32using ::testing::StrictMock;
33using ::testing::WithArg;
34using ::testing::SaveArg;
35using ::testing::_;
36
37class TrainTestHelper;
38
40// void (*g_invoke)(Notifiable *) = &InvokeNotification;
41
42HubFlow gc_hub0(&g_service);
43CanHubFlow can_hub0(&g_service);
44GCAdapterBase *g_gc_adapter = nullptr;
45
46HubFlow gc_hub1(&g_service);
47CanHubFlow can_hub1(&g_service);
48GCAdapterBase *g_gc_adapter1 = nullptr;
49
50openlcb::InitializeFlow g_init_flow(&g_service);
51
54class MockSend : public HubPort
55{
56public:
57 MockSend()
58 : HubPort(&g_service)
59 {
60 }
61
62 MOCK_METHOD1(mwrite, void(const string &s));
63
64 virtual Action entry()
65 {
66 string s(message()->data()->data(), message()->data()->size());
67 mwrite(s);
68 return release_and_exit();
69 }
70};
71
72void InvokeNotification(Notifiable *done)
73{
74 done->notify();
75}
76
77static void print_packet(const string &pkt)
78{
79 fprintf(stderr, "%s\n", pkt.c_str());
80}
81
86class AsyncCanTest : public testing::Test
87{
88public:
91 static void SetUpTestCase()
92 {
93 g_gc_adapter =
94 GCAdapterBase::CreateGridConnectAdapter(&gc_hub0, &can_hub0, false);
95 }
96
99 static void TearDownTestCase()
100 {
101 delete g_gc_adapter;
102 }
103
104protected:
106 {
107 gc_hub0.register_port(&canBus_);
108 }
109
111 {
112 gc_hub0.unregister_port(&canBus_);
113 if (printer_.get())
114 {
115 gc_hub0.unregister_port(printer_.get());
116 }
117 }
118
121 void wait()
122 {
124 }
125
128 void twait()
129 {
131 }
132
133#ifdef __EMSCRIPTEN__
134 void usleep(unsigned long usecs) {
135 long long deadline = usecs;
136 deadline *= 1000;
137 deadline += os_get_time_monotonic();
138 while (os_get_time_monotonic() < deadline) {
139 os_emscripten_yield();
140 }
141 }
142#endif
143
152#define expect_packet(gc_packet) \
153 EXPECT_CALL(canBus_, mwrite(StrCaseEq(gc_packet)))
154
170#define expect_packet_and_send_response(gc_packet, resp_packet) \
171 EXPECT_CALL(canBus_, mwrite(StrCaseEq(gc_packet))) \
172 .WillOnce(::testing::InvokeWithoutArgs( \
173 [this]() { send_packet(resp_packet); }))
174
181 {
182 if (!printer_) { print_all_packets(); }
183 EXPECT_CALL(canBus_, mwrite(_)).Times(AtLeast(0));
184 //.WillRepeatedly(WithArg<0>(Invoke(print_packet)));
185 }
186
191 {
192 HASSERT(!printer_ && "cannot have more than one print_all_packets call");
193 NiceMock<MockSend> *m = new NiceMock<MockSend>();
194 EXPECT_CALL(*m, mwrite(_)).Times(AtLeast(0)).WillRepeatedly(
195 WithArg<0>(Invoke(print_packet)));
196 gc_hub0.register_port(m);
197 printer_.reset(m);
198 }
199
209 void send_packet(const string &gc_packet)
210 {
211 Buffer<HubData> *packet;
212 mainBufferPool->alloc(&packet);
213 packet->data()->assign(gc_packet);
214 packet->data()->skipMember_ = &canBus_;
215 gc_hub0.send(packet);
216 }
217
226 void clear_expect(bool strict = false)
227 {
228 Mock::VerifyAndClear(&canBus_);
229 if (strict) {
230 EXPECT_CALL(canBus_, mwrite(_)).Times(0);
231 }
232 }
233
246#define send_packet_and_expect_response(pkt, resp) \
247 do \
248 { \
249 expect_packet(resp); \
250 send_packet_and_flush_expect(pkt); \
251 } while (0)
252
258 void send_packet_and_flush_expect(const string &pkt)
259 {
260 send_packet(pkt);
261 wait();
262 Mock::VerifyAndClear(&canBus_);
263 }
264
266 NiceMock<MockSend> canBus_;
268 std::unique_ptr<HubPort> printer_;
269};
270
275protected:
276 AsyncCan1Test() { gc_hub1.register_port(&canBus1_); }
279 gc_hub1.unregister_port(&canBus1_);
280 }
281
284 static void SetUpTestCase() {
285 g_gc_adapter1 =
286 GCAdapterBase::CreateGridConnectAdapter(&gc_hub1, &can_hub1, false);
287 }
288
291 static void TearDownTestCase() {
292 delete g_gc_adapter1;
293 }
294
295#define expect_packet1(gc_packet) \
296 EXPECT_CALL(canBus1_, mwrite(StrCaseEq(gc_packet)))
297
302 void send_packet1(const string &gc_packet) {
303 Buffer<HubData> *packet;
304 mainBufferPool->alloc(&packet);
305 packet->data()->assign(gc_packet);
306 packet->data()->skipMember_ = &canBus1_;
307 gc_hub1.send(packet);
308 }
309
311 StrictMock<MockSend> canBus1_;
312};
313
314namespace openlcb
315{
316
317static const NodeID TEST_NODE_ID = 0x02010d000003ULL;
318
319class LocalIf : public If
320{
321public:
322 LocalIf(int local_nodes_count, NodeID gateway_node_id)
323 : If(&g_executor, local_nodes_count)
324 , gatewayNodeID_(gateway_node_id)
325 {
326 }
327
328 void add_owned_flow(Executable *e) override
329 {
330 ownedFlows_.emplace_back(e);
331 }
332
333 void delete_local_node(Node *node) override
334 {
335 remove_local_node_from_map(node);
336 }
337
338 bool matching_node(NodeHandle expected, NodeHandle actual) override
339 {
340 if (expected.id && actual.id)
341 {
342 return expected.id == actual.id;
343 }
344 // Cannot reconcile.
345 LOG(VERBOSE, "Cannot reconcile expected and actual NodeHandles for "
346 "equality testing.");
347 return false;
348 }
349
351 {
352 return gatewayNodeID_;
353 }
354
355private:
356 std::vector<std::unique_ptr<Destructable>> ownedFlows_;
357 NodeID gatewayNodeID_;
358};
359
368{
369protected:
370 static int local_alias_cache_size;
371 static int local_node_count;
372 static int remote_alias_cache_size;
373
376 {
377 ifCan_.reset(new IfCan(&g_executor, &can_hub0, local_alias_cache_size, remote_alias_cache_size, local_node_count));
378 run_x([this]() { ifCan_->local_aliases()->add(TEST_NODE_ID, 0x22A); });
379 ifCan_->set_alias_allocator(
380 new AliasAllocator(TEST_NODE_ID, ifCan_.get()));
381
382 }
383
385 {
386 wait();
388 {
389 run_x([this]() {
390 ifCan_->alias_allocator()->TEST_finish_pending_allocation();
391 });
392 wait();
393 }
394 }
395
396 friend class ::TrainTestHelper;
397
401 {
402 inject_allocated_alias(0x33A);
403 aliasSeed_ = 0x44C;
405 }
406
407 void inject_allocated_alias(NodeAlias alias)
408 {
409 if (!ifCan_->alias_allocator()) {
410 ifCan_->set_alias_allocator(
411 new AliasAllocator(TEST_NODE_ID, ifCan_.get()));
412 }
413 run_x([this, alias]() {
414 ifCan_->alias_allocator()->TEST_add_allocated_alias(alias);
415 });
416 }
417
418 void expect_next_alias_allocation(NodeAlias a = 0)
419 {
421 if (!a)
422 {
423 ifCan_->alias_allocator()->seed_ = aliasSeed_;
424 a = aliasSeed_;
425 aliasSeed_++;
426 }
427 EXPECT_CALL(canBus_, mwrite(StringPrintf(":X17020%03XN;", a)))
428 .Times(1)
429 .RetiresOnSaturation();
430 EXPECT_CALL(canBus_, mwrite(StringPrintf(":X1610D%03XN;", a)))
431 .Times(1)
432 .RetiresOnSaturation();
433 EXPECT_CALL(canBus_, mwrite(StringPrintf(":X15000%03XN;", a)))
434 .Times(1)
435 .RetiresOnSaturation();
436 EXPECT_CALL(canBus_, mwrite(StringPrintf(":X14003%03XN;", a)))
437 .Times(1)
438 .RetiresOnSaturation();
439
440 EXPECT_CALL(canBus_, mwrite(StringPrintf(":X10700%03XN;", a)))
441 .Times(AtMost(1))
442 .RetiresOnSaturation();
443 }
444
445
446 BarrierNotifiable *get_notifiable()
447 {
448 bn_.reset(&n_);
449 return &bn_;
450 }
451
452 void wait_for_notification()
453 {
455 }
456
459
461 std::unique_ptr<IfCan> ifCan_;
469};
470
471int AsyncIfTest::local_alias_cache_size = 10;
472int AsyncIfTest::local_node_count = 9;
473int AsyncIfTest::remote_alias_cache_size = 10;
474
477{
478protected:
480 : eventService_(ifCan_.get())
481 {
482 EXPECT_CALL(canBus_, mwrite(":X1910022AN02010D000003;")).Times(1);
483 ownedNode_.reset(new DefaultNode(ifCan_.get(), TEST_NODE_ID));
484 node_ = ownedNode_.get();
485 ifCan_->add_addressed_message_support();
486 wait();
487 Mock::VerifyAndClear(&canBus_);
488 // AddEventHandlerToIf(ifCan_.get());
489 }
490
492 {
493 wait_for_event_thread();
494 }
495
496 void wait_for_event_thread()
497 {
498 while (EventService::instance->event_processing_pending())
499 {
500#ifdef __EMSCRIPTEN__
501 os_emscripten_yield();
502#else
503 usleep(100);
504#endif
505 }
507 }
508
509 EventService eventService_;
510 std::unique_ptr<DefaultNode> ownedNode_;
511 Node *node_;
512};
513
519{
520public:
521 MOCK_METHOD2(handle_message,
522 void(GenMessage *message, unsigned priority));
523 virtual void send(Buffer<GenMessage> *message, unsigned priority)
524 {
525 handle_message(message->data(), priority);
526 message->unref();
527 }
528};
529
532MATCHER_P(IsBufferValue, id, "")
533{
534 uint64_t value = htobe64(id);
535 if (arg.size() != 8)
536 return false;
537 if (memcmp(&value, arg.data(), 8))
538 return false;
539 return true;
540}
541
544MATCHER_P(IsBufferValueString, expected, "")
545{
546 string s(expected);
547 return arg == s;
548}
549
552MATCHER_P(IsBufferNodeValue, id, "")
553{
554 uint64_t value = htobe64(id);
555 if (arg->used() != 6)
556 return false;
557 const uint8_t *expected = reinterpret_cast<const uint8_t *>(&value) + 2;
558 const uint8_t *actual = static_cast<const uint8_t *>(arg->start());
559 if (memcmp(expected, actual, 6))
560 {
561 for (int i = 0; i < 6; ++i)
562 {
563 if (expected[i] != actual[i])
564 {
565 LOG(INFO, "mismatch at position %d, expected %02x actual %02x",
566 i, expected[i], actual[i]);
567 }
568 }
569 return false;
570 }
571 return true;
572}
573
575MATCHER_P(IsBufferNodeValueString, id, "")
576{
577 uint64_t value = htobe64(id);
578 if (arg.size() != 6)
579 return false;
580 const uint8_t *expected = reinterpret_cast<const uint8_t *>(&value) + 2;
581 const uint8_t *actual = reinterpret_cast<const uint8_t *>(arg.data());
582 if (memcmp(expected, actual, 6))
583 {
584 for (int i = 0; i < 6; ++i)
585 {
586 if (expected[i] != actual[i])
587 {
588 LOG(INFO, "mismatch at position %d, expected %02x actual %02x",
589 i, expected[i], actual[i]);
590 }
591 }
592 return false;
593 }
594 return true;
595}
596
597} // namespace openlcb
598
599#endif // _UTILS_ASYNC_IF_TEST_HELPER_HXX_
DynamicPool * mainBufferPool
main buffer pool instance
Definition Buffer.cxx:37
StateFlow< Buffer< HubData >, QList< 1 > > HubPort
Base class for a port to an ascii hub that is implemented as a stateflow.
Definition Hub.hxx:139
Test fixture base class for a second CAN-bus.
static void TearDownTestCase()
De-Initializes test case with CAN1.
void send_packet1(const string &gc_packet)
Send a gridconnect packet to the second CAN port.
static void SetUpTestCase()
Initializes test case with CAN1.
StrictMock< MockSend > canBus1_
Helper object for setting expectations on the packets sent on the bus.
Test fixture base class for tests that need a CAN-based interface(but not necessary specific to OpenL...
std::unique_ptr< HubPort > printer_
Object for debug-printing every packet (if requested).
void expect_any_packet()
Ignores all produced packets.
void twait()
Delays the current thread until all asynchronous processing and all pending timers have completed.
void send_packet_and_flush_expect(const string &pkt)
Sends a packet to the canbus, waits for the executor to clear, and then verifies all previous expecta...
static void TearDownTestCase()
De-Initializes test case with CAN0.
static void SetUpTestCase()
Initializes test case with CAN0.
void print_all_packets()
Prints all packets sent to the canbus until the end of the current test function.
NiceMock< MockSend > canBus_
Helper object for setting expectations on the packets sent on the bus.
void clear_expect(bool strict=false)
Clears all existing expectations on the CAN-bus packets.
void send_packet(const string &gc_packet)
Injects a packet to the interface.
void wait()
Delays the current thread until we are certain that all asynchrnous processing has completed.
A BarrierNotifiable allows to create a number of child Notifiable and wait for all of them to finish.
BarrierNotifiable * reset(Notifiable *done)
Resets the barrier. Returns &*this. Asserts that is_done().
Base class for all QMember types that hold data in an expandable format.
Definition Buffer.hxx:195
T * data()
get a pointer to the start of the data.
Definition Buffer.hxx:215
An object that can be scheduled on an executor to run.
Publicly visible API for the gridconnect-to-CAN bridge.
static GCAdapterBase * CreateGridConnectAdapter(HubFlow *gc_side, CanHubFlow *can_side, bool double_bytes)
This function connects an ASCII (GridConnect-format) CAN adapter to a binary CAN adapter,...
Helper class for setting expectation on the CANbus traffic in unit tests.
virtual Action entry()
Entry into the StateFlow activity.
An object that can schedule itself on an executor to run.
virtual void notify()=0
Generic callback.
void alloc(Buffer< BufferType > **result, Executable *flow=NULL)
Get a free item out of the pool.
Definition Buffer.hxx:292
Return type for a state flow callback.
Action release_and_exit()
Terminates the processing of the current message.
State flow with a given typed input queue.
A Notifiable for synchronously waiting for a notification.
void wait_for_notification()
Blocks the current thread until the notification is delivered.
This state flow is responsible for reserving node ID aliases.
Test fixture base class with helper methods for exercising the asynchronous interface code.
std::unique_ptr< IfCan > ifCan_
The interface under test.
AliasInfo testAlias_
Temporary object used to send aliases around in the alias allocator flow.
bool pendingAliasAllocation_
true if we have a pending async alias allocation task.
void create_allocated_alias()
Creates an alias allocator flow, and injects an already allocated alias.
NodeAlias aliasSeed_
The next alias we will make the allocator create.
Base class for test cases with one virtual node on a CANbus interface.
Trivial implementation of a virtual Node.
Global Event Service.
Implementation of the OpenLCB interface abstraction for the CAN-bus interface standard.
Definition IfCan.hxx:65
Abstract class representing an OpenLCB Interface.
Definition If.hxx:185
Performs upon-startup initialization of virtual nodes.
bool matching_node(NodeHandle expected, NodeHandle actual) override
NodeID get_default_node_id() override
void add_owned_flow(Executable *e) override
Transfers ownership of a module to the interface.
void delete_local_node(Node *node) override
Removes a local node from this interface.
Test handler for receiving incoming openlcb Message objects from a bus.
Base class for NMRAnet nodes conforming to the asynchronous interface.
Definition Node.hxx:52
#define LOG(level, message...)
Conditionally write a message to the logging output.
Definition logging.h:99
static const int VERBOSE
Loglevel that is usually not printed, reporting debugging information.
Definition logging.h:59
static const int INFO
Loglevel that is printed by default, reporting some status information.
Definition logging.h:57
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
uint64_t NodeID
48-bit NMRAnet Node ID type
MATCHER_P(IsBufferValue, id, "")
GoogleMock matcher on a Payload being equal to a given 64-bit value in network byte order.
uint16_t NodeAlias
Alias to a 48-bit NMRAnet Node ID type.
long long os_get_time_monotonic(void)
Get the monotonic time since the system started.
Definition os.c:571
Information we know locally about an NMRAnet CAN alias.
This class is used in the dispatching of incoming or outgoing NMRAnet messages to the message handler...
Definition If.hxx:72
Container of both a NodeID and NodeAlias.
NodeID id
48-bit NMRAnet Node ID
void wait_for_main_executor()
Blocks the current thread until the main executor has run out of work.
Executor< 1 > g_executor
Global executor thread for tests.
void run_x(std::function< void()> fn)
Synchronously runs a function in the main executor.
void wait_for_main_timers()
Delays the current thread until all asynchronous processing and all pending timers have completed.