| // 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:async'; |
| import 'dart:typed_data'; |
| |
| import 'package:file/memory.dart'; |
| import 'package:flutter_tools/src/base/file_system.dart'; |
| import 'package:flutter_tools/src/convert.dart'; |
| import 'package:flutter_tools/src/proxied_devices/file_transfer.dart'; |
| |
| import '../../src/common.dart'; |
| |
| void main() { |
| group('convertToChunks', () { |
| test('works correctly', () async { |
| final StreamController<Uint8List> controller = StreamController<Uint8List>(); |
| final Stream<Uint8List> chunked = convertToChunks(controller.stream, 4); |
| final Future<List<Uint8List>> chunkedListFuture = chunked.toList(); |
| |
| // Full chunk. |
| controller.add(Uint8List.fromList(<int>[1, 2, 3, 4])); |
| // Multiple of full chunks, on chunk bounraries. |
| controller.add(Uint8List.fromList(<int>[5, 6, 7, 8, 9, 10, 11, 12])); |
| // Larger than one chunk, starts on chunk boundary, ends not on chunk boundary. |
| controller.add(Uint8List.fromList(<int>[13, 14, 15, 16, 17, 18])); |
| // Larger than one chunk, starts not on chunk boundary, ends not on chunk boundary. |
| controller.add(Uint8List.fromList(<int>[19, 20, 21, 22, 23])); |
| // Larger than one chunk, starts not on chunk boundary, ends on chunk boundary. |
| controller.add(Uint8List.fromList(<int>[24, 25, 26, 27, 28])); |
| // Smaller than one chunk, starts on chunk boundary, ends not on chunk boundary. |
| controller.add(Uint8List.fromList(<int>[29, 30])); |
| // Smaller than one chunk, starts not on chunk boundary, ends not on chunk boundary. |
| controller.add(Uint8List.fromList(<int>[31, 32, 33])); |
| // Full chunk, not on chunk boundary. |
| controller.add(Uint8List.fromList(<int>[34, 35, 36, 37])); |
| // Smaller than one chunk, starts not on chunk boundary, ends on chunk boundary. |
| controller.add(Uint8List.fromList(<int>[38, 39, 40])); |
| // Empty chunk. |
| controller.add(Uint8List.fromList(<int>[])); |
| // Extra chunk. |
| controller.add(Uint8List.fromList(<int>[41, 42])); |
| |
| await controller.close(); |
| |
| final List<Uint8List> chunkedList = await chunkedListFuture; |
| |
| expect(chunkedList, hasLength(11)); |
| expect(chunkedList[0], <int>[1, 2, 3, 4]); |
| expect(chunkedList[1], <int>[5, 6, 7, 8]); |
| expect(chunkedList[2], <int>[9, 10, 11, 12]); |
| expect(chunkedList[3], <int>[13, 14, 15, 16]); |
| expect(chunkedList[4], <int>[17, 18, 19, 20]); |
| expect(chunkedList[5], <int>[21, 22, 23, 24]); |
| expect(chunkedList[6], <int>[25, 26, 27, 28]); |
| expect(chunkedList[7], <int>[29, 30, 31, 32]); |
| expect(chunkedList[8], <int>[33, 34, 35, 36]); |
| expect(chunkedList[9], <int>[37, 38, 39, 40]); |
| expect(chunkedList[10], <int>[41, 42]); |
| }); |
| }); |
| |
| group('adler32Hash', () { |
| test('works correctly', () { |
| final int hash = adler32Hash(utf8.encode('abcdefg')); |
| expect(hash, 0x0adb02bd); |
| }); |
| }); |
| |
| group('RollingAdler32', () { |
| test('works correctly without rolling', () { |
| final RollingAdler32 adler32 = RollingAdler32(7); |
| utf8.encode('abcdefg').forEach(adler32.push); |
| expect(adler32.hash, adler32Hash(utf8.encode('abcdefg'))); |
| }); |
| |
| test('works correctly after rolling once', () { |
| final RollingAdler32 adler32 = RollingAdler32(7); |
| utf8.encode('12abcdefg').forEach(adler32.push); |
| expect(adler32.hash, adler32Hash(utf8.encode('abcdefg'))); |
| }); |
| |
| test('works correctly after rolling multiple cycles', () { |
| final RollingAdler32 adler32 = RollingAdler32(7); |
| utf8.encode('1234567890123456789abcdefg').forEach(adler32.push); |
| expect(adler32.hash, adler32Hash(utf8.encode('abcdefg'))); |
| }); |
| |
| test('works correctly after reset', () { |
| final RollingAdler32 adler32 = RollingAdler32(7); |
| utf8.encode('1234567890123456789abcdefg').forEach(adler32.push); |
| adler32.reset(); |
| utf8.encode('abcdefg').forEach(adler32.push); |
| expect(adler32.hash, adler32Hash(utf8.encode('abcdefg'))); |
| }); |
| |
| test('currentBlock returns the correct entry when read less than one block', () { |
| final RollingAdler32 adler32 = RollingAdler32(7); |
| utf8.encode('abcd').forEach(adler32.push); |
| expect(adler32.currentBlock(), utf8.encode('abcd')); |
| }); |
| |
| test('currentBlock returns the correct entry when read exactly one block', () { |
| final RollingAdler32 adler32 = RollingAdler32(7); |
| utf8.encode('abcdefg').forEach(adler32.push); |
| expect(adler32.currentBlock(), utf8.encode('abcdefg')); |
| }); |
| |
| test('currentBlock returns the correct entry when read more than one block', () { |
| final RollingAdler32 adler32 = RollingAdler32(7); |
| utf8.encode('123456789abcdefg').forEach(adler32.push); |
| expect(adler32.currentBlock(), utf8.encode('abcdefg')); |
| }); |
| }); |
| |
| group('FileTransfer', () { |
| const String content1 = 'a...b...c...d...e.'; |
| const String content2 = 'b...c...d...a...f...g...b...h..'; |
| const List<FileDeltaBlock> expectedDelta = <FileDeltaBlock>[ |
| FileDeltaBlock.fromDestination(start: 4, size: 12), |
| FileDeltaBlock.fromDestination(start: 0, size: 4), |
| FileDeltaBlock.fromSource(start: 16, size: 8), |
| FileDeltaBlock.fromDestination(start: 4, size: 4), |
| FileDeltaBlock.fromSource(start: 28, size: 3), |
| ]; |
| const String expectedBinaryForRebuilding = 'f...g...h..'; |
| late MemoryFileSystem fileSystem; |
| setUp(() { |
| fileSystem = MemoryFileSystem(); |
| }); |
| |
| test('calculateBlockHashesOfFile works normally', () async { |
| final File file = fileSystem.file('test')..writeAsStringSync(content1); |
| |
| final BlockHashes hashes = await FileTransfer().calculateBlockHashesOfFile(file, blockSize: 4); |
| expect(hashes.blockSize, 4); |
| expect(hashes.totalSize, content1.length); |
| expect(hashes.adler32, hasLength(5)); |
| expect(hashes.adler32, <int>[ |
| 0x029c00ec, |
| 0x02a000ed, |
| 0x02a400ee, |
| 0x02a800ef, |
| 0x00fa0094, |
| ]); |
| expect(hashes.md5, hasLength(5)); |
| expect(hashes.md5, <String>[ |
| 'zB0S8R/fGt05GcI5v8AjIQ==', |
| 'uZCZ4i/LUGFYAD+K1ZD0Wg==', |
| '6kbZGS8T1NJl/naWODQcNw==', |
| 'kKh/aA2XAhR/r0HdZa3Bxg==', |
| '34eF7Bs/OhfoJ5+sAw0zyw==', |
| ]); |
| expect(hashes.fileMd5, 'VT/gkSEdctzUEUJCxclxuQ=='); |
| }); |
| |
| test('computeDelta returns empty list if file is identical', () async { |
| final File file1 = fileSystem.file('file1')..writeAsStringSync(content1); |
| final File file2 = fileSystem.file('file1')..writeAsStringSync(content1); |
| |
| final BlockHashes hashes = await FileTransfer().calculateBlockHashesOfFile(file1, blockSize: 4); |
| final List<FileDeltaBlock> delta = await FileTransfer().computeDelta(file2, hashes); |
| |
| expect(delta, isEmpty); |
| }); |
| |
| test('computeDelta returns the correct delta', () async { |
| final File file1 = fileSystem.file('file1')..writeAsStringSync(content1); |
| final File file2 = fileSystem.file('file2')..writeAsStringSync(content2); |
| |
| final BlockHashes hashes = await FileTransfer().calculateBlockHashesOfFile(file1, blockSize: 4); |
| final List<FileDeltaBlock> delta = await FileTransfer().computeDelta(file2, hashes); |
| |
| expect(delta, expectedDelta); |
| }); |
| |
| test('binaryForRebuilding returns the correct binary', () async { |
| final File file = fileSystem.file('file')..writeAsStringSync(content2); |
| final List<int> binaryForRebuilding = await FileTransfer().binaryForRebuilding(file, expectedDelta); |
| expect(binaryForRebuilding, utf8.encode(expectedBinaryForRebuilding)); |
| }); |
| |
| test('rebuildFile can rebuild the correct file', () async { |
| final File file = fileSystem.file('file')..writeAsStringSync(content1); |
| await FileTransfer().rebuildFile(file, expectedDelta, Stream<List<int>>.fromIterable(<List<int>>[utf8.encode(expectedBinaryForRebuilding)])); |
| expect(file.readAsStringSync(), content2); |
| }); |
| }); |
| } |