// Copyright 2018 The Chromium 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/build_info.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/fingerprint.dart'; import 'package:flutter_tools/src/version.dart'; import 'package:mockito/mockito.dart'; import 'package:test/test.dart'; import '../src/context.dart'; void main() { group('Fingerprinter', () { const String kVersion = '123456abcdef'; MemoryFileSystem fs; MockFlutterVersion mockVersion; setUp(() { fs = new MemoryFileSystem(); mockVersion = new MockFlutterVersion(); when(mockVersion.frameworkRevision).thenReturn(kVersion); }); final Map contextOverrides = { FileSystem: () => fs, }; testUsingContext('creates fingerprint with specified properties and files', () async { await fs.file('a.dart').create(); final Fingerprinter fingerprinter = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart'], properties: { 'foo': 'bar', 'wibble': 'wobble', }, ); final Fingerprint fingerprint = await fingerprinter.buildFingerprint(); expect(fingerprint, new Fingerprint.fromBuildInputs({ 'foo': 'bar', 'wibble': 'wobble', }, ['a.dart'])); }, overrides: contextOverrides); testUsingContext('creates fingerprint with file checksums', () async { await fs.file('a.dart').create(); await fs.file('b.dart').create(); await fs.file('depfile').writeAsString('depfile : b.dart'); final Fingerprinter fingerprinter = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart'], depfilePaths: ['depfile'], properties: { 'bar': 'baz', 'wobble': 'womble', }, ); final Fingerprint fingerprint = await fingerprinter.buildFingerprint(); expect(fingerprint, new Fingerprint.fromBuildInputs({ 'bar': 'baz', 'wobble': 'womble', }, ['a.dart', 'b.dart'])); }, overrides: contextOverrides); testUsingContext('fingerprint does not match if not present', () async { await fs.file('a.dart').create(); await fs.file('b.dart').create(); final Fingerprinter fingerprinter = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart', 'b.dart'], properties: { 'bar': 'baz', 'wobble': 'womble', }, ); expect(await fingerprinter.doesFingerprintMatch(), isFalse); }, overrides: contextOverrides); testUsingContext('fingerprint does match if different', () async { await fs.file('a.dart').create(); await fs.file('b.dart').create(); final Fingerprinter fingerprinter1 = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart', 'b.dart'], properties: { 'bar': 'baz', 'wobble': 'womble', }, ); await fingerprinter1.writeFingerprint(); final Fingerprinter fingerprinter2 = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart', 'b.dart'], properties: { 'bar': 'baz', 'wobble': 'elbmow', }, ); expect(await fingerprinter2.doesFingerprintMatch(), isFalse); }, overrides: contextOverrides); testUsingContext('fingerprint does match if identical', () async { await fs.file('a.dart').create(); await fs.file('b.dart').create(); final Fingerprinter fingerprinter = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart', 'b.dart'], properties: { 'bar': 'baz', 'wobble': 'womble', }, ); await fingerprinter.writeFingerprint(); expect(await fingerprinter.doesFingerprintMatch(), isTrue); }, overrides: contextOverrides); testUsingContext('fails to write fingerprint if inputs are missing', () async { final Fingerprinter fingerprinter = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart'], properties: { 'foo': 'bar', 'wibble': 'wobble', }, ); await fingerprinter.writeFingerprint(); expect(fs.file('out.fingerprint').existsSync(), isFalse); }, overrides: contextOverrides); testUsingContext('applies path filter to inputs paths', () async { await fs.file('a.dart').create(); await fs.file('ab.dart').create(); await fs.file('depfile').writeAsString('depfile : ab.dart c.dart'); final Fingerprinter fingerprinter = new Fingerprinter( fingerprintPath: 'out.fingerprint', paths: ['a.dart'], depfilePaths: ['depfile'], properties: { 'foo': 'bar', 'wibble': 'wobble', }, pathFilter: (String path) => path.startsWith('a'), ); await fingerprinter.writeFingerprint(); expect(fs.file('out.fingerprint').existsSync(), isTrue); }, overrides: contextOverrides); }); group('Fingerprint', () { MockFlutterVersion mockVersion; const String kVersion = '123456abcdef'; setUp(() { mockVersion = new MockFlutterVersion(); when(mockVersion.frameworkRevision).thenReturn(kVersion); }); group('fromBuildInputs', () { MemoryFileSystem fs; setUp(() { fs = new MemoryFileSystem(); }); testUsingContext('throws if any input file does not exist', () async { await fs.file('a.dart').create(); expect( () => new Fingerprint.fromBuildInputs({}, ['a.dart', 'b.dart']), throwsArgumentError, ); }, overrides: { FileSystem: () => fs }); testUsingContext('populates checksums for valid files', () async { await fs.file('a.dart').writeAsString('This is a'); await fs.file('b.dart').writeAsString('This is b'); final Fingerprint fingerprint = new Fingerprint.fromBuildInputs({}, ['a.dart', 'b.dart']); final Map jsonObject = json.decode(fingerprint.toJson()); expect(jsonObject['files'], hasLength(2)); expect(jsonObject['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698'); expect(jsonObject['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c'); }, overrides: { FileSystem: () => fs }); testUsingContext('includes framework version', () { final Fingerprint fingerprint = new Fingerprint.fromBuildInputs({}, []); final Map jsonObject = json.decode(fingerprint.toJson()); expect(jsonObject['version'], mockVersion.frameworkRevision); }, overrides: { FlutterVersion: () => mockVersion }); testUsingContext('includes provided properties', () { final Fingerprint fingerprint = new Fingerprint.fromBuildInputs({'a': 'A', 'b': 'B'}, []); final Map jsonObject = json.decode(fingerprint.toJson()); expect(jsonObject['properties'], hasLength(2)); expect(jsonObject['properties']['a'], 'A'); expect(jsonObject['properties']['b'], 'B'); }, overrides: { FlutterVersion: () => mockVersion }); }); group('fromJson', () { testUsingContext('throws if JSON is invalid', () async { expect(() => new Fingerprint.fromJson(''), throwsA(anything)); }, overrides: { FlutterVersion: () => mockVersion, }); testUsingContext('creates fingerprint from valid JSON', () async { final String jsonString = json.encode({ 'version': kVersion, 'properties': { 'buildMode': BuildMode.release.toString(), 'targetPlatform': TargetPlatform.ios.toString(), 'entryPoint': 'a.dart', }, 'files': { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }); final Fingerprint fingerprint = new Fingerprint.fromJson(jsonString); final Map content = 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['files'], hasLength(2)); expect(content['files']['a.dart'], '8a21a15fad560b799f6731d436c1b698'); expect(content['files']['b.dart'], '6f144e08b58cd0925328610fad7ac07c'); }, overrides: { FlutterVersion: () => mockVersion, }); testUsingContext('throws ArgumentError for unknown versions', () async { final String jsonString = json.encode({ 'version': 'bad', 'properties':{}, 'files':{}, }); expect(() => new Fingerprint.fromJson(jsonString), throwsArgumentError); }, overrides: { FlutterVersion: () => mockVersion, }); testUsingContext('throws ArgumentError if version is not present', () async { final String jsonString = json.encode({ 'properties':{}, 'files':{}, }); expect(() => new Fingerprint.fromJson(jsonString), throwsArgumentError); }, overrides: { FlutterVersion: () => mockVersion, }); testUsingContext('treats missing properties and files entries as if empty', () async { final String jsonString = json.encode({ 'version': kVersion, }); expect(new Fingerprint.fromJson(jsonString), new Fingerprint.fromBuildInputs({}, [])); }, overrides: { FlutterVersion: () => mockVersion, }); }); group('operator ==', () { testUsingContext('reports not equal if properties do not match', () async { final Map a = { 'version': kVersion, 'properties': { 'buildMode': BuildMode.debug.toString(), }, 'files': {}, }; final Map b = new Map.from(a); b['properties'] = { 'buildMode': BuildMode.release.toString(), }; expect(new Fingerprint.fromJson(json.encode(a)) == new Fingerprint.fromJson(json.encode(b)), isFalse); }, overrides: { FlutterVersion: () => mockVersion, }); testUsingContext('reports not equal if file checksums do not match', () async { final Map a = { 'version': kVersion, 'properties': {}, 'files': { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; final Map b = new Map.from(a); b['files'] = { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07d', }; expect(new Fingerprint.fromJson(json.encode(a)) == new Fingerprint.fromJson(json.encode(b)), isFalse); }, overrides: { FlutterVersion: () => mockVersion, }); testUsingContext('reports not equal if file paths do not match', () async { final Map a = { 'version': kVersion, 'properties': {}, 'files': { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; final Map b = new Map.from(a); b['files'] = { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'c.dart': '6f144e08b58cd0925328610fad7ac07d', }; expect(new Fingerprint.fromJson(json.encode(a)) == new Fingerprint.fromJson(json.encode(b)), isFalse); }, overrides: { FlutterVersion: () => mockVersion, }); testUsingContext('reports equal if properties and file checksums match', () async { final Map a = { 'version': kVersion, 'properties': { 'buildMode': BuildMode.debug.toString(), 'targetPlatform': TargetPlatform.ios.toString(), 'entryPoint': 'a.dart', }, 'files': { 'a.dart': '8a21a15fad560b799f6731d436c1b698', 'b.dart': '6f144e08b58cd0925328610fad7ac07c', }, }; expect(new Fingerprint.fromJson(json.encode(a)) == new Fingerprint.fromJson(json.encode(a)), isTrue); }, overrides: { FlutterVersion: () => mockVersion, }); }); group('hashCode', () { testUsingContext('is consistent with equals, even if map entries are reordered', () async { final Fingerprint a = new Fingerprint.fromJson('{"version":"$kVersion","properties":{"a":"A","b":"B"},"files":{}}'); final Fingerprint b = new Fingerprint.fromJson('{"version":"$kVersion","properties":{"b":"B","a":"A"},"files":{}}'); expect(a, b); expect(a.hashCode, b.hashCode); }, overrides: { FlutterVersion: () => mockVersion, }); }); }); group('readDepfile', () { MemoryFileSystem fs; setUp(() { fs = new MemoryFileSystem(); }); final Map contextOverrides = { FileSystem: () => fs }; testUsingContext('returns one file if only one is listed', () async { await fs.file('a.d').writeAsString('snapshot.d: /foo/a.dart'); expect(await readDepfile('a.d'), unorderedEquals(['/foo/a.dart'])); }, overrides: contextOverrides); testUsingContext('returns multiple files', () async { await fs.file('a.d').writeAsString('snapshot.d: /foo/a.dart /foo/b.dart'); expect(await readDepfile('a.d'), unorderedEquals([ '/foo/a.dart', '/foo/b.dart', ])); }, overrides: contextOverrides); testUsingContext('trims extra spaces between files', () async { await fs.file('a.d').writeAsString('snapshot.d: /foo/a.dart /foo/b.dart /foo/c.dart'); expect(await readDepfile('a.d'), unorderedEquals([ '/foo/a.dart', '/foo/b.dart', '/foo/c.dart', ])); }, overrides: contextOverrides); testUsingContext('returns files with spaces and backslashes', () async { await fs.file('a.d').writeAsString(r'snapshot.d: /foo/a\ a.dart /foo/b\\b.dart /foo/c\\ c.dart'); expect(await readDepfile('a.d'), unorderedEquals([ r'/foo/a a.dart', r'/foo/b\b.dart', r'/foo/c\ c.dart', ])); }, overrides: contextOverrides); }); }