@@ -374,7 +374,20 @@ class TestWaitable : public rclcpp::Waitable
374
374
{
375
375
(void ) data;
376
376
count_++;
377
- std::this_thread::sleep_for (3ms);
377
+ if (nullptr != on_execute_callback_) {
378
+ on_execute_callback_ ();
379
+ } else {
380
+ // TODO(wjwwood): I don't know why this was here, but probably it should
381
+ // not be there, or test cases where that is important should use the
382
+ // on_execute_callback?
383
+ std::this_thread::sleep_for (3ms);
384
+ }
385
+ }
386
+
387
+ void
388
+ set_on_execute_callback (std::function<void ()> on_execute_callback)
389
+ {
390
+ on_execute_callback_ = on_execute_callback;
378
391
}
379
392
380
393
void
@@ -404,6 +417,7 @@ class TestWaitable : public rclcpp::Waitable
404
417
private:
405
418
size_t count_ = 0 ;
406
419
rclcpp::GuardCondition gc_;
420
+ std::function<void ()> on_execute_callback_ = nullptr ;
407
421
};
408
422
409
423
TYPED_TEST (TestExecutors, spinAll)
@@ -448,45 +462,180 @@ TYPED_TEST(TestExecutors, spinAll)
448
462
spinner.join ();
449
463
}
450
464
451
- TYPED_TEST (TestExecutors, spinSome)
465
+ // Helper function to convert chrono durations into a scalar that GoogleTest
466
+ // can more easily compare and print.
467
+ template <typename DurationT>
468
+ auto
469
+ to_nanoseconds_helper (DurationT duration)
470
+ {
471
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count ();
472
+ }
473
+
474
+ // The purpose of this test is to check that the ExecutorT.spin_some() method:
475
+ // - works nominally (it can execute entities)
476
+ // - it can execute multiple items at once
477
+ // - it does not wait for work to be available before returning
478
+ TYPED_TEST (TestExecutors, spin_some)
452
479
{
453
480
using ExecutorType = TypeParam;
454
- ExecutorType executor;
481
+
482
+ // Use an isolated callback group to avoid interference from any housekeeping
483
+ // items that may be in the default callback group of the node.
484
+ constexpr bool automatically_add_to_executor_with_node = false ;
485
+ auto isolated_callback_group = this ->node ->create_callback_group (
486
+ rclcpp::CallbackGroupType::MutuallyExclusive,
487
+ automatically_add_to_executor_with_node);
488
+
489
+ // Check that spin_some() returns quickly when there is no work to be done.
490
+ // This can be a false positive if there is somehow some work for the executor
491
+ // to do that has not been considered, but the isolated callback group should
492
+ // avoid that.
493
+ {
494
+ ExecutorType executor;
495
+ executor.add_callback_group (isolated_callback_group, this ->node ->get_node_base_interface ());
496
+
497
+ auto start = std::chrono::steady_clock::now ();
498
+ // spin_some with some non-trival "max_duration" and check that it does not
499
+ // take anywhere near that long to execute.
500
+ constexpr auto max_duration = 10s;
501
+ executor.spin_some (max_duration);
502
+ EXPECT_LT (
503
+ to_nanoseconds_helper (std::chrono::steady_clock::now () - start),
504
+ to_nanoseconds_helper (max_duration / 2 ))
505
+ << " spin_some() took a long time to execute when it should have done "
506
+ << " nothing and should not have blocked either, but this could be a "
507
+ << " false negative if the computer is really slow" ;
508
+ }
509
+
510
+ // Check that having one thing ready gets executed by spin_some().
455
511
auto waitable_interfaces = this ->node ->get_node_waitables_interface ();
456
- auto my_waitable = std::make_shared<TestWaitable>();
457
- waitable_interfaces->add_waitable (my_waitable, nullptr );
458
- executor.add_node (this ->node );
512
+ auto my_waitable1 = std::make_shared<TestWaitable>();
513
+ waitable_interfaces->add_waitable (my_waitable1, isolated_callback_group);
514
+ {
515
+ ExecutorType executor;
516
+ executor.add_callback_group (isolated_callback_group, this ->node ->get_node_base_interface ());
517
+
518
+ my_waitable1->trigger ();
519
+
520
+ // The long duration should not matter, as executing the waitable is
521
+ // non-blocking, and spin_some() should exit after completing the available
522
+ // work.
523
+ auto start = std::chrono::steady_clock::now ();
524
+ constexpr auto max_duration = 10s;
525
+ executor.spin_some (max_duration);
526
+ EXPECT_LT (
527
+ to_nanoseconds_helper (std::chrono::steady_clock::now () - start),
528
+ to_nanoseconds_helper (max_duration / 2 ))
529
+ << " spin_some() took a long time to execute when it should have very "
530
+ << " little to do and should not have blocked either, but this could be a "
531
+ << " false negative if the computer is really slow" ;
532
+
533
+ EXPECT_EQ (my_waitable1->get_count (), 1u )
534
+ << " spin_some() failed to execute a waitable that was triggered" ;
535
+ }
459
536
460
- // Long timeout, doesn't block test from finishing because spin_some should exit after the
461
- // first one completes.
462
- bool spin_exited = false ;
463
- std::thread spinner ([&spin_exited, &executor, this ]() {
464
- executor.spin_some (1s);
465
- executor.remove_node (this ->node , true );
466
- spin_exited = true ;
467
- });
537
+ // Check that multiple things being ready are executed by spin_some().
538
+ auto my_waitable2 = std::make_shared<TestWaitable>();
539
+ waitable_interfaces->add_waitable (my_waitable2, isolated_callback_group);
540
+ {
541
+ ExecutorType executor;
542
+ executor.add_callback_group (isolated_callback_group, this ->node ->get_node_base_interface ());
543
+
544
+ const size_t original_my_waitable1_count = my_waitable1->get_count ();
545
+ my_waitable1->trigger ();
546
+ my_waitable2->trigger ();
547
+
548
+ // The long duration should not matter, as executing the waitable is
549
+ // non-blocking, and spin_some() should exit after completing the available
550
+ // work.
551
+ auto start = std::chrono::steady_clock::now ();
552
+ constexpr auto max_duration = 10s;
553
+ executor.spin_some (max_duration);
554
+ EXPECT_LT (
555
+ to_nanoseconds_helper (std::chrono::steady_clock::now () - start),
556
+ to_nanoseconds_helper (max_duration / 2 ))
557
+ << " spin_some() took a long time to execute when it should have very "
558
+ << " little to do and should not have blocked either, but this could be a "
559
+ << " false negative if the computer is really slow" ;
560
+
561
+ EXPECT_EQ (my_waitable1->get_count (), original_my_waitable1_count + 1 )
562
+ << " spin_some() failed to execute a waitable that was triggered" ;
563
+ EXPECT_EQ (my_waitable2->get_count (), 1u )
564
+ << " spin_some() failed to execute a waitable that was triggered" ;
565
+ }
566
+ }
468
567
469
- // Do some work until sufficient calls to the waitable occur, but keep going until either
470
- // count becomes too large, spin exits, or the 1 second timeout completes.
471
- auto start = std::chrono::steady_clock::now ();
472
- while (
473
- my_waitable->get_count () <= 1 &&
474
- !spin_exited &&
475
- (std::chrono::steady_clock::now () - start < 1s))
568
+ // The purpose of this test is to check that the ExecutorT.spin_some() method:
569
+ // - does not continue executing after max_duration has elapsed
570
+ TYPED_TEST (TestExecutors, spin_some_max_duration)
571
+ {
572
+ using ExecutorType = TypeParam;
573
+
574
+ // TODO(wjwwood): The `StaticSingleThreadedExecutor` and the `EventsExecutor`
575
+ // do not properly implement max_duration (it seems), so disable this test
576
+ // for them in the meantime.
577
+ // see: https://github.com/ros2/rclcpp/issues/2462
578
+ if (
579
+ std::is_same<ExecutorType, rclcpp::executors::StaticSingleThreadedExecutor>() ||
580
+ std::is_same<ExecutorType, rclcpp::experimental::executors::EventsExecutor>())
476
581
{
477
- my_waitable->trigger ();
478
- this ->publisher ->publish (test_msgs::msg::Empty ());
479
- std::this_thread::sleep_for (1ms);
582
+ GTEST_SKIP ();
480
583
}
481
- // The count of "execute" depends on whether the executor starts spinning before (1) or after (0)
482
- // the first iteration of the while loop
483
- EXPECT_LE (1u , my_waitable->get_count ());
484
- waitable_interfaces->remove_waitable (my_waitable, nullptr );
485
- EXPECT_TRUE (spin_exited);
486
- // Cancel if it hasn't exited already.
487
- executor.cancel ();
488
584
489
- spinner.join ();
585
+ // Use an isolated callback group to avoid interference from any housekeeping
586
+ // items that may be in the default callback group of the node.
587
+ constexpr bool automatically_add_to_executor_with_node = false ;
588
+ auto isolated_callback_group = this ->node ->create_callback_group (
589
+ rclcpp::CallbackGroupType::MutuallyExclusive,
590
+ automatically_add_to_executor_with_node);
591
+
592
+ // Set up a situation with two waitables that take time to execute, such that
593
+ // the time it takes to execute two waitables far exceeds the max_duration
594
+ // given to spin_some(), which should result in spin_some() starting to
595
+ // execute one of them, have the max duration elapse, finish executing one
596
+ // of them, then returning before starting on the second.
597
+ constexpr auto max_duration = 100ms; // relatively short because we expect to exceed it
598
+ constexpr auto waitable_callback_duration = max_duration * 2 ;
599
+ auto long_running_callback = [&waitable_callback_duration]() {
600
+ std::this_thread::sleep_for (waitable_callback_duration);
601
+ };
602
+
603
+ auto waitable_interfaces = this ->node ->get_node_waitables_interface ();
604
+
605
+ auto my_waitable1 = std::make_shared<TestWaitable>();
606
+ my_waitable1->set_on_execute_callback (long_running_callback);
607
+ waitable_interfaces->add_waitable (my_waitable1, isolated_callback_group);
608
+
609
+ auto my_waitable2 = std::make_shared<TestWaitable>();
610
+ my_waitable2->set_on_execute_callback (long_running_callback);
611
+ waitable_interfaces->add_waitable (my_waitable2, isolated_callback_group);
612
+
613
+ ExecutorType executor;
614
+ executor.add_callback_group (isolated_callback_group, this ->node ->get_node_base_interface ());
615
+
616
+ auto start = std::chrono::steady_clock::now ();
617
+ // spin_some and check that it does not take longer than two of waitable_callback_duration,
618
+ // nor significantly less than a single waitable_callback_duration.
619
+ executor.spin_some (max_duration);
620
+ auto spin_some_run_time = std::chrono::steady_clock::now () - start;
621
+ EXPECT_GT (
622
+ to_nanoseconds_helper (spin_some_run_time),
623
+ to_nanoseconds_helper (waitable_callback_duration / 2 ))
624
+ << " spin_some() took less than half the expected time to execute a single "
625
+ << " waitable, which implies it did not actually execute one when it was "
626
+ << " expected to" ;
627
+ EXPECT_LT (
628
+ to_nanoseconds_helper (spin_some_run_time),
629
+ to_nanoseconds_helper (waitable_callback_duration * 2 ))
630
+ << " spin_some() took longer than expected to execute by a significant margin, but "
631
+ << " this could be a false positive on a very slow computer" ;
632
+
633
+ // check that exactly one of the waitables were executed (do not depend on a specific order)
634
+ size_t number_of_waitables_executed = my_waitable1->get_count () + my_waitable2->get_count ();
635
+ EXPECT_EQ (number_of_waitables_executed, 1u )
636
+ << " expected exactly one of the two waitables to be executed, but "
637
+ << " my_waitable1->get_count(): " << my_waitable1->get_count () << " and "
638
+ << " my_waitable2->get_count(): " << my_waitable2->get_count ();
490
639
}
491
640
492
641
// Check spin_node_until_future_complete with node base pointer
0 commit comments