mirror of
https://github.com/dart-lang/sdk
synced 2024-10-14 10:48:25 +00:00
b464fc9037
This reverts commitcd2c566bcf
. Reason for revert: Updating to not remove field used by Flutter engine. Original change's description: > Revert "Tweak `expect.dart` library." > > This reverts commitff5f391c0a
. > > Reason for revert: The expect library is used by Flutter engine, and some of its tests use assertStatementsEnabled. There should be a migration path that doesn't require an atomic change, like adding the replacement api before removing the old one. > > Original change's description: > > Tweak `expect.dart` library. > > > > Make API more consistent for a few methods. > > Reduce the number of language features used in tests: > > * Never iterating an iterable, always converting it > > using `.toList()` first and iterating using indices > > (fx `setEquals`). > > Also require a `List` in places where an `Iterable` > > wasn't necessary. > > * Avoid doing complicated computations that are also > > used for the error message. Do simple check first, > > then recompute to get better error messages > > (fx `allDistinct`). > > > > Renamed some rarely used members for consistency > > (`stringContainsInOrder`->`containsInOrder`, > > where other string-contains functions just start > > with `contains`, and `containsOneOf` -> `containsAny` > > to match `Iterable.any` phrasing, and also it accepts > > if containing at least one, not precisely one.) > > > > Removed a function that wasn't used anywhere. > > > > Moved `assertStatementsEnabled` to `variations.dart` as `asserts`. > > Removed `typeAssertionsEnabled` and `checkedModeEnabled`. The former used in one place, where it was replaced with `checkedImplicitDowncasts` from `variations.dart`, the latter wasn't used anywhere. > > > > Deprecates `package:expect/minitest.dart`. It was never intended > > to be used for new tests, only as a help to convert existing tests > > written against `package:unit_test`. > > All existing imports marked as `// ignore: deprecated_member_use`. > > > > Change-Id: I07e21d4c0f3ccf11b82ee34af2668fdbb22264d2 > > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352360 > > Reviewed-by: Slava Egorov <vegorov@google.com> > > Reviewed-by: Ömer Ağacan <omersa@google.com> > > Reviewed-by: Nate Bosch <nbosch@google.com> > > Reviewed-by: Stephen Adams <sra@google.com> > > Commit-Queue: Lasse Nielsen <lrn@google.com> > > Change-Id: I360b4347470a0bb2b63c3108e2b83ee2a771bf3f > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362020 > Reviewed-by: Nate Bosch <nbosch@google.com> > Reviewed-by: Ömer Ağacan <omersa@google.com> > Reviewed-by: Stephen Adams <sra@google.com> > Reviewed-by: Leaf Petersen <leafp@google.com> > Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com> > Commit-Queue: William Hesse <whesse@google.com> CoreLibraryReviewExempt: Reland Change-Id: I53db40edc0733842a008839c3913d51c885e39ab Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/362502 Reviewed-by: Alexander Thomas <athom@google.com> Reviewed-by: Nate Bosch <nbosch@google.com> Reviewed-by: Ömer Ağacan <omersa@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Reviewed-by: William Hesse <whesse@google.com> Commit-Queue: Lasse Nielsen <lrn@google.com>
287 lines
9.2 KiB
Dart
287 lines
9.2 KiB
Dart
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
/// VMOptions=--dwarf-stack-traces --save-debugging-info=$TEST_COMPILATION_DIR/dwarf.so
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:expect/config.dart';
|
|
import 'package:expect/expect.dart';
|
|
import 'package:native_stack_traces/native_stack_traces.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
@pragma("vm:prefer-inline")
|
|
bar() {
|
|
// Keep the 'throw' and its argument on separate lines.
|
|
throw // force linebreak with dart format
|
|
"Hello, Dwarf!";
|
|
}
|
|
|
|
@pragma("vm:never-inline")
|
|
foo() {
|
|
bar();
|
|
}
|
|
|
|
Future<void> main() async {
|
|
String rawStack = "";
|
|
try {
|
|
foo();
|
|
} catch (e, st) {
|
|
rawStack = st.toString();
|
|
}
|
|
|
|
if (!isVmAotConfiguration) {
|
|
return; // Not running from an AOT compiled snapshot.
|
|
}
|
|
|
|
if (Platform.isAndroid) {
|
|
return; // Generated dwarf.so not available on the test device.
|
|
}
|
|
|
|
final dwarf = Dwarf.fromFile(
|
|
path.join(Platform.environment["TEST_COMPILATION_DIR"]!, "dwarf.so"))!;
|
|
|
|
await checkStackTrace(rawStack, dwarf, expectedCallsInfo);
|
|
}
|
|
|
|
Future<void> checkStackTrace(String rawStack, Dwarf dwarf,
|
|
List<List<DartCallInfo>> expectedCallsInfo) async {
|
|
print("");
|
|
print("Raw stack trace:");
|
|
print(rawStack);
|
|
|
|
final rawLines =
|
|
await Stream.value(rawStack).transform(const LineSplitter()).toList();
|
|
|
|
final pcOffsets = collectPCOffsets(rawLines).toList();
|
|
Expect.isNotEmpty(pcOffsets);
|
|
|
|
print('PCOffsets:');
|
|
for (final offset in pcOffsets) {
|
|
print('* $offset');
|
|
}
|
|
print('');
|
|
|
|
// We should have at least enough PC addresses to cover the frames we'll be
|
|
// checking.
|
|
Expect.isTrue(pcOffsets.length >= expectedCallsInfo.length);
|
|
|
|
final isolateStart = dwarf.isolateStartAddress(pcOffsets.first.architecture);
|
|
Expect.isNotNull(isolateStart);
|
|
print('Isolate start offset: 0x${isolateStart!.toRadixString(16)}');
|
|
|
|
// The addresses of the stack frames in the separate DWARF debugging info.
|
|
final virtualAddresses =
|
|
pcOffsets.map((o) => dwarf.virtualAddressOf(o)).toList();
|
|
|
|
print('Virtual addresses from PCOffsets:');
|
|
for (final address in virtualAddresses) {
|
|
print('* 0x${address.toRadixString(16)}');
|
|
}
|
|
print('');
|
|
|
|
// Some double-checks using other information in the non-symbolic stack trace.
|
|
final dsoBase = dsoBaseAddresses(rawLines).single;
|
|
print('DSO base address: 0x${dsoBase.toRadixString(16)}');
|
|
|
|
final absoluteIsolateStart = isolateStartAddresses(rawLines).single;
|
|
print('Absolute isolate start address: '
|
|
'0x${absoluteIsolateStart.toRadixString(16)}');
|
|
|
|
final absolutes = absoluteAddresses(rawLines);
|
|
// The relocated addresses of the stack frames in the loaded DSO. These is
|
|
// only guaranteed to be the same as virtualAddresses if the built-in ELF
|
|
// generator was used to create the snapshot.
|
|
final relocatedAddresses = absolutes.map((a) => a - dsoBase);
|
|
|
|
print('Relocated absolute addresses:');
|
|
for (final address in relocatedAddresses) {
|
|
print('* 0x${address.toRadixString(16)}');
|
|
}
|
|
print('');
|
|
|
|
// Explicits will be empty if not generating ELF snapshots directly, which
|
|
// means we can't depend on virtual addresses in the snapshot lining up with
|
|
// those in the separate debugging information.
|
|
final explicits = explicitVirtualAddresses(rawLines);
|
|
if (explicits.isNotEmpty) {
|
|
print('Explicit virtual addresses:');
|
|
for (final address in explicits) {
|
|
print('* 0x${address.toRadixString(16)}');
|
|
}
|
|
print('');
|
|
// Direct-to-ELF snapshots should have a build ID.
|
|
Expect.isNotNull(dwarf.buildId);
|
|
Expect.deepEquals(explicits, virtualAddresses);
|
|
|
|
// This is an ELF snapshot, so check that these two are the same.
|
|
Expect.deepEquals(virtualAddresses, relocatedAddresses);
|
|
}
|
|
|
|
final gotCallsInfo = <List<DartCallInfo>>[];
|
|
|
|
for (final offset in pcOffsets) {
|
|
final externalCallInfo = dwarf.callInfoForPCOffset(offset);
|
|
Expect.isNotNull(externalCallInfo);
|
|
final allCallInfo =
|
|
dwarf.callInfoForPCOffset(offset, includeInternalFrames: true);
|
|
Expect.isNotNull(allCallInfo);
|
|
for (final call in externalCallInfo!) {
|
|
Expect.isTrue(call is DartCallInfo, "got non-Dart call info ${call}");
|
|
Expect.isFalse(call.isInternal);
|
|
Expect.isTrue(allCallInfo!.contains(call),
|
|
"External call info ${call} is not among all calls");
|
|
}
|
|
for (final call in allCallInfo!) {
|
|
if (!call.isInternal) {
|
|
Expect.isTrue(externalCallInfo.contains(call),
|
|
"External call info ${call} is not among external calls");
|
|
}
|
|
}
|
|
gotCallsInfo.add(externalCallInfo.cast<DartCallInfo>().toList());
|
|
}
|
|
|
|
print("");
|
|
print("Call information for PC addresses:");
|
|
for (var i = 0; i < virtualAddresses.length; i++) {
|
|
print("For PC 0x${virtualAddresses[i].toRadixString(16)}:");
|
|
print(" Calls corresponding to user or library code:");
|
|
gotCallsInfo[i].forEach((frame) => print(" ${frame}"));
|
|
}
|
|
|
|
// Remove empty entries which correspond to skipped internal frames.
|
|
gotCallsInfo.removeWhere((calls) => calls.isEmpty);
|
|
|
|
checkFrames(gotCallsInfo, expectedCallsInfo);
|
|
|
|
final gotSymbolizedLines = await Stream.fromIterable(rawLines)
|
|
.transform(DwarfStackTraceDecoder(dwarf, includeInternalFrames: false))
|
|
.toList();
|
|
|
|
final gotSymbolizedCalls =
|
|
gotSymbolizedLines.where((s) => s.startsWith('#')).toList();
|
|
|
|
print("");
|
|
print("Symbolized stack trace:");
|
|
gotSymbolizedLines.forEach(print);
|
|
print("");
|
|
print("Extracted calls:");
|
|
gotSymbolizedCalls.forEach(print);
|
|
|
|
final expectedStrings = extractCallStrings(expectedCallsInfo);
|
|
// There are two strings in the list for each line in the output.
|
|
final expectedCallCount = expectedStrings.length ~/ 2;
|
|
|
|
Expect.isTrue(gotSymbolizedCalls.length >= expectedCallCount);
|
|
|
|
// Strip off any unexpected lines, so we can also make sure we didn't get
|
|
// unexpected calls prior to those calls we expect.
|
|
final gotCallsTrace =
|
|
gotSymbolizedCalls.sublist(0, expectedCallCount).join('\n');
|
|
|
|
Expect.containsInOrder(expectedStrings, gotCallsTrace);
|
|
}
|
|
|
|
final expectedCallsInfo = <List<DartCallInfo>>[
|
|
// The first frame should correspond to the throw in bar, which was inlined
|
|
// into foo (so we'll get information for two calls for that PC address).
|
|
[
|
|
DartCallInfo(
|
|
function: "bar",
|
|
filename: "dwarf_stack_trace_test.dart",
|
|
line: 17,
|
|
column: 3,
|
|
inlined: true),
|
|
DartCallInfo(
|
|
function: "foo",
|
|
filename: "dwarf_stack_trace_test.dart",
|
|
line: 23,
|
|
column: 3,
|
|
inlined: false)
|
|
],
|
|
// The second frame corresponds to call to foo in main.
|
|
[
|
|
DartCallInfo(
|
|
function: "main",
|
|
filename: "dwarf_stack_trace_test.dart",
|
|
line: 29,
|
|
column: 5,
|
|
inlined: false)
|
|
],
|
|
// Don't assume anything about any of the frames below the call to foo
|
|
// in main, as this makes the test too brittle.
|
|
];
|
|
|
|
void checkFrames(
|
|
List<List<DartCallInfo>> gotInfo, List<List<DartCallInfo>> expectedInfo) {
|
|
// There may be frames below those we check.
|
|
Expect.isTrue(gotInfo.length >= expectedInfo.length);
|
|
|
|
// We can't just use deep equality, since we only have the filenames in the
|
|
// expected version, not the whole path, and we don't really care if
|
|
// non-positive line numbers match, as long as they're both non-positive.
|
|
for (var i = 0; i < expectedInfo.length; i++) {
|
|
for (var j = 0; j < expectedInfo[i].length; j++) {
|
|
final got = gotInfo[i][j];
|
|
final expected = expectedInfo[i][j];
|
|
Expect.equals(expected.function, got.function);
|
|
Expect.equals(expected.inlined, got.inlined);
|
|
Expect.equals(expected.filename, path.basename(got.filename));
|
|
if (expected.isInternal) {
|
|
Expect.isTrue(got.isInternal);
|
|
} else {
|
|
Expect.equals(expected.line, got.line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
List<String> extractCallStrings(List<List<CallInfo>> expectedCalls) {
|
|
var ret = <String>[];
|
|
for (final frame in expectedCalls) {
|
|
for (final call in frame) {
|
|
if (call is DartCallInfo) {
|
|
ret.add(call.function);
|
|
if (call.isInternal) {
|
|
ret.add("${call.filename}:??");
|
|
} else {
|
|
ret.add("${call.filename}:${call.line}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
Iterable<int> parseUsingAddressRegExp(RegExp re, Iterable<String> lines) sync* {
|
|
for (final line in lines) {
|
|
final match = re.firstMatch(line);
|
|
if (match == null) continue;
|
|
final s = match.group(1);
|
|
if (s == null) continue;
|
|
yield int.parse(s, radix: 16);
|
|
}
|
|
}
|
|
|
|
final _absRE = RegExp(r'abs ([a-f\d]+)');
|
|
|
|
Iterable<int> absoluteAddresses(Iterable<String> lines) =>
|
|
parseUsingAddressRegExp(_absRE, lines);
|
|
|
|
final _virtRE = RegExp(r'virt ([a-f\d]+)');
|
|
|
|
Iterable<int> explicitVirtualAddresses(Iterable<String> lines) =>
|
|
parseUsingAddressRegExp(_virtRE, lines);
|
|
|
|
final _dsoBaseRE = RegExp(r'isolate_dso_base: ([a-f\d]+)');
|
|
|
|
Iterable<int> dsoBaseAddresses(Iterable<String> lines) =>
|
|
parseUsingAddressRegExp(_dsoBaseRE, lines);
|
|
|
|
final _isolateStartRE = RegExp(r'isolate_instructions: ([a-f\d]+)');
|
|
|
|
Iterable<int> isolateStartAddresses(Iterable<String> lines) =>
|
|
parseUsingAddressRegExp(_isolateStartRE, lines);
|