mirror of
https://github.com/flutter/packages.git
synced 2025-05-23 19:56:43 +08:00

Updates the tooling to fetch and use `ktfmt` for Kotlin code, the same way it currently does for the Google Java formatter, and applies that formatting (in the default mode; see discussion in the linked issue) to the repository. In the future we could revisit the formatter or mode, but since this currently seems to be the most consistent with our other languages and to google3 formatting this is likely the option we'll want to stick with. Fixes https://github.com/flutter/flutter/issues/118756
797 lines
25 KiB
Dart
797 lines
25 KiB
Dart
// Copyright 2013 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.
|
|
|
|
import 'package:args/command_runner.dart';
|
|
import 'package:file/file.dart';
|
|
import 'package:file/memory.dart';
|
|
import 'package:flutter_plugin_tools/src/common/core.dart';
|
|
import 'package:flutter_plugin_tools/src/common/file_utils.dart';
|
|
import 'package:flutter_plugin_tools/src/format_command.dart';
|
|
import 'package:path/path.dart' as p;
|
|
import 'package:test/test.dart';
|
|
|
|
import 'mocks.dart';
|
|
import 'util.dart';
|
|
|
|
void main() {
|
|
late FileSystem fileSystem;
|
|
late MockPlatform mockPlatform;
|
|
late Directory packagesDir;
|
|
late RecordingProcessRunner processRunner;
|
|
late FormatCommand analyzeCommand;
|
|
late CommandRunner<void> runner;
|
|
late String javaFormatPath;
|
|
late String kotlinFormatPath;
|
|
|
|
setUp(() {
|
|
fileSystem = MemoryFileSystem();
|
|
mockPlatform = MockPlatform();
|
|
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
|
processRunner = RecordingProcessRunner();
|
|
analyzeCommand = FormatCommand(
|
|
packagesDir,
|
|
processRunner: processRunner,
|
|
platform: mockPlatform,
|
|
);
|
|
|
|
// Create the Java and Kotlin formatter files that the command checks for,
|
|
// to avoid a download.
|
|
final p.Context path = analyzeCommand.path;
|
|
javaFormatPath = path.join(path.dirname(path.fromUri(mockPlatform.script)),
|
|
'google-java-format-1.3-all-deps.jar');
|
|
fileSystem.file(javaFormatPath).createSync(recursive: true);
|
|
kotlinFormatPath = path.join(
|
|
path.dirname(path.fromUri(mockPlatform.script)),
|
|
'ktfmt-0.46-jar-with-dependencies.jar');
|
|
fileSystem.file(kotlinFormatPath).createSync(recursive: true);
|
|
|
|
runner = CommandRunner<void>('format_command', 'Test for format_command');
|
|
runner.addCommand(analyzeCommand);
|
|
});
|
|
|
|
/// Returns a modified version of a list of [relativePaths] that are relative
|
|
/// to [package] to instead be relative to [packagesDir].
|
|
List<String> getPackagesDirRelativePaths(
|
|
RepositoryPackage package, List<String> relativePaths) {
|
|
final p.Context path = analyzeCommand.path;
|
|
final String relativeBase =
|
|
path.relative(package.path, from: packagesDir.path);
|
|
return relativePaths
|
|
.map((String relativePath) => path.join(relativeBase, relativePath))
|
|
.toList();
|
|
}
|
|
|
|
/// Returns a list of [count] relative paths to pass to [createFakePlugin]
|
|
/// or [createFakePackage] with name [packageName] such that each path will
|
|
/// be 99 characters long relative to [packagesDir].
|
|
///
|
|
/// This is for each of testing batching, since it means each file will
|
|
/// consume 100 characters of the batch length.
|
|
List<String> get99CharacterPathExtraFiles(String packageName, int count) {
|
|
final int padding = 99 -
|
|
packageName.length -
|
|
1 - // the path separator after the package name
|
|
1 - // the path separator after the padding
|
|
10; // the file name
|
|
const int filenameBase = 10000;
|
|
|
|
final p.Context path = analyzeCommand.path;
|
|
return <String>[
|
|
for (int i = filenameBase; i < filenameBase + count; ++i)
|
|
path.join('a' * padding, '$i.dart'),
|
|
];
|
|
}
|
|
|
|
test('formats .dart files', () async {
|
|
const List<String> files = <String>[
|
|
'lib/a.dart',
|
|
'lib/src/b.dart',
|
|
'lib/src/c.dart',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'dart',
|
|
<String>['format', ...getPackagesDirRelativePaths(plugin, files)],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('does not format .dart files with pragma', () async {
|
|
const List<String> formattedFiles = <String>[
|
|
'lib/a.dart',
|
|
'lib/src/b.dart',
|
|
'lib/src/c.dart',
|
|
];
|
|
const String unformattedFile = 'lib/src/d.dart';
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: <String>[
|
|
...formattedFiles,
|
|
unformattedFile,
|
|
],
|
|
);
|
|
|
|
final p.Context posixContext = p.posix;
|
|
childFileWithSubcomponents(
|
|
plugin.directory, posixContext.split(unformattedFile))
|
|
.writeAsStringSync(
|
|
'// copyright bla bla\n// This file is hand-formatted.\ncode...');
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'dart',
|
|
<String>[
|
|
'format',
|
|
...getPackagesDirRelativePaths(plugin, formattedFiles)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('fails if dart format fails', () async {
|
|
const List<String> files = <String>[
|
|
'lib/a.dart',
|
|
'lib/src/b.dart',
|
|
'lib/src/c.dart',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['dart'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['format'])
|
|
];
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['format'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Failed to format Dart files: exit code 1.'),
|
|
]));
|
|
});
|
|
|
|
test('formats .java files', () async {
|
|
const List<String> files = <String>[
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/a.java',
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/b.java',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
const ProcessCall('java', <String>['-version'], null),
|
|
ProcessCall(
|
|
'java',
|
|
<String>[
|
|
'-jar',
|
|
javaFormatPath,
|
|
'--replace',
|
|
...getPackagesDirRelativePaths(plugin, files)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('fails with a clear message if Java is not in the path', () async {
|
|
const List<String> files = <String>[
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/a.java',
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/b.java',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['java'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['-version'])
|
|
];
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['format'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Unable to run "java". Make sure that it is in your path, or '
|
|
'provide a full path with --java.'),
|
|
]));
|
|
});
|
|
|
|
test('fails if Java formatter fails', () async {
|
|
const List<String> files = <String>[
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/a.java',
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/b.java',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['java'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(
|
|
MockProcess(), <String>['-version']), // check for working java
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['-jar']), // format
|
|
];
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['format'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Failed to format Java files: exit code 1.'),
|
|
]));
|
|
});
|
|
|
|
test('honors --java flag', () async {
|
|
const List<String> files = <String>[
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/a.java',
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/b.java',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format', '--java=/path/to/java']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
const ProcessCall('/path/to/java', <String>['--version'], null),
|
|
ProcessCall(
|
|
'/path/to/java',
|
|
<String>[
|
|
'-jar',
|
|
javaFormatPath,
|
|
'--replace',
|
|
...getPackagesDirRelativePaths(plugin, files)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('formats c-ish files', () async {
|
|
const List<String> files = <String>[
|
|
'ios/Classes/Foo.h',
|
|
'ios/Classes/Foo.m',
|
|
'linux/foo_plugin.cc',
|
|
'macos/Classes/Foo.h',
|
|
'macos/Classes/Foo.mm',
|
|
'windows/foo_plugin.cpp',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
const ProcessCall('clang-format', <String>['--version'], null),
|
|
ProcessCall(
|
|
'clang-format',
|
|
<String>[
|
|
'-i',
|
|
'--style=file',
|
|
...getPackagesDirRelativePaths(plugin, files)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('fails with a clear message if clang-format is not in the path',
|
|
() async {
|
|
const List<String> files = <String>[
|
|
'linux/foo_plugin.cc',
|
|
'macos/Classes/Foo.h',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['clang-format'] =
|
|
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['format'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Unable to run "clang-format". Make sure that it is in your '
|
|
'path, or provide a full path with --clang-format.'),
|
|
]));
|
|
});
|
|
|
|
test('falls back to working clang-format in the path', () async {
|
|
const List<String> files = <String>[
|
|
'linux/foo_plugin.cc',
|
|
'macos/Classes/Foo.h',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
processRunner.mockProcessesForExecutable['clang-format'] =
|
|
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
|
|
processRunner.mockProcessesForExecutable['which'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(
|
|
MockProcess(
|
|
stdout:
|
|
'/usr/local/bin/clang-format\n/path/to/working-clang-format'),
|
|
<String>['-a', 'clang-format'])
|
|
];
|
|
processRunner.mockProcessesForExecutable['/usr/local/bin/clang-format'] =
|
|
<FakeProcessInfo>[FakeProcessInfo(MockProcess(exitCode: 1))];
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
containsAll(<ProcessCall>[
|
|
const ProcessCall(
|
|
'/path/to/working-clang-format', <String>['--version'], null),
|
|
ProcessCall(
|
|
'/path/to/working-clang-format',
|
|
<String>[
|
|
'-i',
|
|
'--style=file',
|
|
...getPackagesDirRelativePaths(plugin, files)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('honors --clang-format flag', () async {
|
|
const List<String> files = <String>[
|
|
'windows/foo_plugin.cpp',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(
|
|
runner, <String>['format', '--clang-format=/path/to/clang-format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
const ProcessCall(
|
|
'/path/to/clang-format', <String>['--version'], null),
|
|
ProcessCall(
|
|
'/path/to/clang-format',
|
|
<String>[
|
|
'-i',
|
|
'--style=file',
|
|
...getPackagesDirRelativePaths(plugin, files)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('fails if clang-format fails', () async {
|
|
const List<String> files = <String>[
|
|
'linux/foo_plugin.cc',
|
|
'macos/Classes/Foo.h',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['clang-format'] =
|
|
<FakeProcessInfo>[
|
|
FakeProcessInfo(MockProcess(),
|
|
<String>['--version']), // check for working clang-format
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['-i']), // format
|
|
];
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['format'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Failed to format C, C++, and Objective-C files: exit code 1.'),
|
|
]));
|
|
});
|
|
|
|
group('kotlin-format', () {
|
|
test('formats .kt files', () async {
|
|
const List<String> files = <String>[
|
|
'android/src/main/kotlin/io/flutter/plugins/a_plugin/a.kt',
|
|
'android/src/main/kotlin/io/flutter/plugins/a_plugin/b.kt',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
const ProcessCall('java', <String>['-version'], null),
|
|
ProcessCall(
|
|
'java',
|
|
<String>[
|
|
'-jar',
|
|
kotlinFormatPath,
|
|
...getPackagesDirRelativePaths(plugin, files)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('fails if Kotlin formatter fails', () async {
|
|
const List<String> files = <String>[
|
|
'android/src/main/kotlin/io/flutter/plugins/a_plugin/a.kt',
|
|
'android/src/main/kotlin/io/flutter/plugins/a_plugin/b.kt',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['java'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(
|
|
MockProcess(), <String>['-version']), // check for working java
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['-jar']), // format
|
|
];
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['format'], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Failed to format Kotlin files: exit code 1.'),
|
|
]));
|
|
});
|
|
});
|
|
|
|
group('swift-format', () {
|
|
test('formats Swift if --swift-format flag is provided', () async {
|
|
const List<String> files = <String>[
|
|
'macos/foo.swift',
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(
|
|
runner, <String>['format', '--swift-format=/path/to/swift-format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
orderedEquals(<ProcessCall>[
|
|
ProcessCall(
|
|
'/path/to/swift-format',
|
|
<String>['-i', ...getPackagesDirRelativePaths(plugin, files)],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('skips Swift if --swift-format flag is not provided', () async {
|
|
const List<String> files = <String>[
|
|
'macos/foo.swift',
|
|
];
|
|
createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: files,
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(processRunner.recordedCalls, orderedEquals(<ProcessCall>[]));
|
|
});
|
|
|
|
test('fails if swift-format fails', () async {
|
|
const List<String> files = <String>[
|
|
'macos/foo.swift',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['swift-format'] =
|
|
<FakeProcessInfo>[
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['-i']),
|
|
];
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
runner, <String>['format', '--swift-format=swift-format'],
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Failed to format Swift files: exit code 1.'),
|
|
]));
|
|
});
|
|
});
|
|
|
|
test('skips known non-repo files', () async {
|
|
const List<String> skipFiles = <String>[
|
|
'/example/build/SomeFramework.framework/Headers/SomeFramework.h',
|
|
'/example/Pods/APod.framework/Headers/APod.h',
|
|
'.dart_tool/internals/foo.cc',
|
|
'.dart_tool/internals/Bar.java',
|
|
'.dart_tool/internals/baz.dart',
|
|
];
|
|
const List<String> clangFiles = <String>['ios/Classes/Foo.h'];
|
|
const List<String> dartFiles = <String>['lib/a.dart'];
|
|
const List<String> javaFiles = <String>[
|
|
'android/src/main/java/io/flutter/plugins/a_plugin/a.java'
|
|
];
|
|
final RepositoryPackage plugin = createFakePlugin(
|
|
'a_plugin',
|
|
packagesDir,
|
|
extraFiles: <String>[
|
|
...skipFiles,
|
|
// Include some files that should be formatted to validate that it's
|
|
// correctly filtering even when running the commands.
|
|
...clangFiles,
|
|
...dartFiles,
|
|
...javaFiles,
|
|
],
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
containsAll(<ProcessCall>[
|
|
ProcessCall(
|
|
'clang-format',
|
|
<String>[
|
|
'-i',
|
|
'--style=file',
|
|
...getPackagesDirRelativePaths(plugin, clangFiles)
|
|
],
|
|
packagesDir.path),
|
|
ProcessCall(
|
|
'dart',
|
|
<String>[
|
|
'format',
|
|
...getPackagesDirRelativePaths(plugin, dartFiles)
|
|
],
|
|
packagesDir.path),
|
|
ProcessCall(
|
|
'java',
|
|
<String>[
|
|
'-jar',
|
|
javaFormatPath,
|
|
'--replace',
|
|
...getPackagesDirRelativePaths(plugin, javaFiles)
|
|
],
|
|
packagesDir.path),
|
|
]));
|
|
});
|
|
|
|
test('fails if files are changed with --fail-on-change', () async {
|
|
const List<String> files = <String>[
|
|
'linux/foo_plugin.cc',
|
|
'macos/Classes/Foo.h',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
const String changedFilePath = 'packages/a_plugin/linux/foo_plugin.cc';
|
|
processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(
|
|
MockProcess(stdout: changedFilePath), <String>['ls-files']),
|
|
];
|
|
|
|
Error? commandError;
|
|
final List<String> output =
|
|
await runCapturingPrint(runner, <String>['format', '--fail-on-change'],
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('These files are not formatted correctly'),
|
|
contains(changedFilePath),
|
|
// Ensure the error message links to instructions.
|
|
contains(
|
|
'https://github.com/flutter/packages/blob/main/script/tool/README.md#format-code'),
|
|
contains('patch -p1 <<DONE'),
|
|
]));
|
|
});
|
|
|
|
test('fails if git ls-files fails', () async {
|
|
const List<String> files = <String>[
|
|
'linux/foo_plugin.cc',
|
|
'macos/Classes/Foo.h',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['ls-files'])
|
|
];
|
|
Error? commandError;
|
|
final List<String> output =
|
|
await runCapturingPrint(runner, <String>['format', '--fail-on-change'],
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Unable to determine changed files.'),
|
|
]));
|
|
});
|
|
|
|
test('reports git diff failures', () async {
|
|
const List<String> files = <String>[
|
|
'linux/foo_plugin.cc',
|
|
'macos/Classes/Foo.h',
|
|
];
|
|
createFakePlugin('a_plugin', packagesDir, extraFiles: files);
|
|
|
|
const String changedFilePath = 'packages/a_plugin/linux/foo_plugin.cc';
|
|
processRunner.mockProcessesForExecutable['git'] = <FakeProcessInfo>[
|
|
FakeProcessInfo(
|
|
MockProcess(stdout: changedFilePath), <String>['ls-files']),
|
|
FakeProcessInfo(MockProcess(exitCode: 1), <String>['diff']),
|
|
];
|
|
|
|
Error? commandError;
|
|
final List<String> output =
|
|
await runCapturingPrint(runner, <String>['format', '--fail-on-change'],
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('These files are not formatted correctly'),
|
|
contains(changedFilePath),
|
|
contains('Unable to determine diff.'),
|
|
]));
|
|
});
|
|
|
|
test('Batches moderately long file lists on Windows', () async {
|
|
mockPlatform.isWindows = true;
|
|
|
|
const String pluginName = 'a_plugin';
|
|
// -1 since the command itself takes some length.
|
|
const int batchSize = (windowsCommandLineMax ~/ 100) - 1;
|
|
|
|
// Make the file list one file longer than would fit in the batch.
|
|
final List<String> batch1 =
|
|
get99CharacterPathExtraFiles(pluginName, batchSize + 1);
|
|
final String extraFile = batch1.removeLast();
|
|
|
|
createFakePlugin(
|
|
pluginName,
|
|
packagesDir,
|
|
extraFiles: <String>[...batch1, extraFile],
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
// Ensure that it was batched...
|
|
expect(processRunner.recordedCalls.length, 2);
|
|
// ... and that the spillover into the second batch was only one file.
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(
|
|
ProcessCall(
|
|
'dart',
|
|
<String>[
|
|
'format',
|
|
'$pluginName\\$extraFile',
|
|
],
|
|
packagesDir.path),
|
|
));
|
|
});
|
|
|
|
// Validates that the Windows limit--which is much lower than the limit on
|
|
// other platforms--isn't being used on all platforms, as that would make
|
|
// formatting slower on Linux and macOS.
|
|
test('Does not batch moderately long file lists on non-Windows', () async {
|
|
const String pluginName = 'a_plugin';
|
|
// -1 since the command itself takes some length.
|
|
const int batchSize = (windowsCommandLineMax ~/ 100) - 1;
|
|
|
|
// Make the file list one file longer than would fit in a Windows batch.
|
|
final List<String> batch =
|
|
get99CharacterPathExtraFiles(pluginName, batchSize + 1);
|
|
|
|
createFakePlugin(
|
|
pluginName,
|
|
packagesDir,
|
|
extraFiles: batch,
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
expect(processRunner.recordedCalls.length, 1);
|
|
});
|
|
|
|
test('Batches extremely long file lists on non-Windows', () async {
|
|
const String pluginName = 'a_plugin';
|
|
// -1 since the command itself takes some length.
|
|
const int batchSize = (nonWindowsCommandLineMax ~/ 100) - 1;
|
|
|
|
// Make the file list one file longer than would fit in the batch.
|
|
final List<String> batch1 =
|
|
get99CharacterPathExtraFiles(pluginName, batchSize + 1);
|
|
final String extraFile = batch1.removeLast();
|
|
|
|
createFakePlugin(
|
|
pluginName,
|
|
packagesDir,
|
|
extraFiles: <String>[...batch1, extraFile],
|
|
);
|
|
|
|
await runCapturingPrint(runner, <String>['format']);
|
|
|
|
// Ensure that it was batched...
|
|
expect(processRunner.recordedCalls.length, 2);
|
|
// ... and that the spillover into the second batch was only one file.
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(
|
|
ProcessCall(
|
|
'dart',
|
|
<String>[
|
|
'format',
|
|
'$pluginName/$extraFile',
|
|
],
|
|
packagesDir.path),
|
|
));
|
|
});
|
|
}
|