diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index a4144217442..5d10d2ab7ad 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -4,6 +4,8 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import 'base/context.dart'; import 'base/file_system.dart'; import 'base/logger.dart'; @@ -33,10 +35,19 @@ class Cache { /// /// This is used by the tests since they run simultaneously and all in one /// process and so it would be a mess if they had to use the lock. + @visibleForTesting static void disableLocking() { _lockEnabled = false; } + /// Turn on the [lock]/[releaseLockEarly] mechanism. + /// + /// This is used by the tests. + @visibleForTesting + static void enableLocking() { + _lockEnabled = true; + } + /// Lock the cache directory. /// /// This happens automatically on startup (see [FlutterCommandRunner.runCommand]). @@ -48,7 +59,7 @@ class Cache { if (!_lockEnabled) return null; assert(_lock == null); - _lock = fs.file(fs.path.join(flutterRoot, 'bin', 'cache', 'lockfile')).openSync(mode: FileMode.WRITE); + _lock = await fs.file(fs.path.join(flutterRoot, 'bin', 'cache', 'lockfile')).open(mode: FileMode.WRITE); bool locked = false; bool printed = false; while (!locked) { @@ -77,7 +88,7 @@ class Cache { /// Checks if the current process owns the lock for the cache directory at /// this very moment; throws a [StateError] if it doesn't. static void checkLockAcquired() { - if (_lockEnabled && _lock == null) { + if (_lockEnabled && _lock == null && platform.environment['FLUTTER_ALREADY_LOCKED'] != 'true') { throw new StateError( 'The current process does not own the lock for the cache directory. This is a bug in Flutter CLI tools.', ); diff --git a/packages/flutter_tools/test/src/cache_test.dart b/packages/flutter_tools/test/src/cache_test.dart new file mode 100644 index 00000000000..5c7a40196a4 --- /dev/null +++ b/packages/flutter_tools/test/src/cache_test.dart @@ -0,0 +1,67 @@ +// Copyright 2016 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:async'; + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; +import 'package:platform/platform.dart'; + +import 'package:flutter_tools/src/cache.dart'; + +import 'context.dart'; + +void main() { + group('$Cache.checkLockAcquired', () { + setUp(() { + Cache.enableLocking(); + }); + + tearDown(() { + // Restore locking to prevent potential side-effects in + // tests outside this group (this option is globally shared). + Cache.enableLocking(); + }); + + test('should throw when locking is not acquired', () { + expect(() => Cache.checkLockAcquired(), throwsStateError); + }); + + test('should not throw when locking is disabled', () { + Cache.disableLocking(); + Cache.checkLockAcquired(); + }); + + testUsingContext('should not throw when lock is acquired', () async { + await Cache.lock(); + Cache.checkLockAcquired(); + }, overrides: { + FileSystem: () => new MockFileSystem(), + }); + + testUsingContext('should not throw when FLUTTER_ALREADY_LOCKED is set', () async { + Cache.checkLockAcquired(); + }, overrides: { + Platform: () => new FakePlatform()..environment = {'FLUTTER_ALREADY_LOCKED': 'true'}, + }); + }); +} + +class MockFileSystem extends MemoryFileSystem { + @override + File file(dynamic path) { + return new MockFile(); + } +} + +class MockFile extends Mock implements File { + @override + Future open({FileMode mode: FileMode.READ}) async { + return new MockRandomAccessFile(); + } +} + +class MockRandomAccessFile extends Mock implements RandomAccessFile {}