From 287134040710d60a67ae17302b34145249b02b62 Mon Sep 17 00:00:00 2001 From: Srujan Gaddam <58529443+srujzs@users.noreply.github.com> Date: Mon, 12 May 2025 10:53:07 -0700 Subject: [PATCH] Add test for stack trace mapping and test expression eval tests using DDC library bundle format (#168017) The bootstrap script utilizes a DDC utility function that maps paths to their module name in order for the stack traces to show the Dart paths and not the JS paths. This gets utilized in some stack tracing functionalities like StackTrace.current. We should test that the mapping is correct and we expect the correct stack traces. These tests should be run with the DDC library bundle format as well, so refactors them into shared code. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. ---- Once we have the CL to emit inlined source maps for the DDC library bundle format landed, we can fork this test and run it with that format as well. --- .../expression_evaluation_web_amd_test.dart | 14 ++++ ...valuation_web_ddc_library_bundle_test.dart | 14 ++++ .../expression_evaluation_web_common.dart} | 71 +++++++++++++++---- .../hot_restart_web_test_common.dart | 4 -- 4 files changed, 87 insertions(+), 16 deletions(-) create mode 100644 packages/flutter_tools/test/web.shard/expression_evaluation_web_amd_test.dart create mode 100644 packages/flutter_tools/test/web.shard/expression_evaluation_web_ddc_library_bundle_test.dart rename packages/flutter_tools/test/web.shard/{expression_evaluation_web_test.dart => test_data/expression_evaluation_web_common.dart} (78%) diff --git a/packages/flutter_tools/test/web.shard/expression_evaluation_web_amd_test.dart b/packages/flutter_tools/test/web.shard/expression_evaluation_web_amd_test.dart new file mode 100644 index 00000000000..6e72fb1da42 --- /dev/null +++ b/packages/flutter_tools/test/web.shard/expression_evaluation_web_amd_test.dart @@ -0,0 +1,14 @@ +// Copyright 2014 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. + +@Tags(['flutter-test-driver']) +library; + +import '../src/common.dart'; + +import 'test_data/expression_evaluation_web_common.dart'; + +void main() async { + await testAll(useDDCLibraryBundleFormat: false); +} diff --git a/packages/flutter_tools/test/web.shard/expression_evaluation_web_ddc_library_bundle_test.dart b/packages/flutter_tools/test/web.shard/expression_evaluation_web_ddc_library_bundle_test.dart new file mode 100644 index 00000000000..7062b6e4479 --- /dev/null +++ b/packages/flutter_tools/test/web.shard/expression_evaluation_web_ddc_library_bundle_test.dart @@ -0,0 +1,14 @@ +// Copyright 2014 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. + +@Tags(['flutter-test-driver']) +library; + +import '../src/common.dart'; + +import 'test_data/expression_evaluation_web_common.dart'; + +void main() async { + await testAll(useDDCLibraryBundleFormat: true); +} diff --git a/packages/flutter_tools/test/web.shard/expression_evaluation_web_test.dart b/packages/flutter_tools/test/web.shard/test_data/expression_evaluation_web_common.dart similarity index 78% rename from packages/flutter_tools/test/web.shard/expression_evaluation_web_test.dart rename to packages/flutter_tools/test/web.shard/test_data/expression_evaluation_web_common.dart index 4692f7279d8..2b0667cc446 100644 --- a/packages/flutter_tools/test/web.shard/expression_evaluation_web_test.dart +++ b/packages/flutter_tools/test/web.shard/test_data/expression_evaluation_web_common.dart @@ -2,19 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -@Tags(['flutter-test-driver']) -library; - import 'package:file/file.dart'; import 'package:vm_service/vm_service.dart'; -import '../integration.shard/test_data/basic_project.dart'; -import '../integration.shard/test_data/tests_project.dart'; -import '../integration.shard/test_driver.dart'; -import '../integration.shard/test_utils.dart'; -import '../src/common.dart'; +import '../../integration.shard/test_data/basic_project.dart'; +import '../../integration.shard/test_data/tests_project.dart'; +import '../../integration.shard/test_driver.dart'; +import '../../integration.shard/test_utils.dart'; +import '../../src/common.dart'; -void main() { +// Created here as multiple groups use it. +final RegExp stackTraceCurrentRegexp = RegExp(r'\.dart\s+[0-9]+:[0-9]+\s+get current'); + +Future testAll({required bool useDDCLibraryBundleFormat}) async { group('Flutter run for web', () { final BasicProject project = BasicProject(); late Directory tempDir; @@ -42,7 +42,10 @@ void main() { withDebugger: true, chrome: true, expressionEvaluation: expressionEvaluation, - additionalCommandArgs: ['--verbose'], + additionalCommandArgs: [ + '--verbose', + if (useDDCLibraryBundleFormat) '--web-experimental-hot-reload', + ], ); } @@ -113,6 +116,27 @@ void main() { await start(expressionEvaluation: true); await evaluateWebLibraryBooleanFromEnvironmentInLibrary(flutter); }); + + testWithoutContext('evaluated expression includes correctly mapped stack trace', () async { + await start(expressionEvaluation: true); + await breakInTopLevelFunction(flutter); + // Test that the call comes from some Dart getter called `current` (the + // location of which will be compiler-specific) and that the lines and + // file name of the current location is correct and reports a Dart path. + await evaluateStackTraceCurrent(flutter, (String stackTrace) { + final Iterable matches = stackTraceCurrentRegexp.allMatches(stackTrace); + if (matches.length != 1) { + return false; + } + int end = matches.first.end; + end = stackTrace.indexOf('package:test/main.dart 24:5', end); + if (end == -1) { + return false; + } + end = stackTrace.indexOf('package:test/main.dart 15:7', end); + return end != -1; + }); + }); }); group('Flutter test for web', () { @@ -181,6 +205,20 @@ void main() { await startPaused(expressionEvaluation: true); await evaluateWebLibraryBooleanFromEnvironmentInLibrary(flutter); }); + + testWithoutContext('evaluated expression includes correctly mapped stack trace', () async { + await startPaused(expressionEvaluation: true); + await breakInMethod(flutter); + await evaluateStackTraceCurrent(flutter, (String stackTrace) { + final Iterable matches = stackTraceCurrentRegexp.allMatches(stackTrace); + if (matches.length != 1) { + return false; + } + int end = matches.first.end; + end = stackTrace.indexOf('test.dart 6:9', end); + return end != -1; + }); + }); }); } @@ -246,6 +284,15 @@ Future evaluateWebLibraryBooleanFromEnvironmentInLibrary(FlutterTestDriver expectInstance(res, InstanceKind.kBool, true.toString()); } +Future evaluateStackTraceCurrent( + FlutterTestDriver flutter, + bool Function(String) matchStackTraces, +) async { + final LibraryRef library = await getRootLibrary(flutter); + final ObjRef res = await flutter.evaluate(library.id!, 'StackTrace.current.toString()'); + expectInstance(res, InstanceKind.kString, predicate(matchStackTraces)); +} + Future getRootLibrary(FlutterTestDriver flutter) async { // `isolate.rootLib` returns incorrect library, so find the // entrypoint manually here instead. @@ -255,12 +302,12 @@ Future getRootLibrary(FlutterTestDriver flutter) async { return isolate.libraries!.firstWhere((LibraryRef l) => l.uri!.contains('org-dartlang-app')); } -void expectInstance(ObjRef result, String kind, String message) { +void expectInstance(ObjRef result, String kind, Object matcher) { expect( result, const TypeMatcher() .having((InstanceRef instance) => instance.kind, 'kind', kind) - .having((InstanceRef instance) => instance.valueAsString, 'valueAsString', message), + .having((InstanceRef instance) => instance.valueAsString, 'valueAsString', matcher), ); } diff --git a/packages/flutter_tools/test/web.shard/test_data/hot_restart_web_test_common.dart b/packages/flutter_tools/test/web.shard/test_data/hot_restart_web_test_common.dart index c81592555ae..2adb089a3ac 100644 --- a/packages/flutter_tools/test/web.shard/test_data/hot_restart_web_test_common.dart +++ b/packages/flutter_tools/test/web.shard/test_data/hot_restart_web_test_common.dart @@ -13,10 +13,6 @@ import '../../src/common.dart'; import 'hot_reload_index_html_samples.dart'; -void main() async { - await testAll(useDDCLibraryBundleFormat: false); -} - Future testAll({required bool useDDCLibraryBundleFormat}) async { await _testProject( HotReloadProject(),