analyze_once_test.dart 8.75 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8
// 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
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/analyze.dart';
import 'package:flutter_tools/src/commands/create.dart';
13
import 'package:flutter_tools/src/dart/pub.dart';
14 15
import 'package:flutter_tools/src/runner/flutter_command.dart';

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

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

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

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

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

    tearDownAll(() {
40
      tryToDelete(tempDir);
41 42 43 44 45
    });

    // Create a project to be analyzed
    testUsingContext('flutter create', () async {
      await runCommand(
46
        command: CreateCommand(),
47
        arguments: <String>['--no-wrap', 'create', projectPath],
48 49
        statusTextContains: <String>[
          'All done!',
50
          'Your application code is in ${fs.path.normalize(fs.path.join(fs.path.relative(projectPath), 'lib', 'main.dart'))}',
51 52 53
        ],
      );
      expect(libMain.existsSync(), isTrue);
54
    }, overrides: <Type, Generator>{
55 56
      Pub: () => const Pub(),
    });
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
    }, overrides: <Type, Generator>{
66 67
      Pub: () => const Pub(),
    });
68 69

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

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

99
      // Analyze in the current directory - no arguments
100
      await runCommand(
101
        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
102 103 104
        arguments: <String>['analyze'],
        statusTextContains: <String>[
          'Analyzing',
105
          'warning $analyzerSeparator The parameter \'onPressed\' is required',
106
          'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
107
        ],
108
        exitMessageContains: '2 issues found.',
109 110
        toolExit: true,
      );
111
    }, overrides: <Type, Generator>{
112 113 114
      Pub: () => const Pub(),
      ...noColorTerminalOverride,
    });
115 116

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

128
      // Analyze in the current directory - no arguments
129
      await runCommand(
130
        command: AnalyzeCommand(workingDirectory: fs.directory(projectPath)),
131 132 133
        arguments: <String>['analyze'],
        statusTextContains: <String>[
          'Analyzing',
134
          'warning $analyzerSeparator The parameter \'onPressed\' is required',
135
          'info $analyzerSeparator The declaration \'_incrementCounter\' isn\'t',
136
          'info $analyzerSeparator Only throw instances of classes extending either Exception or Error',
137
        ],
138
        exitMessageContains: '3 issues found.',
139 140
        toolExit: true,
      );
141
    }, overrides: <Type, Generator>{
142 143 144
      Pub: () => const Pub(),
      ...noColorTerminalOverride
    });
145

146
    testUsingContext('no duplicate issues', () async {
147
      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_2.').absolute;
148 149 150 151 152 153

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

154
void foo() => bar();
155 156 157 158 159 160 161 162 163 164 165 166
''');

        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(
167
          command: AnalyzeCommand(workingDirectory: tempDir),
168 169 170 171
          arguments: <String>['analyze'],
          statusTextContains: <String>[
            'Analyzing',
          ],
172
          exitMessageContains: '1 issue found.',
173 174 175
          toolExit: true,
        );
      } finally {
176
        tryToDelete(tempDir);
177
      }
178 179 180 181
    }, overrides: <Type, Generator>{
      Pub: () => const Pub(),
      ...noColorTerminalOverride
    });
182

183
    testUsingContext('returns no issues when source is error-free', () async {
184
      const String contents = '''
185 186
StringBuffer bar = StringBuffer('baz');
''';
187
      final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_analyze_once_test_3.');
188 189 190
      tempDir.childFile('main.dart').writeAsStringSync(contents);
      try {
        await runCommand(
191
          command: AnalyzeCommand(workingDirectory: fs.directory(tempDir)),
192
          arguments: <String>['analyze'],
193 194 195
          statusTextContains: <String>['No issues found!'],
        );
      } finally {
196
        tryToDelete(tempDir);
197
      }
198 199 200 201
    }, overrides: <Type, Generator>{
      Pub: () => const Pub(),
      ...noColorTerminalOverride
    });
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

    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);
218
      }
219 220 221 222
    }, overrides: <Type, Generator>{
      Pub: () => const Pub(),
      ...noColorTerminalOverride
    });
223 224 225 226 227 228 229 230 231 232 233 234 235
  });
}

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

236
Future<void> runCommand({
237 238 239 240
  FlutterCommand command,
  List<String> arguments,
  List<String> statusTextContains,
  List<String> errorTextContains,
241
  bool toolExit = false,
242
  String exitMessageContains,
243 244 245 246 247
}) async {
  try {
    arguments.insert(0, '--flutter-root=${Cache.flutterRoot}');
    await createTestCommandRunner(command).run(arguments);
    expect(toolExit, isFalse, reason: 'Expected ToolExit exception');
248
  } on ToolExit catch (e) {
249 250 251 252
    if (!toolExit) {
      testLogger.clear();
      rethrow;
    }
253 254 255
    if (exitMessageContains != null) {
      expect(e.message, contains(exitMessageContains));
    }
256 257 258
  }
  assertContains(testLogger.statusText, statusTextContains);
  assertContains(testLogger.errorText, errorTextContains);
259

260 261
  testLogger.clear();
}