| // Copyright (c) 2019, 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. | |
| // ignore_for_file: unnecessary_string_escapes | |
| import 'dart:math' as math; | |
| import 'package:expect/expect.dart'; | |
| import 'package:test_runner/src/static_error.dart'; | |
| import 'package:test_runner/src/update_errors.dart'; | |
| import 'utils.dart'; | |
| // Note: This test file validates how some of the special markers used by the | |
| // test runner are parsed. But this test is also run *by* that same test | |
| // runner, and we don't want it to see the markers inside the string literals | |
| // here as significant, so we obfuscate them using seemingly-pointless string | |
| // escapes here like `\/`. | |
| void main() { | |
| // Inserts single-front end errors. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| int another = "wrong"; | |
| int third = "boo"; | |
| """, errors: [ | |
| makeError(line: 1, column: 9, length: 5, analyzerError: "some.error"), | |
| makeError(line: 3, column: 15, length: 7, cfeError: "Bad."), | |
| makeError(line: 5, column: 13, length: 5, webError: "Web.\nError."), | |
| ], expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [cfe] Bad. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [web] Web. | |
| /\/ Error. | |
| """); | |
| // Inserts errors for multiple front ends. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| int another = "wrong"; | |
| int third = "boo"; | |
| int last = "oops"; | |
| """, errors: [ | |
| makeError(line: 1, column: 9, length: 5, analyzerError: "some.error"), | |
| makeError(line: 1, column: 9, length: 5, cfeError: "Bad."), | |
| makeError(line: 3, column: 15, length: 7, cfeError: "Another bad."), | |
| makeError(line: 3, column: 15, length: 7, webError: "Web.\nError."), | |
| makeError(line: 5, column: 13, length: 5, analyzerError: "third.error"), | |
| makeError(line: 5, column: 13, length: 5, webError: "Web error."), | |
| makeError(line: 7, column: 12, length: 6, analyzerError: "last.error"), | |
| makeError(line: 7, column: 12, length: 6, cfeError: "Final.\nError."), | |
| makeError(line: 7, column: 12, length: 6, webError: "Web error."), | |
| ], expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| /\/ [cfe] Bad. | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [cfe] Another bad. | |
| /\/ [web] Web. | |
| /\/ Error. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] third.error | |
| /\/ [web] Web error. | |
| int last = "oops"; | |
| /\/ ^^^^^^ | |
| /\/ [analyzer] last.error | |
| /\/ [cfe] Final. | |
| /\/ Error. | |
| /\/ [web] Web error. | |
| """); | |
| // Removes only analyzer errors. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [cfe] Bad. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] an.error | |
| /\/ [cfe] Wrong. | |
| """, remove: {ErrorSource.analyzer}, expected: """ | |
| int i = "bad"; | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [cfe] Bad. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [cfe] Wrong. | |
| """); | |
| // Removes only CFE errors. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [cfe] Bad. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] an.error | |
| /\/ [cfe] Wrong. | |
| """, remove: {ErrorSource.cfe}, expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| int another = "wrong"; | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] an.error | |
| """); | |
| // Removes only web errors. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [web] Bad. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [cfe] Error. | |
| /\/ [web] Wrong. | |
| """, remove: {ErrorSource.web}, expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| int another = "wrong"; | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [cfe] Error. | |
| """); | |
| // Removes multiple error sources. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] some.error | |
| /\/ [cfe] CFE error. | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [web] Bad. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] another.error | |
| /\/ [cfe] Error. | |
| /\/ [web] Wrong. | |
| """, remove: {ErrorSource.analyzer, ErrorSource.web}, expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [cfe] CFE error. | |
| int another = "wrong"; | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [cfe] Error. | |
| """); | |
| // Preserves previous error's indentation if possible. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| /\/ ^^ | |
| /\/ [analyzer] previous.error | |
| """, errors: [ | |
| makeError(line: 1, column: 9, length: 5, analyzerError: "updated.error"), | |
| makeError( | |
| line: 1, column: 9, length: 5, cfeError: "Long.\nError.\nMessage."), | |
| ], expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] updated.error | |
| /\/ [cfe] Long. | |
| /\/ Error. | |
| /\/ Message. | |
| """); | |
| // Uses previous line's indentation if there was no existing error | |
| // expectation. | |
| expectUpdate(""" | |
| main() { | |
| int i = "bad"; | |
| } | |
| """, errors: [ | |
| makeError(line: 2, column: 11, length: 5, analyzerError: "updated.error"), | |
| ], expected: """ | |
| main() { | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] updated.error | |
| } | |
| """); | |
| // Discards previous error indentation if it would collide with carets. | |
| expectUpdate(""" | |
| // This fits: | |
| int i = "bad"; | |
| /\/ ^^ | |
| /\/ [analyzer] previous.error | |
| // This does not: | |
| int j = "bad"; | |
| /\/ ^^ | |
| /\/ [analyzer] previous.error | |
| """, errors: [ | |
| makeError(line: 2, column: 9, length: 5, cfeError: "Error."), | |
| makeError(line: 7, column: 9, length: 5, cfeError: "Error."), | |
| ], expected: """ | |
| // This fits: | |
| int i = "bad"; | |
| /\/^^^^^ | |
| /\/ [cfe] Error. | |
| // This does not: | |
| int j = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [cfe] Error. | |
| """); | |
| // Uses explicit location if indenting based on preceding code would collide | |
| // with carets. | |
| expectUpdate(""" | |
| main() { | |
| ["bad"]; | |
| } | |
| """, errors: [ | |
| makeError(line: 2, column: 4, length: 5, cfeError: "Error."), | |
| ], expected: """ | |
| main() { | |
| ["bad"]; | |
| /\/ [error column 4, length 5] | |
| /\/ [cfe] Error. | |
| } | |
| """); | |
| // Uses length one if there's no length. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| """, errors: [makeError(line: 1, column: 9, cfeError: "Error.")], expected: """ | |
| int i = "bad"; | |
| /\/ ^ | |
| /\/ [cfe] Error. | |
| """); | |
| // Explicit error location handles null length. | |
| expectUpdate(""" | |
| int i = | |
| "bad"; | |
| """, errors: [makeError(line: 2, column: 1, cfeError: "Error.")], expected: """ | |
| int i = | |
| "bad"; | |
| /\/ [error column 1] | |
| /\/ [cfe] Error. | |
| """); | |
| // Handles shifted line numbers in explicit error locations. | |
| // Note that the reported line is line 6, but the output is line 3 to take | |
| // into account the three removed lines. | |
| expectUpdate(""" | |
| main() { | |
| /\/ ^^ | |
| /\/ [analyzer] ERROR.CODE | |
| /\/ [cfe] Error. | |
| } | |
| Error here; | |
| """, errors: [ | |
| makeError(line: 6, column: 1, length: 5, analyzerError: "NEW.ERROR"), | |
| makeError(line: 6, column: 2, length: 3, cfeError: "Error."), | |
| ], expected: """ | |
| main() { | |
| } | |
| Error here; | |
| /\/ [error column 1, length 5] | |
| /\/ [analyzer] NEW.ERROR | |
| /\/ [error column 2, length 3] | |
| /\/ [cfe] Error. | |
| """); | |
| // Inserts a blank line if a subsequent line comment would become part of the | |
| // error message. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| // Line comment. | |
| """, errors: [ | |
| makeError(line: 1, column: 9, length: 5, cfeError: "Wrong."), | |
| ], expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [cfe] Wrong. | |
| // Line comment. | |
| """); | |
| // Inserts a blank line if a subsequent line comment would become part of the | |
| // error message. | |
| expectUpdate(""" | |
| int i = "bad"; | |
| // Line comment. | |
| """, errors: [ | |
| makeError(line: 1, column: 9, length: 5, analyzerError: "ERR.CODE"), | |
| ], expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [analyzer] ERR.CODE | |
| // Line comment. | |
| """); | |
| // Multiple errors on the same line are ordered by column then length. | |
| expectUpdate(""" | |
| someBadCode(); | |
| """, errors: [ | |
| makeError(line: 1, column: 9, length: 5, cfeError: "Wrong 1."), | |
| makeError(line: 1, column: 9, length: 4, cfeError: "Wrong 2."), | |
| makeError(line: 1, column: 6, length: 3, cfeError: "Wrong 3."), | |
| makeError(line: 1, column: 5, length: 5, cfeError: "Wrong 4."), | |
| ], expected: """ | |
| someBadCode(); | |
| /\/ ^^^^^ | |
| /\/ [cfe] Wrong 4. | |
| /\/ ^^^ | |
| /\/ [cfe] Wrong 3. | |
| /\/ ^^^^ | |
| /\/ [cfe] Wrong 2. | |
| /\/ ^^^^^ | |
| /\/ [cfe] Wrong 1. | |
| """); | |
| // Shared locations between errors with and without length. | |
| expectUpdate(""" | |
| someBadCode(arg); | |
| moreBadCode(arg); | |
| """, errors: [ | |
| makeError(line: 1, column: 13, length: 3, analyzerError: "Error.CODE"), | |
| makeError(line: 1, column: 13, cfeError: "Wrong 1."), | |
| makeError(line: 3, column: 13, cfeError: "Wrong 2."), | |
| makeError(line: 3, column: 13, length: 3, webError: "Web error."), | |
| ], expected: """ | |
| someBadCode(arg); | |
| /\/ ^^^ | |
| /\/ [analyzer] Error.CODE | |
| /\/ [cfe] Wrong 1. | |
| moreBadCode(arg); | |
| /\/ ^^^ | |
| /\/ [web] Web error. | |
| /\/ [cfe] Wrong 2. | |
| """); | |
| // Doesn't crash with RangeError. | |
| expectUpdate(""" | |
| x | |
| // [error line 1, column 1, length 0] | |
| // [cfe] Whatever""", errors: [ | |
| makeError(line: 1, column: 1, length: 0, cfeError: "Foo"), | |
| ], expected: """ | |
| x | |
| // [error column 1] | |
| // [cfe] Foo"""); | |
| contextMessages(); | |
| regression(); | |
| } | |
| void contextMessages() { | |
| // Inserts context messages. | |
| expectUpdate( | |
| """ | |
| int i = "bad"; | |
| int another = "wrong"; | |
| int third = "boo"; | |
| """, | |
| errors: [ | |
| makeError( | |
| line: 3, | |
| column: 15, | |
| length: 7, | |
| analyzerError: "some.error", | |
| context: [ | |
| makeError( | |
| line: 1, | |
| column: 9, | |
| length: 5, | |
| contextError: "Analyzer context."), | |
| makeError( | |
| line: 5, | |
| column: 13, | |
| length: 5, | |
| contextError: "More context."), | |
| ]), | |
| makeError( | |
| line: 3, | |
| column: 15, | |
| length: 7, | |
| cfeError: "CFE error.", | |
| context: [ | |
| makeError( | |
| line: 1, column: 9, length: 5, contextError: "CFE context."), | |
| ]), | |
| ], | |
| remove: {ErrorSource.analyzer}, | |
| includeContext: true, | |
| expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [context 1] Analyzer context. | |
| /\/ [context 2] CFE context. | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [analyzer 1] some.error | |
| /\/ [cfe 2] CFE error. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [context 1] More context. | |
| """); | |
| // Removes context messages for removed errors. | |
| expectUpdate( | |
| """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [context 1] Analyzer context. | |
| /\/ [context 2] CFE context. | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [analyzer 1] some.error | |
| /\/ [cfe 2] CFE error. | |
| int third = "boo"; | |
| /\/ ^^^^^ | |
| /\/ [context 1] More context. | |
| """, | |
| remove: {ErrorSource.analyzer}, | |
| includeContext: true, | |
| expected: """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [context 1] CFE context. | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [cfe 1] CFE error. | |
| int third = "boo"; | |
| """); | |
| // Discards context messages when not told to include them. | |
| expectUpdate( | |
| """ | |
| int i = "bad"; | |
| int another = "wrong"; | |
| int third = "boo"; | |
| """, | |
| errors: [ | |
| makeError( | |
| line: 3, | |
| column: 15, | |
| length: 7, | |
| analyzerError: "some.error", | |
| context: [ | |
| makeError( | |
| line: 1, | |
| column: 9, | |
| length: 5, | |
| contextError: "Analyzer context."), | |
| makeError( | |
| line: 5, | |
| column: 13, | |
| length: 5, | |
| contextError: "More context."), | |
| ]), | |
| makeError( | |
| line: 3, | |
| column: 15, | |
| length: 7, | |
| cfeError: "CFE error.", | |
| context: [ | |
| makeError( | |
| line: 1, column: 9, length: 5, contextError: "CFE context."), | |
| ]), | |
| ], | |
| includeContext: false, | |
| expected: """ | |
| int i = "bad"; | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [analyzer] some.error | |
| /\/ [cfe] CFE error. | |
| int third = "boo"; | |
| """); | |
| // Discards existing context messages when not told to include them. | |
| expectUpdate( | |
| """ | |
| int i = "bad"; | |
| /\/ ^^^^^ | |
| /\/ [context 1] CFE context. | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [cfe 1] CFE error. | |
| int third = "boo"; | |
| """, | |
| errors: [ | |
| makeError( | |
| line: 5, | |
| column: 15, | |
| length: 7, | |
| analyzerError: "some.error", | |
| context: [ | |
| makeError( | |
| line: 1, | |
| column: 9, | |
| length: 5, | |
| contextError: "Analyzer context."), | |
| makeError( | |
| line: 7, | |
| column: 13, | |
| length: 5, | |
| contextError: "More context."), | |
| ]), | |
| ], | |
| remove: {ErrorSource.analyzer}, | |
| includeContext: false, | |
| expected: """ | |
| int i = "bad"; | |
| int another = "wrong"; | |
| /\/ ^^^^^^^ | |
| /\/ [analyzer] some.error | |
| /\/ [cfe] CFE error. | |
| int third = "boo"; | |
| """); | |
| } | |
| void regression() { | |
| // https://github.com/dart-lang/sdk/issues/37990. | |
| expectUpdate(""" | |
| int get xx => 3; | |
| int get yy => 3; | |
| class A { | |
| void test() { | |
| xx = 1; | |
| /\/ ^^^^^^^^^^^^^^ | |
| /\/ [cfe] unspecified | |
| /\/ ^^^^^^^^^^^^^^ | |
| /\/ [analyzer] unspecified | |
| yy(4); | |
| /\/ ^^^^^^^^^^^^^^ | |
| /\/ [cfe] unspecified | |
| /\/ ^^^^^^^^^^^^^^ | |
| /\/ [analyzer] unspecified | |
| } | |
| } | |
| """, remove: { | |
| ErrorSource.cfe | |
| }, errors: [ | |
| makeError( | |
| line: 6, column: 5, length: 14, cfeError: "Setter not found: 'xx'."), | |
| makeError( | |
| line: 16, | |
| column: 7, | |
| cfeError: "The method 'call' isn't defined for the class 'int'.") | |
| ], expected: """ | |
| int get xx => 3; | |
| int get yy => 3; | |
| class A { | |
| void test() { | |
| xx = 1; | |
| /\/ ^^^^^^^^^^^^^^ | |
| /\/ [analyzer] unspecified | |
| /\/ [cfe] Setter not found: 'xx'. | |
| yy(4); | |
| /\/ ^^^^^^^^^^^^^^ | |
| /\/ [analyzer] unspecified | |
| /\/ ^ | |
| /\/ [cfe] The method 'call' isn't defined for the class 'int'. | |
| } | |
| } | |
| """); | |
| } | |
| void expectUpdate(String original, | |
| {List<StaticError>? errors, | |
| Set<ErrorSource>? remove, | |
| bool? includeContext, | |
| String? expected}) { | |
| errors ??= const []; | |
| remove ??= ErrorSource.all.toSet(); | |
| includeContext ??= false; | |
| var actual = updateErrorExpectations('some_test.dart', original, errors, | |
| remove: remove, includeContext: includeContext); | |
| if (actual != expected) { | |
| // Not using Expect.equals() because the diffs it shows aren't helpful for | |
| // strings this large. | |
| var actualLines = actual.split("\n"); | |
| var expectedLines = expected!.split("\n"); | |
| // Figure out which leading lines do match so we can ignore those and | |
| // highlight the offending ones. | |
| var matchingActual = <int>{}; | |
| var matchingExpected = <int>{}; | |
| for (var i = 0; | |
| i < math.min(actualLines.length, expectedLines.length); | |
| i++) { | |
| if (actualLines[i] != expectedLines[i]) break; | |
| matchingActual.add(i); | |
| matchingExpected.add(i); | |
| } | |
| // Find which trailing lines are the same so we can hide those too. | |
| for (var i = 0; | |
| i < math.min(actualLines.length, expectedLines.length); | |
| i++) { | |
| // Count backwards from the ends of each list. | |
| var actualIndex = actualLines.length - i - 1; | |
| var expectedIndex = expectedLines.length - i - 1; | |
| if (actualLines[actualIndex] != expectedLines[expectedIndex]) break; | |
| matchingActual.add(actualIndex); | |
| matchingExpected.add(expectedIndex); | |
| } | |
| var buffer = StringBuffer(); | |
| void writeLine(int index, String line, Set<int> matchingLines) { | |
| // Only show the line if it was different from the expectation. | |
| if (matchingLines.contains(index)) { | |
| buffer.writeln(" : $line"); | |
| } else { | |
| buffer.writeln("${(index + 1).toString().padLeft(4)}: $line"); | |
| } | |
| } | |
| buffer.writeln("Output did not match expectation. Expected:"); | |
| for (var i = 0; i < expectedLines.length; i++) { | |
| writeLine(i, expectedLines[i], matchingExpected); | |
| } | |
| buffer.writeln(); | |
| buffer.writeln("Was:"); | |
| for (var i = 0; i < actualLines.length; i++) { | |
| writeLine(i, actualLines[i], matchingActual); | |
| } | |
| Expect.fail(buffer.toString()); | |
| } | |
| } |