-
Notifications
You must be signed in to change notification settings - Fork 15
ExpectExecution
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.
In the following, I'll show how some common patterns are converted from the assertions to the expect_execution harness.
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
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
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 }
expect_execution { ... }.to { have_error_matching ErrorMatcher }