blob: 12e355e4202fd70d0a9b52153cbe4880e2b40ed9 [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 'dart:io' as io show Directory, File, Link;
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:test/test.dart';
import '../bin/bump_version_constraints.dart' as bump;
void main() {
late MemoryFileSystem fileSystem;
late Directory flutterRoot;
late StringBuffer stdout;
late StringBuffer stderr;
int? exitCode;
void mockExit(int code) {
exitCode = code;
}
setUp(() {
fileSystem = MemoryFileSystem.test();
flutterRoot = fileSystem.directory('/flutter')..createSync();
stdout = StringBuffer();
stderr = StringBuffer();
exitCode = null;
});
test('errors when no pubspec files are updated', () {
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(stdout.toString(), contains('Done. Updated 0 pubspec.yaml files.'));
expect(stderr.toString(), contains('Error: No pubspec.yaml files were updated.'));
});
test('updates pubspec.yaml files with matching SDK constraints and skips deviators', () {
final File pubspec1 = fileSystem.file('/flutter/packages/flutter/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: flutter
environment:
sdk: ^3.10.0-0
dependencies:
meta: any
''');
final File pubspec2 = fileSystem.file('/flutter/packages/flutter_tools/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: flutter_tools
environment:
sdk: ^3.10.0-0
''');
final File pubspecDeviator = fileSystem.file('/flutter/packages/deviator/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: deviator
environment:
sdk: ^3.12.0
''');
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, isNull);
expect(pubspec1.readAsStringSync(), '''
name: flutter
environment:
sdk: ^3.13.0-0
dependencies:
meta: any
''');
expect(pubspec2.readAsStringSync(), '''
name: flutter_tools
environment:
sdk: ^3.13.0-0
''');
expect(pubspecDeviator.readAsStringSync(), '''
name: deviator
environment:
sdk: ^3.12.0
''');
expect(
stdout.toString(),
contains('Updated ${fileSystem.path.join('packages', 'flutter', 'pubspec.yaml')}'),
);
expect(
stdout.toString(),
contains('Updated ${fileSystem.path.join('packages', 'flutter_tools', 'pubspec.yaml')}'),
);
expect(
stdout.toString(),
contains(
'Skipping ${fileSystem.path.join('packages', 'deviator', 'pubspec.yaml')}: SDK constraint "^3.12.0" does not match expected "^3.10.0-0".',
),
);
expect(stdout.toString(), contains('Done. Updated 2 pubspec.yaml files.'));
expect(stderr.toString(), isEmpty);
});
test('does not update pubspec.yaml if SDK constraint is already correct', () {
final File pubspec = fileSystem.file('/flutter/packages/flutter/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: flutter
environment:
sdk: ^3.13.0-0
''');
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(pubspec.readAsStringSync(), '''
name: flutter
environment:
sdk: ^3.13.0-0
''');
expect(stdout.toString(), contains('Done. Updated 0 pubspec.yaml files.'));
expect(stderr.toString(), contains('Error: No pubspec.yaml files were updated.'));
});
test('handles environment: without sdk:', () {
final File pubspec = fileSystem.file('/flutter/packages/flutter/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: flutter
environment:
foo: bar
''');
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(pubspec.readAsStringSync(), '''
name: flutter
environment:
foo: bar
''');
expect(stdout.toString(), contains('Done. Updated 0 pubspec.yaml files.'));
expect(stderr.toString(), contains('Error: No pubspec.yaml files were updated.'));
});
test('ignores pubspec.yaml outside flutterRoot, in dot-directories, or in bin/cache', () {
// Hidden dot-directory
final File pubspecHidden = fileSystem.file('/flutter/.git/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: hidden
environment:
sdk: ^3.10.0-0
''');
// Outside flutterRoot
final File pubspecOutside = fileSystem.file('/bar/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: outside
environment:
sdk: ^3.10.0-0
''');
// Inside a build directory
final File pubspecBuild = fileSystem.file('/flutter/build/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: build_package
environment:
sdk: ^3.10.0-0
''');
// Inside bin/cache
final File pubspecCache = fileSystem.file('/flutter/bin/cache/pkg/sky_engine/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: sky_engine
environment:
sdk: ^3.10.0-0
''');
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(pubspecHidden.readAsStringSync(), contains(' sdk: ^3.10.0-0\n'));
expect(pubspecOutside.readAsStringSync(), contains(' sdk: ^3.10.0-0\n'));
expect(pubspecBuild.readAsStringSync(), contains(' sdk: ^3.10.0-0\n'));
expect(pubspecCache.readAsStringSync(), contains(' sdk: ^3.10.0-0\n'));
expect(stdout.toString(), contains('Done. Updated 0 pubspec.yaml files.'));
expect(stderr.toString(), contains('Error: No pubspec.yaml files were updated.'));
});
test('prints usage and exits with 0 on --help or -h', () {
bump.run(
<String>['--help'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 0);
expect(
stdout.toString(),
contains(
'Usage: dart dev/tools/bin/bump_version_constraints.dart <old_sdk_constraint> <new_sdk_constraint>',
),
);
expect(stdout.toString(), contains('Options:'));
expect(stdout.toString(), contains('-h, --help'));
expect(stderr.toString(), isEmpty);
stdout.clear();
stderr.clear();
exitCode = null;
bump.run(
<String>['-h'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 0);
expect(
stdout.toString(),
contains(
'Usage: dart dev/tools/bin/bump_version_constraints.dart <old_sdk_constraint> <new_sdk_constraint>',
),
);
expect(stderr.toString(), isEmpty);
});
test('errors out on wrong number of arguments', () {
bump.run(
<String>[],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(
stderr.toString(),
contains(
'ERROR: Expected exactly two arguments specifying the old SDK constraint and the new SDK constraint.',
),
);
expect(
stderr.toString(),
contains(
'Usage: dart dev/tools/bin/bump_version_constraints.dart <old_sdk_constraint> <new_sdk_constraint>',
),
);
stdout.clear();
stderr.clear();
exitCode = null;
bump.run(
<String>['^3.10.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(
stderr.toString(),
contains(
'ERROR: Expected exactly two arguments specifying the old SDK constraint and the new SDK constraint.',
),
);
stdout.clear();
stderr.clear();
exitCode = null;
bump.run(
<String>['^3.10.0-0', '^3.13.0-0', 'extra-arg'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(
stderr.toString(),
contains(
'ERROR: Expected exactly two arguments specifying the old SDK constraint and the new SDK constraint.',
),
);
});
test('continues and exits with 1 when encountering file read/write errors', () {
final File pubspecGood = fileSystem.file('/flutter/packages/good/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: good
environment:
sdk: ^3.10.0-0
''');
fileSystem.file('/flutter/packages/bad/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: bad
environment:
sdk: ^3.10.0-0
''');
final FileSystem faultyFileSystem = FaultyFileSystem(fileSystem);
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: faultyFileSystem,
flutterRoot: faultyFileSystem.directory('/flutter'),
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(pubspecGood.readAsStringSync(), contains(' sdk: ^3.13.0-0\n'));
expect(
stdout.toString(),
contains('Updated ${fileSystem.path.join('packages', 'good', 'pubspec.yaml')}'),
);
expect(stdout.toString(), contains('Done. Updated 1 pubspec.yaml file.'));
expect(
stderr.toString(),
contains('Error updating ${fileSystem.path.join('packages', 'bad', 'pubspec.yaml')}:'),
);
});
test('errors out on unrecognized flags', () {
bump.run(
<String>['--invalid-flag'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(
stderr.toString(),
contains('ERROR: FormatException: Could not find an option named "--invalid-flag".'),
);
expect(
stderr.toString(),
contains(
'Usage: dart dev/tools/bin/bump_version_constraints.dart <old_sdk_constraint> <new_sdk_constraint>',
),
);
});
test('handles malformed or non-map pubspec.yaml', () {
final File pubspecMalformed = fileSystem.file('/flutter/packages/invalid/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('not a yaml map');
final File pubspecGood = fileSystem.file('/flutter/packages/good/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: good
environment:
sdk: ^3.10.0-0
''');
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(pubspecMalformed.readAsStringSync(), 'not a yaml map');
expect(pubspecGood.readAsStringSync(), contains(' sdk: ^3.13.0-0\n'));
expect(
stderr.toString(),
contains(
'Error: ${fileSystem.path.join('packages', 'invalid', 'pubspec.yaml')} is not a valid YAML map.',
),
);
expect(stdout.toString(), contains('Done. Updated 1 pubspec.yaml file.'));
});
test('handles pubspec.yaml with missing or non-map environment', () {
final File pubspecNoEnv = fileSystem.file('/flutter/packages/no_env/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: no_env
''');
final File pubspecScalarEnv = fileSystem.file('/flutter/packages/scalar_env/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: scalar_env
environment: invalid
''');
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, 1);
expect(pubspecNoEnv.readAsStringSync(), '''
name: no_env
''');
expect(pubspecScalarEnv.readAsStringSync(), '''
name: scalar_env
environment: invalid
''');
expect(stdout.toString(), contains('Done. Updated 0 pubspec.yaml files.'));
expect(stderr.toString(), contains('Error: No pubspec.yaml files were updated.'));
});
test('does not ignore cache directory if outside bin/', () {
final File pubspecCacheOutsideBin = fileSystem.file('/flutter/packages/cache/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: cache_pkg
environment:
sdk: ^3.10.0-0
''');
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: fileSystem,
flutterRoot: flutterRoot,
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(exitCode, isNull);
expect(pubspecCacheOutsideBin.readAsStringSync(), contains(' sdk: ^3.13.0-0\n'));
expect(stdout.toString(), contains('Done. Updated 1 pubspec.yaml file.'));
expect(stderr.toString(), isEmpty);
});
test('handles directory traversal errors gracefully', () {
fileSystem.file('/flutter/packages/unreadable/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: unreadable
environment:
sdk: ^3.10.0-0
''');
final File pubspecGood = fileSystem.file('/flutter/packages/good/pubspec.yaml')
..createSync(recursive: true)
..writeAsStringSync('''
name: good
environment:
sdk: ^3.10.0-0
''');
final FileSystem faultyFileSystem = FaultyFileSystem(fileSystem);
bump.run(
<String>['^3.10.0-0', '^3.13.0-0'],
fileSystem: faultyFileSystem,
flutterRoot: faultyFileSystem.directory('/flutter'),
stdout: stdout,
stderr: stderr,
exit: mockExit,
);
expect(pubspecGood.readAsStringSync(), contains(' sdk: ^3.13.0-0\n'));
expect(exitCode, 1);
expect(
stderr.toString(),
contains('Error traversing ${fileSystem.path.join('/flutter', 'packages', 'unreadable')}:'),
);
});
}
class FaultyFileSystem extends ForwardingFileSystem {
FaultyFileSystem(super.delegate);
@override
File file(dynamic path) => FaultyFile(this, delegate.file(path));
@override
Directory directory(dynamic path) => FaultyDirectory(this, delegate.directory(path));
}
class FaultyFile extends ForwardingFileSystemEntity<File, io.File> with ForwardingFile {
FaultyFile(this._fileSystem, this.delegate);
final FaultyFileSystem _fileSystem;
@override
final io.File delegate;
@override
FileSystem get fileSystem => _fileSystem;
@override
File wrapFile(io.File delegate) => FaultyFile(_fileSystem, delegate as File);
@override
Directory wrapDirectory(io.Directory delegate) =>
FaultyDirectory(_fileSystem, delegate as Directory);
@override
Link wrapLink(io.Link delegate) => delegate as Link;
@override
List<String> readAsLinesSync({Encoding encoding = utf8}) {
if (path.contains('bad')) {
throw const FileSystemException('Simulated read failure');
}
return delegate.readAsLinesSync(encoding: encoding);
}
@override
String readAsStringSync({Encoding encoding = utf8}) {
if (path.contains('bad')) {
throw const FileSystemException('Simulated read failure');
}
return delegate.readAsStringSync(encoding: encoding);
}
@override
void writeAsStringSync(
String content, {
FileMode mode = FileMode.write,
Encoding encoding = utf8,
bool flush = false,
}) {
if (path.contains('bad')) {
throw const FileSystemException('Simulated write failure');
}
delegate.writeAsStringSync(content, mode: mode, encoding: encoding, flush: flush);
}
}
class FaultyDirectory extends ForwardingFileSystemEntity<Directory, io.Directory>
with ForwardingDirectory<Directory> {
FaultyDirectory(this._fileSystem, this.delegate);
final FaultyFileSystem _fileSystem;
@override
final io.Directory delegate;
@override
FileSystem get fileSystem => _fileSystem;
@override
File wrapFile(io.File delegate) => FaultyFile(_fileSystem, delegate as File);
@override
Directory wrapDirectory(io.Directory delegate) =>
FaultyDirectory(_fileSystem, delegate as Directory);
@override
Link wrapLink(io.Link delegate) => delegate as Link;
@override
List<FileSystemEntity> listSync({bool recursive = false, bool followLinks = true}) {
if (path.contains('unreadable')) {
throw const FileSystemException('Simulated directory list failure');
}
return super.listSync(recursive: recursive, followLinks: followLinks);
}
@override
Directory childDirectory(String basename) {
return fileSystem.directory(fileSystem.path.join(path, basename));
}
@override
File childFile(String basename) {
return fileSystem.file(fileSystem.path.join(path, basename));
}
@override
Link childLink(String basename) {
return fileSystem.link(fileSystem.path.join(path, basename));
}
}