// 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:typed_data'; import 'package:args/args.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/artifacts.dart'; import 'package:flutter_tools/src/asset.dart'; import 'package:flutter_tools/src/base/config.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' hide defaultManifestPath; import 'package:flutter_tools/src/bundle_builder.dart'; import 'package:flutter_tools/src/devfs.dart'; import 'package:flutter_tools/src/device.dart'; import 'package:flutter_tools/src/flutter_manifest.dart'; import 'package:flutter_tools/src/globals.dart' as globals; import 'package:flutter_tools/src/project.dart'; import 'package:test/fake.dart'; import '../src/common.dart'; import '../src/context.dart'; import '../src/test_build_system.dart'; // Tests for BundleBuilder. void main() { testUsingContext( 'Copies assets to expected directory after building', () async { final BuildSystem buildSystem = TestBuildSystem.all(BuildResult(success: true), ( Target target, Environment environment, ) { environment.outputDir.childFile('kernel_blob.bin').createSync(recursive: true); environment.outputDir.childFile('isolate_snapshot_data').createSync(); environment.outputDir.childFile('vm_snapshot_data').createSync(); environment.outputDir.childFile('LICENSE').createSync(recursive: true); }); await BundleBuilder().build( platform: TargetPlatform.ios, buildInfo: BuildInfo.debug, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: globals.fs.path.join('lib', 'main.dart'), assetDirPath: 'example', depfilePath: 'example.d', buildSystem: buildSystem, ); expect( globals.fs.file(globals.fs.path.join('example', 'kernel_blob.bin')).existsSync(), true, ); expect(globals.fs.file(globals.fs.path.join('example', 'LICENSE')).existsSync(), true); expect(globals.fs.file(globals.fs.path.join('example.d')).existsSync(), false); }, overrides: { FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => FakeProcessManager.any(), }, ); testWithoutContext( 'writeBundle applies transformations to any assets that have them defined', () async { final MemoryFileSystem fileSystem = MemoryFileSystem.test(); final File asset = fileSystem.file('my-asset.txt') ..createSync() ..writeAsBytesSync([1, 2, 3]); final Artifacts artifacts = Artifacts.test(); final FakeProcessManager processManager = FakeProcessManager.list([ FakeCommand( command: [ artifacts.getArtifactPath(Artifact.engineDartBinary), 'run', 'increment', '--input=/.tmp_rand0/rand0/my-asset.txt-transformOutput0.txt', '--output=/.tmp_rand0/rand0/my-asset.txt-transformOutput1.txt', ], onRun: (List command) { final ArgResults argParseResults = (ArgParser() ..addOption('input', mandatory: true) ..addOption('output', mandatory: true)) .parse(command); final File inputFile = fileSystem.file(argParseResults['input']); final File outputFile = fileSystem.file(argParseResults['output']); expect(inputFile, exists); outputFile ..createSync() ..writeAsBytesSync( Uint8List.fromList(inputFile.readAsBytesSync().map((int b) => b + 1).toList()), ); }, ), ]); final FakeAssetBundle bundle = FakeAssetBundle() ..entries['my-asset.txt'] = AssetBundleEntry( DevFSFileContent(asset), kind: AssetKind.regular, transformers: const [ AssetTransformerEntry(package: 'increment', args: []), ], ); final Directory bundleDir = fileSystem.directory( getAssetBuildDirectory(Config.test(), fileSystem), ); await writeBundle( bundleDir, bundle.entries, targetPlatform: TargetPlatform.tester, impellerStatus: ImpellerStatus.platformDefault, processManager: processManager, fileSystem: fileSystem, artifacts: artifacts, logger: BufferLogger.test(), projectDir: fileSystem.currentDirectory, buildMode: BuildMode.debug, ); final File outputAssetFile = fileSystem.file('build/flutter_assets/my-asset.txt'); expect(outputAssetFile, exists); expect(outputAssetFile.readAsBytesSync(), orderedEquals([2, 3, 4])); }, ); testUsingContext( 'Handles build system failure', () { expect( () => BundleBuilder().build( platform: TargetPlatform.ios, buildInfo: BuildInfo.debug, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: 'lib/main.dart', assetDirPath: 'example', depfilePath: 'example.d', buildSystem: TestBuildSystem.all(BuildResult(success: false)), ), throwsToolExit(), ); }, overrides: { FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => FakeProcessManager.any(), }, ); testUsingContext( 'Passes correct defines to build system', () async { final FlutterProject project = FlutterProject.fromDirectoryTest(globals.fs.currentDirectory); final String mainPath = globals.fs.path.join('lib', 'main.dart'); const String assetDirPath = 'example'; const String depfilePath = 'example.d'; Environment? env; final BuildSystem buildSystem = TestBuildSystem.all(BuildResult(success: true), ( Target target, Environment environment, ) { env = environment; environment.outputDir.childFile('kernel_blob.bin').createSync(recursive: true); environment.outputDir.childFile('isolate_snapshot_data').createSync(); environment.outputDir.childFile('vm_snapshot_data').createSync(); environment.outputDir.childFile('LICENSE').createSync(recursive: true); }); await BundleBuilder().build( platform: TargetPlatform.ios, buildInfo: const BuildInfo( BuildMode.debug, null, trackWidgetCreation: true, frontendServerStarterPath: 'path/to/frontend_server_starter.dart', extraFrontEndOptions: ['test1', 'test2'], extraGenSnapshotOptions: ['test3', 'test4'], fileSystemRoots: ['test5', 'test6'], fileSystemScheme: 'test7', dartDefines: ['test8', 'test9'], treeShakeIcons: true, packageConfigPath: '.dart_tool/package_config.json', ), project: project, mainPath: mainPath, assetDirPath: assetDirPath, depfilePath: depfilePath, buildSystem: buildSystem, ); expect(env, isNotNull); expect(env!.defines[kBuildMode], 'debug'); expect(env!.defines[kTargetPlatform], 'ios'); expect(env!.defines[kTargetFile], mainPath); expect(env!.defines[kTrackWidgetCreation], 'true'); expect(env!.defines[kFrontendServerStarterPath], 'path/to/frontend_server_starter.dart'); expect(env!.defines[kExtraFrontEndOptions], 'test1,test2'); expect(env!.defines[kExtraGenSnapshotOptions], 'test3,test4'); expect(env!.defines[kFileSystemRoots], 'test5,test6'); expect(env!.defines[kFileSystemScheme], 'test7'); expect(env!.defines[kDartDefines], encodeDartDefines(['test8', 'test9'])); expect(env!.defines[kIconTreeShakerFlag], 'true'); expect(env!.defines[kDeferredComponents], 'false'); }, overrides: { FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => FakeProcessManager.any(), }, ); testWithoutContext('--enable-experiment is removed from getDefaultCachedKernelPath hash', () { final FileSystem fileSystem = MemoryFileSystem.test(); final Config config = Config.test(); expect( getDefaultCachedKernelPath( trackWidgetCreation: true, dartDefines: [], extraFrontEndOptions: ['--enable-experiment=foo'], fileSystem: fileSystem, config: config, ), 'build/cache.dill.track.dill', ); expect( getDefaultCachedKernelPath( trackWidgetCreation: true, dartDefines: ['foo=bar'], extraFrontEndOptions: ['--enable-experiment=foo'], fileSystem: fileSystem, config: config, ), 'build/06ad47d8e64bd28de537b62ff85357c4.cache.dill.track.dill', ); expect( getDefaultCachedKernelPath( trackWidgetCreation: false, dartDefines: [], extraFrontEndOptions: ['--enable-experiment=foo'], fileSystem: fileSystem, config: config, ), 'build/cache.dill', ); expect( getDefaultCachedKernelPath( trackWidgetCreation: true, dartDefines: [], extraFrontEndOptions: ['--enable-experiment=foo', '--foo=bar'], fileSystem: fileSystem, config: config, ), 'build/95b595cca01caa5f0ca0a690339dd7f6.cache.dill.track.dill', ); }); testUsingContext( 'Release bundle includes native assets', () async { final List dependencies = []; final BuildSystem buildSystem = TestBuildSystem.all(BuildResult(success: true), ( Target target, Environment environment, ) { for (final Target dep in target.dependencies) { dependencies.add(dep.name); } }); await BundleBuilder().build( platform: TargetPlatform.ios, buildInfo: BuildInfo.release, project: FlutterProject.fromDirectoryTest(globals.fs.currentDirectory), mainPath: globals.fs.path.join('lib', 'main.dart'), assetDirPath: 'example', depfilePath: 'example.d', buildSystem: buildSystem, ); expect(dependencies, contains('install_code_assets')); }, overrides: { FileSystem: () => MemoryFileSystem.test(), ProcessManager: () => FakeProcessManager.any(), }, ); } class FakeAssetBundle extends Fake implements AssetBundle { @override final Map entries = {}; }