Skip to content
Sylvain Joyeux edited this page Sep 21, 2017 · 1 revision

The last major change before the release of Roby 3 has been the removal of the assertion-based tests that had been introduced earlier during the development of Roby 3, replaced by a different infrastructure.

The new infrastructure deals much better with the synchronous nature of the Roby engine. It handles complex test patterns that were previously really hard to handle with assertions.

The main usage pattern is:

expect_execution do
  ... do things ...
end.to do
  ... setup expectations ...
end

The main change w.r.t. the assertions is that the expectations are aggregated. Composite assertions such as

assert_fatal_exception(...) do
  assert_event_emission(...) do
  end
end

were really fragile, and would fail in a lot of cases. A lot of assertion combinations were simply not possible.

Major difference the garbage collection is not run by default when using expectations. Which means that one does need to setup root tasks as permanent/missions. Moreover, the harness will ignore errors that are related to expected "things. For instance:

plan.add_mission_task(task = Roby::Tasks::Simple.new)
task.depends_on(child = Roby::Tasks::Simple.new)
expect_execution do
  task.start!
  child.start!
  child.stop!
end.to do
  emit child.stop_event
end

one does not need to catch the ChildFailedError and MissionFailed exceptions because they are both caused by child.stop_event, which is explicitly expected.

Converting assertions

In the following, I'll show how some common patterns are converted from the assertions to the expect_execution harness.

assert_event_emission

assert_event_emission(...) do
  ...
end

becomes

expect_execution { ... }.to do
  emit positive_event # test that this event is emitted
  not_emit negative_event # test that this event is not emitted
end

Exception matchers

The assert_*_exception and assert_fails_to_start assertions were usually given an exception class as well as a bunch of side tests on the final error (trace, original exception, ...)

Whenever an exception needs to be matched under expect_execution, one must create an error matcher. It can simply be the error class if that's enough. Otherwise, one can refine the match with ErrorClass.match for localized error (e.g. ChildFailedError, PlanningFailedError). It creates an object of class LocalizedErrorMatcher

assert_fails_to_start

expect_execution { ... }.to { fail_to_start task }

if one wants to check why the task failed to start, an exception matcher can be provided

expect_execution { ... }.to { fail_to_start task, reason: ErrorMatcher }

assert_*_exception

expect_execution { ... }.to { have_error_matching ErrorMatcher }