diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 115877b8104..50d4b165d02 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; @@ -16,6 +17,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart' show TestWindow; import 'package:quiver/testing/async.dart'; import 'package:quiver/time.dart'; +import 'package:path/path.dart' as path; import 'package:test_api/test_api.dart' as test_package; import 'package:stack_trace/stack_trace.dart' as stack_trace; import 'package:vector_math/vector_math_64.dart'; @@ -707,6 +709,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { super.initInstances(); window.onBeginFrame = null; window.onDrawFrame = null; + _mockFlutterAssets(); } FakeAsync _currentFakeAsync; // set in runTest; cleared in postTest @@ -736,6 +739,40 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding { @override int get microtaskCount => _currentFakeAsync.microtaskCount; + static Set _allowedKeys; + + void _mockFlutterAssets() { + if (!Platform.environment.containsKey('UNIT_TEST_ASSETS')) { + return; + } + final String assetFolderPath = Platform.environment['UNIT_TEST_ASSETS']; + _ensureInitialized(assetFolderPath); + BinaryMessages.setMockMessageHandler('flutter/assets', (ByteData message) { + final String key = utf8.decode(message.buffer.asUint8List()); + if (_allowedKeys.contains(key)) { + final File asset = File(path.join(assetFolderPath, key)); + final Uint8List encoded = Uint8List.fromList(asset.readAsBytesSync()); + return Future.value(encoded.buffer.asByteData()); + } + }); + } + + void _ensureInitialized(String assetFolderPath) { + if (_allowedKeys == null) { + final File manifestFile = File( + path.join(assetFolderPath, 'AssetManifest.json')); + final Map manifest = json.decode( + manifestFile.readAsStringSync()); + _allowedKeys = { + 'AssetManifest.json', + }; + for (List value in manifest.values) { + final List strList = List.from(value); + _allowedKeys.addAll(strList); + } + } + } + @override Future pump([ Duration duration, EnginePhase newPhase = EnginePhase.sendSemanticsUpdate ]) { return TestAsyncUtils.guard(() { diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart index c6160133e85..c776bd72d8c 100644 --- a/packages/flutter_tools/lib/src/commands/test.dart +++ b/packages/flutter_tools/lib/src/commands/test.dart @@ -5,9 +5,11 @@ import 'dart:async'; import 'dart:math' as math; +import '../asset.dart'; import '../base/common.dart'; import '../base/file_system.dart'; import '../base/platform.dart'; +import '../bundle.dart'; import '../cache.dart'; import '../codegen.dart'; import '../dart/pub.dart'; @@ -18,7 +20,6 @@ import '../test/coverage_collector.dart'; import '../test/event_printer.dart'; import '../test/runner.dart'; import '../test/watcher.dart'; - class TestCommand extends FastFlutterCommand { TestCommand({ bool verboseHelp = false }) { requiresPubspecYaml(); @@ -83,7 +84,14 @@ class TestCommand extends FastFlutterCommand { abbr: 'j', defaultsTo: math.max(1, platform.numberOfProcessors - 2).toString(), help: 'The number of concurrent test processes to run.', - valueHelp: 'jobs'); + valueHelp: 'jobs' + ) + ..addFlag('test-assets', + defaultsTo: true, + negatable: true, + help: 'Whether to build the assets bundle for testing.\n' + 'Consider using --no-test-assets if assets are not required.', + ); } @override @@ -110,6 +118,10 @@ class TestCommand extends FastFlutterCommand { if (shouldRunPub) { await pubGet(context: PubContext.getVerifyContext(name), skipPubspecYamlCheck: true); } + final bool buildTestAssets = argResults['test-assets']; + if (buildTestAssets) { + await _buildTestAsset(); + } final List names = argResults['name']; final List plainNames = argResults['plain-name']; final FlutterProject flutterProject = await FlutterProject.current(); @@ -195,6 +207,7 @@ class TestCommand extends FastFlutterCommand { trackWidgetCreation: argResults['track-widget-creation'], updateGoldens: argResults['update-goldens'], concurrency: jobs, + buildTestAssets: buildTestAssets, flutterProject: flutterProject, ); @@ -208,6 +221,15 @@ class TestCommand extends FastFlutterCommand { throwToolExit(null); return const FlutterCommandResult(ExitStatus.success); } + + Future _buildTestAsset() async { + final AssetBundle assetBundle = AssetBundleFactory.instance.createBundle(); + final int build = await assetBundle.build(); + if (build != 0) { + throwToolExit('Error: Failed to build asset bundle'); + } + await writeBundle(fs.directory(fs.path.join('build', 'unit_test_assets')), assetBundle.entries); + } } Iterable _findTests(Directory directory) { diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index 91caadecbff..6e43615e9ca 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -88,6 +88,7 @@ void installHook({ Map precompiledDillFiles, bool trackWidgetCreation = false, bool updateGoldens = false, + bool buildTestAssets = false, int observatoryPort, InternetAddressType serverType = InternetAddressType.IPv4, Uri projectRootDirectory, @@ -110,6 +111,7 @@ void installHook({ precompiledDillFiles: precompiledDillFiles, trackWidgetCreation: trackWidgetCreation, updateGoldens: updateGoldens, + buildTestAssets: buildTestAssets, projectRootDirectory: projectRootDirectory, flutterProject: flutterProject, icudtlPath: icudtlPath, @@ -390,6 +392,7 @@ class _FlutterPlatform extends PlatformPlugin { this.precompiledDillFiles, this.trackWidgetCreation, this.updateGoldens, + this.buildTestAssets, this.projectRootDirectory, this.flutterProject, this.icudtlPath, @@ -407,6 +410,7 @@ class _FlutterPlatform extends PlatformPlugin { final Map precompiledDillFiles; final bool trackWidgetCreation; final bool updateGoldens; + final bool buildTestAssets; final Uri projectRootDirectory; final FlutterProject flutterProject; final String icudtlPath; @@ -977,6 +981,10 @@ class _FlutterPlatform extends PlatformPlugin { 'FONTCONFIG_FILE': _fontConfigFile.path, 'SERVER_PORT': serverPort.toString(), }; + if (buildTestAssets) { + environment['UNIT_TEST_ASSETS'] = fs.path.join( + flutterProject.directory.path, 'build', 'unit_test_assets'); + } return processManager.start(command, environment: environment); } diff --git a/packages/flutter_tools/lib/src/test/runner.dart b/packages/flutter_tools/lib/src/test/runner.dart index 14ed2d438f5..26ce0db470d 100644 --- a/packages/flutter_tools/lib/src/test/runner.dart +++ b/packages/flutter_tools/lib/src/test/runner.dart @@ -35,6 +35,7 @@ Future runTests( bool updateGoldens = false, TestWatcher watcher, @required int concurrency, + bool buildTestAssets = false, FlutterProject flutterProject, String icudtlPath, Directory coverageDirectory, @@ -83,6 +84,7 @@ Future runTests( precompiledDillFiles: precompiledDillFiles, trackWidgetCreation: trackWidgetCreation, updateGoldens: updateGoldens, + buildTestAssets: buildTestAssets, projectRootDirectory: fs.currentDirectory.uri, flutterProject: flutterProject, icudtlPath: icudtlPath,