| // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file |
| // for details. 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:io' as io; |
| |
| import 'package:front_end/src/byte_store/protected_file_byte_store.dart'; |
| import 'package:path/path.dart' as pathos; |
| import 'package:test/test.dart'; |
| import 'package:test_reflective_loader/test_reflective_loader.dart'; |
| |
| main() { |
| defineReflectiveSuite(() { |
| defineReflectiveTests(ProtectedKeysTest); |
| defineReflectiveTests(ProtectedFileByteStoreTest); |
| }); |
| } |
| |
| List<int> _b(int length) { |
| return new List<int>.filled(length, 0); |
| } |
| |
| @reflectiveTest |
| class ProtectedFileByteStoreTest { |
| static const PADDING = 4; |
| |
| io.Directory cacheDirectory; |
| String cachePath; |
| ProtectedFileByteStore store; |
| |
| int time = 0; |
| |
| String get protectedKeysText { |
| String path = |
| pathos.join(cachePath, ProtectedFileByteStore.PROTECTED_FILE_NAME); |
| return new io.File(path).readAsStringSync(); |
| } |
| |
| void setUp() { |
| io.Directory systemTemp = io.Directory.systemTemp; |
| cacheDirectory = systemTemp.createTempSync('ProtectedFileByteStoreTest'); |
| cachePath = cacheDirectory.absolute.path; |
| _createStore(); |
| } |
| |
| void tearDown() { |
| try { |
| cacheDirectory.deleteSync(recursive: true); |
| } on io.FileSystemException {} |
| } |
| |
| test_flush() async { |
| store.put('a', _b(1)); |
| store.put('b', _b(2)); |
| store.put('c', _b(3)); |
| store.put('d', _b(4)); |
| |
| store.updateProtectedKeys(add: ['b', 'd']); |
| |
| // Add a delay to give the store time to write to disk. |
| await new Future.delayed(const Duration(milliseconds: 200)); |
| |
| // Flush, only protected 'b' and 'd' survive. |
| store.flush(); |
| store.flush(); |
| _assertCacheContent({'b': 2, 'd': 4}, ['a', 'c']); |
| |
| // Remove 'b' and flush. |
| // Only 'd' survives. |
| store.updateProtectedKeys(remove: ['b']); |
| store.flush(); |
| _assertCacheContent({'d': 4}, ['b']); |
| } |
| |
| test_put() async { |
| store.put('a', _b(65)); |
| store.put('b', _b(63)); |
| store.put('c', _b(1)); |
| |
| // We can access all results. |
| expect(store.get('a'), hasLength(65)); |
| expect(store.get('b'), hasLength(63)); |
| expect(store.get('c'), hasLength(1)); |
| |
| // Add a delay to give the store time to write to disk. |
| await new Future.delayed(const Duration(milliseconds: 200)); |
| |
| _assertCacheContent({'a': 65, 'b': 63, 'c': 1}, []); |
| } |
| |
| test_put_reservedKey() { |
| expect(() { |
| store.put(ProtectedFileByteStore.PROTECTED_FILE_NAME, <int>[]); |
| }, throwsArgumentError); |
| } |
| |
| test_updateProtectedKeys_add() { |
| store.updateProtectedKeys(add: ['a', 'b']); |
| _assertKeys({'a': 0, 'b': 0}); |
| |
| time++; |
| store.updateProtectedKeys(add: ['c']); |
| _assertKeys({'a': 0, 'b': 0, 'c': 1}); |
| } |
| |
| test_updateProtectedKeys_add_hasSame() { |
| store.updateProtectedKeys(add: ['a', 'b', 'c']); |
| _assertKeys({'a': 0, 'b': 0, 'c': 0}); |
| |
| time++; |
| store.updateProtectedKeys(add: ['b', 'd']); |
| _assertKeys({'a': 0, 'b': 1, 'c': 0, 'd': 1}); |
| } |
| |
| test_updateProtectedKeys_add_removeTooOld() { |
| store.updateProtectedKeys(add: ['a', 'b']); |
| _assertKeys({'a': 0, 'b': 0}); |
| |
| // Move time to 10 ms, both 'a' and 'b' are still alive. |
| time = 10; |
| store.updateProtectedKeys(add: ['c']); |
| _assertKeys({'a': 0, 'b': 0, 'c': 10}); |
| |
| // Move time to 11 ms, now 'a' and 'b' are too old and removed. |
| time = 11; |
| store.updateProtectedKeys(add: ['d']); |
| _assertKeys({'c': 10, 'd': 11}); |
| } |
| |
| test_updateProtectedKeys_add_removeTooOld_nullDuration() { |
| _createStore(protectionDuration: null); |
| |
| store.updateProtectedKeys(add: ['a', 'b']); |
| _assertKeys({'a': 0, 'b': 0}); |
| |
| // Move time far into the future, both 'a' and 'b' are still alive. |
| time = 1 << 30; |
| store.updateProtectedKeys(add: ['c']); |
| _assertKeys({'a': 0, 'b': 0, 'c': time}); |
| } |
| |
| test_updateProtectedKeys_addRemove() { |
| store.updateProtectedKeys(add: ['a', 'b', 'c']); |
| _assertKeys({'a': 0, 'b': 0, 'c': 0}); |
| |
| time++; |
| store.updateProtectedKeys(add: ['d'], remove: ['b']); |
| _assertKeys({'a': 0, 'c': 0, 'd': 1}); |
| } |
| |
| test_updateProtectedKeys_addRemove_same() { |
| store.updateProtectedKeys(add: ['a', 'b', 'c']); |
| _assertKeys({'a': 0, 'b': 0, 'c': 0}); |
| |
| time++; |
| store.updateProtectedKeys(add: ['b'], remove: ['b']); |
| _assertKeys({'a': 0, 'c': 0}); |
| } |
| |
| test_updateProtectedKeys_remove() { |
| store.updateProtectedKeys(add: ['a', 'b', 'c']); |
| _assertKeys({'a': 0, 'b': 0, 'c': 0}); |
| |
| time++; |
| store.updateProtectedKeys(remove: ['b']); |
| _assertKeys({'a': 0, 'c': 0}); |
| } |
| |
| void _assertCacheContent(Map<String, int> includes, List<String> excludes) { |
| Map<String, int> keyToLength = {}; |
| for (var file in cacheDirectory.listSync()) { |
| String key = pathos.basename(file.path); |
| if (file is io.File) { |
| keyToLength[key] = file.lengthSync(); |
| } |
| } |
| includes.forEach((expectedKey, expectedLength) { |
| expect(keyToLength, contains(expectedKey)); |
| expect(keyToLength, containsPair(expectedKey, expectedLength + PADDING)); |
| }); |
| for (var excludedKey in excludes) { |
| expect(keyToLength.keys, isNot(contains(excludedKey))); |
| } |
| } |
| |
| void _assertKeys(Map<String, int> expected) { |
| var path = |
| pathos.join(cachePath, ProtectedFileByteStore.PROTECTED_FILE_NAME); |
| var text = new io.File(path).readAsStringSync(); |
| var keys = new ProtectedKeys.decode(text); |
| expect(keys.map.keys, expected.keys); |
| expected.forEach((key, start) { |
| expect(keys.map, containsPair(key, start)); |
| }); |
| } |
| |
| void _createStore( |
| {Duration protectionDuration: const Duration(milliseconds: 10)}) { |
| store = new ProtectedFileByteStore(cachePath, |
| protectionDuration: protectionDuration, |
| cacheSizeBytes: 256, |
| getCurrentTime: _getTime); |
| } |
| |
| int _getTime() => time; |
| } |
| |
| @reflectiveTest |
| class ProtectedKeysTest { |
| test_decode() { |
| var keys = new ProtectedKeys({'/a/b/c': 10, '/a/d/e': 123}); |
| |
| String text = keys.encode(); |
| expect(text, r''' |
| /a/b/c |
| 10 |
| /a/d/e |
| 123'''); |
| |
| keys = _decode(text); |
| expect(keys.map['/a/b/c'], 10); |
| expect(keys.map['/a/d/e'], 123); |
| } |
| |
| test_decode_empty() { |
| var keys = _decode(''); |
| expect(keys.map, isEmpty); |
| } |
| |
| test_decode_error_notEvenNumberOfLines() { |
| var keys = _decode('a'); |
| expect(keys.map, isEmpty); |
| } |
| |
| test_decode_error_startIsEmpty() { |
| var keys = _decode('a\n'); |
| expect(keys.map, isEmpty); |
| } |
| |
| test_decode_error_startIsNotInt() { |
| var keys = _decode('a\n1.23'); |
| expect(keys.map, isEmpty); |
| } |
| |
| test_decode_error_startIsNotNumber() { |
| var keys = _decode('a\nb'); |
| expect(keys.map, isEmpty); |
| } |
| |
| test_removeOlderThan() { |
| var keys = new ProtectedKeys({'a': 1, 'b': 2, 'c': 3}); |
| _assertKeys(keys, {'a': 1, 'b': 2, 'c': 3}); |
| |
| keys.removeOlderThan(5, 7); |
| _assertKeys(keys, {'b': 2, 'c': 3}); |
| } |
| |
| void _assertKeys(ProtectedKeys keys, Map<String, int> expected) { |
| expect(keys.map.keys, expected.keys); |
| expected.forEach((key, start) { |
| expect(keys.map, containsPair(key, start)); |
| }); |
| } |
| |
| ProtectedKeys _decode(String text) { |
| return new ProtectedKeys.decode(text); |
| } |
| } |