blob: 6177d22e0407b2b57d9eabc4866c1a1d7e3a0e53 [file]
// 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.
import 'dart:convert';
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart' as io;
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/build_system/build_system.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/packages.dart';
import 'package:flutter_tools/src/dart/pub.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:test/fake.dart';
import 'package:unified_analytics/unified_analytics.dart';
import '../../src/common.dart';
import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart' hide FakeProcess;
import '../../src/package_config.dart';
import '../../src/test_build_system.dart';
import '../../src/test_flutter_command_runner.dart';
const minimalV2EmbeddingManifest = r'''
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:name="${applicationName}">
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
''';
void main() {
late FileSystem fileSystem;
late _PubTestProcessManager processManager;
late BufferLogger logger;
setUp(() {
Cache.disableLocking();
fileSystem = MemoryFileSystem.test();
processManager = _PubTestProcessManager(fileSystem);
logger = BufferLogger.test();
});
tearDown(() {
Cache.enableLocking();
});
PackagesGetCommand createPackagesGetCommand(
String commandName,
String description,
PubContext context,
) {
final toolContext = FakeToolContext(
fs: fileSystem,
logger: logger,
processManager: processManager,
);
return PackagesGetCommand(
commandName,
description,
context,
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
toolContext: toolContext,
);
}
testWithoutContext('pub shows help', () async {
final toolContext = FakeToolContext(fs: fileSystem, logger: logger);
final command = PackagesCommand(
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
toolContext: toolContext,
);
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['pub']);
expect(
logger.statusText,
allOf(
contains('Commands for managing Flutter packages.'),
contains('Usage: flutter pub <subcommand> [arguments]'),
),
);
});
testWithoutContext(
'pub get usage values are resilient to missing package config files before running "pub get"',
() async {
fileSystem.currentDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
fileSystem.currentDirectory.childFile('.flutter-plugins').createSync();
fileSystem.currentDirectory.childFile('.flutter-plugins-dependencies').createSync();
fileSystem.currentDirectory.childDirectory('android').childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(minimalV2EmbeddingManifest);
final PackagesGetCommand command = createPackagesGetCommand('get', '', PubContext.pubGet);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
await commandRunner.run(<String>['get']);
expect(
await command.unifiedAnalyticsUsageValues('pub'),
Event.commandUsageValues(
workflow: 'pub',
commandHasTerminal: false,
packagesNumberPlugins: 0,
packagesProjectModule: false,
packagesAndroidEmbeddingVersion: 'v2',
),
);
},
);
testWithoutContext(
'pub get usage values are resilient to poorly formatted package config before "pub get"',
() async {
fileSystem.currentDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
fileSystem.currentDirectory.childFile('.flutter-plugins').createSync();
fileSystem.currentDirectory.childFile('.flutter-plugins-dependencies').createSync();
fileSystem.currentDirectory.childFile('.dart_tool/package_config.json')
..createSync(recursive: true)
..writeAsBytesSync(<int>[0]);
fileSystem.currentDirectory.childDirectory('android').childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(minimalV2EmbeddingManifest);
final PackagesGetCommand command = createPackagesGetCommand('get', '', PubContext.pubGet);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
await commandRunner.run(<String>['get']);
expect(
await command.unifiedAnalyticsUsageValues('pub'),
Event.commandUsageValues(
workflow: 'pub',
commandHasTerminal: false,
packagesNumberPlugins: 0,
packagesProjectModule: false,
packagesAndroidEmbeddingVersion: 'v2',
),
);
},
);
testWithoutContext('pub get on target directory', () async {
fileSystem.currentDirectory.childDirectory('target').createSync();
final Directory targetDirectory = fileSystem.currentDirectory.childDirectory('target');
targetDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
final PackagesGetCommand command = createPackagesGetCommand('get', '', PubContext.pubGet);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
await commandRunner.run(<String>['get', '--directory=${targetDirectory.path}']);
final FlutterProject rootProject = FlutterProject.fromDirectoryTest(targetDirectory, logger);
final File packageConfigFile = rootProject.dartTool.childFile('package_config.json');
expect(packageConfigFile.existsSync(), true);
expect(json.decode(packageConfigFile.readAsStringSync()), <String, Object>{
'configVersion': 2,
'packages': <Object?>[
<String, Object?>{
'name': 'my_app',
'rootUri': '../',
'packageUri': 'lib/',
'languageVersion': '3.7',
},
],
});
});
testWithoutContext("pub get doesn't treat unknown flag as directory", () async {
fileSystem.currentDirectory.childDirectory('target').createSync();
fileSystem.currentDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
final PackagesGetCommand command = createPackagesGetCommand('get', '', PubContext.pubGet);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
await commandRunner.run(<String>['get', '--unknown-flag']);
expect(
processManager.lastCommand,
containsAllInOrder(<String>['get', '--unknown-flag', '--example', '--directory', '.']),
);
});
testWithoutContext("pub get doesn't treat -v as directory", () async {
fileSystem.currentDirectory.childDirectory('target').createSync();
fileSystem.currentDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
final PackagesGetCommand command = createPackagesGetCommand('get', '', PubContext.pubGet);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
await commandRunner.run(<String>['get', '-v']);
expect(
processManager.lastCommand,
containsAllInOrder(<String>['get', '-v', '--example', '--directory', '.']),
);
});
// Regression test for https://github.com/flutter/flutter/issues/144898
// Regression test for https://github.com/flutter/flutter/issues/160145
testWithoutContext("pub add doesn't treat dependency syntax as directory", () async {
fileSystem.currentDirectory.childDirectory('target').createSync();
fileSystem.currentDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
fileSystem.currentDirectory.childDirectory('example').createSync(recursive: true);
fileSystem.currentDirectory.childDirectory('android').childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(minimalV2EmbeddingManifest);
final PackagesGetCommand command = createPackagesGetCommand('add', '', PubContext.pubAdd);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
const availableSyntax = <String>[
'foo:{"path":"../foo"}',
'foo:{"hosted":"my-pub.dev"}',
'foo:{"sdk":"flutter"}',
'foo:{"git":"https://github.com/foo/foo"}',
];
for (final syntax in availableSyntax) {
await commandRunner.run(<String>['add', syntax]);
expect(
processManager.lastCommand,
containsAllInOrder(<String>['add', syntax, '--example', '--directory', '.']),
);
}
});
testWithoutContext(
"pub get skips example directory if it doesn't contain a pubspec.yaml",
() async {
fileSystem.currentDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
fileSystem.currentDirectory.childDirectory('example').createSync(recursive: true);
fileSystem.currentDirectory.childDirectory('android').childFile('AndroidManifest.xml')
..createSync(recursive: true)
..writeAsStringSync(minimalV2EmbeddingManifest);
final PackagesGetCommand command = createPackagesGetCommand('get', '', PubContext.pubGet);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
await commandRunner.run(<String>['get']);
expect(
await command.unifiedAnalyticsUsageValues('pub'),
Event.commandUsageValues(
workflow: 'pub',
commandHasTerminal: false,
packagesNumberPlugins: 0,
packagesProjectModule: false,
packagesAndroidEmbeddingVersion: 'v2',
),
);
},
);
testWithoutContext('pub get throws error on missing directory', () async {
final PackagesGetCommand command = createPackagesGetCommand('get', '', PubContext.pubGet);
final CommandRunner<void> commandRunner = createTestCommandRunner(command);
try {
await commandRunner.run(<String>['get', '--directory=missing_dir']);
fail('expected an exception');
} on Exception catch (e) {
expect(e.toString(), contains('Expected to find project root in missing_dir'));
}
});
testWithoutContext(
'packages forward command forwards command name and arguments to pub',
() async {
final toolContext = FakeToolContext(
fs: fileSystem,
logger: logger,
processManager: processManager,
);
fileSystem.currentDirectory.childFile('pubspec.yaml').writeAsStringSync('name: my_app');
final command = PackagesCommand(
buildSystem: TestBuildSystem.all(BuildResult(success: true)),
toolContext: toolContext,
);
final CommandRunner<void> runner = createTestCommandRunner(command);
await runner.run(<String>['pub', 'outdated', '--json']);
expect(
processManager.lastCommand,
containsAllInOrder(<String>['pub', '--suppress-analytics', 'outdated', '--json']),
);
},
);
}
class _PubTestProcessManager extends Fake implements ProcessManager {
_PubTestProcessManager(this.fileSystem);
final FileSystem fileSystem;
List<String>? lastCommand;
@override
bool canRun(Object? executable, {String? workingDirectory}) => true;
@override
Future<io.Process> start(
List<Object> command, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
io.ProcessStartMode mode = io.ProcessStartMode.normal,
}) async {
final stringCommand = <String>[for (final e in command) e.toString()];
lastCommand = stringCommand;
String? dir;
const directoryFlagPrefix = '--directory=';
for (final (index, arg) in stringCommand.indexed) {
if (arg == '--directory' && index + 1 < stringCommand.length) {
dir = stringCommand[index + 1];
break;
}
if (arg.startsWith(directoryFlagPrefix)) {
dir = arg.substring(directoryFlagPrefix.length);
break;
}
}
final Directory targetDir = dir != null
? fileSystem.directory(dir)
: fileSystem.currentDirectory;
writePackageConfigFiles(directory: targetDir, mainLibName: 'my_app');
return FakeProcess();
}
@override
io.ProcessResult runSync(
List<Object> command, {
String? workingDirectory,
Map<String, String>? environment,
bool includeParentEnvironment = true,
bool runInShell = false,
covariant Object? stdoutEncoding = io.systemEncoding,
covariant Object? stderrEncoding = io.systemEncoding,
}) {
if (command.contains('tag')) {
return io.ProcessResult(1, 0, '1.2.3', '');
}
return io.ProcessResult(1, 0, '1234567890abcdef', '');
}
}