| // 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)); | |
| } | |
| } |