// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:convert' show json; import 'package:file/memory.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 '../../src/common.dart'; void main() { group('Fingerprinter', () { late MemoryFileSystem fileSystem; setUp(() { fileSystem = MemoryFileSystem.test(); }); testWithoutContext('creates fingerprint with specified properties and files', () { fileSystem.file('a.dart').createSync(); final Fingerprinter fingerprinter = Fingerprinter( fingerprintPath: 'out.fingerprint', paths: <String>['a.dart'], fileSystem: fileSystem, logger: BufferLogger.test(), ); final Fingerprint fingerprint = fingerprinter.buildFingerprint(); expect(fingerprint, Fingerprint.fromBuildInputs(const <String>['a.dart'], fileSystem)); }); testWithoutContext('creates fingerprint with file checksums', () { fileSystem.file('a.dart').createSync(); final Fingerprinter fingerprinter = Fingerprinter( fingerprintPath: 'out.fingerprint', paths: <String>['a.dart'], fileSystem: fileSystem, logger: BufferLogger.test(), ); final Fingerprint fingerprint = fingerprinter.buildFingerprint(); expect(fingerprint, Fingerprint.fromBuildInputs(const <String>['a.dart'], fileSystem)); }); testWithoutContext('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'], fileSystem: fileSystem, logger: BufferLogger.test(), ); expect(fingerprinter.doesFingerprintMatch(), isFalse); }); 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'], fileSystem: fileSystem, logger: BufferLogger.test(), ); fingerprinter.writeFingerprint(); expect(fingerprinter.doesFingerprintMatch(), isTrue); }); testWithoutContext('fails to write fingerprint if inputs are missing', () { final Fingerprinter fingerprinter = Fingerprinter( fingerprintPath: 'out.fingerprint', paths: <String>['a.dart'], fileSystem: fileSystem, logger: BufferLogger.test(), ); fingerprinter.writeFingerprint(); expect(fileSystem.file('out.fingerprint').existsSync(), isFalse); }); group('Fingerprint', () { group('fromBuildInputs', () { late MemoryFileSystem fileSystem; setUp(() { fileSystem = MemoryFileSystem.test(); }); testWithoutContext('throws if any input file does not exist', () { fileSystem.file('a.dart').createSync(); expect( () => Fingerprint.fromBuildInputs(const <String>['a.dart', 'b.dart'], fileSystem), throwsException, ); }); 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>['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'); }); }); group('fromJson', () { testWithoutContext('throws if JSON is invalid', () { expect(() => Fingerprint.fromJson('<xml></xml>'), throwsA(anything)); }); testWithoutContext('creates fingerprint from valid JSON', () { final String jsonString = json.encode(<String, dynamic>{ 'files': <String, dynamic>{ 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }); final Fingerprint fingerprint = Fingerprint.fromJson(jsonString); final Map<String, dynamic>? content = castStringKeyedMap(json.decode(fingerprint.toJson())); expect(content, hasLength(1)); expect(content!['files'], hasLength(2)); expect(content['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698'); expect(content['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c'); }); 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 ==', () { testWithoutContext('reports not equal if file checksums do not match', () { final Map<String, dynamic> a = <String, dynamic>{ 'files': <String, dynamic>{ 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; final Map<String, dynamic> b = Map<String, dynamic>.of(a); b['files'] = <String, dynamic>{ 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07d', }; expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse); }); testWithoutContext('reports not equal if file paths do not match', () { final Map<String, dynamic> a = <String, dynamic>{ 'files': <String, dynamic>{ 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; final Map<String, dynamic> b = Map<String, dynamic>.of(a); b['files'] = <String, dynamic>{ 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'c.dart': '6f144e08b58cd0925328610fad7ac07d', }; expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(b)), isFalse); }); testWithoutContext('reports equal if properties and file checksums match', () { final Map<String, dynamic> a = <String, dynamic>{ 'files': <String, dynamic>{ 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; expect(Fingerprint.fromJson(json.encode(a)) == Fingerprint.fromJson(json.encode(a)), isTrue); }); }); group('hashCode', () { 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); }); }); }); }); }