Chromium Code Reviews| 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..6b9d83e8b257314647030fdddd2a8845d7efd8cc |
| --- /dev/null |
| +++ b/tests/compiler/dart2js/sourcemaps/stacktrace_test.dart |
| @@ -0,0 +1,329 @@ |
| +// 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); |
| +} |
| + |
| +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) { |
|
Siggi Cherem (dart-lang)
2016/10/27 13:25:19
the code here to compute start-lines seems very si
Johnni Winther
2016/11/02 14:34:30
This is not just for extracting the values but to
|
| + int charCode = code.codeUnitAt(index); |
| + switch (charCode) { |
| + case 0x0A: |
|
Siggi Cherem (dart-lang)
2016/10/27 13:25:19
can you define a couple const variables to give th
Johnni Winther
2016/11/02 14:34:30
Done.
|
| + codeBuffer.write('\n'); |
| + lineNo++; |
| + columnNo = 1; |
| + break; |
| + case 0x0D: |
| + if (index + 1 < code.length && code.codeUnitAt(index + 1) == 0x0A) { |
| + index++; |
| + } |
| + codeBuffer.write('\n'); |
| + lineNo++; |
| + columnNo = 1; |
| + break; |
| + case 0x40: |
| + if (index + 1 < code.length && code.codeUnitAt(index + 1) == 0x7B) { |
| + 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]); |
|
Siggi Cherem (dart-lang)
2016/10/27 13:25:19
when running test.py, are we guaranteed that the c
Johnni Winther
2016/11/02 14:34:30
We are always running from the root (other unittes
|
| + 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); |
| + |
| + factory StackTraceLine.fromText(String text) { |
|
Siggi Cherem (dart-lang)
2016/10/27 13:25:19
it might help to add a dartdoc with an example sta
Johnni Winther
2016/11/02 14:34:30
Done.
|
| + 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(':'); |
|
Siggi Cherem (dart-lang)
2016/10/27 13:25:19
idea: consider splitting text by ':' first, then t
Johnni Winther
2016/11/02 14:34:30
Doesn't work on Windows - file names may contain :
Siggi Cherem (dart-lang)
2016/11/02 15:25:24
I see, for this specific case, is it because the d
Johnni Winther
2016/11/03 09:20:18
Alas `t.lastIndexOf(':', i1 - 1);` throws if `i1 -
Johnni Winther
2016/11/03 11:21:10
Should have been: That's why we need to go backwar
|
| + 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.'); |
| +} |