Files
sdk/pkg/dartdev/test/commands/fix_test.dart
Brian Wilkerson 291730c940 Fix the windows bot
The CL https://dart-review.git.corp.google.com/c/sdk/+/505045 broke the
windows bot because the file path is different. It's different by more
than just the path separator, and given that the point of the test isn't
to ensure that the right file path is being printed, I decided to
remove the file path check from the expectations.

Change-Id: Ie5aef4beddb2051c63b2d1686664afb8841297fa
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/505383
Commit-Queue: Brian Wilkerson <brianwilkerson@google.com>
Reviewed-by: Samuel Rawlins <srawlins@google.com>
2026-05-22 07:00:21 -07:00

978 lines
22 KiB
Dart

// Copyright (c) 2020, 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:io';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';
import '../utils.dart';
void main() {
group('fix', defineFix, timeout: longTimeout);
}
final bullet = '';
final nonAnsiBullet = '-';
void defineFix() {
TestProject? p;
late ProcessResult result;
void assertResult({int exitCode = 0}) {
String message;
if (result.exitCode != exitCode) {
if (result.stderr.isNotEmpty) {
message = 'Error code was ${result.exitCode} and stderr was not empty';
} else {
message = 'Error code was ${result.exitCode}';
}
} else if (result.stderr.isNotEmpty) {
message = 'stderr was not empty';
} else {
return;
}
fail('''
$message
stdout:
${result.stdout}
stderr:
${result.stderr}
''');
}
group('usage', () {
test('--help', () async {
p = project(mainSrc: 'int get foo => 1;\n');
var result = await p!.runFix([p!.dirPath, '--help']);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
contains('Apply automated fixes to Dart source code.'),
);
expect(result.stdout, contains('Usage: dart fix [arguments]'));
});
test('--help --verbose', () async {
p = project(mainSrc: 'int get foo => 1;\n');
var result = await p!.runFix([p!.dirPath, '--help', '--verbose']);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
contains('Apply automated fixes to Dart source code.'),
);
expect(
result.stdout,
contains('Usage: dart [vm-options] fix [arguments]'),
);
});
test('no args', () async {
p = project(mainSrc: 'int get foo => 1;\n');
var result = await p!.runFix([p!.dirPath]);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
contains('Apply automated fixes to Dart source code.'),
);
});
});
test('--enable-experiment is accepted', () async {
p = project(mainSrc: 'int get foo => 1;\n');
var result = await p!.runFix([
'--enable-experiment=test-experiment',
'--apply',
]);
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
expect(result.stdout, contains('Nothing to fix!'));
});
group('perform', () {
test('--apply (nothing to fix)', () async {
p = project(mainSrc: 'int get foo => 1;\n');
var result = await p!.runFix(['--apply', p!.dirPath]);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(result.stdout, contains('Nothing to fix!'));
});
test('--apply (no args)', () async {
p = project(
mainSrc: '''
var x = "";
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix(['--apply'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_single_quotes $bullet 1 fix',
]),
);
});
test('--dry-run', () async {
p = project(
mainSrc: '''
class A {
String a() => "";
}
class B extends A {
String a() => "";
}
''',
analysisOptions: '''
linter:
rules:
- annotate_overrides
- prefer_single_quotes
''',
);
var result = await p!.runFix(['--dry-run', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'3 proposed fixes in 1 file.',
'lib${Platform.pathSeparator}main.dart',
' annotate_overrides $bullet 1 fix',
' prefer_single_quotes $bullet 2 fixes',
'To fix an individual diagnostic, run one of:',
' dart fix --apply --code=annotate_overrides .',
' dart fix --apply --code=prefer_single_quotes .',
'To fix all diagnostics, run:',
' dart fix --apply .',
]),
);
});
test('--dry-run --code=(single)', () async {
p = project(
mainSrc: '''
var x = "";
class A {
A a() => new A();
}
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
- unnecessary_new
''',
);
var result = await p!.runFix([
'--dry-run',
'--code',
'prefer_single_quotes',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'1 proposed fix in 1 file.',
'lib${Platform.pathSeparator}main.dart',
' prefer_single_quotes $bullet 1 fix',
]),
);
});
test('--dry-run --code=(single: undefined)', () async {
p = project(
mainSrc: '''
var x = "";
class A {
A a() => new A();
}
''',
analysisOptions: '''
linter:
rules:
- _undefined_
- unnecessary_new
''',
);
var result = await p!.runFix([
'--dry-run',
'--code',
'_undefined_',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
"Unable to compute fixes: The diagnostic '_undefined_' is not defined by the analyzer.",
]),
);
});
test('--apply lib/main.dart', () async {
p = project(
mainSrc: '''
var x = "";
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix([
'--apply',
path.join('lib', 'main.dart'),
], workingDir: p!.dirPath);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'main.dart',
' prefer_single_quotes $bullet 1 fix',
'1 fix made in 1 file.',
]),
);
expect(result.exitCode, 0);
});
test('--apply --code=(single)', () async {
p = project(
mainSrc: '''
var x = "";
class A {
A a() => new A();
}
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
- unnecessary_new
''',
);
var result = await p!.runFix([
'--apply',
'--code',
'prefer_single_quotes',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_single_quotes $bullet 1 fix',
'1 fix made in 1 file.',
]),
);
});
test('--apply --code=(undefined)', () async {
p = project(mainSrc: '');
var result = await p!.runFix([
'--apply',
'--code',
'_undefined_',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
"Unable to compute fixes: The diagnostic '_undefined_' is not defined by the analyzer.",
]),
);
});
test('--apply --code=(not enabled)', () async {
p = project(
mainSrc: '''
var x = "";
class A {
A a() => new A();
}
''',
analysisOptions: '''
linter:
rules:
- unnecessary_new
''',
);
var result = await p!.runFix([
'--apply',
'--code',
'prefer_single_quotes',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'prefer_single_quotes - 1 fix',
'1 fix made in 1 file.',
]),
);
});
test('--apply --code=(multiple: one undefined)', () async {
p = project(
mainSrc: '''
var x = "";
class A {
A a() => new A();
}
''',
analysisOptions: '''
linter:
rules:
- _undefined_
- unnecessary_new
''',
);
var result = await p!.runFix([
'--apply',
'--code',
'_undefined_',
'--code',
'unnecessary_new',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 3);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
"Unable to compute fixes: The diagnostic '_undefined_' is not defined by the analyzer.",
]),
);
});
test('--apply --code=(multiple)', () async {
p = project(
mainSrc: '''
var x = "";
class A {
A a() => new A();
}
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
- unnecessary_new
''',
);
var result = await p!.runFix([
'--apply',
'--code',
'prefer_single_quotes',
'--code',
'unnecessary_new',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_single_quotes $bullet 1 fix',
' unnecessary_new $bullet 1 fix',
'2 fixes made in 1 file.',
]),
);
});
test('--apply part.dart', () async {
p = project(
mainSrc: '''
part 'part.dart';
void a() {
b();
}
''',
analysisOptions: '''
linter:
rules:
- prefer_const_constructors
''',
);
p!.file('lib/part.dart', '''
part of 'main.dart';
Stream<String> b() {
return Stream.empty();
}
''');
var result = await p!.runFix([
'--apply',
'--code',
'empty_statements',
'--code',
'prefer_const_constructors',
'./lib/part.dart',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'part.dart',
' prefer_const_constructors $bullet 1 fix',
'1 fix made in 1 file.',
]),
);
});
test('--apply --code=(multiple) [part file]', () async {
p = project(
mainSrc: '''
part 'part.dart';
void a() {
// need to trigger a lint in main.dart for the bug to happen
;
b();
}
''',
analysisOptions: '''
linter:
rules:
- empty_statements
- prefer_const_constructors
''',
);
p!.file('lib/part.dart', '''
part of 'main.dart';
Stream<String> b() {
// dart fix should only add a single const
return Stream.empty();
}
''');
var result = await p!.runFix([
'--apply',
'--code',
'empty_statements',
'--code',
'prefer_const_constructors',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' empty_statements $bullet 1 fix',
'lib${Platform.pathSeparator}part.dart',
' prefer_const_constructors $bullet 1 fix',
'2 fixes made in 2 files.',
]),
);
});
test('--apply --code=(multiple: comma-delimited)', () async {
p = project(
mainSrc: '''
var x = "";
class A {
A a() => new A();
}
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
- unnecessary_new
''',
);
var result = await p!.runFix([
'--apply',
'--code=prefer_single_quotes,unnecessary_new',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_single_quotes $bullet 1 fix',
' unnecessary_new $bullet 1 fix',
'2 fixes made in 1 file.',
]),
);
});
test('--apply (.)', () async {
p = project(
mainSrc: '''
var x = "";
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_single_quotes $bullet 1 fix',
'1 fix made in 1 file.',
]),
);
});
test('--apply (contradictory lints do not loop infinitely)', () async {
p = project(
mainSrc: '''
var x = "";
''',
analysisOptions: '''
linter:
rules:
- prefer_double_quotes
- prefer_single_quotes
''',
);
var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_double_quotes $bullet 2 fixes',
' prefer_single_quotes $bullet 2 fixes',
'4 fixes made in 1 file.',
]),
);
});
test('--apply (excludes)', () async {
p = project(
mainSrc: '''
var x = "";
''',
analysisOptions: '''
analyzer:
exclude:
- lib/**
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(result.stdout, contains('Nothing to fix!'));
});
test('--apply (ignores)', () async {
p = project(
mainSrc: '''
// ignore: prefer_single_quotes
var x = "";
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(result.stdout, contains('Nothing to fix!'));
});
test('--apply (unused imports require a second pass)', () async {
p = project(
mainSrc: '''
import 'dart:math';
var x = "";
''',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_single_quotes $bullet 1 fix',
' unused_import $bullet 1 fix',
'2 fixes made in 1 file.',
]),
);
});
test('--apply (contiguous initializing formals require a second '
'pass)', () async {
// We can't convert contiguous leading initializers or assignments to
// initializing formals because the edits collide on the comma or
// whitespace between them. But two passes is enough to catch them all.
p = project(
mainSrc: '''
class C {
int a;
int b;
int c;
int d;
int e;
int f;
C(int a, int b, int c, int d, int e, int f) : a = a, b = b, c = c {
this.d = d;
this.e = e;
this.f = f;
}
}
''',
analysisOptions: '''
linter:
rules:
- prefer_initializing_formals
''',
);
var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Applying fixes...',
'lib${Platform.pathSeparator}main.dart',
' prefer_initializing_formals $bullet 6',
'6 fixes made in 1 file.',
]),
);
expect(p!.findFile('lib/main.dart')!.readAsStringSync(), '''
class C {
int a;
int b;
int c;
int d;
int e;
int f;
C(this.a, this.b, this.c, this.d, this.e, this.f);
}
''');
});
group('AOT mode', () {
test('--use-aot-snapshot', () async {
p = project(
mainSrc: 'String a() => "";',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix([
'--use-aot-snapshot',
'--dry-run',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(result.stdout, contains('1 proposed fix in 1 file.'));
});
test('--no-use-aot-snapshot', () async {
p = project(
mainSrc: 'String a() => "";',
analysisOptions: '''
linter:
rules:
- prefer_single_quotes
''',
);
var result = await p!.runFix([
'--no-use-aot-snapshot',
'--dry-run',
'.',
], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(result.stdout, contains('1 proposed fix in 1 file.'));
});
});
});
group('regression', () {
test('field without name', () async {
// This is a test for https://github.com/dart-lang/sdk/issues/60927.
p = project(
mainSrc: '''
class C {
int? a,;
}
''',
analysisOptions: '''
linter:
rules:
- prefer_final_fields
''',
);
var result = await p!.runFix(['--apply', '.'], workingDir: p!.dirPath);
expect(result.exitCode, 0);
expect(result.stderr, isEmpty);
expect(
result.stdout,
stringContainsInOrderWithVariableBullets([
'Computing fixes in myapp...',
'Nothing to fix!',
]),
);
});
});
group('compare-to-golden', () {
test('target is not a directory', () async {
p = project(
mainSrc: '''
class A {
String a() => "";
}
class B extends A {
String a() => "";
}
''',
analysisOptions: '''
linter:
rules:
- annotate_overrides
- prefer_single_quotes
''',
);
p!.file('lib/main.dart.expect', '''
class A {
String a() => '';
}
class B extends A {
@override
String a() => '';
}
''');
result = await p!.runFix([
'--compare-to-golden',
'lib/main.dart.expect',
], workingDir: p!.dirPath);
expect(result.exitCode, 64);
expect(
result.stderr,
startsWith('Golden comparison requires a directory argument.'),
);
});
test('applied fixes do not match expected', () async {
p = project(
mainSrc: '''
class A {
String a() => "";
}
class B extends A {
String a() => "";
}
''',
analysisOptions: '''
linter:
rules:
- annotate_overrides
- prefer_single_quotes
''',
);
p!.file('lib/main.dart.expect', '''
class A {
String a() => '';
}
class B extends A {
String a() => '';
}
''');
result = await p!.runFix([
'--compare-to-golden',
'.',
], workingDir: p!.dirPath);
assertResult(exitCode: 1);
});
test('applied fixes match expected', () async {
p = project(
mainSrc: '''
class A {
String a() => "";
}
class B extends A {
String a() => "";
}
''',
analysisOptions: '''
linter:
rules:
- annotate_overrides
- prefer_single_quotes
''',
);
p!.file('lib/main.dart.expect', '''
class A {
String a() => '';
}
class B extends A {
@override
String a() => '';
}
''');
result = await p!.runFix([
'--compare-to-golden',
'.',
], workingDir: p!.dirPath);
assertResult();
});
test('missing expect', () async {
p = project(
mainSrc: '''
class A {
String a() => "";
}
class B extends A {
String a() => "";
}
''',
analysisOptions: '''
linter:
rules:
- annotate_overrides
- prefer_single_quotes
''',
);
result = await p!.runFix([
'--compare-to-golden',
'.',
], workingDir: p!.dirPath);
assertResult(exitCode: 1);
});
test('missing original', () async {
p = project(
mainSrc: '''
class C {}
''',
);
p!.file('lib/main.dart.expect', '''
class C {}
''');
p!.file('lib/secondary.dart.expect', '''
class A {}
''');
result = await p!.runFix([
'--compare-to-golden',
'.',
], workingDir: p!.dirPath);
assertResult(exitCode: 1);
});
test('no fixes to apply does not match expected', () async {
p = project(
mainSrc: '''
class A {
String a() => "";
}
''',
analysisOptions: '''
linter:
rules:
- annotate_overrides
''',
);
p!.file('lib/main.dart.expect', '''
class A {
String a() => '';
}
''');
result = await p!.runFix([
'--compare-to-golden',
'.',
], workingDir: p!.dirPath);
assertResult(exitCode: 1);
});
});
}
/// Allow for different bullets; depending on how the test harness is run,
/// subprocesses may decide to give us ansi bullets or normal bullets.
/// TODO(jcollins): find a way to detect which one we should be expecting.
Matcher stringContainsInOrderWithVariableBullets(List<String> substrings) {
var substringMatcher = stringContainsInOrder(substrings);
if (substrings.any((s) => s.contains(bullet))) {
var alternatives = [
for (var s in substrings) s.replaceAll(bullet, nonAnsiBullet),
];
return anyOf(substringMatcher, stringContainsInOrder(alternatives));
}
return substringMatcher;
}