tests: Re-raise exceptions from callbacks when exiting Loop.run()

Exceptions in callbacks are caught with Loop.excepthook, are stored as
an attribute of the Loop instance, and the loop is stopped. When
exiting Loop.run() or Loop.walk(), if any exceptions were caught, they
are re-raised.

The creation of the loop is extracted from the individual test classes
into TestCase. A separate loop instance is used for every test instead
of the default loop, to improve isolation between tests.