// 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.

@TestOn('vm')
import 'dart:async';
import 'dart:convert';
import 'dart:io' as io;

import 'package:file/file.dart';
import 'package:file_testing/file_testing.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import 'package:test/test.dart' as testpkg show group, setUp, tearDown, test;

import 'utils.dart';

/// Callback used in [runCommonTests] to produce the root folder in which all
/// file system entities will be created.
typedef RootPathGenerator = String Function();

/// Callback used in [runCommonTests] to create the file system under test.
/// It must return either a [FileSystem] or a [Future] that completes with a
/// [FileSystem].
typedef FileSystemGenerator = dynamic Function();

/// A function to run before tests (passed to [setUp]) or after tests
/// (passed to [tearDown]).
typedef SetUpTearDown = dynamic Function();

/// Runs a suite of tests common to all file system implementations. All file
/// system implementations should run *at least* these tests to ensure
/// compliance with file system API.
///
/// If [root] is specified, its return value will be used as the root folder
/// in which all file system entities will be created. If not specified, the
/// tests will attempt to create entities in the file system root.
///
/// [skip] may be used to skip certain tests (or entire groups of tests) in
/// this suite (to be used, for instance, if a file system implementation is
/// not yet fully complete). The format of each entry in the list is:
/// `$group1Description > $group2Description > ... > $testDescription`.
/// Entries may use regular expression syntax.
///
/// If [replay] is specified, each test (and its setup callbacks) will run
/// twice - once as a "setup" pass with the file system returned by
/// [createFileSystem], and again as the "test" pass with the file system
/// returned by [replay]. This is intended for use with `ReplayFileSystem`,
/// where in order for the file system to behave as expected, a recording of
/// the invocation(s) must first be made.
void runCommonTests(
  FileSystemGenerator createFileSystem, {
  RootPathGenerator? root,
  List<String> skip = const <String>[],
  FileSystemGenerator? replay,
}) {
  RootPathGenerator? rootfn = root;

  group('common', () {
    late FileSystemGenerator createFs;
    late List<SetUpTearDown> setUps;
    late List<SetUpTearDown> tearDowns;
    late FileSystem fs;
    late String root;
    List<String> stack = <String>[];

    void skipIfNecessary(String description, void Function() callback) {
      stack.add(description);
      bool matchesCurrentFrame(String input) =>
          RegExp('^$input\$').hasMatch(stack.join(' > '));
      if (skip.where(matchesCurrentFrame).isEmpty) {
        callback();
      }
      stack.removeLast();
    }

    testpkg.setUp(() async {
      createFs = createFileSystem;
      setUps = <SetUpTearDown>[];
      tearDowns = <SetUpTearDown>[];
    });

    void setUp(FutureOr<void> Function() callback) {
      testpkg.setUp(replay == null ? callback : () => setUps.add(callback));
    }

    void tearDown(FutureOr<void> Function() callback) {
      if (replay == null) {
        testpkg.tearDown(callback);
      } else {
        testpkg.setUp(() => tearDowns.insert(0, callback));
      }
    }

    void group(String description, void Function() body) =>
        skipIfNecessary(description, () => testpkg.group(description, body));

    void test(String description, FutureOr<void> Function() body,
            {dynamic skip}) =>
        skipIfNecessary(description, () {
          if (replay == null) {
            testpkg.test(description, body, skip: skip);
          } else {
            group('rerun', () {
              testpkg.setUp(() async {
                await Future.forEach(setUps, (SetUpTearDown setUp) => setUp());
                await body();
                for (SetUpTearDown tearDown in tearDowns) {
                  await tearDown();
                }
                createFs = replay;
                await Future.forEach(setUps, (SetUpTearDown setUp) => setUp());
              });

              testpkg.test(description, body, skip: skip);

              testpkg.tearDown(() async {
                for (SetUpTearDown tearDown in tearDowns) {
                  await tearDown();
                }
              });
            });
          }
        });

    /// Returns [path] prefixed by the [root] namespace.
    /// This is only intended for absolute paths.
    String ns(String path) {
      p.Context posix = p.Context(style: p.Style.posix);
      List<String> parts = posix.split(path);
      parts[0] = root;
      path = fs.path.joinAll(parts);
      String rootPrefix = fs.path.rootPrefix(path);
      assert(rootPrefix.isNotEmpty);
      String result = root == rootPrefix
          ? path
          : (path == rootPrefix
              ? root
              : fs.path.join(root, fs.path.joinAll(parts.sublist(1))));
      return result;
    }

    setUp(() async {
      root = rootfn != null ? rootfn() : '/';
      fs = await createFs() as FileSystem;
      assert(fs.path.isAbsolute(root));
      assert(!root.endsWith(fs.path.separator) ||
          fs.path.rootPrefix(root) == root);
    });

    group('FileSystem', () {
      group('directory', () {
        test('allowsStringArgument', () {
          expect(fs.directory(ns('/foo')), isDirectory);
        });

        test('allowsUriArgument', () {
          expect(fs.directory(Uri.parse('file:///')), isDirectory);
        });

        test('succeedsWithUriArgument', () {
          fs.directory(ns('/foo')).createSync();
          Uri uri = fs.path.toUri(ns('/foo'));
          expect(fs.directory(uri), exists);
        });

        test('allowsDirectoryArgument', () {
          expect(fs.directory(io.Directory(ns('/foo'))), isDirectory);
        });

        test('disallowsOtherArgumentType', () {
          expect(() => fs.directory(123), throwsArgumentError);
        });

        // Fails due to
        // https://github.com/google/file.dart/issues/112
        test('considersBothSlashesEquivalent', () {
          fs.directory(r'foo\bar_dir').createSync(recursive: true);
          expect(fs.directory(r'foo/bar_dir'), exists);
        }, skip: 'Fails due to https://github.com/google/file.dart/issues/112');
      });

      group('file', () {
        test('allowsStringArgument', () {
          expect(fs.file(ns('/foo')), isFile);
        });

        test('allowsUriArgument', () {
          expect(fs.file(Uri.parse('file:///')), isFile);
        });

        test('succeedsWithUriArgument', () {
          fs.file(ns('/foo')).createSync();
          Uri uri = fs.path.toUri(ns('/foo'));
          expect(fs.file(uri), exists);
        });

        test('allowsDirectoryArgument', () {
          expect(fs.file(io.File(ns('/foo'))), isFile);
        });

        test('disallowsOtherArgumentType', () {
          expect(() => fs.file(123), throwsArgumentError);
        });

        // Fails due to
        // https://github.com/google/file.dart/issues/112
        test('considersBothSlashesEquivalent', () {
          fs.file(r'foo\bar_file').createSync(recursive: true);
          expect(fs.file(r'foo/bar_file'), exists);
        }, skip: 'Fails due to https://github.com/google/file.dart/issues/112');
      });

      group('link', () {
        test('allowsStringArgument', () {
          expect(fs.link(ns('/foo')), isLink);
        });

        test('allowsUriArgument', () {
          expect(fs.link(Uri.parse('file:///')), isLink);
        });

        test('succeedsWithUriArgument', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          Uri uri = fs.path.toUri(ns('/bar'));
          expect(fs.link(uri), exists);
        });

        test('allowsDirectoryArgument', () {
          expect(fs.link(io.File(ns('/foo'))), isLink);
        });

        test('disallowsOtherArgumentType', () {
          expect(() => fs.link(123), throwsArgumentError);
        });
      });

      group('path', () {
        test('hasCorrectCurrentWorkingDirectory', () {
          expect(fs.path.current, fs.currentDirectory.path);
        });

        test('separatorIsAmongExpectedValues', () {
          expect(fs.path.separator, anyOf('/', r'\'));
        });
      });

      group('systemTempDirectory', () {
        test('existsAsDirectory', () {
          Directory tmp = fs.systemTempDirectory;
          expect(tmp, isDirectory);
          expect(tmp, exists);
        });
      });

      group('currentDirectory', () {
        test('defaultsToRoot', () {
          expect(fs.currentDirectory.path, root);
        });

        test('throwsIfSetToNonExistentPath', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.currentDirectory = ns('/foo');
          });
        });

        test('throwsIfHasNonExistentPathInComplexChain', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.currentDirectory = ns('/bar/../foo');
          });
        });

        test('succeedsIfSetToValidStringPath', () {
          fs.directory(ns('/foo')).createSync();
          fs.currentDirectory = ns('/foo');
          expect(fs.currentDirectory.path, ns('/foo'));
        });

        test('succeedsIfSetToValidDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.currentDirectory = io.Directory(ns('/foo'));
          expect(fs.currentDirectory.path, ns('/foo'));
        });

        test('throwsIfArgumentIsNotStringOrDirectory', () {
          expect(() {
            fs.currentDirectory = 123;
          }, throwsArgumentError);
        });

        test('succeedsIfSetToRelativePath', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          fs.currentDirectory = 'foo';
          expect(fs.currentDirectory.path, ns('/foo'));
          fs.currentDirectory = 'bar';
          expect(fs.currentDirectory.path, ns('/foo/bar'));
        });

        test('succeedsIfSetToAbsolutePathWhenCwdIsNotRoot', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          fs.directory(ns('/baz/qux')).createSync(recursive: true);
          fs.currentDirectory = ns('/foo/bar');
          expect(fs.currentDirectory.path, ns('/foo/bar'));
          fs.currentDirectory = fs.directory(ns('/baz/qux'));
          expect(fs.currentDirectory.path, ns('/baz/qux'));
        });

        test('succeedsIfSetToParentDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.currentDirectory = 'foo';
          expect(fs.currentDirectory.path, ns('/foo'));
          fs.currentDirectory = '..';
          expect(fs.currentDirectory.path, ns('/'));
        });

        test('staysAtRootIfSetToParentOfRoot', () {
          fs.currentDirectory =
              List<String>.filled(20, '..').join(fs.path.separator);
          String cwd = fs.currentDirectory.path;
          expect(cwd, fs.path.rootPrefix(cwd));
        });

        test('removesTrailingSlashIfSet', () {
          fs.directory(ns('/foo')).createSync();
          fs.currentDirectory = ns('/foo/');
          expect(fs.currentDirectory.path, ns('/foo'));
        });

        test('throwsIfSetToFilePathSegmentAtTail', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            fs.currentDirectory = ns('/foo');
          });
        });

        test('throwsIfSetToFilePathSegmentViaTraversal', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            fs.currentDirectory = ns('/foo/bar/baz');
          });
        });

        test('resolvesLinksIfEncountered', () {
          fs.link(ns('/foo/bar/baz')).createSync(ns('/qux'), recursive: true);
          fs.directory(ns('/qux')).createSync();
          fs.directory(ns('/quux')).createSync();
          fs.currentDirectory = ns('/foo/bar/baz/../quux/');
          expect(fs.currentDirectory.path, ns('/quux'));
        });

        test('succeedsIfSetToDirectoryLinkAtTail', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.currentDirectory = ns('/bar');
          expect(fs.currentDirectory.path, ns('/foo'));
        });

        test('throwsIfSetToLinkLoop', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(
            anyOf(ErrorCodes.EMLINK, ErrorCodes.ELOOP),
            () {
              fs.currentDirectory = ns('/foo');
            },
          );
        });
      });

      group('stat', () {
        test('isNotFoundForEmptyPath', () {
          FileStat stat = fs.statSync('');
          expect(stat.type, FileSystemEntityType.notFound);
        });

        test('isNotFoundForPathToNonExistentEntityAtTail', () {
          FileStat stat = fs.statSync(ns('/foo'));
          expect(stat.type, FileSystemEntityType.notFound);
        });

        test('isNotFoundForPathToNonExistentEntityInTraversal', () {
          FileStat stat = fs.statSync(ns('/foo/bar'));
          expect(stat.type, FileSystemEntityType.notFound);
        });

        test('isDirectoryForDirectory', () {
          fs.directory(ns('/foo')).createSync();
          FileStat stat = fs.statSync(ns('/foo'));
          expect(stat.type, FileSystemEntityType.directory);
        });

        test('isFileForFile', () {
          fs.file(ns('/foo')).createSync();
          FileStat stat = fs.statSync(ns('/foo'));
          expect(stat.type, FileSystemEntityType.file);
        });

        test('isFileForLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          FileStat stat = fs.statSync(ns('/bar'));
          expect(stat.type, FileSystemEntityType.file);
        });

        test('isNotFoundForLinkWithCircularReference', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/baz'));
          fs.link(ns('/baz')).createSync(ns('/foo'));
          FileStat stat = fs.statSync(ns('/foo'));
          expect(stat.type, FileSystemEntityType.notFound);
        });
      });

      group('identical', () {
        test('isTrueForIdenticalPathsToExistentFile', () {
          fs.file(ns('/foo')).createSync();
          expect(fs.identicalSync(ns('/foo'), ns('/foo')), true);
        });

        test('isFalseForDifferentPathsToDifferentFiles', () {
          fs.file(ns('/foo')).createSync();
          fs.file(ns('/bar')).createSync();
          expect(fs.identicalSync(ns('/foo'), ns('/bar')), false);
        });

        test('isTrueForDifferentPathsToSameFileViaLinkInTraversal', () {
          fs.file(ns('/foo/file')).createSync(recursive: true);
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.identicalSync(ns('/foo/file'), ns('/bar/file')), true);
        });

        test('isFalseForDifferentPathsToSameFileViaLinkAtTail', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.identicalSync(ns('/foo'), ns('/bar')), false);
        });

        test('throwsForDifferentPathsToNonExistentEntities', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.identicalSync(ns('/foo'), ns('/bar'));
          });
        });

        test('throwsForDifferentPathsToOneFileOneNonExistentEntity', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.identicalSync(ns('/foo'), ns('/bar'));
          });
        });
      });

      group('type', () {
        test('isFileForFile', () {
          fs.file(ns('/foo')).createSync();
          FileSystemEntityType type = fs.typeSync(ns('/foo'));
          expect(type, FileSystemEntityType.file);
        });

        test('isDirectoryForDirectory', () {
          fs.directory(ns('/foo')).createSync();
          FileSystemEntityType type = fs.typeSync(ns('/foo'));
          expect(type, FileSystemEntityType.directory);
        });

        test('isDirectoryForAncestorOfRoot', () {
          FileSystemEntityType type = fs
              .typeSync(List<String>.filled(20, '..').join(fs.path.separator));
          expect(type, FileSystemEntityType.directory);
        });

        test('isFileForLinkToFileAndFollowLinksTrue', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          FileSystemEntityType type = fs.typeSync(ns('/bar'));
          expect(type, FileSystemEntityType.file);
        });

        test('isLinkForLinkToFileAndFollowLinksFalse', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          FileSystemEntityType type =
              fs.typeSync(ns('/bar'), followLinks: false);
          expect(type, FileSystemEntityType.link);
        });

        test('isNotFoundForLinkWithCircularReferenceAndFollowLinksTrue', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/baz'));
          fs.link(ns('/baz')).createSync(ns('/foo'));
          FileSystemEntityType type = fs.typeSync(ns('/foo'));
          expect(type, FileSystemEntityType.notFound);
        });

        test('isNotFoundForNoEntityAtTail', () {
          FileSystemEntityType type = fs.typeSync(ns('/foo'));
          expect(type, FileSystemEntityType.notFound);
        });

        test('isNotFoundForNoDirectoryInTraversal', () {
          FileSystemEntityType type = fs.typeSync(ns('/foo/bar/baz'));
          expect(type, FileSystemEntityType.notFound);
        });
      });

      group('isFile/isDirectory/isLink', () {
        late String filePath;
        late String directoryPath;
        late String fileLinkPath;
        late String directoryLinkPath;

        setUp(() {
          filePath = ns('/file');
          directoryPath = ns('/directory');
          fileLinkPath = ns('/file-link');
          directoryLinkPath = ns('/directory-link');

          fs.file(filePath).createSync();
          fs.directory(directoryPath).createSync();
          fs.link(fileLinkPath).createSync(filePath);
          fs.link(directoryLinkPath).createSync(directoryPath);
        });

        test('isFile', () {
          expect(fs.isFileSync(filePath), true);
          expect(fs.isFileSync(directoryPath), false);
          expect(fs.isFileSync(fileLinkPath), true);
          expect(fs.isFileSync(directoryLinkPath), false);
        });

        test('isDirectory', () {
          expect(fs.isDirectorySync(filePath), false);
          expect(fs.isDirectorySync(directoryPath), true);
          expect(fs.isDirectorySync(fileLinkPath), false);
          expect(fs.isDirectorySync(directoryLinkPath), true);
        });

        test('isLink', () {
          expect(fs.isLinkSync(filePath), false);
          expect(fs.isLinkSync(directoryPath), false);
          expect(fs.isLinkSync(fileLinkPath), true);
          expect(fs.isLinkSync(directoryLinkPath), true);
        });
      });
    });

    group('Directory', () {
      test('uri', () {
        expect(fs.directory(ns('/foo')).uri, fs.path.toUri('${ns('/foo')}/'));
        expect(fs.directory('foo').uri.toString(), 'foo/');
      });

      group('exists', () {
        test('falseIfNotExists', () {
          expect(fs.directory(ns('/foo')), isNot(exists));
          expect(fs.directory('foo'), isNot(exists));
          expect(fs.directory(ns('/foo/bar')), isNot(exists));
        });

        test('trueIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expect(fs.directory(ns('/foo')), exists);
          expect(fs.directory('foo'), exists);
        });

        test('falseIfExistsAsFile', () {
          fs.file(ns('/foo')).createSync();
          expect(fs.directory(ns('/foo')), isNot(exists));
          expect(fs.directory('foo'), isNot(exists));
        });

        test('trueIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.directory(ns('/bar')), exists);
          expect(fs.directory('bar'), exists);
        });

        test('falseIfExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.directory(ns('/bar')), isNot(exists));
          expect(fs.directory('bar'), isNot(exists));
        });

        test('falseIfNotFoundSegmentExistsThenIsBackedOut', () {
          fs.directory(ns('/foo')).createSync();
          expect(fs.directory(ns('/bar/../foo')), isNot(exists));
        });
      });

      group('create', () {
        test('returnsCovariantType', () async {
          expect(await fs.directory(ns('/foo')).create(), isDirectory);
        });

        test('succeedsIfAlreadyExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.directory(ns('/foo')).createSync();
        });

        test('throwsIfAlreadyExistsAsFile', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            fs.directory(ns('/foo')).createSync();
          });
        });

        test('succeedsIfAlreadyExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.directory(ns('/bar')).createSync();
        });

        test('throwsIfAlreadyExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          // TODO(tvolkert): Change this to just be 'Not a directory'
          // once Dart 1.22 is stable.
          expectFileSystemException(
            anyOf(ErrorCodes.EEXIST, ErrorCodes.ENOTDIR),
            () {
              fs.directory(ns('/bar')).createSync();
            },
          );
        });

        test('throwsIfAlreadyExistsAsLinkToNotFoundAtTail', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo')).createSync();
          });
        });

        test('throwsIfAlreadyExistsAsLinkToNotFoundViaTraversal', () {
          fs.link(ns('/foo')).createSync(ns('/bar/baz'));
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo')).createSync();
          });
        });

        test('throwsIfAlreadyExistsAsLinkToNotFoundInDifferentDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.directory(ns('/bar')).createSync();
          fs.link(ns('/bar/baz')).createSync(ns('/foo/qux'));
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/bar/baz')).createSync();
          });
        });

        test('succeedsIfTailDoesntExist', () {
          expect(fs.directory(ns('/')), exists);
          fs.directory(ns('/foo')).createSync();
          expect(fs.directory(ns('/foo')), exists);
        });

        test('throwsIfAncestorDoesntExistRecursiveFalse', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo/bar')).createSync();
          });
        });

        test('succeedsIfAncestorDoesntExistRecursiveTrue', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          expect(fs.directory(ns('/foo')), exists);
          expect(fs.directory(ns('/foo/bar')), exists);
        });
      });

      group('rename', () {
        test('returnsCovariantType', () async {
          Directory src() => fs.directory(ns('/foo'))..createSync();
          expect(src().renameSync(ns('/bar')), isDirectory);
          expect(await src().rename(ns('/baz')), isDirectory);
        });

        test('succeedsIfDestinationDoesntExist', () {
          Directory src = fs.directory(ns('/foo'))..createSync();
          Directory dest = src.renameSync(ns('/bar'));
          expect(dest.path, ns('/bar'));
          expect(dest, exists);
        });

        test(
          'succeedsIfDestinationIsEmptyDirectory',
          () {
            fs.directory(ns('/bar')).createSync();
            Directory src = fs.directory(ns('/foo'))..createSync();
            Directory dest = src.renameSync(ns('/bar'));
            expect(src, isNot(exists));
            expect(dest, exists);
          },
          // See https://github.com/google/file.dart/issues/197.
          skip: io.Platform.isWindows,
        );

        test('throwsIfDestinationIsFile', () {
          fs.file(ns('/bar')).createSync();
          Directory src = fs.directory(ns('/foo'))..createSync();
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            src.renameSync(ns('/bar'));
          });
        });

        test('throwsIfDestinationParentFolderDoesntExist', () {
          Directory src = fs.directory(ns('/foo'))..createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            src.renameSync(ns('/bar/baz'));
          });
        });

        test('throwsIfDestinationIsNonEmptyDirectory', () {
          fs.file(ns('/bar/baz')).createSync(recursive: true);
          Directory src = fs.directory(ns('/foo'))..createSync();
          // The error will be 'Directory not empty' on OS X, but it will be
          // 'File exists' on Linux.
          expectFileSystemException(
            anyOf(ErrorCodes.ENOTEMPTY, ErrorCodes.EEXIST),
            () {
              src.renameSync(ns('/bar'));
            },
          );
        });

        test('throwsIfSourceDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo')).renameSync(ns('/bar'));
          });
        });

        test('throwsIfSourceIsFile', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            fs.directory(ns('/foo')).renameSync(ns('/bar'));
          });
        });

        test('succeedsIfSourceIsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.directory(ns('/bar')).renameSync(ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/bar')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/baz')).targetSync(), ns('/foo'));
        });

        test('throwsIfDestinationIsLinkToNotFound', () {
          Directory src = fs.directory(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/baz'));
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            src.renameSync(ns('/bar'));
          });
        });

        test('throwsIfDestinationIsLinkToEmptyDirectory', () {
          Directory src = fs.directory(ns('/foo'))..createSync();
          fs.directory(ns('/bar')).createSync();
          fs.link(ns('/baz')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            src.renameSync(ns('/baz'));
          });
        });

        test('succeedsIfDestinationIsInDifferentDirectory', () {
          Directory src = fs.directory(ns('/foo'))..createSync();
          fs.directory(ns('/bar')).createSync();
          src.renameSync(ns('/bar/baz'));
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar/baz')), FileSystemEntityType.directory);
        });

        test('succeedsIfSourceIsLinkToDifferentDirectory', () {
          fs.directory(ns('/foo/subfoo')).createSync(recursive: true);
          fs.directory(ns('/bar/subbar')).createSync(recursive: true);
          fs.directory(ns('/baz/subbaz')).createSync(recursive: true);
          fs.link(ns('/foo/subfoo/lnk')).createSync(ns('/bar/subbar'));
          fs.directory(ns('/foo/subfoo/lnk')).renameSync(ns('/baz/subbaz/dst'));
          expect(fs.typeSync(ns('/foo/subfoo/lnk')),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/baz/subbaz/dst'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.typeSync(ns('/baz/subbaz/dst'), followLinks: true),
              FileSystemEntityType.directory);
        });
      });

      group('delete', () {
        test('returnsCovariantType', () async {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          expect(await dir.delete(), isDirectory);
        });

        test('succeedsIfEmptyDirectoryExistsAndRecursiveFalse', () {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          dir.deleteSync();
          expect(dir, isNot(exists));
        });

        test('succeedsIfEmptyDirectoryExistsAndRecursiveTrue', () {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          dir.deleteSync(recursive: true);
          expect(dir, isNot(exists));
        });

        test('throwsIfNonEmptyDirectoryExistsAndRecursiveFalse', () {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          fs.file(ns('/foo/bar')).createSync();
          expectFileSystemException(ErrorCodes.ENOTEMPTY, () {
            dir.deleteSync();
          });
        });

        test('succeedsIfNonEmptyDirectoryExistsAndRecursiveTrue', () {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          fs.file(ns('/foo/bar')).createSync();
          dir.deleteSync(recursive: true);
          expect(fs.directory(ns('/foo')), isNot(exists));
          expect(fs.file(ns('/foo/bar')), isNot(exists));
        });

        test('throwsIfDirectoryDoesntExistAndRecursiveFalse', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo')).deleteSync();
          });
        });

        test('throwsIfDirectoryDoesntExistAndRecursiveTrue', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo')).deleteSync(recursive: true);
          });
        });

        test('succeedsIfPathReferencesFileAndRecursiveTrue', () {
          fs.file(ns('/foo')).createSync();
          fs.directory(ns('/foo')).deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
        });

        test('throwsIfPathReferencesFileAndRecursiveFalse', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            fs.directory(ns('/foo')).deleteSync();
          });
        });

        test('succeedsIfPathReferencesLinkToDirectoryAndRecursiveTrue', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.directory(ns('/bar')).deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.notFound);
        });

        test('succeedsIfPathReferencesLinkToDirectoryAndRecursiveFalse', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.directory(ns('/bar')).deleteSync();
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.notFound);
        });

        test('succeedsIfExistsAsLinkToDirectoryInDifferentDirectory', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          fs.link(ns('/baz/qux')).createSync(ns('/foo/bar'), recursive: true);
          fs.directory(ns('/baz/qux')).deleteSync();
          expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
              FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/baz/qux'), followLinks: false),
              FileSystemEntityType.notFound);
        });

        test('succeedsIfPathReferencesLinkToFileAndRecursiveTrue', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.directory(ns('/bar')).deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.notFound);
        });

        test('throwsIfPathReferencesLinkToFileAndRecursiveFalse', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            fs.directory(ns('/bar')).deleteSync();
          });
        });

        test('throwsIfPathReferencesLinkToNotFoundAndRecursiveFalse', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.ENOTDIR, () {
            fs.directory(ns('/foo')).deleteSync();
          });
        });
      });

      group('resolveSymbolicLinks', () {
        test('succeedsForRootDirectory', () {
          expect(fs.directory(ns('/')).resolveSymbolicLinksSync(), ns('/'));
        });

        test('throwsIfPathIsEmpty', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory('').resolveSymbolicLinksSync();
          });
        });

        test('throwsIfLoopInLinkChain', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/baz'));
          fs.link(ns('/baz')).createSync(ns('/foo'));
          expectFileSystemException(
            anyOf(ErrorCodes.EMLINK, ErrorCodes.ELOOP),
            () {
              fs.directory(ns('/foo')).resolveSymbolicLinksSync();
            },
          );
        });

        test('throwsIfPathNotFoundInTraversal', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo/bar')).resolveSymbolicLinksSync();
          });
        });

        test('throwsIfPathNotFoundAtTail', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo')).resolveSymbolicLinksSync();
          });
        });

        test('throwsIfPathNotFoundInMiddleThenBackedOut', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo/baz/../bar')).resolveSymbolicLinksSync();
          });
        });

        test('resolvesRelativePathToCurrentDirectory', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          fs.link(ns('/foo/baz')).createSync(ns('/foo/bar'));
          fs.currentDirectory = ns('/foo');
          expect(
              fs.directory('baz').resolveSymbolicLinksSync(), ns('/foo/bar'));
        });

        test('resolvesAbsolutePathsAbsolutely', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          fs.currentDirectory = ns('/foo');
          expect(fs.directory(ns('/foo/bar')).resolveSymbolicLinksSync(),
              ns('/foo/bar'));
        });

        test('handlesRelativeLinks', () {
          fs.directory(ns('/foo/bar/baz')).createSync(recursive: true);
          fs.link(ns('/foo/qux')).createSync(fs.path.join('bar', 'baz'));
          expect(
            fs.directory(ns('/foo/qux')).resolveSymbolicLinksSync(),
            ns('/foo/bar/baz'),
          );
          expect(
            fs.directory(fs.path.join('foo', 'qux')).resolveSymbolicLinksSync(),
            ns('/foo/bar/baz'),
          );
        });

        test('handlesAbsoluteLinks', () {
          fs.directory(ns('/foo')).createSync();
          fs.directory(ns('/bar/baz/qux')).createSync(recursive: true);
          fs.link(ns('/foo/quux')).createSync(ns('/bar/baz/qux'));
          expect(fs.directory(ns('/foo/quux')).resolveSymbolicLinksSync(),
              ns('/bar/baz/qux'));
        });

        test('handlesLinksWhoseTargetsHaveNestedLinks', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/foo/quuz')).createSync(ns('/bar'));
          fs.link(ns('/foo/grault')).createSync(ns('/baz/quux'));
          fs.directory(ns('/bar')).createSync();
          fs.link(ns('/bar/qux')).createSync(ns('/baz'));
          fs.link(ns('/bar/garply')).createSync(ns('/foo'));
          fs.directory(ns('/baz')).createSync();
          fs.link(ns('/baz/quux')).createSync(ns('/bar/garply/quuz'));
          expect(fs.directory(ns('/foo/grault/qux')).resolveSymbolicLinksSync(),
              ns('/baz'));
        });

        test('handlesParentAndThisFolderReferences', () {
          fs.directory(ns('/foo/bar/baz')).createSync(recursive: true);
          fs.link(ns('/foo/bar/baz/qux')).createSync(fs.path.join('..', '..'));
          String resolved = fs
              .directory(ns('/foo/./bar/baz/../baz/qux/bar'))
              .resolveSymbolicLinksSync();
          expect(resolved, ns('/foo/bar'));
        });

        test('handlesBackToBackSlashesInPath', () {
          fs.directory(ns('/foo/bar/baz')).createSync(recursive: true);
          expect(fs.directory(ns('//foo/bar///baz')).resolveSymbolicLinksSync(),
              ns('/foo/bar/baz'));
        });

        test('handlesComplexPathWithMultipleLinks', () {
          fs
              .link(ns('/foo/bar/baz'))
              .createSync(fs.path.join('..', '..', 'qux'), recursive: true);
          fs.link(ns('/qux')).createSync('quux');
          fs.link(ns('/quux/quuz')).createSync(ns('/foo'), recursive: true);
          String resolved = fs
              .directory(ns('/foo//bar/./baz/quuz/bar/..///bar/baz/'))
              .resolveSymbolicLinksSync();
          expect(resolved, ns('/quux'));
        });
      });

      group('absolute', () {
        test('returnsCovariantType', () {
          expect(fs.directory('foo').absolute, isDirectory);
        });

        test('returnsSamePathIfAlreadyAbsolute', () {
          expect(fs.directory(ns('/foo')).absolute.path, ns('/foo'));
        });

        test('succeedsForRelativePaths', () {
          expect(fs.directory('foo').absolute.path, ns('/foo'));
        });
      });

      group('parent', () {
        late String root;

        setUp(() {
          root = fs.path.style.name == 'windows' ? r'C:\' : '/';
        });

        test('returnsCovariantType', () {
          expect(fs.directory(root).parent, isDirectory);
        });

        test('returnsRootForRoot', () {
          expect(fs.directory(root).parent.path, root);
        });

        test('succeedsForNonRoot', () {
          expect(fs.directory(ns('/foo/bar')).parent.path, ns('/foo'));
        });
      });

      group('createTemp', () {
        test('returnsCovariantType', () {
          expect(fs.directory(ns('/')).createTempSync(), isDirectory);
        });

        test('throwsIfDirectoryDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/foo')).createTempSync();
          });
        });

        test('resolvesNameCollisions', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          Directory tmp = fs.directory(ns('/foo')).createTempSync('bar');
          expect(tmp.path,
              allOf(isNot(ns('/foo/bar')), startsWith(ns('/foo/bar'))));
        });

        test('succeedsWithoutPrefix', () {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          expect(dir.createTempSync().path, startsWith(ns('/foo/')));
        });

        test('succeedsWithPrefix', () {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          expect(dir.createTempSync('bar').path, startsWith(ns('/foo/bar')));
        });

        test('succeedsWithNestedPathPrefixThatExists', () {
          fs.directory(ns('/foo/bar')).createSync(recursive: true);
          Directory tmp = fs.directory(ns('/foo')).createTempSync('bar/baz');
          expect(tmp.path, startsWith(ns('/foo/bar/baz')));
        });

        test('throwsWithNestedPathPrefixThatDoesntExist', () {
          Directory dir = fs.directory(ns('/foo'))..createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            dir.createTempSync('bar/baz');
          });
        });
      });

      group('list', () {
        late Directory dir;

        setUp(() {
          dir = fs.currentDirectory = fs.directory(ns('/foo'))..createSync();
          fs.file('bar').createSync();
          fs.file(fs.path.join('baz', 'qux')).createSync(recursive: true);
          fs.link('quux').createSync(fs.path.join('baz', 'qux'));
          fs
              .link(fs.path.join('baz', 'quuz'))
              .createSync(fs.path.join('..', 'quux'));
          fs.link(fs.path.join('baz', 'grault')).createSync('.');
          fs.currentDirectory = ns('/');
        });

        test('returnsCovariantType', () async {
          void expectIsFileSystemEntity(dynamic entity) {
            expect(entity, isFileSystemEntity);
          }

          dir.listSync().forEach(expectIsFileSystemEntity);
          (await dir.list().toList()).forEach(expectIsFileSystemEntity);
        });

        test('returnsEmptyListForEmptyDirectory', () {
          Directory empty = fs.directory(ns('/bar'))..createSync();
          expect(empty.listSync(), isEmpty);
        });

        test('throwsIfDirectoryDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.directory(ns('/bar')).listSync();
          });
        });

        test('returnsLinkObjectsIfFollowLinksFalse', () {
          List<FileSystemEntity> list = dir.listSync(followLinks: false);
          expect(list, hasLength(3));
          expect(list, contains(allOf(isFile, hasPath(ns('/foo/bar')))));
          expect(list, contains(allOf(isDirectory, hasPath(ns('/foo/baz')))));
          expect(list, contains(allOf(isLink, hasPath(ns('/foo/quux')))));
        });

        test('followsLinksIfFollowLinksTrue', () {
          List<FileSystemEntity> list = dir.listSync();
          expect(list, hasLength(3));
          expect(list, contains(allOf(isFile, hasPath(ns('/foo/bar')))));
          expect(list, contains(allOf(isDirectory, hasPath(ns('/foo/baz')))));
          expect(list, contains(allOf(isFile, hasPath(ns('/foo/quux')))));
        });

        test('returnsLinkObjectsForRecursiveLinkIfFollowLinksTrue', () {
          expect(
            dir.listSync(recursive: true),
            allOf(
              hasLength(9),
              allOf(
                contains(allOf(isFile, hasPath(ns('/foo/bar')))),
                contains(allOf(isFile, hasPath(ns('/foo/quux')))),
                contains(allOf(isFile, hasPath(ns('/foo/baz/qux')))),
                contains(allOf(isFile, hasPath(ns('/foo/baz/quuz')))),
                contains(allOf(isFile, hasPath(ns('/foo/baz/grault/qux')))),
                contains(allOf(isFile, hasPath(ns('/foo/baz/grault/quuz')))),
              ),
              allOf(
                contains(allOf(isDirectory, hasPath(ns('/foo/baz')))),
                contains(allOf(isDirectory, hasPath(ns('/foo/baz/grault')))),
              ),
              contains(allOf(isLink, hasPath(ns('/foo/baz/grault/grault')))),
            ),
          );
        });

        test('recurseIntoDirectoriesIfRecursiveTrueFollowLinksFalse', () {
          expect(
            dir.listSync(recursive: true, followLinks: false),
            allOf(
              hasLength(6),
              contains(allOf(isFile, hasPath(ns('/foo/bar')))),
              contains(allOf(isFile, hasPath(ns('/foo/baz/qux')))),
              contains(allOf(isLink, hasPath(ns('/foo/quux')))),
              contains(allOf(isLink, hasPath(ns('/foo/baz/quuz')))),
              contains(allOf(isLink, hasPath(ns('/foo/baz/grault')))),
              contains(allOf(isDirectory, hasPath(ns('/foo/baz')))),
            ),
          );
        });

        test('childEntriesNotNormalized', () {
          dir = fs.directory(ns('/bar/baz'))..createSync(recursive: true);
          fs.file(ns('/bar/baz/qux')).createSync();
          List<FileSystemEntity> list =
              fs.directory(ns('/bar//../bar/./baz')).listSync();
          expect(list, hasLength(1));
          expect(list[0], allOf(isFile, hasPath(ns('/bar//../bar/./baz/qux'))));
        });

        test('symlinksToNotFoundAlwaysReturnedAsLinks', () {
          dir = fs.directory(ns('/bar'))..createSync();
          fs.link(ns('/bar/baz')).createSync('qux');
          for (bool followLinks in const <bool>[true, false]) {
            List<FileSystemEntity> list =
                dir.listSync(followLinks: followLinks);
            expect(list, hasLength(1));
            expect(list[0], allOf(isLink, hasPath(ns('/bar/baz'))));
          }
        });
      });

      test('childEntities', () {
        Directory dir = fs.directory(ns('/foo'))..createSync();
        dir.childDirectory('bar').createSync();
        dir.childFile('baz').createSync();
        dir.childLink('qux').createSync('bar');
        expect(fs.directory(ns('/foo/bar')), exists);
        expect(fs.file(ns('/foo/baz')), exists);
        expect(fs.link(ns('/foo/qux')), exists);
      });
    });

    group('File', () {
      test('uri', () {
        expect(fs.file(ns('/foo')).uri, fs.path.toUri(ns('/foo')));
        expect(fs.file('foo').uri.toString(), 'foo');
      });

      group('create', () {
        test('returnsCovariantType', () async {
          expect(await fs.file(ns('/foo')).create(), isFile);
        });

        test('succeedsIfTailDoesntAlreadyExist', () {
          fs.file(ns('/foo')).createSync();
          expect(fs.file(ns('/foo')), exists);
        });

        test('succeedsIfAlreadyExistsAsFile', () {
          fs.file(ns('/foo')).createSync();
          fs.file(ns('/foo')).createSync();
          expect(fs.file(ns('/foo')), exists);
        });

        test('throwsIfAncestorDoesntExistRecursiveFalse', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo/bar')).createSync();
          });
        });

        test('succeedsIfAncestorDoesntExistRecursiveTrue', () {
          fs.file(ns('/foo/bar')).createSync(recursive: true);
          expect(fs.file(ns('/foo/bar')), exists);
        });

        test('throwsIfAlreadyExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).createSync();
          });
        });

        test('throwsIfAlreadyExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/bar')).createSync();
          });
        });

        test('succeedsIfAlreadyExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.file(ns('/bar')).createSync();
          expect(fs.file(ns('/bar')), exists);
        });

        test('succeedsIfAlreadyExistsAsLinkToNotFoundAtTail', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          fs.file(ns('/foo')).createSync();
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.file);
        });

        test('throwsIfAlreadyExistsAsLinkToNotFoundViaTraversal', () {
          fs.link(ns('/foo')).createSync(ns('/bar/baz'));
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).createSync();
          });
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).createSync(recursive: true);
          });
        });

        /*
        test('throwsIfPathSegmentIsLinkToNotFoundAndRecursiveTrue', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo/baz')).createSync(recursive: true);
          });
        });
        */

        test('succeedsIfAlreadyExistsAsLinkToNotFoundInDifferentDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.directory(ns('/bar')).createSync();
          fs.link(ns('/bar/baz')).createSync(ns('/foo/qux'));
          fs.file(ns('/bar/baz')).createSync();
          expect(fs.typeSync(ns('/bar/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.typeSync(ns('/foo/qux'), followLinks: false),
              FileSystemEntityType.file);
        });
      });

      group('rename', () {
        test('returnsCovariantType', () async {
          File f() => fs.file(ns('/foo'))..createSync();
          expect(await f().rename(ns('/bar')), isFile);
          expect(f().renameSync(ns('/baz')), isFile);
        });

        test('succeedsIfDestinationDoesntExistAtTail', () {
          File src = fs.file(ns('/foo'))..createSync();
          File dest = src.renameSync(ns('/bar'));
          expect(fs.file(ns('/foo')), isNot(exists));
          expect(fs.file(ns('/bar')), exists);
          expect(dest.path, ns('/bar'));
        });

        test('throwsIfDestinationDoesntExistViaTraversal', () {
          File f = fs.file(ns('/foo'))..createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            f.renameSync(ns('/bar/baz'));
          });
        });

        test('succeedsIfDestinationExistsAsFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.file(ns('/bar')).createSync();
          f.renameSync(ns('/bar'));
          expect(fs.file(ns('/foo')), isNot(exists));
          expect(fs.file(ns('/bar')), exists);
        });

        test('throwsIfDestinationExistsAsDirectory', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.directory(ns('/bar')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            f.renameSync(ns('/bar'));
          });
        });

        test('succeedsIfDestinationExistsAsLinkToFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.file(ns('/bar')).createSync();
          fs.link(ns('/baz')).createSync(ns('/bar'));
          f.renameSync(ns('/baz'));
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.file);
        });

        test('throwsIfDestinationExistsAsLinkToDirectory', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.directory(ns('/bar')).createSync();
          fs.link(ns('/baz')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            f.renameSync(ns('/baz'));
          });
        });

        test('succeedsIfDestinationExistsAsLinkToNotFound', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/baz'));
          f.renameSync(ns('/bar'));
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.file);
        });

        test('throwsIfSourceDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).renameSync(ns('/bar'));
          });
        });

        test('throwsIfSourceExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).renameSync(ns('/bar'));
          });
        });

        test('succeedsIfSourceExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.file(ns('/bar')).renameSync(ns('/baz'));
          expect(fs.typeSync(ns('/bar')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.typeSync(ns('/baz'), followLinks: true),
              FileSystemEntityType.file);
        });

        test('throwsIfSourceExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/bar')).renameSync(ns('/baz'));
          });
        });

        test('throwsIfSourceExistsAsLinkToNotFound', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).renameSync(ns('/baz'));
          });
        });
      });

      group('copy', () {
        test('returnsCovariantType', () async {
          File f() => fs.file(ns('/foo'))..createSync();
          expect(await f().copy(ns('/bar')), isFile);
          expect(f().copySync(ns('/baz')), isFile);
        });

        test('succeedsIfDestinationDoesntExistAtTail', () {
          File f = fs.file(ns('/foo'))
            ..createSync()
            ..writeAsStringSync('foo');
          f.copySync(ns('/bar'));
          expect(fs.file(ns('/foo')), exists);
          expect(fs.file(ns('/bar')), exists);
          expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
        });

        test('throwsIfDestinationDoesntExistViaTraversal', () {
          File f = fs.file(ns('/foo'))..createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            f.copySync(ns('/bar/baz'));
          });
        });

        test('succeedsIfDestinationExistsAsFile', () {
          File f = fs.file(ns('/foo'))
            ..createSync()
            ..writeAsStringSync('foo');
          fs.file(ns('/bar'))
            ..createSync()
            ..writeAsStringSync('bar');
          f.copySync(ns('/bar'));
          expect(fs.file(ns('/foo')), exists);
          expect(fs.file(ns('/bar')), exists);
          expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
          expect(fs.file(ns('/bar')).readAsStringSync(), 'foo');
        });

        test('throwsIfDestinationExistsAsDirectory', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.directory(ns('/bar')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            f.copySync(ns('/bar'));
          });
        });

        test('succeedsIfDestinationExistsAsLinkToFile', () {
          File f = fs.file(ns('/foo'))
            ..createSync()
            ..writeAsStringSync('foo');
          fs.file(ns('/bar'))
            ..createSync()
            ..writeAsStringSync('bar');
          fs.link(ns('/baz')).createSync(ns('/bar'));
          f.copySync(ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
          expect(fs.file(ns('/bar')).readAsStringSync(), 'foo');
        }, skip: io.Platform.isWindows /* No links on Windows */);

        test('throwsIfDestinationExistsAsLinkToDirectory', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.directory(ns('/bar')).createSync();
          fs.link(ns('/baz')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            f.copySync(ns('/baz'));
          });
        });

        test('throwsIfSourceDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).copySync(ns('/bar'));
          });
        });

        test('throwsIfSourceExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).copySync(ns('/bar'));
          });
        });

        test('succeedsIfSourceExistsAsLinkToFile', () {
          fs.file(ns('/foo'))
            ..createSync()
            ..writeAsStringSync('foo');
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.file(ns('/bar')).copySync(ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.file(ns('/foo')).readAsStringSync(), 'foo');
          expect(fs.file(ns('/baz')).readAsStringSync(), 'foo');
        });

        test('succeedsIfDestinationIsInDifferentDirectoryThanSource', () {
          File f = fs.file(ns('/foo/bar'))
            ..createSync(recursive: true)
            ..writeAsStringSync('foo');
          fs.directory(ns('/baz')).createSync();
          f.copySync(ns('/baz/qux'));
          expect(fs.file(ns('/foo/bar')), exists);
          expect(fs.file(ns('/baz/qux')), exists);
          expect(fs.file(ns('/foo/bar')).readAsStringSync(), 'foo');
          expect(fs.file(ns('/baz/qux')).readAsStringSync(), 'foo');
        });

        test('succeedsIfSourceIsLinkToFileInDifferentDirectory', () {
          fs.file(ns('/foo/bar'))
            ..createSync(recursive: true)
            ..writeAsStringSync('foo');
          fs.link(ns('/baz/qux')).createSync(ns('/foo/bar'), recursive: true);
          fs.file(ns('/baz/qux')).copySync(ns('/baz/quux'));
          expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/baz/qux'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.typeSync(ns('/baz/quux'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.file(ns('/foo/bar')).readAsStringSync(), 'foo');
          expect(fs.file(ns('/baz/quux')).readAsStringSync(), 'foo');
        });

        test('succeedsIfDestinationIsLinkToFileInDifferentDirectory', () {
          fs.file(ns('/foo/bar'))
            ..createSync(recursive: true)
            ..writeAsStringSync('bar');
          fs.file(ns('/baz/qux'))
            ..createSync(recursive: true)
            ..writeAsStringSync('qux');
          fs.link(ns('/baz/quux')).createSync(ns('/foo/bar'));
          fs.file(ns('/baz/qux')).copySync(ns('/baz/quux'));
          expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/baz/qux'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/baz/quux'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.file(ns('/foo/bar')).readAsStringSync(), 'qux');
          expect(fs.file(ns('/baz/qux')).readAsStringSync(), 'qux');
        }, skip: io.Platform.isWindows /* No links on Windows */);
      });

      group('length', () {
        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).lengthSync();
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).lengthSync();
          });
        });

        test('returnsZeroForNewlyCreatedFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          expect(f.lengthSync(), 0);
        });

        test('writeNBytesReturnsLengthN', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsBytesSync(<int>[1, 2, 3, 4], flush: true);
          expect(f.lengthSync(), 4);
        });

        test('succeedsIfExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.file(ns('/bar')).lengthSync(), 0);
        });
      });

      group('absolute', () {
        test('returnsSamePathIfAlreadyAbsolute', () {
          expect(fs.file(ns('/foo')).absolute.path, ns('/foo'));
        });

        test('succeedsForRelativePaths', () {
          expect(fs.file('foo').absolute.path, ns('/foo'));
        });
      });

      group('lastAccessed', () {
        test('isNowForNewlyCreatedFile', () {
          DateTime before = downstairs();
          File f = fs.file(ns('/foo'))..createSync();
          DateTime after = ceil();
          DateTime accessed = f.lastAccessedSync();
          expect(accessed, isSameOrAfter(before));
          expect(accessed, isSameOrBefore(after));
        });

        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).lastAccessedSync();
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).lastAccessedSync();
          });
        });

        test('succeedsIfExistsAsLinkToFile', () {
          DateTime before = downstairs();
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          DateTime after = ceil();
          DateTime accessed = fs.file(ns('/bar')).lastAccessedSync();
          expect(accessed, isSameOrAfter(before));
          expect(accessed, isSameOrBefore(after));
        });
      });

      group('setLastAccessed', () {
        final DateTime time = DateTime(1999);

        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).setLastAccessedSync(time);
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).setLastAccessedSync(time);
          });
        });

        test('succeedsIfExistsAsFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.setLastAccessedSync(time);
          expect(fs.file(ns('/foo')).lastAccessedSync(), time);
        });

        test('succeedsIfExistsAsLinkToFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          f.setLastAccessedSync(time);
          expect(fs.file(ns('/bar')).lastAccessedSync(), time);
        });
      });

      group('lastModified', () {
        test('isNowForNewlyCreatedFile', () {
          DateTime before = downstairs();
          File f = fs.file(ns('/foo'))..createSync();
          DateTime after = ceil();
          DateTime modified = f.lastModifiedSync();
          expect(modified, isSameOrAfter(before));
          expect(modified, isSameOrBefore(after));
        });

        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).lastModifiedSync();
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).lastModifiedSync();
          });
        });

        test('succeedsIfExistsAsLinkToFile', () {
          DateTime before = downstairs();
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          DateTime after = ceil();
          DateTime modified = fs.file(ns('/bar')).lastModifiedSync();
          expect(modified, isSameOrAfter(before));
          expect(modified, isSameOrBefore(after));
        });
      });

      group('setLastModified', () {
        final DateTime time = DateTime(1999);

        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).setLastModifiedSync(time);
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).setLastModifiedSync(time);
          });
        });

        test('succeedsIfExistsAsFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.setLastModifiedSync(time);
          expect(fs.file(ns('/foo')).lastModifiedSync(), time);
        });

        test('succeedsIfExistsAsLinkToFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          f.setLastModifiedSync(time);
          expect(fs.file(ns('/bar')).lastModifiedSync(), time);
        });
      });

      group('open', () {
        void testIfDoesntExistAtTail(FileMode mode) {
          if (mode == FileMode.read) {
            test('throwsIfDoesntExistAtTail', () {
              expectFileSystemException(ErrorCodes.ENOENT, () {
                fs.file(ns('/bar')).openSync(mode: mode);
              });
            });
          } else {
            test('createsFileIfDoesntExistAtTail', () {
              RandomAccessFile raf = fs.file(ns('/bar')).openSync(mode: mode);
              raf.closeSync();
              expect(fs.file(ns('/bar')), exists);
            });
          }
        }

        void testThrowsIfDoesntExistViaTraversal(FileMode mode) {
          test('throwsIfDoesntExistViaTraversal', () {
            expectFileSystemException(ErrorCodes.ENOENT, () {
              fs.file(ns('/bar/baz')).openSync(mode: mode);
            });
          });
        }

        void testRandomAccessFileOperations(FileMode mode) {
          group('RandomAccessFile', () {
            late File f;
            late RandomAccessFile raf;

            setUp(() {
              f = fs.file(ns('/foo'))..createSync();
              f.writeAsStringSync('pre-existing content\n', flush: true);
              raf = f.openSync(mode: mode);
            });

            tearDown(() {
              try {
                raf.closeSync();
              } on FileSystemException {
                // Ignore; a test may have already closed it.
              }
            });

            test('succeedsIfClosedAfterClosed', () {
              raf.closeSync();
              expectFileSystemException(null, () {
                raf.closeSync();
              });
            });

            test('throwsIfReadAfterClose', () {
              raf.closeSync();
              expectFileSystemException(null, () {
                raf.readByteSync();
              });
            });

            test('throwsIfWriteAfterClose', () {
              raf.closeSync();
              expectFileSystemException(null, () {
                raf.writeByteSync(0xBAD);
              });
            });

            test('throwsIfTruncateAfterClose', () {
              raf.closeSync();
              expectFileSystemException(null, () {
                raf.truncateSync(0);
              });
            });

            if (mode == FileMode.write || mode == FileMode.writeOnly) {
              test('lengthIsResetToZeroIfOpened', () {
                expect(raf.lengthSync(), equals(0));
              });

              test('throwsIfAsyncUnawaited', () async {
                try {
                  final Future<void> future = raf.flush();
                  expectFileSystemException(null, () => raf.flush());
                  expectFileSystemException(null, () => raf.flushSync());
                  await expectLater(future, completes);
                  raf.flushSync();
                } finally {
                  raf.closeSync();
                }
              });
            } else {
              test('lengthIsNotModifiedIfOpened', () {
                expect(raf.lengthSync(), isNot(equals(0)));
              });
            }

            if (mode == FileMode.writeOnly ||
                mode == FileMode.writeOnlyAppend) {
              test('throwsIfReadByte', () {
                expectFileSystemException(ErrorCodes.EBADF, () {
                  raf.readByteSync();
                });
              });

              test('throwsIfRead', () {
                expectFileSystemException(ErrorCodes.EBADF, () {
                  raf.readSync(2);
                });
              });

              test('throwsIfReadInto', () {
                expectFileSystemException(ErrorCodes.EBADF, () {
                  raf.readIntoSync(List<int>.filled(5, 0));
                });
              });
            } else {
              group('read', () {
                setUp(() {
                  if (mode == FileMode.write) {
                    // Write data back that we truncated when opening the file.
                    raf.writeStringSync('pre-existing content\n');
                  }
                  // Reset the position to zero so we can read the content.
                  raf.setPositionSync(0);
                });

                test('readByte', () {
                  expect(utf8.decode(<int>[raf.readByteSync()]), 'p');
                });

                test('read', () {
                  List<int> bytes = raf.readSync(1024);
                  expect(bytes.length, 21);
                  expect(utf8.decode(bytes), 'pre-existing content\n');
                });

                test('readIntoWithBufferLargerThanContent', () {
                  List<int> buffer = List<int>.filled(1024, 0);
                  int numRead = raf.readIntoSync(buffer);
                  expect(numRead, 21);
                  expect(utf8.decode(buffer.sublist(0, 21)),
                      'pre-existing content\n');
                });

                test('readIntoWithBufferSmallerThanContent', () {
                  List<int> buffer = List<int>.filled(10, 0);
                  int numRead = raf.readIntoSync(buffer);
                  expect(numRead, 10);
                  expect(utf8.decode(buffer), 'pre-existi');
                });

                test('readIntoWithStart', () {
                  List<int> buffer = List<int>.filled(10, 0);
                  int numRead = raf.readIntoSync(buffer, 2);
                  expect(numRead, 8);
                  expect(utf8.decode(buffer.sublist(2)), 'pre-exis');
                });

                test('readIntoWithStartAndEnd', () {
                  List<int> buffer = List<int>.filled(10, 0);
                  int numRead = raf.readIntoSync(buffer, 2, 5);
                  expect(numRead, 3);
                  expect(utf8.decode(buffer.sublist(2, 5)), 'pre');
                });

                test('openReadHandleDoesNotChange', () {
                  final String initial = utf8.decode(raf.readSync(4));
                  expect(initial, 'pre-');
                  final File newFile = f.renameSync(ns('/bar'));
                  String rest = utf8.decode(raf.readSync(1024));
                  expect(rest, 'existing content\n');

                  assert(newFile.path != f.path);
                  expect(f, isNot(exists));
                  expect(newFile, exists);

                  // [RandomAccessFile.path] always returns the original path.
                  expect(raf.path, f.path);
                });
              });
            }

            if (mode == FileMode.read) {
              test('throwsIfWriteByte', () {
                expectFileSystemException(ErrorCodes.EBADF, () {
                  raf.writeByteSync(0xBAD);
                });
              });

              test('throwsIfWriteFrom', () {
                expectFileSystemException(ErrorCodes.EBADF, () {
                  raf.writeFromSync(<int>[1, 2, 3, 4]);
                });
              });

              test('throwsIfWriteString', () {
                expectFileSystemException(ErrorCodes.EBADF, () {
                  raf.writeStringSync('This should throw.');
                });
              });
            } else {
              test('lengthGrowsAsDataIsWritten', () {
                int lengthBefore = f.lengthSync();
                raf.writeByteSync(0xFACE);
                expect(raf.lengthSync(), lengthBefore + 1);
              });

              test('flush', () {
                int lengthBefore = f.lengthSync();
                raf.writeByteSync(0xFACE);
                raf.flushSync();
                expect(f.lengthSync(), lengthBefore + 1);
              });

              test('writeByte', () {
                raf.writeByteSync(utf8.encode('A').first);
                raf.flushSync();
                if (mode == FileMode.write || mode == FileMode.writeOnly) {
                  expect(f.readAsStringSync(), 'A');
                } else {
                  expect(f.readAsStringSync(), 'pre-existing content\nA');
                }
              });

              test('writeFrom', () {
                raf.writeFromSync(utf8.encode('Hello world'));
                raf.flushSync();
                if (mode == FileMode.write || mode == FileMode.writeOnly) {
                  expect(f.readAsStringSync(), 'Hello world');
                } else {
                  expect(f.readAsStringSync(),
                      'pre-existing content\nHello world');
                }
              });

              test('writeFromWithStart', () {
                raf.writeFromSync(utf8.encode('Hello world'), 2);
                raf.flushSync();
                if (mode == FileMode.write || mode == FileMode.writeOnly) {
                  expect(f.readAsStringSync(), 'llo world');
                } else {
                  expect(
                      f.readAsStringSync(), 'pre-existing content\nllo world');
                }
              });

              test('writeFromWithStartAndEnd', () {
                raf.writeFromSync(utf8.encode('Hello world'), 2, 5);
                raf.flushSync();
                if (mode == FileMode.write || mode == FileMode.writeOnly) {
                  expect(f.readAsStringSync(), 'llo');
                } else {
                  expect(f.readAsStringSync(), 'pre-existing content\nllo');
                }
              });

              test('writeString', () {
                raf.writeStringSync('Hello world');
                raf.flushSync();
                if (mode == FileMode.write || mode == FileMode.writeOnly) {
                  expect(f.readAsStringSync(), 'Hello world');
                } else {
                  expect(f.readAsStringSync(),
                      'pre-existing content\nHello world');
                }
              });

              test('openWriteHandleDoesNotChange', () {
                raf.writeStringSync('Hello ');
                final File newFile = f.renameSync(ns('/bar'));
                raf.writeStringSync('world');

                final String contents = newFile.readAsStringSync();
                if (mode == FileMode.write || mode == FileMode.writeOnly) {
                  expect(contents, 'Hello world');
                } else {
                  expect(contents, 'pre-existing content\nHello world');
                }

                assert(newFile.path != f.path);
                expect(f, isNot(exists));
                expect(newFile, exists);

                // [RandomAccessFile.path] always returns the original path.
                expect(raf.path, f.path);
              });
            }

            if (mode == FileMode.append || mode == FileMode.writeOnlyAppend) {
              test('positionInitializedToEndOfFile', () {
                expect(raf.positionSync(), 21);
              });
            } else {
              test('positionInitializedToZero', () {
                expect(raf.positionSync(), 0);
              });
            }

            group('position', () {
              setUp(() {
                if (mode == FileMode.write || mode == FileMode.writeOnly) {
                  // Write data back that we truncated when opening the file.
                  raf.writeStringSync('pre-existing content\n');
                }
              });

              if (mode != FileMode.writeOnly &&
                  mode != FileMode.writeOnlyAppend) {
                test('growsAfterRead', () {
                  raf.setPositionSync(0);
                  raf.readSync(10);
                  expect(raf.positionSync(), 10);
                });

                test('affectsRead', () {
                  raf.setPositionSync(5);
                  expect(utf8.decode(raf.readSync(5)), 'xisti');
                });
              }

              if (mode == FileMode.read) {
                test('succeedsIfSetPastEndOfFile', () {
                  raf.setPositionSync(32);
                  expect(raf.positionSync(), 32);
                });
              } else {
                test('growsAfterWrite', () {
                  int positionBefore = raf.positionSync();
                  raf.writeStringSync('Hello world');
                  expect(raf.positionSync(), positionBefore + 11);
                });

                test('affectsWrite', () {
                  raf.setPositionSync(5);
                  raf.writeStringSync('-yo-');
                  raf.flushSync();
                  expect(f.readAsStringSync(), 'pre-e-yo-ing content\n');
                });

                test('succeedsIfSetAndWrittenPastEndOfFile', () {
                  raf.setPositionSync(32);
                  expect(raf.positionSync(), 32);
                  raf.writeStringSync('here');
                  raf.flushSync();
                  List<int> bytes = f.readAsBytesSync();
                  expect(bytes.length, 36);
                  expect(utf8.decode(bytes.sublist(0, 21)),
                      'pre-existing content\n');
                  expect(utf8.decode(bytes.sublist(32, 36)), 'here');
                  expect(bytes.sublist(21, 32), everyElement(0));
                });
              }

              test('throwsIfSetToNegativeNumber', () {
                expectFileSystemException(ErrorCodes.EINVAL, () {
                  raf.setPositionSync(-12);
                });
              });
            });

            if (mode == FileMode.read) {
              test('throwsIfTruncate', () {
                expectFileSystemException(ErrorCodes.EINVAL, () {
                  raf.truncateSync(5);
                });
              });
            } else {
              group('truncate', () {
                setUp(() {
                  if (mode == FileMode.write || mode == FileMode.writeOnly) {
                    // Write data back that we truncated when opening the file.
                    raf.writeStringSync('pre-existing content\n');
                  }
                });

                test('succeedsIfSetWithinRangeOfContent', () {
                  raf.truncateSync(5);
                  raf.flushSync();
                  expect(f.lengthSync(), 5);
                  expect(f.readAsStringSync(), 'pre-e');
                });

                test('succeedsIfSetToZero', () {
                  raf.truncateSync(0);
                  raf.flushSync();
                  expect(f.lengthSync(), 0);
                  expect(f.readAsStringSync(), isEmpty);
                });

                test('throwsIfSetToNegativeNumber', () {
                  expectFileSystemException(ErrorCodes.EINVAL, () {
                    raf.truncateSync(-2);
                  });
                });

                test('extendsFileIfSetPastEndOfFile', () {
                  raf.truncateSync(32);
                  raf.flushSync();
                  List<int> bytes = f.readAsBytesSync();
                  expect(bytes.length, 32);
                  expect(utf8.decode(bytes.sublist(0, 21)),
                      'pre-existing content\n');
                  expect(bytes.sublist(21, 32), everyElement(0));
                });
              });
            }
          });
        }

        void testOpenWithMode(FileMode mode) {
          testIfDoesntExistAtTail(mode);
          testThrowsIfDoesntExistViaTraversal(mode);
          testRandomAccessFileOperations(mode);
        }

        group('READ', () => testOpenWithMode(FileMode.read));
        group('WRITE', () => testOpenWithMode(FileMode.write));
        group('APPEND', () => testOpenWithMode(FileMode.append));
        group('WRITE_ONLY', () => testOpenWithMode(FileMode.writeOnly));
        group('WRITE_ONLY_APPEND',
            () => testOpenWithMode(FileMode.writeOnlyAppend));
      });

      group('openRead', () {
        test('throwsIfDoesntExist', () {
          Stream<List<int>> stream = fs.file(ns('/foo')).openRead();
          expect(stream.drain<void>(),
              throwsFileSystemException(ErrorCodes.ENOENT));
        });

        test('succeedsIfExistsAsFile', () async {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello world', flush: true);
          Stream<List<int>> stream = f.openRead();
          List<List<int>> data = await stream.toList();
          expect(data, hasLength(1));
          expect(utf8.decode(data[0]), 'Hello world');
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          Stream<List<int>> stream = fs.file(ns('/foo')).openRead();
          expect(stream.drain<void>(),
              throwsFileSystemException(ErrorCodes.EISDIR));
        });

        test('succeedsIfExistsAsLinkToFile', () async {
          File f = fs.file(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          f.writeAsStringSync('Hello world', flush: true);
          Stream<List<int>> stream = fs.file(ns('/bar')).openRead();
          List<List<int>> data = await stream.toList();
          expect(data, hasLength(1));
          expect(utf8.decode(data[0]), 'Hello world');
        });

        test('respectsStartAndEndParameters', () async {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello world', flush: true);
          Stream<List<int>> stream = f.openRead(2);
          List<List<int>> data = await stream.toList();
          expect(data, hasLength(1));
          expect(utf8.decode(data[0]), 'llo world');
          stream = f.openRead(2, 5);
          data = await stream.toList();
          expect(data, hasLength(1));
          expect(utf8.decode(data[0]), 'llo');
        });

        test('throwsIfStartParameterIsNegative', () async {
          File f = fs.file(ns('/foo'))..createSync();
          Stream<List<int>> stream = f.openRead(-2);
          expect(stream.drain<void>(), throwsRangeError);
        });

        test('stopsAtEndOfFileIfEndParameterIsPastEndOfFile', () async {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello world', flush: true);
          Stream<List<int>> stream = f.openRead(2, 1024);
          List<List<int>> data = await stream.toList();
          expect(data, hasLength(1));
          expect(utf8.decode(data[0]), 'llo world');
        });

        test('providesSingleSubscriptionStream', () async {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello world', flush: true);
          Stream<List<int>> stream = f.openRead();
          expect(stream.isBroadcast, isFalse);
          await stream.drain<void>();
        });

        test('openReadHandleDoesNotChange', () async {
          // Ideally, `data` should be large enough so that its contents are
          // split across multiple chunks in the [Stream].  However, there
          // doesn't seem to be a good way to determine the chunk size used by
          // [io.File].
          final List<int> data = List<int>.generate(
            1024 * 256,
            (int index) => index & 0xFF,
            growable: false,
          );

          final File f = fs.file(ns('/foo'))..createSync();

          f.writeAsBytesSync(data, flush: true);
          final Stream<List<int>> stream = f.openRead();

          File? newFile;
          List<int>? initialChunk;
          final List<int> remainingChunks = <int>[];

          await for (List<int> chunk in stream) {
            if (initialChunk == null) {
              initialChunk = chunk;
              assert(initialChunk.isNotEmpty);
              expect(initialChunk, data.getRange(0, initialChunk.length));

              newFile = f.renameSync(ns('/bar'));
            } else {
              remainingChunks.addAll(chunk);
            }
          }

          expect(
            remainingChunks,
            data.getRange(initialChunk!.length, data.length),
          );

          assert(newFile?.path != f.path);
          expect(f, isNot(exists));
          expect(newFile, exists);
        });

        test('openReadCompatibleWithUtf8Decoder', () async {
          const content = 'Hello world!';
          File file = fs.file(ns('/foo'))
            ..createSync()
            ..writeAsStringSync(content);
          expect(
            await file
                .openRead()
                .transform(utf8.decoder)
                .transform(const LineSplitter())
                .first,
            content,
          );
        });
      });

      group('openWrite', () {
        test('createsFileIfDoesntExist', () async {
          await fs.file(ns('/foo')).openWrite().close();
          expect(fs.file(ns('/foo')), exists);
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expect(fs.file(ns('/foo')).openWrite().close(),
              throwsFileSystemException(ErrorCodes.EISDIR));
        });

        test('throwsIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.file(ns('/bar')).openWrite().close(),
              throwsFileSystemException(ErrorCodes.EISDIR));
        });

        test('throwsIfModeIsRead', () {
          expect(() => fs.file(ns('/foo')).openWrite(mode: FileMode.read),
              throwsArgumentError);
        });

        test('succeedsIfExistsAsEmptyFile', () async {
          File f = fs.file(ns('/foo'))..createSync();
          IOSink sink = f.openWrite();
          sink.write('Hello world');
          await sink.flush();
          await sink.close();
          expect(f.readAsStringSync(), 'Hello world');
        });

        test('succeedsIfExistsAsLinkToFile', () async {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          IOSink sink = fs.file(ns('/bar')).openWrite();
          sink.write('Hello world');
          await sink.flush();
          await sink.close();
          expect(fs.file(ns('/foo')).readAsStringSync(), 'Hello world');
        });

        test('overwritesContentInWriteMode', () async {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello');
          IOSink sink = f.openWrite();
          sink.write('Goodbye');
          await sink.flush();
          await sink.close();
          expect(fs.file(ns('/foo')).readAsStringSync(), 'Goodbye');
        });

        test('appendsContentInAppendMode', () async {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello');
          IOSink sink = f.openWrite(mode: FileMode.append);
          sink.write('Goodbye');
          await sink.flush();
          await sink.close();
          expect(fs.file(ns('/foo')).readAsStringSync(), 'HelloGoodbye');
        });

        test('openWriteHandleDoesNotChange', () async {
          File f = fs.file(ns('/foo'))..createSync();
          IOSink sink = f.openWrite();
          sink.write('Hello');
          await sink.flush();

          final File newFile = f.renameSync(ns('/bar'));
          sink.write('Goodbye');
          await sink.flush();
          await sink.close();

          expect(newFile.readAsStringSync(), 'HelloGoodbye');

          assert(newFile.path != f.path);
          expect(f, isNot(exists));
          expect(newFile, exists);
        });

        group('ioSink', () {
          late File f;
          late IOSink sink;
          late bool isSinkClosed;

          Future<dynamic> closeSink() {
            Future<dynamic> future = sink.close();
            isSinkClosed = true;
            return future;
          }

          setUp(() {
            f = fs.file(ns('/foo'));
            sink = f.openWrite();
            isSinkClosed = false;
          });

          tearDown(() async {
            if (!isSinkClosed) {
              await closeSink();
            }
          });

          test('throwsIfAddError', () async {
            sink.addError(ArgumentError());
            expect(sink.done, throwsArgumentError);
            isSinkClosed = true;
          });

          test('allowsChangingEncoding', () async {
            sink.encoding = latin1;
            sink.write('ÿ');
            sink.encoding = utf8;
            sink.write('ÿ');
            await sink.flush();
            expect(await f.readAsBytes(), <int>[255, 195, 191]);
          });

          test('succeedsIfAddRawData', () async {
            sink.add(<int>[1, 2, 3, 4]);
            await sink.flush();
            expect(await f.readAsBytes(), <int>[1, 2, 3, 4]);
          });

          test('succeedsIfWrite', () async {
            sink.write('Hello world');
            await sink.flush();
            expect(await f.readAsString(), 'Hello world');
          });

          test('succeedsIfWriteAll', () async {
            sink.writeAll(<String>['foo', 'bar', 'baz'], ' ');
            await sink.flush();
            expect(await f.readAsString(), 'foo bar baz');
          });

          test('succeedsIfWriteCharCode', () async {
            sink.writeCharCode(35);
            await sink.flush();
            expect(await f.readAsString(), '#');
          });

          test('succeedsIfWriteln', () async {
            sink.writeln('Hello world');
            await sink.flush();
            expect(await f.readAsString(), 'Hello world\n');
          });

          test('ignoresDataWrittenAfterClose', () async {
            sink.write('Before close');
            await closeSink();
            expect(() => sink.write('After close'), throwsStateError);
            expect(await f.readAsString(), 'Before close');
          });

          test('ignoresCloseAfterAlreadyClosed', () async {
            sink.write('Hello world');
            Future<dynamic> f1 = closeSink();
            Future<dynamic> f2 = closeSink();
            await Future.wait<dynamic>(<Future<dynamic>>[f1, f2]);
          });

          test('returnsAccurateDoneFuture', () async {
            bool done = false;
            // ignore: unawaited_futures
            sink.done.then((dynamic _) => done = true);
            expect(done, isFalse);
            sink.write('foo');
            expect(done, isFalse);
            await sink.close();
            expect(done, isTrue);
          });

          group('addStream', () {
            late StreamController<List<int>> controller;
            late bool isControllerClosed;

            Future<dynamic> closeController() {
              Future<dynamic> future = controller.close();
              isControllerClosed = true;
              return future;
            }

            setUp(() {
              controller = StreamController<List<int>>();
              isControllerClosed = false;
              sink.addStream(controller.stream);
            });

            tearDown(() async {
              if (!isControllerClosed) {
                await closeController();
              }
            });

            test('succeedsIfStreamProducesData', () async {
              controller.add(<int>[1, 2, 3, 4, 5]);
              await closeController();
              await sink.flush();
              expect(await f.readAsBytes(), <int>[1, 2, 3, 4, 5]);
            });

            test('blocksCallToAddWhileStreamIsActive', () {
              expect(() => sink.add(<int>[1, 2, 3]), throwsStateError);
            });

            test('blocksCallToWriteWhileStreamIsActive', () {
              expect(() => sink.write('foo'), throwsStateError);
            });

            test('blocksCallToWriteAllWhileStreamIsActive', () {
              expect(() => sink.writeAll(<String>['a', 'b']), throwsStateError);
            });

            test('blocksCallToWriteCharCodeWhileStreamIsActive', () {
              expect(() => sink.writeCharCode(35), throwsStateError);
            });

            test('blocksCallToWritelnWhileStreamIsActive', () {
              expect(() => sink.writeln('foo'), throwsStateError);
            });

            test('blocksCallToFlushWhileStreamIsActive', () {
              expect(() => sink.flush(), throwsStateError);
            });
          });
        });
      });

      group('readAsBytes', () {
        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).readAsBytesSync();
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).readAsBytesSync();
          });
        });

        test('throwsIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/bar')).readAsBytesSync();
          });
        });

        test('succeedsIfExistsAsFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsBytesSync(<int>[1, 2, 3, 4]);
          expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
        });

        test('succeedsIfExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.file(ns('/foo')).writeAsBytesSync(<int>[1, 2, 3, 4]);
          expect(fs.file(ns('/bar')).readAsBytesSync(), <int>[1, 2, 3, 4]);
        });

        test('returnsEmptyListForZeroByteFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          expect(f.readAsBytesSync(), isEmpty);
        });

        test('returns a copy, not a view, of the file content', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsBytesSync(<int>[1, 2, 3, 4]);
          List<int> result = f.readAsBytesSync();
          expect(result, <int>[1, 2, 3, 4]);
          result[0] = 10;
          expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
        });
      });

      group('readAsString', () {
        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).readAsStringSync();
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).readAsStringSync();
          });
        });

        test('throwsIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/bar')).readAsStringSync();
          });
        });

        test('succeedsIfExistsAsFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello world');
          expect(f.readAsStringSync(), 'Hello world');
        });

        test('succeedsIfExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.file(ns('/foo')).writeAsStringSync('Hello world');
          expect(fs.file(ns('/bar')).readAsStringSync(), 'Hello world');
        });

        test('returnsEmptyStringForZeroByteFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          expect(f.readAsStringSync(), isEmpty);
        });
      });

      group('readAsLines', () {
        const String testString = 'Hello world\nHow are you?\nI am fine';
        final List<String> expectedLines = <String>[
          'Hello world',
          'How are you?',
          'I am fine',
        ];

        test('throwsIfDoesntExist', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).readAsLinesSync();
          });
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).readAsLinesSync();
          });
        });

        test('throwsIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/bar')).readAsLinesSync();
          });
        });

        test('succeedsIfExistsAsFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync(testString);
          expect(f.readAsLinesSync(), expectedLines);
        });

        test('succeedsIfExistsAsLinkToFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          f.writeAsStringSync(testString);
          expect(f.readAsLinesSync(), expectedLines);
        });

        test('returnsEmptyListForZeroByteFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          expect(f.readAsLinesSync(), isEmpty);
        });

        test('isTrailingNewlineAgnostic', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('$testString\n');
          expect(f.readAsLinesSync(), expectedLines);

          f.writeAsStringSync('\n');
          expect(f.readAsLinesSync(), <String>['']);

          f.writeAsStringSync('\n\n');
          expect(f.readAsLinesSync(), <String>['', '']);
        });
      });

      group('writeAsBytes', () {
        test('returnsCovariantType', () async {
          expect(await fs.file(ns('/foo')).writeAsBytes(<int>[]), isFile);
        });

        test('createsFileIfDoesntExist', () {
          File f = fs.file(ns('/foo'));
          expect(f, isNot(exists));
          f.writeAsBytesSync(<int>[1, 2, 3, 4]);
          expect(f, exists);
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).writeAsBytesSync(<int>[1, 2, 3, 4]);
          });
        });

        test('throwsIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).writeAsBytesSync(<int>[1, 2, 3, 4]);
          });
        });

        test('succeedsIfExistsAsLinkToFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.file(ns('/bar')).writeAsBytesSync(<int>[1, 2, 3, 4]);
          expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
        });

        test('throwsIfFileModeRead', () {
          File f = fs.file(ns('/foo'))..createSync();
          expectFileSystemException(ErrorCodes.EBADF, () {
            f.writeAsBytesSync(<int>[1], mode: FileMode.read);
          });
        });

        test('overwritesContentIfFileModeWrite', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsBytesSync(<int>[1, 2]);
          expect(f.readAsBytesSync(), <int>[1, 2]);
          f.writeAsBytesSync(<int>[3, 4]);
          expect(f.readAsBytesSync(), <int>[3, 4]);
        });

        test('appendsContentIfFileModeAppend', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsBytesSync(<int>[1, 2], mode: FileMode.append);
          expect(f.readAsBytesSync(), <int>[1, 2]);
          f.writeAsBytesSync(<int>[3, 4], mode: FileMode.append);
          expect(f.readAsBytesSync(), <int>[1, 2, 3, 4]);
        });

        test('acceptsEmptyBytesList', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsBytesSync(<int>[]);
          expect(f.readAsBytesSync(), <int>[]);
        });

        test('updatesLastModifiedTime', () async {
          File f = fs.file(ns('/foo'))..createSync();
          DateTime before = f.statSync().modified;
          await Future<void>.delayed(const Duration(seconds: 2));
          f.writeAsBytesSync(<int>[1, 2, 3]);
          DateTime after = f.statSync().modified;
          expect(after, isAfter(before));
        });
      });

      group('writeAsString', () {
        test('returnsCovariantType', () async {
          expect(await fs.file(ns('/foo')).writeAsString('foo'), isFile);
        });

        test('createsFileIfDoesntExist', () {
          File f = fs.file(ns('/foo'));
          expect(f, isNot(exists));
          f.writeAsStringSync('Hello world');
          expect(f, exists);
        });

        test('throwsIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).writeAsStringSync('Hello world');
          });
        });

        test('throwsIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).writeAsStringSync('Hello world');
          });
        });

        test('succeedsIfExistsAsLinkToFile', () {
          File f = fs.file(ns('/foo'))..createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          fs.file(ns('/bar')).writeAsStringSync('Hello world');
          expect(f.readAsStringSync(), 'Hello world');
        });

        test('throwsIfFileModeRead', () {
          File f = fs.file(ns('/foo'))..createSync();
          expectFileSystemException(ErrorCodes.EBADF, () {
            f.writeAsStringSync('Hello world', mode: FileMode.read);
          });
        });

        test('overwritesContentIfFileModeWrite', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello world');
          expect(f.readAsStringSync(), 'Hello world');
          f.writeAsStringSync('Goodbye cruel world');
          expect(f.readAsStringSync(), 'Goodbye cruel world');
        });

        test('appendsContentIfFileModeAppend', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('Hello', mode: FileMode.append);
          expect(f.readAsStringSync(), 'Hello');
          f.writeAsStringSync('Goodbye', mode: FileMode.append);
          expect(f.readAsStringSync(), 'HelloGoodbye');
        });

        test('acceptsEmptyString', () {
          File f = fs.file(ns('/foo'))..createSync();
          f.writeAsStringSync('');
          expect(f.readAsStringSync(), isEmpty);
        });
      });

      group('exists', () {
        test('trueIfExists', () {
          fs.file(ns('/foo')).createSync();
          expect(fs.file(ns('/foo')), exists);
        });

        test('falseIfDoesntExistAtTail', () {
          expect(fs.file(ns('/foo')), isNot(exists));
        });

        test('falseIfDoesntExistViaTraversal', () {
          expect(fs.file(ns('/foo/bar')), isNot(exists));
        });

        test('falseIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expect(fs.file(ns('/foo')), isNot(exists));
        });

        test('falseIfExistsAsLinkToDirectory', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.file(ns('/bar')), isNot(exists));
        });

        test('trueIfExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.file(ns('/bar')), exists);
        });

        test('falseIfNotFoundSegmentExistsThenIsBackedOut', () {
          fs.file(ns('/foo/bar')).createSync(recursive: true);
          expect(fs.directory(ns('/baz/../foo/bar')), isNot(exists));
        });
      });

      group('stat', () {
        test('isNotFoundIfDoesntExistAtTail', () {
          FileStat stat = fs.file(ns('/foo')).statSync();
          expect(stat.type, FileSystemEntityType.notFound);
        });

        test('isNotFoundIfDoesntExistViaTraversal', () {
          FileStat stat = fs.file(ns('/foo/bar')).statSync();
          expect(stat.type, FileSystemEntityType.notFound);
        });

        test('isDirectoryIfExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          FileStat stat = fs.file(ns('/foo')).statSync();
          expect(stat.type, FileSystemEntityType.directory);
        });

        test('isFileIfExistsAsFile', () {
          fs.file(ns('/foo')).createSync();
          FileStat stat = fs.file(ns('/foo')).statSync();
          expect(stat.type, FileSystemEntityType.file);
        });

        test('isFileIfExistsAsLinkToFile', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          FileStat stat = fs.file(ns('/bar')).statSync();
          expect(stat.type, FileSystemEntityType.file);
        });
      });

      group('delete', () {
        test('returnsCovariantType', () async {
          File f = fs.file(ns('/foo'))..createSync();
          expect(await f.delete(), isFile);
        });

        test('succeedsIfExistsAsFile', () {
          fs.file(ns('/foo')).createSync();
          expect(fs.file(ns('/foo')), exists);
          fs.file(ns('/foo')).deleteSync();
          expect(fs.file(ns('/foo')), isNot(exists));
        });

        test('throwsIfDoesntExistAndRecursiveFalse', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).deleteSync();
          });
        });

        test('throwsIfDoesntExistAndRecursiveTrue', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.file(ns('/foo')).deleteSync(recursive: true);
          });
        });

        test('succeedsIfExistsAsDirectoryAndRecursiveTrue', () {
          fs.directory(ns('/foo')).createSync();
          fs.file(ns('/foo')).deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
        });

        test('throwsIfExistsAsDirectoryAndRecursiveFalse', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/foo')).deleteSync();
          });
        });

        test('succeedsIfExistsAsLinkToFileAndRecursiveTrue', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.file(ns('/bar')), exists);
          fs.file(ns('/bar')).deleteSync(recursive: true);
          expect(fs.file(ns('/bar')), isNot(exists));
        });

        test('succeedsIfExistsAsLinkToFileAndRecursiveFalse', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.file(ns('/bar')), exists);
          fs.file(ns('/bar')).deleteSync();
          expect(fs.file(ns('/bar')), isNot(exists));
        });

        test('succeedsIfExistsAsLinkToDirectoryAndRecursiveTrue', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(fs.typeSync(ns('/bar')), FileSystemEntityType.directory);
          fs.file(ns('/bar')).deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/bar')), FileSystemEntityType.notFound);
        });

        test('throwsIfExistsAsLinkToDirectoryAndRecursiveFalse', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.file(ns('/bar')).deleteSync();
          });
        });
      });
    });

    group('Link', () {
      group('uri', () {
        test('whenTargetIsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          Link l = fs.link(ns('/bar'))..createSync(ns('/foo'));
          expect(l.uri, fs.path.toUri(ns('/bar')));
          expect(fs.link('bar').uri.toString(), 'bar');
        });

        test('whenTargetIsFile', () {
          fs.file(ns('/foo')).createSync();
          Link l = fs.link(ns('/bar'))..createSync(ns('/foo'));
          expect(l.uri, fs.path.toUri(ns('/bar')));
          expect(fs.link('bar').uri.toString(), 'bar');
        });

        test('whenLinkDoesntExist', () {
          expect(fs.link(ns('/foo')).uri, fs.path.toUri(ns('/foo')));
          expect(fs.link('foo').uri.toString(), 'foo');
        });
      });

      group('exists', () {
        test('isFalseIfLinkDoesntExistAtTail', () {
          expect(fs.link(ns('/foo')), isNot(exists));
        });

        test('isFalseIfLinkDoesntExistViaTraversal', () {
          expect(fs.link(ns('/foo/bar')), isNot(exists));
        });

        test('isFalseIfPathReferencesFile', () {
          fs.file(ns('/foo')).createSync();
          expect(fs.link(ns('/foo')), isNot(exists));
        });

        test('isFalseIfPathReferencesDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expect(fs.link(ns('/foo')), isNot(exists));
        });

        test('isTrueIfTargetIsNotFound', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          expect(l, exists);
        });

        test('isTrueIfTargetIsFile', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/bar')).createSync();
          expect(l, exists);
        });

        test('isTrueIfTargetIsDirectory', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/bar')).createSync();
          expect(l, exists);
        });

        test('isTrueIfTargetIsLinkLoop', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(l, exists);
        });
      });

      group('stat', () {
        test('isNotFoundIfLinkDoesntExistAtTail', () {
          expect(fs.link(ns('/foo')).statSync().type,
              FileSystemEntityType.notFound);
        });

        test('isNotFoundIfLinkDoesntExistViaTraversal', () {
          expect(fs.link(ns('/foo/bar')).statSync().type,
              FileSystemEntityType.notFound);
        });

        test('isFileIfPathReferencesFile', () {
          fs.file(ns('/foo')).createSync();
          expect(
              fs.link(ns('/foo')).statSync().type, FileSystemEntityType.file);
        });

        test('isDirectoryIfPathReferencesDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expect(fs.link(ns('/foo')).statSync().type,
              FileSystemEntityType.directory);
        });

        test('isNotFoundIfTargetNotFoundAtTail', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          expect(l.statSync().type, FileSystemEntityType.notFound);
        });

        test('isNotFoundIfTargetNotFoundViaTraversal', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar/baz'));
          expect(l.statSync().type, FileSystemEntityType.notFound);
        });

        test('isNotFoundIfTargetIsLinkLoop', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(l.statSync().type, FileSystemEntityType.notFound);
        });

        test('isFileIfTargetIsFile', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/bar')).createSync();
          expect(l.statSync().type, FileSystemEntityType.file);
        });

        test('isDirectoryIfTargetIsDirectory', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/bar')).createSync();
          expect(l.statSync().type, FileSystemEntityType.directory);
        });
      });

      group('delete', () {
        test('returnsCovariantType', () async {
          Link link = fs.link(ns('/foo'))..createSync(ns('/bar'));
          expect(await link.delete(), isLink);
        });

        test('throwsIfLinkDoesntExistAtTail', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo')).deleteSync();
          });
        });

        test('throwsIfLinkDoesntExistViaTraversal', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo/bar')).deleteSync();
          });
        });

        test('throwsIfPathReferencesFileAndRecursiveFalse', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EINVAL, () {
            fs.link(ns('/foo')).deleteSync();
          });
        });

        test('succeedsIfPathReferencesFileAndRecursiveTrue', () {
          fs.file(ns('/foo')).createSync();
          fs.link(ns('/foo')).deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
        });

        test('throwsIfPathReferencesDirectoryAndRecursiveFalse', () {
          fs.directory(ns('/foo')).createSync();
          // TODO(tvolkert): Change this to just be 'Is a directory'
          // once Dart 1.22 is stable.
          expectFileSystemException(
            anyOf(ErrorCodes.EINVAL, ErrorCodes.EISDIR),
            () {
              fs.link(ns('/foo')).deleteSync();
            },
          );
        });

        test('succeedsIfPathReferencesDirectoryAndRecursiveTrue', () {
          fs.directory(ns('/foo')).createSync();
          fs.link(ns('/foo')).deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
        });

        test('unlinksIfTargetIsFileAndRecursiveFalse', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/bar')).createSync();
          l.deleteSync();
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.file);
        });

        test('unlinksIfTargetIsFileAndRecursiveTrue', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/bar')).createSync();
          l.deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.file);
        });

        test('unlinksIfTargetIsDirectoryAndRecursiveFalse', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/bar')).createSync();
          l.deleteSync();
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.directory);
        });

        test('unlinksIfTargetIsDirectoryAndRecursiveTrue', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/bar')).createSync();
          l.deleteSync(recursive: true);
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.directory);
        });

        test('unlinksIfTargetIsLinkLoop', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/foo'));
          l.deleteSync();
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.link);
        });
      });

      group('parent', () {
        test('returnsCovariantType', () {
          expect(fs.link(ns('/foo')).parent, isDirectory);
        });

        test('succeedsIfLinkDoesntExist', () {
          expect(fs.link(ns('/foo')).parent.path, ns('/'));
        });

        test('ignoresLinkTarget', () {
          Link l = fs.link(ns('/foo/bar'))
            ..createSync(ns('/baz/qux'), recursive: true);
          expect(l.parent.path, ns('/foo'));
        });
      });

      group('create', () {
        test('returnsCovariantType', () async {
          expect(await fs.link(ns('/foo')).create(ns('/bar')), isLink);
        });

        test('succeedsIfLinkDoesntExistAtTail', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.link);
          expect(l.targetSync(), ns('/bar'));
        });

        test('throwsIfLinkDoesntExistViaTraversalAndRecursiveFalse', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo/bar')).createSync('baz');
          });
        });

        test('succeedsIfLinkDoesntExistViaTraversalAndRecursiveTrue', () {
          Link l = fs.link(ns('/foo/bar'))..createSync('baz', recursive: true);
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/foo/bar'), followLinks: false),
              FileSystemEntityType.link);
          expect(l.targetSync(), 'baz');
        });

        test('throwsIfAlreadyExistsAsFile', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EEXIST, () {
            fs.link(ns('/foo')).createSync(ns('/bar'));
          });
        });

        test('throwsIfAlreadyExistsAsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EEXIST, () {
            fs.link(ns('/foo')).createSync(ns('/bar'));
          });
        });

        test('throwsIfAlreadyExistsWithSameTarget', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.EEXIST, () {
            fs.link(ns('/foo')).createSync(ns('/bar'));
          });
        });

        test('throwsIfAlreadyExistsWithDifferentTarget', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.EEXIST, () {
            fs.link(ns('/foo')).createSync(ns('/baz'));
          });
        });
      });

      group('update', () {
        test('returnsCovariantType', () async {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          expect(await l.update(ns('/baz')), isLink);
        });

        test('throwsIfLinkDoesntExistAtTail', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo')).updateSync(ns('/bar'));
          });
        });

        test('throwsIfLinkDoesntExistViaTraversal', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo/bar')).updateSync(ns('/baz'));
          });
        });

        test('throwsIfPathReferencesFile', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EINVAL, () {
            fs.link(ns('/foo')).updateSync(ns('/bar'));
          });
        });

        test('throwsIfPathReferencesDirectory', () {
          fs.directory(ns('/foo')).createSync();
          // TODO(tvolkert): Change this to just be 'Is a directory'
          // once Dart 1.22 is stable.
          expectFileSystemException(
            anyOf(ErrorCodes.EINVAL, ErrorCodes.EISDIR),
            () {
              fs.link(ns('/foo')).updateSync(ns('/bar'));
            },
          );
        });

        test('succeedsIfNewTargetSameAsOldTarget', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          fs.link(ns('/foo')).updateSync(ns('/bar'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/foo')).targetSync(), ns('/bar'));
        });

        test('succeedsIfNewTargetDifferentFromOldTarget', () {
          fs.link(ns('/foo')).createSync(ns('/bar'));
          fs.link(ns('/foo')).updateSync(ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/foo')).targetSync(), ns('/baz'));
        });
      });

      group('absolute', () {
        test('returnsCovariantType', () {
          expect(fs.link('foo').absolute, isLink);
        });

        test('returnsSamePathIfAlreadyAbsolute', () {
          expect(fs.link(ns('/foo')).absolute.path, ns('/foo'));
        });

        test('succeedsForRelativePaths', () {
          expect(fs.link('foo').absolute.path, ns('/foo'));
        });
      });

      group('target', () {
        test('throwsIfLinkDoesntExistAtTail', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo')).targetSync();
          });
        });

        test('throwsIfLinkDoesntExistViaTraversal', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo/bar')).targetSync();
          });
        });

        test('throwsIfPathReferencesFile', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo')).targetSync();
          });
        });

        test('throwsIfPathReferencesDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo')).targetSync();
          });
        });

        test('succeedsIfTargetIsNotFound', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          expect(l.targetSync(), ns('/bar'));
        });

        test('succeedsIfTargetIsFile', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/bar')).createSync();
          expect(l.targetSync(), ns('/bar'));
        });

        test('succeedsIfTargetIsDirectory', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/bar')).createSync();
          expect(l.targetSync(), ns('/bar'));
        });

        test('succeedsIfTargetIsLinkLoop', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/foo'));
          expect(l.targetSync(), ns('/bar'));
        });
      });

      group('rename', () {
        test('returnsCovariantType', () async {
          Link l() => fs.link(ns('/foo'))..createSync(ns('/bar'));
          expect(l().renameSync(ns('/bar')), isLink);
          expect(await l().rename(ns('/bar')), isLink);
        });

        test('throwsIfSourceDoesntExistAtTail', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo')).renameSync(ns('/bar'));
          });
        });

        test('throwsIfSourceDoesntExistViaTraversal', () {
          expectFileSystemException(ErrorCodes.ENOENT, () {
            fs.link(ns('/foo/bar')).renameSync(ns('/bar'));
          });
        });

        test('throwsIfSourceIsFile', () {
          fs.file(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EINVAL, () {
            fs.link(ns('/foo')).renameSync(ns('/bar'));
          });
        });

        test('throwsIfSourceIsDirectory', () {
          fs.directory(ns('/foo')).createSync();
          expectFileSystemException(ErrorCodes.EISDIR, () {
            fs.link(ns('/foo')).renameSync(ns('/bar'));
          });
        });

        test('succeedsIfSourceIsLinkToFile', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/bar')).createSync();
          Link renamed = l.renameSync(ns('/baz'));
          expect(renamed.path, ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
        });

        test('succeedsIfSourceIsLinkToNotFound', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          Link renamed = l.renameSync(ns('/baz'));
          expect(renamed.path, ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
        });

        test('succeedsIfSourceIsLinkToDirectory', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/bar')).createSync();
          Link renamed = l.renameSync(ns('/baz'));
          expect(renamed.path, ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
        });

        test('succeedsIfSourceIsLinkLoop', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.link(ns('/bar')).createSync(ns('/foo'));
          Link renamed = l.renameSync(ns('/baz'));
          expect(renamed.path, ns('/baz'));
          expect(fs.typeSync(ns('/foo'), followLinks: false),
              FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/bar'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
        });

        test('succeedsIfDestinationDoesntExistAtTail', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          Link renamed = l.renameSync(ns('/baz'));
          expect(renamed.path, ns('/baz'));
          expect(fs.link(ns('/foo')), isNot(exists));
          expect(fs.link(ns('/baz')), exists);
        });

        test('throwsIfDestinationDoesntExistViaTraversal', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          expectFileSystemException(ErrorCodes.ENOENT, () {
            l.renameSync(ns('/baz/qux'));
          });
        });

        test('throwsIfDestinationExistsAsFile', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/baz')).createSync();
          expectFileSystemException(ErrorCodes.EINVAL, () {
            l.renameSync(ns('/baz'));
          });
        });

        test('throwsIfDestinationExistsAsDirectory', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/baz')).createSync();
          expectFileSystemException(ErrorCodes.EINVAL, () {
            l.renameSync(ns('/baz'));
          });
        });

        test('succeedsIfDestinationExistsAsLinkToFile', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.file(ns('/baz')).createSync();
          fs.link(ns('/qux')).createSync(ns('/baz'));
          l.renameSync(ns('/qux'));
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.file);
          expect(fs.typeSync(ns('/qux'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/qux')).targetSync(), ns('/bar'));
        });

        test('throwsIfDestinationExistsAsLinkToDirectory', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.directory(ns('/baz')).createSync();
          fs.link(ns('/qux')).createSync(ns('/baz'));
          l.renameSync(ns('/qux'));
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.directory);
          expect(fs.typeSync(ns('/qux'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/qux')).targetSync(), ns('/bar'));
        });

        test('succeedsIfDestinationExistsAsLinkToNotFound', () {
          Link l = fs.link(ns('/foo'))..createSync(ns('/bar'));
          fs.link(ns('/baz')).createSync(ns('/qux'));
          l.renameSync(ns('/baz'));
          expect(fs.typeSync(ns('/foo')), FileSystemEntityType.notFound);
          expect(fs.typeSync(ns('/baz'), followLinks: false),
              FileSystemEntityType.link);
          expect(fs.link(ns('/baz')).targetSync(), ns('/bar'));
        });
      });
    });
  });
}
