Do not stop flutter_tester if microtasks are still pending (flutter/engine#56432)

Flutter_tester has a task observer that checks whether the test's Dart code has finished execution.  If Dart no longer has live ports but does have pending microtasks, then flutter_tester should continue running and force a drain of the microtask queue.

Fixes https://github.com/flutter/flutter/issues/158129
This commit is contained in:
Jason Simmons 2024-11-11 11:22:07 -08:00 committed by GitHub
parent d83dc6bdae
commit ef5c2fc014
9 changed files with 68 additions and 0 deletions

View File

@ -169,6 +169,10 @@ void UIDartState::FlushMicrotasksNow() {
microtask_queue_.RunMicrotasks();
}
bool UIDartState::HasPendingMicrotasks() {
return microtask_queue_.HasMicrotasks();
}
void UIDartState::AddOrRemoveTaskObserver(bool add) {
auto task_runner = context_.task_runners.GetUITaskRunner();
if (!task_runner) {

View File

@ -130,6 +130,8 @@ class UIDartState : public tonic::DartState {
void FlushMicrotasksNow();
bool HasPendingMicrotasks();
fml::WeakPtr<IOManager> GetIOManager() const;
fml::RefPtr<flutter::SkiaUnrefQueue> GetSkiaUnrefQueue() const;

View File

@ -511,6 +511,14 @@ bool RuntimeController::HasLivePorts() {
return Dart_HasLivePorts();
}
bool RuntimeController::HasPendingMicrotasks() {
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
if (!root_isolate) {
return false;
}
return root_isolate->HasPendingMicrotasks();
}
tonic::DartErrorHandleType RuntimeController::GetLastError() {
std::shared_ptr<DartIsolate> root_isolate = root_isolate_.lock();
return root_isolate ? root_isolate->GetLastError() : tonic::kNoError;

View File

@ -523,6 +523,15 @@ class RuntimeController : public PlatformConfigurationClient,
///
bool HasLivePorts();
//----------------------------------------------------------------------------
/// @brief Returns if the root isolate has any pending microtasks.
///
/// @return True if there are microtasks that have been queued but not
/// run, False otherwise. Return False if the root isolate is not
/// running as well.
///
bool HasPendingMicrotasks();
//----------------------------------------------------------------------------
/// @brief Get the last error encountered by the microtask queue.
///

View File

@ -293,6 +293,10 @@ bool Engine::UIIsolateHasLivePorts() {
return runtime_controller_->HasLivePorts();
}
bool Engine::UIIsolateHasPendingMicrotasks() {
return runtime_controller_->HasPendingMicrotasks();
}
tonic::DartErrorHandleType Engine::GetUIIsolateLastError() {
return runtime_controller_->GetLastError();
}

View File

@ -682,6 +682,15 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
///
bool UIIsolateHasLivePorts();
/// @brief Another signal of liveness is the presence of microtasks that
/// have been queued by the application but have not yet been
/// executed. Embedders may want to check for pending microtasks
/// and ensure that the microtask queue has been drained before
/// the embedder terminates.
///
/// @return Check if the root isolate has any pending microtasks.
bool UIIsolateHasPendingMicrotasks();
//----------------------------------------------------------------------------
/// @brief Errors that are unhandled on the Dart message loop are kept
/// for further inspection till the next unhandled error comes

View File

@ -710,6 +710,17 @@ bool Shell::EngineHasLivePorts() const {
return weak_engine_->UIIsolateHasLivePorts();
}
bool Shell::EngineHasPendingMicrotasks() const {
FML_DCHECK(is_set_up_);
FML_DCHECK(task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread());
if (!weak_engine_) {
return false;
}
return weak_engine_->UIIsolateHasPendingMicrotasks();
}
bool Shell::IsSetup() const {
return is_set_up_;
}

View File

@ -358,6 +358,17 @@ class Shell final : public PlatformView::Delegate,
///
bool EngineHasLivePorts() const;
//----------------------------------------------------------------------------
/// @brief Used by embedders to check if the Engine is running and has
/// any microtasks that have been queued but have not yet run.
/// The Flutter tester uses this as a signal that a test is still
/// running.
///
/// @return Returns if the shell has an engine and the engine has pending
/// microtasks.
///
bool EngineHasPendingMicrotasks() const;
//----------------------------------------------------------------------------
/// @brief Accessor for the disable GPU SyncSwitch.
// |Rasterizer::Delegate|

View File

@ -267,9 +267,11 @@ class ScriptCompletionTaskObserver {
public:
ScriptCompletionTaskObserver(Shell& shell,
fml::RefPtr<fml::TaskRunner> main_task_runner,
fml::RefPtr<fml::TaskRunner> ui_task_runner,
bool run_forever)
: shell_(shell),
main_task_runner_(std::move(main_task_runner)),
ui_task_runner_(std::move(ui_task_runner)),
run_forever_(run_forever) {}
int GetExitCodeForLastError() const {
@ -283,6 +285,12 @@ class ScriptCompletionTaskObserver {
// just yet.
return;
}
if (shell_.EngineHasPendingMicrotasks()) {
// Post an empty task to force a run of the engine task observer that
// drains the microtask queue.
ui_task_runner_->PostTask([] {});
return;
}
if (run_forever_) {
// We need this script to run forever. We have already recorded the last
@ -302,6 +310,7 @@ class ScriptCompletionTaskObserver {
private:
Shell& shell_;
fml::RefPtr<fml::TaskRunner> main_task_runner_;
fml::RefPtr<fml::TaskRunner> ui_task_runner_;
bool run_forever_ = false;
std::optional<DartErrorCode> last_error_;
bool has_terminated_ = false;
@ -456,6 +465,7 @@ int RunTester(const flutter::Settings& settings,
*shell, // a valid shell
fml::MessageLoop::GetCurrent()
.GetTaskRunner(), // the message loop to terminate
ui_task_runner, // runner for Dart microtasks
run_forever // should the exit be ignored
);