Flutter Engine
persistent_cache_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/persistent_cache.h"
6 
7 #include <memory>
8 
9 #include "flutter/assets/directory_asset_bundle.h"
10 #include "flutter/flow/layers/container_layer.h"
11 #include "flutter/flow/layers/layer.h"
12 #include "flutter/flow/layers/physical_shape_layer.h"
13 #include "flutter/flow/layers/picture_layer.h"
14 #include "flutter/fml/command_line.h"
15 #include "flutter/fml/file.h"
16 #include "flutter/fml/log_settings.h"
17 #include "flutter/fml/unique_fd.h"
18 #include "flutter/shell/common/shell_test.h"
19 #include "flutter/shell/common/switches.h"
20 #include "flutter/shell/version/version.h"
21 #include "flutter/testing/testing.h"
22 #include "include/core/SkPicture.h"
23 
24 namespace flutter {
25 namespace testing {
26 
27 static void WaitForIO(Shell* shell) {
28  std::promise<bool> io_task_finished;
30  [&io_task_finished]() { io_task_finished.set_value(true); });
31  io_task_finished.get_future().wait();
32 }
33 
34 TEST_F(ShellTest, CacheSkSLWorks) {
35  // Create a temp dir to store the persistent cache
39 
40  auto settings = CreateSettingsForFixture();
41  settings.cache_sksl = true;
42  settings.dump_skp_on_shader_compilation = true;
43 
44  fml::AutoResetWaitableEvent firstFrameLatch;
45  settings.frame_rasterized_callback =
46  [&firstFrameLatch](const FrameTiming& t) { firstFrameLatch.Signal(); };
47 
48  auto sksl_config = RunConfiguration::InferFromSettings(settings);
49  sksl_config.SetEntrypoint("emptyMain");
50  std::unique_ptr<Shell> shell = CreateShell(settings);
51  PlatformViewNotifyCreated(shell.get());
52  RunEngine(shell.get(), std::move(sksl_config));
53 
54  // Initially, we should have no SkSL cache
56  ASSERT_EQ(cache.size(), 0u);
57 
58  // Draw something to trigger shader compilations.
59  LayerTreeBuilder builder = [](std::shared_ptr<ContainerLayer> root) {
60  SkPath path;
61  path.addCircle(50, 50, 20);
62  auto physical_shape_layer = std::make_shared<PhysicalShapeLayer>(
63  SK_ColorRED, SK_ColorBLUE, 1.0f, path, Clip::antiAlias);
64  root->Add(physical_shape_layer);
65  };
66  PumpOneFrame(shell.get(), 100, 100, builder);
67  firstFrameLatch.Wait();
68  WaitForIO(shell.get());
69 
70  // Some skp should be dumped due to shader compilations.
71  int skp_count = 0;
72  fml::FileVisitor skp_visitor = [&skp_count](const fml::UniqueFD& directory,
73  const std::string& filename) {
74  if (filename.size() >= 4 &&
75  filename.substr(filename.size() - 4, 4) == ".skp") {
76  skp_count += 1;
77  }
78  return true;
79  };
80  fml::VisitFilesRecursively(dir.fd(), skp_visitor);
81  ASSERT_GT(skp_count, 0);
82 
83  // SkSL cache should be generated by the last run.
85  ASSERT_GT(cache.size(), 0u);
86 
87  // Run the engine again with cache_sksl = false and check that the previously
88  // generated SkSL cache is used for precompile.
90  settings.cache_sksl = false;
91  settings.dump_skp_on_shader_compilation = true;
92  auto normal_config = RunConfiguration::InferFromSettings(settings);
93  normal_config.SetEntrypoint("emptyMain");
94  DestroyShell(std::move(shell));
95  shell = CreateShell(settings);
96  PlatformViewNotifyCreated(shell.get());
97  RunEngine(shell.get(), std::move(normal_config));
98  firstFrameLatch.Reset();
99  PumpOneFrame(shell.get(), 100, 100, builder);
100  firstFrameLatch.Wait();
101  WaitForIO(shell.get());
102 
103 // Shader precompilation from SKSL is not implemented on the Skia Vulkan
104 // backend so don't run the second half of this test on Vulkan. This can get
105 // removed if SKSL precompilation is implemented in the Skia Vulkan backend.
106 #if !defined(SHELL_ENABLE_VULKAN)
107  // To check that all shaders are precompiled, verify that no new skp is dumped
108  // due to shader compilations.
109  int old_skp_count = skp_count;
110  skp_count = 0;
111  fml::VisitFilesRecursively(dir.fd(), skp_visitor);
112  ASSERT_EQ(skp_count, old_skp_count);
113 #endif // !defined(SHELL_ENABLE_VULKAN)
114 
115  // Remove all files generated
116  fml::FileVisitor remove_visitor = [&remove_visitor](
117  const fml::UniqueFD& directory,
118  const std::string& filename) {
119  if (fml::IsDirectory(directory, filename.c_str())) {
120  { // To trigger fml::~UniqueFD before fml::UnlinkDirectory
121  fml::UniqueFD sub_dir =
122  fml::OpenDirectoryReadOnly(directory, filename.c_str());
123  fml::VisitFiles(sub_dir, remove_visitor);
124  }
125  fml::UnlinkDirectory(directory, filename.c_str());
126  } else {
127  fml::UnlinkFile(directory, filename.c_str());
128  }
129  return true;
130  };
131  fml::VisitFiles(dir.fd(), remove_visitor);
132  DestroyShell(std::move(shell));
133 }
134 
135 static void CheckTextSkData(sk_sp<SkData> data, const std::string& expected) {
136  std::string data_string(reinterpret_cast<const char*>(data->bytes()),
137  data->size());
138  ASSERT_EQ(data_string, expected);
139 }
140 
141 static void ResetAssetManager() {
143  ASSERT_EQ(PersistentCache::GetCacheForProcess()->LoadSkSLs().size(), 0u);
144 }
145 
146 static void CheckTwoSkSLsAreLoaded() {
148  ASSERT_EQ(shaders.size(), 2u);
149 }
150 
151 TEST_F(ShellTest, CanLoadSkSLsFromAsset) {
152  // Avoid polluting unit tests output by hiding INFO level logging.
153  fml::LogSettings warning_only = {fml::LOG_WARNING};
154  fml::ScopedSetLogSettings scoped_set_log_settings(warning_only);
155 
156  // The SkSL key is Base32 encoded. "IE" is the encoding of "A" and "II" is the
157  // encoding of "B".
158  //
159  // The SkSL data is Base64 encoded. "eA==" is the encoding of "x" and "eQ=="
160  // is the encoding of "y".
161  const std::string kTestJson =
162  "{\n"
163  " \"data\": {\n"
164  " \"IE\": \"eA==\",\n"
165  " \"II\": \"eQ==\"\n"
166  " }\n"
167  "}\n";
168 
169  // Temp dir for the asset.
171 
172  auto data = std::make_unique<fml::DataMapping>(
173  std::vector<uint8_t>{kTestJson.begin(), kTestJson.end()});
175 
176  // 1st, test that RunConfiguration::InferFromSettings sets the asset manager.
178  auto settings = CreateSettingsForFixture();
179  settings.assets_path = asset_dir.path();
182 
183  // 2nd, test that the RunConfiguration constructor sets the asset manager.
184  // (Android is directly calling that constructor without InferFromSettings.)
186  auto asset_manager = std::make_shared<AssetManager>();
187  RunConfiguration config(nullptr, asset_manager);
188  asset_manager->PushBack(std::make_unique<DirectoryAssetBundle>(
189  fml::OpenDirectory(asset_dir.path().c_str(), false,
191  false));
193 
194  // 3rd, test the content of the SkSLs in the asset.
195  {
197  ASSERT_EQ(shaders.size(), 2u);
198 
199  // Make sure that the 2 shaders are sorted by their keys. Their keys should
200  // be "A" and "B" (decoded from "II" and "IE").
201  if (shaders[0].first->bytes()[0] == 'B') {
202  std::swap(shaders[0], shaders[1]);
203  }
204 
205  CheckTextSkData(shaders[0].first, "A");
206  CheckTextSkData(shaders[1].first, "B");
207  CheckTextSkData(shaders[0].second, "x");
208  CheckTextSkData(shaders[1].second, "y");
209  }
210 
211  // Cleanup.
213 }
214 
215 TEST_F(ShellTest, CanRemoveOldPersistentCache) {
217  ASSERT_TRUE(base_dir.fd().is_valid());
218 
219  fml::CreateDirectory(base_dir.fd(),
220  {"flutter_engine", GetFlutterEngineVersion(), "skia"},
222 
223  constexpr char kOldEngineVersion[] = "old";
224  auto old_created = fml::CreateDirectory(
225  base_dir.fd(), {"flutter_engine", kOldEngineVersion, "skia"},
227  ASSERT_TRUE(old_created.is_valid());
228 
231 
232  auto engine_dir = fml::OpenDirectoryReadOnly(base_dir.fd(), "flutter_engine");
233  auto current_dir =
235  auto old_dir = fml::OpenDirectoryReadOnly(engine_dir, kOldEngineVersion);
236 
237  ASSERT_TRUE(engine_dir.is_valid());
238  ASSERT_TRUE(current_dir.is_valid());
239  ASSERT_FALSE(old_dir.is_valid());
240 
241  // Cleanup
242  fml::RemoveFilesInDirectory(base_dir.fd());
243 }
244 
245 TEST_F(ShellTest, CanPurgePersistentCache) {
247  ASSERT_TRUE(base_dir.fd().is_valid());
248  auto cache_dir = fml::CreateDirectory(
249  base_dir.fd(),
250  {"flutter_engine", GetFlutterEngineVersion(), "skia", GetSkiaVersion()},
254 
255  // Generate a dummy persistent cache.
256  fml::DataMapping test_data(std::string("test"));
257  ASSERT_TRUE(fml::WriteAtomically(cache_dir, "test", test_data));
258  auto file = fml::OpenFileReadOnly(cache_dir, "test");
259  ASSERT_TRUE(file.is_valid());
260 
261  // Run engine with purge_persistent_cache to remove the dummy cache.
262  auto settings = CreateSettingsForFixture();
263  settings.purge_persistent_cache = true;
264  auto config = RunConfiguration::InferFromSettings(settings);
265  std::unique_ptr<Shell> shell = CreateShell(settings);
266  RunEngine(shell.get(), std::move(config));
267 
268  // Verify that the dummy is purged.
269  file = fml::OpenFileReadOnly(cache_dir, "test");
270  ASSERT_FALSE(file.is_valid());
271 
272  // Cleanup
273  fml::RemoveFilesInDirectory(base_dir.fd());
274  DestroyShell(std::move(shell));
275 }
276 
277 TEST_F(ShellTest, PurgeAllowsFutureSkSLCache) {
278  sk_sp<SkData> shader_key = SkData::MakeWithCString("key");
279  sk_sp<SkData> shader_value = SkData::MakeWithCString("value");
280  std::string shader_filename = PersistentCache::SkKeyToFilePath(*shader_key);
281 
283  ASSERT_TRUE(base_dir.fd().is_valid());
286 
287  // Run engine with purge_persistent_cache and cache_sksl.
288  auto settings = CreateSettingsForFixture();
289  settings.purge_persistent_cache = true;
290  settings.cache_sksl = true;
291  auto config = RunConfiguration::InferFromSettings(settings);
292  std::unique_ptr<Shell> shell = CreateShell(settings);
293  RunEngine(shell.get(), std::move(config));
294  auto persistent_cache = PersistentCache::GetCacheForProcess();
295  ASSERT_EQ(persistent_cache->LoadSkSLs().size(), 0u);
296 
297  // Store the cache and verify it's valid.
298  StorePersistentCache(persistent_cache, *shader_key, *shader_value);
299  std::promise<bool> io_flushed;
300  shell->GetTaskRunners().GetIOTaskRunner()->PostTask(
301  [&io_flushed]() { io_flushed.set_value(true); });
302  io_flushed.get_future().get(); // Wait for the IO thread to flush the file.
303  ASSERT_GT(persistent_cache->LoadSkSLs().size(), 0u);
304 
305  // Cleanup
306  fml::RemoveFilesInDirectory(base_dir.fd());
307  DestroyShell(std::move(shell));
308 }
309 
310 } // namespace testing
311 } // namespace flutter
const std::string & path() const
Definition: file.h:146
const char * GetFlutterEngineVersion()
Definition: version.cc:11
DEF_SWITCHES_START snapshot asset path
Definition: switches.h:32
bool RemoveFilesInDirectory(const fml::UniqueFD &directory)
Definition: file.cc:102
std::function< bool(const fml::UniqueFD &directory, const std::string &filename)> FileVisitor
Definition: file.h:98
const TaskRunners & GetTaskRunners() const override
If callers wish to interact directly with any shell subcomponents, they must (on the platform thread)...
Definition: shell.cc:583
virtual void PostTask(const fml::closure &task)
Definition: task_runner.cc:24
constexpr std::size_t size(T(&array)[N])
Definition: size.h:13
void swap(scoped_nsprotocol< C > &p1, scoped_nsprotocol< C > &p2)
static constexpr char kAssetFileName[]
bool is_valid() const
Definition: unique_object.h:89
fml::UniqueFD OpenDirectory(const char *path, bool create_if_necessary, FilePermission permission)
Definition: file_posix.cc:94
bool VisitFiles(const fml::UniqueFD &directory, const FileVisitor &visitor)
Definition: file_posix.cc:233
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 SetAssetManager(std::shared_ptr< AssetManager > value)
static void SetCacheDirectoryPath(std::string path)
Specifies all the configuration required by the runtime library to launch the root isolate...
std::vector< SkSLCache > LoadSkSLs()
Load all the SkSL shader caches in the right directory.
bool IsDirectory(const fml::UniqueFD &directory)
Definition: file_posix.cc:124
constexpr LogSeverity LOG_WARNING
Definition: log_level.h:14
const UniqueFD & fd()
Definition: file.h:147
static fml::UniqueFD CreateDirectory(const fml::UniqueFD &base_directory, const std::vector< std::string > &components, FilePermission permission, size_t index)
Definition: file.cc:12
static void CheckTextSkData(sk_sp< SkData > data, const std::string &expected)
TEST_F(BackdropFilterLayerTest, PaintingEmptyLayerDies)
static std::string SkKeyToFilePath(const SkData &data)
fml::RefPtr< fml::TaskRunner > GetIOTaskRunner() const
Definition: task_runners.cc:38
const char * GetSkiaVersion()
Definition: version.cc:15
fml::UniqueFD OpenDirectoryReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition: file.cc:97
bool VisitFilesRecursively(const fml::UniqueFD &directory, const FileVisitor &visitor)
Definition: file.cc:71
bool UnlinkFile(const char *path)
Definition: file_posix.cc:168
static void ResetCacheForProcess()
static PersistentCache * GetCacheForProcess()
static void WaitForIO(Shell *shell)
bool WriteAtomically(const fml::UniqueFD &base_directory, const char *file_name, const Mapping &mapping)
Definition: file_posix.cc:188
bool UnlinkDirectory(const char *path)
Definition: file_posix.cc:160
fml::UniqueFD OpenFileReadOnly(const fml::UniqueFD &base_directory, const char *path)
Definition: file.cc:92