Extend 'flutter symbolize' to handle deferred loading units. (#149315)
Adds `-u`/`--unit-id-debug-info` arguments to `flutter symbolize` to pass paths to DWARF information for deferred loading units. The argument passed via `-u` should be of the form `N:P`, where `N` is the loading unit ID (an integer) and `P` is the path to the debug information for loading unit `N`. The DWARF information for the root loading unit can either be passed by `-d`/`--debug-info` as before or by `--unit-id-debug-info 1:<path>`. Partial fix for https://github.com/flutter/flutter/issues/137527. Additional work is needed to adjust tools built on top of `flutter symbolize` to store and pass along this additional information appropriately when there are deferred loading units.
This commit is contained in:
parent
b565379812
commit
c50b3fea32
@ -14,6 +14,8 @@ import '../base/io.dart';
|
||||
import '../convert.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
|
||||
const int rootLoadingUnitId = 1;
|
||||
|
||||
/// Support for symbolizing a Dart stack trace.
|
||||
///
|
||||
/// This command accepts either paths to an input file containing the
|
||||
@ -34,6 +36,13 @@ class SymbolizeCommand extends FlutterCommand {
|
||||
valueHelp: '/out/android/app.arm64.symbols',
|
||||
help: 'A path to the symbols file generated with "--split-debug-info".'
|
||||
);
|
||||
argParser.addMultiOption(
|
||||
'unit-id-debug-info',
|
||||
abbr: 'u',
|
||||
valueHelp: '2:/out/android/app.arm64.symbols-2.part.so',
|
||||
help: 'A loading unit id and the path to the symbols file for that'
|
||||
' unit generated with "--split-debug-info".'
|
||||
);
|
||||
argParser.addOption(
|
||||
'input',
|
||||
abbr: 'i',
|
||||
@ -63,18 +72,83 @@ class SymbolizeCommand extends FlutterCommand {
|
||||
@override
|
||||
bool get shouldUpdateCache => false;
|
||||
|
||||
File _handleDSYM(String fileName) {
|
||||
final FileSystemEntityType type = _fileSystem.typeSync(fileName);
|
||||
final bool isDSYM = fileName.endsWith('.dSYM');
|
||||
if (type == FileSystemEntityType.notFound) {
|
||||
throw FileNotFoundException(fileName);
|
||||
}
|
||||
if (type == FileSystemEntityType.directory) {
|
||||
if (!isDSYM) {
|
||||
throw StateError('$fileName is a directory, not a file');
|
||||
}
|
||||
final Directory dwarfDir = _fileSystem
|
||||
.directory(fileName)
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF');
|
||||
// The DWARF directory inside the .dSYM contains a single MachO file.
|
||||
return dwarfDir.listSync().single as File;
|
||||
}
|
||||
if (isDSYM) {
|
||||
throw StateError('$fileName is not a dSYM package directory');
|
||||
}
|
||||
return _fileSystem.file(fileName);
|
||||
}
|
||||
|
||||
Map<int, File> _unitDebugInfoPathMap() {
|
||||
final Map<int, File> map = <int, File>{};
|
||||
final String? rootInfo = stringArg('debug-info');
|
||||
if (rootInfo != null) {
|
||||
map[rootLoadingUnitId] = _handleDSYM(rootInfo);
|
||||
}
|
||||
for (final String arg in stringsArg('unit-id-debug-info')) {
|
||||
final int separatorIndex = arg.indexOf(':');
|
||||
final String unitIdString = arg.substring(0, separatorIndex);
|
||||
final int unitId = int.parse(unitIdString);
|
||||
final String unitDebugPath = arg.substring(separatorIndex + 1);
|
||||
if (map.containsKey(unitId) && map[unitId]!.path != unitDebugPath) {
|
||||
throw StateError('Different paths were given for the same loading unit'
|
||||
' $unitId: "${map[unitId]!.path}" and "$unitDebugPath".');
|
||||
}
|
||||
map[unitId] = _handleDSYM(unitDebugPath);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> validateCommand() {
|
||||
if (argResults?.wasParsed('debug-info') != true) {
|
||||
throwToolExit('"--debug-info" is required to symbolize stack traces.');
|
||||
Future<void> validateCommand() async {
|
||||
if (argResults?.wasParsed('debug-info') != true &&
|
||||
argResults?.wasParsed('unit-id-debug-info') != true) {
|
||||
throwToolExit(
|
||||
'Either "--debug-info" or "--unit-id-debug-info" is required to symbolize stack traces.');
|
||||
}
|
||||
final String debugInfoPath = stringArg('debug-info')!;
|
||||
if (debugInfoPath.endsWith('.dSYM')
|
||||
? !_fileSystem.isDirectorySync(debugInfoPath)
|
||||
: !_fileSystem.isFileSync(debugInfoPath)) {
|
||||
throwToolExit('$debugInfoPath does not exist.');
|
||||
for (final String arg in stringsArg('unit-id-debug-info')) {
|
||||
final int separatorIndex = arg.indexOf(':');
|
||||
if (separatorIndex == -1) {
|
||||
throwToolExit(
|
||||
'The argument to "--unit-id-debug-info" must contain a unit ID and path,'
|
||||
' separated by ":": "$arg".');
|
||||
}
|
||||
final String unitIdString = arg.substring(0, separatorIndex);
|
||||
final int? unitId = int.tryParse(unitIdString);
|
||||
if (unitId == null) {
|
||||
throwToolExit('The argument to "--unit-id-debug-info" must begin with'
|
||||
' a unit ID: "$unitIdString" is not an integer.');
|
||||
}
|
||||
}
|
||||
if ((argResults?.wasParsed('input') ?? false) && !_fileSystem.isFileSync(stringArg('input')!)) {
|
||||
late final Map<int, File> map;
|
||||
try {
|
||||
map = _unitDebugInfoPathMap();
|
||||
} on Object catch (e) {
|
||||
throwToolExit(e.toString());
|
||||
}
|
||||
if (!map.containsKey(rootLoadingUnitId)) {
|
||||
throwToolExit('Missing debug info for the root loading unit'
|
||||
' (id $rootLoadingUnitId).');
|
||||
}
|
||||
if ((argResults?.wasParsed('input') ?? false) &&
|
||||
!await _fileSystem.isFile(stringArg('input')!)) {
|
||||
throwToolExit('${stringArg('input')} does not exist.');
|
||||
}
|
||||
return super.validateCommand();
|
||||
@ -82,10 +156,8 @@ class SymbolizeCommand extends FlutterCommand {
|
||||
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
Stream<List<int>> input;
|
||||
IOSink output;
|
||||
|
||||
// Configure output to either specified file or stdout.
|
||||
late final IOSink output;
|
||||
if (argResults?.wasParsed('output') ?? false) {
|
||||
final File outputFile = _fileSystem.file(stringArg('output'));
|
||||
if (!outputFile.parent.existsSync()) {
|
||||
@ -93,44 +165,28 @@ class SymbolizeCommand extends FlutterCommand {
|
||||
}
|
||||
output = outputFile.openWrite();
|
||||
} else {
|
||||
final StreamController<List<int>> outputController = StreamController<List<int>>();
|
||||
outputController
|
||||
.stream
|
||||
.transform(utf8.decoder)
|
||||
.listen(_stdio.stdoutWrite);
|
||||
final StreamController<List<int>> outputController =
|
||||
StreamController<List<int>>();
|
||||
outputController.stream
|
||||
.transform(utf8.decoder)
|
||||
.listen(_stdio.stdoutWrite);
|
||||
output = IOSink(outputController);
|
||||
}
|
||||
|
||||
// Configure input from either specified file or stdin.
|
||||
if (argResults?.wasParsed('input') ?? false) {
|
||||
input = _fileSystem.file(stringArg('input')).openRead();
|
||||
} else {
|
||||
input = _stdio.stdin;
|
||||
}
|
||||
final Stream<List<int>> input = (argResults?.wasParsed('input') ?? false)
|
||||
? _fileSystem.file(stringArg('input')).openRead()
|
||||
: _stdio.stdin;
|
||||
|
||||
String debugInfoPath = stringArg('debug-info')!;
|
||||
final Map<int, Uint8List> unitSymbols = <int, Uint8List>{
|
||||
for (final MapEntry<int, File> entry in _unitDebugInfoPathMap().entries)
|
||||
entry.key: entry.value.readAsBytesSync(),
|
||||
};
|
||||
|
||||
// If it's a dSYM container, expand the path to the actual DWARF.
|
||||
if (debugInfoPath.endsWith('.dSYM')) {
|
||||
final Directory debugInfoDir = _fileSystem
|
||||
.directory(debugInfoPath)
|
||||
.childDirectory('Contents')
|
||||
.childDirectory('Resources')
|
||||
.childDirectory('DWARF');
|
||||
|
||||
final List<FileSystemEntity> dwarfFiles = debugInfoDir.listSync().whereType<File>().toList();
|
||||
if (dwarfFiles.length == 1) {
|
||||
debugInfoPath = dwarfFiles.first.path;
|
||||
} else {
|
||||
throwToolExit('Expected a single DWARF file in a dSYM container.');
|
||||
}
|
||||
}
|
||||
|
||||
final Uint8List symbols = _fileSystem.file(debugInfoPath).readAsBytesSync();
|
||||
await _dwarfSymbolizationService.decode(
|
||||
await _dwarfSymbolizationService.decodeWithUnits(
|
||||
input: input,
|
||||
output: output,
|
||||
symbols: symbols,
|
||||
unitSymbols: unitSymbols,
|
||||
);
|
||||
|
||||
return FlutterCommandResult.success();
|
||||
@ -138,17 +194,34 @@ class SymbolizeCommand extends FlutterCommand {
|
||||
}
|
||||
|
||||
typedef SymbolsTransformer = StreamTransformer<String, String> Function(Uint8List);
|
||||
typedef UnitSymbolsTransformer = StreamTransformer<String, String> Function(Map<int, Uint8List>);
|
||||
|
||||
StreamTransformer<String, String> _defaultTransformer(Uint8List symbols) {
|
||||
final Dwarf? dwarf = Dwarf.fromBytes(symbols);
|
||||
if (dwarf == null) {
|
||||
throwToolExit('Failed to decode symbols file');
|
||||
return _defaultUnitsTransformer(<int, Uint8List>{ rootLoadingUnitId: symbols});
|
||||
}
|
||||
|
||||
StreamTransformer<String, String> _defaultUnitsTransformer(Map<int, Uint8List> unitSymbols) {
|
||||
final Map<int, Dwarf> map = <int, Dwarf>{};
|
||||
for (final int unitId in unitSymbols.keys) {
|
||||
final Uint8List symbols = unitSymbols[unitId]!;
|
||||
final Dwarf? dwarf = Dwarf.fromBytes(symbols);
|
||||
if (dwarf == null) {
|
||||
throwToolExit('Failed to decode symbols file for loading unit $unitId');
|
||||
}
|
||||
map[unitId] = dwarf;
|
||||
}
|
||||
return DwarfStackTraceDecoder(dwarf, includeInternalFrames: true);
|
||||
if (!map.containsKey(rootLoadingUnitId)) {
|
||||
throwToolExit('Missing symbols file for root loading unit (id $rootLoadingUnitId)');
|
||||
}
|
||||
return DwarfStackTraceDecoder(
|
||||
map[rootLoadingUnitId]!,
|
||||
includeInternalFrames: true,
|
||||
dwarfByUnitId: map,
|
||||
);
|
||||
}
|
||||
|
||||
// A no-op transformer for `DwarfSymbolizationService.test`
|
||||
StreamTransformer<String, String> _testTransformer(Uint8List buffer) {
|
||||
StreamTransformer<String, String> _testUnitsTransformer(Map<int, Uint8List> buffer) {
|
||||
return StreamTransformer<String, String>.fromHandlers(
|
||||
handleData: (String data, EventSink<String> sink) {
|
||||
sink.add(data);
|
||||
@ -166,17 +239,24 @@ StreamTransformer<String, String> _testTransformer(Uint8List buffer) {
|
||||
class DwarfSymbolizationService {
|
||||
const DwarfSymbolizationService({
|
||||
SymbolsTransformer symbolsTransformer = _defaultTransformer,
|
||||
}) : _transformer = symbolsTransformer;
|
||||
}) : _transformer = symbolsTransformer,
|
||||
_unitsTransformer = _defaultUnitsTransformer;
|
||||
|
||||
const DwarfSymbolizationService.withUnits({
|
||||
UnitSymbolsTransformer unitSymbolsTransformer = _defaultUnitsTransformer,
|
||||
}) : _transformer = null,
|
||||
_unitsTransformer = unitSymbolsTransformer;
|
||||
|
||||
/// Create a DwarfSymbolizationService with a no-op transformer for testing.
|
||||
@visibleForTesting
|
||||
factory DwarfSymbolizationService.test() {
|
||||
return const DwarfSymbolizationService(
|
||||
symbolsTransformer: _testTransformer
|
||||
return const DwarfSymbolizationService.withUnits(
|
||||
unitSymbolsTransformer: _testUnitsTransformer,
|
||||
);
|
||||
}
|
||||
|
||||
final SymbolsTransformer _transformer;
|
||||
final SymbolsTransformer? _transformer;
|
||||
final UnitSymbolsTransformer _unitsTransformer;
|
||||
|
||||
/// Decode a stack trace from [input] and place the results in [output].
|
||||
///
|
||||
@ -190,13 +270,37 @@ class DwarfSymbolizationService {
|
||||
required IOSink output,
|
||||
required Uint8List symbols,
|
||||
}) async {
|
||||
await decodeWithUnits(
|
||||
input: input,
|
||||
output: output,
|
||||
unitSymbols: <int, Uint8List>{
|
||||
rootLoadingUnitId: symbols,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Decode a stack trace from [input] and place the results in [output].
|
||||
///
|
||||
/// Requires [unitSymbols] to map integer unit IDs to buffers created from
|
||||
/// the `--split-debug-info` command line flag.
|
||||
///
|
||||
/// Throws a [ToolExit] if the symbols cannot be parsed or the stack trace
|
||||
/// cannot be decoded.
|
||||
Future<void> decodeWithUnits({
|
||||
required Stream<List<int>> input,
|
||||
required IOSink output,
|
||||
required Map<int, Uint8List> unitSymbols,
|
||||
}) async {
|
||||
final UnitSymbolsTransformer unitSymbolsTransformer = _transformer != null
|
||||
? ((Map<int, Uint8List> m) => _transformer(m[rootLoadingUnitId]!))
|
||||
: _unitsTransformer;
|
||||
final Completer<void> onDone = Completer<void>();
|
||||
StreamSubscription<void>? subscription;
|
||||
subscription = input
|
||||
.cast<List<int>>()
|
||||
.transform(const Utf8Decoder())
|
||||
.transform(const LineSplitter())
|
||||
.transform(_transformer(symbols))
|
||||
.transform(unitSymbolsTransformer(unitSymbols))
|
||||
.listen((String line) {
|
||||
try {
|
||||
output.writeln(line);
|
||||
|
@ -52,7 +52,7 @@ void main() {
|
||||
});
|
||||
|
||||
|
||||
testUsingContext('symbolize exits when --debug-info argument is missing', () async {
|
||||
testUsingContext('symbolize exits when --debug-info and --unit-id-debug-info arguments are missing', () async {
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
@ -61,85 +61,388 @@ void main() {
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize']);
|
||||
|
||||
expect(result, throwsToolExit(message: '"--debug-info" is required to symbolize stack traces.'));
|
||||
expect(result, throwsToolExit(message: 'Either "--debug-info" or "--unit-id-debug-info" is required to symbolize stack traces.'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --debug-info dwarf file is missing', () async {
|
||||
const String fileName = 'app.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=app.debug']);
|
||||
.run(const <String>['symbolize', '--debug-info=$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'app.debug does not exist.'));
|
||||
expect(result, throwsToolExit(message: 'File not found: $fileName'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --unit-id-debug-info dwarf file is missing', () async {
|
||||
const String fileName = 'app.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=$rootLoadingUnitId:$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'File not found: $fileName'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --debug-info dSYM is missing', () async {
|
||||
const String fileName = 'app.dSYM';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=app.dSYM']);
|
||||
.run(const <String>['symbolize', '--debug-info=$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'app.dSYM does not exist.'));
|
||||
expect(result, throwsToolExit(message: 'File not found: $fileName'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --unit-id-debug-info dSYM is missing', () async {
|
||||
const String fileName = 'app.dSYM';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=$rootLoadingUnitId:$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'File not found: $fileName'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --debug-info dSYM is not a directory', () async {
|
||||
const String fileName = 'app.dSYM';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(fileName).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: '$fileName is not a dSYM package directory'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --unit-id-debug-info dSYM is not a directory', () async {
|
||||
const String fileName = 'app.dSYM';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(fileName).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=$rootLoadingUnitId:$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: '$fileName is not a dSYM package directory'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits if --unit-id-debug-info is just given a path', () async {
|
||||
const String fileName = 'app.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(fileName).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'The argument to "--unit-id-debug-info" must contain a unit ID and path,'
|
||||
' separated by ":": "$fileName".'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits if the unit id for --unit-id-debug-info is not a valid integer', () async {
|
||||
const String fileName = 'app.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(fileName).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=foo:$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'The argument to "--unit-id-debug-info" must begin with'
|
||||
' a unit ID: "foo" is not an integer.'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when different paths are given for the root loading unit via --debug-info and --unit-id-debug-info', () async {
|
||||
const String fileName1 = 'app.debug';
|
||||
const String fileName2 = 'app2.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(fileName1).createSync();
|
||||
fileSystem.file(fileName2).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=$fileName1', '--unit-id-debug-info=$rootLoadingUnitId:$fileName2']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'Different paths were given for'
|
||||
' the same loading unit $rootLoadingUnitId: "$fileName1" and'
|
||||
' "$fileName2".'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when different paths are given for a non-root loading unit via --unit-id-debug-info', () async {
|
||||
const String fileName1 = 'app.debug';
|
||||
const String fileName2 = 'app2.debug';
|
||||
const String fileName3 = 'app3.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(fileName1).createSync();
|
||||
fileSystem.file(fileName2).createSync();
|
||||
fileSystem.file(fileName3).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=$fileName1', '--unit-id-debug-info=${rootLoadingUnitId+1}:$fileName2', '--unit-id-debug-info=${rootLoadingUnitId+1}:$fileName3']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'Different paths were given for'
|
||||
' the same loading unit ${rootLoadingUnitId+1}: "$fileName2" and'
|
||||
' "$fileName3".'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize exits when --input file is missing', () async {
|
||||
const String fileName = 'app.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file('app.debug').createSync();
|
||||
fileSystem.file(fileName).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=app.debug', '--input=foo.stack', '--output=results/foo.result']);
|
||||
.run(const <String>['symbolize', '--debug-info=$fileName', '--input=foo.stack', '--output=results/foo.result']);
|
||||
|
||||
expect(result, throwsToolExit(message: ''));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize succeeds when DwarfSymbolizationService does not throw', () async {
|
||||
testUsingContext('symbolize exits when --debug-info argument is missing and --unit-id-debug-info is not provided for the root loading unit', () async {
|
||||
const String fileName = 'app.debug';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file('app.debug').writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file('foo.stack').writeAsStringSync('hello');
|
||||
fileSystem.file(fileName).createSync();
|
||||
final Future<void> result = createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=${rootLoadingUnitId+1}:$fileName']);
|
||||
|
||||
expect(result, throwsToolExit(message: 'Missing debug info for the root loading unit (id $rootLoadingUnitId).'));
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize succeeds when DwarfSymbolizationService does not throw', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
await createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=app.debug', '--input=foo.stack', '--output=results/foo.result']);
|
||||
.run(const <String>['symbolize', '--debug-info=$debugName', '--input=$inputName', '--output=$outputPath']);
|
||||
|
||||
expect(fileSystem.file('results/foo.result'), exists);
|
||||
expect(fileSystem.file('results/foo.result').readAsBytesSync(), <int>[104, 101, 108, 108, 111, 10]); // hello
|
||||
expect(fileSystem.file(outputPath), exists);
|
||||
expect(fileSystem.file(outputPath).readAsBytesSync(), <int>[104, 101, 108, 108, 111, 10]); // hello
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize succeeds when DwarfSymbolizationService with a single --unit-id-debug-info argument for the root loading unit does not throw', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
await createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=$rootLoadingUnitId:$debugName', '--input=$inputName', '--output=$outputPath']);
|
||||
|
||||
expect(fileSystem.file(outputPath), exists);
|
||||
expect(fileSystem.file(outputPath).readAsBytesSync(), <int>[104, 101, 108, 108, 111, 10]); // hello
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize succeeds when DwarfSymbolizationService with --debug-info and --unit-id-debug-info arguments does not throw', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String debugName2 = '$debugName-2.part.so';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(debugName2).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
await createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--debug-info=$debugName', '--unit-id-debug-info=${rootLoadingUnitId+1}:$debugName2', '--input=$inputName', '--output=$outputPath']);
|
||||
|
||||
expect(fileSystem.file(outputPath), exists);
|
||||
expect(fileSystem.file(outputPath).readAsBytesSync(), <int>[104, 101, 108, 108, 111, 10]); // hello
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize succeeds when DwarfSymbolizationService with multiple --unit-id-debug-info arguments does not throw', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String debugName2 = '$debugName-2.part.so';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: DwarfSymbolizationService.test(),
|
||||
);
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(debugName2).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
await createTestCommandRunner(command)
|
||||
.run(const <String>['symbolize', '--unit-id-debug-info=$rootLoadingUnitId:$debugName', '--unit-id-debug-info=${rootLoadingUnitId+1}:$debugName2', '--input=$inputName', '--output=$outputPath']);
|
||||
|
||||
expect(fileSystem.file(outputPath), exists);
|
||||
expect(fileSystem.file(outputPath).readAsBytesSync(), <int>[104, 101, 108, 108, 111, 10]); // hello
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize throws when DwarfSymbolizationService throws', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: ThrowingDwarfSymbolizationService(),
|
||||
);
|
||||
|
||||
fileSystem.file('app.debug').writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file('foo.stack').writeAsStringSync('hello');
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
expect(
|
||||
createTestCommandRunner(command).run(const <String>[
|
||||
'symbolize', '--debug-info=app.debug', '--input=foo.stack', '--output=results/foo.result',
|
||||
'symbolize', '--debug-info=$debugName', '--input=$inputName', '--output=$outputPath',
|
||||
]),
|
||||
throwsToolExit(message: 'test'),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize throws when DwarfSymbolizationService with a single --unit-id-debug-info argument for the root loading unit throws', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: ThrowingDwarfSymbolizationService(),
|
||||
);
|
||||
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
expect(
|
||||
createTestCommandRunner(command).run(const <String>[
|
||||
'symbolize', '--unit-id-debug-info=$rootLoadingUnitId:$debugName', '--input=$inputName', '--output=$outputPath',
|
||||
]),
|
||||
throwsToolExit(message: 'test'),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize throws when DwarfSymbolizationService with --debug-info and --unit-id-debug-info arguments throws', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String debugName2 = '$debugName-2.part.so';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: ThrowingDwarfSymbolizationService(),
|
||||
);
|
||||
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(debugName2).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
expect(
|
||||
createTestCommandRunner(command).run(const <String>[
|
||||
'symbolize', '--debug-info=$debugName', '--unit-id-debug-info=${rootLoadingUnitId+1}:$debugName2', '--input=$inputName', '--output=$outputPath',
|
||||
]),
|
||||
throwsToolExit(message: 'test'),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
OutputPreferences: () => OutputPreferences.test(),
|
||||
});
|
||||
|
||||
testUsingContext('symbolize throws when DwarfSymbolizationService with multiple --unit-id-debug-info arguments throws', () async {
|
||||
const String debugName = 'app.debug';
|
||||
const String debugName2 = '$debugName-2.part.so';
|
||||
const String inputName = 'foo.stack';
|
||||
const String outputPath = 'results/foo.result';
|
||||
final SymbolizeCommand command = SymbolizeCommand(
|
||||
stdio: stdio,
|
||||
fileSystem: fileSystem,
|
||||
dwarfSymbolizationService: ThrowingDwarfSymbolizationService(),
|
||||
);
|
||||
|
||||
fileSystem.file(debugName).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(debugName2).writeAsBytesSync(<int>[1, 2, 3]);
|
||||
fileSystem.file(inputName).writeAsStringSync('hello');
|
||||
|
||||
expect(
|
||||
createTestCommandRunner(command).run(const <String>[
|
||||
'symbolize', '--unit-id-debug-info=$rootLoadingUnitId:$debugName', '--unit-id-debug-info=${rootLoadingUnitId+1}:$debugName2', '--input=$inputName', '--output=$outputPath',
|
||||
]),
|
||||
throwsToolExit(message: 'test'),
|
||||
);
|
||||
@ -150,10 +453,10 @@ void main() {
|
||||
|
||||
class ThrowingDwarfSymbolizationService extends Fake implements DwarfSymbolizationService {
|
||||
@override
|
||||
Future<void> decode({
|
||||
Future<void> decodeWithUnits({
|
||||
required Stream<List<int>> input,
|
||||
required IOSink output,
|
||||
required Uint8List symbols,
|
||||
required Map<int, Uint8List> unitSymbols,
|
||||
}) async {
|
||||
throwToolExit('test');
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user