// 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 'package:args/command_runner.dart'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/base/common.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/build_system/build_system.dart'; import 'package:flutter_tools/src/bundle.dart'; import 'package:flutter_tools/src/bundle_builder.dart'; import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/commands/build_bundle.dart'; import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'package:meta/meta.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/fakes.dart'; import '../../src/test_build_system.dart'; import '../../src/test_flutter_command_runner.dart'; void main() { Cache.disableLocking(); late Directory tempDir; late FakeBundleBuilder fakeBundleBuilder; final FileSystemStyle fileSystemStyle = globals.fs.path.separator == '/' ? FileSystemStyle.posix : FileSystemStyle.windows; setUp(() { tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_packages_test.'); fakeBundleBuilder = FakeBundleBuilder(); }); tearDown(() { tryToDelete(tempDir); }); MemoryFileSystem fsFactory() { return MemoryFileSystem.test(style: fileSystemStyle); } Future<BuildBundleCommand> runCommandIn(String projectPath, { List<String>? arguments }) async { final BuildBundleCommand command = BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, ); final CommandRunner<void> runner = createTestCommandRunner(command); await runner.run(<String>[ 'bundle', ...?arguments, '--target=$projectPath/lib/main.dart', '--no-pub', ]); return command; } testUsingContext('bundle getUsage indicate that project is a module', () async { final String projectPath = await createProject(tempDir, arguments: <String>['--no-pub', '--template=module']); final BuildBundleCommand command = await runCommandIn(projectPath); expect((await command.usageValues).commandBuildBundleIsModule, true); }); testUsingContext('bundle getUsage indicate that project is not a module', () async { final String projectPath = await createProject(tempDir, arguments: <String>['--no-pub', '--template=app']); final BuildBundleCommand command = await runCommandIn(projectPath); expect((await command.usageValues).commandBuildBundleIsModule, false); }); testUsingContext('bundle getUsage indicate the target platform', () async { final String projectPath = await createProject(tempDir, arguments: <String>['--no-pub', '--template=app']); final BuildBundleCommand command = await runCommandIn(projectPath); expect((await command.usageValues).commandBuildBundleTargetPlatform, 'android-arm'); }); testUsingContext('bundle fails to build for Windows if feature is disabled', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(recursive: true); globals.fs.file('.packages').createSync(recursive: true); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); expect(() => runner.run(<String>[ 'bundle', '--no-pub', '--target-platform=windows-x64', ]), throwsToolExit()); }, overrides: <Type, Generator>{ FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(), }); testUsingContext('bundle fails to build for Linux if feature is disabled', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); expect(() => runner.run(<String>[ 'bundle', '--no-pub', '--target-platform=linux-x64', ]), throwsToolExit()); }, overrides: <Type, Generator>{ FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(), }); testUsingContext('bundle fails to build for macOS if feature is disabled', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); expect(() => runner.run(<String>[ 'bundle', '--no-pub', '--target-platform=darwin', ]), throwsToolExit()); }, overrides: <Type, Generator>{ FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(), }); testUsingContext('bundle --tree-shake-icons fails', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); expect(() => runner.run(<String>[ 'bundle', '--no-pub', '--release', '--tree-shake-icons', ]), throwsToolExit(message: 'tree-shake-icons')); }, overrides: <Type, Generator>{ FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('bundle can build for Windows if feature is enabled', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--target-platform=windows-x64', ]); }, overrides: <Type, Generator>{ FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); testUsingContext('bundle can build for Linux if feature is enabled', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--target-platform=linux-x64', ]); }, overrides: <Type, Generator>{ FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), }); testUsingContext('bundle can build for macOS if feature is enabled', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--target-platform=darwin', ]); }, overrides: <Type, Generator>{ FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), }); testUsingContext('passes track widget creation through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--debug', '--target-platform=android-arm', '--track-widget-creation', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'debug', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('passes dart-define through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--debug', '--target-platform=android-arm', '--dart-define=foo=bar', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'debug', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root', kDartDefines: 'Zm9vPWJhcg==', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('passes filesystem-scheme through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--debug', '--target-platform=android-arm', '--filesystem-scheme=org-dartlang-root2', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'debug', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root2', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('passes filesystem-roots through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--debug', '--target-platform=android-arm', '--filesystem-root=test1,test2', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'debug', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root', kFileSystemRoots: 'test1,test2', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('passes extra frontend-options through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--debug', '--target-platform=android-arm', '--extra-front-end-options=--testflag,--testflag2', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'debug', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root', kExtraFrontEndOptions: '--testflag,--testflag2', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('passes extra gen_snapshot-options through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--debug', '--target-platform=android-arm', '--extra-gen-snapshot-options=--testflag,--testflag2', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'debug', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root', kExtraGenSnapshotOptions: '--testflag,--testflag2', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('passes profile options through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--profile', '--dart-define=foo=bar', '--target-platform=android-arm', '--track-widget-creation', '--filesystem-scheme=org-dartlang-root', '--filesystem-root=test1,test2', '--extra-gen-snapshot-options=--testflag,--testflag2', '--extra-front-end-options=--testflagFront,--testflagFront2', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'profile', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kDartDefines: 'Zm9vPWJhcg==', kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root', kFileSystemRoots: 'test1,test2', kExtraGenSnapshotOptions: '--testflag,--testflag2', kExtraFrontEndOptions: '--testflagFront,--testflagFront2', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('passes release options through', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--release', '--dart-define=foo=bar', '--target-platform=android-arm', '--track-widget-creation', '--filesystem-scheme=org-dartlang-root', '--filesystem-root=test1,test2', '--extra-gen-snapshot-options=--testflag,--testflag2', '--extra-front-end-options=--testflagFront,--testflagFront2', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines, <String, String>{ kBuildMode: 'release', kTargetPlatform: 'android-arm', kTargetFile: globals.fs.path.join('lib', 'main.dart'), kDartDefines: 'Zm9vPWJhcg==', kTrackWidgetCreation: 'true', kFileSystemScheme: 'org-dartlang-root', kFileSystemRoots: 'test1,test2', kExtraGenSnapshotOptions: '--testflag,--testflag2', kExtraFrontEndOptions: '--testflagFront,--testflagFront2', kIconTreeShakerFlag: 'false', kDeferredComponents: 'false', kDartObfuscation: 'false', }); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('test --dart-define-from-file option', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); await globals.fs.file('config.json').writeAsString( ''' { "kInt": 1, "kDouble": 1.1, "name": "denghaizhu", "title": "this is title from config json file" } ''' ); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); await runner.run(<String>[ 'bundle', '--no-pub', '--dart-define-from-file=config.json', ]); }, overrides: <Type, Generator>{ BuildSystem: () => TestBuildSystem.all(BuildResult(success: true), (Target target, Environment environment) { expect(environment.defines[kDartDefines], 'a0ludD0x,a0RvdWJsZT0xLjE=,bmFtZT1kZW5naGFpemh1,dGl0bGU9dGhpcyBpcyB0aXRsZSBmcm9tIGNvbmZpZyBqc29uIGZpbGU='); }), FileSystem: fsFactory, ProcessManager: () => FakeProcessManager.any(), }); testUsingContext('test --dart-define-from-file option by corrupted json', () async { globals.fs.file(globals.fs.path.join('lib', 'main.dart')).createSync(recursive: true); globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('.packages').createSync(); await globals.fs.file('config.json').writeAsString( ''' { "kInt": 1Error json format "kDouble": 1.1, "name": "denghaizhu", "title": "this is title from config json file" } ''' ); final CommandRunner<void> runner = createTestCommandRunner(BuildBundleCommand( logger: BufferLogger.test(), bundleBuilder: fakeBundleBuilder, )); expect(() => runner.run(<String>[ 'bundle', '--no-pub', '--dart-define-from-file=config.json', ]), throwsA(predicate<Exception>((Exception e) => e is ToolExit && e.message!.startsWith('Json config define file "--dart-define-from-file=config.json" format err')))); }, overrides: <Type, Generator>{ FileSystem: fsFactory, BuildSystem: () => TestBuildSystem.all(BuildResult(success: true)), ProcessManager: () => FakeProcessManager.any(), }); } class FakeBundleBuilder extends Fake implements BundleBuilder { @override Future<void> build({ required TargetPlatform platform, required BuildInfo buildInfo, FlutterProject? project, String? mainPath, String manifestPath = defaultManifestPath, String? applicationKernelFilePath, String? depfilePath, String? assetDirPath, @visibleForTesting BuildSystem? buildSystem, }) async {} }