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

Add --watch mode to flutter test (Redo) #27192

Closed
wants to merge 28 commits into from
Closed

Conversation

gamebox
Copy link
Contributor

@gamebox gamebox commented Jan 28, 2019

This is a clean rebase of my previous PR (#26247). Merge conflicts got out of control on old branch. This should read much easier.

Other people looking at this, please consult the other PR for previous conversation.l

So that it can instead be used inside of `test/runner.dart`
So that it can be used in 'test/runner.dart'
To allow for test watching and incremental compilation
Allows for incremental compile and running of test suites
Use new runTests method, and handle cleanup manually
when tests complete.

This is blocked by dart-lang/test#973 being
merged and the flutter/flutter repo updating its
dependency on a version that includes that PR
Also, a little polish on the UX, following hot reload
semantics.
Removed references to 'hot reload', and cleaned style nits.
Also, updated packages to pass analyze.
@zoechi zoechi added a: tests "flutter test", flutter_test, or one of our tests tool Affects the "flutter" command-line tool. See also t: labels. labels Jan 28, 2019
@jonahwilliams
Copy link
Member

Killing a watch via Ctrl+C does not correctly take down the watcher:

Unhandled exception:
Bad state: Stream has already been listened to.
#0      _StreamController._subscribe (dart:async/stream_controller.dart:668:7)
#1      _ControllerStream._createSubscription (dart:async/stream_controller.dart:818:19)
#2      _StreamImpl.listen (dart:async/stream_impl.dart:472:9)
#3      _Socket.listen (dart:io/runtime/binsocket_patch.dart:1640:31)
#4      _StdStream.listen (dart:io/stdio.dart:20:20)
#5      new _SinkTransformerStreamSubscription (dart:async/stream_transformers.dart:49:16)
#6      _BoundSinkStream.listen (dart:async/stream_transformers.dart:185:13)
#7      new _SinkTransformerStreamSubscription (dart:async/stream_transformers.dart:49:16)
#8      _BoundSinkStream.listen (dart:async/stream_transformers.dart:185:13)
#9      lineSplitter.<anonymous closure> (package:test_api/src/utils.dart:24:10)
#10     _BoundSubscriptionStream.listen (dart:async/stream_transformers.dart:336:36)
#11     _StreamQueue._cancel (package:async/src/stream_queue.dart:515:62)
#12     StreamQueue.cancel (package:async/src/stream_queue.dart:398:12)
#13     completeShutdown (package:test_core/src/executable.dart:55:14)
#14     _execute.<anonymous closure> (package:test_core/src/executable.dart:143:5)
<asynchronous suspension>
#15     _rootRunUnary (dart:async/zone.dart:1132:38)
#16     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#17     _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
#18     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
#19     _BufferingStreamSubscription._add (dart:async/stream_impl.dart:263:7)
#20     _SyncStreamController._sendData (dart:async/stream_controller.dart:764:19)
#21     _StreamController._add (dart:async/stream_controller.dart:640:7)
#22     _StreamController.add (dart:async/stream_controller.dart:586:5)
#23     _rootRunUnary (dart:async/zone.dart:1132:38)
#24     _CustomZone.runUnary (dart:async/zone.dart:1029:19)
#25     _CustomZone.runUnaryGuarded (dart:async/zone.dart:931:7)
#26     _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:336:11)
#27     _DelayedData.perform (dart:async/stream_impl.dart:591:14)
#28     _StreamImplEvents.handleNext (dart:async/stream_impl.dart:707:11)
#29     _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:667:7)
#30     _rootRun (dart:async/zone.dart:1124:13)
#31     _CustomZone.run (dart:async/zone.dart:1021:19)
#32     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:947:23)
#33     _microtaskLoop (dart:async/schedule_microtask.dart:41:21)
#34     _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5)
#35     _runPendingImmediateCallback (dart:isolate/runtime/libisolate_patch.dart:115:13)
#36     _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:172:5)

@jonahwilliams
Copy link
Member

Notes:

  • Doesn't work with --name/--plain-name (should work or be disallowed)
  • --start-paused seems odd, maybe should be disabled?
  • Does coverage work? it accepted the flag but doesn't seem to be updating coverage on reruns.
  • --update-goldens should probably not be supported?
  • Does -j do anything?

@gamebox
Copy link
Contributor Author

gamebox commented Feb 6, 2019

Sorry for the late reply, been doing work to get ready for this release.

The issue with Ctrl+C is known. The issue is that the test_core package lazily creates another listener to stdin, only to clean it up. The discussion can be found at dart-lang/async#72

The resolution was to wrap the exception in a try{} catch and basically eat the exception, only printing it with the -v flag. It's not a state I'm happy with either. I'd love to hear some ideas to work around this.

As for your notes:

  • --name/--plain-name should work, will investigate
  • I will disable --start-paused, coverage, and --update-goldens when --watch is used. None of them make sense for the usecase of this feature(at least at this point).
  • I'm not familiar with -j, but I didn't change any handling of it. Are you seeing unexpected results?

@gamebox
Copy link
Contributor Author

gamebox commented Feb 6, 2019

The --plain-name flag seems to work for me:

⚡ flutter test --watch --plain-name 'backdrop_demo' test/accessibility_test.dart
Building flutter tool...
Running "flutter packages get" in flutter_gallery...                0.4s
00:00 +0: All material demos meet recommended tap target sizes backdrop_demo
00:00 +1: All material demos have labeled tap targets backdrop_demo
00:00 +2: All material demos meet text contrast guidelines backdrop_demo kLightGalleryTheme
00:00 +3: All material demos meet text contrast guidelines backdrop_demo ThemeData.light()
00:01 +4: All tests passed!
Press 'r' to rerun your tests, 'q' to quit

packages/flutter_tools/lib/src/commands/test.dart Outdated Show resolved Hide resolved
packages/flutter_tools/lib/src/test/runner.dart Outdated Show resolved Hide resolved
packages/flutter_tools/lib/src/test/runner.dart Outdated Show resolved Hide resolved
packages/flutter_tools/lib/src/test/runner.dart Outdated Show resolved Hide resolved
packages/flutter_tools/lib/src/test/runner.dart Outdated Show resolved Hide resolved
packages/flutter_tools/lib/src/test/runner.dart Outdated Show resolved Hide resolved
packages/flutter_tools/lib/src/test/runner.dart Outdated Show resolved Hide resolved
packages/flutter_tools/lib/src/test/runner.dart Outdated Show resolved Hide resolved
* Cleaned up trailing whitespace
* Refactored watcher to use await for
* Addressed style nits
* Added flaky annotation to new devicelab test
* Ensured that --watch is only used with valid other flags
@jonahwilliams
Copy link
Member

I compared the time on flutter_gallery test/demo/material/expansion_panels_demo_test.dart below:

watch run
overhead (ms) 800 2000
compile 0 invalidated (ms) 200 1400
compile 1 invalidated (ms) 300 1400
total (ms) 3500 5500

The performance improvement of compilation is not huge, but definitely noticeable. However, the biggest win is the removal of the 1+ seconds it takes just to start the test suite. This is a serious problem we should look into (separately) for the flutter tool

Fail early when mismatched flags are used with --watch
Stop from having an indefinitely growing list of files to invalidate
@gamebox
Copy link
Contributor Author

gamebox commented Feb 11, 2019

With all of your feedback addressed(I think), do we look good to merge?

@jonahwilliams
Copy link
Member

You still need to resolve the conflicting files. I also haven't seen an performance comparison yet, so that would be good to note for future archeologists.

@gamebox
Copy link
Contributor Author

gamebox commented Feb 11, 2019

Performance Comparison

This compares the performance between flutter test in different scenarios between HEAD of this branch versus master/HEAD

@ HEAD

Whole flutter_gallery suite

flutter_gallery[master]⚡ time flutter test
00:35 +129 ~15: .../flutter/examples/flutter_gallery/test/smoke_test.dart: Flutter Gallery app smoke test
> VideoDemo initController "butterfly"
> VideoDemo initController "bee"
> VideoDemo dispose
< VideoDemo dispose
01:05 +130 ~15: .../flutter/examples/flutter_gallery/test/smoke_test.dart: Flutter Gallery app smoke test with semantics
> VideoDemo initController "butterfly"
> VideoDemo initController "bee"
> VideoDemo dispose
< VideoDemo dispose
01:06 +131 ~15: All tests passed!

real	1m7.442s
user	2m21.090s
sys	0m9.429s

Individual spec "drawer_test.dart"

flutter_gallery[master]⚡ time flutter test test/drawer_test.dart
00:02 +1: All tests passed!

real	0m3.753s
user	0m4.902s
sys	0m0.726s

After this PR

Whole flutter_gallery suite - single run

flutter_gallery[]⚡ time flutter test
00:33 +129 ~15: .../flutter/examples/flutter_gallery/test/smoke_test.dart: Flutter Gallery app smoke test
> VideoDemo initController "butterfly"
> VideoDemo initController "bee"
> VideoDemo dispose
< VideoDemo dispose
01:04 +130 ~15: .../flutter_gallery/test/smoke_test.dart: Flutter Gallery app smoke test with semantics
> VideoDemo initController "butterfly"
> VideoDemo initController "bee"
> VideoDemo dispose
< VideoDemo dispose
01:05 +131 ~15: All tests passed!

real	1m9.738s
user	2m25.467s
sys	0m10.202s

Individual spec "drawer_test.dart"

time flutter test test/drawer_test.dart
00:01 +1: All tests passed!

real	0m3.533s
user	0m4.650s
sys	0m0.688s

A whole directory "test/demo/**/*.dart" - single run

flutter_gallery[ !]⚡ time flutter test test/demo/**/*.dart
Compiling Test Files...                                             1.9s
00:01 +4: All tests passed!

real	0m4.546s
user	0m9.934s
sys	0m1.222s

A whole directory with --watch "test/demo/**/*.dart" - multiple runs

The first run...

flutter_gallery[ !]⚡ time flutter test --watch test/demo/**/*.dart
Compiling Test Files...                                             1.8s
00:02 +4: All tests passed!
Press 'r' to rerun your tests, 'q' to quit

Second run - no changes

00:01 +4: All tests passed!
Press 'r' to rerun your tests, 'q' to quit

Third run - introduce error in test file

Recompiling test files...
                                          1.0s
00:00 +0: .../flutter/examples/flutter_gallery/test/demo/material/drawer_demo_test.dart: Drawer header does not scroll
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following StateError was thrown running a test:
Bad state: No element

When the exception was thrown, this was the stack:
... [STACK TRACE] ...
(elided 28 frames from class _FakeAsync, package dart:async, and package stack_trace)

The test description was:
Drawer header does not scroll
════════════════════════════════════════════════════════════════════════════════════════════════════
00:00 +0 -1: .../flutter/examples/flutter_gallery/test/demo/material/drawer_demo_test.dart: Drawer header does not scroll [E]
  Test failed. See exception logs above.
  The test description was: Drawer header does not scroll

00:01 +3 -1: Some tests failed.
Press 'r' to rerun your tests, 'q' to quit

Fourth run - fix error in test file

Recompiling test files...
                                          0.9s
00:01 +4: All tests passed!
Press 'r' to rerun your tests, 'q' to quit

Fifth run - introduce error to source file

Recompiling test files...
                                          1.1s
00:00 +0: .../flutter/examples/flutter_gallery/test/demo/material/drawer_demo_test.dart: Drawer header does not scroll
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following StateError was thrown running a test:
Bad state: No element

When the exception was thrown, this was the stack:
... [STACK TRACE] ...
(elided 28 frames from class _FakeAsync, package dart:async, and package stack_trace)

The test description was:
Drawer header does not scroll
════════════════════════════════════════════════════════════════════════════════════════════════════
00:00 +0 -1: .../flutter/examples/flutter_gallery/test/demo/material/drawer_demo_test.dart: Drawer header does not scroll [E]
  Test failed. See exception logs above.
  The test description was: Drawer header does not scroll

00:02 +3 -1: Some tests failed.
Press 'r' to rerun your tests, 'q' to quit

Sixth run - Fix error in source file

Recompiling test files...
                                          1.1s
00:01 +4: All tests passed!
Press 'r' to rerun your tests, 'q' to quit

As can be seen the biggest source of improvement is by cutting compilation time in roughly half, and cutting out the 1-1.5 second delay of starting the flutter tools. So the gains are really only realized for scenarios in which either compilation is very complex, or where that startup time represents a large percentage of the overall test execution time (say 20+%).

@jonahwilliams
Copy link
Member

@gamebox have the changes needed in the test packages rolled into google3 yet?

And don't allow another run until the tests have completed, not
just compiled.
@gamebox
Copy link
Contributor Author

gamebox commented Feb 11, 2019

The changes are basically ready to merge. Waiting for an LGTM

Copy link
Member

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

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

I checked out your branch again, cleaned and ran flutter test --watch test/material/slider_theme_test.dart.

It worked on the first run and subsequently failed on a compile error with no changes:

jonahwilliams-macbookpro2:flutter jonahwilliams$ flutter test --watch test/material/slider_theme_test.dart 
00:01 +7: All tests passed!                                                                                                                                                                                
DONE: 1506 ms
Press 'r' to rerun your tests, 'q' to quit
00:00 +0: loading /Users/jonahwilliams/Documents/flutter/packages/flutter/test/material/slider_theme_test.dart                                                                                             Shell: [ERROR:flutter/shell/common/engine.cc(175)] Could not prepare to run the isolate.
Shell: [ERROR:flutter/shell/common/engine.cc(122)] Engine not prepare and launch isolate.
Shell: [ERROR:flutter/shell/testing/tester_main.cc(199)] Could not launch the engine with configuration.
00:00 +0 -1: loading /Users/jonahwilliams/Documents/flutter/packages/flutter/test/material/slider_theme_test.dart [E]                                                                                      
  Failed to load "/Users/jonahwilliams/Documents/flutter/packages/flutter/test/material/slider_theme_test.dart":
  Shell subprocess cleanly reported an error before connecting to test harness. Check the logs above for an error message.
  Test: /Users/jonahwilliams/Documents/flutter/packages/flutter/test/material/slider_theme_test.dart
  Shell: /Users/jonahwilliams/Documents/flutter/bin/cache/artifacts/engine/darwin-x64/flutter_tester
  
  
00:00 +0 -1: Some tests failed.                                                                                                                                                                            
DONE: 81 ms
unhandled error during finalization of test:
/Users/jonahwilliams/Documents/flutter/packages/flutter/test/material/slider_theme_test.dart
FileSystemException: Deletion failed, path = '/var/folders/sq/vd7vtp0j60ld19wt58vg7n5c00c4f_/T/flutter_test_listener.tAHNze' (OS Error: No such file or directory, errno = 2)
#0      _Directory._deleteSync (dart:io/directory_impl.dart:206:7)
#1      FileSystemEntity.deleteSync (dart:io/file_system_entity.dart:466:47)
#2      ForwardingFileSystemEntity.deleteSync (package:file/src/forwarding/forwarding_file_system_entity.dart:72:16)
#3      createListenerDart.<anonymous closure> (package:flutter_tools/src/test/bootstrap.dart:143:15)
<asynchronous suspension>
#4      _FlutterPlatform._startTest (package:flutter_tools/src/test/flutter_platform.dart:478:26)
<asynchronous suspension>
#5      _FlutterPlatform.loadChannel (package:flutter_tools/src/test/flutter_platform.dart:193:36)
#6      _FlutterPlatform.load (package:flutter_tools/src/test/flutter_platform.dart:151:46)
<asynchronous suspension>
#7      Loader.loadFile.<anonymous closure> (package:test_core/src/runner/loader.dart:223:36)
<asynchronous suspension>
#8      new LoadSuite.<anonymous closure>.<anonymous closure> (package:test_core/src/runner/load_suite.dart:98:31)
<asynchronous suspension>
#9      invoke (package:test_api/src/utils.dart:241:5)
#10     new LoadSuite.<anonymous closure> (package:test_core/src/runner/load_suite.dart:97:7)
#11     Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:400:25)
<asynchronous suspension>
#12     new Future.<anonymous closure> (dart:async/future.dart:176:37)
#13     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#14     StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#15     _rootRun (dart:async/zone.dart:1120:38)
#16     _CustomZone.run (dart:async/zone.dart:1021:19)
#17     _CustomZone.runGuarded (dart:async/zone.dart:923:7)
#18     _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:963:23)
#19     StackZoneSpecification._run (package:stack_trace/src/stack_zone_specification.dart:209:15)
#20     StackZoneSpecification._registerCallback.<anonymous closure> (package:stack_trace/src/stack_zone_specification.dart:119:48)
#21     _rootRun (dart:async/zone.dart:1124:13)
#22     _CustomZone.run (dart:async/zone.dart:1021:19)
#23     _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:947:23)
#24     Timer._createTimer.<anonymous closure> (dart:async/runtime/libtimer_patch.dart:21:15)
#25     _Timer._runTimers (dart:isolate/runtime/libtimer_impl.dart:382:19)
#26     _Timer._handleMessage (dart:isolate/runtime/libtimer_impl.dart:416:5)
#27     _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12)


Press 'r' to rerun your tests, 'q' to quit

I've fetched/updated, are you seeing this too or do I have a local issue?

@@ -0,0 +1,185 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

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

This needs the original copyright year

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The year that is attached to the original file?

@@ -0,0 +1,169 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
Copy link
Member

Choose a reason for hiding this comment

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

This needs the original copyright year

}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

nit: newline missing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@gamebox
Copy link
Contributor Author

gamebox commented Feb 12, 2019

Cannot reproduce your issue. May be local. Happy to take a look

@jonahwilliams
Copy link
Member

It seems as if the temporary files are not being cleaned up

@gamebox
Copy link
Contributor Author

gamebox commented Feb 12, 2019

Did you rebuild the snapshot? I get the following when run with the 'v' flag

[   +7 ms] test 0: awaiting initial result for pid 247071
[ +239 ms] test 0: process with pid 247071 connected to test harness
[        ] test 0: awaiting test result for pid 247071
00:01 +22: The ... skip all component painting except the value indicator  [ +765 ms] test 0: process with pid 247071 no longer needed by test harness
[        ] test 0: cleaning up...
[        ] test 0: ensuring end-of-process for shell
[   +4 ms] test 0: shutting down test harness socket server
[   +1 ms] test 0: deleting temporary directory
[   +2 ms] test 0: finished
00:01 +22: All tests passed!                                               
[   +6 ms] Deleting /tmp/flutter_test_fonts.MXPXBB...
[   +6 ms] Press 'r' to rerun your tests, 'q' to quit

Will be replaced soon with package:build
@jonahwilliams
Copy link
Member

I can reliably reproduce this when re-running a single test: flutter test --watch test/drawer_test.dart

@jonahwilliams
Copy link
Member

Another point in favor of this mode of running tests: when integrating package build we will have some non-trivial overhead in verifying an incremental build. This can also help with that.

@ened
Copy link
Contributor

ened commented Mar 30, 2019

@gamebox could you pick this great PR up again? It's such a great addition and removes SO MUCH friction when running tests / building a test suite.

@audkar
Copy link

audkar commented Jul 16, 2019

I hope that work done in this PR will not be wasted. This feature greatly improves high quality software development.

@jonahwilliams
Copy link
Member

@gamebox if you're not working on this right now, could we close it to get it off the PR queue?

@christopherfujino christopherfujino added the waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds label Aug 1, 2019
@dnfield
Copy link
Contributor

dnfield commented Aug 15, 2019

I'm closing this pull request since it is out of sync with head, has had no activity in roughly a month, and does not appear to be resolved. @gamebox if you plan to continue with this, please feel free to re-open after synchronizing this with HEAD. Otherwise, this work is tracked by the linked issue.

@dnfield dnfield closed this Aug 15, 2019
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: tests "flutter test", flutter_test, or one of our tests tool Affects the "flutter" command-line tool. See also t: labels. waiting for customer response The Flutter team cannot make further progress on this issue until the original reporter responds
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

9 participants