Repository: flutter/flutter
Pull request #168017: Add test for stack trace mapping and test expression eval tests using DDC library bundle format
Author: login: srujzs
Labels: name: tool
description: Affects the "flutter" command-line tool. See also t: labels.
name: will affect goldens
description: Changes to golden files
Created: 2025-04-29T21:43:38Z | Merged: 2025-05-12T18:31:13Z

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.


Diff:
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 0000000000000..6e72fb1da42c2
--- /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(<String>['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 0000000000000..7062b6e447955
--- /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(<String>['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 4692f7279d8fe..2b0667cc44679 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(<String>['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';
+
+// Created here as multiple groups use it.
+final RegExp stackTraceCurrentRegexp = RegExp(r'\.dart\s+[0-9]+:[0-9]+\s+get current');
 
-void main() {
+Future<void> 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: <String>['--verbose'],
+        additionalCommandArgs: <String>[
+          '--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<RegExpMatch> 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<RegExpMatch> 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<void> evaluateWebLibraryBooleanFromEnvironmentInLibrary(FlutterTestDriver
   expectInstance(res, InstanceKind.kBool, true.toString());
 }
 
+Future<void> 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<LibraryRef> getRootLibrary(FlutterTestDriver flutter) async {
   // `isolate.rootLib` returns incorrect library, so find the
   // entrypoint manually here instead.
@@ -255,12 +302,12 @@ Future<LibraryRef> 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<InstanceRef>()
         .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 c81592555aebe..2adb089a3acca 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<void> testAll({required bool useDDCLibraryBundleFormat}) async {
   await _testProject(
     HotReloadProject(),


Reviews:
user: login: nshahan
body: LGTM with one question about the stack frames.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T19:28:22Z
user: login: srujzs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T23:52:44Z

Review comments:
diff_hunk: @@ -113,6 +113,19 @@ 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>[
+        'get current',
+        'package:test/main.dart 24:5',
+        'package:test/main.dart 15:7',
path: packages/flutter_tools/test/web.shard/expression_evaluation_web_test.dart
user: login: nshahan
body: Are these the frames you would expect? Seems like there should be something outside of `package:test`.
created_at: 2025-05-01T19:27:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -113,6 +113,19 @@ 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>[
+        'get current',
+        'package:test/main.dart 24:5',
+        'package:test/main.dart 15:7',
path: packages/flutter_tools/test/web.shard/expression_evaluation_web_test.dart
user: login: srujzs
body: Talked offline but this is a red herring. The actual test package that Flutter uses is called `test` and is unrelated to the real `package:test`. :)
created_at: 2025-05-01T23:52:44Z
author_association: CONTRIBUTOR

PR comments:
user: login: srujzs
created_at: 2025-05-01T23:53:35Z
body: I updated the test to use a regex to check for the SDK stack frame so we know we're getting a Dart file and not a JS file (as `get current` appears in both stack traces).
author_association: CONTRIBUTOR
user: login: flutter-dashboard[bot]
created_at: 2025-05-09T01:37:06Z
body: Golden file changes have been found for this pull request. Click [here to view and triage](https://flutter-gold.skia.org/cl/github/168017) (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.



For more guidance, visit [Writing a golden file test for `package:flutter`](https://github.com/flutter/flutter/blob/master/docs/contributing/testing/Writing-a-golden-file-test-for-package-flutter.md).

__Reviewers__: Read the [Tree Hygiene page](https://github.com/flutter/flutter/blob/master/docs/contributing/Tree-hygiene.md#how-to-review-code) and make sure this patch meets those guidelines before LGTMing.

_Changes reported for pull request #168017 at sha c0baa3eda164e86284e65bdaf4d23c01bd54e886_


author_association: NONE
user: login: auto-submit[bot]
created_at: 2025-05-09T02:47:26Z
body: autosubmit label was removed for flutter/flutter/168017, because - The status or check suite [Google testing](http://frob/#/flutter/presubmit?pr=168017) has failed. Please fix the issues identified (or deflake) before re-applying this label.

author_association: CONTRIBUTOR