Don't ignore outstanding work on failure (#1815)

Fixes #1512

The test invoker tracks the work done by the test, both the callback
bodies and the other work registered with the framework, and in the
typical case waits for it to complete before the test is considered
done.

Previously, any error would immediately ignore all outstanding work. In
the case of an unhandled async error, the test may still be using
resources that will be cleaned up by the tear down.

Some tests that previously would have failed quickly will now result in
a timeout. This should be uncommon, and will only impact tests that
fail anyway.

- Stop ignoring all outstanding work on any error.
- Explicitly ignore outstanding work for a timeout.
- Fix some cases that run tests that do fail and then timeout. These
  tests had been manually incrementing the outstanding callback count
  and relying on the known failure to prevent a timeout. Decrement the
  outstanding callback count after the errors have been delivered.
- More reliably decrement the outstanding work counter in a few
  situations where an exception from a test callback could cause it to
  be missed so it had to be cleaned up by the error handler.
  - Use a try/finally around callbacks which introduce their own
    outstanding work tracking zones.
  - Use a try/catch/rethrow around the body for load suites (the
    platform work including the user `main` method). Using a try/finally
    can cause the runner to be held open waiting for `main` to complete
    after a signal should have stopped tests.
10 files changed