Skip to content

Conversation

@jiahaog
Copy link
Member

@jiahaog jiahaog commented Nov 9, 2020

Description

This integrates with directRunTests from package:test_core, and allows users to use test() to wrap their tests, instead of only supporting testWidgets() and failing silently otherwise. In addition, exceptions thrown in other lifecycle blocks such as setUp will be caught and reported.

This is a non-breaking change, so the existing reporting mechanism will continue to work. A global variable is used to ensure that using run will disable the existing reporting mechanism.

Moving the PR originally at flutter/plugins#3179.

Example usage

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() => run(_testMain);

void _testMain() {
  test('A test', () {
    expect(true, true);
  });
}

But we will want to make it easier for users to use this API. A CLI will be added as a follow up PR to wrap this method as part of #66264, so users can write the following instead, and the CLI will take care of wrapping the users' script with run appropriately.

import 'package:flutter_test/flutter_test.dart';

void main() {
  test('A test', () {
    expect(true, true);
  });
}

Related Issues

Fixes #50816.

Tests

I added the following tests:

Tests for the new reporter.

Breaking Change

Does your PR require plugin users to manually update their apps to accommodate your change?

  • No, this is not a breaking change.

Comment on lines 132 to 134
print('Using the legacy test result reporter, which will not catch all '
'errors thrown in declared tests. Consider wrapping tests with [run] '
'instead.');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than [run] here, I think we probably want a link to either API docs or a wiki page about this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, now that this lives in the Flutter repo, we may want the flutter drive command to handle this - /cc @jonahwilliams for thoughts on that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure the context, but we could accept more command line args for the drive script if that is what you mean?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jiahaog would like to extend some functionality to make it easier to run integration tests - in particular, to wrap the test script with this kind of logic, and to help find all integration tests and run them (the way it works with other _test.dart files for regular unit tests).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonahwilliams There's more detailed context in #66264.

The short summary is that flutter drive is a pretty low level CLI that is not too friendly for users when they need to specify different tests / driver scripts. In addition, there's a bit of boilerplate that can we can hide from users in some sort of executable that we provide. With this PR, users will need to wrap their tests with run

Before

import 'package:integration_test/integration_test.dart';

void main() {
  testWidgets("failing test example", (WidgetTester tester) async { ... });
}

After

import 'package:integration_test/integration_test.dart';

void main() => run(_testMain);

void _testMain() {
  testWidgets("failing test example", (WidgetTester tester) async { ... });
}

It will be great if we could hide the need to call run from users, in a "bootstrap" sort of functionality in the executable – I believe package:flutter_test and package:test do something similar in the executables that are provided.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh okay. No I think putting this in integration_test is the right way to go. The tool API should remain as is - and generally speaking we should avoid increasing that surface area because it will be difficult/impossible to change

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the flutter release process, will having a separate tool for integration tests in packages/integration_test/bin/ be the correct approach here? (Users will be invoking it with something like flutter pub run integration_test)

Comment on lines 156 to 157
_allTestsPassed
.complete(!results.values.any((Object val) => val is Failure));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: formatting

Is there any way we could avoid switching on types here? That can become brittle. Could we instead have the results be of such a type that it has some property saying whether it's an error?

Maybe rather than a separate Failure type, we have a .failure ctor?

Copy link
Member Author

@jiahaog jiahaog Nov 10, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming you mean something like this:

enum ResultStatus {
  pass, fail
}

class Result {
  final ResultStatus status;

  Result.failure(...);

  Result.success(...);
}

I'm all for this, but the inconvenient bit is that Failure today is already part of the public type, and we might not want to replace Failure with the Result above.

Dart doesn't have type unions, but another option would be to have some sort of superclass type:

class Result {}

// make the old failure extend [Result]
class Failure extends Result {}

class Success extends Result {}

And then store them in the binding as Map<String, Result>, or even List<Result> (since the key for the test name can be contained within the class). What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think having the supertype is fine. We could then just add some bool getter on it about whether the result describes a failure, and make Failure implement it as true.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added the supertype. I think the bool getter is a bit unnecessary and switching on types should be alright – now that the parent type is Result checking for Failure should be safer

Comment on lines 35 to 37
: Failure(
liveTest.test.name, liveTest.errors.first.stackTrace.toString(),
error: liveTest.errors.first.error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
: Failure(
liveTest.test.name, liveTest.errors.first.stackTrace.toString(),
error: liveTest.errors.first.error)
: Failure(
liveTest.test.name,
liveTest.errors.first.stackTrace.toString(),
error: liveTest.errors.first.error,
)

Also, this seems concerning that it will only have the first error. It seems like it would be better to capture them all.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved the formatting issue.

Also, this seems concerning that it will only have the first error. It seems like it would be better to capture them all.

I took this snippet from the Google3 test reporter. Will do a bit of research to find out if this is necessary and follow up :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@natebosch may know.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in any system which expects only a single reason for a test case to fail it is sensible to choose the first error to report. The Dart test runner allows a single test to have multiple errors, but if we are reporting into a system that only allows a single reason to fail we have pick between trying to do a strange concatenation or only reporting the first error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should probably support whatever errors we can access internally, and then we can either bundle them up or not when we report to JUint/XCTest/etc.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made this collect the list of errors. I did not wire the errors into the platform side as it's a bit out of scope.

@jiahaog jiahaog requested a review from dnfield November 12, 2020 10:19
Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jiahaog jiahaog merged commit af5eb3b into flutter:master Nov 13, 2020
@jiahaog jiahaog deleted the integration-test-reporter branch November 13, 2020 11:57
jonahwilliams pushed a commit that referenced this pull request Nov 13, 2020
jonahwilliams pushed a commit that referenced this pull request Nov 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: contributor-productivity Team-specific productivity, code health, technical debt.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make E2E use a custom test reporter so that invoking test() will work.

4 participants