// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/common/engine.h"

// #include <cstring>

#include "flutter/common/constants.h"
#include "flutter/lib/ui/compositing/scene_builder.h"
#include "flutter/shell/common/shell_test.h"
#include "flutter/testing/fixture_test.h"
#include "gmock/gmock.h"
#include "impeller/core/runtime_types.h"

// CREATE_NATIVE_ENTRY is leaky by design
// NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)

namespace flutter {

namespace {

using ::testing::Invoke;
using ::testing::ReturnRef;

fml::AutoResetWaitableEvent native_latch;

void PostSync(const fml::RefPtr<fml::TaskRunner>& task_runner,
              const fml::closure& task) {
  fml::AutoResetWaitableEvent latch;
  fml::TaskRunner::RunNowOrPostTask(task_runner, [&latch, &task] {
    task();
    latch.Signal();
  });
  latch.Wait();
}

// Sort the argument list of `LayerTreeTask` into a new list that is sorted by
// their view IDs. `FrameItem::layer_tree_tasks` might not come sorted.
std::vector<const LayerTreeTask*> Sorted(
    const std::vector<std::unique_ptr<LayerTreeTask>>& layer_tree_tasks) {
  std::vector<const LayerTreeTask*> result;
  result.reserve(layer_tree_tasks.size());
  for (auto& task_ptr : layer_tree_tasks) {
    result.push_back(task_ptr.get());
  }
  std::sort(result.begin(), result.end(),
            [](const LayerTreeTask* a, const LayerTreeTask* b) {
              return a->view_id < b->view_id;
            });
  return result;
}

class MockDelegate : public Engine::Delegate {
 public:
  MOCK_METHOD(void,
              OnEngineUpdateSemantics,
              (int64_t, SemanticsNodeUpdates, CustomAccessibilityActionUpdates),
              (override));
  MOCK_METHOD(void,
              OnEngineHandlePlatformMessage,
              (std::unique_ptr<PlatformMessage>),
              (override));
  MOCK_METHOD(void, OnPreEngineRestart, (), (override));
  MOCK_METHOD(void, OnRootIsolateCreated, (), (override));
  MOCK_METHOD(void,
              UpdateIsolateDescription,
              (const std::string, int64_t),
              (override));
  MOCK_METHOD(void, SetNeedsReportTimings, (bool), (override));
  MOCK_METHOD(std::unique_ptr<std::vector<std::string>>,
              ComputePlatformResolvedLocale,
              (const std::vector<std::string>&),
              (override));
  MOCK_METHOD(void, RequestDartDeferredLibrary, (intptr_t), (override));
  MOCK_METHOD(fml::TimePoint, GetCurrentTimePoint, (), (override));
  MOCK_METHOD(const std::shared_ptr<PlatformMessageHandler>&,
              GetPlatformMessageHandler,
              (),
              (const, override));
  MOCK_METHOD(void, OnEngineChannelUpdate, (std::string, bool), (override));
  MOCK_METHOD(double,
              GetScaledFontSize,
              (double font_size, int configuration_id),
              (const, override));
  MOCK_METHOD(void,
              RequestViewFocusChange,
              (const ViewFocusChangeRequest&),
              (override));
};

class MockAnimatorDelegate : public Animator::Delegate {
 public:
  /* Animator::Delegate */
  MOCK_METHOD(void,
              OnAnimatorBeginFrame,
              (fml::TimePoint frame_target_time, uint64_t frame_number),
              (override));
  MOCK_METHOD(void,
              OnAnimatorNotifyIdle,
              (fml::TimeDelta deadline),
              (override));
  MOCK_METHOD(void,
              OnAnimatorUpdateLatestFrameTargetTime,
              (fml::TimePoint frame_target_time),
              (override));
  MOCK_METHOD(void,
              OnAnimatorDraw,
              (std::shared_ptr<FramePipeline> pipeline),
              (override));
  MOCK_METHOD(void,
              OnAnimatorDrawLastLayerTrees,
              (std::unique_ptr<FrameTimingsRecorder> frame_timings_recorder),
              (override));
};

class MockPlatformMessageHandler : public PlatformMessageHandler {
 public:
  MOCK_METHOD(void,
              HandlePlatformMessage,
              (std::unique_ptr<PlatformMessage> message),
              (override));
  MOCK_METHOD(bool,
              DoesHandlePlatformMessageOnPlatformThread,
              (),
              (const, override));
  MOCK_METHOD(void,
              InvokePlatformMessageResponseCallback,
              (int response_id, std::unique_ptr<fml::Mapping> mapping),
              (override));
  MOCK_METHOD(void,
              InvokePlatformMessageEmptyResponseCallback,
              (int response_id),
              (override));
};

class EngineAnimatorTest : public testing::FixtureTest {
 public:
  EngineAnimatorTest()
      : thread_host_("EngineAnimatorTest",
                     ThreadHost::Type::kPlatform | ThreadHost::Type::kIo |
                         ThreadHost::Type::kUi | ThreadHost::Type::kRaster),
        task_runners_({
            "EngineAnimatorTest",
            thread_host_.platform_thread->GetTaskRunner(),  // platform
            thread_host_.raster_thread->GetTaskRunner(),    // raster
            thread_host_.ui_thread->GetTaskRunner(),        // ui
            thread_host_.io_thread->GetTaskRunner()         // io
        }) {}

  void PostUITaskSync(const std::function<void()>& function) {
    fml::AutoResetWaitableEvent latch;
    task_runners_.GetUITaskRunner()->PostTask([&] {
      function();
      latch.Signal();
    });
    latch.Wait();
  }

 protected:
  void SetUp() override {
    settings_ = CreateSettingsForFixture();
    dispatcher_maker_ = [](PointerDataDispatcher::Delegate&) {
      return nullptr;
    };
  }

  MockDelegate delegate_;
  PointerDataDispatcherMaker dispatcher_maker_;
  ThreadHost thread_host_;
  TaskRunners task_runners_;
  Settings settings_;
  std::unique_ptr<Animator> animator_;
  fml::WeakPtr<IOManager> io_manager_;
  std::unique_ptr<RuntimeController> runtime_controller_;
  std::shared_ptr<fml::ConcurrentTaskRunner> image_decoder_task_runner_;
  fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate_;
};

// A class that can launch an Engine with the specified Engine::Delegate.
//
// To use this class, contruct this class with Create, call Run, and use the
// engine with EngineTaskSync().
class EngineContext {
 public:
  using EngineCallback = std::function<void(Engine&)>;

  [[nodiscard]] static std::unique_ptr<EngineContext> Create(
      Engine::Delegate& delegate,       //
      Settings settings,                //
      const TaskRunners& task_runners,  //
      std::unique_ptr<Animator> animator) {
    auto [vm, isolate_snapshot] = Shell::InferVmInitDataFromSettings(settings);
    FML_CHECK(vm) << "Must be able to initialize the VM.";
    // Construct the class with `new` because `make_unique` has no access to the
    // private constructor.
    EngineContext* raw_pointer =
        new EngineContext(delegate, settings, task_runners, std::move(animator),
                          vm, isolate_snapshot);
    return std::unique_ptr<EngineContext>(raw_pointer);
  }

  void Run(RunConfiguration configuration) {
    PostSync(task_runners_.GetUITaskRunner(), [this, &configuration] {
      Engine::RunStatus run_status = engine_->Run(std::move(configuration));
      FML_CHECK(run_status == Engine::RunStatus::Success)
          << "Engine failed to run.";
      (void)run_status;  // Suppress unused-variable warning
    });
  }

  // Run a task that operates the Engine on the UI thread, and wait for the
  // task to end.
  //
  // If called on the UI thread, the task is executed synchronously.
  void EngineTaskSync(EngineCallback task) {
    ASSERT_TRUE(engine_);
    ASSERT_TRUE(task);
    auto runner = task_runners_.GetUITaskRunner();
    if (runner->RunsTasksOnCurrentThread()) {
      task(*engine_);
    } else {
      PostSync(task_runners_.GetUITaskRunner(), [&]() { task(*engine_); });
    }
  }

  ~EngineContext() {
    PostSync(task_runners_.GetUITaskRunner(), [this] { engine_.reset(); });
  }

 private:
  EngineContext(Engine::Delegate& delegate,          //
                Settings settings,                   //
                const TaskRunners& task_runners,     //
                std::unique_ptr<Animator> animator,  //
                const DartVMRef& vm,                 //
                fml::RefPtr<const DartSnapshot> isolate_snapshot)
      : task_runners_(task_runners), vm_(vm) {
    PostSync(task_runners.GetUITaskRunner(), [this, &settings, &animator,
                                              &delegate, &isolate_snapshot] {
      auto dispatcher_maker =
          [](DefaultPointerDataDispatcher::Delegate& delegate) {
            return std::make_unique<DefaultPointerDataDispatcher>(delegate);
          };
      std::promise<impeller::RuntimeStageBackend> rsb;
      rsb.set_value(impeller::RuntimeStageBackend::kVulkan);
      engine_ = std::make_unique<Engine>(
          /*delegate=*/delegate,
          /*dispatcher_maker=*/dispatcher_maker,
          /*vm=*/*&vm_,
          /*isolate_snapshot=*/std::move(isolate_snapshot),
          /*task_runners=*/task_runners_,
          /*platform_data=*/PlatformData(),
          /*settings=*/settings,
          /*animator=*/std::move(animator),
          /*io_manager=*/io_manager_,
          /*unref_queue=*/nullptr,
          /*snapshot_delegate=*/snapshot_delegate_,
          /*gpu_disabled_switch=*/std::make_shared<fml::SyncSwitch>(),
          /*runtime_stage_backend=*/rsb.get_future());
    });
  }

  TaskRunners task_runners_;
  DartVMRef vm_;
  std::unique_ptr<Engine> engine_;

  fml::WeakPtr<IOManager> io_manager_;
  fml::TaskRunnerAffineWeakPtr<SnapshotDelegate> snapshot_delegate_;
};
}  // namespace

TEST_F(EngineAnimatorTest, AnimatorAcceptsMultipleRenders) {
  MockAnimatorDelegate animator_delegate;
  std::unique_ptr<EngineContext> engine_context;

  std::shared_ptr<PlatformMessageHandler> platform_message_handler =
      std::make_shared<MockPlatformMessageHandler>();
  EXPECT_CALL(delegate_, GetPlatformMessageHandler)
      .WillOnce(ReturnRef(platform_message_handler));
  fml::AutoResetWaitableEvent draw_latch;
  EXPECT_CALL(animator_delegate, OnAnimatorDraw)
      .WillOnce(
          Invoke([&draw_latch](const std::shared_ptr<FramePipeline>& pipeline) {
            auto status =
                pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
                  auto tasks = Sorted(item->layer_tree_tasks);
                  EXPECT_EQ(tasks.size(), 2u);
                  EXPECT_EQ(tasks[0]->view_id, 1);
                  EXPECT_EQ(tasks[1]->view_id, 2);
                });
            EXPECT_EQ(status, PipelineConsumeResult::Done);
            draw_latch.Signal();
          }));
  EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
      .WillOnce(Invoke([&engine_context](fml::TimePoint frame_target_time,
                                         uint64_t frame_number) {
        engine_context->EngineTaskSync([&](Engine& engine) {
          engine.BeginFrame(frame_target_time, frame_number);
        });
      }));

  native_latch.Reset();
  AddNativeCallback("NotifyNative", [](auto args) { native_latch.Signal(); });

  std::unique_ptr<Animator> animator;
  PostSync(task_runners_.GetUITaskRunner(),
           [&animator, &animator_delegate, &task_runners = task_runners_] {
             animator = std::make_unique<Animator>(
                 animator_delegate, task_runners,
                 static_cast<std::unique_ptr<VsyncWaiter>>(
                     std::make_unique<testing::ConstantFiringVsyncWaiter>(
                         task_runners)));
           });

  engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
                                         std::move(animator));
  auto configuration = RunConfiguration::InferFromSettings(settings_);
  configuration.SetEntrypoint("onDrawFrameRenderAllViews");
  engine_context->Run(std::move(configuration));

  engine_context->EngineTaskSync([](Engine& engine) {
    engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
    engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
  });

  native_latch.Wait();

  engine_context->EngineTaskSync(
      [](Engine& engine) { engine.ScheduleFrame(); });
  draw_latch.Wait();
}

TEST_F(EngineAnimatorTest, IgnoresOutOfFrameRenders) {
  MockAnimatorDelegate animator_delegate;
  std::unique_ptr<EngineContext> engine_context;

  std::shared_ptr<PlatformMessageHandler> platform_message_handler =
      std::make_shared<MockPlatformMessageHandler>();
  EXPECT_CALL(delegate_, GetPlatformMessageHandler)
      .WillOnce(ReturnRef(platform_message_handler));
  fml::AutoResetWaitableEvent draw_latch;
  EXPECT_CALL(animator_delegate, OnAnimatorDraw)
      .WillOnce(
          Invoke([&draw_latch](const std::shared_ptr<FramePipeline>& pipeline) {
            auto status =
                pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
                  // View 1 is rendered before the frame, and is ignored.
                  // View 2 is rendered within the frame, and is accepted.
                  EXPECT_EQ(item->layer_tree_tasks.size(), 1u);
                  EXPECT_EQ(item->layer_tree_tasks[0]->view_id, 2);
                });
            EXPECT_EQ(status, PipelineConsumeResult::Done);
            draw_latch.Signal();
          }));
  EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
      .WillOnce(Invoke([&engine_context](fml::TimePoint frame_target_time,
                                         uint64_t frame_number) {
        engine_context->EngineTaskSync([&](Engine& engine) {
          engine.BeginFrame(frame_target_time, frame_number);
        });
      }));

  std::unique_ptr<Animator> animator;
  PostSync(task_runners_.GetUITaskRunner(),
           [&animator, &animator_delegate, &task_runners = task_runners_] {
             animator = std::make_unique<Animator>(
                 animator_delegate, task_runners,
                 static_cast<std::unique_ptr<VsyncWaiter>>(
                     std::make_unique<testing::ConstantFiringVsyncWaiter>(
                         task_runners)));
           });

  engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
                                         std::move(animator));

  engine_context->EngineTaskSync([](Engine& engine) {
    engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
    engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
  });

  auto configuration = RunConfiguration::InferFromSettings(settings_);
  configuration.SetEntrypoint("renderViewsInFrameAndOutOfFrame");
  engine_context->Run(std::move(configuration));

  draw_latch.Wait();
}

TEST_F(EngineAnimatorTest, IgnoresDuplicateRenders) {
  MockAnimatorDelegate animator_delegate;
  std::unique_ptr<EngineContext> engine_context;

  std::vector<std::shared_ptr<Layer>> benchmark_layers;
  auto capture_root_layer = [&benchmark_layers](Dart_NativeArguments args) {
    auto handle = Dart_GetNativeArgument(args, 0);
    intptr_t peer = 0;
    Dart_Handle result = Dart_GetNativeInstanceField(
        handle, tonic::DartWrappable::kPeerIndex, &peer);
    ASSERT_FALSE(Dart_IsError(result));
    SceneBuilder* scene_builder = reinterpret_cast<SceneBuilder*>(peer);
    ASSERT_TRUE(scene_builder);
    std::shared_ptr<ContainerLayer> root_layer =
        scene_builder->layer_stack()[0];
    ASSERT_TRUE(root_layer);
    benchmark_layers = root_layer->layers();
  };

  std::shared_ptr<PlatformMessageHandler> platform_message_handler =
      std::make_shared<MockPlatformMessageHandler>();
  EXPECT_CALL(delegate_, GetPlatformMessageHandler)
      .WillOnce(ReturnRef(platform_message_handler));
  fml::AutoResetWaitableEvent draw_latch;
  EXPECT_CALL(animator_delegate, OnAnimatorDraw)
      .WillOnce(Invoke([&draw_latch, &benchmark_layers](
                           const std::shared_ptr<FramePipeline>& pipeline) {
        auto status = pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
          EXPECT_EQ(item->layer_tree_tasks.size(), 1u);
          EXPECT_EQ(item->layer_tree_tasks[0]->view_id, kFlutterImplicitViewId);
          ContainerLayer* root_layer = reinterpret_cast<ContainerLayer*>(
              item->layer_tree_tasks[0]->layer_tree->root_layer());
          std::vector<std::shared_ptr<Layer>> result_layers =
              root_layer->layers();
          EXPECT_EQ(result_layers.size(), benchmark_layers.size());
          EXPECT_EQ(result_layers[0], benchmark_layers[0]);
        });
        EXPECT_EQ(status, PipelineConsumeResult::Done);
        draw_latch.Signal();
      }));
  EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
      .WillOnce(Invoke([&engine_context](fml::TimePoint frame_target_time,
                                         uint64_t frame_number) {
        engine_context->EngineTaskSync([&](Engine& engine) {
          engine.BeginFrame(frame_target_time, frame_number);
        });
      }));

  AddNativeCallback("CaptureRootLayer",
                    CREATE_NATIVE_ENTRY(capture_root_layer));

  std::unique_ptr<Animator> animator;
  PostSync(task_runners_.GetUITaskRunner(),
           [&animator, &animator_delegate, &task_runners = task_runners_] {
             animator = std::make_unique<Animator>(
                 animator_delegate, task_runners,
                 static_cast<std::unique_ptr<VsyncWaiter>>(
                     std::make_unique<testing::ConstantFiringVsyncWaiter>(
                         task_runners)));
           });

  engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
                                         std::move(animator));

  engine_context->EngineTaskSync([](Engine& engine) {
    engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
  });

  auto configuration = RunConfiguration::InferFromSettings(settings_);
  configuration.SetEntrypoint("renderTwiceForOneView");
  engine_context->Run(std::move(configuration));

  draw_latch.Wait();
}

TEST_F(EngineAnimatorTest, AnimatorSubmitsImplicitViewBeforeDrawFrameEnds) {
  MockAnimatorDelegate animator_delegate;
  std::unique_ptr<EngineContext> engine_context;

  std::shared_ptr<PlatformMessageHandler> platform_message_handler =
      std::make_shared<MockPlatformMessageHandler>();
  EXPECT_CALL(delegate_, GetPlatformMessageHandler)
      .WillOnce(ReturnRef(platform_message_handler));

  bool rasterization_started = false;
  EXPECT_CALL(animator_delegate, OnAnimatorDraw)
      .WillOnce(Invoke([&rasterization_started](
                           const std::shared_ptr<FramePipeline>& pipeline) {
        rasterization_started = true;
        auto status = pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
          EXPECT_EQ(item->layer_tree_tasks.size(), 1u);
          EXPECT_EQ(item->layer_tree_tasks[0]->view_id, kFlutterImplicitViewId);
        });
        EXPECT_EQ(status, PipelineConsumeResult::Done);
      }));
  EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
      .WillRepeatedly(Invoke([&engine_context](fml::TimePoint frame_target_time,
                                               uint64_t frame_number) {
        engine_context->EngineTaskSync([&](Engine& engine) {
          engine.BeginFrame(frame_target_time, frame_number);
        });
      }));

  std::unique_ptr<Animator> animator;
  PostSync(task_runners_.GetUITaskRunner(),
           [&animator, &animator_delegate, &task_runners = task_runners_] {
             animator = std::make_unique<Animator>(
                 animator_delegate, task_runners,
                 static_cast<std::unique_ptr<VsyncWaiter>>(
                     std::make_unique<testing::ConstantFiringVsyncWaiter>(
                         task_runners)));
           });

  native_latch.Reset();
  // The native_latch is signaled at the end of handleDrawFrame.
  AddNativeCallback("NotifyNative",
                    CREATE_NATIVE_ENTRY([&rasterization_started](auto args) {
                      EXPECT_EQ(rasterization_started, true);
                      native_latch.Signal();
                    }));

  engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
                                         std::move(animator));

  engine_context->EngineTaskSync([](Engine& engine) {
    engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1.0, 10, 10, 1, 0},
                   [](bool added) { ASSERT_TRUE(added); });
  });

  auto configuration = RunConfiguration::InferFromSettings(settings_);
  configuration.SetEntrypoint("renderSingleViewAndCallAfterOnDrawFrame");
  engine_context->Run(std::move(configuration));

  native_latch.Wait();
}

// The animator should submit to the pipeline the implicit view rendered in a
// warm up frame if there's already a continuation (i.e. Animator::BeginFrame
// has been called)
TEST_F(EngineAnimatorTest, AnimatorSubmitWarmUpImplicitView) {
  MockAnimatorDelegate animator_delegate;
  std::unique_ptr<EngineContext> engine_context;

  std::shared_ptr<PlatformMessageHandler> platform_message_handler =
      std::make_shared<MockPlatformMessageHandler>();
  EXPECT_CALL(delegate_, GetPlatformMessageHandler)
      .WillOnce(ReturnRef(platform_message_handler));

  fml::AutoResetWaitableEvent continuation_ready_latch;
  fml::AutoResetWaitableEvent draw_latch;
  EXPECT_CALL(animator_delegate, OnAnimatorDraw)
      .WillOnce(Invoke([&draw_latch](
                           const std::shared_ptr<FramePipeline>& pipeline) {
        auto status = pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
          EXPECT_EQ(item->layer_tree_tasks.size(), 1u);
          EXPECT_EQ(item->layer_tree_tasks[0]->view_id, kFlutterImplicitViewId);
        });
        EXPECT_EQ(status, PipelineConsumeResult::Done);
        draw_latch.Signal();
      }));
  EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
      .WillRepeatedly(
          Invoke([&engine_context, &continuation_ready_latch](
                     fml::TimePoint frame_target_time, uint64_t frame_number) {
            continuation_ready_latch.Signal();
            engine_context->EngineTaskSync([&](Engine& engine) {
              engine.BeginFrame(frame_target_time, frame_number);
            });
          }));

  std::unique_ptr<Animator> animator;
  PostSync(task_runners_.GetUITaskRunner(),
           [&animator, &animator_delegate, &task_runners = task_runners_] {
             animator = std::make_unique<Animator>(
                 animator_delegate, task_runners,
                 static_cast<std::unique_ptr<VsyncWaiter>>(
                     std::make_unique<testing::ConstantFiringVsyncWaiter>(
                         task_runners)));
           });

  engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
                                         std::move(animator));

  engine_context->EngineTaskSync([](Engine& engine) {
    // Schedule a frame to trigger Animator::BeginFrame to create a
    // continuation. The continuation needs to be available before `Engine::Run`
    // since the Dart program immediately schedules a warm up frame.
    engine.ScheduleFrame(true);
    // Add the implicit view so that the engine recognizes it and that its
    // metrics is not empty.
    engine.AddView(kFlutterImplicitViewId, ViewportMetrics{1.0, 10, 10, 1, 0},
                   [](bool added) { ASSERT_TRUE(added); });
  });
  continuation_ready_latch.Wait();

  auto configuration = RunConfiguration::InferFromSettings(settings_);
  configuration.SetEntrypoint("renderWarmUpImplicitView");
  engine_context->Run(std::move(configuration));

  draw_latch.Wait();
}

// The warm up frame should work if only some of the registered views are
// included.
//
// This test also verifies that the warm up frame can render multiple views.
TEST_F(EngineAnimatorTest, AnimatorSubmitPartialViewsForWarmUp) {
  MockAnimatorDelegate animator_delegate;
  std::unique_ptr<EngineContext> engine_context;

  std::shared_ptr<PlatformMessageHandler> platform_message_handler =
      std::make_shared<MockPlatformMessageHandler>();
  EXPECT_CALL(delegate_, GetPlatformMessageHandler)
      .WillOnce(ReturnRef(platform_message_handler));

  fml::AutoResetWaitableEvent continuation_ready_latch;
  fml::AutoResetWaitableEvent draw_latch;
  EXPECT_CALL(animator_delegate, OnAnimatorDraw)
      .WillOnce(
          Invoke([&draw_latch](const std::shared_ptr<FramePipeline>& pipeline) {
            auto status =
                pipeline->Consume([&](std::unique_ptr<FrameItem> item) {
                  auto tasks = Sorted(item->layer_tree_tasks);
                  EXPECT_EQ(tasks.size(), 2u);
                  EXPECT_EQ(tasks[0]->view_id, 1);
                  EXPECT_EQ(tasks[1]->view_id, 2);
                });
            EXPECT_EQ(status, PipelineConsumeResult::Done);
            draw_latch.Signal();
          }));
  EXPECT_CALL(animator_delegate, OnAnimatorBeginFrame)
      .WillRepeatedly(
          Invoke([&engine_context, &continuation_ready_latch](
                     fml::TimePoint frame_target_time, uint64_t frame_number) {
            continuation_ready_latch.Signal();
            engine_context->EngineTaskSync([&](Engine& engine) {
              engine.BeginFrame(frame_target_time, frame_number);
            });
          }));

  std::unique_ptr<Animator> animator;
  PostSync(task_runners_.GetUITaskRunner(),
           [&animator, &animator_delegate, &task_runners = task_runners_] {
             animator = std::make_unique<Animator>(
                 animator_delegate, task_runners,
                 static_cast<std::unique_ptr<VsyncWaiter>>(
                     std::make_unique<testing::ConstantFiringVsyncWaiter>(
                         task_runners)));
           });

  engine_context = EngineContext::Create(delegate_, settings_, task_runners_,
                                         std::move(animator));

  engine_context->EngineTaskSync([](Engine& engine) {
    // Schedule a frame to make the animator create a continuation.
    engine.ScheduleFrame(true);
    // Add multiple views.
    engine.AddView(0, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
    engine.AddView(1, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
    engine.AddView(2, ViewportMetrics{1, 10, 10, 22, 0},
                   [](bool added) { ASSERT_TRUE(added); });
  });

  continuation_ready_latch.Wait();

  auto configuration = RunConfiguration::InferFromSettings(settings_);
  configuration.SetEntrypoint("renderWarmUpView1and2");
  engine_context->Run(std::move(configuration));

  draw_latch.Wait();
}

}  // namespace flutter

// NOLINTEND(clang-analyzer-core.StackAddressEscape)
