| Index: tests/compiler/dart2js/sourcemaps/stacktrace_test.dart
|
| diff --git a/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart b/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9fcebdc0c9e2c2c8b3be4a30d42e8611e44cadcb
|
| --- /dev/null
|
| +++ b/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart
|
| @@ -0,0 +1,344 @@
|
| +// Copyright (c) 2016, 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.
|
| +
|
| +import 'dart:async';
|
| +import 'dart:convert';
|
| +import 'dart:io';
|
| +
|
| +import 'package:async_helper/async_helper.dart';
|
| +import 'package:compiler/compiler_new.dart';
|
| +import 'package:compiler/src/apiimpl.dart';
|
| +import 'package:compiler/src/commandline_options.dart';
|
| +import 'package:compiler/src/dart2js.dart' as entry;
|
| +import 'package:expect/expect.dart';
|
| +import 'package:source_maps/source_maps.dart';
|
| +import 'package:source_maps/src/utils.dart';
|
| +
|
| +import '../source_map_validator_helper.dart';
|
| +
|
| +const String EXCEPTION_MARKER = '>ExceptionMarker<';
|
| +const String INPUT_FILE_NAME = 'in.dart';
|
| +
|
| +const List<String> TESTS = const <String>[
|
| + '''
|
| +main() {
|
| + @{1:main}throw '$EXCEPTION_MARKER';
|
| +}
|
| +''',
|
| + '''
|
| +main() {
|
| + @{1:main}test();
|
| +}
|
| +test() {
|
| + @{2:test}throw '$EXCEPTION_MARKER';
|
| +}
|
| +''',
|
| + '''
|
| +main() {
|
| + @{1:main}Class.test();
|
| +}
|
| +class Class {
|
| + static test() {
|
| + @{2:Class.test}throw '$EXCEPTION_MARKER';
|
| + }
|
| +}
|
| +''',
|
| + '''
|
| +main() {
|
| + var c = new Class();
|
| + c.@{1:main}test();
|
| +}
|
| +class Class {
|
| + test() {
|
| + @{2:Class.test}throw '$EXCEPTION_MARKER';
|
| + }
|
| +}
|
| +''',
|
| + '''
|
| +import 'package:expect/expect.dart';
|
| +main() {
|
| + var c = @{1:main}new Class();
|
| +}
|
| +class Class {
|
| + @NoInline()
|
| + Class() {
|
| + @{2:Class}throw '$EXCEPTION_MARKER';
|
| + }
|
| +}
|
| +''',
|
| +];
|
| +
|
| +class Test {
|
| + final String code;
|
| + final List<StackTraceLine> expectedLines;
|
| +
|
| + Test(this.code, this.expectedLines);
|
| +}
|
| +
|
| +const int _LF = 0x0A;
|
| +const int _CR = 0x0D;
|
| +const int _LBRACE = 0x7B;
|
| +
|
| +
|
| +Test processTestCode(String code) {
|
| + StringBuffer codeBuffer = new StringBuffer();
|
| + Map<int, StackTraceLine> stackTraceMap = <int, StackTraceLine>{};
|
| + int index = 0;
|
| + int lineNo = 1;
|
| + int columnNo = 1;
|
| + while (index < code.length) {
|
| + int charCode = code.codeUnitAt(index);
|
| + switch (charCode) {
|
| + case _LF:
|
| + codeBuffer.write('\n');
|
| + lineNo++;
|
| + columnNo = 1;
|
| + break;
|
| + case _CR:
|
| + if (index + 1 < code.length && code.codeUnitAt(index + 1) == _LF) {
|
| + index++;
|
| + }
|
| + codeBuffer.write('\n');
|
| + lineNo++;
|
| + columnNo = 1;
|
| + break;
|
| + case 0x40:
|
| + if (index + 1 < code.length && code.codeUnitAt(index + 1) == _LBRACE) {
|
| + int colonIndex = code.indexOf(':', index);
|
| + int endIndex = code.indexOf('}', index);
|
| + int stackTraceIndex =
|
| + int.parse(code.substring(index + 2, colonIndex));
|
| + String methodName = code.substring(colonIndex + 1, endIndex);
|
| + assert(!stackTraceMap.containsKey(stackTraceIndex));
|
| + stackTraceMap[stackTraceIndex] =
|
| + new StackTraceLine(methodName, INPUT_FILE_NAME, lineNo, columnNo);
|
| + index = endIndex;
|
| + } else {
|
| + codeBuffer.writeCharCode(charCode);
|
| + columnNo++;
|
| + }
|
| + break;
|
| + default:
|
| + codeBuffer.writeCharCode(charCode);
|
| + columnNo++;
|
| + }
|
| + index++;
|
| + }
|
| + List<StackTraceLine> expectedLines = <StackTraceLine>[];
|
| + for (int stackTraceIndex in (stackTraceMap.keys.toList()..sort()).reversed) {
|
| + expectedLines.add(stackTraceMap[stackTraceIndex]);
|
| + }
|
| + return new Test(codeBuffer.toString(), expectedLines);
|
| +}
|
| +
|
| +void main(List<String> arguments) {
|
| + asyncTest(() async {
|
| + for (String code in TESTS) {
|
| + await runTest(processTestCode(code));
|
| + }
|
| + });
|
| +}
|
| +
|
| +Future runTest(Test test) async {
|
| + Directory tmpDir = await createTempDir();
|
| + String input = '${tmpDir.path}/$INPUT_FILE_NAME';
|
| + new File(input).writeAsStringSync(test.code);
|
| + String output = '${tmpDir.path}/out.js';
|
| + List<String> arguments = [
|
| + '-o$output',
|
| + '--library-root=sdk',
|
| + '--packages=${Platform.packageConfig}',
|
| + Flags.useNewSourceInfo,
|
| + input,
|
| + ];
|
| + print("--------------------------------------------------------------------");
|
| + print("Compiling dart2js ${arguments.join(' ')}\n${test.code}");
|
| + CompilationResult compilationResult = await entry.internalMain(arguments);
|
| + Expect.isTrue(compilationResult.isSuccess,
|
| + "Unsuccessful compilation of test:\n${test.code}");
|
| + CompilerImpl compiler = compilationResult.compiler;
|
| + SingleMapping sourceMap = new SingleMapping.fromJson(
|
| + JSON.decode(new File('$output.map').readAsStringSync()));
|
| +
|
| + print("Running d8 $output");
|
| + ProcessResult runResult =
|
| + Process.runSync(d8executable, [output]);
|
| + String out = '${runResult.stderr}\n${runResult.stdout}';
|
| + List<String> lines = out.split(new RegExp(r'(\r|\n|\r\n)'));
|
| + List<StackTraceLine> jsStackTrace = <StackTraceLine>[];
|
| + bool seenMarker = false;
|
| + for (String line in lines) {
|
| + if (seenMarker) {
|
| + line = line.trim();
|
| + if (line.startsWith('at ')) {
|
| + jsStackTrace.add(new StackTraceLine.fromText(line));
|
| + }
|
| + } else if (line == EXCEPTION_MARKER) {
|
| + seenMarker = true;
|
| + }
|
| + }
|
| +
|
| + List<StackTraceLine> dartStackTrace = <StackTraceLine>[];
|
| + for (StackTraceLine line in jsStackTrace) {
|
| + TargetEntry targetEntry = _findColumn(line.lineNo - 1, line.columnNo - 1,
|
| + _findLine(sourceMap, line.lineNo - 1));
|
| + if (targetEntry == null) {
|
| + dartStackTrace.add(line);
|
| + } else {
|
| + String methodName;
|
| + if (targetEntry.sourceNameId != 0) {
|
| + methodName = sourceMap.names[targetEntry.sourceNameId];
|
| + }
|
| + String fileName;
|
| + if (targetEntry.sourceUrlId != 0) {
|
| + fileName = sourceMap.urls[targetEntry.sourceUrlId];
|
| + }
|
| + dartStackTrace.add(new StackTraceLine(methodName, fileName,
|
| + targetEntry.sourceLine + 1, targetEntry.sourceColumn + 1));
|
| + }
|
| + }
|
| +
|
| + int expectedIndex = 0;
|
| + for (StackTraceLine line in dartStackTrace) {
|
| + if (expectedIndex < test.expectedLines.length) {
|
| + StackTraceLine expectedLine = test.expectedLines[expectedIndex];
|
| + if (line.methodName == expectedLine.methodName &&
|
| + line.lineNo == expectedLine.lineNo &&
|
| + line.columnNo == expectedLine.columnNo) {
|
| + expectedIndex++;
|
| + }
|
| + }
|
| + }
|
| + Expect.equals(
|
| + expectedIndex,
|
| + test.expectedLines.length,
|
| + "Missing stack trace lines for test:\n${test.code}\n"
|
| + "Actual:\n${dartStackTrace.join('\n')}\n"
|
| + "Expected:\n${test.expectedLines.join('\n')}\n");
|
| +
|
| + print("Deleting '${tmpDir.path}'.");
|
| + tmpDir.deleteSync(recursive: true);
|
| +}
|
| +
|
| +class StackTraceLine {
|
| + String methodName;
|
| + String fileName;
|
| + int lineNo;
|
| + int columnNo;
|
| +
|
| + StackTraceLine(this.methodName, this.fileName, this.lineNo, this.columnNo);
|
| +
|
| + /// Creates a [StackTraceLine] by parsing a d8 stack trace line [text]. The
|
| + /// expected formats are
|
| + ///
|
| + /// at <methodName>(<fileName>:<lineNo>:<columnNo>)
|
| + /// at <methodName>(<fileName>:<lineNo>)
|
| + /// at <methodName>(<fileName>)
|
| + /// at <fileName>:<lineNo>:<columnNo>
|
| + /// at <fileName>:<lineNo>
|
| + /// at <fileName>
|
| + ///
|
| + factory StackTraceLine.fromText(String text) {
|
| + text = text.trim();
|
| + assert(text.startsWith('at '));
|
| + text = text.substring('at '.length);
|
| + String methodName;
|
| + if (text.endsWith(')')) {
|
| + int nameEnd = text.indexOf(' (');
|
| + methodName = text.substring(0, nameEnd);
|
| + text = text.substring(nameEnd + 2, text.length - 1);
|
| + }
|
| + int lineNo;
|
| + int columnNo;
|
| + String fileName;
|
| + int lastColon = text.lastIndexOf(':');
|
| + if (lastColon != -1) {
|
| + int lastValue =
|
| + int.parse(text.substring(lastColon + 1), onError: (_) => null);
|
| + if (lastValue != null) {
|
| + int secondToLastColon = text.lastIndexOf(':', lastColon - 1);
|
| + if (secondToLastColon != -1) {
|
| + int secondToLastValue = int.parse(
|
| + text.substring(secondToLastColon + 1, lastColon),
|
| + onError: (_) => null);
|
| + if (secondToLastValue != null) {
|
| + lineNo = secondToLastValue;
|
| + columnNo = lastValue;
|
| + fileName = text.substring(0, secondToLastColon);
|
| + } else {
|
| + lineNo = lastValue;
|
| + fileName = text.substring(0, lastColon);
|
| + }
|
| + } else {
|
| + lineNo = lastValue;
|
| + fileName = text.substring(0, lastColon);
|
| + }
|
| + } else {
|
| + fileName = text;
|
| + }
|
| + } else {
|
| + fileName = text;
|
| + }
|
| + return new StackTraceLine(methodName, fileName, lineNo, columnNo);
|
| + }
|
| +
|
| + String toString() {
|
| + StringBuffer sb = new StringBuffer();
|
| + sb.write(' at ');
|
| + if (methodName != null) {
|
| + sb.write(methodName);
|
| + sb.write(' (');
|
| + sb.write(fileName ?? '?');
|
| + sb.write(':');
|
| + sb.write(lineNo);
|
| + sb.write(':');
|
| + sb.write(columnNo);
|
| + sb.write(')');
|
| + } else {
|
| + sb.write(fileName ?? '?');
|
| + sb.write(':');
|
| + sb.write(lineNo);
|
| + sb.write(':');
|
| + sb.write(columnNo);
|
| + }
|
| + return sb.toString();
|
| + }
|
| +}
|
| +
|
| +/// Returns [TargetLineEntry] which includes the location in the target [line]
|
| +/// number. In particular, the resulting entry is the last entry whose line
|
| +/// number is lower or equal to [line].
|
| +///
|
| +/// Copied from [SingleMapping._findLine].
|
| +TargetLineEntry _findLine(SingleMapping sourceMap, int line) {
|
| + int index = binarySearch(sourceMap.lines, (e) => e.line > line);
|
| + return (index <= 0) ? null : sourceMap.lines[index - 1];
|
| +}
|
| +
|
| +/// Returns [TargetEntry] which includes the location denoted by
|
| +/// [line], [column]. If [lineEntry] corresponds to [line], then this will be
|
| +/// the last entry whose column is lower or equal than [column]. If
|
| +/// [lineEntry] corresponds to a line prior to [line], then the result will be
|
| +/// the very last entry on that line.
|
| +///
|
| +/// Copied from [SingleMapping._findColumn].
|
| +TargetEntry _findColumn(int line, int column, TargetLineEntry lineEntry) {
|
| + if (lineEntry == null || lineEntry.entries.length == 0) return null;
|
| + if (lineEntry.line != line) return lineEntry.entries.last;
|
| + var entries = lineEntry.entries;
|
| + int index = binarySearch(entries, (e) => e.column > column);
|
| + return (index <= 0) ? null : entries[index - 1];
|
| +}
|
| +
|
| +/// Returns the path of the d8 executable.
|
| +String get d8executable {
|
| + if (Platform.isWindows) {
|
| + return 'third_party/d8/windows/d8.exe';
|
| + } else if (Platform.isLinux) {
|
| + return 'third_party/d8/linux/d8';
|
| + } else if (Platform.isMacOS) {
|
| + return 'third_party/d8/macos/d8';
|
| + }
|
| + throw new UnsupportedError('Unsupported platform.');
|
| +}
|
|
|