Support SKP captures in flutter_tester (#25566)
diff --git a/shell/common/rasterizer.cc b/shell/common/rasterizer.cc
index d0f70b6..acd3397 100644
--- a/shell/common/rasterizer.cc
+++ b/shell/common/rasterizer.cc
@@ -454,7 +454,6 @@
// Deleting a surface also clears the GL context. Therefore, acquire the
// frame after calling `BeginFrame` as this operation resets the GL context.
auto frame = surface_->AcquireFrame(layer_tree.frame_size());
-
if (frame == nullptr) {
return RasterStatus::kFailed;
}
diff --git a/shell/gpu/gpu_surface_software_delegate.h b/shell/gpu/gpu_surface_software_delegate.h
index 81cf30a..e46f4ec 100644
--- a/shell/gpu/gpu_surface_software_delegate.h
+++ b/shell/gpu/gpu_surface_software_delegate.h
@@ -21,7 +21,7 @@
/// rasterizer needs to allocate and present the software backing
/// store.
///
-/// @see |IOSurfaceSoftware|, |AndroidSurfaceSoftware|,
+/// @see |IOSSurfaceSoftware|, |AndroidSurfaceSoftware|,
/// |EmbedderSurfaceSoftware|.
///
class GPUSurfaceSoftwareDelegate {
diff --git a/shell/testing/BUILD.gn b/shell/testing/BUILD.gn
index f82f481..678f620 100644
--- a/shell/testing/BUILD.gn
+++ b/shell/testing/BUILD.gn
@@ -28,6 +28,7 @@
"//flutter/fml",
"//flutter/lib/snapshot",
"//flutter/shell/common",
+ "//flutter/shell/gpu:gpu_surface_software",
"//flutter/third_party/tonic",
"//third_party/dart/runtime:libdart_jit",
"//third_party/dart/runtime/bin:dart_io_api",
diff --git a/shell/testing/tester_main.cc b/shell/testing/tester_main.cc
index 6095ffe..cbdaabf 100644
--- a/shell/testing/tester_main.cc
+++ b/shell/testing/tester_main.cc
@@ -22,6 +22,7 @@
#include "flutter/shell/common/shell.h"
#include "flutter/shell/common/switches.h"
#include "flutter/shell/common/thread_host.h"
+#include "flutter/shell/gpu/gpu_surface_software.h"
#include "third_party/dart/runtime/include/bin/dart_io_api.h"
#include "third_party/dart/runtime/include/dart_api.h"
@@ -31,6 +32,51 @@
namespace flutter {
+class TesterPlatformView : public PlatformView,
+ public GPUSurfaceSoftwareDelegate {
+ public:
+ TesterPlatformView(Delegate& delegate, TaskRunners task_runners)
+ : PlatformView(delegate, std::move(task_runners)) {}
+
+ // |PlatformView|
+ std::unique_ptr<Surface> CreateRenderingSurface() override {
+ auto surface = std::make_unique<GPUSurfaceSoftware>(
+ this, true /* render to surface */);
+ FML_DCHECK(surface->IsValid());
+ return surface;
+ }
+
+ // |GPUSurfaceSoftwareDelegate|
+ sk_sp<SkSurface> AcquireBackingStore(const SkISize& size) override {
+ if (sk_surface_ != nullptr &&
+ SkISize::Make(sk_surface_->width(), sk_surface_->height()) == size) {
+ // The old and new surface sizes are the same. Nothing to do here.
+ return sk_surface_;
+ }
+
+ SkImageInfo info =
+ SkImageInfo::MakeN32(size.fWidth, size.fHeight, kPremul_SkAlphaType,
+ SkColorSpace::MakeSRGB());
+ sk_surface_ = SkSurface::MakeRaster(info, nullptr);
+
+ if (sk_surface_ == nullptr) {
+ FML_LOG(ERROR)
+ << "Could not create backing store for software rendering.";
+ return nullptr;
+ }
+
+ return sk_surface_;
+ }
+
+ // |GPUSurfaceSoftwareDelegate|
+ bool PresentBackingStore(sk_sp<SkSurface> backing_store) override {
+ return true;
+ }
+
+ private:
+ sk_sp<SkSurface> sk_surface_ = nullptr;
+};
+
// Checks whether the engine's main Dart isolate has no pending work. If so,
// then exit the given message loop.
class ScriptCompletionTaskObserver {
@@ -138,7 +184,8 @@
Shell::CreateCallback<PlatformView> on_create_platform_view =
[](Shell& shell) {
- return std::make_unique<PlatformView>(shell, shell.GetTaskRunners());
+ return std::make_unique<TesterPlatformView>(shell,
+ shell.GetTaskRunners());
};
Shell::CreateCallback<Rasterizer> on_create_rasterizer = [](Shell& shell) {
@@ -162,6 +209,8 @@
return EXIT_FAILURE;
}
+ shell->GetPlatformView()->NotifyCreated();
+
// Initialize default testing locales. There is no platform to
// pass locales on the tester, so to retain expected locale behavior,
// we emulate it in here by passing in 'en_US' and 'zh_CN' as test locales.
diff --git a/testing/dart/observatory/README.md b/testing/dart/observatory/README.md
new file mode 100644
index 0000000..41b57d8
--- /dev/null
+++ b/testing/dart/observatory/README.md
@@ -0,0 +1,5 @@
+Tests in this folder need to be run with the observatory enabled, e.g. to make
+VM service method calls.
+
+The `run_tests.py` script disables the observatory for other tests in the
+parent directory.
\ No newline at end of file
diff --git a/testing/dart/observatory/skp_test.dart b/testing/dart/observatory/skp_test.dart
new file mode 100644
index 0000000..2b2e3e8
--- /dev/null
+++ b/testing/dart/observatory/skp_test.dart
@@ -0,0 +1,67 @@
+// 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.
+
+// @dart=2.12
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:developer' as developer;
+import 'dart:typed_data';
+import 'dart:ui';
+
+import 'package:test/test.dart';
+import 'package:vm_service/vm_service.dart' as vms;
+import 'package:vm_service/vm_service_io.dart';
+
+void main() {
+ late vms.VmService vmService;
+
+ setUpAll(() async {
+ final developer.ServiceProtocolInfo info =
+ await developer.Service.getInfo();
+
+ if (info.serverUri == null) {
+ fail('This test must not be run with --disable-observatory.');
+ }
+
+ vmService = await vmServiceConnectUri(
+ 'ws://localhost:${info.serverUri!.port}${info.serverUri!.path}ws',
+ );
+ });
+
+ tearDownAll(() async {
+ await vmService.dispose();
+ });
+
+ test('Capture an SKP ', () async {
+ final Completer<void> completer = Completer<void>();
+ window.onBeginFrame = (Duration timeStamp) {
+ final PictureRecorder recorder = PictureRecorder();
+ final Canvas canvas = Canvas(recorder);
+ canvas.drawRect(const Rect.fromLTRB(10, 10, 20, 20), Paint());
+ final Picture picture = recorder.endRecording();
+
+ final SceneBuilder builder = SceneBuilder();
+ builder.addPicture(Offset.zero, picture);
+ final Scene scene = builder.build();
+
+ window.render(scene);
+ scene.dispose();
+ // window.onBeginFrame = (Duration timeStamp) {
+ completer.complete();
+ // };
+ // window.scheduleFrame();
+ };
+ window.scheduleFrame();
+ await completer.future;
+
+ final vms.Response response = await vmService.callServiceExtension('_flutter.screenshotSkp');
+
+ final String base64data = response.json!['skp'] as String;
+ expect(base64data, isNotNull);
+ expect(base64data, isNotEmpty);
+ final Uint8List decoded = base64Decode(base64data);
+ expect(decoded.sublist(0, 8), 'skiapict'.codeUnits);
+ });
+}
diff --git a/testing/dart/pubspec.yaml b/testing/dart/pubspec.yaml
index a605e5b..9033468 100644
--- a/testing/dart/pubspec.yaml
+++ b/testing/dart/pubspec.yaml
@@ -4,9 +4,10 @@
sdk: '>=2.8.0 <3.0.0'
dependencies:
- test: 1.3.0
- path: 1.6.2
- image: ^2.1.4
+ test: 1.16.8
+ path: 1.8.0
+ image: 3.0.2
+ vm_service: 6.2.0
dependency_overrides:
sky_engine:
diff --git a/testing/run_tests.py b/testing/run_tests.py
index d5dd747..4a21171 100755
--- a/testing/run_tests.py
+++ b/testing/run_tests.py
@@ -225,14 +225,17 @@
assert os.path.exists(kernel_file_output)
-def RunDartTest(build_dir, dart_file, verbose_dart_snapshot, multithreaded):
+def RunDartTest(build_dir, dart_file, verbose_dart_snapshot, multithreaded, enable_observatory=False):
kernel_file_name = os.path.basename(dart_file) + '.kernel.dill'
kernel_file_output = os.path.join(out_dir, kernel_file_name)
SnapshotTest(build_dir, dart_file, kernel_file_output, verbose_dart_snapshot)
- command_args = [
- '--disable-observatory',
+ command_args = []
+ if not enable_observatory:
+ command_args.append('--disable-observatory')
+
+ command_args += [
'--use-test-fonts',
kernel_file_output
]
@@ -415,8 +418,18 @@
# Now that we have the Sky packages at the hardcoded location, run `pub get`.
RunEngineExecutable(build_dir, os.path.join('dart-sdk', 'bin', 'pub'), None, flags=['get'], cwd=dart_tests_dir)
+ dart_observatory_tests = glob.glob('%s/observatory/*_test.dart' % dart_tests_dir)
dart_tests = glob.glob('%s/*_test.dart' % dart_tests_dir)
+ if 'release' not in build_dir:
+ for dart_test_file in dart_observatory_tests:
+ if filter is not None and os.path.basename(dart_test_file) not in filter:
+ print("Skipping %s due to filter." % dart_test_file)
+ else:
+ print("Testing dart file %s with observatory enabled" % dart_test_file)
+ RunDartTest(build_dir, dart_test_file, verbose_dart_snapshot, True, True)
+ RunDartTest(build_dir, dart_test_file, verbose_dart_snapshot, False, True)
+
for dart_test_file in dart_tests:
if filter is not None and os.path.basename(dart_test_file) not in filter:
print("Skipping %s due to filter." % dart_test_file)