[flutter_tools] refactor FlutterManifest to be context-free (#54555)

diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart
index c2c33d1..6db13de 100644
--- a/packages/flutter_tools/lib/src/asset.dart
+++ b/packages/flutter_tools/lib/src/asset.dart
@@ -128,7 +128,11 @@
     packagesPath ??= globals.fs.path.absolute(PackageMap.globalPackagesPath);
     FlutterManifest flutterManifest;
     try {
-      flutterManifest = FlutterManifest.createFromPath(manifestPath);
+      flutterManifest = FlutterManifest.createFromPath(
+        manifestPath,
+        logger: globals.logger,
+        fileSystem: globals.fs,
+      );
     } on Exception catch (e) {
       globals.printStatus('Error detected in pubspec.yaml:', emphasis: true);
       globals.printError('$e');
@@ -187,7 +191,11 @@
       final Uri packageUri = package.packageUriRoot;
       if (packageUri != null && packageUri.scheme == 'file') {
         final String packageManifestPath = globals.fs.path.fromUri(packageUri.resolve('../pubspec.yaml'));
-        final FlutterManifest packageFlutterManifest = FlutterManifest.createFromPath(packageManifestPath);
+        final FlutterManifest packageFlutterManifest = FlutterManifest.createFromPath(
+          packageManifestPath,
+          logger: globals.logger,
+          fileSystem: globals.fs,
+        );
         if (packageFlutterManifest == null) {
           continue;
         }
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart
index b86ca76..aa3e9b4 100644
--- a/packages/flutter_tools/lib/src/flutter_manifest.dart
+++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -7,42 +7,45 @@
 import 'package:yaml/yaml.dart';
 
 import 'base/file_system.dart';
+import 'base/logger.dart';
 import 'base/user_messages.dart';
 import 'base/utils.dart';
 import 'cache.dart';
-import 'globals.dart' as globals;
 import 'plugins.dart';
 
 /// A wrapper around the `flutter` section in the `pubspec.yaml` file.
 class FlutterManifest {
-  FlutterManifest._();
+  FlutterManifest._(this._logger);
 
   /// Returns an empty manifest.
-  static FlutterManifest empty() {
-    final FlutterManifest manifest = FlutterManifest._();
+  factory FlutterManifest.empty({ @required Logger logger }) {
+    final FlutterManifest manifest = FlutterManifest._(logger);
     manifest._descriptor = const <String, dynamic>{};
     manifest._flutterDescriptor = const <String, dynamic>{};
     return manifest;
   }
 
   /// Returns null on invalid manifest. Returns empty manifest on missing file.
-  static FlutterManifest createFromPath(String path) {
-    if (path == null || !globals.fs.isFileSync(path)) {
-      return _createFromYaml(null);
+  static FlutterManifest createFromPath(String path, {
+    @required FileSystem fileSystem,
+    @required Logger logger,
+  }) {
+    if (path == null || !fileSystem.isFileSync(path)) {
+      return _createFromYaml(null, logger);
     }
-    final String manifest = globals.fs.file(path).readAsStringSync();
-    return createFromString(manifest);
+    final String manifest = fileSystem.file(path).readAsStringSync();
+    return FlutterManifest.createFromString(manifest, logger: logger);
   }
 
   /// Returns null on missing or invalid manifest
   @visibleForTesting
-  static FlutterManifest createFromString(String manifest) {
-    return _createFromYaml(loadYaml(manifest) as YamlMap);
+  static FlutterManifest createFromString(String manifest, { @required Logger logger }) {
+    return _createFromYaml(loadYaml(manifest) as YamlMap, logger);
   }
 
-  static FlutterManifest _createFromYaml(YamlMap yamlDocument) {
-    final FlutterManifest pubspec = FlutterManifest._();
-    if (yamlDocument != null && !_validate(yamlDocument)) {
+  static FlutterManifest _createFromYaml(YamlMap yamlDocument, Logger logger) {
+    final FlutterManifest pubspec = FlutterManifest._(logger);
+    if (yamlDocument != null && !_validate(yamlDocument, logger)) {
       return null;
     }
 
@@ -63,6 +66,8 @@
     return pubspec;
   }
 
+  final Logger _logger;
+
   /// A map representation of the entire `pubspec.yaml` file.
   Map<String, dynamic> _descriptor;
 
@@ -91,7 +96,7 @@
       version = Version.parse(verStr);
     } on Exception {
       if (!_hasShowInvalidVersionMsg) {
-        globals.printStatus(userMessages.invalidVersionSettingHintMessage(verStr), emphasis: true);
+        _logger.printStatus(userMessages.invalidVersionSettingHintMessage(verStr), emphasis: true);
         _hasShowInvalidVersionMsg = true;
       }
     }
@@ -203,14 +208,14 @@
     final List<Uri> results = <Uri>[];
     for (final Object asset in assets) {
       if (asset is! String || asset == null || asset == '') {
-        globals.printError('Asset manifest contains a null or empty uri.');
+        _logger.printError('Asset manifest contains a null or empty uri.');
         continue;
       }
       final String stringAsset = asset as String;
       try {
         results.add(Uri(pathSegments: stringAsset.split('/')));
       } on FormatException {
-        globals.printError('Asset manifest contains invalid uri: $asset.');
+        _logger.printError('Asset manifest contains invalid uri: $asset.');
       }
     }
     return results;
@@ -233,11 +238,11 @@
       final YamlList fontFiles = fontFamily['fonts'] as YamlList;
       final String familyName = fontFamily['family'] as String;
       if (familyName == null) {
-        globals.printError('Warning: Missing family name for font.', emphasis: true);
+        _logger.printError('Warning: Missing family name for font.', emphasis: true);
         continue;
       }
       if (fontFiles == null) {
-        globals.printError('Warning: No fonts specified for font $familyName', emphasis: true);
+        _logger.printError('Warning: No fonts specified for font $familyName', emphasis: true);
         continue;
       }
 
@@ -245,7 +250,7 @@
       for (final Map<dynamic, dynamic> fontFile in fontFiles.cast<Map<dynamic, dynamic>>()) {
         final String asset = fontFile['asset'] as String;
         if (asset == null) {
-          globals.printError('Warning: Missing asset in fonts for $familyName', emphasis: true);
+          _logger.printError('Warning: Missing asset in fonts for $familyName', emphasis: true);
           continue;
         }
 
@@ -310,16 +315,16 @@
 }
 
 @visibleForTesting
-String buildSchemaDir(FileSystem fs) {
-  return globals.fs.path.join(
-    globals.fs.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema',
+String buildSchemaDir(FileSystem fileSystem) {
+  return fileSystem.path.join(
+    fileSystem.path.absolute(Cache.flutterRoot), 'packages', 'flutter_tools', 'schema',
   );
 }
 
 @visibleForTesting
-String buildSchemaPath(FileSystem fs) {
-  return globals.fs.path.join(
-    buildSchemaDir(fs),
+String buildSchemaPath(FileSystem fileSystem) {
+  return fileSystem.path.join(
+    buildSchemaDir(fileSystem),
     'pubspec_yaml.json',
   );
 }
@@ -327,7 +332,7 @@
 /// This method should be kept in sync with the schema in
 /// `$FLUTTER_ROOT/packages/flutter_tools/schema/pubspec_yaml.json`,
 /// but avoid introducing dependencies on packages for simple validation.
-bool _validate(YamlMap manifest) {
+bool _validate(YamlMap manifest, Logger logger) {
   final List<String> errors = <String>[];
   for (final MapEntry<dynamic, dynamic> kvp in manifest.entries) {
     if (kvp.key is! String) {
@@ -357,8 +362,8 @@
   }
 
   if (errors.isNotEmpty) {
-    globals.printStatus('Error detected in pubspec.yaml:', emphasis: true);
-    globals.printError(errors.join('\n'));
+    logger.printStatus('Error detected in pubspec.yaml:', emphasis: true);
+    logger.printError(errors.join('\n'));
     return false;
   }
 
diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart
index 43a743b..5031e3e 100644
--- a/packages/flutter_tools/lib/src/globals.dart
+++ b/packages/flutter_tools/lib/src/globals.dart
@@ -43,7 +43,10 @@
 OperatingSystemUtils get os => context.get<OperatingSystemUtils>();
 PersistentToolState get persistentToolState => PersistentToolState.instance;
 Usage get flutterUsage => context.get<Usage>();
-FlutterProjectFactory get projectFactory => context.get<FlutterProjectFactory>() ?? FlutterProjectFactory();
+FlutterProjectFactory get projectFactory => context.get<FlutterProjectFactory>() ?? FlutterProjectFactory(
+  logger: logger,
+  fileSystem: fs,
+);
 
 const FileSystem _kLocalFs = LocalFileSystem();
 
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 4efd7c5..9928c96 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -12,6 +12,7 @@
 import 'artifacts.dart';
 import 'base/common.dart';
 import 'base/file_system.dart';
+import 'base/logger.dart';
 import 'build_info.dart';
 import 'bundle.dart' as bundle;
 import 'features.dart';
@@ -24,7 +25,14 @@
 import 'template.dart';
 
 class FlutterProjectFactory {
-  FlutterProjectFactory();
+  FlutterProjectFactory({
+    @required Logger logger,
+    @required FileSystem fileSystem,
+  }) : _logger = logger,
+       _fileSystem = fileSystem;
+
+  final Logger _logger;
+  final FileSystem _fileSystem;
 
   @visibleForTesting
   final Map<String, FlutterProject> projects =
@@ -34,14 +42,18 @@
   /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid.
   FlutterProject fromDirectory(Directory directory) {
     assert(directory != null);
-    return projects.putIfAbsent(directory.path, /* ifAbsent */ () {
+    return projects.putIfAbsent(directory.path, () {
       final FlutterManifest manifest = FlutterProject._readManifest(
         directory.childFile(bundle.defaultManifestPath).path,
+        logger: _logger,
+        fileSystem: _fileSystem,
       );
       final FlutterManifest exampleManifest = FlutterProject._readManifest(
         FlutterProject._exampleDirectory(directory)
             .childFile(bundle.defaultManifestPath)
             .path,
+        logger: _logger,
+        fileSystem: _fileSystem,
       );
       return FlutterProject(directory, manifest, exampleManifest);
     });
@@ -167,7 +179,7 @@
   FlutterProject get example => FlutterProject(
     _exampleDirectory(directory),
     _exampleManifest,
-    FlutterManifest.empty(),
+    FlutterManifest.empty(logger: globals.logger),
   );
 
   /// True if this project is a Flutter module project.
@@ -187,13 +199,20 @@
   ///
   /// Completes with an empty [FlutterManifest], if the file does not exist.
   /// Completes with a ToolExit on validation error.
-  static FlutterManifest _readManifest(String path) {
+  static FlutterManifest _readManifest(String path, {
+    @required Logger logger,
+    @required FileSystem fileSystem,
+  }) {
     FlutterManifest manifest;
     try {
-      manifest = FlutterManifest.createFromPath(path);
+      manifest = FlutterManifest.createFromPath(
+        path,
+        logger: logger,
+        fileSystem: fileSystem,
+      );
     } on YamlException catch (e) {
-      globals.printStatus('Error detected in pubspec.yaml:', emphasis: true);
-      globals.printError('$e');
+      logger.printStatus('Error detected in pubspec.yaml:', emphasis: true);
+      logger.printError('$e');
     }
     if (manifest == null) {
       throwToolExit('Please correct the pubspec.yaml file at $path');
diff --git a/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart b/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart
index 81f98300..ace5077 100644
--- a/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart
+++ b/packages/flutter_tools/test/general.shard/flutter_manifest_test.dart
@@ -2,54 +2,60 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:async';
-
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
-import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/flutter_manifest.dart';
 
 import '../src/common.dart';
 import '../src/context.dart';
-import '../src/pubspec_schema.dart';
 
 void main() {
   setUpAll(() {
     Cache.flutterRoot = getFlutterRoot();
   });
 
-  group('FlutterManifest', () {
-    testUsingContext('is empty when the pubspec.yaml file is empty', () async {
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString('');
-      expect(flutterManifest.isEmpty, true);
-      expect(flutterManifest.appName, '');
-      expect(flutterManifest.usesMaterialDesign, false);
-      expect(flutterManifest.fontsDescriptor, isEmpty);
-      expect(flutterManifest.fonts, isEmpty);
-      expect(flutterManifest.assets, isEmpty);
-    });
+  testWithoutContext('FlutterManifest is empty when the pubspec.yaml file is empty', () async {
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      '',
+      logger: logger,
+    );
 
-    test('has no fonts or assets when the "flutter" section is empty', () async {
-      const String manifest = '''
+    expect(flutterManifest.isEmpty, true);
+    expect(flutterManifest.appName, '');
+    expect(flutterManifest.usesMaterialDesign, false);
+    expect(flutterManifest.fontsDescriptor, isEmpty);
+    expect(flutterManifest.fonts, isEmpty);
+    expect(flutterManifest.assets, isEmpty);
+  });
+
+  testWithoutContext('FlutterManifest has no fonts or assets when the "flutter" section is empty', () async {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
     sdk: flutter
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest, isNotNull);
-      expect(flutterManifest.isEmpty, false);
-      expect(flutterManifest.appName, 'test');
-      expect(flutterManifest.usesMaterialDesign, false);
-      expect(flutterManifest.fontsDescriptor, isEmpty);
-      expect(flutterManifest.fonts, isEmpty);
-      expect(flutterManifest.assets, isEmpty);
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('knows if material design is used', () async {
-      const String manifest = '''
+    expect(flutterManifest, isNotNull);
+    expect(flutterManifest.isEmpty, false);
+    expect(flutterManifest.appName, 'test');
+    expect(flutterManifest.usesMaterialDesign, false);
+    expect(flutterManifest.fontsDescriptor, isEmpty);
+    expect(flutterManifest.fonts, isEmpty);
+    expect(flutterManifest.assets, isEmpty);
+  });
+
+  testWithoutContext('FlutterManifest knows if material design is used', () async {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -57,12 +63,17 @@
 flutter:
   uses-material-design: true
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.usesMaterialDesign, true);
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('has two assets', () async {
-      const String manifest = '''
+    expect(flutterManifest.usesMaterialDesign, true);
+  });
+
+  testWithoutContext('FlutterManifest has two assets', () async {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -73,14 +84,20 @@
     - a/foo
     - a/bar
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.assets.length, 2);
-      expect(flutterManifest.assets[0], Uri.parse('a/foo'));
-      expect(flutterManifest.assets[1], Uri.parse('a/bar'));
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('has one font family with one asset', () async {
-      const String manifest = '''
+    expect(flutterManifest.assets, <Uri>[
+      Uri.parse('a/foo'),
+      Uri.parse('a/bar'),
+    ]);
+  });
+
+  testWithoutContext('FlutterManifest has one font family with one asset', () async {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -92,24 +109,29 @@
       fonts:
         - asset: a/bar
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
-      expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
-      final List<Font> fonts = flutterManifest.fonts;
-      expect(fonts.length, 1);
-      final Font font = fonts[0];
-      final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}]}; // ignore: always_specify_types
-      expect(font.descriptor, fooFontDescriptor);
-      expect(font.familyName, 'foo');
-      final List<FontAsset> assets = font.fontAssets;
-      expect(assets.length, 1);
-      final FontAsset fontAsset = assets[0];
-      expect(fontAsset.assetUri.path, 'a/bar');
-      expect(fontAsset.weight, isNull);
-      expect(fontAsset.style, isNull);
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('has one font family with a simple asset and one with weight', () async {
+    expect(flutterManifest.fonts, hasLength(1));
+    expect(flutterManifest.fonts.single, matchesFont(
+      familyName: 'foo',
+      descriptor: <String, Object>{
+        'family': 'foo',
+        'fonts': <Object>[
+          <String, Object>{'asset': 'a/bar'},
+        ],
+      },
+      fontAssets: <Matcher>[
+        matchesFontAsset(assetUri: Uri.parse('a/bar')),
+      ],
+    ));
+  });
+
+  testWithoutContext('FlutterManifest has one font family with a simple asset '
+    'and one with weight', () async {
       const String manifest = '''
 name: test
 dependencies:
@@ -124,29 +146,32 @@
         - asset: a/bar
           weight: 400
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
-      expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
-      final List<Font> fonts = flutterManifest.fonts;
-      expect(fonts.length, 1);
-      final Font font = fonts[0];
-      final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'asset': 'a/bar'}]}; // ignore: always_specify_types
-      expect(font.descriptor, fooFontDescriptor);
-      expect(font.familyName, 'foo');
-      final List<FontAsset> assets = font.fontAssets;
-      expect(assets.length, 2);
-      final FontAsset fontAsset0 = assets[0];
-      expect(fontAsset0.assetUri.path, 'a/bar');
-      expect(fontAsset0.weight, isNull);
-      expect(fontAsset0.style, isNull);
-      final FontAsset fontAsset1 = assets[1];
-      expect(fontAsset1.assetUri.path, 'a/bar');
-      expect(fontAsset1.weight, 400);
-      expect(fontAsset1.style, isNull);
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('has one font family with a simple asset and one with weight and style', () async {
-      const String manifest = '''
+    expect(flutterManifest.fonts, hasLength(1));
+    expect(flutterManifest.fonts.single, matchesFont(
+      familyName: 'foo',
+      descriptor: <String, Object>{
+        'family': 'foo',
+        'fonts': <Object>[
+          <String, Object>{'asset': 'a/bar'},
+          <String, Object>{'weight': 400, 'asset': 'a/bar'},
+        ],
+      },
+      fontAssets: <Matcher>[
+        matchesFontAsset(assetUri: Uri.parse('a/bar')),
+        matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400),
+      ])
+    );
+  });
+
+  testWithoutContext('FlutterManifest has one font family with a simple asset '
+    'and one with weight and style', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -161,30 +186,32 @@
           weight: 400
           style: italic
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
-      final List<Font> fonts = flutterManifest.fonts;
-      expect(fonts.length, 1);
-      final Font font = fonts[0];
-      final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types
-      expect(font.descriptor, fooFontDescriptor);
-      expect(font.familyName, 'foo');
-      final List<FontAsset> assets = font.fontAssets;
-      expect(assets.length, 2);
-      final FontAsset fontAsset0 = assets[0];
-      expect(fontAsset0.assetUri.path, 'a/bar');
-      expect(fontAsset0.weight, isNull);
-      expect(fontAsset0.style, isNull);
-      final FontAsset fontAsset1 = assets[1];
-      expect(fontAsset1.assetUri.path, 'a/bar');
-      expect(fontAsset1.weight, 400);
-      expect(fontAsset1.style, 'italic');
-    });
+    expect(flutterManifest.fonts, hasLength(1));
+    expect(flutterManifest.fonts.single, matchesFont(
+      familyName: 'foo',
+      descriptor: <String, Object>{
+        'family': 'foo',
+        'fonts': <Object>[
+          <String, Object>{'asset': 'a/bar'},
+          <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
+        ],
+      },
+      fontAssets: <Matcher>[
+        matchesFontAsset(assetUri: Uri.parse('a/bar')),
+        matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
+      ],
+    ));
+  });
 
-    test('has two font families, each with one simple asset and one with weight and style', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest has two font families, each with one '
+    'simple asset and one with weight and style', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -205,48 +232,95 @@
           asset: a/baz
           style: italic
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      final dynamic expectedFontsDescriptor = <dynamic>[
-        {'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}, // ignore: always_specify_types
-        {'fonts': [{'asset': 'a/baz'}, {'style': 'italic', 'weight': 400, 'asset': 'a/baz'}], 'family': 'bar'}, // ignore: always_specify_types
-      ];
-      expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
-      final List<Font> fonts = flutterManifest.fonts;
-      expect(fonts.length, 2);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      final Font fooFont = fonts[0];
-      final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types
-      expect(fooFont.descriptor, fooFontDescriptor);
-      expect(fooFont.familyName, 'foo');
-      final List<FontAsset> fooAassets = fooFont.fontAssets;
-      expect(fooAassets.length, 2);
-      final FontAsset fooFontAsset0 = fooAassets[0];
-      expect(fooFontAsset0.assetUri.path, 'a/bar');
-      expect(fooFontAsset0.weight, isNull);
-      expect(fooFontAsset0.style, isNull);
-      final FontAsset fooFontAsset1 = fooAassets[1];
-      expect(fooFontAsset1.assetUri.path, 'a/bar');
-      expect(fooFontAsset1.weight, 400);
-      expect(fooFontAsset1.style, 'italic');
+    expect(flutterManifest.fonts, hasLength(2));
+    expect(flutterManifest.fonts, containsAll(<Matcher>[
+      matchesFont(
+        familyName: 'foo',
+        descriptor:  <String, Object>{
+          'family': 'foo',
+          'fonts': <Object>[
+            <String, Object>{'asset': 'a/bar'},
+            <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
+          ],
+        },
+        fontAssets: <Matcher>[
+          matchesFontAsset(assetUri: Uri.parse('a/bar')),
+          matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
+        ],
+      ),
+      matchesFont(
+        familyName: 'bar',
+        descriptor: <String, Object>{
+          'family': 'bar',
+          'fonts': <Object>[
+            <String, Object>{'asset': 'a/baz'},
+            <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/baz'},
+          ],
+        },
+        fontAssets: <Matcher>[
+          matchesFontAsset(assetUri: Uri.parse('a/baz')),
+          matchesFontAsset(assetUri: Uri.parse('a/baz'), weight: 400, style: 'italic'),
+        ],
+      ),
+    ]));
+  });
 
-      final Font barFont = fonts[1];
-      const String fontDescriptor = '{family: bar, fonts: [{asset: a/baz}, {weight: 400, style: italic, asset: a/baz}]}'; // ignore: always_specify_types
-      expect(barFont.descriptor.toString(), fontDescriptor);
-      expect(barFont.familyName, 'bar');
-      final List<FontAsset> barAssets = barFont.fontAssets;
-      expect(barAssets.length, 2);
-      final FontAsset barFontAsset0 = barAssets[0];
-      expect(barFontAsset0.assetUri.path, 'a/baz');
-      expect(barFontAsset0.weight, isNull);
-      expect(barFontAsset0.style, isNull);
-      final FontAsset barFontAsset1 = barAssets[1];
-      expect(barFontAsset1.assetUri.path, 'a/baz');
-      expect(barFontAsset1.weight, 400);
-      expect(barFontAsset1.style, 'italic');
-    });
+  testWithoutContext('FlutterManifest.fontsDescriptor combines descriptors from '
+    'individual fonts', () {
+    const String manifest = '''
+name: test
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+  uses-material-design: true
+  fonts:
+    - family: foo
+      fonts:
+        - asset: a/bar
+        - asset: a/bar
+          weight: 400
+          style: italic
+    - family: bar
+      fonts:
+        - asset: a/baz
+        - weight: 400
+          asset: a/baz
+          style: italic
+''';
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    testUsingContext('has only one of two font families when one declaration is missing the "family" option', () async {
-      const String manifest = '''
+    expect(flutterManifest.fontsDescriptor, <Object>[
+      <String, Object>{
+        'family': 'foo',
+        'fonts': <Object>[
+          <String, Object>{'asset': 'a/bar'},
+          <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
+        ],
+      },
+      <String, Object>{
+        'family': 'bar',
+        'fonts': <Object>[
+          <String, Object>{'asset': 'a/baz'},
+          <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/baz'},
+        ],
+      },
+    ]);
+  });
+
+  testWithoutContext('FlutterManifest has only one of two font families when '
+    'one declaration is missing the "family" option', () async {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -266,30 +340,34 @@
           weight: 400
           style: italic
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
-      expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
-      final List<Font> fonts = flutterManifest.fonts;
-      expect(fonts.length, 1);
-      final Font fooFont = fonts[0];
-      final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types
-      expect(fooFont.descriptor, fooFontDescriptor);
-      expect(fooFont.familyName, 'foo');
-      final List<FontAsset> fooAassets = fooFont.fontAssets;
-      expect(fooAassets.length, 2);
-      final FontAsset fooFontAsset0 = fooAassets[0];
-      expect(fooFontAsset0.assetUri.path, 'a/bar');
-      expect(fooFontAsset0.weight, isNull);
-      expect(fooFontAsset0.style, isNull);
-      final FontAsset fooFontAsset1 = fooAassets[1];
-      expect(fooFontAsset1.assetUri.path, 'a/bar');
-      expect(fooFontAsset1.weight, 400);
-      expect(fooFontAsset1.style, 'italic');
-    });
+    expect(flutterManifest.fonts, hasLength(1));
+    expect(flutterManifest.fonts, containsAll(<Matcher>[
+      matchesFont(
+        familyName: 'foo',
+        descriptor:  <String, Object>{
+          'family': 'foo',
+          'fonts': <Object>[
+            <String, Object>{'asset': 'a/bar'},
+            <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
+          ],
+        },
+        fontAssets: <Matcher>[
+          matchesFontAsset(assetUri: Uri.parse('a/bar')),
+          matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
+        ],
+      ),
+    ]));
+  });
 
-    testUsingContext('has only one of two font families when one declaration is missing the "fonts" option', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest has only one of two font families when '
+    'one declaration is missing the "fonts" option', () async {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -305,29 +383,34 @@
           style: italic
     - family: bar
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      final dynamic expectedFontsDescriptor = [{'fonts': [{'asset': 'a/bar'}, {'style': 'italic', 'weight': 400, 'asset': 'a/bar'}], 'family': 'foo'}]; // ignore: always_specify_types
-      expect(flutterManifest.fontsDescriptor, expectedFontsDescriptor);
-      final List<Font> fonts = flutterManifest.fonts;
-      expect(fonts.length, 1);
-      final Font fooFont = fonts[0];
-      final dynamic fooFontDescriptor = {'family': 'foo', 'fonts': [{'asset': 'a/bar'}, {'weight': 400, 'style': 'italic', 'asset': 'a/bar'}]}; // ignore: always_specify_types
-      expect(fooFont.descriptor, fooFontDescriptor);
-      expect(fooFont.familyName, 'foo');
-      final List<FontAsset> fooAassets = fooFont.fontAssets;
-      expect(fooAassets.length, 2);
-      final FontAsset fooFontAsset0 = fooAassets[0];
-      expect(fooFontAsset0.assetUri.path, 'a/bar');
-      expect(fooFontAsset0.weight, isNull);
-      expect(fooFontAsset0.style, isNull);
-      final FontAsset fooFontAsset1 = fooAassets[1];
-      expect(fooFontAsset1.assetUri.path, 'a/bar');
-      expect(fooFontAsset1.weight, 400);
-      expect(fooFontAsset1.style, 'italic');
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    testUsingContext('has no font family when declaration is missing the "asset" option', () async {
-      const String manifest = '''
+    expect(flutterManifest.fonts, hasLength(1));
+    expect(flutterManifest.fonts, containsAll(<Matcher>[
+      matchesFont(
+        familyName: 'foo',
+        descriptor:  <String, Object>{
+          'family': 'foo',
+          'fonts': <Object>[
+            <String, Object>{'asset': 'a/bar'},
+            <String, Object>{'weight': 400, 'style': 'italic', 'asset': 'a/bar'},
+          ],
+        },
+        fontAssets: <Matcher>[
+          matchesFontAsset(assetUri: Uri.parse('a/bar')),
+          matchesFontAsset(assetUri: Uri.parse('a/bar'), weight: 400, style: 'italic'),
+        ],
+      ),
+    ]));
+  });
+
+  testWithoutContext('FlutterManifest has no font family when declaration is '
+    'missing the "asset" option', () async {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -339,57 +422,76 @@
       fonts:
         - weight: 400
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      expect(flutterManifest.fontsDescriptor, <dynamic>[]);
-      final List<Font> fonts = flutterManifest.fonts;
-      expect(fonts.length, 0);
-    });
+    expect(flutterManifest.fontsDescriptor, isEmpty);
+    expect(flutterManifest.fonts, isEmpty);
+  });
 
-    test('allows a blank flutter section', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest allows a blank flutter section', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
     sdk: flutter
 flutter:
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.isEmpty, false);
-      expect(flutterManifest.isModule, false);
-      expect(flutterManifest.isPlugin, false);
-      expect(flutterManifest.androidPackage, null);
-      expect(flutterManifest.usesAndroidX, false);
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('allows a module declaration', () async {
-      const String manifest = '''
+    expect(flutterManifest.isEmpty, false);
+    expect(flutterManifest.isModule, false);
+    expect(flutterManifest.isPlugin, false);
+    expect(flutterManifest.androidPackage, null);
+    expect(flutterManifest.usesAndroidX, false);
+  });
+
+  testWithoutContext('FlutterManifest allows a module declaration', () {
+    const String manifest = '''
 name: test
 flutter:
   module:
     androidPackage: com.example
     androidX: true
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.isModule, true);
-      expect(flutterManifest.androidPackage, 'com.example');
-      expect(flutterManifest.usesAndroidX, true);
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('allows a legacy plugin declaration', () async {
-      const String manifest = '''
+    expect(flutterManifest.isModule, true);
+    expect(flutterManifest.androidPackage, 'com.example');
+    expect(flutterManifest.usesAndroidX, true);
+  });
+
+  testWithoutContext('FlutterManifest allows a legacy plugin declaration', () {
+    const String manifest = '''
 name: test
 flutter:
   plugin:
     androidPackage: com.example
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.isPlugin, true);
-      expect(flutterManifest.androidPackage, 'com.example');
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('allows a multi-plat plugin declaration with android only', () async {
-      const String manifest = '''
+    expect(flutterManifest.isPlugin, true);
+    expect(flutterManifest.androidPackage, 'com.example');
+  });
+
+  testWithoutContext('FlutterManifest allows a multi-plat plugin declaration '
+    'with android only', () {
+    const String manifest = '''
 name: test
 flutter:
     plugin:
@@ -398,13 +500,19 @@
           package: com.example
           pluginClass: TestPlugin
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.isPlugin, true);
-      expect(flutterManifest.androidPackage, 'com.example');
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('allows a multi-plat plugin declaration with ios only', () async {
-      const String manifest = '''
+    expect(flutterManifest.isPlugin, true);
+    expect(flutterManifest.androidPackage, 'com.example');
+  });
+
+  testWithoutContext('FlutterManifest allows a multi-plat plugin declaration '
+    'with ios only', () {
+    const String manifest = '''
 name: test
 flutter:
     plugin:
@@ -412,37 +520,35 @@
         ios:
           pluginClass: HelloPlugin
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.isPlugin, true);
-      expect(flutterManifest.androidPackage, isNull);
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    testUsingContext('handles an invalid plugin declaration', () async {
-      const String manifest = '''
+    expect(flutterManifest.isPlugin, true);
+    expect(flutterManifest.androidPackage, isNull);
+  });
+
+  testUsingContext('FlutterManifest handles an invalid plugin declaration', () {
+    const String manifest = '''
 name: test
 flutter:
     plugin:
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest, null);
-      expect(testLogger.errorText, contains('Expected "plugin" to be an object, but got null'));
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
+    expect(flutterManifest, null);
+    expect(logger.errorText,
+      contains('Expected "plugin" to be an object, but got null'));
+  });
 
-    Future<void> checkManifestVersion({
-      String manifest,
-      String expectedAppVersion,
-      String expectedBuildName,
-      String expectedBuildNumber,
-    }) async {
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.appVersion, expectedAppVersion);
-      expect(flutterManifest.buildName, expectedBuildName);
-      expect(flutterManifest.buildNumber, expectedBuildNumber);
-    }
-
-    test('parses major.minor.patch+build version clause 1', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest parses major.minor.patch+build version clause 1', () {
+    const String manifest = '''
 name: test
 version: 1.0.0+2
 dependencies:
@@ -450,16 +556,21 @@
     sdk: flutter
 flutter:
 ''';
-      await checkManifestVersion(
-        manifest: manifest,
-        expectedAppVersion: '1.0.0+2',
-        expectedBuildName: '1.0.0',
-        expectedBuildNumber: '2',
-      );
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('parses major.minor.patch with no build version', () async {
-      const String manifest = '''
+    expect(flutterManifest, matchesManifest(
+      appVersion: '1.0.0+2',
+      buildName: '1.0.0',
+      buildNumber: '2'),
+    );
+  });
+
+  testWithoutContext('FlutterManifest parses major.minor.patch with no build version', () {
+    const String manifest = '''
 name: test
 version: 0.0.1
 dependencies:
@@ -467,16 +578,21 @@
     sdk: flutter
 flutter:
 ''';
-      await checkManifestVersion(
-        manifest: manifest,
-        expectedAppVersion: '0.0.1',
-        expectedBuildName: '0.0.1',
-        expectedBuildNumber: null,
-      );
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('parses major.minor.patch+build version clause 2', () async {
-      const String manifest = '''
+    expect(flutterManifest, matchesManifest(
+      appVersion:  '0.0.1',
+      buildName: '0.0.1',
+      buildNumber: null),
+    );
+  });
+
+  testWithoutContext('FlutterManifest parses major.minor.patch+build version clause 2', () {
+    const String manifest = '''
 name: test
 version: 1.0.0-beta+exp.sha.5114f85
 dependencies:
@@ -484,16 +600,21 @@
     sdk: flutter
 flutter:
 ''';
-      await checkManifestVersion(
-        manifest: manifest,
-        expectedAppVersion: '1.0.0-beta+exp.sha.5114f85',
-        expectedBuildName: '1.0.0-beta',
-        expectedBuildNumber: 'exp.sha.5114f85',
-      );
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('parses major.minor+build version clause', () async {
-      const String manifest = '''
+    expect(flutterManifest, matchesManifest(
+      appVersion: '1.0.0-beta+exp.sha.5114f85',
+      buildName: '1.0.0-beta',
+      buildNumber: 'exp.sha.5114f85'),
+    );
+  });
+
+  testWithoutContext('FlutterManifest parses major.minor+build version clause', () {
+    const String manifest = '''
 name: test
 version: 1.0+2
 dependencies:
@@ -501,16 +622,21 @@
     sdk: flutter
 flutter:
 ''';
-      await checkManifestVersion(
-        manifest: manifest,
-        expectedAppVersion: '1.0+2',
-        expectedBuildName: '1.0',
-        expectedBuildNumber: '2',
-      );
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('parses empty version clause', () async {
-      const String manifest = '''
+    expect(flutterManifest, matchesManifest(
+      appVersion: '1.0+2',
+      buildName: '1.0',
+      buildNumber: '2'),
+    );
+  });
+
+  testWithoutContext('FlutterManifest parses empty version clause', () {
+    const String manifest = '''
 name: test
 version:
 dependencies:
@@ -518,33 +644,43 @@
     sdk: flutter
 flutter:
 ''';
-      await checkManifestVersion(
-        manifest: manifest,
-        expectedAppVersion: null,
-        expectedBuildName: null,
-        expectedBuildNumber: null,
-      );
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    test('parses no version clause', () async {
-      const String manifest = '''
+    expect(flutterManifest, matchesManifest(
+      appVersion: null,
+      buildName: null,
+      buildNumber: null),
+    );
+  });
+
+  testWithoutContext('FlutterManifest parses no version clause', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
     sdk: flutter
 flutter:
 ''';
-      await checkManifestVersion(
-        manifest: manifest,
-        expectedAppVersion: null,
-        expectedBuildName: null,
-        expectedBuildNumber: null,
-      );
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
+
+    expect(flutterManifest, matchesManifest(
+      appVersion: null,
+      buildName: null,
+      buildNumber: null),
+    );
+  });
 
     // Regression test for https://github.com/flutter/flutter/issues/31764
-    testUsingContext('Returns proper error when font detail is malformed', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest returns proper error when font detail is malformed', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -555,14 +691,20 @@
       fonts:
         -asset: a/bar
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      expect(flutterManifest, null);
-      expect(testLogger.errorText, contains('Expected "fonts" to either be null or a list.'));
-    });
+    expect(flutterManifest, null);
+    expect(logger.errorText,
+      contains('Expected "fonts" to either be null or a list.'));
+  });
 
-    testUsingContext('Returns proper error when font detail is not a list of maps', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest returns proper error when font detail is '
+    'not a list of maps', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -573,14 +715,20 @@
       fonts:
         - asset
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      expect(flutterManifest, null);
-      expect(testLogger.errorText, contains('Expected "fonts" to be a list of maps.'));
-    });
+    expect(flutterManifest, null);
+    expect(logger.errorText,
+      contains('Expected "fonts" to be a list of maps.'));
+  });
 
-    testUsingContext('Returns proper error when font is a map instead of a list', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest returns proper error when font is a map '
+    'instead of a list', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -591,14 +739,19 @@
     fonts:
       -asset: a/bar
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      expect(flutterManifest, null);
-      expect(testLogger.errorText, contains('Expected "fonts" to be a list'));
-    });
+    expect(flutterManifest, null);
+    expect(logger.errorText, contains('Expected "fonts" to be a list'));
+  });
 
-    testUsingContext('Returns proper error when second font family is invalid', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest returns proper error when second font '
+    'family is invalid', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -611,13 +764,18 @@
         - asset: a/bar
     - string
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest, null);
-      expect(testLogger.errorText, contains('Expected a map.'));
-    });
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-    testUsingContext('Does not crash on empty entry', () async {
-      const String manifest = '''
+    expect(flutterManifest, null);
+    expect(logger.errorText, contains('Expected a map.'));
+  });
+
+  testWithoutContext('FlutterManifest does not crash on empty entry', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -628,15 +786,19 @@
     - lib/gallery/example_code.dart
     -
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      final List<Uri> assets = flutterManifest.assets;
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
+    final List<Uri> assets = flutterManifest.assets;
 
-      expect(testLogger.errorText, contains('Asset manifest contains a null or empty uri.'));
-      expect(assets.length, 1);
-    });
+    expect(logger.errorText, contains('Asset manifest contains a null or empty uri.'));
+    expect(assets, hasLength(1));
+  });
 
-    testUsingContext('Special characters in asset URIs', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest handles special characters in asset URIs', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -648,17 +810,24 @@
     - lib/gallery/abc?xyz
     - lib/gallery/aaa bbb
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      final List<Uri> assets = flutterManifest.assets;
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
+    final List<Uri> assets = flutterManifest.assets;
 
-      expect(assets.length, 3);
-      expect(assets[0].path, 'lib/gallery/abc%23xyz');
-      expect(assets[1].path, 'lib/gallery/abc%3Fxyz');
-      expect(assets[2].path, 'lib/gallery/aaa%20bbb');
-    });
+    expect(assets, hasLength(3));
+    expect(assets, <Uri>[
+      Uri.parse('lib/gallery/abc%23xyz'),
+      Uri.parse('lib/gallery/abc%3Fxyz'),
+      Uri.parse('lib/gallery/aaa%20bbb'),
+    ]);
+  });
 
-    testUsingContext('Returns proper error when flutter is a list instead of a map', () async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest returns proper error when flutter is a '
+    'list instead of a map', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -666,16 +835,43 @@
 flutter:
   - uses-material-design: true
 ''';
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromString(
+      manifest,
+      logger: logger,
+    );
 
-      expect(flutterManifest, null);
-      expect(testLogger.errorText, contains('Expected "flutter" section to be an object or null, but got [{uses-material-design: true}].'));
-    });
+    expect(flutterManifest, null);
+    expect(logger.errorText,
+      contains(
+        'Expected "flutter" section to be an object or null, but got '
+        '[{uses-material-design: true}].',
+      ),
+    );
   });
 
-  group('FlutterManifest with MemoryFileSystem', () {
-    Future<void> assertSchemaIsReadable() async {
-      const String manifest = '''
+  testWithoutContext('FlutterManifest can parse manifest on posix filesystem', () {
+    const String manifest = '''
+name: test
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+    final FileSystem fileSystem = MemoryFileSystem.test();
+    fileSystem.file('pubspec.yaml').writeAsStringSync(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromPath(
+       'pubspec.yaml',
+      fileSystem: fileSystem,
+      logger: logger,
+    );
+
+    expect(flutterManifest.isEmpty, false);
+  });
+
+  testWithoutContext('FlutterManifest can parse manifest on windows filesystem', () {
+    const String manifest = '''
 name: test
 dependencies:
   flutter:
@@ -683,47 +879,48 @@
 flutter:
 ''';
 
-      final FlutterManifest flutterManifest = FlutterManifest.createFromString(manifest);
-      expect(flutterManifest.isEmpty, false);
-    }
-
-    void testUsingContextAndFs(
-      String description,
-      FileSystem filesystem,
-      dynamic testMethod(),
-    ) {
-      testUsingContext(
-        description,
-        () async {
-          writeEmptySchemaFile(filesystem);
-          testMethod();
-        },
-        overrides: <Type, Generator>{
-          FileSystem: () => filesystem,
-          ProcessManager: () => FakeProcessManager.any(),
-        },
-      );
-    }
-
-    testUsingContext('Validate manifest on original fs', () {
-      assertSchemaIsReadable();
-    });
-
-    testUsingContextAndFs(
-      'Validate manifest on Posix FS',
-      MemoryFileSystem(style: FileSystemStyle.posix),
-      () {
-        assertSchemaIsReadable();
-      },
+    final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
+    fileSystem.file('pubspec.yaml').writeAsStringSync(manifest);
+    final BufferLogger logger = BufferLogger.test();
+    final FlutterManifest flutterManifest = FlutterManifest.createFromPath(
+      'pubspec.yaml',
+      fileSystem: fileSystem,
+      logger: logger,
     );
 
-    testUsingContextAndFs(
-      'Validate manifest on Windows FS',
-      MemoryFileSystem(style: FileSystemStyle.windows),
-      () {
-        assertSchemaIsReadable();
-      },
-    );
-
+    expect(flutterManifest.isEmpty, false);
   });
 }
+
+Matcher matchesManifest({
+  String appVersion,
+  String buildName,
+  String buildNumber,
+}) {
+  return isA<FlutterManifest>()
+    .having((FlutterManifest manifest) => manifest.appVersion, 'appVersion', appVersion)
+    .having((FlutterManifest manifest) => manifest.buildName, 'buildName', buildName)
+    .having((FlutterManifest manifest) => manifest.buildNumber, 'buildNumber', buildNumber);
+}
+
+Matcher matchesFontAsset({
+  Uri assetUri,
+  int weight,
+  String style,
+}) {
+  return isA<FontAsset>()
+    .having((FontAsset fontAsset) => fontAsset.assetUri, 'assetUri', assetUri)
+    .having((FontAsset fontAsset) => fontAsset.weight, 'weight', weight)
+    .having((FontAsset fontAsset) => fontAsset.style, 'style', style);
+}
+
+Matcher matchesFont({
+  Map<String, Object> descriptor,
+  String familyName,
+  List<Matcher> fontAssets,
+}) {
+  return isA<Font>()
+    .having((Font font) => font.descriptor, 'descriptor', descriptor)
+    .having((Font font) => font.familyName, 'familyName', familyName)
+    .having((Font font) => font.fontAssets, 'fontAssets', containsAll(fontAssets));
+}
diff --git a/packages/flutter_tools/test/general.shard/github_template_test.dart b/packages/flutter_tools/test/general.shard/github_template_test.dart
index b5142c1..1082d51 100644
--- a/packages/flutter_tools/test/general.shard/github_template_test.dart
+++ b/packages/flutter_tools/test/general.shard/github_template_test.dart
@@ -160,7 +160,10 @@
           fileSystem: fs,
           logger: logger,
           client: SuccessShortenURLFakeHttpClient(),
-          flutterProjectFactory: FlutterProjectFactory(),
+          flutterProjectFactory: FlutterProjectFactory(
+            fileSystem: fs,
+            logger: logger,
+          ),
         );
         expect(
             await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText),
@@ -176,7 +179,10 @@
           fileSystem: fs,
           logger: logger,
           client: FakeHttpClient(),
-          flutterProjectFactory: FlutterProjectFactory(),
+          flutterProjectFactory: FlutterProjectFactory(
+            fileSystem: fs,
+            logger: logger,
+          ),
         );
         expect(
             await creator.toolCrashIssueTemplateGitHubURL(command, error, stackTrace, doctorText),
@@ -199,7 +205,10 @@
           fileSystem: fs,
           logger: logger,
           client: FakeHttpClient(),
-          flutterProjectFactory: FlutterProjectFactory(),
+          flutterProjectFactory: FlutterProjectFactory(
+            fileSystem: fs,
+            logger: logger,
+          ),
         );
         final Directory projectDirectory = fs.currentDirectory;
 
diff --git a/packages/flutter_tools/test/general.shard/project_test.dart b/packages/flutter_tools/test/general.shard/project_test.dart
index 1647480..b212434 100644
--- a/packages/flutter_tools/test/general.shard/project_test.dart
+++ b/packages/flutter_tools/test/general.shard/project_test.dart
@@ -8,6 +8,7 @@
 import 'package:file/memory.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/features.dart';
 import 'package:flutter_tools/src/flutter_manifest.dart';
@@ -23,6 +24,10 @@
 import '../src/testbed.dart';
 
 void main() {
+  // TODO(jonahwilliams): remove once FlutterProject is fully refactored.
+  // this is safe since no tests have expectations on the test logger.
+  final BufferLogger logger = BufferLogger.test();
+
   group('Project', () {
     group('construction', () {
       testInMemory('fails on null directory', () async {
@@ -154,8 +159,8 @@
       testInMemory('does nothing, if project is not created', () async {
         final FlutterProject project = FlutterProject(
           globals.fs.directory('not_created'),
-          FlutterManifest.empty(),
-          FlutterManifest.empty(),
+          FlutterManifest.empty(logger: logger),
+          FlutterManifest.empty(logger: logger),
         );
         await project.ensureReadyForPlatformSpecificTooling();
         expectNotExists(project.directory);
@@ -197,7 +202,10 @@
         FileSystem: () => MemoryFileSystem(),
         ProcessManager: () => FakeProcessManager.any(),
         FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
-        FlutterProjectFactory: () => FlutterProjectFactory(),
+        FlutterProjectFactory: () => FlutterProjectFactory(
+          logger: logger,
+          fileSystem: globals.fs,
+        ),
       });
       testUsingContext('generates Xcode configuration for macOS', () async {
         final FlutterProject project = await someProject();
@@ -208,7 +216,10 @@
         FileSystem: () => MemoryFileSystem(),
         ProcessManager: () => FakeProcessManager.any(),
         FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true),
-        FlutterProjectFactory: () => FlutterProjectFactory(),
+        FlutterProjectFactory: () => FlutterProjectFactory(
+          logger: logger,
+          fileSystem: globals.fs,
+        ),
       });
       testUsingContext('injects plugins for Linux', () async {
         final FlutterProject project = await someProject();
@@ -220,7 +231,10 @@
         FileSystem: () => MemoryFileSystem(),
         ProcessManager: () => FakeProcessManager.any(),
         FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
-        FlutterProjectFactory: () => FlutterProjectFactory(),
+        FlutterProjectFactory: () => FlutterProjectFactory(
+          logger: logger,
+          fileSystem: globals.fs,
+        ),
       });
       testUsingContext('injects plugins for Windows', () async {
         final FlutterProject project = await someProject();
@@ -246,7 +260,10 @@
         FileSystem: () => MemoryFileSystem(),
         ProcessManager: () => FakeProcessManager.any(),
         FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
-        FlutterProjectFactory: () => FlutterProjectFactory(),
+        FlutterProjectFactory: () => FlutterProjectFactory(
+          logger: logger,
+          fileSystem: globals.fs,
+        ),
       });
       testInMemory('creates Android library in module', () async {
         final FlutterProject project = await aModuleProject();
@@ -312,7 +329,10 @@
       setUp(() {
         fs = MemoryFileSystem();
         mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
-        flutterProjectFactory = FlutterProjectFactory();
+        flutterProjectFactory = FlutterProjectFactory(
+          logger: logger,
+          fileSystem: fs,
+        );
       });
 
       testInMemory('default host app language', () async {
@@ -348,7 +368,10 @@
         fs = MemoryFileSystem();
         mockPlistUtils = MockPlistUtils();
         mockXcodeProjectInterpreter = MockXcodeProjectInterpreter();
-        flutterProjectFactory = FlutterProjectFactory();
+        flutterProjectFactory = FlutterProjectFactory(
+          fileSystem: fs,
+          logger: logger,
+        );
       });
 
       void testWithMocks(String description, Future<void> testMethod()) {
@@ -521,8 +544,12 @@
     FlutterProjectFactory flutterProjectFactory;
 
     setUp(() {
-      testbed = Testbed();
-      flutterProjectFactory = FlutterProjectFactory();
+      testbed = Testbed(setup: () {
+        flutterProjectFactory = FlutterProjectFactory(
+          fileSystem: globals.fs,
+          logger: globals.logger,
+        );
+      });
     });
 
     test('Handles asking for builders from an invalid pubspec', () => testbed.run(() {
@@ -646,7 +673,10 @@
   packagesFile.createSync(recursive: true);
   packagesFile.writeAsStringSync('flutter_template_images:${dummyTemplateImagesDirectory.uri}');
 
-  final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory();
+  final FlutterProjectFactory flutterProjectFactory = FlutterProjectFactory(
+    fileSystem: testFileSystem,
+    logger: globals.logger ?? BufferLogger.test(),
+  );
 
   testUsingContext(
     description,
diff --git a/packages/flutter_tools/test/src/android_common.dart b/packages/flutter_tools/test/src/android_common.dart
index 01eec17..cba857f 100644
--- a/packages/flutter_tools/test/src/android_common.dart
+++ b/packages/flutter_tools/test/src/android_common.dart
@@ -4,6 +4,7 @@
 
 import 'package:meta/meta.dart';
 
+import 'package:flutter_tools/src/globals.dart' as globals;
 import 'package:flutter_tools/src/android/android_builder.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/build_info.dart';
@@ -39,7 +40,11 @@
 /// within [directoryOverride].
 class FakeFlutterProjectFactory extends FlutterProjectFactory {
   FakeFlutterProjectFactory(this.directoryOverride) :
-    assert(directoryOverride != null);
+    assert(directoryOverride != null),
+    super(
+      fileSystem: globals.fs,
+      logger: globals.logger,
+    );
 
   final Directory directoryOverride;