Skip to content

Refactor fake #245

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkgs/io_file/lib/io_file.dart
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@
// 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.

export 'src/exceptions.dart';
export 'src/file_system.dart';
export 'src/vm_file_system_property.dart'
if (dart.library.html) 'src/web_file_system_property.dart';
3 changes: 1 addition & 2 deletions pkgs/io_file/lib/posix_file_system.dart
Original file line number Diff line number Diff line change
@@ -2,5 +2,4 @@
// 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.

export 'src/vm_posix_file_system.dart'
if (dart.library.html) 'src/web_posix_file_system.dart';
export 'src/posix_file_system.dart';
66 changes: 66 additions & 0 deletions pkgs/io_file/lib/src/exceptions.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright (c) 2025, 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.

class SystemCallError {
final String systemCall;
final int errorCode;
final String message;

const SystemCallError(this.systemCall, this.errorCode, this.message);
}

class IOFileException implements Exception {
final String message;

/// The file system path on which the error occurred.
///
/// Can be `null` if the exception does not relate directly
/// to a file system path.
final String? path;

/// The underlying OS error.
///
/// Can be `null` if the exception is not raised due to an OS error.
final SystemCallError? systemCall;

const IOFileException(this.message, {this.path, this.systemCall});

String _toStringHelper(String className) {
final sb = StringBuffer('$className: $message');
if (path != null) {
sb.write(', path = "$path"');
}
if (systemCall != null) {
sb.write(
' (${systemCall!.systemCall}: ${systemCall!.message}, '
'errno=${systemCall!.errorCode} )',
);
}
return sb.toString();
}

@override
String toString() => _toStringHelper('IOFileException');
}

class PathAccessException extends IOFileException {
const PathAccessException(super.message, {super.path, super.systemCall});

@override
String toString() => _toStringHelper('PathAccessException');
}

class PathExistsException extends IOFileException {
const PathExistsException(super.message, {super.path, super.systemCall});

@override
String toString() => _toStringHelper('PathExistsException');
}

class PathNotFoundException extends IOFileException {
const PathNotFoundException(super.message, {super.path, super.systemCall});

@override
String toString() => _toStringHelper('PathNotFoundException');
}
211 changes: 211 additions & 0 deletions pkgs/io_file/lib/src/fake_posix_file_system.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import 'dart:convert';

import 'dart:typed_data';

import 'package:path/path.dart' as p;

import 'exceptions.dart';
import 'file_system.dart';
import 'posix_file_system.dart';

sealed class _Entity {}

class _File extends _Entity {
_File();
}

class _Directory extends _Entity {
final children = <String, _Entity>{};

@override
String toString() => '<Directory children=$children>';
}

class _FakeMetadata implements PosixMetadata {
@override
DateTime get access => throw UnimplementedError();

@override
DateTime? get creation => throw UnimplementedError();

@override
bool get isDirectory => this.type == FileSystemType.directory;

@override
bool get isFile => throw UnimplementedError();

@override
bool? get isHidden => throw UnimplementedError();

@override
bool get isLink => throw UnimplementedError();

@override
DateTime get modification => throw UnimplementedError();

@override
int get size => throw UnimplementedError();

@override
final FileSystemType type;

_FakeMetadata(this.type);

@override
int get accessedTimeNanos => throw UnimplementedError();

@override
int? get creationTimeNanos => throw UnimplementedError();

@override
int get modificationTimeNanos => throw UnimplementedError();
}

final class FakePosixFileSystem extends PosixFileSystem {
final context = p.Context(style: p.Style.posix);
final root = _Directory();
final tmp = _Directory();

FakePosixFileSystem() {
print('Reset!');
root.children['tmp'] = tmp;
}

_Entity? _findComponents(List<String> components) {
_Entity e = root;
for (var child in components.skip(1)) {
print('$child => $e');
if (e is _Directory) {
if (!e.children.containsKey(child)) return null;
e = e.children[child]!;
} else {
return null;
}
}
return e;
}

_Entity? _findEntity(String path) {
path = context.absolute(path);
return _findComponents(p.split(path));
}

_Entity? _upToLast(String path) {
path = context.absolute(path);
return _findComponents(p.split(p.dirname(path)));
}

@override
void createDirectory(String path) {
print('createDirectory($path)');
if (_findEntity(path) != null) {
throw PathExistsException(
'create directory failed',
path: path,
systemCall: const SystemCallError('XXX', 17, 'XXX'),
);
}
path = context.absolute(path);
final base = context.basename(path);

final parent = _upToLast(path);
print('parent $parent');
if (parent is _Directory) {
parent.children[base] = _Directory();
} else if (parent == null) {
throw PathNotFoundException(
'create directory failed',
path: path,
systemCall: const SystemCallError('XXX', 2, 'XXX'),
);
} else {
throw UnsupportedError(path);
}
}

@override
String createTemporaryDirectory({String? parent, String? prefix}) {
final parentDir = parent ?? '/tmp';

String path = p.join(parentDir, prefix);
createDirectory(path);
return path;
}

@override
Metadata metadata(String path) {
final e = _findEntity(path);
return _FakeMetadata(switch (e) {
_Directory() => FileSystemType.directory,
_File() => FileSystemType.file,
_ => FileSystemType.unknown,
});
}

@override
Uint8List readAsBytes(String path) {
// TODO: implement readAsBytes
throw UnimplementedError();
}

@override
void removeDirectory(String path) {
// TODO: implement removeDirectory
}

@override
void removeDirectoryTree(String path) {
final parent = _upToLast(path);
final base = context.basename(path);

if (parent is _Directory) {
if (parent.children.containsKey(base)) {
// Check that it is a directory.
parent.children.remove(base);
} else {
throw UnsupportedError('XXX');
}
} else {
throw UnsupportedError('XXX');
}
}

@override
void rename(String oldPath, String newPath) {
// TODO: implement rename
}

@override
bool same(String path1, String path2) {
// TODO: implement same
throw UnimplementedError();
}

@override
void writeAsBytes(
String path,
Uint8List data, [
WriteMode mode = WriteMode.failExisting,
]) {
// TODO: implement writeAsBytes
}

@override
void writeAsString(
String path,
String contents, [
WriteMode mode = WriteMode.failExisting,
Encoding encoding = utf8,
String? lineTerminator,
]) {
final parent = _upToLast(path);
final base = context.basename(path);

if (parent is _Directory) {
// Do something with the file contents.
parent.children[base] = _File();
} else {
throw UnsupportedError('XXX');
}
}
}
2 changes: 1 addition & 1 deletion pkgs/io_file/lib/src/file_system.dart
Original file line number Diff line number Diff line change
@@ -132,7 +132,7 @@ class WriteMode {
///
/// TODO(brianquinlan): Far now, this class is not meant to be implemented,
/// extended outside of this package. Clarify somewhere that people implementing
/// this class should reach out to be.
/// this class should reach out to me.
@sealed
abstract class FileSystem {
/// Create a directory at the given path.
Original file line number Diff line number Diff line change
@@ -11,9 +11,11 @@ import 'dart:typed_data';
import 'package:ffi/ffi.dart' as ffi;
import 'package:path/path.dart' as p;

import 'exceptions.dart';
import 'file_system.dart';
import 'internal_constants.dart';
import 'libc.dart' as libc;
import 'posix_file_system.dart';

/// The default `mode` to use with `open` calls that may create a file.
const _defaultMode = 438; // => 0666 => rw-rw-rw-
@@ -26,23 +28,24 @@ const _nanosecondsPerSecond = 1000000000;
bool _isDotOrDotDot(Pointer<Char> s) => // ord('.') == 46
s[0] == 46 && ((s[1] == 0) || (s[1] == 46 && s[2] == 0));

Exception _getError(int err, String message, String path) {
Exception _getError(int err, String message, String path, String systemCall) {
// TODO(brianquinlan): In the long-term, do we need to avoid exceptions that
// are part of `dart:io`? Can we move those exceptions into a different
// namespace?
final osError = io.OSError(
libc.strerror(err).cast<ffi.Utf8>().toDartString(),
final systemError = SystemCallError(
systemCall,
err,
libc.strerror(err).cast<ffi.Utf8>().toDartString(),
);

if (err == libc.EPERM || err == libc.EACCES) {
return io.PathAccessException(path, osError, message);
return PathAccessException(message, path: path, systemCall: systemError);
} else if (err == libc.EEXIST) {
return io.PathExistsException(path, osError, message);
return PathExistsException(message, path: path, systemCall: systemError);
} else if (err == libc.ENOENT) {
return io.PathNotFoundException(path, osError, message);
return PathNotFoundException(message, path: path, systemCall: systemError);
} else {
return io.FileSystemException(message, path, osError);
return IOFileException(message, path: path, systemCall: systemError);
}
}

@@ -56,7 +59,7 @@ int _tempFailureRetry(int Function() f) {
}

/// Information about a directory, link, etc. stored in the [PosixFileSystem].
final class PosixMetadata implements Metadata {
final class NativePosixMetadata implements PosixMetadata {
/// The `st_mode` field of the POSIX stat struct.
///
/// See [stat.h](https://pubs.opengroup.org/onlinepubs/009696799/basedefs/sys/stat.h.html)
@@ -67,27 +70,13 @@ final class PosixMetadata implements Metadata {
@override
final int size;

/// The time that the file system object was last accessed in nanoseconds
/// since the epoch.
///
/// Access time is updated when the object is read or modified.
///
/// The resolution of the access time varies by platform and file system.
@override
final int accessedTimeNanos;

/// The time that the file system object was created in nanoseconds since the
/// epoch.
///
/// This will always be `null` on Android and Linux.
///
/// The resolution of the creation time varies by platform and file system.
@override
final int? creationTimeNanos;

/// The time that the file system object was last modified in nanoseconds
/// since the epoch.
///
/// The resolution of the modification time varies by platform and file
/// system.
@override
final int modificationTimeNanos;

int get _fmt => mode & libc.S_IFMT;
@@ -149,7 +138,7 @@ final class PosixMetadata implements Metadata {
return null;
}

PosixMetadata._(
NativePosixMetadata._(
this.mode,
this._flags,
this.size,
@@ -158,15 +147,15 @@ final class PosixMetadata implements Metadata {
this.modificationTimeNanos,
);

/// Construct [PosixMetadata] from data returned by the `stat` system call.
factory PosixMetadata.fromFileAttributes({
/// Construct [NativePosixMetadata] from data returned by the `stat` system call.
factory NativePosixMetadata.fromFileAttributes({
required int mode,
int flags = 0,
int size = 0,
int accessedTimeNanos = 0,
int? creationTimeNanos,
int modificationTimeNanos = 0,
}) => PosixMetadata._(
}) => NativePosixMetadata._(
mode,
flags,
size,
@@ -177,7 +166,7 @@ final class PosixMetadata implements Metadata {

@override
bool operator ==(Object other) =>
other is PosixMetadata &&
other is NativePosixMetadata &&
mode == other.mode &&
_flags == other._flags &&
size == other.size &&
@@ -210,19 +199,19 @@ external int write(int fd, Pointer<Uint8> buf, int count);

/// A [FileSystem] implementation for POSIX systems (e.g. Android, iOS, Linux,
/// macOS).
final class PosixFileSystem extends FileSystem {
final class NativePosixFileSystem extends PosixFileSystem {
@override
bool same(String path1, String path2) => ffi.using((arena) {
final stat1 = arena<libc.Stat>();
if (libc.stat(path1.toNativeUtf8(allocator: arena).cast(), stat1) == -1) {
final errno = libc.errno;
throw _getError(errno, 'stat failed', path1);
throw _getError(errno, 'stat failed', path1, 'stat');
}

final stat2 = arena<libc.Stat>();
if (libc.stat(path2.toNativeUtf8(allocator: arena).cast(), stat2) == -1) {
final errno = libc.errno;
throw _getError(errno, 'stat failed', path2);
throw _getError(errno, 'stat failed', path2, 'stat');
}

return (stat1.ref.st_ino == stat2.ref.st_ino) &&
@@ -237,7 +226,7 @@ final class PosixFileSystem extends FileSystem {
) ==
-1) {
final errno = libc.errno;
throw _getError(errno, 'create directory failed', path);
throw _getError(errno, 'create directory failed', path, 'mkdir');
}
});

@@ -252,21 +241,21 @@ final class PosixFileSystem extends FileSystem {
);
if (path == nullptr) {
final errno = libc.errno;
throw _getError(errno, 'mkdtemp failed', template);
throw _getError(errno, 'mkdtemp failed', template, 'mkdtemp');
}
return path.cast<ffi.Utf8>().toDartString();
});

@override
PosixMetadata metadata(String path) => ffi.using((arena) {
NativePosixMetadata metadata(String path) => ffi.using((arena) {
final stat = arena<libc.Stat>();

if (libc.lstat(path.toNativeUtf8(allocator: arena).cast(), stat) == -1) {
final errno = libc.errno;
throw _getError(errno, 'stat failed', path);
throw _getError(errno, 'stat failed', path, 'lstat');
}

return PosixMetadata.fromFileAttributes(
return NativePosixMetadata.fromFileAttributes(
mode: stat.ref.st_mode,
flags: stat.ref.st_flags,
size: stat.ref.st_size,
@@ -291,7 +280,7 @@ final class PosixFileSystem extends FileSystem {
) ==
-1) {
final errno = libc.errno;
throw _getError(errno, 'remove directory failed', path);
throw _getError(errno, 'remove directory failed', path, 'unlinkat');
}
});

@@ -308,13 +297,13 @@ final class PosixFileSystem extends FileSystem {
);
if (fd == -1) {
final errno = libc.errno;
throw _getError(errno, 'openat failed', path);
throw _getError(errno, 'openat failed', path, 'openat');
}
try {
final dir = libc.fdopendir(fd);
if (dir == nullptr) {
final errno = libc.errno;
throw _getError(errno, 'fdopendir failed', path);
throw _getError(errno, 'fdopendir failed', path, 'fdopendir');
}
try {
// `readdir` returns `NULL` but leaves `errno` unchanged if the end of
@@ -344,7 +333,7 @@ final class PosixFileSystem extends FileSystem {
/// DT_UNKNOWN.
if (libc.fstatat(fd, child, stat, libc.AT_SYMLINK_NOFOLLOW) == -1) {
final errno = libc.errno;
throw _getError(errno, 'fstatat failed', childPath);
throw _getError(errno, 'fstatat failed', childPath, 'fstatat');
}
type =
stat.ref.st_mode & libc.S_IFMT == libc.S_IFDIR
@@ -356,19 +345,19 @@ final class PosixFileSystem extends FileSystem {
} else {
if (libc.unlinkat(fd, child, 0) == -1) {
final errno = libc.errno;
throw _getError(errno, 'unlinkat failed', childPath);
throw _getError(errno, 'unlinkat failed', childPath, 'unlinkat');
}
}
libc.errno = 0;
dirent = libc.readdir(dir);
}
if (libc.errno != 0) {
final errno = libc.errno;
throw _getError(errno, 'readdir failed', path);
throw _getError(errno, 'readdir failed', path, 'readdir');
}
if (libc.unlinkat(parentfd, name.cast(), libc.AT_REMOVEDIR) == -1) {
final errno = libc.errno;
throw _getError(errno, 'unlinkat failed', path);
throw _getError(errno, 'unlinkat failed', path, 'unlinkat');
}
} finally {
libc.closedir(dir);
@@ -396,7 +385,7 @@ final class PosixFileSystem extends FileSystem {
) !=
0) {
final errno = libc.errno;
throw _getError(errno, 'rename failed', oldPath);
throw _getError(errno, 'rename failed', oldPath, 'rename');
}
});

@@ -411,7 +400,7 @@ final class PosixFileSystem extends FileSystem {
);
if (fd == -1) {
final errno = libc.errno;
throw _getError(errno, 'open failed', path);
throw _getError(errno, 'open failed', path, 'open');
}
try {
final stat = arena<libc.Stat>();
@@ -439,7 +428,7 @@ final class PosixFileSystem extends FileSystem {
switch (r) {
case -1:
final errno = libc.errno;
throw _getError(errno, 'read failed', path);
throw _getError(errno, 'read failed', path, 'read');
case 0:
return builder.takeBytes();
default:
@@ -467,7 +456,7 @@ final class PosixFileSystem extends FileSystem {
switch (r) {
case -1:
final errno = libc.errno;
throw _getError(errno, 'read failed', path);
throw _getError(errno, 'read failed', path, 'read');
case 0:
return buffer.asTypedList(
bufferOffset,
@@ -524,7 +513,7 @@ final class PosixFileSystem extends FileSystem {
try {
if (fd == -1) {
final errno = libc.errno;
throw _getError(errno, 'open failed', path);
throw _getError(errno, 'open failed', path, 'open');
}

ffi.using((arena) {
@@ -541,7 +530,7 @@ final class PosixFileSystem extends FileSystem {
);
if (w == -1) {
final errno = libc.errno;
throw _getError(errno, 'write failed', path);
throw _getError(errno, 'write failed', path, 'write');
}
remaining -= w;
buffer += w;
22 changes: 22 additions & 0 deletions pkgs/io_file/lib/src/posix_file_system.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import '../io_file.dart';

abstract class PosixMetadata implements Metadata {
int get accessedTimeNanos;

/// The time that the file system object was created in nanoseconds since the
/// epoch.
///
/// This will always be `null` on Android and Linux.
///
/// The resolution of the creation time varies by platform and file system.
int? get creationTimeNanos;

/// The time that the file system object was last modified in nanoseconds
/// since the epoch.
///
/// The resolution of the modification time varies by platform and file
/// system.
int get modificationTimeNanos;
}

abstract class PosixFileSystem extends FileSystem {}
4 changes: 2 additions & 2 deletions pkgs/io_file/lib/src/vm_file_system_property.dart
Original file line number Diff line number Diff line change
@@ -5,9 +5,9 @@
import 'dart:io';

import 'file_system.dart';
import 'vm_posix_file_system.dart';
import 'native_posix_file_system.dart';
import 'vm_windows_file_system.dart';

/// Return the default [FileSystem] for the current platform.
FileSystem get fileSystem =>
Platform.isWindows ? WindowsFileSystem() : PosixFileSystem();
Platform.isWindows ? WindowsFileSystem() : NativePosixFileSystem();
134 changes: 107 additions & 27 deletions pkgs/io_file/lib/src/vm_windows_file_system.dart
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ import 'package:ffi/ffi.dart' as ffi;
import 'package:path/path.dart' as p;
import 'package:win32/win32.dart' as win32;

import 'exceptions.dart';
import 'file_system.dart';
import 'internal_constants.dart';

@@ -52,8 +53,19 @@ String _formatMessage(int errorCode) {
}
}

Exception _getError(int errorCode, String message, [String? path]) {
final osError = io.OSError(_formatMessage(errorCode), errorCode);
// FileSystemException: unable to delete directory (OS Error: no such file or directory, errno=2): /tmp/somedir
// FileSystemException: unable to delete directory, path="/tmp/somedir" (unlinkat: 2, no such file or directory)
Exception _getError(
String message,
int errorCode,
String systemCall, [
String? path,
]) {
final systemError = SystemCallError(
systemCall,
errorCode,
_formatMessage(errorCode),
);

if (path != null) {
switch (errorCode) {
@@ -65,10 +77,18 @@ Exception _getError(int errorCode, String message, [String? path]) {
case win32.ERROR_LOCK_VIOLATION:
case win32.ERROR_NETWORK_ACCESS_DENIED:
case win32.ERROR_DRIVE_LOCKED:
return io.PathAccessException(path, osError, message);
return PathAccessException(
message,
path: path,
systemCall: systemError,
);
case win32.ERROR_FILE_EXISTS:
case win32.ERROR_ALREADY_EXISTS:
return io.PathExistsException(path, osError, message);
return PathExistsException(
message,
path: path,
systemCall: systemError,
);
case win32.ERROR_FILE_NOT_FOUND:
case win32.ERROR_PATH_NOT_FOUND:
case win32.ERROR_INVALID_DRIVE:
@@ -77,12 +97,16 @@ Exception _getError(int errorCode, String message, [String? path]) {
case win32.ERROR_BAD_NETPATH:
case win32.ERROR_BAD_NET_NAME:
case win32.ERROR_BAD_PATHNAME:
return io.PathNotFoundException(path, osError, message);
return PathNotFoundException(
message,
path: path,
systemCall: systemError,
);
default:
return io.FileSystemException(message, path, osError);
return IOFileException(message, path: path, systemCall: systemError);
}
} else {
return io.FileSystemException(message, path, osError);
return IOFileException(message, path: path, systemCall: systemError);
}
}

@@ -257,7 +281,12 @@ final class WindowsFileSystem extends FileSystem {
if (win32.CreateDirectory(path.toNativeUtf16(allocator: arena), nullptr) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'create directory failed', path);
throw _getError(
'create directory failed',
errorCode,
'CreateDirectory',
path,
);
}
});

@@ -279,7 +308,12 @@ final class WindowsFileSystem extends FileSystem {
if (win32.RemoveDirectory(path.toNativeUtf16(allocator: arena)) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'remove directory failed', path);
throw _getError(
'remove directory failed',
errorCode,
'RemoveDirectory',
path,
);
}
});

@@ -297,7 +331,12 @@ final class WindowsFileSystem extends FileSystem {

if (findHandle == win32.INVALID_HANDLE_VALUE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'FindFirstFile failed', path);
throw _getError(
'removeDirectoryTree failed',
errorCode,
'FindFirstFile',
path,
);
}

do {
@@ -316,16 +355,22 @@ final class WindowsFileSystem extends FileSystem {
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(
'removeDirectoryTree failed',
errorCode,
'RemoveDirectory failed for link',
'RemoveDirectory',
fullPath,
);
}
} else {
if (win32.DeleteFile(fullPath.toNativeUtf16(allocator: arena)) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'DeleteFile failed for link', fullPath);
throw _getError(
'removeDirectoryTree failed',
errorCode,
'DeleteFile',
fullPath,
);
}
}
} else if ((attributes & win32.FILE_ATTRIBUTE_DIRECTORY) != 0) {
@@ -334,20 +379,35 @@ final class WindowsFileSystem extends FileSystem {
if (win32.DeleteFile(fullPath.toNativeUtf16(allocator: arena)) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'DeleteFile failed', fullPath);
throw _getError(
'removeDirectoryTree failed',
errorCode,
'DeleteFile',
fullPath,
);
}
}
} while (win32.FindNextFile(findHandle, findData) != win32.FALSE);

final errorCode = win32.GetLastError();
if (errorCode != win32.ERROR_NO_MORE_FILES) {
throw _getError(errorCode, 'FindNextFile failed', path);
throw _getError(
'removeDirectoryTree failed',
errorCode,
'FindNextFile',
path,
);
}

if (win32.RemoveDirectory(path.toNativeUtf16(allocator: arena)) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'remove directory failed', path);
throw _getError(
'removeDirectoryTree failed',
errorCode,
'RemoveDirectory',
path,
);
}
});

@@ -362,7 +422,7 @@ final class WindowsFileSystem extends FileSystem {
) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'rename failed', oldPath);
throw _getError('rename failed', errorCode, 'MoveFileEx', oldPath);
}
});

@@ -407,7 +467,12 @@ final class WindowsFileSystem extends FileSystem {
) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'set metadata failed', path);
throw _getError(
'set metadata failed',
errorCode,
'GetFileAttributesEx',
path,
);
}
attributes = fileInfo.ref.dwFileAttributes;
} else {
@@ -456,7 +521,12 @@ final class WindowsFileSystem extends FileSystem {
}
if (win32.SetFileAttributes(nativePath, attributes) == win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'set metadata failed', path);
throw _getError(
'set metadata failed',
errorCode,
'SetFileAttributes',
path,
);
}
});

@@ -473,7 +543,12 @@ final class WindowsFileSystem extends FileSystem {
) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'metadata failed', path);
throw _getError(
'metadata failed',
errorCode,
'GetFileAttributesEx',
path,
);
}
final info = fileInfo.ref;
final attributes = info.dwFileAttributes;
@@ -530,7 +605,7 @@ final class WindowsFileSystem extends FileSystem {
);
if (f == win32.INVALID_HANDLE_VALUE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'open failed', path);
throw _getError('open failed', errorCode, 'CreateFile', path);
}
try {
// The result of `GetFileSize` is not defined for non-seeking devices
@@ -574,7 +649,7 @@ final class WindowsFileSystem extends FileSystem {
errorCode == win32.ERROR_SUCCESS) {
return builder.takeBytes();
}
throw _getError(errorCode, 'read failed', path);
throw _getError('read failed', errorCode, 'ReadFile', path);
}

if (bytesRead.value == 0) {
@@ -605,7 +680,7 @@ final class WindowsFileSystem extends FileSystem {
) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'read failed', path);
throw _getError('read failed', errorCode, 'ReadFile', path);
}
bufferOffset += bytesRead.value;
if (bytesRead.value == 0) {
@@ -651,13 +726,18 @@ final class WindowsFileSystem extends FileSystem {
);
if (h == win32.INVALID_HANDLE_VALUE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'CreateFile failed', path);
throw _getError('same failed', errorCode, 'CreateFile', path);
}
try {
final info = arena<win32.BY_HANDLE_FILE_INFORMATION>();
if (win32.GetFileInformationByHandle(h, info) == win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'GetFileInformationByHandle failed', path);
throw _getError(
'same failed',
errorCode,
'GetFileInformationByHandle',
path,
);
}
return info.ref;
} finally {
@@ -673,7 +753,7 @@ final class WindowsFileSystem extends FileSystem {
final length = win32.GetTempPath2(maxLength, buffer);
if (length == 0) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'GetTempPath failed');
throw _getError('temporaryDirectory failed', errorCode, 'GetTempPath2');
}
return p.canonicalize(buffer.toDartString());
} finally {
@@ -710,7 +790,7 @@ final class WindowsFileSystem extends FileSystem {
);
if (f == win32.INVALID_HANDLE_VALUE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'open failed', path);
throw _getError('write failed', errorCode, 'open failed', path);
}

try {
@@ -731,7 +811,7 @@ final class WindowsFileSystem extends FileSystem {
) ==
win32.FALSE) {
final errorCode = win32.GetLastError();
throw _getError(errorCode, 'write failed', path);
throw _getError('write failed', errorCode, 'WriteFile', path);
}

remaining -= bytesWritten.value;
7 changes: 4 additions & 3 deletions pkgs/io_file/lib/src/web_file_system_property.dart
Original file line number Diff line number Diff line change
@@ -2,9 +2,10 @@
// 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 'fake_posix_file_system.dart';
import 'file_system.dart';

FileSystem? _fileSystem;

/// Return the default [FileSystem] for the current platform.
FileSystem get fileSystem {
throw UnsupportedError('fileSystem');
}
FileSystem get fileSystem => _fileSystem ??= FakePosixFileSystem();
72 changes: 0 additions & 72 deletions pkgs/io_file/lib/src/web_posix_file_system.dart

This file was deleted.

59 changes: 37 additions & 22 deletions pkgs/io_file/test/create_directory_test.dart
Original file line number Diff line number Diff line change
@@ -2,86 +2,101 @@
// 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.

@TestOn('vm')
library;

import 'dart:io';

import 'package:errno/errno.dart';
import 'package:io_file/io_file.dart';
import 'package:io_file/src/fake_posix_file_system.dart';
import 'package:io_file/windows_file_system.dart';
import 'package:test/test.dart';
import 'package:win32/win32.dart' as win32;

import 'errors.dart' as errors;
import 'test_utils.dart';
import 'test_utils_self.dart' show SelfTestUtils;

void main() {
void testDirectory(FileSystem fs, TestUtils testUtils) {
group('createDirectory', () {
late String tmp;

setUp(() => tmp = createTemp('createDirectory'));
setUp(() => tmp = testUtils.createTestDirectory('createDirectory'));

tearDown(() => deleteTemp(tmp));
tearDown(() => testUtils.deleteDirectoryTree(tmp));

//TODO(brianquinlan): test with a very long path.

test('success', () {
final path = '$tmp/dir';

fileSystem.createDirectory(path);
expect(FileSystemEntity.isDirectorySync(path), isTrue);
fs.createDirectory(path);
expect(testUtils.isDir(path), isTrue);
});

test('create in non-existent directory', () {
final path = '$tmp/foo/dir';

expect(
() => fileSystem.createDirectory(path),
() => fs.createDirectory(path),
throwsA(
isA<PathNotFoundException>()
.having((e) => e.message, 'message', 'create directory failed')
.having(
(e) => e.osError?.errorCode,
(e) => e.systemCall?.errorCode,
'errorCode',
Platform.isWindows ? win32.ERROR_PATH_NOT_FOUND : errors.enoent,
fs is WindowsFileSystem
? WindowsErrors.pathNotFound
: errors.enoent,
),
),
);
});

test('create over existing directory', () {
final path = '$tmp/dir';
Directory(path).createSync();
testUtils.createDirectory(path);

expect(
() => fileSystem.createDirectory(path),
() => fs.createDirectory(path),
throwsA(
isA<PathExistsException>()
.having((e) => e.message, 'message', 'create directory failed')
.having(
(e) => e.osError?.errorCode,
(e) => e.systemCall?.errorCode,
'errorCode',
Platform.isWindows ? win32.ERROR_ALREADY_EXISTS : errors.eexist,
fs is WindowsFileSystem
? WindowsErrors.alreadyExists
: errors.eexist,
),
),
);
});

test('create over existing file', () {
final path = '$tmp/file';
File(path).createSync();
testUtils.createTextFile(path, 'Hello World!');

expect(
() => fileSystem.createDirectory(path),
() => fs.createDirectory(path),
throwsA(
isA<PathExistsException>()
.having((e) => e.message, 'message', 'create directory failed')
.having(
(e) => e.osError?.errorCode,
(e) => e.systemCall?.errorCode,
'errorCode',
Platform.isWindows ? win32.ERROR_ALREADY_EXISTS : errors.eexist,
fs is WindowsFileSystem
? WindowsErrors.alreadyExists
: errors.eexist,
),
),
);
});
});
}

void main() {
group('default', () {
testDirectory(fileSystem, testUtils());
});

group('fake', () {
final fs = FakePosixFileSystem();
testDirectory(fs, SelfTestUtils(fs));
});
}
20 changes: 10 additions & 10 deletions pkgs/io_file/test/errors.dart
Original file line number Diff line number Diff line change
@@ -2,21 +2,21 @@
// 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 'dart:io';
import 'package:errno/errno.dart';

int get eaccess => Platform.isMacOS ? DarwinErrors.eacces : LinuxErrors.eacces;
// true

int get eexist => Platform.isMacOS ? DarwinErrors.eexist : LinuxErrors.eexist;
int get eaccess => true ? DarwinErrors.eacces : LinuxErrors.eacces;

int get eisdir => Platform.isMacOS ? DarwinErrors.eisdir : LinuxErrors.eisdir;
int get eexist => true ? DarwinErrors.eexist : LinuxErrors.eexist;

int get enoent => Platform.isMacOS ? DarwinErrors.enoent : LinuxErrors.enoent;
int get eisdir => true ? DarwinErrors.eisdir : LinuxErrors.eisdir;

int get enotdir =>
Platform.isMacOS ? DarwinErrors.enotdir : LinuxErrors.enotdir;
int get enoent => true ? DarwinErrors.enoent : LinuxErrors.enoent;

int get enotempty =>
Platform.isMacOS ? DarwinErrors.enotempty : LinuxErrors.enotempty;
int get enotdir => true ? DarwinErrors.enotdir : LinuxErrors.enotdir;

int get eperm => Platform.isMacOS ? DarwinErrors.eperm : LinuxErrors.eperm;
int get enotempty => true ? DarwinErrors.enotempty : LinuxErrors.enotempty;

int get eperm => true ? DarwinErrors.eperm : LinuxErrors.eperm;
2 changes: 1 addition & 1 deletion pkgs/io_file/test/metadata_apple_test.dart
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ import 'dart:ffi';
import 'dart:io';

import 'package:ffi/ffi.dart';
import 'package:io_file/src/vm_posix_file_system.dart';
import 'package:io_file/src/native_posix_file_system.dart';
import 'package:stdlibc/stdlibc.dart' as stdlibc;
import 'package:test/test.dart';

17 changes: 16 additions & 1 deletion pkgs/io_file/test/test_utils.dart
Original file line number Diff line number Diff line change
@@ -2,13 +2,17 @@
// 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 'dart:math';
import 'dart:typed_data';
export 'test_utils_self.dart'
if (dart.library.io) 'test_utils_native.dart'
show testUtils;

/*
String createTemp(String testName) =>
Directory.systemTemp.createTempSync(testName).absolute.path;
void deleteTemp(String path) => Directory(path).deleteSync(recursive: true);
*/

Uint8List randomUint8List(int length, {int? seed}) {
final random = Random(seed);
@@ -18,3 +22,14 @@ Uint8List randomUint8List(int length, {int? seed}) {
}
return l;
}

abstract interface class TestUtils {
String createTestDirectory(String testName);
void deleteDirectoryTree(String path);

bool isDir(String path);

void createDirectory(String path);

void createTextFile(String path, String s);
}
24 changes: 24 additions & 0 deletions pkgs/io_file/test/test_utils_native.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import 'dart:io';

import 'test_utils.dart';

class NativeTestUtils implements TestUtils {
@override
String createTestDirectory(String testName) =>
Directory.systemTemp.createTempSync(testName).absolute.path;

@override
void deleteDirectoryTree(String path) =>
Directory(path).deleteSync(recursive: true);

@override
bool isDir(String path) => FileSystemEntity.isDirectorySync(path);

@override
void createDirectory(String path) => Directory(path).createSync();

@override
void createTextFile(String path, String s) => File(path).writeAsStringSync(s);
}

TestUtils testUtils() => NativeTestUtils();
33 changes: 33 additions & 0 deletions pkgs/io_file/test/test_utils_self.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:io_file/io_file.dart';

import 'test_utils.dart';

class SelfTestUtils implements TestUtils {
final FileSystem fs;

SelfTestUtils([FileSystem? fs]) : fs = fs ?? fileSystem;

@override
void createDirectory(String path) {
fs.createDirectory(path);
}

@override
String createTestDirectory(String testName) =>
fs.createTemporaryDirectory(prefix: testName);

@override
void createTextFile(String path, String s) {
fs.writeAsString(path, s);
}

@override
void deleteDirectoryTree(String path) {
fs.removeDirectoryTree(path);
}

@override
bool isDir(String path) => fs.metadata(path).isDirectory;
}

TestUtils testUtils() => SelfTestUtils();