diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a9ecdbddd7..503b4b65960 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,16 +24,18 @@ methods are now supported on iOS and OSX. * Deprecated `SecurityContext.alpnSupported` as ALPN is now supported on all platforms. - * Added 'timeout' parameter to 'Socket.connect', 'RawSocket.connect', - 'SecureSocket.connect' and 'RawSecureSocket.connect. If a connection attempt - takes longer than the duration specified in 'timeout', a 'SocketException' - will be thrown. Note: if the duration specified in 'timeout' is greater than - the system level timeout duration, a timeout may occur sooner than specified - in 'timeout'. + * Added a `timeout` parameter to `Socket.connect`, `RawSocket.connect`, + `SecureSocket.connect` and `RawSecureSocket.connect`. If a connection attempt + takes longer than the duration specified in `timeout`, a `SocketException` + will be thrown. Note: if the duration specified in `timeout` is greater than + the OS level timeout, a timeout may occur sooner than specified in + `timeout`. * Added `Platform.operatingSystemVersion` that gives a platform-specific String describing the version of the operating system. * Added `RawZLibFilter` for low-level access to compression and - decompression. + decompression routines. + * Added `IoOverrides` and `HttpOverrides` to aid in writing tests that wish to + mock varios `dart:io` objects. * `dart:core` * The `Uri` class now correctly handles paths while running on Node.js on diff --git a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart index 6e4456a059d..f1190827b88 100644 --- a/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart +++ b/pkg/dev_compiler/tool/input_sdk/patch/io_patch.dart @@ -84,12 +84,12 @@ class FileStat { @patch class FileSystemEntity { @patch - static _getType(_Namespace namespace, String path, bool followLinks) { + static _getTypeNative(_Namespace namespace, String path, bool followLinks) { throw new UnsupportedError("FileSystemEntity._getType"); } @patch - static _identical(_Namespace namespace, String path1, String path2) { + static _identicalNative(_Namespace namespace, String path1, String path2) { throw new UnsupportedError("FileSystemEntity._identical"); } diff --git a/runtime/bin/file_system_entity_patch.dart b/runtime/bin/file_system_entity_patch.dart index 9205019e4a4..3aecd6697ca 100644 --- a/runtime/bin/file_system_entity_patch.dart +++ b/runtime/bin/file_system_entity_patch.dart @@ -13,10 +13,10 @@ class FileStat { @patch class FileSystemEntity { @patch - static _getType(_Namespace namespace, String path, bool followLinks) + static _getTypeNative(_Namespace namespace, String path, bool followLinks) native "File_GetType"; @patch - static _identical(_Namespace namespace, String path1, String path2) + static _identicalNative(_Namespace namespace, String path1, String path2) native "File_AreIdentical"; @patch static _resolveSymbolicLinks(_Namespace namespace, String path) diff --git a/sdk/lib/_http/http.dart b/sdk/lib/_http/http.dart index 93252ac31de..bf4ab47c632 100644 --- a/sdk/lib/_http/http.dart +++ b/sdk/lib/_http/http.dart @@ -26,6 +26,7 @@ part 'http_headers.dart'; part 'http_impl.dart'; part 'http_parser.dart'; part 'http_session.dart'; +part 'overrides.dart'; part 'websocket.dart'; part 'websocket_impl.dart'; @@ -1363,7 +1364,13 @@ abstract class HttpClient { */ String userAgent; - factory HttpClient({SecurityContext context}) => new _HttpClient(context); + factory HttpClient({SecurityContext context}) { + HttpOverrides overrides = HttpOverrides.current; + if (overrides == null) { + return new _HttpClient(context); + } + return overrides.createHttpClient(context); + } /** * Opens a HTTP connection. @@ -1623,7 +1630,11 @@ abstract class HttpClient { */ static String findProxyFromEnvironment(Uri url, {Map environment}) { - return _HttpClient._findProxyFromEnvironment(url, environment); + HttpOverrides overrides = HttpOverrides.current; + if (overrides == null) { + return _HttpClient._findProxyFromEnvironment(url, environment); + } + return overrides.findProxyFromEnvironment(url, environment); } /** diff --git a/sdk/lib/_http/http_sources.gni b/sdk/lib/_http/http_sources.gni index 146fd6a3319..1ce36b444f2 100644 --- a/sdk/lib/_http/http_sources.gni +++ b/sdk/lib/_http/http_sources.gni @@ -12,6 +12,7 @@ http_sdk_sources = [ "http_impl.dart", "http_parser.dart", "http_session.dart", + "overrides.dart", "websocket.dart", "websocket_impl.dart", ] diff --git a/sdk/lib/_http/overrides.dart b/sdk/lib/_http/overrides.dart new file mode 100644 index 00000000000..7ef35b27d08 --- /dev/null +++ b/sdk/lib/_http/overrides.dart @@ -0,0 +1,107 @@ +// 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. + +part of dart._http; + +final _httpOverridesToken = new Object(); + +const _asyncRunZoned = runZoned; + +/// This class facilitates overriding [HttpClient] with a mock implementation. +/// It should be extended by another class in client code with overrides +/// that construct a mock implementation. The implementation in this base class +/// defaults to the actual [HttpClient] implementation. For example: +/// +/// ``` +/// class MyHttpClient implements HttpClient { +/// ... +/// // An implementation of the HttpClient interface +/// ... +/// } +/// +/// main() { +/// HttpOverrides.runZoned(() { +/// ... +/// // Operations will use MyHttpClient instead of the real HttpClient +/// // implementation whenever HttpClient is used. +/// ... +/// }, createHttpClient: (SecurityContext c) => new MyHttpClient(c)); +/// } +/// ``` +abstract class HttpOverrides { + static HttpOverrides get current { + return Zone.current[_httpOverridesToken]; + } + + /// Runs [body] in a fresh [Zone] using the provided overrides. + static R runZoned(R body(), + {HttpClient Function(SecurityContext) createHttpClient, + String Function(Uri uri, Map environment) + findProxyFromEnvironment, + ZoneSpecification zoneSpecification, + Function onError}) { + HttpOverrides overrides = + new _HttpOverridesScope(createHttpClient, findProxyFromEnvironment); + return _asyncRunZoned(body, + zoneValues: {_httpOverridesToken: overrides}, + zoneSpecification: zoneSpecification, + onError: onError); + } + + /// Runs [body] in a fresh [Zone] using the overrides found in [overrides]. + /// + /// Note that [overrides] should be an instance of a class that extends + /// [HttpOverrides]. + static R runWithHttpOverrides(R body(), HttpOverrides overrides, + {ZoneSpecification zoneSpecification, Function onError}) { + return _asyncRunZoned(body, + zoneValues: {_httpOverridesToken: overrides}, + zoneSpecification: zoneSpecification, + onError: onError); + } + + /// Returns a new [HttpClient] using the given [context]. + /// + /// When this override is installed, this function overrides the behavior of + /// `new HttpClient`. + HttpClient createHttpClient(SecurityContext context) { + return new _HttpClient(context); + } + + /// Resolves the proxy server to be used for HTTP connections. + /// + /// When this override is installed, this function overrides the behavior of + /// `HttpClient.findProxyFromEnvironment`. + String findProxyFromEnvironment(Uri url, Map environment) { + return _HttpClient._findProxyFromEnvironment(url, environment); + } +} + +class _HttpOverridesScope extends HttpOverrides { + final HttpOverrides _previous = HttpOverrides.current; + + final HttpClient Function(SecurityContext) _createHttpClient; + final String Function(Uri uri, Map environment) + _findProxyFromEnvironment; + + _HttpOverridesScope(this._createHttpClient, this._findProxyFromEnvironment); + + @override + HttpClient createHttpClient(SecurityContext context) { + if (_createHttpClient != null) return _createHttpClient(context); + if (_previous != null) return _previous.createHttpClient(context); + return super.createHttpClient(context); + } + + @override + String findProxyFromEnvironment(Uri url, Map environment) { + if (_findProxyFromEnvironment != null) { + return _findProxyFromEnvironment(url, environment); + } + if (_previous != null) { + return _previous.findProxyFromEnvironment(url, environment); + } + return super.findProxyFromEnvironment(url, environment); + } +} diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart index 75da09f4f1c..8a49d4f3de8 100644 --- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart @@ -84,12 +84,12 @@ class FileStat { @patch class FileSystemEntity { @patch - static _getType(_Namespace namespace, String path, bool followLinks) { + static _getTypeNative(_Namespace namespace, String path, bool followLinks) { throw new UnsupportedError("FileSystemEntity._getType"); } @patch - static _identical(_Namespace namespace, String path1, String path2) { + static _identicalNative(_Namespace namespace, String path1, String path2) { throw new UnsupportedError("FileSystemEntity._identical"); } diff --git a/sdk/lib/io/directory.dart b/sdk/lib/io/directory.dart index 90021f21aaa..c99dbf4a290 100644 --- a/sdk/lib/io/directory.dart +++ b/sdk/lib/io/directory.dart @@ -126,7 +126,13 @@ abstract class Directory implements FileSystemEntity { * If [path] is an absolute path, it will be immune to changes to the * current working directory. */ - factory Directory(String path) => new _Directory(path); + factory Directory(String path) { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return new _Directory(path); + } + return overrides.createDirectory(path); + } /** * Create a Directory object from a URI. @@ -139,7 +145,13 @@ abstract class Directory implements FileSystemEntity { * Creates a directory object pointing to the current working * directory. */ - static Directory get current => _Directory.current; + static Directory get current { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _Directory.current; + } + return overrides.getCurrentDirectory(); + } /** * Returns a [Uri] representing the directory's location. @@ -169,7 +181,12 @@ abstract class Directory implements FileSystemEntity { * are working with the file system, can lead to unexpected results. */ static void set current(path) { - _Directory.current = path; + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + _Directory.current = path; + return; + } + overrides.setCurrentDirectory(path); } /** @@ -204,7 +221,13 @@ abstract class Directory implements FileSystemEntity { * The location of the system temp directory is platform-dependent, * and may be set by an environment variable. */ - static Directory get systemTemp => _Directory.systemTemp; + static Directory get systemTemp { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _Directory.systemTemp; + } + return overrides.getSystemTempDirectory(); + } /** * Creates a temporary directory in this directory. Additional random diff --git a/sdk/lib/io/file.dart b/sdk/lib/io/file.dart index 9acfdcbd70f..757682e5b45 100644 --- a/sdk/lib/io/file.dart +++ b/sdk/lib/io/file.dart @@ -219,7 +219,13 @@ abstract class File implements FileSystemEntity { * If [path] is an absolute path, it will be immune to changes to the * current working directory. */ - factory File(String path) => new _File(path); + factory File(String path) { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return new _File(path); + } + return overrides.createFile(path); + } /** * Create a File object from a URI. diff --git a/sdk/lib/io/file_system_entity.dart b/sdk/lib/io/file_system_entity.dart index d4b1692f31b..7375415bd57 100644 --- a/sdk/lib/io/file_system_entity.dart +++ b/sdk/lib/io/file_system_entity.dart @@ -49,30 +49,40 @@ class FileStat { /** * The time of the last change to the data or metadata of the file system - * object. On Windows platforms, this is instead the file creation time. + * object. + * + * On Windows platforms, this is instead the file creation time. */ final DateTime changed; + /** - * The time of the last change to the data of the file system - * object. + * The time of the last change to the data of the file system object. */ final DateTime modified; + /** - * The time of the last access to the data of the file system - * object. On Windows platforms, this may have 1 day granularity, and be + * The time of the last access to the data of the file system object. + * + * On Windows platforms, this may have 1 day granularity, and be * out of date by an hour. */ final DateTime accessed; + /** - * The type of the object (file, directory, or link). If the call to - * stat() fails, the type of the returned object is NOT_FOUND. + * The type of the object (file, directory, or link). + * + * If the call to stat() fails, the type of the returned object is NOT_FOUND. */ final FileSystemEntityType type; + /** - * The mode of the file system object. Permissions are encoded in the lower - * 16 bits of this number, and can be decoded using the [modeString] getter. + * The mode of the file system object. + * + * Permissions are encoded in the lower 16 bits of this number, and can be + * decoded using the [modeString] getter. */ final int mode; + /** * The size of the file system object. */ @@ -93,11 +103,20 @@ class FileStat { /** * Calls the operating system's stat() function on [path]. + * * Returns a [FileStat] object containing the data returned by stat(). * If the call fails, returns a [FileStat] object with .type set to * FileSystemEntityType.NOT_FOUND and the other fields invalid. */ static FileStat statSync(String path) { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _statSyncInternal(path); + } + return overrides.statSync(path); + } + + static FileStat _statSyncInternal(String path) { // Trailing path is not supported on Windows. if (Platform.isWindows) { path = FileSystemEntity._trimTrailingPathSeparators(path); @@ -115,12 +134,21 @@ class FileStat { /** * Asynchronously calls the operating system's stat() function on [path]. + * * Returns a Future which completes with a [FileStat] object containing - * the data returned by stat(). - * If the call fails, completes the future with a [FileStat] object with - * .type set to FileSystemEntityType.NOT_FOUND and the other fields invalid. + * the data returned by stat(). If the call fails, completes the future with a + * [FileStat] object with `.type` set to FileSystemEntityType.NOT_FOUND and + * the other fields invalid. */ static Future stat(String path) { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _stat(path); + } + return overrides.stat(path); + } + + static Future _stat(String path) { // Trailing path is not supported on Windows. if (Platform.isWindows) { path = FileSystemEntity._trimTrailingPathSeparators(path); @@ -151,11 +179,13 @@ FileStat: type $type size $size"""; /** - * Returns the mode value as a human-readable string, in the format - * "rwxrwxrwx", reflecting the user, group, and world permissions to - * read, write, and execute the file system object, with "-" replacing the - * letter for missing permissions. Extra permission bits may be represented - * by prepending "(suid)", "(guid)", and/or "(sticky)" to the mode string. + * Returns the mode value as a human-readable string. + * + * The string is in the format "rwxrwxrwx", reflecting the user, group, and + * world permissions to read, write, and execute the file system object, with + * "-" replacing the letter for missing permissions. Extra permission bits + * may be represented by prepending "(suid)", "(guid)", and/or "(sticky)" to + * the mode string. */ String modeString() { var permissions = mode & 0xFFF; @@ -255,9 +285,10 @@ abstract class FileSystemEntity { bool existsSync(); /** - * Renames this file system entity. Returns a `Future` - * that completes with a [FileSystemEntity] instance for the renamed - * file system entity. + * Renames this file system entity. + * + * Returns a `Future` that completes with a + * [FileSystemEntity] instance for the renamed file system entity. * * If [newPath] identifies an existing entity of the same type, that entity * is replaced. If [newPath] identifies an existing entity of a different @@ -266,8 +297,9 @@ abstract class FileSystemEntity { Future rename(String newPath); /** - * Synchronously renames this file system entity. Returns a [FileSystemEntity] - * instance for the renamed entity. + * Synchronously renames this file system entity. + * + * Returns a [FileSystemEntity] instance for the renamed entity. * * If [newPath] identifies an existing entity of the same type, that entity * is replaced. If [newPath] identifies an existing entity of a different @@ -277,8 +309,10 @@ abstract class FileSystemEntity { /** * Resolves the path of a file system object relative to the - * current working directory, resolving all symbolic links on - * the path and resolving all `..` and `.` path segments. + * current working directory. + * + * Resolves all symbolic links on the path and resolves all `..` and `.` path + * segments. * * [resolveSymbolicLinks] uses the operating system's native * file system API to resolve the path, using the `realpath` function @@ -316,8 +350,10 @@ abstract class FileSystemEntity { /** * Resolves the path of a file system object relative to the - * current working directory, resolving all symbolic links on - * the path and resolving all `..` and `.` path segments. + * current working directory. + * + * Resolves all symbolic links on the path and resolves all `..` and `.` path + * segments. * * [resolveSymbolicLinksSync] uses the operating system's native * file system API to resolve the path, using the `realpath` function @@ -349,7 +385,9 @@ abstract class FileSystemEntity { /** * Calls the operating system's stat() function on the [path] of this - * [FileSystemEntity]. Identical to [:FileStat.stat(this.path):]. + * [FileSystemEntity]. + * + * Identical to [:FileStat.stat(this.path):]. * * Returns a [:Future:] object containing the data returned by * stat(). @@ -363,6 +401,7 @@ abstract class FileSystemEntity { /** * Synchronously calls the operating system's stat() function on the * [path] of this [FileSystemEntity]. + * * Identical to [:FileStat.statSync(this.path):]. * * Returns a [FileStat] object containing the data returned by stat(). @@ -443,16 +482,34 @@ abstract class FileSystemEntity { * A move event may be reported as seperate delete and create events. */ Stream watch( - {int events: FileSystemEvent.ALL, bool recursive: false}) => - _FileSystemWatcher._watch( - _trimTrailingPathSeparators(path), events, recursive); + {int events: FileSystemEvent.ALL, bool recursive: false}) { + final String trimmedPath = _trimTrailingPathSeparators(path); + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _FileSystemWatcher._watch(trimmedPath, events, recursive); + } + return overrides.fsWatch(trimmedPath, events, recursive); + } Future _delete({bool recursive: false}); void _deleteSync({bool recursive: false}); + static Future _identical(String path1, String path2) { + return _File._dispatchWithNamespace( + _FILE_IDENTICAL, [null, path1, path2]).then((response) { + if (_isErrorResponse(response)) { + throw _exceptionFromResponse(response, + "Error in FileSystemEntity.identical($path1, $path2)", ""); + } + return response; + }); + } + /** * Checks whether two paths refer to the same object in the - * file system. Returns a [:Future:] that completes with the result. + * file system. + * + * Returns a [:Future:] that completes with the result. * * Comparing a link to its target returns false, as does comparing two links * that point to the same target. To check the target of a link, use @@ -463,14 +520,11 @@ abstract class FileSystemEntity { * to an object that does not exist. */ static Future identical(String path1, String path2) { - return _File._dispatchWithNamespace( - _FILE_IDENTICAL, [null, path1, path2]).then((response) { - if (_isErrorResponse(response)) { - throw _exceptionFromResponse(response, - "Error in FileSystemEntity.identical($path1, $path2)", ""); - } - return response; - }); + IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _identical(path1, path2); + } + return overrides.fseIdentical(path1, path2); } static final RegExp _absoluteWindowsPathPattern = @@ -493,6 +547,7 @@ abstract class FileSystemEntity { /** * Returns a [FileSystemEntity] whose path is the absolute path to [this]. + * * The type of the returned instance is the type of [this]. * * The absolute path is computed by prefixing @@ -512,6 +567,12 @@ abstract class FileSystemEntity { } } + static bool _identicalSync(String path1, String path2) { + var result = _identicalNative(_Namespace._namespace, path1, path2); + _throwIfError(result, 'Error in FileSystemEntity.identicalSync'); + return result; + } + /** * Synchronously checks whether two paths refer to the same object in the * file system. @@ -525,9 +586,11 @@ abstract class FileSystemEntity { * exist. */ static bool identicalSync(String path1, String path2) { - var result = _identical(_Namespace._namespace, path1, path2); - _throwIfError(result, 'Error in FileSystemEntity.identicalSync'); - return result; + IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _identicalSync(path1, path2); + } + return overrides.fseIdenticalSync(path1, path2); } /** @@ -535,11 +598,18 @@ abstract class FileSystemEntity { * * OS X 10.6 and below is not supported. */ - static bool get isWatchSupported => _FileSystemWatcher.isSupported; + static bool get isWatchSupported { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _FileSystemWatcher.isSupported; + } + return overrides.fsWatchIsSupported(); + } /** - * Finds the type of file system object that a path points to. Returns - * a [:Future:] that completes with the result. + * Finds the type of file system object that a path points to. + * + * Returns a [:Future:] that completes with the result. * * [FileSystemEntityType] has the constant instances FILE, DIRECTORY, * LINK, and NOT_FOUND. [type] will return LINK only if the optional @@ -550,11 +620,13 @@ abstract class FileSystemEntity { * caused by passing the wrong type of arguments to the function. */ static Future type(String path, - {bool followLinks: true}) => - _getTypeAsync(path, followLinks).then(FileSystemEntityType._lookup); + {bool followLinks: true}) { + return _getType(path, followLinks); + } /** * Synchronously finds the type of file system object that a path points to. + * * Returns a [FileSystemEntityType]. * * [FileSystemEntityType] has the constant instances FILE, DIRECTORY, @@ -565,51 +637,53 @@ abstract class FileSystemEntity { * error or exception that may be thrown is ArgumentError, * caused by passing the wrong type of arguments to the function. */ - static FileSystemEntityType typeSync(String path, {bool followLinks: true}) => - FileSystemEntityType._lookup(_getTypeSync(path, followLinks)); + static FileSystemEntityType typeSync(String path, {bool followLinks: true}) { + return _getTypeSync(path, followLinks); + } /** - * Checks if type(path, followLinks: false) returns - * FileSystemEntityType.LINK. + * Checks if type(path, followLinks: false) returns FileSystemEntityType.LINK. */ - static Future isLink(String path) => _getTypeAsync(path, false) - .then((type) => (type == FileSystemEntityType.LINK._type)); + static Future isLink(String path) => + _getType(path, false).then((type) => (type == FileSystemEntityType.LINK)); /** * Checks if type(path) returns FileSystemEntityType.FILE. */ - static Future isFile(String path) => _getTypeAsync(path, true) - .then((type) => (type == FileSystemEntityType.FILE._type)); + static Future isFile(String path) => + _getType(path, true).then((type) => (type == FileSystemEntityType.FILE)); /** * Checks if type(path) returns FileSystemEntityType.DIRECTORY. */ - static Future isDirectory(String path) => _getTypeAsync(path, true) - .then((type) => (type == FileSystemEntityType.DIRECTORY._type)); + static Future isDirectory(String path) => _getType(path, true) + .then((type) => (type == FileSystemEntityType.DIRECTORY)); /** * Synchronously checks if typeSync(path, followLinks: false) returns * FileSystemEntityType.LINK. */ static bool isLinkSync(String path) => - (_getTypeSync(path, false) == FileSystemEntityType.LINK._type); + (_getTypeSync(path, false) == FileSystemEntityType.LINK); /** * Synchronously checks if typeSync(path) returns * FileSystemEntityType.FILE. */ static bool isFileSync(String path) => - (_getTypeSync(path, true) == FileSystemEntityType.FILE._type); + (_getTypeSync(path, true) == FileSystemEntityType.FILE); /** * Synchronously checks if typeSync(path) returns * FileSystemEntityType.DIRECTORY. */ static bool isDirectorySync(String path) => - (_getTypeSync(path, true) == FileSystemEntityType.DIRECTORY._type); + (_getTypeSync(path, true) == FileSystemEntityType.DIRECTORY); - external static _getType(_Namespace namespace, String path, bool followLinks); - external static _identical(_Namespace namespace, String path1, String path2); + external static _getTypeNative( + _Namespace namespace, String path, bool followLinks); + external static _identicalNative( + _Namespace namespace, String path1, String path2); external static _resolveSymbolicLinks(_Namespace namespace, String path); // Finds the next-to-last component when dividing at path separators. @@ -619,9 +693,11 @@ abstract class FileSystemEntity { /** * Removes the final path component of a path, using the platform's - * path separator to split the path. Will not remove the root component - * of a Windows path, like "C:\\" or "\\\\server_name\\". - * Ignores trailing path separators, and leaves no trailing path separators. + * path separator to split the path. + * + * Will not remove the root component of a Windows path, like "C:\\" or + * "\\\\server_name\\". Ignores trailing path separators, and leaves no + * trailing path separators. */ static String parentOf(String path) { int rootEnd = -1; @@ -653,22 +729,40 @@ abstract class FileSystemEntity { */ Directory get parent => new Directory(parentOf(path)); - static int _getTypeSync(String path, bool followLinks) { - var result = _getType(_Namespace._namespace, path, followLinks); + static FileSystemEntityType _getTypeSyncHelper( + String path, bool followLinks) { + var result = _getTypeNative(_Namespace._namespace, path, followLinks); _throwIfError(result, 'Error getting type of FileSystemEntity'); - return result; + return FileSystemEntityType._lookup(result); } - static Future _getTypeAsync(String path, bool followLinks) { + static FileSystemEntityType _getTypeSync(String path, bool followLinks) { + IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _getTypeSyncHelper(path, followLinks); + } + return overrides.fseGetTypeSync(path, followLinks); + } + + static Future _getTypeRequest( + String path, bool followLinks) { return _File._dispatchWithNamespace( _FILE_TYPE, [null, path, followLinks]).then((response) { if (_isErrorResponse(response)) { throw _exceptionFromResponse(response, "Error getting type", path); } - return response; + return FileSystemEntityType._lookup(response); }); } + static Future _getType(String path, bool followLinks) { + IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return _getTypeRequest(path, followLinks); + } + return overrides.fseGetType(path, followLinks); + } + static _throwIfError(Object result, String msg, [String path]) { if (result is OSError) { throw new FileSystemException(msg, path, result); @@ -750,8 +844,10 @@ class FileSystemEvent { final int type; /** - * The path that triggered the event. Depending on the platform and the - * FileSystemEntity, the path may be relative. + * The path that triggered the event. + * + * Depending on the platform and the FileSystemEntity, the path may be + * relative. */ final String path; diff --git a/sdk/lib/io/io.dart b/sdk/lib/io/io.dart index d4e8871bcf5..c80e602107a 100644 --- a/sdk/lib/io/io.dart +++ b/sdk/lib/io/io.dart @@ -220,6 +220,7 @@ part 'io_sink.dart'; part 'io_service.dart'; part 'link.dart'; part 'namespace_impl.dart'; +part 'overrides.dart'; part 'platform.dart'; part 'platform_impl.dart'; part 'process.dart'; diff --git a/sdk/lib/io/io_sources.gni b/sdk/lib/io/io_sources.gni index bbaf7fb8804..51e283a990e 100644 --- a/sdk/lib/io/io_sources.gni +++ b/sdk/lib/io/io_sources.gni @@ -21,6 +21,7 @@ io_sdk_sources = [ "io_service.dart", "link.dart", "namespace_impl.dart", + "overrides.dart", "platform.dart", "platform_impl.dart", "process.dart", diff --git a/sdk/lib/io/link.dart b/sdk/lib/io/link.dart index 25856decd43..6fb49d9219c 100644 --- a/sdk/lib/io/link.dart +++ b/sdk/lib/io/link.dart @@ -12,7 +12,13 @@ abstract class Link implements FileSystemEntity { /** * Creates a Link object. */ - factory Link(String path) => new _Link(path); + factory Link(String path) { + final IoOverrides overrides = IoOverrides.current; + if (overrides == null) { + return new _Link(path); + } + return overrides.createLink(path); + } /** * Creates a [Link] object. diff --git a/sdk/lib/io/overrides.dart b/sdk/lib/io/overrides.dart new file mode 100644 index 00000000000..e233295b491 --- /dev/null +++ b/sdk/lib/io/overrides.dart @@ -0,0 +1,395 @@ +// 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. + +part of dart.io; + +final _ioOverridesToken = new Object(); + +const _asyncRunZoned = runZoned; + +/// This class facilitates overriding various APIs of dart:io with mock +/// implementations. +/// +/// This abstract base class should be extended with overrides for the +/// operations needed to construct mocks. The implementations in this base class +/// default to the actual dart:io implementation. For example: +/// +/// ``` +/// class MyDirectory implements Directory { +/// ... +/// // An implementation of the Directory interface +/// ... +/// } +/// +/// main() { +/// IoOverrides.runZoned(() { +/// ... +/// // Operations will use MyDirectory instead of dart:io's Directory +/// // implementation whenever Directory is used. +/// ... +/// }, createDirectory: (String path) => new MyDirectory(path)); +/// } +/// ``` +abstract class IoOverrides { + static IoOverrides get current { + return Zone.current[_ioOverridesToken]; + } + + /// Runs [body] in a fresh [Zone] using the provided overrides. + /// + /// See the documentation on the corresponding methods of IoOverrides for + /// information about what the optional arguments do. + static R runZoned(R body(), + { + // Directory + Directory Function(String) createDirectory, + Directory Function() getCurrentDirectory, + void Function(String) setCurrentDirectory, + Directory Function() getSystemTempDirectory, + + // File + File Function(String) createFile, + + // FileStat + Future Function(String) stat, + FileStat Function(String) statSync, + + // FileSystemEntity + Future Function(String, String) fseIdentical, + bool Function(String, String) fseIdenticalSync, + Future Function(String, bool) fseGetType, + FileSystemEntityType Function(String, bool) fseGetTypeSync, + + // _FileSystemWatcher + Stream Function(String, int, bool) fsWatch, + bool Function() fsWatchIsSupported, + + // Link + Link Function(String) createLink, + + // Optional Zone parameters + ZoneSpecification zoneSpecification, + Function onError}) { + IoOverrides overrides = new _IoOverridesScope( + // Directory + createDirectory, + getCurrentDirectory, + setCurrentDirectory, + getSystemTempDirectory, + + // File + createFile, + + // FileStat + stat, + statSync, + + // FileSystemEntity + fseIdentical, + fseIdenticalSync, + fseGetType, + fseGetTypeSync, + + // _FileSystemWatcher + fsWatch, + fsWatchIsSupported, + + // Link + createLink, + ); + return _asyncRunZoned(body, + zoneValues: {_ioOverridesToken: overrides}, + zoneSpecification: zoneSpecification, + onError: onError); + } + + /// Runs [body] in a fresh [Zone] using the overrides found in [overrides]. + /// + /// Note that [overrides] should be an instance of a class that extends + /// [IoOverrides]. + static R runWithIoOverrides(R body(), IoOverrides overrides, + {ZoneSpecification zoneSpecification, Function onError}) { + return _asyncRunZoned(body, + zoneValues: {_ioOverridesToken: overrides}, + zoneSpecification: zoneSpecification, + onError: onError); + } + + // Directory + + /// Creates a new [Directory] object for the given [path]. + /// + /// When this override is installed, this function overrides the behavior of + /// `new Directory()` and `new Directory.fromUri()`. + Directory createDirectory(String path) => new _Directory(path); + + /// Returns the current working directory. + /// + /// When this override is installed, this function overrides the behavior of + /// the static getter `Directory.current` + Directory getCurrentDirectory() => _Directory.current; + + /// Sets the current working directory to be [path]. + /// + /// When this override is installed, this function overrides the behavior of + /// the setter `Directory.current`. + void setCurrentDirectory(String path) { + _Directory.current = path; + } + + /// Returns the system temporary directory. + /// + /// When this override is installed, this function overrides the behavior of + /// `Directory.systemTemp`. + Directory getSystemTempDirectory() => _Directory.systemTemp; + + // File + + /// Creates a new [File] object for the given [path]. + /// + /// When this override is installed, this function overrides the behavior of + /// `new File()` and `new File.fromUri()`. + File createFile(String path) => new _File(path); + + // FileStat + + /// Asynchronously returns [FileStat] information for [path]. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileStat.stat()`. + Future stat(String path) { + return FileStat._stat(path); + } + + /// Returns [FileStat] information for [path]. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileStat.statSync()`. + FileStat statSync(String path) { + return FileStat._statSyncInternal(path); + } + + // FileSystemEntity + + /// Asynchronously returns `true` if [path1] and [path2] are paths to the + /// same file system object. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileSystemEntity.identical`. + Future fseIdentical(String path1, String path2) { + return FileSystemEntity._identical(path1, path2); + } + + /// Returns `true` if [path1] and [path2] are paths to the + /// same file system object. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileSystemEntity.identicalSync`. + bool fseIdenticalSync(String path1, String path2) { + return FileSystemEntity._identicalSync(path1, path2); + } + + /// Asynchronously returns the [FileSystemEntityType] for [path]. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileSystemEntity.type`. + Future fseGetType(String path, bool followLinks) { + return FileSystemEntity._getTypeRequest(path, followLinks); + } + + /// Returns the [FileSystemEntityType] for [path]. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileSystemEntity.typeSync`. + FileSystemEntityType fseGetTypeSync(String path, bool followLinks) { + return FileSystemEntity._getTypeSyncHelper(path, followLinks); + } + + // _FileSystemWatcher + + /// Returns a [Stream] of [FileSystemEvent]s. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileSystemEntity.watch()`. + Stream fsWatch(String path, int events, bool recursive) { + return _FileSystemWatcher._watch(path, events, recursive); + } + + /// Returns `true` when [FileSystemEntity.watch] is supported. + /// + /// When this override is installed, this function overrides the behavior of + /// `FileSystemEntity.isWatchSupported`. + bool fsWatchIsSupported() => _FileSystemWatcher.isSupported; + + // Link + + /// Returns a new [Link] object for the given [path]. + /// When this override is installed, this function overrides the behavior of + /// `new Link()` and `new Link.fromUri()`. + Link createLink(String path) => new _Link(path); +} + +class _IoOverridesScope extends IoOverrides { + final IoOverrides _previous = IoOverrides.current; + + // Directory + Directory Function(String) _createDirectory; + Directory Function() _getCurrentDirectory; + void Function(String) _setCurrentDirectory; + Directory Function() _getSystemTempDirectory; + + // File + File Function(String) _createFile; + + // FileStat + Future Function(String) _stat; + FileStat Function(String) _statSync; + + // FileSystemEntity + Future Function(String, String) _fseIdentical; + bool Function(String, String) _fseIdenticalSync; + Future Function(String, bool) _fseGetType; + FileSystemEntityType Function(String, bool) _fseGetTypeSync; + + // _FileSystemWatcher + Stream Function(String, int, bool) _fsWatch; + bool Function() _fsWatchIsSupported; + + // Link + Link Function(String) _createLink; + + _IoOverridesScope( + // Directory + this._createDirectory, + this._getCurrentDirectory, + this._setCurrentDirectory, + this._getSystemTempDirectory, + + // File + this._createFile, + + // FileStat + this._stat, + this._statSync, + + // FileSystemEntity + this._fseIdentical, + this._fseIdenticalSync, + this._fseGetType, + this._fseGetTypeSync, + + // _FileSystemWatcher + this._fsWatch, + this._fsWatchIsSupported, + + // Link + this._createLink, + ); + + // Directory + @override + Directory createDirectory(String path) { + if (_createDirectory != null) return _createDirectory(path); + if (_previous != null) return _previous.createDirectory(path); + return super.createDirectory(path); + } + + @override + Directory getCurrentDirectory() { + if (_getCurrentDirectory != null) return _getCurrentDirectory(); + if (_previous != null) return _previous.getCurrentDirectory(); + return super.getCurrentDirectory(); + } + + @override + void setCurrentDirectory(String path) { + if (_setCurrentDirectory != null) + _setCurrentDirectory(path); + else if (_previous != null) + _previous.setCurrentDirectory(path); + else + super.setCurrentDirectory(path); + } + + @override + Directory getSystemTempDirectory() { + if (_getSystemTempDirectory != null) return _getSystemTempDirectory(); + if (_previous != null) return _previous.getSystemTempDirectory(); + return super.getSystemTempDirectory(); + } + + // File + @override + File createFile(String path) { + if (_createFile != null) return _createFile(path); + if (_previous != null) return _previous.createFile(path); + return super.createFile(path); + } + + // FileStat + @override + Future stat(String path) { + if (_stat != null) return _stat(path); + if (_previous != null) return _previous.stat(path); + return super.stat(path); + } + + @override + FileStat statSync(String path) { + if (_stat != null) return _statSync(path); + if (_previous != null) return _previous.statSync(path); + return super.statSync(path); + } + + // FileSystemEntity + @override + Future fseIdentical(String path1, String path2) { + if (_fseIdentical != null) return _fseIdentical(path1, path2); + if (_previous != null) return _previous.fseIdentical(path1, path2); + return super.fseIdentical(path1, path2); + } + + @override + bool fseIdenticalSync(String path1, String path2) { + if (_fseIdenticalSync != null) return _fseIdenticalSync(path1, path2); + if (_previous != null) return _previous.fseIdenticalSync(path1, path2); + return super.fseIdenticalSync(path1, path2); + } + + @override + Future fseGetType(String path, bool followLinks) { + if (_fseGetType != null) return _fseGetType(path, followLinks); + if (_previous != null) return _previous.fseGetType(path, followLinks); + return super.fseGetType(path, followLinks); + } + + @override + FileSystemEntityType fseGetTypeSync(String path, bool followLinks) { + if (_fseGetTypeSync != null) return _fseGetTypeSync(path, followLinks); + if (_previous != null) return _previous.fseGetTypeSync(path, followLinks); + return super.fseGetTypeSync(path, followLinks); + } + + // _FileSystemWatcher + @override + Stream fsWatch(String path, int events, bool recursive) { + if (_fsWatch != null) return _fsWatch(path, events, recursive); + if (_previous != null) return _previous.fsWatch(path, events, recursive); + return super.fsWatch(path, events, recursive); + } + + bool fsWatchIsSupported() { + if (_fsWatchIsSupported != null) return _fsWatchIsSupported(); + if (_previous != null) return _previous.fsWatchIsSupported(); + return super.fsWatchIsSupported(); + } + + // Link + @override + Link createLink(String path) { + if (_createLink != null) return _createLink(path); + if (_previous != null) return _previous.createLink(path); + return super.createLink(path); + } +} diff --git a/tests/standalone/io/http_override_test.dart b/tests/standalone/io/http_override_test.dart new file mode 100644 index 00000000000..8edf35f1593 --- /dev/null +++ b/tests/standalone/io/http_override_test.dart @@ -0,0 +1,178 @@ +// 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:io'; + +import "package:expect/expect.dart"; + +class MyHttpClient1 implements HttpClient { + String userAgent = "MyHttpClient1"; + + MyHttpClient1(SecurityContext context); + + Duration idleTimeout; + int maxConnectionsPerHost; + bool autoUncompress; + + Future open( + String method, String host, int port, String path) => + null; + Future openUrl(String method, Uri url) => null; + Future get(String host, int port, String path) => null; + Future getUrl(Uri url) => null; + Future post(String host, int port, String path) => null; + Future postUrl(Uri url) => null; + Future put(String host, int port, String path) => null; + Future putUrl(Uri url) => null; + Future delete(String host, int port, String path) => null; + Future deleteUrl(Uri url) => null; + Future patch(String host, int port, String path) => null; + Future patchUrl(Uri url) => null; + Future head(String host, int port, String path) => null; + Future headUrl(Uri url) => null; + set authenticate(Future f(Uri url, String scheme, String realm)) {} + void addCredentials( + Uri url, String realm, HttpClientCredentials credentials) {} + set findProxy(String f(Uri url)) {} + set authenticateProxy( + Future f(String host, int port, String scheme, String realm)) {} + void addProxyCredentials( + String host, int port, String realm, HttpClientCredentials credentials) {} + set badCertificateCallback( + bool callback(X509Certificate cert, String host, int port)) {} + void close({bool force: false}) {} +} + +class MyHttpClient2 implements HttpClient { + String userAgent = "MyHttpClient2"; + + MyHttpClient2(SecurityContext context); + + Duration idleTimeout; + int maxConnectionsPerHost; + bool autoUncompress; + + Future open( + String method, String host, int port, String path) => + null; + Future openUrl(String method, Uri url) => null; + Future get(String host, int port, String path) => null; + Future getUrl(Uri url) => null; + Future post(String host, int port, String path) => null; + Future postUrl(Uri url) => null; + Future put(String host, int port, String path) => null; + Future putUrl(Uri url) => null; + Future delete(String host, int port, String path) => null; + Future deleteUrl(Uri url) => null; + Future patch(String host, int port, String path) => null; + Future patchUrl(Uri url) => null; + Future head(String host, int port, String path) => null; + Future headUrl(Uri url) => null; + set authenticate(Future f(Uri url, String scheme, String realm)) {} + void addCredentials( + Uri url, String realm, HttpClientCredentials credentials) {} + set findProxy(String f(Uri url)) {} + set authenticateProxy( + Future f(String host, int port, String scheme, String realm)) {} + void addProxyCredentials( + String host, int port, String realm, HttpClientCredentials credentials) {} + set badCertificateCallback( + bool callback(X509Certificate cert, String host, int port)) {} + void close({bool force: false}) {} +} + +class MyHttpOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext context) { + return new MyHttpClient1(context); + } +} + +HttpClient myCreateHttp1Client(SecurityContext context) { + return new MyHttpClient1(context); +} + +HttpClient myCreateHttp2Client(SecurityContext context) { + return new MyHttpClient2(context); +} + +String myFindProxyFromEnvironment(Uri url, Map environment) { + return "proxy"; +} + +withHttpOverridesTest() { + HttpOverrides.runZoned(() { + var httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient1); + Expect.equals((new MyHttpClient1(null)).userAgent, httpClient.userAgent); + }, createHttpClient: myCreateHttp1Client); + var httpClient = new HttpClient(); + Expect.isTrue(httpClient is HttpClient); + Expect.isTrue(httpClient is! MyHttpClient1); +} + +nestedWithHttpOverridesTest() { + HttpOverrides.runZoned(() { + var httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient1); + Expect.equals((new MyHttpClient1(null)).userAgent, httpClient.userAgent); + HttpOverrides.runZoned(() { + var httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient2); + Expect.equals((new MyHttpClient2(null)).userAgent, httpClient.userAgent); + }, createHttpClient: myCreateHttp2Client); + httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient1); + Expect.equals((new MyHttpClient1(null)).userAgent, httpClient.userAgent); + }, createHttpClient: myCreateHttp1Client); + var httpClient = new HttpClient(); + Expect.isTrue(httpClient is HttpClient); + Expect.isTrue(httpClient is! MyHttpClient1); + Expect.isTrue(httpClient is! MyHttpClient2); +} + +nestedDifferentOverridesTest() { + HttpOverrides.runZoned(() { + var httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient1); + Expect.equals((new MyHttpClient1(null)).userAgent, httpClient.userAgent); + HttpOverrides.runZoned(() { + var httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient1); + Expect.equals((new MyHttpClient1(null)).userAgent, httpClient.userAgent); + Expect.equals(myFindProxyFromEnvironment(null, null), + HttpClient.findProxyFromEnvironment(null)); + }, findProxyFromEnvironment: myFindProxyFromEnvironment); + httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient1); + Expect.equals((new MyHttpClient1(null)).userAgent, httpClient.userAgent); + }, createHttpClient: myCreateHttp1Client); + var httpClient = new HttpClient(); + Expect.isTrue(httpClient is HttpClient); + Expect.isTrue(httpClient is! MyHttpClient1); + Expect.isTrue(httpClient is! MyHttpClient2); +} + +zonedWithHttpOverridesTest() { + HttpOverrides.runWithHttpOverrides(() { + var httpClient = new HttpClient(); + Expect.isNotNull(httpClient); + Expect.isTrue(httpClient is MyHttpClient1); + Expect.equals((new MyHttpClient1(null)).userAgent, httpClient.userAgent); + }, new MyHttpOverrides()); +} + +main() { + withHttpOverridesTest(); + nestedWithHttpOverridesTest(); + nestedDifferentOverridesTest(); + zonedWithHttpOverridesTest(); +} diff --git a/tests/standalone/io/io_override_test.dart b/tests/standalone/io/io_override_test.dart new file mode 100644 index 00000000000..e187bd178b4 --- /dev/null +++ b/tests/standalone/io/io_override_test.dart @@ -0,0 +1,193 @@ +// 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:convert'; +import 'dart:io'; + +import "package:expect/expect.dart"; + +class DirectoryMock extends FileSystemEntity implements Directory { + final String path = "/mockdir"; + + DirectoryMock(String path); + + static DirectoryMock createDirectory(String path) => new DirectoryMock(path); + static DirectoryMock getCurrent() => new DirectoryMock(null); + static void setCurrent(String path) {} + static DirectoryMock getSystemTemp() => new DirectoryMock(null); + + Uri get uri => null; + Future create({bool recursive: false}) => null; + void createSync({bool recursive: false}) {} + Future createTemp([String prefix]) => null; + Directory createTempSync([String prefix]) => null; + Future resolveSymbolicLinks() => null; + String resolveSymbolicLinksSync() => null; + Future rename(String newPath) => null; + Directory renameSync(String newPath) => null; + Directory get absolute => null; + Stream list( + {bool recursive: false, bool followLinks: true}) => + null; + List listSync( + {bool recursive: false, bool followLinks: true}) => + null; +} + +class FileMock extends FileSystemEntity implements File { + String get path => "/mockfile"; + + FileMock(String path); + + static FileMock createFile(String path) => new FileMock(path); + + Future create({bool recursive: false}) => null; + void createSync({bool recursive: false}) {} + Future rename(String newPath) => null; + File renameSync(String newPath) => null; + Future copy(String newPath) => null; + File copySync(String newPath) => null; + Future length() => null; + int lengthSync() => null; + File get absolute => null; + Future lastAccessed() => null; + DateTime lastAccessedSync() => null; + Future setLastAccessed(DateTime time) => null; + void setLastAccessedSync(DateTime time) {} + Future lastModified() => null; + DateTime lastModifiedSync() => null; + Future setLastModified(DateTime time) => null; + void setLastModifiedSync(DateTime time) {} + Future open({FileMode mode: FileMode.READ}) => null; + RandomAccessFile openSync({FileMode mode: FileMode.READ}) => null; + Stream> openRead([int start, int end]) => null; + IOSink openWrite({FileMode mode: FileMode.WRITE, Encoding encoding: UTF8}) => + null; + Future> readAsBytes() => null; + List readAsBytesSync() => null; + Future readAsString({Encoding encoding: UTF8}) => null; + String readAsStringSync({Encoding encoding: UTF8}) => null; + Future> readAsLines({Encoding encoding: UTF8}) => null; + List readAsLinesSync({Encoding encoding: UTF8}) => null; + Future writeAsBytes(List bytes, + {FileMode mode: FileMode.WRITE, bool flush: false}) => + null; + void writeAsBytesSync(List bytes, + {FileMode mode: FileMode.WRITE, bool flush: false}) {} + Future writeAsString(String contents, + {FileMode mode: FileMode.WRITE, + Encoding encoding: UTF8, + bool flush: false}) => + null; + void writeAsStringSync(String contents, + {FileMode mode: FileMode.WRITE, + Encoding encoding: UTF8, + bool flush: false}) {} +} + +class FileStatMock implements FileStat { + final DateTime changed; + final DateTime modified; + final DateTime accessed; + final FileSystemEntityType type; + final int mode; + final int size; + + FileStatMock(); + + static Future stat(String path) { + return new Future.value(new FileStatMock()); + } + + static FileStat statSync(String path) => new FileStatMock(); + + String modeString() => null; +} + +class FileSystemEntityMock { + static Future identical(String path1, String path2) { + return new Future.value(false); + } + + static bool identicalSync(String path1, String path2) => false; + + static Future getType(String path, bool followLinks) { + return new Future.value(FileSystemEntityType.FILE); + } + + static FileSystemEntityType getTypeSync(String path, bool followLinks) { + return FileSystemEntityType.FILE; + } +} + +class FileSystemWatcherMock { + static Stream watch( + String path, int events, bool recursive) { + return null; + } + + static bool watchSupported() => false; +} + +class LinkMock extends FileSystemEntity implements Link { + LinkMock(String path); + + static createLink(String path) => new LinkMock(path); + + Future create(String target, {bool recursive: false}) => null; + void createSync(String target, {bool recursive: false}) {} + void updateSync(String target) {} + Future update(String target) => null; + Future resolveSymbolicLinks() => null; + String resolveSymbolicLinksSync() => null; + Future rename(String newPath) => null; + Link renameSync(String newPath) => null; + Link get absolute => null; + Future target() => null; + String targetSync() => null; +} + +Future ioOverridesRunTest() async { + Future f = IoOverrides.runZoned( + () async { + Expect.isTrue(new Directory("directory") is DirectoryMock); + Expect.isTrue(Directory.current is DirectoryMock); + Expect.isTrue(Directory.systemTemp is DirectoryMock); + Expect.isTrue(new File("file") is FileMock); + Expect.isTrue(await FileStat.stat("file") is FileStatMock); + Expect.isTrue(FileStat.statSync("file") is FileStatMock); + Expect.isFalse(await FileSystemEntity.identical("file", "file")); + Expect.isFalse(FileSystemEntity.identicalSync("file", "file")); + Expect.equals( + await FileSystemEntity.type("file"), FileSystemEntityType.FILE); + Expect.equals( + FileSystemEntity.typeSync("file"), FileSystemEntityType.FILE); + Expect.isFalse(FileSystemEntity.isWatchSupported); + Expect.isNull(new Directory("directory").watch()); + Expect.isTrue(new Link("link") is LinkMock); + }, + createDirectory: DirectoryMock.createDirectory, + getCurrentDirectory: DirectoryMock.getCurrent, + setCurrentDirectory: DirectoryMock.setCurrent, + getSystemTempDirectory: DirectoryMock.getSystemTemp, + createFile: FileMock.createFile, + stat: FileStatMock.stat, + statSync: FileStatMock.statSync, + fseIdentical: FileSystemEntityMock.identical, + fseIdenticalSync: FileSystemEntityMock.identicalSync, + fseGetType: FileSystemEntityMock.getType, + fseGetTypeSync: FileSystemEntityMock.getTypeSync, + fsWatch: FileSystemWatcherMock.watch, + fsWatchIsSupported: FileSystemWatcherMock.watchSupported, + createLink: LinkMock.createLink, + ); + Expect.isFalse(new Directory("directory") is DirectoryMock); + Expect.isTrue(new Directory("directory") is Directory); + await f; +} + +main() async { + await ioOverridesRunTest(); +}