mirror of
https://github.com/flutter/packages.git
synced 2025-05-31 21:48:04 +08:00
923 lines
31 KiB
Dart
923 lines
31 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 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io' as io;
|
|
|
|
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/publish_command.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http/testing.dart';
|
|
import 'package:mockito/mockito.dart';
|
|
import 'package:platform/platform.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'common/package_command_test.mocks.dart';
|
|
import 'mocks.dart';
|
|
import 'util.dart';
|
|
|
|
void main() {
|
|
final String flutterCommand = getFlutterCommand(const LocalPlatform());
|
|
|
|
late Directory packagesDir;
|
|
late MockGitDir gitDir;
|
|
late TestProcessRunner processRunner;
|
|
late CommandRunner<void> commandRunner;
|
|
late MockStdin mockStdin;
|
|
late FileSystem fileSystem;
|
|
// Map of package name to mock response.
|
|
late Map<String, Map<String, dynamic>> mockHttpResponses;
|
|
|
|
void createMockCredentialFile() {
|
|
final String credentialPath = PublishCommand.getCredentialPath();
|
|
fileSystem.file(credentialPath)
|
|
..createSync(recursive: true)
|
|
..writeAsStringSync('some credential');
|
|
}
|
|
|
|
setUp(() async {
|
|
fileSystem = MemoryFileSystem();
|
|
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
|
processRunner = TestProcessRunner();
|
|
|
|
mockHttpResponses = <String, Map<String, dynamic>>{};
|
|
final MockClient mockClient = MockClient((http.Request request) async {
|
|
final String packageName =
|
|
request.url.pathSegments.last.replaceAll('.json', '');
|
|
final Map<String, dynamic>? response = mockHttpResponses[packageName];
|
|
if (response != null) {
|
|
return http.Response(json.encode(response), 200);
|
|
}
|
|
// Default to simulating the plugin never having been published.
|
|
return http.Response('', 404);
|
|
});
|
|
|
|
gitDir = MockGitDir();
|
|
when(gitDir.path).thenReturn(packagesDir.parent.path);
|
|
when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
|
|
.thenAnswer((Invocation invocation) {
|
|
final List<String> arguments =
|
|
invocation.positionalArguments[0]! as List<String>;
|
|
// Route git calls through the process runner, to make mock output
|
|
// consistent with outer processes. Attach the first argument to the
|
|
// command to make targeting the mock results easier.
|
|
final String gitCommand = arguments.removeAt(0);
|
|
return processRunner.run('git-$gitCommand', arguments);
|
|
});
|
|
|
|
mockStdin = MockStdin();
|
|
commandRunner = CommandRunner<void>('tester', '')
|
|
..addCommand(PublishCommand(
|
|
packagesDir,
|
|
processRunner: processRunner,
|
|
stdinput: mockStdin,
|
|
gitDir: gitDir,
|
|
httpClient: mockClient,
|
|
));
|
|
});
|
|
|
|
group('Initial validation', () {
|
|
test('refuses to proceed with dirty files', () async {
|
|
final RepositoryPackage plugin =
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
processRunner.mockProcessesForExecutable['git-status'] = <io.Process>[
|
|
MockProcess(stdout: '?? ${plugin.directory.childFile('tmp').path}\n')
|
|
];
|
|
|
|
Error? commandError;
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains("There are files in the package directory that haven't "
|
|
'been saved in git. Refusing to publish these files:\n\n'
|
|
'?? /packages/foo/tmp\n\n'
|
|
'If the directory should be clean, you can run `git clean -xdf && '
|
|
'git reset --hard HEAD` to wipe all local changes.'),
|
|
contains('foo:\n'
|
|
' uncommitted changes'),
|
|
]));
|
|
});
|
|
|
|
test("fails immediately if the remote doesn't exist", () async {
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
processRunner.mockProcessesForExecutable['git-remote'] = <io.Process>[
|
|
MockProcess(exitCode: 1),
|
|
];
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(
|
|
commandRunner, <String>['publish', '--packages=foo'],
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Unable to find URL for remote upstream; cannot push tags'),
|
|
]));
|
|
});
|
|
});
|
|
|
|
group('Publishes package', () {
|
|
test('while showing all output from pub publish to the user', () async {
|
|
createFakePlugin('plugin1', packagesDir, examples: <String>[]);
|
|
createFakePlugin('plugin2', packagesDir, examples: <String>[]);
|
|
|
|
processRunner.mockProcessesForExecutable[flutterCommand] = <io.Process>[
|
|
MockProcess(
|
|
stdout: 'Foo',
|
|
stderr: 'Bar',
|
|
stdoutEncoding: utf8,
|
|
stderrEncoding: utf8), // pub publish for plugin1
|
|
MockProcess(
|
|
stdout: 'Baz',
|
|
stdoutEncoding: utf8,
|
|
stderrEncoding: utf8), // pub publish for plugin1
|
|
];
|
|
|
|
final List<String> output = await runCapturingPrint(
|
|
commandRunner, <String>['publish', '--packages=plugin1,plugin2']);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Running `pub publish ` in /packages/plugin1...'),
|
|
contains('Foo'),
|
|
contains('Bar'),
|
|
contains('Package published!'),
|
|
contains('Running `pub publish ` in /packages/plugin2...'),
|
|
contains('Baz'),
|
|
contains('Package published!'),
|
|
]));
|
|
});
|
|
|
|
test('forwards input from the user to `pub publish`', () async {
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
mockStdin.mockUserInputs.add(utf8.encode('user input'));
|
|
|
|
await runCapturingPrint(
|
|
commandRunner, <String>['publish', '--packages=foo']);
|
|
|
|
expect(processRunner.mockPublishProcess.stdinMock.lines,
|
|
contains('user input'));
|
|
});
|
|
|
|
test('forwards --pub-publish-flags to pub publish', () async {
|
|
final RepositoryPackage plugin =
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
'--pub-publish-flags',
|
|
'--dry-run,--server=bar'
|
|
]);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(ProcessCall(
|
|
flutterCommand,
|
|
const <String>['pub', 'publish', '--dry-run', '--server=bar'],
|
|
plugin.path)));
|
|
});
|
|
|
|
test(
|
|
'--skip-confirmation flag automatically adds --force to --pub-publish-flags',
|
|
() async {
|
|
createMockCredentialFile();
|
|
final RepositoryPackage plugin =
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
'--skip-confirmation',
|
|
'--pub-publish-flags',
|
|
'--server=bar'
|
|
]);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(ProcessCall(
|
|
flutterCommand,
|
|
const <String>['pub', 'publish', '--server=bar', '--force'],
|
|
plugin.path)));
|
|
});
|
|
|
|
test('--force is only added once, regardless of plugin count', () async {
|
|
createMockCredentialFile();
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin_a', packagesDir, examples: <String>[]);
|
|
final RepositoryPackage plugin2 =
|
|
createFakePlugin('plugin_b', packagesDir, examples: <String>[]);
|
|
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=plugin_a,plugin_b',
|
|
'--skip-confirmation',
|
|
'--pub-publish-flags',
|
|
'--server=bar'
|
|
]);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
containsAllInOrder(<ProcessCall>[
|
|
ProcessCall(
|
|
flutterCommand,
|
|
const <String>['pub', 'publish', '--server=bar', '--force'],
|
|
plugin1.path),
|
|
ProcessCall(
|
|
flutterCommand,
|
|
const <String>['pub', 'publish', '--server=bar', '--force'],
|
|
plugin2.path),
|
|
]));
|
|
});
|
|
|
|
test('throws if pub publish fails', () async {
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
processRunner.mockProcessesForExecutable[flutterCommand] = <io.Process>[
|
|
MockProcess(exitCode: 128) // pub publish
|
|
];
|
|
|
|
Error? commandError;
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Publishing foo failed.'),
|
|
]));
|
|
});
|
|
|
|
test('publish, dry run', () async {
|
|
final RepositoryPackage plugin =
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
'--dry-run',
|
|
]);
|
|
|
|
expect(
|
|
processRunner.recordedCalls
|
|
.map((ProcessCall call) => call.executable),
|
|
isNot(contains('git-push')));
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('=============== DRY RUN ==============='),
|
|
contains('Running for foo'),
|
|
contains('Running `pub publish ` in ${plugin.path}...'),
|
|
contains('Tagging release foo-v0.0.1...'),
|
|
contains('Pushing tag to upstream...'),
|
|
contains('Published foo successfully!'),
|
|
]));
|
|
});
|
|
|
|
test('can publish non-flutter package', () async {
|
|
const String packageName = 'a_package';
|
|
createFakePackage(packageName, packagesDir);
|
|
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=$packageName',
|
|
]);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(
|
|
<Matcher>[
|
|
contains('Running `pub publish ` in /packages/a_package...'),
|
|
contains('Package published!'),
|
|
],
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('Tags release', () {
|
|
test('with the version and name from the pubspec.yaml', () async {
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
]);
|
|
|
|
expect(processRunner.recordedCalls,
|
|
contains(const ProcessCall('git-tag', <String>['foo-v0.0.1'], null)));
|
|
});
|
|
|
|
test('only if publishing succeeded', () async {
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
processRunner.mockProcessesForExecutable[flutterCommand] = <io.Process>[
|
|
MockProcess(exitCode: 128) // pub publish
|
|
];
|
|
|
|
Error? commandError;
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
], errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Publishing foo failed.'),
|
|
]));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
isNot(contains(
|
|
const ProcessCall('git-tag', <String>['foo-v0.0.1'], null))));
|
|
});
|
|
});
|
|
|
|
group('Pushes tags', () {
|
|
test('to upstream by default', () async {
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
]);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'foo-v0.0.1'], null)));
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Pushing tag to upstream...'),
|
|
contains('Published foo successfully!'),
|
|
]));
|
|
});
|
|
|
|
test('does not ask for user input if the --skip-confirmation flag is on',
|
|
() async {
|
|
createMockCredentialFile();
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--skip-confirmation',
|
|
'--packages=foo',
|
|
]);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'foo-v0.0.1'], null)));
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Published foo successfully!'),
|
|
]));
|
|
});
|
|
|
|
test('to upstream by default, dry run', () async {
|
|
final RepositoryPackage plugin =
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output = await runCapturingPrint(
|
|
commandRunner, <String>['publish', '--packages=foo', '--dry-run']);
|
|
|
|
expect(
|
|
processRunner.recordedCalls
|
|
.map((ProcessCall call) => call.executable),
|
|
isNot(contains('git-push')));
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('=============== DRY RUN ==============='),
|
|
contains('Running `pub publish ` in ${plugin.path}...'),
|
|
contains('Tagging release foo-v0.0.1...'),
|
|
contains('Pushing tag to upstream...'),
|
|
contains('Published foo successfully!'),
|
|
]));
|
|
});
|
|
|
|
test('to different remotes based on a flag', () async {
|
|
createFakePlugin('foo', packagesDir, examples: <String>[]);
|
|
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output =
|
|
await runCapturingPrint(commandRunner, <String>[
|
|
'publish',
|
|
'--packages=foo',
|
|
'--remote',
|
|
'origin',
|
|
]);
|
|
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['origin', 'foo-v0.0.1'], null)));
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Published foo successfully!'),
|
|
]));
|
|
});
|
|
});
|
|
|
|
group('Auto release (all-changed flag)', () {
|
|
test('can release newly created plugins', () async {
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'plugin1',
|
|
'versions': <String>[],
|
|
};
|
|
|
|
mockHttpResponses['plugin2'] = <String, dynamic>{
|
|
'name': 'plugin2',
|
|
'versions': <String>[],
|
|
};
|
|
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir);
|
|
// federated
|
|
final RepositoryPackage plugin2 = createFakePlugin(
|
|
'plugin2',
|
|
packagesDir.childDirectory('plugin2'),
|
|
);
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.pubspecFile.path}\n'
|
|
'${plugin2.pubspecFile.path}\n')
|
|
];
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~']);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'Publishing all packages that have changed relative to "HEAD~"'),
|
|
contains('Running `pub publish ` in ${plugin1.path}...'),
|
|
contains('Running `pub publish ` in ${plugin2.path}...'),
|
|
contains('plugin1 - \x1B[32mpublished\x1B[0m'),
|
|
contains('plugin2/plugin2 - \x1B[32mpublished\x1B[0m'),
|
|
]));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'plugin1-v0.0.1'], null)));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'plugin2-v0.0.1'], null)));
|
|
});
|
|
|
|
test('can release newly created plugins, while there are existing plugins',
|
|
() async {
|
|
mockHttpResponses['plugin0'] = <String, dynamic>{
|
|
'name': 'plugin0',
|
|
'versions': <String>['0.0.1'],
|
|
};
|
|
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'plugin1',
|
|
'versions': <String>[],
|
|
};
|
|
|
|
mockHttpResponses['plugin2'] = <String, dynamic>{
|
|
'name': 'plugin2',
|
|
'versions': <String>[],
|
|
};
|
|
|
|
// The existing plugin.
|
|
createFakePlugin('plugin0', packagesDir);
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir);
|
|
// federated
|
|
final RepositoryPackage plugin2 =
|
|
createFakePlugin('plugin2', packagesDir.childDirectory('plugin2'));
|
|
|
|
// Git results for plugin0 having been released already, and plugin1 and
|
|
// plugin2 being new.
|
|
processRunner.mockProcessesForExecutable['git-tag'] = <io.Process>[
|
|
MockProcess(stdout: 'plugin0-v0.0.1\n')
|
|
];
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.pubspecFile.path}\n'
|
|
'${plugin2.pubspecFile.path}\n')
|
|
];
|
|
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~']);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<String>[
|
|
'Running `pub publish ` in ${plugin1.path}...\n',
|
|
'Running `pub publish ` in ${plugin2.path}...\n',
|
|
]));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'plugin1-v0.0.1'], null)));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'plugin2-v0.0.1'], null)));
|
|
});
|
|
|
|
test('can release newly created plugins, dry run', () async {
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'plugin1',
|
|
'versions': <String>[],
|
|
};
|
|
|
|
mockHttpResponses['plugin2'] = <String, dynamic>{
|
|
'name': 'plugin2',
|
|
'versions': <String>[],
|
|
};
|
|
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir);
|
|
// federated
|
|
final RepositoryPackage plugin2 =
|
|
createFakePlugin('plugin2', packagesDir.childDirectory('plugin2'));
|
|
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.pubspecFile.path}\n'
|
|
'${plugin2.pubspecFile.path}\n')
|
|
];
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output = await runCapturingPrint(
|
|
commandRunner, <String>[
|
|
'publish',
|
|
'--all-changed',
|
|
'--base-sha=HEAD~',
|
|
'--dry-run'
|
|
]);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('=============== DRY RUN ==============='),
|
|
contains('Running `pub publish ` in ${plugin1.path}...'),
|
|
contains('Tagging release plugin1-v0.0.1...'),
|
|
contains('Pushing tag to upstream...'),
|
|
contains('Published plugin1 successfully!'),
|
|
contains('Running `pub publish ` in ${plugin2.path}...'),
|
|
contains('Tagging release plugin2-v0.0.1...'),
|
|
contains('Pushing tag to upstream...'),
|
|
contains('Published plugin2 successfully!'),
|
|
]));
|
|
expect(
|
|
processRunner.recordedCalls
|
|
.map((ProcessCall call) => call.executable),
|
|
isNot(contains('git-push')));
|
|
});
|
|
|
|
test('version change triggers releases.', () async {
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'plugin1',
|
|
'versions': <String>['0.0.1'],
|
|
};
|
|
|
|
mockHttpResponses['plugin2'] = <String, dynamic>{
|
|
'name': 'plugin2',
|
|
'versions': <String>['0.0.1'],
|
|
};
|
|
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir, version: '0.0.2');
|
|
// federated
|
|
final RepositoryPackage plugin2 = createFakePlugin(
|
|
'plugin2', packagesDir.childDirectory('plugin2'),
|
|
version: '0.0.2');
|
|
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.pubspecFile.path}\n'
|
|
'${plugin2.pubspecFile.path}\n')
|
|
];
|
|
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output2 = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~']);
|
|
expect(
|
|
output2,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Running `pub publish ` in ${plugin1.path}...'),
|
|
contains('Published plugin1 successfully!'),
|
|
contains('Running `pub publish ` in ${plugin2.path}...'),
|
|
contains('Published plugin2 successfully!'),
|
|
]));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'plugin1-v0.0.2'], null)));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'plugin2-v0.0.2'], null)));
|
|
});
|
|
|
|
test(
|
|
'delete package will not trigger publish but exit the command successfully!',
|
|
() async {
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'plugin1',
|
|
'versions': <String>['0.0.1'],
|
|
};
|
|
|
|
mockHttpResponses['plugin2'] = <String, dynamic>{
|
|
'name': 'plugin2',
|
|
'versions': <String>['0.0.1'],
|
|
};
|
|
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir, version: '0.0.2');
|
|
// federated
|
|
final RepositoryPackage plugin2 =
|
|
createFakePlugin('plugin2', packagesDir.childDirectory('plugin2'));
|
|
plugin2.directory.deleteSync(recursive: true);
|
|
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.pubspecFile.path}\n'
|
|
'${plugin2.pubspecFile.path}\n')
|
|
];
|
|
|
|
mockStdin.readLineOutput = 'y';
|
|
|
|
final List<String> output2 = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~']);
|
|
expect(
|
|
output2,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('Running `pub publish ` in ${plugin1.path}...'),
|
|
contains('Published plugin1 successfully!'),
|
|
contains(
|
|
'The pubspec file for plugin2/plugin2 does not exist, so no publishing will happen.\nSafe to ignore if the package is deleted in this commit.\n'),
|
|
contains('SKIPPING: package deleted'),
|
|
contains('skipped (with warning)'),
|
|
]));
|
|
expect(
|
|
processRunner.recordedCalls,
|
|
contains(const ProcessCall(
|
|
'git-push', <String>['upstream', 'plugin1-v0.0.2'], null)));
|
|
});
|
|
|
|
test('Existing versions do not trigger release, also prints out message.',
|
|
() async {
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'plugin1',
|
|
'versions': <String>['0.0.2'],
|
|
};
|
|
|
|
mockHttpResponses['plugin2'] = <String, dynamic>{
|
|
'name': 'plugin2',
|
|
'versions': <String>['0.0.2'],
|
|
};
|
|
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir, version: '0.0.2');
|
|
// federated
|
|
final RepositoryPackage plugin2 = createFakePlugin(
|
|
'plugin2', packagesDir.childDirectory('plugin2'),
|
|
version: '0.0.2');
|
|
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.pubspecFile.path}\n'
|
|
'${plugin2.pubspecFile.path}\n')
|
|
];
|
|
processRunner.mockProcessesForExecutable['git-tag'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: 'plugin1-v0.0.2\n'
|
|
'plugin2-v0.0.2\n')
|
|
];
|
|
|
|
final List<String> output = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~']);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('plugin1 0.0.2 has already been published'),
|
|
contains('SKIPPING: already published'),
|
|
contains('plugin2 0.0.2 has already been published'),
|
|
contains('SKIPPING: already published'),
|
|
]));
|
|
|
|
expect(
|
|
processRunner.recordedCalls
|
|
.map((ProcessCall call) => call.executable),
|
|
isNot(contains('git-push')));
|
|
});
|
|
|
|
test(
|
|
'Existing versions do not trigger release, but fail if the tags do not exist.',
|
|
() async {
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'plugin1',
|
|
'versions': <String>['0.0.2'],
|
|
};
|
|
|
|
mockHttpResponses['plugin2'] = <String, dynamic>{
|
|
'name': 'plugin2',
|
|
'versions': <String>['0.0.2'],
|
|
};
|
|
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir, version: '0.0.2');
|
|
// federated
|
|
final RepositoryPackage plugin2 = createFakePlugin(
|
|
'plugin2', packagesDir.childDirectory('plugin2'),
|
|
version: '0.0.2');
|
|
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.pubspecFile.path}\n'
|
|
'${plugin2.pubspecFile.path}\n')
|
|
];
|
|
|
|
Error? commandError;
|
|
final List<String> output = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~'],
|
|
errorHandler: (Error e) {
|
|
commandError = e;
|
|
});
|
|
|
|
expect(commandError, isA<ToolExit>());
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains('plugin1 0.0.2 has already been published, '
|
|
'however the git release tag (plugin1-v0.0.2) was not found.'),
|
|
contains('plugin2 0.0.2 has already been published, '
|
|
'however the git release tag (plugin2-v0.0.2) was not found.'),
|
|
]));
|
|
expect(
|
|
processRunner.recordedCalls
|
|
.map((ProcessCall call) => call.executable),
|
|
isNot(contains('git-push')));
|
|
});
|
|
|
|
test('No version change does not release any plugins', () async {
|
|
// Non-federated
|
|
final RepositoryPackage plugin1 =
|
|
createFakePlugin('plugin1', packagesDir);
|
|
// federated
|
|
final RepositoryPackage plugin2 =
|
|
createFakePlugin('plugin2', packagesDir.childDirectory('plugin2'));
|
|
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(
|
|
stdout: '${plugin1.libDirectory.childFile('plugin1.dart').path}\n'
|
|
'${plugin2.libDirectory.childFile('plugin2.dart').path}\n')
|
|
];
|
|
|
|
final List<String> output = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~']);
|
|
|
|
expect(output, containsAllInOrder(<String>['Ran for 0 package(s)']));
|
|
expect(
|
|
processRunner.recordedCalls
|
|
.map((ProcessCall call) => call.executable),
|
|
isNot(contains('git-push')));
|
|
});
|
|
|
|
test('Do not release flutter_plugin_tools', () async {
|
|
mockHttpResponses['plugin1'] = <String, dynamic>{
|
|
'name': 'flutter_plugin_tools',
|
|
'versions': <String>[],
|
|
};
|
|
|
|
final RepositoryPackage flutterPluginTools =
|
|
createFakePlugin('flutter_plugin_tools', packagesDir);
|
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
|
MockProcess(stdout: flutterPluginTools.pubspecFile.path)
|
|
];
|
|
|
|
final List<String> output = await runCapturingPrint(commandRunner,
|
|
<String>['publish', '--all-changed', '--base-sha=HEAD~']);
|
|
|
|
expect(
|
|
output,
|
|
containsAllInOrder(<Matcher>[
|
|
contains(
|
|
'SKIPPING: publishing flutter_plugin_tools via the tool is not supported')
|
|
]));
|
|
expect(
|
|
output.contains(
|
|
'Running `pub publish ` in ${flutterPluginTools.path}...',
|
|
),
|
|
isFalse);
|
|
expect(
|
|
processRunner.recordedCalls
|
|
.map((ProcessCall call) => call.executable),
|
|
isNot(contains('git-push')));
|
|
});
|
|
});
|
|
}
|
|
|
|
/// An extension of [RecordingProcessRunner] that stores 'flutter pub publish'
|
|
/// calls so that their input streams can be checked in tests.
|
|
class TestProcessRunner extends RecordingProcessRunner {
|
|
// Most recent returned publish process.
|
|
late MockProcess mockPublishProcess;
|
|
|
|
@override
|
|
Future<io.Process> start(String executable, List<String> args,
|
|
{Directory? workingDirectory}) async {
|
|
final io.Process process =
|
|
await super.start(executable, args, workingDirectory: workingDirectory);
|
|
if (executable == getFlutterCommand(const LocalPlatform()) &&
|
|
args.isNotEmpty &&
|
|
args[0] == 'pub' &&
|
|
args[1] == 'publish') {
|
|
mockPublishProcess = process as MockProcess;
|
|
}
|
|
return process;
|
|
}
|
|
}
|
|
|
|
class MockStdin extends Mock implements io.Stdin {
|
|
List<List<int>> mockUserInputs = <List<int>>[];
|
|
final StreamController<List<int>> _controller = StreamController<List<int>>();
|
|
String? readLineOutput;
|
|
|
|
@override
|
|
Stream<S> transform<S>(StreamTransformer<List<int>, S> streamTransformer) {
|
|
mockUserInputs.forEach(_addUserInputsToSteam);
|
|
return _controller.stream.transform(streamTransformer);
|
|
}
|
|
|
|
@override
|
|
StreamSubscription<List<int>> listen(void Function(List<int> event)? onData,
|
|
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
|
|
return _controller.stream.listen(onData,
|
|
onError: onError, onDone: onDone, cancelOnError: cancelOnError);
|
|
}
|
|
|
|
@override
|
|
String? readLineSync(
|
|
{Encoding encoding = io.systemEncoding,
|
|
bool retainNewlines = false}) =>
|
|
readLineOutput;
|
|
|
|
void _addUserInputsToSteam(List<int> input) => _controller.add(input);
|
|
}
|