// Copyright 2017 The Chromium Authors. 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 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/commands/create.dart'; import 'package:flutter_tools/src/runner/flutter_command.dart'; import 'package:test/test.dart'; import '../src/common.dart'; import '../src/context.dart'; void main() { final String analyzerSeparator = platform.isWindows ? '-' : '•'; group('analyze once', () { Directory tempDir; String projectPath; File libMain; setUpAll(() { Cache.disableLocking(); tempDir = fs.systemTempDirectory.createTempSync('analyze_once_test_').absolute; projectPath = fs.path.join(tempDir.path, 'flutter_project'); libMain = fs.file(fs.path.join(projectPath, 'lib', 'main.dart')); }); tearDownAll(() { tempDir?.deleteSync(recursive: true); }); // Create a project to be analyzed testUsingContext('flutter create', () async { await runCommand( command: new CreateCommand(), arguments: <String>['create', projectPath], statusTextContains: <String>[ 'All done!', 'Your main program file is lib/main.dart', ], ); expect(libMain.existsSync(), isTrue); }, timeout: allowForRemotePubInvocation); // Analyze in the current directory - no arguments testUsingContext('flutter analyze working directory', () async { await runCommand( command: new AnalyzeCommand(workingDirectory: fs.directory(projectPath)), arguments: <String>['analyze'], statusTextContains: <String>['No issues found!'], ); }); // Analyze a specific file outside the current directory testUsingContext('flutter analyze one file', () async { await runCommand( command: new AnalyzeCommand(), arguments: <String>['analyze', libMain.path], statusTextContains: <String>['No issues found!'], ); }); // Analyze in the current directory - no arguments testUsingContext('flutter analyze working directory with errors', () async { // Break the code to produce the "The parameter 'onPressed' is required" hint // that is upgraded to a warning in package:flutter/analysis_options_user.yaml // to assert that we are using the default Flutter analysis options. // 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. String source = await libMain.readAsString(); source = source.replaceFirst( 'onPressed: _incrementCounter,', '// onPressed: _incrementCounter,', ); source = source.replaceFirst( '_counter++;', '_counter++; throw "an error message";', ); await libMain.writeAsString(source); // Analyze in the current directory - no arguments await runCommand( command: new AnalyzeCommand(workingDirectory: fs.directory(projectPath)), arguments: <String>['analyze'], statusTextContains: <String>[ 'Analyzing', 'warning $analyzerSeparator The parameter \'onPressed\' is required', 'hint $analyzerSeparator The method \'_incrementCounter\' isn\'t used', '2 issues found.', ], toolExit: true, ); }); // Analyze a specific file outside the current directory testUsingContext('flutter analyze one file with errors', () async { await runCommand( command: new AnalyzeCommand(), arguments: <String>['analyze', libMain.path], statusTextContains: <String>[ 'Analyzing', 'warning $analyzerSeparator The parameter \'onPressed\' is required', 'hint $analyzerSeparator The method \'_incrementCounter\' isn\'t used', '2 issues found.', ], toolExit: true, ); }); // Analyze in the current directory - no arguments testUsingContext('flutter analyze working directory with local options', () async { // Insert an analysis_options.yaml file in the project // which will trigger a lint for broken code that was inserted earlier final File optionsFile = fs.file(fs.path.join(projectPath, 'analysis_options.yaml')); await optionsFile.writeAsString(''' include: package:flutter/analysis_options_user.yaml linter: rules: - only_throw_errors '''); // Analyze in the current directory - no arguments await runCommand( command: new AnalyzeCommand(workingDirectory: fs.directory(projectPath)), arguments: <String>['analyze'], statusTextContains: <String>[ 'Analyzing', 'warning $analyzerSeparator The parameter \'onPressed\' is required', 'hint $analyzerSeparator The method \'_incrementCounter\' isn\'t used', 'lint $analyzerSeparator Only throw instances of classes extending either Exception or Error', '3 issues found.', ], toolExit: true, ); }); testUsingContext('flutter analyze no duplicate issues', () async { final Directory tempDir = fs.systemTempDirectory.createTempSync('analyze_once_test_').absolute; try { final File foo = fs.file(fs.path.join(tempDir.path, 'foo.dart')); foo.writeAsStringSync(''' import 'bar.dart'; foo() => bar(); '''); final File bar = fs.file(fs.path.join(tempDir.path, 'bar.dart')); bar.writeAsStringSync(''' import 'dart:async'; // unused void bar() { } '''); // Analyze in the current directory - no arguments await runCommand( command: new AnalyzeCommand(workingDirectory: tempDir), arguments: <String>['analyze'], statusTextContains: <String>[ 'Analyzing', '1 issue found.', ], toolExit: true, ); } finally { tempDir.deleteSync(recursive: true); } }); // Analyze a specific file outside the current directory testUsingContext('flutter analyze one file with local options', () async { await runCommand( command: new AnalyzeCommand(), arguments: <String>['analyze', libMain.path], statusTextContains: <String>[ 'Analyzing', 'warning $analyzerSeparator The parameter \'onPressed\' is required', 'hint $analyzerSeparator The method \'_incrementCounter\' isn\'t used', 'lint $analyzerSeparator Only throw instances of classes extending either Exception or Error', '3 issues found.', ], toolExit: true, ); }); }); } void assertContains(String text, List<String> patterns) { if (patterns == null) { expect(text, isEmpty); } else { for (String pattern in patterns) { expect(text, contains(pattern)); } } } Future<Null> runCommand({ FlutterCommand command, List<String> arguments, List<String> statusTextContains, List<String> errorTextContains, bool toolExit: false, }) async { try { arguments.insert(0, '--flutter-root=${Cache.flutterRoot}'); await createTestCommandRunner(command).run(arguments); expect(toolExit, isFalse, reason: 'Expected ToolExit exception'); } on ToolExit { if (!toolExit) { testLogger.clear(); rethrow; } } assertContains(testLogger.statusText, statusTextContains); assertContains(testLogger.errorText, errorTextContains); testLogger.clear(); }