Commit 79c4c38c authored by Dan Field's avatar Dan Field Committed by Flutter GitHub Bot

Make analyze once test not depend on test order or flutter create command (#48003)

parent f6a88d03
...@@ -10,9 +10,8 @@ import 'package:flutter_tools/src/base/common.dart'; ...@@ -10,9 +10,8 @@ import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/commands/analyze.dart';
import 'package:flutter_tools/src/commands/create.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart';
import 'package:flutter_tools/src/runner/flutter_command_runner.dart';
import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/globals.dart' as globals;
import '../../src/common.dart'; import '../../src/common.dart';
...@@ -27,67 +26,86 @@ void main() { ...@@ -27,67 +26,86 @@ void main() {
final String analyzerSeparator = globals.platform.isWindows ? '-' : '•'; final String analyzerSeparator = globals.platform.isWindows ? '-' : '•';
group('analyze once', () { group('analyze once', () {
setUpAll(() {
Cache.disableLocking();
Cache.flutterRoot = FlutterCommandRunner.defaultFlutterRoot;
});
void _createDotPackages(String projectPath) {
final StringBuffer flutterRootUri = StringBuffer('file://');
final String canonicalizedFlutterRootPath = globals.fs.path.canonicalize(Cache.flutterRoot);
if (globals.platform.isWindows) {
flutterRootUri
..write('/')
..write(canonicalizedFlutterRootPath.replaceAll('\\', '/'));
} else {
flutterRootUri.write(canonicalizedFlutterRootPath);
}
final String dotPackagesSrc = '''# Generated
flutter:$flutterRootUri/packages/flutter/lib/
sky_engine:$flutterRootUri/bin/cache/pkg/sky_engine/lib/
flutter_project:lib/
''';
globals.fs.file(globals.fs.path.join(projectPath, '.packages'))
..createSync(recursive: true)
..writeAsStringSync(dotPackagesSrc);
}
group('default libMain', () {
Directory tempDir; Directory tempDir;
String projectPath; String projectPath;
File libMain; File libMain;
setUpAll(() { setUpAll(() async {
Cache.disableLocking();
tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute; tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute;
projectPath = globals.fs.path.join(tempDir.path, 'flutter_project'); projectPath = globals.fs.path.join(tempDir.path, 'flutter_project');
libMain = globals.fs.file(globals.fs.path.join(projectPath, 'lib', 'main.dart')); globals.fs.file(globals.fs.path.join(projectPath, 'pubspec.yaml'))
..createSync(recursive: true)
..writeAsStringSync(pubspecYamlSrc);
_createDotPackages(projectPath);
}); });
tearDownAll(() { setUp(() {
tryToDelete(tempDir); libMain = globals.fs.file(globals.fs.path.join(projectPath, 'lib', 'main.dart'))
..createSync(recursive: true)
..writeAsStringSync(mainDartSrc);
}); });
// Create a project to be analyzed tearDownAll(() {
testUsingContext('flutter create', () async { tryToDelete(tempDir);
await runCommand(
command: CreateCommand(),
arguments: <String>['--no-wrap', 'create', projectPath],
statusTextContains: <String>[
'All done!',
'Your application code is in ${globals.fs.path.normalize(globals.fs.path.join(globals.fs.path.relative(projectPath), 'lib', 'main.dart'))}',
],
);
expect(libMain.existsSync(), isTrue);
}, overrides: <Type, Generator>{
Pub: () => const Pub(),
}); });
// Analyze in the current directory - no arguments // Analyze in the current directory - no arguments
testUsingContext('working directory', () async { testUsingContext('working directory', () async {
await runCommand( await runCommand(
command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)), command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)),
arguments: <String>['analyze'], arguments: <String>['analyze', '--no-pub'],
statusTextContains: <String>['No issues found!'], statusTextContains: <String>['No issues found!'],
); );
}, overrides: <Type, Generator>{
Pub: () => const Pub(),
}); });
// Analyze a specific file outside the current directory // Analyze a specific file outside the current directory
testUsingContext('passing one file throws', () async { testUsingContext('passing one file throws', () async {
await runCommand( await runCommand(
command: AnalyzeCommand(), command: AnalyzeCommand(),
arguments: <String>['analyze', libMain.path], arguments: <String>['analyze', '--no-pub', libMain.path],
toolExit: true, toolExit: true,
exitMessageContains: 'is not a directory', exitMessageContains: 'is not a directory',
); );
}, overrides: <Type, Generator>{
Pub: () => const Pub(),
}); });
// Analyze in the current directory - no arguments // Analyze in the current directory - no arguments
testUsingContext('working directory with errors', () async { testUsingContext('working directory with errors', () async {
// Break the code to produce the "The parameter 'onPressed' is required" hint // Break the code to produce the "Avoid empty else" hint
// that is upgraded to a warning in package:flutter/analysis_options_user.yaml // that is upgraded to a warning in package:flutter/analysis_options_user.yaml
// to assert that we are using the default Flutter analysis options. // to assert that we are using the default Flutter analysis options.
// Also insert a statement that should not trigger a lint here // Also insert a statement that should not trigger a lint here
// but will trigger a lint later on when an analysis_options.yaml is added. // but will trigger a lint later on when an analysis_options.yaml is added.
String source = await libMain.readAsString(); String source = await libMain.readAsString();
source = source.replaceFirst(
'return MaterialApp(',
'if (debugPrintRebuildDirtyWidgets) {} else ; return MaterialApp(',
);
source = source.replaceFirst( source = source.replaceFirst(
'onPressed: _incrementCounter,', 'onPressed: _incrementCounter,',
'// onPressed: _incrementCounter,', '// onPressed: _incrementCounter,',
...@@ -96,23 +114,21 @@ void main() { ...@@ -96,23 +114,21 @@ void main() {
'_counter++;', '_counter++;',
'_counter++; throw "an error message";', '_counter++; throw "an error message";',
); );
await libMain.writeAsString(source); libMain.writeAsStringSync(source);
// Analyze in the current directory - no arguments // Analyze in the current directory - no arguments
await runCommand( await runCommand(
command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)), command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)),
arguments: <String>['analyze'], arguments: <String>['analyze', '--no-pub'],
statusTextContains: <String>[ statusTextContains: <String>[
'Analyzing', 'Analyzing',
'warning $analyzerSeparator The parameter \'onPressed\' is required', 'info $analyzerSeparator Avoid empty else statements',
'info $analyzerSeparator Avoid empty statements',
'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t', 'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
], ],
exitMessageContains: '2 issues found.', exitMessageContains: '3 issues found.',
toolExit: true, toolExit: true,
); );
}, overrides: <Type, Generator>{
Pub: () => const Pub(),
...noColorTerminalOverride,
}); });
// Analyze in the current directory - no arguments // Analyze in the current directory - no arguments
...@@ -120,33 +136,47 @@ void main() { ...@@ -120,33 +136,47 @@ void main() {
// Insert an analysis_options.yaml file in the project // Insert an analysis_options.yaml file in the project
// which will trigger a lint for broken code that was inserted earlier // which will trigger a lint for broken code that was inserted earlier
final File optionsFile = globals.fs.file(globals.fs.path.join(projectPath, 'analysis_options.yaml')); final File optionsFile = globals.fs.file(globals.fs.path.join(projectPath, 'analysis_options.yaml'));
await optionsFile.writeAsString(''' try {
optionsFile.writeAsStringSync('''
include: package:flutter/analysis_options_user.yaml include: package:flutter/analysis_options_user.yaml
linter: linter:
rules: rules:
- only_throw_errors - only_throw_errors
'''); ''');
String source = libMain.readAsStringSync();
source = source.replaceFirst(
'onPressed: _incrementCounter,',
'// onPressed: _incrementCounter,',
);
source = source.replaceFirst(
'_counter++;',
'_counter++; throw "an error message";',
);
libMain.writeAsStringSync(source);
// Analyze in the current directory - no arguments // Analyze in the current directory - no arguments
await runCommand( await runCommand(
command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)), command: AnalyzeCommand(workingDirectory: globals.fs.directory(projectPath)),
arguments: <String>['analyze'], arguments: <String>['analyze', '--no-pub'],
statusTextContains: <String>[ statusTextContains: <String>[
'Analyzing', 'Analyzing',
'warning $analyzerSeparator The parameter \'onPressed\' is required',
'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t', 'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
'info $analyzerSeparator Only throw instances of classes extending either Exception or Error', 'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
], ],
exitMessageContains: '3 issues found.', exitMessageContains: '2 issues found.',
toolExit: true, toolExit: true,
); );
}, overrides: <Type, Generator>{ } finally {
Pub: () => const Pub(), if (optionsFile.existsSync()) {
...noColorTerminalOverride optionsFile.deleteSync();
}
}
});
}); });
testUsingContext('no duplicate issues', () async { testUsingContext('no duplicate issues', () async {
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute; final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
_createDotPackages(tempDir.path);
try { try {
final File foo = globals.fs.file(globals.fs.path.join(tempDir.path, 'foo.dart')); final File foo = globals.fs.file(globals.fs.path.join(tempDir.path, 'foo.dart'));
...@@ -167,7 +197,7 @@ void bar() { ...@@ -167,7 +197,7 @@ void bar() {
// Analyze in the current directory - no arguments // Analyze in the current directory - no arguments
await runCommand( await runCommand(
command: AnalyzeCommand(workingDirectory: tempDir), command: AnalyzeCommand(workingDirectory: tempDir),
arguments: <String>['analyze'], arguments: <String>['analyze', '--no-pub'],
statusTextContains: <String>[ statusTextContains: <String>[
'Analyzing', 'Analyzing',
], ],
...@@ -177,9 +207,6 @@ void bar() { ...@@ -177,9 +207,6 @@ void bar() {
} finally { } finally {
tryToDelete(tempDir); tryToDelete(tempDir);
} }
}, overrides: <Type, Generator>{
Pub: () => const Pub(),
...noColorTerminalOverride
}); });
testUsingContext('returns no issues when source is error-free', () async { testUsingContext('returns no issues when source is error-free', () async {
...@@ -187,18 +214,19 @@ void bar() { ...@@ -187,18 +214,19 @@ void bar() {
StringBuffer bar = StringBuffer('baz'); StringBuffer bar = StringBuffer('baz');
'''; ''';
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.'); final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
_createDotPackages(tempDir.path);
tempDir.childFile('main.dart').writeAsStringSync(contents); tempDir.childFile('main.dart').writeAsStringSync(contents);
try { try {
await runCommand( await runCommand(
command: AnalyzeCommand(workingDirectory: globals.fs.directory(tempDir)), command: AnalyzeCommand(workingDirectory: globals.fs.directory(tempDir)),
arguments: <String>['analyze'], arguments: <String>['analyze', '--no-pub'],
statusTextContains: <String>['No issues found!'], statusTextContains: <String>['No issues found!'],
); );
} finally { } finally {
tryToDelete(tempDir); tryToDelete(tempDir);
} }
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Pub: () => const Pub(),
...noColorTerminalOverride ...noColorTerminalOverride
}); });
...@@ -208,18 +236,19 @@ StringBuffer bar = StringBuffer('baz'); ...@@ -208,18 +236,19 @@ StringBuffer bar = StringBuffer('baz');
StringBuffer bar = StringBuffer('baz'); StringBuffer bar = StringBuffer('baz');
'''; ''';
final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.'); final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.');
_createDotPackages(tempDir.path);
tempDir.childFile('main.dart').writeAsStringSync(contents); tempDir.childFile('main.dart').writeAsStringSync(contents);
try { try {
await runCommand( await runCommand(
command: AnalyzeCommand(workingDirectory: globals.fs.directory(tempDir)), command: AnalyzeCommand(workingDirectory: globals.fs.directory(tempDir)),
arguments: <String>['analyze'], arguments: <String>['analyze', '--no-pub'],
statusTextContains: <String>['No issues found!'], statusTextContains: <String>['No issues found!'],
); );
} finally { } finally {
tryToDelete(tempDir); tryToDelete(tempDir);
} }
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
Pub: () => const Pub(),
...noColorTerminalOverride ...noColorTerminalOverride
}); });
}); });
...@@ -261,3 +290,78 @@ Future<void> runCommand({ ...@@ -261,3 +290,78 @@ Future<void> runCommand({
testLogger.clear(); testLogger.clear();
} }
const String mainDartSrc = r'''
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
''';
const String pubspecYamlSrc = r'''name: flutter_project
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
''';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment