Flutter Linux Embedder
fl_task_runner_test.cc
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "gtest/gtest.h"
6 
7 #include "flutter/shell/platform/embedder/test_utils/proc_table_replacement.h"
10 #include "flutter/shell/platform/linux/testing/linux_test.h"
11 
12 #include <vector>
13 
14 // MOCK_ENGINE_PROC is leaky by design.
15 // NOLINTBEGIN(clang-analyzer-core.StackAddressEscape)
16 
17 // Test fixture that provides an engine (with a real binary messenger) and a
18 // main loop via the shared LinuxTest base.
19 class FlTaskRunnerTest : public flutter::testing::LinuxTest {};
20 
21 // Builds a FlutterTask carrying the given identifier. The runner pointer is
22 // unused as the engine's RunTask is mocked out in these tests.
23 static FlutterTask make_task(uint64_t id) {
24  FlutterTask task = {};
25  task.runner = nullptr;
26  task.task = id;
27  return task;
28 }
29 
30 // The default maximum time pump_main_loop_until waits for its predicate to
31 // become true before giving up.
32 static constexpr gint64 kDefaultPumpTimeoutMicros = 10 * G_USEC_PER_SEC;
33 
34 // Iterates the default main context until either the timeout elapses or the
35 // predicate returns true.
36 template <typename Predicate>
37 static void pump_main_loop_until(Predicate predicate,
38  gint64 timeout = kDefaultPumpTimeoutMicros) {
39  gint64 deadline = g_get_monotonic_time() + timeout;
40  while (!predicate() && g_get_monotonic_time() < deadline) {
41  g_main_context_iteration(nullptr, TRUE);
42  }
43 }
44 
45 // A posted task is executed on the main loop and forwarded to the engine.
46 TEST_F(FlTaskRunnerTest, PostTaskExecutedOnMainLoop) {
47  g_autoptr(FlTaskRunner) task_runner = fl_task_runner_new(engine);
48 
49  std::vector<uint64_t> executed;
50  fl_engine_get_embedder_api(engine)->RunTask = MOCK_ENGINE_PROC(
51  RunTask, ([&executed](auto engine, const FlutterTask* task) {
52  executed.push_back(task->task);
53  return kSuccess;
54  }));
55 
56  // A target time in the past means the task is due immediately.
57  fl_task_runner_post_flutter_task(task_runner, make_task(42), 0);
58 
59  pump_main_loop_until([&executed]() { return !executed.empty(); });
60 
61  ASSERT_EQ(executed.size(), 1u);
62  EXPECT_EQ(executed[0], 42u);
63 }
64 
65 // Multiple posted tasks are all executed.
66 TEST_F(FlTaskRunnerTest, MultipleTasksExecuted) {
67  g_autoptr(FlTaskRunner) task_runner = fl_task_runner_new(engine);
68 
69  std::vector<uint64_t> executed;
70  fl_engine_get_embedder_api(engine)->RunTask = MOCK_ENGINE_PROC(
71  RunTask, ([&executed](auto engine, const FlutterTask* task) {
72  executed.push_back(task->task);
73  return kSuccess;
74  }));
75 
76  fl_task_runner_post_flutter_task(task_runner, make_task(1), 0);
77  fl_task_runner_post_flutter_task(task_runner, make_task(2), 0);
78  fl_task_runner_post_flutter_task(task_runner, make_task(3), 0);
79 
80  pump_main_loop_until([&executed]() { return executed.size() >= 3; });
81 
82  ASSERT_EQ(executed.size(), 3u);
83  EXPECT_EQ(executed[0], 1u);
84  EXPECT_EQ(executed[1], 2u);
85  EXPECT_EQ(executed[2], 3u);
86 }
87 
88 // A task whose target time has not yet arrived is still eventually executed.
89 TEST_F(FlTaskRunnerTest, DelayedTaskExecuted) {
90  g_autoptr(FlTaskRunner) task_runner = fl_task_runner_new(engine);
91 
92  gboolean executed = FALSE;
93  fl_engine_get_embedder_api(engine)->RunTask = MOCK_ENGINE_PROC(
94  RunTask, ([&executed](auto engine, const FlutterTask* task) {
95  executed = TRUE;
96  return kSuccess;
97  }));
98 
99  // Schedule roughly 20ms into the future (target time is in nanoseconds).
100  uint64_t target_time_nanos =
101  (g_get_monotonic_time() + 20 * G_TIME_SPAN_MILLISECOND) * 1000;
103  target_time_nanos);
104 
105  // Not run before its time.
106  EXPECT_FALSE(executed);
107 
108  pump_main_loop_until([&executed]() { return executed; });
109 
110  EXPECT_TRUE(executed);
111 }
112 
113 // fl_task_runner_wait runs a task that is already due without needing the main
114 // loop.
115 TEST_F(FlTaskRunnerTest, WaitRunsExpiredTask) {
116  g_autoptr(FlTaskRunner) task_runner = fl_task_runner_new(engine);
117 
118  std::vector<uint64_t> executed;
119  fl_engine_get_embedder_api(engine)->RunTask = MOCK_ENGINE_PROC(
120  RunTask, ([&executed](auto engine, const FlutterTask* task) {
121  executed.push_back(task->task);
122  return kSuccess;
123  }));
124 
125  fl_task_runner_post_flutter_task(task_runner, make_task(99), 0);
126 
127  // The task is already due, so wait processes it and returns immediately.
128  fl_task_runner_wait(task_runner, g_get_monotonic_time() + G_USEC_PER_SEC);
129 
130  ASSERT_EQ(executed.size(), 1u);
131  EXPECT_EQ(executed[0], 99u);
132 }
133 
134 struct StopWaitData {
135  FlTaskRunner* task_runner;
136  gint done;
137 };
138 
139 // Repeatedly interrupts fl_task_runner_wait until the main thread signals that
140 // the wait has returned. Repeating guards against the interrupt arriving before
141 // the wait has started blocking.
142 static gpointer stop_wait_thread(gpointer user_data) {
143  StopWaitData* data = static_cast<StopWaitData*>(user_data);
144  while (g_atomic_int_get(&data->done) == 0) {
145  g_usleep(10 * G_TIME_SPAN_MILLISECOND);
147  }
148  return nullptr;
149 }
150 
151 // fl_task_runner_stop_wait causes a blocking fl_task_runner_wait to return.
152 TEST_F(FlTaskRunnerTest, StopWaitInterruptsWait) {
153  g_autoptr(FlTaskRunner) task_runner = fl_task_runner_new(engine);
154 
155  StopWaitData data = {task_runner, 0};
156  GThread* thread = g_thread_new("stop-wait", stop_wait_thread, &data);
157 
158  // With no tasks scheduled this would otherwise block until the far-future
159  // expiry time.
160  gint64 start = g_get_monotonic_time();
161  fl_task_runner_wait(task_runner, start + 30 * G_USEC_PER_SEC);
162  gint64 elapsed = g_get_monotonic_time() - start;
163 
164  g_atomic_int_set(&data.done, 1);
165  g_thread_join(thread);
166 
167  // The wait returned because of stop_wait, well before the 30s expiry.
168  EXPECT_LT(elapsed, 5 * G_USEC_PER_SEC);
169 }
170 
171 // Calling fl_task_runner_stop_wait when no wait is in progress is a no-op: the
172 // runner still works and a later wait runs a due task (the stray signal is not
173 // "remembered" to interrupt the subsequent wait).
174 TEST_F(FlTaskRunnerTest, StopWaitWithoutWaitIsNoOp) {
175  g_autoptr(FlTaskRunner) task_runner = fl_task_runner_new(engine);
176 
177  std::vector<uint64_t> executed;
178  fl_engine_get_embedder_api(engine)->RunTask = MOCK_ENGINE_PROC(
179  RunTask, ([&executed](auto engine, const FlutterTask* task) {
180  executed.push_back(task->task);
181  return kSuccess;
182  }));
183 
184  // No wait is in progress, so this should do nothing.
185  fl_task_runner_stop_wait(task_runner);
186 
187  // The runner is still functional: a due task is run by a subsequent wait.
188  fl_task_runner_post_flutter_task(task_runner, make_task(123), 0);
189  fl_task_runner_wait(task_runner, g_get_monotonic_time() + G_USEC_PER_SEC);
190 
191  ASSERT_EQ(executed.size(), 1u);
192  EXPECT_EQ(executed[0], 123u);
193 }
194 
195 // NOLINTEND(clang-analyzer-core.StackAddressEscape)
g_autoptr(FlEngine) engine
const char FlTextDirection FlAssertiveness gpointer user_data
return TRUE
fl_task_runner_stop_wait(self->task_runner)
FlutterEngineProcTable * fl_engine_get_embedder_api(FlEngine *self)
Definition: fl_engine.cc:920
int64_t id
void fl_task_runner_post_flutter_task(FlTaskRunner *self, FlutterTask task, uint64_t target_time_nanos)
void fl_task_runner_wait(FlTaskRunner *self, gint64 expiry_time)
FlTaskRunner * fl_task_runner_new(FlEngine *engine)
static void pump_main_loop_until(Predicate predicate, gint64 timeout=kDefaultPumpTimeoutMicros)
TEST_F(FlTaskRunnerTest, PostTaskExecutedOnMainLoop)
static FlutterTask make_task(uint64_t id)
static constexpr gint64 kDefaultPumpTimeoutMicros
static gpointer stop_wait_thread(gpointer user_data)
FlTaskRunner * task_runner