analyze_once_test.dart 8.45 KB
Newer Older
1 2 3 4 5 6 7 8
// 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';
9
import 'package:flutter_tools/src/base/platform.dart';
10 11 12 13 14
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';

15 16
import '../../src/common.dart';
import '../../src/context.dart';
17

18
/// Test case timeout for tests involving project analysis.
19
const Timeout allowForSlowAnalyzeTests = Timeout.factor(5.0);
20

21 22 23 24 25
final Generator _kNoColorTerminalPlatform = () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false;
final Map<Type, Generator> noColorTerminalOverride = <Type, Generator>{
  Platform: _kNoColorTerminalPlatform,
};

26
void main() {
27 28
  final String analyzerSeparator = platform.isWindows ? '-' : '•';

29 30
  group('analyze once', () {
    Directory tempDir;
31
    String projectPath;
32 33 34 35
    File libMain;

    setUpAll(() {
      Cache.disableLocking();
36
      tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_1.').absolute;
37 38
      projectPath = fs.path.join(tempDir.path, 'flutter_project');
      libMain = fs.file(fs.path.join(projectPath, 'lib', 'main.dart'));
39 40 41
    });

    tearDownAll(() {
42
      tryToDelete(tempDir);
43 44 45 46 47
    });

    // Create a project to be analyzed
    testUsingContext('flutter create', () async {
      await runCommand(
48
        command: CreateCommand(),
49
        arguments: <String>['--no-wrap', 'create', projectPath],
50 51
        statusTextContains: <String>[
          'All done!',
52
          'Your application code is in ${fs.path.normalize(fs.path.join(fs.path.relative(projectPath), 'lib', 'main.dart'))}',
53 54 55
        ],
      );
      expect(libMain.existsSync(), isTrue);
56
    }, timeout: allowForRemotePubInvocation);
57 58

    // Analyze in the current directory - no arguments
59
    testUsingContext('working directory', () async {
60
      await runCommand(
61
        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
62 63 64
        arguments: <String>['analyze'],
        statusTextContains: <String>['No issues found!'],
      );
65
    }, timeout: allowForSlowAnalyzeTests);
66 67

    // Analyze a specific file outside the current directory
68
    testUsingContext('passing one file throws', () async {
69
      await runCommand(
70
        command: AnalyzeCommand(),
71
        arguments: <String>['analyze', libMain.path],
72 73
        toolExit: true,
        exitMessageContains: 'is not a directory',
74 75 76 77
      );
    });

    // Analyze in the current directory - no arguments
78
    testUsingContext('working directory with errors', () async {
79
      // Break the code to produce the "The parameter 'onPressed' is required" hint
80 81 82 83 84 85
      // 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(
86 87
        'onPressed: _incrementCounter,',
        '// onPressed: _incrementCounter,',
88 89 90 91 92 93 94
      );
      source = source.replaceFirst(
        '_counter++;',
        '_counter++; throw "an error message";',
      );
      await libMain.writeAsString(source);

95
      // Analyze in the current directory - no arguments
96
      await runCommand(
97
        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
98 99 100
        arguments: <String>['analyze'],
        statusTextContains: <String>[
          'Analyzing',
101
          'warning $analyzerSeparator The parameter \'onPressed\' is required',
102
          'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
103
        ],
104
        exitMessageContains: '2 issues found.',
105 106
        toolExit: true,
      );
107
    }, timeout: allowForSlowAnalyzeTests, overrides: noColorTerminalOverride);
108 109

    // Analyze in the current directory - no arguments
110
    testUsingContext('working directory with local options', () async {
111 112
      // Insert an analysis_options.yaml file in the project
      // which will trigger a lint for broken code that was inserted earlier
113
      final File optionsFile = fs.file(fs.path.join(projectPath, 'analysis_options.yaml'));
114 115 116 117 118 119 120
      await optionsFile.writeAsString('''
  include: package:flutter/analysis_options_user.yaml
  linter:
    rules:
      - only_throw_errors
  ''');

121
      // Analyze in the current directory - no arguments
122
      await runCommand(
123
        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
124 125 126
        arguments: <String>['analyze'],
        statusTextContains: <String>[
          'Analyzing',
127
          'warning $analyzerSeparator The parameter \'onPressed\' is required',
128
          'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
129
          'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
130
        ],
131
        exitMessageContains: '3 issues found.',
132 133
        toolExit: true,
      );
134
    }, timeout: allowForSlowAnalyzeTests, overrides: noColorTerminalOverride);
135

136
    testUsingContext('no duplicate issues', () async {
137
      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
138 139 140 141 142 143

      try {
        final File foo = fs.file(fs.path.join(tempDir.path, 'foo.dart'));
        foo.writeAsStringSync('''
import 'bar.dart';

144
void foo() => bar();
145 146 147 148 149 150 151 152 153 154 155 156
''');

        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(
157
          command: AnalyzeCommand(workingDirectory: tempDir),
158 159 160 161
          arguments: <String>['analyze'],
          statusTextContains: <String>[
            'Analyzing',
          ],
162
          exitMessageContains: '1 issue found.',
163 164 165
          toolExit: true,
        );
      } finally {
166
        tryToDelete(tempDir);
167
      }
168
    }, overrides: noColorTerminalOverride);
169

170
    testUsingContext('returns no issues when source is error-free', () async {
171
      const String contents = '''
172 173
StringBuffer bar = StringBuffer('baz');
''';
174
      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
175 176 177
      tempDir.childFile('main.dart').writeAsStringSync(contents);
      try {
        await runCommand(
178
          command: AnalyzeCommand(workingDirectory: fs.directory(tempDir)),
179
          arguments: <String>['analyze'],
180 181 182
          statusTextContains: <String>['No issues found!'],
        );
      } finally {
183
        tryToDelete(tempDir);
184
      }
185
    }, overrides: noColorTerminalOverride);
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201

    testUsingContext('returns no issues for todo comments', () async {
      const String contents = '''
// TODO(foobar):
StringBuffer bar = StringBuffer('baz');
''';
      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_4.');
      tempDir.childFile('main.dart').writeAsStringSync(contents);
      try {
        await runCommand(
          command: AnalyzeCommand(workingDirectory: fs.directory(tempDir)),
          arguments: <String>['analyze'],
          statusTextContains: <String>['No issues found!'],
        );
      } finally {
        tryToDelete(tempDir);
202
      }
203
    }, overrides: noColorTerminalOverride);
204 205 206 207 208 209 210 211 212 213 214 215 216
  });
}

void assertContains(String text, List<String> patterns) {
  if (patterns == null) {
    expect(text, isEmpty);
  } else {
    for (String pattern in patterns) {
      expect(text, contains(pattern));
    }
  }
}

217
Future<void> runCommand({
218 219 220 221
  FlutterCommand command,
  List<String> arguments,
  List<String> statusTextContains,
  List<String> errorTextContains,
222
  bool toolExit = false,
223
  String exitMessageContains,
224 225 226 227 228
}) async {
  try {
    arguments.insert(0, '--flutter-root=${Cache.flutterRoot}');
    await createTestCommandRunner(command).run(arguments);
    expect(toolExit, isFalse, reason: 'Expected ToolExit exception');
229
  } on ToolExit catch (e) {
230 231 232 233
    if (!toolExit) {
      testLogger.clear();
      rethrow;
    }
234 235 236
    if (exitMessageContains != null) {
      expect(e.message, contains(exitMessageContains));
    }
237 238 239
  }
  assertContains(testLogger.statusText, statusTextContains);
  assertContains(testLogger.errorText, errorTextContains);
240

241 242
  testLogger.clear();
}