mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
This removes most of the remaining FLUTTER_NOLINT comments and opts these files back into linter enforcement. I've filed https://github.com/flutter/flutter/issues/68273 to require that all FLUTTER_NOLINT comments be followed by a GitHub issue URL describing the problem to be fixed.
417 lines
18 KiB
C++
417 lines
18 KiB
C++
// 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/shell_test.h"
|
|
#include "flutter/testing/testing.h"
|
|
|
|
namespace flutter {
|
|
namespace testing {
|
|
|
|
// Throughout these tests, the choice of time unit is irrelevant as long as all
|
|
// times have the same units.
|
|
using UnitlessTime = int;
|
|
|
|
// Signature of a generator function that takes the frame index as input and
|
|
// returns the time of that frame.
|
|
using Generator = std::function<UnitlessTime(int)>;
|
|
|
|
//----------------------------------------------------------------------------
|
|
/// Simulate n input events where the i-th one is delivered at delivery_time(i).
|
|
///
|
|
/// Simulation results will be written into events_consumed_at_frame whose
|
|
/// length will be equal to the number of frames drawn. Each element in the
|
|
/// vector is the number of input events consumed up to that frame. (We can't
|
|
/// return such vector because ASSERT_TRUE requires return type of void.)
|
|
///
|
|
/// We assume (and check) that the delivery latency is some base latency plus a
|
|
/// random latency where the random latency must be within one frame:
|
|
///
|
|
/// 1. latency = delivery_time(i) - j * frame_time = base_latency +
|
|
/// random_latency
|
|
/// 2. 0 <= base_latency, 0 <= random_latency < frame_time
|
|
///
|
|
/// We also assume that there will be at least one input event per frame if
|
|
/// there were no latency. Let j = floor( (delivery_time(i) - base_latency) /
|
|
/// frame_time ) be the frame index if there were no latency. Then the set of j
|
|
/// should be all integers from 0 to continuous_frame_count - 1 for some
|
|
/// integer continuous_frame_count.
|
|
///
|
|
/// (Note that there coulds be multiple input events within one frame.)
|
|
///
|
|
/// The test here is insensitive to the choice of time unit as long as
|
|
/// delivery_time and frame_time are in the same unit.
|
|
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) {
|
|
///// Begin constructing shell ///////////////////////////////////////////////
|
|
auto settings = fixture->CreateSettingsForFixture();
|
|
std::unique_ptr<Shell> shell = fixture->CreateShell(settings, true);
|
|
|
|
auto configuration = RunConfiguration::InferFromSettings(settings);
|
|
configuration.SetEntrypoint("onPointerDataPacketMain");
|
|
|
|
// The following 4 variables are only accessed in the UI thread by
|
|
// nativeOnPointerDataPacket and nativeOnBeginFrame between their
|
|
// initializations and `shell.reset()`.
|
|
events_consumed_at_frame.clear();
|
|
bool will_draw_new_frame = true;
|
|
int events_consumed = 0;
|
|
int frame_drawn = 0;
|
|
auto nativeOnPointerDataPacket = [&events_consumed_at_frame,
|
|
&will_draw_new_frame, &events_consumed,
|
|
&frame_drawn](Dart_NativeArguments args) {
|
|
events_consumed += 1;
|
|
if (will_draw_new_frame) {
|
|
frame_drawn += 1;
|
|
will_draw_new_frame = false;
|
|
events_consumed_at_frame.push_back(events_consumed);
|
|
} else {
|
|
events_consumed_at_frame.back() = events_consumed;
|
|
}
|
|
};
|
|
fixture->AddNativeCallback("NativeOnPointerDataPacket",
|
|
CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
|
|
|
|
ASSERT_TRUE(configuration.IsValid());
|
|
fixture->RunEngine(shell.get(), std::move(configuration));
|
|
|
|
if (restart_engine) {
|
|
auto new_configuration = RunConfiguration::InferFromSettings(settings);
|
|
new_configuration.SetEntrypoint("onPointerDataPacketMain");
|
|
ASSERT_TRUE(new_configuration.IsValid());
|
|
fixture->RestartEngine(shell.get(), std::move(new_configuration));
|
|
}
|
|
///// End constructing shell /////////////////////////////////////////////////
|
|
|
|
ASSERT_GE(base_latency, 0);
|
|
|
|
// Check that delivery_time satisfies our assumptions.
|
|
int continuous_frame_count = 0;
|
|
for (int i = 0; i < num_events; i += 1) {
|
|
// j is the frame index of event i if there were no latency.
|
|
int j = static_cast<int>((delivery_time(i) - base_latency) / frame_time);
|
|
if (j == continuous_frame_count) {
|
|
continuous_frame_count += 1;
|
|
}
|
|
double random_latency = delivery_time(i) - j * frame_time - base_latency;
|
|
ASSERT_GE(random_latency, 0);
|
|
ASSERT_LT(random_latency, frame_time);
|
|
|
|
// If there were no latency, there should be at least one event per frame.
|
|
// Hence j should never skip any integer less than continuous_frame_count.
|
|
ASSERT_LT(j, continuous_frame_count);
|
|
}
|
|
|
|
// This has to be running on a different thread than Platform thread to avoid
|
|
// dead locks.
|
|
auto simulation = std::async(std::launch::async, [&]() {
|
|
// i is the input event's index.
|
|
// j is the frame's index.
|
|
for (int i = 0, j = 0; i < num_events; j += 1) {
|
|
double t = j * frame_time;
|
|
while (i < num_events && delivery_time(i) <= t) {
|
|
ShellTest::DispatchFakePointerData(shell.get());
|
|
i += 1;
|
|
}
|
|
ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
|
|
}
|
|
// Finally, issue a vsync for the pending event that may be generated duing
|
|
// the last vsync.
|
|
ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
|
|
});
|
|
|
|
simulation.wait();
|
|
|
|
TaskRunners task_runners = fixture->GetTaskRunnersForFixture();
|
|
fml::AutoResetWaitableEvent latch;
|
|
task_runners.GetPlatformTaskRunner()->PostTask([&shell, &latch]() mutable {
|
|
shell.reset();
|
|
latch.Signal();
|
|
});
|
|
latch.Wait();
|
|
|
|
// Make sure that all events have been consumed so
|
|
// https://github.com/flutter/flutter/issues/40863 won't happen again.
|
|
ASSERT_EQ(events_consumed_at_frame.back(), num_events);
|
|
}
|
|
|
|
void CreateSimulatedPointerData(PointerData& data,
|
|
PointerData::Change change,
|
|
double dx,
|
|
double dy) {
|
|
data.time_stamp = 0;
|
|
data.change = change;
|
|
data.kind = PointerData::DeviceKind::kTouch;
|
|
data.signal_kind = PointerData::SignalKind::kNone;
|
|
data.device = 0;
|
|
data.pointer_identifier = 0;
|
|
data.physical_x = dx;
|
|
data.physical_y = dy;
|
|
data.physical_delta_x = 0.0;
|
|
data.physical_delta_y = 0.0;
|
|
data.buttons = 0;
|
|
data.obscured = 0;
|
|
data.synthesized = 0;
|
|
data.pressure = 0.0;
|
|
data.pressure_min = 0.0;
|
|
data.pressure_max = 0.0;
|
|
data.distance = 0.0;
|
|
data.distance_max = 0.0;
|
|
data.size = 0.0;
|
|
data.radius_major = 0.0;
|
|
data.radius_minor = 0.0;
|
|
data.radius_min = 0.0;
|
|
data.radius_max = 0.0;
|
|
data.orientation = 0.0;
|
|
data.tilt = 0.0;
|
|
data.platformData = 0;
|
|
data.scroll_delta_x = 0.0;
|
|
data.scroll_delta_y = 0.0;
|
|
}
|
|
|
|
TEST_F(ShellTest, MissAtMostOneFrameForIrregularInputEvents) {
|
|
// We don't use `constexpr int frame_time` here because MSVC doesn't handle
|
|
// it well with lambda capture.
|
|
UnitlessTime frame_time = 10;
|
|
UnitlessTime base_latency = 0.5 * frame_time;
|
|
Generator extreme = [frame_time, base_latency](int i) {
|
|
return static_cast<UnitlessTime>(
|
|
i * frame_time + base_latency +
|
|
(i % 2 == 0 ? 0.1 * frame_time : 0.9 * frame_time));
|
|
};
|
|
constexpr int n = 40;
|
|
std::vector<int> events_consumed_at_frame;
|
|
TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
|
|
events_consumed_at_frame);
|
|
int frame_drawn = events_consumed_at_frame.size();
|
|
ASSERT_GE(frame_drawn, n - 1);
|
|
|
|
// Make sure that it also works after an engine restart.
|
|
TestSimulatedInputEvents(this, n, base_latency, extreme, frame_time,
|
|
events_consumed_at_frame, true /* restart_engine */);
|
|
int frame_drawn_after_restart = events_consumed_at_frame.size();
|
|
ASSERT_GE(frame_drawn_after_restart, n - 1);
|
|
}
|
|
|
|
TEST_F(ShellTest, DelayAtMostOneEventForFasterThanVSyncInputEvents) {
|
|
// We don't use `constexpr int frame_time` here because MSVC doesn't handle
|
|
// it well with lambda capture.
|
|
UnitlessTime frame_time = 10;
|
|
UnitlessTime base_latency = 0.2 * frame_time;
|
|
Generator double_sampling = [frame_time, base_latency](int i) {
|
|
return static_cast<UnitlessTime>(i * 0.5 * frame_time + base_latency);
|
|
};
|
|
constexpr int n = 40;
|
|
std::vector<int> events_consumed_at_frame;
|
|
TestSimulatedInputEvents(this, n, base_latency, double_sampling, frame_time,
|
|
events_consumed_at_frame);
|
|
|
|
// Draw one extra frame due to delaying a pending packet for the next frame.
|
|
int frame_drawn = events_consumed_at_frame.size();
|
|
ASSERT_EQ(frame_drawn, n / 2 + 1);
|
|
|
|
for (int i = 0; i < n / 2; i += 1) {
|
|
ASSERT_GE(events_consumed_at_frame[i], 2 * i - 1);
|
|
}
|
|
}
|
|
|
|
TEST_F(ShellTest, HandlesActualIphoneXsInputEvents) {
|
|
// Actual delivery times measured on iPhone Xs, in the unit of frame_time
|
|
// (16.67ms for 60Hz).
|
|
static constexpr double iphone_xs_times[] = {0.15,
|
|
1.0773046874999999,
|
|
2.1738720703124996,
|
|
3.0579052734374996,
|
|
4.0890087890624995,
|
|
5.0952685546875,
|
|
6.1251708984375,
|
|
7.1253076171875,
|
|
8.125927734374999,
|
|
9.37248046875,
|
|
10.133950195312499,
|
|
11.161201171875,
|
|
12.226992187499999,
|
|
13.1443798828125,
|
|
14.440327148437499,
|
|
15.091684570312498,
|
|
16.138681640625,
|
|
17.126469726562497,
|
|
18.1592431640625,
|
|
19.371372070312496,
|
|
20.033774414062496,
|
|
21.021782226562497,
|
|
22.070053710937497,
|
|
23.325541992187496,
|
|
24.119648437499997,
|
|
25.084262695312496,
|
|
26.077866210937497,
|
|
27.036547851562496,
|
|
28.035073242187497,
|
|
29.081411132812498,
|
|
30.066064453124998,
|
|
31.089360351562497,
|
|
32.086142578125,
|
|
33.4618798828125,
|
|
34.14697265624999,
|
|
35.0513525390625,
|
|
36.136025390624994,
|
|
37.1618408203125,
|
|
38.144472656249995,
|
|
39.201123046875,
|
|
40.4339501953125,
|
|
41.1552099609375,
|
|
42.102128906249995,
|
|
43.0426318359375,
|
|
44.070131835937495,
|
|
45.08862304687499,
|
|
46.091469726562494};
|
|
constexpr int n = sizeof(iphone_xs_times) / sizeof(iphone_xs_times[0]);
|
|
// We don't use `constexpr int frame_time` here because MSVC doesn't handle
|
|
// it well with lambda capture.
|
|
UnitlessTime frame_time = 10000;
|
|
for (double base_latency_f = 0; base_latency_f < 1; base_latency_f += 0.1) {
|
|
// Everything is converted to int to avoid floating point error in
|
|
// TestSimulatedInputEvents.
|
|
UnitlessTime base_latency =
|
|
static_cast<UnitlessTime>(base_latency_f * frame_time);
|
|
Generator iphone_xs_generator = [frame_time, base_latency](int i) {
|
|
return base_latency +
|
|
static_cast<UnitlessTime>(iphone_xs_times[i] * frame_time);
|
|
};
|
|
std::vector<int> events_consumed_at_frame;
|
|
TestSimulatedInputEvents(this, n, base_latency, iphone_xs_generator,
|
|
frame_time, events_consumed_at_frame);
|
|
int frame_drawn = events_consumed_at_frame.size();
|
|
ASSERT_GE(frame_drawn, n - 1);
|
|
}
|
|
}
|
|
|
|
TEST_F(ShellTest, CanCorrectlyPipePointerPacket) {
|
|
// Sets up shell with test fixture.
|
|
auto settings = CreateSettingsForFixture();
|
|
std::unique_ptr<Shell> shell = CreateShell(settings, true);
|
|
|
|
auto configuration = RunConfiguration::InferFromSettings(settings);
|
|
configuration.SetEntrypoint("onPointerDataPacketMain");
|
|
// Sets up native handler.
|
|
fml::AutoResetWaitableEvent reportLatch;
|
|
std::vector<int64_t> result_sequence;
|
|
auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
|
|
Dart_NativeArguments args) {
|
|
Dart_Handle exception = nullptr;
|
|
result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
|
|
args, 0, exception);
|
|
reportLatch.Signal();
|
|
};
|
|
// Starts engine.
|
|
AddNativeCallback("NativeOnPointerDataPacket",
|
|
CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
|
|
ASSERT_TRUE(configuration.IsValid());
|
|
RunEngine(shell.get(), std::move(configuration));
|
|
// Starts test.
|
|
auto packet = std::make_unique<PointerDataPacket>(6);
|
|
PointerData data;
|
|
CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0.0, 0.0);
|
|
packet->SetPointerData(0, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kHover, 3.0, 0.0);
|
|
packet->SetPointerData(1, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kDown, 3.0, 0.0);
|
|
packet->SetPointerData(2, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kMove, 3.0, 4.0);
|
|
packet->SetPointerData(3, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kUp, 3.0, 4.0);
|
|
packet->SetPointerData(4, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kRemove, 3.0, 4.0);
|
|
packet->SetPointerData(5, data);
|
|
ShellTest::DispatchPointerData(shell.get(), std::move(packet));
|
|
bool will_draw_new_frame;
|
|
ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
|
|
|
|
reportLatch.Wait();
|
|
size_t expect_length = 6;
|
|
ASSERT_EQ(result_sequence.size(), expect_length);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[1]),
|
|
PointerData::Change::kHover);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[2]),
|
|
PointerData::Change::kDown);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[3]),
|
|
PointerData::Change::kMove);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[5]),
|
|
PointerData::Change::kRemove);
|
|
|
|
// Cleans up shell.
|
|
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
|
DestroyShell(std::move(shell));
|
|
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
|
|
}
|
|
|
|
TEST_F(ShellTest, CanCorrectlySynthesizePointerPacket) {
|
|
// Sets up shell with test fixture.
|
|
auto settings = CreateSettingsForFixture();
|
|
std::unique_ptr<Shell> shell = CreateShell(settings, true);
|
|
|
|
auto configuration = RunConfiguration::InferFromSettings(settings);
|
|
configuration.SetEntrypoint("onPointerDataPacketMain");
|
|
// Sets up native handler.
|
|
fml::AutoResetWaitableEvent reportLatch;
|
|
std::vector<int64_t> result_sequence;
|
|
auto nativeOnPointerDataPacket = [&reportLatch, &result_sequence](
|
|
Dart_NativeArguments args) {
|
|
Dart_Handle exception = nullptr;
|
|
result_sequence = tonic::DartConverter<std::vector<int64_t>>::FromArguments(
|
|
args, 0, exception);
|
|
reportLatch.Signal();
|
|
};
|
|
// Starts engine.
|
|
AddNativeCallback("NativeOnPointerDataPacket",
|
|
CREATE_NATIVE_ENTRY(nativeOnPointerDataPacket));
|
|
ASSERT_TRUE(configuration.IsValid());
|
|
RunEngine(shell.get(), std::move(configuration));
|
|
// Starts test.
|
|
auto packet = std::make_unique<PointerDataPacket>(4);
|
|
PointerData data;
|
|
CreateSimulatedPointerData(data, PointerData::Change::kAdd, 0.0, 0.0);
|
|
packet->SetPointerData(0, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kDown, 3.0, 0.0);
|
|
packet->SetPointerData(1, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kUp, 3.0, 4.0);
|
|
packet->SetPointerData(2, data);
|
|
CreateSimulatedPointerData(data, PointerData::Change::kRemove, 3.0, 4.0);
|
|
packet->SetPointerData(3, data);
|
|
ShellTest::DispatchPointerData(shell.get(), std::move(packet));
|
|
bool will_draw_new_frame;
|
|
ShellTest::VSyncFlush(shell.get(), will_draw_new_frame);
|
|
|
|
reportLatch.Wait();
|
|
size_t expect_length = 6;
|
|
ASSERT_EQ(result_sequence.size(), expect_length);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[0]), PointerData::Change::kAdd);
|
|
// The pointer data packet converter should synthesize a hover event.
|
|
ASSERT_EQ(PointerData::Change(result_sequence[1]),
|
|
PointerData::Change::kHover);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[2]),
|
|
PointerData::Change::kDown);
|
|
// The pointer data packet converter should synthesize a move event.
|
|
ASSERT_EQ(PointerData::Change(result_sequence[3]),
|
|
PointerData::Change::kMove);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[4]), PointerData::Change::kUp);
|
|
ASSERT_EQ(PointerData::Change(result_sequence[5]),
|
|
PointerData::Change::kRemove);
|
|
|
|
// Cleans up shell.
|
|
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
|
|
DestroyShell(std::move(shell));
|
|
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
|
|
}
|
|
|
|
} // namespace testing
|
|
} // namespace flutter
|