Unverified Commit a42c5679 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[flutter_tools] shrink fingerprinter API to currently used subset (#63840)

parent 39c735f4
......@@ -6,59 +6,46 @@ import 'package:crypto/crypto.dart' show md5;
import 'package:meta/meta.dart';
import '../convert.dart' show json;
import '../globals.dart' as globals;
import 'file_system.dart';
import 'logger.dart';
import 'utils.dart';
typedef FingerprintPathFilter = bool Function(String path);
/// A tool that can be used to compute, compare, and write [Fingerprint]s for a
/// set of input files and associated build settings.
///
/// This class can be used during build actions to compute a fingerprint of the
/// build action inputs and options, and if unchanged from the previous build,
/// skip the build step. This assumes that build outputs are strictly a product
/// of the fingerprint inputs.
/// This class should only be used in situations where `assemble` is not appropriate,
/// such as checking if Cocoapods should be run.
class Fingerprinter {
Fingerprinter({
@required this.fingerprintPath,
@required Iterable<String> paths,
@required Map<String, String> properties,
Iterable<String> depfilePaths = const <String>[],
FingerprintPathFilter pathFilter,
@required FileSystem fileSystem,
@required Logger logger,
}) : _paths = paths.toList(),
_properties = Map<String, String>.of(properties),
_depfilePaths = depfilePaths.toList(),
_pathFilter = pathFilter,
assert(fingerprintPath != null),
assert(paths != null && paths.every((String path) => path != null)),
assert(properties != null),
assert(depfilePaths != null && depfilePaths.every((String path) => path != null));
_logger = logger,
_fileSystem = fileSystem;
final String fingerprintPath;
final List<String> _paths;
final Map<String, String> _properties;
final List<String> _depfilePaths;
final FingerprintPathFilter _pathFilter;
final Logger _logger;
final FileSystem _fileSystem;
Fingerprint buildFingerprint() {
final List<String> paths = _getPaths();
return Fingerprint.fromBuildInputs(_properties, paths);
return Fingerprint.fromBuildInputs(paths, _fileSystem);
}
bool doesFingerprintMatch() {
try {
final File fingerprintFile = globals.fs.file(fingerprintPath);
final File fingerprintFile = _fileSystem.file(fingerprintPath);
if (!fingerprintFile.existsSync()) {
return false;
}
if (!_depfilePaths.every(globals.fs.isFileSync)) {
return false;
}
final List<String> paths = _getPaths();
if (!paths.every(globals.fs.isFileSync)) {
if (!paths.every(_fileSystem.isFileSync)) {
return false;
}
......@@ -67,7 +54,7 @@ class Fingerprinter {
return oldFingerprint == newFingerprint;
} on Exception catch (e) {
// Log exception and continue, fingerprinting is only a performance improvement.
globals.printTrace('Fingerprint check error: $e');
_logger.printTrace('Fingerprint check error: $e');
}
return false;
}
......@@ -75,22 +62,14 @@ class Fingerprinter {
void writeFingerprint() {
try {
final Fingerprint fingerprint = buildFingerprint();
globals.fs.file(fingerprintPath).writeAsStringSync(fingerprint.toJson());
_fileSystem.file(fingerprintPath).writeAsStringSync(fingerprint.toJson());
} on Exception catch (e) {
// Log exception and continue, fingerprinting is only a performance improvement.
globals.printTrace('Fingerprint write error: $e');
_logger.printTrace('Fingerprint write error: $e');
}
}
List<String> _getPaths() {
final Set<String> paths = <String>{
..._paths,
for (final String depfilePath in _depfilePaths)
...readDepfile(depfilePath),
};
final FingerprintPathFilter filter = _pathFilter ?? (String path) => true;
return paths.where(filter).toList()..sort();
}
List<String> _getPaths() => _paths;
}
/// A fingerprint that uniquely identifies a set of build input files and
......@@ -101,23 +80,19 @@ class Fingerprinter {
class Fingerprint {
const Fingerprint._({
Map<String, String> checksums,
Map<String, String> properties,
}) : _checksums = checksums,
_properties = properties;
}) : _checksums = checksums;
factory Fingerprint.fromBuildInputs(Map<String, String> properties, Iterable<String> inputPaths) {
final Iterable<File> files = inputPaths.map<File>(globals.fs.file);
factory Fingerprint.fromBuildInputs(Iterable<String> inputPaths, FileSystem fileSystem) {
final Iterable<File> files = inputPaths.map<File>(fileSystem.file);
final Iterable<File> missingInputs = files.where((File file) => !file.existsSync());
if (missingInputs.isNotEmpty) {
throw Exception('Missing input files:\n' + missingInputs.join('\n'));
}
return Fingerprint._(
// ignore: prefer_const_literals_to_create_immutables, https://github.com/dart-lang/linter/issues/2025
checksums: <String, String>{
for (final File file in files)
file.path: md5.convert(file.readAsBytesSync()).toString(),
},
properties: <String, String>{...properties},
);
}
......@@ -127,37 +102,21 @@ class Fingerprint {
/// serializing framework and this framework.
factory Fingerprint.fromJson(String jsonData) {
final Map<String, dynamic> content = castStringKeyedMap(json.decode(jsonData));
final String version = content['version'] as String;
if (version != globals.flutterVersion.frameworkRevision) {
throw Exception('Incompatible fingerprint version: $version');
}
return Fingerprint._(
checksums: castStringKeyedMap(content['files'])?.cast<String,String>() ?? <String, String>{},
properties: castStringKeyedMap(content['properties'])?.cast<String,String>() ?? <String, String>{},
);
}
final Map<String, String> _checksums;
final Map<String, String> _properties;
String toJson() => json.encode(<String, dynamic>{
'version': globals.flutterVersion.frameworkRevision,
'properties': _properties,
'files': _checksums,
});
@override
bool operator==(Object other) {
if (identical(other, this)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is Fingerprint
&& _equalMaps(other._checksums, _checksums)
&& _equalMaps(other._properties, _properties);
&& _equalMaps(other._checksums, _checksums);
}
bool _equalMaps(Map<String, String> a, Map<String, String> b) {
......@@ -169,37 +128,8 @@ class Fingerprint {
// Ignore map entries here to avoid becoming inconsistent with equals
// due to differences in map entry order. This is a really bad hash
// function and should eventually be deprecated and removed.
int get hashCode => _properties.length + _checksums.length;
int get hashCode => _checksums.length.hashCode;
@override
String toString() => '{checksums: $_checksums, properties: $_properties}';
}
final RegExp _separatorExpr = RegExp(r'([^\\]) ');
final RegExp _escapeExpr = RegExp(r'\\(.)');
/// Parses a VM snapshot dependency file.
///
/// Snapshot dependency files are a single line mapping the output snapshot to a
/// space-separated list of input files used to generate that output. Spaces and
/// backslashes are escaped with a backslash. For example:
///
/// outfile : file1.dart fil\\e2.dart fil\ e3.dart
///
/// will return a set containing: 'file1.dart', 'fil\e2.dart', 'fil e3.dart'.
Set<String> readDepfile(String depfilePath) {
// Depfile format:
// outfile1 outfile2 : file1.dart file2.dart file3.dart
final String contents = globals.fs.file(depfilePath).readAsStringSync();
final List<String> dependencies = contents.split(': ');
if (dependencies.length < 2) {
throw Exception('malformed depfile');
}
return dependencies[1]
.replaceAllMapped(_separatorExpr, (Match match) => '${match.group(1)}\n')
.split('\n')
.map<String>((String path) => path.replaceAllMapped(_escapeExpr, (Match match) => match.group(1)).trim())
.where((String path) => path.isNotEmpty)
.toSet();
String toString() => '{checksums: $_checksums}';
}
......@@ -13,7 +13,11 @@ import '../project.dart';
/// For a given build, determines whether dependencies have changed since the
/// last call to processPods, then calls processPods with that information.
Future<void> processPodsIfNeeded(XcodeBasedProject xcodeProject, String buildDirectory, BuildMode buildMode) async {
Future<void> processPodsIfNeeded(
XcodeBasedProject xcodeProject,
String buildDirectory,
BuildMode buildMode,
) async {
final FlutterProject project = xcodeProject.parent;
// Ensure that the plugin list is up to date, since hasPlugins relies on it.
await refreshPluginsList(project);
......@@ -29,7 +33,8 @@ Future<void> processPodsIfNeeded(XcodeBasedProject xcodeProject, String buildDir
xcodeProject.podfile.path,
xcodeProject.generatedXcodePropertiesFile.path,
],
properties: <String, String>{},
fileSystem: globals.fs,
logger: globals.logger,
);
final bool didPodInstall = await globals.cocoaPods.processPods(
......
......@@ -5,237 +5,83 @@
import 'dart:convert' show json;
import 'package:file/memory.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/fingerprint.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/version.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
import '../../src/context.dart';
void main() {
group('Fingerprinter', () {
const String kVersion = '123456abcdef';
MemoryFileSystem fileSystem;
MockFlutterVersion mockVersion;
setUp(() {
fileSystem = MemoryFileSystem.test();
mockVersion = MockFlutterVersion();
when(mockVersion.frameworkRevision).thenReturn(kVersion);
});
final Map<Type, Generator> contextOverrides = <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
};
testUsingContext('throws when depfile is malformed', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('b.dart').createSync();
fileSystem.file('depfile').createSync();
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart'],
depfilePaths: <String>['depfile'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
);
expect(fingerprinter.buildFingerprint, throwsA(anything));
}, overrides: contextOverrides);
testUsingContext('creates fingerprint with specified properties and files', () {
testWithoutContext('creates fingerprint with specified properties and files', () {
fileSystem.file('a.dart').createSync();
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart'],
properties: <String, String>{
'foo': 'bar',
'wibble': 'wobble',
},
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
final Fingerprint fingerprint = fingerprinter.buildFingerprint();
expect(fingerprint, Fingerprint.fromBuildInputs(const <String, String>{
'foo': 'bar',
'wibble': 'wobble',
}, const <String>['a.dart']));
}, overrides: contextOverrides);
expect(fingerprint, Fingerprint.fromBuildInputs(const <String>['a.dart'], fileSystem));
});
testUsingContext('creates fingerprint with file checksums', () {
testWithoutContext('creates fingerprint with file checksums', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('b.dart').createSync();
fileSystem.file('depfile').writeAsStringSync('depfile : b.dart');
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart'],
depfilePaths: <String>['depfile'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
final Fingerprint fingerprint = fingerprinter.buildFingerprint();
expect(fingerprint, Fingerprint.fromBuildInputs(const <String, String>{
'bar': 'baz',
'wobble': 'womble',
}, const <String>['a.dart', 'b.dart']));
}, overrides: contextOverrides);
testUsingContext('fingerprint does not match if not present', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('b.dart').createSync();
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart', 'b.dart'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
);
expect(fingerprinter.doesFingerprintMatch(), isFalse);
}, overrides: contextOverrides);
testUsingContext('fingerprint does match if different', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('b.dart').createSync();
final Fingerprinter fingerprinter1 = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart', 'b.dart'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
);
fingerprinter1.writeFingerprint();
final Fingerprinter fingerprinter2 = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart', 'b.dart'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'elbmow',
},
);
expect(fingerprinter2.doesFingerprintMatch(), isFalse);
}, overrides: contextOverrides);
testUsingContext('fingerprint does not match if depfile is malformed', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('b.dart').createSync();
fileSystem.file('depfile').writeAsStringSync('depfile : b.dart');
// Write a valid fingerprint
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart', 'b.dart'],
depfilePaths: <String>['depfile'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
);
fingerprinter.writeFingerprint();
// Write a corrupt depfile.
fileSystem.file('depfile').writeAsStringSync('');
final Fingerprinter badFingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart', 'b.dart'],
depfilePaths: <String>['depfile'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
);
expect(badFingerprinter.doesFingerprintMatch(), isFalse);
}, overrides: contextOverrides);
expect(fingerprint, Fingerprint.fromBuildInputs(const <String>['a.dart'], fileSystem));
});
testUsingContext('fingerprint does not match if previous fingerprint is malformed', () {
testWithoutContext('fingerprint does not match if not present', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('b.dart').createSync();
fileSystem.file('out.fingerprint').writeAsStringSync('** not JSON **');
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart', 'b.dart'],
depfilePaths: <String>['depfile'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
expect(fingerprinter.doesFingerprintMatch(), isFalse);
}, overrides: contextOverrides);
testUsingContext('fingerprint does match if identical', () {
});
testWithoutContext('fingerprint does match if identical', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('b.dart').createSync();
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart', 'b.dart'],
properties: <String, String>{
'bar': 'baz',
'wobble': 'womble',
},
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
fingerprinter.writeFingerprint();
expect(fingerprinter.doesFingerprintMatch(), isTrue);
}, overrides: contextOverrides);
});
testUsingContext('fails to write fingerprint if inputs are missing', () {
testWithoutContext('fails to write fingerprint if inputs are missing', () {
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart'],
properties: <String, String>{
'foo': 'bar',
'wibble': 'wobble',
},
fileSystem: fileSystem,
logger: BufferLogger.test(),
);
fingerprinter.writeFingerprint();
expect(fileSystem.file('out.fingerprint').existsSync(), isFalse);
}, overrides: contextOverrides);
testUsingContext('applies path filter to inputs paths', () {
fileSystem.file('a.dart').createSync();
fileSystem.file('ab.dart').createSync();
fileSystem.file('depfile').writeAsStringSync('depfile : ab.dart c.dart');
final Fingerprinter fingerprinter = Fingerprinter(
fingerprintPath: 'out.fingerprint',
paths: <String>['a.dart'],
depfilePaths: <String>['depfile'],
properties: <String, String>{
'foo': 'bar',
'wibble': 'wobble',
},
pathFilter: (String path) => path.startsWith('a'),
);
fingerprinter.writeFingerprint();
expect(fileSystem.file('out.fingerprint').existsSync(), isTrue);
}, overrides: contextOverrides);
});
group('Fingerprint', () {
MockFlutterVersion mockVersion;
const String kVersion = '123456abcdef';
setUp(() {
mockVersion = MockFlutterVersion();
when(mockVersion.frameworkRevision).thenReturn(kVersion);
});
group('Fingerprint', () {
group('fromBuildInputs', () {
MemoryFileSystem fileSystem;
......@@ -243,63 +89,33 @@ void main() {
fileSystem = MemoryFileSystem.test();
});
testUsingContext('throws if any input file does not exist', () {
testWithoutContext('throws if any input file does not exist', () {
fileSystem.file('a.dart').createSync();
expect(
() => Fingerprint.fromBuildInputs(const <String, String>{}, const <String>['a.dart', 'b.dart']),
() => Fingerprint.fromBuildInputs(const <String>['a.dart', 'b.dart'], fileSystem),
throwsException,
);
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('populates checksums for valid files', () {
testWithoutContext('populates checksums for valid files', () {
fileSystem.file('a.dart').writeAsStringSync('This is a');
fileSystem.file('b.dart').writeAsStringSync('This is b');
final Fingerprint fingerprint = Fingerprint.fromBuildInputs(const <String, String>{}, const <String>['a.dart', 'b.dart']);
final Fingerprint fingerprint = Fingerprint.fromBuildInputs(const <String>['a.dart', 'b.dart'], fileSystem);
final Map<String, dynamic> jsonObject = castStringKeyedMap(json.decode(fingerprint.toJson()));
expect(jsonObject['files'], hasLength(2));
expect(jsonObject['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
expect(jsonObject['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
}, overrides: <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
});
testUsingContext('includes framework version', () {
final Fingerprint fingerprint = Fingerprint.fromBuildInputs(const <String, String>{}, const <String>[]);
final Map<String, dynamic> jsonObject = castStringKeyedMap(json.decode(fingerprint.toJson()));
expect(jsonObject['version'], mockVersion.frameworkRevision);
}, overrides: <Type, Generator>{FlutterVersion: () => mockVersion});
testUsingContext('includes provided properties', () {
final Fingerprint fingerprint = Fingerprint.fromBuildInputs(const <String, String>{'a': 'A', 'b': 'B'}, const <String>[]);
final Map<String, dynamic> jsonObject = castStringKeyedMap(json.decode(fingerprint.toJson()));
expect(jsonObject['properties'], hasLength(2));
expect(jsonObject['properties']['a'], 'A');
expect(jsonObject['properties']['b'], 'B');
}, overrides: <Type, Generator>{FlutterVersion: () => mockVersion});
});
group('fromJson', () {
testUsingContext('throws if JSON is invalid', () {
testWithoutContext('throws if JSON is invalid', () {
expect(() => Fingerprint.fromJson('<xml></xml>'), throwsA(anything));
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('creates fingerprint from valid JSON', () {
testWithoutContext('creates fingerprint from valid JSON', () {
final String jsonString = json.encode(<String, dynamic>{
'version': kVersion,
'properties': <String, String>{
'buildMode': BuildMode.release.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entryPoint': 'a.dart',
},
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -307,72 +123,20 @@ void main() {
});
final Fingerprint fingerprint = Fingerprint.fromJson(jsonString);
final Map<String, dynamic> content = castStringKeyedMap(json.decode(fingerprint.toJson()));
expect(content, hasLength(3));
expect(content['version'], mockVersion.frameworkRevision);
expect(content['properties'], hasLength(3));
expect(content['properties']['buildMode'], BuildMode.release.toString());
expect(content['properties']['targetPlatform'], TargetPlatform.ios.toString());
expect(content['properties']['entryPoint'], 'a.dart');
expect(content, hasLength(1));
expect(content['files'], hasLength(2));
expect(content['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698');
expect(content['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c');
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('throws ArgumentError for unknown versions', () {
final String jsonString = json.encode(<String, dynamic>{
'version': 'bad',
'properties': <String, String>{},
'files': <String, String>{},
});
expect(() => Fingerprint.fromJson(jsonString), throwsException);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('throws ArgumentError if version is not present', () {
final String jsonString = json.encode(<String, dynamic>{
'properties': <String, String>{},
'files': <String, String>{},
});
expect(() => Fingerprint.fromJson(jsonString), throwsException);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('treats missing properties and files entries as if empty', () {
final String jsonString = json.encode(<String, dynamic>{
'version': kVersion,
});
expect(Fingerprint.fromJson(jsonString), Fingerprint.fromBuildInputs(const <String, String>{}, const <String>[]));
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
testWithoutContext('treats missing properties and files entries as if empty', () {
final String jsonString = json.encode(<String, dynamic>{});
expect(Fingerprint.fromJson(jsonString), Fingerprint.fromBuildInputs(const <String>[], fileSystem));
});
});
group('operator ==', () {
testUsingContext('reports not equal if properties do not match', () {
testWithoutContext('reports not equal if file checksums do not match', () {
final Map<String, dynamic> a = <String, dynamic>{
'version': kVersion,
'properties': <String, String>{
'buildMode': BuildMode.debug.toString(),
},
'files': <String, dynamic>{},
};
final Map<String, dynamic> b = Map<String, dynamic>.of(a);
b['properties'] = <String, String>{
'buildMode': BuildMode.release.toString(),
};
expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('reports not equal if file checksums do not match', () {
final Map<String, dynamic> a = <String, dynamic>{
'version': kVersion,
'properties': <String, String>{},
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -384,14 +148,10 @@ void main() {
'b.dart': '6f144e08b58cd0925328610fad7ac07d',
};
expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('reports not equal if file paths do not match', () {
testWithoutContext('reports not equal if file paths do not match', () {
final Map<String, dynamic> a = <String, dynamic>{
'version': kVersion,
'properties': <String, String>{},
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
......@@ -403,82 +163,26 @@ void main() {
'c.dart': '6f144e08b58cd0925328610fad7ac07d',
};
expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
testUsingContext('reports equal if properties and file checksums match', () {
testWithoutContext('reports equal if properties and file checksums match', () {
final Map<String, dynamic> a = <String, dynamic>{
'version': kVersion,
'properties': <String, String>{
'buildMode': BuildMode.debug.toString(),
'targetPlatform': TargetPlatform.ios.toString(),
'entryPoint': 'a.dart',
},
'files': <String, dynamic>{
'a.dart': '8a21a15fad560b799f6731d436c1b698',
'b.dart': '6f144e08b58cd0925328610fad7ac07c',
},
};
expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(a)), isTrue);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
});
group('hashCode', () {
testUsingContext('is consistent with equals, even if map entries are reordered', () {
final Fingerprint a = Fingerprint.fromJson('{"version":"$kVersion","properties":{"a":"A","b":"B"},"files":{}}');
final Fingerprint b = Fingerprint.fromJson('{"version":"$kVersion","properties":{"b":"B","a":"A"},"files":{}}');
testWithoutContext('is consistent with equals, even if map entries are reordered', () {
final Fingerprint a = Fingerprint.fromJson('{"properties":{"a":"A","b":"B"},"files":{}}');
final Fingerprint b = Fingerprint.fromJson('{"properties":{"b":"B","a":"A"},"files":{}}');
expect(a, b);
expect(a.hashCode, b.hashCode);
}, overrides: <Type, Generator>{
FlutterVersion: () => mockVersion,
});
});
});
group('readDepfile', () {
MemoryFileSystem fileSystem;
setUp(() {
fileSystem = MemoryFileSystem.test();
});
final Map<Type, Generator> contextOverrides = <Type, Generator>{
FileSystem: () => fileSystem,
ProcessManager: () => FakeProcessManager.any(),
};
testUsingContext('returns one file if only one is listed', () {
fileSystem.file('a.d').writeAsStringSync('snapshot.d: /foo/a.dart');
expect(readDepfile('a.d'), unorderedEquals(<String>['/foo/a.dart']));
}, overrides: contextOverrides);
testUsingContext('returns multiple files', () {
fileSystem.file('a.d').writeAsStringSync('snapshot.d: /foo/a.dart /foo/b.dart');
expect(readDepfile('a.d'), unorderedEquals(<String>[
'/foo/a.dart',
'/foo/b.dart',
]));
}, overrides: contextOverrides);
testUsingContext('trims extra spaces between files', () {
fileSystem.file('a.d').writeAsStringSync('snapshot.d: /foo/a.dart /foo/b.dart /foo/c.dart');
expect(readDepfile('a.d'), unorderedEquals(<String>[
'/foo/a.dart',
'/foo/b.dart',
'/foo/c.dart',
]));
}, overrides: contextOverrides);
testUsingContext('returns files with spaces and backslashes', () {
fileSystem.file('a.d').writeAsStringSync(r'snapshot.d: /foo/a\ a.dart /foo/b\\b.dart /foo/c\\ c.dart');
expect(readDepfile('a.d'), unorderedEquals(<String>[
r'/foo/a a.dart',
r'/foo/b\b.dart',
r'/foo/c\ c.dart',
]));
}, overrides: contextOverrides);
});
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment