// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // @dart = 2.8 import 'dart:async'; import 'package:fake_async/fake_async.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/analyze.dart'; import 'package:flutter_tools/src/dart/analysis.dart'; import 'package:flutter_tools/src/dart/pub.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fake_process_manager.dart'; import '../../src/test_flutter_command_runner.dart'; void main() { setUpAll(() { Cache.flutterRoot = getFlutterRoot(); }); AnalysisServer server; Directory tempDir; FileSystem fileSystem; Platform platform; ProcessManager processManager; AnsiTerminal terminal; Logger logger; setUp(() { fileSystem = globals.localFileSystem; platform = const LocalPlatform(); processManager = const LocalProcessManager(); terminal = AnsiTerminal(platform: platform, stdio: Stdio()); logger = BufferLogger(outputPreferences: OutputPreferences.test(), terminal: terminal); tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_analysis_test.'); }); tearDown(() { tryToDelete(tempDir); return server?.dispose(); }); void _createSampleProject(Directory directory, { bool brokenCode = false }) { final File pubspecFile = fileSystem.file(fileSystem.path.join(directory.path, 'pubspec.yaml')); pubspecFile.writeAsStringSync(''' name: foo_project environment: sdk: '>=2.10.0 <3.0.0' '''); final File dartFile = fileSystem.file(fileSystem.path.join(directory.path, 'lib', 'main.dart')); dartFile.parent.createSync(); dartFile.writeAsStringSync(''' void main() { print('hello world'); ${brokenCode ? 'prints("hello world");' : ''} } '''); } group('analyze --watch', () { testUsingContext('AnalysisServer success', () async { _createSampleProject(tempDir); final Pub pub = Pub( fileSystem: fileSystem, logger: logger, processManager: processManager, platform: const LocalPlatform(), botDetector: globals.botDetector, usage: globals.flutterUsage, ); await pub.get( context: PubContext.flutterTests, directory: tempDir.path, generateSyntheticPackage: false, ); server = AnalysisServer( globals.artifacts.getHostArtifact(HostArtifact.engineDartSdkPath).path, <String>[tempDir.path], fileSystem: fileSystem, platform: platform, processManager: processManager, logger: logger, terminal: terminal, ); int errorCount = 0; final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first; server.onErrors.listen((FileAnalysisErrors errors) => errorCount += errors.errors.length); await server.start(); await onDone; expect(errorCount, 0); }); }); testUsingContext('AnalysisServer errors', () async { _createSampleProject(tempDir, brokenCode: true); final Pub pub = Pub( fileSystem: fileSystem, logger: logger, processManager: processManager, platform: const LocalPlatform(), usage: globals.flutterUsage, botDetector: globals.botDetector, ); await pub.get( context: PubContext.flutterTests, directory: tempDir.path, generateSyntheticPackage: false, ); server = AnalysisServer( globals.artifacts.getHostArtifact(HostArtifact.engineDartSdkPath).path, <String>[tempDir.path], fileSystem: fileSystem, platform: platform, processManager: processManager, logger: logger, terminal: terminal, ); int errorCount = 0; final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first; server.onErrors.listen((FileAnalysisErrors errors) { errorCount += errors.errors.length; }); await server.start(); await onDone; expect(errorCount, greaterThan(0)); }); testUsingContext('Returns no errors when source is error-free', () async { const String contents = "StringBuffer bar = StringBuffer('baz');"; tempDir.childFile('main.dart').writeAsStringSync(contents); server = AnalysisServer( globals.artifacts.getHostArtifact(HostArtifact.engineDartSdkPath).path, <String>[tempDir.path], fileSystem: fileSystem, platform: platform, processManager: processManager, logger: logger, terminal: terminal, ); int errorCount = 0; final Future<bool> onDone = server.onAnalyzing.where((bool analyzing) => analyzing == false).first; server.onErrors.listen((FileAnalysisErrors errors) { errorCount += errors.errors.length; }); await server.start(); await onDone; expect(errorCount, 0); }); testUsingContext('Can run AnalysisService with customized cache location', () async { final StreamController<List<int>> stdin = StreamController<List<int>>(); final FakeProcessManager processManager = FakeProcessManager.list( <FakeCommand>[ FakeCommand( command: const <String>[ 'HostArtifact.engineDartSdkPath/bin/dart', '--disable-dart-dev', 'HostArtifact.engineDartSdkPath/bin/snapshots/analysis_server.dart.snapshot', '--disable-server-feature-completion', '--disable-server-feature-search', '--sdk', 'HostArtifact.engineDartSdkPath', ], stdin: IOSink(stdin.sink), ), ]); final Artifacts artifacts = Artifacts.test(); final AnalyzeCommand command = AnalyzeCommand( terminal: Terminal.test(), artifacts: artifacts, logger: BufferLogger.test(), platform: FakePlatform(), fileSystem: MemoryFileSystem.test(), processManager: processManager, ); final TestFlutterCommandRunner commandRunner = TestFlutterCommandRunner(); commandRunner.addCommand(command); unawaited(commandRunner.run(<String>['analyze', '--watch'])); await stdin.stream.first; expect(processManager, hasNoRemainingExpectations); }); testUsingContext('Can run AnalysisService with customized cache location --watch', () async { final MemoryFileSystem fileSystem = MemoryFileSystem.test(); fileSystem.directory('directoryA').childFile('foo').createSync(recursive: true); final BufferLogger logger = BufferLogger.test(); final Completer<void> completer = Completer<void>(); final StreamController<List<int>> stdin = StreamController<List<int>>(); final FakeProcessManager processManager = FakeProcessManager.list( <FakeCommand>[ FakeCommand( command: const <String>[ 'HostArtifact.engineDartSdkPath/bin/dart', '--disable-dart-dev', 'HostArtifact.engineDartSdkPath/bin/snapshots/analysis_server.dart.snapshot', '--disable-server-feature-completion', '--disable-server-feature-search', '--sdk', 'HostArtifact.engineDartSdkPath', ], stdin: IOSink(stdin.sink), stdout: ''' {"event":"server.status","params":{"analysis":{"isAnalyzing":true}}} {"event":"analysis.errors","params":{"file":"/directoryA/foo","errors":[{"type":"TestError","message":"It's an error.","severity":"warning","code":"500","location":{"file":"/directoryA/foo","startLine": 100,"startColumn":5,"offset":0}}]}} {"event":"server.status","params":{"analysis":{"isAnalyzing":false}}} ''' ), ]); final Artifacts artifacts = Artifacts.test(); final AnalyzeCommand command = AnalyzeCommand( terminal: Terminal.test(), artifacts: artifacts, logger: logger, platform: FakePlatform(), fileSystem: fileSystem, processManager: processManager, ); await FakeAsync().run((FakeAsync time) async { final TestFlutterCommandRunner commandRunner = TestFlutterCommandRunner(); commandRunner.addCommand(command); unawaited(commandRunner.run(<String>['analyze', '--watch'])); while (!logger.statusText.contains('analyzed 1 file')) { time.flushMicrotasks(); } completer.complete(); return completer.future; }); expect(logger.statusText, contains("warning • It's an error • directoryA/foo:100:5 • 500")); expect(logger.statusText, contains('1 issue found. (1 new)')); expect(logger.errorText, isEmpty); expect(processManager, hasNoRemainingExpectations); }); testUsingContext('AnalysisService --watch skips errors from non-files', () async { final BufferLogger logger = BufferLogger.test(); final Completer<void> completer = Completer<void>(); final StreamController<List<int>> stdin = StreamController<List<int>>(); final FakeProcessManager processManager = FakeProcessManager.list( <FakeCommand>[ FakeCommand( command: const <String>[ 'HostArtifact.engineDartSdkPath/bin/dart', '--disable-dart-dev', 'HostArtifact.engineDartSdkPath/bin/snapshots/analysis_server.dart.snapshot', '--disable-server-feature-completion', '--disable-server-feature-search', '--sdk', 'HostArtifact.engineDartSdkPath', ], stdin: IOSink(stdin.sink), stdout: ''' {"event":"server.status","params":{"analysis":{"isAnalyzing":true}}} {"event":"analysis.errors","params":{"file":"/directoryA/bar","errors":[{"type":"TestError","message":"It's an error.","severity":"warning","code":"500","location":{"file":"/directoryA/bar","startLine":100,"startColumn":5,"offset":0}}]}} {"event":"server.status","params":{"analysis":{"isAnalyzing":false}}} ''' ), ]); final Artifacts artifacts = Artifacts.test(); final AnalyzeCommand command = AnalyzeCommand( terminal: Terminal.test(), artifacts: artifacts, logger: logger, platform: FakePlatform(), fileSystem: MemoryFileSystem.test(), processManager: processManager, ); await FakeAsync().run((FakeAsync time) async { final TestFlutterCommandRunner commandRunner = TestFlutterCommandRunner(); commandRunner.addCommand(command); unawaited(commandRunner.run(<String>['analyze', '--watch'])); while (!logger.statusText.contains('analyzed 1 file')) { time.flushMicrotasks(); } completer.complete(); return completer.future; }); expect(logger.statusText, contains('No issues found!')); expect(logger.errorText, isEmpty); expect(processManager, hasNoRemainingExpectations); }); }