Flutter Engine
input_events_unittests.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 "flutter/shell/common/shell_test.h"
6 #include "flutter/testing/testing.h"
7 
8 namespace flutter {
9 namespace testing {
10 
11 // Throughout these tests, the choice of time unit is irrelevant as long as all
12 // times have the same units.
13 using UnitlessTime = int;
14 
15 // Signature of a generator function that takes the frame index as input and
16 // returns the time of that frame.
17 using Generator = std::function<UnitlessTime(int)>;
18 
19 //----------------------------------------------------------------------------
20 /// Simulate n input events where the i-th one is delivered at delivery_time(i).
21 ///
22 /// Simulation results will be written into events_consumed_at_frame whose
23 /// length will be equal to the number of frames drawn. Each element in the
24 /// vector is the number of input events consumed up to that frame. (We can't
25 /// return such vector because ASSERT_TRUE requires return type of void.)
26 ///
27 /// We assume (and check) that the delivery latency is some base latency plus a
28 /// random latency where the random latency must be within one frame:
29 ///
30 /// 1. latency = delivery_time(i) - j * frame_time = base_latency +
31 /// random_latency
32 /// 2. 0 <= base_latency, 0 <= random_latency < frame_time
33 ///
34 /// We also assume that there will be at least one input event per frame if
35 /// there were no latency. Let j = floor( (delivery_time(i) - base_latency) /
36 /// frame_time ) be the frame index if there were no latency. Then the set of j
37 /// should be all integers from 0 to continuous_frame_count - 1 for some
38 /// integer continuous_frame_count.
39 ///
40 /// (Note that there coulds be multiple input events within one frame.)
41 ///
42 /// The test here is insensitive to the choice of time unit as long as
43 /// delivery_time and frame_time are in the same unit.
45  ShellTest* fixture,
46  int num_events,
47  UnitlessTime base_latency,
48  Generator delivery_time,
49  UnitlessTime frame_time,
50  std::vector<UnitlessTime>& events_consumed_at_frame,
51  bool restart_engine = false) {
52  ///// Begin constructing shell ///////////////////////////////////////////////
53  auto settings = fixture->CreateSettingsForFixture();
54  std::unique_ptr<Shell> shell = fixture->CreateShell(settings, true);
55 
56  auto configuration = RunConfiguration::InferFromSettings(settings);
57  configuration.SetEntrypoint("onPointerDataPacketMain");
58 
59  // The following 4 variables are only accessed in the UI thread by
60  // nativeOnPointerDataPacket and nativeOnBeginFrame between their
61  // initializations and `shell.reset()`.
62  events_consumed_at_frame.clear();
63  bool will_draw_new_frame = true;
64  int events_consumed = 0;
65  int frame_drawn = 0;
66  auto nativeOnPointerDataPacket = [&events_consumed_at_frame,
67  &will_draw_new_frame, &events_consumed,
68  &frame_drawn](Dart_NativeArguments args) {
69  events_consumed += 1;
70  if (will_draw_new_frame) {
71  frame_drawn += 1;
72  will_draw_new_frame = false;
73  events_consumed_at_frame.push_back(events_consumed);
74  } else {
75  events_consumed_at_frame.back() = events_consumed;
76  }
77  };
78  fixture->AddNativeCallback("NativeOnPointerDataPacket",
79  CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
80 
81  ASSERT_TRUE(configuration.IsValid());
82  fixture->RunEngine(shell.get(), std::move(configuration));
83 
84  if (restart_engine) {
85  auto new_configuration = RunConfiguration::InferFromSettings(settings);
86  new_configuration.SetEntrypoint("onPointerDataPacketMain");
87  ASSERT_TRUE(new_configuration.IsValid());
88  fixture->RestartEngine(shell.get(), std::move(new_configuration));
89  }
90  ///// End constructing shell /////////////////////////////////////////////////
91 
92  ASSERT_GE(base_latency, 0);
93 
94  // Check that delivery_time satisfies our assumptions.
95  int continuous_frame_count = 0;
96  for (int i = 0; i < num_events; i += 1) {
97  // j is the frame index of event i if there were no latency.
98  int j = static_cast<int>((delivery_time(i) - base_latency) / frame_time);
99  if (j == continuous_frame_count) {
100  continuous_frame_count += 1;
101  }
102  double random_latency = delivery_time(i) - j * frame_time - base_latency;
103  ASSERT_GE(random_latency, 0);
104  ASSERT_LT(random_latency, frame_time);
105 
106  // If there were no latency, there should be at least one event per frame.
107  // Hence j should never skip any integer less than continuous_frame_count.
108  ASSERT_LT(j, continuous_frame_count);
109  }
110 
111  // This has to be running on a different thread than Platform thread to avoid
112  // dead locks.
113  auto simulation = std::async(std::launch::async, [&]() {
114  // i is the input event's index.
115  // j is the frame's index.
116  for (int i = 0, j = 0; i < num_events; j += 1) {
117  double t = j * frame_time;
118  while (i < num_events && delivery_time(i) <= t) {
120  i += 1;
121  }
122  ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
123  }
124  // Finally, issue a vsync for the pending event that may be generated duing
125  // the last vsync.
126  ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
127  });
128 
129  simulation.wait();
130 
131  TaskRunners task_runners = fixture->GetTaskRunnersForFixture();
133  task_runners.GetPlatformTaskRunner()->PostTask([&shell, &latch]() mutable {
134  shell.reset();
135  latch.Signal();
136  });
137  latch.Wait();
138 
139  // Make sure that all events have been consumed so
140  // https://github.com/flutter/flutter/issues/40863 won't happen again.
141  ASSERT_EQ(events_consumed_at_frame.back(), num_events);
142 }
143 
145  PointerData::Change change,
146  double dx,
147  double dy) {
148  data.time_stamp = 0;
149  data.change = change;
152  data.device = 0;
153  data.pointer_identifier = 0;
154  data.physical_x = dx;
155  data.physical_y = dy;
156  data.physical_delta_x = 0.0;
157  data.physical_delta_y = 0.0;
158  data.buttons = 0;
159  data.obscured = 0;
160  data.synthesized = 0;
161  data.pressure = 0.0;
162  data.pressure_min = 0.0;
163  data.pressure_max = 0.0;
164  data.distance = 0.0;
165  data.distance_max = 0.0;
166  data.size = 0.0;
167  data.radius_major = 0.0;
168  data.radius_minor = 0.0;
169  data.radius_min = 0.0;
170  data.radius_max = 0.0;
171  data.orientation = 0.0;
172  data.tilt = 0.0;
173  data.platformData = 0;
174  data.scroll_delta_x = 0.0;
175  data.scroll_delta_y = 0.0;
176 }
177 
178 TEST_F(ShellTest, MissAtMostOneFrameForIrregularInputEvents) {
179  // We don't use `constexpr int frame_time` here because MSVC doesn't handle
180  // it well with lambda capture.
181  UnitlessTime frame_time = 10;
182  UnitlessTime base_latency = 0.5 * frame_time;
183  Generator extreme = [frame_time, base_latency](int i) {
184  return static_cast<UnitlessTime>(
185  i * frame_time + base_latency +
186  (i % 2 == 0 ? 0.1 * frame_time : 0.9 * frame_time));
187  };
188  constexpr int n = 40;
189  std::vector<int> events_consumed_at_frame;
190  TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
191  events_consumed_at_frame);
192  int frame_drawn = events_consumed_at_frame.size();
193  ASSERT_GE(frame_drawn, n - 1);
194 
195  // Make sure that it also works after an engine restart.
196  TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
197  events_consumed_at_frame, true /* restart_engine */);
198  int frame_drawn_after_restart = events_consumed_at_frame.size();
199  ASSERT_GE(frame_drawn_after_restart, n - 1);
200 }
201 
202 TEST_F(ShellTest, DelayAtMostOneEventForFasterThanVSyncInputEvents) {
203  // We don't use `constexpr int frame_time` here because MSVC doesn't handle
204  // it well with lambda capture.
205  UnitlessTime frame_time = 10;
206  UnitlessTime base_latency = 0.2 * frame_time;
207  Generator double_sampling = [frame_time, base_latency](int i) {
208  return static_cast<UnitlessTime>(i * 0.5 * frame_time + base_latency);
209  };
210  constexpr int n = 40;
211  std::vector<int> events_consumed_at_frame;
212  TestSimulatedInputEvents(this, n, base_latency, double_sampling, frame_time,
213  events_consumed_at_frame);
214 
215  // Draw one extra frame due to delaying a pending packet for the next frame.
216  int frame_drawn = events_consumed_at_frame.size();
217  ASSERT_EQ(frame_drawn, n / 2 + 1);
218 
219  for (int i = 0; i < n / 2; i += 1) {
220  ASSERT_GE(events_consumed_at_frame[i], 2 * i - 1);
221  }
222 }
223 
224 TEST_F(ShellTest, HandlesActualIphoneXsInputEvents) {
225  // Actual delivery times measured on iPhone Xs, in the unit of frame_time
226  // (16.67ms for 60Hz).
227  static constexpr double iphone_xs_times[] = {0.15,
228  1.0773046874999999,
229  2.1738720703124996,
230  3.0579052734374996,
231  4.0890087890624995,
232  5.0952685546875,
233  6.1251708984375,
234  7.1253076171875,
235  8.125927734374999,
236  9.37248046875,
237  10.133950195312499,
238  11.161201171875,
239  12.226992187499999,
240  13.1443798828125,
241  14.440327148437499,
242  15.091684570312498,
243  16.138681640625,
244  17.126469726562497,
245  18.1592431640625,
246  19.371372070312496,
247  20.033774414062496,
248  21.021782226562497,
249  22.070053710937497,
250  23.325541992187496,
251  24.119648437499997,
252  25.084262695312496,
253  26.077866210937497,
254  27.036547851562496,
255  28.035073242187497,
256  29.081411132812498,
257  30.066064453124998,
258  31.089360351562497,
259  32.086142578125,
260  33.4618798828125,
261  34.14697265624999,
262  35.0513525390625,
263  36.136025390624994,
264  37.1618408203125,
265  38.144472656249995,
266  39.201123046875,
267  40.4339501953125,
268  41.1552099609375,
269  42.102128906249995,
270  43.0426318359375,
271  44.070131835937495,
272  45.08862304687499,
273  46.091469726562494};
274  constexpr int n = sizeof(iphone_xs_times) / sizeof(iphone_xs_times[0]);
275  // We don't use `constexpr int frame_time` here because MSVC doesn't handle
276  // it well with lambda capture.
277  UnitlessTime frame_time = 10000;
278  for (double base_latency_f = 0; base_latency_f < 1; base_latency_f += 0.1) {
279  // Everything is converted to int to avoid floating point error in
280  // TestSimulatedInputEvents.
281  UnitlessTime base_latency =
282  static_cast<UnitlessTime>(base_latency_f * frame_time);
283  Generator iphone_xs_generator = [frame_time, base_latency](int i) {
284  return base_latency +
285  static_cast<UnitlessTime>(iphone_xs_times[i] * frame_time);
286  };
287  std::vector<int> events_consumed_at_frame;
288  TestSimulatedInputEvents(this, n, base_latency, iphone_xs_generator,
289  frame_time, events_consumed_at_frame);
290  int frame_drawn = events_consumed_at_frame.size();
291  ASSERT_GE(frame_drawn, n - 1);
292  }
293 }
294 
295 TEST_F(ShellTest, CanCorrectlyPipePointerPacket) {
296  // Sets up shell with test fixture.
297  auto settings = CreateSettingsForFixture();
298  std::unique_ptr<Shell> shell = CreateShell(settings, true);
299 
300  auto configuration = RunConfiguration::InferFromSettings(settings);
301  configuration.SetEntrypoint("onPointerDataPacketMain");
302  // Sets up native handler.
303  fml::AutoResetWaitableEvent reportLatch;
304  std::vector<int64_t> result_sequence;
305  auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
306  Dart_NativeArguments args) {
307  Dart_Handle exception = nullptr;
308  result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
309  args, 0, exception);
310  reportLatch.Signal();
311  };
312  // Starts engine.
313  AddNativeCallback("NativeOnPointerDataPacket",
314  CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
315  ASSERT_TRUE(configuration.IsValid());
316  RunEngine(shell.get(), std::move(configuration));
317  // Starts test.
318  auto packet = std::make_unique<PointerDataPacket>(6);
319  PointerData data;
321  packet->SetPointerData(0, data);
323  packet->SetPointerData(1, data);
325  packet->SetPointerData(2, data);
327  packet->SetPointerData(3, data);
329  packet->SetPointerData(4, data);
331  packet->SetPointerData(5, data);
332  ShellTest::DispatchPointerData(shell.get(), std::move(packet));
333  bool will_draw_new_frame;
334  ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
335 
336  reportLatch.Wait();
337  size_t expect_length = 6;
338  ASSERT_EQ(result_sequence.size(), expect_length);
339  ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
340  ASSERT_EQ(PointerData::Change(result_sequence[1]),
342  ASSERT_EQ(PointerData::Change(result_sequence[2]),
344  ASSERT_EQ(PointerData::Change(result_sequence[3]),
346  ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
347  ASSERT_EQ(PointerData::Change(result_sequence[5]),
349 
350  // Cleans up shell.
351  ASSERT_TRUE(DartVMRef::IsInstanceRunning());
352  DestroyShell(std::move(shell));
353  ASSERT_FALSE(DartVMRef::IsInstanceRunning());
354 }
355 
356 TEST_F(ShellTest, CanCorrectlySynthesizePointerPacket) {
357  // Sets up shell with test fixture.
358  auto settings = CreateSettingsForFixture();
359  std::unique_ptr<Shell> shell = CreateShell(settings, true);
360 
361  auto configuration = RunConfiguration::InferFromSettings(settings);
362  configuration.SetEntrypoint("onPointerDataPacketMain");
363  // Sets up native handler.
364  fml::AutoResetWaitableEvent reportLatch;
365  std::vector<int64_t> result_sequence;
366  auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
367  Dart_NativeArguments args) {
368  Dart_Handle exception = nullptr;
369  result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
370  args, 0, exception);
371  reportLatch.Signal();
372  };
373  // Starts engine.
374  AddNativeCallback("NativeOnPointerDataPacket",
375  CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
376  ASSERT_TRUE(configuration.IsValid());
377  RunEngine(shell.get(), std::move(configuration));
378  // Starts test.
379  auto packet = std::make_unique<PointerDataPacket>(4);
380  PointerData data;
382  packet->SetPointerData(0, data);
384  packet->SetPointerData(1, data);
386  packet->SetPointerData(2, data);
388  packet->SetPointerData(3, data);
389  ShellTest::DispatchPointerData(shell.get(), std::move(packet));
390  bool will_draw_new_frame;
391  ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
392 
393  reportLatch.Wait();
394  size_t expect_length = 6;
395  ASSERT_EQ(result_sequence.size(), expect_length);
396  ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
397  // The pointer data packet converter should synthesize a hover event.
398  ASSERT_EQ(PointerData::Change(result_sequence[1]),
400  ASSERT_EQ(PointerData::Change(result_sequence[2]),
402  // The pointer data packet converter should synthesize a move event.
403  ASSERT_EQ(PointerData::Change(result_sequence[3]),
405  ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
406  ASSERT_EQ(PointerData::Change(result_sequence[5]),
408 
409  // Cleans up shell.
410  ASSERT_TRUE(DartVMRef::IsInstanceRunning());
411  DestroyShell(std::move(shell));
412  ASSERT_FALSE(DartVMRef::IsInstanceRunning());
413 }
414 
415 } // namespace testing
416 } // namespace flutter
G_BEGIN_DECLS FlValue * args
static void DispatchPointerData(Shell *shell, std::unique_ptr< PointerDataPacket > packet)
Definition: shell_test.cc:210
SignalKind signal_kind
Definition: pointer_data.h:65
int64_t pointer_identifier
Definition: pointer_data.h:67
fml::RefPtr< fml::TaskRunner > GetPlatformTaskRunner() const
Definition: task_runners.cc:30
virtual void PostTask(const fml::closure &task)
Definition: task_runner.cc:24
static bool IsInstanceRunning()
static void TestSimulatedInputEvents(ShellTest *fixture, int num_events, UnitlessTime base_latency, Generator delivery_time, UnitlessTime frame_time, std::vector< UnitlessTime > &events_consumed_at_frame, bool restart_engine=false)
static RunConfiguration InferFromSettings(const Settings &settings, fml::RefPtr< fml::TaskRunner > io_worker=nullptr)
Attempts to infer a run configuration from the settings object. This tries to create a run configurat...
static void DispatchFakePointerData(Shell *shell)
Definition: shell_test.cc:205
#define CREATE_NATIVE_ENTRY(native_entry)
Settings CreateSettingsForFixture() override
Definition: shell_test.cc:273
TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies)
void CreateSimulatedPointerData(PointerData &data, PointerData::Change change, int64_t device, double dx, double dy)
TaskRunners GetTaskRunnersForFixture()
Definition: shell_test.cc:292
std::function< UnitlessTime(int)> Generator
static void RestartEngine(Shell *shell, RunConfiguration configuration)
Definition: shell_test.cc:76
static void VSyncFlush(Shell *shell, bool &will_draw_new_frame)
Definition: shell_test.cc:86
void AddNativeCallback(std::string name, Dart_NativeFunction callback)
Definition: fixture_test.cc:58
static void RunEngine(Shell *shell, RunConfiguration configuration)
Definition: shell_test.cc:62
std::unique_ptr< Shell > CreateShell(Settings settings, bool simulate_vsync=false)
Definition: shell_test.cc:306