Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tearDown is called before a test block completes when there are async errors #1512

Closed
jiahaog opened this issue Apr 29, 2021 · 5 comments · Fixed by #1815
Closed

tearDown is called before a test block completes when there are async errors #1512

jiahaog opened this issue Apr 29, 2021 · 5 comments · Fixed by #1815

Comments

@jiahaog
Copy link
Contributor

jiahaog commented Apr 29, 2021

Consider the following:

import 'package:test/test.dart';

Future<void> makeAsyncError() async {
  Future.error('some async error');
}

Future<void> nothing() async {}

void main() {
  test('foo', () async {
    // Same behavior if this is declared with a [tearDown] outside the [test]
    // block.
    addTearDown(() => print('tearDown is called'));

    await makeAsyncError();
    await nothing();

    // If we put the [addTearDown] after the async calls instead, "tearDown is
    // called" will not be printed at all.
    // addTearDown(() => print('tearDown is called'));

    print('End of test');
  });
}

Output:

00:01 +0 -1: test/foo_test.dart: foo [E]
  some async error

00:01 +0 -1: loading test/foo_test.dart
Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'pub run test --chain-stack-traces'.
00:01 +0 -1: test/foo_test.dart: foo
teardown is called
End of test
00:01 +0 -1: Some tests failed.

It would be expected that "tearDown is called" should be printed after "End of test":

00:01 +0 -1: test/foo_test.dart: foo [E]
  some async error

00:01 +0 -1: loading test/foo_test.dart
Consider enabling the flag chain-stack-traces to receive more detailed exceptions.
For example, 'pub run test --chain-stack-traces'.
00:01 +0 -1: test/foo_test.dart: foo
End of test
teardown is called
00:01 +0 -1: Some tests failed.

await nothing(); in the example above is needed to cause this incorrect behavior.

Versions:

Dart SDK version: 2.13.0-38.0.dev (dev) (Mon Feb 15 10:21:50 2021 -0800) on "macos_x64"
package:test: 1.17.2
@jakemac53
Copy link
Contributor

cc @natebosch do you think the recent changes could have modified the behavior here?

@jiahaog
Copy link
Contributor Author

jiahaog commented Apr 29, 2021

This is probably an existing problem for quite some time - I was able to reproduce this internally while synced to a release from ~1 year ago

@natebosch
Copy link
Member

This is probably working as intended, but I would imagine we should be able to change it.

What is the motivation for wanting the test to continue? The downside is that some tests that fail quickly today could fail much more slowly (with timeouts) if we always wait for the main body of a test to complete even if we know the test won't pass.

@jakemac53
Copy link
Contributor

jakemac53 commented Apr 29, 2021

Running tearDown before the test completes is likely to cause other errors in the test and mask the original failure? For instance it might kill processes or delete temp directories that the test is using.

I think we probably should allow the test to continue and not run tearDown until the test is completed.

@jiahaog
Copy link
Contributor Author

jiahaog commented Apr 30, 2021

Context: I found this when looking into flutter/flutter#81521. This happens in flutter_test because it makes the assumption that addTearDown is only called after the test body finishes, resulting in tripped assertions. The following is a (vastly) simplified version of how testWidgets is implemented:

void testWidgets(String description, Future<void> Function() callback) {
  test(description, () {
    bool inTest = true;

    addTearDown(() => inTest = false);

    return () async {
      assert(inTest);

      await callback();
      await nothing();

      // tearDown runs around here, setting inTest to false.

      assert(inTest); // Throws AssertionError

      // More omitted clean up...
    }();
  });
}

Future<void> nothing() async {}

natebosch added a commit that referenced this issue Dec 10, 2022
Fixes #1512

The test invoker tracks the work down by the test, both the callback
bodies and the other work registered with the framework, and in the
typical case waits for it to be complete before the test is considered
done and the next work, like running teardowns, can resume.

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.

- Stop ignoring all outstanding work on any error.
- Explicitly ignore outstanding work for a timeout.
- More reliably decrement the outstanding work counter in a few
  situations where an exception from a test callback could cause it to
  be missed.

Previously these errors would have also caused the outstanding work to
be ignored, so it didn't matter if the decrement happened. 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 for the LoadSuite case can cause the runner to be held open
waiting for `main` to complete after a signal should have stopped tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants