Add implementations of the front end FileSystem API.

This required some small changes to the API contract.

Note that the tests use supermixins.  I'm assuming that supermixin
functionality will be available on all platforms by the time this is
needed.  If not, I will be happy to rewrite them.

R=scheglov@google.com, sigmund@google.com

Review URL: https://codereview.chromium.org/2471283002 .
This commit is contained in:
Paul Berry 2016-11-03 08:21:10 -07:00
parent 4682ea9953
commit 28b6a1f7ea
8 changed files with 643 additions and 6 deletions

View file

@ -35,6 +35,7 @@ dartdoc:third_party/pkg/dartdoc/lib
dev_compiler:pkg/dev_compiler/lib
expect:pkg/expect/lib
fixnum:third_party/pkg/fixnum/lib
front_end:pkg/front_end/lib
func:third_party/pkg/func/lib
glob:third_party/pkg/glob/lib
html:third_party/pkg/html/lib

View file

@ -31,10 +31,9 @@ abstract class FileSystem {
/// Returns a [FileSystemEntity] corresponding to the given [uri].
///
/// Uses of `..` and `.` in the URI are normalized before returning. Relative
/// paths are also converted to absolute paths.
/// Uses of `..` and `.` in the URI are normalized before returning.
///
/// If [uri] is not a `file:` URI, an [Error] will be thrown.
/// If [uri] is not an absolute `file:` URI, an [Error] will be thrown.
///
/// Does not check whether a file or folder exists at the given location.
FileSystemEntity entityForUri(Uri uri);
@ -42,6 +41,9 @@ abstract class FileSystem {
/// Abstract representation of a file system entity that may or may not exist.
///
/// Instances of this class have suitable implementations of equality tests and
/// hashCode.
///
/// Not intended to be implemented or extended by clients.
abstract class FileSystemEntity {
/// Returns the absolute normalized path represented by this file system
@ -67,7 +69,7 @@ abstract class FileSystemEntity {
/// The file is assumed to be UTF-8 encoded.
///
/// If an error occurs while attempting to read the file (e.g. because no such
/// file exists, or the entity is a directory), the future is completed with
/// an [Exception].
/// file exists, the entity is a directory, or the file is not valid UTF-8),
/// the future is completed with an [Exception].
Future<String> readAsString();
}

View file

@ -0,0 +1,100 @@
// Copyright (c) 2016, 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.
library front_end.memory_file_system;
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:path/path.dart' as p;
import 'file_system.dart';
/// Concrete implementation of [FileSystem] which performs its operations on an
/// in-memory virtual file system.
///
/// Not intended to be implemented or extended by clients.
class MemoryFileSystem implements FileSystem {
@override
final p.Context context;
final Map<String, Uint8List> _files = {};
/// The "current directory" in the in-memory virtual file system.
///
/// This is used to convert relative paths to absolute paths.
String currentDirectory;
MemoryFileSystem(this.context, this.currentDirectory);
@override
MemoryFileSystemEntity entityForPath(String path) =>
new MemoryFileSystemEntity._(
this, context.normalize(context.join(currentDirectory, path)));
@override
MemoryFileSystemEntity entityForUri(Uri uri) {
if (uri.scheme != 'file') throw new ArgumentError('File URI expected');
// Note: we don't have to verify that the URI's path is absolute, because
// URIs with non-empty schemes always have absolute paths.
return entityForPath(context.fromUri(uri));
}
}
/// Concrete implementation of [FileSystemEntity] for use by
/// [MemoryFileSystem].
class MemoryFileSystemEntity implements FileSystemEntity {
final MemoryFileSystem _fileSystem;
@override
final String path;
MemoryFileSystemEntity._(this._fileSystem, this.path);
@override
int get hashCode => path.hashCode;
@override
bool operator ==(Object other) =>
other is MemoryFileSystemEntity &&
other.path == path &&
identical(other._fileSystem, _fileSystem);
@override
Future<List<int>> readAsBytes() async {
List<int> contents = _fileSystem._files[path];
if (contents != null) {
return contents.toList();
}
throw new Exception('File does not exist');
}
@override
Future<String> readAsString() async {
List<int> contents = await readAsBytes();
return UTF8.decode(contents);
}
/// Writes the given raw bytes to this file system entity.
///
/// If no file exists, one is created. If a file exists already, it is
/// overwritten.
void writeAsBytesSync(List<int> bytes) {
_fileSystem._files[path] = new Uint8List.fromList(bytes);
}
/// Writes the given string to this file system entity.
///
/// The string is encoded as UTF-8.
///
/// If no file exists, one is created. If a file exists already, it is
/// overwritten.
void writeAsStringSync(String s) {
// Note: the return type of UTF8.encode is List<int>, but in practice it
// always returns Uint8List. We rely on that for efficiency, so that we
// don't have to make an extra copy.
_fileSystem._files[path] = UTF8.encode(s) as Uint8List;
}
}

View file

@ -0,0 +1,59 @@
// Copyright (c) 2016, 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.
library front_end.physical_file_system;
import 'dart:async';
import 'dart:io' as io;
import 'package:path/path.dart' as p;
import 'file_system.dart';
/// Concrete implementation of [FileSystem] which performs its operations using
/// I/O.
///
/// Not intended to be implemented or extended by clients.
class PhysicalFileSystem implements FileSystem {
static final PhysicalFileSystem instance = new PhysicalFileSystem._();
PhysicalFileSystem._();
@override
p.Context get context => p.context;
@override
FileSystemEntity entityForPath(String path) =>
new _PhysicalFileSystemEntity(context.normalize(context.absolute(path)));
@override
FileSystemEntity entityForUri(Uri uri) {
if (uri.scheme != 'file') throw new ArgumentError('File URI expected');
// Note: we don't have to verify that the URI's path is absolute, because
// URIs with non-empty schemes always have absolute paths.
return entityForPath(context.fromUri(uri));
}
}
/// Concrete implementation of [FileSystemEntity] for use by
/// [PhysicalFileSystem].
class _PhysicalFileSystemEntity implements FileSystemEntity {
@override
final String path;
_PhysicalFileSystemEntity(this.path);
@override
int get hashCode => path.hashCode;
@override
bool operator ==(Object other) =>
other is _PhysicalFileSystemEntity && other.path == path;
@override
Future<List<int>> readAsBytes() => new io.File(path).readAsBytes();
@override
Future<String> readAsString() => new io.File(path).readAsString();
}

View file

@ -10,10 +10,12 @@ dependencies:
path: '^1.3.9'
source_span: '^1.2.3'
dev_dependencies:
package_config: '^1.0.0'
# TODO(sigmund): update to a version constraint once we roll the latest kernel
# to the repo.
kernel: {path: ../../third_party/pkg/kernel}
package_config: '^1.0.0'
test: ^0.12.0
test_reflective_loader: ^0.1.0
# TODO(sigmund): remove once kernel is moved into the sdk repo.
dependency_overrides:
analyzer: '^0.29.0'

View file

@ -0,0 +1,259 @@
// Copyright (c) 2016, 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.
// SharedOptions=--supermixin
library front_end.test.memory_file_system_test;
import 'dart:convert';
import 'dart:io' as io;
import 'package:front_end/memory_file_system.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(MemoryFileSystemTestNative);
defineReflectiveTests(MemoryFileSystemTestPosix);
defineReflectiveTests(MemoryFileSystemTestWindows);
defineReflectiveTests(FileTest);
});
}
@reflectiveTest
class FileTest extends _BaseTestNative {
String path;
MemoryFileSystemEntity file;
setUp() {
super.setUp();
path = join(tempPath, 'file.txt');
file = fileSystem.entityForPath(path);
}
test_equals_differentPaths() {
expect(
file == fileSystem.entityForPath(join(tempPath, 'file2.txt')), isFalse);
}
test_equals_samePath() {
expect(
file == fileSystem.entityForPath(join(tempPath, 'file.txt')), isTrue);
}
test_hashCode_samePath() {
expect(file.hashCode,
fileSystem.entityForPath(join(tempPath, 'file.txt')).hashCode);
}
test_path() {
expect(file.path, path);
}
test_readAsBytes_badUtf8() async {
// A file containing invalid UTF-8 can still be read as raw bytes.
List<int> bytes = [0xc0, 0x40]; // Invalid UTF-8
file.writeAsBytesSync(bytes);
expect(await file.readAsBytes(), bytes);
}
test_readAsBytes_doesNotExist() {
expect(file.readAsBytes(), throwsException);
}
test_readAsBytes_exists() async {
var s = 'contents';
file.writeAsStringSync(s);
expect(await file.readAsBytes(), UTF8.encode(s));
}
test_readAsString_badUtf8() {
file.writeAsBytesSync([0xc0, 0x40]); // Invalid UTF-8
expect(file.readAsString(), throwsException);
}
test_readAsString_doesNotExist() {
expect(file.readAsString(), throwsException);
}
test_readAsString_exists() async {
var s = 'contents';
file.writeAsStringSync(s);
expect(await file.readAsString(), s);
}
test_readAsString_utf8() async {
file.writeAsBytesSync([0xe2, 0x82, 0xac]); // Unicode symbol, in UTF-8
expect(await file.readAsString(), '\u20ac');
}
test_writeAsBytesSync_modifyAfterRead() async {
file.writeAsBytesSync([1]);
(await file.readAsBytes())[0] = 2;
expect(await file.readAsBytes(), [1]);
}
test_writeAsBytesSync_modifyAfterWrite() async {
var bytes = [1];
file.writeAsBytesSync(bytes);
bytes[0] = 2;
expect(await file.readAsBytes(), [1]);
}
test_writeAsBytesSync_overwrite() async {
file.writeAsBytesSync([1]);
file.writeAsBytesSync([2]);
expect(await file.readAsBytes(), [2]);
}
test_writeAsStringSync_overwrite() async {
file.writeAsStringSync('first');
file.writeAsStringSync('second');
expect(await file.readAsString(), 'second');
}
test_writeAsStringSync_utf8() async {
file.writeAsStringSync('\u20ac'); // Unicode symbol
expect(await file.readAsBytes(), [0xe2, 0x82, 0xac]);
}
}
abstract class MemoryFileSystemTestMixin extends _BaseTest {
Uri tempUri;
setUp() {
super.setUp();
tempUri = fileSystem.context.toUri(tempPath);
}
test_entityForPath() {
var path = join(tempPath, 'file.txt');
expect(fileSystem.entityForPath(path).path, path);
}
test_entityForPath_absolutize() {
expect(fileSystem.entityForPath('file.txt').path,
join(fileSystem.currentDirectory, 'file.txt'));
}
test_entityForPath_normalize_dot() {
expect(fileSystem.entityForPath(join(tempPath, '.', 'file.txt')).path,
join(tempPath, 'file.txt'));
}
test_entityForPath_normalize_dotDot() {
expect(
fileSystem.entityForPath(join(tempPath, 'foo', '..', 'file.txt')).path,
join(tempPath, 'file.txt'));
}
test_entityForUri() {
expect(fileSystem.entityForUri(Uri.parse('$tempUri/file.txt')).path,
join(tempPath, 'file.txt'));
}
test_entityForUri_bareUri_absolute() {
expect(() => fileSystem.entityForUri(Uri.parse('/file.txt')),
throwsA(new isInstanceOf<Error>()));
}
test_entityForUri_bareUri_relative() {
expect(() => fileSystem.entityForUri(Uri.parse('file.txt')),
throwsA(new isInstanceOf<Error>()));
}
test_entityForUri_fileUri_relative() {
// A weird quirk of the Uri class is that it doesn't seem possible to create
// a `file:` uri with a relative path, no matter how many slashes you use or
// if you populate the fields directly. But just to be certain, try to do
// so, and make that `file:` uris with relative paths are rejected.
for (var uri in <Uri>[
new Uri(scheme: 'file', path: 'file.txt'),
Uri.parse('file:file.txt'),
Uri.parse('file:/file.txt'),
Uri.parse('file://file.txt'),
Uri.parse('file:///file.txt')
]) {
if (!uri.path.startsWith('/')) {
expect(() => fileSystem.entityForUri(uri),
throwsA(new isInstanceOf<Error>()));
}
}
}
test_entityForUri_nonFileUri() {
expect(() => fileSystem.entityForUri(Uri.parse('package:foo/bar.dart')),
throwsA(new isInstanceOf<Error>()));
}
test_entityForUri_normalize_dot() {
expect(fileSystem.entityForUri(Uri.parse('$tempUri/./file.txt')).path,
join(tempPath, 'file.txt'));
}
test_entityForUri_normalize_dotDot() {
expect(fileSystem.entityForUri(Uri.parse('$tempUri/foo/../file.txt')).path,
join(tempPath, 'file.txt'));
}
}
@reflectiveTest
class MemoryFileSystemTestNative extends _BaseTestNative
with MemoryFileSystemTestMixin {}
@reflectiveTest
class MemoryFileSystemTestPosix extends _BaseTestPosix
with MemoryFileSystemTestMixin {}
@reflectiveTest
class MemoryFileSystemTestWindows extends _BaseTestWindows
with MemoryFileSystemTestMixin {}
abstract class _BaseTest {
MemoryFileSystem get fileSystem;
String get tempPath;
String join(String path1, String path2, [String path3, String path4]);
void setUp();
}
class _BaseTestNative extends _BaseTest {
MemoryFileSystem fileSystem;
String tempPath;
String join(String path1, String path2, [String path3, String path4]) =>
pathos.join(path1, path2, path3, path4);
setUp() {
tempPath = pathos.join(io.Directory.systemTemp.path, 'test_file_system');
fileSystem =
new MemoryFileSystem(pathos.context, io.Directory.current.path);
}
}
class _BaseTestPosix extends _BaseTest {
MemoryFileSystem fileSystem;
String tempPath;
String join(String path1, String path2, [String path3, String path4]) =>
pathos.posix.join(path1, path2, path3, path4);
void setUp() {
tempPath = '/test_file_system';
fileSystem = new MemoryFileSystem(pathos.posix, '/cwd');
}
}
class _BaseTestWindows extends _BaseTest {
MemoryFileSystem fileSystem;
String tempPath;
String join(String path1, String path2, [String path3, String path4]) =>
pathos.windows.join(path1, path2, path3, path4);
void setUp() {
tempPath = r'c:\test_file_system';
fileSystem = new MemoryFileSystem(pathos.windows, r'c:\cwd');
}
}

View file

@ -0,0 +1,212 @@
// Copyright (c) 2016, 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.
// SharedOptions=--supermixin
library front_end.test.physical_file_system_test;
import 'dart:convert';
import 'dart:io' as io;
import 'package:front_end/file_system.dart';
import 'package:front_end/physical_file_system.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(PhysicalFileSystemTest);
defineReflectiveTests(FileTest);
});
}
@reflectiveTest
class FileTest extends _BaseTest {
String path;
FileSystemEntity file;
setUp() {
super.setUp();
path = p.join(tempPath, 'file.txt');
file = PhysicalFileSystem.instance.entityForPath(path);
}
test_equals_differentPaths() {
expect(
file ==
PhysicalFileSystem.instance
.entityForPath(p.join(tempPath, 'file2.txt')),
isFalse);
}
test_equals_samePath() {
expect(
file ==
PhysicalFileSystem.instance
.entityForPath(p.join(tempPath, 'file.txt')),
isTrue);
}
test_hashCode_samePath() {
expect(
file.hashCode,
PhysicalFileSystem.instance
.entityForPath(p.join(tempPath, 'file.txt'))
.hashCode);
}
test_path() {
expect(file.path, path);
}
test_readAsBytes_badUtf8() async {
// A file containing invalid UTF-8 can still be read as raw bytes.
List<int> bytes = [0xc0, 0x40]; // Invalid UTF-8
new io.File(path).writeAsBytesSync(bytes);
expect(await file.readAsBytes(), bytes);
}
test_readAsBytes_doesNotExist() {
expect(file.readAsBytes(), throwsException);
}
test_readAsBytes_exists() async {
var s = 'contents';
new io.File(path).writeAsStringSync(s);
expect(await file.readAsBytes(), UTF8.encode(s));
}
test_readAsString_badUtf8() {
new io.File(path).writeAsBytesSync([0xc0, 0x40]); // Invalid UTF-8
expect(file.readAsString(), throwsException);
}
test_readAsString_doesNotExist() {
expect(file.readAsString(), throwsException);
}
test_readAsString_exists() async {
var s = 'contents';
new io.File(path).writeAsStringSync(s);
expect(await file.readAsString(), s);
}
test_readAsString_utf8() async {
var bytes = [0xe2, 0x82, 0xac]; // Unicode symbol (in UTF-8)
new io.File(path).writeAsBytesSync(bytes);
expect(await file.readAsString(), '\u20ac');
}
}
@reflectiveTest
class PhysicalFileSystemTest extends _BaseTest {
Uri tempUri;
setUp() {
super.setUp();
tempUri = new Uri.directory(tempPath);
}
test_entityForPath() {
var path = p.join(tempPath, 'file.txt');
expect(PhysicalFileSystem.instance.entityForPath(path).path, path);
}
test_entityForPath_absolutize() {
expect(PhysicalFileSystem.instance.entityForPath('file.txt').path,
new io.File('file.txt').absolute.path);
}
test_entityForPath_normalize_dot() {
expect(
PhysicalFileSystem.instance
.entityForPath(p.join(tempPath, '.', 'file.txt'))
.path,
p.join(tempPath, 'file.txt'));
}
test_entityForPath_normalize_dotDot() {
expect(
PhysicalFileSystem.instance
.entityForPath(p.join(tempPath, 'foo', '..', 'file.txt'))
.path,
p.join(tempPath, 'file.txt'));
}
test_entityForUri() {
expect(
PhysicalFileSystem.instance
.entityForUri(Uri.parse('$tempUri/file.txt'))
.path,
p.join(tempPath, 'file.txt'));
}
test_entityForUri_bareUri_absolute() {
expect(
() => PhysicalFileSystem.instance.entityForUri(Uri.parse('/file.txt')),
throwsA(new isInstanceOf<Error>()));
}
test_entityForUri_bareUri_relative() {
expect(
() => PhysicalFileSystem.instance.entityForUri(Uri.parse('file.txt')),
throwsA(new isInstanceOf<Error>()));
}
test_entityForUri_fileUri_relative() {
// A weird quirk of the Uri class is that it doesn't seem possible to create
// a `file:` uri with a relative path, no matter how many slashes you use or
// if you populate the fields directly. But just to be certain, try to do
// so, and make that `file:` uris with relative paths are rejected.
for (var uri in <Uri>[
new Uri(scheme: 'file', path: 'file.txt'),
Uri.parse('file:file.txt'),
Uri.parse('file:/file.txt'),
Uri.parse('file://file.txt'),
Uri.parse('file:///file.txt')
]) {
if (!uri.path.startsWith('/')) {
expect(() => PhysicalFileSystem.instance.entityForUri(uri),
throwsA(new isInstanceOf<Error>()));
}
}
}
test_entityForUri_nonFileUri() {
expect(
() => PhysicalFileSystem.instance
.entityForUri(Uri.parse('package:foo/bar.dart')),
throwsA(new isInstanceOf<Error>()));
}
test_entityForUri_normalize_dot() {
expect(
PhysicalFileSystem.instance
.entityForUri(Uri.parse('$tempUri/./file.txt'))
.path,
p.join(tempPath, 'file.txt'));
}
test_entityForUri_normalize_dotDot() {
expect(
PhysicalFileSystem.instance
.entityForUri(Uri.parse('$tempUri/foo/../file.txt'))
.path,
p.join(tempPath, 'file.txt'));
}
}
class _BaseTest {
io.Directory tempDirectory;
String tempPath;
setUp() {
tempDirectory = io.Directory.systemTemp.createTempSync('test_file_system');
tempPath = tempDirectory.absolute.path;
}
tearDown() {
tempDirectory.deleteSync(recursive: true);
}
}

View file

@ -53,6 +53,8 @@ compiler/tool/*: SkipByDesign # Only meant to run on vm
front_end/tool/*: SkipByDesign # Only meant to run on vm
lookup_map/test/version_check_test: SkipByDesign # Only meant to run in vm.
typed_data/test/typed_buffers_test/01: Fail # Not supporting Int64List, Uint64List.
front_end/test/memory_file_system_test: CompileTimeError # Issue 23773
front_end/test/physical_file_system_test: SkipByDesign # Uses dart:io
[ $compiler == dart2js && $builder_tag != dart2js_analyzer ]
analyzer/test/*: Skip # Issue 26813